Mailing List Archive

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

- Log -----------------------------------------------------------------
commit c5b647ec478e6b8f344a8a42b12a392cdea2f30c
Author: sunnavy <sunnavy@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@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@localhost' },
+ { Subject => 't2', Status => 'new', "RT::CustomRole-$cr_id" => 'root@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@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@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" );


-----------------------------------------------------------------------
_______________________________________________
rt-commit mailing list
rt-commit@lists.bestpractical.com
http://lists.bestpractical.com/cgi-bin/mailman/listinfo/rt-commit