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 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