# Pull Request Template ## Description Blocked contacts are now excluded from SLA assignment, processing, reports, and conversation SLA UI while they remain blocked. Existing SLA records are preserved, and SLA behavior resumes if the contact is unblocked. Fixes https://linear.app/chatwoot/issue/CW-7435/sla-should-not-trigger-for-blocked-contacts ## Type of change - [x] Bug fix (non-breaking change which fixes an issue) ## How Has This Been Tested? - `bundle exec rspec spec/enterprise/models/conversation_spec.rb spec/enterprise/models/applied_sla_spec.rb spec/enterprise/services/enterprise/action_service_spec.rb spec/enterprise/services/sla/evaluate_applied_sla_service_spec.rb spec/enterprise/jobs/sla/process_account_applied_slas_job_spec.rb spec/enterprise/controllers/api/v1/accounts/applied_slas_controller_spec.rb spec/enterprise/controllers/api/v1/accounts/conversations_controller_spec.rb spec/enterprise/controllers/enterprise/api/v1/accounts/conversations_controller_spec.rb spec/enterprise/presenters/conversations/event_data_presenter_spec.rb` — 78 examples, 0 failures - `bundle exec rubocop enterprise/app/controllers/api/v1/accounts/applied_slas_controller.rb enterprise/app/jobs/sla/process_account_applied_slas_job.rb enterprise/app/models/applied_sla.rb enterprise/app/models/enterprise/concerns/conversation.rb enterprise/app/presenters/enterprise/conversations/event_data_presenter.rb enterprise/app/services/enterprise/action_service.rb enterprise/app/services/sla/evaluate_applied_sla_service.rb lib/tasks/apply_sla.rake spec/enterprise/controllers/api/v1/accounts/applied_slas_controller_spec.rb spec/enterprise/controllers/api/v1/accounts/conversations_controller_spec.rb spec/enterprise/controllers/enterprise/api/v1/accounts/conversations_controller_spec.rb spec/enterprise/jobs/sla/process_account_applied_slas_job_spec.rb spec/enterprise/models/applied_sla_spec.rb spec/enterprise/models/conversation_spec.rb spec/enterprise/presenters/conversations/event_data_presenter_spec.rb spec/enterprise/services/enterprise/action_service_spec.rb spec/enterprise/services/sla/evaluate_applied_sla_service_spec.rb` — no offenses - `pnpm exec vitest --no-watch --no-cache --no-coverage app/javascript/dashboard/components/widgets/conversation/specs/ConversationCard.spec.js` — 2 tests passed - `pnpm exec eslint app/javascript/dashboard/components-next/Conversation/ConversationCard/CardMessagePreviewWithMeta.vue app/javascript/dashboard/components-next/Conversation/ConversationCard/ConversationCardExpanded.vue app/javascript/dashboard/components/widgets/conversation/ConversationCard.vue app/javascript/dashboard/components/widgets/conversation/ConversationHeader.vue app/javascript/dashboard/components/widgets/conversation/specs/ConversationCard.spec.js` — passed with existing raw-text warnings in `ConversationHeader.vue` - `git diff --cached --check` — clean ## Checklist: - [x] My code follows the style guidelines of this project - [x] I have performed a self-review of my code - [ ] I have commented on my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [x] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes - [ ] Any dependent changes have been merged and published in downstream modules --------- Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com> Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
138 lines
5.5 KiB
Ruby
138 lines
5.5 KiB
Ruby
require 'rails_helper'
|
|
|
|
RSpec.describe Conversation, type: :model do
|
|
describe 'associations' do
|
|
it { is_expected.to belong_to(:sla_policy).optional }
|
|
end
|
|
|
|
describe 'SLA policy updates' do
|
|
let(:conversation) { create(:conversation) }
|
|
let!(:sla_policy) { create(:sla_policy, account: conversation.account) }
|
|
|
|
before do
|
|
stub_request(:get, %r{\Ahttps://www\.gravatar\.com.*}).to_return(status: 404)
|
|
stub_request(:get, %r{\Ahttps://www\.google\.com/s2/favicons.*}).to_return(status: 404)
|
|
end
|
|
|
|
it 'generates an activity message when the SLA policy is updated' do
|
|
conversation.update!(sla_policy_id: sla_policy.id)
|
|
|
|
perform_enqueued_jobs
|
|
|
|
activity_message = conversation.messages.where(message_type: 'activity').last
|
|
|
|
expect(activity_message).not_to be_nil
|
|
expect(activity_message.message_type).to eq('activity')
|
|
expect(activity_message.content).to include('added SLA policy')
|
|
end
|
|
|
|
# TODO: Reenable this when we let the SLA policy be removed from a conversation
|
|
# it 'generates an activity message when the SLA policy is removed' do
|
|
# conversation.update!(sla_policy_id: sla_policy.id)
|
|
# conversation.update!(sla_policy_id: nil)
|
|
|
|
# perform_enqueued_jobs
|
|
|
|
# activity_message = conversation.messages.where(message_type: 'activity').last
|
|
|
|
# expect(activity_message).not_to be_nil
|
|
# expect(activity_message.message_type).to eq('activity')
|
|
# expect(activity_message.content).to include('removed SLA policy')
|
|
# end
|
|
end
|
|
|
|
describe 'sla_policy' do
|
|
let(:account) { create(:account) }
|
|
let(:conversation) { create(:conversation, account: account) }
|
|
let(:sla_policy) { create(:sla_policy, account: account) }
|
|
let(:different_account_sla_policy) { create(:sla_policy) }
|
|
|
|
context 'when sla_policy is getting updated' do
|
|
it 'throws error if sla policy belongs to different account' do
|
|
conversation.sla_policy = different_account_sla_policy
|
|
expect(conversation.valid?).to be false
|
|
expect(conversation.errors[:sla_policy]).to include('sla policy account mismatch')
|
|
end
|
|
|
|
it 'creates applied sla record if sla policy is present' do
|
|
conversation.sla_policy = sla_policy
|
|
conversation.save!
|
|
expect(conversation.applied_sla.sla_policy_id).to eq(sla_policy.id)
|
|
end
|
|
|
|
it 'throws error if contact is blocked' do
|
|
conversation.contact.update!(blocked: true)
|
|
conversation.sla_policy = sla_policy
|
|
|
|
expect(conversation.valid?).to be false
|
|
expect(conversation.errors[:sla_policy]).to eq(['cannot be assigned to conversations with blocked contacts'])
|
|
end
|
|
|
|
it 'allows assigning sla after contact is unblocked' do
|
|
conversation.contact.update!(blocked: true)
|
|
conversation.contact.update!(blocked: false)
|
|
conversation.sla_policy = sla_policy
|
|
|
|
conversation.save!
|
|
|
|
expect(conversation.applied_sla.sla_policy_id).to eq(sla_policy.id)
|
|
end
|
|
|
|
it 'keeps existing behavior when contact is missing' do
|
|
conversation.update_columns(contact_id: nil, contact_inbox_id: nil) # rubocop:disable Rails/SkipsModelValidations
|
|
|
|
expect(conversation.reload.sla_applicable?).to be true
|
|
end
|
|
end
|
|
|
|
context 'when conversation already has a different sla' do
|
|
before do
|
|
conversation.update(sla_policy: create(:sla_policy, account: account))
|
|
end
|
|
|
|
it 'throws error if trying to assing a different sla' do
|
|
conversation.sla_policy = sla_policy
|
|
expect(conversation.valid?).to be false
|
|
expect(conversation.errors[:sla_policy]).to eq(['conversation already has a different sla'])
|
|
end
|
|
|
|
it 'throws error if trying to set sla to nil' do
|
|
conversation.sla_policy = nil
|
|
expect(conversation.valid?).to be false
|
|
expect(conversation.errors[:sla_policy]).to eq(['cannot remove sla policy from conversation'])
|
|
end
|
|
end
|
|
end
|
|
|
|
describe 'assignment capacity limits' do
|
|
describe 'team assignment with inbox auto-assignment disabled' do
|
|
let(:account) { create(:account) }
|
|
let(:inbox) { create(:inbox, account: account, enable_auto_assignment: false, auto_assignment_config: { max_assignment_limit: 1 }) }
|
|
let(:team) { create(:team, account: account, allow_auto_assign: true) }
|
|
let!(:agent1) { create(:user, account: account, role: :agent, auto_offline: false) }
|
|
let!(:agent2) { create(:user, account: account, role: :agent, auto_offline: false) }
|
|
|
|
before do
|
|
create(:inbox_member, inbox: inbox, user: agent1)
|
|
create(:inbox_member, inbox: inbox, user: agent2)
|
|
create(:team_member, team: team, user: agent1)
|
|
create(:team_member, team: team, user: agent2)
|
|
# Both agents are over the limit (simulate by assigning open conversations)
|
|
create_list(:conversation, 2, inbox: inbox, assignee: agent1, status: :open)
|
|
create_list(:conversation, 2, inbox: inbox, assignee: agent2, status: :open)
|
|
end
|
|
|
|
it 'does not enforce max_assignment_limit for team assignment when inbox auto-assignment is disabled' do
|
|
conversation = create(:conversation, inbox: inbox, account: account, assignee: nil, status: :open)
|
|
|
|
# Assign to team to trigger the assignment logic
|
|
conversation.update!(team: team)
|
|
|
|
# Should assign to a team member even if they are over the limit
|
|
expect(conversation.reload.assignee).to be_present
|
|
expect([agent1, agent2]).to include(conversation.reload.assignee)
|
|
end
|
|
end
|
|
end
|
|
end
|