wingify-fme-ruby-sdk 1.50.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.
Files changed (88) hide show
  1. checksums.yaml +7 -0
  2. data/lib/resources/debug_messages.json +21 -0
  3. data/lib/resources/error_messages.json +63 -0
  4. data/lib/resources/info_messages.json +43 -0
  5. data/lib/resources/warn_messages.json +1 -0
  6. data/lib/vwo.rb +40 -0
  7. data/lib/wingify/api/get_flag.rb +244 -0
  8. data/lib/wingify/api/set_attribute.rb +57 -0
  9. data/lib/wingify/api/track_event.rb +80 -0
  10. data/lib/wingify/constants/constants.rb +106 -0
  11. data/lib/wingify/decorators/storage_decorator.rb +82 -0
  12. data/lib/wingify/enums/api_enum.rb +23 -0
  13. data/lib/wingify/enums/campaign_type_enum.rb +19 -0
  14. data/lib/wingify/enums/debug_category_enum.rb +20 -0
  15. data/lib/wingify/enums/decision_types_enum.rb +18 -0
  16. data/lib/wingify/enums/event_enum.rb +22 -0
  17. data/lib/wingify/enums/headers_enum.rb +20 -0
  18. data/lib/wingify/enums/hooks_enum.rb +17 -0
  19. data/lib/wingify/enums/http_method_enum.rb +18 -0
  20. data/lib/wingify/enums/log_level_enum.rb +21 -0
  21. data/lib/wingify/enums/log_level_to_number.rb +27 -0
  22. data/lib/wingify/enums/status_enum.rb +19 -0
  23. data/lib/wingify/enums/storage_enum.rb +22 -0
  24. data/lib/wingify/enums/url_enum.rb +22 -0
  25. data/lib/wingify/models/campaign/campaign_model.rb +192 -0
  26. data/lib/wingify/models/campaign/feature_model.rb +111 -0
  27. data/lib/wingify/models/campaign/impact_campaign_model.rb +38 -0
  28. data/lib/wingify/models/campaign/metric_model.rb +44 -0
  29. data/lib/wingify/models/campaign/rule_model.rb +56 -0
  30. data/lib/wingify/models/campaign/variable_model.rb +51 -0
  31. data/lib/wingify/models/campaign/variation_model.rb +137 -0
  32. data/lib/wingify/models/gateway_service_model.rb +39 -0
  33. data/lib/wingify/models/schemas/settings_schema_validation.rb +104 -0
  34. data/lib/wingify/models/settings/settings_model.rb +101 -0
  35. data/lib/wingify/models/storage/storage_data_model.rb +44 -0
  36. data/lib/wingify/models/user/context_model.rb +154 -0
  37. data/lib/wingify/models/user/context_vwo_model.rb +38 -0
  38. data/lib/wingify/models/user/get_flag_response.rb +50 -0
  39. data/lib/wingify/models/vwo_options_model.rb +129 -0
  40. data/lib/wingify/packages/decision_maker/decision_maker.rb +60 -0
  41. data/lib/wingify/packages/logger/core/log_manager.rb +92 -0
  42. data/lib/wingify/packages/logger/core/transport_manager.rb +76 -0
  43. data/lib/wingify/packages/logger/log_message_builder.rb +70 -0
  44. data/lib/wingify/packages/logger/logger.rb +38 -0
  45. data/lib/wingify/packages/logger/transports/console_transport.rb +49 -0
  46. data/lib/wingify/packages/network_layer/client/network_client.rb +276 -0
  47. data/lib/wingify/packages/network_layer/handlers/request_handler.rb +37 -0
  48. data/lib/wingify/packages/network_layer/manager/network_manager.rb +145 -0
  49. data/lib/wingify/packages/network_layer/models/global_request_model.rb +105 -0
  50. data/lib/wingify/packages/network_layer/models/request_model.rb +179 -0
  51. data/lib/wingify/packages/network_layer/models/response_model.rb +62 -0
  52. data/lib/wingify/packages/segmentation_evaluator/core/segmentation_manager.rb +77 -0
  53. data/lib/wingify/packages/segmentation_evaluator/enums/segment_operand_regex_enum.rb +29 -0
  54. data/lib/wingify/packages/segmentation_evaluator/enums/segment_operand_value_enum.rb +26 -0
  55. data/lib/wingify/packages/segmentation_evaluator/enums/segment_operator_value_enum.rb +33 -0
  56. data/lib/wingify/packages/segmentation_evaluator/evaluators/segment_evaluator.rb +218 -0
  57. data/lib/wingify/packages/segmentation_evaluator/evaluators/segment_operand_evaluator.rb +415 -0
  58. data/lib/wingify/packages/segmentation_evaluator/utils/segment_util.rb +44 -0
  59. data/lib/wingify/packages/storage/connector.rb +26 -0
  60. data/lib/wingify/packages/storage/storage.rb +47 -0
  61. data/lib/wingify/services/batch_event_queue.rb +179 -0
  62. data/lib/wingify/services/campaign_decision_service.rb +161 -0
  63. data/lib/wingify/services/hooks_service.rb +51 -0
  64. data/lib/wingify/services/logger_service.rb +114 -0
  65. data/lib/wingify/services/settings_service.rb +178 -0
  66. data/lib/wingify/services/storage_service.rb +66 -0
  67. data/lib/wingify/utils/batch_event_dispatcher_util.rb +178 -0
  68. data/lib/wingify/utils/brand_context.rb +33 -0
  69. data/lib/wingify/utils/brand_util.rb +53 -0
  70. data/lib/wingify/utils/campaign_util.rb +284 -0
  71. data/lib/wingify/utils/data_type_util.rb +105 -0
  72. data/lib/wingify/utils/debugger_service_util.rb +40 -0
  73. data/lib/wingify/utils/decision_util.rb +259 -0
  74. data/lib/wingify/utils/event_util.rb +55 -0
  75. data/lib/wingify/utils/function_util.rb +141 -0
  76. data/lib/wingify/utils/gateway_service_util.rb +101 -0
  77. data/lib/wingify/utils/impression_util.rb +66 -0
  78. data/lib/wingify/utils/log_message_util.rb +42 -0
  79. data/lib/wingify/utils/meg_util.rb +357 -0
  80. data/lib/wingify/utils/network_util.rb +503 -0
  81. data/lib/wingify/utils/rule_evaluation_util.rb +57 -0
  82. data/lib/wingify/utils/settings_util.rb +38 -0
  83. data/lib/wingify/utils/usage_stats_util.rb +119 -0
  84. data/lib/wingify/utils/uuid_util.rb +96 -0
  85. data/lib/wingify/wingify_builder.rb +261 -0
  86. data/lib/wingify/wingify_client.rb +227 -0
  87. data/lib/wingify.rb +117 -0
  88. metadata +327 -0
@@ -0,0 +1,503 @@
1
+ # Copyright 2024-2026 Wingify Software Pvt. Ltd.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ require_relative '../enums/log_level_enum'
16
+ require_relative '../services/logger_service'
17
+ require_relative '../enums/headers_enum'
18
+ require_relative '../enums/http_method_enum'
19
+ require_relative '../enums/url_enum'
20
+ require_relative '../constants/constants'
21
+ require_relative '../packages/network_layer/manager/network_manager'
22
+ require_relative '../packages/network_layer/models/request_model'
23
+ require_relative '../packages/network_layer/models/response_model'
24
+ require_relative '../utils/uuid_util'
25
+ require_relative '../utils/usage_stats_util'
26
+ require_relative '../models/user/context_model'
27
+ require_relative '../utils/log_message_util'
28
+ require_relative '../utils/function_util'
29
+ require_relative '../enums/api_enum'
30
+
31
+ class NetworkUtil
32
+ class << self
33
+ # Converts hash map query parameters to URL-encoded query string
34
+ # @param params [Hash] Hash containing query parameters
35
+ # @return [String] URL-encoded query string
36
+ def convert_params_to_string(params)
37
+ return '' if params.nil? || params.empty?
38
+
39
+ '?' + params.map do |key, value|
40
+ "#{URI.encode_www_form_component(key.to_s)}=#{URI.encode_www_form_component(value.to_s)}"
41
+ end.join('&')
42
+ end
43
+
44
+ # Returns the base properties for bulk operations
45
+ def get_base_properties_for_bulk(account_id, user_id)
46
+ {
47
+ sId: get_current_unix_timestamp, # Session ID
48
+ u: UUIDUtil.get_uuid(user_id, account_id) # UUID based on user and account ID
49
+ }
50
+ end
51
+
52
+ # Returns settings path with sdkKey and accountId
53
+ def get_settings_path(sdk_key, account_id)
54
+ {
55
+ i: sdk_key, # API key
56
+ r: rand, # Random number for cache busting
57
+ a: account_id # Account ID
58
+ }
59
+ end
60
+
61
+ # Returns the event tracking path
62
+ def get_track_event_path(event, account_id, user_id)
63
+ {
64
+ event_type: event, # Type of event
65
+ account_id: account_id, # Account ID
66
+ uId: user_id, # User ID
67
+ u: UUIDUtil.get_uuid(user_id, account_id), # UUID for user
68
+ sdk: SettingsService.instance.sdk_name, # SDK Name
69
+ 'sdk-v': Constants::SDK_VERSION, # SDK Version
70
+ random: get_random_number, # Random number for uniqueness
71
+ sId: get_current_unix_timestamp, # Session ID
72
+ ed: JSON.generate({ p: 'server' }) # Additional encoded data
73
+ }
74
+ end
75
+
76
+ # Returns query params for event batching
77
+ def get_event_batching_query_params(account_id)
78
+ {
79
+ a: account_id, # Account ID
80
+ sd: SettingsService.instance.sdk_name, # SDK Name
81
+ sv: Constants::SDK_VERSION # SDK Version
82
+ }
83
+ end
84
+
85
+ # Builds generic properties for different tracking calls
86
+ def get_events_base_properties(event_name, visitor_user_agent = '', ip_address = '', is_usage_stats_event = false, usage_stat_account_id = '')
87
+ properties = {
88
+ en: event_name,
89
+ a: SettingsService.instance.account_id,
90
+ eTime: get_current_unix_timestamp_in_millis,
91
+ random: get_random_number,
92
+ p: 'FS',
93
+ visitor_ua: visitor_user_agent || '',
94
+ visitor_ip: ip_address || ''
95
+ }
96
+
97
+ if !is_usage_stats_event
98
+ # set env key for standard sdk events
99
+ properties[:env] = SettingsService.instance.sdk_key
100
+ else
101
+ # set env key for usage stats events
102
+ properties[:a] = usage_stat_account_id
103
+ end
104
+
105
+ properties
106
+ end
107
+
108
+ # Builds base payload for tracking events
109
+ def _get_event_base_payload(user_id, event_name, visitor_user_agent = '', ip_address = '', is_usage_stats_event = false, usage_stat_account_id = '')
110
+ account_id = SettingsService.instance.account_id
111
+
112
+ if is_usage_stats_event
113
+ account_id = usage_stat_account_id
114
+ end
115
+
116
+ uuid = UUIDUtil.get_uuid(user_id.to_s, account_id.to_s)
117
+ sdk_key = SettingsService.instance.sdk_key
118
+
119
+ payload = {
120
+ d: {
121
+ msgId: "#{uuid}-#{get_current_unix_timestamp_in_millis}",
122
+ visId: uuid,
123
+ sessionId: get_current_unix_timestamp,
124
+ event: {
125
+ props: {
126
+ vwo_sdkName: SettingsService.instance.sdk_name,
127
+ vwo_sdkVersion: Constants::SDK_VERSION,
128
+ },
129
+ name: event_name,
130
+ time: get_current_unix_timestamp_in_millis
131
+ }
132
+ }
133
+ }
134
+
135
+ if !is_usage_stats_event
136
+ # set env key for standard sdk events
137
+ payload[:d][:event][:props][:vwo_envKey] = sdk_key
138
+
139
+ # set visitor props for standard sdk events
140
+ payload[:d][:visitor] = {
141
+ props: {
142
+ vwo_fs_environment: sdk_key
143
+ }
144
+ }
145
+ end
146
+
147
+ payload
148
+ end
149
+
150
+ # Builds track-user payload data
151
+ def get_track_user_payload_data(event_name, campaign_id, variation_id, context)
152
+ user_id = context.get_id
153
+ visitor_user_agent = context.get_user_agent
154
+ ip_address = context.get_ip_address
155
+ custom_variables = context.get_custom_variables
156
+ post_segmentation_variables = context.get_post_segmentation_variables
157
+
158
+ properties = _get_event_base_payload(user_id, event_name, visitor_user_agent, ip_address)
159
+
160
+ # use sessionId from context if present
161
+ if context.get_session_id
162
+ properties[:d][:sessionId] = context.get_session_id
163
+ end
164
+
165
+ # use uuid from context if present
166
+ if context.get_uuid && !context.get_uuid.empty?
167
+ properties[:d][:visId] = context.get_uuid
168
+ properties[:d][:msgId] = "#{context.get_uuid}-#{get_current_unix_timestamp_in_millis}"
169
+ end
170
+
171
+ properties[:d][:event][:props][:id] = campaign_id
172
+ properties[:d][:event][:props][:variation] = variation_id
173
+ properties[:d][:event][:props][:isFirst] = 1
174
+
175
+ # Only add visitor_ua and visitor_ip if they are non-null
176
+ properties[:d][:visitor_ua] = visitor_user_agent if visitor_user_agent && !visitor_user_agent.empty?
177
+ properties[:d][:visitor_ip] = ip_address if ip_address && !ip_address.empty?
178
+
179
+ # Add post-segmentation variables if they exist in custom variables
180
+ if post_segmentation_variables&.any? && custom_variables&.any?
181
+ post_segmentation_variables.each do |key|
182
+ # Try to get value using string key first, then symbol key
183
+ value = custom_variables[key] || custom_variables[key.to_sym]
184
+ if value
185
+ properties[:d][:visitor][:props][key] = value
186
+ end
187
+ end
188
+ end
189
+
190
+ # Add IP address as a standard attribute if available
191
+ if ip_address && !ip_address.empty?
192
+ properties[:d][:visitor][:props][:ip] = ip_address
193
+ end
194
+
195
+ LoggerService.log(LogLevelEnum::DEBUG, "IMPRESSION_FOR_TRACK_USER", {
196
+ accountId: SettingsService.instance.account_id,
197
+ userId: user_id,
198
+ campaignId: campaign_id
199
+ })
200
+
201
+ properties
202
+ end
203
+
204
+ # Constructs payload for tracking goals with custom event properties
205
+ def get_track_goal_payload_data(event_name, event_properties, context)
206
+ properties = _get_event_base_payload(context.id, event_name, context.user_agent, context.ip_address)
207
+ properties[:d][:event][:props][:isCustomEvent] = true
208
+
209
+ # use sessionId from context if present
210
+ if context.get_session_id
211
+ properties[:d][:sessionId] = context.get_session_id
212
+ end
213
+
214
+ # use uuid from context if present
215
+ if context.get_uuid && !context.get_uuid.empty?
216
+ properties[:d][:visId] = context.get_uuid
217
+ properties[:d][:msgId] = "#{context.get_uuid}-#{get_current_unix_timestamp_in_millis}"
218
+ end
219
+
220
+ if SettingsService.instance.is_gateway_service_provided
221
+ properties[:d][:event][:props][:variation] = 1
222
+ properties[:d][:event][:props][:id] = 1 # Temporary value for ID
223
+ end
224
+
225
+ if event_properties.is_a?(Hash) && !event_properties.empty?
226
+ event_properties.each { |key, value| properties[:d][:event][:props][key] = value }
227
+ end
228
+
229
+ LoggerService.log(LogLevelEnum::DEBUG, "IMPRESSION_FOR_TRACK_GOAL", {
230
+ eventName: event_name,
231
+ accountId: SettingsService.instance.account_id,
232
+ userId: context.id
233
+ })
234
+
235
+ properties
236
+ end
237
+
238
+ def get_attribute_payload_data(event_name, event_properties, context)
239
+ properties = _get_event_base_payload(context.id, event_name, context.user_agent, context.ip_address)
240
+ properties[:d][:event][:props][:isCustomEvent] = true
241
+
242
+ # use sessionId from context if present
243
+ if context.get_session_id
244
+ properties[:d][:sessionId] = context.get_session_id
245
+ end
246
+
247
+ # use uuid from context if present
248
+ if context.get_uuid && !context.get_uuid.empty?
249
+ properties[:d][:visId] = context.get_uuid
250
+ properties[:d][:msgId] = "#{context.get_uuid}-#{get_current_unix_timestamp_in_millis}"
251
+ end
252
+
253
+ if event_properties.is_a?(Hash) && !event_properties.empty?
254
+ event_properties.each { |key, value| properties[:d][:visitor][:props][key] = value }
255
+ end
256
+
257
+ LoggerService.log(LogLevelEnum::DEBUG, "IMPRESSION_FOR_SYNC_VISITOR_PROP", {
258
+ eventName: event_name,
259
+ accountId: SettingsService.instance.account_id,
260
+ userId: context.id
261
+ })
262
+ properties
263
+ end
264
+
265
+ # Constructs the payload for init called event.
266
+ # @param event_name - The name of the event.
267
+ # @param settings_fetch_time - Time taken to fetch settings in milliseconds.
268
+ # @param sdk_init_time - Time taken to initialize the SDK in milliseconds.
269
+ # @returns The constructed payload with required fields.
270
+ def get_sdk_init_event_payload(event_name, settings_fetch_time, sdk_init_time)
271
+ user_id = SettingsService.instance.account_id.to_s + "_" + SettingsService.instance.sdk_key
272
+ properties = _get_event_base_payload(user_id, event_name, nil, nil)
273
+ properties[:d][:event][:props][:vwo_fs_environment] = SettingsService.instance.sdk_key
274
+ properties[:d][:event][:props][:product] = Constants::PRODUCT_NAME
275
+ data = {
276
+ "isSDKInitialized": true,
277
+ "settingsFetchTime": settings_fetch_time,
278
+ "sdkInitTime": sdk_init_time
279
+ }
280
+ properties[:d][:event][:props][:data] = data
281
+ properties
282
+ end
283
+
284
+ # Constructs the payload for usage stats called event.
285
+ # @param event_name - The name of the event.
286
+ # @param usage_stats_account_id - The account id for usage stats.
287
+ # @returns The constructed payload with required fields.
288
+ def get_sdk_usage_stats_payload_data(event_name, usage_stats_account_id)
289
+ user_id = SettingsService.instance.account_id.to_s + "_" + SettingsService.instance.sdk_key
290
+ properties = _get_event_base_payload(user_id, event_name, nil, nil, true, usage_stats_account_id)
291
+ properties[:d][:event][:props][:product] = Constants::PRODUCT_NAME
292
+ properties[:d][:event][:props][:vwoMeta] = UsageStatsUtil.get_usage_stats
293
+ properties
294
+ end
295
+
296
+ # Sends a POST API request with given properties and payload
297
+ def send_post_api_request(properties, payload, campaign_info = {})
298
+ network_instance = NetworkManager.instance
299
+ headers = {}
300
+ headers[HeadersEnum::USER_AGENT] = payload[:d][:visitor_ua] if payload[:d][:visitor_ua]
301
+ headers[HeadersEnum::IP] = payload[:d][:visitor_ip] if payload[:d][:visitor_ip]
302
+
303
+ request = RequestModel.new(
304
+ SettingsService.instance.events_hostname,
305
+ HttpMethodEnum::POST,
306
+ SettingsService.instance.get_updated_endpoint_with_collection_prefix(UrlEnum::EVENTS, SettingsService.instance.is_gateway_service_provided),
307
+ properties,
308
+ payload,
309
+ headers,
310
+ SettingsService.instance.protocol,
311
+ SettingsService.instance.port
312
+ )
313
+
314
+ api_name = nil
315
+ extra_data_for_message = nil
316
+ if properties.key?(:en) && properties[:en] == EventEnum::VARIATION_SHOWN
317
+ api_name = ApiEnum::GET_FLAG
318
+ if campaign_info && campaign_info.key?(:campaign_type) && (campaign_info[:campaign_type] == CampaignTypeEnum::ROLLOUT || campaign_info[:campaign_type] == CampaignTypeEnum::PERSONALIZE)
319
+ extra_data_for_message = "feature: #{campaign_info[:feature_key]}, rule: #{campaign_info[:variation_name]}"
320
+ elsif campaign_info
321
+ extra_data_for_message = "feature: #{campaign_info[:feature_key]}, rule: #{campaign_info[:campaign_key]} and variation: #{campaign_info[:variation_name]}"
322
+ end
323
+ elsif properties.key?(:en) && properties[:en] != EventEnum::VARIATION_SHOWN
324
+ if properties.key?(:en) && properties[:en] == EventEnum::SYNC_VISITOR_PROP
325
+ api_name = ApiEnum::SET_ATTRIBUTE
326
+ extra_data_for_message = api_name
327
+ elsif properties.key?(:en) && properties[:en] != EventEnum::VARIATION_SHOWN && properties[:en] != EventEnum::DEBUGGER_EVENT && properties[:en] != EventEnum::INIT_CALLED
328
+ api_name = ApiEnum::TRACK_EVENT
329
+ extra_data_for_message = "event: #{properties[:en]}"
330
+ end
331
+ end
332
+ begin
333
+ if network_instance.get_client.get_should_use_threading
334
+ network_instance.get_client.get_thread_pool.post {
335
+ response = network_instance.post(request)
336
+ if response.get_total_attempts > 0
337
+ debug_event_props = create_network_and_retry_debug_event(response, request.get_body, api_name, extra_data_for_message)
338
+ debug_event_props[:uuid] = request.get_body[:d][:visId]
339
+ DebuggerServiceUtil.send_debugger_event(debug_event_props)
340
+ end
341
+ if response.get_status_code == 200
342
+ LoggerService.log(LogLevelEnum::INFO, "NETWORK_CALL_SUCCESS", {
343
+ event: properties[:en],
344
+ endPoint: UrlEnum::EVENTS,
345
+ accountId: SettingsService.instance.account_id,
346
+ uuid: request.get_body[:d][:visId]
347
+ })
348
+ end
349
+ response
350
+ }
351
+ else
352
+ response = network_instance.post(request)
353
+ if response.get_status_code == 200
354
+ LoggerService.log(LogLevelEnum::INFO, "NETWORK_CALL_SUCCESS", {
355
+ event: properties[:en],
356
+ endPoint: UrlEnum::EVENTS,
357
+ accountId: SettingsService.instance.account_id,
358
+ uuid: request.get_body[:d][:visId]
359
+ })
360
+ end
361
+ response
362
+ end
363
+ rescue ResponseModel => err
364
+ LoggerService.log(LogLevelEnum::ERROR, "NETWORK_CALL_FAILED", {
365
+ method: extra_data_for_message,
366
+ err: get_formatted_error_message(err.get_error)
367
+ })
368
+ end
369
+ end
370
+
371
+ # Sends an event to VWO (generic event sender).
372
+ # @param properties - Query parameters for the request.
373
+ # @param payload - The payload for the request.
374
+ # @param event_name - The name of the event to send.
375
+ def send_event(properties, payload)
376
+ network_instance = NetworkManager.instance
377
+ headers = {}
378
+ headers[HeadersEnum::USER_AGENT] = payload[:d][:visitor_ua] if payload[:d][:visitor_ua]
379
+ headers[HeadersEnum::IP] = payload[:d][:visitor_ip] if payload[:d][:visitor_ip]
380
+
381
+ request = RequestModel.new(
382
+ SettingsService.instance.events_hostname,
383
+ HttpMethodEnum::POST,
384
+ SettingsService.instance.get_updated_endpoint_with_collection_prefix(UrlEnum::EVENTS, SettingsService.instance.is_gateway_service_provided),
385
+ properties,
386
+ payload,
387
+ headers,
388
+ SettingsService.instance.protocol,
389
+ SettingsService.instance.port,
390
+ )
391
+
392
+ begin
393
+ if network_instance.get_client.get_should_use_threading
394
+ network_instance.get_client.get_thread_pool.post {
395
+ response = network_instance.post(request)
396
+ if response.get_status_code == 200
397
+ LoggerService.log(LogLevelEnum::INFO, "NETWORK_CALL_SUCCESS", {
398
+ event: properties[:en],
399
+ endPoint: UrlEnum::EVENTS,
400
+ accountId: SettingsService.instance.account_id,
401
+ uuid: request.get_body[:d][:visId]
402
+ })
403
+ end
404
+ response
405
+ }
406
+ else
407
+ response = network_instance.post(request)
408
+ if response.get_status_code == 200
409
+ LoggerService.log(LogLevelEnum::INFO, "NETWORK_CALL_SUCCESS", {
410
+ event: properties[:en],
411
+ endPoint: UrlEnum::EVENTS,
412
+ accountId: SettingsService.instance.account_id,
413
+ uuid: request.get_body[:d][:visId]
414
+ })
415
+ end
416
+ response
417
+ end
418
+ rescue ResponseModel => err
419
+ if properties.key?(:en) && properties[:en] != EventEnum::DEBUGGER_EVENT
420
+ LoggerService.log(LogLevelEnum::ERROR, "NETWORK_CALL_FAILED", {
421
+ method: "event: #{properties[:en]}",
422
+ err: get_formatted_error_message(err.get_error)
423
+ })
424
+ end
425
+ end
426
+ end
427
+
428
+ # get debugger event payload
429
+ def get_debugger_event_payload(event_props)
430
+ user_id = SettingsService.instance.account_id.to_s + "_" + SettingsService.instance.sdk_key
431
+ properties = _get_event_base_payload(user_id, EventEnum::DEBUGGER_EVENT, nil, nil)
432
+
433
+ # check if event_props contains uuid key and should be non null and non empty string
434
+ if event_props.key?(:uuid) && event_props[:uuid].is_a?(String) && !event_props[:uuid].empty?
435
+ properties[:d][:msgId] = "#{event_props[:uuid]}-#{get_current_unix_timestamp_in_millis}"
436
+ properties[:d][:visId] = event_props[:uuid]
437
+ else
438
+ event_props[:uuid] = properties[:d][:visId]
439
+ end
440
+
441
+ # check if event_props contains sessionId key
442
+ if event_props.key?(:sId)
443
+ properties[:d][:sessionId] = event_props[:sId]
444
+ else
445
+ event_props[:sId] = properties[:d][:sessionId]
446
+ end
447
+
448
+ event_props[:a] = SettingsService.instance.account_id.to_s
449
+ event_props[:product] = Constants::PRODUCT_NAME
450
+ event_props[:sn] = SettingsService.instance.sdk_name
451
+ event_props[:sv] = Constants::SDK_VERSION
452
+ event_props[:eventId] = UUIDUtil.get_random_uuid(SettingsService.instance.sdk_key)
453
+
454
+ properties[:d][:event][:props] = {}
455
+ properties[:d][:event][:props][:vwoMeta] = event_props
456
+ properties
457
+ end
458
+
459
+ def create_network_and_retry_debug_event(response, payload, api_name, extra_data)
460
+ begin
461
+ category = DebugCategoryEnum::RETRY
462
+ msg_t = Constants::NETWORK_CALL_SUCCESS_WITH_RETRIES
463
+ msg = build_message(LoggerService.get_messages(LogLevelEnum::INFO)[msg_t], {
464
+ extraData: extra_data,
465
+ attempts: response.get_total_attempts,
466
+ err: get_formatted_error_message(response.get_error)
467
+ })
468
+ lt = LogLevelEnum::INFO.to_s
469
+ if response.get_status_code != 200
470
+ category = DebugCategoryEnum::NETWORK
471
+ msg_t = Constants::NETWORK_CALL_FAILURE_AFTER_MAX_RETRIES
472
+ msg = build_message(LoggerService.get_messages(LogLevelEnum::ERROR)[msg_t], {
473
+ extraData: extra_data,
474
+ attempts: response.get_total_attempts,
475
+ err: get_formatted_error_message(response.get_error)
476
+ })
477
+ lt = LogLevelEnum::ERROR.to_s
478
+ end
479
+ debug_event_props = {
480
+ cg: category,
481
+ msg_t: msg_t,
482
+ msg: msg,
483
+ lt: lt
484
+ }
485
+ if api_name
486
+ debug_event_props[:an] = api_name
487
+ end
488
+
489
+ if payload && payload[:d] && payload[:d][:sessionId]
490
+ debug_event_props[:sId] = payload[:d][:sessionId]
491
+ else
492
+ debug_event_props[:sId] = get_current_unix_timestamp
493
+ end
494
+ debug_event_props
495
+ rescue StandardError => err
496
+ LoggerService.log(LogLevelEnum::ERROR, "NETWORK_CALL_FAILED", {
497
+ method: extra_data,
498
+ err: get_formatted_error_message(err)
499
+ })
500
+ end
501
+ end
502
+ end
503
+ end
@@ -0,0 +1,57 @@
1
+ # Copyright 2024-2026 Wingify Software Pvt. Ltd.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ require_relative '../models/campaign/campaign_model'
16
+ require_relative '../models/campaign/feature_model'
17
+ require_relative '../models/settings/settings_model'
18
+ require_relative '../models/user/context_model'
19
+ require_relative '../services/storage_service'
20
+ require_relative './data_type_util'
21
+ require_relative './decision_util'
22
+ require_relative './network_util'
23
+ require_relative './impression_util'
24
+
25
+ # Evaluates the rules for a given campaign and feature based on the provided context.
26
+ #
27
+ # @param settings [SettingsModel] The settings configuration for evaluation.
28
+ # @param feature [FeatureModel] The feature being evaluated.
29
+ # @param campaign [CampaignModel] The campaign associated with the feature.
30
+ # @param context [ContextModel] The user context for evaluation.
31
+ # @param evaluated_feature_map [Hash] A hash of evaluated features.
32
+ # @param meg_group_winner_campaigns [Hash] A hash of MEG group winner campaigns.
33
+ # @param storage_service [StorageService] The storage service for persistence.
34
+ # @param decision [Hash] The decision object that will be updated based on the evaluation.
35
+ # @return [Hash] A hash containing the result of the pre-segmentation and the whitelisted object.
36
+ def evaluate_rule(settings, feature, campaign, context, evaluated_feature_map, meg_group_winner_campaigns, storage_service, decision)
37
+ # Perform whitelisting and pre-segmentation checks
38
+ pre_segmentation_result, whitelisted_object = DecisionUtil.check_whitelisting_and_pre_seg(
39
+ settings, feature, campaign, context, evaluated_feature_map, meg_group_winner_campaigns, storage_service, decision
40
+ )
41
+
42
+ # If pre-segmentation is successful and a whitelisted object exists, proceed to send an impression
43
+ if pre_segmentation_result && whitelisted_object.is_a?(Hash) && !whitelisted_object.empty?
44
+ # Update the decision object with campaign and variation details
45
+ decision.merge!(
46
+ experiment_id: campaign.get_id,
47
+ experiment_key: campaign.get_key,
48
+ experiment_variation_id: whitelisted_object[:variation_id]
49
+ )
50
+
51
+ # Send an impression for the variation shown
52
+ create_and_send_impression_for_variation_shown(settings, campaign.get_id, whitelisted_object[:variation_id], context, feature.get_key)
53
+ end
54
+
55
+ # Return the results of the evaluation
56
+ { pre_segmentation_result: pre_segmentation_result, whitelisted_object: whitelisted_object, updated_decision: decision }
57
+ end
@@ -0,0 +1,38 @@
1
+ # Copyright 2024-2026 Wingify Software Pvt. Ltd.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ require_relative '../models/settings/settings_model'
16
+ require_relative 'campaign_util'
17
+ require_relative 'function_util'
18
+ require_relative 'gateway_service_util'
19
+
20
+ # Sets settings and adds campaigns to rules
21
+ #
22
+ # @param settings [Hash] The settings configuration
23
+ # @param vwo_client_instance [VWOClient] The VWOClient instance
24
+ def set_settings_and_add_campaigns_to_rules(settings, vwo_client_instance)
25
+ # Create settings model and assign it to vwo_client_instance
26
+ vwo_client_instance.settings = SettingsModel.new(settings)
27
+ vwo_client_instance.original_settings = settings
28
+
29
+ # Optimize loop by avoiding multiple calls to get_campaigns()
30
+ campaigns = vwo_client_instance.settings.get_campaigns
31
+ campaigns.each_with_index do |campaign, index|
32
+ CampaignUtil.set_variation_allocation(campaign)
33
+ campaigns[index] = campaign
34
+ end
35
+
36
+ add_linked_campaigns_to_settings(vwo_client_instance.settings)
37
+ add_is_gateway_service_required_flag(vwo_client_instance.settings)
38
+ end