flipper 0.19.0 → 0.20.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
  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