diff --git a/resources/sql/autopatches/20150910.owners.custom.1.sql b/resources/sql/autopatches/20150910.owners.custom.1.sql new file mode 100644 --- /dev/null +++ b/resources/sql/autopatches/20150910.owners.custom.1.sql @@ -0,0 +1,25 @@ +CREATE TABLE {$NAMESPACE}_owners.owners_customfieldstorage ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + objectPHID VARBINARY(64) NOT NULL, + fieldIndex BINARY(12) NOT NULL, + fieldValue LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT}, + UNIQUE KEY (objectPHID, fieldIndex) +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; + +CREATE TABLE {$NAMESPACE}_owners.owners_customfieldstringindex ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + objectPHID VARBINARY(64) NOT NULL, + indexKey BINARY(12) NOT NULL, + indexValue LONGTEXT NOT NULL COLLATE {$COLLATE_SORT}, + KEY `key_join` (objectPHID, indexKey, indexValue(64)), + KEY `key_find` (indexKey, indexValue(64)) +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; + +CREATE TABLE {$NAMESPACE}_owners.owners_customfieldnumericindex ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + objectPHID VARBINARY(64) NOT NULL, + indexKey BINARY(12) NOT NULL, + indexValue BIGINT NOT NULL, + KEY `key_join` (objectPHID, indexKey, indexValue), + KEY `key_find` (indexKey, indexValue) +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -2437,7 +2437,12 @@ 'PhabricatorOwnerPathQuery' => 'applications/owners/query/PhabricatorOwnerPathQuery.php', 'PhabricatorOwnersApplication' => 'applications/owners/application/PhabricatorOwnersApplication.php', 'PhabricatorOwnersConfigOptions' => 'applications/owners/config/PhabricatorOwnersConfigOptions.php', + 'PhabricatorOwnersConfiguredCustomField' => 'applications/owners/customfield/PhabricatorOwnersConfiguredCustomField.php', 'PhabricatorOwnersController' => 'applications/owners/controller/PhabricatorOwnersController.php', + 'PhabricatorOwnersCustomField' => 'applications/owners/customfield/PhabricatorOwnersCustomField.php', + 'PhabricatorOwnersCustomFieldNumericIndex' => 'applications/owners/storage/PhabricatorOwnersCustomFieldNumericIndex.php', + 'PhabricatorOwnersCustomFieldStorage' => 'applications/owners/storage/PhabricatorOwnersCustomFieldStorage.php', + 'PhabricatorOwnersCustomFieldStringIndex' => 'applications/owners/storage/PhabricatorOwnersCustomFieldStringIndex.php', 'PhabricatorOwnersDAO' => 'applications/owners/storage/PhabricatorOwnersDAO.php', 'PhabricatorOwnersDetailController' => 'applications/owners/controller/PhabricatorOwnersDetailController.php', 'PhabricatorOwnersEditController' => 'applications/owners/controller/PhabricatorOwnersEditController.php', @@ -6425,7 +6430,15 @@ 'PhabricatorOwnerPathQuery' => 'Phobject', 'PhabricatorOwnersApplication' => 'PhabricatorApplication', 'PhabricatorOwnersConfigOptions' => 'PhabricatorApplicationConfigOptions', + 'PhabricatorOwnersConfiguredCustomField' => array( + 'PhabricatorOwnersCustomField', + 'PhabricatorStandardCustomFieldInterface', + ), 'PhabricatorOwnersController' => 'PhabricatorController', + 'PhabricatorOwnersCustomField' => 'PhabricatorCustomField', + 'PhabricatorOwnersCustomFieldNumericIndex' => 'PhabricatorCustomFieldNumericIndexStorage', + 'PhabricatorOwnersCustomFieldStorage' => 'PhabricatorCustomFieldStorage', + 'PhabricatorOwnersCustomFieldStringIndex' => 'PhabricatorCustomFieldStringIndexStorage', 'PhabricatorOwnersDAO' => 'PhabricatorLiskDAO', 'PhabricatorOwnersDetailController' => 'PhabricatorOwnersController', 'PhabricatorOwnersEditController' => 'PhabricatorOwnersController', @@ -6435,6 +6448,7 @@ 'PhabricatorOwnersDAO', 'PhabricatorPolicyInterface', 'PhabricatorApplicationTransactionInterface', + 'PhabricatorCustomFieldInterface', ), 'PhabricatorOwnersPackageDatasource' => 'PhabricatorTypeaheadDatasource', 'PhabricatorOwnersPackageFunctionDatasource' => 'PhabricatorTypeaheadCompositeDatasource', diff --git a/src/applications/owners/config/PhabricatorOwnersConfigOptions.php b/src/applications/owners/config/PhabricatorOwnersConfigOptions.php --- a/src/applications/owners/config/PhabricatorOwnersConfigOptions.php +++ b/src/applications/owners/config/PhabricatorOwnersConfigOptions.php @@ -20,9 +20,35 @@ } public function getOptions() { + $custom_field_type = 'custom:PhabricatorCustomFieldConfigOptionType'; + $default_fields = array(); + + $field_base_class = id(new PhabricatorOwnersPackage()) + ->getCustomFieldBaseClass(); + + $fields_example = array( + 'mycompany:lore' => array( + 'name' => pht('Package Lore'), + 'type' => 'remarkup', + 'caption' => pht('Tales of adventure for this package.'), + ), + ); + $fields_example = id(new PhutilJSON())->encodeFormatted($fields_example); + return array( $this->newOption('metamta.package.subject-prefix', 'string', '[Package]') ->setDescription(pht('Subject prefix for Owners email.')), + $this->newOption('owners.fields', $custom_field_type, $default_fields) + ->setCustomData($field_base_class) + ->setDescription(pht('Select and reorder package fields.')), + $this->newOption('owners.custom-field-definitions', 'wild', array()) + ->setSummary(pht('Custom Owners fields.')) + ->setDescription( + pht( + 'Map of custom fields for Owners packages. For details on '. + 'adding custom fields to Owners, see "Configuring Custom '. + 'Fields" in the documentation.')) + ->addExample($fields_example, pht('Valid Setting')), ); } diff --git a/src/applications/owners/controller/PhabricatorOwnersDetailController.php b/src/applications/owners/controller/PhabricatorOwnersDetailController.php --- a/src/applications/owners/controller/PhabricatorOwnersDetailController.php +++ b/src/applications/owners/controller/PhabricatorOwnersDetailController.php @@ -37,8 +37,15 @@ $repositories = array(); } + $field_list = PhabricatorCustomField::getObjectFields( + $package, + PhabricatorCustomField::ROLE_VIEW); + $field_list + ->setViewer($viewer) + ->readFieldsFromStorage($package); + $actions = $this->buildPackageActionView($package); - $properties = $this->buildPackagePropertyView($package); + $properties = $this->buildPackagePropertyView($package, $field_list); $properties->setActionList($actions); if ($package->isArchived()) { @@ -156,7 +163,10 @@ } - private function buildPackagePropertyView(PhabricatorOwnersPackage $package) { + private function buildPackagePropertyView( + PhabricatorOwnersPackage $package, + PhabricatorCustomFieldList $field_list) { + $viewer = $this->getViewer(); $view = id(new PHUIPropertyListView()) @@ -187,6 +197,13 @@ $viewer)); } + $view->invokeWillRenderEvent(); + + $field_list->appendFieldsToPropertyList( + $package, + $viewer, + $view); + return $view; } diff --git a/src/applications/owners/controller/PhabricatorOwnersEditController.php b/src/applications/owners/controller/PhabricatorOwnersEditController.php --- a/src/applications/owners/controller/PhabricatorOwnersEditController.php +++ b/src/applications/owners/controller/PhabricatorOwnersEditController.php @@ -36,6 +36,11 @@ $v_description = $package->getDescription(); $v_status = $package->getStatus(); + $field_list = PhabricatorCustomField::getObjectFields( + $package, + PhabricatorCustomField::ROLE_EDIT); + $field_list->setViewer($viewer); + $field_list->readFieldsFromStorage($package); $errors = array(); if ($request->isFormPost()) { @@ -75,6 +80,12 @@ ->setNewValue($v_status); } + $field_xactions = $field_list->buildFieldTransactionsFromRequest( + new PhabricatorOwnersPackageTransaction(), + $request); + + $xactions = array_merge($xactions, $field_xactions); + $editor = id(new PhabricatorOwnersPackageTransactionEditor()) ->setActor($viewer) ->setContentSourceFromRequest($request) @@ -126,41 +137,44 @@ ->setName('owners') ->setValue($v_owners)); - if (!$is_new) { - $form->appendChild( - id(new AphrontFormSelectControl()) - ->setLabel(pht('Status')) - ->setName('status') - ->setValue($v_status) - ->setOptions($package->getStatusNameMap())); - } - + if (!$is_new) { $form->appendChild( id(new AphrontFormSelectControl()) - ->setName('auditing') - ->setLabel(pht('Auditing')) - ->setCaption( - pht( - 'With auditing enabled, all future commits that touch '. - 'this package will be reviewed to make sure an owner '. - 'of the package is involved and the commit message has '. - 'a valid revision, reviewed by, and author.')) - ->setOptions( - array( - 'disabled' => pht('Disabled'), - 'enabled' => pht('Enabled'), - )) - ->setValue(($v_auditing ? 'enabled' : 'disabled'))) + ->setLabel(pht('Status')) + ->setName('status') + ->setValue($v_status) + ->setOptions($package->getStatusNameMap())); + } + + $form->appendChild( + id(new AphrontFormSelectControl()) + ->setName('auditing') + ->setLabel(pht('Auditing')) + ->setCaption( + pht( + 'With auditing enabled, all future commits that touch '. + 'this package will be reviewed to make sure an owner '. + 'of the package is involved and the commit message has '. + 'a valid revision, reviewed by, and author.')) + ->setOptions( + array( + 'disabled' => pht('Disabled'), + 'enabled' => pht('Enabled'), + )) + ->setValue(($v_auditing ? 'enabled' : 'disabled'))) ->appendChild( id(new PhabricatorRemarkupControl()) ->setUser($viewer) ->setLabel(pht('Description')) ->setName('description') - ->setValue($v_description)) - ->appendChild( - id(new AphrontFormSubmitControl()) - ->addCancelButton($cancel_uri) - ->setValue($button_text)); + ->setValue($v_description)); + + $field_list->appendFieldsToForm($form); + + $form->appendChild( + id(new AphrontFormSubmitControl()) + ->addCancelButton($cancel_uri) + ->setValue($button_text)); $form_box = id(new PHUIObjectBoxView()) ->setHeaderText($title) diff --git a/src/applications/owners/customfield/PhabricatorOwnersConfiguredCustomField.php b/src/applications/owners/customfield/PhabricatorOwnersConfiguredCustomField.php new file mode 100644 --- /dev/null +++ b/src/applications/owners/customfield/PhabricatorOwnersConfiguredCustomField.php @@ -0,0 +1,23 @@ +assertAttached($this->customFields); + } + + public function attachCustomFields(PhabricatorCustomFieldAttachment $fields) { + $this->customFields = $fields; + return $this; + } + } diff --git a/src/docs/user/configuration/custom_fields.diviner b/src/docs/user/configuration/custom_fields.diviner --- a/src/docs/user/configuration/custom_fields.diviner +++ b/src/docs/user/configuration/custom_fields.diviner @@ -16,11 +16,12 @@ | Application | Support | |-------------|---------| -| Maniphest | Full Support | -| Projects | Full Support | -| People | Full Support | | Differential | Partial Support | | Diffusion | Limited Support | +| Maniphest | Full Support | +| Owners | Full Support | +| People | Full Support | +| Projects | Full Support | Custom fields can appear in many interfaces and support search, editing, and other features. @@ -38,11 +39,12 @@ | Application | Add Fields | Select Fields | |-------------|------------|---------------| -| Maniphest | `maniphest.custom-field-definitions` | `maniphest.fields` | -| Projects | `projects.custom-field-definitions` | `projects.fields` | -| People | `user.custom-field-definitions` | `user.fields` | | Differential | Planned | `differential.fields` | | Diffusion | Planned | Planned | +| Maniphest | `maniphest.custom-field-definitions` | `maniphest.fields` | +| Owners | `owners.custom-field-definitions` | `owners.fields` | +| People | `user.custom-field-definitions` | `user.fields` | +| Projects | `projects.custom-field-definitions` | `projects.fields` | When adding fields, you'll specify a JSON blob like this (for example, as the value of `maniphest.custom-field-definitions`): @@ -157,11 +159,12 @@ | Application | Extend | |-------------|---------| -| Maniphest | @{class:ManiphestCustomField} | -| Projects | @{class:PhabricatorProjectCustomField} | -| People | @{class:PhabricatorUserCustomField} | | Differential | @{class:DifferentialCustomField} | | Diffusion | @{class:PhabricatorCommitCustomField} | +| Maniphest | @{class:ManiphestCustomField} | +| Owners | @{class:PhabricatorOwnersCustomField} | +| People | @{class:PhabricatorUserCustomField} | +| Projects | @{class:PhabricatorProjectCustomField} | The easiest way to get started is to drop your subclass into `phabricator/src/extensions/`, which should make it immediately available in the