vwo-fme-ruby-sdk 1.0.0 → 1.1.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 (76) hide show
  1. checksums.yaml +4 -4
  2. data/lib/vwo/api/get_flag.rb +236 -0
  3. data/lib/vwo/api/set_attribute.rb +57 -0
  4. data/lib/vwo/api/track_event.rb +77 -0
  5. data/lib/vwo/constants/constants.rb +54 -0
  6. data/lib/vwo/decorators/storage_decorator.rb +86 -0
  7. data/lib/vwo/{utils/logger_helper.rb → enums/api_enum.rb} +5 -10
  8. data/lib/vwo/enums/campaign_type_enum.rb +19 -0
  9. data/lib/vwo/enums/decision_types_enum.rb +18 -0
  10. data/lib/vwo/enums/event_enum.rb +19 -0
  11. data/lib/vwo/enums/headers_enum.rb +20 -0
  12. data/lib/vwo/enums/hooks_enum.rb +17 -0
  13. data/lib/vwo/enums/http_method_enum.rb +18 -0
  14. data/lib/vwo/enums/log_level_enum.rb +21 -0
  15. data/lib/vwo/enums/status_enum.rb +19 -0
  16. data/lib/vwo/enums/storage_enum.rb +22 -0
  17. data/lib/vwo/enums/url_enum.rb +21 -0
  18. data/lib/vwo/models/campaign/campaign_model.rb +192 -0
  19. data/lib/vwo/models/campaign/feature_model.rb +111 -0
  20. data/lib/vwo/models/campaign/impact_campaign_model.rb +38 -0
  21. data/lib/vwo/models/campaign/metric_model.rb +44 -0
  22. data/lib/vwo/models/campaign/rule_model.rb +56 -0
  23. data/lib/vwo/models/campaign/variable_model.rb +51 -0
  24. data/lib/vwo/models/campaign/variation_model.rb +137 -0
  25. data/lib/vwo/models/gateway_service_model.rb +39 -0
  26. data/lib/vwo/models/schemas/settings_schema_validation.rb +102 -0
  27. data/lib/vwo/models/settings/settings_model.rb +85 -0
  28. data/lib/vwo/models/storage/storage_data_model.rb +44 -0
  29. data/lib/vwo/models/user/context_model.rb +100 -0
  30. data/lib/vwo/models/user/context_vwo_model.rb +38 -0
  31. data/lib/vwo/{utils/feature_flag_response.rb → models/user/get_flag_response.rb} +14 -14
  32. data/lib/vwo/models/vwo_options_model.rb +107 -0
  33. data/lib/vwo/packages/decision_maker/decision_maker.rb +60 -0
  34. data/lib/vwo/packages/logger/core/log_manager.rb +90 -0
  35. data/lib/vwo/packages/logger/core/transport_manager.rb +87 -0
  36. data/lib/vwo/packages/logger/log_message_builder.rb +70 -0
  37. data/lib/vwo/packages/logger/logger.rb +38 -0
  38. data/lib/vwo/packages/logger/transports/console_transport.rb +49 -0
  39. data/lib/vwo/packages/network_layer/client/network_client.rb +107 -0
  40. data/lib/vwo/packages/network_layer/handlers/request_handler.rb +37 -0
  41. data/lib/vwo/packages/network_layer/manager/network_manager.rb +78 -0
  42. data/lib/vwo/packages/network_layer/models/global_request_model.rb +105 -0
  43. data/lib/vwo/packages/network_layer/models/request_model.rb +145 -0
  44. data/lib/vwo/packages/network_layer/models/response_model.rb +45 -0
  45. data/lib/vwo/packages/segmentation_evaluator/core/segmentation_manager.rb +76 -0
  46. data/lib/vwo/packages/segmentation_evaluator/enums/segment_operand_regex_enum.rb +29 -0
  47. data/lib/vwo/packages/segmentation_evaluator/enums/segment_operand_value_enum.rb +26 -0
  48. data/lib/vwo/packages/segmentation_evaluator/enums/segment_operator_value_enum.rb +30 -0
  49. data/lib/vwo/packages/segmentation_evaluator/evaluators/segment_evaluator.rb +210 -0
  50. data/lib/vwo/packages/segmentation_evaluator/evaluators/segment_operand_evaluator.rb +198 -0
  51. data/lib/vwo/packages/segmentation_evaluator/utils/segment_util.rb +44 -0
  52. data/lib/vwo/{constants.rb → packages/storage/connector.rb} +12 -10
  53. data/lib/vwo/packages/storage/storage.rb +45 -0
  54. data/lib/vwo/services/campaign_decision_service.rb +153 -0
  55. data/lib/vwo/services/hooks_service.rb +51 -0
  56. data/lib/vwo/services/logger_service.rb +83 -0
  57. data/lib/vwo/services/settings_service.rb +120 -0
  58. data/lib/vwo/services/storage_service.rb +65 -0
  59. data/lib/vwo/utils/campaign_util.rb +249 -0
  60. data/lib/vwo/utils/data_type_util.rb +105 -0
  61. data/lib/vwo/utils/decision_util.rb +253 -0
  62. data/lib/vwo/utils/function_util.rb +123 -0
  63. data/lib/vwo/utils/gateway_service_util.rb +101 -0
  64. data/lib/vwo/utils/impression_util.rb +49 -0
  65. data/lib/vwo/utils/log_message_util.rb +42 -0
  66. data/lib/vwo/utils/meg_util.rb +350 -0
  67. data/lib/vwo/utils/network_util.rb +235 -0
  68. data/lib/vwo/utils/rule_evaluation_util.rb +57 -0
  69. data/lib/vwo/utils/settings_util.rb +38 -0
  70. data/lib/vwo/utils/url_util.rb +46 -0
  71. data/lib/vwo/utils/uuid_util.rb +55 -0
  72. data/lib/vwo/vwo_builder.rb +156 -11
  73. data/lib/vwo/vwo_client.rb +163 -113
  74. data/lib/vwo.rb +49 -31
  75. metadata +187 -9
  76. data/lib/vwo/utils/request.rb +0 -89
@@ -0,0 +1,253 @@
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/campaign_type_enum'
16
+ require_relative '../enums/status_enum'
17
+ require_relative '../models/campaign/campaign_model'
18
+ require_relative '../models/campaign/feature_model'
19
+ require_relative '../models/campaign/variation_model'
20
+ require_relative '../models/settings/settings_model'
21
+ require_relative '../models/user/context_model'
22
+ require_relative '../packages/decision_maker/decision_maker'
23
+ require_relative '../packages/segmentation_evaluator/core/segmentation_manager'
24
+ require_relative '../services/campaign_decision_service'
25
+ require_relative '../services/storage_service'
26
+ require_relative '../utils/data_type_util'
27
+ require_relative '../constants/constants'
28
+ require_relative '../utils/campaign_util'
29
+ require_relative '../utils/function_util'
30
+ require_relative '../utils/meg_util'
31
+ require_relative '../utils/uuid_util'
32
+ require_relative '../decorators/storage_decorator'
33
+ require_relative '../services/logger_service'
34
+ require_relative '../enums/log_level_enum'
35
+
36
+ class DecisionUtil
37
+ # Check if the campaign satisfies whitelisting and pre-segmentation
38
+ # @param settings [SettingsModel] The settings for the VWO instance
39
+ # @param feature [FeatureModel] The feature to evaluate
40
+ # @param campaign [CampaignModel] The campaign to evaluate
41
+ # @param context [ContextModel] The context for the evaluation
42
+ # @param evaluated_feature_map [Hash] The map of evaluated features
43
+ # @param meg_group_winner_campaigns [Hash] The map of MEG group winner campaigns
44
+ def self.check_whitelisting_and_pre_seg(settings, feature, campaign, context, evaluated_feature_map, meg_group_winner_campaigns, storage_service, decision)
45
+ vwo_user_id = UUIDUtil.get_uuid(context.get_id, settings.get_account_id)
46
+ campaign_id = campaign.get_id
47
+
48
+ if campaign.get_type == CampaignTypeEnum::AB
49
+ # Set _vwoUserId for variation targeting variables
50
+ variation_targeting_vars = context.get_variation_targeting_variables || {}
51
+ variation_targeting_vars["_vwoUserId"] = campaign.get_is_user_list_enabled ? vwo_user_id : context.get_id
52
+ context.set_variation_targeting_variables(variation_targeting_vars)
53
+ decision[:variation_targeting_variables] = variation_targeting_vars
54
+
55
+ # Check for whitelisting
56
+ if campaign.get_is_forced_variation_enabled
57
+ whitelisted_variation = check_campaign_whitelisting(campaign, context)
58
+ return [true, whitelisted_variation] if whitelisted_variation && !whitelisted_variation.empty?
59
+ else
60
+ LoggerService.log(LogLevelEnum::INFO, "WHITELISTING_SKIP", {
61
+ campaignKey: campaign.get_rule_key,
62
+ userId: context.get_id
63
+ })
64
+ end
65
+ end
66
+
67
+ # User list segment check for campaign pre-segmentation
68
+ custom_vars = context.get_custom_variables || {}
69
+ custom_vars["_vwoUserId"] = campaign.get_is_user_list_enabled ? vwo_user_id : context.get_id
70
+ context.set_custom_variables(custom_vars)
71
+ decision[:custom_variables] = custom_vars
72
+
73
+ # Check if rule belongs to Mutually Exclusive Group (MEG)
74
+ group_details = CampaignUtil.get_group_details_if_campaign_part_of_it(settings, campaign_id, campaign.get_type == CampaignTypeEnum::PERSONALIZE ? campaign.get_variations[0].get_id : nil)
75
+ group_id = group_details[:group_id]
76
+
77
+ # Check if the group has already been evaluated
78
+ group_winner_campaign_id = meg_group_winner_campaigns[group_id] if meg_group_winner_campaigns && meg_group_winner_campaigns.key?(group_id)
79
+ return evaluate_meg_campaign(group_winner_campaign_id, campaign, context, meg_group_winner_campaigns, group_id) if group_winner_campaign_id
80
+
81
+ # Check in storage if the group was already evaluated
82
+ stored_data = StorageDecorator.new.get_feature_from_storage("#{Constants::VWO_META_MEG_KEY}#{group_id}", context, storage_service)
83
+ if stored_data && stored_data[:experiment_key] && stored_data[:experiment_id]
84
+ LoggerService.log(LogLevelEnum::INFO, "MEG_CAMPAIGN_FOUND_IN_STORAGE", {
85
+ campaignKey: stored_data[:experiment_key],
86
+ userId: context.get_id
87
+ })
88
+
89
+ if stored_data[:experiment_id] == campaign_id
90
+ return evaluate_meg_personalization(campaign, stored_data, meg_group_winner_campaigns, group_id)
91
+ end
92
+ meg_group_winner_campaigns[group_id] = stored_data[:experiment_variation_id] != -1 ? "#{stored_data[:experiment_id]}_#{stored_data[:experiment_variation_id]}" : stored_data[:experiment_id]
93
+ return [false, nil]
94
+ end
95
+
96
+ # Pre-segmentation check
97
+ pre_segmentation_passed = CampaignDecisionService.new.get_pre_segmentation_decision(campaign, context)
98
+
99
+ if pre_segmentation_passed && group_id
100
+ winner_campaign = evaluate_groups(settings, feature, group_id, evaluated_feature_map, context, storage_service)
101
+ return evaluate_meg_campaign_winner(winner_campaign, campaign, context, meg_group_winner_campaigns, group_id)
102
+ end
103
+
104
+ [pre_segmentation_passed, nil]
105
+ end
106
+
107
+ # Evaluate the MEG campaign
108
+ # @param group_winner_campaign_id [String] The ID of the MEG group winner campaign
109
+ # @param campaign [CampaignModel] The campaign to evaluate
110
+ # @param context [ContextModel] The context for the evaluation
111
+ # @param meg_group_winner_campaigns [Hash] The map of MEG group winner campaigns
112
+ # @param group_id [String] The ID of the MEG group
113
+ def self.evaluate_meg_campaign(group_winner_campaign_id, campaign, context, meg_group_winner_campaigns, group_id)
114
+ if campaign.get_type == CampaignTypeEnum::AB && group_winner_campaign_id == campaign.get_id
115
+ return [true, nil]
116
+ elsif campaign.get_type == CampaignTypeEnum::PERSONALIZE && group_winner_campaign_id == "#{campaign.get_id}_#{campaign.get_variations[0].get_id}"
117
+ return [true, nil]
118
+ end
119
+ [false, nil]
120
+ end
121
+
122
+ def self.evaluate_meg_personalization(campaign, stored_data, meg_group_winner_campaigns, group_id)
123
+ if campaign.get_type == CampaignTypeEnum::PERSONALIZE
124
+ if stored_data[:experiment_variation_id] == campaign.get_variations[0].get_id
125
+ return [true, nil]
126
+ else
127
+ meg_group_winner_campaigns[group_id] = "#{stored_data[:experiment_id]}_#{stored_data[:experiment_variation_id]}"
128
+ return [false, nil]
129
+ end
130
+ else
131
+ return [true, nil]
132
+ end
133
+ end
134
+
135
+ # Evaluate the MEG campaign winner
136
+ # @param winner_campaign [CampaignModel] The winner campaign
137
+ # @param campaign [CampaignModel] The campaign to evaluate
138
+ # @param context [ContextModel] The context for the evaluation
139
+ # @param meg_group_winner_campaigns [Hash] The map of MEG group winner campaigns
140
+ # @param group_id [String] The ID of the MEG group
141
+ def self.evaluate_meg_campaign_winner(winner_campaign, campaign, context, meg_group_winner_campaigns, group_id)
142
+ if winner_campaign && winner_campaign.get_id == campaign.get_id
143
+ if winner_campaign.get_type == CampaignTypeEnum::AB
144
+ return [true, nil]
145
+ else
146
+ # if personalise then check if the requested variation is the winner
147
+ if winner_campaign.get_variations[0].get_id == campaign.get_variations[0].get_id
148
+ return [true, nil]
149
+ else
150
+ meg_group_winner_campaigns[group_id] = "#{winner_campaign.get_id}_#{winner_campaign.get_variations[0].get_id}"
151
+ return [false, nil]
152
+ end
153
+ end
154
+ elsif winner_campaign
155
+ if winner_campaign.get_type == CampaignTypeEnum::AB
156
+ meg_group_winner_campaigns[group_id] = winner_campaign.get_id
157
+ else
158
+ meg_group_winner_campaigns[group_id] = "#{winner_campaign.get_id}_#{winner_campaign.get_variations[0].get_id}"
159
+ end
160
+ return [false, nil]
161
+ end
162
+
163
+ meg_group_winner_campaigns[group_id] = -1
164
+ [false, nil]
165
+ end
166
+
167
+ # Evaluate the traffic and get the variation
168
+ # @param settings [SettingsModel] The settings for the VWO instance
169
+ # @param campaign [CampaignModel] The campaign to evaluate
170
+ # @param user_id [String] The ID of the user
171
+ def self.evaluate_traffic_and_get_variation(settings, campaign, user_id)
172
+ variation = CampaignDecisionService.new.get_variation_alloted(user_id, settings.get_account_id, campaign)
173
+ if variation.nil?
174
+ LoggerService.log(LogLevelEnum::INFO, "USER_CAMPAIGN_BUCKET_INFO", {
175
+ campaignKey: campaign.get_type == CampaignTypeEnum::AB ? campaign.get_key : "#{campaign.get_name}_#{campaign.get_rule_key}",
176
+ userId: user_id,
177
+ status: 'did not get any variation'
178
+ })
179
+ return nil
180
+ end
181
+ LoggerService.log(LogLevelEnum::INFO, "USER_CAMPAIGN_BUCKET_INFO", {
182
+ campaignKey: campaign.get_type == CampaignTypeEnum::AB ? campaign.get_key : "#{campaign.get_name}_#{campaign.get_rule_key}",
183
+ userId: user_id,
184
+ status: "got variation: #{variation.get_key}"
185
+ })
186
+ variation
187
+ end
188
+
189
+ # Check if the campaign satisfies whitelisting
190
+ # @param campaign [CampaignModel] The campaign to evaluate
191
+ # @param context [ContextModel] The context for the evaluation
192
+ def self.check_campaign_whitelisting(campaign, context)
193
+ # Check if the campaign satisfies whitelisting
194
+ whitelisting_result = evaluate_whitelisting(campaign, context)
195
+ status = whitelisting_result ? StatusEnum::PASSED : StatusEnum::FAILED
196
+ variation_string = whitelisting_result ? whitelisting_result[:variation].get_key : ''
197
+
198
+ LoggerService.log(LogLevelEnum::INFO, "WHITELISTING_STATUS", {
199
+ userId: context.get_id,
200
+ campaignKey: campaign.get_type == CampaignTypeEnum::AB ? campaign.get_key : "#{campaign.get_name}_#{campaign.get_rule_key}",
201
+ status: status,
202
+ variationString: variation_string
203
+ })
204
+
205
+ whitelisting_result
206
+ end
207
+
208
+ # Evaluate the whitelisting
209
+ # @param campaign [CampaignModel] The campaign to evaluate
210
+ # @param context [ContextModel] The context for the evaluation
211
+ def self.evaluate_whitelisting(campaign, context)
212
+ targeted_variations = []
213
+
214
+ campaign.get_variations.each do |variation|
215
+ next if DataTypeUtil.is_object(variation.get_segments) && variation.get_segments.empty?
216
+ if DataTypeUtil.is_object(variation.get_segments)
217
+ segmentation_result = SegmentationManager.instance.validate_segmentation(
218
+ variation.get_segments,
219
+ context.get_variation_targeting_variables
220
+ )
221
+
222
+ targeted_variations.push(clone_object(variation)) if segmentation_result
223
+ end
224
+ end
225
+
226
+ # Determine the whitelisted variation
227
+ whitelisted_variation = nil
228
+ if targeted_variations.length > 1
229
+ scale_variation_weights(targeted_variations)
230
+ current_allocation = 0
231
+
232
+ targeted_variations.each do |variation|
233
+ step_factor = assign_range_values(variation, current_allocation)
234
+ current_allocation += step_factor
235
+ end
236
+
237
+ whitelisted_variation = CampaignDecisionService.new.get_variation(
238
+ targeted_variations,
239
+ DecisionMaker.new.calculate_bucket_value(CampaignUtil.get_bucketing_seed(context.get_id, campaign, nil))
240
+ )
241
+ else
242
+ whitelisted_variation = targeted_variations.first
243
+ end
244
+
245
+ return nil unless whitelisted_variation
246
+
247
+ {
248
+ variation: whitelisted_variation,
249
+ variation_name: whitelisted_variation.get_key,
250
+ variation_id: whitelisted_variation.get_id
251
+ }
252
+ end
253
+ end
@@ -0,0 +1,123 @@
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/campaign_type_enum'
16
+ require_relative '../models/campaign/campaign_model'
17
+ require_relative '../models/campaign/feature_model'
18
+ require_relative '../models/settings/settings_model'
19
+ require_relative '../utils/data_type_util'
20
+
21
+ # Clones an object deeply.
22
+ # @param obj [Object] The object to clone.
23
+ # @return [Object] The cloned object.
24
+ def clone_object(obj)
25
+ return obj unless obj
26
+ Marshal.load(Marshal.dump(obj))
27
+ end
28
+
29
+ # Gets the current time in ISO string format.
30
+ # @return [String] The current time in ISO string format.
31
+ def get_current_time
32
+ Time.now.utc.iso8601
33
+ end
34
+
35
+ # Gets the current Unix timestamp in seconds.
36
+ # @return [Integer] The current Unix timestamp.
37
+ def get_current_unix_timestamp
38
+ Time.now.to_i
39
+ end
40
+
41
+ # Gets the current Unix timestamp in milliseconds.
42
+ # @return [Integer] The current Unix timestamp in milliseconds.
43
+ def get_current_unix_timestamp_in_millis
44
+ (Time.now.to_f * 1000).to_i
45
+ end
46
+
47
+ # Generates a random number between 0 and 1.
48
+ # @return [Float] A random number.
49
+ def get_random_number
50
+ rand
51
+ end
52
+
53
+ # Retrieves specific rules based on the type from a feature.
54
+ # @param feature [FeatureModel] The feature object.
55
+ # @param type [CampaignTypeEnum, nil] The type of the rules to retrieve.
56
+ # @return [Array] An array of rules that match the type.
57
+ def get_specific_rules_based_on_type(feature, type = nil)
58
+ return [] unless feature&.get_rules_linked_campaign
59
+
60
+ return feature.get_rules_linked_campaign if type.nil? || !DataTypeUtil.is_string(type)
61
+
62
+ feature.get_rules_linked_campaign.select do |rule|
63
+ rule_model = CampaignModel.new.model_from_dictionary(rule)
64
+ rule_model.get_type == type
65
+ end
66
+ end
67
+
68
+ # Retrieves all AB and Personalize rules from a feature.
69
+ # @param feature [FeatureModel] The feature object.
70
+ # @return [Array] An array of AB and Personalize rules.
71
+ def get_all_experiment_rules(feature)
72
+ return [] unless feature
73
+
74
+ feature.get_rules_linked_campaign.select do |rule|
75
+ [CampaignTypeEnum::AB, CampaignTypeEnum::PERSONALIZE].include?(rule.get_type)
76
+ end
77
+ end
78
+
79
+ # Retrieves a feature by its key from the settings.
80
+ # @param settings [SettingsModel] The settings containing features.
81
+ # @param feature_key [String] The key of the feature to find.
82
+ # @return [FeatureModel, nil] The feature if found, otherwise nil.
83
+ def get_feature_from_key(settings, feature_key)
84
+ return nil unless settings&.get_features
85
+
86
+ settings.get_features.find { |feature| feature.get_key == feature_key }
87
+ end
88
+
89
+ # Checks if an event exists within any feature's metrics.
90
+ # @param event_name [String] The name of the event to check.
91
+ # @param settings [SettingsModel] The settings containing features.
92
+ # @return [Boolean] True if the event exists, otherwise false.
93
+ def does_event_belong_to_any_feature(event_name, settings)
94
+ settings.get_features.any? do |feature|
95
+ feature.get_metrics.any? { |metric| metric.get_identifier == event_name }
96
+ end
97
+ end
98
+
99
+ # Adds linked campaigns to each feature in the settings based on rules.
100
+ # @param settings [SettingsModel] The settings file to modify.
101
+ def add_linked_campaigns_to_settings(settings)
102
+ campaign_map = settings.get_campaigns.each_with_object({}) do |campaign, map|
103
+ map[campaign.get_id] = campaign
104
+ end
105
+
106
+ settings.get_features.each do |feature|
107
+ rules_linked_campaign = feature.get_rules.map do |rule|
108
+ campaign = campaign_map[rule.get_campaign_id]
109
+ next unless campaign
110
+
111
+ linked_campaign = campaign.clone
112
+ linked_campaign.set_rule_key(rule.get_rule_key)
113
+ if rule.get_variation_id
114
+ variation = campaign.get_variations.find { |v| v.get_id == rule.get_variation_id }
115
+ linked_campaign.set_variations([variation]) if variation
116
+ end
117
+
118
+ linked_campaign
119
+ end.compact
120
+
121
+ feature.set_rules_linked_campaign(rules_linked_campaign)
122
+ end
123
+ end
@@ -0,0 +1,101 @@
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/settings/settings_model'
16
+ require_relative '../packages/network_layer/manager/network_manager'
17
+ require_relative '../packages/network_layer/models/request_model'
18
+ require_relative '../enums/http_method_enum'
19
+ require_relative '../services/settings_service'
20
+ require_relative 'url_util'
21
+ require_relative '../enums/campaign_type_enum'
22
+ require_relative '../services/logger_service'
23
+ require_relative '../enums/log_level_enum'
24
+
25
+ # Retrieves data from a web service using the specified query parameters and endpoint.
26
+ # @param query_params [Hash] The parameters to be used in the query string of the request.
27
+ # @param endpoint [String] The endpoint URL to which the request is sent.
28
+ # @return [Hash, Boolean] The response data or false if an error occurs.
29
+ def get_from_gateway_service(query_params, endpoint)
30
+ network_instance = NetworkManager.instance
31
+
32
+ unless SettingsService.instance.is_gateway_service_provided
33
+ LoggerService.log(LogLevelEnum::ERROR, "GATEWAY_URL_ERROR")
34
+ return false
35
+ end
36
+
37
+ begin
38
+ request = RequestModel.new(
39
+ UrlUtil.get_base_url,
40
+ HttpMethodEnum::GET,
41
+ endpoint,
42
+ query_params,
43
+ nil,
44
+ nil,
45
+ SettingsService.instance.protocol,
46
+ SettingsService.instance.port
47
+ )
48
+
49
+ response = network_instance.get(request)
50
+ return response.get_data
51
+ rescue StandardError => e
52
+ LoggerService.log(LogLevelEnum::ERROR, "Error fetching from Gateway Service: #{e.message}", nil)
53
+ return false
54
+ end
55
+ end
56
+
57
+ # Encodes the query parameters to ensure they are URL-safe.
58
+ # @param query_params [Hash] The query parameters to be encoded.
59
+ # @return [Hash] An object containing the encoded query parameters.
60
+ def get_query_params(query_params)
61
+ encoded_params = {}
62
+
63
+ query_params.each do |key, value|
64
+ encoded_params[key] = URI.encode_www_form_component(value.to_s)
65
+ end
66
+
67
+ encoded_params
68
+ end
69
+
70
+ # Adds isGatewayServiceRequired flag to each feature in the settings based on pre-segmentation.
71
+ # @param settings [SettingsModel] The settings file to modify.
72
+ def add_is_gateway_service_required_flag(settings)
73
+ pattern = /
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
76
+ |
77
+ "custom_variable"\s*:\s*{\s*"name"\s*:\s*"inlist\([^)]*\)"
78
+ /x
79
+
80
+ settings.get_features.each do |feature|
81
+ feature.get_rules_linked_campaign.each do |rule|
82
+ segments = {}
83
+
84
+ if [CampaignTypeEnum::PERSONALIZE, CampaignTypeEnum::ROLLOUT].include?(rule.get_type)
85
+ segments = rule.get_variations[0].get_segments
86
+ else
87
+ segments = rule.get_segments
88
+ end
89
+
90
+ next unless segments
91
+
92
+ json_segments = segments.to_json
93
+ matches = json_segments.scan(pattern)
94
+
95
+ if matches.any?
96
+ feature.set_is_gateway_service_required(true)
97
+ break
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,49 @@
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/settings/settings_model'
16
+ require_relative './network_util'
17
+ require_relative '../models/user/context_model'
18
+ require_relative '../enums/event_enum'
19
+
20
+ # Creates and sends an impression for a variation shown event.
21
+ # This function constructs the necessary properties and payload for the event
22
+ # and uses the NetworkUtil to send a POST API request.
23
+ #
24
+ # @param settings [SettingsModel] The settings model containing configuration.
25
+ # @param campaign_id [Integer] The ID of the campaign.
26
+ # @param variation_id [Integer] The ID of the variation shown to the user.
27
+ # @param context [ContextModel] The user context model containing user-specific data.
28
+ def create_and_send_impression_for_variation_shown(settings, campaign_id, variation_id, context)
29
+ # Get base properties for the event
30
+ properties = NetworkUtil.get_events_base_properties(
31
+ settings,
32
+ EventEnum::VWO_VARIATION_SHOWN,
33
+ URI.encode_www_form_component(context.get_user_agent), # Encode user agent for URL safety
34
+ context.get_ip_address
35
+ )
36
+
37
+ # Construct payload data for tracking the user
38
+ payload = NetworkUtil.get_track_user_payload_data(
39
+ settings,
40
+ context.get_id,
41
+ EventEnum::VWO_VARIATION_SHOWN,
42
+ campaign_id,
43
+ variation_id,
44
+ context.get_user_agent,
45
+ context.get_ip_address
46
+ )
47
+
48
+ NetworkUtil.send_post_api_request(properties, payload)
49
+ end
@@ -0,0 +1,42 @@
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
+ # Constructs a message by replacing placeholders in a template with corresponding values from a data hash.
16
+ #
17
+ # @param template [String] The message template containing placeholders in the format `{key}`.
18
+ # @param data [Hash] An object containing keys and values used to replace the placeholders in the template.
19
+ # @return [String] The constructed message with all placeholders replaced by their corresponding values from the data hash.
20
+ def build_message(template, data = {})
21
+ begin
22
+ template.gsub(/\{([0-9a-zA-Z_]+)\}/) do |match|
23
+ key = match.tr('{}', '') # Extract the key from `{key}`
24
+
25
+ # Check for escaped placeholders like `{{key}}`
26
+ if template[template.index(match) - 1] == '{' && template[template.index(match) + match.length] == '}'
27
+ key
28
+ else
29
+ value = data[key.to_sym] || data[key] # Support both string and symbol keys
30
+
31
+ # Return empty string if key is missing
32
+ next '' if value.nil?
33
+
34
+ # If value is a callable (Proc/Lambda), execute it
35
+ value.respond_to?(:call) ? value.call : value
36
+ end
37
+ end
38
+ rescue StandardError
39
+ template # Return the original template in case of an error
40
+ end
41
+ end
42
+