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 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; } }