flipper 0.16.0 → 1.4.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 +5 -5
- data/.codeclimate.yml +1 -0
- data/.github/FUNDING.yml +1 -0
- data/.github/dependabot.yml +6 -0
- data/.github/workflows/ci.yml +110 -0
- data/.github/workflows/examples.yml +105 -0
- data/.github/workflows/release.yml +54 -0
- data/.rspec +1 -0
- data/CLAUDE.md +87 -0
- data/Changelog.md +2 -215
- data/Dockerfile +1 -1
- data/Gemfile +28 -20
- data/README.md +72 -62
- data/Rakefile +13 -3
- 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/docker-compose.yml +37 -34
- data/docs/DockerCompose.md +0 -1
- data/docs/README.md +1 -0
- data/docs/images/banner.jpg +0 -0
- data/docs/images/flipper_cloud.png +0 -0
- data/examples/api/basic.ru +18 -0
- data/examples/api/custom_memoized.ru +36 -0
- data/examples/api/memoized.ru +42 -0
- data/examples/basic.rb +1 -12
- 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/poll_interval/README.md +111 -0
- data/examples/cloud/poll_interval/client.rb +108 -0
- data/examples/cloud/poll_interval/server.rb +98 -0
- data/examples/cloud/threaded.rb +33 -0
- data/examples/configuring_default.rb +2 -5
- data/examples/dsl.rb +10 -35
- data/examples/enabled_for_actor.rb +10 -15
- data/examples/expressions.rb +237 -0
- data/examples/group.rb +3 -6
- data/examples/group_dynamic_lookup.rb +5 -19
- data/examples/group_with_members.rb +4 -14
- data/examples/importing.rb +1 -1
- data/examples/individual_actor.rb +2 -5
- data/examples/instrumentation.rb +2 -2
- data/examples/instrumentation_last_accessed_at.rb +38 -0
- data/examples/memoizing.rb +35 -0
- data/examples/mirroring.rb +59 -0
- data/examples/percentage_of_actors.rb +6 -16
- data/examples/percentage_of_actors_enabled_check.rb +7 -10
- data/examples/percentage_of_actors_group.rb +5 -18
- data/examples/percentage_of_time.rb +3 -6
- data/examples/strict.rb +18 -0
- data/exe/flipper +5 -0
- data/flipper-cloud.gemspec +19 -0
- data/flipper.gemspec +10 -7
- data/lib/flipper/actor.rb +10 -3
- data/lib/flipper/adapter.rb +50 -8
- data/lib/flipper/adapter_builder.rb +44 -0
- data/lib/flipper/adapters/actor_limit.rb +54 -0
- data/lib/flipper/adapters/cache_base.rb +161 -0
- data/lib/flipper/adapters/dual_write.rb +63 -0
- data/lib/flipper/adapters/failover.rb +85 -0
- data/lib/flipper/adapters/failsafe.rb +72 -0
- data/lib/flipper/adapters/http/client.rb +64 -7
- data/lib/flipper/adapters/http/error.rb +19 -1
- data/lib/flipper/adapters/http.rb +97 -43
- data/lib/flipper/adapters/instrumented.rb +47 -26
- data/lib/flipper/adapters/memoizable.rb +44 -40
- data/lib/flipper/adapters/memory.rb +75 -111
- data/lib/flipper/adapters/operation_logger.rb +22 -78
- data/lib/flipper/adapters/poll/poller.rb +2 -0
- data/lib/flipper/adapters/poll.rb +52 -0
- data/lib/flipper/adapters/pstore.rb +27 -17
- 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 +14 -1
- data/lib/flipper/adapters/sync/interval_synchronizer.rb +2 -7
- data/lib/flipper/adapters/sync/synchronizer.rb +13 -6
- data/lib/flipper/adapters/sync.rb +23 -29
- data/lib/flipper/adapters/wrapper.rb +54 -0
- data/lib/flipper/cli.rb +314 -0
- data/lib/flipper/cloud/configuration.rb +271 -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/migrate.rb +71 -0
- data/lib/flipper/cloud/routes.rb +14 -0
- data/lib/flipper/cloud/telemetry/backoff_policy.rb +96 -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 +100 -0
- data/lib/flipper/cloud/telemetry.rb +191 -0
- data/lib/flipper/cloud.rb +54 -0
- data/lib/flipper/configuration.rb +54 -7
- data/lib/flipper/dsl.rb +58 -47
- data/lib/flipper/engine.rb +102 -0
- data/lib/flipper/errors.rb +3 -21
- data/lib/flipper/export.rb +24 -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 +9 -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/equal.rb +9 -0
- data/lib/flipper/expressions/feature_enabled.rb +34 -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 +16 -0
- data/lib/flipper/feature.rb +95 -28
- 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 +17 -0
- data/lib/flipper/instrumentation/log_subscriber.rb +35 -8
- data/lib/flipper/instrumentation/statsd.rb +4 -2
- data/lib/flipper/instrumentation/statsd_subscriber.rb +2 -4
- data/lib/flipper/instrumentation/subscriber.rb +8 -5
- data/lib/flipper/instrumenters/memory.rb +6 -2
- data/lib/flipper/metadata.rb +8 -1
- data/lib/flipper/middleware/memoizer.rb +46 -27
- data/lib/flipper/middleware/setup_env.rb +13 -3
- data/lib/flipper/model/active_record.rb +23 -0
- data/lib/flipper/poller.rb +157 -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 +122 -56
- data/lib/flipper/test/shared_adapter_test.rb +120 -52
- data/lib/flipper/test_help.rb +43 -0
- data/lib/flipper/typecast.rb +59 -18
- data/lib/flipper/types/actor.rb +19 -13
- data/lib/flipper/types/group.rb +12 -5
- data/lib/flipper/types/percentage.rb +1 -1
- data/lib/flipper/version.rb +11 -1
- data/lib/flipper.rb +71 -12
- data/lib/generators/flipper/setup_generator.rb +68 -0
- data/lib/generators/flipper/templates/initializer.rb +45 -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/actor_spec.rb +10 -2
- data/spec/flipper/adapter_builder_spec.rb +72 -0
- data/spec/flipper/adapter_spec.rb +52 -6
- data/spec/flipper/adapters/actor_limit_spec.rb +75 -0
- data/spec/flipper/adapters/dual_write_spec.rb +82 -0
- data/spec/flipper/adapters/failover_spec.rb +141 -0
- 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 +402 -65
- data/spec/flipper/adapters/instrumented_spec.rb +31 -13
- data/spec/flipper/adapters/memoizable_spec.rb +51 -33
- data/spec/flipper/adapters/memory_spec.rb +33 -5
- data/spec/flipper/adapters/operation_logger_spec.rb +38 -12
- data/spec/flipper/adapters/poll_spec.rb +41 -0
- data/spec/flipper/adapters/pstore_spec.rb +0 -2
- data/spec/flipper/adapters/read_only_spec.rb +32 -18
- data/spec/flipper/adapters/strict_spec.rb +64 -0
- data/spec/flipper/adapters/sync/feature_synchronizer_spec.rb +39 -1
- data/spec/flipper/adapters/sync/interval_synchronizer_spec.rb +4 -5
- data/spec/flipper/adapters/sync/synchronizer_spec.rb +87 -1
- data/spec/flipper/adapters/sync_spec.rb +17 -6
- data/spec/flipper/cli_spec.rb +217 -0
- data/spec/flipper/cloud/configuration_spec.rb +257 -0
- data/spec/flipper/cloud/dsl_spec.rb +90 -0
- data/spec/flipper/cloud/message_verifier_spec.rb +104 -0
- data/spec/flipper/cloud/middleware_spec.rb +307 -0
- data/spec/flipper/cloud/migrate_spec.rb +160 -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 +186 -0
- data/spec/flipper/configuration_spec.rb +37 -3
- data/spec/flipper/dsl_spec.rb +67 -80
- data/spec/flipper/engine_spec.rb +374 -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/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 +29 -0
- data/spec/flipper/feature_check_context_spec.rb +18 -20
- data/spec/flipper/feature_spec.rb +461 -48
- data/spec/flipper/gate_spec.rb +0 -2
- data/spec/flipper/gate_values_spec.rb +2 -34
- data/spec/flipper/gates/actor_spec.rb +0 -2
- data/spec/flipper/gates/boolean_spec.rb +1 -3
- data/spec/flipper/gates/expression_spec.rb +190 -0
- data/spec/flipper/gates/group_spec.rb +2 -5
- data/spec/flipper/gates/percentage_of_actors_spec.rb +61 -7
- data/spec/flipper/gates/percentage_of_time_spec.rb +2 -4
- data/spec/flipper/identifier_spec.rb +12 -0
- data/spec/flipper/instrumentation/log_subscriber_spec.rb +24 -7
- data/spec/flipper/instrumentation/statsd_subscriber_spec.rb +26 -3
- data/spec/flipper/instrumenters/memory_spec.rb +18 -1
- data/spec/flipper/instrumenters/noop_spec.rb +14 -8
- data/spec/flipper/middleware/memoizer_spec.rb +199 -62
- data/spec/flipper/middleware/setup_env_spec.rb +23 -5
- data/spec/flipper/model/active_record_spec.rb +72 -0
- data/spec/flipper/poller_spec.rb +390 -0
- data/spec/flipper/registry_spec.rb +0 -1
- 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 -7
- data/spec/flipper/types/actor_spec.rb +63 -47
- data/spec/flipper/types/boolean_spec.rb +0 -1
- data/spec/flipper/types/group_spec.rb +24 -3
- data/spec/flipper/types/percentage_of_actors_spec.rb +0 -1
- data/spec/flipper/types/percentage_of_time_spec.rb +0 -1
- data/spec/flipper/types/percentage_spec.rb +0 -1
- data/spec/{integration_spec.rb → flipper_integration_spec.rb} +301 -59
- data/spec/flipper_spec.rb +123 -29
- data/spec/{helper.rb → spec_helper.rb} +23 -21
- data/spec/support/actor_names.yml +1 -0
- data/spec/support/descriptions.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 +53 -6
- data/test/adapters/actor_limit_test.rb +20 -0
- data/test/test_helper.rb +2 -1
- data/test_rails/generators/flipper/setup_generator_test.rb +69 -0
- data/test_rails/generators/flipper/update_generator_test.rb +96 -0
- data/test_rails/helper.rb +31 -0
- data/test_rails/system/test_help_test.rb +52 -0
- metadata +200 -82
- data/.rubocop.yml +0 -54
- data/.rubocop_todo.yml +0 -199
- data/docs/Adapters.md +0 -124
- data/docs/Caveats.md +0 -4
- data/docs/Gates.md +0 -167
- data/docs/Instrumentation.md +0 -27
- data/docs/Optimization.md +0 -114
- data/docs/api/README.md +0 -849
- data/docs/http/README.md +0 -35
- data/docs/read-only/README.md +0 -21
- data/examples/example_setup.rb +0 -8
- data/test/helper.rb +0 -11
|
@@ -1,7 +1,44 @@
|
|
|
1
1
|
module Flipper
|
|
2
2
|
class Configuration
|
|
3
|
-
def initialize
|
|
4
|
-
@
|
|
3
|
+
def initialize(options = {})
|
|
4
|
+
@builder = AdapterBuilder.new { store Flipper::Adapters::Memory }
|
|
5
|
+
@default = -> { Flipper.new(@builder.to_adapter) }
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
# The default adapter to use.
|
|
9
|
+
#
|
|
10
|
+
# Pass a block to assign the adapter, and invoke without a block to
|
|
11
|
+
# return the configured adapter instance.
|
|
12
|
+
#
|
|
13
|
+
# Flipper.configure do |config|
|
|
14
|
+
# config.adapter # => instance of default Memory adapter
|
|
15
|
+
#
|
|
16
|
+
# # Configure it to use the ActiveRecord adapter
|
|
17
|
+
# config.adapter do
|
|
18
|
+
# require "flipper/adapters/active_record"
|
|
19
|
+
# Flipper::Adapters::ActiveRecord.new
|
|
20
|
+
# end
|
|
21
|
+
#
|
|
22
|
+
# config.adapter # => instance of ActiveRecord adapter
|
|
23
|
+
# end
|
|
24
|
+
#
|
|
25
|
+
def adapter(&block)
|
|
26
|
+
if block_given?
|
|
27
|
+
@builder.store(block)
|
|
28
|
+
else
|
|
29
|
+
@builder.to_adapter
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# An adapter to use to augment the primary storage adapter. See `AdapterBuilder#use`
|
|
34
|
+
if RUBY_VERSION >= '3.0'
|
|
35
|
+
def use(klass, *args, **kwargs, &block)
|
|
36
|
+
@builder.use(klass, *args, **kwargs, &block)
|
|
37
|
+
end
|
|
38
|
+
else
|
|
39
|
+
def use(klass, *args, &block)
|
|
40
|
+
@builder.use(klass, *args, &block)
|
|
41
|
+
end
|
|
5
42
|
end
|
|
6
43
|
|
|
7
44
|
# Controls the default instance for flipper. When used with a block it
|
|
@@ -9,15 +46,15 @@ module Flipper
|
|
|
9
46
|
# without a block, it performs a block invocation and returns the result.
|
|
10
47
|
#
|
|
11
48
|
# configuration = Flipper::Configuration.new
|
|
12
|
-
# configuration.default # =>
|
|
49
|
+
# configuration.default # => Flipper::DSL instance using Memory adapter
|
|
13
50
|
#
|
|
14
|
-
# # sets the default block to generate a new instance using
|
|
51
|
+
# # sets the default block to generate a new instance using ActiveRecord adapter
|
|
15
52
|
# configuration.default do
|
|
16
|
-
# require "flipper/adapters/
|
|
17
|
-
# Flipper.new(Flipper::Adapters::
|
|
53
|
+
# require "flipper/adapters/active_record"
|
|
54
|
+
# Flipper.new(Flipper::Adapters::ActiveRecord.new)
|
|
18
55
|
# end
|
|
19
56
|
#
|
|
20
|
-
# configuration.default # => Flipper::DSL instance using
|
|
57
|
+
# configuration.default # => Flipper::DSL instance using ActiveRecord adapter
|
|
21
58
|
#
|
|
22
59
|
# Returns result of default block invocation if called without block. If
|
|
23
60
|
# called with block, assigns the default block.
|
|
@@ -28,5 +65,15 @@ module Flipper
|
|
|
28
65
|
@default.call
|
|
29
66
|
end
|
|
30
67
|
end
|
|
68
|
+
|
|
69
|
+
def statsd
|
|
70
|
+
require 'flipper/instrumentation/statsd_subscriber'
|
|
71
|
+
Flipper::Instrumentation::StatsdSubscriber.client
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def statsd=(client)
|
|
75
|
+
require "flipper/instrumentation/statsd"
|
|
76
|
+
Flipper::Instrumentation::StatsdSubscriber.client = client
|
|
77
|
+
end
|
|
31
78
|
end
|
|
32
79
|
end
|
data/lib/flipper/dsl.rb
CHANGED
|
@@ -10,17 +10,19 @@ module Flipper
|
|
|
10
10
|
# Private: What is being used to instrument all the things.
|
|
11
11
|
attr_reader :instrumenter
|
|
12
12
|
|
|
13
|
-
def_delegators :@adapter, :memoize=, :memoizing
|
|
13
|
+
def_delegators :@adapter, :memoize=, :memoizing?, :import, :export, :adapter_stack
|
|
14
14
|
|
|
15
15
|
# Public: Returns a new instance of the DSL.
|
|
16
16
|
#
|
|
17
17
|
# adapter - The adapter that this DSL instance should use.
|
|
18
18
|
# options - The Hash of options.
|
|
19
19
|
# :instrumenter - What should be used to instrument all the things.
|
|
20
|
+
# :memoize - Should adapter be wrapped by memoize adapter or not.
|
|
20
21
|
def initialize(adapter, options = {})
|
|
21
22
|
@instrumenter = options.fetch(:instrumenter, Instrumenters::Noop)
|
|
22
|
-
|
|
23
|
-
|
|
23
|
+
memoize = options.fetch(:memoize, true)
|
|
24
|
+
adapter = Adapters::Memoizable.new(adapter) if memoize
|
|
25
|
+
@adapter = adapter
|
|
24
26
|
@memoized_features = {}
|
|
25
27
|
end
|
|
26
28
|
|
|
@@ -44,6 +46,25 @@ module Flipper
|
|
|
44
46
|
feature(name).enable(*args)
|
|
45
47
|
end
|
|
46
48
|
|
|
49
|
+
# Public: Enable a feature for an expression.
|
|
50
|
+
#
|
|
51
|
+
# name - The String or Symbol name of the feature.
|
|
52
|
+
# expression - a Flipper::Expression instance or a Hash.
|
|
53
|
+
#
|
|
54
|
+
# Returns result of Feature#enable.
|
|
55
|
+
def enable_expression(name, expression)
|
|
56
|
+
feature(name).enable_expression(expression)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Public: Add an expression to a feature.
|
|
60
|
+
#
|
|
61
|
+
# expression - an expression or Hash that can be converted to an expression.
|
|
62
|
+
#
|
|
63
|
+
# Returns result of enable.
|
|
64
|
+
def add_expression(name, expression)
|
|
65
|
+
feature(name).add_expression(expression)
|
|
66
|
+
end
|
|
67
|
+
|
|
47
68
|
# Public: Enable a feature for an actor.
|
|
48
69
|
#
|
|
49
70
|
# name - The String or Symbol name of the feature.
|
|
@@ -98,6 +119,24 @@ module Flipper
|
|
|
98
119
|
feature(name).disable(*args)
|
|
99
120
|
end
|
|
100
121
|
|
|
122
|
+
# Public: Disable expression for feature.
|
|
123
|
+
#
|
|
124
|
+
# name - The String or Symbol name of the feature.
|
|
125
|
+
#
|
|
126
|
+
# Returns result of Feature#disable.
|
|
127
|
+
def disable_expression(name)
|
|
128
|
+
feature(name).disable_expression
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# Public: Remove an expression from a feature.
|
|
132
|
+
#
|
|
133
|
+
# expression - an Expression or Hash that can be converted to an expression.
|
|
134
|
+
#
|
|
135
|
+
# Returns result of enable.
|
|
136
|
+
def remove_expression(name, expression)
|
|
137
|
+
feature(name).remove_expression(expression)
|
|
138
|
+
end
|
|
139
|
+
|
|
101
140
|
# Public: Disable a feature for an actor.
|
|
102
141
|
#
|
|
103
142
|
# name - The String or Symbol name of the feature.
|
|
@@ -208,22 +247,6 @@ module Flipper
|
|
|
208
247
|
# Returns an instance of Flipper::Feature.
|
|
209
248
|
alias_method :[], :feature
|
|
210
249
|
|
|
211
|
-
# Public: Shortcut for getting a boolean type instance.
|
|
212
|
-
#
|
|
213
|
-
# value - The true or false value for the boolean.
|
|
214
|
-
#
|
|
215
|
-
# Returns a Flipper::Types::Boolean instance.
|
|
216
|
-
def boolean(value = true)
|
|
217
|
-
Types::Boolean.new(value)
|
|
218
|
-
end
|
|
219
|
-
|
|
220
|
-
# Public: Even shorter shortcut for getting a boolean type instance.
|
|
221
|
-
#
|
|
222
|
-
# value - The true or false value for the boolean.
|
|
223
|
-
#
|
|
224
|
-
# Returns a Flipper::Types::Boolean instance.
|
|
225
|
-
alias_method :bool, :boolean
|
|
226
|
-
|
|
227
250
|
# Public: Access a flipper group by name.
|
|
228
251
|
#
|
|
229
252
|
# name - The String or Symbol name of the feature.
|
|
@@ -233,35 +256,14 @@ module Flipper
|
|
|
233
256
|
Flipper.group(name)
|
|
234
257
|
end
|
|
235
258
|
|
|
236
|
-
# Public:
|
|
237
|
-
#
|
|
238
|
-
# thing - The object that you would like to wrap.
|
|
239
|
-
#
|
|
240
|
-
# Returns an instance of Flipper::Types::Actor.
|
|
241
|
-
# Raises ArgumentError if thing does not respond to `flipper_id`.
|
|
242
|
-
def actor(thing)
|
|
243
|
-
Types::Actor.new(thing)
|
|
244
|
-
end
|
|
245
|
-
|
|
246
|
-
# Public: Shortcut for getting a percentage of time instance.
|
|
259
|
+
# Public: Gets the expression for the feature.
|
|
247
260
|
#
|
|
248
|
-
#
|
|
249
|
-
#
|
|
250
|
-
# Returns Flipper::Types::PercentageOfTime.
|
|
251
|
-
def time(number)
|
|
252
|
-
Types::PercentageOfTime.new(number)
|
|
253
|
-
end
|
|
254
|
-
alias_method :percentage_of_time, :time
|
|
255
|
-
|
|
256
|
-
# Public: Shortcut for getting a percentage of actors instance.
|
|
257
|
-
#
|
|
258
|
-
# number - The percentage of actors that should be enabled.
|
|
261
|
+
# name - The String or Symbol name of the feature.
|
|
259
262
|
#
|
|
260
|
-
# Returns Flipper::
|
|
261
|
-
def
|
|
262
|
-
|
|
263
|
+
# Returns an instance of Flipper::Expression.
|
|
264
|
+
def expression(name)
|
|
265
|
+
feature(name).expression
|
|
263
266
|
end
|
|
264
|
-
alias_method :percentage_of_actors, :actors
|
|
265
267
|
|
|
266
268
|
# Public: Returns a Set of the known features for this adapter.
|
|
267
269
|
#
|
|
@@ -270,8 +272,17 @@ module Flipper
|
|
|
270
272
|
adapter.features.map { |name| feature(name) }.to_set
|
|
271
273
|
end
|
|
272
274
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
+
# Public: Does this adapter support writes or not.
|
|
276
|
+
def read_only?
|
|
277
|
+
adapter.read_only?
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
# Cloud DSL method that does nothing for open source version.
|
|
281
|
+
def sync
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
# Cloud DSL method that does nothing for open source version.
|
|
285
|
+
def sync_secret
|
|
275
286
|
end
|
|
276
287
|
end
|
|
277
288
|
end
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
module Flipper
|
|
2
|
+
class Engine < Rails::Engine
|
|
3
|
+
def self.default_strict_value
|
|
4
|
+
value = ENV["FLIPPER_STRICT"]
|
|
5
|
+
if value.in?(["warn", "raise", "noop"])
|
|
6
|
+
value.to_sym
|
|
7
|
+
elsif value
|
|
8
|
+
Typecast.to_boolean(value) ? :raise : false
|
|
9
|
+
elsif Rails.env.production?
|
|
10
|
+
false
|
|
11
|
+
else
|
|
12
|
+
# Warn in development for now. Future versions may default to :raise in development and test
|
|
13
|
+
Rails.env.development? && :warn
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
paths["config/routes.rb"] = ["lib/flipper/cloud/routes.rb"]
|
|
18
|
+
|
|
19
|
+
config.before_configuration do
|
|
20
|
+
config.flipper = ActiveSupport::OrderedOptions.new.update(
|
|
21
|
+
env_key: ENV.fetch('FLIPPER_ENV_KEY', 'flipper'),
|
|
22
|
+
memoize: ENV.fetch('FLIPPER_MEMOIZE', 'true').casecmp('true').zero?,
|
|
23
|
+
preload: ENV.fetch('FLIPPER_PRELOAD', 'true').casecmp('true').zero?,
|
|
24
|
+
instrumenter: ENV.fetch('FLIPPER_INSTRUMENTER', 'ActiveSupport::Notifications').constantize,
|
|
25
|
+
log: ENV.fetch('FLIPPER_LOG', 'true').casecmp('true').zero?,
|
|
26
|
+
cloud_path: "_flipper",
|
|
27
|
+
strict: default_strict_value,
|
|
28
|
+
actor_limit: ENV["FLIPPER_ACTOR_LIMIT"]&.to_i || 100,
|
|
29
|
+
test_help: Flipper::Typecast.to_boolean(ENV["FLIPPER_TEST_HELP"] || Rails.env.test?),
|
|
30
|
+
)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
initializer "flipper.properties" do
|
|
34
|
+
ActiveSupport.on_load(:active_record) do
|
|
35
|
+
require "flipper/model/active_record"
|
|
36
|
+
ActiveRecord::Base.include Flipper::Model::ActiveRecord
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
initializer "flipper.default", before: :load_config_initializers do |app|
|
|
41
|
+
# Load cloud secrets from Rails credentials
|
|
42
|
+
ENV["FLIPPER_CLOUD_TOKEN"] ||= app.credentials.dig(:flipper, :cloud_token)
|
|
43
|
+
ENV["FLIPPER_CLOUD_SYNC_SECRET"] ||= app.credentials.dig(:flipper, :cloud_sync_secret)
|
|
44
|
+
|
|
45
|
+
require 'flipper/cloud' if cloud?
|
|
46
|
+
|
|
47
|
+
Flipper.configure do |config|
|
|
48
|
+
config.default do
|
|
49
|
+
if cloud?
|
|
50
|
+
Flipper::Cloud.new(
|
|
51
|
+
local_adapter: config.adapter,
|
|
52
|
+
instrumenter: app.config.flipper.instrumenter
|
|
53
|
+
)
|
|
54
|
+
else
|
|
55
|
+
Flipper.new(config.adapter, instrumenter: app.config.flipper.instrumenter)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
initializer "flipper.log", after: :load_config_initializers do |app|
|
|
62
|
+
flipper = app.config.flipper
|
|
63
|
+
|
|
64
|
+
if flipper.log && flipper.instrumenter == ActiveSupport::Notifications
|
|
65
|
+
require "flipper/instrumentation/log_subscriber"
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
initializer "flipper.adapters", after: :load_config_initializers do |app|
|
|
70
|
+
flipper = app.config.flipper
|
|
71
|
+
|
|
72
|
+
Flipper.configure do |config|
|
|
73
|
+
config.use Flipper::Adapters::Strict, flipper.strict if flipper.strict
|
|
74
|
+
config.use Flipper::Adapters::ActorLimit, flipper.actor_limit if flipper.actor_limit
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
initializer "flipper.memoizer", after: :load_config_initializers do |app|
|
|
79
|
+
flipper = app.config.flipper
|
|
80
|
+
|
|
81
|
+
if flipper.memoize
|
|
82
|
+
app.middleware.use Flipper::Middleware::Memoizer, {
|
|
83
|
+
env_key: flipper.env_key,
|
|
84
|
+
preload: flipper.preload,
|
|
85
|
+
if: flipper.memoize.respond_to?(:call) ? flipper.memoize : nil
|
|
86
|
+
}
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
initializer "flipper.test" do |app|
|
|
91
|
+
require "flipper/test_help" if app.config.flipper.test_help
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def cloud?
|
|
95
|
+
!!ENV["FLIPPER_CLOUD_TOKEN"]
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def self.deprecated_rails_version?
|
|
99
|
+
Gem::Version.new(Rails.version) < Gem::Version.new(Flipper::NEXT_REQUIRED_RAILS_VERSION)
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
data/lib/flipper/errors.rb
CHANGED
|
@@ -2,26 +2,16 @@ module Flipper
|
|
|
2
2
|
# Top level error that all other errors inherit from.
|
|
3
3
|
class Error < StandardError; end
|
|
4
4
|
|
|
5
|
-
# Raised when gate can not be found for
|
|
5
|
+
# Raised when gate can not be found for an actor.
|
|
6
6
|
class GateNotFound < Error
|
|
7
|
-
def initialize(
|
|
8
|
-
super "Could not find gate for #{
|
|
7
|
+
def initialize(actor)
|
|
8
|
+
super "Could not find gate for #{actor.inspect}"
|
|
9
9
|
end
|
|
10
10
|
end
|
|
11
11
|
|
|
12
12
|
# Raised when attempting to declare a group name that has already been used.
|
|
13
13
|
class DuplicateGroup < Error; end
|
|
14
14
|
|
|
15
|
-
# Raised when default instance not configured but there is an attempt to
|
|
16
|
-
# use it.
|
|
17
|
-
class DefaultNotSet < Flipper::Error
|
|
18
|
-
def initialize(message = nil)
|
|
19
|
-
default = "Default flipper instance not configured. See " \
|
|
20
|
-
"Flipper.configure for how to configure the default instance."
|
|
21
|
-
super(message || default)
|
|
22
|
-
end
|
|
23
|
-
end
|
|
24
|
-
|
|
25
15
|
# Raised when an invalid value is set to a configuration property
|
|
26
16
|
class InvalidConfigurationValue < Flipper::Error
|
|
27
17
|
def initialize(message = nil)
|
|
@@ -29,12 +19,4 @@ module Flipper
|
|
|
29
19
|
super(message || default)
|
|
30
20
|
end
|
|
31
21
|
end
|
|
32
|
-
|
|
33
|
-
# Raised when accessing a configuration property that has been deprecated
|
|
34
|
-
class ConfigurationDeprecated < Flipper::Error
|
|
35
|
-
def initialize(message = nil)
|
|
36
|
-
default = "The configuration property has been deprecated"
|
|
37
|
-
super(message || default)
|
|
38
|
-
end
|
|
39
|
-
end
|
|
40
22
|
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
module Flipper
|
|
2
|
+
class Export
|
|
3
|
+
attr_reader :contents, :format, :version
|
|
4
|
+
|
|
5
|
+
def initialize(contents:, format: :json, version: 1)
|
|
6
|
+
@contents = contents
|
|
7
|
+
@format = format
|
|
8
|
+
@version = version
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def features
|
|
12
|
+
raise NotImplementedError
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def adapter
|
|
16
|
+
@adapter ||= Flipper::Adapters::Memory.new(features)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def eql?(other)
|
|
20
|
+
self.class.eql?(other.class) && @contents == other.contents && @format == other.format && @version == other.version
|
|
21
|
+
end
|
|
22
|
+
alias_method :==, :eql?
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -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
|