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 | ||||
|   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
	 Shivam Mishra
					Shivam Mishra