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
|
def message_finder_params
|
||||||
{
|
{
|
||||||
filter_internal_messages: true,
|
filter_internal_messages: true,
|
||||||
before: permitted_params[:before]
|
before: permitted_params[:before],
|
||||||
|
after: permitted_params[:after]
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -62,7 +63,7 @@ class Api::V1::Widget::MessagesController < Api::V1::Widget::BaseController
|
|||||||
|
|
||||||
def permitted_params
|
def permitted_params
|
||||||
# timestamp parameter is used in create conversation method
|
# 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
|
end
|
||||||
|
|
||||||
def set_message
|
def set_message
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ const sendAttachmentAPI = async attachment => {
|
|||||||
return API.post(urlData.url, urlData.params);
|
return API.post(urlData.url, urlData.params);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getMessagesAPI = async ({ before }) => {
|
const getMessagesAPI = async ({ before, after }) => {
|
||||||
const urlData = endPoints.getConversation({ before });
|
const urlData = endPoints.getConversation({ before, after });
|
||||||
return API.get(urlData.url, { params: urlData.params });
|
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}`,
|
url: `/api/v1/widget/messages${window.location.search}`,
|
||||||
params: { before },
|
params: { before, after },
|
||||||
});
|
});
|
||||||
|
|
||||||
const updateMessage = id => ({
|
const updateMessage = id => ({
|
||||||
|
|||||||
@@ -79,3 +79,26 @@ describe('#triggerCampaign', () => {
|
|||||||
spy.mockRestore();
|
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 => {
|
onStatusChange = data => {
|
||||||
if (data.status === 'resolved') {
|
if (data.status === 'resolved') {
|
||||||
this.app.$store.dispatch('campaign/resetCampaign');
|
this.app.$store.dispatch('campaign/resetCampaign');
|
||||||
|
|||||||
@@ -52,6 +52,10 @@ export const actions = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
setLastMessageId: async ({ commit }) => {
|
||||||
|
commit('setLastMessageId');
|
||||||
|
},
|
||||||
|
|
||||||
sendAttachment: async ({ commit }, params) => {
|
sendAttachment: async ({ commit }, params) => {
|
||||||
const {
|
const {
|
||||||
attachment: { thumbUrl, fileType },
|
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 }) => {
|
clearConversations: ({ commit }) => {
|
||||||
commit('clearConversations');
|
commit('clearConversations');
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ const state = {
|
|||||||
isAgentTyping: false,
|
isAgentTyping: false,
|
||||||
isCreating: false,
|
isCreating: false,
|
||||||
},
|
},
|
||||||
|
lastMessageId: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|||||||
@@ -62,6 +62,10 @@ export const mutations = {
|
|||||||
payload.map(message => Vue.set($state.conversations, message.id, message));
|
payload.map(message => Vue.set($state.conversations, message.id, message));
|
||||||
},
|
},
|
||||||
|
|
||||||
|
setMissingMessagesInConversation($state, payload) {
|
||||||
|
Vue.set($state, 'conversation', payload);
|
||||||
|
},
|
||||||
|
|
||||||
updateMessage($state, { id, content_attributes }) {
|
updateMessage($state, { id, content_attributes }) {
|
||||||
$state.conversations[id] = {
|
$state.conversations[id] = {
|
||||||
...$state.conversations[id],
|
...$state.conversations[id],
|
||||||
@@ -94,4 +98,12 @@ export const mutations = {
|
|||||||
setMetaUserLastSeenAt($state, lastSeen) {
|
setMetaUserLastSeenAt($state, lastSeen) {
|
||||||
$state.meta.userLastSeenAt = 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({});
|
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