Transpose the aggregate constituents table

This way, it grows vertically rather than horizontally.  Horizontal
may be more "logical", but this is more practical.
This commit is contained in:
Eelco Dolstra 2013-08-27 13:45:27 +00:00
parent 7725038821
commit 7685596aa8
6 changed files with 86 additions and 41 deletions

View file

@ -40,7 +40,7 @@ sub overview : Chained('job') PathPart('') Args(0) {
# If this is an aggregate job, then get its constituents. # If this is an aggregate job, then get its constituents.
my @constituents = $c->model('DB::Builds')->search( my @constituents = $c->model('DB::Builds')->search(
{ aggregate => { -in => $job->builds->search({}, { columns => ["id"], order_by => "id desc", rows => 10 })->as_query } }, { aggregate => { -in => $job->builds->search({}, { columns => ["id"], order_by => "id desc", rows => 15 })->as_query } },
{ join => 'aggregateconstituents_constituents', { join => 'aggregateconstituents_constituents',
columns => ['id', 'job', 'finished', 'buildstatus'], columns => ['id', 'job', 'finished', 'buildstatus'],
+select => ['aggregateconstituents_constituents.aggregate'], +select => ['aggregateconstituents_constituents.aggregate'],
@ -51,11 +51,17 @@ sub overview : Chained('job') PathPart('') Args(0) {
my %constituentJobs; my %constituentJobs;
foreach my $b (@constituents) { foreach my $b (@constituents) {
my $jobName = $b->get_column('job'); my $jobName = $b->get_column('job');
$aggregates->{$b->get_column('aggregate')}->{$jobName} = $aggregates->{$b->get_column('aggregate')}->{constituents}->{$jobName} =
{ id => $b->id, finished => $b->finished, buildstatus => $b->buildstatus}; { id => $b->id, finished => $b->finished, buildstatus => $b->buildstatus};
$constituentJobs{$jobName} = 1; $constituentJobs{$jobName} = 1;
} }
foreach my $agg (keys %$aggregates) {
# FIXME: could be done in one query.
$aggregates->{$agg}->{build} =
$c->model('DB::Builds')->find({id => $agg}, {columns => [@buildListColumns]}) or die;
}
$c->stash->{aggregates} = $aggregates; $c->stash->{aggregates} = $aggregates;
$c->stash->{constituentJobs} = [sort (keys %constituentJobs)]; $c->stash->{constituentJobs} = [sort (keys %constituentJobs)];
} }

View file

@ -20,7 +20,7 @@ BLOCK renderJobsetName %]
BLOCK renderJobName %] BLOCK renderJobName %]
<a [% IF inRow %]class="row-link"[% END %] href="[% c.uri_for('/job' project jobset job) %]"><tt>[% job %]</tt></a> <a [% IF inRow %]class="row-link"[% END %] href="[% c.uri_for('/job' project jobset job) %]">[% job %]</a>
[% END; [% END;

View file

@ -28,51 +28,34 @@
<div id="tabs-constituents" class="tab-pane"> <div id="tabs-constituents" class="tab-pane">
<table class="table table-striped table-condensed"> <div class="well well-small">This is an <em>aggregate job</em>:
<thead> its success or failure is determined entirely by the result of
<tr> building its <em>constituent jobs</em>. The table below shows
<th>#</th> the status of each constituent job for the [%
[% FOREACH j IN constituentJobs %] aggregates.keys.size %] most recent builds of the
<th>[% HTML.escape(j) %]</th> aggregate.</div>
[% END %]
</tr>
</thead>
<tbody>
[% FOREACH agg IN aggregates.keys.nsort.reverse %]
<tr>
<td><a class="row-link" href="[% c.uri_for('/build' agg) %]">[% agg %]</a></td>
[% FOREACH j IN constituentJobs %]
<td>
[% r = aggregates.$agg.$j; IF r.id %]
<a href="[% c.uri_for('/build' r.id) %]">
[% INCLUDE renderBuildStatusIcon size=16 build=r %]
</a>
[% END %]
</td>
[% END %]
</tr>
[% END %]
</tbody>
</table>
<hr/>
<table class="table table-striped table-condensed"> [% aggs = aggregates.keys.nsort.reverse %]
<table class="table table-striped table-condensed table-header-rotated">
<thead> <thead>
<tr> <tr>
<th>#</th> <th>Job</th>
[% FOREACH j IN constituentJobs %] [% FOREACH agg IN aggs %]
<th>[% HTML.escape(j) %]</th> <th class="rotate-45">
[% agg_ = aggregates.$agg %]
<div><span class="[% agg_.build.finished == 0 ? "text-info" : (agg_.build.buildstatus == 0 ? "text-success" : "text-warning") %] override-link">
<a href="[% c.uri_for('/build' agg) %]">[% agg %]</a>
</span></div></th>
[% END %] [% END %]
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
[% FOREACH agg IN aggregates.keys.nsort.reverse %] [% FOREACH j IN constituentJobs %]
<tr> <tr>
<td><a class="row-link" href="[% c.uri_for('/build' agg) %]">[% agg %]</a></td> <th style="width: 1em;">[% INCLUDE renderJobName project=project.name jobset=jobset.name job=j %]</th>
[% FOREACH j IN constituentJobs %] [% FOREACH agg IN aggs %]
<td> <td>
[% r = aggregates.$agg.$j; IF r.id %] [% r = aggregates.$agg.constituents.$j; IF r.id %]
<a href="[% c.uri_for('/build' r.id) %]"> <a href="[% c.uri_for('/build' r.id) %]">
[% INCLUDE renderBuildStatusIcon size=16 build=r %] [% INCLUDE renderBuildStatusIcon size=16 build=r %]
</a> </a>
@ -84,7 +67,6 @@
</tbody> </tbody>
</table> </table>
</div> </div>
[% END %] [% END %]

View file

@ -24,6 +24,7 @@
<!-- hydra.css must be included before bootstrap-responsive to <!-- hydra.css must be included before bootstrap-responsive to
make the @media rule work. --> make the @media rule work. -->
<link rel="stylesheet" href="/static/css/hydra.css" type="text/css" /> <link rel="stylesheet" href="/static/css/hydra.css" type="text/css" />
<link rel="stylesheet" href="/static/css/rotated-th.css" type="text/css" />
<link href="/static/bootstrap/css/bootstrap-responsive.min.css" rel="stylesheet" /> <link href="/static/bootstrap/css/bootstrap-responsive.min.css" rel="stylesheet" />
<style> <style>

View file

@ -91,3 +91,7 @@ div.news-item:not(:first-child) {
td.nowrap { td.nowrap {
white-space: nowrap; white-space: nowrap;
} }
.override-link a {
color: inherit;
}

View file

@ -0,0 +1,52 @@
/* Rotated table headers, borrowed from http://jimmybonney.com/articles/column_header_rotation_css */
.tab-content {
margin-right: 5em;
overflow: visible;
}
td.centered {
text-align: center;
}
.table-header-rotated th.rotate-45{
height: 80px;
width: 40px;
min-width: 40px;
max-width: 40px;
position: relative;
vertical-align: bottom;
padding: 0;
font-size: 100%;
line-height: 0.9;
}
.table-header-rotated th.rotate-45 > div {
position: relative;
top: 0px;
left: 40px; /* 80 * tan(45) / 2 = 40 where 80 is the height on the cell and 45 is the transform angle*/
height: 100%;
-ms-transform:skew(-45deg,0deg);
-moz-transform:skew(-45deg,0deg);
-webkit-transform:skew(-45deg,0deg);
-o-transform:skew(-45deg,0deg);
transform:skew(-45deg,0deg);
overflow: hidden;
border-left: 1px solid #dddddd;
}
.table-header-rotated th.rotate-45 span {
-ms-transform:skew(45deg,0deg) rotate(315deg);
-moz-transform:skew(45deg,0deg) rotate(315deg);
-webkit-transform:skew(45deg,0deg) rotate(315deg);
-o-transform:skew(45deg,0deg) rotate(315deg);
transform:skew(45deg,0deg) rotate(315deg);
position: absolute;
bottom: 30px; /* 40 cos(45) = 28 with an additional 2px margin*/
left: -25px; /*Because it looked good, but there is probably a mathematical link here as well*/
display: inline-block;
// width: 100%;
width: 85px; /* 80 / cos(45) - 40 cos (45) = 85 where 80 is the height of the cell, 40 the width of the cell and 45 the transform angle*/
text-align: left;
// white-space: nowrap; /*whether to display in one line or not*/
}