reports_kits 0.7.5 → 0.7.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (132) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +6 -0
  3. data/.rubocop.yml +85 -0
  4. data/.travis.yml +21 -0
  5. data/Appraisals +27 -0
  6. data/Gemfile +3 -0
  7. data/MIT-LICENSE +20 -0
  8. data/README.md +35 -0
  9. data/Rakefile +2 -0
  10. data/app/assets/javascripts/reports_kits/application.js +14 -0
  11. data/app/assets/javascripts/reports_kits/lib/_init.js +9 -0
  12. data/app/assets/javascripts/reports_kits/lib/chart.js +73 -0
  13. data/app/assets/javascripts/reports_kits/lib/report.js +135 -0
  14. data/app/assets/javascripts/reports_kits/lib/table.js +87 -0
  15. data/app/assets/javascripts/reports_kits/vendor/chart.js +12269 -0
  16. data/app/assets/javascripts/reports_kits/vendor/daterangepicker.js +1627 -0
  17. data/app/assets/javascripts/reports_kits/vendor/jquery.tablesorter.min.js +4 -0
  18. data/app/assets/javascripts/reports_kits/vendor/moment.js +4040 -0
  19. data/app/assets/javascripts/reports_kits/vendor/select2.full.js +6436 -0
  20. data/app/assets/stylesheets/reports_kits/application.css.scss +3 -0
  21. data/app/assets/stylesheets/reports_kits/reports.css.sass +33 -0
  22. data/app/assets/stylesheets/reports_kits/select2_overrides.css.sass +7 -0
  23. data/app/assets/stylesheets/reports_kits/vendor/daterangepicker.css +269 -0
  24. data/app/assets/stylesheets/reports_kits/vendor/select2-bootstrap.css +721 -0
  25. data/app/assets/stylesheets/reports_kits/vendor/select2.css +484 -0
  26. data/config/initializers/mime_types.rb +1 -0
  27. data/config/routes.rb +10 -0
  28. data/docs/images/demo.gif +0 -0
  29. data/docs/images/users_by_created_at.png +0 -0
  30. data/gemfiles/mysql.gemfile +7 -0
  31. data/gemfiles/mysql.gemfile.lock +167 -0
  32. data/gemfiles/postgresql.gemfile +7 -0
  33. data/gemfiles/postgresql.gemfile.lock +165 -0
  34. data/gemfiles/postgresql_rails_5.1.4.gemfile +8 -0
  35. data/gemfiles/postgresql_rails_5.1.4.gemfile.lock +168 -0
  36. data/gemfiles/rails_4_mysql.gemfile +8 -0
  37. data/gemfiles/rails_4_mysql.gemfile.lock +165 -0
  38. data/gemfiles/rails_4_postgresql.gemfile +8 -0
  39. data/gemfiles/rails_4_postgresql.gemfile.lock +163 -0
  40. data/gemfiles/rails_5.1.4_postgresql.gemfile +8 -0
  41. data/gemfiles/rails_5.1.4_postgresql.gemfile.lock +169 -0
  42. data/gemfiles/rails_5_mysql.gemfile +8 -0
  43. data/gemfiles/rails_5_mysql.gemfile.lock +171 -0
  44. data/gemfiles/rails_5_postgresql.gemfile +8 -0
  45. data/gemfiles/rails_5_postgresql.gemfile.lock +169 -0
  46. data/lib/reports_kits/base_controller.rb +17 -0
  47. data/lib/reports_kits/cache.rb +37 -0
  48. data/lib/reports_kits/configuration.rb +50 -0
  49. data/lib/reports_kits/engine.rb +21 -0
  50. data/lib/reports_kits/entity.rb +3 -0
  51. data/lib/reports_kits/filters_controller.rb +11 -0
  52. data/lib/reports_kits/form_builder.rb +66 -0
  53. data/lib/reports_kits/helper.rb +24 -0
  54. data/lib/reports_kits/model.rb +16 -0
  55. data/lib/reports_kits/model_configuration.rb +28 -0
  56. data/lib/reports_kits/normalized_params.rb +16 -0
  57. data/lib/reports_kits/order.rb +33 -0
  58. data/lib/reports_kits/relative_time.rb +42 -0
  59. data/lib/reports_kits/report_builder.rb +88 -0
  60. data/lib/reports_kits/reports/abstract_series.rb +9 -0
  61. data/lib/reports_kits/reports/adapters/mysql.rb +26 -0
  62. data/lib/reports_kits/reports/adapters/postgresql.rb +26 -0
  63. data/lib/reports_kits/reports/composite_series.rb +48 -0
  64. data/lib/reports_kits/reports/contextual_filter.rb +19 -0
  65. data/lib/reports_kits/reports/data/add_table_aggregations.rb +105 -0
  66. data/lib/reports_kits/reports/data/aggregate_composite.rb +97 -0
  67. data/lib/reports_kits/reports/data/aggregate_one_dimension.rb +39 -0
  68. data/lib/reports_kits/reports/data/aggregate_two_dimensions.rb +39 -0
  69. data/lib/reports_kits/reports/data/chart_data_for_data_method.rb +62 -0
  70. data/lib/reports_kits/reports/data/chart_options.rb +155 -0
  71. data/lib/reports_kits/reports/data/format_one_dimension.rb +140 -0
  72. data/lib/reports_kits/reports/data/format_table.rb +63 -0
  73. data/lib/reports_kits/reports/data/format_two_dimensions.rb +143 -0
  74. data/lib/reports_kits/reports/data/generate.rb +156 -0
  75. data/lib/reports_kits/reports/data/generate_for_properties.rb +97 -0
  76. data/lib/reports_kits/reports/data/normalize_properties.rb +62 -0
  77. data/lib/reports_kits/reports/data/populate_one_dimension.rb +54 -0
  78. data/lib/reports_kits/reports/data/populate_two_dimensions.rb +104 -0
  79. data/lib/reports_kits/reports/data/utils.rb +178 -0
  80. data/lib/reports_kits/reports/dimension.rb +27 -0
  81. data/lib/reports_kits/reports/dimension_with_series.rb +144 -0
  82. data/lib/reports_kits/reports/filter.rb +29 -0
  83. data/lib/reports_kits/reports/filter_types/base.rb +48 -0
  84. data/lib/reports_kits/reports/filter_types/boolean.rb +47 -0
  85. data/lib/reports_kits/reports/filter_types/datetime.rb +51 -0
  86. data/lib/reports_kits/reports/filter_types/number.rb +30 -0
  87. data/lib/reports_kits/reports/filter_types/records.rb +26 -0
  88. data/lib/reports_kits/reports/filter_types/string.rb +38 -0
  89. data/lib/reports_kits/reports/filter_with_series.rb +97 -0
  90. data/lib/reports_kits/reports/generate_autocomplete_method_results.rb +29 -0
  91. data/lib/reports_kits/reports/generate_autocomplete_results.rb +57 -0
  92. data/lib/reports_kits/reports/inferrable_configuration.rb +116 -0
  93. data/lib/reports_kits/reports/model_settings.rb +30 -0
  94. data/lib/reports_kits/reports/properties.rb +10 -0
  95. data/lib/reports_kits/reports/properties_to_filter.rb +40 -0
  96. data/lib/reports_kits/reports/series.rb +121 -0
  97. data/lib/reports_kits/reports_controller.rb +65 -0
  98. data/lib/reports_kits/utils.rb +11 -0
  99. data/lib/reports_kits/value.rb +3 -0
  100. data/lib/reports_kits/version.rb +3 -0
  101. data/lib/reports_kits.rb +79 -0
  102. data/reports_kits.gemspec +26 -0
  103. data/spec/factories/issue_factory.rb +4 -0
  104. data/spec/factories/issues_label_factory.rb +4 -0
  105. data/spec/factories/label_factory.rb +4 -0
  106. data/spec/factories/pro_repo_factory.rb +5 -0
  107. data/spec/factories/repo_factory.rb +5 -0
  108. data/spec/factories/tag_factory.rb +4 -0
  109. data/spec/fixtures/generate_inputs.yml +254 -0
  110. data/spec/fixtures/generate_outputs.yml +1216 -0
  111. data/spec/reports_kit/form_builder_spec.rb +26 -0
  112. data/spec/reports_kit/relative_time_spec.rb +29 -0
  113. data/spec/reports_kit/reports/data/generate/contextual_filters_spec.rb +60 -0
  114. data/spec/reports_kit/reports/data/generate_spec.rb +1371 -0
  115. data/spec/reports_kit/reports/data/normalize_properties_spec.rb +196 -0
  116. data/spec/reports_kit/reports/dimension_with_series_spec.rb +67 -0
  117. data/spec/reports_kit/reports/filter_with_series_spec.rb +39 -0
  118. data/spec/reports_kit/reports/generate_autocomplete_results_spec.rb +69 -0
  119. data/spec/spec_helper.rb +77 -0
  120. data/spec/support/config.rb +41 -0
  121. data/spec/support/example_data_methods.rb +25 -0
  122. data/spec/support/factory_girl.rb +5 -0
  123. data/spec/support/helpers.rb +25 -0
  124. data/spec/support/models/issue.rb +14 -0
  125. data/spec/support/models/issues_label.rb +4 -0
  126. data/spec/support/models/label.rb +5 -0
  127. data/spec/support/models/pro/repo.rb +5 -0
  128. data/spec/support/models/pro/special_issue.rb +4 -0
  129. data/spec/support/models/repo.rb +13 -0
  130. data/spec/support/models/tag.rb +4 -0
  131. data/spec/support/schema.rb +39 -0
  132. 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