Mailing List Archive

rt branch 5.0/search-chart-sort-axis-labels created. rt-5.0.5-185-g3b3303c351
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 3b3303c35108f11e1a110ec7a676ed44c96b3477 (commit)

- Log -----------------------------------------------------------------
commit 3b3303c35108f11e1a110ec7a676ed44c96b3477
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 4a29bba916b9e44fc7f5a2efc172c1b0859021b4
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..5fe1156ccb 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,38 @@ $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 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