splitclient-rb 8.4.0 → 8.11.0
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/.github/workflows/ci.yml +9 -2
- data/CHANGES.txt +24 -0
- data/LICENSE +169 -13
- data/NOTICE.txt +5 -0
- data/lib/splitclient-rb/cache/fetchers/split_fetcher.rb +9 -7
- data/lib/splitclient-rb/cache/filter/bloom_filter.rb +10 -6
- data/lib/splitclient-rb/cache/repositories/impressions/redis_repository.rb +1 -0
- data/lib/splitclient-rb/cache/repositories/rule_based_segments_repository.rb +136 -0
- data/lib/splitclient-rb/cache/repositories/segments_repository.rb +21 -2
- data/lib/splitclient-rb/cache/repositories/splits_repository.rb +42 -12
- data/lib/splitclient-rb/cache/senders/impressions_formatter.rb +24 -10
- data/lib/splitclient-rb/cache/stores/localhost_split_builder.rb +2 -1
- data/lib/splitclient-rb/clients/split_client.rb +146 -62
- data/lib/splitclient-rb/engine/api/client.rb +8 -0
- data/lib/splitclient-rb/engine/api/faraday_middleware/gzip.rb +1 -0
- data/lib/splitclient-rb/engine/api/splits.rb +100 -24
- data/lib/splitclient-rb/engine/auth_api_client.rb +3 -3
- data/lib/splitclient-rb/engine/common/impressions_manager.rb +56 -30
- data/lib/splitclient-rb/engine/events/events_delivery.rb +20 -0
- data/lib/splitclient-rb/engine/events/events_manager.rb +194 -0
- data/lib/splitclient-rb/engine/events/events_manager_config.rb +96 -0
- data/lib/splitclient-rb/engine/events/events_task.rb +50 -0
- data/lib/splitclient-rb/engine/events/noop_events_queue.rb +13 -0
- data/lib/splitclient-rb/engine/fallback_treatment_calculator.rb +48 -0
- data/lib/splitclient-rb/engine/impressions/unique_keys_tracker.rb +58 -11
- 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 +78 -0
- data/lib/splitclient-rb/engine/models/evaluation_options.rb +9 -0
- data/lib/splitclient-rb/engine/models/event_active_subscriptions.rb +14 -0
- data/lib/splitclient-rb/engine/models/events_metadata.rb +10 -0
- data/lib/splitclient-rb/engine/models/fallback_treatment.rb +11 -0
- data/lib/splitclient-rb/engine/models/fallback_treatments_configuration.rb +36 -0
- data/lib/splitclient-rb/engine/models/label.rb +1 -0
- data/lib/splitclient-rb/engine/models/sdk_event.rb +4 -0
- data/lib/splitclient-rb/engine/models/sdk_event_type.rb +4 -0
- data/lib/splitclient-rb/engine/models/sdk_internal_event.rb +8 -0
- data/lib/splitclient-rb/engine/models/sdk_internal_event_notification.rb +14 -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/models/valid_sdk_event.rb +14 -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/status_manager.rb +7 -1
- data/lib/splitclient-rb/engine/sync_manager.rb +6 -6
- data/lib/splitclient-rb/engine/synchronizer.rb +37 -17
- data/lib/splitclient-rb/helpers/evaluator_helper.rb +37 -0
- data/lib/splitclient-rb/helpers/repository_helper.rb +39 -1
- data/lib/splitclient-rb/helpers/util.rb +12 -3
- data/lib/splitclient-rb/managers/split_manager.rb +2 -1
- data/lib/splitclient-rb/spec.rb +1 -1
- data/lib/splitclient-rb/split_config.rb +45 -5
- data/lib/splitclient-rb/split_factory.rb +27 -21
- data/lib/splitclient-rb/sse/event_source/client.rb +86 -29
- data/lib/splitclient-rb/sse/event_source/event_parser.rb +2 -2
- data/lib/splitclient-rb/sse/event_source/event_types.rb +1 -0
- data/lib/splitclient-rb/sse/notification_manager_keeper.rb +3 -3
- data/lib/splitclient-rb/sse/notification_processor.rb +3 -1
- data/lib/splitclient-rb/sse/workers/segments_worker.rb +4 -4
- data/lib/splitclient-rb/sse/workers/splits_worker.rb +63 -24
- data/lib/splitclient-rb/utilitites.rb +6 -10
- data/lib/splitclient-rb/validators.rb +56 -25
- data/lib/splitclient-rb/version.rb +1 -1
- data/lib/splitclient-rb.rb +22 -0
- data/sonar-project.properties +1 -0
- data/splitclient-rb.gemspec +3 -4
- metadata +32 -38
|
@@ -31,8 +31,11 @@ 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
|
-
def initialize(config, flag_sets_repository, flag_set_filter)
|
|
38
|
+
def initialize(config, flag_sets_repository, flag_set_filter, internal_events_queue)
|
|
36
39
|
super(config)
|
|
37
40
|
@tt_cache = {}
|
|
38
41
|
@adapter = case @config.cache_adapter.class.to_s
|
|
@@ -43,16 +46,26 @@ module SplitIoClient
|
|
|
43
46
|
end
|
|
44
47
|
@flag_sets = flag_sets_repository
|
|
45
48
|
@flag_set_filter = flag_set_filter
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
@adapter.initialize_map(namespace_key('.segments.registered'))
|
|
49
|
-
end
|
|
49
|
+
@internal_events_queue = internal_events_queue
|
|
50
|
+
initialize_keys
|
|
50
51
|
end
|
|
51
52
|
|
|
52
53
|
def update(to_add, to_delete, new_change_number)
|
|
53
54
|
to_add.each{ |feature_flag| add_feature_flag(feature_flag) }
|
|
54
55
|
to_delete.each{ |feature_flag| remove_feature_flag(feature_flag) }
|
|
55
56
|
set_change_number(new_change_number)
|
|
57
|
+
|
|
58
|
+
if to_add.length > 0 || to_delete.length > 0
|
|
59
|
+
@internal_events_queue.push(
|
|
60
|
+
SplitIoClient::Engine::Models::SdkInternalEventNotification.new(
|
|
61
|
+
SplitIoClient::Engine::Models::SdkInternalEvent::FLAGS_UPDATED,
|
|
62
|
+
SplitIoClient::Engine::Models::EventsMetadata.new(
|
|
63
|
+
SplitIoClient::Engine::Models::SdkEventType::FLAG_UPDATE,
|
|
64
|
+
to_add.map {|flag| flag[:name]} | to_delete.map {|flag| flag[:name]}
|
|
65
|
+
)
|
|
66
|
+
)
|
|
67
|
+
)
|
|
68
|
+
end
|
|
56
69
|
end
|
|
57
70
|
|
|
58
71
|
def get_split(name)
|
|
@@ -87,16 +100,16 @@ module SplitIoClient
|
|
|
87
100
|
|
|
88
101
|
# Return an array of Split Names excluding control keys like splits.till
|
|
89
102
|
def split_names
|
|
90
|
-
@adapter.find_strings_by_prefix(namespace_key(
|
|
91
|
-
.map { |split| split.gsub(namespace_key(
|
|
103
|
+
@adapter.find_strings_by_prefix(namespace_key(SPLIT_PREFIX))
|
|
104
|
+
.map { |split| split.gsub(namespace_key(SPLIT_PREFIX), '') }
|
|
92
105
|
end
|
|
93
106
|
|
|
94
107
|
def set_change_number(since)
|
|
95
|
-
@adapter.set_string(namespace_key(
|
|
108
|
+
@adapter.set_string(namespace_key(TILL_PREFIX), since)
|
|
96
109
|
end
|
|
97
110
|
|
|
98
111
|
def get_change_number
|
|
99
|
-
@adapter.string(namespace_key(
|
|
112
|
+
@adapter.string(namespace_key(TILL_PREFIX))
|
|
100
113
|
end
|
|
101
114
|
|
|
102
115
|
def set_segment_names(names)
|
|
@@ -112,21 +125,22 @@ module SplitIoClient
|
|
|
112
125
|
end
|
|
113
126
|
|
|
114
127
|
def ready?
|
|
115
|
-
@adapter.string(namespace_key(
|
|
128
|
+
@adapter.string(namespace_key(READY_PREFIX)).to_i != -1
|
|
116
129
|
end
|
|
117
130
|
|
|
118
131
|
def not_ready!
|
|
119
|
-
@adapter.set_string(namespace_key(
|
|
132
|
+
@adapter.set_string(namespace_key(READY_PREFIX), -1)
|
|
120
133
|
end
|
|
121
134
|
|
|
122
135
|
def ready!
|
|
123
|
-
@adapter.set_string(namespace_key(
|
|
136
|
+
@adapter.set_string(namespace_key(READY_PREFIX), Time.now.utc.to_i)
|
|
124
137
|
end
|
|
125
138
|
|
|
126
139
|
def clear
|
|
127
140
|
@tt_cache.clear
|
|
128
141
|
|
|
129
142
|
@adapter.clear(namespace_key)
|
|
143
|
+
initialize_keys
|
|
130
144
|
end
|
|
131
145
|
|
|
132
146
|
def kill(change_number, split_name, default_treatment)
|
|
@@ -139,6 +153,15 @@ module SplitIoClient
|
|
|
139
153
|
split[:changeNumber] = change_number
|
|
140
154
|
|
|
141
155
|
@adapter.set_string(namespace_key(".split.#{split_name}"), split.to_json)
|
|
156
|
+
@internal_events_queue.push(
|
|
157
|
+
SplitIoClient::Engine::Models::SdkInternalEventNotification.new(
|
|
158
|
+
SplitIoClient::Engine::Models::SdkInternalEvent::FLAG_KILLED_NOTIFICATION,
|
|
159
|
+
SplitIoClient::Engine::Models::EventsMetadata.new(
|
|
160
|
+
SplitIoClient::Engine::Models::SdkEventType::FLAG_UPDATE,
|
|
161
|
+
[split_name]
|
|
162
|
+
)
|
|
163
|
+
)
|
|
164
|
+
)
|
|
142
165
|
end
|
|
143
166
|
|
|
144
167
|
def splits_count
|
|
@@ -167,6 +190,13 @@ module SplitIoClient
|
|
|
167
190
|
|
|
168
191
|
private
|
|
169
192
|
|
|
193
|
+
def initialize_keys
|
|
194
|
+
unless @config.mode.equal?(:consumer)
|
|
195
|
+
@adapter.set_string(namespace_key(TILL_PREFIX), '-1')
|
|
196
|
+
@adapter.initialize_map(namespace_key('.segments.registered'))
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
|
|
170
200
|
def add_feature_flag(split)
|
|
171
201
|
return unless split[:name]
|
|
172
202
|
existing_split = get_split(split[:name])
|
|
@@ -37,15 +37,28 @@ module SplitIoClient
|
|
|
37
37
|
|
|
38
38
|
def current_impressions(feature_impressions)
|
|
39
39
|
feature_impressions.map do |impression|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
40
|
+
if impression[:i][:properties].nil?
|
|
41
|
+
impression = {
|
|
42
|
+
k: impression[:i][:k],
|
|
43
|
+
t: impression[:i][:t],
|
|
44
|
+
m: impression[:i][:m],
|
|
45
|
+
b: impression[:i][:b],
|
|
46
|
+
r: impression[:i][:r],
|
|
47
|
+
c: impression[:i][:c],
|
|
48
|
+
pt: impression[:i][:pt]
|
|
49
|
+
}
|
|
50
|
+
else
|
|
51
|
+
impression = {
|
|
52
|
+
k: impression[:i][:k],
|
|
53
|
+
t: impression[:i][:t],
|
|
54
|
+
m: impression[:i][:m],
|
|
55
|
+
b: impression[:i][:b],
|
|
56
|
+
r: impression[:i][:r],
|
|
57
|
+
c: impression[:i][:c],
|
|
58
|
+
pt: impression[:i][:pt],
|
|
59
|
+
properties: impression[:i][:properties].to_json.to_s
|
|
60
|
+
}
|
|
61
|
+
end
|
|
49
62
|
end
|
|
50
63
|
end
|
|
51
64
|
|
|
@@ -73,7 +86,8 @@ module SplitIoClient
|
|
|
73
86
|
"#{impression[:i][:b]}:" \
|
|
74
87
|
"#{impression[:i][:c]}:" \
|
|
75
88
|
"#{impression[:i][:t]}:" \
|
|
76
|
-
"#{impression[:i][:pt]}"
|
|
89
|
+
"#{impression[:i][:pt]}" \
|
|
90
|
+
"#{impression[:i][:properties]}" \
|
|
77
91
|
end
|
|
78
92
|
end
|
|
79
93
|
end
|
|
@@ -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
|
|
|
@@ -18,10 +18,11 @@ module SplitIoClient
|
|
|
18
18
|
# @param sdk_key [String] the SDK key for your split account
|
|
19
19
|
#
|
|
20
20
|
# @return [SplitIoClient] split.io client instance
|
|
21
|
-
def initialize(sdk_key, repositories, status_manager, config, impressions_manager, telemetry_evaluation_producer, evaluator, split_validator)
|
|
21
|
+
def initialize(sdk_key, repositories, status_manager, config, impressions_manager, telemetry_evaluation_producer, evaluator, split_validator, fallback_treatment_calculator, events_manager)
|
|
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
|
|
@@ -31,26 +32,33 @@ module SplitIoClient
|
|
|
31
32
|
@telemetry_evaluation_producer = telemetry_evaluation_producer
|
|
32
33
|
@split_validator = split_validator
|
|
33
34
|
@evaluator = evaluator
|
|
35
|
+
@fallback_treatment_calculator = fallback_treatment_calculator
|
|
36
|
+
@events_manager = events_manager
|
|
34
37
|
end
|
|
35
38
|
|
|
36
39
|
def get_treatment(
|
|
37
|
-
key, split_name, attributes = {}, split_data = nil, store_impressions =
|
|
40
|
+
key, split_name, attributes = {}, evaluation_options = nil, split_data = nil, store_impressions = nil,
|
|
38
41
|
multiple = false, evaluator = nil
|
|
39
42
|
)
|
|
40
|
-
|
|
43
|
+
log_deprecated_warning(GET_TREATMENT, evaluator, 'evaluator')
|
|
44
|
+
|
|
45
|
+
result = treatment(key, split_name, attributes, split_data, store_impressions, GET_TREATMENT, multiple, evaluation_options)
|
|
41
46
|
return result.tap { |t| t.delete(:config) } if multiple
|
|
42
47
|
result[:treatment]
|
|
43
48
|
end
|
|
44
49
|
|
|
45
50
|
def get_treatment_with_config(
|
|
46
|
-
key, split_name, attributes = {}, split_data = nil, store_impressions =
|
|
51
|
+
key, split_name, attributes = {}, evaluation_options = nil, split_data = nil, store_impressions = nil,
|
|
47
52
|
multiple = false, evaluator = nil
|
|
48
53
|
)
|
|
49
|
-
|
|
54
|
+
log_deprecated_warning(GET_TREATMENT, evaluator, 'evaluator')
|
|
55
|
+
result = treatment(key, split_name, attributes, split_data, store_impressions, GET_TREATMENT_WITH_CONFIG, multiple, evaluation_options)
|
|
56
|
+
|
|
57
|
+
{ :config => result[:config], :treatment => result[:treatment] }
|
|
50
58
|
end
|
|
51
59
|
|
|
52
|
-
def get_treatments(key, split_names, attributes = {})
|
|
53
|
-
treatments = treatments(key, split_names, attributes)
|
|
60
|
+
def get_treatments(key, split_names, attributes = {}, evaluation_options = nil)
|
|
61
|
+
treatments = treatments(key, split_names, attributes, evaluation_options)
|
|
54
62
|
|
|
55
63
|
return treatments if treatments.nil?
|
|
56
64
|
keys = treatments.keys
|
|
@@ -58,51 +66,63 @@ module SplitIoClient
|
|
|
58
66
|
Hash[keys.zip(treats)]
|
|
59
67
|
end
|
|
60
68
|
|
|
61
|
-
def get_treatments_with_config(key, split_names, attributes = {})
|
|
62
|
-
treatments(key, split_names, attributes, GET_TREATMENTS_WITH_CONFIG)
|
|
69
|
+
def get_treatments_with_config(key, split_names, attributes = {}, evaluation_options = nil)
|
|
70
|
+
results = treatments(key, split_names, attributes, evaluation_options, GET_TREATMENTS_WITH_CONFIG)
|
|
71
|
+
|
|
72
|
+
results.map{|key, value|
|
|
73
|
+
[key, { treatment: value[:treatment], config: value[:config] }]
|
|
74
|
+
}.to_h
|
|
63
75
|
end
|
|
64
76
|
|
|
65
|
-
def get_treatments_by_flag_set(key, flag_set, attributes = {})
|
|
77
|
+
def get_treatments_by_flag_set(key, flag_set, attributes = {}, evaluation_options = nil)
|
|
66
78
|
valid_flag_set = @split_validator.valid_flag_sets(GET_TREATMENTS_BY_FLAG_SET, [flag_set])
|
|
67
79
|
split_names = @splits_repository.get_feature_flags_by_sets(valid_flag_set)
|
|
68
|
-
treatments = treatments(key, split_names, attributes, GET_TREATMENTS_BY_FLAG_SET)
|
|
80
|
+
treatments = treatments(key, split_names, attributes, evaluation_options, GET_TREATMENTS_BY_FLAG_SET)
|
|
69
81
|
return treatments if treatments.nil?
|
|
70
82
|
keys = treatments.keys
|
|
71
83
|
treats = treatments.map { |_,t| t[:treatment] }
|
|
72
84
|
Hash[keys.zip(treats)]
|
|
73
85
|
end
|
|
74
86
|
|
|
75
|
-
def get_treatments_by_flag_sets(key, flag_sets, attributes = {})
|
|
87
|
+
def get_treatments_by_flag_sets(key, flag_sets, attributes = {}, evaluation_options = nil)
|
|
76
88
|
valid_flag_set = @split_validator.valid_flag_sets(GET_TREATMENTS_BY_FLAG_SETS, flag_sets)
|
|
77
89
|
split_names = @splits_repository.get_feature_flags_by_sets(valid_flag_set)
|
|
78
|
-
treatments = treatments(key, split_names, attributes, GET_TREATMENTS_BY_FLAG_SETS)
|
|
90
|
+
treatments = treatments(key, split_names, attributes, evaluation_options, GET_TREATMENTS_BY_FLAG_SETS)
|
|
79
91
|
return treatments if treatments.nil?
|
|
80
92
|
keys = treatments.keys
|
|
81
93
|
treats = treatments.map { |_,t| t[:treatment] }
|
|
82
94
|
Hash[keys.zip(treats)]
|
|
83
95
|
end
|
|
84
96
|
|
|
85
|
-
def get_treatments_with_config_by_flag_set(key, flag_set, attributes = {})
|
|
97
|
+
def get_treatments_with_config_by_flag_set(key, flag_set, attributes = {}, evaluation_options = nil)
|
|
86
98
|
valid_flag_set = @split_validator.valid_flag_sets(GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SET, [flag_set])
|
|
87
99
|
split_names = @splits_repository.get_feature_flags_by_sets(valid_flag_set)
|
|
88
|
-
treatments(key, split_names, attributes, GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SET)
|
|
100
|
+
results = treatments(key, split_names, attributes, evaluation_options, GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SET)
|
|
101
|
+
|
|
102
|
+
results.map{|key, value|
|
|
103
|
+
[key, { treatment: value[:treatment], config: value[:config] }]
|
|
104
|
+
}.to_h
|
|
89
105
|
end
|
|
90
106
|
|
|
91
|
-
def get_treatments_with_config_by_flag_sets(key, flag_sets, attributes = {})
|
|
107
|
+
def get_treatments_with_config_by_flag_sets(key, flag_sets, attributes = {}, evaluation_options = nil)
|
|
92
108
|
valid_flag_set = @split_validator.valid_flag_sets(GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SETS, flag_sets)
|
|
93
109
|
split_names = @splits_repository.get_feature_flags_by_sets(valid_flag_set)
|
|
94
|
-
treatments(key, split_names, attributes, GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SETS)
|
|
110
|
+
results = treatments(key, split_names, attributes, evaluation_options, GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SETS)
|
|
111
|
+
|
|
112
|
+
results.map{|key, value|
|
|
113
|
+
[key, { treatment: value[:treatment], config: value[:config] }]
|
|
114
|
+
}.to_h
|
|
95
115
|
end
|
|
96
116
|
|
|
97
117
|
def destroy
|
|
98
118
|
@config.logger.info('Split client shutdown started...') if @config.debug_enabled
|
|
99
119
|
if !@config.cache_adapter.is_a?(SplitIoClient::Cache::Adapters::RedisAdapter) && @config.impressions_mode != :none &&
|
|
100
120
|
(!@impressions_repository.empty? || !@events_repository.empty?)
|
|
101
|
-
@config.logger.debug("Impressions and/or Events cache is not empty")
|
|
121
|
+
@config.logger.debug("Impressions and/or Events cache is not empty") if @config.debug_enabled
|
|
102
122
|
# Adding small delay to ensure sender threads are fully running
|
|
103
123
|
sleep(0.1)
|
|
104
124
|
if !@config.threads.key?(:impressions_sender) || !@config.threads.key?(:events_sender)
|
|
105
|
-
@config.logger.debug("Periodic data recording thread has not started yet, waiting for service startup.")
|
|
125
|
+
@config.logger.debug("Periodic data recording thread has not started yet, waiting for service startup.") if @config.debug_enabled
|
|
106
126
|
@config.threads[:start_sdk].join(5) if @config.threads.key?(:start_sdk)
|
|
107
127
|
end
|
|
108
128
|
end
|
|
@@ -115,6 +135,7 @@ module SplitIoClient
|
|
|
115
135
|
|
|
116
136
|
@splits_repository.clear
|
|
117
137
|
@segments_repository.clear
|
|
138
|
+
@rule_based_segments_repository.clear
|
|
118
139
|
|
|
119
140
|
SplitIoClient.load_factory_registry
|
|
120
141
|
SplitIoClient.split_factory_registry.remove(@api_key)
|
|
@@ -133,10 +154,7 @@ module SplitIoClient
|
|
|
133
154
|
if !properties.nil?
|
|
134
155
|
properties, size = validate_properties(properties)
|
|
135
156
|
properties_size += size
|
|
136
|
-
|
|
137
|
-
@config.logger.error("The maximum size allowed for the properties is #{EVENTS_SIZE_THRESHOLD}. Current is #{properties_size}. Event not queued")
|
|
138
|
-
return false
|
|
139
|
-
end
|
|
157
|
+
return false unless check_properties_size(properties_size)
|
|
140
158
|
end
|
|
141
159
|
|
|
142
160
|
if ready? && !@config.localhost_mode && !@splits_repository.traffic_type_exists(traffic_type_name)
|
|
@@ -159,8 +177,24 @@ module SplitIoClient
|
|
|
159
177
|
@status_manager.wait_until_ready(time) if @status_manager
|
|
160
178
|
end
|
|
161
179
|
|
|
180
|
+
def register(sdk_event, handler)
|
|
181
|
+
@events_manager.register(sdk_event, handler)
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def unregister(sdk_event, handler)
|
|
185
|
+
@events_manager.unregister(sdk_event)
|
|
186
|
+
end
|
|
187
|
+
|
|
162
188
|
private
|
|
163
189
|
|
|
190
|
+
def check_properties_size(properties_size, msg = "Event not queued")
|
|
191
|
+
if (properties_size > EVENTS_SIZE_THRESHOLD)
|
|
192
|
+
@config.logger.error("The maximum size allowed for the properties is #{EVENTS_SIZE_THRESHOLD}. Current is #{properties_size}. #{msg}")
|
|
193
|
+
return false
|
|
194
|
+
end
|
|
195
|
+
return true
|
|
196
|
+
end
|
|
197
|
+
|
|
164
198
|
def keys_from_key(key)
|
|
165
199
|
case key
|
|
166
200
|
when Hash
|
|
@@ -204,7 +238,7 @@ module SplitIoClient
|
|
|
204
238
|
end
|
|
205
239
|
end
|
|
206
240
|
|
|
207
|
-
def validate_properties(properties)
|
|
241
|
+
def validate_properties(properties, method = 'Event')
|
|
208
242
|
properties_count = 0
|
|
209
243
|
size = 0
|
|
210
244
|
|
|
@@ -223,11 +257,21 @@ module SplitIoClient
|
|
|
223
257
|
end
|
|
224
258
|
}
|
|
225
259
|
|
|
226
|
-
@config.logger.warn(
|
|
260
|
+
@config.logger.warn("#{method} has more than 300 properties. Some of them will be trimmed when processed") if properties_count > 300
|
|
227
261
|
|
|
228
262
|
return fixed_properties, size
|
|
229
263
|
end
|
|
230
264
|
|
|
265
|
+
def validate_evaluation_options(evaluation_options)
|
|
266
|
+
if !evaluation_options.is_a?(SplitIoClient::Engine::Models::EvaluationOptions)
|
|
267
|
+
@config.logger.warn("Option #{evaluation_options} should be a EvaluationOptions type. Setting value to nil")
|
|
268
|
+
return nil, 0
|
|
269
|
+
end
|
|
270
|
+
evaluation_options.properties = evaluation_options.properties.transform_keys(&:to_sym)
|
|
271
|
+
evaluation_options.properties, size = validate_properties(evaluation_options.properties, 'Treatment')
|
|
272
|
+
return evaluation_options, size
|
|
273
|
+
end
|
|
274
|
+
|
|
231
275
|
def valid_client
|
|
232
276
|
if @destroyed
|
|
233
277
|
@config.logger.error('Client has already been destroyed - no calls possible')
|
|
@@ -236,8 +280,7 @@ module SplitIoClient
|
|
|
236
280
|
@config.valid_mode
|
|
237
281
|
end
|
|
238
282
|
|
|
239
|
-
def treatments(key, feature_flag_names, attributes = {}, calling_method = 'get_treatments')
|
|
240
|
-
attributes = {} if attributes.nil?
|
|
283
|
+
def treatments(key, feature_flag_names, attributes = {}, evaluation_options = nil, calling_method = 'get_treatments')
|
|
241
284
|
sanitized_feature_flag_names = sanitize_split_names(calling_method, feature_flag_names)
|
|
242
285
|
|
|
243
286
|
if sanitized_feature_flag_names.nil?
|
|
@@ -253,11 +296,12 @@ module SplitIoClient
|
|
|
253
296
|
bucketing_key, matching_key = keys_from_key(key)
|
|
254
297
|
bucketing_key = bucketing_key ? bucketing_key.to_s : nil
|
|
255
298
|
matching_key = matching_key ? matching_key.to_s : nil
|
|
299
|
+
attributes = parsed_attributes(attributes)
|
|
256
300
|
|
|
257
301
|
if !@config.split_validator.valid_get_treatments_parameters(calling_method, key, sanitized_feature_flag_names, matching_key, bucketing_key, attributes)
|
|
258
302
|
to_return = Hash.new
|
|
259
303
|
sanitized_feature_flag_names.each {|name|
|
|
260
|
-
to_return[name.to_sym] =
|
|
304
|
+
to_return[name.to_sym] = check_fallback_treatment(name, '')
|
|
261
305
|
}
|
|
262
306
|
return to_return
|
|
263
307
|
end
|
|
@@ -266,8 +310,12 @@ module SplitIoClient
|
|
|
266
310
|
impressions = []
|
|
267
311
|
to_return = Hash.new
|
|
268
312
|
sanitized_feature_flag_names.each {|name|
|
|
269
|
-
|
|
270
|
-
|
|
313
|
+
treatment_data = check_fallback_treatment(name, Engine::Models::Label::NOT_READY)
|
|
314
|
+
to_return[name.to_sym] = treatment_data
|
|
315
|
+
|
|
316
|
+
impressions << { :impression => @impressions_manager.build_impression(matching_key, bucketing_key, name.to_sym,
|
|
317
|
+
get_treatment_without_config(treatment_data), false, { attributes: attributes, time: nil },
|
|
318
|
+
evaluation_options), :disabled => false }
|
|
271
319
|
}
|
|
272
320
|
@impressions_manager.track(impressions)
|
|
273
321
|
return to_return
|
|
@@ -278,7 +326,6 @@ module SplitIoClient
|
|
|
278
326
|
valid_feature_flag_names << feature_flag_name unless feature_flag_name.nil?
|
|
279
327
|
}
|
|
280
328
|
start = Time.now
|
|
281
|
-
impressions_total = []
|
|
282
329
|
|
|
283
330
|
feature_flags = @splits_repository.splits(valid_feature_flag_names)
|
|
284
331
|
treatments = Hash.new
|
|
@@ -287,19 +334,18 @@ module SplitIoClient
|
|
|
287
334
|
if feature_flag.nil?
|
|
288
335
|
@config.logger.warn("#{calling_method}: you passed #{key} that " \
|
|
289
336
|
'does not exist in this environment, please double check what feature flags exist in the Split user interface')
|
|
290
|
-
invalid_treatments[key] =
|
|
337
|
+
invalid_treatments[key] = check_fallback_treatment(key, Engine::Models::Label::NOT_FOUND)
|
|
291
338
|
next
|
|
292
339
|
end
|
|
293
|
-
treatments_labels_change_numbers, impressions = evaluate_treatment(feature_flag, key, bucketing_key, matching_key, attributes, calling_method)
|
|
294
|
-
impressions_total.concat(impressions) unless impressions.nil?
|
|
340
|
+
treatments_labels_change_numbers, impressions = evaluate_treatment(feature_flag, key, bucketing_key, matching_key, attributes, calling_method, false, evaluation_options)
|
|
295
341
|
treatments[key] =
|
|
296
342
|
{
|
|
297
343
|
treatment: treatments_labels_change_numbers[:treatment],
|
|
298
344
|
config: treatments_labels_change_numbers[:config]
|
|
299
345
|
}
|
|
346
|
+
@impressions_manager.track(impressions) unless impressions.empty?
|
|
300
347
|
end
|
|
301
348
|
record_latency(calling_method, start)
|
|
302
|
-
@impressions_manager.track(impressions_total) unless impressions_total.empty?
|
|
303
349
|
|
|
304
350
|
treatments.merge(invalid_treatments)
|
|
305
351
|
end
|
|
@@ -313,14 +359,18 @@ module SplitIoClient
|
|
|
313
359
|
# @param split_data [Hash] split data, when provided this method doesn't fetch splits_repository for the data
|
|
314
360
|
# @param store_impressions [Boolean] impressions aren't stored if this flag is false
|
|
315
361
|
# @return [String/Hash] Treatment as String or Hash of treatments in case of array of features
|
|
316
|
-
def treatment(key, feature_flag_name, attributes = {}, split_data = nil, store_impressions =
|
|
317
|
-
calling_method = 'get_treatment', multiple = false)
|
|
362
|
+
def treatment(key, feature_flag_name, attributes = {}, split_data = nil, store_impressions = nil,
|
|
363
|
+
calling_method = 'get_treatment', multiple = false, evaluation_options = nil)
|
|
364
|
+
|
|
365
|
+
log_deprecated_warning(calling_method, split_data, 'split_data')
|
|
366
|
+
log_deprecated_warning(calling_method, store_impressions, 'store_impressions')
|
|
367
|
+
|
|
318
368
|
impressions = []
|
|
319
369
|
bucketing_key, matching_key = keys_from_key(key)
|
|
320
370
|
|
|
321
371
|
attributes = parsed_attributes(attributes)
|
|
322
372
|
|
|
323
|
-
return parsed_treatment(
|
|
373
|
+
return parsed_treatment(check_fallback_treatment(feature_flag_name, ""), multiple) unless valid_client && @config.split_validator.valid_get_treatment_parameters(calling_method, key, feature_flag_name, matching_key, bucketing_key, attributes)
|
|
324
374
|
|
|
325
375
|
bucketing_key = bucketing_key ? bucketing_key.to_s : nil
|
|
326
376
|
matching_key = matching_key.to_s
|
|
@@ -332,54 +382,54 @@ module SplitIoClient
|
|
|
332
382
|
end
|
|
333
383
|
|
|
334
384
|
feature_flag = @splits_repository.get_split(feature_flag_name)
|
|
335
|
-
treatments,
|
|
385
|
+
treatments, impressions_decorator = evaluate_treatment(feature_flag, feature_flag_name, bucketing_key, matching_key, attributes, calling_method, multiple, evaluation_options)
|
|
336
386
|
|
|
337
|
-
@impressions_manager.track(
|
|
387
|
+
@impressions_manager.track(impressions_decorator) unless impressions_decorator.nil?
|
|
338
388
|
treatments
|
|
339
389
|
end
|
|
340
390
|
|
|
341
|
-
def
|
|
342
|
-
|
|
391
|
+
def log_deprecated_warning(calling_method, parameter, parameter_name)
|
|
392
|
+
@config.logger.warn("#{calling_method}: detected #{parameter_name} parameter used, this parameter is deprecated and its value is ignored.") unless parameter.nil?
|
|
393
|
+
end
|
|
394
|
+
|
|
395
|
+
def evaluate_treatment(feature_flag, feature_flag_name, bucketing_key, matching_key, attributes, calling_method, multiple = false, evaluation_options = nil)
|
|
396
|
+
impressions_decorator = []
|
|
343
397
|
begin
|
|
344
398
|
start = Time.now
|
|
345
399
|
if feature_flag.nil? && ready?
|
|
346
400
|
@config.logger.warn("#{calling_method}: you passed #{feature_flag_name} that " \
|
|
347
401
|
'does not exist in this environment, please double check what feature flags exist in the Split user interface')
|
|
348
|
-
return
|
|
402
|
+
return check_fallback_treatment(feature_flag_name, Engine::Models::Label::NOT_FOUND), nil
|
|
349
403
|
end
|
|
350
|
-
|
|
404
|
+
|
|
351
405
|
if !feature_flag.nil? && ready?
|
|
352
|
-
@evaluator.evaluate_feature_flag(
|
|
406
|
+
treatment_data = @evaluator.evaluate_feature_flag(
|
|
353
407
|
{ bucketing_key: bucketing_key, matching_key: matching_key }, feature_flag, attributes
|
|
354
408
|
)
|
|
409
|
+
impressions_disabled = feature_flag[:impressionsDisabled]
|
|
355
410
|
else
|
|
356
411
|
@config.logger.error("#{calling_method}: the SDK is not ready, results may be incorrect for feature flag #{feature_flag_name}. Make sure to wait for SDK readiness before using this method.")
|
|
357
|
-
|
|
412
|
+
treatment_data = check_fallback_treatment(feature_flag_name, Engine::Models::Label::NOT_READY)
|
|
413
|
+
impressions_disabled = false
|
|
358
414
|
end
|
|
359
415
|
|
|
416
|
+
evaluation_options, size = validate_evaluation_options(evaluation_options) unless evaluation_options.nil?
|
|
417
|
+
evaluation_options.properties = nil unless evaluation_options.nil? or check_properties_size((EVENT_AVERAGE_SIZE + size), "Properties are ignored")
|
|
418
|
+
|
|
360
419
|
record_latency(calling_method, start)
|
|
361
|
-
|
|
362
|
-
|
|
420
|
+
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 }
|
|
421
|
+
impressions_decorator << impression_decorator unless impression_decorator.nil?
|
|
363
422
|
rescue StandardError => e
|
|
364
423
|
@config.log_found_exception(__method__.to_s, e)
|
|
365
|
-
|
|
366
424
|
record_exception(calling_method)
|
|
425
|
+
treatment_data = check_fallback_treatment(feature_flag_name, Engine::Models::Label::EXCEPTION)
|
|
426
|
+
impression_decorator = { :impression => @impressions_manager.build_impression(matching_key, bucketing_key, feature_flag_name, get_treatment_without_config(treatment_data), false, { attributes: attributes, time: nil }, evaluation_options), :disabled => false }
|
|
367
427
|
|
|
368
|
-
|
|
369
|
-
impressions << impression unless impression.nil?
|
|
428
|
+
impressions_decorator << impression_decorator unless impression_decorator.nil?
|
|
370
429
|
|
|
371
|
-
return parsed_treatment(
|
|
430
|
+
return parsed_treatment(treatment_data, multiple), impressions_decorator
|
|
372
431
|
end
|
|
373
|
-
|
|
374
|
-
return parsed_treatment(treatment_data, multiple), impressions
|
|
375
|
-
end
|
|
376
|
-
|
|
377
|
-
def control_treatment
|
|
378
|
-
{ treatment: Engine::Models::Treatment::CONTROL }
|
|
379
|
-
end
|
|
380
|
-
|
|
381
|
-
def control_treatment_with_config
|
|
382
|
-
{:treatment => Engine::Models::Treatment::CONTROL, :config => nil}
|
|
432
|
+
return parsed_treatment(treatment_data, multiple), impressions_decorator
|
|
383
433
|
end
|
|
384
434
|
|
|
385
435
|
def variable_size(value)
|
|
@@ -442,5 +492,39 @@ module SplitIoClient
|
|
|
442
492
|
@telemetry_evaluation_producer.record_exception(Telemetry::Domain::Constants::TRACK)
|
|
443
493
|
end
|
|
444
494
|
end
|
|
495
|
+
|
|
496
|
+
def check_fallback_treatment(feature_name, label)
|
|
497
|
+
return {
|
|
498
|
+
label: (label != '')? label : nil,
|
|
499
|
+
treatment: Engine::Models::Treatment::CONTROL,
|
|
500
|
+
config: nil,
|
|
501
|
+
change_number: nil
|
|
502
|
+
} unless feature_name.is_a?(Symbol) || feature_name.is_a?(String)
|
|
503
|
+
|
|
504
|
+
fallback_treatment = @fallback_treatment_calculator.resolve(feature_name.to_sym, label)
|
|
505
|
+
|
|
506
|
+
{
|
|
507
|
+
label: (label != '')? fallback_treatment.label : nil,
|
|
508
|
+
treatment: fallback_treatment.treatment,
|
|
509
|
+
config: get_fallback_config(fallback_treatment),
|
|
510
|
+
change_number: nil
|
|
511
|
+
}
|
|
512
|
+
end
|
|
513
|
+
|
|
514
|
+
def get_treatment_without_config(treatment)
|
|
515
|
+
{
|
|
516
|
+
label: treatment[:label],
|
|
517
|
+
treatment: treatment[:treatment],
|
|
518
|
+
}
|
|
519
|
+
end
|
|
520
|
+
|
|
521
|
+
def get_fallback_config(fallback_treatment)
|
|
522
|
+
if fallback_treatment.config != nil
|
|
523
|
+
return fallback_treatment.config
|
|
524
|
+
end
|
|
525
|
+
|
|
526
|
+
return nil
|
|
527
|
+
end
|
|
528
|
+
|
|
445
529
|
end
|
|
446
530
|
end
|
|
@@ -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
|