flipper 1.3.2 → 1.3.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/.github/workflows/ci.yml +1 -1
- data/.github/workflows/examples.yml +1 -1
- data/Gemfile +6 -2
- data/examples/cloud/backoff_policy.rb +1 -1
- data/lib/flipper/adapters/http/error.rb +1 -1
- data/lib/flipper/adapters/http.rb +2 -2
- data/lib/flipper/adapters/poll.rb +15 -0
- data/lib/flipper/cloud/configuration.rb +4 -1
- data/lib/flipper/cloud/telemetry/backoff_policy.rb +6 -3
- data/lib/flipper/cloud/telemetry/submitter.rb +3 -1
- data/lib/flipper/cloud/telemetry.rb +2 -2
- data/lib/flipper/export.rb +0 -2
- data/lib/flipper/expressions/all.rb +0 -2
- data/lib/flipper/feature.rb +8 -1
- data/lib/flipper/instrumentation/log_subscriber.rb +0 -1
- data/lib/flipper/instrumentation/statsd.rb +4 -2
- data/lib/flipper/instrumentation/subscriber.rb +0 -4
- data/lib/flipper/metadata.rb +1 -0
- data/lib/flipper/poller.rb +2 -2
- data/lib/flipper/version.rb +1 -1
- data/lib/generators/flipper/setup_generator.rb +5 -0
- data/lib/generators/flipper/templates/initializer.rb +45 -0
- data/spec/flipper/adapters/http_spec.rb +1 -0
- data/spec/flipper/adapters/poll_spec.rb +41 -0
- data/spec/flipper/cloud/dsl_spec.rb +1 -1
- data/spec/flipper/cloud/telemetry/backoff_policy_spec.rb +3 -3
- data/spec/flipper/cloud/telemetry/submitter_spec.rb +4 -4
- data/spec/flipper/cloud/telemetry_spec.rb +6 -6
- data/spec/flipper/cloud_spec.rb +9 -4
- data/spec/flipper/dsl_spec.rb +0 -3
- data/spec/flipper/engine_spec.rb +1 -0
- data/spec/flipper/feature_spec.rb +22 -11
- data/spec/flipper/instrumentation/log_subscriber_spec.rb +1 -0
- data/spec/flipper/instrumentation/statsd_subscriber_spec.rb +1 -1
- data/spec/flipper/middleware/memoizer_spec.rb +4 -5
- data/spec/flipper/model/active_record_spec.rb +11 -0
- data/spec/flipper_spec.rb +1 -1
- data/spec/spec_helper.rb +8 -5
- data/test_rails/generators/flipper/setup_generator_test.rb +5 -0
- data/test_rails/generators/flipper/update_generator_test.rb +1 -1
- data/test_rails/helper.rb +3 -0
- metadata +8 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8078cb39434d25df42e6eb6b254386568da4eed95f519f6675b79eba2bdb74c2
|
4
|
+
data.tar.gz: 6dbefd84a3553b00d0654cdeef452404a5e25980984ee2e6a07abbb8885e2b61
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: dd0b492e592bbdf9a969d9ae027de871c8f298aaaa7be9c011d6b258918e4a45421ec4a253d1bd6eed6f246f0dae35124670e91c7c4d6e6617d7f90f4f2acb42
|
7
|
+
data.tar.gz: 70006d2e9bcddd9cf4085555b8d7cae79bdcbc93848abe1e6ee4efe0f3fd4d5dc76cae073ab1a8f981f2bd29cc5ed1e7f90795428e53acf0c571f166c1406a82
|
data/.github/workflows/ci.yml
CHANGED
@@ -84,7 +84,7 @@ jobs:
|
|
84
84
|
- name: Setup memcached
|
85
85
|
uses: KeisukeYamashita/memcached-actions@v1
|
86
86
|
- name: Start MongoDB
|
87
|
-
uses: supercharge/mongodb-github-action@1.
|
87
|
+
uses: supercharge/mongodb-github-action@1.12.0
|
88
88
|
with:
|
89
89
|
mongodb-version: 4.0
|
90
90
|
- name: Check out repository code
|
@@ -66,7 +66,7 @@ jobs:
|
|
66
66
|
- name: Setup memcached
|
67
67
|
uses: KeisukeYamashita/memcached-actions@v1
|
68
68
|
- name: Start MongoDB
|
69
|
-
uses: supercharge/mongodb-github-action@1.
|
69
|
+
uses: supercharge/mongodb-github-action@1.12.0
|
70
70
|
with:
|
71
71
|
mongodb-version: 4.0
|
72
72
|
- name: Check out repository code
|
data/Gemfile
CHANGED
@@ -6,16 +6,19 @@ Dir['flipper-*.gemspec'].each do |gemspec|
|
|
6
6
|
gemspec(name: "flipper-#{plugin}", development_group: plugin)
|
7
7
|
end
|
8
8
|
|
9
|
+
gem 'concurrent-ruby', '1.3.4'
|
10
|
+
gem 'connection_pool'
|
9
11
|
gem 'debug'
|
10
12
|
gem 'rake'
|
11
13
|
gem 'statsd-ruby', '~> 1.2.1'
|
12
14
|
gem 'rspec', '~> 3.0'
|
13
15
|
gem 'rack-test'
|
14
16
|
gem 'rackup', '= 1.0.0'
|
15
|
-
gem 'sqlite3', "~> #{ENV['SQLITE3_VERSION'] || '1.
|
16
|
-
gem 'rails', "~> #{ENV['RAILS_VERSION'] || '
|
17
|
+
gem 'sqlite3', "~> #{ENV['SQLITE3_VERSION'] || '2.1.0'}"
|
18
|
+
gem 'rails', "~> #{ENV['RAILS_VERSION'] || '8.0'}"
|
17
19
|
gem 'minitest', '~> 5.18'
|
18
20
|
gem 'minitest-documentation'
|
21
|
+
gem 'pstore'
|
19
22
|
gem 'webmock'
|
20
23
|
gem 'ice_age'
|
21
24
|
gem 'redis-namespace'
|
@@ -28,6 +31,7 @@ gem 'mysql2'
|
|
28
31
|
gem 'pg'
|
29
32
|
gem 'cuprite'
|
30
33
|
gem 'puma'
|
34
|
+
gem 'warning'
|
31
35
|
|
32
36
|
group(:guard) do
|
33
37
|
gem 'guard'
|
@@ -59,8 +59,8 @@ module Flipper
|
|
59
59
|
response = @client.get("/features?exclude_gate_names=true")
|
60
60
|
raise Error, response unless response.is_a?(Net::HTTPOK)
|
61
61
|
|
62
|
-
parsed_response = Typecast.from_json(response.body)
|
63
|
-
parsed_features = parsed_response
|
62
|
+
parsed_response = response.body.empty? ? {} : Typecast.from_json(response.body)
|
63
|
+
parsed_features = parsed_response['features'] || []
|
64
64
|
gates_by_key = parsed_features.each_with_object({}) do |parsed_feature, hash|
|
65
65
|
hash[parsed_feature['key']] = parsed_feature['gates']
|
66
66
|
hash
|
@@ -18,6 +18,21 @@ module Flipper
|
|
18
18
|
@adapter = adapter
|
19
19
|
@poller = poller
|
20
20
|
@last_synced_at = 0
|
21
|
+
|
22
|
+
# If the adapter is empty, we need to sync before starting the poller.
|
23
|
+
# Yes, this will block the main thread, but that's better than thinking
|
24
|
+
# nothing is enabled.
|
25
|
+
if adapter.features.empty?
|
26
|
+
begin
|
27
|
+
@poller.sync
|
28
|
+
rescue
|
29
|
+
# TODO: Warn here that it's possible that no data has been synced
|
30
|
+
# and flags are being evaluated without flag data being present
|
31
|
+
# until a sync completes. We rescue to avoid flipper being down
|
32
|
+
# causing your processes to crash.
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
21
36
|
@poller.start
|
22
37
|
end
|
23
38
|
|
@@ -3,7 +3,6 @@ require "socket"
|
|
3
3
|
require "flipper/adapters/http"
|
4
4
|
require "flipper/adapters/poll"
|
5
5
|
require "flipper/poller"
|
6
|
-
require "flipper/adapters/memory"
|
7
6
|
require "flipper/adapters/dual_write"
|
8
7
|
require "flipper/adapters/sync/synchronizer"
|
9
8
|
require "flipper/cloud/telemetry"
|
@@ -135,6 +134,10 @@ module Flipper
|
|
135
134
|
logger.send(level, "name=flipper_cloud #{message}")
|
136
135
|
end
|
137
136
|
|
137
|
+
def instrument(name, payload = {}, &block)
|
138
|
+
instrumenter.instrument(name, payload, &block)
|
139
|
+
end
|
140
|
+
|
138
141
|
private
|
139
142
|
|
140
143
|
def app_adapter
|
@@ -3,10 +3,10 @@ module Flipper
|
|
3
3
|
class Telemetry
|
4
4
|
class BackoffPolicy
|
5
5
|
# Private: The default minimum timeout between intervals in milliseconds.
|
6
|
-
MIN_TIMEOUT_MS =
|
6
|
+
MIN_TIMEOUT_MS = 30_000
|
7
7
|
|
8
8
|
# Private: The default maximum timeout between intervals in milliseconds.
|
9
|
-
MAX_TIMEOUT_MS =
|
9
|
+
MAX_TIMEOUT_MS = 120_000
|
10
10
|
|
11
11
|
# Private: The value to multiply the current interval with for each
|
12
12
|
# retry attempt.
|
@@ -67,7 +67,10 @@ module Flipper
|
|
67
67
|
|
68
68
|
@attempts += 1
|
69
69
|
|
70
|
-
|
70
|
+
# cap the interval to the max timeout
|
71
|
+
result = [interval, @max_timeout_ms].min
|
72
|
+
# jitter even when maxed out
|
73
|
+
result == @max_timeout_ms ? add_jitter(result, 0.05) : result
|
71
74
|
end
|
72
75
|
|
73
76
|
def reset
|
@@ -34,7 +34,7 @@ module Flipper
|
|
34
34
|
return if drained.empty?
|
35
35
|
body = to_body(drained)
|
36
36
|
return if body.nil? || body.empty?
|
37
|
-
retry_with_backoff(
|
37
|
+
retry_with_backoff(5) { submit(body) }
|
38
38
|
end
|
39
39
|
|
40
40
|
private
|
@@ -51,6 +51,7 @@ module Flipper
|
|
51
51
|
|
52
52
|
Typecast.to_gzip(json)
|
53
53
|
rescue => exception
|
54
|
+
@cloud_configuration.instrument "telemetry_error.#{Flipper::InstrumentationNamespace}", exception: exception, request_id: request_id
|
54
55
|
@cloud_configuration.log "action=to_body request_id=#{request_id} error=#{exception.inspect}", level: :error
|
55
56
|
end
|
56
57
|
|
@@ -63,6 +64,7 @@ module Flipper
|
|
63
64
|
result, should_retry = yield
|
64
65
|
return [result, nil] unless should_retry
|
65
66
|
rescue => error
|
67
|
+
@cloud_configuration.instrument "telemetry_retry.#{Flipper::InstrumentationNamespace}", attempts_remaining: attempts_remaining, exception: error
|
66
68
|
@cloud_configuration.log "action=post_to_cloud attempts_remaining=#{attempts_remaining} error=#{error.inspect}", level: :error
|
67
69
|
should_retry = true
|
68
70
|
caught_exception = error
|
@@ -75,8 +75,8 @@ module Flipper
|
|
75
75
|
|
76
76
|
@metric_storage = MetricStorage.new
|
77
77
|
|
78
|
-
@pool = Concurrent::FixedThreadPool.new(
|
79
|
-
max_queue:
|
78
|
+
@pool = Concurrent::FixedThreadPool.new(1, {
|
79
|
+
max_queue: 20, # ~ 20 minutes of data at 1 minute intervals
|
80
80
|
fallback_policy: :discard,
|
81
81
|
name: "flipper-telemetry-post-to-cloud-pool".freeze,
|
82
82
|
})
|
data/lib/flipper/export.rb
CHANGED
data/lib/flipper/feature.rb
CHANGED
@@ -100,7 +100,14 @@ module Flipper
|
|
100
100
|
#
|
101
101
|
# Returns true if enabled, false if not.
|
102
102
|
def enabled?(*actors)
|
103
|
-
actors = actors.
|
103
|
+
actors = Array(actors).
|
104
|
+
# Avoids to_ary warning that happens when passing DelegateClass of an
|
105
|
+
# ActiveRecord object and using flatten here. This is tested in
|
106
|
+
# spec/flipper/model/active_record_spec.rb.
|
107
|
+
flat_map { |actor| actor.is_a?(Array) ? actor : [actor] }.
|
108
|
+
# Allows null object pattern. See PR for more. https://github.com/flippercloud/flipper/pull/887
|
109
|
+
reject(&:nil?).
|
110
|
+
map { |actor| Types::Actor.wrap(actor) }
|
104
111
|
actors = nil if actors.empty?
|
105
112
|
|
106
113
|
# thing is left for backwards compatibility
|
@@ -2,5 +2,7 @@ require 'securerandom'
|
|
2
2
|
require 'active_support/notifications'
|
3
3
|
require 'flipper/instrumentation/statsd_subscriber'
|
4
4
|
|
5
|
-
ActiveSupport::Notifications.subscribe
|
6
|
-
|
5
|
+
ActiveSupport::Notifications.subscribe(
|
6
|
+
/\.flipper$/,
|
7
|
+
Flipper::Instrumentation::StatsdSubscriber
|
8
|
+
)
|
@@ -42,7 +42,6 @@ module Flipper
|
|
42
42
|
# Private
|
43
43
|
def update_feature_operation_metrics
|
44
44
|
feature_name = @payload[:feature_name]
|
45
|
-
gate_name = @payload[:gate_name]
|
46
45
|
operation = strip_trailing_question_mark(@payload[:operation])
|
47
46
|
result = @payload[:result]
|
48
47
|
|
@@ -64,9 +63,6 @@ module Flipper
|
|
64
63
|
def update_adapter_operation_metrics
|
65
64
|
adapter_name = @payload[:adapter_name]
|
66
65
|
operation = @payload[:operation]
|
67
|
-
result = @payload[:result]
|
68
|
-
value = @payload[:value]
|
69
|
-
key = @payload[:key]
|
70
66
|
|
71
67
|
update_timer "flipper.adapter.#{adapter_name}.#{operation}"
|
72
68
|
end
|
data/lib/flipper/metadata.rb
CHANGED
@@ -7,5 +7,6 @@ module Flipper
|
|
7
7
|
"source_code_uri" => "https://github.com/flippercloud/flipper",
|
8
8
|
"bug_tracker_uri" => "https://github.com/flippercloud/flipper/issues",
|
9
9
|
"changelog_uri" => "https://github.com/flippercloud/flipper/releases/tag/v#{Flipper::VERSION}",
|
10
|
+
"funding_uri" => "https://github.com/sponsors/flippercloud",
|
10
11
|
}.freeze
|
11
12
|
end
|
data/lib/flipper/poller.rb
CHANGED
data/lib/flipper/version.rb
CHANGED
@@ -4,10 +4,15 @@ module Flipper
|
|
4
4
|
module Generators
|
5
5
|
class SetupGenerator < ::Rails::Generators::Base
|
6
6
|
desc 'Peform any necessary steps to install Flipper'
|
7
|
+
source_paths << File.expand_path('templates', __dir__)
|
7
8
|
|
8
9
|
class_option :token, type: :string, default: nil, aliases: '-t',
|
9
10
|
desc: "Your personal environment token for Flipper Cloud"
|
10
11
|
|
12
|
+
def generate_initializer
|
13
|
+
template 'initializer.rb', 'config/initializers/flipper.rb'
|
14
|
+
end
|
15
|
+
|
11
16
|
def generate_active_record
|
12
17
|
invoke 'flipper:active_record' if defined?(Flipper::Adapters::ActiveRecord)
|
13
18
|
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
Rails.application.configure do
|
2
|
+
## Memoization ensures that only one adapter call is made per feature per request.
|
3
|
+
## For more info, see https://www.flippercloud.io/docs/optimization#memoization
|
4
|
+
# config.flipper.memoize = true
|
5
|
+
|
6
|
+
## Flipper preloads all features before each request, which is recommended if:
|
7
|
+
## * you have a limited number of features (< 100?)
|
8
|
+
## * most of your requests depend on most of your features
|
9
|
+
## * you have limited gate data combined across all features (< 1k enabled gates, like individual actors, across all features)
|
10
|
+
##
|
11
|
+
## For more info, see https://www.flippercloud.io/docs/optimization#preloading
|
12
|
+
# config.flipper.preload = true
|
13
|
+
|
14
|
+
## Warn or raise an error if an unknown feature is checked
|
15
|
+
## Can be set to `:warn`, `:raise`, or `false`
|
16
|
+
# config.flipper.strict = Rails.env.development? && :warn
|
17
|
+
|
18
|
+
## Show Flipper checks in logs
|
19
|
+
# config.flipper.log = true
|
20
|
+
|
21
|
+
## Reconfigure Flipper to use the Memory adaper and disable Cloud in tests
|
22
|
+
# config.flipper.test_help = true
|
23
|
+
|
24
|
+
## The path that Flipper Cloud will use to sync features
|
25
|
+
# config.flipper.cloud_path = "_flipper"
|
26
|
+
|
27
|
+
## The instrumenter that Flipper will use. Defaults to ActiveSupport::Notifications.
|
28
|
+
# config.flipper.instrumenter = ActiveSupport::Notifications
|
29
|
+
end
|
30
|
+
|
31
|
+
Flipper.configure do |config|
|
32
|
+
## Configure other adapters that you want to use here:
|
33
|
+
## See http://flippercloud.io/docs/adapters
|
34
|
+
# config.use Flipper::Adapters::ActiveSupportCacheStore, Rails.cache, expires_in: 5.minutes
|
35
|
+
end
|
36
|
+
|
37
|
+
## Register a group that can be used for enabling features.
|
38
|
+
##
|
39
|
+
## Flipper.enable_group :my_feature, :admins
|
40
|
+
##
|
41
|
+
## See https://www.flippercloud.io/docs/features#enablement-group
|
42
|
+
#
|
43
|
+
# Flipper.register(:admins) do |actor|
|
44
|
+
# actor.respond_to?(:admin?) && actor.admin?
|
45
|
+
# end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'flipper/adapters/poll'
|
2
|
+
|
3
|
+
RSpec.describe Flipper::Adapters::Poll do
|
4
|
+
let(:remote_adapter) {
|
5
|
+
adapter = Flipper::Adapters::Memory.new(threadsafe: true)
|
6
|
+
flipper = Flipper.new(adapter)
|
7
|
+
flipper.enable(:search)
|
8
|
+
flipper.enable(:analytics)
|
9
|
+
adapter
|
10
|
+
}
|
11
|
+
let(:local_adapter) { Flipper::Adapters::Memory.new(threadsafe: true) }
|
12
|
+
let(:poller) {
|
13
|
+
Flipper::Poller.get("for_spec", {
|
14
|
+
start_automatically: false,
|
15
|
+
remote_adapter: remote_adapter,
|
16
|
+
})
|
17
|
+
}
|
18
|
+
|
19
|
+
it "syncs in main thread if local adapter is empty" do
|
20
|
+
instance = described_class.new(poller, local_adapter)
|
21
|
+
instance.features # call something to force sync
|
22
|
+
expect(local_adapter.features).to eq(remote_adapter.features)
|
23
|
+
end
|
24
|
+
|
25
|
+
it "does not sync in main thread if local adapter is not empty" do
|
26
|
+
# make local not empty by importing remote
|
27
|
+
flipper = Flipper.new(local_adapter)
|
28
|
+
flipper.import(remote_adapter)
|
29
|
+
|
30
|
+
# make a fake poller to verify calls
|
31
|
+
poller = double("Poller", last_synced_at: Concurrent::AtomicFixnum.new(0))
|
32
|
+
expect(poller).to receive(:start).twice
|
33
|
+
expect(poller).not_to receive(:sync)
|
34
|
+
|
35
|
+
# create new instance and call something to force sync
|
36
|
+
instance = described_class.new(poller, local_adapter)
|
37
|
+
instance.features # call something to force sync
|
38
|
+
|
39
|
+
expect(local_adapter.features).to eq(remote_adapter.features)
|
40
|
+
end
|
41
|
+
end
|
@@ -4,8 +4,8 @@ RSpec.describe Flipper::Cloud::Telemetry::BackoffPolicy do
|
|
4
4
|
context "#initialize" do
|
5
5
|
it "with no options" do
|
6
6
|
policy = described_class.new
|
7
|
-
expect(policy.min_timeout_ms).to eq(
|
8
|
-
expect(policy.max_timeout_ms).to eq(
|
7
|
+
expect(policy.min_timeout_ms).to eq(30_000)
|
8
|
+
expect(policy.max_timeout_ms).to eq(120_000)
|
9
9
|
expect(policy.multiplier).to eq(1.5)
|
10
10
|
expect(policy.randomization_factor).to eq(0.5)
|
11
11
|
end
|
@@ -87,7 +87,7 @@ RSpec.describe Flipper::Cloud::Telemetry::BackoffPolicy do
|
|
87
87
|
randomization_factor: 0.5,
|
88
88
|
})
|
89
89
|
10.times { policy.next_interval }
|
90
|
-
expect(policy.next_interval).to
|
90
|
+
expect(policy.next_interval).to be_within(10_000*0.1).of(10_000)
|
91
91
|
end
|
92
92
|
end
|
93
93
|
|
@@ -71,15 +71,15 @@ RSpec.describe Flipper::Cloud::Telemetry::Submitter do
|
|
71
71
|
to_return(status: 429, body: "{}").
|
72
72
|
to_return(status: 200, body: "{}")
|
73
73
|
instance = described_class.new(cloud_configuration)
|
74
|
-
expect(instance.backoff_policy.min_timeout_ms).to eq(
|
75
|
-
expect(instance.backoff_policy.max_timeout_ms).to eq(
|
74
|
+
expect(instance.backoff_policy.min_timeout_ms).to eq(30_000)
|
75
|
+
expect(instance.backoff_policy.max_timeout_ms).to eq(120_000)
|
76
76
|
end
|
77
77
|
|
78
78
|
it "tries 10 times by default" do
|
79
79
|
stub_request(:post, "https://www.flippercloud.io/adapter/telemetry").
|
80
80
|
to_return(status: 500, body: "{}")
|
81
81
|
subject.call(enabled_metrics)
|
82
|
-
expect(subject.backoff_policy.retries).to eq(
|
82
|
+
expect(subject.backoff_policy.retries).to eq(4) # 4 retries + 1 initial attempt
|
83
83
|
end
|
84
84
|
|
85
85
|
[
|
@@ -105,7 +105,7 @@ RSpec.describe Flipper::Cloud::Telemetry::Submitter do
|
|
105
105
|
stub_request(:post, "https://www.flippercloud.io/adapter/telemetry").
|
106
106
|
to_raise(error_class)
|
107
107
|
subject.call(enabled_metrics)
|
108
|
-
expect(subject.backoff_policy.retries).to eq(
|
108
|
+
expect(subject.backoff_policy.retries).to eq(4)
|
109
109
|
end
|
110
110
|
end
|
111
111
|
|
@@ -25,7 +25,7 @@ RSpec.describe Flipper::Cloud::Telemetry do
|
|
25
25
|
|
26
26
|
expect(telemetry.interval).to eq(60)
|
27
27
|
expect(telemetry.timer.execution_interval).to eq(60)
|
28
|
-
expect(stub).to have_been_requested
|
28
|
+
expect(stub).to have_been_requested.at_least_once
|
29
29
|
end
|
30
30
|
|
31
31
|
it "phones home and updates telemetry interval if present" do
|
@@ -45,7 +45,7 @@ RSpec.describe Flipper::Cloud::Telemetry do
|
|
45
45
|
|
46
46
|
expect(telemetry.interval).to eq(120)
|
47
47
|
expect(telemetry.timer.execution_interval).to eq(120)
|
48
|
-
expect(stub).to have_been_requested
|
48
|
+
expect(stub).to have_been_requested.at_least_once
|
49
49
|
end
|
50
50
|
|
51
51
|
it "phones home and requests shutdown if telemetry-shutdown header is true" do
|
@@ -67,7 +67,7 @@ RSpec.describe Flipper::Cloud::Telemetry do
|
|
67
67
|
result: true,
|
68
68
|
})
|
69
69
|
telemetry.stop
|
70
|
-
expect(stub).to have_been_requested
|
70
|
+
expect(stub).to have_been_requested.at_least_once
|
71
71
|
expect(output.string).to match(/action=telemetry_shutdown message=The server has requested that telemetry be shut down./)
|
72
72
|
end
|
73
73
|
|
@@ -90,7 +90,7 @@ RSpec.describe Flipper::Cloud::Telemetry do
|
|
90
90
|
result: true,
|
91
91
|
})
|
92
92
|
telemetry.stop
|
93
|
-
expect(stub).to have_been_requested
|
93
|
+
expect(stub).to have_been_requested.at_least_once
|
94
94
|
expect(output.string).not_to match(/action=telemetry_shutdown message=The server has requested that telemetry be shut down./)
|
95
95
|
end
|
96
96
|
|
@@ -122,7 +122,7 @@ RSpec.describe Flipper::Cloud::Telemetry do
|
|
122
122
|
# Check the conig interval and the timer interval.
|
123
123
|
expect(telemetry.interval).to eq(120)
|
124
124
|
expect(telemetry.timer.execution_interval).to eq(120)
|
125
|
-
expect(stub).to have_been_requested.times(
|
125
|
+
expect(stub).to have_been_requested.times(5)
|
126
126
|
end
|
127
127
|
|
128
128
|
it "doesn't try to update telemetry interval from error if not response error" do
|
@@ -152,7 +152,7 @@ RSpec.describe Flipper::Cloud::Telemetry do
|
|
152
152
|
|
153
153
|
expect(telemetry.interval).to eq(60)
|
154
154
|
expect(telemetry.timer.execution_interval).to eq(60)
|
155
|
-
expect(stub).to have_been_requested.times(
|
155
|
+
expect(stub).to have_been_requested.times(5)
|
156
156
|
end
|
157
157
|
|
158
158
|
describe '#record' do
|
data/spec/flipper/cloud_spec.rb
CHANGED
@@ -43,6 +43,7 @@ RSpec.describe Flipper::Cloud do
|
|
43
43
|
|
44
44
|
context 'initialize with token and options' do
|
45
45
|
it 'sets correct url' do
|
46
|
+
stub_request(:any, %r{fakeflipper.com}).to_return(status: 200)
|
46
47
|
instance = described_class.new(token: 'asdf', url: 'https://www.fakeflipper.com/sadpanda')
|
47
48
|
# pardon the nesting...
|
48
49
|
memoized = instance.adapter
|
@@ -78,27 +79,31 @@ RSpec.describe Flipper::Cloud do
|
|
78
79
|
end
|
79
80
|
|
80
81
|
it 'can set debug_output' do
|
82
|
+
instance = Flipper::Adapters::Http::Client.new(token: 'asdf', url: 'https://www.flippercloud.io/adapter')
|
81
83
|
expect(Flipper::Adapters::Http::Client).to receive(:new)
|
82
|
-
.with(hash_including(debug_output: STDOUT)).at_least(:once)
|
84
|
+
.with(hash_including(debug_output: STDOUT)).at_least(:once).and_return(instance)
|
83
85
|
described_class.new(token: 'asdf', debug_output: STDOUT)
|
84
86
|
end
|
85
87
|
|
86
88
|
it 'can set read_timeout' do
|
89
|
+
instance = Flipper::Adapters::Http::Client.new(token: 'asdf', url: 'https://www.flippercloud.io/adapter')
|
87
90
|
expect(Flipper::Adapters::Http::Client).to receive(:new)
|
88
|
-
.with(hash_including(read_timeout: 1)).at_least(:once)
|
91
|
+
.with(hash_including(read_timeout: 1)).at_least(:once).and_return(instance)
|
89
92
|
described_class.new(token: 'asdf', read_timeout: 1)
|
90
93
|
end
|
91
94
|
|
92
95
|
it 'can set open_timeout' do
|
96
|
+
instance = Flipper::Adapters::Http::Client.new(token: 'asdf', url: 'https://www.flippercloud.io/adapter')
|
93
97
|
expect(Flipper::Adapters::Http::Client).to receive(:new)
|
94
|
-
.with(hash_including(open_timeout: 1)).at_least(:once)
|
98
|
+
.with(hash_including(open_timeout: 1)).at_least(:once).and_return(instance)
|
95
99
|
described_class.new(token: 'asdf', open_timeout: 1)
|
96
100
|
end
|
97
101
|
|
98
102
|
if RUBY_VERSION >= '2.6.0'
|
99
103
|
it 'can set write_timeout' do
|
104
|
+
instance = Flipper::Adapters::Http::Client.new(token: 'asdf', url: 'https://www.flippercloud.io/adapter')
|
100
105
|
expect(Flipper::Adapters::Http::Client).to receive(:new)
|
101
|
-
.with(hash_including(open_timeout: 1)).at_least(:once)
|
106
|
+
.with(hash_including(open_timeout: 1)).at_least(:once).and_return(instance)
|
102
107
|
described_class.new(token: 'asdf', open_timeout: 1)
|
103
108
|
end
|
104
109
|
end
|
data/spec/flipper/dsl_spec.rb
CHANGED
@@ -225,9 +225,6 @@ RSpec.describe Flipper::DSL do
|
|
225
225
|
|
226
226
|
describe '#enable_group/disable_group' do
|
227
227
|
it 'enables and disables the feature for group' do
|
228
|
-
actor = Flipper::Actor.new(5)
|
229
|
-
group = Flipper.register(:fives) { |actor| actor.flipper_id == 5 }
|
230
|
-
|
231
228
|
expect(subject[:stats].groups_value).to be_empty
|
232
229
|
subject.enable_group(:stats, :fives)
|
233
230
|
expect(subject[:stats].groups_value).to eq(Set['fives'])
|
data/spec/flipper/engine_spec.rb
CHANGED
@@ -12,6 +12,7 @@ RSpec.describe Flipper::Engine do
|
|
12
12
|
end
|
13
13
|
|
14
14
|
before do
|
15
|
+
stub_request(:get, /flippercloud\.io/).to_return(status: 200, body: "{}")
|
15
16
|
Rails.application = nil
|
16
17
|
ActiveSupport::Dependencies.autoload_paths = ActiveSupport::Dependencies.autoload_paths.dup
|
17
18
|
ActiveSupport::Dependencies.autoload_once_paths = ActiveSupport::Dependencies.autoload_once_paths.dup
|
@@ -76,6 +76,26 @@ RSpec.describe Flipper::Feature do
|
|
76
76
|
expect(subject.enabled?(actors)).to be(false)
|
77
77
|
end
|
78
78
|
end
|
79
|
+
|
80
|
+
context "for an object that implements .nil? == true" do
|
81
|
+
let(:actor) { Flipper::Actor.new("User;1") }
|
82
|
+
|
83
|
+
before do
|
84
|
+
def actor.nil?
|
85
|
+
true
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
it 'returns true if feature is enabled' do
|
90
|
+
subject.enable
|
91
|
+
expect(subject.enabled?(actor)).to be(true)
|
92
|
+
end
|
93
|
+
|
94
|
+
it 'returns false if feature is disabled' do
|
95
|
+
subject.disable
|
96
|
+
expect(subject.enabled?(actor)).to be(false)
|
97
|
+
end
|
98
|
+
end
|
79
99
|
end
|
80
100
|
|
81
101
|
describe '#to_s' do
|
@@ -195,7 +215,7 @@ RSpec.describe Flipper::Feature do
|
|
195
215
|
|
196
216
|
it 'is recorded for enable' do
|
197
217
|
actor = Flipper::Types::Actor.new(Flipper::Actor.new('1'))
|
198
|
-
|
218
|
+
subject.gate_for(actor)
|
199
219
|
|
200
220
|
subject.enable(actor)
|
201
221
|
|
@@ -210,7 +230,7 @@ RSpec.describe Flipper::Feature do
|
|
210
230
|
|
211
231
|
it 'always instruments flipper type instance for enable' do
|
212
232
|
actor = Flipper::Actor.new('1')
|
213
|
-
|
233
|
+
subject.gate_for(actor)
|
214
234
|
|
215
235
|
subject.enable(actor)
|
216
236
|
|
@@ -221,7 +241,6 @@ RSpec.describe Flipper::Feature do
|
|
221
241
|
|
222
242
|
it 'is recorded for disable' do
|
223
243
|
thing = Flipper::Types::Boolean.new
|
224
|
-
gate = subject.gate_for(thing)
|
225
244
|
|
226
245
|
subject.disable(thing)
|
227
246
|
|
@@ -266,7 +285,6 @@ RSpec.describe Flipper::Feature do
|
|
266
285
|
|
267
286
|
it 'always instruments flipper type instance for disable' do
|
268
287
|
actor = Flipper::Actor.new('1')
|
269
|
-
gate = subject.gate_for(actor)
|
270
288
|
|
271
289
|
subject.disable(actor)
|
272
290
|
|
@@ -709,7 +727,6 @@ RSpec.describe Flipper::Feature do
|
|
709
727
|
context "with expression instance" do
|
710
728
|
it "updates gate values to equal expression or clears expression" do
|
711
729
|
expression = Flipper.property(:plan).eq("basic")
|
712
|
-
other_expression = Flipper.property(:age).gte(21)
|
713
730
|
expect(subject.gate_values.expression).to be(nil)
|
714
731
|
subject.enable_expression(expression)
|
715
732
|
expect(subject.gate_values.expression).to eq(expression.value)
|
@@ -721,7 +738,6 @@ RSpec.describe Flipper::Feature do
|
|
721
738
|
context "with Hash" do
|
722
739
|
it "updates gate values to equal expression or clears expression" do
|
723
740
|
expression = Flipper.property(:plan).eq("basic")
|
724
|
-
other_expression = Flipper.property(:age).gte(21)
|
725
741
|
expect(subject.gate_values.expression).to be(nil)
|
726
742
|
subject.enable_expression(expression.value)
|
727
743
|
expect(subject.gate_values.expression).to eq(expression.value)
|
@@ -1078,8 +1094,6 @@ RSpec.describe Flipper::Feature do
|
|
1078
1094
|
describe '#enable_group/disable_group' do
|
1079
1095
|
context 'with symbol group name' do
|
1080
1096
|
it 'updates the gate values to include the group' do
|
1081
|
-
actor = Flipper::Actor.new(5)
|
1082
|
-
group = Flipper.register(:five_only) { |actor| actor.flipper_id == 5 }
|
1083
1097
|
expect(subject.gate_values.groups).to be_empty
|
1084
1098
|
subject.enable_group(:five_only)
|
1085
1099
|
expect(subject.gate_values.groups).to eq(Set['five_only'])
|
@@ -1090,8 +1104,6 @@ RSpec.describe Flipper::Feature do
|
|
1090
1104
|
|
1091
1105
|
context 'with string group name' do
|
1092
1106
|
it 'updates the gate values to include the group' do
|
1093
|
-
actor = Flipper::Actor.new(5)
|
1094
|
-
group = Flipper.register(:five_only) { |actor| actor.flipper_id == 5 }
|
1095
1107
|
expect(subject.gate_values.groups).to be_empty
|
1096
1108
|
subject.enable_group('five_only')
|
1097
1109
|
expect(subject.gate_values.groups).to eq(Set['five_only'])
|
@@ -1102,7 +1114,6 @@ RSpec.describe Flipper::Feature do
|
|
1102
1114
|
|
1103
1115
|
context 'with group instance' do
|
1104
1116
|
it 'updates the gate values for the group' do
|
1105
|
-
actor = Flipper::Actor.new(5)
|
1106
1117
|
group = Flipper.register(:five_only) { |actor| actor.flipper_id == 5 }
|
1107
1118
|
expect(subject.gate_values.groups).to be_empty
|
1108
1119
|
subject.enable_group(group)
|
@@ -18,7 +18,7 @@ RSpec.describe Flipper::Instrumentation::StatsdSubscriber do
|
|
18
18
|
Flipper.new(adapter, instrumenter: ActiveSupport::Notifications)
|
19
19
|
end
|
20
20
|
|
21
|
-
let(:user) {
|
21
|
+
let(:user) { Flipper::Actor.new('1') }
|
22
22
|
|
23
23
|
before do
|
24
24
|
described_class.client = statsd_client
|
@@ -80,7 +80,7 @@ RSpec.describe Flipper::Middleware::Memoizer do
|
|
80
80
|
context 'with preload: true' do
|
81
81
|
let(:app) do
|
82
82
|
# ensure scoped for builder block, annoying...
|
83
|
-
|
83
|
+
flipper
|
84
84
|
middleware = described_class
|
85
85
|
|
86
86
|
Rack::Builder.new do
|
@@ -141,7 +141,7 @@ RSpec.describe Flipper::Middleware::Memoizer do
|
|
141
141
|
context 'with preload specific' do
|
142
142
|
let(:app) do
|
143
143
|
# ensure scoped for builder block, annoying...
|
144
|
-
|
144
|
+
flipper
|
145
145
|
middleware = described_class
|
146
146
|
|
147
147
|
Rack::Builder.new do
|
@@ -266,7 +266,7 @@ RSpec.describe Flipper::Middleware::Memoizer do
|
|
266
266
|
context 'with multiple instances' do
|
267
267
|
let(:app) do
|
268
268
|
# ensure scoped for builder block, annoying...
|
269
|
-
|
269
|
+
flipper
|
270
270
|
middleware = described_class
|
271
271
|
|
272
272
|
Rack::Builder.new do
|
@@ -316,7 +316,7 @@ RSpec.describe Flipper::Middleware::Memoizer do
|
|
316
316
|
context 'with flipper setup in env' do
|
317
317
|
let(:app) do
|
318
318
|
# ensure scoped for builder block, annoying...
|
319
|
-
|
319
|
+
flipper
|
320
320
|
middleware = described_class
|
321
321
|
|
322
322
|
Rack::Builder.new do
|
@@ -460,7 +460,6 @@ RSpec.describe Flipper::Middleware::Memoizer do
|
|
460
460
|
cache.clear
|
461
461
|
cached = Flipper::Adapters::ActiveSupportCacheStore.new(logged_memory, cache)
|
462
462
|
logged_cached = Flipper::Adapters::OperationLogger.new(cached)
|
463
|
-
memo = {}
|
464
463
|
flipper = Flipper.new(logged_cached)
|
465
464
|
flipper[:stats].enable
|
466
465
|
flipper[:shiny].enable
|
@@ -30,9 +30,20 @@ RSpec.describe Flipper::Model::ActiveRecord do
|
|
30
30
|
include Flipper::Model::ActiveRecord
|
31
31
|
end
|
32
32
|
|
33
|
+
class DelegatedUser < DelegateClass(User)
|
34
|
+
end
|
35
|
+
|
33
36
|
class Admin < User
|
34
37
|
end
|
35
38
|
|
39
|
+
it "doesn't warn for to_ary" do
|
40
|
+
# looks like we should remove this but you are wrong, we have specs that
|
41
|
+
# fail if there are warnings and if this regresses it will print a warning
|
42
|
+
# so it is in fact testing something
|
43
|
+
user = User.create!(name: "Test")
|
44
|
+
Flipper.enabled?(:something, DelegatedUser.new(user))
|
45
|
+
end
|
46
|
+
|
36
47
|
describe "flipper_id" do
|
37
48
|
it "returns class name and id" do
|
38
49
|
expect(User.new(id: 1).flipper_id).to eq("User;1")
|
data/spec/flipper_spec.rb
CHANGED
@@ -313,7 +313,7 @@ RSpec.describe Flipper do
|
|
313
313
|
|
314
314
|
describe '.group_exists' do
|
315
315
|
it 'returns true if the group is already created' do
|
316
|
-
|
316
|
+
described_class.register('admins', &:admin?)
|
317
317
|
expect(described_class.group_exists?(:admins)).to eq(true)
|
318
318
|
end
|
319
319
|
|
data/spec/spec_helper.rb
CHANGED
@@ -2,12 +2,14 @@ $LOAD_PATH.unshift(File.expand_path('../../lib', __FILE__))
|
|
2
2
|
|
3
3
|
require 'pp'
|
4
4
|
require 'pathname'
|
5
|
-
|
6
|
-
|
7
|
-
require 'rubygems'
|
8
|
-
require 'bundler'
|
5
|
+
require 'bundler/setup'
|
9
6
|
|
10
|
-
|
7
|
+
require 'warning'
|
8
|
+
Warning.ignore(/lib\/statsd/)
|
9
|
+
Warning.ignore(/lib\/debug\//)
|
10
|
+
Warning.ignore(/lib\/ice_age\//)
|
11
|
+
Warning.ignore(/lib\/moneta\//)
|
12
|
+
Warning.ignore(/lib\/mongo\//)
|
11
13
|
|
12
14
|
require 'debug'
|
13
15
|
require 'statsd'
|
@@ -20,6 +22,7 @@ require 'flipper/spec/shared_adapter_specs'
|
|
20
22
|
require 'flipper/ui'
|
21
23
|
require 'flipper/test_help'
|
22
24
|
|
25
|
+
FlipperRoot = Pathname(__FILE__).dirname.join('..').expand_path
|
23
26
|
Dir[FlipperRoot.join('spec/support/**/*.rb')].sort.each { |f| require f }
|
24
27
|
|
25
28
|
# Disable telemetry logging in specs.
|
@@ -17,6 +17,11 @@ class SetupGeneratorTest < Rails::Generators::TestCase
|
|
17
17
|
end
|
18
18
|
end
|
19
19
|
|
20
|
+
test "generates an initializer" do
|
21
|
+
run_generator
|
22
|
+
assert_file 'config/initializers/flipper.rb', /Flipper\.configure/
|
23
|
+
end
|
24
|
+
|
20
25
|
test "does not invoke flipper:active_record generator if ActiveRecord adapter not defined" do
|
21
26
|
# Ensure adapter not defined
|
22
27
|
Flipper::Adapters.send(:remove_const, :ActiveRecord) rescue nil
|
data/test_rails/helper.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: flipper
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.3.
|
4
|
+
version: 1.3.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- John Nunemaker
|
8
|
-
autorequire:
|
9
8
|
bindir: exe
|
10
9
|
cert_chain: []
|
11
|
-
date:
|
10
|
+
date: 2025-02-24 00:00:00.000000000 Z
|
12
11
|
dependencies:
|
13
12
|
- !ruby/object:Gem::Dependency
|
14
13
|
name: concurrent-ruby
|
@@ -24,7 +23,6 @@ dependencies:
|
|
24
23
|
- - "<"
|
25
24
|
- !ruby/object:Gem::Version
|
26
25
|
version: '2'
|
27
|
-
description:
|
28
26
|
email: support@flippercloud.io
|
29
27
|
executables:
|
30
28
|
- flipper
|
@@ -193,6 +191,7 @@ files:
|
|
193
191
|
- lib/flipper/types/percentage_of_time.rb
|
194
192
|
- lib/flipper/version.rb
|
195
193
|
- lib/generators/flipper/setup_generator.rb
|
194
|
+
- lib/generators/flipper/templates/initializer.rb
|
196
195
|
- lib/generators/flipper/templates/update/migrations/01_create_flipper_tables.rb.erb
|
197
196
|
- lib/generators/flipper/templates/update/migrations/02_change_flipper_gates_value_to_text.rb.erb
|
198
197
|
- lib/generators/flipper/update_generator.rb
|
@@ -214,6 +213,7 @@ files:
|
|
214
213
|
- spec/flipper/adapters/memoizable_spec.rb
|
215
214
|
- spec/flipper/adapters/memory_spec.rb
|
216
215
|
- spec/flipper/adapters/operation_logger_spec.rb
|
216
|
+
- spec/flipper/adapters/poll_spec.rb
|
217
217
|
- spec/flipper/adapters/pstore_spec.rb
|
218
218
|
- spec/flipper/adapters/read_only_spec.rb
|
219
219
|
- spec/flipper/adapters/strict_spec.rb
|
@@ -314,8 +314,8 @@ metadata:
|
|
314
314
|
homepage_uri: https://www.flippercloud.io
|
315
315
|
source_code_uri: https://github.com/flippercloud/flipper
|
316
316
|
bug_tracker_uri: https://github.com/flippercloud/flipper/issues
|
317
|
-
changelog_uri: https://github.com/flippercloud/flipper/releases/tag/v1.3.
|
318
|
-
|
317
|
+
changelog_uri: https://github.com/flippercloud/flipper/releases/tag/v1.3.3
|
318
|
+
funding_uri: https://github.com/sponsors/flippercloud
|
319
319
|
rdoc_options: []
|
320
320
|
require_paths:
|
321
321
|
- lib
|
@@ -330,8 +330,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
330
330
|
- !ruby/object:Gem::Version
|
331
331
|
version: '0'
|
332
332
|
requirements: []
|
333
|
-
rubygems_version: 3.5
|
334
|
-
signing_key:
|
333
|
+
rubygems_version: 3.6.5
|
335
334
|
specification_version: 4
|
336
335
|
summary: Beautiful, performant feature flags for Ruby and Rails.
|
337
336
|
test_files:
|
@@ -351,6 +350,7 @@ test_files:
|
|
351
350
|
- spec/flipper/adapters/memoizable_spec.rb
|
352
351
|
- spec/flipper/adapters/memory_spec.rb
|
353
352
|
- spec/flipper/adapters/operation_logger_spec.rb
|
353
|
+
- spec/flipper/adapters/poll_spec.rb
|
354
354
|
- spec/flipper/adapters/pstore_spec.rb
|
355
355
|
- spec/flipper/adapters/read_only_spec.rb
|
356
356
|
- spec/flipper/adapters/strict_spec.rb
|