Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
90b165b624 | ||
|
|
ca9bedfba1 | ||
|
|
4edf626af2 |
@@ -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,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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'"
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user