Files
leadchat/app/javascript/dashboard/store/modules/specs/summaryReports.spec.js
Shivam Mishra 0e30e3c00a fix: add loading and silent retry to summary reports (#13455)
For large accounts, summary report queries can take several seconds to
complete, often times hitting the 15-second production request timeout.
The existing implementation silently swallows these failures and
provides no feedback during loading. Users see stale data with no
indication that a fetch is in progress, and if they interact with
filters while a request is in flight, they trigger race conditions that
can result in mismatched data being displayed.

This is a UX-level fix for what is fundamentally a performance problem.
While the underlying query performance is addressed separately, users
need proper feedback either way

## Approach

The PR adds three things: 

1. A loading overlay on the table, to provide feedback on loading state
2. Disabled filter inputs during loading so that the user does not
request new information that can cause race conditions in updating the
store
3. Silent retry before showing an error.

The retry exists because these queries often succeed on the second
attempt—likely due to database query caching. Rather than immediately
showing an error and forcing the user to manually retry, we do it
automatically. If the second attempt also fails, we show a toast so the
user knows something went wrong.

The store previously caught and discarded errors entirely. It now
rethrows them after resetting the loading flag, allowing components to
handle failures as they see fit.

### Previews

#### Double Retry and Error


https://github.com/user-attachments/assets/c189b173-8017-44b7-9493-417d65582c95

#### Loading State


https://github.com/user-attachments/assets/9f899c20-fbad-469b-93cc-f0d05d0853b0

---------

Co-authored-by: iamsivin <iamsivin@gmail.com>
2026-02-06 19:53:46 +05:30

232 lines
7.3 KiB
JavaScript

import { describe, it, expect, vi, beforeEach } from 'vitest';
import SummaryReportsAPI from 'dashboard/api/summaryReports';
import store, { initialState } from '../summaryReports';
vi.mock('dashboard/api/summaryReports', () => ({
default: {
getInboxReports: vi.fn(),
getAgentReports: vi.fn(),
getTeamReports: vi.fn(),
getLabelReports: vi.fn(),
},
}));
describe('Summary Reports Store', () => {
let commit;
beforeEach(() => {
// Reset all mocks before each test
vi.clearAllMocks();
commit = vi.fn();
});
describe('Initial State', () => {
it('should have the correct initial state structure', () => {
expect(initialState).toEqual({
inboxSummaryReports: [],
agentSummaryReports: [],
teamSummaryReports: [],
labelSummaryReports: [],
uiFlags: {
isFetchingInboxSummaryReports: false,
isFetchingAgentSummaryReports: false,
isFetchingTeamSummaryReports: false,
isFetchingLabelSummaryReports: false,
},
});
});
});
describe('Getters', () => {
const state = {
inboxSummaryReports: [{ id: 1 }],
agentSummaryReports: [{ id: 2 }],
teamSummaryReports: [{ id: 3 }],
labelSummaryReports: [{ id: 4 }],
uiFlags: { isFetchingInboxSummaryReports: true },
};
it('should return inbox summary reports', () => {
expect(store.getters.getInboxSummaryReports(state)).toEqual([{ id: 1 }]);
});
it('should return agent summary reports', () => {
expect(store.getters.getAgentSummaryReports(state)).toEqual([{ id: 2 }]);
});
it('should return team summary reports', () => {
expect(store.getters.getTeamSummaryReports(state)).toEqual([{ id: 3 }]);
});
it('should return label summary reports', () => {
expect(store.getters.getLabelSummaryReports(state)).toEqual([{ id: 4 }]);
});
it('should return UI flags', () => {
expect(store.getters.getUIFlags(state)).toEqual({
isFetchingInboxSummaryReports: true,
});
});
});
describe('Mutations', () => {
it('should set inbox summary report', () => {
const state = { ...initialState };
const data = [{ id: 1 }];
store.mutations.setInboxSummaryReport(state, data);
expect(state.inboxSummaryReports).toEqual(data);
});
it('should set agent summary report', () => {
const state = { ...initialState };
const data = [{ id: 2 }];
store.mutations.setAgentSummaryReport(state, data);
expect(state.agentSummaryReports).toEqual(data);
});
it('should set team summary report', () => {
const state = { ...initialState };
const data = [{ id: 3 }];
store.mutations.setTeamSummaryReport(state, data);
expect(state.teamSummaryReports).toEqual(data);
});
it('should set label summary report', () => {
const state = { ...initialState };
const data = [{ id: 4 }];
store.mutations.setLabelSummaryReport(state, data);
expect(state.labelSummaryReports).toEqual(data);
});
it('should merge UI flags with existing flags', () => {
const state = {
uiFlags: { flag1: true, flag2: false },
};
const newFlags = { flag2: true, flag3: true };
store.mutations.setUIFlags(state, newFlags);
expect(state.uiFlags).toEqual({
flag1: true,
flag2: true,
flag3: true,
});
});
});
describe('Actions', () => {
describe('fetchInboxSummaryReports', () => {
it('should fetch inbox reports successfully', async () => {
const params = { date: '2025-01-01' };
const mockResponse = {
data: [{ report_id: 1, report_name: 'Test' }],
};
SummaryReportsAPI.getInboxReports.mockResolvedValue(mockResponse);
await store.actions.fetchInboxSummaryReports({ commit }, params);
expect(commit).toHaveBeenCalledWith('setUIFlags', {
isFetchingInboxSummaryReports: true,
});
expect(SummaryReportsAPI.getInboxReports).toHaveBeenCalledWith(params);
expect(commit).toHaveBeenCalledWith('setInboxSummaryReport', [
{ reportId: 1, reportName: 'Test' },
]);
expect(commit).toHaveBeenCalledWith('setUIFlags', {
isFetchingInboxSummaryReports: false,
});
});
it('should reset uiFlags and rethrow error on failure', async () => {
SummaryReportsAPI.getInboxReports.mockRejectedValue(
new Error('API Error')
);
await expect(
store.actions.fetchInboxSummaryReports({ commit }, {})
).rejects.toThrow('API Error');
expect(commit).toHaveBeenCalledWith('setUIFlags', {
isFetchingInboxSummaryReports: false,
});
});
});
describe('fetchAgentSummaryReports', () => {
it('should fetch agent reports successfully', async () => {
const params = { agentId: 123 };
const mockResponse = {
data: [{ agent_id: 123, agent_name: 'Test Agent' }],
};
SummaryReportsAPI.getAgentReports.mockResolvedValue(mockResponse);
await store.actions.fetchAgentSummaryReports({ commit }, params);
expect(commit).toHaveBeenCalledWith('setUIFlags', {
isFetchingAgentSummaryReports: true,
});
expect(SummaryReportsAPI.getAgentReports).toHaveBeenCalledWith(params);
expect(commit).toHaveBeenCalledWith('setAgentSummaryReport', [
{ agentId: 123, agentName: 'Test Agent' },
]);
expect(commit).toHaveBeenCalledWith('setUIFlags', {
isFetchingAgentSummaryReports: false,
});
});
});
describe('fetchTeamSummaryReports', () => {
it('should fetch team reports successfully', async () => {
const params = { teamId: 456 };
const mockResponse = {
data: [{ team_id: 456, team_name: 'Test Team' }],
};
SummaryReportsAPI.getTeamReports.mockResolvedValue(mockResponse);
await store.actions.fetchTeamSummaryReports({ commit }, params);
expect(commit).toHaveBeenCalledWith('setUIFlags', {
isFetchingTeamSummaryReports: true,
});
expect(SummaryReportsAPI.getTeamReports).toHaveBeenCalledWith(params);
expect(commit).toHaveBeenCalledWith('setTeamSummaryReport', [
{ teamId: 456, teamName: 'Test Team' },
]);
expect(commit).toHaveBeenCalledWith('setUIFlags', {
isFetchingTeamSummaryReports: false,
});
});
});
describe('fetchLabelSummaryReports', () => {
it('should fetch label reports successfully', async () => {
const params = { labelId: 789 };
const mockResponse = {
data: [{ label_id: 789, label_name: 'Test Label' }],
};
SummaryReportsAPI.getLabelReports.mockResolvedValue(mockResponse);
await store.actions.fetchLabelSummaryReports({ commit }, params);
expect(commit).toHaveBeenCalledWith('setUIFlags', {
isFetchingLabelSummaryReports: true,
});
expect(SummaryReportsAPI.getLabelReports).toHaveBeenCalledWith(params);
expect(commit).toHaveBeenCalledWith('setLabelSummaryReport', [
{ labelId: 789, labelName: 'Test Label' },
]);
expect(commit).toHaveBeenCalledWith('setUIFlags', {
isFetchingLabelSummaryReports: false,
});
});
});
});
});