Class: SegmentOperandEvaluator

Inherits:
Object
  • Object
show all
Defined in:
lib/wingify/packages/segmentation_evaluator/evaluators/segment_operand_evaluator.rb

Overview

SegmentOperandEvaluator class provides methods to evaluate different types of DSL (Domain Specific Language) expressions based on the segment conditions defined for custom variables, user IDs, and user agents.

Constant Summary collapse

NON_NUMERIC_PATTERN =

Regex pattern to check if a string contains non-numeric characters (except decimal point)

/[^0-9.]/

Instance Method Summary collapse

Instance Method Details

#compare_versions(version1, version2) ⇒ Integer

Compares two version strings using semantic versioning rules. Supports formats like “1.2.3”, “1.0”, “2.1.4.5”, etc.

Parameters:

  • version1 (String)

    First version string.

  • version2 (String)

    Second version string.

Returns:

  • (Integer)

    -1 if version1 < version2, 0 if equal, 1 if version1 > version2.



394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
# File 'lib/wingify/packages/segmentation_evaluator/evaluators/segment_operand_evaluator.rb', line 394

def compare_versions(version1, version2)
  # Split versions by dots and convert to integers
  parts1 = version1.split('.').map { |part| part.match?(/^\d+$/) ? part.to_i : 0 }
  parts2 = version2.split('.').map { |part| part.match?(/^\d+$/) ? part.to_i : 0 }

  # Find the maximum length to handle different version formats
  max_length = [parts1.length, parts2.length].max

  (0...max_length).each do |i|
    part1 = i < parts1.length ? parts1[i] : 0
    part2 = i < parts2.length ? parts2[i] : 0

    if part1 < part2
      return -1
    elsif part1 > part2
      return 1
    end
  end

  0 # Versions are equal
end

#evaluate_browser_version_dsl(dsl_operand_value, context) ⇒ Boolean

Evaluates browser version DSL expression.

Parameters:

  • dsl_operand_value (String)

    The DSL expression for the browser version.

  • context (ContextModel)

    The context object containing the user agent info.

Returns:

  • (Boolean)

    True if the browser version matches the DSL condition, otherwise false.



147
148
149
# File 'lib/wingify/packages/segmentation_evaluator/evaluators/segment_operand_evaluator.rb', line 147

def evaluate_browser_version_dsl(dsl_operand_value, context)
  evaluate_string_operand_dsl(dsl_operand_value, context, SegmentOperatorValueEnum::BROWSER_VERSION)
end

#evaluate_custom_variable_dsl(dsl_operand_value, properties) ⇒ Boolean

Evaluates the custom variable DSL

Parameters:

  • dsl_operand_value (Hash)

    The operand value to evaluate

  • properties (Hash)

    The properties to evaluate the operand against

Returns:

  • (Boolean)

    True if the operand value matches the tag value, false otherwise



37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
# File 'lib/wingify/packages/segmentation_evaluator/evaluators/segment_operand_evaluator.rb', line 37

def evaluate_custom_variable_dsl(dsl_operand_value, properties)
  key_value = get_key_value(dsl_operand_value)
  return false unless key_value

  operand_key = key_value[:key].to_sym
  operand = key_value[:value]

  return false unless properties.key?(operand_key)

  if operand.include?('inlist')
    match = operand.match(/inlist\(([^)]+)\)/)
    unless match
      LoggerService.log(LogLevelEnum::ERROR, "INVALID_ATTRIBUTE_LIST_FORMAT", { an: ApiEnum::GET_FLAG, sId: @context.get_session_id, uuid: @context.get_uuid})
      return false
    end

    tag_value = properties[operand_key]
    attribute_value = pre_process_tag_value(tag_value)
    list_id = match[1]

    query_params_obj = { attribute: attribute_value, listId: list_id }

    begin
      res = get_from_gateway_service(query_params_obj, UrlEnum::ATTRIBUTE_CHECK)
      if res.nil? || res == false || res == 'false' || (res.is_a?(Hash) && res[:status] == 0)
        return false
      end
      return res
    rescue StandardError => e
      LoggerService.log(LogLevelEnum::ERROR, "ERROR_FETCHING_DATA_FROM_GATEWAY", { err: e.message, an: ApiEnum::GET_FLAG, sId: @context.get_session_id, uuid: @context.get_uuid})
      return false
    end

    false
  else
    tag_value = properties[operand_key]
    tag_value = pre_process_tag_value(tag_value)
    processed_operand = pre_process_operand_value(operand)
    processed_values = process_values(processed_operand[:operand_value], tag_value)
    tag_value = processed_values[:tag_value]
    extract_result(processed_operand[:operand_type], processed_values[:operand_value], tag_value)
  end
end

#evaluate_ip_dsl(dsl_operand_value, context) ⇒ Boolean

Evaluates IP address DSL expression.

Parameters:

  • dsl_operand_value (String)

    The DSL expression for the IP address.

  • context (ContextModel)

    The context object containing the IP address.

Returns:

  • (Boolean)

    True if the IP address matches the DSL condition, otherwise false.



139
140
141
# File 'lib/wingify/packages/segmentation_evaluator/evaluators/segment_operand_evaluator.rb', line 139

def evaluate_ip_dsl(dsl_operand_value, context)
  evaluate_string_operand_dsl(dsl_operand_value, context, SegmentOperatorValueEnum::IP)
end

#evaluate_os_version_dsl(dsl_operand_value, context) ⇒ Boolean

Evaluates OS version DSL expression.

Parameters:

  • dsl_operand_value (String)

    The DSL expression for the OS version.

  • context (ContextModel)

    The context object containing the user agent info.

Returns:

  • (Boolean)

    True if the OS version matches the DSL condition, otherwise false.



155
156
157
# File 'lib/wingify/packages/segmentation_evaluator/evaluators/segment_operand_evaluator.rb', line 155

def evaluate_os_version_dsl(dsl_operand_value, context)
  evaluate_string_operand_dsl(dsl_operand_value, context, SegmentOperatorValueEnum::OS_VERSION)
end

#evaluate_string_operand_dsl(dsl_operand_value, context, operand_type) ⇒ Boolean

Evaluates a given string tag value against a DSL operand value.

Parameters:

  • dsl_operand_value (String)

    The DSL operand string (e.g., “contains("value")”).

  • context (ContextModel)

    The context object containing the value to evaluate.

  • operand_type (String)

    The type of operand being evaluated (ip_address, browser_version, os_version).

Returns:

  • (Boolean)

    True if tag value matches DSL operand criteria, false otherwise.



113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
# File 'lib/wingify/packages/segmentation_evaluator/evaluators/segment_operand_evaluator.rb', line 113

def evaluate_string_operand_dsl(dsl_operand_value, context, operand_type)
  operand = dsl_operand_value.to_s

  # Determine the tag value based on operand type
  tag_value = get_tag_value_for_operand_type(context, operand_type)

  if tag_value.nil?
    log_missing_context_error(operand_type)
    return false
  end

  operand_type_and_value = pre_process_operand_value(operand)
  processed_values = process_values(operand_type_and_value[:operand_value], tag_value, operand_type)
  processed_tag_value = processed_values[:tag_value]

  extract_result(
    operand_type_and_value[:operand_type],
    processed_values[:operand_value].to_s.strip.gsub(/"/, ''),
    processed_tag_value
  )
end

#evaluate_user_agent_dsl(dsl_operand_value, context) ⇒ Boolean

Evaluates the user agent DSL

Parameters:

  • dsl_operand_value (String)

    The operand value to evaluate

  • context (ContextModel)

    The context to evaluate the operand against

Returns:

  • (Boolean)

    True if the operand value matches the tag value, false otherwise



94
95
96
97
98
99
100
101
102
103
104
105
106
# File 'lib/wingify/packages/segmentation_evaluator/evaluators/segment_operand_evaluator.rb', line 94

def evaluate_user_agent_dsl(dsl_operand_value, context)
  operand = dsl_operand_value
  unless context.get_user_agent
    LoggerService.log(LogLevelEnum::INFO, 'To Evaluate UserAgent segmentation, please provide userAgent in context', nil)
    return false
  end

  tag_value = CGI.unescape(context.get_user_agent)
  processed_operand = pre_process_operand_value(operand)
  processed_values = process_values(processed_operand[:operand_value], tag_value)
  tag_value = processed_values[:tag_value]
  extract_result(processed_operand[:operand_type], processed_values[:operand_value], tag_value)
end

#evaluate_user_dsl(dsl_operand_value, properties) ⇒ Boolean

Evaluates the user DSL

Parameters:

  • dsl_operand_value (String)

    The operand value to evaluate

  • properties (Hash)

    The properties to evaluate the operand against

Returns:

  • (Boolean)

    True if the operand value matches the tag value, false otherwise



85
86
87
88
# File 'lib/wingify/packages/segmentation_evaluator/evaluators/segment_operand_evaluator.rb', line 85

def evaluate_user_dsl(dsl_operand_value, properties)
  users = dsl_operand_value.split(',')
  users.any? { |user| user.strip == properties["_vwoUserId"].to_s }
end

#extract_operand_value(operand, regex) ⇒ String

Extracts the operand value from the operand

Parameters:

  • operand (String)

    The operand to extract the value from

  • regex (String)

    The regex to match the operand against

Returns:

  • (String)

    The extracted operand value



281
282
283
284
# File 'lib/wingify/packages/segmentation_evaluator/evaluators/segment_operand_evaluator.rb', line 281

def extract_operand_value(operand, regex)
  match_result = match_with_regex(operand, regex)
  match_result && match_result[1] ? match_result[1] : ''
end

#extract_result(operand_type, operand_value, tag_value) ⇒ Boolean

Extracts the result from the operand value and tag value

Parameters:

  • operand_type (Symbol)

    The type of operand

  • operand_value (String)

    The value of the operand

  • tag_value (String)

    The value of the tag

Returns:

  • (Boolean)

    True if the operand value matches the tag value, false otherwise



337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
# File 'lib/wingify/packages/segmentation_evaluator/evaluators/segment_operand_evaluator.rb', line 337

def extract_result(operand_type, operand_value, tag_value)
  result = false

  return false if tag_value.nil?

  # Ensure operand_value and tag_value are strings
  operand_value_str = operand_value.to_s
  tag_value_str = tag_value.to_s

  case operand_type
  when SegmentOperandValueEnum::LOWER_VALUE
    result = operand_value_str.downcase == tag_value_str.downcase
  when SegmentOperandValueEnum::STARTING_ENDING_STAR_VALUE
    result = tag_value_str.include?(operand_value_str)
  when SegmentOperandValueEnum::STARTING_STAR_VALUE
    result = tag_value_str.end_with?(operand_value_str)
  when SegmentOperandValueEnum::ENDING_STAR_VALUE
    result = tag_value_str.start_with?(operand_value_str)
  when SegmentOperandValueEnum::REGEX_VALUE
    begin
      pattern = Regexp.new(operand_value_str)
      result = pattern.match?(tag_value_str)
    rescue StandardError
      result = false
    end
  when SegmentOperandValueEnum::GREATER_THAN_VALUE
    result = compare_versions(tag_value_str, operand_value_str) > 0
  when SegmentOperandValueEnum::GREATER_THAN_EQUAL_TO_VALUE
    result = compare_versions(tag_value_str, operand_value_str) >= 0
  when SegmentOperandValueEnum::LESS_THAN_VALUE
    result = compare_versions(tag_value_str, operand_value_str) < 0
  when SegmentOperandValueEnum::LESS_THAN_EQUAL_TO_VALUE
    result = compare_versions(tag_value_str, operand_value_str) <= 0
  else
    # For version-like strings, use version comparison; otherwise use string comparison
    if version_string?(tag_value_str) && version_string?(operand_value_str)
      result = compare_versions(tag_value_str, operand_value_str) == 0
    else
      result = tag_value_str == operand_value_str
    end
  end

  result
end

#get_browser_version_from_context(context) ⇒ String?

Gets browser version from context.

Parameters:

Returns:

  • (String, nil)

    The browser version or nil if not available.



178
179
180
181
182
183
184
185
186
187
# File 'lib/wingify/packages/segmentation_evaluator/evaluators/segment_operand_evaluator.rb', line 178

def get_browser_version_from_context(context)
  user_agent = context.get_vwo&.get_ua_info
  return nil unless user_agent && user_agent.is_a?(Hash) && !user_agent.empty?

  # Assuming UserAgent dictionary contains browser_version
  if user_agent.key?('browser_version')
    return user_agent['browser_version']&.to_s
  end
  nil
end

#get_os_version_from_context(context) ⇒ String?

Gets OS version from context.

Parameters:

Returns:

  • (String, nil)

    The OS version or nil if not available.



192
193
194
195
196
197
198
199
200
201
# File 'lib/wingify/packages/segmentation_evaluator/evaluators/segment_operand_evaluator.rb', line 192

def get_os_version_from_context(context)
  user_agent = context.get_vwo&.get_ua_info
  return nil unless user_agent && user_agent.is_a?(Hash) && !user_agent.empty?

  # Assuming UserAgent dictionary contains os_version
  if user_agent.key?('os_version')
    return user_agent['os_version']&.to_s
  end
  nil
end

#get_tag_value_for_operand_type(context, operand_type) ⇒ String?

Gets the appropriate tag value based on the operand type.

Parameters:

  • context (ContextModel)

    The context object.

  • operand_type (String)

    The type of operand.

Returns:

  • (String, nil)

    The tag value or nil if not available.



163
164
165
166
167
168
169
170
171
172
173
# File 'lib/wingify/packages/segmentation_evaluator/evaluators/segment_operand_evaluator.rb', line 163

def get_tag_value_for_operand_type(context, operand_type)
  case operand_type
  when SegmentOperatorValueEnum::IP
    context.get_ip_address
  when SegmentOperatorValueEnum::BROWSER_VERSION
    get_browser_version_from_context(context)
  else
    # Default works for OS version
    get_os_version_from_context(context)
  end
end

#log_missing_context_error(operand_type) ⇒ Object

Logs appropriate error message for missing context.

Parameters:

  • operand_type (String)

    The type of operand.



205
206
207
208
209
210
211
212
213
214
# File 'lib/wingify/packages/segmentation_evaluator/evaluators/segment_operand_evaluator.rb', line 205

def log_missing_context_error(operand_type)
  case operand_type
  when SegmentOperatorValueEnum::IP
    LoggerService.log(LogLevelEnum::INFO, 'To evaluate IP segmentation, please provide ipAddress in context', nil)
  when SegmentOperatorValueEnum::BROWSER_VERSION
    LoggerService.log(LogLevelEnum::INFO, 'To evaluate browser version segmentation, please provide userAgent in context', nil)
  else
    LoggerService.log(LogLevelEnum::INFO, 'To evaluate OS version segmentation, please provide userAgent in context', nil)
  end
end

#pre_process_operand_value(operand) ⇒ Hash

Pre-processes the operand value

Parameters:

  • operand (String)

    The operand to pre-process

Returns:

  • (Hash)

    The pre-processed operand value



238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
# File 'lib/wingify/packages/segmentation_evaluator/evaluators/segment_operand_evaluator.rb', line 238

def pre_process_operand_value(operand)
  case operand
  when /#{SegmentOperandRegexEnum::LOWER_MATCH}/
    { operand_type: SegmentOperandValueEnum::LOWER_VALUE, operand_value: extract_operand_value(operand, SegmentOperandRegexEnum::LOWER_MATCH) }
  when /#{SegmentOperandRegexEnum::WILDCARD_MATCH}/
    value = extract_operand_value(operand, SegmentOperandRegexEnum::WILDCARD_MATCH)
    starting_star = match_with_regex(value, SegmentOperandRegexEnum::STARTING_STAR)
    ending_star = match_with_regex(value, SegmentOperandRegexEnum::ENDING_STAR)
    
    # Determine specific wildcard type
    if starting_star && ending_star
      type = SegmentOperandValueEnum::STARTING_ENDING_STAR_VALUE
    elsif starting_star
      type = SegmentOperandValueEnum::STARTING_STAR_VALUE
    elsif ending_star
      type = SegmentOperandValueEnum::ENDING_STAR_VALUE
    end
    
    # Remove wildcard characters from the operand value
    value = value
      .gsub(Regexp.new(SegmentOperandRegexEnum::STARTING_STAR), '')
      .gsub(Regexp.new(SegmentOperandRegexEnum::ENDING_STAR), '')
    
    { operand_type: type, operand_value: value }
  when /#{SegmentOperandRegexEnum::REGEX_MATCH}/
    { operand_type: SegmentOperandValueEnum::REGEX_VALUE, operand_value: extract_operand_value(operand, SegmentOperandRegexEnum::REGEX_MATCH) }
  when /#{SegmentOperandRegexEnum::GREATER_THAN_MATCH}/
    { operand_type: SegmentOperandValueEnum::GREATER_THAN_VALUE, operand_value: extract_operand_value(operand, SegmentOperandRegexEnum::GREATER_THAN_MATCH) }
  when /#{SegmentOperandRegexEnum::LESS_THAN_MATCH}/
    { operand_type: SegmentOperandValueEnum::LESS_THAN_VALUE, operand_value: extract_operand_value(operand, SegmentOperandRegexEnum::LESS_THAN_MATCH) }
  when /#{SegmentOperandRegexEnum::GREATER_THAN_EQUAL_TO_MATCH}/
    { operand_type: SegmentOperandValueEnum::GREATER_THAN_EQUAL_TO_VALUE, operand_value: extract_operand_value(operand, SegmentOperandRegexEnum::GREATER_THAN_EQUAL_TO_MATCH) }
  when /#{SegmentOperandRegexEnum::LESS_THAN_EQUAL_TO_MATCH}/
    { operand_type: SegmentOperandValueEnum::LESS_THAN_EQUAL_TO_VALUE, operand_value: extract_operand_value(operand, SegmentOperandRegexEnum::LESS_THAN_EQUAL_TO_MATCH) }
  else
    { operand_type: SegmentOperandValueEnum::EQUAL_VALUE, operand_value: operand }
  end
end

#pre_process_tag_value(tag_value) ⇒ String, Boolean

Pre-processes the tag value to ensure it is in the correct format for evaluation.

Parameters:

  • tag_value (Any)

    The value to be processed.

Returns:

  • (String, Boolean)

    The processed tag value, either as a string or a boolean.



219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
# File 'lib/wingify/packages/segmentation_evaluator/evaluators/segment_operand_evaluator.rb', line 219

def pre_process_tag_value(tag_value)
  # Default to empty string if undefined
  if tag_value.nil?
    tag_value = ''
  end
  # Convert boolean values to boolean type
  if DataTypeUtil.is_boolean(tag_value)
    tag_value = tag_value ? true : false
  end
  # Convert all non-null values to string
  unless tag_value.nil?
    tag_value = tag_value.to_s
  end
  tag_value
end

#process_values(operand_value, tag_value, operand_type = nil) ⇒ Hash

Processes numeric values from operand and tag values, converting them to strings.

Parameters:

  • operand_value (Any)

    The operand value to process.

  • tag_value (Any)

    The tag value to process.

  • operand_type (String) (defaults to: nil)

    The type of operand being evaluated (optional).

Returns:

  • (Hash)

    An object containing the processed operand and tag values as strings.



291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
# File 'lib/wingify/packages/segmentation_evaluator/evaluators/segment_operand_evaluator.rb', line 291

def process_values(operand_value, tag_value, operand_type = nil)
  if [SegmentOperatorValueEnum::IP, SegmentOperatorValueEnum::BROWSER_VERSION, SegmentOperatorValueEnum::OS_VERSION].include?(operand_type)
    return {
      operand_value: operand_value,
      tag_value: tag_value
    }
  end

  # Convert operand and tag values to floats
  if NON_NUMERIC_PATTERN.match?(tag_value.to_s)
    return {
      operand_value: operand_value,
      tag_value: tag_value
    }
  end

  processed_operand_value = operand_value.to_f
  processed_tag_value = tag_value.to_f

  # Return original values if conversion fails
  if processed_operand_value == 0 && operand_value.to_s != '0' && operand_value.to_s != '0.0'
    return {
      operand_value: operand_value,
      tag_value: tag_value
    }
  end

  if processed_tag_value == 0 && tag_value.to_s != '0' && tag_value.to_s != '0.0'
    return {
      operand_value: operand_value,
      tag_value: tag_value
    }
  end

  # Convert numeric values back to strings
  {
    operand_value: processed_operand_value.to_s,
    tag_value: processed_tag_value.to_s
  }
end

#version_string?(str) ⇒ Boolean

Checks if a string appears to be a version string (contains only digits and dots).

Parameters:

  • str (String)

    The string to check.

Returns:

  • (Boolean)

    True if the string appears to be a version string.



385
386
387
# File 'lib/wingify/packages/segmentation_evaluator/evaluators/segment_operand_evaluator.rb', line 385

def version_string?(str)
  /^(\d+\.)*\d+$/.match?(str)
end