mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-25 16:22:43 +01:00
Support for config-based custom fields in Maniphest
Test Plan: Add fields to config based on specification on T335. View on Task Edit and Task Detail. Supported types are string, int and select Reviewed By: epriestley Reviewers: epriestley CC: aran, epriestley, hunterbridges Differential Revision: 753
This commit is contained in:
parent
b5ada76ab0
commit
4903038940
16 changed files with 464 additions and 0 deletions
|
@ -427,6 +427,12 @@ return array(
|
|||
|
||||
'maniphest.enabled' => true,
|
||||
|
||||
// Array of custom fields for Maniphest tasks. The array should contain
|
||||
// arrays of field specifications keyed with 'type', 'label', 'caption',
|
||||
// 'required' and whatever specific options exist for the given field
|
||||
// type.
|
||||
'maniphest.custom-fields' => array(),
|
||||
|
||||
// -- Remarkup -------------------------------------------------------------- //
|
||||
|
||||
// If you enable this, linked YouTube videos will be embeded inline. This has
|
||||
|
|
|
@ -267,9 +267,14 @@ phutil_register_library_map(array(
|
|||
'LiskIsolationTestCase' => 'storage/lisk/dao/__tests__',
|
||||
'LiskIsolationTestDAO' => 'storage/lisk/dao/__tests__',
|
||||
'LiskIsolationTestDAOException' => 'storage/lisk/dao/__tests__',
|
||||
'ManiphestAuxiliaryFieldDefaultSpecification' => 'applications/maniphest/auxiliaryfield/default',
|
||||
'ManiphestAuxiliaryFieldSpecification' => 'applications/maniphest/auxiliaryfield/base',
|
||||
'ManiphestAuxiliaryFieldTypeException' => 'applications/maniphest/auxiliaryfield/typeexception',
|
||||
'ManiphestAuxiliaryFieldValidationException' => 'applications/maniphest/auxiliaryfield/validationexception',
|
||||
'ManiphestConstants' => 'applications/maniphest/constants/base',
|
||||
'ManiphestController' => 'applications/maniphest/controller/base',
|
||||
'ManiphestDAO' => 'applications/maniphest/storage/base',
|
||||
'ManiphestDefaultTaskExtensions' => 'applications/maniphest/extensions/task',
|
||||
'ManiphestReplyHandler' => 'applications/maniphest/replyhandler',
|
||||
'ManiphestTask' => 'applications/maniphest/storage/task',
|
||||
'ManiphestTaskAuxiliaryStorage' => 'applications/maniphest/storage/auxiliary',
|
||||
|
@ -832,6 +837,7 @@ phutil_register_library_map(array(
|
|||
'HeraldTranscriptListController' => 'HeraldController',
|
||||
'LiskIsolationTestCase' => 'PhabricatorTestCase',
|
||||
'LiskIsolationTestDAO' => 'LiskDAO',
|
||||
'ManiphestAuxiliaryFieldDefaultSpecification' => 'ManiphestAuxiliaryFieldSpecification',
|
||||
'ManiphestController' => 'PhabricatorController',
|
||||
'ManiphestDAO' => 'PhabricatorLiskDAO',
|
||||
'ManiphestReplyHandler' => 'PhabricatorMailReplyHandler',
|
||||
|
|
|
@ -0,0 +1,86 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* Copyright 2011 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @group maniphest
|
||||
*/
|
||||
abstract class ManiphestAuxiliaryFieldSpecification {
|
||||
|
||||
private $label;
|
||||
private $auxiliaryKey;
|
||||
private $caption;
|
||||
private $value;
|
||||
|
||||
public function setLabel($val) {
|
||||
$this->label = $val;
|
||||
}
|
||||
|
||||
public function getLabel() {
|
||||
return $this->label;
|
||||
}
|
||||
|
||||
public function setAuxiliaryKey($val) {
|
||||
$this->auxiliaryKey = $val;
|
||||
}
|
||||
|
||||
public function getAuxiliaryKey() {
|
||||
return $this->auxiliaryKey;
|
||||
}
|
||||
|
||||
public function setCaption($val) {
|
||||
$this->caption = $val;
|
||||
}
|
||||
|
||||
public function getCaption() {
|
||||
return $this->caption;
|
||||
}
|
||||
|
||||
public function setValue($val) {
|
||||
$this->value = $val;
|
||||
}
|
||||
|
||||
public function getValue() {
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
public function validate() {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
public function isRequired() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public function setType($val)
|
||||
{
|
||||
$this->type = $val;
|
||||
}
|
||||
|
||||
public function getType() {
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
public function renderControl() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public function renderForDetailView() {
|
||||
return phutil_escape_html($this->getValue());
|
||||
}
|
||||
|
||||
}
|
12
src/applications/maniphest/auxiliaryfield/base/__init__.php
Normal file
12
src/applications/maniphest/auxiliaryfield/base/__init__.php
Normal file
|
@ -0,0 +1,12 @@
|
|||
<?php
|
||||
/**
|
||||
* This file is automatically generated. Lint this module to rebuild it.
|
||||
* @generated
|
||||
*/
|
||||
|
||||
|
||||
|
||||
phutil_require_module('phutil', 'markup');
|
||||
|
||||
|
||||
phutil_require_source('ManiphestAuxiliaryFieldSpecification.php');
|
|
@ -0,0 +1,137 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* Copyright 2011 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @group maniphest
|
||||
*/
|
||||
class ManiphestAuxiliaryFieldDefaultSpecification
|
||||
extends ManiphestAuxiliaryFieldSpecification {
|
||||
|
||||
private $required;
|
||||
private $fieldType;
|
||||
|
||||
private $selectOptions;
|
||||
private $error;
|
||||
|
||||
const TYPE_SELECT = 'select';
|
||||
const TYPE_STRING = 'string';
|
||||
const TYPE_INT = 'int';
|
||||
|
||||
public function getFieldType() {
|
||||
return $this->fieldType;
|
||||
}
|
||||
|
||||
public function setFieldType($val) {
|
||||
$this->fieldType = $val;
|
||||
}
|
||||
|
||||
public function getError() {
|
||||
return $this->error;
|
||||
}
|
||||
|
||||
public function setError($val) {
|
||||
$this->error = $val;
|
||||
}
|
||||
|
||||
public function getSelectOptions() {
|
||||
return $this->selectOptions;
|
||||
}
|
||||
|
||||
public function setSelectOptions($array) {
|
||||
$this->selectOptions = $array;
|
||||
}
|
||||
|
||||
public function setRequired($bool) {
|
||||
$this->required = $bool;
|
||||
}
|
||||
|
||||
public function isRequired() {
|
||||
return $this->required;
|
||||
}
|
||||
|
||||
public function renderControl() {
|
||||
$control = null;
|
||||
|
||||
switch ($this->getFieldType()) {
|
||||
case self::TYPE_INT:
|
||||
|
||||
$control = new AphrontFormTextControl();
|
||||
break;
|
||||
|
||||
case self::TYPE_STRING:
|
||||
$control = new AphrontFormTextControl();
|
||||
break;
|
||||
|
||||
case self::TYPE_SELECT:
|
||||
$control = new AphrontFormSelectControl();
|
||||
$control->setOptions($this->getSelectOptions());
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new ManiphestAuxiliaryFieldTypeException(
|
||||
$this->getFieldType().' is not a valid type for '.$this->getLabel()
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
$control->setValue($this->getValue());
|
||||
$control->setLabel($this->getLabel());
|
||||
$control->setName('auxiliary['.$this->getAuxiliaryKey().']');
|
||||
$control->setError($this->getError());
|
||||
|
||||
return $control;
|
||||
}
|
||||
|
||||
public function setValueFromRequest($request) {
|
||||
$aux_post_values = $request->getArr('auxiliary');
|
||||
|
||||
$this->setValue(
|
||||
$aux_post_values[$this->getAuxiliaryKey()]
|
||||
);
|
||||
}
|
||||
|
||||
public function getValueForStorage() {
|
||||
return $this->getValue();
|
||||
}
|
||||
|
||||
public function setValueFromStorage($value) {
|
||||
$this->setValue($value);
|
||||
}
|
||||
|
||||
public function validate() {
|
||||
switch ($this->getFieldType()) {
|
||||
case self::TYPE_INT:
|
||||
if (!is_numeric($this->getValue())) {
|
||||
throw new ManiphestAuxiliaryFieldValidationException(
|
||||
$this->getLabel().' must be an integer value.'
|
||||
);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case self::TYPE_STRING:
|
||||
return true;
|
||||
break;
|
||||
|
||||
case self::TYPE_SELECT:
|
||||
return true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
/**
|
||||
* This file is automatically generated. Lint this module to rebuild it.
|
||||
* @generated
|
||||
*/
|
||||
|
||||
|
||||
|
||||
phutil_require_module('phabricator', 'applications/maniphest/auxiliaryfield/base');
|
||||
phutil_require_module('phabricator', 'applications/maniphest/auxiliaryfield/typeexception');
|
||||
phutil_require_module('phabricator', 'applications/maniphest/auxiliaryfield/validationexception');
|
||||
phutil_require_module('phabricator', 'view/form/control/select');
|
||||
phutil_require_module('phabricator', 'view/form/control/text');
|
||||
|
||||
|
||||
phutil_require_source('ManiphestAuxiliaryFieldDefaultSpecification.php');
|
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* Copyright 2011 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @group maniphest
|
||||
*/
|
||||
class ManiphestAuxiliaryFieldTypeException extends Exception {
|
||||
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
/**
|
||||
* This file is automatically generated. Lint this module to rebuild it.
|
||||
* @generated
|
||||
*/
|
||||
|
||||
|
||||
|
||||
|
||||
phutil_require_source('ManiphestAuxiliaryFieldTypeException.php');
|
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* Copyright 2011 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @group maniphest
|
||||
*/
|
||||
class ManiphestAuxiliaryFieldValidationException extends Exception {
|
||||
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
/**
|
||||
* This file is automatically generated. Lint this module to rebuild it.
|
||||
* @generated
|
||||
*/
|
||||
|
||||
|
||||
|
||||
|
||||
phutil_require_source('ManiphestAuxiliaryFieldValidationException.php');
|
|
@ -41,6 +41,9 @@ class ManiphestTaskDetailController extends ManiphestController {
|
|||
return new Aphront404Response();
|
||||
}
|
||||
|
||||
$aux_fields = id(new ManiphestDefaultTaskExtensions())
|
||||
->getAuxiliaryFieldSpecifications();
|
||||
|
||||
$transactions = id(new ManiphestTransaction())->loadAllWhere(
|
||||
'taskID = %d ORDER BY id ASC',
|
||||
$task->getID());
|
||||
|
@ -112,6 +115,20 @@ class ManiphestTaskDetailController extends ManiphestController {
|
|||
$dict['Projects'] = '<em>None</em>';
|
||||
}
|
||||
|
||||
if ($aux_fields) {
|
||||
foreach ($aux_fields as $aux_field) {
|
||||
$attribute = $task->loadAuxiliaryAttribute(
|
||||
$aux_field->getAuxiliaryKey()
|
||||
);
|
||||
|
||||
if ($attribute) {
|
||||
$aux_field->setValue($attribute->getValue());
|
||||
}
|
||||
|
||||
$dict[$aux_field->getLabel()] = $aux_field->renderForDetailView();
|
||||
}
|
||||
}
|
||||
|
||||
if (idx($attached, PhabricatorPHIDConstants::PHID_TYPE_DREV)) {
|
||||
$revs = idx($attached, PhabricatorPHIDConstants::PHID_TYPE_DREV);
|
||||
$rev_links = array();
|
||||
|
|
|
@ -13,6 +13,7 @@ phutil_require_module('phabricator', 'applications/maniphest/constants/priority'
|
|||
phutil_require_module('phabricator', 'applications/maniphest/constants/status');
|
||||
phutil_require_module('phabricator', 'applications/maniphest/constants/transactiontype');
|
||||
phutil_require_module('phabricator', 'applications/maniphest/controller/base');
|
||||
phutil_require_module('phabricator', 'applications/maniphest/extensions/task');
|
||||
phutil_require_module('phabricator', 'applications/maniphest/storage/task');
|
||||
phutil_require_module('phabricator', 'applications/maniphest/storage/transaction');
|
||||
phutil_require_module('phabricator', 'applications/maniphest/view/transactionlist');
|
||||
|
|
|
@ -75,6 +75,9 @@ class ManiphestTaskEditController extends ManiphestController {
|
|||
$errors = array();
|
||||
$e_title = true;
|
||||
|
||||
$aux_fields = id(new ManiphestDefaultTaskExtensions())
|
||||
->getAuxiliaryFieldSpecifications();
|
||||
|
||||
if ($request->isFormPost()) {
|
||||
|
||||
$changes = array();
|
||||
|
@ -112,6 +115,24 @@ class ManiphestTaskEditController extends ManiphestController {
|
|||
$errors[] = 'Title is required.';
|
||||
}
|
||||
|
||||
foreach ($aux_fields as $aux_field) {
|
||||
$aux_field->setValueFromRequest($request);
|
||||
|
||||
if ($aux_field->isRequired() && !strlen($aux_field->getValue())) {
|
||||
$errors[] = $aux_field->getLabel() . ' is required.';
|
||||
$aux_field->setError('Required');
|
||||
}
|
||||
|
||||
if (strlen($aux_field->getValue())) {
|
||||
try {
|
||||
$aux_field->validate();
|
||||
} catch (Exception $e) {
|
||||
$errors[] = $e->getMessage();
|
||||
$aux_field->setError('Invalid');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($errors) {
|
||||
$task->setPriority($request->getInt('priority'));
|
||||
$task->setOwnerPHID($owner_phid);
|
||||
|
@ -143,6 +164,14 @@ class ManiphestTaskEditController extends ManiphestController {
|
|||
$changes[ManiphestTransactionType::TYPE_PROJECTS] = $new_proj_arr;
|
||||
}
|
||||
|
||||
// TODO: Capture auxiliary field changes in a transaction
|
||||
foreach ($aux_fields as $aux_field) {
|
||||
$task->setAuxiliaryAttribute(
|
||||
$aux_field->getAuxiliaryKey(),
|
||||
$aux_field->getValueForStorage()
|
||||
);
|
||||
}
|
||||
|
||||
if ($files) {
|
||||
$file_map = mpull($files, 'getPHID');
|
||||
$file_map = array_fill_keys($file_map, true);
|
||||
|
@ -312,6 +341,30 @@ class ManiphestTaskEditController extends ManiphestController {
|
|||
'Create New Project'))
|
||||
->setDatasource('/typeahead/common/projects/'));
|
||||
|
||||
$attributes = $task->loadAuxiliaryAttributes();
|
||||
$attributes = mpull($attributes, 'getValue', 'getName');
|
||||
|
||||
foreach ($aux_fields as $aux_field)
|
||||
{
|
||||
if (!$request->isFormPost()) {
|
||||
$attribute = null;
|
||||
|
||||
if (isset($attributes[$aux_field->getAuxiliaryKey()])) {
|
||||
$attribute = $attributes[$aux_field->getAuxiliaryKey()];
|
||||
$aux_field->setValueFromStorage($attribute);
|
||||
}
|
||||
}
|
||||
|
||||
if ($aux_field->isRequired() && !$aux_field->getError()
|
||||
&& !$aux_field->getValue()) {
|
||||
$aux_field->setError(true);
|
||||
}
|
||||
|
||||
$aux_control = $aux_field->renderControl();
|
||||
|
||||
$form->appendChild($aux_control);
|
||||
}
|
||||
|
||||
require_celerity_resource('aphront-error-view-css');
|
||||
|
||||
Javelin::initBehavior('maniphest-project-create', array(
|
||||
|
|
|
@ -14,6 +14,7 @@ phutil_require_module('phabricator', 'applications/maniphest/constants/status');
|
|||
phutil_require_module('phabricator', 'applications/maniphest/constants/transactiontype');
|
||||
phutil_require_module('phabricator', 'applications/maniphest/controller/base');
|
||||
phutil_require_module('phabricator', 'applications/maniphest/editor/transaction');
|
||||
phutil_require_module('phabricator', 'applications/maniphest/extensions/task');
|
||||
phutil_require_module('phabricator', 'applications/maniphest/storage/task');
|
||||
phutil_require_module('phabricator', 'applications/maniphest/storage/transaction');
|
||||
phutil_require_module('phabricator', 'applications/phid/constants');
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* Copyright 2011 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @group maniphest
|
||||
*/
|
||||
final class ManiphestDefaultTaskExtensions {
|
||||
|
||||
public function getAuxiliaryFieldSpecifications() {
|
||||
$fields = PhabricatorEnv::getEnvConfig('maniphest.custom-fields');
|
||||
$specs = array();
|
||||
foreach ($fields as $aux => $info) {
|
||||
$spec = new ManiphestAuxiliaryFieldDefaultSpecification();
|
||||
$spec->setAuxiliaryKey($aux);
|
||||
$spec->setLabel(idx($info, 'label'));
|
||||
$spec->setCaption(idx($info, 'caption'));
|
||||
$spec->setFieldType(idx($info, 'type'));
|
||||
$spec->setRequired(idx($info, 'required'));
|
||||
|
||||
if ($spec->getFieldType() ==
|
||||
ManiphestAuxiliaryFieldDefaultSpecification::TYPE_SELECT) {
|
||||
$spec->setSelectOptions(idx($info, 'options'));
|
||||
}
|
||||
|
||||
$specs[] = $spec;
|
||||
}
|
||||
|
||||
return $specs;
|
||||
}
|
||||
|
||||
}
|
15
src/applications/maniphest/extensions/task/__init__.php
Normal file
15
src/applications/maniphest/extensions/task/__init__.php
Normal file
|
@ -0,0 +1,15 @@
|
|||
<?php
|
||||
/**
|
||||
* This file is automatically generated. Lint this module to rebuild it.
|
||||
* @generated
|
||||
*/
|
||||
|
||||
|
||||
|
||||
phutil_require_module('phabricator', 'applications/maniphest/auxiliaryfield/default');
|
||||
phutil_require_module('phabricator', 'infrastructure/env');
|
||||
|
||||
phutil_require_module('phutil', 'utils');
|
||||
|
||||
|
||||
phutil_require_source('ManiphestDefaultTaskExtensions.php');
|
Loading…
Reference in a new issue