flipper 0.26.0 → 1.1.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/FUNDING.yml +1 -0
- data/.github/workflows/ci.yml +19 -13
- data/.github/workflows/examples.yml +32 -15
- data/Changelog.md +294 -154
- data/Gemfile +15 -10
- data/README.md +13 -11
- data/benchmark/enabled_ips.rb +10 -0
- data/benchmark/enabled_multiple_actors_ips.rb +20 -0
- data/benchmark/enabled_profile.rb +20 -0
- data/benchmark/instrumentation_ips.rb +21 -0
- data/benchmark/typecast_ips.rb +27 -0
- data/docs/images/flipper_cloud.png +0 -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/cloud/app.ru +12 -0
- data/examples/cloud/backoff_policy.rb +13 -0
- data/examples/cloud/basic.rb +22 -0
- data/examples/cloud/cloud_setup.rb +20 -0
- data/examples/cloud/forked.rb +36 -0
- data/examples/cloud/import.rb +17 -0
- data/examples/cloud/threaded.rb +33 -0
- data/examples/dsl.rb +1 -15
- data/examples/enabled_for_actor.rb +4 -2
- data/examples/expressions.rb +213 -0
- data/examples/mirroring.rb +59 -0
- data/examples/strict.rb +18 -0
- data/flipper-cloud.gemspec +19 -0
- data/flipper.gemspec +3 -5
- data/lib/flipper/actor.rb +6 -3
- data/lib/flipper/adapter.rb +33 -7
- data/lib/flipper/adapter_builder.rb +44 -0
- data/lib/flipper/adapters/dual_write.rb +1 -3
- data/lib/flipper/adapters/failover.rb +0 -4
- data/lib/flipper/adapters/failsafe.rb +0 -4
- data/lib/flipper/adapters/http/client.rb +26 -7
- data/lib/flipper/adapters/http/error.rb +1 -1
- data/lib/flipper/adapters/http.rb +29 -16
- data/lib/flipper/adapters/instrumented.rb +25 -6
- data/lib/flipper/adapters/memoizable.rb +33 -21
- data/lib/flipper/adapters/memory.rb +81 -46
- data/lib/flipper/adapters/operation_logger.rb +16 -7
- data/lib/flipper/adapters/poll/poller.rb +2 -125
- data/lib/flipper/adapters/poll.rb +5 -3
- data/lib/flipper/adapters/pstore.rb +17 -11
- data/lib/flipper/adapters/read_only.rb +4 -4
- data/lib/flipper/adapters/strict.rb +47 -0
- data/lib/flipper/adapters/sync/feature_synchronizer.rb +10 -1
- data/lib/flipper/adapters/sync.rb +0 -4
- data/lib/flipper/cloud/configuration.rb +258 -0
- data/lib/flipper/cloud/dsl.rb +27 -0
- data/lib/flipper/cloud/message_verifier.rb +95 -0
- data/lib/flipper/cloud/middleware.rb +63 -0
- data/lib/flipper/cloud/routes.rb +14 -0
- data/lib/flipper/cloud/telemetry/backoff_policy.rb +93 -0
- data/lib/flipper/cloud/telemetry/instrumenter.rb +26 -0
- data/lib/flipper/cloud/telemetry/metric.rb +39 -0
- data/lib/flipper/cloud/telemetry/metric_storage.rb +30 -0
- data/lib/flipper/cloud/telemetry/submitter.rb +98 -0
- data/lib/flipper/cloud/telemetry.rb +183 -0
- data/lib/flipper/cloud.rb +53 -0
- data/lib/flipper/configuration.rb +25 -4
- data/lib/flipper/dsl.rb +46 -45
- data/lib/flipper/engine.rb +88 -0
- 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/expression/builder.rb +73 -0
- data/lib/flipper/expression/constant.rb +25 -0
- data/lib/flipper/expression.rb +71 -0
- data/lib/flipper/expressions/all.rb +11 -0
- data/lib/flipper/expressions/any.rb +9 -0
- data/lib/flipper/expressions/boolean.rb +9 -0
- data/lib/flipper/expressions/comparable.rb +13 -0
- data/lib/flipper/expressions/duration.rb +28 -0
- data/lib/flipper/expressions/equal.rb +9 -0
- data/lib/flipper/expressions/greater_than.rb +9 -0
- data/lib/flipper/expressions/greater_than_or_equal_to.rb +9 -0
- data/lib/flipper/expressions/less_than.rb +9 -0
- data/lib/flipper/expressions/less_than_or_equal_to.rb +9 -0
- data/lib/flipper/expressions/not_equal.rb +9 -0
- data/lib/flipper/expressions/now.rb +9 -0
- data/lib/flipper/expressions/number.rb +9 -0
- data/lib/flipper/expressions/percentage.rb +9 -0
- data/lib/flipper/expressions/percentage_of_actors.rb +12 -0
- data/lib/flipper/expressions/property.rb +9 -0
- data/lib/flipper/expressions/random.rb +9 -0
- data/lib/flipper/expressions/string.rb +9 -0
- data/lib/flipper/expressions/time.rb +9 -0
- data/lib/flipper/feature.rb +87 -26
- data/lib/flipper/feature_check_context.rb +10 -6
- data/lib/flipper/gate.rb +13 -11
- data/lib/flipper/gate_values.rb +5 -18
- data/lib/flipper/gates/actor.rb +10 -17
- data/lib/flipper/gates/boolean.rb +1 -1
- data/lib/flipper/gates/expression.rb +75 -0
- data/lib/flipper/gates/group.rb +5 -7
- data/lib/flipper/gates/percentage_of_actors.rb +10 -13
- data/lib/flipper/gates/percentage_of_time.rb +1 -2
- data/lib/flipper/identifier.rb +2 -2
- data/lib/flipper/instrumentation/log_subscriber.rb +24 -5
- data/lib/flipper/instrumentation/statsd_subscriber.rb +2 -4
- data/lib/flipper/instrumentation/subscriber.rb +8 -1
- data/lib/flipper/metadata.rb +5 -1
- data/lib/flipper/middleware/memoizer.rb +30 -14
- data/lib/flipper/poller.rb +117 -0
- data/lib/flipper/serializers/gzip.rb +24 -0
- data/lib/flipper/serializers/json.rb +19 -0
- data/lib/flipper/spec/shared_adapter_specs.rb +95 -54
- data/lib/flipper/test/shared_adapter_test.rb +91 -48
- data/lib/flipper/typecast.rb +56 -15
- data/lib/flipper/types/actor.rb +13 -13
- data/lib/flipper/types/group.rb +4 -4
- data/lib/flipper/types/percentage.rb +1 -1
- data/lib/flipper/version.rb +1 -1
- data/lib/flipper.rb +47 -10
- data/spec/fixtures/flipper_pstore_1679087600.json +46 -0
- data/spec/flipper/adapter_builder_spec.rb +73 -0
- data/spec/flipper/adapter_spec.rb +30 -2
- data/spec/flipper/adapters/dual_write_spec.rb +2 -2
- data/spec/flipper/adapters/http_spec.rb +64 -8
- data/spec/flipper/adapters/instrumented_spec.rb +29 -11
- data/spec/flipper/adapters/memoizable_spec.rb +51 -31
- data/spec/flipper/adapters/memory_spec.rb +14 -3
- data/spec/flipper/adapters/operation_logger_spec.rb +31 -12
- data/spec/flipper/adapters/read_only_spec.rb +32 -17
- data/spec/flipper/adapters/strict_spec.rb +62 -0
- data/spec/flipper/adapters/sync/feature_synchronizer_spec.rb +27 -0
- data/spec/flipper/cloud/configuration_spec.rb +252 -0
- data/spec/flipper/cloud/dsl_spec.rb +82 -0
- data/spec/flipper/cloud/message_verifier_spec.rb +104 -0
- data/spec/flipper/cloud/middleware_spec.rb +289 -0
- data/spec/flipper/cloud/telemetry/backoff_policy_spec.rb +108 -0
- data/spec/flipper/cloud/telemetry/metric_spec.rb +87 -0
- data/spec/flipper/cloud/telemetry/metric_storage_spec.rb +58 -0
- data/spec/flipper/cloud/telemetry/submitter_spec.rb +145 -0
- data/spec/flipper/cloud/telemetry_spec.rb +156 -0
- data/spec/flipper/cloud_spec.rb +180 -0
- data/spec/flipper/configuration_spec.rb +17 -0
- data/spec/flipper/dsl_spec.rb +54 -73
- data/spec/flipper/engine_spec.rb +291 -0
- 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/expression/builder_spec.rb +248 -0
- data/spec/flipper/expression_spec.rb +188 -0
- data/spec/flipper/expressions/all_spec.rb +15 -0
- data/spec/flipper/expressions/any_spec.rb +15 -0
- data/spec/flipper/expressions/boolean_spec.rb +15 -0
- data/spec/flipper/expressions/duration_spec.rb +43 -0
- data/spec/flipper/expressions/equal_spec.rb +24 -0
- data/spec/flipper/expressions/greater_than_or_equal_to_spec.rb +28 -0
- data/spec/flipper/expressions/greater_than_spec.rb +28 -0
- data/spec/flipper/expressions/less_than_or_equal_to_spec.rb +28 -0
- data/spec/flipper/expressions/less_than_spec.rb +32 -0
- data/spec/flipper/expressions/not_equal_spec.rb +15 -0
- data/spec/flipper/expressions/now_spec.rb +11 -0
- data/spec/flipper/expressions/number_spec.rb +21 -0
- data/spec/flipper/expressions/percentage_of_actors_spec.rb +20 -0
- data/spec/flipper/expressions/percentage_spec.rb +15 -0
- data/spec/flipper/expressions/property_spec.rb +13 -0
- data/spec/flipper/expressions/random_spec.rb +9 -0
- data/spec/flipper/expressions/string_spec.rb +11 -0
- data/spec/flipper/expressions/time_spec.rb +13 -0
- data/spec/flipper/feature_check_context_spec.rb +17 -17
- data/spec/flipper/feature_spec.rb +436 -33
- data/spec/flipper/gate_values_spec.rb +2 -33
- data/spec/flipper/gates/boolean_spec.rb +1 -1
- data/spec/flipper/gates/expression_spec.rb +108 -0
- 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/identifier_spec.rb +4 -5
- data/spec/flipper/instrumentation/log_subscriber_spec.rb +15 -5
- data/spec/flipper/instrumentation/statsd_subscriber_spec.rb +25 -1
- data/spec/flipper/middleware/memoizer_spec.rb +67 -0
- data/spec/flipper/poller_spec.rb +47 -0
- data/spec/flipper/serializers/gzip_spec.rb +13 -0
- data/spec/flipper/serializers/json_spec.rb +13 -0
- data/spec/flipper/typecast_spec.rb +121 -6
- data/spec/flipper/types/actor_spec.rb +63 -46
- data/spec/flipper/types/group_spec.rb +2 -2
- data/spec/flipper_integration_spec.rb +168 -58
- data/spec/flipper_spec.rb +92 -28
- data/spec/spec_helper.rb +6 -13
- data/spec/support/actor_names.yml +1 -0
- data/spec/support/climate_control.rb +7 -0
- data/spec/support/fake_backoff_policy.rb +15 -0
- data/spec/support/skippable.rb +18 -0
- data/spec/support/spec_helpers.rb +11 -3
- metadata +166 -13
- data/.github/workflows/release.yml +0 -44
- data/.tool-versions +0 -1
- data/lib/flipper/railtie.rb +0 -47
- data/spec/flipper/railtie_spec.rb +0 -109
@@ -45,7 +45,6 @@ module Flipper
|
|
45
45
|
gate_name = @payload[:gate_name]
|
46
46
|
operation = strip_trailing_question_mark(@payload[:operation])
|
47
47
|
result = @payload[:result]
|
48
|
-
thing = @payload[:thing]
|
49
48
|
|
50
49
|
update_timer "flipper.feature_operation.#{operation}"
|
51
50
|
|
@@ -72,6 +71,14 @@ module Flipper
|
|
72
71
|
update_timer "flipper.adapter.#{adapter_name}.#{operation}"
|
73
72
|
end
|
74
73
|
|
74
|
+
def update_poller_metrics
|
75
|
+
# noop
|
76
|
+
end
|
77
|
+
|
78
|
+
def update_synchronizer_call_metrics
|
79
|
+
# noop
|
80
|
+
end
|
81
|
+
|
75
82
|
QUESTION_MARK = '?'.freeze
|
76
83
|
|
77
84
|
# Private
|
data/lib/flipper/metadata.rb
CHANGED
@@ -1,5 +1,9 @@
|
|
1
1
|
module Flipper
|
2
2
|
METADATA = {
|
3
|
-
|
3
|
+
"documentation_uri" => "https://www.flippercloud.io/docs",
|
4
|
+
"homepage_uri" => "https://www.flippercloud.io",
|
5
|
+
"source_code_uri" => "https://github.com/flippercloud/flipper",
|
6
|
+
"bug_tracker_uri" => "https://github.com/flippercloud/flipper/issues",
|
7
|
+
"changelog_uri" => "https://github.com/flippercloud/flipper/blob/main/Changelog.md",
|
4
8
|
}.freeze
|
5
9
|
end
|
@@ -20,6 +20,14 @@ module Flipper
|
|
20
20
|
# # using with preload specific features
|
21
21
|
# use Flipper::Middleware::Memoizer, preload: [:stats, :search, :some_feature]
|
22
22
|
#
|
23
|
+
# # using with preload block that returns true/false
|
24
|
+
# use Flipper::Middleware::Memoizer, preload: ->(request) { !request.path.start_with?('/assets') }
|
25
|
+
#
|
26
|
+
# # using with preload block that returns specific features
|
27
|
+
# use Flipper::Middleware::Memoizer, preload: ->(request) {
|
28
|
+
# request.path.starts_with?('/admin') ? [:stats, :search] : false
|
29
|
+
# }
|
30
|
+
#
|
23
31
|
def initialize(app, opts = {})
|
24
32
|
if opts.is_a?(Flipper::DSL) || opts.is_a?(Proc)
|
25
33
|
raise 'Flipper::Middleware::Memoizer no longer initializes with a flipper instance or block. Read more at: https://git.io/vSo31.'
|
@@ -34,7 +42,7 @@ module Flipper
|
|
34
42
|
request = Rack::Request.new(env)
|
35
43
|
|
36
44
|
if memoize?(request)
|
37
|
-
memoized_call(
|
45
|
+
memoized_call(request)
|
38
46
|
else
|
39
47
|
@app.call(env)
|
40
48
|
end
|
@@ -52,26 +60,34 @@ module Flipper
|
|
52
60
|
end
|
53
61
|
end
|
54
62
|
|
55
|
-
def memoized_call(
|
56
|
-
|
57
|
-
flipper = env.fetch(@env_key) { Flipper }
|
63
|
+
def memoized_call(request)
|
64
|
+
flipper = request.env.fetch(@env_key) { Flipper }
|
58
65
|
|
59
66
|
# Already memoizing. This instance does not need to do anything.
|
60
67
|
if flipper.memoizing?
|
61
|
-
warn "Flipper::Middleware::Memoizer appears to be running twice. Read how to resolve this at https://github.com/
|
62
|
-
return @app.call(env)
|
68
|
+
warn "Flipper::Middleware::Memoizer appears to be running twice. Read how to resolve this at https://github.com/flippercloud/flipper/pull/523"
|
69
|
+
return @app.call(request.env)
|
63
70
|
end
|
64
71
|
|
65
|
-
|
72
|
+
begin
|
73
|
+
flipper.memoize = true
|
66
74
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
75
|
+
# Preloading is pointless without memoizing.
|
76
|
+
preload = if @opts[:preload].respond_to?(:call)
|
77
|
+
@opts[:preload].call(request)
|
78
|
+
else
|
79
|
+
@opts[:preload]
|
80
|
+
end
|
71
81
|
|
72
|
-
|
73
|
-
|
74
|
-
|
82
|
+
case preload
|
83
|
+
when true then flipper.preload_all
|
84
|
+
when Array then flipper.preload(preload)
|
85
|
+
end
|
86
|
+
|
87
|
+
@app.call(request.env)
|
88
|
+
ensure
|
89
|
+
flipper.memoize = false
|
90
|
+
end
|
75
91
|
end
|
76
92
|
end
|
77
93
|
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
require 'logger'
|
2
|
+
require 'concurrent/utility/monotonic_time'
|
3
|
+
require 'concurrent/map'
|
4
|
+
require 'concurrent/atomic/atomic_fixnum'
|
5
|
+
|
6
|
+
module Flipper
|
7
|
+
class Poller
|
8
|
+
attr_reader :adapter, :thread, :pid, :mutex, :interval, :last_synced_at
|
9
|
+
|
10
|
+
def self.instances
|
11
|
+
@instances ||= Concurrent::Map.new
|
12
|
+
end
|
13
|
+
private_class_method :instances
|
14
|
+
|
15
|
+
def self.get(key, options = {})
|
16
|
+
instances.compute_if_absent(key) { new(options) }
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.reset
|
20
|
+
instances.each {|_, instance| instance.stop }.clear
|
21
|
+
end
|
22
|
+
|
23
|
+
def initialize(options = {})
|
24
|
+
@thread = nil
|
25
|
+
@pid = Process.pid
|
26
|
+
@mutex = Mutex.new
|
27
|
+
@instrumenter = options.fetch(:instrumenter, Instrumenters::Noop)
|
28
|
+
@remote_adapter = options.fetch(:remote_adapter)
|
29
|
+
@interval = options.fetch(:interval, 10).to_f
|
30
|
+
@last_synced_at = Concurrent::AtomicFixnum.new(0)
|
31
|
+
@adapter = Adapters::Memory.new(nil, threadsafe: true)
|
32
|
+
|
33
|
+
if @interval < 1
|
34
|
+
warn "Flipper::Cloud poll interval must be greater than or equal to 1 but was #{@interval}. Setting @interval to 1."
|
35
|
+
@interval = 1
|
36
|
+
end
|
37
|
+
|
38
|
+
@start_automatically = options.fetch(:start_automatically, true)
|
39
|
+
|
40
|
+
if options.fetch(:shutdown_automatically, true)
|
41
|
+
at_exit { stop }
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def start
|
46
|
+
reset if forked?
|
47
|
+
ensure_worker_running
|
48
|
+
end
|
49
|
+
|
50
|
+
def stop
|
51
|
+
@instrumenter.instrument("poller.#{InstrumentationNamespace}", {
|
52
|
+
operation: :stop,
|
53
|
+
})
|
54
|
+
@thread&.kill
|
55
|
+
end
|
56
|
+
|
57
|
+
def run
|
58
|
+
loop do
|
59
|
+
sleep jitter
|
60
|
+
start = Concurrent.monotonic_time
|
61
|
+
begin
|
62
|
+
sync
|
63
|
+
rescue => exception
|
64
|
+
# you can instrument these using poller.flipper
|
65
|
+
end
|
66
|
+
|
67
|
+
sleep_interval = interval - (Concurrent.monotonic_time - start)
|
68
|
+
sleep sleep_interval if sleep_interval.positive?
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def sync
|
73
|
+
@instrumenter.instrument("poller.#{InstrumentationNamespace}", operation: :poll) do
|
74
|
+
@adapter.import @remote_adapter
|
75
|
+
@last_synced_at.update { |time| Concurrent.monotonic_time }
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
private
|
80
|
+
|
81
|
+
def jitter
|
82
|
+
rand
|
83
|
+
end
|
84
|
+
|
85
|
+
def forked?
|
86
|
+
pid != Process.pid
|
87
|
+
end
|
88
|
+
|
89
|
+
def ensure_worker_running
|
90
|
+
# Return early if thread is alive and avoid the mutex lock and unlock.
|
91
|
+
return if thread_alive?
|
92
|
+
|
93
|
+
# If another thread is starting worker thread, then return early so this
|
94
|
+
# thread can enqueue and move on with life.
|
95
|
+
return unless mutex.try_lock
|
96
|
+
|
97
|
+
begin
|
98
|
+
return if thread_alive?
|
99
|
+
@thread = Thread.new { run }
|
100
|
+
@instrumenter.instrument("poller.#{InstrumentationNamespace}", {
|
101
|
+
operation: :thread_start,
|
102
|
+
})
|
103
|
+
ensure
|
104
|
+
mutex.unlock
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def thread_alive?
|
109
|
+
@thread && @thread.alive?
|
110
|
+
end
|
111
|
+
|
112
|
+
def reset
|
113
|
+
@pid = Process.pid
|
114
|
+
mutex.unlock if mutex.locked?
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require "zlib"
|
2
|
+
require "stringio"
|
3
|
+
|
4
|
+
module Flipper
|
5
|
+
module Serializers
|
6
|
+
module Gzip
|
7
|
+
module_function
|
8
|
+
|
9
|
+
def serialize(source)
|
10
|
+
return if source.nil?
|
11
|
+
output = StringIO.new
|
12
|
+
gz = Zlib::GzipWriter.new(output)
|
13
|
+
gz.write(source)
|
14
|
+
gz.close
|
15
|
+
output.string
|
16
|
+
end
|
17
|
+
|
18
|
+
def deserialize(source)
|
19
|
+
return if source.nil?
|
20
|
+
Zlib::GzipReader.wrap(StringIO.new(source), &:read)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require "json"
|
2
|
+
|
3
|
+
module Flipper
|
4
|
+
module Serializers
|
5
|
+
module Json
|
6
|
+
module_function
|
7
|
+
|
8
|
+
def serialize(source)
|
9
|
+
return if source.nil?
|
10
|
+
JSON.generate(source)
|
11
|
+
end
|
12
|
+
|
13
|
+
def deserialize(source)
|
14
|
+
return if source.nil?
|
15
|
+
JSON.parse(source)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -4,11 +4,12 @@ RSpec.shared_examples_for 'a flipper adapter' do
|
|
4
4
|
let(:flipper) { Flipper.new(subject) }
|
5
5
|
let(:feature) { flipper[:stats] }
|
6
6
|
|
7
|
-
let(:boolean_gate)
|
8
|
-
let(:
|
9
|
-
let(:
|
10
|
-
let(:
|
11
|
-
let(:
|
7
|
+
let(:boolean_gate) { feature.gate(:boolean) }
|
8
|
+
let(:expression_gate) { feature.gate(:expression) }
|
9
|
+
let(:group_gate) { feature.gate(:group) }
|
10
|
+
let(:actor_gate) { feature.gate(:actor) }
|
11
|
+
let(:actors_gate) { feature.gate(:percentage_of_actors) }
|
12
|
+
let(:time_gate) { feature.gate(:percentage_of_time) }
|
12
13
|
|
13
14
|
before do
|
14
15
|
Flipper.register(:admins) do |actor|
|
@@ -33,17 +34,25 @@ RSpec.shared_examples_for 'a flipper adapter' do
|
|
33
34
|
expect(subject.class.ancestors).to include(Flipper::Adapter)
|
34
35
|
end
|
35
36
|
|
37
|
+
it 'knows how to get adapter from source' do
|
38
|
+
adapter = Flipper::Adapters::Memory.new
|
39
|
+
flipper = Flipper.new(adapter)
|
40
|
+
expect(subject.class.from(adapter).class.ancestors).to include(Flipper::Adapter)
|
41
|
+
expect(subject.class.from(flipper).class.ancestors).to include(Flipper::Adapter)
|
42
|
+
end
|
43
|
+
|
36
44
|
it 'returns correct default values for the gates if none are enabled' do
|
45
|
+
expect(subject.get(feature)).to eq(subject.class.default_config)
|
37
46
|
expect(subject.get(feature)).to eq(subject.default_config)
|
38
47
|
end
|
39
48
|
|
40
49
|
it 'can enable, disable and get value for boolean gate' do
|
41
|
-
expect(subject.enable(feature, boolean_gate,
|
50
|
+
expect(subject.enable(feature, boolean_gate, Flipper::Types::Boolean.new)).to eq(true)
|
42
51
|
|
43
52
|
result = subject.get(feature)
|
44
53
|
expect(result[:boolean]).to eq('true')
|
45
54
|
|
46
|
-
expect(subject.disable(feature, boolean_gate,
|
55
|
+
expect(subject.disable(feature, boolean_gate, Flipper::Types::Boolean.new(false))).to eq(true)
|
47
56
|
|
48
57
|
result = subject.get(feature)
|
49
58
|
expect(result[:boolean]).to eq(nil)
|
@@ -51,17 +60,34 @@ RSpec.shared_examples_for 'a flipper adapter' do
|
|
51
60
|
|
52
61
|
it 'fully disables all enabled things when boolean gate disabled' do
|
53
62
|
actor22 = Flipper::Actor.new('22')
|
54
|
-
expect(subject.enable(feature, boolean_gate,
|
63
|
+
expect(subject.enable(feature, boolean_gate, Flipper::Types::Boolean.new)).to eq(true)
|
55
64
|
expect(subject.enable(feature, group_gate, flipper.group(:admins))).to eq(true)
|
56
|
-
expect(subject.enable(feature, actor_gate,
|
57
|
-
expect(subject.enable(feature, actors_gate,
|
58
|
-
expect(subject.enable(feature, time_gate,
|
59
|
-
|
60
|
-
expect(subject.disable(feature, boolean_gate, flipper.boolean(false))).to eq(true)
|
65
|
+
expect(subject.enable(feature, actor_gate, Flipper::Types::Actor.new(actor22))).to eq(true)
|
66
|
+
expect(subject.enable(feature, actors_gate, Flipper::Types::PercentageOfActors.new(25))).to eq(true)
|
67
|
+
expect(subject.enable(feature, time_gate, Flipper::Types::PercentageOfTime.new(45))).to eq(true)
|
61
68
|
|
69
|
+
expect(subject.disable(feature, boolean_gate, Flipper::Types::Boolean.new(false))).to eq(true)
|
62
70
|
expect(subject.get(feature)).to eq(subject.default_config)
|
63
71
|
end
|
64
72
|
|
73
|
+
it 'can enable, disable and get value for expression gate' do
|
74
|
+
basic_expression = Flipper.property(:plan).eq("basic")
|
75
|
+
age_expression = Flipper.property(:age).gte(21)
|
76
|
+
any_expression = Flipper.any(basic_expression, age_expression)
|
77
|
+
|
78
|
+
expect(subject.enable(feature, expression_gate, any_expression)).to eq(true)
|
79
|
+
result = subject.get(feature)
|
80
|
+
expect(result[:expression]).to eq(any_expression.value)
|
81
|
+
|
82
|
+
expect(subject.enable(feature, expression_gate, basic_expression)).to eq(true)
|
83
|
+
result = subject.get(feature)
|
84
|
+
expect(result[:expression]).to eq(basic_expression.value)
|
85
|
+
|
86
|
+
expect(subject.disable(feature, expression_gate, basic_expression)).to eq(true)
|
87
|
+
result = subject.get(feature)
|
88
|
+
expect(result[:expression]).to be(nil)
|
89
|
+
end
|
90
|
+
|
65
91
|
it 'can enable, disable and get value for group gate' do
|
66
92
|
expect(subject.enable(feature, group_gate, flipper.group(:admins))).to eq(true)
|
67
93
|
expect(subject.enable(feature, group_gate, flipper.group(:early_access))).to eq(true)
|
@@ -82,34 +108,34 @@ RSpec.shared_examples_for 'a flipper adapter' do
|
|
82
108
|
actor22 = Flipper::Actor.new('22')
|
83
109
|
actor_asdf = Flipper::Actor.new('asdf')
|
84
110
|
|
85
|
-
expect(subject.enable(feature, actor_gate,
|
86
|
-
expect(subject.enable(feature, actor_gate,
|
111
|
+
expect(subject.enable(feature, actor_gate, Flipper::Types::Actor.new(actor22))).to eq(true)
|
112
|
+
expect(subject.enable(feature, actor_gate, Flipper::Types::Actor.new(actor_asdf))).to eq(true)
|
87
113
|
|
88
114
|
result = subject.get(feature)
|
89
115
|
expect(result[:actors]).to eq(Set['22', 'asdf'])
|
90
116
|
|
91
|
-
expect(subject.disable(feature, actor_gate,
|
117
|
+
expect(subject.disable(feature, actor_gate, Flipper::Types::Actor.new(actor22))).to eq(true)
|
92
118
|
result = subject.get(feature)
|
93
119
|
expect(result[:actors]).to eq(Set['asdf'])
|
94
120
|
|
95
|
-
expect(subject.disable(feature, actor_gate,
|
121
|
+
expect(subject.disable(feature, actor_gate, Flipper::Types::Actor.new(actor_asdf))).to eq(true)
|
96
122
|
result = subject.get(feature)
|
97
123
|
expect(result[:actors]).to eq(Set.new)
|
98
124
|
end
|
99
125
|
|
100
126
|
it 'can enable, disable and get value for percentage of actors gate' do
|
101
|
-
expect(subject.enable(feature, actors_gate,
|
127
|
+
expect(subject.enable(feature, actors_gate, Flipper::Types::PercentageOfActors.new(15))).to eq(true)
|
102
128
|
result = subject.get(feature)
|
103
129
|
expect(result[:percentage_of_actors]).to eq('15')
|
104
130
|
|
105
|
-
expect(subject.disable(feature, actors_gate,
|
131
|
+
expect(subject.disable(feature, actors_gate, Flipper::Types::PercentageOfActors.new(0))).to eq(true)
|
106
132
|
result = subject.get(feature)
|
107
133
|
expect(result[:percentage_of_actors]).to eq('0')
|
108
134
|
end
|
109
135
|
|
110
136
|
it 'can enable percentage of actors gate many times and consistently return values' do
|
111
137
|
(1..100).each do |percentage|
|
112
|
-
expect(subject.enable(feature, actors_gate,
|
138
|
+
expect(subject.enable(feature, actors_gate, Flipper::Types::PercentageOfActors.new(percentage))).to eq(true)
|
113
139
|
result = subject.get(feature)
|
114
140
|
expect(result[:percentage_of_actors]).to eq(percentage.to_s)
|
115
141
|
end
|
@@ -117,25 +143,25 @@ RSpec.shared_examples_for 'a flipper adapter' do
|
|
117
143
|
|
118
144
|
it 'can disable percentage of actors gate many times and consistently return values' do
|
119
145
|
(1..100).each do |percentage|
|
120
|
-
expect(subject.disable(feature, actors_gate,
|
146
|
+
expect(subject.disable(feature, actors_gate, Flipper::Types::PercentageOfActors.new(percentage))).to eq(true)
|
121
147
|
result = subject.get(feature)
|
122
148
|
expect(result[:percentage_of_actors]).to eq(percentage.to_s)
|
123
149
|
end
|
124
150
|
end
|
125
151
|
|
126
152
|
it 'can enable, disable and get value for percentage of time gate' do
|
127
|
-
expect(subject.enable(feature, time_gate,
|
153
|
+
expect(subject.enable(feature, time_gate, Flipper::Types::PercentageOfTime.new(10))).to eq(true)
|
128
154
|
result = subject.get(feature)
|
129
155
|
expect(result[:percentage_of_time]).to eq('10')
|
130
156
|
|
131
|
-
expect(subject.disable(feature, time_gate,
|
157
|
+
expect(subject.disable(feature, time_gate, Flipper::Types::PercentageOfTime.new(0))).to eq(true)
|
132
158
|
result = subject.get(feature)
|
133
159
|
expect(result[:percentage_of_time]).to eq('0')
|
134
160
|
end
|
135
161
|
|
136
162
|
it 'can enable percentage of time gate many times and consistently return values' do
|
137
163
|
(1..100).each do |percentage|
|
138
|
-
expect(subject.enable(feature, time_gate,
|
164
|
+
expect(subject.enable(feature, time_gate, Flipper::Types::PercentageOfTime.new(percentage))).to eq(true)
|
139
165
|
result = subject.get(feature)
|
140
166
|
expect(result[:percentage_of_time]).to eq(percentage.to_s)
|
141
167
|
end
|
@@ -143,20 +169,20 @@ RSpec.shared_examples_for 'a flipper adapter' do
|
|
143
169
|
|
144
170
|
it 'can disable percentage of time gate many times and consistently return values' do
|
145
171
|
(1..100).each do |percentage|
|
146
|
-
expect(subject.disable(feature, time_gate,
|
172
|
+
expect(subject.disable(feature, time_gate, Flipper::Types::PercentageOfTime.new(percentage))).to eq(true)
|
147
173
|
result = subject.get(feature)
|
148
174
|
expect(result[:percentage_of_time]).to eq(percentage.to_s)
|
149
175
|
end
|
150
176
|
end
|
151
177
|
|
152
178
|
it 'converts boolean value to a string' do
|
153
|
-
expect(subject.enable(feature, boolean_gate,
|
179
|
+
expect(subject.enable(feature, boolean_gate, Flipper::Types::Boolean.new)).to eq(true)
|
154
180
|
result = subject.get(feature)
|
155
181
|
expect(result[:boolean]).to eq('true')
|
156
182
|
end
|
157
183
|
|
158
184
|
it 'converts the actor value to a string' do
|
159
|
-
expect(subject.enable(feature, actor_gate,
|
185
|
+
expect(subject.enable(feature, actor_gate, Flipper::Types::Actor.new(Flipper::Actor.new(22)))).to eq(true)
|
160
186
|
result = subject.get(feature)
|
161
187
|
expect(result[:actors]).to eq(Set['22'])
|
162
188
|
end
|
@@ -168,13 +194,13 @@ RSpec.shared_examples_for 'a flipper adapter' do
|
|
168
194
|
end
|
169
195
|
|
170
196
|
it 'converts percentage of time integer value to a string' do
|
171
|
-
expect(subject.enable(feature, time_gate,
|
197
|
+
expect(subject.enable(feature, time_gate, Flipper::Types::PercentageOfTime.new(10))).to eq(true)
|
172
198
|
result = subject.get(feature)
|
173
199
|
expect(result[:percentage_of_time]).to eq('10')
|
174
200
|
end
|
175
201
|
|
176
202
|
it 'converts percentage of actors integer value to a string' do
|
177
|
-
expect(subject.enable(feature, actors_gate,
|
203
|
+
expect(subject.enable(feature, actors_gate, Flipper::Types::PercentageOfActors.new(10))).to eq(true)
|
178
204
|
result = subject.get(feature)
|
179
205
|
expect(result[:percentage_of_actors]).to eq('10')
|
180
206
|
end
|
@@ -197,11 +223,11 @@ RSpec.shared_examples_for 'a flipper adapter' do
|
|
197
223
|
|
198
224
|
it 'clears all the gate values for the feature on remove' do
|
199
225
|
actor22 = Flipper::Actor.new('22')
|
200
|
-
expect(subject.enable(feature, boolean_gate,
|
226
|
+
expect(subject.enable(feature, boolean_gate, Flipper::Types::Boolean.new)).to eq(true)
|
201
227
|
expect(subject.enable(feature, group_gate, flipper.group(:admins))).to eq(true)
|
202
|
-
expect(subject.enable(feature, actor_gate,
|
203
|
-
expect(subject.enable(feature, actors_gate,
|
204
|
-
expect(subject.enable(feature, time_gate,
|
228
|
+
expect(subject.enable(feature, actor_gate, Flipper::Types::Actor.new(actor22))).to eq(true)
|
229
|
+
expect(subject.enable(feature, actors_gate, Flipper::Types::PercentageOfActors.new(25))).to eq(true)
|
230
|
+
expect(subject.enable(feature, time_gate, Flipper::Types::PercentageOfTime.new(45))).to eq(true)
|
205
231
|
|
206
232
|
expect(subject.remove(feature)).to eq(true)
|
207
233
|
|
@@ -213,11 +239,11 @@ RSpec.shared_examples_for 'a flipper adapter' do
|
|
213
239
|
subject.add(feature)
|
214
240
|
expect(subject.features).to include(feature.key)
|
215
241
|
|
216
|
-
expect(subject.enable(feature, boolean_gate,
|
242
|
+
expect(subject.enable(feature, boolean_gate, Flipper::Types::Boolean.new)).to eq(true)
|
217
243
|
expect(subject.enable(feature, group_gate, flipper.group(:admins))).to eq(true)
|
218
|
-
expect(subject.enable(feature, actor_gate,
|
219
|
-
expect(subject.enable(feature, actors_gate,
|
220
|
-
expect(subject.enable(feature, time_gate,
|
244
|
+
expect(subject.enable(feature, actor_gate, Flipper::Types::Actor.new(actor22))).to eq(true)
|
245
|
+
expect(subject.enable(feature, actors_gate, Flipper::Types::PercentageOfActors.new(25))).to eq(true)
|
246
|
+
expect(subject.enable(feature, time_gate, Flipper::Types::PercentageOfTime.new(45))).to eq(true)
|
221
247
|
|
222
248
|
expect(subject.clear(feature)).to eq(true)
|
223
249
|
expect(subject.features).to include(feature.key)
|
@@ -230,7 +256,7 @@ RSpec.shared_examples_for 'a flipper adapter' do
|
|
230
256
|
|
231
257
|
it 'can get multiple features' do
|
232
258
|
expect(subject.add(flipper[:stats])).to eq(true)
|
233
|
-
expect(subject.enable(flipper[:stats], boolean_gate,
|
259
|
+
expect(subject.enable(flipper[:stats], boolean_gate, Flipper::Types::Boolean.new)).to eq(true)
|
234
260
|
expect(subject.add(flipper[:search])).to eq(true)
|
235
261
|
|
236
262
|
result = subject.get_multi([flipper[:stats], flipper[:search], flipper[:other]])
|
@@ -246,16 +272,16 @@ RSpec.shared_examples_for 'a flipper adapter' do
|
|
246
272
|
|
247
273
|
it 'can get all features' do
|
248
274
|
expect(subject.add(flipper[:stats])).to eq(true)
|
249
|
-
expect(subject.enable(flipper[:stats], boolean_gate,
|
275
|
+
expect(subject.enable(flipper[:stats], boolean_gate, Flipper::Types::Boolean.new)).to eq(true)
|
250
276
|
expect(subject.add(flipper[:search])).to eq(true)
|
277
|
+
flipper.enable :analytics, Flipper.property(:plan).eq("pro")
|
251
278
|
|
252
279
|
result = subject.get_all
|
253
|
-
expect(result).to be_instance_of(Hash)
|
254
280
|
|
255
|
-
|
256
|
-
|
257
|
-
expect(
|
258
|
-
expect(
|
281
|
+
expect(result).to be_instance_of(Hash)
|
282
|
+
expect(result["stats"]).to eq(subject.default_config.merge(boolean: 'true'))
|
283
|
+
expect(result["search"]).to eq(subject.default_config)
|
284
|
+
expect(result["analytics"]).to eq(subject.default_config.merge(expression: {"Equal"=>[{"Property"=>["plan"]}, "pro"]}))
|
259
285
|
end
|
260
286
|
|
261
287
|
it 'includes explicitly disabled features when getting all features' do
|
@@ -269,8 +295,8 @@ RSpec.shared_examples_for 'a flipper adapter' do
|
|
269
295
|
|
270
296
|
it 'can double enable an actor without error' do
|
271
297
|
actor = Flipper::Actor.new('Flipper::Actor;22')
|
272
|
-
expect(subject.enable(feature, actor_gate,
|
273
|
-
expect(subject.enable(feature, actor_gate,
|
298
|
+
expect(subject.enable(feature, actor_gate, Flipper::Types::Actor.new(actor))).to eq(true)
|
299
|
+
expect(subject.enable(feature, actor_gate, Flipper::Types::Actor.new(actor))).to eq(true)
|
274
300
|
expect(subject.get(feature).fetch(:actors)).to eq(Set['Flipper::Actor;22'])
|
275
301
|
end
|
276
302
|
|
@@ -281,13 +307,13 @@ RSpec.shared_examples_for 'a flipper adapter' do
|
|
281
307
|
end
|
282
308
|
|
283
309
|
it 'can double enable percentage without error' do
|
284
|
-
expect(subject.enable(feature, actors_gate,
|
285
|
-
expect(subject.enable(feature, actors_gate,
|
310
|
+
expect(subject.enable(feature, actors_gate, Flipper::Types::PercentageOfActors.new(25))).to eq(true)
|
311
|
+
expect(subject.enable(feature, actors_gate, Flipper::Types::PercentageOfActors.new(25))).to eq(true)
|
286
312
|
end
|
287
313
|
|
288
314
|
it 'can double enable without error' do
|
289
|
-
expect(subject.enable(feature, boolean_gate,
|
290
|
-
expect(subject.enable(feature, boolean_gate,
|
315
|
+
expect(subject.enable(feature, boolean_gate, Flipper::Types::Boolean.new)).to eq(true)
|
316
|
+
expect(subject.enable(feature, boolean_gate, Flipper::Types::Boolean.new)).to eq(true)
|
291
317
|
end
|
292
318
|
|
293
319
|
it 'can get_all features when there are none' do
|
@@ -297,11 +323,26 @@ RSpec.shared_examples_for 'a flipper adapter' do
|
|
297
323
|
|
298
324
|
it 'clears other gate values on enable' do
|
299
325
|
actor = Flipper::Actor.new('Flipper::Actor;22')
|
300
|
-
subject.enable(feature, actors_gate,
|
301
|
-
subject.enable(feature, time_gate,
|
326
|
+
subject.enable(feature, actors_gate, Flipper::Types::PercentageOfActors.new(25))
|
327
|
+
subject.enable(feature, time_gate, Flipper::Types::PercentageOfTime.new(25))
|
302
328
|
subject.enable(feature, group_gate, flipper.group(:admins))
|
303
|
-
subject.enable(feature, actor_gate,
|
304
|
-
subject.enable(feature, boolean_gate,
|
329
|
+
subject.enable(feature, actor_gate, Flipper::Types::Actor.new(actor))
|
330
|
+
subject.enable(feature, boolean_gate, Flipper::Types::Boolean.new(true))
|
305
331
|
expect(subject.get(feature)).to eq(subject.default_config.merge(boolean: "true"))
|
306
332
|
end
|
333
|
+
|
334
|
+
it 'can import and export' do
|
335
|
+
adapter = Flipper::Adapters::Memory.new
|
336
|
+
source_flipper = Flipper.new(adapter)
|
337
|
+
source_flipper.enable(:stats)
|
338
|
+
export = adapter.export
|
339
|
+
|
340
|
+
# some adapters cannot import so if they return false lets assert it
|
341
|
+
# didn't happen
|
342
|
+
if subject.import(export)
|
343
|
+
expect(flipper[:stats]).to be_enabled
|
344
|
+
else
|
345
|
+
expect(flipper[:stats]).not_to be_enabled
|
346
|
+
end
|
347
|
+
end
|
307
348
|
end
|