Path: blob/master/src/applications/harbormaster/controller/HarbormasterBuildViewController.php
12256 views
<?php12final class HarbormasterBuildViewController3extends HarbormasterController {45public function shouldAllowPublic() {6return true;7}89public function handleRequest(AphrontRequest $request) {10$request = $this->getRequest();11$viewer = $request->getUser();1213$id = $request->getURIData('id');1415$build = id(new HarbormasterBuildQuery())16->setViewer($viewer)17->withIDs(array($id))18->executeOne();19if (!$build) {20return new Aphront404Response();21}2223require_celerity_resource('harbormaster-css');2425$title = pht('Build %d', $id);26$warnings = array();2728$page_header = id(new PHUIHeaderView())29->setHeader($title)30->setUser($viewer)31->setPolicyObject($build)32->setHeaderIcon('fa-cubes');3334$status = $build->getBuildPendingStatusObject();3536$status_icon = $status->getIconIcon();37$status_color = $status->getIconColor();38$status_name = $status->getName();3940$page_header->setStatus($status_icon, $status_color, $status_name);4142$max_generation = (int)$build->getBuildGeneration();43if ($max_generation === 0) {44$min_generation = 0;45} else {46$min_generation = 1;47}4849if ($build->isRestarting()) {50$max_generation = $max_generation + 1;51}5253$generation = $request->getURIData('generation');54if ($generation === null) {55$generation = $max_generation;56} else {57$generation = (int)$generation;58}5960if ($generation < $min_generation || $generation > $max_generation) {61return new Aphront404Response();62}6364if ($generation < $max_generation) {65$warnings[] = pht(66'You are viewing an older run of this build. %s',67phutil_tag(68'a',69array(70'href' => $build->getURI(),71),72pht('View Current Build')));73}7475$curtain = $this->buildCurtainView($build);76$properties = $this->buildPropertyList($build);77$history = $this->buildHistoryTable(78$build,79$generation,80$min_generation,81$max_generation);8283$crumbs = $this->buildApplicationCrumbs();84$this->addBuildableCrumb($crumbs, $build->getBuildable());85$crumbs->addTextCrumb($title);86$crumbs->setBorder(true);8788$build_targets = id(new HarbormasterBuildTargetQuery())89->setViewer($viewer)90->needBuildSteps(true)91->withBuildPHIDs(array($build->getPHID()))92->withBuildGenerations(array($generation))93->execute();9495if ($build_targets) {96$messages = id(new HarbormasterBuildMessageQuery())97->setViewer($viewer)98->withReceiverPHIDs(mpull($build_targets, 'getPHID'))99->execute();100$messages = mgroup($messages, 'getReceiverPHID');101} else {102$messages = array();103}104105if ($build_targets) {106$artifacts = id(new HarbormasterBuildArtifactQuery())107->setViewer($viewer)108->withBuildTargetPHIDs(mpull($build_targets, 'getPHID'))109->execute();110$artifacts = msort($artifacts, 'getArtifactKey');111$artifacts = mgroup($artifacts, 'getBuildTargetPHID');112} else {113$artifacts = array();114}115116117$targets = array();118foreach ($build_targets as $build_target) {119$header = id(new PHUIHeaderView())120->setHeader($build_target->getName())121->setUser($viewer)122->setHeaderIcon('fa-bullseye');123124$target_box = id(new PHUIObjectBoxView())125->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)126->setHeader($header);127128$tab_group = new PHUITabGroupView();129$target_box->addTabGroup($tab_group);130131$property_list = new PHUIPropertyListView();132133$target_artifacts = idx($artifacts, $build_target->getPHID(), array());134135$links = array();136$type_uri = HarbormasterURIArtifact::ARTIFACTCONST;137foreach ($target_artifacts as $artifact) {138if ($artifact->getArtifactType() == $type_uri) {139$impl = $artifact->getArtifactImplementation();140if ($impl->isExternalLink()) {141$links[] = $impl->renderLink();142}143}144}145146if ($links) {147$links = phutil_implode_html(phutil_tag('br'), $links);148$property_list->addProperty(149pht('External Link'),150$links);151}152153$status_view = new PHUIStatusListView();154$item = new PHUIStatusItemView();155156$status = $build_target->getTargetStatus();157$status_name =158HarbormasterBuildTarget::getBuildTargetStatusName($status);159$icon = HarbormasterBuildTarget::getBuildTargetStatusIcon($status);160$color = HarbormasterBuildTarget::getBuildTargetStatusColor($status);161162$item->setTarget($status_name);163$item->setIcon($icon, $color);164$status_view->addItem($item);165166$when = array();167$started = $build_target->getDateStarted();168$now = PhabricatorTime::getNow();169if ($started) {170$ended = $build_target->getDateCompleted();171if ($ended) {172$when[] = pht(173'Completed at %s',174phabricator_datetime($ended, $viewer));175176$duration = ($ended - $started);177if ($duration) {178$when[] = pht(179'Built for %s',180phutil_format_relative_time_detailed($duration));181} else {182$when[] = pht('Built instantly');183}184} else {185$when[] = pht(186'Started at %s',187phabricator_datetime($started, $viewer));188$duration = ($now - $started);189if ($duration) {190$when[] = pht(191'Running for %s',192phutil_format_relative_time_detailed($duration));193}194}195} else {196$created = $build_target->getDateCreated();197$when[] = pht(198'Queued at %s',199phabricator_datetime($started, $viewer));200$duration = ($now - $created);201if ($duration) {202$when[] = pht(203'Waiting for %s',204phutil_format_relative_time_detailed($duration));205}206}207208$property_list->addProperty(209pht('When'),210phutil_implode_html(" \xC2\xB7 ", $when));211212$property_list->addProperty(pht('Status'), $status_view);213214$tab_group->addTab(215id(new PHUITabView())216->setName(pht('Overview'))217->setKey('overview')218->appendChild($property_list));219220$step = $build_target->getBuildStep();221222if ($step) {223$description = $step->getDescription();224if ($description) {225$description = new PHUIRemarkupView($viewer, $description);226$property_list->addSectionHeader(227pht('Description'), PHUIPropertyListView::ICON_SUMMARY);228$property_list->addTextContent($description);229}230} else {231$target_box->setFormErrors(232array(233pht(234'This build step has since been deleted on the build plan. '.235'Some information may be omitted.'),236));237}238239$details = $build_target->getDetails();240$property_list = new PHUIPropertyListView();241foreach ($details as $key => $value) {242$property_list->addProperty($key, $value);243}244$tab_group->addTab(245id(new PHUITabView())246->setName(pht('Configuration'))247->setKey('configuration')248->appendChild($property_list));249250$variables = $build_target->getVariables();251$variables_tab = $this->buildProperties($variables);252$tab_group->addTab(253id(new PHUITabView())254->setName(pht('Variables'))255->setKey('variables')256->appendChild($variables_tab));257258$artifacts_tab = $this->buildArtifacts($build_target, $target_artifacts);259$tab_group->addTab(260id(new PHUITabView())261->setName(pht('Artifacts'))262->setKey('artifacts')263->appendChild($artifacts_tab));264265$build_messages = idx($messages, $build_target->getPHID(), array());266$messages_tab = $this->buildMessages($build_messages);267$tab_group->addTab(268id(new PHUITabView())269->setName(pht('Messages'))270->setKey('messages')271->appendChild($messages_tab));272273$property_list = new PHUIPropertyListView();274$property_list->addProperty(275pht('Build Target ID'),276$build_target->getID());277$property_list->addProperty(278pht('Build Target PHID'),279$build_target->getPHID());280281$tab_group->addTab(282id(new PHUITabView())283->setName(pht('Metadata'))284->setKey('metadata')285->appendChild($property_list));286287$targets[] = $target_box;288289$targets[] = $this->buildLog($build, $build_target, $generation);290}291292$timeline = $this->buildTransactionTimeline(293$build,294new HarbormasterBuildTransactionQuery());295$timeline->setShouldTerminate(true);296297if ($warnings) {298$warnings = id(new PHUIInfoView())299->setErrors($warnings)300->setSeverity(PHUIInfoView::SEVERITY_WARNING);301} else {302$warnings = null;303}304305$view = id(new PHUITwoColumnView())306->setHeader($page_header)307->setCurtain($curtain)308->setMainColumn(309array(310$warnings,311$properties,312$history,313$targets,314$timeline,315));316317return $this->newPage()318->setTitle($title)319->setCrumbs($crumbs)320->appendChild($view);321322}323324private function buildArtifacts(325HarbormasterBuildTarget $build_target,326array $artifacts) {327$viewer = $this->getViewer();328329$rows = array();330foreach ($artifacts as $artifact) {331$impl = $artifact->getArtifactImplementation();332333if ($impl) {334$summary = $impl->renderArtifactSummary($viewer);335$type_name = $impl->getArtifactTypeName();336} else {337$summary = pht('<Unknown Artifact Type>');338$type_name = $artifact->getType();339}340341$rows[] = array(342$artifact->getArtifactKey(),343$type_name,344$summary,345);346}347348$table = id(new AphrontTableView($rows))349->setNoDataString(pht('This target has no associated artifacts.'))350->setHeaders(351array(352pht('Key'),353pht('Type'),354pht('Summary'),355))356->setColumnClasses(357array(358'pri',359'',360'wide',361));362363return $table;364}365366private function buildLog(367HarbormasterBuild $build,368HarbormasterBuildTarget $build_target,369$generation) {370371$request = $this->getRequest();372$viewer = $request->getUser();373$limit = $request->getInt('l', 25);374375$logs = id(new HarbormasterBuildLogQuery())376->setViewer($viewer)377->withBuildTargetPHIDs(array($build_target->getPHID()))378->execute();379380$empty_logs = array();381382$log_boxes = array();383foreach ($logs as $log) {384$start = 1;385$lines = preg_split("/\r\n|\r|\n/", $log->getLogText());386if ($limit !== 0) {387$start = count($lines) - $limit;388if ($start >= 1) {389$lines = array_slice($lines, -$limit, $limit);390} else {391$start = 1;392}393}394395$id = null;396$is_empty = false;397if (count($lines) === 1 && trim($lines[0]) === '') {398// Prevent Harbormaster from showing empty build logs.399$id = celerity_generate_unique_node_id();400$empty_logs[] = $id;401$is_empty = true;402}403404$log_view = new ShellLogView();405$log_view->setLines($lines);406$log_view->setStart($start);407408$subheader = $this->createLogHeader($build, $log, $limit, $generation);409410$prototype_view = id(new PHUIButtonView())411->setTag('a')412->setHref($log->getURI())413->setIcon('fa-file-text-o')414->setText(pht('New View (Prototype)'));415416$header = id(new PHUIHeaderView())417->setHeader(pht(418'Build Log %d (%s - %s)',419$log->getID(),420$log->getLogSource(),421$log->getLogType()))422->addActionLink($prototype_view)423->setSubheader($subheader)424->setUser($viewer);425426$log_box = id(new PHUIObjectBoxView())427->setHeader($header)428->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)429->setForm($log_view);430431if ($is_empty) {432$log_box = phutil_tag(433'div',434array(435'style' => 'display: none',436'id' => $id,437),438$log_box);439}440441$log_boxes[] = $log_box;442}443444if ($empty_logs) {445$hide_id = celerity_generate_unique_node_id();446447Javelin::initBehavior('phabricator-reveal-content');448449$expand = phutil_tag(450'div',451array(452'id' => $hide_id,453'class' => 'harbormaster-empty-logs-are-hidden',454),455array(456pht(457'%s empty logs are hidden.',458phutil_count($empty_logs)),459' ',460javelin_tag(461'a',462array(463'href' => '#',464'sigil' => 'reveal-content',465'meta' => array(466'showIDs' => $empty_logs,467'hideIDs' => array($hide_id),468),469),470pht('Show all logs.')),471));472473array_unshift($log_boxes, $expand);474}475476return $log_boxes;477}478479private function createLogHeader($build, $log, $limit, $generation) {480$options = array(481array(482'n' => 25,483),484array(485'n' => 50,486),487array(488'n' => 100,489),490array(491'n' => 0,492'label' => pht('Unlimited'),493),494);495496$base_uri = id(new PhutilURI($build->getURI().$generation.'/'));497498$links = array();499foreach ($options as $option) {500$n = $option['n'];501$label = idx($option, 'label', $n);502503$is_selected = ($limit == $n);504if ($is_selected) {505$links[] = phutil_tag(506'strong',507array(),508$label);509} else {510$links[] = phutil_tag(511'a',512array(513'href' => (string)$base_uri->alter('l', $n),514),515$label);516}517}518519return phutil_tag(520'span',521array(),522array(523phutil_implode_html(' - ', $links),524' ',525pht('Lines'),526));527}528529private function buildCurtainView(HarbormasterBuild $build) {530$viewer = $this->getViewer();531$id = $build->getID();532533$curtain = $this->newCurtainView($build);534535$messages = array(536new HarbormasterBuildMessageRestartTransaction(),537new HarbormasterBuildMessagePauseTransaction(),538new HarbormasterBuildMessageResumeTransaction(),539new HarbormasterBuildMessageAbortTransaction(),540);541542foreach ($messages as $message) {543$can_send = $message->canSendMessage($viewer, $build);544545$message_uri = urisprintf(546'/build/%s/%d/',547$message->getHarbormasterBuildMessageType(),548$id);549$message_uri = $this->getApplicationURI($message_uri);550551$action = id(new PhabricatorActionView())552->setName($message->getHarbormasterBuildMessageName())553->setIcon($message->getIcon())554->setHref($message_uri)555->setDisabled(!$can_send)556->setWorkflow(true);557558$curtain->addAction($action);559}560561return $curtain;562}563564private function buildPropertyList(HarbormasterBuild $build) {565$viewer = $this->getViewer();566567$properties = id(new PHUIPropertyListView())568->setUser($viewer);569570$handles = id(new PhabricatorHandleQuery())571->setViewer($viewer)572->withPHIDs(array(573$build->getBuildablePHID(),574$build->getBuildPlanPHID(),575))576->execute();577578$properties->addProperty(579pht('Buildable'),580$handles[$build->getBuildablePHID()]->renderLink());581582$properties->addProperty(583pht('Build Plan'),584$handles[$build->getBuildPlanPHID()]->renderLink());585586return id(new PHUIObjectBoxView())587->setHeaderText(pht('Properties'))588->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)589->appendChild($properties);590591}592593private function buildHistoryTable(594HarbormasterBuild $build,595$generation,596$min_generation,597$max_generation) {598599if ($max_generation === $min_generation) {600return null;601}602603$viewer = $this->getViewer();604605$uri = $build->getURI();606607$rows = array();608$rowc = array();609for ($ii = $max_generation; $ii >= $min_generation; $ii--) {610if ($generation == $ii) {611$rowc[] = 'highlighted';612} else {613$rowc[] = null;614}615616$rows[] = array(617phutil_tag(618'a',619array(620'href' => $uri.$ii.'/',621),622pht('Run %d', $ii)),623);624}625626$table = id(new AphrontTableView($rows))627->setColumnClasses(628array(629'pri wide',630))631->setRowClasses($rowc);632633return id(new PHUIObjectBoxView())634->setHeaderText(pht('History'))635->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)636->setTable($table);637}638639private function buildMessages(array $messages) {640$viewer = $this->getRequest()->getUser();641642if ($messages) {643$handles = id(new PhabricatorHandleQuery())644->setViewer($viewer)645->withPHIDs(mpull($messages, 'getAuthorPHID'))646->execute();647} else {648$handles = array();649}650651$rows = array();652foreach ($messages as $message) {653$rows[] = array(654$message->getID(),655$handles[$message->getAuthorPHID()]->renderLink(),656$message->getType(),657$message->getIsConsumed() ? pht('Consumed') : null,658phabricator_datetime($message->getDateCreated(), $viewer),659);660}661662$table = new AphrontTableView($rows);663$table->setNoDataString(pht('No messages for this build target.'));664$table->setHeaders(665array(666pht('ID'),667pht('From'),668pht('Type'),669pht('Consumed'),670pht('Received'),671));672$table->setColumnClasses(673array(674'',675'',676'wide',677'',678'date',679));680681return $table;682}683684private function buildProperties(array $properties) {685ksort($properties);686687$rows = array();688foreach ($properties as $key => $value) {689$rows[] = array(690$key,691$value,692);693}694695$table = id(new AphrontTableView($rows))696->setHeaders(697array(698pht('Key'),699pht('Value'),700))701->setColumnClasses(702array(703'pri right',704'wide',705));706707return $table;708}709710}711712713