From b823764199fb2b0b5462b4ffab249dcf21923160 Mon Sep 17 00:00:00 2001 From: Sojan Jose Date: Tue, 7 Jul 2026 01:55:54 -0700 Subject: [PATCH] refactor: optimize agent bot assignment dropdown (#14866) --- .../accounts/assignable_agents_controller.rb | 3 ++ .../dashboard/api/assignableAgents.js | 7 +++- .../dashboard/api/inbox/conversation.js | 3 +- .../api/specs/assignableAgents.spec.js | 10 +++++ .../api/specs/inbox/conversation.spec.js | 7 +++- .../widgets/conversation/ConversationBox.vue | 5 ++- .../widgets/conversation/ConversationCard.vue | 16 ++++++-- .../conversation/EmailTranscriptModal.vue | 8 +++- .../conversation/contextMenu/menuItem.vue | 11 ++++- .../composables/spec/useAgentsList.spec.js | 21 +++++++++- .../dashboard/composables/useAgentsList.js | 11 ++++- .../dashboard/helper/agentHelper.js | 2 +- .../conversation/ConversationAction.vue | 24 +++++++++-- .../store/modules/conversations/actions.js | 14 +++++-- .../store/modules/conversations/index.js | 3 +- .../store/modules/inboxAssignableAgents.js | 37 +++++++++++++---- .../specs/conversations/actions.spec.js | 4 +- .../specs/conversations/mutations.spec.js | 2 + .../inboxAssignableMembers/actions.spec.js | 39 ++++++++++++++++-- .../inboxAssignableMembers/getters.spec.js | 23 ++++++++++- .../components/ui/MultiselectDropdown.vue | 25 +++++++++-- .../ui/MultiselectDropdownItems.vue | 41 ++++++++++++++++--- .../assignable_agents/index.json.jbuilder | 15 ++++++- .../assignable_agents_controller_spec.rb | 18 ++++++++ 24 files changed, 302 insertions(+), 47 deletions(-) diff --git a/app/controllers/api/v1/accounts/assignable_agents_controller.rb b/app/controllers/api/v1/accounts/assignable_agents_controller.rb index a712342dd..29dcb62b2 100644 --- a/app/controllers/api/v1/accounts/assignable_agents_controller.rb +++ b/app/controllers/api/v1/accounts/assignable_agents_controller.rb @@ -2,6 +2,8 @@ class Api::V1::Accounts::AssignableAgentsController < Api::V1::Accounts::BaseCon before_action :fetch_inboxes def index + # TODO: Remove this opt-in once mobile clients support AgentBot assignees in this payload. + @include_agent_bots = params[:include_agent_bots].present? agent_ids = @inboxes.map do |inbox| authorize inbox, :show? member_ids = inbox.members.pluck(:user_id) @@ -10,6 +12,7 @@ class Api::V1::Accounts::AssignableAgentsController < Api::V1::Accounts::BaseCon agent_ids = agent_ids.inject(:&) agents = Current.account.users.where(id: agent_ids) @assignable_agents = (agents + Current.account.administrators).uniq + @agent_bots = @include_agent_bots ? AgentBot.accessible_to(Current.account) : [] end private diff --git a/app/javascript/dashboard/api/assignableAgents.js b/app/javascript/dashboard/api/assignableAgents.js index 5b999facf..febb05ff9 100644 --- a/app/javascript/dashboard/api/assignableAgents.js +++ b/app/javascript/dashboard/api/assignableAgents.js @@ -6,9 +6,12 @@ class AssignableAgents extends ApiClient { super('assignable_agents', { accountScoped: true }); } - get(inboxIds) { + get(inboxIds, { includeAgentBots = false } = {}) { return axios.get(this.url, { - params: { inbox_ids: inboxIds }, + params: { + inbox_ids: inboxIds, + ...(includeAgentBots ? { include_agent_bots: true } : {}), + }, }); } } diff --git a/app/javascript/dashboard/api/inbox/conversation.js b/app/javascript/dashboard/api/inbox/conversation.js index f94fca452..08820aac9 100644 --- a/app/javascript/dashboard/api/inbox/conversation.js +++ b/app/javascript/dashboard/api/inbox/conversation.js @@ -62,9 +62,10 @@ class ConversationApi extends ApiClient { }); } - assignAgent({ conversationId, agentId }) { + assignAgent({ conversationId, agentId, assigneeType }) { return axios.post(`${this.url}/${conversationId}/assignments`, { assignee_id: agentId, + assignee_type: assigneeType, }); } diff --git a/app/javascript/dashboard/api/specs/assignableAgents.spec.js b/app/javascript/dashboard/api/specs/assignableAgents.spec.js index d553d55cb..be00cf07f 100644 --- a/app/javascript/dashboard/api/specs/assignableAgents.spec.js +++ b/app/javascript/dashboard/api/specs/assignableAgents.spec.js @@ -26,5 +26,15 @@ describe('#AssignableAgentsAPI', () => { }, }); }); + + it('#getAssignableAgents with agent bots', () => { + assignableAgentsAPI.get([1], { includeAgentBots: true }); + expect(axiosMock.get).toHaveBeenCalledWith('/api/v1/assignable_agents', { + params: { + inbox_ids: [1], + include_agent_bots: true, + }, + }); + }); }); }); diff --git a/app/javascript/dashboard/api/specs/inbox/conversation.spec.js b/app/javascript/dashboard/api/specs/inbox/conversation.spec.js index de0d7a7d0..ea0ef3e75 100644 --- a/app/javascript/dashboard/api/specs/inbox/conversation.spec.js +++ b/app/javascript/dashboard/api/specs/inbox/conversation.spec.js @@ -90,11 +90,16 @@ describe('#ConversationAPI', () => { }); it('#assignAgent', () => { - conversationAPI.assignAgent({ conversationId: 12, agentId: 34 }); + conversationAPI.assignAgent({ + conversationId: 12, + agentId: 34, + assigneeType: 'AgentBot', + }); expect(axiosMock.post).toHaveBeenCalledWith( `/api/v1/conversations/12/assignments`, { assignee_id: 34, + assignee_type: 'AgentBot', } ); }); diff --git a/app/javascript/dashboard/components/widgets/conversation/ConversationBox.vue b/app/javascript/dashboard/components/widgets/conversation/ConversationBox.vue index 7d04470ed..a9304eaba 100644 --- a/app/javascript/dashboard/components/widgets/conversation/ConversationBox.vue +++ b/app/javascript/dashboard/components/widgets/conversation/ConversationBox.vue @@ -62,7 +62,10 @@ export default { immediate: true, handler(inboxId) { if (inboxId) { - this.$store.dispatch('inboxAssignableAgents/fetch', [inboxId]); + this.$store.dispatch('inboxAssignableAgents/fetch', { + inboxIds: [inboxId], + includeAgentBots: true, + }); } }, }, diff --git a/app/javascript/dashboard/components/widgets/conversation/ConversationCard.vue b/app/javascript/dashboard/components/widgets/conversation/ConversationCard.vue index a5dec806d..ca0d5fdf4 100644 --- a/app/javascript/dashboard/components/widgets/conversation/ConversationCard.vue +++ b/app/javascript/dashboard/components/widgets/conversation/ConversationCard.vue @@ -2,6 +2,7 @@ import { computed, ref, watch } from 'vue'; import { getLastMessage } from 'dashboard/helper/conversationHelper'; import Avatar from 'next/avatar/Avatar.vue'; +import Icon from 'dashboard/components-next/icon/Icon.vue'; import MessagePreview from './MessagePreview.vue'; import InboxName from '../InboxName.vue'; import TimeAgo from 'dashboard/components/ui/TimeAgo.vue'; @@ -57,6 +58,10 @@ const showMetaSection = computed(() => { ); }); +const isAgentBotAssignee = computed( + () => props.chat?.meta?.assignee_type === 'AgentBot' +); + const hasSlaPolicyId = computed( () => props.chat?.applied_sla?.id && !props.currentContact?.blocked ); @@ -159,10 +164,15 @@ watch( > - - {{ assignee.name }} + + {{ assignee.name }} -
+
+ > + + 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 +);