mirror of
				https://github.com/lingble/chatwoot.git
				synced 2025-11-03 20:48:07 +00:00 
			
		
		
		
	feat: Show last non-activity messages in the chat list (#5864)
This commit is contained in:
		@@ -2,7 +2,7 @@ class Api::V1::Accounts::Contacts::ConversationsController < Api::V1::Accounts::
 | 
				
			|||||||
  def index
 | 
					  def index
 | 
				
			||||||
    @conversations = Current.account.conversations.includes(
 | 
					    @conversations = Current.account.conversations.includes(
 | 
				
			||||||
      :assignee, :contact, :inbox, :taggings
 | 
					      :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
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private
 | 
					  private
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,11 +1,38 @@
 | 
				
			|||||||
/* eslint no-console: 0 */
 | 
					const getLastNonActivityMessage = (messageInStore, messageFromAPI) => {
 | 
				
			||||||
/* eslint no-undef: "error" */
 | 
					  // If both API value and store value for last non activity message
 | 
				
			||||||
/* eslint no-unused-expressions: ["error", { "allowShortCircuit": true }] */
 | 
					  // 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 {
 | 
					export default {
 | 
				
			||||||
  methods: {
 | 
					  methods: {
 | 
				
			||||||
    lastMessage(m) {
 | 
					    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) {
 | 
					    unreadMessagesCount(m) {
 | 
				
			||||||
      return m.messages.filter(
 | 
					      return m.messages.filter(
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										100
									
								
								app/javascript/dashboard/mixins/specs/conversation.spec.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										100
									
								
								app/javascript/dashboard/mixins/specs/conversation.spec.js
									
									
									
									
									
										Normal file
									
								
							@@ -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]
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
@@ -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);
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
@@ -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
 | 
					  # .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 :unread_since, ->(datetime) { where('EXTRACT(EPOCH FROM created_at) > (?)', datetime.to_i.succ) }
 | 
				
			||||||
  scope :chat, -> { where.not(message_type: :activity).where(private: false) }
 | 
					  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) }
 | 
					  scope :today, -> { where("date_trunc('day', created_at) = ?", Date.current) }
 | 
				
			||||||
  default_scope { order(created_at: :asc) }
 | 
					  default_scope { order(created_at: :asc) }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -39,3 +39,4 @@ json.snoozed_until conversation.snoozed_until
 | 
				
			|||||||
json.status conversation.status
 | 
					json.status conversation.status
 | 
				
			||||||
json.timestamp conversation.last_activity_at.to_i
 | 
					json.timestamp conversation.last_activity_at.to_i
 | 
				
			||||||
json.unread_count conversation.unread_incoming_messages.count
 | 
					json.unread_count conversation.unread_incoming_messages.count
 | 
				
			||||||
 | 
					json.last_non_activity_message conversation.messages.non_activity_messages.first.try(:push_event_data)
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user