mirror of
https://github.com/lingble/chatwoot.git
synced 2025-11-02 12:08:01 +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