From 52fad886b88e71f88a81ad44916da746b60cd56d Mon Sep 17 00:00:00 2001 From: Pranav Raj S Date: Fri, 27 May 2022 09:26:59 +0530 Subject: [PATCH] feat: Add business hours in downloadable reports (#4674) Co-authored-by: Aswin Dev P.S --- .../api/v2/accounts/reports_controller.rb | 27 +++++---- app/helpers/api/v2/accounts/reports_helper.rb | 56 +++++++++++++++++++ app/javascript/dashboard/api/reports.js | 16 +++--- .../dashboard/api/specs/reports.spec.js | 13 +++-- .../dashboard/helper/downloadHelper.js | 9 ++- .../helper/specs/downloadHelper.spec.js | 4 ++ .../reports/components/WootReports.vue | 6 +- .../dashboard/store/modules/reports.js | 8 +-- .../api/v2/accounts/reports/agents.csv.erb | 9 +-- .../api/v2/accounts/reports/inboxes.csv.erb | 19 ++++--- .../api/v2/accounts/reports/labels.csv.erb | 18 +++--- .../api/v2/accounts/reports/teams.csv.erb | 9 +-- config/locales/en.yml | 11 ++++ 13 files changed, 138 insertions(+), 67 deletions(-) create mode 100644 app/helpers/api/v2/accounts/reports_helper.rb diff --git a/app/controllers/api/v2/accounts/reports_controller.rb b/app/controllers/api/v2/accounts/reports_controller.rb index c2117583a..bbc4dde7b 100644 --- a/app/controllers/api/v2/accounts/reports_controller.rb +++ b/app/controllers/api/v2/accounts/reports_controller.rb @@ -1,4 +1,5 @@ class Api::V2::Accounts::ReportsController < Api::V1::Accounts::BaseController + include Api::V2::Accounts::ReportsHelper before_action :check_authorization def index @@ -12,27 +13,23 @@ class Api::V2::Accounts::ReportsController < Api::V1::Accounts::BaseController end def agents - response.headers['Content-Type'] = 'text/csv' - response.headers['Content-Disposition'] = 'attachment; filename=agents_report.csv' - render layout: false, template: 'api/v2/accounts/reports/agents.csv.erb', format: 'csv' + @report_data = generate_agents_report + generate_csv('agents_report', 'api/v2/accounts/reports/agents.csv.erb') end def inboxes - response.headers['Content-Type'] = 'text/csv' - response.headers['Content-Disposition'] = 'attachment; filename=inboxes_report.csv' - render layout: false, template: 'api/v2/accounts/reports/inboxes.csv.erb', format: 'csv' + @report_data = generate_inboxes_report + generate_csv('inboxes_report', 'api/v2/accounts/reports/inboxes.csv.erb') end def labels - response.headers['Content-Type'] = 'text/csv' - response.headers['Content-Disposition'] = 'attachment; filename=labels_report.csv' - render layout: false, template: 'api/v2/accounts/reports/labels.csv.erb', format: 'csv' + @report_data = generate_labels_report + generate_csv('labels_report', 'api/v2/accounts/reports/labels.csv.erb') end def teams - response.headers['Content-Type'] = 'text/csv' - response.headers['Content-Disposition'] = 'attachment; filename=teams_report.csv' - render layout: false, template: 'api/v2/accounts/reports/teams.csv.erb', format: 'csv' + @report_data = generate_teams_report + generate_csv('teams_report', 'api/v2/accounts/reports/teams.csv.erb') end def conversations @@ -43,6 +40,12 @@ class Api::V2::Accounts::ReportsController < Api::V1::Accounts::BaseController private + def generate_csv(filename, template) + response.headers['Content-Type'] = 'text/csv' + response.headers['Content-Disposition'] = "attachment; filename=#{filename}.csv" + render layout: false, template: template, format: 'csv' + end + def check_authorization raise Pundit::NotAuthorizedError unless Current.account_user.administrator? end diff --git a/app/helpers/api/v2/accounts/reports_helper.rb b/app/helpers/api/v2/accounts/reports_helper.rb new file mode 100644 index 000000000..0604eba2f --- /dev/null +++ b/app/helpers/api/v2/accounts/reports_helper.rb @@ -0,0 +1,56 @@ +module Api::V2::Accounts::ReportsHelper + def generate_agents_report + Current.account.users.map do |agent| + agent_report = generate_report({ type: :agent, id: agent.id }) + [agent.name] + generate_readable_report_metrics(agent_report) + end + end + + def generate_inboxes_report + Current.account.inboxes.map do |inbox| + inbox_report = generate_report({ type: :inbox, id: inbox.id }) + [inbox.name, inbox.channel&.name] + generate_readable_report_metrics(inbox_report) + end + end + + def generate_teams_report + Current.account.teams.map do |team| + team_report = generate_report({ type: :team, id: team.id }) + [team.name] + generate_readable_report_metrics(team_report) + end + end + + def generate_labels_report + Current.account.labels.map do |label| + label_report = generate_report({ type: :label, id: label.id }) + [label.title] + generate_readable_report_metrics(label_report) + end + end + + def generate_report(report_params) + V2::ReportBuilder.new( + Current.account, + report_params.merge( + { + since: params[:since], + until: params[:until], + business_hours: ActiveModel::Type::Boolean.new.cast(params[:business_hours]) + } + ) + ).summary + end + + private + + def generate_readable_report_metrics(report_metric) + [ + report_metric[:conversations_count], + time_to_minutes(report_metric[:avg_first_response_time]), + time_to_minutes(report_metric[:avg_resolution_time]) + ] + end + + def time_to_minutes(time_in_seconds) + (time_in_seconds / 60).to_i + end +end diff --git a/app/javascript/dashboard/api/reports.js b/app/javascript/dashboard/api/reports.js index ca50c062f..61fd0adca 100644 --- a/app/javascript/dashboard/api/reports.js +++ b/app/javascript/dashboard/api/reports.js @@ -53,27 +53,27 @@ class ReportsAPI extends ApiClient { }); } - getAgentReports(since, until) { + getAgentReports({ from: since, to: until, businessHours }) { return axios.get(`${this.url}/agents`, { - params: { since, until }, + params: { since, until, business_hours: businessHours }, }); } - getLabelReports(since, until) { + getLabelReports({ from: since, to: until, businessHours }) { return axios.get(`${this.url}/labels`, { - params: { since, until }, + params: { since, until, business_hours: businessHours }, }); } - getInboxReports(since, until) { + getInboxReports({ from: since, to: until, businessHours }) { return axios.get(`${this.url}/inboxes`, { - params: { since, until }, + params: { since, until, business_hours: businessHours }, }); } - getTeamReports(since, until) { + getTeamReports({ from: since, to: until, businessHours }) { return axios.get(`${this.url}/teams`, { - params: { since, until }, + params: { since, until, business_hours: businessHours }, }); } } diff --git a/app/javascript/dashboard/api/specs/reports.spec.js b/app/javascript/dashboard/api/specs/reports.spec.js index b51c87db5..4e2a8f38f 100644 --- a/app/javascript/dashboard/api/specs/reports.spec.js +++ b/app/javascript/dashboard/api/specs/reports.spec.js @@ -47,20 +47,25 @@ describe('#Reports API', () => { }); it('#getAgentReports', () => { - reportsAPI.getAgentReports(1621103400, 1621621800); + reportsAPI.getAgentReports({ + from: 1621103400, + to: 1621621800, + businessHours: true, + }); expect(context.axiosMock.get).toHaveBeenCalledWith( '/api/v2/reports/agents', { params: { since: 1621103400, until: 1621621800, + business_hours: true, }, } ); }); it('#getLabelReports', () => { - reportsAPI.getLabelReports(1621103400, 1621621800); + reportsAPI.getLabelReports({ from: 1621103400, to: 1621621800 }); expect(context.axiosMock.get).toHaveBeenCalledWith( '/api/v2/reports/labels', { @@ -73,7 +78,7 @@ describe('#Reports API', () => { }); it('#getInboxReports', () => { - reportsAPI.getInboxReports(1621103400, 1621621800); + reportsAPI.getInboxReports({ from: 1621103400, to: 1621621800 }); expect(context.axiosMock.get).toHaveBeenCalledWith( '/api/v2/reports/inboxes', { @@ -86,7 +91,7 @@ describe('#Reports API', () => { }); it('#getTeamReports', () => { - reportsAPI.getTeamReports(1621103400, 1621621800); + reportsAPI.getTeamReports({ from: 1621103400, to: 1621621800 }); expect(context.axiosMock.get).toHaveBeenCalledWith( '/api/v2/reports/teams', { diff --git a/app/javascript/dashboard/helper/downloadHelper.js b/app/javascript/dashboard/helper/downloadHelper.js index 68c3b5cee..94b6a69bd 100644 --- a/app/javascript/dashboard/helper/downloadHelper.js +++ b/app/javascript/dashboard/helper/downloadHelper.js @@ -13,5 +13,10 @@ export const downloadCsvFile = (fileName, content) => { return link; }; -export const generateFileName = ({ type, to }) => - `${type}-report-${format(fromUnixTime(to), 'dd-MM-yyyy')}.csv`; +export const generateFileName = ({ type, to, businessHours = false }) => { + let name = `${type}-report-${format(fromUnixTime(to), 'dd-MM-yyyy')}`; + if (businessHours) { + name = `${name}-business-hours`; + } + return `${name}.csv`; +}; diff --git a/app/javascript/dashboard/helper/specs/downloadHelper.spec.js b/app/javascript/dashboard/helper/specs/downloadHelper.spec.js index 6cdec4160..477123422 100644 --- a/app/javascript/dashboard/helper/specs/downloadHelper.spec.js +++ b/app/javascript/dashboard/helper/specs/downloadHelper.spec.js @@ -5,5 +5,9 @@ describe('#generateFileName', () => { expect(generateFileName({ type: 'csat', to: 1652812199 })).toEqual( 'csat-report-17-05-2022.csv' ); + + expect( + generateFileName({ type: 'csat', to: 1652812199, businessHours: true }) + ).toEqual('csat-report-17-05-2022-business-hours.csv'); }); }); diff --git a/app/javascript/dashboard/routes/dashboard/settings/reports/components/WootReports.vue b/app/javascript/dashboard/routes/dashboard/settings/reports/components/WootReports.vue index 34188a12a..098960e85 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/reports/components/WootReports.vue +++ b/app/javascript/dashboard/routes/dashboard/settings/reports/components/WootReports.vue @@ -251,7 +251,7 @@ export default { }); }, downloadReports() { - const { from, to, type } = this; + const { from, to, type, businessHours } = this; const dispatchMethods = { agent: 'downloadAgentReports', label: 'downloadLabelReports', @@ -259,8 +259,8 @@ export default { team: 'downloadTeamReports', }; if (dispatchMethods[type]) { - const fileName = generateFileName({ type, to }); - const params = { from, to, fileName }; + const fileName = generateFileName({ type, to, businessHours }); + const params = { from, to, fileName, businessHours }; this.$store.dispatch(dispatchMethods[type], params); } }, diff --git a/app/javascript/dashboard/store/modules/reports.js b/app/javascript/dashboard/store/modules/reports.js index 9ae571803..0404d1aca 100644 --- a/app/javascript/dashboard/store/modules/reports.js +++ b/app/javascript/dashboard/store/modules/reports.js @@ -120,7 +120,7 @@ export const actions = { commit(types.default.UPDATE_REPORT_AGENTS_STATUS, data); }, downloadAgentReports(_, reportObj) { - return Report.getAgentReports(reportObj.from, reportObj.to) + return Report.getAgentReports(reportObj) .then(response => { downloadCsvFile(reportObj.fileName, response.data); }) @@ -129,7 +129,7 @@ export const actions = { }); }, downloadLabelReports(_, reportObj) { - return Report.getLabelReports(reportObj.from, reportObj.to) + return Report.getLabelReports(reportObj) .then(response => { downloadCsvFile(reportObj.fileName, response.data); }) @@ -138,7 +138,7 @@ export const actions = { }); }, downloadInboxReports(_, reportObj) { - return Report.getInboxReports(reportObj.from, reportObj.to) + return Report.getInboxReports(reportObj) .then(response => { downloadCsvFile(reportObj.fileName, response.data); }) @@ -147,7 +147,7 @@ export const actions = { }); }, downloadTeamReports(_, reportObj) { - return Report.getTeamReports(reportObj.from, reportObj.to) + return Report.getTeamReports(reportObj) .then(response => { downloadCsvFile(reportObj.fileName, response.data); }) diff --git a/app/views/api/v2/accounts/reports/agents.csv.erb b/app/views/api/v2/accounts/reports/agents.csv.erb index 9c8f1f814..bed88dfd7 100644 --- a/app/views/api/v2/accounts/reports/agents.csv.erb +++ b/app/views/api/v2/accounts/reports/agents.csv.erb @@ -6,14 +6,7 @@ ] %> <%= CSV.generate_line headers -%> -<% Current.account.users.each do |agent| %> - <% agent_report = V2::ReportBuilder.new(Current.account, { - type: :agent, - id: agent.id, - since: params[:since], - until: params[:until] - }).summary %> - <% row = [ agent.name, agent_report[:conversations_count], (agent_report[:avg_first_response_time]/60).to_i, (agent_report[:avg_resolution_time]/60).to_i ] %> +<% @report_data.each do |row| %> <%= CSV.generate_line row -%> <% end %> <%= CSV.generate_line [I18n.t('reports.period', since: Date.strptime(params[:since], '%s'), until: Date.strptime(params[:until], '%s'))] %> diff --git a/app/views/api/v2/accounts/reports/inboxes.csv.erb b/app/views/api/v2/accounts/reports/inboxes.csv.erb index c82be2df4..89b3b1182 100644 --- a/app/views/api/v2/accounts/reports/inboxes.csv.erb +++ b/app/views/api/v2/accounts/reports/inboxes.csv.erb @@ -1,12 +1,13 @@ -<% headers = ['Inbox name', 'Conversations count', 'Avg first response time (Minutes)', 'Avg resolution time (Minutes)'] %> +<% headers = [ + I18n.t('reports.inbox_csv.inbox_name'), + I18n.t('reports.inbox_csv.inbox_type'), + I18n.t('reports.inbox_csv.conversations_count'), + I18n.t('reports.inbox_csv.avg_first_response_time'), + I18n.t('reports.inbox_csv.avg_resolution_time') + ] +%> <%= CSV.generate_line headers -%> -<% Current.account.inboxes.each do |inbox| %> - <% inbox_report = V2::ReportBuilder.new(Current.account, { - type: :inbox, - id: inbox.id, - since: params[:since], - until: params[:until] - }).summary %> - <% row = [ inbox.name, inbox_report[:conversations_count], (inbox_report[:avg_first_response_time]/60).to_i, (inbox_report[:avg_resolution_time]/60).to_i ] %> +<% @report_data.each do |row| %> <%= CSV.generate_line row -%> <% end %> +<%= CSV.generate_line [I18n.t('reports.period', since: Date.strptime(params[:since], '%s'), until: Date.strptime(params[:until], '%s'))] %> diff --git a/app/views/api/v2/accounts/reports/labels.csv.erb b/app/views/api/v2/accounts/reports/labels.csv.erb index dcbc2b974..9fd0a9cc5 100644 --- a/app/views/api/v2/accounts/reports/labels.csv.erb +++ b/app/views/api/v2/accounts/reports/labels.csv.erb @@ -1,12 +1,12 @@ -<% headers = ['Label Title', 'Conversations count', 'Avg first response time (Minutes)', 'Avg resolution time (Minutes)'] %> +<% headers = [ + I18n.t('reports.label_csv.label_title'), + I18n.t('reports.label_csv.conversations_count'), + I18n.t('reports.label_csv.avg_first_response_time'), + I18n.t('reports.label_csv.avg_resolution_time') + ] +%> <%= CSV.generate_line headers -%> -<% Current.account.labels.each do |label| %> - <% label_report = V2::ReportBuilder.new(Current.account, { - type: :label, - id: label.id, - since: params[:since], - until: params[:until] - }).summary %> - <% row = [ label.title, label_report[:conversations_count], (label_report[:avg_first_response_time]/60).to_i, (label_report[:avg_resolution_time]/60).to_i ] %> +<% @report_data.each do |row| %> <%= CSV.generate_line row -%> <% end %> +<%= CSV.generate_line [I18n.t('reports.period', since: Date.strptime(params[:since], '%s'), until: Date.strptime(params[:until], '%s'))] %> diff --git a/app/views/api/v2/accounts/reports/teams.csv.erb b/app/views/api/v2/accounts/reports/teams.csv.erb index f7b82cb1d..e50e195bd 100644 --- a/app/views/api/v2/accounts/reports/teams.csv.erb +++ b/app/views/api/v2/accounts/reports/teams.csv.erb @@ -6,14 +6,7 @@ ] %> <%= CSV.generate_line headers -%> -<% Current.account.teams.each do |team| %> - <% team_report = V2::ReportBuilder.new(Current.account, { - type: :team, - id: team.id, - since: params[:since], - until: params[:until] - }).summary %> - <% row = [ team.name, team_report[:conversations_count], (team_report[:avg_first_response_time]/60).to_i, (team_report[:avg_resolution_time]/60).to_i ] %> +<% @report_data.each do |row| %> <%= CSV.generate_line row -%> <% end %> <%= CSV.generate_line [I18n.t('reports.period', since: Date.strptime(params[:since], '%s'), until: Date.strptime(params[:until], '%s'))] %> diff --git a/config/locales/en.yml b/config/locales/en.yml index bd7829b53..b986ef1c1 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -54,6 +54,17 @@ en: conversations_count: Conversations count avg_first_response_time: Avg first response time (Minutes) avg_resolution_time: Avg resolution time (Minutes) + inbox_csv: + inbox_name: Inbox name + inbox_type: Inbox type + conversations_count: No. of conversations + avg_first_response_time: Avg first response time (Minutes) + avg_resolution_time: Avg resolution time (Minutes) + label_csv: + label_title: Label + conversations_count: No. of conversations + avg_first_response_time: Avg first response time (Minutes) + avg_resolution_time: Avg resolution time (Minutes) team_csv: team_name: Team name conversations_count: Conversations count