mirror of
https://github.com/lingble/chatwoot.git
synced 2025-11-02 03:57:52 +00:00
chore: Simplify reply message lookup with store-based fetching
This commit is contained in:
@@ -1,10 +1,9 @@
|
||||
<script setup>
|
||||
import { defineProps, computed, reactive } from 'vue';
|
||||
import { defineProps, computed, ref } from 'vue';
|
||||
import Message from './Message.vue';
|
||||
import { MESSAGE_TYPES } from './constants.js';
|
||||
import { useCamelCase } from 'dashboard/composables/useTransformKeys';
|
||||
import { useMapGetter } from 'dashboard/composables/store.js';
|
||||
import MessageApi from 'dashboard/api/inbox/message.js';
|
||||
import { useMapGetter, useStore } from 'dashboard/composables/store.js';
|
||||
|
||||
/**
|
||||
* Props definition for the component
|
||||
@@ -40,53 +39,15 @@ const props = defineProps({
|
||||
});
|
||||
|
||||
const emit = defineEmits(['retry']);
|
||||
const store = useStore();
|
||||
|
||||
const fetchingConversations = ref(new Set());
|
||||
const allMessages = computed(() => {
|
||||
return useCamelCase(props.messages, { deep: true });
|
||||
});
|
||||
|
||||
const currentChat = useMapGetter('getSelectedChat');
|
||||
|
||||
// Cache for fetched reply messages to avoid duplicate API calls
|
||||
const fetchedReplyMessages = reactive(new Map());
|
||||
|
||||
/**
|
||||
* Fetches a specific message from the API by trying to get messages around it
|
||||
* @param {number} messageId - The ID of the message to fetch
|
||||
* @param {number} conversationId - The ID of the conversation
|
||||
* @returns {Promise<Object|null>} - The fetched message or null if not found/error
|
||||
*/
|
||||
const fetchReplyMessage = async (messageId, conversationId) => {
|
||||
// Return cached result if already fetched
|
||||
if (fetchedReplyMessages.has(messageId)) {
|
||||
return fetchedReplyMessages.get(messageId);
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await MessageApi.getPreviousMessages({
|
||||
conversationId,
|
||||
before: messageId + 100,
|
||||
after: messageId - 100,
|
||||
});
|
||||
|
||||
const messages = response.data?.payload || [];
|
||||
const targetMessage = messages.find(msg => msg.id === messageId);
|
||||
|
||||
if (targetMessage) {
|
||||
const camelCaseMessage = useCamelCase(targetMessage);
|
||||
fetchedReplyMessages.set(messageId, camelCaseMessage);
|
||||
return camelCaseMessage;
|
||||
}
|
||||
|
||||
// Cache null result to avoid repeated API calls
|
||||
fetchedReplyMessages.set(messageId, null);
|
||||
return null;
|
||||
} catch (error) {
|
||||
fetchedReplyMessages.set(messageId, null);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Determines if a message should be grouped with the next message
|
||||
* @param {Number} index - Index of the current message
|
||||
@@ -126,36 +87,43 @@ const shouldGroupWithNext = (index, searchList) => {
|
||||
* @returns {Object|null} - The message being replied to, or null if not found
|
||||
*/
|
||||
const getInReplyToMessage = parentMessage => {
|
||||
if (!parentMessage) return null;
|
||||
|
||||
const inReplyToMessageId =
|
||||
parentMessage.contentAttributes?.inReplyTo ??
|
||||
parentMessage.content_attributes?.in_reply_to;
|
||||
parentMessage?.contentAttributes?.inReplyTo ??
|
||||
parentMessage?.content_attributes?.in_reply_to;
|
||||
|
||||
if (!inReplyToMessageId) return null;
|
||||
|
||||
// Try to find in current messages first
|
||||
let replyMessage = props.messages?.find(msg => msg.id === inReplyToMessageId);
|
||||
// 1. Check props messages (already camelCased via allMessages)
|
||||
const foundInProps = allMessages.value?.find(
|
||||
m => m.id === inReplyToMessageId
|
||||
);
|
||||
if (foundInProps) return foundInProps;
|
||||
|
||||
// Then try store messages
|
||||
if (!replyMessage && currentChat.value?.messages) {
|
||||
replyMessage = currentChat.value.messages.find(
|
||||
msg => msg.id === inReplyToMessageId
|
||||
);
|
||||
// 2. Check store messages
|
||||
const foundInStore = currentChat.value?.messages?.find(
|
||||
m => m.id === inReplyToMessageId
|
||||
);
|
||||
if (foundInStore) return useCamelCase(foundInStore);
|
||||
|
||||
// 3. Fetch if not currently fetching for this conversation
|
||||
const conversationId = currentChat.value?.id;
|
||||
if (
|
||||
conversationId &&
|
||||
!currentChat.value.allMessagesLoaded &&
|
||||
!fetchingConversations.value.has(conversationId)
|
||||
) {
|
||||
fetchingConversations.value.add(conversationId);
|
||||
store
|
||||
.dispatch('fetchPreviousMessages', {
|
||||
conversationId,
|
||||
before: currentChat.value.messages?.[0]?.id ?? null,
|
||||
})
|
||||
.finally(() => {
|
||||
fetchingConversations.value.delete(conversationId);
|
||||
});
|
||||
}
|
||||
|
||||
// Then check fetch cache
|
||||
if (!replyMessage && fetchedReplyMessages.has(inReplyToMessageId)) {
|
||||
replyMessage = fetchedReplyMessages.get(inReplyToMessageId);
|
||||
}
|
||||
|
||||
// If still not found and we have conversation context, fetch it
|
||||
if (!replyMessage && currentChat.value?.id) {
|
||||
fetchReplyMessage(inReplyToMessageId, currentChat.value.id);
|
||||
return null; // Let UI handle loading state
|
||||
}
|
||||
|
||||
return replyMessage ? useCamelCase(replyMessage) : null;
|
||||
return null;
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
@@ -74,9 +74,17 @@ export const mutations = {
|
||||
[types.SET_PREVIOUS_CONVERSATIONS](_state, { id, data }) {
|
||||
if (data.length) {
|
||||
const [chat] = _state.allConversations.filter(c => c.id === id);
|
||||
chat.messages.unshift(...data);
|
||||
if (chat) {
|
||||
// Duplicate check: only add messages that don't already exist
|
||||
const existingIds = new Set(chat.messages.map(m => m.id));
|
||||
const newMessages = data.filter(m => !existingIds.has(m.id));
|
||||
if (newMessages.length) {
|
||||
chat.messages.unshift(...newMessages);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
[types.SET_ALL_ATTACHMENTS](_state, { id, data }) {
|
||||
_state.attachments[id] = [...data];
|
||||
},
|
||||
|
||||
@@ -615,6 +615,29 @@ describe('#mutations', () => {
|
||||
mutations[types.SET_PREVIOUS_CONVERSATIONS](state, payload);
|
||||
expect(state.allConversations[0].messages).toEqual([{ id: 'msg2' }]);
|
||||
});
|
||||
|
||||
it('should filter out duplicate messages', () => {
|
||||
const state = {
|
||||
allConversations: [{ id: 1, messages: [{ id: 'msg2' }] }],
|
||||
};
|
||||
const payload = { id: 1, data: [{ id: 'msg1' }, { id: 'msg2' }] };
|
||||
|
||||
mutations[types.SET_PREVIOUS_CONVERSATIONS](state, payload);
|
||||
expect(state.allConversations[0].messages).toEqual([
|
||||
{ id: 'msg1' },
|
||||
{ id: 'msg2' },
|
||||
]);
|
||||
});
|
||||
|
||||
it('should do nothing if conversation is not found', () => {
|
||||
const state = {
|
||||
allConversations: [{ id: 2, messages: [{ id: 'msg2' }] }],
|
||||
};
|
||||
const payload = { id: 1, data: [{ id: 'msg1' }] };
|
||||
|
||||
mutations[types.SET_PREVIOUS_CONVERSATIONS](state, payload);
|
||||
expect(state.allConversations[0].messages).toEqual([{ id: 'msg2' }]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#SET_MISSING_MESSAGES', () => {
|
||||
|
||||
Reference in New Issue
Block a user