prefab-cloud-ruby 0.24.3 → 0.24.4
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/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
|