From b56512eb56242a8216dcb8dd564a4f0a3e293edf Mon Sep 17 00:00:00 2001 From: Muhsin Keloth Date: Thu, 15 Jul 2021 14:27:37 +0530 Subject: [PATCH] fix: Hide deleted messages on widget side (#2614) --- .../widgets/conversation/Message.vue | 5 +- .../widget/components/AgentMessage.vue | 17 +--- .../widget/components/UserMessage.vue | 8 +- app/javascript/widget/helpers/actionCable.js | 10 ++- app/javascript/widget/mixins/messageMixin.js | 13 +++ .../widget/mixins/specs/messageFixture.js | 27 ++++++ .../widget/mixins/specs/messageMixin.spec.js | 37 ++++++++ .../store/modules/conversation/actions.js | 17 ++-- .../store/modules/conversation/helpers.js | 6 ++ .../store/modules/conversation/mutations.js | 6 ++ .../specs/conversation/actions.spec.js | 87 +++++++++++++++---- .../specs/conversation/helpers.spec.js | 35 ++++++++ .../specs/conversation/mutations.spec.js | 8 ++ 13 files changed, 222 insertions(+), 54 deletions(-) create mode 100644 app/javascript/widget/mixins/messageMixin.js create mode 100644 app/javascript/widget/mixins/specs/messageFixture.js create mode 100644 app/javascript/widget/mixins/specs/messageMixin.spec.js diff --git a/app/javascript/dashboard/components/widgets/conversation/Message.vue b/app/javascript/dashboard/components/widgets/conversation/Message.vue index ae58222fb..97a777cfd 100644 --- a/app/javascript/dashboard/components/widgets/conversation/Message.vue +++ b/app/javascript/dashboard/components/widgets/conversation/Message.vue @@ -63,7 +63,7 @@
0 - ); - }, readableTime() { const { created_at: createdAt = '' } = this.message; return this.messageStamp(createdAt, 'LLL d yyyy, h:mm a'); @@ -111,10 +107,6 @@ export default { const { content_type: type = '' } = this.message; return type; }, - messageContentAttributes() { - const { content_attributes: attribute = {} } = this.message; - return attribute; - }, agentName() { if (this.message.message_type === MESSAGE_TYPE.TEMPLATE) { return 'Bot'; @@ -153,9 +145,8 @@ export default { if (this.messageContentAttributes.submitted_values) { if (this.contentType === 'input_select') { - const [ - selectionOption = {}, - ] = this.messageContentAttributes.submitted_values; + const [selectionOption = {}] = + this.messageContentAttributes.submitted_values; return { content: selectionOption.title || selectionOption.value }; } } diff --git a/app/javascript/widget/components/UserMessage.vue b/app/javascript/widget/components/UserMessage.vue index 0baa6f1d8..94d85d72c 100755 --- a/app/javascript/widget/components/UserMessage.vue +++ b/app/javascript/widget/components/UserMessage.vue @@ -37,6 +37,7 @@ import UserMessageBubble from 'widget/components/UserMessageBubble'; import ImageBubble from 'widget/components/ImageBubble'; import FileBubble from 'widget/components/FileBubble'; import timeMixin from 'dashboard/mixins/time'; +import messageMixin from '../mixins/messageMixin'; import { mapGetters } from 'vuex'; export default { @@ -46,7 +47,7 @@ export default { ImageBubble, FileBubble, }, - mixins: [timeMixin], + mixins: [timeMixin, messageMixin], props: { message: { type: Object, @@ -62,11 +63,6 @@ export default { const { status = '' } = this.message; return status === 'in_progress'; }, - hasAttachments() { - return !!( - this.message.attachments && this.message.attachments.length > 0 - ); - }, showTextBubble() { const { message } = this; return !!message.content; diff --git a/app/javascript/widget/helpers/actionCable.js b/app/javascript/widget/helpers/actionCable.js index 8ac4919d3..9b6cf2211 100644 --- a/app/javascript/widget/helpers/actionCable.js +++ b/app/javascript/widget/helpers/actionCable.js @@ -18,13 +18,15 @@ class ActionCableConnector extends BaseActionCableConnector { }; onMessageCreated = data => { - this.app.$store.dispatch('conversation/addMessage', data).then(() => { - window.bus.$emit('on-agent-message-recieved'); - }); + this.app.$store + .dispatch('conversation/addOrUpdateMessage', data) + .then(() => { + window.bus.$emit('on-agent-message-recieved'); + }); }; onMessageUpdated = data => { - this.app.$store.dispatch('conversation/updateMessage', data); + this.app.$store.dispatch('conversation/addOrUpdateMessage', data); }; onPresenceUpdate = data => { diff --git a/app/javascript/widget/mixins/messageMixin.js b/app/javascript/widget/mixins/messageMixin.js new file mode 100644 index 000000000..dfa1e0e6a --- /dev/null +++ b/app/javascript/widget/mixins/messageMixin.js @@ -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 + ); + }, + }, +}; diff --git a/app/javascript/widget/mixins/specs/messageFixture.js b/app/javascript/widget/mixins/specs/messageFixture.js new file mode 100644 index 000000000..f11c92887 --- /dev/null +++ b/app/javascript/widget/mixins/specs/messageFixture.js @@ -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, + }, +}; diff --git a/app/javascript/widget/mixins/specs/messageMixin.spec.js b/app/javascript/widget/mixins/specs/messageMixin.spec.js new file mode 100644 index 000000000..a6443ccd6 --- /dev/null +++ b/app/javascript/widget/mixins/specs/messageMixin.spec.js @@ -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); + }); +}); diff --git a/app/javascript/widget/store/modules/conversation/actions.js b/app/javascript/widget/store/modules/conversation/actions.js index c3134620e..49ab9b331 100644 --- a/app/javascript/widget/store/modules/conversation/actions.js +++ b/app/javascript/widget/store/modules/conversation/actions.js @@ -8,7 +8,7 @@ import { } from 'widget/api/conversation'; import { refreshActionCableConnector } from '../../../helpers/actionCable'; -import { createTemporaryMessage } from './helpers'; +import { createTemporaryMessage, getNonDeletedMessages } from './helpers'; export const actions = { createConversation: async ({ commit, dispatch }, params) => { @@ -60,12 +60,12 @@ export const actions = { // Show error } }, - fetchOldConversations: async ({ commit }, { before } = {}) => { try { commit('setConversationListLoading', true); const { data } = await getMessagesAPI({ before }); - commit('setMessagesInConversation', data); + const formattedMessages = getNonDeletedMessages({ messages: data }); + commit('setMessagesInConversation', formattedMessages); commit('setConversationListLoading', false); } catch (error) { commit('setConversationListLoading', false); @@ -76,11 +76,12 @@ export const actions = { commit('clearConversations'); }, - addMessage: async ({ commit }, data) => { - commit('pushMessageToConversation', data); - }, - - updateMessage({ commit }, data) { + addOrUpdateMessage: async ({ commit }, data) => { + const { id, content_attributes } = data; + if (content_attributes && content_attributes.deleted) { + commit('deleteMessage', id); + return; + } commit('pushMessageToConversation', data); }, diff --git a/app/javascript/widget/store/modules/conversation/helpers.js b/app/javascript/widget/store/modules/conversation/helpers.js index 03227f8da..44e2a6729 100644 --- a/app/javascript/widget/store/modules/conversation/helpers.js +++ b/app/javascript/widget/store/modules/conversation/helpers.js @@ -46,3 +46,9 @@ export const findUndeliveredMessage = (messageInbox, { content }) => Object.values(messageInbox).filter( message => message.content === content && message.status === 'in_progress' ); + +export const getNonDeletedMessages = ({ messages }) => { + return messages.filter( + item => !(item.content_attributes && item.content_attributes.deleted) + ); +}; diff --git a/app/javascript/widget/store/modules/conversation/mutations.js b/app/javascript/widget/store/modules/conversation/mutations.js index d42d08cf2..6f8d35a89 100644 --- a/app/javascript/widget/store/modules/conversation/mutations.js +++ b/app/javascript/widget/store/modules/conversation/mutations.js @@ -8,6 +8,7 @@ export const mutations = { }, pushMessageToConversation($state, message) { const { id, status, message_type: type } = message; + const messagesInbox = $state.conversations; const isMessageIncoming = type === MESSAGE_TYPE.INCOMING; 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 }) { const isTyping = status === 'on'; $state.uiFlags.isAgentTyping = isTyping; diff --git a/app/javascript/widget/store/modules/specs/conversation/actions.spec.js b/app/javascript/widget/store/modules/specs/conversation/actions.spec.js index ca1b702ee..9bde844de 100644 --- a/app/javascript/widget/store/modules/specs/conversation/actions.spec.js +++ b/app/javascript/widget/store/modules/specs/conversation/actions.spec.js @@ -8,21 +8,6 @@ jest.mock('widget/helpers/axios'); const commit = jest.fn(); 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', () => { it('sends correct mutations', async () => { API.post.mockResolvedValue({ @@ -60,10 +45,40 @@ describe('#actions', () => { }); }); - describe('#updateMessage', () => { - it('sends correct mutations', () => { - actions.updateMessage({ commit }, { id: 1 }); - expect(commit).toBeCalledWith('pushMessageToConversation', { id: 1 }); + describe('#addOrUpdateMessage', () => { + it('sends correct actions for non-deleted message', () => { + actions.addOrUpdateMessage( + { 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'); }); }); + + 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], + ]); + }); + }); }); diff --git a/app/javascript/widget/store/modules/specs/conversation/helpers.spec.js b/app/javascript/widget/store/modules/specs/conversation/helpers.spec.js index fd9bab99e..d2d7943b1 100644 --- a/app/javascript/widget/store/modules/specs/conversation/helpers.spec.js +++ b/app/javascript/widget/store/modules/specs/conversation/helpers.spec.js @@ -1,6 +1,7 @@ import { findUndeliveredMessage, createTemporaryMessage, + getNonDeletedMessages, } from '../../conversation/helpers'; describe('#findUndeliveredMessage', () => { @@ -35,3 +36,37 @@ describe('#createTemporaryMessage', () => { 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: {}, + }, + ]); + }); +}); diff --git a/app/javascript/widget/store/modules/specs/conversation/mutations.spec.js b/app/javascript/widget/store/modules/specs/conversation/mutations.spec.js index 7e80454e8..96d2e8da8 100644 --- a/app/javascript/widget/store/modules/specs/conversation/mutations.spec.js +++ b/app/javascript/widget/store/modules/specs/conversation/mutations.spec.js @@ -175,4 +175,12 @@ describe('#mutations', () => { 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({}); + }); + }); });