@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

Import raw GitHub event data into Nuance

Summary:
Ref T10537. Ref T10538. This polls the GitHub events API and creates Nuance items from the raw data.

It does nothing useful with them.

Test Plan:
- Polled GitHub.
- Saw some items get created.
- X-Poll-Interval seemed to work.
- ETag seemed to work.
- Recognizing when we hit items we've already seen seemed to work.

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T10537, T10538

Differential Revision: https://secure.phabricator.com/D15440

+423 -138
+2
resources/sql/autopatches/20160308.nuance.06.label.sql
··· 1 + ALTER TABLE {$NAMESPACE}_nuance.nuance_item 2 + DROP sourceLabel;
+2
resources/sql/autopatches/20160308.nuance.07.itemtype.sql
··· 1 + ALTER TABLE {$NAMESPACE}_nuance.nuance_item 2 + ADD itemType VARCHAR(64) NOT NULL COLLATE {$COLLATE_TEXT};
+2
resources/sql/autopatches/20160308.nuance.08.itemkey.sql
··· 1 + ALTER TABLE {$NAMESPACE}_nuance.nuance_item 2 + ADD itemKey VARCHAR(64) NOT NULL COLLATE {$COLLATE_TEXT};
+2
resources/sql/autopatches/20160308.nuance.09.itemcontainer.sql
··· 1 + ALTER TABLE {$NAMESPACE}_nuance.nuance_item 2 + ADD itemContainerKey VARCHAR(64) COLLATE {$COLLATE_TEXT};
+2
resources/sql/autopatches/20160308.nuance.10.itemkeyu.sql
··· 1 + UPDATE {$NAMESPACE}_nuance.nuance_item 2 + SET itemKey = id WHERE itemKey = '';
+2
resources/sql/autopatches/20160308.nuance.11.requestor.sql
··· 1 + ALTER TABLE {$NAMESPACE}_nuance.nuance_item 2 + CHANGE requestorPHID requestorPHID VARBINARY(64);
+2
resources/sql/autopatches/20160308.nuance.12.queue.sql
··· 1 + ALTER TABLE {$NAMESPACE}_nuance.nuance_item 2 + CHANGE queuePHID queuePHID VARBINARY(64);
+10 -3
src/__phutil_library_map__.php
··· 1419 1419 'NuanceConduitAPIMethod' => 'applications/nuance/conduit/NuanceConduitAPIMethod.php', 1420 1420 'NuanceConsoleController' => 'applications/nuance/controller/NuanceConsoleController.php', 1421 1421 'NuanceController' => 'applications/nuance/controller/NuanceController.php', 1422 - 'NuanceCreateItemConduitAPIMethod' => 'applications/nuance/conduit/NuanceCreateItemConduitAPIMethod.php', 1423 1422 'NuanceDAO' => 'applications/nuance/storage/NuanceDAO.php', 1424 1423 'NuanceGitHubRepositoryImportCursor' => 'applications/nuance/cursor/NuanceGitHubRepositoryImportCursor.php', 1425 1424 'NuanceGitHubRepositorySourceDefinition' => 'applications/nuance/source/NuanceGitHubRepositorySourceDefinition.php', ··· 1428 1427 'NuanceImportCursorDataQuery' => 'applications/nuance/query/NuanceImportCursorDataQuery.php', 1429 1428 'NuanceImportCursorPHIDType' => 'applications/nuance/phid/NuanceImportCursorPHIDType.php', 1430 1429 'NuanceItem' => 'applications/nuance/storage/NuanceItem.php', 1430 + 'NuanceItemController' => 'applications/nuance/controller/NuanceItemController.php', 1431 1431 'NuanceItemEditController' => 'applications/nuance/controller/NuanceItemEditController.php', 1432 1432 'NuanceItemEditor' => 'applications/nuance/editor/NuanceItemEditor.php', 1433 + 'NuanceItemListController' => 'applications/nuance/controller/NuanceItemListController.php', 1433 1434 'NuanceItemPHIDType' => 'applications/nuance/phid/NuanceItemPHIDType.php', 1434 1435 'NuanceItemQuery' => 'applications/nuance/query/NuanceItemQuery.php', 1436 + 'NuanceItemSearchEngine' => 'applications/nuance/query/NuanceItemSearchEngine.php', 1435 1437 'NuanceItemTransaction' => 'applications/nuance/storage/NuanceItemTransaction.php', 1436 1438 'NuanceItemTransactionComment' => 'applications/nuance/storage/NuanceItemTransactionComment.php', 1437 1439 'NuanceItemTransactionQuery' => 'applications/nuance/query/NuanceItemTransactionQuery.php', ··· 5668 5670 'NuanceConduitAPIMethod' => 'ConduitAPIMethod', 5669 5671 'NuanceConsoleController' => 'NuanceController', 5670 5672 'NuanceController' => 'PhabricatorController', 5671 - 'NuanceCreateItemConduitAPIMethod' => 'NuanceConduitAPIMethod', 5672 5673 'NuanceDAO' => 'PhabricatorLiskDAO', 5673 5674 'NuanceGitHubRepositoryImportCursor' => 'NuanceImportCursor', 5674 5675 'NuanceGitHubRepositorySourceDefinition' => 'NuanceSourceDefinition', 5675 5676 'NuanceImportCursor' => 'Phobject', 5676 - 'NuanceImportCursorData' => 'NuanceDAO', 5677 + 'NuanceImportCursorData' => array( 5678 + 'NuanceDAO', 5679 + 'PhabricatorPolicyInterface', 5680 + ), 5677 5681 'NuanceImportCursorDataQuery' => 'NuanceQuery', 5678 5682 'NuanceImportCursorPHIDType' => 'PhabricatorPHIDType', 5679 5683 'NuanceItem' => array( ··· 5681 5685 'PhabricatorPolicyInterface', 5682 5686 'PhabricatorApplicationTransactionInterface', 5683 5687 ), 5688 + 'NuanceItemController' => 'NuanceController', 5684 5689 'NuanceItemEditController' => 'NuanceController', 5685 5690 'NuanceItemEditor' => 'PhabricatorApplicationTransactionEditor', 5691 + 'NuanceItemListController' => 'NuanceItemController', 5686 5692 'NuanceItemPHIDType' => 'PhabricatorPHIDType', 5687 5693 'NuanceItemQuery' => 'NuanceQuery', 5694 + 'NuanceItemSearchEngine' => 'PhabricatorApplicationSearchEngine', 5688 5695 'NuanceItemTransaction' => 'NuanceTransaction', 5689 5696 'NuanceItemTransactionComment' => 'PhabricatorApplicationTransactionComment', 5690 5697 'NuanceItemTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
+1
src/applications/nuance/application/PhabricatorNuanceApplication.php
··· 40 40 '/nuance/' => array( 41 41 '' => 'NuanceConsoleController', 42 42 'item/' => array( 43 + $this->getQueryRoutePattern() => 'NuanceItemListController', 43 44 'view/(?P<id>[1-9]\d*)/' => 'NuanceItemViewController', 44 45 'edit/(?P<id>[1-9]\d*)/' => 'NuanceItemEditController', 45 46 'new/' => 'NuanceItemEditController',
-73
src/applications/nuance/conduit/NuanceCreateItemConduitAPIMethod.php
··· 1 - <?php 2 - 3 - final class NuanceCreateItemConduitAPIMethod extends NuanceConduitAPIMethod { 4 - 5 - public function getAPIMethodName() { 6 - return 'nuance.createitem'; 7 - } 8 - 9 - public function getMethodDescription() { 10 - return pht('Create a new item.'); 11 - } 12 - 13 - protected function defineParamTypes() { 14 - return array( 15 - 'requestorPHID' => 'required string', 16 - 'sourcePHID' => 'required string', 17 - 'ownerPHID' => 'optional string', 18 - ); 19 - } 20 - 21 - protected function defineReturnType() { 22 - return 'nonempty dict'; 23 - } 24 - 25 - protected function defineErrorTypes() { 26 - return array( 27 - 'ERR-NO-REQUESTOR-PHID' => pht('Items must have a requestor.'), 28 - 'ERR-NO-SOURCE-PHID' => pht('Items must have a source.'), 29 - ); 30 - } 31 - 32 - protected function execute(ConduitAPIRequest $request) { 33 - $source_phid = $request->getValue('sourcePHID'); 34 - $owner_phid = $request->getValue('ownerPHID'); 35 - $requestor_phid = $request->getValue('requestorPHID'); 36 - 37 - $user = $request->getUser(); 38 - 39 - $item = NuanceItem::initializeNewItem(); 40 - $xactions = array(); 41 - 42 - if ($source_phid) { 43 - $xactions[] = id(new NuanceItemTransaction()) 44 - ->setTransactionType(NuanceItemTransaction::TYPE_SOURCE) 45 - ->setNewValue($source_phid); 46 - } else { 47 - throw new ConduitException('ERR-NO-SOURCE-PHID'); 48 - } 49 - 50 - if ($owner_phid) { 51 - $xactions[] = id(new NuanceItemTransaction()) 52 - ->setTransactionType(NuanceItemTransaction::TYPE_OWNER) 53 - ->setNewValue($owner_phid); 54 - } 55 - 56 - if ($requestor_phid) { 57 - $xactions[] = id(new NuanceItemTransaction()) 58 - ->setTransactionType(NuanceItemTransaction::TYPE_REQUESTOR) 59 - ->setNewValue($requestor_phid); 60 - } else { 61 - throw new ConduitException('ERR-NO-REQUESTOR-PHID'); 62 - } 63 - 64 - $source = PhabricatorContentSource::newFromConduitRequest($request); 65 - $editor = id(new NuanceItemEditor()) 66 - ->setActor($user) 67 - ->setContentSource($source) 68 - ->applyTransactions($item, $xactions); 69 - 70 - return $item->toDictionary(); 71 - } 72 - 73 - }
+7
src/applications/nuance/controller/NuanceConsoleController.php
··· 26 26 ->setIcon('fa-filter') 27 27 ->addAttribute(pht('Manage Nuance sources.'))); 28 28 29 + $menu->addItem( 30 + id(new PHUIObjectItemView()) 31 + ->setHeader(pht('Items')) 32 + ->setHref($this->getApplicationURI('item/')) 33 + ->setIcon('fa-clone') 34 + ->addAttribute(pht('Manage Nuance items.'))); 35 + 29 36 $crumbs = $this->buildApplicationCrumbs(); 30 37 $crumbs->addTextCrumb(pht('Console')); 31 38
+11
src/applications/nuance/controller/NuanceItemController.php
··· 1 + <?php 2 + 3 + abstract class NuanceItemController 4 + extends NuanceController { 5 + 6 + public function buildApplicationMenu() { 7 + return $this->newApplicationMenu() 8 + ->setSearchEngine(new NuanceItemSearchEngine()); 9 + } 10 + 11 + }
+1 -1
src/applications/nuance/controller/NuanceItemEditController.php
··· 74 74 $viewer->renderHandle($item->getQueuePHID())); 75 75 76 76 $source = $item->getSource(); 77 - $definition = $source->requireDefinition(); 77 + $definition = $source->getDefinition(); 78 78 79 79 $definition->renderItemEditProperties( 80 80 $viewer,
+12
src/applications/nuance/controller/NuanceItemListController.php
··· 1 + <?php 2 + 3 + final class NuanceItemListController 4 + extends NuanceItemController { 5 + 6 + public function handleRequest(AphrontRequest $request) { 7 + return id(new NuanceItemSearchEngine()) 8 + ->setController($this) 9 + ->buildResponse(); 10 + } 11 + 12 + }
+177 -26
src/applications/nuance/cursor/NuanceGitHubRepositoryImportCursor.php
··· 37 37 } 38 38 39 39 protected function pullDataFromSource() { 40 + $viewer = $this->getViewer(); 41 + $now = PhabricatorTime::getNow(); 42 + 40 43 $source = $this->getSource(); 41 44 42 45 $user = $source->getSourceProperty('github.user'); 43 46 $repository = $source->getSourceProperty('github.repository'); 44 47 $api_token = $source->getSourceProperty('github.token'); 45 48 46 - $uri = "/repos/{$user}/{$repository}/events"; 47 - $data = array(); 49 + // This API only supports fetching 10 pages of 30 events each, for a total 50 + // of 300 events. 51 + $etag = null; 52 + $new_items = array(); 53 + $hit_known_items = false; 54 + for ($page = 1; $page <= 10; $page++) { 55 + $uri = "/repos/{$user}/{$repository}/events"; 56 + $data = array( 57 + 'page' => $page, 58 + ); 59 + 60 + $future = id(new PhutilGitHubFuture()) 61 + ->setAccessToken($api_token) 62 + ->setRawGitHubQuery($uri, $data); 63 + 64 + if ($page == 1) { 65 + $cursor_etag = $this->getCursorProperty('github.poll.etag'); 66 + if ($cursor_etag) { 67 + $future->addHeader('If-None-Match', $cursor_etag); 68 + } 69 + } 70 + 71 + $this->logInfo( 72 + pht( 73 + 'Polling GitHub Repository API endpoint "%s".', 74 + $uri)); 75 + $response = $future->resolve(); 76 + 77 + // Do this first: if we hit the rate limit, we get a response but the 78 + // body isn't valid. 79 + $this->updateRateLimits($response); 80 + 81 + if ($response->getStatus()->getStatusCode() == 304) { 82 + $this->logInfo( 83 + pht( 84 + 'Received a 304 Not Modified from GitHub, no new events.')); 85 + } 48 86 49 - $future = id(new PhutilGitHubFuture()) 50 - ->setAccessToken($api_token) 51 - ->setRawGitHubQuery($uri, $data); 87 + // This means we hit a rate limit or a "Not Modified" because of the 88 + // "ETag" header. In either case, we should bail out. 89 + if ($response->getStatus()->isError()) { 90 + $this->updatePolling($response, $now, false); 91 + $this->getCursorData()->save(); 92 + return false; 93 + } 94 + 95 + if ($page == 1) { 96 + $etag = $response->getHeaderValue('ETag'); 97 + } 98 + 99 + $records = $response->getBody(); 100 + foreach ($records as $record) { 101 + $item = $this->newNuanceItemFromGitHubEvent($record); 102 + $item_key = $item->getItemKey(); 103 + 104 + $this->logInfo( 105 + pht( 106 + 'Fetched event "%s".', 107 + $item_key)); 108 + 109 + $new_items[$item->getItemKey()] = $item; 110 + } 111 + 112 + if ($new_items) { 113 + $existing = id(new NuanceItemQuery()) 114 + ->setViewer($viewer) 115 + ->withSourcePHIDs(array($source->getPHID())) 116 + ->withItemKeys(array_keys($new_items)) 117 + ->execute(); 118 + $existing = mpull($existing, null, 'getItemKey'); 119 + foreach ($new_items as $key => $new_item) { 120 + if (isset($existing[$key])) { 121 + unset($new_items[$key]); 122 + $hit_known_items = true; 123 + 124 + $this->logInfo( 125 + pht( 126 + 'Event "%s" is previously known.', 127 + $key)); 128 + } 129 + } 130 + } 131 + 132 + if ($hit_known_items) { 133 + break; 134 + } 135 + 136 + if (count($records) < 30) { 137 + break; 138 + } 139 + } 140 + 141 + // TODO: When we go through the whole queue without hitting anything we 142 + // have seen before, we should record some sort of global event so we 143 + // can tell the user when the bridging started or was interrupted? 144 + if (!$hit_known_items) { 145 + $already_polled = $this->getCursorProperty('github.polled'); 146 + if ($already_polled) { 147 + // TODO: This is bad: we missed some items, maybe because too much 148 + // stuff happened too fast or the daemons were broken for a long 149 + // time. 150 + } else { 151 + // TODO: This is OK, we're doing the initial import. 152 + } 153 + } 52 154 53 - $etag = $this->getCursorProperty('github.poll.etag'); 54 - if ($etag) { 55 - $future->addHeader('If-None-Match', $etag); 155 + if ($etag !== null) { 156 + $this->updateETag($etag); 56 157 } 57 158 58 - $this->logInfo( 59 - pht( 60 - 'Polling GitHub Repository API endpoint "%s".', 61 - $uri)); 62 - $response = $future->resolve(); 159 + $this->updatePolling($response, $now, true); 63 160 64 - // Do this first: if we hit the rate limit, we get a response but the 65 - // body isn't valid. 66 - $this->updateRateLimits($response); 161 + $source->openTransaction(); 162 + foreach ($new_items as $new_item) { 163 + $new_item->save(); 164 + } 165 + $this->getCursorData()->save(); 166 + $source->saveTransaction(); 67 167 68 - // This means we hit a rate limit or a "Not Modified" because of the "ETag" 69 - // header. In either case, we should bail out. 70 - if ($response->getStatus()->isError()) { 71 - // TODO: Save cursor data! 72 - return false; 168 + foreach ($new_items as $new_item) { 169 + PhabricatorWorker::scheduleTask( 170 + 'NuanceImportWorker', 171 + array( 172 + 'itemPHID' => $new_item->getPHID(), 173 + ), 174 + array( 175 + 'objectPHID' => $new_item->getPHID(), 176 + )); 73 177 } 74 178 75 - $this->updateETag($response); 76 - 77 - var_dump($response->getBody()); 179 + return false; 78 180 } 79 181 80 182 private function updateRateLimits(PhutilGitHubResponse $response) { ··· 100 202 new PhutilNumber($limit_reset - $now))); 101 203 } 102 204 103 - private function updateETag(PhutilGitHubResponse $response) { 104 - $etag = $response->getHeaderValue('ETag'); 205 + private function updateETag($etag) { 105 206 106 207 $this->setCursorProperty('github.poll.etag', $etag); 107 208 ··· 109 210 pht( 110 211 'ETag for this request was "%s".', 111 212 $etag)); 213 + } 214 + 215 + private function updatePolling( 216 + PhutilGitHubResponse $response, 217 + $start, 218 + $success) { 219 + 220 + if ($success) { 221 + $this->setCursorProperty('github.polled', true); 222 + } 223 + 224 + $poll_interval = (int)$response->getHeaderValue('X-Poll-Interval'); 225 + $poll_ttl = $start + $poll_interval; 226 + $this->setCursorProperty('github.poll.ttl', $poll_ttl); 227 + 228 + $now = PhabricatorTime::getNow(); 229 + 230 + $this->logInfo( 231 + pht( 232 + 'Set API poll TTL to +%s second(s) (%s second(s) from now).', 233 + new PhutilNumber($poll_interval), 234 + new PhutilNumber($poll_ttl - $now))); 235 + } 236 + 237 + private function newNuanceItemFromGitHubEvent(array $record) { 238 + $source = $this->getSource(); 239 + 240 + $id = $record['id']; 241 + $item_key = "github.event.{$id}"; 242 + 243 + $container_key = null; 244 + 245 + $issue_id = idxv( 246 + $record, 247 + array( 248 + 'payload', 249 + 'issue', 250 + 'id', 251 + )); 252 + if ($issue_id) { 253 + $container_key = "github.issue.{$issue_id}"; 254 + } 255 + 256 + return NuanceItem::initializeNewItem() 257 + ->setStatus(NuanceItem::STATUS_IMPORTING) 258 + ->setSourcePHID($source->getPHID()) 259 + ->setItemType('github.event') 260 + ->setItemKey($item_key) 261 + ->setItemContainerKey($container_key) 262 + ->setItemProperty('api.raw', $record); 112 263 } 113 264 114 265 }
+10
src/applications/nuance/cursor/NuanceImportCursor.php
··· 5 5 private $cursorData; 6 6 private $cursorKey; 7 7 private $source; 8 + private $viewer; 8 9 9 10 abstract protected function shouldPullDataFromSource(); 10 11 abstract protected function pullDataFromSource(); ··· 38 39 39 40 public function getCursorKey() { 40 41 return $this->cursorKey; 42 + } 43 + 44 + public function setViewer($viewer) { 45 + $this->viewer = $viewer; 46 + return $this; 47 + } 48 + 49 + public function getViewer() { 50 + return $this->viewer; 41 51 } 42 52 43 53 final public function importFromSource() {
+39
src/applications/nuance/query/NuanceItemQuery.php
··· 6 6 private $ids; 7 7 private $phids; 8 8 private $sourcePHIDs; 9 + private $itemTypes; 10 + private $itemKeys; 11 + private $containerKeys; 9 12 10 13 public function withIDs(array $ids) { 11 14 $this->ids = $ids; ··· 19 22 20 23 public function withSourcePHIDs(array $source_phids) { 21 24 $this->sourcePHIDs = $source_phids; 25 + return $this; 26 + } 27 + 28 + public function withItemTypes(array $item_types) { 29 + $this->itemTypes = $item_types; 30 + return $this; 31 + } 32 + 33 + public function withItemKeys(array $item_keys) { 34 + $this->itemKeys = $item_keys; 35 + return $this; 36 + } 37 + 38 + public function withItemContainerKeys(array $container_keys) { 39 + $this->containerKeys = $container_keys; 22 40 return $this; 23 41 } 24 42 ··· 77 95 $conn, 78 96 'phid IN (%Ls)', 79 97 $this->phids); 98 + } 99 + 100 + if ($this->itemTypes !== null) { 101 + $where[] = qsprintf( 102 + $conn, 103 + 'itemType IN (%Ls)', 104 + $this->itemTypes); 105 + } 106 + 107 + if ($this->itemKeys !== null) { 108 + $where[] = qsprintf( 109 + $conn, 110 + 'itemKey IN (%Ls)', 111 + $this->itemKeys); 112 + } 113 + 114 + if ($this->containerKeys !== null) { 115 + $where[] = qsprintf( 116 + $conn, 117 + 'itemContainerKey IN (%Ls)', 118 + $this->containerKeys); 80 119 } 81 120 82 121 return $where;
+81
src/applications/nuance/query/NuanceItemSearchEngine.php
··· 1 + <?php 2 + 3 + final class NuanceItemSearchEngine 4 + extends PhabricatorApplicationSearchEngine { 5 + 6 + public function getApplicationClassName() { 7 + return 'PhabricatorNuanceApplication'; 8 + } 9 + 10 + public function getResultTypeDescription() { 11 + return pht('Nuance Items'); 12 + } 13 + 14 + public function newQuery() { 15 + return new NuanceItemQuery(); 16 + } 17 + 18 + protected function buildQueryFromParameters(array $map) { 19 + $query = $this->newQuery(); 20 + 21 + return $query; 22 + } 23 + 24 + protected function buildCustomSearchFields() { 25 + return array( 26 + ); 27 + } 28 + 29 + protected function getURI($path) { 30 + return '/nuance/item/'.$path; 31 + } 32 + 33 + protected function getBuiltinQueryNames() { 34 + $names = array( 35 + 'all' => pht('All Items'), 36 + ); 37 + 38 + return $names; 39 + } 40 + 41 + public function buildSavedQueryFromBuiltin($query_key) { 42 + $query = $this->newSavedQuery(); 43 + $query->setQueryKey($query_key); 44 + 45 + switch ($query_key) { 46 + case 'all': 47 + return $query; 48 + } 49 + 50 + return parent::buildSavedQueryFromBuiltin($query_key); 51 + } 52 + 53 + protected function renderResultList( 54 + array $items, 55 + PhabricatorSavedQuery $query, 56 + array $handles) { 57 + assert_instances_of($items, 'NuanceItem'); 58 + 59 + $viewer = $this->requireViewer(); 60 + 61 + $list = new PHUIObjectItemListView(); 62 + $list->setUser($viewer); 63 + foreach ($items as $item) { 64 + $view = id(new PHUIObjectItemView()) 65 + ->setObjectName(pht('Item %d', $item->getID())) 66 + ->setHeader($item->getDisplayName()) 67 + ->setHref($item->getURI()); 68 + 69 + $view->addIcon('none', $item->getItemType()); 70 + 71 + $list->addItem($view); 72 + } 73 + 74 + $result = new PhabricatorApplicationSearchResultView(); 75 + $result->setObjectList($list); 76 + $result->setNoDataString(pht('No items found.')); 77 + 78 + return $result; 79 + } 80 + 81 + }
+7 -5
src/applications/nuance/source/NuanceSourceDefinition.php
··· 53 53 pht('This source has no input cursors.')); 54 54 } 55 55 56 + $viewer = PhabricatorUser::getOmnipotentUser(); 56 57 $source = $this->getSource(); 57 58 $cursors = $this->newImportCursors(); 58 59 59 60 $data = id(new NuanceImportCursorDataQuery()) 60 - ->setViewer(PhabricatorUser::getOmnipotentUser()) 61 + ->setViewer($viewer) 61 62 ->withSourcePHIDs(array($source->getPHID())) 62 63 ->execute(); 63 - $data = mpull($data, 'getCursorKey'); 64 + $data = mpull($data, null, 'getCursorKey'); 64 65 65 66 $map = array(); 66 67 foreach ($cursors as $cursor) { ··· 102 103 103 104 $map[$key] = $cursor; 104 105 105 - $cursor->setSource($source); 106 - 107 106 $cursor_data = idx($data, $key); 108 107 if (!$cursor_data) { 109 108 $cursor_data = $cursor->newEmptyCursorData($source); 110 109 } 111 110 112 - $cursor->setCursorData($cursor_data); 111 + $cursor 112 + ->setViewer($viewer) 113 + ->setSource($source) 114 + ->setCursorData($cursor_data); 113 115 } 114 116 115 117 return $cursors;
+27 -1
src/applications/nuance/storage/NuanceImportCursorData.php
··· 1 1 <?php 2 2 3 3 final class NuanceImportCursorData 4 - extends NuanceDAO { 4 + extends NuanceDAO 5 + implements PhabricatorPolicyInterface { 5 6 6 7 protected $sourcePHID; 7 8 protected $cursorKey; ··· 39 40 public function setCursorProperty($key, $value) { 40 41 $this->properties[$key] = $value; 41 42 return $this; 43 + } 44 + 45 + 46 + /* -( PhabricatorPolicyInterface )----------------------------------------- */ 47 + 48 + 49 + public function getCapabilities() { 50 + return array( 51 + PhabricatorPolicyCapability::CAN_VIEW, 52 + ); 53 + } 54 + 55 + public function getPolicy($capability) { 56 + switch ($capability) { 57 + case PhabricatorPolicyCapability::CAN_VIEW: 58 + return PhabricatorPolicies::POLICY_USER; 59 + } 60 + } 61 + 62 + public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { 63 + return false; 64 + } 65 + 66 + public function describeAutomaticCapability($capability) { 67 + return null; 42 68 } 43 69 44 70 }
+26 -29
src/applications/nuance/storage/NuanceItem.php
··· 6 6 PhabricatorPolicyInterface, 7 7 PhabricatorApplicationTransactionInterface { 8 8 9 - const STATUS_OPEN = 0; 10 - const STATUS_ASSIGNED = 10; 11 - const STATUS_CLOSED = 20; 9 + const STATUS_IMPORTING = 'importing'; 10 + const STATUS_OPEN = 'open'; 11 + const STATUS_ASSIGNED = 'assigned'; 12 + const STATUS_CLOSED = 'closed'; 12 13 13 14 protected $status; 14 15 protected $ownerPHID; 15 16 protected $requestorPHID; 16 17 protected $sourcePHID; 17 - protected $sourceLabel; 18 + protected $queuePHID; 19 + protected $itemType; 20 + protected $itemKey; 21 + protected $itemContainerKey; 18 22 protected $data = array(); 19 23 protected $mailKey; 20 - protected $queuePHID; 21 24 22 25 private $source = self::ATTACHABLE; 23 26 ··· 34 37 ), 35 38 self::CONFIG_COLUMN_SCHEMA => array( 36 39 'ownerPHID' => 'phid?', 37 - 'sourceLabel' => 'text255?', 38 - 'status' => 'uint32', 40 + 'requestorPHID' => 'phid?', 41 + 'queuePHID' => 'phid?', 42 + 'itemType' => 'text64', 43 + 'itemKey' => 'text64', 44 + 'itemContainerKey' => 'text64?', 45 + 'status' => 'text32', 39 46 'mailKey' => 'bytes20', 40 47 ), 41 48 self::CONFIG_KEY_SCHEMA => array( ··· 50 57 ), 51 58 'key_queue' => array( 52 59 'columns' => array('queuePHID', 'status'), 60 + ), 61 + 'key_container' => array( 62 + 'columns' => array('sourcePHID', 'itemContainerKey'), 63 + ), 64 + 'key_item' => array( 65 + 'columns' => array('sourcePHID', 'itemKey'), 66 + 'unique' => true, 53 67 ), 54 68 ), 55 69 ) + parent::getConfiguration(); ··· 72 86 } 73 87 74 88 public function getLabel(PhabricatorUser $viewer) { 75 - // this is generated at the time the item is created based on 76 - // the configuration from the item source. It is typically 77 - // something like 'Twitter'. 78 - $source_label = $this->getSourceLabel(); 79 - 80 - return pht( 81 - 'Item via %s @ %s.', 82 - $source_label, 83 - phabricator_datetime($this->getDateCreated(), $viewer)); 89 + return pht('TODO: An Item'); 84 90 } 85 91 86 92 public function getRequestor() { ··· 99 105 $this->source = $source; 100 106 } 101 107 102 - public function getNuanceProperty($key, $default = null) { 108 + public function getItemProperty($key, $default = null) { 103 109 return idx($this->data, $key, $default); 104 110 } 105 111 106 - public function setNuanceProperty($key, $value) { 112 + public function setItemProperty($key, $value) { 107 113 $this->data[$key] = $value; 108 114 return $this; 109 115 } ··· 135 141 return null; 136 142 } 137 143 138 - public function toDictionary() { 139 - return array( 140 - 'id' => $this->getID(), 141 - 'phid' => $this->getPHID(), 142 - 'ownerPHID' => $this->getOwnerPHID(), 143 - 'requestorPHID' => $this->getRequestorPHID(), 144 - 'sourcePHID' => $this->getSourcePHID(), 145 - 'sourceLabel' => $this->getSourceLabel(), 146 - 'dateCreated' => $this->getDateCreated(), 147 - 'dateModified' => $this->getDateModified(), 148 - ); 144 + public function getDisplayName() { 145 + return pht('An Item'); 149 146 } 150 147 151 148