[Bps-public-commit] smokingit branch, master, updated. 15caa61b1d4413396c558a3c2d36ac21b97f9709

Alex Vandiver alexmv at bestpractical.com
Sun Nov 4 23:49:15 EST 2012


The branch, master has been updated
       via  15caa61b1d4413396c558a3c2d36ac21b97f9709 (commit)
       via  fe9344b9cefae3976631be1618e7d024a1f6a7d3 (commit)
       via  37651f7b11e6a734614b3b77a240bad63bd19700 (commit)
       via  568040e26f76fe89362ca54f32a2c1a5dec0e388 (commit)
       via  598e7fef6563decb0443e1db941837c7702f894e (commit)
       via  ae3378f7b943e92ac3d3860161db95542e37df11 (commit)
       via  dbca24044f519ab9db1579970f02e524dcc5d615 (commit)
       via  f9b55c47d3a12a4fc633024b92eed9c21a442474 (commit)
       via  6c9cf864d032587279dbf25acf0f03012b2af94c (commit)
       via  7b905dae939d95e963b135508a2c921bbee07638 (commit)
       via  f4b92237f6433490681acfa90d82da229d6b5062 (commit)
       via  546527a9572621314d4b4dc77773f52f9707b604 (commit)
       via  7535959ee0789cd54094c1c8cb12f023b4d830a7 (commit)
       via  dd06a6c6b5840565462c26a4950a67ad12fbff88 (commit)
       via  1f3fc27b6987c3e01b5ab2132fa6cfad2700298e (commit)
       via  e2e24641907698901acd75d136e13a7e14e3795e (commit)
       via  ee4701f0481e9a7a809eb8931aaf8986d7c5a849 (commit)
       via  be74911c5d9f26b21733fbfe8a83aa904b06b849 (commit)
       via  1f8c7071063e76464af8518bb68d44e68efa81d5 (commit)
       via  0088b3e98644f1154f326367295010e7f9679b7d (commit)
       via  fd618b071d52175f8637bbbad7558093731bbf26 (commit)
      from  d3de530f504643e1cafab64c125a235fe068e7ef (commit)

Summary of changes:
 Makefile.PL                                     |   1 -
 bin/local_updates                               | 146 +++++++++-----------
 bin/rpc                                         |  23 ++++
 bin/start-gearmand                              |   9 --
 bin/update-user                                 |  13 ++
 etc/config.yml                                  |  14 +-
 lib/Smokingit.pm                                |  33 ++---
 lib/Smokingit/Action/Test.pm                    | 168 ++++++++++++++++++++++++
 lib/Smokingit/Model/Branch.pm                   |  50 +++++--
 lib/Smokingit/Model/Commit.pm                   |  42 +++---
 lib/Smokingit/Model/Configuration.pm            |  18 ++-
 lib/Smokingit/Model/Project.pm                  |  28 +++-
 lib/Smokingit/Model/SmokeResult.pm              |  88 ++++++++-----
 lib/Smokingit/Model/User.pm                     |  36 +++++
 lib/Smokingit/Status.pm                         |  93 +++++++++++++
 lib/Smokingit/View.pm                           |   3 +
 lib/Smokingit/View/Branch.pm                    |  25 +++-
 lib/Smokingit/View/Configuration.pm             |   9 ++
 lib/Smokingit/View/GitHub.pm                    |   5 +-
 lib/Smokingit/View/Page.pm                      |  52 ++++++++
 lib/Smokingit/View/Project.pm                   | 114 ++++++++++++----
 share/web/static/css/app-late.css               |  22 +++-
 share/web/static/css/login.css                  |  64 +++++++++
 share/web/static/css/main.css                   |   3 +
 share/web/static/images/silk/control_play.png   | Bin 0 -> 592 bytes
 share/web/static/images/silk/control_repeat.png | Bin 0 -> 422 bytes
 share/web/static/js/app-late.js                 |  30 +++++
 27 files changed, 859 insertions(+), 230 deletions(-)
 create mode 100755 bin/rpc
 delete mode 100755 bin/start-gearmand
 create mode 100755 bin/update-user
 create mode 100644 lib/Smokingit/Action/Test.pm
 create mode 100644 lib/Smokingit/Model/User.pm
 create mode 100644 lib/Smokingit/Status.pm
 create mode 100644 share/web/static/css/login.css
 create mode 100644 share/web/static/images/silk/control_play.png
 create mode 100644 share/web/static/images/silk/control_repeat.png
 create mode 100644 share/web/static/js/app-late.js

- Log -----------------------------------------------------------------
commit fd618b071d52175f8637bbbad7558093731bbf26
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Thu Mar 15 03:31:44 2012 -0400

    Add a simple username-only model to hang auth off of

diff --git a/bin/update-user b/bin/update-user
new file mode 100755
index 0000000..ae53d68
--- /dev/null
+++ b/bin/update-user
@@ -0,0 +1,13 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+
+use Jifty;
+BEGIN { Jifty-> new };
+
+my ($username, $password) = @ARGV;
+
+my $user = Smokingit::Model::User->new( current_user => Smokingit::CurrentUser->superuser );
+$user->load_or_create( name => $username );
+$user->set_password( $password );
+
diff --git a/etc/config.yml b/etc/config.yml
index da58a75..6c35058 100644
--- a/etc/config.yml
+++ b/etc/config.yml
@@ -11,7 +11,7 @@ framework:
     Host: localhost
     Password: ''
     User: postgres
-    Version: 0.0.5
+    Version: 0.0.6
   DevelMode: 1
   LogLevel: INFO
   Mailer: Sendmail
@@ -20,6 +20,10 @@ framework:
     - CompressedCSSandJS: {}
     - ErrorTemplates: {}
     - REST: {}
+    - Authentication::Password:
+        login_by: username
+        nav_menu: 0
+        signup: 0
   SkipAccessControl: 1
   Web: 
     BaseURL: http://localhost
diff --git a/lib/Smokingit/Model/User.pm b/lib/Smokingit/Model/User.pm
new file mode 100644
index 0000000..9590d79
--- /dev/null
+++ b/lib/Smokingit/Model/User.pm
@@ -0,0 +1,36 @@
+use strict;
+use warnings;
+
+package Smokingit::Model::User;
+use Jifty::DBI::Schema;
+
+use Smokingit::Record schema {};
+
+use Jifty::Plugin::User::Mixin::Model::User;
+use Jifty::Plugin::Authentication::Password::Mixin::Model::User;
+
+sub is_protected {1}
+
+sub since { '0.0.6' }
+
+sub create {
+    my $self = shift;
+    my (%args) = @_;
+    $args{email} ||= $args{name} . '@bestpractical.com';
+    $args{email_confirmed} = 1;
+    $self->SUPER::create(%args);
+}
+
+sub current_user_can {
+    my $self  = shift;
+    my $right = shift;
+    my %args  = (@_);
+
+    return 1 if $right eq "read";
+    return 1 if $right eq "update"
+        and $self->current_user->id == $self->id;
+
+    return $self->SUPER::current_user_can( $right, %args );
+}
+
+1;

commit 0088b3e98644f1154f326367295010e7f9679b7d
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Fri Mar 23 04:31:48 2012 -0400

    Login and logout

diff --git a/lib/Smokingit/View/Page.pm b/lib/Smokingit/View/Page.pm
index 6ecc5fb..047e6f4 100644
--- a/lib/Smokingit/View/Page.pm
+++ b/lib/Smokingit/View/Page.pm
@@ -9,6 +9,58 @@ use Jifty::View::Declare::Helpers;
 sub render_page {
     my $self = shift;
     my $title = shift;
+
+    div {
+        { id is 'authbox' };
+        if (Jifty->web->current_user->id) {
+            div {
+                { class is 'username' };
+                js_handlers {
+                    onclick => "jQuery('#authbox .logout').toggle()" };
+                outs(Jifty->web->current_user->username);
+                div {
+                    { class is 'logout' };
+                    form {
+                        my $logout = new_action(
+                            class => "Logout",
+                            moniker => "logout"
+                        );
+                        form_submit( submit => $logout, label => "Log out" );
+                    }
+                }
+            }
+        } else {
+            div {
+                { class is 'prompt' };
+                js_handlers {
+                    onclick => [
+                        "jQuery('#authbox .prompt').hide()",
+                        "jQuery('#authbox .loginform').show()",
+                        "jQuery('.loginform input.argument-username').focus()",
+                    ],
+                };
+                "Login";
+            };
+            div {
+                { class is 'loginform' };
+                form {
+                    my $login = new_action(
+                        class => "Login",
+                        moniker => "login",
+                    );
+                    render_param( $login => $_ )
+                        for qw/username password token hashed_password/;
+                    render_param(
+                        $login => "remember",
+                        render_as => 'hidden',
+                        default_value => 1
+                    );
+                    form_submit( label => "Login", onclick => "return getPasswordToken('login');" );
+                };
+            }
+        }
+    }
+
     div {
         { id is 'content' };
         $self->instrument_content;
diff --git a/share/web/static/css/login.css b/share/web/static/css/login.css
new file mode 100644
index 0000000..697900a
--- /dev/null
+++ b/share/web/static/css/login.css
@@ -0,0 +1,64 @@
+#authbox {
+    width: 7em;
+    padding: 0 0.5em;
+    position: absolute;
+    background: #ccc;
+    -webkit-border-radius: 1em;
+    -moz-border-radius: 1em;
+    z-index: 5;
+    margin-top: -0.5em;
+    margin-left: -0.5em;
+}
+
+#authbox .prompt, #authbox .username {
+    text-align: center;
+    padding: 0.3em;
+    font-weight: bold;
+    cursor: pointer;
+}
+
+#authbox .loginform {
+    display: none;
+    padding: 0.5em 0;
+}
+
+#authbox .form_field {
+    padding-top: 0;
+}
+
+#authbox label {
+    width: inherit;
+    display: block;
+    text-align: center;
+    float: none;
+}
+
+#authbox input[type="text"], #authbox input[type="password"] {
+    width: 7em;
+}
+
+#authbox .submit_button {
+    text-align: center;
+}
+
+#authbox .submit_button input {
+    margin: 0;
+}
+
+#authbox .logout {
+    display: none;
+    padding-top: 0.5em;
+}
+
+#authbox div.form_field .error {
+    width: 14em;
+    background-color: #CBB;
+    -webkit-border-radius: 0.5em;
+    -moz-border-radius: 0.5em;
+    opacity: 0.80;
+    margin-right: -4em;
+    padding-top: 0.5em;
+    padding-bottom: 0.5em;
+    margin-bottom: 0.5em;
+    border: 1px solid #A00000;
+}
\ No newline at end of file
diff --git a/share/web/static/css/main.css b/share/web/static/css/main.css
index 3b88cd0..34c7031 100644
--- a/share/web/static/css/main.css
+++ b/share/web/static/css/main.css
@@ -8,4 +8,7 @@
 @import "jquery.autocomplete.css";
 @import "jquery.jgrowl.css";
 @import "facebox.css";
+
+ at import "login.css";
+
 @import "app-late.css";

commit 1f8c7071063e76464af8518bb68d44e68efa81d5
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Thu Mar 15 03:29:38 2012 -0400

    Add ACLs to objects

diff --git a/etc/config.yml b/etc/config.yml
index 6c35058..e89a8fd 100644
--- a/etc/config.yml
+++ b/etc/config.yml
@@ -24,7 +24,7 @@ framework:
         login_by: username
         nav_menu: 0
         signup: 0
-  SkipAccessControl: 1
+  SkipAccessControl: 0
   Web: 
     BaseURL: http://localhost
     DataDir: var/mason
diff --git a/lib/Smokingit/Model/Branch.pm b/lib/Smokingit/Model/Branch.pm
index 6a5289a..bcb0f38 100644
--- a/lib/Smokingit/Model/Branch.pm
+++ b/lib/Smokingit/Model/Branch.pm
@@ -6,7 +6,8 @@ use Jifty::DBI::Schema;
 
 use Smokingit::Record schema {
     column project_id =>
-        references Smokingit::Model::Project;
+        references Smokingit::Model::Project,
+        is protected;
 
     column name =>
         type is 'text',
@@ -14,16 +15,20 @@ use Smokingit::Record schema {
         label is _("Branch name");
 
     column first_commit_id =>
-        references Smokingit::Model::Commit;
+        references Smokingit::Model::Commit,
+        is protected;
 
     column current_commit_id =>
-        references Smokingit::Model::Commit;
+        references Smokingit::Model::Commit,
+        is protected;
 
     column tested_commit_id =>
-        references Smokingit::Model::Commit;
+        references Smokingit::Model::Commit,
+        is protected;
 
     column last_status_update =>
-        references Smokingit::Model::Commit;
+        references Smokingit::Model::Commit,
+        is protected;
 
     column status =>
         type is 'text',
@@ -54,7 +59,8 @@ use Smokingit::Record schema {
 
     column current_actor =>
         type is 'text',
-        since '0.0.3';
+        since '0.0.3',
+        is protected;
 };
 
 sub create {
@@ -286,5 +292,16 @@ sub format_user {
 
 }
 
+sub current_user_can {
+    my $self  = shift;
+    my $right = shift;
+    my %args  = (@_);
+
+    return 1 if $right eq 'read';
+    return 1 if $right eq 'update' and $self->current_user->id;
+
+    return $self->SUPER::current_user_can($right => %args);
+}
+
 1;
 
diff --git a/lib/Smokingit/Model/Commit.pm b/lib/Smokingit/Model/Commit.pm
index f971566..4dba40f 100644
--- a/lib/Smokingit/Model/Commit.pm
+++ b/lib/Smokingit/Model/Commit.pm
@@ -36,6 +36,7 @@ use Smokingit::Record schema {
     column body =>
         type is 'text';
 };
+sub is_protected {1}
 
 sub create {
     my $self = shift;
@@ -230,5 +231,15 @@ sub status_cache_key {
     return "status-" . $self->sha;
 }
 
+sub current_user_can {
+    my $self  = shift;
+    my $right = shift;
+    my %args  = (@_);
+
+    return 1 if $right eq 'read';
+
+    return $self->SUPER::current_user_can($right => %args);
+}
+
 1;
 
diff --git a/lib/Smokingit/Model/Configuration.pm b/lib/Smokingit/Model/Configuration.pm
index 42b5f11..e48738c 100644
--- a/lib/Smokingit/Model/Configuration.pm
+++ b/lib/Smokingit/Model/Configuration.pm
@@ -47,5 +47,18 @@ sub create {
     return ($ok, $msg);
 }
 
+sub current_user_can {
+    my $self  = shift;
+    my $right = shift;
+    my %args  = (@_);
+
+    return 1 if $right eq 'create' and $self->current_user->id;
+    return 1 if $right eq 'read';
+    return 1 if $right eq 'update' and $self->current_user->id;
+    return 1 if $right eq 'delete' and $self->current_user->id;
+
+    return $self->SUPER::current_user_can($right => %args);
+}
+
 1;
 
diff --git a/lib/Smokingit/Model/Project.pm b/lib/Smokingit/Model/Project.pm
index e7cb530..1404a9a 100644
--- a/lib/Smokingit/Model/Project.pm
+++ b/lib/Smokingit/Model/Project.pm
@@ -266,5 +266,18 @@ sub schedule_tests {
     return $smokes;
 }
 
+sub current_user_can {
+    my $self  = shift;
+    my $right = shift;
+    my %args  = (@_);
+
+    return 1 if $right eq 'create' and $self->current_user->id;
+    return 1 if $right eq 'read';
+    return 1 if $right eq 'update' and $self->current_user->id;
+    return 1 if $right eq 'delete' and $self->current_user->id;
+
+    return $self->SUPER::current_user_can($right => %args);
+}
+
 1;
 
diff --git a/lib/Smokingit/Model/SmokeResult.pm b/lib/Smokingit/Model/SmokeResult.pm
index 846171a..3d79d96 100644
--- a/lib/Smokingit/Model/SmokeResult.pm
+++ b/lib/Smokingit/Model/SmokeResult.pm
@@ -60,6 +60,7 @@ use Smokingit::Record schema {
 
     column elapsed      => type is 'integer';
 };
+sub is_protected {1}
 
 sub short_error {
     my $self = shift;
@@ -190,5 +191,16 @@ sub post_result {
              .": ".($self->is_ok ? "OK" : "NOT OK"));
 }
 
+
+sub current_user_can {
+    my $self  = shift;
+    my $right = shift;
+    my %args  = (@_);
+
+    return 1 if $right eq 'read';
+
+    return $self->SUPER::current_user_can($right => %args);
+}
+
 1;
 

commit be74911c5d9f26b21733fbfe8a83aa904b06b849
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Thu Mar 15 03:30:12 2012 -0400

    Promote some tasks to running as the superuser for rights reasons

diff --git a/bin/local_updates b/bin/local_updates
index ed9b1cf..dda017f 100755
--- a/bin/local_updates
+++ b/bin/local_updates
@@ -53,8 +53,8 @@ $worker->register_function(
             $smoke->load_by_cols( %lookup );
             if ($smoke->id) {
                 $msg->("Re-testing $summary\n");
-                $smoke->set_submitted_at(undef);
-                $smoke->set_gearman_process(undef);
+                $smoke->as_superuser->set_submitted_at(undef);
+                $smoke->as_superuser->set_gearman_process(undef);
                 $tests += $smoke->run_smoke;
             } else {
                 # Guess which branch
@@ -82,7 +82,9 @@ $worker->register_function(
         my $job = shift;
         my $project_name = $job->arg;
 
-        my $project = Smokingit::Model::Project->new;
+        my $project = Smokingit::Model::Project->new(
+            current_user => Smokingit::CurrentUser->superuser,
+        );
         $project->load_by_cols( name => $project_name );
         return "No such project: $project_name\n"
             unless $project->id;
@@ -96,7 +98,9 @@ $worker->register_function(
 $worker->register_function(
     post_results => sub {
         my $job = shift;
-        my $smoke = Smokingit::Model::SmokeResult->new;
+        my $smoke = Smokingit::Model::SmokeResult->new(
+            current_user => Smokingit::CurrentUser->superuser,
+        );
         my ($ok, $msg) = $smoke->post_result($job->arg);
         warn "$msg\n";
         return $ok;
@@ -109,7 +113,9 @@ $worker->register_function(
         my $job = shift;
         my $project_name = $job->arg;
 
-        my $projects = Smokingit::Model::ProjectCollection->new;
+        my $projects = Smokingit::Model::ProjectCollection->new(
+            current_user => Smokingit::CurrentUser->superuser,
+        );
         if ($project_name) {
             $projects->limit( column => "name", value => $project_name );
         } else {
diff --git a/lib/Smokingit/Model/Commit.pm b/lib/Smokingit/Model/Commit.pm
index 4dba40f..933ad35 100644
--- a/lib/Smokingit/Model/Commit.pm
+++ b/lib/Smokingit/Model/Commit.pm
@@ -77,7 +77,9 @@ sub run_smoke {
         configuration_id => $config->id,
         commit_id        => $self->id,
     );
-    my $smoke = Smokingit::Model::SmokeResult->new;
+    my $smoke = Smokingit::Model::SmokeResult->new(
+        current_user => Smokingit::CurrentUser->superuser,
+    );
     $smoke->load_by_cols( %lookup );
     return 0 if $smoke->id;
 
diff --git a/lib/Smokingit/Model/Project.pm b/lib/Smokingit/Model/Project.pm
index 1404a9a..d700a27 100644
--- a/lib/Smokingit/Model/Project.pm
+++ b/lib/Smokingit/Model/Project.pm
@@ -60,7 +60,11 @@ sub sha {
     my $sha = shift;
     local $ENV{GIT_DIR} = $self->repository_path;
     my $commit = Smokingit::Model::Commit->new;
-    $commit->load_or_create( project_id => $self->id, sha => $sha );
+    $commit->load_by_cols( project_id => $self->id, sha => $sha );
+    return $commit if $commit->id;
+
+    $commit->as_superuser->create( project_id => $self->id, sha => $sha );
+    $commit->load_by_cols( project_id => $self->id, sha => $sha );
     return $commit;
 }
 
diff --git a/lib/Smokingit/Model/SmokeResult.pm b/lib/Smokingit/Model/SmokeResult.pm
index 3d79d96..b064642 100644
--- a/lib/Smokingit/Model/SmokeResult.pm
+++ b/lib/Smokingit/Model/SmokeResult.pm
@@ -112,8 +112,8 @@ sub run_smoke {
         { uniq => $self->id },
     );
     warn "Unable to insert run_tests job!\n" unless $job_id;
-    $self->set_gearman_process($job_id || "failed");
-    $self->set_queued_at( Jifty::DateTime->now );
+    $self->as_superuser->set_gearman_process($job_id || "failed");
+    $self->as_superuser->set_queued_at( Jifty::DateTime->now );
 
     # If we had a result for this already, we need to clean its status
     # out of the memcached cache.  Remove both the cache on the commit,

commit ee4701f0481e9a7a809eb8931aaf8986d7c5a849
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Thu Mar 15 03:30:37 2012 -0400

    Remove create/update parts of the UI for unpriv users

diff --git a/lib/Smokingit/View.pm b/lib/Smokingit/View.pm
index 2946d21..8b30fa0 100644
--- a/lib/Smokingit/View.pm
+++ b/lib/Smokingit/View.pm
@@ -38,6 +38,9 @@ template '/index.html' => page {
         };
     }
 
+    my $project = Smokingit::Model::Project->new;
+    return unless $project->current_user_can("create");
+
     div {
         { id is "create-project"; };
         h2 { "Add a project" };
diff --git a/lib/Smokingit/View/Branch.pm b/lib/Smokingit/View/Branch.pm
index 88c5a62..8c83437 100644
--- a/lib/Smokingit/View/Branch.pm
+++ b/lib/Smokingit/View/Branch.pm
@@ -100,7 +100,7 @@ template '/fragments/branch/properties' => sub {
         { id is "branch-properties" };
         js_handlers {
             onclick => {replace_with => "/fragments/branch/edit" }
-        };
+        } if $b->current_user_can("update");
 
         row {
             th { "Status" };
@@ -149,6 +149,10 @@ template '/fragments/branch/properties' => sub {
 template '/fragments/branch/edit' => sub {
     my $b = Smokingit::Model::Branch->new;
     $b->load( get('branch_id') );
+
+    redirect "/fragments/branch/properties"
+        unless $b->current_user_can("update");
+
     my $status = $b->status;
     form {
         my $update = $b->as_update_action( moniker => "update" );
diff --git a/lib/Smokingit/View/Configuration.pm b/lib/Smokingit/View/Configuration.pm
index f88e4fd..5d832a4 100644
--- a/lib/Smokingit/View/Configuration.pm
+++ b/lib/Smokingit/View/Configuration.pm
@@ -7,6 +7,10 @@ use Jifty::View::Declare -base;
 template '/config' => page {
     my $c = get('config');
     redirect '/' unless $c;
+
+    redirect "/project/". $c->project->name . "/"
+        unless $c->current_user_can("update");
+
     page_title is $c->name;
     div {{class is "subtitle"} $c->project->name };
     form {
@@ -26,6 +30,11 @@ template '/config' => page {
 
 template '/new-configuration' => page {
     redirect '/' unless get('project');
+
+    my $config = Smokingit::Model::Configuration->new;
+    redirect "/project/". get('project')->name . "/"
+        unless $config->current_user_can("create");
+
     page_title is get('project')->name;
     div {{class is "subtitle"} "New configuration" };
     form {
diff --git a/lib/Smokingit/View/Project.pm b/lib/Smokingit/View/Project.pm
index 391fe05..ebac662 100644
--- a/lib/Smokingit/View/Project.pm
+++ b/lib/Smokingit/View/Project.pm
@@ -24,17 +24,22 @@ template '/project' => page {
             ul {
                 while (my $c = $configs->next) {
                     li {
-                        hyperlink(
-                            label => $c->name,
-                            url => "config/" . $c->name,
-                        );
+                        if ($c->current_user_can("update")) {
+                            hyperlink(
+                                label => $c->name,
+                                url => "config/" . $c->name
+                            );
+                        } else {
+                            outs $c->name;
+                        }
                     };
                 }
             };
+            my $config = Smokingit::Model::Configuration->new;
             hyperlink(
                 label => "New configuration",
                 url => "new-configuration",
-            );
+            ) if $config->current_user_can("create");
         };
 
         div {

commit e2e24641907698901acd75d136e13a7e14e3795e
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Fri Mar 23 00:58:06 2012 -0400

    Slightly dimmer green on passing tests for legibility

diff --git a/share/web/static/css/app-late.css b/share/web/static/css/app-late.css
index 34b2a00..2c935e3 100644
--- a/share/web/static/css/app-late.css
+++ b/share/web/static/css/app-late.css
@@ -279,7 +279,7 @@ li {
   font-size: 145%;
 }
 
-.commitlist .commit.passing   .sha { color: #1c1; }
+.commitlist .commit.passing   .sha { color: #191; }
 .commitlist .commit.failing   .sha { color: #f11; }
 .commitlist .commit.todo      .sha { color: #fa0; }
 .commitlist .commit.parsefail .sha { color: #f11; }

commit 1f3fc27b6987c3e01b5ab2132fa6cfad2700298e
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Fri Mar 23 00:58:30 2012 -0400

    Reorder css rules for commits to a canonical order

diff --git a/share/web/static/css/app-late.css b/share/web/static/css/app-late.css
index 2c935e3..0ec6794 100644
--- a/share/web/static/css/app-late.css
+++ b/share/web/static/css/app-late.css
@@ -279,15 +279,15 @@ li {
   font-size: 145%;
 }
 
+.commitlist .commit.untested  .sha { color: #555; }
 .commitlist .commit.passing   .sha { color: #191; }
-.commitlist .commit.failing   .sha { color: #f11; }
 .commitlist .commit.todo      .sha { color: #fa0; }
+.commitlist .commit.failing   .sha { color: #f11; }
 .commitlist .commit.parsefail .sha { color: #f11; }
 .commitlist .commit.errors    .sha { color: #f11; }
 .commitlist .commit.testing   .sha { color: #11f; }
 .commitlist .commit.queued    .sha { color: #115; }
 .commitlist .commit.broken    .sha { color: #f11; }
-.commitlist .commit.untested  .sha { color: #555; }
 
 .commitlist .okbox {
   float: left;

commit dd06a6c6b5840565462c26a4950a67ad12fbff88
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Fri Mar 23 04:25:20 2012 -0400

    Order branches by mine first

diff --git a/lib/Smokingit/View/Project.pm b/lib/Smokingit/View/Project.pm
index ebac662..5799765 100644
--- a/lib/Smokingit/View/Project.pm
+++ b/lib/Smokingit/View/Project.pm
@@ -145,7 +145,6 @@ template '/fragments/project/branch-list' => sub {
 
 sub branchlist {
     my ($branches, %args) = @_;
-    $branches->order_by( column => "name" );
     if ($branches->count) {
         $branches->prefetch( name => "current_commit" );
         my $results = $branches->join(
@@ -169,6 +168,13 @@ sub branchlist {
                            error is_ok exit wait
                            passed failed parse_errors todo_passed/],
         );
+        my @order = ({ column   => "name" });
+        if (Jifty->web->current_user->id) {
+            my $actor = '%<'.Jifty->web->current_user->user_object->email.'>';
+            $actor = Jifty->handle->quote_value($actor);
+            unshift @order, { function => "main.current_actor like $actor", order => 'DESC' };
+        }
+        $branches->order_by( @order );
         div { class is "hline"; }
             if $args{hline};
         ul {

commit 7535959ee0789cd54094c1c8cb12f023b4d830a7
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Sat Mar 24 21:05:47 2012 -0400

    Missing smoke_results prefetch is is expected on new shas
    
    When a branch on a new project is rendered, we generate new Commit
    objects on demand, which will not have prefetched smoke_results.  As not
    having those isn't an error, merely a slight performance hit, remove the
    associated warning.

diff --git a/lib/Smokingit/Model/Commit.pm b/lib/Smokingit/Model/Commit.pm
index 933ad35..faa5026 100644
--- a/lib/Smokingit/Model/Commit.pm
+++ b/lib/Smokingit/Model/Commit.pm
@@ -93,10 +93,7 @@ sub run_smoke {
 sub hash_results {
     my $self = shift;
     my $results = shift || $self->prefetched( "smoke_results" );
-    unless ($results) {
-        warn "$self does not have smoke_results prefetched!\n";
-        return;
-    }
+    return unless $results;
     $self->{results} = {};
     $self->{results}{$_->configuration->id} = $_
         for @{$results->items_array_ref};

commit 546527a9572621314d4b4dc77773f52f9707b604
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Wed Jun 20 01:11:49 2012 -0400

    Move to Jifty::Plugin::RPC instead of Gearman
    
    Gearman, while it has its uses, does not have guaranteed execution.
    Make use of the Jifty::Plugin::RPC plugin to use AMQP for a back-end
    message bus for RPC; this provides guaranteed execution as well as a
    generalized message bus for other uses.  Guaranteed execution obviates
    the need for a check_queue call or method, but does require that the
    `gearman_process` column be renamed to the more general `queue_status`
    column.
    
    This commit removes the real-time completion updates that
    $status->percent provided; such functionality will be later replaced by
    the message bus.

diff --git a/Makefile.PL b/Makefile.PL
index 44c4dfa..66cb8fd 100644
--- a/Makefile.PL
+++ b/Makefile.PL
@@ -3,7 +3,6 @@ use inc::Module::Install;
 name        'Smokingit';
 version     '0.01';
 requires    'Jifty' => '1.01209';
-requires    'Gearman::Client';
 requires    'Git::PurePerl';
 
 WriteAll;
diff --git a/bin/local_updates b/bin/local_updates
index dda017f..f39c67b 100755
--- a/bin/local_updates
+++ b/bin/local_updates
@@ -7,24 +7,14 @@ use lib 'lib';
 use Jifty;
 BEGIN { Jifty->new; }
 
-use Gearman::Worker;
-my $worker = Gearman::Worker->new(
-    job_servers => Jifty->config->app('job_servers'),
-);
-
-# User task, synchronous
-$worker->register_function(
-    check_queue => sub {
-        my $restarted = Smokingit->check_queue;
-        return "Restarted $restarted tasks\n";
-    },
-);
+use AnyEvent;
 
 # User task, synchronous
-$worker->register_function(
-    retest => sub {
-        my $job = shift;
-        my ($sha,$configname) = $job->arg =~ /^([0-9a-fA-F]+)(?:\s*\[(.*)\])?/;
+Jifty->rpc->register(
+    name => "retest",
+    run  => sub {
+        my $arg = shift;
+        my ($sha,$configname) = $arg =~ /^([0-9a-fA-F]+)(?:\s*\[(.*)\])?/;
 
         my $commits = Smokingit::Model::CommitCollection->new;
         $commits->limit( column => "sha", operator => "like", value => "$sha%" );
@@ -54,7 +44,7 @@ $worker->register_function(
             if ($smoke->id) {
                 $msg->("Re-testing $summary\n");
                 $smoke->as_superuser->set_submitted_at(undef);
-                $smoke->as_superuser->set_gearman_process(undef);
+                $smoke->as_superuser->set_queue_status(undef);
                 $tests += $smoke->run_smoke;
             } else {
                 # Guess which branch
@@ -77,10 +67,10 @@ $worker->register_function(
 );
 
 # User or background task
-$worker->register_function(
-    sync_project => sub {
-        my $job = shift;
-        my $project_name = $job->arg;
+Jifty->rpc->register(
+    name => "sync_project",
+    run  => sub {
+        my $project_name = shift;
 
         my $project = Smokingit::Model::Project->new(
             current_user => Smokingit::CurrentUser->superuser,
@@ -95,23 +85,24 @@ $worker->register_function(
 );
 
 # Background task, from worker
-$worker->register_function(
-    post_results => sub {
-        my $job = shift;
+Jifty->rpc->register(
+    name => "post_results",
+    run  => sub {
+        my $data = shift;
         my $smoke = Smokingit::Model::SmokeResult->new(
             current_user => Smokingit::CurrentUser->superuser,
         );
-        my ($ok, $msg) = $smoke->post_result($job->arg);
+        my ($ok, $msg) = $smoke->post_result($data);
         warn "$msg\n";
         return $ok;
     },
 );
 
 # Background task
-$worker->register_function(
-    plan_tests => sub {
-        my $job = shift;
-        my $project_name = $job->arg;
+Jifty->rpc->register(
+    name => "plan_tests",
+    run  => sub {
+        my $project_name = shift;
 
         my $projects = Smokingit::Model::ProjectCollection->new(
             current_user => Smokingit::CurrentUser->superuser,
@@ -135,11 +126,9 @@ $worker->register_function(
 );
 
 # Schedule some basic maintenance
-Smokingit->gearman->dispatch_background( check_queue => 1 );
-
 my $projects = Smokingit::Model::ProjectCollection->new;
 $projects->unlimit;
-Smokingit->gearman->dispatch_background( sync_project => $_->name )
+Jifty->rpc->call( name => "sync_project", args => $_->name )
     while $_ = $projects->next;
 
-$worker->work while 1;
+AE::cv->recv;
diff --git a/bin/rpc b/bin/rpc
new file mode 100755
index 0000000..f6f7d67
--- /dev/null
+++ b/bin/rpc
@@ -0,0 +1,23 @@
+#!/usr/bin/env perl
+
+use AnyEvent::RabbitMQ::RPC;
+
+use strict;
+use warnings;
+
+die unless @ARGV == 2;
+my ($queue, $arg) = @ARGV;
+
+my $rpc = AnyEvent::RabbitMQ::RPC->new(
+    host   => 'localhost',
+    port   => 5672,
+    user   => 'guest',
+    pass   => 'guest',
+    vhost  => '/',
+    serialize => 'Storable',
+);
+
+print $rpc->call(
+    name => $queue,
+    args => $arg,
+);
diff --git a/bin/start-gearmand b/bin/start-gearmand
deleted file mode 100755
index 01d8a90..0000000
--- a/bin/start-gearmand
+++ /dev/null
@@ -1,9 +0,0 @@
-#!/bin/bash
-
-gearmand \
-    --daemon\
-    --verbose \
-    --verbose \
-    --pid-file=var/gearman.pid \
-    --log-file=log/gearmand.log \
-    --port=4730
diff --git a/etc/config.yml b/etc/config.yml
index e89a8fd..ffffea9 100644
--- a/etc/config.yml
+++ b/etc/config.yml
@@ -11,12 +11,13 @@ framework:
     Host: localhost
     Password: ''
     User: postgres
-    Version: 0.0.6
+    Version: 0.0.7
   DevelMode: 1
   LogLevel: INFO
   Mailer: Sendmail
   MailerArgs: []
-  Plugins: 
+  Plugins:
+    - RPC: {}
     - CompressedCSSandJS: {}
     - ErrorTemplates: {}
     - REST: {}
@@ -34,7 +35,5 @@ framework:
     StaticRoot: share/web/static
     TemplateRoot: share/web/templates
 application:
-  job_servers:
-    - 127.0.0.1:4730
   memcached_servers:
     - 127.0.0.1:11211
diff --git a/lib/Smokingit.pm b/lib/Smokingit.pm
index d2306a7..dbcfa7c 100644
--- a/lib/Smokingit.pm
+++ b/lib/Smokingit.pm
@@ -2,36 +2,15 @@ use strict;
 use warnings;
 
 package Smokingit;
-use Gearman::Client;
 use Cache::Memcached;
 
-our( $GEARMAN, $MEMCACHED );
+our( $MEMCACHED );
 
 sub start {
-    $GEARMAN = Gearman::Client->new;
-    $GEARMAN->job_servers( Jifty->config->app('job_servers') );
-
     $MEMCACHED = Cache::Memcached->new(
         { servers => Jifty->config->app( 'memcached_servers' ) } );
 }
 
-sub gearman   { $GEARMAN   }
 sub memcached { $MEMCACHED }
 
-sub check_queue {
-    my $job = shift;
-    my $queued = Smokingit::Model::SmokeResultCollection->new;
-    $queued->limit(
-        column => "submitted_at",
-        operator => "IS",
-        value => "NULL",
-    );
-    my $restarted = 0;
-    while (my $smoke = $queued->next) {
-        next if $smoke->gearman_status->known;
-        $restarted += $smoke->run_smoke;
-    }
-    return $restarted;
-}
-
 1;
diff --git a/lib/Smokingit/Model/Branch.pm b/lib/Smokingit/Model/Branch.pm
index bcb0f38..cdd081e 100644
--- a/lib/Smokingit/Model/Branch.pm
+++ b/lib/Smokingit/Model/Branch.pm
@@ -91,8 +91,9 @@ sub create {
         unless $self->project->branches->count == 1
             or $self->to_merge_into->id;
 
-    Smokingit->gearman->dispatch_background(
-        plan_tests => $self->project->name,
+    Jifty->rpc->call(
+        name => "plan_tests",
+        args => $self->project->name,
     ) if $plan_tests;
 
     return ($ok, $msg);
@@ -173,8 +174,9 @@ sub set_status {
         # It's no longer ignored; start testing where the tip is now,
         # not where it was when we first found it
         $self->set_tested_commit_id( $self->current_commit->id );
-        Smokingit->gearman->dispatch_background(
-            plan_tests => $self->project->name,
+        Jifty->rpc->call(
+            name => "plan_tests",
+            args => $self->project->name,
         );
     }
 
@@ -239,7 +241,7 @@ sub commit_list {
         name    => "smoke_results",
         alias   => $results,
         class   => "Smokingit::Model::SmokeResultCollection",
-        columns => [qw/id gearman_process configuration_id commit_id
+        columns => [qw/id queue_status configuration_id commit_id
                        error is_ok exit wait
                        passed failed parse_errors todo_passed/],
     );
diff --git a/lib/Smokingit/Model/Commit.pm b/lib/Smokingit/Model/Commit.pm
index faa5026..3bdd262 100644
--- a/lib/Smokingit/Model/Commit.pm
+++ b/lib/Smokingit/Model/Commit.pm
@@ -129,24 +129,18 @@ sub status {
         return @{$cache_value} if $cache_value;
 
         my @return;
-        if ($result->gearman_process) {
-            my $status = $result->gearman_status;
-            if (not $status->known) {
-                return ("broken", "Unknown");
-            } elsif ($status->running) {
-                my $percent = defined $status->percent
-                    ? int($status->percent*100)."%" : undef;
-                my $msg = defined $percent
-                    ? "$percent complete"
-                        : "Configuring";
-                return ("testing", $msg, $percent);
-            } else {
+        if (my $status = $result->queue_status) {
+            if ($status eq "queued") {
                 return ("queued", "Queued to test");
+            } elsif ($status eq "broken") {
+                return ("broken", "Failed to queue!");
+            } else {
+                return ("testing", $status, undef);
             }
         } elsif ($result->error) {
             @return = ("errors", $result->short_error);
         } elsif ($result->is_ok) {
-            @return = ("passing", $result->passed . " OK")
+            @return = ("passing", $result->passed . " OK");
         } elsif ($result->failed) {
             @return = ("failing", $result->failed . " failed");
         } elsif ($result->parse_errors) {
diff --git a/lib/Smokingit/Model/Configuration.pm b/lib/Smokingit/Model/Configuration.pm
index e48738c..1ea17ec 100644
--- a/lib/Smokingit/Model/Configuration.pm
+++ b/lib/Smokingit/Model/Configuration.pm
@@ -40,8 +40,9 @@ sub create {
     my ($ok, $msg) = $self->SUPER::create(@_);
     return ($ok, $msg) unless $ok;
 
-    Smokingit->gearman->dispatch_background(
-        plan_tests => $self->project->name,
+    Jifty->rpc->call(
+        name => "plan_tests",
+        args => $self->project->name,
     );
 
     return ($ok, $msg);
diff --git a/lib/Smokingit/Model/Project.pm b/lib/Smokingit/Model/Project.pm
index d700a27..44a84bb 100644
--- a/lib/Smokingit/Model/Project.pm
+++ b/lib/Smokingit/Model/Project.pm
@@ -36,8 +36,9 @@ sub create {
     return ($ok, $msg) unless $ok;
 
     # Kick off the clone in the background
-    Smokingit->gearman->dispatch_background(
-        sync_project => $self->name,
+    Jifty->rpc->call(
+        name => "sync_project",
+        args => $self->name,
     );
 
     return ($ok, $msg);
@@ -109,7 +110,7 @@ sub planned_tests {
     my $self = shift;
     my $tests = Smokingit::Model::SmokeResultCollection->new;
     $tests->limit(
-        column => "gearman_process",
+        column => "queue_status",
         operator => "IS NOT",
         value => "NULL"
     );
@@ -126,7 +127,7 @@ sub finished_tests {
     my $self = shift;
     my $tests = Smokingit::Model::SmokeResultCollection->new;
     $tests->limit(
-        column => "gearman_process",
+        column => "queue_status",
         operator => "IS",
         value => "NULL"
     );
diff --git a/lib/Smokingit/Model/SmokeResult.pm b/lib/Smokingit/Model/SmokeResult.pm
index b064642..292bec8 100644
--- a/lib/Smokingit/Model/SmokeResult.pm
+++ b/lib/Smokingit/Model/SmokeResult.pm
@@ -4,8 +4,6 @@ use warnings;
 package Smokingit::Model::SmokeResult;
 use Jifty::DBI::Schema;
 
-use Storable qw/nfreeze thaw/;
-
 use Smokingit::Record schema {
     column project_id =>
         is mandatory,
@@ -29,7 +27,12 @@ use Smokingit::Record schema {
         since '0.0.4';
 
     column gearman_process =>
-        type is 'text';
+        type is 'text',
+        till '0.0.7';
+
+    column queue_status =>
+        type is 'text',
+        since '0.0.7';
 
     column queued_at =>
         is timestamp,
@@ -69,18 +72,10 @@ sub short_error {
     return $msg;
 }
 
-use Gearman::JobStatus;
-sub gearman_status {
-    my $self = shift;
-    return Gearman::JobStatus->new(0,0) unless $self->gearman_process;
-    return $self->{job_status} ||= Smokingit->gearman->get_status($self->gearman_process)
-        || Gearman::JobStatus->new(0,0);
-}
-
 sub run_smoke {
     my $self = shift;
 
-    if ($self->gearman_status->known) {
+    if ($self->queue_status) {
         warn join( ":",
               $self->project->name,
               $self->configuration->name,
@@ -96,9 +91,9 @@ sub run_smoke {
               $self->commit->short_sha
           )."\n";
 
-    my $job_id = Smokingit->gearman->dispatch_background(
-        "run_tests",
-        nfreeze( {
+    Jifty->rpc->call(
+        name => "run_tests",
+        args => {
             smoke_id       => $self->id,
 
             project        => $self->project->name,
@@ -108,19 +103,21 @@ sub run_smoke {
             env            => $self->configuration->env,
             parallel       => ($self->configuration->parallel ? 1 : 0),
             test_glob      => $self->configuration->test_glob,
-        } ),
-        { uniq => $self->id },
+        },
+        on_sent => sub {
+            my $ok = shift;
+            $self->as_superuser->set_queue_status($ok ? "queued" : "broken");
+            $self->as_superuser->set_queued_at( Jifty::DateTime->now );
+
+            # If we had a result for this already, we need to clean its status
+            # out of the memcached cache.  Remove both the cache on the commit,
+            # as well as this smoke.
+            Smokingit->memcached->delete( $self->commit->status_cache_key );
+            Smokingit->memcached->delete( $self->status_cache_key );
+        },
     );
-    warn "Unable to insert run_tests job!\n" unless $job_id;
-    $self->as_superuser->set_gearman_process($job_id || "failed");
-    $self->as_superuser->set_queued_at( Jifty::DateTime->now );
-
-    # If we had a result for this already, we need to clean its status
-    # out of the memcached cache.  Remove both the cache on the commit,
-    # as well as this smoke.
-    Smokingit->memcached->delete( $self->commit->status_cache_key );
-    Smokingit->memcached->delete( $self->status_cache_key );
-    return $job_id ? 1 : 0;
+
+    return 1;
 }
 
 sub status_cache_key {
@@ -132,9 +129,7 @@ sub post_result {
     my $self = shift;
     my ($arg) = @_;
 
-    my %result;
-    eval { %result = %{ thaw( $arg ) } };
-    return (0, "Thaw failed: $@") if $@;
+    my %result = %{ $arg };
 
     # Properties to extract from the aggregator
     my @props =
@@ -168,7 +163,7 @@ sub post_result {
     $self->load( $smokeid );
     if (not $self->id) {
         return (0, "Invalid smoke ID: $smokeid");
-    } elsif (not $self->gearman_process) {
+    } elsif (not $self->queue_status) {
         return (0, "Smoke report on $smokeid which wasn't being smoked? (last report at @{[$self->submitted_at]})");
     }
 
@@ -178,7 +173,7 @@ sub post_result {
         $self->$method($result{$key});
     }
     # Mark as no longer smoking
-    $self->set_gearman_process(undef);
+    $self->set_queue_status(undef);
 
     # And commit all of that
     Jifty->handle->commit;
diff --git a/lib/Smokingit/View/GitHub.pm b/lib/Smokingit/View/GitHub.pm
index 8595f8d..e4e3a26 100644
--- a/lib/Smokingit/View/GitHub.pm
+++ b/lib/Smokingit/View/GitHub.pm
@@ -35,8 +35,9 @@ template '/github' => sub {
                 or return "Branch is not currently tested\n";
         }
 
-        Smokingit->gearman->dispatch_background(
-            sync_project => $project->name,
+        Jifty->api->call(
+            name => "sync_project",
+            args => $project->name,
         );
         return undef;
     };
diff --git a/lib/Smokingit/View/Project.pm b/lib/Smokingit/View/Project.pm
index 5799765..7b66d82 100644
--- a/lib/Smokingit/View/Project.pm
+++ b/lib/Smokingit/View/Project.pm
@@ -164,7 +164,7 @@ sub branchlist {
             name    => "smoke_results",
             alias   => $results,
             class   => "Smokingit::Model::SmokeResultCollection",
-            columns => [qw/id gearman_process configuration_id
+            columns => [qw/id queue_status configuration_id
                            error is_ok exit wait
                            passed failed parse_errors todo_passed/],
         );

commit f4b92237f6433490681acfa90d82da229d6b5062
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Wed Jun 20 01:43:59 2012 -0400

    Subscribe to updates on the `worker_progress` topic
    
    The worker (as of 4f044a7 in smokingit-worker.git) publishes status
    updates, including what Gearman used to provide via ->progress, as
    structured data on the `worker_progress` topic.  Subscribe to it in the
    local update thread and set the queue_status column based on it.

diff --git a/bin/local_updates b/bin/local_updates
index f39c67b..4e467a3 100755
--- a/bin/local_updates
+++ b/bin/local_updates
@@ -125,6 +125,30 @@ Jifty->rpc->register(
     }
 );
 
+my $listen = Jifty->bus->new_listener;
+$listen->subscribe( Jifty->bus->topic("worker_progress") );
+$listen->poll(
+    sub {
+        my %message = %{ shift() };
+        my $smoke = Smokingit::Model::SmokeResult->new(
+            current_user => Smokingit::CurrentUser->superuser,
+        );
+        $smoke->load( $message{smoke_id} );
+        my $status = $message{status};
+        if ($status eq "started") {
+            $smoke->set_queue_status("Processing commit");
+        } elsif ($status eq "configuring") {
+            $smoke->set_queue_status("Configuring");
+        } elsif ($status eq "testing") {
+            my $fraction = $message{complete}/($message{total} || 1);
+            $smoke->set_queue_status(
+                int($fraction*100) ."% complete");
+        } else {
+            warn "Unknown worker status: $status";
+        }
+    }
+);
+
 # Schedule some basic maintenance
 my $projects = Smokingit::Model::ProjectCollection->new;
 $projects->unlimit;
diff --git a/etc/config.yml b/etc/config.yml
index ffffea9..1b9d03a 100644
--- a/etc/config.yml
+++ b/etc/config.yml
@@ -17,6 +17,7 @@ framework:
   Mailer: Sendmail
   MailerArgs: []
   Plugins:
+    - PubSub: {}
     - RPC: {}
     - CompressedCSSandJS: {}
     - ErrorTemplates: {}
diff --git a/lib/Smokingit/Model/Commit.pm b/lib/Smokingit/Model/Commit.pm
index 3bdd262..be12c67 100644
--- a/lib/Smokingit/Model/Commit.pm
+++ b/lib/Smokingit/Model/Commit.pm
@@ -130,7 +130,9 @@ sub status {
 
         my @return;
         if (my $status = $result->queue_status) {
-            if ($status eq "queued") {
+            if ($status =~ /^(\d+%) complete$/) {
+                return ("testing", $status, $1);
+            } elsif ($status eq "queued") {
                 return ("queued", "Queued to test");
             } elsif ($status eq "broken") {
                 return ("broken", "Failed to queue!");

commit 7b905dae939d95e963b135508a2c921bbee07638
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Wed Jun 20 02:02:30 2012 -0400

    Refactor testing into an action

diff --git a/bin/local_updates b/bin/local_updates
index 4e467a3..1d6065e 100755
--- a/bin/local_updates
+++ b/bin/local_updates
@@ -5,7 +5,7 @@ use warnings;
 use lib 'lib';
 
 use Jifty;
-BEGIN { Jifty->new; }
+BEGIN { Jifty->new( no_request => 1 ); }
 
 use AnyEvent;
 
@@ -13,56 +13,7 @@ use AnyEvent;
 Jifty->rpc->register(
     name => "retest",
     run  => sub {
-        my $arg = shift;
-        my ($sha,$configname) = $arg =~ /^([0-9a-fA-F]+)(?:\s*\[(.*)\])?/;
-
-        my $commits = Smokingit::Model::CommitCollection->new;
-        $commits->limit( column => "sha", operator => "like", value => "$sha%" );
-        return "Unknown SHA\n"   if $commits->count == 0;
-        return "Ambiguous SHA\n" if $commits->count > 1;
-
-        my @msgs;
-        my $msg = sub { warn @_; push @msgs, @_ };
-
-        my $commit = $commits->next;
-
-        my $branch;
-
-        my $configs = $commit->project->configurations;
-        $configs->limit( column => "name", operator => "MATCHES", value => $configname )
-            if defined $configname and length $configname;
-        my $tests = 0;
-        while (my $config = $configs->next) {
-            my $summary = $commit->short_sha . "[" . $config->name ."]";
-            my %lookup = (
-                project_id       => $commit->project->id,
-                configuration_id => $config->id,
-                commit_id        => $commit->id,
-            );
-            my $smoke = Smokingit::Model::SmokeResult->new;
-            $smoke->load_by_cols( %lookup );
-            if ($smoke->id) {
-                $msg->("Re-testing $summary\n");
-                $smoke->as_superuser->set_submitted_at(undef);
-                $smoke->as_superuser->set_queue_status(undef);
-                $tests += $smoke->run_smoke;
-            } else {
-                # Guess which branch
-                unless ($branch) {
-                    $branch = Smokingit::Model::Branch->new;
-                    my @branches = $commit->branches;
-                    $branch->load_by_cols( name => $branches[0], project_id => $commit->project->id)
-                        if @branches == 1;
-                }
-                if ($branch->id) {
-                    $msg->("Testing $summary\n");
-                    $tests += $commit->run_smoke( $config, $branch );
-                } else {
-                    $msg->("No existing smoke for $summary found, and can't determine branch\n");
-                }
-            }
-        }
-        return join "", @msgs;
+        return Smokingit->test( @_ ) . "\n";
     },
 );
 
diff --git a/lib/Smokingit.pm b/lib/Smokingit.pm
index dbcfa7c..b1d5293 100644
--- a/lib/Smokingit.pm
+++ b/lib/Smokingit.pm
@@ -13,4 +13,19 @@ sub start {
 
 sub memcached { $MEMCACHED }
 
+sub test {
+    my $self = shift;
+
+    my $arg = shift;
+    my $action = Smokingit::Action::Test->new(
+        current_user => Smokingit::CurrentUser->superuser,
+        arguments    => { commit => $arg },
+    );
+    $action->validate;
+    return $action->result->field_error("commit") . "\n"
+        unless $action->result->success;
+    $action->run;
+    return ($action->result->message || $action->result->error);
+}
+
 1;
diff --git a/lib/Smokingit/Action/Test.pm b/lib/Smokingit/Action/Test.pm
new file mode 100644
index 0000000..fe0e309
--- /dev/null
+++ b/lib/Smokingit/Action/Test.pm
@@ -0,0 +1,168 @@
+use strict;
+use warnings;
+
+=head1 NAME
+
+Smokingit::Action::Test
+
+=cut
+
+package Smokingit::Action::Test;
+use base qw/Smokingit::Action/;
+
+use Jifty::Param::Schema;
+use Jifty::Action schema {
+    param 'commit' =>
+        label is 'Commit to test',
+        is mandatory;
+};
+
+sub validate_commit {
+    my $self = shift;
+    my $arg = shift;
+
+    unless ($arg =~ /^([0-9a-fA-F]+)(?:\s*\[\s*(.*)\s*\])?(?:\s*\{\s*(.*)\s*\})?$/) {
+        return $self->validation_error(
+            commit => "That doesn't look like a SHA" );
+    }
+    my ($sha,$config_text, $branch_text) = ($1, $2, $3);
+
+    my $commits = Smokingit::Model::CommitCollection->new;
+    $commits->limit( column => "sha", operator => "like", value => "$sha%" );
+    return $self->validation_error(
+        commit => "Unknown SHA $sha"
+    ) if $commits->count == 0;
+    return $self->validation_error(
+        commit => "Ambiguous SHA (".$commits->count." matches)"
+    ) if $commits->count > 1;
+
+    my $commit = $commits->first;
+    $sha = $commit->sha;
+
+    if (not $config_text) {
+        # We'll do all of them
+    } elsif ($config_text =~ /^\d+$/) {
+        my $config = Smokingit::Model::Configuration->new;
+        $config->load( $config_text );
+        return $self->validation_error( commit => "Invalid configuration id" )
+            unless $config->id and $config->project->id == $commit->project->id;
+    } else {
+        my $configs = $commit->project->configurations;
+        $configs->limit( column => "name", operator => "MATCHES", value => $config_text );
+        return $self->validation_error( commit => "Invalid configuration name" )
+            unless $configs->count == 1;
+        $config_text = $configs->first->id;
+    }
+
+    my $existing = Smokingit::Model::SmokeResultCollection->new;
+    $existing->limit(
+        column => "project_id",
+        value  => $commit->project->id
+    );
+    $existing->limit(
+        column => "commit_id",
+        value => $commit->id
+    );
+    $existing->limit(
+        column => "configuration_id",
+        value => $config_text
+    ) if $config_text;
+
+    if ($existing->count) {
+        # Re-testing the existing smokes
+        undef $branch_text;
+    } else {
+        my @branches = $commit->branches;
+        if ($branch_text) {
+            if ($branch_text =~ /^\d+$/) {
+                my $branch = Smokingit::Model::Branch->new;
+                $branch->load($branch_text);
+                return $self->validation_error( commit => "Invalid branch id" )
+                    unless $branch->id;
+                $branch_text = $branch->name;
+            }
+            return $self->validation_error( commit => "Invalid branch name" )
+                unless grep {$_ eq $branch_text} @branches;
+        } elsif (@branches == 1) {
+            $branch_text = $branches[0];
+        } else {
+            return $self->validation_error( commit => "Can't determine which branch to test on" );
+        }
+    }
+
+    $sha .= "[$config_text]" if $config_text;
+    $sha .= "{$branch_text}" if $branch_text;
+    $self->argument_value( commit => $sha );
+    return $self->validation_ok( "commit" );
+}
+
+sub take_action {
+    my $self = shift;
+
+    my $arg = $self->argument_value("commit");
+    my ($sha, $config, $branchname) =
+        $arg =~ /^([0-9a-fA-F]+)(?:\[(\d+)\])?(?:\{(.*)\})?$/;
+
+    my $commit = Smokingit::Model::Commit->new;
+    $commit->load_by_cols( sha => $sha );
+
+    if ($branchname) {
+        # Testing new commits
+        my $branch = Smokingit::Model::Branch->new;
+        $branch->load_by_cols( name => $branchname, project_id => $commit->project->id);
+
+        my $configs = $commit->project->configurations;
+        $configs->limit( column => "id", value => $config ) if $config;
+
+        while (my $config = $configs->next) {
+            $commit->run_smoke( $config, $branch );
+        }
+        $self->result->message(
+            "Testing "
+              . $configs->count
+              . " configurations of "
+              . $commit->short_sha
+          );
+    } else {
+        # Re-testing old commits
+        my $existing = Smokingit::Model::SmokeResultCollection->new;
+        $existing->limit(
+            column => "project_id",
+            value  => $commit->project->id,
+        );
+        $existing->limit(
+            column => "commit_id",
+            value => $commit->id,
+        );
+        $existing->limit(
+            column => "queue_status",
+            operator => "IS",
+            value => "NULL",
+        );
+        $existing->limit(
+            column => "configuration_id",
+            value => $config,
+        ) if $config;
+        $existing->order_by( column => 'queued_at' );
+
+        while (my $smoke = $existing->next) {
+            $smoke->as_superuser->set_submitted_at(undef);
+            $smoke->as_superuser->set_queue_status(undef);
+            $smoke->run_smoke;
+        }
+
+        if ($existing->count == 0) {
+            $self->result->message("Commits already queued for testing");
+        } else {
+            $self->result->message(
+                "Retesting "
+                  . $existing->count
+                  . " configurations of "
+                  . $commit->short_sha
+              );
+        }
+
+    }
+}
+
+1;

commit 6c9cf864d032587279dbf25acf0f03012b2af94c
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Wed Jun 20 02:06:19 2012 -0400

    Publish progress updates

diff --git a/bin/local_updates b/bin/local_updates
index 1d6065e..cffc2a1 100755
--- a/bin/local_updates
+++ b/bin/local_updates
@@ -85,18 +85,19 @@ $listen->poll(
             current_user => Smokingit::CurrentUser->superuser,
         );
         $smoke->load( $message{smoke_id} );
-        my $status = $message{status};
-        if ($status eq "started") {
+        my $status = Smokingit::Status->new( $smoke );
+        if ($message{status} eq "started") {
             $smoke->set_queue_status("Processing commit");
-        } elsif ($status eq "configuring") {
+        } elsif ($message{status} eq "configuring") {
             $smoke->set_queue_status("Configuring");
-        } elsif ($status eq "testing") {
+        } elsif ($message{status} eq "testing") {
             my $fraction = $message{complete}/($message{total} || 1);
             $smoke->set_queue_status(
                 int($fraction*100) ."% complete");
         } else {
-            warn "Unknown worker status: $status";
+            warn "Unknown worker status: $message{status}";
         }
+        $status->publish;
     }
 );
 
diff --git a/lib/Smokingit/Model/SmokeResult.pm b/lib/Smokingit/Model/SmokeResult.pm
index 292bec8..8f44910 100644
--- a/lib/Smokingit/Model/SmokeResult.pm
+++ b/lib/Smokingit/Model/SmokeResult.pm
@@ -3,6 +3,7 @@ use warnings;
 
 package Smokingit::Model::SmokeResult;
 use Jifty::DBI::Schema;
+use Smokingit::Status;
 
 use Smokingit::Record schema {
     column project_id =>
@@ -91,6 +92,7 @@ sub run_smoke {
               $self->commit->short_sha
           )."\n";
 
+    my $status = Smokingit::Status->new( $self );
     Jifty->rpc->call(
         name => "run_tests",
         args => {
@@ -108,12 +110,15 @@ sub run_smoke {
             my $ok = shift;
             $self->as_superuser->set_queue_status($ok ? "queued" : "broken");
             $self->as_superuser->set_queued_at( Jifty::DateTime->now );
+            $self->load($self->id);
 
             # If we had a result for this already, we need to clean its status
             # out of the memcached cache.  Remove both the cache on the commit,
             # as well as this smoke.
             Smokingit->memcached->delete( $self->commit->status_cache_key );
             Smokingit->memcached->delete( $self->status_cache_key );
+
+            $status->publish;
         },
     );
 
@@ -157,6 +162,8 @@ sub post_result {
     }
     $result{submitted_at} = Jifty::DateTime->now;
 
+    my $status = Smokingit::Status->new( $self );
+
     # Find the smoke
     Jifty->handle->begin_transaction;
     my $smokeid = delete $result{smoke_id};
@@ -178,6 +185,8 @@ sub post_result {
     # And commit all of that
     Jifty->handle->commit;
 
+    $status->publish;
+
     return (1, "Test result for "
              . $self->project->name
              ." ". $self->commit->short_sha
diff --git a/lib/Smokingit/Status.pm b/lib/Smokingit/Status.pm
new file mode 100644
index 0000000..1a17285
--- /dev/null
+++ b/lib/Smokingit/Status.pm
@@ -0,0 +1,93 @@
+use warnings;
+use strict;
+
+package Smokingit::Status;
+
+sub new {
+    my $class = shift;
+    my ($smoke) = @_;
+    my $self = bless { smoke => $smoke }, $class;
+    $self->before;
+    return $self;
+}
+
+sub before {
+    my $self = shift;
+    my $smoke = $self->{smoke};
+    $self->{before} = [ $smoke->commit->status($smoke) ],
+}
+
+sub publish {
+    my $self = shift;
+    my $smoke = $self->{smoke};
+    my $commit = $smoke->commit;
+
+    my $before = $self->{before};
+    my ($short, $long, $percent) = $commit->status($smoke);
+
+    # Set up for next time
+    $self->before;
+
+    return unless $before->[0] ne $short
+        or $before->[1] ne $long
+            or ($before->[2]||"") ne ($percent||"");
+
+    Jifty->bus->topic("test_progress")->publish( {
+        type        => "test_progress",
+        short_sha   => $commit->short_sha,
+        sha         => $commit->sha,
+        branch      => $smoke->branch_name,
+        config_name => $smoke->configuration->name,
+        config      => $smoke->configuration->id,
+        smoke_id    => $smoke->id,
+        raw_status  => $short,
+        status      => $long,
+        percent     => $percent,
+    } );
+
+    return if $before->[0] eq $short;
+
+    Jifty->bus->topic("commit_status")->publish( {
+        type       => "commit_status",
+        sha        => $commit->sha,
+        commit_id  => $commit->id,
+        raw_status => $commit->status,
+    } );
+
+    if ($short eq "queued") {
+        Jifty->bus->topic("test_queued")->publish( {
+            type       => "test_queued",
+            sha        => $commit->sha,
+            config     => $smoke->configuration->id,
+            smoke_id   => $smoke->id,
+        } );
+    }
+
+    return unless $short =~ /^(errors|passing|failing|parsefail|todo)$/;
+    Jifty->bus->topic("test_result")->publish( {
+        type       => "test_result",
+        sha        => $commit->sha,
+        config     => $smoke->configuration->id,
+        smoke_id   => $smoke->id,
+        raw_status => $short,
+        status     => $long,
+    } );
+
+    my $smoked = Smokingit::Model::SmokeResultCollection->new;
+    $smoked->limit( column => "commit_id", value => $commit->id );
+    $smoked->limit( column => "project_id", value => $smoke->project->id );
+    $smoked->limit(
+        column => "queue_status",
+        operator => "IS",
+        value => "NULL"
+    );
+    return unless $smoked->count == $smoke->project->configurations->count;
+    Jifty->bus->topic("commit_result")->publish( {
+        type       => "commit_result",
+        sha        => $commit->sha,
+        commit_id  => $commit->id,
+        raw_status => $commit->status,
+    } );
+}
+
+1;

commit f9b55c47d3a12a4fc633024b92eed9c21a442474
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Wed Jun 20 02:07:29 2012 -0400

    Push updates to planned and recent tests based on events

diff --git a/lib/Smokingit/View/Project.pm b/lib/Smokingit/View/Project.pm
index 7b66d82..e026db6 100644
--- a/lib/Smokingit/View/Project.pm
+++ b/lib/Smokingit/View/Project.pm
@@ -55,27 +55,53 @@ template '/project' => page {
             };
         };
 
-        my $tests = get('project')->finished_tests;
-        $tests->rows_per_page(10);
-        if ($tests->count) {
-            div {
-                id is "recent-tests";
-                class is "commitlist";
-                h2 { "Recent tests" };
-                test_result($_) while $_ = $tests->next;
-            };
-        }
+        render_region(
+            name => "finished",
+            path => "/fragments/project/finished",
+            defaults => { project_id => get('project')->id },
+        );
 
-        my $planned = get('project')->planned_tests;
-        if ($planned->count) {
-            div {
-                id is "planned-tests";
-                class is "commitlist";
-                h2 { "Planned tests" };
-                test_result($_) while $_ = $planned->next;
-            }
-        }
+        render_region(
+            name => "planned",
+            path => "/fragments/project/planned",
+            defaults => { project_id => get('project')->id },
+        );
+    };
+};
+
+template '/fragments/project/finished' => sub {
+    my $project = Smokingit::Model::Project->new;
+    $project->load( get('project_id') );
+
+    my $tests = $project->finished_tests;
+    $tests->rows_per_page(10);
+    div {
+        id is "recent-tests";
+        h2 { "Recent tests" };
+        span {
+            class is "commitlist";
+            test_result($_) while $_ = $tests->next;
+        };
+    };
+    Jifty->subs->update_on( topic => "test_queued" );
+    Jifty->subs->update_on( topic => "test_result" );
+};
+
+template '/fragments/project/planned' => sub {
+    my $project = Smokingit::Model::Project->new;
+    $project->load( get('project_id') );
+
+    my $planned = $project->planned_tests;
+    div {
+        id is "planned-tests";
+        h2 { "Planned tests" };
+        span {
+            class is "commitlist";
+            test_result($_) while $_ = $planned->next;
+        };
     };
+    Jifty->subs->update_on( topic => "test_queued" );
+    Jifty->subs->update_on( topic => "test_result" );
 };
 
 sub test_result {

commit dbca24044f519ab9db1579970f02e524dcc5d615
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Wed Jun 20 02:08:10 2012 -0400

    Add more classes to allow client-side updates

diff --git a/lib/Smokingit/View/Branch.pm b/lib/Smokingit/View/Branch.pm
index 8c83437..0948596 100644
--- a/lib/Smokingit/View/Branch.pm
+++ b/lib/Smokingit/View/Branch.pm
@@ -53,17 +53,17 @@ template '/branch' => page {
             my $merge = $commit->subject =~ /^Merge branch /
                 ? "merge" : "nonmerge";
             div {
-                {class is "$merge commit ".$commit->status};
+                {class is $commit->sha." $merge commit ".$commit->status};
                 for my $config (@configs) {
                     my ($status, $msg, $in) = $commit->status($config);
                     if ($status =~ /^(untested|testing|queued)$/) {
                         span {
-                            attr { class => "okbox $status", title => $msg };
+                            attr { class => "okbox $status config-".$config->id, title => $msg };
                             outs_raw($in ||" ")
                         };
                     } else {
                         hyperlink(
-                            class => "okbox $status",
+                            class => "okbox $status config-".$config->id,
                             label => " ",
                             escape_label => 0,
                             url   => "/test/".$commit->sha."/".$config->name,
diff --git a/lib/Smokingit/View/Project.pm b/lib/Smokingit/View/Project.pm
index e026db6..3d73bb3 100644
--- a/lib/Smokingit/View/Project.pm
+++ b/lib/Smokingit/View/Project.pm
@@ -108,10 +108,10 @@ sub test_result {
     my $test = shift;
     my ($status, $msg, $in) = $test->commit->status($test);
     div {
-        class is "commit $status";
+        class is $test->commit->sha." config-".$test->configuration->id." commit $status";
         if ($status =~ /^(untested|queued|testing|broken)$/) {
             span {
-                attr { class => "okbox $status", title => $msg };
+                attr { class => "okbox $status config-".$test->configuration->id, title => $msg };
                 outs_raw($in || " ")
             };
             span {
@@ -207,7 +207,7 @@ sub branchlist {
             while (my $b = $branches->next) {
                 $b->current_commit->hash_results( $b->prefetched("smoke_results") );
                 li {
-                    { class is $b->test_status; }
+                    { class is $b->test_status . " " . $b->current_commit->sha; }
                     hyperlink(
                         label => $b->name . " (" . $b->format_user('current_actor') . ")",
                         url => "branch/" . $b->name,

commit ae3378f7b943e92ac3d3860161db95542e37df11
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Wed Jun 20 02:08:42 2012 -0400

    Do client-side updates of the UI based on events

diff --git a/lib/Smokingit.pm b/lib/Smokingit.pm
index b1d5293..9873527 100644
--- a/lib/Smokingit.pm
+++ b/lib/Smokingit.pm
@@ -9,6 +9,7 @@ our( $MEMCACHED );
 sub start {
     $MEMCACHED = Cache::Memcached->new(
         { servers => Jifty->config->app( 'memcached_servers' ) } );
+    Jifty->web->add_javascript( "app-late.js" );
 }
 
 sub memcached { $MEMCACHED }
diff --git a/lib/Smokingit/View/Branch.pm b/lib/Smokingit/View/Branch.pm
index 0948596..129bab1 100644
--- a/lib/Smokingit/View/Branch.pm
+++ b/lib/Smokingit/View/Branch.pm
@@ -7,6 +7,10 @@ use Jifty::View::Declare -base;
 template '/branch' => page {
     my $b = get('branch');
     redirect '/' unless $b;
+
+    Jifty->subs->add( topic => $_ )
+        for qw/ test_progress commit_status /;
+
     page_title is $b->name;
     div {
         { class is "subtitle" }
diff --git a/lib/Smokingit/View/Project.pm b/lib/Smokingit/View/Project.pm
index 3d73bb3..7754a3e 100644
--- a/lib/Smokingit/View/Project.pm
+++ b/lib/Smokingit/View/Project.pm
@@ -6,6 +6,10 @@ use Jifty::View::Declare -base;
 
 template '/project' => page {
     redirect '/' unless get('project');
+
+    Jifty->subs->add( topic => $_ )
+        for qw/ test_progress commit_status /;
+
     page_title is get('project')->name;
     div {{class is "subtitle"} get('project')->repository_url };
 
diff --git a/share/web/static/js/app-late.js b/share/web/static/js/app-late.js
new file mode 100644
index 0000000..bb90313
--- /dev/null
+++ b/share/web/static/js/app-late.js
@@ -0,0 +1,30 @@
+var all_statuses = "broken errors failing todo passing parsefail testing queued untested";
+jQuery(function(){
+    jQuery(pubsub).bind("message.test_progress", function (event, d) {
+        jQuery(".commit."+d.sha+" .okbox.config-"+d.config)
+            .removeClass(all_statuses)
+            .addClass(d.raw_status)
+            .attr("title",d.status)
+            .text( d.percent );
+        jQuery(".commit."+d.sha+".config-"+d.config)
+            .removeClass(all_statuses)
+            .addClass(d.raw_status);
+        jQuery(".commit."+d.sha+".config-"+d.config+" .sha")
+            .attr("title",d.status);
+        if ( d.raw_status != "testing" )
+            jQuery(".commit."+d.sha+" .okbox.config-"+d.config)
+                .html( "&nbsp" );
+    });
+
+    jQuery(pubsub).bind("message.commit_status", function (event, d) {
+        jQuery(".biglist .commit."+d.sha)
+            .removeClass(all_statuses)
+            .addClass(d.raw_status);
+        jQuery("#branch-list ."+d.sha)
+            .removeClass(all_statuses)
+            .addClass(d.raw_status);
+        jQuery(".biglist .commit."+d.sha+" .testme")
+            .removeClass("testme")
+            .addClass("retestme");
+    });
+});

commit 598e7fef6563decb0443e1db941837c7702f894e
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Wed Jun 20 02:09:09 2012 -0400

    (Re-)testing buttons for logged-in users

diff --git a/lib/Smokingit/View/Branch.pm b/lib/Smokingit/View/Branch.pm
index 129bab1..8a71a0d 100644
--- a/lib/Smokingit/View/Branch.pm
+++ b/lib/Smokingit/View/Branch.pm
@@ -75,6 +75,15 @@ template '/branch' => page {
                         );
                     }
                 }
+                span {
+                    { class is ( $commit->status eq "untested" ? "testme" : "retestme" ) };
+                    my $sha = $commit->sha;
+                    my $branch = $b->id;
+                    js_handlers {
+                        onclick => "pubsub.send({type:'jifty.action',class:'Test',arguments:{commit:'$sha\{$branch}'}})"
+                    };
+                    outs_raw " ";
+                } if Jifty->web->current_user->id;
                 if ($commit->status =~ /^(untested|testing|queued)$/) {
                     span {
                         { class is "sha" };
diff --git a/lib/Smokingit/View/Project.pm b/lib/Smokingit/View/Project.pm
index 7754a3e..0ba371e 100644
--- a/lib/Smokingit/View/Project.pm
+++ b/lib/Smokingit/View/Project.pm
@@ -115,6 +115,10 @@ sub test_result {
         class is $test->commit->sha." config-".$test->configuration->id." commit $status";
         if ($status =~ /^(untested|queued|testing|broken)$/) {
             span {
+                attr { class => "spacer" };
+                outs_raw(" ")
+            } if Jifty->web->current_user->id;
+            span {
                 attr { class => "okbox $status config-".$test->configuration->id, title => $msg };
                 outs_raw($in || " ")
             };
@@ -123,6 +127,15 @@ sub test_result {
                 $test->commit->short_sha
             };
         } else {
+            span {
+                { class is "retestme" };
+                my $sha = $test->commit->sha;
+                my $config = $test->configuration->id;
+                js_handlers {
+                    onclick => "pubsub.send({type:'jifty.action',class:'Test',arguments:{commit:'$sha\[$config]'}})"
+                };
+                outs_raw " ";
+            } if Jifty->web->current_user->id;
             hyperlink(
                 class   => "okbox $status",
                 label   => " ",
diff --git a/share/web/static/css/app-late.css b/share/web/static/css/app-late.css
index 0ec6794..3fcffa8 100644
--- a/share/web/static/css/app-late.css
+++ b/share/web/static/css/app-late.css
@@ -289,7 +289,11 @@ li {
 .commitlist .commit.queued    .sha { color: #115; }
 .commitlist .commit.broken    .sha { color: #f11; }
 
-.commitlist .okbox {
+.commitlist .spacer {
+  float: left;
+  width: 32px;
+}
+.commitlist .okbox, .commitlist .testme, .commitlist .retestme {
   float: left;
   width: 32px;
   background-repeat: no-repeat;
@@ -310,6 +314,16 @@ li {
 .commitlist .okbox.queued    { background-image: url('/static/images/silk/hourglass.png');       }
 .commitlist .okbox.broken    { background-image: url('/static/images/silk/server_error.png');    }
 
+.commitlist .testme, .commitlist .retestme {
+    cursor: pointer;
+    opacity: 0.3;
+}
+.commitlist .testme:hover, .commitlist .retestme:hover {
+    opacity: 1;
+}
+.commitlist .testme   { background-image: url('/static/images/silk/control_play.png');   }
+.commitlist .retestme { background-image: url('/static/images/silk/control_repeat.png'); }
+
 .commitlist .branchpoint {
   clear: both;
   margin-top: 0.5em;
diff --git a/share/web/static/images/silk/control_play.png b/share/web/static/images/silk/control_play.png
new file mode 100644
index 0000000..0846555
Binary files /dev/null and b/share/web/static/images/silk/control_play.png differ
diff --git a/share/web/static/images/silk/control_repeat.png b/share/web/static/images/silk/control_repeat.png
new file mode 100644
index 0000000..1c4f57a
Binary files /dev/null and b/share/web/static/images/silk/control_repeat.png differ

commit 568040e26f76fe89362ca54f32a2c1a5dec0e388
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Sun Nov 4 01:22:58 2012 -0500

    Use now() to get millisecond resolution on queued_at

diff --git a/lib/Smokingit/Model/SmokeResult.pm b/lib/Smokingit/Model/SmokeResult.pm
index 8f44910..273c301 100644
--- a/lib/Smokingit/Model/SmokeResult.pm
+++ b/lib/Smokingit/Model/SmokeResult.pm
@@ -109,7 +109,10 @@ sub run_smoke {
         on_sent => sub {
             my $ok = shift;
             $self->as_superuser->set_queue_status($ok ? "queued" : "broken");
-            $self->as_superuser->set_queued_at( Jifty::DateTime->now );
+            # Use SQL so we get millisecond accuracy in the DB.  Otherwise rows
+            # inserted during the same second may not sort the same as they show
+            # up in the worker's queue.
+            $self->__set( column => 'queued_at', value => 'now()', is_sql_function => 1 );
             $self->load($self->id);
 
             # If we had a result for this already, we need to clean its status
@@ -160,7 +163,6 @@ sub post_result {
         # Unset the existing data if there was a fail
         $result{$_} = undef for @props, "is_ok", "elapsed";
     }
-    $result{submitted_at} = Jifty::DateTime->now;
 
     my $status = Smokingit::Status->new( $self );
 
@@ -174,6 +176,10 @@ sub post_result {
         return (0, "Smoke report on $smokeid which wasn't being smoked? (last report at @{[$self->submitted_at]})");
     }
 
+    # Use SQL so we get millisecond accuracy in the DB.  This is not as
+    # necessary as for queued_at (above), but is useful nonetheless.
+    $self->__set( column => 'submitted_at', value => 'now()', is_sql_function => 1 );
+
     # Update with the new data
     for my $key (keys %result) {
         my $method = "set_$key";

commit 37651f7b11e6a734614b3b77a240bad63bd19700
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Sun Nov 4 02:15:24 2012 -0500

    Never assign to global (non-lexical) $_, due to AnyEvent event loop
    
    Any place wherein a postfix while assigns to $_, it is modifying the
    global $_, not the lexical $_ that for and map make use of.  This leads
    to the possibility of action at a distance -- particularly when in an
    event-driven context such as AnyEvent.
    
    Observed failure modes of global $_ being modified when running under
    Twiggy include unpredictable server hangs and spurious errors; these
    were much more common under 5.16.0 than under 5.10.1, for whatever
    reason.

diff --git a/bin/local_updates b/bin/local_updates
index cffc2a1..681ad31 100755
--- a/bin/local_updates
+++ b/bin/local_updates
@@ -104,7 +104,8 @@ $listen->poll(
 # Schedule some basic maintenance
 my $projects = Smokingit::Model::ProjectCollection->new;
 $projects->unlimit;
-Jifty->rpc->call( name => "sync_project", args => $_->name )
-    while $_ = $projects->next;
+while (my $project = $projects->next) {
+    Jifty->rpc->call( name => "sync_project", args => $project->name )
+}
 
 AE::cv->recv;
diff --git a/lib/Smokingit/Model/Branch.pm b/lib/Smokingit/Model/Branch.pm
index cdd081e..a81159f 100644
--- a/lib/Smokingit/Model/Branch.pm
+++ b/lib/Smokingit/Model/Branch.pm
@@ -246,7 +246,10 @@ sub commit_list {
                        passed failed parse_errors todo_passed/],
     );
     my %commits;
-    $commits{$_->sha} = $_ while $_ = $commits->next;
+    while (my $commit = $commits->next) {
+        $commits{$commit->sha} = $commit;
+    }
+
     return map $commits{$_} || $self->project->sha($_), @revs;
 }
 
diff --git a/lib/Smokingit/View/Project.pm b/lib/Smokingit/View/Project.pm
index 0ba371e..498c262 100644
--- a/lib/Smokingit/View/Project.pm
+++ b/lib/Smokingit/View/Project.pm
@@ -84,7 +84,9 @@ template '/fragments/project/finished' => sub {
         h2 { "Recent tests" };
         span {
             class is "commitlist";
-            test_result($_) while $_ = $tests->next;
+            while (my $test = $tests->next) {
+                test_result($test);
+            }
         };
     };
     Jifty->subs->update_on( topic => "test_queued" );
@@ -101,7 +103,9 @@ template '/fragments/project/planned' => sub {
         h2 { "Planned tests" };
         span {
             class is "commitlist";
-            test_result($_) while $_ = $planned->next;
+            while (my $test = $planned->next) {
+                test_result($test);
+            }
         };
     };
     Jifty->subs->update_on( topic => "test_queued" );

commit fe9344b9cefae3976631be1618e7d024a1f6a7d3
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Sun Nov 4 19:28:48 2012 -0500

    Display undef next_actor better, with fewer warnings

diff --git a/lib/Smokingit/Model/Branch.pm b/lib/Smokingit/Model/Branch.pm
index a81159f..dc031c4 100644
--- a/lib/Smokingit/Model/Branch.pm
+++ b/lib/Smokingit/Model/Branch.pm
@@ -287,7 +287,9 @@ When we get user accounts, this will become $branch->current_actor->name
 sub format_user {
     my ($self, $type) = @_;
     if ($self->can($type)) {
-        if ( $self->$type =~ m/<(.*?)@/ ) {
+        if ( not defined $self->$type ) {
+            return "somebody";
+        } elsif ( $self->$type =~ m/<(.*?)@/ ) {
             return $1;
         }
     } else {

commit 15caa61b1d4413396c558a3c2d36ac21b97f9709
Merge: d3de530 fe9344b
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Sun Nov 4 23:27:18 2012 -0500

    Merge branch 'pubsub'


-----------------------------------------------------------------------



More information about the Bps-public-commit mailing list