feat: Refetch the latest messages on action cable reconnect in widget (#6996)
This commit is contained in:
@@ -48,7 +48,8 @@ class Api::V1::Widget::MessagesController < Api::V1::Widget::BaseController
|
||||
def message_finder_params
|
||||
{
|
||||
filter_internal_messages: true,
|
||||
before: permitted_params[:before]
|
||||
before: permitted_params[:before],
|
||||
after: permitted_params[:after]
|
||||
}
|
||||
end
|
||||
|
||||
@@ -62,7 +63,7 @@ class Api::V1::Widget::MessagesController < Api::V1::Widget::BaseController
|
||||
|
||||
def permitted_params
|
||||
# timestamp parameter is used in create conversation method
|
||||
params.permit(:id, :before, :website_token, contact: [:name, :email], message: [:content, :referer_url, :timestamp, :echo_id])
|
||||
params.permit(:id, :before, :after, :website_token, contact: [:name, :email], message: [:content, :referer_url, :timestamp, :echo_id])
|
||||
end
|
||||
|
||||
def set_message
|
||||
|
||||
@@ -16,8 +16,8 @@ const sendAttachmentAPI = async attachment => {
|
||||
return API.post(urlData.url, urlData.params);
|
||||
};
|
||||
|
||||
const getMessagesAPI = async ({ before }) => {
|
||||
const urlData = endPoints.getConversation({ before });
|
||||
const getMessagesAPI = async ({ before, after }) => {
|
||||
const urlData = endPoints.getConversation({ before, after });
|
||||
return API.get(urlData.url, { params: urlData.params });
|
||||
};
|
||||
|
||||
|
||||
@@ -57,9 +57,9 @@ const sendAttachment = ({ attachment }) => {
|
||||
};
|
||||
};
|
||||
|
||||
const getConversation = ({ before }) => ({
|
||||
const getConversation = ({ before, after }) => ({
|
||||
url: `/api/v1/widget/messages${window.location.search}`,
|
||||
params: { before },
|
||||
params: { before, after },
|
||||
});
|
||||
|
||||
const updateMessage = id => ({
|
||||
|
||||
@@ -79,3 +79,26 @@ describe('#triggerCampaign', () => {
|
||||
spy.mockRestore();
|
||||
});
|
||||
});
|
||||
|
||||
describe('#getConversation', () => {
|
||||
it('should returns correct payload', () => {
|
||||
const spy = jest.spyOn(global, 'Date').mockImplementation(() => ({
|
||||
toString: () => 'mock date',
|
||||
}));
|
||||
const windowSpy = jest.spyOn(window, 'window', 'get');
|
||||
expect(
|
||||
endPoints.getConversation({
|
||||
after: 123,
|
||||
})
|
||||
).toEqual({
|
||||
url: `/api/v1/widget/messages`,
|
||||
params: {
|
||||
after: 123,
|
||||
before: undefined,
|
||||
},
|
||||
});
|
||||
windowSpy.mockRestore();
|
||||
|
||||
spy.mockRestore();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -25,6 +25,22 @@ class ActionCableConnector extends BaseActionCableConnector {
|
||||
};
|
||||
}
|
||||
|
||||
onDisconnected = () => {
|
||||
this.setLastMessageId();
|
||||
};
|
||||
|
||||
onReconnect = () => {
|
||||
this.syncLatestMessages();
|
||||
};
|
||||
|
||||
setLastMessageId = () => {
|
||||
this.app.$store.dispatch('conversation/setLastMessageId');
|
||||
};
|
||||
|
||||
syncLatestMessages = () => {
|
||||
this.app.$store.dispatch('conversation/syncLatestMessages');
|
||||
};
|
||||
|
||||
onStatusChange = data => {
|
||||
if (data.status === 'resolved') {
|
||||
this.app.$store.dispatch('campaign/resetCampaign');
|
||||
|
||||
@@ -52,6 +52,10 @@ export const actions = {
|
||||
}
|
||||
},
|
||||
|
||||
setLastMessageId: async ({ commit }) => {
|
||||
commit('setLastMessageId');
|
||||
},
|
||||
|
||||
sendAttachment: async ({ commit }, params) => {
|
||||
const {
|
||||
attachment: { thumbUrl, fileType },
|
||||
@@ -99,6 +103,36 @@ export const actions = {
|
||||
}
|
||||
},
|
||||
|
||||
syncLatestMessages: async ({ state, commit }) => {
|
||||
try {
|
||||
const { lastMessageId, conversations } = state;
|
||||
|
||||
const {
|
||||
data: { payload, meta },
|
||||
} = await getMessagesAPI({ after: lastMessageId });
|
||||
|
||||
const { contact_last_seen_at: lastSeen } = meta;
|
||||
const formattedMessages = getNonDeletedMessages({ messages: payload });
|
||||
const missingMessages = formattedMessages.filter(
|
||||
message => conversations?.[message.id] === undefined
|
||||
);
|
||||
if (!missingMessages.length) return;
|
||||
missingMessages.forEach(message => {
|
||||
conversations[message.id] = message;
|
||||
});
|
||||
// Sort conversation messages by created_at
|
||||
const updatedConversation = Object.fromEntries(
|
||||
Object.entries(conversations).sort(
|
||||
(a, b) => a[1].created_at - b[1].created_at
|
||||
)
|
||||
);
|
||||
commit('conversation/setMetaUserLastSeenAt', lastSeen, { root: true });
|
||||
commit('setMissingMessagesInConversation', updatedConversation);
|
||||
} catch (error) {
|
||||
// IgnoreError
|
||||
}
|
||||
},
|
||||
|
||||
clearConversations: ({ commit }) => {
|
||||
commit('clearConversations');
|
||||
},
|
||||
|
||||
@@ -13,6 +13,7 @@ const state = {
|
||||
isAgentTyping: false,
|
||||
isCreating: false,
|
||||
},
|
||||
lastMessageId: null,
|
||||
};
|
||||
|
||||
export default {
|
||||
|
||||
@@ -62,6 +62,10 @@ export const mutations = {
|
||||
payload.map(message => Vue.set($state.conversations, message.id, message));
|
||||
},
|
||||
|
||||
setMissingMessagesInConversation($state, payload) {
|
||||
Vue.set($state, 'conversation', payload);
|
||||
},
|
||||
|
||||
updateMessage($state, { id, content_attributes }) {
|
||||
$state.conversations[id] = {
|
||||
...$state.conversations[id],
|
||||
@@ -94,4 +98,12 @@ export const mutations = {
|
||||
setMetaUserLastSeenAt($state, lastSeen) {
|
||||
$state.meta.userLastSeenAt = lastSeen;
|
||||
},
|
||||
|
||||
setLastMessageId($state) {
|
||||
const { conversations } = $state;
|
||||
const lastMessage = Object.values(conversations).pop();
|
||||
if (!lastMessage) return;
|
||||
const { id } = lastMessage;
|
||||
$state.lastMessageId = id;
|
||||
},
|
||||
};
|
||||
|
||||
@@ -217,4 +217,209 @@ describe('#actions', () => {
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#syncLatestMessages', () => {
|
||||
it('latest message should append to end of list', async () => {
|
||||
const state = {
|
||||
uiFlags: { allMessagesLoaded: false },
|
||||
conversations: {
|
||||
'454': {
|
||||
id: 454,
|
||||
content: 'hi',
|
||||
message_type: 0,
|
||||
content_type: 'text',
|
||||
content_attributes: {},
|
||||
created_at: 1682244355, // Sunday, 23 April 2023 10:05:55
|
||||
conversation_id: 20,
|
||||
},
|
||||
'463': {
|
||||
id: 463,
|
||||
content: 'ss',
|
||||
message_type: 0,
|
||||
content_type: 'text',
|
||||
content_attributes: {},
|
||||
created_at: 1682490729, // Wednesday, 26 April 2023 06:32:09
|
||||
conversation_id: 20,
|
||||
},
|
||||
},
|
||||
lastMessageId: 463,
|
||||
};
|
||||
API.get.mockResolvedValue({
|
||||
data: {
|
||||
payload: [
|
||||
{
|
||||
id: 465,
|
||||
content: 'hi',
|
||||
message_type: 0,
|
||||
content_type: 'text',
|
||||
content_attributes: {},
|
||||
created_at: 1682504326, // Wednesday, 26 April 2023 10:18:46
|
||||
conversation_id: 20,
|
||||
},
|
||||
],
|
||||
meta: {
|
||||
contact_last_seen_at: 1466424490,
|
||||
},
|
||||
},
|
||||
});
|
||||
await actions.syncLatestMessages({ state, commit }, {});
|
||||
expect(commit.mock.calls).toEqual([
|
||||
['conversation/setMetaUserLastSeenAt', 1466424490, { root: true }],
|
||||
[
|
||||
'setMissingMessagesInConversation',
|
||||
|
||||
{
|
||||
'454': {
|
||||
id: 454,
|
||||
content: 'hi',
|
||||
message_type: 0,
|
||||
content_type: 'text',
|
||||
content_attributes: {},
|
||||
created_at: 1682244355,
|
||||
conversation_id: 20,
|
||||
},
|
||||
'463': {
|
||||
id: 463,
|
||||
content: 'ss',
|
||||
message_type: 0,
|
||||
content_type: 'text',
|
||||
content_attributes: {},
|
||||
created_at: 1682490729,
|
||||
conversation_id: 20,
|
||||
},
|
||||
'465': {
|
||||
id: 465,
|
||||
content: 'hi',
|
||||
message_type: 0,
|
||||
content_type: 'text',
|
||||
content_attributes: {},
|
||||
created_at: 1682504326,
|
||||
conversation_id: 20,
|
||||
},
|
||||
},
|
||||
],
|
||||
]);
|
||||
});
|
||||
|
||||
it('old message should insert to exact position', async () => {
|
||||
const state = {
|
||||
uiFlags: { allMessagesLoaded: false },
|
||||
conversations: {
|
||||
'454': {
|
||||
id: 454,
|
||||
content: 'hi',
|
||||
message_type: 0,
|
||||
content_type: 'text',
|
||||
content_attributes: {},
|
||||
created_at: 1682244355, // Sunday, 23 April 2023 10:05:55
|
||||
conversation_id: 20,
|
||||
},
|
||||
'463': {
|
||||
id: 463,
|
||||
content: 'ss',
|
||||
message_type: 0,
|
||||
content_type: 'text',
|
||||
content_attributes: {},
|
||||
created_at: 1682490729, // Wednesday, 26 April 2023 06:32:09
|
||||
conversation_id: 20,
|
||||
},
|
||||
},
|
||||
lastMessageId: 463,
|
||||
};
|
||||
API.get.mockResolvedValue({
|
||||
data: {
|
||||
payload: [
|
||||
{
|
||||
id: 460,
|
||||
content: 'Hi how are you',
|
||||
message_type: 0,
|
||||
content_type: 'text',
|
||||
content_attributes: {},
|
||||
created_at: 1682417926, // Tuesday, 25 April 2023 10:18:46
|
||||
conversation_id: 20,
|
||||
},
|
||||
],
|
||||
meta: {
|
||||
contact_last_seen_at: 14664223490,
|
||||
},
|
||||
},
|
||||
});
|
||||
await actions.syncLatestMessages({ state, commit }, {});
|
||||
|
||||
expect(commit.mock.calls).toEqual([
|
||||
['conversation/setMetaUserLastSeenAt', 14664223490, { root: true }],
|
||||
[
|
||||
'setMissingMessagesInConversation',
|
||||
|
||||
{
|
||||
'454': {
|
||||
id: 454,
|
||||
content: 'hi',
|
||||
message_type: 0,
|
||||
content_type: 'text',
|
||||
content_attributes: {},
|
||||
created_at: 1682244355,
|
||||
conversation_id: 20,
|
||||
},
|
||||
'460': {
|
||||
id: 460,
|
||||
content: 'Hi how are you',
|
||||
message_type: 0,
|
||||
content_type: 'text',
|
||||
content_attributes: {},
|
||||
created_at: 1682417926,
|
||||
conversation_id: 20,
|
||||
},
|
||||
'463': {
|
||||
id: 463,
|
||||
content: 'ss',
|
||||
message_type: 0,
|
||||
content_type: 'text',
|
||||
content_attributes: {},
|
||||
created_at: 1682490729,
|
||||
conversation_id: 20,
|
||||
},
|
||||
},
|
||||
],
|
||||
]);
|
||||
});
|
||||
|
||||
it('abort syncing if there is no missing messages ', async () => {
|
||||
const state = {
|
||||
uiFlags: { allMessagesLoaded: false },
|
||||
conversation: {
|
||||
'454': {
|
||||
id: 454,
|
||||
content: 'hi',
|
||||
message_type: 0,
|
||||
content_type: 'text',
|
||||
content_attributes: {},
|
||||
created_at: 1682244355, // Sunday, 23 April 2023 10:05:55
|
||||
conversation_id: 20,
|
||||
},
|
||||
'463': {
|
||||
id: 463,
|
||||
content: 'ss',
|
||||
message_type: 0,
|
||||
content_type: 'text',
|
||||
content_attributes: {},
|
||||
created_at: 1682490729, // Wednesday, 26 April 2023 06:32:09
|
||||
conversation_id: 20,
|
||||
},
|
||||
},
|
||||
lastMessageId: 463,
|
||||
};
|
||||
API.get.mockResolvedValue({
|
||||
data: {
|
||||
payload: [],
|
||||
meta: {
|
||||
contact_last_seen_at: 14664223490,
|
||||
},
|
||||
},
|
||||
});
|
||||
await actions.syncLatestMessages({ state, commit }, {});
|
||||
|
||||
expect(commit.mock.calls).toEqual([]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -183,4 +183,72 @@ describe('#mutations', () => {
|
||||
expect(state.conversations).toEqual({});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#setMissingMessages', () => {
|
||||
it('sets messages if payload is not empty', () => {
|
||||
const state = {
|
||||
uiFlags: { allMessagesLoaded: false },
|
||||
conversations: {
|
||||
'454': {
|
||||
id: 454,
|
||||
content: 'hi',
|
||||
message_type: 0,
|
||||
content_type: 'text',
|
||||
content_attributes: {},
|
||||
created_at: 1682432667,
|
||||
conversation_id: 20,
|
||||
},
|
||||
'464': {
|
||||
id: 464,
|
||||
content: 'hey will be back soon',
|
||||
message_type: 3,
|
||||
content_type: 'text',
|
||||
content_attributes: {},
|
||||
created_at: 1682490729,
|
||||
conversation_id: 20,
|
||||
},
|
||||
},
|
||||
};
|
||||
mutations.setMessagesInConversation(state, [
|
||||
{
|
||||
id: 455,
|
||||
content: 'Hey billowing-grass-423 how are you?',
|
||||
message_type: 3,
|
||||
content_type: 'text',
|
||||
content_attributes: {},
|
||||
created_at: 1682432667,
|
||||
conversation_id: 20,
|
||||
},
|
||||
]);
|
||||
expect(state.conversations).toEqual({
|
||||
'454': {
|
||||
id: 454,
|
||||
content: 'hi',
|
||||
message_type: 0,
|
||||
content_type: 'text',
|
||||
content_attributes: {},
|
||||
created_at: 1682432667,
|
||||
conversation_id: 20,
|
||||
},
|
||||
'455': {
|
||||
id: 455,
|
||||
content: 'Hey billowing-grass-423 how are you?',
|
||||
message_type: 3,
|
||||
content_type: 'text',
|
||||
content_attributes: {},
|
||||
created_at: 1682432667,
|
||||
conversation_id: 20,
|
||||
},
|
||||
'464': {
|
||||
id: 464,
|
||||
content: 'hey will be back soon',
|
||||
message_type: 3,
|
||||
content_type: 'text',
|
||||
content_attributes: {},
|
||||
created_at: 1682490729,
|
||||
conversation_id: 20,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user