4 * Publish events (like comments on a revision) to external objects which are
5 * linked through Doorkeeper (like a linked JIRA or Asana task).
7 * These workers are invoked by feed infrastructure during normal task queue
8 * operations. They read feed stories and publish information about them to
9 * external systems, generally mirroring comments and updates in Phabricator
10 * into remote systems by making API calls.
12 * @task publish Publishing Stories
13 * @task context Story Context
14 * @task internal Internals
16 abstract class DoorkeeperFeedWorker
extends FeedPushWorker
{
23 /* -( Publishing Stories )------------------------------------------------- */
27 * Actually publish the feed story. Subclasses will generally make API calls
28 * to publish some version of the story into external systems.
33 abstract protected function publishFeedStory();
37 * Enable or disable the worker. Normally, this checks configuration to
38 * see if Phabricator is linked to applicable external systems.
40 * @return bool True if this worker should try to publish stories.
43 abstract public function isEnabled();
46 /* -( Story Context )------------------------------------------------------ */
50 * Get the @{class:PhabricatorFeedStory} that should be published.
52 * @return PhabricatorFeedStory The story to publish.
55 protected function getFeedStory() {
56 if (!$this->feedStory
) {
57 $story = $this->loadFeedStory();
58 $this->feedStory
= $story;
60 return $this->feedStory
;
65 * Get the viewer for the act of publishing.
67 * NOTE: Publishing currently uses the omnipotent viewer because it depends
68 * on loading external accounts. Possibly we should tailor this. See T3732.
69 * Using the actor for most operations might make more sense.
71 * @return PhabricatorUser Viewer.
74 protected function getViewer() {
75 return PhabricatorUser
::getOmnipotentUser();
80 * Get the @{class:DoorkeeperFeedStoryPublisher} which handles this object.
82 * @return DoorkeeperFeedStoryPublisher Object publisher.
85 protected function getPublisher() {
86 return $this->publisher
;
91 * Get the primary object the story is about, like a
92 * @{class:DifferentialRevision} or @{class:ManiphestTask}.
94 * @return object Object which the story is about.
97 protected function getStoryObject() {
98 if (!$this->storyObject
) {
99 $story = $this->getFeedStory();
101 $object = $story->getPrimaryObject();
102 } catch (Exception
$ex) {
103 throw new PhabricatorWorkerPermanentFailureException(
106 $this->storyObject
= $object;
108 return $this->storyObject
;
112 /* -( Internals )---------------------------------------------------------- */
116 * Load the @{class:DoorkeeperFeedStoryPublisher} which corresponds to this
117 * object. Publishers provide a common API for pushing object updates into
120 * @return DoorkeeperFeedStoryPublisher Publisher for the story's object.
123 private function loadPublisher() {
124 $story = $this->getFeedStory();
125 $viewer = $this->getViewer();
126 $object = $this->getStoryObject();
128 $publishers = id(new PhutilClassMapQuery())
129 ->setAncestorClass('DoorkeeperFeedStoryPublisher')
132 foreach ($publishers as $publisher) {
133 if (!$publisher->canPublishStory($story, $object)) {
139 ->setFeedStory($story);
141 $object = $publisher->willPublishStory($object);
142 $this->storyObject
= $object;
144 $this->publisher
= $publisher;
148 return $this->publisher
;
152 /* -( Inherited )---------------------------------------------------------- */
156 * Doorkeeper workers set up some context, then call
157 * @{method:publishFeedStory}.
159 final protected function doWork() {
160 if (PhabricatorEnv
::getEnvConfig('phabricator.silent')) {
161 $this->log("%s\n", pht('This software is running in silent mode.'));
165 if (!$this->isEnabled()) {
168 pht("Doorkeeper worker '%s' is not enabled.", get_class($this)));
172 $publisher = $this->loadPublisher();
174 $this->log("%s\n", pht('Story is about an unsupported object type.'));
177 $this->log("%s\n", pht("Using publisher '%s'.", get_class($publisher)));
180 $this->publishFeedStory();
185 * By default, Doorkeeper workers perform a small number of retries with
186 * exponential backoff. A consideration in this policy is that many of these
187 * workers are laden with side effects.
189 public function getMaximumRetryCount() {
195 * See @{method:getMaximumRetryCount} for a description of Doorkeeper
198 public function getWaitBeforeRetry(PhabricatorWorkerTask
$task) {
199 $count = $task->getFailureCount();
200 return (5 * 60) * pow(8, $count);