From d86fb8230f3b9e6c26ccb501581ac0e32eea3fe2 Mon Sep 17 00:00:00 2001 From: Abbas-khaliq <abbas.khaliq@gmail.com> Date: Thu, 23 Dec 2021 14:42:34 +0530 Subject: [PATCH] feat(opt-out): opt-out of tracking by VWO without removing code --- CHANGELOG.md | 5 ++- lib/vwo.rb | 99 ++++++++++++++++++++++++++++++++++++++++---- lib/vwo/constants.rb | 3 +- lib/vwo/enums.rb | 2 + tests/test_vwo.rb | 46 +++++++++++++++++++- 5 files changed, 144 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8065cb9..c4be674 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,13 +35,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - For events architecture accounts, tracking same goal across multiple campaigns will not send multiple tracking calls. Instead, one single `POST` call would be made to track the same goal across multiple different campaigns running on the same environment. - Multiple custom dimension can be pushed via `push` API. For events architecture enabled account, only one single asynchronous call would be made to track multiple custom dimensions. -```ruby + + ```ruby custom_dimension_map = { browser: 'chrome', price: '20' } vwo_client_instance.push(custom_dimension_map, user_id) -``` + ``` ## [1.24.1] - 2021-12-09 diff --git a/lib/vwo.rb b/lib/vwo.rb index 209f2f9..cc5c954 100644 --- a/lib/vwo.rb +++ b/lib/vwo.rb @@ -65,6 +65,7 @@ def initialize( options = {} ) options = convert_to_symbol_hash(options) + @is_opted_out = false @account_id = account_id @sdk_key = sdk_key @user_storage = user_storage @@ -208,6 +209,10 @@ def get_settings(settings_file = nil) # VWO get_settings method to get settings for a particular account_id def get_and_update_settings_file + if is_opted_out(ApiMethods::GET_AND_UPDATE_SETTINGS_FILE) + return false + end + unless @is_instance_valid @logger.log( LogLevelEnum::ERROR, @@ -217,7 +222,7 @@ def get_and_update_settings_file api_name: ApiMethods.GET_AND_UPDATE_SETTINGS_FILE ) ) - return + return false end latest_settings = @settings_file_manager.get_settings_file(true) @@ -267,6 +272,10 @@ def get_and_update_settings_file # otherwise null in case of user not becoming part def activate(campaign_key, user_id, options = {}) + if is_opted_out(ApiMethods::ACTIVATE) + return nil + end + unless @is_instance_valid @logger.log( LogLevelEnum::ERROR, @@ -444,6 +453,10 @@ def activate(campaign_key, user_id, options = {}) # Otherwise null in case of user not becoming part # def get_variation_name(campaign_key, user_id, options = {}) + if is_opted_out(ApiMethods::GET_VARIATION_NAME) + return nil + end + unless @is_instance_valid @logger.log( LogLevelEnum::ERROR, @@ -555,6 +568,10 @@ def get_variation_name(campaign_key, user_id, options = {}) # def track(campaign_key, user_id, goal_identifier, options = {}) + if is_opted_out(ApiMethods::TRACK) + return false + end + unless @is_instance_valid @logger.log( LogLevelEnum::ERROR, @@ -786,6 +803,10 @@ def track(campaign_key, user_id, goal_identifier, options = {}) # @return[Boolean] true if user becomes part of feature test/rollout, otherwise false. def feature_enabled?(campaign_key, user_id, options = {}) + if is_opted_out(ApiMethods::IS_FEATURE_ENABLED) + return false + end + unless @is_instance_valid @logger.log( LogLevelEnum::ERROR, @@ -983,13 +1004,17 @@ def feature_enabled?(campaign_key, user_id, options = {}) # def get_feature_variable_value(campaign_key, variable_key, user_id, options = {}) + if is_opted_out(ApiMethods::GET_FEATURE_VARIABLE_VALUE) + return nil + end + unless @is_instance_valid @logger.log( LogLevelEnum::ERROR, format( LogMessageEnum::ErrorMessages::API_CONFIG_CORRUPTED, file: FILE, - api_name: ApiMethods.GET_FEATURE_VARIABLE_VALUE + api_name: ApiMethods::GET_FEATURE_VARIABLE_VALUE ) ) return @@ -1137,16 +1162,20 @@ def get_feature_variable_value(campaign_key, variable_key, user_id, options = {} # @return true if call is made successfully, else false def push(tag_key, tag_value, user_id = nil) + if is_opted_out(ApiMethods::PUSH) + return false + end + unless @is_instance_valid @logger.log( LogLevelEnum::ERROR, format( LogMessageEnum::ErrorMessages::API_CONFIG_CORRUPTED, file: FILE, - api_name: ApiMethods.PUSH + api_name: ApiMethods::PUSH ) ) - return + return false end # Argument reshuffling. @@ -1245,6 +1274,10 @@ def is_eligible_to_send_impression() end def flush_events + if is_opted_out(ApiMethods::FLUSH_EVENTS) + return false + end + unless @is_instance_valid @logger.log( LogLevelEnum::ERROR, @@ -1254,10 +1287,13 @@ def flush_events api_name: ApiMethods::FLUSH_EVENTS ) ) - return + return false + end + result = false + if defined?(@batch_events) && !@batch_events_queue.nil? + result = @batch_events_queue.flush(manual: true) + @batch_events_queue.kill_thread end - result = @batch_events_queue.flush(manual: true) - @batch_events_queue.kill_thread result rescue StandardError => e @logger.log( @@ -1294,6 +1330,55 @@ def get_goal_type_to_track(options) goal_type_to_track end + # Manually opting out of VWO SDK, No tracking will happen + # + # return[bool] + # + def set_opt_out + @logger.log( + LogLevelEnum::INFO, + format( + LogMessageEnum::InfoMessages::OPT_OUT_API_CALLED, + file: FILE + ) + ) + if defined?(@batch_events) && !@batch_events_queue.nil? + @batch_events_queue.flush(manual: true) + @batch_events_queue.kill_thread + end + + @is_opted_out = true + @settings_file = nil + @user_storage = nil + @event_dispatcher = nil + @variation_decider = nil + @config = nil + @usage_stats = nil + @batch_event_dispatcher = nil + @batch_events_queue = nil + @batch_events = nil + + return @is_opted_out + end + + + # Check if VWO SDK is manually opted out + # @param[String] :api_name api_name is used in logging + # @return[bool] + def is_opted_out(api_name) + if @is_opted_out + @logger.log( + LogLevelEnum::INFO, + format( + LogMessageEnum::InfoMessages::API_NOT_ENABLED, + file: FILE, + api: api_name + ) + ) + end + return @is_opted_out + end + def is_event_arch_enabled return @settings_file.key?('isEventArchEnabled') && @settings_file['isEventArchEnabled'] end diff --git a/lib/vwo/constants.rb b/lib/vwo/constants.rb index b98a827..845c600 100644 --- a/lib/vwo/constants.rb +++ b/lib/vwo/constants.rb @@ -27,7 +27,7 @@ module CONSTANTS HTTP_PROTOCOL = 'http://' HTTPS_PROTOCOL = 'https://' URL_NAMESPACE = '6ba7b811-9dad-11d1-80b4-00c04fd430c8' - SDK_VERSION = '1.25.0' + SDK_VERSION = '1.28.0' SDK_NAME = 'ruby' VWO_DELIMITER = '_vwo_' MAX_EVENTS_PER_REQUEST = 5000 @@ -102,6 +102,7 @@ module ApiMethods PUSH = 'push' GET_AND_UPDATE_SETTINGS_FILE = 'get_and_update_settings_file' FLUSH_EVENTS = 'flush_events' + OPT_OUT = 'opt_out' end module PushApi diff --git a/lib/vwo/enums.rb b/lib/vwo/enums.rb index 92721d2..94d1802 100644 --- a/lib/vwo/enums.rb +++ b/lib/vwo/enums.rb @@ -164,6 +164,8 @@ module InfoMessages GOT_ELIGIBLE_CAMPAIGNS = "(%<file>s): Got %<no_of_eligible_campaigns>s eligible winners out of %<no_of_group_campaigns>s from the Group:%<group_name>s and for User ID:%<user_id>s" CALLED_CAMPAIGN_NOT_WINNER = "(%<file>s): Campaign:%<campaign_key>s does not qualify from the mutually exclusive group:%<group_name>s for User ID:%<user_id>s" OTHER_CAMPAIGN_SATISFIES_WHITELISTING_OR_STORAGE = "(%<file>s): Campaign:%<campaign_key>s of Group:%<group_name>s satisfies %<type>s for User ID:%<user_id>s" + OPT_OUT_API_CALLED = "(%<file>s): You have opted out for not tracking i.e. all API calls will stop functioning and will simply early return" + API_NOT_ENABLED = "(%<file>s): %<api>s API is disabled as you opted out for tracking. Reinitialize the SDK to enable the normal functioning of all APIs." end # Warning Messages diff --git a/tests/test_vwo.rb b/tests/test_vwo.rb index fff89bf..f21e225 100644 --- a/tests/test_vwo.rb +++ b/tests/test_vwo.rb @@ -1148,7 +1148,7 @@ def flush_callback(events) def test_flush_events_with_corrupted_vwo_instance set_up('EMPTY_SETTINGS_FILE') - assert_equal(@vwo.flush_events, nil) + assert_equal(@vwo.flush_events, false) end def test_flush_events_raises_exception @@ -1603,4 +1603,48 @@ def test_get_variation_as_user_hash_passes_whitelisting } assert_equal(vwo_instance.get_variation_name('AB_T_100_W_25_25_25_25', 'Rohit'), 'Variation-1') end + + def test_set_opt_out_api + set_up('T_50_W_50_50_WS') + assert_equal(@vwo.set_opt_out, true) + end + + def test_apis_when_set_opt_out_called + set_up('T_50_W_50_50_WS') + + assert_equal(@vwo.set_opt_out, true) + assert_equal(@vwo.activate('T_50_W_50_50_WS', 'Ashley', {}), nil) + assert_equal(@vwo.get_variation_name('T_50_W_50_50_WS', 'Ashley', {}), nil) + goal_identifier = 'ddd' + assert_equal(@vwo.track('T_50_W_50_50_WS', 'Ashley', goal_identifier, {}), false) + assert_equal(@vwo.feature_enabled?('T_50_W_50_50_WS', 'Ashley', {}), false) + assert_equal(@vwo.get_feature_variable_value('FT_T_75_W_10_20_30_40_WS', 'STRING_VARIABLE', 'Ashley', {}), nil) + assert_equal(@vwo.get_and_update_settings_file, false) + assert_equal(@vwo.push('tagKey', 'tagValue', 'Ashley'), false) + assert_equal(@vwo.flush_events, false) + end + + def test_apis_when_set_opt_out_called_with_event_batch + def flush_callback(events) + end + + options = { + batch_events: { + events_per_request: 3, + request_time_interval: 5 + } + } + vwo_instance = initialize_vwo_with_batch_events_option('AB_T_50_W_50_50', options) + + assert_equal(vwo_instance.set_opt_out, true) + assert_equal(vwo_instance.activate('T_50_W_50_50_WS', 'Ashley', {}), nil) + assert_equal(vwo_instance.get_variation_name('T_50_W_50_50_WS', 'Ashley', {}), nil) + goal_identifier = 'ddd' + assert_equal(vwo_instance.track('T_50_W_50_50_WS', 'Ashley', goal_identifier, {}), false) + assert_equal(vwo_instance.feature_enabled?('T_50_W_50_50_WS', 'Ashley', {}), false) + assert_equal(vwo_instance.get_feature_variable_value('FT_T_75_W_10_20_30_40_WS', 'STRING_VARIABLE', 'Ashley', {}), nil) + assert_equal(vwo_instance.get_and_update_settings_file, false) + assert_equal(vwo_instance.push('tagKey', 'tagValue', 'Ashley'), false) + assert_equal(vwo_instance.flush_events, false) + end end