Skip to content
1 change: 1 addition & 0 deletions lib/splitclient-rb.rb
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@
require 'splitclient-rb/engine/matchers/between_semver_matcher'
require 'splitclient-rb/engine/matchers/in_list_semver_matcher'
require 'splitclient-rb/engine/matchers/rule_based_segment_matcher'
require 'splitclient-rb/engine/matchers/prerequisites_matcher'
require 'splitclient-rb/engine/evaluator/splitter'
require 'splitclient-rb/engine/impressions/noop_unique_keys_tracker'
require 'splitclient-rb/engine/impressions/unique_keys_tracker'
Expand Down
3 changes: 2 additions & 1 deletion lib/splitclient-rb/cache/stores/localhost_split_builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ def build_split(feature, treatments)
seed: 2_089_907_429,
defaultTreatment: 'control_treatment',
configurations: build_configurations(treatments),
conditions: build_conditions(treatments)
conditions: build_conditions(treatments),
prerequisites: []
}
end

Expand Down
31 changes: 31 additions & 0 deletions lib/splitclient-rb/engine/matchers/prerequisites_matcher.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# frozen_string_literal: true

module SplitIoClient
class PrerequisitesMatcher
def initialize(prerequisites, logger)
@prerequisites = prerequisites
@logger = logger
end

def match?(args)
keys = { matching_key: args[:matching_key], bucketing_key: args[:bucketing_key] }

match = true
@prerequisites.each do |prerequisite|
evaluate = args[:evaluator].evaluate_feature_flag(keys, prerequisite[:n], args[:attributes])
next if prerequisite[:ts].include?(evaluate[:treatment])

@logger.log_if_debug("[PrerequisitesMatcher] feature flag #{prerequisite[:n]} evaluated to #{evaluate[:treatment]} \
that does not exist in prerequisited treatments.")
match = false
break
end

match
end

def string_type?
false
end
end
end
10 changes: 10 additions & 0 deletions lib/splitclient-rb/engine/parser/evaluator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,16 @@ def split_configurations(treatment, split)
end

def match(split, keys, attributes)
if split.key?(:prerequisites) && !split[:prerequisites].nil?
prerequisites_matcher = SplitIoClient::PrerequisitesMatcher.new(split[:prerequisites], @config.split_logger)
return treatment_hash(Models::Label::PREREQUISITES_NOT_MET, split[:defaultTreatment], split[:changeNumber], split_configurations(split[:defaultTreatment], split)) unless prerequisites_matcher.match?(
matching_key: keys[:matching_key],
bucketing_key: keys[:bucketing_key],
evaluator: self,
attributes: attributes
)
end

in_rollout = false
key = keys[:bucketing_key] ? keys[:bucketing_key] : keys[:matching_key]
legacy_algo = (split[:algo] == 1 || split[:algo] == nil) ? true : false
Expand Down
12 changes: 10 additions & 2 deletions lib/splitclient-rb/helpers/repository_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ def self.update_feature_flag_repository(feature_flag_repository, feature_flags,
next
end

feature_flag = check_impressions_disabled(feature_flag, config)
feature_flag = check_missing_elements(feature_flag, config)

config.logger.debug("storing feature flag (#{feature_flag[:name]})") if config.debug_enabled
to_add.push(feature_flag)
Expand All @@ -22,13 +22,21 @@ def self.update_feature_flag_repository(feature_flag_repository, feature_flags,
feature_flag_repository.update(to_add, to_delete, change_number)
end

def self.check_impressions_disabled(feature_flag, config)
def self.check_missing_elements(feature_flag, config)
unless feature_flag.key?(:impressionsDisabled)
feature_flag[:impressionsDisabled] = false
if config.debug_enabled
config.logger.debug("feature flag (#{feature_flag[:name]}) does not have impressionsDisabled field, setting it to false")
end
end

unless feature_flag.key?(:prerequisites)
feature_flag[:prerequisites] = []
if config.debug_enabled
config.logger.debug("feature flag (#{feature_flag[:name]}) does not have prerequisites field, setting it to empty array")
end
end

feature_flag
end

Expand Down
26 changes: 26 additions & 0 deletions spec/engine/matchers/prerequisites_matcher_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# frozen_string_literal: true

require 'spec_helper'

describe SplitIoClient::PrerequisitesMatcher do
let(:evaluator) { double }

it 'matches with empty prerequisites' do
expect(described_class.new([], @split_logger)
.match?(matching_key: 'foo', bucketing_key: 'bar', evaluator: evaluator)).to eq(true)
end

it 'matches with prerequisite treatments' do
allow(evaluator).to receive(:evaluate_feature_flag).with({ matching_key: 'foo', bucketing_key: 'bar' }, 'flag1', nil)
.and_return(treatment: 'on')

expect(described_class.new([:n => 'flag1', :ts => ['on']], @split_logger)
.match?(matching_key: 'foo', bucketing_key: 'bar', evaluator: evaluator)).to eq(true)
expect(described_class.new([:n => 'flag1', :ts => ['off']], @split_logger)
.match?(matching_key: 'foo', bucketing_key: 'bar', evaluator: evaluator)).to eq(false)
end

it 'is not string type matcher' do
expect(described_class.new([], @split_logger).string_type?).to be false
end
end
24 changes: 24 additions & 0 deletions spec/integrations/in_memory_client_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,30 @@
expect(impressions[1][:treatment][:change_number]).to eq(1_506_703_262_916)
end

it 'returns treatments with prereq_flag feature and check impressions' do
stub_request(:get, "https://sdk.split.io/api/splitChanges?s=1.3&since=1506703262916&rbSince=-1").to_return(status: 200, body: 'ok')
client.block_until_ready
expect(client.get_treatment('nico_test', 'prereq_flag')).to eq 'on'
expect(client.get_treatment('bla', 'prereq_flag')).to eq 'off_default'

sleep 0.5
impressions = custom_impression_listener.queue

expect(impressions.size).to eq 2

expect(impressions[0][:matching_key]).to eq('nico_test')
expect(impressions[0][:split_name]).to eq('prereq_flag')
expect(impressions[0][:treatment][:treatment]).to eq('on')
expect(impressions[0][:treatment][:label]).to eq('in segment all')
expect(impressions[0][:treatment][:change_number]).to eq(1494593336752)

expect(impressions[1][:matching_key]).to eq('bla')
expect(impressions[1][:split_name]).to eq('prereq_flag')
expect(impressions[1][:treatment][:treatment]).to eq('off_default')
expect(impressions[1][:treatment][:label]).to eq('prerequisites not met')
expect(impressions[1][:treatment][:change_number]).to eq(1494593336752)
end

it 'returns treatments with Test_Save_1 feature and check impressions' do
stub_request(:get, "https://sdk.split.io/api/splitChanges?s=1.3&since=1506703262916&rbSince=-1").to_return(status: 200, body: 'ok')
client.block_until_ready
Expand Down
16 changes: 16 additions & 0 deletions spec/repository_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -83,5 +83,21 @@
expect(feature_flag_repository.get_split('split2').nil?).to eq(false)
expect(feature_flag_repository.get_split('split1').nil?).to eq(true)
end

it 'test prerequisites element' do
config = SplitIoClient::SplitConfig.new(cache_adapter: :memory)
flag_set_filter = SplitIoClient::Cache::Filter::FlagSetsFilter.new([])
flag_sets_repository = SplitIoClient::Cache::Repositories::MemoryFlagSetsRepository.new([])
feature_flag_repository = SplitIoClient::Cache::Repositories::SplitsRepository.new(
config,
flag_sets_repository,
flag_set_filter)

SplitIoClient::Helpers::RepositoryHelper.update_feature_flag_repository(feature_flag_repository, [{:name => 'split1', :status => 'ACTIVE', conditions: [], :sets => []}], -1, config, false)
expect(feature_flag_repository.get_split('split1')[:prerequisites]).to eq([])

SplitIoClient::Helpers::RepositoryHelper.update_feature_flag_repository(feature_flag_repository, [{:name => 'split2', :status => 'ACTIVE', conditions: [], :prerequisites => [{:n => 'flag', :ts => ['on']}], :sets => []}], -1, config, false)
expect(feature_flag_repository.get_split('split2')[:prerequisites]).to eq([{:n => 'flag', :ts => ['on']}])
end
end
end
53 changes: 53 additions & 0 deletions spec/test_data/integrations/splits.json
Original file line number Diff line number Diff line change
Expand Up @@ -2521,6 +2521,59 @@
"label": "in segment all"
}
]
},
{
"impressionsDisabled": false,
"trafficTypeName": "account",
"name": "prereq_flag",
"prerequisites": [
{"n": "MAURO_TEST", "ts": ["off"]},
{"n": "FACUNDO_TEST", "ts": ["on"]}
],
"trafficAllocation": 100,
"trafficAllocationSeed": -285565213,
"seed": -1992295819,
"status": "ACTIVE",
"killed": false,
"defaultTreatment": "off_default",
"changeNumber": 1494593336752,
"algo": 2,
"conditions": [
{
"conditionType": "ROLLOUT",
"matcherGroup": {
"combiner": "AND",
"matchers": [
{
"keySelector": {
"trafficType": "user",
"attribute": null
},
"matcherType": "ALL_KEYS",
"negate": false,
"userDefinedSegmentMatcherData": null,
"whitelistMatcherData": null,
"unaryNumericMatcherData": null,
"betweenMatcherData": null,
"booleanMatcherData": null,
"dependencyMatcherData": null,
"stringMatcherData": null
}
]
},
"partitions": [
{
"treatment": "on",
"size": 100
},
{
"treatment": "off",
"size": 0
}
],
"label": "in segment all"
}
]
}
],
"s": -1,
Expand Down