* Showing releases.

This commit is contained in:
Eelco Dolstra 2008-11-27 18:27:19 +00:00
parent 7b19257830
commit d6f71f2248
8 changed files with 267 additions and 200 deletions

View file

@ -201,22 +201,76 @@ sub attrsToSQL {
}
sub getReleaseSet {
my ($c, $projectName, $releaseName) = @_;
my $project = $c->model('DB::Projects')->find($projectName);
die "Project $projectName doesn't exist." if !defined $project;
$c->stash->{curProject} = $project;
(my $releaseSet) = $c->model('DB::Releasesets')->find($projectName, $releaseName);
die "Release set $releaseName doesn't exist." if !defined $releaseSet;
$c->stash->{releaseSet} = $releaseSet;
(my $primaryJob) = $releaseSet->releasesetjobs->search({isprimary => 1});
die "Release set $releaseName doesn't have a primary job." if !defined $primaryJob;
$c->stash->{jobs} = [$releaseSet->releasesetjobs->search({},
{order_by => ["isprimary DESC", "job", "attrs"]})];
return ($project, $releaseSet, $primaryJob);
}
sub getRelease {
my ($c, $primaryBuild) = @_;
my @jobs = ();
my $status = 0; # = okay
foreach my $job (@{$c->stash->{jobs}}) {
my $thisBuild;
if ($job->isprimary == 1) {
$thisBuild = $primaryBuild;
} else {
# Find a build of this job that had the primary build
# as input. If there are multiple, prefer successful
# ones, and then oldest. !!! order_by buildstatus is hacky
($thisBuild) = $primaryBuild->dependentBuilds->search(
{ attrname => $job->job, finished => 1 },
{ join => 'resultInfo', rows => 1
, order_by => ["buildstatus", "timestamp"]
, where => \ attrsToSQL($job->attrs, "build.id")
});
}
if ($job->mayfail != 1) {
if (!defined $thisBuild) {
$status = 2 if $status == 0; # = unfinished
} elsif ($thisBuild->resultInfo->buildstatus != 0) {
$status = 1; # = failed
}
}
push @jobs, { build => $thisBuild, job => $job };
}
return
{ id => $primaryBuild->id
, releasename => $primaryBuild->get_column('releasename')
, jobs => [@jobs]
, status => $status
};
}
sub releases :Local {
my ($self, $c, $projectName, $releaseName) = @_;
$c->stash->{template} = 'releases.tt';
my $project = $c->model('DB::Projects')->find($projectName);
return error($c, "Project $projectName doesn't exist.") if !defined $project;
$c->stash->{curProject} = $project;
(my $releaseSet) = $c->model('DB::Releasesets')->find($projectName, $releaseName);
return error($c, "Release set $releaseName doesn't exist.") if !defined $releaseSet;
$c->stash->{releaseSet} = $releaseSet;
(my $primaryJob) = $releaseSet->releasesetjobs->search({isprimary => 1});
return error($c, "Release set $releaseName doesn't have a primary job.") if !defined $primaryJob;
$c->stash->{jobs} = [$releaseSet->releasesetjobs->search({}, {order_by => "isprimary DESC"})];
my ($project, $releaseSet, $primaryJob) = getReleaseSet($c, $projectName, $releaseName);
my @primaryBuilds = $project->builds->search(
{ attrname => $primaryJob->job, finished => 1 },
@ -226,52 +280,28 @@ sub releases :Local {
});
my @releases = ();
foreach my $primaryBuild (@primaryBuilds) {
my @jobs = ();
my $status = 0; # = okay
foreach my $job (@{$c->stash->{jobs}}) {
my $thisBuild;
if ($job->isprimary == 1) {
$thisBuild = $primaryBuild;
} else {
# Find a build of this job that had the primary build
# as input. If there are multiple, prefer successful
# ones, and then oldest. !!! order_by buildstatus is hacky
($thisBuild) = $primaryBuild->dependentBuilds->search(
{ attrname => $job->job, finished => 1 },
{ join => 'resultInfo', rows => 1
, order_by => ["buildstatus", "timestamp"]
, where => \ attrsToSQL($job->attrs, "build.id")
});
}
if ($job->mayfail != 1) {
if (!defined $thisBuild) {
$status = 2 if $status == 0; # = unfinished
} elsif ($thisBuild->resultInfo->buildstatus != 0) {
$status = 1; # = failed
}
}
push @jobs, { build => $thisBuild };
}
push @releases,
{ id => $primaryBuild->id
, releasename => $primaryBuild->get_column('releasename')
, jobs => [@jobs]
, status => $status
};
}
push @releases, getRelease($c, $_) foreach @primaryBuilds;
$c->stash->{releases} = [@releases];
}
sub release :Local {
my ($self, $c, $projectName, $releaseName, $releaseId) = @_;
$c->stash->{template} = 'release.tt';
my ($project, $releaseSet, $primaryJob) = getReleaseSet($c, $projectName, $releaseName);
# Note: we don't actually check whether $releaseId is a primary
# build, but who cares?
my $primaryBuild = $project->builds->find($releaseId,
{ join => 'resultInfo', '+select' => ["resultInfo.releasename"], '+as' => ["releasename"] });
return error($c, "Release $releaseId doesn't exist.") if !defined $primaryBuild;
$c->stash->{release} = getRelease($c, $primaryBuild);
}
sub updateProject {
my ($c, $project) = @_;
my $projectName = trim $c->request->params->{name};

View file

@ -1,5 +1,6 @@
[% WRAPPER layout.tt title="Build Information" %]
[% PROCESS common.tt %]
[% PROCESS "product-list.tt" %]
[% USE HTML %]
[% USE mibs=format("%.2f") %]
@ -231,113 +232,9 @@
[% IF build.buildproducts %]
<h2>Build products</h2>
<ul class="productList">
[% FOREACH product IN build.buildproducts -%]
<li class="product">
[% SWITCH product.type %]
[% CASE "nix-build" %]
<a href="[% c.uri_for('/closure' build.id product.productnr) %]">
<img src="/static/images/nix-build.png" alt="Source" />
Nix build of path <tt>[% product.path %]</tt>
</a>
[<a class="productDetailsToggle" href="javascript:">help</a>]
<div class="help productDetails">
<p>If you have Nix installed on your machine, this build and all
its dependencies can be unpacked into your local Nix store by
doing:</p>
<pre>$ gunzip < [% HTML.escape(build.nixname) %].closure.gz | nix-store --import</pre>
or to download and unpack in one command:
<pre>$ curl [% c.uri_for('/closure' build.id product.productnr) %] | gunzip | nix-store --import</pre>
<p>The package can then be found in the path <tt>[%
product.path %]</tt>. If you get the error message “imported
archive lacks a signature”, you should make sure that you have
sufficient access rights to the Nix store, e.g., run the
command as <tt>root</tt>.</p>
</div>
[% CASE "file" %]
<a href="[% c.uri_for('/download' build.id product.productnr product.name) %]">
[% SWITCH product.subtype %]
[% CASE "source-dist" %]
<img src="/static/images/source-dist.png" alt="Source" /> Source distribution <tt>[% product.name %]</tt>
[% CASE "rpm" %]
<img src="/static/images/rpm.png" alt="RPM" /> RPM package <tt>[% product.name %]</tt>
[% CASE "deb" %]
<img src="/static/images/debian.png" alt="RPM" /> Debian package <tt>[% product.name %]</tt>
[% CASE DEFAULT %]
File <tt>[% product.name %]</tt> of type <tt>[% product.subtype %]</tt>
[% END %]
</a>
[<a class="productDetailsToggle" href="javascript:">details</a>]
<div class="productDetails">
<table>
<tr>
<th>URL:</th>
<td>
<a href="[% c.uri_for('/download' build.id product.productnr product.name) %]">
<tt>[% c.uri_for('/download' build.id product.productnr product.name) %]</tt>
</a>
</td>
</tr>
<tr><th>File size:</th><td>[% product.filesize %] bytes ([% mibs(product.filesize / (1024 * 1024)) %] MiB)</td></tr>
<tr><th>SHA-1 hash:</th><td>[% product.sha1hash %]</td></tr>
<tr><th>SHA-256 hash:</th><td>[% product.sha256hash %]</td></tr>
<tr><th>Full path:</th><td><tt>[% product.path %]</tt></td></tr>
</table>
</div>
[% CASE "report" %]
<a href="[% c.uri_for('/download' build.id product.productnr product.name) %]">
<img src="/static/images/report.png" alt="Report" />
[% SWITCH product.subtype %]
[% CASE "coverage" %]
Code coverage analysis report
[% CASE DEFAULT %]
Report of type <tt>[% product.subtype %]</tt>
[% END %]
</a>
[% CASE "doc" %]
<a href="[% c.uri_for('/download' build.id product.productnr product.name) %]">
<img src="/static/images/document.png" alt="Document" />
[% SWITCH product.subtype %]
[% CASE "readme" %]
“README” file
[% CASE "manual" %]
Manual
[% CASE DEFAULT %]
Documentation of type <tt>[% product.subtype %]</tt>
[% END %]
</a>
[% CASE DEFAULT %]
Something of type <tt>[% product.type %]</tt>
[% END %]
</li>
[% END -%]
</ul>
<script>
$(document).ready(function() {
$('.productDetailsToggle').toggle(
function () { $(".productDetails", $(this).parents(".product")).fadeIn(); },
function () { $(".productDetails", $(this).parents(".product")).hide(); }
);
});
</script>
[% PROCESS renderProductList %]
[% END %]

View file

@ -94,4 +94,9 @@
</tr>
</table>
[% END %]
[% END %]
[% BLOCK renderReleaseJobName -%]
[% IF job.description; HTML.escape(job.description); ELSE %]<tt>[% job.job %]</tt> ([% job.attrs %])[% END -%]
[% END -%]

View file

@ -101,6 +101,7 @@
<ul class="subsubmenu">
[% INCLUDE makeLink uri = c.uri_for('/project' project.name 'jobstatus') title = "Job status" %]
[% INCLUDE makeLink uri = c.uri_for('/project' project.name 'all') title = "All builds" %]
[% INCLUDE makeLink uri = c.uri_for('/releasesets' project.name) title = "Releases" %]
[% INCLUDE makeLink uri = c.uri_for('/project' project.name 'edit') title = "Edit" %]
</ul>
[% END %]

View file

@ -0,0 +1,109 @@
[% BLOCK renderProductList -%]
<ul class="productList">
[% FOREACH product IN build.buildproducts -%]
<li class="product">
[% SWITCH product.type %]
[% CASE "nix-build" %]
<a href="[% c.uri_for('/closure' build.id product.productnr) %]">
<img src="/static/images/nix-build.png" alt="Source" />
Nix build of path <tt>[% product.path %]</tt>
</a>
[<a class="productDetailsToggle" href="javascript:">help</a>]
<div class="help productDetails">
<p>If you have Nix installed on your machine, this build and all
its dependencies can be unpacked into your local Nix store by
doing:</p>
<pre>$ gunzip < [% HTML.escape(build.nixname) %].closure.gz | nix-store --import</pre>
or to download and unpack in one command:
<pre>$ curl [% c.uri_for('/closure' build.id product.productnr) %] | gunzip | nix-store --import</pre>
<p>The package can then be found in the path <tt>[%
product.path %]</tt>. If you get the error message “imported
archive lacks a signature”, you should make sure that you have
sufficient access rights to the Nix store, e.g., run the
command as <tt>root</tt>.</p>
</div>
[% CASE "file" %]
<a href="[% c.uri_for('/download' build.id product.productnr product.name) %]">
[% SWITCH product.subtype %]
[% CASE "source-dist" %]
<img src="/static/images/source-dist.png" alt="Source" /> Source distribution <tt>[% product.name %]</tt>
[% CASE "rpm" %]
<img src="/static/images/rpm.png" alt="RPM" /> RPM package <tt>[% product.name %]</tt>
[% CASE "deb" %]
<img src="/static/images/debian.png" alt="RPM" /> Debian package <tt>[% product.name %]</tt>
[% CASE DEFAULT %]
File <tt>[% product.name %]</tt> of type <tt>[% product.subtype %]</tt>
[% END %]
</a>
[<a class="productDetailsToggle" href="javascript:">details</a>]
<div class="productDetails">
<table>
<tr>
<th>URL:</th>
<td>
<a href="[% c.uri_for('/download' build.id product.productnr product.name) %]">
<tt>[% c.uri_for('/download' build.id product.productnr product.name) %]</tt>
</a>
</td>
</tr>
<tr><th>File size:</th><td>[% product.filesize %] bytes ([% mibs(product.filesize / (1024 * 1024)) %] MiB)</td></tr>
<tr><th>SHA-1 hash:</th><td>[% product.sha1hash %]</td></tr>
<tr><th>SHA-256 hash:</th><td>[% product.sha256hash %]</td></tr>
<tr><th>Full path:</th><td><tt>[% product.path %]</tt></td></tr>
</table>
</div>
[% CASE "report" %]
<a href="[% c.uri_for('/download' build.id product.productnr product.name) %]">
<img src="/static/images/report.png" alt="Report" />
[% SWITCH product.subtype %]
[% CASE "coverage" %]
Code coverage analysis report
[% CASE DEFAULT %]
Report of type <tt>[% product.subtype %]</tt>
[% END %]
</a>
[% CASE "doc" %]
<a href="[% c.uri_for('/download' build.id product.productnr product.name) %]">
<img src="/static/images/document.png" alt="Document" />
[% SWITCH product.subtype %]
[% CASE "readme" %]
“README” file
[% CASE "manual" %]
Manual
[% CASE DEFAULT %]
Documentation of type <tt>[% product.subtype %]</tt>
[% END %]
</a>
[% CASE DEFAULT %]
Something of type <tt>[% product.type %]</tt>
[% END %]
</li>
[% END -%]
</ul>
[% END %]
<script>
$(document).ready(function() {
$('.productDetailsToggle').toggle(
function () { $(".productDetails", $(this).parents(".product")).fadeIn(); },
function () { $(".productDetails", $(this).parents(".product")).hide(); }
);
});
</script>

43
src/Hydra/root/release.tt Normal file
View file

@ -0,0 +1,43 @@
[% releaseName = (release.releasename || "(No name)") -%]
[% WRAPPER layout.tt title="Release $releaseName" %]
[% PROCESS common.tt %]
[% PROCESS "product-list.tt" %]
[% USE HTML %]
<h1>Release <tt>[% releaseName %]</tt></h1>
[% IF release.status == 1 %]
<p class="error">This is a failed release. One of its jobs has failed. See below for details.</p>
[% ELSIF release.status == 2 %]
<p class="error">This is an incomplete release. One of its jobs has not been built (yet). See below for details.</p>
[% END %]
[% FOREACH job IN release.jobs %]
<h2>
[% IF job.build %]<a href="[% c.uri_for('/build' job.build.id) %]">[% END %]
[% INCLUDE renderReleaseJobName job=job.job %]
[% IF job.build %]</a>[% END %]
</h2>
[% IF job.build %]
[% IF job.build.resultInfo.buildstatus == 0 %]
[% INCLUDE renderProductList build=job.build %]
[% ELSE %]
<p class="error">Build failed</p>
[% END %]
[% ELSE %]
<p class="error">Build not (yet) performed.</p>
[% END %]
[% END %]
[% END %]

View file

@ -11,14 +11,15 @@
<th>#</th>
<th>Release</th>
[% FOREACH job IN jobs %]
<th>[% IF job.description; HTML.escape(job.description); ELSE %]<tt>[% job.job %]</tt> ([% job.attrs %])[% END %]</th>
<th class="releaseSetJobName">[% PROCESS renderReleaseJobName %]</th>
[% END %]
</tr>
</thead>
<tbody>
[% FOREACH release IN releases %]
<tr>
[% FOREACH release IN releases %]
[% link = c.uri_for('/release' releaseSet.project.name releaseSet.name release.id) %]
<tr class="clickable" onclick="window.location = '[% link %]'">
<td>
[% IF release.status == 0 %]
<img src="/static/images/success.gif" />
@ -28,7 +29,7 @@
<img src="/static/images/question-mark.png" />
[% END %]
</td>
<td>[% release.id %]</td>
<td><a href="[% link %]">[% release.id %]</a></td>
<td>
[% IF release.releasename %]
<tt>[% release.releasename %]</tt>
@ -37,7 +38,7 @@
[% END %]
</td>
[% FOREACH job IN release.jobs %]
<td>
<td class="centered">
[% IF job.build %]
<a href="[% c.uri_for('/build' job.build.id) %]">
[% IF job.build.resultInfo.buildstatus == 0 %]
@ -45,7 +46,6 @@
[% ELSE %]
<img src="/static/images/failure.gif" />
[% END %]
[% job.build.id %]
</a>
[% END %]
</td>

View file

@ -52,8 +52,16 @@ td {
vertical-align: top;
}
th {
td.centered {
text-align: center;
}
.layoutTable th {
vertical-align: top;
}
th {
vertical-align: center;
background: #ffffc0;
}
@ -116,27 +124,6 @@ span.system {
font-style: italic;
}
table.derivationList {
margin-left: 2em;
margin-right: 2em;
}
table.derivationList, table.derivationList td, table.derivationList th {
border: 1px solid #808080;
}
table.derivationList tr.odd {
background: #f0f0f0;
}
table.derivationList td {
vertical-align: top;
}
table.derivationList td.system {
font-style: italic;
}
a {
text-decoration: none;
}
@ -149,15 +136,6 @@ img {
border-style: none;
}
table.buildfarmResults td, table.buildfarmResults th {
border: none;
}
td.buildfarmMainColumn {
background-color: #E0E0E0;
border: solid;
}
.error-msg {
color: red;
white-space: pre;
@ -210,10 +188,6 @@ ul.productList li {
display: none;
}
.template {
display: none;
}
div.jobset {
border: solid black 1px;
-moz-border-radius: 1em;
@ -273,6 +247,14 @@ table.tablesorter thead tr th {
}
/* Overriding tablesorter... */
th.releaseSetJobName {
font-size: 60%;
padding: 0 0 0 0;
}
/* Navbar */
#leftnavbar {