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,198 @@
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 '../utils/segment_util'
16
+ require_relative '../enums/segment_operand_value_enum'
17
+ require_relative '../enums/segment_operand_regex_enum'
18
+ require_relative '../../../utils/data_type_util'
19
+ require_relative '../../../utils/gateway_service_util'
20
+ require_relative '../../../enums/url_enum'
21
+ require_relative '../../../services/logger_service'
22
+ require_relative '../../../models/user/context_model'
23
+ require_relative '../../../enums/log_level_enum'
24
+ class SegmentOperandEvaluator
25
+ # Evaluates the custom variable DSL
26
+ # @param dsl_operand_value [String] The operand value to evaluate
27
+ # @param properties [Hash] The properties to evaluate the operand against
28
+ # @return [Boolean] True if the operand value matches the tag value, false otherwise
29
+ def evaluate_custom_variable_dsl(dsl_operand_value, properties)
30
+ key_value = get_key_value(dsl_operand_value)
31
+ return false unless key_value
32
+
33
+ operand_key = key_value[:key].to_sym
34
+ operand = key_value[:value]
35
+
36
+ return false unless properties.key?(operand_key)
37
+
38
+ if operand.include?('inlist')
39
+ match = operand.match(/inlist\(([^)]+)\)/)
40
+ unless match
41
+ LoggerService.log(LogLevelEnum::ERROR, "Invalid 'inList' operand format", nil)
42
+ return false
43
+ end
44
+
45
+ tag_value = pre_process_tag_value(properties[operand_key])
46
+ list_id = match[1]
47
+
48
+ query_params_obj = { attribute: tag_value, listId: list_id }
49
+
50
+ begin
51
+ res = get_from_gateway_service(query_params_obj, UrlEnum::ATTRIBUTE_CHECK)
52
+ if res.nil? || res == false || res == 'false' || (res.is_a?(Hash) && res[:status] == 0)
53
+ return false
54
+ end
55
+ return res
56
+ rescue StandardError => e
57
+ LoggerService.log(LogLevelEnum::ERROR, "Error while fetching data: #{e}", nil)
58
+ return false
59
+ end
60
+
61
+ false
62
+ else
63
+ tag_value = pre_process_tag_value(properties[operand_key])
64
+ processed_values = process_values(*pre_process_operand_value(operand), tag_value)
65
+ extract_result(processed_values[:operand_type], processed_values[:operand_value], processed_values[:tag_value])
66
+ end
67
+ end
68
+
69
+ # Evaluates the user DSL
70
+ # @param dsl_operand_value [String] The operand value to evaluate
71
+ # @param properties [Hash] The properties to evaluate the operand against
72
+ # @return [Boolean] True if the operand value matches the tag value, false otherwise
73
+ def evaluate_user_dsl(dsl_operand_value, properties)
74
+ users = dsl_operand_value.split(',')
75
+ users.any? { |user| user.strip == properties["_vwoUserId"].to_s }
76
+ end
77
+
78
+ # Evaluates the user agent DSL
79
+ # @param dsl_operand_value [String] The operand value to evaluate
80
+ # @param context [ContextModel] The context to evaluate the operand against
81
+ # @return [Boolean] True if the operand value matches the tag value, false otherwise
82
+ def evaluate_user_agent_dsl(dsl_operand_value, context)
83
+ return false unless context.get_user_agent
84
+
85
+ tag_value = CGI.unescape(context.get_user_agent)
86
+ processed_values = process_values(*pre_process_operand_value(dsl_operand_value), tag_value)
87
+ extract_result(processed_values[:operand_type], processed_values[:operand_value], processed_values[:tag_value])
88
+ end
89
+
90
+ # Pre-processes the tag value
91
+ # @param tag_value [String] The tag value to pre-process
92
+ # @return [String] The pre-processed tag value
93
+ def pre_process_tag_value(tag_value)
94
+ return '' if tag_value.nil?
95
+ return tag_value if [true, false].include?(tag_value)
96
+
97
+ tag_value.to_s
98
+ end
99
+
100
+ # Pre-processes the operand value
101
+ # @param operand [String] The operand to pre-process
102
+ # @return [Hash] The pre-processed operand value
103
+ def pre_process_operand_value(operand)
104
+ case operand
105
+ when /#{SegmentOperandRegexEnum::LOWER_MATCH}/
106
+ { operand_type: SegmentOperandValueEnum::LOWER_VALUE, operand_value: extract_operand_value(operand, SegmentOperandRegexEnum::LOWER_MATCH) }
107
+ when /#{SegmentOperandRegexEnum::WILDCARD_MATCH}/
108
+ value = extract_operand_value(operand, SegmentOperandRegexEnum::WILDCARD_MATCH)
109
+ if value.match?(SegmentOperandRegexEnum::STARTING_STAR) && value.match?(SegmentOperandRegexEnum::ENDING_STAR)
110
+ type = SegmentOperandValueEnum::STARTING_ENDING_STAR_VALUE
111
+ value = value.gsub(/^\*|\*$/, '')
112
+ elsif value.match?(SegmentOperandRegexEnum::STARTING_STAR)
113
+ type = SegmentOperandValueEnum::STARTING_STAR_VALUE
114
+ value = value.gsub(/^\*/, '')
115
+ elsif value.match?(SegmentOperandRegexEnum::ENDING_STAR)
116
+ type = SegmentOperandValueEnum::ENDING_STAR_VALUE
117
+ value = value.gsub(/\*$/, '')
118
+ end
119
+ { operand_type: type, operand_value: value }
120
+ when /#{SegmentOperandRegexEnum::REGEX_MATCH}/
121
+ { operand_type: SegmentOperandValueEnum::REGEX_VALUE, operand_value: extract_operand_value(operand, SegmentOperandRegexEnum::REGEX_MATCH) }
122
+ when /#{SegmentOperandRegexEnum::GREATER_THAN_MATCH}/
123
+ { operand_type: SegmentOperandValueEnum::GREATER_THAN_VALUE, operand_value: extract_operand_value(operand, SegmentOperandRegexEnum::GREATER_THAN_MATCH) }
124
+ when /#{SegmentOperandRegexEnum::LESS_THAN_MATCH}/
125
+ { operand_type: SegmentOperandValueEnum::LESS_THAN_VALUE, operand_value: extract_operand_value(operand, SegmentOperandRegexEnum::LESS_THAN_MATCH) }
126
+ when /#{SegmentOperandRegexEnum::GREATER_THAN_EQUAL_TO_MATCH}/
127
+ { operand_type: SegmentOperandValueEnum::GREATER_THAN_EQUAL_TO_VALUE, operand_value: extract_operand_value(operand, SegmentOperandRegexEnum::GREATER_THAN_EQUAL_TO_MATCH) }
128
+ when /#{SegmentOperandRegexEnum::LESS_THAN_EQUAL_TO_MATCH}/
129
+ { operand_type: SegmentOperandValueEnum::LESS_THAN_EQUAL_TO_VALUE, operand_value: extract_operand_value(operand, SegmentOperandRegexEnum::LESS_THAN_EQUAL_TO_MATCH) }
130
+ else
131
+ { operand_type: SegmentOperandValueEnum::EQUAL_VALUE, operand_value: operand }
132
+ end
133
+ end
134
+
135
+ # Extracts the operand value from the operand
136
+ # @param operand [String] The operand to extract the value from
137
+ # @param regex [String] The regex to match the operand against
138
+ # @return [String] The extracted operand value
139
+ def extract_operand_value(operand, regex)
140
+ match = operand.match(/#{regex}/)
141
+ match ? match[1] : ''
142
+ end
143
+
144
+ # Processes the values from the operand and tag value
145
+ # @param operand_type [Symbol] The type of operand
146
+ # @param operand_value [String] The value of the operand
147
+ # @param tag_value [String] The value of the tag
148
+ # @return [Hash] The processed operand value and tag value
149
+ def process_values(operand_type, operand_value, tag_value)
150
+ # Extract values from arrays if needed
151
+ operand_type = operand_type[1] if operand_type.is_a?(Array)
152
+ operand_value = operand_value[1] if operand_value.is_a?(Array)
153
+ tag_value = tag_value[1] if tag_value.is_a?(Array)
154
+
155
+ processed_operand_value = operand_value.to_f
156
+ processed_tag_value = tag_value.to_f
157
+
158
+ if processed_operand_value == 0 || processed_tag_value == 0
159
+ return { operand_type: operand_type, operand_value: operand_value, tag_value: tag_value }
160
+ end
161
+
162
+ { operand_type: operand_type, operand_value: processed_operand_value.to_s, tag_value: processed_tag_value.to_s }
163
+ end
164
+
165
+ # Extracts the result from the operand value and tag value
166
+ # @param operand_type [Symbol] The type of operand
167
+ # @param operand_value [String] The value of the operand
168
+ # @param tag_value [String] The value of the tag
169
+ # @return [Boolean] True if the operand value matches the tag value, false otherwise
170
+ def extract_result(operand_type, operand_value, tag_value)
171
+ case operand_type
172
+ when SegmentOperandValueEnum::LOWER_VALUE
173
+ operand_value.downcase == tag_value.downcase
174
+ when SegmentOperandValueEnum::STARTING_ENDING_STAR_VALUE
175
+ tag_value.include?(operand_value)
176
+ when SegmentOperandValueEnum::STARTING_STAR_VALUE
177
+ tag_value.end_with?(operand_value)
178
+ when SegmentOperandValueEnum::ENDING_STAR_VALUE
179
+ tag_value.start_with?(operand_value)
180
+ when SegmentOperandValueEnum::REGEX_VALUE
181
+ begin
182
+ !!Regexp.new(operand_value).match?(tag_value)
183
+ rescue StandardError
184
+ false
185
+ end
186
+ when SegmentOperandValueEnum::GREATER_THAN_VALUE
187
+ operand_value.to_f < tag_value.to_f
188
+ when SegmentOperandValueEnum::LESS_THAN_VALUE
189
+ operand_value.to_f > tag_value.to_f
190
+ when SegmentOperandValueEnum::GREATER_THAN_EQUAL_TO_VALUE
191
+ operand_value.to_f <= tag_value.to_f
192
+ when SegmentOperandValueEnum::LESS_THAN_EQUAL_TO_VALUE
193
+ operand_value.to_f >= tag_value.to_f
194
+ else
195
+ tag_value == operand_value
196
+ end
197
+ end
198
+ end
@@ -0,0 +1,44 @@
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 'json'
16
+
17
+ # Utility function to check if a value is an object (excluding arrays and other types)
18
+ def is_object?(val)
19
+ val.is_a?(Hash)
20
+ end
21
+
22
+ # Extracts the first key-value pair from the provided object.
23
+ # @param obj [Hash] The object from which to extract the key-value pair.
24
+ # @return [Hash, nil] A hash containing the first key and value, or nil if input is not a hash.
25
+ def get_key_value(obj)
26
+ return nil unless is_object?(obj)
27
+
28
+ key = obj.keys.first
29
+ return nil unless key
30
+
31
+ { key: key, value: obj[key] }
32
+ end
33
+
34
+ # Matches a string against a regular expression and returns the match result.
35
+ # @param string [String] The string to match against the regex.
36
+ # @param regex [String] The regex pattern as a string.
37
+ # @return [Array, nil] The results of the regex match, or nil if an error occurs.
38
+ def match_with_regex(string, regex)
39
+ begin
40
+ return string.match(Regexp.new(regex))
41
+ rescue StandardError
42
+ return nil
43
+ end
44
+ 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,15 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
- module VWO
16
- module Constants
17
- HTTPS_PROTOCOL = 'https://'
18
- BASE_URL = 'dev.visualwebsiteoptimizer.com'
19
- ENDPOINT_GET_FLAG = '/server-side/getFlag'
20
- ENDPOINT_TRACK_EVENT = '/server-side/trackEvent'
21
- ENDPOINT_SET_ATTRIBUTE = '/server-side/setAttribute'
22
- ENDPOINT_ACCOUNT_SETTINGS = '/server-side/v2-settings'
15
+ class Connector
16
+ # Define an abstract method for setting data
17
+ def set(_key, _data)
18
+ raise NotImplementedError, "#{self.class} must implement the `set` method"
23
19
  end
24
- end
20
+
21
+ # Define an abstract method for getting data
22
+ def get(_key)
23
+ raise NotImplementedError, "#{self.class} must implement the `get` method"
24
+ end
25
+ end
26
+
@@ -0,0 +1,45 @@
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 'connector' # Import Connector class
16
+
17
+ class Storage
18
+ @instance = nil
19
+
20
+ attr_reader :connector
21
+
22
+ def initialize
23
+ @connector = nil
24
+ end
25
+
26
+ # Attach a connector (can be an instance or a class)
27
+ def attach_connector(connector)
28
+ if connector.is_a?(Class) # Check if it's a class before instantiating
29
+ @connector = connector.new
30
+ else
31
+ @connector = connector
32
+ end
33
+ @connector
34
+ end
35
+
36
+ # Singleton instance method
37
+ def self.instance
38
+ @instance ||= new
39
+ end
40
+
41
+ # Get the attached connector
42
+ def get_connector
43
+ @connector
44
+ end
45
+ end
@@ -0,0 +1,153 @@
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 '../packages/decision_maker/decision_maker'
16
+ require_relative '../services/logger_service'
17
+ require_relative '../enums/log_level_enum'
18
+ require_relative '../packages/segmentation_evaluator/core/segmentation_manager'
19
+
20
+ require_relative '../constants/constants'
21
+ require_relative '../models/campaign/variation_model'
22
+ require_relative '../models/campaign/campaign_model'
23
+ require_relative '../models/user/context_model'
24
+ require_relative '../enums/campaign_type_enum'
25
+ require_relative '../utils/data_type_util'
26
+ require_relative '../utils/log_message_util'
27
+
28
+ class CampaignDecisionService
29
+ # Determines if a user is part of a campaign based on bucketing logic
30
+ # @param user_id [String] The ID of the user
31
+ # @param campaign [CampaignModel] The campaign to check
32
+ # @return [Boolean] True if the user is part of the campaign, false otherwise
33
+ def is_user_part_of_campaign(user_id, campaign)
34
+ return false if campaign.nil? || user_id.nil?
35
+
36
+ is_rollout_or_personalize = [CampaignTypeEnum::ROLLOUT, CampaignTypeEnum::PERSONALIZE].include?(campaign.get_type)
37
+ salt = is_rollout_or_personalize ? campaign.get_variations.first.get_salt : campaign.get_salt
38
+ traffic_allocation = is_rollout_or_personalize ? campaign.get_variations.first.get_weight : campaign.get_traffic
39
+
40
+ bucket_key = salt ? "#{salt}_#{user_id}" : "#{campaign.get_id}_#{user_id}"
41
+ value_assigned_to_user = DecisionMaker.new.get_bucket_value_for_user(bucket_key)
42
+
43
+ is_user_part = value_assigned_to_user != 0 && value_assigned_to_user <= traffic_allocation
44
+
45
+ LoggerService.log(LogLevelEnum::INFO, "USER_PART_OF_CAMPAIGN", {
46
+ userId: user_id,
47
+ notPart: is_user_part ? '' : 'not',
48
+ campaignKey: campaign.get_type == CampaignTypeEnum::AB ? campaign.get_key : "#{campaign.get_name}_#{campaign.get_rule_key}"
49
+ })
50
+
51
+ is_user_part
52
+ end
53
+
54
+ # Returns the variation assigned to a user based on bucket value
55
+ # @param variations [Array<VariationModel>] The variations to check
56
+ # @param bucket_value [Integer] The bucket value to check
57
+ # @return [VariationModel] The variation assigned to the user
58
+ def get_variation(variations, bucket_value)
59
+ variations.find { |variation| bucket_value >= variation.get_start_range_variation && bucket_value <= variation.get_end_range_variation }
60
+ end
61
+
62
+ # Checks if the bucket value is in the range of the variation
63
+ # @param variation [VariationModel] The variation to check
64
+ # @param bucket_value [Integer] The bucket value to check
65
+ # @return [VariationModel] The variation if the bucket value is in the range, nil otherwise
66
+ def check_in_range(variation, bucket_value)
67
+ variation if bucket_value >= variation.get_start_range_variation && bucket_value <= variation.get_end_range_variation
68
+ end
69
+
70
+ # Buckets a user into a variation for a given campaign
71
+ # @param user_id [String] The ID of the user
72
+ # @param account_id [String] The ID of the account
73
+ # @param campaign [CampaignModel] The campaign to bucket the user into
74
+ # @return [VariationModel] The variation assigned to the user
75
+ def bucket_user_to_variation(user_id, account_id, campaign)
76
+ return nil if campaign.nil? || user_id.nil?
77
+
78
+ multiplier = campaign.get_traffic ? 1 : nil
79
+ percent_traffic = campaign.get_traffic
80
+ salt = campaign.get_salt
81
+ bucket_key = salt ? "#{salt}_#{account_id}_#{user_id}" : "#{campaign.get_id}_#{account_id}_#{user_id}"
82
+
83
+ hash_value = DecisionMaker.new.generate_hash_value(bucket_key)
84
+ bucket_value = DecisionMaker.new.generate_bucket_value(hash_value, Constants::MAX_TRAFFIC_VALUE, multiplier)
85
+
86
+ LoggerService.log(LogLevelEnum::DEBUG, "USER_BUCKET_TO_VARIATION", {
87
+ userId: user_id,
88
+ campaignKey: campaign.get_key,
89
+ percentTraffic: percent_traffic,
90
+ bucketValue: bucket_value,
91
+ hashValue: hash_value
92
+ })
93
+
94
+ get_variation(campaign.get_variations, bucket_value)
95
+ end
96
+
97
+ # Pre-segmentation decision based on user context and campaign rules
98
+ # @param campaign [CampaignModel] The campaign to evaluate
99
+ # @param context [ContextModel] The context for the evaluation
100
+ # @return [Boolean] True if the user satisfies the campaign rules, false otherwise
101
+ def get_pre_segmentation_decision(campaign, context)
102
+ campaign_type = campaign.get_type
103
+ segments = if [CampaignTypeEnum::ROLLOUT, CampaignTypeEnum::PERSONALIZE].include?(campaign_type)
104
+ campaign.get_variations.first.get_segments
105
+ elsif campaign_type == CampaignTypeEnum::AB
106
+ campaign.get_segments
107
+ end
108
+
109
+ if DataTypeUtil.is_object(segments) && segments.empty?
110
+ LoggerService.log(LogLevelEnum::INFO, "SEGMENTATION_SKIP", {
111
+ userId: context.get_id,
112
+ campaignKey: campaign.get_type == CampaignTypeEnum::AB ? campaign.get_key : "#{campaign.get_name}_#{campaign.get_rule_key}"
113
+ })
114
+ return true
115
+ else
116
+ pre_segmentation_result = SegmentationManager.instance.validate_segmentation(segments, context.get_custom_variables)
117
+
118
+ if !pre_segmentation_result
119
+ LoggerService.log(LogLevelEnum::INFO, "SEGMENTATION_STATUS", {
120
+ userId: context.get_id,
121
+ campaignKey: campaign.get_type == CampaignTypeEnum::AB ? campaign.get_key : "#{campaign.get_name}_#{campaign.get_rule_key}",
122
+ status: 'failed'
123
+ })
124
+ return false
125
+ end
126
+
127
+ LoggerService.log(LogLevelEnum::INFO, "SEGMENTATION_STATUS", {
128
+ userId: context.get_id,
129
+ campaignKey: campaign.get_type == CampaignTypeEnum::AB ? campaign.get_key : "#{campaign.get_name}_#{campaign.get_rule_key}",
130
+ status: 'passed'
131
+ })
132
+
133
+ return true
134
+ end
135
+ end
136
+
137
+ # Determines the variation assigned to a user for a campaign
138
+ # @param user_id [String] The ID of the user
139
+ # @param account_id [String] The ID of the account
140
+ # @param campaign [CampaignModel] The campaign to evaluate
141
+ # @return [VariationModel] The variation assigned to the user
142
+ def get_variation_alloted(user_id, account_id, campaign)
143
+ is_user_part = is_user_part_of_campaign(user_id, campaign)
144
+
145
+ if [CampaignTypeEnum::ROLLOUT, CampaignTypeEnum::PERSONALIZE].include?(campaign.get_type)
146
+ return campaign.get_variations.first if is_user_part
147
+ else
148
+ return bucket_user_to_variation(user_id, account_id, campaign) if is_user_part
149
+ end
150
+
151
+ nil
152
+ end
153
+ end
@@ -0,0 +1,51 @@
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/vwo_options_model'
16
+ require_relative '../utils/data_type_util'
17
+
18
+ class HooksService
19
+ attr_reader :decision
20
+
21
+ def initialize(options)
22
+ @callback = options.dig(:integrations, :callback)
23
+ @is_callback_function = is_function?(@callback)
24
+ @decision = {}
25
+ end
26
+
27
+ # Executes the callback
28
+ # @param properties [Hash] Properties from the callback
29
+ def execute(properties)
30
+ @callback.call(properties) if @is_callback_function
31
+ end
32
+
33
+ # Sets properties to the decision object
34
+ # @param properties [Hash] Properties to set
35
+ def set(properties)
36
+ @decision = properties if @is_callback_function
37
+ end
38
+
39
+ # Retrieves the decision object
40
+ # @return [Hash] The decision object
41
+ def get
42
+ @decision
43
+ end
44
+
45
+ private
46
+
47
+ # Helper function to check if a value is a function (assumed from data_type_util.rb)
48
+ def is_function?(val)
49
+ val.respond_to?(:call)
50
+ end
51
+ end
@@ -0,0 +1,83 @@
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 'json'
16
+ require_relative '../vwo_client'
17
+ require_relative '../packages/logger/core/log_manager'
18
+ require_relative '../enums/log_level_enum'
19
+ require_relative '../utils/log_message_util'
20
+
21
+ class LoggerService
22
+ class << self
23
+ attr_accessor :debug_messages, :info_messages, :error_messages, :warning_messages
24
+ end
25
+
26
+ def self.log(level, key = nil, map = {})
27
+ log_manager = LogManager.instance
28
+
29
+ if key && map
30
+ message = build_message(get_messages(level)[key], map)
31
+ else
32
+ message = key # key acts as the message when no map is provided
33
+ end
34
+
35
+ case level
36
+ when LogLevelEnum::DEBUG
37
+ log_manager.debug(message)
38
+ when LogLevelEnum::INFO
39
+ log_manager.info(message)
40
+ when LogLevelEnum::WARN
41
+ log_manager.warn(message)
42
+ else
43
+ log_manager.error(message)
44
+ end
45
+ end
46
+
47
+ def initialize(config = {})
48
+ # Initialize the LogManager
49
+ LogManager.instance(config)
50
+
51
+ # Read the log files and set class variables
52
+ self.class.debug_messages = read_log_files('debug_messages.json')
53
+ self.class.info_messages = read_log_files('info_messages.json')
54
+ self.class.error_messages = read_log_files('error_messages.json')
55
+ self.class.warning_messages = read_log_files('warn_messages.json')
56
+ end
57
+
58
+ private
59
+
60
+ def read_log_files(file_name)
61
+ begin
62
+ # Use absolute path resolution from the project root
63
+ file_path = File.join(File.expand_path('../../resources', __dir__), file_name)
64
+ return JSON.parse(File.read(file_path))
65
+ rescue StandardError => e
66
+ puts "Error reading log file #{file_name}: #{e.message}"
67
+ return {}
68
+ end
69
+ end
70
+
71
+ def self.get_messages(level)
72
+ case level
73
+ when LogLevelEnum::DEBUG
74
+ @debug_messages
75
+ when LogLevelEnum::INFO
76
+ @info_messages
77
+ when LogLevelEnum::WARN
78
+ @warning_messages
79
+ else
80
+ @error_messages
81
+ end
82
+ end
83
+ end