feat: refactor SLA evaluation logic (#9133)

* feat: update SLA evaluation logic

* chore: handle nrt

* chore: handle applied_sla status

* chore: refactor spec to bring down expecations in a single block

* chore: fix process_account_applied_sla spec

* chore: add spec to test multiple nrt misses

* feat: persist sla notifications

* feat: revert persist sla notifications

* chore: refactor sla_status to include active_with_misses

* chore: refactor spec

* Update evaluate_applied_sla_service.rb

* minor refactors

* clean up

* move notification related spec

* chore: refactor notifications spec to sla_event model

---------

Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
Co-authored-by: Sojan Jose <sojan@pepalo.com>
This commit is contained in:
Vishnu Narayanan
2024-03-29 02:01:43 +11:00
committed by GitHub
parent 9a1c54a82d
commit 6956436a76
7 changed files with 184 additions and 80 deletions

View File

@@ -7,18 +7,20 @@ RSpec.describe Sla::ProcessAccountAppliedSlasJob do
let!(:applied_sla) { create(:applied_sla, account: account, sla_policy: sla_policy, sla_status: 'active') }
let!(:hit_applied_sla) { create(:applied_sla, account: account, sla_policy: sla_policy, sla_status: 'hit') }
let!(:miss_applied_sla) { create(:applied_sla, account: account, sla_policy: sla_policy, sla_status: 'missed') }
let!(:active_with_misses_applied_sla) { create(:applied_sla, account: account, sla_policy: sla_policy, sla_status: 'active_with_misses') }
it 'enqueues the job' do
expect { described_class.perform_later }.to have_enqueued_job(described_class)
.on_queue('medium')
end
it 'calls the ProcessAppliedSlaJob' do
it 'calls the ProcessAppliedSlaJob for both active and active_with_misses' do
expect(Sla::ProcessAppliedSlaJob).to receive(:perform_later).with(active_with_misses_applied_sla).and_call_original
expect(Sla::ProcessAppliedSlaJob).to receive(:perform_later).with(applied_sla).and_call_original
described_class.perform_now(account)
end
it 'does not call the ProcessAppliedSlaJob for not active applied slas' do
it 'does not call the ProcessAppliedSlaJob for applied slas that are hit or miss' do
expect(Sla::ProcessAppliedSlaJob).not_to receive(:perform_later).with(hit_applied_sla)
expect(Sla::ProcessAppliedSlaJob).not_to receive(:perform_later).with(miss_applied_sla)
described_class.perform_now(account)

View File

@@ -25,4 +25,37 @@ RSpec.describe SlaEvent, type: :model do
expect(sla_event.sla_policy_id).to eq sla_event.applied_sla.sla_policy_id
end
end
describe 'create notifications' do
# create account, user and inbox
let!(:account) { create(:account) }
let!(:assignee) { create(:user, account: account) }
let!(:participant) { create(:user, account: account) }
let!(:admin) { create(:user, account: account, role: :administrator) }
let!(:inbox) { create(:inbox, account: account) }
let(:conversation) { create(:conversation, inbox: inbox, assignee: assignee, account: account) }
let(:sla_policy) { create(:sla_policy, account: conversation.account) }
let(:sla_event) { create(:sla_event, event_type: 'frt', conversation: conversation, sla_policy: sla_policy) }
before do
# to ensure notifications are not sent to other users
create(:user, account: account)
create(:inbox_member, inbox: inbox, user: participant)
create(:conversation_participant, conversation: conversation, user: participant)
end
it 'creates notifications for conversation participants, admins, and assignee' do
sla_event
expect(Notification.count).to eq(3)
# check if notification type is sla_missed_first_response
expect(Notification.where(notification_type: 'sla_missed_first_response').count).to eq(3)
# Check if notification is created for the assignee
expect(Notification.where(user_id: assignee.id).count).to eq(1)
# Check if notification is created for the account admin
expect(Notification.where(user_id: admin.id).count).to eq(1)
# Check if notification is created for participant
expect(Notification.where(user_id: participant.id).count).to eq(1)
end
end
end

View File

@@ -3,8 +3,6 @@ require 'rails_helper'
RSpec.describe Sla::EvaluateAppliedSlaService do
let!(:account) { create(:account) }
let!(:user_1) { create(:user, account: account) }
let!(:user_2) { create(:user, account: account) }
let!(:admin) { create(:user, account: account, role: :administrator) }
let!(:sla_policy) do
create(:sla_policy,
@@ -28,19 +26,17 @@ RSpec.describe Sla::EvaluateAppliedSlaService do
it 'updates the SLA status to missed and logs a warning' do
allow(Rails.logger).to receive(:warn)
described_class.new(applied_sla: applied_sla).perform
expect(Rails.logger).to have_received(:warn).with("SLA missed for conversation #{conversation.id} in account " \
expect(Rails.logger).to have_received(:warn).with("SLA frt missed for conversation #{conversation.id} in account " \
"#{applied_sla.account_id} for sla_policy #{sla_policy.id}")
expect(applied_sla.reload.sla_status).to eq('missed')
expect(applied_sla.reload.sla_status).to eq('active_with_misses')
end
expect(Notification.count).to eq(2)
# check if notification type is sla_missed_first_response
expect(Notification.where(notification_type: 'sla_missed_first_response').count).to eq(2)
# Check if notification is created for the assignee
expect(Notification.where(user_id: user_1.id).count).to eq(1)
# Check if notification is created for the account admin
expect(Notification.where(user_id: admin.id).count).to eq(1)
# Check if no notification is created for other user
expect(Notification.where(user_id: user_2.id).count).to eq(0)
it 'creates SlaEvent only for frt miss' do
described_class.new(applied_sla: applied_sla).perform
expect(SlaEvent.where(applied_sla: applied_sla, event_type: 'frt').count).to eq(1)
expect(SlaEvent.where(applied_sla: applied_sla, event_type: 'nrt').count).to eq(0)
expect(SlaEvent.where(applied_sla: applied_sla, event_type: 'rt').count).to eq(0)
end
end
@@ -53,19 +49,17 @@ RSpec.describe Sla::EvaluateAppliedSlaService do
it 'updates the SLA status to missed and logs a warning' do
allow(Rails.logger).to receive(:warn)
described_class.new(applied_sla: applied_sla).perform
expect(Rails.logger).to have_received(:warn).with("SLA missed for conversation #{conversation.id} in account " \
expect(Rails.logger).to have_received(:warn).with("SLA nrt missed for conversation #{conversation.id} in account " \
"#{applied_sla.account_id} for sla_policy #{sla_policy.id}")
expect(applied_sla.reload.sla_status).to eq('missed')
expect(applied_sla.reload.sla_status).to eq('active_with_misses')
end
expect(Notification.count).to eq(2)
# check if notification type is sla_missed_first_response
expect(Notification.where(notification_type: 'sla_missed_next_response').count).to eq(2)
# Check if notification is created for the assignee
expect(Notification.where(user_id: user_1.id).count).to eq(1)
# Check if notification is created for the account admin
expect(Notification.where(user_id: admin.id).count).to eq(1)
# Check if no notification is created for other user
expect(Notification.where(user_id: user_2.id).count).to eq(0)
it 'creates SlaEvent only for nrt miss' do
described_class.new(applied_sla: applied_sla).perform
expect(SlaEvent.where(applied_sla: applied_sla, event_type: 'frt').count).to eq(0)
expect(SlaEvent.where(applied_sla: applied_sla, event_type: 'nrt').count).to eq(1)
expect(SlaEvent.where(applied_sla: applied_sla, event_type: 'rt').count).to eq(0)
end
end
@@ -75,18 +69,18 @@ RSpec.describe Sla::EvaluateAppliedSlaService do
it 'updates the SLA status to missed and logs a warning' do
allow(Rails.logger).to receive(:warn)
described_class.new(applied_sla: applied_sla).perform
expect(Rails.logger).to have_received(:warn).with("SLA missed for conversation #{conversation.id} in account " \
expect(Rails.logger).to have_received(:warn).with("SLA rt missed for conversation #{conversation.id} in account " \
"#{applied_sla.account_id} for sla_policy #{sla_policy.id}")
expect(applied_sla.reload.sla_status).to eq('missed')
expect(Notification.count).to eq(2)
expect(Notification.where(notification_type: 'sla_missed_resolution').count).to eq(2)
# Check if notification is created for the assignee
expect(Notification.where(user_id: user_1.id).count).to eq(1)
# Check if notification is created for the account admin
expect(Notification.where(user_id: admin.id).count).to eq(1)
# Check if no notification is created for other user
expect(Notification.where(user_id: user_2.id).count).to eq(0)
expect(applied_sla.reload.sla_status).to eq('active_with_misses')
end
it 'creates SlaEvent only for rt miss' do
described_class.new(applied_sla: applied_sla).perform
expect(SlaEvent.where(applied_sla: applied_sla, event_type: 'frt').count).to eq(0)
expect(SlaEvent.where(applied_sla: applied_sla, event_type: 'nrt').count).to eq(0)
expect(SlaEvent.where(applied_sla: applied_sla, event_type: 'rt').count).to eq(1)
end
end
@@ -110,13 +104,14 @@ RSpec.describe Sla::EvaluateAppliedSlaService do
conversation.update(first_reply_created_at: 5.hours.ago, waiting_since: 5.hours.ago)
end
it 'updates the SLA status to missed and logs a warning' do
it 'updates the SLA status to missed and logs multiple warnings' do
allow(Rails.logger).to receive(:warn)
described_class.new(applied_sla: applied_sla).perform
expect(Rails.logger).to have_received(:warn).with("SLA missed for conversation #{conversation.id} in account " \
expect(Rails.logger).to have_received(:warn).with("SLA rt missed for conversation #{conversation.id} in account " \
"#{applied_sla.account_id} for sla_policy #{sla_policy.id}").exactly(1).time
expect(applied_sla.reload.sla_status).to eq('missed')
expect(Notification.count).to eq(2)
expect(Rails.logger).to have_received(:warn).with("SLA nrt missed for conversation #{conversation.id} in account " \
"#{applied_sla.account_id} for sla_policy #{sla_policy.id}").exactly(1).time
expect(applied_sla.reload.sla_status).to eq('active_with_misses')
end
end
end
@@ -140,6 +135,7 @@ RSpec.describe Sla::EvaluateAppliedSlaService do
expect(Rails.logger).to have_received(:info).with("SLA hit for conversation #{conversation.id} in account " \
"#{applied_sla.account_id} for sla_policy #{sla_policy.id}")
expect(applied_sla.reload.sla_status).to eq('hit')
expect(SlaEvent.count).to eq(0)
expect(Notification.count).to eq(0)
end
end
@@ -162,6 +158,7 @@ RSpec.describe Sla::EvaluateAppliedSlaService do
expect(Rails.logger).to have_received(:info).with("SLA hit for conversation #{conversation.id} in account " \
"#{applied_sla.account_id} for sla_policy #{sla_policy.id}")
expect(applied_sla.reload.sla_status).to eq('hit')
expect(SlaEvent.count).to eq(0)
end
end
@@ -177,7 +174,52 @@ RSpec.describe Sla::EvaluateAppliedSlaService do
expect(Rails.logger).to have_received(:info).with("SLA hit for conversation #{conversation.id} in account " \
"#{applied_sla.account_id} for sla_policy #{sla_policy.id}")
expect(applied_sla.reload.sla_status).to eq('hit')
expect(SlaEvent.count).to eq(0)
end
end
end
describe 'SLA evaluation with frt hit, multiple nrt misses and rt miss' do
before do
# Setup SLA Policy thresholds
sla_policy.update(
first_response_time_threshold: 2.hours, # Hit frt
next_response_time_threshold: 1.hour, # Miss nrt multiple times
resolution_time_threshold: 4.hours # Miss rt
)
# Simulate conversation timeline
# Hit frt
# incoming message from customer
create(:message, conversation: conversation, created_at: 6.hours.ago, message_type: :incoming)
# outgoing message from agent within frt
create(:message, conversation: conversation, created_at: 5.hours.ago, message_type: :outgoing)
# Miss nrt first time
create(:message, conversation: conversation, created_at: 4.hours.ago, message_type: :incoming)
described_class.new(applied_sla: applied_sla).perform
# Miss nrt second time
create(:message, conversation: conversation, created_at: 3.hours.ago, message_type: :incoming)
described_class.new(applied_sla: applied_sla).perform
# Conversation is resolved missing rt
conversation.update(status: 'resolved')
# this will not create a new notification for rt miss as conversation is resolved
# but we would have already created an rt miss notification during previous evaluation
described_class.new(applied_sla: applied_sla).perform
end
it 'updates the SLA status to missed' do
# the status would be missed as the conversation is resolved
expect(applied_sla.reload.sla_status).to eq('missed')
end
it 'creates necessary sla events' do
expect(SlaEvent.where(applied_sla: applied_sla, event_type: 'frt').count).to eq(0)
expect(SlaEvent.where(applied_sla: applied_sla, event_type: 'nrt').count).to eq(2)
expect(SlaEvent.where(applied_sla: applied_sla, event_type: 'rt').count).to eq(1)
end
end
end