Fixes #3616 - Provide meaningful modal if report profile tries to use dates out side the filtered date range
This commit is contained in:
parent
d2f24fc0fc
commit
04d1459121
16 changed files with 1481 additions and 1471 deletions
|
@ -11,7 +11,6 @@
|
||||||
- bundle exec rails test test/integration/elasticsearch_active_test.rb
|
- bundle exec rails test test/integration/elasticsearch_active_test.rb
|
||||||
- bundle exec rails test test/integration/elasticsearch_test.rb
|
- bundle exec rails test test/integration/elasticsearch_test.rb
|
||||||
- bundle exec rspec --tag searchindex --tag ~type:system --profile 10
|
- bundle exec rspec --tag searchindex --tag ~type:system --profile 10
|
||||||
- bundle exec rails test test/integration/report_test.rb
|
|
||||||
|
|
||||||
es:7:
|
es:7:
|
||||||
<<: *template_integration_es
|
<<: *template_integration_es
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
class App.ControllerTechnicalErrorModal extends App.ControllerModal
|
||||||
|
head: "StatusCode: #{status}"
|
||||||
|
contentCode: ''
|
||||||
|
buttonClose: false
|
||||||
|
buttonSubmit: 'Ok'
|
||||||
|
onSubmit: (e) -> @close(e)
|
||||||
|
|
||||||
|
content: ->
|
||||||
|
"<pre><code>#{@contentCode}</code></pre>"
|
|
@ -171,6 +171,13 @@ class Graph extends App.Controller
|
||||||
backends: @params.backendSelected
|
backends: @params.backendSelected
|
||||||
)
|
)
|
||||||
processData: true
|
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) =>
|
success: (data) =>
|
||||||
@update(data)
|
@update(data)
|
||||||
@delay(@render, interval, 'report-update', 'page')
|
@delay(@render, interval, 'report-update', 'page')
|
||||||
|
|
|
@ -102,12 +102,18 @@ class _ajaxSingleton
|
||||||
# do not show any error message with code 502
|
# do not show any error message with code 502
|
||||||
return if status is 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
|
# show error message
|
||||||
new App.ControllerModal(
|
new App.ControllerTechnicalErrorModal(
|
||||||
|
contentCode: escaped
|
||||||
head: "StatusCode: #{status}"
|
head: "StatusCode: #{status}"
|
||||||
contentInline: "<pre>#{App.Utils.htmlEscape(detail)}</pre>"
|
|
||||||
buttonClose: true
|
|
||||||
buttonSubmit: false
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -358,6 +358,26 @@ ol, ul {
|
||||||
z-index: 1;
|
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 {
|
pre {
|
||||||
display: block;
|
display: block;
|
||||||
padding: 9.5px;
|
padding: 9.5px;
|
||||||
|
@ -371,30 +391,26 @@ pre {
|
||||||
border: 1px solid hsl(0,0%,90%);
|
border: 1px solid hsl(0,0%,90%);
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.modal-content pre {
|
||||||
|
background: hsl(0, 0%, 97%);
|
||||||
|
border: 1px solid hsl(0, 0%, 87%);
|
||||||
|
}
|
||||||
|
|
||||||
pre code {
|
pre code {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
font-size: inherit;
|
font-size: inherit;
|
||||||
color: inherit;
|
color: inherit;
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
background-color: transparent;
|
|
||||||
border-radius: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hljs,
|
|
||||||
code {
|
|
||||||
background: none;
|
background: none;
|
||||||
padding: 2px 4px;
|
border-radius: 0;
|
||||||
font-size: 0.88em;
|
border: none;
|
||||||
}
|
overflow-x: auto;
|
||||||
|
|
||||||
code:not(.hljs) {
|
&.hljs {
|
||||||
border: 1px solid rgba(0,0,0,.2);
|
padding: 0;
|
||||||
border-radius: 3px;
|
background: none;
|
||||||
white-space: nowrap;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pre code.hljs {
|
|
||||||
font-size: 1em;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.textarea::placeholder,
|
.textarea::placeholder,
|
||||||
|
|
|
@ -22,6 +22,7 @@ class ReportsController < ApplicationController
|
||||||
get_params = params_all
|
get_params = params_all
|
||||||
return if !get_params
|
return if !get_params
|
||||||
|
|
||||||
|
begin
|
||||||
result = {}
|
result = {}
|
||||||
get_params[:metric][:backend].each do |backend|
|
get_params[:metric][:backend].each do |backend|
|
||||||
condition = get_params[:profile].condition
|
condition = get_params[:profile].condition
|
||||||
|
@ -43,6 +44,13 @@ class ReportsController < ApplicationController
|
||||||
current_user: current_user
|
current_user: current_user
|
||||||
)
|
)
|
||||||
end
|
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: {
|
render json: {
|
||||||
data: result
|
data: result
|
||||||
|
|
|
@ -438,6 +438,8 @@ example for aggregations within one year
|
||||||
|
|
||||||
data = selector2query(selectors, options, aggs_interval)
|
data = selector2query(selectors, options, aggs_interval)
|
||||||
|
|
||||||
|
verify_date_range(url, data)
|
||||||
|
|
||||||
response = make_request(url, data: data)
|
response = make_request(url, data: data)
|
||||||
|
|
||||||
if !response.success?
|
if !response.success?
|
||||||
|
@ -1208,4 +1210,87 @@ helper method for making HTTP calls and raising error if response was not succes
|
||||||
)
|
)
|
||||||
end
|
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<Range<DateTime>>] 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<DateTime>]
|
||||||
|
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<DateTime>]
|
||||||
|
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
|
end
|
||||||
|
|
|
@ -6,5 +6,20 @@ FactoryBot.define do
|
||||||
active { true }
|
active { true }
|
||||||
created_by_id { 1 }
|
created_by_id { 1 }
|
||||||
updated_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
|
||||||
end
|
end
|
||||||
|
|
192
spec/lib/report/ticket_first_solution_spec.rb
Normal file
192
spec/lib/report/ticket_first_solution_spec.rb
Normal file
|
@ -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
|
|
@ -1,33 +1,333 @@
|
||||||
# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/
|
# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/
|
||||||
|
|
||||||
require 'rails_helper'
|
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 = Report::TicketGenericTime.items(
|
result = described_class.aggs(
|
||||||
range_start: '2015-01-01T00:00:00Z',
|
range_start: Time.zone.parse('2015-01-01T00:00:00Z'),
|
||||||
range_end: '2015-12-31T23:59:59Z',
|
range_end: Time.zone.parse('2015-12-31T23:59:59Z'),
|
||||||
selector: selector, # ticket selector to get only a collection of tickets
|
interval: 'month', # year, quarter, month, week, day, hour, minute, second
|
||||||
|
selector: {}, # ticket selector to get only a collection of tickets
|
||||||
params: { field: 'created_at' },
|
params: { field: 'created_at' },
|
||||||
)
|
)
|
||||||
|
|
||||||
returns
|
expect(result).to eq [0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 1, 0]
|
||||||
|
end
|
||||||
|
|
||||||
{
|
it 'gets monthly aggregated results by created_at not merged' do
|
||||||
count: 123,
|
result = described_class.aggs(
|
||||||
ticket_ids: [4,5,1,5,0,51,5,56,7,4],
|
range_start: Time.zone.parse('2015-01-01T00:00:00Z'),
|
||||||
assets: assets,
|
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' },
|
||||||
|
)
|
||||||
|
|
||||||
=end
|
expect(result).to eq [0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 1, 0]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe 'items' do
|
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' },
|
||||||
|
)
|
||||||
|
|
||||||
|
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
|
# Regression test for issue #2246 - Records in Reporting not updated when single ActiveRecord can not be found
|
||||||
it 'correctly handles missing tickets' do
|
it 'correctly handles missing tickets', searchindex: false do
|
||||||
class_double('SearchIndexBackend', selectors: { ticket_ids: [-1] }).as_stubbed_const
|
class_double('SearchIndexBackend', selectors: { ticket_ids: [-1] }, drop_index: nil, drop_pipeline: nil).as_stubbed_const
|
||||||
|
|
||||||
expect do
|
expect do
|
||||||
described_class.items(
|
described_class.items(
|
||||||
|
@ -39,4 +339,78 @@ returns
|
||||||
end.not_to raise_error
|
end.not_to raise_error
|
||||||
end
|
end
|
||||||
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
|
end
|
||||||
|
|
148
spec/lib/report/ticket_moved_spec.rb
Normal file
148
spec/lib/report/ticket_moved_spec.rb
Normal file
|
@ -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
|
129
spec/lib/report/ticket_reopened_spec.rb
Normal file
129
spec/lib/report/ticket_reopened_spec.rb
Normal file
|
@ -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
|
245
spec/lib/report_examples.rb
Normal file
245
spec/lib/report_examples.rb
Normal file
|
@ -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
|
|
@ -2,11 +2,12 @@
|
||||||
|
|
||||||
require 'rails_helper'
|
require 'rails_helper'
|
||||||
|
|
||||||
RSpec.describe SearchIndexBackend, searchindex: true do
|
RSpec.describe SearchIndexBackend do
|
||||||
|
|
||||||
before do
|
before do |example|
|
||||||
configure_elasticsearch
|
next if !example.metadata[:searchindex]
|
||||||
rebuild_searchindex
|
|
||||||
|
configure_elasticsearch(required: true, rebuild: true)
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '.build_query' do
|
describe '.build_query' do
|
||||||
|
@ -19,7 +20,7 @@ RSpec.describe SearchIndexBackend, searchindex: true do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '.search' do
|
describe '.search', searchindex: true do
|
||||||
|
|
||||||
context 'query finds results' do
|
context 'query finds results' do
|
||||||
|
|
||||||
|
@ -200,7 +201,8 @@ RSpec.describe SearchIndexBackend, searchindex: true do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '.remove' do
|
describe '.remove', searchindex: true do
|
||||||
|
|
||||||
context 'record gets deleted' do
|
context 'record gets deleted' do
|
||||||
|
|
||||||
let(:record_type) { 'Ticket'.freeze }
|
let(:record_type) { 'Ticket'.freeze }
|
||||||
|
@ -239,7 +241,7 @@ RSpec.describe SearchIndexBackend, searchindex: true do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '.selectors' do
|
describe '.selectors', searchindex: true do
|
||||||
|
|
||||||
let(:group1) { create :group }
|
let(:group1) { create :group }
|
||||||
let(:organization1) { create :organization, note: 'hihi' }
|
let(:organization1) { create :organization, note: 'hihi' }
|
||||||
|
@ -845,4 +847,145 @@ RSpec.describe SearchIndexBackend, searchindex: true do
|
||||||
end
|
end
|
||||||
end
|
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
|
end
|
||||||
|
|
|
@ -33,4 +33,42 @@ RSpec.describe 'Report', type: :system, searchindex: true do
|
||||||
end
|
end
|
||||||
end
|
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
|
end
|
||||||
|
|
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue