From 2b2252b66e71a4e8d109e398df1d7f4094cbc4cc Mon Sep 17 00:00:00 2001 From: Fayaz Ahmed <15716057+fayazara@users.noreply.github.com> Date: Wed, 20 Apr 2022 16:03:12 +0530 Subject: [PATCH] feat: Add a read indicator for web-widget channel (#4224) Co-authored-by: Pranav Raj S --- .../components/widgets/conversation/Message.vue | 17 +++++++++++++++++ .../widgets/conversation/MessagesView.vue | 14 ++++++++++++++ .../widgets/conversation/bubble/Actions.vue | 15 +++++++++++++++ app/javascript/dashboard/helper/actionCable.js | 6 ++++++ .../dashboard/i18n/locale/en/chatlist.json | 3 ++- .../dashboard/mixins/conversations.js | 3 +++ .../dashboard/mixins/specs/conversaton.spec.js | 7 +++++++ .../store/modules/conversations/actions.js | 4 ++++ .../store/modules/conversations/getters.js | 3 +++ .../store/modules/conversations/index.js | 4 ++++ .../modules/specs/conversations/actions.spec.js | 9 +++++++++ .../modules/specs/conversations/getters.spec.js | 10 ++++++++++ .../specs/conversations/mutations.spec.js | 12 ++++++++++++ .../dashboard/store/mutation-types.js | 1 + .../components/FluentIcon/dashboard-icons.json | 2 ++ 15 files changed, 109 insertions(+), 1 deletion(-) diff --git a/app/javascript/dashboard/components/widgets/conversation/Message.vue b/app/javascript/dashboard/components/widgets/conversation/Message.vue index 4035a62d0..db37e7558 100644 --- a/app/javascript/dashboard/components/widgets/conversation/Message.vue +++ b/app/javascript/dashboard/components/widgets/conversation/Message.vue @@ -60,6 +60,7 @@ :readable-time="readableTime" :source-id="data.source_id" :inbox-id="data.inbox_id" + :message-read="showReadTicks" /> @@ -153,6 +154,14 @@ export default { type: Boolean, default: false, }, + hasUserReadMessage: { + type: Boolean, + default: false, + }, + isWebWidgetInbox: { + type: Boolean, + default: false, + }, }, data() { return { @@ -268,6 +277,14 @@ export default { isOutgoing() { return this.data.message_type === MESSAGE_TYPE.OUTGOING; }, + showReadTicks() { + return ( + (this.isOutgoing || this.isTemplate) && + this.hasUserReadMessage && + this.isWebWidgetInbox && + !this.data.private + ); + }, isTemplate() { return this.data.message_type === MESSAGE_TYPE.TEMPLATE; }, diff --git a/app/javascript/dashboard/components/widgets/conversation/MessagesView.vue b/app/javascript/dashboard/components/widgets/conversation/MessagesView.vue index 280ddc158..3ebf59a54 100644 --- a/app/javascript/dashboard/components/widgets/conversation/MessagesView.vue +++ b/app/javascript/dashboard/components/widgets/conversation/MessagesView.vue @@ -48,6 +48,10 @@ :data="message" :is-a-tweet="isATweet" :has-instagram-story="hasInstagramStory" + :has-user-read-message=" + hasUserReadMessage(message.created_at, getLastSeenAt) + " + :is-web-widget-inbox="isAWebWidgetInbox" />
  • @@ -66,6 +70,10 @@ :data="message" :is-a-tweet="isATweet" :has-instagram-story="hasInstagramStory" + :has-user-read-message=" + hasUserReadMessage(message.created_at, getLastSeenAt) + " + :is-web-widget-inbox="isAWebWidgetInbox" />
    + { + const { contact_last_seen_at: lastSeen } = data; + this.app.$store.dispatch('updateConversationRead', lastSeen); + }; + onLogout = () => AuthAPI.logout(); onMessageCreated = data => { diff --git a/app/javascript/dashboard/i18n/locale/en/chatlist.json b/app/javascript/dashboard/i18n/locale/en/chatlist.json index ccff2c33b..e3d637b71 100644 --- a/app/javascript/dashboard/i18n/locale/en/chatlist.json +++ b/app/javascript/dashboard/i18n/locale/en/chatlist.json @@ -81,6 +81,7 @@ "NO_MESSAGES": "No Messages", "NO_CONTENT": "No content available", "HIDE_QUOTED_TEXT": "Hide Quoted Text", - "SHOW_QUOTED_TEXT": "Show Quoted Text" + "SHOW_QUOTED_TEXT": "Show Quoted Text", + "MESSAGE_READ": "Read" } } diff --git a/app/javascript/dashboard/mixins/conversations.js b/app/javascript/dashboard/mixins/conversations.js index 2242685af..3b1def4fc 100644 --- a/app/javascript/dashboard/mixins/conversations.js +++ b/app/javascript/dashboard/mixins/conversations.js @@ -15,6 +15,9 @@ export default { chat.private !== true ).length; }, + hasUserReadMessage(createdAt, contactLastSeen) { + return !(contactLastSeen - createdAt < 0); + }, readMessages(m) { return m.messages.filter( chat => chat.created_at * 1000 <= m.agent_last_seen_at * 1000 diff --git a/app/javascript/dashboard/mixins/specs/conversaton.spec.js b/app/javascript/dashboard/mixins/specs/conversaton.spec.js index eaa9aab97..a1784afeb 100644 --- a/app/javascript/dashboard/mixins/specs/conversaton.spec.js +++ b/app/javascript/dashboard/mixins/specs/conversaton.spec.js @@ -26,4 +26,11 @@ describe('#conversationMixin', () => { conversationMixin.methods.unReadMessages(conversationFixture.conversation) ).toEqual(conversationFixture.unReadMessages); }); + it('should return the user message read flag', () => { + const contactLastSeen = 1649856659; + const createdAt = 1649859419; + expect( + conversationMixin.methods.hasUserReadMessage(createdAt, contactLastSeen) + ).toEqual(false); + }); }); diff --git a/app/javascript/dashboard/store/modules/conversations/actions.js b/app/javascript/dashboard/store/modules/conversations/actions.js index ca42dfae0..2012a6edd 100644 --- a/app/javascript/dashboard/store/modules/conversations/actions.js +++ b/app/javascript/dashboard/store/modules/conversations/actions.js @@ -199,6 +199,10 @@ const actions = { } }, + updateConversationRead({ commit }, timestamp) { + commit(types.SET_CONVERSATION_LAST_SEEN, timestamp); + }, + updateMessage({ commit }, message) { commit(types.ADD_MESSAGE, message); }, diff --git a/app/javascript/dashboard/store/modules/conversations/getters.js b/app/javascript/dashboard/store/modules/conversations/getters.js index 75d9aef44..c5b34af60 100644 --- a/app/javascript/dashboard/store/modules/conversations/getters.js +++ b/app/javascript/dashboard/store/modules/conversations/getters.js @@ -91,6 +91,9 @@ const getters = { value => value.id === Number(conversationId) ); }, + getConversationLastSeen: _state => { + return _state.conversationLastSeen; + }, }; export default getters; diff --git a/app/javascript/dashboard/store/modules/conversations/index.js b/app/javascript/dashboard/store/modules/conversations/index.js index 87d81d070..e05743dbe 100644 --- a/app/javascript/dashboard/store/modules/conversations/index.js +++ b/app/javascript/dashboard/store/modules/conversations/index.js @@ -13,6 +13,7 @@ const state = { currentInbox: null, selectedChatId: null, appliedFilters: [], + conversationLastSeen: null, }; // mutations @@ -33,6 +34,9 @@ export const mutations = { _state.allConversations = []; _state.selectedChatId = null; }, + [types.SET_CONVERSATION_LAST_SEEN](_state, timestamp) { + _state.conversationLastSeen = timestamp; + }, [types.SET_ALL_MESSAGES_LOADED](_state) { const [chat] = getSelectedChatConversation(_state); Vue.set(chat, 'allMessagesLoaded', true); 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 680b6dbec..91cbfa626 100644 --- a/app/javascript/dashboard/store/modules/specs/conversations/actions.spec.js +++ b/app/javascript/dashboard/store/modules/specs/conversations/actions.spec.js @@ -372,6 +372,15 @@ describe('#actions', () => { expect(commit.mock.calls).toEqual([[types.CLEAR_CONVERSATION_FILTERS]]); }); }); + + describe('#updateConversationRead', () => { + it('commits the correct mutation and sets the contact_last_seen', () => { + actions.updateConversationRead({ commit }, 1649856659); + expect(commit.mock.calls).toEqual([ + [types.SET_CONVERSATION_LAST_SEEN, 1649856659], + ]); + }); + }); }); describe('#deleteMessage', () => { diff --git a/app/javascript/dashboard/store/modules/specs/conversations/getters.spec.js b/app/javascript/dashboard/store/modules/specs/conversations/getters.spec.js index 7dc0dade5..ef891deb2 100644 --- a/app/javascript/dashboard/store/modules/specs/conversations/getters.spec.js +++ b/app/javascript/dashboard/store/modules/specs/conversations/getters.spec.js @@ -132,6 +132,16 @@ describe('#getters', () => { }); }); + describe('#getConversationLastSeen', () => { + it('getConversationLastSeen', () => { + const timestamp = 1649856659; + const state = { + conversationLastSeen: timestamp, + }; + expect(getters.getConversationLastSeen(state)).toEqual(timestamp); + }); + }); + describe('#getLastEmailInSelectedChat', () => { it('Returns cc in last email', () => { const state = {}; 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 b7828fff3..8d4535b21 100644 --- a/app/javascript/dashboard/store/modules/specs/conversations/mutations.spec.js +++ b/app/javascript/dashboard/store/modules/specs/conversations/mutations.spec.js @@ -187,6 +187,18 @@ describe('#mutations', () => { ]); }); + describe('#SET_CONVERSATION_LAST_SEEN', () => { + it('sets conversation last seen timestamp', () => { + const state = { + conversationLastSeen: null, + }; + + mutations[types.SET_CONVERSATION_LAST_SEEN](state, 1649856659); + + expect(state.conversationLastSeen).toEqual(1649856659); + }); + }); + describe('#UPDATE_CONVERSATION_CUSTOM_ATTRIBUTES', () => { it('update conversation custom attributes', () => { const custom_attributes = { order_id: 1001 }; diff --git a/app/javascript/dashboard/store/mutation-types.js b/app/javascript/dashboard/store/mutation-types.js index 1c99f97d3..4ae2d0e10 100755 --- a/app/javascript/dashboard/store/mutation-types.js +++ b/app/javascript/dashboard/store/mutation-types.js @@ -21,6 +21,7 @@ export default { CLEAR_CONTACT_CONVERSATIONS: 'CLEAR_CONTACT_CONVERSATIONS', SET_CONVERSATION_FILTERS: 'SET_CONVERSATION_FILTERS', CLEAR_CONVERSATION_FILTERS: 'CLEAR_CONVERSATION_FILTERS', + SET_CONVERSATION_LAST_SEEN: 'SET_CONVERSATION_LAST_SEEN', SET_CURRENT_CHAT_WINDOW: 'SET_CURRENT_CHAT_WINDOW', CLEAR_CURRENT_CHAT_WINDOW: 'CLEAR_CURRENT_CHAT_WINDOW', diff --git a/app/javascript/shared/components/FluentIcon/dashboard-icons.json b/app/javascript/shared/components/FluentIcon/dashboard-icons.json index 9f19f8a9d..f145159d1 100644 --- a/app/javascript/shared/components/FluentIcon/dashboard-icons.json +++ b/app/javascript/shared/components/FluentIcon/dashboard-icons.json @@ -37,6 +37,8 @@ "checkmark-circle-solid": "M12 2c5.523 0 10 4.477 10 10s-4.477 10-10 10S2 17.523 2 12 6.477 2 12 2Zm3.22 6.97-4.47 4.47-1.97-1.97a.75.75 0 0 0-1.06 1.06l2.5 2.5a.75.75 0 0 0 1.06 0l5-5a.75.75 0 1 0-1.06-1.06Z", "checkmark-square-solid": "M18 3a3 3 0 0 1 3 3v12a3 3 0 0 1-3 3H6a3 3 0 0 1-3-3V6a3 3 0 0 1 3-3h12zm-1.53 4.97L10 14.44l-2.47-2.47a.75.75 0 0 0-1.06 1.06l3 3a.75.75 0 0 0 1.06 0l7-7a.75.75 0 0 0-1.06-1.06z", "checkmark-outline": "M4.53 12.97a.75.75 0 0 0-1.06 1.06l4.5 4.5a.75.75 0 0 0 1.06 0l11-11a.75.75 0 0 0-1.06-1.06L8.5 16.94l-3.97-3.97Z", + "checkmark-solid": "m8.5 16.586l-3.793-3.793a1 1 0 0 0-1.414 1.414l4.5 4.5a1 1 0 0 0 1.414 0l11-11a1 1 0 0 0-1.414-1.414L8.5 16.586Z", + "checkmark-double-outline": "M10.2929 16.8787C9.90237 17.2692 9.90237 17.9024 10.2929 18.2929C10.6834 18.6834 11.3166 18.6834 11.7071 18.2929L23.2929 6.70711C23.6834 6.31658 23.6834 5.68342 23.2929 5.29289C22.9024 4.90237 22.2692 4.90237 21.8787 5.29289L10.2929 16.8787ZM2.70711 11.7929L6.5 15.5858L16.7929 5.29289C17.1834 4.90237 17.8166 4.90237 18.2071 5.29289C18.5976 5.68342 18.5976 6.31658 18.2071 6.70711L7.20711 17.7071C6.81658 18.0976 6.18342 18.0976 5.79289 17.7071L1.29289 13.2071C0.902369 12.8166 0.902369 12.1834 1.29289 11.7929C1.68342 11.4024 2.31658 11.4024 2.70711 11.7929Z", "chevron-down-outline": "M4.22 8.47a.75.75 0 0 1 1.06 0L12 15.19l6.72-6.72a.75.75 0 1 1 1.06 1.06l-7.25 7.25a.75.75 0 0 1-1.06 0L4.22 9.53a.75.75 0 0 1 0-1.06Z", "chevron-left-outline": "M15.53 4.22a.75.75 0 0 1 0 1.06L8.81 12l6.72 6.72a.75.75 0 1 1-1.06 1.06l-7.25-7.25a.75.75 0 0 1 0-1.06l7.25-7.25a.75.75 0 0 1 1.06 0Z", "chevron-right-outline": "M8.293 4.293a1 1 0 0 0 0 1.414L14.586 12l-6.293 6.293a1 1 0 1 0 1.414 1.414l7-7a1 1 0 0 0 0-1.414l-7-7a1 1 0 0 0-1.414 0Z",