diff --git a/CHANGES.txt b/CHANGES.txt index 33219edd..be40f790 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,5 +1,8 @@ CHANGES +8.7.0 (Aug 1, 2025) +- Added support for feature flag prerequisites. This allows customers to define dependency conditions between flags, which are evaluated before any allowlists or targeting rules. + 8.6.0 (Jun 17, 2025) - Added support for rule-based segments. These segments determine membership at runtime by evaluating their configured rules against the user attributes provided to the SDK. - Added support for feature flag prerequisites. This allows customers to define dependency conditions between flags, which are evaluated before any allowlists or targeting rules. diff --git a/lib/splitclient-rb.rb b/lib/splitclient-rb.rb index b4ffcdeb..30e0a2b3 100644 --- a/lib/splitclient-rb.rb +++ b/lib/splitclient-rb.rb @@ -109,6 +109,7 @@ require 'splitclient-rb/engine/models/segment_type' require 'splitclient-rb/engine/models/treatment' require 'splitclient-rb/engine/models/split_http_response' +require 'splitclient-rb/engine/models/evaluation_options' require 'splitclient-rb/engine/auth_api_client' require 'splitclient-rb/engine/back_off' require 'splitclient-rb/engine/push_manager' diff --git a/lib/splitclient-rb/cache/senders/impressions_formatter.rb b/lib/splitclient-rb/cache/senders/impressions_formatter.rb index b2f1de95..a84522f5 100644 --- a/lib/splitclient-rb/cache/senders/impressions_formatter.rb +++ b/lib/splitclient-rb/cache/senders/impressions_formatter.rb @@ -37,15 +37,28 @@ def feature_impressions(filtered_impressions, feature) def current_impressions(feature_impressions) feature_impressions.map do |impression| - { - k: impression[:i][:k], - t: impression[:i][:t], - m: impression[:i][:m], - b: impression[:i][:b], - r: impression[:i][:r], - c: impression[:i][:c], - pt: impression[:i][:pt] - } + if impression[:i][:properties].nil? + impression = { + k: impression[:i][:k], + t: impression[:i][:t], + m: impression[:i][:m], + b: impression[:i][:b], + r: impression[:i][:r], + c: impression[:i][:c], + pt: impression[:i][:pt] + } + else + impression = { + k: impression[:i][:k], + t: impression[:i][:t], + m: impression[:i][:m], + b: impression[:i][:b], + r: impression[:i][:r], + c: impression[:i][:c], + pt: impression[:i][:pt], + properties: impression[:i][:properties].to_json.to_s + } + end end end @@ -73,7 +86,8 @@ def impression_hash(impression) "#{impression[:i][:b]}:" \ "#{impression[:i][:c]}:" \ "#{impression[:i][:t]}:" \ - "#{impression[:i][:pt]}" + "#{impression[:i][:pt]}" \ + "#{impression[:i][:properties]}" \ end end end diff --git a/lib/splitclient-rb/clients/split_client.rb b/lib/splitclient-rb/clients/split_client.rb index ed0e1021..31a40742 100644 --- a/lib/splitclient-rb/clients/split_client.rb +++ b/lib/splitclient-rb/clients/split_client.rb @@ -35,23 +35,26 @@ 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 = {}, evaluation_options = nil, split_data = nil, store_impressions = nil, multiple = false, evaluator = nil ) - result = treatment(key, split_name, attributes, split_data, store_impressions, GET_TREATMENT, multiple) + log_deprecated_warning(GET_TREATMENT, evaluator, 'evaluator') + + result = treatment(key, split_name, attributes, split_data, store_impressions, GET_TREATMENT, multiple, evaluation_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 = {}, evaluation_options = nil, split_data = nil, store_impressions = nil, multiple = false, evaluator = nil ) - treatment(key, split_name, attributes, split_data, store_impressions, GET_TREATMENT_WITH_CONFIG, multiple) + log_deprecated_warning(GET_TREATMENT, evaluator, 'evaluator') + treatment(key, split_name, attributes, split_data, store_impressions, GET_TREATMENT_WITH_CONFIG, multiple, evaluation_options) end - def get_treatments(key, split_names, attributes = {}) - treatments = treatments(key, split_names, attributes) + def get_treatments(key, split_names, attributes = {}, evaluation_options = nil) + treatments = treatments(key, split_names, attributes, evaluation_options) return treatments if treatments.nil? keys = treatments.keys @@ -59,40 +62,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 = {}, evaluation_options = nil) + treatments(key, split_names, attributes, evaluation_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 = {}, evaluation_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, evaluation_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 = {}, evaluation_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, evaluation_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 = {}, evaluation_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, evaluation_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 = {}, evaluation_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, evaluation_options, GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SETS) end def destroy @@ -135,10 +138,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 +163,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 +214,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 +233,21 @@ 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_evaluation_options(evaluation_options) + if !evaluation_options.is_a?(SplitIoClient::Engine::Models::EvaluationOptions) + @config.logger.warn("Option #{evaluation_options} should be a EvaluationOptions type. Setting value to nil") + return nil, 0 + end + evaluation_options.properties = evaluation_options.properties.transform_keys(&:to_sym) + evaluation_options.properties, size = validate_properties(evaluation_options.properties, 'Treatment') + return evaluation_options, size + end + def valid_client if @destroyed @config.logger.error('Client has already been destroyed - no calls possible') @@ -238,8 +256,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 = {}, evaluation_options = nil, calling_method = 'get_treatments') sanitized_feature_flag_names = sanitize_split_names(calling_method, feature_flag_names) if sanitized_feature_flag_names.nil? @@ -255,6 +272,7 @@ def treatments(key, feature_flag_names, attributes = {}, calling_method = 'get_t bucketing_key, matching_key = keys_from_key(key) bucketing_key = bucketing_key ? bucketing_key.to_s : nil matching_key = matching_key ? matching_key.to_s : nil + attributes = parsed_attributes(attributes) if !@config.split_validator.valid_get_treatments_parameters(calling_method, key, sanitized_feature_flag_names, matching_key, bucketing_key, attributes) to_return = Hash.new @@ -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 }, + evaluation_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, evaluation_options) treatments[key] = { treatment: treatments_labels_change_numbers[:treatment], @@ -313,8 +333,12 @@ def treatments(key, feature_flag_names, attributes = {}, calling_method = 'get_t # @param split_data [Hash] split data, when provided this method doesn't fetch splits_repository for the data # @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) + def treatment(key, feature_flag_name, attributes = {}, split_data = nil, store_impressions = nil, + calling_method = 'get_treatment', multiple = false, evaluation_options = nil) + + log_deprecated_warning(calling_method, split_data, 'split_data') + log_deprecated_warning(calling_method, store_impressions, 'store_impressions') + impressions = [] bucketing_key, matching_key = keys_from_key(key) @@ -332,13 +356,17 @@ 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, evaluation_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 log_deprecated_warning(calling_method, parameter, parameter_name) + @config.logger.warn("#{calling_method}: detected #{parameter_name} parameter used, this parameter is deprecated and its value is ignored.") unless parameter.nil? + end + + def evaluate_treatment(feature_flag, feature_flag_name, bucketing_key, matching_key, attributes, calling_method, multiple = false, evaluation_options = nil) impressions_decorator = [] begin start = Time.now @@ -359,18 +387,20 @@ def evaluate_treatment(feature_flag, feature_flag_name, bucketing_key, matching_ impressions_disabled = false end + evaluation_options, size = validate_evaluation_options(evaluation_options) unless evaluation_options.nil? + evaluation_options.properties = nil unless evaluation_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 }, evaluation_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 }, evaluation_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 adb71c6f..d685dd7b 100644 --- a/lib/splitclient-rb/engine/common/impressions_manager.rb +++ b/lib/splitclient-rb/engine/common/impressions_manager.rb @@ -18,17 +18,24 @@ def initialize(config, @unique_keys_tracker = unique_keys_tracker end - def build_impression(matching_key, bucketing_key, split_name, treatment_data, impressions_disabled, params = {}) - impression_data = impression_data(matching_key, bucketing_key, split_name, treatment_data, params[:time]) + def build_impression(matching_key, bucketing_key, split_name, treatment_data, impressions_disabled, params = {}, + evaluation_options = nil) + properties = get_properties(evaluation_options) + impression_data = impression_data(matching_key, bucketing_key, split_name, treatment_data, params[:time], properties) + return impression(impression_data, params[:attributes]) if check_return_conditions(properties) + begin - if @config.impressions_mode == :none || impressions_disabled + if check_none_mode(impressions_disabled) @impression_counter.inc(split_name, impression_data[:m]) @unique_keys_tracker.track(split_name, matching_key) - elsif @config.impressions_mode == :debug # In DEBUG mode we should calculate the pt only. - impression_data[:pt] = @impression_observer.test_and_set(impression_data) - else # In OPTIMIZED mode we should track the total amount of evaluations and deduplicate the impressions. + end + if check_observe_impressions + # In DEBUG mode we should calculate the pt only. impression_data[:pt] = @impression_observer.test_and_set(impression_data) - @impression_counter.inc(split_name, impression_data[:m]) unless impression_data[:pt].nil? + end + if check_impression_counter(impression_data) + # In OPTIMIZED mode we should track the total amount of evaluations and deduplicate the impressions. + @impression_counter.inc(split_name, impression_data[:m]) end rescue StandardError => e @config.log_found_exception(__method__.to_s, e) @@ -61,6 +68,26 @@ def track(impressions_decorator) private + def check_return_conditions(properties) + return (@config.impressions_mode == :debug || @config.impressions_mode == :optimized) && !properties.nil? + end + + def check_none_mode(impressions_disabled) + return @config.impressions_mode == :none || impressions_disabled + end + + def check_observe_impressions + return @config.impressions_mode == :debug || @config.impressions_mode == :optimized + end + + def check_impression_counter(impression_data) + return @config.impressions_mode == :optimized && !impression_data[:pt].nil? + end + + def get_properties(evaluation_options) + return evaluation_options.nil? ? nil : evaluation_options.properties + end + def impression_router @impression_router ||= SplitIoClient::ImpressionRouter.new(@config) rescue StandardError => e @@ -79,7 +106,8 @@ def record_stats(stats) end # added param time for test - def impression_data(matching_key, bucketing_key, split_name, treatment, time = nil) + def impression_data(matching_key, bucketing_key, split_name, treatment, time = nil, + properties = nil) { k: matching_key, b: bucketing_key, @@ -88,7 +116,8 @@ def impression_data(matching_key, bucketing_key, split_name, treatment, time = n r: applied_rule(treatment[:label]), c: treatment[:change_number], m: time || (Time.now.to_f * 1000.0).to_i, - pt: nil + pt: nil, + properties: properties } end diff --git a/lib/splitclient-rb/engine/models/evaluation_options.rb b/lib/splitclient-rb/engine/models/evaluation_options.rb new file mode 100644 index 00000000..c79c710a --- /dev/null +++ b/lib/splitclient-rb/engine/models/evaluation_options.rb @@ -0,0 +1,9 @@ +module SplitIoClient::Engine::Models + class EvaluationOptions + attr_accessor :properties + + def initialize(properties) + @properties = properties + end + end +end diff --git a/lib/splitclient-rb/version.rb b/lib/splitclient-rb/version.rb index d0431b85..c34677cc 100644 --- a/lib/splitclient-rb/version.rb +++ b/lib/splitclient-rb/version.rb @@ -1,3 +1,3 @@ module SplitIoClient - VERSION = '8.6.0' + VERSION = '8.7.0' end diff --git a/spec/cache/repositories/impressions_repository_spec.rb b/spec/cache/repositories/impressions_repository_spec.rb index c422d34c..4b815e34 100644 --- a/spec/cache/repositories/impressions_repository_spec.rb +++ b/spec/cache/repositories/impressions_repository_spec.rb @@ -25,7 +25,8 @@ r: 'sample_rule', c: 1_533_177_602_748, m: 1_478_113_516_002, - pt: nil + pt: nil, + properties: nil }, attributes: {} }, @@ -39,7 +40,8 @@ r: 'sample_rule', c: 1_533_177_602_749, m: 1_478_113_516_002, - pt: nil + pt: nil, + properties: nil }, attributes: {} } @@ -55,8 +57,8 @@ it 'adds impressions' do params = { attributes: {}, time: 1_478_113_516_002 } impressions = [] - impressions << { :impression => impressions_manager.build_impression('matching_key1', nil, :foo, treatment1, false, params), :disabled => false } - impressions << { :impression => impressions_manager.build_impression('matching_key1', nil, :bar, treatment2, false, params), :disabled => false } + impressions << { :impression => impressions_manager.build_impression('matching_key1', nil, :foo, treatment1, false, params, nil), :disabled => false } + impressions << { :impression => impressions_manager.build_impression('matching_key1', nil, :bar, treatment2, false, params, nil), :disabled => false } impressions_manager.track(impressions) expect(repository.batch).to match_array(result) @@ -67,8 +69,8 @@ it 'adds impressions in bulk' do params = { attributes: {}, time: 1_478_113_516_002 } impressions = [] - impressions << { :impression => impressions_manager.build_impression('matching_key1', nil, :foo, treatment1, false, params), :disabled => false } - impressions << { :impression => impressions_manager.build_impression('matching_key1', nil, :bar, treatment2, false, params), :disabled => false } + impressions << { :impression => impressions_manager.build_impression('matching_key1', nil, :foo, treatment1, false, params, nil), :disabled => false } + impressions << { :impression => impressions_manager.build_impression('matching_key1', nil, :bar, treatment2, false, params, nil), :disabled => false } impressions_manager.track(impressions) expect(repository.batch).to match_array(result) @@ -80,7 +82,7 @@ config.labels_enabled = false params = { attributes: {}, time: 1_478_113_516_002 } impressions = [] - impressions << { :impression => impressions_manager.build_impression('matching_key1', nil, :foo, treatment1, false, params), :disabled => false } + impressions << { :impression => impressions_manager.build_impression('matching_key1', nil, :foo, treatment1, false, params, nil), :disabled => false } impressions_manager.track(impressions) expect(repository.batch.first[:i][:r]).to be_nil @@ -89,8 +91,8 @@ it 'bulk size less than the actual queue' do params = { attributes: {}, time: 1_478_113_516_002 } impressions = [] - impressions << { :impression => impressions_manager.build_impression('matching_key1', nil, :foo, treatment1, false, params), :disabled => false } - impressions << { :impression => impressions_manager.build_impression('matching_key1', nil, :foo, treatment2, false, params), :disabled => false } + impressions << { :impression => impressions_manager.build_impression('matching_key1', nil, :foo, treatment1, false, params, nil), :disabled => false } + impressions << { :impression => impressions_manager.build_impression('matching_key1', nil, :foo, treatment2, false, params, nil), :disabled => false } impressions_manager.track(impressions) config.impressions_bulk_size = 1 @@ -142,8 +144,8 @@ treatment = { treatment: 'on', label: 'sample_rule', change_number: 1_533_177_602_748 } params = { attributes: {}, time: 1_478_113_516_002 } impressions = [] - impressions << { :impression => impressions_manager.build_impression('matching_key1', nil, :foo1, treatment, false, params), :disabled => false } - impressions << { :impression => impressions_manager.build_impression('matching_key2', nil, :foo1, treatment, false, params), :disabled => false } + impressions << { :impression => impressions_manager.build_impression('matching_key1', nil, :foo1, treatment, false, params, nil), :disabled => false } + impressions << { :impression => impressions_manager.build_impression('matching_key2', nil, :foo1, treatment, false, params, nil), :disabled => false } impressions_manager.track(impressions) expect(repository.batch.size).to eq(1) @@ -200,8 +202,8 @@ expect(config.impressions_adapter).to receive(:expire).once.with(anything, 3600) params = { attributes: {}, time: 1_478_113_516_002 } impressions = [] - impressions << { :impression => impressions_manager.build_impression('matching_key', nil, :foo1, treatment, false, params), :disabled => false } - impressions << { :impression => impressions_manager.build_impression('matching_key', nil, :foo1, treatment, false, params), :disabled => false } + impressions << { :impression => impressions_manager.build_impression('matching_key', nil, :foo1, treatment, false, params, nil), :disabled => false } + impressions << { :impression => impressions_manager.build_impression('matching_key', nil, :foo1, treatment, false, params, nil), :disabled => false } impressions_manager.track(impressions) end @@ -211,7 +213,7 @@ params = { attributes: {}, time: 1_478_113_516_002 } impressions = [] - impressions << { :impression => impressions_manager.build_impression('matching_key', nil, :foo1, treatment, false, params), :disabled => false } + impressions << { :impression => impressions_manager.build_impression('matching_key', nil, :foo1, treatment, false, params, nil), :disabled => false } impressions_manager.track(impressions) expect(repository.batch).to eq([]) @@ -221,8 +223,8 @@ other_treatment = { treatment: 'on', label: 'sample_rule_2', change_number: 1_533_177_602_748 } params = { attributes: {}, time: 1_478_113_516_002 } impressions = [] - impressions << { :impression => impressions_manager.build_impression('matching_key', nil, :foo1, treatment, false, params), :disabled => false } - impressions << { :impression => impressions_manager.build_impression('matching_key', nil, :foo2, other_treatment, false, params), :disabled => false } + impressions << { :impression => impressions_manager.build_impression('matching_key', nil, :foo1, treatment, false, params, nil), :disabled => false } + impressions << { :impression => impressions_manager.build_impression('matching_key', nil, :foo2, other_treatment, false, params, nil), :disabled => false } impressions_manager.track(impressions) adapter.get_from_queue('SPLITIO.impressions', 0).map do |e| @@ -252,8 +254,8 @@ params = { attributes: {}, time: 1_478_113_516_002 } impressions = [] - impressions << { :impression => custom_impressions_manager.build_impression('matching_key', nil, :foo1, treatment, false, params), :disabled => false } - impressions << { :impression => custom_impressions_manager.build_impression('matching_key', nil, :foo2, other_treatment, false, params), :disabled => false } + impressions << { :impression => custom_impressions_manager.build_impression('matching_key', nil, :foo1, treatment, false, params, nil), :disabled => false } + impressions << { :impression => custom_impressions_manager.build_impression('matching_key', nil, :foo2, other_treatment, false, params, nil), :disabled => false } custom_impressions_manager.track(impressions) custom_adapter.get_from_queue('SPLITIO.impressions', 0).map do |e| diff --git a/spec/cache/senders/impressions_formatter_spec.rb b/spec/cache/senders/impressions_formatter_spec.rb index e55228ff..6c649a48 100644 --- a/spec/cache/senders/impressions_formatter_spec.rb +++ b/spec/cache/senders/impressions_formatter_spec.rb @@ -58,7 +58,7 @@ b: 'foo1', r: 'custom_label1', c: 123_456, - pt: nil }] + pt: nil}] }, { f: :foo2, @@ -68,14 +68,14 @@ b: 'foo2', r: 'custom_label2', c: 123_499, - pt: nil }] + pt: nil}] }]) end it 'formats multiple impressions for one key' do params = { attributes: {}, time: 1_478_113_518_900 } impressions = [] - impressions << { :impression => impressions_manager.build_impression('matching_key3', nil, 'foo2', treatment3, false, params), :disabled => false } + impressions << { :impression => impressions_manager.build_impression('matching_key3', nil, 'foo2', treatment3, false, params, SplitIoClient::Engine::Models::EvaluationOptions.new({:prop => "val"})), :disabled => false } impressions_manager.track(impressions) expect(formatted_impressions.find { |i| i[:f] == :foo1 }[:i]).to match_array( @@ -110,7 +110,8 @@ b: nil, r: nil, c: nil, - pt: nil + pt: nil, + properties: '{"prop":"val"}' } ] ) diff --git a/spec/cache/senders/impressions_sender_spec.rb b/spec/cache/senders/impressions_sender_spec.rb index 96da8aa9..ccb5b885 100644 --- a/spec/cache/senders/impressions_sender_spec.rb +++ b/spec/cache/senders/impressions_sender_spec.rb @@ -46,7 +46,7 @@ params2 = { attributes: {}, time: 1_478_113_518_285 } impressions = [] impressions << { :impression => impressions_manager.build_impression('matching_key', 'foo1', 'foo1', treatment1, false, params), :disabled => false } - impressions << { :impression => impressions_manager.build_impression('matching_key2', 'foo2', 'foo2', treatment2, false, params2), :disabled => false } + impressions << { :impression => impressions_manager.build_impression('matching_key2', 'foo2', 'foo2', treatment2, false, params2, SplitIoClient::Engine::Models::EvaluationOptions.new({:prop => "val"})), :disabled => false } impressions_manager.track(impressions) end @@ -89,7 +89,8 @@ b: 'foo2', r: 'custom_label2', c: 123_499, - pt: nil + pt: nil, + properties: '{"prop":"val"}' } ] } diff --git a/spec/engine/common/impression_manager_spec.rb b/spec/engine/common/impression_manager_spec.rb index 21875236..02aab198 100644 --- a/spec/engine/common/impression_manager_spec.rb +++ b/spec/engine/common/impression_manager_spec.rb @@ -59,7 +59,8 @@ r: 'default label', c: 1_478_113_516_002, m: 1_478_113_516_222, - pt: nil + pt: nil, + properties: {"prop":"val"} }, attributes: {} } @@ -71,7 +72,7 @@ 'split_name_test', treatment, false, - params) + params, SplitIoClient::Engine::Models::EvaluationOptions.new({"prop":"val"})) expect(impression).to match(expected) result_count = impression_counter.pop_all @@ -97,7 +98,8 @@ r: 'default label', c: 1_478_113_516_002, m: 1_478_113_516_222, - pt: nil + pt: nil, + properties: nil }, attributes: {} } @@ -155,7 +157,8 @@ r: 'default label', c: 1_478_113_516_002, m: 1_478_113_516_222, - pt: nil + pt: nil, + properties: nil }, attributes: {} } @@ -297,7 +300,8 @@ r: 'default label', c: 1_478_113_516_002, m: 1_478_113_516_222, - pt: nil + pt: nil, + properties: nil }, attributes: {} } diff --git a/spec/engine_spec.rb b/spec/engine_spec.rb index 7909d64d..3501468c 100644 --- a/spec/engine_spec.rb +++ b/spec/engine_spec.rb @@ -77,6 +77,22 @@ treatment: 'off', config: nil ) + expect(subject.get_treatment_with_config('nicolas', 'mauro_test', {}, nil)).to eq( + treatment: 'off', + config: nil + ) + expect(subject.get_treatment_with_config('nicolas', 'mauro_test', {}, {})).to eq( + treatment: 'off', + config: nil + ) + expect(subject.get_treatment_with_config('nicolas', 'mauro_test', {}, "prop")).to eq( + treatment: 'off', + config: nil + ) + expect(subject.get_treatment_with_config('nicolas', 'mauro_test', {}, 123)).to eq( + treatment: 'off', + config: nil + ) close_redis end @@ -84,6 +100,10 @@ expect(subject.get_treatment('nicolas', 'mauro_test', nil)).to eq 'off' expect(subject.get_treatment('nicolas', 'mauro_test')).to eq 'off' expect(subject.get_treatment('nicolas', 'mauro_test', {})).to eq 'off' + expect(subject.get_treatment('nicolas', 'mauro_test', {}, nil)).to eq 'off' + expect(subject.get_treatment('nicolas', 'mauro_test', {}, {})).to eq 'off' + expect(subject.get_treatment('nicolas', 'mauro_test', {}, "prop")).to eq 'off' + expect(subject.get_treatment('nicolas', 'mauro_test', {}, 123)).to eq 'off' end it 'get_treatments returns off' do @@ -96,6 +116,18 @@ expect(subject.get_treatments('nicolas', ['mauro_test'], {})).to eq( mauro_test: 'off' ) + expect(subject.get_treatments('nicolas', ['mauro_test'], {}, nil)).to eq( + mauro_test: 'off' + ) + expect(subject.get_treatments('nicolas', ['mauro_test'], {}, {})).to eq( + mauro_test: 'off' + ) + expect(subject.get_treatments('nicolas', ['mauro_test'], {}, "prop")).to eq( + mauro_test: 'off' + ) + expect(subject.get_treatments('nicolas', ['mauro_test'], {}, 123)).to eq( + mauro_test: 'off' + ) close_redis end @@ -109,6 +141,18 @@ expect(subject.get_treatments_by_flag_set('nicolas', 'set_2', {})).to eq( mauro_test: 'off' ) + expect(subject.get_treatments_by_flag_set('nicolas', 'set_2', {}, {})).to eq( + mauro_test: 'off' + ) + expect(subject.get_treatments_by_flag_set('nicolas', 'set_2', {}, nil)).to eq( + mauro_test: 'off' + ) + expect(subject.get_treatments_by_flag_set('nicolas', 'set_2', {}, "prop")).to eq( + mauro_test: 'off' + ) + expect(subject.get_treatments_by_flag_set('nicolas', 'set_2', {}, 123)).to eq( + mauro_test: 'off' + ) close_redis end @@ -122,6 +166,18 @@ expect(subject.get_treatments_by_flag_sets('nicolas', ['set_2'], {})).to eq( mauro_test: 'off' ) + expect(subject.get_treatments_by_flag_sets('nicolas', ['set_2'], {}, {})).to eq( + mauro_test: 'off' + ) + expect(subject.get_treatments_by_flag_sets('nicolas', ['set_2'], {}, nil)).to eq( + mauro_test: 'off' + ) + expect(subject.get_treatments_by_flag_sets('nicolas', ['set_2'], {}, "prop")).to eq( + mauro_test: 'off' + ) + expect(subject.get_treatments_by_flag_sets('nicolas', ['set_2'], {}, 123)).to eq( + mauro_test: 'off' + ) close_redis end end @@ -141,8 +197,7 @@ end it 'returns CONTROL and label for incorrect feature name' do - treatment = subject.get_treatment('random_user_id', 'test_featur', nil, nil, false, true) - puts treatment + treatment = subject.get_treatment('random_user_id', 'test_featur', nil, nil, nil, false, true) expect(treatment).to eq( treatment: SplitIoClient::Engine::Models::Treatment::CONTROL, label: SplitIoClient::Engine::Models::Label::NOT_FOUND, @@ -229,7 +284,7 @@ #TODO We will remove multiple param in the future. it 'returns CONTROL and label on nil key' do - expect(subject.get_treatment(nil, 'test_feature', nil, nil, false, true)).to eq( + expect(subject.get_treatment(nil, 'test_feature', nil, nil, nil, false, true)).to eq( treatment: SplitIoClient::Engine::Models::Treatment::CONTROL, label: nil, change_number: nil @@ -289,7 +344,7 @@ #TODO We will remove multiple param in the future. it 'returns CONTROL and label on nil split_name' do - expect(subject.get_treatment('random_user_id', nil, nil, nil, false, true)).to eq( + expect(subject.get_treatment('random_user_id', nil, nil, nil, nil, false, true)).to eq( treatment: SplitIoClient::Engine::Models::Treatment::CONTROL, label: nil, change_number: nil diff --git a/spec/splitclient/split_client_spec.rb b/spec/splitclient/split_client_spec.rb index fd19dc0c..6c49fffd 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', {}, SplitIoClient::Engine::Models::EvaluationOptions.new({:prop => "value"}))).to eq('off') + check_properties(impressions_repository.batch) + expect(split_client.get_treatments('key_prop', ['testing222'], {}, SplitIoClient::Engine::Models::EvaluationOptions.new({:prop => "value"}))).to eq({:testing222 => 'off'}) + check_properties(impressions_repository.batch) + expect(split_client.get_treatment_with_config('key', 'testing222', {}, SplitIoClient::Engine::Models::EvaluationOptions.new({:prop => "value"}))).to eq({:treatment => 'off', :config => nil}) + check_properties(impressions_repository.batch) + expect(split_client.get_treatments_with_config('key', ['testing222'], {}, SplitIoClient::Engine::Models::EvaluationOptions.new({: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', {}, SplitIoClient::Engine::Models::EvaluationOptions.new({:prop => "value"}))).to eq({:testing222 => 'off'}) + check_properties(impressions_repository.batch) + expect(split_client.get_treatments_by_flag_sets('key', ['set_2'], {}, SplitIoClient::Engine::Models::EvaluationOptions.new({:prop => "value"}))).to eq({:testing222 => 'off'}) + check_properties(impressions_repository.batch) + expect(split_client.get_treatments_with_config_by_flag_set('key', 'set_1', {}, SplitIoClient::Engine::Models::EvaluationOptions.new({: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'], {}, SplitIoClient::Engine::Models::EvaluationOptions.new({: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