[Rt-commit] rt branch 5.0/cf-numeric-values created. rt-5.0.2-66-gf60b6cfd5a

BPS Git Server git at git.bestpractical.com
Mon Feb 14 21:17:35 UTC 2022


This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "rt".

The branch, 5.0/cf-numeric-values has been created
        at  f60b6cfd5a295a2465b433dd61dc3ae92058dd22 (commit)

- Log -----------------------------------------------------------------
commit f60b6cfd5a295a2465b433dd61dc3ae92058dd22
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Tue Feb 15 03:57:43 2022 +0800

    Test searching/sorting cf values numerically

diff --git a/t/ticket/search_by_cf_numeric.t b/t/ticket/search_by_cf_numeric.t
new file mode 100644
index 0000000000..493847e307
--- /dev/null
+++ b/t/ticket/search_by_cf_numeric.t
@@ -0,0 +1,51 @@
+
+use strict;
+use warnings;
+
+use RT::Test nodata => 1, tests => undef;
+
+my $queue = RT::Test->load_or_create_queue( Name => 'General' );
+my $cf    = RT::Test->load_or_create_custom_field( Name => 'test_cf', Queue => $queue->id, Type => 'FreeformSingle' );
+ok( $cf->SetNumericValues(1) );
+my $cfid = $cf->id;
+
+my $cf2    = RT::Test->load_or_create_custom_field( Name => 'test_cf2', Queue => $queue->id, Type => 'FreeformSingle' );
+ok( $cf2->SetNumericValues(1) );
+my $cf2id = $cf2->id;
+
+my @tickets = RT::Test->create_tickets(
+    { Queue   => $queue->Name },
+    { Subject => 'Big',   "CustomField-$cfid" => 12, "CustomField-$cf2id" => 5 },
+    { Subject => 'Small', "CustomField-$cfid" => 3, "CustomField-$cf2id" => 10 },
+);
+
+my $tickets = RT::Tickets->new( RT->SystemUser );
+$tickets->FromSQL(q{Queue = 'General' AND CF.test_cf > 5 });
+is( $tickets->Count,     1,               'Found 1 ticket' );
+is( $tickets->First->id, $tickets[0]->id, 'Found the big ticket' );
+
+$tickets->FromSQL(q{Queue = 'General' AND CF.test_cf = 12 });
+is( $tickets->Count,     1,               'Found 1 ticket' );
+is( $tickets->First->id, $tickets[0]->id, 'Found the big ticket' );
+
+$tickets->FromSQL(q{Queue = 'General' AND CF.test_cf < 5});
+is( $tickets->Count,     1,               'Found 1 ticket' );
+is( $tickets->First->id, $tickets[1]->id, 'Found the small ticket' );
+
+$tickets->FromSQL(q{Queue = 'General' AND CF.test_cf = 3});
+is( $tickets->Count,     1,               'Found 1 ticket' );
+is( $tickets->First->id, $tickets[1]->id, 'Found the small ticket' );
+
+$tickets->FromSQL(q{Queue = 'General' AND CF.test_cf < CF.test_cf2 });
+is( $tickets->Count,     1,               'Found 1 ticket' );
+is( $tickets->First->id, $tickets[1]->id, 'Found the small ticket' );
+
+$tickets->FromSQL(q{Queue = 'General'});
+is( $tickets->Count, 2, 'Found 2 tickets' );
+$tickets->OrderByCols( { FIELD => 'CustomField.test_cf' } );
+is( $tickets->First->id, $tickets[1]->id, 'Small ticket first' );
+
+$tickets->OrderByCols( { FIELD => 'CustomField.test_cf', ORDER => 'DESC' } );
+is( $tickets->First->id, $tickets[0]->id, 'Big ticket first' );
+
+done_testing;

commit 319e20b7c6a63c5becaecd18f6290a921dbaf215
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Sat Feb 12 00:32:46 2022 +0800

    Support to search/sort cf values numerically

diff --git a/lib/RT/CustomField.pm b/lib/RT/CustomField.pm
index a89ad6a4b9..3ac00cb6f1 100644
--- a/lib/RT/CustomField.pm
+++ b/lib/RT/CustomField.pm
@@ -2385,6 +2385,61 @@ sub CleanupDefaultValues {
     }
 }
 
+=head2 SupportNumericValues
+
+Return true if this custom field supports numeric values, false otherwise.
+
+Currently it's supported for Freeform/Select/Combobox/Autocomplete.
+
+=cut
+
+sub SupportNumericValues {
+    my $self = shift;
+    return 0 unless $self->Id;
+    return $self->Type =~ /Freeform|Select|Combobox|Autocomplete/;
+}
+
+=head2 SetNumericValues BOOL
+
+Enable or Disable numeric values.
+
+=cut
+
+sub SetNumericValues {
+    my $self  = shift;
+    my $value = shift || 0;
+    return 0 unless $self->Id;
+    return 1 if $self->NumericValues == $value;
+
+    my ( $ret, $msg ) = $self->SetAttribute( Name => 'NumericValues', Description => '', Content => $value );
+    if ($ret) {
+        if ($value) {
+            return ( $ret, $self->loc('Numeric values enabled') );
+        }
+        else {
+            return ( $ret, $self->loc('Numeric values disabled') );
+        }
+    }
+    else {
+        return ( $ret, $self->loc( "Can't set NumericValues to [_1]: [_2]", $value, $msg ) );
+    }
+}
+
+=head2 NumericValues
+
+Return true if this custom field has numeric values enabled, false otherwise.
+
+=cut
+
+sub NumericValues {
+    my $self = shift;
+    return 0 unless $self->Id && $self->SupportNumericValues;
+    if ( my $attr = $self->FirstAttribute('NumericValues') ) {
+        return $attr->Content;
+    }
+    return 0;
+}
+
 =head2 id
 
 Returns the current value of id. 
diff --git a/lib/RT/SearchBuilder.pm b/lib/RT/SearchBuilder.pm
index caf1d035b1..fed79ed800 100644
--- a/lib/RT/SearchBuilder.pm
+++ b/lib/RT/SearchBuilder.pm
@@ -169,8 +169,15 @@ sub _OrderByCF {
         ENTRYAGGREGATOR => 'AND'
     );
 
-    return { %$row, ALIAS => $CFvs,  FIELD => 'SortOrder' },
-           { %$row, ALIAS => $ocfvs, FIELD => 'Content' };
+    return { %$row, ALIAS => $CFvs, FIELD => 'SortOrder' },
+        {
+            %$row,
+            ALIAS => $ocfvs,
+            FIELD => 'Content',
+            blessed $cf && $cf->NumericValues
+            ? ( FUNCTION => $self->_CastToDecimal('Content') )
+            : ()
+        };
 }
 
 sub OrderByCols {
@@ -529,9 +536,20 @@ sub _LimitCustomField {
 
 
     my $fix_op = sub {
+        my %args = @_;
+
+        if (   $args{'FIELD'} eq 'Content'
+            && blessed $cf
+            && $cf->NumericValues
+            && ( !$args{QUOTEVALUE} || Scalar::Util::looks_like_number($args{'VALUE'}) ) )
+        {
+            $args{QUOTEVALUE} = 0;
+            $args{FUNCTION} = $self->_CastToDecimal( "$args{ALIAS}.$args{FIELD}" );
+            return %args;
+        }
+
         return @_ unless RT->Config->Get('DatabaseType') eq 'Oracle';
 
-        my %args = @_;
         return %args unless $args{'FIELD'} eq 'LargeContent';
 
         my $op = $args{'OPERATOR'};
@@ -827,17 +845,18 @@ sub _LimitCustomField {
         );
     } else {
         # Otherwise, go looking at the Content
-        $self->Limit(
+        $self->Limit( $fix_op->(
             %args,
             ALIAS    => $ocfvalias,
             FIELD    => 'Content',
             OPERATOR => $op,
             VALUE    => $value,
             CASESENSITIVE => 0,
-        );
+        ) );
     }
 
-    if (!$value_is_long and $op eq "=") {
+    if ( ( blessed($cf) and $cf->NumericValues ) or ( !$value_is_long and $op eq "=" ) ) {
+        # Skip LargeContent comparison for numeric values.
         # Doesn't matter what LargeContent contains, as it cannot match
         # the short value.
     } elsif (!$value_is_long and $op =~ /^(!=|<>)$/) {
@@ -1140,6 +1159,23 @@ sub DistinctFieldValues {
     return @values;
 }
 
+sub _CastToDecimal {
+    my $self = shift;
+    my $field = shift or return;
+
+    my $db_type = RT->Config->Get('DatabaseType');
+    if ( $db_type eq 'Oracle' ) {
+        return "TO_NUMBER($field)";
+    }
+    elsif ( $db_type eq 'mysql' ) {
+        # mysql's CAST decimal requires precision specification, which we don't know.
+        return "($field+0)";
+    }
+    else {
+        return "CAST($field AS DECIMAL)";
+    }
+}
+
 RT::Base->_ImportOverlays();
 
 1;
diff --git a/lib/RT/Tickets.pm b/lib/RT/Tickets.pm
index 4ffbd6ab61..43adffe1ce 100644
--- a/lib/RT/Tickets.pm
+++ b/lib/RT/Tickets.pm
@@ -1568,7 +1568,14 @@ sub OrderByCols {
                         ENTRYAGGREGATOR => 'AND'
                     );
                     push @res, { %$row, ALIAS => $CFvs, FIELD => 'SortOrder' },
-                        { %$row, ALIAS => $ocfvs, FIELD => 'Content' };
+                        {
+                            %$row,
+                            ALIAS => $ocfvs,
+                            FIELD => 'Content',
+                            blessed $cf && $cf->NumericValues
+                            ? ( FUNCTION => $self->_CastToDecimal('Content') )
+                            : ()
+                        };
                 }
                 else {
                     RT->Logger->warning("Couldn't load user custom field $cf_name");
@@ -3462,28 +3469,39 @@ sub _parser {
                 $value = "main.$value" if $class eq 'RT::Tickets' && $value =~ /^\w+$/;
 
                 if ( $class eq 'RT::ObjectCustomFieldValues' ) {
+                    my $cast_to;
+                    if ( $meta->[0] eq 'CUSTOMFIELD' ) {
+                        my ($object, $field, $cf, $column) = $self->_CustomFieldDecipher( $subkey );
+                        if ( $cf && $cf->NumericValues ) {
+                            $cast_to = 'DECIMAL';
+                        }
+                    }
+
                     if ( RT->Config->Get('DatabaseType') eq 'Pg' ) {
-                        my $cast_to;
-                        if ($subkey) {
+                        if ( !$cast_to ) {
+                            if ($subkey) {
 
-                            # like Requestor.id
-                            if ( $subkey eq 'id' ) {
-                                $cast_to = 'INTEGER';
-                            }
-                        }
-                        elsif ( my $meta = $self->RecordClass->_ClassAccessible->{$key} ) {
-                            if ( $meta->{is_numeric} ) {
-                                $cast_to = 'INTEGER';
+                                # like Requestor.id
+                                if ( $subkey eq 'id' ) {
+                                    $cast_to = 'INTEGER';
+                                }
                             }
-                            elsif ( $meta->{type} eq 'datetime' ) {
-                                $cast_to = 'TIMESTAMP';
+                            elsif ( my $meta = $self->RecordClass->_ClassAccessible->{$key} ) {
+                                if ( $meta->{is_numeric} ) {
+                                    $cast_to = 'INTEGER';
+                                }
+                                elsif ( $meta->{type} eq 'datetime' ) {
+                                    $cast_to = 'TIMESTAMP';
+                                }
                             }
                         }
-
                         $value = "CAST($value AS $cast_to)" if $cast_to;
                     }
                     elsif ( RT->Config->Get('DatabaseType') eq 'Oracle' ) {
-                        if ($subkey) {
+                        if ( $cast_to && $cast_to eq 'DECIMAL' ) {
+                            $value = "TO_NUMBER($value)";
+                        }
+                        elsif ($subkey) {
 
                             # like Requestor.id
                             if ( $subkey eq 'id' ) {
diff --git a/share/html/Admin/CustomFields/Modify.html b/share/html/Admin/CustomFields/Modify.html
index 324630a7ab..30edfe080d 100644
--- a/share/html/Admin/CustomFields/Modify.html
+++ b/share/html/Admin/CustomFields/Modify.html
@@ -217,6 +217,21 @@ jQuery( function() {
   </div>
 </div>
 
+% if ( $CustomFieldObj->Id && $CustomFieldObj->SupportNumericValues ) {
+<div class="form-row">
+  <div class="label col-3"></div>
+  <div class="value col-9">
+    <input type="hidden" class="hidden" name="SetNumericValues" value="1" />
+    <div class="custom-control custom-checkbox">
+      <input type="checkbox" class="custom-control-input" id="NumericValues" name="NumericValues" value="1" <% $NumericValuesChecked |n %> />
+      <label class="custom-control-label" for="NumericValues">
+        <&|/l&>Values are numeric</&>
+      </label>
+    </div>
+  </div>
+</div>
+% }
+
 % $m->callback(CallbackName => 'BeforeEnabled', CustomField => $CustomFieldObj, CFvalidations => \@CFvalidations);
 
 <div class="form-row">
@@ -469,6 +484,10 @@ if ( $ARGS{'Update'} && $id ne 'new' ) {
         }
     }
 
+    if ( $CustomFieldObj->SupportNumericValues ) {
+        my ( $ret, $msg ) = $CustomFieldObj->SetNumericValues( $NumericValues ? 1 : 0 );
+        push @results, $msg;
+    }
 }
 
 # for old tests only, users should use ajax calls and this should never be called in real usage.
@@ -518,6 +537,9 @@ $EnabledChecked = '' if $CustomFieldObj->Disabled;
 my $UniqueValuesChecked = qq[checked="checked"];
 $UniqueValuesChecked = '' if !$CustomFieldObj->UniqueValues;
 
+my $NumericValuesChecked = '';
+$NumericValuesChecked = qq[checked="checked"] if $CustomFieldObj->NumericValues;
+
 my @CFvalidations = (
     '(?#Mandatory).',
     '(?#Digits)^[\d.]+$',
@@ -540,6 +562,7 @@ $SetEnabled => undef
 $Enabled => 0
 $SetUniqueValues => undef
 $UniqueValues => 0
+$NumericValues => 0
 $ValuesClass => 'RT::CustomFieldValues'
 $CanonicalizeClass => undef
 $RenderType => undef

-----------------------------------------------------------------------


hooks/post-receive
-- 
rt


More information about the rt-commit mailing list