Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
da0a2661ac |
@@ -1,7 +1,7 @@
|
||||
class Api::V1::Accounts::Conversations::AssignmentsController < Api::V1::Accounts::Conversations::BaseController
|
||||
# assigns agent/team to a conversation
|
||||
def create
|
||||
if params.key?(:assignee_id) || agent_bot_assignment?
|
||||
if params.key?(:assignee_id) || ai_assignment?
|
||||
set_agent
|
||||
elsif params.key?(:team_id)
|
||||
set_team
|
||||
@@ -28,6 +28,8 @@ class Api::V1::Accounts::Conversations::AssignmentsController < Api::V1::Account
|
||||
render partial: 'api/v1/models/agent', formats: [:json], locals: { resource: resource }
|
||||
when AgentBot
|
||||
render partial: 'api/v1/models/agent_bot_slim', formats: [:json], locals: { resource: resource }
|
||||
when Captain::Assistant
|
||||
render partial: 'api/v1/models/captain_assistant_slim', formats: [:json], locals: { resource: resource }
|
||||
else
|
||||
render json: nil
|
||||
end
|
||||
@@ -39,7 +41,7 @@ class Api::V1::Accounts::Conversations::AssignmentsController < Api::V1::Account
|
||||
render json: @team
|
||||
end
|
||||
|
||||
def agent_bot_assignment?
|
||||
params[:assignee_type].to_s == 'AgentBot'
|
||||
def ai_assignment?
|
||||
params[:assignee_type].to_s.in?(%w[AgentBot CaptainAssistant])
|
||||
end
|
||||
end
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
# updated_at :datetime not null
|
||||
# account_id :integer not null
|
||||
# assignee_agent_bot_id :bigint
|
||||
# assignee_captain_assistant_id :bigint
|
||||
# assignee_id :integer
|
||||
# campaign_id :bigint
|
||||
# contact_id :bigint
|
||||
@@ -66,7 +67,7 @@ class Conversation < ApplicationRecord
|
||||
validates :inbox_id, presence: true
|
||||
validates :contact_id, presence: true
|
||||
before_validation :validate_additional_attributes
|
||||
before_validation :reset_agent_bot_when_assignee_present
|
||||
before_validation :reset_ai_assignees_when_assignee_present
|
||||
validates :additional_attributes, jsonb_attributes_length: true
|
||||
validates :custom_attributes, jsonb_attributes_length: true
|
||||
validates :uuid, uniqueness: true
|
||||
@@ -104,6 +105,7 @@ class Conversation < ApplicationRecord
|
||||
belongs_to :inbox
|
||||
belongs_to :assignee, class_name: 'User', optional: true, inverse_of: :assigned_conversations
|
||||
belongs_to :assignee_agent_bot, class_name: 'AgentBot', optional: true
|
||||
belongs_to :assignee_captain_assistant, class_name: 'Captain::Assistant', optional: true
|
||||
belongs_to :contact
|
||||
belongs_to :contact_inbox
|
||||
belongs_to :team, optional: true
|
||||
@@ -195,6 +197,7 @@ class Conversation < ApplicationRecord
|
||||
|
||||
# Virtual attribute till we switch completely to polymorphic assignee
|
||||
def assignee_type
|
||||
return 'CaptainAssistant' if assignee_captain_assistant_id.present?
|
||||
return 'AgentBot' if assignee_agent_bot_id.present?
|
||||
return 'User' if assignee_id.present?
|
||||
|
||||
@@ -202,7 +205,7 @@ class Conversation < ApplicationRecord
|
||||
end
|
||||
|
||||
def assigned_entity
|
||||
assignee_agent_bot || assignee
|
||||
assignee_captain_assistant || assignee_agent_bot || assignee
|
||||
end
|
||||
|
||||
def tweet?
|
||||
@@ -271,10 +274,11 @@ class Conversation < ApplicationRecord
|
||||
self.additional_attributes = {} unless additional_attributes.is_a?(Hash)
|
||||
end
|
||||
|
||||
def reset_agent_bot_when_assignee_present
|
||||
def reset_ai_assignees_when_assignee_present
|
||||
return if assignee_id.blank?
|
||||
|
||||
self.assignee_agent_bot_id = nil
|
||||
self.assignee_captain_assistant_id = nil
|
||||
end
|
||||
|
||||
def determine_conversation_status
|
||||
@@ -308,8 +312,8 @@ class Conversation < ApplicationRecord
|
||||
end
|
||||
|
||||
def list_of_keys
|
||||
%w[team_id assignee_id assignee_agent_bot_id status snoozed_until custom_attributes label_list waiting_since
|
||||
first_reply_created_at priority]
|
||||
%w[team_id assignee_id assignee_agent_bot_id assignee_captain_assistant_id status snoozed_until custom_attributes label_list
|
||||
waiting_since first_reply_created_at priority]
|
||||
end
|
||||
|
||||
def allowed_keys?
|
||||
|
||||
@@ -6,7 +6,10 @@ class Conversations::AssignmentService
|
||||
end
|
||||
|
||||
def perform
|
||||
agent_bot_assignment? ? assign_agent_bot : assign_agent
|
||||
return assign_agent_bot if agent_bot_assignment?
|
||||
return assign_captain_assistant if captain_assistant_assignment?
|
||||
|
||||
assign_agent
|
||||
end
|
||||
|
||||
private
|
||||
@@ -14,8 +17,11 @@ class Conversations::AssignmentService
|
||||
attr_reader :conversation, :assignee_id, :assignee_type
|
||||
|
||||
def assign_agent
|
||||
captain_owned = conversation.assignee_captain_assistant_id.present?
|
||||
conversation.assignee = assignee
|
||||
conversation.assignee_agent_bot = nil
|
||||
conversation.assignee_captain_assistant = nil
|
||||
conversation.status = :open if assignee.present? && captain_owned && conversation.pending?
|
||||
conversation.save!
|
||||
assignee
|
||||
end
|
||||
@@ -25,10 +31,22 @@ class Conversations::AssignmentService
|
||||
|
||||
conversation.assignee = nil
|
||||
conversation.assignee_agent_bot = agent_bot
|
||||
conversation.assignee_captain_assistant = nil
|
||||
conversation.save!
|
||||
agent_bot
|
||||
end
|
||||
|
||||
def assign_captain_assistant
|
||||
return unless captain_assistant
|
||||
|
||||
conversation.assignee = nil
|
||||
conversation.assignee_agent_bot = nil
|
||||
conversation.assignee_captain_assistant = captain_assistant
|
||||
conversation.status = :pending
|
||||
conversation.save!
|
||||
captain_assistant
|
||||
end
|
||||
|
||||
def assignee
|
||||
@assignee ||= conversation.account.users.find_by(id: assignee_id)
|
||||
end
|
||||
@@ -37,7 +55,15 @@ class Conversations::AssignmentService
|
||||
@agent_bot ||= AgentBot.accessible_to(conversation.account).find_by(id: assignee_id)
|
||||
end
|
||||
|
||||
def captain_assistant
|
||||
@captain_assistant ||= Captain::Assistant.for_account(conversation.account_id).find_by(id: assignee_id)
|
||||
end
|
||||
|
||||
def agent_bot_assignment?
|
||||
assignee_type.to_s == 'AgentBot'
|
||||
end
|
||||
|
||||
def captain_assistant_assignment?
|
||||
assignee_type.to_s == 'CaptainAssistant'
|
||||
end
|
||||
end
|
||||
|
||||
@@ -7,7 +7,12 @@ json.meta do
|
||||
json.partial! 'api/v1/models/contact', formats: [:json], resource: conversation.contact
|
||||
end
|
||||
json.channel conversation.inbox.try(:channel_type)
|
||||
if conversation.assigned_entity.is_a?(AgentBot)
|
||||
if conversation.assignee_type == 'CaptainAssistant'
|
||||
json.assignee do
|
||||
json.partial! 'api/v1/models/captain_assistant_slim', formats: [:json], resource: conversation.assigned_entity
|
||||
end
|
||||
json.assignee_type 'CaptainAssistant'
|
||||
elsif conversation.assigned_entity.is_a?(AgentBot)
|
||||
json.assignee do
|
||||
json.partial! 'api/v1/models/agent_bot_slim', formats: [:json], resource: conversation.assigned_entity
|
||||
end
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
json.id resource.id
|
||||
json.name resource.name
|
||||
json.description resource.description
|
||||
json.thumbnail resource.push_event_data[:avatar_url]
|
||||
@@ -0,0 +1,5 @@
|
||||
class AddAssigneeCaptainAssistantToConversations < ActiveRecord::Migration[7.1]
|
||||
def change
|
||||
add_reference :conversations, :assignee_captain_assistant, type: :bigint, index: true
|
||||
end
|
||||
end
|
||||
+3
-1
@@ -10,7 +10,7 @@
|
||||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema[7.1].define(version: 2026_06_20_000000) do
|
||||
ActiveRecord::Schema[7.1].define(version: 2026_06_28_000000) do
|
||||
# These extensions should be enabled to support this database
|
||||
enable_extension "pg_stat_statements"
|
||||
enable_extension "pg_trgm"
|
||||
@@ -726,11 +726,13 @@ ActiveRecord::Schema[7.1].define(version: 2026_06_20_000000) do
|
||||
t.datetime "waiting_since"
|
||||
t.text "cached_label_list"
|
||||
t.bigint "assignee_agent_bot_id"
|
||||
t.bigint "assignee_captain_assistant_id"
|
||||
t.index ["account_id", "display_id"], name: "index_conversations_on_account_id_and_display_id", unique: true
|
||||
t.index ["account_id", "id"], name: "index_conversations_on_id_and_account_id"
|
||||
t.index ["account_id", "inbox_id", "status", "assignee_id"], name: "conv_acid_inbid_stat_asgnid_idx"
|
||||
t.index ["account_id"], name: "index_conversations_on_account_id"
|
||||
t.index ["assignee_id", "account_id"], name: "index_conversations_on_assignee_id_and_account_id"
|
||||
t.index ["assignee_captain_assistant_id"], name: "index_conversations_on_assignee_captain_assistant_id"
|
||||
t.index ["campaign_id"], name: "index_conversations_on_campaign_id"
|
||||
t.index ["contact_id"], name: "index_conversations_on_contact_id"
|
||||
t.index ["contact_inbox_id"], name: "index_conversations_on_contact_inbox_id"
|
||||
|
||||
@@ -37,6 +37,7 @@ RSpec.describe 'Conversation Assignment API', type: :request do
|
||||
context 'when it is an authenticated user with access to the inbox' do
|
||||
let(:agent) { create(:user, account: account, role: :agent) }
|
||||
let(:agent_bot) { create(:agent_bot, account: account) }
|
||||
let(:captain_assistant) { create(:captain_assistant, account: account) }
|
||||
let(:team) { create(:team, account: account) }
|
||||
|
||||
before do
|
||||
@@ -74,6 +75,27 @@ RSpec.describe 'Conversation Assignment API', type: :request do
|
||||
expect(conversation.assignee).to be_nil
|
||||
end
|
||||
|
||||
it 'assigns a Captain assistant to the conversation' do
|
||||
params = { assignee_id: captain_assistant.id, assignee_type: 'CaptainAssistant' }
|
||||
|
||||
expect(Conversations::AssignmentService).to receive(:new)
|
||||
.with(hash_including(conversation: conversation, assignee_id: captain_assistant.id, assignee_type: 'CaptainAssistant'))
|
||||
.and_call_original
|
||||
|
||||
post api_v1_account_conversation_assignments_url(account_id: account.id, conversation_id: conversation.display_id),
|
||||
params: params,
|
||||
headers: agent.create_new_auth_token,
|
||||
as: :json
|
||||
|
||||
expect(response).to have_http_status(:success)
|
||||
expect(response.parsed_body['name']).to eq(captain_assistant.name)
|
||||
conversation.reload
|
||||
expect(conversation.assignee_captain_assistant).to eq(captain_assistant)
|
||||
expect(conversation.assignee_agent_bot).to be_nil
|
||||
expect(conversation.assignee).to be_nil
|
||||
expect(conversation.status).to eq('pending')
|
||||
end
|
||||
|
||||
it 'assigns a team to the conversation' do
|
||||
team_member = create(:user, account: account, role: :agent, auto_offline: false)
|
||||
create(:inbox_member, inbox: conversation.inbox, user: team_member)
|
||||
|
||||
@@ -4,35 +4,39 @@ describe Conversations::AssignmentService do
|
||||
let(:account) { create(:account) }
|
||||
let(:agent) { create(:user, account: account) }
|
||||
let(:agent_bot) { create(:agent_bot, account: account) }
|
||||
let(:captain_assistant) { create(:captain_assistant, account: account) }
|
||||
let(:conversation) { create(:conversation, account: account) }
|
||||
|
||||
describe '#perform' do
|
||||
context 'when assignee_id is blank' do
|
||||
before do
|
||||
conversation.update!(assignee: agent, assignee_agent_bot: agent_bot)
|
||||
conversation.update!(assignee_agent_bot: agent_bot, assignee_captain_assistant: captain_assistant)
|
||||
end
|
||||
|
||||
it 'clears both human and bot assignees' do
|
||||
it 'clears human and AI assignees' do
|
||||
described_class.new(conversation: conversation, assignee_id: nil).perform
|
||||
|
||||
conversation.reload
|
||||
expect(conversation.assignee_id).to be_nil
|
||||
expect(conversation.assignee_agent_bot_id).to be_nil
|
||||
expect(conversation.assignee_captain_assistant_id).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'when assigning a user' do
|
||||
before do
|
||||
conversation.update!(assignee_agent_bot: agent_bot, assignee: nil)
|
||||
conversation.update!(assignee_captain_assistant: captain_assistant, assignee: nil, status: :pending)
|
||||
end
|
||||
|
||||
it 'sets the agent and clears agent bot' do
|
||||
it 'sets the agent and clears AI ownership' do
|
||||
result = described_class.new(conversation: conversation, assignee_id: agent.id).perform
|
||||
|
||||
conversation.reload
|
||||
expect(result).to eq(agent)
|
||||
expect(conversation.assignee_id).to eq(agent.id)
|
||||
expect(conversation.assignee_agent_bot_id).to be_nil
|
||||
expect(conversation.assignee_captain_assistant_id).to be_nil
|
||||
expect(conversation.status).to eq('open')
|
||||
end
|
||||
end
|
||||
|
||||
@@ -45,8 +49,8 @@ describe Conversations::AssignmentService do
|
||||
)
|
||||
end
|
||||
|
||||
it 'sets the agent bot and clears human assignee' do
|
||||
conversation.update!(assignee: agent, assignee_agent_bot: nil)
|
||||
it 'sets the agent bot and clears other assignees' do
|
||||
conversation.update!(assignee_agent_bot: nil, assignee_captain_assistant: captain_assistant)
|
||||
|
||||
result = service.perform
|
||||
|
||||
@@ -54,6 +58,30 @@ describe Conversations::AssignmentService do
|
||||
expect(result).to eq(agent_bot)
|
||||
expect(conversation.assignee_agent_bot_id).to eq(agent_bot.id)
|
||||
expect(conversation.assignee_id).to be_nil
|
||||
expect(conversation.assignee_captain_assistant_id).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'when assigning a Captain assistant' do
|
||||
let(:service) do
|
||||
described_class.new(
|
||||
conversation: conversation,
|
||||
assignee_id: captain_assistant.id,
|
||||
assignee_type: 'CaptainAssistant'
|
||||
)
|
||||
end
|
||||
|
||||
it 'sets the Captain assistant, clears other assignees, and marks pending' do
|
||||
conversation.update!(assignee_agent_bot: agent_bot, status: :resolved)
|
||||
|
||||
result = service.perform
|
||||
|
||||
conversation.reload
|
||||
expect(result).to eq(captain_assistant)
|
||||
expect(conversation.assignee_captain_assistant_id).to eq(captain_assistant.id)
|
||||
expect(conversation.assignee_agent_bot_id).to be_nil
|
||||
expect(conversation.assignee_id).to be_nil
|
||||
expect(conversation.status).to eq('pending')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user