[Rt-commit] rt branch, 4.2/autocomplete-links, created. rt-4.1.13-282-g6375cfb
? sunnavy
sunnavy at bestpractical.com
Mon Jul 8 12:01:16 EDT 2013
The branch, 4.2/autocomplete-links has been created
at 6375cfbc6ae423336347c484468229a64723b380 (commit)
- Log -----------------------------------------------------------------
commit d43c6cfe83ce9dfcc0df1ec099f8858471c709d9
Author: sunnavy <sunnavy at bestpractical.com>
Date: Mon Jul 8 20:33:07 2013 +0800
allow text-like searching of integer fields for tickets
initial reason to do this is to autocomplete ticket links.
note that we need to "cast" int to text for Pg
diff --git a/lib/RT/Tickets.pm b/lib/RT/Tickets.pm
index 326ba3e..c261465 100644
--- a/lib/RT/Tickets.pm
+++ b/lib/RT/Tickets.pm
@@ -395,8 +395,18 @@ Meta Data:
sub _IntLimit {
my ( $sb, $field, $op, $value, @rest ) = @_;
- die "Invalid Operator $op for $field"
- unless $op =~ /^(=|!=|>|<|>=|<=)$/;
+ my $is_a_like = $op =~ /MATCHES|ENDSWITH|STARTSWITH|LIKE/i;
+
+ # We want to support <id LIKE '1%'> for ticket autocomplete,
+ # but we need to explicitly typecast on Postgres
+ if ( $is_a_like && RT->Config->Get('DatabaseType') eq 'Pg' ) {
+ return $sb->_SQLLimit(
+ FUNCTION => "CAST(main.$field AS TEXT)",
+ OPERATOR => $op,
+ VALUE => $value,
+ @rest,
+ );
+ }
$sb->Limit(
FIELD => $field,
commit 6375cfbc6ae423336347c484468229a64723b380
Author: sunnavy <sunnavy at bestpractical.com>
Date: Mon Jul 8 20:37:22 2013 +0800
autocomplete tickets support
note that we use FromSQL to avoid the *not working* multiple ->Limit calls
with "ENTRYAGGREGATOR => 'OR'"
diff --git a/etc/RT_Config.pm.in b/etc/RT_Config.pm.in
index 41a3996..36cfa3d 100755
--- a/etc/RT_Config.pm.in
+++ b/etc/RT_Config.pm.in
@@ -1503,6 +1503,23 @@ your users.
Set($AllowUserAutocompleteForUnprivileged, 0);
+=item C<$TicketAutocompleteFields>
+
+Specifies which fields of L<RT::Ticket> to match against and how to match each
+field when autocompleting users. Valid match methods are LIKE, STARTSWITH,
+ENDSWITH, C<=>, and C<!=>.
+
+Not all Ticket fields are publically accessible and hence won't work for
+autocomplete unless you override their accessibility using a local overlay or a
+plugin. Out of the box the following fields are public: id, Subject.
+
+=cut
+
+Set( $TicketAutocompleteFields, {
+ id => 'STARTSWITH',
+ Subject => 'LIKE',
+});
+
=item C<$DisplayTicketAfterQuickCreate>
Enable this to redirect to the created ticket display page
diff --git a/share/html/Elements/AddLinks b/share/html/Elements/AddLinks
index a859e8a..b26ba47 100644
--- a/share/html/Elements/AddLinks
+++ b/share/html/Elements/AddLinks
@@ -71,32 +71,32 @@ my $id = ($Object and $Object->id)
% if ($Merge) {
<tr>
<td class="label"><&|/l&>Merge into</&>:</td>
- <td class="entry"><input name="<%$id%>-MergeInto" value="<% $ARGSRef->{"$id-MergeInto"} || '' %>" /> <i><&|/l&>(only one ticket)</&></i></td>
+ <td class="entry"><input name="<%$id%>-MergeInto" value="<% $ARGSRef->{"$id-MergeInto"} || '' %>" data-autocomplete="Tickets" data-autocomplete-exclude="<% $id %>" /> <i><&|/l&>(only one ticket)</&></i></td>
</tr>
% }
<tr>
<td class="label"><& ShowRelationLabel, Object => $Object, Label => loc('Depends on').':', Relation => 'DependsOn' &></td>
- <td class="entry"><input name="<%$id%>-DependsOn" value="<% $ARGSRef->{"$id-DependsOn"} || '' %>" /></td>
+ <td class="entry"><input name="<%$id%>-DependsOn" value="<% $ARGSRef->{"$id-DependsOn"} || '' %>" data-autocomplete="Tickets" data-autocomplete-exclude="<% $id %>" data-autocomplete-multiple="1" /></td>
</tr>
<tr>
<td class="label"><& ShowRelationLabel, Object => $Object, Label => loc('Depended on by').':', Relation => 'DependedOnBy' &></td>
- <td class="entry"><input name="DependsOn-<%$id%>" value="<% $ARGSRef->{"DependsOn-$id"} || '' %>" /></td>
+ <td class="entry"><input name="DependsOn-<%$id%>" value="<% $ARGSRef->{"DependsOn-$id"} || '' %>" data-autocomplete="Tickets" data-autocomplete-exclude="<% $id %>" data-autocomplete-multiple="1" /></td>
</tr>
<tr>
<td class="label"><& ShowRelationLabel, Object => $Object, Label => loc('Parents').':', Relation => 'Parents' &></td>
- <td class="entry"><input name="<%$id%>-MemberOf" value="<% $ARGSRef->{"$id-MemberOf"} || '' %>" /></td>
+ <td class="entry"><input name="<%$id%>-MemberOf" value="<% $ARGSRef->{"$id-MemberOf"} || '' %>" data-autocomplete="Tickets" data-autocomplete-exclude="<% $id %>" data-autocomplete-multiple="1" /></td>
</tr>
<tr>
<td class="label"><& ShowRelationLabel, Object => $Object, Label => loc('Children').':', Relation => 'Children' &></td>
- <td class="entry"> <input name="MemberOf-<%$id%>" value="<% $ARGSRef->{"MemberOf-$id"} || '' %>" /></td>
+ <td class="entry"> <input name="MemberOf-<%$id%>" value="<% $ARGSRef->{"MemberOf-$id"} || '' %>" data-autocomplete="Tickets" data-autocomplete-exclude="<% $id %>" data-autocomplete-multiple="1" /></td>
</tr>
<tr>
<td class="label"><& ShowRelationLabel, Object => $Object, Label => loc('Refers to').':', Relation => 'RefersTo' &></td>
- <td class="entry"><input name="<%$id%>-RefersTo" value="<% $ARGSRef->{"$id-RefersTo"} || '' %>" /></td>
+ <td class="entry"><input name="<%$id%>-RefersTo" value="<% $ARGSRef->{"$id-RefersTo"} || '' %>" data-autocomplete="Tickets" data-autocomplete-exclude="<% $id %>" data-autocomplete-multiple="1" /></td>
</tr>
<tr>
<td class="label"><& ShowRelationLabel, Object => $Object, Label => loc('Referred to by').':', Relation => 'ReferredToBy' &></td>
- <td class="entry"> <input name="RefersTo-<%$id%>" value="<% $ARGSRef->{"RefersTo-$id"} || '' %>" /></td>
+ <td class="entry"> <input name="RefersTo-<%$id%>" value="<% $ARGSRef->{"RefersTo-$id"} || '' %>" data-autocomplete="Tickets" data-autocomplete-exclude="<% $id %>" data-autocomplete-multiple="1" /></td>
</tr>
<& /Elements/EditCustomFields,
Object => $Object,
diff --git a/share/html/Helpers/Autocomplete/Tickets b/share/html/Helpers/Autocomplete/Tickets
new file mode 100644
index 0000000..251bf64
--- /dev/null
+++ b/share/html/Helpers/Autocomplete/Tickets
@@ -0,0 +1,114 @@
+%# BEGIN BPS TAGGED BLOCK {{{
+%#
+%# COPYRIGHT:
+%#
+%# This software is Copyright (c) 1996-2013 Best Practical Solutions, LLC
+%# <jesse at 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 }}}
+% $r->content_type('application/json; charset=utf-8');
+<% JSON::to_json( \@suggestions ) |n %>
+% $m->abort;
+<%ARGS>
+$return => ''
+$term => undef
+$max => 10
+$exclude => ''
+</%ARGS>
+<%INIT>
+require JSON;
+
+# Only allow certain return fields
+$return = 'id'
+ unless $return =~ /^(?:id|Subject)$/;
+
+$m->abort unless defined $return
+ and defined $term
+ and length $term;
+
+my $CurrentUser = $session{'CurrentUser'};
+
+# Require privileged users
+$m->abort unless $CurrentUser->Privileged;
+
+$term =~ s/(['\\])/\\$1/g;
+
+my @excludes;
+
+if ( $term =~ /[^\d\s]/ ) { # to search subject, which may contain spaces
+ (my $prev, $term) = $term =~ /(.*?)\s*\b([^\d\s].*)/;
+ @excludes = split /\s+/, $prev if $prev;
+}
+else {
+ my @terms = split /\s+/, $term;
+ $term = pop @terms;
+ @excludes = @terms;
+}
+push @excludes, split /\s+/, $exclude if $exclude;
+
+my %fields = %{ RT->Config->Get('TicketAutocompleteFields')
+ || { id => 'STARTSWITH', Subject => 'LIKE' } };
+
+my $tickets = RT::Tickets->new( $CurrentUser );
+
+my @clauses;
+while (my ($name, $op) = each %fields) {
+ $op = 'STARTSWITH'
+ unless $op =~ /^(?:LIKE|(?:START|END)SWITH|=|!=)$/i;
+ push @clauses, qq{$name $op '$term'};
+}
+my $sql = join ' OR ', @clauses;
+if ( @excludes ) { # exclude ids already these
+ $sql = join ' AND ', "($sql)", map { qq{id != '$_'} } @excludes;
+}
+
+$tickets->FromSQL($sql);
+$tickets->RowsPerPage( $max );
+
+my @suggestions;
+
+while ( my $ticket = $tickets->Next ) {
+ my $formatted = loc("#[_1]: [_2]", $ticket->Id, $ticket->Subject);
+ push @suggestions, { label => $formatted, value => $ticket->$return };
+}
+
+</%INIT>
diff --git a/share/static/js/autocomplete.js b/share/static/js/autocomplete.js
index f000df0..21099c3 100644
--- a/share/static/js/autocomplete.js
+++ b/share/static/js/autocomplete.js
@@ -10,7 +10,7 @@ jQuery(function() {
var what = input.attr("data-autocomplete");
var wants = input.attr("data-autocomplete-return");
- if (!what || !what.match(/^(Users|Groups)$/))
+ if (!what || !what.match(/^(Users|Groups|Tickets)$/))
return;
var queryargs = [];
@@ -27,7 +27,9 @@ jQuery(function() {
}
if (input.is('[data-autocomplete-multiple]')) {
- queryargs.push("delim=,");
+ if ( what != 'Tickets' ) {
+ queryargs.push("delim=,");
+ }
options.focus = function () {
// prevent value inserted on focus
@@ -35,10 +37,18 @@ jQuery(function() {
}
options.select = function(event, ui) {
- var terms = this.value.split(/,\s*/);
+ var terms = this.value.split(what == 'Tickets' ? /\s+/ : /,\s*/);
terms.pop(); // remove current input
terms.push( ui.item.value ); // add selected item
- this.value = terms.join(", ");
+ if ( what == 'Tickets' ) {
+ // remove non-integers in case subject search with spaces in (like "foo bar")
+ terms = jQuery.grep(terms, function(term) {
+ var str = term + ''; // stringify integers to call .match
+ return str.match(/^\d+$/);
+ } );
+ terms.push(''); // add trailing space so user can input another ticket id directly
+ }
+ this.value = terms.join(what == 'Tickets' ? ' ' : ", ");
return false;
}
}
-----------------------------------------------------------------------
More information about the Rt-commit
mailing list