mirror of
https://we.phorge.it/source/phorge.git
synced 2025-01-04 11:51:02 +01:00
Improve Asana API error handling in Doorkeeper
Summary: Ref T2852. We need to distinguish between an API call which worked but got back nothing (404) and an API call which failed. In particular, Asana hit a sync issue which was likely the result of treating a 500 (or some other error) as a 404. Also clean up a couple small things. Test Plan: Ran syncs against deleted tasks and saw successful syncs of non-tasks, and simulated random failures and saw them get handled correctly. Reviewers: btrahan Reviewed By: btrahan CC: aran Maniphest Tasks: T2852 Differential Revision: https://secure.phabricator.com/D6470
This commit is contained in:
parent
8e0a975e3f
commit
fca534d6b6
4 changed files with 56 additions and 26 deletions
|
@ -941,8 +941,9 @@ final class DifferentialRevisionQuery
|
||||||
foreach ($revision_edges as $user_phid => $edge) {
|
foreach ($revision_edges as $user_phid => $edge) {
|
||||||
$data = $edge['data'];
|
$data = $edge['data'];
|
||||||
$reviewers[] = new DifferentialReviewer(
|
$reviewers[] = new DifferentialReviewer(
|
||||||
$user_phid, $data['status'], idx($data, 'diff', null)
|
$user_phid,
|
||||||
);
|
idx($data, 'status'),
|
||||||
|
idx($data, 'diff'));
|
||||||
}
|
}
|
||||||
|
|
||||||
$revision->attachReviewerStatus($reviewers);
|
$revision->attachReviewerStatus($reviewers);
|
||||||
|
|
|
@ -64,17 +64,33 @@ final class DoorkeeperBridgeAsana extends DoorkeeperBridge {
|
||||||
}
|
}
|
||||||
|
|
||||||
$results = array();
|
$results = array();
|
||||||
|
$failed = array();
|
||||||
foreach (Futures($futures) as $key => $future) {
|
foreach (Futures($futures) as $key => $future) {
|
||||||
try {
|
try {
|
||||||
$results[$key] = $future->resolve();
|
$results[$key] = $future->resolve();
|
||||||
} catch (Exception $ex) {
|
} catch (Exception $ex) {
|
||||||
// TODO: For now, ignore this stuff.
|
if (($ex instanceof HTTPFutureResponseStatus) &&
|
||||||
|
($ex->getStatusCode() == 404)) {
|
||||||
|
// This indicates that the object has been deleted (or never existed,
|
||||||
|
// or isn't visible to the current user) but it's a successful sync of
|
||||||
|
// an object which isn't visible.
|
||||||
|
} else {
|
||||||
|
// This is something else, so consider it a synchronization failure.
|
||||||
|
phlog($ex);
|
||||||
|
$failed[$key] = $ex;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($refs as $ref) {
|
foreach ($refs as $ref) {
|
||||||
$ref->setAttribute('name', pht('Asana Task %s', $ref->getObjectID()));
|
$ref->setAttribute('name', pht('Asana Task %s', $ref->getObjectID()));
|
||||||
|
|
||||||
|
$failed = idx($failed, $ref->getObjectKey());
|
||||||
|
if ($failed) {
|
||||||
|
$ref->setSyncFailed(true);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
$result = idx($results, $ref->getObjectKey());
|
$result = idx($results, $ref->getObjectKey());
|
||||||
if (!$result) {
|
if (!$result) {
|
||||||
continue;
|
continue;
|
||||||
|
|
|
@ -9,6 +9,7 @@ final class DoorkeeperObjectRef extends Phobject {
|
||||||
private $objectID;
|
private $objectID;
|
||||||
private $attributes = array();
|
private $attributes = array();
|
||||||
private $isVisible;
|
private $isVisible;
|
||||||
|
private $syncFailed;
|
||||||
private $externalObject;
|
private $externalObject;
|
||||||
|
|
||||||
public function newExternalObject() {
|
public function newExternalObject() {
|
||||||
|
@ -43,6 +44,15 @@ final class DoorkeeperObjectRef extends Phobject {
|
||||||
return $this->isVisible;
|
return $this->isVisible;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function setSyncFailed($sync_failed) {
|
||||||
|
$this->syncFailed = $sync_failed;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSyncFailed() {
|
||||||
|
return $this->syncFailed;
|
||||||
|
}
|
||||||
|
|
||||||
public function getAttribute($key, $default = null) {
|
public function getAttribute($key, $default = null) {
|
||||||
return idx($this->attributes, $key, $default);
|
return idx($this->attributes, $key, $default);
|
||||||
}
|
}
|
||||||
|
|
|
@ -185,28 +185,23 @@ final class DoorkeeperFeedWorkerAsana extends FeedPushWorker {
|
||||||
$phids = $this->getRelatedUserPHIDs($object);
|
$phids = $this->getRelatedUserPHIDs($object);
|
||||||
list($owner_phid, $active_phids, $passive_phids, $follow_phids) = $phids;
|
list($owner_phid, $active_phids, $passive_phids, $follow_phids) = $phids;
|
||||||
|
|
||||||
$all_follow_phids = array_merge(
|
|
||||||
$active_phids,
|
|
||||||
$passive_phids,
|
|
||||||
$follow_phids);
|
|
||||||
$all_follow_phids = array_unique(array_filter($all_follow_phids));
|
|
||||||
|
|
||||||
$all_phids = array();
|
$all_phids = array();
|
||||||
$all_phids = array_merge(
|
$all_phids = array_merge(
|
||||||
array($owner_phid),
|
array($owner_phid),
|
||||||
$all_follow_phids);
|
$active_phids,
|
||||||
|
$passive_phids,
|
||||||
|
$follow_phids);
|
||||||
$all_phids = array_unique(array_filter($all_phids));
|
$all_phids = array_unique(array_filter($all_phids));
|
||||||
|
|
||||||
$phid_aid_map = $this->lookupAsanaUserIDs($all_phids);
|
$phid_aid_map = $this->lookupAsanaUserIDs($all_phids);
|
||||||
|
|
||||||
if (!$phid_aid_map) {
|
if (!$phid_aid_map) {
|
||||||
throw new PhabricatorWorkerPermanentFailureException(
|
throw new PhabricatorWorkerPermanentFailureException(
|
||||||
'No related users have linked Asana accounts.');
|
'No related users have linked Asana accounts.');
|
||||||
}
|
}
|
||||||
|
|
||||||
$owner_asana_id = idx($phid_aid_map, $owner_phid);
|
$owner_asana_id = idx($phid_aid_map, $owner_phid);
|
||||||
$all_follow_asana_ids = array_select_keys($phid_aid_map, $all_follow_phids);
|
$all_asana_ids = array_select_keys($phid_aid_map, $all_phids);
|
||||||
$all_follow_asana_ids = array_values($all_follow_asana_ids);
|
$all_asana_ids = array_values($all_asana_ids);
|
||||||
|
|
||||||
// Even if the actor isn't a reviewer, etc., try to use their account so
|
// 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
|
// we can post in the correct voice. If we miss, we'll try all the other
|
||||||
|
@ -243,7 +238,7 @@ final class DoorkeeperFeedWorkerAsana extends FeedPushWorker {
|
||||||
|
|
||||||
$main_data = $this->getAsanaTaskData($object) + array(
|
$main_data = $this->getAsanaTaskData($object) + array(
|
||||||
'assignee' => $owner_asana_id,
|
'assignee' => $owner_asana_id,
|
||||||
'followers' => $all_follow_asana_ids,
|
'followers' => $all_asana_ids,
|
||||||
);
|
);
|
||||||
|
|
||||||
$extra_data = array();
|
$extra_data = array();
|
||||||
|
@ -261,7 +256,10 @@ final class DoorkeeperFeedWorkerAsana extends FeedPushWorker {
|
||||||
'DoorkeeperExternalObject could not be loaded.');
|
'DoorkeeperExternalObject could not be loaded.');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$parent_ref->getIsVisible()) {
|
if ($parent_ref->getSyncFailed()) {
|
||||||
|
throw new Exception(
|
||||||
|
'Synchronization of parent task from Asana failed!');
|
||||||
|
} else if (!$parent_ref->getIsVisible()) {
|
||||||
$this->log("Skipping main task update, object is no longer visible.\n");
|
$this->log("Skipping main task update, object is no longer visible.\n");
|
||||||
$extra_data['gone'] = true;
|
$extra_data['gone'] = true;
|
||||||
} else {
|
} else {
|
||||||
|
@ -341,17 +339,6 @@ final class DoorkeeperFeedWorkerAsana extends FeedPushWorker {
|
||||||
'this likely indicates the Asana task has been deleted.');
|
'this likely indicates the Asana task has been deleted.');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Post the feed story itself to the main Asana task.
|
|
||||||
|
|
||||||
$this->makeAsanaAPICall(
|
|
||||||
$oauth_token,
|
|
||||||
'tasks/'.$parent_ref->getObjectID().'/stories',
|
|
||||||
'POST',
|
|
||||||
array(
|
|
||||||
'text' => $story->renderText(),
|
|
||||||
));
|
|
||||||
|
|
||||||
|
|
||||||
// Now, handle the subtasks.
|
// Now, handle the subtasks.
|
||||||
|
|
||||||
$sub_editor = id(new PhabricatorEdgeEditor())
|
$sub_editor = id(new PhabricatorEdgeEditor())
|
||||||
|
@ -371,6 +358,10 @@ final class DoorkeeperFeedWorkerAsana extends FeedPushWorker {
|
||||||
->execute();
|
->execute();
|
||||||
|
|
||||||
foreach ($refs as $ref) {
|
foreach ($refs as $ref) {
|
||||||
|
if ($ref->getSyncFailed()) {
|
||||||
|
throw new Exception(
|
||||||
|
'Synchronization of child task from Asana failed!');
|
||||||
|
}
|
||||||
if (!$ref->getIsVisible()) {
|
if (!$ref->getIsVisible()) {
|
||||||
$ref->getExternalObject()->delete();
|
$ref->getExternalObject()->delete();
|
||||||
continue;
|
continue;
|
||||||
|
@ -497,6 +488,18 @@ final class DoorkeeperFeedWorkerAsana extends FeedPushWorker {
|
||||||
|
|
||||||
$sub_editor->save();
|
$sub_editor->save();
|
||||||
|
|
||||||
|
|
||||||
|
// Post the feed story itself to the main Asana task. We do this last
|
||||||
|
// because everything else is idempotent, so this is the only effect we
|
||||||
|
// can't safely run more than once.
|
||||||
|
|
||||||
|
$this->makeAsanaAPICall(
|
||||||
|
$oauth_token,
|
||||||
|
'tasks/'.$parent_ref->getObjectID().'/stories',
|
||||||
|
'POST',
|
||||||
|
array(
|
||||||
|
'text' => $story->renderText(),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
private function lookupAsanaUserIDs($all_phids) {
|
private function lookupAsanaUserIDs($all_phids) {
|
||||||
|
|
Loading…
Reference in a new issue