feat: Enable Capacity Config UI (#5164)

- Enables Capacity Config in UI
- Rewrite auto assignment Logic to consider only online agents

fixes: #4990
This commit is contained in:
Sojan Jose
2022-08-16 16:58:23 +05:30
committed by GitHub
parent 287f0a6de0
commit 2ecb2ca0f0
20 changed files with 225 additions and 216 deletions

View File

@@ -35,7 +35,7 @@ RSpec.describe 'Conversation Assignment API', type: :request do
end
it 'assigns a team to the conversation' do
team_member = create(:user, account: account, role: :agent)
team_member = create(:user, account: account, role: :agent, auto_offline: false)
create(:inbox_member, inbox: conversation.inbox, user: team_member)
create(:team_member, team: team, user: team_member)
params = { team_id: team.id }

View File

@@ -220,7 +220,7 @@ RSpec.describe 'Conversations API', type: :request do
end
context 'when it is an authenticated user' do
let(:agent) { create(:user, account: account, role: :agent) }
let(:agent) { create(:user, account: account, role: :agent, auto_offline: false) }
let(:team) { create(:team, account: account) }
it 'will not create a new conversation if agent does not have access to inbox' do

View File

@@ -5,6 +5,7 @@ FactoryBot.define do
transient do
skip_confirmation { true }
role { 'agent' }
auto_offline { true }
account { nil }
inviter { nil }
end
@@ -18,7 +19,10 @@ FactoryBot.define do
after(:build) do |user, evaluator|
user.skip_confirmation! if evaluator.skip_confirmation
create(:account_user, user: user, account: evaluator.account, role: evaluator.role, inviter: evaluator.inviter) if evaluator.account
if evaluator.account
create(:account_user, user: user, account: evaluator.account, role: evaluator.role, inviter: evaluator.inviter,
auto_offline: evaluator.auto_offline)
end
end
trait :with_avatar do

View File

@@ -6,7 +6,7 @@ shared_examples_for 'assignment_handler' do
describe '#update_team' do
let(:conversation) { create(:conversation, assignee: create(:user)) }
let(:agent) do
create(:user, email: 'agent@example.com', account: conversation.account, role: :agent)
create(:user, email: 'agent@example.com', account: conversation.account, role: :agent, auto_offline: false)
end
let(:team) do
create(:team, account: conversation.account, allow_auto_assign: false)

View File

@@ -2,10 +2,10 @@
require 'rails_helper'
shared_examples_for 'round_robin_handler' do
describe '#round robin' do
shared_examples_for 'auto_assignment_handler' do
describe '#auto assignment' do
let(:account) { create(:account) }
let(:agent) { create(:user, email: 'agent1@example.com', account: account) }
let(:agent) { create(:user, email: 'agent1@example.com', account: account, auto_offline: false) }
let(:inbox) { create(:inbox, account: account) }
let(:conversation) do
create(
@@ -23,14 +23,12 @@ shared_examples_for 'round_robin_handler' do
end
it 'runs round robin on after_save callbacks' do
# run_round_robin
expect(conversation.reload.assignee).to eq(agent)
end
it 'will not auto assign agent if enable_auto_assignment is false' do
inbox.update(enable_auto_assignment: false)
# run_round_robin
expect(conversation.reload.assignee).to be_nil
end
@@ -44,7 +42,6 @@ shared_examples_for 'round_robin_handler' do
assignee: nil
)
# run_round_robin
expect(conversation.reload.assignee).to be_nil
end
@@ -55,7 +52,7 @@ shared_examples_for 'round_robin_handler' do
inbox.inbox_members.where(user_id: agent.id).first.destroy!
# round robin changes assignee in this case since agent doesn't have access to inbox
agent2 = create(:user, email: 'agent2@example.com', account: account)
agent2 = create(:user, email: 'agent2@example.com', account: account, auto_offline: false)
create(:inbox_member, inbox: inbox, user: agent2)
allow(Redis::Alfred).to receive(:rpoplpush).and_return(agent2.id)
conversation.status = 'open'

View File

@@ -2,7 +2,7 @@
require 'rails_helper'
require Rails.root.join 'spec/models/concerns/assignment_handler_shared.rb'
require Rails.root.join 'spec/models/concerns/round_robin_handler_shared.rb'
require Rails.root.join 'spec/models/concerns/auto_assignment_handler_shared.rb'
RSpec.describe Conversation, type: :model do
describe 'associations' do
@@ -12,7 +12,7 @@ RSpec.describe Conversation, type: :model do
describe 'concerns' do
it_behaves_like 'assignment_handler'
it_behaves_like 'round_robin_handler'
it_behaves_like 'auto_assignment_handler'
end
describe '.before_create' do

View File

@@ -0,0 +1,38 @@
require 'rails_helper'
RSpec.describe AutoAssignment::AgentAssignmentService do
let!(:account) { create(:account) }
let!(:inbox) { create(:inbox, account: account, enable_auto_assignment: false) }
let!(:inbox_members) { create_list(:inbox_member, 5, inbox: inbox) }
let!(:conversation) { create(:conversation, inbox: inbox, account: account) }
let!(:online_users) do
{
inbox_members[0].user_id.to_s => 'busy',
inbox_members[1].user_id.to_s => 'busy',
inbox_members[2].user_id.to_s => 'busy',
inbox_members[3].user_id.to_s => 'online',
inbox_members[4].user_id.to_s => 'online'
}
end
before do
allow(::OnlineStatusTracker).to receive(:get_available_users).and_return(online_users)
end
describe '#perform' do
it 'will assign an online agent to the conversation' do
expect(conversation.reload.assignee).to be_nil
described_class.new(conversation: conversation, allowed_agent_ids: inbox_members.map(&:user_id).map(&:to_s)).perform
expect(conversation.reload.assignee).not_to be_nil
end
end
describe '#find_assignee' do
it 'will return an online agent from the allowed agent ids in roud robin' do
expect(described_class.new(conversation: conversation,
allowed_agent_ids: inbox_members.map(&:user_id).map(&:to_s)).find_assignee).to eq(inbox_members[3].user)
expect(described_class.new(conversation: conversation,
allowed_agent_ids: inbox_members.map(&:user_id).map(&:to_s)).find_assignee).to eq(inbox_members[4].user)
end
end
end

View File

@@ -0,0 +1,59 @@
require 'rails_helper'
describe AutoAssignment::InboxRoundRobinService do
subject(:inbox_round_robin_service) { described_class.new(inbox: inbox) }
let!(:account) { create(:account) }
let!(:inbox) { create(:inbox, account: account) }
let!(:inbox_members) { create_list(:inbox_member, 5, inbox: inbox) }
describe '#available_agent' do
it 'returns nil if allowed_agent_ids is not passed or empty' do
expect(described_class.new(inbox: inbox).available_agent).to be_nil
end
it 'gets the first available agent in allowed_agent_ids and move agent to end of the list' do
expected_queue = [inbox_members[0].user_id, inbox_members[4].user_id, inbox_members[3].user_id, inbox_members[2].user_id,
inbox_members[1].user_id].map(&:to_s)
described_class.new(inbox: inbox).available_agent(allowed_agent_ids: [inbox_members[0].user_id, inbox_members[4].user_id].map(&:to_s))
expect(inbox_round_robin_service.send(:queue)).to eq(expected_queue)
end
it 'constructs round_robin_queue if queue is not present' do
inbox_round_robin_service.clear_queue
expect(inbox_round_robin_service.send(:queue)).to eq([])
inbox_round_robin_service.available_agent
# the service constructed the redis queue before performing
expect(inbox_round_robin_service.send(:queue).sort.map(&:to_i)).to eq(inbox_members.map(&:user_id).sort)
end
it 'validates the queue and correct it before performing round robin' do
# adding some invalid ids to queue
inbox_round_robin_service.add_agent_to_queue([2, 3, 5, 9])
expect(inbox_round_robin_service.send(:queue).sort.map(&:to_i)).not_to eq(inbox_members.map(&:user_id).sort)
inbox_round_robin_service.available_agent
# the service have refreshed the redis queue before performing
expect(inbox_round_robin_service.send(:queue).sort.map(&:to_i)).to eq(inbox_members.map(&:user_id).sort)
end
context 'when allowed_agent_ids is passed' do
it 'will get the first allowed member and move it to the end of the queue' do
expected_queue = [inbox_members[3].user_id, inbox_members[2].user_id, inbox_members[4].user_id, inbox_members[1].user_id,
inbox_members[0].user_id].map(&:to_s)
expect(described_class.new(inbox: inbox).available_agent(
allowed_agent_ids: [
inbox_members[3].user_id,
inbox_members[2].user_id
].map(&:to_s)
)).to eq inbox_members[2].user
expect(described_class.new(inbox: inbox).available_agent(
allowed_agent_ids: [
inbox_members[3].user_id,
inbox_members[2].user_id
].map(&:to_s)
)).to eq inbox_members[3].user
expect(inbox_round_robin_service.send(:queue)).to eq(expected_queue)
end
end
end
end

View File

@@ -1,75 +0,0 @@
require 'rails_helper'
describe RoundRobin::ManageService do
subject(:round_robin_manage_service) { described_class.new(inbox: inbox) }
let!(:account) { create(:account) }
let!(:inbox) { create(:inbox, account: account) }
let!(:inbox_members) { create_list(:inbox_member, 5, inbox: inbox) }
describe '#available_agent' do
it 'returns nil if allowed_member_ids is empty' do
expect(described_class.new(inbox: inbox, allowed_member_ids: []).available_agent).to be_nil
end
it 'gets the first available agent in allowed_member_ids and move agent to end of the list' do
expected_queue = [inbox_members[0].user_id, inbox_members[4].user_id, inbox_members[3].user_id, inbox_members[2].user_id,
inbox_members[1].user_id].map(&:to_s)
described_class.new(inbox: inbox, allowed_member_ids: [inbox_members[0].user_id, inbox_members[4].user_id]).available_agent
expect(round_robin_manage_service.send(:queue)).to eq(expected_queue)
end
it 'gets intersection of priority list and agent queue. get and move agent to the end of the list' do
expected_queue = [inbox_members[2].user_id, inbox_members[4].user_id, inbox_members[3].user_id, inbox_members[1].user_id,
inbox_members[0].user_id].map(&:to_s)
# prority list will be ids in string, since thats what redis supplies to us
expect(described_class.new(inbox: inbox, allowed_member_ids: [inbox_members[2].user_id])
.available_agent(priority_list: [inbox_members[3].user_id.to_s, inbox_members[2].user_id.to_s])).to eq inbox_members[2].user
expect(round_robin_manage_service.send(:queue)).to eq(expected_queue)
end
it 'constructs round_robin_queue if queue is not present' do
round_robin_manage_service.clear_queue
expect(round_robin_manage_service.send(:queue)).to eq([])
round_robin_manage_service.available_agent
# the service constructed the redis queue before performing
expect(round_robin_manage_service.send(:queue).sort.map(&:to_i)).to eq(inbox_members.map(&:user_id).sort)
end
it 'validates the queue and correct it before performing round robin' do
# adding some invalid ids to queue
round_robin_manage_service.add_agent_to_queue([2, 3, 5, 9])
expect(round_robin_manage_service.send(:queue).sort.map(&:to_i)).not_to eq(inbox_members.map(&:user_id).sort)
round_robin_manage_service.available_agent
# the service have refreshed the redis queue before performing
expect(round_robin_manage_service.send(:queue).sort.map(&:to_i)).to eq(inbox_members.map(&:user_id).sort)
end
context 'when allowed_member_ids is passed' do
it 'will get the first allowed member and move it to the end of the queue' do
expected_queue = [inbox_members[3].user_id, inbox_members[2].user_id, inbox_members[4].user_id, inbox_members[1].user_id,
inbox_members[0].user_id].map(&:to_s)
expect(described_class.new(inbox: inbox,
allowed_member_ids: [inbox_members[3].user_id,
inbox_members[2].user_id]).available_agent).to eq inbox_members[2].user
expect(described_class.new(inbox: inbox,
allowed_member_ids: [inbox_members[3].user_id,
inbox_members[2].user_id]).available_agent).to eq inbox_members[3].user
expect(round_robin_manage_service.send(:queue)).to eq(expected_queue)
end
it 'will get union of priority list and allowed_member_ids and move it to the end of the queue' do
expected_queue = [inbox_members[3].user_id, inbox_members[4].user_id, inbox_members[2].user_id, inbox_members[1].user_id,
inbox_members[0].user_id].map(&:to_s)
# prority list will be ids in string, since thats what redis supplies to us
expect(described_class.new(inbox: inbox,
allowed_member_ids: [inbox_members[3].user_id,
inbox_members[2].user_id])
.available_agent(
priority_list: [inbox_members[3].user_id.to_s]
)).to eq inbox_members[3].user
expect(round_robin_manage_service.send(:queue)).to eq(expected_queue)
end
end
end
end