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