summaryrefslogtreecommitdiff
path: root/rt/share/html/Search
diff options
context:
space:
mode:
Diffstat (limited to 'rt/share/html/Search')
-rw-r--r--rt/share/html/Search/Build.html26
-rwxr-xr-xrt/share/html/Search/Bulk.html226
-rw-r--r--rt/share/html/Search/Chart463
-rw-r--r--rt/share/html/Search/Chart.html140
-rw-r--r--rt/share/html/Search/Elements/BuildFormatString13
-rw-r--r--rt/share/html/Search/Elements/Chart105
-rw-r--r--rt/share/html/Search/Elements/ChartTable119
-rw-r--r--rt/share/html/Search/Elements/ConditionRow8
-rw-r--r--rt/share/html/Search/Elements/EditFormat13
-rw-r--r--rt/share/html/Search/Elements/EditSearches20
-rw-r--r--rt/share/html/Search/Elements/EditSort2
-rw-r--r--rt/share/html/Search/Elements/PickBasics26
-rw-r--r--rt/share/html/Search/Elements/PickCFs21
-rw-r--r--rt/share/html/Search/Elements/PickCriteria2
-rw-r--r--rt/share/html/Search/Elements/PickObjectCFs76
-rw-r--r--rt/share/html/Search/Elements/PickTicketCFs5
-rw-r--r--rt/share/html/Search/Elements/ResultsRSSView122
-rw-r--r--rt/share/html/Search/Elements/SearchPrivacy6
-rw-r--r--rt/share/html/Search/Elements/SearchesForObject4
-rw-r--r--rt/share/html/Search/Elements/SelectAndOr4
-rw-r--r--rt/share/html/Search/Elements/SelectChartFunction79
-rw-r--r--rt/share/html/Search/Elements/SelectChartType3
-rw-r--r--rt/share/html/Search/Elements/SelectGroup2
-rw-r--r--rt/share/html/Search/Elements/SelectGroupBy26
-rw-r--r--rt/share/html/Search/Elements/SelectLinks19
-rw-r--r--rt/share/html/Search/Elements/SelectPersonType4
-rwxr-xr-xrt/share/html/Search/Results.html24
-rw-r--r--rt/share/html/Search/Results.tsv66
-rw-r--r--rt/share/html/Search/Simple.html4
-rw-r--r--rt/share/html/Search/index.html (renamed from rt/share/html/Search/Graph.html)6
30 files changed, 1031 insertions, 603 deletions
diff --git a/rt/share/html/Search/Build.html b/rt/share/html/Search/Build.html
index eea5f81..8ce404a 100644
--- a/rt/share/html/Search/Build.html
+++ b/rt/share/html/Search/Build.html
@@ -53,7 +53,7 @@
%# Build/Edit.html (Advanced).)
%#
%# After doing some stuff with default arguments and saved searches, the ParseQuery
-%# function (which is similar to, but not the same as, _parser in lib/RT/Tickets_SQL.pm)
+%# function (which is similar to, but not the same as, _parser in lib/RT/Tickets.pm)
%# converts the Query into a RT::Interface::Web::QueryBuilder::Tree. This mason file
%# then adds stuff to or modifies the tree based on the actions that had been requested
%# by clicking buttons. It then calls GetQueryAndOptionList on the tree to generate
@@ -140,7 +140,11 @@ if ( $NewQuery ) {
my $current = $session{'CurrentSearchHash'};
my $prefs = $session{'CurrentUser'}->UserObj->Preferences("SearchDisplay") || {};
- my $default = { Query => '', Format => '', OrderBy => 'id', Order => 'ASC', RowsPerPage => 50 };
+ my $default = { Query => '',
+ Format => '',
+ OrderBy => RT->Config->Get('DefaultSearchResultOrderBy'),
+ Order => RT->Config->Get('DefaultSearchResultOrder'),
+ RowsPerPage => 50 };
for( qw(Query Format OrderBy Order RowsPerPage) ) {
$query{$_} = $current->{$_} unless defined $query{$_};
@@ -188,9 +192,15 @@ my @options = $tree->GetDisplayedNodes;
my @current_values = grep defined, @options[@clauses];
my @new_values = ();
+my $cf_field_names =
+ join "|",
+ map quotemeta,
+ grep { $RT::Tickets::FIELD_METADATA{$_}->[0] eq 'CUSTOMFIELD' }
+ sort keys %RT::Tickets::FIELD_METADATA;
+
# Try to find if we're adding a clause
foreach my $arg ( keys %ARGS ) {
- next unless $arg =~ m/^ValueOf([\w\.]+|'\w*CF\.\{.*?\}')$/
+ next unless $arg =~ m/^ValueOf(\w+|($cf_field_names).\{.*?\})$/
&& ( ref $ARGS{$arg} eq "ARRAY"
? grep $_ ne '', @{ $ARGS{$arg} }
: $ARGS{$arg} ne '' );
@@ -220,6 +230,7 @@ foreach my $arg ( keys %ARGS ) {
for ( my $i = 0; $i < @ops; $i++ ) {
my ( $op, $value ) = ( $ops[$i], $values[$i] );
next if !defined $value || $value eq '';
+ my $rawvalue = $value;
if ( $value =~ /^NULL$/i && $op =~ /=/ ) {
if ( $op eq '=' ) {
@@ -234,16 +245,15 @@ foreach my $arg ( keys %ARGS ) {
$value = "'$value'";
}
- if ($keyword =~ /^'(\w*CF)\.\{(.*)\}'/) {
- my ($field, $cf) = ($1, $2);
- $cf =~ s/(['\\])/\\$1/g;
- $keyword = "'$field.{$cf}'";
+ if ($keyword =~ s/(['\\])/\\$1/g or $keyword =~ /[^{}\w\.]/) {
+ $keyword = "'$keyword'";
}
my $clause = {
Key => $keyword,
Op => $op,
- Value => $value
+ Value => $value,
+ RawValue => $rawvalue,
};
push @new_values, RT::Interface::Web::QueryBuilder::Tree->new($clause);
diff --git a/rt/share/html/Search/Bulk.html b/rt/share/html/Search/Bulk.html
index 89e850b..90fec68 100755
--- a/rt/share/html/Search/Bulk.html
+++ b/rt/share/html/Search/Bulk.html
@@ -50,13 +50,13 @@
<& /Elements/ListActions, actions => \@results &>
<form method="post" action="<% RT->Config->Get('WebPath') %>/Search/Bulk.html" enctype="multipart/form-data" name="BulkUpdate" id="BulkUpdate">
-% foreach my $var (qw(Query Format OrderBy Order Rows Page SavedChartSearchId)) {
+% foreach my $var (qw(Query Format OrderBy Order Rows Page SavedSearchId SavedChartSearchId Token)) {
<input type="hidden" class="hidden" name="<%$var%>" value="<%$ARGS{$var} || ''%>" />
%}
<& /Elements/CollectionList,
Query => $Query,
- DisplayFormat => $Format,
- Format => $ARGS{'Format'},
+ DisplayFormat => $DisplayFormat,
+ Format => $Format,
Verbatim => 1,
AllowSorting => 1,
OrderBy => $OrderBy,
@@ -71,7 +71,7 @@
<hr />
-<& /Elements/Submit, Label => loc('Update'), CheckboxNameRegex => '/^UpdateTicket\d+$/', CheckAll => 1, ClearAll => 1 &>
+<& /Elements/Submit, Label => loc('Update'), CheckboxNameRegex => '/^UpdateTicket(All)?$/', CheckAll => 1, ClearAll => 1 &>
<br />
<&|/Widgets/TitleBox, title => $title &>
<table>
@@ -80,20 +80,20 @@
<table>
<tr><td class="label"> <&|/l&>Make Owner</&>: </td>
<td class="value"> <& /Elements/SelectOwner, Name => "Owner", Default => $ARGS{Owner} || '' &>
-(<input type="checkbox" class="checkbox" name="ForceOwnerChange"
- <% $ARGS{ForceOwnerChange} ? 'checked="checked"' : '' %> /> <&|/l&>Force change</&>) </td></tr>
+<label>(<input type="checkbox" class="checkbox" name="ForceOwnerChange"
+<% $ARGS{ForceOwnerChange} ? 'checked="checked"' : '' %> /> <&|/l&>Force change</&>)</label></td></tr>
<tr><td class="label"> <&|/l&>Add Requestor</&>: </td>
-<td class="value"> <input name="AddRequestor" size="20" value="<% $ARGS{AddRequestor} || '' %>" /> </td></tr>
+<td class="value"> <& /Elements/EmailInput, Name => "AddRequestor", Size=> 20, Default => $ARGS{AddRequestor} &> </td></tr>
<tr><td class="label"> <&|/l&>Remove Requestor</&>: </td>
-<td class="value"> <input name="DeleteRequestor" size="20" value="<% $ARGS{DeleteRequestor} || '' %>"/> </td></tr>
+<td class="value"> <& /Elements/EmailInput, Name => "DeleteRequestor", Size=> 20, Default => $ARGS{DeleteRequestor} &> </td></tr>
<tr><td class="label"> <&|/l&>Add Cc</&>: </td>
-<td class="value"> <input name="AddCc" size="20" value="<% $ARGS{AddCc} || '' %>" /> </td></tr>
+<td class="value"> <& /Elements/EmailInput, Name => "AddCc", Size=> 20, Default => $ARGS{AddCc} &> </td></tr>
<tr><td class="label"> <&|/l&>Remove Cc</&>: </td>
-<td class="value"> <input name="DeleteCc" size="20" value="<% $ARGS{DeleteCc} || '' %>" /> </td></tr>
+<td class="value"> <& /Elements/EmailInput, Name => "DeleteCc", Size=> 20, Default => $ARGS{DeleteCc} &> </td></tr>
<tr><td class="label"> <&|/l&>Add AdminCc</&>: </td>
-<td class="value"> <input name="AddAdminCc" size="20" value="<% $ARGS{AddAdminCc} || '' %>" /> </td></tr>
+<td class="value"> <& /Elements/EmailInput, Name => "AddAdminCc", Size=> 20, Default => $ARGS{AddAdminCc} &> </td></tr>
<tr><td class="label"> <&|/l&>Remove AdminCc</&>: </td>
-<td class="value"> <input name="DeleteAdminCc" size="20" value="<% $ARGS{DeleteAdminCc} || '' %>" /> </td></tr>
+<td class="value"> <& /Elements/EmailInput, Name => "DeleteAdminCc", Size=> 20, Default => $ARGS{DeleteAdminCc} &> </td></tr>
</table>
</td>
<td valign="top">
@@ -111,7 +111,7 @@
<tr><td class="label"> <&|/l&>Make queue</&>: </td>
<td class="value"> <& /Elements/SelectQueue, Name => "Queue", Default => $ARGS{Queue} &> </td></tr>
<tr><td class="label"> <&|/l&>Make Status</&>: </td>
-<td class="value"> <& /Elements/SelectStatus, Name => "Status", Default => $ARGS{Status}, Queues => $seen_queues &> </td></tr>
+<td class="value"> <& /Ticket/Elements/SelectStatus, Name => "Status", Default => $ARGS{Status}, Queues => $seen_queues &> </td></tr>
<tr><td class="label"> <&|/l&>Make date Starts</&>: </td>
<td class="value"> <& /Elements/SelectDate, Name => "Starts_Date", Default => $ARGS{Starts_Date} || '' &> </td></tr>
<tr><td class="label"> <&|/l&>Make date Started</&>: </td>
@@ -120,8 +120,6 @@
<td class="value"> <& /Elements/SelectDate, Name => "Told_Date", Default => $ARGS{Told_Date} || '' &> </td></tr>
<tr><td class="label"> <&|/l&>Make date Due</&>: </td>
<td class="value"> <& /Elements/SelectDate, Name => "Due_Date", Default => $ARGS{Due_Date} || '' &> </td></tr>
-<tr><td class="label"> <&|/l&>Make date Resolved</&>: </td>
-<td class="value"> <& /Elements/SelectDate, Name => "Resolved_Date", Default => $ARGS{Resolved_Date} || '' &> </td></tr>
</table>
</td>
@@ -131,28 +129,34 @@
<&| /Widgets/TitleBox, title => loc('Add comments or replies to selected tickets') &>
<table>
<tr><td align="right"><&|/l&>Update Type</&>:</td>
-<td><select name="UpdateType">
+<td><select name="UpdateType" id="UpdateType">
<option value="private" <% $ARGS{UpdateType} && $ARGS{UpdateType} eq 'private' ? 'selected="selected"' : '' %> ><&|/l&>Comments (Not sent to requestors)</&></option>
<option value="response" <% $ARGS{UpdateType} && $ARGS{UpdateType} eq 'response' ? 'selected="selected"' : '' %>><&|/l&>Reply to requestors</&></option>
</select>
</td></tr>
-<tr><td align="right"><&|/l&>Subject</&>:</td><td> <input name="UpdateSubject"
-size="60" value="<% $ARGS{UpdateSubject} || "" %>" /></td></tr>
+<tr>
+ <td align="right"><&|/l&>Subject</&>:</td>
+ <td>
+ <input name="UpdateSubject" size="60" value="<% $ARGS{UpdateSubject} || "" %>" />
+% $m->callback( %ARGS, CallbackName => 'AfterUpdateSubject' );
+ </td>
+</tr>
+% $m->callback( CallbackName => 'BeforeTransactionCustomFields', CustomFields => $TxnCFs );
% while (my $CF = $TxnCFs->Next()) {
<tr>
<td align="right"><% $CF->Name %>:</td>
-<td><& /Elements/EditCustomField,
- CustomField => $CF,
- NamePrefix => "Object-RT::Transaction--CustomField-",
- Default => $ARGS{"Object-RT::Transaction--CustomField-" . $CF->id . '-Values'} ||
- $ARGS{"Object-RT::Transaction--CustomField-" . $CF->id . '-Value'},
+<td><& /Elements/EditCustomField,
+ CustomField => $CF,
+ Object => RT::Transaction->new( $session{'CurrentUser'} ),
&><em><% $CF->FriendlyType %></em></td>
</td></tr>
% } # end if while
<& /Ticket/Elements/AddAttachments, %ARGS &>
- <tr><td class="labeltop"><&|/l&>Message</&>:</td><td>
+ <tr><td class="labeltop"><&|/l&>Message</&>:</td>
+ <td class="messagebox-container action-<% $ARGS{UpdateType} || 'private' %>">
+% $m->callback( %ARGS, CallbackName => 'BeforeMessageBox' );
%# Currently, bulk update always starts with Comment not Reply selected, so we check this unconditionally
% my $IncludeSignature = RT->Config->Get('MessageBoxIncludeSignatureOnComment');
<& /Elements/MessageBox, Name => "UpdateContent",
@@ -168,59 +172,22 @@ size="60" value="<% $ARGS{UpdateSubject} || "" %>" /></td></tr>
my $cfs = RT::CustomFields->new($session{'CurrentUser'});
$cfs->LimitToGlobal();
$cfs->LimitToQueue($_) for keys %$seen_queues;
+$cfs->SetContextObject( values %$seen_queues ) if keys %$seen_queues == 1;
</%perl>
-% if ($cfs->Count) {
-<&|/Widgets/TitleBox, title => loc('Edit Custom Fields'), color => "#336633"&>
-<table>
-<tr>
-<th><&|/l&>Name</&></th>
-<th><&|/l&>Add values</&></th>
-<th><&|/l&>Delete values</&></th>
-</tr>
-% while (my $cf = $cfs->Next()) {
-<tr>
-<td class="label"><% loc($cf->Name) %><br />
-<em>(<%$cf->FriendlyType%>)</em></td>
-% my $rows = 5;
-% my $cf_id = $cf->id;
-% my @add = (NamePrefix => 'Bulk-Add-CustomField-', CustomField => $cf, Rows => $rows,
-% Multiple => ($cf->MaxValues ==1 ? 0 : 1) , Cols => 25,
-% Default => $ARGS{"Bulk-Add-CustomField-$cf_id-Values"} || $ARGS{"Bulk-Add-CustomField-$cf_id-Value"}, );
-% my @del = (NamePrefix => 'Bulk-Delete-CustomField-', CustomField => $cf,
-% Rows => $rows, Multiple => 1, Cols => 25,
-% Default => $ARGS{"Bulk-Delete-CustomField-$cf_id-Values"} || $ARGS{"Bulk-Delete-CustomField-$cf_id-Value"}, );
-% if ($cf->Type eq 'Select') {
-<td><& /Elements/EditCustomFieldSelect, @add &></td>
-<td><& /Elements/EditCustomFieldSelect, @del &></td>
-% } elsif ($cf->Type eq 'Combobox') {
-<td><& /Elements/EditCustomFieldCombobox, @add &></td>
-<td><& /Elements/EditCustomFieldCombobox, @del &></td>
-% } elsif ($cf->Type eq 'Freeform') {
-<td><& /Elements/EditCustomFieldFreeform, @add &></td>
-<td><& /Elements/EditCustomFieldFreeform, @del &></td>
-% } elsif ($cf->Type eq 'Text') {
-<td><& /Elements/EditCustomFieldText, @add &></td>
-<td>&nbsp;</td>
-% } elsif ($cf->Type eq 'Date') {
-<td><& /Elements/EditCustomFieldDate, @add, Default => undef &></td>
-<td><& /Elements/EditCustomFieldDate, @del, Default => undef &></td>
-% } elsif ($cf->Type eq 'DateTime') {
-% # Pass datemanip format to prevent another tz date conversion
-<td><& /Elements/EditCustomFieldDateTime, @add, Default => undef, Format => 'datemanip' &></td>
-<td><& /Elements/EditCustomFieldDateTime, @del, Default => undef, Format => 'datemanip' &></td>
-% } else {
-% $RT::Logger->crit("Unknown CustomField type: " . $cf->Type);
-% }
-</tr>
-% }
-</table>
+% if ( $cfs->Count ) {
+<&|/Widgets/TitleBox, title => loc('Edit Custom Fields') &>
+<& /Elements/BulkCustomFields, $ARGS{'AddMoreAttach'} ? %ARGS : (), CustomFields => $cfs &>
</&>
% }
<&|/Widgets/TitleBox, title => loc('Edit Links'), color => "#336633"&>
<em><&|/l&>Enter tickets or URIs to link tickets to. Separate multiple entries with spaces.</&></em><br />
-<& /Ticket/Elements/BulkLinks, Tickets => $Tickets, $ARGS{'AddMoreAttach'} ? %ARGS : () &>
+<& /Elements/BulkLinks, Collection => $Tickets, $ARGS{'AddMoreAttach'} ? %ARGS : () &>
+</&>
+
+<&| /Widgets/TitleBox, title => loc('Merge'), color => '#336633' &>
+<& /Ticket/Elements/EditMerge, Tickets => $Tickets, %ARGS &>
</&>
<& /Elements/Submit, Label => loc('Update') &>
@@ -248,7 +215,10 @@ $Page ||= 1;
$Format ||= RT->Config->Get('DefaultSearchResultFormat');
# inject _CHECKBOX to the first field.
-$Format =~ s/'?([^']+)'?,/'___CHECKBOX__$1',/; #'
+my $DisplayFormat = "'__CheckBox.{UpdateTicket}__',". $Format;
+$DisplayFormat =~ s/\s*,\s*('?__NEWLINE__'?)/,$1,''/gi;
+
+$DECODED_ARGS->{'UpdateTicketAll'} = 1 unless @UpdateTicket;
my $Tickets = RT::Tickets->new( $session{'CurrentUser'} );
$Tickets->FromSQL($Query);
@@ -276,7 +246,8 @@ Abort( loc("No search to operate on.") ) unless ($Tickets);
my $fields = {};
my $seen_queues = {};
while ( my $ticket = $Tickets->Next ) {
- next if $seen_queues->{ $ticket->Queue }++;
+ next if $seen_queues->{ $ticket->Queue };
+ $seen_queues->{ $ticket->Queue } ||= $ticket->QueueObj;
my $custom_fields = $ticket->CustomFields;
while ( my $field = $custom_fields->Next ) {
@@ -289,13 +260,6 @@ my @linkresults;
$Tickets->RedoSearch();
-# pull out the labels for any custom fields we want to update
-
-my $cf_del_keys;
-@$cf_del_keys = grep { /^Bulk-Delete-CustomField/ } keys %ARGS;
-my $cf_add_keys;
-@$cf_add_keys = grep { /^Bulk-Add-CustomField/ } keys %ARGS;
-
if ( defined($ARGS{'Priority'})
and ($ARGS{'Priority-Mode'} || '') eq 'relative' ) {
# magic in Ticket::SetPriority
@@ -304,19 +268,19 @@ if ( defined($ARGS{'Priority'})
delete $ARGS{'Priority-Mode'};
unless ( $ARGS{'AddMoreAttach'} ) {
- # Add session attachments if any to be processed by ProcessUpdateMessage
- $ARGS{'UpdateAttachments'} = $session{'Attachments'} if ( $session{'Attachments'} );
while ( my $Ticket = $Tickets->Next ) {
- next unless ( $ARGS{ "UpdateTicket" . $Ticket->Id } );
+ my $tid = $Ticket->id;
+ next unless grep $tid == $_, @UpdateTicket;
#Update the links
$ARGS{'id'} = $Ticket->id;
my @updateresults = ProcessUpdateMessage(
- TicketObj => $Ticket,
- ARGSRef => \%ARGS,
- );
+ TicketObj => $Ticket,
+ ARGSRef => \%ARGS,
+ KeepAttachments => 1,
+ );
#Update the basics.
my @basicresults =
@@ -328,86 +292,11 @@ unless ( $ARGS{'AddMoreAttach'} ) {
my @watchresults =
ProcessTicketWatchers( TicketObj => $Ticket, ARGSRef => \%ARGS );
- foreach my $type (qw(MergeInto DependsOn MemberOf RefersTo)) {
- $ARGS{ $Ticket->id . "-" . $type } = $ARGS{"Ticket-$type"};
- $ARGS{ $type . "-" . $Ticket->id } = $ARGS{"$type-Ticket"};
- }
- @linkresults =
- ProcessTicketLinks( TicketObj => $Ticket, ARGSRef => \%ARGS );
- foreach my $type (qw(MergeInto DependsOn MemberOf RefersTo)) {
- delete $ARGS{ $type . "-" . $Ticket->id };
- delete $ARGS{ $Ticket->id . "-" . $type };
- }
-
- my @cfresults;
-
- foreach my $list ( $cf_add_keys, $cf_del_keys ) {
- next unless $list->[0];
-
-
- my $op;
- if ( $list->[0] =~ /Add/ ) {
- $op = 'add';
-
- }
- elsif ( $list->[0] =~ /Del/ ) {
- $op = 'del';
- }
- else {
- $RT::Logger->crit(
- "Got an op that was neither add nor delete. can never happen"
- . $list->[0] );
- last;
- }
-
- foreach my $key (@$list) {
- my ( $cfid, $cf );
- next if $key =~ /CustomField-(\d+)-Category$/;
- if ( $key =~ /CustomField-(\d+)-/ ) {
- $cfid = $1;
- $cf = RT::CustomField->new( $session{'CurrentUser'} );
- $cf->Load($cfid);
- }
- else {next}
- my @values =
- ref( $ARGS{$key} ) eq 'ARRAY'
- ? @{ $ARGS{$key} }
- : ( $ARGS{$key} );
- map { s/(\r\n|\r)/\n/g; } @values; # fix the newlines
- # now break the multiline values into multivalues
- @values = map { split( /\n/, $_ ) } @values
- unless ( $cf->SingleValue );
-
- my $current_values = $Ticket->CustomFieldValues($cfid);
-
- if ( $cf->Type eq 'DateTime' || $cf->Type eq 'Date' ){
- # Clear out empty string submissions to avoid
- # Not set changed to Not set
- @values = grep length, @values;
- }
-
- foreach my $value (@values) {
-
- if ( $op eq 'del' ) {
- if ( my $entry = $current_values->HasEntry($value) ) {
- my ( $id, $msg ) = $Ticket->DeleteCustomFieldValue(
- Field => $cfid,
- ValueId => $entry->id,
- );
- push @cfresults, $msg;
- }
- }
-
- elsif ( $op eq 'add' && !$current_values->HasEntry($value) ) {
- my ( $id, $msg ) = $Ticket->AddCustomFieldValue(
- Field => $cfid,
- Value => $value
- );
- push @cfresults, $msg;
- }
- }
- }
- }
+ @linkresults =
+ ProcessTicketLinks( TicketObj => $Ticket, TicketId => 'Ticket', ARGSRef => \%ARGS );
+
+ my @cfresults = ProcessRecordBulkCustomFields( RecordObj => $Ticket, ARGSRef => \%ARGS );
+
my @statusresults =
ProcessTicketStatus( TicketObj => $Ticket, ARGSRef => \%ARGS );
@@ -426,8 +315,7 @@ unless ( $ARGS{'AddMoreAttach'} ) {
@results = ( @results, @tempresults );
}
- # Cleanup WebUI
- delete $session{'Attachments'};
+ delete $session{'Attachments'}{ $ARGS{'Token'} };
$Tickets->RedoSearch();
}
@@ -435,6 +323,7 @@ unless ( $ARGS{'AddMoreAttach'} ) {
my $TxnCFs = RT::CustomFields->new( $session{CurrentUser} );
$TxnCFs->LimitToLookupType( RT::Transaction->CustomFieldLookupType );
$TxnCFs->LimitToGlobalOrObjectId( keys %$seen_queues );
+$TxnCFs->SetContextObject( values %$seen_queues ) if keys %$seen_queues == 1;
</%INIT>
<%args>
@@ -445,6 +334,5 @@ $RowsPerPage => undef
$Order => 'ASC'
$OrderBy => 'id'
$Query => undef
-$SavedSearchId => undef
-$SavedChartSearchId => undef
+@UpdateTicket => ()
</%args>
diff --git a/rt/share/html/Search/Chart b/rt/share/html/Search/Chart
index 2a28d62..881a3d6 100644
--- a/rt/share/html/Search/Chart
+++ b/rt/share/html/Search/Chart
@@ -46,132 +46,403 @@
%#
%# END BPS TAGGED BLOCK }}}
<%args>
+$Cache => undef
$Query => "id > 0"
-$PrimaryGroupBy => 'Queue'
-$ChartStyle => 'bar'
+@GroupBy => ()
+$ChartStyle => 'bar+table+sql'
+@ChartFunction => 'COUNT'
+$Width => undef
+$Height => undef
</%args>
<%init>
-my $chart_class;
use GD;
use GD::Text;
-if ($ChartStyle eq 'pie') {
- require GD::Graph::pie;
- $chart_class = "GD::Graph::pie";
-} else {
- require GD::Graph::bars;
- $chart_class = "GD::Graph::bars";
-}
+my %font_config = RT->Config->Get('ChartFont');
+my $font = $font_config{ $session{CurrentUser}->UserObj->Lang || '' }
+ || $font_config{'others'};
+
+s/\D//g for grep defined, $Width, $Height;
+$Width ||= 600;
+$Height ||= ($ChartStyle =~ /\bpie\b/ ? $Width : 400);
+$Height = $Width if $ChartStyle =~ /\bpie\b/;
+
+my $plot_error = sub {
+ my $text = shift;
+ my ($plot, $error);
+
+ my $create_plot = sub {
+ my ($width, $height) = @_;
+
+ my $plot = GD::Image->new($width => $height);
+ $plot->colorAllocate(255, 255, 255); # background
+ my $black = $plot->colorAllocate(0, 0, 0);
+
+ require GD::Text::Wrap;
+ my $error = GD::Text::Wrap->new($plot,
+ color => $black,
+ text => $text,
+ align => "left",
+ width => $width - 20,
+ preserve_nl => 1,
+ );
+ $error->set_font( $font, 16 );
+ return ($plot, $error);
+ };
+
+ ($plot, $error) = $create_plot->($Width, $Height);
+ my $text_height = ($error->get_bounds(0, 0))[3];
+
+ # GD requires us to replot it all with the new height
+ ($plot, $error) = $create_plot->($Width, $text_height + 20);
+
+ $error->draw(10, 10);
+ $m->comp( 'SELF:Plot', plot => $plot, %ARGS );
+};
use RT::Report::Tickets;
-my $tix = RT::Report::Tickets->new( $session{'CurrentUser'} );
-my %AllowedGroupings = reverse $tix->Groupings( Query => $Query );
-$PrimaryGroupBy = 'Queue' unless exists $AllowedGroupings{$PrimaryGroupBy};
-my ($count_name, $value_name) = $tix->SetupGroupings(
- Query => $Query, GroupBy => $PrimaryGroupBy,
-);
+my $report = RT::Report::Tickets->new( $session{'CurrentUser'} );
-my %class = (
- Queue => 'RT::Queue',
- Owner => 'RT::User',
- Creator => 'RT::User',
- LastUpdatedBy => 'RT::User',
-);
-my $class = $class{ $PrimaryGroupBy };
+my %columns;
+if ( $Cache and my $data = delete $session{'charts_cache'}{ $Cache } ) {
+ %columns = %{ $data->{'columns'} };
+ $report->Deserialize( $data->{'report'} );
+ $session{'i'}++;
+} else {
+ %columns = $report->SetupGroupings(
+ Query => $Query,
+ GroupBy => \@GroupBy,
+ Function => \@ChartFunction,
+ );
-my %data;
+ $report->SortEntries;
+}
+
+my @data = ([],[]);
my $max_value = 0;
+my $min_value;
my $max_key_length = 0;
-while ( my $entry = $tix->Next ) {
- my $key;
- if ( $class ) {
- my $q = $class->new( $session{'CurrentUser'} );
- $q->Load( $entry->LabelValue( $value_name ) );
- $key = $q->Name;
- }
- else {
- $key = $entry->LabelValue($value_name);
- }
- $key ||= '(no value)';
-
- my $value = $entry->__Value( $count_name );
- if ($chart_class eq 'GD::Graph::pie') {
- $key = loc($key) ." - ". $value;
- } else {
- $key = loc($key);
+while ( my $entry = $report->Next ) {
+ push @{ $data[0] }, [ map $entry->LabelValue( $_ ), @{ $columns{'Groups'} } ];
+
+ my @values;
+ foreach my $column ( @{ $columns{'Functions'} } ) {
+ my $v = $entry->RawValue( $column );
+ unless ( ref $v ) {
+ push @values, $v;
+ next;
+ }
+
+ my @subs = $report->FindImplementationCode(
+ $report->ColumnInfo( $column )->{'META'}{'SubValues'}
+ )->( $report );
+ push @values, map $v->{$_}, @subs;
}
- $data{ $key } = $value;
- $max_value = $value if $max_value < $value;
- $max_key_length = length $key if $max_key_length < length $key;
-}
-unless (keys %data) {
- $data{''} = 0;
+ my $i = 0;
+ push @{ $data[++$i] }, $_ foreach @values;
+
+ foreach my $v ( @values ) {
+ $max_value = $v if $max_value < $v;
+ $min_value = $v if !defined $min_value || $min_value > $v;
+ }
}
+$ChartStyle =~ s/\bpie\b/bar/ if @data > 2;
-my $chart = $chart_class->new( 600 => 400 );
-$chart->set( pie_height => 60 ) if $chart_class eq 'GD::Graph::pie';
-my %font_config = RT->Config->Get('ChartFont');
-my $font = $font_config{ $session{CurrentUser}->UserObj->Lang || '' }
- || $font_config{'others'};
-$chart->set_title_font( $font, 16 ) if $chart->can('set_title_font');
-$chart->set_legend_font( $font, 16 ) if $chart->can('set_legend_font');
-$chart->set_x_label_font( $font, 14 ) if $chart->can('set_x_label_font');
-$chart->set_y_label_font( $font, 14 ) if $chart->can('set_y_label_font');
-$chart->set_label_font( $font, 14 ) if $chart->can('set_label_font');
-$chart->set_x_axis_font( $font, 12 ) if $chart->can('set_x_axis_font');
-$chart->set_y_axis_font( $font, 12 ) if $chart->can('set_y_axis_font');
-$chart->set_values_font( $font, 12 ) if $chart->can('set_values_font');
-$chart->set_value_font( $font, 12 ) if $chart->can('set_value_font');
+my $chart_class;
+if ($ChartStyle =~ /\bpie\b/) {
+ require GD::Graph::pie;
+ $chart_class = "GD::Graph::pie";
+} else {
+ require GD::Graph::bars;
+ $chart_class = "GD::Graph::bars";
+}
# Pie charts don't like having no input, so we show a special image
# that indicates an error message. Because this is used in an <img>
# context, it can't be a simple error message. Without this check,
# the chart will just be a non-loading image.
-if ($tix->Count == 0) {
- my $plot = GD::Image->new(600 => 400);
- $plot->colorAllocate(255, 255, 255); # background
- my $black = $plot->colorAllocate(0, 0, 0);
-
- require GD::Text::Wrap;
- my $error = GD::Text::Wrap->new($plot,
- color => $black,
- text => loc("No tickets found."),
- );
- $error->set_font( $font, 16 );
- $error->draw(0, 0);
-
- $m->comp( 'SELF:Plot', plot => $plot, %ARGS );
+unless ( $report->Count ) {
+ return $plot_error->(loc("No tickets found."));
}
+my $chart = $chart_class->new( $Width => $Height );
+
+my %chart_options;
if ($chart_class eq "GD::Graph::bars") {
- my $count = keys %data;
- $chart->set(
- x_label => $tix->Label( $PrimaryGroupBy ),
- y_label => loc('Tickets'),
- show_values => 1,
+ my $count = @{ $data[0] };
+ $chart_options{'bar_spacing'} =
+ $count > 30 ? 1
+ : $count > 20 ? 2
+ : $count > 10 ? 3
+ : 5
+ ;
+ if ( my $code = $report->LabelValueCode( $columns{'Functions'}[0] ) ) {
+ my %info = %{ $report->ColumnInfo( $columns{'Functions'}[0] ) };
+ $chart_options{'values_format'} = $chart_options{'y_number_format'} = sub {
+ return $code->($report, %info, VALUE => shift );
+ };
+ }
+ $report->GotoFirstItem;
+
+ # normalize min/max values to graph boundaries
+ {
+ my $integer = 1;
+ $integer = 0 for grep $_ ne int $_, $min_value, $max_value;
+
+ $max_value *= $max_value > 0 ? 1.1 : 0.9
+ if $max_value;
+ $min_value *= $min_value > 0 ? 0.9 : 1.1
+ if $min_value;
+
+ if ($integer) {
+ $max_value = int($max_value + ($max_value > 0? 1 : 0) );
+ $min_value = int($min_value + ($min_value < 0? -1 : 0) );
+
+ my $span = abs($max_value - $min_value);
+ $max_value += 5 - ($span % 5);
+ }
+ $chart_options{'y_label_skip'} = 2;
+ $chart_options{'y_tick_number'} = 10;
+ }
+ my $text_size = sub {
+ my ($size, $text) = (@_);
+ my $font_handle = GD::Text::Align->new(
+ $chart->get('graph'), valign => 'top', 'halign' => 'center',
+ );
+ $font_handle->set_font($font, $size);
+ $font_handle->set_text($text);
+ return $font_handle;
+ };
+
+ my $fitter = sub {
+ my %args = @_;
+
+ foreach my $font_size ( @{$args{'sizes'}} ) {
+ my $line_height = $text_size->($font_size, 'Q')->get('height');
+
+ my $keyset_height = $line_height;
+ if ( ref $args{data}->[0] ) {
+ $keyset_height = $text_size->($font_size, join "\n", ('Q')x scalar @{ $args{data}->[0] })
+ ->get('height');
+ }
+
+ my $status = 1;
+ foreach my $e ( @{ $args{data} } ) {
+ $status = $args{'cb'}->(
+ element => $e,
+ size => $font_size,
+ line_height => $line_height,
+ keyset_height => $keyset_height,
+ );
+ last unless $status;
+ }
+ next unless $status;
+
+ return $font_size;
+ }
+ return 0;
+ };
+
+ # try to fit in labels on X axis values, aka key
+ {
+ # we have several labels layouts:
+ # 1) horizontal, one line per label
+ # 2) horizontal, multi-line - doesn't work, GD::Chart bug
+ # 3) vertical, one line
+ # 4) vertical, multi-line
+ my %can = (
+ 'horizontal, one line' => 1,
+ 'vertical, one line' => 1,
+ 'vertical, multi line' => @{$data[0][0]} > 1,
+ );
+
+ my $x_space_for_label = $Width*0.8/($count+1.5);
+ my $y_space_for_label = $Height*0.4;
+
+ my $found_solution = $fitter->(
+ sizes => [12,11,10],
+ data => $data[0],
+ cb => sub {
+ my %args = @_;
+
+ # if horizontal space doesn't allow us to fit one vertical line,
+ # then we need smaller font
+ return 0 if $args{'line_height'} > $x_space_for_label;
+
+ my $width = $text_size->( $args{'size'}, join ' - ', @{ $args{'element'} } )
+ ->get('width');
+
+ if ( $width > $x_space_for_label ) {
+ $can{'horizontal, one line'} = 0;
+ }
+ if ( $width > $y_space_for_label ) {
+ $can{'vertical, one line'} = 0;
+ }
+ if ( $args{'keyset_height'} >= $x_space_for_label ) {
+ $can{'vertical, multi line'} = 0;
+ }
+ if ( $can{'vertical, multi line'} ) {
+ my $width = $text_size->( $args{'size'}, join "\n", @{ $args{'element'} } )
+ ->get('width');
+ if ( $width > $y_space_for_label ) {
+ $can{'vertical, multi line'} = 0;
+ }
+ }
+ return 0 unless grep $_, values %can;
+ return 1;
+ },
+ );
+ if ( $found_solution ) {
+ $chart_options{'x_axis_font'} = [$font, $found_solution];
+
+ if ( $can{'horizontal, one line'} ) {
+ $chart_options{'x_labels_vertical'} = 0;
+ $_ = join ' - ', @$_ foreach @{$data[0]};
+ }
+ elsif ( $can{'vertical, multi line'} ) {
+ $chart_options{'x_labels_vertical'} = 1;
+ $_ = join "\n", @$_ foreach @{$data[0]};
+ }
+ else {
+ $chart_options{'x_labels_vertical'} = 1;
+ $_ = join " - ", @$_ foreach @{$data[0]};
+ }
+ }
+ else {
+ my $font_handle = $text_size->(10, 'Q');
+ my $line_height = $font_handle->get('height');
+ if ( $line_height > $x_space_for_label ) {
+ $Width *= $line_height/$x_space_for_label;
+ $Width = int( $Width+1 );
+ }
+
+ $_ = join " - ", @$_ foreach @{$data[0]};
+
+ my $max_text_width = 0;
+ foreach (@{$data[0]}) {
+ $font_handle->set_text($_);
+ my $width = $font_handle->get('width');
+ $max_text_width = $width if $width > $max_text_width;
+ }
+ if ( $max_text_width > $Height*0.4 ) {
+ $Height = int($max_text_width / 0.4 + 1);
+ }
+
+ $chart_options{'x_labels_vertical'} = 1;
+ $chart_options{'x_axis_font'} = [$font, 10];
+ }
+ }
+
+ # use the same size for y axis labels
+ {
+ $chart_options{'y_axis_font'} = $chart_options{'x_axis_font'};
+ }
+
+ # try to fit in values above bars
+ {
+ # 0.8 is guess, labels for ticks on Y axis can be wider
+ # 1.5 for paddings around bars that GD::Graph adds
+ my $x_space_for_label = $Width*0.8/($count*(@data - 1)+1.5);
+
+ my %can = (
+ 'horizontal, one line' => 1,
+ 'vertical, one line' => 1,
+ );
+
+ my %seen;
+ my $found_solution = $fitter->(
+ sizes => [ grep $_ <= $chart_options{'x_axis_font'}[1], 12, 11, 10, 9 ],
+ data => [ map {@$_} @data[1..(@data-1)] ],
+ cb => sub {
+ my %args = @_;
+
+ # if horizontal space doesn't allow us to fit one vertical line,
+ # then we need smaller font
+ return 0 if $args{'line_height'} > $x_space_for_label;
+
+ my $value = $args{'element'};
+ $value = $chart_options{'values_format'}->($value)
+ if $chart_options{'values_format'};
+ return 1 if $seen{$value}++;
+
+ my $width = $text_size->( $args{'size'}, $value )->get('width');
+ if ( $width > $x_space_for_label ) {
+ $can{'horizontal, one line'} = 0;
+ }
+ my $y_space_for_label = $Height * 0.6
+ *( 1 - ($args{'element'}-$min_value)/($max_value-$min_value) );
+ if ( $width > $y_space_for_label ) {
+ $can{'vertical, one line'} = 0;
+ }
+ return 0 unless grep $_, values %can;
+ return 1;
+ },
+ );
+ $chart_options{'show_values'} = 1;
+ $chart_options{'hide_overlapping_values'} = 1;
+ if ( $found_solution ) {
+ $chart_options{'values_font'} = [ $font, $found_solution ],
+ $chart_options{'values_space'} = 2;
+ $chart_options{'values_vertical'} =
+ $can{'horizontal, one line'} ? 0 : 1;
+ } else {
+ $chart_options{'values_font'} = [ $font, 9 ],
+ $chart_options{'values_space'} = 1;
+ $chart_options{'values_vertical'} = 1;
+ }
+ }
+
+ %chart_options = (
+ %chart_options,
+ x_label => join( ' - ', map $report->Label( $_ ), @{ $columns{'Groups'} } ),
x_label_position => 0.6,
+ y_label => $report->Label( $columns{'Functions'}[0] ),
y_label_position => 0.6,
- values_space => -1,
# use a top margin enough to display values over the top line if needed
t_margin => 18,
# the following line to make sure there's enough space for values to show
- y_max_value => 5*(int($max_value/5) + 2),
+ y_max_value => $max_value,
+ y_min_value => $min_value,
# if there're too many bars or at least one key is too long, use vertical
- x_labels_vertical => ( $count * $max_key_length > 60 ) ? 1 : 0,
- $count > 30 ? ( bar_spacing => 1 ) : ( $count > 20 ? ( bar_spacing => 2 ) :
- ( $count > 10 ? ( bar_spacing => 3 ) : ( bar_spacing => 5 ) )
- ),
+ bargroup_spacing => $chart_options{'bar_spacing'}*5,
);
}
+else {
+ my $i = 0;
+ while ( my $entry = $report->Next ) {
+ push @{ $data[0][$i++] }, $entry->LabelValue( $columns{'Functions'}[0] );
+ }
+ $_ = join ' - ', @$_ foreach @{$data[0]};
+}
+
+if ($chart->get('width') != $Width || $chart->get('height') != $Height ) {
+ $chart = $chart_class->new( $Width => $Height );
+}
-# refine values' colors, with both Color::Scheme's help and my own tweak
-$chart->{dclrs} = [
- '66cc66', 'ff6666', 'ffcc66', '663399',
- '3333cc', '339933', '993333', '996633',
- '33cc33', 'cc3333', 'cc9933', '6633cc',
-];
+%chart_options = (
+ '3d' => 0,
+ title_font => [ $font, 16 ],
+ legend_font => [ $font, 16 ],
+ x_label_font => [ $font, 14 ],
+ y_label_font => [ $font, 14 ],
+ label_font => [ $font, 14 ],
+ y_axis_font => [ $font, 12 ],
+ values_font => [ $font, 12 ],
+ value_font => [ $font, 12 ],
+ %chart_options,
+);
+
+foreach my $opt ( grep /_font$/, keys %chart_options ) {
+ my $v = delete $chart_options{$opt};
+ next unless my $can = $chart->can("set_$opt");
+
+ $can->($chart, @$v);
+}
+$chart->set(%chart_options) if keys %chart_options;
+
+$chart->{dclrs} = [ RT->Config->Get("ChartColors") ];
{
no warnings 'redefine';
@@ -182,8 +453,12 @@ $chart->{dclrs} = [
};
}
-my $plot = $chart->plot( [ [sort keys %data], [map $data{$_}, sort keys %data] ] ) or die $chart->error;
-$m->comp( 'SELF:Plot', plot => $plot, %ARGS );
+if (my $plot = eval { $chart->plot( \@data ) }) {
+ $m->comp( 'SELF:Plot', plot => $plot, %ARGS );
+} else {
+ my $error = join "\n", grep defined && length, $chart->error, $@;
+ $plot_error->(loc("Error plotting chart: [_1]", $error));
+}
</%init>
<%METHOD Plot>
diff --git a/rt/share/html/Search/Chart.html b/rt/share/html/Search/Chart.html
index 2cde513..e08616a 100644
--- a/rt/share/html/Search/Chart.html
+++ b/rt/share/html/Search/Chart.html
@@ -45,36 +45,23 @@
%# those contributions and any derivatives thereof.
%#
%# END BPS TAGGED BLOCK }}}
-<%args>
-$PrimaryGroupBy => 'Queue'
-$ChartStyle => 'bar'
-$Description => undef
-</%args>
<%init>
+my $default_value = {
+ Query => 'id > 0',
+ GroupBy => ['Status'],
+ ChartStyle => ['bar+table+sql'],
+ ChartFunction => ['COUNT'],
+};
+
$m->callback( ARGSRef => \%ARGS, CallbackName => 'Initial' );
-$ARGS{Query} ||= 'id > 0';
-
-# FIXME: should be factored with RT::Report::Tickets::Label :(
-my $PrimaryGroupByLabel;
-if ( $PrimaryGroupBy =~ /^(?:CF|CustomField)\.\{(.*)\}$/ ) {
- my $cf = $1;
- if ( $cf =~ /\D/ ) {
- $PrimaryGroupByLabel = loc( "custom field '[_1]'", $cf );
- } else {
- my $obj = RT::CustomField->new( $session{'CurrentUser'} );
- $obj->Load( $cf );
- $PrimaryGroupByLabel = loc( "custom field '[_1]'", $obj->Name );
- }
-} else {
- $PrimaryGroupByLabel = loc( $PrimaryGroupBy );
-}
-
-my $title = loc( "Search results grouped by [_1]", $PrimaryGroupByLabel );
+my $title = loc( "Grouped search results");
+my @search_fields = qw(Query GroupBy ChartStyle ChartFunction Width Height);
my $saved_search = $m->comp( '/Widgets/SavedSearch:new',
SearchType => 'Chart',
- SearchFields => [qw(Query PrimaryGroupBy ChartStyle)] );
+ SearchFields => [@search_fields],
+);
my @actions = $m->comp( '/Widgets/SavedSearch:process', args => \%ARGS, self => $saved_search );
@@ -113,7 +100,21 @@ my %query;
}
+foreach (@search_fields) {
+ if ( ref $default_value->{$_} ) {
+ $query{$_} = ref $ARGS{$_} ? $ARGS{$_} : [ $ARGS{$_} ];
+ $query{$_} = $default_value->{$_}
+ unless defined $query{$_} && defined $query{$_}[0];
+ }
+ else {
+ $query{$_} = ref $ARGS{$_} ? $ARGS{$_} : $ARGS{$_};
+ $query{$_} = $default_value->{$_}
+ unless defined $query{$_};
+ }
+}
+
$m->callback( ARGSRef => \%ARGS, QueryArgsRef => \%query );
+
</%init>
<& /Elements/Header, Title => $title &>
<& /Elements/Tabs, QueryArgs => \%query &>
@@ -127,15 +128,94 @@ $m->callback( ARGSRef => \%ARGS, QueryArgsRef => \%query );
<div class="chart-meta">
<div class="chart-type">
-<&| /Widgets/TitleBox, title => loc('Chart Properties')&>
-<form method="get" action="<%RT->Config->Get('WebPath')%>/Search/Chart.html">
-<input type="hidden" class="hidden" name="Query" value="<% $ARGS{Query} %>" />
+
+<form method="get" action="<% RT->Config->Get('WebPath') %>/Search/Chart.html">
+<input type="hidden" class="hidden" name="Query" value="<% $query{Query} %>" />
<input type="hidden" class="hidden" name="SavedChartSearchId" value="<% $saved_search->{SearchId} || 'new' %>" />
-<&|/l_unsafe, $m->scomp('Elements/SelectChartType', Name => 'ChartStyle', Default => $ChartStyle), $m->scomp('Elements/SelectGroupBy', Name => 'PrimaryGroupBy', Query => $ARGS{Query}, Default => $PrimaryGroupBy)
-&>[_1] chart by [_2]</&><input type="submit" class="button" value="<%loc('Update Chart')%>" />
-</form>
+<&| /Widgets/TitleBox, title => loc('Group by'), class => "chart-group-by" &>
+<fieldset><legend><% loc('Group tickets by') %></legend>
+<& Elements/SelectGroupBy,
+ Name => 'GroupBy',
+ Query => $query{Query},
+ Default => $query{'GroupBy'}[0],
+ &>
+</fieldset>
+<fieldset><legend><% loc('and then') %></legend>
+<& Elements/SelectGroupBy,
+ Name => 'GroupBy',
+ Query => $query{Query},
+ Default => $query{'GroupBy'}[1] // q{},
+ ShowEmpty => 1,
+ &>
+</fieldset>
+<fieldset><legend><% loc('and then') %></legend>
+<& Elements/SelectGroupBy,
+ Name => 'GroupBy',
+ Query => $query{Query},
+ Default => $query{'GroupBy'}[2] // q{},
+ ShowEmpty => 1,
+ &>
+</fieldset>
+</&>
+
+<&| /Widgets/TitleBox, title => loc("Calculate"), class => "chart-calculate" &>
+
+<fieldset><legend><% loc('Calculate values of') %></legend>
+<& Elements/SelectChartFunction, Default => $query{'ChartFunction'}[0] &>
+</fieldset>
+<fieldset><legend><% loc('and then') %></legend>
+<& Elements/SelectChartFunction, Default => $query{'ChartFunction'}[1] // q{}, ShowEmpty => 1 &>
+</fieldset>
+<fieldset><legend><% loc('and then') %></legend>
+<& Elements/SelectChartFunction, Default => $query{'ChartFunction'}[2] // q{}, ShowEmpty => 1 &>
+</fieldset>
+
</&>
+
+<&| /Widgets/TitleBox, title => loc('Picture'), class => "chart-picture" &>
+<input name="ChartStyle" type="hidden" value="<% $query{ChartStyle}[0] %>" />
+<label><% loc('Style') %>: <& Elements/SelectChartType, Default => $query{ChartStyle}[0] =~ /^(pie|bar|table)\b/ ? $1 : undef &></label>
+<span class="width">
+<label><% loc("Width") %>: <input type="text" name="Width" value="<% $query{'Width'} || q{} %>"> <% loc("px") %></label>
+</span>
+<span class="height">
+ &#x00d7;
+ <label><% loc("Height") %>: <input type="text" name="Height" value="<% $query{'Height'} || q{} %>"> <% loc("px") %></label>
+</span>
+<div class="include-table">
+ <input type="checkbox" name="ChartStyleIncludeTable" <% $query{ChartStyle}[0] =~ /\btable\b/ ? 'checked="checked"' : '' |n %>> <% loc('Include data table') %>
+</div>
+<div class="include-sql">
+ <input type="checkbox" name="ChartStyleIncludeSQL" <% $query{ChartStyle}[0] =~ /\bsql\b/ ? 'checked="checked"' : '' |n %>> <% loc('Include TicketSQL query') %>
+</div>
+</&>
+<script type="text/javascript">
+var updateChartStyle = function() {
+ var val = jQuery(".chart-picture [name=ChartType]").val();
+ if ( val != 'table' && jQuery(".chart-picture [name=ChartStyleIncludeTable]").is(':checked') ) {
+ val += '+table';
+ }
+ if ( jQuery(".chart-picture [name=ChartStyleIncludeSQL]").is(':checked') ) {
+ val += '+sql';
+ }
+ jQuery(".chart-picture [name=ChartStyle]").val(val);
+};
+jQuery(".chart-picture [name=ChartType]").change(function(){
+ var t = jQuery(this);
+ t.closest("form").find("[name=Height]").closest(".height").toggle( t.val() == 'bar' );
+ t.closest("form").find("[name=Width]").closest(".width").toggle( t.val() !== 'table' );
+ t.closest("form .chart-picture").find("div.include-table").toggle( t.val() !== 'table' );
+ updateChartStyle();
+}).change();
+
+jQuery(".chart-picture [name=ChartStyleIncludeTable]").change( updateChartStyle );
+jQuery(".chart-picture [name=ChartStyleIncludeSQL]").change( updateChartStyle );
+</script>
+
+<& /Elements/Submit, Label => loc('Update Chart'), Name => 'Update' &>
+</form>
+
</div>
<div class="saved-search">
<& /Widgets/SavedSearch:show, %ARGS, Action => 'Chart.html', self => $saved_search, Title => loc('Saved charts') &>
diff --git a/rt/share/html/Search/Elements/BuildFormatString b/rt/share/html/Search/Elements/BuildFormatString
index 9a3ba1e..10ac1af 100644
--- a/rt/share/html/Search/Elements/BuildFormatString
+++ b/rt/share/html/Search/Elements/BuildFormatString
@@ -115,10 +115,12 @@ $m->callback( CallbackOnce => 1, CallbackName => 'SetFieldsOnce', Fields => \@fi
my $CustomFields = RT::CustomFields->new( $session{'CurrentUser'});
foreach my $id (keys %queues) {
- # Gotta load up the $queue object, since queues get stored by name now. my $id
+ # Gotta load up the $queue object, since queues get stored by name now.
my $queue = RT::Queue->new($session{'CurrentUser'});
$queue->Load($id);
- $CustomFields->LimitToQueue($queue->Id) if $queue->Id;
+ next unless $queue->Id;
+ $CustomFields->LimitToQueue($queue->Id);
+ $CustomFields->SetContextObject( $queue ) if keys %queues == 1;
}
$CustomFields->LimitToGlobal;
@@ -140,8 +142,7 @@ foreach my $field (@format) {
if ( $RemoveCol ) {
# we do this regex match to avoid a non-numeric warning
- my ($index) = $CurrentDisplayColumns =~ /^(\d+)/;
- my $column = $seen[$index];
+ my ($index) = ($CurrentDisplayColumns // '') =~ /^(\d+)/;
if ( defined($index) ) {
delete $seen[$index];
my @temp = @seen;
@@ -206,7 +207,7 @@ elsif ( $AddCol ) {
}
}
elsif ( $ColUp ) {
- my $index = $CurrentDisplayColumns;
+ my ($index) = ($CurrentDisplayColumns // '') =~ /^(\d+)/;
if ( defined $index && ( $index - 1 ) >= 0 ) {
my $column = $seen[$index];
$seen[$index] = $seen[ $index - 1 ];
@@ -215,7 +216,7 @@ elsif ( $ColUp ) {
}
}
elsif ( $ColDown ) {
- my $index = $CurrentDisplayColumns;
+ my ($index) = ($CurrentDisplayColumns // '') =~ /^(\d+)/;
if ( defined $index && ( $index + 1 ) < scalar @seen ) {
my $column = $seen[$index];
$seen[$index] = $seen[ $index + 1 ];
diff --git a/rt/share/html/Search/Elements/Chart b/rt/share/html/Search/Elements/Chart
index 38c15f6..6285fac 100644
--- a/rt/share/html/Search/Elements/Chart
+++ b/rt/share/html/Search/Elements/Chart
@@ -47,107 +47,44 @@
%# END BPS TAGGED BLOCK }}}
<%args>
$Query => "id > 0"
-$PrimaryGroupBy => 'Queue'
-$ChartStyle => 'bar'
+@GroupBy => ()
+$ChartStyle => 'bar+table+sql'
+@ChartFunction => 'COUNT'
</%args>
<%init>
use RT::Report::Tickets;
-$PrimaryGroupBy ||= 'Queue'; # make sure PrimaryGroupBy is not undef
-my $tix = RT::Report::Tickets->new( $session{'CurrentUser'} );
-my %AllowedGroupings = reverse $tix->Groupings( Query => $Query );
-$PrimaryGroupBy = 'Queue' unless exists $AllowedGroupings{$PrimaryGroupBy};
-my ($count_name, $value_name) = $tix->SetupGroupings(
- Query => $Query, GroupBy => $PrimaryGroupBy,
-);
+my $report = RT::Report::Tickets->new( $session{'CurrentUser'} );
-my %class = (
- Queue => 'RT::Queue',
- Owner => 'RT::User',
- Creator => 'RT::User',
- LastUpdatedBy => 'RT::User',
+my %columns = $report->SetupGroupings(
+ Query => $Query,
+ GroupBy => \@GroupBy,
+ Function => \@ChartFunction,
);
-my $class = $class{ $PrimaryGroupBy };
-
-my (@keys, @values);
-while ( my $entry = $tix->Next ) {
- if ($class) {
- my $q = $class->new( $session{'CurrentUser'} );
- $q->Load( $entry->LabelValue( $value_name ) );
- push @keys, $q->Name;
- }
- else {
- push @keys, $entry->LabelValue( $value_name );
- }
- $keys[-1] ||= loc('(no value)');
- push @values, $entry->__Value( $count_name );
-}
-my %data;
-my %loc_keys;
-foreach my $key (@keys) { $data{$key} = shift @values; $loc_keys{$key} = loc($key); }
-my @sorted_keys = map { $loc_keys{$_}} sort { $loc_keys{$a} cmp $loc_keys{$b} } keys %loc_keys;
-my @sorted_values = map { $data{$_}} sort { $loc_keys{$a} cmp $loc_keys{$b} } keys %loc_keys;
-my $query_string = $m->comp('/Elements/QueryString', %ARGS);
+$report->SortEntries;
-my ($i,$total);
+my $query_string = $m->comp('/Elements/QueryString', %ARGS, GroupBy => \@GroupBy );
</%init>
<div class="chart-wrapper">
-<span class="chart image">
+% if ( ($ChartStyle || '') =~ /\b(pie|bar)\b/ ) {
+<span class="chart image <% $1 %>">
% if (RT->Config->Get('DisableGD')) {
<% loc('Graphical charts are not available.') %><br />
% } else {
-<img src="<%RT->Config->Get('WebPath')%>/Search/Chart?<%$query_string|n%>" />
+% my $key = Digest::MD5::md5_hex( rand(1024) );
+% $session{'charts_cache'}{$key} = { columns => \%columns, report => $report->Serialize };
+% $session{'i'}++;
+<img src="<% RT->Config->Get('WebPath') %>/Search/Chart?Cache=<% $key |un %>&<% $query_string |n %>" />
% }
</span>
-<table class="collection-as-table chart">
-<tr>
-<th class="collection-as-table"><% loc($tix->Label($PrimaryGroupBy)) %>
-</th>
-<th class="collection-as-table"><&|/l&>Tickets</&>
-</th>
-</tr>
-<%perl>
- while (my $key = shift @sorted_keys) {
- $i++;
- my $value = shift @sorted_values;
- $total += $value;
-</%perl>
-<tr class="<% $i%2 ? 'evenline' : 'oddline' %>">
-<%perl>
-# TODO sadly we don't have "creator.city is null" or alike support yet
-# so no link if the key is undef for now
- if ( $PrimaryGroupBy !~ /(Hourly|Daily|Monthly|Annually)$/
- && $key ne loc('(no value)') ) {
- my $group = $PrimaryGroupBy; $group =~ s! !.!;
- my %orig_keys = reverse %loc_keys;
- my $QueryString = $m->comp('/Elements/QueryString',
- Query => "$Query and $group = '$orig_keys{$key}'",
- Format => $ARGS{Format},
- Rows => $ARGS{Rows},
- OrderBy => $ARGS{OrderBy},
- Order => $ARGS{Order},
- );
-</%perl>
-<td class="label collection-as-table">
-<a href=<% RT->Config->Get('WebPath') %>/Search/Results.html?<%$QueryString%>><%$key%></a>
-</td>
-<td class="value collection-as-table">
-<a href=<% RT->Config->Get('WebPath') %>/Search/Results.html?<%$QueryString%>><%$value%></a>
-</td>
-% } else {
-<td class="label collection-as-table"><% $key %></td>
-<td class="value collection-as-table"><% $value %></td>
-% }
-</tr>
% }
-%$i++;
-<tr class="<%$i%2 ? 'evenline' : 'oddline' %> total">
-<td class="label collection-as-table"><%loc('Total')%></td>
-<td class="value collection-as-table"><%$total||'0'%></td>
-</tr>
+% if ( ($ChartStyle || '') =~ /\btable\b/ ) {
+<& ChartTable, %ARGS, Table => { $report->FormatTable( %columns ) } &>
+% }
-</table>
+% if ( ($ChartStyle || '') =~ /\bsql\b/ ) {
<div class="query"><span class="label"><% loc('Query') %>:</span><span class="value"><% $Query %></span></div>
+% }
</div>
diff --git a/rt/share/html/Search/Elements/ChartTable b/rt/share/html/Search/Elements/ChartTable
new file mode 100644
index 0000000..045653a
--- /dev/null
+++ b/rt/share/html/Search/Elements/ChartTable
@@ -0,0 +1,119 @@
+%# BEGIN BPS TAGGED BLOCK {{{
+%#
+%# COPYRIGHT:
+%#
+%# This software is Copyright (c) 1996-2015 Best Practical Solutions, LLC
+%# <sales@bestpractical.com>
+%#
+%# (Except where explicitly superseded by other copyright notices)
+%#
+%#
+%# LICENSE:
+%#
+%# This work is made available to you under the terms of Version 2 of
+%# the GNU General Public License. A copy of that license should have
+%# been provided with this software, but in any event can be snarfed
+%# from www.gnu.org.
+%#
+%# This work is distributed in the hope that it will be useful, but
+%# WITHOUT ANY WARRANTY; without even the implied warranty of
+%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+%# General Public License for more details.
+%#
+%# You should have received a copy of the GNU General Public License
+%# along with this program; if not, write to the Free Software
+%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+%# 02110-1301 or visit their web page on the internet at
+%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+%#
+%#
+%# CONTRIBUTION SUBMISSION POLICY:
+%#
+%# (The following paragraph is not intended to limit the rights granted
+%# to you to modify and distribute this software under the terms of
+%# the GNU General Public License and is only of importance to you if
+%# you choose to contribute your changes and enhancements to the
+%# community by submitting them to Best Practical Solutions, LLC.)
+%#
+%# By intentionally submitting any modifications, corrections or
+%# derivatives to this work, or any other work intended for use with
+%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+%# you are the copyright holder for those contributions and you grant
+%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+%# royalty-free, perpetual, license to use, copy, create derivative
+%# works based on those contributions, and sublicense and distribute
+%# those contributions and any derivatives thereof.
+%#
+%# END BPS TAGGED BLOCK }}}
+<%ARGS>
+%Table => ()
+$Query => undef
+</%ARGS>
+<%INIT>
+
+my $base_query = $m->comp('/Elements/QueryString',
+ Format => $ARGS{Format},
+ Rows => $ARGS{Rows},
+ OrderBy => $ARGS{OrderBy},
+ Order => $ARGS{Order},
+);
+
+my $interp = $m->interp;
+my $eh = sub { $interp->apply_escapes( @_, 'h' ) };
+my $eu = sub { $interp->apply_escapes( @_, 'u' ) };
+
+$m->out('<table class="collection-as-table chart">'. "\n");
+foreach my $section (qw(thead tbody tfoot)) {
+ next unless $Table{ $section } && @{ $Table{ $section } };
+
+ $m->out("<$section>\n");
+ foreach my $row ( @{ $Table{ $section } } ) {
+ $m->out(' <tr');
+ $m->out(' class="'. ($row->{'even'}? 'evenline' : 'oddline') .'"')
+ if defined $row->{'even'};
+ $m->out(">");
+
+ foreach my $cell ( @{ $row->{'cells'} } ) {
+ my $tag = $cell->{'type'} eq 'value'? 'td' : 'th';
+ $m->out("<$tag");
+
+ my @class = ('collection-as-table');
+ push @class, ($cell->{'type'}) unless $cell->{'type'} eq 'head';
+ push @class, $cell->{'even'} ? 'evenline' : 'oddline'
+ if defined $cell->{'even'};
+ $m->out(' class="'. $eh->( join ' ', @class ) .'"');
+
+ foreach my $dir ( grep $cell->{$_}, qw(rowspan colspan) ) {
+ my $value = int $cell->{ $dir };
+ $m->out(qq{ $dir="$value"});
+ }
+ $m->out(' style="background-color: #'. $m->interp->apply_escapes($cell->{color}) .'"')
+ if $cell->{color};
+
+ $m->out('>');
+ if ( defined $cell->{'value'} ) {
+ if ( my $q = $cell->{'query'} ) {
+ $m->out(
+ '<a href="'. $eh->(RT->Config->Get('WebPath')) .'/Search/Results.html'
+ .'?Query='. $eu->(join ' AND ', map "($_)", grep defined && length, $Query, $q)
+ . $eh->('&') . $base_query
+ . '">'
+ );
+ $m->out( $eh->( $cell->{'value'} ) );
+ $m->out('</a>');
+ }
+ else {
+ $m->out( $eh->( $cell->{'value'} ) );
+ }
+ }
+ else {
+ $m->out('&nbsp;');
+ }
+ $m->out("</$tag>");
+ }
+ $m->out("</tr>\n");
+ }
+ $m->out("</$section>\n\n");
+}
+$m->out("</table>");
+</%INIT>
diff --git a/rt/share/html/Search/Elements/ConditionRow b/rt/share/html/Search/Elements/ConditionRow
index edf7381..80ecd97 100644
--- a/rt/share/html/Search/Elements/ConditionRow
+++ b/rt/share/html/Search/Elements/ConditionRow
@@ -74,9 +74,11 @@ $handle_block = sub {
return $m->scomp( $box->{'Path'}, %{ $box->{'Arguments'} }, Name => $name );
}
if ( $box->{'Type'} eq 'text' ) {
- my $default = $box->{'Default'} || '';
- my $size = $box->{'Size'}? qq{size="$box->{'Size'}"} : '';
- return qq{<input id="$name" name="$name" value="$default" $size />};
+ $box->{id} ||= $box->{name} ||= $name;
+ $box->{value} ||= delete($box->{Default}) || '';
+ return "<input ".join(" ", map{$m->interp->apply_escapes(lc($_),'h')
+ .q{="}.$m->interp->apply_escapes($box->{$_},'h').q{"}}
+ sort keys %$box)." />";
}
if ( $box->{'Type'} eq 'select' ) {
my $res = '';
diff --git a/rt/share/html/Search/Elements/EditFormat b/rt/share/html/Search/Elements/EditFormat
index a78fa05..fffec5c 100644
--- a/rt/share/html/Search/Elements/EditFormat
+++ b/rt/share/html/Search/Elements/EditFormat
@@ -59,7 +59,8 @@
<td valign="top"><select size="6" name="SelectDisplayColumns" multiple="multiple">
% my %seen;
% foreach my $field ( grep !$seen{lc $_}++, @$AvailableColumns) {
-<option value="<% $field %>"><% loc($field) %></option>
+<option value="<% $field %>" <% $selected{$field} ? 'selected="selected"' : '' |n%>>\
+<% $field =~ /^(?:CustomField|CF)\./ ? $field : loc($field) %></option>
% }
</select></td>
<td>
@@ -105,8 +106,10 @@
<td valign="top">
<select size="4" name="CurrentDisplayColumns">
% my $i=0;
+% my $current = $ARGS{CurrentDisplayColumns} || ''; $current =~ s/^\d+>//;
% foreach my $field ( @$CurrentFormat ) {
-<option value="<% $i++ %>><% $field->{Column} %>"><% loc( $field->{Column} ) %></option>
+<option value="<% $i++ %>><% $field->{Column} %>" <% $field->{Column} eq $current ? 'selected="selected"' : '' |n%>>\
+<% $field->{Column} =~ /^(?:CustomField|CF)\./ ? $field->{Column} : loc( $field->{Column} ) %></option>
% }
</select>
<br />
@@ -120,6 +123,12 @@
</tr>
</table>
+<%init>
+my $selected = $ARGS{SelectDisplayColumns};
+$selected = [ $selected ] unless ref $selected;
+my %selected;
+$selected{$_}++ for grep {defined} @{ $selected };
+</%init>
<%ARGS>
$CurrentFormat => undef
$AvailableColumns => undef
diff --git a/rt/share/html/Search/Elements/EditSearches b/rt/share/html/Search/Elements/EditSearches
index f5be486..0a55e0d 100644
--- a/rt/share/html/Search/Elements/EditSearches
+++ b/rt/share/html/Search/Elements/EditSearches
@@ -146,7 +146,7 @@ $SavedSearch => {}
$SavedSearch->{'Id'} = ( $ARGS{Type} && $ARGS{Type} eq 'Chart' ?
$ARGS{'SavedChartSearchId'} : $ARGS{'SavedSearchId'} ) || 'new';
-$SavedSearch->{'Description'} = $ARGS{'SavedSearchDescription'} || undef;
+$SavedSearch->{'Description'} = $ARGS{'SavedSearchDescription'} || '';
$SavedSearch->{'Privacy'} = $ARGS{'SavedSearchOwner'} || undef;
my @results;
@@ -158,7 +158,8 @@ if ( $ARGS{'SavedSearchRevert'} ) {
if ( $ARGS{'SavedSearchLoad'} ) {
my ($container, $id ) = _parse_saved_search ($ARGS{'SavedSearchLoad'});
if ( $container ) {
- my $search = $container->Attributes->WithId( $id );
+ my $search = RT::Attribute->new( $session{'CurrentUser'} );
+ $search->Load( $id );
$SavedSearch->{'Id'} = $ARGS{'SavedSearchLoad'};
$SavedSearch->{'Object'} = $search;
$SavedSearch->{'Description'} = $search->Description;
@@ -194,7 +195,8 @@ elsif ( $ARGS{'SavedSearchDelete'} ) {
}
elsif ( $ARGS{'SavedSearchCopy'} ) {
my ($container, $id ) = _parse_saved_search( $ARGS{'SavedSearchId'} );
- $SavedSearch->{'Object'} = $container->Attributes->WithId( $id );
+ $SavedSearch->{'Object'} = RT::Attribute->new( $session{'CurrentUser'} );
+ $SavedSearch->{'Object'}->Load( $id );
if ( $ARGS{'SavedSearchDescription'} && $ARGS{'SavedSearchDescription'} ne $SavedSearch->{'Object'}->Description ) {
$SavedSearch->{'Description'} = $ARGS{'SavedSearchDescription'};
} else {
@@ -208,7 +210,8 @@ if ( $SavedSearch->{'Id'} && $SavedSearch->{'Id'} ne 'new'
&& !$SavedSearch->{'Object'} )
{
my ($container, $id ) = _parse_saved_search( $ARGS{'SavedSearchId'} );
- $SavedSearch->{'Object'} = $container->Attributes->WithId( $id );
+ $SavedSearch->{'Object'} = RT::Attribute->new( $session{'CurrentUser'} );
+ $SavedSearch->{'Object'}->Load( $id );
$SavedSearch->{'Description'} ||= $SavedSearch->{'Object'}->Description;
}
@@ -290,7 +293,7 @@ if ( $obj && $obj->id ) {
}
push @results, loc('Updated saved search "[_1]"', $desc);
}
-elsif ( $id eq 'new' ) {
+elsif ( $id eq 'new' and defined $desc and length $desc ) {
my $saved_search = RT::SavedSearch->new( $session{'CurrentUser'} );
my ($status, $msg) = $saved_search->Save(
Privacy => $privacy,
@@ -300,8 +303,8 @@ elsif ( $id eq 'new' ) {
);
if ( $status ) {
- $SavedSearch->{'Object'} =
- $session{'CurrentUser'}->UserObj->Attributes->WithId( $saved_search->Id );
+ $SavedSearch->{'Object'} = RT::Attribute->new( $session{'CurrentUser'} );
+ $SavedSearch->{'Object'}->Load( $saved_search->Id );
# Build new SearchId
$SavedSearch->{'Id'} =
ref( $session{'CurrentUser'}->UserObj ) . '-'
@@ -313,6 +316,9 @@ elsif ( $id eq 'new' ) {
push @results, loc("Can't find a saved search to work with").': '.loc($msg);
}
}
+elsif ( $id eq 'new' ) {
+ push @results, loc("Can't save a search without a Description");
+}
else {
push @results, loc("Can't save this search");
}
diff --git a/rt/share/html/Search/Elements/EditSort b/rt/share/html/Search/Elements/EditSort
index de5d2d8..43ae729 100644
--- a/rt/share/html/Search/Elements/EditSort
+++ b/rt/share/html/Search/Elements/EditSort
@@ -68,7 +68,7 @@
% if (defined $OrderBy[$o] and $fieldval eq $OrderBy[$o]) {
selected="selected"
% }
-><% loc($field) %></option>
+><% $field =~ /^(?:CustomField|CF)\./ ? $field : loc($field) %></option>
% }
</select>
<select name="Order">
diff --git a/rt/share/html/Search/Elements/PickBasics b/rt/share/html/Search/Elements/PickBasics
index 3aae965..29eea7e 100644
--- a/rt/share/html/Search/Elements/PickBasics
+++ b/rt/share/html/Search/Elements/PickBasics
@@ -70,10 +70,10 @@ my @lines = (
Type => 'component',
Path => '/Elements/SelectBoolean',
Arguments => {
- True => loc("matches"),
- False => loc("doesn't match"),
- TrueVal => 'LIKE',
- FalseVal => 'NOT LIKE',
+ True => loc("matches"),
+ False => loc("doesn't match"),
+ TrueVal => 'LIKE',
+ FalseVal => 'NOT LIKE',
},
},
Value => { Type => 'text', Size => 20 },
@@ -89,7 +89,7 @@ my @lines = (
Value => {
Type => 'component',
Path => '/Elements/SelectQueue',
- Arguments => { NamedValues => 1, CheckQueueRight => 'ShowTicket' },
+ Arguments => { NamedValues => 1, },
},
},
{
@@ -102,7 +102,7 @@ my @lines = (
},
Value => {
Type => 'component',
- Path => '/Elements/SelectStatus',
+ Path => '/Ticket/Elements/SelectStatus',
Arguments => { SkipDeleted => 1, Queues => \%queues },
},
},
@@ -114,6 +114,7 @@ my @lines = (
Owner => loc('Owner'),
Creator => loc('Creator'),
LastUpdatedBy => loc('Last updated by'),
+ UpdatedBy => loc('Updated by'),
],
},
Op => {
@@ -141,6 +142,19 @@ my @lines = (
Value => { Type => 'text', Size => 20 }
},
{
+ Name => 'WatcherGroup',
+ Field => {
+ Type => 'component',
+ Path => 'SelectPersonType',
+ Arguments => { Default => 'Owner', Suffix => 'Group' },
+ },
+ Op => {
+ Type => 'select',
+ Options => [ '=' => loc('is') ],
+ },
+ Value => { Type => 'text', Size => 20, "data-autocomplete" => "Groups" }
+ },
+ {
Name => 'Date',
Field => {
Type => 'component',
diff --git a/rt/share/html/Search/Elements/PickCFs b/rt/share/html/Search/Elements/PickCFs
index cf8c92a..e8d9c71 100644
--- a/rt/share/html/Search/Elements/PickCFs
+++ b/rt/share/html/Search/Elements/PickCFs
@@ -58,7 +58,7 @@ $m->callback(
my @lines;
while ( my $CustomField = $CustomFields->Next ) {
my %line;
- $line{'Name'} = "'$TicketSQLField.{" . $CustomField->Name . "}'";
+ $line{'Name'} = "$TicketSQLField.{" . $CustomField->Name . "}";
$line{'Field'} = $CustomField->Name;
# Op
@@ -88,20 +88,11 @@ while ( my $CustomField = $CustomFields->Next ) {
}
# Value
- if ($CustomField->Type =~ /^Date(Time)?$/) {
- my $is_datetime = $1 ? 1 : 0;
- $line{'Value'} = {
- Type => 'component',
- Path => '/Elements/SelectDate',
- Arguments => { $is_datetime ? (ShowTime => 1) : (ShowTime => 0), },
- };
- } else {
- $line{'Value'} = {
- Type => 'component',
- Path => '/Elements/SelectCustomFieldValue',
- Arguments => { CustomField => $CustomField },
- };
- }
+ $line{'Value'} = {
+ Type => 'component',
+ Path => '/Elements/SelectCustomFieldValue',
+ Arguments => { CustomField => $CustomField },
+ };
push @lines, \%line;
}
diff --git a/rt/share/html/Search/Elements/PickCriteria b/rt/share/html/Search/Elements/PickCriteria
index b2e84ca..e55e270 100644
--- a/rt/share/html/Search/Elements/PickCriteria
+++ b/rt/share/html/Search/Elements/PickCriteria
@@ -54,6 +54,8 @@
<& PickBasics &>
<& PickCustomerFields &>
<& PickTicketCFs, queues => \%queues &>
+<& PickObjectCFs, Class => 'Transaction', queues => \%queues &>
+<& PickObjectCFs, Class => 'Queue', queues => \%queues &>
% $m->callback( %ARGS, CallbackName => "AfterCFs" );
<tr class="separator"><td colspan="3"><hr /></td></tr>
diff --git a/rt/share/html/Search/Elements/PickObjectCFs b/rt/share/html/Search/Elements/PickObjectCFs
new file mode 100644
index 0000000..1a67338
--- /dev/null
+++ b/rt/share/html/Search/Elements/PickObjectCFs
@@ -0,0 +1,76 @@
+%# BEGIN BPS TAGGED BLOCK {{{
+%#
+%# COPYRIGHT:
+%#
+%# This software is Copyright (c) 1996-2015 Best Practical Solutions, LLC
+%# <sales@bestpractical.com>
+%#
+%# (Except where explicitly superseded by other copyright notices)
+%#
+%#
+%# LICENSE:
+%#
+%# This work is made available to you under the terms of Version 2 of
+%# the GNU General Public License. A copy of that license should have
+%# been provided with this software, but in any event can be snarfed
+%# from www.gnu.org.
+%#
+%# This work is distributed in the hope that it will be useful, but
+%# WITHOUT ANY WARRANTY; without even the implied warranty of
+%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+%# General Public License for more details.
+%#
+%# You should have received a copy of the GNU General Public License
+%# along with this program; if not, write to the Free Software
+%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+%# 02110-1301 or visit their web page on the internet at
+%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+%#
+%#
+%# CONTRIBUTION SUBMISSION POLICY:
+%#
+%# (The following paragraph is not intended to limit the rights granted
+%# to you to modify and distribute this software under the terms of
+%# the GNU General Public License and is only of importance to you if
+%# you choose to contribute your changes and enhancements to the
+%# community by submitting them to Best Practical Solutions, LLC.)
+%#
+%# By intentionally submitting any modifications, corrections or
+%# derivatives to this work, or any other work intended for use with
+%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+%# you are the copyright holder for those contributions and you grant
+%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+%# royalty-free, perpetual, license to use, copy, create derivative
+%# works based on those contributions, and sublicense and distribute
+%# those contributions and any derivatives thereof.
+%#
+%# END BPS TAGGED BLOCK }}}
+<%ARGS>
+$Class
+%queues => ()
+</%ARGS>
+<%init>
+my $CustomFields = RT::CustomFields->new( $session{'CurrentUser'} );
+$CustomFields->ApplySortOrder;
+$CustomFields->LimitToLookupType( "RT::$Class"->CustomFieldLookupType );
+$CustomFields->LimitToObjectId(0);
+
+foreach my $name (keys %queues) {
+ my $queue = RT::Queue->new($session{'CurrentUser'});
+ $queue->Load($name);
+ next unless $queue->Id;
+ $CustomFields->LimitToObjectId($queue->Id);
+ $CustomFields->SetContextObject( $queue ) if keys %queues == 1;
+}
+
+my $has_cf = $CustomFields->First ? 1 : 0;
+$CustomFields->GotoFirstItem;
+</%init>
+% if ($has_cf) {
+<tr class="separator">
+ <td colspan="3">
+ <hr><em><% loc("[_1] CFs", loc($Class)) %></em>
+ </td>
+</tr>
+% }
+<& PickCFs, %ARGS, TicketSQLField => "${Class}CF", CustomFields => $CustomFields &>
diff --git a/rt/share/html/Search/Elements/PickTicketCFs b/rt/share/html/Search/Elements/PickTicketCFs
index ac52049..ae3a4a2 100644
--- a/rt/share/html/Search/Elements/PickTicketCFs
+++ b/rt/share/html/Search/Elements/PickTicketCFs
@@ -54,8 +54,11 @@ foreach my $id (keys %queues) {
# Gotta load up the $queue object, since queues get stored by name now.
my $queue = RT::Queue->new($session{'CurrentUser'});
$queue->Load($id);
- $CustomFields->LimitToQueue($queue->Id) if $queue->Id;
+ next unless $queue->Id;
+ $CustomFields->LimitToQueue($queue->Id);
+ $CustomFields->SetContextObject( $queue ) if keys %queues == 1;
}
$CustomFields->LimitToGlobal;
+$CustomFields->OrderBy( FIELD => 'Name', ORDER => 'ASC' );
</%init>
<& PickCFs, %ARGS, TicketSQLField => 'CF', CustomFields => $CustomFields &>
diff --git a/rt/share/html/Search/Elements/ResultsRSSView b/rt/share/html/Search/Elements/ResultsRSSView
index 0bce7ec..f392369 100644
--- a/rt/share/html/Search/Elements/ResultsRSSView
+++ b/rt/share/html/Search/Elements/ResultsRSSView
@@ -46,41 +46,7 @@
%#
%# END BPS TAGGED BLOCK }}}
<%INIT>
-my $current_user = $session{CurrentUser};
-
-if ( $m->request_comp->path =~ RT->Config->Get('WebNoAuthRegex') ) {
- my $path = $m->dhandler_arg;
-
- my $notfound = sub {
- my $mesg = shift;
- $r->headers_out->{'Status'} = '404 Not Found';
- $RT::Logger->info("Error encountered in rss generation: $mesg");
- $m->clear_and_abort;
- };
-
- $notfound->("Invalid path: $path") unless $path =~ m!^([^/]+)/([^/]+)/?!;
-
- my ( $name, $auth ) = ( $1, $2 );
-
- # Unescape parts
- $name =~ s/\%([0-9a-z]{2})/chr(hex($1))/gei;
-
- # Decode from bytes to characters
- $name = Encode::decode( "UTF-8", $name );
-
- my $user = RT::User->new(RT->SystemUser);
- $user->Load($name);
- $notfound->("Invalid user: $user") unless $user->id;
-
- $notfound->("Invalid authstring")
- unless $user->ValidateAuthString( $auth,
- $ARGS{Query} . $ARGS{Order} . $ARGS{OrderBy} );
-
- $current_user = RT::CurrentUser->new;
- $current_user->Load($user);
-}
-
-my $Tickets = RT::Tickets->new($current_user);
+my $Tickets = RT::Tickets->new($session{'CurrentUser'});
$Tickets->FromSQL($ARGS{'Query'});
if ($OrderBy =~ /\|/) {
# Multiple Sorts
@@ -92,48 +58,58 @@ if ($OrderBy =~ /\|/) {
} else {
$Tickets->OrderBy(FIELD => $OrderBy, ORDER => $Order);
}
-$r->content_type('application/rss+xml');
-
-
+$r->content_type('application/rss+xml; charset=utf-8');
- # create an RSS 1.0 file (http://purl.org/rss/1.0/)
- use XML::RSS;
- my $rss = XML::RSS->new(version => '1.0');
- $rss->channel(
- title => RT->Config->Get('rtname').": Search " . $ARGS{'Query'},
- link => RT->Config->Get('WebURL'),
- description => "",
- dc => {
- },
- generator => "RT v" . $RT::VERSION,
- syn => {
- updatePeriod => "hourly",
- updateFrequency => "1",
- updateBase => "1901-01-01T00:00+00:00",
- },
- );
+use XML::RSS;
+my $rss = XML::RSS->new(version => '1.0');
+my $url;
+if ( RT->Config->Get('CanonicalizeURLsInFeeds') ) {
+ $url = RT->Config->Get('WebURL');
+} else {
+ $url = RT::Interface::Web::GetWebURLFromRequest();
+}
- while ( my $Ticket = $Tickets->Next()) {
- my $creator_str = $m->scomp('/Elements/ShowUser', User => $Ticket->CreatorObj);
- $creator_str =~ s/[\r\n]//g;
-
- # Get the plain-text content; it is interpreted as HTML by RSS
- # readers, so it must be escaped (and is escaped _again_ when
- # inserted into the XML).
- my $content = $Ticket->Transactions->First->Content;
- $content = $m->interp->apply_escapes( $content, 'h');
- $rss->add_item(
- title => $Ticket->Subject || loc('No Subject'),
- link => RT->Config->Get('WebURL')."Ticket/Display.html?id=".$Ticket->id,
- description => $content,
- dc => { creator => $creator_str,
- date => $Ticket->CreatedObj->RFC2822,
- },
- guid => $Ticket->Queue . '_' . $Ticket->id,
- );
- }
+my $base_date = RT::Date->new( RT->SystemUser );
+$base_date->SetToNow;
+$base_date->SetToMidnight;
+
+$rss->channel(
+ title => RT->Config->Get('rtname').": Search " . $ARGS{'Query'},
+ link => $url,
+ description => "",
+ dc => { },
+ generator => "RT v" . $RT::VERSION,
+ syn => {
+ updatePeriod => "hourly",
+ updateFrequency => "1",
+ updateBase => $base_date->W3CDTF,
+ },
+);
+
+
+while ( my $Ticket = $Tickets->Next()) {
+ my $creator_str = $Ticket->CreatorObj->Format;
+ $creator_str =~ s/[\r\n]//g;
+
+ # Get the plain-text content; it is interpreted as HTML by RSS
+ # readers, so it must be escaped (and is escaped _again_ when
+ # inserted into the XML).
+ my $content = $Ticket->Transactions->First->Content;
+ $content = $m->interp->apply_escapes( $content, 'h');
+
+ $rss->add_item(
+ title => $Ticket->Subject || loc('No Subject'),
+ link => $url . "Ticket/Display.html?id=".$Ticket->id,
+ description => $content,
+ dc => {
+ creator => $creator_str,
+ date => $Ticket->CreatedObj->W3CDTF,
+ },
+ guid => $Ticket->Queue . '_' . $Ticket->id,
+ );
+}
$m->out($rss->as_string);
$m->abort();
diff --git a/rt/share/html/Search/Elements/SearchPrivacy b/rt/share/html/Search/Elements/SearchPrivacy
index dd7ef3b..1e43dfd 100644
--- a/rt/share/html/Search/Elements/SearchPrivacy
+++ b/rt/share/html/Search/Elements/SearchPrivacy
@@ -53,9 +53,9 @@ my $label;
if (ref($Object) eq 'RT::User') {
$label = $Object->id == $session{'CurrentUser'}->Id
? loc("My saved searches")
- : loc("[_1]'s saved searches", $m->scomp('/Elements/ShowUser', User => $Object));
+ : loc("[_1]'s saved searches", $Object->Format);
} else {
- $label = loc("[_1]'s saved searches", $m->interp->apply_escapes($Object->Name, 'h'));
+ $label = loc("[_1]'s saved searches", $Object->Name);
}
</%init>
-<% $label |n %>\
+<% $label %>\
diff --git a/rt/share/html/Search/Elements/SearchesForObject b/rt/share/html/Search/Elements/SearchesForObject
index 397a0d9..f58752d 100644
--- a/rt/share/html/Search/Elements/SearchesForObject
+++ b/rt/share/html/Search/Elements/SearchesForObject
@@ -55,10 +55,10 @@ my @result;
while (my $search = $Object->Attributes->Next) {
my $desc;
if ($search->Name eq 'SavedSearch') {
- push @result, [$search->Description, $search->Description, $search];
+ push @result, [$search->Description, $search->Description, $search];
}
elsif ($search->Name =~ m/^Search - (.*)/) {
- push @result, [$1, loc($1), $search];
+ push @result, [$1, loc($1), $search];
}
}
return @result;
diff --git a/rt/share/html/Search/Elements/SelectAndOr b/rt/share/html/Search/Elements/SelectAndOr
index cbea34f..d506ef7 100644
--- a/rt/share/html/Search/Elements/SelectAndOr
+++ b/rt/share/html/Search/Elements/SelectAndOr
@@ -45,8 +45,8 @@
%# those contributions and any derivatives thereof.
%#
%# END BPS TAGGED BLOCK }}}
-<input type="radio" class="radio" name="<%$Name%>" checked="checked" value="AND" /><&|/l&>AND</&>
-<input type="radio" class="radio" name="<%$Name%>" value="OR" /><&|/l&>OR</&>
+<label><input type="radio" class="radio" name="<%$Name%>" checked="checked" value="AND" /><&|/l&>AND</&></label>
+<label><input type="radio" class="radio" name="<%$Name%>" value="OR" /><&|/l&>OR</&></label>
<%ARGS>
$Name => "Operator"
diff --git a/rt/share/html/Search/Elements/SelectChartFunction b/rt/share/html/Search/Elements/SelectChartFunction
new file mode 100644
index 0000000..dad6b78
--- /dev/null
+++ b/rt/share/html/Search/Elements/SelectChartFunction
@@ -0,0 +1,79 @@
+%# BEGIN BPS TAGGED BLOCK {{{
+%#
+%# COPYRIGHT:
+%#
+%# This software is Copyright (c) 1996-2015 Best Practical Solutions, LLC
+%# <sales@bestpractical.com>
+%#
+%# (Except where explicitly superseded by other copyright notices)
+%#
+%#
+%# LICENSE:
+%#
+%# This work is made available to you under the terms of Version 2 of
+%# the GNU General Public License. A copy of that license should have
+%# been provided with this software, but in any event can be snarfed
+%# from www.gnu.org.
+%#
+%# This work is distributed in the hope that it will be useful, but
+%# WITHOUT ANY WARRANTY; without even the implied warranty of
+%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+%# General Public License for more details.
+%#
+%# You should have received a copy of the GNU General Public License
+%# along with this program; if not, write to the Free Software
+%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+%# 02110-1301 or visit their web page on the internet at
+%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+%#
+%#
+%# CONTRIBUTION SUBMISSION POLICY:
+%#
+%# (The following paragraph is not intended to limit the rights granted
+%# to you to modify and distribute this software under the terms of
+%# the GNU General Public License and is only of importance to you if
+%# you choose to contribute your changes and enhancements to the
+%# community by submitting them to Best Practical Solutions, LLC.)
+%#
+%# By intentionally submitting any modifications, corrections or
+%# derivatives to this work, or any other work intended for use with
+%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+%# you are the copyright holder for those contributions and you grant
+%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+%# royalty-free, perpetual, license to use, copy, create derivative
+%# works based on those contributions, and sublicense and distribute
+%# those contributions and any derivatives thereof.
+%#
+%# END BPS TAGGED BLOCK }}}
+<select name="<% $Name %>" class="cascade-by-optgroup">
+% if ( $ShowEmpty ) {
+<option value="">&nbsp;</option>
+% }
+<%perl>
+my $in_optgroup = "";
+while ( my ($value, $display) = splice @functions, 0, 2 ) {
+ my $optgroup = $value =~ /\((.+)\)$/ ? $1 : $display;
+ if ($in_optgroup ne $optgroup) {
+ $m->out("</optgroup>\n") if $in_optgroup;
+
+ my $name = $m->interp->apply_escapes(loc($optgroup), 'h');
+ $m->out(qq[<optgroup label="$name">\n]);
+
+ $in_optgroup = $optgroup;
+ }
+</%perl>
+<option value="<% $value %>"<% $value eq $Default ? qq[ selected="selected"] : '' |n %>><% loc( $display ) %></option>
+% }
+% if ($in_optgroup) {
+ </optgroup>
+% }
+</select>
+<%ARGS>
+$Name => 'ChartFunction'
+$Default => 'COUNT'
+$ShowEmpty => 0
+</%ARGS>
+<%INIT>
+my @functions = RT::Report::Tickets->Statistics;
+$Default = '' unless defined $Default;
+</%INIT>
diff --git a/rt/share/html/Search/Elements/SelectChartType b/rt/share/html/Search/Elements/SelectChartType
index 266885f..c4d95d0 100644
--- a/rt/share/html/Search/Elements/SelectChartType
+++ b/rt/share/html/Search/Elements/SelectChartType
@@ -50,9 +50,10 @@ $Name => 'ChartType'
$Default => 'bar'
</%args>
<select id="<%$Name%>" name="<%$Name%>">
-% foreach my $option (qw(bar pie)) {
+% foreach my $option ('bar', 'pie', 'table') {
% # 'bar' # loc
% # 'pie' # loc
+% # 'table' # loc
<option value="<%$option%>"<% $option eq $Default ? qq[ selected="selected"] : '' |n %>><%loc($option)%></option>
% }
</select>
diff --git a/rt/share/html/Search/Elements/SelectGroup b/rt/share/html/Search/Elements/SelectGroup
index 907c88e..27d6a76 100644
--- a/rt/share/html/Search/Elements/SelectGroup
+++ b/rt/share/html/Search/Elements/SelectGroup
@@ -56,7 +56,7 @@
<%INIT>
my $groups = RT::Groups->new($session{'CurrentUser'});
-$groups->Limit(FIELD => 'Domain', OPERATOR => '=', VALUE => $Domain);
+$groups->Limit(FIELD => 'Domain', OPERATOR => '=', VALUE => $Domain, CASESENSITIVE => 0);
</%INIT>
<%ARGS>
diff --git a/rt/share/html/Search/Elements/SelectGroupBy b/rt/share/html/Search/Elements/SelectGroupBy
index 8daab6d..99f0f47 100644
--- a/rt/share/html/Search/Elements/SelectGroupBy
+++ b/rt/share/html/Search/Elements/SelectGroupBy
@@ -49,11 +49,29 @@
$Name => 'GroupBy'
$Default => 'Status'
$Query => ''
+$ShowEmpty => 0
</%args>
-<select id="<% $Name %>" name="<% $Name %>">
-% while (@options) {
-% my ($text, $value) = (shift @options, shift @options);
-<option value="<% $value %>" <% $value eq $Default ? 'selected="selected"' : '' |n%>><% $text %></option>
+<select name="<% $Name %>" class="cascade-by-optgroup">
+% if ( $ShowEmpty ) {
+<option value="">&nbsp;</option>
+% }
+<%perl>
+my $in_optgroup = "";
+while ( my ($label, $value) = splice @options, 0, 2 ) {
+ my ($optgroup, $text) = @$label;
+ if ($in_optgroup ne $optgroup) {
+ $m->out("</optgroup>\n") if $in_optgroup;
+
+ my $name = $m->interp->apply_escapes(loc($optgroup), 'h');
+ $m->out(qq[<optgroup label="$name">\n]);
+
+ $in_optgroup = $optgroup;
+ }
+</%perl>
+<option value="<% $value %>" <% $value eq ($Default||'') ? 'selected="selected"' : '' |n %>><% loc($text) %></option>
+% }
+% if ($in_optgroup) {
+ </optgroup>
% }
</select>
<%init>
diff --git a/rt/share/html/Search/Elements/SelectLinks b/rt/share/html/Search/Elements/SelectLinks
index 3759a58..1b85094 100644
--- a/rt/share/html/Search/Elements/SelectLinks
+++ b/rt/share/html/Search/Elements/SelectLinks
@@ -47,7 +47,7 @@
%# END BPS TAGGED BLOCK }}}
<select name="<%$Name%>">
% foreach (@fields) {
-<option value="<%$_%>"><% loc($_) %></option>
+<option value="<%$_->[0]%>"><% $_->[1] %></option>
% }
</select>
<%ARGS>
@@ -55,12 +55,13 @@ $Name => 'LinksField'
</%ARGS>
<%INIT>
-my @fields = ('HasMember',
- 'MemberOf',
- 'DependsOn',
- 'DependedOnBy',
- 'RefersTo',
- 'ReferredToBy',
- 'LinkedTo',
- );
+my @fields = (
+ [ HasMember => loc("Child") ],
+ [ MemberOf => loc("Parent") ],
+ [ DependsOn => loc("Depends on") ],
+ [ DependedOnBy => loc("Depended on by") ],
+ [ RefersTo => loc("Refers to") ],
+ [ ReferredToBy => loc("Referred to by") ],
+ [ LinkedTo => loc("Links to") ],
+);
</%INIT>
diff --git a/rt/share/html/Search/Elements/SelectPersonType b/rt/share/html/Search/Elements/SelectPersonType
index 7ec875a..0fc541b 100644
--- a/rt/share/html/Search/Elements/SelectPersonType
+++ b/rt/share/html/Search/Elements/SelectPersonType
@@ -51,7 +51,7 @@
% }
% for my $option (@types) {
% if ($Suffix) {
-<option value="<% $option %><% $Suffix %>"<%$option eq $Default && qq[ selected="selected"] |n %> ><%loc($option)%></option>
+<option value="<% $option %><% $Suffix %>"<%$option eq $Default && qq[ selected="selected"] |n %> ><% loc($option) %> <% loc('Group') %></option>
% next;
% }
% foreach my $subtype (@subtypes) {
@@ -66,7 +66,7 @@ if ($Scope =~ /queue/) {
@types = qw(Cc AdminCc);
}
elsif ($Suffix eq 'Group') {
- @types = qw(Requestor Cc AdminCc Watcher);
+ @types = qw(Owner Requestor Cc AdminCc Watcher);
}
else {
@types = qw(Requestor Cc AdminCc Watcher Owner QueueCc QueueAdminCc QueueWatcher);
diff --git a/rt/share/html/Search/Results.html b/rt/share/html/Search/Results.html
index 6125c36..b6b3379 100755
--- a/rt/share/html/Search/Results.html
+++ b/rt/share/html/Search/Results.html
@@ -50,11 +50,12 @@
LinkRel => \%link_rel &>
<& /Elements/Tabs &>
-% $m->callback( ARGSRef => \%ARGS, CallbackName => 'BeforeResults' );
+% my $DisplayFormat;
+% $m->callback( ARGSRef => \%ARGS, Format => \$Format, DisplayFormat => \$DisplayFormat, CallbackName => 'BeforeResults' );
% unless ($ok) {
% $msg =~ s{ at .*? line .*}{}s;
-<&| /Widgets/TitleBox, title => loc("Error"), class => "error" &>
+<&| /Widgets/TitleBox, title => loc("Error"), class => "error-titlebox" &>
<&|/l_unsafe, "<i>".$m->interp->apply_escapes($msg, "h")."</i>" &>There was an error parsing your search query: [_1]. Your RT admin can find more information in the error logs.</&>
</&>
% } else {
@@ -68,10 +69,13 @@
Rows => $Rows,
Page => $Page,
Format => $Format,
+ DisplayFormat => $DisplayFormat, # in case we set it in callbacks
Class => 'RT::Tickets',
- BaseURL => $BaseURL
-
- &>
+ BaseURL => $BaseURL,
+ SavedSearchId => $ARGS{'SavedSearchId'},
+ SavedChartSearchId => $ARGS{'SavedChartSearchId'},
+ PassArguments => [qw(Query Format Rows Page Order OrderBy SavedSearchId SavedChartSearchId)],
+&>
% }
% $m->callback( ARGSRef => \%ARGS, CallbackName => 'AfterResults' );
@@ -94,8 +98,8 @@ my $prefs = $session{'CurrentUser'}->UserObj->Preferences("SearchDisplay") || {}
# These variables are what define a search_hash; this is also
# where we give sane defaults.
$Format ||= $prefs->{'Format'} || RT->Config->Get('DefaultSearchResultFormat');
-$Order ||= $prefs->{'Order'} || 'ASC';
-$OrderBy ||= $prefs->{'OrderBy'} || 'id';
+$Order ||= $prefs->{'Order'} || RT->Config->Get('DefaultSearchResultOrder');
+$OrderBy ||= $prefs->{'OrderBy'} || RT->Config->Get('DefaultSearchResultOrderBy');
# Some forms pass in "RowsPerPage" rather than "Rows"
# We call it RowsPerPage everywhere else.
@@ -140,10 +144,10 @@ $session{'CurrentSearchHash'} = {
};
-my ($title, $ticketcount) = (loc("Found tickets"), 0);
+my ($title, $ticketcount) = (loc("Find tickets"), 0);
if ( $session{'tickets'}->Query()) {
$ticketcount = $session{tickets}->CountAll();
- $title = loc('Found [quant,_1,ticket]', $ticketcount);
+ $title = loc('Found [quant,_1,ticket,tickets]', $ticketcount);
}
my $QueryString = "?".$m->comp('/Elements/QueryString',
@@ -156,7 +160,7 @@ my $QueryString = "?".$m->comp('/Elements/QueryString',
my $ShortQueryString = "?".$m->comp('/Elements/QueryString', Query => $Query);
if ($ARGS{'TicketsRefreshInterval'}) {
- $session{'tickets_refresh_interval'} = $ARGS{'TicketsRefreshInterval'};
+ $session{'tickets_refresh_interval'} = $ARGS{'TicketsRefreshInterval'};
}
my $refresh = $session{'tickets_refresh_interval'}
diff --git a/rt/share/html/Search/Results.tsv b/rt/share/html/Search/Results.tsv
index b28ea48..1e45a33 100644
--- a/rt/share/html/Search/Results.tsv
+++ b/rt/share/html/Search/Results.tsv
@@ -54,56 +54,8 @@ $PreserveNewLines => 0
</%ARGS>
<%INIT>
-$r->content_type('text/tab-separated-values');
$r->header_out('Content-Disposition' => 'attachment;filename="Results.tsv"');
-my $DisplayFormat = $m->comp('/Elements/ScrubHTML', Content => $Format);
-
-my @Format = $m->comp('/Elements/CollectionAsTable/ParseFormat', Format => $DisplayFormat);
-
-my @columns;
-
-my $should_loc = { map { $_ => 1 } qw(Status) };
-
-my $col_entry = sub {
- my $col = shift;
- # in tsv output, "#" is often a comment character but we use it for "id"
- delete $col->{title}
- if $col->{title} and $col->{title} =~ /^\s*#\s*$/;
- return {
- header => loc($col->{title} || $col->{attribute}),
- map => $m->comp(
- "/Elements/ColumnMap",
- Name => $col->{attribute},
- Attr => 'value'
- ),
- should_loc => $should_loc->{$col->{attribute}},
- }
-};
-
-if ($PreserveNewLines) {
- my $col = [];
- push @columns, $col;
- for (@Format) {
- if ($_->{title} eq 'NEWLINE') {
- $col = [];
- push @columns, $col;
- }
- else {
- push @$col, $col_entry->($_);
- }
- }
-}
-else {
- push @columns, [map { $_->{attribute}
- ? $col_entry->($_)
- : () } @Format];
-}
-
-for (@columns) {
- $m->out(join("\t", map { $_->{header} } @$_)."\n");
-}
-
my $Tickets = RT::Tickets->new( $session{'CurrentUser'} );
$Tickets->FromSQL( $Query );
if ( $OrderBy =~ /\|/ ) {
@@ -119,21 +71,5 @@ else {
$Tickets->OrderBy( FIELD => $OrderBy, ORDER => $Order );
}
-my $i = 0;
-my $ii = 0;
-while (my $row = $Tickets->Next) {
- for my $col (@columns) {
- $m->out(join("\t", map {
- my $val = ProcessColumnMapValue($_->{map}, Arguments => [$row, $ii++], Escape => 0);
- $val = loc($val) if $_->{should_loc};
- # remove tabs from all field values, they screw up the tsv
- $val = '' unless defined $val;
- $val =~ s/(?:\n|\r)//g; $val =~ s{\t}{ }g;
- $val;
- } @$col)."\n");
- }
- $m->flush_buffer unless ++$i % 10;
-}
-$m->abort();
-
+$m->comp( "/Elements/TSVExport", Collection => $Tickets, Format => $Format, PreserveNewLines => $PreserveNewLines );
</%INIT>
diff --git a/rt/share/html/Search/Simple.html b/rt/share/html/Search/Simple.html
index fbbffde..f65ad5d 100644
--- a/rt/share/html/Search/Simple.html
+++ b/rt/share/html/Search/Simple.html
@@ -98,7 +98,7 @@
<%INIT>
my $title = loc("Search for tickets");
-use RT::Search::Googleish;
+use RT::Search::Simple;
if ($q) {
my $tickets = RT::Tickets->new( $session{'CurrentUser'} );
@@ -115,7 +115,7 @@ if ($q) {
$m->callback( %ARGS, CallbackName => 'SearchArgs', args => \%args);
- my $search = RT::Search::Googleish->new(%args);
+ my $search = RT::Search::Simple->new(%args);
$m->comp( "Results.html", Query => $search->QueryToSQL() );
$m->comp( "/Elements/Footer" );
diff --git a/rt/share/html/Search/Graph.html b/rt/share/html/Search/index.html
index 185278a..ffb7a5b 100644
--- a/rt/share/html/Search/Graph.html
+++ b/rt/share/html/Search/index.html
@@ -45,6 +45,6 @@
%# those contributions and any derivatives thereof.
%#
%# END BPS TAGGED BLOCK }}}
-<%INIT>
-return $m->comp('/Ticket/Graphs/index.html', %ARGS );
-</%INIT>
+<& /Admin/Elements/Header, Title => loc('Searches') &>
+<& /Elements/Tabs &>
+<& /Elements/ListMenu, menu => Menu()->child('search'), show_children => 1 &>