flipper 0.26.0 → 1.1.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/.github/FUNDING.yml +1 -0
- data/.github/workflows/ci.yml +19 -13
- data/.github/workflows/examples.yml +32 -15
- data/Changelog.md +294 -154
- data/Gemfile +15 -10
- data/README.md +13 -11
- data/benchmark/enabled_ips.rb +10 -0
- data/benchmark/enabled_multiple_actors_ips.rb +20 -0
- data/benchmark/enabled_profile.rb +20 -0
- data/benchmark/instrumentation_ips.rb +21 -0
- data/benchmark/typecast_ips.rb +27 -0
- data/docs/images/flipper_cloud.png +0 -0
- data/examples/api/basic.ru +3 -4
- data/examples/api/custom_memoized.ru +3 -4
- data/examples/api/memoized.ru +3 -4
- data/examples/cloud/app.ru +12 -0
- data/examples/cloud/backoff_policy.rb +13 -0
- data/examples/cloud/basic.rb +22 -0
- data/examples/cloud/cloud_setup.rb +20 -0
- data/examples/cloud/forked.rb +36 -0
- data/examples/cloud/import.rb +17 -0
- data/examples/cloud/threaded.rb +33 -0
- data/examples/dsl.rb +1 -15
- data/examples/enabled_for_actor.rb +4 -2
- data/examples/expressions.rb +213 -0
- data/examples/mirroring.rb +59 -0
- data/examples/strict.rb +18 -0
- data/flipper-cloud.gemspec +19 -0
- data/flipper.gemspec +3 -5
- data/lib/flipper/actor.rb +6 -3
- data/lib/flipper/adapter.rb +33 -7
- data/lib/flipper/adapter_builder.rb +44 -0
- data/lib/flipper/adapters/dual_write.rb +1 -3
- data/lib/flipper/adapters/failover.rb +0 -4
- data/lib/flipper/adapters/failsafe.rb +0 -4
- data/lib/flipper/adapters/http/client.rb +26 -7
- data/lib/flipper/adapters/http/error.rb +1 -1
- data/lib/flipper/adapters/http.rb +29 -16
- data/lib/flipper/adapters/instrumented.rb +25 -6
- data/lib/flipper/adapters/memoizable.rb +33 -21
- data/lib/flipper/adapters/memory.rb +81 -46
- data/lib/flipper/adapters/operation_logger.rb +16 -7
- data/lib/flipper/adapters/poll/poller.rb +2 -125
- data/lib/flipper/adapters/poll.rb +5 -3
- data/lib/flipper/adapters/pstore.rb +17 -11
- data/lib/flipper/adapters/read_only.rb +4 -4
- data/lib/flipper/adapters/strict.rb +47 -0
- data/lib/flipper/adapters/sync/feature_synchronizer.rb +10 -1
- data/lib/flipper/adapters/sync.rb +0 -4
- data/lib/flipper/cloud/configuration.rb +258 -0
- data/lib/flipper/cloud/dsl.rb +27 -0
- data/lib/flipper/cloud/message_verifier.rb +95 -0
- data/lib/flipper/cloud/middleware.rb +63 -0
- data/lib/flipper/cloud/routes.rb +14 -0
- data/lib/flipper/cloud/telemetry/backoff_policy.rb +93 -0
- data/lib/flipper/cloud/telemetry/instrumenter.rb +26 -0
- data/lib/flipper/cloud/telemetry/metric.rb +39 -0
- data/lib/flipper/cloud/telemetry/metric_storage.rb +30 -0
- data/lib/flipper/cloud/telemetry/submitter.rb +98 -0
- data/lib/flipper/cloud/telemetry.rb +183 -0
- data/lib/flipper/cloud.rb +53 -0
- data/lib/flipper/configuration.rb +25 -4
- data/lib/flipper/dsl.rb +46 -45
- data/lib/flipper/engine.rb +88 -0
- data/lib/flipper/errors.rb +3 -3
- data/lib/flipper/export.rb +26 -0
- data/lib/flipper/exporter.rb +17 -0
- data/lib/flipper/exporters/json/export.rb +32 -0
- data/lib/flipper/exporters/json/v1.rb +33 -0
- data/lib/flipper/expression/builder.rb +73 -0
- data/lib/flipper/expression/constant.rb +25 -0
- data/lib/flipper/expression.rb +71 -0
- data/lib/flipper/expressions/all.rb +11 -0
- data/lib/flipper/expressions/any.rb +9 -0
- data/lib/flipper/expressions/boolean.rb +9 -0
- data/lib/flipper/expressions/comparable.rb +13 -0
- data/lib/flipper/expressions/duration.rb +28 -0
- data/lib/flipper/expressions/equal.rb +9 -0
- data/lib/flipper/expressions/greater_than.rb +9 -0
- data/lib/flipper/expressions/greater_than_or_equal_to.rb +9 -0
- data/lib/flipper/expressions/less_than.rb +9 -0
- data/lib/flipper/expressions/less_than_or_equal_to.rb +9 -0
- data/lib/flipper/expressions/not_equal.rb +9 -0
- data/lib/flipper/expressions/now.rb +9 -0
- data/lib/flipper/expressions/number.rb +9 -0
- data/lib/flipper/expressions/percentage.rb +9 -0
- data/lib/flipper/expressions/percentage_of_actors.rb +12 -0
- data/lib/flipper/expressions/property.rb +9 -0
- data/lib/flipper/expressions/random.rb +9 -0
- data/lib/flipper/expressions/string.rb +9 -0
- data/lib/flipper/expressions/time.rb +9 -0
- data/lib/flipper/feature.rb +87 -26
- data/lib/flipper/feature_check_context.rb +10 -6
- data/lib/flipper/gate.rb +13 -11
- data/lib/flipper/gate_values.rb +5 -18
- data/lib/flipper/gates/actor.rb +10 -17
- data/lib/flipper/gates/boolean.rb +1 -1
- data/lib/flipper/gates/expression.rb +75 -0
- data/lib/flipper/gates/group.rb +5 -7
- data/lib/flipper/gates/percentage_of_actors.rb +10 -13
- data/lib/flipper/gates/percentage_of_time.rb +1 -2
- data/lib/flipper/identifier.rb +2 -2
- data/lib/flipper/instrumentation/log_subscriber.rb +24 -5
- data/lib/flipper/instrumentation/statsd_subscriber.rb +2 -4
- data/lib/flipper/instrumentation/subscriber.rb +8 -1
- data/lib/flipper/metadata.rb +5 -1
- data/lib/flipper/middleware/memoizer.rb +30 -14
- data/lib/flipper/poller.rb +117 -0
- data/lib/flipper/serializers/gzip.rb +24 -0
- data/lib/flipper/serializers/json.rb +19 -0
- data/lib/flipper/spec/shared_adapter_specs.rb +95 -54
- data/lib/flipper/test/shared_adapter_test.rb +91 -48
- data/lib/flipper/typecast.rb +56 -15
- data/lib/flipper/types/actor.rb +13 -13
- data/lib/flipper/types/group.rb +4 -4
- data/lib/flipper/types/percentage.rb +1 -1
- data/lib/flipper/version.rb +1 -1
- data/lib/flipper.rb +47 -10
- data/spec/fixtures/flipper_pstore_1679087600.json +46 -0
- data/spec/flipper/adapter_builder_spec.rb +73 -0
- data/spec/flipper/adapter_spec.rb +30 -2
- data/spec/flipper/adapters/dual_write_spec.rb +2 -2
- data/spec/flipper/adapters/http_spec.rb +64 -8
- data/spec/flipper/adapters/instrumented_spec.rb +29 -11
- data/spec/flipper/adapters/memoizable_spec.rb +51 -31
- data/spec/flipper/adapters/memory_spec.rb +14 -3
- data/spec/flipper/adapters/operation_logger_spec.rb +31 -12
- data/spec/flipper/adapters/read_only_spec.rb +32 -17
- data/spec/flipper/adapters/strict_spec.rb +62 -0
- data/spec/flipper/adapters/sync/feature_synchronizer_spec.rb +27 -0
- data/spec/flipper/cloud/configuration_spec.rb +252 -0
- data/spec/flipper/cloud/dsl_spec.rb +82 -0
- data/spec/flipper/cloud/message_verifier_spec.rb +104 -0
- data/spec/flipper/cloud/middleware_spec.rb +289 -0
- data/spec/flipper/cloud/telemetry/backoff_policy_spec.rb +108 -0
- data/spec/flipper/cloud/telemetry/metric_spec.rb +87 -0
- data/spec/flipper/cloud/telemetry/metric_storage_spec.rb +58 -0
- data/spec/flipper/cloud/telemetry/submitter_spec.rb +145 -0
- data/spec/flipper/cloud/telemetry_spec.rb +156 -0
- data/spec/flipper/cloud_spec.rb +180 -0
- data/spec/flipper/configuration_spec.rb +17 -0
- data/spec/flipper/dsl_spec.rb +54 -73
- data/spec/flipper/engine_spec.rb +291 -0
- data/spec/flipper/export_spec.rb +13 -0
- data/spec/flipper/exporter_spec.rb +16 -0
- data/spec/flipper/exporters/json/export_spec.rb +60 -0
- data/spec/flipper/exporters/json/v1_spec.rb +33 -0
- data/spec/flipper/expression/builder_spec.rb +248 -0
- data/spec/flipper/expression_spec.rb +188 -0
- data/spec/flipper/expressions/all_spec.rb +15 -0
- data/spec/flipper/expressions/any_spec.rb +15 -0
- data/spec/flipper/expressions/boolean_spec.rb +15 -0
- data/spec/flipper/expressions/duration_spec.rb +43 -0
- data/spec/flipper/expressions/equal_spec.rb +24 -0
- data/spec/flipper/expressions/greater_than_or_equal_to_spec.rb +28 -0
- data/spec/flipper/expressions/greater_than_spec.rb +28 -0
- data/spec/flipper/expressions/less_than_or_equal_to_spec.rb +28 -0
- data/spec/flipper/expressions/less_than_spec.rb +32 -0
- data/spec/flipper/expressions/not_equal_spec.rb +15 -0
- data/spec/flipper/expressions/now_spec.rb +11 -0
- data/spec/flipper/expressions/number_spec.rb +21 -0
- data/spec/flipper/expressions/percentage_of_actors_spec.rb +20 -0
- data/spec/flipper/expressions/percentage_spec.rb +15 -0
- data/spec/flipper/expressions/property_spec.rb +13 -0
- data/spec/flipper/expressions/random_spec.rb +9 -0
- data/spec/flipper/expressions/string_spec.rb +11 -0
- data/spec/flipper/expressions/time_spec.rb +13 -0
- data/spec/flipper/feature_check_context_spec.rb +17 -17
- data/spec/flipper/feature_spec.rb +436 -33
- data/spec/flipper/gate_values_spec.rb +2 -33
- data/spec/flipper/gates/boolean_spec.rb +1 -1
- data/spec/flipper/gates/expression_spec.rb +108 -0
- data/spec/flipper/gates/group_spec.rb +2 -3
- data/spec/flipper/gates/percentage_of_actors_spec.rb +61 -5
- data/spec/flipper/gates/percentage_of_time_spec.rb +2 -2
- data/spec/flipper/identifier_spec.rb +4 -5
- data/spec/flipper/instrumentation/log_subscriber_spec.rb +15 -5
- data/spec/flipper/instrumentation/statsd_subscriber_spec.rb +25 -1
- data/spec/flipper/middleware/memoizer_spec.rb +67 -0
- data/spec/flipper/poller_spec.rb +47 -0
- data/spec/flipper/serializers/gzip_spec.rb +13 -0
- data/spec/flipper/serializers/json_spec.rb +13 -0
- data/spec/flipper/typecast_spec.rb +121 -6
- data/spec/flipper/types/actor_spec.rb +63 -46
- data/spec/flipper/types/group_spec.rb +2 -2
- data/spec/flipper_integration_spec.rb +168 -58
- data/spec/flipper_spec.rb +92 -28
- data/spec/spec_helper.rb +6 -13
- data/spec/support/actor_names.yml +1 -0
- data/spec/support/climate_control.rb +7 -0
- data/spec/support/fake_backoff_policy.rb +15 -0
- data/spec/support/skippable.rb +18 -0
- data/spec/support/spec_helpers.rb +11 -3
- metadata +166 -13
- data/.github/workflows/release.yml +0 -44
- data/.tool-versions +0 -1
- data/lib/flipper/railtie.rb +0 -47
- data/spec/flipper/railtie_spec.rb +0 -109
@@ -0,0 +1,12 @@
|
|
1
|
+
module Flipper
|
2
|
+
module Expressions
|
3
|
+
class PercentageOfActors
|
4
|
+
SCALING_FACTOR = 1_000
|
5
|
+
|
6
|
+
def self.call(text, percentage, context: {})
|
7
|
+
prefix = context[:feature_name] || ""
|
8
|
+
Zlib.crc32("#{prefix}#{text}") % (100 * SCALING_FACTOR) < percentage * SCALING_FACTOR
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
data/lib/flipper/feature.rb
CHANGED
@@ -96,18 +96,19 @@ module Flipper
|
|
96
96
|
instrument(:clear) { adapter.clear(self) }
|
97
97
|
end
|
98
98
|
|
99
|
-
# Public: Check if a feature is enabled for
|
99
|
+
# Public: Check if a feature is enabled for zero or more actors.
|
100
100
|
#
|
101
101
|
# Returns true if enabled, false if not.
|
102
|
-
def enabled?(
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
102
|
+
def enabled?(*actors)
|
103
|
+
actors = actors.flatten.compact.map { |actor| Types::Actor.wrap(actor) }
|
104
|
+
actors = nil if actors.empty?
|
105
|
+
|
106
|
+
# thing is left for backwards compatibility
|
107
|
+
instrument(:enabled?, thing: actors&.first, actors: actors) do |payload|
|
107
108
|
context = FeatureCheckContext.new(
|
108
109
|
feature_name: @name,
|
109
|
-
values:
|
110
|
-
|
110
|
+
values: gate_values,
|
111
|
+
actors: actors
|
111
112
|
)
|
112
113
|
|
113
114
|
if open_gate = gates.detect { |gate| gate.open?(context) }
|
@@ -119,6 +120,28 @@ module Flipper
|
|
119
120
|
end
|
120
121
|
end
|
121
122
|
|
123
|
+
# Public: Enables an expression_to_add for a feature.
|
124
|
+
#
|
125
|
+
# expression - an Expression or Hash that can be converted to an expression.
|
126
|
+
#
|
127
|
+
# Returns result of enable.
|
128
|
+
def enable_expression(expression)
|
129
|
+
enable Expression.build(expression)
|
130
|
+
end
|
131
|
+
|
132
|
+
# Public: Add an expression for a feature.
|
133
|
+
#
|
134
|
+
# expression_to_add - an expression or Hash that can be converted to an expression.
|
135
|
+
#
|
136
|
+
# Returns result of enable.
|
137
|
+
def add_expression(expression_to_add)
|
138
|
+
if (current_expression = expression)
|
139
|
+
enable current_expression.add(expression_to_add)
|
140
|
+
else
|
141
|
+
enable expression_to_add
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
122
145
|
# Public: Enables a feature for an actor.
|
123
146
|
#
|
124
147
|
# actor - a Flipper::Types::Actor instance or an object that responds
|
@@ -159,6 +182,27 @@ module Flipper
|
|
159
182
|
enable Types::PercentageOfActors.wrap(percentage)
|
160
183
|
end
|
161
184
|
|
185
|
+
# Public: Disables an expression for a feature.
|
186
|
+
#
|
187
|
+
# expression - an expression or Hash that can be converted to an expression.
|
188
|
+
#
|
189
|
+
# Returns result of disable.
|
190
|
+
def disable_expression
|
191
|
+
disable Flipper.all # just need an expression to clear
|
192
|
+
end
|
193
|
+
|
194
|
+
# Public: Remove an expression from a feature. Does nothing if no expression is
|
195
|
+
# currently enabled.
|
196
|
+
#
|
197
|
+
# expression - an Expression or Hash that can be converted to an expression.
|
198
|
+
#
|
199
|
+
# Returns result of enable or nil (if no expression enabled).
|
200
|
+
def remove_expression(expression_to_remove)
|
201
|
+
if (current_expression = expression)
|
202
|
+
enable current_expression.remove(expression_to_remove)
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
162
206
|
# Public: Disables a feature for an actor.
|
163
207
|
#
|
164
208
|
# actor - a Flipper::Types::Actor instance or an object that responds
|
@@ -207,7 +251,7 @@ module Flipper
|
|
207
251
|
|
208
252
|
if values.boolean || values.percentage_of_time == 100
|
209
253
|
:on
|
210
|
-
elsif non_boolean_gates.detect { |gate| gate.enabled?(values
|
254
|
+
elsif non_boolean_gates.detect { |gate| gate.enabled?(values.send(gate.key)) }
|
211
255
|
:conditional
|
212
256
|
else
|
213
257
|
:off
|
@@ -232,7 +276,8 @@ module Flipper
|
|
232
276
|
|
233
277
|
# Public: Returns the raw gate values stored by the adapter.
|
234
278
|
def gate_values
|
235
|
-
|
279
|
+
adapter_values = adapter.get(self)
|
280
|
+
GateValues.new(adapter_values)
|
236
281
|
end
|
237
282
|
|
238
283
|
# Public: Get groups enabled for this feature.
|
@@ -250,6 +295,10 @@ module Flipper
|
|
250
295
|
Flipper.groups - enabled_groups
|
251
296
|
end
|
252
297
|
|
298
|
+
def expression
|
299
|
+
Flipper::Expression.build(expression_value) if expression_value
|
300
|
+
end
|
301
|
+
|
253
302
|
# Public: Get the adapter value for the groups gate.
|
254
303
|
#
|
255
304
|
# Returns Set of String group names.
|
@@ -257,6 +306,13 @@ module Flipper
|
|
257
306
|
gate_values.groups
|
258
307
|
end
|
259
308
|
|
309
|
+
# Public: Get the adapter value for the expression gate.
|
310
|
+
#
|
311
|
+
# Returns expression.
|
312
|
+
def expression_value
|
313
|
+
gate_values.expression
|
314
|
+
end
|
315
|
+
|
260
316
|
# Public: Get the adapter value for the actors gate.
|
261
317
|
#
|
262
318
|
# Returns Set of String flipper_id's.
|
@@ -290,7 +346,7 @@ module Flipper
|
|
290
346
|
# Returns an Array of Flipper::Gate instances.
|
291
347
|
def enabled_gates
|
292
348
|
values = gate_values
|
293
|
-
gates.select { |gate| gate.enabled?(values
|
349
|
+
gates.select { |gate| gate.enabled?(values.send(gate.key)) }
|
294
350
|
end
|
295
351
|
|
296
352
|
# Public: Get the names of the enabled gates.
|
@@ -339,37 +395,42 @@ module Flipper
|
|
339
395
|
#
|
340
396
|
# Returns an array of gates
|
341
397
|
def gates
|
342
|
-
@gates ||=
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
Gates::
|
348
|
-
|
398
|
+
@gates ||= gates_hash.values.freeze
|
399
|
+
end
|
400
|
+
|
401
|
+
def gates_hash
|
402
|
+
@gates_hash ||= {
|
403
|
+
boolean: Gates::Boolean.new,
|
404
|
+
expression: Gates::Expression.new,
|
405
|
+
actor: Gates::Actor.new,
|
406
|
+
percentage_of_actors: Gates::PercentageOfActors.new,
|
407
|
+
percentage_of_time: Gates::PercentageOfTime.new,
|
408
|
+
group: Gates::Group.new,
|
409
|
+
}.freeze
|
349
410
|
end
|
350
411
|
|
351
412
|
# Public: Find a gate by name.
|
352
413
|
#
|
353
414
|
# Returns a Flipper::Gate if found, nil if not.
|
354
415
|
def gate(name)
|
355
|
-
|
416
|
+
gates_hash[name.to_sym]
|
356
417
|
end
|
357
418
|
|
358
|
-
# Public: Find the gate that protects
|
419
|
+
# Public: Find the gate that protects an actor.
|
359
420
|
#
|
360
|
-
#
|
421
|
+
# actor - The object for which you would like to find a gate
|
361
422
|
#
|
362
423
|
# Returns a Flipper::Gate.
|
363
|
-
# Raises Flipper::GateNotFound if no gate found for
|
364
|
-
def gate_for(
|
365
|
-
gates.detect { |gate| gate.protects?(
|
424
|
+
# Raises Flipper::GateNotFound if no gate found for actor
|
425
|
+
def gate_for(actor)
|
426
|
+
gates.detect { |gate| gate.protects?(actor) } || raise(GateNotFound, actor)
|
366
427
|
end
|
367
428
|
|
368
429
|
private
|
369
430
|
|
370
431
|
# Private: Instrument a feature operation.
|
371
|
-
def instrument(operation)
|
372
|
-
@instrumenter.instrument(InstrumentationName) do |payload|
|
432
|
+
def instrument(operation, initial_payload = {})
|
433
|
+
@instrumenter.instrument(InstrumentationName, initial_payload) do |payload|
|
373
434
|
payload[:feature_name] = name
|
374
435
|
payload[:operation] = operation
|
375
436
|
payload[:result] = yield(payload) if block_given?
|
@@ -7,13 +7,17 @@ module Flipper
|
|
7
7
|
# gates for the feature.
|
8
8
|
attr_reader :values
|
9
9
|
|
10
|
-
# Public: The
|
11
|
-
attr_reader :
|
10
|
+
# Public: The actors we want to know if a feature is enabled for.
|
11
|
+
attr_reader :actors
|
12
12
|
|
13
|
-
def initialize(
|
14
|
-
@feature_name =
|
15
|
-
@values =
|
16
|
-
@
|
13
|
+
def initialize(feature_name:, values:, actors:)
|
14
|
+
@feature_name = feature_name
|
15
|
+
@values = values
|
16
|
+
@actors = actors
|
17
|
+
end
|
18
|
+
|
19
|
+
def actors?
|
20
|
+
!@actors.nil? && !@actors.empty?
|
17
21
|
end
|
18
22
|
|
19
23
|
# Public: Convenience method for groups value like Feature has.
|
data/lib/flipper/gate.rb
CHANGED
@@ -18,28 +18,29 @@ module Flipper
|
|
18
18
|
raise 'Not implemented'
|
19
19
|
end
|
20
20
|
|
21
|
-
def enabled?(
|
21
|
+
def enabled?(value)
|
22
22
|
raise 'Not implemented'
|
23
23
|
end
|
24
24
|
|
25
|
-
# Internal: Check if a gate is open for
|
25
|
+
# Internal: Check if a gate is open for one or more actors. Implemented
|
26
|
+
# in subclass.
|
26
27
|
#
|
27
|
-
# Returns true if gate open for
|
28
|
-
def open?(
|
28
|
+
# Returns true if gate open for any actor, false if not.
|
29
|
+
def open?(actors, value, options = {})
|
29
30
|
false
|
30
31
|
end
|
31
32
|
|
32
|
-
# Internal: Check if a gate is protects
|
33
|
+
# Internal: Check if a gate is protects an actor. Implemented in subclass.
|
33
34
|
#
|
34
|
-
# Returns true if gate protects
|
35
|
-
def protects?(
|
35
|
+
# Returns true if gate protects actor, false if not.
|
36
|
+
def protects?(actor)
|
36
37
|
false
|
37
38
|
end
|
38
39
|
|
39
|
-
# Internal: Allows gate to wrap
|
40
|
-
# types so adapters always get
|
41
|
-
def wrap(
|
42
|
-
|
40
|
+
# Internal: Allows gate to wrap actor using one of the supported flipper
|
41
|
+
# types so adapters always get someactor that responds to value.
|
42
|
+
def wrap(actor)
|
43
|
+
actor
|
43
44
|
end
|
44
45
|
|
45
46
|
# Public: Pretty string version for debugging.
|
@@ -59,3 +60,4 @@ require 'flipper/gates/boolean'
|
|
59
60
|
require 'flipper/gates/group'
|
60
61
|
require 'flipper/gates/percentage_of_actors'
|
61
62
|
require 'flipper/gates/percentage_of_time'
|
63
|
+
require 'flipper/gates/expression'
|
data/lib/flipper/gate_values.rb
CHANGED
@@ -3,19 +3,10 @@ require 'flipper/typecast'
|
|
3
3
|
|
4
4
|
module Flipper
|
5
5
|
class GateValues
|
6
|
-
# Private: Array of instance variables that are readable through the []
|
7
|
-
# instance method.
|
8
|
-
LegitIvars = {
|
9
|
-
'boolean' => '@boolean',
|
10
|
-
'actors' => '@actors',
|
11
|
-
'groups' => '@groups',
|
12
|
-
'percentage_of_time' => '@percentage_of_time',
|
13
|
-
'percentage_of_actors' => '@percentage_of_actors',
|
14
|
-
}.freeze
|
15
|
-
|
16
6
|
attr_reader :boolean
|
17
7
|
attr_reader :actors
|
18
8
|
attr_reader :groups
|
9
|
+
attr_reader :expression
|
19
10
|
attr_reader :percentage_of_actors
|
20
11
|
attr_reader :percentage_of_time
|
21
12
|
|
@@ -23,14 +14,9 @@ module Flipper
|
|
23
14
|
@boolean = Typecast.to_boolean(adapter_values[:boolean])
|
24
15
|
@actors = Typecast.to_set(adapter_values[:actors])
|
25
16
|
@groups = Typecast.to_set(adapter_values[:groups])
|
26
|
-
@
|
27
|
-
@
|
28
|
-
|
29
|
-
|
30
|
-
def [](key)
|
31
|
-
if ivar = LegitIvars[key.to_s]
|
32
|
-
instance_variable_get(ivar)
|
33
|
-
end
|
17
|
+
@expression = adapter_values[:expression]
|
18
|
+
@percentage_of_actors = Typecast.to_number(adapter_values[:percentage_of_actors])
|
19
|
+
@percentage_of_time = Typecast.to_number(adapter_values[:percentage_of_time])
|
34
20
|
end
|
35
21
|
|
36
22
|
def eql?(other)
|
@@ -38,6 +24,7 @@ module Flipper
|
|
38
24
|
boolean == other.boolean &&
|
39
25
|
actors == other.actors &&
|
40
26
|
groups == other.groups &&
|
27
|
+
expression == other.expression &&
|
41
28
|
percentage_of_actors == other.percentage_of_actors &&
|
42
29
|
percentage_of_time == other.percentage_of_time
|
43
30
|
end
|
data/lib/flipper/gates/actor.rb
CHANGED
@@ -19,30 +19,23 @@ module Flipper
|
|
19
19
|
!value.empty?
|
20
20
|
end
|
21
21
|
|
22
|
-
# Internal: Checks if the gate is open for
|
22
|
+
# Internal: Checks if the gate is open for an actor.
|
23
23
|
#
|
24
|
-
# Returns true if gate open for
|
24
|
+
# Returns true if gate open for actor, false if not.
|
25
25
|
def open?(context)
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
if protects?(context.thing)
|
31
|
-
actor = wrap(context.thing)
|
32
|
-
enabled_actor_ids = value
|
33
|
-
enabled_actor_ids.include?(actor.value)
|
34
|
-
else
|
35
|
-
false
|
36
|
-
end
|
26
|
+
return false unless context.actors?
|
27
|
+
|
28
|
+
context.actors.any? do |actor|
|
29
|
+
context.values.actors.include?(actor.value)
|
37
30
|
end
|
38
31
|
end
|
39
32
|
|
40
|
-
def wrap(
|
41
|
-
Types::Actor.wrap(
|
33
|
+
def wrap(actor)
|
34
|
+
Types::Actor.wrap(actor)
|
42
35
|
end
|
43
36
|
|
44
|
-
def protects?(
|
45
|
-
Types::Actor.wrappable?(
|
37
|
+
def protects?(actor)
|
38
|
+
Types::Actor.wrappable?(actor)
|
46
39
|
end
|
47
40
|
end
|
48
41
|
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require "flipper/expression"
|
2
|
+
|
3
|
+
module Flipper
|
4
|
+
module Gates
|
5
|
+
class Expression < Gate
|
6
|
+
# Internal: The name of the gate. Used for instrumentation, etc.
|
7
|
+
def name
|
8
|
+
:expression
|
9
|
+
end
|
10
|
+
|
11
|
+
# Internal: Name converted to value safe for adapter.
|
12
|
+
def key
|
13
|
+
:expression
|
14
|
+
end
|
15
|
+
|
16
|
+
def data_type
|
17
|
+
:json
|
18
|
+
end
|
19
|
+
|
20
|
+
def enabled?(value)
|
21
|
+
!value.nil? && !value.empty?
|
22
|
+
end
|
23
|
+
|
24
|
+
# Internal: Checks if the gate is open for a thing.
|
25
|
+
#
|
26
|
+
# Returns true if gate open for thing, false if not.
|
27
|
+
def open?(context)
|
28
|
+
data = context.values.expression
|
29
|
+
return false if data.nil? || data.empty?
|
30
|
+
expression = Flipper::Expression.build(data)
|
31
|
+
|
32
|
+
if context.actors.nil? || context.actors.empty?
|
33
|
+
!!expression.evaluate(feature_name: context.feature_name, properties: DEFAULT_PROPERTIES)
|
34
|
+
else
|
35
|
+
context.actors.any? do |actor|
|
36
|
+
!!expression.evaluate(feature_name: context.feature_name, properties: properties(actor))
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def protects?(thing)
|
42
|
+
thing.is_a?(Flipper::Expression) || thing.is_a?(Hash)
|
43
|
+
end
|
44
|
+
|
45
|
+
def wrap(thing)
|
46
|
+
Flipper::Expression.build(thing)
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
# Internal
|
52
|
+
DEFAULT_PROPERTIES = {}.freeze
|
53
|
+
|
54
|
+
def properties(actor)
|
55
|
+
return DEFAULT_PROPERTIES if actor.nil?
|
56
|
+
|
57
|
+
properties = {}
|
58
|
+
|
59
|
+
if actor.respond_to?(:flipper_properties)
|
60
|
+
properties.update(actor.flipper_properties)
|
61
|
+
else
|
62
|
+
warn "#{actor.inspect} does not respond to `flipper_properties` but should."
|
63
|
+
end
|
64
|
+
|
65
|
+
properties.transform_keys!(&:to_s)
|
66
|
+
|
67
|
+
if actor.respond_to?(:flipper_id)
|
68
|
+
properties["flipper_id".freeze] = actor.flipper_id
|
69
|
+
end
|
70
|
+
|
71
|
+
properties
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
data/lib/flipper/gates/group.rb
CHANGED
@@ -23,13 +23,11 @@ module Flipper
|
|
23
23
|
#
|
24
24
|
# Returns true if gate open for thing, false if not.
|
25
25
|
def open?(context)
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
group = Flipper.group(name)
|
32
|
-
group.match?(context.thing, context)
|
26
|
+
return false unless context.actors?
|
27
|
+
|
28
|
+
context.values.groups.any? do |name|
|
29
|
+
context.actors.any? do |actor|
|
30
|
+
Flipper.group(name).match?(actor, context)
|
33
31
|
end
|
34
32
|
end
|
35
33
|
end
|
@@ -21,21 +21,18 @@ module Flipper
|
|
21
21
|
value > 0
|
22
22
|
end
|
23
23
|
|
24
|
-
#
|
24
|
+
# Private: this constant is used to support up to 3 decimal places
|
25
|
+
# in percentages.
|
26
|
+
SCALING_FACTOR = 1_000
|
27
|
+
private_constant :SCALING_FACTOR
|
28
|
+
|
29
|
+
# Internal: Checks if the gate is open for one or more actors.
|
25
30
|
#
|
26
|
-
# Returns true if gate open for
|
31
|
+
# Returns true if gate open for any actors, false if not.
|
27
32
|
def open?(context)
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
actor = Types::Actor.wrap(context.thing)
|
32
|
-
id = "#{context.feature_name}#{actor.value}"
|
33
|
-
# this is to support up to 3 decimal places in percentages
|
34
|
-
scaling_factor = 1_000
|
35
|
-
Zlib.crc32(id) % (100 * scaling_factor) < percentage * scaling_factor
|
36
|
-
else
|
37
|
-
false
|
38
|
-
end
|
33
|
+
return false unless context.actors?
|
34
|
+
id = "#{context.feature_name}#{context.actors.map(&:value).sort.join}"
|
35
|
+
Zlib.crc32(id) % (100 * SCALING_FACTOR) < context.values.percentage_of_actors * SCALING_FACTOR
|
39
36
|
end
|
40
37
|
|
41
38
|
def protects?(thing)
|
data/lib/flipper/identifier.rb
CHANGED
@@ -10,7 +10,7 @@ module Flipper
|
|
10
10
|
# Example Output
|
11
11
|
#
|
12
12
|
# flipper[:search].enabled?(user)
|
13
|
-
# # Flipper feature(search) enabled? false (1.2ms) [
|
13
|
+
# # Flipper feature(search) enabled? false (1.2ms) [ actors=... ]
|
14
14
|
#
|
15
15
|
# Returns nothing.
|
16
16
|
def feature_operation(event)
|
@@ -20,15 +20,19 @@ module Flipper
|
|
20
20
|
gate_name = event.payload[:gate_name]
|
21
21
|
operation = event.payload[:operation]
|
22
22
|
result = event.payload[:result]
|
23
|
-
thing = event.payload[:thing]
|
24
23
|
|
25
24
|
description = "Flipper feature(#{feature_name}) #{operation} #{result.inspect}"
|
26
|
-
|
25
|
+
|
26
|
+
details = if event.payload.key?(:actors)
|
27
|
+
"actors=#{event.payload[:actors].inspect}"
|
28
|
+
else
|
29
|
+
"thing=#{event.payload[:thing].inspect}"
|
30
|
+
end
|
27
31
|
|
28
32
|
details += " gate_name=#{gate_name}" unless gate_name.nil?
|
29
33
|
|
30
34
|
name = '%s (%.1fms)' % [description, event.duration]
|
31
|
-
debug " #{
|
35
|
+
debug " #{color_name(name)} [ #{details} ]"
|
32
36
|
end
|
33
37
|
|
34
38
|
# Logs an adapter operation. If operation is for a feature, then that
|
@@ -60,12 +64,27 @@ module Flipper
|
|
60
64
|
details = "result=#{result.inspect}"
|
61
65
|
|
62
66
|
name = '%s (%.1fms)' % [description, event.duration]
|
63
|
-
debug " #{
|
67
|
+
debug " #{color_name(name)} [ #{details} ]"
|
64
68
|
end
|
65
69
|
|
66
70
|
def logger
|
67
71
|
self.class.logger
|
68
72
|
end
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
# Rails 7.1 changed the signature of this function.
|
77
|
+
# Checking if > 7.0.99 rather than >= 7.1 so that 7.1 pre-release versions are included.
|
78
|
+
COLOR_OPTIONS = if Rails.gem_version > Gem::Version.new('7.0.99')
|
79
|
+
{ bold: true }.freeze
|
80
|
+
else
|
81
|
+
true
|
82
|
+
end
|
83
|
+
private_constant :COLOR_OPTIONS
|
84
|
+
|
85
|
+
def color_name(name)
|
86
|
+
color(name, CYAN, COLOR_OPTIONS)
|
87
|
+
end
|
69
88
|
end
|
70
89
|
end
|
71
90
|
|
@@ -12,13 +12,11 @@ module Flipper
|
|
12
12
|
end
|
13
13
|
|
14
14
|
def update_timer(metric)
|
15
|
-
|
16
|
-
self.class.client.timing metric, (@duration * 1_000).round
|
17
|
-
end
|
15
|
+
self.class.client&.timing metric, (@duration * 1_000).round
|
18
16
|
end
|
19
17
|
|
20
18
|
def update_counter(metric)
|
21
|
-
self.class.client
|
19
|
+
self.class.client&.increment metric
|
22
20
|
end
|
23
21
|
end
|
24
22
|
end
|