Merge remote-tracking branch 'upstream/who-broke-builds' into upstream-master

Include information about who changed the build status in notification
emails, and enable optional per-input notification of said committers.

Conflicts due to two branches modifying the database schema.

Signed-off-by: Shea Levy <shea@shealevy.com>

Conflicts:
	src/lib/Hydra/Schema/Jobsets.pm
	src/sql/upgrade-23.sql
This commit is contained in:
Shea Levy 2013-10-15 09:49:20 -04:00
commit 0db950931a
11 changed files with 99 additions and 40 deletions

View file

@ -229,7 +229,11 @@ sub updateJobset {
error($c, "Invalid input name $name.") unless $name =~ /^[[:alpha:]][\w-]*$/; error($c, "Invalid input name $name.") unless $name =~ /^[[:alpha:]][\w-]*$/;
error($c, "Invalid input type $type.") unless defined $c->stash->{inputTypes}->{$type}; error($c, "Invalid input type $type.") unless defined $c->stash->{inputTypes}->{$type};
my $input = $jobset->jobsetinputs->create({ name => $name, type => $type }); my $input = $jobset->jobsetinputs->create({
name => $name,
type => $type,
emailresponsible => defined $c->stash->{params}->{"input-$baseName-emailresponsible"} ? 1 : 0
});
# Set the values for this input. # Set the values for this input.
my @values = ref($values) eq 'ARRAY' ? @{$values} : ($values); my @values = ref($values) eq 'ARRAY' ? @{$values} : ($values);

View file

@ -148,7 +148,7 @@ sub fetchInputSystemBuild {
} }
sub fetchInput { sub fetchInput {
my ($plugins, $db, $project, $jobset, $name, $type, $value) = @_; my ($plugins, $db, $project, $jobset, $name, $type, $value, $emailresponsible) = @_;
my @inputs; my @inputs;
if ($type eq "build") { if ($type eq "build") {
@ -177,7 +177,10 @@ sub fetchInput {
die "input `$name' has unknown type `$type'." unless $found; die "input `$name' has unknown type `$type'." unless $found;
} }
$_->{type} = $type foreach @inputs; foreach my $input (@inputs) {
$input->{type} = $type;
$input->{emailresponsible} = $emailresponsible;
}
return @inputs; return @inputs;
} }
@ -542,6 +545,7 @@ sub checkBuild {
, uri => $input->{uri} , uri => $input->{uri}
, revision => $input->{revision} , revision => $input->{revision}
, value => $input->{value} , value => $input->{value}
, emailresponsible => $input->{emailresponsible}
, dependency => $input->{id} , dependency => $input->{id}
, path => $input->{storePath} || "" # !!! temporary hack , path => $input->{storePath} || "" # !!! temporary hack
, sha256hash => $input->{sha256hash} , sha256hash => $input->{sha256hash}

View file

@ -27,6 +27,7 @@ our @EXPORT = qw(
parseJobsetName parseJobsetName
showJobName showJobName
showStatus showStatus
getResponsibleAuthors
); );
@ -261,4 +262,42 @@ sub showStatus {
} }
# Determine who broke/fixed the build.
sub getResponsibleAuthors {
my ($build, $plugins) = @_;
my $prevBuild = getPreviousBuild($build);
my $nrCommits = 0;
my %authors;
my @emailable_authors;
if ($prevBuild) {
foreach my $curInput ($build->buildinputs_builds) {
next unless ($curInput->type eq "git" || $curInput->type eq "hg");
my $prevInput = $prevBuild->buildinputs_builds->find({ name => $curInput->name });
next unless defined $prevInput;
next if $curInput->type ne $prevInput->type;
next if $curInput->uri ne $prevInput->uri;
next if $curInput->revision eq $prevInput->revision;
my @commits;
foreach my $plugin (@{$plugins}) {
push @commits, @{$plugin->getCommits($curInput->type, $curInput->uri, $prevInput->revision, $curInput->revision)};
}
foreach my $commit (@commits) {
#print STDERR "$commit->{revision} by $commit->{author}\n";
$authors{$commit->{author}} = $commit->{email};
push @emailable_authors, $commit->{email} if $curInput->emailresponsible;
$nrCommits++;
}
}
}
return (\%authors, $nrCommits, \@emailable_authors);
}
1; 1;

View file

@ -29,6 +29,11 @@ The following dependent jobs also failed:
[% END -%] [% END -%]
[% END -%] [% END -%]
[% IF nrCommits > 0 -%]
This is likely due to [% IF nrCommits > 1 -%][% nrCommits %] commits by [% END -%][% authorList %].
[% END -%]
[% IF build.buildstatus == 0 -%] [% IF build.buildstatus == 0 -%]
Yay! Yay!
[% ELSE -%] [% ELSE -%]
@ -74,6 +79,14 @@ sub buildFinished {
} }
} }
my ($authors, $nrCommits, $emailable_authors) = getResponsibleAuthors($build, $self->{plugins});
my $authorList;
if (scalar keys %{authors} > 0) {
my @x = map { "$_ <$authors->{$_}>" } (sort keys %{$authors});
$authorList = join(" or ", scalar @x > 1 ? join(", ", @[0..scalar @x - 2]): (), $x[-1]);
$addresses{$_} = { builds => [ $build ] } foreach (@{$emailable_authors});
}
# Send an email to each interested address. # Send an email to each interested address.
# !!! should use the Template Toolkit here. # !!! should use the Template Toolkit here.
@ -89,6 +102,8 @@ sub buildFinished {
, baseurl => $self->{config}->{'base_uri'} || "http://localhost:3000" , baseurl => $self->{config}->{'base_uri'} || "http://localhost:3000"
, showJobName => \&showJobName, showStatus => \&showStatus , showJobName => \&showJobName, showStatus => \&showStatus
, showSystem => index($build->job->name, $build->system) == -1 , showSystem => index($build->job->name, $build->system) == -1
, nrCommits => $nrCommits
, authorList => $authorList
}; };
my $body; my $body;

View file

@ -37,34 +37,7 @@ sub buildFinished {
return if scalar keys %rooms == 0; return if scalar keys %rooms == 0;
# Determine who broke/fixed the build. my ($authors, $nrCommits) = getResponsibleAuthors($build, $self->{plugins});
my $prevBuild = getPreviousBuild($build);
my $nrCommits = 0;
my %authors;
if ($prevBuild) {
foreach my $curInput ($build->buildinputs_builds) {
next unless ($curInput->type eq "git" || $curInput->type eq "hg");
my $prevInput = $prevBuild->buildinputs_builds->find({ name => $curInput->name });
next unless defined $prevInput;
next if $curInput->type ne $prevInput->type;
next if $curInput->uri ne $prevInput->uri;
next if $curInput->revision eq $prevInput->revision;
my @commits;
foreach my $plugin (@{$self->{plugins}}) {
push @commits, @{$plugin->getCommits($curInput->type, $curInput->uri, $prevInput->revision, $curInput->revision)};
}
foreach my $commit (@commits) {
#print STDERR "$commit->{revision} by $commit->{author}\n";
$authors{$commit->{author}} = $commit->{email};
$nrCommits++;
}
}
}
# Send a message to each room. # Send a message to each room.
foreach my $roomId (keys %rooms) { foreach my $roomId (keys %rooms) {
@ -84,9 +57,9 @@ sub buildFinished {
$msg .= " (and ${\scalar @deps} others)" if scalar @deps > 0; $msg .= " (and ${\scalar @deps} others)" if scalar @deps > 0;
$msg .= ": <a href='$baseurl/build/${\$build->id}'>" . showStatus($build) . "</a>"; $msg .= ": <a href='$baseurl/build/${\$build->id}'>" . showStatus($build) . "</a>";
if (scalar keys %authors > 0) { if (scalar keys %{$authors} > 0) {
# FIXME: HTML escaping # FIXME: HTML escaping
my @x = map { "<a href='mailto:$authors{$_}'>$_</a>" } (sort keys %authors); my @x = map { "<a href='mailto:$authors->{$_}'>$_</a>" } (sort keys %{$authors});
$msg .= ", likely due to "; $msg .= ", likely due to ";
$msg .= "$nrCommits commits by " if $nrCommits > 1; $msg .= "$nrCommits commits by " if $nrCommits > 1;
$msg .= join(" or ", scalar @x > 1 ? join(", ", @x[0..scalar @x - 2]) : (), $x[-1]); $msg .= join(" or ", scalar @x > 1 ? join(", ", @x[0..scalar @x - 2]) : (), $x[-1]);

View file

@ -72,6 +72,12 @@ __PACKAGE__->table("BuildInputs");
data_type: 'text' data_type: 'text'
is_nullable: 1 is_nullable: 1
=head2 emailresponsible
data_type: 'integer'
default_value: 0
is_nullable: 0
=head2 dependency =head2 dependency
data_type: 'integer' data_type: 'integer'
@ -105,6 +111,8 @@ __PACKAGE__->add_columns(
{ data_type => "text", is_nullable => 1 }, { data_type => "text", is_nullable => 1 },
"value", "value",
{ data_type => "text", is_nullable => 1 }, { data_type => "text", is_nullable => 1 },
"emailresponsible",
{ data_type => "integer", default_value => 0, is_nullable => 0 },
"dependency", "dependency",
{ data_type => "integer", is_foreign_key => 1, is_nullable => 1 }, { data_type => "integer", is_foreign_key => 1, is_nullable => 1 },
"path", "path",
@ -168,7 +176,7 @@ __PACKAGE__->belongs_to(
); );
# Created by DBIx::Class::Schema::Loader v0.07033 @ 2013-06-13 01:54:50 # Created by DBIx::Class::Schema::Loader v0.07033 @ 2013-10-08 13:08:15
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:tKZAybbNaRIMs9n5tHkqPw # DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:OaJPzRM+8XGsu3eIkqeYEw
1; 1;

View file

@ -57,6 +57,12 @@ __PACKAGE__->table("JobsetInputs");
data_type: 'text' data_type: 'text'
is_nullable: 0 is_nullable: 0
=head2 emailresponsible
data_type: 'integer'
default_value: 0
is_nullable: 0
=cut =cut
__PACKAGE__->add_columns( __PACKAGE__->add_columns(
@ -68,6 +74,8 @@ __PACKAGE__->add_columns(
{ data_type => "text", is_nullable => 0 }, { data_type => "text", is_nullable => 0 },
"type", "type",
{ data_type => "text", is_nullable => 0 }, { data_type => "text", is_nullable => 0 },
"emailresponsible",
{ data_type => "integer", default_value => 0, is_nullable => 0 },
); );
=head1 PRIMARY KEY =head1 PRIMARY KEY
@ -142,7 +150,7 @@ __PACKAGE__->has_many(
); );
# Created by DBIx::Class::Schema::Loader v0.07033 @ 2013-06-13 01:54:50 # Created by DBIx::Class::Schema::Loader v0.07033 @ 2013-10-08 13:06:15
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:UXBzqO0vHPql4LYyXpgEQg # DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:+mZZqLjQNwblb/EWW1alLQ
1; 1;

View file

@ -25,20 +25,23 @@
[% END %] [% END %]
[% IF edit %]<button type="button" class="add-inputalt btn btn-success" onclick='return false'><i class="icon-plus icon-white"></i></button>[% END %] [% IF edit %]<button type="button" class="add-inputalt btn btn-success" onclick='return false'><i class="icon-plus icon-white"></i></button>[% END %]
</td> </td>
<td>
<input type="checkbox" id="[% baseName %]-emailresponsible" name="[% baseName %]-emailresponsible" [% IF input.emailresponsible; 'checked="checked"'; END %]/>
</td>
</tr> </tr>
[% END %] [% END %]
[% BLOCK renderJobsetInputs %] [% BLOCK renderJobsetInputs %]
<table class="table table-striped table-condensed"> <table class="table table-striped table-condensed">
<thead> <thead>
<tr><th>Input name</th><th>Type</th><th>Values</th></tr> <tr><th>Input name</th><th>Type</th><th>Values</th><th>Notify committers</th></tr>
</thead> </thead>
<tbody class="inputs"> <tbody class="inputs">
[% FOREACH input IN jobset.jobsetinputs %] [% FOREACH input IN jobset.jobsetinputs %]
[% INCLUDE renderJobsetInput input=input baseName="input-$input.name" %] [% INCLUDE renderJobsetInput input=input baseName="input-$input.name" %]
[% END %] [% END %]
<tr> <tr>
<td colspan="3" style="text-align: center;"><button type="button" class="add-input btn btn-success"><i class="icon-plus icon-white"></i> Add a new input</button></td> <td colspan="4" style="text-align: center;"><button type="button" class="add-input btn btn-success"><i class="icon-plus icon-white"></i> Add a new input</button></td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
@ -163,6 +166,7 @@
var x = $("#input-template").clone(true).attr("id", "").insertBefore($(this).parents("tr")).show(); var x = $("#input-template").clone(true).attr("id", "").insertBefore($(this).parents("tr")).show();
$("#input-template-name", x).attr("name", newid + "-name"); $("#input-template-name", x).attr("name", newid + "-name");
$("#input-template-type", x).attr("name", newid + "-type"); $("#input-template-type", x).attr("name", newid + "-type");
$("#input-template-emailresponsible", x).attr("name", newid + "-emailresponsible");
$("#input-template", x).attr("id", newid); $("#input-template", x).attr("id", newid);
return false; return false;
}); });

View file

@ -34,7 +34,7 @@ sub fetchInputs {
foreach my $input ($jobset->jobsetinputs->all) { foreach my $input ($jobset->jobsetinputs->all) {
foreach my $alt ($input->jobsetinputalts->all) { foreach my $alt ($input->jobsetinputalts->all) {
push @{$$inputInfo{$input->name}}, $_ push @{$$inputInfo{$input->name}}, $_
foreach fetchInput($plugins, $db, $project, $jobset, $input->name, $input->type, $alt->value); foreach fetchInput($plugins, $db, $project, $jobset, $input->name, $input->type, $alt->value, $input->emailresponsible);
} }
} }
} }

View file

@ -77,6 +77,7 @@ create table JobsetInputs (
jobset text not null, jobset text not null,
name text not null, name text not null,
type text not null, -- "svn", "path", "uri", "string", "boolean", "nix" type text not null, -- "svn", "path", "uri", "string", "boolean", "nix"
emailResponsible integer not null default 0, -- whether to email committers to this input who change a build
primary key (project, jobset, name), primary key (project, jobset, name),
foreign key (project, jobset) references Jobsets(project, name) on delete cascade on update cascade foreign key (project, jobset) references Jobsets(project, name) on delete cascade on update cascade
); );
@ -257,6 +258,7 @@ create table BuildInputs (
uri text, uri text,
revision text, revision text,
value text, value text,
emailResponsible integer not null default 0,
dependency integer, -- build ID of the input, for type == 'build' dependency integer, -- build ID of the input, for type == 'build'
path text, path text,

2
src/sql/upgrade-24.sql Normal file
View file

@ -0,0 +1,2 @@
alter table JobsetInputs add column emailResponsible integer not null default 0;
alter table BuildInputs add column emailResponsible integer not null default 0;