@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 PhabricatorManiphestTaskFactEngine
4 extends PhabricatorTransactionFactEngine {
5
6 /**
7 * @return array<PhabricatorCountFact> All known task related facts
8 */
9 public function newFacts() {
10 return array(
11 id(new PhabricatorCountFact())
12 ->setKey('tasks.count.create'),
13
14 id(new PhabricatorCountFact())
15 ->setKey('tasks.open-count.create'),
16 id(new PhabricatorCountFact())
17 ->setKey('tasks.open-count.status'),
18
19 id(new PhabricatorCountFact())
20 ->setKey('tasks.count.create.project'),
21 id(new PhabricatorCountFact())
22 ->setKey('tasks.count.assign.project'),
23 id(new PhabricatorCountFact())
24 ->setKey('tasks.open-count.create.project'),
25 id(new PhabricatorCountFact())
26 ->setKey('tasks.open-count.status.project'),
27 id(new PhabricatorCountFact())
28 ->setKey('tasks.open-count.assign.project'),
29
30 id(new PhabricatorCountFact())
31 ->setKey('tasks.count.create.owner'),
32 id(new PhabricatorCountFact())
33 ->setKey('tasks.count.assign.owner'),
34 id(new PhabricatorCountFact())
35 ->setKey('tasks.open-count.create.owner'),
36 id(new PhabricatorCountFact())
37 ->setKey('tasks.open-count.status.owner'),
38 id(new PhabricatorCountFact())
39 ->setKey('tasks.open-count.assign.owner'),
40
41 id(new PhabricatorPointsFact())
42 ->setKey('tasks.points.create'),
43 id(new PhabricatorPointsFact())
44 ->setKey('tasks.points.score'),
45
46 id(new PhabricatorPointsFact())
47 ->setKey('tasks.open-points.create'),
48 id(new PhabricatorPointsFact())
49 ->setKey('tasks.open-points.status'),
50 id(new PhabricatorPointsFact())
51 ->setKey('tasks.open-points.score'),
52
53 id(new PhabricatorPointsFact())
54 ->setKey('tasks.points.create.project'),
55 id(new PhabricatorPointsFact())
56 ->setKey('tasks.points.assign.project'),
57 id(new PhabricatorPointsFact())
58 ->setKey('tasks.points.score.project'),
59 id(new PhabricatorPointsFact())
60 ->setKey('tasks.open-points.create.project'),
61 id(new PhabricatorPointsFact())
62 ->setKey('tasks.open-points.status.project'),
63 id(new PhabricatorPointsFact())
64 ->setKey('tasks.open-points.score.project'),
65 id(new PhabricatorPointsFact())
66 ->setKey('tasks.open-points.assign.project'),
67
68 id(new PhabricatorPointsFact())
69 ->setKey('tasks.points.create.owner'),
70 id(new PhabricatorPointsFact())
71 ->setKey('tasks.points.assign.owner'),
72 id(new PhabricatorPointsFact())
73 ->setKey('tasks.points.score.owner'),
74 id(new PhabricatorPointsFact())
75 ->setKey('tasks.open-points.create.owner'),
76 id(new PhabricatorPointsFact())
77 ->setKey('tasks.open-points.status.owner'),
78 id(new PhabricatorPointsFact())
79 ->setKey('tasks.open-points.score.owner'),
80 id(new PhabricatorPointsFact())
81 ->setKey('tasks.open-points.assign.owner'),
82 );
83 }
84
85 public function supportsDatapointsForObject(PhabricatorLiskDAO $object) {
86 return ($object instanceof ManiphestTask);
87 }
88
89 public function newDatapointsForObject(PhabricatorLiskDAO $object) {
90 $xaction_groups = $this->newTransactionGroupsForObject($object);
91
92 $old_open = false;
93 $old_points = 0;
94 $old_owner = null;
95 $project_map = array();
96 $object_phid = $object->getPHID();
97 $is_create = true;
98
99 $specs = array();
100 $datapoints = array();
101 foreach ($xaction_groups as $xaction_group) {
102 $add_projects = array();
103 $rem_projects = array();
104
105 $new_open = $old_open;
106 $new_points = $old_points;
107 $new_owner = $old_owner;
108
109 if ($is_create) {
110 // Assume tasks start open.
111 // TODO: This might be a questionable assumption?
112 $new_open = true;
113 }
114
115 $group_epoch = last($xaction_group)->getDateCreated();
116 foreach ($xaction_group as $xaction) {
117 $old_value = $xaction->getOldValue();
118 $new_value = $xaction->getNewValue();
119 switch ($xaction->getTransactionType()) {
120 case ManiphestTaskStatusTransaction::TRANSACTIONTYPE:
121 $new_open = !ManiphestTaskStatus::isClosedStatus($new_value);
122 break;
123 case ManiphestTaskMergedIntoTransaction::TRANSACTIONTYPE:
124 // When a task is merged into another task, it is changed to a
125 // closed status without generating a separate status transaction.
126 $new_open = false;
127 break;
128 case ManiphestTaskPointsTransaction::TRANSACTIONTYPE:
129 $new_points = (int)$xaction->getNewValue();
130 break;
131 case ManiphestTaskOwnerTransaction::TRANSACTIONTYPE:
132 $new_owner = $xaction->getNewValue();
133 break;
134 case PhabricatorTransactions::TYPE_EDGE:
135 $edge_type = $xaction->getMetadataValue('edge:type');
136 switch ($edge_type) {
137 case PhabricatorProjectObjectHasProjectEdgeType::EDGECONST:
138 $record = PhabricatorEdgeChangeRecord::newFromTransaction(
139 $xaction);
140 $add_projects += array_fuse($record->getAddedPHIDs());
141 $rem_projects += array_fuse($record->getRemovedPHIDs());
142 break;
143 }
144 break;
145 }
146 }
147
148 // If a project was both added and removed, moot it.
149 $mix_projects = array_intersect_key($add_projects, $rem_projects);
150 $add_projects = array_diff_key($add_projects, $mix_projects);
151 $rem_projects = array_diff_key($rem_projects, $mix_projects);
152
153 $project_sets = array(
154 array(
155 'phids' => $rem_projects,
156 'scale' => -1,
157 ),
158 array(
159 'phids' => $add_projects,
160 'scale' => 1,
161 ),
162 );
163
164 if ($is_create) {
165 $action = 'create';
166 $action_points = $new_points;
167 $include_open = $new_open;
168 } else {
169 $action = 'assign';
170 $action_points = $old_points;
171 $include_open = $old_open;
172 }
173
174 foreach ($project_sets as $project_set) {
175 $scale = $project_set['scale'];
176 foreach ($project_set['phids'] as $project_phid) {
177 if ($include_open) {
178 $specs[] = array(
179 "tasks.open-count.{$action}.project",
180 1 * $scale,
181 $project_phid,
182 );
183
184 $specs[] = array(
185 "tasks.open-points.{$action}.project",
186 $action_points * $scale,
187 $project_phid,
188 );
189 }
190
191 $specs[] = array(
192 "tasks.count.{$action}.project",
193 1 * $scale,
194 $project_phid,
195 );
196
197 $specs[] = array(
198 "tasks.points.{$action}.project",
199 $action_points * $scale,
200 $project_phid,
201 );
202
203 if ($scale < 0) {
204 unset($project_map[$project_phid]);
205 } else {
206 $project_map[$project_phid] = $project_phid;
207 }
208 }
209 }
210
211 if ($new_owner !== $old_owner) {
212 $owner_sets = array(
213 array(
214 'phid' => $old_owner,
215 'scale' => -1,
216 ),
217 array(
218 'phid' => $new_owner,
219 'scale' => 1,
220 ),
221 );
222
223 foreach ($owner_sets as $owner_set) {
224 $owner_phid = $owner_set['phid'];
225 if ($owner_phid === null) {
226 continue;
227 }
228
229 $scale = $owner_set['scale'];
230
231 if ($old_open != $new_open) {
232 $specs[] = array(
233 "tasks.open-count.{$action}.owner",
234 1 * $scale,
235 $owner_phid,
236 );
237
238 $specs[] = array(
239 "tasks.open-points.{$action}.owner",
240 $action_points * $scale,
241 $owner_phid,
242 );
243 }
244
245 $specs[] = array(
246 "tasks.count.{$action}.owner",
247 1 * $scale,
248 $owner_phid,
249 );
250
251 if ($action_points) {
252 $specs[] = array(
253 "tasks.points.{$action}.owner",
254 $action_points * $scale,
255 $owner_phid,
256 );
257 }
258 }
259 }
260
261 if ($is_create) {
262 $specs[] = array(
263 'tasks.count.create',
264 1,
265 );
266
267 $specs[] = array(
268 'tasks.points.create',
269 $new_points,
270 );
271
272 if ($new_open) {
273 $specs[] = array(
274 'tasks.open-count.create',
275 1,
276 );
277 $specs[] = array(
278 'tasks.open-points.create',
279 $new_points,
280 );
281 }
282 } else if ($new_open !== $old_open) {
283 if ($new_open) {
284 $scale = 1;
285 } else {
286 $scale = -1;
287 }
288
289 $specs[] = array(
290 'tasks.open-count.status',
291 1 * $scale,
292 );
293
294 $specs[] = array(
295 'tasks.open-points.status',
296 $action_points * $scale,
297 );
298
299 if ($new_owner !== null) {
300 $specs[] = array(
301 'tasks.open-count.status.owner',
302 1 * $scale,
303 $new_owner,
304 );
305 $specs[] = array(
306 'tasks.open-points.status.owner',
307 $action_points * $scale,
308 $new_owner,
309 );
310 }
311
312 foreach ($project_map as $project_phid) {
313 $specs[] = array(
314 'tasks.open-count.status.project',
315 1 * $scale,
316 $project_phid,
317 );
318 $specs[] = array(
319 'tasks.open-points.status.project',
320 $action_points * $scale,
321 $project_phid,
322 );
323 }
324 }
325
326 // The "score" facts only apply to rescoring tasks which already
327 // exist, so we skip them if the task is being created.
328 if (($new_points !== $old_points) && !$is_create) {
329 $delta = ($new_points - $old_points);
330
331 $specs[] = array(
332 'tasks.points.score',
333 $delta,
334 );
335
336 foreach ($project_map as $project_phid) {
337 $specs[] = array(
338 'tasks.points.score.project',
339 $delta,
340 $project_phid,
341 );
342
343 if ($old_open && $new_open) {
344 $specs[] = array(
345 'tasks.open-points.score.project',
346 $delta,
347 $project_phid,
348 );
349 }
350 }
351
352 if ($new_owner !== null) {
353 $specs[] = array(
354 'tasks.points.score.owner',
355 $delta,
356 $new_owner,
357 );
358
359 if ($old_open && $new_open) {
360 $specs[] = array(
361 'tasks.open-points.score.owner',
362 $delta,
363 $new_owner,
364 );
365 }
366 }
367
368 if ($old_open && $new_open) {
369 $specs[] = array(
370 'tasks.open-points.score',
371 $delta,
372 );
373 }
374 }
375
376 $old_points = $new_points;
377 $old_open = $new_open;
378 $old_owner = $new_owner;
379
380 foreach ($specs as $spec) {
381 $spec_key = $spec[0];
382 $spec_value = $spec[1];
383
384 // Don't write any facts with a value of 0. The "count" facts never
385 // have a value of 0, and the "points" facts aren't meaningful if
386 // they have a value of 0.
387 if ($spec_value == 0) {
388 continue;
389 }
390
391 $datapoint = $this->getFact($spec_key)
392 ->newDatapoint();
393
394 $datapoint
395 ->setObjectPHID($object_phid)
396 ->setValue($spec_value)
397 ->setEpoch($group_epoch);
398
399 if (isset($spec[2])) {
400 $datapoint->setDimensionPHID($spec[2]);
401 }
402
403 $datapoints[] = $datapoint;
404 }
405
406 $specs = array();
407 $is_create = false;
408 }
409
410 return $datapoints;
411 }
412
413
414}