@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<?php
2
3final class PhabricatorWorkerTaskDetailController
4 extends PhabricatorDaemonController {
5
6 public function handleRequest(AphrontRequest $request) {
7 $viewer = $request->getViewer();
8 $id = $request->getURIData('id');
9
10 $task = id(new PhabricatorWorkerActiveTask())->load($id);
11 if (!$task) {
12 $tasks = id(new PhabricatorWorkerArchiveTaskQuery())
13 ->withIDs(array($id))
14 ->execute();
15 $task = reset($tasks);
16 }
17
18 $header = new PHUIHeaderView();
19
20 if (!$task) {
21 $title = pht('Task Does Not Exist');
22
23 $header->setHeader(pht('Task %d Missing', $id));
24
25 $error_view = new PHUIInfoView();
26 $error_view->setTitle(pht('No Such Task'));
27 $error_view->appendChild(phutil_tag(
28 'p',
29 array(),
30 pht('This task may have recently been garbage collected.')));
31 $error_view->setSeverity(PHUIInfoView::SEVERITY_NODATA);
32
33 $content = $error_view;
34 } else {
35 $title = pht('Task %d', $task->getID());
36
37 $header->setHeader(
38 pht(
39 'Task %d: %s',
40 $task->getID(),
41 $task->getTaskClass()));
42
43 $properties = $this->buildPropertyListView($task);
44
45 $object_box = id(new PHUIObjectBoxView())
46 ->setHeaderText($title)
47 ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
48 ->addPropertyList($properties);
49
50 $retry_head = id(new PHUIHeaderView())
51 ->setHeader(pht('Retries'));
52
53 $retry_info = $this->buildRetryListView($task);
54
55 $retry_box = id(new PHUIObjectBoxView())
56 ->setHeader($retry_head)
57 ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
58 ->addPropertyList($retry_info);
59
60 $content = array(
61 $object_box,
62 $retry_box,
63 );
64 }
65
66 $header->setHeaderIcon('fa-sort');
67
68 $crumbs = $this->buildApplicationCrumbs();
69 $crumbs->addTextCrumb($title);
70 $crumbs->setBorder(true);
71
72 $view = id(new PHUITwoColumnView())
73 ->setHeader($header)
74 ->setFooter($content);
75
76 return $this->newPage()
77 ->setTitle($title)
78 ->setCrumbs($crumbs)
79 ->appendChild($view);
80 }
81
82 private function buildPropertyListView(PhabricatorWorkerTask $task) {
83 $viewer = $this->getViewer();
84
85 $view = new PHUIPropertyListView();
86
87 $object_phid = $task->getObjectPHID();
88 if ($object_phid) {
89 $handles = $viewer->loadHandles(array($object_phid));
90 $handle = $handles[$object_phid];
91 if ($handle->isComplete()) {
92 $view->addProperty(pht('Object'), $handle->renderLink());
93 }
94 }
95
96 if ($task->isArchived()) {
97 switch ($task->getResult()) {
98 case PhabricatorWorkerArchiveTask::RESULT_SUCCESS:
99 $status = pht('Complete');
100 break;
101 case PhabricatorWorkerArchiveTask::RESULT_FAILURE:
102 $status = pht('Failed');
103 break;
104 case PhabricatorWorkerArchiveTask::RESULT_CANCELLED:
105 $status = pht('Cancelled');
106 break;
107 default:
108 throw new Exception(pht('Unknown task status!'));
109 }
110 } else {
111 $status = pht('Queued');
112 }
113
114 $view->addProperty(
115 pht('Task Status'),
116 $status);
117
118 $view->addProperty(
119 pht('Task Class'),
120 $task->getTaskClass());
121
122 if ($task->getLeaseExpires()) {
123 if ($task->getLeaseExpires() > time()) {
124 $lease_status = pht('Leased');
125 } else {
126 $lease_status = pht('Lease Expired');
127 }
128 } else {
129 $lease_status = phutil_tag('em', array(), pht('Not Leased'));
130 }
131
132 $view->addProperty(
133 pht('Lease Status'),
134 $lease_status);
135
136 $view->addProperty(
137 pht('Lease Owner'),
138 $task->getLeaseOwner()
139 ? $task->getLeaseOwner()
140 : phutil_tag('em', array(), pht('None')));
141
142 if ($task->getLeaseExpires() && $task->getLeaseOwner()) {
143 $expires = ($task->getLeaseExpires() - time());
144 $expires = phutil_format_relative_time_detailed($expires);
145 } else {
146 $expires = phutil_tag('em', array(), pht('None'));
147 }
148
149 $view->addProperty(
150 pht('Lease Expires'),
151 $expires);
152
153 if ($task->isArchived()) {
154 $duration = pht('%s us', new PhutilNumber($task->getDuration()));
155 } else {
156 $duration = phutil_tag('em', array(), pht('Not Completed'));
157 }
158
159 $view->addProperty(
160 pht('Duration'),
161 $duration);
162
163 $data = id(new PhabricatorWorkerTaskData())->load($task->getDataID());
164 $task->setData($data->getData());
165 $worker = $task->getWorkerInstance();
166 $data = $worker->renderForDisplay($viewer);
167
168 if ($data !== null) {
169 $view->addProperty(pht('Data'), $data);
170 }
171
172 return $view;
173 }
174
175 private function buildRetryListView(PhabricatorWorkerTask $task) {
176 $view = new PHUIPropertyListView();
177
178 $data = id(new PhabricatorWorkerTaskData())->load($task->getDataID());
179 $task->setData($data->getData());
180 $worker = $task->getWorkerInstance();
181
182 $view->addProperty(
183 pht('Failure Count'),
184 $task->getFailureCount());
185
186 $retry_count = $worker->getMaximumRetryCount();
187 if ($retry_count === null) {
188 $max_retries = phutil_tag('em', array(), pht('Retries Forever'));
189 $retry_count = INF;
190 } else {
191 $max_retries = $retry_count;
192 }
193
194 $view->addProperty(
195 pht('Maximum Retries'),
196 $max_retries);
197
198 $projection = clone $task;
199 $projection->makeEphemeral();
200
201 $next = array();
202 for ($ii = $task->getFailureCount(); $ii < $retry_count; $ii++) {
203 $projection->setFailureCount($ii);
204 $next[] = $worker->getWaitBeforeRetry($projection);
205 if (count($next) > 10) {
206 break;
207 }
208 }
209
210 if ($next) {
211 $cumulative = 0;
212 foreach ($next as $key => $duration) {
213 if ($duration === null) {
214 $duration = 60;
215 }
216 $cumulative += $duration;
217 $next[$key] = phutil_format_relative_time($cumulative);
218 }
219 if ($ii != $retry_count) {
220 $next[] = '...';
221 }
222 $retries_in = implode(', ', $next);
223 } else {
224 $retries_in = pht('No More Retries');
225 }
226
227 $view->addProperty(
228 pht('Retries After'),
229 $retries_in);
230
231 return $view;
232 }
233
234}