feat: ignore out of sync messages (#11058)
Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
This commit is contained in:
@@ -6,6 +6,7 @@ import { MESSAGE_STATUS } from 'shared/constants/messages';
|
||||
import wootConstants from 'dashboard/constants/globals';
|
||||
import { BUS_EVENTS } from '../../../../shared/constants/busEvents';
|
||||
import { emitter } from 'shared/helpers/mitt';
|
||||
import * as Sentry from '@sentry/vue';
|
||||
|
||||
const state = {
|
||||
allConversations: [],
|
||||
@@ -209,16 +210,30 @@ export const mutations = {
|
||||
|
||||
[types.UPDATE_CONVERSATION](_state, conversation) {
|
||||
const { allConversations } = _state;
|
||||
const currentConversationIndex = allConversations.findIndex(
|
||||
c => c.id === conversation.id
|
||||
);
|
||||
if (currentConversationIndex > -1) {
|
||||
const { messages, ...conversationAttributes } = conversation;
|
||||
const currentConversation = {
|
||||
...allConversations[currentConversationIndex],
|
||||
...conversationAttributes,
|
||||
};
|
||||
allConversations[currentConversationIndex] = currentConversation;
|
||||
const index = allConversations.findIndex(c => c.id === conversation.id);
|
||||
|
||||
if (index > -1) {
|
||||
const selectedConversation = allConversations[index];
|
||||
|
||||
// ignore out of order events
|
||||
if (conversation.updated_at < selectedConversation.updated_at) {
|
||||
Sentry.withScope(scope => {
|
||||
scope.setContext('incoming', conversation);
|
||||
scope.setContext('stored', selectedConversation);
|
||||
scope.setContext('incoming_meta', conversation.meta);
|
||||
scope.setContext('stored_meta', selectedConversation.meta);
|
||||
Sentry.captureMessage('Conversation update mismatch');
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (conversation.updated_at === selectedConversation.updated_at) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { messages, ...updates } = conversation;
|
||||
allConversations[index] = { ...selectedConversation, ...updates };
|
||||
if (_state.selectedChatId === conversation.id) {
|
||||
emitter.emit(BUS_EVENTS.FETCH_LABEL_SUGGESTIONS);
|
||||
emitter.emit(BUS_EVENTS.SCROLL_TO_MESSAGE);
|
||||
|
||||
@@ -569,4 +569,382 @@ describe('#mutations', () => {
|
||||
expect(state.copilotAssistant).toEqual(data.assistant);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#SET_ALL_MESSAGES_LOADED', () => {
|
||||
it('should set allMessagesLoaded to true on selected chat', () => {
|
||||
const state = {
|
||||
allConversations: [{ id: 1, allMessagesLoaded: false }],
|
||||
selectedChatId: 1,
|
||||
};
|
||||
mutations[types.SET_ALL_MESSAGES_LOADED](state);
|
||||
expect(state.allConversations[0].allMessagesLoaded).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#CLEAR_ALL_MESSAGES_LOADED', () => {
|
||||
it('should set allMessagesLoaded to false on selected chat', () => {
|
||||
const state = {
|
||||
allConversations: [{ id: 1, allMessagesLoaded: true }],
|
||||
selectedChatId: 1,
|
||||
};
|
||||
mutations[types.CLEAR_ALL_MESSAGES_LOADED](state);
|
||||
expect(state.allConversations[0].allMessagesLoaded).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#SET_PREVIOUS_CONVERSATIONS', () => {
|
||||
it('should prepend messages to conversation messages array', () => {
|
||||
const state = {
|
||||
allConversations: [{ id: 1, messages: [{ id: 'msg2' }] }],
|
||||
};
|
||||
const payload = { id: 1, data: [{ id: 'msg1' }] };
|
||||
|
||||
mutations[types.SET_PREVIOUS_CONVERSATIONS](state, payload);
|
||||
expect(state.allConversations[0].messages).toEqual([
|
||||
{ id: 'msg1' },
|
||||
{ id: 'msg2' },
|
||||
]);
|
||||
});
|
||||
|
||||
it('should not modify messages if data is empty', () => {
|
||||
const state = {
|
||||
allConversations: [{ id: 1, messages: [{ id: 'msg2' }] }],
|
||||
};
|
||||
const payload = { id: 1, data: [] };
|
||||
|
||||
mutations[types.SET_PREVIOUS_CONVERSATIONS](state, payload);
|
||||
expect(state.allConversations[0].messages).toEqual([{ id: 'msg2' }]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#SET_MISSING_MESSAGES', () => {
|
||||
it('should replace message array with new data', () => {
|
||||
const state = {
|
||||
allConversations: [{ id: 1, messages: [{ id: 'old' }] }],
|
||||
};
|
||||
const payload = { id: 1, data: [{ id: 'new' }] };
|
||||
|
||||
mutations[types.SET_MISSING_MESSAGES](state, payload);
|
||||
expect(state.allConversations[0].messages).toEqual([{ id: 'new' }]);
|
||||
});
|
||||
|
||||
it('should do nothing if conversation is not found', () => {
|
||||
const state = {
|
||||
allConversations: [],
|
||||
};
|
||||
const payload = { id: 1, data: [{ id: 'new' }] };
|
||||
|
||||
mutations[types.SET_MISSING_MESSAGES](state, payload);
|
||||
expect(state.allConversations).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#ASSIGN_AGENT', () => {
|
||||
it('should assign agent to selected conversation', () => {
|
||||
const assignee = { id: 1, name: 'Agent' };
|
||||
const state = {
|
||||
allConversations: [{ id: 1, meta: {} }],
|
||||
selectedChatId: 1,
|
||||
};
|
||||
|
||||
mutations[types.ASSIGN_AGENT](state, assignee);
|
||||
expect(state.allConversations[0].meta.assignee).toEqual(assignee);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#ASSIGN_PRIORITY', () => {
|
||||
it('should assign priority to conversation', () => {
|
||||
const priority = { title: 'Urgent', value: 'urgent' };
|
||||
const state = {
|
||||
allConversations: [{ id: 1 }],
|
||||
};
|
||||
|
||||
mutations[types.ASSIGN_PRIORITY](state, {
|
||||
priority,
|
||||
conversationId: 1,
|
||||
});
|
||||
expect(state.allConversations[0].priority).toEqual(priority);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#MUTE_CONVERSATION', () => {
|
||||
it('should mute selected conversation', () => {
|
||||
const state = {
|
||||
allConversations: [{ id: 1, muted: false }],
|
||||
selectedChatId: 1,
|
||||
};
|
||||
|
||||
mutations[types.MUTE_CONVERSATION](state);
|
||||
expect(state.allConversations[0].muted).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#UNMUTE_CONVERSATION', () => {
|
||||
it('should unmute selected conversation', () => {
|
||||
const state = {
|
||||
allConversations: [{ id: 1, muted: true }],
|
||||
selectedChatId: 1,
|
||||
};
|
||||
|
||||
mutations[types.UNMUTE_CONVERSATION](state);
|
||||
expect(state.allConversations[0].muted).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#UPDATE_CONVERSATION', () => {
|
||||
it('should update existing conversation', () => {
|
||||
const state = {
|
||||
allConversations: [
|
||||
{
|
||||
id: 1,
|
||||
status: 'open',
|
||||
updated_at: 100,
|
||||
messages: [{ id: 'msg1' }],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const conversation = {
|
||||
id: 1,
|
||||
status: 'resolved',
|
||||
updated_at: 200,
|
||||
messages: [{ id: 'msg2' }],
|
||||
};
|
||||
|
||||
mutations[types.UPDATE_CONVERSATION](state, conversation);
|
||||
expect(state.allConversations[0]).toEqual({
|
||||
id: 1,
|
||||
status: 'resolved',
|
||||
updated_at: 200,
|
||||
messages: [{ id: 'msg1' }],
|
||||
});
|
||||
});
|
||||
|
||||
it('should add conversation if not found', () => {
|
||||
const state = {
|
||||
allConversations: [],
|
||||
};
|
||||
|
||||
const conversation = {
|
||||
id: 1,
|
||||
status: 'open',
|
||||
};
|
||||
|
||||
mutations[types.UPDATE_CONVERSATION](state, conversation);
|
||||
expect(state.allConversations).toEqual([conversation]);
|
||||
});
|
||||
|
||||
it('should emit events if updating selected conversation', () => {
|
||||
const state = {
|
||||
allConversations: [
|
||||
{
|
||||
id: 1,
|
||||
status: 'open',
|
||||
updated_at: 100,
|
||||
},
|
||||
],
|
||||
selectedChatId: 1,
|
||||
};
|
||||
|
||||
const conversation = {
|
||||
id: 1,
|
||||
status: 'resolved',
|
||||
updated_at: 200,
|
||||
};
|
||||
|
||||
mutations[types.UPDATE_CONVERSATION](state, conversation);
|
||||
expect(emitter.emit).toHaveBeenCalledWith('FETCH_LABEL_SUGGESTIONS');
|
||||
expect(emitter.emit).toHaveBeenCalledWith('SCROLL_TO_MESSAGE');
|
||||
});
|
||||
|
||||
it('should ignore updates with older timestamps', () => {
|
||||
const state = {
|
||||
allConversations: [
|
||||
{
|
||||
id: 1,
|
||||
status: 'open',
|
||||
updated_at: 200,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const conversation = {
|
||||
id: 1,
|
||||
status: 'resolved',
|
||||
updated_at: 100,
|
||||
};
|
||||
|
||||
mutations[types.UPDATE_CONVERSATION](state, conversation);
|
||||
expect(state.allConversations[0].status).toEqual('open');
|
||||
});
|
||||
|
||||
it('should ignore updates with same timestamps', () => {
|
||||
const state = {
|
||||
allConversations: [
|
||||
{
|
||||
id: 1,
|
||||
status: 'open',
|
||||
updated_at: 100,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const conversation = {
|
||||
id: 1,
|
||||
status: 'resolved',
|
||||
updated_at: 100,
|
||||
};
|
||||
|
||||
mutations[types.UPDATE_CONVERSATION](state, conversation);
|
||||
expect(state.allConversations[0].status).toEqual('open');
|
||||
});
|
||||
});
|
||||
|
||||
describe('#UPDATE_CONVERSATION_CONTACT', () => {
|
||||
it('should update conversation contact data', () => {
|
||||
const state = {
|
||||
allConversations: [
|
||||
{ id: 1, meta: { sender: { id: 1, name: 'Old Name' } } },
|
||||
],
|
||||
};
|
||||
|
||||
const payload = {
|
||||
conversationId: 1,
|
||||
id: 1,
|
||||
name: 'New Name',
|
||||
};
|
||||
|
||||
mutations[types.UPDATE_CONVERSATION_CONTACT](state, payload);
|
||||
// The mutation extracts all properties except conversationId
|
||||
const { conversationId, ...contact } = payload;
|
||||
expect(state.allConversations[0].meta.sender).toEqual(contact);
|
||||
});
|
||||
|
||||
it('should do nothing if conversation is not found', () => {
|
||||
const state = {
|
||||
allConversations: [],
|
||||
};
|
||||
|
||||
const payload = {
|
||||
conversationId: 1,
|
||||
id: 1,
|
||||
name: 'New Name',
|
||||
};
|
||||
|
||||
mutations[types.UPDATE_CONVERSATION_CONTACT](state, payload);
|
||||
expect(state.allConversations).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#SET_ACTIVE_INBOX', () => {
|
||||
it('should set current inbox as integer', () => {
|
||||
const state = {
|
||||
currentInbox: null,
|
||||
};
|
||||
|
||||
mutations[types.SET_ACTIVE_INBOX](state, '1');
|
||||
expect(state.currentInbox).toBe(1);
|
||||
});
|
||||
|
||||
it('should set null if no inbox ID provided', () => {
|
||||
const state = {
|
||||
currentInbox: 1,
|
||||
};
|
||||
|
||||
mutations[types.SET_ACTIVE_INBOX](state, null);
|
||||
expect(state.currentInbox).toBe(null);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#CLEAR_CONTACT_CONVERSATIONS', () => {
|
||||
it('should remove all conversations with matching contact ID', () => {
|
||||
const state = {
|
||||
allConversations: [
|
||||
{ id: 1, meta: { sender: { id: 1 } } },
|
||||
{ id: 2, meta: { sender: { id: 2 } } },
|
||||
{ id: 3, meta: { sender: { id: 1 } } },
|
||||
],
|
||||
};
|
||||
|
||||
mutations[types.CLEAR_CONTACT_CONVERSATIONS](state, 1);
|
||||
expect(state.allConversations).toHaveLength(1);
|
||||
expect(state.allConversations[0].id).toBe(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#ADD_CONVERSATION', () => {
|
||||
it('should add a new conversation', () => {
|
||||
const state = {
|
||||
allConversations: [],
|
||||
};
|
||||
|
||||
const conversation = { id: 1, messages: [] };
|
||||
mutations[types.ADD_CONVERSATION](state, conversation);
|
||||
expect(state.allConversations).toEqual([conversation]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#SET_LIST_LOADING_STATUS', () => {
|
||||
it('should set listLoadingStatus to true', () => {
|
||||
const state = {
|
||||
listLoadingStatus: false,
|
||||
};
|
||||
|
||||
mutations[types.SET_LIST_LOADING_STATUS](state);
|
||||
expect(state.listLoadingStatus).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#CLEAR_LIST_LOADING_STATUS', () => {
|
||||
it('should set listLoadingStatus to false', () => {
|
||||
const state = {
|
||||
listLoadingStatus: true,
|
||||
};
|
||||
|
||||
mutations[types.CLEAR_LIST_LOADING_STATUS](state);
|
||||
expect(state.listLoadingStatus).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#CHANGE_CHAT_STATUS_FILTER', () => {
|
||||
it('should update chat status filter', () => {
|
||||
const state = {
|
||||
chatStatusFilter: 'open',
|
||||
};
|
||||
|
||||
mutations[types.CHANGE_CHAT_STATUS_FILTER](state, 'resolved');
|
||||
expect(state.chatStatusFilter).toBe('resolved');
|
||||
});
|
||||
});
|
||||
|
||||
describe('#UPDATE_ASSIGNEE', () => {
|
||||
it('should update assignee on conversation', () => {
|
||||
const state = {
|
||||
allConversations: [{ id: 1, meta: { assignee: null } }],
|
||||
};
|
||||
|
||||
const payload = {
|
||||
id: 1,
|
||||
assignee: { id: 1, name: 'Agent' },
|
||||
};
|
||||
|
||||
mutations[types.UPDATE_ASSIGNEE](state, payload);
|
||||
expect(state.allConversations[0].meta.assignee).toEqual(payload.assignee);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#SET_LAST_MESSAGE_ID_IN_SYNC_CONVERSATION', () => {
|
||||
it('should update the sync conversation message ID', () => {
|
||||
const state = {
|
||||
syncConversationsMessages: {},
|
||||
};
|
||||
|
||||
mutations[types.SET_LAST_MESSAGE_ID_IN_SYNC_CONVERSATION](state, {
|
||||
conversationId: 1,
|
||||
messageId: 100,
|
||||
});
|
||||
|
||||
expect(state.syncConversationsMessages[1]).toBe(100);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user