From 46ab6c3946108d58f82e7a39b1b6e3ecb762dff8 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Wed, 11 Feb 2026 21:28:17 +0530 Subject: [PATCH] feat: Add rollup read path to report builders --- .../v2/reports/base_summary_builder.rb | 41 +++++++- .../timeseries/average_report_builder.rb | 93 ++++++++++++++++++- .../timeseries/base_timeseries_builder.rb | 1 + .../timeseries/count_report_builder.rb | 88 +++++++++++++++++- 4 files changed, 218 insertions(+), 5 deletions(-) diff --git a/app/builders/v2/reports/base_summary_builder.rb b/app/builders/v2/reports/base_summary_builder.rb index d4a9e7c0b..e7d8eaf54 100644 --- a/app/builders/v2/reports/base_summary_builder.rb +++ b/app/builders/v2/reports/base_summary_builder.rb @@ -1,5 +1,6 @@ class V2::Reports::BaseSummaryBuilder include DateRangeHelper + include V2::Reports::Concerns::RollupConditions def build load_data @@ -10,7 +11,7 @@ class V2::Reports::BaseSummaryBuilder def load_data @conversations_count = fetch_conversations_count - load_reporting_events_data + use_rollup? ? load_rollup_data : load_reporting_events_data end def load_reporting_events_data @@ -34,6 +35,44 @@ class V2::Reports::BaseSummaryBuilder @avg_reply_time = results.transform_values(&:avg_reply_time) end + def load_rollup_data + group_by_key.to_s.split('.').last + dimension_type = dimension_type_to_rollup + + # Fetch rollup data grouped by dimension_id + rollup_rows = ReportingEventRollup.where( + account_id: account.id, + dimension_type: dimension_type, + date: range.first..range.last + ).group(:dimension_id).select( + 'dimension_id', + 'SUM(CASE WHEN metric = \'resolutions_count\' THEN count ELSE 0 END) as resolved_count', + 'SUM(CASE WHEN metric = \'resolution_time\' THEN count ELSE 0 END) as resolution_count', + 'SUM(CASE WHEN metric = \'resolution_time\' THEN sum_value ELSE 0 END) as resolution_sum_value', + 'SUM(CASE WHEN metric = \'first_response\' THEN count ELSE 0 END) as first_response_count', + 'SUM(CASE WHEN metric = \'first_response\' THEN sum_value ELSE 0 END) as first_response_sum_value', + 'SUM(CASE WHEN metric = \'reply_time\' THEN count ELSE 0 END) as reply_count', + 'SUM(CASE WHEN metric = \'reply_time\' THEN sum_value ELSE 0 END) as reply_sum_value' + ) + + # Convert to the expected format + results = {} + rollup_rows.each do |row| + results[row.dimension_id] = row + end + + @resolved_count = results.transform_values { |r| r.resolved_count.to_i } + @avg_resolution_time = results.transform_values do |r| + r.resolution_count.to_i.zero? ? nil : (r.resolution_sum_value.to_f / r.resolution_count.to_i) + end + @avg_first_response_time = results.transform_values do |r| + r.first_response_count.to_i.zero? ? nil : (r.first_response_sum_value.to_f / r.first_response_count.to_i) + end + @avg_reply_time = results.transform_values do |r| + r.reply_count.to_i.zero? ? nil : (r.reply_sum_value.to_f / r.reply_count.to_i) + end + end + def reporting_events @reporting_events ||= account.reporting_events.where(created_at: range) end diff --git a/app/builders/v2/reports/timeseries/average_report_builder.rb b/app/builders/v2/reports/timeseries/average_report_builder.rb index 5df718b6a..97ca007b8 100644 --- a/app/builders/v2/reports/timeseries/average_report_builder.rb +++ b/app/builders/v2/reports/timeseries/average_report_builder.rb @@ -1,5 +1,15 @@ class V2::Reports::Timeseries::AverageReportBuilder < V2::Reports::Timeseries::BaseTimeseriesBuilder def timeseries + use_rollup? ? rollup_timeseries : raw_timeseries + end + + def aggregate_value + use_rollup? ? rollup_aggregate_value : raw_aggregate_value + end + + private + + def raw_timeseries grouped_average_time = reporting_events.average(average_value_key) grouped_event_count = reporting_events.count grouped_average_time.each_with_object([]) do |element, arr| @@ -12,11 +22,90 @@ class V2::Reports::Timeseries::AverageReportBuilder < V2::Reports::Timeseries::B end end - def aggregate_value + def rollup_timeseries + dimension_type = dimension_type_to_rollup + dimension_id = dimension_id_for_rollup + metric = metric_to_rollup_metric(params[:metric]) + + rollup_rows = ReportingEventRollup.where( + account_id: account.id, + metric: metric, + dimension_type: dimension_type, + dimension_id: dimension_id, + date: range.first..range.last + ) + + group_and_aggregate_rollup(rollup_rows) + end + + def raw_aggregate_value object_scope.average(average_value_key) end - private + def rollup_aggregate_value + dimension_type = dimension_type_to_rollup + dimension_id = dimension_id_for_rollup + metric = metric_to_rollup_metric(params[:metric]) + + result = ReportingEventRollup.where( + account_id: account.id, + metric: metric, + dimension_type: dimension_type, + dimension_id: dimension_id, + date: range.first..range.last + ).pluck(Arel.sql('SUM(count), SUM(sum_value)')) + .first + + return nil if result.blank? || result[0].to_i.zero? + + result[1].to_f / result[0].to_i + end + + def dimension_id_for_rollup + case params[:type].to_s + when 'account' + account.id + when 'agent' + params[:id].to_i + when 'inbox' + params[:id].to_i + when 'team' + params[:id].to_i + end + end + + def group_and_aggregate_rollup(rollup_rows) + grouped_data = {} + + rollup_rows.each do |row| + date_key = case group_by + when 'day' + row.date + when 'week' + row.date.beginning_of_week(:monday) + when 'month' + row.date.beginning_of_month + when 'year' + row.date.beginning_of_year + else + row.date + end + + grouped_data[date_key] ||= { count: 0, sum_value: 0.0 } + grouped_data[date_key][:count] += row.count + grouped_data[date_key][:sum_value] += row.sum_value + end + + grouped_data.each_with_object([]) do |(date_key, data), arr| + next if data[:count].zero? + + arr << { + value: data[:sum_value] / data[:count], + timestamp: date_key.in_time_zone(timezone).to_i, + count: data[:count] + } + end.sort_by { |h| h[:timestamp] } + end def event_name metric_to_event_name = { diff --git a/app/builders/v2/reports/timeseries/base_timeseries_builder.rb b/app/builders/v2/reports/timeseries/base_timeseries_builder.rb index 50699417d..d562b396c 100644 --- a/app/builders/v2/reports/timeseries/base_timeseries_builder.rb +++ b/app/builders/v2/reports/timeseries/base_timeseries_builder.rb @@ -1,6 +1,7 @@ class V2::Reports::Timeseries::BaseTimeseriesBuilder include TimezoneHelper include DateRangeHelper + include V2::Reports::Concerns::RollupConditions DEFAULT_GROUP_BY = 'day'.freeze pattr_initialize :account, :params diff --git a/app/builders/v2/reports/timeseries/count_report_builder.rb b/app/builders/v2/reports/timeseries/count_report_builder.rb index bb3b1250c..bdd56eeea 100644 --- a/app/builders/v2/reports/timeseries/count_report_builder.rb +++ b/app/builders/v2/reports/timeseries/count_report_builder.rb @@ -1,5 +1,15 @@ class V2::Reports::Timeseries::CountReportBuilder < V2::Reports::Timeseries::BaseTimeseriesBuilder def timeseries + use_rollup? ? rollup_timeseries : raw_timeseries + end + + def aggregate_value + use_rollup? ? rollup_aggregate_value : raw_aggregate_value + end + + private + + def raw_timeseries grouped_count.each_with_object([]) do |element, arr| event_date, event_count = element @@ -11,11 +21,85 @@ class V2::Reports::Timeseries::CountReportBuilder < V2::Reports::Timeseries::Bas end end - def aggregate_value + def rollup_timeseries + metric = metric_to_rollup_metric(params[:metric]) + return [] if metric.blank? + + dimension_type = dimension_type_to_rollup + dimension_id = dimension_id_for_rollup + + rollup_rows = ReportingEventRollup.where( + account_id: account.id, + metric: metric, + dimension_type: dimension_type, + dimension_id: dimension_id, + date: range.first..range.last + ) + + group_and_aggregate_rollup_counts(rollup_rows) + end + + def raw_aggregate_value object_scope.count end - private + def rollup_aggregate_value + metric = metric_to_rollup_metric(params[:metric]) + return 0 if metric.blank? + + dimension_type = dimension_type_to_rollup + dimension_id = dimension_id_for_rollup + + ReportingEventRollup.where( + account_id: account.id, + metric: metric, + dimension_type: dimension_type, + dimension_id: dimension_id, + date: range.first..range.last + ).sum(:count).to_i + end + + def dimension_id_for_rollup + case params[:type].to_s + when 'account' + account.id + when 'agent' + params[:id].to_i + when 'inbox' + params[:id].to_i + when 'team' + params[:id].to_i + end + end + + def group_and_aggregate_rollup_counts(rollup_rows) + grouped_data = {} + + rollup_rows.each do |row| + date_key = case group_by + when 'day' + row.date + when 'week' + row.date.beginning_of_week(:monday) + when 'month' + row.date.beginning_of_month + when 'year' + row.date.beginning_of_year + else + row.date + end + + grouped_data[date_key] ||= 0 + grouped_data[date_key] += row.count + end + + grouped_data.each_with_object([]) do |(date_key, count), arr| + arr << { + value: count, + timestamp: date_key.in_time_zone(timezone).to_i + } + end.sort_by { |h| h[:timestamp] } + end def metric @metric ||= params[:metric]