diff --git a/spec/lib/stats/ticket_waiting_time_spec.rb b/spec/lib/stats/ticket_waiting_time_spec.rb new file mode 100644 index 000000000..ed0e2b20e --- /dev/null +++ b/spec/lib/stats/ticket_waiting_time_spec.rb @@ -0,0 +1,159 @@ +require 'rails_helper' + +RSpec.describe Stats::TicketWaitingTime do + describe '.generate' do + let(:user) { create(:agent_user, groups: [group]) } + let(:group) { create(:group) } + + context 'when given an agent with no tickets' do + it 'returns a hash with 1-day average ticket wait time for user (in minutes)' do + expect(Stats::TicketWaitingTime.generate(user)).to include(handling_time: 0) + end + + it 'returns a hash with 1-day average ticket wait time across user’s groups (in minutes)' do + expect(Stats::TicketWaitingTime.generate(user)).to include(average_per_agent: 0) + end + + it 'returns a hash with verbal grade for average ticket wait time' do + expect(Stats::TicketWaitingTime.generate(user)).to include(state: 'supergood') + end + + it 'returns a hash with decimal score (0–1) of user’s risk of falling to a lower grade' do + expect(Stats::TicketWaitingTime.generate(user)).to include(percent: 0.0) + end + + context 'and who belongs to a group with other tickets' do + let(:ticket) { create(:ticket, group: group) } + + before do + create(:ticket_article, sender_name: 'Customer', ticket: ticket, created_at: Time.current + 1.hour) + create(:ticket_article, sender_name: 'Agent', ticket: ticket, created_at: Time.current + 2.hours) + end + + it 'returns a hash with 1-day average ticket wait time across user’s groups (in minutes)' do + expect(Stats::TicketWaitingTime.generate(user)).to include(average_per_agent: 60) + end + end + end + + context 'when given an agent with recent (since start-of-day) customer exchanges' do + let(:ticket) { create(:ticket, group: group, owner_id: user.id) } + + before do + create(:ticket_article, sender_name: 'Customer', ticket: ticket, created_at: Time.current + 1.hour) + create(:ticket_article, sender_name: 'Agent', ticket: ticket, created_at: Time.current + 2.hours) + end + + it 'returns a hash with 1-day average ticket wait time for user (in minutes)' do + expect(Stats::TicketWaitingTime.generate(user)).to include(handling_time: 60) + end + + it 'returns a hash with 1-day average ticket wait time across user’s groups (in minutes)' do + expect(Stats::TicketWaitingTime.generate(user)).to include(average_per_agent: 60) + end + + it 'returns a hash with verbal grade for average ticket wait time' do + expect(Stats::TicketWaitingTime.generate(user)).to include(state: 'supergood') + end + + it 'returns a hash with decimal score (0–1) of user’s risk of falling to a lower grade' do + expect(Stats::TicketWaitingTime.generate(user)).to include(percent: 1.0) + end + + context 'and who belongs to a group with other tickets' do + let(:other_ticket) { create(:ticket, group: group) } + + before do + create(:ticket_article, sender_name: 'Customer', ticket: other_ticket, created_at: Time.current + 1.hour) + create(:ticket_article, sender_name: 'Agent', ticket: other_ticket, created_at: Time.current + 3.hours) + end + + it 'returns a hash with 1-day average ticket wait time across user’s groups (in minutes)' do + expect(Stats::TicketWaitingTime.generate(user)).to include(average_per_agent: 90) + end + end + end + end + + describe '.calculate_average' do + let(:ticket) { create(:ticket) } + let(:start_time) { Time.current.beginning_of_day } + + context 'with empty tickets (no articles)' do + it 'returns 0' do + expect(Stats::TicketWaitingTime.calculate_average(ticket.id, start_time)).to eq(0) + end + end + + context 'with old articles (last message predates given start time)' do + before do + create(:ticket_article, sender_name: 'Customer', ticket: ticket, created_at: 1.day.ago) + create(:ticket_article, sender_name: 'Agent', ticket: ticket, created_at: 1.day.ago) + end + + it 'returns 0' do + expect(Stats::TicketWaitingTime.calculate_average(ticket.id, start_time)).to eq(0) + end + end + + context 'with a single exchange' do + before do + create(:ticket_article, sender_name: 'Customer', ticket: ticket, created_at: start_time + 1.minute) + create(:ticket_article, sender_name: 'Agent', ticket: ticket, created_at: start_time + 2.minutes) + end + + it 'returns elapsed time' do + expect(Stats::TicketWaitingTime.calculate_average(ticket.id, start_time)).to eq(1.minute) + end + end + + context 'with internal notes' do + before do + create(:ticket_article, sender_name: 'Customer', ticket: ticket, created_at: start_time + 1.minute) + create(:ticket_article, sender_name: 'Agent', ticket: ticket, created_at: start_time + 2.minutes, internal: true) + create(:ticket_article, sender_name: 'Agent', ticket: ticket, created_at: start_time + 3.minutes) + end + + it 'ignores them (and measures time to actual response)' do + expect(Stats::TicketWaitingTime.calculate_average(ticket.id, start_time)).to eq(2.minutes) + end + end + + context 'with multiple exchanges' do + before do + create(:ticket_article, sender_name: 'Customer', ticket: ticket, created_at: start_time + 1.minute) + create(:ticket_article, sender_name: 'Agent', ticket: ticket, created_at: start_time + 2.minutes) + create(:ticket_article, sender_name: 'Customer', ticket: ticket, created_at: start_time + 10.minutes) + create(:ticket_article, sender_name: 'Agent', ticket: ticket, created_at: start_time + 15.minutes) + end + + it 'returns average of elapsed times' do + expect(Stats::TicketWaitingTime.calculate_average(ticket.id, start_time)).to eq(3.minutes) + end + end + + context 'with all above cases combined' do + before do + # empty ticket + create(:ticket) + + # old messages + create(:ticket_article, sender_name: 'Customer', ticket: ticket, created_at: 1.day.ago) + create(:ticket_article, sender_name: 'Agent', ticket: ticket, created_at: 1.day.ago) + + # first exchange, with internal notes + create(:ticket_article, sender_name: 'Customer', ticket: ticket, created_at: start_time + 1.minute) + create(:ticket_article, sender_name: 'Agent', ticket: ticket, created_at: start_time + 90.seconds, internal: true) + create(:ticket_article, sender_name: 'Agent', ticket: ticket, created_at: start_time + 2.minutes) + + # second exchange + create(:ticket_article, sender_name: 'Customer', ticket: ticket, created_at: start_time + 10.minutes) + create(:ticket_article, sender_name: 'Agent', ticket: ticket, created_at: start_time + 15.minutes) + end + + it 'ignores all edge cases and returns only specified average response time' do + expect(Stats::TicketWaitingTime.calculate_average(ticket.id, start_time)).to eq(3.minutes) + end + end + end +end diff --git a/test/unit/stats_ticket_waiting_time_test.rb b/test/unit/stats_ticket_waiting_time_test.rb deleted file mode 100644 index 4c3c582ae..000000000 --- a/test/unit/stats_ticket_waiting_time_test.rb +++ /dev/null @@ -1,264 +0,0 @@ -require 'test_helper' -require 'stats/ticket_waiting_time' - -class StatsTicketWaitingTimeTest < ActiveSupport::TestCase - - test 'single ticket' do - - group1 = Group.create!( - name: 'Group 1', - active: true, - email_address: EmailAddress.first, - created_by_id: 1, - updated_by_id: 1, - ) - roles = Role.where(name: 'Agent') - user1 = User.create!( - login: 'assets_stats1@example.org', - firstname: 'assets_stats1', - lastname: 'assets_stats1', - email: 'assets_stats1@example.org', - password: 'some_pass', - active: true, - groups: [group1], - roles: roles, - created_by_id: 1, - updated_by_id: 1, - ) - user2 = User.create!( - login: 'assets_stats2@example.org', - firstname: 'assets_stats2', - lastname: 'assets_stats2', - email: 'assets_sla2@example.org', - password: 'some_pass', - active: true, - groups: [group1], - roles: roles, - created_by_id: 1, - updated_by_id: 1, - ) - - result = Stats::TicketWaitingTime.generate(user1) - assert_equal(0, result[:handling_time]) - assert_equal('supergood', result[:state]) - assert_equal(0, result[:average_per_agent]) - assert_equal(0, result[:percent]) - - ticket1 = Ticket.create!( - title: 'com test 1', - group: group1, - customer_id: 2, - state: Ticket::State.lookup(name: 'new'), - priority: Ticket::Priority.lookup(name: '2 normal'), - updated_by_id: 1, - created_by_id: 1, - ) - - # communication 1: waiting time 2 hours (BUT too old yesterday) - Ticket::Article.create!( - ticket_id: ticket1.id, - from: 'a@example.com', - to: 'a@example.com', - subject: 'com test 1', - message_id: 'some@id_com_1', - body: 'some message 123', - internal: false, - sender: Ticket::Article::Sender.find_by(name: 'Customer'), - type: Ticket::Article::Type.find_by(name: 'email'), - updated_by_id: 1, - created_by_id: 1, - created_at: '2017-04-12 08:00', - updated_at: '2017-04-12 08:00', - ) - Ticket::Article.create!( - ticket_id: ticket1.id, - from: 'a@example.com', - to: 'a@example.com', - subject: 'com test 1', - message_id: 'some@id_com_1', - body: 'some message 123', - internal: false, - sender: Ticket::Article::Sender.find_by(name: 'Agent'), - type: Ticket::Article::Type.find_by(name: 'email'), - updated_by_id: 1, - created_by_id: 1, - created_at: '2017-04-12 10:00', - updated_at: '2017-04-12 10:00', - ) - - # communication 2: waiting time 2 hours - Ticket::Article.create!( - ticket_id: ticket1.id, - from: 'a@example.com', - to: 'a@example.com', - subject: 'com test 1', - message_id: 'some@id_com_1', - body: 'some message 123', - internal: false, - sender: Ticket::Article::Sender.find_by(name: 'Customer'), - type: Ticket::Article::Type.find_by(name: 'email'), - updated_by_id: 1, - created_by_id: 1, - created_at: '2017-04-13 08:00', - updated_at: '2017-04-13 08:00', - ) - Ticket::Article.create!( - ticket_id: ticket1.id, - from: 'a@example.com', - to: 'a@example.com', - subject: 'com test 1', - message_id: 'some@id_com_1', - body: 'some message 123', - internal: false, - sender: Ticket::Article::Sender.find_by(name: 'Agent'), - type: Ticket::Article::Type.find_by(name: 'email'), - updated_by_id: 1, - created_by_id: 1, - created_at: '2017-04-13 10:00', - updated_at: '2017-04-13 10:00', - ) - - # communication 3: waiting time 4 hours - Ticket::Article.create!( - ticket_id: ticket1.id, - from: 'a@example.com', - to: 'a@example.com', - subject: 'com test 1', - message_id: 'some@id_com_1', - body: 'some message 123', - internal: false, - sender: Ticket::Article::Sender.find_by(name: 'Customer'), - type: Ticket::Article::Type.find_by(name: 'email'), - updated_by_id: 1, - created_by_id: 1, - created_at: '2017-04-13 11:00', - updated_at: '2017-04-13 11:00', - ) - Ticket::Article.create!( - ticket_id: ticket1.id, - from: 'a@example.com', - to: 'a@example.com', - subject: 'com test 1', - message_id: 'some@id_com_1', - body: 'some message 123', - internal: false, - sender: Ticket::Article::Sender.find_by(name: 'System'), - type: Ticket::Article::Type.find_by(name: 'email'), - updated_by_id: 1, - created_by_id: 1, - created_at: '2017-04-13 15:00', - updated_at: '2017-04-13 15:00', - ) - - # communication 4: INVALID waiting time 1 hour (because internal) - Ticket::Article.create!( - ticket_id: ticket1.id, - from: 'a@example.com', - to: 'a@example.com', - subject: 'com test 1', - message_id: 'some@id_com_1', - body: 'some message 123', - internal: true, - sender: Ticket::Article::Sender.find_by(name: 'Customer'), - type: Ticket::Article::Type.find_by(name: 'email'), - updated_by_id: 1, - created_by_id: 1, - created_at: '2017-04-13 15:00', - updated_at: '2017-04-13 15:00', - ) - Ticket::Article.create!( - ticket_id: ticket1.id, - from: 'a@example.com', - to: 'a@example.com', - subject: 'com test 1', - message_id: 'some@id_com_1', - body: 'some message 123', - internal: true, - sender: Ticket::Article::Sender.find_by(name: 'System'), - type: Ticket::Article::Type.find_by(name: 'email'), - updated_by_id: 1, - created_by_id: 1, - created_at: '2017-04-13 15:10', - updated_at: '2017-04-13 15:10', - ) - - ticket2 = Ticket.create!( - title: 'com test 2', - group: group1, - customer_id: 2, - state: Ticket::State.lookup(name: 'new'), - priority: Ticket::Priority.lookup(name: '2 normal'), - updated_by_id: 1, - created_by_id: 1, - ) - - average_time = Stats::TicketWaitingTime.calculate_average([ticket1.id, ticket2.id], '2017-04-13 00:00:00') - - expected_average_time = 60 * 60 * 2 # for communication 2 - expected_average_time += 60 * 60 * 4 # for communication 3 - expected_average_time = expected_average_time / 2 # for average - - travel_to Time.zone.local(2017, 0o4, 13, 23, 0o0, 44) - - assert_equal(expected_average_time, average_time) - - result = Stats::TicketWaitingTime.generate(user1) - assert_equal(0, result[:handling_time]) - assert_equal('supergood', result[:state]) - assert_equal(180, result[:average_per_agent]) - assert_equal(0.0, result[:percent]) - - ticket3 = Ticket.create!( - title: 'com test 3', - group: group1, - customer_id: 2, - owner: user1, - state: Ticket::State.lookup(name: 'new'), - priority: Ticket::Priority.lookup(name: '2 normal'), - updated_by_id: 1, - created_by_id: 1, - ) - - # communication 1: waiting time 2 hours (BUT too old yesterday) - Ticket::Article.create!( - ticket_id: ticket3.id, - from: 'a@example.com', - to: 'a@example.com', - subject: 'com test 3', - message_id: 'some@id_com_1', - body: 'some message 123', - internal: false, - sender: Ticket::Article::Sender.find_by(name: 'Customer'), - type: Ticket::Article::Type.find_by(name: 'email'), - updated_by_id: 1, - created_by_id: 1, - created_at: '2017-04-13 08:00', - updated_at: '2017-04-13 08:00', - ) - Ticket::Article.create!( - ticket_id: ticket3.id, - from: 'a@example.com', - to: 'a@example.com', - subject: 'com test 3', - message_id: 'some@id_com_1', - body: 'some message 123', - internal: false, - sender: Ticket::Article::Sender.find_by(name: 'Agent'), - type: Ticket::Article::Type.find_by(name: 'email'), - updated_by_id: 1, - created_by_id: 1, - created_at: '2017-04-13 09:00', - updated_at: '2017-04-13 09:00', - ) - - result = Stats::TicketWaitingTime.generate(user1) - assert_equal(60, result[:handling_time]) - assert_equal('supergood', result[:state]) - assert_equal(140, result[:average_per_agent]) - assert_equal(1.0, result[:percent]) - - travel_back - - end - -end