File Coverage

blib/lib/CPAN/Testers/Schema.pm
Criterion Covered Total %
statement 87 101 86.1
branch 21 26 80.7
condition 2 5 40.0
subroutine 8 10 80.0
pod 3 3 100.0
total 121 145 83.4


line stmt bran cond sub pod time code
1             package CPAN::Testers::Schema;
2             our $VERSION = '0.024';
3             # ABSTRACT: Schema for CPANTesters database processed from test reports
4              
5             #pod =head1 SYNOPSIS
6             #pod
7             #pod my $schema = CPAN::Testers::Schema->connect( $dsn, $user, $pass );
8             #pod my $rs = $schema->resultset( 'Stats' )->search( { dist => 'Test-Simple' } );
9             #pod for my $row ( $rs->all ) {
10             #pod if ( $row->state eq 'fail' ) {
11             #pod say sprintf "Fail report from %s: http://cpantesters.org/cpan/report/%s",
12             #pod $row->tester, $row->guid;
13             #pod }
14             #pod }
15             #pod
16             #pod =head1 DESCRIPTION
17             #pod
18             #pod This is a L<DBIx::Class> Schema for the CPANTesters statistics database.
19             #pod This database is generated by processing the incoming data from L<the
20             #pod CPANTesters Metabase|http://metabase.cpantesters.org>, and extracting
21             #pod the useful fields like distribution, version, platform, and others (see
22             #pod L<CPAN::Testers::Schema::Result::Stats> for a full list).
23             #pod
24             #pod This is its own distribution so that it can be shared by the backend
25             #pod processing, data APIs, and the frontend web application.
26             #pod
27             #pod =head1 SEE ALSO
28             #pod
29             #pod L<CPAN::Testers::Schema::Result::Stats>, L<DBIx::Class>
30             #pod
31             #pod =cut
32              
33 13     13   2083031 use CPAN::Testers::Schema::Base;
  13         35  
  13         82  
34 13     13   5620 use File::Share qw( dist_dir );
  13         310883  
  13         811  
35 13     13   10217 use Path::Tiny qw( path );
  13         140665  
  13         825  
36 13     13   108 use List::Util qw( uniq );
  13         27  
  13         860  
37 13     13   91 use base 'DBIx::Class::Schema';
  13         26  
  13         8287  
38 13     13   749616 use Mojo::UserAgent;
  13         5124758  
  13         152  
39 13     13   8122 use DateTime::Format::ISO8601;
  13         6631158  
  13         18419  
40              
41             __PACKAGE__->load_namespaces;
42             __PACKAGE__->load_components(qw/Schema::Versioned/);
43             __PACKAGE__->upgrade_directory( dist_dir( 'CPAN-Testers-Schema' ) );
44              
45             #pod =method connect_from_config
46             #pod
47             #pod my $schema = CPAN::Testers::Schema->connect_from_config( %extra_conf );
48             #pod
49             #pod Connect to the MySQL database using a local MySQL configuration file
50             #pod in C<$HOME/.cpanstats.cnf>. This configuration file should look like:
51             #pod
52             #pod [client]
53             #pod host = ""
54             #pod database = cpanstats
55             #pod user = my_usr
56             #pod password = my_pwd
57             #pod
58             #pod See L<DBD::mysql/mysql_read_default_file>.
59             #pod
60             #pod C<%extra_conf> will be added to the L<DBIx::Class::Schema/connect>
61             #pod method in the C<%dbi_attributes> hashref (see
62             #pod L<DBIx::Class::Storage::DBI/connect_info>).
63             #pod
64             #pod =cut
65              
66             # Convenience connect method
67 0     0 1 0 sub connect_from_config ( $class, %config ) {
  0         0  
  0         0  
  0         0  
68 0         0 my $schema = $class->connect(
69             "DBI:mysql:mysql_read_default_file=$ENV{HOME}/.cpanstats.cnf;".
70             "mysql_read_default_group=application;mysql_enable_utf8=1",
71             undef, # user
72             undef, # pass
73             {
74             AutoCommit => 1,
75             RaiseError => 1,
76             mysql_enable_utf8 => 1,
77             quote_char => '`',
78             name_sep => '.',
79             %config,
80             },
81             );
82 0         0 return $schema;
83             }
84              
85             #pod =method ordered_schema_versions
86             #pod
87             #pod Get the available schema versions by reading the files in the share
88             #pod directory. These versions can then be upgraded to using the
89             #pod L<cpantesters-schema> script.
90             #pod
91             #pod =cut
92              
93 0     0 1 0 sub ordered_schema_versions( $self ) {
  0         0  
  0         0  
94             my @versions =
95             uniq sort
96 0         0 map { /[\d.]+-([\d.]+)/ }
97 0         0 grep { /CPAN-Testers-Schema-[\d.]+-[\d.]+-MySQL[.]sql/ }
  0         0  
98             path( dist_dir( 'CPAN-Testers-Schema' ) )->children;
99 0         0 return '0.000', @versions;
100             }
101              
102             #pod =method populate_from_api
103             #pod
104             #pod $schema->populate_from_api( \%search, @tables );
105             #pod
106             #pod Populate the given tables from the CPAN Testers API (L<http://api.cpantesters.org>).
107             #pod C<%search> has the following keys:
108             #pod
109             #pod =over
110             #pod
111             #pod =item dist
112             #pod
113             #pod A distribution to populate
114             #pod
115             #pod =item version
116             #pod
117             #pod A distribution version to populate
118             #pod
119             #pod =item author
120             #pod
121             #pod Populate an author's data
122             #pod
123             #pod =back
124             #pod
125             #pod The available C<@tables> are:
126             #pod
127             #pod =over
128             #pod
129             #pod =item * upload
130             #pod
131             #pod =item * release
132             #pod
133             #pod =item * summary
134             #pod
135             #pod =item * report
136             #pod
137             #pod =back
138             #pod
139             #pod =cut
140              
141 6     6 1 2658211 sub populate_from_api( $self, $search, @tables ) {
  6         20  
  6         13  
  6         16  
  6         15  
142 6   33     37 my $ua = $self->{_ua} ||= Mojo::UserAgent->new;
143 6   50     30 my $base_url = $self->{_url} ||= 'http://api.cpantesters.org/v3';
144 6         75 my $dtf = DateTime::Format::ISO8601->new();
145              
146             # Establish dependencies
147 6         522 my %tables = map {; $_ => 1 } @tables;
  6         37  
148 6         30 my @order = qw( upload summary release report );
149             # release depends on data in uploads and summary
150 6 100       28 if ( $tables{ release } ) {
151 2         14 @tables{qw( upload summary )} = ( 1, 1 );
152             }
153             # summary depends on data in uploads
154 6 100       28 if ( $tables{ summary } ) {
155 4         15 @tables{qw( upload )} = ( 1 );
156             }
157              
158             # ; use Data::Dumper;
159             # ; say "Fetching tables: " . Dumper \%tables;
160              
161 6         28 for my $table ( @order ) {
162 24 100       8663 next unless $tables{ $table };
163 12         37 my $url = $base_url;
164 12 100       50 if ( $table eq 'upload' ) {
165 6         18 $url .= '/upload';
166 6 100       31 if ( $search->{dist} ) {
    50          
167 5         24 $url .= '/dist/' . $search->{dist};
168             }
169             elsif ( $search->{author} ) {
170 1         5 $url .= '/author/' . $search->{author};
171             }
172 6         44 my $tx = $ua->get( $url );
173             my @rows = map {
174 6         67079 $_->{released} = $dtf->parse_datetime( $_->{released} )->epoch;
  6         2219  
175 6         4783 $_->{type} = 'cpan';
176 6         31 $_;
177             } $tx->res->json->@*;
178 6         47 $self->resultset( 'Upload' )->populate( \@rows );
179             }
180              
181 12 100       20380 if ( $table eq 'summary' ) {
182 4         16 $url .= '/summary';
183 4 50       17 if ( $search->{dist} ) {
184 4         19 $url .= '/' . $search->{dist};
185 4 100       15 if ( $search->{version} ) {
186 2         10 $url .= '/' . $search->{version};
187             }
188             }
189 4         18 my $tx = $ua->get( $url );
190             my @rows = map {
191 4         33180 my $dt = $dtf->parse_datetime( delete $_->{date} );
  4         2096  
192 4         2255 $_->{postdate} = $dt->strftime( '%Y%m' );
193 4         275 $_->{fulldate} = $dt->strftime( '%Y%m%d%H%M' );
194 4         355 $_->{state} = delete $_->{grade};
195 4         16 $_->{type} = 2;
196 4         16 $_->{tester} = delete $_->{reporter};
197             $_->{uploadid} = $self->resultset( 'Upload' )
198 4         24 ->search({ $_->%{qw( dist version )} })
199             ->first->id;
200 4         15814 $_;
201             } $tx->res->json->@*;
202             # ; use Data::Dumper;
203             # ; say "Populate summary: " . Dumper \@rows;
204 4         770 $self->resultset( 'Stats' )->populate( \@rows );
205             }
206              
207 12 100       15899 if ( $table eq 'release' ) {
208 2         10 $url .= '/release';
209 2 50       12 if ( $search->{dist} ) {
    0          
210 2         9 $url .= '/dist/' . $search->{dist};
211 2 100       12 if ( $search->{version} ) {
212 1         4 $url .= '/' . $search->{version};
213             }
214             }
215             elsif ( $search->{author} ) {
216 0         0 $url .= '/author/' . $search->{author};
217             }
218 2         12 my $tx = $ua->get( $url );
219             my @rows = map {
220 2         18507 delete $_->{author}; # Author is from Upload
  2         745  
221             my $stats_rs = $self->resultset( 'Stats' )
222 2         12 ->search({ $_->%{qw( dist version )} });
223 2         1217 $_->{id} = $stats_rs->get_column( 'id' )->max;
224 2         9584 $_->{guid} = $stats_rs->find( $_->{id} )->guid;
225             my $upload = $self->resultset( 'Upload' )
226 2         9891 ->search({ $_->%{qw( dist version )} })
227             ->first;
228 2         6133 $_->{oncpan} = $upload->type eq 'cpan';
229 2         397 $_->{uploadid} = $upload->id;
230             # XXX These are just wrong
231 2         121 $_->{distmat} = 1;
232 2         7 $_->{perlmat} = 1;
233 2         6 $_->{patched} = 1;
234 2         8 $_;
235             } $tx->res->json->@*;
236             # ; use Data::Dumper;
237             # ; say "Populate release: " . Dumper \@rows;
238 2         86 $self->resultset( 'Release' )->populate( \@rows );
239             }
240             }
241             }
242              
243             1;
244              
245             __END__
246              
247             =pod
248              
249             =head1 NAME
250              
251             CPAN::Testers::Schema - Schema for CPANTesters database processed from test reports
252              
253             =head1 VERSION
254              
255             version 0.024
256              
257             =head1 SYNOPSIS
258              
259             my $schema = CPAN::Testers::Schema->connect( $dsn, $user, $pass );
260             my $rs = $schema->resultset( 'Stats' )->search( { dist => 'Test-Simple' } );
261             for my $row ( $rs->all ) {
262             if ( $row->state eq 'fail' ) {
263             say sprintf "Fail report from %s: http://cpantesters.org/cpan/report/%s",
264             $row->tester, $row->guid;
265             }
266             }
267              
268             =head1 DESCRIPTION
269              
270             This is a L<DBIx::Class> Schema for the CPANTesters statistics database.
271             This database is generated by processing the incoming data from L<the
272             CPANTesters Metabase|http://metabase.cpantesters.org>, and extracting
273             the useful fields like distribution, version, platform, and others (see
274             L<CPAN::Testers::Schema::Result::Stats> for a full list).
275              
276             This is its own distribution so that it can be shared by the backend
277             processing, data APIs, and the frontend web application.
278              
279             =head1 METHODS
280              
281             =head2 connect_from_config
282              
283             my $schema = CPAN::Testers::Schema->connect_from_config( %extra_conf );
284              
285             Connect to the MySQL database using a local MySQL configuration file
286             in C<$HOME/.cpanstats.cnf>. This configuration file should look like:
287              
288             [client]
289             host = ""
290             database = cpanstats
291             user = my_usr
292             password = my_pwd
293              
294             See L<DBD::mysql/mysql_read_default_file>.
295              
296             C<%extra_conf> will be added to the L<DBIx::Class::Schema/connect>
297             method in the C<%dbi_attributes> hashref (see
298             L<DBIx::Class::Storage::DBI/connect_info>).
299              
300             =head2 ordered_schema_versions
301              
302             Get the available schema versions by reading the files in the share
303             directory. These versions can then be upgraded to using the
304             L<cpantesters-schema> script.
305              
306             =head2 populate_from_api
307              
308             $schema->populate_from_api( \%search, @tables );
309              
310             Populate the given tables from the CPAN Testers API (L<http://api.cpantesters.org>).
311             C<%search> has the following keys:
312              
313             =over
314              
315             =item dist
316              
317             A distribution to populate
318              
319             =item version
320              
321             A distribution version to populate
322              
323             =item author
324              
325             Populate an author's data
326              
327             =back
328              
329             The available C<@tables> are:
330              
331             =over
332              
333             =item * upload
334              
335             =item * release
336              
337             =item * summary
338              
339             =item * report
340              
341             =back
342              
343             =head1 SEE ALSO
344              
345             L<CPAN::Testers::Schema::Result::Stats>, L<DBIx::Class>
346              
347             =head1 AUTHORS
348              
349             =over 4
350              
351             =item *
352              
353             Oriol Soriano <oriolsoriano@gmail.com>
354              
355             =item *
356              
357             Doug Bell <preaction@cpan.org>
358              
359             =back
360              
361             =head1 CONTRIBUTORS
362              
363             =for stopwords Breno G. de Oliveira Joel Berger Mohammad S Anwar Nick Tonkin Paul Cochrane
364              
365             =over 4
366              
367             =item *
368              
369             Breno G. de Oliveira <garu@cpan.org>
370              
371             =item *
372              
373             Joel Berger <joel.a.berger@gmail.com>
374              
375             =item *
376              
377             Mohammad S Anwar <mohammad.anwar@yahoo.com>
378              
379             =item *
380              
381             Nick Tonkin <1nickt@users.noreply.github.com>
382              
383             =item *
384              
385             Paul Cochrane <paul@liekut.de>
386              
387             =back
388              
389             =head1 COPYRIGHT AND LICENSE
390              
391             This software is copyright (c) 2018 by Oriol Soriano, Doug Bell.
392              
393             This is free software; you can redistribute it and/or modify it under
394             the same terms as the Perl 5 programming language system itself.
395              
396             =cut