splitclient-rb 8.5.0-java → 8.6.0-java
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGES.txt +4 -0
- data/lib/splitclient-rb/cache/fetchers/split_fetcher.rb +9 -7
- data/lib/splitclient-rb/cache/repositories/rule_based_segments_repository.rb +122 -0
- data/lib/splitclient-rb/cache/repositories/segments_repository.rb +7 -0
- data/lib/splitclient-rb/cache/repositories/splits_repository.rb +19 -11
- data/lib/splitclient-rb/cache/stores/localhost_split_builder.rb +2 -1
- data/lib/splitclient-rb/clients/split_client.rb +2 -0
- data/lib/splitclient-rb/engine/api/client.rb +8 -0
- data/lib/splitclient-rb/engine/api/splits.rb +99 -23
- data/lib/splitclient-rb/engine/matchers/combining_matcher.rb +3 -1
- data/lib/splitclient-rb/engine/matchers/prerequisites_matcher.rb +31 -0
- data/lib/splitclient-rb/engine/matchers/rule_based_segment_matcher.rb +75 -0
- data/lib/splitclient-rb/engine/models/label.rb +1 -0
- data/lib/splitclient-rb/engine/models/segment_type.rb +4 -0
- data/lib/splitclient-rb/engine/models/split_http_response.rb +19 -0
- data/lib/splitclient-rb/engine/parser/condition.rb +9 -0
- data/lib/splitclient-rb/engine/parser/evaluator.rb +24 -30
- data/lib/splitclient-rb/engine/synchronizer.rb +33 -13
- data/lib/splitclient-rb/helpers/evaluator_helper.rb +37 -0
- data/lib/splitclient-rb/helpers/repository_helper.rb +38 -7
- data/lib/splitclient-rb/helpers/util.rb +12 -3
- data/lib/splitclient-rb/spec.rb +1 -1
- data/lib/splitclient-rb/split_config.rb +4 -0
- data/lib/splitclient-rb/split_factory.rb +5 -3
- data/lib/splitclient-rb/sse/event_source/event_types.rb +1 -0
- data/lib/splitclient-rb/sse/notification_processor.rb +3 -1
- data/lib/splitclient-rb/sse/workers/splits_worker.rb +58 -19
- data/lib/splitclient-rb/version.rb +1 -1
- data/lib/splitclient-rb.rb +6 -0
- data/splitclient-rb.gemspec +2 -2
- metadata +12 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 45b72f5b1e13cdd2d067607b46c75d873d4df920e65d015f19b1ada3d6bb0c00
|
4
|
+
data.tar.gz: 4a596b26aa997ccb4c78b393d91e5896e0a857266c7454f681f10f508a4959a1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1f823c51ec538daad072c17081946fbeb5d1cea16ed985916f288586bf8c13c5f0394106ab9e35775e4a3d084709d5ff5fca20eeb3dc0ca53156cf55303e4428
|
7
|
+
data.tar.gz: 29177735ef6b1d587c59874fd236afa56d0985ef65e41b33678738bc2179353af8800ee3bd5c97c81317802aec74aa0c371ece6cc2c9dcecbb8edd1de4fddcd2
|
data/CHANGES.txt
CHANGED
@@ -1,5 +1,9 @@
|
|
1
1
|
CHANGES
|
2
2
|
|
3
|
+
8.6.0 (Jun 17, 2025)
|
4
|
+
- 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.
|
5
|
+
- Added support for feature flag prerequisites. This allows customers to define dependency conditions between flags, which are evaluated before any allowlists or targeting rules.
|
6
|
+
|
3
7
|
8.5.0 (Jan 17, 2025)
|
4
8
|
- Fixed high cpu usage when unique keys are cleared every 24 hours.
|
5
9
|
- Added support for the new impressions tracking toggle available on feature flags, both respecting the setting and including the new field being returned on SplitView type objects. Read more in our docs.
|
@@ -2,10 +2,11 @@ module SplitIoClient
|
|
2
2
|
module Cache
|
3
3
|
module Fetchers
|
4
4
|
class SplitFetcher
|
5
|
-
attr_reader :splits_repository
|
5
|
+
attr_reader :splits_repository, :rule_based_segments_repository
|
6
6
|
|
7
|
-
def initialize(splits_repository, api_key, config, telemetry_runtime_producer)
|
7
|
+
def initialize(splits_repository, rule_based_segments_repository, api_key, config, telemetry_runtime_producer)
|
8
8
|
@splits_repository = splits_repository
|
9
|
+
@rule_based_segments_repository = rule_based_segments_repository
|
9
10
|
@api_key = api_key
|
10
11
|
@config = config
|
11
12
|
@semaphore = Mutex.new
|
@@ -23,10 +24,11 @@ module SplitIoClient
|
|
23
24
|
|
24
25
|
def fetch_splits(fetch_options = { cache_control_headers: false, till: nil })
|
25
26
|
@semaphore.synchronize do
|
26
|
-
data = splits_since(@splits_repository.get_change_number, fetch_options)
|
27
|
-
|
28
|
-
SplitIoClient::Helpers::RepositoryHelper.
|
27
|
+
data = splits_since(@splits_repository.get_change_number, @rule_based_segments_repository.get_change_number, fetch_options)
|
28
|
+
SplitIoClient::Helpers::RepositoryHelper.update_feature_flag_repository(@splits_repository, data[:ff][:d], data[:ff][:t], @config, @splits_api.clear_storage)
|
29
|
+
SplitIoClient::Helpers::RepositoryHelper.update_rule_based_segment_repository(@rule_based_segments_repository, data[:rbs][:d], data[:rbs][:t], @config)
|
29
30
|
@splits_repository.set_segment_names(data[:segment_names])
|
31
|
+
@rule_based_segments_repository.set_segment_names(data[:segment_names])
|
30
32
|
@config.logger.debug("segments seen(#{data[:segment_names].length}): #{data[:segment_names].to_a}") if @config.debug_enabled
|
31
33
|
|
32
34
|
{ segment_names: data[:segment_names], success: true }
|
@@ -55,8 +57,8 @@ module SplitIoClient
|
|
55
57
|
end
|
56
58
|
end
|
57
59
|
|
58
|
-
def splits_since(since, fetch_options = { cache_control_headers: false, till: nil })
|
59
|
-
splits_api.since(since, fetch_options)
|
60
|
+
def splits_since(since, since_rbs, fetch_options = { cache_control_headers: false, till: nil })
|
61
|
+
splits_api.since(since, since_rbs, fetch_options)
|
60
62
|
end
|
61
63
|
|
62
64
|
def splits_api
|
@@ -0,0 +1,122 @@
|
|
1
|
+
require 'concurrent'
|
2
|
+
|
3
|
+
module SplitIoClient
|
4
|
+
module Cache
|
5
|
+
module Repositories
|
6
|
+
class RuleBasedSegmentsRepository < Repository
|
7
|
+
attr_reader :adapter
|
8
|
+
DEFAULT_CONDITIONS_TEMPLATE = [{
|
9
|
+
conditionType: "ROLLOUT",
|
10
|
+
matcherGroup: {
|
11
|
+
combiner: "AND",
|
12
|
+
matchers: [
|
13
|
+
{
|
14
|
+
keySelector: nil,
|
15
|
+
matcherType: "ALL_KEYS",
|
16
|
+
negate: false,
|
17
|
+
userDefinedSegmentMatcherData: nil,
|
18
|
+
whitelistMatcherData: nil,
|
19
|
+
unaryNumericMatcherData: nil,
|
20
|
+
betweenMatcherData: nil,
|
21
|
+
dependencyMatcherData: nil,
|
22
|
+
booleanMatcherData: nil,
|
23
|
+
stringMatcherData: nil
|
24
|
+
}]
|
25
|
+
}
|
26
|
+
}]
|
27
|
+
TILL_PREFIX = '.rbsegments.till'
|
28
|
+
RB_SEGMENTS_PREFIX = '.rbsegment.'
|
29
|
+
REGISTERED_PREFIX = '.segments.registered'
|
30
|
+
|
31
|
+
def initialize(config)
|
32
|
+
super(config)
|
33
|
+
@adapter = case @config.cache_adapter.class.to_s
|
34
|
+
when 'SplitIoClient::Cache::Adapters::RedisAdapter'
|
35
|
+
SplitIoClient::Cache::Adapters::CacheAdapter.new(@config)
|
36
|
+
else
|
37
|
+
@config.cache_adapter
|
38
|
+
end
|
39
|
+
unless @config.mode.equal?(:consumer)
|
40
|
+
@adapter.set_string(namespace_key(TILL_PREFIX), '-1')
|
41
|
+
@adapter.initialize_map(namespace_key(REGISTERED_PREFIX))
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def update(to_add, to_delete, new_change_number)
|
46
|
+
to_add.each{ |rule_based_segment| add_rule_based_segment(rule_based_segment) }
|
47
|
+
to_delete.each{ |rule_based_segment| remove_rule_based_segment(rule_based_segment) }
|
48
|
+
set_change_number(new_change_number)
|
49
|
+
end
|
50
|
+
|
51
|
+
def get_rule_based_segment(name)
|
52
|
+
rule_based_segment = @adapter.string(namespace_key("#{RB_SEGMENTS_PREFIX}#{name}"))
|
53
|
+
|
54
|
+
JSON.parse(rule_based_segment, symbolize_names: true) if rule_based_segment
|
55
|
+
end
|
56
|
+
|
57
|
+
def rule_based_segment_names
|
58
|
+
@adapter.find_strings_by_prefix(namespace_key(RB_SEGMENTS_PREFIX))
|
59
|
+
.map { |rule_based_segment_names| rule_based_segment_names.gsub(namespace_key(RB_SEGMENTS_PREFIX), '') }
|
60
|
+
end
|
61
|
+
|
62
|
+
def set_change_number(since)
|
63
|
+
@adapter.set_string(namespace_key(TILL_PREFIX), since)
|
64
|
+
end
|
65
|
+
|
66
|
+
def get_change_number
|
67
|
+
@adapter.string(namespace_key(TILL_PREFIX))
|
68
|
+
end
|
69
|
+
|
70
|
+
def set_segment_names(names)
|
71
|
+
return if names.nil? || names.empty?
|
72
|
+
|
73
|
+
names.each do |name|
|
74
|
+
@adapter.add_to_set(namespace_key(REGISTERED_PREFIX), name)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def exists?(name)
|
79
|
+
@adapter.exists?(namespace_key("#{RB_SEGMENTS_PREFIX}#{name}"))
|
80
|
+
end
|
81
|
+
|
82
|
+
def clear
|
83
|
+
@adapter.clear(namespace_key)
|
84
|
+
end
|
85
|
+
|
86
|
+
def contains?(segment_names)
|
87
|
+
return false if rule_based_segment_names.empty?
|
88
|
+
return set(segment_names).subset?(rule_based_segment_names)
|
89
|
+
end
|
90
|
+
|
91
|
+
private
|
92
|
+
|
93
|
+
def add_rule_based_segment(rule_based_segment)
|
94
|
+
return unless rule_based_segment[:name]
|
95
|
+
|
96
|
+
if check_undefined_matcher(rule_based_segment)
|
97
|
+
@config.logger.warn("Rule based segment #{rule_based_segment[:name]} has undefined matcher, setting conditions to default template.")
|
98
|
+
rule_based_segment[:conditions] = RuleBasedSegmentsRepository::DEFAULT_CONDITIONS_TEMPLATE
|
99
|
+
end
|
100
|
+
|
101
|
+
@adapter.set_string(namespace_key("#{RB_SEGMENTS_PREFIX}#{rule_based_segment[:name]}"), rule_based_segment.to_json)
|
102
|
+
end
|
103
|
+
|
104
|
+
def check_undefined_matcher(rule_based_segment)
|
105
|
+
for condition in rule_based_segment[:conditions]
|
106
|
+
for matcher in condition[:matcherGroup][:matchers]
|
107
|
+
if !SplitIoClient::Condition.instance_methods(false).map(&:to_s).include?("matcher_#{matcher[:matcherType].downcase}")
|
108
|
+
@config.logger.error("Detected undefined matcher #{matcher[:matcherType].downcase} in feature flag #{rule_based_segment[:name]}")
|
109
|
+
return true
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
return false
|
114
|
+
end
|
115
|
+
|
116
|
+
def remove_rule_based_segment(rule_based_segment)
|
117
|
+
@adapter.delete(namespace_key("#{RB_SEGMENTS_PREFIX}#{rule_based_segment[:name]}"))
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
@@ -31,6 +31,9 @@ module SplitIoClient
|
|
31
31
|
],
|
32
32
|
label: "targeting rule type unsupported by sdk"
|
33
33
|
}]
|
34
|
+
TILL_PREFIX = '.splits.till'
|
35
|
+
SPLIT_PREFIX = '.split.'
|
36
|
+
READY_PREFIX = '.splits.ready'
|
34
37
|
|
35
38
|
def initialize(config, flag_sets_repository, flag_set_filter)
|
36
39
|
super(config)
|
@@ -43,10 +46,7 @@ module SplitIoClient
|
|
43
46
|
end
|
44
47
|
@flag_sets = flag_sets_repository
|
45
48
|
@flag_set_filter = flag_set_filter
|
46
|
-
|
47
|
-
@adapter.set_string(namespace_key('.splits.till'), '-1')
|
48
|
-
@adapter.initialize_map(namespace_key('.segments.registered'))
|
49
|
-
end
|
49
|
+
initialize_keys
|
50
50
|
end
|
51
51
|
|
52
52
|
def update(to_add, to_delete, new_change_number)
|
@@ -87,16 +87,16 @@ module SplitIoClient
|
|
87
87
|
|
88
88
|
# Return an array of Split Names excluding control keys like splits.till
|
89
89
|
def split_names
|
90
|
-
@adapter.find_strings_by_prefix(namespace_key(
|
91
|
-
.map { |split| split.gsub(namespace_key(
|
90
|
+
@adapter.find_strings_by_prefix(namespace_key(SPLIT_PREFIX))
|
91
|
+
.map { |split| split.gsub(namespace_key(SPLIT_PREFIX), '') }
|
92
92
|
end
|
93
93
|
|
94
94
|
def set_change_number(since)
|
95
|
-
@adapter.set_string(namespace_key(
|
95
|
+
@adapter.set_string(namespace_key(TILL_PREFIX), since)
|
96
96
|
end
|
97
97
|
|
98
98
|
def get_change_number
|
99
|
-
@adapter.string(namespace_key(
|
99
|
+
@adapter.string(namespace_key(TILL_PREFIX))
|
100
100
|
end
|
101
101
|
|
102
102
|
def set_segment_names(names)
|
@@ -112,21 +112,22 @@ module SplitIoClient
|
|
112
112
|
end
|
113
113
|
|
114
114
|
def ready?
|
115
|
-
@adapter.string(namespace_key(
|
115
|
+
@adapter.string(namespace_key(READY_PREFIX)).to_i != -1
|
116
116
|
end
|
117
117
|
|
118
118
|
def not_ready!
|
119
|
-
@adapter.set_string(namespace_key(
|
119
|
+
@adapter.set_string(namespace_key(READY_PREFIX), -1)
|
120
120
|
end
|
121
121
|
|
122
122
|
def ready!
|
123
|
-
@adapter.set_string(namespace_key(
|
123
|
+
@adapter.set_string(namespace_key(READY_PREFIX), Time.now.utc.to_i)
|
124
124
|
end
|
125
125
|
|
126
126
|
def clear
|
127
127
|
@tt_cache.clear
|
128
128
|
|
129
129
|
@adapter.clear(namespace_key)
|
130
|
+
initialize_keys
|
130
131
|
end
|
131
132
|
|
132
133
|
def kill(change_number, split_name, default_treatment)
|
@@ -167,6 +168,13 @@ module SplitIoClient
|
|
167
168
|
|
168
169
|
private
|
169
170
|
|
171
|
+
def initialize_keys
|
172
|
+
unless @config.mode.equal?(:consumer)
|
173
|
+
@adapter.set_string(namespace_key(TILL_PREFIX), '-1')
|
174
|
+
@adapter.initialize_map(namespace_key('.segments.registered'))
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
170
178
|
def add_feature_flag(split)
|
171
179
|
return unless split[:name]
|
172
180
|
existing_split = get_split(split[:name])
|
@@ -22,7 +22,8 @@ module SplitIoClient
|
|
22
22
|
seed: 2_089_907_429,
|
23
23
|
defaultTreatment: 'control_treatment',
|
24
24
|
configurations: build_configurations(treatments),
|
25
|
-
conditions: build_conditions(treatments)
|
25
|
+
conditions: build_conditions(treatments),
|
26
|
+
prerequisites: []
|
26
27
|
}
|
27
28
|
end
|
28
29
|
|
@@ -22,6 +22,7 @@ module SplitIoClient
|
|
22
22
|
@api_key = sdk_key
|
23
23
|
@splits_repository = repositories[:splits]
|
24
24
|
@segments_repository = repositories[:segments]
|
25
|
+
@rule_based_segments_repository = repositories[:rule_based_segments]
|
25
26
|
@impressions_repository = repositories[:impressions]
|
26
27
|
@events_repository = repositories[:events]
|
27
28
|
@status_manager = status_manager
|
@@ -115,6 +116,7 @@ module SplitIoClient
|
|
115
116
|
|
116
117
|
@splits_repository.clear
|
117
118
|
@segments_repository.clear
|
119
|
+
@rule_based_segments_repository.clear
|
118
120
|
|
119
121
|
SplitIoClient.load_factory_registry
|
120
122
|
SplitIoClient.split_factory_registry.remove(@api_key)
|
@@ -20,6 +20,11 @@ module SplitIoClient
|
|
20
20
|
|
21
21
|
@config.split_logger.log_if_debug("GET #{url} proxy: #{api_client.proxy}")
|
22
22
|
end
|
23
|
+
|
24
|
+
rescue Zlib::GzipFile::Error => e
|
25
|
+
@config.logger.warn("Incorrect formatted response exception: #{e}\n")
|
26
|
+
SplitIoClient::Engine::Models::SplitHttpResponse.new(400, '', true)
|
27
|
+
|
23
28
|
rescue StandardError => e
|
24
29
|
@config.logger.warn("#{e}\nURL:#{url}\nparams:#{params}")
|
25
30
|
raise e, 'Split SDK failed to connect to backend to retrieve information', e.backtrace
|
@@ -50,6 +55,9 @@ module SplitIoClient
|
|
50
55
|
raise e, 'Split SDK failed to connect to backend to post information', e.backtrace
|
51
56
|
end
|
52
57
|
|
58
|
+
def sdk_url_overriden?
|
59
|
+
@config.sdk_url_overriden?
|
60
|
+
end
|
53
61
|
private
|
54
62
|
|
55
63
|
def api_client
|
@@ -5,17 +5,40 @@ module SplitIoClient
|
|
5
5
|
# Retrieves split definitions from the Split Backend
|
6
6
|
class Splits < Client
|
7
7
|
|
8
|
+
PROXY_CHECK_INTERVAL_SECONDS = 24 * 60 * 60
|
9
|
+
SPEC_1_1 = "1.1"
|
10
|
+
|
8
11
|
def initialize(api_key, config, telemetry_runtime_producer)
|
9
12
|
super(config)
|
10
13
|
@api_key = api_key
|
11
14
|
@telemetry_runtime_producer = telemetry_runtime_producer
|
12
15
|
@flag_sets_filter = @config.flag_sets_filter
|
16
|
+
@spec_version = SplitIoClient::Spec::FeatureFlags::SPEC_VERSION
|
17
|
+
@last_proxy_check_timestamp = 0
|
18
|
+
@clear_storage = false
|
19
|
+
@old_spec_since = nil
|
13
20
|
end
|
14
21
|
|
15
|
-
def since(since, fetch_options = { cache_control_headers: false, till: nil, sets: nil})
|
22
|
+
def since(since, since_rbs, fetch_options = { cache_control_headers: false, till: nil, sets: nil})
|
16
23
|
start = Time.now
|
24
|
+
|
25
|
+
if check_last_proxy_check_timestamp
|
26
|
+
@spec_version = SplitIoClient::Spec::FeatureFlags::SPEC_VERSION
|
27
|
+
@config.logger.debug("Switching to new Feature flag spec #{@spec_version} and fetching.")
|
28
|
+
@old_spec_since = since
|
29
|
+
since = -1
|
30
|
+
since_rbs = -1
|
31
|
+
fetch_options = { cache_control_headers: false, till: nil, sets: nil}
|
32
|
+
end
|
33
|
+
|
34
|
+
if @spec_version == Splits::SPEC_1_1
|
35
|
+
since = @old_spec_since unless @old_spec_since.nil?
|
36
|
+
params = { s: @spec_version, since: since }
|
37
|
+
@old_spec_since = nil
|
38
|
+
else
|
39
|
+
params = { s: @spec_version, since: since, rbSince: since_rbs }
|
40
|
+
end
|
17
41
|
|
18
|
-
params = { s: SplitIoClient::Spec::FeatureFlags::SPEC_VERSION, since: since }
|
19
42
|
params[:sets] = @flag_sets_filter.join(",") unless @flag_sets_filter.empty?
|
20
43
|
params[:till] = fetch_options[:till] unless fetch_options[:till].nil?
|
21
44
|
@config.logger.debug("Fetching from splitChanges with #{params}: ")
|
@@ -24,39 +47,92 @@ module SplitIoClient
|
|
24
47
|
@config.logger.error("Error fetching feature flags; the amount of flag sets provided are too big, causing uri length error.")
|
25
48
|
raise ApiException.new response.body, 414
|
26
49
|
end
|
50
|
+
|
51
|
+
if response.status == 400 and sdk_url_overriden? and @spec_version == SplitIoClient::Spec::FeatureFlags::SPEC_VERSION
|
52
|
+
@config.logger.warn("Detected proxy response error, changing spec version from #{@spec_version} to #{Splits::SPEC_1_1} and re-fetching.")
|
53
|
+
@spec_version = Splits::SPEC_1_1
|
54
|
+
@last_proxy_check_timestamp = Time.now
|
55
|
+
return since(since, 0, fetch_options = {cache_control_headers: fetch_options[:cache_control_headers], till: fetch_options[:till],
|
56
|
+
sets: fetch_options[:sets]})
|
57
|
+
end
|
27
58
|
if response.success?
|
28
|
-
result =
|
29
|
-
unless result[:splits].empty?
|
30
|
-
@config.split_logger.log_if_debug("#{result[:splits].length} feature flags retrieved. since=#{since}")
|
31
|
-
end
|
32
|
-
@config.split_logger.log_if_transport("Feature flag changes response: #{result.to_s}")
|
59
|
+
result = JSON.parse(response.body, symbolize_names: true)
|
33
60
|
|
34
|
-
|
35
|
-
|
36
|
-
|
61
|
+
return process_result(result, since, since_rbs, start)
|
62
|
+
end
|
63
|
+
@telemetry_runtime_producer.record_sync_error(Telemetry::Domain::Constants::SPLIT_SYNC, response.status)
|
37
64
|
|
38
|
-
|
39
|
-
|
40
|
-
@telemetry_runtime_producer.record_sync_error(Telemetry::Domain::Constants::SPLIT_SYNC, response.status)
|
65
|
+
@config.logger.error("Unexpected status code while fetching feature flags: #{response.status}. " \
|
66
|
+
'Check your API key and base URI')
|
41
67
|
|
42
|
-
|
43
|
-
|
68
|
+
raise 'Split SDK failed to connect to backend to fetch feature flags definitions'
|
69
|
+
end
|
44
70
|
|
45
|
-
|
46
|
-
|
71
|
+
def clear_storage
|
72
|
+
@clear_storage
|
47
73
|
end
|
48
74
|
|
49
75
|
private
|
50
76
|
|
51
|
-
def
|
52
|
-
|
77
|
+
def process_result(result, since, since_rbs, start)
|
78
|
+
if @spec_version == Splits::SPEC_1_1
|
79
|
+
result = convert_to_new_spec(result)
|
80
|
+
end
|
81
|
+
|
82
|
+
result[:rbs][:d] = check_rbs_data(result[:rbs][:d])
|
83
|
+
result = objects_with_segment_names(result)
|
84
|
+
|
85
|
+
if @spec_version == SplitIoClient::Spec::FeatureFlags::SPEC_VERSION
|
86
|
+
@clear_storage = @last_proxy_check_timestamp != 0
|
87
|
+
@last_proxy_check_timestamp = 0
|
88
|
+
end
|
89
|
+
|
90
|
+
unless result[:ff][:d].empty?
|
91
|
+
@config.split_logger.log_if_debug("#{result[:ff][:d].length} feature flags retrieved. since=#{since}")
|
92
|
+
end
|
93
|
+
@config.split_logger.log_if_transport("Feature flag changes response: #{result[:ff].to_s}")
|
94
|
+
|
95
|
+
unless result[:rbs][:d].empty?
|
96
|
+
@config.split_logger.log_if_debug("#{result[:rbs][:d].length} rule based segments retrieved. since=#{since_rbs}")
|
97
|
+
end
|
98
|
+
@config.split_logger.log_if_transport("rule based segments changes response: #{result[:rbs].to_s}")
|
99
|
+
|
100
|
+
bucket = BinarySearchLatencyTracker.get_bucket((Time.now - start) * 1000.0)
|
101
|
+
@telemetry_runtime_producer.record_sync_latency(Telemetry::Domain::Constants::SPLIT_SYNC, bucket)
|
102
|
+
@telemetry_runtime_producer.record_successful_sync(Telemetry::Domain::Constants::SPLIT_SYNC, (Time.now.to_f * 1000.0).to_i)
|
103
|
+
|
104
|
+
result
|
105
|
+
end
|
106
|
+
|
107
|
+
def check_rbs_data(rbs_data)
|
108
|
+
rbs_data.each do |rb_segment|
|
109
|
+
rb_segment[:excluded] = {:keys => [], :segments => []} if rb_segment[:excluded].nil?
|
110
|
+
rb_segment[:excluded][:keys] = [] if rb_segment[:excluded][:keys].nil?
|
111
|
+
rb_segment[:excluded][:segments] = [] if rb_segment[:excluded][:segments].nil?
|
112
|
+
end
|
113
|
+
rbs_data
|
114
|
+
end
|
53
115
|
|
54
|
-
|
55
|
-
|
56
|
-
|
116
|
+
def objects_with_segment_names(parsed_objects)
|
117
|
+
parsed_objects[:segment_names] = Set.new
|
118
|
+
parsed_objects[:segment_names] =
|
119
|
+
parsed_objects[:ff][:d].each_with_object(Set.new) do |split, splits|
|
120
|
+
splits << Helpers::Util.segment_names_by_object(split, "IN_SEGMENT")
|
57
121
|
end.flatten
|
58
122
|
|
59
|
-
|
123
|
+
parsed_objects[:rbs][:d].each do |rule_based_segment|
|
124
|
+
parsed_objects[:segment_names].merge Helpers::Util.segment_names_in_rb_segment(rule_based_segment, "IN_SEGMENT")
|
125
|
+
end
|
126
|
+
|
127
|
+
parsed_objects
|
128
|
+
end
|
129
|
+
|
130
|
+
def check_last_proxy_check_timestamp
|
131
|
+
@spec_version == Splits::SPEC_1_1 and ((Time.now - @last_proxy_check_timestamp) >= Splits::PROXY_CHECK_INTERVAL_SECONDS)
|
132
|
+
end
|
133
|
+
|
134
|
+
def convert_to_new_spec(body)
|
135
|
+
{:ff => {:d => body[:splits], :s => body[:since], :t => body[:till]}, :rbs => {:d => [], :s => -1, :t => -1}}
|
60
136
|
end
|
61
137
|
end
|
62
138
|
end
|
@@ -56,7 +56,9 @@ module SplitIoClient
|
|
56
56
|
|
57
57
|
@matchers.all? do |matcher|
|
58
58
|
if match_with_key?(matcher)
|
59
|
-
|
59
|
+
key = args[:value]
|
60
|
+
key = args[:matching_key] unless args[:matching_key].nil?
|
61
|
+
matcher.match?(value: key)
|
60
62
|
else
|
61
63
|
matcher.match?(args)
|
62
64
|
end
|
@@ -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
|
@@ -0,0 +1,75 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SplitIoClient
|
4
|
+
#
|
5
|
+
# class to implement the user defined matcher
|
6
|
+
#
|
7
|
+
class RuleBasedSegmentMatcher < Matcher
|
8
|
+
MATCHER_TYPE = 'IN_RULE_BASED_SEGMENT'
|
9
|
+
|
10
|
+
def initialize(segments_repository, rule_based_segments_repository, segment_name, config)
|
11
|
+
super(config.logger)
|
12
|
+
@rule_based_segments_repository = rule_based_segments_repository
|
13
|
+
@segments_repository = segments_repository
|
14
|
+
@segment_name = segment_name
|
15
|
+
@config = config
|
16
|
+
end
|
17
|
+
|
18
|
+
#
|
19
|
+
# evaluates if the key matches the matcher
|
20
|
+
#
|
21
|
+
# @param key [string] key value to be matched
|
22
|
+
#
|
23
|
+
# @return [boolean] evaluation of the key against the segment
|
24
|
+
def match?(args)
|
25
|
+
rule_based_segment = @rule_based_segments_repository.get_rule_based_segment(@segment_name)
|
26
|
+
return false if rule_based_segment.nil?
|
27
|
+
|
28
|
+
key = update_key(args)
|
29
|
+
return false if rule_based_segment[:excluded][:keys].include?(key)
|
30
|
+
|
31
|
+
return false unless check_excluded_segments(rule_based_segment, key, args)
|
32
|
+
|
33
|
+
matches = false
|
34
|
+
rule_based_segment[:conditions].each do |c|
|
35
|
+
condition = SplitIoClient::Condition.new(c, @config)
|
36
|
+
next if condition.empty?
|
37
|
+
|
38
|
+
matches = Helpers::EvaluatorHelper.matcher_type(condition, @segments_repository, @rule_based_segments_repository).match?(args)
|
39
|
+
end
|
40
|
+
@logger.debug("[InRuleSegmentMatcher] #{@segment_name} is in rule based segment -> #{matches}")
|
41
|
+
matches
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def check_excluded_segments(rule_based_segment, key, args)
|
47
|
+
rule_based_segment[:excluded][:segments].each do |segment|
|
48
|
+
if segment[:type] == SplitIoClient::Engine::Models::SegmentType::STANDARD &&
|
49
|
+
@segments_repository.in_segment?(segment[:name], key)
|
50
|
+
return false
|
51
|
+
end
|
52
|
+
return false if segment[:type] == SplitIoClient::Engine::Models::SegmentType::RULE_BASED_SEGMENT && match_rbs(
|
53
|
+
@rule_based_segments_repository.get_rule_based_segment(segment[:name]), args
|
54
|
+
)
|
55
|
+
end
|
56
|
+
true
|
57
|
+
end
|
58
|
+
|
59
|
+
def update_key(args)
|
60
|
+
if args[:value].nil? || args[:value].empty?
|
61
|
+
args[:matching_key]
|
62
|
+
else
|
63
|
+
args[:value]
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def match_rbs(rule_based_segment, args)
|
68
|
+
rbs_matcher = RuleBasedSegmentMatcher.new(@segments_repository, @rule_based_segments_repository,
|
69
|
+
rule_based_segment[:name], @config)
|
70
|
+
rbs_matcher.match?(matching_key: args[:matching_key],
|
71
|
+
bucketing_key: args[:value],
|
72
|
+
attributes: args[:attributes])
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module SplitIoClient
|
2
|
+
module Engine
|
3
|
+
module Models
|
4
|
+
class SplitHttpResponse
|
5
|
+
attr_accessor :status, :body
|
6
|
+
|
7
|
+
def initialize(status, body, success)
|
8
|
+
@status = status
|
9
|
+
@body = body
|
10
|
+
@success = success
|
11
|
+
end
|
12
|
+
|
13
|
+
def success?
|
14
|
+
@success
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -230,6 +230,13 @@ module SplitIoClient
|
|
230
230
|
)
|
231
231
|
end
|
232
232
|
|
233
|
+
def matcher_in_rule_based_segment(params)
|
234
|
+
matcher = params[:matcher]
|
235
|
+
segment_name = matcher[:userDefinedSegmentMatcherData] && matcher[:userDefinedSegmentMatcherData][:segmentName]
|
236
|
+
|
237
|
+
RuleBasedSegmentMatcher.new(params[:segments_repository], params[:rule_based_segments_repository], segment_name, @config)
|
238
|
+
end
|
239
|
+
|
233
240
|
#
|
234
241
|
# @return [object] the negate value for this condition
|
235
242
|
def negate
|
@@ -246,6 +253,8 @@ module SplitIoClient
|
|
246
253
|
# @return [void]
|
247
254
|
def set_partitions
|
248
255
|
partitions_list = []
|
256
|
+
return partitions_list unless @data.key?(:partitions) or @data.key?('partitions')
|
257
|
+
|
249
258
|
@data[:partitions].each do |p|
|
250
259
|
partition = SplitIoClient::Partition.new(p)
|
251
260
|
partitions_list << partition
|