From 662cdf042160c4b381f103f893cce77eb29a9fc8 Mon Sep 17 00:00:00 2001
From: Eelco Dolstra <eelco.dolstra@logicblox.com>
Date: Mon, 2 Apr 2012 16:11:22 +0200
Subject: [PATCH] Add support for viewing jobset evaluations

---
 src/lib/Hydra/Base/Controller/ListBuilds.pm |  2 +-
 src/lib/Hydra/Controller/Jobset.pm          | 29 ++++++++
 src/lib/Hydra/Controller/JobsetEval.pm      | 76 +++++++++++++++++++++
 src/root/all.tt                             | 19 +-----
 src/root/common.tt                          | 33 +++++++--
 src/root/jobset-eval.tt                     | 41 +++++++++++
 src/root/jobset-evals.tt                    | 47 +++++++++++++
 src/root/navbar.tt                          |  3 +
 src/root/static/css/hydra.css               |  9 ++-
 9 files changed, 236 insertions(+), 23 deletions(-)
 create mode 100644 src/lib/Hydra/Controller/JobsetEval.pm
 create mode 100644 src/root/jobset-eval.tt
 create mode 100644 src/root/jobset-evals.tt

diff --git a/src/lib/Hydra/Base/Controller/ListBuilds.pm b/src/lib/Hydra/Base/Controller/ListBuilds.pm
index 573fe344..2cf70d75 100644
--- a/src/lib/Hydra/Base/Controller/ListBuilds.pm
+++ b/src/lib/Hydra/Base/Controller/ListBuilds.pm
@@ -60,7 +60,7 @@ sub all : Chained('get_builds') PathPart {
 
     $c->stash->{page} = $page;
     $c->stash->{resultsPerPage} = $resultsPerPage;
-    $c->stash->{totalBuilds} = $nrBuilds;
+    $c->stash->{total} = $nrBuilds;
 
     $c->stash->{builds} = [ $c->stash->{allBuilds}->search(
         { finished => 1 },
diff --git a/src/lib/Hydra/Controller/Jobset.pm b/src/lib/Hydra/Controller/Jobset.pm
index d9a9b2a9..a0a0cdf5 100644
--- a/src/lib/Hydra/Controller/Jobset.pm
+++ b/src/lib/Hydra/Controller/Jobset.pm
@@ -329,5 +329,34 @@ sub clone_submit : Chained('jobset') PathPart('clone/submit') Args(0) {
 }
 
 
+sub evals : Chained('jobset') PathPart('evals') Args(0) {
+    my ($self, $c) = @_;
+
+    $c->stash->{template} = 'jobset-evals.tt';
+
+    my $page = int($c->req->param('page') || "1") || 1;
+
+    my $resultsPerPage = 20;
+
+    $c->stash->{page} = $page;
+    $c->stash->{resultsPerPage} = $resultsPerPage;
+    $c->stash->{total} = $c->stash->{jobset}->jobsetevals->search({hasnewbuilds => 1})->count;
+
+    $c->stash->{evals} = [ $c->stash->{jobset}->jobsetevals->search(
+        { hasnewbuilds => 1 }, 
+        { order_by => "id DESC"
+        , '+select' => # !!! Slow - should precompute this.
+	    [ "(select count(*) from JobsetEvalMembers where eval = me.id)"
+	    , "(select count(*) from JobsetEvalMembers where eval = me.id and exists(select 1 from Builds b where b.id = build and b.finished = 0))" 
+	    , "(select count(*) from JobsetEvalMembers where eval = me.id and exists(select 1 from Builds b where b.id = build and b.finished = 1))"
+	    , "(select count(*) from JobsetEvalMembers where eval = me.id and exists(select 1 from Builds b where b.id = build and b.finished = 1 and b.buildStatus = 0))" 
+	    ]
+	, '+as' => [ "nrBuilds", "nrScheduled", "nrFinished", "nrSucceeded" ]
+        , rows => $resultsPerPage
+	, page => $page
+        }
+    ) ];
+}
+
 
 1;
diff --git a/src/lib/Hydra/Controller/JobsetEval.pm b/src/lib/Hydra/Controller/JobsetEval.pm
new file mode 100644
index 00000000..23980e9e
--- /dev/null
+++ b/src/lib/Hydra/Controller/JobsetEval.pm
@@ -0,0 +1,76 @@
+package Hydra::Controller::JobsetEval;
+
+use strict;
+use warnings;
+use base 'Catalyst::Controller';
+use Hydra::Helper::Nix;
+use Hydra::Helper::CatalystUtils;
+
+
+sub eval : Chained('/') PathPart('eval') CaptureArgs(1) {
+    my ($self, $c, $evalId) = @_;
+    
+    my $eval = $c->model('DB::JobsetEvals')->find($evalId)
+	or notFound($c, "Evaluation $evalId doesn't exist.");
+
+    $c->stash->{eval} = $eval;
+    $c->stash->{project} = $eval->project;
+    $c->stash->{jobset} = $eval->jobset;
+}
+
+
+sub view : Chained('eval') PathPart('') Args(0) {
+    my ($self, $c) = @_;
+
+    $c->stash->{template} = 'jobset-eval.tt';
+
+    my $eval = $c->stash->{eval};
+
+    my ($eval2) = $eval->jobset->jobsetevals->search(
+        { hasnewbuilds => 1, id => { '<', $eval->id } },
+        { order_by => "id DESC", rows => 1 });
+
+    my @builds = $eval->builds->search({}, { order_by => ["job", "system", "id"], columns => [@buildListColumns] });
+    my @builds2 = $eval2->builds->search({}, { order_by => ["job", "system", "id"], columns => [@buildListColumns] });
+
+    print STDERR "EVAL IS ", $eval2->id, "\n";
+    print STDERR scalar(@builds), "\n";
+    print STDERR scalar(@builds2), "\n";
+
+    $c->stash->{stillSucceed} = [];
+    $c->stash->{stillFail} = [];
+    $c->stash->{nowSucceed} = [];
+    $c->stash->{nowFail} = [];
+
+    my $n = 0;
+    foreach my $build (@builds) {
+        my $d;
+        while ($n < scalar(@builds2)) {
+            my $build2 = $builds2[$n];
+            my $d = $build->get_column('job') cmp $build2->get_column('job')
+                || $build->get_column('system') cmp $build2->get_column('system');
+            #print STDERR $build->id, " ", $build->get_column('job'), " ",  $build->system, " ", $d, "\n";
+            last if $d == -1;
+            if ($d == 0) {
+                #print STDERR $build->buildstatus, "\n";
+                #print STDERR $build2->buildstatus, "\n";
+                if ($build->buildstatus == 0 && $build2->buildstatus == 0) {
+                    push @{$c->stash->{stillSucceed}}, $build;
+                } elsif ($build->buildstatus != 0 && $build2->buildstatus != 0) {
+                    push @{$c->stash->{stillFail}}, $build;
+                } elsif ($build->buildstatus == 0 && $build2->buildstatus != 0) {
+                    push @{$c->stash->{nowSucceed}}, $build;
+                } elsif ($build->buildstatus != 0 && $build2->buildstatus == 0) {
+                    push @{$c->stash->{nowFail}}, $build;
+                }
+                last;
+            }
+            $n++;
+        }
+    }
+    
+    $c->stash->{full} = ($c->req->params->{full} || "0") eq "1";
+}
+
+
+1;
diff --git a/src/root/all.tt b/src/root/all.tt
index 746b16c8..355fac0e 100644
--- a/src/root/all.tt
+++ b/src/root/all.tt
@@ -7,23 +7,10 @@
   [% ELSIF project %] for Project <tt>[% project.name %]</tt>[% END %]</h1>
 
 <p>Showing builds [% (page - 1) * resultsPerPage + 1 %] - [% (page - 1) * resultsPerPage + builds.size %]
-out of [% totalBuilds %] in order of descending timestamp.</p>
+out of [% total %] in order of descending timestamp.</p>
 
-[% BLOCK renderNav %]
-<p>
-[<a href="[% "$baseUri?page=1" %]">First</a>]
-[% IF page > 1 %]
-  [<a href="[% "$baseUri?page="; (page - 1) %]">Prev</a>]
-[% END %]
-[% IF page * resultsPerPage < totalBuilds %]
-  [<a href="[% "$baseUri?page="; (page + 1) %]">Next</a>]
-[% END %]
-[<a href="[% "$baseUri?page="; (totalBuilds - 1) div resultsPerPage + 1 %]">Last</a>]
-</p>
-[% END %]
-
-[% INCLUDE renderNav %]
+[% INCLUDE renderPager %]
 [% INCLUDE renderBuildList hideProjectName=project hideJobsetName=jobset hideJobName=job %]
-[% INCLUDE renderNav %]
+[% INCLUDE renderPager %]
 
 [% END %]
diff --git a/src/root/common.tt b/src/root/common.tt
index 48fd4e3a..ae7ae96e 100644
--- a/src/root/common.tt
+++ b/src/root/common.tt
@@ -62,8 +62,8 @@
 [% END %]
 
 
-[%- BLOCK renderBuildList -%]
-  <table class="buildList tablesorter[% IF !showSchedulingInfo %] clean[% END %]">
+[%- BLOCK renderBuildListHeader -%]
+  <table class="buildList [% IF !unsortable %]tablesorter[% END %] [% IF !showSchedulingInfo %] clean[% END %]">
     <thead>
       <tr>
         [%- IF !hideResultInfo -%]
@@ -89,6 +89,9 @@
       </tr>
     </thead>
     <tbody>
+[%- END -%]
+
+[%- BLOCK renderBuildListBody -%]
       [%- odd = 0 -%]
       [%- FOREACH build IN builds -%]
         <tr class="clickable 
@@ -130,10 +133,20 @@
         [%- END -%]
         </tr>
       [%- END -%]
+[%- END -%]
+
+[%- BLOCK renderBuildListFooter -%]
     </tbody>
   </table>
 [%- END -%]
 
+[%- BLOCK renderBuildList -%]
+[%- INCLUDE renderBuildListHeader -%]
+[%- INCLUDE renderBuildListBody -%]
+[%- INCLUDE renderBuildListFooter -%]
+[%- END -%]
+
+
 [%- BLOCK renderLink -%]<a href="[% uri %]">[% title %]</a>[%- END -%]
 
 [%- BLOCK showBuildStats -%]
@@ -377,8 +390,18 @@
 </table>
 [% END %]
 
-[% BLOCK buildsGraph %]
-
-
 
+[% BLOCK renderPager %]
+<p>
+[<a href="[% "$baseUri?page=1" %]">First</a>]
+[% IF page > 1 %]
+  [<a href="[% "$baseUri?page="; (page - 1) %]">Prev</a>]
 [% END %]
+[% IF page * resultsPerPage < total %]
+  [<a href="[% "$baseUri?page="; (page + 1) %]">Next</a>]
+[% END %]
+[<a href="[% "$baseUri?page="; (total - 1) div resultsPerPage + 1 %]">Last</a>]
+</p>
+[% END %]
+
+
diff --git a/src/root/jobset-eval.tt b/src/root/jobset-eval.tt
new file mode 100644
index 00000000..9e97b7ed
--- /dev/null
+++ b/src/root/jobset-eval.tt
@@ -0,0 +1,41 @@
+[% WRAPPER layout.tt title="Bla" %]
+[% PROCESS common.tt %]
+
+<h1>Jobset <tt>[% project.name %]:[% jobset.name %]</tt> Evaluation [% eval.id %]</h1>
+
+<!-- <p>Info on evaluation [% eval.id %]...<p> -->
+
+[%- BLOCK renderSome -%]
+  [% size = builds.size; max = full ? size : 30; %]
+  [% INCLUDE renderBuildListBody builds=builds.slice(0, (size > max ? max : size) - 1)
+       hideProjectName=1 hideJobsetName=1 %]
+  [% IF size > max %]
+  <tr><td class="centered" colspan="0"><a href="[% c.uri_for(c.controller('JobsetEval').action_for('view'), [eval.id], full => 1) %]"><em>([% size - max %] more builds omitted)</em></a></td></tr>
+  [% END %]
+[% END %]
+
+[% INCLUDE renderBuildListHeader unsortable=1 %]
+
+[% IF nowFail.size > 0 %]
+  <tr><th class="subheader" colspan="0">Builds that now <strong>fail</strong></th></tr>
+  [% INCLUDE renderSome builds=nowFail %]
+[% END %]
+
+[% IF nowSucceed.size > 0 %]
+  <tr><th class="subheader" colspan="0">Builds that now <strong>succeed</strong></th></tr>
+  [% INCLUDE renderSome builds=nowSucceed %]
+[% END %]
+  
+[% IF stillFail.size > 0 %]
+  <tr><th class="subheader" colspan="0">Builds that still <strong>fail</strong></th></tr>
+  [% INCLUDE renderSome builds=stillFail %]
+[% END %]
+
+[% IF stillSucceed.size > 0 %]
+  <tr><th class="subheader" colspan="0">Builds that still <strong>succeed</strong></th></tr>
+  [% INCLUDE renderSome builds=stillSucceed %]
+[% END %]
+
+[% INCLUDE renderBuildListFooter %]
+
+[% END %]
diff --git a/src/root/jobset-evals.tt b/src/root/jobset-evals.tt
new file mode 100644
index 00000000..bd75daba
--- /dev/null
+++ b/src/root/jobset-evals.tt
@@ -0,0 +1,47 @@
+[% WRAPPER layout.tt title="Jobset ‘$project.name:$jobset.name’ evaluations" %]
+[% PROCESS common.tt %]
+
+<h1>Evaluations of Jobset <tt>[% INCLUDE renderLink
+  uri = c.uri_for(c.controller('Project').action_for('view'), [project.name])
+  title = project.name %]:[% jobset.name %]</tt></h1>
+  
+<p>Showing evaluations [% (page - 1) * resultsPerPage + 1 %] - [%
+(page - 1) * resultsPerPage + evals.size %] out of [% total %].</p>
+
+[% INCLUDE renderPager %]
+
+<table class="tablesorter">
+  <thead>
+    <tr>
+      <th>#</th>
+      <th>Date</th>
+      <th colspan='2'>Success</th>
+    </tr>
+  </thead>
+  <tbody>
+    [% last = evals.size - 2; FOREACH n IN [0..last]; eval = evals.$n; m = n + 1; next = evals.$m; %]
+     <tr>
+       <td><a href="[% c.uri_for(c.controller('JobsetEval').action_for('view'), [eval.id]) %]">[% eval.id %]</a>&nbsp;</td>
+       <td>[% INCLUDE renderDateTime timestamp = eval.timestamp %]&nbsp;</td>
+       <td align='right'>
+         [% eval.get_column('nrSucceeded') %] / [% eval.get_column('nrBuilds') %]
+	 [% IF eval.get_column('nrScheduled') > 0 %]
+	 <br />[% eval.get_column('nrScheduled') %] scheduled
+	 [% END %]
+       </td>
+       <td align='right'>
+	 [% diff = eval.get_column('nrSucceeded') - next.get_column('nrSucceeded');
+	    IF diff > 0 %]
+	    <span class='green'><strong>+[% diff %]</strong></span>
+	 [% ELSIF diff < 0 && eval.get_column('nrScheduled') == 0 %]
+	    <span class='red'><strong>[% diff %]</strong></span>
+	 [% END %]
+       </td>
+     </tr>
+    [% END %]
+  </tbody>
+</table>
+
+[% INCLUDE renderPager %]
+
+[% END %]
diff --git a/src/root/navbar.tt b/src/root/navbar.tt
index 8521b7f3..161dc819 100644
--- a/src/root/navbar.tt
+++ b/src/root/navbar.tt
@@ -44,6 +44,9 @@
       [% INCLUDE makeLink
         uri = c.uri_for(c.controller('Jobset').action_for('index'), [project.name, jobset.name])
         title = "Overview" %]
+      [% INCLUDE makeLink
+        uri = c.uri_for(c.controller('Jobset').action_for('evals'), [project.name, jobset.name])
+        title = "Evaluations" %]
       [% INCLUDE makeLink
         uri = c.uri_for(c.controller('Jobset').action_for('all'), [project.name, jobset.name])
         title = "All builds" %]
diff --git a/src/root/static/css/hydra.css b/src/root/static/css/hydra.css
index 87e7cfeb..9df9f5b5 100644
--- a/src/root/static/css/hydra.css
+++ b/src/root/static/css/hydra.css
@@ -38,7 +38,14 @@ th, td {
 }
 
 th {
-    background-color:#E6EEEE;
+    background-color: #E6EEEE;
+}
+
+th.subheader {
+    background-color: #f0f0f8;
+    font-size: 120%;
+    text-align: center;
+    font-weight: normal;
 }
 
 table.tablesorter:not(.clean) tr:nth-child(even) {