@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Generalize Asana-publishable feed story objects

Summary: Ref T2852. Pulls the Differential-specific aspects of the Asana sync out of the worker. Next diff will add a publisher for Audit/Diffusion.

Test Plan: Published events, including state changes. Saw them reflected correctly in Asana.

Reviewers: btrahan

Reviewed By: btrahan

CC: aran

Maniphest Tasks: T2852

Differential Revision: https://secure.phabricator.com/D6569

+189 -68
+3
src/__phutil_library_map__.php
··· 347 347 'DifferentialDiffTestCase' => 'applications/differential/storage/__tests__/DifferentialDiffTestCase.php', 348 348 'DifferentialDiffViewController' => 'applications/differential/controller/DifferentialDiffViewController.php', 349 349 'DifferentialDiffViewPolicyFieldSpecification' => 'applications/differential/field/specification/DifferentialDiffViewPolicyFieldSpecification.php', 350 + 'DifferentialDoorkeeperRevisionFeedStoryPublisher' => 'applications/differential/doorkeeper/DifferentialDoorkeeperRevisionFeedStoryPublisher.php', 350 351 'DifferentialException' => 'applications/differential/exception/DifferentialException.php', 351 352 'DifferentialExceptionMail' => 'applications/differential/mail/DifferentialExceptionMail.php', 352 353 'DifferentialExportPatchFieldSpecification' => 'applications/differential/field/specification/DifferentialExportPatchFieldSpecification.php', ··· 541 542 'DoorkeeperDAO' => 'applications/doorkeeper/storage/DoorkeeperDAO.php', 542 543 'DoorkeeperExternalObject' => 'applications/doorkeeper/storage/DoorkeeperExternalObject.php', 543 544 'DoorkeeperExternalObjectQuery' => 'applications/doorkeeper/query/DoorkeeperExternalObjectQuery.php', 545 + 'DoorkeeperFeedStoryPublisher' => 'applications/doorkeeper/engine/DoorkeeperFeedStoryPublisher.php', 544 546 'DoorkeeperFeedWorkerAsana' => 'applications/doorkeeper/worker/DoorkeeperFeedWorkerAsana.php', 545 547 'DoorkeeperImportEngine' => 'applications/doorkeeper/engine/DoorkeeperImportEngine.php', 546 548 'DoorkeeperObjectRef' => 'applications/doorkeeper/engine/DoorkeeperObjectRef.php', ··· 2335 2337 'DifferentialDiffTestCase' => 'ArcanistPhutilTestCase', 2336 2338 'DifferentialDiffViewController' => 'DifferentialController', 2337 2339 'DifferentialDiffViewPolicyFieldSpecification' => 'DifferentialFieldSpecification', 2340 + 'DifferentialDoorkeeperRevisionFeedStoryPublisher' => 'DoorkeeperFeedStoryPublisher', 2338 2341 'DifferentialException' => 'Exception', 2339 2342 'DifferentialExceptionMail' => 'DifferentialMail', 2340 2343 'DifferentialExportPatchFieldSpecification' => 'DifferentialFieldSpecification',
+95
src/applications/differential/doorkeeper/DifferentialDoorkeeperRevisionFeedStoryPublisher.php
··· 1 + <?php 2 + 3 + final class DifferentialDoorkeeperRevisionFeedStoryPublisher 4 + extends DoorkeeperFeedStoryPublisher { 5 + 6 + public function canPublishStory(PhabricatorFeedStory $story, $object) { 7 + return ($object instanceof DifferentialRevision); 8 + } 9 + 10 + public function willPublishStory($object) { 11 + return id(new DifferentialRevisionQuery()) 12 + ->setViewer($this->getViewer()) 13 + ->withIDs(array($object->getID())) 14 + ->needRelationships(true) 15 + ->executeOne(); 16 + } 17 + 18 + public function getOwnerPHID($object) { 19 + return $object->getAuthorPHID(); 20 + } 21 + 22 + public function getActiveUserPHIDs($object) { 23 + $status = $object->getStatus(); 24 + if ($status == ArcanistDifferentialRevisionStatus::NEEDS_REVIEW) { 25 + return $object->getReviewers(); 26 + } else { 27 + return array(); 28 + } 29 + } 30 + 31 + public function getPassiveUserPHIDs($object) { 32 + $status = $object->getStatus(); 33 + if ($status == ArcanistDifferentialRevisionStatus::NEEDS_REVIEW) { 34 + return array(); 35 + } else { 36 + return $object->getReviewers(); 37 + } 38 + } 39 + 40 + public function getCCUserPHIDs($object) { 41 + return $object->getCCPHIDs(); 42 + } 43 + 44 + public function getObjectTitle($object) { 45 + $prefix = $this->getTitlePrefix($object); 46 + 47 + $lines = new PhutilNumber($object->getLineCount()); 48 + $lines = pht('[Request, %d lines]', $lines); 49 + 50 + $id = $object->getID(); 51 + 52 + $title = $object->getTitle(); 53 + 54 + return "{$prefix} {$lines} D{$id}: {$title}"; 55 + } 56 + 57 + public function getObjectURI($object) { 58 + return PhabricatorEnv::getProductionURI('/D'.$object->getID()); 59 + } 60 + 61 + public function getObjectDescription($object) { 62 + return $object->getSummary(); 63 + } 64 + 65 + public function isObjectClosed($object) { 66 + switch ($object->getStatus()) { 67 + case ArcanistDifferentialRevisionStatus::CLOSED: 68 + case ArcanistDifferentialRevisionStatus::ABANDONED: 69 + return true; 70 + default: 71 + return false; 72 + } 73 + } 74 + 75 + public function getResponsibilityTitle($object) { 76 + $prefix = $this->getTitlePrefix($object); 77 + return pht('%s Review Request', $prefix); 78 + } 79 + 80 + public function getStoryText($object) { 81 + $story = $this->getFeedStory(); 82 + if ($story instanceof PhabricatorFeedStoryDifferential) { 83 + $text = $story->renderForAsanaBridge(); 84 + } else { 85 + $text = $story->renderText(); 86 + } 87 + return $text; 88 + } 89 + 90 + private function getTitlePrefix(DifferentialRevision $revision) { 91 + $prefix_key = 'metamta.differential.subject-prefix'; 92 + return PhabricatorEnv::getEnvConfig($prefix_key); 93 + } 94 + 95 + }
+49
src/applications/doorkeeper/engine/DoorkeeperFeedStoryPublisher.php
··· 1 + <?php 2 + 3 + abstract class DoorkeeperFeedStoryPublisher { 4 + 5 + private $feedStory; 6 + private $viewer; 7 + 8 + public function setFeedStory(PhabricatorFeedStory $feed_story) { 9 + $this->feedStory = $feed_story; 10 + return $this; 11 + } 12 + 13 + public function getFeedStory() { 14 + return $this->feedStory; 15 + } 16 + 17 + public function setViewer(PhabricatorUser $viewer) { 18 + $this->viewer = $viewer; 19 + return $this; 20 + } 21 + 22 + public function getViewer() { 23 + return $this->viewer; 24 + } 25 + 26 + abstract public function canPublishStory( 27 + PhabricatorFeedStory $story, 28 + $object); 29 + 30 + /** 31 + * Hook for publishers to mutate the story object, particularly by loading 32 + * and attaching additional data. 33 + */ 34 + public function willPublishStory($object) { 35 + return $object; 36 + } 37 + 38 + abstract public function getOwnerPHID($object); 39 + abstract public function getActiveUserPHIDs($object); 40 + abstract public function getPassiveUserPHIDs($object); 41 + abstract public function getCCUserPHIDs($object); 42 + abstract public function getObjectTitle($object); 43 + abstract public function getObjectURI($object); 44 + abstract public function getObjectDescription($object); 45 + abstract public function isObjectClosed($object); 46 + abstract public function getResponsibilityTitle($object); 47 + abstract public function getStoryText($object); 48 + 49 + }
+42 -68
src/applications/doorkeeper/worker/DoorkeeperFeedWorkerAsana.php
··· 3 3 final class DoorkeeperFeedWorkerAsana extends FeedPushWorker { 4 4 5 5 private $provider; 6 + private $publisher; 6 7 private $workspaceID; 7 8 private $feedStory; 8 9 private $storyObject; ··· 43 44 return PhabricatorUser::getOmnipotentUser(); 44 45 } 45 46 47 + private function getPublisher() { 48 + return $this->publisher; 49 + } 50 + 46 51 private function getStoryObject() { 47 52 if (!$this->storyObject) { 48 53 $story = $this->getFeedStory(); ··· 57 62 return $this->storyObject; 58 63 } 59 64 60 - private function isObjectSupported($object) { 61 - return ($object instanceof DifferentialRevision); 62 - } 63 - 64 - private function getRelatedUserPHIDs($object) { 65 - $revision = $object; 66 - $revision->loadRelationships(); 67 - 68 - $author_phid = $revision->getAuthorPHID(); 69 - $reviewer_phids = $revision->getReviewers(); 70 - $cc_phids = $revision->getCCPHIDs(); 71 - 72 - switch ($revision->getStatus()) { 73 - case ArcanistDifferentialRevisionStatus::NEEDS_REVIEW: 74 - $active_phids = $reviewer_phids; 75 - $passive_phids = array(); 76 - break; 77 - default: 78 - $active_phids = array(); 79 - $passive_phids = $reviewer_phids; 80 - break; 81 - } 82 - 83 - return array( 84 - $author_phid, 85 - $active_phids, 86 - $passive_phids, 87 - $cc_phids); 88 - } 89 - 90 65 private function getAsanaTaskData($object) { 91 - $revision = $object; 92 - $prefix = $this->getTitlePrefix($object); 93 - $title = $revision->getTitle(); 94 - $lines = pht( 95 - '[Request, %d lines]', 96 - new PhutilNumber($object->getLineCount())); 66 + $publisher = $this->getPublisher(); 97 67 98 - $name = $prefix.' '.$lines.' D'.$revision->getID().': '.$title; 99 - $uri = PhabricatorEnv::getProductionURI('/D'.$revision->getID()); 68 + $title = $publisher->getObjectTitle($object); 69 + $uri = $publisher->getObjectURI($object); 70 + $description = $publisher->getObjectDescription($object); 71 + $is_completed = $publisher->isObjectClosed($object); 100 72 101 73 $notes = array( 102 - $revision->getSummary(), 74 + $description, 103 75 $uri, 104 76 $this->getSynchronizationWarning(), 105 77 ); 106 78 107 79 $notes = implode("\n\n", $notes); 108 80 109 - switch ($revision->getStatus()) { 110 - case ArcanistDifferentialRevisionStatus::CLOSED: 111 - case ArcanistDifferentialRevisionStatus::ABANDONED: 112 - $is_completed = true; 113 - break; 114 - default: 115 - $is_completed = false; 116 - break; 117 - } 118 - 119 81 return array( 120 - 'name' => $name, 82 + 'name' => $title, 121 83 'notes' => $notes, 122 84 'completed' => $is_completed, 123 85 ); 124 86 } 125 87 126 88 private function getAsanaSubtaskData($object) { 127 - $revision = $object; 128 - $prefix = $this->getTitlePrefix($object); 89 + $publisher = $this->getPublisher(); 129 90 130 - $name = $prefix.' Review Request'; 131 - $uri = PhabricatorEnv::getProductionURI('/D'.$revision->getID()); 91 + $title = $publisher->getResponsibilityTitle($object); 92 + $uri = $publisher->getObjectURI($object); 93 + $description = $publisher->getObjectDescription($object); 132 94 133 95 $notes = array( 134 - $revision->getSummary(), 96 + $description, 135 97 $uri, 136 98 $this->getSynchronizationWarning(), 137 99 ); ··· 139 101 $notes = implode("\n\n", $notes); 140 102 141 103 return array( 142 - 'name' => $prefix.' Review Request', 104 + 'name' => $title, 143 105 'notes' => $notes, 144 106 ); 145 107 } ··· 165 127 166 128 $chronological_key = $story->getChronologicalKey(); 167 129 168 - if (!$this->isObjectSupported($object)) { 130 + $publishers = id(new PhutilSymbolLoader()) 131 + ->setAncestorClass('DoorkeeperFeedStoryPublisher') 132 + ->loadObjects(); 133 + foreach ($publishers as $publisher) { 134 + if ($publisher->canPublishStory($story, $object)) { 135 + $publisher 136 + ->setViewer($viewer) 137 + ->setFeedStory($story); 138 + 139 + $object = $publisher->willPublishStory($object); 140 + $this->storyObject = $object; 141 + 142 + $this->publisher = $publisher; 143 + $this->log("Using publisher '%s'.\n", get_class($publisher)); 144 + break; 145 + } 146 + } 147 + 148 + if (!$this->publisher) { 169 149 $this->log("Story is about an unsupported object type.\n"); 170 150 return; 171 151 } ··· 182 162 // revision. 183 163 // - Follow: users who are following the object; generally CCs. 184 164 185 - $phids = $this->getRelatedUserPHIDs($object); 186 - list($owner_phid, $active_phids, $passive_phids, $follow_phids) = $phids; 165 + $owner_phid = $publisher->getOwnerPHID($object); 166 + $active_phids = $publisher->getActiveUserPHIDs($object); 167 + $passive_phids = $publisher->getPassiveUserPHIDs($object); 168 + $follow_phids = $publisher->getCCUserPHIDs($object); 187 169 188 170 $all_phids = array(); 189 171 $all_phids = array_merge( ··· 499 481 // because everything else is idempotent, so this is the only effect we 500 482 // can't safely run more than once. 501 483 502 - if ($story instanceof PhabricatorFeedStoryDifferential) { 503 - $text = $story->renderForAsanaBridge(); 504 - } else { 505 - $text = $story->renderText(); 506 - } 484 + $text = $publisher->getStoryText($object); 507 485 508 486 $this->makeAsanaAPICall( 509 487 $oauth_token, ··· 640 618 public function getWaitBeforeRetry(PhabricatorWorkerTask $task) { 641 619 $count = $task->getFailureCount(); 642 620 return (5 * 60) * pow(8, $count); 643 - } 644 - 645 - public function getTitlePrefix($object) { 646 - return PhabricatorEnv::getEnvConfig('metamta.differential.subject-prefix'); 647 621 } 648 622 649 623 }