diff --git a/app/javascript/dashboard/store/modules/conversations/index.js b/app/javascript/dashboard/store/modules/conversations/index.js index 22f9256b9..cb1507af3 100644 --- a/app/javascript/dashboard/store/modules/conversations/index.js +++ b/app/javascript/dashboard/store/modules/conversations/index.js @@ -208,22 +208,24 @@ 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]; + + // Skip if incoming data is older + 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); } } else { - _state.allConversations.push(conversation); + allConversations.push(conversation); } }, diff --git a/app/javascript/dashboard/store/modules/conversations/specs/mutations.spec.js b/app/javascript/dashboard/store/modules/conversations/specs/mutations.spec.js new file mode 100644 index 000000000..8cccbc5ae --- /dev/null +++ b/app/javascript/dashboard/store/modules/conversations/specs/mutations.spec.js @@ -0,0 +1,127 @@ +import { emitter } from 'shared/helpers/mitt'; +import { mutations } from '..'; +import { BUS_EVENTS } from '../../../../../shared/constants/busEvents'; +import types from '../../../mutation-types'; + +vi.mock('shared/helpers/mitt', () => ({ + emitter: { + emit: vi.fn(), + }, +})); + +describe('UPDATE_CONVERSATION', () => { + let state; + + beforeEach(() => { + state = { + allConversations: [], + selectedChatId: null, + }; + vi.clearAllMocks(); + }); + + it('should add new conversation if not found', () => { + const conversation = { + id: 123, + title: 'Test', + updated_at: Math.floor(Date.now() / 1000), + }; + + mutations[types.UPDATE_CONVERSATION](state, conversation); + + expect(state.allConversations).toHaveLength(1); + expect(state.allConversations[0]).toEqual(conversation); + }); + + it('should skip update if incoming data is older', () => { + const oldTimestamp = Math.floor(new Date('2024-01-01').getTime() / 1000); + const newTimestamp = Math.floor(new Date('2024-01-02').getTime() / 1000); + + const existing = { + id: 123, + title: 'Original', + updated_at: newTimestamp, + messages: ['msg1'], + }; + + const update = { + id: 123, + title: 'Updated', + updated_at: oldTimestamp, + }; + + state.allConversations = [existing]; + mutations[types.UPDATE_CONVERSATION](state, update); + + expect(state.allConversations[0]).toEqual(existing); + }); + + it('should update properties except messages', () => { + const timestamp1 = Math.floor(new Date('2024-01-01').getTime() / 1000); + const timestamp2 = Math.floor(new Date('2024-01-02').getTime() / 1000); + + const existing = { + id: 123, + title: 'Original', + status: 'draft', + updated_at: timestamp1, + messages: ['msg1'], + }; + + const update = { + id: 123, + title: 'Updated', + status: 'published', + updated_at: timestamp2, + messages: ['msg2'], + }; + + state.allConversations = [existing]; + mutations[types.UPDATE_CONVERSATION](state, update); + + expect(state.allConversations[0]).toEqual({ + id: 123, + title: 'Updated', + status: 'published', + updated_at: timestamp2, + messages: ['msg1'], + }); + }); + + it('should emit events when updating selected conversation', () => { + const existing = { + id: 123, + title: 'Original', + status: 'draft', + updated_at: Math.floor(Date.now() / 1000), + messages: ['msg1'], + }; + + const conversation = { + id: 123, + updated_at: Math.floor(Date.now() / 1000) + 1, + }; + + state.selectedChatId = 123; + state.allConversations = [existing]; + + mutations[types.UPDATE_CONVERSATION](state, conversation); + + expect(emitter.emit).toHaveBeenCalledWith( + BUS_EVENTS.FETCH_LABEL_SUGGESTIONS + ); + expect(emitter.emit).toHaveBeenCalledWith(BUS_EVENTS.SCROLL_TO_MESSAGE); + }); + + it('should not emit events when updating non-selected conversation', () => { + const conversation = { + id: 123, + updated_at: Math.floor(Date.now() / 1000), + }; + + state.selectedChatId = '456'; + mutations[types.UPDATE_CONVERSATION](state, conversation); + + expect(emitter.emit).not.toHaveBeenCalled(); + }); +}); diff --git a/app/presenters/conversations/event_data_presenter.rb b/app/presenters/conversations/event_data_presenter.rb index 2617721ec..1ba188de8 100644 --- a/app/presenters/conversations/event_data_presenter.rb +++ b/app/presenters/conversations/event_data_presenter.rb @@ -42,7 +42,8 @@ class Conversations::EventDataPresenter < SimpleDelegator contact_last_seen_at: contact_last_seen_at.to_i, last_activity_at: last_activity_at.to_i, timestamp: last_activity_at.to_i, - created_at: created_at.to_i + created_at: created_at.to_i, + updated_at: updated_at.to_i } end end diff --git a/app/views/api/v1/conversations/partials/_conversation.json.jbuilder b/app/views/api/v1/conversations/partials/_conversation.json.jbuilder index a9a580e26..c32ef4a10 100644 --- a/app/views/api/v1/conversations/partials/_conversation.json.jbuilder +++ b/app/views/api/v1/conversations/partials/_conversation.json.jbuilder @@ -31,6 +31,7 @@ else end json.account_id conversation.account_id +json.updated_at conversation.updated_at json.uuid conversation.uuid json.additional_attributes conversation.additional_attributes json.agent_last_seen_at conversation.agent_last_seen_at.to_i diff --git a/spec/presenters/conversations/event_data_presenter_spec.rb b/spec/presenters/conversations/event_data_presenter_spec.rb index f6a400b96..6f2534efb 100644 --- a/spec/presenters/conversations/event_data_presenter_spec.rb +++ b/spec/presenters/conversations/event_data_presenter_spec.rb @@ -31,6 +31,7 @@ RSpec.describe Conversations::EventDataPresenter do contact_last_seen_at: conversation.contact_last_seen_at.to_i, agent_last_seen_at: conversation.agent_last_seen_at.to_i, created_at: conversation.created_at.to_i, + updated_at: conversation.updated_at.to_i, waiting_since: conversation.waiting_since.to_i, priority: nil, unread_count: 0