mirror of
https://we.phorge.it/source/phorge.git
synced 2025-02-26 05:29:06 +01:00
Summary: Ref T10004. After D14804, we get this behavior by default and no longer need to set it explicitly. (If some endpoint did eventually need to set it explicitly, it could just change what it passes to `setHref()`, but I believe we currently have no such endpoints and do not foresee ever having any.) Test Plan: - As a logged out user, clicked various links in Differential, Maniphest, Files, etc., always got redirected to a sensible place after login. - Grepped for `setObjectURI()`, `getObjectURI()` (there are a few remaining callsites, but to a different method with the same name in Doorkeeper). Reviewers: chad Reviewed By: chad Subscribers: hach-que Maniphest Tasks: T10004 Differential Revision: https://secure.phabricator.com/D14805
625 lines
17 KiB
PHP
625 lines
17 KiB
PHP
<?php
|
|
|
|
final class HarbormasterBuildViewController
|
|
extends HarbormasterController {
|
|
|
|
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(" \xC2\xB7 ", $when));
|
|
|
|
$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'), PHUIPropertyListView::ICON_SUMMARY);
|
|
$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();
|
|
$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,
|
|
));
|
|
}
|
|
|
|
private function buildArtifacts(
|
|
HarbormasterBuildTarget $build_target,
|
|
array $artifacts) {
|
|
$viewer = $this->getViewer();
|
|
|
|
$rows = array();
|
|
foreach ($artifacts as $artifact) {
|
|
$impl = $artifact->getArtifactImplementation();
|
|
|
|
if ($impl) {
|
|
$summary = $impl->renderArtifactSummary($viewer);
|
|
$type_name = $impl->getArtifactTypeName();
|
|
} else {
|
|
$summary = pht('<Unknown Artifact Type>');
|
|
$type_name = $artifact->getType();
|
|
}
|
|
|
|
$rows[] = array(
|
|
$artifact->getArtifactKey(),
|
|
$type_name,
|
|
$summary,
|
|
);
|
|
}
|
|
|
|
$table = id(new AphrontTableView($rows))
|
|
->setNoDataString(pht('This target has no associated artifacts.'))
|
|
->setHeaders(
|
|
array(
|
|
pht('Key'),
|
|
pht('Type'),
|
|
pht('Summary'),
|
|
))
|
|
->setColumnClasses(
|
|
array(
|
|
'pri',
|
|
'',
|
|
'wide',
|
|
));
|
|
|
|
return $table;
|
|
}
|
|
|
|
private function buildLog(
|
|
HarbormasterBuild $build,
|
|
HarbormasterBuildTarget $build_target) {
|
|
|
|
$request = $this->getRequest();
|
|
$viewer = $request->getUser();
|
|
$limit = $request->getInt('l', 25);
|
|
|
|
$logs = id(new HarbormasterBuildLogQuery())
|
|
->setViewer($viewer)
|
|
->withBuildTargetPHIDs(array($build_target->getPHID()))
|
|
->execute();
|
|
|
|
$empty_logs = array();
|
|
|
|
$log_boxes = array();
|
|
foreach ($logs as $log) {
|
|
$start = 1;
|
|
$lines = preg_split("/\r\n|\r|\n/", $log->getLogText());
|
|
if ($limit !== 0) {
|
|
$start = count($lines) - $limit;
|
|
if ($start >= 1) {
|
|
$lines = array_slice($lines, -$limit, $limit);
|
|
} else {
|
|
$start = 1;
|
|
}
|
|
}
|
|
|
|
$id = null;
|
|
$is_empty = false;
|
|
if (count($lines) === 1 && trim($lines[0]) === '') {
|
|
// Prevent Harbormaster from showing empty build logs.
|
|
$id = celerity_generate_unique_node_id();
|
|
$empty_logs[] = $id;
|
|
$is_empty = true;
|
|
}
|
|
|
|
$log_view = new ShellLogView();
|
|
$log_view->setLines($lines);
|
|
$log_view->setStart($start);
|
|
|
|
$header = id(new PHUIHeaderView())
|
|
->setHeader(pht(
|
|
'Build Log %d (%s - %s)',
|
|
$log->getID(),
|
|
$log->getLogSource(),
|
|
$log->getLogType()))
|
|
->setSubheader($this->createLogHeader($build, $log))
|
|
->setUser($viewer);
|
|
|
|
$log_box = id(new PHUIObjectBoxView())
|
|
->setHeader($header)
|
|
->setForm($log_view);
|
|
|
|
if ($is_empty) {
|
|
$log_box = phutil_tag(
|
|
'div',
|
|
array(
|
|
'style' => 'display: none',
|
|
'id' => $id,
|
|
),
|
|
$log_box);
|
|
}
|
|
|
|
$log_boxes[] = $log_box;
|
|
}
|
|
|
|
if ($empty_logs) {
|
|
$hide_id = celerity_generate_unique_node_id();
|
|
|
|
Javelin::initBehavior('phabricator-reveal-content');
|
|
|
|
$expand = phutil_tag(
|
|
'div',
|
|
array(
|
|
'id' => $hide_id,
|
|
'class' => 'harbormaster-empty-logs-are-hidden mlr mlt mll',
|
|
),
|
|
array(
|
|
pht(
|
|
'%s empty logs are hidden.',
|
|
phutil_count($empty_logs)),
|
|
' ',
|
|
javelin_tag(
|
|
'a',
|
|
array(
|
|
'href' => '#',
|
|
'sigil' => 'reveal-content',
|
|
'meta' => array(
|
|
'showIDs' => $empty_logs,
|
|
'hideIDs' => array($hide_id),
|
|
),
|
|
),
|
|
pht('Show all logs.')),
|
|
));
|
|
|
|
array_unshift($log_boxes, $expand);
|
|
}
|
|
|
|
return $log_boxes;
|
|
}
|
|
|
|
private function createLogHeader($build, $log) {
|
|
$request = $this->getRequest();
|
|
$limit = $request->getInt('l', 25);
|
|
|
|
$lines_25 = $this->getApplicationURI('/build/'.$build->getID().'/?l=25');
|
|
$lines_50 = $this->getApplicationURI('/build/'.$build->getID().'/?l=50');
|
|
$lines_100 =
|
|
$this->getApplicationURI('/build/'.$build->getID().'/?l=100');
|
|
$lines_0 = $this->getApplicationURI('/build/'.$build->getID().'/?l=0');
|
|
|
|
$link_25 = phutil_tag('a', array('href' => $lines_25), pht('25'));
|
|
$link_50 = phutil_tag('a', array('href' => $lines_50), pht('50'));
|
|
$link_100 = phutil_tag('a', array('href' => $lines_100), pht('100'));
|
|
$link_0 = phutil_tag('a', array('href' => $lines_0), pht('Unlimited'));
|
|
|
|
if ($limit === 25) {
|
|
$link_25 = phutil_tag('strong', array(), $link_25);
|
|
} else if ($limit === 50) {
|
|
$link_50 = phutil_tag('strong', array(), $link_50);
|
|
} else if ($limit === 100) {
|
|
$link_100 = phutil_tag('strong', array(), $link_100);
|
|
} else if ($limit === 0) {
|
|
$link_0 = phutil_tag('strong', array(), $link_0);
|
|
}
|
|
|
|
return phutil_tag(
|
|
'span',
|
|
array(),
|
|
array(
|
|
$link_25,
|
|
' - ',
|
|
$link_50,
|
|
' - ',
|
|
$link_100,
|
|
' - ',
|
|
$link_0,
|
|
' Lines',
|
|
));
|
|
}
|
|
|
|
private function buildActionList(HarbormasterBuild $build) {
|
|
$request = $this->getRequest();
|
|
$viewer = $request->getUser();
|
|
$id = $build->getID();
|
|
|
|
$list = id(new PhabricatorActionListView())
|
|
->setUser($viewer)
|
|
->setObject($build);
|
|
|
|
$can_restart = $build->canRestartBuild();
|
|
$can_pause = $build->canPauseBuild();
|
|
$can_resume = $build->canResumeBuild();
|
|
$can_abort = $build->canAbortBuild();
|
|
|
|
$list->addAction(
|
|
id(new PhabricatorActionView())
|
|
->setName(pht('Restart Build'))
|
|
->setIcon('fa-repeat')
|
|
->setHref($this->getApplicationURI('/build/restart/'.$id.'/'))
|
|
->setDisabled(!$can_restart)
|
|
->setWorkflow(true));
|
|
|
|
if ($build->canResumeBuild()) {
|
|
$list->addAction(
|
|
id(new PhabricatorActionView())
|
|
->setName(pht('Resume Build'))
|
|
->setIcon('fa-play')
|
|
->setHref($this->getApplicationURI('/build/resume/'.$id.'/'))
|
|
->setDisabled(!$can_resume)
|
|
->setWorkflow(true));
|
|
} else {
|
|
$list->addAction(
|
|
id(new PhabricatorActionView())
|
|
->setName(pht('Pause Build'))
|
|
->setIcon('fa-pause')
|
|
->setHref($this->getApplicationURI('/build/pause/'.$id.'/'))
|
|
->setDisabled(!$can_pause)
|
|
->setWorkflow(true));
|
|
}
|
|
|
|
$list->addAction(
|
|
id(new PhabricatorActionView())
|
|
->setName(pht('Abort Build'))
|
|
->setIcon('fa-exclamation-triangle')
|
|
->setHref($this->getApplicationURI('/build/abort/'.$id.'/'))
|
|
->setDisabled(!$can_abort)
|
|
->setWorkflow(true));
|
|
|
|
return $list;
|
|
}
|
|
|
|
private function buildPropertyLists(
|
|
PHUIObjectBoxView $box,
|
|
HarbormasterBuild $build,
|
|
PhabricatorActionListView $actions) {
|
|
$request = $this->getRequest();
|
|
$viewer = $request->getUser();
|
|
|
|
$properties = id(new PHUIPropertyListView())
|
|
->setUser($viewer)
|
|
->setObject($build)
|
|
->setActionList($actions);
|
|
$box->addPropertyList($properties);
|
|
|
|
$handles = id(new PhabricatorHandleQuery())
|
|
->setViewer($viewer)
|
|
->withPHIDs(array(
|
|
$build->getBuildablePHID(),
|
|
$build->getBuildPlanPHID(),
|
|
))
|
|
->execute();
|
|
|
|
$properties->addProperty(
|
|
pht('Buildable'),
|
|
$handles[$build->getBuildablePHID()]->renderLink());
|
|
|
|
$properties->addProperty(
|
|
pht('Build Plan'),
|
|
$handles[$build->getBuildPlanPHID()]->renderLink());
|
|
|
|
$properties->addProperty(
|
|
pht('Restarts'),
|
|
$build->getBuildGeneration());
|
|
|
|
$properties->addProperty(
|
|
pht('Status'),
|
|
$this->getStatus($build));
|
|
}
|
|
|
|
private function getStatus(HarbormasterBuild $build) {
|
|
$status_view = new PHUIStatusListView();
|
|
|
|
$item = new PHUIStatusItemView();
|
|
|
|
if ($build->isPausing()) {
|
|
$status_name = pht('Pausing');
|
|
$icon = PHUIStatusItemView::ICON_RIGHT;
|
|
$color = 'dark';
|
|
} else {
|
|
$status = $build->getBuildStatus();
|
|
$status_name =
|
|
HarbormasterBuild::getBuildStatusName($status);
|
|
$icon = HarbormasterBuild::getBuildStatusIcon($status);
|
|
$color = HarbormasterBuild::getBuildStatusColor($status);
|
|
}
|
|
|
|
$item->setTarget($status_name);
|
|
$item->setIcon($icon, $color);
|
|
$status_view->addItem($item);
|
|
|
|
return $status_view;
|
|
}
|
|
|
|
private function buildMessages(array $messages) {
|
|
$viewer = $this->getRequest()->getUser();
|
|
|
|
if ($messages) {
|
|
$handles = id(new PhabricatorHandleQuery())
|
|
->setViewer($viewer)
|
|
->withPHIDs(mpull($messages, 'getAuthorPHID'))
|
|
->execute();
|
|
} else {
|
|
$handles = array();
|
|
}
|
|
|
|
$rows = array();
|
|
foreach ($messages as $message) {
|
|
$rows[] = array(
|
|
$message->getID(),
|
|
$handles[$message->getAuthorPHID()]->renderLink(),
|
|
$message->getType(),
|
|
$message->getIsConsumed() ? pht('Consumed') : null,
|
|
phabricator_datetime($message->getDateCreated(), $viewer),
|
|
);
|
|
}
|
|
|
|
$table = new AphrontTableView($rows);
|
|
$table->setNoDataString(pht('No messages for this build target.'));
|
|
$table->setHeaders(
|
|
array(
|
|
pht('ID'),
|
|
pht('From'),
|
|
pht('Type'),
|
|
pht('Consumed'),
|
|
pht('Received'),
|
|
));
|
|
$table->setColumnClasses(
|
|
array(
|
|
'',
|
|
'',
|
|
'wide',
|
|
'',
|
|
'date',
|
|
));
|
|
|
|
return $table;
|
|
}
|
|
|
|
private function buildProperties(array $properties) {
|
|
ksort($properties);
|
|
|
|
$rows = array();
|
|
foreach ($properties as $key => $value) {
|
|
$rows[] = array(
|
|
$key,
|
|
$value,
|
|
);
|
|
}
|
|
|
|
$table = id(new AphrontTableView($rows))
|
|
->setHeaders(
|
|
array(
|
|
pht('Key'),
|
|
pht('Value'),
|
|
))
|
|
->setColumnClasses(
|
|
array(
|
|
'pri right',
|
|
'wide',
|
|
));
|
|
|
|
return $table;
|
|
}
|
|
|
|
}
|