diff --git a/app/controllers/api/v1/accounts/contacts/conversations_controller.rb b/app/controllers/api/v1/accounts/contacts/conversations_controller.rb index cadfe133f..5e0a0e55e 100644 --- a/app/controllers/api/v1/accounts/contacts/conversations_controller.rb +++ b/app/controllers/api/v1/accounts/contacts/conversations_controller.rb @@ -2,7 +2,7 @@ class Api::V1::Accounts::Contacts::ConversationsController < Api::V1::Accounts:: def index @conversations = Current.account.conversations.includes( :assignee, :contact, :inbox, :taggings - ).where(inbox_id: inbox_ids, contact_id: @contact.id) + ).where(inbox_id: inbox_ids, contact_id: @contact.id).order(id: :desc).limit(20) end private diff --git a/app/javascript/dashboard/mixins/conversations.js b/app/javascript/dashboard/mixins/conversations.js index 3b1def4fc..adcd2e802 100644 --- a/app/javascript/dashboard/mixins/conversations.js +++ b/app/javascript/dashboard/mixins/conversations.js @@ -1,11 +1,38 @@ -/* eslint no-console: 0 */ -/* eslint no-undef: "error" */ -/* eslint no-unused-expressions: ["error", { "allowShortCircuit": true }] */ +const getLastNonActivityMessage = (messageInStore, messageFromAPI) => { + // If both API value and store value for last non activity message + // are available, then return the latest one. + if (messageInStore && messageFromAPI) { + if (messageInStore.created_at >= messageFromAPI.created_at) { + return messageInStore; + } + return messageFromAPI; + } + + // Otherwise, return whichever is available + return messageInStore || messageFromAPI; +}; export default { methods: { lastMessage(m) { - return m.messages.last(); + let lastMessageIncludingActivity = m.messages.last(); + + const nonActivityMessages = m.messages.filter( + message => message.message_type !== 2 + ); + let lastNonActivityMessageInStore = nonActivityMessages.last(); + let lastNonActivityMessageFromAPI = m.last_non_activity_message; + + // If API value and store value for last non activity message + // is empty, then return the last activity message + if (!lastNonActivityMessageInStore && !lastNonActivityMessageFromAPI) { + return lastMessageIncludingActivity; + } + + return getLastNonActivityMessage( + lastNonActivityMessageInStore, + lastNonActivityMessageFromAPI + ); }, unreadMessagesCount(m) { return m.messages.filter( diff --git a/app/javascript/dashboard/mixins/specs/conversation.spec.js b/app/javascript/dashboard/mixins/specs/conversation.spec.js new file mode 100644 index 000000000..0eb6c4fc3 --- /dev/null +++ b/app/javascript/dashboard/mixins/specs/conversation.spec.js @@ -0,0 +1,100 @@ +import conversationMixin from '../conversations'; +import conversationFixture from './conversationFixtures'; +import commonHelpers from '../../helper/commons'; +commonHelpers(); + +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', () => { + expect( + conversationMixin.methods.readMessages(conversationFixture.conversation) + ).toEqual(conversationFixture.readMessages); + }); + it('should return read messages if conversation is passed', () => { + expect( + conversationMixin.methods.unReadMessages(conversationFixture.conversation) + ).toEqual(conversationFixture.unReadMessages); + }); + + describe('#lastMessage', () => { + it("should return last activity message if both api and store doesn't have other messages", () => { + const conversation = { + messages: [ + { id: 1, created_at: 1654333, message_type: 2, content: 'Hey' }, + ], + last_non_activity_message: null, + }; + const { messages } = conversation; + expect(conversationMixin.methods.lastMessage(conversation)).toEqual( + messages[messages.length - 1] + ); + }); + + it('should return message from store if store has latest message', () => { + const conversation = { + messages: [], + last_non_activity_message: { + id: 2, + created_at: 1654334, + message_type: 2, + content: 'Hey', + }, + }; + expect(conversationMixin.methods.lastMessage(conversation)).toEqual( + conversation.last_non_activity_message + ); + }); + + it('should return last non activity message from store if api value is empty', () => { + const conversation = { + messages: [ + { + id: 1, + created_at: 1654333, + message_type: 1, + content: 'Outgoing Message', + }, + { id: 2, created_at: 1654334, message_type: 2, content: 'Hey' }, + ], + last_non_activity_message: null, + }; + expect(conversationMixin.methods.lastMessage(conversation)).toEqual( + conversation.messages[0] + ); + }); + + it("should return last non activity message from store if store doesn't have any messages", () => { + const conversation = { + messages: [ + { + id: 1, + created_at: 1654333, + message_type: 1, + content: 'Outgoing Message', + }, + { + id: 3, + created_at: 1654335, + message_type: 0, + content: 'Incoming Message', + }, + ], + last_non_activity_message: { + id: 2, + created_at: 1654334, + message_type: 2, + content: 'Hey', + }, + }; + expect(conversationMixin.methods.lastMessage(conversation)).toEqual( + conversation.messages[1] + ); + }); + }); +}); diff --git a/app/javascript/dashboard/mixins/specs/conversaton.spec.js b/app/javascript/dashboard/mixins/specs/conversaton.spec.js deleted file mode 100644 index eaa9aab97..000000000 --- a/app/javascript/dashboard/mixins/specs/conversaton.spec.js +++ /dev/null @@ -1,29 +0,0 @@ -import conversationMixin from '../conversations'; -import conversationFixture from './conversationFixtures'; -import commonHelpers from '../../helper/commons'; -commonHelpers(); - -describe('#conversationMixin', () => { - it('should return unread message count 2 if conversation is passed', () => { - expect( - conversationMixin.methods.unreadMessagesCount( - conversationFixture.conversation - ) - ).toEqual(2); - }); - it('should return last message if conversation is passed', () => { - expect( - conversationMixin.methods.lastMessage(conversationFixture.conversation) - ).toEqual(conversationFixture.lastMessage); - }); - it('should return read messages if conversation is passed', () => { - expect( - conversationMixin.methods.readMessages(conversationFixture.conversation) - ).toEqual(conversationFixture.readMessages); - }); - it('should return read messages if conversation is passed', () => { - expect( - conversationMixin.methods.unReadMessages(conversationFixture.conversation) - ).toEqual(conversationFixture.unReadMessages); - }); -}); diff --git a/app/models/message.rb b/app/models/message.rb index 7919c5053..2d37b54d6 100644 --- a/app/models/message.rb +++ b/app/models/message.rb @@ -73,6 +73,7 @@ class Message < ApplicationRecord # .succ is a hack to avoid https://makandracards.com/makandra/1057-why-two-ruby-time-objects-are-not-equal-although-they-appear-to-be scope :unread_since, ->(datetime) { where('EXTRACT(EPOCH FROM created_at) > (?)', datetime.to_i.succ) } scope :chat, -> { where.not(message_type: :activity).where(private: false) } + scope :non_activity_messages, -> { where.not(message_type: :activity).reorder('id desc') } scope :today, -> { where("date_trunc('day', created_at) = ?", Date.current) } default_scope { order(created_at: :asc) } diff --git a/app/views/api/v1/conversations/partials/_conversation.json.jbuilder b/app/views/api/v1/conversations/partials/_conversation.json.jbuilder index 24ba32a2b..0d5259a35 100644 --- a/app/views/api/v1/conversations/partials/_conversation.json.jbuilder +++ b/app/views/api/v1/conversations/partials/_conversation.json.jbuilder @@ -39,3 +39,4 @@ json.snoozed_until conversation.snoozed_until json.status conversation.status json.timestamp conversation.last_activity_at.to_i json.unread_count conversation.unread_incoming_messages.count +json.last_non_activity_message conversation.messages.non_activity_messages.first.try(:push_event_data)