mirror of
https://we.phorge.it/source/phorge.git
synced 2024-12-25 06:50:55 +01:00
Emit more usable results from phrequent.tracking
Summary: I think this pretty much does what you would expect? The "active" item is always at the top of the stack. Test Plan: Called `phrequent.tracking` and got reasonable results. Reviewers: hach-que Reviewed By: hach-que Subscribers: epriestley Differential Revision: https://secure.phabricator.com/D9939
This commit is contained in:
parent
ae97617e36
commit
ab3c17a2cd
6 changed files with 173 additions and 40 deletions
|
@ -2550,6 +2550,7 @@ phutil_register_library_map(array(
|
||||||
'PhrequentSearchEngine' => 'applications/phrequent/query/PhrequentSearchEngine.php',
|
'PhrequentSearchEngine' => 'applications/phrequent/query/PhrequentSearchEngine.php',
|
||||||
'PhrequentTimeBlock' => 'applications/phrequent/storage/PhrequentTimeBlock.php',
|
'PhrequentTimeBlock' => 'applications/phrequent/storage/PhrequentTimeBlock.php',
|
||||||
'PhrequentTimeBlockTestCase' => 'applications/phrequent/storage/__tests__/PhrequentTimeBlockTestCase.php',
|
'PhrequentTimeBlockTestCase' => 'applications/phrequent/storage/__tests__/PhrequentTimeBlockTestCase.php',
|
||||||
|
'PhrequentTimeSlices' => 'applications/phrequent/storage/PhrequentTimeSlices.php',
|
||||||
'PhrequentTrackController' => 'applications/phrequent/controller/PhrequentTrackController.php',
|
'PhrequentTrackController' => 'applications/phrequent/controller/PhrequentTrackController.php',
|
||||||
'PhrequentTrackableInterface' => 'applications/phrequent/interface/PhrequentTrackableInterface.php',
|
'PhrequentTrackableInterface' => 'applications/phrequent/interface/PhrequentTrackableInterface.php',
|
||||||
'PhrequentTrackingEditor' => 'applications/phrequent/editor/PhrequentTrackingEditor.php',
|
'PhrequentTrackingEditor' => 'applications/phrequent/editor/PhrequentTrackingEditor.php',
|
||||||
|
@ -5521,6 +5522,7 @@ phutil_register_library_map(array(
|
||||||
'PhrequentSearchEngine' => 'PhabricatorApplicationSearchEngine',
|
'PhrequentSearchEngine' => 'PhabricatorApplicationSearchEngine',
|
||||||
'PhrequentTimeBlock' => 'Phobject',
|
'PhrequentTimeBlock' => 'Phobject',
|
||||||
'PhrequentTimeBlockTestCase' => 'PhabricatorTestCase',
|
'PhrequentTimeBlockTestCase' => 'PhabricatorTestCase',
|
||||||
|
'PhrequentTimeSlices' => 'Phobject',
|
||||||
'PhrequentTrackController' => 'PhrequentController',
|
'PhrequentTrackController' => 'PhrequentController',
|
||||||
'PhrequentTrackingEditor' => 'PhabricatorEditor',
|
'PhrequentTrackingEditor' => 'PhabricatorEditor',
|
||||||
'PhrequentUIEventListener' => 'PhabricatorEventListener',
|
'PhrequentUIEventListener' => 'PhabricatorEventListener',
|
||||||
|
|
|
@ -31,6 +31,7 @@ final class ConduitAPI_phrequent_tracking_Method
|
||||||
$times = id(new PhrequentUserTimeQuery())
|
$times = id(new PhrequentUserTimeQuery())
|
||||||
->setViewer($user)
|
->setViewer($user)
|
||||||
->needPreemptingEvents(true)
|
->needPreemptingEvents(true)
|
||||||
|
->withEnded(PhrequentUserTimeQuery::ENDED_NO)
|
||||||
->withUserPHIDs(array($user->getPHID()))
|
->withUserPHIDs(array($user->getPHID()))
|
||||||
->execute();
|
->execute();
|
||||||
|
|
||||||
|
|
|
@ -199,7 +199,7 @@ final class PhrequentUserTimeQuery
|
||||||
$u_start = $u_event->getDateStarted();
|
$u_start = $u_event->getDateStarted();
|
||||||
$u_end = $u_event->getDateEnded();
|
$u_end = $u_event->getDateEnded();
|
||||||
|
|
||||||
if (($u_start >= $e_start) && ($u_end <= $e_end) &&
|
if (($u_start >= $e_start) &&
|
||||||
($u_end === null || $u_end > $e_start)) {
|
($u_end === null || $u_end > $e_start)) {
|
||||||
$select[] = $u_event;
|
$select[] = $u_event;
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,17 +10,16 @@ final class PhrequentTimeBlock extends Phobject {
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getTimeSpentOnObject($phid, $now) {
|
public function getTimeSpentOnObject($phid, $now) {
|
||||||
$ranges = idx($this->getObjectTimeRanges($now), $phid, array());
|
$slices = idx($this->getObjectTimeRanges(), $phid);
|
||||||
|
|
||||||
$sum = 0;
|
if (!$slices) {
|
||||||
foreach ($ranges as $range) {
|
return null;
|
||||||
$sum += ($range[1] - $range[0]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $sum;
|
return $slices->getDuration($now);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getObjectTimeRanges($now) {
|
public function getObjectTimeRanges() {
|
||||||
$ranges = array();
|
$ranges = array();
|
||||||
|
|
||||||
$range_start = time();
|
$range_start = time();
|
||||||
|
@ -29,6 +28,7 @@ final class PhrequentTimeBlock extends Phobject {
|
||||||
}
|
}
|
||||||
|
|
||||||
$object_ranges = array();
|
$object_ranges = array();
|
||||||
|
$object_ongoing = array();
|
||||||
foreach ($this->events as $event) {
|
foreach ($this->events as $event) {
|
||||||
|
|
||||||
// First, convert each event's preempting stack into a linear timeline
|
// First, convert each event's preempting stack into a linear timeline
|
||||||
|
@ -42,14 +42,16 @@ final class PhrequentTimeBlock extends Phobject {
|
||||||
);
|
);
|
||||||
$timeline[] = array(
|
$timeline[] = array(
|
||||||
'event' => $event,
|
'event' => $event,
|
||||||
'at' => (int)nonempty($event->getDateEnded(), $now),
|
'at' => (int)nonempty($event->getDateEnded(), PHP_INT_MAX),
|
||||||
'type' => 'end',
|
'type' => 'end',
|
||||||
);
|
);
|
||||||
|
|
||||||
$base_phid = $event->getObjectPHID();
|
$base_phid = $event->getObjectPHID();
|
||||||
|
if (!$event->getDateEnded()) {
|
||||||
|
$object_ongoing[$base_phid] = true;
|
||||||
|
}
|
||||||
|
|
||||||
$preempts = $event->getPreemptingEvents();
|
$preempts = $event->getPreemptingEvents();
|
||||||
|
|
||||||
foreach ($preempts as $preempt) {
|
foreach ($preempts as $preempt) {
|
||||||
$same_object = ($preempt->getObjectPHID() == $base_phid);
|
$same_object = ($preempt->getObjectPHID() == $base_phid);
|
||||||
$timeline[] = array(
|
$timeline[] = array(
|
||||||
|
@ -59,7 +61,7 @@ final class PhrequentTimeBlock extends Phobject {
|
||||||
);
|
);
|
||||||
$timeline[] = array(
|
$timeline[] = array(
|
||||||
'event' => $preempt,
|
'event' => $preempt,
|
||||||
'at' => (int)nonempty($preempt->getDateEnded(), $now),
|
'at' => (int)nonempty($preempt->getDateEnded(), PHP_INT_MAX),
|
||||||
'type' => $same_object ? 'end' : 'pop',
|
'type' => $same_object ? 'end' : 'pop',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -89,7 +91,6 @@ final class PhrequentTimeBlock extends Phobject {
|
||||||
$stratum = null;
|
$stratum = null;
|
||||||
$strata = array();
|
$strata = array();
|
||||||
|
|
||||||
|
|
||||||
$ranges = array();
|
$ranges = array();
|
||||||
foreach ($timeline as $timeline_event) {
|
foreach ($timeline as $timeline_event) {
|
||||||
$id = $timeline_event['event']->getID();
|
$id = $timeline_event['event']->getID();
|
||||||
|
@ -173,15 +174,39 @@ final class PhrequentTimeBlock extends Phobject {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Filter out ranges with an indefinite start time. These occur when
|
||||||
|
// popping the stack when there are multiple ongoing events.
|
||||||
|
foreach ($ranges as $key => $range) {
|
||||||
|
if ($range[0] == PHP_INT_MAX) {
|
||||||
|
unset($ranges[$key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$object_ranges[$base_phid][] = $ranges;
|
$object_ranges[$base_phid][] = $ranges;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Finally, collapse all the ranges so we don't double-count time.
|
// Collapse all the ranges so we don't double-count time.
|
||||||
|
|
||||||
foreach ($object_ranges as $phid => $ranges) {
|
foreach ($object_ranges as $phid => $ranges) {
|
||||||
$object_ranges[$phid] = self::mergeTimeRanges(array_mergev($ranges));
|
$object_ranges[$phid] = self::mergeTimeRanges(array_mergev($ranges));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
foreach ($object_ranges as $phid => $ranges) {
|
||||||
|
foreach ($ranges as $key => $range) {
|
||||||
|
if ($range[1] == PHP_INT_MAX) {
|
||||||
|
$ranges[$key][1] = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$object_ranges[$phid] = new PhrequentTimeSlices(
|
||||||
|
$phid,
|
||||||
|
isset($object_ongoing[$phid]),
|
||||||
|
$ranges);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reorder the ranges to be more stack-like, so the first item is the
|
||||||
|
// top of the stack.
|
||||||
|
$object_ranges = array_reverse($object_ranges, $preserve_keys = true);
|
||||||
|
|
||||||
return $object_ranges;
|
return $object_ranges;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -189,33 +214,22 @@ final class PhrequentTimeBlock extends Phobject {
|
||||||
* Returns the current list of work.
|
* Returns the current list of work.
|
||||||
*/
|
*/
|
||||||
public function getCurrentWorkStack($now, $include_inactive = false) {
|
public function getCurrentWorkStack($now, $include_inactive = false) {
|
||||||
$ranges = $this->getObjectTimeRanges($now);
|
$ranges = $this->getObjectTimeRanges();
|
||||||
|
|
||||||
$results = array();
|
$results = array();
|
||||||
foreach ($ranges as $phid => $blocks) {
|
$active = null;
|
||||||
$total = 0;
|
foreach ($ranges as $phid => $slices) {
|
||||||
foreach ($blocks as $block) {
|
if (!$include_inactive) {
|
||||||
$total += $block[1] - $block[0];
|
if (!$slices->getIsOngoing()) {
|
||||||
}
|
continue;
|
||||||
|
|
||||||
$type = 'inactive';
|
|
||||||
foreach ($blocks as $block) {
|
|
||||||
if ($block[1] === $now) {
|
|
||||||
if ($block[0] === $block[1]) {
|
|
||||||
$type = 'suspended';
|
|
||||||
} else {
|
|
||||||
$type = 'active';
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($include_inactive || $type !== 'inactive') {
|
|
||||||
$results[] = array(
|
$results[] = array(
|
||||||
'phid' => $phid,
|
'phid' => $phid,
|
||||||
'time' => $total,
|
'time' => $slices->getDuration($now),
|
||||||
'type' => $type);
|
'ongoing' => $slices->getIsOngoing(),
|
||||||
}
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $results;
|
return $results;
|
||||||
|
|
37
src/applications/phrequent/storage/PhrequentTimeSlices.php
Normal file
37
src/applications/phrequent/storage/PhrequentTimeSlices.php
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
final class PhrequentTimeSlices extends Phobject {
|
||||||
|
|
||||||
|
private $objectPHID;
|
||||||
|
private $isOngoing;
|
||||||
|
private $ranges;
|
||||||
|
|
||||||
|
public function __construct($object_phid, $is_ongoing, array $ranges) {
|
||||||
|
$this->objectPHID = $object_phid;
|
||||||
|
$this->isOngoing = $is_ongoing;
|
||||||
|
$this->ranges = $ranges;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getObjectPHID() {
|
||||||
|
return $this->objectPHID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDuration($now) {
|
||||||
|
foreach ($this->ranges as $range) {
|
||||||
|
if ($range[1] === null) {
|
||||||
|
return $now - $range[0];
|
||||||
|
} else {
|
||||||
|
return $range[1] - $range[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getIsOngoing() {
|
||||||
|
return $this->isOngoing;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getRanges() {
|
||||||
|
return $this->ranges;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -86,7 +86,9 @@ final class PhrequentTimeBlockTestCase extends PhabricatorTestCase {
|
||||||
|
|
||||||
$block = new PhrequentTimeBlock(array($event));
|
$block = new PhrequentTimeBlock(array($event));
|
||||||
|
|
||||||
$ranges = $block->getObjectTimeRanges(1800);
|
$ranges = $block->getObjectTimeRanges();
|
||||||
|
$ranges = $this->reduceRanges($ranges);
|
||||||
|
|
||||||
$this->assertEqual(
|
$this->assertEqual(
|
||||||
array(
|
array(
|
||||||
'T1' => array(
|
'T1' => array(
|
||||||
|
@ -107,7 +109,9 @@ final class PhrequentTimeBlockTestCase extends PhabricatorTestCase {
|
||||||
|
|
||||||
$block = new PhrequentTimeBlock(array($event));
|
$block = new PhrequentTimeBlock(array($event));
|
||||||
|
|
||||||
$ranges = $block->getObjectTimeRanges(1000);
|
$ranges = $block->getObjectTimeRanges();
|
||||||
|
$ranges = $this->reduceRanges($ranges);
|
||||||
|
|
||||||
$this->assertEqual(
|
$this->assertEqual(
|
||||||
array(
|
array(
|
||||||
'T2' => array(
|
'T2' => array(
|
||||||
|
@ -150,7 +154,9 @@ final class PhrequentTimeBlockTestCase extends PhabricatorTestCase {
|
||||||
|
|
||||||
$block = new PhrequentTimeBlock(array($event));
|
$block = new PhrequentTimeBlock(array($event));
|
||||||
|
|
||||||
$ranges = $block->getObjectTimeRanges(1800);
|
$ranges = $block->getObjectTimeRanges();
|
||||||
|
$ranges = $this->reduceRanges($ranges);
|
||||||
|
|
||||||
$this->assertEqual(
|
$this->assertEqual(
|
||||||
array(
|
array(
|
||||||
'T1' => array(
|
'T1' => array(
|
||||||
|
@ -172,7 +178,8 @@ final class PhrequentTimeBlockTestCase extends PhabricatorTestCase {
|
||||||
|
|
||||||
$block = new PhrequentTimeBlock(array($event));
|
$block = new PhrequentTimeBlock(array($event));
|
||||||
|
|
||||||
$ranges = $block->getObjectTimeRanges(1000);
|
$ranges = $block->getObjectTimeRanges();
|
||||||
|
$ranges = $this->reduceRanges($ranges);
|
||||||
|
|
||||||
$this->assertEqual(
|
$this->assertEqual(
|
||||||
array(
|
array(
|
||||||
|
@ -198,7 +205,8 @@ final class PhrequentTimeBlockTestCase extends PhabricatorTestCase {
|
||||||
|
|
||||||
$block = new PhrequentTimeBlock(array($event));
|
$block = new PhrequentTimeBlock(array($event));
|
||||||
|
|
||||||
$ranges = $block->getObjectTimeRanges(1000);
|
$ranges = $block->getObjectTimeRanges();
|
||||||
|
$ranges = $this->reduceRanges($ranges);
|
||||||
|
|
||||||
$this->assertEqual(
|
$this->assertEqual(
|
||||||
array(
|
array(
|
||||||
|
@ -213,6 +221,67 @@ final class PhrequentTimeBlockTestCase extends PhabricatorTestCase {
|
||||||
$ranges);
|
$ranges);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testOngoing() {
|
||||||
|
$event = $this->newEvent('T1', 1, null);
|
||||||
|
$event->attachPreemptingEvents(array());
|
||||||
|
|
||||||
|
$block = new PhrequentTimeBlock(array($event));
|
||||||
|
|
||||||
|
$ranges = $block->getObjectTimeRanges();
|
||||||
|
$ranges = $this->reduceRanges($ranges);
|
||||||
|
|
||||||
|
$this->assertEqual(
|
||||||
|
array(
|
||||||
|
'T1' => array(
|
||||||
|
array(1, null),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
$ranges);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testOngoingInterrupted() {
|
||||||
|
$event = $this->newEvent('T1', 1, null);
|
||||||
|
$event->attachPreemptingEvents(
|
||||||
|
array(
|
||||||
|
$this->newEvent('T2', 100, 900),
|
||||||
|
));
|
||||||
|
|
||||||
|
$block = new PhrequentTimeBlock(array($event));
|
||||||
|
|
||||||
|
$ranges = $block->getObjectTimeRanges();
|
||||||
|
$ranges = $this->reduceRanges($ranges);
|
||||||
|
|
||||||
|
$this->assertEqual(
|
||||||
|
array(
|
||||||
|
'T1' => array(
|
||||||
|
array(1, 100),
|
||||||
|
array(900, null)
|
||||||
|
),
|
||||||
|
),
|
||||||
|
$ranges);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testOngoingPreempted() {
|
||||||
|
$event = $this->newEvent('T1', 1, null);
|
||||||
|
$event->attachPreemptingEvents(
|
||||||
|
array(
|
||||||
|
$this->newEvent('T2', 100, null),
|
||||||
|
));
|
||||||
|
|
||||||
|
$block = new PhrequentTimeBlock(array($event));
|
||||||
|
|
||||||
|
$ranges = $block->getObjectTimeRanges();
|
||||||
|
$ranges = $this->reduceRanges($ranges);
|
||||||
|
|
||||||
|
$this->assertEqual(
|
||||||
|
array(
|
||||||
|
'T1' => array(
|
||||||
|
array(1, 100),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
$ranges);
|
||||||
|
}
|
||||||
|
|
||||||
private function newEvent($object_phid, $start_time, $end_time) {
|
private function newEvent($object_phid, $start_time, $end_time) {
|
||||||
static $id = 0;
|
static $id = 0;
|
||||||
|
|
||||||
|
@ -223,4 +292,14 @@ final class PhrequentTimeBlockTestCase extends PhabricatorTestCase {
|
||||||
->setDateEnded($end_time);
|
->setDateEnded($end_time);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function reduceRanges(array $ranges) {
|
||||||
|
$results = array();
|
||||||
|
|
||||||
|
foreach ($ranges as $phid => $slices) {
|
||||||
|
$results[$phid] = $slices->getRanges();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $results;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue