File Coverage

blib/lib/Tapper/Reports/DPath.pm
Criterion Covered Total %
statement 173 229 75.5
branch 57 94 60.6
condition 11 23 47.8
subroutine 28 33 84.8
pod 10 10 100.0
total 279 389 71.7


line stmt bran cond sub pod time code
1             ## no critic (RequireUseStrict)
2             package Tapper::Reports::DPath;
3             # git description: v5.0.3-2-g27f36fd
4              
5             our $AUTHORITY = 'cpan:TAPPER';
6             # ABSTRACT: Tapper - Extended DPath functionality for Tapper reports
7             $Tapper::Reports::DPath::VERSION = '5.0.4';
8 9     9   386169 use 5.010;
  9         59  
9 9     9   2663 use Moose;
  9         2247201  
  9         83  
10              
11 9     9   71937 use Tapper::Model 'model', 'get_hardware_overview'; #, 'get_systems_id_for_hostname'
  9         8413900  
  9         735  
12 9     9   6581 use Text::Balanced 'extract_codeblock';
  9         87255  
  9         813  
13 9     9   4322 use Data::DPath::Path;
  9         341999  
  9         294  
14 9     9   92 use Data::Dumper;
  9         19  
  9         439  
15 9     9   6007 use CHI;
  9         501022  
  9         614  
16              
17             our $puresqlabstract = 0;
18              
19 9         87 use Sub::Exporter -setup => { exports => [ 'reportdata', 'testrundata', 'testplandata' ],
20             groups => { all => [ 'reportdata', 'testrundata', 'testplandata' ] },
21 9     9   80 };
  9         18  
22              
23             sub _extract_condition_attrs_and_path {
24 38     38   13410 my ($query_path) = @_;
25              
26 38         205 my $condition;
27             my $attrs;
28 38         0 my $path;
29              
30 38         0 my $head;
31 38         0 my $tail;
32 38         0 my $count;
33              
34             # first codeblock is condition
35 38         181 ($condition, $tail) = extract_codeblock($query_path, '{}');
36 38         13728 $count = ($tail =~ s/^\s*::\s*//g);
37              
38             # Maybe there is an optional second codeblock (attrs)
39             # but we need to find out by looking for a third block
40             # and then decide.
41 38         122 ($head, $tail) = extract_codeblock($tail, '{}');
42 38         5175 $count = ($tail =~ s/^\s*::\s*//g);
43              
44 38 100       112 if ($count) {
45 6         12 $attrs = $head;
46 6         10 $path = $tail;
47             } else {
48 32         53 $attrs = undef;
49 32         53 $path = $head;
50             }
51              
52             # tail is path
53 38         89 $path = $tail;
54 38         155 return ($condition, $attrs, $path);
55             }
56              
57             # backwards compatible frontend to new triplet API
58             sub _extract_condition_and_path {
59 23     23   6269 my ($condition, $attrs, $path) = _extract_condition_attrs_and_path(@_);
60 23         1298 warn "DEPRECATED _extract_condition_and_path() - use _extract_condition_attrs_and_path()\n";
61 23         207 return ($condition, $path); # no attrs
62             }
63              
64             # frontend alias for reports_dpath_search
65 16     16 1 25895 sub reportdata { reports_dpath_search(@_) } ## no critic (ProhibitSubroutinePrototypes)
66              
67             # frontend alias for testrun_dpath_search
68 3     3 1 14 sub testrundata { testrun_dpath_search(@_) } ## no critic (ProhibitSubroutinePrototypes)
69              
70             # frontend alias for testplan_dpath_search
71 0     0 1 0 sub testplandata { testplan_dpath_search(@_) } ## no critic (ProhibitSubroutinePrototypes)
72              
73             # allow trivial better readable column names
74             # - foo => 23 ... mapped to "me.foo" => 23
75             # - "report.foo" => 23 ... mapped to "me.foo" => 23
76             # - suite_name => "bar" ... mapped to "suite.name" => "bar"
77             # - -and => ... ... mapped to "-and" => ... # just to ensure that it doesn't produce: "me.-and" => ...
78             sub _fix_condition_reportdata
79             {
80 9     9   7081 no warnings 'uninitialized';
  9         38  
  9         2066  
81 55     55   1665 my $SQLKEYWORDS = 'like|-in|-and|-or';
82 55         153 my ($condition) = @_;
83             # joined suite
84 55         211 $condition =~ s/(['"])?\bsuite_name\b(['"])?\s*=>/"suite.name" =>/; # ';
85 55         143 $condition =~ s/(['"])?\breportgroup_testrun_id\b(['"])?\s*=>/"reportgrouptestrun.testrun_id" =>/; # ';
86 55         133 $condition =~ s/(['"])?\breportgroup_arbitrary_id\b(['"])?\s*=>/"reportgrouparbitrary.arbitrary_id" =>/; # ';
87 55         1164 $condition =~ s/([^-\w])(['"])?((report|me)\.)?(?<!suite\.)(?<!reportgrouparbitrary\.)(?<!reportgrouptestrun\.)(?!$SQLKEYWORDS)(\w+)\b(['"])?(\s*)=>/$1"me.$5" =>/; # ';
88              
89 55         345 return $condition;
90              
91             }
92              
93             # allow trivial better readable column names
94             # - foo => 23 ... mapped to "me.foo" => 23
95             # - "report.foo" => 23 ... mapped to "me.foo" => 23
96             # - testrun_id => 23 ... mapped to "me.testrun_id" => 23
97             # - suite_name => "bar" ... mapped to "suite.name" => "bar"
98             # - -and => ... ... mapped to "-and" => ... # just to ensure that it doesn't produce: "me.-and" => ...
99             sub _fix_condition_testrundata
100             {
101 9     9   87 no warnings 'uninitialized';
  9         23  
  9         19249  
102 3     3   6 my $SQLKEYWORDS = 'like|-in|-and|-or';
103 3         7 my ($condition) = @_;
104             # joined suite
105 3         29 $condition =~ s/([^-\w])(?<!\.)(['"])?((host|queue|testrun)_)(\w+)\b(['"])?(\s*)=>/$1"$4.$5" =>/g; # ';
106 3         9 return $condition;
107              
108             }
109              
110             # ===== CACHE =====
111              
112             # ----- cache complete Tapper::Reports::DPath queries -----
113              
114             sub _cachekey_whole_dpath {
115 0     0   0 my ($query_path) = @_;
116 0   0     0 my $key = ($ENV{TAPPER_DEVELOPMENT} || "0") . '::' . $query_path;
117 0         0 return $key;
118             }
119              
120             sub cache_whole_dpath {
121 19     19 1 78 my ($query_path, $rs_count, $res) = @_;
122              
123 19 50       82 return if $ENV{HARNESS_ACTIVE};
124              
125 0         0 my $cache = CHI->new( driver => 'File',
126             root_dir => '/tmp/cache/dpath',
127             serializer => 'Data::Dumper',
128             compress => 1,
129             );
130              
131 0 0       0 $cache->clear() if -e '/tmp/TAPPER_CACHE_CLEAR';
132              
133             # we cache on the dpath
134             # but need count to verify and maintain cache validity
135              
136             # say STDERR " -> set whole: $query_path ($rs_count)";
137 0         0 $cache->set( _cachekey_whole_dpath($query_path),
138             {
139             count => $rs_count,
140             res => $res,
141             });
142             }
143              
144             sub cached_whole_dpath {
145 19     19 1 73 my ($query_path, $rs_count) = @_;
146              
147 19 50       94 return if $ENV{HARNESS_ACTIVE};
148              
149 0         0 my $cache = CHI->new( driver => 'File',
150             root_dir => '/tmp/cache/dpath',
151             serializer => 'Data::Dumper',
152             compress => 1,
153             );
154 0 0       0 $cache->clear() if -e '/tmp/TAPPER_CACHE_CLEAR';
155 0         0 my $cached_res = $cache->get( _cachekey_whole_dpath($query_path) );
156              
157 0   0     0 my $cached_res_count = $cached_res->{count} || 0;
158             # say STDERR " <- get whole: $query_path ($rs_count vs. $cached_res_count)";
159 0 0       0 return if not defined $cached_res;
160              
161 0 0       0 if ($cached_res_count == $rs_count) {
162             # say STDERR " Gotcha!";
163             return $cached_res->{res}
164 0         0 }
165              
166             # clean up when matching report count changed
167 0         0 $cache->remove( $query_path );
168 0         0 return;
169             }
170              
171             # ----- cache single report dpaths queries -----
172              
173             sub _cachekey_single_dpath {
174 0     0   0 my ($path, $reports_id) = @_;
175 0   0     0 my $key = ($ENV{TAPPER_DEVELOPMENT} || "0") . '::' . $reports_id."::".$path;
176             #say STDERR " . $key";
177 0         0 return $key;
178             }
179              
180             sub cache_single_dpath {
181 41     41 1 134 my ($path, $cache_key, $res) = @_;
182              
183 41 50       197 return if $ENV{HARNESS_ACTIVE};
184              
185 0         0 my $cache = CHI->new( driver => 'File',
186             root_dir => '/tmp/cache/dpath',
187             serializer => 'Data::Dumper',
188             compress => 1,
189             );
190 0 0       0 $cache->clear() if -e '/tmp/TAPPER_CACHE_CLEAR';
191 0         0 $cache->set( _cachekey_single_dpath( $path, $cache_key ),
192             $res
193             );
194             }
195              
196             sub cached_single_dpath {
197 41     41 1 148 my ($path, $cache_key) = @_;
198              
199 41 50       160 return if $ENV{HARNESS_ACTIVE};
200              
201 0         0 my $cache = CHI->new( driver => 'File',
202             root_dir => '/tmp/cache/dpath',
203             serializer => 'Data::Dumper',
204             compress => 1,
205             );
206 0 0       0 $cache->clear() if -e '/tmp/TAPPER_CACHE_CLEAR';
207 0         0 my $cached_res = $cache->get( _cachekey_single_dpath( $path, $cache_key ));
208              
209             # print STDERR " <- get single: $cache_key -- $path: ".Dumper($cached_res);
210 0         0 return $cached_res;
211             }
212              
213             # ===== the query search =====
214              
215             sub reports_dpath_search($) { ## no critic (ProhibitSubroutinePrototypes)
216 16     16 1 56 my ($query_path) = @_;
217              
218 16         68 my ($condition, $path) = _extract_condition_and_path($query_path);
219 16         122 $path =~ s/^\s+|\s+$//; # drop leading+trailing whitespace
220 16         148 my $dpath = new Data::DPath::Path( path => $path );
221 16 50       6604 $condition = _fix_condition_reportdata($condition) unless $puresqlabstract;
222 16 100       55 my %condition = $condition ? %{ eval $condition } : (); ## no critic (ProhibitStringyEval)
  15         888  
223 16         121 my $rs = model('TestrunDB')->resultset('Report')->search
224             (
225             {
226             %condition
227             },
228             {
229             order_by => 'me.id asc',
230             columns => [ qw(
231             id
232             suite_id
233             suite_version
234             reportername
235             peeraddr
236             peerport
237             peerhost
238             successgrade
239             total
240             failed
241             parse_errors
242             passed
243             skipped
244             todo
245             todo_passed
246             success_ratio
247             starttime_test_program
248             endtime_test_program
249             machine_name
250             machine_description
251             created_at
252             updated_at
253             )],
254             join => [ 'suite', 'reportgrouptestrun', 'reportgrouparbitrary' ],
255             '+select' => [ 'suite.name', 'reportgrouptestrun.testrun_id', 'reportgrouparbitrary.arbitrary_id'],
256             '+as' => [ 'suite.name', 'reportgrouptestrun.testrun_id', 'reportgrouparbitrary.arbitrary_id'],
257             }
258             );
259 16         54557 my $rs_count = $rs->count();
260 16         231100 my @res = ();
261              
262             # layer 2 cache
263 16         95 my $cached_res = cached_whole_dpath( $query_path, $rs_count );
264 16 50       59 return @$cached_res if defined $cached_res;
265              
266 16         59 while (my $row = $rs->next)
267             {
268 34         377480 my $report_id = $row->id;
269              
270             # layer 1 cache
271 34         653 my $cached_row_res = cached_single_dpath( $path, "r$report_id" );
272              
273 34 50       122 if (defined $cached_row_res) {
274 0         0 push @res, @$cached_row_res;
275 0         0 next;
276             }
277              
278 34         133 my $data = _report_as_data($row);
279 34         248 my @row_res = $dpath->match( $data );
280              
281 34         91164 cache_single_dpath($path, "r$report_id", \@row_res);
282              
283 34         1593 push @res, @row_res;
284             }
285              
286 16         2942 cache_whole_dpath($query_path, $rs_count, \@res);
287              
288 16         63 return @res;
289             }
290              
291             sub testrun_dpath_search($) { ## no critic (ProhibitSubroutinePrototypes)
292 3     3 1 11 my ($query_path, $nohost) = @_;
293              
294             #my ($condition, $path) = _extract_condition_and_path($query_path);
295 3         11 my ($condition, $attrs, $path) = _extract_condition_attrs_and_path($query_path);
296 3         32 my $dpath = Data::DPath::Path->new( path => $path );
297 3 50       554 $condition = _fix_condition_testrundata($condition) unless $puresqlabstract;
298 3 50       10 my %condition = $condition ? %{ eval $condition } : (); ## no critic (ProhibitStringyEval)
  3         172  
299 3 50       11 my %attrs = $attrs ? %{ eval $attrs } : (); ## no critic (ProhibitStringyEval)
  3         132  
300              
301 3 100       18 my $joins = [ ($nohost ? () : ('host')), 'requested_hosts', 'requested_features', 'queue', 'testrun' ];
302 3 100       18 my $selects = [ ($nohost ? () : ('host.name', 'host.free', 'host.active')), 'queue.name', 'testrun.shortname', 'testrun.notes', 'testrun.starttime_testrun', 'testrun.starttime_test_program', 'testrun.endtime_test_program', 'testrun.owner_id', 'testrun.testplan_id', 'testrun.wait_after_tests', 'testrun.rerun_on_error', 'testrun.created_at', 'testrun.updated_at', 'testrun.topic_name', ];
303 3 100       18 my $as = [ ($nohost ? () : ('host_name', 'host_free', 'host_active')), 'queue_name', 'testrun_shortname', 'testrun_notes', 'testrun_starttime_testrun', 'testrun_starttime_test_program', 'testrun_endtime_test_program', 'testrun_owner_id', 'testrun_testplan_id', 'testrun_wait_after_tests', 'testrun_rerun_on_error', 'testrun_created_at', 'testrun_updated_at', 'testrun_topic_name', ];
304              
305 3 100       43 my %merged_attrs = (
306             order_by => 'testrun_id asc',
307             columns => [ qw(
308             testrun_id
309             queue_id
310             prioqueue_seq
311             status
312             auto_rerun
313             created_at
314             updated_at
315             ),
316             ($nohost ? () : ('host_id')),
317             ],
318             join => $joins,
319             '+select' => $selects,
320             '+as' => $as,
321             limit => 10,
322             %attrs,
323             );
324              
325 3         135 print STDERR "query: $query_path\n";
326             # print STDERR "testrun_dpath_search: ".Dumper(
327             # {
328             # condition => \%condition,
329             # attrs => \%attrs,
330             # merged_attrs => \%merged_attrs,
331             # });
332              
333 3         24 my $rs = model('TestrunDB')->resultset('TestrunScheduling')->search
334             (
335             { %condition },
336             { %merged_attrs },
337             );
338              
339             #print STDERR Dumper($rs);
340              
341 3         17866 my $rs_count = $rs->count();
342 3         56485 my @res = ();
343              
344             # layer 2 cache
345 3         17 my $cached_res = cached_whole_dpath( $query_path, $rs_count );
346 3 50       13 return @$cached_res if defined $cached_res;
347              
348 3         63 while (my $row = $rs->next)
349             {
350 7         46147 my $testrun_id = $row->testrun_id;
351              
352             # layer 1 cache
353 7         164 my $cached_row_res = cached_single_dpath( $path, "tr$testrun_id" );
354              
355 7 50       18 if (defined $cached_row_res) {
356 0         0 push @res, @$cached_row_res;
357 0         0 next;
358             }
359              
360 7         20 my $data = _testrun_as_data($row, $nohost);
361 7         37 my @row_res = $dpath->match( $data );
362              
363 7         851 cache_single_dpath($path, "tr$testrun_id", \@row_res);
364              
365 7         50 push @res, @row_res;
366             }
367              
368 3         342 cache_whole_dpath($query_path, $rs_count, \@res);
369              
370 3         11 return @res;
371             }
372              
373             sub testplan_dpath_search($) { ## no critic (ProhibitSubroutinePrototypes)
374 0     0 1 0 my ($query_path) = @_;
375              
376 0         0 my ($condition, $attrs, $path) = _extract_condition_attrs_and_path($query_path);
377 0         0 my $dpath = Data::DPath::Path->new( path => $path );
378 0 0       0 my %condition = $condition ? %{ eval $condition } : (); ## no critic (ProhibitStringyEval)
  0         0  
379 0 0       0 my %attrs = $attrs ? %{ eval $attrs } : (); ## no critic (ProhibitStringyEval)
  0         0  
380              
381 0         0 print STDERR "testplan_dpath_search: ".Dumper(
382             {
383             condition => \%condition,
384             attrs => \%attrs,
385             });
386 0         0 my $rs = model('TestrunDB')->resultset('TestplanInstance')->search
387             (
388             {
389             %condition
390             },
391             {
392             order_by => 'id asc',
393             columns => [ qw(
394             id
395             path
396             name
397             evaluated_testplan
398             created_at
399             updated_at
400             )],
401             limit => 10,
402             %attrs,
403             }
404             );
405              
406             #print STDERR Dumper($rs);
407              
408 0         0 my @res = ();
409              
410 0         0 while (my $row = $rs->next)
411             {
412 0         0 my $testplan_id = $row->id;
413              
414 0         0 my $data = _testplan_as_data($row);
415 0         0 my @row_res = $dpath->match( $data );
416              
417 0         0 push @res, @row_res;
418             }
419              
420 0         0 return @res;
421             }
422              
423             sub _dummy_needed_for_tests {
424             # once there were problems with eval
425 1     1   200 return eval "12345"; ## no critic (ProhibitStringyEval)
426             }
427              
428             sub _groupcontext {
429 36     36   1151832 my ($report) = @_;
430              
431 36         134 my %groupcontext = ();
432 36         1066 my $id = $report->id;
433 36         1153 my $rga = $report->reportgrouparbitrary;
434 36         3206 my $rgt = $report->reportgrouptestrun;
435 36 100       637 my %groupreports = (
    100          
    100          
    100          
436             arbitrary => $rga ? scalar $rga->groupreports : undef,
437             arbitrary_id => $rga ? $rga->arbitrary_id : undef,
438             testrun => $rgt ? scalar $rgt->groupreports : undef,
439             testrun_id => $rgt ? $rgt->testrun_id : undef,
440             );
441              
442             # if ($report->reportgrouptestrun) {
443             # my $rgt_id = $report->reportgrouptestrun->testrun_id;
444             # my $rgt_reports = model('TestrunDB')->resultset('ReportgroupTestrun')->search({ testrun_id => $rgt_id});
445             # # say STDERR "\nrgt $rgt_id count: ", $rgt_reports->count;
446             # }
447              
448 36         167429 foreach my $type (qw(arbitrary testrun))
449             {
450 72 100       4582 next unless $groupreports{$type};
451 45         168 my $group_id = $groupreports{"${type}_id"};
452              
453             # say STDERR "${type}_id: ", $groupreports{"${type}_id"};
454             # say STDERR " groupreports{$type}.count: ", $groupreports{$type}->count;
455             # say STDERR "* $id - groupreports{$type}.count: ", $groupreports{$type}->count;
456 45         157 while (my $groupreport = $groupreports{$type}->next)
457             {
458 120         244057 my $groupreport_id = $groupreport->id;
459             # say STDERR " gr.id: $groupreport_id";
460 120         1779 my @greportsection_meta = ();
461 120         2058 my $grsections = $groupreport->reportsections;
462             # say STDERR "* $groupreport_id GROUPREPORT_SECTIONS count: ", $grsections->count;
463 120         165732 while (my $section = $grsections->next)
464             {
465 180         284949 my %columns = $section->get_columns;
466 180         4634 foreach (keys %columns) {
467 8100 100       14713 delete $columns{$_} unless defined $columns{$_};
468             }
469 180         903 delete $columns{$_} foreach qw(succession name id report_id);
470 180 50       4961 push @greportsection_meta, {
471             $section->name => {
472             %columns
473             }
474             }
475             if keys %columns;
476             }
477 120         407374 my $primary = 0;
478 120 50 66     1075 $primary = 1 if $type eq "arbitrary" && $groupreport->reportgrouparbitrary->primaryreport;
479 120 100 100     108911 $primary = 1 if $type eq "testrun" && $groupreport->reportgrouptestrun->primaryreport;
480              
481 120 100       321502 $groupcontext{$type}{$group_id}{$groupreport_id}{myself} = $groupreport_id == $id ? 1 : 0;
482 120 100       398 $groupcontext{$type}{$group_id}{$groupreport_id}{primary} = $primary ? 1 : 0;
483 120         534 $groupcontext{$type}{$group_id}{$groupreport_id}{meta} = \@greportsection_meta;
484             }
485             }
486              
487             # say STDERR Dumper(\%groupcontext);
488 36         7018 return \%groupcontext;
489             }
490              
491             sub _reportgroupstats {
492 36     36   89 my ($report) = @_;
493              
494 36         850 my $rgt = $report->reportgrouptestrun;
495 36         11551 my $reportgroupstats = {};
496              
497             # create report group stats
498 36 100 66     594 if ($report->reportgrouptestrun and $report->reportgrouptestrun->testrun_id)
499             {
500 30         2502 my $rgt_stats = model('TestrunDB')->resultset('ReportgroupTestrunStats')->find($rgt->testrun_id);
501 30 100 66     104356 unless ($rgt_stats and $rgt_stats->testrun_id)
502             {
503             # This is just a fail-back mechanism, in case the "fix-missinging-groupstats" script has not yet been run.
504 3         77 $rgt_stats = model('TestrunDB')->resultset('ReportgroupTestrunStats')->new({ testrun_id => $rgt->testrun_id});
505 3         1745 $rgt_stats->update_failed_passed;
506 3         49762 $rgt_stats->insert;
507             }
508 30         46082 my @stat_fields = (qw/failed passed total parse_errors skipped todo todo_passed success_ratio/);
509 9     9   91 no strict 'refs'; ## no critic (ProhibitNoStrict)
  9         27  
  9         4993  
510             $reportgroupstats = {
511 30         95 map { ($_ => $rgt_stats->$_ ) } @stat_fields
  240         7131  
512             };
513             }
514 36         1281 return $reportgroupstats;
515             }
516              
517             sub _report_as_data
518             {
519 36     36   1513161 my ($report) = @_;
520              
521 36         70 my $hwdb;
522 36 50       177 if (my $host = model('TestrunDB')->resultset("Host")->search({name => $report->machine_name}, {rows => 1})->first) {
523 0         0 $hwdb = get_hardware_overview($host->id);
524             }
525 36 50 33     139396 my %hardwaredb_overview = (defined($hwdb) and %$hwdb) ? (hardwaredb => $hwdb) : ();
526              
527 36         4112 my $reportgroupstats = _reportgroupstats($report);
528              
529 36 50 50     276 my $simple_hash = {
    100          
    100          
530             report => {
531             $report->get_columns,
532             suite_name => $report->suite ? $report->suite->name : 'unknown',
533             reportgroup_testrun_id => $report->reportgrouptestrun ? $report->reportgrouptestrun->testrun_id : undef,
534             reportgroup_arbitrary_id => $report->reportgrouparbitrary ? $report->reportgrouparbitrary->arbitrary_id : undef,
535             machine_name => $report->machine_name || 'unknown',
536             created_at_ymd_hms => $report->created_at->ymd('-')." ".$report->created_at->hms(':'),
537             created_at_ymd => $report->created_at->ymd('-'),
538             %hardwaredb_overview,
539             groupstats => {
540             DEPRECATED => 'BETTER_USE_groupstats_FROM_ONE_LEVEL_ABOVE',
541             %$reportgroupstats,
542             },
543             },
544             results => $report->get_cached_tapdom,
545             groupcontext => _groupcontext($report),
546             groupstats => $reportgroupstats,
547             };
548 36         4402 return $simple_hash;
549             }
550              
551             sub _testrun_as_data
552             {
553 7     7   15 my ($testrun, $nohost) = @_;
554              
555 7 100       32 my $simple_hash = {
556             testrun => {
557             $testrun->get_columns,
558             },
559             ($nohost ? () : ( host => { $testrun->host->get_columns } ) ),
560             queue => {
561             $testrun->queue->get_columns,
562             },
563             };
564 7         36193 return $simple_hash;
565             }
566              
567             sub _testplan_as_data
568             {
569 0     0     my ($testplan) = @_;
570              
571 0           my @testruns = $testplan->testruns->all;
572              
573             my $simple_hash = {
574             testplan => {
575             id => $testplan->id,
576             name => $testplan->name,
577             created_at_ymd_hms => $testplan->created_at->ymd('-')." ".$testplan->created_at->hms(':'),
578             created_at_ymd => $testplan->created_at->ymd('-'),
579             #testplan_evaluated_testplan => $testplan->evaluated_testplan,
580             },
581             testruns => [
582             map {
583 0           my $ts = $_->testrun_scheduling;
  0            
584 0           my $rgts = $_->reportgrouptestrunstats;
585 0           my $host = $ts->host;
586 0 0         my $host_name = $host ? $host->name : 'undefined_host';
587             {
588 0 0         id => $_->id,
589             topic_name => $_->topic_name,
590             host_name => $host_name,
591             status => $ts->status->value,
592             stats => {
593             $rgts ? $rgts->get_columns : (),
594             },
595             }
596             } @testruns
597             ],
598             };
599 0           return $simple_hash;
600             }
601              
602             1;
603              
604             __END__
605              
606             =pod
607              
608             =encoding UTF-8
609              
610             =head1 NAME
611              
612             Tapper::Reports::DPath - Tapper - Extended DPath functionality for Tapper reports
613              
614             =head1 SYNOPSIS
615              
616             use Tapper::Reports::DPath 'reports_dpath_search';
617             # the first bogomips entry of math sections:
618             @resultlist = reportdata (
619             '{ suite_name => "TestSuite-LmBench" } :: /tap/section/math/*/bogomips[0]'
620             );
621             # all report IDs of suite_id 17 that FAILed:
622             @resultlist = reportdata (
623             '{ suite_name => "TestSuite-LmBench" } :: /suite_id[value == 17]/../successgrade[value eq 'FAIL']/../id'
624             );
625              
626             #
627             # '{ "reportgrouptestrun.testrun_id" => 4711 } :: /suite_id[value == 17]/../successgrade[value eq 'FAIL']/../id
628             #
629             # '{ "reportgrouparbitrary.arbitrary_id" => "fc123a2" } :: /suite_id[value == 17]/../successgrade[value eq 'FAIL']/../id
630              
631             This searches all reports of the test suite "TestSuite-LmBench" and
632             furthermore in them for a TAP section "math" with the particular
633             subtest "bogomips" and takes the first array entry of them.
634              
635             The part before the '::' selects reports to search in a DBIx::Class
636             search query, the second part is a normal L<Data::DPath|Data::DPath>
637             expression that matches against the datastructure that is build from
638             the DB.
639              
640             =head1 API FUNCTIONS
641              
642             =head2 reportdata
643              
644             The actually exported API function which is the frontend to
645             reports_dpath_search.
646              
647             =head2 testrundata
648              
649             The actually exported API function which is the frontend to
650             testrun_dpath_search.
651              
652             =head2 testrundata_nohost
653              
654             Similar to I<testrundata> but without host data, so it also
655             returns testruns that are not yet started (state C<prepare>
656             or C<schedule>).
657              
658             =head2 testplandata
659              
660             The actually exported API function which is the frontend to
661             testplan_dpath_search.
662              
663             =head1 UTILITY FUNCTIONS
664              
665             =head2 reports_dpath_search
666              
667             This is the backend behind the API function reportdata.
668              
669             It takes an extended DPath expression, applies it to Tapper Reports
670             with TAP::DOM structure and returns the matching results in an array.
671              
672             =head2 testrun_dpath_search($DPATH, $NOHOST)
673              
674             This is the backend behind the API function testrundata.
675              
676             It takes an extended DPath expression, applies it to Tapper Testrun
677             with the resultset as data structure and returns the matching results
678             in an array.
679              
680             Optionally you can pass a flag B<NOHOST> which does not JOIN the host
681             table behind the scenes and therefore also returns testruns that are
682             not yet started (and therefore do not have that host set yet),
683             usually in state C<prepare> or C<schedule>.
684              
685             =head2 testplan_dpath_search
686              
687             This is the backend behind the API function testplandata.
688              
689             It takes an extended DPath expression, applies it to Tapper Testplan
690             with the resultset as data structure and returns the matching results
691             in an array.
692              
693             =head2 cache_single_dpath
694              
695             Cache a result for a raw dpath on a cache key.
696              
697             =head2 cached_single_dpath
698              
699             Return cached result for a raw dpath on a cache key.
700              
701             =head2 cache_whole_dpath
702              
703             Cache a result for a complete tapper::dpath on all reports.
704              
705             =head2 cached_whole_dpath
706              
707             Return cached result for a complete tapper::dpath on all reports.
708              
709             =head1 AUTHORS
710              
711             =over 4
712              
713             =item *
714              
715             AMD OSRC Tapper Team <tapper@amd64.org>
716              
717             =item *
718              
719             Tapper Team <tapper-ops@amazon.com>
720              
721             =back
722              
723             =head1 COPYRIGHT AND LICENSE
724              
725             This software is Copyright (c) 2020 by Advanced Micro Devices, Inc..
726              
727             This is free software, licensed under:
728              
729             The (two-clause) FreeBSD License
730              
731             =cut