prefab-cloud-ruby 0.23.7 → 0.24.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/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/lib/prefab_pb.rb +42 -41
- data/prefab-cloud-ruby.gemspec +6 -4
- 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 -3
- data/lib/prefab_services_pb.rb +0 -80
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
|