feat: Allow users to mark a conversation as unread (#5924)
Allow users to mark conversations as unread. Loom video: https://www.loom.com/share/ab70552d3c9c48b685da7dfa64be8bb3 fixes: #5552 Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
This commit is contained in:
parent
e593e516b8
commit
606fc9046a
20 changed files with 190 additions and 48 deletions
|
@ -75,10 +75,13 @@ class Api::V1::Accounts::ConversationsController < Api::V1::Accounts::BaseContro
|
||||||
end
|
end
|
||||||
|
|
||||||
def update_last_seen
|
def update_last_seen
|
||||||
# rubocop:disable Rails/SkipsModelValidations
|
update_last_seen_on_conversation(DateTime.now.utc, assignee?)
|
||||||
@conversation.update_column(:agent_last_seen_at, DateTime.now.utc)
|
end
|
||||||
@conversation.update_column(:assignee_last_seen_at, DateTime.now.utc) if assignee?
|
|
||||||
# rubocop:enable Rails/SkipsModelValidations
|
def unread
|
||||||
|
last_incoming_message = @conversation.messages.incoming.last
|
||||||
|
last_seen_at = last_incoming_message.created_at - 1.second if last_incoming_message.present?
|
||||||
|
update_last_seen_on_conversation(last_seen_at, true)
|
||||||
end
|
end
|
||||||
|
|
||||||
def custom_attributes
|
def custom_attributes
|
||||||
|
@ -88,6 +91,13 @@ class Api::V1::Accounts::ConversationsController < Api::V1::Accounts::BaseContro
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def update_last_seen_on_conversation(last_seen_at, update_assignee)
|
||||||
|
# rubocop:disable Rails/SkipsModelValidations
|
||||||
|
@conversation.update_column(:agent_last_seen_at, last_seen_at)
|
||||||
|
@conversation.update_column(:assignee_last_seen_at, last_seen_at) if update_assignee.present?
|
||||||
|
# rubocop:enable Rails/SkipsModelValidations
|
||||||
|
end
|
||||||
|
|
||||||
def set_conversation_status
|
def set_conversation_status
|
||||||
status = params[:status] == 'bot' ? 'pending' : params[:status]
|
status = params[:status] == 'bot' ? 'pending' : params[:status]
|
||||||
@conversation.status = status
|
@conversation.status = status
|
||||||
|
@ -163,10 +173,10 @@ class Api::V1::Accounts::ConversationsController < Api::V1::Accounts::BaseContro
|
||||||
end
|
end
|
||||||
|
|
||||||
def conversation_finder
|
def conversation_finder
|
||||||
@conversation_finder ||= ConversationFinder.new(current_user, params)
|
@conversation_finder ||= ConversationFinder.new(Current.user, params)
|
||||||
end
|
end
|
||||||
|
|
||||||
def assignee?
|
def assignee?
|
||||||
@conversation.assignee_id? && current_user == @conversation.assignee
|
@conversation.assignee_id? && Current.user == @conversation.assignee
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -68,6 +68,10 @@ class ConversationApi extends ApiClient {
|
||||||
return axios.post(`${this.url}/${id}/update_last_seen`);
|
return axios.post(`${this.url}/${id}/update_last_seen`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
markMessagesUnread({ id }) {
|
||||||
|
return axios.post(`${this.url}/${id}/unread`);
|
||||||
|
}
|
||||||
|
|
||||||
toggleTyping({ conversationId, status, isPrivate }) {
|
toggleTyping({ conversationId, status, isPrivate }) {
|
||||||
return axios.post(`${this.url}/${conversationId}/toggle_typing_status`, {
|
return axios.post(`${this.url}/${conversationId}/toggle_typing_status`, {
|
||||||
typing_status: status,
|
typing_status: status,
|
||||||
|
|
|
@ -126,6 +126,7 @@
|
||||||
@assign-label="onAssignLabels"
|
@assign-label="onAssignLabels"
|
||||||
@update-conversation-status="toggleConversationStatus"
|
@update-conversation-status="toggleConversationStatus"
|
||||||
@context-menu-toggle="onContextMenuToggle"
|
@context-menu-toggle="onContextMenuToggle"
|
||||||
|
@mark-as-unread="markAsUnread"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div v-if="chatListLoading" class="text-center">
|
<div v-if="chatListLoading" class="text-center">
|
||||||
|
@ -185,6 +186,7 @@ import {
|
||||||
hasPressedAltAndJKey,
|
hasPressedAltAndJKey,
|
||||||
hasPressedAltAndKKey,
|
hasPressedAltAndKKey,
|
||||||
} from 'shared/helpers/KeyboardHelpers';
|
} from 'shared/helpers/KeyboardHelpers';
|
||||||
|
import { conversationListPageURL } from '../helper/URLHelper';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
@ -637,6 +639,29 @@ export default {
|
||||||
this.showAlert(this.$t('BULK_ACTION.ASSIGN_FAILED'));
|
this.showAlert(this.$t('BULK_ACTION.ASSIGN_FAILED'));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
async markAsUnread(conversationId) {
|
||||||
|
try {
|
||||||
|
await this.$store.dispatch('markMessagesUnread', {
|
||||||
|
id: conversationId,
|
||||||
|
});
|
||||||
|
const {
|
||||||
|
params: { accountId, inbox_id: inboxId, label, teamId },
|
||||||
|
name,
|
||||||
|
} = this.$route;
|
||||||
|
this.$router.push(
|
||||||
|
conversationListPageURL({
|
||||||
|
accountId,
|
||||||
|
conversationType: name === 'conversation_mentions' ? 'mention' : '',
|
||||||
|
customViewId: this.foldersId,
|
||||||
|
inboxId,
|
||||||
|
label,
|
||||||
|
teamId,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
// Ignore error
|
||||||
|
}
|
||||||
|
},
|
||||||
async onAssignTeam(team, conversationId = null) {
|
async onAssignTeam(team, conversationId = null) {
|
||||||
try {
|
try {
|
||||||
await this.$store.dispatch('assignTeam', {
|
await this.$store.dispatch('assignTeam', {
|
||||||
|
|
|
@ -102,10 +102,12 @@
|
||||||
<conversation-context-menu
|
<conversation-context-menu
|
||||||
:status="chat.status"
|
:status="chat.status"
|
||||||
:inbox-id="inbox.id"
|
:inbox-id="inbox.id"
|
||||||
|
:has-unread-messages="hasUnread"
|
||||||
@update-conversation="onUpdateConversation"
|
@update-conversation="onUpdateConversation"
|
||||||
@assign-agent="onAssignAgent"
|
@assign-agent="onAssignAgent"
|
||||||
@assign-label="onAssignLabel"
|
@assign-label="onAssignLabel"
|
||||||
@assign-team="onAssignTeam"
|
@assign-team="onAssignTeam"
|
||||||
|
@mark-as-unread="markAsUnread"
|
||||||
/>
|
/>
|
||||||
</woot-context-menu>
|
</woot-context-menu>
|
||||||
</div>
|
</div>
|
||||||
|
@ -241,7 +243,7 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
unreadCount() {
|
unreadCount() {
|
||||||
return this.unreadMessagesCount(this.chat);
|
return this.chat.unread_count;
|
||||||
},
|
},
|
||||||
|
|
||||||
hasUnread() {
|
hasUnread() {
|
||||||
|
@ -359,6 +361,10 @@ export default {
|
||||||
this.$emit('assign-team', team, this.chat.id);
|
this.$emit('assign-team', team, this.chat.id);
|
||||||
this.closeContextMenu();
|
this.closeContextMenu();
|
||||||
},
|
},
|
||||||
|
async markAsUnread() {
|
||||||
|
this.$emit('mark-as-unread', this.chat.id);
|
||||||
|
this.closeContextMenu();
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -44,11 +44,11 @@
|
||||||
"
|
"
|
||||||
:is-web-widget-inbox="isAWebWidgetInbox"
|
:is-web-widget-inbox="isAWebWidgetInbox"
|
||||||
/>
|
/>
|
||||||
<li v-show="getUnreadCount != 0" class="unread--toast">
|
<li v-show="unreadMessageCount != 0" class="unread--toast">
|
||||||
<span class="text-uppercase">
|
<span class="text-uppercase">
|
||||||
{{ getUnreadCount }}
|
{{ unreadMessageCount }}
|
||||||
{{
|
{{
|
||||||
getUnreadCount > 1
|
unreadMessageCount > 1
|
||||||
? $t('CONVERSATION.UNREAD_MESSAGES')
|
? $t('CONVERSATION.UNREAD_MESSAGES')
|
||||||
: $t('CONVERSATION.UNREAD_MESSAGE')
|
: $t('CONVERSATION.UNREAD_MESSAGE')
|
||||||
}}
|
}}
|
||||||
|
@ -137,7 +137,6 @@ export default {
|
||||||
allConversations: 'getAllConversations',
|
allConversations: 'getAllConversations',
|
||||||
inboxesList: 'inboxes/getInboxes',
|
inboxesList: 'inboxes/getInboxes',
|
||||||
listLoadingStatus: 'getAllMessagesLoaded',
|
listLoadingStatus: 'getAllMessagesLoaded',
|
||||||
getUnreadCount: 'getUnreadCount',
|
|
||||||
loadingChatList: 'getChatListLoadingStatus',
|
loadingChatList: 'getChatListLoadingStatus',
|
||||||
}),
|
}),
|
||||||
inboxId() {
|
inboxId() {
|
||||||
|
@ -271,6 +270,9 @@ export default {
|
||||||
}
|
}
|
||||||
return '';
|
return '';
|
||||||
},
|
},
|
||||||
|
unreadMessageCount() {
|
||||||
|
return this.currentChat.unread_count;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
watch: {
|
watch: {
|
||||||
|
@ -331,7 +333,7 @@ export default {
|
||||||
},
|
},
|
||||||
scrollToBottom() {
|
scrollToBottom() {
|
||||||
let relevantMessages = [];
|
let relevantMessages = [];
|
||||||
if (this.getUnreadCount > 0) {
|
if (this.unreadMessageCount > 0) {
|
||||||
// capturing only the unread messages
|
// capturing only the unread messages
|
||||||
relevantMessages = this.conversationPanel.querySelectorAll(
|
relevantMessages = this.conversationPanel.querySelectorAll(
|
||||||
'.message--unread'
|
'.message--unread'
|
||||||
|
|
|
@ -1,5 +1,11 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="menu-container">
|
<div class="menu-container">
|
||||||
|
<menu-item
|
||||||
|
v-if="!hasUnreadMessages"
|
||||||
|
:option="unreadOption"
|
||||||
|
variant="icon"
|
||||||
|
@click="$emit('mark-as-unread')"
|
||||||
|
/>
|
||||||
<template v-for="option in statusMenuConfig">
|
<template v-for="option in statusMenuConfig">
|
||||||
<menu-item
|
<menu-item
|
||||||
v-if="show(option.key)"
|
v-if="show(option.key)"
|
||||||
|
@ -79,6 +85,10 @@ export default {
|
||||||
type: String,
|
type: String,
|
||||||
default: '',
|
default: '',
|
||||||
},
|
},
|
||||||
|
hasUnreadMessages: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
inboxId: {
|
inboxId: {
|
||||||
type: Number,
|
type: Number,
|
||||||
default: null,
|
default: null,
|
||||||
|
@ -87,6 +97,10 @@ export default {
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
STATUS_TYPE: wootConstants.STATUS_TYPE,
|
STATUS_TYPE: wootConstants.STATUS_TYPE,
|
||||||
|
unreadOption: {
|
||||||
|
label: this.$t('CONVERSATION.CARD_CONTEXT_MENU.MARK_AS_UNREAD'),
|
||||||
|
icon: 'mail',
|
||||||
|
},
|
||||||
statusMenuConfig: [
|
statusMenuConfig: [
|
||||||
{
|
{
|
||||||
key: wootConstants.STATUS_TYPE.RESOLVED,
|
key: wootConstants.STATUS_TYPE.RESOLVED,
|
||||||
|
|
|
@ -66,6 +66,7 @@ export const conversationListPageURL = ({
|
||||||
inboxId,
|
inboxId,
|
||||||
label,
|
label,
|
||||||
teamId,
|
teamId,
|
||||||
|
customViewId,
|
||||||
}) => {
|
}) => {
|
||||||
let url = `accounts/${accountId}/dashboard`;
|
let url = `accounts/${accountId}/dashboard`;
|
||||||
if (label) {
|
if (label) {
|
||||||
|
@ -76,6 +77,8 @@ export const conversationListPageURL = ({
|
||||||
url = `accounts/${accountId}/mentions/conversations`;
|
url = `accounts/${accountId}/mentions/conversations`;
|
||||||
} else if (inboxId) {
|
} else if (inboxId) {
|
||||||
url = `accounts/${accountId}/inbox/${inboxId}`;
|
url = `accounts/${accountId}/inbox/${inboxId}`;
|
||||||
|
} else if (customViewId) {
|
||||||
|
url = `accounts/${accountId}/custom_view/${customViewId}`;
|
||||||
}
|
}
|
||||||
return frontendURL(url);
|
return frontendURL(url);
|
||||||
};
|
};
|
||||||
|
|
|
@ -29,6 +29,12 @@ describe('#URL Helpers', () => {
|
||||||
'/app/accounts/1/team/1'
|
'/app/accounts/1/team/1'
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should return url to custom view', () => {
|
||||||
|
expect(conversationListPageURL({ accountId: 1, customViewId: 1 })).toBe(
|
||||||
|
'/app/accounts/1/custom_view/1'
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
describe('conversationUrl', () => {
|
describe('conversationUrl', () => {
|
||||||
it('should return direct conversation URL if activeInbox is nil', () => {
|
it('should return direct conversation URL if activeInbox is nil', () => {
|
||||||
|
|
|
@ -64,6 +64,7 @@
|
||||||
"CARD_CONTEXT_MENU": {
|
"CARD_CONTEXT_MENU": {
|
||||||
"PENDING": "Mark as pending",
|
"PENDING": "Mark as pending",
|
||||||
"RESOLVED": "Mark as resolved",
|
"RESOLVED": "Mark as resolved",
|
||||||
|
"MARK_AS_UNREAD": "Mark as unread",
|
||||||
"REOPEN": "Reopen conversation",
|
"REOPEN": "Reopen conversation",
|
||||||
"SNOOZE": {
|
"SNOOZE": {
|
||||||
"TITLE": "Snooze",
|
"TITLE": "Snooze",
|
||||||
|
|
|
@ -34,14 +34,6 @@ export default {
|
||||||
lastNonActivityMessageFromAPI
|
lastNonActivityMessageFromAPI
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
unreadMessagesCount(m) {
|
|
||||||
return m.messages.filter(
|
|
||||||
chat =>
|
|
||||||
chat.created_at * 1000 > m.agent_last_seen_at * 1000 &&
|
|
||||||
chat.message_type === 0 &&
|
|
||||||
chat.private !== true
|
|
||||||
).length;
|
|
||||||
},
|
|
||||||
hasUserReadMessage(createdAt, contactLastSeen) {
|
hasUserReadMessage(createdAt, contactLastSeen) {
|
||||||
return !(contactLastSeen - createdAt < 0);
|
return !(contactLastSeen - createdAt < 0);
|
||||||
},
|
},
|
||||||
|
|
|
@ -4,13 +4,6 @@ import commonHelpers from '../../helper/commons';
|
||||||
commonHelpers();
|
commonHelpers();
|
||||||
|
|
||||||
describe('#conversationMixin', () => {
|
describe('#conversationMixin', () => {
|
||||||
it('should return unread message count 2 if conversation is passed', () => {
|
|
||||||
expect(
|
|
||||||
conversationMixin.methods.unreadMessagesCount(
|
|
||||||
conversationFixture.conversation
|
|
||||||
)
|
|
||||||
).toEqual(2);
|
|
||||||
});
|
|
||||||
it('should return read messages if conversation is passed', () => {
|
it('should return read messages if conversation is passed', () => {
|
||||||
expect(
|
expect(
|
||||||
conversationMixin.methods.readMessages(conversationFixture.conversation)
|
conversationMixin.methods.readMessages(conversationFixture.conversation)
|
||||||
|
|
|
@ -8,7 +8,7 @@ import {
|
||||||
buildConversationList,
|
buildConversationList,
|
||||||
isOnMentionsView,
|
isOnMentionsView,
|
||||||
} from './helpers/actionHelpers';
|
} from './helpers/actionHelpers';
|
||||||
|
import messageReadActions from './actions/messageReadActions';
|
||||||
// actions
|
// actions
|
||||||
const actions = {
|
const actions = {
|
||||||
getConversation: async ({ commit }, conversationId) => {
|
getConversation: async ({ commit }, conversationId) => {
|
||||||
|
@ -257,17 +257,6 @@ const actions = {
|
||||||
dispatch('contacts/setContact', sender);
|
dispatch('contacts/setContact', sender);
|
||||||
},
|
},
|
||||||
|
|
||||||
markMessagesRead: async ({ commit }, data) => {
|
|
||||||
try {
|
|
||||||
const {
|
|
||||||
data: { id, agent_last_seen_at: lastSeen },
|
|
||||||
} = await ConversationApi.markMessageRead(data);
|
|
||||||
setTimeout(() => commit(types.MARK_MESSAGE_READ, { id, lastSeen }), 4000);
|
|
||||||
} catch (error) {
|
|
||||||
// Handle error
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
setChatFilter({ commit }, data) {
|
setChatFilter({ commit }, data) {
|
||||||
commit(types.CHANGE_CHAT_STATUS_FILTER, data);
|
commit(types.CHANGE_CHAT_STATUS_FILTER, data);
|
||||||
},
|
},
|
||||||
|
@ -336,6 +325,7 @@ const actions = {
|
||||||
clearConversationFilters({ commit }) {
|
clearConversationFilters({ commit }) {
|
||||||
commit(types.CLEAR_CONVERSATION_FILTERS);
|
commit(types.CLEAR_CONVERSATION_FILTERS);
|
||||||
},
|
},
|
||||||
|
...messageReadActions,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default actions;
|
export default actions;
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
import { throwErrorMessage } from 'dashboard/store/utils/api';
|
||||||
|
import ConversationApi from '../../../../api/inbox/conversation';
|
||||||
|
import mutationTypes from '../../../mutation-types';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
markMessagesRead: async ({ commit }, data) => {
|
||||||
|
try {
|
||||||
|
const {
|
||||||
|
data: { id, agent_last_seen_at: lastSeen },
|
||||||
|
} = await ConversationApi.markMessageRead(data);
|
||||||
|
setTimeout(
|
||||||
|
() =>
|
||||||
|
commit(mutationTypes.UPDATE_MESSAGE_UNREAD_COUNT, { id, lastSeen }),
|
||||||
|
4000
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
// Handle error
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
markMessagesUnread: async ({ commit }, { id }) => {
|
||||||
|
try {
|
||||||
|
const {
|
||||||
|
data: { agent_last_seen_at: lastSeen, unread_count: unreadCount },
|
||||||
|
} = await ConversationApi.markMessagesUnread({ id });
|
||||||
|
commit(mutationTypes.UPDATE_MESSAGE_UNREAD_COUNT, {
|
||||||
|
id,
|
||||||
|
lastSeen,
|
||||||
|
unreadCount,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
throwErrorMessage(error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
|
@ -146,13 +146,16 @@ export const mutations = {
|
||||||
_state.listLoadingStatus = false;
|
_state.listLoadingStatus = false;
|
||||||
},
|
},
|
||||||
|
|
||||||
[types.MARK_MESSAGE_READ](_state, { id, lastSeen }) {
|
[types.UPDATE_MESSAGE_UNREAD_COUNT](
|
||||||
|
_state,
|
||||||
|
{ id, lastSeen, unreadCount = 0 }
|
||||||
|
) {
|
||||||
const [chat] = _state.allConversations.filter(c => c.id === id);
|
const [chat] = _state.allConversations.filter(c => c.id === id);
|
||||||
if (chat) {
|
if (chat) {
|
||||||
chat.agent_last_seen_at = lastSeen;
|
Vue.set(chat, 'agent_last_seen_at', lastSeen);
|
||||||
|
Vue.set(chat, 'unread_count', unreadCount);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
[types.CHANGE_CHAT_STATUS_FILTER](_state, data) {
|
[types.CHANGE_CHAT_STATUS_FILTER](_state, data) {
|
||||||
_state.chatStatusFilter = data;
|
_state.chatStatusFilter = data;
|
||||||
},
|
},
|
||||||
|
|
|
@ -245,7 +245,7 @@ describe('#actions', () => {
|
||||||
jest.runAllTimers();
|
jest.runAllTimers();
|
||||||
expect(commit).toHaveBeenCalledTimes(1);
|
expect(commit).toHaveBeenCalledTimes(1);
|
||||||
expect(commit.mock.calls).toEqual([
|
expect(commit.mock.calls).toEqual([
|
||||||
[types.MARK_MESSAGE_READ, { id: 1, lastSeen }],
|
[types.UPDATE_MESSAGE_UNREAD_COUNT, { id: 1, lastSeen }],
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
it('sends correct mutations if api is unsuccessful', async () => {
|
it('sends correct mutations if api is unsuccessful', async () => {
|
||||||
|
@ -255,6 +255,30 @@ describe('#actions', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('#markMessagesUnread', () => {
|
||||||
|
it('sends correct mutations if API is successful', async () => {
|
||||||
|
const lastSeen = new Date().getTime() / 1000;
|
||||||
|
axios.post.mockResolvedValue({
|
||||||
|
data: { id: 1, agent_last_seen_at: lastSeen, unread_count: 1 },
|
||||||
|
});
|
||||||
|
await actions.markMessagesUnread({ commit }, { id: 1 });
|
||||||
|
jest.runAllTimers();
|
||||||
|
expect(commit).toHaveBeenCalledTimes(1);
|
||||||
|
expect(commit.mock.calls).toEqual([
|
||||||
|
[
|
||||||
|
types.UPDATE_MESSAGE_UNREAD_COUNT,
|
||||||
|
{ id: 1, lastSeen, unreadCount: 1 },
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
it('sends correct mutations if API is unsuccessful', async () => {
|
||||||
|
axios.post.mockRejectedValue({ message: 'Incorrect header' });
|
||||||
|
await expect(
|
||||||
|
actions.markMessagesUnread({ commit }, { id: 1 })
|
||||||
|
).rejects.toThrow(Error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('#sendEmailTranscript', () => {
|
describe('#sendEmailTranscript', () => {
|
||||||
it('sends correct mutations if api is successful', async () => {
|
it('sends correct mutations if api is successful', async () => {
|
||||||
axios.post.mockResolvedValue({});
|
axios.post.mockResolvedValue({});
|
||||||
|
|
|
@ -11,20 +11,20 @@ describe('#mutations', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('#MARK_MESSAGE_READ', () => {
|
describe('#UPDATE_MESSAGE_UNREAD_COUNT', () => {
|
||||||
it('mark conversation as read', () => {
|
it('mark conversation as read', () => {
|
||||||
const state = { allConversations: [{ id: 1 }] };
|
const state = { allConversations: [{ id: 1 }] };
|
||||||
const lastSeen = new Date().getTime() / 1000;
|
const lastSeen = new Date().getTime() / 1000;
|
||||||
mutations[types.MARK_MESSAGE_READ](state, { id: 1, lastSeen });
|
mutations[types.UPDATE_MESSAGE_UNREAD_COUNT](state, { id: 1, lastSeen });
|
||||||
expect(state.allConversations).toEqual([
|
expect(state.allConversations).toEqual([
|
||||||
{ id: 1, agent_last_seen_at: lastSeen },
|
{ id: 1, agent_last_seen_at: lastSeen, unread_count: 0 },
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('doesnot send any mutation if chat doesnot exist', () => {
|
it('doesnot send any mutation if chat doesnot exist', () => {
|
||||||
const state = { allConversations: [] };
|
const state = { allConversations: [] };
|
||||||
const lastSeen = new Date().getTime() / 1000;
|
const lastSeen = new Date().getTime() / 1000;
|
||||||
mutations[types.MARK_MESSAGE_READ](state, { id: 1, lastSeen });
|
mutations[types.UPDATE_MESSAGE_UNREAD_COUNT](state, { id: 1, lastSeen });
|
||||||
expect(state.allConversations).toEqual([]);
|
expect(state.allConversations).toEqual([]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -36,7 +36,7 @@ export default {
|
||||||
ADD_MESSAGE: 'ADD_MESSAGE',
|
ADD_MESSAGE: 'ADD_MESSAGE',
|
||||||
DELETE_MESSAGE: 'DELETE_MESSAGE',
|
DELETE_MESSAGE: 'DELETE_MESSAGE',
|
||||||
ADD_PENDING_MESSAGE: 'ADD_PENDING_MESSAGE',
|
ADD_PENDING_MESSAGE: 'ADD_PENDING_MESSAGE',
|
||||||
MARK_MESSAGE_READ: 'MARK_MESSAGE_READ',
|
UPDATE_MESSAGE_UNREAD_COUNT: 'UPDATE_MESSAGE_UNREAD_COUNT',
|
||||||
SET_PREVIOUS_CONVERSATIONS: 'SET_PREVIOUS_CONVERSATIONS',
|
SET_PREVIOUS_CONVERSATIONS: 'SET_PREVIOUS_CONVERSATIONS',
|
||||||
SET_ACTIVE_INBOX: 'SET_ACTIVE_INBOX',
|
SET_ACTIVE_INBOX: 'SET_ACTIVE_INBOX',
|
||||||
UPDATE_CONVERSATION_CUSTOM_ATTRIBUTES:
|
UPDATE_CONVERSATION_CUSTOM_ATTRIBUTES:
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
json.partial! 'api/v1/conversations/partials/conversation', formats: [:json], conversation: @conversation
|
|
@ -85,6 +85,7 @@ Rails.application.routes.draw do
|
||||||
post :toggle_status
|
post :toggle_status
|
||||||
post :toggle_typing_status
|
post :toggle_typing_status
|
||||||
post :update_last_seen
|
post :update_last_seen
|
||||||
|
post :unread
|
||||||
post :custom_attributes
|
post :custom_attributes
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -508,6 +508,38 @@ RSpec.describe 'Conversations API', type: :request do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe 'POST /api/v1/accounts/{account.id}/conversations/:id/unread' do
|
||||||
|
let(:conversation) { create(:conversation, account: account) }
|
||||||
|
|
||||||
|
context 'when it is an unauthenticated user' do
|
||||||
|
it 'returns unauthorized' do
|
||||||
|
post "/api/v1/accounts/#{account.id}/conversations/#{conversation.display_id}/unread"
|
||||||
|
|
||||||
|
expect(response).to have_http_status(:unauthorized)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when it is an authenticated user' do
|
||||||
|
let(:agent) { create(:user, account: account, role: :agent) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
create(:inbox_member, user: agent, inbox: conversation.inbox)
|
||||||
|
create(:message, conversation: conversation, account: account, inbox: conversation.inbox, content: 'Hello', message_type: 'incoming')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'updates last seen' do
|
||||||
|
post "/api/v1/accounts/#{account.id}/conversations/#{conversation.display_id}/unread",
|
||||||
|
headers: agent.create_new_auth_token,
|
||||||
|
as: :json
|
||||||
|
|
||||||
|
expect(response).to have_http_status(:success)
|
||||||
|
last_seen_at = conversation.messages.incoming.last.created_at - 1.second
|
||||||
|
expect(conversation.reload.agent_last_seen_at).to eq(last_seen_at)
|
||||||
|
expect(conversation.reload.assignee_last_seen_at).to eq(last_seen_at)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe 'POST /api/v1/accounts/{account.id}/conversations/:id/mute' do
|
describe 'POST /api/v1/accounts/{account.id}/conversations/:id/mute' do
|
||||||
let(:conversation) { create(:conversation, account: account) }
|
let(:conversation) { create(:conversation, account: account) }
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue