feat: Add sidekiq jobs to monitor applied SLAs (#8828)
Fixes: https://linear.app/chatwoot/issue/CW-2983/sidekiq-jobservice-to-monitor-sla-breach Co-authored-by: Sojan <sojan@pepalo.com>
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe TriggerScheduledItemsJob do
|
||||
subject(:job) { described_class.perform_later }
|
||||
|
||||
it 'triggers Sla::TriggerSlasForAccountsJob' do
|
||||
expect(Sla::TriggerSlasForAccountsJob).to receive(:perform_later).once
|
||||
described_class.perform_now
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,27 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Sla::ProcessAccountAppliedSlasJob do
|
||||
context 'when perform is called' do
|
||||
let!(:account) { create(:account) }
|
||||
let!(:sla_policy) { create(:sla_policy, first_response_time_threshold: 1.hour) }
|
||||
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') }
|
||||
|
||||
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
|
||||
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
|
||||
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)
|
||||
end
|
||||
end
|
||||
end
|
||||
18
spec/enterprise/jobs/sla/process_applied_sla_job_spec.rb
Normal file
18
spec/enterprise/jobs/sla/process_applied_sla_job_spec.rb
Normal file
@@ -0,0 +1,18 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Sla::ProcessAppliedSlaJob do
|
||||
context 'when perform is called' do
|
||||
let(:account) { create(:account) }
|
||||
|
||||
it 'enqueues the job' do
|
||||
expect { described_class.perform_later }.to have_enqueued_job(described_class)
|
||||
.on_queue('medium')
|
||||
end
|
||||
|
||||
it 'calls the EvaluateAppliedSlaService' do
|
||||
applied_sla = create(:applied_sla)
|
||||
expect(Sla::EvaluateAppliedSlaService).to receive(:new).with(applied_sla: applied_sla).and_call_original
|
||||
described_class.perform_now(applied_sla)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,17 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Sla::TriggerSlasForAccountsJob do
|
||||
context 'when perform is called' do
|
||||
let(:account) { create(:account) }
|
||||
|
||||
it 'enqueues the job' do
|
||||
expect { described_class.perform_later }.to have_enqueued_job(described_class)
|
||||
.on_queue('scheduled_jobs')
|
||||
end
|
||||
|
||||
it 'calls the ProcessAccountAppliedSlasJob' do
|
||||
expect(Sla::ProcessAccountAppliedSlasJob).to receive(:perform_later).with(account).and_call_original
|
||||
described_class.perform_now
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,141 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Sla::EvaluateAppliedSlaService do
|
||||
let!(:conversation) { create(:conversation, created_at: 6.hours.ago) }
|
||||
let!(:sla_policy) do
|
||||
create(:sla_policy, account: conversation.account,
|
||||
first_response_time_threshold: nil,
|
||||
next_response_time_threshold: nil,
|
||||
resolution_time_threshold: nil)
|
||||
end
|
||||
let!(:applied_sla) { create(:applied_sla, conversation: conversation, sla_policy: sla_policy, sla_status: 'active') }
|
||||
|
||||
describe '#perform - SLA misses' do
|
||||
context 'when first response SLA is missed' do
|
||||
before { sla_policy.update(first_response_time_threshold: 1.hour) }
|
||||
|
||||
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 " \
|
||||
"#{applied_sla.account_id} for sla_policy #{sla_policy.id}")
|
||||
expect(applied_sla.reload.sla_status).to eq('missed')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when next response SLA is missed' do
|
||||
before do
|
||||
sla_policy.update(next_response_time_threshold: 1.hour)
|
||||
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
|
||||
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 " \
|
||||
"#{applied_sla.account_id} for sla_policy #{sla_policy.id}")
|
||||
expect(applied_sla.reload.sla_status).to eq('missed')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when resolution time SLA is missed' do
|
||||
before { sla_policy.update(resolution_time_threshold: 1.hour) }
|
||||
|
||||
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 " \
|
||||
"#{applied_sla.account_id} for sla_policy #{sla_policy.id}")
|
||||
expect(applied_sla.reload.sla_status).to eq('missed')
|
||||
end
|
||||
end
|
||||
|
||||
# We will mark resolved miss only if while processing the SLA
|
||||
# if the conversation is resolved and the resolution time is missed by small margins then we will not mark it as missed
|
||||
context 'when resolved conversation with resolution time SLA is missed' do
|
||||
before do
|
||||
conversation.resolved!
|
||||
sla_policy.update(resolution_time_threshold: 1.hour)
|
||||
end
|
||||
|
||||
it 'does not update the SLA status to missed' do
|
||||
described_class.new(applied_sla: applied_sla).perform
|
||||
expect(applied_sla.reload.sla_status).to eq('hit')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when multiple SLAs are missed' do
|
||||
before do
|
||||
sla_policy.update(first_response_time_threshold: 1.hour, next_response_time_threshold: 1.hour, resolution_time_threshold: 1.hour)
|
||||
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
|
||||
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 " \
|
||||
"#{applied_sla.account_id} for sla_policy #{sla_policy.id}").exactly(1).time
|
||||
expect(applied_sla.reload.sla_status).to eq('missed')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#perform - SLA hits' do
|
||||
context 'when first response SLA is hit' do
|
||||
before do
|
||||
sla_policy.update(first_response_time_threshold: 6.hours)
|
||||
conversation.update(first_reply_created_at: 30.minutes.ago)
|
||||
end
|
||||
|
||||
it 'sla remains active until conversation is resolved' do
|
||||
described_class.new(applied_sla: applied_sla).perform
|
||||
expect(applied_sla.reload.sla_status).to eq('active')
|
||||
end
|
||||
|
||||
it 'updates the SLA status to hit and logs an info when conversations is resolved' do
|
||||
conversation.resolved!
|
||||
allow(Rails.logger).to receive(:info)
|
||||
described_class.new(applied_sla: applied_sla).perform
|
||||
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')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when next response SLA is hit' do
|
||||
before do
|
||||
sla_policy.update(next_response_time_threshold: 6.hours)
|
||||
conversation.update(first_reply_created_at: 30.minutes.ago, waiting_since: nil)
|
||||
end
|
||||
|
||||
it 'sla remains active until conversation is resolved' do
|
||||
described_class.new(applied_sla: applied_sla).perform
|
||||
expect(applied_sla.reload.sla_status).to eq('active')
|
||||
end
|
||||
|
||||
it 'updates the SLA status to hit and logs an info when conversations is resolved' do
|
||||
conversation.resolved!
|
||||
allow(Rails.logger).to receive(:info)
|
||||
described_class.new(applied_sla: applied_sla).perform
|
||||
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')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when resolution time SLA is hit' do
|
||||
before do
|
||||
sla_policy.update(resolution_time_threshold: 8.hours)
|
||||
conversation.resolved!
|
||||
end
|
||||
|
||||
it 'updates the SLA status to hit and logs an info' do
|
||||
allow(Rails.logger).to receive(:info)
|
||||
described_class.new(applied_sla: applied_sla).perform
|
||||
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')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,8 +1,11 @@
|
||||
FactoryBot.define do
|
||||
factory :applied_sla do
|
||||
account
|
||||
sla_policy
|
||||
conversation
|
||||
sla_status { 'active' }
|
||||
|
||||
after(:build) do |applied_sla|
|
||||
applied_sla.account ||= applied_sla.conversation&.account || create(:account)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -10,6 +10,31 @@ RSpec.describe TriggerScheduledItemsJob do
|
||||
.on_queue('scheduled_jobs')
|
||||
end
|
||||
|
||||
it 'triggers Conversations::ReopenSnoozedConversationsJob' do
|
||||
expect(Conversations::ReopenSnoozedConversationsJob).to receive(:perform_later).once
|
||||
described_class.perform_now
|
||||
end
|
||||
|
||||
it 'triggers Notification::ReopenSnoozedNotificationsJob' do
|
||||
expect(Notification::ReopenSnoozedNotificationsJob).to receive(:perform_later).once
|
||||
described_class.perform_now
|
||||
end
|
||||
|
||||
it 'triggers Account::ConversationsResolutionSchedulerJob' do
|
||||
expect(Account::ConversationsResolutionSchedulerJob).to receive(:perform_later).once
|
||||
described_class.perform_now
|
||||
end
|
||||
|
||||
it 'triggers Channels::Whatsapp::TemplatesSyncSchedulerJob' do
|
||||
expect(Channels::Whatsapp::TemplatesSyncSchedulerJob).to receive(:perform_later).once
|
||||
described_class.perform_now
|
||||
end
|
||||
|
||||
it 'triggers Notification::RemoveOldNotificationJob' do
|
||||
expect(Notification::RemoveOldNotificationJob).to receive(:perform_later).once
|
||||
described_class.perform_now
|
||||
end
|
||||
|
||||
context 'when unexecuted Scheduled campaign jobs' do
|
||||
let!(:twilio_sms) { create(:channel_twilio_sms) }
|
||||
let!(:twilio_inbox) { create(:inbox, channel: twilio_sms) }
|
||||
@@ -20,30 +45,5 @@ RSpec.describe TriggerScheduledItemsJob do
|
||||
expect(Campaigns::TriggerOneoffCampaignJob).to receive(:perform_later).with(campaign).once
|
||||
described_class.perform_now
|
||||
end
|
||||
|
||||
it 'triggers Conversations::ReopenSnoozedConversationsJob' do
|
||||
expect(Conversations::ReopenSnoozedConversationsJob).to receive(:perform_later).once
|
||||
described_class.perform_now
|
||||
end
|
||||
|
||||
it 'triggers Notification::ReopenSnoozedNotificationsJob' do
|
||||
expect(Notification::ReopenSnoozedNotificationsJob).to receive(:perform_later).once
|
||||
described_class.perform_now
|
||||
end
|
||||
|
||||
it 'triggers Account::ConversationsResolutionSchedulerJob' do
|
||||
expect(Account::ConversationsResolutionSchedulerJob).to receive(:perform_later).once
|
||||
described_class.perform_now
|
||||
end
|
||||
|
||||
it 'triggers Channels::Whatsapp::TemplatesSyncSchedulerJob' do
|
||||
expect(Channels::Whatsapp::TemplatesSyncSchedulerJob).to receive(:perform_later).once
|
||||
described_class.perform_now
|
||||
end
|
||||
|
||||
it 'triggers Notification::RemoveOldNotificationJob' do
|
||||
expect(Notification::RemoveOldNotificationJob).to receive(:perform_later).once
|
||||
described_class.perform_now
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user