flipper 0.26.0 → 0.27.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +13 -11
- data/.github/workflows/examples.yml +5 -10
- data/Changelog.md +16 -0
- data/Gemfile +6 -3
- data/benchmark/enabled_ips.rb +10 -0
- data/benchmark/enabled_profile.rb +20 -0
- data/benchmark/instrumentation_ips.rb +21 -0
- data/benchmark/typecast_ips.rb +19 -0
- data/examples/api/basic.ru +3 -4
- data/examples/api/custom_memoized.ru +3 -4
- data/examples/api/memoized.ru +3 -4
- data/flipper.gemspec +0 -2
- data/lib/flipper/adapter.rb +23 -7
- data/lib/flipper/adapters/http.rb +11 -3
- data/lib/flipper/adapters/instrumented.rb +25 -2
- data/lib/flipper/adapters/memoizable.rb +19 -2
- data/lib/flipper/adapters/memory.rb +56 -39
- data/lib/flipper/adapters/operation_logger.rb +16 -3
- data/lib/flipper/adapters/poll/poller.rb +2 -125
- data/lib/flipper/adapters/poll.rb +4 -0
- data/lib/flipper/dsl.rb +1 -5
- data/lib/flipper/export.rb +26 -0
- data/lib/flipper/exporter.rb +17 -0
- data/lib/flipper/exporters/json/export.rb +32 -0
- data/lib/flipper/exporters/json/v1.rb +33 -0
- data/lib/flipper/feature.rb +22 -18
- data/lib/flipper/feature_check_context.rb +4 -4
- data/lib/flipper/gate_values.rb +0 -16
- data/lib/flipper/gates/actor.rb +2 -12
- data/lib/flipper/gates/boolean.rb +1 -1
- data/lib/flipper/gates/group.rb +4 -8
- data/lib/flipper/gates/percentage_of_actors.rb +9 -11
- data/lib/flipper/gates/percentage_of_time.rb +1 -2
- data/lib/flipper/instrumentation/subscriber.rb +8 -0
- data/lib/flipper/poller.rb +117 -0
- data/lib/flipper/spec/shared_adapter_specs.rb +23 -0
- data/lib/flipper/test/shared_adapter_test.rb +24 -0
- data/lib/flipper/typecast.rb +28 -15
- data/lib/flipper/version.rb +1 -1
- data/lib/flipper.rb +2 -1
- data/spec/fixtures/flipper_pstore_1679087600.json +46 -0
- data/spec/flipper/adapter_spec.rb +29 -2
- data/spec/flipper/adapters/http_spec.rb +25 -3
- data/spec/flipper/adapters/instrumented_spec.rb +28 -10
- data/spec/flipper/adapters/memoizable_spec.rb +30 -10
- data/spec/flipper/adapters/memory_spec.rb +3 -1
- data/spec/flipper/adapters/operation_logger_spec.rb +29 -10
- data/spec/flipper/dsl_spec.rb +20 -3
- data/spec/flipper/export_spec.rb +13 -0
- data/spec/flipper/exporter_spec.rb +16 -0
- data/spec/flipper/exporters/json/export_spec.rb +60 -0
- data/spec/flipper/exporters/json/v1_spec.rb +33 -0
- data/spec/flipper/feature_check_context_spec.rb +12 -12
- data/spec/flipper/gate_values_spec.rb +2 -33
- data/spec/flipper/gates/percentage_of_actors_spec.rb +1 -1
- data/spec/flipper/instrumentation/log_subscriber_spec.rb +10 -0
- data/spec/flipper/instrumentation/statsd_subscriber_spec.rb +10 -0
- data/spec/flipper/poller_spec.rb +47 -0
- data/spec/flipper/typecast_spec.rb +82 -3
- data/spec/flipper_spec.rb +7 -1
- data/spec/spec_helper.rb +1 -1
- data/spec/support/skippable.rb +18 -0
- metadata +25 -3
- data/.github/workflows/release.yml +0 -44
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 202d50faf999fd9ed12d91ef7f160f786892b9476ad86b815fd4be5390cf8992
|
4
|
+
data.tar.gz: 1f59767d2f210820f488e97a0388f84aa2d474ac7b573e1650a69104d15f384a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f4b8079927fe805c7c6200694c6b9d9b020539cf1835d62a713724adc1483c48399a3f0073c969d4f9619b0362d615c21813ed79470172ea7037e62cfbb791db
|
7
|
+
data.tar.gz: 8a389963c5386d4d9ef13be89e06f6f38e959dcb0c427b7950ffee628828d627ff567581da375d3e9eb8b3501ba7e24d0f4b82acdf6a04599fb0cf7a2385f250
|
data/.github/workflows/ci.yml
CHANGED
@@ -15,7 +15,7 @@ jobs:
|
|
15
15
|
--health-retries 5
|
16
16
|
strategy:
|
17
17
|
matrix:
|
18
|
-
ruby: ['2.6', '2.7', '3.0', '3.1']
|
18
|
+
ruby: ['2.6', '2.7', '3.0', '3.1', '3.2']
|
19
19
|
rails: ['5.2', '6.0.0', '6.1.0', '7.0.0']
|
20
20
|
exclude:
|
21
21
|
- ruby: "2.6"
|
@@ -26,15 +26,22 @@ jobs:
|
|
26
26
|
rails: "5.2"
|
27
27
|
- ruby: "3.1"
|
28
28
|
rails: "6.0.0"
|
29
|
+
- ruby: "3.2"
|
30
|
+
rails: "5.2"
|
31
|
+
- ruby: "3.2"
|
32
|
+
rails: "6.0.0"
|
33
|
+
- ruby: "3.2"
|
34
|
+
rails: "6.1.0"
|
29
35
|
env:
|
30
36
|
SQLITE3_VERSION: 1.4.1
|
31
37
|
REDIS_URL: redis://localhost:6379/0
|
32
38
|
CI: true
|
39
|
+
RAILS_VERSION: ${{ matrix.rails }}
|
33
40
|
steps:
|
34
41
|
- name: Setup memcached
|
35
42
|
uses: KeisukeYamashita/memcached-actions@v1
|
36
43
|
- name: Start MongoDB
|
37
|
-
uses: supercharge/mongodb-github-action@1.
|
44
|
+
uses: supercharge/mongodb-github-action@1.9.0
|
38
45
|
with:
|
39
46
|
mongodb-version: 4.0
|
40
47
|
- name: Check out repository code
|
@@ -46,17 +53,12 @@ jobs:
|
|
46
53
|
key: ${{ runner.os }}-gems-${{ matrix.ruby }}-${{ matrix.rails }}-${{ hashFiles('**/Gemfile.lock') }}
|
47
54
|
restore-keys: |
|
48
55
|
${{ runner.os }}-gems-${{ matrix.ruby }}-${{ matrix.rails }}-
|
56
|
+
- name: Install libpq-dev
|
57
|
+
run: sudo apt-get -yqq install libpq-dev
|
49
58
|
- name: Set up Ruby ${{ matrix.ruby }}
|
50
59
|
uses: ruby/setup-ruby@v1
|
51
60
|
with:
|
52
61
|
ruby-version: ${{ matrix.ruby }}
|
53
|
-
|
54
|
-
run: sudo apt-get -yqq install libpq-dev
|
55
|
-
- name: Install bundler
|
56
|
-
run: gem install bundler
|
62
|
+
bundler-cache: true # 'bundle install' and cache gems
|
57
63
|
- name: Run Rake with Rails ${{ matrix.rails }}
|
58
|
-
|
59
|
-
RAILS_VERSION: ${{ matrix.rails }}
|
60
|
-
run: |
|
61
|
-
bundle install --jobs 4 --retry 3
|
62
|
-
bundle exec rake
|
64
|
+
run: bundle exec rake
|
@@ -32,11 +32,12 @@ jobs:
|
|
32
32
|
SQLITE3_VERSION: 1.4.1
|
33
33
|
REDIS_URL: redis://localhost:6379/0
|
34
34
|
CI: true
|
35
|
+
RAILS_VERSION: ${{ matrix.rails }}
|
35
36
|
steps:
|
36
37
|
- name: Setup memcached
|
37
38
|
uses: KeisukeYamashita/memcached-actions@v1
|
38
39
|
- name: Start MongoDB
|
39
|
-
uses: supercharge/mongodb-github-action@1.
|
40
|
+
uses: supercharge/mongodb-github-action@1.9.0
|
40
41
|
with:
|
41
42
|
mongodb-version: 4.0
|
42
43
|
- name: Check out repository code
|
@@ -48,20 +49,14 @@ jobs:
|
|
48
49
|
key: ${{ runner.os }}-gems-${{ matrix.ruby }}-${{ matrix.rails }}-${{ hashFiles('**/Gemfile.lock') }}
|
49
50
|
restore-keys: |
|
50
51
|
${{ runner.os }}-gems-${{ matrix.ruby }}-${{ matrix.rails }}-
|
52
|
+
- name: Install libpq-dev
|
53
|
+
run: sudo apt-get -yqq install libpq-dev
|
51
54
|
- name: Set up Ruby ${{ matrix.ruby }}
|
52
55
|
uses: ruby/setup-ruby@v1
|
53
56
|
with:
|
54
57
|
ruby-version: ${{ matrix.ruby }}
|
55
|
-
|
56
|
-
run: sudo apt-get -yqq install libpq-dev
|
57
|
-
- name: Install bundler
|
58
|
-
run: gem install bundler
|
59
|
-
- name: Bundle install with Rails ${{ matrix.rails }}
|
60
|
-
env:
|
61
|
-
RAILS_VERSION: ${{ matrix.rails }}
|
62
|
-
run: bundle install --jobs 4 --retry 3
|
58
|
+
bundler-cache: true # 'bundle install' and cache gems
|
63
59
|
- name: Run Examples with Rails ${{ matrix.rails }}
|
64
60
|
env:
|
65
61
|
FLIPPER_CLOUD_TOKEN: ${{ secrets.FLIPPER_CLOUD_TOKEN }}
|
66
|
-
RAILS_VERSION: ${{ matrix.rails }}
|
67
62
|
run: script/examples
|
data/Changelog.md
CHANGED
@@ -2,6 +2,22 @@
|
|
2
2
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
4
4
|
|
5
|
+
## 0.27.0
|
6
|
+
|
7
|
+
* Easy Import/Export (https://github.com/jnunemaker/flipper/pull/709). This has some breaking changes but only if you are using flipper internals. If you are just using Flipper.* methods, you'll be fine.
|
8
|
+
|
9
|
+
## 0.26.2
|
10
|
+
|
11
|
+
* Improve Active Record Adapter get/get_multi/get_all performance by 5-10x when dealing with thousands of gate values (https://github.com/jnunemaker/flipper/pull/707).
|
12
|
+
|
13
|
+
## 0.26.1
|
14
|
+
|
15
|
+
* Improve `Flipper#enabled?` performance by ~37%-55% (https://github.com/jnunemaker/flipper/pull/706)
|
16
|
+
* Make Memory adapter threadsafe (https://github.com/jnunemaker/flipper/pull/702 and https://github.com/jnunemaker/flipper/pull/703)
|
17
|
+
* ActiveRecord adapter: wrap all reads/writes in `with_connection` (https://github.com/jnunemaker/flipper/pull/705)
|
18
|
+
* Improve performance of background polling (https://github.com/jnunemaker/flipper/pull/699)
|
19
|
+
* Remove executables directive from gem (https://github.com/jnunemaker/flipper/pull/693)
|
20
|
+
|
5
21
|
## 0.26.0
|
6
22
|
|
7
23
|
* Cloud Background Polling (https://github.com/jnunemaker/flipper/pull/682)
|
data/Gemfile
CHANGED
@@ -8,18 +8,21 @@ end
|
|
8
8
|
|
9
9
|
gem 'debug'
|
10
10
|
gem 'rake', '~> 12.3.3'
|
11
|
-
gem 'shotgun', '~> 0.9'
|
12
11
|
gem 'statsd-ruby', '~> 1.2.1'
|
13
12
|
gem 'rspec', '~> 3.0'
|
14
|
-
gem 'rack-test'
|
13
|
+
gem 'rack-test'
|
15
14
|
gem 'sqlite3', "~> #{ENV['SQLITE3_VERSION'] || '1.4.1'}"
|
16
15
|
gem 'rails', "~> #{ENV['RAILS_VERSION'] || '7.0.0'}"
|
17
|
-
gem 'minitest', '~> 5.
|
16
|
+
gem 'minitest', '~> 5.18'
|
18
17
|
gem 'minitest-documentation'
|
19
18
|
gem 'webmock', '~> 3.0'
|
20
19
|
gem 'ice_age'
|
21
20
|
gem 'redis-namespace'
|
22
21
|
gem 'webrick'
|
22
|
+
gem 'stackprof'
|
23
|
+
gem 'benchmark-ips'
|
24
|
+
gem 'stackprof-webnav'
|
25
|
+
gem 'flamegraph'
|
23
26
|
|
24
27
|
group(:guard) do
|
25
28
|
gem 'guard', '~> 2.15'
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'bundler/setup'
|
2
|
+
require 'flipper'
|
3
|
+
require 'stackprof'
|
4
|
+
require 'benchmark/ips'
|
5
|
+
|
6
|
+
flipper = Flipper.new(Flipper::Adapters::Memory.new)
|
7
|
+
feature = flipper.feature(:foo)
|
8
|
+
actor = Flipper::Actor.new("User;1")
|
9
|
+
|
10
|
+
profile = StackProf.run(mode: :wall, interval: 1_000) do
|
11
|
+
2_000_000.times do
|
12
|
+
feature.enabled?(actor)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
result = StackProf::Report.new(profile)
|
17
|
+
puts
|
18
|
+
result.print_text
|
19
|
+
puts "\n\n\n"
|
20
|
+
result.print_method(/Flipper::Feature#enabled?/)
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'bundler/setup'
|
2
|
+
require 'flipper'
|
3
|
+
require 'active_support/notifications'
|
4
|
+
require 'active_support/isolated_execution_state'
|
5
|
+
require 'benchmark/ips'
|
6
|
+
|
7
|
+
class FlipperSubscriber
|
8
|
+
def call(name, start, finish, id, payload)
|
9
|
+
end
|
10
|
+
|
11
|
+
ActiveSupport::Notifications.subscribe(/flipper/, new)
|
12
|
+
end
|
13
|
+
|
14
|
+
actor = Flipper::Actor.new("User;1")
|
15
|
+
bare = Flipper.new(Flipper::Adapters::Memory.new)
|
16
|
+
instrumented = Flipper.new(Flipper::Adapters::Memory.new, instrumenter: ActiveSupport::Notifications)
|
17
|
+
|
18
|
+
Benchmark.ips do |x|
|
19
|
+
x.report("with instrumentation") { instrumented.enabled?(:foo, actor) }
|
20
|
+
x.report("without instrumentation") { bare.enabled?(:foo, actor) }
|
21
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'bundler/setup'
|
2
|
+
require 'flipper'
|
3
|
+
require 'benchmark/ips'
|
4
|
+
|
5
|
+
Benchmark.ips do |x|
|
6
|
+
x.report("Typecast.to_boolean true") { Flipper::Typecast.to_boolean(true) }
|
7
|
+
x.report("Typecast.to_boolean 1") { Flipper::Typecast.to_boolean(1) }
|
8
|
+
x.report("Typecast.to_boolean 'true'") { Flipper::Typecast.to_boolean('true'.freeze) }
|
9
|
+
x.report("Typecast.to_boolean '1'") { Flipper::Typecast.to_boolean('1'.freeze) }
|
10
|
+
x.report("Typecast.to_boolean false") { Flipper::Typecast.to_boolean(false) }
|
11
|
+
|
12
|
+
x.report("Typecast.to_integer 1") { Flipper::Typecast.to_integer(1) }
|
13
|
+
x.report("Typecast.to_integer '1'") { Flipper::Typecast.to_integer('1') }
|
14
|
+
|
15
|
+
x.report("Typecast.to_float 1") { Flipper::Typecast.to_float(1) }
|
16
|
+
x.report("Typecast.to_float '1'") { Flipper::Typecast.to_float('1'.freeze) }
|
17
|
+
x.report("Typecast.to_float 1.01") { Flipper::Typecast.to_float(1) }
|
18
|
+
x.report("Typecast.to_float '1.01'") { Flipper::Typecast.to_float('1'.freeze) }
|
19
|
+
end
|
data/examples/api/basic.ru
CHANGED
@@ -1,19 +1,18 @@
|
|
1
1
|
#
|
2
2
|
# Usage:
|
3
|
-
# # if you want it to not reload and be really fast
|
4
3
|
# bin/rackup examples/api/basic.ru -p 9999
|
5
4
|
#
|
6
|
-
# # if you want reloading
|
7
|
-
# bin/shotgun examples/api/basic.ru -p 9999
|
8
|
-
#
|
9
5
|
# http://localhost:9999/
|
10
6
|
#
|
11
7
|
|
12
8
|
require 'bundler/setup'
|
9
|
+
require 'rack/reloader'
|
13
10
|
require "flipper/api"
|
14
11
|
require "flipper/adapters/pstore"
|
15
12
|
|
16
13
|
# You can uncomment this to get some default data:
|
17
14
|
# Flipper.enable :logging
|
18
15
|
|
16
|
+
use Rack::Reloader
|
17
|
+
|
19
18
|
run Flipper::Api.app
|
@@ -1,15 +1,12 @@
|
|
1
1
|
#
|
2
2
|
# Usage:
|
3
|
-
# # if you want it to not reload and be really fast
|
4
3
|
# bin/rackup examples/api/custom_memoized.ru -p 9999
|
5
4
|
#
|
6
|
-
# # if you want reloading
|
7
|
-
# bin/shotgun examples/api/custom_memoized.ru -p 9999
|
8
|
-
#
|
9
5
|
# http://localhost:9999/
|
10
6
|
#
|
11
7
|
|
12
8
|
require 'bundler/setup'
|
9
|
+
require 'rack/reloader'
|
13
10
|
require "active_support/notifications"
|
14
11
|
require "flipper/api"
|
15
12
|
require "flipper/adapters/pstore"
|
@@ -31,6 +28,8 @@ ActiveSupport::Notifications.subscribe(/.*/, ->(*args) {
|
|
31
28
|
# You can uncomment this to get some default data:
|
32
29
|
# flipper[:logging].enable_percentage_of_time 5
|
33
30
|
|
31
|
+
use Rack::Reloader
|
32
|
+
|
34
33
|
run Flipper::Api.app(flipper) { |builder|
|
35
34
|
builder.use Flipper::Middleware::SetupEnv, flipper
|
36
35
|
builder.use Flipper::Middleware::Memoizer, preload: true
|
data/examples/api/memoized.ru
CHANGED
@@ -1,15 +1,12 @@
|
|
1
1
|
#
|
2
2
|
# Usage:
|
3
|
-
# # if you want it to not reload and be really fast
|
4
3
|
# bin/rackup examples/api/memoized.ru -p 9999
|
5
4
|
#
|
6
|
-
# # if you want reloading
|
7
|
-
# bin/shotgun examples/api/memoized.ru -p 9999
|
8
|
-
#
|
9
5
|
# http://localhost:9999/
|
10
6
|
#
|
11
7
|
|
12
8
|
require 'bundler/setup'
|
9
|
+
require 'rack/reloader'
|
13
10
|
require "active_support/notifications"
|
14
11
|
require "flipper/api"
|
15
12
|
require "flipper/adapters/pstore"
|
@@ -38,6 +35,8 @@ Flipper.register(:admins) { |actor|
|
|
38
35
|
# You can uncomment this to get some default data:
|
39
36
|
# Flipper.enable :logging
|
40
37
|
|
38
|
+
use Rack::Reloader
|
39
|
+
|
41
40
|
run Flipper::Api.app { |builder|
|
42
41
|
builder.use Flipper::Middleware::Memoizer, preload: true
|
43
42
|
}
|
data/flipper.gemspec
CHANGED
@@ -13,7 +13,6 @@ end
|
|
13
13
|
|
14
14
|
ignored_files = plugin_files
|
15
15
|
ignored_files << Dir['script/*']
|
16
|
-
ignored_files << '.travis.yml'
|
17
16
|
ignored_files << '.gitignore'
|
18
17
|
ignored_files << 'Guardfile'
|
19
18
|
ignored_files.flatten!.uniq!
|
@@ -28,7 +27,6 @@ Gem::Specification.new do |gem|
|
|
28
27
|
gem.homepage = 'https://github.com/jnunemaker/flipper'
|
29
28
|
gem.license = 'MIT'
|
30
29
|
|
31
|
-
gem.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
|
32
30
|
gem.files = `git ls-files`.split("\n") - ignored_files + ['lib/flipper/version.rb']
|
33
31
|
gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") - ignored_test_files
|
34
32
|
gem.name = 'flipper'
|
data/lib/flipper/adapter.rb
CHANGED
@@ -1,7 +1,3 @@
|
|
1
|
-
require "set"
|
2
|
-
require "flipper/feature"
|
3
|
-
require "flipper/adapters/sync/synchronizer"
|
4
|
-
|
5
1
|
module Flipper
|
6
2
|
# Adding a module include so we have some hooks for stuff down the road
|
7
3
|
module Adapter
|
@@ -20,6 +16,11 @@ module Flipper
|
|
20
16
|
percentage_of_time: nil,
|
21
17
|
}
|
22
18
|
end
|
19
|
+
|
20
|
+
def from(source)
|
21
|
+
return source if source.is_a?(Flipper::Adapter)
|
22
|
+
source.adapter
|
23
|
+
end
|
23
24
|
end
|
24
25
|
|
25
26
|
# Public: Get all features and gate values in one call. Defaults to one call
|
@@ -43,9 +44,19 @@ module Flipper
|
|
43
44
|
|
44
45
|
# Public: Ensure that adapter is in sync with source adapter provided.
|
45
46
|
#
|
46
|
-
#
|
47
|
-
|
48
|
-
|
47
|
+
# source - The source dsl, adapter or export to import.
|
48
|
+
#
|
49
|
+
# Returns true if successful.
|
50
|
+
def import(source)
|
51
|
+
Adapters::Sync::Synchronizer.new(self, self.class.from(source), raise: true).call
|
52
|
+
true
|
53
|
+
end
|
54
|
+
|
55
|
+
# Public: Exports the adapter in a given format for a given format version.
|
56
|
+
#
|
57
|
+
# Returns a Flipper::Export instance.
|
58
|
+
def export(format: :json, version: 1)
|
59
|
+
Flipper::Exporter.build(format: format, version: version).call(self)
|
49
60
|
end
|
50
61
|
|
51
62
|
# Public: Default config for a feature's gate values.
|
@@ -54,3 +65,8 @@ module Flipper
|
|
54
65
|
end
|
55
66
|
end
|
56
67
|
end
|
68
|
+
|
69
|
+
require "set"
|
70
|
+
require "flipper/exporter"
|
71
|
+
require "flipper/feature"
|
72
|
+
require "flipper/adapters/sync/synchronizer"
|
@@ -39,7 +39,7 @@ module Flipper
|
|
39
39
|
|
40
40
|
def get_multi(features)
|
41
41
|
csv_keys = features.map(&:key).join(',')
|
42
|
-
response = @client.get("/features?keys=#{csv_keys}")
|
42
|
+
response = @client.get("/features?keys=#{csv_keys}&exclude_gate_names=true")
|
43
43
|
raise Error, response unless response.is_a?(Net::HTTPOK)
|
44
44
|
|
45
45
|
parsed_response = JSON.parse(response.body)
|
@@ -57,7 +57,7 @@ module Flipper
|
|
57
57
|
end
|
58
58
|
|
59
59
|
def get_all
|
60
|
-
response = @client.get("/features")
|
60
|
+
response = @client.get("/features?exclude_gate_names=true")
|
61
61
|
raise Error, response unless response.is_a?(Net::HTTPOK)
|
62
62
|
|
63
63
|
parsed_response = JSON.parse(response.body)
|
@@ -76,7 +76,7 @@ module Flipper
|
|
76
76
|
end
|
77
77
|
|
78
78
|
def features
|
79
|
-
response = @client.get('/features')
|
79
|
+
response = @client.get('/features?exclude_gate_names=true')
|
80
80
|
raise Error, response unless response.is_a?(Net::HTTPOK)
|
81
81
|
|
82
82
|
parsed_response = JSON.parse(response.body)
|
@@ -123,6 +123,14 @@ module Flipper
|
|
123
123
|
true
|
124
124
|
end
|
125
125
|
|
126
|
+
def import(source)
|
127
|
+
adapter = self.class.from(source)
|
128
|
+
export = adapter.export(format: :json, version: 1)
|
129
|
+
response = @client.post("/import", export.contents)
|
130
|
+
raise Error, response unless response.is_a?(Net::HTTPNoContent)
|
131
|
+
true
|
132
|
+
end
|
133
|
+
|
126
134
|
private
|
127
135
|
|
128
136
|
def request_body_for_gate(gate, value)
|
@@ -4,7 +4,7 @@ module Flipper
|
|
4
4
|
module Adapters
|
5
5
|
# Internal: Adapter that wraps another adapter and instruments all adapter
|
6
6
|
# operations.
|
7
|
-
class Instrumented
|
7
|
+
class Instrumented
|
8
8
|
include ::Flipper::Adapter
|
9
9
|
|
10
10
|
# Private: The name of instrumentation events.
|
@@ -24,7 +24,6 @@ module Flipper
|
|
24
24
|
# :instrumenter - What to use to instrument all the things.
|
25
25
|
#
|
26
26
|
def initialize(adapter, options = {})
|
27
|
-
super(adapter)
|
28
27
|
@adapter = adapter
|
29
28
|
@name = :instrumented
|
30
29
|
@instrumenter = options.fetch(:instrumenter, Instrumenters::Noop)
|
@@ -146,6 +145,30 @@ module Flipper
|
|
146
145
|
payload[:result] = @adapter.disable(feature, gate, thing)
|
147
146
|
end
|
148
147
|
end
|
148
|
+
|
149
|
+
def import(source)
|
150
|
+
default_payload = {
|
151
|
+
operation: :import,
|
152
|
+
adapter_name: @adapter.name,
|
153
|
+
}
|
154
|
+
|
155
|
+
@instrumenter.instrument(InstrumentationName, default_payload) do |payload|
|
156
|
+
payload[:result] = @adapter.import(source)
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
def export(format: :json, version: 1)
|
161
|
+
default_payload = {
|
162
|
+
operation: :export,
|
163
|
+
adapter_name: @adapter.name,
|
164
|
+
format: format,
|
165
|
+
version: version,
|
166
|
+
}
|
167
|
+
|
168
|
+
@instrumenter.instrument(InstrumentationName, default_payload) do |payload|
|
169
|
+
payload[:result] = @adapter.export(format: format, version: version)
|
170
|
+
end
|
171
|
+
end
|
149
172
|
end
|
150
173
|
end
|
151
174
|
end
|
@@ -5,7 +5,7 @@ module Flipper
|
|
5
5
|
# Internal: Adapter that wraps another adapter with the ability to memoize
|
6
6
|
# adapter calls in memory. Used by flipper dsl and the memoizer middleware
|
7
7
|
# to make it possible to memoize adapter calls for the duration of a request.
|
8
|
-
class Memoizable
|
8
|
+
class Memoizable
|
9
9
|
include ::Flipper::Adapter
|
10
10
|
|
11
11
|
FeaturesKey = :flipper_features
|
@@ -27,7 +27,6 @@ module Flipper
|
|
27
27
|
|
28
28
|
# Public
|
29
29
|
def initialize(adapter, cache = nil)
|
30
|
-
super(adapter)
|
31
30
|
@adapter = adapter
|
32
31
|
@name = :memoizable
|
33
32
|
@cache = cache || {}
|
@@ -128,6 +127,14 @@ module Flipper
|
|
128
127
|
@adapter.disable(feature, gate, thing).tap { expire_feature(feature) }
|
129
128
|
end
|
130
129
|
|
130
|
+
def import(source)
|
131
|
+
@adapter.import(source).tap { cache.clear if memoizing? }
|
132
|
+
end
|
133
|
+
|
134
|
+
def export(format: :json, version: 1)
|
135
|
+
@adapter.export(format: format, version: version)
|
136
|
+
end
|
137
|
+
|
131
138
|
# Internal: Turns local caching on/off.
|
132
139
|
#
|
133
140
|
# value - The Boolean that decides if local caching is on.
|
@@ -141,6 +148,16 @@ module Flipper
|
|
141
148
|
!!@memoize
|
142
149
|
end
|
143
150
|
|
151
|
+
if RUBY_VERSION >= '3.0'
|
152
|
+
def method_missing(name, *args, **kwargs, &block)
|
153
|
+
@adapter.send name, *args, **kwargs, &block
|
154
|
+
end
|
155
|
+
else
|
156
|
+
def method_missing(name, *args, &block)
|
157
|
+
@adapter.send name, *args, &block
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
144
161
|
private
|
145
162
|
|
146
163
|
def key_for(key)
|
@@ -1,4 +1,6 @@
|
|
1
|
-
require
|
1
|
+
require "flipper/adapter"
|
2
|
+
require "flipper/typecast"
|
3
|
+
require 'concurrent/atomic/read_write_lock'
|
2
4
|
|
3
5
|
module Flipper
|
4
6
|
module Adapters
|
@@ -14,86 +16,93 @@ module Flipper
|
|
14
16
|
|
15
17
|
# Public
|
16
18
|
def initialize(source = nil)
|
17
|
-
@source = source
|
19
|
+
@source = Typecast.features_hash(source)
|
18
20
|
@name = :memory
|
21
|
+
@lock = Concurrent::ReadWriteLock.new
|
19
22
|
end
|
20
23
|
|
21
24
|
# Public: The set of known features.
|
22
25
|
def features
|
23
|
-
@source.keys.to_set
|
26
|
+
@lock.with_read_lock { @source.keys }.to_set
|
24
27
|
end
|
25
28
|
|
26
29
|
# Public: Adds a feature to the set of known features.
|
27
30
|
def add(feature)
|
28
|
-
@source[feature.key] ||= default_config
|
31
|
+
@lock.with_write_lock { @source[feature.key] ||= default_config }
|
29
32
|
true
|
30
33
|
end
|
31
34
|
|
32
35
|
# Public: Removes a feature from the set of known features and clears
|
33
36
|
# all the values for the feature.
|
34
37
|
def remove(feature)
|
35
|
-
@source.delete(feature.key)
|
38
|
+
@lock.with_write_lock { @source.delete(feature.key) }
|
36
39
|
true
|
37
40
|
end
|
38
41
|
|
39
42
|
# Public: Clears all the gate values for a feature.
|
40
43
|
def clear(feature)
|
41
|
-
@source[feature.key] = default_config
|
44
|
+
@lock.with_write_lock { @source[feature.key] = default_config }
|
42
45
|
true
|
43
46
|
end
|
44
47
|
|
45
48
|
# Public
|
46
49
|
def get(feature)
|
47
|
-
@source[feature.key] || default_config
|
50
|
+
@lock.with_read_lock { @source[feature.key] } || default_config
|
48
51
|
end
|
49
52
|
|
50
53
|
def get_multi(features)
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
+
@lock.with_read_lock do
|
55
|
+
result = {}
|
56
|
+
features.each do |feature|
|
57
|
+
result[feature.key] = @source[feature.key] || default_config
|
58
|
+
end
|
59
|
+
result
|
54
60
|
end
|
55
|
-
result
|
56
61
|
end
|
57
62
|
|
58
63
|
def get_all
|
59
|
-
@source
|
64
|
+
@lock.with_read_lock { Typecast.features_hash(@source) }
|
60
65
|
end
|
61
66
|
|
62
67
|
# Public
|
63
68
|
def enable(feature, gate, thing)
|
64
|
-
@
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
69
|
+
@lock.with_write_lock do
|
70
|
+
@source[feature.key] ||= default_config
|
71
|
+
|
72
|
+
case gate.data_type
|
73
|
+
when :boolean
|
74
|
+
@source[feature.key] = default_config
|
75
|
+
@source[feature.key][gate.key] = thing.value.to_s
|
76
|
+
when :integer
|
77
|
+
@source[feature.key][gate.key] = thing.value.to_s
|
78
|
+
when :set
|
79
|
+
@source[feature.key][gate.key] << thing.value.to_s
|
80
|
+
else
|
81
|
+
raise "#{gate} is not supported by this adapter yet"
|
82
|
+
end
|
83
|
+
|
84
|
+
true
|
76
85
|
end
|
77
|
-
|
78
|
-
true
|
79
86
|
end
|
80
87
|
|
81
88
|
# Public
|
82
89
|
def disable(feature, gate, thing)
|
83
|
-
@
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
90
|
+
@lock.with_write_lock do
|
91
|
+
@source[feature.key] ||= default_config
|
92
|
+
|
93
|
+
case gate.data_type
|
94
|
+
when :boolean
|
95
|
+
@source[feature.key] = default_config
|
96
|
+
when :integer
|
97
|
+
@source[feature.key][gate.key] = thing.value.to_s
|
98
|
+
when :set
|
99
|
+
@source[feature.key][gate.key].delete thing.value.to_s
|
100
|
+
else
|
101
|
+
raise "#{gate} is not supported by this adapter yet"
|
102
|
+
end
|
103
|
+
|
104
|
+
true
|
94
105
|
end
|
95
|
-
|
96
|
-
true
|
97
106
|
end
|
98
107
|
|
99
108
|
# Public
|
@@ -104,6 +113,14 @@ module Flipper
|
|
104
113
|
]
|
105
114
|
"#<#{self.class.name}:#{object_id} #{attributes.join(', ')}>"
|
106
115
|
end
|
116
|
+
|
117
|
+
# Public: a more efficient implementation of import for this adapter
|
118
|
+
def import(source)
|
119
|
+
adapter = self.class.from(source)
|
120
|
+
get_all = Typecast.features_hash(adapter.get_all)
|
121
|
+
@lock.with_write_lock { @source.replace(get_all) }
|
122
|
+
true
|
123
|
+
end
|
107
124
|
end
|
108
125
|
end
|
109
126
|
end
|