wingify-fme-ruby-sdk 1.50.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 +7 -0
- data/lib/resources/debug_messages.json +21 -0
- data/lib/resources/error_messages.json +63 -0
- data/lib/resources/info_messages.json +43 -0
- data/lib/resources/warn_messages.json +1 -0
- data/lib/vwo.rb +40 -0
- data/lib/wingify/api/get_flag.rb +244 -0
- data/lib/wingify/api/set_attribute.rb +57 -0
- data/lib/wingify/api/track_event.rb +80 -0
- data/lib/wingify/constants/constants.rb +106 -0
- data/lib/wingify/decorators/storage_decorator.rb +82 -0
- data/lib/wingify/enums/api_enum.rb +23 -0
- data/lib/wingify/enums/campaign_type_enum.rb +19 -0
- data/lib/wingify/enums/debug_category_enum.rb +20 -0
- data/lib/wingify/enums/decision_types_enum.rb +18 -0
- data/lib/wingify/enums/event_enum.rb +22 -0
- data/lib/wingify/enums/headers_enum.rb +20 -0
- data/lib/wingify/enums/hooks_enum.rb +17 -0
- data/lib/wingify/enums/http_method_enum.rb +18 -0
- data/lib/wingify/enums/log_level_enum.rb +21 -0
- data/lib/wingify/enums/log_level_to_number.rb +27 -0
- data/lib/wingify/enums/status_enum.rb +19 -0
- data/lib/wingify/enums/storage_enum.rb +22 -0
- data/lib/wingify/enums/url_enum.rb +22 -0
- data/lib/wingify/models/campaign/campaign_model.rb +192 -0
- data/lib/wingify/models/campaign/feature_model.rb +111 -0
- data/lib/wingify/models/campaign/impact_campaign_model.rb +38 -0
- data/lib/wingify/models/campaign/metric_model.rb +44 -0
- data/lib/wingify/models/campaign/rule_model.rb +56 -0
- data/lib/wingify/models/campaign/variable_model.rb +51 -0
- data/lib/wingify/models/campaign/variation_model.rb +137 -0
- data/lib/wingify/models/gateway_service_model.rb +39 -0
- data/lib/wingify/models/schemas/settings_schema_validation.rb +104 -0
- data/lib/wingify/models/settings/settings_model.rb +101 -0
- data/lib/wingify/models/storage/storage_data_model.rb +44 -0
- data/lib/wingify/models/user/context_model.rb +154 -0
- data/lib/wingify/models/user/context_vwo_model.rb +38 -0
- data/lib/wingify/models/user/get_flag_response.rb +50 -0
- data/lib/wingify/models/vwo_options_model.rb +129 -0
- data/lib/wingify/packages/decision_maker/decision_maker.rb +60 -0
- data/lib/wingify/packages/logger/core/log_manager.rb +92 -0
- data/lib/wingify/packages/logger/core/transport_manager.rb +76 -0
- data/lib/wingify/packages/logger/log_message_builder.rb +70 -0
- data/lib/wingify/packages/logger/logger.rb +38 -0
- data/lib/wingify/packages/logger/transports/console_transport.rb +49 -0
- data/lib/wingify/packages/network_layer/client/network_client.rb +276 -0
- data/lib/wingify/packages/network_layer/handlers/request_handler.rb +37 -0
- data/lib/wingify/packages/network_layer/manager/network_manager.rb +145 -0
- data/lib/wingify/packages/network_layer/models/global_request_model.rb +105 -0
- data/lib/wingify/packages/network_layer/models/request_model.rb +179 -0
- data/lib/wingify/packages/network_layer/models/response_model.rb +62 -0
- data/lib/wingify/packages/segmentation_evaluator/core/segmentation_manager.rb +77 -0
- data/lib/wingify/packages/segmentation_evaluator/enums/segment_operand_regex_enum.rb +29 -0
- data/lib/wingify/packages/segmentation_evaluator/enums/segment_operand_value_enum.rb +26 -0
- data/lib/wingify/packages/segmentation_evaluator/enums/segment_operator_value_enum.rb +33 -0
- data/lib/wingify/packages/segmentation_evaluator/evaluators/segment_evaluator.rb +218 -0
- data/lib/wingify/packages/segmentation_evaluator/evaluators/segment_operand_evaluator.rb +415 -0
- data/lib/wingify/packages/segmentation_evaluator/utils/segment_util.rb +44 -0
- data/lib/wingify/packages/storage/connector.rb +26 -0
- data/lib/wingify/packages/storage/storage.rb +47 -0
- data/lib/wingify/services/batch_event_queue.rb +179 -0
- data/lib/wingify/services/campaign_decision_service.rb +161 -0
- data/lib/wingify/services/hooks_service.rb +51 -0
- data/lib/wingify/services/logger_service.rb +114 -0
- data/lib/wingify/services/settings_service.rb +178 -0
- data/lib/wingify/services/storage_service.rb +66 -0
- data/lib/wingify/utils/batch_event_dispatcher_util.rb +178 -0
- data/lib/wingify/utils/brand_context.rb +33 -0
- data/lib/wingify/utils/brand_util.rb +53 -0
- data/lib/wingify/utils/campaign_util.rb +284 -0
- data/lib/wingify/utils/data_type_util.rb +105 -0
- data/lib/wingify/utils/debugger_service_util.rb +40 -0
- data/lib/wingify/utils/decision_util.rb +259 -0
- data/lib/wingify/utils/event_util.rb +55 -0
- data/lib/wingify/utils/function_util.rb +141 -0
- data/lib/wingify/utils/gateway_service_util.rb +101 -0
- data/lib/wingify/utils/impression_util.rb +66 -0
- data/lib/wingify/utils/log_message_util.rb +42 -0
- data/lib/wingify/utils/meg_util.rb +357 -0
- data/lib/wingify/utils/network_util.rb +503 -0
- data/lib/wingify/utils/rule_evaluation_util.rb +57 -0
- data/lib/wingify/utils/settings_util.rb +38 -0
- data/lib/wingify/utils/usage_stats_util.rb +119 -0
- data/lib/wingify/utils/uuid_util.rb +96 -0
- data/lib/wingify/wingify_builder.rb +261 -0
- data/lib/wingify/wingify_client.rb +227 -0
- data/lib/wingify.rb +117 -0
- metadata +327 -0
|
@@ -0,0 +1,26 @@
|
|
|
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
|
+
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"
|
|
19
|
+
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,47 @@
|
|
|
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_relative 'connector' # Import Connector class
|
|
16
|
+
|
|
17
|
+
class Storage
|
|
18
|
+
@instance = nil
|
|
19
|
+
|
|
20
|
+
attr_reader :connector
|
|
21
|
+
attr_accessor :is_storage_enabled
|
|
22
|
+
|
|
23
|
+
def initialize
|
|
24
|
+
@connector = nil
|
|
25
|
+
@is_storage_enabled = false
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Attach a connector (can be an instance or a class)
|
|
29
|
+
def attach_connector(connector)
|
|
30
|
+
if connector.is_a?(Class) # Check if it's a class before instantiating
|
|
31
|
+
@connector = connector.new
|
|
32
|
+
else
|
|
33
|
+
@connector = connector
|
|
34
|
+
end
|
|
35
|
+
@connector
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Singleton instance method
|
|
39
|
+
def self.instance
|
|
40
|
+
@instance ||= new
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Get the attached connector
|
|
44
|
+
def get_connector
|
|
45
|
+
@connector
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,179 @@
|
|
|
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_relative '../utils/data_type_util'
|
|
16
|
+
require_relative '../services/logger_service'
|
|
17
|
+
require_relative '../enums/log_level_enum'
|
|
18
|
+
require_relative '../constants/constants'
|
|
19
|
+
require_relative '../packages/network_layer/manager/network_manager'
|
|
20
|
+
require 'concurrent'
|
|
21
|
+
|
|
22
|
+
class BatchEventsQueue
|
|
23
|
+
class << self
|
|
24
|
+
def instance
|
|
25
|
+
@instance ||= nil
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def configure(batch_config)
|
|
29
|
+
@instance = new(batch_config)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
# Initializes a new batch events queue with the specified configuration
|
|
33
|
+
# @param batch_config Configuration object containing:
|
|
34
|
+
# - request_time_interval: Time interval between batch requests (in seconds)
|
|
35
|
+
# - events_per_request: Maximum number of events to include in a single request
|
|
36
|
+
# - flush_callback: Callback function to execute after flushing events
|
|
37
|
+
# - dispatcher: Function to handle sending the batched events
|
|
38
|
+
def initialize(batch_config)
|
|
39
|
+
@queue = []
|
|
40
|
+
@batch_config = batch_config
|
|
41
|
+
@network_client = NetworkManager.instance.get_client
|
|
42
|
+
|
|
43
|
+
if DataTypeUtil.is_number(batch_config[:request_time_interval]) && batch_config[:request_time_interval] >= 1
|
|
44
|
+
@request_time_interval = batch_config[:request_time_interval]
|
|
45
|
+
else
|
|
46
|
+
@request_time_interval = Constants::DEFAULT_REQUEST_TIME_INTERVAL
|
|
47
|
+
LoggerService.log(LogLevelEnum::INFO, "EVENT_BATCH_DEFAULTS", {
|
|
48
|
+
parameter: 'request_time_interval',
|
|
49
|
+
minLimit: 0,
|
|
50
|
+
defaultValue: "#{@request_time_interval} seconds"
|
|
51
|
+
})
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
if DataTypeUtil.is_number(batch_config[:events_per_request]) &&
|
|
55
|
+
batch_config[:events_per_request] > 0 &&
|
|
56
|
+
batch_config[:events_per_request] <= Constants::MAX_EVENTS_PER_REQUEST
|
|
57
|
+
@events_per_request = batch_config[:events_per_request]
|
|
58
|
+
elsif DataTypeUtil.is_number(batch_config[:events_per_request]) &&
|
|
59
|
+
batch_config[:events_per_request] > Constants::MAX_EVENTS_PER_REQUEST
|
|
60
|
+
@events_per_request = Constants::MAX_EVENTS_PER_REQUEST
|
|
61
|
+
LoggerService.log(LogLevelEnum::INFO, "EVENT_BATCH_MAX_LIMIT", {
|
|
62
|
+
parameter: 'events_per_request',
|
|
63
|
+
maxLimit: Constants::MAX_EVENTS_PER_REQUEST.to_s
|
|
64
|
+
})
|
|
65
|
+
else
|
|
66
|
+
@events_per_request = Constants::DEFAULT_EVENTS_PER_REQUEST
|
|
67
|
+
LoggerService.log(LogLevelEnum::INFO, "EVENT_BATCH_DEFAULTS", {
|
|
68
|
+
parameter: 'events_per_request',
|
|
69
|
+
minLimit: 0,
|
|
70
|
+
defaultValue: @events_per_request.to_s
|
|
71
|
+
})
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
@flush_callback = batch_config[:flush_callback] if batch_config[:flush_callback].respond_to?(:call)
|
|
75
|
+
|
|
76
|
+
@dispatcher = batch_config[:dispatcher]
|
|
77
|
+
@batch_lock = Mutex.new
|
|
78
|
+
@timer = nil
|
|
79
|
+
create_new_batch_timer
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Creates a new timer thread to automatically flush events after request_time_interval
|
|
83
|
+
# The timer is only created if one doesn't already exist
|
|
84
|
+
def create_new_batch_timer
|
|
85
|
+
return if @timer
|
|
86
|
+
|
|
87
|
+
@timer = Time.now + @request_time_interval
|
|
88
|
+
@thread = Thread.new { flush_when_request_times_up }
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Adds a new event to the queue and manages batch processing
|
|
92
|
+
# If queue reaches events_per_request limit, it triggers an immediate flush
|
|
93
|
+
# @param event The event to be added to the queue
|
|
94
|
+
def enqueue(event)
|
|
95
|
+
@queue.push(event)
|
|
96
|
+
|
|
97
|
+
LoggerService.log(LogLevelEnum::INFO, "EVENT_QUEUE", {
|
|
98
|
+
queueType: 'batch',
|
|
99
|
+
event: event.to_json
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
# if the number of events in the queue is equal to the events_per_request, flush
|
|
103
|
+
if @queue.length >= @events_per_request
|
|
104
|
+
flush
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Background thread function that monitors the timer
|
|
109
|
+
# When the timer expires, it flushes the queue and cleans up
|
|
110
|
+
def flush_when_request_times_up
|
|
111
|
+
sleep(1) while @timer && Time.now < @timer
|
|
112
|
+
flush
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Processes and sends all queued events
|
|
116
|
+
# @param manual Boolean indicating if flush was triggered manually
|
|
117
|
+
# Clears the queue after successful processing
|
|
118
|
+
def flush(manual = false)
|
|
119
|
+
@batch_lock.synchronize do
|
|
120
|
+
if @queue.any?
|
|
121
|
+
LoggerService.log(LogLevelEnum::DEBUG, "EVENT_BATCH_BEFORE_FLUSHING", {
|
|
122
|
+
manually: manual ? 'manually' : '',
|
|
123
|
+
length: @queue.length,
|
|
124
|
+
accountId: @batch_config[:account_id],
|
|
125
|
+
timer: manual ? 'Timer will be cleared and registered again' : ''
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
# add events to another queue
|
|
129
|
+
temp_queue = @queue.dup
|
|
130
|
+
@queue = []
|
|
131
|
+
|
|
132
|
+
if manual
|
|
133
|
+
future = Concurrent::Future.new(executor: @network_client.get_thread_pool) do
|
|
134
|
+
handle_flush_response(temp_queue, manual)
|
|
135
|
+
end
|
|
136
|
+
future.execute
|
|
137
|
+
@response = future.value
|
|
138
|
+
else
|
|
139
|
+
@network_client.get_thread_pool.post do
|
|
140
|
+
handle_flush_response(temp_queue, manual)
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
else
|
|
144
|
+
LoggerService.log(LogLevelEnum::DEBUG, "BATCH_QUEUE_EMPTY")
|
|
145
|
+
@response = {status: "success", events: []}
|
|
146
|
+
end
|
|
147
|
+
kill_old_thread if !manual && @thread
|
|
148
|
+
clear_request_timer
|
|
149
|
+
create_new_batch_timer
|
|
150
|
+
@response
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
private
|
|
155
|
+
|
|
156
|
+
def handle_flush_response(temp_queue, manual)
|
|
157
|
+
@response = @dispatcher.call(temp_queue, @flush_callback)
|
|
158
|
+
if @response[:status] == "success"
|
|
159
|
+
LoggerService.log(LogLevelEnum::INFO, "EVENT_BATCH_After_FLUSHING", {
|
|
160
|
+
manually: manual ? 'manually' : '',
|
|
161
|
+
length: temp_queue.length
|
|
162
|
+
})
|
|
163
|
+
else
|
|
164
|
+
@queue.concat(temp_queue)
|
|
165
|
+
end
|
|
166
|
+
temp_queue = []
|
|
167
|
+
@response
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# Resets the request timer to nil
|
|
171
|
+
def clear_request_timer
|
|
172
|
+
@timer = nil
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def kill_old_thread
|
|
176
|
+
@old_thread&.kill
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
|
|
@@ -0,0 +1,161 @@
|
|
|
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_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(context, campaign)
|
|
34
|
+
return false if campaign.nil? || context.nil?
|
|
35
|
+
|
|
36
|
+
user_id = context.get_id
|
|
37
|
+
bucketing_seed = context.get_bucketing_seed
|
|
38
|
+
bucketing_id = bucketing_seed || user_id
|
|
39
|
+
|
|
40
|
+
is_rollout_or_personalize = [CampaignTypeEnum::ROLLOUT, CampaignTypeEnum::PERSONALIZE].include?(campaign.get_type)
|
|
41
|
+
salt = is_rollout_or_personalize ? campaign.get_variations.first.get_salt : campaign.get_salt
|
|
42
|
+
traffic_allocation = is_rollout_or_personalize ? campaign.get_variations.first.get_weight : campaign.get_traffic
|
|
43
|
+
|
|
44
|
+
bucket_key = salt ? "#{salt}_#{bucketing_id}" : "#{campaign.get_id}_#{bucketing_id}"
|
|
45
|
+
value_assigned_to_user = DecisionMaker.new.get_bucket_value_for_user(bucket_key)
|
|
46
|
+
|
|
47
|
+
is_user_part = value_assigned_to_user != 0 && value_assigned_to_user <= traffic_allocation
|
|
48
|
+
|
|
49
|
+
LoggerService.log(LogLevelEnum::INFO, "USER_PART_OF_CAMPAIGN", {
|
|
50
|
+
userId: bucketing_id != user_id ? "#{user_id} (Seed: #{bucketing_id})" : user_id,
|
|
51
|
+
notPart: is_user_part ? '' : 'not',
|
|
52
|
+
campaignKey: campaign.get_type == CampaignTypeEnum::AB ? campaign.get_key : "#{campaign.get_name}_#{campaign.get_rule_key}"
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
is_user_part
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Returns the variation assigned to a user based on bucket value
|
|
59
|
+
# @param variations [Array<VariationModel>] The variations to check
|
|
60
|
+
# @param bucket_value [Integer] The bucket value to check
|
|
61
|
+
# @return [VariationModel] The variation assigned to the user
|
|
62
|
+
def get_variation(variations, bucket_value)
|
|
63
|
+
variations.find { |variation| bucket_value >= variation.get_start_range_variation && bucket_value <= variation.get_end_range_variation }
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Checks if the bucket value is in the range of the variation
|
|
67
|
+
# @param variation [VariationModel] The variation to check
|
|
68
|
+
# @param bucket_value [Integer] The bucket value to check
|
|
69
|
+
# @return [VariationModel] The variation if the bucket value is in the range, nil otherwise
|
|
70
|
+
def check_in_range(variation, bucket_value)
|
|
71
|
+
variation if bucket_value >= variation.get_start_range_variation && bucket_value <= variation.get_end_range_variation
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Buckets a user into a variation for a given campaign
|
|
75
|
+
# @param user_id [String] The ID of the user
|
|
76
|
+
# @param account_id [String] The ID of the account
|
|
77
|
+
# @param campaign [CampaignModel] The campaign to bucket the user into
|
|
78
|
+
# @return [VariationModel] The variation assigned to the user
|
|
79
|
+
def bucket_user_to_variation(context, account_id, campaign)
|
|
80
|
+
return nil if campaign.nil? || context.nil?
|
|
81
|
+
|
|
82
|
+
user_id = context.get_id
|
|
83
|
+
bucketing_seed = context.get_bucketing_seed
|
|
84
|
+
bucketing_id = bucketing_seed || user_id
|
|
85
|
+
|
|
86
|
+
multiplier = campaign.get_traffic ? 1 : nil
|
|
87
|
+
percent_traffic = campaign.get_traffic
|
|
88
|
+
salt = campaign.get_salt
|
|
89
|
+
bucket_key = salt ? "#{salt}_#{account_id}_#{bucketing_id}" : "#{campaign.get_id}_#{account_id}_#{bucketing_id}"
|
|
90
|
+
|
|
91
|
+
hash_value = DecisionMaker.new.generate_hash_value(bucket_key)
|
|
92
|
+
bucket_value = DecisionMaker.new.generate_bucket_value(hash_value, Constants::MAX_TRAFFIC_VALUE, multiplier)
|
|
93
|
+
|
|
94
|
+
LoggerService.log(LogLevelEnum::DEBUG, "USER_BUCKET_TO_VARIATION", {
|
|
95
|
+
userId: bucketing_id != user_id ? "#{user_id} (Seed: #{bucketing_id})" : user_id,
|
|
96
|
+
campaignKey: campaign.get_key,
|
|
97
|
+
percentTraffic: percent_traffic,
|
|
98
|
+
bucketValue: bucket_value,
|
|
99
|
+
hashValue: hash_value
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
get_variation(campaign.get_variations, bucket_value)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Pre-segmentation decision based on user context and campaign rules
|
|
106
|
+
# @param campaign [CampaignModel] The campaign to evaluate
|
|
107
|
+
# @param context [ContextModel] The context for the evaluation
|
|
108
|
+
# @return [Boolean] True if the user satisfies the campaign rules, false otherwise
|
|
109
|
+
def get_pre_segmentation_decision(campaign, context)
|
|
110
|
+
campaign_type = campaign.get_type
|
|
111
|
+
segments = if [CampaignTypeEnum::ROLLOUT, CampaignTypeEnum::PERSONALIZE].include?(campaign_type)
|
|
112
|
+
campaign.get_variations.first.get_segments
|
|
113
|
+
elsif campaign_type == CampaignTypeEnum::AB
|
|
114
|
+
campaign.get_segments
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
if DataTypeUtil.is_object(segments) && segments.empty?
|
|
118
|
+
LoggerService.log(LogLevelEnum::INFO, "SEGMENTATION_SKIP", {
|
|
119
|
+
userId: context.get_id,
|
|
120
|
+
campaignKey: campaign.get_type == CampaignTypeEnum::AB ? campaign.get_key : "#{campaign.get_name}_#{campaign.get_rule_key}"
|
|
121
|
+
})
|
|
122
|
+
return true
|
|
123
|
+
else
|
|
124
|
+
pre_segmentation_result = SegmentationManager.instance.validate_segmentation(segments, context.get_custom_variables)
|
|
125
|
+
|
|
126
|
+
if !pre_segmentation_result
|
|
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: 'failed'
|
|
131
|
+
})
|
|
132
|
+
return false
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
LoggerService.log(LogLevelEnum::INFO, "SEGMENTATION_STATUS", {
|
|
136
|
+
userId: context.get_id,
|
|
137
|
+
campaignKey: campaign.get_type == CampaignTypeEnum::AB ? campaign.get_key : "#{campaign.get_name}_#{campaign.get_rule_key}",
|
|
138
|
+
status: 'passed'
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
return true
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# Determines the variation assigned to a user for a campaign
|
|
146
|
+
# @param user_id [String] The ID of the user
|
|
147
|
+
# @param account_id [String] The ID of the account
|
|
148
|
+
# @param campaign [CampaignModel] The campaign to evaluate
|
|
149
|
+
# @return [VariationModel] The variation assigned to the user
|
|
150
|
+
def get_variation_alloted(context, account_id, campaign)
|
|
151
|
+
is_user_part = is_user_part_of_campaign(context, campaign)
|
|
152
|
+
|
|
153
|
+
if [CampaignTypeEnum::ROLLOUT, CampaignTypeEnum::PERSONALIZE].include?(campaign.get_type)
|
|
154
|
+
return campaign.get_variations.first if is_user_part
|
|
155
|
+
else
|
|
156
|
+
return bucket_user_to_variation(context, account_id, campaign) if is_user_part
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
nil
|
|
160
|
+
end
|
|
161
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
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_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,114 @@
|
|
|
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 '../wingify_client'
|
|
17
|
+
require_relative '../packages/logger/core/log_manager'
|
|
18
|
+
require_relative '../enums/log_level_enum'
|
|
19
|
+
require_relative '../utils/log_message_util'
|
|
20
|
+
require_relative '../enums/debug_category_enum'
|
|
21
|
+
require_relative '../utils/debugger_service_util'
|
|
22
|
+
require_relative '../utils/brand_context'
|
|
23
|
+
require_relative '../utils/brand_util'
|
|
24
|
+
|
|
25
|
+
class LoggerService
|
|
26
|
+
class << self
|
|
27
|
+
attr_accessor :debug_messages, :info_messages, :error_messages, :warning_messages
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def self.log(level, key = nil, map = {}, should_send_to_vwo = true)
|
|
31
|
+
is_via_vwo = BrandContext.is_via_vwo?
|
|
32
|
+
log_manager = LogManager.instance({ prefix: BrandUtil.get_log_prefix(is_via_vwo) })
|
|
33
|
+
|
|
34
|
+
if key && map
|
|
35
|
+
message = build_message(get_messages(level)[key], map)
|
|
36
|
+
else
|
|
37
|
+
message = key # key acts as the message when no map is provided
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
case level
|
|
41
|
+
when LogLevelEnum::DEBUG
|
|
42
|
+
log_manager.debug(message)
|
|
43
|
+
when LogLevelEnum::INFO
|
|
44
|
+
log_manager.info(message)
|
|
45
|
+
when LogLevelEnum::WARN
|
|
46
|
+
log_manager.warn(message)
|
|
47
|
+
else
|
|
48
|
+
log_manager.error(message)
|
|
49
|
+
if should_send_to_vwo
|
|
50
|
+
# print log
|
|
51
|
+
self.send_log_to_vwo(key, message, map)
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def initialize(config = {})
|
|
57
|
+
is_via_vwo = BrandContext.is_via_vwo?
|
|
58
|
+
config[:prefix] ||= BrandUtil.get_log_prefix(is_via_vwo)
|
|
59
|
+
|
|
60
|
+
# Initialize the LogManager
|
|
61
|
+
LogManager.instance(config)
|
|
62
|
+
|
|
63
|
+
# Read the log files and set class variables
|
|
64
|
+
self.class.debug_messages = read_and_stamp_log_files('debug_messages.json', is_via_vwo)
|
|
65
|
+
self.class.info_messages = read_and_stamp_log_files('info_messages.json', is_via_vwo)
|
|
66
|
+
self.class.error_messages = read_and_stamp_log_files('error_messages.json', is_via_vwo)
|
|
67
|
+
self.class.warning_messages = read_and_stamp_log_files('warn_messages.json', is_via_vwo)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
private
|
|
71
|
+
|
|
72
|
+
def read_and_stamp_log_files(file_name, is_via_vwo)
|
|
73
|
+
begin
|
|
74
|
+
# Use absolute path resolution from the project root
|
|
75
|
+
file_path = File.join(File.expand_path('../../resources', __dir__), file_name)
|
|
76
|
+
raw = JSON.parse(File.read(file_path))
|
|
77
|
+
brand = BrandUtil.get_brand_name(is_via_vwo)
|
|
78
|
+
log_prefix = BrandUtil.get_log_prefix(is_via_vwo)
|
|
79
|
+
raw.transform_values do |msg|
|
|
80
|
+
msg.gsub('{brand}', brand).gsub('{log_prefix}', log_prefix)
|
|
81
|
+
end
|
|
82
|
+
rescue StandardError => e
|
|
83
|
+
puts "Error reading log file #{file_name}: #{e.message}"
|
|
84
|
+
return {}
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def self.get_messages(level)
|
|
89
|
+
case level
|
|
90
|
+
when LogLevelEnum::DEBUG
|
|
91
|
+
@debug_messages
|
|
92
|
+
when LogLevelEnum::INFO
|
|
93
|
+
@info_messages
|
|
94
|
+
when LogLevelEnum::WARN
|
|
95
|
+
@warning_messages
|
|
96
|
+
else
|
|
97
|
+
@error_messages
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def self.send_log_to_vwo(template, message, debug_props = {})
|
|
102
|
+
# send debugger event to VWO
|
|
103
|
+
begin
|
|
104
|
+
debug_props[:msg_t] = template
|
|
105
|
+
debug_props[:msg] = message
|
|
106
|
+
debug_props[:lt] = LogLevelEnum::ERROR
|
|
107
|
+
debug_props[:cg] = DebugCategoryEnum::ERROR
|
|
108
|
+
|
|
109
|
+
DebuggerServiceUtil.send_debugger_event(debug_props)
|
|
110
|
+
rescue StandardError => e
|
|
111
|
+
puts "[#{BrandUtil.get_log_prefix(BrandContext.is_via_vwo?)}] ERROR: Error sending log to VWO: #{e.message}"
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|