diff --git a/.gitlab/ci/integration/es.yml b/.gitlab/ci/integration/es.yml index 79464bddf..15039a6c7 100644 --- a/.gitlab/ci/integration/es.yml +++ b/.gitlab/ci/integration/es.yml @@ -11,7 +11,6 @@ - bundle exec rails test test/integration/elasticsearch_active_test.rb - bundle exec rails test test/integration/elasticsearch_test.rb - bundle exec rspec --tag searchindex --tag ~type:system --profile 10 - - bundle exec rails test test/integration/report_test.rb es:7: <<: *template_integration_es diff --git a/app/assets/javascripts/app/controllers/_application_controller/technical_error_modal.coffee b/app/assets/javascripts/app/controllers/_application_controller/technical_error_modal.coffee new file mode 100644 index 000000000..6df18b7cd --- /dev/null +++ b/app/assets/javascripts/app/controllers/_application_controller/technical_error_modal.coffee @@ -0,0 +1,9 @@ +class App.ControllerTechnicalErrorModal extends App.ControllerModal + head: "StatusCode: #{status}" + contentCode: '' + buttonClose: false + buttonSubmit: 'Ok' + onSubmit: (e) -> @close(e) + + content: -> + "
#{@contentCode}
" diff --git a/app/assets/javascripts/app/controllers/report.coffee b/app/assets/javascripts/app/controllers/report.coffee index e839112ab..e2630ae06 100644 --- a/app/assets/javascripts/app/controllers/report.coffee +++ b/app/assets/javascripts/app/controllers/report.coffee @@ -171,6 +171,13 @@ class Graph extends App.Controller backends: @params.backendSelected ) processData: true + error: (xhr) => + return if !_.include([401, 403, 404, 422, 502], xhr.status) + + @bodyModal = new App.ControllerTechnicalErrorModal( + head: 'Cannot generate report' + contentCode: xhr.responseJSON.error + ) success: (data) => @update(data) @delay(@render, interval, 'report-update', 'page') diff --git a/app/assets/javascripts/app/lib/app_post/ajax.coffee b/app/assets/javascripts/app/lib/app_post/ajax.coffee index 9ee86e2f6..a0399c21d 100644 --- a/app/assets/javascripts/app/lib/app_post/ajax.coffee +++ b/app/assets/javascripts/app/lib/app_post/ajax.coffee @@ -102,12 +102,18 @@ class _ajaxSingleton # do not show any error message with code 502 return if status is 502 + try + json = JSON.parse(detail) + text = json.error_human || json.error + + text = detail if !text + + escaped = App.Utils.htmlEscape(text) + # show error message - new App.ControllerModal( - head: "StatusCode: #{status}" - contentInline: "
#{App.Utils.htmlEscape(detail)}
" - buttonClose: true - buttonSubmit: false + new App.ControllerTechnicalErrorModal( + contentCode: escaped + head: "StatusCode: #{status}" ) ) diff --git a/app/assets/stylesheets/zammad.scss b/app/assets/stylesheets/zammad.scss index c7d1f28b7..e6e591aa6 100644 --- a/app/assets/stylesheets/zammad.scss +++ b/app/assets/stylesheets/zammad.scss @@ -358,6 +358,26 @@ ol, ul { z-index: 1; } +code { + background: hsla(0, 0%, 0%, 0.2); + border-radius: 3px; + box-decoration-break: clone; +} + +code, +.hljs { + padding: 2px 4px; + font-size: 0.88em; +} + +.hljs { + background: none; +} + +pre code.hljs { + font-size: 1em; +} + pre { display: block; padding: 9.5px; @@ -371,30 +391,26 @@ pre { border: 1px solid hsl(0,0%,90%); border-radius: 3px; } + +.modal-content pre { + background: hsl(0, 0%, 97%); + border: 1px solid hsl(0, 0%, 87%); +} + pre code { padding: 0; font-size: inherit; color: inherit; white-space: pre-wrap; - background-color: transparent; - border-radius: 0; -} - -.hljs, -code { background: none; - padding: 2px 4px; - font-size: 0.88em; -} + border-radius: 0; + border: none; + overflow-x: auto; -code:not(.hljs) { - border: 1px solid rgba(0,0,0,.2); - border-radius: 3px; - white-space: nowrap; -} - -pre code.hljs { - font-size: 1em; + &.hljs { + padding: 0; + background: none; + } } .textarea::placeholder, diff --git a/app/controllers/reports_controller.rb b/app/controllers/reports_controller.rb index dfcabde2a..448f8e021 100644 --- a/app/controllers/reports_controller.rb +++ b/app/controllers/reports_controller.rb @@ -22,26 +22,34 @@ class ReportsController < ApplicationController get_params = params_all return if !get_params - result = {} - get_params[:metric][:backend].each do |backend| - condition = get_params[:profile].condition - if backend[:condition] - backend[:condition].merge(condition) - else - backend[:condition] = condition - end - next if !backend[:adapter] + begin + result = {} + get_params[:metric][:backend].each do |backend| + condition = get_params[:profile].condition + if backend[:condition] + backend[:condition].merge(condition) + else + backend[:condition] = condition + end + next if !backend[:adapter] - result[backend[:name]] = backend[:adapter].aggs( - range_start: get_params[:start], - range_end: get_params[:stop], - interval: get_params[:range], - selector: backend[:condition], - params: backend[:params], - timezone: get_params[:timezone], - timezone_offset: get_params[:timezone_offset], - current_user: current_user - ) + result[backend[:name]] = backend[:adapter].aggs( + range_start: get_params[:start], + range_end: get_params[:stop], + interval: get_params[:range], + selector: backend[:condition], + params: backend[:params], + timezone: get_params[:timezone], + timezone_offset: get_params[:timezone_offset], + current_user: current_user + ) + end + rescue => e + if e.message.include? 'Conflicting date range' + raise Exceptions::UnprocessableEntity, 'Conflicting date ranges. Please check your selected report profile.' + end + + raise e end render json: { diff --git a/lib/search_index_backend.rb b/lib/search_index_backend.rb index be4af611b..30f68cde9 100644 --- a/lib/search_index_backend.rb +++ b/lib/search_index_backend.rb @@ -438,6 +438,8 @@ example for aggregations within one year data = selector2query(selectors, options, aggs_interval) + verify_date_range(url, data) + response = make_request(url, data: data) if !response.success? @@ -1208,4 +1210,87 @@ helper method for making HTTP calls and raising error if response was not succes ) end + # verifies date range ElasticSearch payload + # + # @param url [String] of ElasticSearch + # @param payload [Hash] Elasticsearch query payload + # + # @return [Boolean] or raises error + def self.verify_date_range(url, payload) + ranges_payload = payload.dig(:query, :bool, :must) + + return true if ranges_payload.nil? + + ranges = ranges_payload + .select { |elem| elem.key? :range } + .map { |elem| [elem[:range].keys.first, convert_es_date_range(elem)] } + .each_with_object({}) { |elem, sum| (sum[elem.first] ||= []) << elem.last } + + return true if ranges.all? { |_, ranges_by_key| verify_single_key_range(ranges_by_key) } + + error_prefix = "Unable to process request to elasticsearch URL '#{url}'." + error_suffix = "Payload:\n#{payload.to_json}" + error_message = 'Conflicting date ranges' + + result = "#{error_prefix} #{error_message} #{error_suffix}" + Rails.logger.error result.first(40_000) + + raise result + end + + # checks if all ranges are overlaping + # + # @param ranges [Array>] to use in search + # + # @return [Boolean] + def self.verify_single_key_range(ranges) + ranges + .each_with_index + .all? do |range, i| + ranges + .slice((i + 1)..) + .all? { |elem| elem.overlaps? range } + end + end + + # Converts paylaod component to dates range + # + # @param elem [Hash] payload component + # + # @return [Range] + def self.convert_es_date_range(elem) + range = elem[:range].first.last + from = parse_es_range_date range[:from] || range[:gt] || '-9999-01-01' + to = parse_es_range_date range[:to] || range[:lt] || '9999-01-01' + + from..to + end + + # Parses absolute date or converts relative date + # + # @param input [String] string representation of date + # + # @return [Range] + def self.parse_es_range_date(input) + match = input.match(%r{^now(-|\+)(\d+)(\w{1})$}) + + return DateTime.parse input if !match + + map = { + d: 'day', + y: 'year', + M: 'month', + h: 'hour', + m: 'minute', + } + + range = match.captures[1].to_i.send map[match.captures[2].to_sym] + + case match.captures[0] + when '-' + range.ago + when '+' + range.from_now + end + end end diff --git a/spec/factories/report/profile.rb b/spec/factories/report/profile.rb index a416752b6..b9f46e18d 100644 --- a/spec/factories/report/profile.rb +++ b/spec/factories/report/profile.rb @@ -6,5 +6,20 @@ FactoryBot.define do active { true } created_by_id { 1 } updated_by_id { 1 } + + trait :condition_created_at do + transient do + ticket_created_at { nil } + end + + condition do + { + 'ticket.created_at' => { + operator: 'before (absolute)', + value: ticket_created_at.iso8601 + } + } + end + end end end diff --git a/spec/lib/report/ticket_first_solution_spec.rb b/spec/lib/report/ticket_first_solution_spec.rb new file mode 100644 index 000000000..bd742f8da --- /dev/null +++ b/spec/lib/report/ticket_first_solution_spec.rb @@ -0,0 +1,192 @@ +# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/ + +# rubocop:disable RSpec/ExampleLength + +require 'rails_helper' +require 'lib/report_examples' + +RSpec.describe Report::TicketFirstSolution, searchindex: true do + include_examples 'with report examples' + + describe '.aggs' do + it 'gets monthly aggregated results' do + result = described_class.aggs( + range_start: Time.zone.parse('2015-01-01T00:00:00Z'), + range_end: Time.zone.parse('2015-12-31T23:59:59Z'), + interval: 'month', + selector: {}, + ) + + expect(result).to eq [0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 0] + end + + it 'gets monthly aggregated results with high priority' do + result = described_class.aggs( + range_start: Time.zone.parse('2015-01-01T00:00:00Z'), + range_end: Time.zone.parse('2015-12-31T23:59:59Z'), + interval: 'month', + selector: { + 'ticket.priority_id' => { + 'operator' => 'is', + 'value' => [Ticket::Priority.lookup(name: '3 high').id], + } + }, + ) + + expect(result).to eq [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0] + end + + it 'gets monthly aggregated results not in merged state' do + result = described_class.aggs( + range_start: Time.zone.parse('2015-01-01T00:00:00Z'), + range_end: Time.zone.parse('2015-12-31T23:59:59Z'), + interval: 'month', + selector: { + 'ticket_state.name' => { + 'operator' => 'is not', + 'value' => 'merged', + } + }, + ) + + expect(result).to eq [0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 0] + end + + it 'gets monthly aggregated results with not high priority' do + result = described_class.aggs( + range_start: Time.zone.parse('2015-01-01T00:00:00Z'), + range_end: Time.zone.parse('2015-12-31T23:59:59Z'), + interval: 'month', + selector: { + 'ticket.priority_id' => { + 'operator' => 'is not', + 'value' => [Ticket::Priority.lookup(name: '3 high').id], + } + }, + ) + + expect(result).to eq [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0] + end + + it 'gets weekly aggregated results' do + result = described_class.aggs( + range_start: Time.zone.parse('2015-10-26T00:00:00Z'), + range_end: Time.zone.parse('2015-10-31T23:59:59Z'), + interval: 'week', + selector: {}, + ) + + expect(result).to eq [0, 0, 1, 0, 0, 1, 1] + end + + it 'gets daily aggregated results' do + result = described_class.aggs( + range_start: Time.zone.parse('2015-10-01T00:00:00Z'), + range_end: Time.zone.parse('2015-11-01T23:59:59Z'), + interval: 'day', + selector: {}, + ) + + expect(result).to eq [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1] + end + + it 'gets hourly aggregated results' do + result = described_class.aggs( + range_start: Time.zone.parse('2015-10-28T00:00:00Z'), + range_end: Time.zone.parse('2015-10-28T23:59:59Z'), + interval: 'hour', + selector: {}, + ) + + expect(result).to eq [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + end + end + + describe '.items' do + it 'gets items in year range' do + result = described_class.items( + range_start: Time.zone.parse('2015-01-01T00:00:00Z'), + range_end: Time.zone.parse('2015-12-31T23:59:59Z'), + selector: {}, + ) + expect(result).to match_tickets ticket_5, ticket_6, ticket_7 + end + + it 'gets items in year range with high priority' do + result = described_class.items( + range_start: Time.zone.parse('2015-01-01T00:00:00Z'), + range_end: Time.zone.parse('2015-12-31T23:59:59Z'), + selector: { + 'ticket.priority_id' => { + 'operator' => 'is', + 'value' => [Ticket::Priority.lookup(name: '3 high').id], + } + } + ) + + expect(result).to match_tickets ticket_5 + end + + it 'gets items in year range not in merged state' do + result = described_class.items( + range_start: Time.zone.parse('2015-01-01T00:00:00Z'), + range_end: Time.zone.parse('2015-12-31T23:59:59Z'), + selector: { + 'ticket_state.name' => { + 'operator' => 'is not', + 'value' => 'merged', + } + } + ) + + expect(result).to match_tickets ticket_5, ticket_6, ticket_7 + end + + it 'gets items in year range with not high priority' do + result = described_class.items( + range_start: Time.zone.parse('2015-01-01T00:00:00Z'), + range_end: Time.zone.parse('2015-12-31T23:59:59Z'), + selector: { + 'ticket.priority_id' => { + 'operator' => 'is not', + 'value' => [Ticket::Priority.lookup(name: '3 high').id], + } + } + ) + + expect(result).to match_tickets ticket_6, ticket_7 + end + + it 'gets items in week range' do + result = described_class.items( + range_start: Time.zone.parse('2015-10-26T00:00:00Z'), + range_end: Time.zone.parse('2015-11-01T23:59:59Z'), + selector: {} + ) + + expect(result).to match_tickets ticket_5, ticket_6, ticket_7 + end + + it 'gets items in day range' do + result = described_class.items( + range_start: Time.zone.parse('2015-10-01T00:00:00Z'), + range_end: Time.zone.parse('2015-10-31T23:59:59Z'), + selector: {} + ) + + expect(result).to match_tickets ticket_5, ticket_6 + end + + it 'gets items in hour range' do + result = described_class.items( + range_start: Time.zone.parse('2015-10-28T00:00:00Z'), + range_end: Time.zone.parse('2015-10-28T23:59:59Z'), + interval: 'hour', + selector: {}, + ) + + expect(result).to match_tickets ticket_5 + end + end +end +# rubocop:enable RSpec/ExampleLength diff --git a/spec/lib/report/ticket_generic_time_spec.rb b/spec/lib/report/ticket_generic_time_spec.rb index 20f2fc512..cbf8fa24f 100644 --- a/spec/lib/report/ticket_generic_time_spec.rb +++ b/spec/lib/report/ticket_generic_time_spec.rb @@ -1,33 +1,333 @@ # Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/ require 'rails_helper' +require 'lib/report_examples' -RSpec.describe Report::TicketGenericTime do +RSpec.describe Report::TicketGenericTime, searchindex: true do + include_examples 'with report examples' -=begin + describe '.aggs' do + it 'gets monthly aggregated results by created_at' do + result = described_class.aggs( + range_start: Time.zone.parse('2015-01-01T00:00:00Z'), + range_end: Time.zone.parse('2015-12-31T23:59:59Z'), + interval: 'month', # year, quarter, month, week, day, hour, minute, second + selector: {}, # ticket selector to get only a collection of tickets + params: { field: 'created_at' }, + ) - result = Report::TicketGenericTime.items( - range_start: '2015-01-01T00:00:00Z', - range_end: '2015-12-31T23:59:59Z', - selector: selector, # ticket selector to get only a collection of tickets - params: { field: 'created_at' }, - ) + expect(result).to eq [0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 1, 0] + end -returns + it 'gets monthly aggregated results by created_at not merged' do + result = described_class.aggs( + range_start: Time.zone.parse('2015-01-01T00:00:00Z'), + range_end: Time.zone.parse('2015-12-31T23:59:59Z'), + interval: 'month', # year, quarter, month, week, day, hour, minute, second + selector: { + 'state' => { + 'operator' => 'is not', + 'value' => 'merged' + } + }, + params: { field: 'created_at' }, + ) - { - count: 123, - ticket_ids: [4,5,1,5,0,51,5,56,7,4], - assets: assets, - } + expect(result).to eq [0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 1, 0] + end + end -=end + describe '.items' do + it 'gets items in year range by created_at' do + result = described_class.items( + range_start: Time.zone.parse('2015-01-01T00:00:00Z'), + range_end: Time.zone.parse('2015-12-31T23:59:59Z'), + selector: {}, # ticket selector to get only a collection of tickets + params: { field: 'created_at' }, + ) - describe 'items' do + expect(result).to match_tickets ticket_7, ticket_6, ticket_5, ticket_4, ticket_3, ticket_2, ticket_1 + end + + it 'gets items in year range by created_at not merged' do + result = described_class.items( + range_start: Time.zone.parse('2015-01-01T00:00:00Z'), + range_end: Time.zone.parse('2015-12-31T23:59:59Z'), + selector: { + 'state' => { + 'operator' => 'is not', + 'value' => 'merged' + } + }, + params: { field: 'created_at' }, + ) + + expect(result).to match_tickets ticket_7, ticket_6, ticket_5, ticket_4, ticket_3, ticket_2, ticket_1 + end + + it 'gets items in year range by created_at before oct 31st' do + result = described_class.items( + range_start: Time.zone.parse('2015-01-01T00:00:00Z'), + range_end: Time.zone.parse('2015-12-31T23:59:59Z'), + selector: { + 'created_at' => { + 'operator' => 'before (absolute)', + 'value' => '2015-10-31T00:00:00Z' + } + }, + params: { field: 'created_at' }, + ) + + expect(result).to match_tickets ticket_5, ticket_4, ticket_3, ticket_2, ticket_1 + end + + it 'gets items in year range by created_at after oct 31st' do + result = described_class.items( + range_start: Time.zone.parse('2015-01-01T00:00:00Z'), + range_end: Time.zone.parse('2015-12-31T23:59:59Z'), + selector: { + 'created_at' => { + 'operator' => 'after (absolute)', + 'value' => '2015-10-31T00:00:00Z' + } + }, + params: { field: 'created_at' }, + ) + + expect(result).to match_tickets ticket_7, ticket_6 + end + + it 'gets items in 1 day from now' do + result = described_class.items( + range_start: 1.year.ago.beginning_of_year, + range_end: 1.year.from_now.at_end_of_year, + selector: { + 'created_at' => { + 'operator' => 'after (relative)', + 'range' => 'day', + 'value' => '1' + } + }, # ticket selector to get only a collection of tickets + params: { field: 'created_at' }, + ) + + expect(result).to match_tickets ticket_after_72h + end + + it 'gets items in 1 month from now' do + result = described_class.items( + range_start: 1.year.ago.beginning_of_year, + range_end: 1.year.from_now.at_end_of_year, + selector: { + 'created_at' => { + 'operator' => 'after (relative)', + 'range' => 'month', + 'value' => '1' + } + }, # ticket selector to get only a collection of tickets + params: { field: 'created_at' }, + ) + + expect(result).to match_tickets [] + end + + it 'gets items in 1 month ago' do + result = described_class.items( + range_start: 1.year.ago.beginning_of_year, + range_end: 1.year.from_now.at_end_of_year, + selector: { + 'created_at' => { + 'operator' => 'before (relative)', + 'range' => 'month', + 'value' => '1' + } + }, # ticket selector to get only a collection of tickets + params: { field: 'created_at' }, + ) + + expect(result).to match_tickets ticket_before_40d + end + + it 'gets items in 5 months ago' do + result = described_class.items( + range_start: 1.year.ago.beginning_of_year, + range_end: 1.year.from_now.at_end_of_year, + selector: { + 'created_at' => { + 'operator' => 'before (relative)', + 'range' => 'month', + 'value' => '5' + } + }, # ticket selector to get only a collection of tickets + params: { field: 'created_at' }, + ) + + expect(result).to match_tickets [] + end + + it 'gets items with aaa+bbb' do + result = described_class.items( + range_start: Time.zone.parse('2015-01-01T00:00:00Z'), + range_end: Time.zone.parse('2015-12-31T23:59:59Z'), + selector: { + 'tags' => { + 'operator' => 'contains all', + 'value' => 'aaa, bbb' + } + }, + params: { field: 'created_at' }, + ) + + expect(result).to match_tickets ticket_1 + end + + it 'gets items with not aaa+bbb' do + result = described_class.items( + range_start: Time.zone.parse('2015-01-01T00:00:00Z'), + range_end: Time.zone.parse('2015-12-31T23:59:59Z'), + selector: { + 'tags' => { + 'operator' => 'contains all not', + 'value' => 'aaa, bbb' + } + }, + params: { field: 'created_at' }, + ) + + expect(result).to match_tickets ticket_7, ticket_6, ticket_5, ticket_4, ticket_3, ticket_2 + end + + it 'gets items with aaa' do + result = described_class.items( + range_start: Time.zone.parse('2015-01-01T00:00:00Z'), + range_end: Time.zone.parse('2015-12-31T23:59:59Z'), + selector: { + 'tags' => { + 'operator' => 'contains all', + 'value' => 'aaa' + } + }, + params: { field: 'created_at' }, + ) + + expect(result).to match_tickets ticket_2, ticket_1 + end + + it 'gets items with not aaa' do + result = described_class.items( + range_start: Time.zone.parse('2015-01-01T00:00:00Z'), + range_end: Time.zone.parse('2015-12-31T23:59:59Z'), + selector: { + 'tags' => { + 'operator' => 'contains all not', + 'value' => 'aaa' + } + }, + params: { field: 'created_at' }, + ) + + expect(result).to match_tickets ticket_7, ticket_6, ticket_5, ticket_4, ticket_3 + end + + it 'gets items with one not aaa' do + result = described_class.items( + range_start: Time.zone.parse('2015-01-01T00:00:00Z'), + range_end: Time.zone.parse('2015-12-31T23:59:59Z'), + selector: { + 'tags' => { + 'operator' => 'contains one not', + 'value' => 'aaa' + } + }, + params: { field: 'created_at' }, + ) + + expect(result).to match_tickets ticket_7, ticket_6, ticket_5, ticket_4, ticket_3 + end + + it 'gets items with one not aaa+bbb' do + result = described_class.items( + range_start: Time.zone.parse('2015-01-01T00:00:00Z'), + range_end: Time.zone.parse('2015-12-31T23:59:59Z'), + selector: { + 'tags' => { + 'operator' => 'contains one not', + 'value' => 'aaa, bbb' + } + }, + params: { field: 'created_at' }, + ) + + expect(result).to match_tickets ticket_7, ticket_6, ticket_4, ticket_3 + end + + it 'gets items with one aaa' do + result = described_class.items( + range_start: Time.zone.parse('2015-01-01T00:00:00Z'), + range_end: Time.zone.parse('2015-12-31T23:59:59Z'), + selector: { + 'tags' => { + 'operator' => 'contains one', + 'value' => 'aaa' + } + }, + params: { field: 'created_at' }, + ) + + expect(result).to match_tickets ticket_2, ticket_1 + end + + it 'gets items with one aaa+bbb' do + result = described_class.items( + range_start: Time.zone.parse('2015-01-01T00:00:00Z'), + range_end: Time.zone.parse('2015-12-31T23:59:59Z'), + selector: { + 'tags' => { + 'operator' => 'contains one', + 'value' => 'aaa, bbb' + } + }, + params: { field: 'created_at' }, + ) + + expect(result).to match_tickets ticket_5, ticket_2, ticket_1 + end + + it 'gets items with test' do + result = described_class.items( + range_start: Time.zone.parse('2015-01-01T00:00:00Z'), + range_end: Time.zone.parse('2015-12-31T23:59:59Z'), + selector: { + 'title' => { + 'operator' => 'contains', + 'value' => 'Test' + } + }, + params: { field: 'created_at' }, + ) + + expect(result).to match_tickets ticket_7, ticket_6, ticket_5, ticket_4, ticket_3, ticket_2, ticket_1 + end + + it 'gets items with not test' do + result = described_class.items( + range_start: Time.zone.parse('2015-01-01T00:00:00Z'), + range_end: Time.zone.parse('2015-12-31T23:59:59Z'), + selector: { + 'title' => { + 'operator' => 'contains not', + 'value' => 'Test' + } + }, + params: { field: 'created_at' }, + ) + + expect(result).to match_tickets [] + end # Regression test for issue #2246 - Records in Reporting not updated when single ActiveRecord can not be found - it 'correctly handles missing tickets' do - class_double('SearchIndexBackend', selectors: { ticket_ids: [-1] }).as_stubbed_const + it 'correctly handles missing tickets', searchindex: false do + class_double('SearchIndexBackend', selectors: { ticket_ids: [-1] }, drop_index: nil, drop_pipeline: nil).as_stubbed_const expect do described_class.items( @@ -39,4 +339,78 @@ returns end.not_to raise_error end end + + context 'when additional attribute exists', db_strategy: :reset do + before do + ObjectManager::Attribute.add( + object: 'Ticket', + name: 'test_category', + display: 'Test 1', + data_type: 'tree_select', + data_option: { + maxlength: 200, + null: false, + default: '', + options: [ + { 'name' => 'aa', 'value' => 'aa', 'children' => [{ 'name' => 'aa', 'value' => 'aa::aa' }, { 'name' => 'bb', 'value' => 'aa::bb' }, { 'name' => 'cc', 'value' => 'aa::cc' }] }, + { 'name' => 'bb', 'value' => 'bb', 'children' => [{ 'name' => 'aa', 'value' => 'bb::aa' }, { 'name' => 'bb', 'value' => 'bb::bb' }, { 'name' => 'cc', 'value' => 'bb::cc' }] }, + { 'name' => 'cc', 'value' => 'cc', 'children' => [{ 'name' => 'aa', 'value' => 'cc::aa' }, { 'name' => 'bb', 'value' => 'cc::bb' }, { 'name' => 'cc', 'value' => 'cc::cc' }] }, + ] + }, + active: true, + screens: {}, + position: 20, + created_by_id: 1, + updated_by_id: 1, + editable: false, + to_migrate: false, + ) + ObjectManager::Attribute.migration_execute + + ticket_with_category + + rebuild_searchindex + end + + let(:ticket_with_category) do + travel_to DateTime.new 2015, 10, 28, 9, 30 + ticket = create(:ticket, + group: group_2, + customer: customer, + test_category: 'cc::bb', + state_name: 'new', + priority_name: '2 normal') + + ticket.tag_add('aaa', 1) + ticket.tag_add('bbb', 1) + create(:ticket_article, + :inbound_email, + ticket: ticket) + + travel 5.hours + + ticket.update! group: group_1 + + travel_back + ticket + end + + describe '.items' do + it 'gets items with test_category cc:bb' do + result = described_class.items( + range_start: Time.zone.parse('2015-01-01T00:00:00Z'), + range_end: Time.zone.parse('2015-12-31T23:59:59Z'), + selector: { + 'test_category' => { + 'operator' => 'is', + 'value' => 'cc::bb' + }, + }, + params: { field: 'created_at' }, + ) + + expect(result).to match_tickets ticket_with_category + end + end + end end diff --git a/spec/lib/report/ticket_moved_spec.rb b/spec/lib/report/ticket_moved_spec.rb new file mode 100644 index 000000000..9efb30424 --- /dev/null +++ b/spec/lib/report/ticket_moved_spec.rb @@ -0,0 +1,148 @@ +# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/ + +# +# rubocop:disable RSpec/ExampleLength + +require 'rails_helper' +require 'lib/report_examples' + +RSpec.describe Report::TicketMoved, searchindex: true do + include_examples 'with report examples' + + describe '.aggs' do + it 'gets monthly aggregated results not in merged state' do + result = described_class.aggs( + range_start: Time.zone.parse('2015-01-01T00:00:00Z'), + range_end: Time.zone.parse('2015-12-31T23:59:59Z'), + interval: 'month', + selector: { + 'ticket_state.name' => { + 'operator' => 'is not', + 'value' => 'merged', + } + }, + params: { + type: 'in', + }, + ) + + expect(result).to eq [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + end + + it 'gets monthly aggregated results in users group' do + result = described_class.aggs( + range_start: Time.zone.parse('2015-01-01T00:00:00Z'), + range_end: Time.zone.parse('2015-12-31T23:59:59Z'), + interval: 'month', + selector: { + 'ticket.group_id' => { + 'operator' => 'is', + 'value' => [Group.lookup(name: 'Users').id], + } + }, + params: { + type: 'in', + }, + ) + + expect(result).to eq [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0] + end + + it 'gets monthly aggregated results not in merged state and outgoing' do + result = described_class.aggs( + range_start: Time.zone.parse('2015-01-01T00:00:00Z'), + range_end: Time.zone.parse('2015-12-31T23:59:59Z'), + interval: 'month', + selector: { + 'ticket_state.name' => { + 'operator' => 'is not', + 'value' => 'merged', + } + }, + params: { + type: 'out', + }, + ) + + expect(result).to eq [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + end + + it 'gets monthly aggregated results in users group and outgoing' do + result = described_class.aggs( + range_start: Time.zone.parse('2015-01-01T00:00:00Z'), + range_end: Time.zone.parse('2015-12-31T23:59:59Z'), + interval: 'month', + selector: { + 'ticket.group_id' => { + 'operator' => 'is', + 'value' => [Group.lookup(name: 'Users').id], + } + }, + params: { + type: 'out', + }, + ) + + expect(result).to eq [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0] + end + + end + + describe '.items' do + it 'gets items in year range in users group' do + result = described_class.items( + range_start: Time.zone.parse('2015-01-01T00:00:00Z'), + range_end: Time.zone.parse('2015-12-31T23:59:59Z'), + selector: { + 'ticket.group_id' => { + 'operator' => 'is', + 'value' => [Group.lookup(name: 'Users').id], + } + }, + params: { + type: 'in', + }, + ) + + expect(result).to match_tickets ticket_1 + end + + it 'gets items in year range not merged and outgoing' do + result = described_class.items( + range_start: Time.zone.parse('2015-01-01T00:00:00Z'), + range_end: Time.zone.parse('2015-12-31T23:59:59Z'), + selector: { + 'ticket_state.name' => { + 'operator' => 'is not', + 'value' => 'merged', + } + }, # ticket selector to get only a collection of tickets + params: { + type: 'out', + }, + ) + + expect(result).to match_tickets [] + end + + it 'gets items in year range in users group and outgoing' do + result = described_class.items( + range_start: Time.zone.parse('2015-01-01T00:00:00Z'), + range_end: Time.zone.parse('2015-12-31T23:59:59Z'), + selector: { + 'ticket.group_id' => { + 'operator' => 'is', + 'value' => [Group.lookup(name: 'Users').id], + } + }, + params: { + type: 'out', + }, + ) + + expect(result).to match_tickets ticket_2 + end + + end +end +# rubocop:enable RSpec/ExampleLength diff --git a/spec/lib/report/ticket_reopened_spec.rb b/spec/lib/report/ticket_reopened_spec.rb new file mode 100644 index 000000000..054fc9663 --- /dev/null +++ b/spec/lib/report/ticket_reopened_spec.rb @@ -0,0 +1,129 @@ +# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/ + +# rubocop:disable RSpec/ExampleLength + +require 'rails_helper' +require 'lib/report_examples' + +RSpec.describe Report::TicketReopened, searchindex: true do + include_examples 'with report examples' + + describe '.aggs' do + it 'gets monthly aggregated results' do + result = described_class.aggs( + range_start: Time.zone.parse('2015-01-01T00:00:00Z'), + range_end: Time.zone.parse('2015-12-31T23:59:59Z'), + interval: 'month', + selector: {}, + ) + + expect(result).to eq [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0] + end + + it 'gets monthly aggregated results with high priority' do + result = described_class.aggs( + range_start: Time.zone.parse('2015-01-01T00:00:00Z'), + range_end: Time.zone.parse('2015-12-31T23:59:59Z'), + interval: 'month', + selector: { + 'ticket.priority_id' => { + 'operator' => 'is', + 'value' => [Ticket::Priority.lookup(name: '3 high').id], + } + } + ) + + expect(result).to eq [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0] + end + + it 'gets monthly aggregated results with not high priority' do + result = described_class.aggs( + range_start: Time.zone.parse('2015-01-01T00:00:00Z'), + range_end: Time.zone.parse('2015-12-31T23:59:59Z'), + interval: 'month', + selector: { + 'ticket.priority_id' => { + 'operator' => 'is not', + 'value' => [Ticket::Priority.lookup(name: '3 high').id], + } + }, + ) + + expect(result).to eq [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + end + + it 'gets monthly aggregated results with not merged' do + result = described_class.aggs( + range_start: Time.zone.parse('2015-01-01T00:00:00Z'), + range_end: Time.zone.parse('2015-12-31T23:59:59Z'), + interval: 'month', + selector: { + 'ticket_state.name' => { + 'operator' => 'is not', + 'value' => 'merged', + } + }, + ) + + expect(result).to eq [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0] + end + end + + describe '.items' do + it 'gets items in year range' do + result = described_class.items( + range_start: Time.zone.parse('2015-01-01T00:00:00Z'), + range_end: Time.zone.parse('2015-12-31T23:59:59Z'), + selector: {}, + ) + + expect(result).to match_tickets ticket_5 + end + + it 'gets items in year range with high priority' do + result = described_class.items( + range_start: Time.zone.parse('2015-01-01T00:00:00Z'), + range_end: Time.zone.parse('2015-12-31T23:59:59Z'), + selector: { + 'ticket.priority_id' => { + 'operator' => 'is', + 'value' => [Ticket::Priority.lookup(name: '3 high').id], + } + }, + ) + + expect(result).to match_tickets ticket_5 + end + + it 'gets items in year range with not high priority' do + result = described_class.items( + range_start: Time.zone.parse('2015-01-01T00:00:00Z'), + range_end: Time.zone.parse('2015-12-31T23:59:59Z'), + selector: { + 'ticket.priority_id' => { + 'operator' => 'is not', + 'value' => [Ticket::Priority.lookup(name: '3 high').id], + } + }, + ) + + expect(result).to match_tickets [] + end + + it 'gets items in year range with not merged' do + result = described_class.items( + range_start: Time.zone.parse('2015-01-01T00:00:00Z'), + range_end: Time.zone.parse('2015-12-31T23:59:59Z'), + selector: { + 'ticket_state.name' => { + 'operator' => 'is not', + 'value' => 'merged', + } + }, + ) + + expect(result).to match_tickets ticket_5 + end + end +end +# rubocop:enable RSpec/ExampleLength diff --git a/spec/lib/report_examples.rb b/spec/lib/report_examples.rb new file mode 100644 index 000000000..e770617b4 --- /dev/null +++ b/spec/lib/report_examples.rb @@ -0,0 +1,245 @@ +# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/ + +RSpec.shared_context 'with report examples' do + before do |example| + next if !example.metadata[:searchindex] + + configure_elasticsearch(required: true, rebuild: true) do + ticket_1 + + ticket_2 + + ticket_3 + + ticket_4 + + ticket_5 + + ticket_6 + + ticket_7 + + ticket_8 + + ticket_9 + + ticket_after_72h + + ticket_before_40d + end + end + + let(:group_1) { Group.lookup(name: 'Users') } + let(:group_2) { create(:group) } + let(:customer) { User.lookup(email: 'nicole.braun@zammad.org') } + + let(:ticket_1) do + travel_to DateTime.new 2015, 10, 28, 9, 30 + ticket = create(:ticket, + group: group_2, + customer: customer, + state_name: 'new', + priority_name: '2 normal') + + ticket.tag_add('aaa', 1) + ticket.tag_add('bbb', 1) + create(:ticket_article, + :inbound_email, + ticket: ticket) + + travel 5.hours + + ticket.update! group: group_1 + + travel_back + ticket + end + + let(:ticket_2) do + travel_to DateTime.new 2015, 10, 28, 9, 30, 1 + ticket = create(:ticket, + group: group_1, + customer: customer, + state_name: 'new', + priority_name: '2 normal') + + ticket.tag_add('aaa', 1) + create(:ticket_article, + :inbound_email, + ticket: ticket) + travel 5.hours - 1.second + + ticket.update! group: group_2 + + travel_back + ticket + end + + let(:ticket_3) do + travel_to DateTime.new 2015, 10, 28, 10, 30 + ticket = create(:ticket, + group: group_1, + customer: customer, + state_name: 'open', + priority_name: '3 high') + create(:ticket_article, + :inbound_email, + ticket: ticket) + + travel_back + ticket + end + + let(:ticket_4) do + travel_to DateTime.new 2015, 10, 28, 10, 30, 1 + ticket = create(:ticket, + group: group_1, + customer: customer, + state_name: 'closed', + priority_name: '2 normal', + close_at: (1.hour - 1.second).from_now) + create(:ticket_article, + :inbound_email, + ticket: ticket) + travel_back + ticket + end + + let(:ticket_5) do + travel_to DateTime.new 2015, 10, 28, 11, 30 + ticket = create(:ticket, + group: group_1, + customer: customer, + state_name: 'closed', + priority_name: '3 high', + close_at: 10.minutes.from_now) + + ticket.tag_add('bbb', 1) + create(:ticket_article, + :outbound_email, + ticket: ticket) + + ticket.update! state: Ticket::State.lookup(name: 'open') + + travel 3.hours + + travel_back + ticket + end + + let(:ticket_6) do + travel_to DateTime.new 2015, 10, 31, 12, 30 + ticket = create(:ticket, + group: group_1, + customer: customer, + state_name: 'closed', + priority_name: '2 normal', + close_at: 5.minutes.from_now) + create(:ticket_article, + :outbound_email, + ticket: ticket) + + travel_back + ticket + end + + let(:ticket_7) do + travel_to DateTime.new 2015, 11, 1, 12, 30 + ticket = create(:ticket, + group: group_1, + customer: customer, + state_name: 'closed', + priority_name: '2 normal', + close_at: Time.zone.now) + create(:ticket_article, + :inbound_email, + ticket: ticket) + travel_back + ticket + end + + let(:ticket_8) do + travel_to DateTime.new 2015, 11, 2, 12, 30 + ticket = create(:ticket, + group: group_1, + customer: customer, + state_name: 'merged', + priority_name: '2 normal', + close_at: Time.zone.now) + + create(:ticket_article, + :inbound_email, + ticket: ticket) + + travel_back + ticket + end + + let(:ticket_9) do + travel_to DateTime.new 2037, 11, 2, 12, 30 + ticket = create(:ticket, + group: group_1, + customer: customer, + state_name: 'merged', + priority_name: '2 normal', + close_at: Time.zone.now) + create(:ticket_article, + :inbound_email, + ticket: ticket) + + travel_back + + ticket + end + + let(:ticket_after_72h) do + travel 72.hours do + ticket = create(:ticket, + group: group_1, + customer: customer, + state_name: 'closed', + priority_name: '2 normal', + close_at: 5.minutes.from_now) + create(:ticket_article, + :outbound_email, + ticket: ticket) + + ticket + end + end + + let(:ticket_before_40d) do + travel(-40.days) do + ticket = create(:ticket, + group: group_1, + customer: customer, + state_name: 'closed', + priority_name: '2 normal', + close_at: 5.minutes.from_now) + create(:ticket_article, + :outbound_email, + ticket: ticket) + + ticket + end + end + + matcher :match_tickets do + match do + if expected_tickets.blank? + actual_ticket_ids.blank? + else + # GenericTime returns string ids :o + actual_ticket_ids.map(&:to_i) == expected_tickets.map(&:id) + end + end + + def expected_tickets + Array(expected) + end + + def actual_ticket_ids + actual[:ticket_ids] + end + end +end diff --git a/spec/lib/search_index_backend_spec.rb b/spec/lib/search_index_backend_spec.rb index 5f3f46c03..c7f488c5b 100644 --- a/spec/lib/search_index_backend_spec.rb +++ b/spec/lib/search_index_backend_spec.rb @@ -2,11 +2,12 @@ require 'rails_helper' -RSpec.describe SearchIndexBackend, searchindex: true do +RSpec.describe SearchIndexBackend do - before do - configure_elasticsearch - rebuild_searchindex + before do |example| + next if !example.metadata[:searchindex] + + configure_elasticsearch(required: true, rebuild: true) end describe '.build_query' do @@ -19,7 +20,7 @@ RSpec.describe SearchIndexBackend, searchindex: true do end end - describe '.search' do + describe '.search', searchindex: true do context 'query finds results' do @@ -200,7 +201,8 @@ RSpec.describe SearchIndexBackend, searchindex: true do end end - describe '.remove' do + describe '.remove', searchindex: true do + context 'record gets deleted' do let(:record_type) { 'Ticket'.freeze } @@ -239,7 +241,7 @@ RSpec.describe SearchIndexBackend, searchindex: true do end end - describe '.selectors' do + describe '.selectors', searchindex: true do let(:group1) { create :group } let(:organization1) { create :organization, note: 'hihi' } @@ -845,4 +847,145 @@ RSpec.describe SearchIndexBackend, searchindex: true do end end end + + describe '.verify_date_range' do + let(:range_1) { { range: { created_at: { from: '2020-01-01T00:00:00.000Z', to: '2021-12-31T23:59:59Z' } } } } + let(:range_2) { { range: { created_at: { from: '2020-03-01T00:00:00.000Z', to: '2020-03-31T23:59:59Z' } } } } + let(:range_3) { { range: { created_at: { from: '2018-03-01T00:00:00.000Z', to: '2018-03-31T23:59:59Z' } } } } + let(:range_4) { { range: { updated_at: { from: '2018-03-01T00:00:00.000Z', to: '2018-03-31T23:59:59Z' } } } } + + def build_payload(*ranges) + { + query: { + bool: { + must: ranges + } + } + } + end + + it 'verifies single range' do + result = described_class.verify_date_range 'url', build_payload(range_1) + + expect(result).to be_truthy + end + + it 'verifies multiple intersecting ranges' do + result = described_class.verify_date_range 'url', build_payload(range_1, range_2) + + expect(result).to be_truthy + end + + it 'verifies non-intersecting ranges on different keys' do + result = described_class.verify_date_range 'url', build_payload(range_1, range_4) + + expect(result).to be_truthy + end + + it 'verifies payload without any ranges' do + result = described_class.verify_date_range 'url', build_payload + + expect(result).to be_truthy + end + + it 'verifies payload without payload' do + result = described_class.verify_date_range 'url', {} + + expect(result).to be_truthy + end + + it 'raises an error with multiple non-intersecting range' do + expect { described_class.verify_date_range 'url', build_payload(range_1, range_3) } + .to raise_error(%r{Conflicting date ranges}) + end + + context 'with a stubbed range' do + before do + allow(described_class).to receive(:convert_es_date_range).and_return(mock_range) + end + + let(:mock_range) { instance_double('Range', overlaps?: true) } + + it 'checks overlap once for 2 ranges' do + described_class.verify_date_range 'url', build_payload(range_1, range_2) + expect(mock_range).to have_received(:overlaps?).exactly(1).times + end + + it 'checks overlap 3 times for 3 ranges' do + described_class.verify_date_range 'url', build_payload(range_1, range_2, range_3) + expect(mock_range).to have_received(:overlaps?).exactly(3).times + end + end + end + + describe '.verify_single_key_range' do + let(:range_1) { DateTime.new(2020, 1, 1)..DateTime.new(2021, 12, 31) } + let(:range_2) { DateTime.new(2020, 3, 1)..DateTime.new(2020, 3, 31) } + let(:range_3) { DateTime.new(2018, 3, 1)..DateTime.new(2018, 3, 31) } + + it 'returns true with a single range' do + result = described_class.verify_single_key_range [range_1] + expect(result).to be_truthy + end + + it 'returns true with overlapping ranges' do + result = described_class.verify_single_key_range [range_1, range_2] + expect(result).to be_truthy + end + + it 'returns false with non-overlapping ranges' do + result = described_class.verify_single_key_range [range_1, range_3] + expect(result).to be_falsey + end + end + + describe '.convert_es_date_range' do + let(:from) { DateTime.new 2018, 1, 1, 17 } + let(:from_placeholder) { DateTime.new(-9999, 1, 1) } + let(:to) { DateTime.new 2020, 10, 1, 23 } + let(:to_placeholder) { DateTime.new 9999, 1, 1 } + + it 'converts range' do + result = described_class.convert_es_date_range( + { + range: { + created_at: { + from: '2018-01-01T17:00:00.000Z', + to: '2020-10-01T23:00:00Z' + } + } + } + ) + + expect(result).to eq from..to + end + + it 'converts less than' do + result = described_class.convert_es_date_range( + { + range: { + created_at: { + lt: '2020-10-01T23:00:00Z' + } + } + } + ) + + expect(result).to eq from_placeholder..to + end + + it 'converts greater than' do + result = described_class.convert_es_date_range( + { + range: { + created_at: { + gt: '2018-01-01T17:00:00.000Z', + } + } + } + ) + + expect(result).to eq from..to_placeholder + end + end end diff --git a/spec/system/report_spec.rb b/spec/system/report_spec.rb index 447701b1d..234179ceb 100644 --- a/spec/system/report_spec.rb +++ b/spec/system/report_spec.rb @@ -33,4 +33,42 @@ RSpec.describe 'Report', type: :system, searchindex: true do end end end + + context 'report profiles are displayed' do + let!(:report_profile_active) { create(:report_profile) } + let!(:report_profile_inactive) { create(:report_profile, active: false) } + + it 'shows report profiles' do + visit 'report' + + expect(page) + .to have_css('ul.checkbox-list .label-text', text: report_profile_active.name) + .and have_no_css('ul.checkbox-list .label-text', text: report_profile_inactive.name) + end + end + + context 'with report profiles with date-based conditions' do + let(:report_profile) { create(:report_profile, :condition_created_at, ticket_created_at: 1.year.ago) } + + before do + freeze_time + report_profile + visit 'report' + end + + it 'shows previous year for a profile with matching conditions' do + click '.js-timePickerYear', text: Time.zone.now.year - 1 + click '.label-text', text: report_profile.name + + expect(page).to have_no_css('.modal') + end + + it 'throws error for a profile when showing a different year than described in the profile' do + click '.label-text', text: report_profile.name + + in_modal disappears: false do + expect(page).to have_text 'Conflicting date ranges' + end + end + end end diff --git a/test/integration/report_test.rb b/test/integration/report_test.rb deleted file mode 100644 index 48db03649..000000000 --- a/test/integration/report_test.rb +++ /dev/null @@ -1,1404 +0,0 @@ -# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/ - -require 'integration_test_helper' - -class ReportTest < ActiveSupport::TestCase - include SearchindexHelper - - setup do - - # create attribute - ObjectManager::Attribute.add( - object: 'Ticket', - name: 'test_category', - display: 'Test 1', - data_type: 'tree_select', - data_option: { - maxlength: 200, - null: false, - default: '', - options: [ - { 'name' => 'aa', 'value' => 'aa', 'children' => [{ 'name' => 'aa', 'value' => 'aa::aa' }, { 'name' => 'bb', 'value' => 'aa::bb' }, { 'name' => 'cc', 'value' => 'aa::cc' }] }, - { 'name' => 'bb', 'value' => 'bb', 'children' => [{ 'name' => 'aa', 'value' => 'bb::aa' }, { 'name' => 'bb', 'value' => 'bb::bb' }, { 'name' => 'cc', 'value' => 'bb::cc' }] }, - { 'name' => 'cc', 'value' => 'cc', 'children' => [{ 'name' => 'aa', 'value' => 'cc::aa' }, { 'name' => 'bb', 'value' => 'cc::bb' }, { 'name' => 'cc', 'value' => 'cc::cc' }] }, - ] - }, - active: true, - screens: {}, - position: 20, - created_by_id: 1, - updated_by_id: 1, - editable: false, - to_migrate: false, - ) - ObjectManager::Attribute.migration_execute - - configure_elasticsearch(required: true) - - Ticket.destroy_all - - rebuild_searchindex - - group1 = Group.lookup(name: 'Users') - group2 = Group.create!( - name: 'Report Test', - updated_by_id: 1, - created_by_id: 1 - ) - - @ticket1 = Ticket.create!( - title: 'test 1', - group: group2, - customer_id: 2, - state: Ticket::State.lookup(name: 'new'), - priority: Ticket::Priority.lookup(name: '2 normal'), - created_at: '2015-10-28 09:30:00 UTC', - updated_at: '2015-10-28 09:30:00 UTC', - test_category: 'cc::bb', - updated_by_id: 1, - created_by_id: 1, - ) - Ticket::Article.create!( - ticket_id: @ticket1.id, - from: 'some_sender@example.com', - to: 'some_recipient@example.com', - subject: 'some subject', - message_id: 'some@id', - body: 'some message article_inbound', - internal: false, - sender: Ticket::Article::Sender.where(name: 'Customer').first, - type: Ticket::Article::Type.where(name: 'email').first, - created_at: '2015-10-28 09:30:00 UTC', - updated_at: '2015-10-28 09:30:00 UTC', - updated_by_id: 1, - created_by_id: 1, - ) - @ticket1.tag_add('aaa', 1) - @ticket1.tag_add('bbb', 1) - @ticket1.update!( - group: Group.lookup(name: 'Users'), - updated_at: '2015-10-28 14:30:00 UTC', - ) - - @ticket2 = Ticket.create!( - title: 'test 2', - group: group1, - customer_id: 2, - state: Ticket::State.lookup(name: 'new'), - priority: Ticket::Priority.lookup(name: '2 normal'), - created_at: '2015-10-28 09:30:01 UTC', - updated_at: '2015-10-28 09:30:01 UTC', - updated_by_id: 1, - created_by_id: 1, - ) - Ticket::Article.create!( - ticket_id: @ticket2.id, - from: 'some_sender@example.com', - to: 'some_recipient@example.com', - subject: 'some subject', - message_id: 'some@id', - body: 'some message article_inbound', - internal: false, - sender: Ticket::Article::Sender.where(name: 'Customer').first, - type: Ticket::Article::Type.where(name: 'email').first, - created_at: '2015-10-28 09:30:01 UTC', - updated_at: '2015-10-28 09:30:01 UTC', - updated_by_id: 1, - created_by_id: 1, - ) - @ticket2.tag_add('aaa', 1) - @ticket2.update!( - group_id: group2.id, - updated_at: '2015-10-28 14:30:00 UTC', - ) - - @ticket3 = Ticket.create!( - title: 'test 3', - group: group1, - customer_id: 2, - state: Ticket::State.lookup(name: 'open'), - priority: Ticket::Priority.lookup(name: '3 high'), - created_at: '2015-10-28 10:30:00 UTC', - updated_at: '2015-10-28 10:30:00 UTC', - updated_by_id: 1, - created_by_id: 1, - ) - Ticket::Article.create!( - ticket_id: @ticket3.id, - from: 'some_sender@example.com', - to: 'some_recipient@example.com', - subject: 'some subject', - message_id: 'some@id', - body: 'some message article_inbound', - internal: false, - sender: Ticket::Article::Sender.where(name: 'Customer').first, - type: Ticket::Article::Type.where(name: 'email').first, - created_at: '2015-10-28 10:30:00 UTC', - updated_at: '2015-10-28 10:30:00 UTC', - updated_by_id: 1, - created_by_id: 1, - ) - - @ticket4 = Ticket.create!( - title: 'test 4', - group: group1, - customer_id: 2, - state: Ticket::State.lookup(name: 'closed'), - priority: Ticket::Priority.lookup(name: '2 normal'), - close_at: '2015-10-28 11:30:00 UTC', - created_at: '2015-10-28 10:30:01 UTC', - updated_at: '2015-10-28 10:30:01 UTC', - updated_by_id: 1, - created_by_id: 1, - ) - Ticket::Article.create!( - ticket_id: @ticket4.id, - from: 'some_sender@example.com', - to: 'some_recipient@example.com', - subject: 'some subject', - message_id: 'some@id', - body: 'some message article_inbound', - internal: false, - sender: Ticket::Article::Sender.where(name: 'Customer').first, - type: Ticket::Article::Type.where(name: 'email').first, - created_at: '2015-10-28 10:30:01 UTC', - updated_at: '2015-10-28 10:30:01 UTC', - updated_by_id: 1, - created_by_id: 1, - ) - - @ticket5 = Ticket.create!( - title: 'test 5', - group: group1, - customer_id: 2, - state: Ticket::State.lookup(name: 'closed'), - priority: Ticket::Priority.lookup(name: '3 high'), - close_at: '2015-10-28 11:40:00 UTC', - created_at: '2015-10-28 11:30:00 UTC', - updated_at: '2015-10-28 11:30:00 UTC', - updated_by_id: 1, - created_by_id: 1, - ) - Ticket::Article.create!( - ticket_id: @ticket5.id, - from: 'some_sender@example.com', - to: 'some_recipient@example.com', - subject: 'some subject', - message_id: 'some@id', - body: 'some message article_outbound', - internal: false, - sender: Ticket::Article::Sender.where(name: 'Agent').first, - type: Ticket::Article::Type.where(name: 'email').first, - created_at: '2015-10-28 11:30:00 UTC', - updated_at: '2015-10-28 11:30:00 UTC', - updated_by_id: 1, - created_by_id: 1, - ) - @ticket5.tag_add('bbb', 1) - @ticket5.update!( - state: Ticket::State.lookup(name: 'open'), - updated_at: '2015-10-28 14:30:00 UTC', - ) - - @ticket6 = Ticket.create!( - title: 'test 6', - group: group1, - customer_id: 2, - state: Ticket::State.lookup(name: 'closed'), - priority: Ticket::Priority.lookup(name: '2 normal'), - close_at: '2015-10-31 12:35:00 UTC', - created_at: '2015-10-31 12:30:00 UTC', - updated_at: '2015-10-31 12:30:00 UTC', - updated_by_id: 1, - created_by_id: 1, - ) - Ticket::Article.create!( - ticket_id: @ticket6.id, - from: 'some_sender@example.com', - to: 'some_recipient@example.com', - subject: 'some subject', - message_id: 'some@id', - body: 'some message article_outbound', - internal: false, - sender: Ticket::Article::Sender.where(name: 'Agent').first, - type: Ticket::Article::Type.where(name: 'email').first, - created_at: '2015-10-31 12:30:00 UTC', - updated_at: '2015-10-31 12:30:00 UTC', - updated_by_id: 1, - created_by_id: 1, - ) - - @ticket7 = Ticket.create!( - title: 'test 7', - group: group1, - customer_id: 2, - state: Ticket::State.lookup(name: 'closed'), - priority: Ticket::Priority.lookup(name: '2 normal'), - close_at: '2015-11-01 12:30:00 UTC', - created_at: '2015-11-01 12:30:00 UTC', - updated_at: '2015-11-01 12:30:00 UTC', - updated_by_id: 1, - created_by_id: 1, - ) - Ticket::Article.create!( - ticket_id: @ticket7.id, - from: 'some_sender@example.com', - to: 'some_recipient@example.com', - subject: 'some subject', - message_id: 'some@id', - body: 'some message article_outbound', - internal: false, - sender: Ticket::Article::Sender.where(name: 'Agent').first, - type: Ticket::Article::Type.where(name: 'email').first, - created_at: '2015-11-01 12:30:00 UTC', - updated_at: '2015-11-01 12:30:00 UTC', - updated_by_id: 1, - created_by_id: 1, - ) - - @ticket8 = Ticket.create!( - title: 'test 8', - group: group1, - customer_id: 2, - state: Ticket::State.lookup(name: 'merged'), - priority: Ticket::Priority.lookup(name: '2 normal'), - close_at: '2015-11-02 12:30:00 UTC', - created_at: '2015-11-02 12:30:00 UTC', - updated_at: '2015-11-02 12:30:00 UTC', - updated_by_id: 1, - created_by_id: 1, - ) - Ticket::Article.create!( - ticket_id: @ticket8.id, - from: 'some_sender@example.com', - to: 'some_recipient@example.com', - subject: 'some subject', - message_id: 'some@id', - body: 'some message article_outbound', - internal: false, - sender: Ticket::Article::Sender.where(name: 'Agent').first, - type: Ticket::Article::Type.where(name: 'email').first, - created_at: '2015-11-02 12:30:00 UTC', - updated_at: '2015-11-02 12:30:00 UTC', - updated_by_id: 1, - created_by_id: 1, - ) - - @ticket9 = Ticket.create!( - title: 'test 9', - group: group1, - customer_id: 2, - state: Ticket::State.lookup(name: 'open'), - priority: Ticket::Priority.lookup(name: '2 normal'), - close_at: '2037-11-02 12:30:00 UTC', - created_at: '2037-11-02 12:30:00 UTC', - updated_at: '2037-11-02 12:30:00 UTC', - updated_by_id: 1, - created_by_id: 1, - ) - Ticket::Article.create!( - ticket_id: @ticket9.id, - from: 'some_sender@example.com', - to: 'some_recipient@example.com', - subject: 'some subject', - message_id: 'some@id', - body: 'some message article_outbound', - internal: false, - sender: Ticket::Article::Sender.where(name: 'Agent').first, - type: Ticket::Article::Type.where(name: 'email').first, - created_at: '2037-11-02 12:30:00 UTC', - updated_at: '2037-11-02 12:30:00 UTC', - updated_by_id: 1, - created_by_id: 1, - ) - - # execute background jobs - Scheduler.worker(true) - SearchIndexBackend.refresh - end - - test 'compare' do - - # first solution - result = Report::TicketFirstSolution.aggs( - range_start: Time.zone.parse('2015-01-01T00:00:00Z'), - range_end: Time.zone.parse('2015-12-31T23:59:59Z'), - interval: 'month', # year, quarter, month, week, day, hour, minute, second - selector: {}, # ticket selector to get only a collection of tickets - ) - assert(result) - assert_equal(0, result[0]) - assert_equal(0, result[1]) - assert_equal(0, result[2]) - assert_equal(0, result[3]) - assert_equal(0, result[4]) - assert_equal(0, result[5]) - assert_equal(0, result[6]) - assert_equal(0, result[7]) - assert_equal(0, result[8]) - assert_equal(2, result[9]) - assert_equal(1, result[10]) - assert_equal(0, result[11]) - assert_nil(result[12]) - - result = Report::TicketFirstSolution.items( - range_start: Time.zone.parse('2015-01-01T00:00:00Z'), - range_end: Time.zone.parse('2015-12-31T23:59:59Z'), - selector: {}, # ticket selector to get only a collection of tickets - ) - assert(result) - assert_equal(@ticket5.id, result[:ticket_ids][0]) - assert_equal(@ticket6.id, result[:ticket_ids][1]) - assert_equal(@ticket7.id, result[:ticket_ids][2]) - assert_nil(result[:ticket_ids][3]) - - # month - with selector #1 - result = Report::TicketFirstSolution.aggs( - range_start: Time.zone.parse('2015-01-01T00:00:00Z'), - range_end: Time.zone.parse('2015-12-31T23:59:59Z'), - interval: 'month', # year, quarter, month, week, day, hour, minute, second - selector: { - 'ticket.priority_id' => { - 'operator' => 'is', - 'value' => [Ticket::Priority.lookup(name: '3 high').id], - } - }, # ticket selector to get only a collection of tickets - ) - assert(result) - assert_equal(0, result[0]) - assert_equal(0, result[1]) - assert_equal(0, result[2]) - assert_equal(0, result[3]) - assert_equal(0, result[4]) - assert_equal(0, result[5]) - assert_equal(0, result[6]) - assert_equal(0, result[7]) - assert_equal(0, result[8]) - assert_equal(1, result[9]) - assert_equal(0, result[10]) - assert_equal(0, result[11]) - assert_nil(result[12]) - - result = Report::TicketFirstSolution.items( - range_start: Time.zone.parse('2015-01-01T00:00:00Z'), - range_end: Time.zone.parse('2015-12-31T23:59:59Z'), - selector: { - 'ticket.priority_id' => { - 'operator' => 'is', - 'value' => [Ticket::Priority.lookup(name: '3 high').id], - } - }, # ticket selector to get only a collection of tickets - ) - assert(result) - assert_equal(@ticket5.id, result[:ticket_ids][0]) - assert_nil(result[:ticket_ids][1]) - - # month - with merged tickets selector - result = Report::TicketFirstSolution.aggs( - range_start: Time.zone.parse('2015-01-01T00:00:00Z'), - range_end: Time.zone.parse('2015-12-31T23:59:59Z'), - interval: 'month', # year, quarter, month, week, day, hour, minute, second - selector: { - 'ticket_state.name' => { - 'operator' => 'is not', - 'value' => 'merged', - } - }, # ticket selector to get only a collection of tickets - ) - assert(result) - assert_equal(0, result[0]) - assert_equal(0, result[1]) - assert_equal(0, result[2]) - assert_equal(0, result[3]) - assert_equal(0, result[4]) - assert_equal(0, result[5]) - assert_equal(0, result[6]) - assert_equal(0, result[7]) - assert_equal(0, result[8]) - assert_equal(2, result[9]) - assert_equal(1, result[10]) - assert_equal(0, result[11]) - assert_nil(result[12]) - - result = Report::TicketFirstSolution.items( - range_start: Time.zone.parse('2015-01-01T00:00:00Z'), - range_end: Time.zone.parse('2015-12-31T23:59:59Z'), - selector: { - 'ticket_state.name' => { - 'operator' => 'is not', - 'value' => 'merged', - } - }, # ticket selector to get only a collection of tickets - ) - assert(result) - assert_equal(@ticket5.id, result[:ticket_ids][0]) - assert_nil(result[:ticket_ids][3]) - - # month - with selector #2 - result = Report::TicketFirstSolution.aggs( - range_start: Time.zone.parse('2015-01-01T00:00:00Z'), - range_end: Time.zone.parse('2015-12-31T23:59:59Z'), - interval: 'month', # year, quarter, month, week, day, hour, minute, second - selector: { - 'ticket.priority_id' => { - 'operator' => 'is not', - 'value' => [Ticket::Priority.lookup(name: '3 high').id], - } - }, # ticket selector to get only a collection of tickets - ) - assert(result) - assert_equal(0, result[0]) - assert_equal(0, result[1]) - assert_equal(0, result[2]) - assert_equal(0, result[3]) - assert_equal(0, result[4]) - assert_equal(0, result[5]) - assert_equal(0, result[6]) - assert_equal(0, result[7]) - assert_equal(0, result[8]) - assert_equal(1, result[9]) - assert_equal(1, result[10]) - assert_equal(0, result[11]) - assert_nil(result[12]) - - result = Report::TicketFirstSolution.items( - range_start: Time.zone.parse('2015-01-01T00:00:00Z'), - range_end: Time.zone.parse('2015-12-31T23:59:59Z'), - selector: { - 'ticket.priority_id' => { - 'operator' => 'is not', - 'value' => [Ticket::Priority.lookup(name: '3 high').id], - } - }, # ticket selector to get only a collection of tickets - ) - assert(result) - assert_equal(@ticket6.id, result[:ticket_ids][0]) - assert_equal(@ticket7.id, result[:ticket_ids][1]) - assert_nil(result[:ticket_ids][2]) - - # week - result = Report::TicketFirstSolution.aggs( - range_start: Time.zone.parse('2015-10-26T00:00:00Z'), - range_end: Time.zone.parse('2015-10-31T23:59:59Z'), - interval: 'week', # year, quarter, month, week, day, hour, minute, second - selector: {}, # ticket selector to get only a collection of tickets - ) - assert(result) - assert_equal(0, result[0]) - assert_equal(0, result[1]) - assert_equal(1, result[2]) - assert_equal(0, result[3]) - assert_equal(0, result[4]) - assert_equal(1, result[5]) - assert_equal(1, result[6]) - assert_nil(result[7]) - - result = Report::TicketFirstSolution.items( - range_start: Time.zone.parse('2015-10-26T00:00:00Z'), - range_end: Time.zone.parse('2015-11-01T23:59:59Z'), - interval: 'week', # year, quarter, month, week, day, hour, minute, second - selector: {}, # ticket selector to get only a collection of tickets - ) - assert(result) - assert_equal(@ticket5.id, result[:ticket_ids][0]) - assert_equal(@ticket6.id, result[:ticket_ids][1]) - assert_equal(@ticket7.id, result[:ticket_ids][2]) - assert_nil(result[:ticket_ids][3]) - - # day - result = Report::TicketFirstSolution.aggs( - range_start: Time.zone.parse('2015-10-01T00:00:00Z'), - range_end: Time.zone.parse('2015-11-01T23:59:59Z'), - interval: 'day', # year, quarter, month, week, day, hour, minute, second - selector: {}, # ticket selector to get only a collection of tickets - ) - assert(result) - assert_equal(0, result[0]) - assert_equal(0, result[1]) - assert_equal(0, result[2]) - assert_equal(0, result[3]) - assert_equal(0, result[4]) - assert_equal(0, result[5]) - assert_equal(0, result[6]) - assert_equal(0, result[7]) - assert_equal(0, result[8]) - assert_equal(0, result[9]) - assert_equal(0, result[10]) - assert_equal(0, result[11]) - assert_equal(0, result[12]) - assert_equal(0, result[13]) - assert_equal(0, result[14]) - assert_equal(0, result[15]) - assert_equal(0, result[16]) - assert_equal(0, result[17]) - assert_equal(0, result[18]) - assert_equal(0, result[19]) - assert_equal(0, result[20]) - assert_equal(0, result[21]) - assert_equal(0, result[22]) - assert_equal(0, result[23]) - assert_equal(0, result[24]) - assert_equal(0, result[25]) - assert_equal(0, result[26]) - assert_equal(1, result[27]) - assert_equal(0, result[28]) - assert_equal(0, result[29]) - assert_equal(1, result[30]) - assert_nil(result[31]) - - result = Report::TicketFirstSolution.items( - range_start: Time.zone.parse('2015-10-01T00:00:00Z'), - range_end: Time.zone.parse('2015-10-31T23:59:59Z'), - interval: 'day', # year, quarter, month, week, day, hour, minute, second - selector: {}, # ticket selector to get only a collection of tickets - ) - assert(result) - assert_equal(@ticket5.id, result[:ticket_ids][0]) - assert_equal(@ticket6.id, result[:ticket_ids][1]) - assert_nil(result[:ticket_ids][2]) - - # hour - result = Report::TicketFirstSolution.aggs( - range_start: Time.zone.parse('2015-10-28T00:00:00Z'), - range_end: Time.zone.parse('2015-10-28T23:59:59Z'), - interval: 'hour', # year, quarter, month, week, day, hour, minute, second - selector: {}, # ticket selector to get only a collection of tickets - ) - assert(result) - assert_equal(0, result[0]) - assert_equal(0, result[1]) - assert_equal(0, result[2]) - assert_equal(0, result[3]) - assert_equal(0, result[4]) - assert_equal(0, result[5]) - assert_equal(0, result[6]) - assert_equal(0, result[7]) - assert_equal(0, result[8]) - assert_equal(0, result[9]) - assert_equal(0, result[10]) - assert_equal(1, result[11]) - assert_equal(0, result[12]) - assert_equal(0, result[13]) - assert_equal(0, result[14]) - assert_equal(0, result[15]) - assert_equal(0, result[16]) - assert_equal(0, result[17]) - assert_equal(0, result[18]) - assert_equal(0, result[19]) - assert_equal(0, result[20]) - assert_equal(0, result[21]) - assert_equal(0, result[22]) - assert_equal(0, result[23]) - assert_nil(result[24]) - - result = Report::TicketFirstSolution.items( - range_start: Time.zone.parse('2015-10-28T00:00:00Z'), - range_end: Time.zone.parse('2015-10-28T23:59:59Z'), - interval: 'hour', # year, quarter, month, week, day, hour, minute, second - selector: {}, # ticket selector to get only a collection of tickets - ) - assert(result) - assert_equal(@ticket5.id, result[:ticket_ids][0]) - assert_nil(result[:ticket_ids][1]) - - # reopen - result = Report::TicketReopened.aggs( - range_start: Time.zone.parse('2015-01-01T00:00:00Z'), - range_end: Time.zone.parse('2015-12-31T23:59:59Z'), - interval: 'month', # year, quarter, month, week, day, hour, minute, second - selector: {}, # ticket selector to get only a collection of tickets - ) - assert(result) - assert_equal(0, result[0]) - assert_equal(0, result[1]) - assert_equal(0, result[2]) - assert_equal(0, result[3]) - assert_equal(0, result[4]) - assert_equal(0, result[5]) - assert_equal(0, result[6]) - assert_equal(0, result[7]) - assert_equal(0, result[8]) - assert_equal(1, result[9]) - assert_equal(0, result[10]) - assert_equal(0, result[11]) - assert_nil(result[12]) - - result = Report::TicketReopened.items( - range_start: Time.zone.parse('2015-01-01T00:00:00Z'), - range_end: Time.zone.parse('2015-12-31T23:59:59Z'), - selector: {}, # ticket selector to get only a collection of tickets - ) - assert(result) - assert_equal(@ticket5.id, result[:ticket_ids][0]) - assert_nil(result[:ticket_ids][1]) - - # month - with selector #1 - result = Report::TicketReopened.aggs( - range_start: Time.zone.parse('2015-01-01T00:00:00Z'), - range_end: Time.zone.parse('2015-12-31T23:59:59Z'), - interval: 'month', # year, quarter, month, week, day, hour, minute, second - selector: { - 'ticket.priority_id' => { - 'operator' => 'is', - 'value' => [Ticket::Priority.lookup(name: '3 high').id], - } - }, # ticket selector to get only a collection of tickets - ) - assert(result) - assert_equal(0, result[0]) - assert_equal(0, result[1]) - assert_equal(0, result[2]) - assert_equal(0, result[3]) - assert_equal(0, result[4]) - assert_equal(0, result[5]) - assert_equal(0, result[6]) - assert_equal(0, result[7]) - assert_equal(0, result[8]) - assert_equal(1, result[9]) - assert_equal(0, result[10]) - assert_equal(0, result[11]) - assert_nil(result[12]) - - result = Report::TicketReopened.items( - range_start: Time.zone.parse('2015-01-01T00:00:00Z'), - range_end: Time.zone.parse('2015-12-31T23:59:59Z'), - selector: { - 'ticket.priority_id' => { - 'operator' => 'is', - 'value' => [Ticket::Priority.lookup(name: '3 high').id], - } - }, # ticket selector to get only a collection of tickets - ) - assert(result) - assert_equal(@ticket5.id, result[:ticket_ids][0]) - assert_nil(result[:ticket_ids][1]) - - # month - with selector #2 - result = Report::TicketReopened.aggs( - range_start: Time.zone.parse('2015-01-01T00:00:00Z'), - range_end: Time.zone.parse('2015-12-31T23:59:59Z'), - interval: 'month', # year, quarter, month, week, day, hour, minute, second - selector: { - 'ticket.priority_id' => { - 'operator' => 'is not', - 'value' => [Ticket::Priority.lookup(name: '3 high').id], - } - }, # ticket selector to get only a collection of tickets - ) - assert(result) - assert_equal(0, result[0]) - assert_equal(0, result[1]) - assert_equal(0, result[2]) - assert_equal(0, result[3]) - assert_equal(0, result[4]) - assert_equal(0, result[5]) - assert_equal(0, result[6]) - assert_equal(0, result[7]) - assert_equal(0, result[8]) - assert_equal(0, result[9]) - assert_equal(0, result[10]) - assert_equal(0, result[11]) - assert_nil(result[12]) - - result = Report::TicketReopened.items( - range_start: Time.zone.parse('2015-01-01T00:00:00Z'), - range_end: Time.zone.parse('2015-12-31T23:59:59Z'), - selector: { - 'ticket.priority_id' => { - 'operator' => 'is not', - 'value' => [Ticket::Priority.lookup(name: '3 high').id], - } - }, # ticket selector to get only a collection of tickets - ) - assert(result) - assert_nil(result[:ticket_ids][0]) - - # month - reopened with merge selector - result = Report::TicketReopened.aggs( - range_start: Time.zone.parse('2015-01-01T00:00:00Z'), - range_end: Time.zone.parse('2015-12-31T23:59:59Z'), - interval: 'month', # year, quarter, month, week, day, hour, minute, second - selector: { - 'ticket_state.name' => { - 'operator' => 'is not', - 'value' => 'merged', - } - }, # ticket selector to get only a collection of tickets - ) - assert(result) - assert_equal(0, result[0]) - assert_equal(0, result[1]) - assert_equal(0, result[2]) - assert_equal(0, result[3]) - assert_equal(0, result[4]) - assert_equal(0, result[5]) - assert_equal(0, result[6]) - assert_equal(0, result[7]) - assert_equal(0, result[8]) - assert_equal(1, result[9]) - assert_equal(0, result[10]) - assert_equal(0, result[11]) - assert_nil(result[12]) - - result = Report::TicketReopened.items( - range_start: Time.zone.parse('2015-01-01T00:00:00Z'), - range_end: Time.zone.parse('2015-12-31T23:59:59Z'), - selector: { - 'ticket_state.name' => { - 'operator' => 'is not', - 'value' => 'merged', - } - }, # ticket selector to get only a collection of tickets - ) - assert(result) - assert_equal(@ticket5.id, result[:ticket_ids][0]) - assert_nil(result[:ticket_ids][1]) - - # move in/out without merged status - result = Report::TicketMoved.aggs( - range_start: Time.zone.parse('2015-01-01T00:00:00Z'), - range_end: Time.zone.parse('2015-12-31T23:59:59Z'), - interval: 'month', # year, quarter, month, week, day, hour, minute, second - selector: { - 'ticket_state.name' => { - 'operator' => 'is not', - 'value' => 'merged', - } - }, # ticket selector to get only a collection of tickets - params: { - type: 'in', - }, - ) - assert(result) - assert_equal(0, result[0]) - assert_equal(0, result[1]) - assert_equal(0, result[2]) - assert_equal(0, result[3]) - assert_equal(0, result[4]) - assert_equal(0, result[5]) - assert_equal(0, result[6]) - assert_equal(0, result[7]) - assert_equal(0, result[8]) - assert_equal(0, result[9]) - assert_equal(0, result[10]) - assert_equal(0, result[11]) - assert_nil(result[12]) - - result = Report::TicketMoved.items( - range_start: Time.zone.parse('2015-01-01T00:00:00Z'), - range_end: Time.zone.parse('2015-12-31T23:59:59Z'), - selector: { - 'ticket.group_id' => { - 'operator' => 'is', - 'value' => [Group.lookup(name: 'Users').id], - } - }, # ticket selector to get only a collection of tickets - params: { - type: 'in', - }, - ) - assert(result) - assert_equal(@ticket1.id, result[:ticket_ids][0]) - assert_nil(result[:ticket_ids][1]) - - # move in/out - result = Report::TicketMoved.aggs( - range_start: Time.zone.parse('2015-01-01T00:00:00Z'), - range_end: Time.zone.parse('2015-12-31T23:59:59Z'), - interval: 'month', # year, quarter, month, week, day, hour, minute, second - selector: { - 'ticket.group_id' => { - 'operator' => 'is', - 'value' => [Group.lookup(name: 'Users').id], - } - }, # ticket selector to get only a collection of tickets - params: { - type: 'in', - }, - ) - assert(result) - assert_equal(0, result[0]) - assert_equal(0, result[1]) - assert_equal(0, result[2]) - assert_equal(0, result[3]) - assert_equal(0, result[4]) - assert_equal(0, result[5]) - assert_equal(0, result[6]) - assert_equal(0, result[7]) - assert_equal(0, result[8]) - assert_equal(1, result[9]) - assert_equal(0, result[10]) - assert_equal(0, result[11]) - assert_nil(result[12]) - - result = Report::TicketMoved.items( - range_start: Time.zone.parse('2015-01-01T00:00:00Z'), - range_end: Time.zone.parse('2015-12-31T23:59:59Z'), - selector: { - 'ticket.group_id' => { - 'operator' => 'is', - 'value' => [Group.lookup(name: 'Users').id], - } - }, # ticket selector to get only a collection of tickets - params: { - type: 'in', - }, - ) - assert(result) - assert_equal(@ticket1.id, result[:ticket_ids][0]) - assert_nil(result[:ticket_ids][1]) - - # out without merged tickets - result = Report::TicketMoved.aggs( - range_start: Time.zone.parse('2015-01-01T00:00:00Z'), - range_end: Time.zone.parse('2015-12-31T23:59:59Z'), - interval: 'month', # year, quarter, month, week, day, hour, minute, second - selector: { - 'ticket_state.name' => { - 'operator' => 'is not', - 'value' => 'merged', - } - }, # ticket selector to get only a collection of tickets - params: { - type: 'out', - }, - ) - assert(result) - assert_equal(0, result[0]) - assert_equal(0, result[1]) - assert_equal(0, result[2]) - assert_equal(0, result[3]) - assert_equal(0, result[4]) - assert_equal(0, result[5]) - assert_equal(0, result[6]) - assert_equal(0, result[7]) - assert_equal(0, result[8]) - assert_equal(0, result[9]) - assert_equal(0, result[10]) - assert_equal(0, result[11]) - assert_nil(result[12]) - - result = Report::TicketMoved.items( - range_start: Time.zone.parse('2015-01-01T00:00:00Z'), - range_end: Time.zone.parse('2015-12-31T23:59:59Z'), - selector: { - 'ticket_state.name' => { - 'operator' => 'is not', - 'value' => 'merged', - } - }, # ticket selector to get only a collection of tickets - params: { - type: 'out', - }, - ) - assert(result) - assert_nil(result[:ticket_ids][0]) - - # out - result = Report::TicketMoved.aggs( - range_start: Time.zone.parse('2015-01-01T00:00:00Z'), - range_end: Time.zone.parse('2015-12-31T23:59:59Z'), - interval: 'month', # year, quarter, month, week, day, hour, minute, second - selector: { - 'ticket.group_id' => { - 'operator' => 'is', - 'value' => [Group.lookup(name: 'Users').id], - } - }, # ticket selector to get only a collection of tickets - params: { - type: 'out', - }, - ) - assert(result) - assert_equal(0, result[0]) - assert_equal(0, result[1]) - assert_equal(0, result[2]) - assert_equal(0, result[3]) - assert_equal(0, result[4]) - assert_equal(0, result[5]) - assert_equal(0, result[6]) - assert_equal(0, result[7]) - assert_equal(0, result[8]) - assert_equal(1, result[9]) - assert_equal(0, result[10]) - assert_equal(0, result[11]) - assert_nil(result[12]) - - result = Report::TicketMoved.items( - range_start: Time.zone.parse('2015-01-01T00:00:00Z'), - range_end: Time.zone.parse('2015-12-31T23:59:59Z'), - selector: { - 'ticket.group_id' => { - 'operator' => 'is', - 'value' => [Group.lookup(name: 'Users').id], - } - }, # ticket selector to get only a collection of tickets - params: { - type: 'out', - }, - ) - assert(result) - assert_equal(@ticket2.id, result[:ticket_ids][0]) - assert_nil(result[:ticket_ids][1]) - - # create at - result = Report::TicketGenericTime.aggs( - range_start: Time.zone.parse('2015-01-01T00:00:00Z'), - range_end: Time.zone.parse('2015-12-31T23:59:59Z'), - interval: 'month', # year, quarter, month, week, day, hour, minute, second - selector: {}, # ticket selector to get only a collection of tickets - params: { field: 'created_at' }, - ) - assert(result) - assert_equal(0, result[0]) - assert_equal(0, result[1]) - assert_equal(0, result[2]) - assert_equal(0, result[3]) - assert_equal(0, result[4]) - assert_equal(0, result[5]) - assert_equal(0, result[6]) - assert_equal(0, result[7]) - assert_equal(0, result[8]) - assert_equal(6, result[9]) - assert_equal(1, result[10]) - assert_equal(0, result[11]) - assert_nil(result[12]) - - result = Report::TicketGenericTime.items( - range_start: Time.zone.parse('2015-01-01T00:00:00Z'), - range_end: Time.zone.parse('2015-12-31T23:59:59Z'), - selector: {}, # ticket selector to get only a collection of tickets - params: { field: 'created_at' }, - ) - assert(result) - assert_equal(@ticket7.id, result[:ticket_ids][0].to_i) - assert_equal(@ticket6.id, result[:ticket_ids][1].to_i) - assert_equal(@ticket5.id, result[:ticket_ids][2].to_i) - assert_equal(@ticket4.id, result[:ticket_ids][3].to_i) - assert_equal(@ticket3.id, result[:ticket_ids][4].to_i) - assert_equal(@ticket2.id, result[:ticket_ids][5].to_i) - assert_equal(@ticket1.id, result[:ticket_ids][6].to_i) - assert_nil(result[:ticket_ids][7]) - - # create at - selector with merge - result = Report::TicketGenericTime.aggs( - range_start: Time.zone.parse('2015-01-01T00:00:00Z'), - range_end: Time.zone.parse('2015-12-31T23:59:59Z'), - interval: 'month', # year, quarter, month, week, day, hour, minute, second - selector: { - 'state' => { - 'operator' => 'is not', - 'value' => 'merged' - } - }, # ticket selector to get only a collection of tickets - params: { field: 'created_at' }, - ) - assert(result) - assert_equal(0, result[0]) - assert_equal(0, result[1]) - assert_equal(0, result[2]) - assert_equal(0, result[3]) - assert_equal(0, result[4]) - assert_equal(0, result[5]) - assert_equal(0, result[6]) - assert_equal(0, result[7]) - assert_equal(0, result[8]) - assert_equal(6, result[9]) - assert_equal(1, result[10]) - assert_equal(0, result[11]) - assert_nil(result[12]) - - result = Report::TicketGenericTime.items( - range_start: Time.zone.parse('2015-01-01T00:00:00Z'), - range_end: Time.zone.parse('2015-12-31T23:59:59Z'), - selector: { - 'state' => { - 'operator' => 'is not', - 'value' => 'merged' - } - }, # ticket selector to get only a collection of tickets - params: { field: 'created_at' }, - ) - - assert(result) - assert_equal(@ticket7.id, result[:ticket_ids][0].to_i) - assert_equal(@ticket6.id, result[:ticket_ids][1].to_i) - assert_equal(@ticket5.id, result[:ticket_ids][2].to_i) - assert_equal(@ticket4.id, result[:ticket_ids][3].to_i) - assert_equal(@ticket3.id, result[:ticket_ids][4].to_i) - assert_equal(@ticket2.id, result[:ticket_ids][5].to_i) - assert_equal(@ticket1.id, result[:ticket_ids][6].to_i) - assert_nil(result[:ticket_ids][7]) - - result = Report::TicketGenericTime.items( - range_start: Time.zone.parse('2015-01-01T00:00:00Z'), - range_end: Time.zone.parse('2015-12-31T23:59:59Z'), - selector: { - 'created_at' => { - 'operator' => 'before (absolute)', - 'value' => '2015-10-31T00:00:00Z' - } - }, # ticket selector to get only a collection of tickets - params: { field: 'created_at' }, - ) - - assert(result) - assert_equal(@ticket5.id, result[:ticket_ids][0].to_i) - assert_equal(@ticket4.id, result[:ticket_ids][1].to_i) - assert_equal(@ticket3.id, result[:ticket_ids][2].to_i) - assert_equal(@ticket2.id, result[:ticket_ids][3].to_i) - assert_equal(@ticket1.id, result[:ticket_ids][4].to_i) - assert_nil(result[:ticket_ids][5]) - - result = Report::TicketGenericTime.items( - range_start: Time.zone.parse('2015-01-01T00:00:00Z'), - range_end: Time.zone.parse('2015-12-31T23:59:59Z'), - selector: { - 'created_at' => { - 'operator' => 'after (absolute)', - 'value' => '2015-10-31T00:00:00Z' - } - }, # ticket selector to get only a collection of tickets - params: { field: 'created_at' }, - ) - - assert(result) - assert_equal(@ticket7.id, result[:ticket_ids][0].to_i) - assert_equal(@ticket6.id, result[:ticket_ids][1].to_i) - assert_nil(result[:ticket_ids][2]) - - Report::TicketGenericTime.items( - range_start: Time.zone.parse('2015-01-01T00:00:00Z'), - range_end: Time.zone.parse('2015-12-31T23:59:59Z'), - selector: { - 'created_at' => { - 'operator' => 'before (relative)', - 'range' => 'day', - 'value' => '1' - } - }, # ticket selector to get only a collection of tickets - params: { field: 'created_at' }, - ) - - result = Report::TicketGenericTime.items( - range_start: Time.zone.parse('2015-01-01T00:00:00Z'), - range_end: Time.zone.parse('2015-12-31T23:59:59Z'), - selector: { - 'created_at' => { - 'operator' => 'after (relative)', - 'range' => 'day', - 'value' => '1' - } - }, # ticket selector to get only a collection of tickets - params: { field: 'created_at' }, - ) - - assert(result) - assert_nil(result[:ticket_ids][0]) - - result = Report::TicketGenericTime.items( - range_start: Time.zone.parse('2037-01-01T00:00:00Z'), - range_end: Time.zone.parse('2037-12-31T23:59:59Z'), - selector: { - 'created_at' => { - 'operator' => 'before (relative)', - 'range' => 'day', - 'value' => '1' - } - }, # ticket selector to get only a collection of tickets - params: { field: 'created_at' }, - ) - - assert(result) - assert_nil(result[:ticket_ids][0]) - - result = Report::TicketGenericTime.items( - range_start: Time.zone.parse('2037-01-01T00:00:00Z'), - range_end: Time.zone.parse('2037-12-31T23:59:59Z'), - selector: { - 'created_at' => { - 'operator' => 'after (relative)', - 'range' => 'day', - 'value' => '5' - } - }, # ticket selector to get only a collection of tickets - params: { field: 'created_at' }, - ) - - assert(result) - assert_equal(@ticket9.id, result[:ticket_ids][0].to_i) - assert_nil(result[:ticket_ids][1]) - - result = Report::TicketGenericTime.items( - range_start: Time.zone.parse('2037-01-01T00:00:00Z'), - range_end: Time.zone.parse('2037-12-31T23:59:59Z'), - selector: { - 'created_at' => { - 'operator' => 'before (relative)', - 'range' => 'month', - 'value' => '1' - } - }, # ticket selector to get only a collection of tickets - params: { field: 'created_at' }, - ) - - assert(result) - assert_nil(result[:ticket_ids][0]) - - result = Report::TicketGenericTime.items( - range_start: Time.zone.parse('2037-01-01T00:00:00Z'), - range_end: Time.zone.parse('2037-12-31T23:59:59Z'), - selector: { - 'created_at' => { - 'operator' => 'after (relative)', - 'range' => 'month', - 'value' => '5' - } - }, # ticket selector to get only a collection of tickets - params: { field: 'created_at' }, - ) - - assert(result) - assert_equal(@ticket9.id, result[:ticket_ids][0].to_i) - assert_nil(result[:ticket_ids][1]) - - result = Report::TicketGenericTime.items( - range_start: Time.zone.parse('2037-01-01T00:00:00Z'), - range_end: Time.zone.parse('2037-12-31T23:59:59Z'), - selector: { - 'created_at' => { - 'operator' => 'before (relative)', - 'range' => 'year', - 'value' => '1' - } - }, # ticket selector to get only a collection of tickets - params: { field: 'created_at' }, - ) - - assert(result) - assert_nil(result[:ticket_ids][0]) - - result = Report::TicketGenericTime.items( - range_start: Time.zone.parse('2037-01-01T00:00:00Z'), - range_end: Time.zone.parse('2037-12-31T23:59:59Z'), - selector: { - 'created_at' => { - 'operator' => 'after (relative)', - 'range' => 'year', - 'value' => '5' - } - }, # ticket selector to get only a collection of tickets - params: { field: 'created_at' }, - ) - - assert(result) - assert_equal(@ticket9.id, result[:ticket_ids][0].to_i) - assert_nil(result[:ticket_ids][1]) - - result = Report::TicketGenericTime.items( - range_start: Time.zone.parse('2015-01-01T00:00:00Z'), - range_end: Time.zone.parse('2015-12-31T23:59:59Z'), - selector: { - 'tags' => { - 'operator' => 'contains all', - 'value' => 'aaa, bbb' - } - }, # ticket selector to get only a collection of tickets - params: { field: 'created_at' }, - ) - - assert(result) - assert_equal(@ticket1.id, result[:ticket_ids][0].to_i) - assert_nil(result[:ticket_ids][1]) - - result = Report::TicketGenericTime.items( - range_start: Time.zone.parse('2015-01-01T00:00:00Z'), - range_end: Time.zone.parse('2015-12-31T23:59:59Z'), - selector: { - 'tags' => { - 'operator' => 'contains all not', - 'value' => 'aaa, bbb' - } - }, # ticket selector to get only a collection of tickets - params: { field: 'created_at' }, - ) - - assert(result) - assert_equal(@ticket7.id, result[:ticket_ids][0].to_i) - assert_equal(@ticket6.id, result[:ticket_ids][1].to_i) - assert_equal(@ticket5.id, result[:ticket_ids][2].to_i) - assert_equal(@ticket4.id, result[:ticket_ids][3].to_i) - assert_equal(@ticket3.id, result[:ticket_ids][4].to_i) - assert_equal(@ticket2.id, result[:ticket_ids][5].to_i) - assert_nil(result[:ticket_ids][6]) - - result = Report::TicketGenericTime.items( - range_start: Time.zone.parse('2015-01-01T00:00:00Z'), - range_end: Time.zone.parse('2015-12-31T23:59:59Z'), - selector: { - 'tags' => { - 'operator' => 'contains all', - 'value' => 'aaa' - } - }, # ticket selector to get only a collection of tickets - params: { field: 'created_at' }, - ) - - assert(result) - - assert_equal(@ticket2.id, result[:ticket_ids][0].to_i) - assert_equal(@ticket1.id, result[:ticket_ids][1].to_i) - assert_nil(result[:ticket_ids][2]) - - result = Report::TicketGenericTime.items( - range_start: Time.zone.parse('2015-01-01T00:00:00Z'), - range_end: Time.zone.parse('2015-12-31T23:59:59Z'), - selector: { - 'tags' => { - 'operator' => 'contains all not', - 'value' => 'aaa' - } - }, # ticket selector to get only a collection of tickets - params: { field: 'created_at' }, - ) - - assert(result) - assert_equal(@ticket7.id, result[:ticket_ids][0].to_i) - assert_equal(@ticket6.id, result[:ticket_ids][1].to_i) - assert_equal(@ticket5.id, result[:ticket_ids][2].to_i) - assert_equal(@ticket4.id, result[:ticket_ids][3].to_i) - assert_equal(@ticket3.id, result[:ticket_ids][4].to_i) - assert_nil(result[:ticket_ids][5]) - - result = Report::TicketGenericTime.items( - range_start: Time.zone.parse('2015-01-01T00:00:00Z'), - range_end: Time.zone.parse('2015-12-31T23:59:59Z'), - selector: { - 'tags' => { - 'operator' => 'contains one not', - 'value' => 'aaa' - } - }, # ticket selector to get only a collection of tickets - params: { field: 'created_at' }, - ) - - assert(result) - - assert_equal(@ticket7.id, result[:ticket_ids][0].to_i) - assert_equal(@ticket6.id, result[:ticket_ids][1].to_i) - assert_equal(@ticket5.id, result[:ticket_ids][2].to_i) - assert_equal(@ticket4.id, result[:ticket_ids][3].to_i) - assert_equal(@ticket3.id, result[:ticket_ids][4].to_i) - assert_nil(result[:ticket_ids][5]) - - result = Report::TicketGenericTime.items( - range_start: Time.zone.parse('2015-01-01T00:00:00Z'), - range_end: Time.zone.parse('2015-12-31T23:59:59Z'), - selector: { - 'tags' => { - 'operator' => 'contains one not', - 'value' => 'aaa, bbb' - } - }, # ticket selector to get only a collection of tickets - params: { field: 'created_at' }, - ) - - assert(result) - assert_equal(@ticket7.id, result[:ticket_ids][0].to_i) - assert_equal(@ticket6.id, result[:ticket_ids][1].to_i) - assert_equal(@ticket4.id, result[:ticket_ids][2].to_i) - assert_equal(@ticket3.id, result[:ticket_ids][3].to_i) - assert_nil(result[:ticket_ids][4]) - - result = Report::TicketGenericTime.items( - range_start: Time.zone.parse('2015-01-01T00:00:00Z'), - range_end: Time.zone.parse('2015-12-31T23:59:59Z'), - selector: { - 'tags' => { - 'operator' => 'contains one', - 'value' => 'aaa' - } - }, # ticket selector to get only a collection of tickets - params: { field: 'created_at' }, - ) - - assert(result) - - assert_equal(@ticket2.id, result[:ticket_ids][0].to_i) - assert_equal(@ticket1.id, result[:ticket_ids][1].to_i) - assert_nil(result[:ticket_ids][2]) - - result = Report::TicketGenericTime.items( - range_start: Time.zone.parse('2015-01-01T00:00:00Z'), - range_end: Time.zone.parse('2015-12-31T23:59:59Z'), - selector: { - 'tags' => { - 'operator' => 'contains one', - 'value' => 'aaa, bbb' - } - }, # ticket selector to get only a collection of tickets - params: { field: 'created_at' }, - ) - - assert(result) - assert_equal(@ticket5.id, result[:ticket_ids][0].to_i) - assert_equal(@ticket2.id, result[:ticket_ids][1].to_i) - assert_equal(@ticket1.id, result[:ticket_ids][2].to_i) - assert_nil(result[:ticket_ids][3]) - - result = Report::TicketGenericTime.items( - range_start: Time.zone.parse('2015-01-01T00:00:00Z'), - range_end: Time.zone.parse('2015-12-31T23:59:59Z'), - selector: { - 'title' => { - 'operator' => 'contains', - 'value' => 'test' - } - }, # ticket selector to get only a collection of tickets - params: { field: 'created_at' }, - ) - - assert(result) - assert_equal(@ticket7.id, result[:ticket_ids][0].to_i) - assert_equal(@ticket6.id, result[:ticket_ids][1].to_i) - assert_equal(@ticket5.id, result[:ticket_ids][2].to_i) - assert_equal(@ticket4.id, result[:ticket_ids][3].to_i) - assert_equal(@ticket3.id, result[:ticket_ids][4].to_i) - assert_equal(@ticket2.id, result[:ticket_ids][5].to_i) - assert_equal(@ticket1.id, result[:ticket_ids][6].to_i) - assert_nil(result[:ticket_ids][7]) - - result = Report::TicketGenericTime.items( - range_start: Time.zone.parse('2015-01-01T00:00:00Z'), - range_end: Time.zone.parse('2015-12-31T23:59:59Z'), - selector: { - 'title' => { - 'operator' => 'contains not', - 'value' => 'test' - } - }, # ticket selector to get only a collection of tickets - params: { field: 'created_at' }, - ) - - assert(result) - assert_nil(result[:ticket_ids][0]) - - # search for test_category.keyword to find values with :: in query - result = Report::TicketGenericTime.items( - range_start: Time.zone.parse('2015-01-01T00:00:00Z'), - range_end: Time.zone.parse('2015-12-31T23:59:59Z'), - selector: { - 'test_category' => { - 'operator' => 'is', - 'value' => 'cc::bb' - }, - }, # ticket selector to get only a collection of tickets - params: { field: 'created_at' }, - ) - - assert(result) - assert_equal(@ticket1.id, result[:ticket_ids][0].to_i) - assert_nil(result[:ticket_ids][1]) - end - -end