fix: stale report value shown if summary fetch breaks (#11270)
This pull request includes several changes to better show the report metrics fetching status on the dashboard. This also prevents showing stale data in case fetching summary fails due to timeout or other issue The most important changes include adding a new fetching status state to the store called `summaryFetchingStatus`, updating the `useReportMetrics` composable, and modifying the `ChartStats` component to handle different fetching statuses. #### Loading  #### Finished  #### Failed <img width="1512" alt="image" src="https://github.com/user-attachments/assets/d521a785-9299-4e59-94dc-561a7a84377e" /> --------- Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
This commit is contained in:
5
app/javascript/dashboard/store/constants.js
Normal file
5
app/javascript/dashboard/store/constants.js
Normal file
@@ -0,0 +1,5 @@
|
||||
export const STATUS = {
|
||||
FAILED: 'failed',
|
||||
FETCHING: 'fetching',
|
||||
FINISHED: 'finished',
|
||||
};
|
||||
@@ -1,5 +1,6 @@
|
||||
/* eslint no-console: 0 */
|
||||
import * as types from '../mutation-types';
|
||||
import { STATUS } from '../constants';
|
||||
import Report from '../../api/reports';
|
||||
import { downloadCsvFile, generateFileName } from '../../helper/downloadHelper';
|
||||
import AnalyticsHelper from '../../helper/AnalyticsHelper';
|
||||
@@ -9,6 +10,8 @@ import liveReports from '../../api/liveReports';
|
||||
|
||||
const state = {
|
||||
fetchingStatus: false,
|
||||
accountSummaryFetchingStatus: STATUS.FINISHED,
|
||||
botSummaryFetchingStatus: STATUS.FINISHED,
|
||||
accountReport: {
|
||||
isFetching: {
|
||||
conversations_count: false,
|
||||
@@ -74,6 +77,12 @@ const getters = {
|
||||
getBotSummary(_state) {
|
||||
return _state.botSummary;
|
||||
},
|
||||
getAccountSummaryFetchingStatus(_state) {
|
||||
return _state.accountSummaryFetchingStatus;
|
||||
},
|
||||
getBotSummaryFetchingStatus(_state) {
|
||||
return _state.botSummaryFetchingStatus;
|
||||
},
|
||||
getAccountConversationMetric(_state) {
|
||||
return _state.overview.accountConversationMetric;
|
||||
},
|
||||
@@ -122,6 +131,7 @@ export const actions = {
|
||||
});
|
||||
},
|
||||
fetchAccountSummary({ commit }, reportObj) {
|
||||
commit(types.default.SET_ACCOUNT_SUMMARY_STATUS, STATUS.FETCHING);
|
||||
Report.getSummary(
|
||||
reportObj.from,
|
||||
reportObj.to,
|
||||
@@ -132,12 +142,14 @@ export const actions = {
|
||||
)
|
||||
.then(accountSummary => {
|
||||
commit(types.default.SET_ACCOUNT_SUMMARY, accountSummary.data);
|
||||
commit(types.default.SET_ACCOUNT_SUMMARY_STATUS, STATUS.FINISHED);
|
||||
})
|
||||
.catch(() => {
|
||||
commit(types.default.TOGGLE_ACCOUNT_REPORT_LOADING, false);
|
||||
commit(types.default.SET_ACCOUNT_SUMMARY_STATUS, STATUS.FAILED);
|
||||
});
|
||||
},
|
||||
fetchBotSummary({ commit }, reportObj) {
|
||||
commit(types.default.SET_BOT_SUMMARY_STATUS, STATUS.FETCHING);
|
||||
Report.getBotSummary({
|
||||
from: reportObj.from,
|
||||
to: reportObj.to,
|
||||
@@ -146,9 +158,10 @@ export const actions = {
|
||||
})
|
||||
.then(botSummary => {
|
||||
commit(types.default.SET_BOT_SUMMARY, botSummary.data);
|
||||
commit(types.default.SET_BOT_SUMMARY_STATUS, STATUS.FINISHED);
|
||||
})
|
||||
.catch(() => {
|
||||
commit(types.default.TOGGLE_ACCOUNT_REPORT_LOADING, false);
|
||||
commit(types.default.SET_BOT_SUMMARY_STATUS, STATUS.FAILED);
|
||||
});
|
||||
},
|
||||
fetchAccountConversationMetric({ commit }, params = {}) {
|
||||
@@ -277,6 +290,12 @@ const mutations = {
|
||||
[types.default.TOGGLE_ACCOUNT_REPORT_LOADING](_state, { metric, value }) {
|
||||
_state.accountReport.isFetching[metric] = value;
|
||||
},
|
||||
[types.default.SET_BOT_SUMMARY_STATUS](_state, status) {
|
||||
_state.botSummaryFetchingStatus = status;
|
||||
},
|
||||
[types.default.SET_ACCOUNT_SUMMARY_STATUS](_state, status) {
|
||||
_state.accountSummaryFetchingStatus = status;
|
||||
},
|
||||
[types.default.TOGGLE_HEATMAP_LOADING](_state, flag) {
|
||||
_state.overview.uiFlags.isFetchingAccountConversationsHeatmap = flag;
|
||||
},
|
||||
|
||||
@@ -1,14 +1,122 @@
|
||||
import axios from 'axios';
|
||||
import { actions } from '../../reports';
|
||||
import * as types from '../../../mutation-types';
|
||||
import { STATUS } from '../../../constants';
|
||||
import * as DownloadHelper from 'dashboard/helper/downloadHelper';
|
||||
import { flushPromises } from '@vue/test-utils';
|
||||
|
||||
global.open = vi.fn();
|
||||
global.axios = axios;
|
||||
global.URL.createObjectURL = vi.fn();
|
||||
|
||||
vi.mock('axios');
|
||||
vi.spyOn(DownloadHelper, 'downloadCsvFile');
|
||||
|
||||
describe('#actions', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('#fetchAccountSummary', () => {
|
||||
it('sends correct actions if API is success', async () => {
|
||||
const commit = vi.fn();
|
||||
const reportObj = {
|
||||
from: 1630504922510,
|
||||
to: 1630504922510,
|
||||
type: 'account',
|
||||
id: 1,
|
||||
groupBy: 'day',
|
||||
businessHours: true,
|
||||
};
|
||||
const summaryData = {
|
||||
conversations_count: 10,
|
||||
incoming_messages_count: 20,
|
||||
outgoing_messages_count: 15,
|
||||
avg_first_response_time: 30,
|
||||
avg_resolution_time: 60,
|
||||
resolutions_count: 5,
|
||||
bot_resolutions_count: 2,
|
||||
bot_handoffs_count: 1,
|
||||
reply_time: 25,
|
||||
};
|
||||
axios.get.mockResolvedValue({ data: summaryData });
|
||||
|
||||
actions.fetchAccountSummary({ commit }, reportObj);
|
||||
await flushPromises();
|
||||
|
||||
expect(commit.mock.calls).toEqual([
|
||||
[types.default.SET_ACCOUNT_SUMMARY_STATUS, STATUS.FETCHING],
|
||||
[types.default.SET_ACCOUNT_SUMMARY, summaryData],
|
||||
[types.default.SET_ACCOUNT_SUMMARY_STATUS, STATUS.FINISHED],
|
||||
]);
|
||||
});
|
||||
|
||||
it('sends correct actions if API fails', async () => {
|
||||
const commit = vi.fn();
|
||||
const reportObj = {
|
||||
from: 1630504922510,
|
||||
to: 1630504922510,
|
||||
};
|
||||
axios.get.mockRejectedValue(new Error('API Error'));
|
||||
|
||||
actions.fetchAccountSummary({ commit }, reportObj);
|
||||
await flushPromises();
|
||||
|
||||
expect(commit.mock.calls).toEqual([
|
||||
[types.default.SET_ACCOUNT_SUMMARY_STATUS, STATUS.FETCHING],
|
||||
[types.default.SET_ACCOUNT_SUMMARY_STATUS, STATUS.FAILED],
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#fetchBotSummary', () => {
|
||||
it('sends correct actions if API is success', async () => {
|
||||
const commit = vi.fn();
|
||||
const reportObj = {
|
||||
from: 1630504922510,
|
||||
to: 1630504922510,
|
||||
groupBy: 'day',
|
||||
businessHours: true,
|
||||
};
|
||||
const summaryData = {
|
||||
bot_resolutions_count: 10,
|
||||
bot_handoffs_count: 5,
|
||||
previous: {
|
||||
bot_resolutions_count: 8,
|
||||
bot_handoffs_count: 4,
|
||||
},
|
||||
};
|
||||
axios.get.mockResolvedValue({ data: summaryData });
|
||||
|
||||
actions.fetchBotSummary({ commit }, reportObj);
|
||||
await flushPromises();
|
||||
|
||||
expect(commit.mock.calls).toEqual([
|
||||
[types.default.SET_BOT_SUMMARY_STATUS, STATUS.FETCHING],
|
||||
[types.default.SET_BOT_SUMMARY, summaryData],
|
||||
[types.default.SET_BOT_SUMMARY_STATUS, STATUS.FINISHED],
|
||||
]);
|
||||
});
|
||||
|
||||
it('sends correct actions if API fails', async () => {
|
||||
const commit = vi.fn();
|
||||
const reportObj = {
|
||||
from: 1630504922510,
|
||||
to: 1630504922510,
|
||||
};
|
||||
const error = new Error('API error');
|
||||
axios.get.mockRejectedValueOnce(error);
|
||||
|
||||
actions.fetchBotSummary({ commit }, reportObj);
|
||||
await flushPromises();
|
||||
|
||||
expect(commit.mock.calls).toEqual([
|
||||
[types.default.SET_BOT_SUMMARY_STATUS, STATUS.FETCHING],
|
||||
[types.default.SET_BOT_SUMMARY_STATUS, STATUS.FAILED],
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#downloadAgentReports', () => {
|
||||
it('open CSV download prompt if API is success', async () => {
|
||||
const data = `Agent name,Conversations count,Avg first response time (Minutes),Avg resolution time (Minutes)
|
||||
@@ -20,7 +128,9 @@ describe('#actions', () => {
|
||||
to: 1630504922510,
|
||||
fileName: 'agent-report-01-09-2021.csv',
|
||||
};
|
||||
await actions.downloadAgentReports(1, param);
|
||||
actions.downloadAgentReports(1, param);
|
||||
await flushPromises();
|
||||
|
||||
expect(DownloadHelper.downloadCsvFile).toBeCalledWith(
|
||||
param.fileName,
|
||||
data
|
||||
@@ -39,7 +149,9 @@ describe('#actions', () => {
|
||||
type: 'label',
|
||||
fileName: 'label-report-01-09-2021.csv',
|
||||
};
|
||||
await actions.downloadLabelReports(1, param);
|
||||
actions.downloadLabelReports(1, param);
|
||||
await flushPromises();
|
||||
|
||||
expect(DownloadHelper.downloadCsvFile).toBeCalledWith(
|
||||
param.fileName,
|
||||
data
|
||||
@@ -59,7 +171,9 @@ describe('#actions', () => {
|
||||
to: 1635013800,
|
||||
fileName: 'inbox-report-24-10-2021.csv',
|
||||
};
|
||||
await actions.downloadInboxReports(1, param);
|
||||
actions.downloadInboxReports(1, param);
|
||||
await flushPromises();
|
||||
|
||||
expect(DownloadHelper.downloadCsvFile).toBeCalledWith(
|
||||
param.fileName,
|
||||
data
|
||||
@@ -78,7 +192,9 @@ describe('#actions', () => {
|
||||
to: 1635013800,
|
||||
fileName: 'inbox-report-24-10-2021.csv',
|
||||
};
|
||||
await actions.downloadInboxReports(1, param);
|
||||
actions.downloadInboxReports(1, param);
|
||||
await flushPromises();
|
||||
|
||||
expect(DownloadHelper.downloadCsvFile).toBeCalledWith(
|
||||
param.fileName,
|
||||
data
|
||||
|
||||
@@ -189,6 +189,8 @@ export default {
|
||||
SET_ACCOUNT_SUMMARY: 'SET_ACCOUNT_SUMMARY',
|
||||
SET_BOT_SUMMARY: 'SET_BOT_SUMMARY',
|
||||
TOGGLE_ACCOUNT_REPORT_LOADING: 'TOGGLE_ACCOUNT_REPORT_LOADING',
|
||||
SET_BOT_SUMMARY_STATUS: 'SET_BOT_SUMMARY_STATUS',
|
||||
SET_ACCOUNT_SUMMARY_STATUS: 'SET_ACCOUNT_SUMMARY_STATUS',
|
||||
SET_ACCOUNT_CONVERSATION_METRIC: 'SET_ACCOUNT_CONVERSATION_METRIC',
|
||||
TOGGLE_ACCOUNT_CONVERSATION_METRIC_LOADING:
|
||||
'TOGGLE_ACCOUNT_CONVERSATION_METRIC_LOADING',
|
||||
|
||||
Reference in New Issue
Block a user