Files
leadchat/app/javascript/dashboard/store/modules/reports.js
Sivin Varghese 821a5b85c2 feat: Add conversations summary CSV export (#13110)
# Pull Request Template

## Description

This PR adds support for exporting conversation summary reports as CSV.
Previously, the Conversations report incorrectly showed an option to
download agent reports; this has now been fixed to export
conversation-level data instead.

Fixes
https://linear.app/chatwoot/issue/CW-6176/conversation-reports-export-button-exports-agent-reports-instead

## Type of change

- [x] Bug fix (non-breaking change which fixes an issue)
- [x] New feature (non-breaking change which adds functionality)

## How Has This Been Tested?

### Screenshot
<img width="1859" height="1154" alt="image"
src="https://github.com/user-attachments/assets/419d26f4-fda9-4782-aea6-55ffad0c37ab"
/>



## Checklist:

- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [ ] I have commented on my code, particularly in hard-to-understand
areas
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published in downstream
modules

Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
2026-01-13 12:30:26 +04:00

368 lines
12 KiB
JavaScript

/* 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';
import { REPORTS_EVENTS } from '../../helper/AnalyticsHelper/events';
import { clampDataBetweenTimeline } from 'shared/helpers/ReportsDataHelper';
import liveReports from '../../api/liveReports';
const state = {
fetchingStatus: false,
accountSummaryFetchingStatus: STATUS.FINISHED,
botSummaryFetchingStatus: STATUS.FINISHED,
accountReport: {
isFetching: {
conversations_count: false,
incoming_messages_count: false,
outgoing_messages_count: false,
avg_first_response_time: false,
avg_resolution_time: false,
resolutions_count: false,
bot_resolutions_count: false,
bot_handoffs_count: false,
reply_time: false,
},
data: {
conversations_count: [],
incoming_messages_count: [],
outgoing_messages_count: [],
avg_first_response_time: [],
avg_resolution_time: [],
resolutions_count: [],
bot_resolutions_count: [],
bot_handoffs_count: [],
reply_time: [],
},
},
accountSummary: {
avg_first_response_time: 0,
avg_resolution_time: 0,
conversations_count: 0,
incoming_messages_count: 0,
outgoing_messages_count: 0,
reply_time: 0,
resolutions_count: 0,
bot_resolutions_count: 0,
bot_handoffs_count: 0,
previous: {},
},
botSummary: {
bot_resolutions_count: 0,
bot_handoffs_count: 0,
previous: {},
},
overview: {
uiFlags: {
isFetchingAccountConversationMetric: false,
isFetchingAccountConversationsHeatmap: false,
isFetchingAccountResolutionsHeatmap: false,
isFetchingAgentConversationMetric: false,
isFetchingTeamConversationMetric: false,
},
accountConversationMetric: {},
accountConversationHeatmap: [],
accountResolutionHeatmap: [],
agentConversationMetric: [],
teamConversationMetric: [],
},
};
const getters = {
getAccountReports(_state) {
return _state.accountReport;
},
getAccountSummary(_state) {
return _state.accountSummary;
},
getBotSummary(_state) {
return _state.botSummary;
},
getAccountSummaryFetchingStatus(_state) {
return _state.accountSummaryFetchingStatus;
},
getBotSummaryFetchingStatus(_state) {
return _state.botSummaryFetchingStatus;
},
getAccountConversationMetric(_state) {
return _state.overview.accountConversationMetric;
},
getAccountConversationHeatmapData(_state) {
return _state.overview.accountConversationHeatmap;
},
getAccountResolutionHeatmapData(_state) {
return _state.overview.accountResolutionHeatmap;
},
getAgentConversationMetric(_state) {
return _state.overview.agentConversationMetric;
},
getTeamConversationMetric(_state) {
return _state.overview.teamConversationMetric;
},
getOverviewUIFlags($state) {
return $state.overview.uiFlags;
},
};
export const actions = {
fetchAccountReport({ commit }, reportObj) {
const { metric } = reportObj;
commit(types.default.TOGGLE_ACCOUNT_REPORT_LOADING, {
metric,
value: true,
});
Report.getReports(reportObj).then(accountReport => {
let { data } = accountReport;
data = clampDataBetweenTimeline(data, reportObj.from, reportObj.to);
commit(types.default.SET_ACCOUNT_REPORTS, {
metric,
data,
});
commit(types.default.TOGGLE_ACCOUNT_REPORT_LOADING, {
metric,
value: false,
});
});
},
fetchAccountConversationHeatmap({ commit }, reportObj) {
commit(types.default.TOGGLE_HEATMAP_LOADING, true);
Report.getReports({ ...reportObj, groupBy: 'hour' }).then(heatmapData => {
let { data } = heatmapData;
data = clampDataBetweenTimeline(data, reportObj.from, reportObj.to);
commit(types.default.SET_HEATMAP_DATA, data);
commit(types.default.TOGGLE_HEATMAP_LOADING, false);
});
},
fetchAccountResolutionHeatmap({ commit }, reportObj) {
commit(types.default.TOGGLE_RESOLUTION_HEATMAP_LOADING, true);
Report.getReports({ ...reportObj, groupBy: 'hour' }).then(heatmapData => {
let { data } = heatmapData;
data = clampDataBetweenTimeline(data, reportObj.from, reportObj.to);
commit(types.default.SET_RESOLUTION_HEATMAP_DATA, data);
commit(types.default.TOGGLE_RESOLUTION_HEATMAP_LOADING, false);
});
},
fetchAccountSummary({ commit }, reportObj) {
commit(types.default.SET_ACCOUNT_SUMMARY_STATUS, STATUS.FETCHING);
Report.getSummary(
reportObj.from,
reportObj.to,
reportObj.type,
reportObj.id,
reportObj.groupBy,
reportObj.businessHours
)
.then(accountSummary => {
commit(types.default.SET_ACCOUNT_SUMMARY, accountSummary.data);
commit(types.default.SET_ACCOUNT_SUMMARY_STATUS, STATUS.FINISHED);
})
.catch(() => {
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,
groupBy: reportObj.groupBy,
businessHours: reportObj.businessHours,
})
.then(botSummary => {
commit(types.default.SET_BOT_SUMMARY, botSummary.data);
commit(types.default.SET_BOT_SUMMARY_STATUS, STATUS.FINISHED);
})
.catch(() => {
commit(types.default.SET_BOT_SUMMARY_STATUS, STATUS.FAILED);
});
},
fetchAccountConversationMetric({ commit }, params = {}) {
commit(types.default.TOGGLE_ACCOUNT_CONVERSATION_METRIC_LOADING, true);
liveReports
.getConversationMetric(params)
.then(accountConversationMetric => {
commit(
types.default.SET_ACCOUNT_CONVERSATION_METRIC,
accountConversationMetric.data
);
commit(types.default.TOGGLE_ACCOUNT_CONVERSATION_METRIC_LOADING, false);
})
.catch(() => {
commit(types.default.TOGGLE_ACCOUNT_CONVERSATION_METRIC_LOADING, false);
});
},
fetchAgentConversationMetric({ commit }) {
commit(types.default.TOGGLE_AGENT_CONVERSATION_METRIC_LOADING, true);
liveReports
.getGroupedConversations({ groupBy: 'assignee_id' })
.then(agentConversationMetric => {
commit(
types.default.SET_AGENT_CONVERSATION_METRIC,
agentConversationMetric.data
);
commit(types.default.TOGGLE_AGENT_CONVERSATION_METRIC_LOADING, false);
})
.catch(() => {
commit(types.default.TOGGLE_AGENT_CONVERSATION_METRIC_LOADING, false);
});
},
fetchTeamConversationMetric({ commit }) {
commit(types.default.TOGGLE_TEAM_CONVERSATION_METRIC_LOADING, true);
liveReports
.getGroupedConversations({ groupBy: 'team_id' })
.then(teamMetric => {
commit(types.default.SET_TEAM_CONVERSATION_METRIC, teamMetric.data);
commit(types.default.TOGGLE_TEAM_CONVERSATION_METRIC_LOADING, false);
})
.catch(() => {
commit(types.default.TOGGLE_TEAM_CONVERSATION_METRIC_LOADING, false);
});
},
downloadAgentReports(_, reportObj) {
return Report.getAgentReports(reportObj)
.then(response => {
downloadCsvFile(reportObj.fileName, response.data);
AnalyticsHelper.track(REPORTS_EVENTS.DOWNLOAD_REPORT, {
reportType: 'agent',
businessHours: reportObj?.businessHours,
});
})
.catch(error => {
console.error(error);
});
},
downloadConversationsSummaryReports(_, reportObj) {
return Report.getConversationsSummaryReports(reportObj)
.then(response => {
downloadCsvFile(reportObj.fileName, response.data);
AnalyticsHelper.track(REPORTS_EVENTS.DOWNLOAD_REPORT, {
reportType: 'conversations_summary',
businessHours: reportObj?.businessHours,
});
})
.catch(error => {
console.error(error);
});
},
downloadLabelReports(_, reportObj) {
return Report.getLabelReports(reportObj)
.then(response => {
downloadCsvFile(reportObj.fileName, response.data);
AnalyticsHelper.track(REPORTS_EVENTS.DOWNLOAD_REPORT, {
reportType: 'label',
businessHours: reportObj?.businessHours,
});
})
.catch(error => {
console.error(error);
});
},
downloadInboxReports(_, reportObj) {
return Report.getInboxReports(reportObj)
.then(response => {
downloadCsvFile(reportObj.fileName, response.data);
AnalyticsHelper.track(REPORTS_EVENTS.DOWNLOAD_REPORT, {
reportType: 'inbox',
businessHours: reportObj?.businessHours,
});
})
.catch(error => {
console.error(error);
});
},
downloadTeamReports(_, reportObj) {
return Report.getTeamReports(reportObj)
.then(response => {
downloadCsvFile(reportObj.fileName, response.data);
AnalyticsHelper.track(REPORTS_EVENTS.DOWNLOAD_REPORT, {
reportType: 'team',
businessHours: reportObj?.businessHours,
});
})
.catch(error => {
console.error(error);
});
},
downloadAccountConversationHeatmap(_, reportObj) {
Report.getConversationTrafficCSV({ daysBefore: reportObj.daysBefore })
.then(response => {
downloadCsvFile(
generateFileName({
type: 'Conversation traffic',
to: reportObj.to,
}),
response.data
);
AnalyticsHelper.track(REPORTS_EVENTS.DOWNLOAD_REPORT, {
reportType: 'conversation_heatmap',
businessHours: false,
});
})
.catch(error => {
console.error(error);
});
},
};
const mutations = {
[types.default.SET_ACCOUNT_REPORTS](_state, { metric, data }) {
_state.accountReport.data[metric] = data;
},
[types.default.SET_HEATMAP_DATA](_state, heatmapData) {
_state.overview.accountConversationHeatmap = heatmapData;
},
[types.default.SET_RESOLUTION_HEATMAP_DATA](_state, heatmapData) {
_state.overview.accountResolutionHeatmap = heatmapData;
},
[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;
},
[types.default.TOGGLE_RESOLUTION_HEATMAP_LOADING](_state, flag) {
_state.overview.uiFlags.isFetchingAccountResolutionsHeatmap = flag;
},
[types.default.SET_ACCOUNT_SUMMARY](_state, summaryData) {
_state.accountSummary = summaryData;
},
[types.default.SET_BOT_SUMMARY](_state, summaryData) {
_state.botSummary = summaryData;
},
[types.default.SET_ACCOUNT_CONVERSATION_METRIC](_state, metricData) {
_state.overview.accountConversationMetric = metricData;
},
[types.default.TOGGLE_ACCOUNT_CONVERSATION_METRIC_LOADING](_state, flag) {
_state.overview.uiFlags.isFetchingAccountConversationMetric = flag;
},
[types.default.SET_AGENT_CONVERSATION_METRIC](_state, metricData) {
_state.overview.agentConversationMetric = metricData;
},
[types.default.TOGGLE_AGENT_CONVERSATION_METRIC_LOADING](_state, flag) {
_state.overview.uiFlags.isFetchingAgentConversationMetric = flag;
},
[types.default.SET_TEAM_CONVERSATION_METRIC](_state, metricData) {
_state.overview.teamConversationMetric = metricData;
},
[types.default.TOGGLE_TEAM_CONVERSATION_METRIC_LOADING](_state, flag) {
_state.overview.uiFlags.isFetchingTeamConversationMetric = flag;
},
};
export default {
state,
getters,
actions,
mutations,
};