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