Page MenuHomePhabricator
Paste P2019

UpdateJIRAStatusDoorkeeperFeedWorker.php
ActivePublic

Authored by avivey on Nov 24 2016, 1:19 AM.
<?php
/*
This does 2 things:
- Adds a label to the JIRA ticket with the repository name
- Puts the JIRA ticket in "In Progress" state, if possible.
*/
final class UpdateJIRAStatusDoorkeeperFeedWorker extends DoorkeeperFeedWorker {
private $provider;
public function isEnabled() {
return (bool)PhabricatorJIRAAuthProvider::getJIRAProvider();
}
protected function publishFeedStory() {
$story = $this->getFeedStory();
$viewer = $this->getViewer();
$provider = $this->getProvider();
$object = $this->getStoryObject();
$publisher = $this->getPublisher();
if (!$object instanceof DifferentialRevision) {
return;
}
$jira_issue_phids = PhabricatorEdgeQuery::loadDestinationPHIDs(
$object->getPHID(),
PhabricatorJiraIssueHasObjectEdgeType::EDGECONST);
if (!$jira_issue_phids) {
$this->log(
"%s\n",
pht('Story is about an object with no linked JIRA issues.'));
return;
}
$xobjs = id(new DoorkeeperExternalObjectQuery())
->setViewer($viewer)
->withPHIDs($jira_issue_phids)
->execute();
if (!$xobjs) {
$this->log(
"%s\n",
pht('Story object has no corresponding external JIRA objects.'));
return;
}
$try_users = $this->findUsersToPossess();
if (!$try_users) {
$this->log(
"%s\n",
pht('No users to act on linked JIRA objects.'));
return;
}
$xobjs = mgroup($xobjs, 'getApplicationDomain');
foreach ($xobjs as $domain => $xobj_list) {
$accounts = id(new PhabricatorExternalAccountQuery())
->setViewer($viewer)
->withUserPHIDs($try_users)
->withAccountTypes(array($provider->getProviderType()))
->withAccountDomains(array($domain))
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->execute();
// Reorder accounts in the original order.
// TODO: This needs to be adjusted if/when we allow you to link multiple
// accounts.
$accounts = mpull($accounts, null, 'getUserPHID');
$accounts = array_select_keys($accounts, $try_users);
foreach ($xobj_list as $xobj) {
foreach ($accounts as $account) {
try {
$jira_key = $xobj->getObjectID();
$this->addRepositoryToTicket($account, $jira_key);
if (!$publisher->isObjectClosed($object)) {
// Closed revisions don't need to mark the ticket "In Progress".
$this->moveTicketToInProgress($account, $jira_key);
}
break;
} catch (HTTPFutureResponseStatus $ex) {
phlog($ex);
$this->log(
"%s\n",
pht(
'Failed to update object %s using user %s.',
$xobj->getObjectID(),
$account->getUserPHID()));
}
}
}
}
}
/**
* This action is idempotent, so just make the extra call.
*/
private function addRepositoryToTicket($account, $jira_key) {
$provider = $this->getProvider();
$revision = $this->getStoryObject();
$publisher = $this->getPublisher();
// Repositories are always loaded for revisions because of visibility policy
$repository = $revision->getRepository();
if (!$repository) {
// but it may be null.
return;
}
$label = 'repo_'.$repository->getCloneName();
$provider->newJIRAFuture(
$account,
'rest/api/2/issue/'.$jira_key,
'PUT',
array(
'update' => array(
'labels' => array(
array('add' => $label),
),
),
))->resolve();
}
/**
* This action is slow because we're making 2 calls - one to learn how to do
* what we want, and one to do it. JIRA doesn't have a concept of "set status
* to something", it has "transitions".
*
* There's also something about permissions, so some accounts may be able to
* see and do a transition and some won't; I'm not going to handle that.
*/
private function moveTicketToInProgress($account, $jira_key) {
$provider = $this->getProvider();
$object = $this->getStoryObject();
$publisher = $this->getPublisher();
$transitions = $provider->newJIRAFuture(
$account,
'rest/api/2/issue/'.$jira_key.'/transitions',
'GET')->resolveJSON();
$transitions = idx($transitions, 'transitions');
$transition_id = null;
foreach ($transitions as $transition) {
$destination = idxv($transition, array('to', 'name'));
if ($destination == 'In Progress') {
$transition_id = idx($transition, 'id');
break;
}
}
if ($transition_id == null) {
$this->log('Found no transition to "In Progress"');
return;
}
$provider->newJIRAFuture(
$account,
'rest/api/2/issue/'.$jira_key.'/transitions',
'POST',
array(
'transition' => $transition_id,
))->resolve();
}
/* -( Internals )---------------------------------------------------------- */
/**
* Get the active JIRA provider.
*
* @return PhabricatorJIRAAuthProvider Active JIRA auth provider.
* @task internal
*/
private function getProvider() {
if (!$this->provider) {
$provider = PhabricatorJIRAAuthProvider::getJIRAProvider();
if (!$provider) {
throw new PhabricatorWorkerPermanentFailureException(
pht('No JIRA provider configured.'));
}
$this->provider = $provider;
}
return $this->provider;
}
/**
* Get a list of users to act as when publishing into JIRA.
*
* @return list<phid> Candidate user PHIDs to act as when publishing this
* story.
* @task internal
*/
private function findUsersToPossess() {
$object = $this->getStoryObject();
$publisher = $this->getPublisher();
$data = $this->getFeedStory()->getStoryData();
// Figure out all the users related to the object. Users go into one of
// four buckets. For JIRA integration, we don't care about which bucket
// a user is in, since we just want to publish an update to linked objects.
$owner_phid = $publisher->getOwnerPHID($object);
$active_phids = $publisher->getActiveUserPHIDs($object);
$passive_phids = $publisher->getPassiveUserPHIDs($object);
$follow_phids = $publisher->getCCUserPHIDs($object);
$all_phids = array_merge(
array($owner_phid),
$active_phids,
$passive_phids,
$follow_phids);
$all_phids = array_unique(array_filter($all_phids));
// Even if the actor isn't a reviewer, etc., try to use their account so
// we can post in the correct voice. If we miss, we'll try all the other
// related users.
$try_users = array_merge(
array($data->getAuthorPHID()),
$all_phids);
$try_users = array_filter($try_users);
return $try_users;
}
}