forked from lix-project/hydra
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:
commit
0db950931a
11 changed files with 99 additions and 40 deletions
|
@ -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);
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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]);
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
});
|
});
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
2
src/sql/upgrade-24.sql
Normal 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;
|
Loading…
Reference in a new issue