Page MenuHomePhabricator

D15447.id37229.diff
No OneTemporary

D15447.id37229.diff

diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -840,6 +840,8 @@
'DoorkeeperAsanaRemarkupRule' => 'applications/doorkeeper/remarkup/DoorkeeperAsanaRemarkupRule.php',
'DoorkeeperBridge' => 'applications/doorkeeper/bridge/DoorkeeperBridge.php',
'DoorkeeperBridgeAsana' => 'applications/doorkeeper/bridge/DoorkeeperBridgeAsana.php',
+ 'DoorkeeperBridgeGitHub' => 'applications/doorkeeper/bridge/DoorkeeperBridgeGitHub.php',
+ 'DoorkeeperBridgeGitHubIssue' => 'applications/doorkeeper/bridge/DoorkeeperBridgeGitHubIssue.php',
'DoorkeeperBridgeJIRA' => 'applications/doorkeeper/bridge/DoorkeeperBridgeJIRA.php',
'DoorkeeperBridgeJIRATestCase' => 'applications/doorkeeper/bridge/__tests__/DoorkeeperBridgeJIRATestCase.php',
'DoorkeeperDAO' => 'applications/doorkeeper/storage/DoorkeeperDAO.php',
@@ -1423,6 +1425,7 @@
'NuanceGitHubEventItemType' => 'applications/nuance/item/NuanceGitHubEventItemType.php',
'NuanceGitHubImportCursor' => 'applications/nuance/cursor/NuanceGitHubImportCursor.php',
'NuanceGitHubIssuesImportCursor' => 'applications/nuance/cursor/NuanceGitHubIssuesImportCursor.php',
+ 'NuanceGitHubRawEvent' => 'applications/nuance/github/NuanceGitHubRawEvent.php',
'NuanceGitHubRepositoryImportCursor' => 'applications/nuance/cursor/NuanceGitHubRepositoryImportCursor.php',
'NuanceGitHubRepositorySourceDefinition' => 'applications/nuance/source/NuanceGitHubRepositorySourceDefinition.php',
'NuanceImportCursor' => 'applications/nuance/cursor/NuanceImportCursor.php',
@@ -4970,6 +4973,8 @@
'DoorkeeperAsanaRemarkupRule' => 'DoorkeeperRemarkupRule',
'DoorkeeperBridge' => 'Phobject',
'DoorkeeperBridgeAsana' => 'DoorkeeperBridge',
+ 'DoorkeeperBridgeGitHub' => 'DoorkeeperBridge',
+ 'DoorkeeperBridgeGitHubIssue' => 'DoorkeeperBridgeGitHub',
'DoorkeeperBridgeJIRA' => 'DoorkeeperBridge',
'DoorkeeperBridgeJIRATestCase' => 'PhabricatorTestCase',
'DoorkeeperDAO' => 'PhabricatorLiskDAO',
@@ -5681,6 +5686,7 @@
'NuanceGitHubEventItemType' => 'NuanceItemType',
'NuanceGitHubImportCursor' => 'NuanceImportCursor',
'NuanceGitHubIssuesImportCursor' => 'NuanceGitHubImportCursor',
+ 'NuanceGitHubRawEvent' => 'Phobject',
'NuanceGitHubRepositoryImportCursor' => 'NuanceGitHubImportCursor',
'NuanceGitHubRepositorySourceDefinition' => 'NuanceSourceDefinition',
'NuanceImportCursor' => 'Phobject',
diff --git a/src/applications/doorkeeper/bridge/DoorkeeperBridge.php b/src/applications/doorkeeper/bridge/DoorkeeperBridge.php
--- a/src/applications/doorkeeper/bridge/DoorkeeperBridge.php
+++ b/src/applications/doorkeeper/bridge/DoorkeeperBridge.php
@@ -3,6 +3,7 @@
abstract class DoorkeeperBridge extends Phobject {
private $viewer;
+ private $context = array();
private $throwOnMissingLink;
public function setThrowOnMissingLink($throw_on_missing_link) {
@@ -19,6 +20,15 @@
return $this->viewer;
}
+ final public function setContext($context) {
+ $this->context = $context;
+ return $this;
+ }
+
+ final public function getContextProperty($key, $default = null) {
+ return idx($this->context, $key, $default);
+ }
+
public function isEnabled() {
return true;
}
diff --git a/src/applications/doorkeeper/bridge/DoorkeeperBridgeGitHub.php b/src/applications/doorkeeper/bridge/DoorkeeperBridgeGitHub.php
new file mode 100644
--- /dev/null
+++ b/src/applications/doorkeeper/bridge/DoorkeeperBridgeGitHub.php
@@ -0,0 +1,50 @@
+<?php
+
+abstract class DoorkeeperBridgeGitHub extends DoorkeeperBridge {
+
+ const APPTYPE_GITHUB = 'github';
+ const APPDOMAIN_GITHUB = 'github.com';
+
+ public function canPullRef(DoorkeeperObjectRef $ref) {
+ if ($ref->getApplicationType() != self::APPTYPE_GITHUB) {
+ return false;
+ }
+
+ if ($ref->getApplicationDomain() != self::APPDOMAIN_GITHUB) {
+ return false;
+ }
+
+ return true;
+ }
+
+ protected function getGitHubAccessToken() {
+ $context_token = $this->getContextProperty('github.token');
+ if ($context_token) {
+ return $context_token->openEnvelope();
+ }
+
+ // TODO: Do a bunch of work to fetch the viewer's linked account if
+ // they have one.
+
+ return $this->didFailOnMissingLink();
+ }
+
+ protected function parseGitHubIssueID($id) {
+ $matches = null;
+ if (!preg_match('(^([^/]+)/([^/]+)#([1-9]\d*)\z)', $id, $matches)) {
+ throw new Exception(
+ pht(
+ 'GitHub Issue ID "%s" is not properly formatted. Expected an ID '.
+ 'in the form "owner/repository#123".',
+ $id));
+ }
+
+ return array(
+ $matches[1],
+ $matches[2],
+ (int)$matches[3],
+ );
+ }
+
+
+}
diff --git a/src/applications/doorkeeper/bridge/DoorkeeperBridgeGitHubIssue.php b/src/applications/doorkeeper/bridge/DoorkeeperBridgeGitHubIssue.php
new file mode 100644
--- /dev/null
+++ b/src/applications/doorkeeper/bridge/DoorkeeperBridgeGitHubIssue.php
@@ -0,0 +1,97 @@
+<?php
+
+final class DoorkeeperBridgeGitHubIssue
+ extends DoorkeeperBridgeGitHub {
+
+ const OBJTYPE_GITHUB_ISSUE = 'github.issue';
+
+ public function canPullRef(DoorkeeperObjectRef $ref) {
+ if (!parent::canPullRef($ref)) {
+ return false;
+ }
+
+ if ($ref->getObjectType() !== self::OBJTYPE_GITHUB_ISSUE) {
+ return false;
+ }
+
+ return true;
+ }
+
+ public function pullRefs(array $refs) {
+ $token = $this->getGitHubAccessToken();
+ if (!strlen($token)) {
+ return null;
+ }
+
+ $template = id(new PhutilGitHubFuture())
+ ->setAccessToken($token);
+
+ $futures = array();
+ $id_map = mpull($refs, 'getObjectID', 'getObjectKey');
+ foreach ($id_map as $key => $id) {
+ list($user, $repository, $number) = $this->parseGitHubIssueID($id);
+ $uri = "/repos/{$user}/{$repository}/issues/{$number}";
+ $data = array();
+ $futures[$key] = id(clone $template)
+ ->setRawGitHubQuery($uri, $data);
+ }
+
+ $results = array();
+ $failed = array();
+ foreach (new FutureIterator($futures) as $key => $future) {
+ try {
+ $results[$key] = $future->resolve();
+ } catch (Exception $ex) {
+ if (($ex instanceof HTTPFutureResponseStatus) &&
+ ($ex->getStatusCode() == 404)) {
+ // TODO: Do we end up here for deleted objects and invisible
+ // objects?
+ } else {
+ phlog($ex);
+ $failed[$key] = $ex;
+ }
+ }
+ }
+
+ $viewer = $this->getViewer();
+
+ foreach ($refs as $ref) {
+ $ref->setAttribute('name', pht('GitHub Issue %s', $ref->getObjectID()));
+
+ $did_fail = idx($failed, $ref->getObjectKey());
+ if ($did_fail) {
+ $ref->setSyncFailed(true);
+ continue;
+ }
+
+ $result = idx($results, $ref->getObjectKey());
+ if (!$result) {
+ continue;
+ }
+
+ $body = $result->getBody();
+
+ $ref->setIsVisible(true);
+ $ref->setAttribute('api.raw', $body);
+ $ref->setAttribute('name', $body['title']);
+
+ $obj = $ref->getExternalObject();
+ if ($obj->getID()) {
+ continue;
+ }
+
+ $this->fillObjectFromData($obj, $result);
+
+ $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
+ $obj->save();
+ unset($unguarded);
+ }
+ }
+
+ public function fillObjectFromData(DoorkeeperExternalObject $obj, $result) {
+ $body = $result->getBody();
+ $uri = $body['html_url'];
+ $obj->setObjectURI($uri);
+ }
+
+}
diff --git a/src/applications/doorkeeper/engine/DoorkeeperImportEngine.php b/src/applications/doorkeeper/engine/DoorkeeperImportEngine.php
--- a/src/applications/doorkeeper/engine/DoorkeeperImportEngine.php
+++ b/src/applications/doorkeeper/engine/DoorkeeperImportEngine.php
@@ -7,6 +7,7 @@
private $phids = array();
private $localOnly;
private $throwOnMissingLink;
+ private $context = array();
public function setViewer(PhabricatorUser $viewer) {
$this->viewer = $viewer;
@@ -37,6 +38,10 @@
return $this;
}
+ public function setContextProperty($key, $value) {
+ $this->context[$key] = $value;
+ return $this;
+ }
/**
* Configure behavior if remote refs can not be retrieved because an
@@ -96,6 +101,7 @@
foreach ($bridges as $key => $bridge) {
$bridge->setViewer($viewer);
$bridge->setThrowOnMissingLink($this->throwOnMissingLink);
+ $bridge->setContext($this->context);
}
$working_set = $refs;
diff --git a/src/applications/nuance/github/NuanceGitHubRawEvent.php b/src/applications/nuance/github/NuanceGitHubRawEvent.php
new file mode 100644
--- /dev/null
+++ b/src/applications/nuance/github/NuanceGitHubRawEvent.php
@@ -0,0 +1,72 @@
+<?php
+
+final class NuanceGitHubRawEvent extends Phobject {
+
+ private $raw;
+ private $type;
+
+ const TYPE_ISSUE = 'issue';
+ const TYPE_REPOSITORY = 'repository';
+
+ public static function newEvent($type, array $raw) {
+ $event = new self();
+ $event->type = $type;
+ $event->raw = $raw;
+ return $event;
+ }
+
+ public function getRepositoryFullName() {
+ return $this->getRepositoryFullRawName();
+ }
+
+ public function isIssueEvent() {
+ if ($this->isPullRequestEvent()) {
+ return false;
+ }
+
+ if ($this->type == self::TYPE_ISSUE) {
+ return true;
+ }
+
+ switch ($this->getIssueRawKind()) {
+ case 'IssuesEvent':
+ case 'IssuesCommentEvent':
+ return true;
+ }
+
+ return false;
+ }
+
+ public function isPullRequestEvent() {
+ return false;
+ }
+
+ public function getIssueNumber() {
+ if (!$this->isIssueEvent()) {
+ return null;
+ }
+
+ $raw = $this->raw;
+
+ if ($this->type == self::TYPE_ISSUE) {
+ return idxv($raw, array('issue', 'number'));
+ }
+
+ if ($this->type == self::TYPE_REPOSITORY) {
+ return idxv($raw, array('payload', 'issue', 'number'));
+ }
+
+ return null;
+ }
+
+ private function getRepositoryFullRawName() {
+ $raw = $this->raw;
+ return idxv($raw, array('repo', 'name'));
+ }
+
+ private function getIssueRawKind() {
+ $raw = $this->raw;
+ return idxv($raw, array('type'));
+ }
+
+}
diff --git a/src/applications/nuance/item/NuanceGitHubEventItemType.php b/src/applications/nuance/item/NuanceGitHubEventItemType.php
--- a/src/applications/nuance/item/NuanceGitHubEventItemType.php
+++ b/src/applications/nuance/item/NuanceGitHubEventItemType.php
@@ -74,13 +74,74 @@
}
protected function updateItemFromSource(NuanceItem $item) {
+ $viewer = $this->getViewer();
+ $is_dirty = false;
+
// TODO: Link up the requestor, etc.
+ $source = $item->getSource();
+ $token = $source->getSourceProperty('github.token');
+ $token = new PhutilOpaqueEnvelope($token);
+
+ $ref = $this->getDoorkeeperRef($item);
+ if ($ref) {
+ $ref = id(new DoorkeeperImportEngine())
+ ->setViewer($viewer)
+ ->setRefs(array($ref))
+ ->setThrowOnMissingLink(true)
+ ->setContextProperty('github.token', $token)
+ ->executeOne();
+
+ if ($ref->getSyncFailed()) {
+ $xobj = null;
+ } else {
+ $xobj = $ref->getExternalObject();
+ }
+
+ if ($xobj) {
+ $item->setItemProperty('doorkeeper.xobj.phid', $xobj->getPHID());
+ $is_dirty = true;
+ }
+ }
+
if ($item->getStatus() == NuanceItem::STATUS_IMPORTING) {
- $item
- ->setStatus(NuanceItem::STATUS_ROUTING)
- ->save();
+ $item->setStatus(NuanceItem::STATUS_ROUTING);
+ $is_dirty = true;
+ }
+
+ if ($is_dirty) {
+ $item->save();
+ }
+ }
+
+ private function getDoorkeeperRef(NuanceItem $item) {
+ $raw = $this->newRawEvent($item);
+
+ $full_repository = $raw->getRepositoryFullName();
+ if (!strlen($full_repository)) {
+ return null;
+ }
+
+ if ($raw->isIssueEvent()) {
+ $ref_type = DoorkeeperBridgeGitHubIssue::OBJTYPE_GITHUB_ISSUE;
+ $issue_number = $raw->getIssueNumber();
+ $full_ref = "{$full_repository}#{$issue_number}";
+ } else {
+ return null;
}
+
+ return id(new DoorkeeperObjectRef())
+ ->setApplicationType(DoorkeeperBridgeGitHub::APPTYPE_GITHUB)
+ ->setApplicationDomain(DoorkeeperBridgeGitHub::APPDOMAIN_GITHUB)
+ ->setObjectType($ref_type)
+ ->setObjectID($full_ref);
+ }
+
+ private function newRawEvent(NuanceItem $item) {
+ $type = $item->getItemProperty('api.type');
+ $raw = $item->getItemProperty('api.raw', array());
+
+ return NuanceGitHubRawEvent::newEvent($type, $raw);
}
}
diff --git a/src/applications/nuance/worker/NuanceItemUpdateWorker.php b/src/applications/nuance/worker/NuanceItemUpdateWorker.php
--- a/src/applications/nuance/worker/NuanceItemUpdateWorker.php
+++ b/src/applications/nuance/worker/NuanceItemUpdateWorker.php
@@ -25,9 +25,14 @@
private function updateItem(NuanceItem $item) {
$impl = $item->getImplementation();
- if ($impl->canUpdateItems()) {
- $impl->updateItem($item);
+ if (!$impl->canUpdateItems()) {
+ return null;
}
+
+ $viewer = $this->getViewer();
+
+ $impl->setViewer($viewer);
+ $impl->updateItem($item);
}
private function routeItem(NuanceItem $item) {

File Metadata

Mime Type
text/plain
Expires
Sat, Mar 15, 11:33 PM (2 w, 6 d ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
7685921
Default Alt Text
D15447.id37229.diff (13 KB)

Event Timeline