web: replace 'errormsg' with 'errormsg IS NULL' in most cases

This is implement in an extremely hacky way due to poor DBIx feature
support. Ideally, what we'd need is a way to tell DBIx to ignore the
errormsg column unless explicitly requested, and to automatically add a
computed 'errormsg IS NULL' column in others. Since it does not support
that, this commit instead hacks some support via method overrides while
taking care to not break anything obvious.
This commit is contained in:
Pierre Bourdon 2024-04-12 17:33:27 +02:00
parent 258e9314a9
commit 6189ba9c5e
Signed by: delroth
GPG key ID: 6FB80DCD84DA0F1C
14 changed files with 85 additions and 13 deletions

View file

@ -87,6 +87,7 @@ let
DateTime DateTime
DBDPg DBDPg
DBDSQLite DBDSQLite
DBIxClassHelpers
DigestSHA1 DigestSHA1
EmailMIME EmailMIME
EmailSender EmailSender

View file

@ -371,6 +371,12 @@ sub errors_GET {
$c->stash->{template} = 'eval-error.tt'; $c->stash->{template} = 'eval-error.tt';
my $jobsetName = $c->stash->{params}->{name};
$c->stash->{jobset} = $c->stash->{project}->jobsets->find(
{ name => $jobsetName },
{ '+columns' => { 'errormsg' => 'errormsg' } }
);
$self->status_ok($c, entity => $c->stash->{jobset}); $self->status_ok($c, entity => $c->stash->{jobset});
} }

View file

@ -93,6 +93,8 @@ sub errors_GET {
$c->stash->{template} = 'eval-error.tt'; $c->stash->{template} = 'eval-error.tt';
$c->stash->{eval} = $c->model('DB::JobsetEvals')->find($c->stash->{eval}->id, { prefetch => 'evaluationerror' });
$self->status_ok($c, entity => $c->stash->{eval}); $self->status_ok($c, entity => $c->stash->{eval});
} }

View file

@ -286,8 +286,7 @@ sub getEvals {
my @evals = $evals_result_set->search( my @evals = $evals_result_set->search(
{ hasnewbuilds => 1 }, { hasnewbuilds => 1 },
{ order_by => "$me.id DESC", rows => $rows, offset => $offset { order_by => "$me.id DESC", rows => $rows, offset => $offset });
, prefetch => { evaluationerror => [ ] } });
my @res = (); my @res = ();
my $cache = {}; my $cache = {};

View file

@ -105,4 +105,6 @@ __PACKAGE__->add_column(
"+id" => { retrieve_on_insert => 1 } "+id" => { retrieve_on_insert => 1 }
); );
__PACKAGE__->mk_group_accessors('column' => 'has_error');
1; 1;

View file

@ -386,6 +386,8 @@ __PACKAGE__->add_column(
"+id" => { retrieve_on_insert => 1 } "+id" => { retrieve_on_insert => 1 }
); );
__PACKAGE__->mk_group_accessors('column' => 'has_error');
sub supportsDynamicRunCommand { sub supportsDynamicRunCommand {
my ($self) = @_; my ($self) = @_;

View file

@ -0,0 +1,30 @@
package Hydra::Schema::ResultSet::EvaluationErrors;
use strict;
use utf8;
use warnings;
use parent 'DBIx::Class::ResultSet';
use Storable qw(dclone);
__PACKAGE__->load_components('Helper::ResultSet::RemoveColumns');
# Exclude expensive error message values unless explicitly requested, and
# replace them with a summary field describing their presence/absence.
sub search_rs {
my ( $class, $query, $attrs ) = @_;
if ($attrs) {
$attrs = dclone($attrs);
}
unless (exists $attrs->{'select'} || exists $attrs->{'columns'}) {
$attrs->{'+columns'}->{'has_error'} = "errormsg != ''";
}
unless (exists $attrs->{'+columns'}->{'errormsg'}) {
push @{ $attrs->{'remove_columns'} }, 'errormsg';
}
return $class->next::method($query, $attrs);
}

View file

@ -0,0 +1,30 @@
package Hydra::Schema::ResultSet::Jobsets;
use strict;
use utf8;
use warnings;
use parent 'DBIx::Class::ResultSet';
use Storable qw(dclone);
__PACKAGE__->load_components('Helper::ResultSet::RemoveColumns');
# Exclude expensive error message values unless explicitly requested, and
# replace them with a summary field describing their presence/absence.
sub search_rs {
my ( $class, $query, $attrs ) = @_;
if ($attrs) {
$attrs = dclone($attrs);
}
unless (exists $attrs->{'select'} || exists $attrs->{'columns'}) {
$attrs->{'+columns'}->{'has_error'} = "errormsg != ''";
}
unless (exists $attrs->{'+columns'}->{'errormsg'}) {
push @{ $attrs->{'remove_columns'} }, 'errormsg';
}
return $class->next::method($query, $attrs);
}

View file

@ -513,7 +513,7 @@ BLOCK renderEvals %]
ELSE %] ELSE %]
- -
[% END %] [% END %]
[% IF eval.evaluationerror.errormsg %] [% IF eval.evaluationerror.has_error %]
<span class="badge badge-warning">Eval Errors</span> <span class="badge badge-warning">Eval Errors</span>
[% END %] [% END %]
</td> </td>
@ -639,7 +639,7 @@ BLOCK renderJobsetOverview %]
<td>[% HTML.escape(j.description) %]</td> <td>[% HTML.escape(j.description) %]</td>
<td>[% IF j.lastcheckedtime; <td>[% IF j.lastcheckedtime;
INCLUDE renderDateTime timestamp = j.lastcheckedtime; INCLUDE renderDateTime timestamp = j.lastcheckedtime;
IF j.errormsg || j.fetcherrormsg; %]&nbsp;<span class = 'badge badge-warning'>Error</span>[% END; IF j.has_error || j.fetcherrormsg; %]&nbsp;<span class = 'badge badge-warning'>Error</span>[% END;
ELSE; "-"; ELSE; "-";
END %]</td> END %]</td>
[% IF j.get_column('nrtotal') > 0 %] [% IF j.get_column('nrtotal') > 0 %]

View file

@ -90,7 +90,7 @@ c.uri_for(c.controller('JobsetEval').action_for('view'),
[% END %] [% END %]
<li class="nav-item"><a class="nav-link" href="#tabs-inputs" data-toggle="tab">Inputs</a></li> <li class="nav-item"><a class="nav-link" href="#tabs-inputs" data-toggle="tab">Inputs</a></li>
[% IF eval.evaluationerror.errormsg %] [% IF eval.evaluationerror.has_error %]
<li class="nav-item"><a class="nav-link" href="#tabs-errors" data-toggle="tab"><span class="text-warning">Evaluation Errors</span></a></li> <li class="nav-item"><a class="nav-link" href="#tabs-errors" data-toggle="tab"><span class="text-warning">Evaluation Errors</span></a></li>
[% END %] [% END %]
</ul> </ul>
@ -165,7 +165,7 @@ c.uri_for(c.controller('JobsetEval').action_for('view'),
[% END %] [% END %]
</div> </div>
[% IF eval.evaluationerror.errormsg %] [% IF eval.evaluationerror.has_error %]
<div id="tabs-errors" class="tab-pane"> <div id="tabs-errors" class="tab-pane">
<iframe src="[% c.uri_for(c.controller('JobsetEval').action_for('errors'), [eval.id], params) %]" loading="lazy" frameBorder="0" width="100%"></iframe> <iframe src="[% c.uri_for(c.controller('JobsetEval').action_for('errors'), [eval.id], params) %]" loading="lazy" frameBorder="0" width="100%"></iframe>
</div> </div>

View file

@ -61,7 +61,7 @@
[% END %] [% END %]
<li class="nav-item"><a class="nav-link active" href="#tabs-evaluations" data-toggle="tab">Evaluations</a></li> <li class="nav-item"><a class="nav-link active" href="#tabs-evaluations" data-toggle="tab">Evaluations</a></li>
[% IF jobset.errormsg || jobset.fetcherrormsg %] [% IF jobset.has_error || jobset.fetcherrormsg %]
<li class="nav-item"><a class="nav-link" href="#tabs-errors" data-toggle="tab"><span class="text-warning">Evaluation Errors</span></a></li> <li class="nav-item"><a class="nav-link" href="#tabs-errors" data-toggle="tab"><span class="text-warning">Evaluation Errors</span></a></li>
[% END %] [% END %]
<li class="nav-item"><a class="nav-link" href="#tabs-jobs" data-toggle="tab">Jobs</a></li> <li class="nav-item"><a class="nav-link" href="#tabs-jobs" data-toggle="tab">Jobs</a></li>
@ -79,7 +79,7 @@
<th>Last checked:</th> <th>Last checked:</th>
<td> <td>
[% IF jobset.lastcheckedtime %] [% IF jobset.lastcheckedtime %]
[% INCLUDE renderDateTime timestamp = jobset.lastcheckedtime %], [% IF jobset.errormsg || jobset.fetcherrormsg %]<em class="text-warning">with errors!</em>[% ELSE %]<em>no errors</em>[% END %] [% INCLUDE renderDateTime timestamp = jobset.lastcheckedtime %], [% IF jobset.has_error || jobset.fetcherrormsg %]<em class="text-warning">with errors!</em>[% ELSE %]<em>no errors</em>[% END %]
[% ELSE %] [% ELSE %]
<em>never</em> <em>never</em>
[% END %] [% END %]
@ -117,7 +117,7 @@
</div> </div>
[% IF jobset.errormsg || jobset.fetcherrormsg %] [% IF jobset.has_error || jobset.fetcherrormsg %]
<div id="tabs-errors" class="tab-pane"> <div id="tabs-errors" class="tab-pane">
<iframe src="[% c.uri_for('/jobset' project.name jobset.name "errors") %]" loading="lazy" frameBorder="0" width="100%"></iframe> <iframe src="[% c.uri_for('/jobset' project.name jobset.name "errors") %]" loading="lazy" frameBorder="0" width="100%"></iframe>
</div> </div>

View file

@ -22,7 +22,7 @@ like(
"The stderr record includes a relevant error message" "The stderr record includes a relevant error message"
); );
$jobset->discard_changes; # refresh from DB $jobset->discard_changes({ '+columns' => {'errormsg' => 'errormsg'} }); # refresh from DB
like( like(
$jobset->errormsg, $jobset->errormsg,
qr/aggregate job mixed_aggregate failed with the error: constituentA: does not exist/, qr/aggregate job mixed_aggregate failed with the error: constituentA: does not exist/,

View file

@ -14,7 +14,7 @@ our @EXPORT = qw(
sub evalSucceeds { sub evalSucceeds {
my ($jobset) = @_; my ($jobset) = @_;
my ($res, $stdout, $stderr) = captureStdoutStderr(60, ("hydra-eval-jobset", $jobset->project->name, $jobset->name)); my ($res, $stdout, $stderr) = captureStdoutStderr(60, ("hydra-eval-jobset", $jobset->project->name, $jobset->name));
$jobset->discard_changes; # refresh from DB $jobset->discard_changes({ '+columns' => {'errormsg' => 'errormsg'} }); # refresh from DB
if ($res) { if ($res) {
chomp $stdout; chomp $stderr; chomp $stdout; chomp $stderr;
utf8::decode($stdout) or die "Invalid unicode in stdout."; utf8::decode($stdout) or die "Invalid unicode in stdout.";
@ -29,7 +29,7 @@ sub evalSucceeds {
sub evalFails { sub evalFails {
my ($jobset) = @_; my ($jobset) = @_;
my ($res, $stdout, $stderr) = captureStdoutStderr(60, ("hydra-eval-jobset", $jobset->project->name, $jobset->name)); my ($res, $stdout, $stderr) = captureStdoutStderr(60, ("hydra-eval-jobset", $jobset->project->name, $jobset->name));
$jobset->discard_changes; # refresh from DB $jobset->discard_changes({ '+columns' => {'errormsg' => 'errormsg'} }); # refresh from DB
if (!$res) { if (!$res) {
chomp $stdout; chomp $stderr; chomp $stdout; chomp $stderr;
utf8::decode($stdout) or die "Invalid unicode in stdout."; utf8::decode($stdout) or die "Invalid unicode in stdout.";

View file

@ -13,7 +13,7 @@ my $constituentBuildA = $builds->{"constituentA"};
my $constituentBuildB = $builds->{"constituentB"}; my $constituentBuildB = $builds->{"constituentB"};
my $eval = $constituentBuildA->jobsetevals->first(); my $eval = $constituentBuildA->jobsetevals->first();
is($eval->evaluationerror->errormsg, ""); is($eval->evaluationerror->has_error, 0);
subtest "Verifying the direct aggregate" => sub { subtest "Verifying the direct aggregate" => sub {
my $aggBuild = $builds->{"direct_aggregate"}; my $aggBuild = $builds->{"direct_aggregate"};