Skip to content

Commit fd1e59e

Browse files
authored
Merge pull request #560 from splitio/feature/prerequisites
Feature/prerequisites
2 parents f8a3c46 + a10f235 commit fd1e59e

9 files changed

Lines changed: 173 additions & 3 deletions

File tree

lib/splitclient-rb.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@
9999
require 'splitclient-rb/engine/matchers/between_semver_matcher'
100100
require 'splitclient-rb/engine/matchers/in_list_semver_matcher'
101101
require 'splitclient-rb/engine/matchers/rule_based_segment_matcher'
102+
require 'splitclient-rb/engine/matchers/prerequisites_matcher'
102103
require 'splitclient-rb/engine/evaluator/splitter'
103104
require 'splitclient-rb/engine/impressions/noop_unique_keys_tracker'
104105
require 'splitclient-rb/engine/impressions/unique_keys_tracker'

lib/splitclient-rb/cache/stores/localhost_split_builder.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ def build_split(feature, treatments)
2222
seed: 2_089_907_429,
2323
defaultTreatment: 'control_treatment',
2424
configurations: build_configurations(treatments),
25-
conditions: build_conditions(treatments)
25+
conditions: build_conditions(treatments),
26+
prerequisites: []
2627
}
2728
end
2829

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# frozen_string_literal: true
2+
3+
module SplitIoClient
4+
class PrerequisitesMatcher
5+
def initialize(prerequisites, logger)
6+
@prerequisites = prerequisites
7+
@logger = logger
8+
end
9+
10+
def match?(args)
11+
keys = { matching_key: args[:matching_key], bucketing_key: args[:bucketing_key] }
12+
13+
match = true
14+
@prerequisites.each do |prerequisite|
15+
evaluate = args[:evaluator].evaluate_feature_flag(keys, prerequisite[:n], args[:attributes])
16+
next if prerequisite[:ts].include?(evaluate[:treatment])
17+
18+
@logger.log_if_debug("[PrerequisitesMatcher] feature flag #{prerequisite[:n]} evaluated to #{evaluate[:treatment]} \
19+
that does not exist in prerequisited treatments.")
20+
match = false
21+
break
22+
end
23+
24+
match
25+
end
26+
27+
def string_type?
28+
false
29+
end
30+
end
31+
end

lib/splitclient-rb/engine/parser/evaluator.rb

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,16 @@ def split_configurations(treatment, split)
3838
end
3939

4040
def match(split, keys, attributes)
41+
if split.key?(:prerequisites) && !split[:prerequisites].nil?
42+
prerequisites_matcher = SplitIoClient::PrerequisitesMatcher.new(split[:prerequisites], @config.split_logger)
43+
return treatment_hash(Models::Label::PREREQUISITES_NOT_MET, split[:defaultTreatment], split[:changeNumber], split_configurations(split[:defaultTreatment], split)) unless prerequisites_matcher.match?(
44+
matching_key: keys[:matching_key],
45+
bucketing_key: keys[:bucketing_key],
46+
evaluator: self,
47+
attributes: attributes
48+
)
49+
end
50+
4151
in_rollout = false
4252
key = keys[:bucketing_key] ? keys[:bucketing_key] : keys[:matching_key]
4353
legacy_algo = (split[:algo] == 1 || split[:algo] == nil) ? true : false

lib/splitclient-rb/helpers/repository_helper.rb

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ def self.update_feature_flag_repository(feature_flag_repository, feature_flags,
1313
next
1414
end
1515

16-
feature_flag = check_impressions_disabled(feature_flag, config)
16+
feature_flag = check_missing_elements(feature_flag, config)
1717

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

25-
def self.check_impressions_disabled(feature_flag, config)
25+
def self.check_missing_elements(feature_flag, config)
2626
unless feature_flag.key?(:impressionsDisabled)
2727
feature_flag[:impressionsDisabled] = false
2828
if config.debug_enabled
2929
config.logger.debug("feature flag (#{feature_flag[:name]}) does not have impressionsDisabled field, setting it to false")
3030
end
3131
end
32+
33+
unless feature_flag.key?(:prerequisites)
34+
feature_flag[:prerequisites] = []
35+
if config.debug_enabled
36+
config.logger.debug("feature flag (#{feature_flag[:name]}) does not have prerequisites field, setting it to empty array")
37+
end
38+
end
39+
3240
feature_flag
3341
end
3442

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# frozen_string_literal: true
2+
3+
require 'spec_helper'
4+
5+
describe SplitIoClient::PrerequisitesMatcher do
6+
let(:evaluator) { double }
7+
8+
it 'matches with empty prerequisites' do
9+
expect(described_class.new([], @split_logger)
10+
.match?(matching_key: 'foo', bucketing_key: 'bar', evaluator: evaluator)).to eq(true)
11+
end
12+
13+
it 'matches with prerequisite treatments' do
14+
allow(evaluator).to receive(:evaluate_feature_flag).with({ matching_key: 'foo', bucketing_key: 'bar' }, 'flag1', nil)
15+
.and_return(treatment: 'on')
16+
17+
expect(described_class.new([:n => 'flag1', :ts => ['on']], @split_logger)
18+
.match?(matching_key: 'foo', bucketing_key: 'bar', evaluator: evaluator)).to eq(true)
19+
expect(described_class.new([:n => 'flag1', :ts => ['off']], @split_logger)
20+
.match?(matching_key: 'foo', bucketing_key: 'bar', evaluator: evaluator)).to eq(false)
21+
end
22+
23+
it 'is not string type matcher' do
24+
expect(described_class.new([], @split_logger).string_type?).to be false
25+
end
26+
end

spec/integrations/in_memory_client_spec.rb

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,30 @@
9090
expect(impressions[1][:treatment][:change_number]).to eq(1_506_703_262_916)
9191
end
9292

93+
it 'returns treatments with prereq_flag feature and check impressions' do
94+
stub_request(:get, "https://sdk.split.io/api/splitChanges?s=1.3&since=1506703262916&rbSince=-1").to_return(status: 200, body: 'ok')
95+
client.block_until_ready
96+
expect(client.get_treatment('nico_test', 'prereq_flag')).to eq 'on'
97+
expect(client.get_treatment('bla', 'prereq_flag')).to eq 'off_default'
98+
99+
sleep 0.5
100+
impressions = custom_impression_listener.queue
101+
102+
expect(impressions.size).to eq 2
103+
104+
expect(impressions[0][:matching_key]).to eq('nico_test')
105+
expect(impressions[0][:split_name]).to eq('prereq_flag')
106+
expect(impressions[0][:treatment][:treatment]).to eq('on')
107+
expect(impressions[0][:treatment][:label]).to eq('in segment all')
108+
expect(impressions[0][:treatment][:change_number]).to eq(1494593336752)
109+
110+
expect(impressions[1][:matching_key]).to eq('bla')
111+
expect(impressions[1][:split_name]).to eq('prereq_flag')
112+
expect(impressions[1][:treatment][:treatment]).to eq('off_default')
113+
expect(impressions[1][:treatment][:label]).to eq('prerequisites not met')
114+
expect(impressions[1][:treatment][:change_number]).to eq(1494593336752)
115+
end
116+
93117
it 'returns treatments with Test_Save_1 feature and check impressions' do
94118
stub_request(:get, "https://sdk.split.io/api/splitChanges?s=1.3&since=1506703262916&rbSince=-1").to_return(status: 200, body: 'ok')
95119
client.block_until_ready

spec/repository_helper.rb

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,5 +83,21 @@
8383
expect(feature_flag_repository.get_split('split2').nil?).to eq(false)
8484
expect(feature_flag_repository.get_split('split1').nil?).to eq(true)
8585
end
86+
87+
it 'test prerequisites element' do
88+
config = SplitIoClient::SplitConfig.new(cache_adapter: :memory)
89+
flag_set_filter = SplitIoClient::Cache::Filter::FlagSetsFilter.new([])
90+
flag_sets_repository = SplitIoClient::Cache::Repositories::MemoryFlagSetsRepository.new([])
91+
feature_flag_repository = SplitIoClient::Cache::Repositories::SplitsRepository.new(
92+
config,
93+
flag_sets_repository,
94+
flag_set_filter)
95+
96+
SplitIoClient::Helpers::RepositoryHelper.update_feature_flag_repository(feature_flag_repository, [{:name => 'split1', :status => 'ACTIVE', conditions: [], :sets => []}], -1, config, false)
97+
expect(feature_flag_repository.get_split('split1')[:prerequisites]).to eq([])
98+
99+
SplitIoClient::Helpers::RepositoryHelper.update_feature_flag_repository(feature_flag_repository, [{:name => 'split2', :status => 'ACTIVE', conditions: [], :prerequisites => [{:n => 'flag', :ts => ['on']}], :sets => []}], -1, config, false)
100+
expect(feature_flag_repository.get_split('split2')[:prerequisites]).to eq([{:n => 'flag', :ts => ['on']}])
101+
end
86102
end
87103
end

spec/test_data/integrations/splits.json

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2521,6 +2521,59 @@
25212521
"label": "in segment all"
25222522
}
25232523
]
2524+
},
2525+
{
2526+
"impressionsDisabled": false,
2527+
"trafficTypeName": "account",
2528+
"name": "prereq_flag",
2529+
"prerequisites": [
2530+
{"n": "MAURO_TEST", "ts": ["off"]},
2531+
{"n": "FACUNDO_TEST", "ts": ["on"]}
2532+
],
2533+
"trafficAllocation": 100,
2534+
"trafficAllocationSeed": -285565213,
2535+
"seed": -1992295819,
2536+
"status": "ACTIVE",
2537+
"killed": false,
2538+
"defaultTreatment": "off_default",
2539+
"changeNumber": 1494593336752,
2540+
"algo": 2,
2541+
"conditions": [
2542+
{
2543+
"conditionType": "ROLLOUT",
2544+
"matcherGroup": {
2545+
"combiner": "AND",
2546+
"matchers": [
2547+
{
2548+
"keySelector": {
2549+
"trafficType": "user",
2550+
"attribute": null
2551+
},
2552+
"matcherType": "ALL_KEYS",
2553+
"negate": false,
2554+
"userDefinedSegmentMatcherData": null,
2555+
"whitelistMatcherData": null,
2556+
"unaryNumericMatcherData": null,
2557+
"betweenMatcherData": null,
2558+
"booleanMatcherData": null,
2559+
"dependencyMatcherData": null,
2560+
"stringMatcherData": null
2561+
}
2562+
]
2563+
},
2564+
"partitions": [
2565+
{
2566+
"treatment": "on",
2567+
"size": 100
2568+
},
2569+
{
2570+
"treatment": "off",
2571+
"size": 0
2572+
}
2573+
],
2574+
"label": "in segment all"
2575+
}
2576+
]
25242577
}
25252578
],
25262579
"s": -1,

0 commit comments

Comments
 (0)