flipper 0.14.0 → 0.15.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 843f963e51439159d1fd4c6d27abefcbc091dd38
4
- data.tar.gz: 96b4532f699e3d1dc61a399be98d2494b47b3f36
3
+ metadata.gz: e763a777dcfe1cdd82ce50066de8634e364050ca
4
+ data.tar.gz: 6ca538b415aba3638eb2ec2ffa713d964c2b8011
5
5
  SHA512:
6
- metadata.gz: 23146daa1a8eff45c01d065f00d0b101b7929ebda1635bfd2e902427ca8c13f98cd97920176d1855d3d9798e3db508c401bde42e40dd18c2960d0eb5034c9aee
7
- data.tar.gz: c5943fff3072a0d9ddab4cb823270cc0c76b3f912a95f27d18053b03a73353a0bd0855437e3ab5a9f073df5d32b34d46531d65eec23619b2565ea02f9b75c6b5
6
+ metadata.gz: af7780249550d2d8a332de8b78715b1db2f0652699a010231292ffc2b8efed5b2059329fe3f8f191bc5cfbfffd145bb2f62697bf0e842a909c950f0f82c14022
7
+ data.tar.gz: 7fd80434279dd6e51f9084855ea9c892db1dac7cb839731b1775a42bc03c0b69b649adca3a88db3fc74f37a53127a7d759d67a9f7acdf50e9a02eb1cc1ae6f4e
@@ -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.
@@ -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: Wipe features and gate values and then import features and gate
41
- # values from provided adapter.
42
+ # Public: Ensure that adapter is in sync with source adapter provided.
42
43
  #
43
- # Returns nothing.
44
+ # Returns result of Synchronizer#call.
44
45
  def import(source_adapter)
45
- wipe
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])
@@ -39,6 +39,7 @@ module Flipper
39
39
  return if local_boolean_enabled?
40
40
  @feature.enable
41
41
  else
42
+ @feature.disable if local_boolean_enabled?
42
43
  sync_actors
43
44
  sync_groups
44
45
  sync_percentage_of_actors
@@ -6,15 +6,24 @@ require "flipper/adapters/sync/feature_synchronizer"
6
6
  module Flipper
7
7
  module Adapters
8
8
  class Sync
9
- # Internal: Given a local and remote adapter, it can update the local to
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
@@ -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
@@ -1,3 +1,3 @@
1
1
  module Flipper
2
- VERSION = '0.14.0'.freeze
2
+ VERSION = '0.15.0'.freeze
3
3
  end
@@ -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
- events = instrumenter.events.select do |event|
16
- event.name == "synchronizer_call.flipper"
17
- end
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 "does not raise, but instruments exceptions for visibility" do
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 }.not_to raise_error
27
+ expect { subject.call }.to raise_error(exception)
28
+ end
26
29
 
27
- events = instrumenter.events.select do |event|
28
- event.name == "synchronizer_exception.flipper"
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
- event = events[0]
33
- expect(event.payload[:exception]).to eq(exception)
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.14.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-03-27 00:00:00.000000000 Z
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.6.14
174
+ rubygems_version: 2.4.5.4
175
175
  signing_key:
176
176
  specification_version: 4
177
177
  summary: Feature flipper for ANYTHING