Page MenuHomestyx hydra

No OneTemporary

diff --git a/src/applications/nuance/controller/NuanceItemViewController.php b/src/applications/nuance/controller/NuanceItemViewController.php
index 091ade2d6b..7ef5d06682 100644
--- a/src/applications/nuance/controller/NuanceItemViewController.php
+++ b/src/applications/nuance/controller/NuanceItemViewController.php
@@ -1,122 +1,126 @@
<?php
final class NuanceItemViewController extends NuanceController {
public function handleRequest(AphrontRequest $request) {
$viewer = $this->getViewer();
$id = $request->getURIData('id');
$item = id(new NuanceItemQuery())
->setViewer($viewer)
->withIDs(array($id))
->executeOne();
if (!$item) {
return new Aphront404Response();
}
$title = pht('Item %d', $item->getID());
$name = $item->getDisplayName();
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addTextCrumb(
pht('Items'),
$this->getApplicationURI('item/'));
$crumbs->addTextCrumb($title);
$crumbs->setBorder(true);
$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);
$view = id(new PHUITwoColumnView())
->setHeader($header)
->setCurtain($curtain)
->setMainColumn($main);
return $this->newPage()
->setTitle($title)
->setCrumbs($crumbs)
->appendChild($view);
}
private function buildCurtain(NuanceItem $item) {
$viewer = $this->getViewer();
$id = $item->getID();
$can_edit = PhabricatorPolicyFilter::hasCapability(
$viewer,
$item,
PhabricatorPolicyCapability::CAN_EDIT);
$curtain = $this->newCurtainView($item);
$curtain->addAction(
id(new PhabricatorActionView())
->setName(pht('Manage Item'))
->setIcon('fa-cogs')
->setHref($this->getApplicationURI("item/manage/{$id}/")));
$impl = $item->getImplementation();
$impl->setViewer($viewer);
foreach ($impl->getItemActions($item) as $action) {
$curtain->addAction($action);
}
+ foreach ($impl->getItemCurtainPanels($item) as $panel) {
+ $curtain->addPanel($panel);
+ }
+
return $curtain;
}
private function buildContent(NuanceItem $item) {
$viewer = $this->getViewer();
$impl = $item->getImplementation();
$impl->setViewer($viewer);
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);
}
}
diff --git a/src/applications/nuance/github/NuanceGitHubRawEvent.php b/src/applications/nuance/github/NuanceGitHubRawEvent.php
index 1283fb43b7..4da2bb8e46 100644
--- a/src/applications/nuance/github/NuanceGitHubRawEvent.php
+++ b/src/applications/nuance/github/NuanceGitHubRawEvent.php
@@ -1,380 +1,386 @@
<?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':
return true;
case 'IssueCommentEvent':
if (!$this->getRawPullRequestData()) {
return true;
}
break;
}
return false;
}
public function isPullRequestEvent() {
if ($this->type == self::TYPE_ISSUE) {
// TODO: This is wrong, some of these are pull events.
return false;
}
$raw = $this->raw;
switch ($this->getIssueRawKind()) {
case 'PullRequestEvent':
return true;
case 'IssueCommentEvent':
if ($this->getRawPullRequestData()) {
return true;
}
break;
}
return false;
}
public function getIssueNumber() {
if (!$this->isIssueEvent()) {
return null;
}
return $this->getRawIssueNumber();
}
public function getPullRequestNumber() {
if (!$this->isPullRequestEvent()) {
return null;
}
return $this->getRawIssueNumber();
}
public function getID() {
$raw = $this->raw;
$id = idx($raw, 'id');
if ($id) {
return (int)$id;
}
return null;
}
public function getComment() {
- return 'TODO: Actually extract comment text.';
+ if (!$this->isIssueEvent() && !$this->isPullRequestEvent()) {
+ return null;
+ }
+
+ $raw = $this->raw;
+
+ return idxv($raw, array('payload', 'comment', 'body'));
}
public function getURI() {
$raw = $this->raw;
if ($this->isIssueEvent() || $this->isPullRequestEvent()) {
if ($this->type == self::TYPE_ISSUE) {
$uri = idxv($raw, array('issue', 'html_url'));
$uri = $uri.'#event-'.$this->getID();
} else {
// The format of pull request events varies so we need to fish around
// a bit to find the correct URI.
$uri = idxv($raw, array('payload', 'pull_request', 'html_url'));
$need_anchor = true;
// For comments, we get a different anchor to link to the comment. In
// this case, the URI comes with an anchor already.
if (!$uri) {
$uri = idxv($raw, array('payload', 'comment', 'html_url'));
$need_anchor = false;
}
if (!$uri) {
$uri = idxv($raw, array('payload', 'issue', 'html_url'));
$need_anchor = true;
}
if ($need_anchor) {
$uri = $uri.'#event-'.$this->getID();
}
}
} else {
switch ($this->getIssueRawKind()) {
case 'CreateEvent':
$ref = idxv($raw, array('payload', 'ref'));
$repo = $this->getRepositoryFullRawName();
return "https://github.com/{$repo}/commits/{$ref}";
case 'PushEvent':
// These don't really have a URI since there may be multiple commits
// involved and GitHub doesn't bundle the push as an object on its
// own. Just try to find the URI for the log. The API also does
// not return any HTML URI for these events.
$head = idxv($raw, array('payload', 'head'));
if ($head === null) {
return null;
}
$repo = $this->getRepositoryFullRawName();
return "https://github.com/{$repo}/commits/{$head}";
case 'WatchEvent':
// These have no reasonable URI.
return null;
default:
return null;
}
}
return $uri;
}
private function getRepositoryFullRawName() {
$raw = $this->raw;
$full = idxv($raw, array('repo', 'name'));
if (strlen($full)) {
return $full;
}
// For issue events, the repository is not identified explicitly in the
// response body. Parse it out of the URI.
$matches = null;
$ok = preg_match(
'(/repos/((?:[^/]+)/(?:[^/]+))/issues/events/)',
idx($raw, 'url'),
$matches);
if ($ok) {
return $matches[1];
}
return null;
}
private function getIssueRawKind() {
$raw = $this->raw;
return idxv($raw, array('type'));
}
private function getRawIssueNumber() {
$raw = $this->raw;
if ($this->type == self::TYPE_ISSUE) {
return idxv($raw, array('issue', 'number'));
}
if ($this->type == self::TYPE_REPOSITORY) {
$issue_number = idxv($raw, array('payload', 'issue', 'number'));
if ($issue_number) {
return $issue_number;
}
$pull_number = idxv($raw, array('payload', 'number'));
if ($pull_number) {
return $pull_number;
}
}
return null;
}
private function getRawPullRequestData() {
$raw = $this->raw;
return idxv($raw, array('payload', 'issue', 'pull_request'));
}
public function getEventFullTitle() {
switch ($this->type) {
case self::TYPE_ISSUE:
$title = $this->getRawIssueEventTitle();
break;
case self::TYPE_REPOSITORY:
$title = $this->getRawRepositoryEventTitle();
break;
default:
$title = pht('Unknown Event Type ("%s")', $this->type);
break;
}
return pht(
'GitHub %s %s (%s)',
$this->getRepositoryFullRawName(),
$this->getTargetObjectName(),
$title);
}
private function getTargetObjectName() {
if ($this->isPullRequestEvent()) {
$number = $this->getRawIssueNumber();
return pht('Pull Request #%d', $number);
} else if ($this->isIssueEvent()) {
$number = $this->getRawIssueNumber();
return pht('Issue #%d', $number);
} else if ($this->type == self::TYPE_REPOSITORY) {
$raw = $this->raw;
$type = idx($raw, 'type');
switch ($type) {
case 'CreateEvent':
$ref = idxv($raw, array('payload', 'ref'));
$ref_type = idxv($raw, array('payload', 'ref_type'));
switch ($ref_type) {
case 'branch':
return pht('Branch %s', $ref);
case 'tag':
return pht('Tag %s', $ref);
default:
return pht('Ref %s', $ref);
}
break;
case 'PushEvent':
$ref = idxv($raw, array('payload', 'ref'));
if (preg_match('(^refs/heads/)', $ref)) {
return pht('Branch %s', substr($ref, strlen('refs/heads/')));
} else {
return pht('Ref %s', $ref);
}
break;
case 'WatchEvent':
$actor = idxv($raw, array('actor', 'login'));
return pht('User %s', $actor);
}
return pht('Unknown Object');
} else {
return pht('Unknown Object');
}
}
private function getRawIssueEventTitle() {
$raw = $this->raw;
$action = idxv($raw, array('event'));
switch ($action) {
case 'assigned':
$assignee = idxv($raw, array('assignee', 'login'));
$title = pht('Assigned: %s', $assignee);
break;
case 'closed':
$title = pht('Closed');
break;
case 'demilestoned':
$milestone = idxv($raw, array('milestone', 'title'));
$title = pht('Removed Milestone: %s', $milestone);
break;
case 'labeled':
$label = idxv($raw, array('label', 'name'));
$title = pht('Added Label: %s', $label);
break;
case 'locked':
$title = pht('Locked');
break;
case 'milestoned':
$milestone = idxv($raw, array('milestone', 'title'));
$title = pht('Added Milestone: %s', $milestone);
break;
case 'renamed':
$title = pht('Renamed');
break;
case 'reopened':
$title = pht('Reopened');
break;
case 'unassigned':
$assignee = idxv($raw, array('assignee', 'login'));
$title = pht('Unassigned: %s', $assignee);
break;
case 'unlabeled':
$label = idxv($raw, array('label', 'name'));
$title = pht('Removed Label: %s', $label);
break;
case 'unlocked':
$title = pht('Unlocked');
break;
default:
$title = pht('"%s"', $action);
break;
}
return $title;
}
private function getRawRepositoryEventTitle() {
$raw = $this->raw;
$type = idx($raw, 'type');
switch ($type) {
case 'CreateEvent':
return pht('Created');
case 'PushEvent':
$head = idxv($raw, array('payload', 'head'));
$head = substr($head, 0, 12);
return pht('Pushed: %s', $head);
case 'IssuesEvent':
$action = idxv($raw, array('payload', 'action'));
switch ($action) {
case 'closed':
return pht('Closed');
case 'opened':
return pht('Created');
case 'reopened':
return pht('Reopened');
default:
return pht('"%s"', $action);
}
break;
case 'IssueCommentEvent':
$action = idxv($raw, array('payload', 'action'));
switch ($action) {
case 'created':
return pht('Comment');
default:
return pht('"%s"', $action);
}
break;
case 'PullRequestEvent':
$action = idxv($raw, array('payload', 'action'));
switch ($action) {
case 'opened':
return pht('Created');
default:
return pht('"%s"', $action);
}
break;
case 'WatchEvent':
return pht('Watched');
}
return pht('"%s"', $type);
}
}
diff --git a/src/applications/nuance/github/__tests__/NuanceGitHubRawEventTestCase.php b/src/applications/nuance/github/__tests__/NuanceGitHubRawEventTestCase.php
index f5e2119141..5bdc3f34aa 100644
--- a/src/applications/nuance/github/__tests__/NuanceGitHubRawEventTestCase.php
+++ b/src/applications/nuance/github/__tests__/NuanceGitHubRawEventTestCase.php
@@ -1,111 +1,112 @@
<?php
final class NuanceGitHubRawEventTestCase
extends PhabricatorTestCase {
public function testIssueEvents() {
$path = dirname(__FILE__).'/issueevents/';
$cases = $this->readTestCases($path);
foreach ($cases as $name => $info) {
$input = $info['input'];
$expect = $info['expect'];
$event = NuanceGitHubRawEvent::newEvent(
NuanceGitHubRawEvent::TYPE_ISSUE,
$input);
$this->assertGitHubRawEventParse($expect, $event, $name);
}
}
public function testRepositoryEvents() {
$path = dirname(__FILE__).'/repositoryevents/';
$cases = $this->readTestCases($path);
foreach ($cases as $name => $info) {
$input = $info['input'];
$expect = $info['expect'];
$event = NuanceGitHubRawEvent::newEvent(
NuanceGitHubRawEvent::TYPE_REPOSITORY,
$input);
$this->assertGitHubRawEventParse($expect, $event, $name);
}
}
private function assertGitHubRawEventParse(
array $expect,
NuanceGitHubRawEvent $event,
$name) {
$actual = array(
'repository.name.full' => $event->getRepositoryFullName(),
'is.issue' => $event->isIssueEvent(),
'is.pull' => $event->isPullRequestEvent(),
'issue.number' => $event->getIssueNumber(),
'pull.number' => $event->getPullRequestNumber(),
'id' => $event->getID(),
'uri' => $event->getURI(),
'title.full' => $event->getEventFullTitle(),
+ 'comment' => $event->getComment(),
);
// Only verify the keys which are actually present in the test. This
// allows tests to specify only relevant keys.
$actual = array_select_keys($actual, array_keys($expect));
ksort($expect);
ksort($actual);
$this->assertEqual($expect, $actual, $name);
}
private function readTestCases($path) {
$files = Filesystem::listDirectory($path, $include_hidden = false);
$tests = array();
foreach ($files as $file) {
$data = Filesystem::readFile($path.$file);
$parts = preg_split('/^~{5,}$/m', $data);
if (count($parts) < 2) {
throw new Exception(
pht(
'Expected test file "%s" to contain an input section in JSON, '.
'then an expected result section in JSON, with the two sections '.
'separated by a line of "~~~~~", but the divider is not present '.
'in the file.',
$file));
} else if (count($parts) > 2) {
throw new Exception(
pht(
'Expected test file "%s" to contain exactly two sections, '.
'but it has more than two sections.'));
}
list($input, $expect) = $parts;
try {
$input = phutil_json_decode($input);
$expect = phutil_json_decode($expect);
} catch (Exception $ex) {
throw new PhutilProxyException(
pht(
'Exception while decoding test data for test "%s".',
$file),
$ex);
}
$tests[$file] = array(
'input' => $input,
'expect' => $expect,
);
}
return $tests;
}
}
diff --git a/src/applications/nuance/github/__tests__/repositoryevents/IssueCommentEvent.created.pull.txt b/src/applications/nuance/github/__tests__/repositoryevents/IssueCommentEvent.created.pull.txt
index 71abbceac4..1991bb568e 100644
--- a/src/applications/nuance/github/__tests__/repositoryevents/IssueCommentEvent.created.pull.txt
+++ b/src/applications/nuance/github/__tests__/repositoryevents/IssueCommentEvent.created.pull.txt
@@ -1,164 +1,165 @@
{
"id": "3740938746",
"type": "IssueCommentEvent",
"actor": {
"id": 102631,
"login": "epriestley",
"gravatar_id": "",
"url": "https://api.github.com/users/epriestley",
"avatar_url": "https://avatars.githubusercontent.com/u/102631?"
},
"repo": {
"id": 14627834,
"name": "epriestley/poems",
"url": "https://api.github.com/repos/epriestley/poems"
},
"payload": {
"action": "created",
"issue": {
"url": "https://api.github.com/repos/epriestley/poems/issues/2",
"repository_url": "https://api.github.com/repos/epriestley/poems",
"labels_url": "https://api.github.com/repos/epriestley/poems/issues/2/labels{/name}",
"comments_url": "https://api.github.com/repos/epriestley/poems/issues/2/comments",
"events_url": "https://api.github.com/repos/epriestley/poems/issues/2/events",
"html_url": "https://github.com/epriestley/poems/pull/2",
"id": 139568860,
"number": 2,
"title": "Please Merge Quack2 into Feature",
"user": {
"login": "epriestley",
"id": 102631,
"avatar_url": "https://avatars.githubusercontent.com/u/102631?v=3",
"gravatar_id": "",
"url": "https://api.github.com/users/epriestley",
"html_url": "https://github.com/epriestley",
"followers_url": "https://api.github.com/users/epriestley/followers",
"following_url": "https://api.github.com/users/epriestley/following{/other_user}",
"gists_url": "https://api.github.com/users/epriestley/gists{/gist_id}",
"starred_url": "https://api.github.com/users/epriestley/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/epriestley/subscriptions",
"organizations_url": "https://api.github.com/users/epriestley/orgs",
"repos_url": "https://api.github.com/users/epriestley/repos",
"events_url": "https://api.github.com/users/epriestley/events{/privacy}",
"received_events_url": "https://api.github.com/users/epriestley/received_events",
"type": "User",
"site_admin": false
},
"labels": [
{
"url": "https://api.github.com/repos/epriestley/poems/labels/bug",
"name": "bug",
"color": "fc2929"
}
],
"state": "open",
"locked": false,
"assignee": {
"login": "epriestley",
"id": 102631,
"avatar_url": "https://avatars.githubusercontent.com/u/102631?v=3",
"gravatar_id": "",
"url": "https://api.github.com/users/epriestley",
"html_url": "https://github.com/epriestley",
"followers_url": "https://api.github.com/users/epriestley/followers",
"following_url": "https://api.github.com/users/epriestley/following{/other_user}",
"gists_url": "https://api.github.com/users/epriestley/gists{/gist_id}",
"starred_url": "https://api.github.com/users/epriestley/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/epriestley/subscriptions",
"organizations_url": "https://api.github.com/users/epriestley/orgs",
"repos_url": "https://api.github.com/users/epriestley/repos",
"events_url": "https://api.github.com/users/epriestley/events{/privacy}",
"received_events_url": "https://api.github.com/users/epriestley/received_events",
"type": "User",
"site_admin": false
},
"milestone": {
"url": "https://api.github.com/repos/epriestley/poems/milestones/1",
"html_url": "https://github.com/epriestley/poems/milestones/b",
"labels_url": "https://api.github.com/repos/epriestley/poems/milestones/1/labels",
"id": 1633589,
"number": 1,
"title": "b",
"description": null,
"creator": {
"login": "epriestley",
"id": 102631,
"avatar_url": "https://avatars.githubusercontent.com/u/102631?v=3",
"gravatar_id": "",
"url": "https://api.github.com/users/epriestley",
"html_url": "https://github.com/epriestley",
"followers_url": "https://api.github.com/users/epriestley/followers",
"following_url": "https://api.github.com/users/epriestley/following{/other_user}",
"gists_url": "https://api.github.com/users/epriestley/gists{/gist_id}",
"starred_url": "https://api.github.com/users/epriestley/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/epriestley/subscriptions",
"organizations_url": "https://api.github.com/users/epriestley/orgs",
"repos_url": "https://api.github.com/users/epriestley/repos",
"events_url": "https://api.github.com/users/epriestley/events{/privacy}",
"received_events_url": "https://api.github.com/users/epriestley/received_events",
"type": "User",
"site_admin": false
},
"open_issues": 1,
"closed_issues": 0,
"state": "open",
"created_at": "2016-03-09T12:42:50Z",
"updated_at": "2016-03-09T12:52:41Z",
"due_on": null,
"closed_at": null
},
"comments": 1,
"created_at": "2016-03-09T12:52:31Z",
"updated_at": "2016-03-09T12:53:06Z",
"closed_at": null,
"pull_request": {
"url": "https://api.github.com/repos/epriestley/poems/pulls/2",
"html_url": "https://github.com/epriestley/poems/pull/2",
"diff_url": "https://github.com/epriestley/poems/pull/2.diff",
"patch_url": "https://github.com/epriestley/poems/pull/2.patch"
},
"body": ""
},
"comment": {
"url": "https://api.github.com/repos/epriestley/poems/issues/comments/194282800",
"html_url": "https://github.com/epriestley/poems/pull/2#issuecomment-194282800",
"issue_url": "https://api.github.com/repos/epriestley/poems/issues/2",
"id": 194282800,
"user": {
"login": "epriestley",
"id": 102631,
"avatar_url": "https://avatars.githubusercontent.com/u/102631?v=3",
"gravatar_id": "",
"url": "https://api.github.com/users/epriestley",
"html_url": "https://github.com/epriestley",
"followers_url": "https://api.github.com/users/epriestley/followers",
"following_url": "https://api.github.com/users/epriestley/following{/other_user}",
"gists_url": "https://api.github.com/users/epriestley/gists{/gist_id}",
"starred_url": "https://api.github.com/users/epriestley/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/epriestley/subscriptions",
"organizations_url": "https://api.github.com/users/epriestley/orgs",
"repos_url": "https://api.github.com/users/epriestley/repos",
"events_url": "https://api.github.com/users/epriestley/events{/privacy}",
"received_events_url": "https://api.github.com/users/epriestley/received_events",
"type": "User",
"site_admin": false
},
"created_at": "2016-03-09T12:53:06Z",
"updated_at": "2016-03-09T12:53:06Z",
"body": "wub wub"
}
},
"public": true,
"created_at": "2016-03-09T12:53:06Z"
}
~~~~~
{
"repository.name.full": "epriestley/poems",
"is.issue": false,
"is.pull": true,
"issue.number": null,
"pull.number": 2,
"id": 3740938746,
"uri": "https://github.com/epriestley/poems/pull/2#issuecomment-194282800",
- "title.full": "GitHub epriestley/poems Pull Request #2 (Comment)"
+ "title.full": "GitHub epriestley/poems Pull Request #2 (Comment)",
+ "comment": "wub wub"
}
diff --git a/src/applications/nuance/github/__tests__/repositoryevents/IssueCommentEvent.created.txt b/src/applications/nuance/github/__tests__/repositoryevents/IssueCommentEvent.created.txt
index a1ca094045..d22fd86e25 100644
--- a/src/applications/nuance/github/__tests__/repositoryevents/IssueCommentEvent.created.txt
+++ b/src/applications/nuance/github/__tests__/repositoryevents/IssueCommentEvent.created.txt
@@ -1,101 +1,102 @@
{
"id": "3733510485",
"type": "IssueCommentEvent",
"actor": {
"id": 102631,
"login": "epriestley",
"gravatar_id": "",
"url": "https://api.github.com/users/epriestley",
"avatar_url": "https://avatars.githubusercontent.com/u/102631?"
},
"repo": {
"id": 14627834,
"name": "epriestley/poems",
"url": "https://api.github.com/repos/epriestley/poems"
},
"payload": {
"action": "created",
"issue": {
"url": "https://api.github.com/repos/epriestley/poems/issues/1",
"repository_url": "https://api.github.com/repos/epriestley/poems",
"labels_url": "https://api.github.com/repos/epriestley/poems/issues/1/labels{/name}",
"comments_url": "https://api.github.com/repos/epriestley/poems/issues/1/comments",
"events_url": "https://api.github.com/repos/epriestley/poems/issues/1/events",
"html_url": "https://github.com/epriestley/poems/issues/1",
"id": 139138813,
"number": 1,
"title": "Enforce haiku in commit messages",
"user": {
"login": "epriestley",
"id": 102631,
"avatar_url": "https://avatars.githubusercontent.com/u/102631?v=3",
"gravatar_id": "",
"url": "https://api.github.com/users/epriestley",
"html_url": "https://github.com/epriestley",
"followers_url": "https://api.github.com/users/epriestley/followers",
"following_url": "https://api.github.com/users/epriestley/following{/other_user}",
"gists_url": "https://api.github.com/users/epriestley/gists{/gist_id}",
"starred_url": "https://api.github.com/users/epriestley/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/epriestley/subscriptions",
"organizations_url": "https://api.github.com/users/epriestley/orgs",
"repos_url": "https://api.github.com/users/epriestley/repos",
"events_url": "https://api.github.com/users/epriestley/events{/privacy}",
"received_events_url": "https://api.github.com/users/epriestley/received_events",
"type": "User",
"site_admin": false
},
"labels": [
],
"state": "open",
"locked": false,
"assignee": null,
"milestone": null,
"comments": 1,
"created_at": "2016-03-08T00:41:08Z",
"updated_at": "2016-03-08T00:41:22Z",
"closed_at": null,
"body": "OK"
},
"comment": {
"url": "https://api.github.com/repos/epriestley/poems/issues/comments/193528669",
"html_url": "https://github.com/epriestley/poems/issues/1#issuecomment-193528669",
"issue_url": "https://api.github.com/repos/epriestley/poems/issues/1",
"id": 193528669,
"user": {
"login": "epriestley",
"id": 102631,
"avatar_url": "https://avatars.githubusercontent.com/u/102631?v=3",
"gravatar_id": "",
"url": "https://api.github.com/users/epriestley",
"html_url": "https://github.com/epriestley",
"followers_url": "https://api.github.com/users/epriestley/followers",
"following_url": "https://api.github.com/users/epriestley/following{/other_user}",
"gists_url": "https://api.github.com/users/epriestley/gists{/gist_id}",
"starred_url": "https://api.github.com/users/epriestley/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/epriestley/subscriptions",
"organizations_url": "https://api.github.com/users/epriestley/orgs",
"repos_url": "https://api.github.com/users/epriestley/repos",
"events_url": "https://api.github.com/users/epriestley/events{/privacy}",
"received_events_url": "https://api.github.com/users/epriestley/received_events",
"type": "User",
"site_admin": false
},
"created_at": "2016-03-08T00:41:22Z",
"updated_at": "2016-03-08T00:41:22Z",
"body": "comment on issue"
}
},
"public": true,
"created_at": "2016-03-08T00:41:22Z"
}
~~~~~
{
"repository.name.full": "epriestley/poems",
"is.issue": true,
"is.pull": false,
"issue.number": 1,
"id": 3733510485,
"uri": "https://github.com/epriestley/poems/issues/1#issuecomment-193528669",
- "title.full": "GitHub epriestley/poems Issue #1 (Comment)"
+ "title.full": "GitHub epriestley/poems Issue #1 (Comment)",
+ "comment": "comment on issue"
}
diff --git a/src/applications/nuance/item/NuanceGitHubEventItemType.php b/src/applications/nuance/item/NuanceGitHubEventItemType.php
index 75f44e8964..617d75e493 100644
--- a/src/applications/nuance/item/NuanceGitHubEventItemType.php
+++ b/src/applications/nuance/item/NuanceGitHubEventItemType.php
@@ -1,347 +1,370 @@
<?php
final class NuanceGitHubEventItemType
extends NuanceItemType {
const ITEMTYPE = 'github.event';
private $externalObject;
public function getItemTypeDisplayName() {
return pht('GitHub Event');
}
public function getItemTypeDisplayIcon() {
return 'fa-github';
}
public function getItemDisplayName(NuanceItem $item) {
return $this->newRawEvent($item)->getEventFullTitle();
}
public function canUpdateItems() {
return true;
}
protected function updateItemFromSource(NuanceItem $item) {
$viewer = $this->getViewer();
$is_dirty = false;
// TODO: Link up the requestor, etc.
$is_dirty = false;
$xobj = $this->reloadExternalObject($item);
if ($xobj) {
$item->setItemProperty('doorkeeper.xobj.phid', $xobj->getPHID());
$is_dirty = true;
}
if ($item->getStatus() == NuanceItem::STATUS_IMPORTING) {
$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 reloadExternalObject(NuanceItem $item, $local = false) {
$ref = $this->getDoorkeeperRef($item);
if (!$ref) {
return null;
}
$source = $item->getSource();
$token = $source->getSourceProperty('github.token');
$token = new PhutilOpaqueEnvelope($token);
$viewer = $this->getViewer();
$ref = id(new DoorkeeperImportEngine())
->setViewer($viewer)
->setRefs(array($ref))
->setThrowOnMissingLink(true)
->setContextProperty('github.token', $token)
->needLocalOnly($local)
->executeOne();
if ($ref->getSyncFailed()) {
$xobj = null;
} else {
$xobj = $ref->getExternalObject();
}
if ($xobj) {
$this->externalObject = $xobj;
}
return $xobj;
}
private function getExternalObject(NuanceItem $item) {
if ($this->externalObject === null) {
$xobj = $this->reloadExternalObject($item, $local = true);
if ($xobj) {
$this->externalObject = $xobj;
} else {
$this->externalObject = false;
}
}
if ($this->externalObject) {
return $this->externalObject;
}
return null;
}
private function newRawEvent(NuanceItem $item) {
$type = $item->getItemProperty('api.type');
$raw = $item->getItemProperty('api.raw', array());
return NuanceGitHubRawEvent::newEvent($type, $raw);
}
public function getItemActions(NuanceItem $item) {
$actions = array();
$xobj = $this->getExternalObject($item);
if ($xobj) {
$actions[] = $this->newItemAction($item, 'reload')
->setName(pht('Reload from GitHub'))
->setIcon('fa-refresh')
->setWorkflow(true)
->setRenderAsForm(true);
}
$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)
->setIcon('fa-code');
return $actions;
}
+ public function getItemCurtainPanels(NuanceItem $item) {
+ $viewer = $this->getViewer();
+
+ $panels = array();
+
+ $xobj = $this->getExternalObject($item);
+ if ($xobj) {
+ $xobj_phid = $xobj->getPHID();
+
+ $task = id(new ManiphestTaskQuery())
+ ->setViewer($viewer)
+ ->withBridgedObjectPHIDs(array($xobj_phid))
+ ->executeOne();
+ if ($task) {
+ $panels[] = $this->newCurtainPanel($item)
+ ->setHeaderText(pht('Imported As'))
+ ->appendChild($viewer->renderHandle($task->getPHID()));
+ }
+ }
+
+ return $panels;
+ }
+
protected function handleAction(NuanceItem $item, $action) {
$viewer = $this->getViewer();
$controller = $this->getController();
switch ($action) {
case 'raw':
$raw = array(
'api.type' => $item->getItemProperty('api.type'),
'api.raw' => $item->getItemProperty('api.raw'),
);
$raw_output = id(new PhutilJSON())->encodeFormatted($raw);
$raw_box = id(new AphrontFormTextAreaControl())
->setCustomClass('PhabricatorMonospaced')
->setLabel(pht('Raw Event'))
->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_TALL)
->setValue($raw_output);
$form = id(new AphrontFormView())
->appendChild($raw_box);
return $controller->newDialog()
->setWidth(AphrontDialogView::WIDTH_FULL)
->setTitle(pht('GitHub Raw Event'))
->appendForm($form)
->addCancelButton($item->getURI(), pht('Done'));
case 'sync':
case 'reload':
$item->issueCommand($viewer->getPHID(), $action);
return id(new AphrontRedirectResponse())->setURI($item->getURI());
}
return null;
}
protected function newItemView(NuanceItem $item) {
$content = array();
$content[] = $this->newGitHubEventItemPropertyBox($item);
return $content;
}
private function newGitHubEventItemPropertyBox($item) {
$viewer = $this->getViewer();
$property_list = id(new PHUIPropertyListView())
->setViewer($viewer);
$event = $this->newRawEvent($item);
$property_list->addProperty(
pht('GitHub Event ID'),
$event->getID());
$event_uri = $event->getURI();
if ($event_uri && PhabricatorEnv::isValidRemoteURIForLink($event_uri)) {
$event_uri = phutil_tag(
'a',
array(
'href' => $event_uri,
),
$event_uri);
}
if ($event_uri) {
$property_list->addProperty(
pht('GitHub Event URI'),
$event_uri);
}
return id(new PHUIObjectBoxView())
->setHeaderText(pht('Event Properties'))
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->appendChild($property_list);
}
protected function handleCommand(
NuanceItem $item,
NuanceItemCommand $command) {
$action = $command->getCommand();
switch ($action) {
case 'sync':
return $this->syncItem($item, $command);
case 'reload':
$this->reloadExternalObject($item);
return true;
}
return null;
}
private function syncItem(
NuanceItem $item,
NuanceItemCommand $command) {
$xobj_phid = $item->getItemProperty('doorkeeper.xobj.phid');
if (!$xobj_phid) {
throw new Exception(
pht(
'Unable to sync: no external object PHID.'));
}
// TODO: Write some kind of marker to prevent double-synchronization.
$viewer = $this->getViewer();
$xobj = id(new DoorkeeperExternalObjectQuery())
->setViewer($viewer)
->withPHIDs(array($xobj_phid))
->executeOne();
if (!$xobj) {
throw new Exception(
pht(
'Unable to sync: failed to load object "%s".',
$xobj_phid));
}
$nuance_phid = id(new PhabricatorNuanceApplication())->getPHID();
$xactions = array();
$task = id(new ManiphestTaskQuery())
->setViewer($viewer)
->withBridgedObjectPHIDs(array($xobj_phid))
->executeOne();
if (!$task) {
$task = ManiphestTask::initializeNewTask($viewer)
->setAuthorPHID($nuance_phid)
->setBridgedObjectPHID($xobj_phid);
$title = $xobj->getProperty('task.title');
if (!strlen($title)) {
$title = pht('Nuance Item %d Task', $item->getID());
}
$description = $xobj->getProperty('task.description');
$created = $xobj->getProperty('task.created');
$state = $xobj->getProperty('task.state');
$xactions[] = id(new ManiphestTransaction())
->setTransactionType(ManiphestTransaction::TYPE_TITLE)
->setNewValue($title)
->setDateCreated($created);
$xactions[] = id(new ManiphestTransaction())
->setTransactionType(ManiphestTransaction::TYPE_DESCRIPTION)
->setNewValue($description)
->setDateCreated($created);
$task->setDateCreated($created);
// TODO: Synchronize state.
}
$event = $this->newRawEvent($item);
$comment = $event->getComment();
if (strlen($comment)) {
$xactions[] = id(new ManiphestTransaction())
->setTransactionType(PhabricatorTransactions::TYPE_COMMENT)
->attachComment(
id(new ManiphestTransactionComment())
->setContent($comment));
}
// TODO: Preserve the item's original source.
$source = PhabricatorContentSource::newForSource(
PhabricatorDaemonContentSource::SOURCECONST);
// TODO: This should really be the external source.
$acting_phid = $nuance_phid;
$editor = id(new ManiphestTransactionEditor())
->setActor($viewer)
->setActingAsPHID($acting_phid)
->setContentSource($source)
->setContinueOnNoEffect(true)
->setContinueOnMissingFields(true);
$xactions = $editor->applyTransactions($task, $xactions);
return array(
'objectPHID' => $task->getPHID(),
'xactionPHIDs' => mpull($xactions, 'getPHID'),
);
}
}
diff --git a/src/applications/nuance/item/NuanceItemType.php b/src/applications/nuance/item/NuanceItemType.php
index d4187bf418..a1186c6ddd 100644
--- a/src/applications/nuance/item/NuanceItemType.php
+++ b/src/applications/nuance/item/NuanceItemType.php
@@ -1,139 +1,147 @@
<?php
abstract class NuanceItemType
extends Phobject {
private $viewer;
private $controller;
public function setViewer(PhabricatorUser $viewer) {
$this->viewer = $viewer;
return $this;
}
public function getViewer() {
return $this->viewer;
}
public function setController(PhabricatorController $controller) {
$this->controller = $controller;
return $this;
}
public function getController() {
return $this->controller;
}
public function canUpdateItems() {
return false;
}
final public function buildItemView(NuanceItem $item) {
return $this->newItemView($item);
}
protected function newItemView(NuanceItem $item) {
return null;
}
public function getItemTypeDisplayIcon() {
return null;
}
public function getItemActions(NuanceItem $item) {
return array();
}
+ public function getItemCurtainPanels(NuanceItem $item) {
+ return array();
+ }
+
abstract public function getItemTypeDisplayName();
abstract public function getItemDisplayName(NuanceItem $item);
final public function updateItem(NuanceItem $item) {
if (!$this->canUpdateItems()) {
throw new Exception(
pht(
'This item type ("%s", of class "%s") can not update items.',
$this->getItemTypeConstant(),
get_class($this)));
}
$this->updateItemFromSource($item);
}
protected function updateItemFromSource(NuanceItem $item) {
throw new PhutilMethodNotImplementedException();
}
final public function getItemTypeConstant() {
return $this->getPhobjectClassConstant('ITEMTYPE', 64);
}
final public static function getAllItemTypes() {
return id(new PhutilClassMapQuery())
->setAncestorClass(__CLASS__)
->setUniqueMethod('getItemTypeConstant')
->execute();
}
final protected function newItemAction(NuanceItem $item, $key) {
$id = $item->getID();
$action_uri = "/nuance/item/action/{$id}/{$key}/";
return id(new PhabricatorActionView())
->setHref($action_uri);
}
+ final protected function newCurtainPanel(NuanceItem $item) {
+ return id(new PHUICurtainPanelView());
+ }
+
final public function buildActionResponse(NuanceItem $item, $action) {
$response = $this->handleAction($item, $action);
if ($response === null) {
return new Aphront404Response();
}
return $response;
}
protected function handleAction(NuanceItem $item, $action) {
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(
PhabricatorDaemonContentSource::SOURCECONST);
$editor = id(new NuanceItemEditor())
->setActor($viewer)
->setActingAsPHID($command->getAuthorPHID())
->setContentSource($source)
->setContinueOnMissingFields(true)
->setContinueOnNoEffect(true)
->applyTransactions($item, array($xaction));
}
protected function handleCommand(
NuanceItem $item,
NuanceItemCommand $command) {
return null;
}
}

File Metadata

Mime Type
text/x-diff
Expires
Wed, Jul 2, 3:13 AM (1 d, 10 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
164649
Default Alt Text
(44 KB)

Event Timeline