diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index f143e452c4..f8015d3f0f 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -347,6 +347,7 @@ phutil_register_library_map(array( 'DifferentialDiffTestCase' => 'applications/differential/storage/__tests__/DifferentialDiffTestCase.php', 'DifferentialDiffViewController' => 'applications/differential/controller/DifferentialDiffViewController.php', 'DifferentialDiffViewPolicyFieldSpecification' => 'applications/differential/field/specification/DifferentialDiffViewPolicyFieldSpecification.php', + 'DifferentialDoorkeeperRevisionFeedStoryPublisher' => 'applications/differential/doorkeeper/DifferentialDoorkeeperRevisionFeedStoryPublisher.php', 'DifferentialException' => 'applications/differential/exception/DifferentialException.php', 'DifferentialExceptionMail' => 'applications/differential/mail/DifferentialExceptionMail.php', 'DifferentialExportPatchFieldSpecification' => 'applications/differential/field/specification/DifferentialExportPatchFieldSpecification.php', @@ -541,6 +542,7 @@ phutil_register_library_map(array( 'DoorkeeperDAO' => 'applications/doorkeeper/storage/DoorkeeperDAO.php', 'DoorkeeperExternalObject' => 'applications/doorkeeper/storage/DoorkeeperExternalObject.php', 'DoorkeeperExternalObjectQuery' => 'applications/doorkeeper/query/DoorkeeperExternalObjectQuery.php', + 'DoorkeeperFeedStoryPublisher' => 'applications/doorkeeper/engine/DoorkeeperFeedStoryPublisher.php', 'DoorkeeperFeedWorkerAsana' => 'applications/doorkeeper/worker/DoorkeeperFeedWorkerAsana.php', 'DoorkeeperImportEngine' => 'applications/doorkeeper/engine/DoorkeeperImportEngine.php', 'DoorkeeperObjectRef' => 'applications/doorkeeper/engine/DoorkeeperObjectRef.php', @@ -2335,6 +2337,7 @@ phutil_register_library_map(array( 'DifferentialDiffTestCase' => 'ArcanistPhutilTestCase', 'DifferentialDiffViewController' => 'DifferentialController', 'DifferentialDiffViewPolicyFieldSpecification' => 'DifferentialFieldSpecification', + 'DifferentialDoorkeeperRevisionFeedStoryPublisher' => 'DoorkeeperFeedStoryPublisher', 'DifferentialException' => 'Exception', 'DifferentialExceptionMail' => 'DifferentialMail', 'DifferentialExportPatchFieldSpecification' => 'DifferentialFieldSpecification', diff --git a/src/applications/differential/doorkeeper/DifferentialDoorkeeperRevisionFeedStoryPublisher.php b/src/applications/differential/doorkeeper/DifferentialDoorkeeperRevisionFeedStoryPublisher.php new file mode 100644 index 0000000000..25a3b397d3 --- /dev/null +++ b/src/applications/differential/doorkeeper/DifferentialDoorkeeperRevisionFeedStoryPublisher.php @@ -0,0 +1,95 @@ +setViewer($this->getViewer()) + ->withIDs(array($object->getID())) + ->needRelationships(true) + ->executeOne(); + } + + public function getOwnerPHID($object) { + return $object->getAuthorPHID(); + } + + public function getActiveUserPHIDs($object) { + $status = $object->getStatus(); + if ($status == ArcanistDifferentialRevisionStatus::NEEDS_REVIEW) { + return $object->getReviewers(); + } else { + return array(); + } + } + + public function getPassiveUserPHIDs($object) { + $status = $object->getStatus(); + if ($status == ArcanistDifferentialRevisionStatus::NEEDS_REVIEW) { + return array(); + } else { + return $object->getReviewers(); + } + } + + public function getCCUserPHIDs($object) { + return $object->getCCPHIDs(); + } + + public function getObjectTitle($object) { + $prefix = $this->getTitlePrefix($object); + + $lines = new PhutilNumber($object->getLineCount()); + $lines = pht('[Request, %d lines]', $lines); + + $id = $object->getID(); + + $title = $object->getTitle(); + + return "{$prefix} {$lines} D{$id}: {$title}"; + } + + public function getObjectURI($object) { + return PhabricatorEnv::getProductionURI('/D'.$object->getID()); + } + + public function getObjectDescription($object) { + return $object->getSummary(); + } + + public function isObjectClosed($object) { + switch ($object->getStatus()) { + case ArcanistDifferentialRevisionStatus::CLOSED: + case ArcanistDifferentialRevisionStatus::ABANDONED: + return true; + default: + return false; + } + } + + public function getResponsibilityTitle($object) { + $prefix = $this->getTitlePrefix($object); + return pht('%s Review Request', $prefix); + } + + public function getStoryText($object) { + $story = $this->getFeedStory(); + if ($story instanceof PhabricatorFeedStoryDifferential) { + $text = $story->renderForAsanaBridge(); + } else { + $text = $story->renderText(); + } + return $text; + } + + private function getTitlePrefix(DifferentialRevision $revision) { + $prefix_key = 'metamta.differential.subject-prefix'; + return PhabricatorEnv::getEnvConfig($prefix_key); + } + +} diff --git a/src/applications/doorkeeper/engine/DoorkeeperFeedStoryPublisher.php b/src/applications/doorkeeper/engine/DoorkeeperFeedStoryPublisher.php new file mode 100644 index 0000000000..460c82fa8b --- /dev/null +++ b/src/applications/doorkeeper/engine/DoorkeeperFeedStoryPublisher.php @@ -0,0 +1,49 @@ +feedStory = $feed_story; + return $this; + } + + public function getFeedStory() { + return $this->feedStory; + } + + public function setViewer(PhabricatorUser $viewer) { + $this->viewer = $viewer; + return $this; + } + + public function getViewer() { + return $this->viewer; + } + + abstract public function canPublishStory( + PhabricatorFeedStory $story, + $object); + + /** + * Hook for publishers to mutate the story object, particularly by loading + * and attaching additional data. + */ + public function willPublishStory($object) { + return $object; + } + + abstract public function getOwnerPHID($object); + abstract public function getActiveUserPHIDs($object); + abstract public function getPassiveUserPHIDs($object); + abstract public function getCCUserPHIDs($object); + abstract public function getObjectTitle($object); + abstract public function getObjectURI($object); + abstract public function getObjectDescription($object); + abstract public function isObjectClosed($object); + abstract public function getResponsibilityTitle($object); + abstract public function getStoryText($object); + +} diff --git a/src/applications/doorkeeper/worker/DoorkeeperFeedWorkerAsana.php b/src/applications/doorkeeper/worker/DoorkeeperFeedWorkerAsana.php index afc0b8e3a4..d4f7486ae6 100644 --- a/src/applications/doorkeeper/worker/DoorkeeperFeedWorkerAsana.php +++ b/src/applications/doorkeeper/worker/DoorkeeperFeedWorkerAsana.php @@ -3,6 +3,7 @@ final class DoorkeeperFeedWorkerAsana extends FeedPushWorker { private $provider; + private $publisher; private $workspaceID; private $feedStory; private $storyObject; @@ -43,6 +44,10 @@ final class DoorkeeperFeedWorkerAsana extends FeedPushWorker { return PhabricatorUser::getOmnipotentUser(); } + private function getPublisher() { + return $this->publisher; + } + private function getStoryObject() { if (!$this->storyObject) { $story = $this->getFeedStory(); @@ -57,81 +62,38 @@ final class DoorkeeperFeedWorkerAsana extends FeedPushWorker { return $this->storyObject; } - private function isObjectSupported($object) { - return ($object instanceof DifferentialRevision); - } - - private function getRelatedUserPHIDs($object) { - $revision = $object; - $revision->loadRelationships(); - - $author_phid = $revision->getAuthorPHID(); - $reviewer_phids = $revision->getReviewers(); - $cc_phids = $revision->getCCPHIDs(); - - switch ($revision->getStatus()) { - case ArcanistDifferentialRevisionStatus::NEEDS_REVIEW: - $active_phids = $reviewer_phids; - $passive_phids = array(); - break; - default: - $active_phids = array(); - $passive_phids = $reviewer_phids; - break; - } - - return array( - $author_phid, - $active_phids, - $passive_phids, - $cc_phids); - } - private function getAsanaTaskData($object) { - $revision = $object; - $prefix = $this->getTitlePrefix($object); - $title = $revision->getTitle(); - $lines = pht( - '[Request, %d lines]', - new PhutilNumber($object->getLineCount())); + $publisher = $this->getPublisher(); - $name = $prefix.' '.$lines.' D'.$revision->getID().': '.$title; - $uri = PhabricatorEnv::getProductionURI('/D'.$revision->getID()); + $title = $publisher->getObjectTitle($object); + $uri = $publisher->getObjectURI($object); + $description = $publisher->getObjectDescription($object); + $is_completed = $publisher->isObjectClosed($object); $notes = array( - $revision->getSummary(), + $description, $uri, $this->getSynchronizationWarning(), ); $notes = implode("\n\n", $notes); - switch ($revision->getStatus()) { - case ArcanistDifferentialRevisionStatus::CLOSED: - case ArcanistDifferentialRevisionStatus::ABANDONED: - $is_completed = true; - break; - default: - $is_completed = false; - break; - } - return array( - 'name' => $name, + 'name' => $title, 'notes' => $notes, 'completed' => $is_completed, ); } private function getAsanaSubtaskData($object) { - $revision = $object; - $prefix = $this->getTitlePrefix($object); + $publisher = $this->getPublisher(); - $name = $prefix.' Review Request'; - $uri = PhabricatorEnv::getProductionURI('/D'.$revision->getID()); + $title = $publisher->getResponsibilityTitle($object); + $uri = $publisher->getObjectURI($object); + $description = $publisher->getObjectDescription($object); $notes = array( - $revision->getSummary(), + $description, $uri, $this->getSynchronizationWarning(), ); @@ -139,7 +101,7 @@ final class DoorkeeperFeedWorkerAsana extends FeedPushWorker { $notes = implode("\n\n", $notes); return array( - 'name' => $prefix.' Review Request', + 'name' => $title, 'notes' => $notes, ); } @@ -165,7 +127,25 @@ final class DoorkeeperFeedWorkerAsana extends FeedPushWorker { $chronological_key = $story->getChronologicalKey(); - if (!$this->isObjectSupported($object)) { + $publishers = id(new PhutilSymbolLoader()) + ->setAncestorClass('DoorkeeperFeedStoryPublisher') + ->loadObjects(); + foreach ($publishers as $publisher) { + if ($publisher->canPublishStory($story, $object)) { + $publisher + ->setViewer($viewer) + ->setFeedStory($story); + + $object = $publisher->willPublishStory($object); + $this->storyObject = $object; + + $this->publisher = $publisher; + $this->log("Using publisher '%s'.\n", get_class($publisher)); + break; + } + } + + if (!$this->publisher) { $this->log("Story is about an unsupported object type.\n"); return; } @@ -182,8 +162,10 @@ final class DoorkeeperFeedWorkerAsana extends FeedPushWorker { // revision. // - Follow: users who are following the object; generally CCs. - $phids = $this->getRelatedUserPHIDs($object); - list($owner_phid, $active_phids, $passive_phids, $follow_phids) = $phids; + $owner_phid = $publisher->getOwnerPHID($object); + $active_phids = $publisher->getActiveUserPHIDs($object); + $passive_phids = $publisher->getPassiveUserPHIDs($object); + $follow_phids = $publisher->getCCUserPHIDs($object); $all_phids = array(); $all_phids = array_merge( @@ -499,11 +481,7 @@ final class DoorkeeperFeedWorkerAsana extends FeedPushWorker { // because everything else is idempotent, so this is the only effect we // can't safely run more than once. - if ($story instanceof PhabricatorFeedStoryDifferential) { - $text = $story->renderForAsanaBridge(); - } else { - $text = $story->renderText(); - } + $text = $publisher->getStoryText($object); $this->makeAsanaAPICall( $oauth_token, @@ -642,8 +620,4 @@ final class DoorkeeperFeedWorkerAsana extends FeedPushWorker { return (5 * 60) * pow(8, $count); } - public function getTitlePrefix($object) { - return PhabricatorEnv::getEnvConfig('metamta.differential.subject-prefix'); - } - }