flipper 0.19.1 → 0.20.1

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: 77bf46fd4c4a3bc04f7cfd38cbd48083dbd72855a809df63ab33a429bf8dc902
4
- data.tar.gz: 43bbedd3cb73e09b6323c9841dd4cec1ac5298a7c42ff68affab032dd7f63fc4
3
+ metadata.gz: 3419ab68fa52334e5a6f71463c213a039f819439dc71c836c8c3b70b4c80270b
4
+ data.tar.gz: d6ade104432d5f5eaab36f00ccd0b559ee9bd9af4772a224cb9f70d26f578062
5
5
  SHA512:
6
- metadata.gz: 98f7becbf89ad1fe63f7c6425b4600e80b27d1af7136fe61c1e3ce0a449dfce01e1720a00d3eee1e3cc08e4fedee055f9b9991c49d6c025e601337fc7ac1c10f
7
- data.tar.gz: f219af56cf0c3ae7ec6b43cf1a774b4e62c9e4b73ad8b5a8b88ae1c8556ee49c2051191e332c456d448fbca810f623f2add81f9d6040ec30fced90cdb48ab651
6
+ metadata.gz: 48bc857cb049418b020bc1216de81238df104321b62c7ff3b3967dfdc8dfedf18a1779d58c84db233cc67d9058a1b7beb295e671739a232f8384cf324879b950
7
+ data.tar.gz: b5e0c90341f3b0a4f61a72ee26810ccdec216f32bd7e07b022f15ca04b075eb44a53bd392afdb91d7a868d3c724c96b2a962a37ed54fd5614a3a5f385e0ac147
@@ -1,3 +1,15 @@
1
+ ## 0.20.1
2
+
3
+ ### Additions/Changes
4
+
5
+ * Just a minor tweak to cloud webhook middleware to provide more debugging information about why a hook wasn't successful.
6
+
7
+ ## 0.20.0
8
+
9
+ ### Additions/Changes
10
+
11
+ * Add support for webhooks to `Flipper::Cloud` (https://github.com/jnunemaker/flipper/pull/489).
12
+
1
13
  ## 0.19.1
2
14
 
3
15
  ### Additions/Changes
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'
@@ -7,7 +7,7 @@ One optimization that flipper provides is a memoizing middleware. The memoizing
7
7
  You can use the middleware like so for Rails:
8
8
 
9
9
  ```ruby
10
- # setup default instance (perhaps in config/initializer/flipper.rb)
10
+ # setup default instance (perhaps in config/initializers/flipper.rb)
11
11
  Flipper.configure do |config|
12
12
  config.default do
13
13
  Flipper.new(...)
@@ -15,7 +15,7 @@ Flipper.configure do |config|
15
15
  end
16
16
 
17
17
  # This assumes you setup a default flipper instance using configure.
18
- config.middleware.use Flipper::Middleware::Memoizer
18
+ Rails.configuration.middleware.use Flipper::Middleware::Memoizer
19
19
  ```
20
20
 
21
21
  **Note**: Be sure that the middleware is high enough up in your stack that all feature checks are wrapped.
@@ -23,8 +23,8 @@ config.middleware.use Flipper::Middleware::Memoizer
23
23
  **Also Note**: If you haven't setup a default instance, you can pass the instance to `SetupEnv` as `Memoizer` uses whatever is setup in the `env`:
24
24
 
25
25
  ```ruby
26
- config.middleware.use Flipper::Middleware::SetupEnv, -> { Flipper.new(...) }
27
- config.middleware.use Flipper::Middleware::Memoizer
26
+ Rails.configuration.middleware.use Flipper::Middleware::SetupEnv, -> { Flipper.new(...) }
27
+ Rails.configuration.middleware.use Flipper::Middleware::Memoizer
28
28
  ```
29
29
 
30
30
  ### Options
@@ -33,18 +33,18 @@ The Memoizer middleware also supports a few options. Use either `preload` or `pr
33
33
 
34
34
  * **`:preload`** - An `Array` of feature names (`Symbol`) to preload for every request. Useful if you have features that are used on every endpoint. `preload` uses `Adapter#get_multi` to attempt to load the features in one network call instead of N+1 network calls.
35
35
  ```ruby
36
- config.middleware.use Flipper::Middleware::Memoizer,
36
+ Rails.configuration.middleware.use Flipper::Middleware::Memoizer,
37
37
  preload: [:stats, :search, :some_feature]
38
38
  ```
39
39
  * **`:preload_all`** - A Boolean value (default: false) of whether or not all features should be preloaded. Using this results in a `preload_all` call with the result of `Adapter#get_all`. Any subsequent feature checks will be memoized and perform no network calls. I wouldn't recommend using this unless you have few features (< 100?) and nearly all of them are used on every request.
40
40
  ```ruby
41
- config.middleware.use Flipper::Middleware::Memoizer,
41
+ Rails.configuration.middleware.use Flipper::Middleware::Memoizer,
42
42
  preload_all: true
43
43
  ```
44
44
  * **`:unless`** - A block that prevents preloading and memoization if it evaluates to true.
45
45
  ```ruby
46
46
  # skip preloading and memoizing if path starts with /assets
47
- config.middleware.use Flipper::Middleware::Memoizer,
47
+ Rails.configuration.middleware.use Flipper::Middleware::Memoizer,
48
48
  unless: ->(request) { request.path.start_with?("/assets") }
49
49
  ```
50
50
 
@@ -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.1'.freeze
2
+ VERSION = '0.20.1'.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.1
4
+ version: 0.20.1
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-12-04 00:00:00.000000000 Z
11
+ date: 2020-12-31 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description:
14
14
  email:
@@ -55,6 +55,7 @@ files:
55
55
  - lib/flipper.rb
56
56
  - lib/flipper/actor.rb
57
57
  - lib/flipper/adapter.rb
58
+ - lib/flipper/adapters/dual_write.rb
58
59
  - lib/flipper/adapters/http.rb
59
60
  - lib/flipper/adapters/http/client.rb
60
61
  - lib/flipper/adapters/http/error.rb
@@ -104,6 +105,7 @@ files:
104
105
  - spec/fixtures/feature.json
105
106
  - spec/flipper/actor_spec.rb
106
107
  - spec/flipper/adapter_spec.rb
108
+ - spec/flipper/adapters/dual_write_spec.rb
107
109
  - spec/flipper/adapters/http_spec.rb
108
110
  - spec/flipper/adapters/instrumented_spec.rb
109
111
  - spec/flipper/adapters/memoizable_spec.rb
@@ -178,6 +180,7 @@ test_files:
178
180
  - spec/fixtures/feature.json
179
181
  - spec/flipper/actor_spec.rb
180
182
  - spec/flipper/adapter_spec.rb
183
+ - spec/flipper/adapters/dual_write_spec.rb
181
184
  - spec/flipper/adapters/http_spec.rb
182
185
  - spec/flipper/adapters/instrumented_spec.rb
183
186
  - spec/flipper/adapters/memoizable_spec.rb