feat: Refetch the latest messages on action cable reconnect in widget (#6996)

This commit is contained in:
Muhsin Keloth
2023-05-12 14:05:22 +05:30
committed by GitHub
parent 020dcc4dc7
commit 708bddf4db
10 changed files with 366 additions and 6 deletions

View File

@@ -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

View File

@@ -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 });
};

View File

@@ -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 => ({

View File

@@ -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();
});
});

View File

@@ -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');

View File

@@ -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');
},

View File

@@ -13,6 +13,7 @@ const state = {
isAgentTyping: false,
isCreating: false,
},
lastMessageId: null,
};
export default {

View File

@@ -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;
},
};

View File

@@ -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([]);
});
});
});

View File

@@ -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,
},
});
});
});
});