forest_liana 3.3.0 → 4.0.0.pre.beta.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d23c0e51c9b7da5f7e2b09400b9b2e76c5152cd6
4
- data.tar.gz: e0e7bee4717ba84f70c6c2603a4250b6b3671a2c
3
+ metadata.gz: dafadb3ed7cf1c34d0010729e459c13ba38e22de
4
+ data.tar.gz: 6dff3ee72a4aa231b68508e3d87b0931fb16d718
5
5
  SHA512:
6
- metadata.gz: 0f65c9b9cc4ee79d2df3bcb89cf549d1985d626a008ba7504459018414e30339b63f9769fbd828d8cf7e03f32bd1d92cec5ee4373ce46ba9a588e7319528cccf
7
- data.tar.gz: 4b91d89dcc180d49f38b73604e0c69fe052fc0ac10fc14cbda60ed6c8eb33c84e9736c917317d6f315649934cc3a0aa9d2177642d79a949e31d479809f25d71a
6
+ metadata.gz: 48338ecfc6319ca9ab824fa47771167e9203e3bbe567b3d38ff907b0ae067dc01f20e5830ed7079bdeb67115d54ede8570ce53125a738a016874b90bbc76979f
7
+ data.tar.gz: 052c0e82c436681276c2982480002adcc107b7e0b0018f1bd7056af1e6f6d5f3e281e9f9022d6b432b798224d08595bf9da81da04ab5c1fb52a87c2071c56d08
@@ -25,10 +25,11 @@ module ForestLiana
25
25
  # NOTICE: The Forest user email is returned to track changes made using
26
26
  # Forest with Papertrail.
27
27
  define_method :user_for_paper_trail do
28
- forest_user['data']['data']['email']
28
+ @jwt_decoded_token['email']
29
29
  end
30
30
  end
31
31
 
32
+ # NOTICE: Helper method for Smart Routes logic based on current user info.
32
33
  def forest_user
33
34
  @jwt_decoded_token
34
35
  end
@@ -69,7 +70,13 @@ module ForestLiana
69
70
 
70
71
  @jwt_decoded_token = JWT.decode(token, ForestLiana.auth_secret, true,
71
72
  { algorithm: 'HS256' }).try(:first)
72
- @rendering_id = @jwt_decoded_token['data']['relationships']['renderings']['data'][0]['id']
73
+
74
+ # NOTICE: Automatically logs out the users that use tokens having an old data format.
75
+ if @jwt_decoded_token['data']
76
+ raise ForestLiana::Errors::HTTP401Error.new("Your token format is invalid, please login again.")
77
+ end
78
+
79
+ @rendering_id = @jwt_decoded_token['rendering_id']
73
80
  else
74
81
  head :unauthorized
75
82
  end
@@ -0,0 +1,266 @@
1
+ module ForestLiana
2
+ class FiltersParser
3
+ AGGREGATOR_OPERATOR = %w(and or)
4
+
5
+ def initialize(filters, resource, timezone)
6
+ begin
7
+ @filters = JSON.parse(filters)
8
+ rescue JSON::ParserError
9
+ raise ForestLiana::Errors::HTTP422Error.new('Invalid filters JSON format')
10
+ end
11
+
12
+ @resource = resource
13
+ @operator_date_parser = OperatorDateIntervalParser.new(timezone)
14
+ @joins = []
15
+ end
16
+
17
+ def apply_filters
18
+ return @resource unless @filters
19
+
20
+ where = parse_aggregation(@filters)
21
+ return @resource unless where
22
+
23
+ @joins.each do |join|
24
+ current_resource = @resource.reflect_on_association(join.name).klass
25
+ current_resource.include(ArelHelpers::Aliases)
26
+ current_resource.aliased_as(join.name) do |aliased_resource|
27
+ @resource = @resource.joins(ArelHelpers.join_association(@resource, join.name, Arel::Nodes::OuterJoin, aliases: [aliased_resource]))
28
+ end
29
+ end
30
+
31
+ @resource.where(where)
32
+ end
33
+
34
+ def parse_aggregation(node)
35
+ ensure_valid_aggregation(node)
36
+
37
+ return parse_condition(node) unless node['aggregator']
38
+
39
+ conditions = []
40
+ node['conditions'].each do |condition|
41
+ conditions.push(parse_aggregation(condition))
42
+ end
43
+
44
+ operator = parse_aggregation_operator(node['aggregator'])
45
+
46
+ conditions.empty? ? nil : "(#{conditions.join(" #{operator} ")})"
47
+ end
48
+
49
+ def parse_condition(condition)
50
+ ensure_valid_condition(condition)
51
+
52
+ operator = condition['operator']
53
+ value = condition['value']
54
+ field = condition['field']
55
+
56
+ if @operator_date_parser.is_date_operator?(operator)
57
+ condition = @operator_date_parser.get_date_filter(operator, value)
58
+ return "#{parse_field_name(field)} #{condition}"
59
+ end
60
+
61
+ if is_belongs_to(field)
62
+ association = field.partition(':').first.to_sym
63
+ association_field = field.partition(':').last
64
+
65
+ unless @resource.reflect_on_association(association)
66
+ raise ForestLiana::Errors::HTTP422Error.new("Association '#{association}' not found")
67
+ end
68
+
69
+ current_resource = @resource.reflect_on_association(association).klass
70
+ else
71
+ association_field = field
72
+ current_resource = @resource
73
+ end
74
+
75
+ # NOTICE: Set the integer value instead of a string if "enum" type
76
+ # NOTICE: Rails 3 do not have a defined_enums method
77
+ if current_resource.respond_to?(:defined_enums) && current_resource.defined_enums.has_key?(association_field)
78
+ value = current_resource.defined_enums[association_field][value]
79
+ end
80
+
81
+ parsed_field = parse_field_name(field)
82
+ parsed_operator = parse_operator(operator)
83
+ parsed_value = parse_value(operator, value)
84
+
85
+ if Rails::VERSION::MAJOR >= 5
86
+ ActiveRecord::Base.sanitize_sql(["#{parsed_field} #{parsed_operator} ?", parsed_value])
87
+ else
88
+ "#{parsed_field} #{parsed_operator} #{ActiveRecord::Base.sanitize(parsed_value)}"
89
+ end
90
+ end
91
+
92
+ def parse_aggregation_operator(aggregator_operator)
93
+ unless AGGREGATOR_OPERATOR.include?(aggregator_operator)
94
+ raise_unknown_operator_error(aggregator_operator)
95
+ end
96
+
97
+ aggregator_operator.upcase
98
+ end
99
+
100
+ def parse_operator(operator)
101
+ case operator
102
+ when 'not'
103
+ 'NOT'
104
+ when 'greater_than', 'after'
105
+ '>'
106
+ when 'less_than', 'before'
107
+ '<'
108
+ when 'contains', 'starts_with', 'ends_with'
109
+ 'LIKE'
110
+ when 'not_contains'
111
+ 'NOT LIKE'
112
+ when 'not_equal'
113
+ '!='
114
+ when 'equal'
115
+ '='
116
+ when 'blank'
117
+ 'IS'
118
+ when 'present'
119
+ 'IS NOT'
120
+ else
121
+ raise_unknown_operator_error(operator)
122
+ end
123
+ end
124
+
125
+ def parse_value(operator, value)
126
+ case operator
127
+ when 'not', 'greater_than', 'less_than', 'not_equal', 'equal', 'before', 'after'
128
+ value
129
+ when 'contains', 'not_contains'
130
+ "%#{value}%"
131
+ when 'starts_with'
132
+ "#{value}%"
133
+ when 'ends_with'
134
+ "%#{value}"
135
+ when 'present', 'blank'
136
+ else
137
+ raise_unknown_operator_error(operator)
138
+ end
139
+ end
140
+
141
+ def parse_field_name(field)
142
+ if is_belongs_to(field)
143
+ current_resource = @resource.reflect_on_association(field.split(':').first.to_sym)&.klass
144
+ raise ForestLiana::Errors::HTTP422Error.new("Field '#{field}' not found") unless current_resource
145
+
146
+ association = get_association_name_for_condition(field)
147
+ quoted_table_name = ActiveRecord::Base.connection.quote_column_name(association)
148
+ quoted_field_name = ActiveRecord::Base.connection.quote_column_name(field.split(':')[1])
149
+ else
150
+ quoted_table_name = @resource.quoted_table_name
151
+ quoted_field_name = ActiveRecord::Base.connection.quote_column_name(field)
152
+ current_resource = @resource
153
+ end
154
+
155
+ column_found = current_resource.columns.find { |column| column.name == field.split(':').last }
156
+
157
+ if column_found.nil?
158
+ raise ForestLiana::Errors::HTTP422Error.new("Field '#{field}' not found")
159
+ end
160
+
161
+ "#{quoted_table_name}.#{quoted_field_name}"
162
+ end
163
+
164
+ def is_belongs_to(field)
165
+ field.include?(':')
166
+ end
167
+
168
+ def get_association_name_for_condition(field)
169
+ field, subfield = field.split(':')
170
+
171
+ association = @resource.reflect_on_association(field.to_sym)
172
+ return nil if association.blank?
173
+
174
+ @joins << association unless @joins.include? association
175
+
176
+ association.name
177
+ end
178
+
179
+ # NOTICE: Look for a previous interval condition matching the following:
180
+ # - If the filter is a simple condition at the root the check is done right away.
181
+ # - There can't be a previous interval condition if the aggregator is 'or' (no meaning).
182
+ # - The condition's operator has to be elligible for a previous interval.
183
+ # - There can't be two previous interval condition.
184
+ def get_previous_interval_condition
185
+ current_previous_interval = nil
186
+ # NOTICE: Leaf condition at root
187
+ unless @filters['aggregator']
188
+ return @filters if @operator_date_parser.has_previous_interval?(@filters['operator'])
189
+ end
190
+
191
+ if @filters['aggregator'] === 'and'
192
+ @filters['conditions'].each do |condition|
193
+ # NOTICE: Nested conditions
194
+ return nil if condition['aggregator']
195
+
196
+ if @operator_date_parser.has_previous_interval?(condition['operator'])
197
+ # NOTICE: There can't be two previous_interval.
198
+ return nil if current_previous_interval
199
+
200
+ current_previous_interval = condition
201
+ end
202
+ end
203
+ end
204
+
205
+ current_previous_interval
206
+ end
207
+
208
+ def apply_filters_on_previous_interval(previous_condition)
209
+ # Ressource should have already been joined
210
+ where = parse_aggregation_on_previous_interval(@filters, previous_condition)
211
+
212
+ @resource.where(where)
213
+ end
214
+
215
+ def parse_aggregation_on_previous_interval(node, previous_condition)
216
+ raise_empty_condition_in_filter_error unless node
217
+
218
+ return parse_previous_interval_condition(node) unless node['aggregator']
219
+
220
+ conditions = []
221
+ node['conditions'].each do |condition|
222
+ if condition == previous_condition
223
+ conditions.push(parse_previous_interval_condition(condition))
224
+ else
225
+ conditions.push(parse_aggregation(condition))
226
+ end
227
+ end
228
+
229
+ operator = parse_aggregation_operator(node['aggregator'])
230
+
231
+ conditions.empty? ? nil : "(#{conditions.join(" #{operator} ")})"
232
+ end
233
+
234
+ def parse_previous_interval_condition(condition)
235
+ raise_empty_condition_in_filter_error unless condition
236
+
237
+ parsed_condition = @operator_date_parser.get_date_filter_for_previous_interval(
238
+ condition['operator'],
239
+ condition['value']
240
+ )
241
+
242
+ "#{parse_field_name(condition['field'])} #{parsed_condition}"
243
+ end
244
+
245
+ def raise_unknown_operator_error(operator)
246
+ raise ForestLiana::Errors::HTTP422Error.new("Unknown provided operator '#{operator}'")
247
+ end
248
+
249
+ def raise_empty_condition_in_filter_error
250
+ raise ForestLiana::Errors::HTTP422Error.new('Empty condition in filter')
251
+ end
252
+
253
+ def ensure_valid_aggregation(node)
254
+ raise ForestLiana::Errors::HTTP422Error.new('Filters cannot be a raw value') unless node.is_a?(Hash)
255
+ raise_empty_condition_in_filter_error if node.empty?
256
+ end
257
+
258
+ def ensure_valid_condition(condition)
259
+ raise_empty_condition_in_filter_error if condition.empty?
260
+ raise ForestLiana::Errors::HTTP422Error.new('Condition cannot be a raw value') unless condition.is_a?(Hash)
261
+ unless condition['field'].is_a?(String) and condition['operator'].is_a?(String)
262
+ raise ForestLiana::Errors::HTTP422Error.new('Invalid condition format')
263
+ end
264
+ end
265
+ end
266
+ end
@@ -22,17 +22,8 @@ module ForestLiana
22
22
  def perform
23
23
  value = get_resource().eager_load(@includes)
24
24
 
25
- if @params[:filterType] && @params[:filters]
26
- conditions = []
27
- filter_operator = " #{@params[:filterType]} ".upcase
28
-
29
- @params[:filters].try(:each) do |filter|
30
- operator, filter_value = OperatorValueParser.parse(filter[:value])
31
- conditions << OperatorValueParser.get_condition(filter[:field],
32
- operator, filter_value, @resource, @params[:timezone])
33
- end
34
-
35
- value = value.where(conditions.join(filter_operator))
25
+ if @params[:filters]
26
+ value = FiltersParser.new(@params[:filters], value, @params[:timezone]).apply_filters
36
27
  end
37
28
 
38
29
  value = value.send(time_range, group_by_date_field, {
@@ -94,25 +94,13 @@ module ForestLiana
94
94
 
95
95
  def create_token(user, rendering_id)
96
96
  JWT.encode({
97
- exp: Time.now.to_i + 2.weeks.to_i,
98
- data: {
99
- id: user['id'],
100
- type: 'users',
101
- data: {
102
- email: user['email'],
103
- first_name: user['first_name'],
104
- last_name: user['last_name'],
105
- teams: user['teams']
106
- },
107
- relationships: {
108
- renderings: {
109
- data: [{
110
- type: 'renderings',
111
- id: rendering_id
112
- }]
113
- }
114
- }
115
- }
97
+ id: user['id'],
98
+ email: user['email'],
99
+ first_name: user['first_name'],
100
+ last_name: user['last_name'],
101
+ team: user['teams'][0],
102
+ rendering_id: rendering_id,
103
+ exp: Time.now.to_i + 2.weeks.to_i
116
104
  }, ForestLiana.auth_secret, 'HS256')
117
105
  end
118
106
  end
@@ -1,67 +1,68 @@
1
1
  module ForestLiana
2
2
  class OperatorDateIntervalParser
3
+ OPERATOR_PAST = 'past'
4
+ OPERATOR_FUTURE = 'future'
5
+ OPERATOR_TODAY = 'today'
6
+
7
+ OPERATOR_YESTERDAY = 'yesterday'
8
+ OPERATOR_PREVIOUS_WEEK = 'previous_week'
9
+ OPERATOR_PREVIOUS_MONTH = 'previous_month'
10
+ OPERATOR_PREVIOUS_QUARTER = 'previous_quarter'
11
+ OPERATOR_PREVIOUS_YEAR = 'previous_year'
12
+ OPERATOR_PREVIOUS_WEEK_TO_DATE = 'previous_week_to_date'
13
+ OPERATOR_PREVIOUS_MONTH_TO_DATE = 'previous_month_to_date'
14
+ OPERATOR_PREVIOUS_QUARTER_TO_DATE = 'previous_quarter_to_date'
15
+ OPERATOR_PREVIOUS_YEAR_TO_DATE = 'previous_year_to_date'
16
+
17
+ OPERATOR_PREVIOUS_X_DAYS = 'previous_x_days'
18
+ OPERATOR_PREVIOUS_X_DAYS_TO_DATE = 'previous_x_days_to_date'
19
+ OPERATOR_BEFORE_X_HOURS_AGO = 'before_x_hours_ago'
20
+ OPERATOR_AFTER_X_HOURS_AGO = 'after_x_hours_ago'
21
+
3
22
  PERIODS = {
4
- :$yesterday => { duration: 1, period: 'day' },
5
- :$previousWeek => { duration: 1, period: 'week' },
6
- :$previousMonth => { duration: 1, period: 'month' },
7
- :$previousQuarter => { duration: 3, period: 'month',
8
- period_of_time: 'quarter' },
9
- :$previousYear => { duration: 1, period: 'year' },
10
- :$weekToDate => { duration: 1, period: 'week', to_date: true },
11
- :$monthToDate => { duration: 1, period: 'month', to_date: true },
12
- :$quarterToDate => { duration: 3, period: 'month',
13
- period_of_time: 'quarter', to_date: true },
14
- :$yearToDate => { duration: 1, period: 'year', to_date: true }
23
+ OPERATOR_YESTERDAY => { duration: 1, period: 'day' },
24
+ OPERATOR_PREVIOUS_WEEK => { duration: 1, period: 'week' },
25
+ OPERATOR_PREVIOUS_WEEK_TO_DATE => { duration: 1, period: 'week', to_date: true },
26
+ OPERATOR_PREVIOUS_MONTH => { duration: 1, period: 'month' },
27
+ OPERATOR_PREVIOUS_MONTH_TO_DATE => { duration: 1, period: 'month', to_date: true },
28
+ OPERATOR_PREVIOUS_QUARTER => { duration: 3, period: 'month', period_of_time: 'quarter' },
29
+ OPERATOR_PREVIOUS_QUARTER_TO_DATE => { duration: 3, period: 'month', period_of_time: 'quarter', to_date: true },
30
+ OPERATOR_PREVIOUS_YEAR => { duration: 1, period: 'year' },
31
+ OPERATOR_PREVIOUS_YEAR_TO_DATE => { duration: 1, period: 'year', to_date: true }
15
32
  }
16
33
 
17
- PERIODS_PAST = '$past';
18
- PERIODS_FUTURE = '$future';
19
- PERIODS_TODAY = '$today';
20
-
21
- PERIODS_PREVIOUS_X_DAYS = /^\$previous(\d+)Days$/;
22
- PERIODS_X_DAYS_TO_DATE = /^\$(\d+)DaysToDate$/;
23
- PERIODS_X_HOURS_BEFORE = /^\$(\d+)HoursBefore$/;
24
- PERIODS_X_HOURS_AFTER = /^\$(\d+)HoursAfter$/;
25
-
26
- def initialize(value, timezone)
27
- @value = value
34
+ DATE_OPERATORS_HAVING_PREVIOUS_INTERVAL = [
35
+ OPERATOR_TODAY,
36
+ OPERATOR_YESTERDAY,
37
+ OPERATOR_PREVIOUS_WEEK,
38
+ OPERATOR_PREVIOUS_MONTH,
39
+ OPERATOR_PREVIOUS_QUARTER,
40
+ OPERATOR_PREVIOUS_YEAR,
41
+ OPERATOR_PREVIOUS_WEEK_TO_DATE,
42
+ OPERATOR_PREVIOUS_MONTH_TO_DATE,
43
+ OPERATOR_PREVIOUS_QUARTER_TO_DATE,
44
+ OPERATOR_PREVIOUS_YEAR_TO_DATE,
45
+ OPERATOR_PREVIOUS_X_DAYS,
46
+ OPERATOR_PREVIOUS_X_DAYS_TO_DATE
47
+ ]
48
+
49
+ DATE_OPERATORS = DATE_OPERATORS_HAVING_PREVIOUS_INTERVAL.concat [
50
+ OPERATOR_FUTURE,
51
+ OPERATOR_PAST,
52
+ OPERATOR_BEFORE_X_HOURS_AGO,
53
+ OPERATOR_AFTER_X_HOURS_AGO
54
+ ]
55
+
56
+ def initialize(timezone)
28
57
  @timezone_offset = Time.now.in_time_zone(timezone).utc_offset / 3600
29
58
  end
30
59
 
31
- def is_interval_date_value
32
- return false if @value.nil?
33
- return true if PERIODS[@value.to_sym]
34
-
35
- return true if [PERIODS_PAST, PERIODS_FUTURE, PERIODS_TODAY].include? @value
36
-
37
- match = PERIODS_PREVIOUS_X_DAYS.match(@value)
38
- return true if match && match[1]
39
-
40
- match = PERIODS_X_DAYS_TO_DATE.match(@value)
41
- return true if match && match[1]
42
-
43
- match = PERIODS_X_HOURS_BEFORE.match(@value)
44
- return true if match && match[1]
45
-
46
- match = PERIODS_X_HOURS_AFTER.match(@value)
47
- return true if match && match[1]
48
-
49
- false
60
+ def is_date_operator?(operator)
61
+ DATE_OPERATORS.include? operator
50
62
  end
51
63
 
52
- def has_previous_interval
53
- return false if @value.nil?
54
- return true if PERIODS[@value.to_sym]
55
-
56
- return true if PERIODS_TODAY == @value
57
-
58
- match = PERIODS_PREVIOUS_X_DAYS.match(@value)
59
- return true if match && match[1]
60
-
61
- match = PERIODS_X_DAYS_TO_DATE.match(@value)
62
- return true if match && match[1]
63
-
64
- false
64
+ def has_previous_interval?(operator)
65
+ DATE_OPERATORS_HAVING_PREVIOUS_INTERVAL.include? operator
65
66
  end
66
67
 
67
68
  def to_client_timezone(date)
@@ -69,46 +70,39 @@ module ForestLiana
69
70
  date - @timezone_offset.hours
70
71
  end
71
72
 
72
- def get_interval_date_filter
73
- return nil unless is_interval_date_value()
74
-
75
- return ">= '#{Time.now}'" if @value == PERIODS_FUTURE
76
- return "<= '#{Time.now}'" if @value == PERIODS_PAST
73
+ def get_date_filter(operator, value)
74
+ return nil unless is_date_operator? operator
77
75
 
78
- if @value == PERIODS_TODAY
76
+ case operator
77
+ when OPERATOR_FUTURE
78
+ return ">= '#{Time.now}'"
79
+ when OPERATOR_PAST
80
+ return "<= '#{Time.now}'"
81
+ when OPERATOR_TODAY
79
82
  return "BETWEEN '#{to_client_timezone(Time.now.beginning_of_day)}' " +
80
83
  "AND '#{to_client_timezone(Time.now.end_of_day)}'"
81
- end
82
-
83
- match = PERIODS_PREVIOUS_X_DAYS.match(@value)
84
- if match && match[1]
84
+ when OPERATOR_PREVIOUS_X_DAYS
85
+ ensure_integer_value(value)
85
86
  return "BETWEEN '" +
86
- "#{to_client_timezone(Integer(match[1]).day.ago.beginning_of_day)}'" +
87
+ "#{to_client_timezone(Integer(value).day.ago.beginning_of_day)}'" +
87
88
  " AND '#{to_client_timezone(1.day.ago.end_of_day)}'"
88
- end
89
-
90
- match = PERIODS_X_DAYS_TO_DATE.match(@value)
91
- if match && match[1]
89
+ when OPERATOR_PREVIOUS_X_DAYS_TO_DATE
90
+ ensure_integer_value(value)
92
91
  return "BETWEEN '" +
93
- "#{to_client_timezone((Integer(match[1]) - 1).day.ago.beginning_of_day)}'" +
92
+ "#{to_client_timezone((Integer(value) - 1).day.ago.beginning_of_day)}'" +
94
93
  " AND '#{Time.now}'"
94
+ when OPERATOR_BEFORE_X_HOURS_AGO
95
+ ensure_integer_value(value)
96
+ return "< '#{to_client_timezone((Integer(value)).hour.ago)}'"
97
+ when OPERATOR_AFTER_X_HOURS_AGO
98
+ ensure_integer_value(value)
99
+ return "> '#{to_client_timezone((Integer(value)).hour.ago)}'"
95
100
  end
96
101
 
97
- match = PERIODS_X_HOURS_BEFORE.match(@value)
98
- if match && match[1]
99
- return "< '#{to_client_timezone((Integer(match[1])).hour.ago)}'"
100
- end
101
-
102
- match = PERIODS_X_HOURS_AFTER.match(@value)
103
- if match && match[1]
104
- return "> '#{to_client_timezone((Integer(match[1])).hour.ago)}'"
105
- end
106
-
107
- duration = PERIODS[@value.to_sym][:duration]
108
- period = PERIODS[@value.to_sym][:period]
109
- period_of_time = PERIODS[@value.to_sym][:period_of_time] ||
110
- PERIODS[@value.to_sym][:period]
111
- to_date = PERIODS[@value.to_sym][:to_date]
102
+ duration = PERIODS[operator][:duration]
103
+ period = PERIODS[operator][:period]
104
+ period_of_time = PERIODS[operator][:period_of_time] || period
105
+ to_date = PERIODS[operator][:to_date]
112
106
 
113
107
  if to_date
114
108
  from = to_client_timezone(Time.now.send("beginning_of_#{period_of_time}"))
@@ -122,45 +116,47 @@ module ForestLiana
122
116
  "BETWEEN '#{from}' AND '#{to}'"
123
117
  end
124
118
 
125
- def get_interval_date_filter_for_previous_interval
126
- return nil unless has_previous_interval()
119
+ def get_date_filter_for_previous_interval(operator, value)
120
+ return nil unless has_previous_interval? operator
127
121
 
128
- if @value == PERIODS_TODAY
122
+ case operator
123
+ when OPERATOR_TODAY
129
124
  return "BETWEEN '#{to_client_timezone(1.day.ago.beginning_of_day)}' AND " +
130
125
  "'#{to_client_timezone(1.day.ago.end_of_day)}'"
131
- end
132
-
133
- match = PERIODS_PREVIOUS_X_DAYS.match(@value)
134
- if match && match[1]
126
+ when OPERATOR_PREVIOUS_X_DAYS
127
+ ensure_integer_value(value)
135
128
  return "BETWEEN '" +
136
- "#{to_client_timezone((Integer(match[1]) * 2).day.ago.beginning_of_day)}'" +
137
- " AND '#{to_client_timezone((Integer(match[1]) + 1).day.ago.end_of_day)}'"
138
- end
139
-
140
- match = PERIODS_X_DAYS_TO_DATE.match(@value)
141
- if match && match[1]
129
+ "#{to_client_timezone((Integer(value) * 2).day.ago.beginning_of_day)}'" +
130
+ " AND '#{to_client_timezone((Integer(value) + 1).day.ago.end_of_day)}'"
131
+ when OPERATOR_PREVIOUS_X_DAYS_TO_DATE
132
+ ensure_integer_value(value)
142
133
  return "BETWEEN '" +
143
- "#{to_client_timezone(((Integer(match[1]) * 2) - 1).day.ago.beginning_of_day)}'" +
144
- " AND '#{to_client_timezone(Integer(match[1]).day.ago)}'"
134
+ "#{to_client_timezone(((Integer(value) * 2) - 1).day.ago.beginning_of_day)}'" +
135
+ " AND '#{to_client_timezone(Integer(value).day.ago)}'"
145
136
  end
146
137
 
147
- duration = PERIODS[@value.to_sym][:duration]
148
- period = PERIODS[@value.to_sym][:period]
149
- period_of_time = PERIODS[@value.to_sym][:period_of_time] ||
150
- PERIODS[@value.to_sym][:period]
151
- to_date = PERIODS[@value.to_sym][:to_date]
138
+ duration = PERIODS[operator][:duration]
139
+ period = PERIODS[operator][:period]
140
+ period_of_time = PERIODS[operator][:period_of_time] || period
141
+ to_date = PERIODS[operator][:to_date]
152
142
 
153
143
  if to_date
154
- from = to_client_timezone((duration).send(period).ago
155
- .send("beginning_of_#{period_of_time}"))
144
+ from = to_client_timezone((duration)
145
+ .send(period).ago.send("beginning_of_#{period_of_time}"))
156
146
  to = to_client_timezone((duration).send(period).ago)
157
147
  else
158
148
  from = to_client_timezone((duration * 2).send(period).ago
159
- .send("beginning_of_#{period_of_time}"))
149
+ .send("beginning_of_#{period_of_time}"))
160
150
  to = to_client_timezone((1 + duration).send(period).ago
161
- .send("end_of_#{period_of_time}"))
151
+ .send("end_of_#{period_of_time}"))
162
152
  end
163
153
  "BETWEEN '#{from}' AND '#{to}'"
164
154
  end
155
+
156
+ def ensure_integer_value(value)
157
+ unless value.is_a?(Integer) || /\A[-+]?\d+\z/.match(value)
158
+ raise ForestLiana::Errors::HTTP422Error.new('\'value\' should be an Integer')
159
+ end
160
+ end
165
161
  end
166
162
  end