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.
- checksums.yaml +4 -4
- data/lib/vwo/api/get_flag.rb +236 -0
- data/lib/vwo/api/set_attribute.rb +57 -0
- data/lib/vwo/api/track_event.rb +77 -0
- data/lib/vwo/constants/constants.rb +54 -0
- data/lib/vwo/decorators/storage_decorator.rb +86 -0
- data/lib/vwo/{utils/logger_helper.rb → enums/api_enum.rb} +5 -10
- data/lib/vwo/enums/campaign_type_enum.rb +19 -0
- data/lib/vwo/enums/decision_types_enum.rb +18 -0
- data/lib/vwo/enums/event_enum.rb +19 -0
- data/lib/vwo/enums/headers_enum.rb +20 -0
- data/lib/vwo/enums/hooks_enum.rb +17 -0
- data/lib/vwo/enums/http_method_enum.rb +18 -0
- data/lib/vwo/enums/log_level_enum.rb +21 -0
- data/lib/vwo/enums/status_enum.rb +19 -0
- data/lib/vwo/enums/storage_enum.rb +22 -0
- data/lib/vwo/enums/url_enum.rb +21 -0
- data/lib/vwo/models/campaign/campaign_model.rb +192 -0
- data/lib/vwo/models/campaign/feature_model.rb +111 -0
- data/lib/vwo/models/campaign/impact_campaign_model.rb +38 -0
- data/lib/vwo/models/campaign/metric_model.rb +44 -0
- data/lib/vwo/models/campaign/rule_model.rb +56 -0
- data/lib/vwo/models/campaign/variable_model.rb +51 -0
- data/lib/vwo/models/campaign/variation_model.rb +137 -0
- data/lib/vwo/models/gateway_service_model.rb +39 -0
- data/lib/vwo/models/schemas/settings_schema_validation.rb +102 -0
- data/lib/vwo/models/settings/settings_model.rb +85 -0
- data/lib/vwo/models/storage/storage_data_model.rb +44 -0
- data/lib/vwo/models/user/context_model.rb +100 -0
- data/lib/vwo/models/user/context_vwo_model.rb +38 -0
- data/lib/vwo/{utils/feature_flag_response.rb → models/user/get_flag_response.rb} +14 -14
- data/lib/vwo/models/vwo_options_model.rb +107 -0
- data/lib/vwo/packages/decision_maker/decision_maker.rb +60 -0
- data/lib/vwo/packages/logger/core/log_manager.rb +90 -0
- data/lib/vwo/packages/logger/core/transport_manager.rb +87 -0
- data/lib/vwo/packages/logger/log_message_builder.rb +70 -0
- data/lib/vwo/packages/logger/logger.rb +38 -0
- data/lib/vwo/packages/logger/transports/console_transport.rb +49 -0
- data/lib/vwo/packages/network_layer/client/network_client.rb +107 -0
- data/lib/vwo/packages/network_layer/handlers/request_handler.rb +37 -0
- data/lib/vwo/packages/network_layer/manager/network_manager.rb +78 -0
- data/lib/vwo/packages/network_layer/models/global_request_model.rb +105 -0
- data/lib/vwo/packages/network_layer/models/request_model.rb +145 -0
- data/lib/vwo/packages/network_layer/models/response_model.rb +45 -0
- data/lib/vwo/packages/segmentation_evaluator/core/segmentation_manager.rb +76 -0
- data/lib/vwo/packages/segmentation_evaluator/enums/segment_operand_regex_enum.rb +29 -0
- data/lib/vwo/packages/segmentation_evaluator/enums/segment_operand_value_enum.rb +26 -0
- data/lib/vwo/packages/segmentation_evaluator/enums/segment_operator_value_enum.rb +30 -0
- data/lib/vwo/packages/segmentation_evaluator/evaluators/segment_evaluator.rb +210 -0
- data/lib/vwo/packages/segmentation_evaluator/evaluators/segment_operand_evaluator.rb +198 -0
- data/lib/vwo/packages/segmentation_evaluator/utils/segment_util.rb +44 -0
- data/lib/vwo/{constants.rb → packages/storage/connector.rb} +12 -10
- data/lib/vwo/packages/storage/storage.rb +45 -0
- data/lib/vwo/services/campaign_decision_service.rb +153 -0
- data/lib/vwo/services/hooks_service.rb +51 -0
- data/lib/vwo/services/logger_service.rb +83 -0
- data/lib/vwo/services/settings_service.rb +120 -0
- data/lib/vwo/services/storage_service.rb +65 -0
- data/lib/vwo/utils/campaign_util.rb +249 -0
- data/lib/vwo/utils/data_type_util.rb +105 -0
- data/lib/vwo/utils/decision_util.rb +253 -0
- data/lib/vwo/utils/function_util.rb +123 -0
- data/lib/vwo/utils/gateway_service_util.rb +101 -0
- data/lib/vwo/utils/impression_util.rb +49 -0
- data/lib/vwo/utils/log_message_util.rb +42 -0
- data/lib/vwo/utils/meg_util.rb +350 -0
- data/lib/vwo/utils/network_util.rb +235 -0
- data/lib/vwo/utils/rule_evaluation_util.rb +57 -0
- data/lib/vwo/utils/settings_util.rb +38 -0
- data/lib/vwo/utils/url_util.rb +46 -0
- data/lib/vwo/utils/uuid_util.rb +55 -0
- data/lib/vwo/vwo_builder.rb +156 -11
- data/lib/vwo/vwo_client.rb +163 -113
- data/lib/vwo.rb +49 -31
- metadata +187 -9
- 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
|
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
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
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
|
-
|
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
|