+ >
+
+
+
+
+
+
diff --git a/app/javascript/dashboard/composables/spec/useAgentsList.spec.js b/app/javascript/dashboard/composables/spec/useAgentsList.spec.js
index 7f6d8e757..3a39a6be9 100644
--- a/app/javascript/dashboard/composables/spec/useAgentsList.spec.js
+++ b/app/javascript/dashboard/composables/spec/useAgentsList.spec.js
@@ -26,11 +26,12 @@ const mockNoneAgent = {
};
const mockUseMapGetter = (overrides = {}) => {
+ const getAssignableAgents = vi.fn(() => allAgentsData);
const defaultGetters = {
getCurrentUser: ref(allAgentsData[0]),
getSelectedChat: ref({ inbox_id: 1, meta: { assignee: true } }),
getCurrentAccountId: ref(1),
- 'inboxAssignableAgents/getAssignableAgents': ref(() => allAgentsData),
+ 'inboxAssignableAgents/getAssignableAgents': ref(getAssignableAgents),
};
const mergedGetters = { ...defaultGetters, ...overrides };
@@ -53,6 +54,24 @@ describe('useAgentsList', () => {
const { agentsList, assignableAgents } = useAgentsList();
expect(assignableAgents.value).toEqual(allAgentsData);
+ expect(
+ useMapGetter('inboxAssignableAgents/getAssignableAgents').value
+ ).toHaveBeenCalledWith(1, { includeAgentBots: false });
+ expect(agentsList.value[0]).toEqual(mockNoneAgent);
+ expect(agentsList.value.length).toBe(
+ formattedAgentsData.slice(1).length + 1
+ );
+ });
+
+ it('requests agent bots when explicitly included', () => {
+ const { agentsList, assignableAgents } = useAgentsList(true, {
+ includeAgentBots: true,
+ });
+
+ expect(assignableAgents.value).toEqual(allAgentsData);
+ expect(
+ useMapGetter('inboxAssignableAgents/getAssignableAgents').value
+ ).toHaveBeenCalledWith(1, { includeAgentBots: true });
expect(agentsList.value[0]).toEqual(mockNoneAgent);
expect(agentsList.value.length).toBe(
formattedAgentsData.slice(1).length + 1
diff --git a/app/javascript/dashboard/composables/useAgentsList.js b/app/javascript/dashboard/composables/useAgentsList.js
index 47e843be6..8e8ee5568 100644
--- a/app/javascript/dashboard/composables/useAgentsList.js
+++ b/app/javascript/dashboard/composables/useAgentsList.js
@@ -10,9 +10,14 @@ import {
* A composable function that provides a list of agents for assignment.
*
* @param {boolean} [includeNoneAgent=true] - Whether to include a 'None' agent option.
+ * @param {Object} [options] - Options for the assignable agents list.
+ * @param {boolean} [options.includeAgentBots=false] - Whether to include AgentBot assignees. Only pass this from surfaces that thread `assignee_type` through the assignment request.
* @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 currentUser = useMapGetter('getCurrentUser');
const currentChat = useMapGetter('getSelectedChat');
@@ -39,7 +44,9 @@ export function useAgentsList(includeNoneAgent = true) {
* @type {import('vue').ComputedRef
}
*/
const assignableAgents = computed(() => {
- return inboxId.value ? assignable.value(inboxId.value) : [];
+ return inboxId.value
+ ? assignable.value(inboxId.value, { includeAgentBots })
+ : [];
});
/**
diff --git a/app/javascript/dashboard/helper/agentHelper.js b/app/javascript/dashboard/helper/agentHelper.js
index d521e7241..ff1123f66 100644
--- a/app/javascript/dashboard/helper/agentHelper.js
+++ b/app/javascript/dashboard/helper/agentHelper.js
@@ -38,7 +38,7 @@ export const getAgentsByUpdatedPresence = (
currentAccountId
) => {
const agentsWithDynamicPresenceUpdate = agents.map(item =>
- item.id === currentUser.id
+ item.id === currentUser.id && (item.assignee_type || 'User') === 'User'
? {
...item,
availability_status: currentUser.accounts.find(
diff --git a/app/javascript/dashboard/routes/dashboard/conversation/ConversationAction.vue b/app/javascript/dashboard/routes/dashboard/conversation/ConversationAction.vue
index fede6e3cb..1e33a192b 100644
--- a/app/javascript/dashboard/routes/dashboard/conversation/ConversationAction.vue
+++ b/app/javascript/dashboard/routes/dashboard/conversation/ConversationAction.vue
@@ -25,7 +25,7 @@ export default {
},
},
setup() {
- const { agentsList } = useAgentsList();
+ const { agentsList } = useAgentsList(true, { includeAgentBots: true });
return {
agentsList,
};
@@ -81,18 +81,27 @@ export default {
},
assignedAgent: {
get() {
- return this.currentChat.meta.assignee;
+ const assignee = this.currentChat.meta.assignee;
+ return (
+ assignee && {
+ ...assignee,
+ assignee_type: this.currentChat.meta.assignee_type || 'User',
+ }
+ );
},
set(agent) {
const agentId = agent ? agent.id : null;
+ const assigneeType = agent ? agent.assignee_type || 'User' : null;
this.$store.dispatch('setCurrentChatAssignee', {
conversationId: this.currentChat.id,
assignee: agent,
+ assigneeType,
});
this.$store
.dispatch('assignAgent', {
conversationId: this.currentChat.id,
agentId,
+ assigneeType,
})
.then(() => {
useAlert(this.$t('CONVERSATION.CHANGE_AGENT'));
@@ -152,7 +161,10 @@ export default {
if (!this.assignedAgent) {
return true;
}
- if (this.assignedAgent.id !== this.currentUser.id) {
+ if (
+ this.assignedAgent.id !== this.currentUser.id ||
+ (this.assignedAgent.assignee_type || 'User') !== 'User'
+ ) {
return true;
}
return false;
@@ -183,7 +195,11 @@ export default {
this.assignedAgent = selfAssign;
},
onClickAssignAgent(selectedItem) {
- if (this.assignedAgent && this.assignedAgent.id === selectedItem.id) {
+ if (
+ this.assignedAgent?.id === selectedItem.id &&
+ (this.assignedAgent?.assignee_type || 'User') ===
+ (selectedItem.assignee_type || 'User')
+ ) {
this.assignedAgent = null;
} else {
this.assignedAgent = selectedItem;
diff --git a/app/javascript/dashboard/store/modules/conversations/actions.js b/app/javascript/dashboard/store/modules/conversations/actions.js
index 72ab8fa5e..f8fdecc36 100644
--- a/app/javascript/dashboard/store/modules/conversations/actions.js
+++ b/app/javascript/dashboard/store/modules/conversations/actions.js
@@ -208,23 +208,31 @@ const actions = {
}
},
- assignAgent: async ({ dispatch }, { conversationId, agentId }) => {
+ assignAgent: async (
+ { dispatch },
+ { conversationId, agentId, assigneeType }
+ ) => {
try {
const response = await ConversationApi.assignAgent({
conversationId,
agentId,
+ assigneeType,
});
dispatch('setCurrentChatAssignee', {
conversationId,
assignee: response.data,
+ assigneeType,
});
} catch (error) {
// Handle error
}
},
- setCurrentChatAssignee({ commit }, { conversationId, assignee }) {
- commit(types.ASSIGN_AGENT, { conversationId, assignee });
+ setCurrentChatAssignee(
+ { commit },
+ { conversationId, assignee, assigneeType }
+ ) {
+ commit(types.ASSIGN_AGENT, { conversationId, assignee, assigneeType });
},
assignTeam: async ({ dispatch }, { conversationId, teamId }) => {
diff --git a/app/javascript/dashboard/store/modules/conversations/index.js b/app/javascript/dashboard/store/modules/conversations/index.js
index 8a13940c0..4f539e22d 100644
--- a/app/javascript/dashboard/store/modules/conversations/index.js
+++ b/app/javascript/dashboard/store/modules/conversations/index.js
@@ -108,10 +108,11 @@ export const mutations = {
}
},
- [types.ASSIGN_AGENT](_state, { conversationId, assignee }) {
+ [types.ASSIGN_AGENT](_state, { conversationId, assignee, assigneeType }) {
const chat = getConversationById(_state)(conversationId);
if (chat) {
chat.meta.assignee = assignee;
+ chat.meta.assignee_type = assigneeType;
}
},
diff --git a/app/javascript/dashboard/store/modules/inboxAssignableAgents.js b/app/javascript/dashboard/store/modules/inboxAssignableAgents.js
index 1b129e3ef..6dbee9ea8 100644
--- a/app/javascript/dashboard/store/modules/inboxAssignableAgents.js
+++ b/app/javascript/dashboard/store/modules/inboxAssignableAgents.js
@@ -7,31 +7,52 @@ const state = {
},
};
+const recordKey = (inboxId, { includeAgentBots = false } = {}) =>
+ includeAgentBots ? `${inboxId}:with_agent_bots` : inboxId;
+
export const types = {
SET_INBOX_ASSIGNABLE_AGENTS_UI_FLAG: 'SET_INBOX_ASSIGNABLE_AGENTS_UI_FLAG',
SET_INBOX_ASSIGNABLE_AGENTS: 'SET_INBOX_ASSIGNABLE_AGENTS',
};
export const getters = {
- getAssignableAgents: $state => inboxId => {
- const allAgents = $state.records[inboxId] || [];
- const verifiedAgents = allAgents.filter(record => record.confirmed);
- return verifiedAgents;
- },
+ getAssignableAgents:
+ $state =>
+ (inboxId, options = {}) => {
+ const includeAgentBots = options.includeAgentBots || false;
+ const allAgents = $state.records[recordKey(inboxId, options)] || [];
+ const verifiedAgents = allAgents.filter(
+ record =>
+ record.confirmed ||
+ (includeAgentBots && record.assignee_type === 'AgentBot')
+ );
+ return verifiedAgents;
+ },
getUIFlags($state) {
return $state.uiFlags;
},
};
export const actions = {
- async fetch({ commit }, inboxIds) {
+ async fetch({ commit }, actionPayload) {
+ const inboxIds = Array.isArray(actionPayload)
+ ? actionPayload
+ : actionPayload.inboxIds;
+ const includeAgentBots =
+ !Array.isArray(actionPayload) && actionPayload.includeAgentBots;
commit(types.SET_INBOX_ASSIGNABLE_AGENTS_UI_FLAG, { isFetching: true });
try {
const {
data: { payload },
- } = await AssignableAgentsAPI.get(inboxIds);
+ } = await AssignableAgentsAPI.get(inboxIds, { includeAgentBots });
+ if (includeAgentBots) {
+ commit(types.SET_INBOX_ASSIGNABLE_AGENTS, {
+ inboxId: inboxIds.join(','),
+ members: payload,
+ });
+ }
commit(types.SET_INBOX_ASSIGNABLE_AGENTS, {
- inboxId: inboxIds.join(','),
+ inboxId: recordKey(inboxIds.join(','), { includeAgentBots }),
members: payload,
});
} catch (error) {
diff --git a/app/javascript/dashboard/store/modules/specs/conversations/actions.spec.js b/app/javascript/dashboard/store/modules/specs/conversations/actions.spec.js
index fa052ec1b..5014b63ca 100644
--- a/app/javascript/dashboard/store/modules/specs/conversations/actions.spec.js
+++ b/app/javascript/dashboard/store/modules/specs/conversations/actions.spec.js
@@ -357,11 +357,12 @@ describe('#actions', () => {
});
await actions.assignAgent(
{ dispatch },
- { conversationId: 1, agentId: 1 }
+ { conversationId: 1, agentId: 1, assigneeType: 'AgentBot' }
);
expect(dispatch).toHaveBeenCalledWith('setCurrentChatAssignee', {
conversationId: 1,
assignee: { id: 1, name: 'User' },
+ assigneeType: 'AgentBot',
});
});
});
@@ -371,6 +372,7 @@ describe('#actions', () => {
const payload = {
conversationId: 1,
assignee: { id: 1, name: 'User' },
+ assigneeType: 'AgentBot',
};
await actions.setCurrentChatAssignee({ commit }, payload);
expect(commit).toHaveBeenCalledTimes(1);
diff --git a/app/javascript/dashboard/store/modules/specs/conversations/mutations.spec.js b/app/javascript/dashboard/store/modules/specs/conversations/mutations.spec.js
index fc1c61b35..a97bdad53 100644
--- a/app/javascript/dashboard/store/modules/specs/conversations/mutations.spec.js
+++ b/app/javascript/dashboard/store/modules/specs/conversations/mutations.spec.js
@@ -712,8 +712,10 @@ describe('#mutations', () => {
mutations[types.ASSIGN_AGENT](state, {
conversationId: 1,
assignee,
+ assigneeType: 'AgentBot',
});
expect(state.allConversations[0].meta.assignee).toEqual(assignee);
+ expect(state.allConversations[0].meta.assignee_type).toEqual('AgentBot');
expect(state.allConversations[1].meta.assignee).toBeUndefined();
});
});
diff --git a/app/javascript/dashboard/store/modules/specs/inboxAssignableMembers/actions.spec.js b/app/javascript/dashboard/store/modules/specs/inboxAssignableMembers/actions.spec.js
index eac8e7d08..bda8f1c6e 100644
--- a/app/javascript/dashboard/store/modules/specs/inboxAssignableMembers/actions.spec.js
+++ b/app/javascript/dashboard/store/modules/specs/inboxAssignableMembers/actions.spec.js
@@ -7,12 +7,21 @@ global.axios = axios;
vi.mock('axios');
describe('#actions', () => {
+ beforeEach(() => {
+ vi.clearAllMocks();
+ });
+
describe('#fetch', () => {
it('sends correct actions if API is success', async () => {
axios.get.mockResolvedValue({
data: { payload: agentsData },
});
await actions.fetch({ commit }, [1]);
+ expect(axios.get).toHaveBeenCalledWith('/api/v1/assignable_agents', {
+ params: {
+ inbox_ids: [1],
+ },
+ });
expect(commit.mock.calls).toEqual([
[types.SET_INBOX_ASSIGNABLE_AGENTS_UI_FLAG, { isFetching: true }],
[
@@ -24,13 +33,37 @@ describe('#actions', () => {
});
it('sends correct actions if API is error', async () => {
axios.get.mockRejectedValue({ message: 'Incorrect header' });
- await expect(actions.fetch({ commit }, { inboxId: 1 })).rejects.toThrow(
- Error
- );
+ await expect(actions.fetch({ commit }, [1])).rejects.toThrow(Error);
expect(commit.mock.calls).toEqual([
[types.SET_INBOX_ASSIGNABLE_AGENTS_UI_FLAG, { isFetching: true }],
[types.SET_INBOX_ASSIGNABLE_AGENTS_UI_FLAG, { isFetching: false }],
]);
});
+
+ it('requests agent bots only when opted in', async () => {
+ axios.get.mockResolvedValue({
+ data: { payload: agentsData },
+ });
+
+ await actions.fetch(
+ { commit },
+ { inboxIds: [1], includeAgentBots: true }
+ );
+
+ expect(axios.get).toHaveBeenCalledWith('/api/v1/assignable_agents', {
+ params: {
+ inbox_ids: [1],
+ include_agent_bots: true,
+ },
+ });
+ expect(commit).toHaveBeenCalledWith(types.SET_INBOX_ASSIGNABLE_AGENTS, {
+ inboxId: '1',
+ members: agentsData,
+ });
+ expect(commit).toHaveBeenCalledWith(types.SET_INBOX_ASSIGNABLE_AGENTS, {
+ inboxId: '1:with_agent_bots',
+ members: agentsData,
+ });
+ });
});
});
diff --git a/app/javascript/dashboard/store/modules/specs/inboxAssignableMembers/getters.spec.js b/app/javascript/dashboard/store/modules/specs/inboxAssignableMembers/getters.spec.js
index ac287e2b2..744bcbccf 100644
--- a/app/javascript/dashboard/store/modules/specs/inboxAssignableMembers/getters.spec.js
+++ b/app/javascript/dashboard/store/modules/specs/inboxAssignableMembers/getters.spec.js
@@ -1,4 +1,4 @@
-import { getters } from '../../teamMembers';
+import { getters } from '../../inboxAssignableAgents';
import agentsData from './fixtures';
describe('#getters', () => {
@@ -8,7 +8,26 @@ describe('#getters', () => {
1: [agentsData[0]],
},
};
- expect(getters.getTeamMembers(state)(1)).toEqual([agentsData[0]]);
+ expect(getters.getAssignableAgents(state)(1)).toEqual([agentsData[0]]);
+ });
+
+ it('keeps agent bots scoped to bot-inclusive lists', () => {
+ const agentBot = {
+ id: 1,
+ name: 'Captain',
+ assignee_type: 'AgentBot',
+ };
+ const state = {
+ records: {
+ 1: [agentBot, agentsData[0]],
+ '1:with_agent_bots': [agentBot, agentsData[0]],
+ },
+ };
+
+ expect(getters.getAssignableAgents(state)(1)).toEqual([agentsData[0]]);
+ expect(
+ getters.getAssignableAgents(state)(1, { includeAgentBots: true })
+ ).toEqual([agentBot, agentsData[0]]);
});
it('getUIFlags', () => {
diff --git a/app/javascript/shared/components/ui/MultiselectDropdown.vue b/app/javascript/shared/components/ui/MultiselectDropdown.vue
index 898f89db4..0f775c679 100644
--- a/app/javascript/shared/components/ui/MultiselectDropdown.vue
+++ b/app/javascript/shared/components/ui/MultiselectDropdown.vue
@@ -63,6 +63,14 @@ const hasValue = computed(() => {
const hasIcon = computed(() => {
return props.selectedItem?.icon || false;
});
+
+const isAgentBot = computed(
+ () => props.selectedItem?.assignee_type === 'AgentBot'
+);
+
+const selectedThumbnail = computed(
+ () => props.selectedItem?.thumbnail || props.selectedItem?.avatar_url
+);
@@ -93,16 +101,25 @@ const hasIcon = computed(() => {