Compare commits

...
23 changed files with 260 additions and 21 deletions
@@ -21,6 +21,11 @@ class Api::V1::Accounts::InboxesController < Api::V1::Accounts::BaseController
@assignable_agents = @inbox.assignable_agents @assignable_agents = @inbox.assignable_agents
end end
def assignable_owners
@assignable_agents = @inbox.assignable_agents.select(&:confirmed?)
@agent_bots = AgentBot.accessible_to(Current.account)
end
def campaigns def campaigns
@campaigns = @inbox.campaigns @campaigns = @inbox.campaigns
end end
@@ -62,9 +62,10 @@ class ConversationApi extends ApiClient {
}); });
} }
assignAgent({ conversationId, agentId }) { assignAgent({ conversationId, agentId, assigneeType }) {
return axios.post(`${this.url}/${conversationId}/assignments`, { return axios.post(`${this.url}/${conversationId}/assignments`, {
assignee_id: agentId, assignee_id: agentId,
assignee_type: assigneeType,
}); });
} }
+4
View File
@@ -15,6 +15,10 @@ class Inboxes extends CacheEnabledApiClient {
return axios.get(`${this.url}/${inboxId}/campaigns`); return axios.get(`${this.url}/${inboxId}/campaigns`);
} }
getAssignableOwners(inboxId) {
return axios.get(`${this.url}/${inboxId}/assignable_owners`);
}
deleteInboxAvatar(inboxId) { deleteInboxAvatar(inboxId) {
return axios.delete(`${this.url}/${inboxId}/avatar`); return axios.delete(`${this.url}/${inboxId}/avatar`);
} }
@@ -90,11 +90,16 @@ describe('#ConversationAPI', () => {
}); });
it('#assignAgent', () => { it('#assignAgent', () => {
conversationAPI.assignAgent({ conversationId: 12, agentId: 34 }); conversationAPI.assignAgent({
conversationId: 12,
agentId: 34,
assigneeType: 'AgentBot',
});
expect(axiosMock.post).toHaveBeenCalledWith( expect(axiosMock.post).toHaveBeenCalledWith(
`/api/v1/conversations/12/assignments`, `/api/v1/conversations/12/assignments`,
{ {
assignee_id: 34, assignee_id: 34,
assignee_type: 'AgentBot',
} }
); );
}); });
@@ -9,6 +9,7 @@ describe('#InboxesAPI', () => {
expect(inboxesAPI).toHaveProperty('create'); expect(inboxesAPI).toHaveProperty('create');
expect(inboxesAPI).toHaveProperty('update'); expect(inboxesAPI).toHaveProperty('update');
expect(inboxesAPI).toHaveProperty('delete'); expect(inboxesAPI).toHaveProperty('delete');
expect(inboxesAPI).toHaveProperty('getAssignableOwners');
expect(inboxesAPI).toHaveProperty('getCampaigns'); expect(inboxesAPI).toHaveProperty('getCampaigns');
expect(inboxesAPI).toHaveProperty('getAgentBot'); expect(inboxesAPI).toHaveProperty('getAgentBot');
expect(inboxesAPI).toHaveProperty('setAgentBot'); expect(inboxesAPI).toHaveProperty('setAgentBot');
@@ -37,6 +38,13 @@ describe('#InboxesAPI', () => {
expect(axiosMock.get).toHaveBeenCalledWith('/api/v1/inboxes/2/campaigns'); expect(axiosMock.get).toHaveBeenCalledWith('/api/v1/inboxes/2/campaigns');
}); });
it('#getAssignableOwners', () => {
inboxesAPI.getAssignableOwners(2);
expect(axiosMock.get).toHaveBeenCalledWith(
'/api/v1/inboxes/2/assignable_owners'
);
});
it('#deleteInboxAvatar', () => { it('#deleteInboxAvatar', () => {
inboxesAPI.deleteInboxAvatar(2); inboxesAPI.deleteInboxAvatar(2);
expect(axiosMock.delete).toHaveBeenCalledWith('/api/v1/inboxes/2/avatar'); expect(axiosMock.delete).toHaveBeenCalledWith('/api/v1/inboxes/2/avatar');
@@ -31,6 +31,7 @@ const mockUseMapGetter = (overrides = {}) => {
getSelectedChat: ref({ inbox_id: 1, meta: { assignee: true } }), getSelectedChat: ref({ inbox_id: 1, meta: { assignee: true } }),
getCurrentAccountId: ref(1), getCurrentAccountId: ref(1),
'inboxAssignableAgents/getAssignableAgents': ref(() => allAgentsData), 'inboxAssignableAgents/getAssignableAgents': ref(() => allAgentsData),
'inboxAssignableAgents/getAssignableOwners': ref(() => []),
}; };
const mergedGetters = { ...defaultGetters, ...overrides }; const mergedGetters = { ...defaultGetters, ...overrides };
@@ -75,6 +76,32 @@ describe('useAgentsList', () => {
expect(agentsList.value.length).toBe(formattedAgentsData.slice(1).length); expect(agentsList.value.length).toBe(formattedAgentsData.slice(1).length);
}); });
it('includes agent bots when includeAgentBots is true', () => {
mockUseMapGetter({
'inboxAssignableAgents/getAssignableOwners': ref(() => [
{ id: 2, name: 'Agent', assignee_type: 'User' },
{
id: 1,
name: 'Bot',
thumbnail: '',
assignee_type: 'AgentBot',
icon: 'i-lucide-bot',
},
]),
});
const { agentsList } = useAgentsList(true, true);
expect(agentsList.value).toContainEqual(
expect.objectContaining({
id: 1,
name: 'Bot',
assignee_type: 'AgentBot',
icon: 'i-lucide-bot',
})
);
});
it('handles empty assignable agents', () => { it('handles empty assignable agents', () => {
mockUseMapGetter({ mockUseMapGetter({
'inboxAssignableAgents/getAssignableAgents': ref(() => []), 'inboxAssignableAgents/getAssignableAgents': ref(() => []),
@@ -10,14 +10,21 @@ import {
* A composable function that provides a list of agents for assignment. * A composable function that provides a list of agents for assignment.
* *
* @param {boolean} [includeNoneAgent=true] - Whether to include a 'None' agent option. * @param {boolean} [includeNoneAgent=true] - Whether to include a 'None' agent option.
* @param {boolean} [includeAgentBots=false] - Whether to include agent bots as assignment options.
* @returns {Object} An object containing the agents list and assignable agents. * @returns {Object} An object containing the agents list and assignable agents.
*/ */
export function useAgentsList(includeNoneAgent = true) { export function useAgentsList(
includeNoneAgent = true,
includeAgentBots = false
) {
const { t } = useI18n(); const { t } = useI18n();
const currentUser = useMapGetter('getCurrentUser'); const currentUser = useMapGetter('getCurrentUser');
const currentChat = useMapGetter('getSelectedChat'); const currentChat = useMapGetter('getSelectedChat');
const currentAccountId = useMapGetter('getCurrentAccountId'); const currentAccountId = useMapGetter('getCurrentAccountId');
const assignable = useMapGetter('inboxAssignableAgents/getAssignableAgents'); const assignable = useMapGetter('inboxAssignableAgents/getAssignableAgents');
const assignableOwners = useMapGetter(
'inboxAssignableAgents/getAssignableOwners'
);
const inboxId = computed(() => currentChat.value?.inbox_id); const inboxId = computed(() => currentChat.value?.inbox_id);
const isAgentSelected = computed(() => currentChat.value?.meta?.assignee); const isAgentSelected = computed(() => currentChat.value?.meta?.assignee);
@@ -42,11 +49,22 @@ export function useAgentsList(includeNoneAgent = true) {
return inboxId.value ? assignable.value(inboxId.value) : []; return inboxId.value ? assignable.value(inboxId.value) : [];
}); });
const owners = computed(() => {
return includeAgentBots && inboxId.value
? assignableOwners.value(inboxId.value)
: [];
});
/** /**
* @type {import('vue').ComputedRef<Array>} * @type {import('vue').ComputedRef<Array>}
*/ */
const agentsList = computed(() => { const agentsList = computed(() => {
const agents = assignableAgents.value || []; const agents = includeAgentBots
? owners.value.filter(owner => owner.assignee_type === 'User')
: assignableAgents.value || [];
const bots = owners.value.filter(
owner => owner.assignee_type === 'AgentBot'
);
const agentsByUpdatedPresence = getAgentsByUpdatedPresence( const agentsByUpdatedPresence = getAgentsByUpdatedPresence(
agents, agents,
currentUser.value, currentUser.value,
@@ -60,6 +78,7 @@ export function useAgentsList(includeNoneAgent = true) {
return [ return [
...(includeNoneAgent && isAgentSelected.value ? [createNoneAgent()] : []), ...(includeNoneAgent && isAgentSelected.value ? [createNoneAgent()] : []),
...filteredAgentsByAvailability, ...filteredAgentsByAvailability,
...bots,
]; ];
}); });
@@ -25,7 +25,7 @@ export default {
}, },
}, },
setup() { setup() {
const { agentsList } = useAgentsList(); const { agentsList } = useAgentsList(true, true);
return { return {
agentsList, agentsList,
}; };
@@ -81,18 +81,27 @@ export default {
}, },
assignedAgent: { assignedAgent: {
get() { get() {
return this.currentChat.meta.assignee; const assignee = this.currentChat.meta.assignee;
if (!assignee) return assignee;
return {
...assignee,
assignee_type: this.currentChat.meta.assignee_type || 'User',
};
}, },
set(agent) { set(agent) {
const agentId = agent ? agent.id : null; const agentId = agent ? agent.id : null;
const assigneeType = agent ? agent.assignee_type || 'User' : null;
this.$store.dispatch('setCurrentChatAssignee', { this.$store.dispatch('setCurrentChatAssignee', {
conversationId: this.currentChat.id, conversationId: this.currentChat.id,
assignee: agent, assignee: agent,
assigneeType,
}); });
this.$store this.$store
.dispatch('assignAgent', { .dispatch('assignAgent', {
conversationId: this.currentChat.id, conversationId: this.currentChat.id,
agentId, agentId,
assigneeType,
}) })
.then(() => { .then(() => {
useAlert(this.$t('CONVERSATION.CHANGE_AGENT')); useAlert(this.$t('CONVERSATION.CHANGE_AGENT'));
@@ -158,6 +167,12 @@ export default {
return false; return false;
}, },
}, },
mounted() {
this.$store.dispatch(
'inboxAssignableAgents/fetchAssignableOwners',
this.currentChat.inbox_id
);
},
methods: { methods: {
onSelfAssign() { onSelfAssign() {
const { const {
@@ -174,6 +189,7 @@ export default {
account_id, account_id,
availability_status, availability_status,
available_name, available_name,
assignee_type: 'User',
email, email,
id, id,
name, name,
@@ -183,7 +199,14 @@ export default {
this.assignedAgent = selfAssign; this.assignedAgent = selfAssign;
}, },
onClickAssignAgent(selectedItem) { onClickAssignAgent(selectedItem) {
if (this.assignedAgent && this.assignedAgent.id === selectedItem.id) { const currentAssigneeType = this.assignedAgent?.assignee_type || 'User';
const selectedAssigneeType = selectedItem.assignee_type || 'User';
if (
this.assignedAgent &&
this.assignedAgent.id === selectedItem.id &&
currentAssigneeType === selectedAssigneeType
) {
this.assignedAgent = null; this.assignedAgent = null;
} else { } else {
this.assignedAgent = selectedItem; this.assignedAgent = selectedItem;
@@ -208,23 +208,31 @@ const actions = {
} }
}, },
assignAgent: async ({ dispatch }, { conversationId, agentId }) => { assignAgent: async (
{ dispatch },
{ conversationId, agentId, assigneeType }
) => {
try { try {
const response = await ConversationApi.assignAgent({ const response = await ConversationApi.assignAgent({
conversationId, conversationId,
agentId, agentId,
assigneeType,
}); });
dispatch('setCurrentChatAssignee', { dispatch('setCurrentChatAssignee', {
conversationId, conversationId,
assignee: response.data, assignee: response.data,
assigneeType,
}); });
} catch (error) { } catch (error) {
// Handle error // Handle error
} }
}, },
setCurrentChatAssignee({ commit }, { conversationId, assignee }) { setCurrentChatAssignee(
commit(types.ASSIGN_AGENT, { conversationId, assignee }); { commit },
{ conversationId, assignee, assigneeType }
) {
commit(types.ASSIGN_AGENT, { conversationId, assignee, assigneeType });
}, },
assignTeam: async ({ dispatch }, { conversationId, teamId }) => { assignTeam: async ({ dispatch }, { conversationId, teamId }) => {
@@ -108,10 +108,16 @@ export const mutations = {
} }
}, },
[types.ASSIGN_AGENT](_state, { conversationId, assignee }) { [types.ASSIGN_AGENT](_state, { conversationId, assignee, assigneeType }) {
const chat = getConversationById(_state)(conversationId); const chat = getConversationById(_state)(conversationId);
if (chat) { if (chat) {
chat.meta.assignee = assignee; chat.meta.assignee = assignee;
chat.meta.assignee_type = assigneeType;
if (assigneeType === 'AgentBot') {
chat.status = 'pending';
} else if (assignee) {
chat.status = 'open';
}
} }
}, },
@@ -1,7 +1,9 @@
import AssignableAgentsAPI from '../../api/assignableAgents'; import AssignableAgentsAPI from '../../api/assignableAgents';
import InboxesAPI from '../../api/inboxes';
const state = { const state = {
records: {}, records: {},
ownerRecords: {},
uiFlags: { uiFlags: {
isFetching: false, isFetching: false,
}, },
@@ -10,6 +12,7 @@ const state = {
export const types = { export const types = {
SET_INBOX_ASSIGNABLE_AGENTS_UI_FLAG: 'SET_INBOX_ASSIGNABLE_AGENTS_UI_FLAG', SET_INBOX_ASSIGNABLE_AGENTS_UI_FLAG: 'SET_INBOX_ASSIGNABLE_AGENTS_UI_FLAG',
SET_INBOX_ASSIGNABLE_AGENTS: 'SET_INBOX_ASSIGNABLE_AGENTS', SET_INBOX_ASSIGNABLE_AGENTS: 'SET_INBOX_ASSIGNABLE_AGENTS',
SET_INBOX_ASSIGNABLE_OWNERS: 'SET_INBOX_ASSIGNABLE_OWNERS',
}; };
export const getters = { export const getters = {
@@ -18,6 +21,7 @@ export const getters = {
const verifiedAgents = allAgents.filter(record => record.confirmed); const verifiedAgents = allAgents.filter(record => record.confirmed);
return verifiedAgents; return verifiedAgents;
}, },
getAssignableOwners: $state => inboxId => $state.ownerRecords[inboxId] || [],
getUIFlags($state) { getUIFlags($state) {
return $state.uiFlags; return $state.uiFlags;
}, },
@@ -40,6 +44,15 @@ export const actions = {
commit(types.SET_INBOX_ASSIGNABLE_AGENTS_UI_FLAG, { isFetching: false }); commit(types.SET_INBOX_ASSIGNABLE_AGENTS_UI_FLAG, { isFetching: false });
} }
}, },
async fetchAssignableOwners({ commit }, inboxId) {
const {
data: { payload },
} = await InboxesAPI.getAssignableOwners(inboxId);
commit(types.SET_INBOX_ASSIGNABLE_OWNERS, {
inboxId,
owners: payload,
});
},
}; };
export const mutations = { export const mutations = {
@@ -55,6 +68,12 @@ export const mutations = {
[inboxId]: members, [inboxId]: members,
}; };
}, },
[types.SET_INBOX_ASSIGNABLE_OWNERS]: ($state, { inboxId, owners }) => {
$state.ownerRecords = {
...$state.ownerRecords,
[inboxId]: owners,
};
},
}; };
export default { export default {
@@ -357,11 +357,12 @@ describe('#actions', () => {
}); });
await actions.assignAgent( await actions.assignAgent(
{ dispatch }, { dispatch },
{ conversationId: 1, agentId: 1 } { conversationId: 1, agentId: 1, assigneeType: 'AgentBot' }
); );
expect(dispatch).toHaveBeenCalledWith('setCurrentChatAssignee', { expect(dispatch).toHaveBeenCalledWith('setCurrentChatAssignee', {
conversationId: 1, conversationId: 1,
assignee: { id: 1, name: 'User' }, assignee: { id: 1, name: 'User' },
assigneeType: 'AgentBot',
}); });
}); });
}); });
@@ -371,6 +372,7 @@ describe('#actions', () => {
const payload = { const payload = {
conversationId: 1, conversationId: 1,
assignee: { id: 1, name: 'User' }, assignee: { id: 1, name: 'User' },
assigneeType: 'AgentBot',
}; };
await actions.setCurrentChatAssignee({ commit }, payload); await actions.setCurrentChatAssignee({ commit }, payload);
expect(commit).toHaveBeenCalledTimes(1); expect(commit).toHaveBeenCalledTimes(1);
@@ -699,11 +699,11 @@ describe('#mutations', () => {
}); });
describe('#ASSIGN_AGENT', () => { describe('#ASSIGN_AGENT', () => {
it('should assign agent to the correct conversation by ID', () => { it('should assign agent bot to the correct conversation by ID', () => {
const assignee = { id: 1, name: 'Agent' }; const assignee = { id: 1, name: 'Agent' };
const state = { const state = {
allConversations: [ allConversations: [
{ id: 1, meta: {} }, { id: 1, meta: {}, status: 'open' },
{ id: 2, meta: {} }, { id: 2, meta: {} },
], ],
selectedChatId: 2, selectedChatId: 2,
@@ -712,10 +712,28 @@ describe('#mutations', () => {
mutations[types.ASSIGN_AGENT](state, { mutations[types.ASSIGN_AGENT](state, {
conversationId: 1, conversationId: 1,
assignee, assignee,
assigneeType: 'AgentBot',
}); });
expect(state.allConversations[0].meta.assignee).toEqual(assignee); expect(state.allConversations[0].meta.assignee).toEqual(assignee);
expect(state.allConversations[0].meta.assignee_type).toEqual('AgentBot');
expect(state.allConversations[0].status).toEqual('pending');
expect(state.allConversations[1].meta.assignee).toBeUndefined(); expect(state.allConversations[1].meta.assignee).toBeUndefined();
}); });
it('should open the conversation when assigning a user', () => {
const assignee = { id: 1, name: 'Agent' };
const state = {
allConversations: [{ id: 1, meta: {}, status: 'pending' }],
};
mutations[types.ASSIGN_AGENT](state, {
conversationId: 1,
assignee,
assigneeType: 'User',
});
expect(state.allConversations[0].status).toEqual('open');
});
}); });
describe('#ASSIGN_PRIORITY', () => { describe('#ASSIGN_PRIORITY', () => {
@@ -1,12 +1,18 @@
import axios from 'axios'; import axios from 'axios';
import InboxesAPI from 'dashboard/api/inboxes';
import { actions, types } from '../../inboxAssignableAgents'; import { actions, types } from '../../inboxAssignableAgents';
import agentsData from './fixtures'; import agentsData from './fixtures';
const commit = vi.fn(); const commit = vi.fn();
global.axios = axios; global.axios = axios;
vi.mock('axios'); vi.mock('axios');
vi.mock('dashboard/api/inboxes');
describe('#actions', () => { describe('#actions', () => {
beforeEach(() => {
vi.clearAllMocks();
});
describe('#fetch', () => { describe('#fetch', () => {
it('sends correct actions if API is success', async () => { it('sends correct actions if API is success', async () => {
axios.get.mockResolvedValue({ axios.get.mockResolvedValue({
@@ -33,4 +39,20 @@ describe('#actions', () => {
]); ]);
}); });
}); });
describe('#fetchAssignableOwners', () => {
it('stores assignable owners for the inbox', async () => {
InboxesAPI.getAssignableOwners.mockResolvedValue({
data: { payload: agentsData },
});
await actions.fetchAssignableOwners({ commit }, 1);
expect(InboxesAPI.getAssignableOwners).toHaveBeenCalledWith(1);
expect(commit).toHaveBeenCalledWith(types.SET_INBOX_ASSIGNABLE_OWNERS, {
inboxId: 1,
owners: agentsData,
});
});
});
}); });
@@ -13,4 +13,16 @@ describe('#mutations', () => {
expect(state.records).toEqual({ 1: agentsData }); expect(state.records).toEqual({ 1: agentsData });
}); });
}); });
describe('#SET_INBOX_ASSIGNABLE_OWNERS', () => {
it('adds inbox owners to ownerRecords', () => {
const state = { ownerRecords: {} };
mutations[types.SET_INBOX_ASSIGNABLE_OWNERS](state, {
owners: [...agentsData],
inboxId: 1,
});
expect(state.ownerRecords).toEqual({ 1: agentsData });
});
});
}); });
@@ -67,7 +67,13 @@ export default {
this.$refs.searchbar.focus(); this.$refs.searchbar.focus();
}, },
isActive(option) { isActive(option) {
return this.selectedItems.some(item => item && option.id === item.id); return this.selectedItems.some(item => {
if (!item || option.id !== item.id) return false;
return (
(option.assignee_type || 'User') === (item.assignee_type || 'User')
);
});
}, },
}, },
}; };
@@ -88,7 +94,10 @@ export default {
<div class="flex items-start justify-start flex-auto overflow-auto mt-2"> <div class="flex items-start justify-start flex-auto overflow-auto mt-2">
<div class="w-full max-h-[10rem]"> <div class="w-full max-h-[10rem]">
<WootDropdownMenu> <WootDropdownMenu>
<WootDropdownItem v-for="option in filteredOptions" :key="option.id"> <WootDropdownItem
v-for="option in filteredOptions"
:key="`${option.assignee_type || 'User'}-${option.id}`"
>
<NextButton <NextButton
slate slate
:variant="isActive(option) ? 'faded' : 'ghost'" :variant="isActive(option) ? 'faded' : 'ghost'"
+4
View File
@@ -30,6 +30,10 @@ class InboxPolicy < ApplicationPolicy
true true
end end
def assignable_owners?
true
end
def agent_bot? def agent_bot?
true true
end end
@@ -16,6 +16,7 @@ class Conversations::AssignmentService
def assign_agent def assign_agent
conversation.assignee = assignee conversation.assignee = assignee
conversation.assignee_agent_bot = nil conversation.assignee_agent_bot = nil
conversation.status = :open if assignee
conversation.save! conversation.save!
assignee assignee
end end
@@ -25,6 +26,7 @@ class Conversations::AssignmentService
conversation.assignee = nil conversation.assignee = nil
conversation.assignee_agent_bot = agent_bot conversation.assignee_agent_bot = agent_bot
conversation.status = :pending
conversation.save! conversation.save!
agent_bot agent_bot
end end
@@ -0,0 +1,15 @@
owners = @assignable_agents.map { |agent| { type: 'User', resource: agent } }
owners += @agent_bots.map { |agent_bot| { type: 'AgentBot', resource: agent_bot } }
json.payload do
json.array! owners do |owner|
if owner[:type] == 'User'
json.partial! 'api/v1/models/agent', formats: [:json], resource: owner[:resource]
json.assignee_type 'User'
else
json.partial! 'api/v1/models/agent_bot_slim', formats: [:json], resource: owner[:resource]
json.assignee_type 'AgentBot'
json.icon 'i-lucide-bot'
end
end
end
+1
View File
@@ -254,6 +254,7 @@ Rails.application.routes.draw do
resources :custom_filters, only: [:index, :show, :create, :update, :destroy] resources :custom_filters, only: [:index, :show, :create, :update, :destroy]
resources :inboxes, only: [:index, :show, :create, :update, :destroy] do resources :inboxes, only: [:index, :show, :create, :update, :destroy] do
get :assignable_agents, on: :member get :assignable_agents, on: :member
get :assignable_owners, on: :member
get :campaigns, on: :member get :campaigns, on: :member
get :agent_bot, on: :member get :agent_bot, on: :member
post :set_agent_bot, on: :member post :set_agent_bot, on: :member
@@ -44,6 +44,7 @@ RSpec.describe 'Conversation Assignment API', type: :request do
end end
it 'assigns a user to the conversation' do it 'assigns a user to the conversation' do
conversation.update!(status: :pending)
params = { assignee_id: agent.id } params = { assignee_id: agent.id }
post api_v1_account_conversation_assignments_url(account_id: account.id, conversation_id: conversation.display_id), post api_v1_account_conversation_assignments_url(account_id: account.id, conversation_id: conversation.display_id),
@@ -52,10 +53,13 @@ RSpec.describe 'Conversation Assignment API', type: :request do
as: :json as: :json
expect(response).to have_http_status(:success) expect(response).to have_http_status(:success)
expect(conversation.reload.assignee).to eq(agent) conversation.reload
expect(conversation.assignee).to eq(agent)
expect(conversation.status).to eq('open')
end end
it 'assigns an agent bot to the conversation' do it 'assigns an agent bot to the conversation' do
conversation.update!(status: :open)
params = { assignee_id: agent_bot.id, assignee_type: 'AgentBot' } params = { assignee_id: agent_bot.id, assignee_type: 'AgentBot' }
expect(Conversations::AssignmentService).to receive(:new) expect(Conversations::AssignmentService).to receive(:new)
@@ -72,6 +76,7 @@ RSpec.describe 'Conversation Assignment API', type: :request do
conversation.reload conversation.reload
expect(conversation.assignee_agent_bot).to eq(agent_bot) expect(conversation.assignee_agent_bot).to eq(agent_bot)
expect(conversation.assignee).to be_nil expect(conversation.assignee).to be_nil
expect(conversation.status).to eq('pending')
end end
it 'assigns a team to the conversation' do it 'assigns a team to the conversation' do
@@ -251,6 +251,28 @@ RSpec.describe 'Inboxes API', type: :request do
end end
end end
describe 'GET /api/v1/accounts/{account.id}/inboxes/{inbox.id}/assignable_owners' do
let(:inbox) { create(:inbox, account: account) }
let!(:account_bot) { create(:agent_bot, account: account, name: 'Account bot') }
let!(:global_bot) { create(:agent_bot, account: nil, name: 'Global bot') }
before do
create(:inbox_member, user: agent, inbox: inbox)
end
it 'returns assignable agents and accessible agent bots' do
get "/api/v1/accounts/#{account.id}/inboxes/#{inbox.id}/assignable_owners",
headers: admin.create_new_auth_token,
as: :json
expect(response).to have_http_status(:success)
response_data = response.parsed_body['payload']
expect(response_data.pluck('assignee_type')).to include('User', 'AgentBot')
expect(response_data.pluck('name')).to include(agent.name, admin.name, account_bot.name, global_bot.name)
end
end
describe 'GET /api/v1/accounts/{account.id}/inboxes/{inbox.id}/campaigns' do describe 'GET /api/v1/accounts/{account.id}/inboxes/{inbox.id}/campaigns' do
let(:inbox) { create(:inbox, account: account) } let(:inbox) { create(:inbox, account: account) }
@@ -23,16 +23,17 @@ describe Conversations::AssignmentService do
context 'when assigning a user' do context 'when assigning a user' do
before do before do
conversation.update!(assignee_agent_bot: agent_bot, assignee: nil) conversation.update!(assignee_agent_bot: agent_bot, assignee: nil, status: :pending)
end end
it 'sets the agent and clears agent bot' do it 'sets the agent, clears agent bot and opens the conversation' do
result = described_class.new(conversation: conversation, assignee_id: agent.id).perform result = described_class.new(conversation: conversation, assignee_id: agent.id).perform
conversation.reload conversation.reload
expect(result).to eq(agent) expect(result).to eq(agent)
expect(conversation.assignee_id).to eq(agent.id) expect(conversation.assignee_id).to eq(agent.id)
expect(conversation.assignee_agent_bot_id).to be_nil expect(conversation.assignee_agent_bot_id).to be_nil
expect(conversation.status).to eq('open')
end end
end end
@@ -45,8 +46,8 @@ describe Conversations::AssignmentService do
) )
end end
it 'sets the agent bot and clears human assignee' do it 'sets the agent bot, clears human assignee and marks the conversation pending' do
conversation.update!(assignee: agent, assignee_agent_bot: nil) conversation.update!(assignee: agent, assignee_agent_bot: nil, status: :open)
result = service.perform result = service.perform
@@ -54,6 +55,7 @@ describe Conversations::AssignmentService do
expect(result).to eq(agent_bot) expect(result).to eq(agent_bot)
expect(conversation.assignee_agent_bot_id).to eq(agent_bot.id) expect(conversation.assignee_agent_bot_id).to eq(agent_bot.id)
expect(conversation.assignee_id).to be_nil expect(conversation.assignee_id).to be_nil
expect(conversation.status).to eq('pending')
end end
end end
end end