flipper 0.28.0 → 1.3.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/FUNDING.yml +1 -0
- data/.github/workflows/ci.yml +50 -7
- data/.github/workflows/examples.yml +52 -10
- data/CLAUDE.md +74 -0
- data/Changelog.md +1 -526
- data/Gemfile +17 -8
- data/README.md +31 -27
- data/Rakefile +2 -2
- data/benchmark/typecast_ips.rb +8 -0
- data/docs/images/banner.jpg +0 -0
- data/docs/images/flipper_cloud.png +0 -0
- 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 +0 -14
- data/examples/expressions.rb +213 -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 +8 -4
- data/lib/flipper/actor.rb +6 -3
- data/lib/flipper/adapter.rb +10 -0
- 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 +0 -4
- data/lib/flipper/adapters/http/client.rb +40 -12
- data/lib/flipper/adapters/http/error.rb +2 -2
- data/lib/flipper/adapters/http.rb +19 -14
- data/lib/flipper/adapters/instrumented.rb +0 -4
- data/lib/flipper/adapters/memoizable.rb +14 -19
- data/lib/flipper/adapters/memory.rb +37 -19
- data/lib/flipper/adapters/operation_logger.rb +18 -92
- data/lib/flipper/adapters/poll.rb +16 -3
- 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 +266 -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 +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 +53 -0
- data/lib/flipper/configuration.rb +25 -4
- data/lib/flipper/dsl.rb +47 -42
- data/lib/flipper/engine.rb +102 -0
- data/lib/flipper/export.rb +0 -2
- data/lib/flipper/exporters/json/export.rb +1 -1
- data/lib/flipper/exporters/json/v1.rb +1 -1
- 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/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 +63 -1
- data/lib/flipper/gate.rb +2 -1
- data/lib/flipper/gate_values.rb +5 -2
- data/lib/flipper/gates/expression.rb +75 -0
- data/lib/flipper/instrumentation/log_subscriber.rb +28 -5
- data/lib/flipper/instrumentation/statsd.rb +4 -2
- data/lib/flipper/instrumentation/statsd_subscriber.rb +2 -4
- data/lib/flipper/instrumentation/subscriber.rb +0 -4
- data/lib/flipper/metadata.rb +8 -1
- data/lib/flipper/middleware/memoizer.rb +30 -14
- data/lib/flipper/model/active_record.rb +23 -0
- data/lib/flipper/poller.rb +10 -9
- data/lib/flipper/serializers/gzip.rb +22 -0
- data/lib/flipper/serializers/json.rb +17 -0
- data/lib/flipper/spec/shared_adapter_specs.rb +82 -63
- data/lib/flipper/test/shared_adapter_test.rb +77 -58
- data/lib/flipper/test_help.rb +43 -0
- data/lib/flipper/typecast.rb +37 -9
- data/lib/flipper/types/percentage.rb +1 -1
- data/lib/flipper/version.rb +11 -1
- data/lib/flipper.rb +44 -7
- 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/flipper/adapter_builder_spec.rb +72 -0
- data/spec/flipper/adapter_spec.rb +1 -0
- data/spec/flipper/adapters/actor_limit_spec.rb +20 -0
- data/spec/flipper/adapters/dual_write_spec.rb +2 -2
- data/spec/flipper/adapters/http/client_spec.rb +61 -0
- data/spec/flipper/adapters/http_spec.rb +135 -74
- data/spec/flipper/adapters/instrumented_spec.rb +1 -1
- data/spec/flipper/adapters/memoizable_spec.rb +21 -21
- data/spec/flipper/adapters/memory_spec.rb +11 -2
- data/spec/flipper/adapters/operation_logger_spec.rb +2 -2
- data/spec/flipper/adapters/poll_spec.rb +41 -0
- 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 +166 -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 +186 -0
- data/spec/flipper/configuration_spec.rb +17 -0
- data/spec/flipper/dsl_spec.rb +34 -73
- data/spec/flipper/engine_spec.rb +374 -0
- data/spec/flipper/exporters/json/v1_spec.rb +3 -3
- 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_spec.rb +380 -10
- data/spec/flipper/gate_values_spec.rb +2 -2
- data/spec/flipper/gates/expression_spec.rb +108 -0
- data/spec/flipper/identifier_spec.rb +4 -5
- data/spec/flipper/instrumentation/log_subscriber_spec.rb +10 -2
- data/spec/flipper/instrumentation/statsd_subscriber_spec.rb +16 -2
- data/spec/flipper/middleware/memoizer_spec.rb +79 -10
- data/spec/flipper/model/active_record_spec.rb +72 -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 +43 -7
- data/spec/flipper/types/actor_spec.rb +18 -1
- data/spec/flipper_integration_spec.rb +114 -16
- data/spec/flipper_spec.rb +87 -29
- data/spec/spec_helper.rb +17 -17
- 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/spec_helpers.rb +34 -8
- data/test/adapters/actor_limit_test.rb +20 -0
- 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 +22 -2
- data/test_rails/system/test_help_test.rb +52 -0
- metadata +179 -19
- data/.tool-versions +0 -1
- data/lib/flipper/railtie.rb +0 -47
- data/spec/flipper/railtie_spec.rb +0 -109
data/lib/flipper/feature.rb
CHANGED
|
@@ -100,7 +100,14 @@ module Flipper
|
|
|
100
100
|
#
|
|
101
101
|
# Returns true if enabled, false if not.
|
|
102
102
|
def enabled?(*actors)
|
|
103
|
-
actors = actors.
|
|
103
|
+
actors = Array(actors).
|
|
104
|
+
# Avoids to_ary warning that happens when passing DelegateClass of an
|
|
105
|
+
# ActiveRecord object and using flatten here. This is tested in
|
|
106
|
+
# spec/flipper/model/active_record_spec.rb.
|
|
107
|
+
flat_map { |actor| actor.is_a?(Array) ? actor : [actor] }.
|
|
108
|
+
# Allows null object pattern. See PR for more. https://github.com/flippercloud/flipper/pull/887
|
|
109
|
+
reject(&:nil?).
|
|
110
|
+
map { |actor| Types::Actor.wrap(actor) }
|
|
104
111
|
actors = nil if actors.empty?
|
|
105
112
|
|
|
106
113
|
# thing is left for backwards compatibility
|
|
@@ -120,6 +127,28 @@ module Flipper
|
|
|
120
127
|
end
|
|
121
128
|
end
|
|
122
129
|
|
|
130
|
+
# Public: Enables an expression_to_add for a feature.
|
|
131
|
+
#
|
|
132
|
+
# expression - an Expression or Hash that can be converted to an expression.
|
|
133
|
+
#
|
|
134
|
+
# Returns result of enable.
|
|
135
|
+
def enable_expression(expression)
|
|
136
|
+
enable Expression.build(expression)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# Public: Add an expression for a feature.
|
|
140
|
+
#
|
|
141
|
+
# expression_to_add - an expression or Hash that can be converted to an expression.
|
|
142
|
+
#
|
|
143
|
+
# Returns result of enable.
|
|
144
|
+
def add_expression(expression_to_add)
|
|
145
|
+
if (current_expression = expression)
|
|
146
|
+
enable current_expression.add(expression_to_add)
|
|
147
|
+
else
|
|
148
|
+
enable expression_to_add
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
123
152
|
# Public: Enables a feature for an actor.
|
|
124
153
|
#
|
|
125
154
|
# actor - a Flipper::Types::Actor instance or an object that responds
|
|
@@ -160,6 +189,27 @@ module Flipper
|
|
|
160
189
|
enable Types::PercentageOfActors.wrap(percentage)
|
|
161
190
|
end
|
|
162
191
|
|
|
192
|
+
# Public: Disables an expression for a feature.
|
|
193
|
+
#
|
|
194
|
+
# expression - an expression or Hash that can be converted to an expression.
|
|
195
|
+
#
|
|
196
|
+
# Returns result of disable.
|
|
197
|
+
def disable_expression
|
|
198
|
+
disable Flipper.all # just need an expression to clear
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
# Public: Remove an expression from a feature. Does nothing if no expression is
|
|
202
|
+
# currently enabled.
|
|
203
|
+
#
|
|
204
|
+
# expression - an Expression or Hash that can be converted to an expression.
|
|
205
|
+
#
|
|
206
|
+
# Returns result of enable or nil (if no expression enabled).
|
|
207
|
+
def remove_expression(expression_to_remove)
|
|
208
|
+
if (current_expression = expression)
|
|
209
|
+
enable current_expression.remove(expression_to_remove)
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
|
|
163
213
|
# Public: Disables a feature for an actor.
|
|
164
214
|
#
|
|
165
215
|
# actor - a Flipper::Types::Actor instance or an object that responds
|
|
@@ -252,6 +302,10 @@ module Flipper
|
|
|
252
302
|
Flipper.groups - enabled_groups
|
|
253
303
|
end
|
|
254
304
|
|
|
305
|
+
def expression
|
|
306
|
+
Flipper::Expression.build(expression_value) if expression_value
|
|
307
|
+
end
|
|
308
|
+
|
|
255
309
|
# Public: Get the adapter value for the groups gate.
|
|
256
310
|
#
|
|
257
311
|
# Returns Set of String group names.
|
|
@@ -259,6 +313,13 @@ module Flipper
|
|
|
259
313
|
gate_values.groups
|
|
260
314
|
end
|
|
261
315
|
|
|
316
|
+
# Public: Get the adapter value for the expression gate.
|
|
317
|
+
#
|
|
318
|
+
# Returns expression.
|
|
319
|
+
def expression_value
|
|
320
|
+
gate_values.expression
|
|
321
|
+
end
|
|
322
|
+
|
|
262
323
|
# Public: Get the adapter value for the actors gate.
|
|
263
324
|
#
|
|
264
325
|
# Returns Set of String flipper_id's.
|
|
@@ -347,6 +408,7 @@ module Flipper
|
|
|
347
408
|
def gates_hash
|
|
348
409
|
@gates_hash ||= {
|
|
349
410
|
boolean: Gates::Boolean.new,
|
|
411
|
+
expression: Gates::Expression.new,
|
|
350
412
|
actor: Gates::Actor.new,
|
|
351
413
|
percentage_of_actors: Gates::PercentageOfActors.new,
|
|
352
414
|
percentage_of_time: Gates::PercentageOfTime.new,
|
data/lib/flipper/gate.rb
CHANGED
|
@@ -26,7 +26,7 @@ module Flipper
|
|
|
26
26
|
# in subclass.
|
|
27
27
|
#
|
|
28
28
|
# Returns true if gate open for any actor, false if not.
|
|
29
|
-
def open?(
|
|
29
|
+
def open?(context)
|
|
30
30
|
false
|
|
31
31
|
end
|
|
32
32
|
|
|
@@ -60,3 +60,4 @@ require 'flipper/gates/boolean'
|
|
|
60
60
|
require 'flipper/gates/group'
|
|
61
61
|
require 'flipper/gates/percentage_of_actors'
|
|
62
62
|
require 'flipper/gates/percentage_of_time'
|
|
63
|
+
require 'flipper/gates/expression'
|
data/lib/flipper/gate_values.rb
CHANGED
|
@@ -6,6 +6,7 @@ module Flipper
|
|
|
6
6
|
attr_reader :boolean
|
|
7
7
|
attr_reader :actors
|
|
8
8
|
attr_reader :groups
|
|
9
|
+
attr_reader :expression
|
|
9
10
|
attr_reader :percentage_of_actors
|
|
10
11
|
attr_reader :percentage_of_time
|
|
11
12
|
|
|
@@ -13,8 +14,9 @@ module Flipper
|
|
|
13
14
|
@boolean = Typecast.to_boolean(adapter_values[:boolean])
|
|
14
15
|
@actors = Typecast.to_set(adapter_values[:actors])
|
|
15
16
|
@groups = Typecast.to_set(adapter_values[:groups])
|
|
16
|
-
@
|
|
17
|
-
@
|
|
17
|
+
@expression = adapter_values[:expression]
|
|
18
|
+
@percentage_of_actors = Typecast.to_number(adapter_values[:percentage_of_actors])
|
|
19
|
+
@percentage_of_time = Typecast.to_number(adapter_values[:percentage_of_time])
|
|
18
20
|
end
|
|
19
21
|
|
|
20
22
|
def eql?(other)
|
|
@@ -22,6 +24,7 @@ module Flipper
|
|
|
22
24
|
boolean == other.boolean &&
|
|
23
25
|
actors == other.actors &&
|
|
24
26
|
groups == other.groups &&
|
|
27
|
+
expression == other.expression &&
|
|
25
28
|
percentage_of_actors == other.percentage_of_actors &&
|
|
26
29
|
percentage_of_time == other.percentage_of_time
|
|
27
30
|
end
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
require "flipper/expression"
|
|
2
|
+
|
|
3
|
+
module Flipper
|
|
4
|
+
module Gates
|
|
5
|
+
class Expression < Gate
|
|
6
|
+
# Internal: The name of the gate. Used for instrumentation, etc.
|
|
7
|
+
def name
|
|
8
|
+
:expression
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# Internal: Name converted to value safe for adapter.
|
|
12
|
+
def key
|
|
13
|
+
:expression
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def data_type
|
|
17
|
+
:json
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def enabled?(value)
|
|
21
|
+
!value.nil? && !value.empty?
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Internal: Checks if the gate is open for a thing.
|
|
25
|
+
#
|
|
26
|
+
# Returns true if gate open for thing, false if not.
|
|
27
|
+
def open?(context)
|
|
28
|
+
data = context.values.expression
|
|
29
|
+
return false if data.nil? || data.empty?
|
|
30
|
+
expression = Flipper::Expression.build(data)
|
|
31
|
+
|
|
32
|
+
if context.actors.nil? || context.actors.empty?
|
|
33
|
+
!!expression.evaluate(feature_name: context.feature_name, properties: DEFAULT_PROPERTIES)
|
|
34
|
+
else
|
|
35
|
+
context.actors.any? do |actor|
|
|
36
|
+
!!expression.evaluate(feature_name: context.feature_name, properties: properties(actor))
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def protects?(thing)
|
|
42
|
+
thing.is_a?(Flipper::Expression) || thing.is_a?(Hash)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def wrap(thing)
|
|
46
|
+
Flipper::Expression.build(thing)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
private
|
|
50
|
+
|
|
51
|
+
# Internal
|
|
52
|
+
DEFAULT_PROPERTIES = {}.freeze
|
|
53
|
+
|
|
54
|
+
def properties(actor)
|
|
55
|
+
return DEFAULT_PROPERTIES if actor.nil?
|
|
56
|
+
|
|
57
|
+
properties = {}
|
|
58
|
+
|
|
59
|
+
if actor.respond_to?(:flipper_properties)
|
|
60
|
+
properties.update(actor.flipper_properties)
|
|
61
|
+
else
|
|
62
|
+
warn "#{actor.inspect} does not respond to `flipper_properties` but should."
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
properties.transform_keys!(&:to_s)
|
|
66
|
+
|
|
67
|
+
if actor.respond_to?(:flipper_id)
|
|
68
|
+
properties["flipper_id".freeze] = actor.flipper_id
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
properties
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
require 'securerandom'
|
|
2
|
+
require 'active_support/gem_version'
|
|
2
3
|
require 'active_support/notifications'
|
|
3
4
|
require 'active_support/log_subscriber'
|
|
4
5
|
|
|
@@ -32,7 +33,7 @@ module Flipper
|
|
|
32
33
|
details += " gate_name=#{gate_name}" unless gate_name.nil?
|
|
33
34
|
|
|
34
35
|
name = '%s (%.1fms)' % [description, event.duration]
|
|
35
|
-
debug " #{
|
|
36
|
+
debug " #{color_name(name)} [ #{details} ]"
|
|
36
37
|
end
|
|
37
38
|
|
|
38
39
|
# Logs an adapter operation. If operation is for a feature, then that
|
|
@@ -52,11 +53,10 @@ module Flipper
|
|
|
52
53
|
|
|
53
54
|
feature_name = event.payload[:feature_name]
|
|
54
55
|
adapter_name = event.payload[:adapter_name]
|
|
55
|
-
gate_name = event.payload[:gate_name]
|
|
56
56
|
operation = event.payload[:operation]
|
|
57
57
|
result = event.payload[:result]
|
|
58
58
|
|
|
59
|
-
description = 'Flipper '
|
|
59
|
+
description = String.new('Flipper ')
|
|
60
60
|
description << "feature(#{feature_name}) " unless feature_name.nil?
|
|
61
61
|
description << "adapter(#{adapter_name}) "
|
|
62
62
|
description << "#{operation} "
|
|
@@ -64,14 +64,37 @@ module Flipper
|
|
|
64
64
|
details = "result=#{result.inspect}"
|
|
65
65
|
|
|
66
66
|
name = '%s (%.1fms)' % [description, event.duration]
|
|
67
|
-
debug " #{
|
|
67
|
+
debug " #{color_name(name)} [ #{details} ]"
|
|
68
68
|
end
|
|
69
69
|
|
|
70
70
|
def logger
|
|
71
71
|
self.class.logger
|
|
72
72
|
end
|
|
73
|
+
|
|
74
|
+
def self.attach
|
|
75
|
+
attach_to InstrumentationNamespace
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def self.detach
|
|
79
|
+
# Rails 5.2 doesn't support this, that's fine
|
|
80
|
+
detach_from InstrumentationNamespace if respond_to?(:detach_from)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
private
|
|
84
|
+
|
|
85
|
+
# Rails 7.1 changed the signature of this function.
|
|
86
|
+
COLOR_OPTIONS = if Gem::Requirement.new(">=7.1").satisfied_by?(ActiveSupport.gem_version)
|
|
87
|
+
{ bold: true }.freeze
|
|
88
|
+
else
|
|
89
|
+
true
|
|
90
|
+
end
|
|
91
|
+
private_constant :COLOR_OPTIONS
|
|
92
|
+
|
|
93
|
+
def color_name(name)
|
|
94
|
+
color(name, CYAN, COLOR_OPTIONS)
|
|
95
|
+
end
|
|
73
96
|
end
|
|
74
97
|
end
|
|
75
98
|
|
|
76
|
-
Instrumentation::LogSubscriber.
|
|
99
|
+
Instrumentation::LogSubscriber.attach
|
|
77
100
|
end
|
|
@@ -2,5 +2,7 @@ require 'securerandom'
|
|
|
2
2
|
require 'active_support/notifications'
|
|
3
3
|
require 'flipper/instrumentation/statsd_subscriber'
|
|
4
4
|
|
|
5
|
-
ActiveSupport::Notifications.subscribe
|
|
6
|
-
|
|
5
|
+
ActiveSupport::Notifications.subscribe(
|
|
6
|
+
/\.flipper$/,
|
|
7
|
+
Flipper::Instrumentation::StatsdSubscriber
|
|
8
|
+
)
|
|
@@ -12,13 +12,11 @@ module Flipper
|
|
|
12
12
|
end
|
|
13
13
|
|
|
14
14
|
def update_timer(metric)
|
|
15
|
-
|
|
16
|
-
self.class.client.timing metric, (@duration * 1_000).round
|
|
17
|
-
end
|
|
15
|
+
self.class.client&.timing metric, (@duration * 1_000).round
|
|
18
16
|
end
|
|
19
17
|
|
|
20
18
|
def update_counter(metric)
|
|
21
|
-
self.class.client
|
|
19
|
+
self.class.client&.increment metric
|
|
22
20
|
end
|
|
23
21
|
end
|
|
24
22
|
end
|
|
@@ -42,7 +42,6 @@ module Flipper
|
|
|
42
42
|
# Private
|
|
43
43
|
def update_feature_operation_metrics
|
|
44
44
|
feature_name = @payload[:feature_name]
|
|
45
|
-
gate_name = @payload[:gate_name]
|
|
46
45
|
operation = strip_trailing_question_mark(@payload[:operation])
|
|
47
46
|
result = @payload[:result]
|
|
48
47
|
|
|
@@ -64,9 +63,6 @@ module Flipper
|
|
|
64
63
|
def update_adapter_operation_metrics
|
|
65
64
|
adapter_name = @payload[:adapter_name]
|
|
66
65
|
operation = @payload[:operation]
|
|
67
|
-
result = @payload[:result]
|
|
68
|
-
value = @payload[:value]
|
|
69
|
-
key = @payload[:key]
|
|
70
66
|
|
|
71
67
|
update_timer "flipper.adapter.#{adapter_name}.#{operation}"
|
|
72
68
|
end
|
data/lib/flipper/metadata.rb
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
|
+
require_relative './version'
|
|
2
|
+
|
|
1
3
|
module Flipper
|
|
2
4
|
METADATA = {
|
|
3
|
-
|
|
5
|
+
"documentation_uri" => "https://www.flippercloud.io/docs",
|
|
6
|
+
"homepage_uri" => "https://www.flippercloud.io",
|
|
7
|
+
"source_code_uri" => "https://github.com/flippercloud/flipper",
|
|
8
|
+
"bug_tracker_uri" => "https://github.com/flippercloud/flipper/issues",
|
|
9
|
+
"changelog_uri" => "https://github.com/flippercloud/flipper/releases/tag/v#{Flipper::VERSION}",
|
|
10
|
+
"funding_uri" => "https://github.com/sponsors/flippercloud",
|
|
4
11
|
}.freeze
|
|
5
12
|
end
|
|
@@ -20,6 +20,14 @@ module Flipper
|
|
|
20
20
|
# # using with preload specific features
|
|
21
21
|
# use Flipper::Middleware::Memoizer, preload: [:stats, :search, :some_feature]
|
|
22
22
|
#
|
|
23
|
+
# # using with preload block that returns true/false
|
|
24
|
+
# use Flipper::Middleware::Memoizer, preload: ->(request) { !request.path.start_with?('/assets') }
|
|
25
|
+
#
|
|
26
|
+
# # using with preload block that returns specific features
|
|
27
|
+
# use Flipper::Middleware::Memoizer, preload: ->(request) {
|
|
28
|
+
# request.path.starts_with?('/admin') ? [:stats, :search] : false
|
|
29
|
+
# }
|
|
30
|
+
#
|
|
23
31
|
def initialize(app, opts = {})
|
|
24
32
|
if opts.is_a?(Flipper::DSL) || opts.is_a?(Proc)
|
|
25
33
|
raise 'Flipper::Middleware::Memoizer no longer initializes with a flipper instance or block. Read more at: https://git.io/vSo31.'
|
|
@@ -34,7 +42,7 @@ module Flipper
|
|
|
34
42
|
request = Rack::Request.new(env)
|
|
35
43
|
|
|
36
44
|
if memoize?(request)
|
|
37
|
-
memoized_call(
|
|
45
|
+
memoized_call(request)
|
|
38
46
|
else
|
|
39
47
|
@app.call(env)
|
|
40
48
|
end
|
|
@@ -52,26 +60,34 @@ module Flipper
|
|
|
52
60
|
end
|
|
53
61
|
end
|
|
54
62
|
|
|
55
|
-
def memoized_call(
|
|
56
|
-
|
|
57
|
-
flipper = env.fetch(@env_key) { Flipper }
|
|
63
|
+
def memoized_call(request)
|
|
64
|
+
flipper = request.env.fetch(@env_key) { Flipper }
|
|
58
65
|
|
|
59
66
|
# Already memoizing. This instance does not need to do anything.
|
|
60
67
|
if flipper.memoizing?
|
|
61
|
-
warn "Flipper::Middleware::Memoizer appears to be running twice. Read how to resolve this at https://github.com/
|
|
62
|
-
return @app.call(env)
|
|
68
|
+
warn "Flipper::Middleware::Memoizer appears to be running twice. Read how to resolve this at https://github.com/flippercloud/flipper/pull/523"
|
|
69
|
+
return @app.call(request.env)
|
|
63
70
|
end
|
|
64
71
|
|
|
65
|
-
|
|
72
|
+
begin
|
|
73
|
+
flipper.memoize = true
|
|
66
74
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
75
|
+
# Preloading is pointless without memoizing.
|
|
76
|
+
preload = if @opts[:preload].respond_to?(:call)
|
|
77
|
+
@opts[:preload].call(request)
|
|
78
|
+
else
|
|
79
|
+
@opts[:preload]
|
|
80
|
+
end
|
|
71
81
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
82
|
+
case preload
|
|
83
|
+
when true then flipper.preload_all
|
|
84
|
+
when Array then flipper.preload(preload)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
@app.call(request.env)
|
|
88
|
+
ensure
|
|
89
|
+
flipper.memoize = false
|
|
90
|
+
end
|
|
75
91
|
end
|
|
76
92
|
end
|
|
77
93
|
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
module Flipper
|
|
2
|
+
module Model
|
|
3
|
+
module ActiveRecord
|
|
4
|
+
# The id of the record when used as an actor.
|
|
5
|
+
#
|
|
6
|
+
# class User < ActiveRecord::Base
|
|
7
|
+
# end
|
|
8
|
+
#
|
|
9
|
+
# user = User.first
|
|
10
|
+
# Flipper.enable :some_feature, user
|
|
11
|
+
# Flipper.enabled? :some_feature, user #=> true
|
|
12
|
+
#
|
|
13
|
+
def flipper_id
|
|
14
|
+
"#{self.class.base_class.name};#{id}"
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Properties used to evaluate expressions
|
|
18
|
+
def flipper_properties
|
|
19
|
+
{"type" => self.class.name}.merge(attributes)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
data/lib/flipper/poller.rb
CHANGED
|
@@ -17,9 +17,11 @@ module Flipper
|
|
|
17
17
|
end
|
|
18
18
|
|
|
19
19
|
def self.reset
|
|
20
|
-
instances.each {|_,
|
|
20
|
+
instances.each {|_, instance| instance.stop }.clear
|
|
21
21
|
end
|
|
22
22
|
|
|
23
|
+
MINIMUM_POLL_INTERVAL = 10
|
|
24
|
+
|
|
23
25
|
def initialize(options = {})
|
|
24
26
|
@thread = nil
|
|
25
27
|
@pid = Process.pid
|
|
@@ -28,11 +30,11 @@ module Flipper
|
|
|
28
30
|
@remote_adapter = options.fetch(:remote_adapter)
|
|
29
31
|
@interval = options.fetch(:interval, 10).to_f
|
|
30
32
|
@last_synced_at = Concurrent::AtomicFixnum.new(0)
|
|
31
|
-
@adapter = Adapters::Memory.new
|
|
33
|
+
@adapter = Adapters::Memory.new(nil, threadsafe: true)
|
|
32
34
|
|
|
33
|
-
if @interval <
|
|
34
|
-
warn "Flipper::Cloud poll interval must be greater than or equal to
|
|
35
|
-
@interval =
|
|
35
|
+
if @interval < MINIMUM_POLL_INTERVAL
|
|
36
|
+
warn "Flipper::Cloud poll interval must be greater than or equal to #{MINIMUM_POLL_INTERVAL} but was #{@interval}. Setting @interval to #{MINIMUM_POLL_INTERVAL}."
|
|
37
|
+
@interval = MINIMUM_POLL_INTERVAL
|
|
36
38
|
end
|
|
37
39
|
|
|
38
40
|
@start_automatically = options.fetch(:start_automatically, true)
|
|
@@ -57,15 +59,14 @@ module Flipper
|
|
|
57
59
|
def run
|
|
58
60
|
loop do
|
|
59
61
|
sleep jitter
|
|
60
|
-
|
|
62
|
+
|
|
61
63
|
begin
|
|
62
64
|
sync
|
|
63
|
-
rescue
|
|
65
|
+
rescue
|
|
64
66
|
# you can instrument these using poller.flipper
|
|
65
67
|
end
|
|
66
68
|
|
|
67
|
-
|
|
68
|
-
sleep sleep_interval if sleep_interval.positive?
|
|
69
|
+
sleep interval
|
|
69
70
|
end
|
|
70
71
|
end
|
|
71
72
|
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
require "zlib"
|
|
2
|
+
require "stringio"
|
|
3
|
+
|
|
4
|
+
module Flipper
|
|
5
|
+
module Serializers
|
|
6
|
+
class Gzip
|
|
7
|
+
def self.serialize(source)
|
|
8
|
+
return if source.nil?
|
|
9
|
+
output = StringIO.new
|
|
10
|
+
gz = Zlib::GzipWriter.new(output)
|
|
11
|
+
gz.write(source)
|
|
12
|
+
gz.close
|
|
13
|
+
output.string
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def self.deserialize(source)
|
|
17
|
+
return if source.nil?
|
|
18
|
+
Zlib::GzipReader.wrap(StringIO.new(source), &:read)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
|
|
3
|
+
module Flipper
|
|
4
|
+
module Serializers
|
|
5
|
+
class Json
|
|
6
|
+
def self.serialize(source)
|
|
7
|
+
return if source.nil?
|
|
8
|
+
JSON.generate(source)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def self.deserialize(source)
|
|
12
|
+
return if source.nil?
|
|
13
|
+
JSON.parse(source)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|