From c28fc8e38c327328810ef9293f4a7733ad662964 Mon Sep 17 00:00:00 2001 From: gibus Date: Sun, 18 Dec 2016 22:46:56 +0100 Subject: [PATCH] Add autocomplete for assets Add support of autocompletion for assets (with RT::Assets-SimpleSearch, therefore using RT->Config->Get('AssetSearchFields') and RT->Config->Get('DefaultCatalog')) in field "Add an asset to this ticket" of the ticket display form. Also add support of completion both for tickets and for assets in each input field of the AddLinks form. Completion is done for tickets (as usual) until the entered term starts with "asset:", where the completion is switched for assets. Signed-off-by: gibus --- share/html/Elements/AddLinks | 47 ++++++++++++++--- share/html/Helpers/Autocomplete/Assets | 50 +++++++++++++++++++ share/html/Helpers/Autocomplete/Tickets | 3 ++ share/html/Helpers/Autocomplete/TicketsAssets | 26 ++++++++++ share/html/Ticket/Elements/ShowAssets | 2 +- share/static/js/autocomplete.js | 14 +++--- 6 files changed, 127 insertions(+), 15 deletions(-) create mode 100644 share/html/Helpers/Autocomplete/Assets create mode 100644 share/html/Helpers/Autocomplete/TicketsAssets diff --git a/share/html/Elements/AddLinks b/share/html/Elements/AddLinks index 840303b7b90..b1b46819e5b 100644 --- a/share/html/Elements/AddLinks +++ b/share/html/Elements/AddLinks @@ -55,8 +55,9 @@ my $id = ($Object and $Object->id) ? $Object->id : "new"; -my $exclude = qq| data-autocomplete="Tickets" data-autocomplete-multiple="1"|; -$exclude .= qq| data-autocomplete-exclude="$id"| if $Object->id; +my $exclude = qq| data-autocomplete="TicketsAssets" data-autocomplete-multiple="1"|; +my @excludes; +push @excludes, $id if $Object->id; % if (ref($Object) eq 'RT::Ticket') { <&|/l&>Enter tickets or URIs to link tickets to. Separate multiple entries with spaces. @@ -73,27 +74,57 @@ $exclude .= qq| data-autocomplete-exclude="$id"| if $Object->id; - +% my @excludes_dependson; +% while (my $link = $Object->DependsOn->Next) { +% push @excludes_dependson, ((UNIVERSAL::isa($link->TargetObj, 'RT::Asset') ? 'asset:' : '') . $link->TargetObj->id); +% } +% my $exclude_dependson = $exclude . ' data-autocomplete-exclude="' .join(' ', (@excludes, @excludes_dependson)). '"' if @excludes_dependson || @excludes; + - +% my @excludes_dependedonby; +% while (my $link = $Object->DependedOnBy->Next) { +% push @excludes_dependedonby, ((UNIVERSAL::isa($link->BaseObj, 'RT::Asset') ? 'asset:' : '') . $link->BaseObj->id); +% } +% my $exclude_dependonby = $exclude . ' data-autocomplete-exclude="' .join(' ', (@excludes, @excludes_dependedonby)). '"' if @excludes_dependedonby || @excludes; + - +% my @excludes_memberof; +% while (my $link = $Object->MemberOf->Next) { +% push @excludes_memberof, ((UNIVERSAL::isa($link->TargetObj, 'RT::Asset') ? 'asset:' : '') . $link->TargetObj->id); +% } +% my $exclude_memberof = $exclude . ' data-autocomplete-exclude="' .join(' ', (@excludes, @excludes_memberof)). '"' if @excludes_memberof || @excludes; + - +% my @excludes_members; +% while (my $link = $Object->Members->Next) { +% push @excludes_members, ((UNIVERSAL::isa($link->BaseObj, 'RT::Asset') ? 'asset:' : '') . $link->BaseObj->id); +% } +% my $exclude_members = $exclude . ' data-autocomplete-exclude="' .join(' ', (@excludes, @excludes_members)). '"' if @excludes_members || @excludes; + - +% my @excludes_refersto; +% while (my $link = $Object->RefersTo->Next) { +% push @excludes_refersto, ((UNIVERSAL::isa($link->TargetObj, 'RT::Asset') ? 'asset:' : '') . $link->TargetObj->id); +% } +% my $exclude_refersto = $exclude . ' data-autocomplete-exclude="' .join(' ', (@excludes, @excludes_refersto)). '"' if @excludes_refersto || @excludes; + - +% my @excludes_referredtoby; +% while (my $link = $Object->ReferredToBy->Next) { +% push @excludes_referredtoby, ((UNIVERSAL::isa($link->BaseObj, 'RT::Asset') ? 'asset:' : '') . $link->BaseObj->id); +% } +% my $exclude_referredtoby = $exclude . ' data-autocomplete-exclude="' .join(' ', (@excludes, @excludes_referredtoby)). '"' if @excludes_referredtoby || @excludes; + <& /Elements/EditCustomFields, Object => $Object, diff --git a/share/html/Helpers/Autocomplete/Assets b/share/html/Helpers/Autocomplete/Assets new file mode 100644 index 00000000000..d1e80d36888 --- /dev/null +++ b/share/html/Helpers/Autocomplete/Assets @@ -0,0 +1,50 @@ +% $r->content_type('application/json; charset=utf-8'); +<% JSON(\@suggestions) |n %> +% $m->abort; + +<%args> +$term => undef +$max => undef +$exclude => '' +$op => 'LIKE' +$return_suggestions => 0 + + +<%init> +# Verify minimum data +$m->abort unless defined $term + and length $term; + +my $CurrentUser = $session{'CurrentUser'}; + +# Require privileged users +$m->abort unless $CurrentUser->Privileged; + +# Sanity check the operator +$op = 'LIKE' unless $op =~ /^(?:LIKE|(?:START|END)SWITH|=|!=)$/i; + +$m->callback(CallbackName => 'ModifyMaxResults', max => \$max); +$max //= 10; + +# Load array of assets +my $assets = RT::Assets->new($session{'CurrentUser'}); + +$assets->RowsPerPage($max); +$assets->LimitToActiveStatus; +$assets->SimpleSearch(Term => $term); + +# Exclude assets we don't want +foreach (split /\s*,\s*/, $exclude) { + $assets->Limit(FIELD => 'id', VALUE => $_, OPERATOR => '!=', ENTRYAGGREGATOR => 'AND', SUBCLAUSE => 'excludeautocomplete'); +} + +# Generate suggestions +my @suggestions; +while (my $asset = $assets->Next) { + my $formatted = loc("#[_1]: [_2] ([_3])", $asset->id, $asset->Name, $asset->Status); + my $suggestion = {id => $asset->id, label => $formatted, value => $asset->id}; + $m->callback(CallbackName => "ModifySuggestion", suggestion => $suggestion, asset => $asset); + push @suggestions, $suggestion; +} +return @suggestions if $return_suggestions; + diff --git a/share/html/Helpers/Autocomplete/Tickets b/share/html/Helpers/Autocomplete/Tickets index 7c71e1d34a2..3b7e720ece7 100644 --- a/share/html/Helpers/Autocomplete/Tickets +++ b/share/html/Helpers/Autocomplete/Tickets @@ -53,6 +53,7 @@ $return => '' $term => undef $max => undef $exclude => '' +$return_suggestions => 0 <%INIT> # Only allow certain return fields @@ -63,6 +64,7 @@ $m->abort unless defined $return and defined $term and length $term; + my $CurrentUser = $session{'CurrentUser'}; # Require privileged users @@ -105,5 +107,6 @@ while ( my $ticket = $tickets->Next ) { my $formatted = loc("#[_1]: [_2]", $ticket->Id, $ticket->Subject); push @suggestions, { label => $formatted, value => $ticket->$return }; } +return @suggestions if $return_suggestions; diff --git a/share/html/Helpers/Autocomplete/TicketsAssets b/share/html/Helpers/Autocomplete/TicketsAssets new file mode 100644 index 00000000000..cb69c712b42 --- /dev/null +++ b/share/html/Helpers/Autocomplete/TicketsAssets @@ -0,0 +1,26 @@ +% $r->content_type('application/json; charset=utf-8'); +<% JSON( \@suggestions ) |n %> +% $m->abort; +<%args> +$return => '' +$term => undef +$max => undef +$exclude => '' + +<%init> +my @suggestions; +my @excludes; + +(my $prev, my $type, $term) = $term =~ /^((?:(asset:)?\d+\s+)*)(.*)/; +@excludes = split ' ', $prev if $prev; +push @excludes, split ' ', $exclude if $exclude; + +if ($term =~ /^asset:./) { + my $exclude_assets = join(',', map(/(\d+)/, grep(/^asset:\d+$/, @excludes))); + @suggestions = $m->comp('Assets', term => substr($term, 6), max => $max, exclude => $exclude_assets, return_suggestions => 1); + @suggestions = map { {id => $_->{id}, label => $_->{label}, value => 'asset:' . $_->{value}} } @suggestions; +} else { + my $exclude_tickets = join(' ', grep(/^\d+$/, @excludes)); + @suggestions = $m->comp('Tickets', return => $return, term => $term, max => $max, exclude => $exclude_tickets, return_suggestions => 1); +} + diff --git a/share/html/Ticket/Elements/ShowAssets b/share/html/Ticket/Elements/ShowAssets index 113b6ccedd1..9402dbf972a 100644 --- a/share/html/Ticket/Elements/ShowAssets +++ b/share/html/Ticket/Elements/ShowAssets @@ -206,7 +206,7 @@ if ($ShowRelatedTickets) {
diff --git a/share/static/js/autocomplete.js b/share/static/js/autocomplete.js index cd8ab2b0d43..41c650ab823 100644 --- a/share/static/js/autocomplete.js +++ b/share/static/js/autocomplete.js @@ -5,7 +5,9 @@ window.RT.Autocomplete.Classes = { Users: 'user', Groups: 'group', Tickets: 'tickets', - Queues: 'queues' + Queues: 'queues', + Assets: 'assets', + TicketsAssets: 'tickets-assets' }; window.RT.Autocomplete.bind = function(from) { @@ -49,7 +51,7 @@ window.RT.Autocomplete.bind = function(from) { } if (input.is('[data-autocomplete-multiple]')) { - if ( what != 'Tickets' ) { + if ( what != 'Tickets' && what != 'TicketsAssets' ) { queryargs.push("delim=,"); } @@ -59,18 +61,18 @@ window.RT.Autocomplete.bind = function(from) { } options.select = function(event, ui) { - var terms = this.value.split(what == 'Tickets' ? /\s+/ : /,\s*/); + var terms = this.value.split((what == 'Tickets' || what == 'TicketsAssets') ? /\s+/ : /,\s*/); terms.pop(); // remove current input terms.push( ui.item.value ); // add selected item - if ( what == 'Tickets' ) { + if ( what == 'Tickets' || what == 'TicketsAssets') { // 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+$/); + return str.match(/^(?:asset:)?\d+$/); } ); } terms.push(''); // add trailing delimeter so user can input another value directly - this.value = terms.join(what == 'Tickets' ? ' ' : ", "); + this.value = terms.join((what == 'Tickets' || what == 'TicketsAssets') ? ' ' : ", "); jQuery(this).change(); return false;
<& ShowRelationLabel, Object => $Object, Label => loc('Depends on').':', Relation => 'DependsOn' &>" <% $exclude |n%>/>" <% $exclude_dependson |n%>/>
<& ShowRelationLabel, Object => $Object, Label => loc('Depended on by').':', Relation => 'DependedOnBy' &>" <% $exclude |n%>/>" <% $exclude_dependonby |n%>/>
<& ShowRelationLabel, Object => $Object, Label => loc('Parents').':', Relation => 'Parents' &>" <% $exclude |n%>/>" <% $exclude_memberof |n%>/>
<& ShowRelationLabel, Object => $Object, Label => loc('Children').':', Relation => 'Children' &> " <% $exclude |n%>/> " <% $exclude_members |n%>/>
<& ShowRelationLabel, Object => $Object, Label => loc('Refers to').':', Relation => 'RefersTo' &>" <% $exclude |n%>/>" <% $exclude_refersto |n%>/>
<& ShowRelationLabel, Object => $Object, Label => loc('Referred to by').':', Relation => 'ReferredToBy' &> " <% $exclude |n%>/> " <% $exclude_referredtoby |n%>/>