File Coverage

blib/lib/App/bif/log.pm
Criterion Covered Total %
statement 41 184 22.2
branch 3 68 4.4
condition 0 7 0.0
subroutine 13 21 61.9
pod 2 10 20.0
total 59 290 20.3


line stmt bran cond sub pod time code
1             package App::bif::log;
2 7     7   1560 use strict;
  7         10  
  7         225  
3 7     7   32 use warnings;
  7         10  
  7         219  
4 7     7   26 use feature 'state';
  7         11  
  7         505  
5 7     7   31 use locale;
  7         7  
  7         56  
6 7     7   181 use Bif::Mo;
  7         11  
  7         35  
7 7     7   34 use utf8;
  7         12  
  7         44  
8 7     7   4765 use Text::Autoformat qw/autoformat/;
  7         636660  
  7         850  
9 7     7   4818 use Text::FormatTable;
  7         17386  
  7         330  
10 7     7   181974 use Time::Duration qw/ago/;
  7         11365  
  7         542  
11 7     7   43 use Time::Piece;
  7         10  
  7         58  
12 7     7   490 use Time::Seconds;
  7         12  
  7         16567  
13              
14             our $VERSION = '0.1.5_5';
15             extends 'App::bif';
16              
17             sub run {
18 1     1 1 2 my $self = shift;
19 1         4 my $opts = $self->opts;
20              
21 1 50       4 if ( $opts->{id} ) {
22 0         0 my $info = $self->get_node( $opts->{id} );
23 0         0 my $class = "App::bif::log::$info->{kind}";
24              
25 0 0       0 if ( eval "require $class" ) {
26             $opts->{path} = delete $opts->{id}
27 0 0       0 if ( $info->{kind} eq 'project' );
28              
29 0         0 return $class->new( db => $self->db, opts => $opts )->run;
30             }
31 0 0       0 die $@ if $@;
32             return $self->err( 'LogUnimplemented',
33 0         0 'cannnot log type: ' . $info->{kind} );
34             }
35              
36 1 50       3 $opts->{format} = 'short' if delete $opts->{short};
37              
38 1 50       9 return $self->run_by_full if $opts->{format} eq 'full';
39 0 0       0 return $self->run_by_short if $opts->{format} eq 'short';
40 0         0 return $self->err( 'FormatUnsupported',
41             "unsupported format type: $opts->{format}" );
42             }
43              
44             sub run_by_full {
45 1     1 0 2 my $self = shift;
46 1         3 my $opts = $self->opts;
47 1         6 my $db = $self->db();
48              
49 0           my @order;
50 0 0         if ( $opts->{order} eq 'time' ) {
    0          
51 0           @order = ( 'c.mtime DESC', 'c.id DESC' );
52             }
53             elsif ( $opts->{order} eq 'id' ) {
54 0           @order = ( 'c.id DESC', 'c.mtime DESC' );
55             }
56             else {
57 0           return $self->err( 'OrderUnsupported',
58             "unsupported order: $opts->{order}" );
59             }
60              
61 0           state $have_dbix = DBIx::ThinSQL->import(qw/ case concat coalesce sq qv/);
62              
63 0           my ( $yellow, $reset ) = $self->colours(qw/yellow reset/);
64 0           my $now = $self->now;
65              
66 0           my $sth = $db->xprepare(
67             with => 'b',
68             as => sq(
69             select => [ 'b.change_id AS start', 'b.change_id2 AS stop' ],
70             from => 'bifkv b',
71             where => { 'b.key' => 'last_sync' },
72             ),
73             select => [
74             '"c" || c.id AS change_id',
75             'n.path AS path',
76             'COALESCE(c.author,e.name) AS author',
77             "COALESCE(c.author_contact_method || ': ' || c.author_contact,"
78             . "ecm.method || ': ' || ecm.mvalue) AS contact",
79             'COALESCE(iss.title, ta.title) AS title',
80             'c.uuid AS change_uuid',
81             'c.action',
82             'c.mtime',
83             'c.mtimetz',
84             'c.mtimetzhm',
85             qq{$now - strftime('%s', c.mtime, 'unixepoch') AS mtime_age},
86             'c.message',
87             '0 AS depth',
88             concat(
89             'c.action',
90             case (
91             when => 'b.start',
92             then => qv( $yellow . ' [+]' . $reset ),
93             else => qv(''),
94             ),
95             qv(' '),
96             'n.path'
97              
98             )->as('action'),
99             ],
100             from => 'changes c',
101             left_join => 'change_deltas cd',
102             on => 'cd.change_id = c.id',
103             inner_join => 'entities e',
104             on => 'e.id = c.identity_id',
105             inner_join => 'entity_contact_methods ecm',
106             on => 'ecm.id = e.default_contact_method_id',
107             inner_join => 'identities i',
108             on => 'i.id = c.identity_id',
109             left_join => 'b',
110             on => 'c.id BETWEEN b.start+1 AND b.stop',
111             left_join => 'tasks ta',
112             on => 'ta.id = cd.action_node_id_1',
113             left_join => 'task_status ts',
114             on => 'ts.id = ta.task_status_id',
115             left_join => 'issues iss',
116             on => 'iss.id = cd.action_node_id_1',
117             left_join => 'projects p',
118             on => 'p.id = ts.project_id OR p.id = cd.action_node_id_1',
119             left_join => 'nodes n',
120             on => 'n.id = c.action_node_id_1',
121             order_by => \@order,
122             );
123              
124 0           $sth->execute;
125              
126 0           $self->start_pager;
127              
128 0           while ( my $row = $sth->hashref ) {
129 0           $self->log_comment( $row, 1 );
130             }
131              
132 0           return $self->ok('LogFull');
133             }
134              
135             sub run_by_short {
136 0     0 0   my $self = shift;
137 0 0         return $self->run_by_time if $self->opts->{order} eq 'time';
138 0 0         return $self->run_by_id if $self->opts->{order} eq 'id';
139             }
140              
141             sub run_by_time {
142 0     0 0   my $self = shift;
143 0           my $opts = $self->opts;
144 0           my $now = $self->now;
145 0           my $db = $self->db;
146 0           my ( $dark, $reset, $yellow ) = $self->colours(qw/yellow reset yellow/);
147              
148 0           state $have_thinsql = DBIx::ThinSQL->import(qw/concat coalesce qv case sq/);
149              
150 0           my $sth = $db->xprepare(
151             with => 'b',
152             as => sq(
153             select => [ 'b.change_id AS start', 'b.change_id2 AS stop' ],
154             from => 'bifkv b',
155             where => { 'b.key' => 'last_sync' },
156             ),
157             select => [
158             q{strftime('%H:%M:%S',c.mtime,'unixepoch','localtime')},
159             '"c" || c.id',
160             'COALESCE(c.author_shortname,i.shortname,e.name) AS author',
161             concat(
162             'c.action',
163             case (
164             when => 'b.start',
165             then => qv( $yellow . ' [+]' . $reset ),
166             else => qv(''),
167             )
168             ),
169             q{strftime('%Y-%m-%d',c.mtime,'unixepoch','localtime') AS mdate},
170             qq{$now - strftime('%s', c.mtime, 'unixepoch')},
171             ],
172             from => 'changes c',
173             left_join => 'change_deltas cd',
174             on => 'cd.change_id = c.id',
175             inner_join => 'entities e',
176             on => 'e.id = c.identity_id',
177             inner_join => 'identities i',
178             on => 'i.id = c.identity_id',
179             left_join => 'b',
180             on => 'c.id BETWEEN b.start+1 AND b.stop',
181             left_join => 'tasks t',
182             on => 't.id = cd.action_node_id_1',
183             left_join => 'task_status ts',
184             on => 'ts.id = t.task_status_id',
185             order_by => [ 'mdate DESC', 'c.mtime ASC', 'c.id DESC' ],
186             );
187              
188 0           $sth->execute;
189              
190 0           $self->start_pager;
191              
192 0           my @days = (
193             qw/Sunday Monday Tuesday Wednesday Thursday Friday
194             Saturday/
195             );
196              
197 0           my $localtime = localtime();
198 0           my $today = $localtime->strftime('%Y-%m-%d');
199 0           my $yesterday =
200             ( $localtime - ONE_HOUR * ( $localtime->hour + 12 ) )
201             ->strftime('%Y-%m-%d');
202              
203 0           my $mdate = '';
204 0           my $table = Text::FormatTable->new(' l l l l ');
205              
206 0           while ( my $n = $sth->arrayref ) {
207 0 0         if ( $n->[4] ne $mdate ) {
208 0 0         $table->rule(' ') if $mdate;
209 0           $mdate = $n->[4];
210 0 0         if ( $mdate eq $today ) {
    0          
211 0           $table->head(
212             $dark . $n->[4],
213             'Who',
214             'Action [today]',
215             'CID' . $reset,
216             );
217             }
218             elsif ( $mdate eq $yesterday ) {
219 0           $table->head(
220             $dark . $n->[4],
221             'Who',
222             'Action [yesterday]',
223             'CID' . $reset
224             );
225             }
226             else {
227 0           $table->head(
228             $dark . $n->[4],
229             'Who',
230             'Action [' . ago( $n->[5], 1 ) . ']',
231             'CID' . $reset,
232             );
233             }
234             }
235              
236 0           $table->row( $n->[0], $n->[2], $n->[3], $n->[1] );
237             }
238              
239 0           print $table->render . "\n";
240              
241 0           return $self->ok('LogTime');
242             }
243              
244             sub run_by_id {
245 0     0 0   my $self = shift;
246 0           my $opts = $self->opts;
247 0           my $now = $self->now;
248 0           my $db = $self->db;
249 0           my ( $dark, $reset, $yellow ) = $self->colours(qw/yellow reset yellow/);
250              
251 0           state $have_thinsql = DBIx::ThinSQL->import(qw/concat coalesce qv case sq/);
252              
253 0           my $sth = $db->xprepare(
254             with => 'b',
255             as => sq(
256             select => [ 'b.change_id AS start', 'b.change_id2 AS stop' ],
257             from => 'bifkv b',
258             where => { 'b.key' => 'last_sync' },
259             ),
260             select => [
261             '"c" || c.id',
262             q{strftime('%Y-%m-%d-%H:%M:%S',c.mtime,'unixepoch','localtime')},
263             'COALESCE(c.author,i.shortname,e.name) AS author',
264             concat(
265             'c.action',
266             case (
267             when => 'b.start',
268             then => qv( $yellow . ' [+]' . $reset ),
269             else => qv(''),
270             )
271             ),
272             ],
273             from => 'changes c',
274             left_join => 'change_deltas cd',
275             on => 'cd.change_id = c.id',
276             inner_join => 'entities e',
277             on => 'e.id = c.identity_id',
278             inner_join => 'identities i',
279             on => 'i.id = c.identity_id',
280             left_join => 'b',
281             on => 'c.id BETWEEN b.start+1 AND b.stop',
282             left_join => 'tasks t',
283             on => 't.id = cd.action_node_id_1',
284             left_join => 'task_status ts',
285             on => 'ts.id = t.task_status_id',
286             left_join => 'projects p',
287             on => 'p.id = ts.project_id OR p.id = cd.action_node_id_1',
288             order_by => ['c.id DESC'],
289             );
290              
291 0           $sth->execute;
292              
293 0   0       my $data = $sth->arrayrefs || return $self->ok('LogUid');
294 0           $self->start_pager( scalar @$data );
295              
296 0           print $self->render_table( ' l l l l ',
297             [ 'CID', 'Date', 'Who', 'Action' ], $data );
298              
299 0           return $self->ok('LogID');
300             }
301              
302             sub reformat {
303 0     0 0   my $self = shift;
304 0           my $text = shift;
305 0   0       my $depth = shift || 0;
306              
307             # $depth-- if $depth;
308              
309 0           my $left = 2 + 2 * $depth;
310 0           my $indent = ' ' x $depth;
311              
312 0           my @result;
313              
314 0           foreach my $para ( split /\n\n/, $text ) {
315 0 0         if ( $para =~ m/^[^\s]/ ) {
316 0           push( @result, autoformat( $para, { left => $left } ) );
317             }
318             else {
319 0           $para =~ s/^/$indent/gm;
320 0           push( @result, $para, "\n\n" );
321             }
322             }
323              
324 0           return @result;
325             }
326              
327             my $title;
328             my $path;
329              
330             sub log_item {
331 0     0 0   my $self = shift;
332 0           my $row = shift;
333 0           my $type = shift;
334              
335 0           my $yellow = $self->colours('yellow');
336 0           my $dark = $self->colours('yellow');
337 0           my $reset = $self->colours('yellow');
338              
339 0           $title = $row->{title};
340 0           $path = $row->{path};
341              
342 0           ( my $id = $row->{change_id} ) =~ s/(.+)\./$yellow$1$dark\./;
343             my @data = (
344             $self->header(
345             $dark . $yellow . 'change',
346             $dark . $yellow . $row->{change_id},
347             $row->{change_uuid}
348 0           ),
349             );
350              
351 0           push( @data, $self->header( 'Action', $row->{action} ) );
352 0           push( @data, $self->header( 'From', $row->{author}, $row->{contact} ) );
353              
354             # push( @data, $self->header( 'To', $row->{path} ) ) if $row->{path};
355 0           push( @data, $self->header( 'When', $self->ctime_ago($row) ) );
356              
357 0 0 0       if ( $row->{title} and $row->{status} ) {
    0          
358 0           push( @data, $self->header( 'Subject', $row->{title} ) );
359             }
360             elsif ( $row->{title} ) {
361 0           push( @data,
362             $self->header( 'Subject', "[$row->{path}] $row->{title}" ) );
363             }
364              
365 0           foreach my $field (@_) {
366 0 0         next unless defined $field->[1];
367 0           push( @data, $self->header(@$field) );
368             }
369              
370 0           print $self->render_table( ' l l', undef, \@data ) . "\n";
371 0           print $self->reformat( $row->{message} ), "\n";
372              
373 0           return;
374             }
375              
376             my $last_change_id;
377             my $last_row;
378              
379             sub log_start {
380 0     0 0   my $self = shift;
381 0           $last_change_id = 'c0';
382 0           $last_row = undef;
383             }
384              
385             sub log_next {
386 0     0 0   my $self = shift;
387 0           my $row = shift;
388              
389 0           state @data;
390 0           state $yellow = $self->colours('yellow');
391 0           state $dark = $self->colours('dark');
392              
393             # Assume finish
394 0 0         if ( !$row ) {
395             print $self->render_table( 'l l', undef, \@data,
396 0           1 + 2 * ( $last_row->{depth} ) )
397             . "\n";
398              
399 0           print $self->reformat( $last_row->{message}, $last_row->{depth} ), "\n";
400 0           return;
401             }
402              
403 0 0         if ( $row->{change_id} ne $last_change_id ) {
404 0 0         if ($last_row) {
405             print $self->render_table( 'l l', undef, \@data,
406 0           1 + 2 * ( $last_row->{depth} ) )
407             . "\n";
408              
409 0           print $self->reformat( $last_row->{message}, $last_row->{depth} ),
410             "\n";
411             }
412              
413             @data = (
414             $self->header(
415             $dark . $yellow . 'change',
416             $dark . $yellow . $row->{change_id},
417             $row->{change_uuid},
418             ),
419             $self->header( 'Action', $row->{action} ),
420 0           $self->header( 'From', $row->{author}, $row->{contact} ),
421             $self->header( 'When', $self->mtime_ago($row) ),
422             );
423             push( @data,
424             $self->header( $dark . 'Delta', $dark . $row->{delta_value} ) )
425 0 0         if $row->{delta};
426             }
427             else {
428             push( @data,
429 0           $self->header( $dark . 'Delta', $dark . $row->{delta_value} ) );
430             }
431              
432 0           $last_change_id = $row->{change_id};
433 0           $last_row = $row;
434             }
435              
436             sub log_comment {
437 0     0 1   my $self = shift;
438 0           my $row = shift;
439 0           my $raw = shift;
440              
441 0           my @data;
442              
443 0           state $yellow = $self->colours('yellow');
444 0           state $dark = $self->colours('yellow');
445              
446             push(
447             @data,
448             $self->header(
449             $dark . $yellow . 'change',
450             $dark . $yellow . $row->{change_id},
451             $row->{change_uuid},
452 0           ),
453             );
454 0           push( @data, $self->header( 'Action', $row->{action} ) );
455 0           push( @data, $self->header( 'From', $row->{author}, $row->{contact} ) );
456              
457 0 0         $path = $row->{path} if $row->{path};
458 0 0         push( @data, $self->header( 'To', $path ) ) if $path;
459 0           push( @data, $self->header( 'When', $self->mtime_ago($row) ) );
460              
461 0 0         if ( $row->{title} ) {
    0          
    0          
462 0 0         $title = $row->{title} if defined $row->{title};
463 0 0         push( @data, $self->header( 'Subject', "$title" ) ) if $title;
464             }
465             elsif ( $row->{status} ) {
466 0 0         push( @data, $self->header( 'Subject', "[$row->{status}] $title" ) )
467             if $title;
468             }
469             elsif ( !$raw ) {
470 0 0         push( @data, $self->header( 'Subject', "↪ $title" ) ) if $title;
471             }
472              
473 0           foreach my $field (@_) {
474 0 0         next unless defined $field->[1];
475 0           push( @data, $self->header(@$field) );
476             }
477              
478             print $self->render_table( 'l l', undef, \@data,
479 0           1 + 2 * ( $row->{depth} ) )
480             . "\n";
481              
482 0 0         if ( $row->{push_to} ) {
483 0           print "[Pushed to " . $row->{push_to} . "]\n\n\n";
484             }
485             else {
486 0           print $self->reformat( $row->{message}, $row->{depth} ), "\n";
487             }
488             }
489              
490             1;
491             __END__