prefab-cloud-ruby 0.23.8 → 0.24.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +10 -8
- data/VERSION +1 -1
- data/lib/prefab/client.rb +55 -21
- data/lib/prefab/config_client.rb +13 -25
- data/lib/prefab/config_resolver.rb +28 -28
- data/lib/prefab/config_value_unwrapper.rb +8 -5
- data/lib/prefab/context.rb +119 -0
- data/lib/prefab/criteria_evaluator.rb +23 -17
- data/lib/prefab/feature_flag_client.rb +7 -7
- data/lib/prefab/local_config_parser.rb +2 -17
- data/lib/prefab/logger_client.rb +3 -6
- data/lib/prefab/resolved_config_presenter.rb +84 -0
- data/lib/prefab/weighted_value_resolver.rb +4 -4
- data/lib/prefab-cloud-ruby.rb +6 -0
- data/prefab-cloud-ruby.gemspec +6 -3
- data/test/.prefab.unit_tests.config.yaml +3 -2
- data/test/integration_test.rb +4 -8
- data/test/test_client.rb +28 -27
- data/test/test_config_resolver.rb +106 -54
- data/test/test_config_value_unwrapper.rb +15 -15
- data/test/test_context.rb +158 -0
- data/test/test_criteria_evaluator.rb +93 -78
- data/test/test_feature_flag_client.rb +14 -20
- data/test/test_helper.rb +1 -1
- data/test/test_integration.rb +30 -14
- data/test/test_local_config_parser.rb +6 -8
- data/test/test_log_path_collector.rb +4 -7
- data/test/test_logger.rb +12 -12
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 54b6a2d9575b6e75d0fffe8b209e700da2a026ec33125819152fae208e1c489f
|
4
|
+
data.tar.gz: 33b150761861978aacebdb996bf8da37e9000743fab6a64f4ed11ca7dbd64b51
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e1be3cf89ba752245325c07e0b04e3db6f4dec5670e08b14e9b53b588b6ea0a1d801a595465a2ae49d83a8f82cdddc316c1434c8a079c3a7ca87c1fce19c191c
|
7
|
+
data.tar.gz: c19b9ff06a9735adab6f320d52be3349e8600773cbcc3fd40b07b3be0f8a0af0329e5ae9fd975978cfb1ff199577943ff7e5b458f5078c5a9f0cf182a2a8a8bd
|
data/README.md
CHANGED
@@ -5,18 +5,20 @@ Ruby Client for Prefab Feature Flags, Dynamic log levels, and Config as a Servic
|
|
5
5
|
```ruby
|
6
6
|
client = Prefab::Client.new
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
8
|
+
context = {
|
9
|
+
user: {
|
10
|
+
team_id: 432,
|
11
|
+
id: 123,
|
12
|
+
subscription_level: 'pro',
|
13
|
+
email: "alice@example.com"
|
14
|
+
}
|
14
15
|
}
|
15
16
|
|
16
|
-
result = client.enabled? "my-first-feature-flag",
|
17
|
+
result = client.enabled? "my-first-feature-flag", context
|
17
18
|
|
18
|
-
puts "my-first-feature-flag is: #{result}
|
19
|
+
puts "my-first-feature-flag is: #{result}"
|
19
20
|
```
|
21
|
+
|
20
22
|
See full documentation https://docs.prefab.cloud/docs/ruby-sdk/ruby
|
21
23
|
|
22
24
|
## Supports
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.24.0
|
data/lib/prefab/client.rb
CHANGED
@@ -6,16 +6,8 @@ module Prefab
|
|
6
6
|
class Client
|
7
7
|
MAX_SLEEP_SEC = 10
|
8
8
|
BASE_SLEEP_SEC = 0.5
|
9
|
-
NO_DEFAULT_PROVIDED = :no_default_provided
|
10
9
|
|
11
|
-
attr_reader :shared_cache
|
12
|
-
attr_reader :stats
|
13
|
-
attr_reader :namespace
|
14
|
-
attr_reader :interceptor
|
15
|
-
attr_reader :api_key
|
16
|
-
attr_reader :prefab_api_url
|
17
|
-
attr_reader :options
|
18
|
-
attr_reader :instance_hash
|
10
|
+
attr_reader :shared_cache, :stats, :namespace, :interceptor, :api_key, :prefab_api_url, :options, :instance_hash
|
19
11
|
|
20
12
|
def initialize(options = Prefab::Options.new)
|
21
13
|
@options = options.is_a?(Prefab::Options) ? options : Prefab::Options.new(options)
|
@@ -34,18 +26,23 @@ module Prefab
|
|
34
26
|
@prefab_api_url = @options.prefab_api_url
|
35
27
|
log_internal ::Logger::INFO, "Prefab Connecting to: #{@prefab_api_url}"
|
36
28
|
end
|
29
|
+
|
30
|
+
context.clear
|
37
31
|
# start config client
|
38
32
|
config_client
|
39
33
|
end
|
40
34
|
|
41
|
-
def with_log_context(
|
42
|
-
|
43
|
-
|
35
|
+
def with_log_context(_lookup_key, properties, &block)
|
36
|
+
warn '[DEPRECATION] `$prefab.with_log_context` is deprecated. Please use `with_context` instead.'
|
37
|
+
with_context(properties, &block)
|
38
|
+
end
|
44
39
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
40
|
+
def with_context(properties, &block)
|
41
|
+
Prefab::Context.with_context(properties, &block)
|
42
|
+
end
|
43
|
+
|
44
|
+
def context
|
45
|
+
Prefab::Context.current
|
49
46
|
end
|
50
47
|
|
51
48
|
def config_client(timeout: 5.0)
|
@@ -78,19 +75,27 @@ module Prefab
|
|
78
75
|
ActiveStorage.logger = log if defined?(ActiveStorage)
|
79
76
|
end
|
80
77
|
|
78
|
+
def on_update(&block)
|
79
|
+
resolver.on_update(&block)
|
80
|
+
end
|
81
|
+
|
81
82
|
def log_internal(level, msg, path = nil)
|
82
83
|
log.log_internal msg, path, nil, level
|
83
84
|
end
|
84
85
|
|
85
|
-
def enabled?(feature_name, lookup_key =
|
86
|
-
|
86
|
+
def enabled?(feature_name, lookup_key = NO_DEFAULT_PROVIDED, properties = NO_DEFAULT_PROVIDED)
|
87
|
+
_, properties = handle_positional_arguments(lookup_key, properties, :enabled?)
|
88
|
+
|
89
|
+
feature_flag_client.feature_is_on_for?(feature_name, properties)
|
87
90
|
end
|
88
91
|
|
89
|
-
def get(key, default_or_lookup_key = NO_DEFAULT_PROVIDED, properties =
|
92
|
+
def get(key, default_or_lookup_key = NO_DEFAULT_PROVIDED, properties = NO_DEFAULT_PROVIDED, ff_default = nil)
|
93
|
+
default, properties = handle_positional_arguments(default_or_lookup_key, properties, :get)
|
94
|
+
|
90
95
|
if is_ff?(key)
|
91
|
-
feature_flag_client.get(key,
|
96
|
+
feature_flag_client.get(key, properties, default: ff_default)
|
92
97
|
else
|
93
|
-
config_client.get(key,
|
98
|
+
config_client.get(key, default, properties)
|
94
99
|
end
|
95
100
|
end
|
96
101
|
|
@@ -98,6 +103,14 @@ module Prefab
|
|
98
103
|
Prefab::HttpConnection.new(@options.prefab_api_url, @api_key).post(path, body)
|
99
104
|
end
|
100
105
|
|
106
|
+
def inspect
|
107
|
+
"#<Prefab::Client:#{object_id} namespace=#{namespace}>"
|
108
|
+
end
|
109
|
+
|
110
|
+
def resolver
|
111
|
+
config_client.resolver
|
112
|
+
end
|
113
|
+
|
101
114
|
private
|
102
115
|
|
103
116
|
def is_ff?(key)
|
@@ -105,5 +118,26 @@ module Prefab
|
|
105
118
|
|
106
119
|
raw && raw.allowable_values.any?
|
107
120
|
end
|
121
|
+
|
122
|
+
# The goal here is to ease transition from the old API to the new one. The
|
123
|
+
# old API had a lookup_key parameter that is deprecated. This method
|
124
|
+
# handles the transition by checking if the first parameter is a string and
|
125
|
+
# if so, it is assumed to be the lookup_key and a deprecation warning is
|
126
|
+
# issued and we know the second argument is the properties. If the first
|
127
|
+
# parameter is a hash, you're on the new API and no further action is
|
128
|
+
# required.
|
129
|
+
def handle_positional_arguments(lookup_key, properties, method)
|
130
|
+
# handle JIT context
|
131
|
+
if lookup_key.is_a?(Hash) && properties == NO_DEFAULT_PROVIDED
|
132
|
+
properties = lookup_key
|
133
|
+
lookup_key = nil
|
134
|
+
end
|
135
|
+
|
136
|
+
if lookup_key.is_a?(String)
|
137
|
+
warn "[DEPRECATION] `$prefab.#{method}`'s lookup_key argument is deprecated. Please use remove it or use context instead."
|
138
|
+
end
|
139
|
+
|
140
|
+
[lookup_key, properties]
|
141
|
+
end
|
108
142
|
end
|
109
143
|
end
|
data/lib/prefab/config_client.rb
CHANGED
@@ -41,36 +41,24 @@ module Prefab
|
|
41
41
|
end
|
42
42
|
end
|
43
43
|
|
44
|
-
def upsert(key, config_value, namespace = nil, previous_key = nil)
|
45
|
-
raise "Key must not contain ':' set namespaces separately" if key.include? ':'
|
46
|
-
raise "Namespace must not contain ':'" if namespace&.include?(':')
|
47
|
-
|
48
|
-
config_delta = Prefab::ConfigClient.value_to_delta(key, config_value, namespace)
|
49
|
-
upsert_req = Prefab::UpsertRequest.new(config_delta: config_delta)
|
50
|
-
upsert_req.previous_key = previous_key if previous_key&.present?
|
51
|
-
|
52
|
-
@base_client.request Prefab::ConfigService, :upsert, req_options: { timeout: @timeout }, params: upsert_req
|
53
|
-
@base_client.stats.increment('prefab.config.upsert')
|
54
|
-
@config_loader.set(config_delta, :upsert)
|
55
|
-
@config_loader.rm(previous_key) if previous_key&.present?
|
56
|
-
@config_resolver.update
|
57
|
-
end
|
58
|
-
|
59
44
|
def to_s
|
60
45
|
@config_resolver.to_s
|
61
46
|
end
|
62
47
|
|
48
|
+
def resolver
|
49
|
+
@config_resolver
|
50
|
+
end
|
51
|
+
|
63
52
|
def self.value_to_delta(key, config_value, namespace = nil)
|
64
53
|
Prefab::Config.new(key: [namespace, key].compact.join(':'),
|
65
54
|
rows: [Prefab::ConfigRow.new(value: config_value)])
|
66
55
|
end
|
67
56
|
|
68
|
-
def get(key, default =
|
69
|
-
value = _get(key,
|
57
|
+
def get(key, default = NO_DEFAULT_PROVIDED, properties = NO_DEFAULT_PROVIDED)
|
58
|
+
value = _get(key, properties)
|
70
59
|
|
71
60
|
if value
|
72
|
-
|
73
|
-
Prefab::ConfigValueUnwrapper.unwrap(value, key, properties.merge(Prefab::CriteriaEvaluator::LOOKUP_KEY => lookup))
|
61
|
+
Prefab::ConfigValueUnwrapper.unwrap(value, key, properties)
|
74
62
|
else
|
75
63
|
handle_default(key, default)
|
76
64
|
end
|
@@ -83,14 +71,14 @@ module Prefab
|
|
83
71
|
end
|
84
72
|
|
85
73
|
def handle_default(key, default)
|
86
|
-
return default if default !=
|
74
|
+
return default if default != NO_DEFAULT_PROVIDED
|
87
75
|
|
88
76
|
raise Prefab::Errors::MissingDefaultError, key if @options.on_no_default == Prefab::Options::ON_NO_DEFAULT::RAISE
|
89
77
|
|
90
78
|
nil
|
91
79
|
end
|
92
80
|
|
93
|
-
def _get(key,
|
81
|
+
def _get(key, properties)
|
94
82
|
# wait timeout sec for the initalization to be complete
|
95
83
|
@initialized_future.value(@options.initialization_timeout_sec)
|
96
84
|
if @initialized_future.incomplete?
|
@@ -101,9 +89,9 @@ module Prefab
|
|
101
89
|
@base_client.log_internal ::Logger::WARN,
|
102
90
|
"Couldn't Initialize In #{@options.initialization_timeout_sec}. Key #{key}. Returning what we have"
|
103
91
|
@initialization_lock.release_write_lock
|
104
|
-
|
105
92
|
end
|
106
|
-
|
93
|
+
|
94
|
+
@config_resolver.get key, properties
|
107
95
|
end
|
108
96
|
|
109
97
|
def load_checkpoint
|
@@ -192,8 +180,8 @@ module Prefab
|
|
192
180
|
auth = "#{AUTH_USER}:#{@base_client.api_key}"
|
193
181
|
auth_string = Base64.strict_encode64(auth)
|
194
182
|
headers = {
|
195
|
-
|
196
|
-
|
183
|
+
'x-prefab-start-at-id': start_at_id,
|
184
|
+
'Authorization': "Basic #{auth_string}"
|
197
185
|
}
|
198
186
|
url = "#{@base_client.prefab_api_url}/api/v1/sse/config"
|
199
187
|
@base_client.log_internal ::Logger::INFO, "SSE Streaming Connect to #{url} start_at #{start_at_id}"
|
@@ -7,7 +7,6 @@ module Prefab
|
|
7
7
|
def initialize(base_client, config_loader)
|
8
8
|
@lock = Concurrent::ReadWriteLock.new
|
9
9
|
@local_store = {}
|
10
|
-
@additional_properties = { Prefab::CriteriaEvaluator::NAMESPACE_KEY => base_client.options.namespace }
|
11
10
|
@config_loader = config_loader
|
12
11
|
@project_env_id = 0 # we don't know this yet, it is set from the API results
|
13
12
|
@base_client = base_client
|
@@ -15,27 +14,11 @@ module Prefab
|
|
15
14
|
end
|
16
15
|
|
17
16
|
def to_s
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
if v.nil?
|
24
|
-
elements << 'tombstone'
|
25
|
-
else
|
26
|
-
config = evaluate(v[:config], {})
|
27
|
-
value = Prefab::ConfigValueUnwrapper.unwrap(config, k, {})
|
28
|
-
elements << value.to_s.slice(0..34).ljust(35)
|
29
|
-
elements << value.class.to_s.slice(0..6).ljust(7)
|
30
|
-
elements << "Match: #{v[:match]}".slice(0..29).ljust(30)
|
31
|
-
elements << "Source: #{v[:source]}"
|
32
|
-
end
|
33
|
-
str += elements.join(' | ') << "\n"
|
34
|
-
end
|
35
|
-
end
|
36
|
-
str
|
37
|
-
rescue StandardError => e
|
38
|
-
"Error printing resolved config: #{e.message}"
|
17
|
+
presenter.to_s
|
18
|
+
end
|
19
|
+
|
20
|
+
def presenter
|
21
|
+
Prefab::ResolvedConfigPresenter.new(self, @lock, @local_store)
|
39
22
|
end
|
40
23
|
|
41
24
|
def raw(key)
|
@@ -44,29 +27,46 @@ module Prefab
|
|
44
27
|
via_key ? via_key[:config] : nil
|
45
28
|
end
|
46
29
|
|
47
|
-
def get(key,
|
30
|
+
def get(key, properties = NO_DEFAULT_PROVIDED)
|
48
31
|
@lock.with_read_lock do
|
49
32
|
raw_config = raw(key)
|
50
33
|
|
51
34
|
return nil unless raw_config
|
52
35
|
|
53
|
-
evaluate(raw(key),
|
36
|
+
evaluate(raw(key), properties)
|
54
37
|
end
|
55
38
|
end
|
56
39
|
|
57
|
-
def evaluate(config,
|
58
|
-
props = properties.merge(@additional_properties).merge(Prefab::CriteriaEvaluator::LOOKUP_KEY => lookup_key)
|
59
|
-
|
40
|
+
def evaluate(config, properties = NO_DEFAULT_PROVIDED)
|
60
41
|
Prefab::CriteriaEvaluator.new(config,
|
61
|
-
project_env_id: @project_env_id,
|
42
|
+
project_env_id: @project_env_id,
|
43
|
+
resolver: self,
|
44
|
+
namespace: @base_client.options.namespace,
|
45
|
+
base_client: @base_client).evaluate(context(properties))
|
62
46
|
end
|
63
47
|
|
64
48
|
def update
|
65
49
|
make_local
|
50
|
+
|
51
|
+
@on_update ? @on_update.call : nil
|
52
|
+
end
|
53
|
+
|
54
|
+
def on_update(&block)
|
55
|
+
@on_update = block
|
66
56
|
end
|
67
57
|
|
68
58
|
private
|
69
59
|
|
60
|
+
def context(properties)
|
61
|
+
if properties == NO_DEFAULT_PROVIDED
|
62
|
+
Context.current
|
63
|
+
elsif properties.is_a?(Context)
|
64
|
+
properties
|
65
|
+
else
|
66
|
+
Context.merge_with_current(properties)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
70
|
def make_local
|
71
71
|
@lock.with_write_lock do
|
72
72
|
@local_store = @config_loader.calc_config
|
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
module Prefab
|
4
4
|
class ConfigValueUnwrapper
|
5
|
-
def self.unwrap(config_value, config_key,
|
5
|
+
def self.unwrap(config_value, config_key, context)
|
6
6
|
return nil unless config_value
|
7
7
|
|
8
8
|
case config_value.type
|
@@ -11,10 +11,13 @@ module Prefab
|
|
11
11
|
when :string_list
|
12
12
|
config_value.string_list.values
|
13
13
|
when :weighted_values
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
14
|
+
value = Prefab::WeightedValueResolver.new(
|
15
|
+
config_value.weighted_values.weighted_values,
|
16
|
+
config_key,
|
17
|
+
context[config_value.weighted_values.hash_by_property_name]
|
18
|
+
).resolve
|
19
|
+
|
20
|
+
unwrap(value.value, config_key, context)
|
18
21
|
else
|
19
22
|
raise "Unknown type: #{config_value.type}"
|
20
23
|
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Prefab
|
4
|
+
class Context
|
5
|
+
BLANK_CONTEXT_NAME = ''
|
6
|
+
|
7
|
+
class NamedContext
|
8
|
+
attr_reader :name
|
9
|
+
|
10
|
+
def initialize(name, hash)
|
11
|
+
@hash = {}
|
12
|
+
@name = name.to_s
|
13
|
+
|
14
|
+
merge!(hash)
|
15
|
+
end
|
16
|
+
|
17
|
+
def get(parts)
|
18
|
+
@hash[parts]
|
19
|
+
end
|
20
|
+
|
21
|
+
def merge!(other)
|
22
|
+
@hash = @hash.merge(other.transform_keys(&:to_s))
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_h
|
26
|
+
@hash
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
THREAD_KEY = :prefab_context
|
31
|
+
attr_reader :contexts
|
32
|
+
|
33
|
+
class << self
|
34
|
+
def current=(context)
|
35
|
+
Thread.current[THREAD_KEY] = context
|
36
|
+
end
|
37
|
+
|
38
|
+
def current
|
39
|
+
Thread.current[THREAD_KEY] ||= new
|
40
|
+
end
|
41
|
+
|
42
|
+
def with_context(context)
|
43
|
+
old_context = Thread.current[THREAD_KEY]
|
44
|
+
Thread.current[THREAD_KEY] = new(context)
|
45
|
+
yield
|
46
|
+
ensure
|
47
|
+
Thread.current[THREAD_KEY] = old_context
|
48
|
+
end
|
49
|
+
|
50
|
+
def clear_current
|
51
|
+
Thread.current[THREAD_KEY] = nil
|
52
|
+
end
|
53
|
+
|
54
|
+
def merge_with_current(new_context_properties = {})
|
55
|
+
new(current.to_h.merge(new_context_properties))
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def initialize(context = {})
|
60
|
+
@contexts = {}
|
61
|
+
|
62
|
+
if context.is_a?(NamedContext)
|
63
|
+
@contexts[context.name] = context
|
64
|
+
elsif context.is_a?(Hash)
|
65
|
+
context.map do |name, values|
|
66
|
+
if values.is_a?(Hash)
|
67
|
+
@contexts[name.to_s] = NamedContext.new(name, values)
|
68
|
+
else
|
69
|
+
warn '[DEPRECATION] Prefab contexts should be a hash with a key of the context name and a value of a hash.'
|
70
|
+
|
71
|
+
@contexts[BLANK_CONTEXT_NAME] ||= NamedContext.new(BLANK_CONTEXT_NAME, {})
|
72
|
+
@contexts[BLANK_CONTEXT_NAME].merge!({ name => values })
|
73
|
+
end
|
74
|
+
end
|
75
|
+
else
|
76
|
+
raise ArgumentError, 'must be a Hash or a NamedContext'
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def merge!(name, hash)
|
81
|
+
@contexts[name.to_s] = context(name).merge!(hash)
|
82
|
+
end
|
83
|
+
|
84
|
+
def set(name, hash)
|
85
|
+
@contexts[name.to_s] = NamedContext.new(name, hash)
|
86
|
+
end
|
87
|
+
|
88
|
+
def []=(name, hash)
|
89
|
+
set(name, hash)
|
90
|
+
end
|
91
|
+
|
92
|
+
def get(property_key)
|
93
|
+
name, key = property_key.split('.', 2)
|
94
|
+
|
95
|
+
if key.nil?
|
96
|
+
name = BLANK_CONTEXT_NAME
|
97
|
+
key = property_key
|
98
|
+
end
|
99
|
+
|
100
|
+
contexts[name] && contexts[name].get(key)
|
101
|
+
end
|
102
|
+
|
103
|
+
def [](property_key)
|
104
|
+
get(property_key)
|
105
|
+
end
|
106
|
+
|
107
|
+
def to_h
|
108
|
+
contexts.map { |name, context| [name, context.to_h] }.to_h
|
109
|
+
end
|
110
|
+
|
111
|
+
def clear
|
112
|
+
@contexts = {}
|
113
|
+
end
|
114
|
+
|
115
|
+
def context(name)
|
116
|
+
contexts[name.to_s] || NamedContext.new(name, {})
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
@@ -2,27 +2,24 @@
|
|
2
2
|
|
3
3
|
module Prefab
|
4
4
|
class CriteriaEvaluator
|
5
|
-
LOOKUP_KEY = 'LOOKUP'
|
6
5
|
NAMESPACE_KEY = 'NAMESPACE'
|
7
6
|
NO_MATCHING_ROWS = [].freeze
|
8
7
|
|
9
|
-
def initialize(config, project_env_id:, resolver:, base_client:)
|
8
|
+
def initialize(config, project_env_id:, resolver:, namespace:, base_client:)
|
10
9
|
@config = config
|
11
10
|
@project_env_id = project_env_id
|
12
11
|
@resolver = resolver
|
12
|
+
@namespace = namespace
|
13
13
|
@base_client = base_client
|
14
14
|
end
|
15
15
|
|
16
16
|
def evaluate(properties)
|
17
|
-
# TODO: optimize this and perhaps do it elsewhere
|
18
|
-
props = properties.transform_keys(&:to_s)
|
19
|
-
|
20
17
|
matching_environment_row_values.each do |conditional_value|
|
21
|
-
return conditional_value.value if all_criteria_match?(conditional_value,
|
18
|
+
return conditional_value.value if all_criteria_match?(conditional_value, properties)
|
22
19
|
end
|
23
20
|
|
24
21
|
default_row_values.each do |conditional_value|
|
25
|
-
return conditional_value.value if all_criteria_match?(conditional_value,
|
22
|
+
return conditional_value.value if all_criteria_match?(conditional_value, properties)
|
26
23
|
end
|
27
24
|
|
28
25
|
nil
|
@@ -35,17 +32,22 @@ module Prefab
|
|
35
32
|
end
|
36
33
|
|
37
34
|
def evaluate_criteron(criterion, properties)
|
38
|
-
|
35
|
+
case criterion.operator
|
36
|
+
when :IN_SEG
|
37
|
+
return in_segment?(criterion, properties)
|
38
|
+
when :NOT_IN_SEG
|
39
|
+
return !in_segment?(criterion, properties)
|
40
|
+
when :ALWAYS_TRUE
|
41
|
+
return true
|
42
|
+
end
|
43
|
+
|
44
|
+
value_from_properties = criterion.property_name === NAMESPACE_KEY ? @namespace : properties.get(criterion.property_name)
|
39
45
|
|
40
46
|
case criterion.operator
|
41
|
-
when :
|
47
|
+
when :PROP_IS_ONE_OF
|
42
48
|
matches?(criterion, value_from_properties, properties)
|
43
|
-
when :
|
49
|
+
when :PROP_IS_NOT_ONE_OF
|
44
50
|
!matches?(criterion, value_from_properties, properties)
|
45
|
-
when :IN_SEG
|
46
|
-
in_segment?(criterion, properties)
|
47
|
-
when :NOT_IN_SEG
|
48
|
-
!in_segment?(criterion, properties)
|
49
51
|
when :PROP_ENDS_WITH_ONE_OF
|
50
52
|
return false unless value_from_properties
|
51
53
|
|
@@ -60,8 +62,6 @@ module Prefab
|
|
60
62
|
end
|
61
63
|
when :HIERARCHICAL_MATCH
|
62
64
|
value_from_properties.start_with?(criterion.value_to_match.string)
|
63
|
-
when :ALWAYS_TRUE
|
64
|
-
true
|
65
65
|
else
|
66
66
|
@base_client.log.info("Unknown Operator: #{criterion.operator}")
|
67
67
|
false
|
@@ -79,7 +79,13 @@ module Prefab
|
|
79
79
|
end
|
80
80
|
|
81
81
|
def in_segment?(criterion, properties)
|
82
|
-
@resolver.get(criterion.value_to_match.string, properties
|
82
|
+
segment = @resolver.get(criterion.value_to_match.string, properties)
|
83
|
+
|
84
|
+
if !segment
|
85
|
+
@base_client.log.info( "Segment #{criterion.value_to_match.string} not found")
|
86
|
+
end
|
87
|
+
|
88
|
+
segment&.bool
|
83
89
|
end
|
84
90
|
|
85
91
|
def matches?(criterion, value_from_properties, properties)
|
@@ -7,27 +7,27 @@ module Prefab
|
|
7
7
|
end
|
8
8
|
|
9
9
|
def feature_is_on?(feature_name)
|
10
|
-
feature_is_on_for?(feature_name,
|
10
|
+
feature_is_on_for?(feature_name, {})
|
11
11
|
end
|
12
12
|
|
13
|
-
def feature_is_on_for?(feature_name,
|
13
|
+
def feature_is_on_for?(feature_name, properties)
|
14
14
|
@base_client.stats.increment('prefab.featureflag.on', tags: ["feature:#{feature_name}"])
|
15
15
|
|
16
|
-
variant = @base_client.config_client.get(feature_name, false,
|
16
|
+
variant = @base_client.config_client.get(feature_name, false, properties)
|
17
17
|
|
18
18
|
is_on?(variant)
|
19
19
|
end
|
20
20
|
|
21
|
-
def get(feature_name,
|
22
|
-
value = _get(feature_name,
|
21
|
+
def get(feature_name, properties, default: false)
|
22
|
+
value = _get(feature_name, properties)
|
23
23
|
|
24
24
|
value.nil? ? default : value
|
25
25
|
end
|
26
26
|
|
27
27
|
private
|
28
28
|
|
29
|
-
def _get(feature_name,
|
30
|
-
@base_client.config_client.get(feature_name, nil,
|
29
|
+
def _get(feature_name, properties)
|
30
|
+
@base_client.config_client.get(feature_name, nil, properties)
|
31
31
|
end
|
32
32
|
|
33
33
|
def is_on?(variant)
|
@@ -60,14 +60,7 @@ module Prefab
|
|
60
60
|
values: [
|
61
61
|
Prefab::ConditionalValue.new(
|
62
62
|
criteria: [criterion].compact,
|
63
|
-
value:
|
64
|
-
weighted_values: Prefab::WeightedValues.new(weighted_values: [
|
65
|
-
Prefab::WeightedValue.new(
|
66
|
-
weight: 1000,
|
67
|
-
value: variant
|
68
|
-
)
|
69
|
-
])
|
70
|
-
)
|
63
|
+
value: variant
|
71
64
|
)
|
72
65
|
]
|
73
66
|
)
|
@@ -88,18 +81,10 @@ module Prefab
|
|
88
81
|
|
89
82
|
def parse_criterion(criterion)
|
90
83
|
Prefab::Criterion.new(operator: criterion['operator'],
|
91
|
-
property_name:
|
84
|
+
property_name: criterion['property'],
|
92
85
|
value_to_match: parse_value_to_match(criterion['values']))
|
93
86
|
end
|
94
87
|
|
95
|
-
def parse_property(criterion)
|
96
|
-
if criterion['operator'] == 'LOOKUP_KEY_IN'
|
97
|
-
Prefab::CriteriaEvaluator::LOOKUP_KEY
|
98
|
-
else
|
99
|
-
criterion['property']
|
100
|
-
end
|
101
|
-
end
|
102
|
-
|
103
88
|
def parse_value_to_match(values)
|
104
89
|
raise "Can't handle #{values}" unless values.instance_of?(Array)
|
105
90
|
|
data/lib/prefab/logger_client.rb
CHANGED
@@ -135,20 +135,17 @@ module Prefab
|
|
135
135
|
|
136
136
|
# Find the closest match to 'log_level.path' in config
|
137
137
|
def level_of(path)
|
138
|
-
properties = Thread.current[:prefab_log_properties] || {}
|
139
|
-
lookup_key = Thread.current[:prefab_log_lookup_key] || nil
|
140
|
-
|
141
138
|
closest_log_level_match = nil
|
142
139
|
|
143
140
|
path.split(SEP).each_with_object([BASE_KEY]) do |n, memo|
|
144
141
|
memo << n
|
145
|
-
val = @config_client.get(memo.join(SEP), NO_DEFAULT
|
142
|
+
val = @config_client.get(memo.join(SEP), NO_DEFAULT)
|
146
143
|
closest_log_level_match = val unless val.nil?
|
147
144
|
end
|
148
145
|
|
149
146
|
if closest_log_level_match.nil?
|
150
147
|
# get the top-level setting or default to WARN
|
151
|
-
closest_log_level_match = @config_client.get(BASE_KEY, :WARN
|
148
|
+
closest_log_level_match = @config_client.get(BASE_KEY, :WARN)
|
152
149
|
end
|
153
150
|
|
154
151
|
closest_log_level_match_int = Prefab::LogLevel.resolve(closest_log_level_match)
|
@@ -177,7 +174,7 @@ module Prefab
|
|
177
174
|
# StubConfigClient to be used while config client initializes
|
178
175
|
# since it may log
|
179
176
|
class BootstrappingConfigClient
|
180
|
-
def get(_key, default = nil, _properties = {}
|
177
|
+
def get(_key, default = nil, _properties = {})
|
181
178
|
ENV['PREFAB_LOG_CLIENT_BOOTSTRAP_LOG_LEVEL'] ? ENV['PREFAB_LOG_CLIENT_BOOTSTRAP_LOG_LEVEL'].upcase.to_sym : default
|
182
179
|
end
|
183
180
|
end
|