mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-26 00:32:42 +01:00
Add an ItemCommand queue to Nuance
Summary: Ref T10537. Generally, when users interact with Nuance items we'll dump a command into a queue and apply it in the background. This avoids race conditions with multiple users interacting with an item, which Nuance is more subject to than other applications because it has an import/external component. The "sync" command doesn't actually do anything yet. Test Plan: {F1186365} Reviewers: chad Reviewed By: chad Subscribers: Luke081515.2 Maniphest Tasks: T10537 Differential Revision: https://secure.phabricator.com/D15506
This commit is contained in:
parent
a90daf5d30
commit
1885c4e03b
11 changed files with 264 additions and 1 deletions
|
@ -0,0 +1,8 @@
|
|||
CREATE TABLE {$NAMESPACE}_nuance.nuance_itemcommand (
|
||||
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
itemPHID VARBINARY(64) NOT NULL,
|
||||
authorPHID VARBINARY(64) NOT NULL,
|
||||
command VARCHAR(64) NOT NULL COLLATE {$COLLATE_TEXT},
|
||||
parameters LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT},
|
||||
KEY `key_item` (itemPHID)
|
||||
) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};
|
|
@ -1440,6 +1440,8 @@ phutil_register_library_map(array(
|
|||
'NuanceImportCursorPHIDType' => 'applications/nuance/phid/NuanceImportCursorPHIDType.php',
|
||||
'NuanceItem' => 'applications/nuance/storage/NuanceItem.php',
|
||||
'NuanceItemActionController' => 'applications/nuance/controller/NuanceItemActionController.php',
|
||||
'NuanceItemCommand' => 'applications/nuance/storage/NuanceItemCommand.php',
|
||||
'NuanceItemCommandQuery' => 'applications/nuance/query/NuanceItemCommandQuery.php',
|
||||
'NuanceItemController' => 'applications/nuance/controller/NuanceItemController.php',
|
||||
'NuanceItemEditor' => 'applications/nuance/editor/NuanceItemEditor.php',
|
||||
'NuanceItemListController' => 'applications/nuance/controller/NuanceItemListController.php',
|
||||
|
@ -5728,6 +5730,11 @@ phutil_register_library_map(array(
|
|||
'PhabricatorApplicationTransactionInterface',
|
||||
),
|
||||
'NuanceItemActionController' => 'NuanceController',
|
||||
'NuanceItemCommand' => array(
|
||||
'NuanceDAO',
|
||||
'PhabricatorPolicyInterface',
|
||||
),
|
||||
'NuanceItemCommandQuery' => 'NuanceQuery',
|
||||
'NuanceItemController' => 'NuanceController',
|
||||
'NuanceItemEditor' => 'PhabricatorApplicationTransactionEditor',
|
||||
'NuanceItemListController' => 'NuanceItemController',
|
||||
|
|
|
@ -26,6 +26,17 @@ final class NuanceItemViewController extends NuanceController {
|
|||
|
||||
$curtain = $this->buildCurtain($item);
|
||||
$content = $this->buildContent($item);
|
||||
$commands = $this->buildCommands($item);
|
||||
|
||||
$timeline = $this->buildTransactionTimeline(
|
||||
$item,
|
||||
new NuanceItemTransactionQuery());
|
||||
|
||||
$main = array(
|
||||
$commands,
|
||||
$content,
|
||||
$timeline,
|
||||
);
|
||||
|
||||
$header = id(new PHUIHeaderView())
|
||||
->setHeader($name);
|
||||
|
@ -33,7 +44,7 @@ final class NuanceItemViewController extends NuanceController {
|
|||
$view = id(new PHUITwoColumnView())
|
||||
->setHeader($header)
|
||||
->setCurtain($curtain)
|
||||
->setMainColumn($content);
|
||||
->setMainColumn($main);
|
||||
|
||||
return $this->newPage()
|
||||
->setTitle($title)
|
||||
|
@ -76,4 +87,36 @@ final class NuanceItemViewController extends NuanceController {
|
|||
return $impl->buildItemView($item);
|
||||
}
|
||||
|
||||
private function buildCommands(NuanceItem $item) {
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$commands = id(new NuanceItemCommandQuery())
|
||||
->setViewer($viewer)
|
||||
->withItemPHIDs(array($item->getPHID()))
|
||||
->execute();
|
||||
$commands = msort($commands, 'getID');
|
||||
|
||||
if (!$commands) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$rows = array();
|
||||
foreach ($commands as $command) {
|
||||
$rows[] = array(
|
||||
$command->getCommand(),
|
||||
);
|
||||
}
|
||||
|
||||
$table = id(new AphrontTableView($rows))
|
||||
->setHeaders(
|
||||
array(
|
||||
pht('Command'),
|
||||
));
|
||||
|
||||
return id(new PHUIObjectBoxView())
|
||||
->setHeaderText(pht('Pending Commands'))
|
||||
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
|
||||
->setTable($table);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ final class NuanceItemEditor
|
|||
$types[] = NuanceItemTransaction::TYPE_REQUESTOR;
|
||||
$types[] = NuanceItemTransaction::TYPE_PROPERTY;
|
||||
$types[] = NuanceItemTransaction::TYPE_QUEUE;
|
||||
$types[] = NuanceItemTransaction::TYPE_COMMAND;
|
||||
|
||||
$types[] = PhabricatorTransactions::TYPE_EDGE;
|
||||
$types[] = PhabricatorTransactions::TYPE_COMMENT;
|
||||
|
@ -45,6 +46,8 @@ final class NuanceItemEditor
|
|||
$key = $xaction->getMetadataValue(
|
||||
NuanceItemTransaction::PROPERTY_KEY);
|
||||
return $object->getNuanceProperty($key);
|
||||
case NuanceItemTransaction::TYPE_COMMAND:
|
||||
return null;
|
||||
}
|
||||
|
||||
return parent::getCustomTransactionOldValue($object, $xaction);
|
||||
|
@ -60,6 +63,7 @@ final class NuanceItemEditor
|
|||
case NuanceItemTransaction::TYPE_OWNER:
|
||||
case NuanceItemTransaction::TYPE_PROPERTY:
|
||||
case NuanceItemTransaction::TYPE_QUEUE:
|
||||
case NuanceItemTransaction::TYPE_COMMAND:
|
||||
return $xaction->getNewValue();
|
||||
}
|
||||
|
||||
|
@ -88,6 +92,8 @@ final class NuanceItemEditor
|
|||
NuanceItemTransaction::PROPERTY_KEY);
|
||||
$object->setNuanceProperty($key, $xaction->getNewValue());
|
||||
break;
|
||||
case NuanceItemTransaction::TYPE_COMMAND:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -101,6 +107,7 @@ final class NuanceItemEditor
|
|||
case NuanceItemTransaction::TYPE_OWNER:
|
||||
case NuanceItemTransaction::TYPE_PROPERTY:
|
||||
case NuanceItemTransaction::TYPE_QUEUE:
|
||||
case NuanceItemTransaction::TYPE_COMMAND:
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -147,6 +147,12 @@ final class NuanceGitHubEventItemType
|
|||
public function getItemActions(NuanceItem $item) {
|
||||
$actions = array();
|
||||
|
||||
$actions[] = $this->newItemAction($item, 'sync')
|
||||
->setName(pht('Import to Maniphest'))
|
||||
->setIcon('fa-anchor')
|
||||
->setWorkflow(true)
|
||||
->setRenderAsForm(true);
|
||||
|
||||
$actions[] = $this->newItemAction($item, 'raw')
|
||||
->setName(pht('View Raw Event'))
|
||||
->setWorkflow(true)
|
||||
|
@ -156,6 +162,7 @@ final class NuanceGitHubEventItemType
|
|||
}
|
||||
|
||||
protected function handleAction(NuanceItem $item, $action) {
|
||||
$viewer = $this->getViewer();
|
||||
$controller = $this->getController();
|
||||
|
||||
switch ($action) {
|
||||
|
@ -181,6 +188,9 @@ final class NuanceGitHubEventItemType
|
|||
->setTitle(pht('GitHub Raw Event'))
|
||||
->appendForm($form)
|
||||
->addCancelButton($item->getURI(), pht('Done'));
|
||||
case 'sync':
|
||||
$item->issueCommand($viewer->getPHID(), $action);
|
||||
return id(new AphrontRedirectResponse())->setURI($item->getURI());
|
||||
}
|
||||
|
||||
return null;
|
||||
|
@ -228,5 +238,9 @@ final class NuanceGitHubEventItemType
|
|||
->appendChild($property_list);
|
||||
}
|
||||
|
||||
protected function handleCommand(NuanceItem $item, $action) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -96,4 +96,43 @@ abstract class NuanceItemType
|
|||
return null;
|
||||
}
|
||||
|
||||
final public function applyCommand(
|
||||
NuanceItem $item,
|
||||
NuanceItemCommand $command) {
|
||||
|
||||
$result = $this->handleCommand($item, $command);
|
||||
|
||||
if ($result === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$xaction = id(new NuanceItemTransaction())
|
||||
->setTransactionType(NuanceItemTransaction::TYPE_COMMAND)
|
||||
->setNewValue(
|
||||
array(
|
||||
'command' => $command->getCommand(),
|
||||
'parameters' => $command->getParameters(),
|
||||
'result' => $result,
|
||||
));
|
||||
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
// TODO: Maybe preserve the actor's original content source?
|
||||
$source = PhabricatorContentSource::newForSource(
|
||||
PhabricatorContentSource::SOURCE_DAEMON,
|
||||
array());
|
||||
|
||||
$editor = id(new NuanceItemEditor())
|
||||
->setActor($viewer)
|
||||
->setActingAsPHID($command->getAuthorPHID())
|
||||
->setContentSource($source)
|
||||
->setContinueOnMissingFields(true)
|
||||
->setContinueOnNoEffect(true)
|
||||
->applyTransactions($item, array($xaction));
|
||||
}
|
||||
|
||||
protected function handleCommand(NuanceItem $item, $action) {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
47
src/applications/nuance/query/NuanceItemCommandQuery.php
Normal file
47
src/applications/nuance/query/NuanceItemCommandQuery.php
Normal file
|
@ -0,0 +1,47 @@
|
|||
<?php
|
||||
|
||||
final class NuanceItemCommandQuery
|
||||
extends NuanceQuery {
|
||||
|
||||
private $ids;
|
||||
private $itemPHIDs;
|
||||
|
||||
public function withIDs(array $ids) {
|
||||
$this->ids = $ids;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function withItemPHIDs(array $item_phids) {
|
||||
$this->itemPHIDs = $item_phids;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function newResultObject() {
|
||||
return new NuanceItemCommand();
|
||||
}
|
||||
|
||||
protected function loadPage() {
|
||||
return $this->loadStandardPage($this->newResultObject());
|
||||
}
|
||||
|
||||
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
|
||||
$where = parent::buildWhereClauseParts($conn);
|
||||
|
||||
if ($this->ids !== null) {
|
||||
$where[] = qsprintf(
|
||||
$conn,
|
||||
'id IN (%Ld)',
|
||||
$this->ids);
|
||||
}
|
||||
|
||||
if ($this->itemPHIDs !== null) {
|
||||
$where[] = qsprintf(
|
||||
$conn,
|
||||
'itemPHID IN (%Ls)',
|
||||
$this->itemPHIDs);
|
||||
}
|
||||
|
||||
return $where;
|
||||
}
|
||||
|
||||
}
|
|
@ -154,6 +154,23 @@ final class NuanceItem
|
|||
));
|
||||
}
|
||||
|
||||
public function issueCommand(
|
||||
$author_phid,
|
||||
$command,
|
||||
array $parameters = array()) {
|
||||
|
||||
$command = id(NuanceItemCommand::initializeNewCommand())
|
||||
->setItemPHID($this->getPHID())
|
||||
->setAuthorPHID($author_phid)
|
||||
->setCommand($command)
|
||||
->setParameters($parameters)
|
||||
->save();
|
||||
|
||||
$this->scheduleUpdate();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getImplementation() {
|
||||
return $this->assertAttached($this->implementation);
|
||||
}
|
||||
|
|
55
src/applications/nuance/storage/NuanceItemCommand.php
Normal file
55
src/applications/nuance/storage/NuanceItemCommand.php
Normal file
|
@ -0,0 +1,55 @@
|
|||
<?php
|
||||
|
||||
final class NuanceItemCommand
|
||||
extends NuanceDAO
|
||||
implements PhabricatorPolicyInterface {
|
||||
|
||||
protected $itemPHID;
|
||||
protected $authorPHID;
|
||||
protected $command;
|
||||
protected $parameters;
|
||||
|
||||
public static function initializeNewCommand() {
|
||||
return new self();
|
||||
}
|
||||
|
||||
protected function getConfiguration() {
|
||||
return array(
|
||||
self::CONFIG_TIMESTAMPS => false,
|
||||
self::CONFIG_SERIALIZATION => array(
|
||||
'parameters' => self::SERIALIZATION_JSON,
|
||||
),
|
||||
self::CONFIG_COLUMN_SCHEMA => array(
|
||||
'command' => 'text64',
|
||||
),
|
||||
self::CONFIG_KEY_SCHEMA => array(
|
||||
'key_item' => array(
|
||||
'columns' => array('itemPHID'),
|
||||
),
|
||||
),
|
||||
) + parent::getConfiguration();
|
||||
}
|
||||
|
||||
|
||||
/* -( PhabricatorPolicyInterface )----------------------------------------- */
|
||||
|
||||
|
||||
public function getCapabilities() {
|
||||
return array(
|
||||
PhabricatorPolicyCapability::CAN_VIEW,
|
||||
);
|
||||
}
|
||||
|
||||
public function getPolicy($capability) {
|
||||
return PhabricatorPolicies::getMostOpenPolicy();
|
||||
}
|
||||
|
||||
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public function describeAutomaticCapability($capability) {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
|
@ -10,6 +10,7 @@ final class NuanceItemTransaction
|
|||
const TYPE_SOURCE = 'nuance.item.source';
|
||||
const TYPE_PROPERTY = 'nuance.item.property';
|
||||
const TYPE_QUEUE = 'nuance.item.queue';
|
||||
const TYPE_COMMAND = 'nuance.item.command';
|
||||
|
||||
public function getApplicationTransactionType() {
|
||||
return NuanceItemPHIDType::TYPECONST;
|
||||
|
@ -65,6 +66,12 @@ final class NuanceItemTransaction
|
|||
'%s routed this item to the %s queue.',
|
||||
$this->renderHandleLink($author_phid),
|
||||
$this->renderHandleLink($new));
|
||||
case self::TYPE_COMMAND:
|
||||
// TODO: Give item types a chance to render this properly.
|
||||
return pht(
|
||||
'%s applied command "%s" to this item.',
|
||||
$this->renderHandleLink($author_phid),
|
||||
idx($new, 'command'));
|
||||
}
|
||||
|
||||
return parent::getTitle();
|
||||
|
|
|
@ -15,6 +15,7 @@ final class NuanceItemUpdateWorker
|
|||
$item = $this->loadItem($item_phid);
|
||||
$this->updateItem($item);
|
||||
$this->routeItem($item);
|
||||
$this->applyCommands($item);
|
||||
} catch (Exception $ex) {
|
||||
$lock->unlock();
|
||||
throw $ex;
|
||||
|
@ -51,4 +52,22 @@ final class NuanceItemUpdateWorker
|
|||
->save();
|
||||
}
|
||||
|
||||
private function applyCommands(NuanceItem $item) {
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$impl = $item->getImplementation();
|
||||
$impl->setViewer($viewer);
|
||||
|
||||
$commands = id(new NuanceItemCommandQuery())
|
||||
->setViewer($viewer)
|
||||
->withItemPHIDs(array($item->getPHID()))
|
||||
->execute();
|
||||
$commands = msort($commands, 'getID');
|
||||
|
||||
foreach ($commands as $command) {
|
||||
$impl->applyCommand($item, $command);
|
||||
$command->delete();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue