1bc3d5614562dbc649793483290fabab9e86de95
[migration-tools.git] / mig-bin / mig-reporter
1 #!/usr/bin/perl
2 # -*- coding: iso-8859-15 -*-
3 ###############################################################################
4 =pod
5
6 =item B<reporter> --analyst "Analyst Name" --report_title "Report Title"
7
8 Generates an asciidoc file in the git working directory that can be converted to 
9 any appropriate format.  The analyst and report parameters are required.
10
11 Optional parameters are : 
12
13 --added_page_title and --added_page_file 
14
15 If one is used both must be.  The added page file can be plain text or asciidoc.  This
16 adds an extra arbitrary page of notes to the report.  Mig assumes the page file is in the mig git directory.
17
18 --tags
19
20 This will define a set of tags to use, if not set it will default to Circs, 
21 Holds, Actors, Bibs, Assets & Money. 
22
23 --debug on
24
25 Gives more information about what is happening. Defaults to off.
26
27 --reports_xml 
28
29 Allows you to override the default evergreen_staged_report.xml in the mig-xml folder.
30
31 --captions on OR --captions off 
32
33 Adds the captions tag to asciidoc header to turn off captions in generated output.
34 Defaults to off.
35
36 =back
37
38 =cut
39
40 ###############################################################################
41
42 use strict;
43 use warnings;
44
45 use DBI;
46 use Data::Dumper;
47 use XML::LibXML;
48 use Env qw(
49     HOME PGHOST PGPORT PGUSER PGDATABASE MIGSCHEMA
50     MIGBASEWORKDIR MIGBASEGITDIR MIGGITDIR MIGWORKDIR
51 );
52 use Pod::Usage;
53 use Switch;
54 use Getopt::Long; 
55 use Cwd 'abs_path';
56 use Cwd qw(getcwd);
57 use FindBin;
58 my $mig_bin = "$FindBin::Bin/";
59 use lib "$FindBin::Bin/";
60 use Mig;
61 use open ':encoding(utf8)';
62
63 pod2usage(-verbose => 2) if defined $ARGV[0] && $ARGV[0] eq '--help';
64 pod2usage(-verbose => 1) if ! $ARGV[1];
65
66 my $analyst = 'Equinox Open Library Initiative';;
67 my $report_title;
68 my $reports_xml = 'evergreen_staged_report.xml';
69 my $tags;
70 my $added_page_title;
71 my $added_page_file;
72 my $captions = 'off';  
73 my $i = 0;
74 my $parser = XML::LibXML->new();
75 my $lines_per_page = 42;
76 my $debug = 'off';
77 my $workbook;
78 my $fh;
79
80 my $ret = GetOptions(
81     'analyst:s'           => \$analyst,
82     'report_title:s'      => \$report_title,
83     'title:s'             => \$report_title,
84     'reports_xml:s'       => \$reports_xml,
85     'tags:s'              => \$tags,
86     'added_page_title:s'  => \$added_page_title,
87     'added_page_file:s'   => \$added_page_file,
88     'captions:s'          => \$captions,
89         'debug:s'             => \$debug
90 );
91
92 if (!defined $tags) {$tags = 'circs.holds.actors.bibs.assets.money.notices'};
93 if (!defined $report_title) { abort('--report_title or --title must be supplied'); }
94 if (!defined $analyst) { abort('--analyst must be supplied'); }
95
96 my $mig_path = abs_path($0);
97 $mig_path =~ s|[^/]+$||;
98 $reports_xml = find_xml($reports_xml,$mig_path);
99 if (!defined $reports_xml) { abort("Can not find xml reports file."); }
100 my $dom = $parser->parse_file($reports_xml);
101
102 if (defined $added_page_file or defined $added_page_title) {
103     abort('must specify --added_page_file and --added_page_title') unless defined $added_page_file and defined $added_page_title;
104     }
105 if (defined $added_page_file) { $added_page_file = $MIGGITDIR . $added_page_file; }
106
107 my $dbh = Mig::db_connect();
108 my $report_file = create_report_name($report_title);
109 $report_file = $MIGGITDIR . $report_file;
110
111 open($fh, '>', $report_file) or abort("Could not open output file $report_file!");
112 write_title_page($report_title,$fh,$analyst,$captions);
113 load_javascript($fh);
114
115 if (defined $added_page_file and defined $added_page_title) { 
116     print $fh "<<<\n";
117     print $fh "== $added_page_title\n";
118     print "$added_page_file\t$added_page_title\n";
119     open(my $an,'<:encoding(UTF-8)', $added_page_file) or abort("Could not open $added_page_file!");
120     while ( my $line = <$an> ) {
121         print $fh $line;
122     }
123     print $fh "\n";
124     close $an;
125 }
126
127 foreach my $func ($dom->findnodes('//function')) {
128     my $fdrop = $func->findvalue('./drop');
129     my $fcreate = $func->findvalue('./create');    
130     my $fname = $func->findvalue('./name');
131     my $sdrop = $dbh->prepare($fdrop);
132     my $screate = $dbh->prepare($fcreate);
133     print "dropping function $fname ... ";
134     $sdrop->execute();
135     print "creating function $fname\n\n";
136     $screate->execute();
137 }
138
139 foreach my $table ($dom->findnodes('//table')) {
140     my $tdrop = $table->findvalue('./drop');
141     my $tcreate = $table->findvalue('./create');
142     my $tname = $table->findvalue('./name');
143     my $sdrop = $dbh->prepare($tdrop);
144     my $screate = $dbh->prepare($tcreate);
145     print "dropping table $tname ... ";
146     $sdrop->execute();
147     print "creating table $tname\n\n";
148     $screate->execute();
149 }
150
151 $tags = lc($tags);
152 my @report_tags = split(/\./,$tags);
153 foreach my $t (@report_tags) {
154     print "\n\n=========== Starting to process tag $t\n";
155     print   "==========================================\n\n";
156
157     my @asset_files;
158     foreach my $asset ($dom->findnodes('//asset')) {
159         if (index($asset->findvalue('./tag'),$t) != -1) {
160             push @asset_files, $asset->findvalue('./file');
161         }
162     }
163
164     foreach my $fname (@asset_files) {
165         my $asset_path = $mig_path . '../mig-asc/' . $fname;
166         open my $a, $asset_path or abort("Could not open $fname.");
167         while ( my $l = <$a> ) {
168             print $fh $l;
169         }
170     print $fh "<<<\n";
171     }
172
173     print_section_header(ucfirst($t),$fh); 
174     my $linecount = $lines_per_page;
175     my $r;
176
177     undef @asset_files;
178     foreach my $asset ($dom->findnodes('//asset')) {
179         if (index($asset->findvalue('./tag'),$t) != -1) {
180             push @asset_files, $asset->findvalue('./file');
181         }
182     }
183
184     my @report_names;
185     foreach my $report ($dom->findnodes('//report')) {
186         if (index($report->findvalue('./tag'),$t) != -1 and $report->findvalue('./iteration') eq '0') {
187             push @report_names, $report->findvalue('./name');
188         }
189     }
190
191     #only has one level of failover now but could change to array of hashes and loops
192     #but this keeps it simple and in practice I haven't needed more than two
193     
194
195     foreach my $rname (@report_names) {
196         my %report0;
197         my %report1;
198         my $check_tables0;
199         my $check_tables1;
200
201         if ($debug eq 'on') {print "\nchecking for $rname ... ";}
202         %report0 = find_report($dom,$t,$rname,'0',$debug);
203         $check_tables0 = check_table($report0{query},$MIGSCHEMA,$debug,$rname);
204         if ($check_tables0 == 1) { $r = print_query($fh,%report0); } else {
205                 %report1 = find_report($dom,$t,$rname,'1',$debug);
206             if (defined $report1{query}) {
207                 $check_tables1 = check_table($report1{query},$MIGSCHEMA,$debug,$rname);
208                 if ($check_tables1 == 1) { $r = print_query($fh,%report1); }
209             }
210         }
211     }
212     
213 }
214
215 print "\n";
216
217 foreach my $table ($dom->findnodes('//table')) {
218     my $tdrop = $table->findvalue('./drop');
219     my $tname = $table->findvalue('./name');
220     my $sdrop = $dbh->prepare($tdrop);
221     print "cleaning up table $tname ... \n";
222     $sdrop->execute();
223 }
224
225 close $fh;
226
227 ############ end of main logic
228
229 sub find_xml {
230     my $reports_xml = shift;
231     my $mig_path = shift;
232
233     if ($reports_xml =~ m/\//) { return $reports_xml; }
234
235     my $mig_test_file =  $mig_path . '/../mig-xml/' . $reports_xml;
236     my $working_test_dir = getcwd();
237     my $working_test_file = $working_test_dir . '/' . $reports_xml;
238
239     if (-e $mig_test_file) { return $mig_test_file; }
240     if (-e $working_test_file) { return $working_test_file; }
241
242     return undef;
243 }
244
245 sub find_report {
246     my $dom = shift;
247     my $tag = shift;
248     my $name = shift;
249     my $iteration = shift;
250     my $debug = shift;
251     my %report;
252
253     if ($debug eq 'on') {print "iteration $iteration ";}
254     foreach my $node ($dom->findnodes('//report')) {
255         if ($node->findvalue('./tag') =~ $tag and $node->findvalue('./iteration') eq $iteration and $node->findvalue('./name') eq $name) {
256             if ($debug eq 'on') {print "succeeded ... \n";}
257             %report = (
258                 name => $node->findvalue('./name'),
259                 report_title => $node->findvalue('./report_title'),
260                 query => $node->findvalue('./query'),
261                 heading => $node->findvalue('./heading'),
262                 tag => $node->findvalue('./tag'),
263                 iteration => $node->findvalue('./iteration'),
264                 note => $node->findvalue('./note'),
265                                 display => $node->findvalue('./display'),
266                         chart_labels => $node->findvalue('./chart_labels'),
267                                 divwidth => $node->findvalue('./divwidth'),
268                                 divheight => $node->findvalue('./divheight'),
269                 );
270             return %report;
271         }
272     }
273     if ($debug eq 'on') {print "failed ... \n";}
274     return %report = (
275         name => "eaten by grue"
276     );
277 }
278
279 sub print_section_header {
280     my $t = shift;
281     my $fh = shift;
282
283     $t =~ s/_/ /g;
284     #$t =~ s/(\w+)/\u$1/g;;
285     print $fh "<<<\n";
286     print $fh "== $t Reports\n";
287     return;
288 }
289
290 sub create_report_name {
291     my $rt = shift;
292
293     my @abbr = qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec);
294     my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);
295     $year += 1900;
296     my $date = $year . '_' . $abbr[$mon] . '_' . $mday;
297     my $report_file;
298     $report_file = $rt . ' ' . $date . '.asciidoc';
299     $report_file =~ s/ /_/g;
300     return $report_file;
301 }
302
303 sub write_title_page {
304     my $rt = shift;
305     my $fh = shift;
306     my $a = shift;
307     my $captions = shift;
308
309     my @abbr = qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec);
310     my $l = length($report_title);
311     my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);
312     $year += 1900;
313     print $fh "= $rt\n"; 
314     print $fh "$mday $abbr[$mon] $year\n";
315     print $fh "$a\n";
316     #print $fh ":title-logo-image: image::eolilogosmall.png[pdfwidth=3in]\n";
317     print $fh ":toc:\n";
318     if ($captions eq 'on') { print $fh ":caption:\n"; }
319     print $fh "\n";
320 }
321
322 sub load_javascript {
323         my $fh = shift;
324
325         print $fh "++++\n";
326         print $fh "<script type=\"text/javascript\" src=\"https://www.gstatic.com/charts/loader.js\"></script>\n";
327         print $fh "++++\n";
328 }
329
330 sub check_table {
331     my $query = shift;
332     my $MIGSCHEMA = shift;
333     my $debug = shift;
334     my $report_name = shift;
335
336     if ($debug eq 'on') {print "$query\n";}
337
338     my $i;
339     my $return_flag = 1;   
340     my @qe = split(/ /,$query);
341     $i = @qe;
342     $i--;
343     my @tables;
344     while ($i > -1) {
345         if ($qe[$i] eq 'FROM' or $qe[$i] eq 'JOIN') {
346             my $q = $i + 1;
347             if ($qe[$q] ne '(SELECT') {
348                 push @tables, $qe[$q];            
349             }
350         }
351         $i--;
352     }
353     if ($debug eq 'on') {print "checking tables ... ";}
354
355     $i = 0;
356     foreach my $table (@tables) {
357         my $sql;
358         my $schema;
359         if (index($table,'.') != -1) {
360             $schema = (split /\./,$table)[0];
361             $table = (split /\./,$table)[1];
362         }
363         $table = clean_query_string($table); 
364         if (defined $schema) {
365             $schema = clean_query_string($schema);
366             $sql = 'SELECT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema = \'' . $schema . '\' AND table_name = \'' . $table . '\');';
367         } else {
368             $sql = 'SELECT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema = \'' . $MIGSCHEMA . '\' AND table_name = \'' . $table . '\');';
369         }
370         my $sth = $dbh->prepare($sql);
371         $sth->execute();
372         while (my @row = $sth->fetchrow_array) {
373             if ($row[0] eq '1') {
374                     next;
375                 } else {
376                     $return_flag = 0;
377                     if ($debug eq 'on') {print "detecting $table failed...\n";}
378                 }
379             if ($row[0] eq '0') {$return_flag = 0;}
380         }
381     }
382     if ($return_flag == 1 and $debug eq 'on') {print "succeeded ...\n";}
383     if ($return_flag == 0) {print "! a table failed the find test for report $report_name\n\n";}
384     return $return_flag;
385 }
386
387 sub clean_query_string {
388     my $str = shift;
389     
390     $str =~ s/(?!_)[[:punct:]]//g; #remove punct except underscores
391     $str =~ s/\n//g;
392     $str =~ s/\r//g;
393     return $str;
394 }
395
396 sub print_query {
397     my $fh = shift;
398     my %report = @_;
399
400         my $display = $report{display};
401         my $height = $report{divheight};
402         my $width = $report{divwidth};
403         if (!defined $display) { $display = 'table'; }
404         my $rname = $report{name};
405     my $query = $report{query};
406         my $title = $report{report_title};
407     my $sth = $dbh->prepare($query);
408     $sth->execute();
409
410         if ($height) { $height = $height . 'px'; }
411         if ($width)  { $width = $width . 'px'; }
412     my $header_flag = 0;
413
414         #print asciidoc
415         if ($display eq 'table') {
416         while (my @row = $sth->fetchrow_array) {
417             if ($header_flag == 0) {
418                 print $fh "\n.*$report{report_title}*\n";
419                 print $fh "|===\n";
420                 my @h = split(/\./,$report{heading});
421                 my $h_length = @h;
422                 my $h_count = 1;
423                 while ($h_count <= $h_length) {
424                     print $fh "|*$h[$h_count-1]* ";
425                     $h_count++;
426                 }
427                 print $fh "\n";
428                 $header_flag = 1;
429             }
430             my $row_length = @row;
431             my $r = 1;
432             while ($r <= $row_length) {
433                 if (! defined $row[$r-1] ) {
434                     $row[$r-1] = 'none';
435                 }
436                 print $fh "|$row[$r-1] ";
437                 $r++;
438             }
439             print $fh "\n";
440         }
441         if ($header_flag == 1) { 
442                 print $fh "|===\n\n"; 
443                 print $fh $report{note};
444                 print $fh "\n\n";
445         }
446         }
447
448     #print pie chart 
449         if ($display eq 'pie_chart' or $display eq 'donut_chart') {
450                 my @h = split(/\./,$report{heading});
451             my @l = split(/\./,$report{chart_labels});
452         
453                 print $fh "++++\n";
454                 if (defined $height and defined $width) { print $fh "<div id=\"$rname\" style=\"width: $width; height: $height;\"></div>\n"; }
455                         else { print $fh "<div id=\"$rname\"></div>\n"; }
456                 print $fh "<script type=\"text/javascript\">\n";
457                 print $fh "google.charts.load('current', {'packages':['corechart']});\n";
458                 print $fh "google.charts.setOnLoadCallback(drawChart);\n";
459                 print $fh "function drawChart() {\n";
460                 print $fh "  var data = google.visualization.arrayToDataTable([\n";
461                 #loop through data here 
462                 print $fh "['$l[0]', '$l[1]' ],\n";
463                 while (my @row = $sth->fetchrow_array) {
464                         my $row_length = @row;
465                         my $r = 1;
466                         while ($r < $row_length) {
467                 print $fh "['$h[$r-1]', $row[$r-1] ],\n";
468                 $r++;
469             }
470                         if ($r = $row_length) { print $fh "['$h[$r-1]', $row[$r-1] ]\n"; }      
471                 }
472                 print $fh "]);\n";
473                 if ($display eq 'pie_chart') { print $fh "var options = {'title':'$title'};\n"; }
474         if ($display eq 'donut_chart') { print $fh "var options = {'title':'$title', pieHole: 0.4};\n"; }
475                 print $fh "var chart = new google.visualization.PieChart(document.getElementById('$rname'));\n";
476         print $fh "chart.draw(data, options);\n";
477                 print $fh "}\n";
478                 print $fh "</script>\n";
479                 print $fh "++++\n";
480         }
481
482     print "successfully wrote output for $report{name}.\n\n";
483 }
484
485 sub give_column {
486     my $i = shift;
487     my $col = "";
488
489     do {
490         $col .= chr( ( $i % 26 ) + ord('A') );
491         $i = int( $i / 26 ) - 1;
492     } while ( $i >= 0 );
493
494     return scalar reverse $col;
495 }
496
497 sub abort {
498     my $msg = shift;
499     print STDERR "$0: $msg", "\n";
500     exit 1;
501 }
502
503