diff --git a/resources/sql/autopatches/20141215.almanacservicetype.sql b/resources/sql/autopatches/20141215.almanacservicetype.sql new file mode 100644 --- /dev/null +++ b/resources/sql/autopatches/20141215.almanacservicetype.sql @@ -0,0 +1,8 @@ +ALTER TABLE {$NAMESPACE}_almanac.almanac_service + ADD serviceClass VARCHAR(64) NOT NULL COLLATE {$COLLATE_TEXT}; + +ALTER TABLE {$NAMESPACE}_almanac.almanac_service + ADD KEY `key_class` (serviceClass); + +UPDATE {$NAMESPACE}_almanac.almanac_service + SET serviceClass = 'AlmanacCustomServiceType' WHERE serviceClass = ''; 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 @@ -19,14 +19,18 @@ 'AlmanacBindingTransaction' => 'applications/almanac/storage/AlmanacBindingTransaction.php', 'AlmanacBindingTransactionQuery' => 'applications/almanac/query/AlmanacBindingTransactionQuery.php', 'AlmanacBindingViewController' => 'applications/almanac/controller/AlmanacBindingViewController.php', + 'AlmanacClusterRepositoryServiceType' => 'applications/almanac/servicetype/AlmanacClusterRepositoryServiceType.php', + 'AlmanacClusterServiceType' => 'applications/almanac/servicetype/AlmanacClusterServiceType.php', 'AlmanacConduitAPIMethod' => 'applications/almanac/conduit/AlmanacConduitAPIMethod.php', 'AlmanacConsoleController' => 'applications/almanac/controller/AlmanacConsoleController.php', 'AlmanacController' => 'applications/almanac/controller/AlmanacController.php', 'AlmanacCoreCustomField' => 'applications/almanac/customfield/AlmanacCoreCustomField.php', + 'AlmanacCreateClusterServicesCapability' => 'applications/almanac/capability/AlmanacCreateClusterServicesCapability.php', 'AlmanacCreateDevicesCapability' => 'applications/almanac/capability/AlmanacCreateDevicesCapability.php', 'AlmanacCreateNetworksCapability' => 'applications/almanac/capability/AlmanacCreateNetworksCapability.php', 'AlmanacCreateServicesCapability' => 'applications/almanac/capability/AlmanacCreateServicesCapability.php', 'AlmanacCustomField' => 'applications/almanac/customfield/AlmanacCustomField.php', + 'AlmanacCustomServiceType' => 'applications/almanac/servicetype/AlmanacCustomServiceType.php', 'AlmanacDAO' => 'applications/almanac/storage/AlmanacDAO.php', 'AlmanacDevice' => 'applications/almanac/storage/AlmanacDevice.php', 'AlmanacDeviceController' => 'applications/almanac/controller/AlmanacDeviceController.php', @@ -79,6 +83,7 @@ 'AlmanacServiceSearchEngine' => 'applications/almanac/query/AlmanacServiceSearchEngine.php', 'AlmanacServiceTransaction' => 'applications/almanac/storage/AlmanacServiceTransaction.php', 'AlmanacServiceTransactionQuery' => 'applications/almanac/query/AlmanacServiceTransactionQuery.php', + 'AlmanacServiceType' => 'applications/almanac/servicetype/AlmanacServiceType.php', 'AlmanacServiceViewController' => 'applications/almanac/controller/AlmanacServiceViewController.php', 'Aphront304Response' => 'aphront/response/Aphront304Response.php', 'Aphront400Response' => 'aphront/response/Aphront400Response.php', @@ -3018,6 +3023,8 @@ 'AlmanacBindingTransaction' => 'PhabricatorApplicationTransaction', 'AlmanacBindingTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'AlmanacBindingViewController' => 'AlmanacServiceController', + 'AlmanacClusterRepositoryServiceType' => 'AlmanacClusterServiceType', + 'AlmanacClusterServiceType' => 'AlmanacServiceType', 'AlmanacConduitAPIMethod' => 'ConduitAPIMethod', 'AlmanacConsoleController' => 'AlmanacController', 'AlmanacController' => 'PhabricatorController', @@ -3025,10 +3032,12 @@ 'AlmanacCustomField', 'PhabricatorStandardCustomFieldInterface', ), + 'AlmanacCreateClusterServicesCapability' => 'PhabricatorPolicyCapability', 'AlmanacCreateDevicesCapability' => 'PhabricatorPolicyCapability', 'AlmanacCreateNetworksCapability' => 'PhabricatorPolicyCapability', 'AlmanacCreateServicesCapability' => 'PhabricatorPolicyCapability', 'AlmanacCustomField' => 'PhabricatorCustomField', + 'AlmanacCustomServiceType' => 'AlmanacServiceType', 'AlmanacDAO' => 'PhabricatorLiskDAO', 'AlmanacDevice' => array( 'AlmanacDAO', @@ -3105,6 +3114,7 @@ 'AlmanacServiceSearchEngine' => 'PhabricatorApplicationSearchEngine', 'AlmanacServiceTransaction' => 'PhabricatorApplicationTransaction', 'AlmanacServiceTransactionQuery' => 'PhabricatorApplicationTransactionQuery', + 'AlmanacServiceType' => 'Phobject', 'AlmanacServiceViewController' => 'AlmanacServiceController', 'Aphront304Response' => 'AphrontResponse', 'Aphront400Response' => 'AphrontResponse', diff --git a/src/applications/almanac/application/PhabricatorAlmanacApplication.php b/src/applications/almanac/application/PhabricatorAlmanacApplication.php --- a/src/applications/almanac/application/PhabricatorAlmanacApplication.php +++ b/src/applications/almanac/application/PhabricatorAlmanacApplication.php @@ -74,6 +74,9 @@ AlmanacCreateNetworksCapability::CAPABILITY => array( 'default' => PhabricatorPolicies::POLICY_ADMIN, ), + AlmanacCreateClusterServicesCapability::CAPABILITY => array( + 'default' => PhabricatorPolicies::POLICY_ADMIN, + ), ); } diff --git a/src/applications/almanac/capability/AlmanacCreateClusterServicesCapability.php b/src/applications/almanac/capability/AlmanacCreateClusterServicesCapability.php new file mode 100644 --- /dev/null +++ b/src/applications/almanac/capability/AlmanacCreateClusterServicesCapability.php @@ -0,0 +1,17 @@ + 'optional list', 'phids' => 'optional list', 'names' => 'optional list', + 'serviceClasses' => 'optional list', ) + self::getPagerParamTypes(); } @@ -49,6 +50,11 @@ $query->withNames($names); } + $classes = $request->getValue('serviceClasses'); + if ($classes !== null) { + $query->withServiceClasses($classes); + } + $pager = $this->newPager($request); $services = $query->executeWithCursorPager($pager); @@ -84,6 +90,7 @@ 'phid' => $service->getPHID(), 'name' => $service->getName(), 'uri' => PhabricatorEnv::getProductionURI($service->getURI()), + 'serviceClass' => $service->getServiceClass(), 'properties' => $this->getPropertiesDictionary($service), ); } diff --git a/src/applications/almanac/controller/AlmanacServiceEditController.php b/src/applications/almanac/controller/AlmanacServiceEditController.php --- a/src/applications/almanac/controller/AlmanacServiceEditController.php +++ b/src/applications/almanac/controller/AlmanacServiceEditController.php @@ -29,13 +29,28 @@ $title = pht('Edit Service'); $save_button = pht('Save Changes'); } else { + $cancel_uri = $list_uri; + $this->requireApplicationCapability( AlmanacCreateServicesCapability::CAPABILITY); + $service_class = $request->getStr('serviceClass'); + $service_types = AlmanacServiceType::getAllServiceTypes(); + if (empty($service_types[$service_class])) { + return $this->buildServiceTypeResponse($service_types, $cancel_uri); + } + + $service_type = $service_types[$service_class]; + if ($service_type->isClusterServiceType()) { + $this->requireApplicationCapability( + AlmanacCreateClusterServicesCapability::CAPABILITY); + } + $service = AlmanacService::initializeNewService(); + $service->setServiceClass($service_class); + $service->attachServiceType($service_type); $is_new = true; - $cancel_uri = $list_uri; $title = pht('Create Service'); $save_button = pht('Create Service'); } @@ -53,7 +68,7 @@ $v_projects = array_reverse($v_projects); } - if ($request->isFormPost()) { + if ($request->isFormPost() && $request->getStr('edit')) { $v_name = $request->getStr('name'); $v_view = $request->getStr('viewPolicy'); $v_edit = $request->getStr('editPolicy'); @@ -115,6 +130,8 @@ $form = id(new AphrontFormView()) ->setUser($viewer) + ->addHiddenInput('edit', true) + ->addHiddenInput('serviceClass', $service->getServiceClass()) ->appendChild( id(new AphrontFormTextControl()) ->setLabel(pht('Name')) @@ -167,4 +184,80 @@ )); } + private function buildServiceTypeResponse(array $service_types, $cancel_uri) { + $request = $this->getRequest(); + $viewer = $this->getViewer(); + + $e_service = null; + $errors = array(); + if ($request->isFormPost()) { + $e_service = pht('Required'); + $errors[] = pht( + 'To create a new service, you must select a service type.'); + } + + list($can_cluster, $cluster_link) = $this->explainApplicationCapability( + AlmanacCreateClusterServicesCapability::CAPABILITY, + pht('You have permission to create cluster services.'), + pht('You do not have permission to create new cluster services.')); + + + $type_control = id(new AphrontFormRadioButtonControl()) + ->setLabel(pht('Service Type')) + ->setName('serviceClass') + ->setError($e_service); + + foreach ($service_types as $service_type) { + $is_cluster = $service_type->isClusterServiceType(); + $is_disabled = ($is_cluster && !$can_cluster); + + if ($is_cluster) { + $extra = $cluster_link; + } else { + $extra = null; + } + + $type_control->addButton( + get_class($service_type), + $service_type->getServiceTypeName(), + array( + $service_type->getServiceTypeDescription(), + $extra, + ), + $is_disabled ? 'disabled' : null, + $is_disabled); + } + + $crumbs = $this->buildApplicationCrumbs(); + $crumbs->addTextCrumb(pht('Create Service')); + + $title = pht('Choose Service Type'); + + $form = id(new AphrontFormView()) + ->setUser($viewer) + ->appendChild($type_control) + ->appendChild( + id(new AphrontFormSubmitControl()) + ->setValue(pht('Continue')) + ->addCancelButton($cancel_uri)); + + $box = id(new PHUIObjectBoxView()) + ->setFormErrors($errors) + ->setHeaderText($title) + ->appendChild($form); + + return $this->buildApplicationPage( + array( + $crumbs, + $box, + ), + array( + 'title' => $title, + )); + } + + + + + } diff --git a/src/applications/almanac/controller/AlmanacServiceViewController.php b/src/applications/almanac/controller/AlmanacServiceViewController.php --- a/src/applications/almanac/controller/AlmanacServiceViewController.php +++ b/src/applications/almanac/controller/AlmanacServiceViewController.php @@ -65,6 +65,10 @@ ->setUser($viewer) ->setObject($service); + $properties->addProperty( + pht('Service Type'), + $service->getServiceType()->getServiceTypeShortName()); + return $properties; } diff --git a/src/applications/almanac/query/AlmanacServiceQuery.php b/src/applications/almanac/query/AlmanacServiceQuery.php --- a/src/applications/almanac/query/AlmanacServiceQuery.php +++ b/src/applications/almanac/query/AlmanacServiceQuery.php @@ -6,6 +6,7 @@ private $ids; private $phids; private $names; + private $serviceClasses; private $needBindings; public function withIDs(array $ids) { @@ -23,6 +24,11 @@ return $this; } + public function withServiceClasses(array $classes) { + $this->serviceClasses = $classes; + return $this; + } + public function needBindings($need_bindings) { $this->needBindings = $need_bindings; return $this; @@ -72,11 +78,35 @@ $hashes); } + if ($this->serviceClasses !== null) { + $where[] = qsprintf( + $conn_r, + 'serviceClass IN (%Ls)', + $this->serviceClasses); + } + $where[] = $this->buildPagingClause($conn_r); return $this->formatWhereClause($where); } + protected function willFilterPage(array $services) { + $service_types = AlmanacServiceType::getAllServiceTypes(); + + foreach ($services as $key => $service) { + $service_class = $service->getServiceClass(); + $service_type = idx($service_types, $service_class); + if (!$service_type) { + $this->didRejectResult($service); + unset($services[$key]); + continue; + } + $service->attachServiceType($service_type); + } + + return $services; + } + protected function didFilterPage(array $services) { if ($this->needBindings) { $service_phids = mpull($services, 'getPHID'); diff --git a/src/applications/almanac/query/AlmanacServiceSearchEngine.php b/src/applications/almanac/query/AlmanacServiceSearchEngine.php --- a/src/applications/almanac/query/AlmanacServiceSearchEngine.php +++ b/src/applications/almanac/query/AlmanacServiceSearchEngine.php @@ -73,7 +73,10 @@ ->setObjectName(pht('Service %d', $service->getID())) ->setHeader($service->getName()) ->setHref($service->getURI()) - ->setObject($service); + ->setObject($service) + ->addIcon( + $service->getServiceType()->getServiceTypeIcon(), + $service->getServiceType()->getServiceTypeShortName()); $list->addItem($item); } diff --git a/src/applications/almanac/servicetype/AlmanacClusterRepositoryServiceType.php b/src/applications/almanac/servicetype/AlmanacClusterRepositoryServiceType.php new file mode 100644 --- /dev/null +++ b/src/applications/almanac/servicetype/AlmanacClusterRepositoryServiceType.php @@ -0,0 +1,19 @@ + Dictionary of available service types. + */ + public static function getAllServiceTypes() { + $types = id(new PhutilSymbolLoader()) + ->setAncestorClass(__CLASS__) + ->loadObjects(); + + return msort($types, 'getServiceTypeName'); + } + + +} diff --git a/src/applications/almanac/storage/AlmanacService.php b/src/applications/almanac/storage/AlmanacService.php --- a/src/applications/almanac/storage/AlmanacService.php +++ b/src/applications/almanac/storage/AlmanacService.php @@ -14,10 +14,12 @@ protected $mailKey; protected $viewPolicy; protected $editPolicy; + protected $serviceClass; private $customFields = self::ATTACHABLE; private $almanacProperties = self::ATTACHABLE; private $bindings = self::ATTACHABLE; + private $serviceType = self::ATTACHABLE; public static function initializeNewService() { return id(new AlmanacService()) @@ -33,6 +35,7 @@ 'name' => 'text128', 'nameIndex' => 'bytes12', 'mailKey' => 'bytes20', + 'serviceClass' => 'text64', ), self::CONFIG_KEY_SCHEMA => array( 'key_name' => array( @@ -42,6 +45,9 @@ 'key_nametext' => array( 'columns' => array('name'), ), + 'key_class' => array( + 'columns' => array('serviceClass'), + ), ), ) + parent::getConfiguration(); } @@ -75,6 +81,15 @@ return $this; } + public function getServiceType() { + return $this->assertAttached($this->serviceType); + } + + public function attachServiceType(AlmanacServiceType $type) { + $this->serviceType = $type; + return $this; + } + /* -( AlmanacPropertyInterface )------------------------------------------- */ diff --git a/src/applications/diffusion/query/DiffusionQuery.php b/src/applications/diffusion/query/DiffusionQuery.php --- a/src/applications/diffusion/query/DiffusionQuery.php +++ b/src/applications/diffusion/query/DiffusionQuery.php @@ -81,6 +81,14 @@ 'be loaded.')); } + $service_type = $service->getServiceType(); + if (!($service_type instanceof AlmanacClusterRepositoryServiceType)) { + throw new Exception( + pht( + 'The Alamnac service for this repository does not have the correct '. + 'service type.')); + } + $bindings = $service->getBindings(); if (!$bindings) { throw new Exception(