[Rt-commit] rt branch, 4.4/chart-group-by-custom-roles, created. rt-4.4.4-545-g19f954c329

? sunnavy sunnavy at bestpractical.com
Wed Jun 16 18:35:09 EDT 2021


The branch, 4.4/chart-group-by-custom-roles has been created
        at  19f954c329614992907a16ca62bf3fb1a87c65fb (commit)

- Log -----------------------------------------------------------------
commit c5b647ec478e6b8f344a8a42b12a392cdea2f30c
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Thu Jul 23 02:12:35 2020 +0800

    Add grouping by custom roles for ticket search charts

diff --git a/lib/RT/Report/Tickets.pm b/lib/RT/Report/Tickets.pm
index 2cbda0bdcf..566fb541f3 100644
--- a/lib/RT/Report/Tickets.pm
+++ b/lib/RT/Report/Tickets.pm
@@ -86,6 +86,7 @@ our @GROUPINGS = (
     Cc            => 'Watcher',         #loc_left_pair
     AdminCc       => 'Watcher',         #loc_left_pair
     Watcher       => 'Watcher',         #loc_left_pair
+    CustomRole    => 'Watcher',
 
     Created       => 'Date',            #loc_left_pair
     Starts        => 'Date',            #loc_left_pair
@@ -126,13 +127,62 @@ our %GROUPINGS_META = (
         Function => 'GenerateUserFunction',
     },
     Watcher => {
-        SubFields => [grep RT::User->_Accessible($_, "public"), qw(
-            Name RealName NickName
-            EmailAddress
-            Organization
-            Lang City Country Timezone
-        )],
+        SubFields => sub {
+            my $self = shift;
+            my $args = shift;
+
+            my @fields = grep RT::User->_Accessible( $_, "public" ),
+                qw( Name RealName NickName EmailAddress Organization Lang City Country Timezone);
+
+            my @res;
+            if ( $args->{key} =~ /^CustomRole/ ) {
+                my $queues = $args->{'Queues'};
+                if ( !$queues && $args->{'Query'} ) {
+                    require RT::Interface::Web::QueryBuilder::Tree;
+                    my $tree = RT::Interface::Web::QueryBuilder::Tree->new('AND');
+                    $tree->ParseSQL( Query => $args->{'Query'}, CurrentUser => $self->CurrentUser );
+                    $queues = $args->{'Queues'} = $tree->GetReferencedQueues( CurrentUser => $self->CurrentUser );
+                }
+                return () unless $queues;
+
+                my $crs = RT::CustomRoles->new( $self->CurrentUser );
+                for my $id ( keys %$queues ) {
+                    my $queue = RT::Queue->new( $self->CurrentUser );
+                    $queue->Load($id);
+                    next unless $queue->id;
+
+                    $crs->LimitToObjectId( $queue->id );
+                }
+                while ( my $cr = $crs->Next ) {
+                    for my $field (@fields) {
+                        push @res, [ $cr->Name, $field ], "CustomRole.{" . $cr->id . "}.$field";
+                    }
+                }
+            }
+            else {
+                for my $field (@fields) {
+                    push @res, [ $args->{key}, $field ], "$args->{key}.$field";
+                }
+            }
+            return @res;
+        },
         Function => 'GenerateWatcherFunction',
+        Label    => sub {
+            my $self = shift;
+            my %args = (@_);
+
+            my $key;
+            if ( $args{KEY} =~ /^CustomRole\.\{(\d+)\}/ ) {
+                my $id = $1;
+                my $cr = RT::CustomRole->new( $self->CurrentUser );
+                $cr->Load($id);
+                $key = $cr->Name;
+            }
+            else {
+                $key = $args{KEY};
+            }
+            return join ' ', $key, $args{SUBKEY};
+        },
     },
     Date => {
         SubFields => [qw(
@@ -400,7 +450,7 @@ sub Groupings {
             push @fields, map { ([$field, $_], "$field.$_") } @{ $meta->{'SubFields'} };
         }
         elsif ( my $code = $self->FindImplementationCode( $meta->{'SubFields'} ) ) {
-            push @fields, $code->( $self, \%args );
+            push @fields, $code->( $self, { %args, key => $field } );
         }
         else {
             $RT::Logger->error(
@@ -417,10 +467,10 @@ sub IsValidGrouping {
     my %args = (@_);
     return 0 unless $args{'GroupBy'};
 
-    my ($key, $subkey) = split /\./, $args{'GroupBy'}, 2;
+    my ($key, $subkey) = split /(?<!CustomRole)\./, $args{'GroupBy'}, 2;
 
     %GROUPINGS = @GROUPINGS unless keys %GROUPINGS;
-    my $type = $GROUPINGS{$key};
+    my $type = $self->_GroupingType( $key );
     return 0 unless $type;
     return 1 unless $subkey;
 
@@ -432,7 +482,7 @@ sub IsValidGrouping {
         return 1 if grep $_ eq $subkey, @{ $meta->{'SubFields'} };
     }
     elsif ( my $code = $self->FindImplementationCode( $meta->{'SubFields'}, 'silent' ) ) {
-        return 1 if grep $_ eq "$key.$subkey", $code->( $self, \%args );
+        return 1 if grep $_ eq "$key.$subkey", $code->( $self, { %args, key => $key } );
     }
     return 0;
 }
@@ -540,10 +590,10 @@ sub SetupGroupings {
             RT->Logger->error("'$e' is not a valid grouping for reports; skipping");
             next;
         }
-        my ($key, $subkey) = split /\./, $e, 2;
+        my ($key, $subkey) = split /(?<!CustomRole)\./, $e, 2;
         $e = { $self->_FieldToFunction( KEY => $key, SUBKEY => $subkey ) };
         $e->{'TYPE'} = 'grouping';
-        $e->{'INFO'} = $GROUPINGS{ $key };
+        $e->{'INFO'} = $self->_GroupingType($key);
         $e->{'META'} = $GROUPINGS_META{ $e->{'INFO'} };
         $e->{'POSITION'} = $i++;
         push @group_by, $e;
@@ -647,7 +697,7 @@ sub _FieldToFunction {
 
     $args{'FIELD'} ||= $args{'KEY'};
 
-    my $meta = $GROUPINGS_META{ $GROUPINGS{ $args{'KEY'} } };
+    my $meta = $GROUPINGS_META{ $self->_GroupingType( $args{'KEY'} ) };
     return ('FUNCTION' => 'NULL') unless $meta;
 
     return %args unless $meta->{'Function'};
@@ -877,6 +927,7 @@ sub GenerateWatcherFunction {
 
     my $type = $args{'FIELD'};
     $type = '' if $type eq 'Watcher';
+    $type =~ s!^CustomRole\.\{(\d+)\}!RT::CustomRole-$1!g;
 
     my $column = $args{'SUBKEY'} || 'Name';
 
@@ -1126,6 +1177,14 @@ sub FormatTable {
     return thead => \@head, tbody => \@body, tfoot => \@footer;
 }
 
+sub _GroupingType {
+    my $self = shift;
+    my $key  = shift or return;
+    # keys for custom roles are like "CustomRole.{1}"
+    $key = 'CustomRole' if $key =~ /^CustomRole/;
+    return $GROUPINGS{$key};
+}
+
 RT::Base->_ImportOverlays();
 
 1;

commit 353c6f078377bb6bc7f601eeb84d8feb5bbd3343
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Thu Jul 23 02:31:55 2020 +0800

    Add tests for grouping by custom roles in ticket search charts

diff --git a/t/charts/group-by-cr.t b/t/charts/group-by-cr.t
new file mode 100644
index 0000000000..cda29a2ad7
--- /dev/null
+++ b/t/charts/group-by-cr.t
@@ -0,0 +1,80 @@
+use strict;
+use warnings;
+
+use RT::Test tests => undef;
+use RT::Ticket;
+
+my $q = RT::Test->load_or_create_queue( Name => 'General' );
+ok $q && $q->id, 'loaded or created queue';
+my $queue = $q->Name;
+
+my $cr = RT::CustomRole->new( RT->SystemUser );
+my ( $id, $msg ) = $cr->Create( Name => 'Test', Queue => $q->id );
+ok $id, $msg;
+ok( $cr->AddToObject( ObjectId => $q->id ) );
+my $cr_id = $cr->id;
+
+my @tickets = RT::Test->create_tickets(
+    {},
+    { Subject => 't1', Status => 'new', "RT::CustomRole-$cr_id" => 'root at localhost' },
+    { Subject => 't2', Status => 'new', "RT::CustomRole-$cr_id" => 'root at localhost' },
+    { Subject => 't3', Status => 'new' },
+);
+
+use_ok 'RT::Report::Tickets';
+
+{
+    my $report  = RT::Report::Tickets->new( RT->SystemUser );
+    my %columns = $report->SetupGroupings(
+        Query    => 'Queue = ' . $q->id,
+        GroupBy  => ["CustomRole.{$cr_id}.Name"],
+        Function => ['COUNT'],
+    );
+    $report->SortEntries;
+
+    my @colors   = RT->Config->Get("ChartColors");
+    my $expected = {
+        'thead' => [
+            {   'cells' => [
+                    { 'value'   => 'Test Name', 'type'  => 'head' },
+                    { 'rowspan' => 1,           'value' => 'Ticket count', 'type' => 'head', 'color' => $colors[0] },
+                ],
+            }
+        ],
+        'tfoot' => [
+            {   'cells' =>
+                    [ { 'colspan' => 1, 'value' => 'Total', 'type' => 'label' }, { 'value' => 3, 'type' => 'value' }, ],
+                'even' => 1,
+            }
+        ],
+        'tbody' => [
+            {   'cells' => [
+                    {   'type'  => 'label',
+                        'value' => '(no value)'
+                    },
+                    {   'query' => '(CustomRole.{1}.Name IS NULL)',
+                        'type'  => 'value',
+                        'value' => '1'
+                    }
+                ],
+                'even' => 1
+            },
+            {   'cells' => [
+                    {   'type'  => 'label',
+                        'value' => 'root'
+                    },
+                    {   'value' => '2',
+                        'query' => '(CustomRole.{1}.Name = \'root\')',
+                        'type'  => 'value'
+                    }
+                ],
+                'even' => 0
+            },
+        ],
+    };
+
+    my %table = $report->FormatTable(%columns);
+    is_deeply( \%table, $expected, "basic table" );
+}
+
+done_testing;

commit 9e13721a922b8f456230edf067f966f639608e84
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Thu Jun 17 01:53:06 2021 +0800

    Group by direct members of role groups for ticket search charts
    
    This is to group by user defined groups, instead of grouping by all the
    users of these groups recursively.
    
    Adding user defined groups to ticket roles is more common these days,
    and it feels more natural and less confusing to show these user defined
    groups in the chart.

diff --git a/lib/RT/Report/Tickets.pm b/lib/RT/Report/Tickets.pm
index 566fb541f3..1db20c06c0 100644
--- a/lib/RT/Report/Tickets.pm
+++ b/lib/RT/Report/Tickets.pm
@@ -131,8 +131,11 @@ our %GROUPINGS_META = (
             my $self = shift;
             my $args = shift;
 
-            my @fields = grep RT::User->_Accessible( $_, "public" ),
-                qw( Name RealName NickName EmailAddress Organization Lang City Country Timezone);
+            my %fields = (
+                user => [ grep RT::User->_Accessible( $_, "public" ),
+                    qw( Name RealName NickName EmailAddress Organization Lang City Country Timezone) ],
+                principal => [ grep RT::User->_Accessible( $_, "public" ), qw( Name ) ],
+            );
 
             my @res;
             if ( $args->{key} =~ /^CustomRole/ ) {
@@ -154,13 +157,13 @@ our %GROUPINGS_META = (
                     $crs->LimitToObjectId( $queue->id );
                 }
                 while ( my $cr = $crs->Next ) {
-                    for my $field (@fields) {
+                    for my $field ( @{ $fields{ $cr->MaxValues ? 'user' : 'principal' } } ) {
                         push @res, [ $cr->Name, $field ], "CustomRole.{" . $cr->id . "}.$field";
                     }
                 }
             }
             else {
-                for my $field (@fields) {
+                for my $field ( @{ $fields{principal} } ) {
                     push @res, [ $args->{key}, $field ], "$args->{key}.$field";
                 }
             }
@@ -183,6 +186,19 @@ our %GROUPINGS_META = (
             }
             return join ' ', $key, $args{SUBKEY};
         },
+        Display => sub {
+            my $self = shift;
+            my %args = (@_);
+            if ( $args{FIELD} eq 'id' ) {
+                my $princ = RT::Principal->new( $self->CurrentUser );
+                $princ->Load( $args{'VALUE'} ) if $args{'VALUE'};
+                return $self->loc('(no value)') unless $princ->Id;
+                return $princ->Object->Name;
+            }
+            else {
+                return $args{VALUE};
+            }
+        },
     },
     Date => {
         SubFields => [qw(
@@ -927,17 +943,39 @@ sub GenerateWatcherFunction {
 
     my $type = $args{'FIELD'};
     $type = '' if $type eq 'Watcher';
-    $type =~ s!^CustomRole\.\{(\d+)\}!RT::CustomRole-$1!g;
 
-    my $column = $args{'SUBKEY'} || 'Name';
+    my $single_role;
 
-    my $u_alias = $self->{"_sql_report_watcher_users_alias_$type"};
-    unless ( $u_alias ) {
-        my ($g_alias, $gm_alias);
-        ($g_alias, $gm_alias, $u_alias) = $self->_WatcherJoin( Name => $type );
-        $self->{"_sql_report_watcher_users_alias_$type"} = $u_alias;
+    if ( $type =~ s!^CustomRole\.\{(\d+)\}!RT::CustomRole-$1! ) {
+        my $id = $1;
+        my $cr = RT::CustomRole->new( $self->CurrentUser );
+        $cr->Load($id);
+        $single_role = 1 if $cr->MaxValues;
     }
-    @args{qw(ALIAS FIELD)} = ($u_alias, $column);
+
+    my $column = $single_role ? $args{'SUBKEY'} || 'Name' : 'id';
+
+    my $alias = $self->{"_sql_report_watcher_alias_$type"};
+    unless ( $alias ) {
+        my $groups = $self->_RoleGroupsJoin(Name => $type);
+        my $group_members = $self->Join(
+            TYPE            => 'LEFT',
+            ALIAS1          => $groups,
+            FIELD1          => 'id',
+            TABLE2          => 'GroupMembers',
+            FIELD2          => 'GroupId',
+            ENTRYAGGREGATOR => 'AND',
+        );
+        $alias = $self->Join(
+            TYPE   => 'LEFT',
+            ALIAS1 => $group_members,
+            FIELD1 => 'MemberId',
+            TABLE2 => $single_role ? 'Users' : 'Principals',
+            FIELD2 => 'id',
+        );
+        $self->{"_sql_report_watcher_alias_$type"} = $alias;
+    }
+    @args{qw(ALIAS FIELD)} = ($alias, $column);
 
     return %args;
 }
diff --git a/lib/RT/Report/Tickets/Entry.pm b/lib/RT/Report/Tickets/Entry.pm
index dbdcde2c35..2428bcfe0d 100644
--- a/lib/RT/Report/Tickets/Entry.pm
+++ b/lib/RT/Report/Tickets/Entry.pm
@@ -121,6 +121,14 @@ sub Query {
             my $value = $self->RawValue( $column );
             my $op = '=';
             if ( defined $value ) {
+                if ( $info->{INFO} eq 'Watcher' && $info->{FIELD} eq 'id' ) {
+
+                    # convert id to name
+                    my $princ = RT::Principal->new( $self->CurrentUser );
+                    $princ->Load($value);
+                    $value = $princ->Object->Name if $princ->Object;
+                }
+
                 unless ( $value =~ /^\d+$/ ) {
                     $value =~ s/(['\\])/\\$1/g;
                     $value = "'$value'";

commit 19f954c329614992907a16ca62bf3fb1a87c65fb
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Thu Jun 17 02:21:02 2021 +0800

    Update tests for the support of grouping by user defined groups in charts
    
    As a multiple-members role, Requestors now only has "Name" subfield, to
    support both users and groups.

diff --git a/t/web/charting.t b/t/web/charting.t
index 0b0329c713..533d721fa0 100644
--- a/t/web/charting.t
+++ b/t/web/charting.t
@@ -49,14 +49,14 @@ is( $m->content_type, "image/png" );
 ok( length($m->content), "Has content" );
 
 
-# Group by Requestor email
-$m->get_ok( "/Search/Chart.html?Query=id>0&GroupBy=Requestor.EmailAddress" );
-$m->content_like(qr{<th[^>]*>Requestor\s+EmailAddress</th>\s*<th[^>]*>Ticket count\s*</th>},
+# Group by Requestor name
+$m->get_ok( "/Search/Chart.html?Query=id>0&GroupBy=Requestor.Name" );
+$m->content_like(qr{<th[^>]*>Requestor\s+Name</th>\s*<th[^>]*>Ticket count\s*</th>},
                  "Grouped by requestor");
 $m->content_like(qr{root0\@localhost\s*</th>\s*<td[^>]*>\s*<a[^>]*>3</a>}, "Found results in table");
 $m->content_like(qr{<img src="/Search/Chart\?}, "Found image");
 
-$m->get_ok( "/Search/Chart?Query=id>0&GroupBy=Requestor.EmailAddress" );
+$m->get_ok( "/Search/Chart?Query=id>0&GroupBy=Requestor.Name" );
 is( $m->content_type, "image/png" );
 ok( length($m->content), "Has content" );
 

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


More information about the rt-commit mailing list