flipper 0.19.0 → 0.20.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
  SHA256:
3
- metadata.gz: 988b31a807cedba4ca7b26ce056207198bddd3ce4a3c8e2ad177bc22c1977f4b
4
- data.tar.gz: 89b7ae96bf65c9975edb06cdab1fc4a0d2453ee9cc876ffa7cb84a7ba44f4b91
3
+ metadata.gz: 45421dabd33028fffb39b87abc060d64c1ee8c896961ec8338a7766208c5288c
4
+ data.tar.gz: 7ce79453077c0e3adac958782bb04bec5031c027eba45394151424c9c6d71054
5
5
  SHA512:
6
- metadata.gz: b2cfb8bdebeb0de99f5f79258ae04eb547bbe06fc8242ee0c2d9c0557968942ffad9eecd4ecd80420cdbda85f75043daddaee80a7fab2bb4f7fcfa18de55d2af
7
- data.tar.gz: 39813690991745a27bdb3e03207ebd89e61b12b2d516fc470a74ea8770c4ebc2445d60a7f17b0870e6d7582d070b221d866a90244864f9a5dfe58d6f97b4c7f0
6
+ metadata.gz: 017436dc788bb6ff4da11b178a1e92a0fcf065bda88ff9b89f887f0acdd416f2364ede0c13512d61867580048e32a9cb10e7931a4dc0db6b45175d09bc0feaad
7
+ data.tar.gz: 892dbad3f907480350eb64cbebe24e5abd95a8053be98f583a313dfff1ec11059108d248f35a40ad55b226431eabb543b8a5f8401304f394c5610bef425d2a1a
@@ -1,13 +1,26 @@
1
- ## 0.19.0
1
+ ## 0.20.0
2
2
 
3
3
  ## Additions/Changes
4
4
 
5
+ * Add support for webhooks to `Flipper::Cloud` (https://github.com/jnunemaker/flipper/pull/489).
6
+
7
+ ## 0.19.1
8
+
9
+ ### Additions/Changes
10
+
11
+ * Bump rack-protection version to < 2.2 (https://github.com/jnunemaker/flipper/pull/487)
12
+ * Add memoizer_options to Flipper::Api.app (https://github.com/jnunemaker/flipper/commit/174ad4bb94046a25c432d3c53fe1ff9f5a76d838)
13
+
14
+ ## 0.19.0
15
+
16
+ ### Additions/Changes
17
+
5
18
  * 100% of actors is now considered conditional. Feature#on?, Feature#conditional?, Feature#state would all be affected. See https://github.com/jnunemaker/flipper/issues/463 for more.
6
19
  * Several doc updates.
7
20
 
8
21
  ## 0.18.0
9
22
 
10
- ## Additions/Changes
23
+ ### Additions/Changes
11
24
 
12
25
  * Add support for feature descriptions to flipper-ui (https://github.com/jnunemaker/flipper/pull/461).
13
26
  * Remove rubocop (https://github.com/jnunemaker/flipper/pull/469).
data/Gemfile CHANGED
@@ -17,6 +17,7 @@ gem 'rails', "~> #{ENV['RAILS_VERSION'] || '6.0.0'}"
17
17
  gem 'minitest', '~> 5.8'
18
18
  gem 'minitest-documentation'
19
19
  gem 'webmock', '~> 3.0'
20
+ gem 'climate_control'
20
21
 
21
22
  group(:guard) do
22
23
  gem 'guard', '~> 2.15'
@@ -0,0 +1,39 @@
1
+ require File.expand_path('../example_setup', __FILE__)
2
+
3
+ require 'flipper'
4
+ require 'flipper/adapters/operation_logger'
5
+ require 'flipper/instrumentation/log_subscriber'
6
+
7
+ Flipper.configure do |config|
8
+ config.default do
9
+ # pick an adapter, this uses memory, any will do
10
+ adapter = Flipper::Adapters::OperationLogger.new(Flipper::Adapters::Memory.new)
11
+
12
+ # pass adapter to handy DSL instance
13
+ Flipper.new(adapter)
14
+ end
15
+ end
16
+
17
+ Flipper.enable(:foo)
18
+ Flipper.enable(:bar)
19
+ Flipper.disable(:baz)
20
+ Flipper.disable(:wick)
21
+ # reset the operation logging adapter to empty for clarity
22
+ Flipper.adapter.reset
23
+
24
+ # Turn on memoization (the memoizing middleware does this per request).
25
+ Flipper.memoize = true
26
+
27
+ # Preload all the features.
28
+ Flipper.preload_all
29
+
30
+ # Do as many feature checks as your heart desires.
31
+ %w[foo bar baz wick].each do |name|
32
+ Flipper.enabled?(name)
33
+ end
34
+
35
+ # See that only one operation exists, a get_all (which is the preload_all).
36
+ pp Flipper.adapter.operations
37
+ # [#<Flipper::Adapters::OperationLogger::Operation:0x00007fdcfe1100e8
38
+ # @args=[],
39
+ # @type=:get_all>]
@@ -65,7 +65,8 @@ module Flipper
65
65
  :time, :percentage_of_time,
66
66
  :features, :feature, :[], :preload, :preload_all,
67
67
  :adapter, :add, :exist?, :remove, :import,
68
- :memoize=, :memoizing?
68
+ :memoize=, :memoizing?,
69
+ :sync, :sync_secret # For Flipper::Cloud. Will error for OSS Flipper.
69
70
 
70
71
  # Public: Use this to register a group by name.
71
72
  #
@@ -0,0 +1,67 @@
1
+ module Flipper
2
+ module Adapters
3
+ class DualWrite
4
+ include ::Flipper::Adapter
5
+
6
+ # Public: The name of the adapter.
7
+ attr_reader :name
8
+
9
+ # Public: Build a new sync instance.
10
+ #
11
+ # local - The local flipper adapter that should serve reads.
12
+ # remote - The remote flipper adapter that writes should go to first (in
13
+ # addition to the local adapter).
14
+ def initialize(local, remote, options = {})
15
+ @name = :dual_write
16
+ @local = local
17
+ @remote = remote
18
+ end
19
+
20
+ def features
21
+ @local.features
22
+ end
23
+
24
+ def get(feature)
25
+ @local.get(feature)
26
+ end
27
+
28
+ def get_multi(features)
29
+ @local.get_multi(features)
30
+ end
31
+
32
+ def get_all
33
+ @local.get_all
34
+ end
35
+
36
+ def add(feature)
37
+ result = @remote.add(feature)
38
+ @local.add(feature)
39
+ result
40
+ end
41
+
42
+ def remove(feature)
43
+ result = @remote.remove(feature)
44
+ @local.remove(feature)
45
+ result
46
+ end
47
+
48
+ def clear(feature)
49
+ result = @remote.clear(feature)
50
+ @local.clear(feature)
51
+ result
52
+ end
53
+
54
+ def enable(feature, gate, thing)
55
+ result = @remote.enable(feature, gate, thing)
56
+ @local.enable(feature, gate, thing)
57
+ result
58
+ end
59
+
60
+ def disable(feature, gate, thing)
61
+ result = @remote.disable(feature, gate, thing)
62
+ @local.disable(feature, gate, thing)
63
+ result
64
+ end
65
+ end
66
+ end
67
+ end
@@ -117,6 +117,11 @@ module Flipper
117
117
  def reset
118
118
  @operations.clear
119
119
  end
120
+
121
+ def inspect
122
+ inspect_id = ::Kernel::format "%x", (object_id * 2)
123
+ %(#<#{self.class}:0x#{inspect_id} @name=#{name.inspect}, @operations=#{@operations.inspect}, @adapter=#{@adapter.inspect}>)
124
+ end
120
125
  end
121
126
  end
122
127
  end
@@ -17,7 +17,7 @@ module Flipper
17
17
  # Public: Build a new sync instance.
18
18
  #
19
19
  # local - The local flipper adapter that should serve reads.
20
- # remote - The remote flipper adpater that should serve writes and update
20
+ # remote - The remote flipper adapter that should serve writes and update
21
21
  # the local on an interval.
22
22
  # interval - The Float or Integer number of seconds between syncs from
23
23
  # remote to local. Default value is set in IntervalSynchronizer.
@@ -34,26 +34,26 @@ module Flipper
34
34
  synchronizer = Synchronizer.new(@local, @remote, sync_options)
35
35
  IntervalSynchronizer.new(synchronizer, interval: options[:interval])
36
36
  end
37
- sync
37
+ synchronize
38
38
  end
39
39
 
40
40
  def features
41
- sync
41
+ synchronize
42
42
  @local.features
43
43
  end
44
44
 
45
45
  def get(feature)
46
- sync
46
+ synchronize
47
47
  @local.get(feature)
48
48
  end
49
49
 
50
50
  def get_multi(features)
51
- sync
51
+ synchronize
52
52
  @local.get_multi(features)
53
53
  end
54
54
 
55
55
  def get_all
56
- sync
56
+ synchronize
57
57
  @local.get_all
58
58
  end
59
59
 
@@ -89,7 +89,7 @@ module Flipper
89
89
 
90
90
  private
91
91
 
92
- def sync
92
+ def synchronize
93
93
  @synchronizer.call
94
94
  end
95
95
  end
@@ -15,6 +15,7 @@ module Flipper
15
15
  # adapter should be brought in line with.
16
16
  # options - The Hash of options.
17
17
  # :instrumenter - The instrumenter used to instrument.
18
+ # :raise - Should errors be raised (default: true).
18
19
  def initialize(local, remote, options = {})
19
20
  @local = local
20
21
  @remote = remote
@@ -273,5 +273,13 @@ module Flipper
273
273
  def import(flipper)
274
274
  adapter.import(flipper.adapter)
275
275
  end
276
+
277
+ # Cloud DSL method that does nothing for open source version.
278
+ def sync
279
+ end
280
+
281
+ # Cloud DSL method that does nothing for open source version.
282
+ def sync_secret
283
+ end
276
284
  end
277
285
  end
@@ -7,7 +7,8 @@ module Flipper
7
7
  #
8
8
  # app - The app this middleware is included in.
9
9
  # flipper_or_block - The Flipper::DSL instance or a block that yields a
10
- # Flipper::DSL instance to use for all operations.
10
+ # Flipper::DSL instance to use for all operations
11
+ # (optional, default: Flipper).
11
12
  #
12
13
  # Examples
13
14
  #
@@ -19,18 +20,27 @@ module Flipper
19
20
  # # using with a block that yields a flipper instance
20
21
  # use Flipper::Middleware::SetupEnv, lambda { Flipper.new(...) }
21
22
  #
22
- def initialize(app, flipper_or_block, options = {})
23
+ # # using default configured Flipper instance
24
+ # Flipper.configure do |config|
25
+ # config.default { Flipper.new(...) }
26
+ # end
27
+ # use Flipper::Middleware::SetupEnv
28
+ def initialize(app, flipper_or_block = nil, options = {})
23
29
  @app = app
24
30
  @env_key = options.fetch(:env_key, 'flipper')
25
31
 
26
32
  if flipper_or_block.respond_to?(:call)
27
33
  @flipper_block = flipper_or_block
28
34
  else
29
- @flipper = flipper_or_block
35
+ @flipper = flipper_or_block || Flipper
30
36
  end
31
37
  end
32
38
 
33
39
  def call(env)
40
+ dup.call!(env)
41
+ end
42
+
43
+ def call!(env)
34
44
  env[@env_key] ||= flipper
35
45
  @app.call(env)
36
46
  end
@@ -1,3 +1,3 @@
1
1
  module Flipper
2
- VERSION = '0.19.0'.freeze
2
+ VERSION = '0.20.0'.freeze
3
3
  end
@@ -0,0 +1,71 @@
1
+ require 'helper'
2
+ require 'flipper/adapters/dual_write'
3
+ require 'flipper/adapters/operation_logger'
4
+ require 'flipper/spec/shared_adapter_specs'
5
+ require 'active_support/notifications'
6
+
7
+ RSpec.describe Flipper::Adapters::DualWrite do
8
+ let(:local_adapter) do
9
+ Flipper::Adapters::OperationLogger.new Flipper::Adapters::Memory.new
10
+ end
11
+ let(:remote_adapter) do
12
+ Flipper::Adapters::OperationLogger.new Flipper::Adapters::Memory.new
13
+ end
14
+ let(:local) { Flipper.new(local_adapter) }
15
+ let(:remote) { Flipper.new(remote_adapter) }
16
+ let(:sync) { Flipper.new(subject) }
17
+
18
+ subject do
19
+ described_class.new(local_adapter, remote_adapter)
20
+ end
21
+
22
+ it_should_behave_like 'a flipper adapter'
23
+
24
+ it 'only uses local for #features' do
25
+ subject.features
26
+ end
27
+
28
+ it 'only uses local for #get' do
29
+ subject.get sync[:search]
30
+ end
31
+
32
+ it 'only uses local for #get_multi' do
33
+ subject.get_multi [sync[:search]]
34
+ end
35
+
36
+ it 'only uses local for #get_all' do
37
+ subject.get_all
38
+ end
39
+
40
+ it 'updates remote and local for #add' do
41
+ subject.add sync[:search]
42
+ expect(remote_adapter.count(:add)).to be(1)
43
+ expect(local_adapter.count(:add)).to be(1)
44
+ end
45
+
46
+ it 'updates remote and local for #remove' do
47
+ subject.remove sync[:search]
48
+ expect(remote_adapter.count(:remove)).to be(1)
49
+ expect(local_adapter.count(:remove)).to be(1)
50
+ end
51
+
52
+ it 'updates remote and local for #clear' do
53
+ subject.clear sync[:search]
54
+ expect(remote_adapter.count(:clear)).to be(1)
55
+ expect(local_adapter.count(:clear)).to be(1)
56
+ end
57
+
58
+ it 'updates remote and local for #enable' do
59
+ feature = sync[:search]
60
+ subject.enable feature, feature.gate(:boolean), local.boolean
61
+ expect(remote_adapter.count(:enable)).to be(1)
62
+ expect(local_adapter.count(:enable)).to be(1)
63
+ end
64
+
65
+ it 'updates remote and local for #disable' do
66
+ feature = sync[:search]
67
+ subject.disable feature, feature.gate(:boolean), local.boolean(false)
68
+ expect(remote_adapter.count(:disable)).to be(1)
69
+ expect(local_adapter.count(:disable)).to be(1)
70
+ end
71
+ end
@@ -11,6 +11,15 @@ RSpec.describe Flipper::Adapters::OperationLogger do
11
11
 
12
12
  it_should_behave_like 'a flipper adapter'
13
13
 
14
+ it 'shows itself when inspect' do
15
+ subject.features
16
+ output = subject.inspect
17
+ expect(output).to match(/OperationLogger/)
18
+ expect(output).to match(/operation_logger/)
19
+ expect(output).to match(/@type=:features/)
20
+ expect(output).to match(/@adapter=#<Flipper::Adapters::Memory/)
21
+ end
22
+
14
23
  it 'forwards missing methods to underlying adapter' do
15
24
  adapter = Class.new do
16
25
  def foo
@@ -175,22 +175,22 @@ RSpec.describe Flipper::Adapters::Sync do
175
175
  end
176
176
 
177
177
  it 'synchronizes for #features' do
178
- expect(subject).to receive(:sync)
178
+ expect(subject).to receive(:synchronize)
179
179
  subject.features
180
180
  end
181
181
 
182
182
  it 'synchronizes for #get' do
183
- expect(subject).to receive(:sync)
183
+ expect(subject).to receive(:synchronize)
184
184
  subject.get sync[:search]
185
185
  end
186
186
 
187
187
  it 'synchronizes for #get_multi' do
188
- expect(subject).to receive(:sync)
188
+ expect(subject).to receive(:synchronize)
189
189
  subject.get_multi [sync[:search]]
190
190
  end
191
191
 
192
192
  it 'synchronizes for #get_all' do
193
- expect(subject).to receive(:sync)
193
+ expect(subject).to receive(:synchronize)
194
194
  subject.get_all
195
195
  end
196
196
 
@@ -56,21 +56,57 @@ RSpec.describe Flipper::Middleware::SetupEnv do
56
56
  end
57
57
  end
58
58
 
59
- context 'when flipper instance is nil' do
59
+ context 'when flipper instance or block are nil but env flipper is configured' do
60
60
  let(:app) do
61
61
  app = lambda do |env|
62
62
  [200, { 'Content-Type' => 'text/html' }, [env['flipper'].object_id.to_s]]
63
63
  end
64
64
  builder = Rack::Builder.new
65
- builder.use described_class, nil
65
+ builder.use described_class
66
66
  builder.run app
67
67
  builder
68
68
  end
69
69
 
70
- it 'leaves env flipper alone' do
70
+ it 'can use env flipper' do
71
71
  env_flipper = build_flipper
72
72
  get '/', {}, 'flipper' => env_flipper
73
73
  expect(last_response.body).to eq(env_flipper.object_id.to_s)
74
74
  end
75
75
  end
76
+
77
+ context 'when flipper instance or block are nil and default Flipper is configured' do
78
+ let(:app) do
79
+ Flipper.configure do |config|
80
+ config.default { flipper }
81
+ end
82
+ app = lambda do |env|
83
+ [200, { 'Content-Type' => 'text/html' }, [env['flipper'].object_id.to_s]]
84
+ end
85
+ builder = Rack::Builder.new
86
+ builder.use described_class
87
+ builder.run app
88
+ builder
89
+ end
90
+
91
+ it 'can use env flipper' do
92
+ get '/', {}, {}
93
+ expect(last_response.body).to eq(Flipper.object_id.to_s)
94
+ end
95
+ end
96
+
97
+ context 'when flipper instance or block are nil and default Flipper is NOT configured' do
98
+ let(:app) do
99
+ app = lambda do |env|
100
+ [200, { 'Content-Type' => 'text/html' }, [env['flipper'].enabled?(:search)]]
101
+ end
102
+ builder = Rack::Builder.new
103
+ builder.use described_class
104
+ builder.run app
105
+ builder
106
+ end
107
+
108
+ it 'can use env flipper' do
109
+ expect { get '/' }.to raise_error(Flipper::DefaultNotSet)
110
+ end
111
+ end
76
112
  end
@@ -1,4 +1,5 @@
1
1
  require 'helper'
2
+ require 'flipper/cloud'
2
3
 
3
4
  RSpec.describe Flipper do
4
5
  describe '.new' do
@@ -215,6 +216,32 @@ RSpec.describe Flipper do
215
216
  it 'delegates memoizing? to instance' do
216
217
  expect(described_class.memoizing?).to eq(described_class.adapter.memoizing?)
217
218
  end
219
+
220
+ it 'delegates sync stuff to instance and does nothing' do
221
+ expect(described_class.sync).to be(nil)
222
+ expect(described_class.sync_secret).to be(nil)
223
+ end
224
+
225
+ it 'delegates sync stuff to instance for Flipper::Cloud' do
226
+ stub = stub_request(:get, "https://www.flippercloud.io/adapter/features").
227
+ with({
228
+ headers: {
229
+ 'Flipper-Cloud-Token'=>'asdf',
230
+ },
231
+ }).to_return(status: 200, body: '{"features": {}}', headers: {})
232
+ cloud_configuration = Flipper::Cloud::Configuration.new({
233
+ token: "asdf",
234
+ sync_secret: "tasty",
235
+ sync_method: :webhook,
236
+ })
237
+
238
+ described_class.configure do |config|
239
+ config.default { Flipper::Cloud::DSL.new(cloud_configuration) }
240
+ end
241
+ described_class.sync
242
+ expect(described_class.sync_secret).to eq("tasty")
243
+ expect(stub).to have_been_requested
244
+ end
218
245
  end
219
246
 
220
247
  describe '.register' do
@@ -1,3 +1,4 @@
1
+ require 'climate_control'
1
2
  require 'json'
2
3
  require 'rack/test'
3
4
 
@@ -56,6 +57,10 @@ module SpecHelpers
56
57
  'more_info' => api_error_code_reference_url,
57
58
  }
58
59
  end
60
+
61
+ def with_modified_env(options, &block)
62
+ ClimateControl.modify(options, &block)
63
+ end
59
64
  end
60
65
 
61
66
  RSpec.configure do |config|
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.19.0
4
+ version: 0.20.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: 2020-09-25 00:00:00.000000000 Z
11
+ date: 2020-12-20 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description:
14
14
  email:
@@ -46,6 +46,7 @@ files:
46
46
  - examples/importing.rb
47
47
  - examples/individual_actor.rb
48
48
  - examples/instrumentation.rb
49
+ - examples/memoizing.rb
49
50
  - examples/percentage_of_actors.rb
50
51
  - examples/percentage_of_actors_enabled_check.rb
51
52
  - examples/percentage_of_actors_group.rb
@@ -54,6 +55,7 @@ files:
54
55
  - lib/flipper.rb
55
56
  - lib/flipper/actor.rb
56
57
  - lib/flipper/adapter.rb
58
+ - lib/flipper/adapters/dual_write.rb
57
59
  - lib/flipper/adapters/http.rb
58
60
  - lib/flipper/adapters/http/client.rb
59
61
  - lib/flipper/adapters/http/error.rb
@@ -103,6 +105,7 @@ files:
103
105
  - spec/fixtures/feature.json
104
106
  - spec/flipper/actor_spec.rb
105
107
  - spec/flipper/adapter_spec.rb
108
+ - spec/flipper/adapters/dual_write_spec.rb
106
109
  - spec/flipper/adapters/http_spec.rb
107
110
  - spec/flipper/adapters/instrumented_spec.rb
108
111
  - spec/flipper/adapters/memoizable_spec.rb
@@ -177,6 +180,7 @@ test_files:
177
180
  - spec/fixtures/feature.json
178
181
  - spec/flipper/actor_spec.rb
179
182
  - spec/flipper/adapter_spec.rb
183
+ - spec/flipper/adapters/dual_write_spec.rb
180
184
  - spec/flipper/adapters/http_spec.rb
181
185
  - spec/flipper/adapters/instrumented_spec.rb
182
186
  - spec/flipper/adapters/memoizable_spec.rb