Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lib/splitclient-rb.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
68 changes: 32 additions & 36 deletions lib/splitclient-rb/clients/split_client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,64 +35,64 @@ def initialize(sdk_key, repositories, status_manager, config, impressions_manage
end

def get_treatment(
key, split_name, attributes = {}, options = nil, split_data = nil, store_impressions = true,
key, split_name, attributes = {}, evaluation_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, options)
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 = {}, options = nil, split_data = nil, store_impressions = true,
key, split_name, attributes = {}, evaluation_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, options)
treatment(key, split_name, attributes, split_data, store_impressions, GET_TREATMENT_WITH_CONFIG, multiple, evaluation_options)
end

def get_treatments(key, split_names, attributes = {}, options = nil)
treatments = treatments(key, split_names, attributes, options)
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
treats = treatments.map { |_,t| t[:treatment] }
Hash[keys.zip(treats)]
end

def get_treatments_with_config(key, split_names, attributes = {}, options = nil)
treatments(key, split_names, attributes, options, 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 = {}, options = nil)
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, options, 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 = {}, options = nil)
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, options, 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 = {}, options = nil)
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, options, 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 = {}, options = nil)
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, options, GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SETS)
treatments(key, split_names, attributes, evaluation_options, GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SETS)
end

def destroy
Expand Down Expand Up @@ -235,18 +235,14 @@ def validate_properties(properties, method = 'Event')
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")
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
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
evaluation_options.properties = evaluation_options.properties.transform_keys(&:to_sym)
evaluation_options.properties, size = validate_properties(evaluation_options.properties, method = 'Treatment')
return evaluation_options, size
end

def valid_client
Expand All @@ -257,7 +253,7 @@ def valid_client
@config.valid_mode
end

def treatments(key, feature_flag_names, attributes = {}, options = nil, calling_method = 'get_treatments')
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?
Expand Down Expand Up @@ -290,7 +286,7 @@ def treatments(key, feature_flag_names, attributes = {}, options = nil, calling_
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 },
options), :disabled => false }
evaluation_options), :disabled => false }
}
@impressions_manager.track(impressions)
return to_return
Expand All @@ -312,7 +308,7 @@ def treatments(key, feature_flag_names, attributes = {}, options = nil, calling_
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, false, options)
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],
Expand All @@ -335,7 +331,7 @@ def treatments(key, feature_flag_names, attributes = {}, options = nil, calling_
# @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, options = nil)
calling_method = 'get_treatment', multiple = false, evaluation_options = nil)
impressions = []
bucketing_key, matching_key = keys_from_key(key)

Expand All @@ -353,13 +349,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, options)
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, options = nil)
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
Expand All @@ -380,16 +376,16 @@ def evaluate_treatment(feature_flag, feature_flag_name, bucketing_key, matching_
impressions_disabled = false
end

options, size = validate_options(options) unless options.nil?
options[:properties] = nil unless options.nil? or check_properties_size((EVENT_AVERAGE_SIZE + size), "Properties are ignored")
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 }, options), :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 }, options), :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
Expand Down
43 changes: 32 additions & 11 deletions lib/splitclient-rb/engine/common/impressions_manager.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,22 +19,23 @@ def initialize(config,
end

def build_impression(matching_key, bucketing_key, split_name, treatment_data, impressions_disabled, params = {},
options = nil)
properties = options.nil? ? nil : options[:properties]
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.
return impression(impression_data, params[:attributes]) unless properties.nil?

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.
return impression(impression_data, params[:attributes]) unless properties.nil?

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)
Expand Down Expand Up @@ -67,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
Expand Down
9 changes: 9 additions & 0 deletions lib/splitclient-rb/engine/models/evaluation_options.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
module SplitIoClient::Engine::Models
class EvaluationOptions
attr_accessor :properties

def initialize(properties)
@properties = properties
end
end
end
2 changes: 1 addition & 1 deletion spec/cache/senders/impressions_formatter_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@
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, {:properties => {:prop => "val"}}), :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(
Expand Down
2 changes: 1 addition & 1 deletion spec/cache/senders/impressions_sender_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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, {:properties => {:prop => "val"}}), :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

Expand Down
2 changes: 1 addition & 1 deletion spec/engine/common/impression_manager_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@
'split_name_test',
treatment,
false,
params, {"properties": {"prop":"val"}})
params, SplitIoClient::Engine::Models::EvaluationOptions.new({"prop":"val"}))
expect(impression).to match(expected)

result_count = impression_counter.pop_all
Expand Down
Loading