@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Allow Almanac service types to define default properties

Summary:
Ref T5833. This allows Almanac ServiceTypes to define default properties for a service, which show up in the UI and are more easily editable.

Overall, this makes it much easier to make structured/usable/consistent service records: you can check a checkbox that says "prevent new allocations" instead of needing to know the meaning of a key.

Test Plan: {F251593}

Reviewers: chad, btrahan

Reviewed By: btrahan

Subscribers: epriestley

Maniphest Tasks: T5833

Differential Revision: https://secure.phabricator.com/D10996

+356 -72
+2
resources/celerity/map.php
··· 34 34 'rsrc/css/aphront/transaction.css' => '5d0cae25', 35 35 'rsrc/css/aphront/two-column.css' => '16ab3ad2', 36 36 'rsrc/css/aphront/typeahead.css' => 'a989b5b3', 37 + 'rsrc/css/application/almanac/almanac.css' => 'dbb9b3af', 37 38 'rsrc/css/application/auth/auth.css' => '1e655982', 38 39 'rsrc/css/application/base/main-menu-view.css' => '33e5f2f6', 39 40 'rsrc/css/application/base/notification-menu.css' => '6aa0a74b', ··· 497 498 'rsrc/swf/aphlict.swf' => 'f19daffb', 498 499 ), 499 500 'symbols' => array( 501 + 'almanac-css' => 'dbb9b3af', 500 502 'aphront-bars' => '231ac33c', 501 503 'aphront-contextbar-view-css' => '1c3b0529', 502 504 'aphront-dark-console-css' => '6378ef3d',
+2
src/__phutil_library_map__.php
··· 67 67 'AlmanacNetworkViewController' => 'applications/almanac/controller/AlmanacNetworkViewController.php', 68 68 'AlmanacProperty' => 'applications/almanac/storage/AlmanacProperty.php', 69 69 'AlmanacPropertyController' => 'applications/almanac/controller/AlmanacPropertyController.php', 70 + 'AlmanacPropertyDeleteController' => 'applications/almanac/controller/AlmanacPropertyDeleteController.php', 70 71 'AlmanacPropertyEditController' => 'applications/almanac/controller/AlmanacPropertyEditController.php', 71 72 'AlmanacPropertyInterface' => 'applications/almanac/property/AlmanacPropertyInterface.php', 72 73 'AlmanacPropertyQuery' => 'applications/almanac/query/AlmanacPropertyQuery.php', ··· 3092 3093 'PhabricatorPolicyInterface', 3093 3094 ), 3094 3095 'AlmanacPropertyController' => 'AlmanacController', 3096 + 'AlmanacPropertyDeleteController' => 'AlmanacDeviceController', 3095 3097 'AlmanacPropertyEditController' => 'AlmanacDeviceController', 3096 3098 'AlmanacPropertyQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 3097 3099 'AlmanacQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
+2 -1
src/applications/almanac/application/PhabricatorAlmanacApplication.php
··· 57 57 '(?P<id>\d+)/' => 'AlmanacNetworkViewController', 58 58 ), 59 59 'property/' => array( 60 - 'edit/(?:(?P<id>\d+)/)?' => 'AlmanacPropertyEditController', 60 + 'edit/' => 'AlmanacPropertyEditController', 61 + 'delete/' => 'AlmanacPropertyDeleteController', 61 62 ), 62 63 ), 63 64 );
+124 -4
src/applications/almanac/controller/AlmanacController.php
··· 9 9 $viewer = $this->getViewer(); 10 10 $properties = $object->getAlmanacProperties(); 11 11 12 + $this->requireResource('almanac-css'); 13 + 14 + $can_edit = PhabricatorPolicyFilter::hasCapability( 15 + $viewer, 16 + $object, 17 + PhabricatorPolicyCapability::CAN_EDIT); 18 + 19 + $field_list = PhabricatorCustomField::getObjectFields( 20 + $object, 21 + PhabricatorCustomField::ROLE_DEFAULT); 22 + 23 + // Before reading values from the object, read defaults. 24 + $defaults = mpull( 25 + $field_list->getFields(), 26 + 'getValueForStorage', 27 + 'getFieldKey'); 28 + 29 + $field_list 30 + ->setViewer($viewer) 31 + ->readFieldsFromStorage($object); 32 + 33 + Javelin::initBehavior('phabricator-tooltips', array()); 34 + 35 + $icon_builtin = id(new PHUIIconView()) 36 + ->setIconFont('fa-circle') 37 + ->addSigil('has-tooltip') 38 + ->setMetadata( 39 + array( 40 + 'tip' => pht('Builtin Property'), 41 + 'align' => 'E', 42 + )); 43 + 44 + $icon_custom = id(new PHUIIconView()) 45 + ->setIconFont('fa-circle-o grey') 46 + ->addSigil('has-tooltip') 47 + ->setMetadata( 48 + array( 49 + 'tip' => pht('Custom Property'), 50 + 'align' => 'E', 51 + )); 52 + 53 + $builtins = $object->getAlmanacPropertyFieldSpecifications(); 54 + 55 + // Sort fields so builtin fields appear first, then fields are ordered 56 + // alphabetically. 57 + $fields = $field_list->getFields(); 58 + $fields = msort($fields, 'getFieldKey'); 59 + 60 + $head = array(); 61 + $tail = array(); 62 + foreach ($fields as $field) { 63 + $key = $field->getFieldKey(); 64 + if (isset($builtins[$key])) { 65 + $head[$key] = $field; 66 + } else { 67 + $tail[$key] = $field; 68 + } 69 + } 70 + 71 + $fields = $head + $tail; 72 + 12 73 $rows = array(); 13 - foreach ($properties as $property) { 14 - $value = $property->getFieldValue(); 74 + foreach ($fields as $key => $field) { 75 + $value = $field->getValueForStorage(); 76 + 77 + $is_builtin = isset($builtins[$key]); 78 + 79 + $delete_uri = $this->getApplicationURI('property/delete/'); 80 + $delete_uri = id(new PhutilURI($delete_uri)) 81 + ->setQueryParams( 82 + array( 83 + 'objectPHID' => $object->getPHID(), 84 + 'key' => $key, 85 + )); 86 + 87 + $edit_uri = $this->getApplicationURI('property/edit/'); 88 + $edit_uri = id(new PhutilURI($edit_uri)) 89 + ->setQueryParams( 90 + array( 91 + 'objectPHID' => $object->getPHID(), 92 + 'key' => $key, 93 + )); 94 + 95 + $delete = javelin_tag( 96 + 'a', 97 + array( 98 + 'class' => ($can_edit 99 + ? 'button grey small' 100 + : 'button grey small disabled'), 101 + 'sigil' => 'workflow', 102 + 'href' => $delete_uri, 103 + ), 104 + $is_builtin ? pht('Reset') : pht('Delete')); 105 + 106 + $default = idx($defaults, $key); 107 + $is_default = ($default !== null && $default === $value); 108 + 109 + $display_value = PhabricatorConfigJSON::prettyPrintJSON($value); 110 + if ($is_default) { 111 + $display_value = phutil_tag( 112 + 'span', 113 + array( 114 + 'class' => 'almanac-default-property-value', 115 + ), 116 + $display_value); 117 + } 118 + 119 + $display_key = $key; 120 + if ($can_edit) { 121 + $display_key = javelin_tag( 122 + 'a', 123 + array( 124 + 'href' => $edit_uri, 125 + 'sigil' => 'workflow', 126 + ), 127 + $display_key); 128 + } 15 129 16 130 $rows[] = array( 17 - $property->getFieldName(), 18 - PhabricatorConfigJSON::prettyPrintJSON($value), 131 + ($is_builtin ? $icon_builtin : $icon_custom), 132 + $display_key, 133 + $display_value, 134 + $delete, 19 135 ); 20 136 } 21 137 ··· 23 139 ->setNoDataString(pht('No properties.')) 24 140 ->setHeaders( 25 141 array( 142 + null, 26 143 pht('Name'), 27 144 pht('Value'), 145 + null, 28 146 )) 29 147 ->setColumnClasses( 30 148 array( 31 149 null, 150 + null, 32 151 'wide', 152 + 'action', 33 153 )); 34 154 35 155 $phid = $object->getPHID();
-1
src/applications/almanac/controller/AlmanacNetworkViewController.php
··· 11 11 $viewer = $request->getViewer(); 12 12 13 13 $id = $request->getURIData('id'); 14 - 15 14 $network = id(new AlmanacNetworkQuery()) 16 15 ->setViewer($viewer) 17 16 ->withIDs(array($id))
+109
src/applications/almanac/controller/AlmanacPropertyDeleteController.php
··· 1 + <?php 2 + 3 + final class AlmanacPropertyDeleteController 4 + extends AlmanacDeviceController { 5 + 6 + public function handleRequest(AphrontRequest $request) { 7 + $viewer = $request->getViewer(); 8 + 9 + $object = id(new PhabricatorObjectQuery()) 10 + ->setViewer($viewer) 11 + ->withPHIDs(array($request->getStr('objectPHID'))) 12 + ->requireCapabilities( 13 + array( 14 + PhabricatorPolicyCapability::CAN_VIEW, 15 + PhabricatorPolicyCapability::CAN_EDIT, 16 + )) 17 + ->executeOne(); 18 + if (!$object) { 19 + return new Aphront404Response(); 20 + } 21 + 22 + if (!($object instanceof AlmanacPropertyInterface)) { 23 + return new Aphront404Response(); 24 + } 25 + 26 + $key = $request->getStr('key'); 27 + if (!strlen($key)) { 28 + return new Aphront404Response(); 29 + } 30 + 31 + $cancel_uri = $object->getURI(); 32 + 33 + $builtins = $object->getAlmanacPropertyFieldSpecifications(); 34 + $is_builtin = isset($builtins[$key]); 35 + 36 + if ($is_builtin) { 37 + // This is a builtin property, so we're going to reset it to the 38 + // default value. 39 + $field_list = PhabricatorCustomField::getObjectFields( 40 + $object, 41 + PhabricatorCustomField::ROLE_DEFAULT); 42 + 43 + // Note that we're NOT loading field values from the object: we just want 44 + // to get the field's default value so we can reset it. 45 + 46 + $fields = $field_list->getFields(); 47 + $field = $fields[$key]; 48 + 49 + $is_delete = false; 50 + $new_value = $field->getValueForStorage(); 51 + 52 + // Now, load the field to get the old value. 53 + 54 + $field_list 55 + ->setViewer($viewer) 56 + ->readFieldsFromStorage($object); 57 + 58 + $old_value = $field->getValueForStorage(); 59 + 60 + $title = pht('Reset Property'); 61 + $body = pht('Reset this property to its default value?'); 62 + $submit_text = pht('Reset'); 63 + } else { 64 + // This is a custom property, so we're going to delete it outright. 65 + $is_delete = true; 66 + $old_value = $object->getAlmanacPropertyValue($key); 67 + $new_value = null; 68 + 69 + $title = pht('Delete Property'); 70 + $body = pht('Delete this property? TODO: DOES NOT WORK YET'); 71 + $submit_text = pht('Delete'); 72 + } 73 + 74 + $validation_exception = null; 75 + if ($request->isFormPost()) { 76 + $xaction = $object->getApplicationTransactionTemplate() 77 + ->setTransactionType(PhabricatorTransactions::TYPE_CUSTOMFIELD) 78 + ->setMetadataValue('customfield:key', $key) 79 + ->setOldValue($old_value) 80 + ->setNewValue($new_value); 81 + 82 + // TODO: We aren't really deleting properties that we claim to delete 83 + // yet, but that needs to be specialized a little bit. 84 + 85 + $editor = $object->getApplicationTransactionEditor() 86 + ->setActor($viewer) 87 + ->setContentSourceFromRequest($request) 88 + ->setContinueOnNoEffect(true) 89 + ->setContinueOnMissingFields(true); 90 + 91 + try { 92 + $editor->applyTransactions($object, array($xaction)); 93 + return id(new AphrontRedirectResponse())->setURI($cancel_uri); 94 + } catch (PhabricatorApplicationTransactionValidationException $ex) { 95 + $validation_exception = $ex; 96 + } 97 + } 98 + 99 + return $this->newDialog() 100 + ->setTitle($title) 101 + ->setValidationException($validation_exception) 102 + ->addHiddenInput('objectPHID', $object->getPHID()) 103 + ->addHiddenInput('key', $key) 104 + ->appendParagraph($body) 105 + ->addCancelButton($cancel_uri) 106 + ->addSubmitButton($submit_text); 107 + } 108 + 109 + }
+44 -56
src/applications/almanac/controller/AlmanacPropertyEditController.php
··· 6 6 public function handleRequest(AphrontRequest $request) { 7 7 $viewer = $request->getViewer(); 8 8 9 - $id = $request->getURIData('id'); 10 - if ($id) { 11 - $property = id(new AlmanacPropertyQuery()) 12 - ->setViewer($viewer) 13 - ->withIDs(array($id)) 14 - ->requireCapabilities( 15 - array( 16 - PhabricatorPolicyCapability::CAN_VIEW, 17 - PhabricatorPolicyCapability::CAN_EDIT, 18 - )) 19 - ->executeOne(); 20 - if (!$property) { 21 - return new Aphront404Response(); 22 - } 9 + $object = id(new PhabricatorObjectQuery()) 10 + ->setViewer($viewer) 11 + ->withPHIDs(array($request->getStr('objectPHID'))) 12 + ->requireCapabilities( 13 + array( 14 + PhabricatorPolicyCapability::CAN_VIEW, 15 + PhabricatorPolicyCapability::CAN_EDIT, 16 + )) 17 + ->executeOne(); 18 + if (!$object) { 19 + return new Aphront404Response(); 20 + } 21 + 22 + if (!($object instanceof AlmanacPropertyInterface)) { 23 + return new Aphront404Response(); 24 + } 25 + 26 + $cancel_uri = $object->getURI(); 23 27 24 - $object = $property->getObject(); 28 + $key = $request->getStr('key'); 29 + if ($key) { 30 + $property_key = $key; 25 31 26 32 $is_new = false; 27 33 $title = pht('Edit Property'); 28 34 $save_button = pht('Save Changes'); 29 35 } else { 30 - $object = id(new PhabricatorObjectQuery()) 31 - ->setViewer($viewer) 32 - ->withPHIDs(array($request->getStr('objectPHID'))) 33 - ->requireCapabilities( 34 - array( 35 - PhabricatorPolicyCapability::CAN_VIEW, 36 - PhabricatorPolicyCapability::CAN_EDIT, 37 - )) 38 - ->executeOne(); 39 - if (!$object) { 40 - return new Aphront404Response(); 41 - } 36 + $property_key = null; 42 37 43 38 $is_new = true; 44 39 $title = pht('Add Property'); 45 40 $save_button = pht('Add Property'); 46 41 } 47 42 48 - if (!($object instanceof AlmanacPropertyInterface)) { 49 - return new Aphront404Response(); 50 - } 51 - 52 - $cancel_uri = $object->getURI(); 53 - 54 43 if ($is_new) { 55 44 $errors = array(); 56 45 $property = null; ··· 77 66 } 78 67 79 68 if (!$errors) { 80 - $property = id(new AlmanacPropertyQuery()) 81 - ->setViewer($viewer) 82 - ->withObjectPHIDs(array($object->getPHID())) 83 - ->withNames(array($name)) 84 - ->requireCapabilities( 85 - array( 86 - PhabricatorPolicyCapability::CAN_VIEW, 87 - PhabricatorPolicyCapability::CAN_EDIT, 88 - )) 89 - ->executeOne(); 90 - if (!$property) { 91 - $property = id(new AlmanacProperty()) 92 - ->setObjectPHID($object->getPHID()) 93 - ->setFieldName($name); 94 - } 69 + $property_key = $name; 95 70 } 96 71 } 97 72 98 - if (!$property) { 73 + if ($property_key === null) { 99 74 $form = id(new AphrontFormView()) 100 75 ->setUser($viewer) 101 76 ->appendChild( ··· 115 90 } 116 91 } 117 92 118 - $v_name = $property->getFieldName(); 119 - $e_name = true; 93 + // Make sure property key is appropriate. 94 + // TODO: It would be cleaner to put this safety check in the Editor. 95 + AlmanacNames::validateServiceOrDeviceName($property_key); 120 96 121 - $v_value = $property->getFieldValue(); 122 - $e_value = null; 97 + // If we're adding a new property, put a placeholder on the object so 98 + // that we can build a CustomField for it. 99 + if (!$object->hasAlmanacProperty($property_key)) { 100 + $temporary_property = id(new AlmanacProperty()) 101 + ->setObjectPHID($object->getPHID()) 102 + ->setFieldName($property_key); 123 103 124 - $object->attachAlmanacProperties(array($property)); 104 + $object->attachAlmanacProperties(array($temporary_property)); 105 + } 125 106 126 107 $field_list = PhabricatorCustomField::getObjectFields( 127 108 $object, 128 - PhabricatorCustomField::ROLE_EDIT); 109 + PhabricatorCustomField::ROLE_DEFAULT); 110 + 111 + // Select only the field being edited. 112 + $fields = $field_list->getFields(); 113 + $fields = array_select_keys($fields, array($property_key)); 114 + $field_list = new PhabricatorCustomFieldList($fields); 115 + 129 116 $field_list 130 117 ->setViewer($viewer) 131 118 ->readFieldsFromStorage($object); ··· 153 140 $form = id(new AphrontFormView()) 154 141 ->setUser($viewer) 155 142 ->addHiddenInput('objectPHID', $request->getStr('objectPHID')) 156 - ->addHiddenInput('name', $request->getStr('name')) 143 + ->addHiddenInput('key', $request->getStr('key')) 144 + ->addHiddenInput('name', $property_key) 157 145 ->addHiddenInput('isValueEdit', true); 158 146 159 147 $field_list->appendFieldsToForm($form);
+21 -6
src/applications/almanac/customfield/AlmanacCoreCustomField.php
··· 8 8 return 'almanac:core'; 9 9 } 10 10 11 + public function getFieldKey() { 12 + return $this->getProxy()->getRawStandardFieldKey(); 13 + } 14 + 15 + public function getFieldName() { 16 + return $this->getFieldKey(); 17 + } 18 + 11 19 public function createFields($object) { 12 - $specs = array(); 20 + 21 + $specs = $object->getAlmanacPropertyFieldSpecifications(); 13 22 23 + $default_specs = array(); 14 24 foreach ($object->getAlmanacProperties() as $property) { 15 - $specs[$property->getFieldName()] = array( 25 + $default_specs[$property->getFieldName()] = array( 16 26 'name' => $property->getFieldName(), 17 27 'type' => 'text', 18 28 ); 19 29 } 20 30 21 - return PhabricatorStandardCustomField::buildStandardFields($this, $specs); 31 + return PhabricatorStandardCustomField::buildStandardFields( 32 + $this, 33 + $specs + $default_specs); 22 34 } 23 35 24 36 public function shouldUseStorage() { ··· 26 38 } 27 39 28 40 public function readValueFromObject(PhabricatorCustomFieldInterface $object) { 29 - $key = $this->getProxy()->getRawStandardFieldKey(); 30 - $this->setValueFromStorage($object->getAlmanacPropertyValue($key)); 41 + $key = $this->getFieldKey(); 42 + 43 + if ($object->hasAlmanacProperty($key)) { 44 + $this->setValueFromStorage($object->getAlmanacPropertyValue($key)); 45 + } 31 46 } 32 47 33 48 public function applyApplicationTransactionInternalEffects( ··· 40 55 41 56 $object = $this->getObject(); 42 57 $phid = $object->getPHID(); 43 - $key = $this->getProxy()->getRawStandardFieldKey(); 58 + $key = $this->getFieldKey(); 44 59 45 60 $property = id(new AlmanacPropertyQuery()) 46 61 ->setViewer($this->getViewer())
+1
src/applications/almanac/property/AlmanacPropertyInterface.php
··· 7 7 public function hasAlmanacProperty($key); 8 8 public function getAlmanacProperty($key); 9 9 public function getAlmanacPropertyValue($key, $default = null); 10 + public function getAlmanacPropertyFieldSpecifications(); 10 11 11 12 }
+1 -1
src/applications/almanac/query/AlmanacPropertyQuery.php
··· 14 14 } 15 15 16 16 public function withObjectPHIDs(array $phids) { 17 - $this->phids = $phids; 17 + $this->objectPHIDs = $phids; 18 18 return $this; 19 19 } 20 20
+2 -1
src/applications/almanac/query/AlmanacQuery.php
··· 12 12 $property_query = id(new AlmanacPropertyQuery()) 13 13 ->setViewer($this->getViewer()) 14 14 ->setParentQuery($this) 15 - ->withObjectPHIDs(mpull($objects, null, 'getPHID')); 15 + ->withObjectPHIDs(mpull($objects, 'getPHID')); 16 16 17 17 // NOTE: We disable policy filtering and object attachment to avoid 18 18 // a cyclic dependency where objects need their properties and properties ··· 21 21 $property_query->setDisablePolicyFilteringAndAttachment(true); 22 22 23 23 $properties = $property_query->execute(); 24 + 24 25 $properties = mgroup($properties, 'getObjectPHID'); 25 26 foreach ($objects as $object) { 26 27 $object_properties = idx($properties, $object->getPHID(), array());
+15
src/applications/almanac/servicetype/AlmanacClusterRepositoryServiceType.php
··· 16 16 'Defines a repository service for use in a Phabricator cluster.'); 17 17 } 18 18 19 + public function getFieldSpecifications() { 20 + return array( 21 + 'closed' => array( 22 + 'type' => 'bool', 23 + 'name' => pht('Closed'), 24 + 'default' => false, 25 + 'strings' => array( 26 + 'edit.checkbox' => pht( 27 + 'Prevent new repositories from being allocated on this '. 28 + 'service.'), 29 + ), 30 + ), 31 + ); 32 + } 33 + 19 34 }
+8
src/applications/almanac/servicetype/AlmanacServiceType.php
··· 47 47 } 48 48 49 49 50 + public function getDefaultPropertyMap() { 51 + return array(); 52 + } 53 + 54 + public function getFieldSpecifications() { 55 + return array(); 56 + } 57 + 50 58 /** 51 59 * List all available service type implementations. 52 60 *
+4
src/applications/almanac/storage/AlmanacBinding.php
··· 119 119 } 120 120 } 121 121 122 + public function getAlmanacPropertyFieldSpecifications() { 123 + return array(); 124 + } 125 + 122 126 123 127 /* -( PhabricatorPolicyInterface )----------------------------------------- */ 124 128
+4
src/applications/almanac/storage/AlmanacDevice.php
··· 97 97 } 98 98 } 99 99 100 + public function getAlmanacPropertyFieldSpecifications() { 101 + return array(); 102 + } 103 + 100 104 101 105 /* -( PhabricatorPolicyInterface )----------------------------------------- */ 102 106
+1 -1
src/applications/almanac/storage/AlmanacNetwork.php
··· 40 40 } 41 41 42 42 public function getURI() { 43 - return '/almanac/network/view/'.$this->getName().'/'; 43 + return '/almanac/network/'.$this->getID().'/'; 44 44 } 45 45 46 46
+4
src/applications/almanac/storage/AlmanacService.php
··· 121 121 } 122 122 } 123 123 124 + public function getAlmanacPropertyFieldSpecifications() { 125 + return $this->getServiceType()->getFieldSpecifications(); 126 + } 127 + 124 128 125 129 /* -( PhabricatorPolicyInterface )----------------------------------------- */ 126 130
+5 -1
src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldBool.php
··· 22 22 return $this->newNumericIndex(0); 23 23 } 24 24 25 + public function readValueFromRequest(AphrontRequest $request) { 26 + $this->setFieldValue((bool)$request->getBool($this->getFieldKey())); 27 + } 28 + 25 29 public function getValueForStorage() { 26 30 $value = $this->getFieldValue(); 27 - if (strlen($value)) { 31 + if ($value !== null) { 28 32 return (int)$value; 29 33 } else { 30 34 return null;
+7
webroot/rsrc/css/application/almanac/almanac.css
··· 1 + /** 2 + * @provides almanac-css 3 + */ 4 + 5 + .almanac-default-property-value { 6 + color: {$lightgreytext}; 7 + }