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.
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