From 4bc21d50208dba3ec540b9cbb9db16fff40056db Mon Sep 17 00:00:00 2001 From: Bilal Al-Shahwany Date: Thu, 3 Jul 2025 15:05:34 -0700 Subject: [PATCH] Update client --- lib/splitclient-rb/clients/split_client.rb | 86 ++++++++++++------- .../engine/common/impressions_manager.rb | 3 +- spec/engine/common/impression_manager_spec.rb | 2 +- spec/splitclient/split_client_spec.rb | 22 +++++ 4 files changed, 79 insertions(+), 34 deletions(-) diff --git a/lib/splitclient-rb/clients/split_client.rb b/lib/splitclient-rb/clients/split_client.rb index ed0e1021..69f7e906 100644 --- a/lib/splitclient-rb/clients/split_client.rb +++ b/lib/splitclient-rb/clients/split_client.rb @@ -35,23 +35,23 @@ def initialize(sdk_key, repositories, status_manager, config, impressions_manage end def get_treatment( - key, split_name, attributes = {}, split_data = nil, store_impressions = true, + key, split_name, attributes = {}, options = nil, split_data = nil, store_impressions = true, multiple = false, evaluator = nil ) - result = treatment(key, split_name, attributes, split_data, store_impressions, GET_TREATMENT, multiple) + result = treatment(key, split_name, attributes, split_data, store_impressions, GET_TREATMENT, multiple, options) return result.tap { |t| t.delete(:config) } if multiple result[:treatment] end def get_treatment_with_config( - key, split_name, attributes = {}, split_data = nil, store_impressions = true, + key, split_name, attributes = {}, options = nil, split_data = nil, store_impressions = true, multiple = false, evaluator = nil ) - treatment(key, split_name, attributes, split_data, store_impressions, GET_TREATMENT_WITH_CONFIG, multiple) + treatment(key, split_name, attributes, split_data, store_impressions, GET_TREATMENT_WITH_CONFIG, multiple, options) end - def get_treatments(key, split_names, attributes = {}) - treatments = treatments(key, split_names, attributes) + def get_treatments(key, split_names, attributes = {}, options = nil) + treatments = treatments(key, split_names, attributes, options) return treatments if treatments.nil? keys = treatments.keys @@ -59,40 +59,40 @@ def get_treatments(key, split_names, attributes = {}) Hash[keys.zip(treats)] end - def get_treatments_with_config(key, split_names, attributes = {}) - treatments(key, split_names, attributes, GET_TREATMENTS_WITH_CONFIG) + def get_treatments_with_config(key, split_names, attributes = {}, options = nil) + treatments(key, split_names, attributes, options, GET_TREATMENTS_WITH_CONFIG) end - def get_treatments_by_flag_set(key, flag_set, attributes = {}) + def get_treatments_by_flag_set(key, flag_set, attributes = {}, options = nil) valid_flag_set = @split_validator.valid_flag_sets(GET_TREATMENTS_BY_FLAG_SET, [flag_set]) split_names = @splits_repository.get_feature_flags_by_sets(valid_flag_set) - treatments = treatments(key, split_names, attributes, GET_TREATMENTS_BY_FLAG_SET) + treatments = treatments(key, split_names, attributes, options, GET_TREATMENTS_BY_FLAG_SET) return treatments if treatments.nil? keys = treatments.keys treats = treatments.map { |_,t| t[:treatment] } Hash[keys.zip(treats)] end - def get_treatments_by_flag_sets(key, flag_sets, attributes = {}) + def get_treatments_by_flag_sets(key, flag_sets, attributes = {}, options = nil) valid_flag_set = @split_validator.valid_flag_sets(GET_TREATMENTS_BY_FLAG_SETS, flag_sets) split_names = @splits_repository.get_feature_flags_by_sets(valid_flag_set) - treatments = treatments(key, split_names, attributes, GET_TREATMENTS_BY_FLAG_SETS) + treatments = treatments(key, split_names, attributes, options, GET_TREATMENTS_BY_FLAG_SETS) return treatments if treatments.nil? keys = treatments.keys treats = treatments.map { |_,t| t[:treatment] } Hash[keys.zip(treats)] end - def get_treatments_with_config_by_flag_set(key, flag_set, attributes = {}) + def get_treatments_with_config_by_flag_set(key, flag_set, attributes = {}, options = nil) valid_flag_set = @split_validator.valid_flag_sets(GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SET, [flag_set]) split_names = @splits_repository.get_feature_flags_by_sets(valid_flag_set) - treatments(key, split_names, attributes, GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SET) + treatments(key, split_names, attributes, options, GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SET) end - def get_treatments_with_config_by_flag_sets(key, flag_sets, attributes = {}) + def get_treatments_with_config_by_flag_sets(key, flag_sets, attributes = {}, options = nil) valid_flag_set = @split_validator.valid_flag_sets(GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SETS, flag_sets) split_names = @splits_repository.get_feature_flags_by_sets(valid_flag_set) - treatments(key, split_names, attributes, GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SETS) + treatments(key, split_names, attributes, options, GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SETS) end def destroy @@ -135,10 +135,7 @@ def track(key, traffic_type_name, event_type, value = nil, properties = nil) if !properties.nil? properties, size = validate_properties(properties) properties_size += size - if (properties_size > EVENTS_SIZE_THRESHOLD) - @config.logger.error("The maximum size allowed for the properties is #{EVENTS_SIZE_THRESHOLD}. Current is #{properties_size}. Event not queued") - return false - end + return false unless check_properties_size(properties_size) end if ready? && !@config.localhost_mode && !@splits_repository.traffic_type_exists(traffic_type_name) @@ -163,6 +160,14 @@ def block_until_ready(time = nil) private + def check_properties_size(properties_size, msg = "Event not queued") + if (properties_size > EVENTS_SIZE_THRESHOLD) + @config.logger.error("The maximum size allowed for the properties is #{EVENTS_SIZE_THRESHOLD}. Current is #{properties_size}. #{msg}") + return false + end + return true + end + def keys_from_key(key) case key when Hash @@ -206,7 +211,7 @@ def sanitize_split_names(calling_method, split_names) end end - def validate_properties(properties) + def validate_properties(properties, method = 'Event') properties_count = 0 size = 0 @@ -225,11 +230,25 @@ def validate_properties(properties) end } - @config.logger.warn('Event has more than 300 properties. Some of them will be trimmed when processed') if properties_count > 300 + @config.logger.warn("#{method} has more than 300 properties. Some of them will be trimmed when processed") if properties_count > 300 return fixed_properties, size end + def validate_options(options) + if !options.is_a?(Hash) + @config.logger.warn("Option #{options} should be a hash type. Setting value to nil") + return nil, 0 + end + options = options.transform_keys(&:to_sym) + if !options.key?(:properties) + @config.logger.warn("Option #{options} hash does not contain properties key. Setting value to nil") + return nil, 0 + end + options[:properties], size = validate_properties(options[:properties], method = 'Treatment') + return options, size + end + def valid_client if @destroyed @config.logger.error('Client has already been destroyed - no calls possible') @@ -238,8 +257,7 @@ def valid_client @config.valid_mode end - def treatments(key, feature_flag_names, attributes = {}, calling_method = 'get_treatments') - attributes = {} if attributes.nil? + def treatments(key, feature_flag_names, attributes = {}, options = nil, calling_method = 'get_treatments') sanitized_feature_flag_names = sanitize_split_names(calling_method, feature_flag_names) if sanitized_feature_flag_names.nil? @@ -269,7 +287,9 @@ def treatments(key, feature_flag_names, attributes = {}, calling_method = 'get_t to_return = Hash.new sanitized_feature_flag_names.each {|name| to_return[name.to_sym] = control_treatment_with_config - impressions << { :impression => @impressions_manager.build_impression(matching_key, bucketing_key, name.to_sym, control_treatment_with_config.merge({ :label => Engine::Models::Label::NOT_READY }), false, { attributes: attributes, time: nil }), :disabled => false } + impressions << { :impression => @impressions_manager.build_impression(matching_key, bucketing_key, name.to_sym, + control_treatment_with_config.merge({ :label => Engine::Models::Label::NOT_READY }), false, { attributes: attributes, time: nil }, + options), :disabled => false } } @impressions_manager.track(impressions) return to_return @@ -291,7 +311,7 @@ def treatments(key, feature_flag_names, attributes = {}, calling_method = 'get_t invalid_treatments[key] = control_treatment_with_config next end - treatments_labels_change_numbers, impressions = evaluate_treatment(feature_flag, key, bucketing_key, matching_key, attributes, calling_method) + treatments_labels_change_numbers, impressions = evaluate_treatment(feature_flag, key, bucketing_key, matching_key, attributes, calling_method, false, options) treatments[key] = { treatment: treatments_labels_change_numbers[:treatment], @@ -314,7 +334,7 @@ def treatments(key, feature_flag_names, attributes = {}, calling_method = 'get_t # @param store_impressions [Boolean] impressions aren't stored if this flag is false # @return [String/Hash] Treatment as String or Hash of treatments in case of array of features def treatment(key, feature_flag_name, attributes = {}, split_data = nil, store_impressions = true, - calling_method = 'get_treatment', multiple = false) + calling_method = 'get_treatment', multiple = false, options = nil) impressions = [] bucketing_key, matching_key = keys_from_key(key) @@ -332,13 +352,13 @@ def treatment(key, feature_flag_name, attributes = {}, split_data = nil, store_i end feature_flag = @splits_repository.get_split(feature_flag_name) - treatments, impressions_decorator = evaluate_treatment(feature_flag, feature_flag_name, bucketing_key, matching_key, attributes, calling_method, multiple) + treatments, impressions_decorator = evaluate_treatment(feature_flag, feature_flag_name, bucketing_key, matching_key, attributes, calling_method, multiple, options) @impressions_manager.track(impressions_decorator) unless impressions_decorator.nil? treatments end - def evaluate_treatment(feature_flag, feature_flag_name, bucketing_key, matching_key, attributes, calling_method, multiple = false) + def evaluate_treatment(feature_flag, feature_flag_name, bucketing_key, matching_key, attributes, calling_method, multiple = false, options = nil) impressions_decorator = [] begin start = Time.now @@ -359,18 +379,20 @@ def evaluate_treatment(feature_flag, feature_flag_name, bucketing_key, matching_ impressions_disabled = false end + options, size = validate_options(options) + options[:properties] = nil unless options.nil? or check_properties_size((EVENT_AVERAGE_SIZE + size), "Properties are ignored") + record_latency(calling_method, start) - impression_decorator = { :impression => @impressions_manager.build_impression(matching_key, bucketing_key, feature_flag_name, treatment_data, impressions_disabled, { attributes: attributes, time: nil }), :disabled => impressions_disabled } + impression_decorator = { :impression => @impressions_manager.build_impression(matching_key, bucketing_key, feature_flag_name, treatment_data, impressions_disabled, { attributes: attributes, time: nil }, options), :disabled => impressions_disabled } impressions_decorator << impression_decorator unless impression_decorator.nil? rescue StandardError => e @config.log_found_exception(__method__.to_s, e) record_exception(calling_method) - impression_decorator = { :impression => @impressions_manager.build_impression(matching_key, bucketing_key, feature_flag_name, control_treatment, false, { attributes: attributes, time: nil }), :disabled => false } + impression_decorator = { :impression => @impressions_manager.build_impression(matching_key, bucketing_key, feature_flag_name, control_treatment, false, { attributes: attributes, time: nil }, options), :disabled => false } impressions_decorator << impression_decorator unless impression_decorator.nil? return parsed_treatment(control_treatment.merge({ :label => Engine::Models::Label::EXCEPTION }), multiple), impressions_decorator end - return parsed_treatment(treatment_data, multiple), impressions_decorator end diff --git a/lib/splitclient-rb/engine/common/impressions_manager.rb b/lib/splitclient-rb/engine/common/impressions_manager.rb index 46efc3b2..a2b196cc 100644 --- a/lib/splitclient-rb/engine/common/impressions_manager.rb +++ b/lib/splitclient-rb/engine/common/impressions_manager.rb @@ -19,7 +19,8 @@ def initialize(config, end def build_impression(matching_key, bucketing_key, split_name, treatment_data, impressions_disabled, params = {}, - properties = nil) + options = nil) + properties = options.nil? ? nil : options[:properties] impression_data = impression_data(matching_key, bucketing_key, split_name, treatment_data, params[:time], properties) begin if @config.impressions_mode == :none || impressions_disabled diff --git a/spec/engine/common/impression_manager_spec.rb b/spec/engine/common/impression_manager_spec.rb index ea93d3ac..c23167c5 100644 --- a/spec/engine/common/impression_manager_spec.rb +++ b/spec/engine/common/impression_manager_spec.rb @@ -72,7 +72,7 @@ 'split_name_test', treatment, false, - params, {"prop":"val"}) + params, {"properties": {"prop":"val"}}) expect(impression).to match(expected) result_count = impression_counter.pop_all diff --git a/spec/splitclient/split_client_spec.rb b/spec/splitclient/split_client_spec.rb index fd19dc0c..9ff88c25 100644 --- a/spec/splitclient/split_client_spec.rb +++ b/spec/splitclient/split_client_spec.rb @@ -35,6 +35,24 @@ expect(split_client.get_treatments_by_flag_sets('key', ['set_2'])).to eq({:testing222 => 'off'}) expect(split_client.get_treatments_with_config_by_flag_set('key', 'set_1')).to eq({:testing222 => {:treatment => 'off', :config => nil}}) expect(split_client.get_treatments_with_config_by_flag_sets('key', ['set_2'])).to eq({:testing222 => {:treatment => 'off', :config => nil}}) + imps = impressions_repository.batch + + expect(split_client.get_treatment('key', 'testing222', {}, {:properties => {:prop => "value"}})).to eq('off') + check_properties(impressions_repository.batch) + expect(split_client.get_treatments('key_prop', ['testing222'], {}, {:properties => {:prop => "value"}})).to eq({:testing222 => 'off'}) + check_properties(impressions_repository.batch) + expect(split_client.get_treatment_with_config('key', 'testing222', {}, {:properties => {:prop => "value"}})).to eq({:treatment => 'off', :config => nil}) + check_properties(impressions_repository.batch) + expect(split_client.get_treatments_with_config('key', ['testing222'], {}, {:properties => {:prop => "value"}})).to eq({:testing222 => {:treatment => 'off', :config => nil}}) + check_properties(impressions_repository.batch) + expect(split_client.get_treatments_by_flag_set('key', 'set_1', {}, {:properties => {:prop => "value"}})).to eq({:testing222 => 'off'}) + check_properties(impressions_repository.batch) + expect(split_client.get_treatments_by_flag_sets('key', ['set_2'], {}, {:properties => {:prop => "value"}})).to eq({:testing222 => 'off'}) + check_properties(impressions_repository.batch) + expect(split_client.get_treatments_with_config_by_flag_set('key', 'set_1', {}, {:properties => {:prop => "value"}})).to eq({:testing222 => {:treatment => 'off', :config => nil}}) + check_properties(impressions_repository.batch) + expect(split_client.get_treatments_with_config_by_flag_sets('key', ['set_2'], {}, {:properties => {:prop => "value"}})).to eq({:testing222 => {:treatment => 'off', :config => nil}}) + check_properties(impressions_repository.batch) end it 'check track' do @@ -221,3 +239,7 @@ def mock_segment_changes(segment_name, segment_json, since) stub_request(:get, "https://sdk.split.io/api/segmentChanges/#{segment_name}?since=#{since}") .to_return(status: 200, body: segment_json) end + +def check_properties(imps) + expect(imps[0][:i][:properties]).to eq({:prop => "value"}) +end