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