diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -2550,6 +2550,7 @@ 'PhrequentSearchEngine' => 'applications/phrequent/query/PhrequentSearchEngine.php', 'PhrequentTimeBlock' => 'applications/phrequent/storage/PhrequentTimeBlock.php', 'PhrequentTimeBlockTestCase' => 'applications/phrequent/storage/__tests__/PhrequentTimeBlockTestCase.php', + 'PhrequentTimeSlices' => 'applications/phrequent/storage/PhrequentTimeSlices.php', 'PhrequentTrackController' => 'applications/phrequent/controller/PhrequentTrackController.php', 'PhrequentTrackableInterface' => 'applications/phrequent/interface/PhrequentTrackableInterface.php', 'PhrequentTrackingEditor' => 'applications/phrequent/editor/PhrequentTrackingEditor.php', @@ -5521,6 +5522,7 @@ 'PhrequentSearchEngine' => 'PhabricatorApplicationSearchEngine', 'PhrequentTimeBlock' => 'Phobject', 'PhrequentTimeBlockTestCase' => 'PhabricatorTestCase', + 'PhrequentTimeSlices' => 'Phobject', 'PhrequentTrackController' => 'PhrequentController', 'PhrequentTrackingEditor' => 'PhabricatorEditor', 'PhrequentUIEventListener' => 'PhabricatorEventListener', diff --git a/src/applications/phrequent/conduit/ConduitAPI_phrequent_tracking_Method.php b/src/applications/phrequent/conduit/ConduitAPI_phrequent_tracking_Method.php --- a/src/applications/phrequent/conduit/ConduitAPI_phrequent_tracking_Method.php +++ b/src/applications/phrequent/conduit/ConduitAPI_phrequent_tracking_Method.php @@ -31,6 +31,7 @@ $times = id(new PhrequentUserTimeQuery()) ->setViewer($user) ->needPreemptingEvents(true) + ->withEnded(PhrequentUserTimeQuery::ENDED_NO) ->withUserPHIDs(array($user->getPHID())) ->execute(); diff --git a/src/applications/phrequent/query/PhrequentUserTimeQuery.php b/src/applications/phrequent/query/PhrequentUserTimeQuery.php --- a/src/applications/phrequent/query/PhrequentUserTimeQuery.php +++ b/src/applications/phrequent/query/PhrequentUserTimeQuery.php @@ -199,7 +199,7 @@ $u_start = $u_event->getDateStarted(); $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)) { $select[] = $u_event; } diff --git a/src/applications/phrequent/storage/PhrequentTimeBlock.php b/src/applications/phrequent/storage/PhrequentTimeBlock.php --- a/src/applications/phrequent/storage/PhrequentTimeBlock.php +++ b/src/applications/phrequent/storage/PhrequentTimeBlock.php @@ -10,17 +10,16 @@ } public function getTimeSpentOnObject($phid, $now) { - $ranges = idx($this->getObjectTimeRanges($now), $phid, array()); + $slices = idx($this->getObjectTimeRanges(), $phid); - $sum = 0; - foreach ($ranges as $range) { - $sum += ($range[1] - $range[0]); + if (!$slices) { + return null; } - return $sum; + return $slices->getDuration($now); } - public function getObjectTimeRanges($now) { + public function getObjectTimeRanges() { $ranges = array(); $range_start = time(); @@ -29,6 +28,7 @@ } $object_ranges = array(); + $object_ongoing = array(); foreach ($this->events as $event) { // First, convert each event's preempting stack into a linear timeline @@ -42,14 +42,16 @@ ); $timeline[] = array( 'event' => $event, - 'at' => (int)nonempty($event->getDateEnded(), $now), + 'at' => (int)nonempty($event->getDateEnded(), PHP_INT_MAX), 'type' => 'end', ); $base_phid = $event->getObjectPHID(); + if (!$event->getDateEnded()) { + $object_ongoing[$base_phid] = true; + } $preempts = $event->getPreemptingEvents(); - foreach ($preempts as $preempt) { $same_object = ($preempt->getObjectPHID() == $base_phid); $timeline[] = array( @@ -59,7 +61,7 @@ ); $timeline[] = array( 'event' => $preempt, - 'at' => (int)nonempty($preempt->getDateEnded(), $now), + 'at' => (int)nonempty($preempt->getDateEnded(), PHP_INT_MAX), 'type' => $same_object ? 'end' : 'pop', ); } @@ -89,7 +91,6 @@ $stratum = null; $strata = array(); - $ranges = array(); foreach ($timeline as $timeline_event) { $id = $timeline_event['event']->getID(); @@ -173,15 +174,39 @@ } } + // 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; } - // 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) { $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; } @@ -189,33 +214,22 @@ * Returns the current list of work. */ public function getCurrentWorkStack($now, $include_inactive = false) { - $ranges = $this->getObjectTimeRanges($now); + $ranges = $this->getObjectTimeRanges(); $results = array(); - foreach ($ranges as $phid => $blocks) { - $total = 0; - foreach ($blocks as $block) { - $total += $block[1] - $block[0]; - } - - $type = 'inactive'; - foreach ($blocks as $block) { - if ($block[1] === $now) { - if ($block[0] === $block[1]) { - $type = 'suspended'; - } else { - $type = 'active'; - } - break; + $active = null; + foreach ($ranges as $phid => $slices) { + if (!$include_inactive) { + if (!$slices->getIsOngoing()) { + continue; } } - if ($include_inactive || $type !== 'inactive') { - $results[] = array( - 'phid' => $phid, - 'time' => $total, - 'type' => $type); - } + $results[] = array( + 'phid' => $phid, + 'time' => $slices->getDuration($now), + 'ongoing' => $slices->getIsOngoing(), + ); } return $results; diff --git a/src/applications/phrequent/storage/PhrequentTimeSlices.php b/src/applications/phrequent/storage/PhrequentTimeSlices.php new file mode 100644 --- /dev/null +++ b/src/applications/phrequent/storage/PhrequentTimeSlices.php @@ -0,0 +1,37 @@ +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; + } + +} diff --git a/src/applications/phrequent/storage/__tests__/PhrequentTimeBlockTestCase.php b/src/applications/phrequent/storage/__tests__/PhrequentTimeBlockTestCase.php --- a/src/applications/phrequent/storage/__tests__/PhrequentTimeBlockTestCase.php +++ b/src/applications/phrequent/storage/__tests__/PhrequentTimeBlockTestCase.php @@ -86,7 +86,9 @@ $block = new PhrequentTimeBlock(array($event)); - $ranges = $block->getObjectTimeRanges(1800); + $ranges = $block->getObjectTimeRanges(); + $ranges = $this->reduceRanges($ranges); + $this->assertEqual( array( 'T1' => array( @@ -107,7 +109,9 @@ $block = new PhrequentTimeBlock(array($event)); - $ranges = $block->getObjectTimeRanges(1000); + $ranges = $block->getObjectTimeRanges(); + $ranges = $this->reduceRanges($ranges); + $this->assertEqual( array( 'T2' => array( @@ -150,7 +154,9 @@ $block = new PhrequentTimeBlock(array($event)); - $ranges = $block->getObjectTimeRanges(1800); + $ranges = $block->getObjectTimeRanges(); + $ranges = $this->reduceRanges($ranges); + $this->assertEqual( array( 'T1' => array( @@ -172,7 +178,8 @@ $block = new PhrequentTimeBlock(array($event)); - $ranges = $block->getObjectTimeRanges(1000); + $ranges = $block->getObjectTimeRanges(); + $ranges = $this->reduceRanges($ranges); $this->assertEqual( array( @@ -198,7 +205,8 @@ $block = new PhrequentTimeBlock(array($event)); - $ranges = $block->getObjectTimeRanges(1000); + $ranges = $block->getObjectTimeRanges(); + $ranges = $this->reduceRanges($ranges); $this->assertEqual( array( @@ -213,6 +221,67 @@ $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) { static $id = 0; @@ -223,4 +292,14 @@ ->setDateEnded($end_time); } + private function reduceRanges(array $ranges) { + $results = array(); + + foreach ($ranges as $phid => $slices) { + $results[$phid] = $slices->getRanges(); + } + + return $results; + } + }