reports_kit 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +6 -0
- data/.rubocop.yml +83 -0
- data/Gemfile +3 -0
- data/MIT-LICENSE +20 -0
- data/README.md +468 -0
- data/Rakefile +2 -0
- data/app/assets/javascripts/reports_kit/application.js +14 -0
- data/app/assets/javascripts/reports_kit/lib/_init.js +8 -0
- data/app/assets/javascripts/reports_kit/lib/chart.js +39 -0
- data/app/assets/javascripts/reports_kit/lib/report.js +119 -0
- data/app/assets/javascripts/reports_kit/vendor/chart.js +12269 -0
- data/app/assets/javascripts/reports_kit/vendor/daterangepicker.js +1627 -0
- data/app/assets/javascripts/reports_kit/vendor/moment.js +4040 -0
- data/app/assets/javascripts/reports_kit/vendor/select2.full.js +6436 -0
- data/app/assets/stylesheets/reports_kit/application.css.scss +3 -0
- data/app/assets/stylesheets/reports_kit/reports.css.sass +7 -0
- data/app/assets/stylesheets/reports_kit/select2_overrides.css.sass +7 -0
- data/app/assets/stylesheets/reports_kit/vendor/daterangepicker.css +269 -0
- data/app/assets/stylesheets/reports_kit/vendor/select2-bootstrap.css +721 -0
- data/app/assets/stylesheets/reports_kit/vendor/select2.css +484 -0
- data/config/routes.rb +10 -0
- data/docs/images/chart_options.png +0 -0
- data/docs/images/dashed_line.png +0 -0
- data/docs/images/flights_by_carrier.png +0 -0
- data/docs/images/flights_by_carrier_and_flight_at.png +0 -0
- data/docs/images/flights_by_delay.png +0 -0
- data/docs/images/flights_by_flight_at.png +0 -0
- data/docs/images/flights_by_hours_delayed.png +0 -0
- data/docs/images/flights_with_check_box.png +0 -0
- data/docs/images/flights_with_configured_boolean.png +0 -0
- data/docs/images/flights_with_configured_datetime.png +0 -0
- data/docs/images/flights_with_configured_number.png +0 -0
- data/docs/images/flights_with_configured_string.png +0 -0
- data/docs/images/flights_with_date_range.png +0 -0
- data/docs/images/flights_with_filters.png +0 -0
- data/docs/images/flights_with_multi_autocomplete.png +0 -0
- data/docs/images/flights_with_string_filter.png +0 -0
- data/docs/images/horizontal_bar.png +0 -0
- data/docs/images/legend_right.png +0 -0
- data/docs/images/users_by_created_at.png +0 -0
- data/gists/doc.txt +58 -0
- data/lib/reports_kit.rb +17 -0
- data/lib/reports_kit/base_controller.rb +11 -0
- data/lib/reports_kit/configuration.rb +10 -0
- data/lib/reports_kit/engine.rb +21 -0
- data/lib/reports_kit/helper.rb +19 -0
- data/lib/reports_kit/model.rb +16 -0
- data/lib/reports_kit/model_configuration.rb +23 -0
- data/lib/reports_kit/rails.rb +5 -0
- data/lib/reports_kit/report_builder.rb +76 -0
- data/lib/reports_kit/reports/data/chart_options.rb +132 -0
- data/lib/reports_kit/reports/data/generate.rb +65 -0
- data/lib/reports_kit/reports/data/one_dimension.rb +71 -0
- data/lib/reports_kit/reports/data/two_dimensions.rb +129 -0
- data/lib/reports_kit/reports/data/utils.rb +79 -0
- data/lib/reports_kit/reports/dimension.rb +78 -0
- data/lib/reports_kit/reports/filter.rb +84 -0
- data/lib/reports_kit/reports/filter_types/base.rb +47 -0
- data/lib/reports_kit/reports/filter_types/boolean.rb +30 -0
- data/lib/reports_kit/reports/filter_types/datetime.rb +27 -0
- data/lib/reports_kit/reports/filter_types/number.rb +28 -0
- data/lib/reports_kit/reports/filter_types/records.rb +26 -0
- data/lib/reports_kit/reports/filter_types/string.rb +38 -0
- data/lib/reports_kit/reports/generate_autocomplete_results.rb +55 -0
- data/lib/reports_kit/reports/inferrable_configuration.rb +113 -0
- data/lib/reports_kit/reports/measure.rb +58 -0
- data/lib/reports_kit/reports_controller.rb +15 -0
- data/lib/reports_kit/resources_controller.rb +8 -0
- data/lib/reports_kit/version.rb +3 -0
- data/reports_kit.gemspec +23 -0
- data/spec/factories/issue_factory.rb +4 -0
- data/spec/factories/issues_label_factory.rb +4 -0
- data/spec/factories/label_factory.rb +4 -0
- data/spec/factories/repo_factory.rb +5 -0
- data/spec/factories/tag_factory.rb +4 -0
- data/spec/fixtures/generate_inputs.yml +35 -0
- data/spec/fixtures/generate_outputs.yml +208 -0
- data/spec/reports_kit/reports/data/generate_spec.rb +275 -0
- data/spec/reports_kit/reports/dimension_spec.rb +38 -0
- data/spec/reports_kit/reports/filter_spec.rb +38 -0
- data/spec/spec_helper.rb +58 -0
- data/spec/support/factory_girl.rb +5 -0
- data/spec/support/helpers.rb +13 -0
- data/spec/support/models/issue.rb +6 -0
- data/spec/support/models/issues_label.rb +4 -0
- data/spec/support/models/label.rb +5 -0
- data/spec/support/models/repo.rb +7 -0
- data/spec/support/models/tag.rb +4 -0
- data/spec/support/schema.rb +38 -0
- metadata +232 -0
@@ -0,0 +1,65 @@
|
|
1
|
+
module ReportsKit
|
2
|
+
module Reports
|
3
|
+
module Data
|
4
|
+
class Generate
|
5
|
+
ROUND_PRECISION = 3
|
6
|
+
|
7
|
+
attr_accessor :properties, :context_record
|
8
|
+
|
9
|
+
def initialize(properties, context_record: nil)
|
10
|
+
self.properties = properties.deep_symbolize_keys
|
11
|
+
self.context_record = context_record
|
12
|
+
end
|
13
|
+
|
14
|
+
def perform
|
15
|
+
if second_dimension
|
16
|
+
data = Data::TwoDimensions.new(measure, dimension, second_dimension).perform
|
17
|
+
else
|
18
|
+
data = Data::OneDimension.new(measure, dimension).perform
|
19
|
+
end
|
20
|
+
|
21
|
+
ChartOptions.new(data, options: properties[:chart], inferred_options: inferred_options).perform
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def measure
|
27
|
+
@measure ||= begin
|
28
|
+
measure_hash = properties[:measure]
|
29
|
+
raise ArgumentError.new('The number of measures must be exactly one') if measure_hash.blank?
|
30
|
+
Measure.new(measure_hash, context_record: context_record)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def dimension
|
35
|
+
@dimension ||= begin
|
36
|
+
Dimension.new(dimension_hashes[0], measure: measure)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def second_dimension
|
41
|
+
@second_dimension ||= begin
|
42
|
+
Dimension.new(dimension_hashes[1], measure: measure) if dimension_hashes[1]
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def dimension_hashes
|
47
|
+
@dimension_hashes ||= begin
|
48
|
+
dimension_hashes = properties[:dimensions]
|
49
|
+
raise ArgumentError.new('Blank dimensions') if dimension_hashes.blank?
|
50
|
+
raise ArgumentError.new('The number of dimensions must be 1-2') unless dimension_hashes.length.in?([1, 2])
|
51
|
+
dimension_hashes = dimension_hashes.values if dimension_hashes.is_a?(Hash) && dimension_hashes.key?(:'0')
|
52
|
+
dimension_hashes
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def inferred_options
|
57
|
+
{
|
58
|
+
x_axis_label: dimension.label,
|
59
|
+
y_axis_label: measure.label
|
60
|
+
}
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module ReportsKit
|
2
|
+
module Reports
|
3
|
+
module Data
|
4
|
+
class OneDimension
|
5
|
+
attr_accessor :measure, :dimension
|
6
|
+
|
7
|
+
def initialize(measure, dimension)
|
8
|
+
self.measure = measure
|
9
|
+
self.dimension = dimension
|
10
|
+
end
|
11
|
+
|
12
|
+
def perform
|
13
|
+
{
|
14
|
+
chart_data: {
|
15
|
+
labels: labels,
|
16
|
+
datasets: datasets
|
17
|
+
}
|
18
|
+
}
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def dimension_keys_values
|
24
|
+
@dimension_keys_values ||= begin
|
25
|
+
relation = measure.filtered_relation
|
26
|
+
relation = relation.group(dimension.group_expression)
|
27
|
+
relation = relation.joins(dimension.joins) if dimension.joins
|
28
|
+
relation = relation.limit(dimension.dimension_instances_limit) if dimension.dimension_instances_limit
|
29
|
+
if dimension.should_be_sorted_by_count?
|
30
|
+
relation = relation.order('1 DESC')
|
31
|
+
else
|
32
|
+
relation = relation.order('2')
|
33
|
+
end
|
34
|
+
dimension_keys_values = relation.distinct.public_send(*measure.aggregate_function)
|
35
|
+
dimension_keys_values = Utils.populate_sparse_values(dimension_keys_values)
|
36
|
+
dimension_keys_values.delete(nil)
|
37
|
+
dimension_keys_values.delete('')
|
38
|
+
dimension_keys_values
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def datasets
|
43
|
+
[
|
44
|
+
{
|
45
|
+
label: measure.label,
|
46
|
+
data: values
|
47
|
+
}
|
48
|
+
]
|
49
|
+
end
|
50
|
+
|
51
|
+
def values
|
52
|
+
dimension_keys_values.values.map { |value| value.round(Generate::ROUND_PRECISION) }
|
53
|
+
end
|
54
|
+
|
55
|
+
def labels
|
56
|
+
keys = dimension_keys_values.keys
|
57
|
+
keys.map do |key|
|
58
|
+
Utils.dimension_key_to_label(key, dimension, dimension_ids_dimension_instances)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def dimension_ids_dimension_instances
|
63
|
+
@dimension_ids_dimension_instances ||= begin
|
64
|
+
dimension_ids = dimension_keys_values.keys
|
65
|
+
Utils.dimension_to_dimension_ids_dimension_instances(dimension, dimension_ids)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,129 @@
|
|
1
|
+
module ReportsKit
|
2
|
+
module Reports
|
3
|
+
module Data
|
4
|
+
class TwoDimensions
|
5
|
+
attr_accessor :measure, :dimension, :second_dimension
|
6
|
+
|
7
|
+
def initialize(measure, dimension, second_dimension)
|
8
|
+
self.measure = measure
|
9
|
+
self.dimension = dimension
|
10
|
+
self.second_dimension = second_dimension
|
11
|
+
end
|
12
|
+
|
13
|
+
def perform
|
14
|
+
{
|
15
|
+
chart_data: {
|
16
|
+
labels: labels,
|
17
|
+
datasets: datasets
|
18
|
+
}
|
19
|
+
}
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def dimension_keys_values
|
25
|
+
@dimension_keys_values ||= begin
|
26
|
+
relation = measure.filtered_relation
|
27
|
+
relation = measure.conditions.call(relation) if measure.conditions
|
28
|
+
relation = relation.group(dimension.group_expression, second_dimension.group_expression)
|
29
|
+
|
30
|
+
relation = relation.joins(dimension.joins) if dimension.joins
|
31
|
+
relation = relation.joins(second_dimension.joins) if second_dimension.joins
|
32
|
+
|
33
|
+
if dimension.should_be_sorted_by_count?
|
34
|
+
relation = relation.order('1 DESC')
|
35
|
+
else
|
36
|
+
relation = relation.order('2')
|
37
|
+
end
|
38
|
+
dimension_keys_values = relation.count
|
39
|
+
|
40
|
+
if dimension.should_be_sorted_by_count?
|
41
|
+
dimension_keys_values = sort_dimension_keys_values_by_count(dimension_keys_values)
|
42
|
+
end
|
43
|
+
Hash[dimension_keys_values]
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def primary_keys_secondary_keys_values
|
48
|
+
@primary_keys_secondary_keys_values ||= begin
|
49
|
+
primary_keys_secondary_keys_values = {}
|
50
|
+
dimension_keys_values.each do |(primary_key, secondary_key), value|
|
51
|
+
primary_keys_secondary_keys_values[primary_key] ||= {}
|
52
|
+
primary_keys_secondary_keys_values[primary_key][secondary_key] = value
|
53
|
+
end
|
54
|
+
primary_keys_secondary_keys_values
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def sort_dimension_keys_values_by_count(dimension_keys_values)
|
59
|
+
primary_keys_counts = Hash.new(0)
|
60
|
+
dimension_keys_values.each do |(primary_key, secondary_key), count|
|
61
|
+
primary_keys_counts[primary_key] += count
|
62
|
+
end
|
63
|
+
primary_keys_counts = primary_keys_counts.to_a
|
64
|
+
sorted_primary_keys = primary_keys_counts.sort_by { |primary_key, count| count }.reverse.map(&:first)
|
65
|
+
dimension_keys_values = dimension_keys_values.sort_by { |(primary_key, secondary_key), count| sorted_primary_keys.index(primary_key) }
|
66
|
+
Hash[dimension_keys_values]
|
67
|
+
end
|
68
|
+
|
69
|
+
def dimension_ids
|
70
|
+
dimension_keys_values.keys.map(&:first)
|
71
|
+
end
|
72
|
+
|
73
|
+
def dimension_ids_dimension_instances
|
74
|
+
@dimension_ids_dimension_instances ||= begin
|
75
|
+
Utils.dimension_to_dimension_ids_dimension_instances(dimension, dimension_keys_values.keys.map(&:first))
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def second_dimension_ids_dimension_instances
|
80
|
+
@second_dimension_ids_dimension_instances ||= begin
|
81
|
+
Utils.dimension_to_dimension_ids_dimension_instances(second_dimension, dimension_keys_values.keys.map(&:last))
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def datasets
|
86
|
+
secondary_keys_values = secondary_keys.map do |secondary_key|
|
87
|
+
values = primary_keys.map do |primary_key|
|
88
|
+
primary_keys_secondary_keys_values[primary_key].try(:[], secondary_key) || 0
|
89
|
+
end
|
90
|
+
[secondary_key, values]
|
91
|
+
end
|
92
|
+
secondary_keys_values = secondary_keys_values.sort_by { |_, values| values.sum }.reverse
|
93
|
+
secondary_keys_values.map do |secondary_key, values|
|
94
|
+
{
|
95
|
+
label: Utils.dimension_key_to_label(secondary_key, second_dimension, second_dimension_ids_dimension_instances),
|
96
|
+
data: values
|
97
|
+
}
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def primary_keys
|
102
|
+
@primary_keys ||= begin
|
103
|
+
keys = Utils.populate_sparse_keys(dimension_keys_values.keys.map(&:first).uniq)
|
104
|
+
if dimension.should_be_sorted_by_count?
|
105
|
+
limit = dimension.dimension_instances_limit
|
106
|
+
keys = keys.first(limit) if limit
|
107
|
+
end
|
108
|
+
keys
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def secondary_keys
|
113
|
+
@secondary_keys ||= begin
|
114
|
+
keys = Utils.populate_sparse_keys(dimension_keys_values.keys.map(&:last).uniq)
|
115
|
+
limit = second_dimension.dimension_instances_limit
|
116
|
+
keys = keys.first(limit) if limit
|
117
|
+
keys
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def labels
|
122
|
+
primary_keys.map do |primary_key|
|
123
|
+
Utils.dimension_key_to_label(primary_key, dimension, dimension_ids_dimension_instances)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
module ReportsKit
|
2
|
+
module Reports
|
3
|
+
module Data
|
4
|
+
class Utils
|
5
|
+
def self.format_time(time)
|
6
|
+
time.strftime('%b %-d, \'%y')
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.populate_sparse_values(dimension_keys_values, use_first_value_key: false)
|
10
|
+
return dimension_keys_values if dimension_keys_values.blank?
|
11
|
+
first_key = dimension_keys_values.first.first
|
12
|
+
return dimension_keys_values unless first_key.is_a?(Time)
|
13
|
+
|
14
|
+
beginning_of_current_week = Time.now.utc.beginning_of_week(ReportsKit.configuration.first_day_of_week)
|
15
|
+
last_key = dimension_keys_values.to_a.last.first
|
16
|
+
last_key = [beginning_of_current_week, last_key].compact.max
|
17
|
+
|
18
|
+
time = first_key
|
19
|
+
full_dimension_instances_values = []
|
20
|
+
if use_first_value_key
|
21
|
+
first_value_key = dimension_keys_values.first.last.keys.first
|
22
|
+
blank_value = { first_value_key => 0 }
|
23
|
+
else
|
24
|
+
blank_value = 0
|
25
|
+
end
|
26
|
+
loop do
|
27
|
+
full_dimension_instances_values << [time, dimension_keys_values[time] || blank_value]
|
28
|
+
break if time >= last_key
|
29
|
+
time += 1.week
|
30
|
+
end
|
31
|
+
Hash[full_dimension_instances_values]
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.populate_sparse_keys(keys)
|
35
|
+
return keys if keys.blank?
|
36
|
+
first_key = keys.first
|
37
|
+
return keys unless first_key.is_a?(Time)
|
38
|
+
keys = keys.sort
|
39
|
+
beginning_of_current_week = Time.now.utc.beginning_of_week(ReportsKit.configuration.first_day_of_week)
|
40
|
+
last_key = keys.last
|
41
|
+
last_key = [beginning_of_current_week, last_key].compact.max
|
42
|
+
|
43
|
+
time = first_key
|
44
|
+
populated_keys = []
|
45
|
+
loop do
|
46
|
+
populated_keys << time
|
47
|
+
break if time >= last_key
|
48
|
+
time += 1.week
|
49
|
+
end
|
50
|
+
populated_keys
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.dimension_to_dimension_ids_dimension_instances(dimension, dimension_ids)
|
54
|
+
return nil unless dimension.instance_class
|
55
|
+
dimension_instances = dimension.instance_class.where(id: dimension_ids.uniq)
|
56
|
+
dimension_ids_dimension_instances = dimension_instances.map do |dimension_instance|
|
57
|
+
[dimension_instance.id, dimension_instance]
|
58
|
+
end
|
59
|
+
Hash[dimension_ids_dimension_instances]
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.dimension_key_to_label(dimension_instance, dimension, ids_dimension_instances)
|
63
|
+
return dimension_instance.to_s if dimension.configured_by_column? && dimension.column_type == :integer
|
64
|
+
case dimension_instance
|
65
|
+
when Time
|
66
|
+
Utils.format_time(dimension_instance)
|
67
|
+
when Fixnum
|
68
|
+
raise ArgumentError.new("ids_dimension_instances must be present for Dimension with identifier: #{dimension_instance}") unless ids_dimension_instances
|
69
|
+
instance = ids_dimension_instances[dimension_instance.to_i]
|
70
|
+
raise ArgumentError.new("instance could not be found for Dimension with identifier: #{dimension_instance}") unless instance
|
71
|
+
instance.to_s
|
72
|
+
else
|
73
|
+
dimension_instance.to_s.gsub(/\.0$/, '')
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
module ReportsKit
|
2
|
+
module Reports
|
3
|
+
class Dimension
|
4
|
+
DEFAULT_DIMENSION_INSTANCES_LIMIT = 30
|
5
|
+
|
6
|
+
attr_accessor :properties, :measure, :configuration
|
7
|
+
|
8
|
+
delegate :configured_by_association?, :configured_by_column?, :configured_by_model?, :configured_by_time?,
|
9
|
+
:settings_from_model, :reflection, :instance_class, :model_class, :column_type,
|
10
|
+
to: :configuration
|
11
|
+
|
12
|
+
def initialize(properties, measure:)
|
13
|
+
self.configuration = InferrableConfiguration.new(self, :dimensions)
|
14
|
+
self.measure = measure
|
15
|
+
|
16
|
+
raise ArgumentError.new('Blank properties') if properties.blank?
|
17
|
+
properties = { key: properties } if properties.is_a?(String)
|
18
|
+
raise ArgumentError.new("Measure properties must be a String or Hash, not a #{properties.class.name}: #{properties.inspect}") unless properties.is_a?(Hash)
|
19
|
+
properties = properties.deep_symbolize_keys
|
20
|
+
self.properties = properties
|
21
|
+
missing_group_setting = settings && !settings.key?(:group)
|
22
|
+
raise ArgumentError.new("Dimension settings for dimension '#{key}' of #{model_class} must include :group") if missing_group_setting
|
23
|
+
end
|
24
|
+
|
25
|
+
def key
|
26
|
+
properties[:key]
|
27
|
+
end
|
28
|
+
|
29
|
+
def label
|
30
|
+
key.titleize
|
31
|
+
end
|
32
|
+
|
33
|
+
def settings
|
34
|
+
inferred_settings.merge(settings_from_model)
|
35
|
+
end
|
36
|
+
|
37
|
+
def inferred_settings
|
38
|
+
configuration.inferred_settings.merge(inferred_dimension_settings)
|
39
|
+
end
|
40
|
+
|
41
|
+
def inferred_dimension_settings
|
42
|
+
{
|
43
|
+
group: group_expression
|
44
|
+
}
|
45
|
+
end
|
46
|
+
|
47
|
+
def group_expression
|
48
|
+
if configured_by_model?
|
49
|
+
settings_from_model[:group]
|
50
|
+
elsif configured_by_association?
|
51
|
+
"#{model_class.table_name}.#{reflection.foreign_key}"
|
52
|
+
elsif configured_by_column? && configured_by_time?
|
53
|
+
"date_trunc('week', #{model_class.table_name}.#{key}::timestamp)"
|
54
|
+
elsif configured_by_column?
|
55
|
+
"#{model_class.table_name}.#{key}"
|
56
|
+
else
|
57
|
+
raise ArgumentError.new('Invalid group_expression')
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def joins
|
62
|
+
settings_from_model[:joins] if configured_by_model?
|
63
|
+
end
|
64
|
+
|
65
|
+
def dimension_instances_limit
|
66
|
+
if configured_by_time?
|
67
|
+
properties[:limit]
|
68
|
+
else
|
69
|
+
properties[:limit] || DEFAULT_DIMENSION_INSTANCES_LIMIT
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def should_be_sorted_by_count?
|
74
|
+
!configured_by_time?
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
module ReportsKit
|
2
|
+
module Reports
|
3
|
+
class Filter
|
4
|
+
CONFIGURATION_STRATEGIES_FILTER_TYPE_CLASSES = {
|
5
|
+
association: FilterTypes::Records,
|
6
|
+
boolean: FilterTypes::Boolean,
|
7
|
+
datetime: FilterTypes::Datetime,
|
8
|
+
integer: FilterTypes::Number,
|
9
|
+
string: FilterTypes::String
|
10
|
+
}
|
11
|
+
COLUMN_TYPES_FILTER_TYPE_CLASSES = {
|
12
|
+
boolean: FilterTypes::Boolean,
|
13
|
+
datetime: FilterTypes::Datetime,
|
14
|
+
integer: FilterTypes::Number,
|
15
|
+
string: FilterTypes::String
|
16
|
+
}
|
17
|
+
|
18
|
+
attr_accessor :properties, :measure, :configuration
|
19
|
+
|
20
|
+
delegate :configured_by_association?, :configured_by_column?, :configured_by_model?, :configured_by_time?,
|
21
|
+
:settings_from_model, :configuration_strategy, :instance_class, :column_type, :column,
|
22
|
+
to: :configuration
|
23
|
+
|
24
|
+
def initialize(properties, measure:)
|
25
|
+
self.configuration = InferrableConfiguration.new(self, :filters)
|
26
|
+
self.measure = measure
|
27
|
+
|
28
|
+
properties = { key: properties } if properties.is_a?(String)
|
29
|
+
raise ArgumentError.new("Measure properties must be a String or Hash, not a #{properties.class.name}: #{properties.inspect}") unless properties.is_a?(Hash)
|
30
|
+
self.properties = properties.deep_symbolize_keys
|
31
|
+
self.properties[:criteria] = filter_type.default_criteria unless self.properties[:criteria]
|
32
|
+
self.properties = self.properties
|
33
|
+
end
|
34
|
+
|
35
|
+
def key
|
36
|
+
properties[:key]
|
37
|
+
end
|
38
|
+
|
39
|
+
def label
|
40
|
+
key.titleize
|
41
|
+
end
|
42
|
+
|
43
|
+
def settings
|
44
|
+
inferred_settings.merge(settings_from_model)
|
45
|
+
end
|
46
|
+
|
47
|
+
def inferred_settings
|
48
|
+
configuration.inferred_settings.merge(inferred_filter_settings)
|
49
|
+
end
|
50
|
+
|
51
|
+
def inferred_filter_settings
|
52
|
+
{
|
53
|
+
column: column
|
54
|
+
}
|
55
|
+
end
|
56
|
+
|
57
|
+
def type_klass
|
58
|
+
type_klass_for_configuration_strategy = CONFIGURATION_STRATEGIES_FILTER_TYPE_CLASSES[configuration_strategy]
|
59
|
+
return type_klass_for_configuration_strategy if type_klass_for_configuration_strategy
|
60
|
+
type_klass_for_column_type = COLUMN_TYPES_FILTER_TYPE_CLASSES[column_type]
|
61
|
+
return type_klass_for_column_type if type_klass_for_column_type
|
62
|
+
return filter_type_class_from_model if configured_by_model?
|
63
|
+
raise ArgumentError.new("No configuration found for filter with key: '#{key}'")
|
64
|
+
end
|
65
|
+
|
66
|
+
def filter_type
|
67
|
+
type_klass.new(settings, properties)
|
68
|
+
end
|
69
|
+
|
70
|
+
def filter_type_class_from_model
|
71
|
+
return unless settings
|
72
|
+
type_key = settings[:type_key]
|
73
|
+
raise ArgumentError.new("No type specified for filter with key: '#{key}'") unless type_key
|
74
|
+
type_class = CONFIGURATION_STRATEGIES_FILTER_TYPE_CLASSES[type_key]
|
75
|
+
raise ArgumentError.new("Invalid type ('#{type_key}') specified for filter with key: '#{key}'") unless type_class
|
76
|
+
type_class
|
77
|
+
end
|
78
|
+
|
79
|
+
def apply(relation)
|
80
|
+
filter_type.apply_filter(relation)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|