fix: Hide deleted messages on widget side (#2614)
This commit is contained in:
parent
cf785123a5
commit
b56512eb56
13 changed files with 222 additions and 54 deletions
|
@ -63,7 +63,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="context-menu-wrap">
|
<div class="context-menu-wrap">
|
||||||
<context-menu
|
<context-menu
|
||||||
v-if="isBubble && !isMessageDeleted"
|
v-if="isBubble"
|
||||||
:is-open="showContextMenu"
|
:is-open="showContextMenu"
|
||||||
:show-copy="hasText"
|
:show-copy="hasText"
|
||||||
:menu-position="contextMenuPosition"
|
:menu-position="contextMenuPosition"
|
||||||
|
@ -207,9 +207,6 @@ export default {
|
||||||
hasText() {
|
hasText() {
|
||||||
return !!this.data.content;
|
return !!this.data.content;
|
||||||
},
|
},
|
||||||
isMessageDeleted() {
|
|
||||||
return this.contentAttributes.deleted;
|
|
||||||
},
|
|
||||||
sentByMessage() {
|
sentByMessage() {
|
||||||
const { sender } = this;
|
const { sender } = this;
|
||||||
return this.data.message_type === 1 && !isEmptyObject(sender)
|
return this.data.message_type === 1 && !isEmptyObject(sender)
|
||||||
|
|
|
@ -65,6 +65,7 @@ import FileBubble from 'widget/components/FileBubble';
|
||||||
import Thumbnail from 'dashboard/components/widgets/Thumbnail';
|
import Thumbnail from 'dashboard/components/widgets/Thumbnail';
|
||||||
import { MESSAGE_TYPE } from 'widget/helpers/constants';
|
import { MESSAGE_TYPE } from 'widget/helpers/constants';
|
||||||
import configMixin from '../mixins/configMixin';
|
import configMixin from '../mixins/configMixin';
|
||||||
|
import messageMixin from '../mixins/messageMixin';
|
||||||
import { isASubmittedFormMessage } from 'shared/helpers/MessageTypeHelper';
|
import { isASubmittedFormMessage } from 'shared/helpers/MessageTypeHelper';
|
||||||
export default {
|
export default {
|
||||||
name: 'AgentMessage',
|
name: 'AgentMessage',
|
||||||
|
@ -75,7 +76,7 @@ export default {
|
||||||
UserMessage,
|
UserMessage,
|
||||||
FileBubble,
|
FileBubble,
|
||||||
},
|
},
|
||||||
mixins: [timeMixin, configMixin],
|
mixins: [timeMixin, configMixin, messageMixin],
|
||||||
props: {
|
props: {
|
||||||
message: {
|
message: {
|
||||||
type: Object,
|
type: Object,
|
||||||
|
@ -94,11 +95,6 @@ export default {
|
||||||
if (!this.message.content) return false;
|
if (!this.message.content) return false;
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
hasAttachments() {
|
|
||||||
return !!(
|
|
||||||
this.message.attachments && this.message.attachments.length > 0
|
|
||||||
);
|
|
||||||
},
|
|
||||||
readableTime() {
|
readableTime() {
|
||||||
const { created_at: createdAt = '' } = this.message;
|
const { created_at: createdAt = '' } = this.message;
|
||||||
return this.messageStamp(createdAt, 'LLL d yyyy, h:mm a');
|
return this.messageStamp(createdAt, 'LLL d yyyy, h:mm a');
|
||||||
|
@ -111,10 +107,6 @@ export default {
|
||||||
const { content_type: type = '' } = this.message;
|
const { content_type: type = '' } = this.message;
|
||||||
return type;
|
return type;
|
||||||
},
|
},
|
||||||
messageContentAttributes() {
|
|
||||||
const { content_attributes: attribute = {} } = this.message;
|
|
||||||
return attribute;
|
|
||||||
},
|
|
||||||
agentName() {
|
agentName() {
|
||||||
if (this.message.message_type === MESSAGE_TYPE.TEMPLATE) {
|
if (this.message.message_type === MESSAGE_TYPE.TEMPLATE) {
|
||||||
return 'Bot';
|
return 'Bot';
|
||||||
|
@ -153,9 +145,8 @@ export default {
|
||||||
|
|
||||||
if (this.messageContentAttributes.submitted_values) {
|
if (this.messageContentAttributes.submitted_values) {
|
||||||
if (this.contentType === 'input_select') {
|
if (this.contentType === 'input_select') {
|
||||||
const [
|
const [selectionOption = {}] =
|
||||||
selectionOption = {},
|
this.messageContentAttributes.submitted_values;
|
||||||
] = this.messageContentAttributes.submitted_values;
|
|
||||||
return { content: selectionOption.title || selectionOption.value };
|
return { content: selectionOption.title || selectionOption.value };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,6 +37,7 @@ import UserMessageBubble from 'widget/components/UserMessageBubble';
|
||||||
import ImageBubble from 'widget/components/ImageBubble';
|
import ImageBubble from 'widget/components/ImageBubble';
|
||||||
import FileBubble from 'widget/components/FileBubble';
|
import FileBubble from 'widget/components/FileBubble';
|
||||||
import timeMixin from 'dashboard/mixins/time';
|
import timeMixin from 'dashboard/mixins/time';
|
||||||
|
import messageMixin from '../mixins/messageMixin';
|
||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
@ -46,7 +47,7 @@ export default {
|
||||||
ImageBubble,
|
ImageBubble,
|
||||||
FileBubble,
|
FileBubble,
|
||||||
},
|
},
|
||||||
mixins: [timeMixin],
|
mixins: [timeMixin, messageMixin],
|
||||||
props: {
|
props: {
|
||||||
message: {
|
message: {
|
||||||
type: Object,
|
type: Object,
|
||||||
|
@ -62,11 +63,6 @@ export default {
|
||||||
const { status = '' } = this.message;
|
const { status = '' } = this.message;
|
||||||
return status === 'in_progress';
|
return status === 'in_progress';
|
||||||
},
|
},
|
||||||
hasAttachments() {
|
|
||||||
return !!(
|
|
||||||
this.message.attachments && this.message.attachments.length > 0
|
|
||||||
);
|
|
||||||
},
|
|
||||||
showTextBubble() {
|
showTextBubble() {
|
||||||
const { message } = this;
|
const { message } = this;
|
||||||
return !!message.content;
|
return !!message.content;
|
||||||
|
|
|
@ -18,13 +18,15 @@ class ActionCableConnector extends BaseActionCableConnector {
|
||||||
};
|
};
|
||||||
|
|
||||||
onMessageCreated = data => {
|
onMessageCreated = data => {
|
||||||
this.app.$store.dispatch('conversation/addMessage', data).then(() => {
|
this.app.$store
|
||||||
window.bus.$emit('on-agent-message-recieved');
|
.dispatch('conversation/addOrUpdateMessage', data)
|
||||||
});
|
.then(() => {
|
||||||
|
window.bus.$emit('on-agent-message-recieved');
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
onMessageUpdated = data => {
|
onMessageUpdated = data => {
|
||||||
this.app.$store.dispatch('conversation/updateMessage', data);
|
this.app.$store.dispatch('conversation/addOrUpdateMessage', data);
|
||||||
};
|
};
|
||||||
|
|
||||||
onPresenceUpdate = data => {
|
onPresenceUpdate = data => {
|
||||||
|
|
13
app/javascript/widget/mixins/messageMixin.js
Normal file
13
app/javascript/widget/mixins/messageMixin.js
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
export default {
|
||||||
|
computed: {
|
||||||
|
messageContentAttributes() {
|
||||||
|
const { content_attributes: attribute = {} } = this.message;
|
||||||
|
return attribute;
|
||||||
|
},
|
||||||
|
hasAttachments() {
|
||||||
|
return !!(
|
||||||
|
this.message.attachments && this.message.attachments.length > 0
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
27
app/javascript/widget/mixins/specs/messageFixture.js
Normal file
27
app/javascript/widget/mixins/specs/messageFixture.js
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
export default {
|
||||||
|
nonDeletedMessage: {
|
||||||
|
content: 'Hey',
|
||||||
|
content_attributes: {},
|
||||||
|
attachments: [
|
||||||
|
{
|
||||||
|
data_url: 'https://assets.com/kseb-bill.pdf',
|
||||||
|
extension: null,
|
||||||
|
file_type: 'file',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
content_type: 'text',
|
||||||
|
conversation_id: 1,
|
||||||
|
created_at: 1626111625,
|
||||||
|
id: 1,
|
||||||
|
message_type: 0,
|
||||||
|
},
|
||||||
|
deletedMessage: {
|
||||||
|
content: 'This message was deleted',
|
||||||
|
content_attributes: { deleted: true },
|
||||||
|
content_type: null,
|
||||||
|
conversation_id: 1,
|
||||||
|
created_at: 1626111634,
|
||||||
|
id: 2,
|
||||||
|
message_type: 1,
|
||||||
|
},
|
||||||
|
};
|
37
app/javascript/widget/mixins/specs/messageMixin.spec.js
Normal file
37
app/javascript/widget/mixins/specs/messageMixin.spec.js
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
import { shallowMount } from '@vue/test-utils';
|
||||||
|
import messageMixin from '../messageMixin';
|
||||||
|
import messages from './messageFixture';
|
||||||
|
|
||||||
|
describe('messageMixin', () => {
|
||||||
|
let Component = {
|
||||||
|
render() {},
|
||||||
|
title: 'TestComponent',
|
||||||
|
mixins: [messageMixin],
|
||||||
|
};
|
||||||
|
|
||||||
|
it('deleted messages', async () => {
|
||||||
|
const wrapper = shallowMount(Component, {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
message: messages.deletedMessage,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(wrapper.vm.messageContentAttributes).toEqual({
|
||||||
|
deleted: true,
|
||||||
|
});
|
||||||
|
expect(wrapper.vm.hasAttachments).toBe(false);
|
||||||
|
});
|
||||||
|
it('non-deleted messages', async () => {
|
||||||
|
const wrapper = shallowMount(Component, {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
message: messages.nonDeletedMessage,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(wrapper.vm.deletedMessage).toBe(undefined);
|
||||||
|
expect(wrapper.vm.messageContentAttributes).toEqual({});
|
||||||
|
expect(wrapper.vm.hasAttachments).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
|
@ -8,7 +8,7 @@ import {
|
||||||
} from 'widget/api/conversation';
|
} from 'widget/api/conversation';
|
||||||
import { refreshActionCableConnector } from '../../../helpers/actionCable';
|
import { refreshActionCableConnector } from '../../../helpers/actionCable';
|
||||||
|
|
||||||
import { createTemporaryMessage } from './helpers';
|
import { createTemporaryMessage, getNonDeletedMessages } from './helpers';
|
||||||
|
|
||||||
export const actions = {
|
export const actions = {
|
||||||
createConversation: async ({ commit, dispatch }, params) => {
|
createConversation: async ({ commit, dispatch }, params) => {
|
||||||
|
@ -60,12 +60,12 @@ export const actions = {
|
||||||
// Show error
|
// Show error
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
fetchOldConversations: async ({ commit }, { before } = {}) => {
|
fetchOldConversations: async ({ commit }, { before } = {}) => {
|
||||||
try {
|
try {
|
||||||
commit('setConversationListLoading', true);
|
commit('setConversationListLoading', true);
|
||||||
const { data } = await getMessagesAPI({ before });
|
const { data } = await getMessagesAPI({ before });
|
||||||
commit('setMessagesInConversation', data);
|
const formattedMessages = getNonDeletedMessages({ messages: data });
|
||||||
|
commit('setMessagesInConversation', formattedMessages);
|
||||||
commit('setConversationListLoading', false);
|
commit('setConversationListLoading', false);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
commit('setConversationListLoading', false);
|
commit('setConversationListLoading', false);
|
||||||
|
@ -76,11 +76,12 @@ export const actions = {
|
||||||
commit('clearConversations');
|
commit('clearConversations');
|
||||||
},
|
},
|
||||||
|
|
||||||
addMessage: async ({ commit }, data) => {
|
addOrUpdateMessage: async ({ commit }, data) => {
|
||||||
commit('pushMessageToConversation', data);
|
const { id, content_attributes } = data;
|
||||||
},
|
if (content_attributes && content_attributes.deleted) {
|
||||||
|
commit('deleteMessage', id);
|
||||||
updateMessage({ commit }, data) {
|
return;
|
||||||
|
}
|
||||||
commit('pushMessageToConversation', data);
|
commit('pushMessageToConversation', data);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -46,3 +46,9 @@ export const findUndeliveredMessage = (messageInbox, { content }) =>
|
||||||
Object.values(messageInbox).filter(
|
Object.values(messageInbox).filter(
|
||||||
message => message.content === content && message.status === 'in_progress'
|
message => message.content === content && message.status === 'in_progress'
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const getNonDeletedMessages = ({ messages }) => {
|
||||||
|
return messages.filter(
|
||||||
|
item => !(item.content_attributes && item.content_attributes.deleted)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
|
@ -8,6 +8,7 @@ export const mutations = {
|
||||||
},
|
},
|
||||||
pushMessageToConversation($state, message) {
|
pushMessageToConversation($state, message) {
|
||||||
const { id, status, message_type: type } = message;
|
const { id, status, message_type: type } = message;
|
||||||
|
|
||||||
const messagesInbox = $state.conversations;
|
const messagesInbox = $state.conversations;
|
||||||
const isMessageIncoming = type === MESSAGE_TYPE.INCOMING;
|
const isMessageIncoming = type === MESSAGE_TYPE.INCOMING;
|
||||||
const isTemporaryMessage = status === 'in_progress';
|
const isTemporaryMessage = status === 'in_progress';
|
||||||
|
@ -71,6 +72,11 @@ export const mutations = {
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
deleteMessage($state, id) {
|
||||||
|
const messagesInbox = $state.conversations;
|
||||||
|
Vue.delete(messagesInbox, id);
|
||||||
|
},
|
||||||
|
|
||||||
toggleAgentTypingStatus($state, { status }) {
|
toggleAgentTypingStatus($state, { status }) {
|
||||||
const isTyping = status === 'on';
|
const isTyping = status === 'on';
|
||||||
$state.uiFlags.isAgentTyping = isTyping;
|
$state.uiFlags.isAgentTyping = isTyping;
|
||||||
|
|
|
@ -8,21 +8,6 @@ jest.mock('widget/helpers/axios');
|
||||||
const commit = jest.fn();
|
const commit = jest.fn();
|
||||||
|
|
||||||
describe('#actions', () => {
|
describe('#actions', () => {
|
||||||
describe('#addMessage', () => {
|
|
||||||
it('sends correct mutations', () => {
|
|
||||||
actions.addMessage({ commit }, { id: 1 });
|
|
||||||
expect(commit).toBeCalledWith('pushMessageToConversation', { id: 1 });
|
|
||||||
});
|
|
||||||
|
|
||||||
it('plays audio when agent sends a message', () => {
|
|
||||||
actions.addMessage({ commit }, { id: 1, message_type: 1 });
|
|
||||||
expect(commit).toBeCalledWith('pushMessageToConversation', {
|
|
||||||
id: 1,
|
|
||||||
message_type: 1,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('#createConversation', () => {
|
describe('#createConversation', () => {
|
||||||
it('sends correct mutations', async () => {
|
it('sends correct mutations', async () => {
|
||||||
API.post.mockResolvedValue({
|
API.post.mockResolvedValue({
|
||||||
|
@ -60,10 +45,40 @@ describe('#actions', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('#updateMessage', () => {
|
describe('#addOrUpdateMessage', () => {
|
||||||
it('sends correct mutations', () => {
|
it('sends correct actions for non-deleted message', () => {
|
||||||
actions.updateMessage({ commit }, { id: 1 });
|
actions.addOrUpdateMessage(
|
||||||
expect(commit).toBeCalledWith('pushMessageToConversation', { id: 1 });
|
{ commit },
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
content: 'Hey',
|
||||||
|
content_attributes: {},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
expect(commit).toBeCalledWith('pushMessageToConversation', {
|
||||||
|
id: 1,
|
||||||
|
content: 'Hey',
|
||||||
|
content_attributes: {},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('sends correct actions for non-deleted message', () => {
|
||||||
|
actions.addOrUpdateMessage(
|
||||||
|
{ commit },
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
content: 'Hey',
|
||||||
|
content_attributes: { deleted: true },
|
||||||
|
}
|
||||||
|
);
|
||||||
|
expect(commit).toBeCalledWith('deleteMessage', 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('plays audio when agent sends a message', () => {
|
||||||
|
actions.addOrUpdateMessage({ commit }, { id: 1, message_type: 1 });
|
||||||
|
expect(commit).toBeCalledWith('pushMessageToConversation', {
|
||||||
|
id: 1,
|
||||||
|
message_type: 1,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -160,4 +175,38 @@ describe('#actions', () => {
|
||||||
expect(commit).toBeCalledWith('clearConversations');
|
expect(commit).toBeCalledWith('clearConversations');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('#fetchOldConversations', () => {
|
||||||
|
it('sends correct actions', async () => {
|
||||||
|
API.get.mockResolvedValue({
|
||||||
|
data: [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
text: 'hey',
|
||||||
|
content_attributes: {},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
text: 'welcome',
|
||||||
|
content_attributes: { deleted: true },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
await actions.fetchOldConversations({ commit }, {});
|
||||||
|
expect(commit.mock.calls).toEqual([
|
||||||
|
['setConversationListLoading', true],
|
||||||
|
[
|
||||||
|
'setMessagesInConversation',
|
||||||
|
[
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
text: 'hey',
|
||||||
|
content_attributes: {},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
],
|
||||||
|
['setConversationListLoading', false],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import {
|
import {
|
||||||
findUndeliveredMessage,
|
findUndeliveredMessage,
|
||||||
createTemporaryMessage,
|
createTemporaryMessage,
|
||||||
|
getNonDeletedMessages,
|
||||||
} from '../../conversation/helpers';
|
} from '../../conversation/helpers';
|
||||||
|
|
||||||
describe('#findUndeliveredMessage', () => {
|
describe('#findUndeliveredMessage', () => {
|
||||||
|
@ -35,3 +36,37 @@ describe('#createTemporaryMessage', () => {
|
||||||
expect(message.status).toBe('in_progress');
|
expect(message.status).toBe('in_progress');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('#getNonDeletedMessages', () => {
|
||||||
|
it('returns non-deleted messages', () => {
|
||||||
|
const messages = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
content: 'Hello',
|
||||||
|
content_attributes: {},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
content: 'Hey',
|
||||||
|
content_attributes: { deleted: true },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
content: 'How may I help you',
|
||||||
|
content_attributes: {},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
expect(getNonDeletedMessages({ messages })).toStrictEqual([
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
content: 'Hello',
|
||||||
|
content_attributes: {},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
content: 'How may I help you',
|
||||||
|
content_attributes: {},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
|
@ -175,4 +175,12 @@ describe('#mutations', () => {
|
||||||
expect(state.conversations).toEqual({});
|
expect(state.conversations).toEqual({});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('#deleteMessage', () => {
|
||||||
|
it('delete the message from conversation', () => {
|
||||||
|
const state = { conversations: { 1: { id: 1 } } };
|
||||||
|
mutations.deleteMessage(state, 1);
|
||||||
|
expect(state.conversations).toEqual({});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue