Skip to content

Commit 17aaa8d

Browse files
committed
feat: Create a proper build step details page
Build steps are, in my view, an important concept that Hydra doesn't yet give enough attention. This is my attempt to rectify that. A new detail page at `/build/:id/step/:stepnr` shows output paths, derivation, system, machine, duration, status, and log links, reusing the shared blocks extracted in the previous commit. Clicking anywhere in the build steps table row now navigates to this page, instead of the step log page. The step log pages also have a link back to this page. Also reflecting giving build steps more status, introduce `Hydra::Controller::BuildStep`, chained off `/build/buildChain`, giving them a first-class controller. For a bit of back story, note that in the future, we might switch associating build steps more with derivations than builds. This reflects that we don't really care *why* something was scheduled (the build/root derivation) as much as *what* was scheduled, especially when multiple new builds would "race" to schedule the same derivation. It also bodes well for a future where Hydra can act as a Nix derivation that receives ad-hoc build requests, so the "build" in this case would be rather lacking in metadata. Both these scenarios point to a world where `BuildStep`s become more important than `Build`s, building (ahem) atop this refactor. The step log handling is now better suited to live as part of this controller. Accordingly, it is moved from `/build/:id/nixlog/:stepnr[/raw|/tail]` to `/build/:id/step/:stepnr/log[/raw|/tail]`. The old `nixlog` URLs are preserved as 301 redirects in `Build.pm`. All templates updated to generate the new canonical URLs.
1 parent 93432dc commit 17aaa8d

8 files changed

Lines changed: 115 additions & 19 deletions

File tree

subprojects/hydra/lib/Hydra/Controller/Build.pm

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -134,17 +134,13 @@ sub constituents_GET {
134134
}
135135

136136

137-
sub view_nixlog : Chained('buildChain') PathPart('nixlog') {
137+
# Redirect old /build/:id/nixlog/:stepnr[/:mode] URLs to new canonical paths
138+
sub nixlog_redirect : Chained('buildChain') PathPart('nixlog') {
138139
my ($self, $c, $stepnr, $mode) = @_;
139140

140-
my $step = $c->stash->{build}->buildsteps->find({stepnr => $stepnr});
141-
notFound($c, "Build doesn't have a build step $stepnr.") if !defined $step;
142-
143-
$c->stash->{step} = $step;
144-
145-
my $drvPath = $step->drvpath;
146-
my $log_uri = $c->uri_for($c->controller('Root')->action_for("log"), [WWW::Form::UrlEncoded::PP::url_encode(basename($drvPath))]);
147-
showLog($c, $mode, $log_uri);
141+
my @path = ('/build', $c->stash->{id}, 'step', $stepnr, 'log');
142+
push @path, $mode if defined $mode;
143+
$c->res->redirect($c->uri_for(@path), 301);
148144
}
149145

150146

@@ -167,7 +163,6 @@ sub view_runcommandlog : Chained('buildChain') PathPart('runcommandlog') {
167163
}
168164

169165

170-
171166
sub defaultUriForProduct {
172167
my ($self, $c, $product, @path) = @_;
173168
my $x = $product->productnr
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package Hydra::Controller::BuildStep;
2+
3+
use utf8;
4+
use strict;
5+
use warnings;
6+
use base 'Hydra::Base::Controller::REST';
7+
use Hydra::Helper::CatalystUtils;
8+
use Hydra::Helper::LogEndpoints;
9+
use File::Basename;
10+
use WWW::Form::UrlEncoded::PP qw();
11+
12+
13+
sub buildStepChain :Chained('/build/buildChain') :PathPart('step') :CaptureArgs(1) {
14+
my ($self, $c, $stepnr) = @_;
15+
16+
my $step = $c->stash->{build}->buildsteps->find({stepnr => $stepnr});
17+
notFound($c, "Build doesn't have a build step $stepnr.") if !defined $step;
18+
19+
$c->stash->{step} = $step;
20+
}
21+
22+
23+
sub buildStep : Chained('buildStepChain') PathPart('') Args(0) {
24+
my ($self, $c) = @_;
25+
$c->stash->{template} = 'build-step.tt';
26+
}
27+
28+
29+
sub view_nixlog : Chained('buildStepChain') PathPart('log') {
30+
my ($self, $c, $mode) = @_;
31+
32+
my $drvPath = $c->stash->{step}->drvpath;
33+
my $log_uri = $c->uri_for($c->controller('Root')->action_for("log"), [WWW::Form::UrlEncoded::PP::url_encode(basename($drvPath))]);
34+
showLog($c, $mode, $log_uri);
35+
}
36+
37+
38+
1;
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
[%
2+
buildUrl = c.uri_for('/build', build.id);
3+
%]
4+
[% WRAPPER layout.tt
5+
title="Step ${step.stepnr} of build ${build.id} of job " _ makeNameTextForJob(build.jobset, job)
6+
titleHTML="Step ${step.stepnr}"
7+
_ " of <a href=\"$buildUrl\">build ${build.id}</a>"
8+
_ " of job " _ linkToJob(build.jobset, job)
9+
%]
10+
[% PROCESS common.tt %]
11+
[% PROCESS "build-common.tt" %]
12+
[% USE HTML %]
13+
14+
[% logUrl = c.uri_for('/build' build.id 'step' step.stepnr 'log') %]
15+
[% has_log = buildStepLogExists(step) %]
16+
17+
<table class="info-table">
18+
<tr>
19+
<th>Type:</th>
20+
<td>[% IF step.type == 0 %]Build[% ELSE %]Substitution[% END %]</td>
21+
</tr>
22+
<tr>
23+
<th>Derivation:</th>
24+
<td><tt>[% step.drvpath | html %]</tt></td>
25+
</tr>
26+
<tr>
27+
<th>Output store paths:</th>
28+
<td>[% INCLUDE renderOutputsTable outputs=step.buildstepoutputs %]</td>
29+
</tr>
30+
[% IF step.system %]
31+
<tr>
32+
<th>System:</th>
33+
<td><tt>[% step.system | html %]</tt></td>
34+
</tr>
35+
[% END %]
36+
<tr>
37+
<th>Machine:</th>
38+
<td>[% INCLUDE renderStepMachine %]</td>
39+
</tr>
40+
<tr>
41+
<th>Duration:</th>
42+
<td>[% INCLUDE renderStepDuration %]</td>
43+
</tr>
44+
<tr>
45+
<th>Status:</th>
46+
<td>
47+
[% INCLUDE renderStepStatus %]
48+
[% IF step.propagatedfrom %]
49+
(propagated from [% INCLUDE renderBuildIdLink id=step.propagatedfrom.get_column('id') %])
50+
[% END %]
51+
</td>
52+
</tr>
53+
[% IF has_log %]
54+
<tr>
55+
<th>Logfile:</th>
56+
<td>[% INCLUDE renderLogButtons url=logUrl %]</td>
57+
</tr>
58+
[% END %]
59+
</table>
60+
61+
[% END %]

subprojects/hydra/root/build.tt

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,10 @@ END;
3030
[% IF ( type == "All" ) || ( type == "Failed" && step.busy == 0 && step.status != 0 ) || ( type == "Running" && step.busy != 0 ) %]
3131
[% has_log = seen.${step.drvpath} ? 0 : buildStepLogExists(step);
3232
seen.${step.drvpath} = 1;
33-
log = c.uri_for('/build' build.id 'nixlog' step.stepnr); %]
33+
stepUrl = c.uri_for('/build' build.id 'step' step.stepnr);
34+
logUrl = c.uri_for('/build' build.id 'step' step.stepnr 'log'); %]
3435
<tr>
35-
<td>[% HTML.escape(step.stepnr) %]</td>
36+
<td><a class="row-link" [% HTML.attributes(href => stepUrl) %]>[% HTML.escape(step.stepnr) %]</a></td>
3637
<td>
3738
[% IF step.type == 0 %]
3839
Build of <tt>[% INCLUDE renderOutputs outputs=step.buildstepoutputs %]</tt>
@@ -44,7 +45,7 @@ END;
4445
<td>[% INCLUDE renderStepMachine %]</td>
4546
<td class="step-status">
4647
[% INCLUDE renderStepStatus %]
47-
[%%] [%+ IF has_log; INCLUDE renderLogLinks url=log inRow=1; END %]
48+
[%%] [%+ IF has_log; INCLUDE renderLogLinks url=logUrl; END %]
4849
[%+ IF step.propagatedfrom; %](propagated from [% INCLUDE renderBuildIdLink id=step.propagatedfrom.get_column('id') %])[% END %]
4950
</td>
5051
</tr>
@@ -177,7 +178,7 @@ END;
177178
<tr>
178179
<th>Logfile:</th>
179180
<td>
180-
[% actualLog = cachedBuildStep ? c.uri_for('/build' cachedBuild.id 'nixlog' cachedBuildStep.stepnr) : c.uri_for('/build' build.id 'log') %]
181+
[% actualLog = cachedBuildStep ? c.uri_for('/build' cachedBuild.id 'step' cachedBuildStep.stepnr 'log') : c.uri_for('/build' build.id 'log') %]
181182
[% INCLUDE renderLogButtons url=actualLog %]
182183
</td>
183184
</tr>

subprojects/hydra/root/deps.tt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
[% IF node.buildStep %]
1414
<a [% HTML.attributes(href => c.uri_for('/build' node.buildStep.get_column('build'))) %]><tt>[% node.name %]</tt></a> [%
1515
IF buildStepLogExists(node.buildStep);
16-
INCLUDE renderLogLinks url=c.uri_for('/build' node.buildStep.get_column('build') 'nixlog' node.buildStep.stepnr);
16+
INCLUDE renderLogLinks url=c.uri_for('/build' node.buildStep.get_column('build') 'step' node.buildStep.stepnr 'log');
1717
END %]
1818
[% ELSE %]
1919
<tt>[% node.name | html %]</tt> (<em>no info</em>)

subprojects/hydra/root/log.tt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
[%
22
buildUrl = c.uri_for('/build', build.id);
33
IF step;
4-
logUrl = c.uri_for('/build', build.id, 'nixlog', step.stepnr);
4+
stepUrl = c.uri_for('/build', build.id, 'step', step.stepnr);
5+
logUrl = c.uri_for('/build', build.id, 'step', step.stepnr, 'log');
56
drvpath = step.drvpath;
67
titleText = "Log of step ${step.stepnr} of build ${build.id} of job " _ makeNameTextForJob(build.jobset, job);
7-
titleLink = "Log of step ${step.stepnr}"
8+
titleLink = "Log of <a href=\"$stepUrl\">step ${step.stepnr}</a>"
89
_ " of <a href=\"$buildUrl\">build ${build.id}</a>"
910
_ " of job " _ linkToJob(build.jobset, job);
1011
ELSE;

subprojects/hydra/root/machine-status.tt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@
7777
<tr>
7878
<td><tt>[% INCLUDE renderFullJobName project=step.project jobset=step.jobset job=step.job %]</tt></td>
7979
<td><a [% HTML.attributes(href => c.uri_for('/build' step.build)) %]>[% HTML.escape(step.build) %]</a></td>
80-
<td>[% IF step.busy >= 30 %]<a class="row-link" [% HTML.attributes(href => c.uri_for('/build' step.build 'nixlog' step.stepnr 'tail')) %]>[% HTML.escape(step.stepnr) %]</a>[% ELSE; HTML.escape(step.stepnr); END %]</td>
80+
<td>[% IF step.busy >= 30 %]<a class="row-link" [% HTML.attributes(href => c.uri_for('/build' step.build 'step' step.stepnr 'log' 'tail')) %]>[% HTML.escape(step.stepnr) %]</a>[% ELSE; HTML.escape(step.stepnr); END %]</td>
8181
<td><tt>[% step.drvpath.match('-(.*)').0 | html %]</tt></td>
8282
<td>[% INCLUDE renderBusyStatus %]</td>
8383
<td style="width: 10em">[% INCLUDE renderDuration duration = curTime - step.starttime %] </td>

subprojects/hydra/root/steps.tt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ order of descending finish time.</p>
2525
<td><tt>[% step.drvpath.match('-(.*).drv').0 %]</tt></td>
2626
<td><tt>[% INCLUDE renderFullJobNameOfBuild build=step.build %]</tt></td>
2727
<td><a [% HTML.attributes(href => c.uri_for('/build' step.build.id)) %]>[% HTML.escape(step.build.id) %]</a></td>
28-
<td><a class="row-link" [% HTML.attributes(href => c.uri_for('/build' step.build.id 'nixlog' step.stepnr 'tail')) %]>[% HTML.escape(step.stepnr) %]</a></td>
28+
<td><a class="row-link" [% HTML.attributes(href => c.uri_for('/build' step.build.id 'step' step.stepnr 'log' 'tail')) %]>[% HTML.escape(step.stepnr) %]</a></td>
2929
<td>[% INCLUDE renderRelativeDate timestamp=step.stoptime %]</td>
3030
<td style="width: 10em">[% INCLUDE renderDuration duration = step.stoptime - step.starttime %] </td>
3131
<td><tt>[% INCLUDE renderMachineName machine=step.machine %]</tt></td>

0 commit comments

Comments
 (0)