mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-26 08:42:41 +01:00
Give Almanac generic, custom-field-based properties
Summary: Ref T5833. Currently, we have an `AlmanacDeviceProperty`, but it doesn't use CustomFields and is specific to devices. Make this more generic: - Reuse most of the CustomField infrastructure (so we can eventually get easy support for nice editor UIs, etc). - Make properties more generic so Services, Bindings and Devices can all have them. The major difference between this implementation and existing CustomField implementations is that all other implementations are application-authoritative: the application code determines what the available list of fields is. I want Almanac to be a bit more freeform (basically: you can write whatever properties you want, and we'll put nice UIs on them if we have a nice UI available). For example, we might have some sort of "ServiceTemplate" that says "a database binding should usually have the fields 'writable', 'active', 'credential'", which would do things like offer these as options and put a nice UI on them, but you should also be able to write whatever other properties you want and add services without building a specific service template for them. This involves a little bit of rule bending, but ends up pretty clean. We can adjust CustomField to accommodate this a bit more gracefully later on if it makes sense. Test Plan: {F229172} Reviewers: btrahan Reviewed By: btrahan Subscribers: epriestley Maniphest Tasks: T5833 Differential Revision: https://secure.phabricator.com/D10777
This commit is contained in:
parent
dd7d8cf910
commit
2f1b5ae010
18 changed files with 598 additions and 36 deletions
1
resources/sql/autopatches/20141103.almanac.1.delprop.sql
Normal file
1
resources/sql/autopatches/20141103.almanac.1.delprop.sql
Normal file
|
@ -0,0 +1 @@
|
||||||
|
DROP TABLE {$NAMESPACE}_almanac.almanac_deviceproperty;
|
8
resources/sql/autopatches/20141103.almanac.2.addprop.sql
Normal file
8
resources/sql/autopatches/20141103.almanac.2.addprop.sql
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
CREATE TABLE {$NAMESPACE}_almanac.almanac_property (
|
||||||
|
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
objectPHID VARBINARY(64) NOT NULL,
|
||||||
|
fieldIndex BINARY(12) NOT NULL,
|
||||||
|
fieldName VARCHAR(128) NOT NULL COLLATE {$COLLATE_TEXT},
|
||||||
|
fieldValue LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT},
|
||||||
|
UNIQUE KEY `objectPHID` (objectPHID, fieldIndex)
|
||||||
|
) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};
|
|
@ -22,9 +22,11 @@ phutil_register_library_map(array(
|
||||||
'AlmanacConduitUtil' => 'applications/almanac/util/AlmanacConduitUtil.php',
|
'AlmanacConduitUtil' => 'applications/almanac/util/AlmanacConduitUtil.php',
|
||||||
'AlmanacConsoleController' => 'applications/almanac/controller/AlmanacConsoleController.php',
|
'AlmanacConsoleController' => 'applications/almanac/controller/AlmanacConsoleController.php',
|
||||||
'AlmanacController' => 'applications/almanac/controller/AlmanacController.php',
|
'AlmanacController' => 'applications/almanac/controller/AlmanacController.php',
|
||||||
|
'AlmanacCoreCustomField' => 'applications/almanac/customfield/AlmanacCoreCustomField.php',
|
||||||
'AlmanacCreateDevicesCapability' => 'applications/almanac/capability/AlmanacCreateDevicesCapability.php',
|
'AlmanacCreateDevicesCapability' => 'applications/almanac/capability/AlmanacCreateDevicesCapability.php',
|
||||||
'AlmanacCreateNetworksCapability' => 'applications/almanac/capability/AlmanacCreateNetworksCapability.php',
|
'AlmanacCreateNetworksCapability' => 'applications/almanac/capability/AlmanacCreateNetworksCapability.php',
|
||||||
'AlmanacCreateServicesCapability' => 'applications/almanac/capability/AlmanacCreateServicesCapability.php',
|
'AlmanacCreateServicesCapability' => 'applications/almanac/capability/AlmanacCreateServicesCapability.php',
|
||||||
|
'AlmanacCustomField' => 'applications/almanac/customfield/AlmanacCustomField.php',
|
||||||
'AlmanacDAO' => 'applications/almanac/storage/AlmanacDAO.php',
|
'AlmanacDAO' => 'applications/almanac/storage/AlmanacDAO.php',
|
||||||
'AlmanacDevice' => 'applications/almanac/storage/AlmanacDevice.php',
|
'AlmanacDevice' => 'applications/almanac/storage/AlmanacDevice.php',
|
||||||
'AlmanacDeviceController' => 'applications/almanac/controller/AlmanacDeviceController.php',
|
'AlmanacDeviceController' => 'applications/almanac/controller/AlmanacDeviceController.php',
|
||||||
|
@ -32,7 +34,6 @@ phutil_register_library_map(array(
|
||||||
'AlmanacDeviceEditor' => 'applications/almanac/editor/AlmanacDeviceEditor.php',
|
'AlmanacDeviceEditor' => 'applications/almanac/editor/AlmanacDeviceEditor.php',
|
||||||
'AlmanacDeviceListController' => 'applications/almanac/controller/AlmanacDeviceListController.php',
|
'AlmanacDeviceListController' => 'applications/almanac/controller/AlmanacDeviceListController.php',
|
||||||
'AlmanacDevicePHIDType' => 'applications/almanac/phid/AlmanacDevicePHIDType.php',
|
'AlmanacDevicePHIDType' => 'applications/almanac/phid/AlmanacDevicePHIDType.php',
|
||||||
'AlmanacDeviceProperty' => 'applications/almanac/storage/AlmanacDeviceProperty.php',
|
|
||||||
'AlmanacDeviceQuery' => 'applications/almanac/query/AlmanacDeviceQuery.php',
|
'AlmanacDeviceQuery' => 'applications/almanac/query/AlmanacDeviceQuery.php',
|
||||||
'AlmanacDeviceSearchEngine' => 'applications/almanac/query/AlmanacDeviceSearchEngine.php',
|
'AlmanacDeviceSearchEngine' => 'applications/almanac/query/AlmanacDeviceSearchEngine.php',
|
||||||
'AlmanacDeviceTransaction' => 'applications/almanac/storage/AlmanacDeviceTransaction.php',
|
'AlmanacDeviceTransaction' => 'applications/almanac/storage/AlmanacDeviceTransaction.php',
|
||||||
|
@ -59,6 +60,11 @@ phutil_register_library_map(array(
|
||||||
'AlmanacNetworkTransaction' => 'applications/almanac/storage/AlmanacNetworkTransaction.php',
|
'AlmanacNetworkTransaction' => 'applications/almanac/storage/AlmanacNetworkTransaction.php',
|
||||||
'AlmanacNetworkTransactionQuery' => 'applications/almanac/query/AlmanacNetworkTransactionQuery.php',
|
'AlmanacNetworkTransactionQuery' => 'applications/almanac/query/AlmanacNetworkTransactionQuery.php',
|
||||||
'AlmanacNetworkViewController' => 'applications/almanac/controller/AlmanacNetworkViewController.php',
|
'AlmanacNetworkViewController' => 'applications/almanac/controller/AlmanacNetworkViewController.php',
|
||||||
|
'AlmanacProperty' => 'applications/almanac/storage/AlmanacProperty.php',
|
||||||
|
'AlmanacPropertyController' => 'applications/almanac/controller/AlmanacPropertyController.php',
|
||||||
|
'AlmanacPropertyEditController' => 'applications/almanac/controller/AlmanacPropertyEditController.php',
|
||||||
|
'AlmanacPropertyInterface' => 'applications/almanac/property/AlmanacPropertyInterface.php',
|
||||||
|
'AlmanacPropertyQuery' => 'applications/almanac/query/AlmanacPropertyQuery.php',
|
||||||
'AlmanacService' => 'applications/almanac/storage/AlmanacService.php',
|
'AlmanacService' => 'applications/almanac/storage/AlmanacService.php',
|
||||||
'AlmanacServiceController' => 'applications/almanac/controller/AlmanacServiceController.php',
|
'AlmanacServiceController' => 'applications/almanac/controller/AlmanacServiceController.php',
|
||||||
'AlmanacServiceEditController' => 'applications/almanac/controller/AlmanacServiceEditController.php',
|
'AlmanacServiceEditController' => 'applications/almanac/controller/AlmanacServiceEditController.php',
|
||||||
|
@ -2978,9 +2984,14 @@ phutil_register_library_map(array(
|
||||||
'AlmanacConduitUtil' => 'Phobject',
|
'AlmanacConduitUtil' => 'Phobject',
|
||||||
'AlmanacConsoleController' => 'AlmanacController',
|
'AlmanacConsoleController' => 'AlmanacController',
|
||||||
'AlmanacController' => 'PhabricatorController',
|
'AlmanacController' => 'PhabricatorController',
|
||||||
|
'AlmanacCoreCustomField' => array(
|
||||||
|
'AlmanacCustomField',
|
||||||
|
'PhabricatorStandardCustomFieldInterface',
|
||||||
|
),
|
||||||
'AlmanacCreateDevicesCapability' => 'PhabricatorPolicyCapability',
|
'AlmanacCreateDevicesCapability' => 'PhabricatorPolicyCapability',
|
||||||
'AlmanacCreateNetworksCapability' => 'PhabricatorPolicyCapability',
|
'AlmanacCreateNetworksCapability' => 'PhabricatorPolicyCapability',
|
||||||
'AlmanacCreateServicesCapability' => 'PhabricatorPolicyCapability',
|
'AlmanacCreateServicesCapability' => 'PhabricatorPolicyCapability',
|
||||||
|
'AlmanacCustomField' => 'PhabricatorCustomField',
|
||||||
'AlmanacDAO' => 'PhabricatorLiskDAO',
|
'AlmanacDAO' => 'PhabricatorLiskDAO',
|
||||||
'AlmanacDevice' => array(
|
'AlmanacDevice' => array(
|
||||||
'AlmanacDAO',
|
'AlmanacDAO',
|
||||||
|
@ -2991,7 +3002,6 @@ phutil_register_library_map(array(
|
||||||
'AlmanacDeviceEditor' => 'PhabricatorApplicationTransactionEditor',
|
'AlmanacDeviceEditor' => 'PhabricatorApplicationTransactionEditor',
|
||||||
'AlmanacDeviceListController' => 'AlmanacDeviceController',
|
'AlmanacDeviceListController' => 'AlmanacDeviceController',
|
||||||
'AlmanacDevicePHIDType' => 'PhabricatorPHIDType',
|
'AlmanacDevicePHIDType' => 'PhabricatorPHIDType',
|
||||||
'AlmanacDeviceProperty' => 'AlmanacDAO',
|
|
||||||
'AlmanacDeviceQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
|
'AlmanacDeviceQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
|
||||||
'AlmanacDeviceSearchEngine' => 'PhabricatorApplicationSearchEngine',
|
'AlmanacDeviceSearchEngine' => 'PhabricatorApplicationSearchEngine',
|
||||||
'AlmanacDeviceTransaction' => 'PhabricatorApplicationTransaction',
|
'AlmanacDeviceTransaction' => 'PhabricatorApplicationTransaction',
|
||||||
|
@ -3024,9 +3034,19 @@ phutil_register_library_map(array(
|
||||||
'AlmanacNetworkTransaction' => 'PhabricatorApplicationTransaction',
|
'AlmanacNetworkTransaction' => 'PhabricatorApplicationTransaction',
|
||||||
'AlmanacNetworkTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
|
'AlmanacNetworkTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
|
||||||
'AlmanacNetworkViewController' => 'AlmanacNetworkController',
|
'AlmanacNetworkViewController' => 'AlmanacNetworkController',
|
||||||
|
'AlmanacProperty' => array(
|
||||||
|
'PhabricatorCustomFieldStorage',
|
||||||
|
'PhabricatorPolicyInterface',
|
||||||
|
),
|
||||||
|
'AlmanacPropertyController' => 'AlmanacController',
|
||||||
|
'AlmanacPropertyEditController' => 'AlmanacDeviceController',
|
||||||
|
'AlmanacPropertyQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
|
||||||
'AlmanacService' => array(
|
'AlmanacService' => array(
|
||||||
'AlmanacDAO',
|
'AlmanacDAO',
|
||||||
'PhabricatorPolicyInterface',
|
'PhabricatorPolicyInterface',
|
||||||
|
'PhabricatorCustomFieldInterface',
|
||||||
|
'PhabricatorApplicationTransactionInterface',
|
||||||
|
'AlmanacPropertyInterface',
|
||||||
),
|
),
|
||||||
'AlmanacServiceController' => 'AlmanacController',
|
'AlmanacServiceController' => 'AlmanacController',
|
||||||
'AlmanacServiceEditController' => 'AlmanacServiceController',
|
'AlmanacServiceEditController' => 'AlmanacServiceController',
|
||||||
|
|
|
@ -56,6 +56,9 @@ final class PhabricatorAlmanacApplication extends PhabricatorApplication {
|
||||||
'edit/(?:(?P<id>\d+)/)?' => 'AlmanacNetworkEditController',
|
'edit/(?:(?P<id>\d+)/)?' => 'AlmanacNetworkEditController',
|
||||||
'(?P<id>\d+)/' => 'AlmanacNetworkViewController',
|
'(?P<id>\d+)/' => 'AlmanacNetworkViewController',
|
||||||
),
|
),
|
||||||
|
'property/' => array(
|
||||||
|
'edit/(?:(?P<id>\d+)/)?' => 'AlmanacPropertyEditController',
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,65 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
abstract class AlmanacController
|
abstract class AlmanacController
|
||||||
extends PhabricatorController {}
|
extends PhabricatorController {
|
||||||
|
|
||||||
|
|
||||||
|
protected function buildAlmanacPropertiesTable($object) {
|
||||||
|
$viewer = $this->getViewer();
|
||||||
|
|
||||||
|
$properties = id(new AlmanacPropertyQuery())
|
||||||
|
->setViewer($viewer)
|
||||||
|
->withObjectPHIDs(array($object->getPHID()))
|
||||||
|
->execute();
|
||||||
|
|
||||||
|
$rows = array();
|
||||||
|
foreach ($properties as $property) {
|
||||||
|
$value = $property->getFieldValue();
|
||||||
|
|
||||||
|
$rows[] = array(
|
||||||
|
$property->getFieldName(),
|
||||||
|
PhabricatorConfigJSON::prettyPrintJSON($value),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$table = id(new AphrontTableView($rows))
|
||||||
|
->setNoDataString(pht('No properties.'))
|
||||||
|
->setHeaders(
|
||||||
|
array(
|
||||||
|
pht('Name'),
|
||||||
|
pht('Value'),
|
||||||
|
))
|
||||||
|
->setColumnClasses(
|
||||||
|
array(
|
||||||
|
null,
|
||||||
|
'wide',
|
||||||
|
));
|
||||||
|
|
||||||
|
$phid = $object->getPHID();
|
||||||
|
$add_uri = $this->getApplicationURI("property/edit/?objectPHID={$phid}");
|
||||||
|
|
||||||
|
$can_edit = PhabricatorPolicyFilter::hasCapability(
|
||||||
|
$viewer,
|
||||||
|
$object,
|
||||||
|
PhabricatorPolicyCapability::CAN_EDIT);
|
||||||
|
|
||||||
|
$add_button = id(new PHUIButtonView())
|
||||||
|
->setTag('a')
|
||||||
|
->setHref($add_uri)
|
||||||
|
->setWorkflow(true)
|
||||||
|
->setDisabled(!$can_edit)
|
||||||
|
->setText(pht('Add Property'))
|
||||||
|
->setIcon(
|
||||||
|
id(new PHUIIconView())
|
||||||
|
->setIconFont('fa-plus'));
|
||||||
|
|
||||||
|
$header = id(new PHUIHeaderView())
|
||||||
|
->setHeader(pht('Properties'))
|
||||||
|
->addActionLink($add_button);
|
||||||
|
|
||||||
|
return id(new PHUIObjectBoxView())
|
||||||
|
->setHeader($header)
|
||||||
|
->appendChild($table);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
abstract class AlmanacPropertyController extends AlmanacController {}
|
|
@ -0,0 +1,169 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
final class AlmanacPropertyEditController
|
||||||
|
extends AlmanacDeviceController {
|
||||||
|
|
||||||
|
public function handleRequest(AphrontRequest $request) {
|
||||||
|
$viewer = $request->getViewer();
|
||||||
|
|
||||||
|
$id = $request->getURIData('id');
|
||||||
|
if ($id) {
|
||||||
|
$property = id(new AlmanacPropertyQuery())
|
||||||
|
->setViewer($viewer)
|
||||||
|
->withIDs(array($id))
|
||||||
|
->requireCapabilities(
|
||||||
|
array(
|
||||||
|
PhabricatorPolicyCapability::CAN_VIEW,
|
||||||
|
PhabricatorPolicyCapability::CAN_EDIT,
|
||||||
|
))
|
||||||
|
->executeOne();
|
||||||
|
if (!$property) {
|
||||||
|
return new Aphront404Response();
|
||||||
|
}
|
||||||
|
|
||||||
|
$object = $property->getObject();
|
||||||
|
|
||||||
|
$is_new = false;
|
||||||
|
$title = pht('Edit Property');
|
||||||
|
$save_button = pht('Save Changes');
|
||||||
|
} else {
|
||||||
|
$object = id(new PhabricatorObjectQuery())
|
||||||
|
->setViewer($viewer)
|
||||||
|
->withPHIDs(array($request->getStr('objectPHID')))
|
||||||
|
->requireCapabilities(
|
||||||
|
array(
|
||||||
|
PhabricatorPolicyCapability::CAN_VIEW,
|
||||||
|
PhabricatorPolicyCapability::CAN_EDIT,
|
||||||
|
))
|
||||||
|
->executeOne();
|
||||||
|
if (!$object) {
|
||||||
|
return new Aphront404Response();
|
||||||
|
}
|
||||||
|
|
||||||
|
$is_new = true;
|
||||||
|
$title = pht('Add Property');
|
||||||
|
$save_button = pht('Add Property');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!($object instanceof AlmanacPropertyInterface)) {
|
||||||
|
return new Aphront404Response();
|
||||||
|
}
|
||||||
|
|
||||||
|
$cancel_uri = $object->getURI();
|
||||||
|
|
||||||
|
if ($is_new) {
|
||||||
|
$errors = array();
|
||||||
|
$property = null;
|
||||||
|
|
||||||
|
$v_name = null;
|
||||||
|
$e_name = true;
|
||||||
|
|
||||||
|
if ($request->isFormPost()) {
|
||||||
|
$name = $request->getStr('name');
|
||||||
|
if (!strlen($name)) {
|
||||||
|
$e_name = pht('Required');
|
||||||
|
$errors[] = pht('You must provide a property name.');
|
||||||
|
} else {
|
||||||
|
$caught = null;
|
||||||
|
try {
|
||||||
|
AlmanacNames::validateServiceOrDeviceName($name);
|
||||||
|
} catch (Exception $ex) {
|
||||||
|
$caught = $ex;
|
||||||
|
}
|
||||||
|
if ($caught) {
|
||||||
|
$e_name = pht('Invalid');
|
||||||
|
$errors[] = $caught->getMessage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$errors) {
|
||||||
|
$property = id(new AlmanacPropertyQuery())
|
||||||
|
->setViewer($viewer)
|
||||||
|
->withObjectPHIDs(array($object->getPHID()))
|
||||||
|
->withNames(array($name))
|
||||||
|
->requireCapabilities(
|
||||||
|
array(
|
||||||
|
PhabricatorPolicyCapability::CAN_VIEW,
|
||||||
|
PhabricatorPolicyCapability::CAN_EDIT,
|
||||||
|
))
|
||||||
|
->executeOne();
|
||||||
|
if (!$property) {
|
||||||
|
$property = id(new AlmanacProperty())
|
||||||
|
->setObjectPHID($object->getPHID())
|
||||||
|
->setFieldName($name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$property) {
|
||||||
|
$form = id(new AphrontFormView())
|
||||||
|
->setUser($viewer)
|
||||||
|
->appendChild(
|
||||||
|
id(new AphrontFormTextControl())
|
||||||
|
->setName('name')
|
||||||
|
->setLabel(pht('Name'))
|
||||||
|
->setValue($v_name)
|
||||||
|
->setError($e_name));
|
||||||
|
|
||||||
|
return $this->newDialog()
|
||||||
|
->setTitle($title)
|
||||||
|
->setErrors($errors)
|
||||||
|
->addHiddenInput('objectPHID', $request->getStr('objectPHID'))
|
||||||
|
->appendForm($form)
|
||||||
|
->addSubmitButton(pht('Continue'))
|
||||||
|
->addCancelButton($cancel_uri);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$v_name = $property->getFieldName();
|
||||||
|
$e_name = true;
|
||||||
|
|
||||||
|
$v_value = $property->getFieldValue();
|
||||||
|
$e_value = null;
|
||||||
|
|
||||||
|
$object->attachAlmanacProperties(array($property));
|
||||||
|
|
||||||
|
$field_list = PhabricatorCustomField::getObjectFields(
|
||||||
|
$object,
|
||||||
|
PhabricatorCustomField::ROLE_EDIT);
|
||||||
|
$field_list
|
||||||
|
->setViewer($viewer)
|
||||||
|
->readFieldsFromStorage($object);
|
||||||
|
|
||||||
|
$validation_exception = null;
|
||||||
|
if ($request->isFormPost() && $request->getStr('isValueEdit')) {
|
||||||
|
$xactions = $field_list->buildFieldTransactionsFromRequest(
|
||||||
|
$object->getApplicationTransactionTemplate(),
|
||||||
|
$request);
|
||||||
|
|
||||||
|
$editor = $object->getApplicationTransactionEditor()
|
||||||
|
->setActor($viewer)
|
||||||
|
->setContentSourceFromRequest($request)
|
||||||
|
->setContinueOnNoEffect(true)
|
||||||
|
->setContinueOnMissingFields(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$editor->applyTransactions($object, $xactions);
|
||||||
|
return id(new AphrontRedirectResponse())->setURI($cancel_uri);
|
||||||
|
} catch (PhabricatorApplicationTransactionValidationException $ex) {
|
||||||
|
$validation_exception = $ex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$form = id(new AphrontFormView())
|
||||||
|
->setUser($viewer)
|
||||||
|
->addHiddenInput('objectPHID', $request->getStr('objectPHID'))
|
||||||
|
->addHiddenInput('name', $request->getStr('name'))
|
||||||
|
->addHiddenInput('isValueEdit', true);
|
||||||
|
|
||||||
|
$field_list->appendFieldsToForm($form);
|
||||||
|
|
||||||
|
return $this->newDialog()
|
||||||
|
->setTitle($title)
|
||||||
|
->setValidationException($validation_exception)
|
||||||
|
->appendForm($form)
|
||||||
|
->addSubmitButton($save_button)
|
||||||
|
->addCancelButton($cancel_uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -15,6 +15,7 @@ final class AlmanacServiceViewController
|
||||||
$service = id(new AlmanacServiceQuery())
|
$service = id(new AlmanacServiceQuery())
|
||||||
->setViewer($viewer)
|
->setViewer($viewer)
|
||||||
->withNames(array($name))
|
->withNames(array($name))
|
||||||
|
->needProperties(true)
|
||||||
->executeOne();
|
->executeOne();
|
||||||
if (!$service) {
|
if (!$service) {
|
||||||
return new Aphront404Response();
|
return new Aphront404Response();
|
||||||
|
@ -56,6 +57,7 @@ final class AlmanacServiceViewController
|
||||||
$crumbs,
|
$crumbs,
|
||||||
$box,
|
$box,
|
||||||
$bindings,
|
$bindings,
|
||||||
|
$this->buildAlmanacPropertiesTable($service),
|
||||||
$xaction_view,
|
$xaction_view,
|
||||||
),
|
),
|
||||||
array(
|
array(
|
||||||
|
|
|
@ -0,0 +1,62 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
final class AlmanacCoreCustomField
|
||||||
|
extends AlmanacCustomField
|
||||||
|
implements PhabricatorStandardCustomFieldInterface {
|
||||||
|
|
||||||
|
public function getStandardCustomFieldNamespace() {
|
||||||
|
return 'almanac:core';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function createFields($object) {
|
||||||
|
$specs = array();
|
||||||
|
|
||||||
|
foreach ($object->getAlmanacProperties() as $property) {
|
||||||
|
$specs[$property->getFieldName()] = array(
|
||||||
|
'name' => $property->getFieldName(),
|
||||||
|
'type' => 'text',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return PhabricatorStandardCustomField::buildStandardFields($this, $specs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function shouldUseStorage() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function readValueFromObject(PhabricatorCustomFieldInterface $object) {
|
||||||
|
$key = $this->getProxy()->getRawStandardFieldKey();
|
||||||
|
$this->setValueFromStorage($object->getAlmanacPropertyValue($key));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function applyApplicationTransactionInternalEffects(
|
||||||
|
PhabricatorApplicationTransaction $xaction) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function applyApplicationTransactionExternalEffects(
|
||||||
|
PhabricatorApplicationTransaction $xaction) {
|
||||||
|
|
||||||
|
$object = $this->getObject();
|
||||||
|
$phid = $object->getPHID();
|
||||||
|
$key = $this->getProxy()->getRawStandardFieldKey();
|
||||||
|
|
||||||
|
$property = id(new AlmanacPropertyQuery())
|
||||||
|
->setViewer($this->getViewer())
|
||||||
|
->withObjectPHIDs(array($phid))
|
||||||
|
->withNames(array($key))
|
||||||
|
->executeOne();
|
||||||
|
if (!$property) {
|
||||||
|
$property = id(new AlmanacProperty())
|
||||||
|
->setObjectPHID($phid)
|
||||||
|
->setFieldIndex(PhabricatorHash::digestForIndex($key))
|
||||||
|
->setFieldName($key);
|
||||||
|
}
|
||||||
|
|
||||||
|
$property
|
||||||
|
->setFieldValue($xaction->getNewValue())
|
||||||
|
->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
abstract class AlmanacCustomField
|
||||||
|
extends PhabricatorCustomField {}
|
|
@ -25,15 +25,15 @@ final class AlmanacManagementRegisterWorkflow
|
||||||
->setName(php_uname('n'))
|
->setName(php_uname('n'))
|
||||||
->save();
|
->save();
|
||||||
|
|
||||||
id(new AlmanacDeviceProperty())
|
id(new AlmanacProperty())
|
||||||
->setDevicePHID($host->getPHID())
|
->setObjectPHID($host->getPHID())
|
||||||
->setKey('conduitPublicOpenSSHKey')
|
->setName('conduitPublicOpenSSHKey')
|
||||||
->setValue($public_key)
|
->setValue($public_key)
|
||||||
->save();
|
->save();
|
||||||
|
|
||||||
id(new AlmanacDeviceProperty())
|
id(new AlmanacProperty())
|
||||||
->setDevicePHID($host->getPHID())
|
->setObjectPHID($host->getPHID())
|
||||||
->setKey('conduitPublicOpenSSLKey')
|
->setName('conduitPublicOpenSSLKey')
|
||||||
->setValue($this->convertToOpenSSLPublicKey($public_key))
|
->setValue($this->convertToOpenSSLPublicKey($public_key))
|
||||||
->save();
|
->save();
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
interface AlmanacPropertyInterface {
|
||||||
|
|
||||||
|
public function attachAlmanacProperties(array $properties);
|
||||||
|
public function getAlmanacProperties();
|
||||||
|
public function hasAlmanacProperty($key);
|
||||||
|
public function getAlmanacProperty($key);
|
||||||
|
public function getAlmanacPropertyValue($key, $default = null);
|
||||||
|
|
||||||
|
}
|
86
src/applications/almanac/query/AlmanacPropertyQuery.php
Normal file
86
src/applications/almanac/query/AlmanacPropertyQuery.php
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
final class AlmanacPropertyQuery
|
||||||
|
extends PhabricatorCursorPagedPolicyAwareQuery {
|
||||||
|
|
||||||
|
private $objectPHIDs;
|
||||||
|
private $names;
|
||||||
|
|
||||||
|
public function withObjectPHIDs(array $phids) {
|
||||||
|
$this->phids = $phids;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function withNames(array $names) {
|
||||||
|
$this->names = $names;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function loadPage() {
|
||||||
|
$table = new AlmanacProperty();
|
||||||
|
$conn_r = $table->establishConnection('r');
|
||||||
|
|
||||||
|
$data = queryfx_all(
|
||||||
|
$conn_r,
|
||||||
|
'SELECT * FROM %T %Q %Q %Q',
|
||||||
|
$table->getTableName(),
|
||||||
|
$this->buildWhereClause($conn_r),
|
||||||
|
$this->buildOrderClause($conn_r),
|
||||||
|
$this->buildLimitClause($conn_r));
|
||||||
|
|
||||||
|
return $table->loadAllFromArray($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function willFilterPage(array $properties) {
|
||||||
|
$object_phids = mpull($properties, 'getObjectPHID');
|
||||||
|
|
||||||
|
$objects = id(new PhabricatorObjectQuery())
|
||||||
|
->setViewer($this->getViewer())
|
||||||
|
->setParentQuery($this)
|
||||||
|
->withPHIDs($object_phids)
|
||||||
|
->execute();
|
||||||
|
$objects = mpull($objects, null, 'getPHID');
|
||||||
|
|
||||||
|
foreach ($properties as $key => $property) {
|
||||||
|
$object = idx($objects, $property->getObjectPHID());
|
||||||
|
if (!$object) {
|
||||||
|
unset($properties[$key]);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$property->attachObject($object);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $properties;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function buildWhereClause($conn_r) {
|
||||||
|
$where = array();
|
||||||
|
|
||||||
|
if ($this->objectPHIDs !== null) {
|
||||||
|
$where[] = qsprintf(
|
||||||
|
$conn_r,
|
||||||
|
'objectPHID IN (%Ls)',
|
||||||
|
$this->objectPHIDs);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->names !== null) {
|
||||||
|
$hashes = array();
|
||||||
|
foreach ($this->names as $name) {
|
||||||
|
$hashes[] = PhabricatorHash::digestForIndex($name);
|
||||||
|
}
|
||||||
|
$where[] = qsprintf(
|
||||||
|
$conn_r,
|
||||||
|
'fieldIndex IN (%Ls)',
|
||||||
|
$hashes);
|
||||||
|
}
|
||||||
|
|
||||||
|
$where[] = $this->buildPagingClause($conn_r);
|
||||||
|
|
||||||
|
return $this->formatWhereClause($where);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getQueryApplicationClass() {
|
||||||
|
return 'PhabricatorAlmanacApplication';
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -6,6 +6,7 @@ final class AlmanacServiceQuery
|
||||||
private $ids;
|
private $ids;
|
||||||
private $phids;
|
private $phids;
|
||||||
private $names;
|
private $names;
|
||||||
|
private $needProperties;
|
||||||
|
|
||||||
public function withIDs(array $ids) {
|
public function withIDs(array $ids) {
|
||||||
$this->ids = $ids;
|
$this->ids = $ids;
|
||||||
|
@ -22,6 +23,11 @@ final class AlmanacServiceQuery
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function needProperties($need) {
|
||||||
|
$this->needProperties = $need;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
protected function loadPage() {
|
protected function loadPage() {
|
||||||
$table = new AlmanacService();
|
$table = new AlmanacService();
|
||||||
$conn_r = $table->establishConnection('r');
|
$conn_r = $table->establishConnection('r');
|
||||||
|
@ -71,6 +77,25 @@ final class AlmanacServiceQuery
|
||||||
return $this->formatWhereClause($where);
|
return $this->formatWhereClause($where);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function didFilterPage(array $services) {
|
||||||
|
// NOTE: We load properties unconditionally because CustomField assumes
|
||||||
|
// it can always generate a list of fields on an object. It may make
|
||||||
|
// sense to re-examine that assumption eventually.
|
||||||
|
|
||||||
|
$properties = id(new AlmanacPropertyQuery())
|
||||||
|
->setViewer($this->getViewer())
|
||||||
|
->setParentQuery($this)
|
||||||
|
->withObjectPHIDs(mpull($services, null, 'getPHID'))
|
||||||
|
->execute();
|
||||||
|
$properties = mgroup($properties, 'getObjectPHID');
|
||||||
|
foreach ($services as $service) {
|
||||||
|
$service_properties = idx($properties, $service->getPHID(), array());
|
||||||
|
$service->attachAlmanacProperties($service_properties);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $services;
|
||||||
|
}
|
||||||
|
|
||||||
public function getQueryApplicationClass() {
|
public function getQueryApplicationClass() {
|
||||||
return 'PhabricatorAlmanacApplication';
|
return 'PhabricatorAlmanacApplication';
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,25 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
final class AlmanacDeviceProperty extends AlmanacDAO {
|
|
||||||
|
|
||||||
protected $devicePHID;
|
|
||||||
protected $key;
|
|
||||||
protected $value;
|
|
||||||
|
|
||||||
public function getConfiguration() {
|
|
||||||
return array(
|
|
||||||
self::CONFIG_SERIALIZATION => array(
|
|
||||||
'value' => self::SERIALIZATION_JSON,
|
|
||||||
),
|
|
||||||
self::CONFIG_COLUMN_SCHEMA => array(
|
|
||||||
'key' => 'text128',
|
|
||||||
),
|
|
||||||
self::CONFIG_KEY_SCHEMA => array(
|
|
||||||
'key_device' => array(
|
|
||||||
'columns' => array('devicePHID', 'key'),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
) + parent::getConfiguration();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
56
src/applications/almanac/storage/AlmanacProperty.php
Normal file
56
src/applications/almanac/storage/AlmanacProperty.php
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
final class AlmanacProperty
|
||||||
|
extends PhabricatorCustomFieldStorage
|
||||||
|
implements PhabricatorPolicyInterface {
|
||||||
|
|
||||||
|
protected $fieldName;
|
||||||
|
|
||||||
|
private $object = self::ATTACHABLE;
|
||||||
|
|
||||||
|
public function getApplicationName() {
|
||||||
|
return 'almanac';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getConfiguration() {
|
||||||
|
$config = parent::getConfiguration();
|
||||||
|
|
||||||
|
$config[self::CONFIG_COLUMN_SCHEMA] += array(
|
||||||
|
'fieldName' => 'text128',
|
||||||
|
);
|
||||||
|
|
||||||
|
return $config;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getObject() {
|
||||||
|
return $this->assertAttached($this->object);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function attachObject(PhabricatorLiskDAO $object) {
|
||||||
|
$this->object = $object;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -( PhabricatorPolicyInterface )----------------------------------------- */
|
||||||
|
|
||||||
|
|
||||||
|
public function getCapabilities() {
|
||||||
|
return array(
|
||||||
|
PhabricatorPolicyCapability::CAN_VIEW,
|
||||||
|
PhabricatorPolicyCapability::CAN_EDIT,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPolicy($capability) {
|
||||||
|
return $this->getObject()->getPolicy($capability);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
|
||||||
|
return $this->getObject()->hasAutomaticCapability($capability, $viewer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function describeAutomaticCapability($capability) {
|
||||||
|
return pht('Properties inherit the policies of their object.');
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -2,7 +2,11 @@
|
||||||
|
|
||||||
final class AlmanacService
|
final class AlmanacService
|
||||||
extends AlmanacDAO
|
extends AlmanacDAO
|
||||||
implements PhabricatorPolicyInterface {
|
implements
|
||||||
|
PhabricatorPolicyInterface,
|
||||||
|
PhabricatorCustomFieldInterface,
|
||||||
|
PhabricatorApplicationTransactionInterface,
|
||||||
|
AlmanacPropertyInterface {
|
||||||
|
|
||||||
protected $name;
|
protected $name;
|
||||||
protected $nameIndex;
|
protected $nameIndex;
|
||||||
|
@ -10,6 +14,9 @@ final class AlmanacService
|
||||||
protected $viewPolicy;
|
protected $viewPolicy;
|
||||||
protected $editPolicy;
|
protected $editPolicy;
|
||||||
|
|
||||||
|
private $customFields = self::ATTACHABLE;
|
||||||
|
private $almanacProperties = self::ATTACHABLE;
|
||||||
|
|
||||||
public static function initializeNewService() {
|
public static function initializeNewService() {
|
||||||
return id(new AlmanacService())
|
return id(new AlmanacService())
|
||||||
->setViewPolicy(PhabricatorPolicies::POLICY_USER)
|
->setViewPolicy(PhabricatorPolicies::POLICY_USER)
|
||||||
|
@ -56,6 +63,38 @@ final class AlmanacService
|
||||||
return '/almanac/service/view/'.$this->getName().'/';
|
return '/almanac/service/view/'.$this->getName().'/';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* -( AlmanacPropertyInterface )------------------------------------------- */
|
||||||
|
|
||||||
|
|
||||||
|
public function attachAlmanacProperties(array $properties) {
|
||||||
|
assert_instances_of($properties, 'AlmanacProperty');
|
||||||
|
$this->almanacProperties = mpull($properties, null, 'getFieldName');
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAlmanacProperties() {
|
||||||
|
return $this->assertAttached($this->almanacProperties);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function hasAlmanacProperty($key) {
|
||||||
|
$this->assertAttached($this->almanacProperties);
|
||||||
|
return isset($this->almanacProperties[$key]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAlmanacProperty($key) {
|
||||||
|
return $this->assertAttachedKey($this->almanacProperties, $key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAlmanacPropertyValue($key, $default = null) {
|
||||||
|
if ($this->hasAlmanacProperty($key)) {
|
||||||
|
return $this->getAlmanacProperty($key)->getFieldValue();
|
||||||
|
} else {
|
||||||
|
return $default;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* -( PhabricatorPolicyInterface )----------------------------------------- */
|
/* -( PhabricatorPolicyInterface )----------------------------------------- */
|
||||||
|
|
||||||
|
|
||||||
|
@ -83,4 +122,41 @@ final class AlmanacService
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* -( PhabricatorCustomFieldInterface )------------------------------------ */
|
||||||
|
|
||||||
|
|
||||||
|
public function getCustomFieldSpecificationForRole($role) {
|
||||||
|
return array();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCustomFieldBaseClass() {
|
||||||
|
return 'AlmanacCustomField';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCustomFields() {
|
||||||
|
return $this->assertAttached($this->customFields);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function attachCustomFields(PhabricatorCustomFieldAttachment $fields) {
|
||||||
|
$this->customFields = $fields;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* -( PhabricatorApplicationTransactionInterface )------------------------- */
|
||||||
|
|
||||||
|
|
||||||
|
public function getApplicationTransactionEditor() {
|
||||||
|
return new AlmanacServiceEditor();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getApplicationTransactionObject() {
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getApplicationTransactionTemplate() {
|
||||||
|
return new AlmanacServiceTransaction();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -119,7 +119,7 @@ abstract class PhabricatorApplicationTransactionQuery
|
||||||
|
|
||||||
// NOTE: We have to do this after loading objects, because the objects
|
// NOTE: We have to do this after loading objects, because the objects
|
||||||
// may help determine which handles are required (for example, in the case
|
// may help determine which handles are required (for example, in the case
|
||||||
// of custom fields.
|
// of custom fields).
|
||||||
|
|
||||||
if ($this->needHandles) {
|
if ($this->needHandles) {
|
||||||
$phids = array();
|
$phids = array();
|
||||||
|
|
Loading…
Reference in a new issue