flipper 0.27.0 → 0.28.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Changelog.md +24 -0
- data/benchmark/enabled_multiple_actors_ips.rb +20 -0
- data/examples/dsl.rb +3 -3
- data/examples/enabled_for_actor.rb +4 -2
- data/lib/flipper/dsl.rb +4 -4
- data/lib/flipper/errors.rb +3 -3
- data/lib/flipper/feature.rb +12 -10
- data/lib/flipper/feature_check_context.rb +8 -4
- data/lib/flipper/gate.rb +12 -11
- data/lib/flipper/gates/actor.rb +11 -8
- data/lib/flipper/gates/group.rb +4 -2
- data/lib/flipper/gates/percentage_of_actors.rb +4 -5
- data/lib/flipper/identifier.rb +2 -2
- data/lib/flipper/instrumentation/log_subscriber.rb +7 -3
- data/lib/flipper/instrumentation/subscriber.rb +0 -1
- data/lib/flipper/types/actor.rb +13 -13
- data/lib/flipper/types/group.rb +4 -4
- data/lib/flipper/version.rb +1 -1
- data/lib/flipper.rb +4 -3
- data/spec/flipper/dsl_spec.rb +5 -5
- data/spec/flipper/feature_check_context_spec.rb +5 -5
- data/spec/flipper/feature_spec.rb +76 -32
- data/spec/flipper/gates/boolean_spec.rb +1 -1
- 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/instrumentation/log_subscriber_spec.rb +5 -5
- data/spec/flipper/types/actor_spec.rb +45 -45
- data/spec/flipper/types/group_spec.rb +2 -2
- data/spec/flipper_integration_spec.rb +62 -50
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a581a01f7e6dade34fb261d0b6215f262125e4041a0095081dd54719db303656
|
4
|
+
data.tar.gz: 1d3df8718d8a46f88cb33e97e4ef2d193e0eae2dd2cf4eecd23fc87dd977d11a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e56bd86ca42668104197ad8b64596f7639bbf25c04ac48e3da6cff8f3d71736ab62282d9e01a13f2eb85fa7c058a11e2360e1a260fe249972e16f911d83ca2f5
|
7
|
+
data.tar.gz: a11b459234c76ac9ab928b26c9e12009736af655f602a04557d776dc72579877cf1b7ab38f4faecd54d49b765186ff0bf754eef174a96ee1f5a584daf2dacd16
|
data/Changelog.md
CHANGED
@@ -2,6 +2,30 @@
|
|
2
2
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
4
4
|
|
5
|
+
## 0.28.0
|
6
|
+
|
7
|
+
### Additions/Changes
|
8
|
+
|
9
|
+
* Allow multiple actors for Flipper.enabled?. Improves performance of feature flags for multiple actors and simplifies code for users of flipper. This likely breaks things for anyone using Flipper internal classes related to actors, but that isn't likely you so you should be fine.
|
10
|
+
```diff
|
11
|
+
- [user, user.team, user.org].any? { |actor| Flipper.enabled?(:my_feature, actor) }
|
12
|
+
+ Flipper.enabled?(:my_feature, user, user.team, user.org)
|
13
|
+
```
|
14
|
+
|
15
|
+
### Deprecations
|
16
|
+
|
17
|
+
* `:thing` in `enabled?` instrumentation payload. Use `:actors` instead.
|
18
|
+
```diff
|
19
|
+
ActiveSupport::Notifications.subscribe('enabled?.feature_operation.flipper') do |name, start, finish, id, payload|
|
20
|
+
- payload[:thing]
|
21
|
+
+ payload[:actors]
|
22
|
+
end
|
23
|
+
```
|
24
|
+
|
25
|
+
## 0.27.1
|
26
|
+
|
27
|
+
* Quick fix for missing require of "flipper/version" that was causing issues with some flipper-ui people.
|
28
|
+
|
5
29
|
## 0.27.0
|
6
30
|
|
7
31
|
* Easy Import/Export (https://github.com/jnunemaker/flipper/pull/709). This has some breaking changes but only if you are using flipper internals. If you are just using Flipper.* methods, you'll be fine.
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'bundler/setup'
|
2
|
+
require 'flipper'
|
3
|
+
require 'benchmark/ips'
|
4
|
+
|
5
|
+
actor1 = Flipper::Actor.new("User;1")
|
6
|
+
actor2 = Flipper::Actor.new("User;2")
|
7
|
+
actor3 = Flipper::Actor.new("User;3")
|
8
|
+
actor4 = Flipper::Actor.new("User;4")
|
9
|
+
actor5 = Flipper::Actor.new("User;5")
|
10
|
+
actor6 = Flipper::Actor.new("User;6")
|
11
|
+
actor7 = Flipper::Actor.new("User;7")
|
12
|
+
actor8 = Flipper::Actor.new("User;8")
|
13
|
+
|
14
|
+
actors = [actor1, actor2, actor3, actor4, actor5, actor6, actor7, actor8]
|
15
|
+
|
16
|
+
Benchmark.ips do |x|
|
17
|
+
x.report("with array of actors") { Flipper.enabled?(:foo, actors) }
|
18
|
+
x.report("with multiple enabled? checks") { actors.each { |actor| Flipper.enabled?(:foo, actor) } }
|
19
|
+
x.compare!
|
20
|
+
end
|
data/examples/dsl.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
require 'bundler/setup'
|
2
2
|
require 'flipper'
|
3
3
|
|
4
|
-
# create
|
4
|
+
# create an actor with an identifier
|
5
5
|
class Person < Struct.new(:id)
|
6
6
|
include Flipper::Identifier
|
7
7
|
end
|
@@ -58,8 +58,8 @@ responds_to_flipper_id = Struct.new(:flipper_id).new(10)
|
|
58
58
|
puts Flipper.actor(responds_to_flipper_id).inspect
|
59
59
|
|
60
60
|
# get an instance of an actor using an object
|
61
|
-
|
62
|
-
puts Flipper.actor(
|
61
|
+
actor = Struct.new(:flipper_id).new(22)
|
62
|
+
puts Flipper.actor(actor).inspect
|
63
63
|
|
64
64
|
# register a top level group
|
65
65
|
admins = Flipper.register(:admins) { |actor|
|
@@ -31,5 +31,7 @@ Flipper.enable_percentage_of_actors :pro_stats, 50
|
|
31
31
|
Flipper.enable_group :tweets, :admins
|
32
32
|
Flipper.enable_actor :posts, user2
|
33
33
|
|
34
|
-
pp Flipper.features.select { |feature| feature.enabled?(user1) }.map(&:name)
|
35
|
-
pp Flipper.features.select { |feature| feature.enabled?(user2) }.map(&:name)
|
34
|
+
pp Flipper.features.select { |feature| feature.enabled?(user1) }.map(&:name).sort
|
35
|
+
pp Flipper.features.select { |feature| feature.enabled?(user2) }.map(&:name).sort
|
36
|
+
pp Flipper.features.select { |feature| feature.enabled?(user1, user2) }.map(&:name).sort
|
37
|
+
pp Flipper.features.select { |feature| feature.enabled?([user2, user1]) }.map(&:name).sort
|
data/lib/flipper/dsl.rb
CHANGED
@@ -237,12 +237,12 @@ module Flipper
|
|
237
237
|
|
238
238
|
# Public: Wraps an object as a flipper actor.
|
239
239
|
#
|
240
|
-
#
|
240
|
+
# actor - The object that you would like to wrap.
|
241
241
|
#
|
242
242
|
# Returns an instance of Flipper::Types::Actor.
|
243
|
-
# Raises ArgumentError if
|
244
|
-
def actor(
|
245
|
-
Types::Actor.new(
|
243
|
+
# Raises ArgumentError if actor does not respond to `flipper_id`.
|
244
|
+
def actor(actor)
|
245
|
+
Types::Actor.new(actor)
|
246
246
|
end
|
247
247
|
|
248
248
|
# Public: Shortcut for getting a percentage of time instance.
|
data/lib/flipper/errors.rb
CHANGED
@@ -2,10 +2,10 @@ module Flipper
|
|
2
2
|
# Top level error that all other errors inherit from.
|
3
3
|
class Error < StandardError; end
|
4
4
|
|
5
|
-
# Raised when gate can not be found for
|
5
|
+
# Raised when gate can not be found for an actor.
|
6
6
|
class GateNotFound < Error
|
7
|
-
def initialize(
|
8
|
-
super "Could not find gate for #{
|
7
|
+
def initialize(actor)
|
8
|
+
super "Could not find gate for #{actor.inspect}"
|
9
9
|
end
|
10
10
|
end
|
11
11
|
|
data/lib/flipper/feature.rb
CHANGED
@@ -96,17 +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
|
-
|
102
|
+
def enabled?(*actors)
|
103
|
+
actors = actors.flatten.compact.map { |actor| Types::Actor.wrap(actor) }
|
104
|
+
actors = nil if actors.empty?
|
104
105
|
|
105
|
-
|
106
|
+
# thing is left for backwards compatibility
|
107
|
+
instrument(:enabled?, thing: actors&.first, actors: actors) do |payload|
|
106
108
|
context = FeatureCheckContext.new(
|
107
109
|
feature_name: @name,
|
108
110
|
values: gate_values,
|
109
|
-
|
111
|
+
actors: actors
|
110
112
|
)
|
111
113
|
|
112
114
|
if open_gate = gates.detect { |gate| gate.open?(context) }
|
@@ -359,14 +361,14 @@ module Flipper
|
|
359
361
|
gates_hash[name.to_sym]
|
360
362
|
end
|
361
363
|
|
362
|
-
# Public: Find the gate that protects
|
364
|
+
# Public: Find the gate that protects an actor.
|
363
365
|
#
|
364
|
-
#
|
366
|
+
# actor - The object for which you would like to find a gate
|
365
367
|
#
|
366
368
|
# Returns a Flipper::Gate.
|
367
|
-
# Raises Flipper::GateNotFound if no gate found for
|
368
|
-
def gate_for(
|
369
|
-
gates.detect { |gate| gate.protects?(
|
369
|
+
# Raises Flipper::GateNotFound if no gate found for actor
|
370
|
+
def gate_for(actor)
|
371
|
+
gates.detect { |gate| gate.protects?(actor) } || raise(GateNotFound, actor)
|
370
372
|
end
|
371
373
|
|
372
374
|
private
|
@@ -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(feature_name:, values:,
|
13
|
+
def initialize(feature_name:, values:, actors:)
|
14
14
|
@feature_name = feature_name
|
15
15
|
@values = values
|
16
|
-
@
|
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.
|
data/lib/flipper/gates/actor.rb
CHANGED
@@ -19,20 +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
|
-
return false
|
27
|
-
|
26
|
+
return false unless context.actors?
|
27
|
+
|
28
|
+
context.actors.any? do |actor|
|
29
|
+
context.values.actors.include?(actor.value)
|
30
|
+
end
|
28
31
|
end
|
29
32
|
|
30
|
-
def wrap(
|
31
|
-
Types::Actor.wrap(
|
33
|
+
def wrap(actor)
|
34
|
+
Types::Actor.wrap(actor)
|
32
35
|
end
|
33
36
|
|
34
|
-
def protects?(
|
35
|
-
Types::Actor.wrappable?(
|
37
|
+
def protects?(actor)
|
38
|
+
Types::Actor.wrappable?(actor)
|
36
39
|
end
|
37
40
|
end
|
38
41
|
end
|
data/lib/flipper/gates/group.rb
CHANGED
@@ -23,10 +23,12 @@ module Flipper
|
|
23
23
|
#
|
24
24
|
# Returns true if gate open for thing, false if not.
|
25
25
|
def open?(context)
|
26
|
-
return false
|
26
|
+
return false unless context.actors?
|
27
27
|
|
28
28
|
context.values.groups.any? do |name|
|
29
|
-
|
29
|
+
context.actors.any? do |actor|
|
30
|
+
Flipper.group(name).match?(actor, context)
|
31
|
+
end
|
30
32
|
end
|
31
33
|
end
|
32
34
|
|
@@ -26,13 +26,12 @@ module Flipper
|
|
26
26
|
SCALING_FACTOR = 1_000
|
27
27
|
private_constant :SCALING_FACTOR
|
28
28
|
|
29
|
-
# Internal: Checks if the gate is open for
|
29
|
+
# Internal: Checks if the gate is open for one or more actors.
|
30
30
|
#
|
31
|
-
# Returns true if gate open for
|
31
|
+
# Returns true if gate open for any actors, false if not.
|
32
32
|
def open?(context)
|
33
|
-
return false
|
34
|
-
|
35
|
-
id = "#{context.feature_name}#{context.thing.value}"
|
33
|
+
return false unless context.actors?
|
34
|
+
id = "#{context.feature_name}#{context.actors.map(&:value).sort.join}"
|
36
35
|
Zlib.crc32(id) % (100 * SCALING_FACTOR) < context.values.percentage_of_actors * SCALING_FACTOR
|
37
36
|
end
|
38
37
|
|
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,10 +20,14 @@ 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
|
|
data/lib/flipper/types/actor.rb
CHANGED
@@ -1,35 +1,35 @@
|
|
1
1
|
module Flipper
|
2
2
|
module Types
|
3
3
|
class Actor < Type
|
4
|
-
def self.wrappable?(
|
5
|
-
return false if
|
6
|
-
|
4
|
+
def self.wrappable?(actor)
|
5
|
+
return false if actor.nil?
|
6
|
+
actor.respond_to?(:flipper_id)
|
7
7
|
end
|
8
8
|
|
9
|
-
attr_reader :
|
9
|
+
attr_reader :actor
|
10
10
|
|
11
|
-
def initialize(
|
12
|
-
raise ArgumentError, '
|
11
|
+
def initialize(actor)
|
12
|
+
raise ArgumentError, 'actor cannot be nil' if actor.nil?
|
13
13
|
|
14
|
-
unless
|
15
|
-
raise ArgumentError, "#{
|
14
|
+
unless actor.respond_to?(:flipper_id)
|
15
|
+
raise ArgumentError, "#{actor.inspect} must respond to flipper_id, but does not"
|
16
16
|
end
|
17
17
|
|
18
|
-
@
|
19
|
-
@value =
|
18
|
+
@actor = actor
|
19
|
+
@value = actor.flipper_id.to_s
|
20
20
|
end
|
21
21
|
|
22
22
|
def respond_to?(*args)
|
23
|
-
super || @
|
23
|
+
super || @actor.respond_to?(*args)
|
24
24
|
end
|
25
25
|
|
26
26
|
if RUBY_VERSION >= '3.0'
|
27
27
|
def method_missing(name, *args, **kwargs, &block)
|
28
|
-
@
|
28
|
+
@actor.send name, *args, **kwargs, &block
|
29
29
|
end
|
30
30
|
else
|
31
31
|
def method_missing(name, *args, &block)
|
32
|
-
@
|
32
|
+
@actor.send name, *args, &block
|
33
33
|
end
|
34
34
|
end
|
35
35
|
end
|
data/lib/flipper/types/group.rb
CHANGED
@@ -16,16 +16,16 @@ module Flipper
|
|
16
16
|
@block = block
|
17
17
|
@single_argument = call_with_no_context?(@block)
|
18
18
|
else
|
19
|
-
@block = ->(
|
19
|
+
@block = ->(actor, context) { false }
|
20
20
|
@single_argument = false
|
21
21
|
end
|
22
22
|
end
|
23
23
|
|
24
|
-
def match?(
|
24
|
+
def match?(actor, context)
|
25
25
|
if @single_argument
|
26
|
-
@block.call(
|
26
|
+
@block.call(actor)
|
27
27
|
else
|
28
|
-
@block.call(
|
28
|
+
@block.call(actor, context)
|
29
29
|
end
|
30
30
|
end
|
31
31
|
|
data/lib/flipper/version.rb
CHANGED
data/lib/flipper.rb
CHANGED
@@ -72,12 +72,12 @@ module Flipper
|
|
72
72
|
#
|
73
73
|
# name - The Symbol name of the group.
|
74
74
|
# block - The block that should be used to determine if the group matches a
|
75
|
-
# given
|
75
|
+
# given actor.
|
76
76
|
#
|
77
77
|
# Examples
|
78
78
|
#
|
79
|
-
# Flipper.register(:admins) { |
|
80
|
-
#
|
79
|
+
# Flipper.register(:admins) { |actor|
|
80
|
+
# actor.respond_to?(:admin?) && actor.admin?
|
81
81
|
# }
|
82
82
|
#
|
83
83
|
# Returns a Flipper::Group.
|
@@ -165,5 +165,6 @@ require 'flipper/types/percentage'
|
|
165
165
|
require 'flipper/types/percentage_of_actors'
|
166
166
|
require 'flipper/types/percentage_of_time'
|
167
167
|
require 'flipper/typecast'
|
168
|
+
require 'flipper/version'
|
168
169
|
|
169
170
|
require "flipper/railtie" if defined?(Rails::Railtie)
|
data/spec/flipper/dsl_spec.rb
CHANGED
@@ -149,12 +149,12 @@ RSpec.describe Flipper::DSL do
|
|
149
149
|
end
|
150
150
|
|
151
151
|
describe '#actor' do
|
152
|
-
context 'for
|
152
|
+
context 'for an actor' do
|
153
153
|
it 'returns actor instance' do
|
154
|
-
|
155
|
-
|
156
|
-
expect(
|
157
|
-
expect(
|
154
|
+
actor = Flipper::Actor.new(33)
|
155
|
+
flipper_actor = subject.actor(actor)
|
156
|
+
expect(flipper_actor).to be_instance_of(Flipper::Types::Actor)
|
157
|
+
expect(flipper_actor.value).to eq('33')
|
158
158
|
end
|
159
159
|
end
|
160
160
|
|
@@ -1,12 +1,12 @@
|
|
1
1
|
RSpec.describe Flipper::FeatureCheckContext do
|
2
2
|
let(:feature_name) { :new_profiles }
|
3
3
|
let(:values) { Flipper::GateValues.new({}) }
|
4
|
-
let(:
|
4
|
+
let(:actor) { Flipper::Actor.new('5') }
|
5
5
|
let(:options) do
|
6
6
|
{
|
7
7
|
feature_name: feature_name,
|
8
8
|
values: values,
|
9
|
-
|
9
|
+
actors: [actor],
|
10
10
|
}
|
11
11
|
end
|
12
12
|
|
@@ -14,7 +14,7 @@ RSpec.describe Flipper::FeatureCheckContext do
|
|
14
14
|
instance = described_class.new(**options)
|
15
15
|
expect(instance.feature_name).to eq(feature_name)
|
16
16
|
expect(instance.values).to eq(values)
|
17
|
-
expect(instance.
|
17
|
+
expect(instance.actors).to eq([actor])
|
18
18
|
end
|
19
19
|
|
20
20
|
it 'requires feature_name' do
|
@@ -31,8 +31,8 @@ RSpec.describe Flipper::FeatureCheckContext do
|
|
31
31
|
end.to raise_error(ArgumentError)
|
32
32
|
end
|
33
33
|
|
34
|
-
it 'requires
|
35
|
-
options.delete(:
|
34
|
+
it 'requires actors' do
|
35
|
+
options.delete(:actors)
|
36
36
|
expect do
|
37
37
|
described_class.new(**options)
|
38
38
|
end.to raise_error(ArgumentError)
|