Mailing List Archive

rt branch 5.0/search-chart-sort-axis-labels created. rt-5.0.5-187-g02d3925cb7
This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "rt".

The branch, 5.0/search-chart-sort-axis-labels has been created
at 02d3925cb7e1c771ecd61b6a7bf364eec990b823 (commit)

- Log -----------------------------------------------------------------
commit 02d3925cb7e1c771ecd61b6a7bf364eec990b823
Author: Jim Brandt <jbrandt@bestpractical.com>
Date: Fri Apr 12 11:33:35 2024 -0400

Remove unresolved link to the configure script

diff --git a/docs/charts.pod b/docs/charts.pod
index 6064c68f24..42d35e12e1 100644
--- a/docs/charts.pod
+++ b/docs/charts.pod
@@ -33,7 +33,7 @@ While charts is a core part of RT, if you want to use GD charting in place
of JS charting then you do need to enable GD it using the C<--enable-gd>
option and install the required dependencies when you install RT.
If you didn't originally install with this flag, you can enable it by
-re-running the L<< C<configure> >> script from the RT distribution
+re-running the C<configure> script from the RT distribution
(including all previous options passed to it originally) or doing the
following in your current install:


commit d7a2e4898d28222d37bb98e8e7ae71505fa88ff2
Author: Jim Brandt <jbrandt@bestpractical.com>
Date: Fri Apr 12 11:29:45 2024 -0400

Document chart x-axis options

diff --git a/docs/charts.pod b/docs/charts.pod
index fb6fcc2076..6064c68f24 100644
--- a/docs/charts.pod
+++ b/docs/charts.pod
@@ -261,6 +261,47 @@ height of the generated chart by entering a size in pixels. These width
and height values are saved if you save the chart and are used if
you include the chart on a Dashboard as well.

+=head1 Configuring the X-Axis
+
+Sometimes you can generate a chart that contains a large amount of data,
+making it difficult to see the important information you are interested
+in. For example, you might do a search on a support queue and select a
+Group By of Requestor. If you have a bunch of tickets, your chart might
+look like the following.
+
+=for html <img alt="Support Tickets by Requestor" src="images/chart-tickets-by-requestor.png">
+
+=for :text [Support Tickets by Requestor F<docs/images/chart-tickets-by-requestor.png>]
+
+=for :man [Support Tickets by Requestor F<docs/images/chart-tickets-by-requestor.png>]
+
+This chart defaults to ordering the results based on the Requestor name,
+which makes sense in some cases, but here it doesn't help us understand
+our support activity. What we might want to see is which users have opened
+the most tickets.
+
+To improve the layout, in the X-Axis section, change Order by from "Label"
+to "Value" and change "Ascending" to "Descending". The chart will now order
+the X-axis based on the "value" which is the number of tickets for each
+Requestor. Selecting "Descending" will display the largest count on the left.
+
+=for html <img alt="Chart Ordered By Value" src="images/chart-ordered-by-value.png">
+
+=for :text [Chart Ordered By Value F<docs/images/chart-ordered-by-value.png>]
+
+=for :man [Chart Ordered By Value F<docs/images/chart-ordered-by-value.png>]
+
+That's better, but maybe you are actually only interested in the top 10 Requestors
+in that chart. To trim off all of the low counts, set "Limit chart to" to the
+"Top" 10 items. All of the low results are now trimmed off, leaving just the
+values you want to see.
+
+=for html <img alt="Chart Limited to Top 10 Results" src="images/chart-limited-to-top-10.png">
+
+=for :text [Chart Limited to Top 10 Results F<docs/images/chart-limited-to-top-10.png>]
+
+=for :man [Chart Limited to Top 10 Results F<docs/images/chart-limited-to-top-10.png>]
+
=head1 Saving Charts

Much like searches, you can save charts once you get them configured the
diff --git a/docs/images/chart-limited-to-top-10.png b/docs/images/chart-limited-to-top-10.png
new file mode 100644
index 0000000000..faacea05b9
Binary files /dev/null and b/docs/images/chart-limited-to-top-10.png differ
diff --git a/docs/images/chart-ordered-by-value.png b/docs/images/chart-ordered-by-value.png
new file mode 100644
index 0000000000..dd78263d58
Binary files /dev/null and b/docs/images/chart-ordered-by-value.png differ
diff --git a/docs/images/chart-tickets-by-requestor.png b/docs/images/chart-tickets-by-requestor.png
new file mode 100644
index 0000000000..7f0202dd75
Binary files /dev/null and b/docs/images/chart-tickets-by-requestor.png differ

commit d7837c90effe4cb8a9485c538abbed452da3a7d6
Author: sunnavy <sunnavy@bestpractical.com>
Date: Thu Mar 28 11:41:17 2024 -0400

Test sorting/limiting axis labels of search charts

diff --git a/t/charts/basics.t b/t/charts/basics.t
index 0f93e5a243..2896c5378d 100644
--- a/t/charts/basics.t
+++ b/t/charts/basics.t
@@ -12,7 +12,7 @@ my @tickets = add_tix_from_data(
{ Subject => 'n', Status => 'new' },
{ Subject => 'o', Status => 'open' },
{ Subject => 'o', Status => 'open' },
- { Subject => 'r', Status => 'resolved' },
+ { Subject => 'o', Status => 'open' },
{ Subject => 'r', Status => 'resolved' },
{ Subject => 'r', Status => 'resolved' },
);
@@ -28,6 +28,19 @@ use_ok 'RT::Report::Tickets';
);
$report->SortEntries;

+ my $new_cell = [.
+ { 'value' => 'new', 'type' => 'label' },
+ { 'query' => '(Status = \'new\')', 'value' => '1', 'type' => 'value' },
+ ];
+ my $open_cell = [.
+ { 'value' => 'open', 'type' => 'label' },
+ { 'query' => '(Status = \'open\')', 'value' => '3', 'type' => 'value' }
+ ];
+ my $resolved_cell = [.
+ { 'value' => 'resolved', 'type' => 'label' },
+ { 'query' => '(Status = \'resolved\')', 'value' => '2', 'type' => 'value' }
+ ];
+
my @colors = RT->Config->Get("ChartColors");
my $expected = {
'thead' => [ {
@@ -45,24 +58,15 @@ use_ok 'RT::Report::Tickets';
} ],
'tbody' => [.
{
- 'cells' => [.
- { 'value' => 'new', 'type' => 'label' },
- { 'query' => '(Status = \'new\')', 'value' => '1', 'type' => 'value' },
- ],
+ 'cells' => $new_cell,
'even' => 1
},
{
- 'cells' => [.
- { 'value' => 'open', 'type' => 'label' },
- { 'query' => '(Status = \'open\')', 'value' => '2', 'type' => 'value' }
- ],
+ 'cells' => $open_cell,
'even' => 0
},
{
- 'cells' => [.
- { 'value' => 'resolved', 'type' => 'label' },
- { 'query' => '(Status = \'resolved\')', 'value' => '3', 'type' => 'value' }
- ],
+ 'cells' => $resolved_cell,
'even' => 1
},
]
@@ -70,6 +74,61 @@ use_ok 'RT::Report::Tickets';

my %table = $report->FormatTable( %columns );
is_deeply( \%table, $expected, "basic table" );
+
+ $report->SortEntries( ChartOrderBy => 'label', ChartOrder => 'ASC' );
+ %table = $report->FormatTable( %columns );
+ is_deeply( \%table, $expected, "basic table sorted by label ASC" );
+
+ $report->SortEntries( ChartOrderBy => 'label', ChartOrder => 'DESC' );
+ %table = $report->FormatTable( %columns );
+ @{$expected->{'tbody'}} = reverse @{$expected->{'tbody'}};
+ is_deeply( \%table, $expected, "basic table sorted by label DESC" );
+
+ $report->SortEntries( ChartOrderBy => 'value', ChartOrder => 'ASC' );
+ %table = $report->FormatTable( %columns );
+ $expected->{'tbody'} = [.
+ {
+ 'cells' => $new_cell,
+ 'even' => 1
+ },
+ {
+ 'cells' => $resolved_cell,
+ 'even' => 0,
+ },
+ {
+ 'cells' => $open_cell,
+ 'even' => 1
+ },
+ ];
+ is_deeply( \%table, $expected, "basic table sorted by value ASC" );
+
+ $report->SortEntries( ChartOrderBy => 'value', ChartOrder => 'DESC' );
+ %table = $report->FormatTable( %columns );
+ @{$expected->{'tbody'}} = reverse @{$expected->{'tbody'}};
+ is_deeply( \%table, $expected, "basic table sorted by value DESC" );
+
+ $report->SortEntries( ChartOrderBy => 'value', ChartOrder => 'DESC', ChartLimit => 2 );
+ %table = $report->FormatTable( %columns );
+ pop @{$expected->{'tbody'}};
+ $expected->{'tfoot'}[0]{'even'} = 1;
+ $expected->{'tfoot'}[0]{'cells'}[1]{'value'} = 5;
+ is_deeply( \%table, $expected, "basic table sorted by value DESC with 2 items" );
+
+ $report->_DoSearch; # previous search removed an element
+ $report->SortEntries( ChartOrderBy => 'value', ChartOrder => 'DESC', ChartLimit => 2, ChartLimitType => 'Bottom' );
+ %table = $report->FormatTable( %columns );
+ $expected->{'tbody'} = [.
+ {
+ 'cells' => $resolved_cell,
+ 'even' => 1,
+ },
+ {
+ 'cells' => $new_cell,
+ 'even' => 0,
+ },
+ ];
+ $expected->{'tfoot'}[0]{'cells'}[1]{'value'} = 3;
+ is_deeply( \%table, $expected, "basic table sorted by value DESC with 2 bottom items" );
}

done_testing;

commit abd460f5d8180aec360b19e8a91cdd168a98eb10
Author: sunnavy <sunnavy@bestpractical.com>
Date: Wed Mar 27 20:16:23 2024 -0400

Support to sort/limit axis labels in search charts

Previously charts were always sorted by labels in ascending order, this
commit enhances it to support descending order too, as well as sorting by
values(i.e. asset/ticket/transaction count).

Limiting the number of results is also implemented in this commit, with
which users can easily get a more friendly chart that contains significant
data only.

diff --git a/lib/RT/Interface/Web.pm b/lib/RT/Interface/Web.pm
index 6fe866f97f..972c11250b 100644
--- a/lib/RT/Interface/Web.pm
+++ b/lib/RT/Interface/Web.pm
@@ -84,7 +84,7 @@ use RT::Interface::Web::ReportsRegistry;

our @SHORTENER_SEARCH_FIELDS
= qw/Class ObjectType BaseQuery Query Format RowsPerPage Order OrderBy ExtraQueryParams ResultPage/;
-our @SHORTENER_CHART_FIELDS = qw/Width Height ChartStyle GroupBy ChartFunction StackedGroupBy/;
+our @SHORTENER_CHART_FIELDS = qw/Width Height ChartStyle GroupBy ChartFunction StackedGroupBy ChartOrderBy ChartOrder ChartLimit ChartLimitType/;

=head2 SquishedCSS $style

diff --git a/lib/RT/Report.pm b/lib/RT/Report.pm
index 5e76fa29ed..4a99319d76 100644
--- a/lib/RT/Report.pm
+++ b/lib/RT/Report.pm
@@ -759,6 +759,13 @@ sub _FieldToFunction {

sub SortEntries {
my $self = shift;
+ my %args = (
+ ChartOrderBy => 'label',
+ ChartOrder => 'ASC',
+ ChartLimit => undef,
+ ChartLimitType => 'Top',
+ @_,
+ );

$self->_DoSearch if $self->{'must_redo_search'};
return unless $self->{'items'} && @{ $self->{'items'} };
@@ -772,16 +779,21 @@ sub SortEntries {
my @SORT_OPS;
my $by_multiple = sub ($$) {
for my $f ( @SORT_OPS ) {
- my $r = $f->($_[0], $_[1]);
+ my $r = uc $f->($args{ChartOrder} eq 'ASC' ? ( $_[0], $_[1] ) : ( $_[1], $_[0] ) );
return $r if $r;
}
};
+
my @data = map [$_], @{ $self->{'items'} };

- for ( my $i = 0; $i < @groups; $i++ ) {
- my $group_by = $groups[$i];
- my $idx = $i+1;
+ if ( $args{ChartOrderBy} eq 'value' && grep { $_ eq 'id' } $self->ColumnsList ) {
+ $_->[1] = $_->[0]->RawValue('id') for @data;
+ push @SORT_OPS, sub { $_[0][1] <=> $_[1][1] };
+ }

+ # Even if charts are sorted by value, it's still good to sort by labels next, to sort items with the same value.
+ for my $group_by ( @groups ) {
+ my $idx = @SORT_OPS + 1;
my $order = $group_by->{'META'}{Sort} || 'label';
my $method = $order =~ /label$/ ? 'LabelValue' : 'RawValue';

@@ -834,6 +846,15 @@ sub SortEntries {
map $_->[0],
sort $by_multiple @data
];
+
+ if ( $args{ChartLimit} && $args{ChartLimit} > 0 && @{ $self->{'items'} } > $args{ChartLimit} ) {
+ if ( $args{ChartLimitType} eq 'Top' ) {
+ @{ $self->{'items'} } = @{ $self->{'items'} }[ 0 .. $args{ChartLimit} - 1 ];
+ }
+ else {
+ @{ $self->{'items'} } = @{ $self->{'items'} }[ -$args{ChartLimit} .. -1 ];
+ }
+ }
}

sub PostProcessRecords {
diff --git a/share/html/Search/Chart b/share/html/Search/Chart
index 5b3a19afcc..235b2847c9 100644
--- a/share/html/Search/Chart
+++ b/share/html/Search/Chart
@@ -119,7 +119,7 @@ if ( $Cache and my $data = delete $session{'charts_cache'}{ $Cache } ) {
Function => \@ChartFunction,
);

- $report->SortEntries;
+ $report->SortEntries( map { $_ => $ARGS{$_} } grep { $ARGS{$_} } qw(ChartOrderBy ChartOrder ChartLimit ChartLimitType) );
}

my @data = ([],[]);
diff --git a/share/html/Search/Chart.html b/share/html/Search/Chart.html
index 14774cdb37..928656b0b1 100644
--- a/share/html/Search/Chart.html
+++ b/share/html/Search/Chart.html
@@ -56,13 +56,17 @@ my $default_value = {
GroupBy => [ $report->DefaultGroupBy ],
ChartStyle => 'bar+table+sql',
ChartFunction => ['COUNT'],
+ ChartOrderBy => 'label',
+ ChartOrder => 'ASC',
+ ChartLimit => '',
+ ChartLimitType => 'Top',
};

$m->callback( ARGSRef => \%ARGS, CallbackName => 'Initial' );

my $title = loc( "Grouped search results");

-my @search_fields = ( qw(Query GroupBy StackedGroupBy ChartStyle ChartFunction Width Height Class ExtraQueryParams), grep $_, @ExtraQueryParams );
+my @search_fields = ( qw(Query GroupBy StackedGroupBy ChartStyle ChartFunction Width Height Class ExtraQueryParams ChartOrderBy ChartOrder ChartLimit ChartLimitType), grep $_, @ExtraQueryParams );
my $saved_search = $m->comp( '/Widgets/SavedSearch:new',
SearchType => 'Chart',
SearchFields => [@search_fields],
@@ -273,6 +277,40 @@ $m->callback( ARGSRef => \%ARGS, QueryArgsRef => \%query );
<input type="checkbox" id="ChartStyleIncludeSQL" name="ChartStyleIncludeSQL" class="custom-control-input" <% $query{ChartStyle} =~ /\bsql\b/ ? 'checked="checked"' : '' |n %>>
<label class="custom-control-label" for="ChartStyleIncludeSQL"><&|/l&>Include TicketSQL query</&></label>
</div>
+
+ <div class="form-row sorting">
+ <h5 class="titlebox-inner-heading mt-2 ml-2">X-Axis</h5>
+ </div>
+ <div class="form-row sorting">
+ <div class="label col-auto">
+ <&|/l&>Order by</&>:<span class="far fa-question-circle icon-helper" data-toggle="tooltip" data-placement="top" data-original-title="<&|/l&>Value only works for count calculations</&>"></span>
+ </div>
+ <div class="value col-auto">
+ <select name="ChartOrderBy" class="form-control selectpicker">
+ <option value="label" <% $query{ChartOrderBy} eq 'label' ? 'selected="selected"' : '' |n %>><&|/l&>Label</&></option>
+ <option value="value" <% $query{ChartOrderBy} eq 'value' ? 'selected="selected"' : '' |n %>><&|/l&>Value</&></option>
+ </select>
+ </div>
+ <div class="value col-auto">
+ <select name="ChartOrder" class="form-control selectpicker">
+ <option value="ASC" <% $query{ChartOrder} eq 'ASC' ? 'selected="selected"' : '' |n %>><&|/l&>Ascending</&></option>
+ <option value="DESC" <% $query{ChartOrder} eq 'DESC' ? 'selected="selected"' : '' |n %>><&|/l&>Descending</&></option>
+ </select>
+ </div>
+ </div>
+ <div class="form-row sorting">
+ <div class="label col-auto"><&|/l&>Limit chart to</&>:</div>
+ <div class="value col-auto">
+ <select name="ChartLimitType" class="form-control selectpicker">
+ <option value="Top" <% $query{ChartLimitType} eq 'Top' ? 'selected="selected"' : '' |n %>><&|/l&>Top</&></option>
+ <option value="Bottom" <% $query{ChartLimitType} eq 'Bottom' ? 'selected="selected"' : '' |n %>><&|/l&>Bottom</&></option>
+ </select>
+ </div>
+ <div class="value col-auto">
+ <input name="ChartLimit" size="3" class="form-control" value="<% $query{ChartLimit} // '' %>" />
+ </div>
+ <span class="label col-auto"><&|/l&>items</&></span>
+ </div>
</&>

<script type="text/javascript">
diff --git a/share/html/Search/Elements/Chart b/share/html/Search/Elements/Chart
index 7079551846..0afd3359f7 100644
--- a/share/html/Search/Elements/Chart
+++ b/share/html/Search/Elements/Chart
@@ -65,7 +65,7 @@ my %columns = $report->SetupGroupings(
Function => \@ChartFunction,
);

-$report->SortEntries;
+$report->SortEntries(map { $_ => $ARGS{$_} } grep { $ARGS{$_} } qw(ChartOrderBy ChartOrder ChartLimit ChartLimitType));

my $query_string = $m->comp('/Elements/QueryString', %ARGS, GroupBy => \@GroupBy );

diff --git a/share/html/Search/JSChart b/share/html/Search/JSChart
index dc72c4cddf..d0d7d34083 100644
--- a/share/html/Search/JSChart
+++ b/share/html/Search/JSChart
@@ -208,7 +208,7 @@ if ( $Cache and my $data = delete $session{'charts_cache'}{ $Cache } ) {
Function => \@ChartFunction,
);

- $report->SortEntries;
+ $report->SortEntries( map { $_ => $ARGS{$_} } grep { $ARGS{$_} } qw(ChartOrderBy ChartOrder ChartLimit ChartLimitType) );
}

my @data = ([],[]);
diff --git a/share/static/css/elevator-light/boxes.css b/share/static/css/elevator-light/boxes.css
index f1bc9c61e2..bb734688d8 100644
--- a/share/static/css/elevator-light/boxes.css
+++ b/share/static/css/elevator-light/boxes.css
@@ -60,6 +60,10 @@
border-radius: 0 0.25em 0 0.25em;
}

+.titlebox-inner-heading {
+ color: #3858a3;
+}
+
.titlebox:hover .titlebox-title .right a,
.titlebox:active .titlebox-title .right a {
color: #000

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


hooks/post-receive
--
rt
_______________________________________________
rt-commit mailing list
rt-commit@lists.bestpractical.com
https://lists.bestpractical.com/mailman/listinfo/rt-commit