diff --git a/src/applications/maniphest/query/ManiphestTaskSearchEngine.php b/src/applications/maniphest/query/ManiphestTaskSearchEngine.php --- a/src/applications/maniphest/query/ManiphestTaskSearchEngine.php +++ b/src/applications/maniphest/query/ManiphestTaskSearchEngine.php @@ -456,6 +456,15 @@ id(new PhabricatorStringExportField()) ->setKey('statusName') ->setLabel(pht('Status Name')), + id(new PhabricatorEpochExportField()) + ->setKey('dateClosed') + ->setLabel(pht('Date Closed')), + id(new PhabricatorPHIDExportField()) + ->setKey('closerPHID') + ->setLabel(pht('Closer PHID')), + id(new PhabricatorStringExportField()) + ->setKey('closer') + ->setLabel(pht('Closer')), id(new PhabricatorStringExportField()) ->setKey('priority') ->setLabel(pht('Priority')), @@ -492,6 +501,7 @@ foreach ($tasks as $task) { $phids[] = $task->getAuthorPHID(); $phids[] = $task->getOwnerPHID(); + $phids[] = $task->getCloserPHID(); } $handles = $viewer->loadHandles($phids); @@ -512,6 +522,13 @@ $owner_name = null; } + $closer_phid = $task->getCloserPHID(); + if ($closer_phid) { + $closer_name = $handles[$closer_phid]->getName(); + } else { + $closer_name = null; + } + $status_value = $task->getStatus(); $status_name = ManiphestTaskStatus::getTaskStatusName($status_value); @@ -534,6 +551,9 @@ 'title' => $task->getTitle(), 'uri' => PhabricatorEnv::getProductionURI($task->getURI()), 'description' => $task->getDescription(), + 'dateClosed' => $task->getClosedEpoch(), + 'closerPHID' => $closer_phid, + 'closer' => $closer_name, ); } diff --git a/src/applications/maniphest/storage/ManiphestTask.php b/src/applications/maniphest/storage/ManiphestTask.php --- a/src/applications/maniphest/storage/ManiphestTask.php +++ b/src/applications/maniphest/storage/ManiphestTask.php @@ -513,6 +513,16 @@ ->setKey('subtype') ->setType('string') ->setDescription(pht('Subtype of the task.')), + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('closerPHID') + ->setType('phid?') + ->setDescription( + pht('User who closed the task, if the task is closed.')), + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('dateClosed') + ->setType('int?') + ->setDescription( + pht('Epoch timestamp when the task was closed.')), ); } @@ -532,6 +542,11 @@ 'color' => ManiphestTaskPriority::getTaskPriorityColor($priority_value), ); + $closed_epoch = $this->getClosedEpoch(); + if ($closed_epoch !== null) { + $closed_epoch = (int)$closed_epoch; + } + return array( 'name' => $this->getTitle(), 'description' => array( @@ -543,6 +558,8 @@ 'priority' => $priority_info, 'points' => $this->getPoints(), 'subtype' => $this->getSubtype(), + 'closerPHID' => $this->getCloserPHID(), + 'dateClosed' => $closed_epoch, ); } diff --git a/src/applications/maniphest/view/ManiphestTaskListView.php b/src/applications/maniphest/view/ManiphestTaskListView.php --- a/src/applications/maniphest/view/ManiphestTaskListView.php +++ b/src/applications/maniphest/view/ManiphestTaskListView.php @@ -86,9 +86,24 @@ $item->setStatusIcon($icon.' '.$color, $tooltip); - $item->addIcon( - 'none', - phabricator_datetime($task->getDateModified(), $this->getUser())); + if ($task->isClosed()) { + $closed_epoch = $task->getClosedEpoch(); + + // We don't expect a task to be closed without a closed epoch, but + // recover if we find one. This can happen with older objects or with + // lipsum test data. + if (!$closed_epoch) { + $closed_epoch = $task->getDateModified(); + } + + $item->addIcon( + 'fa-check-square-o grey', + phabricator_datetime($closed_epoch, $this->getUser())); + } else { + $item->addIcon( + 'none', + phabricator_datetime($task->getDateModified(), $this->getUser())); + } if ($this->showSubpriorityControls) { $item->setGrippable(true);