wingify-fme-ruby-sdk 1.60.1 → 1.62.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 689add6103ca863234921afae6aa6da415d466439c2888bf3b9026bc96d56aa7
4
- data.tar.gz: b3178336c9f0e9c244507acb083c10f8c3bab352ee3468feefe790242ee01aca
3
+ metadata.gz: 960bee01b76cc7b563f9066f1b916debff3480d1660892ff2c4efcda8cf1581f
4
+ data.tar.gz: bca8c1d3fb285a2082526273df933fb717bb23b579f1aed48f526b382be2af19
5
5
  SHA512:
6
- metadata.gz: 5740126a7a6c8abefc07dc8b3e010f2b7dfc8f17b1e8485538ac820ccdd64b959a229359a320a4e19fe5e0bd4b7831c917985126f7cd551662f23a92183e38c1
7
- data.tar.gz: f59985ee2de683173c4728c8af6e43f2fb680a53a4877b10b80ed7d9c376bc90620396eeaa99429d19ac289d43e29db7a0fb6321debb04d3ec26429647601340
6
+ metadata.gz: 3e1f775e8c6d03f6c251860ba1e4fd4a4e213027440cd37584f5a00d1439682a6d84cc77daddab0efe7bafd909f7c6ca2ded371ed1febed48b8d85f9d6ffc377
7
+ data.tar.gz: 74e3bf97831c43fe6ecab34989adb413533b5ac4aff4538b8db57d88b74be83c39578d1f1f9162786db463ac388f6a022a0fbcc81c93cbbebf78e56b3ff6fb28
@@ -17,7 +17,7 @@
17
17
  # Define the Constants module
18
18
  module Constants
19
19
  SDK_NAME = 'vwo-fme-ruby-sdk'.freeze
20
- SDK_VERSION = '1.60.1'.freeze
20
+ SDK_VERSION = '1.62.0'.freeze
21
21
 
22
22
  # --- Brand-specific constants ---
23
23
 
@@ -103,4 +103,8 @@ module Constants
103
103
  NETWORK_CALL_FAILURE_AFTER_MAX_RETRIES = 'NETWORK_CALL_FAILURE_AFTER_MAX_RETRIES'.freeze
104
104
  NETWORK_CALL_SUCCESS_WITH_RETRIES = 'NETWORK_CALL_SUCCESS_WITH_RETRIES'.freeze
105
105
  IMPACT_ANALYSIS = 'IMPACT_ANALYSIS'.freeze
106
+
107
+ # Internal events sampling defaults (used when settings omit or have invalid values)
108
+ INTERNAL_EVENTS_DEFAULT_SAMPLING_PERCENT_SERVER = 10.0
109
+ INTERNAL_EVENTS_DEFAULT_ALWAYS_APPLY_SAMPLING = false
106
110
  end
@@ -0,0 +1,31 @@
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
+ # Debug error template keys (msg_t) that are subject to sampling.
16
+ # Keys not listed here always send and bypass sampling.
17
+ class SampledDebugErrorTemplateKeys
18
+ SAMPLED_KEYS = %w[
19
+ EVENT_NOT_FOUND
20
+ FEATURE_NOT_FOUND
21
+ FEATURE_NOT_FOUND_WITH_ID
22
+ ].freeze
23
+
24
+ # Checks whether a debug error template key should be sampled before sending.
25
+ #
26
+ # @param message_template_key [String] The msg_t value from the debug event
27
+ # @return [Boolean] true if the key is in the sampled set; false otherwise
28
+ def self.contains?(message_template_key)
29
+ !message_template_key.nil? && SAMPLED_KEYS.include?(message_template_key)
30
+ end
31
+ end
@@ -0,0 +1,62 @@
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
+ # Holds the sampling percentage for a specific platform (e.g. server).
16
+ # Maps to the per-platform object inside sampling.usage or sampling.debug.
17
+ class PlatformSamplingRates
18
+ def initialize(data)
19
+ data ||= {}
20
+ @server = data['server']
21
+ end
22
+
23
+ # @return [Numeric, nil] Sampling percentage for the server runtime
24
+ def get_server
25
+ @server
26
+ end
27
+ end
28
+
29
+ # Groups sampling rates by event category: usage and debug.
30
+ # Maps to the top-level sampling object in the DaCDN settings.
31
+ class EventCategorySamplingRates
32
+ def initialize(data)
33
+ data ||= {}
34
+ # Only build nested models when the category key is present
35
+ @usage = data['usage'] ? PlatformSamplingRates.new(data['usage']) : nil
36
+ @debug = data['debug'] ? PlatformSamplingRates.new(data['debug']) : nil
37
+ end
38
+
39
+ # @return [PlatformSamplingRates, nil]
40
+ def get_usage
41
+ @usage
42
+ end
43
+
44
+ # @return [PlatformSamplingRates, nil]
45
+ def get_debug
46
+ @debug
47
+ end
48
+ end
49
+
50
+ # Controls whether sampling is enforced before sending an event.
51
+ # When server is false (the default), usage-stats and sampled debug events bypass sampling and are always sent.
52
+ class AlwaysApplySampling
53
+ def initialize(data)
54
+ data ||= {}
55
+ @server = data['server']
56
+ end
57
+
58
+ # @return [Boolean, nil] Whether sampling must be applied on the server runtime
59
+ def get_server
60
+ @server
61
+ end
62
+ end
@@ -15,6 +15,7 @@
15
15
  require_relative '../campaign/campaign_model'
16
16
  require_relative '../campaign/feature_model'
17
17
  require_relative '../../constants/constants'
18
+ require_relative 'internal_event_sampling_settings_model'
18
19
 
19
20
  class SettingsModel
20
21
  attr_reader :sdk_key, :account_id, :usage_stats_account_id, :version, :collection_prefix,
@@ -33,6 +34,8 @@ class SettingsModel
33
34
  @groups = settings["groups"] || {}
34
35
  @is_web_connectivity_enabled = settings.fetch("isWebConnectivityEnabled", true)
35
36
  @is_tracking_usage_enabled = settings.fetch("isMAU", false)
37
+ @always_apply_sampling = settings["alwaysApplySampling"].nil? ? nil : AlwaysApplySampling.new(settings["alwaysApplySampling"] || {})
38
+ @sampling = settings["sampling"].nil? ? nil : EventCategorySamplingRates.new(settings["sampling"] || {})
36
39
 
37
40
  process_features(settings)
38
41
  process_campaigns(settings)
@@ -103,4 +106,14 @@ class SettingsModel
103
106
  def get_is_tracking_usage_enabled
104
107
  @is_tracking_usage_enabled
105
108
  end
109
+
110
+ # @return [AlwaysApplySampling, nil] alwaysApplySampling settings, if present
111
+ def get_always_apply_sampling
112
+ @always_apply_sampling
113
+ end
114
+
115
+ # @return [EventCategorySamplingRates, nil] sampling rates by event category, if present
116
+ def get_sampling
117
+ @sampling
118
+ end
106
119
  end
@@ -0,0 +1,54 @@
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/internal_events_sampling_util'
16
+
17
+ # Applies sampling rules for internal SDK events:
18
+ # vwo_sdkUsageStats and sampled vwo_sdkDebug.
19
+ class InternalEventsSamplingService
20
+ # Creates a sampling service.
21
+ #
22
+ # @param random_value_provider [Proc, nil] Optional supplier returning a value in [0, 1)
23
+ # for sampling checks; overridable in tests.
24
+ def initialize(random_value_provider = nil)
25
+ # Use Kernel#rand by default; inject a custom provider when testing
26
+ @random_value_provider = random_value_provider || -> { rand }
27
+ end
28
+
29
+ # Determines whether the usage-stats event should be sent.
30
+ # On server, always sends unless alwaysApplySampling.server is enabled.
31
+ #
32
+ # @param settings [SettingsModel, nil] Parsed settings from the server
33
+ # @return [Boolean] true when the usage-stats event should be sent
34
+ def should_send_usage_stats_event(settings)
35
+ # Skip the percent check when alwaysApplySampling.server is off
36
+ return true unless InternalEventsSamplingUtil.should_apply_sampling(settings)
37
+
38
+ usage_stats_sampling_percent = InternalEventsSamplingUtil.get_usage_stats_sampling_percent(settings)
39
+ InternalEventsSamplingUtil.passes_sampling_percent(usage_stats_sampling_percent, @random_value_provider.call)
40
+ end
41
+
42
+ # Determines whether a sampled debug event should be sent.
43
+ # Only called for msg_t keys in the sampled set; always-send keys bypass this.
44
+ #
45
+ # @param settings [SettingsModel, nil] Parsed settings from the server
46
+ # @return [Boolean] true when the sampled debug event should be sent
47
+ def should_send_sampled_debug_event(settings)
48
+ # Skip the percent check when alwaysApplySampling.server is off
49
+ return true unless InternalEventsSamplingUtil.should_apply_sampling(settings)
50
+
51
+ debug_sampling_percent = InternalEventsSamplingUtil.get_debug_event_sampling_percent(settings)
52
+ InternalEventsSamplingUtil.passes_sampling_percent(debug_sampling_percent, @random_value_provider.call)
53
+ end
54
+ end
@@ -17,10 +17,29 @@ require_relative '../enums/log_level_enum'
17
17
  require_relative '../utils/network_util'
18
18
  require_relative '../services/batch_event_queue'
19
19
  require_relative '../enums/event_enum'
20
+ require_relative '../utils/internal_events_sampling_util'
21
+ require_relative '../services/internal_events_sampling_service'
20
22
 
21
23
  class DebuggerServiceUtil
22
24
  class << self
23
25
  def send_debugger_event(event_props)
26
+ # Sampled keys: apply sampling only when alwaysApplySampling.server is true; others always send
27
+ msg_t = event_props[:msg_t] || event_props['msg_t']
28
+ is_sampled_debug_event = msg_t.is_a?(String) && InternalEventsSamplingUtil.is_sampled_debug_error_template_key(msg_t)
29
+
30
+ # Resolve settings from the live SDK instance without a hard require (avoids circular load)
31
+ settings = nil
32
+ if defined?(Wingify) && Wingify.respond_to?(:instance)
33
+ instance = Wingify.instance
34
+ settings = instance.respond_to?(:settings) ? instance.settings : nil
35
+ end
36
+
37
+ # If msg_t is in the sampled set, drop the event when the sampling check fails
38
+ if is_sampled_debug_event
39
+ sampling_service = InternalEventsSamplingService.new
40
+ return unless sampling_service.should_send_sampled_debug_event(settings)
41
+ end
42
+
24
43
  # get base properties for the event
25
44
  properties = NetworkUtil.get_events_base_properties(EventEnum::DEBUGGER_EVENT)
26
45
 
@@ -0,0 +1,108 @@
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 '../constants/constants'
16
+ require_relative '../constants/sampled_debug_error_template_keys'
17
+
18
+ # Helpers for reading and applying internal-events sampling settings.
19
+ class InternalEventsSamplingUtil
20
+ class << self
21
+ # Returns the default server sampling percentage when settings omit the value.
22
+ #
23
+ # @return [Float] Default sampling percentage (10.0)
24
+ def get_default_sampling_percent
25
+ Constants::INTERNAL_EVENTS_DEFAULT_SAMPLING_PERCENT_SERVER
26
+ end
27
+
28
+ # Normalizes a sampling value to a valid percentage in the range [0, 100].
29
+ # Falls back to the server default when the value is missing or invalid.
30
+ #
31
+ # @param sampling_value [Numeric, nil] Raw value from settings
32
+ # @return [Float] Valid sampling percentage between 0 and 100
33
+ def normalize_sampling_percent(sampling_value)
34
+ # Accept only numeric values that fall within 0–100 inclusive
35
+ if !sampling_value.nil? && sampling_value.is_a?(Numeric) && sampling_value >= 0 && sampling_value <= 100
36
+ return sampling_value.to_f
37
+ end
38
+
39
+ get_default_sampling_percent
40
+ end
41
+
42
+ # Reads the server sampling percentage from a runtime sampling config object.
43
+ #
44
+ # @param runtime_sampling_config [PlatformSamplingRates, nil] Nested config (e.g. sampling.usage)
45
+ # @return [Float] Sampling percentage for the server runtime
46
+ def get_runtime_sampling_percent(runtime_sampling_config)
47
+ return get_default_sampling_percent if runtime_sampling_config.nil?
48
+
49
+ normalize_sampling_percent(runtime_sampling_config.get_server)
50
+ end
51
+
52
+ # Reads the usage-stats sampling percentage for the server runtime from settings.
53
+ #
54
+ # @param settings [SettingsModel, nil] Parsed settings from the server
55
+ # @return [Float] Configured usage-stats sampling percentage (0-100)
56
+ def get_usage_stats_sampling_percent(settings)
57
+ sampling = settings ? settings.get_sampling : nil
58
+ usage_sampling = sampling ? sampling.get_usage : nil
59
+ get_runtime_sampling_percent(usage_sampling)
60
+ end
61
+
62
+ # Reads the debug-event sampling percentage for the server runtime from settings.
63
+ #
64
+ # @param settings [SettingsModel, nil] Parsed settings from the server
65
+ # @return [Float] Configured debug sampling percentage (0-100)
66
+ def get_debug_event_sampling_percent(settings)
67
+ sampling = settings ? settings.get_sampling : nil
68
+ debug_sampling = sampling ? sampling.get_debug : nil
69
+ get_runtime_sampling_percent(debug_sampling)
70
+ end
71
+
72
+ # Determines whether sampling should be evaluated before send.
73
+ # Controlled by alwaysApplySampling.server; defaults to false.
74
+ #
75
+ # @param settings [SettingsModel, nil] Parsed settings from the server
76
+ # @return [Boolean] true when a sampling check is required before sending
77
+ def should_apply_sampling(settings)
78
+ return Constants::INTERNAL_EVENTS_DEFAULT_ALWAYS_APPLY_SAMPLING if settings.nil?
79
+
80
+ always_apply_sampling = settings.get_always_apply_sampling
81
+ # Missing config or missing server flag → use default (do not sample)
82
+ if always_apply_sampling.nil? || always_apply_sampling.get_server.nil?
83
+ return Constants::INTERNAL_EVENTS_DEFAULT_ALWAYS_APPLY_SAMPLING
84
+ end
85
+
86
+ always_apply_sampling.get_server
87
+ end
88
+
89
+ # Evaluates whether an event qualifies under the configured sampling percentage.
90
+ #
91
+ # @param sampling_percent [Float] Configured sampling percentage (0-100)
92
+ # @param random_value [Float] Random value in the range [0, 1)
93
+ # @return [Boolean] true when the random draw falls within the sampling threshold
94
+ def passes_sampling_percent(sampling_percent, random_value)
95
+ # Map random [0, 1) to an integer percent bucket [0, 100]
96
+ normalized_random_percent = (random_value * 101).floor
97
+ normalized_random_percent <= sampling_percent
98
+ end
99
+
100
+ # Checks whether a debug error template key is in the sampled set.
101
+ #
102
+ # @param message_template_key [String] The msg_t value from the debug event
103
+ # @return [Boolean] true when the key requires sampling before send
104
+ def is_sampled_debug_error_template_key(message_template_key)
105
+ SampledDebugErrorTemplateKeys.contains?(message_template_key)
106
+ end
107
+ end
108
+ end
data/lib/wingify.rb CHANGED
@@ -20,6 +20,7 @@ require_relative 'wingify/utils/brand_context'
20
20
  require_relative 'wingify/utils/brand_util'
21
21
  require_relative 'wingify/utils/event_util'
22
22
  require_relative 'wingify/services/settings_service'
23
+ require_relative 'wingify/services/internal_events_sampling_service'
23
24
 
24
25
  module Wingify
25
26
  @@wingify_builder = nil
@@ -48,7 +49,9 @@ module Wingify
48
49
  else
49
50
  LoggerService.log(LogLevelEnum::ERROR, "INVALID_SETTINGS_SCHEMA", { accountId: options[:account_id], sdkKey: options[:sdk_key], settings: options[:settings], an: ApiEnum::INIT}, false)
50
51
  end
51
- return @@wingify_builder.build(options[:settings])
52
+ # Keep Wingify.instance as the built client so debug sampling can read settings
53
+ @@instance = @@wingify_builder.build(options[:settings])
54
+ return @@instance
52
55
  end
53
56
 
54
57
  settings = @@wingify_builder.get_settings
@@ -99,9 +102,13 @@ module Wingify
99
102
  send_sdk_init_event(SettingsService.instance.settings_fetch_time, time_taken_for_init.to_s)
100
103
  end
101
104
 
102
- usage_stats_account_id = new_instance&.original_settings&.dig("usageStatsAccountId")
105
+ # Send usage stats only when account id is present and sampling allows it
106
+ usage_stats_account_id = new_instance&.settings&.get_usage_stats_account_id
103
107
  if usage_stats_account_id
104
- send_sdk_usage_stats_event(usage_stats_account_id)
108
+ sampling_service = InternalEventsSamplingService.new
109
+ if sampling_service.should_send_usage_stats_event(new_instance.settings)
110
+ send_sdk_usage_stats_event(usage_stats_account_id)
111
+ end
105
112
  end
106
113
 
107
114
  new_instance
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: wingify-fme-ruby-sdk
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.60.1
4
+ version: 1.62.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Wingify
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-07-03 00:00:00.000000000 Z
11
+ date: 2026-09-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: uuidtools
@@ -221,6 +221,7 @@ files:
221
221
  - lib/wingify/api/set_attribute.rb
222
222
  - lib/wingify/api/track_event.rb
223
223
  - lib/wingify/constants/constants.rb
224
+ - lib/wingify/constants/sampled_debug_error_template_keys.rb
224
225
  - lib/wingify/decorators/storage_decorator.rb
225
226
  - lib/wingify/enums/api_enum.rb
226
227
  - lib/wingify/enums/campaign_type_enum.rb
@@ -244,6 +245,7 @@ files:
244
245
  - lib/wingify/models/campaign/variation_model.rb
245
246
  - lib/wingify/models/gateway_service_model.rb
246
247
  - lib/wingify/models/schemas/settings_schema_validation.rb
248
+ - lib/wingify/models/settings/internal_event_sampling_settings_model.rb
247
249
  - lib/wingify/models/settings/settings_model.rb
248
250
  - lib/wingify/models/storage/storage_data_model.rb
249
251
  - lib/wingify/models/user/context_model.rb
@@ -275,6 +277,7 @@ files:
275
277
  - lib/wingify/services/batch_event_queue.rb
276
278
  - lib/wingify/services/campaign_decision_service.rb
277
279
  - lib/wingify/services/hooks_service.rb
280
+ - lib/wingify/services/internal_events_sampling_service.rb
278
281
  - lib/wingify/services/logger_service.rb
279
282
  - lib/wingify/services/settings_service.rb
280
283
  - lib/wingify/services/storage_service.rb
@@ -289,6 +292,7 @@ files:
289
292
  - lib/wingify/utils/function_util.rb
290
293
  - lib/wingify/utils/gateway_service_util.rb
291
294
  - lib/wingify/utils/impression_util.rb
295
+ - lib/wingify/utils/internal_events_sampling_util.rb
292
296
  - lib/wingify/utils/log_message_util.rb
293
297
  - lib/wingify/utils/meg_util.rb
294
298
  - lib/wingify/utils/network_util.rb