flipper 1.0.0 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/FUNDING.yml +1 -0
- data/.github/workflows/ci.yml +7 -3
- data/.github/workflows/examples.yml +27 -5
- data/Changelog.md +42 -0
- data/Gemfile +4 -4
- data/README.md +13 -11
- data/benchmark/typecast_ips.rb +8 -0
- data/docs/images/flipper_cloud.png +0 -0
- data/examples/cloud/backoff_policy.rb +13 -0
- data/examples/cloud/cloud_setup.rb +16 -0
- data/examples/cloud/forked.rb +7 -2
- data/examples/cloud/threaded.rb +15 -18
- data/examples/expressions.rb +213 -0
- data/examples/strict.rb +18 -0
- data/flipper.gemspec +1 -2
- 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/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 +26 -7
- data/lib/flipper/adapters/http/error.rb +1 -1
- data/lib/flipper/adapters/http.rb +18 -13
- data/lib/flipper/adapters/instrumented.rb +0 -4
- data/lib/flipper/adapters/memoizable.rb +14 -19
- data/lib/flipper/adapters/memory.rb +4 -6
- data/lib/flipper/adapters/operation_logger.rb +0 -4
- data/lib/flipper/adapters/poll.rb +1 -3
- data/lib/flipper/adapters/pstore.rb +17 -11
- data/lib/flipper/adapters/read_only.rb +4 -4
- data/lib/flipper/adapters/strict.rb +47 -0
- data/lib/flipper/adapters/sync/feature_synchronizer.rb +10 -1
- data/lib/flipper/adapters/sync.rb +0 -4
- data/lib/flipper/cloud/configuration.rb +121 -52
- data/lib/flipper/cloud/telemetry/backoff_policy.rb +93 -0
- data/lib/flipper/cloud/telemetry/instrumenter.rb +26 -0
- data/lib/flipper/cloud/telemetry/metric.rb +39 -0
- data/lib/flipper/cloud/telemetry/metric_storage.rb +30 -0
- data/lib/flipper/cloud/telemetry/submitter.rb +98 -0
- data/lib/flipper/cloud/telemetry.rb +183 -0
- data/lib/flipper/configuration.rb +25 -4
- data/lib/flipper/dsl.rb +51 -0
- data/lib/flipper/engine.rb +28 -3
- 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 +11 -0
- data/lib/flipper/expressions/any.rb +9 -0
- data/lib/flipper/expressions/boolean.rb +9 -0
- data/lib/flipper/expressions/comparable.rb +13 -0
- data/lib/flipper/expressions/duration.rb +28 -0
- data/lib/flipper/expressions/equal.rb +9 -0
- data/lib/flipper/expressions/greater_than.rb +9 -0
- data/lib/flipper/expressions/greater_than_or_equal_to.rb +9 -0
- data/lib/flipper/expressions/less_than.rb +9 -0
- data/lib/flipper/expressions/less_than_or_equal_to.rb +9 -0
- data/lib/flipper/expressions/not_equal.rb +9 -0
- data/lib/flipper/expressions/now.rb +9 -0
- data/lib/flipper/expressions/number.rb +9 -0
- data/lib/flipper/expressions/percentage.rb +9 -0
- data/lib/flipper/expressions/percentage_of_actors.rb +12 -0
- data/lib/flipper/expressions/property.rb +9 -0
- data/lib/flipper/expressions/random.rb +9 -0
- data/lib/flipper/expressions/string.rb +9 -0
- data/lib/flipper/expressions/time.rb +9 -0
- data/lib/flipper/feature.rb +55 -0
- data/lib/flipper/gate.rb +1 -0
- data/lib/flipper/gate_values.rb +5 -2
- data/lib/flipper/gates/expression.rb +75 -0
- data/lib/flipper/instrumentation/statsd_subscriber.rb +2 -4
- data/lib/flipper/middleware/memoizer.rb +29 -13
- data/lib/flipper/poller.rb +1 -1
- data/lib/flipper/serializers/gzip.rb +24 -0
- data/lib/flipper/serializers/json.rb +19 -0
- data/lib/flipper/spec/shared_adapter_specs.rb +29 -11
- data/lib/flipper/test/shared_adapter_test.rb +24 -5
- data/lib/flipper/typecast.rb +34 -6
- data/lib/flipper/types/percentage.rb +1 -1
- data/lib/flipper/version.rb +1 -1
- data/lib/flipper.rb +38 -1
- data/spec/flipper/adapter_builder_spec.rb +73 -0
- data/spec/flipper/adapter_spec.rb +1 -0
- data/spec/flipper/adapters/http_spec.rb +39 -5
- data/spec/flipper/adapters/memoizable_spec.rb +15 -15
- data/spec/flipper/adapters/read_only_spec.rb +26 -11
- data/spec/flipper/adapters/strict_spec.rb +62 -0
- data/spec/flipper/adapters/sync/feature_synchronizer_spec.rb +27 -0
- data/spec/flipper/cloud/configuration_spec.rb +6 -23
- data/spec/flipper/cloud/telemetry/backoff_policy_spec.rb +108 -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 +156 -0
- data/spec/flipper/cloud_spec.rb +12 -12
- data/spec/flipper/configuration_spec.rb +17 -0
- data/spec/flipper/dsl_spec.rb +39 -0
- data/spec/flipper/engine_spec.rb +108 -7
- 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 +360 -1
- 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/statsd_subscriber_spec.rb +15 -1
- data/spec/flipper/middleware/memoizer_spec.rb +67 -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 +102 -4
- data/spec/flipper_spec.rb +89 -1
- data/spec/spec_helper.rb +5 -0
- data/spec/support/actor_names.yml +1 -0
- data/spec/support/fake_backoff_policy.rb +15 -0
- data/spec/support/spec_helpers.rb +11 -3
- metadata +104 -18
- data/lib/flipper/cloud/instrumenter.rb +0 -48
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b100ee572f33c8486771b22c3f56b52ba810b73838044e86fa28b161c3484859
|
4
|
+
data.tar.gz: cb16f4c82dab816911e1bea6edc3c177caf5f32f5893dd076a2e1fc2f8dab2f5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cb69d068e21b5f695dc8c31e94eaa930e8d48c8de9127a4040ab104ce95da7caec71d433c5ebd5de4089910623ad2d938ee7ec10ea6019d5c10e9c2915040905
|
7
|
+
data.tar.gz: 1bb2e7272037c6e73788bf23f407a64576305d8a2834ef3b351d40b787d607ed765e4689d1d7cb298345803b2856e1db5c0384bc4c5fd3596b98dfbd1075ba58
|
data/.github/FUNDING.yml
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
github: flippercloud
|
data/.github/workflows/ci.yml
CHANGED
@@ -16,10 +16,14 @@ jobs:
|
|
16
16
|
strategy:
|
17
17
|
matrix:
|
18
18
|
ruby: ['2.6', '2.7', '3.0', '3.1', '3.2']
|
19
|
-
rails: ['5.2', '6.0.0', '6.1.0', '7.0.0']
|
19
|
+
rails: ['5.2', '6.0.0', '6.1.0', '7.0.0', '7.1.0']
|
20
20
|
exclude:
|
21
|
+
- ruby: "2.6"
|
22
|
+
rails: "7.1.0"
|
21
23
|
- ruby: "2.6"
|
22
24
|
rails: "7.0.0"
|
25
|
+
- ruby: "2.7"
|
26
|
+
rails: "7.1.0"
|
23
27
|
- ruby: "3.0"
|
24
28
|
rails: "5.2"
|
25
29
|
- ruby: "3.1"
|
@@ -41,11 +45,11 @@ jobs:
|
|
41
45
|
- name: Setup memcached
|
42
46
|
uses: KeisukeYamashita/memcached-actions@v1
|
43
47
|
- name: Start MongoDB
|
44
|
-
uses: supercharge/mongodb-github-action@
|
48
|
+
uses: supercharge/mongodb-github-action@v1.10.0
|
45
49
|
with:
|
46
50
|
mongodb-version: 4.0
|
47
51
|
- name: Check out repository code
|
48
|
-
uses: actions/checkout@
|
52
|
+
uses: actions/checkout@v4
|
49
53
|
- name: Do some action caching
|
50
54
|
uses: actions/cache@v3
|
51
55
|
with:
|
@@ -16,18 +16,27 @@ jobs:
|
|
16
16
|
--health-retries 5
|
17
17
|
strategy:
|
18
18
|
matrix:
|
19
|
-
ruby: ['2.6', '2.7', '3.0', '3.1']
|
20
|
-
rails: ['5.2', '6.0.0', '6.1.0', '7.0.0']
|
19
|
+
ruby: ['2.6', '2.7', '3.0', '3.1', '3.2']
|
20
|
+
rails: ['5.2', '6.0.0', '6.1.0', '7.0.0', '7.1.0']
|
21
21
|
exclude:
|
22
|
+
- ruby: "2.6"
|
23
|
+
rails: "7.1.0"
|
22
24
|
- ruby: "2.6"
|
23
25
|
rails: "7.0.0"
|
26
|
+
- ruby: "2.7"
|
27
|
+
rails: "7.1.0"
|
24
28
|
- ruby: "3.0"
|
25
29
|
rails: "5.2"
|
26
30
|
- ruby: "3.1"
|
27
31
|
rails: "5.2"
|
28
32
|
- ruby: "3.1"
|
29
33
|
rails: "6.0.0"
|
30
|
-
|
34
|
+
- ruby: "3.2"
|
35
|
+
rails: "5.2"
|
36
|
+
- ruby: "3.2"
|
37
|
+
rails: "6.0.0"
|
38
|
+
- ruby: "3.2"
|
39
|
+
rails: "6.1.0"
|
31
40
|
env:
|
32
41
|
SQLITE3_VERSION: 1.4.1
|
33
42
|
REDIS_URL: redis://localhost:6379/0
|
@@ -37,11 +46,11 @@ jobs:
|
|
37
46
|
- name: Setup memcached
|
38
47
|
uses: KeisukeYamashita/memcached-actions@v1
|
39
48
|
- name: Start MongoDB
|
40
|
-
uses: supercharge/mongodb-github-action@
|
49
|
+
uses: supercharge/mongodb-github-action@v1.10.0
|
41
50
|
with:
|
42
51
|
mongodb-version: 4.0
|
43
52
|
- name: Check out repository code
|
44
|
-
uses: actions/checkout@
|
53
|
+
uses: actions/checkout@v4
|
45
54
|
- name: Do some action caching
|
46
55
|
uses: actions/cache@v3
|
47
56
|
with:
|
@@ -59,4 +68,17 @@ jobs:
|
|
59
68
|
- name: Run Examples with Rails ${{ matrix.rails }}
|
60
69
|
env:
|
61
70
|
FLIPPER_CLOUD_TOKEN: ${{ secrets.FLIPPER_CLOUD_TOKEN }}
|
71
|
+
FLIPPER_CLOUD_TOKEN_26_52: ${{ secrets.FLIPPER_CLOUD_TOKEN_26_52 }}
|
72
|
+
FLIPPER_CLOUD_TOKEN_26_60: ${{ secrets.FLIPPER_CLOUD_TOKEN_26_60 }}
|
73
|
+
FLIPPER_CLOUD_TOKEN_26_61: ${{ secrets.FLIPPER_CLOUD_TOKEN_26_61 }}
|
74
|
+
FLIPPER_CLOUD_TOKEN_27_52: ${{ secrets.FLIPPER_CLOUD_TOKEN_27_52 }}
|
75
|
+
FLIPPER_CLOUD_TOKEN_27_60: ${{ secrets.FLIPPER_CLOUD_TOKEN_27_60 }}
|
76
|
+
FLIPPER_CLOUD_TOKEN_27_61: ${{ secrets.FLIPPER_CLOUD_TOKEN_27_61 }}
|
77
|
+
FLIPPER_CLOUD_TOKEN_27_70: ${{ secrets.FLIPPER_CLOUD_TOKEN_27_70 }}
|
78
|
+
FLIPPER_CLOUD_TOKEN_30_60: ${{ secrets.FLIPPER_CLOUD_TOKEN_30_60 }}
|
79
|
+
FLIPPER_CLOUD_TOKEN_30_61: ${{ secrets.FLIPPER_CLOUD_TOKEN_30_61 }}
|
80
|
+
FLIPPER_CLOUD_TOKEN_30_70: ${{ secrets.FLIPPER_CLOUD_TOKEN_30_70 }}
|
81
|
+
FLIPPER_CLOUD_TOKEN_31_61: ${{ secrets.FLIPPER_CLOUD_TOKEN_31_61 }}
|
82
|
+
FLIPPER_CLOUD_TOKEN_31_70: ${{ secrets.FLIPPER_CLOUD_TOKEN_31_70 }}
|
83
|
+
RAILS_VERSION: ${{ matrix.rails }}
|
62
84
|
run: script/examples
|
data/Changelog.md
CHANGED
@@ -2,6 +2,48 @@
|
|
2
2
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
4
4
|
|
5
|
+
## Unreleased
|
6
|
+
|
7
|
+
### Additions/Changes
|
8
|
+
|
9
|
+
* Better read-only mode for UI (https://github.com/flippercloud/flipper/pull/772).
|
10
|
+
* Allow configuring preload via a block similar to memoize (https://github.com/flippercloud/flipper/pull/771).
|
11
|
+
```ruby
|
12
|
+
Rails.application.configure do
|
13
|
+
# don't preload features for /assets/* but do for everything else
|
14
|
+
config.flipper.preload = ->(request) { !request.path.start_with?('/assets') }
|
15
|
+
end
|
16
|
+
```
|
17
|
+
* Added `strict` configuration to warn when accessing a feature that doesn't exist (https://github.com/flippercloud/flipper/pull/760, https://github.com/flippercloud/flipper/pull/763)
|
18
|
+
```ruby
|
19
|
+
Rails.application.configure do
|
20
|
+
# Setting to `true` or `:raise` will raise error when a feature doesn't exist.
|
21
|
+
# Use `:warn` to log a warning instead.
|
22
|
+
config.flipper.strict = !Rails.env.production?
|
23
|
+
end
|
24
|
+
```
|
25
|
+
* Handle deprecation of Rack::File in Rack 3.1 (https://github.com/flippercloud/flipper/pull/773).
|
26
|
+
* Human readable actor names in flipper-ui (https://github.com/flippercloud/flipper/pull/737).
|
27
|
+
* Expressions are now available and considered "alpha". They are not yet documented, but you can see examples in [examples/expressions.rb](examples/expressions.rb). (https://github.com/flippercloud/flipper/pull/692)
|
28
|
+
* Allow head requests to api (https://github.com/flippercloud/flipper/pull/759)
|
29
|
+
* Cloud Telemetry alpha (https://github.com/flippercloud/flipper/pull/775).
|
30
|
+
* Easier statsd setup (https://github.com/flippercloud/flipper/pull/779).
|
31
|
+
```ruby
|
32
|
+
# You can now just do this in your configure block and it'll require the files and configure the client.
|
33
|
+
Flipper.configure do |conf|
|
34
|
+
conf.statsd = my_client
|
35
|
+
end
|
36
|
+
```
|
37
|
+
* Load cloud secrets from Rails credentials (https://github.com/flippercloud/flipper/pull/782)
|
38
|
+
```bash
|
39
|
+
$ rails credentials:edit -e development
|
40
|
+
```
|
41
|
+
```yaml
|
42
|
+
flipper:
|
43
|
+
cloud_token: <your-cloud-token>
|
44
|
+
cloud_sync_secret: <your-webhook-secret>
|
45
|
+
```
|
46
|
+
|
5
47
|
## 1.0.0
|
6
48
|
|
7
49
|
### Additions/Changes
|
data/Gemfile
CHANGED
@@ -27,8 +27,8 @@ gem 'flamegraph'
|
|
27
27
|
gem 'climate_control'
|
28
28
|
|
29
29
|
group(:guard) do
|
30
|
-
gem 'guard'
|
31
|
-
gem 'guard-rspec'
|
32
|
-
gem 'guard-bundler'
|
33
|
-
gem 'rb-fsevent'
|
30
|
+
gem 'guard'
|
31
|
+
gem 'guard-rspec'
|
32
|
+
gem 'guard-bundler'
|
33
|
+
gem 'rb-fsevent'
|
34
34
|
end
|
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
[![Flipper Mark](docs/images/banner.jpg)](https://www.flippercloud.io)
|
2
2
|
|
3
|
-
[Website](https://flippercloud.io) | [Documentation](https://flippercloud.io/docs) | [Examples](examples) | [Twitter](https://twitter.com/flipper_cloud)
|
3
|
+
[Website](https://flippercloud.io?utm_source=oss&utm_medium=readme&utm_campaign=website_link) | [Documentation](https://flippercloud.io/docs?utm_source=oss&utm_medium=readme&utm_campaign=docs_link) | [Examples](examples) | [Twitter](https://twitter.com/flipper_cloud) | [Ruby.social](https://ruby.social/@flipper)
|
4
4
|
|
5
5
|
# Flipper
|
6
6
|
|
@@ -35,7 +35,7 @@ Or install it yourself with:
|
|
35
35
|
|
36
36
|
## Subscribe & Ship
|
37
37
|
|
38
|
-
[💌 Subscribe](https://
|
38
|
+
[💌 Subscribe](https://blog.flippercloud.io/#/portal/signup) - we'll send you short and sweet emails when we release new versions ([examples](https://blog.flippercloud.io/tag/releases/)).
|
39
39
|
|
40
40
|
## Getting Started
|
41
41
|
|
@@ -43,7 +43,7 @@ Use `Flipper#enabled?` in your app to check if a feature is enabled.
|
|
43
43
|
|
44
44
|
```ruby
|
45
45
|
# check if search is enabled
|
46
|
-
if Flipper.enabled?
|
46
|
+
if Flipper.enabled?(:search, current_user)
|
47
47
|
puts 'Search away!'
|
48
48
|
else
|
49
49
|
puts 'No search for you!'
|
@@ -66,24 +66,26 @@ Flipper.enable_group :search, :admin
|
|
66
66
|
Flipper.enable_percentage_of_actors :search, 2
|
67
67
|
```
|
68
68
|
|
69
|
-
Read more about [getting started with Flipper](https://flippercloud.io/docs) and [enabling features](https://flippercloud.io/docs/features).
|
69
|
+
Read more about [getting started with Flipper](https://flippercloud.io/docs?utm_source=oss&utm_medium=readme&utm_campaign=getting_started) and [enabling features](https://flippercloud.io/docs/features?utm_source=oss&utm_medium=readme&utm_campaign=enabling_features).
|
70
70
|
|
71
71
|
## Flipper Cloud
|
72
72
|
|
73
|
-
Like Flipper and want more? Check out [Flipper Cloud](https://www.flippercloud.io), which comes with:
|
73
|
+
Like Flipper and want more? Check out [Flipper Cloud](https://www.flippercloud.io?utm_source=oss&utm_medium=readme&utm_campaign=check_out), which comes with:
|
74
74
|
|
75
|
-
* **
|
76
|
-
* **
|
77
|
-
* **
|
78
|
-
* **personal environments** — no more rake scripts or manual enable/disable to get your laptop to look like production. Every developer gets a personal environment that inherits from production that they can override as they please ([read more](https://www.johnnunemaker.com/flipper-cloud-environments/)).
|
79
|
-
* **no maintenance** — we'll keep the lights on for you. We also have handy webhooks for keeping your app in sync with Cloud, so **our availability won't affect yours**. All your feature flag reads are local to your app.
|
75
|
+
* **multiple environments** — production, staging, per continent, whatever you need. Every environment inherits from production by default and every project comes with a [project overview page](https://blog.flippercloud.io/project-overview/) that shows each feature and its status in each environment.
|
76
|
+
* **personal environments** — everyone on your team gets a personal environment (that inherits from production) which they can modify however they want without stepping on anyone else's toes.
|
77
|
+
* **permissions** — grant access to everyone in your organization or lockdown each project to particular people. You can even limit access to a particular environment (like production) to specific people.
|
80
78
|
* **audit history** — every feature change and who made it.
|
81
79
|
* **rollbacks** — enable or disable a feature accidentally? No problem. You can roll back to any point in the audit history with a single click.
|
80
|
+
* **maintenance** — we'll keep the lights on for you. We also have handy webhooks and background polling for keeping your app in sync with Cloud, so **our availability won't affect yours**. All your feature flag reads are local to your app.
|
81
|
+
* **everything in one place** — no need to bounce around from different application UIs or IRB consoles.
|
82
82
|
|
83
|
-
[![Flipper Cloud Screenshot](docs/images/flipper_cloud.png)](https://www.flippercloud.io)
|
83
|
+
[![Flipper Cloud Screenshot](docs/images/flipper_cloud.png)](https://www.flippercloud.io?utm_source=oss&utm_medium=readme&utm_campaign=screenshot)
|
84
84
|
|
85
85
|
Cloud is super simple to integrate with Rails ([demo app](https://github.com/fewerandfaster/flipper-rails-demo)), Sinatra or any other framework.
|
86
86
|
|
87
|
+
We also have a [free plan](https://www.flippercloud.io?utm_source=oss&utm_medium=readme&utm_campaign=free_plan) that you can use forever.
|
88
|
+
|
87
89
|
## Contributing
|
88
90
|
|
89
91
|
1. Fork it
|
data/benchmark/typecast_ips.rb
CHANGED
@@ -16,4 +16,12 @@ Benchmark.ips do |x|
|
|
16
16
|
x.report("Typecast.to_float '1'") { Flipper::Typecast.to_float('1'.freeze) }
|
17
17
|
x.report("Typecast.to_float 1.01") { Flipper::Typecast.to_float(1) }
|
18
18
|
x.report("Typecast.to_float '1.01'") { Flipper::Typecast.to_float('1'.freeze) }
|
19
|
+
|
20
|
+
x.report("Typecast.to_number 1") { Flipper::Typecast.to_number(1) }
|
21
|
+
x.report("Typecast.to_number 1.1") { Flipper::Typecast.to_number(1.1) }
|
22
|
+
x.report("Typecast.to_number '1'") { Flipper::Typecast.to_number('1'.freeze) }
|
23
|
+
x.report("Typecast.to_number '1.1'") { Flipper::Typecast.to_number('1.1'.freeze) }
|
24
|
+
x.report("Typecast.to_number nil") { Flipper::Typecast.to_number(nil) }
|
25
|
+
time = Time.now
|
26
|
+
x.report("Typecast.to_number Time.now") { Flipper::Typecast.to_number(time) }
|
19
27
|
end
|
Binary file
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# Just a simple example that shows how the backoff policy works.
|
2
|
+
require 'bundler/setup'
|
3
|
+
require 'flipper/cloud/telemetry/backoff_policy'
|
4
|
+
|
5
|
+
intervals = []
|
6
|
+
policy = Flipper::Cloud::Telemetry::BackoffPolicy.new
|
7
|
+
|
8
|
+
10.times do |n|
|
9
|
+
intervals << policy.next_interval
|
10
|
+
end
|
11
|
+
|
12
|
+
pp intervals.map { |i| i.round(2) }
|
13
|
+
puts "Total: #{intervals.sum.round(2)}ms (#{(intervals.sum/1_000.0).round(2)} sec)"
|
@@ -2,3 +2,19 @@ if ENV["FLIPPER_CLOUD_TOKEN"].nil? || ENV["FLIPPER_CLOUD_TOKEN"].empty?
|
|
2
2
|
warn "FLIPPER_CLOUD_TOKEN missing so skipping cloud example."
|
3
3
|
exit
|
4
4
|
end
|
5
|
+
|
6
|
+
matrix_key = if ENV["CI"]
|
7
|
+
suffix_rails = ENV["RAILS_VERSION"].split(".").take(2).join
|
8
|
+
suffix_ruby = RUBY_VERSION.split(".").take(2).join
|
9
|
+
"FLIPPER_CLOUD_TOKEN_#{suffix_ruby}_#{suffix_rails}"
|
10
|
+
else
|
11
|
+
"FLIPPER_CLOUD_TOKEN"
|
12
|
+
end
|
13
|
+
|
14
|
+
if matrix_token = ENV[matrix_key]
|
15
|
+
puts "Using #{matrix_key} for FLIPPER_CLOUD_TOKEN"
|
16
|
+
ENV["FLIPPER_CLOUD_TOKEN"] = matrix_token
|
17
|
+
else
|
18
|
+
warn "Missing #{matrix_key}. Go create an environment in flipper cloud and set #{matrix_key} to the adapter token for that environment in github actions secrets."
|
19
|
+
exit 1
|
20
|
+
end
|
data/examples/cloud/forked.rb
CHANGED
@@ -5,11 +5,16 @@ require_relative "./cloud_setup"
|
|
5
5
|
require 'bundler/setup'
|
6
6
|
require 'flipper/cloud'
|
7
7
|
|
8
|
-
|
8
|
+
puts Process.pid
|
9
|
+
|
10
|
+
# Make a call in the parent process so we can detect forking.
|
11
|
+
Flipper.enabled?(:stats)
|
12
|
+
|
13
|
+
pids = 2.times.map do |n|
|
9
14
|
fork {
|
10
15
|
# Check every second to see if the feature is enabled
|
11
16
|
threads = []
|
12
|
-
|
17
|
+
2.times do
|
13
18
|
threads << Thread.new do
|
14
19
|
loop do
|
15
20
|
sleep rand
|
data/examples/cloud/threaded.rb
CHANGED
@@ -4,33 +4,30 @@
|
|
4
4
|
require_relative "./cloud_setup"
|
5
5
|
require 'bundler/setup'
|
6
6
|
require 'flipper/cloud'
|
7
|
-
require "active_support/notifications"
|
8
|
-
require "active_support/isolated_execution_state"
|
9
7
|
|
10
|
-
|
11
|
-
p args: args
|
12
|
-
end
|
8
|
+
puts Process.pid
|
13
9
|
|
14
10
|
Flipper.configure do |config|
|
15
11
|
config.default {
|
16
|
-
Flipper::Cloud.new(
|
12
|
+
Flipper::Cloud.new(
|
13
|
+
local_adapter: config.adapter,
|
14
|
+
debug_output: STDOUT,
|
15
|
+
)
|
17
16
|
}
|
18
17
|
end
|
19
18
|
|
19
|
+
# You might want to do this at some point to see different results:
|
20
|
+
# Flipper.enable(:search)
|
21
|
+
# Flipper.disable(:stats)
|
22
|
+
|
20
23
|
# Check every second to see if the feature is enabled
|
21
|
-
|
22
|
-
|
23
|
-
threads << Thread.new do
|
24
|
+
5.times.map { |i|
|
25
|
+
Thread.new {
|
24
26
|
loop do
|
25
27
|
sleep rand
|
26
28
|
|
27
|
-
|
28
|
-
|
29
|
-
else
|
30
|
-
puts "#{Time.now.to_i} Disabled!"
|
31
|
-
end
|
29
|
+
Flipper.enabled?(:stats)
|
30
|
+
Flipper.enabled?(:search)
|
32
31
|
end
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
threads.map(&:join)
|
32
|
+
}
|
33
|
+
}.each(&:join)
|
@@ -0,0 +1,213 @@
|
|
1
|
+
require 'bundler/setup'
|
2
|
+
require 'flipper'
|
3
|
+
|
4
|
+
def assert(value)
|
5
|
+
if value
|
6
|
+
p value
|
7
|
+
else
|
8
|
+
puts "Expected true but was #{value}. Please correct."
|
9
|
+
exit 1
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def refute(value)
|
14
|
+
if value
|
15
|
+
puts "Expected false but was #{value}. Please correct."
|
16
|
+
exit 1
|
17
|
+
else
|
18
|
+
p value
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def reset
|
23
|
+
Flipper.disable_expression :something
|
24
|
+
end
|
25
|
+
|
26
|
+
class User < Struct.new(:id, :flipper_properties)
|
27
|
+
include Flipper::Identifier
|
28
|
+
end
|
29
|
+
|
30
|
+
class Org < Struct.new(:id, :flipper_properties)
|
31
|
+
include Flipper::Identifier
|
32
|
+
end
|
33
|
+
|
34
|
+
NOW = Time.now.to_i
|
35
|
+
DAY = 60 * 60 * 24
|
36
|
+
|
37
|
+
org = Org.new(1, {
|
38
|
+
"type" => "Org",
|
39
|
+
"id" => 1,
|
40
|
+
"now" => NOW,
|
41
|
+
})
|
42
|
+
|
43
|
+
user = User.new(1, {
|
44
|
+
"type" => "User",
|
45
|
+
"id" => 1,
|
46
|
+
"plan" => "basic",
|
47
|
+
"age" => 39,
|
48
|
+
"team_user" => true,
|
49
|
+
"now" => NOW,
|
50
|
+
})
|
51
|
+
|
52
|
+
admin_user = User.new(2, {
|
53
|
+
"type" => "User",
|
54
|
+
"id" => 2,
|
55
|
+
"admin" => true,
|
56
|
+
"team_user" => true,
|
57
|
+
"now" => NOW,
|
58
|
+
})
|
59
|
+
|
60
|
+
other_user = User.new(3, {
|
61
|
+
"type" => "User",
|
62
|
+
"id" => 3,
|
63
|
+
"plan" => "plus",
|
64
|
+
"age" => 18,
|
65
|
+
"org_admin" => true,
|
66
|
+
"now" => NOW - DAY,
|
67
|
+
})
|
68
|
+
|
69
|
+
age_expression = Flipper.property(:age).gte(21)
|
70
|
+
plan_expression = Flipper.property(:plan).eq("basic")
|
71
|
+
admin_expression = Flipper.property(:admin).eq(true)
|
72
|
+
|
73
|
+
puts "Single Expression"
|
74
|
+
refute Flipper.enabled?(:something, user)
|
75
|
+
|
76
|
+
puts "Enabling single expression"
|
77
|
+
Flipper.enable :something, plan_expression
|
78
|
+
assert Flipper.enabled?(:something, user)
|
79
|
+
refute Flipper.enabled?(:something, admin_user)
|
80
|
+
refute Flipper.enabled?(:something, other_user)
|
81
|
+
|
82
|
+
puts "Disabling single expression"
|
83
|
+
reset
|
84
|
+
refute Flipper.enabled?(:something, user)
|
85
|
+
|
86
|
+
puts "\n\nAny Expression"
|
87
|
+
any_expression = Flipper.any(plan_expression, age_expression)
|
88
|
+
refute Flipper.enabled?(:something, user)
|
89
|
+
|
90
|
+
puts "Enabling any expression"
|
91
|
+
Flipper.enable :something, any_expression
|
92
|
+
assert Flipper.enabled?(:something, user)
|
93
|
+
refute Flipper.enabled?(:something, admin_user)
|
94
|
+
refute Flipper.enabled?(:something, other_user)
|
95
|
+
|
96
|
+
puts "Disabling any expression"
|
97
|
+
reset
|
98
|
+
refute Flipper.enabled?(:something, user)
|
99
|
+
|
100
|
+
puts "\n\nAll Expression"
|
101
|
+
all_expression = Flipper.all(plan_expression, age_expression)
|
102
|
+
refute Flipper.enabled?(:something, user)
|
103
|
+
|
104
|
+
puts "Enabling all expression"
|
105
|
+
Flipper.enable :something, all_expression
|
106
|
+
assert Flipper.enabled?(:something, user)
|
107
|
+
refute Flipper.enabled?(:something, admin_user)
|
108
|
+
refute Flipper.enabled?(:something, other_user)
|
109
|
+
|
110
|
+
puts "Disabling all expression"
|
111
|
+
reset
|
112
|
+
refute Flipper.enabled?(:something, user)
|
113
|
+
|
114
|
+
puts "\n\nNested Expression"
|
115
|
+
nested_expression = Flipper.any(admin_expression, all_expression)
|
116
|
+
refute Flipper.enabled?(:something, user)
|
117
|
+
refute Flipper.enabled?(:something, admin_user)
|
118
|
+
refute Flipper.enabled?(:something, other_user)
|
119
|
+
|
120
|
+
puts "Enabling nested expression"
|
121
|
+
Flipper.enable :something, nested_expression
|
122
|
+
assert Flipper.enabled?(:something, user)
|
123
|
+
assert Flipper.enabled?(:something, admin_user)
|
124
|
+
refute Flipper.enabled?(:something, other_user)
|
125
|
+
|
126
|
+
puts "Disabling nested expression"
|
127
|
+
reset
|
128
|
+
refute Flipper.enabled?(:something, user)
|
129
|
+
refute Flipper.enabled?(:something, admin_user)
|
130
|
+
refute Flipper.enabled?(:something, other_user)
|
131
|
+
|
132
|
+
puts "\n\nBoolean Expression"
|
133
|
+
boolean_expression = Flipper.boolean(true)
|
134
|
+
Flipper.enable :something, boolean_expression
|
135
|
+
assert Flipper.enabled?(:something)
|
136
|
+
assert Flipper.enabled?(:something, user)
|
137
|
+
reset
|
138
|
+
|
139
|
+
puts "\n\nSet of Actors Expression"
|
140
|
+
set_of_actors_expression = Flipper.any(
|
141
|
+
Flipper.property(:flipper_id).eq("User;1"),
|
142
|
+
Flipper.property(:flipper_id).eq("User;3"),
|
143
|
+
)
|
144
|
+
Flipper.enable :something, set_of_actors_expression
|
145
|
+
assert Flipper.enabled?(:something, user)
|
146
|
+
assert Flipper.enabled?(:something, other_user)
|
147
|
+
refute Flipper.enabled?(:something, admin_user)
|
148
|
+
reset
|
149
|
+
|
150
|
+
puts "\n\n% of Actors Expression"
|
151
|
+
percentage_of_actors = Flipper.property(:flipper_id).percentage_of_actors(30)
|
152
|
+
Flipper.enable :something, percentage_of_actors
|
153
|
+
refute Flipper.enabled?(:something, user)
|
154
|
+
refute Flipper.enabled?(:something, other_user)
|
155
|
+
assert Flipper.enabled?(:something, admin_user)
|
156
|
+
reset
|
157
|
+
|
158
|
+
puts "\n\n% of Actors Per Type Expression"
|
159
|
+
percentage_of_actors_per_type = Flipper.any(
|
160
|
+
Flipper.all(
|
161
|
+
Flipper.property(:type).eq("User"),
|
162
|
+
Flipper.property(:flipper_id).percentage_of_actors(40),
|
163
|
+
),
|
164
|
+
Flipper.all(
|
165
|
+
Flipper.property(:type).eq("Org"),
|
166
|
+
Flipper.property(:flipper_id).percentage_of_actors(10),
|
167
|
+
)
|
168
|
+
)
|
169
|
+
Flipper.enable :something, percentage_of_actors_per_type
|
170
|
+
refute Flipper.enabled?(:something, user) # not in the 40% enabled for Users
|
171
|
+
assert Flipper.enabled?(:something, other_user)
|
172
|
+
assert Flipper.enabled?(:something, admin_user)
|
173
|
+
refute Flipper.enabled?(:something, org) # not in the 10% of enabled for Orgs
|
174
|
+
reset
|
175
|
+
|
176
|
+
puts "\n\nPercentage of Time Expression"
|
177
|
+
percentage_of_time_expression = Flipper.random(100).lt(50)
|
178
|
+
Flipper.enable :something, percentage_of_time_expression
|
179
|
+
results = (1..10000).map { |n| Flipper.enabled?(:something, user) }
|
180
|
+
enabled, disabled = results.partition { |r| r }
|
181
|
+
p enabled: enabled.size
|
182
|
+
p disabled: disabled.size
|
183
|
+
assert (4_700..5_200).include?(enabled.size)
|
184
|
+
assert (4_700..5_200).include?(disabled.size)
|
185
|
+
reset
|
186
|
+
|
187
|
+
puts "\n\nChanging single expression to all expression"
|
188
|
+
Flipper.enable :something, plan_expression
|
189
|
+
Flipper.enable :something, Flipper.expression(:something).all.add(age_expression)
|
190
|
+
assert Flipper.enabled?(:something, user)
|
191
|
+
refute Flipper.enabled?(:something, admin_user)
|
192
|
+
refute Flipper.enabled?(:something, other_user)
|
193
|
+
|
194
|
+
puts "\n\nChanging single expression to any expression"
|
195
|
+
Flipper.enable :something, plan_expression
|
196
|
+
Flipper.enable :something, Flipper.expression(:something).any.add(age_expression, admin_expression)
|
197
|
+
assert Flipper.enabled?(:something, user)
|
198
|
+
assert Flipper.enabled?(:something, admin_user)
|
199
|
+
refute Flipper.enabled?(:something, other_user)
|
200
|
+
|
201
|
+
puts "\n\nChanging single expression to any expression by adding to condition"
|
202
|
+
Flipper.enable :something, plan_expression
|
203
|
+
Flipper.enable :something, Flipper.expression(:something).add(admin_expression)
|
204
|
+
assert Flipper.enabled?(:something, user)
|
205
|
+
assert Flipper.enabled?(:something, admin_user)
|
206
|
+
refute Flipper.enabled?(:something, other_user)
|
207
|
+
|
208
|
+
puts "\n\nEnabling based on time"
|
209
|
+
scheduled_time_expression = Flipper.property(:now).gte(NOW)
|
210
|
+
Flipper.enable :something, scheduled_time_expression
|
211
|
+
assert Flipper.enabled?(:something, user)
|
212
|
+
assert Flipper.enabled?(:something, admin_user)
|
213
|
+
refute Flipper.enabled?(:something, other_user)
|
data/examples/strict.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'bundler/setup'
|
2
|
+
require 'flipper'
|
3
|
+
|
4
|
+
adapter = Flipper::Adapters::Strict.new(Flipper::Adapters::Memory.new)
|
5
|
+
flipper = Flipper.new(adapter)
|
6
|
+
|
7
|
+
begin
|
8
|
+
puts "Checking :unknown_feature, which should raise an error."
|
9
|
+
flipper.enabled?(:unknown_feature)
|
10
|
+
warn "An error was not raised, but should have been"
|
11
|
+
exit 1
|
12
|
+
rescue Flipper::Adapters::Strict::NotFound => exception
|
13
|
+
puts "Ok, the exepcted error was raised: #{exception.message}"
|
14
|
+
end
|
15
|
+
|
16
|
+
puts "Flipper.add(:new_feature)"
|
17
|
+
flipper.add(:new_feature)
|
18
|
+
puts "Flipper.enabled?(:new_feature) => #{flipper.enabled?(:new_feature)}"
|
data/flipper.gemspec
CHANGED
@@ -23,7 +23,7 @@ ignored_test_files.flatten!.uniq!
|
|
23
23
|
Gem::Specification.new do |gem|
|
24
24
|
gem.authors = ['John Nunemaker']
|
25
25
|
gem.email = 'support@flippercloud.io'
|
26
|
-
gem.summary = '
|
26
|
+
gem.summary = 'Beautiful, performant feature flags for Ruby.'
|
27
27
|
gem.homepage = 'https://www.flippercloud.io/docs'
|
28
28
|
gem.license = 'MIT'
|
29
29
|
|
@@ -35,5 +35,4 @@ Gem::Specification.new do |gem|
|
|
35
35
|
gem.metadata = Flipper::METADATA
|
36
36
|
|
37
37
|
gem.add_dependency 'concurrent-ruby', '< 2'
|
38
|
-
gem.add_dependency 'brow', '~> 0.4.1'
|
39
38
|
end
|
data/lib/flipper/actor.rb
CHANGED
@@ -2,14 +2,17 @@
|
|
2
2
|
# to Flipper::Feature#enabled?.
|
3
3
|
module Flipper
|
4
4
|
class Actor
|
5
|
-
attr_reader :flipper_id
|
5
|
+
attr_reader :flipper_id, :flipper_properties
|
6
6
|
|
7
|
-
def initialize(flipper_id)
|
7
|
+
def initialize(flipper_id, flipper_properties = {})
|
8
8
|
@flipper_id = flipper_id
|
9
|
+
@flipper_properties = flipper_properties
|
9
10
|
end
|
10
11
|
|
11
12
|
def eql?(other)
|
12
|
-
self.class.eql?(other.class) &&
|
13
|
+
self.class.eql?(other.class) &&
|
14
|
+
@flipper_id == other.flipper_id &&
|
15
|
+
@flipper_properties == other.flipper_properties
|
13
16
|
end
|
14
17
|
alias_method :==, :eql?
|
15
18
|
|
data/lib/flipper/adapter.rb
CHANGED
@@ -12,6 +12,7 @@ module Flipper
|
|
12
12
|
boolean: nil,
|
13
13
|
groups: Set.new,
|
14
14
|
actors: Set.new,
|
15
|
+
expression: nil,
|
15
16
|
percentage_of_actors: nil,
|
16
17
|
percentage_of_time: nil,
|
17
18
|
}
|
@@ -23,6 +24,10 @@ module Flipper
|
|
23
24
|
end
|
24
25
|
end
|
25
26
|
|
27
|
+
def read_only?
|
28
|
+
false
|
29
|
+
end
|
30
|
+
|
26
31
|
# Public: Get all features and gate values in one call. Defaults to one call
|
27
32
|
# to features and another to get_multi. Feel free to override per adapter to
|
28
33
|
# make this more efficient.
|
@@ -63,6 +68,11 @@ module Flipper
|
|
63
68
|
def default_config
|
64
69
|
self.class.default_config
|
65
70
|
end
|
71
|
+
|
72
|
+
# Public: default name of the adapter
|
73
|
+
def name
|
74
|
+
@name ||= self.class.name.split('::').last.split(/(?=[A-Z])/).join('_').downcase.to_sym
|
75
|
+
end
|
66
76
|
end
|
67
77
|
end
|
68
78
|
|