mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-18 04:42:40 +01:00
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
This commit is contained in:
parent
e3a97e31a0
commit
5d6bb0ffeb
21 changed files with 424 additions and 139 deletions
2
resources/sql/autopatches/20160308.nuance.06.label.sql
Normal file
2
resources/sql/autopatches/20160308.nuance.06.label.sql
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
ALTER TABLE {$NAMESPACE}_nuance.nuance_item
|
||||||
|
DROP sourceLabel;
|
|
@ -0,0 +1,2 @@
|
||||||
|
ALTER TABLE {$NAMESPACE}_nuance.nuance_item
|
||||||
|
ADD itemType VARCHAR(64) NOT NULL COLLATE {$COLLATE_TEXT};
|
2
resources/sql/autopatches/20160308.nuance.08.itemkey.sql
Normal file
2
resources/sql/autopatches/20160308.nuance.08.itemkey.sql
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
ALTER TABLE {$NAMESPACE}_nuance.nuance_item
|
||||||
|
ADD itemKey VARCHAR(64) NOT NULL COLLATE {$COLLATE_TEXT};
|
|
@ -0,0 +1,2 @@
|
||||||
|
ALTER TABLE {$NAMESPACE}_nuance.nuance_item
|
||||||
|
ADD itemContainerKey VARCHAR(64) COLLATE {$COLLATE_TEXT};
|
|
@ -0,0 +1,2 @@
|
||||||
|
UPDATE {$NAMESPACE}_nuance.nuance_item
|
||||||
|
SET itemKey = id WHERE itemKey = '';
|
|
@ -0,0 +1,2 @@
|
||||||
|
ALTER TABLE {$NAMESPACE}_nuance.nuance_item
|
||||||
|
CHANGE requestorPHID requestorPHID VARBINARY(64);
|
2
resources/sql/autopatches/20160308.nuance.12.queue.sql
Normal file
2
resources/sql/autopatches/20160308.nuance.12.queue.sql
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
ALTER TABLE {$NAMESPACE}_nuance.nuance_item
|
||||||
|
CHANGE queuePHID queuePHID VARBINARY(64);
|
|
@ -1419,7 +1419,6 @@ phutil_register_library_map(array(
|
||||||
'NuanceConduitAPIMethod' => 'applications/nuance/conduit/NuanceConduitAPIMethod.php',
|
'NuanceConduitAPIMethod' => 'applications/nuance/conduit/NuanceConduitAPIMethod.php',
|
||||||
'NuanceConsoleController' => 'applications/nuance/controller/NuanceConsoleController.php',
|
'NuanceConsoleController' => 'applications/nuance/controller/NuanceConsoleController.php',
|
||||||
'NuanceController' => 'applications/nuance/controller/NuanceController.php',
|
'NuanceController' => 'applications/nuance/controller/NuanceController.php',
|
||||||
'NuanceCreateItemConduitAPIMethod' => 'applications/nuance/conduit/NuanceCreateItemConduitAPIMethod.php',
|
|
||||||
'NuanceDAO' => 'applications/nuance/storage/NuanceDAO.php',
|
'NuanceDAO' => 'applications/nuance/storage/NuanceDAO.php',
|
||||||
'NuanceGitHubRepositoryImportCursor' => 'applications/nuance/cursor/NuanceGitHubRepositoryImportCursor.php',
|
'NuanceGitHubRepositoryImportCursor' => 'applications/nuance/cursor/NuanceGitHubRepositoryImportCursor.php',
|
||||||
'NuanceGitHubRepositorySourceDefinition' => 'applications/nuance/source/NuanceGitHubRepositorySourceDefinition.php',
|
'NuanceGitHubRepositorySourceDefinition' => 'applications/nuance/source/NuanceGitHubRepositorySourceDefinition.php',
|
||||||
|
@ -1428,10 +1427,13 @@ phutil_register_library_map(array(
|
||||||
'NuanceImportCursorDataQuery' => 'applications/nuance/query/NuanceImportCursorDataQuery.php',
|
'NuanceImportCursorDataQuery' => 'applications/nuance/query/NuanceImportCursorDataQuery.php',
|
||||||
'NuanceImportCursorPHIDType' => 'applications/nuance/phid/NuanceImportCursorPHIDType.php',
|
'NuanceImportCursorPHIDType' => 'applications/nuance/phid/NuanceImportCursorPHIDType.php',
|
||||||
'NuanceItem' => 'applications/nuance/storage/NuanceItem.php',
|
'NuanceItem' => 'applications/nuance/storage/NuanceItem.php',
|
||||||
|
'NuanceItemController' => 'applications/nuance/controller/NuanceItemController.php',
|
||||||
'NuanceItemEditController' => 'applications/nuance/controller/NuanceItemEditController.php',
|
'NuanceItemEditController' => 'applications/nuance/controller/NuanceItemEditController.php',
|
||||||
'NuanceItemEditor' => 'applications/nuance/editor/NuanceItemEditor.php',
|
'NuanceItemEditor' => 'applications/nuance/editor/NuanceItemEditor.php',
|
||||||
|
'NuanceItemListController' => 'applications/nuance/controller/NuanceItemListController.php',
|
||||||
'NuanceItemPHIDType' => 'applications/nuance/phid/NuanceItemPHIDType.php',
|
'NuanceItemPHIDType' => 'applications/nuance/phid/NuanceItemPHIDType.php',
|
||||||
'NuanceItemQuery' => 'applications/nuance/query/NuanceItemQuery.php',
|
'NuanceItemQuery' => 'applications/nuance/query/NuanceItemQuery.php',
|
||||||
|
'NuanceItemSearchEngine' => 'applications/nuance/query/NuanceItemSearchEngine.php',
|
||||||
'NuanceItemTransaction' => 'applications/nuance/storage/NuanceItemTransaction.php',
|
'NuanceItemTransaction' => 'applications/nuance/storage/NuanceItemTransaction.php',
|
||||||
'NuanceItemTransactionComment' => 'applications/nuance/storage/NuanceItemTransactionComment.php',
|
'NuanceItemTransactionComment' => 'applications/nuance/storage/NuanceItemTransactionComment.php',
|
||||||
'NuanceItemTransactionQuery' => 'applications/nuance/query/NuanceItemTransactionQuery.php',
|
'NuanceItemTransactionQuery' => 'applications/nuance/query/NuanceItemTransactionQuery.php',
|
||||||
|
@ -5668,12 +5670,14 @@ phutil_register_library_map(array(
|
||||||
'NuanceConduitAPIMethod' => 'ConduitAPIMethod',
|
'NuanceConduitAPIMethod' => 'ConduitAPIMethod',
|
||||||
'NuanceConsoleController' => 'NuanceController',
|
'NuanceConsoleController' => 'NuanceController',
|
||||||
'NuanceController' => 'PhabricatorController',
|
'NuanceController' => 'PhabricatorController',
|
||||||
'NuanceCreateItemConduitAPIMethod' => 'NuanceConduitAPIMethod',
|
|
||||||
'NuanceDAO' => 'PhabricatorLiskDAO',
|
'NuanceDAO' => 'PhabricatorLiskDAO',
|
||||||
'NuanceGitHubRepositoryImportCursor' => 'NuanceImportCursor',
|
'NuanceGitHubRepositoryImportCursor' => 'NuanceImportCursor',
|
||||||
'NuanceGitHubRepositorySourceDefinition' => 'NuanceSourceDefinition',
|
'NuanceGitHubRepositorySourceDefinition' => 'NuanceSourceDefinition',
|
||||||
'NuanceImportCursor' => 'Phobject',
|
'NuanceImportCursor' => 'Phobject',
|
||||||
'NuanceImportCursorData' => 'NuanceDAO',
|
'NuanceImportCursorData' => array(
|
||||||
|
'NuanceDAO',
|
||||||
|
'PhabricatorPolicyInterface',
|
||||||
|
),
|
||||||
'NuanceImportCursorDataQuery' => 'NuanceQuery',
|
'NuanceImportCursorDataQuery' => 'NuanceQuery',
|
||||||
'NuanceImportCursorPHIDType' => 'PhabricatorPHIDType',
|
'NuanceImportCursorPHIDType' => 'PhabricatorPHIDType',
|
||||||
'NuanceItem' => array(
|
'NuanceItem' => array(
|
||||||
|
@ -5681,10 +5685,13 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorPolicyInterface',
|
'PhabricatorPolicyInterface',
|
||||||
'PhabricatorApplicationTransactionInterface',
|
'PhabricatorApplicationTransactionInterface',
|
||||||
),
|
),
|
||||||
|
'NuanceItemController' => 'NuanceController',
|
||||||
'NuanceItemEditController' => 'NuanceController',
|
'NuanceItemEditController' => 'NuanceController',
|
||||||
'NuanceItemEditor' => 'PhabricatorApplicationTransactionEditor',
|
'NuanceItemEditor' => 'PhabricatorApplicationTransactionEditor',
|
||||||
|
'NuanceItemListController' => 'NuanceItemController',
|
||||||
'NuanceItemPHIDType' => 'PhabricatorPHIDType',
|
'NuanceItemPHIDType' => 'PhabricatorPHIDType',
|
||||||
'NuanceItemQuery' => 'NuanceQuery',
|
'NuanceItemQuery' => 'NuanceQuery',
|
||||||
|
'NuanceItemSearchEngine' => 'PhabricatorApplicationSearchEngine',
|
||||||
'NuanceItemTransaction' => 'NuanceTransaction',
|
'NuanceItemTransaction' => 'NuanceTransaction',
|
||||||
'NuanceItemTransactionComment' => 'PhabricatorApplicationTransactionComment',
|
'NuanceItemTransactionComment' => 'PhabricatorApplicationTransactionComment',
|
||||||
'NuanceItemTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
|
'NuanceItemTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
|
||||||
|
|
|
@ -40,6 +40,7 @@ final class PhabricatorNuanceApplication extends PhabricatorApplication {
|
||||||
'/nuance/' => array(
|
'/nuance/' => array(
|
||||||
'' => 'NuanceConsoleController',
|
'' => 'NuanceConsoleController',
|
||||||
'item/' => array(
|
'item/' => array(
|
||||||
|
$this->getQueryRoutePattern() => 'NuanceItemListController',
|
||||||
'view/(?P<id>[1-9]\d*)/' => 'NuanceItemViewController',
|
'view/(?P<id>[1-9]\d*)/' => 'NuanceItemViewController',
|
||||||
'edit/(?P<id>[1-9]\d*)/' => 'NuanceItemEditController',
|
'edit/(?P<id>[1-9]\d*)/' => 'NuanceItemEditController',
|
||||||
'new/' => 'NuanceItemEditController',
|
'new/' => 'NuanceItemEditController',
|
||||||
|
|
|
@ -1,73 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
final class NuanceCreateItemConduitAPIMethod extends NuanceConduitAPIMethod {
|
|
||||||
|
|
||||||
public function getAPIMethodName() {
|
|
||||||
return 'nuance.createitem';
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getMethodDescription() {
|
|
||||||
return pht('Create a new item.');
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function defineParamTypes() {
|
|
||||||
return array(
|
|
||||||
'requestorPHID' => 'required string',
|
|
||||||
'sourcePHID' => 'required string',
|
|
||||||
'ownerPHID' => 'optional string',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function defineReturnType() {
|
|
||||||
return 'nonempty dict';
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function defineErrorTypes() {
|
|
||||||
return array(
|
|
||||||
'ERR-NO-REQUESTOR-PHID' => pht('Items must have a requestor.'),
|
|
||||||
'ERR-NO-SOURCE-PHID' => pht('Items must have a source.'),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function execute(ConduitAPIRequest $request) {
|
|
||||||
$source_phid = $request->getValue('sourcePHID');
|
|
||||||
$owner_phid = $request->getValue('ownerPHID');
|
|
||||||
$requestor_phid = $request->getValue('requestorPHID');
|
|
||||||
|
|
||||||
$user = $request->getUser();
|
|
||||||
|
|
||||||
$item = NuanceItem::initializeNewItem();
|
|
||||||
$xactions = array();
|
|
||||||
|
|
||||||
if ($source_phid) {
|
|
||||||
$xactions[] = id(new NuanceItemTransaction())
|
|
||||||
->setTransactionType(NuanceItemTransaction::TYPE_SOURCE)
|
|
||||||
->setNewValue($source_phid);
|
|
||||||
} else {
|
|
||||||
throw new ConduitException('ERR-NO-SOURCE-PHID');
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($owner_phid) {
|
|
||||||
$xactions[] = id(new NuanceItemTransaction())
|
|
||||||
->setTransactionType(NuanceItemTransaction::TYPE_OWNER)
|
|
||||||
->setNewValue($owner_phid);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($requestor_phid) {
|
|
||||||
$xactions[] = id(new NuanceItemTransaction())
|
|
||||||
->setTransactionType(NuanceItemTransaction::TYPE_REQUESTOR)
|
|
||||||
->setNewValue($requestor_phid);
|
|
||||||
} else {
|
|
||||||
throw new ConduitException('ERR-NO-REQUESTOR-PHID');
|
|
||||||
}
|
|
||||||
|
|
||||||
$source = PhabricatorContentSource::newFromConduitRequest($request);
|
|
||||||
$editor = id(new NuanceItemEditor())
|
|
||||||
->setActor($user)
|
|
||||||
->setContentSource($source)
|
|
||||||
->applyTransactions($item, $xactions);
|
|
||||||
|
|
||||||
return $item->toDictionary();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -26,6 +26,13 @@ final class NuanceConsoleController extends NuanceController {
|
||||||
->setIcon('fa-filter')
|
->setIcon('fa-filter')
|
||||||
->addAttribute(pht('Manage Nuance sources.')));
|
->addAttribute(pht('Manage Nuance sources.')));
|
||||||
|
|
||||||
|
$menu->addItem(
|
||||||
|
id(new PHUIObjectItemView())
|
||||||
|
->setHeader(pht('Items'))
|
||||||
|
->setHref($this->getApplicationURI('item/'))
|
||||||
|
->setIcon('fa-clone')
|
||||||
|
->addAttribute(pht('Manage Nuance items.')));
|
||||||
|
|
||||||
$crumbs = $this->buildApplicationCrumbs();
|
$crumbs = $this->buildApplicationCrumbs();
|
||||||
$crumbs->addTextCrumb(pht('Console'));
|
$crumbs->addTextCrumb(pht('Console'));
|
||||||
|
|
||||||
|
|
11
src/applications/nuance/controller/NuanceItemController.php
Normal file
11
src/applications/nuance/controller/NuanceItemController.php
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
abstract class NuanceItemController
|
||||||
|
extends NuanceController {
|
||||||
|
|
||||||
|
public function buildApplicationMenu() {
|
||||||
|
return $this->newApplicationMenu()
|
||||||
|
->setSearchEngine(new NuanceItemSearchEngine());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -74,7 +74,7 @@ final class NuanceItemEditController extends NuanceController {
|
||||||
$viewer->renderHandle($item->getQueuePHID()));
|
$viewer->renderHandle($item->getQueuePHID()));
|
||||||
|
|
||||||
$source = $item->getSource();
|
$source = $item->getSource();
|
||||||
$definition = $source->requireDefinition();
|
$definition = $source->getDefinition();
|
||||||
|
|
||||||
$definition->renderItemEditProperties(
|
$definition->renderItemEditProperties(
|
||||||
$viewer,
|
$viewer,
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
final class NuanceItemListController
|
||||||
|
extends NuanceItemController {
|
||||||
|
|
||||||
|
public function handleRequest(AphrontRequest $request) {
|
||||||
|
return id(new NuanceItemSearchEngine())
|
||||||
|
->setController($this)
|
||||||
|
->buildResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -37,44 +37,146 @@ final class NuanceGitHubRepositoryImportCursor
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function pullDataFromSource() {
|
protected function pullDataFromSource() {
|
||||||
|
$viewer = $this->getViewer();
|
||||||
|
$now = PhabricatorTime::getNow();
|
||||||
|
|
||||||
$source = $this->getSource();
|
$source = $this->getSource();
|
||||||
|
|
||||||
$user = $source->getSourceProperty('github.user');
|
$user = $source->getSourceProperty('github.user');
|
||||||
$repository = $source->getSourceProperty('github.repository');
|
$repository = $source->getSourceProperty('github.repository');
|
||||||
$api_token = $source->getSourceProperty('github.token');
|
$api_token = $source->getSourceProperty('github.token');
|
||||||
|
|
||||||
$uri = "/repos/{$user}/{$repository}/events";
|
// This API only supports fetching 10 pages of 30 events each, for a total
|
||||||
$data = array();
|
// of 300 events.
|
||||||
|
$etag = null;
|
||||||
|
$new_items = array();
|
||||||
|
$hit_known_items = false;
|
||||||
|
for ($page = 1; $page <= 10; $page++) {
|
||||||
|
$uri = "/repos/{$user}/{$repository}/events";
|
||||||
|
$data = array(
|
||||||
|
'page' => $page,
|
||||||
|
);
|
||||||
|
|
||||||
$future = id(new PhutilGitHubFuture())
|
$future = id(new PhutilGitHubFuture())
|
||||||
->setAccessToken($api_token)
|
->setAccessToken($api_token)
|
||||||
->setRawGitHubQuery($uri, $data);
|
->setRawGitHubQuery($uri, $data);
|
||||||
|
|
||||||
$etag = $this->getCursorProperty('github.poll.etag');
|
if ($page == 1) {
|
||||||
if ($etag) {
|
$cursor_etag = $this->getCursorProperty('github.poll.etag');
|
||||||
$future->addHeader('If-None-Match', $etag);
|
if ($cursor_etag) {
|
||||||
|
$future->addHeader('If-None-Match', $cursor_etag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->logInfo(
|
||||||
|
pht(
|
||||||
|
'Polling GitHub Repository API endpoint "%s".',
|
||||||
|
$uri));
|
||||||
|
$response = $future->resolve();
|
||||||
|
|
||||||
|
// Do this first: if we hit the rate limit, we get a response but the
|
||||||
|
// body isn't valid.
|
||||||
|
$this->updateRateLimits($response);
|
||||||
|
|
||||||
|
if ($response->getStatus()->getStatusCode() == 304) {
|
||||||
|
$this->logInfo(
|
||||||
|
pht(
|
||||||
|
'Received a 304 Not Modified from GitHub, no new events.'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// This means we hit a rate limit or a "Not Modified" because of the
|
||||||
|
// "ETag" header. In either case, we should bail out.
|
||||||
|
if ($response->getStatus()->isError()) {
|
||||||
|
$this->updatePolling($response, $now, false);
|
||||||
|
$this->getCursorData()->save();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($page == 1) {
|
||||||
|
$etag = $response->getHeaderValue('ETag');
|
||||||
|
}
|
||||||
|
|
||||||
|
$records = $response->getBody();
|
||||||
|
foreach ($records as $record) {
|
||||||
|
$item = $this->newNuanceItemFromGitHubEvent($record);
|
||||||
|
$item_key = $item->getItemKey();
|
||||||
|
|
||||||
|
$this->logInfo(
|
||||||
|
pht(
|
||||||
|
'Fetched event "%s".',
|
||||||
|
$item_key));
|
||||||
|
|
||||||
|
$new_items[$item->getItemKey()] = $item;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($new_items) {
|
||||||
|
$existing = id(new NuanceItemQuery())
|
||||||
|
->setViewer($viewer)
|
||||||
|
->withSourcePHIDs(array($source->getPHID()))
|
||||||
|
->withItemKeys(array_keys($new_items))
|
||||||
|
->execute();
|
||||||
|
$existing = mpull($existing, null, 'getItemKey');
|
||||||
|
foreach ($new_items as $key => $new_item) {
|
||||||
|
if (isset($existing[$key])) {
|
||||||
|
unset($new_items[$key]);
|
||||||
|
$hit_known_items = true;
|
||||||
|
|
||||||
|
$this->logInfo(
|
||||||
|
pht(
|
||||||
|
'Event "%s" is previously known.',
|
||||||
|
$key));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($hit_known_items) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count($records) < 30) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->logInfo(
|
// TODO: When we go through the whole queue without hitting anything we
|
||||||
pht(
|
// have seen before, we should record some sort of global event so we
|
||||||
'Polling GitHub Repository API endpoint "%s".',
|
// can tell the user when the bridging started or was interrupted?
|
||||||
$uri));
|
if (!$hit_known_items) {
|
||||||
$response = $future->resolve();
|
$already_polled = $this->getCursorProperty('github.polled');
|
||||||
|
if ($already_polled) {
|
||||||
// Do this first: if we hit the rate limit, we get a response but the
|
// TODO: This is bad: we missed some items, maybe because too much
|
||||||
// body isn't valid.
|
// stuff happened too fast or the daemons were broken for a long
|
||||||
$this->updateRateLimits($response);
|
// time.
|
||||||
|
} else {
|
||||||
// This means we hit a rate limit or a "Not Modified" because of the "ETag"
|
// TODO: This is OK, we're doing the initial import.
|
||||||
// header. In either case, we should bail out.
|
}
|
||||||
if ($response->getStatus()->isError()) {
|
|
||||||
// TODO: Save cursor data!
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->updateETag($response);
|
if ($etag !== null) {
|
||||||
|
$this->updateETag($etag);
|
||||||
|
}
|
||||||
|
|
||||||
var_dump($response->getBody());
|
$this->updatePolling($response, $now, true);
|
||||||
|
|
||||||
|
$source->openTransaction();
|
||||||
|
foreach ($new_items as $new_item) {
|
||||||
|
$new_item->save();
|
||||||
|
}
|
||||||
|
$this->getCursorData()->save();
|
||||||
|
$source->saveTransaction();
|
||||||
|
|
||||||
|
foreach ($new_items as $new_item) {
|
||||||
|
PhabricatorWorker::scheduleTask(
|
||||||
|
'NuanceImportWorker',
|
||||||
|
array(
|
||||||
|
'itemPHID' => $new_item->getPHID(),
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
'objectPHID' => $new_item->getPHID(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function updateRateLimits(PhutilGitHubResponse $response) {
|
private function updateRateLimits(PhutilGitHubResponse $response) {
|
||||||
|
@ -100,8 +202,7 @@ final class NuanceGitHubRepositoryImportCursor
|
||||||
new PhutilNumber($limit_reset - $now)));
|
new PhutilNumber($limit_reset - $now)));
|
||||||
}
|
}
|
||||||
|
|
||||||
private function updateETag(PhutilGitHubResponse $response) {
|
private function updateETag($etag) {
|
||||||
$etag = $response->getHeaderValue('ETag');
|
|
||||||
|
|
||||||
$this->setCursorProperty('github.poll.etag', $etag);
|
$this->setCursorProperty('github.poll.etag', $etag);
|
||||||
|
|
||||||
|
@ -111,4 +212,54 @@ final class NuanceGitHubRepositoryImportCursor
|
||||||
$etag));
|
$etag));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function updatePolling(
|
||||||
|
PhutilGitHubResponse $response,
|
||||||
|
$start,
|
||||||
|
$success) {
|
||||||
|
|
||||||
|
if ($success) {
|
||||||
|
$this->setCursorProperty('github.polled', true);
|
||||||
|
}
|
||||||
|
|
||||||
|
$poll_interval = (int)$response->getHeaderValue('X-Poll-Interval');
|
||||||
|
$poll_ttl = $start + $poll_interval;
|
||||||
|
$this->setCursorProperty('github.poll.ttl', $poll_ttl);
|
||||||
|
|
||||||
|
$now = PhabricatorTime::getNow();
|
||||||
|
|
||||||
|
$this->logInfo(
|
||||||
|
pht(
|
||||||
|
'Set API poll TTL to +%s second(s) (%s second(s) from now).',
|
||||||
|
new PhutilNumber($poll_interval),
|
||||||
|
new PhutilNumber($poll_ttl - $now)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private function newNuanceItemFromGitHubEvent(array $record) {
|
||||||
|
$source = $this->getSource();
|
||||||
|
|
||||||
|
$id = $record['id'];
|
||||||
|
$item_key = "github.event.{$id}";
|
||||||
|
|
||||||
|
$container_key = null;
|
||||||
|
|
||||||
|
$issue_id = idxv(
|
||||||
|
$record,
|
||||||
|
array(
|
||||||
|
'payload',
|
||||||
|
'issue',
|
||||||
|
'id',
|
||||||
|
));
|
||||||
|
if ($issue_id) {
|
||||||
|
$container_key = "github.issue.{$issue_id}";
|
||||||
|
}
|
||||||
|
|
||||||
|
return NuanceItem::initializeNewItem()
|
||||||
|
->setStatus(NuanceItem::STATUS_IMPORTING)
|
||||||
|
->setSourcePHID($source->getPHID())
|
||||||
|
->setItemType('github.event')
|
||||||
|
->setItemKey($item_key)
|
||||||
|
->setItemContainerKey($container_key)
|
||||||
|
->setItemProperty('api.raw', $record);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ abstract class NuanceImportCursor extends Phobject {
|
||||||
private $cursorData;
|
private $cursorData;
|
||||||
private $cursorKey;
|
private $cursorKey;
|
||||||
private $source;
|
private $source;
|
||||||
|
private $viewer;
|
||||||
|
|
||||||
abstract protected function shouldPullDataFromSource();
|
abstract protected function shouldPullDataFromSource();
|
||||||
abstract protected function pullDataFromSource();
|
abstract protected function pullDataFromSource();
|
||||||
|
@ -40,6 +41,15 @@ abstract class NuanceImportCursor extends Phobject {
|
||||||
return $this->cursorKey;
|
return $this->cursorKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function setViewer($viewer) {
|
||||||
|
$this->viewer = $viewer;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getViewer() {
|
||||||
|
return $this->viewer;
|
||||||
|
}
|
||||||
|
|
||||||
final public function importFromSource() {
|
final public function importFromSource() {
|
||||||
if (!$this->shouldPullDataFromSource()) {
|
if (!$this->shouldPullDataFromSource()) {
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -6,6 +6,9 @@ final class NuanceItemQuery
|
||||||
private $ids;
|
private $ids;
|
||||||
private $phids;
|
private $phids;
|
||||||
private $sourcePHIDs;
|
private $sourcePHIDs;
|
||||||
|
private $itemTypes;
|
||||||
|
private $itemKeys;
|
||||||
|
private $containerKeys;
|
||||||
|
|
||||||
public function withIDs(array $ids) {
|
public function withIDs(array $ids) {
|
||||||
$this->ids = $ids;
|
$this->ids = $ids;
|
||||||
|
@ -22,6 +25,21 @@ final class NuanceItemQuery
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function withItemTypes(array $item_types) {
|
||||||
|
$this->itemTypes = $item_types;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function withItemKeys(array $item_keys) {
|
||||||
|
$this->itemKeys = $item_keys;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function withItemContainerKeys(array $container_keys) {
|
||||||
|
$this->containerKeys = $container_keys;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
public function newResultObject() {
|
public function newResultObject() {
|
||||||
return new NuanceItem();
|
return new NuanceItem();
|
||||||
}
|
}
|
||||||
|
@ -79,6 +97,27 @@ final class NuanceItemQuery
|
||||||
$this->phids);
|
$this->phids);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($this->itemTypes !== null) {
|
||||||
|
$where[] = qsprintf(
|
||||||
|
$conn,
|
||||||
|
'itemType IN (%Ls)',
|
||||||
|
$this->itemTypes);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->itemKeys !== null) {
|
||||||
|
$where[] = qsprintf(
|
||||||
|
$conn,
|
||||||
|
'itemKey IN (%Ls)',
|
||||||
|
$this->itemKeys);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->containerKeys !== null) {
|
||||||
|
$where[] = qsprintf(
|
||||||
|
$conn,
|
||||||
|
'itemContainerKey IN (%Ls)',
|
||||||
|
$this->containerKeys);
|
||||||
|
}
|
||||||
|
|
||||||
return $where;
|
return $where;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
81
src/applications/nuance/query/NuanceItemSearchEngine.php
Normal file
81
src/applications/nuance/query/NuanceItemSearchEngine.php
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
final class NuanceItemSearchEngine
|
||||||
|
extends PhabricatorApplicationSearchEngine {
|
||||||
|
|
||||||
|
public function getApplicationClassName() {
|
||||||
|
return 'PhabricatorNuanceApplication';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getResultTypeDescription() {
|
||||||
|
return pht('Nuance Items');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function newQuery() {
|
||||||
|
return new NuanceItemQuery();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function buildQueryFromParameters(array $map) {
|
||||||
|
$query = $this->newQuery();
|
||||||
|
|
||||||
|
return $query;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function buildCustomSearchFields() {
|
||||||
|
return array(
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getURI($path) {
|
||||||
|
return '/nuance/item/'.$path;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getBuiltinQueryNames() {
|
||||||
|
$names = array(
|
||||||
|
'all' => pht('All Items'),
|
||||||
|
);
|
||||||
|
|
||||||
|
return $names;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function buildSavedQueryFromBuiltin($query_key) {
|
||||||
|
$query = $this->newSavedQuery();
|
||||||
|
$query->setQueryKey($query_key);
|
||||||
|
|
||||||
|
switch ($query_key) {
|
||||||
|
case 'all':
|
||||||
|
return $query;
|
||||||
|
}
|
||||||
|
|
||||||
|
return parent::buildSavedQueryFromBuiltin($query_key);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function renderResultList(
|
||||||
|
array $items,
|
||||||
|
PhabricatorSavedQuery $query,
|
||||||
|
array $handles) {
|
||||||
|
assert_instances_of($items, 'NuanceItem');
|
||||||
|
|
||||||
|
$viewer = $this->requireViewer();
|
||||||
|
|
||||||
|
$list = new PHUIObjectItemListView();
|
||||||
|
$list->setUser($viewer);
|
||||||
|
foreach ($items as $item) {
|
||||||
|
$view = id(new PHUIObjectItemView())
|
||||||
|
->setObjectName(pht('Item %d', $item->getID()))
|
||||||
|
->setHeader($item->getDisplayName())
|
||||||
|
->setHref($item->getURI());
|
||||||
|
|
||||||
|
$view->addIcon('none', $item->getItemType());
|
||||||
|
|
||||||
|
$list->addItem($view);
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = new PhabricatorApplicationSearchResultView();
|
||||||
|
$result->setObjectList($list);
|
||||||
|
$result->setNoDataString(pht('No items found.'));
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -53,14 +53,15 @@ abstract class NuanceSourceDefinition extends Phobject {
|
||||||
pht('This source has no input cursors.'));
|
pht('This source has no input cursors.'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$viewer = PhabricatorUser::getOmnipotentUser();
|
||||||
$source = $this->getSource();
|
$source = $this->getSource();
|
||||||
$cursors = $this->newImportCursors();
|
$cursors = $this->newImportCursors();
|
||||||
|
|
||||||
$data = id(new NuanceImportCursorDataQuery())
|
$data = id(new NuanceImportCursorDataQuery())
|
||||||
->setViewer(PhabricatorUser::getOmnipotentUser())
|
->setViewer($viewer)
|
||||||
->withSourcePHIDs(array($source->getPHID()))
|
->withSourcePHIDs(array($source->getPHID()))
|
||||||
->execute();
|
->execute();
|
||||||
$data = mpull($data, 'getCursorKey');
|
$data = mpull($data, null, 'getCursorKey');
|
||||||
|
|
||||||
$map = array();
|
$map = array();
|
||||||
foreach ($cursors as $cursor) {
|
foreach ($cursors as $cursor) {
|
||||||
|
@ -102,14 +103,15 @@ abstract class NuanceSourceDefinition extends Phobject {
|
||||||
|
|
||||||
$map[$key] = $cursor;
|
$map[$key] = $cursor;
|
||||||
|
|
||||||
$cursor->setSource($source);
|
|
||||||
|
|
||||||
$cursor_data = idx($data, $key);
|
$cursor_data = idx($data, $key);
|
||||||
if (!$cursor_data) {
|
if (!$cursor_data) {
|
||||||
$cursor_data = $cursor->newEmptyCursorData($source);
|
$cursor_data = $cursor->newEmptyCursorData($source);
|
||||||
}
|
}
|
||||||
|
|
||||||
$cursor->setCursorData($cursor_data);
|
$cursor
|
||||||
|
->setViewer($viewer)
|
||||||
|
->setSource($source)
|
||||||
|
->setCursorData($cursor_data);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $cursors;
|
return $cursors;
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
final class NuanceImportCursorData
|
final class NuanceImportCursorData
|
||||||
extends NuanceDAO {
|
extends NuanceDAO
|
||||||
|
implements PhabricatorPolicyInterface {
|
||||||
|
|
||||||
protected $sourcePHID;
|
protected $sourcePHID;
|
||||||
protected $cursorKey;
|
protected $cursorKey;
|
||||||
|
@ -41,4 +42,29 @@ final class NuanceImportCursorData
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* -( PhabricatorPolicyInterface )----------------------------------------- */
|
||||||
|
|
||||||
|
|
||||||
|
public function getCapabilities() {
|
||||||
|
return array(
|
||||||
|
PhabricatorPolicyCapability::CAN_VIEW,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPolicy($capability) {
|
||||||
|
switch ($capability) {
|
||||||
|
case PhabricatorPolicyCapability::CAN_VIEW:
|
||||||
|
return PhabricatorPolicies::POLICY_USER;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function describeAutomaticCapability($capability) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,18 +6,21 @@ final class NuanceItem
|
||||||
PhabricatorPolicyInterface,
|
PhabricatorPolicyInterface,
|
||||||
PhabricatorApplicationTransactionInterface {
|
PhabricatorApplicationTransactionInterface {
|
||||||
|
|
||||||
const STATUS_OPEN = 0;
|
const STATUS_IMPORTING = 'importing';
|
||||||
const STATUS_ASSIGNED = 10;
|
const STATUS_OPEN = 'open';
|
||||||
const STATUS_CLOSED = 20;
|
const STATUS_ASSIGNED = 'assigned';
|
||||||
|
const STATUS_CLOSED = 'closed';
|
||||||
|
|
||||||
protected $status;
|
protected $status;
|
||||||
protected $ownerPHID;
|
protected $ownerPHID;
|
||||||
protected $requestorPHID;
|
protected $requestorPHID;
|
||||||
protected $sourcePHID;
|
protected $sourcePHID;
|
||||||
protected $sourceLabel;
|
protected $queuePHID;
|
||||||
|
protected $itemType;
|
||||||
|
protected $itemKey;
|
||||||
|
protected $itemContainerKey;
|
||||||
protected $data = array();
|
protected $data = array();
|
||||||
protected $mailKey;
|
protected $mailKey;
|
||||||
protected $queuePHID;
|
|
||||||
|
|
||||||
private $source = self::ATTACHABLE;
|
private $source = self::ATTACHABLE;
|
||||||
|
|
||||||
|
@ -34,8 +37,12 @@ final class NuanceItem
|
||||||
),
|
),
|
||||||
self::CONFIG_COLUMN_SCHEMA => array(
|
self::CONFIG_COLUMN_SCHEMA => array(
|
||||||
'ownerPHID' => 'phid?',
|
'ownerPHID' => 'phid?',
|
||||||
'sourceLabel' => 'text255?',
|
'requestorPHID' => 'phid?',
|
||||||
'status' => 'uint32',
|
'queuePHID' => 'phid?',
|
||||||
|
'itemType' => 'text64',
|
||||||
|
'itemKey' => 'text64',
|
||||||
|
'itemContainerKey' => 'text64?',
|
||||||
|
'status' => 'text32',
|
||||||
'mailKey' => 'bytes20',
|
'mailKey' => 'bytes20',
|
||||||
),
|
),
|
||||||
self::CONFIG_KEY_SCHEMA => array(
|
self::CONFIG_KEY_SCHEMA => array(
|
||||||
|
@ -51,6 +58,13 @@ final class NuanceItem
|
||||||
'key_queue' => array(
|
'key_queue' => array(
|
||||||
'columns' => array('queuePHID', 'status'),
|
'columns' => array('queuePHID', 'status'),
|
||||||
),
|
),
|
||||||
|
'key_container' => array(
|
||||||
|
'columns' => array('sourcePHID', 'itemContainerKey'),
|
||||||
|
),
|
||||||
|
'key_item' => array(
|
||||||
|
'columns' => array('sourcePHID', 'itemKey'),
|
||||||
|
'unique' => true,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
) + parent::getConfiguration();
|
) + parent::getConfiguration();
|
||||||
}
|
}
|
||||||
|
@ -72,15 +86,7 @@ final class NuanceItem
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getLabel(PhabricatorUser $viewer) {
|
public function getLabel(PhabricatorUser $viewer) {
|
||||||
// this is generated at the time the item is created based on
|
return pht('TODO: An Item');
|
||||||
// the configuration from the item source. It is typically
|
|
||||||
// something like 'Twitter'.
|
|
||||||
$source_label = $this->getSourceLabel();
|
|
||||||
|
|
||||||
return pht(
|
|
||||||
'Item via %s @ %s.',
|
|
||||||
$source_label,
|
|
||||||
phabricator_datetime($this->getDateCreated(), $viewer));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getRequestor() {
|
public function getRequestor() {
|
||||||
|
@ -99,11 +105,11 @@ final class NuanceItem
|
||||||
$this->source = $source;
|
$this->source = $source;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getNuanceProperty($key, $default = null) {
|
public function getItemProperty($key, $default = null) {
|
||||||
return idx($this->data, $key, $default);
|
return idx($this->data, $key, $default);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setNuanceProperty($key, $value) {
|
public function setItemProperty($key, $value) {
|
||||||
$this->data[$key] = $value;
|
$this->data[$key] = $value;
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
@ -135,17 +141,8 @@ final class NuanceItem
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function toDictionary() {
|
public function getDisplayName() {
|
||||||
return array(
|
return pht('An Item');
|
||||||
'id' => $this->getID(),
|
|
||||||
'phid' => $this->getPHID(),
|
|
||||||
'ownerPHID' => $this->getOwnerPHID(),
|
|
||||||
'requestorPHID' => $this->getRequestorPHID(),
|
|
||||||
'sourcePHID' => $this->getSourcePHID(),
|
|
||||||
'sourceLabel' => $this->getSourceLabel(),
|
|
||||||
'dateCreated' => $this->getDateCreated(),
|
|
||||||
'dateModified' => $this->getDateModified(),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue