private function renderServerStatus(array $status)
 {
     $rows = array();
     foreach ($status as $key => $value) {
         switch ($key) {
             case 'uptime':
                 $value /= 1000;
                 $value = phutil_format_relative_time_detailed($value);
                 break;
             case 'log':
                 break;
             default:
                 $value = number_format($value);
                 break;
         }
         $rows[] = array($key, $value);
     }
     $table = new AphrontTableView($rows);
     $table->setColumnClasses(array('header', 'wide'));
     $test_icon = id(new PHUIIconView())->setIconFont('fa-exclamation-triangle');
     $test_button = id(new PHUIButtonView())->setTag('a')->setWorkflow(true)->setText(pht('Send Test Notification'))->setHref($this->getApplicationURI('test/'))->setIcon($test_icon);
     $header = id(new PHUIHeaderView())->setHeader(pht('Notification Server Status'))->addActionLink($test_button);
     $box = id(new PHUIObjectBoxView())->setHeader($header)->appendChild($table);
     return $box;
 }
 private function buildClusterNotificationStatus()
 {
     $viewer = $this->getViewer();
     $servers = PhabricatorNotificationServerRef::newRefs();
     Javelin::initBehavior('phabricator-tooltips');
     $rows = array();
     foreach ($servers as $server) {
         if ($server->isAdminServer()) {
             $type_icon = 'fa-database sky';
             $type_tip = pht('Admin Server');
         } else {
             $type_icon = 'fa-bell sky';
             $type_tip = pht('Client Server');
         }
         $type_icon = id(new PHUIIconView())->setIcon($type_icon)->addSigil('has-tooltip')->setMetadata(array('tip' => $type_tip));
         $messages = array();
         $details = array();
         if ($server->isAdminServer()) {
             try {
                 $details = $server->loadServerStatus();
                 $status_icon = 'fa-exchange green';
                 $status_label = pht('Version %s', idx($details, 'version'));
             } catch (Exception $ex) {
                 $status_icon = 'fa-times red';
                 $status_label = pht('Connection Error');
                 $messages[] = $ex->getMessage();
             }
         } else {
             try {
                 $server->testClient();
                 $status_icon = 'fa-exchange green';
                 $status_label = pht('Connected');
             } catch (Exception $ex) {
                 $status_icon = 'fa-times red';
                 $status_label = pht('Connection Error');
                 $messages[] = $ex->getMessage();
             }
         }
         if ($details) {
             $uptime = idx($details, 'uptime');
             $uptime = $uptime / 1000;
             $uptime = phutil_format_relative_time_detailed($uptime);
             $clients = pht('%s Active / %s Total', new PhutilNumber(idx($details, 'clients.active')), new PhutilNumber(idx($details, 'clients.total')));
             $stats = pht('%s In / %s Out', new PhutilNumber(idx($details, 'messages.in')), new PhutilNumber(idx($details, 'messages.out')));
         } else {
             $uptime = null;
             $clients = null;
             $stats = null;
         }
         $status_view = array(id(new PHUIIconView())->setIcon($status_icon), ' ', $status_label);
         $messages = phutil_implode_html(phutil_tag('br'), $messages);
         $rows[] = array($type_icon, $server->getProtocol(), $server->getHost(), $server->getPort(), $status_view, $uptime, $clients, $stats, $messages);
     }
     $table = id(new AphrontTableView($rows))->setNoDataString(pht('No notification servers are configured.'))->setHeaders(array(null, pht('Proto'), pht('Host'), pht('Port'), pht('Status'), pht('Uptime'), pht('Clients'), pht('Messages'), null))->setColumnClasses(array(null, null, null, null, null, null, null, null, 'wide'));
     $doc_href = PhabricatorEnv::getDoclink('Cluster: Notifications');
     $header = id(new PHUIHeaderView())->setHeader(pht('Cluster Notification Status'))->addActionLink(id(new PHUIButtonView())->setIcon('fa-book')->setHref($doc_href)->setTag('a')->setText(pht('Documentation')));
     return id(new PHUIObjectBoxView())->setHeader($header)->setTable($table);
 }
 private function buildPropertyListView(PhabricatorWorkerTask $task)
 {
     $viewer = $this->getRequest()->getUser();
     $view = new PHUIPropertyListView();
     if ($task->isArchived()) {
         switch ($task->getResult()) {
             case PhabricatorWorkerArchiveTask::RESULT_SUCCESS:
                 $status = pht('Complete');
                 break;
             case PhabricatorWorkerArchiveTask::RESULT_FAILURE:
                 $status = pht('Failed');
                 break;
             case PhabricatorWorkerArchiveTask::RESULT_CANCELLED:
                 $status = pht('Cancelled');
                 break;
             default:
                 throw new Exception(pht('Unknown task status!'));
         }
     } else {
         $status = pht('Queued');
     }
     $view->addProperty(pht('Task Status'), $status);
     $view->addProperty(pht('Task Class'), $task->getTaskClass());
     if ($task->getLeaseExpires()) {
         if ($task->getLeaseExpires() > time()) {
             $lease_status = pht('Leased');
         } else {
             $lease_status = pht('Lease Expired');
         }
     } else {
         $lease_status = phutil_tag('em', array(), pht('Not Leased'));
     }
     $view->addProperty(pht('Lease Status'), $lease_status);
     $view->addProperty(pht('Lease Owner'), $task->getLeaseOwner() ? $task->getLeaseOwner() : phutil_tag('em', array(), pht('None')));
     if ($task->getLeaseExpires() && $task->getLeaseOwner()) {
         $expires = $task->getLeaseExpires() - time();
         $expires = phutil_format_relative_time_detailed($expires);
     } else {
         $expires = phutil_tag('em', array(), pht('None'));
     }
     $view->addProperty(pht('Lease Expires'), $expires);
     if ($task->isArchived()) {
         $duration = pht('%s us', new PhutilNumber($task->getDuration()));
     } else {
         $duration = phutil_tag('em', array(), pht('Not Completed'));
     }
     $view->addProperty(pht('Duration'), $duration);
     $data = id(new PhabricatorWorkerTaskData())->load($task->getDataID());
     $task->setData($data->getData());
     $worker = $task->getWorkerInstance();
     $data = $worker->renderForDisplay($viewer);
     if ($data !== null) {
         $view->addProperty(pht('Data'), $data);
     }
     return $view;
 }
 public function testDetailedDurationFormatting()
 {
     $expected_zero = 'now';
     $tests = array(12095939 => '19 w, 6 d', -12095939 => '19 w, 6 d ago', 3380521 => '5 w, 4 d', -3380521 => '5 w, 4 d ago', 0 => $expected_zero);
     foreach ($tests as $duration => $expect) {
         $this->assertEqual($expect, phutil_format_relative_time_detailed($duration), 'phutil_format_relative_time_detailed(' . $duration . ')');
     }
     $tests = array(3380521 => array(-1 => '5 w', 0 => '5 w', 1 => '5 w', 2 => '5 w, 4 d', 3 => '5 w, 4 d, 3 h', 4 => '5 w, 4 d, 3 h, 2 m', 5 => '5 w, 4 d, 3 h, 2 m, 1 s', 6 => '5 w, 4 d, 3 h, 2 m, 1 s'), -3380521 => array(-1 => '5 w ago', 0 => '5 w ago', 1 => '5 w ago', 2 => '5 w, 4 d ago', 3 => '5 w, 4 d, 3 h ago', 4 => '5 w, 4 d, 3 h, 2 m ago', 5 => '5 w, 4 d, 3 h, 2 m, 1 s ago', 6 => '5 w, 4 d, 3 h, 2 m, 1 s ago'), 0 => array(-1 => $expected_zero, 0 => $expected_zero, 1 => $expected_zero, 2 => $expected_zero, 3 => $expected_zero, 4 => $expected_zero, 5 => $expected_zero, 6 => $expected_zero));
     foreach ($tests as $duration => $sub_tests) {
         if (is_array($sub_tests)) {
             foreach ($sub_tests as $levels => $expect) {
                 $this->assertEqual($expect, phutil_format_relative_time_detailed($duration, $levels), 'phutil_format_relative_time_detailed(' . $duration . ',
           ' . $levels . ')');
             }
         } else {
             $expect = $sub_tests;
             $this->assertEqual($expect, phutil_format_relative_time_detailed($duration), 'phutil_format_relative_time_detailed(' . $duration . ')');
         }
     }
 }
 private function buildRepositoryUpdateInterval(PhabricatorRepository $repository)
 {
     $smart_wait = $repository->loadUpdateInterval();
     $doc_href = PhabricatorEnv::getDoclink('Diffusion User Guide: Repository Updates');
     return array(phutil_format_relative_time_detailed($smart_wait), " · ", phutil_tag('a', array('href' => $doc_href, 'target' => '_blank'), pht('Learn More')));
 }
 private function buildRepositoryStatus(PhabricatorRepository $repository, array $messages)
 {
     $viewer = $this->getViewer();
     $is_cluster = $repository->getAlmanacServicePHID();
     $view = new PHUIStatusListView();
     if ($repository->isTracked()) {
         $view->addItem(id(new PHUIStatusItemView())->setIcon(PHUIStatusItemView::ICON_ACCEPT, 'green')->setTarget(pht('Repository Active')));
     } else {
         $view->addItem(id(new PHUIStatusItemView())->setIcon(PHUIStatusItemView::ICON_WARNING, 'bluegrey')->setTarget(pht('Repository Inactive'))->setNote(pht('Activate this repository to begin or resume import.')));
         return $view;
     }
     $binaries = array();
     $svnlook_check = false;
     switch ($repository->getVersionControlSystem()) {
         case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
             $binaries[] = 'git';
             break;
         case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
             $binaries[] = 'svn';
             break;
         case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
             $binaries[] = 'hg';
             break;
     }
     if ($repository->isHosted()) {
         $proto_https = PhabricatorRepositoryURI::BUILTIN_PROTOCOL_HTTPS;
         $proto_http = PhabricatorRepositoryURI::BUILTIN_PROTOCOL_HTTP;
         $can_http = $repository->canServeProtocol($proto_http, false) || $repository->canServeProtocol($proto_https, false);
         if ($can_http) {
             switch ($repository->getVersionControlSystem()) {
                 case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
                     $binaries[] = 'git-http-backend';
                     break;
                 case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
                     $binaries[] = 'svnserve';
                     $binaries[] = 'svnadmin';
                     $binaries[] = 'svnlook';
                     $svnlook_check = true;
                     break;
                 case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
                     $binaries[] = 'hg';
                     break;
             }
         }
         $proto_ssh = PhabricatorRepositoryURI::BUILTIN_PROTOCOL_SSH;
         $can_ssh = $repository->canServeProtocol($proto_ssh, false);
         if ($can_ssh) {
             switch ($repository->getVersionControlSystem()) {
                 case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
                     $binaries[] = 'git-receive-pack';
                     $binaries[] = 'git-upload-pack';
                     break;
                 case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
                     $binaries[] = 'svnserve';
                     $binaries[] = 'svnadmin';
                     $binaries[] = 'svnlook';
                     $svnlook_check = true;
                     break;
                 case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
                     $binaries[] = 'hg';
                     break;
             }
         }
     }
     $binaries = array_unique($binaries);
     if (!$is_cluster) {
         // We're only checking for binaries if we aren't running with a cluster
         // configuration. In theory, we could check for binaries on the
         // repository host machine, but we'd need to make this more complicated
         // to do that.
         foreach ($binaries as $binary) {
             $where = Filesystem::resolveBinary($binary);
             if (!$where) {
                 $view->addItem(id(new PHUIStatusItemView())->setIcon(PHUIStatusItemView::ICON_WARNING, 'red')->setTarget(pht('Missing Binary %s', phutil_tag('tt', array(), $binary)))->setNote(pht("Unable to find this binary in the webserver's PATH. You may " . "need to configure %s.", $this->getEnvConfigLink())));
             } else {
                 $view->addItem(id(new PHUIStatusItemView())->setIcon(PHUIStatusItemView::ICON_ACCEPT, 'green')->setTarget(pht('Found Binary %s', phutil_tag('tt', array(), $binary)))->setNote(phutil_tag('tt', array(), $where)));
             }
         }
         // This gets checked generically above. However, for svn commit hooks, we
         // need this to be in environment.append-paths because subversion strips
         // PATH.
         if ($svnlook_check) {
             $where = Filesystem::resolveBinary('svnlook');
             if ($where) {
                 $path = substr($where, 0, strlen($where) - strlen('svnlook'));
                 $dirs = PhabricatorEnv::getEnvConfig('environment.append-paths');
                 $in_path = false;
                 foreach ($dirs as $dir) {
                     if (Filesystem::isDescendant($path, $dir)) {
                         $in_path = true;
                         break;
                     }
                 }
                 if (!$in_path) {
                     $view->addItem(id(new PHUIStatusItemView())->setIcon(PHUIStatusItemView::ICON_WARNING, 'red')->setTarget(pht('Missing Binary %s', phutil_tag('tt', array(), $binary)))->setNote(pht('Unable to find this binary in `%s`. ' . 'You need to configure %s and include %s.', 'environment.append-paths', $this->getEnvConfigLink(), $path)));
                 }
             }
         }
     }
     $doc_href = PhabricatorEnv::getDocLink('Managing Daemons with phd');
     $daemon_instructions = pht('Use %s to start daemons. See %s.', phutil_tag('tt', array(), 'bin/phd start'), phutil_tag('a', array('href' => $doc_href), pht('Managing Daemons with phd')));
     $pull_daemon = id(new PhabricatorDaemonLogQuery())->setViewer(PhabricatorUser::getOmnipotentUser())->withStatus(PhabricatorDaemonLogQuery::STATUS_ALIVE)->withDaemonClasses(array('PhabricatorRepositoryPullLocalDaemon'))->setLimit(1)->execute();
     if ($pull_daemon) {
         // TODO: In a cluster environment, we need a daemon on this repository's
         // host, specifically, and we aren't checking for that right now. This
         // is a reasonable proxy for things being more-or-less correctly set up,
         // though.
         $view->addItem(id(new PHUIStatusItemView())->setIcon(PHUIStatusItemView::ICON_ACCEPT, 'green')->setTarget(pht('Pull Daemon Running')));
     } else {
         $view->addItem(id(new PHUIStatusItemView())->setIcon(PHUIStatusItemView::ICON_WARNING, 'red')->setTarget(pht('Pull Daemon Not Running'))->setNote($daemon_instructions));
     }
     $task_daemon = id(new PhabricatorDaemonLogQuery())->setViewer(PhabricatorUser::getOmnipotentUser())->withStatus(PhabricatorDaemonLogQuery::STATUS_ALIVE)->withDaemonClasses(array('PhabricatorTaskmasterDaemon'))->setLimit(1)->execute();
     if ($task_daemon) {
         $view->addItem(id(new PHUIStatusItemView())->setIcon(PHUIStatusItemView::ICON_ACCEPT, 'green')->setTarget(pht('Task Daemon Running')));
     } else {
         $view->addItem(id(new PHUIStatusItemView())->setIcon(PHUIStatusItemView::ICON_WARNING, 'red')->setTarget(pht('Task Daemon Not Running'))->setNote($daemon_instructions));
     }
     if ($is_cluster) {
         // Just omit this status check for now in cluster environments. We
         // could make a service call and pull it from the repository host
         // eventually.
     } else {
         if ($repository->usesLocalWorkingCopy()) {
             $local_parent = dirname($repository->getLocalPath());
             if (Filesystem::pathExists($local_parent)) {
                 $view->addItem(id(new PHUIStatusItemView())->setIcon(PHUIStatusItemView::ICON_ACCEPT, 'green')->setTarget(pht('Storage Directory OK'))->setNote(phutil_tag('tt', array(), $local_parent)));
             } else {
                 $view->addItem(id(new PHUIStatusItemView())->setIcon(PHUIStatusItemView::ICON_WARNING, 'red')->setTarget(pht('No Storage Directory'))->setNote(pht('Storage directory %s does not exist, or is not readable by ' . 'the webserver. Create this directory or make it readable.', phutil_tag('tt', array(), $local_parent))));
                 return $view;
             }
             $local_path = $repository->getLocalPath();
             $message = idx($messages, PhabricatorRepositoryStatusMessage::TYPE_INIT);
             if ($message) {
                 switch ($message->getStatusCode()) {
                     case PhabricatorRepositoryStatusMessage::CODE_ERROR:
                         $view->addItem(id(new PHUIStatusItemView())->setIcon(PHUIStatusItemView::ICON_WARNING, 'red')->setTarget(pht('Initialization Error'))->setNote($message->getParameter('message')));
                         return $view;
                     case PhabricatorRepositoryStatusMessage::CODE_OKAY:
                         if (Filesystem::pathExists($local_path)) {
                             $view->addItem(id(new PHUIStatusItemView())->setIcon(PHUIStatusItemView::ICON_ACCEPT, 'green')->setTarget(pht('Working Copy OK'))->setNote(phutil_tag('tt', array(), $local_path)));
                         } else {
                             $view->addItem(id(new PHUIStatusItemView())->setIcon(PHUIStatusItemView::ICON_WARNING, 'red')->setTarget(pht('Working Copy Error'))->setNote(pht('Working copy %s has been deleted, or is not ' . 'readable by the webserver. Make this directory ' . 'readable. If it has been deleted, the daemons should ' . 'restore it automatically.', phutil_tag('tt', array(), $local_path))));
                             return $view;
                         }
                         break;
                     case PhabricatorRepositoryStatusMessage::CODE_WORKING:
                         $view->addItem(id(new PHUIStatusItemView())->setIcon(PHUIStatusItemView::ICON_CLOCK, 'green')->setTarget(pht('Initializing Working Copy'))->setNote(pht('Daemons are initializing the working copy.')));
                         return $view;
                     default:
                         $view->addItem(id(new PHUIStatusItemView())->setIcon(PHUIStatusItemView::ICON_WARNING, 'red')->setTarget(pht('Unknown Init Status'))->setNote($message->getStatusCode()));
                         return $view;
                 }
             } else {
                 $view->addItem(id(new PHUIStatusItemView())->setIcon(PHUIStatusItemView::ICON_CLOCK, 'orange')->setTarget(pht('No Working Copy Yet'))->setNote(pht('Waiting for daemons to build a working copy.')));
                 return $view;
             }
         }
     }
     $message = idx($messages, PhabricatorRepositoryStatusMessage::TYPE_FETCH);
     if ($message) {
         switch ($message->getStatusCode()) {
             case PhabricatorRepositoryStatusMessage::CODE_ERROR:
                 $message = $message->getParameter('message');
                 $suggestion = null;
                 if (preg_match('/Permission denied \\(publickey\\)./', $message)) {
                     $suggestion = pht('Public Key Error: This error usually indicates that the ' . 'keypair you have configured does not have permission to ' . 'access the repository.');
                 }
                 $view->addItem(id(new PHUIStatusItemView())->setIcon(PHUIStatusItemView::ICON_WARNING, 'red')->setTarget(pht('Update Error'))->setNote($suggestion));
                 return $view;
             case PhabricatorRepositoryStatusMessage::CODE_OKAY:
                 $ago = PhabricatorTime::getNow() - $message->getEpoch();
                 $view->addItem(id(new PHUIStatusItemView())->setIcon(PHUIStatusItemView::ICON_ACCEPT, 'green')->setTarget(pht('Updates OK'))->setNote(pht('Last updated %s (%s ago).', phabricator_datetime($message->getEpoch(), $viewer), phutil_format_relative_time_detailed($ago))));
                 break;
         }
     } else {
         $view->addItem(id(new PHUIStatusItemView())->setIcon(PHUIStatusItemView::ICON_CLOCK, 'orange')->setTarget(pht('Waiting For Update'))->setNote(pht('Waiting for daemons to read updates.')));
     }
     if ($repository->isImporting()) {
         $ratio = $repository->loadImportProgress();
         $percentage = sprintf('%.2f%%', 100 * $ratio);
         $view->addItem(id(new PHUIStatusItemView())->setIcon(PHUIStatusItemView::ICON_CLOCK, 'green')->setTarget(pht('Importing'))->setNote(pht('%s Complete', $percentage)));
     } else {
         $view->addItem(id(new PHUIStatusItemView())->setIcon(PHUIStatusItemView::ICON_ACCEPT, 'green')->setTarget(pht('Fully Imported')));
     }
     if (idx($messages, PhabricatorRepositoryStatusMessage::TYPE_NEEDS_UPDATE)) {
         $view->addItem(id(new PHUIStatusItemView())->setIcon(PHUIStatusItemView::ICON_UP, 'indigo')->setTarget(pht('Prioritized'))->setNote(pht('This repository will be updated soon!')));
     }
     return $view;
 }
 public function renderPropertyViewValue(array $handles)
 {
     $absolute = phabricator_datetime($this->getObject()->getDateCreated(), $this->getViewer());
     $relative = phutil_format_relative_time_detailed(time() - $this->getObject()->getDateCreated(), $levels = 2);
     return hsprintf('%s (%s)', $absolute, $relative);
 }
 public function processRequest()
 {
     $request = $this->getRequest();
     $viewer = $request->getUser();
     $id = $this->id;
     $generation = $request->getInt('g');
     $build = id(new HarbormasterBuildQuery())->setViewer($viewer)->withIDs(array($id))->executeOne();
     if (!$build) {
         return new Aphront404Response();
     }
     require_celerity_resource('harbormaster-css');
     $title = pht('Build %d', $id);
     $header = id(new PHUIHeaderView())->setHeader($title)->setUser($viewer)->setPolicyObject($build);
     if ($build->isRestarting()) {
         $header->setStatus('fa-exclamation-triangle', 'red', pht('Restarting'));
     } else {
         if ($build->isStopping()) {
             $header->setStatus('fa-exclamation-triangle', 'red', pht('Pausing'));
         } else {
             if ($build->isResuming()) {
                 $header->setStatus('fa-exclamation-triangle', 'red', pht('Resuming'));
             }
         }
     }
     $box = id(new PHUIObjectBoxView())->setHeader($header);
     $actions = $this->buildActionList($build);
     $this->buildPropertyLists($box, $build, $actions);
     $crumbs = $this->buildApplicationCrumbs();
     $crumbs->addTextCrumb($build->getBuildable()->getMonogram(), '/' . $build->getBuildable()->getMonogram());
     $crumbs->addTextCrumb($title);
     if ($generation === null || $generation > $build->getBuildGeneration() || $generation < 0) {
         $generation = $build->getBuildGeneration();
     }
     $build_targets = id(new HarbormasterBuildTargetQuery())->setViewer($viewer)->needBuildSteps(true)->withBuildPHIDs(array($build->getPHID()))->withBuildGenerations(array($generation))->execute();
     if ($build_targets) {
         $messages = id(new HarbormasterBuildMessageQuery())->setViewer($viewer)->withBuildTargetPHIDs(mpull($build_targets, 'getPHID'))->execute();
         $messages = mgroup($messages, 'getBuildTargetPHID');
     } else {
         $messages = array();
     }
     $targets = array();
     foreach ($build_targets as $build_target) {
         $header = id(new PHUIHeaderView())->setHeader($build_target->getName())->setUser($viewer);
         $target_box = id(new PHUIObjectBoxView())->setHeader($header);
         $properties = new PHUIPropertyListView();
         $status_view = new PHUIStatusListView();
         $item = new PHUIStatusItemView();
         $status = $build_target->getTargetStatus();
         $status_name = HarbormasterBuildTarget::getBuildTargetStatusName($status);
         $icon = HarbormasterBuildTarget::getBuildTargetStatusIcon($status);
         $color = HarbormasterBuildTarget::getBuildTargetStatusColor($status);
         $item->setTarget($status_name);
         $item->setIcon($icon, $color);
         $status_view->addItem($item);
         $properties->addProperty(pht('Name'), $build_target->getName());
         if ($build_target->getDateStarted() !== null) {
             $properties->addProperty(pht('Started'), phabricator_datetime($build_target->getDateStarted(), $viewer));
             if ($build_target->isComplete()) {
                 $properties->addProperty(pht('Completed'), phabricator_datetime($build_target->getDateCompleted(), $viewer));
                 $properties->addProperty(pht('Duration'), phutil_format_relative_time_detailed($build_target->getDateCompleted() - $build_target->getDateStarted()));
             } else {
                 $properties->addProperty(pht('Elapsed'), phutil_format_relative_time_detailed(time() - $build_target->getDateStarted()));
             }
         }
         $properties->addProperty(pht('Status'), $status_view);
         $target_box->addPropertyList($properties, pht('Overview'));
         $step = $build_target->getBuildStep();
         if ($step) {
             $description = $step->getDescription();
             if ($description) {
                 $rendered = PhabricatorMarkupEngine::renderOneObject(id(new PhabricatorMarkupOneOff())->setContent($description)->setPreserveLinebreaks(true), 'default', $viewer);
                 $properties->addSectionHeader(pht('Description'));
                 $properties->addTextContent($rendered);
             }
         } else {
             $target_box->setFormErrors(array(pht('This build step has since been deleted on the build plan.  ' . 'Some information may be omitted.')));
         }
         $details = $build_target->getDetails();
         if ($details) {
             $properties = new PHUIPropertyListView();
             foreach ($details as $key => $value) {
                 $properties->addProperty($key, $value);
             }
             $target_box->addPropertyList($properties, pht('Configuration'));
         }
         $variables = $build_target->getVariables();
         if ($variables) {
             $properties = new PHUIPropertyListView();
             foreach ($variables as $key => $value) {
                 $properties->addProperty($key, $value);
             }
             $target_box->addPropertyList($properties, pht('Variables'));
         }
         $artifacts = $this->buildArtifacts($build_target);
         if ($artifacts) {
             $properties = new PHUIPropertyListView();
             $properties->addRawContent($artifacts);
             $target_box->addPropertyList($properties, pht('Artifacts'));
         }
         $build_messages = idx($messages, $build_target->getPHID(), array());
         if ($build_messages) {
             $properties = new PHUIPropertyListView();
             $properties->addRawContent($this->buildMessages($build_messages));
             $target_box->addPropertyList($properties, pht('Messages'));
         }
         $properties = new PHUIPropertyListView();
         $properties->addProperty('Build Target ID', $build_target->getID());
         $target_box->addPropertyList($properties, pht('Metadata'));
         $targets[] = $target_box;
         $targets[] = $this->buildLog($build, $build_target);
     }
     $xactions = id(new HarbormasterBuildTransactionQuery())->setViewer($viewer)->withObjectPHIDs(array($build->getPHID()))->execute();
     $timeline = id(new PhabricatorApplicationTransactionView())->setUser($viewer)->setObjectPHID($build->getPHID())->setTransactions($xactions);
     return $this->buildApplicationPage(array($crumbs, $box, $targets, $timeline), array('title' => $title));
 }
 public function handleRequest(AphrontRequest $request)
 {
     $request = $this->getRequest();
     $viewer = $request->getUser();
     $id = $request->getURIData('id');
     $generation = $request->getInt('g');
     $build = id(new HarbormasterBuildQuery())->setViewer($viewer)->withIDs(array($id))->executeOne();
     if (!$build) {
         return new Aphront404Response();
     }
     require_celerity_resource('harbormaster-css');
     $title = pht('Build %d', $id);
     $header = id(new PHUIHeaderView())->setHeader($title)->setUser($viewer)->setPolicyObject($build);
     if ($build->isRestarting()) {
         $header->setStatus('fa-exclamation-triangle', 'red', pht('Restarting'));
     } else {
         if ($build->isPausing()) {
             $header->setStatus('fa-exclamation-triangle', 'red', pht('Pausing'));
         } else {
             if ($build->isResuming()) {
                 $header->setStatus('fa-exclamation-triangle', 'red', pht('Resuming'));
             } else {
                 if ($build->isAborting()) {
                     $header->setStatus('fa-exclamation-triangle', 'red', pht('Aborting'));
                 }
             }
         }
     }
     $box = id(new PHUIObjectBoxView())->setHeader($header);
     $actions = $this->buildActionList($build);
     $this->buildPropertyLists($box, $build, $actions);
     $crumbs = $this->buildApplicationCrumbs();
     $this->addBuildableCrumb($crumbs, $build->getBuildable());
     $crumbs->addTextCrumb($title);
     if ($generation === null || $generation > $build->getBuildGeneration() || $generation < 0) {
         $generation = $build->getBuildGeneration();
     }
     $build_targets = id(new HarbormasterBuildTargetQuery())->setViewer($viewer)->needBuildSteps(true)->withBuildPHIDs(array($build->getPHID()))->withBuildGenerations(array($generation))->execute();
     if ($build_targets) {
         $messages = id(new HarbormasterBuildMessageQuery())->setViewer($viewer)->withBuildTargetPHIDs(mpull($build_targets, 'getPHID'))->execute();
         $messages = mgroup($messages, 'getBuildTargetPHID');
     } else {
         $messages = array();
     }
     if ($build_targets) {
         $artifacts = id(new HarbormasterBuildArtifactQuery())->setViewer($viewer)->withBuildTargetPHIDs(mpull($build_targets, 'getPHID'))->execute();
         $artifacts = msort($artifacts, 'getArtifactKey');
         $artifacts = mgroup($artifacts, 'getBuildTargetPHID');
     } else {
         $artifacts = array();
     }
     $targets = array();
     foreach ($build_targets as $build_target) {
         $header = id(new PHUIHeaderView())->setHeader($build_target->getName())->setUser($viewer);
         $target_box = id(new PHUIObjectBoxView())->setHeader($header);
         $properties = new PHUIPropertyListView();
         $target_artifacts = idx($artifacts, $build_target->getPHID(), array());
         $links = array();
         $type_uri = HarbormasterURIArtifact::ARTIFACTCONST;
         foreach ($target_artifacts as $artifact) {
             if ($artifact->getArtifactType() == $type_uri) {
                 $impl = $artifact->getArtifactImplementation();
                 if ($impl->isExternalLink()) {
                     $links[] = $impl->renderLink();
                 }
             }
         }
         if ($links) {
             $links = phutil_implode_html(phutil_tag('br'), $links);
             $properties->addProperty(pht('External Link'), $links);
         }
         $status_view = new PHUIStatusListView();
         $item = new PHUIStatusItemView();
         $status = $build_target->getTargetStatus();
         $status_name = HarbormasterBuildTarget::getBuildTargetStatusName($status);
         $icon = HarbormasterBuildTarget::getBuildTargetStatusIcon($status);
         $color = HarbormasterBuildTarget::getBuildTargetStatusColor($status);
         $item->setTarget($status_name);
         $item->setIcon($icon, $color);
         $status_view->addItem($item);
         $when = array();
         $started = $build_target->getDateStarted();
         $now = PhabricatorTime::getNow();
         if ($started) {
             $ended = $build_target->getDateCompleted();
             if ($ended) {
                 $when[] = pht('Completed at %s', phabricator_datetime($started, $viewer));
                 $duration = $ended - $started;
                 if ($duration) {
                     $when[] = pht('Built for %s', phutil_format_relative_time_detailed($duration));
                 } else {
                     $when[] = pht('Built instantly');
                 }
             } else {
                 $when[] = pht('Started at %s', phabricator_datetime($started, $viewer));
                 $duration = $now - $started;
                 if ($duration) {
                     $when[] = pht('Running for %s', phutil_format_relative_time_detailed($duration));
                 }
             }
         } else {
             $created = $build_target->getDateCreated();
             $when[] = pht('Queued at %s', phabricator_datetime($started, $viewer));
             $duration = $now - $created;
             if ($duration) {
                 $when[] = pht('Waiting for %s', phutil_format_relative_time_detailed($duration));
             }
         }
         $properties->addProperty(pht('When'), phutil_implode_html(" · ", $when));
         $properties->addProperty(pht('Status'), $status_view);
         $target_box->addPropertyList($properties, pht('Overview'));
         $step = $build_target->getBuildStep();
         if ($step) {
             $description = $step->getDescription();
             if ($description) {
                 $description = new PHUIRemarkupView($viewer, $description);
                 $properties->addSectionHeader(pht('Description'), PHUIPropertyListView::ICON_SUMMARY);
                 $properties->addTextContent($description);
             }
         } else {
             $target_box->setFormErrors(array(pht('This build step has since been deleted on the build plan.  ' . 'Some information may be omitted.')));
         }
         $details = $build_target->getDetails();
         $properties = new PHUIPropertyListView();
         foreach ($details as $key => $value) {
             $properties->addProperty($key, $value);
         }
         $target_box->addPropertyList($properties, pht('Configuration'));
         $variables = $build_target->getVariables();
         $properties = new PHUIPropertyListView();
         $properties->addRawContent($this->buildProperties($variables));
         $target_box->addPropertyList($properties, pht('Variables'));
         $artifacts_tab = $this->buildArtifacts($build_target, $target_artifacts);
         $properties = new PHUIPropertyListView();
         $properties->addRawContent($artifacts_tab);
         $target_box->addPropertyList($properties, pht('Artifacts'));
         $build_messages = idx($messages, $build_target->getPHID(), array());
         $properties = new PHUIPropertyListView();
         $properties->addRawContent($this->buildMessages($build_messages));
         $target_box->addPropertyList($properties, pht('Messages'));
         $properties = new PHUIPropertyListView();
         $properties->addProperty(pht('Build Target ID'), $build_target->getID());
         $properties->addProperty(pht('Build Target PHID'), $build_target->getPHID());
         $target_box->addPropertyList($properties, pht('Metadata'));
         $targets[] = $target_box;
         $targets[] = $this->buildLog($build, $build_target);
     }
     $timeline = $this->buildTransactionTimeline($build, new HarbormasterBuildTransactionQuery());
     $timeline->setShouldTerminate(true);
     return $this->buildApplicationPage(array($crumbs, $box, $targets, $timeline), array('title' => $title));
 }
 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);
         $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', 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;
 }