1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-12-28 08:20:57 +01:00

Nuance - federate out the design of NuanceSource via NuanceSourceDefinition

Summary: ...and get the basic edit flow "working" for a new NuanceSourceDefinition - the Phabricator Form. ...and fix a dumb bug in the query class so when you redirect to the view page / try to edit an existing NuanceSource you don't fatal.

Test Plan: played around with the edit form and it worked!

Reviewers: epriestley

Reviewed By: epriestley

CC: Korvin, epriestley, aran

Differential Revision: https://secure.phabricator.com/D7585
This commit is contained in:
Bob Trahan 2013-11-20 13:41:19 -08:00
parent a07f444f2a
commit 7b718bb033
13 changed files with 557 additions and 82 deletions

View file

@ -0,0 +1,11 @@
ALTER TABLE {$NAMESPACE}_nuance.nuance_source
DROP KEY key_type;
ALTER TABLE {$NAMESPACE}_nuance.nuance_source
DROP COLUMN type;
ALTER TABLE {$NAMESPACE}_nuance.nuance_source
ADD type VARCHAR(32) NOT NULL COLLATE utf8_bin AFTER name;
ALTER TABLE {$NAMESPACE}_nuance.nuance_source
ADD KEY `key_type` (type, dateModified);

View file

@ -867,6 +867,7 @@ phutil_register_library_map(array(
'NuancePHIDTypeQueue' => 'applications/nuance/phid/NuancePHIDTypeQueue.php',
'NuancePHIDTypeRequestor' => 'applications/nuance/phid/NuancePHIDTypeRequestor.php',
'NuancePHIDTypeSource' => 'applications/nuance/phid/NuancePHIDTypeSource.php',
'NuancePhabricatorFormSourceDefinition' => 'applications/nuance/source/NuancePhabricatorFormSourceDefinition.php',
'NuanceQuery' => 'applications/nuance/query/NuanceQuery.php',
'NuanceQueue' => 'applications/nuance/storage/NuanceQueue.php',
'NuanceQueueEditController' => 'applications/nuance/controller/NuanceQueueEditController.php',
@ -887,13 +888,13 @@ phutil_register_library_map(array(
'NuanceRequestorTransactionQuery' => 'applications/nuance/query/NuanceRequestorTransactionQuery.php',
'NuanceRequestorViewController' => 'applications/nuance/controller/NuanceRequestorViewController.php',
'NuanceSource' => 'applications/nuance/storage/NuanceSource.php',
'NuanceSourceDefinition' => 'applications/nuance/source/NuanceSourceDefinition.php',
'NuanceSourceEditController' => 'applications/nuance/controller/NuanceSourceEditController.php',
'NuanceSourceEditor' => 'applications/nuance/editor/NuanceSourceEditor.php',
'NuanceSourceQuery' => 'applications/nuance/query/NuanceSourceQuery.php',
'NuanceSourceTransaction' => 'applications/nuance/storage/NuanceSourceTransaction.php',
'NuanceSourceTransactionComment' => 'applications/nuance/storage/NuanceSourceTransactionComment.php',
'NuanceSourceTransactionQuery' => 'applications/nuance/query/NuanceSourceTransactionQuery.php',
'NuanceSourceType' => 'applications/nuance/constants/NuanceSourceType.php',
'NuanceSourceViewController' => 'applications/nuance/controller/NuanceSourceViewController.php',
'NuanceTransaction' => 'applications/nuance/storage/NuanceTransaction.php',
'OwnersPackageReplyHandler' => 'applications/owners/mail/OwnersPackageReplyHandler.php',
@ -3238,6 +3239,7 @@ phutil_register_library_map(array(
'NuancePHIDTypeQueue' => 'PhabricatorPHIDType',
'NuancePHIDTypeRequestor' => 'PhabricatorPHIDType',
'NuancePHIDTypeSource' => 'PhabricatorPHIDType',
'NuancePhabricatorFormSourceDefinition' => 'NuanceSourceDefinition',
'NuanceQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'NuanceQueue' =>
array(
@ -3266,13 +3268,13 @@ phutil_register_library_map(array(
0 => 'NuanceDAO',
1 => 'PhabricatorPolicyInterface',
),
'NuanceSourceDefinition' => 'Phobject',
'NuanceSourceEditController' => 'NuanceController',
'NuanceSourceEditor' => 'PhabricatorApplicationTransactionEditor',
'NuanceSourceQuery' => 'NuanceQuery',
'NuanceSourceTransaction' => 'NuanceTransaction',
'NuanceSourceTransactionComment' => 'PhabricatorApplicationTransactionComment',
'NuanceSourceTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'NuanceSourceType' => 'NuanceConstants',
'NuanceSourceViewController' => 'NuanceController',
'NuanceTransaction' => 'PhabricatorApplicationTransaction',
'OwnersPackageReplyHandler' => 'PhabricatorMailReplyHandler',

View file

@ -23,6 +23,10 @@ final class PhabricatorApplicationNuance extends PhabricatorApplication {
return true;
}
public function getBaseURI() {
return '/nuance/';
}
public function getRoutes() {
return array(
'/nuance/' => array(

View file

@ -1,22 +0,0 @@
<?php
final class NuanceSourceType extends NuanceConstants {
/* internal source types */
const PHABRICATOR_FORM = 1;
/* social media source types */
const TWITTER = 101;
/* engineering media source types */
const GITHUB = 201;
public static function getSelectOptions() {
return array(
self::PHABRICATOR_FORM => pht('Phabricator Form'),
);
}
}

View file

@ -28,7 +28,6 @@ final class NuanceSourceEditController extends NuanceController {
if ($is_new) {
$source = NuanceSource::initializeNewSource($user);
$title = pht('Create Source');
} else {
$source = id(new NuanceSourceQuery())
->setViewer($user)
@ -39,71 +38,29 @@ final class NuanceSourceEditController extends NuanceController {
PhabricatorPolicyCapability::CAN_EDIT,
))
->executeOne();
$title = pht('Edit Source');
}
if (!$source) {
return new Aphront404Response();
}
$error_view = null;
$e_name = null;
if ($request->isFormPost()) {
$error_view = id(new AphrontErrorView())
->setTitle(pht('This does not work at all yet.'));
}
$definition = NuanceSourceDefinition::getDefinitionForSource($source);
$definition->setActor($user);
$policies = id(new PhabricatorPolicyQuery())
->setViewer($user)
->setObject($source)
->execute();
$response = $definition->buildEditLayout($request);
if ($response instanceof AphrontResponse) {
return $response;
}
$layout = $response;
$crumbs = $this->buildApplicationCrumbs();
$form = id(new AphrontFormView())
->setUser($user)
->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('Name'))
->setName('name')
->setError($e_name)
->setValue($source->getName()))
->appendChild(
id(new AphrontFormSelectControl())
->setLabel(pht('Type'))
->setName('type')
->setOptions(NuanceSourceType::getSelectOptions())
->setValue($source->getType()))
->appendChild(
id(new AphrontFormPolicyControl())
->setUser($user)
->setCapability(PhabricatorPolicyCapability::CAN_VIEW)
->setPolicyObject($source)
->setPolicies($policies)
->setName('viewPolicy'))
->appendChild(
id(new AphrontFormPolicyControl())
->setUser($user)
->setCapability(PhabricatorPolicyCapability::CAN_EDIT)
->setPolicyObject($source)
->setPolicies($policies)
->setName('editPolicy'))
->appendChild(
id(new AphrontFormSubmitControl())
->setValue(pht('Save')));
$layout = id(new PHUIObjectBoxView())
->setHeaderText($title)
->setFormError($error_view)
->setForm($form);
return $this->buildApplicationPage(
array(
$crumbs,
$layout,
),
array(
'title' => $title,
'title' => $definition->getEditTitle(),
'device' => true));
}
}

View file

@ -18,11 +18,11 @@ final class NuanceSourceViewController extends NuanceController {
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$viewer = $request->getUser();
$source_id = $this->getSourceID();
$source = id(new NuanceSourceQuery())
->setViewer($user)
->setViewer($viewer)
->withIDs(array($source_id))
->executeOne();
@ -30,13 +30,107 @@ final class NuanceSourceViewController extends NuanceController {
return new Aphront404Response();
}
$source_phid = $source->getPHID();
$xactions = id(new NuanceSourceTransactionQuery())
->setViewer($viewer)
->withObjectPHIDs(array($source_phid))
->execute();
$engine = id(new PhabricatorMarkupEngine())
->setViewer($viewer);
$timeline = id(new PhabricatorApplicationTransactionView())
->setUser($viewer)
->setObjectPHID($source_phid)
->setMarkupEngine($engine)
->setTransactions($xactions);
$title = pht('%s', $source->getName());
$crumbs = $this->buildApplicationCrumbs();
$title = 'TODO';
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName($title));
$header = $this->buildHeaderView($source);
$actions = $this->buildActionView($source);
$properties = $this->buildPropertyView($source, $actions);
$box = id(new PHUIObjectBoxView())
->setHeader($header)
->addPropertyList($properties);
return $this->buildApplicationPage(
$crumbs,
array(
$crumbs,
$box,
$timeline,
),
array(
'title' => $title,
'device' => true));
'device' => true,
));
}
private function buildHeaderView(NuanceSource $source) {
$viewer = $this->getRequest()->getUser();
$header = id(new PHUIHeaderView())
->setUser($viewer)
->setHeader($source->getName())
->setPolicyObject($source);
return $header;
}
private function buildActionView(NuanceSource $source) {
$viewer = $this->getRequest()->getUser();
$id = $source->getID();
$actions = id(new PhabricatorActionListView())
->setObjectURI($source->getURI())
->setUser($viewer);
$can_edit = PhabricatorPolicyFilter::hasCapability(
$viewer,
$source,
PhabricatorPolicyCapability::CAN_EDIT);
$actions->addAction(
id(new PhabricatorActionView())
->setName(pht('Edit Source'))
->setIcon('edit')
->setHref($this->getApplicationURI("source/edit/{$id}/"))
->setDisabled(!$can_edit)
->setWorkflow(!$can_edit));
return $actions;
}
private function buildPropertyView(
NuanceSource $source,
PhabricatorActionListView $actions) {
$viewer = $this->getRequest()->getUser();
$properties = id(new PHUIPropertyListView())
->setUser($viewer)
->setObject($source)
->setActionList($actions);
$definition = NuanceSourceDefinition::getDefinitionForSource($source);
$properties->addProperty(
pht('Source Type'),
$definition->getName());
$descriptions = PhabricatorPolicyQuery::renderPolicyDescriptions(
$viewer,
$source);
$properties->addProperty(
pht('Editable By'),
$descriptions[PhabricatorPolicyCapability::CAN_EDIT]);
return $properties;
}
}

View file

@ -6,6 +6,8 @@ final class NuanceSourceEditor
public function getTransactionTypes() {
$types = parent::getTransactionTypes();
$types[] = NuanceSourceTransaction::TYPE_NAME;
$types[] = PhabricatorTransactions::TYPE_EDGE;
$types[] = PhabricatorTransactions::TYPE_COMMENT;
$types[] = PhabricatorTransactions::TYPE_VIEW_POLICY;
@ -14,4 +16,80 @@ final class NuanceSourceEditor
return $types;
}
protected function getCustomTransactionOldValue(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
switch ($xaction->getTransactionType()) {
case NuanceSourceTransaction::TYPE_NAME:
return $object->getName();
}
return parent::getCustomTransactionOldValue($object, $xaction);
}
protected function getCustomTransactionNewValue(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
switch ($xaction->getTransactionType()) {
case NuanceSourceTransaction::TYPE_NAME:
return $xaction->getNewValue();
}
return parent::getCustomTransactionNewValue($object, $xaction);
}
protected function applyCustomInternalTransaction(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
switch ($xaction->getTransactionType()) {
case NuanceSourceTransaction::TYPE_NAME:
$object->setName($xaction->getNewValue());
break;
}
}
protected function applyCustomExternalTransaction(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
switch ($xaction->getTransactionType()) {
case NuanceSourceTransaction::TYPE_NAME:
return;
}
return parent::applyCustomExternalTransaction($object, $xaction);
}
protected function validateTransaction(
PhabricatorLiskDAO $object,
$type,
array $xactions) {
$errors = parent::validateTransaction($object, $type, $xactions);
switch ($type) {
case NuanceSourceTransaction::TYPE_NAME:
$missing = $this->validateIsEmptyTextField(
$object->getName(),
$xactions);
if ($missing) {
$error = new PhabricatorApplicationTransactionValidationError(
$type,
pht('Required'),
pht('Source name is required.'),
nonempty(last($xactions), null));
$error->setIsMissingFieldError(true);
$errors[] = $error;
}
break;
}
return $errors;
}
}

View file

@ -35,7 +35,7 @@ final class NuanceSourceQuery
$data = queryfx_all(
$conn_r,
'SELECT FROM %T %Q %Q %Q',
'SELECT * FROM %T %Q %Q %Q',
$table->getTableName(),
$this->buildWhereClause($conn_r),
$this->buildOrderClause($conn_r),

View file

@ -0,0 +1,43 @@
<?php
final class NuancePhabricatorFormSourceDefinition
extends NuanceSourceDefinition {
public function getName() {
return pht('Phabricator Form');
}
public function getSourceTypeConstant() {
return 'phabricator-form';
}
public function updateItems() {
return null;
}
protected function augmentEditForm(
AphrontFormView $form,
PhabricatorApplicationTransactionValidationException $ex = null) {
/* TODO - add a box to allow for custom fields to be defined here, so that
* these NuanceSource objects made from this defintion can be used to
* capture arbitrary data */
return $form;
}
protected function buildTransactions(AphrontRequest $request) {
$transactions = parent::buildTransactions($request);
// TODO -- as above
return $transactions;
}
public function renderView() {
}
public function renderListView() {
}
}

View file

@ -0,0 +1,274 @@
<?php
abstract class NuanceSourceDefinition extends Phobject {
private $actor;
private $sourceObject;
public function setActor(PhabricatorUser $actor) {
$this->actor = $actor;
return $this;
}
public function getActor() {
return $this->actor;
}
public function requireActor() {
$actor = $this->getActor();
if (!$actor) {
throw new Exception('You must "setActor()" first!');
}
return $actor;
}
public function setSourceObject(NuanceSource $source) {
$source->setType($this->getSourceTypeConstant());
$this->sourceObject = $source;
return $this;
}
public function getSourceObject() {
return $this->sourceObject;
}
public function requireSourceObject() {
$source = $this->getSourceObject();
if (!$source) {
throw new Exception('You must "setSourceObject()" first!');
}
return $source;
}
public static function getSelectOptions() {
$definitions = self::getAllDefinitions();
$options = array();
foreach ($definitions as $definition) {
$key = $definition->getSourceTypeConstant();
$name = $definition->getName();
$options[$key] = $name;
}
return $options;
}
/**
* Gives a @{class:NuanceSourceDefinition} object for a given
* @{class:NuanceSource}. Note you still need to @{method:setActor}
* before the @{class:NuanceSourceDefinition} object will be useful.
*/
public static function getDefinitionForSource(NuanceSource $source) {
$definitions = self::getAllDefinitions();
$map = mpull($definitions, null, 'getSourceTypeConstant');
$definition = $map[$source->getType()];
$definition->setSourceObject($source);
return $definition;
}
public static function getAllDefinitions() {
static $definitions;
if ($definitions === null) {
$objects = id(new PhutilSymbolLoader())
->setAncestorClass(__CLASS__)
->loadObjects();
foreach ($objects as $definition) {
$key = $definition->getSourceTypeConstant();
$name = $definition->getName();
if (isset($definitions[$key])) {
$conflict = $definitions[$key];
throw new Exception(sprintf(
'Defintion %s conflicts with definition %s. This is a programming '.
'error.',
$conflict,
$name));
}
}
$definitions = $objects;
}
return $definitions;
}
/**
* A human readable string like "Twitter" or "Phabricator Form".
*/
abstract public function getName();
/**
* This should be a any VARCHAR(32).
*
* @{method:getAllDefinitions} will throw if you choose a string that
* collides with another @{class:NuanceSourceDefinition} class.
*/
abstract public function getSourceTypeConstant();
/**
* Code to create and update @{class:NuanceItem}s and
* @{class:NuanceRequestor}s via daemons goes here.
*
* If that does not make sense for the @{class:NuanceSource} you are
* defining, simply return null. For example,
* @{class:NuancePhabricatorFormSourceDefinition} since these are one-way
* contact forms.
*/
abstract public function updateItems();
private function loadSourceObjectPolicies(
PhabricatorUser $user,
NuanceSource $source) {
$user = $this->requireActor();
$source = $this->requireSourceObject();
return id(new PhabricatorPolicyQuery())
->setViewer($user)
->setObject($source)
->execute();
}
final public function getEditTitle() {
$source = $this->requireSourceObject();
if ($source->getPHID()) {
$title = pht('Edit "%s" source.', $source->getName());
} else {
$title = pht('Create a new "%s" source.', $this->getName());
}
return $title;
}
final public function buildEditLayout(AphrontRequest $request) {
$actor = $this->requireActor();
$source = $this->requireSourceObject();
$form_errors = array();
$error_messages = array();
$transactions = array();
$validation_exception = null;
if ($request->isFormPost()) {
$transactions = $this->buildTransactions($request);
try {
$editor = id(new NuanceSourceEditor())
->setActor($actor)
->setContentSourceFromRequest($request)
->setContinueOnNoEffect(true)
->applyTransactions($source, $transactions);
return id(new AphrontRedirectResponse())
->setURI($source->getURI());
} catch (PhabricatorApplicationTransactionValidationException $ex) {
$validation_exception = $ex;
}
}
$form = $this->renderEditForm($validation_exception);
$layout = id(new PHUIObjectBoxView())
->setHeaderText($this->getEditTitle())
->setValidationException($validation_exception)
->setForm($form);
if ($error_messages) {
$layout->setFormError($this->renderEditErrorView($error_messages));
}
return $layout;
}
/**
* Code to create a form to edit the @{class:NuanceItem} you are defining.
*
* return @{class:AphrontFormView}
*/
private function renderEditForm(
PhabricatorApplicationTransactionValidationException $ex = null) {
$user = $this->requireActor();
$source = $this->requireSourceObject();
$policies = $this->loadSourceObjectPolicies($user, $source);
$e_name = null;
if ($ex) {
$e_name = $ex->getShortMessage(NuanceSourceTransaction::TYPE_NAME);
}
$form = id(new AphrontFormView())
->setUser($user)
->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('Name'))
->setName('name')
->setError($e_name)
->setValue($source->getName()))
->appendChild(
id(new AphrontFormSelectControl())
->setLabel(pht('Type'))
->setName('type')
->setOptions(self::getSelectOptions())
->setValue($source->getType()));
$form = $this->augmentEditForm($form, $ex);
$form
->appendChild(
id(new AphrontFormPolicyControl())
->setUser($user)
->setCapability(PhabricatorPolicyCapability::CAN_VIEW)
->setPolicyObject($source)
->setPolicies($policies)
->setName('viewPolicy'))
->appendChild(
id(new AphrontFormPolicyControl())
->setUser($user)
->setCapability(PhabricatorPolicyCapability::CAN_EDIT)
->setPolicyObject($source)
->setPolicies($policies)
->setName('editPolicy'))
->appendChild(
id(new AphrontFormSubmitControl())
->addCancelButton($source->getURI())
->setValue(pht('Save')));
return $form;
}
/**
* return @{class:AphrontFormView}
*/
protected function augmentEditForm(
AphrontFormView $form,
PhabricatorApplicationTransactionValidationException $ex = null) {
return $form;
}
/**
* return @{class:AphrontErrorView}
*/
public function renderEditErrorView(array $errors) {
return id(new AphrontErrorView())
->setTitle(pht('Error with submission.'))
->setErrors($errors);
}
/**
* Hook to build up @{class:PhabricatorTransactions}.
*
* return array $transactions
*/
protected function buildTransactions(AphrontRequest $request) {
$transactions = array();
$transactions[] = id(new NuanceSourceTransaction())
->setTransactionType(PhabricatorTransactions::TYPE_EDIT_POLICY)
->setNewValue($request->getStr('editPolicy'));
$transactions[] = id(new NuanceSourceTransaction())
->setTransactionType(PhabricatorTransactions::TYPE_VIEW_POLICY)
->setNewValue($request->getStr('viewPolicy'));
$transactions[] = id(new NuanceSourceTransaction())
->setTransactionType(NuanceSourceTransaction::TYPE_NAME)
->setNewvalue($request->getStr('name'));
return $transactions;
}
abstract public function renderView();
abstract public function renderListView();
}

View file

@ -47,9 +47,14 @@ final class NuanceSource
$edit_policy = $app->getPolicy(
NuanceCapabilitySourceDefaultEdit::CAPABILITY);
$definitions = NuanceSourceDefinition::getAllDefinitions();
$lucky_definition = head($definitions);
return id(new NuanceSource())
->setViewPolicy($view_policy)
->setEditPolicy($edit_policy);
->setEditPolicy($edit_policy)
->setType($lucky_definition->getSourceTypeConstant());
}
public function getCapabilities() {

View file

@ -3,6 +3,8 @@
final class NuanceSourceTransaction
extends NuanceTransaction {
const TYPE_NAME = 'name-source';
public function getApplicationTransactionType() {
return NuancePHIDTypeSource::TYPECONST;
}
@ -11,4 +13,27 @@ final class NuanceSourceTransaction
return new NuanceSourceTransactionComment();
}
public function getTitle() {
$old = $this->getOldValue();
$new = $this->getNewValue();
$author_phid = $this->getAuthorPHID();
switch ($this->getTransactionType()) {
case self::TYPE_NAME:
if ($old === null) {
return pht(
'%s created this source.',
$this->renderHandleLink($author_phid));
} else {
return pht(
'%s renamed this source from "%s" to "%s".',
$this->renderHandleLink($author_phid),
$old,
$new);
}
break;
}
}
}

View file

@ -1768,6 +1768,10 @@ final class PhabricatorBuiltinPatchList extends PhabricatorSQLPatchList {
'type' => 'sql',
'name' => $this->getPatchPath('20131119.passphrase.sql'),
),
'20131120.nuancesourcetype.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('20131120.nuancesourcetype.sql'),
),
);
}
}