feat: Add a view for mentions (#3505)
- Added a new table mentions for saving user mentions - Added a filter conversation_type in the API - Added a view to see the mentions
This commit is contained in:
@@ -6,7 +6,15 @@ class ConversationApi extends ApiClient {
|
||||
super('conversations', { accountScoped: true });
|
||||
}
|
||||
|
||||
get({ inboxId, status, assigneeType, page, labels, teamId }) {
|
||||
get({
|
||||
inboxId,
|
||||
status,
|
||||
assigneeType,
|
||||
page,
|
||||
labels,
|
||||
teamId,
|
||||
conversationType,
|
||||
}) {
|
||||
return axios.get(this.url, {
|
||||
params: {
|
||||
inbox_id: inboxId,
|
||||
@@ -15,6 +23,7 @@ class ConversationApi extends ApiClient {
|
||||
assignee_type: assigneeType,
|
||||
page,
|
||||
labels,
|
||||
conversation_type: conversationType,
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -74,7 +83,7 @@ class ConversationApi extends ApiClient {
|
||||
return axios.post(`${this.url}/${conversationId}/unmute`);
|
||||
}
|
||||
|
||||
meta({ inboxId, status, assigneeType, labels, teamId }) {
|
||||
meta({ inboxId, status, assigneeType, labels, teamId, conversationType }) {
|
||||
return axios.get(`${this.url}/meta`, {
|
||||
params: {
|
||||
inbox_id: inboxId,
|
||||
@@ -82,6 +91,7 @@ class ConversationApi extends ApiClient {
|
||||
assignee_type: assigneeType,
|
||||
labels,
|
||||
team_id: teamId,
|
||||
conversation_type: conversationType,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -51,6 +51,7 @@ l<template>
|
||||
:active-label="label"
|
||||
:team-id="teamId"
|
||||
:chat="chat"
|
||||
:conversation-type="conversationType"
|
||||
:show-assignee="showAssigneeInConversationCard"
|
||||
/>
|
||||
|
||||
@@ -133,6 +134,10 @@ export default {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
conversationType: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -203,6 +208,9 @@ export default {
|
||||
page: this.currentPage + 1,
|
||||
labels: this.label ? [this.label] : undefined,
|
||||
teamId: this.teamId ? this.teamId : undefined,
|
||||
conversationType: this.conversationType
|
||||
? this.conversationType
|
||||
: undefined,
|
||||
};
|
||||
},
|
||||
pageTitle() {
|
||||
@@ -215,6 +223,9 @@ export default {
|
||||
if (this.label) {
|
||||
return `#${this.label}`;
|
||||
}
|
||||
if (this.conversationType === 'mention') {
|
||||
return this.$t('CHAT_LIST.MENTION_HEADING');
|
||||
}
|
||||
return this.$t('CHAT_LIST.TAB_HEADING');
|
||||
},
|
||||
conversationList() {
|
||||
@@ -251,6 +262,9 @@ export default {
|
||||
label() {
|
||||
this.resetAndFetchData();
|
||||
},
|
||||
conversationType() {
|
||||
this.resetAndFetchData();
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.$store.dispatch('setChatFilter', this.activeStatus);
|
||||
|
||||
@@ -12,6 +12,8 @@ const conversations = accountId => ({
|
||||
'conversations_through_label',
|
||||
'team_conversations',
|
||||
'conversations_through_team',
|
||||
'conversation_mentions',
|
||||
'conversation_through_mentions',
|
||||
],
|
||||
menuItems: [
|
||||
{
|
||||
@@ -22,6 +24,13 @@ const conversations = accountId => ({
|
||||
toolTip: 'Conversation from all subscribed inboxes',
|
||||
toStateName: 'home',
|
||||
},
|
||||
{
|
||||
icon: 'mention',
|
||||
label: 'MENTIONED_CONVERSATIONS',
|
||||
key: 'conversation_mentions',
|
||||
toState: frontendURL(`accounts/${accountId}/mentions/conversations`),
|
||||
toStateName: 'conversation_mentions',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { mount } from '@vue/test-utils';
|
||||
import { shallowMount } from '@vue/test-utils';
|
||||
import SidemenuIcon from '../SidemenuIcon';
|
||||
|
||||
describe('SidemenuIcon', () => {
|
||||
test('matches snapshot', () => {
|
||||
const wrapper = mount(SidemenuIcon);
|
||||
const wrapper = shallowMount(SidemenuIcon);
|
||||
expect(wrapper.vm).toBeTruthy();
|
||||
expect(wrapper.element).toMatchSnapshot();
|
||||
});
|
||||
|
||||
@@ -133,6 +133,10 @@ export default {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
conversationType: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
|
||||
computed: {
|
||||
@@ -243,6 +247,7 @@ export default {
|
||||
id: chat.id,
|
||||
label: this.activeLabel,
|
||||
teamId: this.teamId,
|
||||
conversationType: this.conversationType,
|
||||
});
|
||||
router.push({ path: frontendURL(path) });
|
||||
},
|
||||
|
||||
@@ -5,7 +5,7 @@ import VTooltip from 'v-tooltip';
|
||||
|
||||
import Button from 'dashboard/components/buttons/Button';
|
||||
import i18n from 'dashboard/i18n';
|
||||
|
||||
import FluentIcon from 'shared/components/FluentIcon/DashboardIcon';
|
||||
import MoreActions from '../MoreActions';
|
||||
|
||||
const localVue = createLocalVue();
|
||||
@@ -13,6 +13,7 @@ localVue.use(Vuex);
|
||||
localVue.use(VueI18n);
|
||||
localVue.use(VTooltip);
|
||||
|
||||
localVue.component('fluent-icon', FluentIcon);
|
||||
localVue.component('woot-button', Button);
|
||||
|
||||
const i18nConfig = new VueI18n({
|
||||
|
||||
@@ -11,17 +11,19 @@ export const conversationUrl = ({
|
||||
id,
|
||||
label,
|
||||
teamId,
|
||||
conversationType = '',
|
||||
}) => {
|
||||
let url = `accounts/${accountId}/conversations/${id}`;
|
||||
if (activeInbox) {
|
||||
return `accounts/${accountId}/inbox/${activeInbox}/conversations/${id}`;
|
||||
url = `accounts/${accountId}/inbox/${activeInbox}/conversations/${id}`;
|
||||
} else if (label) {
|
||||
url = `accounts/${accountId}/label/${label}/conversations/${id}`;
|
||||
} else if (teamId) {
|
||||
url = `accounts/${accountId}/team/${teamId}/conversations/${id}`;
|
||||
} else if (conversationType === 'mention') {
|
||||
url = `accounts/${accountId}/mentions/conversations/${id}`;
|
||||
}
|
||||
if (label) {
|
||||
return `accounts/${accountId}/label/${label}/conversations/${id}`;
|
||||
}
|
||||
if (teamId) {
|
||||
return `accounts/${accountId}/team/${teamId}/conversations/${id}`;
|
||||
}
|
||||
return `accounts/${accountId}/conversations/${id}`;
|
||||
return url;
|
||||
};
|
||||
|
||||
export const accountIdFromPathname = pathname => {
|
||||
|
||||
@@ -21,6 +21,7 @@ class ActionCableConnector extends BaseActionCableConnector {
|
||||
'presence.update': this.onPresenceUpdate,
|
||||
'contact.deleted': this.onContactDelete,
|
||||
'contact.updated': this.onContactUpdate,
|
||||
'conversation.mentioned': this.onConversationMentioned,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -97,6 +98,10 @@ class ActionCableConnector extends BaseActionCableConnector {
|
||||
});
|
||||
};
|
||||
|
||||
onConversationMentioned = data => {
|
||||
this.app.$store.dispatch('addMentions', data);
|
||||
};
|
||||
|
||||
clearTimer = conversationId => {
|
||||
const timerEvent = this.CancelTyping[conversationId];
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
"404": "There are no active conversations in this group."
|
||||
},
|
||||
"TAB_HEADING": "Conversations",
|
||||
"MENTION_HEADING": "Mentions",
|
||||
"SEARCH": {
|
||||
"INPUT": "Search for People, Chats, Saved Replies .."
|
||||
},
|
||||
|
||||
@@ -136,6 +136,7 @@
|
||||
"SIDEBAR": {
|
||||
"CONVERSATIONS": "Conversations",
|
||||
"ALL_CONVERSATIONS": "All Conversations",
|
||||
"MENTIONED_CONVERSATIONS": "Mentions",
|
||||
"REPORTS": "Reports",
|
||||
"SETTINGS": "Settings",
|
||||
"CONTACTS": "Contacts",
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
:conversation-inbox="inboxId"
|
||||
:label="label"
|
||||
:team-id="teamId"
|
||||
:conversation-type="conversationType"
|
||||
@conversation-load="onConversationLoad"
|
||||
>
|
||||
<pop-over-search />
|
||||
@@ -49,6 +50,10 @@ export default {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
conversationType: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
@@ -13,15 +13,6 @@ export default {
|
||||
return { inboxId: 0 };
|
||||
},
|
||||
},
|
||||
{
|
||||
path: frontendURL('accounts/:accountId/inbox/:inbox_id'),
|
||||
name: 'inbox_dashboard',
|
||||
roles: ['administrator', 'agent'],
|
||||
component: ConversationView,
|
||||
props: route => {
|
||||
return { inboxId: route.params.inbox_id };
|
||||
},
|
||||
},
|
||||
{
|
||||
path: frontendURL('accounts/:accountId/conversations/:conversation_id'),
|
||||
name: 'inbox_conversation',
|
||||
@@ -31,6 +22,15 @@ export default {
|
||||
return { inboxId: 0, conversationId: route.params.conversation_id };
|
||||
},
|
||||
},
|
||||
{
|
||||
path: frontendURL('accounts/:accountId/inbox/:inbox_id'),
|
||||
name: 'inbox_dashboard',
|
||||
roles: ['administrator', 'agent'],
|
||||
component: ConversationView,
|
||||
props: route => {
|
||||
return { inboxId: route.params.inbox_id };
|
||||
},
|
||||
},
|
||||
{
|
||||
path: frontendURL(
|
||||
'accounts/:accountId/inbox/:inbox_id/conversations/:conversation_id'
|
||||
@@ -83,5 +83,24 @@ export default {
|
||||
teamId: route.params.teamId,
|
||||
}),
|
||||
},
|
||||
{
|
||||
path: frontendURL('accounts/:accountId/mentions/conversations'),
|
||||
name: 'conversation_mentions',
|
||||
roles: ['administrator', 'agent'],
|
||||
component: ConversationView,
|
||||
props: () => ({ conversationType: 'mention' }),
|
||||
},
|
||||
{
|
||||
path: frontendURL(
|
||||
'accounts/:accountId/mentions/conversations/:conversationId'
|
||||
),
|
||||
name: 'conversation_through_mentions',
|
||||
roles: ['administrator', 'agent'],
|
||||
component: ConversationView,
|
||||
props: route => ({
|
||||
conversationId: route.params.conversationId,
|
||||
conversationType: 'mention',
|
||||
}),
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@@ -4,43 +4,11 @@ import ConversationApi from '../../../api/inbox/conversation';
|
||||
import MessageApi from '../../../api/inbox/message';
|
||||
import { MESSAGE_STATUS, MESSAGE_TYPE } from 'shared/constants/messages';
|
||||
import { createPendingMessage } from 'dashboard/helper/commons';
|
||||
import {
|
||||
buildConversationList,
|
||||
isOnMentionsView,
|
||||
} from './helpers/actionHelpers';
|
||||
|
||||
const setPageFilter = ({ dispatch, filter, page, markEndReached }) => {
|
||||
dispatch('conversationPage/setCurrentPage', { filter, page }, { root: true });
|
||||
if (markEndReached) {
|
||||
dispatch('conversationPage/setEndReached', { filter }, { root: true });
|
||||
}
|
||||
};
|
||||
|
||||
const setContacts = (commit, chatList) => {
|
||||
commit(
|
||||
`contacts/${types.SET_CONTACTS}`,
|
||||
chatList.map(chat => chat.meta.sender)
|
||||
);
|
||||
};
|
||||
|
||||
const buildConversationList = (
|
||||
context,
|
||||
requestPayload,
|
||||
responseData,
|
||||
filterType
|
||||
) => {
|
||||
const { payload: conversationList, meta: metaData } = responseData;
|
||||
context.commit(types.SET_ALL_CONVERSATION, conversationList);
|
||||
context.dispatch('conversationStats/set', metaData);
|
||||
context.dispatch(
|
||||
'conversationLabels/setBulkConversationLabels',
|
||||
conversationList
|
||||
);
|
||||
context.commit(types.CLEAR_LIST_LOADING_STATUS);
|
||||
setContacts(context.commit, conversationList);
|
||||
setPageFilter({
|
||||
dispatch: context.dispatch,
|
||||
filter: filterType,
|
||||
page: requestPayload.page,
|
||||
markEndReached: !conversationList.length,
|
||||
});
|
||||
};
|
||||
// actions
|
||||
const actions = {
|
||||
getConversation: async ({ commit }, conversationId) => {
|
||||
@@ -233,21 +201,32 @@ const actions = {
|
||||
}
|
||||
},
|
||||
|
||||
addConversation({ commit, state, dispatch }, conversation) {
|
||||
addConversation({ commit, state, dispatch, rootState }, conversation) {
|
||||
const { currentInbox, appliedFilters } = state;
|
||||
const {
|
||||
inbox_id: inboxId,
|
||||
meta: { sender },
|
||||
} = conversation;
|
||||
|
||||
const hasAppliedFilters = !!appliedFilters.length;
|
||||
const isMatchingInboxFilter =
|
||||
!currentInbox || Number(currentInbox) === inboxId;
|
||||
if (!hasAppliedFilters && isMatchingInboxFilter) {
|
||||
if (
|
||||
!hasAppliedFilters &&
|
||||
!isOnMentionsView(rootState) &&
|
||||
isMatchingInboxFilter
|
||||
) {
|
||||
commit(types.ADD_CONVERSATION, conversation);
|
||||
dispatch('contacts/setContact', sender);
|
||||
}
|
||||
},
|
||||
|
||||
addMentions({ dispatch, rootState }, conversation) {
|
||||
if (isOnMentionsView(rootState)) {
|
||||
dispatch('updateConversation', conversation);
|
||||
}
|
||||
},
|
||||
|
||||
updateConversation({ commit, dispatch }, conversation) {
|
||||
const {
|
||||
meta: { sender },
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
import types from '../../../mutation-types';
|
||||
|
||||
export const setPageFilter = ({ dispatch, filter, page, markEndReached }) => {
|
||||
dispatch('conversationPage/setCurrentPage', { filter, page }, { root: true });
|
||||
if (markEndReached) {
|
||||
dispatch('conversationPage/setEndReached', { filter }, { root: true });
|
||||
}
|
||||
};
|
||||
|
||||
export const setContacts = (commit, chatList) => {
|
||||
commit(
|
||||
`contacts/${types.SET_CONTACTS}`,
|
||||
chatList.map(chat => chat.meta.sender)
|
||||
);
|
||||
};
|
||||
|
||||
export const isOnMentionsView = ({ route: { name: routeName } }) => {
|
||||
const MENTION_ROUTES = [
|
||||
'conversation_mentions',
|
||||
'conversation_through_mentions',
|
||||
];
|
||||
return MENTION_ROUTES.includes(routeName);
|
||||
};
|
||||
|
||||
export const buildConversationList = (
|
||||
context,
|
||||
requestPayload,
|
||||
responseData,
|
||||
filterType
|
||||
) => {
|
||||
const { payload: conversationList, meta: metaData } = responseData;
|
||||
context.commit(types.SET_ALL_CONVERSATION, conversationList);
|
||||
context.dispatch('conversationStats/set', metaData);
|
||||
context.dispatch(
|
||||
'conversationLabels/setBulkConversationLabels',
|
||||
conversationList
|
||||
);
|
||||
context.commit(types.CLEAR_LIST_LOADING_STATUS);
|
||||
setContacts(context.commit, conversationList);
|
||||
setPageFilter({
|
||||
dispatch: context.dispatch,
|
||||
filter: filterType,
|
||||
page: requestPayload.page,
|
||||
markEndReached: !conversationList.length,
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,12 @@
|
||||
import { isOnMentionsView } from '../actionHelpers';
|
||||
|
||||
describe('#isOnMentionsView', () => {
|
||||
it('return valid responses when passing the state', () => {
|
||||
expect(isOnMentionsView({ route: { name: 'conversation_mentions' } })).toBe(
|
||||
true
|
||||
);
|
||||
expect(isOnMentionsView({ route: { name: 'conversation_messages' } })).toBe(
|
||||
false
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -59,7 +59,10 @@ describe('#actions', () => {
|
||||
messages: [],
|
||||
meta: { sender: { id: 1, name: 'john-doe' } },
|
||||
};
|
||||
actions.updateConversation({ commit, dispatch }, conversation);
|
||||
actions.updateConversation(
|
||||
{ commit, rootState: { route: { name: 'home' } }, dispatch },
|
||||
conversation
|
||||
);
|
||||
expect(commit.mock.calls).toEqual([
|
||||
[types.UPDATE_CONVERSATION, conversation],
|
||||
]);
|
||||
@@ -86,6 +89,7 @@ describe('#actions', () => {
|
||||
actions.addConversation(
|
||||
{
|
||||
commit,
|
||||
rootState: { route: { name: 'home' } },
|
||||
dispatch,
|
||||
state: { currentInbox: 1, appliedFilters: [] },
|
||||
},
|
||||
@@ -105,6 +109,27 @@ describe('#actions', () => {
|
||||
actions.addConversation(
|
||||
{
|
||||
commit,
|
||||
rootState: { route: { name: 'home' } },
|
||||
dispatch,
|
||||
state: { currentInbox: 1, appliedFilters: [{ id: 'random-filter' }] },
|
||||
},
|
||||
conversation
|
||||
);
|
||||
expect(commit.mock.calls).toEqual([]);
|
||||
expect(dispatch.mock.calls).toEqual([]);
|
||||
});
|
||||
|
||||
it('doesnot send mutation if the view is conversation mentions', () => {
|
||||
const conversation = {
|
||||
id: 1,
|
||||
messages: [],
|
||||
meta: { sender: { id: 1, name: 'john-doe' } },
|
||||
inbox_id: 1,
|
||||
};
|
||||
actions.addConversation(
|
||||
{
|
||||
commit,
|
||||
rootState: { route: { name: 'conversation_mentions' } },
|
||||
dispatch,
|
||||
state: { currentInbox: 1, appliedFilters: [{ id: 'random-filter' }] },
|
||||
},
|
||||
@@ -124,6 +149,7 @@ describe('#actions', () => {
|
||||
actions.addConversation(
|
||||
{
|
||||
commit,
|
||||
rootState: { route: { name: 'home' } },
|
||||
dispatch,
|
||||
state: { currentInbox: 1, appliedFilters: [] },
|
||||
},
|
||||
@@ -151,7 +177,12 @@ describe('#actions', () => {
|
||||
inbox_id: 1,
|
||||
};
|
||||
actions.addConversation(
|
||||
{ commit, dispatch, state: { appliedFilters: [] } },
|
||||
{
|
||||
commit,
|
||||
rootState: { route: { name: 'home' } },
|
||||
dispatch,
|
||||
state: { appliedFilters: [] },
|
||||
},
|
||||
conversation
|
||||
);
|
||||
expect(commit.mock.calls).toEqual([
|
||||
@@ -379,3 +410,27 @@ describe('#deleteMessage', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#addMentions', () => {
|
||||
it('does not send mutations if the view is not mentions', () => {
|
||||
actions.addMentions(
|
||||
{ commit, dispatch, rootState: { route: { name: 'home' } } },
|
||||
{ id: 1 }
|
||||
);
|
||||
expect(commit.mock.calls).toEqual([]);
|
||||
expect(dispatch.mock.calls).toEqual([]);
|
||||
});
|
||||
|
||||
it('send mutations if the view is mentions', () => {
|
||||
actions.addMentions(
|
||||
{
|
||||
dispatch,
|
||||
rootState: { route: { name: 'conversation_mentions' } },
|
||||
},
|
||||
{ id: 1, meta: { sender: { id: 1 } } }
|
||||
);
|
||||
expect(dispatch.mock.calls).toEqual([
|
||||
['updateConversation', { id: 1, meta: { sender: { id: 1 } } }],
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user