flipper 0.10.2 → 0.11.0.beta1
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/.rubocop.yml +42 -0
- data/.rubocop_todo.yml +188 -0
- data/Changelog.md +10 -0
- data/Gemfile +6 -3
- data/README.md +4 -3
- data/Rakefile +13 -13
- data/docs/Adapters.md +2 -1
- data/docs/DockerCompose.md +6 -3
- data/docs/Gates.md +25 -3
- data/docs/Optimization.md +27 -5
- data/docs/api/README.md +73 -32
- data/docs/http/README.md +34 -0
- data/docs/read-only/README.md +22 -0
- data/examples/percentage_of_actors_group.rb +49 -0
- data/flipper.gemspec +15 -15
- data/lib/flipper.rb +2 -5
- data/lib/flipper/adapter.rb +10 -0
- data/lib/flipper/adapters/http.rb +147 -0
- data/lib/flipper/adapters/http/client.rb +83 -0
- data/lib/flipper/adapters/http/error.rb +14 -0
- data/lib/flipper/adapters/instrumented.rb +36 -36
- data/lib/flipper/adapters/memoizable.rb +2 -6
- data/lib/flipper/adapters/memory.rb +10 -9
- data/lib/flipper/adapters/operation_logger.rb +1 -1
- data/lib/flipper/adapters/pstore.rb +12 -11
- data/lib/flipper/adapters/read_only.rb +6 -6
- data/lib/flipper/dsl.rb +1 -3
- data/lib/flipper/feature.rb +11 -16
- data/lib/flipper/gate.rb +3 -3
- data/lib/flipper/gate_values.rb +6 -6
- data/lib/flipper/gates/group.rb +2 -2
- data/lib/flipper/gates/percentage_of_actors.rb +2 -2
- data/lib/flipper/instrumentation/log_subscriber.rb +2 -4
- data/lib/flipper/instrumentation/metriks.rb +1 -1
- data/lib/flipper/instrumentation/statsd.rb +1 -1
- data/lib/flipper/instrumentation/statsd_subscriber.rb +1 -3
- data/lib/flipper/instrumentation/subscriber.rb +11 -10
- data/lib/flipper/instrumenters/memory.rb +1 -5
- data/lib/flipper/instrumenters/noop.rb +1 -1
- data/lib/flipper/middleware/memoizer.rb +11 -27
- data/lib/flipper/middleware/setup_env.rb +44 -0
- data/lib/flipper/registry.rb +8 -10
- data/lib/flipper/spec/shared_adapter_specs.rb +45 -67
- data/lib/flipper/test/shared_adapter_test.rb +25 -31
- data/lib/flipper/typecast.rb +2 -2
- data/lib/flipper/types/actor.rb +2 -4
- data/lib/flipper/types/group.rb +1 -1
- data/lib/flipper/types/percentage.rb +2 -1
- data/lib/flipper/version.rb +1 -1
- data/spec/fixtures/feature.json +31 -0
- data/spec/flipper/adapters/http_spec.rb +148 -0
- data/spec/flipper/adapters/instrumented_spec.rb +20 -20
- data/spec/flipper/adapters/memoizable_spec.rb +59 -59
- data/spec/flipper/adapters/operation_logger_spec.rb +16 -16
- data/spec/flipper/adapters/pstore_spec.rb +6 -6
- data/spec/flipper/adapters/read_only_spec.rb +28 -34
- data/spec/flipper/dsl_spec.rb +73 -84
- data/spec/flipper/feature_check_context_spec.rb +27 -27
- data/spec/flipper/feature_spec.rb +186 -196
- data/spec/flipper/gate_spec.rb +11 -11
- data/spec/flipper/gate_values_spec.rb +46 -45
- data/spec/flipper/gates/actor_spec.rb +2 -2
- data/spec/flipper/gates/boolean_spec.rb +24 -23
- data/spec/flipper/gates/group_spec.rb +19 -19
- data/spec/flipper/gates/percentage_of_actors_spec.rb +10 -10
- data/spec/flipper/gates/percentage_of_time_spec.rb +2 -2
- data/spec/flipper/instrumentation/log_subscriber_spec.rb +20 -20
- data/spec/flipper/instrumentation/metriks_subscriber_spec.rb +20 -20
- data/spec/flipper/instrumentation/statsd_subscriber_spec.rb +11 -11
- data/spec/flipper/instrumenters/memory_spec.rb +5 -5
- data/spec/flipper/instrumenters/noop_spec.rb +6 -6
- data/spec/flipper/middleware/memoizer_spec.rb +83 -100
- data/spec/flipper/middleware/setup_env_spec.rb +76 -0
- data/spec/flipper/registry_spec.rb +35 -39
- data/spec/flipper/typecast_spec.rb +18 -18
- data/spec/flipper/types/actor_spec.rb +30 -29
- data/spec/flipper/types/boolean_spec.rb +8 -8
- data/spec/flipper/types/group_spec.rb +28 -28
- data/spec/flipper/types/percentage_spec.rb +14 -14
- data/spec/flipper_spec.rb +61 -54
- data/spec/helper.rb +26 -21
- data/spec/integration_spec.rb +121 -113
- data/spec/support/fake_udp_socket.rb +1 -1
- data/spec/support/spec_helpers.rb +32 -4
- data/test/adapters/pstore_test.rb +3 -3
- data/test/test_helper.rb +1 -1
- metadata +20 -5
@@ -1,5 +1,5 @@
|
|
1
|
-
require
|
2
|
-
require
|
1
|
+
require 'pstore'
|
2
|
+
require 'set'
|
3
3
|
|
4
4
|
module Flipper
|
5
5
|
module Adapters
|
@@ -17,7 +17,7 @@ module Flipper
|
|
17
17
|
attr_reader :path
|
18
18
|
|
19
19
|
# Public
|
20
|
-
def initialize(path =
|
20
|
+
def initialize(path = 'flipper.pstore')
|
21
21
|
@path = path
|
22
22
|
@store = ::PStore.new(path)
|
23
23
|
@name = :pstore
|
@@ -55,14 +55,15 @@ module Flipper
|
|
55
55
|
result = {}
|
56
56
|
|
57
57
|
feature.gates.each do |gate|
|
58
|
-
result[gate.key] =
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
58
|
+
result[gate.key] =
|
59
|
+
case gate.data_type
|
60
|
+
when :boolean, :integer
|
61
|
+
read key(feature, gate)
|
62
|
+
when :set
|
63
|
+
set_members key(feature, gate)
|
64
|
+
else
|
65
|
+
raise "#{gate} is not supported by this adapter yet"
|
66
|
+
end
|
66
67
|
end
|
67
68
|
|
68
69
|
result
|
@@ -6,7 +6,7 @@ module Flipper
|
|
6
6
|
|
7
7
|
class WriteAttempted < Error
|
8
8
|
def initialize(message = nil)
|
9
|
-
super(message ||
|
9
|
+
super(message || 'write attempted while in read only mode')
|
10
10
|
end
|
11
11
|
end
|
12
12
|
|
@@ -27,23 +27,23 @@ module Flipper
|
|
27
27
|
@adapter.get(feature)
|
28
28
|
end
|
29
29
|
|
30
|
-
def add(
|
30
|
+
def add(_feature)
|
31
31
|
raise WriteAttempted
|
32
32
|
end
|
33
33
|
|
34
|
-
def remove(
|
34
|
+
def remove(_feature)
|
35
35
|
raise WriteAttempted
|
36
36
|
end
|
37
37
|
|
38
|
-
def clear(
|
38
|
+
def clear(_feature)
|
39
39
|
raise WriteAttempted
|
40
40
|
end
|
41
41
|
|
42
|
-
def enable(
|
42
|
+
def enable(_feature, _gate, _thing)
|
43
43
|
raise WriteAttempted
|
44
44
|
end
|
45
45
|
|
46
|
-
def disable(
|
46
|
+
def disable(_feature, _gate, _thing)
|
47
47
|
raise WriteAttempted
|
48
48
|
end
|
49
49
|
end
|
data/lib/flipper/dsl.rb
CHANGED
@@ -158,9 +158,7 @@ module Flipper
|
|
158
158
|
raise ArgumentError, "#{name} must be a String or Symbol"
|
159
159
|
end
|
160
160
|
|
161
|
-
@memoized_features[name.to_sym] ||= Feature.new(name, @adapter,
|
162
|
-
:instrumenter => instrumenter,
|
163
|
-
})
|
161
|
+
@memoized_features[name.to_sym] ||= Feature.new(name, @adapter, instrumenter: instrumenter)
|
164
162
|
end
|
165
163
|
|
166
164
|
# Public: Preload the features with the given names.
|
data/lib/flipper/feature.rb
CHANGED
@@ -41,7 +41,7 @@ module Flipper
|
|
41
41
|
#
|
42
42
|
# Returns the result of Adapter#enable.
|
43
43
|
def enable(thing = true)
|
44
|
-
instrument(:enable)
|
44
|
+
instrument(:enable) do |payload|
|
45
45
|
adapter.add self
|
46
46
|
|
47
47
|
gate = gate_for(thing)
|
@@ -50,14 +50,14 @@ module Flipper
|
|
50
50
|
payload[:thing] = wrapped_thing
|
51
51
|
|
52
52
|
adapter.enable self, gate, wrapped_thing
|
53
|
-
|
53
|
+
end
|
54
54
|
end
|
55
55
|
|
56
56
|
# Public: Disable this feature for something.
|
57
57
|
#
|
58
58
|
# Returns the result of Adapter#disable.
|
59
59
|
def disable(thing = false)
|
60
|
-
instrument(:disable)
|
60
|
+
instrument(:disable) do |payload|
|
61
61
|
adapter.add self
|
62
62
|
|
63
63
|
gate = gate_for(thing)
|
@@ -65,12 +65,8 @@ module Flipper
|
|
65
65
|
payload[:gate_name] = gate.name
|
66
66
|
payload[:thing] = wrapped_thing
|
67
67
|
|
68
|
-
|
69
|
-
|
70
|
-
else
|
71
|
-
adapter.disable self, gate, wrapped_thing
|
72
|
-
end
|
73
|
-
}
|
68
|
+
adapter.disable self, gate, wrapped_thing
|
69
|
+
end
|
74
70
|
end
|
75
71
|
|
76
72
|
# Public: Removes this feature.
|
@@ -84,14 +80,14 @@ module Flipper
|
|
84
80
|
#
|
85
81
|
# Returns true if enabled, false if not.
|
86
82
|
def enabled?(thing = nil)
|
87
|
-
instrument(:enabled?)
|
83
|
+
instrument(:enabled?) do |payload|
|
88
84
|
values = gate_values
|
89
85
|
thing = gate(:actor).wrap(thing) unless thing.nil?
|
90
86
|
payload[:thing] = thing
|
91
87
|
context = FeatureCheckContext.new(
|
92
88
|
feature_name: @name,
|
93
89
|
values: values,
|
94
|
-
thing: thing
|
90
|
+
thing: thing
|
95
91
|
)
|
96
92
|
|
97
93
|
if open_gate = gates.detect { |gate| gate.open?(context) }
|
@@ -100,7 +96,7 @@ module Flipper
|
|
100
96
|
else
|
101
97
|
false
|
102
98
|
end
|
103
|
-
|
99
|
+
end
|
104
100
|
end
|
105
101
|
|
106
102
|
# Public: Enables a feature for an actor.
|
@@ -346,19 +342,18 @@ module Flipper
|
|
346
342
|
# Returns a Flipper::Gate.
|
347
343
|
# Raises Flipper::GateNotFound if no gate found for thing
|
348
344
|
def gate_for(thing)
|
349
|
-
gates.detect { |gate| gate.protects?(thing) } ||
|
350
|
-
raise(GateNotFound.new(thing))
|
345
|
+
gates.detect { |gate| gate.protects?(thing) } || raise(GateNotFound, thing)
|
351
346
|
end
|
352
347
|
|
353
348
|
private
|
354
349
|
|
355
350
|
# Private: Instrument a feature operation.
|
356
351
|
def instrument(operation)
|
357
|
-
@instrumenter.instrument(InstrumentationName)
|
352
|
+
@instrumenter.instrument(InstrumentationName) do |payload|
|
358
353
|
payload[:feature_name] = name
|
359
354
|
payload[:operation] = operation
|
360
355
|
payload[:result] = yield(payload) if block_given?
|
361
|
-
|
356
|
+
end
|
362
357
|
end
|
363
358
|
end
|
364
359
|
end
|
data/lib/flipper/gate.rb
CHANGED
@@ -18,21 +18,21 @@ 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
25
|
# Internal: Check if a gate is open for a thing. Implemented in subclass.
|
26
26
|
#
|
27
27
|
# Returns true if gate open for thing, false if not.
|
28
|
-
def open?(
|
28
|
+
def open?(_thing, _value, _options = {})
|
29
29
|
false
|
30
30
|
end
|
31
31
|
|
32
32
|
# Internal: Check if a gate is protects a thing. Implemented in subclass.
|
33
33
|
#
|
34
34
|
# Returns true if gate protects thing, false if not.
|
35
|
-
def protects?(
|
35
|
+
def protects?(_thing)
|
36
36
|
false
|
37
37
|
end
|
38
38
|
|
data/lib/flipper/gate_values.rb
CHANGED
@@ -1,15 +1,15 @@
|
|
1
|
-
require
|
1
|
+
require 'set'
|
2
2
|
|
3
3
|
module Flipper
|
4
4
|
class GateValues
|
5
5
|
# Private: Array of instance variables that are readable through the []
|
6
6
|
# instance method.
|
7
7
|
LegitIvars = {
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
8
|
+
'boolean' => '@boolean',
|
9
|
+
'actors' => '@actors',
|
10
|
+
'groups' => '@groups',
|
11
|
+
'percentage_of_time' => '@percentage_of_time',
|
12
|
+
'percentage_of_actors' => '@percentage_of_actors',
|
13
13
|
}.freeze
|
14
14
|
|
15
15
|
attr_reader :boolean
|
data/lib/flipper/gates/group.rb
CHANGED
@@ -27,14 +27,14 @@ module Flipper
|
|
27
27
|
if context.thing.nil?
|
28
28
|
false
|
29
29
|
else
|
30
|
-
value.any?
|
30
|
+
value.any? do |name|
|
31
31
|
begin
|
32
32
|
group = Flipper.group(name)
|
33
33
|
group.match?(context.thing, context)
|
34
34
|
rescue GroupNotRegistered
|
35
35
|
false
|
36
36
|
end
|
37
|
-
|
37
|
+
end
|
38
38
|
end
|
39
39
|
end
|
40
40
|
|
@@ -29,8 +29,8 @@ module Flipper
|
|
29
29
|
|
30
30
|
if Types::Actor.wrappable?(context.thing)
|
31
31
|
actor = Types::Actor.wrap(context.thing)
|
32
|
-
|
33
|
-
Zlib.crc32(
|
32
|
+
id = "#{context.feature_name}#{actor.value}"
|
33
|
+
Zlib.crc32(id) % 100 < percentage
|
34
34
|
else
|
35
35
|
false
|
36
36
|
end
|
@@ -25,9 +25,7 @@ module Flipper
|
|
25
25
|
description = "Flipper feature(#{feature_name}) #{operation} #{result.inspect}"
|
26
26
|
details = "thing=#{thing.inspect}"
|
27
27
|
|
28
|
-
unless gate_name.nil?
|
29
|
-
details += " gate_name=#{gate_name}"
|
30
|
-
end
|
28
|
+
details += " gate_name=#{gate_name}" unless gate_name.nil?
|
31
29
|
|
32
30
|
name = '%s (%.1fms)' % [description, event.duration]
|
33
31
|
debug " #{color(name, CYAN, true)} [ #{details} ]"
|
@@ -54,7 +52,7 @@ module Flipper
|
|
54
52
|
operation = event.payload[:operation]
|
55
53
|
result = event.payload[:result]
|
56
54
|
|
57
|
-
description =
|
55
|
+
description = 'Flipper '
|
58
56
|
description << "feature(#{feature_name}) " unless feature_name.nil?
|
59
57
|
description << "adapter(#{adapter_name}) "
|
60
58
|
description << "#{operation} "
|
@@ -17,12 +17,12 @@ module Flipper
|
|
17
17
|
end
|
18
18
|
|
19
19
|
# Internal: Override in subclass.
|
20
|
-
def update_timer(
|
20
|
+
def update_timer(_metric)
|
21
21
|
raise 'not implemented'
|
22
22
|
end
|
23
23
|
|
24
24
|
# Internal: Override in subclass.
|
25
|
-
def update_counter(
|
25
|
+
def update_counter(_metric)
|
26
26
|
raise 'not implemented'
|
27
27
|
end
|
28
28
|
|
@@ -34,7 +34,8 @@ module Flipper
|
|
34
34
|
if respond_to?(method_name)
|
35
35
|
send(method_name)
|
36
36
|
else
|
37
|
-
puts "Could not update #{operation_type} metrics as #{self.class}
|
37
|
+
puts "Could not update #{operation_type} metrics as #{self.class} " \
|
38
|
+
"did not respond to `#{method_name}`"
|
38
39
|
end
|
39
40
|
end
|
40
41
|
|
@@ -49,11 +50,12 @@ module Flipper
|
|
49
50
|
update_timer "flipper.feature_operation.#{operation}"
|
50
51
|
|
51
52
|
if @payload[:operation] == :enabled?
|
52
|
-
metric_name =
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
53
|
+
metric_name =
|
54
|
+
if result
|
55
|
+
"flipper.feature.#{feature_name}.enabled"
|
56
|
+
else
|
57
|
+
"flipper.feature.#{feature_name}.disabled"
|
58
|
+
end
|
57
59
|
|
58
60
|
update_counter metric_name
|
59
61
|
end
|
@@ -67,11 +69,10 @@ module Flipper
|
|
67
69
|
value = @payload[:value]
|
68
70
|
key = @payload[:key]
|
69
71
|
|
70
|
-
|
71
72
|
update_timer "flipper.adapter.#{adapter_name}.#{operation}"
|
72
73
|
end
|
73
74
|
|
74
|
-
QUESTION_MARK =
|
75
|
+
QUESTION_MARK = '?'.freeze
|
75
76
|
|
76
77
|
# Private
|
77
78
|
def strip_trailing_question_mark(operation)
|
@@ -17,11 +17,7 @@ module Flipper
|
|
17
17
|
# block rather than the one passed to #instrument.
|
18
18
|
payload = payload.dup
|
19
19
|
|
20
|
-
result = if block_given?
|
21
|
-
yield payload
|
22
|
-
else
|
23
|
-
nil
|
24
|
-
end
|
20
|
+
result = (yield payload if block_given?)
|
25
21
|
@events << Event.new(name, payload, result)
|
26
22
|
result
|
27
23
|
end
|
@@ -3,43 +3,29 @@ require 'rack/body_proxy'
|
|
3
3
|
module Flipper
|
4
4
|
module Middleware
|
5
5
|
class Memoizer
|
6
|
-
# Public: Initializes an instance of the Memoizer middleware.
|
6
|
+
# Public: Initializes an instance of the Memoizer middleware. The flipper
|
7
|
+
# instance must be setup in the env of the request. You can do this by
|
8
|
+
# using the Flipper::Middleware::SetupEnv middleware.
|
7
9
|
#
|
8
10
|
# app - The app this middleware is included in.
|
9
|
-
# flipper_or_block - The Flipper::DSL instance or a block that yields a
|
10
|
-
# Flipper::DSL instance to use for all operations.
|
11
11
|
#
|
12
12
|
# Examples
|
13
13
|
#
|
14
|
-
#
|
15
|
-
# flipper = Flipper.new(...)
|
16
|
-
# use Flipper::Middleware::Memoizer, flipper
|
17
|
-
#
|
18
|
-
# # using with a block that yields a flipper instance
|
19
|
-
# use Flipper::Middleware::Memoizer, lambda { Flipper.new(...) }
|
14
|
+
# use Flipper::Middleware::Memoizer
|
20
15
|
#
|
21
16
|
# # using with preload_all features
|
22
|
-
# use Flipper::Middleware::Memoizer,
|
17
|
+
# use Flipper::Middleware::Memoizer, preload_all: true
|
23
18
|
#
|
24
19
|
# # using with preload specific features
|
25
|
-
# use Flipper::Middleware::Memoizer,
|
20
|
+
# use Flipper::Middleware::Memoizer, preload: [:stats, :search, :some_feature]
|
26
21
|
#
|
27
|
-
def initialize(app,
|
22
|
+
def initialize(app, opts = {})
|
28
23
|
@app = app
|
29
24
|
@opts = opts
|
30
|
-
|
31
|
-
if flipper_or_block.respond_to?(:call)
|
32
|
-
@flipper_block = flipper_or_block
|
33
|
-
else
|
34
|
-
@flipper = flipper_or_block
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
|
-
def flipper
|
39
|
-
@flipper ||= @flipper_block.call
|
40
25
|
end
|
41
26
|
|
42
27
|
def call(env)
|
28
|
+
flipper = env.fetch('flipper')
|
43
29
|
original = flipper.adapter.memoizing?
|
44
30
|
flipper.adapter.memoize = true
|
45
31
|
|
@@ -48,14 +34,12 @@ module Flipper
|
|
48
34
|
flipper.preload(names)
|
49
35
|
end
|
50
36
|
|
51
|
-
if @opts[:preload]
|
52
|
-
flipper.preload(@opts[:preload])
|
53
|
-
end
|
37
|
+
flipper.preload(@opts[:preload]) if @opts[:preload]
|
54
38
|
|
55
39
|
response = @app.call(env)
|
56
|
-
response[2] = Rack::BodyProxy.new(response[2])
|
40
|
+
response[2] = Rack::BodyProxy.new(response[2]) do
|
57
41
|
flipper.adapter.memoize = original
|
58
|
-
|
42
|
+
end
|
59
43
|
response
|
60
44
|
rescue
|
61
45
|
flipper.adapter.memoize = original
|