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