vwo-fme-ruby-sdk 1.50.0 → 1.60.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/resources/debug_messages.json +1 -0
- data/lib/resources/error_messages.json +5 -1
- data/lib/resources/info_messages.json +1 -0
- data/lib/wingify/api/get_flag.rb +32 -2
- data/lib/wingify/constants/constants.rb +1 -1
- data/lib/wingify/enums/event_enum.rb +1 -0
- data/lib/wingify/models/settings/settings_model.rb +6 -1
- data/lib/wingify/models/user/context_model.rb +12 -2
- data/lib/wingify/packages/segmentation_evaluator/core/segmentation_manager.rb +39 -0
- data/lib/wingify/packages/segmentation_evaluator/enums/segment_operator_value_enum.rb +1 -0
- data/lib/wingify/packages/segmentation_evaluator/evaluators/segment_evaluator.rb +2 -0
- data/lib/wingify/packages/segmentation_evaluator/evaluators/segment_operand_evaluator.rb +17 -0
- data/lib/wingify/packages/segmentation_evaluator/utils/web_testing_segment_util.rb +146 -0
- data/lib/wingify/utils/impression_util.rb +35 -0
- data/lib/wingify/utils/network_util.rb +27 -0
- data/lib/wingify.rb +5 -0
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 734b280edc8de1dad1ea2256570267ecdbf0b87a840820f5d8d1c09e7627af33
|
|
4
|
+
data.tar.gz: e5b779d4263129a46e2c862e6317b555482fc1695f6d721e6682ba310ad4881e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: cf8f0bb234458b3055b957522a66b17688a6c8eabc323b41492a52973c48659496dbdb0ecebda043318188880c323c4676e1b103726d4229ff25ddda63a92237
|
|
7
|
+
data.tar.gz: 0f6a49ca2d7f3cdd98586a920ab37f4287b95976ac7d8b292f2070f2768b9338c54855c5eff158c0a24fbe0537b08a82323c3e2d0edc17b76b5f576f8a113df1
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
"IMPRESSION_FOR_TRACK_USER": "Impression built for vwo_variationShown({brand} standard event for tracking user) event haivng Account ID:{accountId}, User ID:{userId}, and experiment ID:{campaignId}",
|
|
12
12
|
"IMPRESSION_FOR_TRACK_GOAL": "Impression built for event:{eventName} event having Account ID:{accountId}, and user ID:{userId}",
|
|
13
13
|
"IMPRESSION_FOR_SYNC_VISITOR_PROP": "Impression built for {eventName}({brand} internal event) event for Account ID:{accountId}, and user ID:{userId}",
|
|
14
|
+
"IMPRESSION_FOR_USAGE_TRACKING": "Impression built for vwo_feTrackUsage({brand} standard event for usage tracking call) event having Account ID:{accountId}, and user ID:{userId}",
|
|
14
15
|
|
|
15
16
|
"CONFIG_BATCH_EVENT_LIMIT_EXCEEDED": "Impression event - {endPoint} failed due to exceeding payload size. Parameter eventsPerRequest in batchEvents config in init API has value:{eventsPerRequest} for account ID:{accountId}. Please read the official documentation for knowing the size limits",
|
|
16
17
|
|
|
@@ -59,5 +59,9 @@
|
|
|
59
59
|
|
|
60
60
|
"ERROR_EVALUATING_RULE": "Error occurred while evaluating rule. Error: {err}",
|
|
61
61
|
"BATCH_FLUSH_FAILED": "Failed to send batch events. Re-enqueuing events for retry.",
|
|
62
|
-
"ERROR_PARSING_GATEWAY_URL": "Error occurred while parsing gateway service URL: {err}"
|
|
62
|
+
"ERROR_PARSING_GATEWAY_URL": "Error occurred while parsing gateway service URL: {err}",
|
|
63
|
+
|
|
64
|
+
"INVALID_WEB_TESTING_CAMPAIGNS_DUPLICATE_KEY": "Duplicate campaignId found in webTestingCampaigns",
|
|
65
|
+
"INVALID_WEB_TESTING_CAMPAIGNS_JSON": "webTestingCampaigns provided in context is not a valid JSON string",
|
|
66
|
+
"INVALID_WEB_TESTING_CAMPAIGNS_TYPE": "webTestingCampaigns provided in context is of type {kind}, but must be of type string or object"
|
|
63
67
|
}
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
"MEG_WINNER_CAMPAIGN": "MEG: Campaign {campaignKey} is the winner for group {groupId} for user ID:{userId} {algo}",
|
|
31
31
|
"SETTINGS_UPDATED": "Settings fetched and updated successfully on the current {brand} client instance when API: {apiName} got called having isViaWebhook param as {isViaWebhook}",
|
|
32
32
|
"NETWORK_CALL_SUCCESS": "Impression for {event} - {endPoint} was successfully received by {brand} having Account ID:{accountId}, UUID: {uuid}",
|
|
33
|
+
"USAGE_TRACKING_DISPATCHED": "FE track usage call dispatched for Account ID:{accountId}, User ID:{userId}, and feature key:{featureKey}",
|
|
33
34
|
|
|
34
35
|
"EVENT_BATCH_DEFAULTS": "{parameter} in SDK configuration is missing or invalid (should be greater than {minLimit}). Using default value: {defaultValue}",
|
|
35
36
|
"EVENT_QUEUE": "Event with payload:{event} pushed to the {queueType} queue",
|
data/lib/wingify/api/get_flag.rb
CHANGED
|
@@ -40,6 +40,10 @@ class FlagApi
|
|
|
40
40
|
rollout_variation_to_return = nil
|
|
41
41
|
experiment_variation_to_return = nil
|
|
42
42
|
should_check_for_experiments_rules = false
|
|
43
|
+
# Flag to check if variation shown event has been fired
|
|
44
|
+
is_variation_shown_fired = false
|
|
45
|
+
# Flag to check if tracking usage is enabled
|
|
46
|
+
is_tracking_usage_enabled = settings.get_is_tracking_usage_enabled
|
|
43
47
|
|
|
44
48
|
passed_rules_information = {} # for storing integration callback
|
|
45
49
|
evaluated_feature_map = {}
|
|
@@ -65,6 +69,8 @@ class FlagApi
|
|
|
65
69
|
|
|
66
70
|
if variation
|
|
67
71
|
LoggerService.log(LogLevelEnum::INFO, "STORED_VARIATION_FOUND", {variationKey: variation.get_key, userId: context.get_id, experimentKey: stored_data[:experiment_key], experimentType: "experiment"})
|
|
72
|
+
# Send usage tracking call when user is served from storage
|
|
73
|
+
send_tracking_usage(settings, context, is_tracking_usage_enabled, feature_key)
|
|
68
74
|
return GetFlagResponse.new(true, variation.get_variables, context.get_uuid, context.get_session_id)
|
|
69
75
|
end
|
|
70
76
|
end
|
|
@@ -91,6 +97,8 @@ class FlagApi
|
|
|
91
97
|
|
|
92
98
|
if feature.nil?
|
|
93
99
|
LoggerService.log(LogLevelEnum::ERROR, "FEATURE_NOT_FOUND", {featureKey: feature_key, an: ApiEnum::GET_FLAG, sId: context.get_session_id, uuid: context.get_uuid})
|
|
100
|
+
# Send usage tracking call when feature is not found
|
|
101
|
+
send_tracking_usage(settings, context, is_tracking_usage_enabled, feature_key)
|
|
94
102
|
return GetFlagResponse.new(false, [], context.get_uuid, context.get_session_id)
|
|
95
103
|
end
|
|
96
104
|
|
|
@@ -106,6 +114,12 @@ class FlagApi
|
|
|
106
114
|
rollout_rules.each do |rule|
|
|
107
115
|
result = evaluate_rule(settings, feature, rule, context, evaluated_feature_map, nil, storage_service, decision)
|
|
108
116
|
pre_segmentation_result = result[:pre_segmentation_result]
|
|
117
|
+
whitelisted_object = result[:whitelisted_object]
|
|
118
|
+
|
|
119
|
+
if whitelisted_object.is_a?(Hash) && !whitelisted_object.empty?
|
|
120
|
+
is_variation_shown_fired = true
|
|
121
|
+
end
|
|
122
|
+
|
|
109
123
|
updated_decision = result[:updated_decision]
|
|
110
124
|
decision.merge!(updated_decision) if updated_decision
|
|
111
125
|
|
|
@@ -131,6 +145,8 @@ class FlagApi
|
|
|
131
145
|
update_integrations_decision_object(passed_rollout_campaign, variation, passed_rules_information, decision)
|
|
132
146
|
|
|
133
147
|
create_and_send_impression_for_variation_shown(settings, passed_rollout_campaign.get_id, variation.get_id, context, feature_key)
|
|
148
|
+
# set is_variation_shown_fired to true since we already sent variation shown call for rollout
|
|
149
|
+
is_variation_shown_fired = true
|
|
134
150
|
end
|
|
135
151
|
end
|
|
136
152
|
elsif rollout_rules.empty?
|
|
@@ -147,11 +163,16 @@ class FlagApi
|
|
|
147
163
|
result = evaluate_rule(settings, feature, rule, context, evaluated_feature_map, meg_group_winner_campaigns, storage_service, decision)
|
|
148
164
|
pre_segmentation_result = result[:pre_segmentation_result]
|
|
149
165
|
whitelisted_object = result[:whitelisted_object]
|
|
166
|
+
|
|
167
|
+
if whitelisted_object.is_a?(Hash) && !whitelisted_object.empty?
|
|
168
|
+
is_variation_shown_fired = true
|
|
169
|
+
end
|
|
170
|
+
|
|
150
171
|
updated_decision = result[:updated_decision]
|
|
151
172
|
decision.merge!(updated_decision) if updated_decision
|
|
152
173
|
|
|
153
174
|
if pre_segmentation_result
|
|
154
|
-
if whitelisted_object.nil?
|
|
175
|
+
if whitelisted_object.nil? || whitelisted_object.empty?
|
|
155
176
|
experiment_rules_to_evaluate << rule
|
|
156
177
|
else
|
|
157
178
|
is_enabled = true
|
|
@@ -176,6 +197,8 @@ class FlagApi
|
|
|
176
197
|
update_integrations_decision_object(campaign, variation, passed_rules_information, decision)
|
|
177
198
|
|
|
178
199
|
create_and_send_impression_for_variation_shown(settings, campaign.get_id, variation.get_id, context, feature_key)
|
|
200
|
+
# set is_variation_shown_fired to true since we already sent variation shown call for experiment
|
|
201
|
+
is_variation_shown_fired = true
|
|
179
202
|
end
|
|
180
203
|
end
|
|
181
204
|
end
|
|
@@ -216,8 +239,15 @@ class FlagApi
|
|
|
216
239
|
context,
|
|
217
240
|
feature_key
|
|
218
241
|
)
|
|
242
|
+
# set is_variation_shown_fired to true since we already sent variation shown call for impact analysis
|
|
243
|
+
is_variation_shown_fired = true
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
# Send usage tracking call when no primary variation_shown event was dispatched.
|
|
247
|
+
# If a primary variation_shown event was fired, the server already has the usage tracking signal.
|
|
248
|
+
if !is_variation_shown_fired
|
|
249
|
+
send_tracking_usage(settings, context, is_tracking_usage_enabled, feature_key)
|
|
219
250
|
end
|
|
220
|
-
|
|
221
251
|
# Return final evaluated feature flag
|
|
222
252
|
return GetFlagResponse.new(is_enabled, experiment_variation_to_return&.get_variables || rollout_variation_to_return&.get_variables || [], context.get_uuid, context.get_session_id)
|
|
223
253
|
end
|
|
@@ -18,7 +18,7 @@ require_relative '../../constants/constants'
|
|
|
18
18
|
|
|
19
19
|
class SettingsModel
|
|
20
20
|
attr_reader :sdk_key, :account_id, :usage_stats_account_id, :version, :collection_prefix,
|
|
21
|
-
:features, :campaigns, :campaign_groups, :groups, :poll_interval, :is_web_connectivity_enabled
|
|
21
|
+
:features, :campaigns, :campaign_groups, :groups, :poll_interval, :is_web_connectivity_enabled, :is_tracking_usage_enabled
|
|
22
22
|
|
|
23
23
|
def initialize(settings)
|
|
24
24
|
@sdk_key = settings["sdkKey"]
|
|
@@ -32,6 +32,7 @@ class SettingsModel
|
|
|
32
32
|
@campaign_groups = settings["campaignGroups"] || {}
|
|
33
33
|
@groups = settings["groups"] || {}
|
|
34
34
|
@is_web_connectivity_enabled = settings.fetch("isWebConnectivityEnabled", true)
|
|
35
|
+
@is_tracking_usage_enabled = settings.fetch("isMAU", false)
|
|
35
36
|
|
|
36
37
|
process_features(settings)
|
|
37
38
|
process_campaigns(settings)
|
|
@@ -98,4 +99,8 @@ class SettingsModel
|
|
|
98
99
|
def get_is_web_connectivity_enabled
|
|
99
100
|
@is_web_connectivity_enabled
|
|
100
101
|
end
|
|
102
|
+
|
|
103
|
+
def get_is_tracking_usage_enabled
|
|
104
|
+
@is_tracking_usage_enabled
|
|
105
|
+
end
|
|
101
106
|
end
|
|
@@ -17,15 +17,16 @@ require_relative '../../utils/uuid_util'
|
|
|
17
17
|
require_relative '../../services/settings_service'
|
|
18
18
|
|
|
19
19
|
class ContextModel
|
|
20
|
-
attr_accessor :id, :user_agent, :ip_address, :custom_variables, :variation_targeting_variables, :post_segmentation_variables, :vwo, :session_id, :uuid, :bucketing_seed
|
|
20
|
+
attr_accessor :id, :user_agent, :ip_address, :custom_variables, :variation_targeting_variables, :post_segmentation_variables, :platform_variables, :vwo, :session_id, :uuid, :bucketing_seed
|
|
21
21
|
|
|
22
|
-
def initialize(id = nil, user_agent = nil, ip_address = nil, custom_variables = {}, variation_targeting_variables = {}, post_segmentation_variables = {}, vwo = nil, session_id = nil, uuid = nil, bucketing_seed = nil)
|
|
22
|
+
def initialize(id = nil, user_agent = nil, ip_address = nil, custom_variables = {}, variation_targeting_variables = {}, post_segmentation_variables = {}, platform_variables = {}, vwo = nil, session_id = nil, uuid = nil, bucketing_seed = nil)
|
|
23
23
|
@id = id
|
|
24
24
|
@user_agent = user_agent
|
|
25
25
|
@ip_address = ip_address
|
|
26
26
|
@custom_variables = custom_variables || {}
|
|
27
27
|
@variation_targeting_variables = variation_targeting_variables || {}
|
|
28
28
|
@post_segmentation_variables = post_segmentation_variables || {}
|
|
29
|
+
@platform_variables = platform_variables || {}
|
|
29
30
|
@vwo = vwo
|
|
30
31
|
@session_id = session_id
|
|
31
32
|
@uuid = uuid
|
|
@@ -52,6 +53,7 @@ class ContextModel
|
|
|
52
53
|
@custom_variables = context[:customVariables] if context.key?(:customVariables)
|
|
53
54
|
@variation_targeting_variables = context[:variationTargetingVariables] if context.key?(:variationTargetingVariables)
|
|
54
55
|
@post_segmentation_variables = context[:postSegmentationVariables] if context.key?(:postSegmentationVariables)
|
|
56
|
+
@platform_variables = context[:platformVariables] if context.key?(:platformVariables)
|
|
55
57
|
@vwo = ContextVWOModel.new.model_from_dictionary(context[:_vwo]) if context.key?(:_vwo)
|
|
56
58
|
@bucketing_seed = context[:bucketingSeed].to_s if context.key?(:bucketingSeed)
|
|
57
59
|
|
|
@@ -120,6 +122,14 @@ class ContextModel
|
|
|
120
122
|
@post_segmentation_variables = post_segmentation_variables
|
|
121
123
|
end
|
|
122
124
|
|
|
125
|
+
def get_platform_variables
|
|
126
|
+
@platform_variables
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def set_platform_variables(platform_variables)
|
|
130
|
+
@platform_variables = platform_variables
|
|
131
|
+
end
|
|
132
|
+
|
|
123
133
|
def get_vwo
|
|
124
134
|
@vwo
|
|
125
135
|
end
|
|
@@ -24,6 +24,8 @@ require_relative '../../../services/settings_service'
|
|
|
24
24
|
require_relative '../../../utils/data_type_util'
|
|
25
25
|
require_relative '../../../enums/log_level_enum'
|
|
26
26
|
require_relative '../../../enums/api_enum'
|
|
27
|
+
require_relative '../enums/segment_operator_value_enum'
|
|
28
|
+
|
|
27
29
|
class SegmentationManager
|
|
28
30
|
@@instance = nil # Singleton instance
|
|
29
31
|
|
|
@@ -72,6 +74,43 @@ class SegmentationManager
|
|
|
72
74
|
end
|
|
73
75
|
|
|
74
76
|
def validate_segmentation(dsl, properties)
|
|
77
|
+
if has_campaign_variation_node?(dsl)
|
|
78
|
+
platform_vars = @evaluator.context&.get_platform_variables
|
|
79
|
+
web_testing_campaigns = platform_vars && (platform_vars[:webTestingCampaigns] || platform_vars['webTestingCampaigns'])
|
|
80
|
+
return false unless web_testing_campaigns
|
|
81
|
+
end
|
|
82
|
+
|
|
75
83
|
@evaluator.is_segmentation_valid(dsl, properties)
|
|
76
84
|
end
|
|
85
|
+
|
|
86
|
+
private
|
|
87
|
+
|
|
88
|
+
# Checks if the provided DSL contains a campaign variation node.
|
|
89
|
+
# This is used to determine if web testing pre-segmentation should be evaluated.
|
|
90
|
+
#
|
|
91
|
+
# @param dsl [Hash] The DSL (Domain Specific Language) representing the segmentation rules.
|
|
92
|
+
# @return [Boolean] Returns true if a web campaign variation node is found, false otherwise.
|
|
93
|
+
def has_campaign_variation_node?(dsl)
|
|
94
|
+
# Return false immediately if the DSL is not a hash
|
|
95
|
+
return false unless dsl.is_a?(Hash)
|
|
96
|
+
|
|
97
|
+
# Iterate through each operator and operand in the DSL
|
|
98
|
+
dsl.each do |operator, operand|
|
|
99
|
+
# Check if the current operator matches the web campaign variation operator
|
|
100
|
+
return true if operator.to_s == SegmentOperatorValueEnum::WEB_CAMPAIGN_VARIATION
|
|
101
|
+
|
|
102
|
+
# If the operand is an array, recursively check each sub-DSL within it
|
|
103
|
+
if operand.is_a?(Array)
|
|
104
|
+
operand.each do |sub_dsl|
|
|
105
|
+
return true if has_campaign_variation_node?(sub_dsl)
|
|
106
|
+
end
|
|
107
|
+
# If the operand is a hash, recursively check the hash itself
|
|
108
|
+
elsif operand.is_a?(Hash)
|
|
109
|
+
return true if has_campaign_variation_node?(operand)
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Return false if no campaign variation node is found in the entire DSL structure
|
|
114
|
+
false
|
|
115
|
+
end
|
|
77
116
|
end
|
|
@@ -57,6 +57,8 @@ class SegmentEvaluator
|
|
|
57
57
|
SegmentOperandEvaluator.new.evaluate_custom_variable_dsl(sub_dsl, properties)
|
|
58
58
|
when SegmentOperatorValueEnum::USER
|
|
59
59
|
SegmentOperandEvaluator.new.evaluate_user_dsl(sub_dsl, properties)
|
|
60
|
+
when SegmentOperatorValueEnum::WEB_CAMPAIGN_VARIATION
|
|
61
|
+
SegmentOperandEvaluator.new.evaluate_web_testing_campaign_variation_dsl(sub_dsl, @context)
|
|
60
62
|
when SegmentOperatorValueEnum::UA
|
|
61
63
|
SegmentOperandEvaluator.new.evaluate_user_agent_dsl(sub_dsl, @context)
|
|
62
64
|
when SegmentOperatorValueEnum::IP
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
|
|
15
15
|
require_relative '../utils/segment_util'
|
|
16
|
+
require_relative '../utils/web_testing_segment_util'
|
|
16
17
|
require_relative '../enums/segment_operand_value_enum'
|
|
17
18
|
require_relative '../enums/segment_operand_regex_enum'
|
|
18
19
|
require_relative '../enums/segment_operator_value_enum'
|
|
@@ -105,6 +106,22 @@ class SegmentOperandEvaluator
|
|
|
105
106
|
extract_result(processed_operand[:operand_type], processed_values[:operand_value], tag_value)
|
|
106
107
|
end
|
|
107
108
|
|
|
109
|
+
# Evaluates Web Testing pre-segmentation against `context.platform_variables[:webTestingCampaigns]`.
|
|
110
|
+
# @param dsl_operand_value [String] The operand value to evaluate
|
|
111
|
+
# @param context [ContextModel] The context to evaluate the operand against
|
|
112
|
+
# @return [Boolean] True if the operand value matches the tag value, false otherwise
|
|
113
|
+
def evaluate_web_testing_campaign_variation_dsl(dsl_operand_value, context)
|
|
114
|
+
operand = dsl_operand_value.to_s.strip
|
|
115
|
+
assigned_variations_by_campaign_id = WebTestingSegmentUtil.parse_web_testing_campaigns_from_context(context)
|
|
116
|
+
result_obj = WebTestingSegmentUtil.evaluate_web_testing_campaign_variation(operand, assigned_variations_by_campaign_id)
|
|
117
|
+
|
|
118
|
+
if result_obj[:invalid_format]
|
|
119
|
+
return false
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
result_obj[:result]
|
|
123
|
+
end
|
|
124
|
+
|
|
108
125
|
# Evaluates a given string tag value against a DSL operand value.
|
|
109
126
|
# @param dsl_operand_value [String] The DSL operand string (e.g., "contains(\"value\")").
|
|
110
127
|
# @param context [ContextModel] The context object containing the value to evaluate.
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# Copyright 2024-2026 Wingify Software Pvt. Ltd.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
require 'json'
|
|
16
|
+
require_relative '../../../enums/api_enum'
|
|
17
|
+
require_relative '../../../services/logger_service'
|
|
18
|
+
require_relative '../../../enums/log_level_enum'
|
|
19
|
+
require_relative '../../../utils/data_type_util'
|
|
20
|
+
|
|
21
|
+
module WebTestingSegmentUtil
|
|
22
|
+
class << self
|
|
23
|
+
# Normalizes Web Testing campaign map keys and variation values to strings.
|
|
24
|
+
# @param raw_assignments [Hash] The raw assignments map from the context.
|
|
25
|
+
# @return [Hash] The normalized assignments map with campaign_id as key and variation_id as value.
|
|
26
|
+
def normalize_web_testing_campaigns_map(raw_assignments)
|
|
27
|
+
campaign_id_to_variation_id = {}
|
|
28
|
+
raw_assignments.each do |campaign_id, assigned_variation_id|
|
|
29
|
+
unless assigned_variation_id.nil? || campaign_id.to_s.empty?
|
|
30
|
+
campaign_id_to_variation_id[campaign_id.to_s] = assigned_variation_id.to_s
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
campaign_id_to_variation_id
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Parses `context.platform_variables[:webTestingCampaigns]` (JSON string or plain object).
|
|
37
|
+
# @param context [ContextModel]
|
|
38
|
+
# @return [Hash, nil]
|
|
39
|
+
def parse_web_testing_campaigns_from_context(context)
|
|
40
|
+
platform_variables = context.get_platform_variables || {}
|
|
41
|
+
# support both string and symbol keys
|
|
42
|
+
web_testing_campaigns_input = platform_variables[:webTestingCampaigns] || platform_variables['webTestingCampaigns']
|
|
43
|
+
|
|
44
|
+
if web_testing_campaigns_input.nil?
|
|
45
|
+
return nil
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
if web_testing_campaigns_input.is_a?(Hash)
|
|
49
|
+
return normalize_web_testing_campaigns_map(web_testing_campaigns_input)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
if web_testing_campaigns_input.is_a?(String)
|
|
53
|
+
trimmed_json = web_testing_campaigns_input.strip
|
|
54
|
+
if trimmed_json.empty?
|
|
55
|
+
return nil
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
begin
|
|
59
|
+
# Check for duplicate keys using regex
|
|
60
|
+
all_campaign_id_tokens = trimmed_json.scan(/"([^"\\]*)"\s*:/).flatten
|
|
61
|
+
if all_campaign_id_tokens.any?
|
|
62
|
+
campaign_ids = all_campaign_id_tokens
|
|
63
|
+
has_duplicate_campaign_id = campaign_ids.length != campaign_ids.uniq.length
|
|
64
|
+
if has_duplicate_campaign_id
|
|
65
|
+
LoggerService.log(
|
|
66
|
+
LogLevelEnum::ERROR,
|
|
67
|
+
'INVALID_WEB_TESTING_CAMPAIGNS_DUPLICATE_KEY',
|
|
68
|
+
{ an: ApiEnum::GET_FLAG, uuid: context.get_uuid, sId: context.get_session_id }
|
|
69
|
+
)
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
parsed_assignments = JSON.parse(trimmed_json)
|
|
74
|
+
if parsed_assignments.is_a?(Hash)
|
|
75
|
+
return normalize_web_testing_campaigns_map(parsed_assignments)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
LoggerService.log(
|
|
79
|
+
LogLevelEnum::ERROR,
|
|
80
|
+
'INVALID_WEB_TESTING_CAMPAIGNS_JSON',
|
|
81
|
+
{ an: ApiEnum::GET_FLAG, uuid: context.get_uuid, sId: context.get_session_id }
|
|
82
|
+
)
|
|
83
|
+
rescue JSON::ParserError
|
|
84
|
+
LoggerService.log(
|
|
85
|
+
LogLevelEnum::ERROR,
|
|
86
|
+
'INVALID_WEB_TESTING_CAMPAIGNS_JSON',
|
|
87
|
+
{ an: ApiEnum::GET_FLAG, uuid: context.get_uuid, sId: context.get_session_id }
|
|
88
|
+
)
|
|
89
|
+
end
|
|
90
|
+
return nil
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# For array, numeric, boolean etc.
|
|
94
|
+
kind = web_testing_campaigns_input.is_a?(Array) ? 'array' : DataTypeUtil.get_type(web_testing_campaigns_input).downcase
|
|
95
|
+
LoggerService.log(
|
|
96
|
+
LogLevelEnum::ERROR,
|
|
97
|
+
'INVALID_WEB_TESTING_CAMPAIGNS_TYPE',
|
|
98
|
+
{ kind: kind, an: ApiEnum::GET_FLAG, uuid: context.get_uuid, sId: context.get_session_id }
|
|
99
|
+
)
|
|
100
|
+
nil
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Evaluates campaignVariation operand encoding:
|
|
104
|
+
# - "!C" — user is not in campaign C (no entry in map)
|
|
105
|
+
# - "C_!V" — user is in campaign C and assigned variation is not V
|
|
106
|
+
# - "C_V" — user is in campaign C with variation V
|
|
107
|
+
# - "C" (digits only) — user is in campaign C (any variation)
|
|
108
|
+
def evaluate_web_testing_campaign_variation(campaign_variation_operand, assigned_variations_by_campaign_id)
|
|
109
|
+
assignments = assigned_variations_by_campaign_id || {}
|
|
110
|
+
|
|
111
|
+
# match type !C
|
|
112
|
+
if match = /^!(\d+)$/.match(campaign_variation_operand)
|
|
113
|
+
campaign_id = match[1]
|
|
114
|
+
return { result: !assignments.key?(campaign_id), invalid_format: false }
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# match type C_!V
|
|
118
|
+
if match = /^(\d+)_!(\d+)$/.match(campaign_variation_operand)
|
|
119
|
+
campaign_id = match[1]
|
|
120
|
+
variation_id = match[2]
|
|
121
|
+
if !assignments.key?(campaign_id)
|
|
122
|
+
return { result: false, invalid_format: false }
|
|
123
|
+
end
|
|
124
|
+
return { result: assignments[campaign_id] != variation_id, invalid_format: false }
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# match type C_V
|
|
128
|
+
if match = /^(\d+)_(\d+)$/.match(campaign_variation_operand)
|
|
129
|
+
campaign_id = match[1]
|
|
130
|
+
variation_id = match[2]
|
|
131
|
+
if !assignments.key?(campaign_id)
|
|
132
|
+
return { result: false, invalid_format: false }
|
|
133
|
+
end
|
|
134
|
+
return { result: assignments[campaign_id] == variation_id, invalid_format: false }
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# match type C
|
|
138
|
+
if match = /^(\d+)$/.match(campaign_variation_operand)
|
|
139
|
+
campaign_id = match[1]
|
|
140
|
+
return { result: assignments.key?(campaign_id), invalid_format: false }
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
{ result: false, invalid_format: true }
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
|
@@ -64,3 +64,38 @@ def create_and_send_impression_for_variation_shown(settings, campaign_id, variat
|
|
|
64
64
|
NetworkUtil.send_post_api_request(properties, payload, { campaign_type: campaign_type, feature_key: feature_key, campaign_key: campaign_key, variation_name: variation_name })
|
|
65
65
|
end
|
|
66
66
|
end
|
|
67
|
+
|
|
68
|
+
# Sends the usage tracking payload to the server.
|
|
69
|
+
# Called whenever a user is evaluated via get_flag() but no variation_shown event is dispatched.
|
|
70
|
+
#
|
|
71
|
+
# @param settings [SettingsModel] The settings model containing configuration.
|
|
72
|
+
# @param context [ContextModel] The user context model.
|
|
73
|
+
# @param is_tracking_usage_enabled [Boolean] Whether usage tracking is active.
|
|
74
|
+
# @param feature_key [String] The key of the evaluated feature.
|
|
75
|
+
def send_tracking_usage(settings, context, is_tracking_usage_enabled, feature_key)
|
|
76
|
+
return unless is_tracking_usage_enabled
|
|
77
|
+
|
|
78
|
+
# Construct payload data for tracking usage
|
|
79
|
+
payload = NetworkUtil.get_tracking_usage_payload_data(context)
|
|
80
|
+
|
|
81
|
+
# Get base properties for the event
|
|
82
|
+
properties = NetworkUtil.get_events_base_properties(
|
|
83
|
+
EventEnum::USER_EVALUATED,
|
|
84
|
+
URI.encode_www_form_component(context.get_user_agent), # Encode user agent for URL safety
|
|
85
|
+
context.get_ip_address
|
|
86
|
+
)
|
|
87
|
+
LoggerService.log(LogLevelEnum::INFO, "USAGE_TRACKING_DISPATCHED", {
|
|
88
|
+
accountId: settings.account_id,
|
|
89
|
+
userId: context.get_id,
|
|
90
|
+
featureKey: feature_key
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
# check if batching is enabled
|
|
94
|
+
if BatchEventsQueue.instance
|
|
95
|
+
# add the payload to the batch events queue
|
|
96
|
+
BatchEventsQueue.instance.enqueue(payload)
|
|
97
|
+
else
|
|
98
|
+
# Send the constructed payload via POST request
|
|
99
|
+
NetworkUtil.send_post_api_request(properties, payload)
|
|
100
|
+
end
|
|
101
|
+
end
|
|
@@ -293,6 +293,33 @@ class NetworkUtil
|
|
|
293
293
|
properties
|
|
294
294
|
end
|
|
295
295
|
|
|
296
|
+
# Constructs the payload data for usage tracking call.
|
|
297
|
+
# Sent to server when a user is evaluated via get_flag() but no variation_shown
|
|
298
|
+
# event is dispatched by the SDK.
|
|
299
|
+
# @param context [ContextModel] The context model instance.
|
|
300
|
+
# @returns [Hash] The constructed payload.
|
|
301
|
+
def get_tracking_usage_payload_data(context)
|
|
302
|
+
properties = _get_event_base_payload(context.get_id, EventEnum::USER_EVALUATED, context.get_user_agent, context.get_ip_address)
|
|
303
|
+
|
|
304
|
+
# use sessionId from context if present
|
|
305
|
+
if context.get_session_id
|
|
306
|
+
properties[:d][:sessionId] = context.get_session_id
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
# use uuid from context if present
|
|
310
|
+
if context.get_uuid && !context.get_uuid.empty?
|
|
311
|
+
properties[:d][:visId] = context.get_uuid
|
|
312
|
+
properties[:d][:msgId] = "#{context.get_uuid}-#{get_current_unix_timestamp_in_millis}"
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
LoggerService.log(LogLevelEnum::DEBUG, "IMPRESSION_FOR_USAGE_TRACKING", {
|
|
316
|
+
accountId: SettingsService.instance.account_id,
|
|
317
|
+
userId: context.get_id
|
|
318
|
+
})
|
|
319
|
+
|
|
320
|
+
properties
|
|
321
|
+
end
|
|
322
|
+
|
|
296
323
|
# Sends a POST API request with given properties and payload
|
|
297
324
|
def send_post_api_request(properties, payload, campaign_info = {})
|
|
298
325
|
network_instance = NetworkManager.instance
|
data/lib/wingify.rb
CHANGED
|
@@ -43,6 +43,11 @@ module Wingify
|
|
|
43
43
|
.init_batch
|
|
44
44
|
|
|
45
45
|
if options[:settings]
|
|
46
|
+
if SettingsSchema.new.is_settings_valid(options[:settings])
|
|
47
|
+
SettingsService.instance.is_settings_valid = true
|
|
48
|
+
else
|
|
49
|
+
LoggerService.log(LogLevelEnum::ERROR, "INVALID_SETTINGS_SCHEMA", { accountId: options[:account_id], sdkKey: options[:sdk_key], settings: options[:settings], an: ApiEnum::INIT}, false)
|
|
50
|
+
end
|
|
46
51
|
return @@wingify_builder.build(options[:settings])
|
|
47
52
|
end
|
|
48
53
|
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: vwo-fme-ruby-sdk
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.60.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- VWO
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-06-
|
|
11
|
+
date: 2026-06-30 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: uuidtools
|
|
@@ -269,6 +269,7 @@ files:
|
|
|
269
269
|
- lib/wingify/packages/segmentation_evaluator/evaluators/segment_evaluator.rb
|
|
270
270
|
- lib/wingify/packages/segmentation_evaluator/evaluators/segment_operand_evaluator.rb
|
|
271
271
|
- lib/wingify/packages/segmentation_evaluator/utils/segment_util.rb
|
|
272
|
+
- lib/wingify/packages/segmentation_evaluator/utils/web_testing_segment_util.rb
|
|
272
273
|
- lib/wingify/packages/storage/connector.rb
|
|
273
274
|
- lib/wingify/packages/storage/storage.rb
|
|
274
275
|
- lib/wingify/services/batch_event_queue.rb
|