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

Fix some of the most obvious bugs in fact generation from Maniphest tasks

Summary:
Depends on D19121. Ref T13083. Group transactions and show groups in the debugging view.

Fix some of the most obvious issues with fact generation:

- No more 0-point facts.
- Engine can now generate at least one of every type of fact.

Test Plan: Generated facts, viewed them in the debugging view, fact generation largely appeared to align with reality. No more "no facts in storage" facts.

Subscribers: yelirekim

Maniphest Tasks: T13083

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

+198 -34
+4 -2
src/__phutil_library_map__.php
··· 2928 2928 'PhabricatorFactManagementDestroyWorkflow' => 'applications/fact/management/PhabricatorFactManagementDestroyWorkflow.php', 2929 2929 'PhabricatorFactManagementListWorkflow' => 'applications/fact/management/PhabricatorFactManagementListWorkflow.php', 2930 2930 'PhabricatorFactManagementWorkflow' => 'applications/fact/management/PhabricatorFactManagementWorkflow.php', 2931 - 'PhabricatorFactManiphestTaskEngine' => 'applications/fact/engine/PhabricatorFactManiphestTaskEngine.php', 2932 2931 'PhabricatorFactObjectController' => 'applications/fact/controller/PhabricatorFactObjectController.php', 2933 2932 'PhabricatorFactObjectDimension' => 'applications/fact/storage/PhabricatorFactObjectDimension.php', 2934 2933 'PhabricatorFactRaw' => 'applications/fact/storage/PhabricatorFactRaw.php', ··· 3264 3263 'PhabricatorManagementWorkflow' => 'infrastructure/management/PhabricatorManagementWorkflow.php', 3265 3264 'PhabricatorManiphestApplication' => 'applications/maniphest/application/PhabricatorManiphestApplication.php', 3266 3265 'PhabricatorManiphestConfigOptions' => 'applications/maniphest/config/PhabricatorManiphestConfigOptions.php', 3266 + 'PhabricatorManiphestTaskFactEngine' => 'applications/fact/engine/PhabricatorManiphestTaskFactEngine.php', 3267 3267 'PhabricatorManiphestTaskTestDataGenerator' => 'applications/maniphest/lipsum/PhabricatorManiphestTaskTestDataGenerator.php', 3268 3268 'PhabricatorManualActivitySetupCheck' => 'applications/config/check/PhabricatorManualActivitySetupCheck.php', 3269 3269 'PhabricatorMarkupCache' => 'applications/cache/storage/PhabricatorMarkupCache.php', ··· 4362 4362 'PhabricatorTokensSettingsPanel' => 'applications/settings/panel/PhabricatorTokensSettingsPanel.php', 4363 4363 'PhabricatorTokensToken' => 'applications/tokens/storage/PhabricatorTokensToken.php', 4364 4364 'PhabricatorTransactionChange' => 'applications/transactions/data/PhabricatorTransactionChange.php', 4365 + 'PhabricatorTransactionFactEngine' => 'applications/fact/engine/PhabricatorTransactionFactEngine.php', 4365 4366 'PhabricatorTransactionRemarkupChange' => 'applications/transactions/data/PhabricatorTransactionRemarkupChange.php', 4366 4367 'PhabricatorTransactions' => 'applications/transactions/constants/PhabricatorTransactions.php', 4367 4368 'PhabricatorTransactionsApplication' => 'applications/transactions/application/PhabricatorTransactionsApplication.php', ··· 8459 8460 'PhabricatorFactManagementDestroyWorkflow' => 'PhabricatorFactManagementWorkflow', 8460 8461 'PhabricatorFactManagementListWorkflow' => 'PhabricatorFactManagementWorkflow', 8461 8462 'PhabricatorFactManagementWorkflow' => 'PhabricatorManagementWorkflow', 8462 - 'PhabricatorFactManiphestTaskEngine' => 'PhabricatorFactEngine', 8463 8463 'PhabricatorFactObjectController' => 'PhabricatorFactController', 8464 8464 'PhabricatorFactObjectDimension' => 'PhabricatorFactDimension', 8465 8465 'PhabricatorFactRaw' => 'PhabricatorFactDAO', ··· 8833 8833 'PhabricatorManagementWorkflow' => 'PhutilArgumentWorkflow', 8834 8834 'PhabricatorManiphestApplication' => 'PhabricatorApplication', 8835 8835 'PhabricatorManiphestConfigOptions' => 'PhabricatorApplicationConfigOptions', 8836 + 'PhabricatorManiphestTaskFactEngine' => 'PhabricatorTransactionFactEngine', 8836 8837 'PhabricatorManiphestTaskTestDataGenerator' => 'PhabricatorTestDataGenerator', 8837 8838 'PhabricatorManualActivitySetupCheck' => 'PhabricatorSetupCheck', 8838 8839 'PhabricatorMarkupCache' => 'PhabricatorCacheDAO', ··· 10156 10157 'PhabricatorConduitResultInterface', 10157 10158 ), 10158 10159 'PhabricatorTransactionChange' => 'Phobject', 10160 + 'PhabricatorTransactionFactEngine' => 'PhabricatorFactEngine', 10159 10161 'PhabricatorTransactionRemarkupChange' => 'PhabricatorTransactionChange', 10160 10162 'PhabricatorTransactions' => 'Phobject', 10161 10163 'PhabricatorTransactionsApplication' => 'PhabricatorApplication',
+56
src/applications/fact/controller/PhabricatorFactObjectController.php
··· 17 17 18 18 $engines = PhabricatorFactEngine::loadAllEngines(); 19 19 foreach ($engines as $key => $engine) { 20 + $engine = id(clone $engine) 21 + ->setViewer($viewer); 22 + $engines[$key] = $engine; 23 + 20 24 if (!$engine->supportsDatapointsForObject($object)) { 21 25 unset($engines[$key]); 22 26 } ··· 250 254 ->setTable($table); 251 255 252 256 $content[] = $box; 257 + 258 + if ($engine instanceof PhabricatorTransactionFactEngine) { 259 + $groups = $engine->newTransactionGroupsForObject($object); 260 + $groups = array_values($groups); 261 + 262 + $xaction_phids = array(); 263 + foreach ($groups as $group_key => $xactions) { 264 + foreach ($xactions as $xaction) { 265 + $xaction_phids[] = $xaction->getAuthorPHID(); 266 + } 267 + } 268 + $xaction_handles = $viewer->loadHandles($xaction_phids); 269 + 270 + $rows = array(); 271 + foreach ($groups as $group_key => $xactions) { 272 + foreach ($xactions as $xaction) { 273 + $rows[] = array( 274 + $group_key, 275 + $xaction->getTransactionType(), 276 + $xaction_handles[$xaction->getAuthorPHID()]->renderLink(), 277 + phabricator_datetime($xaction->getDateCreated(), $viewer), 278 + ); 279 + } 280 + } 281 + 282 + $table = id(new AphrontTableView($rows)) 283 + ->setHeaders( 284 + array( 285 + pht('Group'), 286 + pht('Type'), 287 + pht('Author'), 288 + pht('Date'), 289 + )) 290 + ->setColumnClasses( 291 + array( 292 + null, 293 + 'pri', 294 + 'wide', 295 + 'right', 296 + )); 297 + 298 + $header = pht( 299 + '%s (Transactions)', 300 + get_class($engine)); 301 + 302 + $xaction_box = id(new PHUIObjectBoxView()) 303 + ->setHeaderText($header) 304 + ->setTable($table); 305 + 306 + $content[] = $xaction_box; 307 + } 308 + 253 309 } 254 310 255 311 $crumbs = $this->buildApplicationCrumbs()
+5
src/applications/fact/daemon/PhabricatorFactDaemon.php
··· 62 62 public function setEngines(array $engines) { 63 63 assert_instances_of($engines, 'PhabricatorFactEngine'); 64 64 65 + $viewer = PhabricatorUser::getOmnipotentUser(); 66 + foreach ($engines as $engine) { 67 + $engine->setViewer($viewer); 68 + } 69 + 65 70 $this->engines = $engines; 66 71 return $this; 67 72 }
+12 -2
src/applications/fact/engine/PhabricatorFactEngine.php
··· 3 3 abstract class PhabricatorFactEngine extends Phobject { 4 4 5 5 private $factMap; 6 + private $viewer; 6 7 7 8 final public static function loadAllEngines() { 8 9 return id(new PhutilClassMapQuery()) ··· 35 36 return $this->factMap[$key]; 36 37 } 37 38 38 - final protected function getViewer() { 39 - return PhabricatorUser::getOmnipotentUser(); 39 + public function setViewer(PhabricatorUser $viewer) { 40 + $this->viewer = $viewer; 41 + return $this; 42 + } 43 + 44 + public function getViewer() { 45 + if (!$this->viewer) { 46 + throw new PhutilInvalidStateException('setViewer'); 47 + } 48 + 49 + return $this->viewer; 40 50 } 41 51 42 52 }
+37 -30
src/applications/fact/engine/PhabricatorFactManiphestTaskEngine.php src/applications/fact/engine/PhabricatorManiphestTaskFactEngine.php
··· 1 1 <?php 2 2 3 - final class PhabricatorFactManiphestTaskEngine 4 - extends PhabricatorFactEngine { 3 + final class PhabricatorManiphestTaskFactEngine 4 + extends PhabricatorTransactionFactEngine { 5 5 6 6 public function newFacts() { 7 7 return array( ··· 73 73 id(new PhabricatorPointsFact()) 74 74 ->setKey('tasks.open-points.status.owner'), 75 75 id(new PhabricatorPointsFact()) 76 - ->setKey('tasks.open-points.score.project'), 76 + ->setKey('tasks.open-points.score.owner'), 77 77 id(new PhabricatorPointsFact()) 78 78 ->setKey('tasks.open-points.assign.owner'), 79 79 ); ··· 84 84 } 85 85 86 86 public function newDatapointsForObject(PhabricatorLiskDAO $object) { 87 - $viewer = $this->getViewer(); 88 - 89 - $xaction_query = PhabricatorApplicationTransactionQuery::newQueryForObject( 90 - $object); 91 - $xactions = $xaction_query 92 - ->setViewer($viewer) 93 - ->withObjectPHIDs(array($object->getPHID())) 94 - ->execute(); 95 - 96 - $xactions = msortv($xactions, 'newChronologicalSortVector'); 87 + $xaction_groups = $this->newTransactionGroupsForObject($object); 97 88 98 89 $old_open = false; 99 90 $old_points = 0; ··· 104 95 105 96 $specs = array(); 106 97 $datapoints = array(); 107 - foreach ($xactions as $xaction_group) { 98 + foreach ($xaction_groups as $xaction_group) { 108 99 $add_projects = array(); 109 100 $rem_projects = array(); 110 101 ··· 112 103 $new_points = $old_points; 113 104 $new_owner = $old_owner; 114 105 115 - // TODO: Actually group concurrent transactions. 116 - $xaction_group = array($xaction_group); 106 + if ($is_create) { 107 + // Assume tasks start open. 108 + // TODO: This might be a questionable assumption? 109 + $new_open = true; 110 + } 117 111 118 112 $group_epoch = last($xaction_group)->getDateCreated(); 119 113 foreach ($xaction_group as $xaction) { ··· 167 161 if ($is_create) { 168 162 $action = 'create'; 169 163 $action_points = $new_points; 164 + $include_open = $new_open; 170 165 } else { 171 166 $action = 'assign'; 172 167 $action_points = $old_points; 168 + $include_open = $old_open; 173 169 } 174 170 175 171 foreach ($project_sets as $project_set) { 176 172 $scale = $project_set['scale']; 177 173 foreach ($project_set['phids'] as $project_phid) { 178 - if ($old_open) { 174 + if ($include_open) { 179 175 $specs[] = array( 180 176 "tasks.open-count.{$action}.project", 181 177 1 * $scale, ··· 227 223 continue; 228 224 } 229 225 230 - if ($old_open) { 226 + $scale = $owner_set['scale']; 227 + 228 + if ($old_open != $new_open) { 231 229 $specs[] = array( 232 230 "tasks.open-count.{$action}.owner", 233 231 1 * $scale, ··· 247 245 $owner_phid, 248 246 ); 249 247 250 - $specs[] = array( 251 - "tasks.points.{$action}.owner", 252 - $action_points * $scale, 253 - $owner_phid, 254 - ); 248 + if ($action_points) { 249 + $specs[] = array( 250 + "tasks.points.{$action}.owner", 251 + $action_points * $scale, 252 + $owner_phid, 253 + ); 254 + } 255 255 } 256 - 257 - $old_owner = $new_owner; 258 256 } 259 257 260 258 if ($is_create) { ··· 262 260 'tasks.count.create', 263 261 1, 264 262 ); 263 + 265 264 $specs[] = array( 266 265 'tasks.points.create', 267 266 $new_points, ··· 316 315 $specs[] = array( 317 316 'tasks.open-points.status.project', 318 317 $action_points * $scale, 319 - $new_owner, 318 + $project_phid, 320 319 ); 321 320 } 322 - 323 - $old_open = $new_open; 324 321 } 325 322 326 323 // The "score" facts only apply to rescoring tasks which already ··· 371 368 $delta, 372 369 ); 373 370 } 371 + } 374 372 375 - $old_points = $new_points; 376 - } 373 + $old_points = $new_points; 374 + $old_open = $new_open; 375 + $old_owner = $new_owner; 377 376 378 377 foreach ($specs as $spec) { 379 378 $spec_key = $spec[0]; 380 379 $spec_value = $spec[1]; 380 + 381 + // Don't write any facts with a value of 0. The "count" facts never 382 + // have a value of 0, and the "points" facts aren't meaningful if 383 + // they have a value of 0. 384 + if ($spec_value == 0) { 385 + continue; 386 + } 381 387 382 388 $datapoint = $this->getFact($spec_key) 383 389 ->newDatapoint(); ··· 400 406 401 407 return $datapoints; 402 408 } 409 + 403 410 404 411 }
+84
src/applications/fact/engine/PhabricatorTransactionFactEngine.php
··· 1 + <?php 2 + 3 + abstract class PhabricatorTransactionFactEngine 4 + extends PhabricatorFactEngine { 5 + 6 + public function newTransactionGroupsForObject(PhabricatorLiskDAO $object) { 7 + $viewer = $this->getViewer(); 8 + 9 + $xaction_query = PhabricatorApplicationTransactionQuery::newQueryForObject( 10 + $object); 11 + $xactions = $xaction_query 12 + ->setViewer($viewer) 13 + ->withObjectPHIDs(array($object->getPHID())) 14 + ->execute(); 15 + 16 + $xactions = msortv($xactions, 'newChronologicalSortVector'); 17 + 18 + return $this->groupTransactions($xactions); 19 + } 20 + 21 + protected function groupTransactions(array $xactions) { 22 + // These grouping rules are generally much looser than the display grouping 23 + // rules. As long as the same user is editing the task and they don't leave 24 + // it alone for a particularly long time, we'll group things together. 25 + 26 + $breaks = array(); 27 + 28 + $touch_window = phutil_units('15 minutes in seconds'); 29 + $user_type = PhabricatorPeopleUserPHIDType::TYPECONST; 30 + 31 + $last_actor = null; 32 + $last_epoch = null; 33 + 34 + foreach ($xactions as $key => $xaction) { 35 + $this_actor = $xaction->getAuthorPHID(); 36 + if (phid_get_type($this_actor) != $user_type) { 37 + $this_actor = null; 38 + } 39 + 40 + if ($this_actor && $last_actor && ($this_actor != $last_actor)) { 41 + $breaks[$key] = true; 42 + } 43 + 44 + // If too much time passed between changes, group them separately. 45 + $this_epoch = $xaction->getDateCreated(); 46 + if ($last_epoch) { 47 + if (($this_epoch - $last_epoch) > $touch_window) { 48 + $breaks[$key] = true; 49 + } 50 + } 51 + 52 + // The clock gets reset every time the same real user touches the 53 + // task, but does not reset if an automated actor touches things. 54 + if (!$last_actor || ($this_actor == $last_actor)) { 55 + $last_epoch = $this_epoch; 56 + } 57 + 58 + if ($this_actor && ($last_actor != $this_actor)) { 59 + $last_actor = $this_actor; 60 + $last_epoch = $this_epoch; 61 + } 62 + } 63 + 64 + $groups = array(); 65 + $group = array(); 66 + foreach ($xactions as $key => $xaction) { 67 + if (isset($breaks[$key])) { 68 + if ($group) { 69 + $groups[] = $group; 70 + $group = array(); 71 + } 72 + } 73 + 74 + $group[] = $xaction; 75 + } 76 + 77 + if ($group) { 78 + $groups[] = $group; 79 + } 80 + 81 + return $groups; 82 + } 83 + 84 + }