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.



411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
# File 'lib/wingify/packages/segmentation_evaluator/evaluators/segment_operand_evaluator.rb', line 411

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.



164
165
166
# File 'lib/wingify/packages/segmentation_evaluator/evaluators/segment_operand_evaluator.rb', line 164

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



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
80
# File 'lib/wingify/packages/segmentation_evaluator/evaluators/segment_operand_evaluator.rb', line 38

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.



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

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.



172
173
174
# File 'lib/wingify/packages/segmentation_evaluator/evaluators/segment_operand_evaluator.rb', line 172

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.



130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
# File 'lib/wingify/packages/segmentation_evaluator/evaluators/segment_operand_evaluator.rb', line 130

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



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

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



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

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

#evaluate_web_testing_campaign_variation_dsl(dsl_operand_value, context) ⇒ Boolean

Evaluates Web Testing pre-segmentation against context.platform_variables[:webTestingCampaigns].

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



113
114
115
116
117
118
119
120
121
122
123
# File 'lib/wingify/packages/segmentation_evaluator/evaluators/segment_operand_evaluator.rb', line 113

def evaluate_web_testing_campaign_variation_dsl(dsl_operand_value, context)
  operand = dsl_operand_value.to_s.strip
  assigned_variations_by_campaign_id = WebTestingSegmentUtil.parse_web_testing_campaigns_from_context(context)
  result_obj = WebTestingSegmentUtil.evaluate_web_testing_campaign_variation(operand, assigned_variations_by_campaign_id)
  
  if result_obj[:invalid_format]
    return false
  end
  
  result_obj[:result]
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



298
299
300
301
# File 'lib/wingify/packages/segmentation_evaluator/evaluators/segment_operand_evaluator.rb', line 298

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



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
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
# File 'lib/wingify/packages/segmentation_evaluator/evaluators/segment_operand_evaluator.rb', line 354

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.



195
196
197
198
199
200
201
202
203
204
# File 'lib/wingify/packages/segmentation_evaluator/evaluators/segment_operand_evaluator.rb', line 195

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.



209
210
211
212
213
214
215
216
217
218
# File 'lib/wingify/packages/segmentation_evaluator/evaluators/segment_operand_evaluator.rb', line 209

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.



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

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.



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

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



255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
# File 'lib/wingify/packages/segmentation_evaluator/evaluators/segment_operand_evaluator.rb', line 255

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.



236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
# File 'lib/wingify/packages/segmentation_evaluator/evaluators/segment_operand_evaluator.rb', line 236

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.



308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
# File 'lib/wingify/packages/segmentation_evaluator/evaluators/segment_operand_evaluator.rb', line 308

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.



402
403
404
# File 'lib/wingify/packages/segmentation_evaluator/evaluators/segment_operand_evaluator.rb', line 402

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