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.
Files changed (88) hide show
  1. checksums.yaml +7 -0
  2. data/lib/resources/debug_messages.json +21 -0
  3. data/lib/resources/error_messages.json +63 -0
  4. data/lib/resources/info_messages.json +43 -0
  5. data/lib/resources/warn_messages.json +1 -0
  6. data/lib/vwo.rb +40 -0
  7. data/lib/wingify/api/get_flag.rb +244 -0
  8. data/lib/wingify/api/set_attribute.rb +57 -0
  9. data/lib/wingify/api/track_event.rb +80 -0
  10. data/lib/wingify/constants/constants.rb +106 -0
  11. data/lib/wingify/decorators/storage_decorator.rb +82 -0
  12. data/lib/wingify/enums/api_enum.rb +23 -0
  13. data/lib/wingify/enums/campaign_type_enum.rb +19 -0
  14. data/lib/wingify/enums/debug_category_enum.rb +20 -0
  15. data/lib/wingify/enums/decision_types_enum.rb +18 -0
  16. data/lib/wingify/enums/event_enum.rb +22 -0
  17. data/lib/wingify/enums/headers_enum.rb +20 -0
  18. data/lib/wingify/enums/hooks_enum.rb +17 -0
  19. data/lib/wingify/enums/http_method_enum.rb +18 -0
  20. data/lib/wingify/enums/log_level_enum.rb +21 -0
  21. data/lib/wingify/enums/log_level_to_number.rb +27 -0
  22. data/lib/wingify/enums/status_enum.rb +19 -0
  23. data/lib/wingify/enums/storage_enum.rb +22 -0
  24. data/lib/wingify/enums/url_enum.rb +22 -0
  25. data/lib/wingify/models/campaign/campaign_model.rb +192 -0
  26. data/lib/wingify/models/campaign/feature_model.rb +111 -0
  27. data/lib/wingify/models/campaign/impact_campaign_model.rb +38 -0
  28. data/lib/wingify/models/campaign/metric_model.rb +44 -0
  29. data/lib/wingify/models/campaign/rule_model.rb +56 -0
  30. data/lib/wingify/models/campaign/variable_model.rb +51 -0
  31. data/lib/wingify/models/campaign/variation_model.rb +137 -0
  32. data/lib/wingify/models/gateway_service_model.rb +39 -0
  33. data/lib/wingify/models/schemas/settings_schema_validation.rb +104 -0
  34. data/lib/wingify/models/settings/settings_model.rb +101 -0
  35. data/lib/wingify/models/storage/storage_data_model.rb +44 -0
  36. data/lib/wingify/models/user/context_model.rb +154 -0
  37. data/lib/wingify/models/user/context_vwo_model.rb +38 -0
  38. data/lib/wingify/models/user/get_flag_response.rb +50 -0
  39. data/lib/wingify/models/vwo_options_model.rb +129 -0
  40. data/lib/wingify/packages/decision_maker/decision_maker.rb +60 -0
  41. data/lib/wingify/packages/logger/core/log_manager.rb +92 -0
  42. data/lib/wingify/packages/logger/core/transport_manager.rb +76 -0
  43. data/lib/wingify/packages/logger/log_message_builder.rb +70 -0
  44. data/lib/wingify/packages/logger/logger.rb +38 -0
  45. data/lib/wingify/packages/logger/transports/console_transport.rb +49 -0
  46. data/lib/wingify/packages/network_layer/client/network_client.rb +276 -0
  47. data/lib/wingify/packages/network_layer/handlers/request_handler.rb +37 -0
  48. data/lib/wingify/packages/network_layer/manager/network_manager.rb +145 -0
  49. data/lib/wingify/packages/network_layer/models/global_request_model.rb +105 -0
  50. data/lib/wingify/packages/network_layer/models/request_model.rb +179 -0
  51. data/lib/wingify/packages/network_layer/models/response_model.rb +62 -0
  52. data/lib/wingify/packages/segmentation_evaluator/core/segmentation_manager.rb +77 -0
  53. data/lib/wingify/packages/segmentation_evaluator/enums/segment_operand_regex_enum.rb +29 -0
  54. data/lib/wingify/packages/segmentation_evaluator/enums/segment_operand_value_enum.rb +26 -0
  55. data/lib/wingify/packages/segmentation_evaluator/enums/segment_operator_value_enum.rb +33 -0
  56. data/lib/wingify/packages/segmentation_evaluator/evaluators/segment_evaluator.rb +218 -0
  57. data/lib/wingify/packages/segmentation_evaluator/evaluators/segment_operand_evaluator.rb +415 -0
  58. data/lib/wingify/packages/segmentation_evaluator/utils/segment_util.rb +44 -0
  59. data/lib/wingify/packages/storage/connector.rb +26 -0
  60. data/lib/wingify/packages/storage/storage.rb +47 -0
  61. data/lib/wingify/services/batch_event_queue.rb +179 -0
  62. data/lib/wingify/services/campaign_decision_service.rb +161 -0
  63. data/lib/wingify/services/hooks_service.rb +51 -0
  64. data/lib/wingify/services/logger_service.rb +114 -0
  65. data/lib/wingify/services/settings_service.rb +178 -0
  66. data/lib/wingify/services/storage_service.rb +66 -0
  67. data/lib/wingify/utils/batch_event_dispatcher_util.rb +178 -0
  68. data/lib/wingify/utils/brand_context.rb +33 -0
  69. data/lib/wingify/utils/brand_util.rb +53 -0
  70. data/lib/wingify/utils/campaign_util.rb +284 -0
  71. data/lib/wingify/utils/data_type_util.rb +105 -0
  72. data/lib/wingify/utils/debugger_service_util.rb +40 -0
  73. data/lib/wingify/utils/decision_util.rb +259 -0
  74. data/lib/wingify/utils/event_util.rb +55 -0
  75. data/lib/wingify/utils/function_util.rb +141 -0
  76. data/lib/wingify/utils/gateway_service_util.rb +101 -0
  77. data/lib/wingify/utils/impression_util.rb +66 -0
  78. data/lib/wingify/utils/log_message_util.rb +42 -0
  79. data/lib/wingify/utils/meg_util.rb +357 -0
  80. data/lib/wingify/utils/network_util.rb +503 -0
  81. data/lib/wingify/utils/rule_evaluation_util.rb +57 -0
  82. data/lib/wingify/utils/settings_util.rb +38 -0
  83. data/lib/wingify/utils/usage_stats_util.rb +119 -0
  84. data/lib/wingify/utils/uuid_util.rb +96 -0
  85. data/lib/wingify/wingify_builder.rb +261 -0
  86. data/lib/wingify/wingify_client.rb +227 -0
  87. data/lib/wingify.rb +117 -0
  88. 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