@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.

Give AlmanacServices a service type

Summary:
Ref T5833. This allows services to be typed, to distinguish between different kinds of services. This makes a few things easier:

- It's easier for clients to select the services they're interested in (see note in T5873 about Phacility). This isn't a full-power solution, but gets is some of the way there.
- It's easier to set appropriate permissions around when modifications to the Phabricator cluster are allowed. These service nodes need to be demarcated as special in some way no matter what (see T6741). This also defines a new policy for users who are permitted to create services.
- It's easier to browse/review/understand services.
- Future diffs will allow ServiceTypes to specify more service structure (for example, default properties) to make it easier to configure services correctly. Instead of a free-for-all, you'll get a useful list of things that consumers of the service expect to read.

The "custom" service type allows unstructured/freeform services to be created.

Test Plan:
- Created a new service (and hit error cases).
- Edited an existing service.
- Saw service types on list and detail views.
- Poked around new permission stuff.
- Ran `almanac.queryservices` with service class specification.

Reviewers: btrahan

Reviewed By: btrahan

Subscribers: epriestley

Maniphest Tasks: T5833

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

+315 -3
+8
resources/sql/autopatches/20141215.almanacservicetype.sql
··· 1 + ALTER TABLE {$NAMESPACE}_almanac.almanac_service 2 + ADD serviceClass VARCHAR(64) NOT NULL COLLATE {$COLLATE_TEXT}; 3 + 4 + ALTER TABLE {$NAMESPACE}_almanac.almanac_service 5 + ADD KEY `key_class` (serviceClass); 6 + 7 + UPDATE {$NAMESPACE}_almanac.almanac_service 8 + SET serviceClass = 'AlmanacCustomServiceType' WHERE serviceClass = '';
+10
src/__phutil_library_map__.php
··· 19 19 'AlmanacBindingTransaction' => 'applications/almanac/storage/AlmanacBindingTransaction.php', 20 20 'AlmanacBindingTransactionQuery' => 'applications/almanac/query/AlmanacBindingTransactionQuery.php', 21 21 'AlmanacBindingViewController' => 'applications/almanac/controller/AlmanacBindingViewController.php', 22 + 'AlmanacClusterRepositoryServiceType' => 'applications/almanac/servicetype/AlmanacClusterRepositoryServiceType.php', 23 + 'AlmanacClusterServiceType' => 'applications/almanac/servicetype/AlmanacClusterServiceType.php', 22 24 'AlmanacConduitAPIMethod' => 'applications/almanac/conduit/AlmanacConduitAPIMethod.php', 23 25 'AlmanacConsoleController' => 'applications/almanac/controller/AlmanacConsoleController.php', 24 26 'AlmanacController' => 'applications/almanac/controller/AlmanacController.php', 25 27 'AlmanacCoreCustomField' => 'applications/almanac/customfield/AlmanacCoreCustomField.php', 28 + 'AlmanacCreateClusterServicesCapability' => 'applications/almanac/capability/AlmanacCreateClusterServicesCapability.php', 26 29 'AlmanacCreateDevicesCapability' => 'applications/almanac/capability/AlmanacCreateDevicesCapability.php', 27 30 'AlmanacCreateNetworksCapability' => 'applications/almanac/capability/AlmanacCreateNetworksCapability.php', 28 31 'AlmanacCreateServicesCapability' => 'applications/almanac/capability/AlmanacCreateServicesCapability.php', 29 32 'AlmanacCustomField' => 'applications/almanac/customfield/AlmanacCustomField.php', 33 + 'AlmanacCustomServiceType' => 'applications/almanac/servicetype/AlmanacCustomServiceType.php', 30 34 'AlmanacDAO' => 'applications/almanac/storage/AlmanacDAO.php', 31 35 'AlmanacDevice' => 'applications/almanac/storage/AlmanacDevice.php', 32 36 'AlmanacDeviceController' => 'applications/almanac/controller/AlmanacDeviceController.php', ··· 79 83 'AlmanacServiceSearchEngine' => 'applications/almanac/query/AlmanacServiceSearchEngine.php', 80 84 'AlmanacServiceTransaction' => 'applications/almanac/storage/AlmanacServiceTransaction.php', 81 85 'AlmanacServiceTransactionQuery' => 'applications/almanac/query/AlmanacServiceTransactionQuery.php', 86 + 'AlmanacServiceType' => 'applications/almanac/servicetype/AlmanacServiceType.php', 82 87 'AlmanacServiceViewController' => 'applications/almanac/controller/AlmanacServiceViewController.php', 83 88 'Aphront304Response' => 'aphront/response/Aphront304Response.php', 84 89 'Aphront400Response' => 'aphront/response/Aphront400Response.php', ··· 3018 3023 'AlmanacBindingTransaction' => 'PhabricatorApplicationTransaction', 3019 3024 'AlmanacBindingTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 3020 3025 'AlmanacBindingViewController' => 'AlmanacServiceController', 3026 + 'AlmanacClusterRepositoryServiceType' => 'AlmanacClusterServiceType', 3027 + 'AlmanacClusterServiceType' => 'AlmanacServiceType', 3021 3028 'AlmanacConduitAPIMethod' => 'ConduitAPIMethod', 3022 3029 'AlmanacConsoleController' => 'AlmanacController', 3023 3030 'AlmanacController' => 'PhabricatorController', ··· 3025 3032 'AlmanacCustomField', 3026 3033 'PhabricatorStandardCustomFieldInterface', 3027 3034 ), 3035 + 'AlmanacCreateClusterServicesCapability' => 'PhabricatorPolicyCapability', 3028 3036 'AlmanacCreateDevicesCapability' => 'PhabricatorPolicyCapability', 3029 3037 'AlmanacCreateNetworksCapability' => 'PhabricatorPolicyCapability', 3030 3038 'AlmanacCreateServicesCapability' => 'PhabricatorPolicyCapability', 3031 3039 'AlmanacCustomField' => 'PhabricatorCustomField', 3040 + 'AlmanacCustomServiceType' => 'AlmanacServiceType', 3032 3041 'AlmanacDAO' => 'PhabricatorLiskDAO', 3033 3042 'AlmanacDevice' => array( 3034 3043 'AlmanacDAO', ··· 3105 3114 'AlmanacServiceSearchEngine' => 'PhabricatorApplicationSearchEngine', 3106 3115 'AlmanacServiceTransaction' => 'PhabricatorApplicationTransaction', 3107 3116 'AlmanacServiceTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 3117 + 'AlmanacServiceType' => 'Phobject', 3108 3118 'AlmanacServiceViewController' => 'AlmanacServiceController', 3109 3119 'Aphront304Response' => 'AphrontResponse', 3110 3120 'Aphront400Response' => 'AphrontResponse',
+3
src/applications/almanac/application/PhabricatorAlmanacApplication.php
··· 74 74 AlmanacCreateNetworksCapability::CAPABILITY => array( 75 75 'default' => PhabricatorPolicies::POLICY_ADMIN, 76 76 ), 77 + AlmanacCreateClusterServicesCapability::CAPABILITY => array( 78 + 'default' => PhabricatorPolicies::POLICY_ADMIN, 79 + ), 77 80 ); 78 81 } 79 82
+17
src/applications/almanac/capability/AlmanacCreateClusterServicesCapability.php
··· 1 + <?php 2 + 3 + final class AlmanacCreateClusterServicesCapability 4 + extends PhabricatorPolicyCapability { 5 + 6 + const CAPABILITY = 'almanac.cluster'; 7 + 8 + public function getCapabilityName() { 9 + return pht('Can Create Cluster Services'); 10 + } 11 + 12 + public function describeCapabilityRejection() { 13 + return pht( 14 + 'You do not have permission to create Almanac cluster services.'); 15 + } 16 + 17 + }
+7
src/applications/almanac/conduit/AlmanacQueryServicesConduitAPIMethod.php
··· 16 16 'ids' => 'optional list<id>', 17 17 'phids' => 'optional list<phid>', 18 18 'names' => 'optional list<phid>', 19 + 'serviceClasses' => 'optional list<string>', 19 20 ) + self::getPagerParamTypes(); 20 21 } 21 22 ··· 49 50 $query->withNames($names); 50 51 } 51 52 53 + $classes = $request->getValue('serviceClasses'); 54 + if ($classes !== null) { 55 + $query->withServiceClasses($classes); 56 + } 57 + 52 58 $pager = $this->newPager($request); 53 59 54 60 $services = $query->executeWithCursorPager($pager); ··· 84 90 'phid' => $service->getPHID(), 85 91 'name' => $service->getName(), 86 92 'uri' => PhabricatorEnv::getProductionURI($service->getURI()), 93 + 'serviceClass' => $service->getServiceClass(), 87 94 'properties' => $this->getPropertiesDictionary($service), 88 95 ); 89 96 }
+95 -2
src/applications/almanac/controller/AlmanacServiceEditController.php
··· 29 29 $title = pht('Edit Service'); 30 30 $save_button = pht('Save Changes'); 31 31 } else { 32 + $cancel_uri = $list_uri; 33 + 32 34 $this->requireApplicationCapability( 33 35 AlmanacCreateServicesCapability::CAPABILITY); 34 36 37 + $service_class = $request->getStr('serviceClass'); 38 + $service_types = AlmanacServiceType::getAllServiceTypes(); 39 + if (empty($service_types[$service_class])) { 40 + return $this->buildServiceTypeResponse($service_types, $cancel_uri); 41 + } 42 + 43 + $service_type = $service_types[$service_class]; 44 + if ($service_type->isClusterServiceType()) { 45 + $this->requireApplicationCapability( 46 + AlmanacCreateClusterServicesCapability::CAPABILITY); 47 + } 48 + 35 49 $service = AlmanacService::initializeNewService(); 50 + $service->setServiceClass($service_class); 51 + $service->attachServiceType($service_type); 36 52 $is_new = true; 37 53 38 - $cancel_uri = $list_uri; 39 54 $title = pht('Create Service'); 40 55 $save_button = pht('Create Service'); 41 56 } ··· 53 68 $v_projects = array_reverse($v_projects); 54 69 } 55 70 56 - if ($request->isFormPost()) { 71 + if ($request->isFormPost() && $request->getStr('edit')) { 57 72 $v_name = $request->getStr('name'); 58 73 $v_view = $request->getStr('viewPolicy'); 59 74 $v_edit = $request->getStr('editPolicy'); ··· 115 130 116 131 $form = id(new AphrontFormView()) 117 132 ->setUser($viewer) 133 + ->addHiddenInput('edit', true) 134 + ->addHiddenInput('serviceClass', $service->getServiceClass()) 118 135 ->appendChild( 119 136 id(new AphrontFormTextControl()) 120 137 ->setLabel(pht('Name')) ··· 166 183 'title' => $title, 167 184 )); 168 185 } 186 + 187 + private function buildServiceTypeResponse(array $service_types, $cancel_uri) { 188 + $request = $this->getRequest(); 189 + $viewer = $this->getViewer(); 190 + 191 + $e_service = null; 192 + $errors = array(); 193 + if ($request->isFormPost()) { 194 + $e_service = pht('Required'); 195 + $errors[] = pht( 196 + 'To create a new service, you must select a service type.'); 197 + } 198 + 199 + list($can_cluster, $cluster_link) = $this->explainApplicationCapability( 200 + AlmanacCreateClusterServicesCapability::CAPABILITY, 201 + pht('You have permission to create cluster services.'), 202 + pht('You do not have permission to create new cluster services.')); 203 + 204 + 205 + $type_control = id(new AphrontFormRadioButtonControl()) 206 + ->setLabel(pht('Service Type')) 207 + ->setName('serviceClass') 208 + ->setError($e_service); 209 + 210 + foreach ($service_types as $service_type) { 211 + $is_cluster = $service_type->isClusterServiceType(); 212 + $is_disabled = ($is_cluster && !$can_cluster); 213 + 214 + if ($is_cluster) { 215 + $extra = $cluster_link; 216 + } else { 217 + $extra = null; 218 + } 219 + 220 + $type_control->addButton( 221 + get_class($service_type), 222 + $service_type->getServiceTypeName(), 223 + array( 224 + $service_type->getServiceTypeDescription(), 225 + $extra, 226 + ), 227 + $is_disabled ? 'disabled' : null, 228 + $is_disabled); 229 + } 230 + 231 + $crumbs = $this->buildApplicationCrumbs(); 232 + $crumbs->addTextCrumb(pht('Create Service')); 233 + 234 + $title = pht('Choose Service Type'); 235 + 236 + $form = id(new AphrontFormView()) 237 + ->setUser($viewer) 238 + ->appendChild($type_control) 239 + ->appendChild( 240 + id(new AphrontFormSubmitControl()) 241 + ->setValue(pht('Continue')) 242 + ->addCancelButton($cancel_uri)); 243 + 244 + $box = id(new PHUIObjectBoxView()) 245 + ->setFormErrors($errors) 246 + ->setHeaderText($title) 247 + ->appendChild($form); 248 + 249 + return $this->buildApplicationPage( 250 + array( 251 + $crumbs, 252 + $box, 253 + ), 254 + array( 255 + 'title' => $title, 256 + )); 257 + } 258 + 259 + 260 + 261 + 169 262 170 263 }
+4
src/applications/almanac/controller/AlmanacServiceViewController.php
··· 65 65 ->setUser($viewer) 66 66 ->setObject($service); 67 67 68 + $properties->addProperty( 69 + pht('Service Type'), 70 + $service->getServiceType()->getServiceTypeShortName()); 71 + 68 72 return $properties; 69 73 } 70 74
+30
src/applications/almanac/query/AlmanacServiceQuery.php
··· 6 6 private $ids; 7 7 private $phids; 8 8 private $names; 9 + private $serviceClasses; 9 10 private $needBindings; 10 11 11 12 public function withIDs(array $ids) { ··· 20 21 21 22 public function withNames(array $names) { 22 23 $this->names = $names; 24 + return $this; 25 + } 26 + 27 + public function withServiceClasses(array $classes) { 28 + $this->serviceClasses = $classes; 23 29 return $this; 24 30 } 25 31 ··· 72 78 $hashes); 73 79 } 74 80 81 + if ($this->serviceClasses !== null) { 82 + $where[] = qsprintf( 83 + $conn_r, 84 + 'serviceClass IN (%Ls)', 85 + $this->serviceClasses); 86 + } 87 + 75 88 $where[] = $this->buildPagingClause($conn_r); 76 89 77 90 return $this->formatWhereClause($where); 91 + } 92 + 93 + protected function willFilterPage(array $services) { 94 + $service_types = AlmanacServiceType::getAllServiceTypes(); 95 + 96 + foreach ($services as $key => $service) { 97 + $service_class = $service->getServiceClass(); 98 + $service_type = idx($service_types, $service_class); 99 + if (!$service_type) { 100 + $this->didRejectResult($service); 101 + unset($services[$key]); 102 + continue; 103 + } 104 + $service->attachServiceType($service_type); 105 + } 106 + 107 + return $services; 78 108 } 79 109 80 110 protected function didFilterPage(array $services) {
+4 -1
src/applications/almanac/query/AlmanacServiceSearchEngine.php
··· 73 73 ->setObjectName(pht('Service %d', $service->getID())) 74 74 ->setHeader($service->getName()) 75 75 ->setHref($service->getURI()) 76 - ->setObject($service); 76 + ->setObject($service) 77 + ->addIcon( 78 + $service->getServiceType()->getServiceTypeIcon(), 79 + $service->getServiceType()->getServiceTypeShortName()); 77 80 78 81 $list->addItem($item); 79 82 }
+19
src/applications/almanac/servicetype/AlmanacClusterRepositoryServiceType.php
··· 1 + <?php 2 + 3 + final class AlmanacClusterRepositoryServiceType 4 + extends AlmanacClusterServiceType { 5 + 6 + public function getServiceTypeShortName() { 7 + return pht('Cluster Repository'); 8 + } 9 + 10 + public function getServiceTypeName() { 11 + return pht('Phabricator Cluster: Repository'); 12 + } 13 + 14 + public function getServiceTypeDescription() { 15 + return pht( 16 + 'Defines a repository service for use in a Phabricator cluster.'); 17 + } 18 + 19 + }
+14
src/applications/almanac/servicetype/AlmanacClusterServiceType.php
··· 1 + <?php 2 + 3 + abstract class AlmanacClusterServiceType 4 + extends AlmanacServiceType { 5 + 6 + public function isClusterServiceType() { 7 + return true; 8 + } 9 + 10 + public function getServiceTypeIcon() { 11 + return 'fa-sitemap'; 12 + } 13 + 14 + }
+17
src/applications/almanac/servicetype/AlmanacCustomServiceType.php
··· 1 + <?php 2 + 3 + final class AlmanacCustomServiceType extends AlmanacServiceType { 4 + 5 + public function getServiceTypeShortName() { 6 + return pht('Custom'); 7 + } 8 + 9 + public function getServiceTypeName() { 10 + return pht('Custom Service'); 11 + } 12 + 13 + public function getServiceTypeDescription() { 14 + return pht('Defines a unstructured custom service.'); 15 + } 16 + 17 + }
+64
src/applications/almanac/servicetype/AlmanacServiceType.php
··· 1 + <?php 2 + 3 + abstract class AlmanacServiceType extends Phobject { 4 + 5 + 6 + /** 7 + * Return a very short human-readable name for this service type, like 8 + * "Custom". 9 + * 10 + * @return string Very short human-readable service type name. 11 + */ 12 + abstract public function getServiceTypeShortName(); 13 + 14 + /** 15 + * Return a short, human-readable name for this service type, like 16 + * "Custom Service". 17 + * 18 + * @return string Human-readable name for this service type. 19 + */ 20 + abstract public function getServiceTypeName(); 21 + 22 + 23 + /** 24 + * Return a brief summary of this service type. 25 + * 26 + * This summary should be a sentence or two long. 27 + * 28 + * @return string Brief, human-readable description of this service type. 29 + */ 30 + abstract public function getServiceTypeDescription(); 31 + 32 + 33 + public function getServiceTypeIcon() { 34 + return 'fa-cog'; 35 + } 36 + 37 + /** 38 + * Return `true` if this service type is a Phabricator cluster service type. 39 + * 40 + * These special services change the behavior of Phabricator, and require 41 + * elevated permission to create. 42 + * 43 + * @return bool True if this is a Phabricator cluster service type. 44 + */ 45 + public function isClusterServiceType() { 46 + return false; 47 + } 48 + 49 + 50 + /** 51 + * List all available service type implementations. 52 + * 53 + * @return map<string, object> Dictionary of available service types. 54 + */ 55 + public static function getAllServiceTypes() { 56 + $types = id(new PhutilSymbolLoader()) 57 + ->setAncestorClass(__CLASS__) 58 + ->loadObjects(); 59 + 60 + return msort($types, 'getServiceTypeName'); 61 + } 62 + 63 + 64 + }
+15
src/applications/almanac/storage/AlmanacService.php
··· 14 14 protected $mailKey; 15 15 protected $viewPolicy; 16 16 protected $editPolicy; 17 + protected $serviceClass; 17 18 18 19 private $customFields = self::ATTACHABLE; 19 20 private $almanacProperties = self::ATTACHABLE; 20 21 private $bindings = self::ATTACHABLE; 22 + private $serviceType = self::ATTACHABLE; 21 23 22 24 public static function initializeNewService() { 23 25 return id(new AlmanacService()) ··· 33 35 'name' => 'text128', 34 36 'nameIndex' => 'bytes12', 35 37 'mailKey' => 'bytes20', 38 + 'serviceClass' => 'text64', 36 39 ), 37 40 self::CONFIG_KEY_SCHEMA => array( 38 41 'key_name' => array( ··· 42 45 'key_nametext' => array( 43 46 'columns' => array('name'), 44 47 ), 48 + 'key_class' => array( 49 + 'columns' => array('serviceClass'), 50 + ), 45 51 ), 46 52 ) + parent::getConfiguration(); 47 53 } ··· 72 78 73 79 public function attachBindings(array $bindings) { 74 80 $this->bindings = $bindings; 81 + return $this; 82 + } 83 + 84 + public function getServiceType() { 85 + return $this->assertAttached($this->serviceType); 86 + } 87 + 88 + public function attachServiceType(AlmanacServiceType $type) { 89 + $this->serviceType = $type; 75 90 return $this; 76 91 } 77 92
+8
src/applications/diffusion/query/DiffusionQuery.php
··· 81 81 'be loaded.')); 82 82 } 83 83 84 + $service_type = $service->getServiceType(); 85 + if (!($service_type instanceof AlmanacClusterRepositoryServiceType)) { 86 + throw new Exception( 87 + pht( 88 + 'The Alamnac service for this repository does not have the correct '. 89 + 'service type.')); 90 + } 91 + 84 92 $bindings = $service->getBindings(); 85 93 if (!$bindings) { 86 94 throw new Exception(