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
34 changes: 28 additions & 6 deletions lib/splitclient-rb/clients/split_client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,9 @@ def get_treatment_with_config(
multiple = false, evaluator = nil
)
log_deprecated_warning(GET_TREATMENT, evaluator, 'evaluator')
treatment(key, split_name, attributes, split_data, store_impressions, GET_TREATMENT_WITH_CONFIG, multiple, evaluation_options)
result = treatment(key, split_name, attributes, split_data, store_impressions, GET_TREATMENT_WITH_CONFIG, multiple, evaluation_options)

{ :config => result[:config], :treatment => result[:treatment] }
end

def get_treatments(key, split_names, attributes = {}, evaluation_options = nil)
Expand All @@ -64,7 +66,11 @@ def get_treatments(key, split_names, attributes = {}, evaluation_options = nil)
end

def get_treatments_with_config(key, split_names, attributes = {}, evaluation_options = nil)
treatments(key, split_names, attributes, evaluation_options, GET_TREATMENTS_WITH_CONFIG)
results = treatments(key, split_names, attributes, evaluation_options, GET_TREATMENTS_WITH_CONFIG)

results.map{|key, value|
[key, { treatment: value[:treatment], config: value[:config] }]
}.to_h
end

def get_treatments_by_flag_set(key, flag_set, attributes = {}, evaluation_options = nil)
Expand All @@ -90,13 +96,21 @@ def get_treatments_by_flag_sets(key, flag_sets, attributes = {}, evaluation_opti
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, evaluation_options, GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SET)
results = treatments(key, split_names, attributes, evaluation_options, GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SET)

results.map{|key, value|
[key, { treatment: value[:treatment], config: value[:config] }]
}.to_h
end

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, evaluation_options, GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SETS)
results = treatments(key, split_names, attributes, evaluation_options, GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SETS)

results.map{|key, value|
[key, { treatment: value[:treatment], config: value[:config] }]
}.to_h
end

def destroy
Expand Down Expand Up @@ -471,12 +485,20 @@ def record_exception(method)
end

def check_fallback_treatment(feature_name, label)
return {
label: (label != '')? label : nil,
treatment: Engine::Models::Treatment::CONTROL,
config: nil,
change_number: nil
} unless feature_name.is_a?(Symbol) || feature_name.is_a?(String)

fallback_treatment = @fallback_treatment_calculator.resolve(feature_name.to_sym, label)

{
label: fallback_treatment.label,
label: (label != '')? fallback_treatment.label : nil,
treatment: fallback_treatment.treatment,
config: get_fallback_config(fallback_treatment)
config: get_fallback_config(fallback_treatment),
change_number: nil
}
end

Expand Down
3 changes: 1 addition & 2 deletions lib/splitclient-rb/split_factory.rb
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,7 @@ def initialize(api_key, config_hash = {})
@evaluator = Engine::Parser::Evaluator.new(@segments_repository, @splits_repository, @rule_based_segment_repository, @config)

start!

fallback_treatment_calculator = SplitIoClient::Engine::FallbackTreatmentCalculator.new(SplitIoClient::Engine::Models::FallbackTreatmentsConfiguration.new)
fallback_treatment_calculator = SplitIoClient::Engine::FallbackTreatmentCalculator.new(@config.fallback_treatments_configuration)
@client = SplitClient.new(@api_key, repositories, @status_manager, @config, @impressions_manager, @evaluation_producer, @evaluator, @split_validator, fallback_treatment_calculator)
@manager = SplitManager.new(@splits_repository, @status_manager, @config)
end
Expand Down
3 changes: 2 additions & 1 deletion spec/allocations/splitclient-rb/clients/split_client_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
let(:telemetry_api) { SplitIoClient::Api::TelemetryApi.new(config, api_key, runtime_producer) }
let(:impressions_api) { SplitIoClient::Api::Impressions.new(api_key, config, runtime_producer) }
let(:evaluator) { SplitIoClient::Engine::Parser::Evaluator.new(segments_repository, splits_repository, rule_based_segments_repository, config) }
let(:fallback_treatment_calculator) { SplitIoClient::Engine::FallbackTreatmentCalculator.new(SplitIoClient::Engine::Models::FallbackTreatmentsConfiguration.new) }
let(:sender_adapter) do
SplitIoClient::Cache::Senders::ImpressionsSenderAdapter.new(config,
telemetry_api,
Expand All @@ -41,7 +42,7 @@
unique_keys_tracker)
end
let(:client) do
SplitIoClient::SplitClient.new('', {:splits => splits_repository, :segments => segments_repository, :impressions => impressions_repository, :events => nil}, nil, config, impressions_manager, evaluation_producer, evaluator, SplitIoClient::Validators.new(config))
SplitIoClient::SplitClient.new('', {:splits => splits_repository, :segments => segments_repository, :impressions => impressions_repository, :events => nil}, nil, config, impressions_manager, evaluation_producer, evaluator, SplitIoClient::Validators.new(config), fallback_treatment_calculator)
end

context 'control' do
Expand Down
116 changes: 116 additions & 0 deletions spec/integrations/in_memory_client_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1443,6 +1443,122 @@
expect(client_old_spec.get_treatment('whitelisted_user', 'whitelist_feature')).to eq('control')
end
end

context 'fallback treatments' do
it 'feature not found' do
splits_fallback = File.read(File.join(SplitIoClient.root, 'spec/test_data/splits/imp-toggle.json'))
stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=-1&rbSince=-1')
.to_return(status: 200, body: splits_fallback)
factory_fallback =
SplitIoClient::SplitFactory.new('test_api_key',
fallback_treatments: SplitIoClient::Engine::Models::FallbackTreatmentsConfiguration.new(
SplitIoClient::Engine::Models::FallbackTreatment.new(
"on-global", '{"prop": "global"}'
),
{:feature => SplitIoClient::Engine::Models::FallbackTreatment.new(
"on-local", '{"prop": "local"}'
)
}
),
impressions_mode: :optimized,
features_refresh_rate: 9999,
telemetry_refresh_rate: 99999,
impressions_refresh_rate: 99999,
streaming_enabled: false
)

client_fallback = factory_fallback.client
client_fallback.block_until_ready
result = client_fallback.get_treatment_with_config('key2', 'feature')
expect(result[:treatment]).to eq('on-local')
expect(result[:config]).to eq('{"prop": "local"}')

result = client_fallback.get_treatment_with_config('key3', 'feature2')
expect(result[:treatment]).to eq('on-global')
expect(result[:config]).to eq('{"prop": "global"}')

impressions_repository = client_fallback.instance_variable_get(:@impressions_repository)
imps = impressions_repository.batch
expect(imps.length()).to eq(0)
end

it 'exception' do
splits_fallback = File.read(File.join(SplitIoClient.root, 'spec/test_data/splits/imp-toggle.json'))
stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=-1&rbSince=-1')
.to_return(status: 200, body: splits_fallback)
factory_fallback =
SplitIoClient::SplitFactory.new('test_api_key',
fallback_treatments: SplitIoClient::Engine::Models::FallbackTreatmentsConfiguration.new(
SplitIoClient::Engine::Models::FallbackTreatment.new(
"on-global", '{"prop": "global"}'
),
{:feature => SplitIoClient::Engine::Models::FallbackTreatment.new(
"on-local", '{"prop": "local"}'
)
}
),
impressions_mode: :optimized,
features_refresh_rate: 9999,
telemetry_refresh_rate: 99999,
impressions_refresh_rate: 99999,
streaming_enabled: false
)

client_fallback = factory_fallback.client
client_fallback.block_until_ready

splits_repository = client_fallback.instance_variable_get(:@splits_repository)
splits = File.read(File.join(SplitIoClient.root, 'spec/test_data/splits/imp-toggle.json'))
split = JSON.parse(splits,:symbolize_names => true)[:ff][:d][0]
split[:trafficAllocation] = nil
splits_repository.update([split], [], -1)

result = client_fallback.get_treatment_with_config('key3', 'with_track_disabled')
expect(result[:treatment]).to eq('on-global')
expect(result[:config]).to eq('{"prop": "global"}')

impressions_repository = client_fallback.instance_variable_get(:@impressions_repository)
imps = impressions_repository.batch
expect(imps.length()).to eq(1)
expect(imps[0][:i][:f]).to eq('with_track_disabled')
expect(imps[0][:i][:r]).to eq('fallback - exception')
end

it 'client not ready' do
splits_fallback = File.read(File.join(SplitIoClient.root, 'spec/test_data/splits/imp-toggle.json'))
stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=-1&rbSince=-1')
.to_return(status: 200, body: splits_fallback)
factory_fallback =
SplitIoClient::SplitFactory.new('test_api_key',
fallback_treatments: SplitIoClient::Engine::Models::FallbackTreatmentsConfiguration.new(
SplitIoClient::Engine::Models::FallbackTreatment.new(
"on-global", '{"prop": "global"}'
),
{:feature => SplitIoClient::Engine::Models::FallbackTreatment.new(
"on-local", '{"prop": "local"}'
)
}
),
impressions_mode: :optimized,
features_refresh_rate: 9999,
telemetry_refresh_rate: 99999,
impressions_refresh_rate: 99999,
streaming_enabled: false
)

client_fallback = factory_fallback.client

result = client_fallback.get_treatment_with_config('key3', 'with_track_disabled')
expect(result[:treatment]).to eq('on-global')
expect(result[:config]).to eq('{"prop": "global"}')

impressions_repository = client_fallback.instance_variable_get(:@impressions_repository)
imps = impressions_repository.batch
expect(imps.length()).to eq(1)
expect(imps[0][:i][:f]).to eq('with_track_disabled')
expect(imps[0][:i][:r]).to eq('fallback - not ready')
end
end
end

private
Expand Down
88 changes: 88 additions & 0 deletions spec/integrations/redis_client_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -992,6 +992,94 @@
expect(events.size).to eq 0
end
end

context 'fallback treatments' do
it 'feature not found' do
factory_fallback =
SplitIoClient::SplitFactory.new('test_api_key',
fallback_treatments: SplitIoClient::Engine::Models::FallbackTreatmentsConfiguration.new(
SplitIoClient::Engine::Models::FallbackTreatment.new(
"on-global", '{"prop": "global"}'
),
{:feature => SplitIoClient::Engine::Models::FallbackTreatment.new(
"on-local", '{"prop": "local"}'
)
}
),
logger: Logger.new(log),
cache_adapter: :redis,
redis_namespace: 'test',
mode: :consumer,
redis_url: 'redis://127.0.0.1:6379/0',
impression_listener: custom_impression_listener
)

client_fallback = factory_fallback.client
load_splits_redis(splits, client_fallback)
load_segment_redis(segment1, client_fallback)
load_segment_redis(segment2, client_fallback)
load_segment_redis(segment3, client_fallback)
load_flag_sets_redis(flag_sets, client_fallback)
client_fallback.block_until_ready

result = client_fallback.get_treatment_with_config('key2', 'feature')
expect(result[:treatment]).to eq('on-local')
expect(result[:config]).to eq('{"prop": "local"}')

result = client_fallback.get_treatment_with_config('key3', 'feature2')
expect(result[:treatment]).to eq('on-global')
expect(result[:config]).to eq('{"prop": "global"}')

sleep 0.5
impressions = custom_impression_listener.queue
expect(impressions.size).to eq 0
end

it 'exception' do
factory_fallback =
SplitIoClient::SplitFactory.new('test_api_key',
fallback_treatments: SplitIoClient::Engine::Models::FallbackTreatmentsConfiguration.new(
SplitIoClient::Engine::Models::FallbackTreatment.new(
"on-global", '{"prop": "global"}'
),
{:feature => SplitIoClient::Engine::Models::FallbackTreatment.new(
"on-local", '{"prop": "local"}'
)
}
),
logger: Logger.new(log),
cache_adapter: :redis,
redis_namespace: 'test',
mode: :consumer,
redis_url: 'redis://127.0.0.1:6379/0',
impression_listener: custom_impression_listener
)

client_fallback = factory_fallback.client
load_splits_redis(splits, client_fallback)
load_segment_redis(segment1, client_fallback)
load_segment_redis(segment2, client_fallback)
load_segment_redis(segment3, client_fallback)
load_flag_sets_redis(flag_sets, client_fallback)
client_fallback.block_until_ready

splits_repository = client_fallback.instance_variable_get(:@splits_repository)
splits = File.read(File.join(SplitIoClient.root, 'spec/test_data/splits/imp-toggle.json'))
split = JSON.parse(splits,:symbolize_names => true)[:ff][:d][0]
split[:trafficAllocation] = nil
splits_repository.update([split], [], -1)

result = client_fallback.get_treatment_with_config('key3', 'with_track_disabled')
expect(result[:treatment]).to eq('on-global')
expect(result[:config]).to eq('{"prop": "global"}')

sleep 0.5
impressions = custom_impression_listener.queue
expect(impressions.size).to eq 1
expect(impressions[0][:split_name]).to eq('with_track_disabled')
expect(impressions[0][:treatment][:label]).to eq('fallback - exception')
end
end
end

private
Expand Down
27 changes: 27 additions & 0 deletions spec/splitclient/engine_localhost_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -160,4 +160,31 @@
end
end
end

context 'fallback treatment' do
subject { SplitIoClient::SplitFactoryBuilder.build('localhost',
fallback_treatments: SplitIoClient::Engine::Models::FallbackTreatmentsConfiguration.new(
SplitIoClient::Engine::Models::FallbackTreatment.new(
"on-global", '{"prop": "global"}'
),
{:feature => SplitIoClient::Engine::Models::FallbackTreatment.new(
"on-local", '{"prop": "local"}'
)
}
),
split_file: split_file).client
}

let(:split_file) { File.expand_path(File.join(File.dirname(__FILE__), '../test_data/local_treatments/split.yaml')) }

it 'feature does not exist' do
result = subject.get_treatment_with_config('john_doe', 'feature')
expect(result[:treatment]).to eq('on-local')
expect(result[:config]).to eq('{"prop": "local"}')

result = subject.get_treatment_with_config('john_doe', 'feature2')
expect(result[:treatment]).to eq('on-global')
expect(result[:config]).to eq('{"prop": "global"}')
end
end
end