fix: Retry message not working if the conversation has an external issue (#8529)
This commit is contained in:
@@ -86,6 +86,12 @@ class MessageApi extends ApiClient {
|
|||||||
return axios.delete(`${this.url}/${conversationID}/messages/${messageId}`);
|
return axios.delete(`${this.url}/${conversationID}/messages/${messageId}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
retry(conversationID, messageId) {
|
||||||
|
return axios.post(
|
||||||
|
`${this.url}/${conversationID}/messages/${messageId}/retry`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
getPreviousMessages({ conversationId, after, before }) {
|
getPreviousMessages({ conversationId, after, before }) {
|
||||||
const params = { before };
|
const params = { before };
|
||||||
if (after && Number(after) !== Number(before)) {
|
if (after && Number(after) !== Number(before)) {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<li v-if="shouldRenderMessage" :id="`message${data.id}`" :class="alignBubble">
|
<li v-if="shouldRenderMessage" :id="`message${data.id}`" :class="alignBubble">
|
||||||
<div :class="wrapClass">
|
<div :class="wrapClass">
|
||||||
<div v-if="isFailed" class="message-failed--alert">
|
<div v-if="isFailed && !hasOneDayPassed" class="message-failed--alert">
|
||||||
<woot-button
|
<woot-button
|
||||||
v-tooltip.top-end="$t('CONVERSATION.TRY_AGAIN')"
|
v-tooltip.top-end="$t('CONVERSATION.TRY_AGAIN')"
|
||||||
size="tiny"
|
size="tiny"
|
||||||
@@ -148,6 +148,7 @@ import { BUS_EVENTS } from 'shared/constants/busEvents';
|
|||||||
import { ACCOUNT_EVENTS } from 'dashboard/helper/AnalyticsHelper/events';
|
import { ACCOUNT_EVENTS } from 'dashboard/helper/AnalyticsHelper/events';
|
||||||
import { LOCAL_STORAGE_KEYS } from 'dashboard/constants/localStorage';
|
import { LOCAL_STORAGE_KEYS } from 'dashboard/constants/localStorage';
|
||||||
import { LocalStorage } from 'shared/helpers/localStorage';
|
import { LocalStorage } from 'shared/helpers/localStorage';
|
||||||
|
import { getDayDifferenceFromNow } from 'shared/helpers/DateHelper';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
@@ -209,6 +210,10 @@ export default {
|
|||||||
created_at: this.data.created_at || '',
|
created_at: this.data.created_at || '',
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
|
hasOneDayPassed() {
|
||||||
|
// Disable retry button if the message is failed and the message is older than 24 hours
|
||||||
|
return getDayDifferenceFromNow(new Date(), this.data?.created_at) >= 1;
|
||||||
|
},
|
||||||
shouldRenderMessage() {
|
shouldRenderMessage() {
|
||||||
return (
|
return (
|
||||||
this.hasAttachments ||
|
this.hasAttachments ||
|
||||||
|
|||||||
@@ -11,6 +11,19 @@ import {
|
|||||||
} from './helpers/actionHelpers';
|
} from './helpers/actionHelpers';
|
||||||
import messageReadActions from './actions/messageReadActions';
|
import messageReadActions from './actions/messageReadActions';
|
||||||
import messageTranslateActions from './actions/messageTranslateActions';
|
import messageTranslateActions from './actions/messageTranslateActions';
|
||||||
|
|
||||||
|
export const hasMessageFailedWithExternalError = pendingMessage => {
|
||||||
|
// This helper is used to check if the message has failed with an external error.
|
||||||
|
// We have two cases
|
||||||
|
// 1. Messages that fail from the UI itself (due to large attachments or a failed network):
|
||||||
|
// In this case, the message will have a status of failed but no external error. So we need to create that message again
|
||||||
|
// 2. Messages sent from Chatwoot but failed to deliver to the customer for some reason (user blocking or client system down):
|
||||||
|
// In this case, the message will have a status of failed and an external error. So we need to retry that message
|
||||||
|
const { content_attributes: contentAttributes, status } = pendingMessage;
|
||||||
|
const externalError = contentAttributes?.external_error ?? '';
|
||||||
|
return status === MESSAGE_STATUS.FAILED && externalError !== '';
|
||||||
|
};
|
||||||
|
|
||||||
// actions
|
// actions
|
||||||
const actions = {
|
const actions = {
|
||||||
getConversation: async ({ commit }, conversationId) => {
|
getConversation: async ({ commit }, conversationId) => {
|
||||||
@@ -242,12 +255,15 @@ const actions = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
sendMessageWithData: async ({ commit }, pendingMessage) => {
|
sendMessageWithData: async ({ commit }, pendingMessage) => {
|
||||||
|
const { conversation_id: conversationId, id } = pendingMessage;
|
||||||
try {
|
try {
|
||||||
commit(types.ADD_MESSAGE, {
|
commit(types.ADD_MESSAGE, {
|
||||||
...pendingMessage,
|
...pendingMessage,
|
||||||
status: MESSAGE_STATUS.PROGRESS,
|
status: MESSAGE_STATUS.PROGRESS,
|
||||||
});
|
});
|
||||||
const response = await MessageApi.create(pendingMessage);
|
const response = hasMessageFailedWithExternalError(pendingMessage)
|
||||||
|
? await MessageApi.retry(conversationId, id)
|
||||||
|
: await MessageApi.create(pendingMessage);
|
||||||
commit(types.ADD_MESSAGE, {
|
commit(types.ADD_MESSAGE, {
|
||||||
...response.data,
|
...response.data,
|
||||||
status: MESSAGE_STATUS.SENT,
|
status: MESSAGE_STATUS.SENT,
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import actions from '../../conversations/actions';
|
import actions, {
|
||||||
|
hasMessageFailedWithExternalError,
|
||||||
|
} from '../../conversations/actions';
|
||||||
import types from '../../../mutation-types';
|
import types from '../../../mutation-types';
|
||||||
const dataToSend = {
|
const dataToSend = {
|
||||||
payload: [
|
payload: [
|
||||||
@@ -18,6 +20,41 @@ const dispatch = jest.fn();
|
|||||||
global.axios = axios;
|
global.axios = axios;
|
||||||
jest.mock('axios');
|
jest.mock('axios');
|
||||||
|
|
||||||
|
describe('#hasMessageFailedWithExternalError', () => {
|
||||||
|
it('returns false if message is sent', () => {
|
||||||
|
const pendingMessage = {
|
||||||
|
status: 'sent',
|
||||||
|
content_attributes: {},
|
||||||
|
};
|
||||||
|
expect(hasMessageFailedWithExternalError(pendingMessage)).toBe(false);
|
||||||
|
});
|
||||||
|
it('returns false if status is not failed', () => {
|
||||||
|
const pendingMessage = {
|
||||||
|
status: 'progress',
|
||||||
|
content_attributes: {},
|
||||||
|
};
|
||||||
|
expect(hasMessageFailedWithExternalError(pendingMessage)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns false if status is failed but no external error', () => {
|
||||||
|
const pendingMessage = {
|
||||||
|
status: 'failed',
|
||||||
|
content_attributes: {},
|
||||||
|
};
|
||||||
|
expect(hasMessageFailedWithExternalError(pendingMessage)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns true if status is failed and has external error', () => {
|
||||||
|
const pendingMessage = {
|
||||||
|
status: 'failed',
|
||||||
|
content_attributes: {
|
||||||
|
external_error: 'error',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
expect(hasMessageFailedWithExternalError(pendingMessage)).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('#actions', () => {
|
describe('#actions', () => {
|
||||||
describe('#getConversation', () => {
|
describe('#getConversation', () => {
|
||||||
it('sends correct actions if API is success', async () => {
|
it('sends correct actions if API is success', async () => {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import fromUnixTime from 'date-fns/fromUnixTime';
|
|||||||
import format from 'date-fns/format';
|
import format from 'date-fns/format';
|
||||||
import isToday from 'date-fns/isToday';
|
import isToday from 'date-fns/isToday';
|
||||||
import isYesterday from 'date-fns/isYesterday';
|
import isYesterday from 'date-fns/isYesterday';
|
||||||
import { endOfDay, getUnixTime, startOfDay } from 'date-fns';
|
import { endOfDay, getUnixTime, startOfDay, differenceInDays } from 'date-fns';
|
||||||
|
|
||||||
export const formatUnixDate = (date, dateFormat = 'MMM dd, yyyy') => {
|
export const formatUnixDate = (date, dateFormat = 'MMM dd, yyyy') => {
|
||||||
const unixDate = fromUnixTime(date);
|
const unixDate = fromUnixTime(date);
|
||||||
@@ -45,3 +45,8 @@ export const generateRelativeTime = (value, unit, languageCode) => {
|
|||||||
});
|
});
|
||||||
return rtf.format(value, unit);
|
return rtf.format(value, unit);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getDayDifferenceFromNow = (now, timestampInSeconds) => {
|
||||||
|
const date = new Date(timestampInSeconds * 1000);
|
||||||
|
return differenceInDays(now, date);
|
||||||
|
};
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import {
|
|||||||
formatDigitToString,
|
formatDigitToString,
|
||||||
isTimeAfter,
|
isTimeAfter,
|
||||||
generateRelativeTime,
|
generateRelativeTime,
|
||||||
|
getDayDifferenceFromNow,
|
||||||
} from '../DateHelper';
|
} from '../DateHelper';
|
||||||
|
|
||||||
describe('#DateHelper', () => {
|
describe('#DateHelper', () => {
|
||||||
@@ -120,3 +121,25 @@ describe('generateRelativeTime', () => {
|
|||||||
expect(actualResult).toBe(expectedResult);
|
expect(actualResult).toBe(expectedResult);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('#getDayDifferenceFromNow', () => {
|
||||||
|
it('should return the difference if in same day', () => {
|
||||||
|
const now = new Date('2023-12-08T00:00:00.000Z');
|
||||||
|
const timestampInSeconds = 1702020305; // 08/12/2023, 12:55:05 (GMT+05:30)
|
||||||
|
const expectedResult = 0;
|
||||||
|
|
||||||
|
const actualResult = getDayDifferenceFromNow(now, timestampInSeconds);
|
||||||
|
|
||||||
|
expect(actualResult).toBe(expectedResult);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return the difference if in different day', () => {
|
||||||
|
const now = new Date('2023-12-11T00:00:00.000Z');
|
||||||
|
const timestampInSeconds = 1702020305; // 08/12/2023, 12:55:05 (GMT+05:30)
|
||||||
|
const expectedResult = 2;
|
||||||
|
|
||||||
|
const actualResult = getDayDifferenceFromNow(now, timestampInSeconds);
|
||||||
|
|
||||||
|
expect(actualResult).toBe(expectedResult);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user