prefab-cloud-ruby 0.24.4 → 0.24.6

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.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +9 -1
  3. data/VERSION +1 -1
  4. data/compile_protos.sh +7 -0
  5. data/lib/prefab/client.rb +17 -4
  6. data/lib/prefab/config_client.rb +10 -10
  7. data/lib/prefab/config_value_unwrapper.rb +20 -9
  8. data/lib/prefab/context.rb +39 -7
  9. data/lib/prefab/context_shape_aggregator.rb +1 -1
  10. data/lib/prefab/criteria_evaluator.rb +24 -16
  11. data/lib/prefab/evaluated_keys_aggregator.rb +1 -1
  12. data/lib/prefab/evaluation.rb +48 -0
  13. data/lib/prefab/evaluation_summary_aggregator.rb +85 -0
  14. data/lib/prefab/example_contexts_aggregator.rb +76 -0
  15. data/lib/prefab/exponential_backoff.rb +5 -0
  16. data/lib/prefab/http_connection.rb +5 -1
  17. data/lib/prefab/log_path_aggregator.rb +1 -1
  18. data/lib/prefab/logger_client.rb +12 -13
  19. data/lib/prefab/options.rb +27 -14
  20. data/lib/prefab/periodic_sync.rb +30 -13
  21. data/lib/prefab/rate_limit_cache.rb +41 -0
  22. data/lib/prefab/resolved_config_presenter.rb +2 -4
  23. data/lib/prefab/weighted_value_resolver.rb +1 -1
  24. data/lib/prefab-cloud-ruby.rb +6 -3
  25. data/lib/prefab_pb.rb +11 -1
  26. data/prefab-cloud-ruby.gemspec +14 -5
  27. data/test/support/common_helpers.rb +105 -0
  28. data/test/support/mock_base_client.rb +44 -0
  29. data/test/support/mock_config_client.rb +19 -0
  30. data/test/support/mock_config_loader.rb +1 -0
  31. data/test/test_client.rb +257 -2
  32. data/test/test_config_resolver.rb +25 -24
  33. data/test/test_config_value_unwrapper.rb +22 -32
  34. data/test/test_context_shape_aggregator.rb +0 -1
  35. data/test/test_criteria_evaluator.rb +179 -133
  36. data/test/test_evaluation_summary_aggregator.rb +162 -0
  37. data/test/test_example_contexts_aggregator.rb +238 -0
  38. data/test/test_helper.rb +5 -131
  39. data/test/test_local_config_parser.rb +2 -2
  40. data/test/test_logger.rb +5 -5
  41. data/test/test_options.rb +8 -0
  42. data/test/test_rate_limit_cache.rb +44 -0
  43. data/test/test_weighted_value_resolver.rb +13 -7
  44. metadata +13 -4
  45. data/lib/prefab/evaluated_configs_aggregator.rb +0 -60
  46. data/test/test_evaluated_configs_aggregator.rb +0 -254
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: prefab-cloud-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.24.4
4
+ version: 0.24.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jeff Dwyer
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-07-06 00:00:00.000000000 Z
11
+ date: 2023-07-31 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: concurrent-ruby
@@ -211,8 +211,10 @@ files:
211
211
  - lib/prefab/errors/initialization_timeout_error.rb
212
212
  - lib/prefab/errors/invalid_api_key_error.rb
213
213
  - lib/prefab/errors/missing_default_error.rb
214
- - lib/prefab/evaluated_configs_aggregator.rb
215
214
  - lib/prefab/evaluated_keys_aggregator.rb
215
+ - lib/prefab/evaluation.rb
216
+ - lib/prefab/evaluation_summary_aggregator.rb
217
+ - lib/prefab/example_contexts_aggregator.rb
216
218
  - lib/prefab/exponential_backoff.rb
217
219
  - lib/prefab/feature_flag_client.rb
218
220
  - lib/prefab/http_connection.rb
@@ -225,6 +227,7 @@ files:
225
227
  - lib/prefab/noop_stats.rb
226
228
  - lib/prefab/options.rb
227
229
  - lib/prefab/periodic_sync.rb
230
+ - lib/prefab/rate_limit_cache.rb
228
231
  - lib/prefab/resolved_config_presenter.rb
229
232
  - lib/prefab/sse_logger.rb
230
233
  - lib/prefab/time_helpers.rb
@@ -236,6 +239,10 @@ files:
236
239
  - test/.prefab.unit_tests.config.yaml
237
240
  - test/integration_test.rb
238
241
  - test/integration_test_helpers.rb
242
+ - test/support/common_helpers.rb
243
+ - test/support/mock_base_client.rb
244
+ - test/support/mock_config_client.rb
245
+ - test/support/mock_config_loader.rb
239
246
  - test/test_client.rb
240
247
  - test/test_config_client.rb
241
248
  - test/test_config_loader.rb
@@ -245,8 +252,9 @@ files:
245
252
  - test/test_context_shape.rb
246
253
  - test/test_context_shape_aggregator.rb
247
254
  - test/test_criteria_evaluator.rb
248
- - test/test_evaluated_configs_aggregator.rb
249
255
  - test/test_evaluated_keys_aggregator.rb
256
+ - test/test_evaluation_summary_aggregator.rb
257
+ - test/test_example_contexts_aggregator.rb
250
258
  - test/test_exponential_backoff.rb
251
259
  - test/test_feature_flag_client.rb
252
260
  - test/test_helper.rb
@@ -255,6 +263,7 @@ files:
255
263
  - test/test_log_path_aggregator.rb
256
264
  - test/test_logger.rb
257
265
  - test/test_options.rb
266
+ - test/test_rate_limit_cache.rb
258
267
  - test/test_weighted_value_resolver.rb
259
268
  homepage: http://github.com/prefab-cloud/prefab-cloud-ruby
260
269
  licenses:
@@ -1,60 +0,0 @@
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
@@ -1,254 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'test_helper'
4
- require 'timecop'
5
-
6
- class TestEvaluatedConfigsAggregator < Minitest::Test
7
- MAX_WAIT = 2
8
- SLEEP_TIME = 0.01
9
-
10
- def test_push
11
- aggregator = Prefab::EvaluatedConfigsAggregator.new(client: new_client, max_configs: 2, sync_interval: 1000)
12
-
13
- aggregator.push([])
14
- aggregator.push([])
15
-
16
- assert_equal 2, aggregator.data.size
17
-
18
- # we've reached the limit, so no more
19
- aggregator.push([])
20
- assert_equal 2, aggregator.data.size
21
- end
22
-
23
- def test_coerce_to_proto
24
- aggregator = Prefab::EvaluatedConfigsAggregator.new(client: new_client, max_configs: 2, sync_interval: 2)
25
-
26
- Timecop.freeze do
27
- coerced = aggregator.coerce_to_proto([
28
- CONFIG_1,
29
- DESIRED_VALUE,
30
- Prefab::Context.new(CONTEXT)
31
- ])
32
-
33
- assert_equal PrefabProto::EvaluatedConfig.new(
34
- key: CONFIG_1.key,
35
- config_version: CONFIG_1.id,
36
- result: DESIRED_VALUE,
37
- context: PrefabProto::ContextSet.new(
38
- contexts: [
39
- PrefabProto::Context.new(
40
- type: "user",
41
- values: {
42
- "id" => PrefabProto::ConfigValue.new(int: 1),
43
- "email_suffix" => PrefabProto::ConfigValue.new(string: "hotmail.com")
44
- }
45
- ),
46
- PrefabProto::Context.new(
47
- type: "team",
48
- values: {
49
- "id" => PrefabProto::ConfigValue.new(int: 2),
50
- "name" => PrefabProto::ConfigValue.new(string: "team-name")
51
- }
52
- ),
53
- PrefabProto::Context.new(
54
- type: "prefab",
55
- values: {
56
- "current-time" => PrefabProto::ConfigValue.new(int: Prefab::TimeHelpers.now_in_ms),
57
- }
58
- ),
59
- ]
60
- ),
61
- timestamp: Prefab::TimeHelpers.now_in_ms
62
- ), coerced
63
- end
64
- end
65
-
66
- def test_sync
67
- client = new_client(namespace: 'this.is.a.namespace')
68
-
69
- inject_config(client, CONFIG_1)
70
- inject_config(client, CONFIG_2)
71
- inject_project_env_id(client, PROJECT_ENV_ID)
72
-
73
- client.get CONFIG_1.key, 'default', CONTEXT
74
- client.get CONFIG_1.key, 'default', { user: { email_suffix: "example.com" }, device: { mobile: true } }
75
- client.get CONFIG_2.key, 'default', CONTEXT
76
-
77
- # logger items are not reported
78
- client.get "#{Prefab::ConfigClient::LOGGING_KEY_PREFIX}something", 'default', CONTEXT
79
- client.get "#{Prefab::LoggerClient::BASE_KEY}", 'default', CONTEXT
80
-
81
- requests = wait_for_post_requests(client) do
82
- client.evaluated_configs_aggregator.send(:sync)
83
- end
84
-
85
- assert_equal [[
86
- '/api/v1/evaluated-configs',
87
- PrefabProto::EvaluatedConfigs.new(
88
- configs: [
89
- PrefabProto::EvaluatedConfig.new(
90
- key: CONFIG_1.key,
91
- config_version: CONFIG_1.id,
92
- result: DESIRED_VALUE,
93
- context: PrefabProto::ContextSet.new(
94
- contexts: [
95
- PrefabProto::Context.new(
96
- type: "user",
97
- values: {
98
- "id" => PrefabProto::ConfigValue.new(int: 1),
99
- "email_suffix" => PrefabProto::ConfigValue.new(string: "hotmail.com")
100
- }
101
- ),
102
- PrefabProto::Context.new(
103
- type: "team",
104
- values: {
105
- "id" => PrefabProto::ConfigValue.new(int: 2),
106
- "name" => PrefabProto::ConfigValue.new(string: "team-name")
107
- }
108
- ),
109
- PrefabProto::Context.new(
110
- type: "prefab",
111
- values: {
112
- "current-time" => PrefabProto::ConfigValue.new(int: Prefab::TimeHelpers.now_in_ms),
113
- "namespace" => PrefabProto::ConfigValue.new(string: "this.is.a.namespace"),
114
- }
115
- ),
116
- ]
117
- ),
118
- timestamp: Prefab::TimeHelpers.now_in_ms
119
- ),
120
- PrefabProto::EvaluatedConfig.new(
121
- key: CONFIG_1.key,
122
- config_version: CONFIG_1.id,
123
- result: DEFAULT_VALUE,
124
- context: PrefabProto::ContextSet.new(
125
- contexts: [
126
- PrefabProto::Context.new(
127
- type: "user",
128
- values: {
129
- "email_suffix" => PrefabProto::ConfigValue.new(string: "example.com")
130
- }
131
- ),
132
- PrefabProto::Context.new(
133
- type: "device",
134
- values: {
135
- "mobile" => PrefabProto::ConfigValue.new(bool: true),
136
- }
137
- ),
138
- PrefabProto::Context.new(
139
- type: "prefab",
140
- values: {
141
- "current-time" => PrefabProto::ConfigValue.new(int: Prefab::TimeHelpers.now_in_ms),
142
- "namespace" => PrefabProto::ConfigValue.new(string: "this.is.a.namespace"),
143
- }
144
- )
145
-
146
- ]
147
- ),
148
- timestamp: Prefab::TimeHelpers.now_in_ms
149
- ),
150
-
151
- PrefabProto::EvaluatedConfig.new(
152
- key: CONFIG_2.key,
153
- config_version: CONFIG_2.id,
154
- result: DEFAULT_VALUE,
155
- context: PrefabProto::ContextSet.new(
156
- contexts: [
157
- PrefabProto::Context.new(
158
- type: "user",
159
- values: {
160
- "id" => PrefabProto::ConfigValue.new(int: 1),
161
- "email_suffix" => PrefabProto::ConfigValue.new(string: "hotmail.com")
162
- }
163
- ),
164
- PrefabProto::Context.new(
165
- type: "team",
166
- values: {
167
- "id" => PrefabProto::ConfigValue.new(int: 2),
168
- "name" => PrefabProto::ConfigValue.new(string: "team-name")
169
- }
170
- ),
171
- PrefabProto::Context.new(
172
- type: "prefab",
173
- values: {
174
- "current-time" => PrefabProto::ConfigValue.new(int: Prefab::TimeHelpers.now_in_ms),
175
- "namespace" => PrefabProto::ConfigValue.new(string: "this.is.a.namespace"),
176
- }
177
- )
178
- ]
179
- ),
180
- timestamp: Prefab::TimeHelpers.now_in_ms
181
- )
182
-
183
- ]
184
- )
185
- ]], requests
186
- end
187
-
188
- private
189
-
190
- def new_client(overrides = {})
191
- super(**{
192
- prefab_datasources: Prefab::Options::DATASOURCES::ALL,
193
- initialization_timeout_sec: 0,
194
- on_init_failure: Prefab::Options::ON_INITIALIZATION_FAILURE::RETURN,
195
- api_key: '123-development-yourapikey-SDK',
196
- collect_sync_interval: 1000, # we'll trigger sync manually in our test
197
- collect_evaluations: true
198
- }.merge(overrides))
199
- end
200
-
201
- DEFAULT_VALUE = PrefabProto::ConfigValue.new(string: '❌')
202
-
203
- DESIRED_VALUE = PrefabProto::ConfigValue.new(string: "✅")
204
-
205
- DEFAULT_ROW = PrefabProto::ConfigRow.new(
206
- values: [
207
- PrefabProto::ConditionalValue.new(
208
- value: DEFAULT_VALUE
209
- )
210
- ]
211
- )
212
-
213
- PROJECT_ENV_ID = 1
214
-
215
- CONFIG_1 = PrefabProto::Config.new(
216
- id: 1,
217
- key: "key.1",
218
- rows: [
219
- PrefabProto::ConfigRow.new(
220
- project_env_id: PROJECT_ENV_ID,
221
- values: [
222
- PrefabProto::ConditionalValue.new(
223
- criteria: [
224
- PrefabProto::Criterion.new(
225
- operator: PrefabProto::Criterion::CriterionOperator::PROP_IS_ONE_OF,
226
- value_to_match: string_list(['hotmail.com', 'gmail.com']),
227
- property_name: 'user.email_suffix'
228
- )
229
- ],
230
- value: DESIRED_VALUE
231
- )
232
- ]
233
- ),
234
- DEFAULT_ROW
235
- ]
236
- )
237
-
238
- CONFIG_2 = PrefabProto::Config.new(
239
- id: 2,
240
- key: "key.2",
241
- rows: [DEFAULT_ROW,]
242
- )
243
-
244
- CONTEXT = {
245
- user: {
246
- id: 1,
247
- email_suffix: 'hotmail.com'
248
- },
249
- team: {
250
- id: 2,
251
- name: 'team-name'
252
- }
253
- }
254
- end