4 * Publishes feed stories into JIRA, using the "JIRA Issues" field to identify
7 final class DoorkeeperJIRAFeedWorker
extends DoorkeeperFeedWorker
{
12 /* -( Publishing Stories )------------------------------------------------- */
16 * This worker is enabled when a JIRA authentication provider is active.
18 public function isEnabled() {
19 return (bool)PhabricatorJIRAAuthProvider
::getJIRAProvider();
24 * Publishes stories into JIRA using the JIRA API.
26 protected function publishFeedStory() {
27 $story = $this->getFeedStory();
28 $viewer = $this->getViewer();
29 $provider = $this->getProvider();
30 $object = $this->getStoryObject();
31 $publisher = $this->getPublisher();
33 $jira_issue_phids = PhabricatorEdgeQuery
::loadDestinationPHIDs(
35 PhabricatorJiraIssueHasObjectEdgeType
::EDGECONST
);
36 if (!$jira_issue_phids) {
39 pht('Story is about an object with no linked JIRA issues.'));
43 $do_anything = ($this->shouldPostComment() ||
$this->shouldPostLink());
47 pht('JIRA integration is configured not to post anything.'));
51 $xobjs = id(new DoorkeeperExternalObjectQuery())
53 ->withPHIDs($jira_issue_phids)
59 pht('Story object has no corresponding external JIRA objects.'));
63 $try_users = $this->findUsersToPossess();
67 pht('No users to act on linked JIRA objects.'));
72 $xobjs = mgroup($xobjs, 'getApplicationDomain');
73 foreach ($xobjs as $domain => $xobj_list) {
74 $accounts = id(new PhabricatorExternalAccountQuery())
76 ->withUserPHIDs($try_users)
77 ->withProviderConfigPHIDs(
79 $provider->getProviderConfigPHID(),
81 ->requireCapabilities(
83 PhabricatorPolicyCapability
::CAN_VIEW
,
84 PhabricatorPolicyCapability
::CAN_EDIT
,
88 // Reorder accounts in the original order.
89 // TODO: This needs to be adjusted if/when we allow you to link multiple
91 $accounts = mpull($accounts, null, 'getUserPHID');
92 $accounts = array_select_keys($accounts, $try_users);
94 foreach ($xobj_list as $xobj) {
95 foreach ($accounts as $account) {
97 $jira_key = $xobj->getObjectID();
99 if ($this->shouldPostComment()) {
100 $this->postComment($account, $jira_key);
103 if ($this->shouldPostLink()) {
104 $this->postLink($account, $jira_key);
108 } catch (HTTPFutureResponseStatus
$ex) {
113 'Failed to update object %s using user %s.',
114 $xobj->getObjectID(),
115 $account->getUserPHID()));
123 /* -( Internals )---------------------------------------------------------- */
127 * Get the active JIRA provider.
129 * @return PhabricatorJIRAAuthProvider Active JIRA auth provider.
132 private function getProvider() {
133 if (!$this->provider
) {
134 $provider = PhabricatorJIRAAuthProvider
::getJIRAProvider();
136 throw new PhabricatorWorkerPermanentFailureException(
137 pht('No JIRA provider configured.'));
139 $this->provider
= $provider;
141 return $this->provider
;
146 * Get a list of users to act as when publishing into JIRA.
148 * @return list<phid> Candidate user PHIDs to act as when publishing this
152 private function findUsersToPossess() {
153 $object = $this->getStoryObject();
154 $publisher = $this->getPublisher();
155 $data = $this->getFeedStory()->getStoryData();
157 // Figure out all the users related to the object. Users go into one of
158 // four buckets. For JIRA integration, we don't care about which bucket
159 // a user is in, since we just want to publish an update to linked objects.
161 $owner_phid = $publisher->getOwnerPHID($object);
162 $active_phids = $publisher->getActiveUserPHIDs($object);
163 $passive_phids = $publisher->getPassiveUserPHIDs($object);
164 $follow_phids = $publisher->getCCUserPHIDs($object);
166 $all_phids = array_merge(
171 $all_phids = array_unique(array_filter($all_phids));
173 // Even if the actor isn't a reviewer, etc., try to use their account so
174 // we can post in the correct voice. If we miss, we'll try all the other
177 $try_users = array_merge(
178 array($data->getAuthorPHID()),
180 $try_users = array_filter($try_users);
185 private function shouldPostComment() {
186 return $this->getProvider()->shouldCreateJIRAComment();
189 private function shouldPostLink() {
190 return $this->getProvider()->shouldCreateJIRALink();
193 private function postComment($account, $jira_key) {
194 $provider = $this->getProvider();
196 $provider->newJIRAFuture(
198 'rest/api/2/issue/'.$jira_key.'/comment',
201 'body' => $this->renderStoryText(),
205 private function renderStoryText() {
206 $object = $this->getStoryObject();
207 $publisher = $this->getPublisher();
209 $text = $publisher->getStoryText($object);
211 if ($this->shouldPostLink()) {
214 // include the link in the comment
215 return $text."\n\n".$publisher->getObjectURI($object);
219 private function postLink($account, $jira_key) {
220 $provider = $this->getProvider();
221 $object = $this->getStoryObject();
222 $publisher = $this->getPublisher();
223 $icon_uri = celerity_get_resource_uri('rsrc/favicons/favicon-16x16.png');
225 $provider->newJIRAFuture(
227 'rest/api/2/issue/'.$jira_key.'/remotelink',
230 // format documented at http://bit.ly/1K5T0Li
232 'globalId' => $object->getPHID(),
233 'application' => array(
234 'type' => 'com.phacility.phabricator',
235 'name' => 'Phabricator',
237 'relationship' => 'implemented in',
239 'url' => $publisher->getObjectURI($object),
240 'title' => $publisher->getObjectTitle($object),
242 'url16x16' => $icon_uri,
243 'title' => 'Phabricator',
246 'resolved' => $publisher->isObjectClosed($object),