mirror of
https://github.com/lingble/chatwoot.git
synced 2025-11-01 03:27:52 +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
|
class Api::V2::Accounts::ReportsController < Api::V1::Accounts::BaseController
|
||||||
include Api::V2::Accounts::ReportsHelper
|
include Api::V2::Accounts::ReportsHelper
|
||||||
|
include Api::V2::Accounts::HeatmapHelper
|
||||||
|
|
||||||
before_action :check_authorization
|
before_action :check_authorization
|
||||||
|
|
||||||
def index
|
def index
|
||||||
@@ -34,6 +36,9 @@ class Api::V2::Accounts::ReportsController < Api::V1::Accounts::BaseController
|
|||||||
|
|
||||||
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 = ActiveSupport::TimeZone[timezone_offset]
|
||||||
|
|
||||||
generate_csv('conversation_traffic_reports', 'api/v2/accounts/reports/conversation_traffic')
|
generate_csv('conversation_traffic_reports', 'api/v2/accounts/reports/conversation_traffic')
|
||||||
end
|
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
|
||||||
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)
|
def generate_report(report_params)
|
||||||
V2::ReportBuilder.new(
|
V2::ReportBuilder.new(
|
||||||
Current.account,
|
Current.account,
|
||||||
|
|||||||
@@ -59,9 +59,9 @@ class ReportsAPI extends ApiClient {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getConversationTrafficCSV({ from: since, to: until }) {
|
getConversationTrafficCSV() {
|
||||||
return axios.get(`${this.url}/conversation_traffic`, {
|
return axios.get(`${this.url}/conversation_traffic`, {
|
||||||
params: { since, until },
|
params: { timezone_offset: getTimeOffset() },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -142,10 +142,8 @@ export default {
|
|||||||
},
|
},
|
||||||
downloadHeatmapData() {
|
downloadHeatmapData() {
|
||||||
let to = endOfDay(new Date());
|
let to = endOfDay(new Date());
|
||||||
let from = startOfDay(subDays(to, 6));
|
|
||||||
|
|
||||||
this.$store.dispatch('downloadAccountConversationHeatmap', {
|
this.$store.dispatch('downloadAccountConversationHeatmap', {
|
||||||
from: getUnixTime(from),
|
|
||||||
to: getUnixTime(to),
|
to: getUnixTime(to),
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -180,7 +180,7 @@ export const actions = {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
downloadAccountConversationHeatmap(_, reportObj) {
|
downloadAccountConversationHeatmap(_, reportObj) {
|
||||||
Report.getConversationTrafficCSV(reportObj)
|
Report.getConversationTrafficCSV()
|
||||||
.then(response => {
|
.then(response => {
|
||||||
downloadCsvFile(
|
downloadCsvFile(
|
||||||
generateFileName({
|
generateFileName({
|
||||||
|
|||||||
@@ -1,12 +1,4 @@
|
|||||||
<% headers = [
|
<%= CSVSafe.generate_line [I18n.t('reports.conversation_traffic_csv.timezone'), @timezone] %>
|
||||||
I18n.t('reports.conversation_traffic_csv.date'),
|
|
||||||
I18n.t('reports.conversation_traffic_csv.conversations_count'),
|
|
||||||
]
|
|
||||||
%>
|
|
||||||
<%= CSVSafe.generate_line headers -%>
|
|
||||||
<% @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'))] %>
|
|
||||||
<%= CSVSafe.generate_line [I18n.t('reports.utc_warning')] %>
|
|
||||||
|
|||||||
@@ -96,8 +96,7 @@ en:
|
|||||||
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)
|
||||||
conversation_traffic_csv:
|
conversation_traffic_csv:
|
||||||
date: Date and time
|
timezone: Timezone
|
||||||
conversations_count: No. of conversations
|
|
||||||
default_group_by: day
|
default_group_by: day
|
||||||
csat:
|
csat:
|
||||||
headers:
|
headers:
|
||||||
|
|||||||
Reference in New Issue
Block a user