[Rt-commit] rt branch, 4.4/columns-as-values-in-ticket-search, created. rt-4.4.4-8-g1cad1ce87
Jim Brandt
jbrandt at bestpractical.com
Fri Apr 5 16:27:09 EDT 2019
The branch, 4.4/columns-as-values-in-ticket-search has been created
at 1cad1ce87520dffaab889966870ee9b6a9da523c (commit)
- Log -----------------------------------------------------------------
commit 528277f60977be25aa66d44a3d91bdd8619999e1
Author: sunnavy <sunnavy at bestpractical.com>
Date: Wed Mar 27 05:11:08 2019 +0800
Support columns as values in ticket search
So we can compare 2 fields directly, e.g.
LastUpdated < Due
To search tickets with multiple CFs being set to the same value, e.g.
CF.Foo IS NOT NULL AND CF.Bar = ObjectCustomFieldValues_1.Content
diff --git a/lib/RT/Interface/Web/QueryBuilder/Tree.pm b/lib/RT/Interface/Web/QueryBuilder/Tree.pm
index 1ab187794..1440d2866 100644
--- a/lib/RT/Interface/Web/QueryBuilder/Tree.pm
+++ b/lib/RT/Interface/Web/QueryBuilder/Tree.pm
@@ -220,7 +220,7 @@ sub __LinearizeTree {
if ( $op =~ /^IS( NOT)?$/i ) {
$value = 'NULL';
- } elsif ( $value !~ /^[+-]?[0-9]+$/ ) {
+ } elsif ( $clause->{QuoteValue} ) {
$value =~ s/(['\\])/\\$1/g;
$value = "'$value'";
}
@@ -269,7 +269,7 @@ sub ParseSQL {
$callback{'CloseParen'} = sub { $node = $node->getParent };
$callback{'EntryAggregator'} = sub { $node->setNodeValue( $_[0] ) };
$callback{'Condition'} = sub {
- my ($key, $op, $value) = @_;
+ my ($key, $op, $value, $value_is_quoted) = @_;
my ($main_key, $subkey) = split /[.]/, $key, 2;
@@ -281,9 +281,14 @@ sub ParseSQL {
# Hardcode value for IS / IS NOT
$value = 'NULL' if $op =~ /^IS( NOT)?$/i;
- my $clause = { Key => $main_key, Subkey => $subkey,
- Meta => $field{ $main_key },
- Op => $op, Value => $value };
+ my $clause = {
+ Key => $main_key,
+ Subkey => $subkey,
+ Meta => $field{$main_key},
+ Op => $op,
+ Value => $value,
+ QuoteValue => $value_is_quoted,
+ };
$node->addChild( __PACKAGE__->new( $clause ) );
};
$callback{'Error'} = sub { push @results, @_ };
diff --git a/lib/RT/SQL.pm b/lib/RT/SQL.pm
index b3a396a99..d4c7c8057 100644
--- a/lib/RT/SQL.pm
+++ b/lib/RT/SQL.pm
@@ -64,7 +64,7 @@ my @tokens = qw[VALUE AGGREGATOR OPERATOR OPEN_PAREN CLOSE_PAREN KEYWORD];
use Regexp::Common qw /delimited/;
my $re_aggreg = qr[(?i:AND|OR)];
my $re_delim = qr[$RE{delimited}{-delim=>qq{\'\"}}];
-my $re_value = qr[[+-]?\d+|(?i:NULL)|$re_delim];
+my $re_value = qr[[\w\.]+|[+-]?\d+|(?i:NULL)|$re_delim];
my $re_keyword = qr[[{}\w\.]+|$re_delim];
my $re_op = qr[=|!=|>=|<=|>|<|(?i:IS NOT)|(?i:IS)|(?i:NOT LIKE)|(?i:LIKE)|(?i:NOT STARTSWITH)|(?i:STARTSWITH)|(?i:NOT ENDSWITH)|(?i:ENDSWITH)]; # long to short
my $re_open_paren = qr[\(];
@@ -169,7 +169,8 @@ sub Parse {
s!\\(.)!$1!g;
}
- $cb->{'Condition'}->( $key, $op, $value );
+ my $value_is_quoted = $match =~ $re_delim ? 1 : 0;
+ $cb->{'Condition'}->( $key, $op, $value, $value_is_quoted );
($key,$op,$value) = ("","","");
$want = AGGREG;
diff --git a/lib/RT/Tickets.pm b/lib/RT/Tickets.pm
index 5dcd50f07..6cce1f595 100644
--- a/lib/RT/Tickets.pm
+++ b/lib/RT/Tickets.pm
@@ -631,10 +631,14 @@ sub _DateLimit {
);
}
- my $date = RT::Date->new( $sb->CurrentUser );
- $date->Set( Format => 'unknown', Value => $value );
+ my $date;
+ if ( $rest{QUOTEVALUE} ) {
+ $date = RT::Date->new( $sb->CurrentUser );
+ $date->Set( Format => 'unknown', Value => $value );
+ }
- if ( $op eq "=" ) {
+
+ if ( $op eq "=" && $date ) {
# if we're specifying =, that means we want everything on a
# particular single day. in the database, we need to check for >
@@ -667,10 +671,9 @@ sub _DateLimit {
}
else {
$sb->Limit(
- FUNCTION => $sb->NotSetDateToNullFunction,
FIELD => $meta->[1],
OPERATOR => $op,
- VALUE => $date->ISO,
+ VALUE => $date ? $date->ISO : $value,
%rest,
);
}
@@ -3161,8 +3164,8 @@ sub _parser {
$ea = $node->getParent->getNodeValue if $node->getIndex > 0;
return $self->_OpenParen unless $node->isLeaf;
- my ($key, $subkey, $meta, $op, $value, $bundle)
- = @{$node->getNodeValue}{qw/Key Subkey Meta Op Value Bundle/};
+ my ($key, $subkey, $meta, $op, $value, $bundle, $quote_value)
+ = @{$node->getNodeValue}{qw/Key Subkey Meta Op Value Bundle QuoteValue/};
# normalize key and get class (type)
my $class = $meta->[0];
@@ -3176,12 +3179,38 @@ sub _parser {
my $sub = $dispatch{ $class }
or die "No dispatch method for class '$class'";
+ if ( !$quote_value && $value !~ /^(?:[+-]?[0-9]+|NULL)$/i ) {
+ my ( $class, $field );
+
+ # e.g. ObjectCustomFieldValues_1.Content
+ if ( $value =~ /^(\w+?)(?:_\d+)?\.(\w+)$/ ) {
+ my $table = $1;
+ $field = $2;
+ $class = $table =~ /main/i ? 'RT::Tickets' : "RT::$table";
+ }
+ else {
+ $class = 'RT::Tickets';
+ $field = $value;
+ }
+
+ my $valid;
+ if ( $class->can('RecordClass')
+ and ( my $record_class = $class->RecordClass ) )
+ {
+ $valid = $record_class->_ClassAccessible->{$field}
+ && $record_class->_ClassAccessible->{$field}{read};
+ }
+
+ die $self->loc( "Wrong query, no such column '[_1]' in '[_2]'", $value, $string ) unless $valid;
+ }
+
# A reference to @res may be pushed onto $sub_tree{$key} from
# above, and we fill it here.
$sub->( $self, $key, $op, $value,
ENTRYAGGREGATOR => $ea,
SUBKEY => $subkey,
BUNDLE => $bundle,
+ QUOTEVALUE => $quote_value,
);
},
sub {
diff --git a/share/html/Search/Build.html b/share/html/Search/Build.html
index b887b81e9..0acb07ae9 100644
--- a/share/html/Search/Build.html
+++ b/share/html/Search/Build.html
@@ -235,6 +235,7 @@ foreach my $arg ( keys %ARGS ) {
Key => $keyword,
Op => $op,
Value => $value,
+ QuoteValue => $value =~ /^[+-]?[0-9]+$/ ? 0 : 1,
};
push @new_values, RT::Interface::Web::QueryBuilder::Tree->new($clause);
commit 10c640ef34bf49171934cceab4edee91072c99af
Author: sunnavy <sunnavy at bestpractical.com>
Date: Wed Mar 27 05:16:09 2019 +0800
Add/Update tests for columns as values in ticket search
diff --git a/t/api/tickets.t b/t/api/tickets.t
index 407981baa..38dd0247e 100644
--- a/t/api/tickets.t
+++ b/t/api/tickets.t
@@ -151,14 +151,31 @@ ok( $unlimittickets->Count > 0, "UnLimited tickets object should return tickets"
warning_like {
( $ret, $msg ) = $tickets->FromSQL( "LastUpdated < yesterday" );
}
- qr/Couldn't parse query: Wrong query, expecting a VALUE in 'LastUpdated < >yesterday<--here'/;
+ qr/Wrong query, no such column 'yesterday' in 'LastUpdated < yesterday'/;
ok( !$ret, 'Invalid query' );
like(
$msg,
- qr/Wrong query, expecting a VALUE in 'LastUpdated < >yesterday<--here'/,
+ qr/Wrong query, no such column 'yesterday' in 'LastUpdated < yesterday'/,
'Invalid query message'
);
}
+{
+ my $ticket = RT::Ticket->new( RT->SystemUser );
+ ok $ticket->Load(1), "Loaded test ticket 1";
+ my $date = RT::Date->new(RT->SystemUser);
+ $date->SetToNow();
+ $date->AddDays(1);
+
+ ok $ticket->SetDue( $date->ISO ), "Set Due to tomorrow";
+ my $tickets = RT::Tickets->new( RT->SystemUser );
+ my ( $ret, $msg ) = $tickets->FromSQL("LastUpdated < Due");
+
+ ok( $ret, 'Ran query with Due as searched value' );
+ my $count = $tickets->Count();
+ ok $count == 1, "Found one ticket";
+ undef $count;
+}
+
done_testing;
commit 1cad1ce87520dffaab889966870ee9b6a9da523c
Author: sunnavy <sunnavy at bestpractical.com>
Date: Tue Apr 2 22:20:24 2019 +0800
Document columns as values in ticket search
diff --git a/docs/query_builder.pod b/docs/query_builder.pod
index 1195ea06e..d778312d1 100644
--- a/docs/query_builder.pod
+++ b/docs/query_builder.pod
@@ -158,3 +158,48 @@ piece of RT metadata, please see the Definitions of Ticket Metadata document.
=cut
+=head1 Advanced
+
+In addition to the graphical query builder, RT also has an Advanced page
+where you can write and modify queries and formats directly. For example,
+you can type the following query directly in the Query box:
+
+ Status = 'resolved' AND Resolved > '2019-04-01' AND Queue = 'RT'
+
+and then click "Apply" to let RT validate it. RT will display any syntax errors
+if you make any mistakes so you can fix them.
+
+=head2 Valid Search Values
+
+In the above example, search values like C<'resolved'>, C<'2019-04-01'>,
+and C<'RT'> are all literal search terms: a status, a date, and a string
+representing the name of a queue. These are all static values that will then
+return matching tickets. However, sometimes you want to compare 2 columns
+in RT tables without knowing exact values. This is also supported, for
+example:
+
+=over
+
+=item Search tickets where LastUpdated is after (later than) Resolved
+
+ LastUpdated > Resolved
+
+This finds tickets that have been commented on or otherwise updated after
+they were resolved. Note that C<Resolved> is not quoted, indicating that it's
+the relative date value from a ticket instead of the literal string "Resolved".
+
+=item Search tickets where the Requestor is also the Owner
+
+ Requestor.id = Owner
+
+=item Search tickets where custom fields Foo and Bar have the same value
+
+ CF.Foo IS NOT NULL AND CF.Bar = ObjectCustomFieldValues_1.Content
+
+This advanced query requires some knowledge of RT's search internals,
+but following this example should allow most users to construct a
+similar query by changing the custom field names. It works because
+C<CF.Foo> appears first as a custom field in the query and RT then
+sets I<ObjectCustomFieldValues_1.Content> as its Content.
+
+=back
-----------------------------------------------------------------------
More information about the rt-commit
mailing list