vwo-fme-ruby-sdk 1.0.0 → 1.2.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 (80) hide show
  1. checksums.yaml +4 -4
  2. data/lib/resources/debug_messages.json +13 -0
  3. data/lib/resources/error_messages.json +30 -0
  4. data/lib/resources/info_messages.json +33 -0
  5. data/lib/resources/warn_messages.json +1 -0
  6. data/lib/vwo/api/get_flag.rb +236 -0
  7. data/lib/vwo/api/set_attribute.rb +57 -0
  8. data/lib/vwo/api/track_event.rb +77 -0
  9. data/lib/vwo/constants/constants.rb +54 -0
  10. data/lib/vwo/decorators/storage_decorator.rb +86 -0
  11. data/lib/vwo/{utils/logger_helper.rb → enums/api_enum.rb} +5 -10
  12. data/lib/vwo/enums/campaign_type_enum.rb +19 -0
  13. data/lib/vwo/enums/decision_types_enum.rb +18 -0
  14. data/lib/vwo/enums/event_enum.rb +19 -0
  15. data/lib/vwo/enums/headers_enum.rb +20 -0
  16. data/lib/vwo/enums/hooks_enum.rb +17 -0
  17. data/lib/vwo/enums/http_method_enum.rb +18 -0
  18. data/lib/vwo/enums/log_level_enum.rb +21 -0
  19. data/lib/vwo/enums/status_enum.rb +19 -0
  20. data/lib/vwo/enums/storage_enum.rb +22 -0
  21. data/lib/vwo/enums/url_enum.rb +21 -0
  22. data/lib/vwo/models/campaign/campaign_model.rb +192 -0
  23. data/lib/vwo/models/campaign/feature_model.rb +111 -0
  24. data/lib/vwo/models/campaign/impact_campaign_model.rb +38 -0
  25. data/lib/vwo/models/campaign/metric_model.rb +44 -0
  26. data/lib/vwo/models/campaign/rule_model.rb +56 -0
  27. data/lib/vwo/models/campaign/variable_model.rb +51 -0
  28. data/lib/vwo/models/campaign/variation_model.rb +137 -0
  29. data/lib/vwo/models/gateway_service_model.rb +39 -0
  30. data/lib/vwo/models/schemas/settings_schema_validation.rb +102 -0
  31. data/lib/vwo/models/settings/settings_model.rb +85 -0
  32. data/lib/vwo/models/storage/storage_data_model.rb +44 -0
  33. data/lib/vwo/models/user/context_model.rb +100 -0
  34. data/lib/vwo/models/user/context_vwo_model.rb +38 -0
  35. data/lib/vwo/{utils/feature_flag_response.rb → models/user/get_flag_response.rb} +14 -14
  36. data/lib/vwo/models/vwo_options_model.rb +107 -0
  37. data/lib/vwo/packages/decision_maker/decision_maker.rb +60 -0
  38. data/lib/vwo/packages/logger/core/log_manager.rb +90 -0
  39. data/lib/vwo/packages/logger/core/transport_manager.rb +87 -0
  40. data/lib/vwo/packages/logger/log_message_builder.rb +70 -0
  41. data/lib/vwo/packages/logger/logger.rb +38 -0
  42. data/lib/vwo/packages/logger/transports/console_transport.rb +49 -0
  43. data/lib/vwo/packages/network_layer/client/network_client.rb +107 -0
  44. data/lib/vwo/packages/network_layer/handlers/request_handler.rb +37 -0
  45. data/lib/vwo/packages/network_layer/manager/network_manager.rb +78 -0
  46. data/lib/vwo/packages/network_layer/models/global_request_model.rb +105 -0
  47. data/lib/vwo/packages/network_layer/models/request_model.rb +145 -0
  48. data/lib/vwo/packages/network_layer/models/response_model.rb +45 -0
  49. data/lib/vwo/packages/segmentation_evaluator/core/segmentation_manager.rb +76 -0
  50. data/lib/vwo/packages/segmentation_evaluator/enums/segment_operand_regex_enum.rb +29 -0
  51. data/lib/vwo/packages/segmentation_evaluator/enums/segment_operand_value_enum.rb +26 -0
  52. data/lib/vwo/packages/segmentation_evaluator/enums/segment_operator_value_enum.rb +30 -0
  53. data/lib/vwo/packages/segmentation_evaluator/evaluators/segment_evaluator.rb +210 -0
  54. data/lib/vwo/packages/segmentation_evaluator/evaluators/segment_operand_evaluator.rb +198 -0
  55. data/lib/vwo/packages/segmentation_evaluator/utils/segment_util.rb +44 -0
  56. data/lib/vwo/{constants.rb → packages/storage/connector.rb} +12 -10
  57. data/lib/vwo/packages/storage/storage.rb +45 -0
  58. data/lib/vwo/services/campaign_decision_service.rb +153 -0
  59. data/lib/vwo/services/hooks_service.rb +51 -0
  60. data/lib/vwo/services/logger_service.rb +83 -0
  61. data/lib/vwo/services/settings_service.rb +120 -0
  62. data/lib/vwo/services/storage_service.rb +65 -0
  63. data/lib/vwo/utils/campaign_util.rb +249 -0
  64. data/lib/vwo/utils/data_type_util.rb +105 -0
  65. data/lib/vwo/utils/decision_util.rb +253 -0
  66. data/lib/vwo/utils/function_util.rb +123 -0
  67. data/lib/vwo/utils/gateway_service_util.rb +101 -0
  68. data/lib/vwo/utils/impression_util.rb +49 -0
  69. data/lib/vwo/utils/log_message_util.rb +42 -0
  70. data/lib/vwo/utils/meg_util.rb +350 -0
  71. data/lib/vwo/utils/network_util.rb +235 -0
  72. data/lib/vwo/utils/rule_evaluation_util.rb +57 -0
  73. data/lib/vwo/utils/settings_util.rb +38 -0
  74. data/lib/vwo/utils/url_util.rb +46 -0
  75. data/lib/vwo/utils/uuid_util.rb +55 -0
  76. data/lib/vwo/vwo_builder.rb +156 -11
  77. data/lib/vwo/vwo_client.rb +163 -113
  78. data/lib/vwo.rb +49 -31
  79. metadata +191 -9
  80. data/lib/vwo/utils/request.rb +0 -89
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8f333409ec82d99443663437007b9b0934fe205e07e1cef53d905c911faf7980
4
- data.tar.gz: 7a866f482484ff481d503a90bca9534bbce6832daf99e81a1791a36d0e41e290
3
+ metadata.gz: 8e0d70f7fcb19c20548cdb55639655c0098f5fa149137014982baafb8bd185e9
4
+ data.tar.gz: 54c0eab76aef36e5de32fc4953e3cea7add5bbed100d1777c08da18cff900b01
5
5
  SHA512:
6
- metadata.gz: 0db232a732e10984da13efbc1b47a4e9bb72103a9453da6f910e4c76b43f35b507db4fbd556175e8428c10d9dd17c89fc45b8d097435b1e2133b247d3e101686
7
- data.tar.gz: 80119347d3b874637c6af47aa2ee91bdc2fe095a665c4b63de3d8761466e7e587557ad42c24cae879630a06fff211decec14328441fbf0cbf8175ef23ab389a2
6
+ metadata.gz: e78ca66f6fb6c71e60519658e04362c0b701d81ac3c79ecd18b8ad7aafedce4aec619dac425f1060b4c3f23aec55d43844645be69f998c4a280cd9a0ee70a9b7
7
+ data.tar.gz: 543ed4464c0e6685a6fa695d5237a573f0553f29f39bd13476451f2ba6e2a4ea21bfe595c01b1aceb356c9ca159e1c739d2f9e33f2eefc3ce9afcb70283c04ab
@@ -0,0 +1,13 @@
1
+ {
2
+ "API_CALLED": "API - {apiName} called",
3
+ "SERVICE_INITIALIZED": "VWO {service} initialized while creating an instance of SDK",
4
+
5
+ "EXPERIMENTS_EVALUATION_WHEN_ROLLOUT_PASSED": "Rollout rule got passed for user {userId}. Hence, evaluating experiments",
6
+ "EXPERIMENTS_EVALUATION_WHEN_NO_ROLLOUT_PRESENT": "No Rollout rules present for the feature. Hence, checking experiment rules",
7
+
8
+ "USER_BUCKET_TO_VARIATION": "User ID:{userId} for experiment:{campaignKey} having percent traffic:{percentTraffic} got bucket-value:{bucketValue} and hash-value:{hashValue}",
9
+
10
+ "IMPRESSION_FOR_TRACK_USER": "Impression built for vwo_variationShown(VWO standard event for tracking user) event haivng Account ID:{accountId}, User ID:{userId}, and experiment ID:{campaignId}",
11
+ "IMPRESSION_FOR_TRACK_GOAL": "Impression built for event:{eventName} event having Account ID:{accountId}, and user ID:{userId}",
12
+ "IMPRESSION_FOR_SYNC_VISITOR_PROP": "Impression built for {eventName}(VWO internal event) event for Account ID:{accountId}, and user ID:{userId}"
13
+ }
@@ -0,0 +1,30 @@
1
+ {
2
+ "API_THROW_ERROR": "API - {apiName} failed to execute. Trace:{err}",
3
+ "INIT_OPTIONS_ERROR": "[ERROR]: VWO-SDK {date} Options should be of type object",
4
+ "INIT_OPTIONS_SDK_KEY_ERROR": "[ERROR]: VWO-SDK {date} Please provide the sdkKey in the options and should be a of type string",
5
+ "INIT_OPTIONS_ACCOUNT_ID_ERROR": "[ERROR]: VWO-SDK {date} Please provide VWO account ID in the options and should be a of type string|number",
6
+
7
+ "INIT_OPTIONS_INVALID": "Invalid key:{key} passed in options. Should be of type:{correctType} and greater than equal to 1000",
8
+
9
+ "SETTINGS_FETCH_ERROR": "Settings could not be fetched. Error:{err}",
10
+ "SETTINGS_SCHEMA_INVALID": "Settings are not valid. Failed schema validation",
11
+
12
+ "POLLING_FETCH_SETTINGS_FAILED": "Error while fetching VWO settings with polling",
13
+
14
+ "API_INVALID_PARAM": "Key:{key} passed to API:{apiName} is not of valid type. Got type:{type}, should be:{correctType}",
15
+ "API_SETTING_INVALID": "Settings are not valid. Contact VWO Support",
16
+ "API_CONTEXT_INVALID": "Context should be an object and must contain a mandatory key - id, which is User ID",
17
+
18
+ "FEATURE_NOT_FOUND": "Feature not found for the key:{featureKey}",
19
+ "EVENT_NOT_FOUND": "Event:{eventName} not found in any of the features' metrics",
20
+
21
+ "STORED_DATA_ERROR": "Error in getting data from storage. Error:{err}",
22
+ "STORING_DATA_ERROR": "Key:{featureKey} is not valid. Not able to store data into storage",
23
+
24
+ "GATEWAY_URL_ERROR": "Please provide a valid URL for VWO Gateway Service while initializing the SDK",
25
+
26
+ "NETWORK_CALL_FAILED": "Error occurred while sending {method} request. Error:{err}",
27
+ "SETTINGS_FETCH_FAILED": "Failed to fetch settings and hence VWO client instance couldn't be updated when API: {apiName} got called having isViaWebhook param as {isViaWebhook}. Error: {err}",
28
+ "NETWORK_CALL_RETRY_ATTEMPT": "Request failed for {endPoint}, Error: {err}. Retrying in {delay} seconds, attempt {attempt} of {maxRetries}",
29
+ "NETWORK_CALL_RETRY_FAILED": "Max retries reached. Request failed for {endPoint}, Error: {err}"
30
+ }
@@ -0,0 +1,33 @@
1
+ {
2
+ "ON_INIT_ALREADY_RESOLVED": "[INFO]: VWO-SDK {date} {apiName} already resolved",
3
+ "ON_INIT_SETTINGS_FAILED": "[INFO]: VWO-SDK {date} VWO settings could not be fetched",
4
+
5
+ "POLLING_SET_SETTINGS": "There's a change in settings from the last settings fetched. Hence, instantiating a new VWO client internally",
6
+ "POLLING_NO_CHANGE_IN_SETTINGS": "No change in settings with the last settings fetched. Hence, not instantiating new VWO client",
7
+
8
+ "SETTINGS_FETCH_SUCCESS": "Settings fetched successfully",
9
+
10
+ "CLIENT_INITIALIZED": "VWO Client initialized",
11
+
12
+ "STORED_VARIATION_FOUND": "Variation {variationKey} found in storage for the user {userId} for the {experimentType} experiment:{experimentKey}",
13
+
14
+ "USER_PART_OF_CAMPAIGN": "User ID:{userId} is {notPart} part of experiment:{campaignKey}",
15
+ "SEGMENTATION_SKIP": "For userId:{userId} of experiment:{campaignKey}, segments was missing. Hence, skipping segmentation",
16
+ "SEGMENTATION_STATUS": "Segmentation {status} for userId:{userId} of experiment:{campaignKey}",
17
+
18
+ "USER_CAMPAIGN_BUCKET_INFO": "User ID:{userId} for experiment:{campaignKey} {status}",
19
+
20
+ "WHITELISTING_SKIP": "Whitelisting is not used for experiment:{campaignKey}, hence skipping evaluating whitelisting {variation} for User ID:{userId}",
21
+ "WHITELISTING_STATUS": "User ID:{userId} for experiment:{campaignKey} {status} whitelisting {variationString}",
22
+
23
+ "VARIATION_RANGE_ALLOCATION": "Variation:{variationKey} of experiment:{campaignKey} having weight:{variationWeight} got bucketing range: ({startRange} - {endRange})",
24
+
25
+ "IMPACT_ANALYSIS": "Tracking feature:{featureKey} being {status} for Impact Analysis Campaign for the user {userId}",
26
+
27
+ "MEG_SKIP_ROLLOUT_EVALUATE_EXPERIMENTS": "No rollout rule found for feature:{featureKey}. Hence, evaluating experiments",
28
+ "MEG_CAMPAIGN_FOUND_IN_STORAGE": "Campaign {campaignKey} found in storage for user ID:{userId}",
29
+ "MEG_CAMPAIGN_ELIGIBLE": "Campaign {campaignKey} is eligible for user ID:{userId}",
30
+ "MEG_WINNER_CAMPAIGN": "MEG: Campaign {campaignKey} is the winner for group {groupId} for user ID:{userId} {algo}",
31
+ "SETTINGS_UPDATED": "Settings fetched and updated successfully on the current VWO client instance when API: {apiName} got called having isViaWebhook param as {isViaWebhook}",
32
+ "NETWORK_CALL_SUCCESS": "Impression for {event} - {endPoint} was successfully received by VWO having Account ID:{accountId}, User ID:{userId} and UUID: {uuid}"
33
+ }
@@ -0,0 +1 @@
1
+ {}
@@ -0,0 +1,236 @@
1
+ # Copyright 2025 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/api_enum'
16
+ require_relative '../enums/campaign_type_enum'
17
+ require_relative '../enums/decision_types_enum'
18
+ require_relative '../decorators/storage_decorator'
19
+ require_relative '../services/storage_service'
20
+ require_relative '../models/campaign/feature_model'
21
+ require_relative '../models/campaign/campaign_model'
22
+ require_relative '../packages/segmentation_evaluator/core/segmentation_manager'
23
+ require_relative '../services/logger_service'
24
+ require_relative '../utils/campaign_util'
25
+ require_relative '../utils/rule_evaluation_util'
26
+ require_relative '../utils/impression_util'
27
+ require_relative '../utils/decision_util'
28
+ require_relative '../models/user/get_flag_response'
29
+
30
+ class FlagApi
31
+ # Get the flag for a given feature key and context
32
+ # @param feature_key [String] The key of the feature to get the flag for
33
+ # @param settings [SettingsModel] The settings for the VWO instance
34
+ # @param context [ContextModel] The context for the evaluation
35
+ # @param hooks_service [HooksService] The hooks service for the VWO instance
36
+ # @return [GetFlagResponse] The flag for the given feature key and context
37
+ def get(feature_key, settings, context, hooks_service)
38
+ is_enabled = false
39
+ rollout_variation_to_return = nil
40
+ experiment_variation_to_return = nil
41
+ should_check_for_experiments_rules = false
42
+
43
+ passed_rules_information = {} # for storing integration callback
44
+ evaluated_feature_map = {}
45
+
46
+ # Fetch feature object using the feature key
47
+ feature = get_feature_from_key(settings, feature_key)
48
+
49
+ decision = {
50
+ feature_name: feature&.get_name,
51
+ feature_id: feature&.get_id,
52
+ feature_key: feature&.get_key,
53
+ user_id: context&.get_id,
54
+ api: ApiEnum::GET_FLAG
55
+ }
56
+
57
+ storage_service = StorageService.new
58
+ stored_data = StorageDecorator.new.get_feature_from_storage(feature_key, context, storage_service)
59
+
60
+ if stored_data && stored_data[:experiment_variation_id]
61
+ if stored_data[:experiment_key]
62
+ variation = CampaignUtil.get_variation_from_campaign_key(settings, stored_data[:experiment_key], stored_data[:experiment_variation_id])
63
+
64
+ if variation
65
+ LoggerService.log(LogLevelEnum::INFO, "STORED_VARIATION_FOUND", {variationKey: variation.get_key, userId: context.get_id, experimentKey: stored_data[:experiment_key], experimentType: "experiment"})
66
+ return GetFlagResponse.new(true, variation.get_variables)
67
+ end
68
+ end
69
+ elsif stored_data && stored_data[:rollout_key] && stored_data[:rollout_id]
70
+ variation = CampaignUtil.get_variation_from_campaign_key(settings, stored_data[:rollout_key], stored_data[:rollout_variation_id])
71
+
72
+ if variation
73
+ LoggerService.log(LogLevelEnum::INFO, "STORED_VARIATION_FOUND", {variationKey: variation.get_key, userId: context.get_id, experimentKey: stored_data[:rollout_key], experimentType: "rollout"})
74
+ LoggerService.log(LogLevelEnum::DEBUG, "EXPERIMENTS_EVALUATION_WHEN_ROLLOUT_PASSED", {userId: context.get_id})
75
+
76
+ is_enabled = true
77
+ should_check_for_experiments_rules = true
78
+ rollout_variation_to_return = variation
79
+ feature_info = {
80
+ rollout_id: stored_data[:rollout_id],
81
+ rollout_key: stored_data[:rollout_key],
82
+ rollout_variation_id: stored_data[:rollout_variation_id]
83
+ }
84
+ evaluated_feature_map[feature_key] = feature_info
85
+ passed_rules_information.merge!(feature_info)
86
+ end
87
+ end
88
+
89
+ if feature.nil?
90
+ LoggerService.log(LogLevelEnum::ERROR, "FEATURE_NOT_FOUND", {featureKey: feature_key})
91
+ return GetFlagResponse.new(false, [])
92
+ end
93
+
94
+ # Segmentation evaluation
95
+ SegmentationManager.instance.set_contextual_data(settings, feature, context)
96
+
97
+ # Get all rollout rules
98
+ rollout_rules = get_specific_rules_based_on_type(feature, CampaignTypeEnum::ROLLOUT)
99
+
100
+ if rollout_rules.any? && !is_enabled
101
+ rollout_rules_to_evaluate = []
102
+
103
+ rollout_rules.each do |rule|
104
+ result = evaluate_rule(settings, feature, rule, context, evaluated_feature_map, nil, storage_service, decision)
105
+ pre_segmentation_result = result[:pre_segmentation_result]
106
+ updated_decision = result[:updated_decision]
107
+ decision.merge!(updated_decision) if updated_decision
108
+
109
+ if pre_segmentation_result
110
+ rollout_rules_to_evaluate << rule
111
+ evaluated_feature_map[feature_key] = {
112
+ rollout_id: rule.get_id,
113
+ rollout_key: rule.get_key,
114
+ rollout_variation_id: rule.get_variations.first&.get_id
115
+ }
116
+ break
117
+ end
118
+ end
119
+
120
+ if rollout_rules_to_evaluate.any?
121
+ passed_rollout_campaign = CampaignModel.new.model_from_dictionary(rollout_rules_to_evaluate.first)
122
+ variation = DecisionUtil.evaluate_traffic_and_get_variation(settings, passed_rollout_campaign, context.get_id)
123
+
124
+ if variation
125
+ is_enabled = true
126
+ should_check_for_experiments_rules = true
127
+ rollout_variation_to_return = variation
128
+ update_integrations_decision_object(passed_rollout_campaign, variation, passed_rules_information, decision)
129
+
130
+ create_and_send_impression_for_variation_shown(settings, passed_rollout_campaign.get_id, variation.get_id, context)
131
+ end
132
+ end
133
+ elsif rollout_rules.empty?
134
+ LoggerService.log(LogLevelEnum::DEBUG, "EXPERIMENTS_EVALUATION_WHEN_NO_ROLLOUT_PRESENT")
135
+ should_check_for_experiments_rules = true
136
+ end
137
+
138
+ if should_check_for_experiments_rules
139
+ experiment_rules = get_all_experiment_rules(feature)
140
+ experiment_rules_to_evaluate = []
141
+ meg_group_winner_campaigns = {}
142
+
143
+ experiment_rules.each do |rule|
144
+ result = evaluate_rule(settings, feature, rule, context, evaluated_feature_map, meg_group_winner_campaigns, storage_service, decision)
145
+ pre_segmentation_result = result[:pre_segmentation_result]
146
+ whitelisted_object = result[:whitelisted_object]
147
+ updated_decision = result[:updated_decision]
148
+ decision.merge!(updated_decision) if updated_decision
149
+
150
+ if pre_segmentation_result
151
+ if whitelisted_object.nil?
152
+ experiment_rules_to_evaluate << rule
153
+ else
154
+ is_enabled = true
155
+ experiment_variation_to_return = whitelisted_object[:variation]
156
+ passed_rules_information.merge!(
157
+ experiment_id: rule.get_id,
158
+ experiment_key: rule.get_key,
159
+ experiment_variation_id: whitelisted_object[:variation_id]
160
+ )
161
+ end
162
+ break
163
+ end
164
+ end
165
+
166
+ if experiment_rules_to_evaluate.any?
167
+ campaign = CampaignModel.new.model_from_dictionary(experiment_rules_to_evaluate.first)
168
+ variation = DecisionUtil.evaluate_traffic_and_get_variation(settings, campaign, context.get_id)
169
+
170
+ if variation
171
+ is_enabled = true
172
+ experiment_variation_to_return = variation
173
+ update_integrations_decision_object(campaign, variation, passed_rules_information, decision)
174
+
175
+ create_and_send_impression_for_variation_shown(settings, campaign.get_id, variation.get_id, context)
176
+ end
177
+ end
178
+ end
179
+
180
+ # Store evaluated feature in storage
181
+ if is_enabled
182
+ StorageDecorator.new.set_data_in_storage(
183
+ { feature_key: feature_key, context: context }.merge(passed_rules_information),
184
+ storage_service
185
+ )
186
+ end
187
+
188
+ # Execute hooks
189
+ hooks_service.set(decision)
190
+ hooks_service.execute(hooks_service.get)
191
+
192
+ if feature&.get_impact_campaign&.get_campaign_id
193
+ LoggerService.log(
194
+ LogLevelEnum::INFO,
195
+ "IMPACT_ANALYSIS",
196
+ {
197
+ userId: context.get_id,
198
+ featureKey: feature_key,
199
+ status: is_enabled ? 'enabled' : 'disabled'
200
+ }
201
+ )
202
+
203
+ variation_id = is_enabled ? 2 : 1 # 2 for Variation(flag enabled), 1 for Control(flag disabled)
204
+
205
+ create_and_send_impression_for_variation_shown(
206
+ settings,
207
+ feature&.get_impact_campaign&.get_campaign_id,
208
+ variation_id,
209
+ context
210
+ )
211
+ end
212
+
213
+ # Return final evaluated feature flag
214
+ return GetFlagResponse.new(is_enabled, experiment_variation_to_return&.get_variables || rollout_variation_to_return&.get_variables || [])
215
+ end
216
+
217
+ private
218
+
219
+ def update_integrations_decision_object(campaign, variation, passed_rules_information, decision)
220
+ if campaign.get_type == CampaignTypeEnum::ROLLOUT
221
+ passed_rules_information.merge!(
222
+ rollout_id: campaign.get_id,
223
+ rollout_key: campaign.get_key,
224
+ rollout_variation_id: variation.get_id
225
+ )
226
+ else
227
+ passed_rules_information.merge!(
228
+ experiment_id: campaign.get_id,
229
+ experiment_key: campaign.get_key,
230
+ experiment_variation_id: variation.get_id
231
+ )
232
+ end
233
+ decision.merge!(passed_rules_information)
234
+ end
235
+ end
236
+
@@ -0,0 +1,57 @@
1
+ # Copyright 2025 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/user/context_model'
16
+ require_relative '../enums/event_enum'
17
+ require_relative '../utils/network_util'
18
+ require_relative '../models/settings/settings_model'
19
+
20
+ class SetAttributeApi
21
+ # Sets multiple attributes for a user in a single network call.
22
+ # @param settings [SettingsModel] Configuration settings.
23
+ # @param attributes [Hash] Key-value map of attributes.
24
+ # @param context [ContextModel] Context containing user information.
25
+ def set_attribute(settings, attributes, context)
26
+ create_impression_for_attributes(settings, attributes, context)
27
+ end
28
+
29
+ private
30
+
31
+ # Creates an impression for multiple user attributes and sends it to the server.
32
+ # @param settings [SettingsModel] Configuration settings.
33
+ # @param attributes [Hash] Key-value map of attributes.
34
+ # @param context [ContextModel] Context containing user information.
35
+ def create_impression_for_attributes(settings, attributes, context)
36
+ # Retrieve base properties for the event
37
+ properties = NetworkUtil.get_events_base_properties(
38
+ settings,
39
+ EventEnum::VWO_SYNC_VISITOR_PROP,
40
+ URI.encode_www_form_component(context.user_agent),
41
+ context.ip_address
42
+ )
43
+
44
+ # Construct payload data for multiple attributes
45
+ payload = NetworkUtil.get_attribute_payload_data(
46
+ settings,
47
+ context.id,
48
+ EventEnum::VWO_SYNC_VISITOR_PROP,
49
+ attributes,
50
+ context.user_agent,
51
+ context.ip_address
52
+ )
53
+
54
+ # Send the constructed payload via POST request
55
+ NetworkUtil.send_post_api_request(properties, payload)
56
+ end
57
+ end
@@ -0,0 +1,77 @@
1
+ # Copyright 2025 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/api_enum'
16
+ require_relative '../models/settings/settings_model'
17
+ require_relative '../models/user/context_model'
18
+ require_relative '../services/hooks_service'
19
+ require_relative '../utils/function_util'
20
+ require_relative '../utils/network_util'
21
+ require_relative '../services/logger_service'
22
+ class TrackApi
23
+ # Tracks an event with given properties and context.
24
+ # @param settings [SettingsModel] Configuration settings.
25
+ # @param event_name [String] Name of the event to track.
26
+ # @param context [ContextModel] Contextual information like user details.
27
+ # @param event_properties [Hash] Properties associated with the event.
28
+ # @param hooks_service [HooksService] Manager for handling hooks and callbacks.
29
+ # @return [Hash] A hash indicating success or failure of event tracking.
30
+ def track(settings, event_name, context, event_properties, hooks_service)
31
+ if does_event_belong_to_any_feature(event_name, settings)
32
+ # Create an impression for the track event
33
+ create_impression_for_track(settings, event_name, context, event_properties)
34
+
35
+ # Set and execute integration callback for the track event
36
+ hooks_service.set({ event_name: event_name, api: ApiEnum::TRACK })
37
+ hooks_service.execute(hooks_service.get)
38
+
39
+ return { event_name: true }
40
+ end
41
+
42
+ # Log an error if the event does not exist
43
+ LoggerService.log(LogLevelEnum::ERROR, "EVENT_NOT_FOUND", { eventName: event_name })
44
+
45
+ { event_name: false }
46
+ end
47
+
48
+ private
49
+
50
+ # Creates an impression for a track event and sends it via a POST API request.
51
+ # @param settings [SettingsModel] Configuration settings.
52
+ # @param event_name [String] Name of the event to track.
53
+ # @param context [ContextModel] User details.
54
+ # @param event_properties [Hash] Properties associated with the event.
55
+ def create_impression_for_track(settings, event_name, context, event_properties)
56
+ # Get base properties for the event
57
+ properties = NetworkUtil.get_events_base_properties(
58
+ settings,
59
+ event_name,
60
+ URI.encode_www_form_component(context.user_agent),
61
+ context.ip_address
62
+ )
63
+
64
+ # Prepare the payload for the track goal
65
+ payload = NetworkUtil.get_track_goal_payload_data(
66
+ settings,
67
+ context.id,
68
+ event_name,
69
+ event_properties,
70
+ context.user_agent,
71
+ context.ip_address
72
+ )
73
+
74
+ # Send the prepared payload via POST API request
75
+ NetworkUtil.send_post_api_request(properties, payload)
76
+ end
77
+ end
@@ -0,0 +1,54 @@
1
+ # Copyright 2025 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
+ # Constants file for Ruby SDK
16
+
17
+ # Define the Constants module
18
+ module Constants
19
+ SDK_NAME = 'vwo-fme-ruby-sdk'.freeze
20
+ SDK_VERSION = '1.2.0'.freeze
21
+
22
+ MAX_TRAFFIC_PERCENT = 100
23
+ MAX_TRAFFIC_VALUE = 10_000
24
+ STATUS_RUNNING = 'RUNNING'.freeze
25
+
26
+ SEED_VALUE = 1
27
+ MAX_EVENTS_PER_REQUEST = 5_000
28
+ DEFAULT_REQUEST_TIME_INTERVAL = 600 # 10 minutes in seconds
29
+ DEFAULT_EVENTS_PER_REQUEST = 100
30
+
31
+ SEED_URL = 'https://vwo.com'.freeze # Define SEED_URL
32
+ HTTP_PROTOCOL = 'http'.freeze
33
+ HTTPS_PROTOCOL = 'https'.freeze
34
+
35
+ SETTINGS = 'settings'.freeze
36
+ SETTINGS_EXPIRY = 10_000_000
37
+ SETTINGS_TIMEOUT = 50_000
38
+
39
+ HOST_NAME = 'dev.visualwebsiteoptimizer.com'.freeze
40
+ SETTINGS_ENDPOINT = '/server-side/v2-settings'.freeze
41
+ WEBHOOK_SETTINGS_ENDPOINT = '/server-side/v2-pull'.freeze
42
+ LOCATION_ENDPOINT = '/getLocation'.freeze
43
+
44
+ VWO_FS_ENVIRONMENT = 'vwo_fs_environment'.freeze
45
+
46
+ RANDOM_ALGO = 1
47
+
48
+ API_VERSION = '1'.freeze
49
+
50
+ VWO_META_MEG_KEY = '_vwo_meta_meg_'.freeze
51
+
52
+ SHOULD_USE_THREADING = true
53
+ MAX_POOL_SIZE = 5
54
+ end
@@ -0,0 +1,86 @@
1
+ # Copyright 2025 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/storage_enum'
16
+ require_relative '../models/campaign/feature_model'
17
+ require_relative '../models/campaign/variation_model'
18
+ require_relative '../services/storage_service'
19
+ require_relative '../models/user/context_model'
20
+ require_relative '../services/logger_service'
21
+ require_relative '../enums/log_level_enum'
22
+ class StorageDecorator
23
+ # Retrieves a feature from storage based on the feature key and user.
24
+ # @param feature_key [String] The key of the feature to retrieve.
25
+ # @param context [ContextModel] The user object.
26
+ # @param storage_service [StorageService] The storage service instance.
27
+ # @return [Hash, StorageEnum] The retrieved feature or relevant status.
28
+ def get_feature_from_storage(feature_key, context, storage_service)
29
+ campaign_map = storage_service.get_data_from_storage(feature_key, context)
30
+
31
+ case campaign_map
32
+ when StorageEnum::STORAGE_UNDEFINED, StorageEnum::NO_DATA_FOUND,
33
+ StorageEnum::CAMPAIGN_PAUSED, StorageEnum::WHITELISTED_VARIATION
34
+ nil # Return nil when there's no valid data
35
+ when StorageEnum::INCORRECT_DATA, StorageEnum::VARIATION_NOT_FOUND
36
+ campaign_map # Return the relevant error constant
37
+ else
38
+ campaign_map # Return valid stored data
39
+ end
40
+ end
41
+
42
+ # Sets data in storage based on the provided data object.
43
+ # @param data [Hash] The data to be stored, including feature key and user details.
44
+ # @param storage_service [StorageService] The storage service instance.
45
+ def set_data_in_storage(data, storage_service)
46
+ feature_key = data[:feature_key]
47
+ context = data[:context]
48
+ rollout_id = data[:rollout_id]
49
+ rollout_key = data[:rollout_key]
50
+ rollout_variation_id = data[:rollout_variation_id]
51
+ experiment_id = data[:experiment_id]
52
+ experiment_key = data[:experiment_key]
53
+ experiment_variation_id = data[:experiment_variation_id]
54
+
55
+ if feature_key.nil?
56
+ LoggerService.log(LogLevelEnum::ERROR, "STORING_DATA_ERROR", { key: 'featureKey' })
57
+ raise 'Feature key is missing'
58
+ end
59
+
60
+ if context.nil? || context.id.nil?
61
+ LoggerService.log(LogLevelEnum::ERROR, "STORING_DATA_ERROR", { key: 'Context or Context.id' })
62
+ raise 'Context ID is missing'
63
+ end
64
+
65
+ if rollout_key && !experiment_key && !rollout_variation_id
66
+ LoggerService.log(LogLevelEnum::ERROR, "STORING_DATA_ERROR", { key: 'Variation:(rolloutKey, experimentKey or rolloutVariationId)' })
67
+ raise 'Invalid rollout variation'
68
+ end
69
+
70
+ if experiment_key && !experiment_variation_id
71
+ LoggerService.log(LogLevelEnum::ERROR, "STORING_DATA_ERROR", { key: 'Variation:(experimentKey or rolloutVariationId)' })
72
+ raise 'Invalid experiment variation'
73
+ end
74
+
75
+ storage_service.set_data_in_storage({
76
+ feature_key: feature_key,
77
+ user_id: context.id,
78
+ rollout_id: rollout_id,
79
+ rollout_key: rollout_key,
80
+ rollout_variation_id: rollout_variation_id,
81
+ experiment_id: experiment_id,
82
+ experiment_key: experiment_key,
83
+ experiment_variation_id: experiment_variation_id
84
+ })
85
+ end
86
+ end
@@ -1,4 +1,4 @@
1
- # Copyright 2024 Wingify Software Pvt. Ltd.
1
+ # Copyright 2025 Wingify Software Pvt. Ltd.
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -12,13 +12,8 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
- require 'logger'
16
-
17
- module LoggerHelper
18
- # Singleton logger instance
19
- def self.logger
20
- @logger ||= Logger.new(STDOUT) # You can log to a file like 'application.log'
21
- @logger.level = Logger::DEBUG # Set the default logging level
22
- @logger
15
+ module ApiEnum
16
+ GET_FLAG = 'getFlag'
17
+ TRACK = 'track'
23
18
  end
24
- end
19
+
@@ -0,0 +1,19 @@
1
+ # Copyright 2025 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
+ module CampaignTypeEnum
16
+ ROLLOUT = 'FLAG_ROLLOUT'
17
+ AB = 'FLAG_TESTING'
18
+ PERSONALIZE = 'FLAG_PERSONALIZE'
19
+ end
@@ -0,0 +1,18 @@
1
+ # Copyright 2025 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
+ module DecisionTypes
16
+ CAMPAIGN_DECISION = 'CAMPAIGN_DECISION'
17
+ end
18
+