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