## PR2: Report builder refactor — DataSource abstraction
The existing report builders (timeseries + summary) had their SQL
queries inlined — each builder constructed its own scopes, groupings,
and aggregations directly. This made it hard to swap the underlying data
source without duplicating builder logic.
This PR extracts all raw-event querying into a `Reports::RawDataSource`
behind a `Reports::DataSource` factory. Builders now call
`data_source.timeseries`, `.aggregate`, or `.summary` instead of
constructing queries themselves. Behavior is identical —
`DataSource.for(...)` returns `RawDataSource` in all cases today.
The timeseries path had two separate builders (`CountReportBuilder`,
`AverageReportBuilder`) that were selected via a metric-name case
statement in `Conversations::BaseReportBuilder`. These are replaced by a
single `ReportBuilder` that delegates to the data source. The metric
type (count vs average) is now decided inside the data source, not the
builder.
Summary builders similarly moved their inline SQL into
`RawDataSource#summary`, which returns a unified hash keyed by dimension
ID.
the rollup read path.
## Flow
### Before
```
ReportsController ──▶ case metric ──▶ AverageReportBuilder ──▶ inline SQL ──▶ DB
└──▶ CountReportBuilder ──▶ inline SQL ──▶ DB
SummaryController ──▶ AgentSummaryBuilder ──▶ inline SQL ──▶ DB
└──▶ InboxSummaryBuilder ──▶ inline SQL ──▶ DB
└──▶ TeamSummaryBuilder ──▶ inline SQL ──▶ DB
```
### After
```
ReportsController ──▶ ReportBuilder ──┐
├──▶ DataSource.for ──▶ RawDataSource ──▶ DB
SummaryController ──▶ SummaryBuilder ──┘
```
### Expected (after rollup read path)
```
ReportsController ──▶ ReportBuilder ──┐
├──▶ DataSource.for ──▶ RawDataSource ──▶ reporting_events
SummaryController ──▶ SummaryBuilder ──┘ └──▶ RollupDataSource ──▶ reporting_events_rollups
```
### What changed
- `Reports::DataSource` factory + `Reports::RawDataSource`
- `TimezoneHelper#timezone_name_from_params` — prefers IANA name, falls
back to offset
- Unified `Timeseries::ReportBuilder` replaces `CountReportBuilder` +
`AverageReportBuilder`
- Summary builders delegate to `DataSource` instead of querying directly
### How to test
This is a pure refactor — all existing report pages (Overview, Agent,
Inbox, Label, Team) should produce identical numbers. No feature flag or
new config needed.
---------
Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
Co-authored-by: Tanmay Deep Sharma <tanmaydeepsharma21@gmail.com>
Co-authored-by: Tanmay Deep Sharma <32020192+tds-1@users.noreply.github.com>
59 lines
1.6 KiB
Ruby
59 lines
1.6 KiB
Ruby
class Api::V2::Accounts::SummaryReportsController < Api::V1::Accounts::BaseController
|
|
before_action :check_authorization
|
|
before_action :prepare_builder_params, only: [:agent, :team, :inbox, :label, :channel]
|
|
|
|
def agent
|
|
render_report_with(V2::Reports::AgentSummaryBuilder, type: :agent)
|
|
end
|
|
|
|
def team
|
|
render_report_with(V2::Reports::TeamSummaryBuilder, type: :team)
|
|
end
|
|
|
|
def inbox
|
|
render_report_with(V2::Reports::InboxSummaryBuilder, type: :inbox)
|
|
end
|
|
|
|
def label
|
|
render_report_with(V2::Reports::LabelSummaryBuilder)
|
|
end
|
|
|
|
def channel
|
|
return render_could_not_create_error(I18n.t('errors.reports.date_range_too_long')) if date_range_too_long?
|
|
|
|
render_report_with(V2::Reports::ChannelSummaryBuilder)
|
|
end
|
|
|
|
private
|
|
|
|
def check_authorization
|
|
authorize :report, :view?
|
|
end
|
|
|
|
def prepare_builder_params
|
|
@builder_params = {
|
|
since: permitted_params[:since],
|
|
until: permitted_params[:until],
|
|
business_hours: ActiveModel::Type::Boolean.new.cast(permitted_params[:business_hours])
|
|
}
|
|
end
|
|
|
|
def render_report_with(builder_class, type: nil)
|
|
builder_params = type.present? ? @builder_params.merge(type: type) : @builder_params
|
|
builder = builder_class.new(account: Current.account, params: builder_params)
|
|
render json: builder.build
|
|
end
|
|
|
|
def permitted_params
|
|
params.permit(:since, :until, :business_hours)
|
|
end
|
|
|
|
def date_range_too_long?
|
|
return false if permitted_params[:since].blank? || permitted_params[:until].blank?
|
|
|
|
since_time = Time.zone.at(permitted_params[:since].to_i)
|
|
until_time = Time.zone.at(permitted_params[:until].to_i)
|
|
(until_time - since_time) > 6.months
|
|
end
|
|
end
|