mirror of
https://github.com/lingble/chatwoot.git
synced 2025-11-02 03:57:52 +00:00
feat: Improve the rendering of CSV exports (#8914)
Improve the rendering of the datestamp
This commit is contained in:
@@ -45,12 +45,8 @@ module Api::V2::Accounts::ReportsHelper
|
|||||||
def generate_readable_report_metrics(report_metric)
|
def generate_readable_report_metrics(report_metric)
|
||||||
[
|
[
|
||||||
report_metric[:conversations_count],
|
report_metric[:conversations_count],
|
||||||
time_to_minutes(report_metric[:avg_first_response_time]),
|
Reports::TimeFormatPresenter.new(report_metric[:avg_first_response_time]).format,
|
||||||
time_to_minutes(report_metric[:avg_resolution_time])
|
Reports::TimeFormatPresenter.new(report_metric[:avg_resolution_time]).format
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|
||||||
def time_to_minutes(time_in_seconds)
|
|
||||||
(time_in_seconds / 60).to_i
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|||||||
33
app/presenters/reports/time_format_presenter.rb
Normal file
33
app/presenters/reports/time_format_presenter.rb
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
class Reports::TimeFormatPresenter
|
||||||
|
include ActionView::Helpers::TextHelper
|
||||||
|
|
||||||
|
attr_reader :seconds
|
||||||
|
|
||||||
|
def initialize(seconds)
|
||||||
|
@seconds = seconds.to_i
|
||||||
|
end
|
||||||
|
|
||||||
|
def format
|
||||||
|
return '--' if seconds.nil? || seconds.zero?
|
||||||
|
|
||||||
|
days, remainder = seconds.divmod(86_400)
|
||||||
|
hours, remainder = remainder.divmod(3600)
|
||||||
|
minutes, seconds = remainder.divmod(60)
|
||||||
|
|
||||||
|
format_components(days: days, hours: hours, minutes: minutes, seconds: seconds)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def format_components(components)
|
||||||
|
formatted_components = components.filter_map do |unit, value|
|
||||||
|
next if value.zero?
|
||||||
|
|
||||||
|
I18n.t("time_units.#{unit}", count: value)
|
||||||
|
end
|
||||||
|
|
||||||
|
return I18n.t('time_units.seconds', count: 0) if formatted_components.empty?
|
||||||
|
|
||||||
|
formatted_components.first(2).join(' ')
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
<%= CSVSafe.generate_line [I18n.t('reports.period', since: Date.strptime(params[:since], '%s'), until: Date.strptime(params[:until], '%s'))] %>
|
||||||
|
|
||||||
<% headers = [
|
<% headers = [
|
||||||
I18n.t('reports.agent_csv.agent_name'),
|
I18n.t('reports.agent_csv.agent_name'),
|
||||||
I18n.t('reports.agent_csv.conversations_count'),
|
I18n.t('reports.agent_csv.conversations_count'),
|
||||||
@@ -9,4 +11,3 @@
|
|||||||
<% @report_data.each do |row| %>
|
<% @report_data.each do |row| %>
|
||||||
<%= CSVSafe.generate_line row -%>
|
<%= CSVSafe.generate_line row -%>
|
||||||
<% end %>
|
<% end %>
|
||||||
<%= CSVSafe.generate_line [I18n.t('reports.period', since: Date.strptime(params[:since], '%s'), until: Date.strptime(params[:until], '%s'))] %>
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<%= CSV.generate_line [I18n.t('reports.conversation_traffic_csv.timezone'), @timezone] %>
|
<%= CSV.generate_line [I18n.t('reports.conversation_traffic_csv.timezone'), @timezone] %>
|
||||||
|
|
||||||
<% @report_data.each do |row| %>
|
<% @report_data.each do |row| %>
|
||||||
<%= CSVSafe.generate_line row -%>
|
<%= CSVSafe.generate_line row -%>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
<%= CSVSafe.generate_line [I18n.t('reports.period', since: Date.strptime(params[:since], '%s'), until: Date.strptime(params[:until], '%s'))] %>
|
||||||
|
|
||||||
<% headers = [
|
<% headers = [
|
||||||
I18n.t('reports.inbox_csv.inbox_name'),
|
I18n.t('reports.inbox_csv.inbox_name'),
|
||||||
I18n.t('reports.inbox_csv.inbox_type'),
|
I18n.t('reports.inbox_csv.inbox_type'),
|
||||||
@@ -10,4 +12,3 @@
|
|||||||
<% @report_data.each do |row| %>
|
<% @report_data.each do |row| %>
|
||||||
<%= CSVSafe.generate_line row -%>
|
<%= CSVSafe.generate_line row -%>
|
||||||
<% end %>
|
<% end %>
|
||||||
<%= CSVSafe.generate_line [I18n.t('reports.period', since: Date.strptime(params[:since], '%s'), until: Date.strptime(params[:until], '%s'))] %>
|
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
<%= CSVSafe.generate_line [I18n.t('reports.period', since: Date.strptime(params[:since], '%s'), until: Date.strptime(params[:until], '%s'))] %>
|
||||||
|
|
||||||
<% headers = [
|
<% headers = [
|
||||||
I18n.t('reports.label_csv.label_title'),
|
I18n.t('reports.label_csv.label_title'),
|
||||||
I18n.t('reports.label_csv.conversations_count'),
|
I18n.t('reports.label_csv.conversations_count'),
|
||||||
@@ -9,4 +11,3 @@
|
|||||||
<% @report_data.each do |row| %>
|
<% @report_data.each do |row| %>
|
||||||
<%= CSVSafe.generate_line row -%>
|
<%= CSVSafe.generate_line row -%>
|
||||||
<% end %>
|
<% end %>
|
||||||
<%= CSVSafe.generate_line [I18n.t('reports.period', since: Date.strptime(params[:since], '%s'), until: Date.strptime(params[:until], '%s'))] %>
|
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
<%= CSVSafe.generate_line [I18n.t('reports.period', since: Date.strptime(params[:since], '%s'), until: Date.strptime(params[:until], '%s'))] %>
|
||||||
|
|
||||||
<% headers = [
|
<% headers = [
|
||||||
I18n.t('reports.team_csv.team_name'),
|
I18n.t('reports.team_csv.team_name'),
|
||||||
I18n.t('reports.team_csv.conversations_count'),
|
I18n.t('reports.team_csv.conversations_count'),
|
||||||
@@ -9,4 +11,3 @@
|
|||||||
<% @report_data.each do |row| %>
|
<% @report_data.each do |row| %>
|
||||||
<%= CSVSafe.generate_line row -%>
|
<%= CSVSafe.generate_line row -%>
|
||||||
<% end %>
|
<% end %>
|
||||||
<%= CSVSafe.generate_line [I18n.t('reports.period', since: Date.strptime(params[:since], '%s'), until: Date.strptime(params[:until], '%s'))] %>
|
|
||||||
|
|||||||
@@ -83,25 +83,25 @@ en:
|
|||||||
utc_warning: The report generated is in UTC timezone
|
utc_warning: The report generated is in UTC timezone
|
||||||
agent_csv:
|
agent_csv:
|
||||||
agent_name: Agent name
|
agent_name: Agent name
|
||||||
conversations_count: Conversations count
|
conversations_count: Assigned conversations
|
||||||
avg_first_response_time: Avg first response time (Minutes)
|
avg_first_response_time: Avg first response time
|
||||||
avg_resolution_time: Avg resolution time (Minutes)
|
avg_resolution_time: Avg resolution time
|
||||||
inbox_csv:
|
inbox_csv:
|
||||||
inbox_name: Inbox name
|
inbox_name: Inbox name
|
||||||
inbox_type: Inbox type
|
inbox_type: Inbox type
|
||||||
conversations_count: No. of conversations
|
conversations_count: No. of conversations
|
||||||
avg_first_response_time: Avg first response time (Minutes)
|
avg_first_response_time: Avg first response time
|
||||||
avg_resolution_time: Avg resolution time (Minutes)
|
avg_resolution_time: Avg resolution time
|
||||||
label_csv:
|
label_csv:
|
||||||
label_title: Label
|
label_title: Label
|
||||||
conversations_count: No. of conversations
|
conversations_count: No. of conversations
|
||||||
avg_first_response_time: Avg first response time (Minutes)
|
avg_first_response_time: Avg first response time
|
||||||
avg_resolution_time: Avg resolution time (Minutes)
|
avg_resolution_time: Avg resolution time
|
||||||
team_csv:
|
team_csv:
|
||||||
team_name: Team name
|
team_name: Team name
|
||||||
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
|
||||||
avg_resolution_time: Avg resolution time (Minutes)
|
avg_resolution_time: Avg resolution time
|
||||||
conversation_traffic_csv:
|
conversation_traffic_csv:
|
||||||
timezone: Timezone
|
timezone: Timezone
|
||||||
default_group_by: day
|
default_group_by: day
|
||||||
@@ -245,3 +245,16 @@ en:
|
|||||||
inbox_name: Inbox
|
inbox_name: Inbox
|
||||||
inbox_type: Inbox Type
|
inbox_type: Inbox Type
|
||||||
button: Open conversation
|
button: Open conversation
|
||||||
|
time_units:
|
||||||
|
days:
|
||||||
|
one: "%{count} day"
|
||||||
|
other: "%{count} days"
|
||||||
|
hours:
|
||||||
|
one: "%{count} hour"
|
||||||
|
other: "%{count} hours"
|
||||||
|
minutes:
|
||||||
|
one: "%{count} minute"
|
||||||
|
other: "%{count} minutes"
|
||||||
|
seconds:
|
||||||
|
one: "%{count} second"
|
||||||
|
other: "%{count} seconds"
|
||||||
|
|||||||
73
spec/presenters/reports/time_format_presenter_spec.rb
Normal file
73
spec/presenters/reports/time_format_presenter_spec.rb
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
RSpec.describe Reports::TimeFormatPresenter do
|
||||||
|
describe '#format' do
|
||||||
|
context 'when formatting days' do
|
||||||
|
it 'formats single day correctly' do
|
||||||
|
expect(described_class.new(86_400).format).to eq '1 day'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'formats multiple days correctly' do
|
||||||
|
expect(described_class.new(172_800).format).to eq '2 days'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'includes seconds with days correctly' do
|
||||||
|
expect(described_class.new(86_401).format).to eq '1 day 1 second'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'includes hours with days correctly' do
|
||||||
|
expect(described_class.new(93_600).format).to eq '1 day 2 hours'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'includes minutes with days correctly' do
|
||||||
|
expect(described_class.new(86_461).format).to eq '1 day 1 minute'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when formatting hours' do
|
||||||
|
it 'formats single hour correctly' do
|
||||||
|
expect(described_class.new(3600).format).to eq '1 hour'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'formats multiple hours correctly' do
|
||||||
|
expect(described_class.new(7200).format).to eq '2 hours'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'includes seconds with hours correctly' do
|
||||||
|
expect(described_class.new(3601).format).to eq '1 hour 1 second'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'includes minutes with hours correctly' do
|
||||||
|
expect(described_class.new(3660).format).to eq '1 hour 1 minute'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when formatting minutes' do
|
||||||
|
it 'formats single minute correctly' do
|
||||||
|
expect(described_class.new(60).format).to eq '1 minute'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'formats multiple minutes correctly' do
|
||||||
|
expect(described_class.new(120).format).to eq '2 minutes'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'includes seconds with minutes correctly' do
|
||||||
|
expect(described_class.new(62).format).to eq '1 minute 2 seconds'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when formatting seconds' do
|
||||||
|
it 'formats multiple seconds correctly' do
|
||||||
|
expect(described_class.new(56).format).to eq '56 seconds'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'handles floating-point seconds by truncating to the nearest lower second' do
|
||||||
|
expect(described_class.new(55.2).format).to eq '55 seconds'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'formats single second correctly' do
|
||||||
|
expect(described_class.new(1).format).to eq '1 second'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
Reference in New Issue
Block a user