mirror of
				https://github.com/lingble/chatwoot.git
				synced 2025-10-31 02:57:57 +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
	 Shivam Mishra
					Shivam Mishra