vwo-fme-ruby-sdk 1.5.0 → 1.6.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
  SHA256:
3
- metadata.gz: 070ef4b08bf92bddf7d91a7a520a570651ed5c79c2d792a73021889803589f46
4
- data.tar.gz: 5a6bb42880b3d4e73da654d33c78e05566bd487c15205918bef6020447793454
3
+ metadata.gz: aba596eb41128d18fecdbac11316dfe345d0fdcd2b8c1a0a612b1145bde5b779
4
+ data.tar.gz: 0ca14ec46f853f5cd86171ee4a0a7c93fb57f009872f847e2777fc3375e01cd6
5
5
  SHA512:
6
- metadata.gz: 5f48860c62dd6c81743ee2647fe7144ba503bff54fc68f4ddb8619bde4ff698b13ba46db401f1ea40abffd30b4909b8ca0a42af628b9b2b71670eef15f54b215
7
- data.tar.gz: dd3eaf38a66d744dd3e9f0b553e707610ae1b130980b1359eca5b9ae9ee70f063cfe4dd3a1c43a397676d52a2289e3770638ab1b076cdfc769b5bc72f5bb3f63
6
+ metadata.gz: 398d9be0a02c8b364712eb1b1ef38061138ecfcd71a8ec4f8ec5a6ad5d7ef1b0a6ee678f8ed1cb057bc6db337baf885cd3852c0ad935d480d5cd705ab7e2768c
7
+ data.tar.gz: ae7f284247ccc40c0c881dcd7ab23665989b71f3252931e755417a428cc606cf5e7af091cac9a28f20d274c5a782afab095633635ced57a23879b965579d56a6
@@ -17,7 +17,7 @@
17
17
  # Define the Constants module
18
18
  module Constants
19
19
  SDK_NAME = 'vwo-fme-ruby-sdk'.freeze
20
- SDK_VERSION = '1.5.0'.freeze
20
+ SDK_VERSION = '1.6.0'.freeze
21
21
 
22
22
  MAX_TRAFFIC_PERCENT = 100
23
23
  MAX_TRAFFIC_VALUE = 10_000
@@ -15,14 +15,15 @@
15
15
  require_relative './context_vwo_model'
16
16
 
17
17
  class ContextModel
18
- attr_accessor :id, :user_agent, :ip_address, :custom_variables, :variation_targeting_variables, :vwo
18
+ attr_accessor :id, :user_agent, :ip_address, :custom_variables, :variation_targeting_variables, :post_segmentation_variables, :vwo
19
19
 
20
- def initialize(id = nil, user_agent = nil, ip_address = nil, custom_variables = {}, variation_targeting_variables = {}, vwo = nil)
20
+ def initialize(id = nil, user_agent = nil, ip_address = nil, custom_variables = {}, variation_targeting_variables = {}, post_segmentation_variables = {}, vwo = nil)
21
21
  @id = id
22
22
  @user_agent = user_agent
23
23
  @ip_address = ip_address
24
24
  @custom_variables = custom_variables || {}
25
25
  @variation_targeting_variables = variation_targeting_variables || {}
26
+ @post_segmentation_variables = post_segmentation_variables || {}
26
27
  @vwo = vwo
27
28
  end
28
29
 
@@ -45,6 +46,7 @@ class ContextModel
45
46
  @ip_address = context[:ipAddress]
46
47
  @custom_variables = context[:customVariables] if context.key?(:customVariables)
47
48
  @variation_targeting_variables = context[:variationTargetingVariables] if context.key?(:variationTargetingVariables)
49
+ @post_segmentation_variables = context[:postSegmentationVariables] if context.key?(:postSegmentationVariables)
48
50
  @vwo = ContextVWOModel.new.model_from_dictionary(context[:_vwo]) if context.key?(:_vwo)
49
51
 
50
52
  self
@@ -90,6 +92,14 @@ class ContextModel
90
92
  @variation_targeting_variables = variation_targeting_variables
91
93
  end
92
94
 
95
+ def get_post_segmentation_variables
96
+ @post_segmentation_variables
97
+ end
98
+
99
+ def set_post_segmentation_variables(post_segmentation_variables)
100
+ @post_segmentation_variables = post_segmentation_variables
101
+ end
102
+
93
103
  def get_vwo
94
104
  @vwo
95
105
  end
@@ -21,9 +21,9 @@ module SegmentOperandRegexEnum
21
21
  REGEX_MATCH = '^regex\\((.*)\\)'
22
22
  STARTING_STAR = '^\\*'
23
23
  ENDING_STAR = '\\*$'
24
- GREATER_THAN_MATCH = '^gt\\((\\d+\\.?\\d*|\\.\\d+)\\)'
25
- GREATER_THAN_EQUAL_TO_MATCH = '^gte\\((\\d+\\.?\\d*|\\.\\d+)\\)'
26
- LESS_THAN_MATCH = '^lt\\((\\d+\\.?\\d*|\\.\\d+)\\)'
27
- LESS_THAN_EQUAL_TO_MATCH = '^lte\\((\\d+\\.?\\d*|\\.\\d+)\\)'
24
+ GREATER_THAN_MATCH = '^gt\\((\\d+(?:\\.\\d+)*|\\.\\d+)\\)'
25
+ GREATER_THAN_EQUAL_TO_MATCH = '^gte\\((\\d+(?:\\.\\d+)*|\\.\\d+)\\)'
26
+ LESS_THAN_MATCH = '^lt\\((\\d+(?:\\.\\d+)*|\\.\\d+)\\)'
27
+ LESS_THAN_EQUAL_TO_MATCH = '^lte\\((\\d+(?:\\.\\d+)*|\\.\\d+)\\)'
28
28
  end
29
29
 
@@ -27,4 +27,7 @@ module SegmentOperatorValueEnum
27
27
  BROWSER_AGENT = 'browser_string'
28
28
  UA = 'ua'
29
29
  FEATURE_ID = 'featureId'
30
+ IP = 'ip_address'
31
+ BROWSER_VERSION = 'browser_version'
32
+ OS_VERSION = 'os_version'
30
33
  end
@@ -57,6 +57,12 @@ class SegmentEvaluator
57
57
  SegmentOperandEvaluator.new.evaluate_user_dsl(sub_dsl, properties)
58
58
  when SegmentOperatorValueEnum::UA
59
59
  SegmentOperandEvaluator.new.evaluate_user_agent_dsl(sub_dsl, @context)
60
+ when SegmentOperatorValueEnum::IP
61
+ SegmentOperandEvaluator.new.evaluate_string_operand_dsl(sub_dsl, @context, SegmentOperatorValueEnum::IP)
62
+ when SegmentOperatorValueEnum::BROWSER_VERSION
63
+ SegmentOperandEvaluator.new.evaluate_string_operand_dsl(sub_dsl, @context, SegmentOperatorValueEnum::BROWSER_VERSION)
64
+ when SegmentOperatorValueEnum::OS_VERSION
65
+ SegmentOperandEvaluator.new.evaluate_string_operand_dsl(sub_dsl, @context, SegmentOperatorValueEnum::OS_VERSION)
60
66
  else
61
67
  false
62
68
  end
@@ -15,15 +15,22 @@
15
15
  require_relative '../utils/segment_util'
16
16
  require_relative '../enums/segment_operand_value_enum'
17
17
  require_relative '../enums/segment_operand_regex_enum'
18
+ require_relative '../enums/segment_operator_value_enum'
18
19
  require_relative '../../../utils/data_type_util'
19
20
  require_relative '../../../utils/gateway_service_util'
20
21
  require_relative '../../../enums/url_enum'
21
22
  require_relative '../../../services/logger_service'
22
23
  require_relative '../../../models/user/context_model'
23
24
  require_relative '../../../enums/log_level_enum'
25
+
26
+ # SegmentOperandEvaluator class provides methods to evaluate different types of DSL (Domain Specific Language)
27
+ # expressions based on the segment conditions defined for custom variables, user IDs, and user agents.
24
28
  class SegmentOperandEvaluator
29
+ # Regex pattern to check if a string contains non-numeric characters (except decimal point)
30
+ NON_NUMERIC_PATTERN = /[^0-9.]/
31
+
25
32
  # Evaluates the custom variable DSL
26
- # @param dsl_operand_value [String] The operand value to evaluate
33
+ # @param dsl_operand_value [Hash] The operand value to evaluate
27
34
  # @param properties [Hash] The properties to evaluate the operand against
28
35
  # @return [Boolean] True if the operand value matches the tag value, false otherwise
29
36
  def evaluate_custom_variable_dsl(dsl_operand_value, properties)
@@ -42,10 +49,11 @@ class SegmentOperandEvaluator
42
49
  return false
43
50
  end
44
51
 
45
- tag_value = pre_process_tag_value(properties[operand_key])
52
+ tag_value = properties[operand_key]
53
+ attribute_value = pre_process_tag_value(tag_value)
46
54
  list_id = match[1]
47
55
 
48
- query_params_obj = { attribute: tag_value, listId: list_id }
56
+ query_params_obj = { attribute: attribute_value, listId: list_id }
49
57
 
50
58
  begin
51
59
  res = get_from_gateway_service(query_params_obj, UrlEnum::ATTRIBUTE_CHECK)
@@ -60,9 +68,12 @@ class SegmentOperandEvaluator
60
68
 
61
69
  false
62
70
  else
63
- tag_value = pre_process_tag_value(properties[operand_key])
64
- processed_values = process_values(*pre_process_operand_value(operand), tag_value)
65
- extract_result(processed_values[:operand_type], processed_values[:operand_value], processed_values[:tag_value])
71
+ tag_value = properties[operand_key]
72
+ tag_value = pre_process_tag_value(tag_value)
73
+ processed_operand = pre_process_operand_value(operand)
74
+ processed_values = process_values(processed_operand[:operand_value], tag_value)
75
+ tag_value = processed_values[:tag_value]
76
+ extract_result(processed_operand[:operand_type], processed_values[:operand_value], tag_value)
66
77
  end
67
78
  end
68
79
 
@@ -80,21 +91,144 @@ class SegmentOperandEvaluator
80
91
  # @param context [ContextModel] The context to evaluate the operand against
81
92
  # @return [Boolean] True if the operand value matches the tag value, false otherwise
82
93
  def evaluate_user_agent_dsl(dsl_operand_value, context)
83
- return false unless context.get_user_agent
94
+ operand = dsl_operand_value
95
+ unless context.get_user_agent
96
+ LoggerService.log(LogLevelEnum::INFO, 'To Evaluate UserAgent segmentation, please provide userAgent in context', nil)
97
+ return false
98
+ end
84
99
 
85
100
  tag_value = CGI.unescape(context.get_user_agent)
86
- processed_values = process_values(*pre_process_operand_value(dsl_operand_value), tag_value)
87
- extract_result(processed_values[:operand_type], processed_values[:operand_value], processed_values[:tag_value])
101
+ processed_operand = pre_process_operand_value(operand)
102
+ processed_values = process_values(processed_operand[:operand_value], tag_value)
103
+ tag_value = processed_values[:tag_value]
104
+ extract_result(processed_operand[:operand_type], processed_values[:operand_value], tag_value)
88
105
  end
89
106
 
90
- # Pre-processes the tag value
91
- # @param tag_value [String] The tag value to pre-process
92
- # @return [String] The pre-processed tag value
93
- def pre_process_tag_value(tag_value)
94
- return '' if tag_value.nil?
95
- return tag_value if [true, false].include?(tag_value)
107
+ # Evaluates a given string tag value against a DSL operand value.
108
+ # @param dsl_operand_value [String] The DSL operand string (e.g., "contains(\"value\")").
109
+ # @param context [ContextModel] The context object containing the value to evaluate.
110
+ # @param operand_type [String] The type of operand being evaluated (ip_address, browser_version, os_version).
111
+ # @return [Boolean] True if tag value matches DSL operand criteria, false otherwise.
112
+ def evaluate_string_operand_dsl(dsl_operand_value, context, operand_type)
113
+ operand = dsl_operand_value.to_s
114
+
115
+ # Determine the tag value based on operand type
116
+ tag_value = get_tag_value_for_operand_type(context, operand_type)
117
+
118
+ if tag_value.nil?
119
+ log_missing_context_error(operand_type)
120
+ return false
121
+ end
122
+
123
+ operand_type_and_value = pre_process_operand_value(operand)
124
+ processed_values = process_values(operand_type_and_value[:operand_value], tag_value, operand_type)
125
+ processed_tag_value = processed_values[:tag_value]
126
+
127
+ extract_result(
128
+ operand_type_and_value[:operand_type],
129
+ processed_values[:operand_value].to_s.strip.gsub(/"/, ''),
130
+ processed_tag_value
131
+ )
132
+ end
133
+
134
+ # Evaluates IP address DSL expression.
135
+ # @param dsl_operand_value [String] The DSL expression for the IP address.
136
+ # @param context [ContextModel] The context object containing the IP address.
137
+ # @return [Boolean] True if the IP address matches the DSL condition, otherwise false.
138
+ def evaluate_ip_dsl(dsl_operand_value, context)
139
+ evaluate_string_operand_dsl(dsl_operand_value, context, SegmentOperatorValueEnum::IP)
140
+ end
141
+
142
+ # Evaluates browser version DSL expression.
143
+ # @param dsl_operand_value [String] The DSL expression for the browser version.
144
+ # @param context [ContextModel] The context object containing the user agent info.
145
+ # @return [Boolean] True if the browser version matches the DSL condition, otherwise false.
146
+ def evaluate_browser_version_dsl(dsl_operand_value, context)
147
+ evaluate_string_operand_dsl(dsl_operand_value, context, SegmentOperatorValueEnum::BROWSER_VERSION)
148
+ end
149
+
150
+ # Evaluates OS version DSL expression.
151
+ # @param dsl_operand_value [String] The DSL expression for the OS version.
152
+ # @param context [ContextModel] The context object containing the user agent info.
153
+ # @return [Boolean] True if the OS version matches the DSL condition, otherwise false.
154
+ def evaluate_os_version_dsl(dsl_operand_value, context)
155
+ evaluate_string_operand_dsl(dsl_operand_value, context, SegmentOperatorValueEnum::OS_VERSION)
156
+ end
157
+
158
+ # Gets the appropriate tag value based on the operand type.
159
+ # @param context [ContextModel] The context object.
160
+ # @param operand_type [String] The type of operand.
161
+ # @return [String, nil] The tag value or nil if not available.
162
+ def get_tag_value_for_operand_type(context, operand_type)
163
+ case operand_type
164
+ when SegmentOperatorValueEnum::IP
165
+ context.get_ip_address
166
+ when SegmentOperatorValueEnum::BROWSER_VERSION
167
+ get_browser_version_from_context(context)
168
+ else
169
+ # Default works for OS version
170
+ get_os_version_from_context(context)
171
+ end
172
+ end
173
+
174
+ # Gets browser version from context.
175
+ # @param context [ContextModel] The context object.
176
+ # @return [String, nil] The browser version or nil if not available.
177
+ def get_browser_version_from_context(context)
178
+ user_agent = context.get_vwo&.get_ua_info
179
+ return nil unless user_agent && user_agent.is_a?(Hash) && !user_agent.empty?
180
+
181
+ # Assuming UserAgent dictionary contains browser_version
182
+ if user_agent.key?('browser_version')
183
+ return user_agent['browser_version']&.to_s
184
+ end
185
+ nil
186
+ end
187
+
188
+ # Gets OS version from context.
189
+ # @param context [ContextModel] The context object.
190
+ # @return [String, nil] The OS version or nil if not available.
191
+ def get_os_version_from_context(context)
192
+ user_agent = context.get_vwo&.get_ua_info
193
+ return nil unless user_agent && user_agent.is_a?(Hash) && !user_agent.empty?
194
+
195
+ # Assuming UserAgent dictionary contains os_version
196
+ if user_agent.key?('os_version')
197
+ return user_agent['os_version']&.to_s
198
+ end
199
+ nil
200
+ end
201
+
202
+ # Logs appropriate error message for missing context.
203
+ # @param operand_type [String] The type of operand.
204
+ def log_missing_context_error(operand_type)
205
+ case operand_type
206
+ when SegmentOperatorValueEnum::IP
207
+ LoggerService.log(LogLevelEnum::INFO, 'To evaluate IP segmentation, please provide ipAddress in context', nil)
208
+ when SegmentOperatorValueEnum::BROWSER_VERSION
209
+ LoggerService.log(LogLevelEnum::INFO, 'To evaluate browser version segmentation, please provide userAgent in context', nil)
210
+ else
211
+ LoggerService.log(LogLevelEnum::INFO, 'To evaluate OS version segmentation, please provide userAgent in context', nil)
212
+ end
213
+ end
96
214
 
97
- tag_value.to_s
215
+ # Pre-processes the tag value to ensure it is in the correct format for evaluation.
216
+ # @param tag_value [Any] The value to be processed.
217
+ # @return [String, Boolean] The processed tag value, either as a string or a boolean.
218
+ def pre_process_tag_value(tag_value)
219
+ # Default to empty string if undefined
220
+ if tag_value.nil?
221
+ tag_value = ''
222
+ end
223
+ # Convert boolean values to boolean type
224
+ if DataTypeUtil.is_boolean(tag_value)
225
+ tag_value = tag_value ? true : false
226
+ end
227
+ # Convert all non-null values to string
228
+ unless tag_value.nil?
229
+ tag_value = tag_value.to_s
230
+ end
231
+ tag_value
98
232
  end
99
233
 
100
234
  # Pre-processes the operand value
@@ -106,16 +240,23 @@ class SegmentOperandEvaluator
106
240
  { operand_type: SegmentOperandValueEnum::LOWER_VALUE, operand_value: extract_operand_value(operand, SegmentOperandRegexEnum::LOWER_MATCH) }
107
241
  when /#{SegmentOperandRegexEnum::WILDCARD_MATCH}/
108
242
  value = extract_operand_value(operand, SegmentOperandRegexEnum::WILDCARD_MATCH)
109
- if value.match?(SegmentOperandRegexEnum::STARTING_STAR) && value.match?(SegmentOperandRegexEnum::ENDING_STAR)
243
+ starting_star = match_with_regex(value, SegmentOperandRegexEnum::STARTING_STAR)
244
+ ending_star = match_with_regex(value, SegmentOperandRegexEnum::ENDING_STAR)
245
+
246
+ # Determine specific wildcard type
247
+ if starting_star && ending_star
110
248
  type = SegmentOperandValueEnum::STARTING_ENDING_STAR_VALUE
111
- value = value.gsub(/^\*|\*$/, '')
112
- elsif value.match?(SegmentOperandRegexEnum::STARTING_STAR)
249
+ elsif starting_star
113
250
  type = SegmentOperandValueEnum::STARTING_STAR_VALUE
114
- value = value.gsub(/^\*/, '')
115
- elsif value.match?(SegmentOperandRegexEnum::ENDING_STAR)
251
+ elsif ending_star
116
252
  type = SegmentOperandValueEnum::ENDING_STAR_VALUE
117
- value = value.gsub(/\*$/, '')
118
253
  end
254
+
255
+ # Remove wildcard characters from the operand value
256
+ value = value
257
+ .gsub(Regexp.new(SegmentOperandRegexEnum::STARTING_STAR), '')
258
+ .gsub(Regexp.new(SegmentOperandRegexEnum::ENDING_STAR), '')
259
+
119
260
  { operand_type: type, operand_value: value }
120
261
  when /#{SegmentOperandRegexEnum::REGEX_MATCH}/
121
262
  { operand_type: SegmentOperandValueEnum::REGEX_VALUE, operand_value: extract_operand_value(operand, SegmentOperandRegexEnum::REGEX_MATCH) }
@@ -137,29 +278,54 @@ class SegmentOperandEvaluator
137
278
  # @param regex [String] The regex to match the operand against
138
279
  # @return [String] The extracted operand value
139
280
  def extract_operand_value(operand, regex)
140
- match = operand.match(/#{regex}/)
141
- match ? match[1] : ''
281
+ match_result = match_with_regex(operand, regex)
282
+ match_result && match_result[1] ? match_result[1] : ''
142
283
  end
143
284
 
144
- # Processes the values from the operand and tag value
145
- # @param operand_type [Symbol] The type of operand
146
- # @param operand_value [String] The value of the operand
147
- # @param tag_value [String] The value of the tag
148
- # @return [Hash] The processed operand value and tag value
149
- def process_values(operand_type, operand_value, tag_value)
150
- # Extract values from arrays if needed
151
- operand_type = operand_type[1] if operand_type.is_a?(Array)
152
- operand_value = operand_value[1] if operand_value.is_a?(Array)
153
- tag_value = tag_value[1] if tag_value.is_a?(Array)
285
+ # Processes numeric values from operand and tag values, converting them to strings.
286
+ # @param operand_value [Any] The operand value to process.
287
+ # @param tag_value [Any] The tag value to process.
288
+ # @param operand_type [String] The type of operand being evaluated (optional).
289
+ # @return [Hash] An object containing the processed operand and tag values as strings.
290
+ def process_values(operand_value, tag_value, operand_type = nil)
291
+ if [SegmentOperatorValueEnum::IP, SegmentOperatorValueEnum::BROWSER_VERSION, SegmentOperatorValueEnum::OS_VERSION].include?(operand_type)
292
+ return {
293
+ operand_value: operand_value,
294
+ tag_value: tag_value
295
+ }
296
+ end
297
+
298
+ # Convert operand and tag values to floats
299
+ if NON_NUMERIC_PATTERN.match?(tag_value.to_s)
300
+ return {
301
+ operand_value: operand_value,
302
+ tag_value: tag_value
303
+ }
304
+ end
154
305
 
155
306
  processed_operand_value = operand_value.to_f
156
307
  processed_tag_value = tag_value.to_f
157
308
 
158
- if processed_operand_value == 0 || processed_tag_value == 0
159
- return { operand_type: operand_type, operand_value: operand_value, tag_value: tag_value }
309
+ # Return original values if conversion fails
310
+ if processed_operand_value == 0 && operand_value.to_s != '0' && operand_value.to_s != '0.0'
311
+ return {
312
+ operand_value: operand_value,
313
+ tag_value: tag_value
314
+ }
315
+ end
316
+
317
+ if processed_tag_value == 0 && tag_value.to_s != '0' && tag_value.to_s != '0.0'
318
+ return {
319
+ operand_value: operand_value,
320
+ tag_value: tag_value
321
+ }
160
322
  end
161
323
 
162
- { operand_type: operand_type, operand_value: processed_operand_value.to_s, tag_value: processed_tag_value.to_s }
324
+ # Convert numeric values back to strings
325
+ {
326
+ operand_value: processed_operand_value.to_s,
327
+ tag_value: processed_tag_value.to_s
328
+ }
163
329
  end
164
330
 
165
331
  # Extracts the result from the operand value and tag value
@@ -168,31 +334,81 @@ class SegmentOperandEvaluator
168
334
  # @param tag_value [String] The value of the tag
169
335
  # @return [Boolean] True if the operand value matches the tag value, false otherwise
170
336
  def extract_result(operand_type, operand_value, tag_value)
337
+ result = false
338
+
339
+ return false if tag_value.nil?
340
+
341
+ # Ensure operand_value and tag_value are strings
342
+ operand_value_str = operand_value.to_s
343
+ tag_value_str = tag_value.to_s
344
+
171
345
  case operand_type
172
346
  when SegmentOperandValueEnum::LOWER_VALUE
173
- operand_value.downcase == tag_value.downcase
347
+ result = operand_value_str.downcase == tag_value_str.downcase
174
348
  when SegmentOperandValueEnum::STARTING_ENDING_STAR_VALUE
175
- tag_value.include?(operand_value)
349
+ result = tag_value_str.include?(operand_value_str)
176
350
  when SegmentOperandValueEnum::STARTING_STAR_VALUE
177
- tag_value.end_with?(operand_value)
351
+ result = tag_value_str.end_with?(operand_value_str)
178
352
  when SegmentOperandValueEnum::ENDING_STAR_VALUE
179
- tag_value.start_with?(operand_value)
353
+ result = tag_value_str.start_with?(operand_value_str)
180
354
  when SegmentOperandValueEnum::REGEX_VALUE
181
355
  begin
182
- !!Regexp.new(operand_value).match?(tag_value)
356
+ pattern = Regexp.new(operand_value_str)
357
+ result = pattern.match?(tag_value_str)
183
358
  rescue StandardError
184
- false
359
+ result = false
185
360
  end
186
361
  when SegmentOperandValueEnum::GREATER_THAN_VALUE
187
- operand_value.to_f < tag_value.to_f
188
- when SegmentOperandValueEnum::LESS_THAN_VALUE
189
- operand_value.to_f > tag_value.to_f
362
+ result = compare_versions(tag_value_str, operand_value_str) > 0
190
363
  when SegmentOperandValueEnum::GREATER_THAN_EQUAL_TO_VALUE
191
- operand_value.to_f <= tag_value.to_f
364
+ result = compare_versions(tag_value_str, operand_value_str) >= 0
365
+ when SegmentOperandValueEnum::LESS_THAN_VALUE
366
+ result = compare_versions(tag_value_str, operand_value_str) < 0
192
367
  when SegmentOperandValueEnum::LESS_THAN_EQUAL_TO_VALUE
193
- operand_value.to_f >= tag_value.to_f
368
+ result = compare_versions(tag_value_str, operand_value_str) <= 0
194
369
  else
195
- tag_value == operand_value
370
+ # For version-like strings, use version comparison; otherwise use string comparison
371
+ if version_string?(tag_value_str) && version_string?(operand_value_str)
372
+ result = compare_versions(tag_value_str, operand_value_str) == 0
373
+ else
374
+ result = tag_value_str == operand_value_str
375
+ end
376
+ end
377
+
378
+ result
379
+ end
380
+
381
+ # Checks if a string appears to be a version string (contains only digits and dots).
382
+ # @param str [String] The string to check.
383
+ # @return [Boolean] True if the string appears to be a version string.
384
+ def version_string?(str)
385
+ /^(\d+\.)*\d+$/.match?(str)
386
+ end
387
+
388
+ # Compares two version strings using semantic versioning rules.
389
+ # Supports formats like "1.2.3", "1.0", "2.1.4.5", etc.
390
+ # @param version1 [String] First version string.
391
+ # @param version2 [String] Second version string.
392
+ # @return [Integer] -1 if version1 < version2, 0 if equal, 1 if version1 > version2.
393
+ def compare_versions(version1, version2)
394
+ # Split versions by dots and convert to integers
395
+ parts1 = version1.split('.').map { |part| part.match?(/^\d+$/) ? part.to_i : 0 }
396
+ parts2 = version2.split('.').map { |part| part.match?(/^\d+$/) ? part.to_i : 0 }
397
+
398
+ # Find the maximum length to handle different version formats
399
+ max_length = [parts1.length, parts2.length].max
400
+
401
+ (0...max_length).each do |i|
402
+ part1 = i < parts1.length ? parts1[i] : 0
403
+ part2 = i < parts2.length ? parts2[i] : 0
404
+
405
+ if part1 < part2
406
+ return -1
407
+ elsif part1 > part2
408
+ return 1
409
+ end
196
410
  end
411
+
412
+ 0 # Versions are equal
197
413
  end
198
414
  end
@@ -72,7 +72,7 @@ end
72
72
  def add_is_gateway_service_required_flag(settings)
73
73
  pattern = /
74
74
  (?!custom_variable\s*:\s*{\s*"name"\s*:\s*") # Prevent matching inside custom_variable
75
- \b(country|region|city|os|device|device_type|browser_string|ua)\b
75
+ \b(country|region|city|os|device|device_type|browser_string|ua|browser_version|os_version)\b
76
76
  |
77
77
  "custom_variable"\s*:\s*{\s*"name"\s*:\s*"inlist\([^)]*\)"
78
78
  /x
@@ -35,12 +35,10 @@ def create_and_send_impression_for_variation_shown(settings, campaign_id, variat
35
35
 
36
36
  # Construct payload data for tracking the user
37
37
  payload = NetworkUtil.get_track_user_payload_data(
38
- context.get_id,
39
38
  EventEnum::VWO_VARIATION_SHOWN,
40
39
  campaign_id,
41
40
  variation_id,
42
- context.get_user_agent,
43
- context.get_ip_address
41
+ context
44
42
  )
45
43
 
46
44
  # check if batching is enabled
@@ -24,6 +24,7 @@ require_relative '../packages/network_layer/models/response_model'
24
24
  require_relative '../utils/url_util'
25
25
  require_relative '../utils/uuid_util'
26
26
  require_relative '../utils/usage_stats_util'
27
+ require_relative '../models/user/context_model'
27
28
 
28
29
  class NetworkUtil
29
30
  class << self
@@ -146,8 +147,15 @@ class NetworkUtil
146
147
  end
147
148
 
148
149
  # Builds track-user payload data
149
- def get_track_user_payload_data(user_id, event_name, campaign_id, variation_id, visitor_user_agent = '', ip_address = '')
150
+ def get_track_user_payload_data(event_name, campaign_id, variation_id, context)
151
+ user_id = context.get_id
152
+ visitor_user_agent = context.get_user_agent
153
+ ip_address = context.get_ip_address
154
+ custom_variables = context.get_custom_variables
155
+ post_segmentation_variables = context.get_post_segmentation_variables
156
+
150
157
  properties = _get_event_base_payload(user_id, event_name, visitor_user_agent, ip_address)
158
+
151
159
  properties[:d][:event][:props][:id] = campaign_id
152
160
  properties[:d][:event][:props][:variation] = variation_id
153
161
  properties[:d][:event][:props][:isFirst] = 1
@@ -156,6 +164,22 @@ class NetworkUtil
156
164
  properties[:d][:visitor_ua] = visitor_user_agent if visitor_user_agent && !visitor_user_agent.empty?
157
165
  properties[:d][:visitor_ip] = ip_address if ip_address && !ip_address.empty?
158
166
 
167
+ # Add post-segmentation variables if they exist in custom variables
168
+ if post_segmentation_variables&.any? && custom_variables&.any?
169
+ post_segmentation_variables.each do |key|
170
+ # Try to get value using string key first, then symbol key
171
+ value = custom_variables[key] || custom_variables[key.to_sym]
172
+ if value
173
+ properties[:d][:visitor][:props][key] = value
174
+ end
175
+ end
176
+ end
177
+
178
+ # Add IP address as a standard attribute if available
179
+ if ip_address && !ip_address.empty?
180
+ properties[:d][:visitor][:props][:ip] = ip_address
181
+ end
182
+
159
183
  LoggerService.log(LogLevelEnum::DEBUG, "IMPRESSION_FOR_TRACK_USER", {
160
184
  accountId: SettingsService.instance.account_id,
161
185
  userId: user_id,
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: vwo-fme-ruby-sdk
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.5.0
4
+ version: 1.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - VWO
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-08-29 00:00:00.000000000 Z
11
+ date: 2025-09-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: uuidtools