flipper 0.26.2 → 0.28.3
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/Changelog.md +61 -0
- data/Gemfile +2 -3
- data/benchmark/enabled_multiple_actors_ips.rb +20 -0
- data/examples/api/basic.ru +3 -4
- data/examples/api/custom_memoized.ru +3 -4
- data/examples/api/memoized.ru +3 -4
- data/examples/dsl.rb +3 -3
- data/examples/enabled_for_actor.rb +4 -2
- data/examples/mirroring.rb +59 -0
- 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 +40 -16
- data/lib/flipper/adapters/operation_logger.rb +16 -3
- data/lib/flipper/dsl.rb +5 -9
- data/lib/flipper/errors.rb +3 -3
- 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 +12 -10
- data/lib/flipper/feature_check_context.rb +8 -4
- data/lib/flipper/gate.rb +12 -11
- data/lib/flipper/gates/actor.rb +11 -8
- data/lib/flipper/gates/group.rb +4 -2
- data/lib/flipper/gates/percentage_of_actors.rb +4 -5
- data/lib/flipper/identifier.rb +2 -2
- data/lib/flipper/instrumentation/log_subscriber.rb +24 -5
- data/lib/flipper/instrumentation/subscriber.rb +8 -1
- data/lib/flipper/poller.rb +1 -1
- 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 +17 -0
- data/lib/flipper/types/actor.rb +13 -13
- data/lib/flipper/types/group.rb +4 -4
- data/lib/flipper/version.rb +1 -1
- data/lib/flipper.rb +5 -4
- 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 +11 -2
- data/spec/flipper/adapters/operation_logger_spec.rb +29 -10
- data/spec/flipper/dsl_spec.rb +25 -8
- 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 +5 -5
- data/spec/flipper/feature_spec.rb +76 -32
- data/spec/flipper/gates/boolean_spec.rb +1 -1
- data/spec/flipper/gates/group_spec.rb +2 -3
- data/spec/flipper/gates/percentage_of_actors_spec.rb +61 -5
- data/spec/flipper/gates/percentage_of_time_spec.rb +2 -2
- data/spec/flipper/instrumentation/log_subscriber_spec.rb +15 -5
- data/spec/flipper/instrumentation/statsd_subscriber_spec.rb +10 -0
- data/spec/flipper/typecast_spec.rb +79 -0
- data/spec/flipper/types/actor_spec.rb +45 -45
- data/spec/flipper/types/group_spec.rb +2 -2
- data/spec/flipper_integration_spec.rb +62 -50
- data/spec/flipper_spec.rb +7 -1
- data/spec/support/skippable.rb +18 -0
- metadata +20 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 32360e70231522c5c5b41e5e9e889fd0d4df13c59ba223dd5416433d904954e5
|
4
|
+
data.tar.gz: 5ec60d72b7b00d4d4bb5458d5746a4df4b89f3656fa9d4c7bfeddb27caae9c81
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 12e61dc74aa548fe5b266c1d909e650190416d8350adfd12c608a84b16b5ede9cce4b753d74c5fed472506bfb0dd9a3e0e768bcb193f6f751408f9550513774e
|
7
|
+
data.tar.gz: a2c0dc70d0fd50848fe9aedc2ed9f8f396dd51f5b604badc1477efd0249104e9fa7276d5161ac95e25992d8b1765eadd491e9a1a1d79640c6644e9fdd69442f2
|
data/Changelog.md
CHANGED
@@ -2,6 +2,67 @@
|
|
2
2
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
4
4
|
|
5
|
+
## 0.28.3
|
6
|
+
|
7
|
+
* Updated cloud config to ensure that poll adapter ONLY syncs from cloud to local adapter (and never back to cloud). Shouldn't affect anyone other than making things more safe if an incorrect response is received from the cloud poll endpoint. (https://github.com/jnunemaker/flipper/pull/740)
|
8
|
+
|
9
|
+
## 0.28.2
|
10
|
+
|
11
|
+
* UI: fix path to bundled assets when mounted in another Rack app (https://github.com/jnunemaker/flipper/pull/742)
|
12
|
+
|
13
|
+
## 0.28.1
|
14
|
+
|
15
|
+
### Additions/Changes
|
16
|
+
|
17
|
+
* Use new method of making logs bold for rails (https://github.com/jnunemaker/flipper/pull/726)
|
18
|
+
* Bundle bootstrap, jquery and poppler with the library. (https://github.com/jnunemaker/flipper/pull/731)
|
19
|
+
|
20
|
+
## 0.28.0
|
21
|
+
|
22
|
+
### Additions/Changes
|
23
|
+
|
24
|
+
* Allow multiple actors for Flipper.enabled?. Improves performance of feature flags for multiple actors and simplifies code for users of flipper. This likely breaks things for anyone using Flipper internal classes related to actors, but that isn't likely you so you should be fine.
|
25
|
+
```diff
|
26
|
+
- [user, user.team, user.org].any? { |actor| Flipper.enabled?(:my_feature, actor) }
|
27
|
+
+ Flipper.enabled?(:my_feature, user, user.team, user.org)
|
28
|
+
```
|
29
|
+
* If you currently use `actor.thing` in a group, you'll need to change it to `actor.actor`.
|
30
|
+
```diff
|
31
|
+
- Flipper.register(:our_group) do |actor|
|
32
|
+
- actor.thing.is_a?(OurClassName)
|
33
|
+
- end
|
34
|
+
+ Flipper.register(:our_group) do |actor|
|
35
|
+
+ actor.actor.is_a?(OurClassName)
|
36
|
+
+ end
|
37
|
+
```
|
38
|
+
* If you currently use `context.thing` in a group or elsewhere, you'll need to change it to `context.actors`.
|
39
|
+
```diff
|
40
|
+
- Flipper.register(:our_group) do |actor, context|
|
41
|
+
- context.thing.is_a?(OurClassName)
|
42
|
+
- end
|
43
|
+
+ Flipper.register(:our_group) do |actor, context|
|
44
|
+
+ context.actors.any? { |actor| actor.is_a?(OurClassName) }
|
45
|
+
+ end
|
46
|
+
```
|
47
|
+
|
48
|
+
### Deprecations
|
49
|
+
|
50
|
+
* `:thing` in `enabled?` instrumentation payload. Use `:actors` instead.
|
51
|
+
```diff
|
52
|
+
ActiveSupport::Notifications.subscribe('enabled?.feature_operation.flipper') do |name, start, finish, id, payload|
|
53
|
+
- payload[:thing]
|
54
|
+
+ payload[:actors]
|
55
|
+
end
|
56
|
+
```
|
57
|
+
|
58
|
+
## 0.27.1
|
59
|
+
|
60
|
+
* Quick fix for missing require of "flipper/version" that was causing issues with some flipper-ui people.
|
61
|
+
|
62
|
+
## 0.27.0
|
63
|
+
|
64
|
+
* 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.
|
65
|
+
|
5
66
|
## 0.26.2
|
6
67
|
|
7
68
|
* 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).
|
data/Gemfile
CHANGED
@@ -8,13 +8,12 @@ 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'
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'bundler/setup'
|
2
|
+
require 'flipper'
|
3
|
+
require 'benchmark/ips'
|
4
|
+
|
5
|
+
actor1 = Flipper::Actor.new("User;1")
|
6
|
+
actor2 = Flipper::Actor.new("User;2")
|
7
|
+
actor3 = Flipper::Actor.new("User;3")
|
8
|
+
actor4 = Flipper::Actor.new("User;4")
|
9
|
+
actor5 = Flipper::Actor.new("User;5")
|
10
|
+
actor6 = Flipper::Actor.new("User;6")
|
11
|
+
actor7 = Flipper::Actor.new("User;7")
|
12
|
+
actor8 = Flipper::Actor.new("User;8")
|
13
|
+
|
14
|
+
actors = [actor1, actor2, actor3, actor4, actor5, actor6, actor7, actor8]
|
15
|
+
|
16
|
+
Benchmark.ips do |x|
|
17
|
+
x.report("with array of actors") { Flipper.enabled?(:foo, actors) }
|
18
|
+
x.report("with multiple enabled? checks") { actors.each { |actor| Flipper.enabled?(:foo, actor) } }
|
19
|
+
x.compare!
|
20
|
+
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/examples/dsl.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
require 'bundler/setup'
|
2
2
|
require 'flipper'
|
3
3
|
|
4
|
-
# create
|
4
|
+
# create an actor with an identifier
|
5
5
|
class Person < Struct.new(:id)
|
6
6
|
include Flipper::Identifier
|
7
7
|
end
|
@@ -58,8 +58,8 @@ responds_to_flipper_id = Struct.new(:flipper_id).new(10)
|
|
58
58
|
puts Flipper.actor(responds_to_flipper_id).inspect
|
59
59
|
|
60
60
|
# get an instance of an actor using an object
|
61
|
-
|
62
|
-
puts Flipper.actor(
|
61
|
+
actor = Struct.new(:flipper_id).new(22)
|
62
|
+
puts Flipper.actor(actor).inspect
|
63
63
|
|
64
64
|
# register a top level group
|
65
65
|
admins = Flipper.register(:admins) { |actor|
|
@@ -31,5 +31,7 @@ Flipper.enable_percentage_of_actors :pro_stats, 50
|
|
31
31
|
Flipper.enable_group :tweets, :admins
|
32
32
|
Flipper.enable_actor :posts, user2
|
33
33
|
|
34
|
-
pp Flipper.features.select { |feature| feature.enabled?(user1) }.map(&:name)
|
35
|
-
pp Flipper.features.select { |feature| feature.enabled?(user2) }.map(&:name)
|
34
|
+
pp Flipper.features.select { |feature| feature.enabled?(user1) }.map(&:name).sort
|
35
|
+
pp Flipper.features.select { |feature| feature.enabled?(user2) }.map(&:name).sort
|
36
|
+
pp Flipper.features.select { |feature| feature.enabled?(user1, user2) }.map(&:name).sort
|
37
|
+
pp Flipper.features.select { |feature| feature.enabled?([user2, user1]) }.map(&:name).sort
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'bundler/setup'
|
2
|
+
require_relative 'active_record/ar_setup'
|
3
|
+
require 'flipper'
|
4
|
+
require 'flipper/adapters/redis'
|
5
|
+
require 'flipper/adapters/active_record'
|
6
|
+
|
7
|
+
# Say you have production...
|
8
|
+
production_adapter = Flipper::Adapters::Memory.new
|
9
|
+
production = Flipper.new(production_adapter)
|
10
|
+
|
11
|
+
# And production has some stuff enabled...
|
12
|
+
production.enable(:search)
|
13
|
+
production.enable_percentage_of_time(:verbose_logging, 5)
|
14
|
+
production.enable_percentage_of_actors(:new_feature, 5)
|
15
|
+
production.enable_actor(:issues, Flipper::Actor.new('1'))
|
16
|
+
production.enable_actor(:issues, Flipper::Actor.new('2'))
|
17
|
+
production.enable_group(:request_tracing, :staff)
|
18
|
+
|
19
|
+
# And you would like to mirror production to staging...
|
20
|
+
staging_adapter = Flipper::Adapters::Memory.new
|
21
|
+
staging = Flipper.new(staging_adapter)
|
22
|
+
staging_export = staging.export
|
23
|
+
|
24
|
+
puts "Here is the state of the world for staging and production..."
|
25
|
+
puts "Staging"
|
26
|
+
staging.features.each do |feature|
|
27
|
+
pp feature: feature.key, values: feature.gate_values
|
28
|
+
end
|
29
|
+
puts "Production"
|
30
|
+
production.features.each do |feature|
|
31
|
+
pp feature: feature.key, values: feature.gate_values
|
32
|
+
end
|
33
|
+
|
34
|
+
# NOTE: This wipes active record clean and copies features/gates from redis into active record.
|
35
|
+
puts "Mirroring production to staging..."
|
36
|
+
staging.import(production.export)
|
37
|
+
puts "Staging is now identical to Production."
|
38
|
+
|
39
|
+
puts "Staging"
|
40
|
+
staging.features.each do |feature|
|
41
|
+
pp feature: feature.key, values: feature.gate_values
|
42
|
+
end
|
43
|
+
puts "Production"
|
44
|
+
production.features.each do |feature|
|
45
|
+
pp feature: feature.key, values: feature.gate_values
|
46
|
+
end
|
47
|
+
|
48
|
+
puts "Restoring staging to original state..."
|
49
|
+
staging.import(staging_export)
|
50
|
+
puts "Staging restored."
|
51
|
+
|
52
|
+
puts "Staging"
|
53
|
+
staging.features.each do |feature|
|
54
|
+
pp feature: feature.key, values: feature.gate_values
|
55
|
+
end
|
56
|
+
puts "Production"
|
57
|
+
production.features.each do |feature|
|
58
|
+
pp feature: feature.key, values: feature.gate_values
|
59
|
+
end
|
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,5 @@
|
|
1
|
-
require
|
1
|
+
require "flipper/adapter"
|
2
|
+
require "flipper/typecast"
|
2
3
|
|
3
4
|
module Flipper
|
4
5
|
module Adapters
|
@@ -13,43 +14,44 @@ module Flipper
|
|
13
14
|
attr_reader :name
|
14
15
|
|
15
16
|
# Public
|
16
|
-
def initialize(source = nil)
|
17
|
-
@source =
|
17
|
+
def initialize(source = nil, threadsafe: true)
|
18
|
+
@source = Typecast.features_hash(source)
|
18
19
|
@name = :memory
|
19
|
-
@lock =
|
20
|
+
@lock = Mutex.new if threadsafe
|
21
|
+
reset
|
20
22
|
end
|
21
23
|
|
22
24
|
# Public: The set of known features.
|
23
25
|
def features
|
24
|
-
|
26
|
+
synchronize { @source.keys }.to_set
|
25
27
|
end
|
26
28
|
|
27
29
|
# Public: Adds a feature to the set of known features.
|
28
30
|
def add(feature)
|
29
|
-
|
31
|
+
synchronize { @source[feature.key] ||= default_config }
|
30
32
|
true
|
31
33
|
end
|
32
34
|
|
33
35
|
# Public: Removes a feature from the set of known features and clears
|
34
36
|
# all the values for the feature.
|
35
37
|
def remove(feature)
|
36
|
-
|
38
|
+
synchronize { @source.delete(feature.key) }
|
37
39
|
true
|
38
40
|
end
|
39
41
|
|
40
42
|
# Public: Clears all the gate values for a feature.
|
41
43
|
def clear(feature)
|
42
|
-
|
44
|
+
synchronize { @source[feature.key] = default_config }
|
43
45
|
true
|
44
46
|
end
|
45
47
|
|
46
48
|
# Public
|
47
49
|
def get(feature)
|
48
|
-
|
50
|
+
synchronize { @source[feature.key] } || default_config
|
49
51
|
end
|
50
52
|
|
51
53
|
def get_multi(features)
|
52
|
-
|
54
|
+
synchronize do
|
53
55
|
result = {}
|
54
56
|
features.each do |feature|
|
55
57
|
result[feature.key] = @source[feature.key] || default_config
|
@@ -59,12 +61,12 @@ module Flipper
|
|
59
61
|
end
|
60
62
|
|
61
63
|
def get_all
|
62
|
-
|
64
|
+
synchronize { Typecast.features_hash(@source) }
|
63
65
|
end
|
64
66
|
|
65
67
|
# Public
|
66
68
|
def enable(feature, gate, thing)
|
67
|
-
|
69
|
+
synchronize do
|
68
70
|
@source[feature.key] ||= default_config
|
69
71
|
|
70
72
|
case gate.data_type
|
@@ -85,7 +87,7 @@ module Flipper
|
|
85
87
|
|
86
88
|
# Public
|
87
89
|
def disable(feature, gate, thing)
|
88
|
-
|
90
|
+
synchronize do
|
89
91
|
@source[feature.key] ||= default_config
|
90
92
|
|
91
93
|
case gate.data_type
|
@@ -113,9 +115,31 @@ module Flipper
|
|
113
115
|
end
|
114
116
|
|
115
117
|
# Public: a more efficient implementation of import for this adapter
|
116
|
-
def import(
|
117
|
-
|
118
|
-
|
118
|
+
def import(source)
|
119
|
+
adapter = self.class.from(source)
|
120
|
+
get_all = Typecast.features_hash(adapter.get_all)
|
121
|
+
synchronize { @source.replace(get_all) }
|
122
|
+
true
|
123
|
+
end
|
124
|
+
|
125
|
+
private
|
126
|
+
|
127
|
+
def reset
|
128
|
+
@pid = Process.pid
|
129
|
+
@lock&.unlock if @lock&.locked?
|
130
|
+
end
|
131
|
+
|
132
|
+
def forked?
|
133
|
+
@pid != Process.pid
|
134
|
+
end
|
135
|
+
|
136
|
+
def synchronize(&block)
|
137
|
+
if @lock
|
138
|
+
reset if forked?
|
139
|
+
@lock.synchronize(&block)
|
140
|
+
else
|
141
|
+
block.call
|
142
|
+
end
|
119
143
|
end
|
120
144
|
end
|
121
145
|
end
|
@@ -5,8 +5,8 @@ module Flipper
|
|
5
5
|
# Public: Adapter that wraps another adapter and stores the operations.
|
6
6
|
#
|
7
7
|
# Useful in tests to verify calls and such. Never use outside of testing.
|
8
|
-
class OperationLogger
|
9
|
-
include
|
8
|
+
class OperationLogger
|
9
|
+
include Flipper::Adapter
|
10
10
|
|
11
11
|
class Operation
|
12
12
|
attr_reader :type, :args
|
@@ -18,6 +18,8 @@ module Flipper
|
|
18
18
|
end
|
19
19
|
|
20
20
|
OperationTypes = [
|
21
|
+
:import,
|
22
|
+
:export,
|
21
23
|
:features,
|
22
24
|
:add,
|
23
25
|
:remove,
|
@@ -37,7 +39,6 @@ module Flipper
|
|
37
39
|
|
38
40
|
# Public
|
39
41
|
def initialize(adapter, operations = nil)
|
40
|
-
super(adapter)
|
41
42
|
@adapter = adapter
|
42
43
|
@name = :operation_logger
|
43
44
|
@operations = operations || []
|
@@ -98,6 +99,18 @@ module Flipper
|
|
98
99
|
@adapter.disable(feature, gate, thing)
|
99
100
|
end
|
100
101
|
|
102
|
+
# Public
|
103
|
+
def import(source)
|
104
|
+
@operations << Operation.new(:import, [source])
|
105
|
+
@adapter.import(source)
|
106
|
+
end
|
107
|
+
|
108
|
+
# Public
|
109
|
+
def export(format: :json, version: 1)
|
110
|
+
@operations << Operation.new(:export, [format, version])
|
111
|
+
@adapter.export(format: format, version: version)
|
112
|
+
end
|
113
|
+
|
101
114
|
# Public: Count the number of times a certain operation happened.
|
102
115
|
def count(type)
|
103
116
|
type(type).size
|
data/lib/flipper/dsl.rb
CHANGED
@@ -10,7 +10,7 @@ module Flipper
|
|
10
10
|
# Private: What is being used to instrument all the things.
|
11
11
|
attr_reader :instrumenter
|
12
12
|
|
13
|
-
def_delegators :@adapter, :memoize=, :memoizing
|
13
|
+
def_delegators :@adapter, :memoize=, :memoizing?, :import, :export
|
14
14
|
|
15
15
|
# Public: Returns a new instance of the DSL.
|
16
16
|
#
|
@@ -237,12 +237,12 @@ module Flipper
|
|
237
237
|
|
238
238
|
# Public: Wraps an object as a flipper actor.
|
239
239
|
#
|
240
|
-
#
|
240
|
+
# actor - The object that you would like to wrap.
|
241
241
|
#
|
242
242
|
# Returns an instance of Flipper::Types::Actor.
|
243
|
-
# Raises ArgumentError if
|
244
|
-
def actor(
|
245
|
-
Types::Actor.new(
|
243
|
+
# Raises ArgumentError if actor does not respond to `flipper_id`.
|
244
|
+
def actor(actor)
|
245
|
+
Types::Actor.new(actor)
|
246
246
|
end
|
247
247
|
|
248
248
|
# Public: Shortcut for getting a percentage of time instance.
|
@@ -272,10 +272,6 @@ module Flipper
|
|
272
272
|
adapter.features.map { |name| feature(name) }.to_set
|
273
273
|
end
|
274
274
|
|
275
|
-
def import(flipper)
|
276
|
-
adapter.import(flipper.adapter)
|
277
|
-
end
|
278
|
-
|
279
275
|
# Cloud DSL method that does nothing for open source version.
|
280
276
|
def sync
|
281
277
|
end
|