summaryrefslogtreecommitdiffstats
path: root/extensions/TryAutoLand
diff options
context:
space:
mode:
Diffstat (limited to 'extensions/TryAutoLand')
-rw-r--r--extensions/TryAutoLand/Config.pm19
-rw-r--r--extensions/TryAutoLand/Extension.pm323
-rwxr-xr-xextensions/TryAutoLand/bin/TryAutoLand.getBugs.pl60
-rwxr-xr-xextensions/TryAutoLand/bin/TryAutoLand.updateStatus.pl65
-rwxr-xr-xextensions/TryAutoLand/bin/TryAutoLand.updateStatus_json.pl65
-rw-r--r--extensions/TryAutoLand/lib/Constants.pm31
-rw-r--r--extensions/TryAutoLand/lib/WebService.pm189
-rw-r--r--extensions/TryAutoLand/template/en/default/hook/bug/edit-after_custom_fields.html.tmpl101
-rw-r--r--extensions/TryAutoLand/template/en/default/hook/bug/field-help-end.none.tmpl15
-rw-r--r--extensions/TryAutoLand/template/en/default/hook/bug/show-header-end.html.tmpl11
-rw-r--r--extensions/TryAutoLand/template/en/default/hook/global/user-error-auth_failure_object.html.tmpl11
-rw-r--r--extensions/TryAutoLand/template/en/default/hook/global/user-error-errors.html.tmpl33
-rw-r--r--extensions/TryAutoLand/web/style.css23
13 files changed, 946 insertions, 0 deletions
diff --git a/extensions/TryAutoLand/Config.pm b/extensions/TryAutoLand/Config.pm
new file mode 100644
index 000000000..8b299183b
--- /dev/null
+++ b/extensions/TryAutoLand/Config.pm
@@ -0,0 +1,19 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+# This Source Code Form is "Incompatible With Secondary Licenses", as
+# defined by the Mozilla Public License, v. 2.0.
+
+package Bugzilla::Extension::TryAutoLand;
+use strict;
+
+use constant NAME => 'TryAutoLand';
+
+use constant REQUIRED_MODULES => [
+];
+
+use constant OPTIONAL_MODULES => [
+];
+
+__PACKAGE__->NAME;
diff --git a/extensions/TryAutoLand/Extension.pm b/extensions/TryAutoLand/Extension.pm
new file mode 100644
index 000000000..40dbb70d9
--- /dev/null
+++ b/extensions/TryAutoLand/Extension.pm
@@ -0,0 +1,323 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+# This Source Code Form is "Incompatible With Secondary Licenses", as
+# defined by the Mozilla Public License, v. 2.0.
+
+package Bugzilla::Extension::TryAutoLand;
+
+use strict;
+
+use base qw(Bugzilla::Extension);
+
+use Bugzilla::Bug;
+use Bugzilla::Attachment;
+use Bugzilla::User;
+use Bugzilla::Util qw(trick_taint diff_arrays);
+use Bugzilla::Error;
+
+use Bugzilla::Extension::TryAutoLand::Constants;
+
+our $VERSION = '0.01';
+
+BEGIN {
+ *Bugzilla::Bug::autoland_branches = \&_autoland_branches;
+ *Bugzilla::Bug::autoland_try_syntax = \&_autoland_try_syntax;
+ *Bugzilla::Attachment::autoland_checked = \&_autoland_attachment_checked;
+ *Bugzilla::Attachment::autoland_who = \&_autoland_attachment_who;
+ *Bugzilla::Attachment::autoland_status = \&_autoland_attachment_status;
+ *Bugzilla::Attachment::autoland_status_when = \&_autoland_attachment_status_when;
+ *Bugzilla::Attachment::autoland_update_status = \&_autoland_attachment_update_status;
+ *Bugzilla::Attachment::autoland_remove = \&_autoland_attachment_remove;
+}
+
+sub db_schema_abstract_schema {
+ my ($self, $args) = @_;
+ $args->{'schema'}->{'autoland_branches'} = {
+ FIELDS => [
+ bug_id => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ PRIMARYKEY => 1,
+ REFERENCES => {
+ TABLE => 'bugs',
+ COLUMN => 'bug_id',
+ DELETE => 'CASCADE'
+ }
+ },
+ branches => {
+ TYPE => 'VARCHAR(255)',
+ NOTNULL => 1
+ },
+ try_syntax => {
+ TYPE => 'VARCHAR(255)',
+ NOTNULL => 1,
+ DEFAULT => "''",
+ }
+ ],
+ };
+
+ $args->{'schema'}->{'autoland_attachments'} = {
+ FIELDS => [
+ attach_id => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ PRIMARYKEY => 1,
+ REFERENCES => {
+ TABLE => 'attachments',
+ COLUMN => 'attach_id',
+ DELETE => 'CASCADE'
+ },
+ },
+ who => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {
+ TABLE => 'profiles',
+ COLUMN => 'userid',
+ },
+ },
+ status => {
+ TYPE => 'varchar(64)',
+ NOTNULL => 1
+ },
+ status_when => {
+ TYPE => 'DATETIME',
+ NOTNULL => 1,
+ },
+ ],
+ };
+}
+
+sub install_update_db {
+ my ($self) = @_;
+ my $dbh = Bugzilla->dbh;
+
+ if (!$dbh->bz_column_info('autoland_branches', 'try_syntax')) {
+ $dbh->bz_add_column('autoland_branches', 'try_syntax', {
+ TYPE => 'VARCHAR(255)',
+ NOTNULL => 1,
+ DEFAULT => "''",
+ });
+ }
+}
+
+sub _autoland_branches {
+ my $self = shift;
+ return $self->{'autoland_branches'} if exists $self->{'autoland_branches'};
+ _preload_bug_data($self);
+ return $self->{'autoland_branches'};
+}
+
+sub _autoland_try_syntax {
+ my $self = shift;
+ return $self->{'autoland_try_syntax'} if exists $self->{'autoland_try_syntax'};
+ _preload_bug_data($self);
+ return $self->{'autoland_try_syntax'};
+}
+
+sub _preload_bug_data {
+ my ($self) = @_;
+ my $dbh = Bugzilla->dbh;
+ my $result = $dbh->selectrow_hashref("SELECT branches, try_syntax FROM autoland_branches
+ WHERE bug_id = ?", { Slice => {} }, $self->id);
+ if ($result) {
+ $self->{'autoland_branches'} = $result->{'branches'};
+ $self->{'autoland_try_syntax'} = $result->{'try_syntax'};
+ }
+ else {
+ $self->{'autoland_branches'} = undef;
+ $self->{'autoland_try_syntax'} = undef;
+ }
+}
+
+sub _autoland_attachment_checked {
+ my $self = shift;
+ my $dbh = Bugzilla->dbh;
+ return $self->{'autoland_checked'} if exists $self->{'autoland_checked'};
+ my $result = $dbh->selectrow_hashref("SELECT who, status, status_when
+ FROM autoland_attachments
+ WHERE attach_id = ?", { Slice => {} }, $self->id);
+ if ($result) {
+ $self->{'autoland_checked'} = 1;
+ $self->{'autoland_who'} = Bugzilla::User->new($result->{'who'});
+ $self->{'autoland_status'} = $result->{'status'};
+ $self->{'autoland_status_when'} = $result->{'status_when'};
+ }
+ else {
+ $self->{'autoland_checked'} = 0;
+ $self->{'autoland_who'} = undef;
+ $self->{'autoland_status'} = undef;
+ $self->{'autoland_status_when'} = undef;
+ }
+ return $self->{'autoland_checked'};
+}
+
+sub _autoland_attachment_who {
+ my $self = shift;
+ return undef if !$self->autoland_checked;
+ return $self->{'autoland_who'};
+}
+
+sub _autoland_attachment_status {
+ my $self = shift;
+ return undef if !$self->autoland_checked;
+ return $self->{'autoland_status'};
+}
+
+sub _autoland_attachment_status_when {
+ my $self = shift;
+ return undef if !$self->autoland_checked;
+ return $self->{'autoland_status_when'};
+}
+
+sub _autoland_attachment_update_status {
+ my ($self, $status) = @_;
+ my $dbh = Bugzilla->dbh;
+
+ return undef if !$self->autoland_checked;
+
+ grep($_ eq $status, VALID_STATUSES)
+ || ThrowUserError('autoland_invalid_status',
+ { status => $status,
+ valid => [ VALID_STATUSES ] });
+
+ if ($self->autoland_status ne $status) {
+ my $timestamp = $dbh->selectrow_array("SELECT LOCALTIMESTAMP(0)");
+ trick_taint($status);
+ $dbh->do("UPDATE autoland_attachments SET status = ?, status_when = ?
+ WHERE attach_id = ?", undef, $status, $timestamp, $self->id);
+ $self->{'autoland_status'} = $status;
+ $self->{'autoland_status_when'} = $timestamp;
+ }
+
+ return 1;
+}
+
+sub _autoland_attachment_remove {
+ my ($self) = @_;
+ my $dbh = Bugzilla->dbh;
+ return undef if !$self->autoland_checked;
+ $dbh->do("DELETE FROM autoland_attachments WHERE attach_id = ?", undef, $self->id);
+ delete $self->{'autoland_checked'};
+ delete $self->{'autoland_who'};
+ delete $self->{'autoland_status'};
+ delete $self->{'autoland_status_when'};
+}
+
+sub object_end_of_update {
+ my ($self, $args) = @_;
+ my $object = $args->{'object'};
+ my $user = Bugzilla->user;
+ my $dbh = Bugzilla->dbh;
+ my $cgi = Bugzilla->cgi;
+ my $params = Bugzilla->input_params;
+
+ return if !$user->in_group('autoland');
+
+ if ($object->isa('Bugzilla::Bug')) {
+ # First make any needed changes to the branches and try_syntax fields
+ my $bug_id = $object->bug_id;
+ my $bug_result = $dbh->selectrow_hashref("SELECT branches, try_syntax
+ FROM autoland_branches
+ WHERE bug_id = ?",
+ { Slice => {} }, $bug_id);
+
+ my $old_branches = '';
+ my $old_try_syntax = '';
+ if ($bug_result) {
+ $old_branches = $bug_result->{'branches'};
+ $old_try_syntax = $bug_result->{'try_syntax'};
+ }
+
+ my $new_branches = $params->{'autoland_branches'} || '';
+ my $new_try_syntax = $params->{'autoland_try_syntax'} || '';
+
+ my $set_attachments = [];
+ if (ref $params->{'autoland_attachments'}) {
+ $set_attachments = $params->{'autoland_attachments'};
+ } elsif ($params->{'autoland_attachments'}) {
+ $set_attachments = [ $params->{'autoland_attachments'} ];
+ }
+
+ # Check for required values
+ (!$new_branches && @{$set_attachments})
+ && ThrowUserError('autoland_empty_branches');
+ ($new_branches && !$new_try_syntax)
+ && ThrowUserError('autoland_empty_try_syntax');
+
+ trick_taint($new_branches);
+ if (!$new_branches && $old_branches) {
+ $dbh->do("DELETE FROM autoland_branches WHERE bug_id = ?",
+ undef, $bug_id);
+ }
+ elsif ($new_branches && !$old_branches) {
+ $dbh->do("INSERT INTO autoland_branches (bug_id, branches)
+ VALUES (?, ?)", undef, $bug_id, $new_branches);
+ }
+ elsif ($old_branches ne $new_branches) {
+ $dbh->do("UPDATE autoland_branches SET branches = ? WHERE bug_id = ?",
+ undef, $new_branches, $bug_id);
+ }
+
+ trick_taint($new_try_syntax);
+ if (($old_try_syntax ne $new_try_syntax) && $new_branches) {
+ $dbh->do("UPDATE autoland_branches SET try_syntax = ? WHERE bug_id = ?",
+ undef, $new_try_syntax, $bug_id);
+ }
+
+ # Next make any changes needed to each of the attachments.
+ # 1. If an attachment is checked it has a row in the table, if
+ # there is no row in the table it is not checked.
+ # 2. Do not allow changes to checked state if status == 'running' or status == 'waiting'
+ my $check_attachments = ref $params->{'defined_autoland_attachments'}
+ ? $params->{'defined_autoland_attachments'}
+ : [ $params->{'defined_autoland_attachments'} ];
+ my ($removed_attachments) = diff_arrays($check_attachments, $set_attachments);
+ foreach my $attachment (@{$object->attachments}) {
+ next if !$attachment->ispatch;
+ my $attach_id = $attachment->id;
+
+ my $checked = (grep $_ == $attach_id, @$set_attachments) ? 1 : 0;
+ my $unchecked = (grep $_ == $attach_id, @$removed_attachments) ? 1 : 0;
+ my $old_checked = $dbh->selectrow_array("SELECT 1 FROM autoland_attachments
+ WHERE attach_id = ?", undef, $attach_id) || 0;
+
+ next if $checked && $old_checked;
+
+ if ($unchecked && $old_checked && $attachment->autoland_status =~ /^(failed|success)$/) {
+ $dbh->do("DELETE FROM autoland_attachments WHERE attach_id = ?", undef, $attach_id);
+ }
+ elsif ($checked && !$old_checked) {
+ $dbh->do("INSERT INTO autoland_attachments (attach_id, who, status, status_when)
+ VALUES (?, ?, 'waiting', now())", undef, $attach_id, $user->id);
+ }
+ }
+
+ }
+}
+
+sub template_before_process {
+ my ($self, $args) = @_;
+ my $file = $args->{'file'};
+ my $vars = $args->{'vars'};
+
+ # in the header we just need to set the var to ensure the css gets included
+ if ($file eq 'bug/show-header.html.tmpl' && Bugzilla->user->in_group('autoland') ) {
+ $vars->{'autoland'} = 1;
+ }
+
+ if ($file eq 'bug/edit.html.tmpl') {
+ $vars->{'autoland_default_try_syntax'} = DEFAULT_TRY_SYNTAX;
+ }
+}
+
+sub webservice {
+ my ($self, $args) = @_;
+
+ my $dispatch = $args->{dispatch};
+ $dispatch->{TryAutoLand} = "Bugzilla::Extension::TryAutoLand::WebService";
+}
+
+__PACKAGE__->NAME;
diff --git a/extensions/TryAutoLand/bin/TryAutoLand.getBugs.pl b/extensions/TryAutoLand/bin/TryAutoLand.getBugs.pl
new file mode 100755
index 000000000..5d05831a8
--- /dev/null
+++ b/extensions/TryAutoLand/bin/TryAutoLand.getBugs.pl
@@ -0,0 +1,60 @@
+#!/usr/bin/perl -w
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+# This Source Code Form is "Incompatible With Secondary Licenses", as
+# defined by the Mozilla Public License, v. 2.0.
+
+use XMLRPC::Lite;
+use Data::Dumper;
+use HTTP::Cookies;
+
+###################################
+# Need to login first #
+###################################
+
+my $username = shift;
+my $password = shift;
+
+my $cookie_jar = new HTTP::Cookies( file => "/tmp/lwp_cookies.dat" );
+
+my $rpc = new XMLRPC::Lite;
+
+$rpc->proxy('http://fedora/726193/xmlrpc.cgi');
+
+$rpc->encoding('UTF-8');
+
+$rpc->transport->cookie_jar($cookie_jar);
+
+my $call = $rpc->call( 'User.login',
+ { login => $username, password => $password } );
+
+if ( $call->faultstring ) {
+ print $call->faultstring . "\n";
+ exit;
+}
+
+# Save the cookies in the cookie file
+$rpc->transport->cookie_jar->extract_cookies(
+ $rpc->transport->http_response );
+$rpc->transport->cookie_jar->save;
+
+print "Successfully logged in.\n";
+
+###################################
+# Main call here #
+###################################
+
+$call = $rpc->call('TryAutoLand.getBugs', { status => [] });
+
+my $result = "";
+if ( $call->faultstring ) {
+ print $call->faultstring . "\n";
+ exit;
+}
+else {
+ $result = $call->result;
+}
+
+print Dumper($result);
diff --git a/extensions/TryAutoLand/bin/TryAutoLand.updateStatus.pl b/extensions/TryAutoLand/bin/TryAutoLand.updateStatus.pl
new file mode 100755
index 000000000..4a8f92089
--- /dev/null
+++ b/extensions/TryAutoLand/bin/TryAutoLand.updateStatus.pl
@@ -0,0 +1,65 @@
+#!/usr/bin/perl -w
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+# This Source Code Form is "Incompatible With Secondary Licenses", as
+# defined by the Mozilla Public License, v. 2.0.
+
+use XMLRPC::Lite;
+use Data::Dumper;
+use HTTP::Cookies;
+
+###################################
+# Need to login first #
+###################################
+
+my $username = shift;
+my $password = shift;
+
+my $cookie_jar = new HTTP::Cookies( file => "/tmp/lwp_cookies.dat" );
+
+my $rpc = new XMLRPC::Lite;
+
+$rpc->proxy('http://fedora/726193/xmlrpc.cgi');
+
+$rpc->encoding('UTF-8');
+
+$rpc->transport->cookie_jar($cookie_jar);
+
+my $call = $rpc->call( 'User.login',
+ { login => $username, password => $password } );
+
+if ( $call->faultstring ) {
+ print $call->faultstring . "\n";
+ exit;
+}
+
+# Save the cookies in the cookie file
+$rpc->transport->cookie_jar->extract_cookies(
+ $rpc->transport->http_response );
+$rpc->transport->cookie_jar->save;
+
+print "Successfully logged in.\n";
+
+###################################
+# Main call here #
+###################################
+
+my $attach_id = shift;
+my $action = shift;
+my $status = shift;
+
+$call = $rpc->call('TryAutoLand.update',
+ { attach_id => $attach_id, action => $action, status => $status });
+
+my $result = "";
+if ( $call->faultstring ) {
+ print $call->faultstring . "\n";
+ exit;
+}
+else {
+ $result = $call->result;
+}
+
+print Dumper($result);
diff --git a/extensions/TryAutoLand/bin/TryAutoLand.updateStatus_json.pl b/extensions/TryAutoLand/bin/TryAutoLand.updateStatus_json.pl
new file mode 100755
index 000000000..f39b55229
--- /dev/null
+++ b/extensions/TryAutoLand/bin/TryAutoLand.updateStatus_json.pl
@@ -0,0 +1,65 @@
+#!/usr/bin/perl -w
+
+use JSON::RPC::Client;
+use Data::Dumper;
+use HTTP::Cookies;
+
+###################################
+# Need to login first #
+###################################
+
+my $username = shift;
+my $password = shift;
+
+my $cookie_jar = HTTP::Cookies->new( file => "/tmp/lwp_cookies.dat" );
+
+my $rpc = new JSON::RPC::Client;
+
+$rpc->ua->ssl_opts(verify_hostname => 0);
+
+my $uri = "http://fedora/726193/jsonrpc.cgi";
+
+#$rpc->ua->cookie_jar($cookie_jar);
+
+#my $result = $rpc->call($uri, { method => 'User.login', params =>
+# { login => $username, password => $password } });
+
+#if ($result) {
+# if ($result->is_error) {
+# print "Error : ", $result->error_message;
+# exit;
+# }
+# else {
+# print "Successfully logged in.\n";
+# }
+#}
+#else {
+# print $rpc->status_line;
+#}
+
+###################################
+# Main call here #
+###################################
+
+my $attach_id = shift;
+my $action = shift;
+my $status = shift;
+
+$result = $rpc->call($uri, { method => 'TryAutoLand.update',
+ params => { attach_id => $attach_id,
+ action => $action,
+ status => $status,
+ Bugzilla_login => $username,
+ Bugzilla_password => $password } });
+
+if ($result) {
+ if ($result->is_error) {
+ print "Error : ", $result->error_message;
+ exit;
+ }
+}
+else {
+ print $rpc->status_line;
+}
+
+print Dumper($result->result);
diff --git a/extensions/TryAutoLand/lib/Constants.pm b/extensions/TryAutoLand/lib/Constants.pm
new file mode 100644
index 000000000..53bad630a
--- /dev/null
+++ b/extensions/TryAutoLand/lib/Constants.pm
@@ -0,0 +1,31 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+# This Source Code Form is "Incompatible With Secondary Licenses", as
+# defined by the Mozilla Public License, v. 2.0.
+
+package Bugzilla::Extension::TryAutoLand::Constants;
+
+use strict;
+
+use base qw(Exporter);
+
+our @EXPORT = qw(
+ VALID_STATUSES
+ WEBSERVICE_USER
+ DEFAULT_TRY_SYNTAX
+);
+
+use constant VALID_STATUSES => qw(
+ waiting
+ running
+ failed
+ success
+);
+
+use constant WEBSERVICE_USER => 'autoland-try@mozilla.bugs';
+
+use constant DEFAULT_TRY_SYNTAX => '-b do -p all -u none -t none';
+
+1;
diff --git a/extensions/TryAutoLand/lib/WebService.pm b/extensions/TryAutoLand/lib/WebService.pm
new file mode 100644
index 000000000..1088386dd
--- /dev/null
+++ b/extensions/TryAutoLand/lib/WebService.pm
@@ -0,0 +1,189 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+# This Source Code Form is "Incompatible With Secondary Licenses", as
+# defined by the Mozilla Public License, v. 2.0.
+
+package Bugzilla::Extension::TryAutoLand::WebService;
+
+use strict;
+use warnings;
+
+use base qw(Bugzilla::WebService);
+
+use Bugzilla::Error;
+use Bugzilla::Util qw(trick_taint);
+
+use Bugzilla::Extension::TryAutoLand::Constants;
+
+use constant READ_ONLY => qw(
+ getBugs
+);
+
+# TryAutoLand.getBugs
+# Params: status - List of statuses to filter attachments (only 'waiting' is default)
+# Returns: List of bugs, each being a hash of data needed by the AutoLand polling server
+# Params
+# [ { bug_id => $bug_id1, attachments => [ $attach_id1, $attach_id2 ] }, branches => $branchListFromTextField ... ]
+
+sub getBugs {
+ my ($self, $params) = @_;
+ my $user = Bugzilla->user;
+ my $dbh = Bugzilla->dbh;
+ my %bugs;
+
+ if ($user->login ne WEBSERVICE_USER) {
+ ThrowUserError("auth_failure", { action => "access",
+ object => "autoland_attachments" });
+ }
+
+ my $status_where = "AND status = 'waiting'";
+ my $status_values = [];
+ if (exists $params->{'status'}) {
+ my $statuses = ref $params->{'status'}
+ ? $params->{'status'}
+ : [ $params->{'status'} ];
+ foreach my $status (@$statuses) {
+ if (grep($_ eq $status, VALID_STATUSES)) {
+ trick_taint($status);
+ push(@$status_values, $status);
+ }
+ }
+ if (@$status_values) {
+ my @qmarks = ("?") x @$status_values;
+ $status_where = "AND " . $dbh->sql_in('status', \@qmarks);
+ }
+
+ }
+
+ my $attachments = $dbh->selectall_arrayref("
+ SELECT attachments.bug_id,
+ attachments.attach_id,
+ autoland_attachments.who,
+ autoland_attachments.status,
+ autoland_attachments.status_when
+ FROM attachments, autoland_attachments
+ WHERE attachments.attach_id = autoland_attachments.attach_id
+ $status_where
+ ORDER BY attachments.bug_id",
+ undef, @$status_values);
+
+ foreach my $row (@$attachments) {
+ my ($bug_id, $attach_id, $al_who, $al_status, $al_status_when) = @$row;
+
+ my $al_user = Bugzilla::User->new($al_who);
+
+ # Silent Permission checks
+ next if !$user->can_see_bug($bug_id);
+ my $attachment = Bugzilla::Attachment->new($attach_id);
+ next if !$attachment
+ || $attachment->isobsolete
+ || ($attachment->isprivate && !$user->is_insider);
+
+ $bugs{$bug_id} = {} if !exists $bugs{$bug_id};
+
+ if (!$bugs{$bug_id}{'branches'}) {
+ my $bug_result = $dbh->selectrow_hashref("SELECT branches, try_syntax
+ FROM autoland_branches
+ WHERE bug_id = ?",
+ undef, $bug_id);
+ $bugs{$bug_id}{'branches'} = $bug_result->{'branches'};
+ $bugs{$bug_id}{'try_syntax'} = $bug_result->{'try_syntax'};
+ }
+
+ $bugs{$bug_id}{'attachments'} = [] if !exists $bugs{$bug_id}{'attachments'};
+
+ push(@{$bugs{$bug_id}{'attachments'}}, {
+ id => $self->type('int', $attach_id),
+ who => $self->type('string', $al_user->login),
+ status => $self->type('string', $al_status),
+ status_when => $self->type('dateTime', $al_status_when),
+ });
+ }
+
+ return [
+ map
+ { { bug_id => $_, attachments => $bugs{$_}{'attachments'},
+ branches => $bugs{$_}{'branches'}, try_syntax => $bugs{$_}{'try_syntax'} } }
+ keys %bugs
+ ];
+}
+
+# TryAutoLand.update({ attach_id => $attach_id, action => $action, status => $status })
+# Let's BMO know if a patch has landed or not and BMO will update the auto_land table accordingly
+# If $action eq 'status', $status will be a predetermined set of status values -- when waiting,
+# the UI for submitting autoland will be locked and once complete status update occurs or the
+# mapping is removed, the UI can be unlocked for the $attach_id
+# Allowed statuses: waiting, running, failed, or success
+#
+# If $action eq 'remove', the attach_id will be removed from the mapping table and the UI
+# will be unlocked for the $attach_id.
+
+sub update {
+ my ($self, $params) = @_;
+ my $user = Bugzilla->user;
+ my $dbh = Bugzilla->dbh;
+
+ if ($user->login ne WEBSERVICE_USER) {
+ ThrowUserError("auth_failure", { action => "modify",
+ object => "autoland_attachments" });
+ }
+
+ foreach my $param ('attach_id', 'action') {
+ defined $params->{$param}
+ || ThrowCodeError('param_required',
+ { param => $param });
+ }
+
+ my $action = delete $params->{'action'};
+ my $attach_id = delete $params->{'attach_id'};
+ my $status = delete $params->{'status'};
+
+ if ($action eq 'status' && !$status) {
+ ThrowCodeError('param_required', { param => 'status' });
+ }
+
+ grep($_ eq $action, ('remove', 'status'))
+ || ThrowUserError('autoland_update_invalid_action',
+ { action => $action,
+ valid => ["remove", "status"] });
+
+ my $attachment = Bugzilla::Attachment->new($attach_id);
+ $attachment
+ || ThrowUserError('autoland_invalid_attach_id',
+ { attach_id => $attach_id });
+
+ # Loud Permission checks
+ if (!$user->can_see_bug($attachment->bug_id)) {
+ ThrowUserError("bug_access_denied", { bug_id => $attachment->bug_id });
+ }
+ if ($attachment->isprivate && !$user->is_insider) {
+ ThrowUserError('auth_failure', { action => 'access',
+ object => 'attachment',
+ attach_id => $attachment->id });
+ }
+
+ $attachment->autoland_checked
+ || ThrowUserError('autoland_invalid_attach_id',
+ { attach_id => $attach_id });
+
+ if ($action eq 'status') {
+ # Update the status
+ $attachment->autoland_update_status($status);
+
+ return {
+ id => $self->type('int', $attachment->id),
+ who => $self->type('string', $attachment->autoland_who->login),
+ status => $self->type('string', $attachment->autoland_status),
+ status_when => $self->type('dateTime', $attachment->autoland_status_when),
+ };
+ }
+ elsif ($action eq 'remove') {
+ $attachment->autoland_remove();
+ }
+
+ return {};
+}
+
+1;
diff --git a/extensions/TryAutoLand/template/en/default/hook/bug/edit-after_custom_fields.html.tmpl b/extensions/TryAutoLand/template/en/default/hook/bug/edit-after_custom_fields.html.tmpl
new file mode 100644
index 000000000..ed6224afe
--- /dev/null
+++ b/extensions/TryAutoLand/template/en/default/hook/bug/edit-after_custom_fields.html.tmpl
@@ -0,0 +1,101 @@
+[%# This Source Code Form is subject to the terms of the Mozilla Public
+ # License, v. 2.0. If a copy of the MPL was not distributed with this
+ # file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ #
+ # This Source Code Form is "Incompatible With Secondary Licenses", as
+ # defined by the Mozilla Public License, v. 2.0.
+ #%]
+
+[% IF user.in_group('autoland') %]
+ [% autoland_attachments = [] %]
+ [% autoland_waiting = 0 %]
+ [% autoland_running = 0 %]
+ [% autoland_finished = 0 %]
+ [% FOREACH attachment = bug.attachments %]
+ [% NEXT IF attachment.isprivate && !user.is_insider && attachment.attacher.id != user.id %]
+ [% NEXT IF attachment.isobsolete %]
+ [% NEXT IF !attachment.ispatch %]
+ [% autoland_attachments.push(attachment) %]
+ [% IF attachment.autoland_checked %]
+ [% IF attachment.autoland_status == 'waiting' %]
+ [% autoland_waiting = autoland_waiting + 1 %]
+ [% END %]
+ [% IF attachment.autoland_status == 'running' %]
+ [% autoland_running = autoland_running + 1 %]
+ [% END %]
+ [% IF attachment.autoland_status == 'success' || attachment.autoland_status == 'failed' %]
+ [% autoland_finished = autoland_finished + 1 %]
+ [% END %]
+ [% END %]
+ [% END %]
+ [% IF autoland_attachments.size %]
+ <tr>
+ <th class="field_label field_land_autoland">
+ <a title="[% help_html.autoland FILTER txt FILTER collapse FILTER html %]"
+ class="field_help_link" href="https://wiki.mozilla.org/Build:Autoland">
+ AutoLand:</a>
+ </th>
+ <td>
+ <span id="autoland_edit_container">
+ (<a href="#" id="autoland_edit_action">edit</a>)
+ Total: [% autoland_attachments.size FILTER html %] -
+ <span class="autoland_waiting">Waiting:</span> [% autoland_waiting FILTER html %] -
+ <span class="autoland_running">Running:</span> [% autoland_running FILTER html %] -
+ <span class="autoland_success">Finished:</span> [% autoland_finished FILTER html %]
+ </span>
+ <div id="autoland_edit_input">
+ Branches (required):<br>
+ <input type="text" id="autoland_branches" name="autoland_branches"
+ value="[% bug.autoland_branches FILTER html %]" size="40"
+ class="text_input"><br>
+ Try Syntax (required): (Default: [% autoland_default_try_syntax FILTER html %])<br>
+ <input type="text" id="autoland_try_syntax" name="autoland_try_syntax"
+ value="[% bug.autoland_try_syntax || autoland_default_try_syntax FILTER html %]" size="40"
+ class="text_input"><br>
+ Patches:
+ <br>
+ <table id="autoland_edit_table">
+ [% FOREACH attachment = autoland_attachments %]
+ <tr>
+ <td>
+ [% IF attachment.autoland_checked %]
+ <input type="hidden" name="defined_autoland_attachments"
+ value="[% attachment.id FILTER html %]">
+ [% END %]
+ <input type="checkbox" name="autoland_attachments" value="[% attachment.id FILTER html %]"
+ [% ' checked="checked"' IF attachment.autoland_checked %]
+ [% IF attachment.autoland_status == 'running' || attachment.autoland_status == 'waiting' %]
+ disabled="disabled"
+ [% END %]>
+ </td>
+ <td>
+ <span title="[% attachment.description FILTER html %]">
+ [% attachment.filename FILTER html %]
+ </span>
+ <td>
+ [% IF attachment.autoland_checked %]
+ <span class="autoland_[% attachment.autoland_status FILTER html %]">
+ [% attachment.autoland_status FILTER html %]
+ </span>
+ [% END %]
+ </td>
+ <td>
+ [% IF attachment.autoland_checked %]
+ [% attachment.autoland_status_when FILTER time('%Y-%m-%d %H:%M') %]
+ [% END %]
+ </td>
+ </tr>
+ [% END %]
+ </table>
+ </div>
+ <script type="text/javascript">
+ hideEditableField('autoland_edit_container',
+ 'autoland_edit_input',
+ 'autoland_edit_action',
+ '',
+ '');
+ </script>
+ </td>
+ </tr>
+ [% END %]
+[% END %]
diff --git a/extensions/TryAutoLand/template/en/default/hook/bug/field-help-end.none.tmpl b/extensions/TryAutoLand/template/en/default/hook/bug/field-help-end.none.tmpl
new file mode 100644
index 000000000..899db60c4
--- /dev/null
+++ b/extensions/TryAutoLand/template/en/default/hook/bug/field-help-end.none.tmpl
@@ -0,0 +1,15 @@
+[%# This Source Code Form is subject to the terms of the Mozilla Public
+ # License, v. 2.0. If a copy of the MPL was not distributed with this
+ # file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ #
+ # This Source Code Form is "Incompatible With Secondary Licenses", as
+ # defined by the Mozilla Public License, v. 2.0.
+ #%]
+
+[%
+ vars.help_html.autoland =
+ "TryAutoLand is a BMO extension that allows integration with the $terms.Bugzilla
+ AutoLanding system. Select patches on a $terms.bug will be picked up
+ automatically and landed on the try build server for specified branches.
+ Results of the try build will be sent back to the bug report as comments."
+%]
diff --git a/extensions/TryAutoLand/template/en/default/hook/bug/show-header-end.html.tmpl b/extensions/TryAutoLand/template/en/default/hook/bug/show-header-end.html.tmpl
new file mode 100644
index 000000000..c61f478ea
--- /dev/null
+++ b/extensions/TryAutoLand/template/en/default/hook/bug/show-header-end.html.tmpl
@@ -0,0 +1,11 @@
+[%# This Source Code Form is subject to the terms of the Mozilla Public
+ # License, v. 2.0. If a copy of the MPL was not distributed with this
+ # file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ #
+ # This Source Code Form is "Incompatible With Secondary Licenses", as
+ # defined by the Mozilla Public License, v. 2.0.
+ #%]
+
+[% IF autoland %]
+ [% style_urls.push('extensions/TryAutoLand/web/style.css') %]
+[% END %]
diff --git a/extensions/TryAutoLand/template/en/default/hook/global/user-error-auth_failure_object.html.tmpl b/extensions/TryAutoLand/template/en/default/hook/global/user-error-auth_failure_object.html.tmpl
new file mode 100644
index 000000000..50a1e48d5
--- /dev/null
+++ b/extensions/TryAutoLand/template/en/default/hook/global/user-error-auth_failure_object.html.tmpl
@@ -0,0 +1,11 @@
+[%# This Source Code Form is subject to the terms of the Mozilla Public
+ # License, v. 2.0. If a copy of the MPL was not distributed with this
+ # file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ #
+ # This Source Code Form is "Incompatible With Secondary Licenses", as
+ # defined by the Mozilla Public License, v. 2.0.
+ #%]
+
+[% IF object == 'autoland_attachments' %]
+ AutoLand attachments
+[% END %]
diff --git a/extensions/TryAutoLand/template/en/default/hook/global/user-error-errors.html.tmpl b/extensions/TryAutoLand/template/en/default/hook/global/user-error-errors.html.tmpl
new file mode 100644
index 000000000..c12950dcf
--- /dev/null
+++ b/extensions/TryAutoLand/template/en/default/hook/global/user-error-errors.html.tmpl
@@ -0,0 +1,33 @@
+[%# This Source Code Form is subject to the terms of the Mozilla Public
+ # License, v. 2.0. If a copy of the MPL was not distributed with this
+ # file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ #
+ # This Source Code Form is "Incompatible With Secondary Licenses", as
+ # defined by the Mozilla Public License, v. 2.0.
+ #%]
+
+[% IF error == "autoland_invalid_status" %]
+ [% title = "AutoLand Invalid Status" %]
+ The status '[% status FILTER html %]' is not a valid
+ status for the AutoLand extension. Valid statuses
+ are [% valid.join(', ') FILTER html %].
+
+[% ELSIF error == "autoland_invalid_attach_id" %]
+ [% title = "AutoLand Invalid Attachment ID" %]
+ The attachment id '[% attach_id FILTER html %]' is not
+ a valid id for the AutoLand extension.
+
+[% ELSIF error == "autoland_empty_try_syntax" %]
+ [% title = "AutoLand Empty Try Syntax" %]
+ You cannot have a value for Branches and have an empty Try Syntax value.
+
+[% ELSIF error == "autoland_empty_branches" %]
+ [% title = "AutoLand Empty Branches" %]
+ You cannot check one or more patches for AutoLanding and have an empty
+ Branches value.
+
+[% ELSIF error == "autoland_update_invalid_action" %]
+ [% title = "AutoLand Update Invalid Action" %]
+ The action '[% action FILTER html %]' is not a valid action.
+ Valid actions are [% valid.join(', ') FILTER html %].
+[% END %]
diff --git a/extensions/TryAutoLand/web/style.css b/extensions/TryAutoLand/web/style.css
new file mode 100644
index 000000000..99409c0c0
--- /dev/null
+++ b/extensions/TryAutoLand/web/style.css
@@ -0,0 +1,23 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This Source Code Form is "Incompatible With Secondary Licenses", as
+ * defined by the Mozilla Public License, v. 2.0.
+ */
+
+.autoland_waiting {
+ color: blue;
+}
+
+.autoland_running {
+ color: orange;
+}
+
+.autoland_failed {
+ color: red;
+}
+
+.autoland_success {
+ color: green;
+}