feat: Add a read indicator for web-widget channel (#4224)

Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
This commit is contained in:
Fayaz Ahmed
2022-04-20 16:03:12 +05:30
committed by GitHub
parent f2f0d466f2
commit 2b2252b66e
15 changed files with 109 additions and 1 deletions

View File

@@ -60,6 +60,7 @@
:readable-time="readableTime"
:source-id="data.source_id"
:inbox-id="data.inbox_id"
:message-read="showReadTicks"
/>
</div>
<spinner v-if="isPending" size="tiny" />
@@ -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;
},

View File

@@ -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"
/>
<li v-show="getUnreadCount != 0" class="unread--toast">
<span class="text-uppercase">
@@ -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"
/>
</ul>
<div
@@ -141,6 +149,7 @@ export default {
listLoadingStatus: 'getAllMessagesLoaded',
getUnreadCount: 'getUnreadCount',
loadingChatList: 'getChatListLoadingStatus',
conversationLastSeen: 'getConversationLastSeen',
}),
inboxId() {
return this.currentChat.inbox_id;
@@ -241,6 +250,11 @@ export default {
}
return 'arrow-chevron-left';
},
getLastSeenAt() {
if (this.conversationLastSeen) return this.conversationLastSeen;
const { contact_last_seen_at: contactLastSeenAt } = this.currentChat;
return contactLastSeenAt;
},
},
watch: {

View File

@@ -8,6 +8,13 @@
size="16"
/>
</span>
<fluent-icon
v-if="messageRead"
v-tooltip.top-start="$t('CHAT_LIST.MESSAGE_READ')"
icon="checkmark-double"
class="action--icon read-tick"
size="12"
/>
<fluent-icon
v-if="isEmail"
v-tooltip.top-start="$t('CHAT_LIST.RECEIVED_VIA_EMAIL')"
@@ -120,6 +127,10 @@ export default {
type: [String, Number],
default: 0,
},
messageRead: {
type: Boolean,
default: false,
},
},
computed: {
inbox() {
@@ -173,6 +184,10 @@ export default {
}
.action--icon {
&.read-tick {
color: var(--v-100);
margin-top: calc(var(--space-micro) + var(--space-micro) / 2);
}
color: var(--white);
}

View File

@@ -23,6 +23,7 @@ class ActionCableConnector extends BaseActionCableConnector {
'contact.updated': this.onContactUpdate,
'conversation.mentioned': this.onConversationMentioned,
'notification.created': this.onNotificationCreated,
'conversation.read': this.onConversationRead,
};
}
@@ -64,6 +65,11 @@ class ActionCableConnector extends BaseActionCableConnector {
this.fetchConversationStats();
};
onConversationRead = data => {
const { contact_last_seen_at: lastSeen } = data;
this.app.$store.dispatch('updateConversationRead', lastSeen);
};
onLogout = () => AuthAPI.logout();
onMessageCreated = data => {

View File

@@ -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"
}
}

View File

@@ -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

View File

@@ -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);
});
});

View File

@@ -199,6 +199,10 @@ const actions = {
}
},
updateConversationRead({ commit }, timestamp) {
commit(types.SET_CONVERSATION_LAST_SEEN, timestamp);
},
updateMessage({ commit }, message) {
commit(types.ADD_MESSAGE, message);
},

View File

@@ -91,6 +91,9 @@ const getters = {
value => value.id === Number(conversationId)
);
},
getConversationLastSeen: _state => {
return _state.conversationLastSeen;
},
};
export default getters;

View File

@@ -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);

View File

@@ -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', () => {

View File

@@ -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 = {};

View File

@@ -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 };

View File

@@ -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',