[Rt-commit] rt branch, 4.6/jschart, updated. rt-4.4.4-63-geea1c5910

? sunnavy sunnavy at bestpractical.com
Tue Jun 11 15:58:08 EDT 2019


The branch, 4.6/jschart has been updated
       via  eea1c5910a2d32063d57ba0d9a6593040fe49cba (commit)
      from  aa59518521086d4aea59aed82a1116552b27840e (commit)

Summary of changes:
 lib/RT/Report/Tickets.pm       | 272 +++++++++++++++++++++++++++++++++++++++++
 lib/RT/Report/Tickets/Entry.pm |  29 +++++
 2 files changed, 301 insertions(+)

- Log -----------------------------------------------------------------
commit eea1c5910a2d32063d57ba0d9a6593040fe49cba
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Wed Jun 12 03:27:23 2019 +0800

    Support group by durations like "Created-Resolved" for charts

diff --git a/lib/RT/Report/Tickets.pm b/lib/RT/Report/Tickets.pm
index 65733a43f..e56a1de16 100644
--- a/lib/RT/Report/Tickets.pm
+++ b/lib/RT/Report/Tickets.pm
@@ -144,6 +144,22 @@ our %GROUPINGS_META = (
             Year Annually
             WeekOfYear
         )],  # loc_qw
+        StrftimeFormat => {
+            Time       => '%T',
+            Hourly     => '%Y-%m-%d %H',
+            Hour       => '%H',
+            Date       => '%F',
+            Daily      => '%F',
+            DayOfWeek  => '%w',
+            Day        => '%F',
+            DayOfMonth => '%d',
+            DayOfYear  => '%j',
+            Month      => '%m',
+            Monthly    => '%Y-%m',
+            Year       => '%Y',
+            Annually   => '%Y',
+            WeekOfYear => '%W',
+        },
         Function => 'GenerateDateFunction',
         Display => sub {
             my $self = shift;
@@ -211,6 +227,12 @@ our %GROUPINGS_META = (
     Enum => {
         Localize => 1,
     },
+    Duration => {
+        Localize => 1,
+        Short    => 0,
+        Show     => 1,
+        Sort     => 'duration',
+    },
 );
 
 # loc'able strings below generated with (s/loq/loc/):
@@ -295,6 +317,7 @@ foreach my $pair (qw(
         "MIN($pair)" => ["Minimum $pair", 'DateTimeInterval', 'MIN', $from, $to ],
         "MAX($pair)" => ["Maximum $pair", 'DateTimeInterval', 'MAX', $from, $to ],
     );
+    push @GROUPINGS, $pair => 'Duration';
 }
 
 our %STATISTICS;
@@ -595,6 +618,11 @@ sub SetupGroupings {
 
     $self->{'column_info'} = \%column_info;
 
+    if ( $args{Query} && grep { $_->{INFO} eq 'Duration' } map { $column_info{$_} } @{ $res{Groups} } ) {
+        # Need to do the groupby/calculation at Perl level
+        $self->{_query} = $args{'Query'};
+    }
+
     return %res;
 }
 
@@ -607,6 +635,246 @@ columns if it makes sense
 
 sub _DoSearch {
     my $self = shift;
+
+    # When groupby/calculation can't be done at SQL level, do it at Perl level
+    if ( $self->{_query} ) {
+        my $tickets = RT::Tickets->new( $self->CurrentUser );
+        $tickets->FromSQL( $self->{_query} );
+        my @groups = grep { $_->{TYPE} eq 'grouping' } map { $self->ColumnInfo($_) } $self->ColumnsList;
+        my %info;
+        while ( my $ticket = $tickets->Next ) {
+            my @keys;
+            my $max = 1;
+            for my $group ( @groups ) {
+                my $value;
+
+                if ( $ticket->_Accessible($group->{KEY}, 'read' )) {
+                    if ( $group->{SUBKEY} ) {
+                        my $method = "$group->{KEY}Obj";
+                        if ( my $obj = $ticket->$method ) {
+                            if ( $group->{INFO} eq 'Date' ) {
+                                if ( $obj->Unix > 0 ) {
+                                    $value = $obj->Strftime( $GROUPINGS_META{Date}{StrftimeFormat}{ $group->{SUBKEY} },
+                                        Timezone => 'user' );
+                                }
+                                else {
+                                    $value = $self->loc('(no value)')
+                                }
+                            }
+                            else {
+                                $value = $obj->_Value($group->{SUBKEY});
+                            }
+                            $value //= $self->loc('(no value)');
+                        }
+                    }
+                    $value //= $ticket->_Value( $group->{KEY} ) // $self->loc('(no value)');
+                }
+                elsif ( $group->{INFO} eq 'Watcher' ) {
+                    my @values;
+                    if ( $ticket->can($group->{KEY}) ) {
+                        my $method = $group->{KEY};
+                        push @values, @{$ticket->$method->UserMembersObj->ItemsArrayRef};
+                    }
+                    elsif ( $group->{KEY} eq 'Watcher' ) {
+                        push @values, @{$ticket->$_->UserMembersObj->ItemsArrayRef} for /Requestor Cc AdminCc/;
+                    }
+                    else {
+                        RT->Logger->error("Unsupported group by $group->{KEY}");
+                        next;
+                    }
+
+                    @values = map { $_->_Value( $group->{SUBKEY} || 'Name' ) } @values;
+                    @values = $self->loc('(no value)') unless @values;
+                    $value = \@values;
+                }
+                elsif ( $group->{INFO} eq 'CustomField' ) {
+                    my ($id) = $group->{SUBKEY} =~ /{(\d+)}/;
+                    my $values = $ticket->CustomFieldValues($id);
+                    if ( $values->Count ) {
+                        $value = [ map { $_->Content } @{ $values->ItemsArrayRef } ];
+                    }
+                    else {
+                        $value = $self->loc('(no value)');
+                    }
+                }
+                elsif ( $group->{INFO} eq 'Duration' ) {
+                    my ($start, $end) = split /-/, $group->{FIELD};
+                    my $start_method = $start . 'Obj';
+                    my $end_method   = $end . 'Obj';
+
+                    if ( $ticket->$end_method->Unix > 0 && $ticket->$start_method->Unix > 0 ) {
+                        $value = RT::Date->new( $self->CurrentUser )->DurationAsString(
+                            $ticket->$end_method->Unix - $ticket->$start_method->Unix,
+                            Show  => $group->{META}{Show} // 3,
+                            Short => $group->{META}{Short} // 1,
+                        );
+                    }
+                    else {
+                        $value = $self->loc('(no value)');
+                    }
+                }
+                else {
+                    RT->Logger->error("Unsupported group by $group->{KEY}");
+                    next;
+                }
+                push @keys, $value;
+            }
+
+            # @keys could contain arrayrefs, so we need to expand it.
+            # e.g. "open", [ "root", "foo" ], "General" )
+            # will be expanded to:
+            #   "open", "root", "General"
+            #   "open", "foo", "General"
+
+            my @all_keys;
+            for my $key (@keys) {
+                if ( ref $key eq 'ARRAY' ) {
+                    if (@all_keys) {
+                        my @new_all_keys;
+                        for my $keys ( @all_keys ) {
+                            push @new_all_keys, [ @$keys, $_ ] for @$key;
+                        }
+                        @all_keys = @new_all_keys;
+                    }
+                    else {
+                        push @all_keys, [$_] for @$key;
+                    }
+                }
+                else {
+                    if (@all_keys) {
+                        @all_keys = map { [ @$_, $key ] } @all_keys;
+                    }
+                    else {
+                        push @all_keys, [$key];
+                    }
+                }
+            }
+
+            my @fields = grep { $_->{TYPE} eq 'statistic' }
+                map { $self->ColumnInfo($_) } $self->ColumnsList;
+
+            while ( my $field = shift @fields ) {
+                for my $keys (@all_keys) {
+                    my $key = join ';;;', @$keys;
+                    if ( $field->{NAME} =~ /^id/ && $field->{FUNCTION} eq 'COUNT' ) {
+                        $info{$key}{ $field->{NAME} }++;
+                    }
+                    elsif ( $field->{INFO}[1] eq 'Time' ) {
+                        if ( $field->{NAME} =~ /^postfunction/ ) {
+                            if ( $field->{MAP} ) {
+                                my ($meta_type) = $field->{INFO}[1] =~ /^(\w+)All$/;
+                                for my $item ( values %{ $field->{MAP} } ) {
+                                    push @fields,
+                                        {
+                                        NAME  => $item->{NAME},
+                                        FIELD => $item->{FIELD},
+                                        INFO  => [
+                                            '', $meta_type,
+                                            $item->{FUNCTION} =~ /^(\w+)/ ? $1 : '',
+                                            @{ $field->{INFO} }[ 2 .. $#{ $field->{INFO} } ],
+                                        ],
+                                        };
+                                }
+                            }
+                        }
+                        elsif ( $field->{NAME} =~ /^(TimeWorked|TimeEstimated|TimeLeft)$/ ) {
+                            my $method = $1;
+
+                            if ( $field->{INFO}[2] eq 'SUM' ) {
+                                $info{$key}{ lc $field->{NAME} } += $ticket->$method * 60;
+                            }
+                            elsif ( $field->{INFO}[2] eq 'AVG' ) {
+                                $info{$key}{ lc $field->{NAME} }{total} += $ticket->$method * 60;
+                                $info{$key}{ lc $field->{NAME} }{count}++;
+                                $info{$key}{ lc $field->{NAME} }{calculate} = sub {
+                                    my $item = shift;
+                                    return sprintf '%.0f', $item->{total} / $item->{count};
+                                };
+                            }
+                            elsif ( $field->{INFO}[2] eq 'MAX' ) {
+                                $info{$key}{ lc $field->{NAME} } = $ticket->$method * 60
+                                    unless $info{$key}{ lc $field->{NAME} }
+                                    && $info{$key}{ lc $field->{NAME} } > $ticket->$method * 60;
+                            }
+                            elsif ( $field->{INFO}[2] eq 'MIN' ) {
+                                $info{$key}{ lc $field->{NAME} } = $ticket->$method * 60
+                                    unless $info{$key}{ lc $field->{NAME} }
+                                    && $info{$key}{ lc $field->{NAME} } < $ticket->$method * 60;
+                            }
+                            else {
+                                RT->Logger->error("Unsupported type $field->{INFO}[2]");
+                            }
+                        }
+                        else {
+                            RT->Logger->error("Unsupported field $field->{NAME}");
+                        }
+                    }
+                    elsif ( $field->{INFO}[1] eq 'DateTimeInterval' ) {
+                        my ( undef, undef, $type, $start, $end ) = @{ $field->{INFO} };
+
+                        my $start_method = $start . 'Obj';
+                        my $end_method   = $end . 'Obj';
+                        next unless $ticket->$end_method->Unix > 0 && $ticket->$start_method->Unix > 0;
+                        my $value = $ticket->$end_method->Unix - $ticket->$start_method->Unix;
+                        if ( $field->{INFO}[2] eq 'SUM' ) {
+                            $info{$key}{ lc $field->{NAME} } += $value;
+                        }
+                        elsif ( $field->{INFO}[2] eq 'AVG' ) {
+                            $info{$key}{ lc $field->{NAME} }{total} += $value;
+                            $info{$key}{ lc $field->{NAME} }{count}++;
+                            $info{$key}{ lc $field->{NAME} }{calculate} = sub {
+                                my $item = shift;
+                                return sprintf '%.0f', $item->{total} / $item->{count};
+                            };
+                        }
+                        elsif ( $field->{INFO}[2] eq 'MAX' ) {
+                            $info{$key}{ lc $field->{NAME} } = $value
+                                unless $info{$key}{ lc $field->{NAME} }
+                                && $info{$key}{ lc $field->{NAME} } > $value;
+                        }
+                        elsif ( $field->{INFO}[2] eq 'MIN' ) {
+                            $info{$key}{ lc $field->{NAME} } = $value
+                                unless $info{$key}{ lc $field->{NAME} }
+                                && $info{$key}{ lc $field->{NAME} } < $value;
+                        }
+                        else {
+                            RT->Logger->error("Unsupported type $field->{INFO}[2]");
+                        }
+                    }
+                    else {
+                        RT->Logger->error("Unsupported field $field->{INFO}[1]");
+                    }
+                }
+            }
+        }
+
+        # Make generated results real SB results
+        for my $key ( keys %info ) {
+            my @keys = split /;;;/, $key;
+            my $row;
+            for my $group ( @groups ) {
+                $row->{lc $group->{NAME}} = shift @keys;
+            }
+            for my $field ( keys %{ $info{$key} } ) {
+                my $value = $info{$key}{$field};
+                if ( ref $value eq 'HASH' && $value->{calculate} ) {
+                    $row->{$field} = $value->{calculate}->($value);
+                }
+                else {
+                    $row->{$field} = $info{$key}{$field};
+                }
+            }
+            my $item = $self->NewItem();
+            $item->LoadFromHash($row);
+            $self->AddRecord($item);
+        }
+        $self->{must_redo_search} = 0;
+        $self->{is_limited} = 1;
+        $self->PostProcessRecords;
+
+        return;
+    }
+
     $self->SUPER::_DoSearch( @_ );
     if ( $self->{'must_redo_search'} ) {
         $RT::Logger->crit(
@@ -727,6 +995,10 @@ sub SortEntries {
         elsif ( $order eq 'numeric raw' ) {
             push @SORT_OPS, sub { $_[0][$idx] <=> $_[1][$idx] };
             $method = 'RawValue';
+        }
+        elsif ( $order eq 'duration' ) {
+            push @SORT_OPS, sub { $_[0][$idx] <=> $_[1][$idx] };
+            $method = 'DurationValue';
         } else {
             $RT::Logger->error("Unknown sorting function '$order'");
             next;
diff --git a/lib/RT/Report/Tickets/Entry.pm b/lib/RT/Report/Tickets/Entry.pm
index dbdcde2c3..410aee02a 100644
--- a/lib/RT/Report/Tickets/Entry.pm
+++ b/lib/RT/Report/Tickets/Entry.pm
@@ -144,6 +144,35 @@ sub Report {
     return $_[0]->{'report'};
 }
 
+sub DurationValue {
+    my $self  = shift;
+    my $value = $self->__Value(@_);
+
+    return 0 unless $value;
+
+    if ( $value =~ /(\d+)(?:s| second)/ ) {
+        return $1;
+    }
+    elsif ( $value =~ /(\d+)(?:m| minute)/ ) {
+        return $1 * $RT::Date::MINUTE;
+    }
+    elsif ( $value =~ /(\d+)(?:h| hour)/ ) {
+        return $1 * $RT::Date::HOUR;
+    }
+    elsif ( $value =~ /(\d+)(?:d| day)/ ) {
+        return $1 * $RT::Date::DAY;
+    }
+    elsif ( $value =~ /(\d+)(?:W| week)/ ) {
+        return $1 * $RT::Date::WEEK;
+    }
+    elsif ( $value =~ /(\d+)(?:M| month)/ ) {
+        return $1 * $RT::Date::MONTH;
+    }
+    elsif ( $value =~ /(\d+)(?:Y| year)/ ) {
+        return $1 * $RT::Date::YEAR;
+    }
+}
+
 RT::Base->_ImportOverlays();
 
 1;

-----------------------------------------------------------------------


More information about the rt-commit mailing list