Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Group results #42

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 21 additions & 2 deletions lib/active_reporting/report.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class Report
extend Forwardable
def_delegators :@metric, :fact_model, :model

def initialize(metric, dimension_identifiers: true, dimension_filter: {}, dimensions: [], metric_filter: {})
def initialize(metric, dimension_identifiers: true, dimension_filter: {}, dimensions: [], metric_filter: {}, data_format: :standard)
@metric = metric.is_a?(Metric) ? metric : ActiveReporting.fetch_metric(metric)
raise UnknownMetric, "Unknown metric #{metric}" if @metric.nil?

Expand All @@ -24,23 +24,42 @@ def initialize(metric, dimension_identifiers: true, dimension_filter: {}, dimens
@metric_filter = @metric.metric_filter.merge(metric_filter)
@ordering = @metric.order_by_dimension
partition_dimension_filters dimension_filter
@data_format = data_format
end

# Builds and executes a query, returning the raw result
#
# @return [Array]
# @return [Array, Hash]
def run
@run ||= build_data

# Pass format as a block
block_given? ? yield(@metric, @dimensions, @run) : @run
end

private ######################################################################

def build_data
@data = model.connection.exec_query(statement.to_sql).to_a
apply_dimension_callbacks
format_data unless @data_format == :standard
@data
end

def format_data
case @data_format
when :grouped
if @dimensions.any?
dimension_label_names = @dimensions.map { |d| d.label_name.to_s }
@data = Hash[@data.map { |r| [ r.fetch_values(*dimension_label_names), r.fetch(@metric.name.to_s)] }]
else
@data = Hash[@data.map { |r| [ r.keys, r.fetch(@metric.name.to_s)] }]
end
else
raise UnknownDataFormat
end
end

def partition_dimension_filters(user_dimension_filter)
@dimension_filters = { ransack: {}, scope: {}, lambda: {} }
user_dimension_filter.merge(@metric.dimension_filter).each do |key, value|
Expand Down
2 changes: 1 addition & 1 deletion lib/active_reporting/reporting_dimension.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class ReportingDimension
DATETIME_HIERARCHIES = %i[microseconds milliseconds second minute hour day week month quarter year decade
century millennium date].freeze
JOIN_METHODS = { joins: :joins, left_outer_joins: :left_outer_joins }.freeze
attr_reader :join_method, :label
attr_reader :join_method, :label, :label_name

def_delegators :@dimension, :name, :type, :klass, :association, :model, :hierarchical?

Expand Down
48 changes: 48 additions & 0 deletions test/active_reporting/report_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,52 @@ def test_accept_dimension_join_method_option
report = ActiveReporting::Report.new(metric)
assert report.send(:statement).to_sql.include?("LEFT OUTER JOIN")
end

### test_data_format_grouped
# The standard data format returns an array of hashes (records).
# Specifying 'data_format: grouped' should return metric values grouped by metric dimensions e.g.
# ----- Data Format: Standard -----
# [{"a_metric"=>26, "platform"=>"3DS"}, {"a_metric"=>1, "platform"=>"Switch"}, {"a_metric"=>20, "platform"=>"Wii U"}]
# ----- Data Format: Grouped -----
# {["3DS"]=>26, ["Switch"]=>1, ["Wii U"]=>20}
def test_data_format_grouped_single_dimension
metric = ActiveReporting::Metric.new(:a_metric, fact_model: GameFactModel, dimensions: [:platform], aggregate: :count)
standard_report = ActiveReporting::Report.new(metric, dimension_identifiers: false)
standard_data = standard_report.run
assert standard_data.is_a?(Array)

grouped_report = ActiveReporting::Report.new(metric, dimension_identifiers: false, data_format: :grouped)
grouped_data = grouped_report.run
assert grouped_data.is_a?(Hash)

# Group the standard result so we can check we have the right result
metric_name = metric.instance_variable_get(:@name)
dimension_names = standard_report.instance_variable_get(:@dimensions).map { |d| d.instance_variable_get(:@label_name).to_s }
standard_grouped = Hash[standard_data.map { |r| [ r.fetch_values(*dimension_names), r.fetch(metric_name.to_s)] }]

assert_equal standard_grouped, grouped_data
end

def test_run_with_a_block
metric = ActiveReporting::Metric.new(:a_metric, fact_model: GameFactModel, dimensions: [:platform], aggregate: :count)
report = ActiveReporting::Report.new(metric, dimension_identifiers: false)
standard_data = report.run
assert standard_data.is_a?(Array)

grouped_data = report.run do |metric, dimensions, data|
if dimensions.any?
dimension_label_names = dimensions.map { |d| d.label_name.to_s }
Hash[data.map { |r| [ r.fetch_values(*dimension_label_names), r.fetch(metric.name.to_s)] }]
else
Hash[data.map { |r| [ r.keys, r.fetch(metric.name.to_s)] }]
end
end
assert grouped_data.is_a?(Hash)

# Group the standard result so we can check we have the right result
metric_name = metric.instance_variable_get(:@name)
dimension_names = report.instance_variable_get(:@dimensions).map { |d| d.instance_variable_get(:@label_name).to_s }
standard_grouped = Hash[standard_data.map { |r| [ r.fetch_values(*dimension_names), r.fetch(metric_name.to_s)] }]
assert_equal standard_grouped, grouped_data
end
end