diff --git a/src/applications/phrequent/query/PhrequentSearchEngine.php b/src/applications/phrequent/query/PhrequentSearchEngine.php index c45fd4a290..0569c93822 100644 --- a/src/applications/phrequent/query/PhrequentSearchEngine.php +++ b/src/applications/phrequent/query/PhrequentSearchEngine.php @@ -1,203 +1,201 @@ getParameter('limit', 1000); } public function buildSavedQueryFromRequest(AphrontRequest $request) { $saved = new PhabricatorSavedQuery(); $saved->setParameter( 'userPHIDs', $this->readUsersFromRequest($request, 'users')); $saved->setParameter('ended', $request->getStr('ended')); $saved->setParameter('order', $request->getStr('order')); return $saved; } public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) { - $query = id(new PhrequentUserTimeQuery()); + $query = id(new PhrequentUserTimeQuery()) + ->needPreemptingEvents(true); $user_phids = $saved->getParameter('userPHIDs'); if ($user_phids) { $query->withUserPHIDs($user_phids); } $ended = $saved->getParameter('ended'); if ($ended != null) { $query->withEnded($ended); } $order = $saved->getParameter('order'); if ($order != null) { $query->setOrder($order); } return $query; } public function buildSearchForm( AphrontFormView $form, PhabricatorSavedQuery $saved_query) { $user_phids = $saved_query->getParameter('userPHIDs', array()); $ended = $saved_query->getParameter( 'ended', PhrequentUserTimeQuery::ENDED_ALL); $order = $saved_query->getParameter( 'order', PhrequentUserTimeQuery::ORDER_ENDED_DESC); $phids = array_merge($user_phids); $handles = id(new PhabricatorHandleQuery()) ->setViewer($this->requireViewer()) ->withPHIDs($phids) ->execute(); $form ->appendChild( id(new AphrontFormTokenizerControl()) ->setDatasource(new PhabricatorPeopleDatasource()) ->setName('users') ->setLabel(pht('Users')) ->setValue($handles)) ->appendChild( id(new AphrontFormSelectControl()) ->setLabel(pht('Ended')) ->setName('ended') ->setValue($ended) ->setOptions(PhrequentUserTimeQuery::getEndedSearchOptions())) ->appendChild( id(new AphrontFormSelectControl()) ->setLabel(pht('Order')) ->setName('order') ->setValue($order) ->setOptions(PhrequentUserTimeQuery::getOrderSearchOptions())); } protected function getURI($path) { return '/phrequent/'.$path; } public function getBuiltinQueryNames() { return array( 'tracking' => pht('Currently Tracking'), 'all' => pht('All Tracked'), ); } public function buildSavedQueryFromBuiltin($query_key) { $query = $this->newSavedQuery(); $query->setQueryKey($query_key); switch ($query_key) { case 'all': return $query ->setParameter('order', PhrequentUserTimeQuery::ORDER_ENDED_DESC); case 'tracking': return $query ->setParameter('ended', PhrequentUserTimeQuery::ENDED_NO) ->setParameter('order', PhrequentUserTimeQuery::ORDER_ENDED_DESC); } return parent::buildSavedQueryFromBuiltin($query_key); } protected function getRequiredHandlePHIDsForResultList( array $usertimes, PhabricatorSavedQuery $query) { return array_mergev( array( mpull($usertimes, 'getUserPHID'), mpull($usertimes, 'getObjectPHID'), )); } protected function renderResultList( array $usertimes, PhabricatorSavedQuery $query, array $handles) { assert_instances_of($usertimes, 'PhrequentUserTime'); $viewer = $this->requireViewer(); $view = id(new PHUIObjectItemListView()) ->setUser($viewer); foreach ($usertimes as $usertime) { $item = new PHUIObjectItemView(); - if ($usertime->getObjectPHID() === null) { $item->setHeader($usertime->getNote()); } else { $obj = $handles[$usertime->getObjectPHID()]; $item->setHeader($obj->getLinkName()); $item->setHref($obj->getURI()); } $item->setObject($usertime); $item->addByline( pht( 'Tracked: %s', $handles[$usertime->getUserPHID()]->renderLink())); $started_date = phabricator_date($usertime->getDateStarted(), $viewer); $item->addIcon('none', $started_date); - if ($usertime->getDateEnded() !== null) { - $time_spent = $usertime->getDateEnded() - $usertime->getDateStarted(); - $time_ended = phabricator_datetime($usertime->getDateEnded(), $viewer); - } else { - $time_spent = time() - $usertime->getDateStarted(); - } + $block = new PhrequentTimeBlock(array($usertime)); + $time_spent = $block->getTimeSpentOnObject( + $usertime->getObjectPHID(), + PhabricatorTime::getNow()); $time_spent = $time_spent == 0 ? 'none' : phutil_format_relative_time_detailed($time_spent); if ($usertime->getDateEnded() !== null) { $item->addAttribute( pht( 'Tracked %s', $time_spent)); $item->addAttribute( pht( 'Ended on %s', - $time_ended)); + phabricator_datetime($usertime->getDateEnded(), $viewer))); } else { $item->addAttribute( pht( 'Tracked %s so far', $time_spent)); if ($usertime->getObjectPHID() !== null && $usertime->getUserPHID() === $viewer->getPHID()) { $item->addAction( id(new PHUIListItemView()) ->setIcon('fa-stop') ->addSigil('phrequent-stop-tracking') ->setWorkflow(true) ->setRenderNameAsTooltip(true) ->setName(pht('Stop')) ->setHref( '/phrequent/track/stop/'. $usertime->getObjectPHID().'/')); } $item->setBarColor('green'); } $view->addItem($item); } return $view; } } diff --git a/src/applications/phrequent/query/PhrequentUserTimeQuery.php b/src/applications/phrequent/query/PhrequentUserTimeQuery.php index c173741c7a..2bb4674ddd 100644 --- a/src/applications/phrequent/query/PhrequentUserTimeQuery.php +++ b/src/applications/phrequent/query/PhrequentUserTimeQuery.php @@ -1,309 +1,330 @@ userPHIDs = $user_phids; return $this; } public function withObjectPHIDs($object_phids) { $this->objectPHIDs = $object_phids; return $this; } public function withEnded($ended) { $this->ended = $ended; return $this; } public function setOrder($order) { $this->order = $order; return $this; } public function needPreemptingEvents($need_events) { $this->needPreemptingEvents = $need_events; return $this; } private function buildWhereClause(AphrontDatabaseConnection $conn) { $where = array(); if ($this->userPHIDs) { $where[] = qsprintf( $conn, 'userPHID IN (%Ls)', $this->userPHIDs); } if ($this->objectPHIDs) { $where[] = qsprintf( $conn, 'objectPHID IN (%Ls)', $this->objectPHIDs); } switch ($this->ended) { case self::ENDED_ALL: break; case self::ENDED_YES: $where[] = qsprintf( $conn, 'dateEnded IS NOT NULL'); break; case self::ENDED_NO: $where[] = qsprintf( $conn, 'dateEnded IS NULL'); break; default: throw new Exception("Unknown ended '{$this->ended}'!"); } $where[] = $this->buildPagingClause($conn); return $this->formatWhereClause($where); } protected function getPagingColumn() { switch ($this->order) { case self::ORDER_ID_ASC: case self::ORDER_ID_DESC: return 'id'; case self::ORDER_STARTED_ASC: case self::ORDER_STARTED_DESC: return 'dateStarted'; case self::ORDER_ENDED_ASC: case self::ORDER_ENDED_DESC: return 'dateEnded'; case self::ORDER_DURATION_ASC: case self::ORDER_DURATION_DESC: return 'COALESCE(dateEnded, UNIX_TIMESTAMP()) - dateStarted'; default: throw new Exception("Unknown order '{$this->order}'!"); } } protected function getPagingValue($result) { switch ($this->order) { case self::ORDER_ID_ASC: case self::ORDER_ID_DESC: return $result->getID(); case self::ORDER_STARTED_ASC: case self::ORDER_STARTED_DESC: return $result->getDateStarted(); case self::ORDER_ENDED_ASC: case self::ORDER_ENDED_DESC: return $result->getDateEnded(); case self::ORDER_DURATION_ASC: case self::ORDER_DURATION_DESC: return ($result->getDateEnded() || time()) - $result->getDateStarted(); default: throw new Exception("Unknown order '{$this->order}'!"); } } protected function getReversePaging() { switch ($this->order) { case self::ORDER_ID_ASC: case self::ORDER_STARTED_ASC: case self::ORDER_ENDED_ASC: case self::ORDER_DURATION_ASC: return true; case self::ORDER_ID_DESC: case self::ORDER_STARTED_DESC: case self::ORDER_ENDED_DESC: case self::ORDER_DURATION_DESC: return false; default: throw new Exception("Unknown order '{$this->order}'!"); } } protected function loadPage() { $usertime = new PhrequentUserTime(); $conn = $usertime->establishConnection('r'); $data = queryfx_all( $conn, 'SELECT usertime.* FROM %T usertime %Q %Q %Q', $usertime->getTableName(), $this->buildWhereClause($conn), $this->buildOrderClause($conn), $this->buildLimitClause($conn)); return $usertime->loadAllFromArray($data); } protected function didFilterPage(array $page) { if ($this->needPreemptingEvents) { $usertime = new PhrequentUserTime(); $conn_r = $usertime->establishConnection('r'); $preempt = array(); foreach ($page as $event) { $preempt[] = qsprintf( $conn_r, '(userPHID = %s AND (dateStarted BETWEEN %d AND %d) AND (dateEnded IS NULL OR dateEnded > %d))', $event->getUserPHID(), $event->getDateStarted(), nonempty($event->getDateEnded(), PhabricatorTime::getNow()), $event->getDateStarted()); } $preempting_events = queryfx_all( $conn_r, 'SELECT * FROM %T WHERE %Q ORDER BY dateStarted ASC, id ASC', $usertime->getTableName(), implode(' OR ', $preempt)); $preempting_events = $usertime->loadAllFromArray($preempting_events); $preempting_events = mgroup($preempting_events, 'getUserPHID'); foreach ($page as $event) { $e_start = $event->getDateStarted(); $e_end = $event->getDateEnded(); $select = array(); $user_events = idx($preempting_events, $event->getUserPHID(), array()); foreach ($user_events as $u_event) { if ($u_event->getID() == $event->getID()) { // Don't allow an event to preempt itself. continue; } $u_start = $u_event->getDateStarted(); $u_end = $u_event->getDateEnded(); - if (($u_start >= $e_start) && - ($u_end === null || $u_end > $e_start)) { - $select[] = $u_event; + if ($u_start < $e_start) { + // This event started before our event started, so it's not + // preempting us. + continue; + } + + if ($u_start == $e_start) { + if ($u_event->getID() < $event->getID()) { + // This event started at the same time as our event started, + // but has a lower ID, so it's not preempting us. + continue; + } + } + + if (($e_end !== null) && ($u_start > $e_end)) { + // Our event has ended, and this event started after it ended. + continue; } + + if (($u_end !== null) && ($u_end < $e_start)) { + // This event ended before our event began. + continue; + } + + $select[] = $u_event; } $event->attachPreemptingEvents($select); } } return $page; } /* -( Helper Functions ) --------------------------------------------------- */ public static function getEndedSearchOptions() { return array( self::ENDED_ALL => pht('All'), self::ENDED_NO => pht('No'), self::ENDED_YES => pht('Yes')); } public static function getOrderSearchOptions() { return array( self::ORDER_STARTED_ASC => pht('by furthest start date'), self::ORDER_STARTED_DESC => pht('by nearest start date'), self::ORDER_ENDED_ASC => pht('by furthest end date'), self::ORDER_ENDED_DESC => pht('by nearest end date'), self::ORDER_DURATION_ASC => pht('by smallest duration'), self::ORDER_DURATION_DESC => pht('by largest duration')); } public static function getUserTotalObjectsTracked( PhabricatorUser $user) { $usertime_dao = new PhrequentUserTime(); $conn = $usertime_dao->establishConnection('r'); $count = queryfx_one( $conn, 'SELECT COUNT(usertime.id) N FROM %T usertime '. 'WHERE usertime.userPHID = %s '. 'AND usertime.dateEnded IS NULL', $usertime_dao->getTableName(), $user->getPHID()); return $count['N']; } public static function isUserTrackingObject( PhabricatorUser $user, $phid) { $usertime_dao = new PhrequentUserTime(); $conn = $usertime_dao->establishConnection('r'); $count = queryfx_one( $conn, 'SELECT COUNT(usertime.id) N FROM %T usertime '. 'WHERE usertime.userPHID = %s '. 'AND usertime.objectPHID = %s '. 'AND usertime.dateEnded IS NULL', $usertime_dao->getTableName(), $user->getPHID(), $phid); return $count['N'] > 0; } public static function getUserTimeSpentOnObject( PhabricatorUser $user, $phid) { $usertime_dao = new PhrequentUserTime(); $conn = $usertime_dao->establishConnection('r'); // First calculate all the time spent where the // usertime blocks have ended. $sum_ended = queryfx_one( $conn, 'SELECT SUM(usertime.dateEnded - usertime.dateStarted) N '. 'FROM %T usertime '. 'WHERE usertime.userPHID = %s '. 'AND usertime.objectPHID = %s '. 'AND usertime.dateEnded IS NOT NULL', $usertime_dao->getTableName(), $user->getPHID(), $phid); // Now calculate the time spent where the usertime // blocks have not yet ended. $sum_not_ended = queryfx_one( $conn, 'SELECT SUM(UNIX_TIMESTAMP() - usertime.dateStarted) N '. 'FROM %T usertime '. 'WHERE usertime.userPHID = %s '. 'AND usertime.objectPHID = %s '. 'AND usertime.dateEnded IS NULL', $usertime_dao->getTableName(), $user->getPHID(), $phid); return $sum_ended['N'] + $sum_not_ended['N']; } public function getQueryApplicationClass() { return 'PhabricatorPhrequentApplication'; } }