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 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.
my @values = ref($values) eq 'ARRAY' ? @{$values} : ($values);

View file

@ -148,7 +148,7 @@ sub fetchInputSystemBuild {
}
sub fetchInput {
my ($plugins, $db, $project, $jobset, $name, $type, $value) = @_;
my ($plugins, $db, $project, $jobset, $name, $type, $value, $emailresponsible) = @_;
my @inputs;
if ($type eq "build") {
@ -177,7 +177,10 @@ sub fetchInput {
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;
}
@ -542,6 +545,7 @@ sub checkBuild {
, uri => $input->{uri}
, revision => $input->{revision}
, value => $input->{value}
, emailresponsible => $input->{emailresponsible}
, dependency => $input->{id}
, path => $input->{storePath} || "" # !!! temporary hack
, sha256hash => $input->{sha256hash}

View file

@ -27,6 +27,7 @@ our @EXPORT = qw(
parseJobsetName
showJobName
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;

View file

@ -29,6 +29,11 @@ The following dependent jobs also failed:
[% END -%]
[% END -%]
[% IF nrCommits > 0 -%]
This is likely due to [% IF nrCommits > 1 -%][% nrCommits %] commits by [% END -%][% authorList %].
[% END -%]
[% IF build.buildstatus == 0 -%]
Yay!
[% 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.
# !!! should use the Template Toolkit here.
@ -89,6 +102,8 @@ sub buildFinished {
, baseurl => $self->{config}->{'base_uri'} || "http://localhost:3000"
, showJobName => \&showJobName, showStatus => \&showStatus
, showSystem => index($build->job->name, $build->system) == -1
, nrCommits => $nrCommits
, authorList => $authorList
};
my $body;

View file

@ -37,34 +37,7 @@ sub buildFinished {
return if scalar keys %rooms == 0;
# Determine who broke/fixed the build.
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++;
}
}
}
my ($authors, $nrCommits) = getResponsibleAuthors($build, $self->{plugins});
# Send a message to each room.
foreach my $roomId (keys %rooms) {
@ -84,9 +57,9 @@ sub buildFinished {
$msg .= " (and ${\scalar @deps} others)" if scalar @deps > 0;
$msg .= ": <a href='$baseurl/build/${\$build->id}'>" . showStatus($build) . "</a>";
if (scalar keys %authors > 0) {
if (scalar keys %{$authors} > 0) {
# 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 .= "$nrCommits commits by " if $nrCommits > 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'
is_nullable: 1
=head2 emailresponsible
data_type: 'integer'
default_value: 0
is_nullable: 0
=head2 dependency
data_type: 'integer'
@ -105,6 +111,8 @@ __PACKAGE__->add_columns(
{ data_type => "text", is_nullable => 1 },
"value",
{ data_type => "text", is_nullable => 1 },
"emailresponsible",
{ data_type => "integer", default_value => 0, is_nullable => 0 },
"dependency",
{ data_type => "integer", is_foreign_key => 1, is_nullable => 1 },
"path",
@ -168,7 +176,7 @@ __PACKAGE__->belongs_to(
);
# Created by DBIx::Class::Schema::Loader v0.07033 @ 2013-06-13 01:54:50
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:tKZAybbNaRIMs9n5tHkqPw
# Created by DBIx::Class::Schema::Loader v0.07033 @ 2013-10-08 13:08:15
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:OaJPzRM+8XGsu3eIkqeYEw
1;

View file

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

View file

@ -25,20 +25,23 @@
[% 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>
<input type="checkbox" id="[% baseName %]-emailresponsible" name="[% baseName %]-emailresponsible" [% IF input.emailresponsible; 'checked="checked"'; END %]/>
</td>
</tr>
[% END %]
[% BLOCK renderJobsetInputs %]
<table class="table table-striped table-condensed">
<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>
<tbody class="inputs">
[% FOREACH input IN jobset.jobsetinputs %]
[% INCLUDE renderJobsetInput input=input baseName="input-$input.name" %]
[% END %]
<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>
</tbody>
</table>
@ -163,6 +166,7 @@
var x = $("#input-template").clone(true).attr("id", "").insertBefore($(this).parents("tr")).show();
$("#input-template-name", x).attr("name", newid + "-name");
$("#input-template-type", x).attr("name", newid + "-type");
$("#input-template-emailresponsible", x).attr("name", newid + "-emailresponsible");
$("#input-template", x).attr("id", newid);
return false;
});

View file

@ -34,7 +34,7 @@ sub fetchInputs {
foreach my $input ($jobset->jobsetinputs->all) {
foreach my $alt ($input->jobsetinputalts->all) {
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,
name text not null,
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),
foreign key (project, jobset) references Jobsets(project, name) on delete cascade on update cascade
);
@ -257,6 +258,7 @@ create table BuildInputs (
uri text,
revision text,
value text,
emailResponsible integer not null default 0,
dependency integer, -- build ID of the input, for type == 'build'
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;