From 17ff1f11a7aa966b2819d15e2b68dc98f146ab6e Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Tue, 11 Apr 2023 09:40:54 +0530 Subject: [PATCH] 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 --- .../api/v2/accounts/reports_controller.rb | 5 + app/helpers/api/v2/accounts/heatmap_helper.rb | 103 ++++++++++++++++++ app/helpers/api/v2/accounts/reports_helper.rb | 21 ---- app/javascript/dashboard/api/reports.js | 4 +- .../settings/reports/LiveReports.vue | 2 - .../dashboard/store/modules/reports.js | 2 +- .../accounts/reports/conversation_traffic.erb | 12 +- config/locales/en.yml | 3 +- 8 files changed, 114 insertions(+), 38 deletions(-) create mode 100644 app/helpers/api/v2/accounts/heatmap_helper.rb diff --git a/app/controllers/api/v2/accounts/reports_controller.rb b/app/controllers/api/v2/accounts/reports_controller.rb index a9fc5d4b4..8e95ea594 100644 --- a/app/controllers/api/v2/accounts/reports_controller.rb +++ b/app/controllers/api/v2/accounts/reports_controller.rb @@ -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 diff --git a/app/helpers/api/v2/accounts/heatmap_helper.rb b/app/helpers/api/v2/accounts/heatmap_helper.rb new file mode 100644 index 000000000..597135f47 --- /dev/null +++ b/app/helpers/api/v2/accounts/heatmap_helper.rb @@ -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 diff --git a/app/helpers/api/v2/accounts/reports_helper.rb b/app/helpers/api/v2/accounts/reports_helper.rb index b15683afd..0604eba2f 100644 --- a/app/helpers/api/v2/accounts/reports_helper.rb +++ b/app/helpers/api/v2/accounts/reports_helper.rb @@ -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, diff --git a/app/javascript/dashboard/api/reports.js b/app/javascript/dashboard/api/reports.js index 9d8d49b96..89b8554a5 100644 --- a/app/javascript/dashboard/api/reports.js +++ b/app/javascript/dashboard/api/reports.js @@ -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() }, }); } diff --git a/app/javascript/dashboard/routes/dashboard/settings/reports/LiveReports.vue b/app/javascript/dashboard/routes/dashboard/settings/reports/LiveReports.vue index 7acfc6251..a0cdf02cc 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/reports/LiveReports.vue +++ b/app/javascript/dashboard/routes/dashboard/settings/reports/LiveReports.vue @@ -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), }); }, diff --git a/app/javascript/dashboard/store/modules/reports.js b/app/javascript/dashboard/store/modules/reports.js index b8616af3e..0aa24ccd3 100644 --- a/app/javascript/dashboard/store/modules/reports.js +++ b/app/javascript/dashboard/store/modules/reports.js @@ -180,7 +180,7 @@ export const actions = { }); }, downloadAccountConversationHeatmap(_, reportObj) { - Report.getConversationTrafficCSV(reportObj) + Report.getConversationTrafficCSV() .then(response => { downloadCsvFile( generateFileName({ diff --git a/app/views/api/v2/accounts/reports/conversation_traffic.erb b/app/views/api/v2/accounts/reports/conversation_traffic.erb index fafe8ebeb..a25dd1308 100644 --- a/app/views/api/v2/accounts/reports/conversation_traffic.erb +++ b/app/views/api/v2/accounts/reports/conversation_traffic.erb @@ -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')] %> +<% end %> \ No newline at end of file diff --git a/config/locales/en.yml b/config/locales/en.yml index 9cc48be18..72439195e 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -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: