flipper 0.26.0 → 0.27.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 +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
|