flipper 0.14.0 → 0.15.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/Changelog.md +7 -0
- data/lib/flipper/adapter.rb +5 -42
- data/lib/flipper/adapters/sync.rb +3 -1
- data/lib/flipper/adapters/sync/feature_synchronizer.rb +1 -0
- data/lib/flipper/adapters/sync/synchronizer.rb +18 -4
- data/lib/flipper/errors.rb +8 -0
- data/lib/flipper/instrumenters/memory.rb +12 -0
- data/lib/flipper/version.rb +1 -1
- data/spec/flipper/adapters/sync/feature_synchronizer_spec.rb +23 -1
- data/spec/flipper/adapters/sync/synchronizer_spec.rb +64 -10
- data/spec/flipper/adapters/sync_spec.rb +6 -0
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e763a777dcfe1cdd82ce50066de8634e364050ca
|
4
|
+
data.tar.gz: 6ca538b415aba3638eb2ec2ffa713d964c2b8011
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: af7780249550d2d8a332de8b78715b1db2f0652699a010231292ffc2b8efed5b2059329fe3f8f191bc5cfbfffd145bb2f62697bf0e842a909c950f0f82c14022
|
7
|
+
data.tar.gz: 7fd80434279dd6e51f9084855ea9c892db1dac7cb839731b1775a42bc03c0b69b649adca3a88db3fc74f37a53127a7d759d67a9f7acdf50e9a02eb1cc1ae6f4e
|
data/Changelog.md
CHANGED
@@ -1,3 +1,10 @@
|
|
1
|
+
## master (to be 0.15.0)
|
2
|
+
|
3
|
+
* Move Flipper::UI configuration options to Flipper::UI::Configuration (https://github.com/jnunemaker/flipper/pull/345).
|
4
|
+
* Bug fix in adapter synchronizing and switched DSL#import to use Synchronizer (https://github.com/jnunemaker/flipper/pull/347).
|
5
|
+
* Fix AR adapter table name prefix/suffix bug (https://github.com/jnunemaker/flipper/pull/350).
|
6
|
+
* Allow feature names to end with "features" in UI (https://github.com/jnunemaker/flipper/pull/353).
|
7
|
+
|
1
8
|
## 0.14.0
|
2
9
|
|
3
10
|
* Changed sync_interval to be seconds instead of milliseconds.
|
data/lib/flipper/adapter.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require "flipper/adapters/sync/synchronizer"
|
2
|
+
|
1
3
|
module Flipper
|
2
4
|
# Adding a module include so we have some hooks for stuff down the road
|
3
5
|
module Adapter
|
@@ -37,55 +39,16 @@ module Flipper
|
|
37
39
|
result
|
38
40
|
end
|
39
41
|
|
40
|
-
# Public:
|
41
|
-
# values from provided adapter.
|
42
|
+
# Public: Ensure that adapter is in sync with source adapter provided.
|
42
43
|
#
|
43
|
-
# Returns
|
44
|
+
# Returns result of Synchronizer#call.
|
44
45
|
def import(source_adapter)
|
45
|
-
|
46
|
-
copy_features_and_gates(source_adapter)
|
47
|
-
nil
|
46
|
+
Adapters::Sync::Synchronizer.new(self, source_adapter, raise: true).call
|
48
47
|
end
|
49
48
|
|
50
49
|
# Public: Default config for a feature's gate values.
|
51
50
|
def default_config
|
52
51
|
self.class.default_config
|
53
52
|
end
|
54
|
-
|
55
|
-
private
|
56
|
-
|
57
|
-
# Private: Copy source adapter features and gate values into self.
|
58
|
-
def copy_features_and_gates(source_adapter)
|
59
|
-
source_adapter.features.each do |key|
|
60
|
-
source_feature = Flipper::Feature.new(key, source_adapter)
|
61
|
-
destination_feature = Flipper::Feature.new(key, self)
|
62
|
-
|
63
|
-
case source_feature.state
|
64
|
-
when :on
|
65
|
-
destination_feature.enable
|
66
|
-
when :conditional
|
67
|
-
source_feature.groups_value.each do |value|
|
68
|
-
destination_feature.enable_group(value)
|
69
|
-
end
|
70
|
-
|
71
|
-
source_feature.actors_value.each do |value|
|
72
|
-
destination_feature.enable_actor(Flipper::Actor.new(value))
|
73
|
-
end
|
74
|
-
|
75
|
-
destination_feature.enable_percentage_of_actors(source_feature.percentage_of_actors_value)
|
76
|
-
destination_feature.enable_percentage_of_time(source_feature.percentage_of_time_value)
|
77
|
-
when :off
|
78
|
-
destination_feature.add
|
79
|
-
end
|
80
|
-
end
|
81
|
-
end
|
82
|
-
|
83
|
-
# Private: Completely wipe adapter features and gate values.
|
84
|
-
def wipe
|
85
|
-
features.each do |key|
|
86
|
-
feature = Flipper::Feature.new(key, self)
|
87
|
-
remove(feature)
|
88
|
-
end
|
89
|
-
end
|
90
53
|
end
|
91
54
|
end
|
@@ -27,8 +27,10 @@ module Flipper
|
|
27
27
|
@local = local
|
28
28
|
@remote = remote
|
29
29
|
@synchronizer = options.fetch(:synchronizer) do
|
30
|
+
sync_options = {
|
31
|
+
raise: false,
|
32
|
+
}
|
30
33
|
instrumenter = options[:instrumenter]
|
31
|
-
sync_options = {}
|
32
34
|
sync_options[:instrumenter] = instrumenter if instrumenter
|
33
35
|
synchronizer = Synchronizer.new(@local, @remote, sync_options)
|
34
36
|
IntervalSynchronizer.new(synchronizer, interval: options[:interval])
|
@@ -6,15 +6,24 @@ require "flipper/adapters/sync/feature_synchronizer"
|
|
6
6
|
module Flipper
|
7
7
|
module Adapters
|
8
8
|
class Sync
|
9
|
-
#
|
9
|
+
# Public: Given a local and remote adapter, it can update the local to
|
10
10
|
# match the remote doing only the necessary enable/disable operations.
|
11
11
|
class Synchronizer
|
12
|
+
# Public: Initializes a new synchronizer.
|
13
|
+
#
|
14
|
+
# local - The Flipper adapter to get in sync with the remote.
|
15
|
+
# remote - The Flipper adapter that is source of truth that the local
|
16
|
+
# adapter should be brought in line with.
|
17
|
+
# options - The Hash of options.
|
18
|
+
# :instrumenter - The instrumenter used to instrument.
|
12
19
|
def initialize(local, remote, options = {})
|
13
20
|
@local = local
|
14
21
|
@remote = remote
|
15
22
|
@instrumenter = options.fetch(:instrumenter, Instrumenters::Noop)
|
23
|
+
@raise = options.fetch(:raise, true)
|
16
24
|
end
|
17
25
|
|
26
|
+
# Public: Forces a sync.
|
18
27
|
def call
|
19
28
|
@instrumenter.instrument("synchronizer_call.flipper") { sync }
|
20
29
|
end
|
@@ -23,8 +32,6 @@ module Flipper
|
|
23
32
|
|
24
33
|
def sync
|
25
34
|
local_get_all = @local.get_all
|
26
|
-
# TODO: Move remote get all to background thread to minimize impact to
|
27
|
-
# whatever is happening in main thread.
|
28
35
|
remote_get_all = @remote.get_all
|
29
36
|
|
30
37
|
# Sync all the gate values.
|
@@ -36,11 +43,18 @@ module Flipper
|
|
36
43
|
FeatureSynchronizer.new(feature, local_gate_values, remote_gate_values).call
|
37
44
|
end
|
38
45
|
|
39
|
-
# Add features that are missing
|
46
|
+
# Add features that are missing in local and present in remote.
|
40
47
|
features_to_add = remote_get_all.keys - local_get_all.keys
|
41
48
|
features_to_add.each { |key| Feature.new(key, @local).add }
|
49
|
+
|
50
|
+
# Remove features that are present in local and missing in remote.
|
51
|
+
features_to_remove = local_get_all.keys - remote_get_all.keys
|
52
|
+
features_to_remove.each { |key| Feature.new(key, @local).remove }
|
53
|
+
|
54
|
+
nil
|
42
55
|
rescue => exception
|
43
56
|
@instrumenter.instrument("synchronizer_exception.flipper", exception: exception)
|
57
|
+
raise if @raise
|
44
58
|
end
|
45
59
|
end
|
46
60
|
end
|
data/lib/flipper/errors.rb
CHANGED
@@ -29,4 +29,12 @@ module Flipper
|
|
29
29
|
super(message || default)
|
30
30
|
end
|
31
31
|
end
|
32
|
+
|
33
|
+
# Raised when accessing a configuration property that has been deprecated
|
34
|
+
class ConfigurationDeprecated < Flipper::Error
|
35
|
+
def initialize(message = nil)
|
36
|
+
default = "The configuration property has been deprecated"
|
37
|
+
super(message || default)
|
38
|
+
end
|
39
|
+
end
|
32
40
|
end
|
@@ -21,6 +21,18 @@ module Flipper
|
|
21
21
|
@events << Event.new(name, payload, result)
|
22
22
|
result
|
23
23
|
end
|
24
|
+
|
25
|
+
def events_by_name(name)
|
26
|
+
@events.select { |event| event.name == name }
|
27
|
+
end
|
28
|
+
|
29
|
+
def event_by_name(name)
|
30
|
+
events_by_name(name).first
|
31
|
+
end
|
32
|
+
|
33
|
+
def reset
|
34
|
+
@events = []
|
35
|
+
end
|
24
36
|
end
|
25
37
|
end
|
26
38
|
end
|
data/lib/flipper/version.rb
CHANGED
@@ -56,7 +56,29 @@ RSpec.describe Flipper::Adapters::Sync::FeatureSynchronizer do
|
|
56
56
|
end
|
57
57
|
end
|
58
58
|
|
59
|
-
context "when conditionally enabled" do
|
59
|
+
context "when remote conditionally enabled" do
|
60
|
+
it "disables feature locally and syncs conditional enablements" do
|
61
|
+
feature.enable
|
62
|
+
adapter.reset
|
63
|
+
remote_gate_values_hash = {
|
64
|
+
boolean: nil,
|
65
|
+
actors: Set["1"],
|
66
|
+
groups: Set["staff"],
|
67
|
+
percentage_of_time: 10,
|
68
|
+
percentage_of_actors: 15,
|
69
|
+
}
|
70
|
+
remote = Flipper::GateValues.new(remote_gate_values_hash)
|
71
|
+
|
72
|
+
described_class.new(feature, feature.gate_values, remote).call
|
73
|
+
|
74
|
+
local_gate_values_hash = adapter.get(feature)
|
75
|
+
expect(local_gate_values_hash.fetch(:boolean)).to be(nil)
|
76
|
+
expect(local_gate_values_hash.fetch(:actors)).to eq(Set["1"])
|
77
|
+
expect(local_gate_values_hash.fetch(:groups)).to eq(Set["staff"])
|
78
|
+
expect(local_gate_values_hash.fetch(:percentage_of_time)).to eq("10")
|
79
|
+
expect(local_gate_values_hash.fetch(:percentage_of_actors)).to eq("15")
|
80
|
+
end
|
81
|
+
|
60
82
|
it "adds remotely added actors" do
|
61
83
|
remote = Flipper::GateValues.new(actors: Set["1", "2"])
|
62
84
|
feature.enable_actor(Flipper::Actor.new("1"))
|
@@ -6,30 +6,84 @@ require "flipper/adapters/sync/synchronizer"
|
|
6
6
|
RSpec.describe Flipper::Adapters::Sync::Synchronizer do
|
7
7
|
let(:local) { Flipper::Adapters::Memory.new }
|
8
8
|
let(:remote) { Flipper::Adapters::Memory.new }
|
9
|
+
let(:local_flipper) { Flipper.new(local) }
|
10
|
+
let(:remote_flipper) { Flipper.new(remote) }
|
9
11
|
let(:instrumenter) { Flipper::Instrumenters::Memory.new }
|
10
12
|
|
11
13
|
subject { described_class.new(local, remote, instrumenter: instrumenter) }
|
12
14
|
|
13
15
|
it "instruments call" do
|
14
16
|
subject.call
|
15
|
-
|
16
|
-
|
17
|
-
|
17
|
+
expect(instrumenter.events_by_name("synchronizer_exception.flipper").size).to be(0)
|
18
|
+
|
19
|
+
events = instrumenter.events_by_name("synchronizer_call.flipper")
|
18
20
|
expect(events.size).to be(1)
|
19
21
|
end
|
20
22
|
|
21
|
-
it "
|
23
|
+
it "raises errors by default" do
|
22
24
|
exception = StandardError.new
|
23
25
|
expect(remote).to receive(:get_all).and_raise(exception)
|
24
26
|
|
25
|
-
expect { subject.call }.
|
27
|
+
expect { subject.call }.to raise_error(exception)
|
28
|
+
end
|
26
29
|
|
27
|
-
|
28
|
-
|
30
|
+
context "when raise disabled" do
|
31
|
+
subject do
|
32
|
+
options = {
|
33
|
+
instrumenter: instrumenter,
|
34
|
+
raise: false,
|
35
|
+
}
|
36
|
+
described_class.new(local, remote, options)
|
37
|
+
end
|
38
|
+
|
39
|
+
it "does not raise, but instruments exceptions for visibility" do
|
40
|
+
exception = StandardError.new
|
41
|
+
expect(remote).to receive(:get_all).and_raise(exception)
|
42
|
+
|
43
|
+
expect { subject.call }.not_to raise_error
|
44
|
+
|
45
|
+
events = instrumenter.events_by_name("synchronizer_exception.flipper")
|
46
|
+
expect(events.size).to be(1)
|
47
|
+
|
48
|
+
event = events[0]
|
49
|
+
expect(event.payload[:exception]).to eq(exception)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
describe '#call' do
|
54
|
+
it 'returns nothing' do
|
55
|
+
expect(subject.call).to be(nil)
|
56
|
+
expect(instrumenter.events_by_name("synchronizer_exception.flipper").size).to be(0)
|
29
57
|
end
|
30
|
-
expect(events.size).to be(1)
|
31
58
|
|
32
|
-
|
33
|
-
|
59
|
+
it 'syncs each remote feature to local' do
|
60
|
+
remote_flipper.enable(:search)
|
61
|
+
remote_flipper.enable_percentage_of_time(:logging, 10)
|
62
|
+
|
63
|
+
subject.call
|
64
|
+
expect(instrumenter.events_by_name("synchronizer_exception.flipper").size).to be(0)
|
65
|
+
|
66
|
+
expect(local_flipper[:search].boolean_value).to eq(true)
|
67
|
+
expect(local_flipper[:logging].percentage_of_time_value).to eq(10)
|
68
|
+
expect(local_flipper.features.map(&:key).sort).to eq(%w(logging search))
|
69
|
+
end
|
70
|
+
|
71
|
+
it 'adds features in remote that are not in local' do
|
72
|
+
remote_flipper.add(:search)
|
73
|
+
|
74
|
+
subject.call
|
75
|
+
expect(instrumenter.events_by_name("synchronizer_exception.flipper").size).to be(0)
|
76
|
+
|
77
|
+
expect(local_flipper.features.map(&:key)).to eq(["search"])
|
78
|
+
end
|
79
|
+
|
80
|
+
it 'removes features in local that are not in remote' do
|
81
|
+
local_flipper.add(:stats)
|
82
|
+
|
83
|
+
subject.call
|
84
|
+
expect(instrumenter.events_by_name("synchronizer_exception.flipper").size).to be(0)
|
85
|
+
|
86
|
+
expect(local_flipper.features.map(&:key)).to eq([])
|
87
|
+
end
|
34
88
|
end
|
35
89
|
end
|
@@ -194,4 +194,10 @@ RSpec.describe Flipper::Adapters::Sync do
|
|
194
194
|
expect(subject).to receive(:sync)
|
195
195
|
subject.get_all
|
196
196
|
end
|
197
|
+
|
198
|
+
it 'does not raise sync exceptions' do
|
199
|
+
exception = StandardError.new
|
200
|
+
expect(remote_adapter).to receive(:get_all).and_raise(exception)
|
201
|
+
expect { subject.get_all }.not_to raise_error
|
202
|
+
end
|
197
203
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: flipper
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.15.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- John Nunemaker
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-
|
11
|
+
date: 2018-05-12 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: Feature flipper is the act of enabling/disabling features in your application,
|
14
14
|
ideally without re-deploying or changing anything in your code base. Flipper makes
|
@@ -171,7 +171,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
171
171
|
version: '0'
|
172
172
|
requirements: []
|
173
173
|
rubyforge_project:
|
174
|
-
rubygems_version: 2.
|
174
|
+
rubygems_version: 2.4.5.4
|
175
175
|
signing_key:
|
176
176
|
specification_version: 4
|
177
177
|
summary: Feature flipper for ANYTHING
|