feat: Add a view for unattended conversations (#5890)

Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
This commit is contained in:
Nithin David Thomas
2022-11-29 08:18:00 -08:00
committed by GitHub
parent c94ba16565
commit 85b52a1d3f
20 changed files with 174 additions and 32 deletions

View File

@@ -335,10 +335,8 @@ export default {
status: this.activeStatus,
page: this.currentPage + 1,
labels: this.label ? [this.label] : undefined,
teamId: this.teamId ? this.teamId : undefined,
conversationType: this.conversationType
? this.conversationType
: undefined,
teamId: this.teamId || undefined,
conversationType: this.conversationType || undefined,
folders: this.hasActiveFolders ? this.savedFoldersValue : undefined,
};
},
@@ -355,6 +353,9 @@ export default {
if (this.conversationType === 'mention') {
return this.$t('CHAT_LIST.MENTION_HEADING');
}
if (this.conversationType === 'unattended') {
return this.$t('CHAT_LIST.UNATTENDED_HEADING');
}
if (this.hasActiveFolders) {
return this.activeFolder.name;
}

View File

@@ -16,6 +16,8 @@ const conversations = accountId => ({
'conversation_through_mentions',
'folder_conversations',
'conversations_through_folders',
'conversation_unattended',
'conversation_through_unattended',
],
menuItems: [
{
@@ -33,6 +35,13 @@ const conversations = accountId => ({
toState: frontendURL(`accounts/${accountId}/mentions/conversations`),
toStateName: 'conversation_mentions',
},
{
icon: 'mail-unread',
label: 'UNATTENDED_CONVERSATIONS',
key: 'conversation_unattended',
toState: frontendURL(`accounts/${accountId}/unattended/conversations`),
toStateName: 'conversation_unattended',
},
],
});

View File

@@ -56,6 +56,8 @@ export const conversationUrl = ({
url = `accounts/${accountId}/custom_view/${foldersId}/conversations/${id}`;
} else if (conversationType === 'mention') {
url = `accounts/${accountId}/mentions/conversations/${id}`;
} else if (conversationType === 'unattended') {
url = `accounts/${accountId}/unattended/conversations/${id}`;
}
return url;
};

View File

@@ -8,6 +8,7 @@
},
"TAB_HEADING": "Conversations",
"MENTION_HEADING": "Mentions",
"UNATTENDED_HEADING": "Unattended",
"SEARCH": {
"INPUT": "Search for People, Chats, Saved Replies .."
},

View File

@@ -177,6 +177,7 @@
"CONVERSATIONS": "Conversations",
"ALL_CONVERSATIONS": "All Conversations",
"MENTIONED_CONVERSATIONS": "Mentions",
"UNATTENDED_CONVERSATIONS": "Unattended",
"REPORTS": "Reports",
"SETTINGS": "Settings",
"CONTACTS": "Contacts",

View File

@@ -121,5 +121,25 @@ export default {
conversationType: 'mention',
}),
},
{
path: frontendURL('accounts/:accountId/unattended/conversations'),
name: 'conversation_unattended',
roles: ['administrator', 'agent'],
component: ConversationView,
props: () => ({ conversationType: 'unattended' }),
},
{
path: frontendURL(
'accounts/:accountId/unattended/conversations/:conversationId'
),
name: 'conversation_through_unattended',
roles: ['administrator', 'agent'],
component: ConversationView,
props: route => ({
conversationId: route.params.conversationId,
conversationType: 'unattended',
}),
},
],
};

View File

@@ -7,6 +7,7 @@ import { createPendingMessage } from 'dashboard/helper/commons';
import {
buildConversationList,
isOnMentionsView,
isOnUnattendedView,
} from './helpers/actionHelpers';
import messageReadActions from './actions/messageReadActions';
// actions
@@ -230,6 +231,7 @@ const actions = {
if (
!hasAppliedFilters &&
!isOnMentionsView(rootState) &&
!isOnUnattendedView(rootState) &&
isMatchingInboxFilter
) {
commit(types.ADD_CONVERSATION, conversation);
@@ -243,6 +245,12 @@ const actions = {
}
},
addUnattended({ dispatch, rootState }, conversation) {
if (isOnUnattendedView(rootState)) {
dispatch('updateConversation', conversation);
}
},
updateConversation({ commit, dispatch }, conversation) {
const {
meta: { sender },

View File

@@ -5,33 +5,54 @@ export const findPendingMessageIndex = (chat, message) => {
);
};
const filterByStatus = (chatStatus, filterStatus) =>
export const filterByStatus = (chatStatus, filterStatus) =>
filterStatus === 'all' ? true : chatStatus === filterStatus;
export const filterByInbox = (shouldFilter, inboxId, chatInboxId) => {
const isOnInbox = Number(inboxId) === chatInboxId;
return inboxId ? isOnInbox && shouldFilter : shouldFilter;
};
export const filterByTeam = (shouldFilter, teamId, chatTeamId) => {
const isOnTeam = Number(teamId) === chatTeamId;
return teamId ? isOnTeam && shouldFilter : shouldFilter;
};
export const filterByLabel = (shouldFilter, labels, chatLabels) => {
const isOnLabel = labels.every(label => chatLabels.includes(label));
return labels.length ? isOnLabel && shouldFilter : shouldFilter;
};
export const filterByUnattended = (
shouldFilter,
conversationType,
firstReplyOn
) => {
return conversationType === 'unattended'
? !firstReplyOn && shouldFilter
: shouldFilter;
};
export const applyPageFilters = (conversation, filters) => {
const { inboxId, status, labels = [], teamId } = filters;
const { inboxId, status, labels = [], teamId, conversationType } = filters;
const {
status: chatStatus,
inbox_id: chatInboxId,
labels: chatLabels = [],
meta = {},
first_reply_created_at: firstReplyOn,
} = conversation;
const team = meta.team || {};
const { id: chatTeamId } = team;
let shouldFilter = filterByStatus(chatStatus, status);
if (inboxId) {
const filterByInbox = Number(inboxId) === chatInboxId;
shouldFilter = shouldFilter && filterByInbox;
}
if (teamId) {
const filterByTeam = Number(teamId) === chatTeamId;
shouldFilter = shouldFilter && filterByTeam;
}
if (labels.length) {
const filterByLabels = labels.every(label => chatLabels.includes(label));
shouldFilter = shouldFilter && filterByLabels;
}
shouldFilter = filterByInbox(shouldFilter, inboxId, chatInboxId);
shouldFilter = filterByTeam(shouldFilter, teamId, chatTeamId);
shouldFilter = filterByLabel(shouldFilter, labels, chatLabels);
shouldFilter = filterByUnattended(
shouldFilter,
conversationType,
firstReplyOn
);
return shouldFilter;
};

View File

@@ -22,6 +22,14 @@ export const isOnMentionsView = ({ route: { name: routeName } }) => {
return MENTION_ROUTES.includes(routeName);
};
export const isOnUnattendedView = ({ route: { name: routeName } }) => {
const UNATTENDED_ROUTES = [
'conversation_unattended',
'conversation_through_unattended',
];
return UNATTENDED_ROUTES.includes(routeName);
};
export const buildConversationList = (
context,
requestPayload,

View File

@@ -1,6 +1,10 @@
import {
findPendingMessageIndex,
applyPageFilters,
filterByInbox,
filterByTeam,
filterByLabel,
filterByUnattended,
} from '../../conversations/helpers';
const conversationList = [
@@ -119,3 +123,52 @@ describe('#applyPageFilters', () => {
});
});
});
describe('#filterByInbox', () => {
it('returns true if conversation has inbox filter active', () => {
const inboxId = '1';
const chatInboxId = 1;
expect(filterByInbox(true, inboxId, chatInboxId)).toEqual(true);
});
it('returns false if inbox filter is not active', () => {
const inboxId = '1';
const chatInboxId = 13;
expect(filterByInbox(true, inboxId, chatInboxId)).toEqual(false);
});
});
describe('#filterByTeam', () => {
it('returns true if conversation has team and team filter is active', () => {
const [teamId, chatTeamId] = ['1', 1];
expect(filterByTeam(true, teamId, chatTeamId)).toEqual(true);
});
it('returns false if team filter is not active', () => {
const [teamId, chatTeamId] = ['1', 12];
expect(filterByTeam(true, teamId, chatTeamId)).toEqual(false);
});
});
describe('#filterByLabel', () => {
it('returns true if conversation has labels and labels filter is active', () => {
const labels = ['dev', 'cs'];
const chatLabels = ['dev', 'cs', 'sales'];
expect(filterByLabel(true, labels, chatLabels)).toEqual(true);
});
it('returns false if conversation has not all labels', () => {
const labels = ['dev', 'cs', 'sales'];
const chatLabels = ['cs', 'sales'];
expect(filterByLabel(true, labels, chatLabels)).toEqual(false);
});
});
describe('#filterByUnattended', () => {
it('returns true if conversation type is unattended and has no first reply', () => {
expect(filterByUnattended(true, 'unattended', undefined)).toEqual(true);
});
it('returns false if conversation type is not unattended and has no first reply', () => {
expect(filterByUnattended(false, 'mentions', undefined)).toEqual(false);
});
it('returns true if conversation type is unattended and has first reply', () => {
expect(filterByUnattended(true, 'mentions', 123)).toEqual(true);
});
});