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

Implemented Phrequent time tracking functionality.

Summary:
This differential implements Phrequent's time tracking
functionality for users and hooks it up to Maniphest. It
also includes a basic "Time Tracked" list for the Phrequent
application, where users can review what they've spent time
working on.

Test Plan:
Apply the patch and track some things in Maniphest. They
should appear in the "Time Tracked" view of Phrequent.

There is also a `phrequent.show-prompt` option which toggles
whether to display a prompt when tracking time. I'm unsure
of whether the prompt is useful or is more likely to cause
people to click "Track Time", go off and do the task and then
come back to the prompt still waiting for them to confirm. A
potential solution to the "accidentally clicking the button
and recording 2 seconds of time" might be to show a prompt
on stop if the total time is under 10 seconds, asking whether
the user wants to keep or discard the tracked time.

Reviewers: epriestley

Reviewed By: epriestley

CC: aran, Korvin

Maniphest Tasks: T2857

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

authored by

James Rhodes and committed by
epriestley
e555b902 02739ef0

+601 -2
+21
src/__phutil_library_map__.php
··· 703 703 'PhabricatorApplicationPhlux' => 'applications/phlux/application/PhabricatorApplicationPhlux.php', 704 704 'PhabricatorApplicationPholio' => 'applications/pholio/application/PhabricatorApplicationPholio.php', 705 705 'PhabricatorApplicationPhortune' => 'applications/phortune/application/PhabricatorApplicationPhortune.php', 706 + 'PhabricatorApplicationPhrequent' => 'applications/phrequent/application/PhabricatorApplicationPhrequent.php', 706 707 'PhabricatorApplicationPhriction' => 'applications/phriction/application/PhabricatorApplicationPhriction.php', 707 708 'PhabricatorApplicationPonder' => 'applications/ponder/application/PhabricatorApplicationPonder.php', 708 709 'PhabricatorApplicationProject' => 'applications/project/application/PhabricatorApplicationProject.php', ··· 1181 1182 'PhabricatorPhabricatorOAuthConfigOptions' => 'applications/config/option/PhabricatorPhabricatorOAuthConfigOptions.php', 1182 1183 'PhabricatorPhameConfigOptions' => 'applications/phame/config/PhabricatorPhameConfigOptions.php', 1183 1184 'PhabricatorPholioConfigOptions' => 'applications/pholio/config/PhabricatorPholioConfigOptions.php', 1185 + 'PhabricatorPhrequentConfigOptions' => 'applications/phrequent/config/PhabricatorPhrequentConfigOptions.php', 1184 1186 'PhabricatorPhrictionConfigOptions' => 'applications/phriction/config/PhabricatorPhrictionConfigOptions.php', 1185 1187 'PhabricatorPinboardItemView' => 'view/layout/PhabricatorPinboardItemView.php', 1186 1188 'PhabricatorPinboardView' => 'view/layout/PhabricatorPinboardView.php', ··· 1556 1558 'PhortunePurchase' => 'applications/phortune/storage/PhortunePurchase.php', 1557 1559 'PhortuneStripePaymentFormView' => 'applications/phortune/view/PhortuneStripePaymentFormView.php', 1558 1560 'PhortuneUtil' => 'applications/phortune/util/PhortuneUtil.php', 1561 + 'PhrequentController' => 'applications/phrequent/controller/PhrequentController.php', 1562 + 'PhrequentDAO' => 'applications/phrequent/storage/PhrequentDAO.php', 1563 + 'PhrequentListController' => 'applications/phrequent/controller/PhrequentListController.php', 1564 + 'PhrequentTrackController' => 'applications/phrequent/controller/PhrequentTrackController.php', 1565 + 'PhrequentTrackableInterface' => 'applications/phrequent/interface/PhrequentTrackableInterface.php', 1566 + 'PhrequentUIEventListener' => 'applications/phrequent/event/PhrequentUIEventListener.php', 1567 + 'PhrequentUserTime' => 'applications/phrequent/storage/PhrequentUserTime.php', 1568 + 'PhrequentUserTimeQuery' => 'applications/phrequent/query/PhrequentUserTimeQuery.php', 1559 1569 'PhrictionActionConstants' => 'applications/phriction/constants/PhrictionActionConstants.php', 1560 1570 'PhrictionChangeType' => 'applications/phriction/constants/PhrictionChangeType.php', 1561 1571 'PhrictionConstants' => 'applications/phriction/constants/PhrictionConstants.php', ··· 2052 2062 0 => 'DifferentialDAO', 2053 2063 1 => 'PhabricatorTokenReceiverInterface', 2054 2064 2 => 'PhabricatorPolicyInterface', 2065 + 3 => 'PhrequentTrackableInterface', 2055 2066 ), 2056 2067 'DifferentialRevisionCommentListView' => 'AphrontView', 2057 2068 'DifferentialRevisionCommentView' => 'AphrontView', ··· 2276 2287 1 => 'PhabricatorMarkupInterface', 2277 2288 2 => 'PhabricatorPolicyInterface', 2278 2289 3 => 'PhabricatorTokenReceiverInterface', 2290 + 4 => 'PhrequentTrackableInterface', 2279 2291 ), 2280 2292 'ManiphestTaskAuxiliaryStorage' => 'ManiphestDAO', 2281 2293 'ManiphestTaskDescriptionChangeController' => 'ManiphestController', ··· 2354 2366 'PhabricatorApplicationPhlux' => 'PhabricatorApplication', 2355 2367 'PhabricatorApplicationPholio' => 'PhabricatorApplication', 2356 2368 'PhabricatorApplicationPhortune' => 'PhabricatorApplication', 2369 + 'PhabricatorApplicationPhrequent' => 'PhabricatorApplication', 2357 2370 'PhabricatorApplicationPhriction' => 'PhabricatorApplication', 2358 2371 'PhabricatorApplicationPonder' => 'PhabricatorApplication', 2359 2372 'PhabricatorApplicationProject' => 'PhabricatorApplication', ··· 2821 2834 'PhabricatorPhabricatorOAuthConfigOptions' => 'PhabricatorApplicationConfigOptions', 2822 2835 'PhabricatorPhameConfigOptions' => 'PhabricatorApplicationConfigOptions', 2823 2836 'PhabricatorPholioConfigOptions' => 'PhabricatorApplicationConfigOptions', 2837 + 'PhabricatorPhrequentConfigOptions' => 'PhabricatorApplicationConfigOptions', 2824 2838 'PhabricatorPhrictionConfigOptions' => 'PhabricatorApplicationConfigOptions', 2825 2839 'PhabricatorPinboardItemView' => 'AphrontView', 2826 2840 'PhabricatorPinboardView' => 'AphrontView', ··· 3222 3236 'PhortuneProductViewController' => 'PhabricatorController', 3223 3237 'PhortunePurchase' => 'PhortuneDAO', 3224 3238 'PhortuneStripePaymentFormView' => 'AphrontView', 3239 + 'PhrequentController' => 'PhabricatorController', 3240 + 'PhrequentDAO' => 'PhabricatorLiskDAO', 3241 + 'PhrequentListController' => 'PhrequentController', 3242 + 'PhrequentTrackController' => 'PhabricatorApplicationsController', 3243 + 'PhrequentUIEventListener' => 'PhutilEventListener', 3244 + 'PhrequentUserTime' => 'PhrequentDAO', 3245 + 'PhrequentUserTimeQuery' => 'PhabricatorOffsetPagedQuery', 3225 3246 'PhrictionActionConstants' => 'PhrictionConstants', 3226 3247 'PhrictionChangeType' => 'PhrictionConstants', 3227 3248 'PhrictionContent' =>
+4 -1
src/applications/differential/storage/DifferentialRevision.php
··· 1 1 <?php 2 2 3 3 final class DifferentialRevision extends DifferentialDAO 4 - implements PhabricatorTokenReceiverInterface, PhabricatorPolicyInterface { 4 + implements 5 + PhabricatorTokenReceiverInterface, 6 + PhabricatorPolicyInterface, 7 + PhrequentTrackableInterface { 5 8 6 9 protected $title; 7 10 protected $originalTitle;
+2 -1
src/applications/maniphest/storage/ManiphestTask.php
··· 7 7 implements 8 8 PhabricatorMarkupInterface, 9 9 PhabricatorPolicyInterface, 10 - PhabricatorTokenReceiverInterface { 10 + PhabricatorTokenReceiverInterface, 11 + PhrequentTrackableInterface { 11 12 12 13 const MARKUP_FIELD_DESCRIPTION = 'markup:desc'; 13 14
+73
src/applications/phrequent/application/PhabricatorApplicationPhrequent.php
··· 1 + <?php 2 + 3 + final class PhabricatorApplicationPhrequent extends PhabricatorApplication { 4 + 5 + public function getShortDescription() { 6 + return pht('Track Time'); 7 + } 8 + 9 + public function getBaseURI() { 10 + return '/phrequent/'; 11 + } 12 + 13 + public function isBeta() { 14 + return true; 15 + } 16 + 17 + public function getIconName() { 18 + return 'phrequent'; 19 + } 20 + 21 + public function getApplicationGroup() { 22 + return self::GROUP_ORGANIZATION; 23 + } 24 + 25 + public function getApplicationOrder() { 26 + return 0.110; 27 + } 28 + 29 + public function getEventListeners() { 30 + return array( 31 + new PhrequentUIEventListener(), 32 + ); 33 + } 34 + 35 + public function getRoutes() { 36 + return array( 37 + '/phrequent/' => array( 38 + '' => 'PhrequentListController', 39 + 'track/(?P<verb>[a-z]+)/(?P<phid>[^/]+)/' 40 + => 'PhrequentTrackController' 41 + ), 42 + ); 43 + } 44 + 45 + public function loadStatus(PhabricatorUser $user) { 46 + $status = array(); 47 + 48 + // TODO: Show number of timers that are currently 49 + // running for a user. 50 + 51 + /* 52 + 53 + $query = id(new ManiphestTaskQuery()) 54 + ->withStatus(ManiphestTaskQuery::STATUS_OPEN) 55 + ->withOwners(array($user->getPHID())) 56 + ->setLimit(1) 57 + ->setCalculateRows(true); 58 + $query->execute(); 59 + 60 + $count = $query->getRowCount(); 61 + $type = PhabricatorApplicationStatusView::TYPE_WARNING; 62 + $status[] = id(new PhabricatorApplicationStatusView()) 63 + ->setType($type) 64 + ->setText(pht('%d Assigned Task(s)', $count)) 65 + ->setCount($count); 66 + 67 + */ 68 + 69 + return $status; 70 + } 71 + 72 + } 73 +
+18
src/applications/phrequent/config/PhabricatorPhrequentConfigOptions.php
··· 1 + <?php 2 + 3 + final class PhabricatorPhrequentConfigOptions 4 + extends PhabricatorApplicationConfigOptions { 5 + 6 + public function getName() { 7 + return pht("Phrequent"); 8 + } 9 + 10 + public function getDescription() { 11 + return pht("Configure Phrequent."); 12 + } 13 + 14 + public function getOptions() { 15 + return array(); 16 + } 17 + 18 + }
+15
src/applications/phrequent/controller/PhrequentController.php
··· 1 + <?php 2 + 3 + abstract class PhrequentController extends PhabricatorController { 4 + 5 + protected function buildNav($view) { 6 + $nav = new AphrontSideNavFilterView(); 7 + $nav->setBaseURI(new PhutilURI('/phrequent/')); 8 + 9 + $nav->addFilter('usertime', 'Time Tracked'); 10 + 11 + $nav->selectFilter($view); 12 + 13 + return $nav; 14 + } 15 + }
+146
src/applications/phrequent/controller/PhrequentListController.php
··· 1 + <?php 2 + 3 + final class PhrequentListController extends PhrequentController { 4 + 5 + public function processRequest() { 6 + $request = $this->getRequest(); 7 + $user = $request->getUser(); 8 + 9 + $nav = $this->buildNav('usertime'); 10 + 11 + $query = new PhrequentUserTimeQuery(); 12 + $query->setOrder(PhrequentUserTimeQuery::ORDER_ENDED); 13 + 14 + $pager = new AphrontPagerView(); 15 + $pager->setPageSize(500); 16 + $pager->setOffset($request->getInt('offset')); 17 + $pager->setURI($request->getRequestURI(), 'offset'); 18 + 19 + $logs = $query->executeWithOffsetPager($pager); 20 + 21 + $title = pht('Time Tracked'); 22 + 23 + $header = id(new PhabricatorHeaderView()) 24 + ->setHeader($title); 25 + 26 + $table = $this->buildTableView($logs); 27 + $table->appendChild($pager); 28 + 29 + $nav->appendChild( 30 + array( 31 + $header, 32 + $table, 33 + $pager, 34 + )); 35 + 36 + $crumbs = $this->buildApplicationCrumbs(); 37 + $crumbs->addCrumb( 38 + id(new PhabricatorCrumbView()) 39 + ->setName($title) 40 + ->setHref($this->getApplicationURI('/'))); 41 + $nav->setCrumbs($crumbs); 42 + 43 + return $this->buildApplicationPage( 44 + $nav, 45 + array( 46 + 'title' => $title, 47 + 'device' => true, 48 + )); 49 + 50 + } 51 + 52 + protected function buildTableView(array $usertimes) { 53 + assert_instances_of($usertimes, 'PhrequentUserTime'); 54 + 55 + $user = $this->getRequest()->getUser(); 56 + 57 + $phids = array(); 58 + foreach ($usertimes as $usertime) { 59 + $phids[] = $usertime->getUserPHID(); 60 + $phids[] = $usertime->getObjectPHID(); 61 + } 62 + $handles = $this->loadViewerHandles($phids); 63 + 64 + $rows = array(); 65 + foreach ($usertimes as $usertime) { 66 + 67 + if ($usertime->getDateEnded() !== null) { 68 + $time_spent = $usertime->getDateEnded() - $usertime->getDateStarted(); 69 + $time_ended = phabricator_date($usertime->getDateEnded(), $user); 70 + } else { 71 + $time_spent = time() - $usertime->getDateStarted(); 72 + $time_ended = phutil_tag( 73 + 'em', 74 + array(), 75 + pht('Ongoing')); 76 + } 77 + 78 + $usertime_user = $handles[$usertime->getUserPHID()]; 79 + $usertime_object = null; 80 + $object = null; 81 + if ($usertime->getObjectPHID() !== null) { 82 + $usertime_object = $handles[$usertime->getObjectPHID()]; 83 + $object = phutil_tag( 84 + 'a', 85 + array( 86 + 'href' => $usertime_object->getURI() 87 + ), 88 + $usertime_object->getFullName()); 89 + } else { 90 + $object = phutil_tag( 91 + 'em', 92 + array(), 93 + pht('None')); 94 + } 95 + 96 + $rows[] = array( 97 + $object, 98 + phutil_tag( 99 + 'a', 100 + array( 101 + 'href' => $usertime_user->getURI() 102 + ), 103 + $usertime_user->getFullName()), 104 + phabricator_date($usertime->getDateStarted(), $user), 105 + $time_ended, 106 + $time_spent == 0 ? 'none' : 107 + phabricator_format_relative_time_detailed($time_spent), 108 + $usertime->getNote() 109 + ); 110 + } 111 + 112 + $table = new AphrontTableView($rows); 113 + $table->setDeviceReadyTable(true); 114 + $table->setHeaders( 115 + array( 116 + 'Object', 117 + 'User', 118 + 'Started', 119 + 'Ended', 120 + 'Duration', 121 + 'Note' 122 + )); 123 + $table->setShortHeaders( 124 + array( 125 + 'O', 126 + 'U', 127 + 'S', 128 + 'E', 129 + 'D', 130 + 'Note', 131 + '', 132 + )); 133 + $table->setColumnClasses( 134 + array( 135 + '', 136 + '', 137 + '', 138 + '', 139 + '', 140 + 'wide' 141 + )); 142 + 143 + return $table; 144 + } 145 + 146 + }
+70
src/applications/phrequent/controller/PhrequentTrackController.php
··· 1 + <?php 2 + 3 + final class PhrequentTrackController 4 + extends PhabricatorApplicationsController { 5 + 6 + private $verb; 7 + private $phid; 8 + 9 + public function willProcessRequest(array $data) { 10 + $this->phid = $data['phid']; 11 + $this->verb = $data['verb']; 12 + } 13 + 14 + public function processRequest() { 15 + $request = $this->getRequest(); 16 + $user = $request->getUser(); 17 + 18 + if (!$this->isStartingTracking() && 19 + !$this->isStoppingTracking()) { 20 + throw new Exception('Unrecognized verb: ' . $this->verb); 21 + } 22 + 23 + if ($this->isStartingTracking()) { 24 + $this->startTracking($user, $this->phid); 25 + } else if ($this->isStoppingTracking()) { 26 + $this->stopTracking($user, $this->phid); 27 + } 28 + return id(new AphrontRedirectResponse()); 29 + } 30 + 31 + private function isStartingTracking() { 32 + return $this->verb === 'start'; 33 + } 34 + 35 + private function isStoppingTracking() { 36 + return $this->verb === 'stop'; 37 + } 38 + 39 + private function startTracking($user, $phid) { 40 + $usertime = new PhrequentUserTime(); 41 + $usertime->setDateStarted(time()); 42 + $usertime->setUserPHID($user->getPHID()); 43 + $usertime->setObjectPHID($phid); 44 + $usertime->save(); 45 + } 46 + 47 + private function stopTracking($user, $phid) { 48 + if (!PhrequentUserTimeQuery::isUserTrackingObject($user, $phid)) { 49 + // Don't do anything, it's not being tracked. 50 + return; 51 + } 52 + 53 + $usertime_dao = new PhrequentUserTime(); 54 + $conn = $usertime_dao->establishConnection('r'); 55 + 56 + queryfx( 57 + $conn, 58 + 'UPDATE %T usertime '. 59 + 'SET usertime.dateEnded = UNIX_TIMESTAMP() '. 60 + 'WHERE usertime.userPHID = %s '. 61 + 'AND usertime.objectPHID = %s '. 62 + 'AND usertime.dateEnded IS NULL '. 63 + 'ORDER BY usertime.dateStarted, usertime.id DESC '. 64 + 'LIMIT 1', 65 + $usertime_dao->getTableName(), 66 + $user->getPHID(), 67 + $phid); 68 + } 69 + 70 + }
+81
src/applications/phrequent/event/PhrequentUIEventListener.php
··· 1 + <?php 2 + 3 + final class PhrequentUIEventListener 4 + extends PhutilEventListener { 5 + 6 + public function register() { 7 + $this->listen(PhabricatorEventType::TYPE_UI_DIDRENDERACTIONS); 8 + $this->listen(PhabricatorEventType::TYPE_UI_WILLRENDERPROPERTIES); 9 + } 10 + 11 + public function handleEvent(PhutilEvent $event) { 12 + switch ($event->getType()) { 13 + case PhabricatorEventType::TYPE_UI_DIDRENDERACTIONS: 14 + $this->handleActionEvent($event); 15 + break; 16 + case PhabricatorEventType::TYPE_UI_WILLRENDERPROPERTIES: 17 + $this->handlePropertyEvent($event); 18 + break; 19 + } 20 + } 21 + 22 + private function handleActionEvent($event) { 23 + $user = $event->getUser(); 24 + $object = $event->getValue('object'); 25 + 26 + if (!$object || !$object->getPHID()) { 27 + // No object, or the object has no PHID yet.. 28 + return; 29 + } 30 + 31 + if (!($object instanceof PhrequentTrackableInterface)) { 32 + // This object isn't a time trackable object. 33 + return; 34 + } 35 + 36 + $tracking = PhrequentUserTimeQuery::isUserTrackingObject( 37 + $user, 38 + $object->getPHID()); 39 + if (!$tracking) { 40 + $track_action = id(new PhabricatorActionView()) 41 + ->setName(pht('Track Time')) 42 + ->setIcon('history') 43 + ->setWorkflow(true) 44 + ->setHref('/phrequent/track/start/'.$object->getPHID().'/'); 45 + } else { 46 + $track_action = id(new PhabricatorActionView()) 47 + ->setName(pht('Stop Tracking')) 48 + ->setIcon('history') 49 + ->setWorkflow(true) 50 + ->setHref('/phrequent/track/stop/'.$object->getPHID().'/'); 51 + } 52 + 53 + $actions = $event->getValue('actions'); 54 + $actions[] = $track_action; 55 + $event->setValue('actions', $actions); 56 + } 57 + 58 + private function handlePropertyEvent($event) { 59 + $user = $event->getUser(); 60 + $object = $event->getValue('object'); 61 + 62 + if (!$object || !$object->getPHID()) { 63 + // No object, or the object has no PHID yet.. 64 + return; 65 + } 66 + 67 + if (!($object instanceof PhrequentTrackableInterface)) { 68 + // This object isn't a time trackable object. 69 + return; 70 + } 71 + 72 + $time_spent = PhrequentUserTimeQuery::getTotalTimeSpentOnObject( 73 + $object->getPHID()); 74 + $view = $event->getValue('view'); 75 + $view->addProperty( 76 + pht('Time Spent'), 77 + $time_spent == 0 ? 'none' : 78 + phabricator_format_relative_time_detailed($time_spent)); 79 + } 80 + 81 + }
+5
src/applications/phrequent/interface/PhrequentTrackableInterface.php
··· 1 + <?php 2 + 3 + interface PhrequentTrackableInterface { 4 + 5 + }
+166
src/applications/phrequent/query/PhrequentUserTimeQuery.php
··· 1 + <?php 2 + 3 + final class PhrequentUserTimeQuery extends PhabricatorOffsetPagedQuery { 4 + 5 + const ORDER_ID = 'order-id'; 6 + const ORDER_STARTED = 'order-started'; 7 + const ORDER_ENDED = 'order-ended'; 8 + const ORDER_DURATION = 'order-duration'; 9 + 10 + private $userPHIDs; 11 + private $objectPHIDs; 12 + private $order = self::ORDER_ID; 13 + 14 + public function setUsers($user_phids) { 15 + $this->userPHIDs = $user_phids; 16 + return $this; 17 + } 18 + 19 + public function setObjects($object_phids) { 20 + $this->objectPHIDs = $object_phids; 21 + return $this; 22 + } 23 + 24 + public function setOrder($order) { 25 + $this->order = $order; 26 + return $this; 27 + } 28 + 29 + public function execute() { 30 + $usertime_dao = new PhrequentUserTime(); 31 + $conn = $usertime_dao->establishConnection('r'); 32 + 33 + $data = queryfx_all( 34 + $conn, 35 + 'SELECT usertime.* FROM %T usertime %Q %Q %Q', 36 + $usertime_dao->getTableName(), 37 + $this->buildWhereClause($conn), 38 + $this->buildOrderClause($conn), 39 + $this->buildLimitClause($conn)); 40 + 41 + return $usertime_dao->loadAllFromArray($data); 42 + } 43 + 44 + private function buildWhereClause(AphrontDatabaseConnection $conn) { 45 + $where = array(); 46 + 47 + if ($this->userPHIDs) { 48 + $where[] = qsprintf( 49 + $conn, 50 + 'userPHID IN (%Ls)', 51 + $this->userPHIDs); 52 + } 53 + 54 + if ($this->objectPHIDs) { 55 + $where[] = qsprintf( 56 + $conn, 57 + 'objectPHID IN (%Ls)', 58 + $this->objectPHIDs); 59 + } 60 + 61 + return $this->formatWhereClause($where); 62 + } 63 + 64 + private function buildOrderClause(AphrontDatabaseConnection $conn) { 65 + switch ($this->order) { 66 + case self::ORDER_ID: 67 + return 'ORDER BY id ASC'; 68 + case self::ORDER_STARTED: 69 + return 'ORDER BY dateStarted DESC'; 70 + case self::ORDER_ENDED: 71 + return 'ORDER BY dateEnded IS NULL, dateEnded DESC, dateStarted DESC'; 72 + case self::ORDER_DURATION: 73 + return 'ORDER BY (COALESCE(dateEnded, UNIX_TIMESTAMP() - dateStarted) '. 74 + 'DESC'; 75 + default: 76 + throw new Exception("Unknown order '{$this->order}'!"); 77 + } 78 + } 79 + 80 + /* -( Helper Functions ) --------------------------------------------------- */ 81 + 82 + public static function isUserTrackingObject( 83 + PhabricatorUser $user, 84 + $phid) { 85 + 86 + $usertime_dao = new PhrequentUserTime(); 87 + $conn = $usertime_dao->establishConnection('r'); 88 + 89 + $count = queryfx_one( 90 + $conn, 91 + 'SELECT COUNT(usertime.id) N FROM %T usertime '. 92 + 'WHERE usertime.userPHID = %s '. 93 + 'AND usertime.objectPHID = %s '. 94 + 'AND usertime.dateEnded IS NULL', 95 + $usertime_dao->getTableName(), 96 + $user->getPHID(), 97 + $phid); 98 + return $count['N'] > 0; 99 + } 100 + 101 + public static function getTotalTimeSpentOnObject($phid) { 102 + $usertime_dao = new PhrequentUserTime(); 103 + $conn = $usertime_dao->establishConnection('r'); 104 + 105 + // First calculate all the time spent where the 106 + // usertime blocks have ended. 107 + $sum_ended = queryfx_one( 108 + $conn, 109 + 'SELECT SUM(usertime.dateEnded - usertime.dateStarted) N '. 110 + 'FROM %T usertime '. 111 + 'WHERE usertime.objectPHID = %s '. 112 + 'AND usertime.dateEnded IS NOT NULL', 113 + $usertime_dao->getTableName(), 114 + $phid); 115 + 116 + // Now calculate the time spent where the usertime 117 + // blocks have not yet ended. 118 + $sum_not_ended = queryfx_one( 119 + $conn, 120 + 'SELECT SUM(UNIX_TIMESTAMP() - usertime.dateStarted) N '. 121 + 'FROM %T usertime '. 122 + 'WHERE usertime.objectPHID = %s '. 123 + 'AND usertime.dateEnded IS NULL', 124 + $usertime_dao->getTableName(), 125 + $phid); 126 + 127 + return $sum_ended['N'] + $sum_not_ended['N']; 128 + } 129 + 130 + public static function getUserTimeSpentOnObject( 131 + PhabricatorUser $user, 132 + $phid) { 133 + 134 + $usertime_dao = new PhrequentUserTime(); 135 + $conn = $usertime_dao->establishConnection('r'); 136 + 137 + // First calculate all the time spent where the 138 + // usertime blocks have ended. 139 + $sum_ended = queryfx_one( 140 + $conn, 141 + 'SELECT SUM(usertime.dateEnded - usertime.dateStarted) N '. 142 + 'FROM %T usertime '. 143 + 'WHERE usertime.userPHID = %s '. 144 + 'AND usertime.objectPHID = %s '. 145 + 'AND usertime.dateEnded IS NOT NULL', 146 + $usertime_dao->getTableName(), 147 + $user->getPHID(), 148 + $phid); 149 + 150 + // Now calculate the time spent where the usertime 151 + // blocks have not yet ended. 152 + $sum_not_ended = queryfx_one( 153 + $conn, 154 + 'SELECT SUM(UNIX_TIMESTAMP() - usertime.dateStarted) N '. 155 + 'FROM %T usertime '. 156 + 'WHERE usertime.userPHID = %s '. 157 + 'AND usertime.objectPHID = %s '. 158 + 'AND usertime.dateEnded IS NULL', 159 + $usertime_dao->getTableName(), 160 + $user->getPHID(), 161 + $phid); 162 + 163 + return $sum_ended['N'] + $sum_not_ended['N']; 164 + } 165 + 166 + }