diff --git a/src/__celerity_resource_map__.php b/src/__celerity_resource_map__.php index 0afbce8d55..b78080b78d 100644 --- a/src/__celerity_resource_map__.php +++ b/src/__celerity_resource_map__.php @@ -51,8 +51,8 @@ celerity_register_resource_map(array( ), '/rsrc/image/autosprite.png' => array( - 'hash' => 'e1735b5cadbaf1f70b70a857eab53601', - 'uri' => '/res/e1735b5c/rsrc/image/autosprite.png', + 'hash' => 'bc9479b2a610a3ecee18dc88744c4ce6', + 'uri' => '/res/bc9479b2/rsrc/image/autosprite.png', 'disk' => '/rsrc/image/autosprite.png', 'type' => 'png', ), @@ -713,7 +713,7 @@ celerity_register_resource_map(array( ), 'autosprite-css' => array( - 'uri' => '/res/10fb7fdc/rsrc/css/autosprite.css', + 'uri' => '/res/6be3e4b3/rsrc/css/autosprite.css', 'type' => 'css', 'requires' => array( diff --git a/src/applications/daemon/controller/PhabricatorWorkerTaskDetailController.php b/src/applications/daemon/controller/PhabricatorWorkerTaskDetailController.php index 30cb0c5556..a514e13b52 100644 --- a/src/applications/daemon/controller/PhabricatorWorkerTaskDetailController.php +++ b/src/applications/daemon/controller/PhabricatorWorkerTaskDetailController.php @@ -31,129 +31,163 @@ final class PhabricatorWorkerTaskDetailController $task = id(new PhabricatorWorkerActiveTask())->load($this->id); if (!$task) { + $task = id(new PhabricatorWorkerArchiveTask())->load($this->id); + } + + if (!$task) { + $title = pht('Task Does Not Exist'); + $error_view = new AphrontErrorView(); $error_view->setTitle('No Such Task'); $error_view->appendChild( - '

This task may have recently completed.

'); - $error_view->setSeverity(AphrontErrorView::SEVERITY_WARNING); - return $this->buildStandardPageResponse( - $error_view, - array( - 'title' => 'Task Does Not Exist', - )); + '

This task may have recently been garbage collected.

'); + $error_view->setSeverity(AphrontErrorView::SEVERITY_NODATA); + + $content = $error_view; + } else { + $title = 'Task '.$task->getID(); + + $header = id(new PhabricatorHeaderView()) + ->setHeader('Task '.$task->getID().' ('.$task->getTaskClass().')'); + + $actions = $this->buildActionListView($task); + $properties = $this->buildPropertyListView($task); + + $content = array( + $header, + $actions, + $properties, + ); } - $data = id(new PhabricatorWorkerTaskData())->loadOneWhere( - 'id = %d', - $task->getDataID()); - - $extra = null; - switch ($task->getTaskClass()) { - case 'PhabricatorRepositorySvnCommitChangeParserWorker': - case 'PhabricatorRepositoryGitCommitChangeParserWorker': - $commit_id = idx($data->getData(), 'commitID'); - if ($commit_id) { - $commit = id(new PhabricatorRepositoryCommit())->load($commit_id); - if ($commit) { - $repository = id(new PhabricatorRepository())->load( - $commit->getRepositoryID()); - if ($repository) { - $extra = - "NOTE: ". - "You can manually retry this task by running this script:". - "
".
-                  "phabricator/\$ ./scripts/repository/reparse.php ".
-                  "r".
-                  phutil_escape_html($repository->getCallsign()).
-                  phutil_escape_html($commit->getCommitIdentifier()).
-                  " ".
-                  "--change".
-                "
"; - } - } - } - break; - default: - break; - } - - if ($data) { - $data = json_encode($data->getData()); - } - - $form = id(new AphrontFormView()) - ->setUser($user) - ->appendChild( - id(new AphrontFormStaticControl()) - ->setLabel('ID') - ->setValue($task->getID())) - ->appendChild( - id(new AphrontFormStaticControl()) - ->setLabel('Type') - ->setValue($task->getTaskClass())) - ->appendChild( - id(new AphrontFormStaticControl()) - ->setLabel('Lease Owner') - ->setValue($task->getLeaseOwner())) - ->appendChild( - id(new AphrontFormStaticControl()) - ->setLabel('Lease Expires') - ->setValue($task->getLeaseExpires() - time())) - ->appendChild( - id(new AphrontFormStaticControl()) - ->setLabel('Failure Count') - ->setValue($task->getFailureCount())) - ->appendChild( - id(new AphrontFormTextAreaControl()) - ->setLabel('Data') - ->setValue($data)); - - if ($extra) { - $form->appendChild( - id(new AphrontFormMarkupControl()) - ->setLabel('More') - ->setValue($extra)); - } - - $form - ->appendChild( - id(new AphrontFormSubmitControl()) - ->addCancelButton('/daemon/', 'Back')); - - $panel = new AphrontPanelView(); - $panel->setHeader('Task Detail'); - $panel->setWidth(AphrontPanelView::WIDTH_WIDE); - $panel->appendChild($form); - - $panel->addButton( - javelin_render_tag( - 'a', - array( - 'href' => '/daemon/task/'.$task->getID().'/delete/', - 'class' => 'button grey', - 'sigil' => 'workflow', - ), - 'Delete Task')); - - $panel->addButton( - javelin_render_tag( - 'a', - array( - 'href' => '/daemon/task/'.$task->getID().'/release/', - 'class' => 'button grey', - 'sigil' => 'workflow', - ), - 'Free Lease')); - $nav = $this->buildSideNavView(); $nav->selectFilter(''); - $nav->appendChild($panel); + $nav->appendChild($content); return $this->buildApplicationPage( $nav, array( - 'title' => 'Task', + 'title' => $title, )); } + private function buildActionListView(PhabricatorWorkerTask $task) { + $user = $this->getRequest()->getUser(); + + $view = new PhabricatorActionListView(); + $view->setUser($user); + + $id = $task->getID(); + + if ($task->isArchived()) { + $result_success = PhabricatorWorkerArchiveTask::RESULT_SUCCESS; + $can_retry = ($task->getResult() != $result_success); + + $view->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Retry Task')) + ->setHref($this->getApplicationURI('/task/'.$id.'/retry/')) + ->setIcon('undo') + ->setWorkflow(true) + ->setDisabled(!$can_retry)); + } else { + $view->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Cancel Task')) + ->setHref($this->getApplicationURI('/task/'.$id.'/cancel/')) + ->setIcon('delete') + ->setWorkflow(true)); + } + + $can_release = (!$task->isArchived()) && + ($task->getLeaseOwner()); + + $view->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Free Lease')) + ->setHref($this->getApplicationURI('/task/'.$id.'/release/')) + ->setIcon('unlock') + ->setWorkflow(true) + ->setDisabled(!$can_release)); + + return $view; + } + + private function buildPropertyListView(PhabricatorWorkerTask $task) { + $view = new PhabricatorPropertyListView(); + + if ($task->isArchived()) { + switch ($task->getResult()) { + case PhabricatorWorkerArchiveTask::RESULT_SUCCESS: + $status = pht('Complete'); + break; + case PhabricatorWorkerArchiveTask::RESULT_FAILURE: + $status = pht('Failed'); + break; + case PhabricatorWorkerArchiveTask::RESULT_CANCELLED: + $status = pht('Cancelled'); + break; + default: + throw new Exception("Unknown task status!"); + } + } else { + $status = pht('Queued'); + } + + $view->addProperty( + pht('Task Status'), + $status); + + $view->addProperty( + pht('Task Class'), + phutil_escape_html($task->getTaskClass())); + + if ($task->getLeaseExpires()) { + if ($task->getLeaseExpires() > time()) { + $lease_status = pht('Leased'); + } else { + $lease_status = pht('Lease Expired'); + } + } else { + $lease_status = ''.pht('Not Leased').''; + } + + $view->addProperty( + pht('Lease Status'), + $lease_status); + + $view->addProperty( + pht('Lease Owner'), + $task->getLeaseOwner() + ? phutil_escape_html($task->getLeaseOwner()) + : ''.pht('None').''); + + if ($task->getLeaseExpires() && $task->getLeaseOwner()) { + $expires = ($task->getLeaseExpires() - time()); + $expires = phabricator_format_relative_time_detailed($expires); + } else { + $expires = ''.pht('None').''; + } + + $view->addProperty( + pht('Lease Expires'), + $expires); + + $view->addProperty( + pht('Failure Count'), + phutil_escape_html($task->getFailureCount())); + + if ($task->isArchived()) { + $duration = phutil_escape_html(number_format($task->getDuration()).' us'); + } else { + $duration = ''.pht('Not Completed').''; + } + + $view->addProperty( + pht('Duration'), + $duration); + + return $view; + } + } diff --git a/src/applications/daemon/controller/PhabricatorWorkerTaskUpdateController.php b/src/applications/daemon/controller/PhabricatorWorkerTaskUpdateController.php index f5d5d445ca..0356473760 100644 --- a/src/applications/daemon/controller/PhabricatorWorkerTaskUpdateController.php +++ b/src/applications/daemon/controller/PhabricatorWorkerTaskUpdateController.php @@ -32,48 +32,104 @@ final class PhabricatorWorkerTaskUpdateController $user = $request->getUser(); $task = id(new PhabricatorWorkerActiveTask())->load($this->id); + if (!$task) { + $task = id(new PhabricatorWorkerArchiveTask())->load($this->id); + } + if (!$task) { return new Aphront404Response(); } + $result_success = PhabricatorWorkerArchiveTask::RESULT_SUCCESS; + $can_retry = ($task->isArchived()) && + ($task->getResult() != $result_success); + + $can_cancel = !$task->isArchived(); + $can_release = (!$task->isArchived()) && + ($task->getLeaseOwner()); + + $next_uri = $this->getApplicationURI('/task/'.$task->getID().'/'); + if ($request->isFormPost()) { switch ($this->action) { - case 'delete': - $task->delete(); + case 'retry': + if ($can_retry) { + $task->unarchiveTask(); + } + break; + case 'cancel': + if ($can_cancel) { + // Forcibly break the lease if one exists, so we can archive the + // task. + $task->setLeaseOwner(null); + $task->setLeaseExpires(time()); + + $task->archiveTask( + PhabricatorWorkerArchiveTask::RESULT_CANCELLED, + 0); + } break; case 'release': - $task->setLeaseOwner(null); - $task->setLeaseExpires(time()); - $task->save(); + if ($can_release) { + $task->setLeaseOwner(null); + $task->setLeaseExpires(time()); + $task->save(); + } break; } - return id(new AphrontRedirectResponse())->setURI('/daemon/'); + return id(new AphrontRedirectResponse()) + ->setURI($next_uri); } $dialog = new AphrontDialogView(); $dialog->setUser($user); switch ($this->action) { - case 'delete': - $dialog->setTitle('Really delete task?'); - $dialog->appendChild( - '

The work this task represents will never be performed if you '. - 'delete it. Are you sure you want to delete it?

'); - $dialog->addSubmitButton('Delete Task'); + case 'retry': + if ($can_retry) { + $dialog->setTitle('Really retry task?'); + $dialog->appendChild( + '

The task will be put back in the queue and executed '. + 'again.

'); + $dialog->addSubmitButton('Retry Task'); + } else { + $dialog->setTitle('Can Not Retry'); + $dialog->appendChild( + '

Only archived, unsuccessful tasks can be retried.

'); + } + break; + case 'cancel': + if ($can_cancel) { + $dialog->setTitle('Really cancel task?'); + $dialog->appendChild( + '

The work this task represents will never be performed if you '. + 'cancel it. Are you sure you want to cancel it?

'); + $dialog->addSubmitButton('Cancel Task'); + } else { + $dialog->setTitle('Can Not Cancel'); + $dialog->appendChild( + '

Only active tasks can be cancelled.

'); + } break; case 'release': - $dialog->setTitle('Really free task lease?'); - $dialog->appendChild( - '

If the process which owns the task lease is still doing work '. - 'on it, the work may be performed twice. Are you sure you '. - 'want to free the lease?

'); - $dialog->addSubmitButton('Free Lease'); + if ($can_release) { + $dialog->setTitle('Really free task lease?'); + $dialog->appendChild( + '

If the process which owns the task lease is still doing work '. + 'on it, the work may be performed twice. Are you sure you '. + 'want to free the lease?

'); + $dialog->addSubmitButton('Free Lease'); + } else { + $dialog->setTitle('Can Not Free Lease'); + $dialog->appendChild( + '

Only active, leased tasks may have their leases freed.

'); + } break; default: return new Aphront404Response(); } - $dialog->addCancelButton('/daemon/'); + $dialog->addCancelButton($next_uri); return id(new AphrontDialogResponse())->setDialog($dialog); } diff --git a/src/infrastructure/daemon/workers/storage/PhabricatorWorkerArchiveTask.php b/src/infrastructure/daemon/workers/storage/PhabricatorWorkerArchiveTask.php index e22370e314..2ff3c98228 100644 --- a/src/infrastructure/daemon/workers/storage/PhabricatorWorkerArchiveTask.php +++ b/src/infrastructure/daemon/workers/storage/PhabricatorWorkerArchiveTask.php @@ -18,8 +18,9 @@ final class PhabricatorWorkerArchiveTask extends PhabricatorWorkerTask { - const RESULT_SUCCESS = 0; - const RESULT_FAILURE = 1; + const RESULT_SUCCESS = 0; + const RESULT_FAILURE = 1; + const RESULT_CANCELLED = 2; protected $duration; protected $result; @@ -63,4 +64,22 @@ final class PhabricatorWorkerArchiveTask extends PhabricatorWorkerTask { return $result; } + public function unarchiveTask() { + $this->openTransaction(); + $active = id(new PhabricatorWorkerActiveTask()) + ->setID($this->getID()) + ->setTaskClass($this->getTaskClass()) + ->setLeaseOwner(null) + ->setLeaseExpires(0) + ->setFailureCount(0) + ->setDataID($this->getDataID()) + ->insert(); + + $this->setDataID(null); + $this->delete(); + $this->saveTransaction(); + + return $active; + } + } diff --git a/src/infrastructure/daemon/workers/storage/PhabricatorWorkerTask.php b/src/infrastructure/daemon/workers/storage/PhabricatorWorkerTask.php index 06c5e78fbf..324ec32b49 100644 --- a/src/infrastructure/daemon/workers/storage/PhabricatorWorkerTask.php +++ b/src/infrastructure/daemon/workers/storage/PhabricatorWorkerTask.php @@ -37,4 +37,8 @@ abstract class PhabricatorWorkerTask extends PhabricatorWorkerDAO { return $this->data; } + public function isArchived() { + return ($this instanceof PhabricatorWorkerArchiveTask); + } + } diff --git a/src/view/layout/PhabricatorActionView.php b/src/view/layout/PhabricatorActionView.php index bf745f5b7c..73dea19d81 100644 --- a/src/view/layout/PhabricatorActionView.php +++ b/src/view/layout/PhabricatorActionView.php @@ -151,6 +151,8 @@ final class PhabricatorActionView extends AphrontView { 'subscribe-add', 'subscribe-auto', 'subscribe-delete', + 'undo', + 'unlock', 'unpublish', 'world', ); diff --git a/webroot/rsrc/css/autosprite.css b/webroot/rsrc/css/autosprite.css index 474b24f252..d673f91c40 100644 --- a/webroot/rsrc/css/autosprite.css +++ b/webroot/rsrc/css/autosprite.css @@ -808,50 +808,58 @@ background-position: 0px -7823px; } -.action-unpublish { +.action-undo { background-position: 0px -7840px; } -.action-world { +.action-unlock { background-position: 0px -7857px; } -.remarkup-assist-b { +.action-unpublish { background-position: 0px -7874px; } +.action-world { + background-position: 0px -7891px; +} + +.remarkup-assist-b { + background-position: 0px -7908px; +} + .remarkup-assist-code { - background-position: 0px -7889px; + background-position: 0px -7923px; } .remarkup-assist-i { - background-position: 0px -7904px; + background-position: 0px -7938px; } .remarkup-assist-image { - background-position: 0px -7919px; + background-position: 0px -7953px; } .remarkup-assist-ol { - background-position: 0px -7934px; + background-position: 0px -7968px; } .remarkup-assist-tag { - background-position: 0px -7949px; + background-position: 0px -7983px; } .remarkup-assist-tt { - background-position: 0px -7964px; + background-position: 0px -7998px; } .remarkup-assist-ul { - background-position: 0px -7979px; + background-position: 0px -8013px; } .remarkup-assist-help { - background-position: 0px -7994px; + background-position: 0px -8028px; } .remarkup-assist-table { - background-position: 0px -8009px; + background-position: 0px -8043px; } diff --git a/webroot/rsrc/image/autosprite.png b/webroot/rsrc/image/autosprite.png index feed650f3a..69c8352f6d 100644 Binary files a/webroot/rsrc/image/autosprite.png and b/webroot/rsrc/image/autosprite.png differ