File Coverage

blib/lib/Labyrinth/Plugin/CPAN/Report.pm
Criterion Covered Total %
statement 58 60 96.6
branch n/a
condition n/a
subroutine 20 20 100.0
pod n/a
total 78 80 97.5


line stmt bran cond sub pod time code
1             package Labyrinth::Plugin::CPAN::Report;
2              
3 4     4   24335 use strict;
  4         10  
  4         100  
4 4     4   19 use warnings;
  4         7  
  4         106  
5              
6 4     4   22 use vars qw($VERSION);
  4         9  
  4         185  
7             $VERSION = '3.59';
8              
9             =head1 NAME
10              
11             Labyrinth::Plugin::CPAN::Report - Plugin to handle Report pages.
12              
13             =cut
14              
15             #----------------------------------------------------------------------------
16             # Libraries
17              
18 4     4   20 use base qw(Labyrinth::Plugin::Base);
  4         9  
  4         972  
19              
20 4     4   1710910 use Labyrinth::Audit;
  4         10  
  4         450  
21 4     4   25 use Labyrinth::DBUtils;
  4         10  
  4         74  
22 4     4   20 use Labyrinth::DTUtils;
  4         9  
  4         212  
23 4     4   25 use Labyrinth::MLUtils;
  4         21  
  4         352  
24 4     4   23 use Labyrinth::Variables;
  4         8  
  4         447  
25 4     4   24 use Labyrinth::Writer;
  4         16  
  4         240  
26              
27 4     4   864 use Labyrinth::Plugin::CPAN;
  4         22806  
  4         23  
28              
29 4     4   1458 use CPAN::Testers::Common::Article;
  4         23143  
  4         42  
30 4     4   1758 use CPAN::Testers::Common::Utils qw(nntp_to_guid guid_to_nntp);
  4         999  
  4         213  
31 4     4   26607 use CPAN::Testers::Fact::LegacyReport;
  4         63385  
  4         98  
32 4     4   1533 use CPAN::Testers::Fact::TestSummary;
  4         1020  
  4         96  
33 4     4   23 use File::Slurp;
  4         7  
  4         235  
34 4     4   23 use HTML::Entities;
  4         9  
  4         178  
35 4     4   33 use JSON::XS;
  4         11  
  4         130  
36 4     4   20 use Metabase::Resource;
  4         9  
  4         57  
37 4     4   1165 use XML::RSS;
  0            
  0            
38             use YAML::XS;
39              
40             #----------------------------------------------------------------------------
41             # Public Interface Functions
42              
43             =head1 METHODS
44              
45             =head2 Public Interface Methods
46              
47             =over 4
48              
49             =item View
50              
51             View a specific report.
52              
53             =item AuthorRSS
54              
55             Return the RSS feed for a given author.
56              
57             =item DistroRSS
58              
59             Return the RSS feed for a given distribution.
60              
61             =item load_rss
62              
63             Reads the appropriate JSON file and returns an RSS feed.
64              
65             =item make_rss
66              
67             Creates an RSS feed from a given data set.
68              
69             =item AuthorYAML
70              
71             Return the YAML feed for a given author.
72              
73             =item DistroYAML
74              
75             Return the YAML feed for a given distribution.
76              
77             =item load_yaml
78              
79             Reads the appropriate JSON file and returns an YAML feed.
80              
81             =back
82              
83             =cut
84              
85             sub View {
86             if($cgiparams{id} =~ /^\d+$/) {
87             my @rows = $dbi->GetQuery('hash','GetStatReport',$cgiparams{id});
88             if(@rows) {
89             if($rows[0]->{guid} =~ /^[0-9]+\-[-\w]+$/) {
90             my $id = guid_to_nntp($rows[0]->{guid});
91             _parse_nntp_report($id);
92             } else {
93             $cgiparams{id} = $rows[0]->{guid};
94             _parse_guid_report();
95             }
96             } else {
97             #$tvars{errcode} = 'NEXT';
98             #$tvars{command} = 'cpan-distunk';
99             }
100             } else {
101             my $id = guid_to_nntp($cgiparams{id});
102             if($id) {
103             _parse_nntp_report($id);
104             } else {
105             _parse_guid_report();
106             }
107             }
108              
109             unless($tvars{article}{article}) {
110             if($cgiparams{id} =~ /^\d+$/) {
111             $tvars{article}{id} = $cgiparams{id};
112             } else {
113             $tvars{article}{guid} = $cgiparams{id};
114             }
115             }
116              
117             if($cgiparams{raw}) {
118             $tvars{article}{raw} = $cgiparams{raw};
119             $tvars{realm} = 'popup';
120             } else {
121             $tvars{realm} = 'wide';
122             }
123             }
124              
125             sub AuthorRSS { load_rss('author'); }
126             sub DistroRSS { load_rss('distro'); }
127              
128             sub load_rss {
129             my $type = shift;
130             my $nopass = 0;
131              
132             if($cgiparams{name} =~ /(.*)\-nopass/) {
133             $cgiparams{name} = $1;
134             $nopass = 1;
135             }
136              
137             my @dt = localtime(time);
138             my $olddate = sprintf "%04d%02d%02d%02d%02d", $dt[5]+1899, $dt[4], $dt[3], $dt[2], $dt[1];
139              
140             my $cache = sprintf "%s/static/%s/%s/%s", $settings{webdir}, $type, substr($cgiparams{name},0,1), $cgiparams{name};
141             #LogDebug("cache=$cache");
142              
143             # load JSON data if available
144             if(-f "$cache.json") {
145             my $json = read_file("$cache.json");
146             my $data = decode_json($json);
147             my @reports;
148             for my $row (sort {$b->{fulldate} <=> $a->{fulldate}} @$data) {
149             next if($row->{fulldate} lt $olddate); # ignore anything older than a year
150             next if($nopass && $row->{state} =~ /PASS|NA/i);
151             push @reports, $row;
152             }
153              
154             $type = 'nopass' if($nopass);
155             $tvars{body} = make_rss( $type, $cgiparams{name}, \@reports );
156              
157             # fall back to any existing RSS
158             } else {
159             my $file = $nopass ? "$cache-nopass.rss" : "$cache.rss";
160             $tvars{body} = read_file("$cache.rss") if(-f $file);
161             }
162              
163             $tvars{realm} = 'rss';
164             }
165              
166             sub make_rss {
167             my ( $type, $item, $data ) = @_;
168             my ( $title, $link, $desc );
169              
170             if($type eq 'distro') {
171             $title = "$item CPAN Testers Reports";
172             $link = "http://www.cpantesters.org/distro/".substr($item,0,1)."/$item.html";
173             $desc = "Automated test results for the $item distribution";
174             } elsif($type eq 'recent') {
175             $title = "Recent CPAN Testers Reports";
176             $link = "http://www.cpantesters.org/static/recent.html";
177             $desc = "Recent CPAN Testers reports";
178             } elsif($type eq 'author') {
179             $title = "Reports for distributions by $item";
180             $link = "http://www.cpantesters.org/author/".substr($item,0,1)."/$item.html";
181             $desc = "Reports for distributions by $item";
182             } elsif($type eq 'nopass') {
183             $title = "Failing Reports for distributions by $item";
184             $link = "http://www.cpantesters.org/author/".substr($item,0,1)."/$item.html";
185             $desc = "Reports for distributions by $item";
186             }
187              
188             #use Data::Dumper;
189             #LogDebug("first report = ".Dumper($data->[0]));
190              
191             my $rss = XML::RSS->new( version => '2.0' );
192             $rss->channel(
193             title => $settings{rsstitle},
194             link => $settings{rsslink},
195             description => $settings{rssdesc},
196             language => 'en',
197             copyrights => $settings{copyright},
198             pubDate => formatDate(16),
199             managingEditor => $settings{rsseditor},
200             webMaster => $settings{rssmaster},
201             generator => 'Labyrinth v' . $tvars{'labversion'},
202             );
203              
204             for my $test (@$data) {
205             $test->{fulldate} ||= '000000000000';
206             $test->{guid} ||= '';
207             $test->{id} ||= 0;
208              
209             my $title = sprintf "%s %s-%s %s on %s %s (%s)", map {$_||''} @{$test}{ qw( status dist version perl osname osvers platform ) };
210              
211             #LogDebug("ERROR: $test->{fulldate} - $title");
212              
213             my $time = unformatDate(22,$test->{fulldate});
214             my $date = formatDate(16,$time);
215              
216             #LogDebug("title=".$title);
217             #LogDebug("link="."$settings{reportlink2}/" . ($test->{guid} || $test->{id}));
218             #LogDebug("guid="."$settings{reportlink2}/" . ($test->{guid} || $test->{id}));
219             #LogDebug("pubDate=$date");
220              
221             $rss->add_item(
222             title => $title,
223             description => $title,
224             link => "$settings{reportlink2}/" . ($test->{guid} || $test->{id}),
225             guid => "$settings{reportlink2}/" . ($test->{guid} || $test->{id}),
226             pubDate => $date,
227             );
228             }
229              
230             #LogDebug("rss = ".$rss->as_string);
231              
232             # the following hacks are necessary as XML::RSS doesn't fully support RSS v2.0
233             $link =~ s/\.html$/\.rss/;
234             $link =~ s/\.rss/-nopass.rss/ if($type eq 'nopass');
235             my $str = $rss->as_string;
236             $str =~ s!<rss version="2.0"!<rss version="2.0"\nxmlns:atom="http://www.w3.org/2005/Atom"!;
237             $str =~ s!<channel>!<channel>\n<atom:link href="$link" rel="self" type="application/rss+xml" />!;
238              
239             return $str;
240             }
241              
242             sub AuthorYAML { load_yaml('author'); }
243             sub DistroYAML { load_yaml('distro'); }
244              
245             sub load_yaml {
246             my $type = shift;
247             my $cache = sprintf "%s/static/%s/%s/%s", $settings{webdir}, $type, substr($cgiparams{name},0,1), $cgiparams{name};
248              
249             #LogDebug("cache=$cache");
250              
251             # load JSON data if available
252             if(-f "$cache.json") {
253             my $json = read_file("$cache.json");
254             my $data = decode_json($json);
255             my @reports;
256             for my $row (@$data) {
257             push @reports, $row;
258             }
259              
260             $tvars{body} = Dump( \@reports );
261              
262             # fall back to any existing RSS
263             } elsif(-f "$cache.yaml") {
264             $tvars{body} = read_file("$cache.yaml");
265             }
266              
267             $tvars{realm} = 'yaml';
268             }
269              
270             #----------------------------------------------------------------------------
271             # Private Interface Functions
272              
273             sub _parse_nntp_report {
274             my $nntpid = shift;
275             my @rows;
276              
277             unless($nntpid) {
278             @rows = $dbi->GetQuery('hash','GetStatReport',$cgiparams{id});
279             return unless(@rows);
280             $nntpid = guid_to_nntp($rows[0]->{guid});
281             }
282              
283             @rows = $dbi->GetQuery('hash','GetArticle',$nntpid);
284             return unless(@rows);
285              
286             $rows[0]->{article} = SafeHTML($rows[0]->{article});
287             $tvars{article} = $rows[0];
288             ($tvars{article}{head},$tvars{article}{body}) = split(/\n\n/,$rows[0]->{article},2);
289              
290             my $object = CPAN::Testers::Common::Article->new($rows[0]->{article});
291             return unless($object);
292              
293             $tvars{article}{nntp} = 1;
294             $tvars{article}{id} = $cgiparams{id};
295             $tvars{article}{body} = $object->body;
296             $tvars{article}{subject} = $object->subject;
297             $tvars{article}{from} = $object->from;
298             $tvars{article}{from} =~ s/\@.*//;
299             $tvars{article}{post} = $object->postdate;
300              
301             my @date = $object->date =~ /^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})/;
302             $tvars{article}{date} = sprintf "%04d-%02d-%02dT%02d:%02d:00Z", @date;
303              
304             return if($tvars{article}{subject} =~ /Re:/i);
305             return unless($tvars{article}{subject} =~ /(CPAN|FAIL|PASS|NA|UNKNOWN)\s+/i);
306              
307             my $state = lc $1;
308              
309             if($state eq 'cpan') {
310             if($object->parse_upload()) {
311             $tvars{article}{dist} = $object->distribution;
312             $tvars{article}{version} = $object->version;
313             $tvars{article}{author} = $object->author;
314             $tvars{article}{letter} = substr($tvars{article}{dist},0,1);
315             }
316             } else {
317             if($object->parse_report()) {
318             $tvars{article}{dist} = $object->distribution;
319             $tvars{article}{version} = $object->version;
320             $tvars{article}{author} = $object->from;
321             $tvars{article}{letter} = substr($tvars{article}{dist},0,1);
322             }
323             }
324             }
325              
326             sub _parse_guid_report {
327             my $cpan = Labyrinth::Plugin::CPAN->new();
328             $cpan->Configure();
329              
330             my @rows = $dbi->GetQuery('hash','GetMetabaseByGUID',$cgiparams{id});
331             return unless(@rows);
332              
333             my $data = decode_json($rows[0]->{report});
334             my $fact = CPAN::Testers::Fact::LegacyReport->from_struct( $data->{'CPAN::Testers::Fact::LegacyReport'} );
335             $tvars{article}{article} = SafeHTML($fact->{content}{textreport});
336             #$tvars{article}{id} = $rows[0]->{id};
337             $tvars{article}{guid} = $rows[0]->{guid};
338              
339             my $report = CPAN::Testers::Fact::TestSummary->from_struct( $data->{'CPAN::Testers::Fact::TestSummary'} );
340             my ($osname) = $cpan->OSName($report->{content}{osname});
341              
342             $tvars{article}{state} = lc $report->{content}{grade};
343             $tvars{article}{platform} = $report->{content}{archname};
344             $tvars{article}{osname} = $osname;
345             $tvars{article}{osvers} = $report->{content}{osversion};
346             $tvars{article}{perl} = $report->{content}{perl_version};
347             $tvars{article}{created} = $report->{metadata}{core}{creation_time};
348              
349             my $dist = Metabase::Resource->new( $report->{metadata}{core}{resource} );
350             $tvars{article}{dist} = $dist->metadata->{dist_name};
351             $tvars{article}{version} = $dist->metadata->{dist_version};
352              
353             ($tvars{article}{author},$tvars{article}{from}) = _get_tester( $report->creator );
354             $tvars{article}{author} =~ s/\@/ [at] /g;
355             $tvars{article}{from} =~ s/\@/ [at] /g;
356             $tvars{article}{from} =~ s/\./ [dot] /g;
357              
358             if($tvars{article}{created}) {
359             my @created = $tvars{article}{created} =~ /(\d+)-(\d+)-(\d+)T(\d+):(\d+):(\d+)Z/; # 2010-02-23T20:33:52Z
360             $tvars{article}{postdate} = sprintf "%04d%02d", $created[0], $created[1];
361             $tvars{article}{fulldate} = sprintf "%04d%02d%02d%02d%02d", $created[0], $created[1], $created[2], $created[3], $created[4];
362             } else {
363             my @created = localtime(time);
364             $tvars{article}{postdate} = sprintf "%04d%02d", $created[5]+1900, $created[4]+1;
365             $tvars{article}{fulldate} = sprintf "%04d%02d%02d%02d%02d", $created[5]+1900, $created[4]+1, $created[3], $created[2], $created[1];
366             }
367              
368             $tvars{article}{letter} = substr($tvars{article}{dist},0,1);
369              
370             $tvars{article}{subject} = sprintf "%s %s-%s %s %s",
371             uc $tvars{article}{state}, $tvars{article}{dist}, $tvars{article}{version}, $tvars{article}{perl}, $tvars{article}{osname};
372             }
373              
374             sub _get_tester {
375             my $creator = shift;
376              
377             #$dbi->{'mysql_enable_utf8'} = 1;
378             my @rows = $dbi->GetQuery('hash','GetTesterFact',$creator);
379             return ($creator,$creator) unless(@rows);
380              
381             #$rows[0]->{fullname} = encode_entities($rows[0]->{fullname});
382             $rows[0]->{email} ||= $creator;
383             $rows[0]->{email} =~ s/\'/''/g if($rows[0]->{email});
384             return ($rows[0]->{fullname},$rows[0]->{email});
385             }
386              
387             1;
388              
389             __END__
390              
391             =head1 SEE ALSO
392              
393             Labyrinth
394              
395             =head1 AUTHOR
396              
397             Barbie, <barbie@missbarbell.co.uk> for
398             Miss Barbell Productions, L<http://www.missbarbell.co.uk/>
399              
400             =head1 COPYRIGHT & LICENSE
401              
402             Copyright (C) 2008-2017 Barbie for Miss Barbell Productions
403             All Rights Reserved.
404              
405             This module is free software; you can redistribute it and/or
406             modify it under the Artistic License 2.0.
407              
408             =cut