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
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
module Flipper
|
|
2
|
+
module TestHelp
|
|
3
|
+
extend self
|
|
4
|
+
|
|
5
|
+
def flipper_configure
|
|
6
|
+
# Use a shared Memory adapter for all tests. This is instantiated outside of the
|
|
7
|
+
# `configure` block so the same instance is returned in new threads.
|
|
8
|
+
adapter = Flipper::Adapters::Memory.new
|
|
9
|
+
|
|
10
|
+
Flipper.configure do |config|
|
|
11
|
+
config.adapter { adapter }
|
|
12
|
+
config.default { Flipper.new(config.adapter) }
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def flipper_reset
|
|
17
|
+
# Remove all features
|
|
18
|
+
Flipper.features.each(&:remove) rescue nil
|
|
19
|
+
|
|
20
|
+
# Reset previous DSL instance
|
|
21
|
+
Flipper.instance = nil
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
if defined?(RSpec) && RSpec.respond_to?(:configure)
|
|
27
|
+
RSpec.configure do |config|
|
|
28
|
+
config.include Flipper::TestHelp
|
|
29
|
+
config.before(:suite) { Flipper::TestHelp.flipper_configure }
|
|
30
|
+
config.before(:each) { flipper_reset }
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
if defined?(ActiveSupport)
|
|
34
|
+
ActiveSupport.on_load(:active_support_test_case) do
|
|
35
|
+
Flipper::TestHelp.flipper_configure
|
|
36
|
+
|
|
37
|
+
ActiveSupport::TestCase.class_eval do
|
|
38
|
+
include Flipper::TestHelp
|
|
39
|
+
|
|
40
|
+
setup :flipper_reset
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
data/lib/flipper/typecast.rb
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
require 'set'
|
|
2
|
+
require "flipper/serializers/json"
|
|
3
|
+
require "flipper/serializers/gzip"
|
|
2
4
|
|
|
3
5
|
module Flipper
|
|
4
|
-
|
|
5
|
-
|
|
6
|
+
class Typecast
|
|
7
|
+
TRUTH_MAP = {
|
|
6
8
|
true => true,
|
|
7
9
|
1 => true,
|
|
8
10
|
'true' => true,
|
|
@@ -13,7 +15,7 @@ module Flipper
|
|
|
13
15
|
#
|
|
14
16
|
# Returns true or false.
|
|
15
17
|
def self.to_boolean(value)
|
|
16
|
-
!!
|
|
18
|
+
!!TRUTH_MAP[value]
|
|
17
19
|
end
|
|
18
20
|
|
|
19
21
|
# Internal: Convert value to an integer.
|
|
@@ -21,11 +23,9 @@ module Flipper
|
|
|
21
23
|
# Returns an Integer representation of the value.
|
|
22
24
|
# Raises ArgumentError if conversion is not possible.
|
|
23
25
|
def self.to_integer(value)
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
raise ArgumentError, "#{value.inspect} cannot be converted to an integer"
|
|
28
|
-
end
|
|
26
|
+
value.to_i
|
|
27
|
+
rescue NoMethodError
|
|
28
|
+
raise ArgumentError, "#{value.inspect} cannot be converted to an integer"
|
|
29
29
|
end
|
|
30
30
|
|
|
31
31
|
# Internal: Convert value to a float.
|
|
@@ -33,24 +33,30 @@ module Flipper
|
|
|
33
33
|
# Returns a Float representation of the value.
|
|
34
34
|
# Raises ArgumentError if conversion is not possible.
|
|
35
35
|
def self.to_float(value)
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
raise ArgumentError, "#{value.inspect} cannot be converted to a float"
|
|
40
|
-
end
|
|
36
|
+
value.to_f
|
|
37
|
+
rescue NoMethodError
|
|
38
|
+
raise ArgumentError, "#{value.inspect} cannot be converted to a float"
|
|
41
39
|
end
|
|
42
40
|
|
|
43
|
-
# Internal: Convert value to a
|
|
41
|
+
# Internal: Convert value to a number.
|
|
44
42
|
#
|
|
45
43
|
# Returns a Integer or Float representation of the value.
|
|
46
44
|
# Raises ArgumentError if conversion is not possible.
|
|
47
|
-
def self.
|
|
48
|
-
|
|
49
|
-
|
|
45
|
+
def self.to_number(value)
|
|
46
|
+
case value
|
|
47
|
+
when Numeric
|
|
48
|
+
value
|
|
49
|
+
when String
|
|
50
|
+
value.include?('.') ? to_float(value) : to_integer(value)
|
|
51
|
+
when NilClass
|
|
52
|
+
0
|
|
50
53
|
else
|
|
51
|
-
|
|
54
|
+
value.to_f
|
|
52
55
|
end
|
|
56
|
+
rescue NoMethodError
|
|
57
|
+
raise ArgumentError, "#{value.inspect} cannot be converted to a number"
|
|
53
58
|
end
|
|
59
|
+
singleton_class.send(:alias_method, :to_percentage, :to_number)
|
|
54
60
|
|
|
55
61
|
# Internal: Convert value to a set.
|
|
56
62
|
#
|
|
@@ -66,5 +72,40 @@ module Flipper
|
|
|
66
72
|
raise ArgumentError, "#{value.inspect} cannot be converted to a set"
|
|
67
73
|
end
|
|
68
74
|
end
|
|
75
|
+
|
|
76
|
+
def self.features_hash(source)
|
|
77
|
+
normalized_source = {}
|
|
78
|
+
(source || {}).each do |feature_key, gates|
|
|
79
|
+
normalized_source[feature_key] ||= {}
|
|
80
|
+
gates.each do |gate_key, value|
|
|
81
|
+
normalized_value = case value
|
|
82
|
+
when Array, Set
|
|
83
|
+
value.to_set
|
|
84
|
+
when Hash
|
|
85
|
+
value
|
|
86
|
+
else
|
|
87
|
+
value ? value.to_s : value
|
|
88
|
+
end
|
|
89
|
+
normalized_source[feature_key][gate_key.to_sym] = normalized_value
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
normalized_source
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def self.to_json(source)
|
|
96
|
+
Serializers::Json.serialize(source)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def self.from_json(source)
|
|
100
|
+
Serializers::Json.deserialize(source)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def self.to_gzip(source)
|
|
104
|
+
Serializers::Gzip.serialize(source)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def self.from_gzip(source)
|
|
108
|
+
Serializers::Gzip.deserialize(source)
|
|
109
|
+
end
|
|
69
110
|
end
|
|
70
111
|
end
|
data/lib/flipper/types/actor.rb
CHANGED
|
@@ -1,30 +1,36 @@
|
|
|
1
1
|
module Flipper
|
|
2
2
|
module Types
|
|
3
3
|
class Actor < Type
|
|
4
|
-
def self.wrappable?(
|
|
5
|
-
return false if
|
|
6
|
-
|
|
4
|
+
def self.wrappable?(actor)
|
|
5
|
+
return false if actor.nil?
|
|
6
|
+
actor.respond_to?(:flipper_id)
|
|
7
7
|
end
|
|
8
8
|
|
|
9
|
-
attr_reader :
|
|
9
|
+
attr_reader :actor
|
|
10
10
|
|
|
11
|
-
def initialize(
|
|
12
|
-
raise ArgumentError, '
|
|
11
|
+
def initialize(actor)
|
|
12
|
+
raise ArgumentError, 'actor cannot be nil' if actor.nil?
|
|
13
13
|
|
|
14
|
-
unless
|
|
15
|
-
raise ArgumentError, "#{
|
|
14
|
+
unless actor.respond_to?(:flipper_id)
|
|
15
|
+
raise ArgumentError, "#{actor.inspect} must respond to flipper_id, but does not"
|
|
16
16
|
end
|
|
17
17
|
|
|
18
|
-
@
|
|
19
|
-
@value =
|
|
18
|
+
@actor = actor
|
|
19
|
+
@value = actor.flipper_id.to_s
|
|
20
20
|
end
|
|
21
21
|
|
|
22
22
|
def respond_to?(*args)
|
|
23
|
-
super || @
|
|
23
|
+
super || @actor.respond_to?(*args)
|
|
24
24
|
end
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
if RUBY_VERSION >= '3.0'
|
|
27
|
+
def method_missing(name, *args, **kwargs, &block)
|
|
28
|
+
@actor.send name, *args, **kwargs, &block
|
|
29
|
+
end
|
|
30
|
+
else
|
|
31
|
+
def method_missing(name, *args, &block)
|
|
32
|
+
@actor.send name, *args, &block
|
|
33
|
+
end
|
|
28
34
|
end
|
|
29
35
|
end
|
|
30
36
|
end
|
data/lib/flipper/types/group.rb
CHANGED
|
@@ -14,20 +14,27 @@ module Flipper
|
|
|
14
14
|
|
|
15
15
|
if block_given?
|
|
16
16
|
@block = block
|
|
17
|
-
@single_argument = @block
|
|
17
|
+
@single_argument = call_with_no_context?(@block)
|
|
18
18
|
else
|
|
19
|
-
@block = ->(
|
|
19
|
+
@block = ->(actor, context) { false }
|
|
20
20
|
@single_argument = false
|
|
21
21
|
end
|
|
22
22
|
end
|
|
23
23
|
|
|
24
|
-
def match?(
|
|
24
|
+
def match?(actor, context)
|
|
25
25
|
if @single_argument
|
|
26
|
-
@block.call(
|
|
26
|
+
@block.call(actor)
|
|
27
27
|
else
|
|
28
|
-
@block.call(
|
|
28
|
+
@block.call(actor, context)
|
|
29
29
|
end
|
|
30
30
|
end
|
|
31
|
+
|
|
32
|
+
NO_PARAMS_IN_RUBY_3 = [[:req], [:rest]]
|
|
33
|
+
def call_with_no_context?(block)
|
|
34
|
+
return true if block.parameters == NO_PARAMS_IN_RUBY_3
|
|
35
|
+
|
|
36
|
+
block.arity.abs == 1
|
|
37
|
+
end
|
|
31
38
|
end
|
|
32
39
|
end
|
|
33
40
|
end
|
data/lib/flipper/version.rb
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
1
|
module Flipper
|
|
2
|
-
VERSION = '
|
|
2
|
+
VERSION = '1.4.0'.freeze
|
|
3
|
+
|
|
4
|
+
REQUIRED_RUBY_VERSION = '2.6'.freeze
|
|
5
|
+
NEXT_REQUIRED_RUBY_VERSION = '3.0'.freeze
|
|
6
|
+
|
|
7
|
+
REQUIRED_RAILS_VERSION = '5.2'.freeze
|
|
8
|
+
NEXT_REQUIRED_RAILS_VERSION = '6.1.0'.freeze
|
|
9
|
+
|
|
10
|
+
def self.deprecated_ruby_version?
|
|
11
|
+
Gem::Version.new(RUBY_VERSION) < Gem::Version.new(NEXT_REQUIRED_RUBY_VERSION)
|
|
12
|
+
end
|
|
3
13
|
end
|
data/lib/flipper.rb
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
require "forwardable"
|
|
2
2
|
|
|
3
3
|
module Flipper
|
|
4
|
-
extend self
|
|
4
|
+
extend self
|
|
5
5
|
extend Forwardable
|
|
6
6
|
|
|
7
7
|
# Private: The namespace for all instrumented events.
|
|
@@ -16,7 +16,7 @@ module Flipper
|
|
|
16
16
|
# Public: Configure flipper.
|
|
17
17
|
#
|
|
18
18
|
# Flipper.configure do |config|
|
|
19
|
-
# config.
|
|
19
|
+
# config.adapter { ... }
|
|
20
20
|
# end
|
|
21
21
|
#
|
|
22
22
|
# Yields Flipper::Configuration instance.
|
|
@@ -56,27 +56,76 @@ module Flipper
|
|
|
56
56
|
# Public: All the methods delegated to instance. These should match the
|
|
57
57
|
# interface of Flipper::DSL.
|
|
58
58
|
def_delegators :instance,
|
|
59
|
-
:enabled?, :enable, :disable,
|
|
60
|
-
:
|
|
59
|
+
:enabled?, :enable, :disable,
|
|
60
|
+
:enable_expression, :disable_expression,
|
|
61
|
+
:expression, :add_expression, :remove_expression,
|
|
62
|
+
:enable_actor, :disable_actor,
|
|
61
63
|
:enable_group, :disable_group,
|
|
62
64
|
:enable_percentage_of_actors, :disable_percentage_of_actors,
|
|
63
|
-
:actors, :percentage_of_actors,
|
|
64
65
|
:enable_percentage_of_time, :disable_percentage_of_time,
|
|
65
|
-
:time, :percentage_of_time,
|
|
66
66
|
:features, :feature, :[], :preload, :preload_all,
|
|
67
|
-
:adapter, :add, :exist?, :remove, :import,
|
|
68
|
-
:memoize=, :memoizing
|
|
67
|
+
:adapter, :adapter_stack, :add, :exist?, :remove, :import, :export,
|
|
68
|
+
:memoize=, :memoizing?, :read_only?,
|
|
69
|
+
:sync, :sync_secret # For Flipper::Cloud. Will error for OSS Flipper.
|
|
70
|
+
|
|
71
|
+
def any(*args)
|
|
72
|
+
Expression.build({ Any: args.flatten })
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def all(*args)
|
|
76
|
+
Expression.build({ All: args.flatten })
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def constant(value)
|
|
80
|
+
Expression.build(value)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def property(name)
|
|
84
|
+
Expression.build({ Property: name })
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def string(value)
|
|
88
|
+
Expression.build({ String: value })
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def number(value)
|
|
92
|
+
Expression.build({ Number: value })
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def boolean(value)
|
|
96
|
+
Expression.build({ Boolean: value })
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def random(max)
|
|
100
|
+
Expression.build({ Random: max })
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def now
|
|
104
|
+
Expression.build({ Now: [] })
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def time(value)
|
|
108
|
+
Expression.build({ Time: value })
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def feature_enabled(name)
|
|
112
|
+
Expression.build({ FeatureEnabled: name })
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def feature_disabled(name)
|
|
116
|
+
feature_enabled(name).eq(false)
|
|
117
|
+
end
|
|
69
118
|
|
|
70
119
|
# Public: Use this to register a group by name.
|
|
71
120
|
#
|
|
72
121
|
# name - The Symbol name of the group.
|
|
73
122
|
# block - The block that should be used to determine if the group matches a
|
|
74
|
-
# given
|
|
123
|
+
# given actor.
|
|
75
124
|
#
|
|
76
125
|
# Examples
|
|
77
126
|
#
|
|
78
|
-
# Flipper.register(:admins) { |
|
|
79
|
-
#
|
|
127
|
+
# Flipper.register(:admins) { |actor|
|
|
128
|
+
# actor.respond_to?(:admin?) && actor.admin?
|
|
80
129
|
# }
|
|
81
130
|
#
|
|
82
131
|
# Returns a Flipper::Group.
|
|
@@ -141,9 +190,13 @@ end
|
|
|
141
190
|
|
|
142
191
|
require 'flipper/actor'
|
|
143
192
|
require 'flipper/adapter'
|
|
193
|
+
require 'flipper/adapters/wrapper'
|
|
194
|
+
require 'flipper/adapters/actor_limit'
|
|
195
|
+
require 'flipper/adapters/instrumented'
|
|
144
196
|
require 'flipper/adapters/memoizable'
|
|
145
197
|
require 'flipper/adapters/memory'
|
|
146
|
-
require 'flipper/adapters/
|
|
198
|
+
require 'flipper/adapters/strict'
|
|
199
|
+
require 'flipper/adapter_builder'
|
|
147
200
|
require 'flipper/configuration'
|
|
148
201
|
require 'flipper/dsl'
|
|
149
202
|
require 'flipper/errors'
|
|
@@ -151,9 +204,12 @@ require 'flipper/feature'
|
|
|
151
204
|
require 'flipper/gate'
|
|
152
205
|
require 'flipper/instrumenters/memory'
|
|
153
206
|
require 'flipper/instrumenters/noop'
|
|
207
|
+
require 'flipper/identifier'
|
|
154
208
|
require 'flipper/middleware/memoizer'
|
|
155
209
|
require 'flipper/middleware/setup_env'
|
|
210
|
+
require 'flipper/poller'
|
|
156
211
|
require 'flipper/registry'
|
|
212
|
+
require 'flipper/expression'
|
|
157
213
|
require 'flipper/type'
|
|
158
214
|
require 'flipper/types/actor'
|
|
159
215
|
require 'flipper/types/boolean'
|
|
@@ -162,3 +218,6 @@ require 'flipper/types/percentage'
|
|
|
162
218
|
require 'flipper/types/percentage_of_actors'
|
|
163
219
|
require 'flipper/types/percentage_of_time'
|
|
164
220
|
require 'flipper/typecast'
|
|
221
|
+
require 'flipper/version'
|
|
222
|
+
|
|
223
|
+
require "flipper/engine" if defined?(Rails)
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
require 'rails/generators/active_record'
|
|
2
|
+
|
|
3
|
+
module Flipper
|
|
4
|
+
module Generators
|
|
5
|
+
class SetupGenerator < ::Rails::Generators::Base
|
|
6
|
+
desc 'Peform any necessary steps to install Flipper'
|
|
7
|
+
source_paths << File.expand_path('templates', __dir__)
|
|
8
|
+
|
|
9
|
+
class_option :token, type: :string, default: nil, aliases: '-t',
|
|
10
|
+
desc: "Your personal environment token for Flipper Cloud"
|
|
11
|
+
|
|
12
|
+
def generate_initializer
|
|
13
|
+
template 'initializer.rb', 'config/initializers/flipper.rb'
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def generate_active_record
|
|
17
|
+
invoke 'flipper:active_record' if defined?(Flipper::Adapters::ActiveRecord)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def configure_cloud_token
|
|
21
|
+
return unless options[:token]
|
|
22
|
+
|
|
23
|
+
configure_with_dotenv || configure_with_credentials
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
def configure_with_dotenv
|
|
29
|
+
['.env.development', '.env.local', '.env'].detect do |file|
|
|
30
|
+
next unless exists?(file)
|
|
31
|
+
append_to_file file, "\nFLIPPER_CLOUD_TOKEN=#{options[:token]}\n"
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def configure_with_credentials
|
|
36
|
+
return unless exists?("config/credentials.yml.enc") && (ENV["RAILS_MASTER_KEY"] || exists?("config/master.key"))
|
|
37
|
+
|
|
38
|
+
content = "flipper:\n cloud_token: #{options[:token]}\n"
|
|
39
|
+
action InjectIntoEncryptedFile.new(self, Rails.application.credentials, content, after: /\z/)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Check if a file exists in the destination root
|
|
43
|
+
def exists?(path)
|
|
44
|
+
File.exist?(File.expand_path(path, destination_root))
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Action to inject content into ActiveSupport::EncryptedFile
|
|
48
|
+
class InjectIntoEncryptedFile < Thor::Actions::InjectIntoFile
|
|
49
|
+
def initialize(base, encrypted_file, data, config)
|
|
50
|
+
@encrypted_file = encrypted_file
|
|
51
|
+
super(base, encrypted_file.content_path, data, config)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def content
|
|
55
|
+
@content ||= @encrypted_file.read
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def replace!(regexp, string, force)
|
|
59
|
+
if force || !replacement_present?
|
|
60
|
+
success = content.gsub!(regexp, string)
|
|
61
|
+
@encrypted_file.write content unless pretend?
|
|
62
|
+
success
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
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 adapter 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,22 @@
|
|
|
1
|
+
class CreateFlipperTables < ActiveRecord::Migration<%= migration_version %>
|
|
2
|
+
def up
|
|
3
|
+
create_table :flipper_features do |t|
|
|
4
|
+
t.string :key, null: false
|
|
5
|
+
t.timestamps null: false
|
|
6
|
+
end
|
|
7
|
+
add_index :flipper_features, :key, unique: true
|
|
8
|
+
|
|
9
|
+
create_table :flipper_gates do |t|
|
|
10
|
+
t.string :feature_key, null: false
|
|
11
|
+
t.string :key, null: false
|
|
12
|
+
t.string :value
|
|
13
|
+
t.timestamps null: false
|
|
14
|
+
end
|
|
15
|
+
add_index :flipper_gates, [:feature_key, :key, :value], unique: true
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def down
|
|
19
|
+
drop_table :flipper_gates
|
|
20
|
+
drop_table :flipper_features
|
|
21
|
+
end
|
|
22
|
+
end
|
data/lib/generators/flipper/templates/update/migrations/02_change_flipper_gates_value_to_text.rb.erb
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class ChangeFlipperGatesValueToText < ActiveRecord::Migration<%= migration_version %>
|
|
4
|
+
def up
|
|
5
|
+
# Ensure this incremental update migration is idempotent
|
|
6
|
+
return unless connection.column_exists? :flipper_gates, :value, :string
|
|
7
|
+
|
|
8
|
+
if index_exists? :flipper_gates, [:feature_key, :key, :value]
|
|
9
|
+
remove_index :flipper_gates, [:feature_key, :key, :value]
|
|
10
|
+
end
|
|
11
|
+
change_column :flipper_gates, :value, :text
|
|
12
|
+
add_index :flipper_gates, [:feature_key, :key, :value], unique: true, length: { value: 255 }
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def down
|
|
16
|
+
change_column :flipper_gates, :value, :string
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'rails/generators'
|
|
4
|
+
require 'rails/generators/active_record'
|
|
5
|
+
|
|
6
|
+
module Flipper
|
|
7
|
+
module Generators
|
|
8
|
+
#
|
|
9
|
+
# Rails generator used for updating Flipper in a Rails application.
|
|
10
|
+
# Run it with +bin/rails g flipper:update+ in your console.
|
|
11
|
+
#
|
|
12
|
+
class UpdateGenerator < Rails::Generators::Base
|
|
13
|
+
include ActiveRecord::Generators::Migration
|
|
14
|
+
|
|
15
|
+
TEMPLATES = File.join(File.dirname(__FILE__), 'templates/update')
|
|
16
|
+
source_paths << TEMPLATES
|
|
17
|
+
|
|
18
|
+
# Generates incremental migration files unless they already exist.
|
|
19
|
+
# All migrations should be idempotent e.g. +add_index+ is guarded with +if_index_exists?+
|
|
20
|
+
def update_migration_files
|
|
21
|
+
migration_templates = Dir.children(File.join(TEMPLATES, 'migrations')).sort
|
|
22
|
+
migration_templates.each do |template_file|
|
|
23
|
+
destination_file = template_file.match(/^\d*_(.*\.rb)/)[1] # 01_create_flipper_tables.rb.erb => create_flipper_tables.rb
|
|
24
|
+
migration_template "migrations/#{template_file}", File.join(db_migrate_path, destination_file), skip: true
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def migration_version
|
|
31
|
+
"[#{ActiveRecord::VERSION::STRING.to_f}]"
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
data/package-lock.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "flipper",
|
|
3
|
+
"lockfileVersion": 3,
|
|
4
|
+
"requires": true,
|
|
5
|
+
"packages": {
|
|
6
|
+
"": {
|
|
7
|
+
"hasInstallScript": true,
|
|
8
|
+
"dependencies": {
|
|
9
|
+
"@popperjs/core": "^2.11.8",
|
|
10
|
+
"bootstrap": "^5.3.3"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"node_modules/@popperjs/core": {
|
|
14
|
+
"version": "2.11.8",
|
|
15
|
+
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
|
|
16
|
+
"integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==",
|
|
17
|
+
"funding": {
|
|
18
|
+
"type": "opencollective",
|
|
19
|
+
"url": "https://opencollective.com/popperjs"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"node_modules/bootstrap": {
|
|
23
|
+
"version": "5.3.3",
|
|
24
|
+
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.3.tgz",
|
|
25
|
+
"integrity": "sha512-8HLCdWgyoMguSO9o+aH+iuZ+aht+mzW0u3HIMzVu7Srrpv7EBBxTnrFlSCskwdY1+EOFQSm7uMJhNQHkdPcmjg==",
|
|
26
|
+
"funding": [
|
|
27
|
+
{
|
|
28
|
+
"type": "github",
|
|
29
|
+
"url": "https://github.com/sponsors/twbs"
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"type": "opencollective",
|
|
33
|
+
"url": "https://opencollective.com/bootstrap"
|
|
34
|
+
}
|
|
35
|
+
],
|
|
36
|
+
"peerDependencies": {
|
|
37
|
+
"@popperjs/core": "^2.11.8"
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
data/package.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Placeholder for config/environment.rb
|