@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.

Modernize worker task detail view

Summary: Make mobile-friendly and provide UI to cancel/retry tasks. Remove display of task data to arbitrary users, as it may be sensitive.

Test Plan:
{F22502}
{F22503}
{F22504}
{F22505}
{F22506}

Reviewers: btrahan

Reviewed By: btrahan

CC: aran

Maniphest Tasks: T2015

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

+264 -141
+3 -3
src/__celerity_resource_map__.php
··· 51 51 ), 52 52 '/rsrc/image/autosprite.png' => 53 53 array( 54 - 'hash' => 'e1735b5cadbaf1f70b70a857eab53601', 55 - 'uri' => '/res/e1735b5c/rsrc/image/autosprite.png', 54 + 'hash' => 'bc9479b2a610a3ecee18dc88744c4ce6', 55 + 'uri' => '/res/bc9479b2/rsrc/image/autosprite.png', 56 56 'disk' => '/rsrc/image/autosprite.png', 57 57 'type' => 'png', 58 58 ), ··· 713 713 ), 714 714 'autosprite-css' => 715 715 array( 716 - 'uri' => '/res/10fb7fdc/rsrc/css/autosprite.css', 716 + 'uri' => '/res/6be3e4b3/rsrc/css/autosprite.css', 717 717 'type' => 'css', 718 718 'requires' => 719 719 array(
+139 -105
src/applications/daemon/controller/PhabricatorWorkerTaskDetailController.php
··· 31 31 32 32 $task = id(new PhabricatorWorkerActiveTask())->load($this->id); 33 33 if (!$task) { 34 + $task = id(new PhabricatorWorkerArchiveTask())->load($this->id); 35 + } 36 + 37 + if (!$task) { 38 + $title = pht('Task Does Not Exist'); 39 + 34 40 $error_view = new AphrontErrorView(); 35 41 $error_view->setTitle('No Such Task'); 36 42 $error_view->appendChild( 37 - '<p>This task may have recently completed.</p>'); 38 - $error_view->setSeverity(AphrontErrorView::SEVERITY_WARNING); 39 - return $this->buildStandardPageResponse( 40 - $error_view, 41 - array( 42 - 'title' => 'Task Does Not Exist', 43 - )); 43 + '<p>This task may have recently been garbage collected.</p>'); 44 + $error_view->setSeverity(AphrontErrorView::SEVERITY_NODATA); 45 + 46 + $content = $error_view; 47 + } else { 48 + $title = 'Task '.$task->getID(); 49 + 50 + $header = id(new PhabricatorHeaderView()) 51 + ->setHeader('Task '.$task->getID().' ('.$task->getTaskClass().')'); 52 + 53 + $actions = $this->buildActionListView($task); 54 + $properties = $this->buildPropertyListView($task); 55 + 56 + $content = array( 57 + $header, 58 + $actions, 59 + $properties, 60 + ); 44 61 } 45 62 46 - $data = id(new PhabricatorWorkerTaskData())->loadOneWhere( 47 - 'id = %d', 48 - $task->getDataID()); 63 + $nav = $this->buildSideNavView(); 64 + $nav->selectFilter(''); 65 + $nav->appendChild($content); 49 66 50 - $extra = null; 51 - switch ($task->getTaskClass()) { 52 - case 'PhabricatorRepositorySvnCommitChangeParserWorker': 53 - case 'PhabricatorRepositoryGitCommitChangeParserWorker': 54 - $commit_id = idx($data->getData(), 'commitID'); 55 - if ($commit_id) { 56 - $commit = id(new PhabricatorRepositoryCommit())->load($commit_id); 57 - if ($commit) { 58 - $repository = id(new PhabricatorRepository())->load( 59 - $commit->getRepositoryID()); 60 - if ($repository) { 61 - $extra = 62 - "<strong>NOTE:</strong> ". 63 - "You can manually retry this task by running this script:". 64 - "<pre>". 65 - "phabricator/\$ ./scripts/repository/reparse.php ". 66 - "r". 67 - phutil_escape_html($repository->getCallsign()). 68 - phutil_escape_html($commit->getCommitIdentifier()). 69 - " ". 70 - "--change". 71 - "</pre>"; 72 - } 73 - } 74 - } 75 - break; 76 - default: 77 - break; 67 + return $this->buildApplicationPage( 68 + $nav, 69 + array( 70 + 'title' => $title, 71 + )); 72 + } 73 + 74 + private function buildActionListView(PhabricatorWorkerTask $task) { 75 + $user = $this->getRequest()->getUser(); 76 + 77 + $view = new PhabricatorActionListView(); 78 + $view->setUser($user); 79 + 80 + $id = $task->getID(); 81 + 82 + if ($task->isArchived()) { 83 + $result_success = PhabricatorWorkerArchiveTask::RESULT_SUCCESS; 84 + $can_retry = ($task->getResult() != $result_success); 85 + 86 + $view->addAction( 87 + id(new PhabricatorActionView()) 88 + ->setName(pht('Retry Task')) 89 + ->setHref($this->getApplicationURI('/task/'.$id.'/retry/')) 90 + ->setIcon('undo') 91 + ->setWorkflow(true) 92 + ->setDisabled(!$can_retry)); 93 + } else { 94 + $view->addAction( 95 + id(new PhabricatorActionView()) 96 + ->setName(pht('Cancel Task')) 97 + ->setHref($this->getApplicationURI('/task/'.$id.'/cancel/')) 98 + ->setIcon('delete') 99 + ->setWorkflow(true)); 78 100 } 79 101 80 - if ($data) { 81 - $data = json_encode($data->getData()); 102 + $can_release = (!$task->isArchived()) && 103 + ($task->getLeaseOwner()); 104 + 105 + $view->addAction( 106 + id(new PhabricatorActionView()) 107 + ->setName(pht('Free Lease')) 108 + ->setHref($this->getApplicationURI('/task/'.$id.'/release/')) 109 + ->setIcon('unlock') 110 + ->setWorkflow(true) 111 + ->setDisabled(!$can_release)); 112 + 113 + return $view; 114 + } 115 + 116 + private function buildPropertyListView(PhabricatorWorkerTask $task) { 117 + $view = new PhabricatorPropertyListView(); 118 + 119 + if ($task->isArchived()) { 120 + switch ($task->getResult()) { 121 + case PhabricatorWorkerArchiveTask::RESULT_SUCCESS: 122 + $status = pht('Complete'); 123 + break; 124 + case PhabricatorWorkerArchiveTask::RESULT_FAILURE: 125 + $status = pht('Failed'); 126 + break; 127 + case PhabricatorWorkerArchiveTask::RESULT_CANCELLED: 128 + $status = pht('Cancelled'); 129 + break; 130 + default: 131 + throw new Exception("Unknown task status!"); 132 + } 133 + } else { 134 + $status = pht('Queued'); 82 135 } 83 136 84 - $form = id(new AphrontFormView()) 85 - ->setUser($user) 86 - ->appendChild( 87 - id(new AphrontFormStaticControl()) 88 - ->setLabel('ID') 89 - ->setValue($task->getID())) 90 - ->appendChild( 91 - id(new AphrontFormStaticControl()) 92 - ->setLabel('Type') 93 - ->setValue($task->getTaskClass())) 94 - ->appendChild( 95 - id(new AphrontFormStaticControl()) 96 - ->setLabel('Lease Owner') 97 - ->setValue($task->getLeaseOwner())) 98 - ->appendChild( 99 - id(new AphrontFormStaticControl()) 100 - ->setLabel('Lease Expires') 101 - ->setValue($task->getLeaseExpires() - time())) 102 - ->appendChild( 103 - id(new AphrontFormStaticControl()) 104 - ->setLabel('Failure Count') 105 - ->setValue($task->getFailureCount())) 106 - ->appendChild( 107 - id(new AphrontFormTextAreaControl()) 108 - ->setLabel('Data') 109 - ->setValue($data)); 137 + $view->addProperty( 138 + pht('Task Status'), 139 + $status); 140 + 141 + $view->addProperty( 142 + pht('Task Class'), 143 + phutil_escape_html($task->getTaskClass())); 110 144 111 - if ($extra) { 112 - $form->appendChild( 113 - id(new AphrontFormMarkupControl()) 114 - ->setLabel('More') 115 - ->setValue($extra)); 145 + if ($task->getLeaseExpires()) { 146 + if ($task->getLeaseExpires() > time()) { 147 + $lease_status = pht('Leased'); 148 + } else { 149 + $lease_status = pht('Lease Expired'); 150 + } 151 + } else { 152 + $lease_status = '<em>'.pht('Not Leased').'</em>'; 116 153 } 117 154 118 - $form 119 - ->appendChild( 120 - id(new AphrontFormSubmitControl()) 121 - ->addCancelButton('/daemon/', 'Back')); 155 + $view->addProperty( 156 + pht('Lease Status'), 157 + $lease_status); 158 + 159 + $view->addProperty( 160 + pht('Lease Owner'), 161 + $task->getLeaseOwner() 162 + ? phutil_escape_html($task->getLeaseOwner()) 163 + : '<em>'.pht('None').'</em>'); 164 + 165 + if ($task->getLeaseExpires() && $task->getLeaseOwner()) { 166 + $expires = ($task->getLeaseExpires() - time()); 167 + $expires = phabricator_format_relative_time_detailed($expires); 168 + } else { 169 + $expires = '<em>'.pht('None').'</em>'; 170 + } 122 171 123 - $panel = new AphrontPanelView(); 124 - $panel->setHeader('Task Detail'); 125 - $panel->setWidth(AphrontPanelView::WIDTH_WIDE); 126 - $panel->appendChild($form); 172 + $view->addProperty( 173 + pht('Lease Expires'), 174 + $expires); 127 175 128 - $panel->addButton( 129 - javelin_render_tag( 130 - 'a', 131 - array( 132 - 'href' => '/daemon/task/'.$task->getID().'/delete/', 133 - 'class' => 'button grey', 134 - 'sigil' => 'workflow', 135 - ), 136 - 'Delete Task')); 176 + $view->addProperty( 177 + pht('Failure Count'), 178 + phutil_escape_html($task->getFailureCount())); 137 179 138 - $panel->addButton( 139 - javelin_render_tag( 140 - 'a', 141 - array( 142 - 'href' => '/daemon/task/'.$task->getID().'/release/', 143 - 'class' => 'button grey', 144 - 'sigil' => 'workflow', 145 - ), 146 - 'Free Lease')); 180 + if ($task->isArchived()) { 181 + $duration = phutil_escape_html(number_format($task->getDuration()).' us'); 182 + } else { 183 + $duration = '<em>'.pht('Not Completed').'</em>'; 184 + } 147 185 148 - $nav = $this->buildSideNavView(); 149 - $nav->selectFilter(''); 150 - $nav->appendChild($panel); 186 + $view->addProperty( 187 + pht('Duration'), 188 + $duration); 151 189 152 - return $this->buildApplicationPage( 153 - $nav, 154 - array( 155 - 'title' => 'Task', 156 - )); 190 + return $view; 157 191 } 158 192 159 193 }
+75 -19
src/applications/daemon/controller/PhabricatorWorkerTaskUpdateController.php
··· 33 33 34 34 $task = id(new PhabricatorWorkerActiveTask())->load($this->id); 35 35 if (!$task) { 36 + $task = id(new PhabricatorWorkerArchiveTask())->load($this->id); 37 + } 38 + 39 + if (!$task) { 36 40 return new Aphront404Response(); 37 41 } 38 42 43 + $result_success = PhabricatorWorkerArchiveTask::RESULT_SUCCESS; 44 + $can_retry = ($task->isArchived()) && 45 + ($task->getResult() != $result_success); 46 + 47 + $can_cancel = !$task->isArchived(); 48 + $can_release = (!$task->isArchived()) && 49 + ($task->getLeaseOwner()); 50 + 51 + $next_uri = $this->getApplicationURI('/task/'.$task->getID().'/'); 52 + 39 53 if ($request->isFormPost()) { 40 54 switch ($this->action) { 41 - case 'delete': 42 - $task->delete(); 55 + case 'retry': 56 + if ($can_retry) { 57 + $task->unarchiveTask(); 58 + } 59 + break; 60 + case 'cancel': 61 + if ($can_cancel) { 62 + // Forcibly break the lease if one exists, so we can archive the 63 + // task. 64 + $task->setLeaseOwner(null); 65 + $task->setLeaseExpires(time()); 66 + 67 + $task->archiveTask( 68 + PhabricatorWorkerArchiveTask::RESULT_CANCELLED, 69 + 0); 70 + } 43 71 break; 44 72 case 'release': 45 - $task->setLeaseOwner(null); 46 - $task->setLeaseExpires(time()); 47 - $task->save(); 73 + if ($can_release) { 74 + $task->setLeaseOwner(null); 75 + $task->setLeaseExpires(time()); 76 + $task->save(); 77 + } 48 78 break; 49 79 } 50 - return id(new AphrontRedirectResponse())->setURI('/daemon/'); 80 + return id(new AphrontRedirectResponse()) 81 + ->setURI($next_uri); 51 82 } 52 83 53 84 $dialog = new AphrontDialogView(); 54 85 $dialog->setUser($user); 55 86 56 87 switch ($this->action) { 57 - case 'delete': 58 - $dialog->setTitle('Really delete task?'); 59 - $dialog->appendChild( 60 - '<p>The work this task represents will never be performed if you '. 61 - 'delete it. Are you sure you want to delete it?</p>'); 62 - $dialog->addSubmitButton('Delete Task'); 88 + case 'retry': 89 + if ($can_retry) { 90 + $dialog->setTitle('Really retry task?'); 91 + $dialog->appendChild( 92 + '<p>The task will be put back in the queue and executed '. 93 + 'again.</p>'); 94 + $dialog->addSubmitButton('Retry Task'); 95 + } else { 96 + $dialog->setTitle('Can Not Retry'); 97 + $dialog->appendChild( 98 + '<p>Only archived, unsuccessful tasks can be retried.</p>'); 99 + } 100 + break; 101 + case 'cancel': 102 + if ($can_cancel) { 103 + $dialog->setTitle('Really cancel task?'); 104 + $dialog->appendChild( 105 + '<p>The work this task represents will never be performed if you '. 106 + 'cancel it. Are you sure you want to cancel it?</p>'); 107 + $dialog->addSubmitButton('Cancel Task'); 108 + } else { 109 + $dialog->setTitle('Can Not Cancel'); 110 + $dialog->appendChild( 111 + '<p>Only active tasks can be cancelled.</p>'); 112 + } 63 113 break; 64 114 case 'release': 65 - $dialog->setTitle('Really free task lease?'); 66 - $dialog->appendChild( 67 - '<p>If the process which owns the task lease is still doing work '. 68 - 'on it, the work may be performed twice. Are you sure you '. 69 - 'want to free the lease?</p>'); 70 - $dialog->addSubmitButton('Free Lease'); 115 + if ($can_release) { 116 + $dialog->setTitle('Really free task lease?'); 117 + $dialog->appendChild( 118 + '<p>If the process which owns the task lease is still doing work '. 119 + 'on it, the work may be performed twice. Are you sure you '. 120 + 'want to free the lease?</p>'); 121 + $dialog->addSubmitButton('Free Lease'); 122 + } else { 123 + $dialog->setTitle('Can Not Free Lease'); 124 + $dialog->appendChild( 125 + '<p>Only active, leased tasks may have their leases freed.</p>'); 126 + } 71 127 break; 72 128 default: 73 129 return new Aphront404Response(); 74 130 } 75 131 76 - $dialog->addCancelButton('/daemon/'); 132 + $dialog->addCancelButton($next_uri); 77 133 78 134 return id(new AphrontDialogResponse())->setDialog($dialog); 79 135 }
+21 -2
src/infrastructure/daemon/workers/storage/PhabricatorWorkerArchiveTask.php
··· 18 18 19 19 final class PhabricatorWorkerArchiveTask extends PhabricatorWorkerTask { 20 20 21 - const RESULT_SUCCESS = 0; 22 - const RESULT_FAILURE = 1; 21 + const RESULT_SUCCESS = 0; 22 + const RESULT_FAILURE = 1; 23 + const RESULT_CANCELLED = 2; 23 24 24 25 protected $duration; 25 26 protected $result; ··· 61 62 $result = parent::delete(); 62 63 $this->saveTransaction(); 63 64 return $result; 65 + } 66 + 67 + public function unarchiveTask() { 68 + $this->openTransaction(); 69 + $active = id(new PhabricatorWorkerActiveTask()) 70 + ->setID($this->getID()) 71 + ->setTaskClass($this->getTaskClass()) 72 + ->setLeaseOwner(null) 73 + ->setLeaseExpires(0) 74 + ->setFailureCount(0) 75 + ->setDataID($this->getDataID()) 76 + ->insert(); 77 + 78 + $this->setDataID(null); 79 + $this->delete(); 80 + $this->saveTransaction(); 81 + 82 + return $active; 64 83 } 65 84 66 85 }
+4
src/infrastructure/daemon/workers/storage/PhabricatorWorkerTask.php
··· 37 37 return $this->data; 38 38 } 39 39 40 + public function isArchived() { 41 + return ($this instanceof PhabricatorWorkerArchiveTask); 42 + } 43 + 40 44 }
+2
src/view/layout/PhabricatorActionView.php
··· 151 151 'subscribe-add', 152 152 'subscribe-auto', 153 153 'subscribe-delete', 154 + 'undo', 155 + 'unlock', 154 156 'unpublish', 155 157 'world', 156 158 );
+20 -12
webroot/rsrc/css/autosprite.css
··· 808 808 background-position: 0px -7823px; 809 809 } 810 810 811 - .action-unpublish { 811 + .action-undo { 812 812 background-position: 0px -7840px; 813 813 } 814 814 815 - .action-world { 815 + .action-unlock { 816 816 background-position: 0px -7857px; 817 817 } 818 818 819 - .remarkup-assist-b { 819 + .action-unpublish { 820 820 background-position: 0px -7874px; 821 821 } 822 822 823 + .action-world { 824 + background-position: 0px -7891px; 825 + } 826 + 827 + .remarkup-assist-b { 828 + background-position: 0px -7908px; 829 + } 830 + 823 831 .remarkup-assist-code { 824 - background-position: 0px -7889px; 832 + background-position: 0px -7923px; 825 833 } 826 834 827 835 .remarkup-assist-i { 828 - background-position: 0px -7904px; 836 + background-position: 0px -7938px; 829 837 } 830 838 831 839 .remarkup-assist-image { 832 - background-position: 0px -7919px; 840 + background-position: 0px -7953px; 833 841 } 834 842 835 843 .remarkup-assist-ol { 836 - background-position: 0px -7934px; 844 + background-position: 0px -7968px; 837 845 } 838 846 839 847 .remarkup-assist-tag { 840 - background-position: 0px -7949px; 848 + background-position: 0px -7983px; 841 849 } 842 850 843 851 .remarkup-assist-tt { 844 - background-position: 0px -7964px; 852 + background-position: 0px -7998px; 845 853 } 846 854 847 855 .remarkup-assist-ul { 848 - background-position: 0px -7979px; 856 + background-position: 0px -8013px; 849 857 } 850 858 851 859 .remarkup-assist-help { 852 - background-position: 0px -7994px; 860 + background-position: 0px -8028px; 853 861 } 854 862 855 863 .remarkup-assist-table { 856 - background-position: 0px -8009px; 864 + background-position: 0px -8043px; 857 865 }
webroot/rsrc/image/autosprite.png

This is a binary file and will not be displayed.