1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-26 08:42:41 +01:00

Provide "almanac.binding.search" and "almanac.binding.edit"

Summary:
Depends on D19338. Ref T13120. Ref T12414. These are the last of the new API methods.

This stuff still doesn't work:

  - You can't actually enable/disable bindings yet. I want to take a look at the use cases and consider changing "disabled" to "status", or providing a different way to solve the problem.
  - You can't edit properties via the API. I expect to enable this for all `AlmanacPropertyInterface` objects with an extension in a future change.

Test Plan:
  - Searched for bindings via API.
  - Viewed binding web UI for API methods.
  - Created bindings via API.

Reviewers: amckinley

Reviewed By: amckinley

Maniphest Tasks: T13120, T12414

Differential Revision: https://secure.phabricator.com/D19340
This commit is contained in:
epriestley 2018-04-11 06:24:10 -07:00
parent e502df509d
commit 208504a5e3
7 changed files with 385 additions and 1 deletions

View file

@ -14,12 +14,17 @@ phutil_register_library_map(array(
'AlmanacBindingDeletePropertyTransaction' => 'applications/almanac/xaction/AlmanacBindingDeletePropertyTransaction.php', 'AlmanacBindingDeletePropertyTransaction' => 'applications/almanac/xaction/AlmanacBindingDeletePropertyTransaction.php',
'AlmanacBindingDisableController' => 'applications/almanac/controller/AlmanacBindingDisableController.php', 'AlmanacBindingDisableController' => 'applications/almanac/controller/AlmanacBindingDisableController.php',
'AlmanacBindingDisableTransaction' => 'applications/almanac/xaction/AlmanacBindingDisableTransaction.php', 'AlmanacBindingDisableTransaction' => 'applications/almanac/xaction/AlmanacBindingDisableTransaction.php',
'AlmanacBindingEditConduitAPIMethod' => 'applications/almanac/conduit/AlmanacBindingEditConduitAPIMethod.php',
'AlmanacBindingEditController' => 'applications/almanac/controller/AlmanacBindingEditController.php', 'AlmanacBindingEditController' => 'applications/almanac/controller/AlmanacBindingEditController.php',
'AlmanacBindingEditEngine' => 'applications/almanac/editor/AlmanacBindingEditEngine.php',
'AlmanacBindingEditor' => 'applications/almanac/editor/AlmanacBindingEditor.php', 'AlmanacBindingEditor' => 'applications/almanac/editor/AlmanacBindingEditor.php',
'AlmanacBindingInterfaceTransaction' => 'applications/almanac/xaction/AlmanacBindingInterfaceTransaction.php', 'AlmanacBindingInterfaceTransaction' => 'applications/almanac/xaction/AlmanacBindingInterfaceTransaction.php',
'AlmanacBindingPHIDType' => 'applications/almanac/phid/AlmanacBindingPHIDType.php', 'AlmanacBindingPHIDType' => 'applications/almanac/phid/AlmanacBindingPHIDType.php',
'AlmanacBindingPropertyEditEngine' => 'applications/almanac/editor/AlmanacBindingPropertyEditEngine.php', 'AlmanacBindingPropertyEditEngine' => 'applications/almanac/editor/AlmanacBindingPropertyEditEngine.php',
'AlmanacBindingQuery' => 'applications/almanac/query/AlmanacBindingQuery.php', 'AlmanacBindingQuery' => 'applications/almanac/query/AlmanacBindingQuery.php',
'AlmanacBindingSearchConduitAPIMethod' => 'applications/almanac/conduit/AlmanacBindingSearchConduitAPIMethod.php',
'AlmanacBindingSearchEngine' => 'applications/almanac/query/AlmanacBindingSearchEngine.php',
'AlmanacBindingServiceTransaction' => 'applications/almanac/xaction/AlmanacBindingServiceTransaction.php',
'AlmanacBindingSetPropertyTransaction' => 'applications/almanac/xaction/AlmanacBindingSetPropertyTransaction.php', 'AlmanacBindingSetPropertyTransaction' => 'applications/almanac/xaction/AlmanacBindingSetPropertyTransaction.php',
'AlmanacBindingTableView' => 'applications/almanac/view/AlmanacBindingTableView.php', 'AlmanacBindingTableView' => 'applications/almanac/view/AlmanacBindingTableView.php',
'AlmanacBindingTransaction' => 'applications/almanac/storage/AlmanacBindingTransaction.php', 'AlmanacBindingTransaction' => 'applications/almanac/storage/AlmanacBindingTransaction.php',
@ -5206,16 +5211,22 @@ phutil_register_library_map(array(
'AlmanacPropertyInterface', 'AlmanacPropertyInterface',
'PhabricatorDestructibleInterface', 'PhabricatorDestructibleInterface',
'PhabricatorExtendedPolicyInterface', 'PhabricatorExtendedPolicyInterface',
'PhabricatorConduitResultInterface',
), ),
'AlmanacBindingDeletePropertyTransaction' => 'AlmanacBindingTransactionType', 'AlmanacBindingDeletePropertyTransaction' => 'AlmanacBindingTransactionType',
'AlmanacBindingDisableController' => 'AlmanacServiceController', 'AlmanacBindingDisableController' => 'AlmanacServiceController',
'AlmanacBindingDisableTransaction' => 'AlmanacBindingTransactionType', 'AlmanacBindingDisableTransaction' => 'AlmanacBindingTransactionType',
'AlmanacBindingEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod',
'AlmanacBindingEditController' => 'AlmanacServiceController', 'AlmanacBindingEditController' => 'AlmanacServiceController',
'AlmanacBindingEditEngine' => 'PhabricatorEditEngine',
'AlmanacBindingEditor' => 'AlmanacEditor', 'AlmanacBindingEditor' => 'AlmanacEditor',
'AlmanacBindingInterfaceTransaction' => 'AlmanacBindingTransactionType', 'AlmanacBindingInterfaceTransaction' => 'AlmanacBindingTransactionType',
'AlmanacBindingPHIDType' => 'PhabricatorPHIDType', 'AlmanacBindingPHIDType' => 'PhabricatorPHIDType',
'AlmanacBindingPropertyEditEngine' => 'AlmanacPropertyEditEngine', 'AlmanacBindingPropertyEditEngine' => 'AlmanacPropertyEditEngine',
'AlmanacBindingQuery' => 'AlmanacQuery', 'AlmanacBindingQuery' => 'AlmanacQuery',
'AlmanacBindingSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod',
'AlmanacBindingSearchEngine' => 'PhabricatorApplicationSearchEngine',
'AlmanacBindingServiceTransaction' => 'AlmanacBindingTransactionType',
'AlmanacBindingSetPropertyTransaction' => 'AlmanacBindingTransactionType', 'AlmanacBindingSetPropertyTransaction' => 'AlmanacBindingTransactionType',
'AlmanacBindingTableView' => 'AphrontView', 'AlmanacBindingTableView' => 'AphrontView',
'AlmanacBindingTransaction' => 'AlmanacModularTransaction', 'AlmanacBindingTransaction' => 'AlmanacModularTransaction',

View file

@ -0,0 +1,19 @@
<?php
final class AlmanacBindingEditConduitAPIMethod
extends PhabricatorEditEngineAPIMethod {
public function getAPIMethodName() {
return 'almanac.binding.edit';
}
public function newEditEngine() {
return new AlmanacBindingEditEngine();
}
public function getMethodSummary() {
return pht(
'Apply transactions to create a new binding or edit an existing one.');
}
}

View file

@ -0,0 +1,18 @@
<?php
final class AlmanacBindingSearchConduitAPIMethod
extends PhabricatorSearchEngineAPIMethod {
public function getAPIMethodName() {
return 'almanac.binding.search';
}
public function newSearchEngine() {
return new AlmanacBindingSearchEngine();
}
public function getMethodSummary() {
return pht('Read information about Almanac bindings.');
}
}

View file

@ -0,0 +1,158 @@
<?php
final class AlmanacBindingEditEngine
extends PhabricatorEditEngine {
const ENGINECONST = 'almanac.binding';
private $service;
public function setService(AlmanacService $service) {
$this->service = $service;
return $this;
}
public function getService() {
if (!$this->service) {
throw new PhutilInvalidStateException('setService');
}
return $this->service;
}
public function isEngineConfigurable() {
return false;
}
public function getEngineName() {
return pht('Almanac Bindings');
}
public function getSummaryHeader() {
return pht('Edit Almanac Binding Configurations');
}
public function getSummaryText() {
return pht('This engine is used to edit Almanac bindings.');
}
public function getEngineApplicationClass() {
return 'PhabricatorAlmanacApplication';
}
protected function newEditableObject() {
$service = $this->getService();
return AlmanacBinding::initializeNewBinding($service);
}
protected function newEditableObjectForDocumentation() {
$service_type = AlmanacCustomServiceType::SERVICETYPE;
$service = AlmanacService::initializeNewService($service_type);
$this->setService($service);
return $this->newEditableObject();
}
protected function newEditableObjectFromConduit(array $raw_xactions) {
$service_phid = null;
foreach ($raw_xactions as $raw_xaction) {
if ($raw_xaction['type'] !== 'service') {
continue;
}
$service_phid = $raw_xaction['value'];
}
if ($service_phid === null) {
throw new Exception(
pht(
'When creating a new Almanac binding via the Conduit API, you '.
'must provide a "service" transaction to select a service to bind.'));
}
$service = id(new AlmanacServiceQuery())
->setViewer($this->getViewer())
->withPHIDs(array($service_phid))
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->executeOne();
if (!$service) {
throw new Exception(
pht(
'Service "%s" is unrecognized, restricted, or you do not have '.
'permission to edit it.',
$service_phid));
}
$this->setService($service);
return $this->newEditableObject();
}
protected function newObjectQuery() {
return new AlmanacBindingQuery();
}
protected function getObjectCreateTitleText($object) {
return pht('Create Binding');
}
protected function getObjectCreateButtonText($object) {
return pht('Create Binding');
}
protected function getObjectEditTitleText($object) {
return pht('Edit Binding');
}
protected function getObjectEditShortText($object) {
return pht('Edit Binding');
}
protected function getObjectCreateShortText() {
return pht('Create Binding');
}
protected function getObjectName() {
return pht('Binding');
}
protected function getEditorURI() {
return '/almanac/binding/edit/';
}
protected function getObjectCreateCancelURI($object) {
return '/almanac/binding/';
}
protected function getObjectViewURI($object) {
return $object->getURI();
}
protected function buildCustomEditFields($object) {
return array(
id(new PhabricatorTextEditField())
->setKey('service')
->setLabel(pht('Service'))
->setIsConduitOnly(true)
->setTransactionType(
AlmanacBindingServiceTransaction::TRANSACTIONTYPE)
->setDescription(pht('Service to create a binding for.'))
->setConduitDescription(pht('Select the service to bind.'))
->setConduitTypeDescription(pht('Service PHID.'))
->setValue($object->getServicePHID()),
id(new PhabricatorTextEditField())
->setKey('interface')
->setLabel(pht('Interface'))
->setIsConduitOnly(true)
->setTransactionType(
AlmanacBindingInterfaceTransaction::TRANSACTIONTYPE)
->setDescription(pht('Interface to bind the service to.'))
->setConduitDescription(pht('Set the interface to bind.'))
->setConduitTypeDescription(pht('Interface PHID.'))
->setValue($object->getInterfacePHID()),
);
}
}

View file

@ -0,0 +1,80 @@
<?php
final class AlmanacBindingSearchEngine
extends PhabricatorApplicationSearchEngine {
public function getResultTypeDescription() {
return pht('Almanac Bindings');
}
public function getApplicationClassName() {
return 'PhabricatorAlmanacApplication';
}
public function newQuery() {
return new AlmanacBindingQuery();
}
protected function buildCustomSearchFields() {
return array(
id(new PhabricatorPHIDsSearchField())
->setLabel(pht('Services'))
->setKey('servicePHIDs')
->setAliases(array('service', 'servicePHID', 'services'))
->setDescription(pht('Search for bindings on particular services.')),
id(new PhabricatorPHIDsSearchField())
->setLabel(pht('Devices'))
->setKey('devicePHIDs')
->setAliases(array('device', 'devicePHID', 'devices'))
->setDescription(pht('Search for bindings on particular devices.')),
);
}
protected function buildQueryFromParameters(array $map) {
$query = $this->newQuery();
if ($map['servicePHIDs']) {
$query->withServicePHIDs($map['servicePHIDs']);
}
if ($map['devicePHIDs']) {
$query->withDevicePHIDs($map['devicePHIDs']);
}
return $query;
}
protected function getURI($path) {
return '/almanac/binding/'.$path;
}
protected function getBuiltinQueryNames() {
$names = array(
'all' => pht('All Bindings'),
);
return $names;
}
public function buildSavedQueryFromBuiltin($query_key) {
$query = $this->newSavedQuery();
$query->setQueryKey($query_key);
switch ($query_key) {
case 'all':
return $query;
}
return parent::buildSavedQueryFromBuiltin($query_key);
}
protected function renderResultList(
array $devices,
PhabricatorSavedQuery $query,
array $handles) {
// For now, this SearchEngine just supports API access via Conduit.
throw new PhutilMethodNotImplementedException();
}
}

View file

@ -7,7 +7,8 @@ final class AlmanacBinding
PhabricatorApplicationTransactionInterface, PhabricatorApplicationTransactionInterface,
AlmanacPropertyInterface, AlmanacPropertyInterface,
PhabricatorDestructibleInterface, PhabricatorDestructibleInterface,
PhabricatorExtendedPolicyInterface { PhabricatorExtendedPolicyInterface,
PhabricatorConduitResultInterface {
protected $servicePHID; protected $servicePHID;
protected $devicePHID; protected $devicePHID;
@ -23,6 +24,7 @@ final class AlmanacBinding
public static function initializeNewBinding(AlmanacService $service) { public static function initializeNewBinding(AlmanacService $service) {
return id(new AlmanacBinding()) return id(new AlmanacBinding())
->setServicePHID($service->getPHID()) ->setServicePHID($service->getPHID())
->attachService($service)
->attachAlmanacProperties(array()) ->attachAlmanacProperties(array())
->setIsDisabled(0); ->setIsDisabled(0);
} }
@ -225,4 +227,36 @@ final class AlmanacBinding
} }
/* -( PhabricatorConduitResultInterface )---------------------------------- */
public function getFieldSpecificationsForConduit() {
return array(
id(new PhabricatorConduitSearchFieldSpecification())
->setKey('servicePHID')
->setType('phid')
->setDescription(pht('The bound service.')),
id(new PhabricatorConduitSearchFieldSpecification())
->setKey('devicePHID')
->setType('phid')
->setDescription(pht('The device the service is bound to.')),
id(new PhabricatorConduitSearchFieldSpecification())
->setKey('interfacePHID')
->setType('phid')
->setDescription(pht('The interface the service is bound to.')),
);
}
public function getFieldValuesForConduit() {
return array(
'servicePHID' => $this->getServicePHID(),
'devicePHID' => $this->getDevicePHID(),
'interfacePHID' => $this->getInterfacePHID(),
);
}
public function getConduitSearchAttachments() {
return array();
}
} }

View file

@ -0,0 +1,64 @@
<?php
final class AlmanacBindingServiceTransaction
extends AlmanacBindingTransactionType {
const TRANSACTIONTYPE = 'almanac:binding:service';
public function generateOldValue($object) {
return $object->getServicePHID();
}
public function applyInternalEffects($object, $value) {
$object->setServicePHID($value);
}
public function validateTransactions($object, array $xactions) {
$errors = array();
$service_phid = $object->getServicePHID();
if ($this->isEmptyTextTransaction($service_phid, $xactions)) {
$errors[] = $this->newRequiredError(
pht('Bindings must have a service.'));
}
foreach ($xactions as $xaction) {
if (!$this->isNewObject()) {
$errors[] = $this->newInvalidError(
pht(
'The service for a binding can not be changed once it has '.
'been created.'),
$xaction);
continue;
}
$service_phid = $xaction->getNewValue();
$services = id(new AlmanacServiceQuery())
->setViewer($this->getActor())
->withPHIDs(array($service_phid))
->execute();
if (!$services) {
$errors[] = $this->newInvalidError(
pht('You can not bind a nonexistent or restricted service.'),
$xaction);
continue;
}
$service = head($services);
$can_edit = PhabricatorPolicyFilter::hasCapability(
$this->getActor(),
$service,
PhabricatorPolicyCapability::CAN_EDIT);
if (!$can_edit) {
$errors[] = $this->newInvalidError(
pht(
'You can not bind a service which you do not have permission '.
'to edit.'));
continue;
}
}
return $errors;
}
}