flipper 1.0.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 +7 -3
- data/.github/workflows/examples.yml +27 -5
- data/Changelog.md +42 -0
- data/Gemfile +4 -4
- data/README.md +13 -11
- data/benchmark/typecast_ips.rb +8 -0
- data/docs/images/flipper_cloud.png +0 -0
- data/examples/cloud/backoff_policy.rb +13 -0
- data/examples/cloud/cloud_setup.rb +16 -0
- data/examples/cloud/forked.rb +7 -2
- data/examples/cloud/threaded.rb +15 -18
- data/examples/expressions.rb +213 -0
- data/examples/strict.rb +18 -0
- data/flipper.gemspec +1 -2
- data/lib/flipper/actor.rb +6 -3
- data/lib/flipper/adapter.rb +10 -0
- 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 +18 -13
- data/lib/flipper/adapters/instrumented.rb +0 -4
- data/lib/flipper/adapters/memoizable.rb +14 -19
- data/lib/flipper/adapters/memory.rb +4 -6
- data/lib/flipper/adapters/operation_logger.rb +0 -4
- data/lib/flipper/adapters/poll.rb +1 -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 +121 -52
- 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/configuration.rb +25 -4
- data/lib/flipper/dsl.rb +51 -0
- data/lib/flipper/engine.rb +28 -3
- data/lib/flipper/exporters/json/export.rb +1 -1
- data/lib/flipper/exporters/json/v1.rb +1 -1
- 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 +55 -0
- data/lib/flipper/gate.rb +1 -0
- data/lib/flipper/gate_values.rb +5 -2
- data/lib/flipper/gates/expression.rb +75 -0
- data/lib/flipper/instrumentation/statsd_subscriber.rb +2 -4
- data/lib/flipper/middleware/memoizer.rb +29 -13
- data/lib/flipper/poller.rb +1 -1
- data/lib/flipper/serializers/gzip.rb +24 -0
- data/lib/flipper/serializers/json.rb +19 -0
- data/lib/flipper/spec/shared_adapter_specs.rb +29 -11
- data/lib/flipper/test/shared_adapter_test.rb +24 -5
- data/lib/flipper/typecast.rb +34 -6
- data/lib/flipper/types/percentage.rb +1 -1
- data/lib/flipper/version.rb +1 -1
- data/lib/flipper.rb +38 -1
- data/spec/flipper/adapter_builder_spec.rb +73 -0
- data/spec/flipper/adapter_spec.rb +1 -0
- data/spec/flipper/adapters/http_spec.rb +39 -5
- data/spec/flipper/adapters/memoizable_spec.rb +15 -15
- data/spec/flipper/adapters/read_only_spec.rb +26 -11
- 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 +6 -23
- 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 +12 -12
- data/spec/flipper/configuration_spec.rb +17 -0
- data/spec/flipper/dsl_spec.rb +39 -0
- data/spec/flipper/engine_spec.rb +108 -7
- data/spec/flipper/exporters/json/v1_spec.rb +3 -3
- 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_spec.rb +360 -1
- data/spec/flipper/gate_values_spec.rb +2 -2
- data/spec/flipper/gates/expression_spec.rb +108 -0
- data/spec/flipper/identifier_spec.rb +4 -5
- data/spec/flipper/instrumentation/statsd_subscriber_spec.rb +15 -1
- data/spec/flipper/middleware/memoizer_spec.rb +67 -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 +43 -7
- data/spec/flipper/types/actor_spec.rb +18 -1
- data/spec/flipper_integration_spec.rb +102 -4
- data/spec/flipper_spec.rb +89 -1
- data/spec/spec_helper.rb +5 -0
- data/spec/support/actor_names.yml +1 -0
- data/spec/support/fake_backoff_policy.rb +15 -0
- data/spec/support/spec_helpers.rb +11 -3
- metadata +104 -18
- data/lib/flipper/cloud/instrumenter.rb +0 -48
@@ -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
@@ -120,6 +120,28 @@ module Flipper
|
|
120
120
|
end
|
121
121
|
end
|
122
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
|
+
|
123
145
|
# Public: Enables a feature for an actor.
|
124
146
|
#
|
125
147
|
# actor - a Flipper::Types::Actor instance or an object that responds
|
@@ -160,6 +182,27 @@ module Flipper
|
|
160
182
|
enable Types::PercentageOfActors.wrap(percentage)
|
161
183
|
end
|
162
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
|
+
|
163
206
|
# Public: Disables a feature for an actor.
|
164
207
|
#
|
165
208
|
# actor - a Flipper::Types::Actor instance or an object that responds
|
@@ -252,6 +295,10 @@ module Flipper
|
|
252
295
|
Flipper.groups - enabled_groups
|
253
296
|
end
|
254
297
|
|
298
|
+
def expression
|
299
|
+
Flipper::Expression.build(expression_value) if expression_value
|
300
|
+
end
|
301
|
+
|
255
302
|
# Public: Get the adapter value for the groups gate.
|
256
303
|
#
|
257
304
|
# Returns Set of String group names.
|
@@ -259,6 +306,13 @@ module Flipper
|
|
259
306
|
gate_values.groups
|
260
307
|
end
|
261
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
|
+
|
262
316
|
# Public: Get the adapter value for the actors gate.
|
263
317
|
#
|
264
318
|
# Returns Set of String flipper_id's.
|
@@ -347,6 +401,7 @@ module Flipper
|
|
347
401
|
def gates_hash
|
348
402
|
@gates_hash ||= {
|
349
403
|
boolean: Gates::Boolean.new,
|
404
|
+
expression: Gates::Expression.new,
|
350
405
|
actor: Gates::Actor.new,
|
351
406
|
percentage_of_actors: Gates::PercentageOfActors.new,
|
352
407
|
percentage_of_time: Gates::PercentageOfTime.new,
|
data/lib/flipper/gate.rb
CHANGED
data/lib/flipper/gate_values.rb
CHANGED
@@ -6,6 +6,7 @@ module Flipper
|
|
6
6
|
attr_reader :boolean
|
7
7
|
attr_reader :actors
|
8
8
|
attr_reader :groups
|
9
|
+
attr_reader :expression
|
9
10
|
attr_reader :percentage_of_actors
|
10
11
|
attr_reader :percentage_of_time
|
11
12
|
|
@@ -13,8 +14,9 @@ module Flipper
|
|
13
14
|
@boolean = Typecast.to_boolean(adapter_values[:boolean])
|
14
15
|
@actors = Typecast.to_set(adapter_values[:actors])
|
15
16
|
@groups = Typecast.to_set(adapter_values[:groups])
|
16
|
-
@
|
17
|
-
@
|
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])
|
18
20
|
end
|
19
21
|
|
20
22
|
def eql?(other)
|
@@ -22,6 +24,7 @@ module Flipper
|
|
22
24
|
boolean == other.boolean &&
|
23
25
|
actors == other.actors &&
|
24
26
|
groups == other.groups &&
|
27
|
+
expression == other.expression &&
|
25
28
|
percentage_of_actors == other.percentage_of_actors &&
|
26
29
|
percentage_of_time == other.percentage_of_time
|
27
30
|
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
|
@@ -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
|
@@ -20,6 +20,14 @@ module Flipper
|
|
20
20
|
# # using with preload specific features
|
21
21
|
# use Flipper::Middleware::Memoizer, preload: [:stats, :search, :some_feature]
|
22
22
|
#
|
23
|
+
# # using with preload block that returns true/false
|
24
|
+
# use Flipper::Middleware::Memoizer, preload: ->(request) { !request.path.start_with?('/assets') }
|
25
|
+
#
|
26
|
+
# # using with preload block that returns specific features
|
27
|
+
# use Flipper::Middleware::Memoizer, preload: ->(request) {
|
28
|
+
# request.path.starts_with?('/admin') ? [:stats, :search] : false
|
29
|
+
# }
|
30
|
+
#
|
23
31
|
def initialize(app, opts = {})
|
24
32
|
if opts.is_a?(Flipper::DSL) || opts.is_a?(Proc)
|
25
33
|
raise 'Flipper::Middleware::Memoizer no longer initializes with a flipper instance or block. Read more at: https://git.io/vSo31.'
|
@@ -34,7 +42,7 @@ module Flipper
|
|
34
42
|
request = Rack::Request.new(env)
|
35
43
|
|
36
44
|
if memoize?(request)
|
37
|
-
memoized_call(
|
45
|
+
memoized_call(request)
|
38
46
|
else
|
39
47
|
@app.call(env)
|
40
48
|
end
|
@@ -52,26 +60,34 @@ module Flipper
|
|
52
60
|
end
|
53
61
|
end
|
54
62
|
|
55
|
-
def memoized_call(
|
56
|
-
|
57
|
-
flipper = env.fetch(@env_key) { Flipper }
|
63
|
+
def memoized_call(request)
|
64
|
+
flipper = request.env.fetch(@env_key) { Flipper }
|
58
65
|
|
59
66
|
# Already memoizing. This instance does not need to do anything.
|
60
67
|
if flipper.memoizing?
|
61
68
|
warn "Flipper::Middleware::Memoizer appears to be running twice. Read how to resolve this at https://github.com/flippercloud/flipper/pull/523"
|
62
|
-
return @app.call(env)
|
69
|
+
return @app.call(request.env)
|
63
70
|
end
|
64
71
|
|
65
|
-
|
72
|
+
begin
|
73
|
+
flipper.memoize = true
|
66
74
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
75
|
+
# Preloading is pointless without memoizing.
|
76
|
+
preload = if @opts[:preload].respond_to?(:call)
|
77
|
+
@opts[:preload].call(request)
|
78
|
+
else
|
79
|
+
@opts[:preload]
|
80
|
+
end
|
71
81
|
|
72
|
-
|
73
|
-
|
74
|
-
|
82
|
+
case preload
|
83
|
+
when true then flipper.preload_all
|
84
|
+
when Array then flipper.preload(preload)
|
85
|
+
end
|
86
|
+
|
87
|
+
@app.call(request.env)
|
88
|
+
ensure
|
89
|
+
flipper.memoize = false
|
90
|
+
end
|
75
91
|
end
|
76
92
|
end
|
77
93
|
end
|
data/lib/flipper/poller.rb
CHANGED
@@ -0,0 +1,24 @@
|
|
1
|
+
require "zlib"
|
2
|
+
require "stringio"
|
3
|
+
|
4
|
+
module Flipper
|
5
|
+
module Serializers
|
6
|
+
module Gzip
|
7
|
+
module_function
|
8
|
+
|
9
|
+
def serialize(source)
|
10
|
+
return if source.nil?
|
11
|
+
output = StringIO.new
|
12
|
+
gz = Zlib::GzipWriter.new(output)
|
13
|
+
gz.write(source)
|
14
|
+
gz.close
|
15
|
+
output.string
|
16
|
+
end
|
17
|
+
|
18
|
+
def deserialize(source)
|
19
|
+
return if source.nil?
|
20
|
+
Zlib::GzipReader.wrap(StringIO.new(source), &:read)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require "json"
|
2
|
+
|
3
|
+
module Flipper
|
4
|
+
module Serializers
|
5
|
+
module Json
|
6
|
+
module_function
|
7
|
+
|
8
|
+
def serialize(source)
|
9
|
+
return if source.nil?
|
10
|
+
JSON.generate(source)
|
11
|
+
end
|
12
|
+
|
13
|
+
def deserialize(source)
|
14
|
+
return if source.nil?
|
15
|
+
JSON.parse(source)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -4,11 +4,12 @@ RSpec.shared_examples_for 'a flipper adapter' do
|
|
4
4
|
let(:flipper) { Flipper.new(subject) }
|
5
5
|
let(:feature) { flipper[:stats] }
|
6
6
|
|
7
|
-
let(:boolean_gate)
|
8
|
-
let(:
|
9
|
-
let(:
|
10
|
-
let(:
|
11
|
-
let(:
|
7
|
+
let(:boolean_gate) { feature.gate(:boolean) }
|
8
|
+
let(:expression_gate) { feature.gate(:expression) }
|
9
|
+
let(:group_gate) { feature.gate(:group) }
|
10
|
+
let(:actor_gate) { feature.gate(:actor) }
|
11
|
+
let(:actors_gate) { feature.gate(:percentage_of_actors) }
|
12
|
+
let(:time_gate) { feature.gate(:percentage_of_time) }
|
12
13
|
|
13
14
|
before do
|
14
15
|
Flipper.register(:admins) do |actor|
|
@@ -66,10 +67,27 @@ RSpec.shared_examples_for 'a flipper adapter' do
|
|
66
67
|
expect(subject.enable(feature, time_gate, Flipper::Types::PercentageOfTime.new(45))).to eq(true)
|
67
68
|
|
68
69
|
expect(subject.disable(feature, boolean_gate, Flipper::Types::Boolean.new(false))).to eq(true)
|
69
|
-
|
70
70
|
expect(subject.get(feature)).to eq(subject.default_config)
|
71
71
|
end
|
72
72
|
|
73
|
+
it 'can enable, disable and get value for expression gate' do
|
74
|
+
basic_expression = Flipper.property(:plan).eq("basic")
|
75
|
+
age_expression = Flipper.property(:age).gte(21)
|
76
|
+
any_expression = Flipper.any(basic_expression, age_expression)
|
77
|
+
|
78
|
+
expect(subject.enable(feature, expression_gate, any_expression)).to eq(true)
|
79
|
+
result = subject.get(feature)
|
80
|
+
expect(result[:expression]).to eq(any_expression.value)
|
81
|
+
|
82
|
+
expect(subject.enable(feature, expression_gate, basic_expression)).to eq(true)
|
83
|
+
result = subject.get(feature)
|
84
|
+
expect(result[:expression]).to eq(basic_expression.value)
|
85
|
+
|
86
|
+
expect(subject.disable(feature, expression_gate, basic_expression)).to eq(true)
|
87
|
+
result = subject.get(feature)
|
88
|
+
expect(result[:expression]).to be(nil)
|
89
|
+
end
|
90
|
+
|
73
91
|
it 'can enable, disable and get value for group gate' do
|
74
92
|
expect(subject.enable(feature, group_gate, flipper.group(:admins))).to eq(true)
|
75
93
|
expect(subject.enable(feature, group_gate, flipper.group(:early_access))).to eq(true)
|
@@ -256,14 +274,14 @@ RSpec.shared_examples_for 'a flipper adapter' do
|
|
256
274
|
expect(subject.add(flipper[:stats])).to eq(true)
|
257
275
|
expect(subject.enable(flipper[:stats], boolean_gate, Flipper::Types::Boolean.new)).to eq(true)
|
258
276
|
expect(subject.add(flipper[:search])).to eq(true)
|
277
|
+
flipper.enable :analytics, Flipper.property(:plan).eq("pro")
|
259
278
|
|
260
279
|
result = subject.get_all
|
261
|
-
expect(result).to be_instance_of(Hash)
|
262
280
|
|
263
|
-
|
264
|
-
|
265
|
-
expect(
|
266
|
-
expect(
|
281
|
+
expect(result).to be_instance_of(Hash)
|
282
|
+
expect(result["stats"]).to eq(subject.default_config.merge(boolean: 'true'))
|
283
|
+
expect(result["search"]).to eq(subject.default_config)
|
284
|
+
expect(result["analytics"]).to eq(subject.default_config.merge(expression: {"Equal"=>[{"Property"=>["plan"]}, "pro"]}))
|
267
285
|
end
|
268
286
|
|
269
287
|
it 'includes explicitly disabled features when getting all features' do
|
@@ -7,6 +7,7 @@ module Flipper
|
|
7
7
|
@feature = @flipper[:stats]
|
8
8
|
@boolean_gate = @feature.gate(:boolean)
|
9
9
|
@group_gate = @feature.gate(:group)
|
10
|
+
@expression_gate = @feature.gate(:expression)
|
10
11
|
@actor_gate = @feature.gate(:actor)
|
11
12
|
@actors_gate = @feature.gate(:percentage_of_actors)
|
12
13
|
@time_gate = @feature.gate(:percentage_of_time)
|
@@ -65,6 +66,24 @@ module Flipper
|
|
65
66
|
assert_equal @adapter.default_config, @adapter.get(@feature)
|
66
67
|
end
|
67
68
|
|
69
|
+
def test_can_enable_disable_and_get_value_for_expression_gate
|
70
|
+
basic_expression = Flipper.property(:plan).eq("basic")
|
71
|
+
age_expression = Flipper.property(:age).gte(21)
|
72
|
+
any_expression = Flipper.any(basic_expression, age_expression)
|
73
|
+
|
74
|
+
assert_equal true, @adapter.enable(@feature, @expression_gate, any_expression)
|
75
|
+
result = @adapter.get(@feature)
|
76
|
+
assert_equal any_expression.value, result[:expression]
|
77
|
+
|
78
|
+
assert_equal true, @adapter.enable(@feature, @expression_gate, basic_expression)
|
79
|
+
result = @adapter.get(@feature)
|
80
|
+
assert_equal basic_expression.value, result[:expression]
|
81
|
+
|
82
|
+
assert_equal true, @adapter.disable(@feature, @expression_gate, basic_expression)
|
83
|
+
result = @adapter.get(@feature)
|
84
|
+
assert_nil result[:expression]
|
85
|
+
end
|
86
|
+
|
68
87
|
def test_can_enable_disable_get_value_for_group_gate
|
69
88
|
assert_equal true, @adapter.enable(@feature, @group_gate, @flipper.group(:admins))
|
70
89
|
assert_equal true, @adapter.enable(@feature, @group_gate, @flipper.group(:early_access))
|
@@ -252,14 +271,14 @@ module Flipper
|
|
252
271
|
assert @adapter.add(@flipper[:stats])
|
253
272
|
assert @adapter.enable(@flipper[:stats], @boolean_gate, Flipper::Types::Boolean.new)
|
254
273
|
assert @adapter.add(@flipper[:search])
|
274
|
+
@flipper.enable :analytics, Flipper.property(:plan).eq("pro")
|
255
275
|
|
256
276
|
result = @adapter.get_all
|
257
|
-
assert_instance_of Hash, result
|
258
277
|
|
259
|
-
|
260
|
-
|
261
|
-
assert_equal @adapter.default_config
|
262
|
-
assert_equal @adapter.default_config,
|
278
|
+
assert_instance_of Hash, result
|
279
|
+
assert_equal @adapter.default_config.merge(boolean: 'true'), result["stats"]
|
280
|
+
assert_equal @adapter.default_config, result["search"]
|
281
|
+
assert_equal @adapter.default_config.merge(expression: {"Equal"=>[{"Property"=>["plan"]}, "pro"]}), result["analytics"]
|
263
282
|
end
|
264
283
|
|
265
284
|
def test_includes_explicitly_disabled_features_when_getting_all_features
|
data/lib/flipper/typecast.rb
CHANGED
@@ -1,4 +1,6 @@
|
|
1
1
|
require 'set'
|
2
|
+
require "flipper/serializers/json"
|
3
|
+
require "flipper/serializers/gzip"
|
2
4
|
|
3
5
|
module Flipper
|
4
6
|
module Typecast
|
@@ -36,17 +38,25 @@ module Flipper
|
|
36
38
|
raise ArgumentError, "#{value.inspect} cannot be converted to a float"
|
37
39
|
end
|
38
40
|
|
39
|
-
# Internal: Convert value to a
|
41
|
+
# Internal: Convert value to a number.
|
40
42
|
#
|
41
43
|
# Returns a Integer or Float representation of the value.
|
42
44
|
# Raises ArgumentError if conversion is not possible.
|
43
|
-
def self.
|
44
|
-
|
45
|
-
|
46
|
-
|
45
|
+
def self.to_number(value)
|
46
|
+
case value
|
47
|
+
when Numeric
|
48
|
+
value
|
49
|
+
when String
|
50
|
+
value.include?('.') ? to_float(value) : to_integer(value)
|
51
|
+
when NilClass
|
52
|
+
0
|
53
|
+
else
|
54
|
+
value.to_f
|
55
|
+
end
|
47
56
|
rescue NoMethodError
|
48
|
-
raise ArgumentError, "#{value.inspect} cannot be converted to a
|
57
|
+
raise ArgumentError, "#{value.inspect} cannot be converted to a number"
|
49
58
|
end
|
59
|
+
singleton_class.send(:alias_method, :to_percentage, :to_number)
|
50
60
|
|
51
61
|
# Internal: Convert value to a set.
|
52
62
|
#
|
@@ -71,6 +81,8 @@ module Flipper
|
|
71
81
|
normalized_value = case value
|
72
82
|
when Array, Set
|
73
83
|
value.to_set
|
84
|
+
when Hash
|
85
|
+
value
|
74
86
|
else
|
75
87
|
value ? value.to_s : value
|
76
88
|
end
|
@@ -79,5 +91,21 @@ module Flipper
|
|
79
91
|
end
|
80
92
|
normalized_source
|
81
93
|
end
|
94
|
+
|
95
|
+
def self.to_json(source)
|
96
|
+
Serializers::Json.serialize(source)
|
97
|
+
end
|
98
|
+
|
99
|
+
def self.from_json(source)
|
100
|
+
Serializers::Json.deserialize(source)
|
101
|
+
end
|
102
|
+
|
103
|
+
def self.to_gzip(source)
|
104
|
+
Serializers::Gzip.serialize(source)
|
105
|
+
end
|
106
|
+
|
107
|
+
def self.from_gzip(source)
|
108
|
+
Serializers::Gzip.deserialize(source)
|
109
|
+
end
|
82
110
|
end
|
83
111
|
end
|
data/lib/flipper/version.rb
CHANGED