1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2025-02-21 19:19:12 +01:00

Convert DrydockBlueprints to EditEngine

Summary:
Ref T10457. Fixes T10024. This primarily just modernizes blueprints to use EditEngine.

This also fixes T10024, which was an issue with stored properties not being flagged correctly.

Also slightly improves typeaheads for blueprints (more information, disabled state).

Test Plan:
  - Created and edited various types of blueprints.
  - Set and removed limits.

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T10024, T10457

Differential Revision: https://secure.phabricator.com/D15390
This commit is contained in:
epriestley 2016-03-03 04:34:47 -08:00
parent 01379958fa
commit 1bdf988556
13 changed files with 275 additions and 243 deletions

View file

@ -867,11 +867,11 @@ phutil_register_library_map(array(
'DrydockBlueprint' => 'applications/drydock/storage/DrydockBlueprint.php',
'DrydockBlueprintController' => 'applications/drydock/controller/DrydockBlueprintController.php',
'DrydockBlueprintCoreCustomField' => 'applications/drydock/customfield/DrydockBlueprintCoreCustomField.php',
'DrydockBlueprintCreateController' => 'applications/drydock/controller/DrydockBlueprintCreateController.php',
'DrydockBlueprintCustomField' => 'applications/drydock/customfield/DrydockBlueprintCustomField.php',
'DrydockBlueprintDatasource' => 'applications/drydock/typeahead/DrydockBlueprintDatasource.php',
'DrydockBlueprintDisableController' => 'applications/drydock/controller/DrydockBlueprintDisableController.php',
'DrydockBlueprintEditController' => 'applications/drydock/controller/DrydockBlueprintEditController.php',
'DrydockBlueprintEditEngine' => 'applications/drydock/editor/DrydockBlueprintEditEngine.php',
'DrydockBlueprintEditor' => 'applications/drydock/editor/DrydockBlueprintEditor.php',
'DrydockBlueprintImplementation' => 'applications/drydock/blueprint/DrydockBlueprintImplementation.php',
'DrydockBlueprintImplementationTestCase' => 'applications/drydock/blueprint/__tests__/DrydockBlueprintImplementationTestCase.php',
@ -3287,6 +3287,7 @@ phutil_register_library_map(array(
'PhabricatorStandardCustomFieldUsers' => 'infrastructure/customfield/standard/PhabricatorStandardCustomFieldUsers.php',
'PhabricatorStandardPageView' => 'view/page/PhabricatorStandardPageView.php',
'PhabricatorStandardSelectCustomFieldDatasource' => 'infrastructure/customfield/datasource/PhabricatorStandardSelectCustomFieldDatasource.php',
'PhabricatorStaticEditField' => 'applications/transactions/editfield/PhabricatorStaticEditField.php',
'PhabricatorStatusController' => 'applications/system/controller/PhabricatorStatusController.php',
'PhabricatorStatusUIExample' => 'applications/uiexample/examples/PhabricatorStatusUIExample.php',
'PhabricatorStorageFixtureScopeGuard' => 'infrastructure/testing/fixture/PhabricatorStorageFixtureScopeGuard.php',
@ -4980,11 +4981,11 @@ phutil_register_library_map(array(
'DrydockBlueprintCustomField',
'PhabricatorStandardCustomFieldInterface',
),
'DrydockBlueprintCreateController' => 'DrydockBlueprintController',
'DrydockBlueprintCustomField' => 'PhabricatorCustomField',
'DrydockBlueprintDatasource' => 'PhabricatorTypeaheadDatasource',
'DrydockBlueprintDisableController' => 'DrydockBlueprintController',
'DrydockBlueprintEditController' => 'DrydockBlueprintController',
'DrydockBlueprintEditEngine' => 'PhabricatorEditEngine',
'DrydockBlueprintEditor' => 'PhabricatorApplicationTransactionEditor',
'DrydockBlueprintImplementation' => 'Phobject',
'DrydockBlueprintImplementationTestCase' => 'PhabricatorTestCase',
@ -7834,6 +7835,7 @@ phutil_register_library_map(array(
'AphrontResponseProducerInterface',
),
'PhabricatorStandardSelectCustomFieldDatasource' => 'PhabricatorTypeaheadDatasource',
'PhabricatorStaticEditField' => 'PhabricatorEditField',
'PhabricatorStatusController' => 'PhabricatorController',
'PhabricatorStatusUIExample' => 'PhabricatorUIExample',
'PhabricatorStorageFixtureScopeGuard' => 'Phobject',

View file

@ -60,8 +60,8 @@ final class PhabricatorDrydockApplication extends PhabricatorApplication {
'authorizations/(?:query/(?P<queryKey>[^/]+)/)?' =>
'DrydockAuthorizationListController',
),
'create/' => 'DrydockBlueprintCreateController',
'edit/(?:(?P<id>[1-9]\d*)/)?' => 'DrydockBlueprintEditController',
$this->getEditRoutePattern('edit/')
=> 'DrydockBlueprintEditController',
),
'(?P<type>resource)/' => array(
'(?:query/(?P<queryKey>[^/]+)/)?' => 'DrydockResourceListController',

View file

@ -3,24 +3,18 @@
abstract class DrydockBlueprintController
extends DrydockController {
public function buildSideNavView() {
$nav = new AphrontSideNavFilterView();
$nav->setBaseURI(new PhutilURI($this->getApplicationURI()));
id(new DrydockBlueprintSearchEngine())
->setViewer($this->getRequest()->getUser())
->addNavigationItems($nav->getMenu());
$nav->selectFilter(null);
return $nav;
public function buildApplicationMenu() {
return $this->newApplicationMenu()
->setSearchEngine(new DrydockBlueprintSearchEngine());
}
protected function buildApplicationCrumbs() {
$crumbs = parent::buildApplicationCrumbs();
$crumbs->addTextCrumb(
pht('Blueprints'),
$this->getApplicationURI('blueprint/'));
return $crumbs;
}

View file

@ -1,79 +0,0 @@
<?php
final class DrydockBlueprintCreateController
extends DrydockBlueprintController {
public function handleRequest(AphrontRequest $request) {
$viewer = $request->getViewer();
$this->requireApplicationCapability(
DrydockCreateBlueprintsCapability::CAPABILITY);
$implementations =
DrydockBlueprintImplementation::getAllBlueprintImplementations();
$errors = array();
$e_blueprint = null;
if ($request->isFormPost()) {
$class = $request->getStr('blueprint-type');
if (!isset($implementations[$class])) {
$e_blueprint = pht('Required');
$errors[] = pht('You must choose a blueprint type.');
}
if (!$errors) {
$edit_uri = $this->getApplicationURI('blueprint/edit/?class='.$class);
return id(new AphrontRedirectResponse())->setURI($edit_uri);
}
}
$control = id(new AphrontFormRadioButtonControl())
->setName('blueprint-type')
->setLabel(pht('Blueprint Type'))
->setError($e_blueprint);
foreach ($implementations as $implementation_name => $implementation) {
$disabled = !$implementation->isEnabled();
$control->addButton(
$implementation_name,
$implementation->getBlueprintName(),
array(
pht('Provides: %s', $implementation->getType()),
phutil_tag('br'),
phutil_tag('br'),
$implementation->getDescription(),
),
$disabled ? 'disabled' : null,
$disabled);
}
$title = pht('Create New Blueprint');
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addTextCrumb(pht('New Blueprint'));
$form = id(new AphrontFormView())
->setUser($viewer)
->appendChild($control)
->appendChild(
id(new AphrontFormSubmitControl())
->addCancelButton($this->getApplicationURI('blueprint/'))
->setValue(pht('Continue')));
$box = id(new PHUIObjectBoxView())
->setFormErrors($errors)
->setHeaderText($title)
->setForm($form);
return $this->buildApplicationPage(
array(
$crumbs,
$box,
),
array(
'title' => $title,
));
}
}

View file

@ -3,168 +3,89 @@
final class DrydockBlueprintEditController extends DrydockBlueprintController {
public function handleRequest(AphrontRequest $request) {
$viewer = $request->getViewer();
$engine = id(new DrydockBlueprintEditEngine())
->setController($this);
$id = $request->getURIData('id');
if ($id) {
$blueprint = id(new DrydockBlueprintQuery())
->setViewer($viewer)
->withIDs(array($id))
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->executeOne();
if (!$blueprint) {
return new Aphront404Response();
}
$impl = $blueprint->getImplementation();
$cancel_uri = $this->getApplicationURI('blueprint/'.$id.'/');
} else {
if (!$id) {
$this->requireApplicationCapability(
DrydockCreateBlueprintsCapability::CAPABILITY);
$class = $request->getStr('class');
$type = $request->getStr('blueprintType');
$impl = DrydockBlueprintImplementation::getNamedImplementation($class);
$impl = DrydockBlueprintImplementation::getNamedImplementation($type);
if (!$impl || !$impl->isEnabled()) {
return new Aphront400Response();
return $this->buildTypeSelectionResponse();
}
$blueprint = DrydockBlueprint::initializeNewBlueprint($viewer)
->setClassName($class)
->attachImplementation($impl);
$cancel_uri = $this->getApplicationURI('blueprint/');
$engine
->addContextParameter('blueprintType', $type)
->setBlueprintImplementation($impl);
}
$field_list = PhabricatorCustomField::getObjectFields(
$blueprint,
PhabricatorCustomField::ROLE_EDIT);
$field_list
->setViewer($viewer)
->readFieldsFromStorage($blueprint);
return $engine->buildResponse();
}
private function buildTypeSelectionResponse() {
$request = $this->getRequest();
$viewer = $this->getViewer();
$implementations =
DrydockBlueprintImplementation::getAllBlueprintImplementations();
$v_name = $blueprint->getBlueprintName();
$e_name = true;
$errors = array();
$validation_exception = null;
$e_blueprint = null;
if ($request->isFormPost()) {
$v_view_policy = $request->getStr('viewPolicy');
$v_edit_policy = $request->getStr('editPolicy');
$v_name = $request->getStr('name');
if (!strlen($v_name)) {
$e_name = pht('Required');
$errors[] = pht('You must name this blueprint.');
}
if (!$errors) {
$xactions = array();
$xactions = $field_list->buildFieldTransactionsFromRequest(
new DrydockBlueprintTransaction(),
$request);
$xactions[] = id(new DrydockBlueprintTransaction())
->setTransactionType(PhabricatorTransactions::TYPE_VIEW_POLICY)
->setNewValue($v_view_policy);
$xactions[] = id(new DrydockBlueprintTransaction())
->setTransactionType(PhabricatorTransactions::TYPE_EDIT_POLICY)
->setNewValue($v_edit_policy);
$xactions[] = id(new DrydockBlueprintTransaction())
->setTransactionType(DrydockBlueprintTransaction::TYPE_NAME)
->setNewValue($v_name);
$editor = id(new DrydockBlueprintEditor())
->setActor($viewer)
->setContentSourceFromRequest($request)
->setContinueOnNoEffect(true);
try {
$editor->applyTransactions($blueprint, $xactions);
$id = $blueprint->getID();
$save_uri = $this->getApplicationURI("blueprint/{$id}/");
return id(new AphrontRedirectResponse())->setURI($save_uri);
} catch (PhabricatorApplicationTransactionValidationException $ex) {
$validation_exception = $ex;
}
$class = $request->getStr('blueprintType');
if (!isset($implementations[$class])) {
$e_blueprint = pht('Required');
$errors[] = pht('You must choose a blueprint type.');
}
}
$policies = id(new PhabricatorPolicyQuery())
->setViewer($viewer)
->setObject($blueprint)
->execute();
$control = id(new AphrontFormRadioButtonControl())
->setName('blueprintType')
->setLabel(pht('Blueprint Type'))
->setError($e_blueprint);
foreach ($implementations as $implementation_name => $implementation) {
$disabled = !$implementation->isEnabled();
$control->addButton(
$implementation_name,
$implementation->getBlueprintName(),
array(
pht('Provides: %s', $implementation->getType()),
phutil_tag('br'),
phutil_tag('br'),
$implementation->getDescription(),
),
$disabled ? 'disabled' : null,
$disabled);
}
$title = pht('Create New Blueprint');
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addTextCrumb(pht('New Blueprint'));
$form = id(new AphrontFormView())
->setUser($viewer)
->addHiddenInput('class', $request->getStr('class'))
->appendChild($control)
->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('Name'))
->setName('name')
->setValue($v_name)
->setError($e_name))
->appendChild(
id(new AphrontFormStaticControl())
->setLabel(pht('Blueprint Type'))
->setValue($impl->getBlueprintName()))
->appendChild(
id(new AphrontFormPolicyControl())
->setName('viewPolicy')
->setPolicyObject($blueprint)
->setCapability(PhabricatorPolicyCapability::CAN_VIEW)
->setPolicies($policies))
->appendChild(
id(new AphrontFormPolicyControl())
->setName('editPolicy')
->setPolicyObject($blueprint)
->setCapability(PhabricatorPolicyCapability::CAN_EDIT)
->setPolicies($policies));
$field_list->appendFieldsToForm($form);
$crumbs = $this->buildApplicationCrumbs();
if ($blueprint->getID()) {
$title = pht('Edit Blueprint');
$header = pht('Edit Blueprint %d', $blueprint->getID());
$crumbs->addTextCrumb(pht('Blueprint %d', $blueprint->getID()));
$crumbs->addTextCrumb(pht('Edit'));
$submit = pht('Save Blueprint');
} else {
$title = pht('New Blueprint');
$header = pht('New Blueprint');
$crumbs->addTextCrumb(pht('New Blueprint'));
$submit = pht('Create Blueprint');
}
$form->appendChild(
id(new AphrontFormSubmitControl())
->setValue($submit)
->addCancelButton($cancel_uri));
id(new AphrontFormSubmitControl())
->addCancelButton($this->getApplicationURI('blueprint/'))
->setValue(pht('Continue')));
$box = id(new PHUIObjectBoxView())
->setHeaderText($header)
->setValidationException($validation_exception)
->setFormErrors($errors)
->setHeaderText($title)
->setForm($form);
return $this->buildApplicationPage(
array(
$crumbs,
$box,
),
array(
'title' => $title,
));
return $this->newPage()
->setTitle($title)
->setCrumbs($crumbs)
->appendChild($box);
}
}

View file

@ -13,17 +13,12 @@ final class DrydockBlueprintListController extends DrydockBlueprintController {
}
protected function buildApplicationCrumbs() {
$can_create = $this->hasApplicationCapability(
DrydockCreateBlueprintsCapability::CAPABILITY);
$crumbs = parent::buildApplicationCrumbs();
$crumbs->addAction(
id(new PHUIListItemView())
->setName(pht('New Blueprint'))
->setHref($this->getApplicationURI('/blueprint/create/'))
->setDisabled(!$can_create)
->setWorkflow(!$can_create)
->setIcon('fa-plus-square'));
id(new DrydockBlueprintEditEngine())
->setViewer($this->getViewer())
->addActionToCrumbs($crumbs);
return $crumbs;
}

View file

@ -28,6 +28,7 @@ final class DrydockBlueprintCoreCustomField
public function readValueFromObject(PhabricatorCustomFieldInterface $object) {
$key = $this->getProxy()->getRawStandardFieldKey();
$this->setValueFromStorage($object->getDetail($key));
$this->didSetValueFromStorage();
}
public function applyApplicationTransactionInternalEffects(

View file

@ -0,0 +1,115 @@
<?php
final class DrydockBlueprintEditEngine
extends PhabricatorEditEngine {
private $blueprintImplementation;
const ENGINECONST = 'drydock.blueprint';
public function isEngineConfigurable() {
return false;
}
public function getEngineName() {
return pht('Drydock Blueprints');
}
public function getSummaryHeader() {
return pht('Edit Drydock Blueprint Configurations');
}
public function getSummaryText() {
return pht('This engine is used to edit Drydock blueprints.');
}
public function getEngineApplicationClass() {
return 'PhabricatorDrydockApplication';
}
public function setBlueprintImplementation(
DrydockBlueprintImplementation $impl) {
$this->blueprintImplementation = $impl;
return $this;
}
public function getBlueprintImplementation() {
return $this->blueprintImplementation;
}
protected function newEditableObject() {
$viewer = $this->getViewer();
$blueprint = DrydockBlueprint::initializeNewBlueprint($viewer);
$impl = $this->getBlueprintImplementation();
if ($impl) {
$blueprint
->setClassName(get_class($impl))
->attachImplementation(clone $impl);
}
return $blueprint;
}
protected function newObjectQuery() {
return new DrydockBlueprintQuery();
}
protected function getObjectCreateTitleText($object) {
return pht('Create Blueprint');
}
protected function getObjectCreateButtonText($object) {
return pht('Create Blueprint');
}
protected function getObjectEditTitleText($object) {
return pht('Edit Blueprint: %s', $object->getBlueprintName());
}
protected function getObjectEditShortText($object) {
return pht('Edit Blueprint');
}
protected function getObjectCreateShortText() {
return pht('Create Blueprint');
}
protected function getEditorURI() {
return '/drydock/blueprint/edit/';
}
protected function getObjectCreateCancelURI($object) {
return '/drydock/blueprint/';
}
protected function getObjectViewURI($object) {
$id = $object->getID();
return "/drydock/blueprint/{$id}/";
}
protected function getCreateNewObjectPolicy() {
return $this->getApplication()->getPolicy(
DrydockCreateBlueprintsCapability::CAPABILITY);
}
protected function buildCustomEditFields($object) {
$impl = $object->getImplementation();
return array(
id(new PhabricatorStaticEditField())
->setKey('type')
->setLabel(pht('Blueprint Type'))
->setDescription(pht('Type of blueprint.'))
->setValue($impl->getBlueprintName()),
id(new PhabricatorTextEditField())
->setKey('name')
->setLabel(pht('Name'))
->setDescription(pht('Name of the blueprint.'))
->setTransactionType(DrydockBlueprintTransaction::TYPE_NAME)
->setIsRequired(true)
->setValue($object->getBlueprintName()),
);
}
}

View file

@ -84,4 +84,36 @@ final class DrydockBlueprintEditor
return parent::applyCustomExternalTransaction($object, $xaction);
}
protected function validateTransaction(
PhabricatorLiskDAO $object,
$type,
array $xactions) {
$errors = parent::validateTransaction($object, $type, $xactions);
switch ($type) {
case DrydockBlueprintTransaction::TYPE_NAME:
$missing = $this->validateIsEmptyTextField(
$object->getBlueprintName(),
$xactions);
if ($missing) {
$error = new PhabricatorApplicationTransactionValidationError(
$type,
pht('Required'),
pht('You must choose a name for this blueprint.'),
nonempty(last($xactions), null));
$error->setIsMissingFieldError(true);
$errors[] = $error;
continue;
}
break;
}
return $errors;
}
}

View file

@ -26,11 +26,20 @@ final class DrydockBlueprintDatasource
->execute();
$results = array();
foreach ($handles as $handle) {
$results[] = id(new PhabricatorTypeaheadResult())
->setName($handle->getName())
foreach ($blueprints as $blueprint) {
$handle = $handles[$blueprint->getPHID()];
$result = id(new PhabricatorTypeaheadResult())
->setName($handle->getFullName())
->setPHID($handle->getPHID());
if ($blueprint->getIsDisabled()) {
$result->setClosed(pht('Disabled'));
}
$results[] = $result;
}
return $results;
}
}

View file

@ -0,0 +1,18 @@
<?php
final class PhabricatorStaticEditField
extends PhabricatorEditField {
protected function newControl() {
return new AphrontFormMarkupControl();
}
protected function newHTTPParameterType() {
return null;
}
protected function newConduitParameterType() {
return null;
}
}

View file

@ -135,4 +135,16 @@ final class PhabricatorStandardCustomFieldCredential
}
protected function getHTTPParameterType() {
return new AphrontPHIDHTTPParameterType();
}
protected function newConduitSearchParameterType() {
return new ConduitPHIDParameterType();
}
protected function newConduitEditParameterType() {
return new ConduitPHIDParameterType();
}
}

View file

@ -52,4 +52,16 @@ abstract class PhabricatorStandardCustomFieldTokenizer
return $this->getDatasource();
}
protected function getHTTPParameterType() {
return new AphrontPHIDListHTTPParameterType();
}
protected function newConduitSearchParameterType() {
return new ConduitPHIDListParameterType();
}
protected function newConduitEditParameterType() {
return new ConduitPHIDListParameterType();
}
}