flipper 0.26.0 → 1.1.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.
Files changed (199) hide show
  1. checksums.yaml +4 -4
  2. data/.github/FUNDING.yml +1 -0
  3. data/.github/workflows/ci.yml +19 -13
  4. data/.github/workflows/examples.yml +32 -15
  5. data/Changelog.md +294 -154
  6. data/Gemfile +15 -10
  7. data/README.md +13 -11
  8. data/benchmark/enabled_ips.rb +10 -0
  9. data/benchmark/enabled_multiple_actors_ips.rb +20 -0
  10. data/benchmark/enabled_profile.rb +20 -0
  11. data/benchmark/instrumentation_ips.rb +21 -0
  12. data/benchmark/typecast_ips.rb +27 -0
  13. data/docs/images/flipper_cloud.png +0 -0
  14. data/examples/api/basic.ru +3 -4
  15. data/examples/api/custom_memoized.ru +3 -4
  16. data/examples/api/memoized.ru +3 -4
  17. data/examples/cloud/app.ru +12 -0
  18. data/examples/cloud/backoff_policy.rb +13 -0
  19. data/examples/cloud/basic.rb +22 -0
  20. data/examples/cloud/cloud_setup.rb +20 -0
  21. data/examples/cloud/forked.rb +36 -0
  22. data/examples/cloud/import.rb +17 -0
  23. data/examples/cloud/threaded.rb +33 -0
  24. data/examples/dsl.rb +1 -15
  25. data/examples/enabled_for_actor.rb +4 -2
  26. data/examples/expressions.rb +213 -0
  27. data/examples/mirroring.rb +59 -0
  28. data/examples/strict.rb +18 -0
  29. data/flipper-cloud.gemspec +19 -0
  30. data/flipper.gemspec +3 -5
  31. data/lib/flipper/actor.rb +6 -3
  32. data/lib/flipper/adapter.rb +33 -7
  33. data/lib/flipper/adapter_builder.rb +44 -0
  34. data/lib/flipper/adapters/dual_write.rb +1 -3
  35. data/lib/flipper/adapters/failover.rb +0 -4
  36. data/lib/flipper/adapters/failsafe.rb +0 -4
  37. data/lib/flipper/adapters/http/client.rb +26 -7
  38. data/lib/flipper/adapters/http/error.rb +1 -1
  39. data/lib/flipper/adapters/http.rb +29 -16
  40. data/lib/flipper/adapters/instrumented.rb +25 -6
  41. data/lib/flipper/adapters/memoizable.rb +33 -21
  42. data/lib/flipper/adapters/memory.rb +81 -46
  43. data/lib/flipper/adapters/operation_logger.rb +16 -7
  44. data/lib/flipper/adapters/poll/poller.rb +2 -125
  45. data/lib/flipper/adapters/poll.rb +5 -3
  46. data/lib/flipper/adapters/pstore.rb +17 -11
  47. data/lib/flipper/adapters/read_only.rb +4 -4
  48. data/lib/flipper/adapters/strict.rb +47 -0
  49. data/lib/flipper/adapters/sync/feature_synchronizer.rb +10 -1
  50. data/lib/flipper/adapters/sync.rb +0 -4
  51. data/lib/flipper/cloud/configuration.rb +258 -0
  52. data/lib/flipper/cloud/dsl.rb +27 -0
  53. data/lib/flipper/cloud/message_verifier.rb +95 -0
  54. data/lib/flipper/cloud/middleware.rb +63 -0
  55. data/lib/flipper/cloud/routes.rb +14 -0
  56. data/lib/flipper/cloud/telemetry/backoff_policy.rb +93 -0
  57. data/lib/flipper/cloud/telemetry/instrumenter.rb +26 -0
  58. data/lib/flipper/cloud/telemetry/metric.rb +39 -0
  59. data/lib/flipper/cloud/telemetry/metric_storage.rb +30 -0
  60. data/lib/flipper/cloud/telemetry/submitter.rb +98 -0
  61. data/lib/flipper/cloud/telemetry.rb +183 -0
  62. data/lib/flipper/cloud.rb +53 -0
  63. data/lib/flipper/configuration.rb +25 -4
  64. data/lib/flipper/dsl.rb +46 -45
  65. data/lib/flipper/engine.rb +88 -0
  66. data/lib/flipper/errors.rb +3 -3
  67. data/lib/flipper/export.rb +26 -0
  68. data/lib/flipper/exporter.rb +17 -0
  69. data/lib/flipper/exporters/json/export.rb +32 -0
  70. data/lib/flipper/exporters/json/v1.rb +33 -0
  71. data/lib/flipper/expression/builder.rb +73 -0
  72. data/lib/flipper/expression/constant.rb +25 -0
  73. data/lib/flipper/expression.rb +71 -0
  74. data/lib/flipper/expressions/all.rb +11 -0
  75. data/lib/flipper/expressions/any.rb +9 -0
  76. data/lib/flipper/expressions/boolean.rb +9 -0
  77. data/lib/flipper/expressions/comparable.rb +13 -0
  78. data/lib/flipper/expressions/duration.rb +28 -0
  79. data/lib/flipper/expressions/equal.rb +9 -0
  80. data/lib/flipper/expressions/greater_than.rb +9 -0
  81. data/lib/flipper/expressions/greater_than_or_equal_to.rb +9 -0
  82. data/lib/flipper/expressions/less_than.rb +9 -0
  83. data/lib/flipper/expressions/less_than_or_equal_to.rb +9 -0
  84. data/lib/flipper/expressions/not_equal.rb +9 -0
  85. data/lib/flipper/expressions/now.rb +9 -0
  86. data/lib/flipper/expressions/number.rb +9 -0
  87. data/lib/flipper/expressions/percentage.rb +9 -0
  88. data/lib/flipper/expressions/percentage_of_actors.rb +12 -0
  89. data/lib/flipper/expressions/property.rb +9 -0
  90. data/lib/flipper/expressions/random.rb +9 -0
  91. data/lib/flipper/expressions/string.rb +9 -0
  92. data/lib/flipper/expressions/time.rb +9 -0
  93. data/lib/flipper/feature.rb +87 -26
  94. data/lib/flipper/feature_check_context.rb +10 -6
  95. data/lib/flipper/gate.rb +13 -11
  96. data/lib/flipper/gate_values.rb +5 -18
  97. data/lib/flipper/gates/actor.rb +10 -17
  98. data/lib/flipper/gates/boolean.rb +1 -1
  99. data/lib/flipper/gates/expression.rb +75 -0
  100. data/lib/flipper/gates/group.rb +5 -7
  101. data/lib/flipper/gates/percentage_of_actors.rb +10 -13
  102. data/lib/flipper/gates/percentage_of_time.rb +1 -2
  103. data/lib/flipper/identifier.rb +2 -2
  104. data/lib/flipper/instrumentation/log_subscriber.rb +24 -5
  105. data/lib/flipper/instrumentation/statsd_subscriber.rb +2 -4
  106. data/lib/flipper/instrumentation/subscriber.rb +8 -1
  107. data/lib/flipper/metadata.rb +5 -1
  108. data/lib/flipper/middleware/memoizer.rb +30 -14
  109. data/lib/flipper/poller.rb +117 -0
  110. data/lib/flipper/serializers/gzip.rb +24 -0
  111. data/lib/flipper/serializers/json.rb +19 -0
  112. data/lib/flipper/spec/shared_adapter_specs.rb +95 -54
  113. data/lib/flipper/test/shared_adapter_test.rb +91 -48
  114. data/lib/flipper/typecast.rb +56 -15
  115. data/lib/flipper/types/actor.rb +13 -13
  116. data/lib/flipper/types/group.rb +4 -4
  117. data/lib/flipper/types/percentage.rb +1 -1
  118. data/lib/flipper/version.rb +1 -1
  119. data/lib/flipper.rb +47 -10
  120. data/spec/fixtures/flipper_pstore_1679087600.json +46 -0
  121. data/spec/flipper/adapter_builder_spec.rb +73 -0
  122. data/spec/flipper/adapter_spec.rb +30 -2
  123. data/spec/flipper/adapters/dual_write_spec.rb +2 -2
  124. data/spec/flipper/adapters/http_spec.rb +64 -8
  125. data/spec/flipper/adapters/instrumented_spec.rb +29 -11
  126. data/spec/flipper/adapters/memoizable_spec.rb +51 -31
  127. data/spec/flipper/adapters/memory_spec.rb +14 -3
  128. data/spec/flipper/adapters/operation_logger_spec.rb +31 -12
  129. data/spec/flipper/adapters/read_only_spec.rb +32 -17
  130. data/spec/flipper/adapters/strict_spec.rb +62 -0
  131. data/spec/flipper/adapters/sync/feature_synchronizer_spec.rb +27 -0
  132. data/spec/flipper/cloud/configuration_spec.rb +252 -0
  133. data/spec/flipper/cloud/dsl_spec.rb +82 -0
  134. data/spec/flipper/cloud/message_verifier_spec.rb +104 -0
  135. data/spec/flipper/cloud/middleware_spec.rb +289 -0
  136. data/spec/flipper/cloud/telemetry/backoff_policy_spec.rb +108 -0
  137. data/spec/flipper/cloud/telemetry/metric_spec.rb +87 -0
  138. data/spec/flipper/cloud/telemetry/metric_storage_spec.rb +58 -0
  139. data/spec/flipper/cloud/telemetry/submitter_spec.rb +145 -0
  140. data/spec/flipper/cloud/telemetry_spec.rb +156 -0
  141. data/spec/flipper/cloud_spec.rb +180 -0
  142. data/spec/flipper/configuration_spec.rb +17 -0
  143. data/spec/flipper/dsl_spec.rb +54 -73
  144. data/spec/flipper/engine_spec.rb +291 -0
  145. data/spec/flipper/export_spec.rb +13 -0
  146. data/spec/flipper/exporter_spec.rb +16 -0
  147. data/spec/flipper/exporters/json/export_spec.rb +60 -0
  148. data/spec/flipper/exporters/json/v1_spec.rb +33 -0
  149. data/spec/flipper/expression/builder_spec.rb +248 -0
  150. data/spec/flipper/expression_spec.rb +188 -0
  151. data/spec/flipper/expressions/all_spec.rb +15 -0
  152. data/spec/flipper/expressions/any_spec.rb +15 -0
  153. data/spec/flipper/expressions/boolean_spec.rb +15 -0
  154. data/spec/flipper/expressions/duration_spec.rb +43 -0
  155. data/spec/flipper/expressions/equal_spec.rb +24 -0
  156. data/spec/flipper/expressions/greater_than_or_equal_to_spec.rb +28 -0
  157. data/spec/flipper/expressions/greater_than_spec.rb +28 -0
  158. data/spec/flipper/expressions/less_than_or_equal_to_spec.rb +28 -0
  159. data/spec/flipper/expressions/less_than_spec.rb +32 -0
  160. data/spec/flipper/expressions/not_equal_spec.rb +15 -0
  161. data/spec/flipper/expressions/now_spec.rb +11 -0
  162. data/spec/flipper/expressions/number_spec.rb +21 -0
  163. data/spec/flipper/expressions/percentage_of_actors_spec.rb +20 -0
  164. data/spec/flipper/expressions/percentage_spec.rb +15 -0
  165. data/spec/flipper/expressions/property_spec.rb +13 -0
  166. data/spec/flipper/expressions/random_spec.rb +9 -0
  167. data/spec/flipper/expressions/string_spec.rb +11 -0
  168. data/spec/flipper/expressions/time_spec.rb +13 -0
  169. data/spec/flipper/feature_check_context_spec.rb +17 -17
  170. data/spec/flipper/feature_spec.rb +436 -33
  171. data/spec/flipper/gate_values_spec.rb +2 -33
  172. data/spec/flipper/gates/boolean_spec.rb +1 -1
  173. data/spec/flipper/gates/expression_spec.rb +108 -0
  174. data/spec/flipper/gates/group_spec.rb +2 -3
  175. data/spec/flipper/gates/percentage_of_actors_spec.rb +61 -5
  176. data/spec/flipper/gates/percentage_of_time_spec.rb +2 -2
  177. data/spec/flipper/identifier_spec.rb +4 -5
  178. data/spec/flipper/instrumentation/log_subscriber_spec.rb +15 -5
  179. data/spec/flipper/instrumentation/statsd_subscriber_spec.rb +25 -1
  180. data/spec/flipper/middleware/memoizer_spec.rb +67 -0
  181. data/spec/flipper/poller_spec.rb +47 -0
  182. data/spec/flipper/serializers/gzip_spec.rb +13 -0
  183. data/spec/flipper/serializers/json_spec.rb +13 -0
  184. data/spec/flipper/typecast_spec.rb +121 -6
  185. data/spec/flipper/types/actor_spec.rb +63 -46
  186. data/spec/flipper/types/group_spec.rb +2 -2
  187. data/spec/flipper_integration_spec.rb +168 -58
  188. data/spec/flipper_spec.rb +92 -28
  189. data/spec/spec_helper.rb +6 -13
  190. data/spec/support/actor_names.yml +1 -0
  191. data/spec/support/climate_control.rb +7 -0
  192. data/spec/support/fake_backoff_policy.rb +15 -0
  193. data/spec/support/skippable.rb +18 -0
  194. data/spec/support/spec_helpers.rb +11 -3
  195. metadata +166 -13
  196. data/.github/workflows/release.yml +0 -44
  197. data/.tool-versions +0 -1
  198. data/lib/flipper/railtie.rb +0 -47
  199. data/spec/flipper/railtie_spec.rb +0 -109
@@ -45,7 +45,6 @@ module Flipper
45
45
  gate_name = @payload[:gate_name]
46
46
  operation = strip_trailing_question_mark(@payload[:operation])
47
47
  result = @payload[:result]
48
- thing = @payload[:thing]
49
48
 
50
49
  update_timer "flipper.feature_operation.#{operation}"
51
50
 
@@ -72,6 +71,14 @@ module Flipper
72
71
  update_timer "flipper.adapter.#{adapter_name}.#{operation}"
73
72
  end
74
73
 
74
+ def update_poller_metrics
75
+ # noop
76
+ end
77
+
78
+ def update_synchronizer_call_metrics
79
+ # noop
80
+ end
81
+
75
82
  QUESTION_MARK = '?'.freeze
76
83
 
77
84
  # Private
@@ -1,5 +1,9 @@
1
1
  module Flipper
2
2
  METADATA = {
3
- 'changelog_uri' => 'https://github.com/jnunemaker/flipper/blob/main/Changelog.md',
3
+ "documentation_uri" => "https://www.flippercloud.io/docs",
4
+ "homepage_uri" => "https://www.flippercloud.io",
5
+ "source_code_uri" => "https://github.com/flippercloud/flipper",
6
+ "bug_tracker_uri" => "https://github.com/flippercloud/flipper/issues",
7
+ "changelog_uri" => "https://github.com/flippercloud/flipper/blob/main/Changelog.md",
4
8
  }.freeze
5
9
  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(env)
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(env)
56
- reset_on_body_close = false
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/jnunemaker/flipper/pull/523"
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
- flipper.memoize = true
72
+ begin
73
+ flipper.memoize = true
66
74
 
67
- case @opts[:preload]
68
- when true then flipper.preload_all
69
- when Array then flipper.preload(@opts[:preload])
70
- end
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
- @app.call(env)
73
- ensure
74
- flipper.memoize = false if flipper
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,117 @@
1
+ require 'logger'
2
+ require 'concurrent/utility/monotonic_time'
3
+ require 'concurrent/map'
4
+ require 'concurrent/atomic/atomic_fixnum'
5
+
6
+ module Flipper
7
+ class Poller
8
+ attr_reader :adapter, :thread, :pid, :mutex, :interval, :last_synced_at
9
+
10
+ def self.instances
11
+ @instances ||= Concurrent::Map.new
12
+ end
13
+ private_class_method :instances
14
+
15
+ def self.get(key, options = {})
16
+ instances.compute_if_absent(key) { new(options) }
17
+ end
18
+
19
+ def self.reset
20
+ instances.each {|_, instance| instance.stop }.clear
21
+ end
22
+
23
+ def initialize(options = {})
24
+ @thread = nil
25
+ @pid = Process.pid
26
+ @mutex = Mutex.new
27
+ @instrumenter = options.fetch(:instrumenter, Instrumenters::Noop)
28
+ @remote_adapter = options.fetch(:remote_adapter)
29
+ @interval = options.fetch(:interval, 10).to_f
30
+ @last_synced_at = Concurrent::AtomicFixnum.new(0)
31
+ @adapter = Adapters::Memory.new(nil, threadsafe: true)
32
+
33
+ if @interval < 1
34
+ warn "Flipper::Cloud poll interval must be greater than or equal to 1 but was #{@interval}. Setting @interval to 1."
35
+ @interval = 1
36
+ end
37
+
38
+ @start_automatically = options.fetch(:start_automatically, true)
39
+
40
+ if options.fetch(:shutdown_automatically, true)
41
+ at_exit { stop }
42
+ end
43
+ end
44
+
45
+ def start
46
+ reset if forked?
47
+ ensure_worker_running
48
+ end
49
+
50
+ def stop
51
+ @instrumenter.instrument("poller.#{InstrumentationNamespace}", {
52
+ operation: :stop,
53
+ })
54
+ @thread&.kill
55
+ end
56
+
57
+ def run
58
+ loop do
59
+ sleep jitter
60
+ start = Concurrent.monotonic_time
61
+ begin
62
+ sync
63
+ rescue => exception
64
+ # you can instrument these using poller.flipper
65
+ end
66
+
67
+ sleep_interval = interval - (Concurrent.monotonic_time - start)
68
+ sleep sleep_interval if sleep_interval.positive?
69
+ end
70
+ end
71
+
72
+ def sync
73
+ @instrumenter.instrument("poller.#{InstrumentationNamespace}", operation: :poll) do
74
+ @adapter.import @remote_adapter
75
+ @last_synced_at.update { |time| Concurrent.monotonic_time }
76
+ end
77
+ end
78
+
79
+ private
80
+
81
+ def jitter
82
+ rand
83
+ end
84
+
85
+ def forked?
86
+ pid != Process.pid
87
+ end
88
+
89
+ def ensure_worker_running
90
+ # Return early if thread is alive and avoid the mutex lock and unlock.
91
+ return if thread_alive?
92
+
93
+ # If another thread is starting worker thread, then return early so this
94
+ # thread can enqueue and move on with life.
95
+ return unless mutex.try_lock
96
+
97
+ begin
98
+ return if thread_alive?
99
+ @thread = Thread.new { run }
100
+ @instrumenter.instrument("poller.#{InstrumentationNamespace}", {
101
+ operation: :thread_start,
102
+ })
103
+ ensure
104
+ mutex.unlock
105
+ end
106
+ end
107
+
108
+ def thread_alive?
109
+ @thread && @thread.alive?
110
+ end
111
+
112
+ def reset
113
+ @pid = Process.pid
114
+ mutex.unlock if mutex.locked?
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,24 @@
1
+ require "zlib"
2
+ require "stringio"
3
+
4
+ module Flipper
5
+ module Serializers
6
+ module Gzip
7
+ module_function
8
+
9
+ def serialize(source)
10
+ return if source.nil?
11
+ output = StringIO.new
12
+ gz = Zlib::GzipWriter.new(output)
13
+ gz.write(source)
14
+ gz.close
15
+ output.string
16
+ end
17
+
18
+ def deserialize(source)
19
+ return if source.nil?
20
+ Zlib::GzipReader.wrap(StringIO.new(source), &:read)
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,19 @@
1
+ require "json"
2
+
3
+ module Flipper
4
+ module Serializers
5
+ module Json
6
+ module_function
7
+
8
+ def serialize(source)
9
+ return if source.nil?
10
+ JSON.generate(source)
11
+ end
12
+
13
+ def deserialize(source)
14
+ return if source.nil?
15
+ JSON.parse(source)
16
+ end
17
+ end
18
+ end
19
+ end
@@ -4,11 +4,12 @@ RSpec.shared_examples_for 'a flipper adapter' do
4
4
  let(:flipper) { Flipper.new(subject) }
5
5
  let(:feature) { flipper[:stats] }
6
6
 
7
- let(:boolean_gate) { feature.gate(:boolean) }
8
- let(:group_gate) { feature.gate(:group) }
9
- let(:actor_gate) { feature.gate(:actor) }
10
- let(:actors_gate) { feature.gate(:percentage_of_actors) }
11
- let(:time_gate) { feature.gate(:percentage_of_time) }
7
+ let(:boolean_gate) { feature.gate(:boolean) }
8
+ let(:expression_gate) { feature.gate(:expression) }
9
+ let(:group_gate) { feature.gate(:group) }
10
+ let(:actor_gate) { feature.gate(:actor) }
11
+ let(:actors_gate) { feature.gate(:percentage_of_actors) }
12
+ let(:time_gate) { feature.gate(:percentage_of_time) }
12
13
 
13
14
  before do
14
15
  Flipper.register(:admins) do |actor|
@@ -33,17 +34,25 @@ RSpec.shared_examples_for 'a flipper adapter' do
33
34
  expect(subject.class.ancestors).to include(Flipper::Adapter)
34
35
  end
35
36
 
37
+ it 'knows how to get adapter from source' do
38
+ adapter = Flipper::Adapters::Memory.new
39
+ flipper = Flipper.new(adapter)
40
+ expect(subject.class.from(adapter).class.ancestors).to include(Flipper::Adapter)
41
+ expect(subject.class.from(flipper).class.ancestors).to include(Flipper::Adapter)
42
+ end
43
+
36
44
  it 'returns correct default values for the gates if none are enabled' do
45
+ expect(subject.get(feature)).to eq(subject.class.default_config)
37
46
  expect(subject.get(feature)).to eq(subject.default_config)
38
47
  end
39
48
 
40
49
  it 'can enable, disable and get value for boolean gate' do
41
- expect(subject.enable(feature, boolean_gate, flipper.boolean)).to eq(true)
50
+ expect(subject.enable(feature, boolean_gate, Flipper::Types::Boolean.new)).to eq(true)
42
51
 
43
52
  result = subject.get(feature)
44
53
  expect(result[:boolean]).to eq('true')
45
54
 
46
- expect(subject.disable(feature, boolean_gate, flipper.boolean(false))).to eq(true)
55
+ expect(subject.disable(feature, boolean_gate, Flipper::Types::Boolean.new(false))).to eq(true)
47
56
 
48
57
  result = subject.get(feature)
49
58
  expect(result[:boolean]).to eq(nil)
@@ -51,17 +60,34 @@ RSpec.shared_examples_for 'a flipper adapter' do
51
60
 
52
61
  it 'fully disables all enabled things when boolean gate disabled' do
53
62
  actor22 = Flipper::Actor.new('22')
54
- expect(subject.enable(feature, boolean_gate, flipper.boolean)).to eq(true)
63
+ expect(subject.enable(feature, boolean_gate, Flipper::Types::Boolean.new)).to eq(true)
55
64
  expect(subject.enable(feature, group_gate, flipper.group(:admins))).to eq(true)
56
- expect(subject.enable(feature, actor_gate, flipper.actor(actor22))).to eq(true)
57
- expect(subject.enable(feature, actors_gate, flipper.actors(25))).to eq(true)
58
- expect(subject.enable(feature, time_gate, flipper.time(45))).to eq(true)
59
-
60
- expect(subject.disable(feature, boolean_gate, flipper.boolean(false))).to eq(true)
65
+ expect(subject.enable(feature, actor_gate, Flipper::Types::Actor.new(actor22))).to eq(true)
66
+ expect(subject.enable(feature, actors_gate, Flipper::Types::PercentageOfActors.new(25))).to eq(true)
67
+ expect(subject.enable(feature, time_gate, Flipper::Types::PercentageOfTime.new(45))).to eq(true)
61
68
 
69
+ expect(subject.disable(feature, boolean_gate, Flipper::Types::Boolean.new(false))).to eq(true)
62
70
  expect(subject.get(feature)).to eq(subject.default_config)
63
71
  end
64
72
 
73
+ it 'can enable, disable and get value for expression gate' do
74
+ basic_expression = Flipper.property(:plan).eq("basic")
75
+ age_expression = Flipper.property(:age).gte(21)
76
+ any_expression = Flipper.any(basic_expression, age_expression)
77
+
78
+ expect(subject.enable(feature, expression_gate, any_expression)).to eq(true)
79
+ result = subject.get(feature)
80
+ expect(result[:expression]).to eq(any_expression.value)
81
+
82
+ expect(subject.enable(feature, expression_gate, basic_expression)).to eq(true)
83
+ result = subject.get(feature)
84
+ expect(result[:expression]).to eq(basic_expression.value)
85
+
86
+ expect(subject.disable(feature, expression_gate, basic_expression)).to eq(true)
87
+ result = subject.get(feature)
88
+ expect(result[:expression]).to be(nil)
89
+ end
90
+
65
91
  it 'can enable, disable and get value for group gate' do
66
92
  expect(subject.enable(feature, group_gate, flipper.group(:admins))).to eq(true)
67
93
  expect(subject.enable(feature, group_gate, flipper.group(:early_access))).to eq(true)
@@ -82,34 +108,34 @@ RSpec.shared_examples_for 'a flipper adapter' do
82
108
  actor22 = Flipper::Actor.new('22')
83
109
  actor_asdf = Flipper::Actor.new('asdf')
84
110
 
85
- expect(subject.enable(feature, actor_gate, flipper.actor(actor22))).to eq(true)
86
- expect(subject.enable(feature, actor_gate, flipper.actor(actor_asdf))).to eq(true)
111
+ expect(subject.enable(feature, actor_gate, Flipper::Types::Actor.new(actor22))).to eq(true)
112
+ expect(subject.enable(feature, actor_gate, Flipper::Types::Actor.new(actor_asdf))).to eq(true)
87
113
 
88
114
  result = subject.get(feature)
89
115
  expect(result[:actors]).to eq(Set['22', 'asdf'])
90
116
 
91
- expect(subject.disable(feature, actor_gate, flipper.actor(actor22))).to eq(true)
117
+ expect(subject.disable(feature, actor_gate, Flipper::Types::Actor.new(actor22))).to eq(true)
92
118
  result = subject.get(feature)
93
119
  expect(result[:actors]).to eq(Set['asdf'])
94
120
 
95
- expect(subject.disable(feature, actor_gate, flipper.actor(actor_asdf))).to eq(true)
121
+ expect(subject.disable(feature, actor_gate, Flipper::Types::Actor.new(actor_asdf))).to eq(true)
96
122
  result = subject.get(feature)
97
123
  expect(result[:actors]).to eq(Set.new)
98
124
  end
99
125
 
100
126
  it 'can enable, disable and get value for percentage of actors gate' do
101
- expect(subject.enable(feature, actors_gate, flipper.actors(15))).to eq(true)
127
+ expect(subject.enable(feature, actors_gate, Flipper::Types::PercentageOfActors.new(15))).to eq(true)
102
128
  result = subject.get(feature)
103
129
  expect(result[:percentage_of_actors]).to eq('15')
104
130
 
105
- expect(subject.disable(feature, actors_gate, flipper.actors(0))).to eq(true)
131
+ expect(subject.disable(feature, actors_gate, Flipper::Types::PercentageOfActors.new(0))).to eq(true)
106
132
  result = subject.get(feature)
107
133
  expect(result[:percentage_of_actors]).to eq('0')
108
134
  end
109
135
 
110
136
  it 'can enable percentage of actors gate many times and consistently return values' do
111
137
  (1..100).each do |percentage|
112
- expect(subject.enable(feature, actors_gate, flipper.actors(percentage))).to eq(true)
138
+ expect(subject.enable(feature, actors_gate, Flipper::Types::PercentageOfActors.new(percentage))).to eq(true)
113
139
  result = subject.get(feature)
114
140
  expect(result[:percentage_of_actors]).to eq(percentage.to_s)
115
141
  end
@@ -117,25 +143,25 @@ RSpec.shared_examples_for 'a flipper adapter' do
117
143
 
118
144
  it 'can disable percentage of actors gate many times and consistently return values' do
119
145
  (1..100).each do |percentage|
120
- expect(subject.disable(feature, actors_gate, flipper.actors(percentage))).to eq(true)
146
+ expect(subject.disable(feature, actors_gate, Flipper::Types::PercentageOfActors.new(percentage))).to eq(true)
121
147
  result = subject.get(feature)
122
148
  expect(result[:percentage_of_actors]).to eq(percentage.to_s)
123
149
  end
124
150
  end
125
151
 
126
152
  it 'can enable, disable and get value for percentage of time gate' do
127
- expect(subject.enable(feature, time_gate, flipper.time(10))).to eq(true)
153
+ expect(subject.enable(feature, time_gate, Flipper::Types::PercentageOfTime.new(10))).to eq(true)
128
154
  result = subject.get(feature)
129
155
  expect(result[:percentage_of_time]).to eq('10')
130
156
 
131
- expect(subject.disable(feature, time_gate, flipper.time(0))).to eq(true)
157
+ expect(subject.disable(feature, time_gate, Flipper::Types::PercentageOfTime.new(0))).to eq(true)
132
158
  result = subject.get(feature)
133
159
  expect(result[:percentage_of_time]).to eq('0')
134
160
  end
135
161
 
136
162
  it 'can enable percentage of time gate many times and consistently return values' do
137
163
  (1..100).each do |percentage|
138
- expect(subject.enable(feature, time_gate, flipper.time(percentage))).to eq(true)
164
+ expect(subject.enable(feature, time_gate, Flipper::Types::PercentageOfTime.new(percentage))).to eq(true)
139
165
  result = subject.get(feature)
140
166
  expect(result[:percentage_of_time]).to eq(percentage.to_s)
141
167
  end
@@ -143,20 +169,20 @@ RSpec.shared_examples_for 'a flipper adapter' do
143
169
 
144
170
  it 'can disable percentage of time gate many times and consistently return values' do
145
171
  (1..100).each do |percentage|
146
- expect(subject.disable(feature, time_gate, flipper.time(percentage))).to eq(true)
172
+ expect(subject.disable(feature, time_gate, Flipper::Types::PercentageOfTime.new(percentage))).to eq(true)
147
173
  result = subject.get(feature)
148
174
  expect(result[:percentage_of_time]).to eq(percentage.to_s)
149
175
  end
150
176
  end
151
177
 
152
178
  it 'converts boolean value to a string' do
153
- expect(subject.enable(feature, boolean_gate, flipper.boolean)).to eq(true)
179
+ expect(subject.enable(feature, boolean_gate, Flipper::Types::Boolean.new)).to eq(true)
154
180
  result = subject.get(feature)
155
181
  expect(result[:boolean]).to eq('true')
156
182
  end
157
183
 
158
184
  it 'converts the actor value to a string' do
159
- expect(subject.enable(feature, actor_gate, flipper.actor(Flipper::Actor.new(22)))).to eq(true)
185
+ expect(subject.enable(feature, actor_gate, Flipper::Types::Actor.new(Flipper::Actor.new(22)))).to eq(true)
160
186
  result = subject.get(feature)
161
187
  expect(result[:actors]).to eq(Set['22'])
162
188
  end
@@ -168,13 +194,13 @@ RSpec.shared_examples_for 'a flipper adapter' do
168
194
  end
169
195
 
170
196
  it 'converts percentage of time integer value to a string' do
171
- expect(subject.enable(feature, time_gate, flipper.time(10))).to eq(true)
197
+ expect(subject.enable(feature, time_gate, Flipper::Types::PercentageOfTime.new(10))).to eq(true)
172
198
  result = subject.get(feature)
173
199
  expect(result[:percentage_of_time]).to eq('10')
174
200
  end
175
201
 
176
202
  it 'converts percentage of actors integer value to a string' do
177
- expect(subject.enable(feature, actors_gate, flipper.actors(10))).to eq(true)
203
+ expect(subject.enable(feature, actors_gate, Flipper::Types::PercentageOfActors.new(10))).to eq(true)
178
204
  result = subject.get(feature)
179
205
  expect(result[:percentage_of_actors]).to eq('10')
180
206
  end
@@ -197,11 +223,11 @@ RSpec.shared_examples_for 'a flipper adapter' do
197
223
 
198
224
  it 'clears all the gate values for the feature on remove' do
199
225
  actor22 = Flipper::Actor.new('22')
200
- expect(subject.enable(feature, boolean_gate, flipper.boolean)).to eq(true)
226
+ expect(subject.enable(feature, boolean_gate, Flipper::Types::Boolean.new)).to eq(true)
201
227
  expect(subject.enable(feature, group_gate, flipper.group(:admins))).to eq(true)
202
- expect(subject.enable(feature, actor_gate, flipper.actor(actor22))).to eq(true)
203
- expect(subject.enable(feature, actors_gate, flipper.actors(25))).to eq(true)
204
- expect(subject.enable(feature, time_gate, flipper.time(45))).to eq(true)
228
+ expect(subject.enable(feature, actor_gate, Flipper::Types::Actor.new(actor22))).to eq(true)
229
+ expect(subject.enable(feature, actors_gate, Flipper::Types::PercentageOfActors.new(25))).to eq(true)
230
+ expect(subject.enable(feature, time_gate, Flipper::Types::PercentageOfTime.new(45))).to eq(true)
205
231
 
206
232
  expect(subject.remove(feature)).to eq(true)
207
233
 
@@ -213,11 +239,11 @@ RSpec.shared_examples_for 'a flipper adapter' do
213
239
  subject.add(feature)
214
240
  expect(subject.features).to include(feature.key)
215
241
 
216
- expect(subject.enable(feature, boolean_gate, flipper.boolean)).to eq(true)
242
+ expect(subject.enable(feature, boolean_gate, Flipper::Types::Boolean.new)).to eq(true)
217
243
  expect(subject.enable(feature, group_gate, flipper.group(:admins))).to eq(true)
218
- expect(subject.enable(feature, actor_gate, flipper.actor(actor22))).to eq(true)
219
- expect(subject.enable(feature, actors_gate, flipper.actors(25))).to eq(true)
220
- expect(subject.enable(feature, time_gate, flipper.time(45))).to eq(true)
244
+ expect(subject.enable(feature, actor_gate, Flipper::Types::Actor.new(actor22))).to eq(true)
245
+ expect(subject.enable(feature, actors_gate, Flipper::Types::PercentageOfActors.new(25))).to eq(true)
246
+ expect(subject.enable(feature, time_gate, Flipper::Types::PercentageOfTime.new(45))).to eq(true)
221
247
 
222
248
  expect(subject.clear(feature)).to eq(true)
223
249
  expect(subject.features).to include(feature.key)
@@ -230,7 +256,7 @@ RSpec.shared_examples_for 'a flipper adapter' do
230
256
 
231
257
  it 'can get multiple features' do
232
258
  expect(subject.add(flipper[:stats])).to eq(true)
233
- expect(subject.enable(flipper[:stats], boolean_gate, flipper.boolean)).to eq(true)
259
+ expect(subject.enable(flipper[:stats], boolean_gate, Flipper::Types::Boolean.new)).to eq(true)
234
260
  expect(subject.add(flipper[:search])).to eq(true)
235
261
 
236
262
  result = subject.get_multi([flipper[:stats], flipper[:search], flipper[:other]])
@@ -246,16 +272,16 @@ RSpec.shared_examples_for 'a flipper adapter' do
246
272
 
247
273
  it 'can get all features' do
248
274
  expect(subject.add(flipper[:stats])).to eq(true)
249
- expect(subject.enable(flipper[:stats], boolean_gate, flipper.boolean)).to eq(true)
275
+ expect(subject.enable(flipper[:stats], boolean_gate, Flipper::Types::Boolean.new)).to eq(true)
250
276
  expect(subject.add(flipper[:search])).to eq(true)
277
+ flipper.enable :analytics, Flipper.property(:plan).eq("pro")
251
278
 
252
279
  result = subject.get_all
253
- expect(result).to be_instance_of(Hash)
254
280
 
255
- stats = result["stats"]
256
- search = result["search"]
257
- expect(stats).to eq(subject.default_config.merge(boolean: 'true'))
258
- expect(search).to eq(subject.default_config)
281
+ expect(result).to be_instance_of(Hash)
282
+ expect(result["stats"]).to eq(subject.default_config.merge(boolean: 'true'))
283
+ expect(result["search"]).to eq(subject.default_config)
284
+ expect(result["analytics"]).to eq(subject.default_config.merge(expression: {"Equal"=>[{"Property"=>["plan"]}, "pro"]}))
259
285
  end
260
286
 
261
287
  it 'includes explicitly disabled features when getting all features' do
@@ -269,8 +295,8 @@ RSpec.shared_examples_for 'a flipper adapter' do
269
295
 
270
296
  it 'can double enable an actor without error' do
271
297
  actor = Flipper::Actor.new('Flipper::Actor;22')
272
- expect(subject.enable(feature, actor_gate, flipper.actor(actor))).to eq(true)
273
- expect(subject.enable(feature, actor_gate, flipper.actor(actor))).to eq(true)
298
+ expect(subject.enable(feature, actor_gate, Flipper::Types::Actor.new(actor))).to eq(true)
299
+ expect(subject.enable(feature, actor_gate, Flipper::Types::Actor.new(actor))).to eq(true)
274
300
  expect(subject.get(feature).fetch(:actors)).to eq(Set['Flipper::Actor;22'])
275
301
  end
276
302
 
@@ -281,13 +307,13 @@ RSpec.shared_examples_for 'a flipper adapter' do
281
307
  end
282
308
 
283
309
  it 'can double enable percentage without error' do
284
- expect(subject.enable(feature, actors_gate, flipper.actors(25))).to eq(true)
285
- expect(subject.enable(feature, actors_gate, flipper.actors(25))).to eq(true)
310
+ expect(subject.enable(feature, actors_gate, Flipper::Types::PercentageOfActors.new(25))).to eq(true)
311
+ expect(subject.enable(feature, actors_gate, Flipper::Types::PercentageOfActors.new(25))).to eq(true)
286
312
  end
287
313
 
288
314
  it 'can double enable without error' do
289
- expect(subject.enable(feature, boolean_gate, flipper.boolean)).to eq(true)
290
- expect(subject.enable(feature, boolean_gate, flipper.boolean)).to eq(true)
315
+ expect(subject.enable(feature, boolean_gate, Flipper::Types::Boolean.new)).to eq(true)
316
+ expect(subject.enable(feature, boolean_gate, Flipper::Types::Boolean.new)).to eq(true)
291
317
  end
292
318
 
293
319
  it 'can get_all features when there are none' do
@@ -297,11 +323,26 @@ RSpec.shared_examples_for 'a flipper adapter' do
297
323
 
298
324
  it 'clears other gate values on enable' do
299
325
  actor = Flipper::Actor.new('Flipper::Actor;22')
300
- subject.enable(feature, actors_gate, flipper.actors(25))
301
- subject.enable(feature, time_gate, flipper.time(25))
326
+ subject.enable(feature, actors_gate, Flipper::Types::PercentageOfActors.new(25))
327
+ subject.enable(feature, time_gate, Flipper::Types::PercentageOfTime.new(25))
302
328
  subject.enable(feature, group_gate, flipper.group(:admins))
303
- subject.enable(feature, actor_gate, flipper.actor(actor))
304
- subject.enable(feature, boolean_gate, flipper.boolean(true))
329
+ subject.enable(feature, actor_gate, Flipper::Types::Actor.new(actor))
330
+ subject.enable(feature, boolean_gate, Flipper::Types::Boolean.new(true))
305
331
  expect(subject.get(feature)).to eq(subject.default_config.merge(boolean: "true"))
306
332
  end
333
+
334
+ it 'can import and export' do
335
+ adapter = Flipper::Adapters::Memory.new
336
+ source_flipper = Flipper.new(adapter)
337
+ source_flipper.enable(:stats)
338
+ export = adapter.export
339
+
340
+ # some adapters cannot import so if they return false lets assert it
341
+ # didn't happen
342
+ if subject.import(export)
343
+ expect(flipper[:stats]).to be_enabled
344
+ else
345
+ expect(flipper[:stats]).not_to be_enabled
346
+ end
347
+ end
307
348
  end