Browse Source

Fixes #4798 - Problem with new condition operators (starts with one of, ends with one of, is any of, is none of) in report profiles.

Rolf Schmidt 1 year ago
parent
commit
28cec1a838
2 changed files with 220 additions and 14 deletions
  1. 33 14
      app/models/ticket/selector/search_index.rb
  2. 187 0
      spec/models/ticket/selector/base_spec.rb

+ 33 - 14
app/models/ticket/selector/search_index.rb

@@ -155,7 +155,7 @@ class Ticket::Selector::SearchIndex < Ticket::Selector::Base
     t = {}
 
     # use .keyword in case of compare exact values
-    if data[:operator] == 'is' || data[:operator] == 'is not'
+    if ['is', 'is not', 'is any of', 'is none of', 'starts with one of', 'ends with one of'].include?(data[:operator])
 
       case data[:pre_condition]
       when 'not_set'
@@ -193,21 +193,37 @@ class Ticket::Selector::SearchIndex < Ticket::Selector::Base
     end
 
     # use .keyword and wildcard search in cases where query contains non A-z chars
-    if data[:operator] == 'contains' || data[:operator] == 'contains not'
+    value_is_string = Array.wrap(data[:value]).any? { |v| v.is_a?(String) && v.match?(%r{[A-z]}) }
+    if ['contains', 'contains not', 'starts with one of', 'ends with one of'].include?(data[:operator]) && value_is_string
+      wildcard_or_term = 'wildcard'
+      if !key_tmp.ends_with?('.keyword')
+        key_tmp += '.keyword'
+      end
 
       if data[:value].is_a?(Array)
-        data[:value].each_with_index do |value, index|
-          next if !value.is_a?(String) || value !~ %r{[A-z]}
+        or_condition = {
+          bool: {
+            should: [],
+          }
+        }
 
-          data[:value][index] = "*#{value}*"
-          key_tmp += '.keyword'
-          wildcard_or_term = 'wildcards'
-          break
+        data[:value].each do |value|
+          t = {}
+          t[wildcard_or_term] = {}
+          t[wildcard_or_term][key_tmp] = if data[:operator] == 'starts with one of'
+                                           "#{value}*"
+                                         elsif data[:operator] == 'ends with one of'
+                                           "*#{value}"
+                                         else
+                                           "*#{value}*"
+                                         end
+
+          or_condition[:bool][:should] << t
         end
-      elsif data[:value].is_a?(String) && %r{[A-z]}.match?(data[:value])
+
+        data[:value] = or_condition
+      else
         data[:value] = "*#{data[:value]}*"
-        key_tmp += '.keyword'
-        wildcard_or_term = 'wildcard'
       end
     end
 
@@ -228,14 +244,17 @@ class Ticket::Selector::SearchIndex < Ticket::Selector::Base
         query_must.push t
       end
 
+    elsif data[:value].is_a?(Hash) && data[:value][:bool].present?
+      query_must.push data[:value]
+
     # is/is not/contains/contains not
-    elsif ['is', 'is not', 'contains', 'contains not'].include?(data[:operator])
+    elsif ['is', 'is not', 'contains', 'contains not', 'is any of', 'is none of'].include?(data[:operator])
       t[wildcard_or_term] = {}
       t[wildcard_or_term][key_tmp] = data[:value]
       case data[:operator]
-      when 'is', 'contains'
+      when 'is', 'contains', 'is any of'
         query_must.push t
-      when 'is not', 'contains not'
+      when 'is not', 'contains not', 'is none of'
         query_must_not.push t
       end
     elsif ['contains all', 'contains one', 'contains all not', 'contains one not'].include?(data[:operator])

+ 187 - 0
spec/models/ticket/selector/base_spec.rb

@@ -446,4 +446,191 @@ RSpec.describe Ticket::Selector::Base, searchindex: true do
       end
     end
   end
+
+  describe 'Report profiles: Problem with some conditions (starts with, ends with, is any, is none) #4798' do
+    context 'when operator is any of' do
+      it 'does match', :aggregate_failures do
+        condition = {
+          operator:   'OR',
+          conditions: [
+            {
+              name:     'ticket.title',
+              operator: 'is any of',
+              value:    %w[bli bla],
+            },
+            {
+              name:     'ticket.title',
+              operator: 'is any of',
+              value:    ['blub'],
+            },
+          ]
+        }
+
+        count, = Ticket.selectors(condition, { current_user: agent })
+        expect(count).to eq(3)
+
+        result = SearchIndexBackend.selectors('Ticket', condition, { current_user: agent })
+        expect(result[:count]).to eq(3)
+      end
+
+      it 'does not match', :aggregate_failures do
+        condition = {
+          operator:   'OR',
+          conditions: [
+            {
+              name:     'ticket.title',
+              operator: 'is any of',
+              value:    %w[blix blax],
+            },
+            {
+              name:     'ticket.title',
+              operator: 'is any of',
+              value:    ['blubx'],
+            },
+          ]
+        }
+
+        count, = Ticket.selectors(condition, { current_user: agent })
+        expect(count).to eq(0)
+
+        result = SearchIndexBackend.selectors('Ticket', condition, { current_user: agent })
+        expect(result[:count]).to eq(0)
+      end
+    end
+
+    context 'when operator is none of' do
+      it 'does match', :aggregate_failures do
+        condition = {
+          operator:   'OR',
+          conditions: [
+            {
+              name:     'ticket.title',
+              operator: 'is none of',
+              value:    %w[blix blax],
+            },
+            {
+              name:     'ticket.title',
+              operator: 'is none of',
+              value:    ['blubx'],
+            },
+          ]
+        }
+
+        count, = Ticket.selectors(condition, { current_user: agent })
+        expect(count).to eq(3)
+
+        result = SearchIndexBackend.selectors('Ticket', condition, { current_user: agent })
+        expect(result[:count]).to eq(3)
+      end
+
+      it 'does not match', :aggregate_failures do
+        condition = {
+          operator:   'AND',
+          conditions: [
+            {
+              name:     'ticket.title',
+              operator: 'is none of',
+              value:    %w[bli bla],
+            },
+            {
+              name:     'ticket.title',
+              operator: 'is none of',
+              value:    ['blub'],
+            },
+          ]
+        }
+
+        count, = Ticket.selectors(condition, { current_user: agent })
+        expect(count).to eq(0)
+
+        result = SearchIndexBackend.selectors('Ticket', condition, { current_user: agent })
+        expect(result[:count]).to eq(0)
+      end
+    end
+
+    context 'when operator starts with one of' do
+      it 'does match', :aggregate_failures do
+        condition = {
+          operator:   'AND',
+          conditions: [
+            {
+              name:     'ticket.title',
+              operator: 'starts with one of',
+              value:    ['bl'],
+            },
+          ]
+        }
+
+        count, = Ticket.selectors(condition, { current_user: agent })
+        expect(count).to eq(3)
+
+        result = SearchIndexBackend.selectors('Ticket', condition, { current_user: agent })
+        expect(result[:count]).to eq(3)
+      end
+
+      it 'does not match', :aggregate_failures do
+        condition = {
+          operator:   'AND',
+          conditions: [
+            {
+              name:     'ticket.title',
+              operator: 'starts with one of',
+              value:    ['aaa'],
+            },
+          ]
+        }
+
+        count, = Ticket.selectors(condition, { current_user: agent })
+        expect(count).to eq(0)
+
+        result = SearchIndexBackend.selectors('Ticket', condition, { current_user: agent })
+        expect(result[:count]).to eq(0)
+      end
+    end
+
+    context 'when operator ends with one of' do
+      it 'does match', :aggregate_failures do
+        condition = {
+          operator:   'OR',
+          conditions: [
+            {
+              name:     'ticket.title',
+              operator: 'ends with one of',
+              value:    %w[li la],
+            },
+            {
+              name:     'ticket.title',
+              operator: 'ends with one of',
+              value:    ['ub'],
+            },
+          ]
+        }
+
+        count, = Ticket.selectors(condition, { current_user: agent })
+        expect(count).to eq(3)
+
+        result = SearchIndexBackend.selectors('Ticket', condition, { current_user: agent })
+        expect(result[:count]).to eq(3)
+      end
+
+      it 'does not match', :aggregate_failures do
+        condition = {
+          operator:   'AND',
+          conditions: [
+            {
+              name:     'ticket.title',
+              operator: 'ends with one of',
+              value:    ['ubx'],
+            },
+          ]
+        }
+
+        count, = Ticket.selectors(condition, { current_user: agent })
+        expect(count).to eq(0)
+
+        result = SearchIndexBackend.selectors('Ticket', condition, { current_user: agent })
+        expect(result[:count]).to eq(0)
+      end
+    end
+  end
 end