flipper 0.4.0 → 0.5.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.
- data/Guardfile +3 -8
- data/README.md +26 -38
- data/examples/percentage_of_actors.rb +17 -12
- data/examples/percentage_of_random.rb +3 -7
- data/lib/flipper.rb +8 -1
- data/lib/flipper/adapter.rb +2 -208
- data/lib/flipper/adapters/decorator.rb +9 -0
- data/lib/flipper/adapters/instrumented.rb +92 -0
- data/lib/flipper/adapters/memoizable.rb +88 -0
- data/lib/flipper/adapters/memory.rb +89 -7
- data/lib/flipper/adapters/operation_logger.rb +31 -45
- data/lib/flipper/decorator.rb +6 -0
- data/lib/flipper/dsl.rb +29 -2
- data/lib/flipper/feature.rb +83 -49
- data/lib/flipper/gate.rb +24 -41
- data/lib/flipper/gates/actor.rb +24 -24
- data/lib/flipper/gates/boolean.rb +28 -15
- data/lib/flipper/gates/group.rb +25 -34
- data/lib/flipper/gates/percentage_of_actors.rb +21 -13
- data/lib/flipper/gates/percentage_of_random.rb +20 -12
- data/lib/flipper/instrumentation/log_subscriber.rb +14 -22
- data/lib/flipper/middleware/memoizer.rb +23 -0
- data/lib/flipper/spec/shared_adapter_specs.rb +141 -92
- data/lib/flipper/types/boolean.rb +5 -1
- data/lib/flipper/version.rb +1 -1
- data/spec/flipper/adapters/instrumented_spec.rb +92 -0
- data/spec/flipper/adapters/memoizable_spec.rb +184 -0
- data/spec/flipper/adapters/memory_spec.rb +1 -11
- data/spec/flipper/adapters/operation_logger_spec.rb +93 -0
- data/spec/flipper/dsl_spec.rb +18 -43
- data/spec/flipper/feature_spec.rb +25 -9
- data/spec/flipper/gate_spec.rb +8 -20
- data/spec/flipper/gates/actor_spec.rb +6 -14
- data/spec/flipper/gates/boolean_spec.rb +80 -13
- data/spec/flipper/gates/group_spec.rb +8 -18
- data/spec/flipper/gates/percentage_of_actors_spec.rb +12 -28
- data/spec/flipper/gates/percentage_of_random_spec.rb +6 -14
- data/spec/flipper/instrumentation/log_subscriber_spec.rb +15 -8
- data/spec/flipper/instrumentation/metriks_subscriber_spec.rb +3 -6
- data/spec/flipper/middleware/{local_cache_spec.rb → memoizer_spec.rb} +25 -55
- data/spec/flipper/types/boolean_spec.rb +13 -3
- data/spec/flipper_spec.rb +7 -0
- data/spec/helper.rb +21 -3
- data/spec/integration_spec.rb +115 -116
- metadata +17 -27
- data/lib/flipper/adapters/memoized.rb +0 -55
- data/lib/flipper/key.rb +0 -38
- data/lib/flipper/middleware/local_cache.rb +0 -36
- data/lib/flipper/toggle.rb +0 -54
- data/lib/flipper/toggles/boolean.rb +0 -54
- data/lib/flipper/toggles/set.rb +0 -25
- data/lib/flipper/toggles/value.rb +0 -25
- data/spec/flipper/adapter_spec.rb +0 -463
- data/spec/flipper/adapters/memoized_spec.rb +0 -93
- data/spec/flipper/key_spec.rb +0 -23
- data/spec/flipper/toggle_spec.rb +0 -22
- data/spec/flipper/toggles/boolean_spec.rb +0 -40
- data/spec/flipper/toggles/set_spec.rb +0 -35
- data/spec/flipper/toggles/value_spec.rb +0 -55
@@ -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) [ thing
|
13
|
+
# # Flipper feature(search) enabled? false (1.2ms) [ thing=... ]
|
14
14
|
#
|
15
15
|
# Returns nothing.
|
16
16
|
def feature_operation(event)
|
@@ -39,36 +39,28 @@ module Flipper
|
|
39
39
|
# Example Output
|
40
40
|
#
|
41
41
|
# # log output for adapter operation with feature
|
42
|
-
# # Flipper feature(search) adapter(memory)
|
42
|
+
# # Flipper feature(search) adapter(memory) enable (0.0ms) [ result=...]
|
43
43
|
#
|
44
44
|
# # log output for adapter operation with no feature
|
45
|
-
# # Flipper adapter(memory)
|
45
|
+
# # Flipper adapter(memory) features (0.0ms) [ result=... ]
|
46
46
|
#
|
47
47
|
# Returns nothing.
|
48
48
|
def adapter_operation(event)
|
49
49
|
return unless logger.debug?
|
50
50
|
|
51
|
+
feature_name = event.payload[:feature_name]
|
51
52
|
adapter_name = event.payload[:adapter_name]
|
53
|
+
gate_name = event.payload[:gate_name]
|
52
54
|
operation = event.payload[:operation]
|
53
55
|
result = event.payload[:result]
|
54
|
-
value = event.payload[:value]
|
55
|
-
key = event.payload[:key]
|
56
56
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
end
|
57
|
+
description = "Flipper "
|
58
|
+
description << "feature(#{feature_name}) " unless feature_name.nil?
|
59
|
+
description << "adapter(#{adapter_name}) "
|
60
|
+
description << "#{operation} "
|
62
61
|
|
63
|
-
adapter_description = "adapter(#{adapter_name})"
|
64
|
-
operation_description = "#{operation}(#{key.to_s.inspect})"
|
65
|
-
description = "#{feature_description} #{adapter_description} #{operation_description}"
|
66
62
|
details = "result=#{result.inspect}"
|
67
63
|
|
68
|
-
if event.payload.key?(:value)
|
69
|
-
details += " value=#{value.inspect}"
|
70
|
-
end
|
71
|
-
|
72
64
|
name = '%s (%.1fms)' % [description, event.duration]
|
73
65
|
debug " #{color(name, CYAN, true)} [ #{details} ]"
|
74
66
|
end
|
@@ -78,11 +70,11 @@ module Flipper
|
|
78
70
|
# Example Output
|
79
71
|
#
|
80
72
|
# flipper[:search].enabled?(user)
|
81
|
-
# # Flipper feature(search) gate(boolean) open false (0.1ms) [ thing
|
82
|
-
# # Flipper feature(search) gate(group) open false (0.1ms) [ thing
|
83
|
-
# # Flipper feature(search) gate(actor) open false (0.1ms) [ thing
|
84
|
-
# # Flipper feature(search) gate(percentage_of_actors) open false (0.1ms) [ thing
|
85
|
-
# # Flipper feature(search) gate(percentage_of_random) open false (0.1ms) [ thing
|
73
|
+
# # Flipper feature(search) gate(boolean) open false (0.1ms) [ thing=... ]
|
74
|
+
# # Flipper feature(search) gate(group) open false (0.1ms) [ thing=... ]
|
75
|
+
# # Flipper feature(search) gate(actor) open false (0.1ms) [ thing=... ]
|
76
|
+
# # Flipper feature(search) gate(percentage_of_actors) open false (0.1ms) [ thing=... ]
|
77
|
+
# # Flipper feature(search) gate(percentage_of_random) open false (0.1ms) [ thing=... ]
|
86
78
|
#
|
87
79
|
# Returns nothing.
|
88
80
|
def gate_operation(event)
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'rack/body_proxy'
|
2
|
+
|
3
|
+
module Flipper
|
4
|
+
module Middleware
|
5
|
+
class Memoizer
|
6
|
+
def initialize(app, flipper)
|
7
|
+
@app = app
|
8
|
+
@flipper = flipper
|
9
|
+
end
|
10
|
+
|
11
|
+
def call(env)
|
12
|
+
original = @flipper.adapter.memoizing?
|
13
|
+
@flipper.adapter.memoize = true
|
14
|
+
|
15
|
+
response = @app.call(env)
|
16
|
+
response[2] = Rack::BodyProxy.new(response[2]) {
|
17
|
+
@flipper.adapter.memoize = original
|
18
|
+
}
|
19
|
+
response
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -1,124 +1,173 @@
|
|
1
|
-
|
1
|
+
# Requires the following methods:
|
2
|
+
# * subject - The instance of the adapter
|
3
|
+
shared_examples_for 'a flipper adapter' do
|
4
|
+
let(:actor_class) { Struct.new(:flipper_id) }
|
5
|
+
|
6
|
+
let(:flipper) { Flipper.new(subject) }
|
7
|
+
let(:feature) { flipper[:stats] }
|
8
|
+
|
9
|
+
let(:boolean_gate) { feature.gate(:boolean) }
|
10
|
+
let(:group_gate) { feature.gate(:group) }
|
11
|
+
let(:actor_gate) { feature.gate(:actor) }
|
12
|
+
let(:actors_gate) { feature.gate(:percentage_of_actors) }
|
13
|
+
let(:random_gate) { feature.gate(:percentage_of_random) }
|
2
14
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
15
|
+
before do
|
16
|
+
Flipper.register(:admins) { |actor|
|
17
|
+
actor.respond_to?(:admin?) && actor.admin?
|
18
|
+
}
|
19
|
+
|
20
|
+
Flipper.register(:early_access) { |actor|
|
21
|
+
actor.respond_to?(:early_access?) && actor.early_access?
|
22
|
+
}
|
7
23
|
end
|
8
|
-
end
|
9
24
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
# write_key(key, value)
|
14
|
-
shared_examples_for 'a flipper adapter' do
|
15
|
-
let(:key) { Flipper::Key.new(:foo, :bar) }
|
25
|
+
after do
|
26
|
+
Flipper.unregister_groups
|
27
|
+
end
|
16
28
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
read_key(key).should be_true
|
21
|
-
end
|
29
|
+
it "has name that is a symbol" do
|
30
|
+
subject.name.should_not be_nil
|
31
|
+
subject.name.should be_instance_of(Symbol)
|
22
32
|
end
|
23
33
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
end
|
34
|
+
it "has included the flipper adapter module" do
|
35
|
+
subject.class.ancestors.should include(Flipper::Adapter)
|
36
|
+
end
|
28
37
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
38
|
+
it "returns correct default values for the gates if none are enabled" do
|
39
|
+
subject.get(feature).should eq({
|
40
|
+
:boolean => nil,
|
41
|
+
:groups => Set.new,
|
42
|
+
:actors => Set.new,
|
43
|
+
:percentage_of_actors => nil,
|
44
|
+
:percentage_of_random => nil,
|
45
|
+
})
|
33
46
|
end
|
34
47
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
48
|
+
it "can enable, disable and get value for boolean gate" do
|
49
|
+
subject.enable(feature, boolean_gate, flipper.boolean).should be_true
|
50
|
+
|
51
|
+
result = subject.get(feature)
|
52
|
+
result[:boolean].should eq('true')
|
53
|
+
|
54
|
+
subject.disable(feature, boolean_gate, flipper.boolean(false)).should be_true
|
55
|
+
|
56
|
+
result = subject.get(feature)
|
57
|
+
result[:boolean].should be_nil
|
41
58
|
end
|
42
59
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
60
|
+
it "fully disables all enabled things when boolean gate disabled" do
|
61
|
+
actor_22 = actor_class.new('22')
|
62
|
+
subject.enable(feature, boolean_gate, flipper.boolean).should be_true
|
63
|
+
subject.enable(feature, group_gate, flipper.group(:admins)).should be_true
|
64
|
+
subject.enable(feature, actor_gate, flipper.actor(actor_22)).should be_true
|
65
|
+
subject.enable(feature, actors_gate, flipper.actors(25)).should be_true
|
66
|
+
subject.enable(feature, random_gate, flipper.random(45)).should be_true
|
67
|
+
|
68
|
+
subject.disable(feature, boolean_gate, flipper.boolean).should be_true
|
69
|
+
|
70
|
+
subject.get(feature).should eq({
|
71
|
+
:boolean => nil,
|
72
|
+
:groups => Set.new,
|
73
|
+
:actors => Set.new,
|
74
|
+
:percentage_of_actors => nil,
|
75
|
+
:percentage_of_random => nil,
|
76
|
+
})
|
77
|
+
end
|
48
78
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
79
|
+
it "can enable, disable and get value for group gate" do
|
80
|
+
subject.enable(feature, group_gate, flipper.group(:admins)).should be_true
|
81
|
+
subject.enable(feature, group_gate, flipper.group(:early_access)).should be_true
|
82
|
+
|
83
|
+
result = subject.get(feature)
|
84
|
+
result[:groups].should eq(Set['admins', 'early_access'])
|
85
|
+
|
86
|
+
subject.disable(feature, group_gate, flipper.group(:early_access)).should be_true
|
87
|
+
result = subject.get(feature)
|
88
|
+
result[:groups].should eq(Set['admins'])
|
89
|
+
|
90
|
+
subject.disable(feature, group_gate, flipper.group(:admins)).should be_true
|
91
|
+
result = subject.get(feature)
|
92
|
+
result[:groups].should eq(Set.new)
|
56
93
|
end
|
57
94
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
95
|
+
it "can enable, disable and get value for actor gate" do
|
96
|
+
actor_22 = actor_class.new('22')
|
97
|
+
actor_asdf = actor_class.new('asdf')
|
98
|
+
|
99
|
+
subject.enable(feature, actor_gate, flipper.actor(actor_22)).should be_true
|
100
|
+
subject.enable(feature, actor_gate, flipper.actor(actor_asdf)).should be_true
|
101
|
+
|
102
|
+
result = subject.get(feature)
|
103
|
+
result[:actors].should eq(Set['22', 'asdf'])
|
104
|
+
|
105
|
+
subject.disable(feature, actor_gate, flipper.actor(actor_22)).should be_true
|
106
|
+
result = subject.get(feature)
|
107
|
+
result[:actors].should eq(Set['asdf'])
|
64
108
|
|
65
|
-
|
66
|
-
|
67
|
-
|
109
|
+
subject.disable(feature, actor_gate, flipper.actor(actor_asdf)).should be_true
|
110
|
+
result = subject.get(feature)
|
111
|
+
result[:actors].should eq(Set.new)
|
68
112
|
end
|
69
113
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
114
|
+
it "can enable, disable and get value for percentage of actors gate" do
|
115
|
+
subject.enable(feature, actors_gate, flipper.actors(15)).should be_true
|
116
|
+
result = subject.get(feature)
|
117
|
+
result[:percentage_of_actors].should eq('15')
|
74
118
|
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
end
|
119
|
+
subject.disable(feature, actors_gate, flipper.actors(0)).should be_true
|
120
|
+
result = subject.get(feature)
|
121
|
+
result[:percentage_of_actors].should eq('0')
|
79
122
|
end
|
80
123
|
|
81
|
-
it "
|
82
|
-
|
124
|
+
it "can enable, disable and get value for percentage of random gate" do
|
125
|
+
subject.enable(feature, random_gate, flipper.random(10)).should be_true
|
126
|
+
result = subject.get(feature)
|
127
|
+
result[:percentage_of_random].should eq('10')
|
128
|
+
|
129
|
+
subject.disable(feature, random_gate, flipper.random(0)).should be_true
|
130
|
+
result = subject.get(feature)
|
131
|
+
result[:percentage_of_random].should eq('0')
|
83
132
|
end
|
84
133
|
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
134
|
+
it "converts boolean value to a string" do
|
135
|
+
subject.enable(feature, boolean_gate, flipper.boolean).should be_true
|
136
|
+
result = subject.get(feature)
|
137
|
+
result[:boolean].should eq('true')
|
138
|
+
end
|
90
139
|
|
91
|
-
|
92
|
-
|
140
|
+
it "converts the actor value to a string" do
|
141
|
+
subject.enable(feature, actor_gate, flipper.actor(actor_class.new(22))).should be_true
|
142
|
+
result = subject.get(feature)
|
143
|
+
result[:actors].should eq(Set['22'])
|
144
|
+
end
|
145
|
+
|
146
|
+
it "converts group value to a string" do
|
147
|
+
subject.enable(feature, group_gate, flipper.group(:admins)).should be_true
|
148
|
+
result = subject.get(feature)
|
149
|
+
result[:groups].should eq(Set['admins'])
|
150
|
+
end
|
93
151
|
|
94
|
-
|
95
|
-
|
96
|
-
|
152
|
+
it "converts percentage of random integer value to a string" do
|
153
|
+
subject.enable(feature, random_gate, flipper.random(10)).should be_true
|
154
|
+
result = subject.get(feature)
|
155
|
+
result[:percentage_of_random].should eq('10')
|
97
156
|
end
|
98
157
|
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
subject.set_members(key).should eq(Set['1', '2'])
|
104
|
-
subject.set_delete key, 2
|
105
|
-
subject.set_members(key).should eq(Set['1'])
|
106
|
-
end
|
158
|
+
it "converts percentage of actors integer value to a string" do
|
159
|
+
subject.enable(feature, actors_gate, flipper.actors(10)).should be_true
|
160
|
+
result = subject.get(feature)
|
161
|
+
result[:percentage_of_actors].should eq('10')
|
107
162
|
end
|
108
163
|
|
109
|
-
|
110
|
-
|
111
|
-
let(:feature) { instance[:feature] }
|
112
|
-
let(:actor) { Struct.new(:id).new(1) }
|
164
|
+
it "can add and list known features" do
|
165
|
+
subject.features.should eq(Set.new)
|
113
166
|
|
114
|
-
|
115
|
-
|
116
|
-
it_should_behave_like 'a working percentage'
|
117
|
-
end
|
167
|
+
subject.add(flipper[:stats]).should be_true
|
168
|
+
subject.features.should eq(Set['stats'])
|
118
169
|
|
119
|
-
|
120
|
-
|
121
|
-
it_should_behave_like 'a working percentage'
|
122
|
-
end
|
170
|
+
subject.add(flipper[:search]).should be_true
|
171
|
+
subject.features.should eq(Set['stats', 'search'])
|
123
172
|
end
|
124
173
|
end
|
data/lib/flipper/version.rb
CHANGED
@@ -0,0 +1,92 @@
|
|
1
|
+
require 'helper'
|
2
|
+
require 'flipper/adapters/memory'
|
3
|
+
require 'flipper/adapters/instrumented'
|
4
|
+
require 'flipper/instrumenters/memory'
|
5
|
+
require 'flipper/spec/shared_adapter_specs'
|
6
|
+
|
7
|
+
describe Flipper::Adapters::Instrumented do
|
8
|
+
let(:instrumenter) { Flipper::Instrumenters::Memory.new }
|
9
|
+
let(:adapter) { Flipper::Adapters::Memory.new }
|
10
|
+
let(:flipper) { Flipper.new(adapter) }
|
11
|
+
|
12
|
+
let(:feature) { flipper[:stats] }
|
13
|
+
let(:gate) { feature.gate(:percentage_of_actors) }
|
14
|
+
let(:thing) { flipper.actors(22) }
|
15
|
+
|
16
|
+
subject {
|
17
|
+
described_class.new(adapter, :instrumenter => instrumenter)
|
18
|
+
}
|
19
|
+
|
20
|
+
it_should_behave_like 'a flipper adapter'
|
21
|
+
|
22
|
+
describe "#get" do
|
23
|
+
it "records instrumentation" do
|
24
|
+
result = subject.get(feature)
|
25
|
+
|
26
|
+
event = instrumenter.events.last
|
27
|
+
event.should_not be_nil
|
28
|
+
event.name.should eq('adapter_operation.flipper')
|
29
|
+
event.payload[:operation].should eq(:get)
|
30
|
+
event.payload[:adapter_name].should eq(:memory)
|
31
|
+
event.payload[:feature_name].should eq(:stats)
|
32
|
+
event.payload[:result].should be(result)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
describe "#enable" do
|
37
|
+
it "records instrumentation" do
|
38
|
+
result = subject.enable(feature, gate, thing)
|
39
|
+
|
40
|
+
event = instrumenter.events.last
|
41
|
+
event.should_not be_nil
|
42
|
+
event.name.should eq('adapter_operation.flipper')
|
43
|
+
event.payload[:operation].should eq(:enable)
|
44
|
+
event.payload[:adapter_name].should eq(:memory)
|
45
|
+
event.payload[:feature_name].should eq(:stats)
|
46
|
+
event.payload[:gate_name].should eq(:percentage_of_actors)
|
47
|
+
event.payload[:result].should be(result)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
describe "#disable" do
|
52
|
+
it "records instrumentation" do
|
53
|
+
result = subject.disable(feature, gate, thing)
|
54
|
+
|
55
|
+
event = instrumenter.events.last
|
56
|
+
event.should_not be_nil
|
57
|
+
event.name.should eq('adapter_operation.flipper')
|
58
|
+
event.payload[:operation].should eq(:disable)
|
59
|
+
event.payload[:adapter_name].should eq(:memory)
|
60
|
+
event.payload[:feature_name].should eq(:stats)
|
61
|
+
event.payload[:gate_name].should eq(:percentage_of_actors)
|
62
|
+
event.payload[:result].should be(result)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
describe "#add" do
|
67
|
+
it "records instrumentation" do
|
68
|
+
result = subject.add(feature)
|
69
|
+
|
70
|
+
event = instrumenter.events.last
|
71
|
+
event.should_not be_nil
|
72
|
+
event.name.should eq('adapter_operation.flipper')
|
73
|
+
event.payload[:operation].should eq(:add)
|
74
|
+
event.payload[:adapter_name].should eq(:memory)
|
75
|
+
event.payload[:feature_name].should eq(:stats)
|
76
|
+
event.payload[:result].should be(result)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
describe "#features" do
|
81
|
+
it "records instrumentation" do
|
82
|
+
result = subject.features
|
83
|
+
|
84
|
+
event = instrumenter.events.last
|
85
|
+
event.should_not be_nil
|
86
|
+
event.name.should eq('adapter_operation.flipper')
|
87
|
+
event.payload[:operation].should eq(:features)
|
88
|
+
event.payload[:adapter_name].should eq(:memory)
|
89
|
+
event.payload[:result].should be(result)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|