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>
This commit is contained in:
@@ -38,6 +38,11 @@ class Api::V2::Accounts::ReportsController < Api::V1::Accounts::BaseController
|
|||||||
generate_csv('teams_report', 'api/v2/accounts/reports/teams')
|
generate_csv('teams_report', 'api/v2/accounts/reports/teams')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def conversations_summary
|
||||||
|
@report_data = generate_conversations_report
|
||||||
|
generate_csv('conversations_summary_report', 'api/v2/accounts/reports/conversations_summary')
|
||||||
|
end
|
||||||
|
|
||||||
def conversation_traffic
|
def conversation_traffic
|
||||||
@report_data = generate_conversations_heatmap_report
|
@report_data = generate_conversations_heatmap_report
|
||||||
timezone_offset = (params[:timezone_offset] || 0).to_f
|
timezone_offset = (params[:timezone_offset] || 0).to_f
|
||||||
|
|||||||
@@ -46,6 +46,13 @@ module Api::V2::Accounts::ReportsHelper
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def generate_conversations_report
|
||||||
|
builder = V2::Reports::Conversations::MetricBuilder.new(Current.account, build_params(type: :account))
|
||||||
|
summary = builder.summary
|
||||||
|
|
||||||
|
[generate_conversation_report_metrics(summary)]
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def build_params(base_params)
|
def build_params(base_params)
|
||||||
@@ -71,4 +78,16 @@ module Api::V2::Accounts::ReportsHelper
|
|||||||
report[:resolved_conversations_count]
|
report[:resolved_conversations_count]
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def generate_conversation_report_metrics(summary)
|
||||||
|
[
|
||||||
|
summary[:conversations_count],
|
||||||
|
summary[:incoming_messages_count],
|
||||||
|
summary[:outgoing_messages_count],
|
||||||
|
Reports::TimeFormatPresenter.new(summary[:avg_first_response_time]).format,
|
||||||
|
Reports::TimeFormatPresenter.new(summary[:avg_resolution_time]).format,
|
||||||
|
summary[:resolutions_count],
|
||||||
|
Reports::TimeFormatPresenter.new(summary[:reply_time]).format
|
||||||
|
]
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -61,6 +61,12 @@ class ReportsAPI extends ApiClient {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getConversationsSummaryReports({ from: since, to: until, businessHours }) {
|
||||||
|
return axios.get(`${this.url}/conversations_summary`, {
|
||||||
|
params: { since, until, business_hours: businessHours },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
getConversationTrafficCSV({ daysBefore = 6 } = {}) {
|
getConversationTrafficCSV({ daysBefore = 6 } = {}) {
|
||||||
return axios.get(`${this.url}/conversation_traffic`, {
|
return axios.get(`${this.url}/conversation_traffic`, {
|
||||||
params: { timezone_offset: getTimeOffset(), days_before: daysBefore },
|
params: { timezone_offset: getTimeOffset(), days_before: daysBefore },
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"HEADER": "Conversations",
|
"HEADER": "Conversations",
|
||||||
"LOADING_CHART": "Loading chart data...",
|
"LOADING_CHART": "Loading chart data...",
|
||||||
"NO_ENOUGH_DATA": "We've not received enough data points to generate report, Please try again later.",
|
"NO_ENOUGH_DATA": "We've not received enough data points to generate report, Please try again later.",
|
||||||
"DOWNLOAD_AGENT_REPORTS": "Download agent reports",
|
"DOWNLOAD_CONVERSATION_REPORTS": "Download conversation reports",
|
||||||
"DATA_FETCHING_FAILED": "Failed to fetch data, please try again later.",
|
"DATA_FETCHING_FAILED": "Failed to fetch data, please try again later.",
|
||||||
"SUMMARY_FETCHING_FAILED": "Failed to fetch summary, please try again later.",
|
"SUMMARY_FETCHING_FAILED": "Failed to fetch summary, please try again later.",
|
||||||
"METRICS": {
|
"METRICS": {
|
||||||
|
|||||||
@@ -76,14 +76,14 @@ export default {
|
|||||||
businessHours,
|
businessHours,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
downloadAgentReports() {
|
downloadConversationReports() {
|
||||||
const { from, to } = this;
|
const { from, to } = this;
|
||||||
const fileName = generateFileName({
|
const fileName = generateFileName({
|
||||||
type: 'agent',
|
type: 'conversation',
|
||||||
to,
|
to,
|
||||||
businessHours: this.businessHours,
|
businessHours: this.businessHours,
|
||||||
});
|
});
|
||||||
this.$store.dispatch('downloadAgentReports', {
|
this.$store.dispatch('downloadConversationsSummaryReports', {
|
||||||
from,
|
from,
|
||||||
to,
|
to,
|
||||||
fileName,
|
fileName,
|
||||||
@@ -109,10 +109,10 @@ export default {
|
|||||||
<template>
|
<template>
|
||||||
<ReportHeader :header-title="$t('REPORT.HEADER')">
|
<ReportHeader :header-title="$t('REPORT.HEADER')">
|
||||||
<V4Button
|
<V4Button
|
||||||
:label="$t('REPORT.DOWNLOAD_AGENT_REPORTS')"
|
:label="$t('REPORT.DOWNLOAD_CONVERSATION_REPORTS')"
|
||||||
icon="i-ph-download-simple"
|
icon="i-ph-download-simple"
|
||||||
size="sm"
|
size="sm"
|
||||||
@click="downloadAgentReports"
|
@click="downloadConversationReports"
|
||||||
/>
|
/>
|
||||||
</ReportHeader>
|
</ReportHeader>
|
||||||
<div class="flex flex-col gap-3">
|
<div class="flex flex-col gap-3">
|
||||||
|
|||||||
@@ -234,6 +234,19 @@ export const actions = {
|
|||||||
console.error(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) {
|
downloadLabelReports(_, reportObj) {
|
||||||
return Report.getLabelReports(reportObj)
|
return Report.getLabelReports(reportObj)
|
||||||
.then(response => {
|
.then(response => {
|
||||||
|
|||||||
@@ -201,4 +201,24 @@ describe('#actions', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('#downloadConversationsSummaryReports', () => {
|
||||||
|
it('open CSV download prompt if API is success', async () => {
|
||||||
|
const data = `Conversations,Messages received,Messages sent,Avg first response time,Avg resolution time,Resolution count,Avg customer waiting time
|
||||||
|
217,323,623,23 hours 22 minutes,179 days 18 hours,30,48 days 4 hours`;
|
||||||
|
axios.get.mockResolvedValue({ data });
|
||||||
|
const param = {
|
||||||
|
from: 1631039400,
|
||||||
|
to: 1635013800,
|
||||||
|
fileName: 'conversations-summary-report-24-10-2021.csv',
|
||||||
|
};
|
||||||
|
actions.downloadConversationsSummaryReports(1, param);
|
||||||
|
await flushPromises();
|
||||||
|
|
||||||
|
expect(DownloadHelper.downloadCsvFile).toBeCalledWith(
|
||||||
|
param.fileName,
|
||||||
|
data
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
<%= CSVSafe.generate_line [I18n.t('reports.period', since: Date.strptime(params[:since], '%s'), until: Date.strptime(params[:until], '%s'))] %>
|
||||||
|
|
||||||
|
<% headers = [
|
||||||
|
I18n.t('reports.conversation_csv.conversations_count'),
|
||||||
|
I18n.t('reports.conversation_csv.incoming_messages_count'),
|
||||||
|
I18n.t('reports.conversation_csv.outgoing_messages_count'),
|
||||||
|
I18n.t('reports.conversation_csv.avg_first_response_time'),
|
||||||
|
I18n.t('reports.conversation_csv.avg_resolution_time'),
|
||||||
|
I18n.t('reports.conversation_csv.resolution_count'),
|
||||||
|
I18n.t('reports.conversation_csv.avg_customer_waiting_time')
|
||||||
|
]
|
||||||
|
%>
|
||||||
|
<%= CSVSafe.generate_line headers -%>
|
||||||
|
<% @report_data.each do |row| %>
|
||||||
|
<%= CSVSafe.generate_line row -%>
|
||||||
|
<% end %>
|
||||||
@@ -172,6 +172,14 @@ en:
|
|||||||
avg_resolution_time: Avg resolution time
|
avg_resolution_time: Avg resolution time
|
||||||
resolution_count: Resolution Count
|
resolution_count: Resolution Count
|
||||||
avg_customer_waiting_time: Avg customer waiting time
|
avg_customer_waiting_time: Avg customer waiting time
|
||||||
|
conversation_csv:
|
||||||
|
conversations_count: Conversations
|
||||||
|
incoming_messages_count: Messages received
|
||||||
|
outgoing_messages_count: Messages sent
|
||||||
|
avg_first_response_time: Avg first response time
|
||||||
|
avg_resolution_time: Avg resolution time
|
||||||
|
resolution_count: Resolution count
|
||||||
|
avg_customer_waiting_time: Avg customer waiting time
|
||||||
conversation_traffic_csv:
|
conversation_traffic_csv:
|
||||||
timezone: Timezone
|
timezone: Timezone
|
||||||
sla_csv:
|
sla_csv:
|
||||||
|
|||||||
@@ -430,6 +430,7 @@ Rails.application.routes.draw do
|
|||||||
get :labels
|
get :labels
|
||||||
get :teams
|
get :teams
|
||||||
get :conversations
|
get :conversations
|
||||||
|
get :conversations_summary
|
||||||
get :conversation_traffic
|
get :conversation_traffic
|
||||||
get :bot_metrics
|
get :bot_metrics
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user