feat(v4): Update the report pages to show aggregate values (#10766)
This PR updates the report pages for agents, inboxes, and teams by replacing charts with aggregate values (under a feature flag). Users can click on any item to view more details if needed. Most users seem to prefer aggregate values, so this change will likely stay. The PR also includes a few fixes: - The summary reports now use the same logic for both the front-end and CSV exports. - Fixed an issue where a single quote was being added to values with hyphens in CSV files. Now, ‘n/a’ is used when no value is available. - Fixed a bug where the average value was calculated incorrectly when multiple accounts were present. These changes should make reports easier to use and more consistent. ### Agents: <img width="1438" alt="Screenshot 2025-01-26 at 10 47 18 AM" src="https://github.com/user-attachments/assets/bf2fcebc-6207-4701-9703-5c2110b7b8a0" /> ### Inboxes <img width="1438" alt="Screenshot 2025-01-26 at 10 47 10 AM" src="https://github.com/user-attachments/assets/b83e1cf2-fd14-4e8e-8dcd-9033404a9f22" /> ### Teams: <img width="1436" alt="Screenshot 2025-01-26 at 10 47 01 AM" src="https://github.com/user-attachments/assets/96b1ce07-f557-42ca-8143-546a111d6458" /> --------- Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com> Co-authored-by: Shivam Mishra <scm.mymail@gmail.com>
This commit is contained in:
co-authored by
Sivin Varghese
Shivam Mishra
parent
9cee8a1713
commit
cb42be8e65
@@ -5,8 +5,8 @@ import agentBots from './modules/agentBots';
|
||||
import agents from './modules/agents';
|
||||
import articles from './modules/helpCenterArticles';
|
||||
import attributes from './modules/attributes';
|
||||
import auth from './modules/auth';
|
||||
import auditlogs from './modules/auditlogs';
|
||||
import auth from './modules/auth';
|
||||
import automations from './modules/automations';
|
||||
import bulkActions from './modules/bulkActions';
|
||||
import campaigns from './modules/campaigns';
|
||||
@@ -25,9 +25,10 @@ import conversationStats from './modules/conversationStats';
|
||||
import conversationTypingStatus from './modules/conversationTypingStatus';
|
||||
import conversationWatchers from './modules/conversationWatchers';
|
||||
import csat from './modules/csat';
|
||||
import customViews from './modules/customViews';
|
||||
import customRole from './modules/customRole';
|
||||
import customViews from './modules/customViews';
|
||||
import dashboardApps from './modules/dashboardApps';
|
||||
import draftMessages from './modules/draftMessages';
|
||||
import globalConfig from 'shared/store/globalConfig';
|
||||
import inboxAssignableAgents from './modules/inboxAssignableAgents';
|
||||
import inboxes from './modules/inboxes';
|
||||
@@ -39,12 +40,12 @@ import notifications from './modules/notifications';
|
||||
import portals from './modules/helpCenterPortals';
|
||||
import reports from './modules/reports';
|
||||
import sla from './modules/sla';
|
||||
import slaReports from './modules/SLAReports';
|
||||
import summaryReports from './modules/summaryReports';
|
||||
import teamMembers from './modules/teamMembers';
|
||||
import teams from './modules/teams';
|
||||
import userNotificationSettings from './modules/userNotificationSettings';
|
||||
import webhooks from './modules/webhooks';
|
||||
import draftMessages from './modules/draftMessages';
|
||||
import SLAReports from './modules/SLAReports';
|
||||
import captainAssistants from './captain/assistant';
|
||||
import captainDocuments from './captain/document';
|
||||
import captainResponses from './captain/response';
|
||||
@@ -58,9 +59,9 @@ export default createStore({
|
||||
agents,
|
||||
articles,
|
||||
attributes,
|
||||
auditlogs,
|
||||
auth,
|
||||
automations,
|
||||
auditlogs,
|
||||
bulkActions,
|
||||
campaigns,
|
||||
cannedResponse,
|
||||
@@ -78,9 +79,10 @@ export default createStore({
|
||||
conversationTypingStatus,
|
||||
conversationWatchers,
|
||||
csat,
|
||||
customViews,
|
||||
customRole,
|
||||
customViews,
|
||||
dashboardApps,
|
||||
draftMessages,
|
||||
globalConfig,
|
||||
inboxAssignableAgents,
|
||||
inboxes,
|
||||
@@ -91,13 +93,13 @@ export default createStore({
|
||||
notifications,
|
||||
portals,
|
||||
reports,
|
||||
sla,
|
||||
slaReports,
|
||||
summaryReports,
|
||||
teamMembers,
|
||||
teams,
|
||||
userNotificationSettings,
|
||||
webhooks,
|
||||
draftMessages,
|
||||
sla,
|
||||
slaReports: SLAReports,
|
||||
captainAssistants,
|
||||
captainDocuments,
|
||||
captainResponses,
|
||||
|
||||
@@ -22,6 +22,9 @@ export const getters = {
|
||||
getUIFlags($state) {
|
||||
return $state.uiFlags;
|
||||
},
|
||||
getAgentById: $state => id => {
|
||||
return $state.records.find(record => record.id === Number(id)) || {};
|
||||
},
|
||||
getAgentStatus($state) {
|
||||
let status = {
|
||||
online: $state.records.filter(
|
||||
|
||||
@@ -0,0 +1,189 @@
|
||||
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(),
|
||||
},
|
||||
}));
|
||||
|
||||
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: [],
|
||||
uiFlags: {
|
||||
isFetchingInboxSummaryReports: false,
|
||||
isFetchingAgentSummaryReports: false,
|
||||
isFetchingTeamSummaryReports: false,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Getters', () => {
|
||||
const state = {
|
||||
inboxSummaryReports: [{ id: 1 }],
|
||||
agentSummaryReports: [{ id: 2 }],
|
||||
teamSummaryReports: [{ id: 3 }],
|
||||
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 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 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 handle errors gracefully', async () => {
|
||||
SummaryReportsAPI.getInboxReports.mockRejectedValue(
|
||||
new Error('API Error')
|
||||
);
|
||||
|
||||
await store.actions.fetchInboxSummaryReports({ commit }, {});
|
||||
|
||||
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,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,98 @@
|
||||
import SummaryReportsAPI from 'dashboard/api/summaryReports';
|
||||
import camelcaseKeys from 'camelcase-keys';
|
||||
|
||||
const typeMap = {
|
||||
inbox: {
|
||||
flagKey: 'isFetchingInboxSummaryReports',
|
||||
apiMethod: 'getInboxReports',
|
||||
mutationKey: 'setInboxSummaryReport',
|
||||
},
|
||||
agent: {
|
||||
flagKey: 'isFetchingAgentSummaryReports',
|
||||
apiMethod: 'getAgentReports',
|
||||
mutationKey: 'setAgentSummaryReport',
|
||||
},
|
||||
team: {
|
||||
flagKey: 'isFetchingTeamSummaryReports',
|
||||
apiMethod: 'getTeamReports',
|
||||
mutationKey: 'setTeamSummaryReport',
|
||||
},
|
||||
};
|
||||
|
||||
async function fetchSummaryReports(type, params, { commit }) {
|
||||
const config = typeMap[type];
|
||||
if (!config) return;
|
||||
|
||||
try {
|
||||
commit('setUIFlags', { [config.flagKey]: true });
|
||||
const response = await SummaryReportsAPI[config.apiMethod](params);
|
||||
commit(config.mutationKey, camelcaseKeys(response.data, { deep: true }));
|
||||
} catch (error) {
|
||||
// Ignore error
|
||||
} finally {
|
||||
commit('setUIFlags', { [config.flagKey]: false });
|
||||
}
|
||||
}
|
||||
|
||||
export const initialState = {
|
||||
inboxSummaryReports: [],
|
||||
agentSummaryReports: [],
|
||||
teamSummaryReports: [],
|
||||
uiFlags: {
|
||||
isFetchingInboxSummaryReports: false,
|
||||
isFetchingAgentSummaryReports: false,
|
||||
isFetchingTeamSummaryReports: false,
|
||||
},
|
||||
};
|
||||
|
||||
export const getters = {
|
||||
getInboxSummaryReports(state) {
|
||||
return state.inboxSummaryReports;
|
||||
},
|
||||
getAgentSummaryReports(state) {
|
||||
return state.agentSummaryReports;
|
||||
},
|
||||
getTeamSummaryReports(state) {
|
||||
return state.teamSummaryReports;
|
||||
},
|
||||
getUIFlags(state) {
|
||||
return state.uiFlags;
|
||||
},
|
||||
};
|
||||
|
||||
export const actions = {
|
||||
fetchInboxSummaryReports({ commit }, params) {
|
||||
return fetchSummaryReports('inbox', params, { commit });
|
||||
},
|
||||
|
||||
fetchAgentSummaryReports({ commit }, params) {
|
||||
return fetchSummaryReports('agent', params, { commit });
|
||||
},
|
||||
|
||||
fetchTeamSummaryReports({ commit }, params) {
|
||||
return fetchSummaryReports('team', params, { commit });
|
||||
},
|
||||
};
|
||||
|
||||
export const mutations = {
|
||||
setInboxSummaryReport(state, data) {
|
||||
state.inboxSummaryReports = data;
|
||||
},
|
||||
setAgentSummaryReport(state, data) {
|
||||
state.agentSummaryReports = data;
|
||||
},
|
||||
setTeamSummaryReport(state, data) {
|
||||
state.teamSummaryReports = data;
|
||||
},
|
||||
setUIFlags(state, uiFlag) {
|
||||
state.uiFlags = { ...state.uiFlags, ...uiFlag };
|
||||
},
|
||||
};
|
||||
|
||||
export default {
|
||||
namespaced: true,
|
||||
state: initialState,
|
||||
getters,
|
||||
actions,
|
||||
mutations,
|
||||
};
|
||||
@@ -2,6 +2,12 @@ export const getters = {
|
||||
getTeams($state) {
|
||||
return Object.values($state.records).sort((a, b) => a.id - b.id);
|
||||
},
|
||||
getTeamById: $state => id => {
|
||||
return (
|
||||
Object.values($state.records).find(record => record.id === Number(id)) ||
|
||||
{}
|
||||
);
|
||||
},
|
||||
getMyTeams($state, $getters) {
|
||||
return $getters.getTeams.filter(team => {
|
||||
const { is_member: isMember } = team;
|
||||
|
||||
Reference in New Issue
Block a user