@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator

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

+264 -1
+8
resources/sql/autopatches/20160322.nuance.01.itemcommand.sql
··· 1 + CREATE TABLE {$NAMESPACE}_nuance.nuance_itemcommand ( 2 + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, 3 + itemPHID VARBINARY(64) NOT NULL, 4 + authorPHID VARBINARY(64) NOT NULL, 5 + command VARCHAR(64) NOT NULL COLLATE {$COLLATE_TEXT}, 6 + parameters LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT}, 7 + KEY `key_item` (itemPHID) 8 + ) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};
+7
src/__phutil_library_map__.php
··· 1440 1440 'NuanceImportCursorPHIDType' => 'applications/nuance/phid/NuanceImportCursorPHIDType.php', 1441 1441 'NuanceItem' => 'applications/nuance/storage/NuanceItem.php', 1442 1442 'NuanceItemActionController' => 'applications/nuance/controller/NuanceItemActionController.php', 1443 + 'NuanceItemCommand' => 'applications/nuance/storage/NuanceItemCommand.php', 1444 + 'NuanceItemCommandQuery' => 'applications/nuance/query/NuanceItemCommandQuery.php', 1443 1445 'NuanceItemController' => 'applications/nuance/controller/NuanceItemController.php', 1444 1446 'NuanceItemEditor' => 'applications/nuance/editor/NuanceItemEditor.php', 1445 1447 'NuanceItemListController' => 'applications/nuance/controller/NuanceItemListController.php', ··· 5728 5730 'PhabricatorApplicationTransactionInterface', 5729 5731 ), 5730 5732 'NuanceItemActionController' => 'NuanceController', 5733 + 'NuanceItemCommand' => array( 5734 + 'NuanceDAO', 5735 + 'PhabricatorPolicyInterface', 5736 + ), 5737 + 'NuanceItemCommandQuery' => 'NuanceQuery', 5731 5738 'NuanceItemController' => 'NuanceController', 5732 5739 'NuanceItemEditor' => 'PhabricatorApplicationTransactionEditor', 5733 5740 'NuanceItemListController' => 'NuanceItemController',
+44 -1
src/applications/nuance/controller/NuanceItemViewController.php
··· 26 26 27 27 $curtain = $this->buildCurtain($item); 28 28 $content = $this->buildContent($item); 29 + $commands = $this->buildCommands($item); 30 + 31 + $timeline = $this->buildTransactionTimeline( 32 + $item, 33 + new NuanceItemTransactionQuery()); 34 + 35 + $main = array( 36 + $commands, 37 + $content, 38 + $timeline, 39 + ); 29 40 30 41 $header = id(new PHUIHeaderView()) 31 42 ->setHeader($name); ··· 33 44 $view = id(new PHUITwoColumnView()) 34 45 ->setHeader($header) 35 46 ->setCurtain($curtain) 36 - ->setMainColumn($content); 47 + ->setMainColumn($main); 37 48 38 49 return $this->newPage() 39 50 ->setTitle($title) ··· 74 85 75 86 $impl->setViewer($viewer); 76 87 return $impl->buildItemView($item); 88 + } 89 + 90 + private function buildCommands(NuanceItem $item) { 91 + $viewer = $this->getViewer(); 92 + 93 + $commands = id(new NuanceItemCommandQuery()) 94 + ->setViewer($viewer) 95 + ->withItemPHIDs(array($item->getPHID())) 96 + ->execute(); 97 + $commands = msort($commands, 'getID'); 98 + 99 + if (!$commands) { 100 + return null; 101 + } 102 + 103 + $rows = array(); 104 + foreach ($commands as $command) { 105 + $rows[] = array( 106 + $command->getCommand(), 107 + ); 108 + } 109 + 110 + $table = id(new AphrontTableView($rows)) 111 + ->setHeaders( 112 + array( 113 + pht('Command'), 114 + )); 115 + 116 + return id(new PHUIObjectBoxView()) 117 + ->setHeaderText(pht('Pending Commands')) 118 + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) 119 + ->setTable($table); 77 120 } 78 121 79 122 }
+7
src/applications/nuance/editor/NuanceItemEditor.php
··· 19 19 $types[] = NuanceItemTransaction::TYPE_REQUESTOR; 20 20 $types[] = NuanceItemTransaction::TYPE_PROPERTY; 21 21 $types[] = NuanceItemTransaction::TYPE_QUEUE; 22 + $types[] = NuanceItemTransaction::TYPE_COMMAND; 22 23 23 24 $types[] = PhabricatorTransactions::TYPE_EDGE; 24 25 $types[] = PhabricatorTransactions::TYPE_COMMENT; ··· 45 46 $key = $xaction->getMetadataValue( 46 47 NuanceItemTransaction::PROPERTY_KEY); 47 48 return $object->getNuanceProperty($key); 49 + case NuanceItemTransaction::TYPE_COMMAND: 50 + return null; 48 51 } 49 52 50 53 return parent::getCustomTransactionOldValue($object, $xaction); ··· 60 63 case NuanceItemTransaction::TYPE_OWNER: 61 64 case NuanceItemTransaction::TYPE_PROPERTY: 62 65 case NuanceItemTransaction::TYPE_QUEUE: 66 + case NuanceItemTransaction::TYPE_COMMAND: 63 67 return $xaction->getNewValue(); 64 68 } 65 69 ··· 88 92 NuanceItemTransaction::PROPERTY_KEY); 89 93 $object->setNuanceProperty($key, $xaction->getNewValue()); 90 94 break; 95 + case NuanceItemTransaction::TYPE_COMMAND: 96 + break; 91 97 } 92 98 } 93 99 ··· 101 107 case NuanceItemTransaction::TYPE_OWNER: 102 108 case NuanceItemTransaction::TYPE_PROPERTY: 103 109 case NuanceItemTransaction::TYPE_QUEUE: 110 + case NuanceItemTransaction::TYPE_COMMAND: 104 111 return; 105 112 } 106 113
+14
src/applications/nuance/item/NuanceGitHubEventItemType.php
··· 147 147 public function getItemActions(NuanceItem $item) { 148 148 $actions = array(); 149 149 150 + $actions[] = $this->newItemAction($item, 'sync') 151 + ->setName(pht('Import to Maniphest')) 152 + ->setIcon('fa-anchor') 153 + ->setWorkflow(true) 154 + ->setRenderAsForm(true); 155 + 150 156 $actions[] = $this->newItemAction($item, 'raw') 151 157 ->setName(pht('View Raw Event')) 152 158 ->setWorkflow(true) ··· 156 162 } 157 163 158 164 protected function handleAction(NuanceItem $item, $action) { 165 + $viewer = $this->getViewer(); 159 166 $controller = $this->getController(); 160 167 161 168 switch ($action) { ··· 181 188 ->setTitle(pht('GitHub Raw Event')) 182 189 ->appendForm($form) 183 190 ->addCancelButton($item->getURI(), pht('Done')); 191 + case 'sync': 192 + $item->issueCommand($viewer->getPHID(), $action); 193 + return id(new AphrontRedirectResponse())->setURI($item->getURI()); 184 194 } 185 195 186 196 return null; ··· 226 236 ->setHeaderText(pht('Event Properties')) 227 237 ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) 228 238 ->appendChild($property_list); 239 + } 240 + 241 + protected function handleCommand(NuanceItem $item, $action) { 242 + return null; 229 243 } 230 244 231 245
+39
src/applications/nuance/item/NuanceItemType.php
··· 96 96 return null; 97 97 } 98 98 99 + final public function applyCommand( 100 + NuanceItem $item, 101 + NuanceItemCommand $command) { 102 + 103 + $result = $this->handleCommand($item, $command); 104 + 105 + if ($result === null) { 106 + return; 107 + } 108 + 109 + $xaction = id(new NuanceItemTransaction()) 110 + ->setTransactionType(NuanceItemTransaction::TYPE_COMMAND) 111 + ->setNewValue( 112 + array( 113 + 'command' => $command->getCommand(), 114 + 'parameters' => $command->getParameters(), 115 + 'result' => $result, 116 + )); 117 + 118 + $viewer = $this->getViewer(); 119 + 120 + // TODO: Maybe preserve the actor's original content source? 121 + $source = PhabricatorContentSource::newForSource( 122 + PhabricatorContentSource::SOURCE_DAEMON, 123 + array()); 124 + 125 + $editor = id(new NuanceItemEditor()) 126 + ->setActor($viewer) 127 + ->setActingAsPHID($command->getAuthorPHID()) 128 + ->setContentSource($source) 129 + ->setContinueOnMissingFields(true) 130 + ->setContinueOnNoEffect(true) 131 + ->applyTransactions($item, array($xaction)); 132 + } 133 + 134 + protected function handleCommand(NuanceItem $item, $action) { 135 + return null; 136 + } 137 + 99 138 }
+47
src/applications/nuance/query/NuanceItemCommandQuery.php
··· 1 + <?php 2 + 3 + final class NuanceItemCommandQuery 4 + extends NuanceQuery { 5 + 6 + private $ids; 7 + private $itemPHIDs; 8 + 9 + public function withIDs(array $ids) { 10 + $this->ids = $ids; 11 + return $this; 12 + } 13 + 14 + public function withItemPHIDs(array $item_phids) { 15 + $this->itemPHIDs = $item_phids; 16 + return $this; 17 + } 18 + 19 + public function newResultObject() { 20 + return new NuanceItemCommand(); 21 + } 22 + 23 + protected function loadPage() { 24 + return $this->loadStandardPage($this->newResultObject()); 25 + } 26 + 27 + protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { 28 + $where = parent::buildWhereClauseParts($conn); 29 + 30 + if ($this->ids !== null) { 31 + $where[] = qsprintf( 32 + $conn, 33 + 'id IN (%Ld)', 34 + $this->ids); 35 + } 36 + 37 + if ($this->itemPHIDs !== null) { 38 + $where[] = qsprintf( 39 + $conn, 40 + 'itemPHID IN (%Ls)', 41 + $this->itemPHIDs); 42 + } 43 + 44 + return $where; 45 + } 46 + 47 + }
+17
src/applications/nuance/storage/NuanceItem.php
··· 154 154 )); 155 155 } 156 156 157 + public function issueCommand( 158 + $author_phid, 159 + $command, 160 + array $parameters = array()) { 161 + 162 + $command = id(NuanceItemCommand::initializeNewCommand()) 163 + ->setItemPHID($this->getPHID()) 164 + ->setAuthorPHID($author_phid) 165 + ->setCommand($command) 166 + ->setParameters($parameters) 167 + ->save(); 168 + 169 + $this->scheduleUpdate(); 170 + 171 + return $this; 172 + } 173 + 157 174 public function getImplementation() { 158 175 return $this->assertAttached($this->implementation); 159 176 }
+55
src/applications/nuance/storage/NuanceItemCommand.php
··· 1 + <?php 2 + 3 + final class NuanceItemCommand 4 + extends NuanceDAO 5 + implements PhabricatorPolicyInterface { 6 + 7 + protected $itemPHID; 8 + protected $authorPHID; 9 + protected $command; 10 + protected $parameters; 11 + 12 + public static function initializeNewCommand() { 13 + return new self(); 14 + } 15 + 16 + protected function getConfiguration() { 17 + return array( 18 + self::CONFIG_TIMESTAMPS => false, 19 + self::CONFIG_SERIALIZATION => array( 20 + 'parameters' => self::SERIALIZATION_JSON, 21 + ), 22 + self::CONFIG_COLUMN_SCHEMA => array( 23 + 'command' => 'text64', 24 + ), 25 + self::CONFIG_KEY_SCHEMA => array( 26 + 'key_item' => array( 27 + 'columns' => array('itemPHID'), 28 + ), 29 + ), 30 + ) + parent::getConfiguration(); 31 + } 32 + 33 + 34 + /* -( PhabricatorPolicyInterface )----------------------------------------- */ 35 + 36 + 37 + public function getCapabilities() { 38 + return array( 39 + PhabricatorPolicyCapability::CAN_VIEW, 40 + ); 41 + } 42 + 43 + public function getPolicy($capability) { 44 + return PhabricatorPolicies::getMostOpenPolicy(); 45 + } 46 + 47 + public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { 48 + return false; 49 + } 50 + 51 + public function describeAutomaticCapability($capability) { 52 + return null; 53 + } 54 + 55 + }
+7
src/applications/nuance/storage/NuanceItemTransaction.php
··· 10 10 const TYPE_SOURCE = 'nuance.item.source'; 11 11 const TYPE_PROPERTY = 'nuance.item.property'; 12 12 const TYPE_QUEUE = 'nuance.item.queue'; 13 + const TYPE_COMMAND = 'nuance.item.command'; 13 14 14 15 public function getApplicationTransactionType() { 15 16 return NuanceItemPHIDType::TYPECONST; ··· 65 66 '%s routed this item to the %s queue.', 66 67 $this->renderHandleLink($author_phid), 67 68 $this->renderHandleLink($new)); 69 + case self::TYPE_COMMAND: 70 + // TODO: Give item types a chance to render this properly. 71 + return pht( 72 + '%s applied command "%s" to this item.', 73 + $this->renderHandleLink($author_phid), 74 + idx($new, 'command')); 68 75 } 69 76 70 77 return parent::getTitle();
+19
src/applications/nuance/worker/NuanceItemUpdateWorker.php
··· 15 15 $item = $this->loadItem($item_phid); 16 16 $this->updateItem($item); 17 17 $this->routeItem($item); 18 + $this->applyCommands($item); 18 19 } catch (Exception $ex) { 19 20 $lock->unlock(); 20 21 throw $ex; ··· 49 50 ->setQueuePHID($source->getDefaultQueuePHID()) 50 51 ->setStatus(NuanceItem::STATUS_OPEN) 51 52 ->save(); 53 + } 54 + 55 + private function applyCommands(NuanceItem $item) { 56 + $viewer = $this->getViewer(); 57 + 58 + $impl = $item->getImplementation(); 59 + $impl->setViewer($viewer); 60 + 61 + $commands = id(new NuanceItemCommandQuery()) 62 + ->setViewer($viewer) 63 + ->withItemPHIDs(array($item->getPHID())) 64 + ->execute(); 65 + $commands = msort($commands, 'getID'); 66 + 67 + foreach ($commands as $command) { 68 + $impl->applyCommand($item, $command); 69 + $command->delete(); 70 + } 52 71 } 53 72 54 73 }