vwo-fme-ruby-sdk 1.2.0 → 1.3.1

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 (82) hide show
  1. checksums.yaml +4 -4
  2. data/lib/resources/debug_messages.json +8 -1
  3. data/lib/resources/error_messages.json +5 -1
  4. data/lib/resources/info_messages.json +9 -1
  5. data/lib/vwo/api/get_flag.rb +30 -27
  6. data/lib/vwo/api/set_attribute.rb +10 -3
  7. data/lib/vwo/api/track_event.rb +11 -3
  8. data/lib/vwo/constants/constants.rb +6 -2
  9. data/lib/vwo/decorators/storage_decorator.rb +1 -1
  10. data/lib/vwo/enums/api_enum.rb +1 -1
  11. data/lib/vwo/enums/campaign_type_enum.rb +1 -1
  12. data/lib/vwo/enums/decision_types_enum.rb +1 -1
  13. data/lib/vwo/enums/event_enum.rb +1 -1
  14. data/lib/vwo/enums/headers_enum.rb +1 -1
  15. data/lib/vwo/enums/hooks_enum.rb +1 -1
  16. data/lib/vwo/enums/http_method_enum.rb +1 -1
  17. data/lib/vwo/enums/log_level_enum.rb +1 -1
  18. data/lib/vwo/enums/log_level_to_number.rb +27 -0
  19. data/lib/vwo/enums/status_enum.rb +1 -1
  20. data/lib/vwo/enums/storage_enum.rb +1 -1
  21. data/lib/vwo/enums/url_enum.rb +2 -1
  22. data/lib/vwo/models/campaign/campaign_model.rb +1 -1
  23. data/lib/vwo/models/campaign/feature_model.rb +1 -1
  24. data/lib/vwo/models/campaign/impact_campaign_model.rb +1 -1
  25. data/lib/vwo/models/campaign/metric_model.rb +1 -1
  26. data/lib/vwo/models/campaign/rule_model.rb +1 -1
  27. data/lib/vwo/models/campaign/variable_model.rb +1 -1
  28. data/lib/vwo/models/campaign/variation_model.rb +1 -1
  29. data/lib/vwo/models/gateway_service_model.rb +1 -1
  30. data/lib/vwo/models/schemas/settings_schema_validation.rb +1 -1
  31. data/lib/vwo/models/settings/settings_model.rb +8 -2
  32. data/lib/vwo/models/storage/storage_data_model.rb +1 -1
  33. data/lib/vwo/models/user/context_model.rb +1 -1
  34. data/lib/vwo/models/user/context_vwo_model.rb +1 -1
  35. data/lib/vwo/models/user/get_flag_response.rb +1 -1
  36. data/lib/vwo/models/vwo_options_model.rb +13 -2
  37. data/lib/vwo/packages/decision_maker/decision_maker.rb +1 -1
  38. data/lib/vwo/packages/logger/core/log_manager.rb +1 -1
  39. data/lib/vwo/packages/logger/core/transport_manager.rb +4 -15
  40. data/lib/vwo/packages/logger/log_message_builder.rb +1 -1
  41. data/lib/vwo/packages/logger/logger.rb +1 -1
  42. data/lib/vwo/packages/logger/transports/console_transport.rb +1 -1
  43. data/lib/vwo/packages/network_layer/client/network_client.rb +39 -25
  44. data/lib/vwo/packages/network_layer/handlers/request_handler.rb +1 -1
  45. data/lib/vwo/packages/network_layer/manager/network_manager.rb +6 -4
  46. data/lib/vwo/packages/network_layer/models/global_request_model.rb +1 -1
  47. data/lib/vwo/packages/network_layer/models/request_model.rb +1 -1
  48. data/lib/vwo/packages/network_layer/models/response_model.rb +9 -1
  49. data/lib/vwo/packages/segmentation_evaluator/core/segmentation_manager.rb +1 -1
  50. data/lib/vwo/packages/segmentation_evaluator/enums/segment_operand_regex_enum.rb +1 -1
  51. data/lib/vwo/packages/segmentation_evaluator/enums/segment_operand_value_enum.rb +1 -1
  52. data/lib/vwo/packages/segmentation_evaluator/enums/segment_operator_value_enum.rb +1 -1
  53. data/lib/vwo/packages/segmentation_evaluator/evaluators/segment_evaluator.rb +1 -1
  54. data/lib/vwo/packages/segmentation_evaluator/evaluators/segment_operand_evaluator.rb +1 -1
  55. data/lib/vwo/packages/segmentation_evaluator/utils/segment_util.rb +1 -1
  56. data/lib/vwo/packages/storage/connector.rb +1 -1
  57. data/lib/vwo/packages/storage/storage.rb +3 -1
  58. data/lib/vwo/services/batch_event_queue.rb +179 -0
  59. data/lib/vwo/services/campaign_decision_service.rb +1 -1
  60. data/lib/vwo/services/hooks_service.rb +1 -1
  61. data/lib/vwo/services/logger_service.rb +1 -1
  62. data/lib/vwo/services/settings_service.rb +4 -2
  63. data/lib/vwo/services/storage_service.rb +1 -1
  64. data/lib/vwo/utils/batch_event_dispatcher.rb +117 -0
  65. data/lib/vwo/utils/campaign_util.rb +1 -1
  66. data/lib/vwo/utils/data_type_util.rb +1 -1
  67. data/lib/vwo/utils/decision_util.rb +1 -1
  68. data/lib/vwo/utils/function_util.rb +5 -1
  69. data/lib/vwo/utils/gateway_service_util.rb +1 -1
  70. data/lib/vwo/utils/impression_util.rb +10 -3
  71. data/lib/vwo/utils/log_message_util.rb +1 -1
  72. data/lib/vwo/utils/meg_util.rb +1 -1
  73. data/lib/vwo/utils/network_util.rb +31 -8
  74. data/lib/vwo/utils/rule_evaluation_util.rb +1 -1
  75. data/lib/vwo/utils/settings_util.rb +1 -1
  76. data/lib/vwo/utils/url_util.rb +1 -1
  77. data/lib/vwo/utils/usage_stats_util.rb +117 -0
  78. data/lib/vwo/utils/uuid_util.rb +1 -1
  79. data/lib/vwo/vwo_builder.rb +107 -24
  80. data/lib/vwo/vwo_client.rb +23 -4
  81. data/lib/vwo.rb +3 -1
  82. metadata +6 -2
@@ -1,4 +1,4 @@
1
- # Copyright 2025 Wingify Software Pvt. Ltd.
1
+ # Copyright 2024-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.
@@ -27,13 +27,26 @@ class NetworkClient
27
27
  def initialize(options = {})
28
28
  # options for threading
29
29
  @should_use_threading = options.key?(:enabled) ? options[:enabled] : Constants::SHOULD_USE_THREADING
30
- @thread_pool = Concurrent::FixedThreadPool.new(options.key?(:max_pool_size) ? options[:max_pool_size] : Constants::MAX_POOL_SIZE)
30
+ @thread_pool = Concurrent::ThreadPoolExecutor.new(
31
+ # Minimum number of threads to keep alive in the pool
32
+ min_threads: 1,
33
+ # Maximum number of threads allowed in the pool, configurable via options or defaults to MAX_POOL_SIZE constant
34
+ max_threads: options.key?(:max_pool_size) ? options[:max_pool_size] : Constants::MAX_POOL_SIZE,
35
+ # Maximum number of tasks that can be queued when all threads are busy
36
+ max_queue: options.key?(:max_queue_size) ? options[:max_queue_size] : Constants::MAX_QUEUE_SIZE,
37
+ # When queue is full, execute task in the caller's thread rather than rejecting it
38
+ fallback_policy: :caller_runs
39
+ )
31
40
  end
32
41
 
33
42
  def get_thread_pool
34
43
  @thread_pool
35
44
  end
36
45
 
46
+ def get_should_use_threading
47
+ @should_use_threading
48
+ end
49
+
37
50
  def get(request_model)
38
51
  # Build the URL and headers
39
52
  url = request_model.get_url + request_model.get_path
@@ -67,22 +80,24 @@ class NetworkClient
67
80
  end
68
81
 
69
82
  def post(request_model)
70
- def execute_post(request_model)
71
- url = request_model.get_url + request_model.get_path
72
- uri = URI(url)
73
- headers = request_model.get_headers # Directly use the hash from the request model
74
- body = JSON.dump(request_model.get_body)
75
-
76
- request = Net::HTTP::Post.new(uri, headers) # Pass the hash of headers directly
77
- request.body = body
78
-
79
- response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https') { |http| http.request(request) }
80
-
81
- response_model = ResponseModel.new
82
- response_model.set_status_code(response.code.to_i)
83
+ url = request_model.get_url + request_model.get_path
84
+ uri = URI(url)
85
+ headers = request_model.get_headers
86
+ body = JSON.dump(request_model.get_body)
87
+
88
+ request = Net::HTTP::Post.new(uri, headers)
89
+ request.body = body
83
90
 
84
- # Check if the response body is empty or invalid before parsing
85
- if response.is_a?(Net::HTTPSuccess) && !response.body.strip.empty?
91
+ response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https') { |http| http.request(request) }
92
+
93
+ response_model = ResponseModel.new
94
+ response_model.set_status_code(response.code.to_i)
95
+
96
+ # Check if the response body is empty or invalid before parsing
97
+ if response.is_a?(Net::HTTPSuccess) && !response.body.strip.empty?
98
+ # Check if the response body is JSON
99
+ content_type = response['Content-Type']&.downcase
100
+ if content_type&.include?('application/json')
86
101
  begin
87
102
  parsed_data = JSON.parse(response.body)
88
103
  response_model.set_data(parsed_data)
@@ -91,17 +106,16 @@ class NetworkClient
91
106
  response_model.set_error("Invalid JSON response: #{e.message}")
92
107
  end
93
108
  else
109
+ response_model.set_data(response.body)
94
110
  end
95
- rescue StandardError => e
96
- LoggerService.log(LogLevelEnum::ERROR, "POST request failed: #{e.message}", nil)
97
111
  end
98
112
 
99
- # Check if threading is enabled in options
100
- if @should_use_threading
101
- @thread_pool.post { execute_post(request_model) }
102
- else
103
- execute_post(request_model)
104
- end
113
+ response_model
114
+ rescue StandardError => e
115
+ LoggerService.log(LogLevelEnum::ERROR, "POST request failed: #{e.message}", nil)
116
+ response_model = ResponseModel.new
117
+ response_model.set_error(e.message)
118
+ response_model
105
119
  end
106
120
 
107
121
  end
@@ -1,4 +1,4 @@
1
- # Copyright 2025 Wingify Software Pvt. Ltd.
1
+ # Copyright 2024-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.
@@ -1,4 +1,4 @@
1
- # Copyright 2025 Wingify Software Pvt. Ltd.
1
+ # Copyright 2024-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.
@@ -69,10 +69,12 @@ class NetworkManager
69
69
  begin
70
70
  network_options = create_request(request)
71
71
  raise 'No URL found' if network_options.get_url.nil?
72
-
73
- @client.post(network_options)
72
+
73
+ response = @client.post(network_options) # Return the response
74
+ response
74
75
  rescue => e
75
76
  LoggerService.log(LogLevelEnum::ERROR, "Error posting: #{e.message}", nil)
77
+ return ResponseModel.new.set_error(e.message) # Return error response
76
78
  end
77
- end
79
+ end
78
80
  end
@@ -1,4 +1,4 @@
1
- # Copyright 2025 Wingify Software Pvt. Ltd.
1
+ # Copyright 2024-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.
@@ -1,4 +1,4 @@
1
- # Copyright 2025 Wingify Software Pvt. Ltd.
1
+ # Copyright 2024-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.
@@ -1,4 +1,4 @@
1
- # Copyright 2025 Wingify Software Pvt. Ltd.
1
+ # Copyright 2024-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.
@@ -41,5 +41,13 @@ class ResponseModel
41
41
  def set_error(error)
42
42
  @error = error
43
43
  end
44
+
45
+ def get_error
46
+ @error
47
+ end
48
+
49
+ def get_status_code
50
+ @status_code
51
+ end
44
52
  end
45
53
 
@@ -1,4 +1,4 @@
1
- # Copyright 2025 Wingify Software Pvt. Ltd.
1
+ # Copyright 2024-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.
@@ -1,4 +1,4 @@
1
- # Copyright 2025 Wingify Software Pvt. Ltd.
1
+ # Copyright 2024-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.
@@ -1,4 +1,4 @@
1
- # Copyright 2025 Wingify Software Pvt. Ltd.
1
+ # Copyright 2024-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.
@@ -1,4 +1,4 @@
1
- # Copyright 2025 Wingify Software Pvt. Ltd.
1
+ # Copyright 2024-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.
@@ -1,4 +1,4 @@
1
- # Copyright 2025 Wingify Software Pvt. Ltd.
1
+ # Copyright 2024-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.
@@ -1,4 +1,4 @@
1
- # Copyright 2025 Wingify Software Pvt. Ltd.
1
+ # Copyright 2024-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.
@@ -1,4 +1,4 @@
1
- # Copyright 2025 Wingify Software Pvt. Ltd.
1
+ # Copyright 2024-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.
@@ -1,4 +1,4 @@
1
- # Copyright 2025 Wingify Software Pvt. Ltd.
1
+ # Copyright 2024-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.
@@ -1,4 +1,4 @@
1
- # Copyright 2025 Wingify Software Pvt. Ltd.
1
+ # Copyright 2024-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.
@@ -18,9 +18,11 @@ class Storage
18
18
  @instance = nil
19
19
 
20
20
  attr_reader :connector
21
+ attr_accessor :is_storage_enabled
21
22
 
22
23
  def initialize
23
24
  @connector = nil
25
+ @is_storage_enabled = false
24
26
  end
25
27
 
26
28
  # Attach a connector (can be an instance or a class)
@@ -0,0 +1,179 @@
1
+ # Copyright 2024-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/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
+
@@ -1,4 +1,4 @@
1
- # Copyright 2025 Wingify Software Pvt. Ltd.
1
+ # Copyright 2024-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.
@@ -1,4 +1,4 @@
1
- # Copyright 2025 Wingify Software Pvt. Ltd.
1
+ # Copyright 2024-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.
@@ -1,4 +1,4 @@
1
- # Copyright 2025 Wingify Software Pvt. Ltd.
1
+ # Copyright 2024-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.
@@ -1,4 +1,4 @@
1
- # Copyright 2025 Wingify Software Pvt. Ltd.
1
+ # Copyright 2024-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.
@@ -23,7 +23,7 @@ require_relative '../enums/log_level_enum'
23
23
  require_relative '../models/schemas/settings_schema_validation'
24
24
 
25
25
  class SettingsService
26
- attr_accessor :sdk_key, :account_id, :expiry, :network_timeout, :hostname, :port, :protocol, :is_gateway_service_provided
26
+ attr_accessor :sdk_key, :account_id, :expiry, :network_timeout, :hostname, :port, :protocol, :is_gateway_service_provided, :is_settings_valid
27
27
 
28
28
  class << self
29
29
  attr_accessor :instance
@@ -38,6 +38,7 @@ class SettingsService
38
38
  @account_id = options[:account_id]
39
39
  @expiry = options.dig(:settings, :expiry) || Constants::SETTINGS_EXPIRY
40
40
  @network_timeout = options.dig(:settings, :timeout) || Constants::SETTINGS_TIMEOUT
41
+ @is_settings_valid = false
41
42
 
42
43
  if options[:gateway_service] && options[:gateway_service][:url]
43
44
  parsed_url = URI.parse(options[:gateway_service][:url].start_with?(Constants::HTTP_PROTOCOL) || options[:gateway_service][:url].start_with?(Constants::HTTPS_PROTOCOL) ? options[:gateway_service][:url] : "#{Constants::HTTPS_PROTOCOL}#{options[:gateway_service][:url]}")
@@ -109,6 +110,7 @@ class SettingsService
109
110
  settings = fetch_settings_and_cache_in_storage
110
111
  is_valid = SettingsSchema.new.is_settings_valid(settings)
111
112
  if is_valid
113
+ @is_settings_valid = true
112
114
  LoggerService.log(LogLevelEnum::INFO, "SETTINGS_FETCH_SUCCESS")
113
115
  settings
114
116
  else
@@ -1,4 +1,4 @@
1
- # Copyright 2025 Wingify Software Pvt. Ltd.
1
+ # Copyright 2024-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.
@@ -0,0 +1,117 @@
1
+ # Copyright 2024-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 './network_util'
16
+ require_relative '../enums/http_method_enum'
17
+ require_relative '../enums/url_enum'
18
+ require_relative '../enums/log_level_enum'
19
+ require_relative '../services/logger_service'
20
+ require_relative '../packages/network_layer/manager/network_manager'
21
+ require_relative '../packages/network_layer/models/request_model'
22
+
23
+ class BatchEventDispatcher
24
+
25
+ class << self
26
+
27
+ # Dispatches a batch of events to the VWO server
28
+ # @param properties [Hash] The event properties to send
29
+ # @param callback [Proc] Optional callback function to execute after the request (defaults to empty proc)
30
+ # @param query_params [Hash] Query parameters to include in the request
31
+ def dispatch(properties, callback = -> {}, query_params)
32
+ # Send the prepared payload via POST API request
33
+ send_batch_post_api_request(query_params, properties, callback)
34
+ end
35
+
36
+ # Sends a POST API request with given properties and payload
37
+ def send_batch_post_api_request(properties, payload, callback)
38
+ network_instance = NetworkManager.instance
39
+ headers = {}
40
+ headers['Authorization'] = "#{SettingsService.instance.sdk_key}"
41
+
42
+ request = RequestModel.new(
43
+ UrlUtil.get_base_url,
44
+ HttpMethodEnum::POST,
45
+ UrlEnum::BATCH_EVENTS,
46
+ properties,
47
+ payload,
48
+ headers,
49
+ SettingsService.instance.protocol,
50
+ SettingsService.instance.port
51
+ )
52
+
53
+ begin
54
+ response = network_instance.post(request)
55
+ handle_batch_response(UrlEnum::BATCH_EVENTS, payload, properties, response, response.get_data, callback)
56
+ rescue StandardError => err
57
+ LoggerService.log(LogLevelEnum::ERROR, "NETWORK_CALL_FAILED", {
58
+ method: "#{HttpMethodEnum::POST} #{UrlEnum::BATCH_EVENTS}",
59
+ err: err.is_a?(Hash) ? err.to_json : err
60
+ })
61
+ end
62
+ end
63
+
64
+ # Handles the response from a batch event API call
65
+ # @param end_point [String] The API endpoint that was called
66
+ # @param payload [Hash] The payload that was sent in the request
67
+ # @param query_params [Hash] The query parameters used in the request
68
+ # @param res [ResponseModel] The response object from the API call
69
+ # @param raw_data [String] The raw response data from the API
70
+ # @param callback [Proc] Optional callback to be executed after handling the response
71
+ def handle_batch_response(end_point, payload, query_params, res, raw_data, callback)
72
+ events_per_request = payload[:ev].length
73
+ account_id = query_params[:a]
74
+
75
+ error = res.get_error
76
+ if error
77
+ LoggerService.log(LogLevelEnum::INFO, "IMPRESSION_BATCH_FAILED")
78
+ LoggerService.log(LogLevelEnum::ERROR, "NETWORK_CALL_FAILED", {
79
+ method: "#{HttpMethodEnum::POST} #{UrlEnum::BATCH_EVENTS}",
80
+ err: error
81
+ })
82
+ callback.call(error, payload.to_json) if callback.respond_to?(:call)
83
+ return {status: "error", events: payload}
84
+ else
85
+ case res.get_status_code
86
+ when 200
87
+ LoggerService.log(LogLevelEnum::INFO, "IMPRESSION_BATCH_SUCCESS", {
88
+ accountId: account_id,
89
+ endPoint: end_point,
90
+ })
91
+ callback.call(nil, payload.to_json) if callback.respond_to?(:call)
92
+ return {status: "success", events: payload}
93
+ when 413
94
+ LoggerService.log(LogLevelEnum::DEBUG, "CONFIG_BATCH_EVENT_LIMIT_EXCEEDED", {
95
+ accountId: account_id,
96
+ endPoint: end_point,
97
+ eventsPerRequest: events_per_request
98
+ })
99
+ LoggerService.log(LogLevelEnum::ERROR, "NETWORK_CALL_FAILED", {
100
+ method: "#{HttpMethodEnum::POST} #{UrlEnum::BATCH_EVENTS}",
101
+ err: error
102
+ })
103
+ callback.call(error, payload.to_json) if callback.respond_to?(:call)
104
+ return {status: "error", events: payload}
105
+ else
106
+ LoggerService.log(LogLevelEnum::INFO, "IMPRESSION_BATCH_FAILED")
107
+ LoggerService.log(LogLevelEnum::ERROR, "NETWORK_CALL_FAILED", {
108
+ method: "#{HttpMethodEnum::POST} #{UrlEnum::BATCH_EVENTS}",
109
+ err: error
110
+ })
111
+ callback.call(error, payload.to_json) if callback.respond_to?(:call)
112
+ return {status: "error", events: payload}
113
+ end
114
+ end
115
+ end
116
+ end
117
+ end
@@ -1,4 +1,4 @@
1
- # Copyright 2025 Wingify Software Pvt. Ltd.
1
+ # Copyright 2024-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.
@@ -1,4 +1,4 @@
1
- # Copyright 2025 Wingify Software Pvt. Ltd.
1
+ # Copyright 2024-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.
@@ -1,4 +1,4 @@
1
- # Copyright 2025 Wingify Software Pvt. Ltd.
1
+ # Copyright 2024-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.
@@ -1,4 +1,4 @@
1
- # Copyright 2025 Wingify Software Pvt. Ltd.
1
+ # Copyright 2024-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.
@@ -17,6 +17,10 @@ require_relative '../models/campaign/campaign_model'
17
17
  require_relative '../models/campaign/feature_model'
18
18
  require_relative '../models/settings/settings_model'
19
19
  require_relative '../utils/data_type_util'
20
+ require_relative '../services/logger_service'
21
+ require_relative '../enums/log_level_enum'
22
+ require_relative '../constants/constants'
23
+ require_relative '../utils/data_type_util'
20
24
 
21
25
  # Clones an object deeply.
22
26
  # @param obj [Object] The object to clone.
@@ -1,4 +1,4 @@
1
- # Copyright 2025 Wingify Software Pvt. Ltd.
1
+ # Copyright 2024-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.