feat: Add business hours in downloadable reports (#4674)

Co-authored-by: Aswin Dev P.S <aswindevps@gmail.com>
This commit is contained in:
Pranav Raj S 2022-05-27 09:26:59 +05:30 committed by GitHub
parent dceeb57a1d
commit 52fad886b8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 138 additions and 67 deletions

View file

@ -1,4 +1,5 @@
class Api::V2::Accounts::ReportsController < Api::V1::Accounts::BaseController class Api::V2::Accounts::ReportsController < Api::V1::Accounts::BaseController
include Api::V2::Accounts::ReportsHelper
before_action :check_authorization before_action :check_authorization
def index def index
@ -12,27 +13,23 @@ class Api::V2::Accounts::ReportsController < Api::V1::Accounts::BaseController
end end
def agents def agents
response.headers['Content-Type'] = 'text/csv' @report_data = generate_agents_report
response.headers['Content-Disposition'] = 'attachment; filename=agents_report.csv' generate_csv('agents_report', 'api/v2/accounts/reports/agents.csv.erb')
render layout: false, template: 'api/v2/accounts/reports/agents.csv.erb', format: 'csv'
end end
def inboxes def inboxes
response.headers['Content-Type'] = 'text/csv' @report_data = generate_inboxes_report
response.headers['Content-Disposition'] = 'attachment; filename=inboxes_report.csv' generate_csv('inboxes_report', 'api/v2/accounts/reports/inboxes.csv.erb')
render layout: false, template: 'api/v2/accounts/reports/inboxes.csv.erb', format: 'csv'
end end
def labels def labels
response.headers['Content-Type'] = 'text/csv' @report_data = generate_labels_report
response.headers['Content-Disposition'] = 'attachment; filename=labels_report.csv' generate_csv('labels_report', 'api/v2/accounts/reports/labels.csv.erb')
render layout: false, template: 'api/v2/accounts/reports/labels.csv.erb', format: 'csv'
end end
def teams def teams
response.headers['Content-Type'] = 'text/csv' @report_data = generate_teams_report
response.headers['Content-Disposition'] = 'attachment; filename=teams_report.csv' generate_csv('teams_report', 'api/v2/accounts/reports/teams.csv.erb')
render layout: false, template: 'api/v2/accounts/reports/teams.csv.erb', format: 'csv'
end end
def conversations def conversations
@ -43,6 +40,12 @@ class Api::V2::Accounts::ReportsController < Api::V1::Accounts::BaseController
private 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 def check_authorization
raise Pundit::NotAuthorizedError unless Current.account_user.administrator? raise Pundit::NotAuthorizedError unless Current.account_user.administrator?
end end

View file

@ -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

View file

@ -53,27 +53,27 @@ class ReportsAPI extends ApiClient {
}); });
} }
getAgentReports(since, until) { getAgentReports({ from: since, to: until, businessHours }) {
return axios.get(`${this.url}/agents`, { 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`, { 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`, { 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`, { return axios.get(`${this.url}/teams`, {
params: { since, until }, params: { since, until, business_hours: businessHours },
}); });
} }
} }

View file

@ -47,20 +47,25 @@ describe('#Reports API', () => {
}); });
it('#getAgentReports', () => { it('#getAgentReports', () => {
reportsAPI.getAgentReports(1621103400, 1621621800); reportsAPI.getAgentReports({
from: 1621103400,
to: 1621621800,
businessHours: true,
});
expect(context.axiosMock.get).toHaveBeenCalledWith( expect(context.axiosMock.get).toHaveBeenCalledWith(
'/api/v2/reports/agents', '/api/v2/reports/agents',
{ {
params: { params: {
since: 1621103400, since: 1621103400,
until: 1621621800, until: 1621621800,
business_hours: true,
}, },
} }
); );
}); });
it('#getLabelReports', () => { it('#getLabelReports', () => {
reportsAPI.getLabelReports(1621103400, 1621621800); reportsAPI.getLabelReports({ from: 1621103400, to: 1621621800 });
expect(context.axiosMock.get).toHaveBeenCalledWith( expect(context.axiosMock.get).toHaveBeenCalledWith(
'/api/v2/reports/labels', '/api/v2/reports/labels',
{ {
@ -73,7 +78,7 @@ describe('#Reports API', () => {
}); });
it('#getInboxReports', () => { it('#getInboxReports', () => {
reportsAPI.getInboxReports(1621103400, 1621621800); reportsAPI.getInboxReports({ from: 1621103400, to: 1621621800 });
expect(context.axiosMock.get).toHaveBeenCalledWith( expect(context.axiosMock.get).toHaveBeenCalledWith(
'/api/v2/reports/inboxes', '/api/v2/reports/inboxes',
{ {
@ -86,7 +91,7 @@ describe('#Reports API', () => {
}); });
it('#getTeamReports', () => { it('#getTeamReports', () => {
reportsAPI.getTeamReports(1621103400, 1621621800); reportsAPI.getTeamReports({ from: 1621103400, to: 1621621800 });
expect(context.axiosMock.get).toHaveBeenCalledWith( expect(context.axiosMock.get).toHaveBeenCalledWith(
'/api/v2/reports/teams', '/api/v2/reports/teams',
{ {

View file

@ -13,5 +13,10 @@ export const downloadCsvFile = (fileName, content) => {
return link; return link;
}; };
export const generateFileName = ({ type, to }) => export const generateFileName = ({ type, to, businessHours = false }) => {
`${type}-report-${format(fromUnixTime(to), 'dd-MM-yyyy')}.csv`; let name = `${type}-report-${format(fromUnixTime(to), 'dd-MM-yyyy')}`;
if (businessHours) {
name = `${name}-business-hours`;
}
return `${name}.csv`;
};

View file

@ -5,5 +5,9 @@ describe('#generateFileName', () => {
expect(generateFileName({ type: 'csat', to: 1652812199 })).toEqual( expect(generateFileName({ type: 'csat', to: 1652812199 })).toEqual(
'csat-report-17-05-2022.csv' 'csat-report-17-05-2022.csv'
); );
expect(
generateFileName({ type: 'csat', to: 1652812199, businessHours: true })
).toEqual('csat-report-17-05-2022-business-hours.csv');
}); });
}); });

View file

@ -251,7 +251,7 @@ export default {
}); });
}, },
downloadReports() { downloadReports() {
const { from, to, type } = this; const { from, to, type, businessHours } = this;
const dispatchMethods = { const dispatchMethods = {
agent: 'downloadAgentReports', agent: 'downloadAgentReports',
label: 'downloadLabelReports', label: 'downloadLabelReports',
@ -259,8 +259,8 @@ export default {
team: 'downloadTeamReports', team: 'downloadTeamReports',
}; };
if (dispatchMethods[type]) { if (dispatchMethods[type]) {
const fileName = generateFileName({ type, to }); const fileName = generateFileName({ type, to, businessHours });
const params = { from, to, fileName }; const params = { from, to, fileName, businessHours };
this.$store.dispatch(dispatchMethods[type], params); this.$store.dispatch(dispatchMethods[type], params);
} }
}, },

View file

@ -120,7 +120,7 @@ export const actions = {
commit(types.default.UPDATE_REPORT_AGENTS_STATUS, data); commit(types.default.UPDATE_REPORT_AGENTS_STATUS, data);
}, },
downloadAgentReports(_, reportObj) { downloadAgentReports(_, reportObj) {
return Report.getAgentReports(reportObj.from, reportObj.to) return Report.getAgentReports(reportObj)
.then(response => { .then(response => {
downloadCsvFile(reportObj.fileName, response.data); downloadCsvFile(reportObj.fileName, response.data);
}) })
@ -129,7 +129,7 @@ export const actions = {
}); });
}, },
downloadLabelReports(_, reportObj) { downloadLabelReports(_, reportObj) {
return Report.getLabelReports(reportObj.from, reportObj.to) return Report.getLabelReports(reportObj)
.then(response => { .then(response => {
downloadCsvFile(reportObj.fileName, response.data); downloadCsvFile(reportObj.fileName, response.data);
}) })
@ -138,7 +138,7 @@ export const actions = {
}); });
}, },
downloadInboxReports(_, reportObj) { downloadInboxReports(_, reportObj) {
return Report.getInboxReports(reportObj.from, reportObj.to) return Report.getInboxReports(reportObj)
.then(response => { .then(response => {
downloadCsvFile(reportObj.fileName, response.data); downloadCsvFile(reportObj.fileName, response.data);
}) })
@ -147,7 +147,7 @@ export const actions = {
}); });
}, },
downloadTeamReports(_, reportObj) { downloadTeamReports(_, reportObj) {
return Report.getTeamReports(reportObj.from, reportObj.to) return Report.getTeamReports(reportObj)
.then(response => { .then(response => {
downloadCsvFile(reportObj.fileName, response.data); downloadCsvFile(reportObj.fileName, response.data);
}) })

View file

@ -6,14 +6,7 @@
] ]
%> %>
<%= CSV.generate_line headers -%> <%= CSV.generate_line headers -%>
<% Current.account.users.each do |agent| %> <% @report_data.each do |row| %>
<% 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 ] %>
<%= CSV.generate_line row -%> <%= CSV.generate_line row -%>
<% end %> <% end %>
<%= CSV.generate_line [I18n.t('reports.period', since: Date.strptime(params[:since], '%s'), until: Date.strptime(params[:until], '%s'))] %> <%= CSV.generate_line [I18n.t('reports.period', since: Date.strptime(params[:since], '%s'), until: Date.strptime(params[:until], '%s'))] %>

View file

@ -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 -%> <%= CSV.generate_line headers -%>
<% Current.account.inboxes.each do |inbox| %> <% @report_data.each do |row| %>
<% 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 ] %>
<%= CSV.generate_line row -%> <%= CSV.generate_line row -%>
<% end %> <% end %>
<%= CSV.generate_line [I18n.t('reports.period', since: Date.strptime(params[:since], '%s'), until: Date.strptime(params[:until], '%s'))] %>

View file

@ -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 -%> <%= CSV.generate_line headers -%>
<% Current.account.labels.each do |label| %> <% @report_data.each do |row| %>
<% 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 ] %>
<%= CSV.generate_line row -%> <%= CSV.generate_line row -%>
<% end %> <% end %>
<%= CSV.generate_line [I18n.t('reports.period', since: Date.strptime(params[:since], '%s'), until: Date.strptime(params[:until], '%s'))] %>

View file

@ -6,14 +6,7 @@
] ]
%> %>
<%= CSV.generate_line headers -%> <%= CSV.generate_line headers -%>
<% Current.account.teams.each do |team| %> <% @report_data.each do |row| %>
<% 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 ] %>
<%= CSV.generate_line row -%> <%= CSV.generate_line row -%>
<% end %> <% end %>
<%= CSV.generate_line [I18n.t('reports.period', since: Date.strptime(params[:since], '%s'), until: Date.strptime(params[:until], '%s'))] %> <%= CSV.generate_line [I18n.t('reports.period', since: Date.strptime(params[:since], '%s'), until: Date.strptime(params[:until], '%s'))] %>

View file

@ -54,6 +54,17 @@ en:
conversations_count: Conversations count conversations_count: Conversations count
avg_first_response_time: Avg first response time (Minutes) avg_first_response_time: Avg first response time (Minutes)
avg_resolution_time: Avg resolution 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_csv:
team_name: Team name team_name: Team name
conversations_count: Conversations count conversations_count: Conversations count