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

Extract count/point data from tasks in Fact engines

Summary:
Depends on D19119. Ref T13083. This is probably still very buggy, but I'm planning to build support tools to make debugging facts easier shortly.

This generates a large number of datapoints, at least, and can render some charts which aren't all completely broken in an obvious way.

Test Plan: Ran `bin/fact analyze --all`, got some charts with lines that went up and down in the web UI.

Subscribers: yelirekim

Maniphest Tasks: T13083

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

+422 -18
+2
src/__phutil_library_map__.php
··· 2588 2588 'PhabricatorCoreCreateTransaction' => 'applications/transactions/xaction/PhabricatorCoreCreateTransaction.php', 2589 2589 'PhabricatorCoreTransactionType' => 'applications/transactions/xaction/PhabricatorCoreTransactionType.php', 2590 2590 'PhabricatorCoreVoidTransaction' => 'applications/transactions/xaction/PhabricatorCoreVoidTransaction.php', 2591 + 'PhabricatorCountFact' => 'applications/fact/fact/PhabricatorCountFact.php', 2591 2592 'PhabricatorCountdown' => 'applications/countdown/storage/PhabricatorCountdown.php', 2592 2593 'PhabricatorCountdownApplication' => 'applications/countdown/application/PhabricatorCountdownApplication.php', 2593 2594 'PhabricatorCountdownController' => 'applications/countdown/controller/PhabricatorCountdownController.php', ··· 8078 8079 'PhabricatorCoreCreateTransaction' => 'PhabricatorCoreTransactionType', 8079 8080 'PhabricatorCoreTransactionType' => 'PhabricatorModularTransactionType', 8080 8081 'PhabricatorCoreVoidTransaction' => 'PhabricatorModularTransactionType', 8082 + 'PhabricatorCountFact' => 'PhabricatorFact', 8081 8083 'PhabricatorCountdown' => array( 8082 8084 'PhabricatorCountdownDAO', 8083 8085 'PhabricatorPolicyInterface',
+3
src/applications/fact/controller/PhabricatorFactChartController.php
··· 16 16 17 17 $key_id = id(new PhabricatorFactKeyDimension()) 18 18 ->newDimensionID($fact->getKey()); 19 + if (!$key_id) { 20 + return new Aphront404Response(); 21 + } 19 22 20 23 $table = $fact->newDatapoint(); 21 24 $conn_r = $table->establishConnection('r');
+13 -6
src/applications/fact/daemon/PhabricatorFactDaemon.php
··· 70 70 $result = null; 71 71 72 72 $datapoints = array(); 73 + $count = 0; 73 74 foreach ($iterator as $key => $object) { 74 75 $phid = $object->getPHID(); 75 76 $this->log(pht('Processing %s...', $phid)); 76 - $datapoints[$phid] = $this->newDatapoints($object); 77 - if (count($datapoints) > 1024) { 77 + $object_datapoints = $this->newDatapoints($object); 78 + $count += count($object_datapoints); 79 + 80 + $datapoints[$phid] = $object_datapoints; 81 + 82 + if ($count > 1024) { 78 83 $this->updateDatapoints($datapoints); 79 84 $datapoints = array(); 85 + $count = 0; 80 86 } 87 + 81 88 $result = $key; 82 89 } 83 90 84 - if ($datapoints) { 91 + if ($count) { 85 92 $this->updateDatapoints($datapoints); 86 93 $datapoints = array(); 94 + $count = 0; 87 95 } 88 96 89 97 return $result; ··· 110 118 if (!$phids) { 111 119 return; 112 120 } 113 - 114 121 115 122 $fact_keys = array(); 116 123 $objects = array(); ··· 129 136 } 130 137 131 138 $key_map = id(new PhabricatorFactKeyDimension()) 132 - ->newDimensionMap(array_keys($fact_keys)); 139 + ->newDimensionMap(array_keys($fact_keys), true); 133 140 $object_map = id(new PhabricatorFactObjectDimension()) 134 - ->newDimensionMap(array_keys($objects)); 141 + ->newDimensionMap(array_keys($objects), true); 135 142 136 143 $table = new PhabricatorFactIntDatapoint(); 137 144 $conn = $table->establishConnection('w');
+4
src/applications/fact/engine/PhabricatorFactEngine.php
··· 35 35 return $this->factMap[$key]; 36 36 } 37 37 38 + final protected function getViewer() { 39 + return PhabricatorUser::getOmnipotentUser(); 40 + } 41 + 38 42 }
+379 -9
src/applications/fact/engine/PhabricatorFactManiphestTaskEngine.php
··· 5 5 6 6 public function newFacts() { 7 7 return array( 8 + id(new PhabricatorCountFact()) 9 + ->setKey('tasks.count.create'), 10 + 11 + id(new PhabricatorCountFact()) 12 + ->setKey('tasks.open-count.create'), 13 + id(new PhabricatorCountFact()) 14 + ->setKey('tasks.open-count.status'), 15 + 16 + id(new PhabricatorCountFact()) 17 + ->setKey('tasks.count.create.project'), 18 + id(new PhabricatorCountFact()) 19 + ->setKey('tasks.count.assign.project'), 20 + id(new PhabricatorCountFact()) 21 + ->setKey('tasks.open-count.create.project'), 22 + id(new PhabricatorCountFact()) 23 + ->setKey('tasks.open-count.status.project'), 24 + id(new PhabricatorCountFact()) 25 + ->setKey('tasks.open-count.assign.project'), 26 + 27 + id(new PhabricatorCountFact()) 28 + ->setKey('tasks.count.create.owner'), 29 + id(new PhabricatorCountFact()) 30 + ->setKey('tasks.count.assign.owner'), 31 + id(new PhabricatorCountFact()) 32 + ->setKey('tasks.open-count.create.owner'), 33 + id(new PhabricatorCountFact()) 34 + ->setKey('tasks.open-count.status.owner'), 35 + id(new PhabricatorCountFact()) 36 + ->setKey('tasks.open-count.assign.owner'), 37 + 8 38 id(new PhabricatorPointsFact()) 9 - ->setKey('tasks.count.open'), 39 + ->setKey('tasks.points.create'), 40 + id(new PhabricatorPointsFact()) 41 + ->setKey('tasks.points.score'), 42 + 43 + id(new PhabricatorPointsFact()) 44 + ->setKey('tasks.open-points.create'), 45 + id(new PhabricatorPointsFact()) 46 + ->setKey('tasks.open-points.status'), 47 + id(new PhabricatorPointsFact()) 48 + ->setKey('tasks.open-points.score'), 49 + 50 + id(new PhabricatorPointsFact()) 51 + ->setKey('tasks.points.create.project'), 52 + id(new PhabricatorPointsFact()) 53 + ->setKey('tasks.points.assign.project'), 54 + id(new PhabricatorPointsFact()) 55 + ->setKey('tasks.points.score.project'), 56 + id(new PhabricatorPointsFact()) 57 + ->setKey('tasks.open-points.create.project'), 58 + id(new PhabricatorPointsFact()) 59 + ->setKey('tasks.open-points.status.project'), 60 + id(new PhabricatorPointsFact()) 61 + ->setKey('tasks.open-points.score.project'), 62 + id(new PhabricatorPointsFact()) 63 + ->setKey('tasks.open-points.assign.project'), 64 + 65 + id(new PhabricatorPointsFact()) 66 + ->setKey('tasks.points.create.owner'), 67 + id(new PhabricatorPointsFact()) 68 + ->setKey('tasks.points.assign.owner'), 69 + id(new PhabricatorPointsFact()) 70 + ->setKey('tasks.points.score.owner'), 71 + id(new PhabricatorPointsFact()) 72 + ->setKey('tasks.open-points.create.owner'), 73 + id(new PhabricatorPointsFact()) 74 + ->setKey('tasks.open-points.status.owner'), 75 + id(new PhabricatorPointsFact()) 76 + ->setKey('tasks.open-points.score.project'), 77 + id(new PhabricatorPointsFact()) 78 + ->setKey('tasks.open-points.assign.owner'), 10 79 ); 11 80 } 12 81 ··· 15 84 } 16 85 17 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'); 97 + 98 + $old_open = false; 99 + $old_points = 0; 100 + $old_owner = null; 101 + $project_map = array(); 102 + $object_phid = $object->getPHID(); 103 + $is_create = true; 104 + 105 + $specs = array(); 18 106 $datapoints = array(); 107 + foreach ($xactions as $xaction_group) { 108 + $add_projects = array(); 109 + $rem_projects = array(); 19 110 20 - $phid = $object->getPHID(); 21 - $type = phid_get_type($phid); 111 + $new_open = $old_open; 112 + $new_points = $old_points; 113 + $new_owner = $old_owner; 22 114 23 - $datapoint = $this->getFact('tasks.count.open') 24 - ->newDatapoint(); 115 + // TODO: Actually group concurrent transactions. 116 + $xaction_group = array($xaction_group); 117 + 118 + $group_epoch = last($xaction_group)->getDateCreated(); 119 + foreach ($xaction_group as $xaction) { 120 + $old_value = $xaction->getOldValue(); 121 + $new_value = $xaction->getNewValue(); 122 + switch ($xaction->getTransactionType()) { 123 + case ManiphestTaskStatusTransaction::TRANSACTIONTYPE: 124 + $new_open = !ManiphestTaskStatus::isClosedStatus($new_value); 125 + break; 126 + case ManiphestTaskMergedIntoTransaction::TRANSACTIONTYPE: 127 + // When a task is merged into another task, it is changed to a 128 + // closed status without generating a separate status transaction. 129 + $new_open = false; 130 + break; 131 + case ManiphestTaskPointsTransaction::TRANSACTIONTYPE: 132 + $new_points = (int)$xaction->getNewValue(); 133 + break; 134 + case ManiphestTaskOwnerTransaction::TRANSACTIONTYPE: 135 + $new_owner = $xaction->getNewValue(); 136 + break; 137 + case PhabricatorTransactions::TYPE_EDGE: 138 + $edge_type = $xaction->getMetadataValue('edge:type'); 139 + switch ($edge_type) { 140 + case PhabricatorProjectObjectHasProjectEdgeType::EDGECONST: 141 + $record = PhabricatorEdgeChangeRecord::newFromTransaction( 142 + $xaction); 143 + $add_projects += array_fuse($record->getAddedPHIDs()); 144 + $rem_projects += array_fuse($record->getRemovedPHIDs()); 145 + break; 146 + } 147 + break; 148 + } 149 + } 150 + 151 + // If a project was both added and removed, moot it. 152 + $mix_projects = array_intersect_key($add_projects, $rem_projects); 153 + $add_projects = array_diff_key($add_projects, $mix_projects); 154 + $rem_projects = array_diff_key($rem_projects, $mix_projects); 155 + 156 + $project_sets = array( 157 + array( 158 + 'phids' => $rem_projects, 159 + 'scale' => -1, 160 + ), 161 + array( 162 + 'phids' => $add_projects, 163 + 'scale' => 1, 164 + ), 165 + ); 25 166 26 - $datapoints[] = $datapoint 27 - ->setObjectPHID($phid) 28 - ->setValue(1) 29 - ->setEpoch($object->getDateCreated()); 167 + if ($is_create) { 168 + $action = 'create'; 169 + $action_points = $new_points; 170 + } else { 171 + $action = 'assign'; 172 + $action_points = $old_points; 173 + } 174 + 175 + foreach ($project_sets as $project_set) { 176 + $scale = $project_set['scale']; 177 + foreach ($project_set['phids'] as $project_phid) { 178 + if ($old_open) { 179 + $specs[] = array( 180 + "tasks.open-count.{$action}.project", 181 + 1 * $scale, 182 + $project_phid, 183 + ); 184 + 185 + $specs[] = array( 186 + "tasks.open-points.{$action}.project", 187 + $action_points * $scale, 188 + $project_phid, 189 + ); 190 + } 191 + 192 + $specs[] = array( 193 + "tasks.count.{$action}.project", 194 + 1 * $scale, 195 + $project_phid, 196 + ); 197 + 198 + $specs[] = array( 199 + "tasks.points.{$action}.project", 200 + $action_points * $scale, 201 + $project_phid, 202 + ); 203 + 204 + if ($scale < 0) { 205 + unset($project_map[$project_phid]); 206 + } else { 207 + $project_map[$project_phid] = $project_phid; 208 + } 209 + } 210 + } 211 + 212 + if ($new_owner !== $old_owner) { 213 + $owner_sets = array( 214 + array( 215 + 'phid' => $old_owner, 216 + 'scale' => -1, 217 + ), 218 + array( 219 + 'phid' => $new_owner, 220 + 'scale' => 1, 221 + ), 222 + ); 223 + 224 + foreach ($owner_sets as $owner_set) { 225 + $owner_phid = $owner_set['phid']; 226 + if ($owner_phid === null) { 227 + continue; 228 + } 229 + 230 + if ($old_open) { 231 + $specs[] = array( 232 + "tasks.open-count.{$action}.owner", 233 + 1 * $scale, 234 + $owner_phid, 235 + ); 236 + 237 + $specs[] = array( 238 + "tasks.open-points.{$action}.owner", 239 + $action_points * $scale, 240 + $owner_phid, 241 + ); 242 + } 243 + 244 + $specs[] = array( 245 + "tasks.count.{$action}.owner", 246 + 1 * $scale, 247 + $owner_phid, 248 + ); 249 + 250 + $specs[] = array( 251 + "tasks.points.{$action}.owner", 252 + $action_points * $scale, 253 + $owner_phid, 254 + ); 255 + } 256 + 257 + $old_owner = $new_owner; 258 + } 259 + 260 + if ($is_create) { 261 + $specs[] = array( 262 + 'tasks.count.create', 263 + 1, 264 + ); 265 + $specs[] = array( 266 + 'tasks.points.create', 267 + $new_points, 268 + ); 269 + 270 + if ($new_open) { 271 + $specs[] = array( 272 + 'tasks.open-count.create', 273 + 1, 274 + ); 275 + $specs[] = array( 276 + 'tasks.open-points.create', 277 + $new_points, 278 + ); 279 + } 280 + } else if ($new_open !== $old_open) { 281 + if ($new_open) { 282 + $scale = 1; 283 + } else { 284 + $scale = -1; 285 + } 286 + 287 + $specs[] = array( 288 + 'tasks.open-count.status', 289 + 1 * $scale, 290 + ); 291 + 292 + $specs[] = array( 293 + 'tasks.open-points.status', 294 + $action_points * $scale, 295 + ); 296 + 297 + if ($new_owner !== null) { 298 + $specs[] = array( 299 + 'tasks.open-count.status.owner', 300 + 1 * $scale, 301 + $new_owner, 302 + ); 303 + $specs[] = array( 304 + 'tasks.open-points.status.owner', 305 + $action_points * $scale, 306 + $new_owner, 307 + ); 308 + } 309 + 310 + foreach ($project_map as $project_phid) { 311 + $specs[] = array( 312 + 'tasks.open-count.status.project', 313 + 1 * $scale, 314 + $project_phid, 315 + ); 316 + $specs[] = array( 317 + 'tasks.open-points.status.project', 318 + $action_points * $scale, 319 + $new_owner, 320 + ); 321 + } 322 + 323 + $old_open = $new_open; 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 + $old_points = $new_points; 376 + } 377 + 378 + foreach ($specs as $spec) { 379 + $spec_key = $spec[0]; 380 + $spec_value = $spec[1]; 381 + 382 + $datapoint = $this->getFact($spec_key) 383 + ->newDatapoint(); 384 + 385 + $datapoint 386 + ->setObjectPHID($object_phid) 387 + ->setValue($spec_value) 388 + ->setEpoch($group_epoch); 389 + 390 + if (isset($spec[2])) { 391 + $datapoint->setDimensionPHID($spec[2]); 392 + } 393 + 394 + $datapoints[] = $datapoint; 395 + } 396 + 397 + $specs = array(); 398 + $is_create = false; 399 + } 30 400 31 401 return $datapoints; 32 402 }
+9
src/applications/fact/fact/PhabricatorCountFact.php
··· 1 + <?php 2 + 3 + final class PhabricatorCountFact extends PhabricatorFact { 4 + 5 + protected function newTemplateDatapoint() { 6 + return new PhabricatorFactIntDatapoint(); 7 + } 8 + 9 + }
+7 -3
src/applications/fact/storage/PhabricatorFactDimension.php
··· 6 6 7 7 final public function newDimensionID($key) { 8 8 $map = $this->newDimensionMap(array($key)); 9 - return $map[$key]; 9 + return idx($map, $key); 10 10 } 11 11 12 - final public function newDimensionMap(array $keys) { 12 + final public function newDimensionMap(array $keys, $create = false) { 13 13 if (!$keys) { 14 14 return array(); 15 15 } ··· 40 40 return $map; 41 41 } 42 42 43 + if (!$create) { 44 + return $map; 45 + } 46 + 43 47 $sql = array(); 44 48 foreach ($need as $key) { 45 49 $sql[] = qsprintf( ··· 66 70 $need); 67 71 $rows = ipull($rows, 'id', $column); 68 72 69 - foreach ($keys as $key) { 73 + foreach ($need as $key) { 70 74 if (isset($rows[$key])) { 71 75 $map[$key] = (int)$rows[$key]; 72 76 } else {
+5
src/applications/transactions/storage/PhabricatorApplicationTransaction.php
··· 260 260 return $this->oldValueHasBeenSet; 261 261 } 262 262 263 + public function newChronologicalSortVector() { 264 + return id(new PhutilSortVector()) 265 + ->addInt((int)$this->getDateCreated()) 266 + ->addInt((int)$this->getID()); 267 + } 263 268 264 269 /* -( Rendering )---------------------------------------------------------- */ 265 270