flipper 0.24.1 → 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/FUNDING.yml +1 -0
- data/.github/dependabot.yml +6 -0
- data/.github/workflows/ci.yml +45 -14
- data/.github/workflows/examples.yml +39 -16
- data/Changelog.md +2 -443
- data/Gemfile +19 -11
- data/README.md +31 -27
- data/Rakefile +6 -4
- 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/banner.jpg +0 -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/instrumentation.rb +1 -0
- data/examples/instrumentation_last_accessed_at.rb +1 -0
- data/examples/mirroring.rb +59 -0
- data/examples/strict.rb +18 -0
- data/exe/flipper +5 -0
- data/flipper-cloud.gemspec +19 -0
- data/flipper.gemspec +10 -6
- 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/actor_limit.rb +28 -0
- data/lib/flipper/adapters/cache_base.rb +143 -0
- data/lib/flipper/adapters/dual_write.rb +1 -3
- data/lib/flipper/adapters/failover.rb +0 -4
- data/lib/flipper/adapters/failsafe.rb +72 -0
- data/lib/flipper/adapters/http/client.rb +44 -20
- data/lib/flipper/adapters/http/error.rb +1 -1
- data/lib/flipper/adapters/http.rb +31 -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 +17 -78
- data/lib/flipper/adapters/poll/poller.rb +2 -0
- data/lib/flipper/adapters/poll.rb +37 -0
- data/lib/flipper/adapters/pstore.rb +17 -11
- data/lib/flipper/adapters/read_only.rb +8 -41
- data/lib/flipper/adapters/strict.rb +45 -0
- data/lib/flipper/adapters/sync/feature_synchronizer.rb +10 -1
- data/lib/flipper/adapters/sync.rb +0 -4
- data/lib/flipper/adapters/wrapper.rb +54 -0
- data/lib/flipper/cli.rb +263 -0
- data/lib/flipper/cloud/configuration.rb +263 -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 +22 -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 +191 -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 +102 -0
- data/lib/flipper/errors.rb +3 -20
- 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 +34 -6
- data/lib/flipper/instrumentation/statsd_subscriber.rb +2 -4
- data/lib/flipper/instrumentation/subscriber.rb +8 -1
- data/lib/flipper/metadata.rb +7 -1
- data/lib/flipper/middleware/memoizer.rb +28 -22
- data/lib/flipper/model/active_record.rb +23 -0
- data/lib/flipper/poller.rb +118 -0
- data/lib/flipper/serializers/gzip.rb +22 -0
- data/lib/flipper/serializers/json.rb +17 -0
- data/lib/flipper/spec/shared_adapter_specs.rb +105 -63
- data/lib/flipper/test/shared_adapter_test.rb +101 -58
- data/lib/flipper/test_help.rb +43 -0
- data/lib/flipper/typecast.rb +59 -18
- 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 +11 -1
- data/lib/flipper.rb +50 -11
- data/lib/generators/flipper/setup_generator.rb +63 -0
- data/lib/generators/flipper/templates/update/migrations/01_create_flipper_tables.rb.erb +22 -0
- data/lib/generators/flipper/templates/update/migrations/02_change_flipper_gates_value_to_text.rb.erb +18 -0
- data/lib/generators/flipper/update_generator.rb +35 -0
- data/package-lock.json +41 -0
- data/package.json +10 -0
- data/spec/fixtures/environment.rb +1 -0
- data/spec/fixtures/flipper_pstore_1679087600.json +46 -0
- data/spec/flipper/adapter_builder_spec.rb +72 -0
- data/spec/flipper/adapter_spec.rb +30 -2
- data/spec/flipper/adapters/actor_limit_spec.rb +20 -0
- data/spec/flipper/adapters/dual_write_spec.rb +2 -2
- data/spec/flipper/adapters/failsafe_spec.rb +58 -0
- data/spec/flipper/adapters/http/client_spec.rb +61 -0
- data/spec/flipper/adapters/http_spec.rb +137 -55
- 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 +64 -0
- data/spec/flipper/adapters/sync/feature_synchronizer_spec.rb +27 -0
- data/spec/flipper/cli_spec.rb +164 -0
- data/spec/flipper/cloud/configuration_spec.rb +251 -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 +107 -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 +208 -0
- data/spec/flipper/cloud_spec.rb +181 -0
- data/spec/flipper/configuration_spec.rb +17 -0
- data/spec/flipper/dsl_spec.rb +54 -73
- data/spec/flipper/engine_spec.rb +373 -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 +23 -6
- data/spec/flipper/instrumentation/statsd_subscriber_spec.rb +25 -1
- data/spec/flipper/middleware/memoizer_spec.rb +74 -24
- data/spec/flipper/model/active_record_spec.rb +61 -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 +93 -29
- data/spec/spec_helper.rb +8 -14
- data/spec/support/actor_names.yml +1 -0
- data/spec/support/fail_on_output.rb +8 -0
- data/spec/support/fake_backoff_policy.rb +15 -0
- data/spec/support/skippable.rb +18 -0
- data/spec/support/spec_helpers.rb +23 -8
- data/test/adapters/actor_limit_test.rb +20 -0
- data/test_rails/generators/flipper/setup_generator_test.rb +64 -0
- data/test_rails/generators/flipper/update_generator_test.rb +96 -0
- data/test_rails/helper.rb +19 -2
- data/test_rails/system/test_help_test.rb +51 -0
- metadata +223 -19
- data/lib/flipper/railtie.rb +0 -47
- data/spec/flipper/railtie_spec.rb +0 -73
@@ -0,0 +1,17 @@
|
|
1
|
+
require "flipper/exporters/json/v1"
|
2
|
+
|
3
|
+
module Flipper
|
4
|
+
module Exporter
|
5
|
+
extend self
|
6
|
+
|
7
|
+
FORMATTERS = {
|
8
|
+
json: {
|
9
|
+
1 => Flipper::Exporters::Json::V1,
|
10
|
+
}
|
11
|
+
}.freeze
|
12
|
+
|
13
|
+
def build(format: :json, version: 1)
|
14
|
+
FORMATTERS.fetch(format).fetch(version).new
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require "flipper/export"
|
2
|
+
require "flipper/typecast"
|
3
|
+
|
4
|
+
module Flipper
|
5
|
+
module Exporters
|
6
|
+
module Json
|
7
|
+
# Raised when the contents of the export are not valid.
|
8
|
+
class InvalidError < StandardError; end
|
9
|
+
class JsonError < InvalidError; end
|
10
|
+
|
11
|
+
# Internal: JSON export class that knows how to build features hash
|
12
|
+
# from data.
|
13
|
+
class Export < ::Flipper::Export
|
14
|
+
def initialize(contents:, version: 1)
|
15
|
+
super contents: contents, version: version, format: :json
|
16
|
+
end
|
17
|
+
|
18
|
+
# Public: The features hash identical to calling get_all on adapter.
|
19
|
+
def features
|
20
|
+
@features ||= begin
|
21
|
+
features = Typecast.from_json(contents).fetch("features")
|
22
|
+
Typecast.features_hash(features)
|
23
|
+
rescue JSON::ParserError
|
24
|
+
raise JsonError
|
25
|
+
rescue
|
26
|
+
raise InvalidError
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require "json"
|
2
|
+
require "flipper/exporters/json/export"
|
3
|
+
|
4
|
+
module Flipper
|
5
|
+
module Exporters
|
6
|
+
module Json
|
7
|
+
class V1
|
8
|
+
VERSION = 1
|
9
|
+
|
10
|
+
def call(adapter)
|
11
|
+
features = adapter.get_all
|
12
|
+
|
13
|
+
# Convert sets to arrays for json
|
14
|
+
features.each do |feature_key, gates|
|
15
|
+
gates.each do |key, value|
|
16
|
+
case value
|
17
|
+
when Set
|
18
|
+
features[feature_key][key] = value.to_a
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
json = Typecast.to_json({
|
24
|
+
version: VERSION,
|
25
|
+
features: features,
|
26
|
+
})
|
27
|
+
|
28
|
+
Json::Export.new(contents: json, version: VERSION)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module Flipper
|
2
|
+
class Expression
|
3
|
+
module Builder
|
4
|
+
def build(object)
|
5
|
+
Expression.build(object)
|
6
|
+
end
|
7
|
+
|
8
|
+
def add(*expressions)
|
9
|
+
group? ? build(name => args + expressions.flatten) : any.add(*expressions)
|
10
|
+
end
|
11
|
+
|
12
|
+
def remove(*expressions)
|
13
|
+
group? ? build(name => args - expressions.flatten) : any.remove(*expressions)
|
14
|
+
end
|
15
|
+
|
16
|
+
def any
|
17
|
+
any? ? self : build({ Any: [self] })
|
18
|
+
end
|
19
|
+
|
20
|
+
def all
|
21
|
+
all? ? self : build({ All: [self] })
|
22
|
+
end
|
23
|
+
|
24
|
+
def equal(object)
|
25
|
+
build({ Equal: [self, object] })
|
26
|
+
end
|
27
|
+
alias eq equal
|
28
|
+
|
29
|
+
def not_equal(object)
|
30
|
+
build({ NotEqual: [self, object] })
|
31
|
+
end
|
32
|
+
alias neq not_equal
|
33
|
+
|
34
|
+
def greater_than(object)
|
35
|
+
build({ GreaterThan: [self, object] })
|
36
|
+
end
|
37
|
+
alias gt greater_than
|
38
|
+
|
39
|
+
def greater_than_or_equal_to(object)
|
40
|
+
build({ GreaterThanOrEqualTo: [self, object] })
|
41
|
+
end
|
42
|
+
alias gte greater_than_or_equal_to
|
43
|
+
alias greater_than_or_equal greater_than_or_equal_to
|
44
|
+
|
45
|
+
def less_than(object)
|
46
|
+
build({ LessThan: [self, object] })
|
47
|
+
end
|
48
|
+
alias lt less_than
|
49
|
+
|
50
|
+
def less_than_or_equal_to(object)
|
51
|
+
build({ LessThanOrEqualTo: [self, object] })
|
52
|
+
end
|
53
|
+
alias lte less_than_or_equal_to
|
54
|
+
alias less_than_or_equal less_than_or_equal_to
|
55
|
+
|
56
|
+
def percentage_of_actors(object)
|
57
|
+
build({ PercentageOfActors: [self, build(object)] })
|
58
|
+
end
|
59
|
+
|
60
|
+
def any?
|
61
|
+
is_a?(Expression) && function == Expressions::Any
|
62
|
+
end
|
63
|
+
|
64
|
+
def all?
|
65
|
+
is_a?(Expression) && function == Expressions::All
|
66
|
+
end
|
67
|
+
|
68
|
+
def group?
|
69
|
+
any? || all?
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Flipper
|
2
|
+
class Expression
|
3
|
+
# Public: A constant value like a "string", Number (1, 3.5), Boolean (true, false).
|
4
|
+
#
|
5
|
+
# Implements the same interface as Expression
|
6
|
+
class Constant
|
7
|
+
include Expression::Builder
|
8
|
+
|
9
|
+
attr_reader :value
|
10
|
+
|
11
|
+
def initialize(value)
|
12
|
+
@value = value
|
13
|
+
end
|
14
|
+
|
15
|
+
def evaluate(_ = nil)
|
16
|
+
value
|
17
|
+
end
|
18
|
+
|
19
|
+
def eql?(other)
|
20
|
+
other.is_a?(self.class) && other.value == value
|
21
|
+
end
|
22
|
+
alias_method :==, :eql?
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require "flipper/expression/builder"
|
2
|
+
require "flipper/expression/constant"
|
3
|
+
|
4
|
+
module Flipper
|
5
|
+
class Expression
|
6
|
+
include Builder
|
7
|
+
|
8
|
+
def self.build(object)
|
9
|
+
return object if object.is_a?(self) || object.is_a?(Constant)
|
10
|
+
|
11
|
+
case object
|
12
|
+
when Hash
|
13
|
+
name = object.keys.first
|
14
|
+
args = object.values.first
|
15
|
+
unless name
|
16
|
+
raise ArgumentError, "#{object.inspect} cannot be converted into an expression"
|
17
|
+
end
|
18
|
+
|
19
|
+
new(name, Array(args).map { |o| build(o) })
|
20
|
+
when String, Numeric, FalseClass, TrueClass
|
21
|
+
Expression::Constant.new(object)
|
22
|
+
when Symbol
|
23
|
+
Expression::Constant.new(object.to_s)
|
24
|
+
else
|
25
|
+
raise ArgumentError, "#{object.inspect} cannot be converted into an expression"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# Use #build
|
30
|
+
private_class_method :new
|
31
|
+
|
32
|
+
attr_reader :name, :function, :args
|
33
|
+
|
34
|
+
def initialize(name, args = [])
|
35
|
+
@name = name.to_s
|
36
|
+
@function = Expressions.const_get(name)
|
37
|
+
@args = args
|
38
|
+
end
|
39
|
+
|
40
|
+
def evaluate(context = {})
|
41
|
+
if call_with_context?
|
42
|
+
function.call(*args.map {|arg| arg.evaluate(context) }, context: context)
|
43
|
+
else
|
44
|
+
function.call(*args.map {|arg| arg.evaluate(context) })
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def eql?(other)
|
49
|
+
other.is_a?(self.class) && @function == other.function && @args == other.args
|
50
|
+
end
|
51
|
+
alias_method :==, :eql?
|
52
|
+
|
53
|
+
def value
|
54
|
+
{
|
55
|
+
name => args.map(&:value)
|
56
|
+
}
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
def call_with_context?
|
62
|
+
function.method(:call).parameters.any? do |type, name|
|
63
|
+
name == :context && [:key, :keyreq].include?(type)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
Dir[File.join(File.dirname(__FILE__), 'expressions', '*.rb')].sort.each do |file|
|
70
|
+
require "flipper/expressions/#{File.basename(file, '.rb')}"
|
71
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Flipper
|
2
|
+
module Expressions
|
3
|
+
class Comparable
|
4
|
+
def self.operator
|
5
|
+
raise NotImplementedError
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.call(left, right)
|
9
|
+
left.respond_to?(operator) && right.respond_to?(operator) && left.public_send(operator, right)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Flipper
|
2
|
+
module Expressions
|
3
|
+
class Duration
|
4
|
+
SECONDS_PER = {
|
5
|
+
"second" => 1,
|
6
|
+
"minute" => 60,
|
7
|
+
"hour" => 3600,
|
8
|
+
"day" => 86400,
|
9
|
+
"week" => 604_800,
|
10
|
+
"month" => 2_629_746, # 1/12 of a gregorian year
|
11
|
+
"year" => 31_556_952 # length of a gregorian year (365.2425 days)
|
12
|
+
}.freeze
|
13
|
+
|
14
|
+
def self.call(scalar, unit = 'second')
|
15
|
+
unit = unit.to_s.downcase.chomp("s")
|
16
|
+
|
17
|
+
unless scalar.is_a?(Numeric)
|
18
|
+
raise ArgumentError.new("Duration value must be a number but was #{scalar.inspect}")
|
19
|
+
end
|
20
|
+
unless SECONDS_PER[unit]
|
21
|
+
raise ArgumentError.new("Duration unit #{unit.inspect} must be one of: #{SECONDS_PER.keys.join(', ')}")
|
22
|
+
end
|
23
|
+
|
24
|
+
scalar * SECONDS_PER[unit]
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module Flipper
|
2
|
+
module Expressions
|
3
|
+
class PercentageOfActors
|
4
|
+
SCALING_FACTOR = 1_000
|
5
|
+
|
6
|
+
def self.call(text, percentage, context: {})
|
7
|
+
prefix = context[:feature_name] || ""
|
8
|
+
Zlib.crc32("#{prefix}#{text}") % (100 * SCALING_FACTOR) < percentage * SCALING_FACTOR
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
data/lib/flipper/feature.rb
CHANGED
@@ -96,18 +96,19 @@ module Flipper
|
|
96
96
|
instrument(:clear) { adapter.clear(self) }
|
97
97
|
end
|
98
98
|
|
99
|
-
# Public: Check if a feature is enabled for
|
99
|
+
# Public: Check if a feature is enabled for zero or more actors.
|
100
100
|
#
|
101
101
|
# Returns true if enabled, false if not.
|
102
|
-
def enabled?(
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
102
|
+
def enabled?(*actors)
|
103
|
+
actors = actors.flatten.compact.map { |actor| Types::Actor.wrap(actor) }
|
104
|
+
actors = nil if actors.empty?
|
105
|
+
|
106
|
+
# thing is left for backwards compatibility
|
107
|
+
instrument(:enabled?, thing: actors&.first, actors: actors) do |payload|
|
107
108
|
context = FeatureCheckContext.new(
|
108
109
|
feature_name: @name,
|
109
|
-
values:
|
110
|
-
|
110
|
+
values: gate_values,
|
111
|
+
actors: actors
|
111
112
|
)
|
112
113
|
|
113
114
|
if open_gate = gates.detect { |gate| gate.open?(context) }
|
@@ -119,6 +120,28 @@ module Flipper
|
|
119
120
|
end
|
120
121
|
end
|
121
122
|
|
123
|
+
# Public: Enables an expression_to_add for a feature.
|
124
|
+
#
|
125
|
+
# expression - an Expression or Hash that can be converted to an expression.
|
126
|
+
#
|
127
|
+
# Returns result of enable.
|
128
|
+
def enable_expression(expression)
|
129
|
+
enable Expression.build(expression)
|
130
|
+
end
|
131
|
+
|
132
|
+
# Public: Add an expression for a feature.
|
133
|
+
#
|
134
|
+
# expression_to_add - an expression or Hash that can be converted to an expression.
|
135
|
+
#
|
136
|
+
# Returns result of enable.
|
137
|
+
def add_expression(expression_to_add)
|
138
|
+
if (current_expression = expression)
|
139
|
+
enable current_expression.add(expression_to_add)
|
140
|
+
else
|
141
|
+
enable expression_to_add
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
122
145
|
# Public: Enables a feature for an actor.
|
123
146
|
#
|
124
147
|
# actor - a Flipper::Types::Actor instance or an object that responds
|
@@ -159,6 +182,27 @@ module Flipper
|
|
159
182
|
enable Types::PercentageOfActors.wrap(percentage)
|
160
183
|
end
|
161
184
|
|
185
|
+
# Public: Disables an expression for a feature.
|
186
|
+
#
|
187
|
+
# expression - an expression or Hash that can be converted to an expression.
|
188
|
+
#
|
189
|
+
# Returns result of disable.
|
190
|
+
def disable_expression
|
191
|
+
disable Flipper.all # just need an expression to clear
|
192
|
+
end
|
193
|
+
|
194
|
+
# Public: Remove an expression from a feature. Does nothing if no expression is
|
195
|
+
# currently enabled.
|
196
|
+
#
|
197
|
+
# expression - an Expression or Hash that can be converted to an expression.
|
198
|
+
#
|
199
|
+
# Returns result of enable or nil (if no expression enabled).
|
200
|
+
def remove_expression(expression_to_remove)
|
201
|
+
if (current_expression = expression)
|
202
|
+
enable current_expression.remove(expression_to_remove)
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
162
206
|
# Public: Disables a feature for an actor.
|
163
207
|
#
|
164
208
|
# actor - a Flipper::Types::Actor instance or an object that responds
|
@@ -207,7 +251,7 @@ module Flipper
|
|
207
251
|
|
208
252
|
if values.boolean || values.percentage_of_time == 100
|
209
253
|
:on
|
210
|
-
elsif non_boolean_gates.detect { |gate| gate.enabled?(values
|
254
|
+
elsif non_boolean_gates.detect { |gate| gate.enabled?(values.send(gate.key)) }
|
211
255
|
:conditional
|
212
256
|
else
|
213
257
|
:off
|
@@ -232,7 +276,8 @@ module Flipper
|
|
232
276
|
|
233
277
|
# Public: Returns the raw gate values stored by the adapter.
|
234
278
|
def gate_values
|
235
|
-
|
279
|
+
adapter_values = adapter.get(self)
|
280
|
+
GateValues.new(adapter_values)
|
236
281
|
end
|
237
282
|
|
238
283
|
# Public: Get groups enabled for this feature.
|
@@ -250,6 +295,10 @@ module Flipper
|
|
250
295
|
Flipper.groups - enabled_groups
|
251
296
|
end
|
252
297
|
|
298
|
+
def expression
|
299
|
+
Flipper::Expression.build(expression_value) if expression_value
|
300
|
+
end
|
301
|
+
|
253
302
|
# Public: Get the adapter value for the groups gate.
|
254
303
|
#
|
255
304
|
# Returns Set of String group names.
|
@@ -257,6 +306,13 @@ module Flipper
|
|
257
306
|
gate_values.groups
|
258
307
|
end
|
259
308
|
|
309
|
+
# Public: Get the adapter value for the expression gate.
|
310
|
+
#
|
311
|
+
# Returns expression.
|
312
|
+
def expression_value
|
313
|
+
gate_values.expression
|
314
|
+
end
|
315
|
+
|
260
316
|
# Public: Get the adapter value for the actors gate.
|
261
317
|
#
|
262
318
|
# Returns Set of String flipper_id's.
|
@@ -290,7 +346,7 @@ module Flipper
|
|
290
346
|
# Returns an Array of Flipper::Gate instances.
|
291
347
|
def enabled_gates
|
292
348
|
values = gate_values
|
293
|
-
gates.select { |gate| gate.enabled?(values
|
349
|
+
gates.select { |gate| gate.enabled?(values.send(gate.key)) }
|
294
350
|
end
|
295
351
|
|
296
352
|
# Public: Get the names of the enabled gates.
|
@@ -339,37 +395,42 @@ module Flipper
|
|
339
395
|
#
|
340
396
|
# Returns an array of gates
|
341
397
|
def gates
|
342
|
-
@gates ||=
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
Gates::
|
348
|
-
|
398
|
+
@gates ||= gates_hash.values.freeze
|
399
|
+
end
|
400
|
+
|
401
|
+
def gates_hash
|
402
|
+
@gates_hash ||= {
|
403
|
+
boolean: Gates::Boolean.new,
|
404
|
+
expression: Gates::Expression.new,
|
405
|
+
actor: Gates::Actor.new,
|
406
|
+
percentage_of_actors: Gates::PercentageOfActors.new,
|
407
|
+
percentage_of_time: Gates::PercentageOfTime.new,
|
408
|
+
group: Gates::Group.new,
|
409
|
+
}.freeze
|
349
410
|
end
|
350
411
|
|
351
412
|
# Public: Find a gate by name.
|
352
413
|
#
|
353
414
|
# Returns a Flipper::Gate if found, nil if not.
|
354
415
|
def gate(name)
|
355
|
-
|
416
|
+
gates_hash[name.to_sym]
|
356
417
|
end
|
357
418
|
|
358
|
-
# Public: Find the gate that protects
|
419
|
+
# Public: Find the gate that protects an actor.
|
359
420
|
#
|
360
|
-
#
|
421
|
+
# actor - The object for which you would like to find a gate
|
361
422
|
#
|
362
423
|
# Returns a Flipper::Gate.
|
363
|
-
# Raises Flipper::GateNotFound if no gate found for
|
364
|
-
def gate_for(
|
365
|
-
gates.detect { |gate| gate.protects?(
|
424
|
+
# Raises Flipper::GateNotFound if no gate found for actor
|
425
|
+
def gate_for(actor)
|
426
|
+
gates.detect { |gate| gate.protects?(actor) } || raise(GateNotFound, actor)
|
366
427
|
end
|
367
428
|
|
368
429
|
private
|
369
430
|
|
370
431
|
# Private: Instrument a feature operation.
|
371
|
-
def instrument(operation)
|
372
|
-
@instrumenter.instrument(InstrumentationName) do |payload|
|
432
|
+
def instrument(operation, initial_payload = {})
|
433
|
+
@instrumenter.instrument(InstrumentationName, initial_payload) do |payload|
|
373
434
|
payload[:feature_name] = name
|
374
435
|
payload[:operation] = operation
|
375
436
|
payload[:result] = yield(payload) if block_given?
|