reports_kits 0.7.5 → 0.7.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +6 -0
- data/.rubocop.yml +85 -0
- data/.travis.yml +21 -0
- data/Appraisals +27 -0
- data/Gemfile +3 -0
- data/MIT-LICENSE +20 -0
- data/README.md +35 -0
- data/Rakefile +2 -0
- data/app/assets/javascripts/reports_kits/application.js +14 -0
- data/app/assets/javascripts/reports_kits/lib/_init.js +9 -0
- data/app/assets/javascripts/reports_kits/lib/chart.js +73 -0
- data/app/assets/javascripts/reports_kits/lib/report.js +135 -0
- data/app/assets/javascripts/reports_kits/lib/table.js +87 -0
- data/app/assets/javascripts/reports_kits/vendor/chart.js +12269 -0
- data/app/assets/javascripts/reports_kits/vendor/daterangepicker.js +1627 -0
- data/app/assets/javascripts/reports_kits/vendor/jquery.tablesorter.min.js +4 -0
- data/app/assets/javascripts/reports_kits/vendor/moment.js +4040 -0
- data/app/assets/javascripts/reports_kits/vendor/select2.full.js +6436 -0
- data/app/assets/stylesheets/reports_kits/application.css.scss +3 -0
- data/app/assets/stylesheets/reports_kits/reports.css.sass +33 -0
- data/app/assets/stylesheets/reports_kits/select2_overrides.css.sass +7 -0
- data/app/assets/stylesheets/reports_kits/vendor/daterangepicker.css +269 -0
- data/app/assets/stylesheets/reports_kits/vendor/select2-bootstrap.css +721 -0
- data/app/assets/stylesheets/reports_kits/vendor/select2.css +484 -0
- data/config/initializers/mime_types.rb +1 -0
- data/config/routes.rb +10 -0
- data/docs/images/demo.gif +0 -0
- data/docs/images/users_by_created_at.png +0 -0
- data/gemfiles/mysql.gemfile +7 -0
- data/gemfiles/mysql.gemfile.lock +167 -0
- data/gemfiles/postgresql.gemfile +7 -0
- data/gemfiles/postgresql.gemfile.lock +165 -0
- data/gemfiles/postgresql_rails_5.1.4.gemfile +8 -0
- data/gemfiles/postgresql_rails_5.1.4.gemfile.lock +168 -0
- data/gemfiles/rails_4_mysql.gemfile +8 -0
- data/gemfiles/rails_4_mysql.gemfile.lock +165 -0
- data/gemfiles/rails_4_postgresql.gemfile +8 -0
- data/gemfiles/rails_4_postgresql.gemfile.lock +163 -0
- data/gemfiles/rails_5.1.4_postgresql.gemfile +8 -0
- data/gemfiles/rails_5.1.4_postgresql.gemfile.lock +169 -0
- data/gemfiles/rails_5_mysql.gemfile +8 -0
- data/gemfiles/rails_5_mysql.gemfile.lock +171 -0
- data/gemfiles/rails_5_postgresql.gemfile +8 -0
- data/gemfiles/rails_5_postgresql.gemfile.lock +169 -0
- data/lib/reports_kits/base_controller.rb +17 -0
- data/lib/reports_kits/cache.rb +37 -0
- data/lib/reports_kits/configuration.rb +50 -0
- data/lib/reports_kits/engine.rb +21 -0
- data/lib/reports_kits/entity.rb +3 -0
- data/lib/reports_kits/filters_controller.rb +11 -0
- data/lib/reports_kits/form_builder.rb +66 -0
- data/lib/reports_kits/helper.rb +24 -0
- data/lib/reports_kits/model.rb +16 -0
- data/lib/reports_kits/model_configuration.rb +28 -0
- data/lib/reports_kits/normalized_params.rb +16 -0
- data/lib/reports_kits/order.rb +33 -0
- data/lib/reports_kits/relative_time.rb +42 -0
- data/lib/reports_kits/report_builder.rb +88 -0
- data/lib/reports_kits/reports/abstract_series.rb +9 -0
- data/lib/reports_kits/reports/adapters/mysql.rb +26 -0
- data/lib/reports_kits/reports/adapters/postgresql.rb +26 -0
- data/lib/reports_kits/reports/composite_series.rb +48 -0
- data/lib/reports_kits/reports/contextual_filter.rb +19 -0
- data/lib/reports_kits/reports/data/add_table_aggregations.rb +105 -0
- data/lib/reports_kits/reports/data/aggregate_composite.rb +97 -0
- data/lib/reports_kits/reports/data/aggregate_one_dimension.rb +39 -0
- data/lib/reports_kits/reports/data/aggregate_two_dimensions.rb +39 -0
- data/lib/reports_kits/reports/data/chart_data_for_data_method.rb +62 -0
- data/lib/reports_kits/reports/data/chart_options.rb +155 -0
- data/lib/reports_kits/reports/data/format_one_dimension.rb +140 -0
- data/lib/reports_kits/reports/data/format_table.rb +63 -0
- data/lib/reports_kits/reports/data/format_two_dimensions.rb +143 -0
- data/lib/reports_kits/reports/data/generate.rb +156 -0
- data/lib/reports_kits/reports/data/generate_for_properties.rb +97 -0
- data/lib/reports_kits/reports/data/normalize_properties.rb +62 -0
- data/lib/reports_kits/reports/data/populate_one_dimension.rb +54 -0
- data/lib/reports_kits/reports/data/populate_two_dimensions.rb +104 -0
- data/lib/reports_kits/reports/data/utils.rb +178 -0
- data/lib/reports_kits/reports/dimension.rb +27 -0
- data/lib/reports_kits/reports/dimension_with_series.rb +144 -0
- data/lib/reports_kits/reports/filter.rb +29 -0
- data/lib/reports_kits/reports/filter_types/base.rb +48 -0
- data/lib/reports_kits/reports/filter_types/boolean.rb +47 -0
- data/lib/reports_kits/reports/filter_types/datetime.rb +51 -0
- data/lib/reports_kits/reports/filter_types/number.rb +30 -0
- data/lib/reports_kits/reports/filter_types/records.rb +26 -0
- data/lib/reports_kits/reports/filter_types/string.rb +38 -0
- data/lib/reports_kits/reports/filter_with_series.rb +97 -0
- data/lib/reports_kits/reports/generate_autocomplete_method_results.rb +29 -0
- data/lib/reports_kits/reports/generate_autocomplete_results.rb +57 -0
- data/lib/reports_kits/reports/inferrable_configuration.rb +116 -0
- data/lib/reports_kits/reports/model_settings.rb +30 -0
- data/lib/reports_kits/reports/properties.rb +10 -0
- data/lib/reports_kits/reports/properties_to_filter.rb +40 -0
- data/lib/reports_kits/reports/series.rb +121 -0
- data/lib/reports_kits/reports_controller.rb +65 -0
- data/lib/reports_kits/utils.rb +11 -0
- data/lib/reports_kits/value.rb +3 -0
- data/lib/reports_kits/version.rb +3 -0
- data/lib/reports_kits.rb +79 -0
- data/reports_kits.gemspec +26 -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/pro_repo_factory.rb +5 -0
- data/spec/factories/repo_factory.rb +5 -0
- data/spec/factories/tag_factory.rb +4 -0
- data/spec/fixtures/generate_inputs.yml +254 -0
- data/spec/fixtures/generate_outputs.yml +1216 -0
- data/spec/reports_kit/form_builder_spec.rb +26 -0
- data/spec/reports_kit/relative_time_spec.rb +29 -0
- data/spec/reports_kit/reports/data/generate/contextual_filters_spec.rb +60 -0
- data/spec/reports_kit/reports/data/generate_spec.rb +1371 -0
- data/spec/reports_kit/reports/data/normalize_properties_spec.rb +196 -0
- data/spec/reports_kit/reports/dimension_with_series_spec.rb +67 -0
- data/spec/reports_kit/reports/filter_with_series_spec.rb +39 -0
- data/spec/reports_kit/reports/generate_autocomplete_results_spec.rb +69 -0
- data/spec/spec_helper.rb +77 -0
- data/spec/support/config.rb +41 -0
- data/spec/support/example_data_methods.rb +25 -0
- data/spec/support/factory_girl.rb +5 -0
- data/spec/support/helpers.rb +25 -0
- data/spec/support/models/issue.rb +14 -0
- data/spec/support/models/issues_label.rb +4 -0
- data/spec/support/models/label.rb +5 -0
- data/spec/support/models/pro/repo.rb +5 -0
- data/spec/support/models/pro/special_issue.rb +4 -0
- data/spec/support/models/repo.rb +13 -0
- data/spec/support/models/tag.rb +4 -0
- data/spec/support/schema.rb +39 -0
- metadata +134 -4
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
module ReportsKits
|
|
2
|
+
module Reports
|
|
3
|
+
class DimensionWithSeries
|
|
4
|
+
DEFAULT_GRANULARITY = 'week'
|
|
5
|
+
VALID_GRANULARITIES = %w(day week month).freeze
|
|
6
|
+
ADAPTER_NAMES_CLASSES = {
|
|
7
|
+
'mysql2' => Adapters::Mysql,
|
|
8
|
+
'postgresql' => Adapters::Postgresql
|
|
9
|
+
}.freeze
|
|
10
|
+
|
|
11
|
+
attr_accessor :dimension, :series, :configuration
|
|
12
|
+
|
|
13
|
+
delegate :key, :expression, :properties, :label, to: :dimension
|
|
14
|
+
delegate :configured_by_association?, :configured_by_column?, :configured_by_model?, :configured_by_time?,
|
|
15
|
+
:settings_from_model, :reflection, :instance_class, :model_class, :column_type,
|
|
16
|
+
to: :configuration
|
|
17
|
+
|
|
18
|
+
def initialize(dimension:, series:)
|
|
19
|
+
self.dimension = dimension
|
|
20
|
+
self.series = series
|
|
21
|
+
self.configuration = InferrableConfiguration.new(self, :dimensions)
|
|
22
|
+
missing_group_setting = settings && !settings.key?(:group)
|
|
23
|
+
raise ArgumentError.new("Dimension settings for dimension '#{key}' of #{model_class} must include :group") if missing_group_setting
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def granularity
|
|
27
|
+
@granularity ||= begin
|
|
28
|
+
return unless configured_by_time?
|
|
29
|
+
granularity = properties[:granularity] || DEFAULT_GRANULARITY
|
|
30
|
+
raise ArgumentError.new("Invalid granularity: #{granularity}") unless VALID_GRANULARITIES.include?(granularity)
|
|
31
|
+
granularity
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def settings
|
|
36
|
+
inferred_settings.merge(settings_from_model)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def inferred_settings
|
|
40
|
+
configuration.inferred_settings.merge(inferred_dimension_settings)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def inferred_dimension_settings
|
|
44
|
+
{
|
|
45
|
+
group: group_expression
|
|
46
|
+
}
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def group_expression
|
|
50
|
+
if configured_by_model?
|
|
51
|
+
settings_from_model[:group]
|
|
52
|
+
elsif configured_by_association?
|
|
53
|
+
inferred_settings_from_association[:column]
|
|
54
|
+
elsif configured_by_column? && configured_by_time?
|
|
55
|
+
case granularity
|
|
56
|
+
when 'day' then day_expression
|
|
57
|
+
when 'month' then month_expression
|
|
58
|
+
else week_expression
|
|
59
|
+
end
|
|
60
|
+
elsif configured_by_column?
|
|
61
|
+
column_expression
|
|
62
|
+
else
|
|
63
|
+
raise ArgumentError.new('Invalid group_expression')
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def inferred_settings_from_association
|
|
68
|
+
through_reflection = reflection.through_reflection
|
|
69
|
+
if through_reflection
|
|
70
|
+
{
|
|
71
|
+
joins: through_reflection.name,
|
|
72
|
+
column: "#{through_reflection.table_name}.#{reflection.source_reflection.foreign_key}"
|
|
73
|
+
}
|
|
74
|
+
else
|
|
75
|
+
{
|
|
76
|
+
column: "#{model_class.table_name}.#{reflection.foreign_key}"
|
|
77
|
+
}
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def joins
|
|
82
|
+
return settings_from_model[:joins] if configured_by_model?
|
|
83
|
+
inferred_settings_from_association[:joins] if configured_by_association?
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def dimension_instances_limit
|
|
87
|
+
if configured_by_time?
|
|
88
|
+
properties[:limit]
|
|
89
|
+
else
|
|
90
|
+
properties[:limit] || ReportsKits.configuration.default_dimension_limit
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def first_key
|
|
95
|
+
return unless configured_by_time? && datetime_filters.present?
|
|
96
|
+
datetime_filters.map(&:start_at).compact.sort.first
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def last_key
|
|
100
|
+
return unless configured_by_time? && datetime_filters.present?
|
|
101
|
+
datetime_filters.map(&:end_at).compact.sort.last
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def key_to_label(key)
|
|
105
|
+
return unless settings[:key_to_label]
|
|
106
|
+
settings[:key_to_label].call(key)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def datetime_filters
|
|
110
|
+
return [] unless series.filters.present?
|
|
111
|
+
series.filters.map(&:filter_type).select { |filter_type| filter_type.is_a?(FilterTypes::Datetime) }
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def should_be_sorted_by_count?
|
|
115
|
+
!configured_by_time?
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def adapter
|
|
119
|
+
@adapter ||= begin
|
|
120
|
+
adapter_name = model_class.connection_config[:adapter]
|
|
121
|
+
adapter = ADAPTER_NAMES_CLASSES[adapter_name]
|
|
122
|
+
raise ArgumentError.new("Unsupported adapter: #{adapter_name}") unless adapter
|
|
123
|
+
adapter
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def column_expression
|
|
128
|
+
"#{model_class.table_name}.#{key}"
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def day_expression
|
|
132
|
+
adapter.truncate_to_day(column_expression)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def week_expression
|
|
136
|
+
adapter.truncate_to_week(column_expression)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def month_expression
|
|
140
|
+
adapter.truncate_to_month(column_expression)
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
module ReportsKits
|
|
2
|
+
module Reports
|
|
3
|
+
class Filter
|
|
4
|
+
attr_accessor :properties
|
|
5
|
+
|
|
6
|
+
def initialize(properties)
|
|
7
|
+
properties = { key: properties } if properties.is_a?(String)
|
|
8
|
+
raise ArgumentError.new("Filter properties must be a String or Hash, not a #{properties.class.name}: #{properties.inspect}") unless properties.is_a?(Hash)
|
|
9
|
+
self.properties = properties.deep_symbolize_keys
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def key
|
|
13
|
+
properties[:key]
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def expression
|
|
17
|
+
properties[:expression] || key
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def label
|
|
21
|
+
key.titleize
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def normalized_properties
|
|
25
|
+
properties
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
module ReportsKits
|
|
2
|
+
module Reports
|
|
3
|
+
module FilterTypes
|
|
4
|
+
class Base
|
|
5
|
+
attr_accessor :settings, :properties, :primary_dimension
|
|
6
|
+
|
|
7
|
+
def initialize(settings, properties, primary_dimension:)
|
|
8
|
+
self.settings = settings || {}
|
|
9
|
+
self.properties = properties
|
|
10
|
+
self.primary_dimension = primary_dimension
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def apply_filter(records)
|
|
14
|
+
return records unless valid?
|
|
15
|
+
records = records.joins(joins) if joins.present?
|
|
16
|
+
return records if value.blank? && !is_a?(FilterTypes::Boolean)
|
|
17
|
+
apply_conditions(records)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def default_criteria
|
|
21
|
+
self.class::DEFAULT_CRITERIA
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def apply_conditions(_records)
|
|
27
|
+
raise NotImplementedError
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def criteria
|
|
31
|
+
@criteria ||= default_criteria.merge(properties[:criteria])
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def value
|
|
35
|
+
criteria[:value]
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def joins
|
|
39
|
+
settings[:joins]
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def column
|
|
43
|
+
settings[:column] || Data::Utils.quote_column_name(properties[:key])
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
module ReportsKits
|
|
2
|
+
module Reports
|
|
3
|
+
module FilterTypes
|
|
4
|
+
class Boolean < Base
|
|
5
|
+
DEFAULT_CRITERIA = {
|
|
6
|
+
value: nil
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
def apply_conditions(records)
|
|
10
|
+
case conditions
|
|
11
|
+
when ::String
|
|
12
|
+
records.where("(#{conditions}) #{sql_operator} true")
|
|
13
|
+
when ::Hash
|
|
14
|
+
boolean_value ? records.where(conditions) : records.not.where(conditions)
|
|
15
|
+
when ::Proc
|
|
16
|
+
conditions.call(records)
|
|
17
|
+
else
|
|
18
|
+
raise ArgumentError.new("Unsupported conditions type: '#{conditions}'")
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def boolean_value
|
|
23
|
+
case value
|
|
24
|
+
when true, 'true'
|
|
25
|
+
true
|
|
26
|
+
when false, 'false'
|
|
27
|
+
false
|
|
28
|
+
else
|
|
29
|
+
raise ArgumentError.new("Unsupported value: '#{value}'")
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def sql_operator
|
|
34
|
+
boolean_value ? '=' : '!='
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def valid?
|
|
38
|
+
value.present?
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def conditions
|
|
42
|
+
settings[:conditions] || Data::Utils.quote_column_name(properties[:key])
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
module ReportsKits
|
|
2
|
+
module Reports
|
|
3
|
+
module FilterTypes
|
|
4
|
+
class Datetime < Base
|
|
5
|
+
DEFAULT_CRITERIA = {
|
|
6
|
+
operator: 'between'
|
|
7
|
+
}
|
|
8
|
+
SEPARATOR = ' - '
|
|
9
|
+
|
|
10
|
+
def apply_conditions(records)
|
|
11
|
+
case criteria[:operator]
|
|
12
|
+
when 'between'
|
|
13
|
+
records.where.not(column => nil).where(column => start_at..end_at)
|
|
14
|
+
else
|
|
15
|
+
raise ArgumentError.new("Unsupported operator: '#{criteria[:operator]}'")
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def start_at_end_at
|
|
20
|
+
@start_at_end_at ||= valid? ? adjust_range_to_dimension(*Reports::Data::Utils.parse_date_range(value, type: Array)) : nil
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def start_at
|
|
24
|
+
start_at_end_at.try(:[], 0)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def end_at
|
|
28
|
+
start_at_end_at.try(:[], 1)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def adjust_range_to_dimension(start_at, end_at)
|
|
32
|
+
return [start_at.beginning_of_day, end_at.end_of_day] unless primary_dimension.configured_by_time?
|
|
33
|
+
return [start_at.beginning_of_day, end_at.end_of_day] if primary_dimension.granularity == 'day'
|
|
34
|
+
return [
|
|
35
|
+
start_at.beginning_of_week(ReportsKits.configuration.first_day_of_week),
|
|
36
|
+
end_at.end_of_week(ReportsKits.configuration.first_day_of_week)
|
|
37
|
+
] if primary_dimension.granularity == 'week'
|
|
38
|
+
return [
|
|
39
|
+
start_at.beginning_of_month,
|
|
40
|
+
end_at.end_of_month
|
|
41
|
+
] if primary_dimension.granularity == 'month'
|
|
42
|
+
[start_at, end_at]
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def valid?
|
|
46
|
+
value.present?
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
module ReportsKits
|
|
2
|
+
module Reports
|
|
3
|
+
module FilterTypes
|
|
4
|
+
class Number < Base
|
|
5
|
+
DEFAULT_CRITERIA = {}
|
|
6
|
+
|
|
7
|
+
def apply_conditions(records)
|
|
8
|
+
case criteria[:operator]
|
|
9
|
+
when '>'
|
|
10
|
+
records.where(column => (value.to_i...Float::INFINITY))
|
|
11
|
+
when '>='
|
|
12
|
+
records.where(column => (value.to_i..Float::INFINITY))
|
|
13
|
+
when '<'
|
|
14
|
+
records.where(column => (-Float::INFINITY...value.to_i))
|
|
15
|
+
when '<='
|
|
16
|
+
records.where(column => (-Float::INFINITY..value.to_i))
|
|
17
|
+
when '='
|
|
18
|
+
records.where(column => value.to_i)
|
|
19
|
+
else
|
|
20
|
+
raise ArgumentError.new("Unsupported operator: '#{criteria[:operator]}'")
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def valid?
|
|
25
|
+
value.present?
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
module ReportsKits
|
|
2
|
+
module Reports
|
|
3
|
+
module FilterTypes
|
|
4
|
+
class Records < Base
|
|
5
|
+
DEFAULT_CRITERIA = {
|
|
6
|
+
operator: 'include'
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
def apply_conditions(records)
|
|
10
|
+
case criteria[:operator]
|
|
11
|
+
when 'include'
|
|
12
|
+
records.where(column => value)
|
|
13
|
+
when 'does_not_include'
|
|
14
|
+
records.where.not(column => value)
|
|
15
|
+
else
|
|
16
|
+
raise ArgumentError.new("Unsupported operator: '#{criteria[:operator]}'")
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def valid?
|
|
21
|
+
value.present?
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
module ReportsKits
|
|
2
|
+
module Reports
|
|
3
|
+
module FilterTypes
|
|
4
|
+
class String < Base
|
|
5
|
+
DEFAULT_CRITERIA = {
|
|
6
|
+
operator: 'contains'
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
def apply_conditions(records)
|
|
10
|
+
case criteria[:operator]
|
|
11
|
+
when 'equals'
|
|
12
|
+
records.where("#{column} = ?", value)
|
|
13
|
+
when 'contains'
|
|
14
|
+
records.where("#{column} ILIKE ?", "%#{value}%")
|
|
15
|
+
when 'starts_with'
|
|
16
|
+
records.where("#{column} ILIKE ?", "#{value}%")
|
|
17
|
+
when 'ends_with'
|
|
18
|
+
records.where("#{column} ILIKE ?", "%#{value}")
|
|
19
|
+
when 'does_not_equal'
|
|
20
|
+
records.where("#{column} != ?", value)
|
|
21
|
+
when 'does_not_contain'
|
|
22
|
+
records.where("#{column} NOT ILIKE ?", "%#{value}%")
|
|
23
|
+
when 'does_not_start_with'
|
|
24
|
+
records.where("#{column} NOT ILIKE ?", "#{value}%")
|
|
25
|
+
when 'does_not_end_with'
|
|
26
|
+
records.where("#{column} NOT ILIKE ?", "%#{value}")
|
|
27
|
+
else
|
|
28
|
+
raise ArgumentError.new("Unsupported operator: '#{criteria[:operator]}'")
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def valid?
|
|
33
|
+
value.present?
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
module ReportsKits
|
|
2
|
+
module Reports
|
|
3
|
+
class FilterWithSeries
|
|
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 :filter, :series, :configuration
|
|
19
|
+
|
|
20
|
+
delegate :key, :expression, :properties, :label, to: :filter
|
|
21
|
+
delegate :configured_by_association?, :configured_by_column?, :configured_by_model?, :configured_by_time?,
|
|
22
|
+
:settings_from_model, :configuration_strategy, :instance_class, :column_type, :column,
|
|
23
|
+
to: :configuration
|
|
24
|
+
|
|
25
|
+
def initialize(filter:, series:)
|
|
26
|
+
self.filter = filter
|
|
27
|
+
self.series = series
|
|
28
|
+
self.configuration = InferrableConfiguration.new(self, :filters)
|
|
29
|
+
|
|
30
|
+
properties[:criteria] = filter_type.default_criteria unless properties[:criteria]
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def normalized_properties
|
|
34
|
+
return properties unless configured_by_time?
|
|
35
|
+
criteria = properties[:criteria]
|
|
36
|
+
return properties if criteria.blank? || criteria[:value].blank?
|
|
37
|
+
values = criteria[:value].split(ReportsKits::Reports::FilterTypes::Datetime::SEPARATOR)
|
|
38
|
+
values = values.map { |value| ReportsKits::Reports::Data::Utils.format_time_value(value) }
|
|
39
|
+
properties[:criteria][:value] = values.join(ReportsKits::Reports::FilterTypes::Datetime::SEPARATOR)
|
|
40
|
+
properties
|
|
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 context_record_association
|
|
58
|
+
properties[:context_record_association] || instance_class.name.tableize
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def type_klass
|
|
62
|
+
type_klass_for_configuration_strategy = CONFIGURATION_STRATEGIES_FILTER_TYPE_CLASSES[configuration_strategy]
|
|
63
|
+
return type_klass_for_configuration_strategy if type_klass_for_configuration_strategy
|
|
64
|
+
type_klass_for_column_type = COLUMN_TYPES_FILTER_TYPE_CLASSES[column_type]
|
|
65
|
+
return type_klass_for_column_type if type_klass_for_column_type
|
|
66
|
+
return filter_type_class_from_model if configured_by_model?
|
|
67
|
+
raise ArgumentError.new("No configuration found for filter with key: '#{key}'")
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def filter_type
|
|
71
|
+
type_klass.new(settings, properties, primary_dimension: series.dimensions.first)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def filter_type_class_from_model
|
|
75
|
+
return unless settings
|
|
76
|
+
type_key = settings[:type_key]
|
|
77
|
+
raise ArgumentError.new("No type specified for filter with key: '#{key}'") unless type_key
|
|
78
|
+
type_class = CONFIGURATION_STRATEGIES_FILTER_TYPE_CLASSES[type_key]
|
|
79
|
+
raise ArgumentError.new("Invalid type ('#{type_key}') specified for filter with key: '#{key}'") unless type_class
|
|
80
|
+
type_class
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def apply_contextual_filters(relation, context_params)
|
|
84
|
+
return relation if properties[:contextual_filters].blank? || context_params.blank?
|
|
85
|
+
contextual_filters = properties[:contextual_filters].map { |key| ContextualFilter.new(key, relation.base_class) }
|
|
86
|
+
contextual_filters.each do |contextual_filter|
|
|
87
|
+
relation = contextual_filter.apply(relation, context_params)
|
|
88
|
+
end
|
|
89
|
+
relation
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def apply(relation)
|
|
93
|
+
filter_type.apply_filter(relation)
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
module ReportsKits
|
|
2
|
+
module Reports
|
|
3
|
+
class GenerateAutocompleteMethodResults
|
|
4
|
+
attr_accessor :filter_key, :params, :properties
|
|
5
|
+
|
|
6
|
+
def initialize(filter_key, properties, params)
|
|
7
|
+
self.filter_key = filter_key
|
|
8
|
+
self.params = params
|
|
9
|
+
self.properties = properties
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def perform
|
|
13
|
+
return unless properties[:ui_filters]
|
|
14
|
+
klass, method_name = ReportsKits::Utils.string_to_class_method(autocomplete_method, 'autocomplete_method')
|
|
15
|
+
klass.public_send(method_name, params, properties)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
def filter_hash
|
|
21
|
+
properties[:ui_filters].find { |filter_params| filter_params.is_a?(Hash) && filter_params[:key] == filter_key }
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def autocomplete_method
|
|
25
|
+
filter_hash[:autocomplete_method]
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
module ReportsKits
|
|
2
|
+
module Reports
|
|
3
|
+
class GenerateAutocompleteResults
|
|
4
|
+
attr_accessor :params, :properties, :filter_key, :filter, :context_params, :context_record
|
|
5
|
+
|
|
6
|
+
def initialize(params, properties, context_record: nil)
|
|
7
|
+
self.params = params
|
|
8
|
+
self.properties = properties
|
|
9
|
+
self.context_params = params[:context_params]
|
|
10
|
+
self.filter_key = params[:key]
|
|
11
|
+
self.filter = Reports::PropertiesToFilter.new(properties, context_record: context_record).perform(filter_key)
|
|
12
|
+
self.context_record = context_record
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def perform
|
|
16
|
+
autocomplete_method_results = Reports::GenerateAutocompleteMethodResults.new(filter_key, properties, params).perform
|
|
17
|
+
return autocomplete_method_results if autocomplete_method_results
|
|
18
|
+
raise ArgumentError.new("Could not find a model for filter_key: '#{filter_key}'") unless model
|
|
19
|
+
return autocomplete_results_method.call(params: params, context_record: context_record, relation: relation) if autocomplete_results_method
|
|
20
|
+
results = filter.apply_contextual_filters(relation, context_params)
|
|
21
|
+
results = results.limit(10_000)
|
|
22
|
+
results = results.map { |result| { id: result.id, text: result.to_s } }
|
|
23
|
+
results = results.sort_by { |result| result[:text].downcase }
|
|
24
|
+
results = filter_results(results)
|
|
25
|
+
results.first(100)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def autocomplete_results_method
|
|
31
|
+
ReportsKits.configuration.autocomplete_results_method
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def relation
|
|
35
|
+
if context_record
|
|
36
|
+
context_record.public_send(filter.context_record_association)
|
|
37
|
+
else
|
|
38
|
+
model
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def filter_results(results)
|
|
43
|
+
query = params[:q].try(:downcase)
|
|
44
|
+
if query.present?
|
|
45
|
+
results = results.to_a.select { |r| r[:text].downcase.include?(query) }
|
|
46
|
+
end
|
|
47
|
+
results
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def model
|
|
51
|
+
@model ||= begin
|
|
52
|
+
filter.instance_class
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|