prefab-cloud-ruby 0.24.3 → 0.24.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/ruby.yml +1 -1
- data/.rubocop.yml +13 -0
- data/CHANGELOG.md +76 -0
- data/Gemfile.lock +4 -4
- data/VERSION +1 -1
- data/bin/console +21 -0
- data/compile_protos.sh +6 -0
- data/lib/prefab/client.rb +25 -4
- data/lib/prefab/config_client.rb +16 -6
- data/lib/prefab/config_loader.rb +1 -1
- data/lib/prefab/config_resolver.rb +2 -4
- data/lib/prefab/config_value_wrapper.rb +18 -0
- data/lib/prefab/context.rb +22 -2
- data/lib/prefab/context_shape.rb +20 -0
- data/lib/prefab/context_shape_aggregator.rb +63 -0
- data/lib/prefab/criteria_evaluator.rb +61 -41
- data/lib/prefab/evaluated_configs_aggregator.rb +60 -0
- data/lib/prefab/evaluated_keys_aggregator.rb +41 -0
- data/lib/prefab/local_config_parser.rb +13 -13
- data/lib/prefab/log_path_aggregator.rb +64 -0
- data/lib/prefab/logger_client.rb +11 -13
- data/lib/prefab/options.rb +37 -1
- data/lib/prefab/periodic_sync.rb +51 -0
- data/lib/prefab/time_helpers.rb +7 -0
- data/lib/prefab-cloud-ruby.rb +8 -1
- data/lib/prefab_pb.rb +33 -220
- data/prefab-cloud-ruby.gemspec +21 -5
- data/test/test_config_loader.rb +15 -15
- data/test/test_config_resolver.rb +102 -102
- data/test/test_config_value_unwrapper.rb +13 -13
- data/test/test_context.rb +42 -0
- data/test/test_context_shape.rb +51 -0
- data/test/test_context_shape_aggregator.rb +137 -0
- data/test/test_criteria_evaluator.rb +253 -150
- data/test/test_evaluated_configs_aggregator.rb +254 -0
- data/test/test_evaluated_keys_aggregator.rb +54 -0
- data/test/test_helper.rb +34 -2
- data/test/test_log_path_aggregator.rb +57 -0
- data/test/test_logger.rb +33 -33
- data/test/test_weighted_value_resolver.rb +2 -2
- metadata +21 -5
- data/lib/prefab/log_path_collector.rb +0 -102
- data/test/test_log_path_collector.rb +0 -58
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a88b7d4c3a88af4b66a623208ec2673453ce763660d2cca7993e0623c39e35e5
|
4
|
+
data.tar.gz: 125b830fb204a7085fa6a3e5b365646f27d8bc4bcf42df0c2a6f2f0e0925dcd9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7a73efdcad8211cd709f0b074db0bf46ae448362387ba6215c94314050087988909d3993a8377422a6a6ef5e09da703f3f40b017b751025d6dbbcbaffd35d012
|
7
|
+
data.tar.gz: 3e2b3e9cbf7686843abfc13f948a87840da068a823c9740eb0ceba7ea13a24502f249aebb1d1fa1931a735b2042ee989b291b8d513e1504ba534df30ab5658ea
|
data/.github/workflows/ruby.yml
CHANGED
data/.rubocop.yml
ADDED
data/CHANGELOG.md
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
# Changelog
|
2
|
+
|
3
|
+
## Unreleased
|
4
|
+
|
5
|
+
## [0.24.4] - 2023-07-06
|
6
|
+
|
7
|
+
- Support Timed Loggers (#119)
|
8
|
+
- Added EvaluatedConfigsAggregator (disabled by default) (#118)
|
9
|
+
- Added EvaluatedKeysAggregator (disabled by default) (#117)
|
10
|
+
- Dropped Ruby 2.6 support (#116)
|
11
|
+
- Capture/report context shapes (#115)
|
12
|
+
- Added bin/console (#114)
|
13
|
+
|
14
|
+
## [0.24.3] - 2023-05-15
|
15
|
+
|
16
|
+
- Add JSON log formatter (#106)
|
17
|
+
|
18
|
+
# [0.24.2] - 2023-05-12
|
19
|
+
|
20
|
+
- Fix bug in FF rollout eval consistency (#108)
|
21
|
+
- Simplify forking (#107)
|
22
|
+
|
23
|
+
# [0.24.1] - 2023-04-26
|
24
|
+
|
25
|
+
- Fix misleading deprecation warning (#105)
|
26
|
+
|
27
|
+
# [0.24.0] - 2023-04-26
|
28
|
+
|
29
|
+
- Backwards compatibility for JIT context (#104)
|
30
|
+
- Remove upsert (#103)
|
31
|
+
- Add resolver presenter and `on_update` callback (#102)
|
32
|
+
- Deprecate `lookup_key` and introduce Context (#99)
|
33
|
+
|
34
|
+
# [0.23.8] - 2023-04-21
|
35
|
+
|
36
|
+
- Update protobuf (#101)
|
37
|
+
|
38
|
+
# [0.23.7] - 2023-04-21
|
39
|
+
|
40
|
+
- Guard against ActiveJob not being loaded (#100)
|
41
|
+
|
42
|
+
# [0.23.6] - 2023-04-17
|
43
|
+
|
44
|
+
- Fix bug in FF rollout eval consistency (#98)
|
45
|
+
- Add tests for block-form of logging (#96)
|
46
|
+
|
47
|
+
# [0.23.5] - 2023-04-13
|
48
|
+
|
49
|
+
- Cast the value to string when checking presence in string list (#95)
|
50
|
+
|
51
|
+
# [0.23.4] - 2023-04-12
|
52
|
+
|
53
|
+
- Remove GRPC (#93)
|
54
|
+
|
55
|
+
# [0.23.3] - 2023-04-07
|
56
|
+
|
57
|
+
- Use exponential backoff for log level uploading (#92)
|
58
|
+
|
59
|
+
# [0.23.2] - 2023-04-04
|
60
|
+
|
61
|
+
- Move log collection logs from INFO to DEBUG (#91)
|
62
|
+
- Fix: Handle trailing slash in PREFAB_API_URL (#90)
|
63
|
+
|
64
|
+
# [0.23.1] - 2023-03-30
|
65
|
+
|
66
|
+
- ActiveStorage not defined in Rails < 5.2 (#87)
|
67
|
+
|
68
|
+
# [0.23.0] - 2023-03-28
|
69
|
+
|
70
|
+
- Convenience for setting Rails.logger (#85)
|
71
|
+
- Log evaluation according to rules (#81)
|
72
|
+
|
73
|
+
# [0.22.0] - 2023-03-15
|
74
|
+
|
75
|
+
- Report log paths and usages (#79)
|
76
|
+
- Accept hash or keyword args in `initialize` (#78)
|
data/Gemfile.lock
CHANGED
@@ -66,7 +66,7 @@ GEM
|
|
66
66
|
rake (~> 13.0)
|
67
67
|
macaddr (1.7.2)
|
68
68
|
systemu (~> 2.6.5)
|
69
|
-
mini_portile2 (2.8.
|
69
|
+
mini_portile2 (2.8.2)
|
70
70
|
minitest (5.16.2)
|
71
71
|
minitest-focus (1.3.1)
|
72
72
|
minitest (>= 4, < 6)
|
@@ -78,8 +78,8 @@ GEM
|
|
78
78
|
multi_json (1.15.0)
|
79
79
|
multi_xml (0.6.0)
|
80
80
|
multipart-post (2.1.1)
|
81
|
-
nokogiri (1.
|
82
|
-
mini_portile2 (~> 2.8.
|
81
|
+
nokogiri (1.15.2)
|
82
|
+
mini_portile2 (~> 2.8.2)
|
83
83
|
racc (~> 1.4)
|
84
84
|
oauth2 (1.4.11)
|
85
85
|
faraday (>= 0.17.3, < 3.0)
|
@@ -89,7 +89,7 @@ GEM
|
|
89
89
|
rack (>= 1.2, < 4)
|
90
90
|
psych (3.3.1)
|
91
91
|
public_suffix (4.0.6)
|
92
|
-
racc (1.
|
92
|
+
racc (1.7.0)
|
93
93
|
rack (3.0.6.1)
|
94
94
|
rake (13.0.6)
|
95
95
|
rchardet (1.8.0)
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.24.
|
1
|
+
0.24.4
|
data/bin/console
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'irb'
|
5
|
+
require 'bundler/setup'
|
6
|
+
|
7
|
+
gemspec = Dir.glob(File.expand_path("../../*.gemspec", __FILE__)).first
|
8
|
+
spec = Gem::Specification.load(gemspec)
|
9
|
+
|
10
|
+
# Add the require paths to the $LOAD_PATH
|
11
|
+
spec.require_paths.each do |path|
|
12
|
+
full_path = File.expand_path("../" + path, __dir__)
|
13
|
+
$LOAD_PATH.unshift(full_path) unless $LOAD_PATH.include?(full_path)
|
14
|
+
end
|
15
|
+
|
16
|
+
spec.require_paths.each do |path|
|
17
|
+
require "./lib/prefab-cloud-ruby"
|
18
|
+
end
|
19
|
+
|
20
|
+
# Start an IRB session
|
21
|
+
IRB.start(__FILE__)
|
data/compile_protos.sh
CHANGED
@@ -1,5 +1,11 @@
|
|
1
1
|
#!/usr/bin/env bash
|
2
|
+
|
3
|
+
gem install grpc-tools
|
4
|
+
|
2
5
|
grpc_tools_ruby_protoc -I ../prefab-cloud/ --ruby_out=lib --grpc_out=lib prefab.proto
|
6
|
+
|
7
|
+
gsed -i 's/^module Prefab$/module PrefabProto/g' lib/prefab_pb.rb
|
8
|
+
|
3
9
|
# on M1 you need to
|
4
10
|
# 1. run in rosetta
|
5
11
|
# 2. mv gems/2.6.0/gems/grpc-tools-1.43.1/bin/x86_64-macos x86-macos
|
data/lib/prefab/client.rb
CHANGED
@@ -53,17 +53,38 @@ module Prefab
|
|
53
53
|
@feature_flag_client ||= Prefab::FeatureFlagClient.new(self)
|
54
54
|
end
|
55
55
|
|
56
|
-
def
|
56
|
+
def log_path_aggregator
|
57
57
|
return nil if @options.collect_max_paths <= 0
|
58
58
|
|
59
|
-
@
|
60
|
-
|
59
|
+
@log_path_aggregator ||= LogPathAggregator.new(client: self, max_paths: @options.collect_max_paths,
|
60
|
+
sync_interval: @options.collect_sync_interval)
|
61
61
|
end
|
62
62
|
|
63
63
|
def log
|
64
64
|
@logger_client ||= Prefab::LoggerClient.new(@options.logdev, formatter: @options.log_formatter,
|
65
65
|
prefix: @options.log_prefix,
|
66
|
-
|
66
|
+
log_path_aggregator: log_path_aggregator)
|
67
|
+
end
|
68
|
+
|
69
|
+
def context_shape_aggregator
|
70
|
+
return nil if @options.collect_max_shapes <= 0
|
71
|
+
|
72
|
+
@context_shape_aggregator ||= ContextShapeAggregator.new(client: self, max_shapes: @options.collect_max_shapes,
|
73
|
+
sync_interval: @options.collect_sync_interval)
|
74
|
+
end
|
75
|
+
|
76
|
+
def evaluated_keys_aggregator
|
77
|
+
return nil if @options.collect_max_keys <= 0
|
78
|
+
|
79
|
+
@evaluated_keys_aggregator ||= EvaluatedKeysAggregator.new(client: self, max_keys: @options.collect_max_keys,
|
80
|
+
sync_interval: @options.collect_sync_interval)
|
81
|
+
end
|
82
|
+
|
83
|
+
def evaluated_configs_aggregator
|
84
|
+
return nil if @options.collect_max_evaluations <= 0
|
85
|
+
|
86
|
+
@evaluated_configs_aggregator ||= EvaluatedConfigsAggregator.new(client: self, max_configs: @options.collect_max_evaluations,
|
87
|
+
sync_interval: @options.collect_sync_interval)
|
67
88
|
end
|
68
89
|
|
69
90
|
def set_rails_loggers
|
data/lib/prefab/config_client.rb
CHANGED
@@ -6,6 +6,7 @@ module Prefab
|
|
6
6
|
DEFAULT_CHECKPOINT_FREQ_SEC = 60
|
7
7
|
SSE_READ_TIMEOUT = 300
|
8
8
|
AUTH_USER = 'authuser'
|
9
|
+
LOGGING_KEY_PREFIX = "#{Prefab::LoggerClient::BASE_KEY}#{Prefab::LoggerClient::SEP}".freeze
|
9
10
|
|
10
11
|
def initialize(base_client, timeout)
|
11
12
|
@base_client = base_client
|
@@ -50,15 +51,24 @@ module Prefab
|
|
50
51
|
end
|
51
52
|
|
52
53
|
def self.value_to_delta(key, config_value, namespace = nil)
|
53
|
-
|
54
|
-
|
54
|
+
PrefabProto::Config.new(key: [namespace, key].compact.join(':'),
|
55
|
+
rows: [PrefabProto::ConfigRow.new(value: config_value)])
|
55
56
|
end
|
56
57
|
|
57
58
|
def get(key, default = NO_DEFAULT_PROVIDED, properties = NO_DEFAULT_PROVIDED)
|
58
59
|
context = @config_resolver.make_context(properties)
|
60
|
+
|
59
61
|
value = _get(key, context)
|
60
62
|
|
63
|
+
@base_client.context_shape_aggregator&.push(context)
|
64
|
+
@base_client.evaluated_keys_aggregator&.push(key)
|
65
|
+
|
61
66
|
if value
|
67
|
+
# NOTE: we don't &.push here because some of the args aren't already available
|
68
|
+
if @base_client.evaluated_configs_aggregator && key != Prefab::LoggerClient::BASE_KEY && !key.start_with?(LOGGING_KEY_PREFIX)
|
69
|
+
@base_client.evaluated_configs_aggregator.push([raw(key), value, context])
|
70
|
+
end
|
71
|
+
|
62
72
|
Prefab::ConfigValueUnwrapper.unwrap(value, key, context)
|
63
73
|
else
|
64
74
|
handle_default(key, default)
|
@@ -120,7 +130,7 @@ module Prefab
|
|
120
130
|
def load_url(conn, source)
|
121
131
|
resp = conn.get('')
|
122
132
|
if resp.status == 200
|
123
|
-
configs =
|
133
|
+
configs = PrefabProto::Configs.decode(resp.body)
|
124
134
|
load_configs(configs, source)
|
125
135
|
true
|
126
136
|
else
|
@@ -181,8 +191,8 @@ module Prefab
|
|
181
191
|
auth = "#{AUTH_USER}:#{@base_client.api_key}"
|
182
192
|
auth_string = Base64.strict_encode64(auth)
|
183
193
|
headers = {
|
184
|
-
'x-prefab-start-at-id'
|
185
|
-
'Authorization'
|
194
|
+
'x-prefab-start-at-id' => start_at_id,
|
195
|
+
'Authorization' => "Basic #{auth_string}"
|
186
196
|
}
|
187
197
|
url = "#{@base_client.prefab_api_url}/api/v1/sse/config"
|
188
198
|
@base_client.log_internal ::Logger::INFO, "SSE Streaming Connect to #{url} start_at #{start_at_id}"
|
@@ -191,7 +201,7 @@ module Prefab
|
|
191
201
|
read_timeout: SSE_READ_TIMEOUT,
|
192
202
|
logger: Prefab::SseLogger.new(@base_client.log)) do |client|
|
193
203
|
client.on_event do |event|
|
194
|
-
configs =
|
204
|
+
configs = PrefabProto::Configs.decode(Base64.decode64(event.data))
|
195
205
|
load_configs(configs, :sse)
|
196
206
|
end
|
197
207
|
end
|
data/lib/prefab/config_loader.rb
CHANGED
@@ -22,9 +22,7 @@ module Prefab
|
|
22
22
|
end
|
23
23
|
|
24
24
|
def raw(key)
|
25
|
-
|
26
|
-
|
27
|
-
via_key ? via_key[:config] : nil
|
25
|
+
@local_store.dig(key, :config)
|
28
26
|
end
|
29
27
|
|
30
28
|
def get(key, properties = NO_DEFAULT_PROVIDED)
|
@@ -33,7 +31,7 @@ module Prefab
|
|
33
31
|
|
34
32
|
return nil unless raw_config
|
35
33
|
|
36
|
-
evaluate(
|
34
|
+
evaluate(raw_config, properties)
|
37
35
|
end
|
38
36
|
end
|
39
37
|
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Prefab
|
2
|
+
class ConfigValueWrapper
|
3
|
+
def self.wrap(value)
|
4
|
+
case value
|
5
|
+
when Integer
|
6
|
+
PrefabProto::ConfigValue.new(int: value)
|
7
|
+
when Float
|
8
|
+
PrefabProto::ConfigValue.new(double: value)
|
9
|
+
when TrueClass, FalseClass
|
10
|
+
PrefabProto::ConfigValue.new(bool: value)
|
11
|
+
when Array
|
12
|
+
PrefabProto::ConfigValue.new(string_list: Prefab::StringList.new(values: value.map(&:to_s)))
|
13
|
+
else
|
14
|
+
PrefabProto::ConfigValue.new(string: value.to_s)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
data/lib/prefab/context.rb
CHANGED
@@ -89,11 +89,11 @@ module Prefab
|
|
89
89
|
key = property_key
|
90
90
|
end
|
91
91
|
|
92
|
-
contexts[name]
|
92
|
+
contexts[name]&.get(key)
|
93
93
|
end
|
94
94
|
|
95
95
|
def to_h
|
96
|
-
contexts.
|
96
|
+
contexts.transform_values(&:to_h)
|
97
97
|
end
|
98
98
|
|
99
99
|
def clear
|
@@ -103,5 +103,25 @@ module Prefab
|
|
103
103
|
def context(name)
|
104
104
|
contexts[name.to_s] || NamedContext.new(name, {})
|
105
105
|
end
|
106
|
+
|
107
|
+
def to_proto(namespace)
|
108
|
+
prefab_context = {
|
109
|
+
'current-time' => ConfigValueWrapper.wrap(Prefab::TimeHelpers.now_in_ms)
|
110
|
+
}
|
111
|
+
|
112
|
+
prefab_context['namespace'] = ConfigValueWrapper.wrap(namespace) if namespace&.length&.positive?
|
113
|
+
|
114
|
+
PrefabProto::ContextSet.new(
|
115
|
+
contexts: contexts.map do |name, context|
|
116
|
+
PrefabProto::Context.new(
|
117
|
+
type: name,
|
118
|
+
values: context.to_h.transform_values do |value|
|
119
|
+
ConfigValueWrapper.wrap(value)
|
120
|
+
end
|
121
|
+
)
|
122
|
+
end.concat([PrefabProto::Context.new(type: 'prefab',
|
123
|
+
values: prefab_context)])
|
124
|
+
)
|
125
|
+
end
|
106
126
|
end
|
107
127
|
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Prefab
|
2
|
+
class ContextShape
|
3
|
+
MAPPING = {
|
4
|
+
Integer => 1,
|
5
|
+
String => 2,
|
6
|
+
Float => 4,
|
7
|
+
TrueClass => 5,
|
8
|
+
FalseClass => 5,
|
9
|
+
Array => 10,
|
10
|
+
}.freeze
|
11
|
+
|
12
|
+
# We default to String if the type isn't a primitive we support.
|
13
|
+
# This is because we do a `to_s` in the CriteriaEvaluator.
|
14
|
+
DEFAULT = MAPPING[String]
|
15
|
+
|
16
|
+
def self.field_type_number(value)
|
17
|
+
MAPPING.fetch(value.class, DEFAULT)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'periodic_sync'
|
4
|
+
|
5
|
+
module Prefab
|
6
|
+
class ContextShapeAggregator
|
7
|
+
include Prefab::PeriodicSync
|
8
|
+
|
9
|
+
attr_reader :data
|
10
|
+
|
11
|
+
def initialize(client:, max_shapes:, sync_interval:)
|
12
|
+
@max_shapes = max_shapes
|
13
|
+
@client = client
|
14
|
+
@name = 'context_shape_aggregator'
|
15
|
+
|
16
|
+
@data = Concurrent::Set.new
|
17
|
+
|
18
|
+
start_periodic_sync(sync_interval)
|
19
|
+
end
|
20
|
+
|
21
|
+
def push(context)
|
22
|
+
return if @data.size >= @max_shapes
|
23
|
+
|
24
|
+
context.contexts.each_pair do |name, name_context|
|
25
|
+
name_context.to_h.each_pair do |key, value|
|
26
|
+
@data.add [name, key, Prefab::ContextShape.field_type_number(value)]
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def prepare_data
|
32
|
+
duped = @data.dup
|
33
|
+
@data.clear
|
34
|
+
|
35
|
+
duped.inject({}) do |acc, (name, key, type)|
|
36
|
+
acc[name] ||= {}
|
37
|
+
acc[name][key] = type
|
38
|
+
acc
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def flush(to_ship, _)
|
45
|
+
@pool.post do
|
46
|
+
log_internal "Uploading context shapes for #{to_ship.values.size}"
|
47
|
+
|
48
|
+
shapes = PrefabProto::ContextShapes.new(
|
49
|
+
shapes: to_ship.map do |name, shape|
|
50
|
+
PrefabProto::ContextShape.new(
|
51
|
+
name: name,
|
52
|
+
field_types: shape
|
53
|
+
)
|
54
|
+
end
|
55
|
+
)
|
56
|
+
|
57
|
+
result = @client.post('/api/v1/context-shapes', shapes)
|
58
|
+
|
59
|
+
log_internal "Uploaded #{to_ship.values.size} shapes: #{result.status}"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -1,5 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
# rubocop:disable Naming/MethodName
|
4
|
+
# We're intentionally keeping the UPCASED method names to match the protobuf
|
5
|
+
# and avoid wasting CPU cycles lowercasing things
|
3
6
|
module Prefab
|
4
7
|
class CriteriaEvaluator
|
5
8
|
NAMESPACE_KEY = 'NAMESPACE'
|
@@ -27,45 +30,55 @@ module Prefab
|
|
27
30
|
|
28
31
|
def all_criteria_match?(conditional_value, props)
|
29
32
|
conditional_value.criteria.all? do |criterion|
|
30
|
-
|
33
|
+
public_send(criterion.operator, criterion, props)
|
31
34
|
end
|
32
35
|
end
|
33
36
|
|
34
|
-
def
|
35
|
-
|
36
|
-
|
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
|
37
|
+
def IN_SEG(criterion, properties)
|
38
|
+
in_segment?(criterion, properties)
|
39
|
+
end
|
43
40
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
41
|
+
def NOT_IN_SEG(criterion, properties)
|
42
|
+
!in_segment?(criterion, properties)
|
43
|
+
end
|
44
|
+
|
45
|
+
def ALWAYS_TRUE(_criterion, _properties)
|
46
|
+
true
|
47
|
+
end
|
48
|
+
|
49
|
+
def PROP_IS_ONE_OF(criterion, properties)
|
50
|
+
matches?(criterion, value_from_properties(criterion, properties), properties)
|
51
|
+
end
|
52
|
+
|
53
|
+
def PROP_IS_NOT_ONE_OF(criterion, properties)
|
54
|
+
!matches?(criterion, value_from_properties(criterion, properties), properties)
|
55
|
+
end
|
56
|
+
|
57
|
+
def PROP_ENDS_WITH_ONE_OF(criterion, properties)
|
58
|
+
prop_ends_with_one_of?(criterion, value_from_properties(criterion, properties))
|
59
|
+
end
|
60
|
+
|
61
|
+
def PROP_DOES_NOT_END_WITH_ONE_OF(criterion, properties)
|
62
|
+
!prop_ends_with_one_of?(criterion, value_from_properties(criterion, properties))
|
63
|
+
end
|
64
|
+
|
65
|
+
def HIERARCHICAL_MATCH(criterion, properties)
|
66
|
+
value = value_from_properties(criterion, properties)
|
67
|
+
value&.start_with?(criterion.value_to_match.string)
|
68
|
+
end
|
69
|
+
|
70
|
+
def IN_INT_RANGE(criterion, properties)
|
71
|
+
value = if criterion.property_name == 'prefab.current-time'
|
72
|
+
Time.now.utc.to_i * 1000
|
73
|
+
else
|
74
|
+
value_from_properties(criterion, properties)
|
75
|
+
end
|
76
|
+
|
77
|
+
value && value >= criterion.value_to_match.int_range.start && value < criterion.value_to_match.int_range.end
|
78
|
+
end
|
79
|
+
|
80
|
+
def value_from_properties(criterion, properties)
|
81
|
+
criterion.property_name == NAMESPACE_KEY ? @namespace : properties.get(criterion.property_name)
|
69
82
|
end
|
70
83
|
|
71
84
|
private
|
@@ -81,24 +94,31 @@ module Prefab
|
|
81
94
|
def in_segment?(criterion, properties)
|
82
95
|
segment = @resolver.get(criterion.value_to_match.string, properties)
|
83
96
|
|
84
|
-
|
85
|
-
@base_client.log.info( "Segment #{criterion.value_to_match.string} not found")
|
86
|
-
end
|
97
|
+
@base_client.log.info("Segment #{criterion.value_to_match.string} not found") unless segment
|
87
98
|
|
88
99
|
segment&.bool
|
89
100
|
end
|
90
101
|
|
91
|
-
def matches?(criterion,
|
102
|
+
def matches?(criterion, value, properties)
|
92
103
|
criterion_value_or_values = Prefab::ConfigValueUnwrapper.unwrap(criterion.value_to_match, @config.key, properties)
|
93
104
|
|
94
105
|
case criterion_value_or_values
|
95
106
|
when Google::Protobuf::RepeatedField
|
96
107
|
# we to_s the value from properties for comparison because the
|
97
108
|
# criterion_value_or_values is a list of strings
|
98
|
-
criterion_value_or_values.include?(
|
109
|
+
criterion_value_or_values.include?(value.to_s)
|
99
110
|
else
|
100
|
-
criterion_value_or_values ==
|
111
|
+
criterion_value_or_values == value
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def prop_ends_with_one_of?(criterion, value)
|
116
|
+
return false unless value
|
117
|
+
|
118
|
+
criterion.value_to_match.string_list.values.any? do |ending|
|
119
|
+
value.end_with?(ending)
|
101
120
|
end
|
102
121
|
end
|
103
122
|
end
|
104
123
|
end
|
124
|
+
# rubocop:enable Naming/MethodName
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'periodic_sync'
|
4
|
+
|
5
|
+
module Prefab
|
6
|
+
class EvaluatedConfigsAggregator
|
7
|
+
include Prefab::PeriodicSync
|
8
|
+
|
9
|
+
attr_reader :data
|
10
|
+
|
11
|
+
def initialize(client:, max_configs:, sync_interval:)
|
12
|
+
@max_configs = max_configs
|
13
|
+
@client = client
|
14
|
+
@name = 'evaluated_configs_aggregator'
|
15
|
+
|
16
|
+
@data = Concurrent::Array.new
|
17
|
+
|
18
|
+
start_periodic_sync(sync_interval)
|
19
|
+
end
|
20
|
+
|
21
|
+
def push(evaluation)
|
22
|
+
return if @data.size >= @max_configs
|
23
|
+
|
24
|
+
@data.push(evaluation)
|
25
|
+
end
|
26
|
+
|
27
|
+
def prepare_data
|
28
|
+
to_ship = @data.dup
|
29
|
+
@data.clear
|
30
|
+
|
31
|
+
to_ship.map { |e| coerce_to_proto(e) }
|
32
|
+
end
|
33
|
+
|
34
|
+
def coerce_to_proto(evaluation)
|
35
|
+
config, result, context = evaluation
|
36
|
+
|
37
|
+
PrefabProto::EvaluatedConfig.new(
|
38
|
+
key: config.key,
|
39
|
+
config_version: config.id,
|
40
|
+
result: result,
|
41
|
+
context: context.to_proto(@client.namespace),
|
42
|
+
timestamp: Prefab::TimeHelpers.now_in_ms
|
43
|
+
)
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def flush(to_ship, _)
|
49
|
+
@pool.post do
|
50
|
+
log_internal "Uploading evaluated configs for #{to_ship.size}"
|
51
|
+
|
52
|
+
configs = PrefabProto::EvaluatedConfigs.new(configs: to_ship)
|
53
|
+
|
54
|
+
result = @client.post('/api/v1/evaluated-configs', configs)
|
55
|
+
|
56
|
+
log_internal "Uploaded #{to_ship.size} configs: #{result.status}"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|