mirror of
https://github.com/lingble/chatwoot.git
synced 2025-11-01 11:37:58 +00:00
feat: better download for conversation traffic heatmap (#6755)
* feat: genearte report in a grid * refactor: update API usage * refactor: separate generate method * refactor: abstract transform_data * feat: annotate with comments * feat: add explicit timezone * feat: download data only in user timezone * fix: dates included in heatmap
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
class Api::V2::Accounts::ReportsController < Api::V1::Accounts::BaseController
|
||||
include Api::V2::Accounts::ReportsHelper
|
||||
include Api::V2::Accounts::HeatmapHelper
|
||||
|
||||
before_action :check_authorization
|
||||
|
||||
def index
|
||||
@@ -34,6 +36,9 @@ class Api::V2::Accounts::ReportsController < Api::V1::Accounts::BaseController
|
||||
|
||||
def conversation_traffic
|
||||
@report_data = generate_conversations_heatmap_report
|
||||
timezone_offset = (params[:timezone_offset] || 0).to_f
|
||||
@timezone = ActiveSupport::TimeZone[timezone_offset]
|
||||
|
||||
generate_csv('conversation_traffic_reports', 'api/v2/accounts/reports/conversation_traffic')
|
||||
end
|
||||
|
||||
|
||||
103
app/helpers/api/v2/accounts/heatmap_helper.rb
Normal file
103
app/helpers/api/v2/accounts/heatmap_helper.rb
Normal file
@@ -0,0 +1,103 @@
|
||||
module Api::V2::Accounts::HeatmapHelper
|
||||
def generate_conversations_heatmap_report
|
||||
timezone_data = generate_heatmap_data_for_timezone(params[:timezone_offset])
|
||||
|
||||
group_traffic_data(timezone_data)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def group_traffic_data(data)
|
||||
# start with an empty array
|
||||
result_arr = []
|
||||
|
||||
# pick all the unique dates from the data in ascending order
|
||||
dates = data.pluck(:date).uniq.sort
|
||||
|
||||
# add the dates as the first row, leave an empty cell for the hour column
|
||||
# e.g. [nil, '2023-01-01', '2023-1-02', '2023-01-03']
|
||||
result_arr << ([nil] + dates)
|
||||
|
||||
# group the data by hour, we do not need to sort it, because the data is already sorted
|
||||
# given it starts from the beginning of the day
|
||||
# here each hour is a key, and the value is an array of all the items for that hour at each date
|
||||
# e.g. hour = 1
|
||||
# value = [{date: 2023-01-01, value: 1}, {date: 2023-01-02, value: 1}, {date: 2023-01-03, value: 1}, ...]
|
||||
data.group_by { |d| d[:hour] }.each do |hour, items|
|
||||
# create a new row for each hour
|
||||
row = [hour]
|
||||
|
||||
# group the items by date, so we can easily access the value for each date
|
||||
# grouped values will be a hasg with the date as the key, and the value as the value
|
||||
# e.g. { '2023-01-01' => [{date: 2023-01-01, value: 1}], '2023-01-02' => [{date: 2023-01-02, value: 1}], ... }
|
||||
grouped_values = items.group_by { |d| d[:date] }
|
||||
|
||||
# now for each unique date we have, we can access the value for that date and append it to the array
|
||||
dates.each do |date|
|
||||
row << (grouped_values[date][0][:value] if grouped_values[date].is_a?(Array))
|
||||
end
|
||||
|
||||
# row will look like [22, 0, 0, 1, 4, 6, 7, 4]
|
||||
# add the row to the result array
|
||||
|
||||
result_arr << row
|
||||
end
|
||||
|
||||
# return the resultant array
|
||||
# the result looks like this
|
||||
# [
|
||||
# [nil, '2023-01-01', '2023-1-02', '2023-01-03'],
|
||||
# [0, 0, 0, 0],
|
||||
# [1, 0, 0, 0],
|
||||
# [2, 0, 0, 0],
|
||||
# [3, 0, 0, 0],
|
||||
# [4, 0, 0, 0],
|
||||
# ]
|
||||
result_arr
|
||||
end
|
||||
|
||||
def generate_heatmap_data_for_timezone(offset)
|
||||
timezone = ActiveSupport::TimeZone[offset]&.name
|
||||
timezone_today = DateTime.now.in_time_zone(timezone).beginning_of_day
|
||||
|
||||
timezone_data_raw = generate_heatmap_data(timezone_today, offset)
|
||||
|
||||
transform_data(timezone_data_raw, false)
|
||||
end
|
||||
|
||||
def generate_heatmap_data(date, offset)
|
||||
report_params = {
|
||||
type: :account,
|
||||
group_by: 'hour',
|
||||
metric: 'conversations_count',
|
||||
business_hours: false
|
||||
}
|
||||
|
||||
V2::ReportBuilder.new(Current.account, report_params.merge({
|
||||
since: since_timestamp(date),
|
||||
until: until_timestamp(date),
|
||||
timezone_offset: offset
|
||||
})).build
|
||||
end
|
||||
|
||||
def transform_data(data, zone_transform)
|
||||
# rubocop:disable Rails/TimeZone
|
||||
data.map do |d|
|
||||
date = zone_transform ? Time.zone.at(d[:timestamp]) : Time.at(d[:timestamp])
|
||||
{
|
||||
date: date.to_date.to_s,
|
||||
hour: date.hour,
|
||||
value: d[:value]
|
||||
}
|
||||
end
|
||||
# rubocop:enable Rails/TimeZone
|
||||
end
|
||||
|
||||
def since_timestamp(date)
|
||||
(date - 6.days).to_i.to_s
|
||||
end
|
||||
|
||||
def until_timestamp(date)
|
||||
date.to_i.to_s
|
||||
end
|
||||
end
|
||||
@@ -27,27 +27,6 @@ module Api::V2::Accounts::ReportsHelper
|
||||
end
|
||||
end
|
||||
|
||||
def generate_conversations_heatmap_report
|
||||
report_params = {
|
||||
type: :account,
|
||||
group_by: 'hour',
|
||||
since: params[:since],
|
||||
until: params[:until],
|
||||
metric: 'conversations_count',
|
||||
business_hours: false
|
||||
}
|
||||
data = V2::ReportBuilder.new(Current.account, report_params).build
|
||||
|
||||
# data format is { timestamp: 1231242342, value: 3}
|
||||
# we need to convert it to { date: "2020-01-01", hour: 12, value: 3}
|
||||
#
|
||||
# the generated report is **always** in UTC timezone
|
||||
data.map do |d|
|
||||
date = Time.zone.at(d[:timestamp]).to_s
|
||||
[date, d[:value]]
|
||||
end
|
||||
end
|
||||
|
||||
def generate_report(report_params)
|
||||
V2::ReportBuilder.new(
|
||||
Current.account,
|
||||
|
||||
@@ -59,9 +59,9 @@ class ReportsAPI extends ApiClient {
|
||||
});
|
||||
}
|
||||
|
||||
getConversationTrafficCSV({ from: since, to: until }) {
|
||||
getConversationTrafficCSV() {
|
||||
return axios.get(`${this.url}/conversation_traffic`, {
|
||||
params: { since, until },
|
||||
params: { timezone_offset: getTimeOffset() },
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -142,10 +142,8 @@ export default {
|
||||
},
|
||||
downloadHeatmapData() {
|
||||
let to = endOfDay(new Date());
|
||||
let from = startOfDay(subDays(to, 6));
|
||||
|
||||
this.$store.dispatch('downloadAccountConversationHeatmap', {
|
||||
from: getUnixTime(from),
|
||||
to: getUnixTime(to),
|
||||
});
|
||||
},
|
||||
|
||||
@@ -180,7 +180,7 @@ export const actions = {
|
||||
});
|
||||
},
|
||||
downloadAccountConversationHeatmap(_, reportObj) {
|
||||
Report.getConversationTrafficCSV(reportObj)
|
||||
Report.getConversationTrafficCSV()
|
||||
.then(response => {
|
||||
downloadCsvFile(
|
||||
generateFileName({
|
||||
|
||||
@@ -1,12 +1,4 @@
|
||||
<% headers = [
|
||||
I18n.t('reports.conversation_traffic_csv.date'),
|
||||
I18n.t('reports.conversation_traffic_csv.conversations_count'),
|
||||
]
|
||||
%>
|
||||
<%= CSVSafe.generate_line headers -%>
|
||||
<%= CSVSafe.generate_line [I18n.t('reports.conversation_traffic_csv.timezone'), @timezone] %>
|
||||
<% @report_data.each do |row| %>
|
||||
<%= CSVSafe.generate_line row -%>
|
||||
<% end %>
|
||||
|
||||
<%= CSVSafe.generate_line [I18n.t('reports.period', since: Date.strptime(params[:since], '%s'), until: Date.strptime(params[:until], '%s'))] %>
|
||||
<%= CSVSafe.generate_line [I18n.t('reports.utc_warning')] %>
|
||||
|
||||
@@ -96,8 +96,7 @@ en:
|
||||
avg_first_response_time: Avg first response time (Minutes)
|
||||
avg_resolution_time: Avg resolution time (Minutes)
|
||||
conversation_traffic_csv:
|
||||
date: Date and time
|
||||
conversations_count: No. of conversations
|
||||
timezone: Timezone
|
||||
default_group_by: day
|
||||
csat:
|
||||
headers:
|
||||
|
||||
Reference in New Issue
Block a user