rollout 2.5.0 → 2.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.circleci/config.yml +56 -32
- data/README.md +12 -1
- data/lib/rollout/feature.rb +18 -9
- data/lib/rollout/logging.rb +1 -0
- data/lib/rollout/version.rb +1 -1
- data/lib/rollout.rb +12 -6
- data/rollout.gemspec +2 -2
- data/spec/rollout/feature_spec.rb +54 -0
- data/spec/rollout/logging_spec.rb +1 -1
- data/spec/rollout_spec.rb +183 -236
- data/spec/spec_helper.rb +2 -2
- metadata +14 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 58914b07d5596f1db7ce88d65de6e5286519e4b527377b40b7c56ad3ea799323
|
4
|
+
data.tar.gz: 4200f92c9784c3d3f6d275a4308714c7b55d35154c97ef01a89a04166e69c041
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4e3fcddc89ff58fe0d652eed810767eba1a03a4a23312f9bceb174e63348b07fd47592a1504316c3e42169610f24fc78580b846b5649200159dcef6c6ffdbdb7
|
7
|
+
data.tar.gz: 02cc8771d302d5e0b33fb370d6a9ed063f5b7d05406f4ec57689f2196921e5ec4e1382b1a1fd175d7804e316f98538d0e2809e6ad7fe25847e06466dd12cafbc
|
data/.circleci/config.yml
CHANGED
@@ -3,58 +3,64 @@ version: 2.1
|
|
3
3
|
workflows:
|
4
4
|
main:
|
5
5
|
jobs:
|
6
|
+
- ruby33
|
7
|
+
- ruby31
|
8
|
+
- ruby32
|
9
|
+
- ruby30
|
6
10
|
- ruby27
|
7
11
|
- ruby26
|
8
12
|
- ruby25
|
9
13
|
- ruby24
|
10
|
-
- ruby23
|
11
14
|
|
12
15
|
executors:
|
16
|
+
ruby33:
|
17
|
+
docker:
|
18
|
+
- image: cimg/ruby:3.3
|
19
|
+
- image: cimg/redis:7.2
|
20
|
+
ruby32:
|
21
|
+
docker:
|
22
|
+
- image: cimg/ruby:3.2
|
23
|
+
- image: cimg/redis:7.2
|
24
|
+
ruby31:
|
25
|
+
docker:
|
26
|
+
- image: cimg/ruby:3.1
|
27
|
+
- image: cimg/redis:7.2
|
28
|
+
ruby30:
|
29
|
+
docker:
|
30
|
+
- image: cimg/ruby:3.0
|
31
|
+
- image: cimg/redis:7.2
|
13
32
|
ruby27:
|
14
33
|
docker:
|
15
|
-
- image:
|
16
|
-
- image:
|
34
|
+
- image: cimg/ruby:2.7
|
35
|
+
- image: cimg/redis:7.2
|
17
36
|
ruby26:
|
18
37
|
docker:
|
19
|
-
- image:
|
20
|
-
- image:
|
38
|
+
- image: cimg/ruby:2.7
|
39
|
+
- image: cimg/redis:7.2
|
21
40
|
ruby25:
|
22
41
|
docker:
|
23
|
-
- image:
|
24
|
-
- image:
|
42
|
+
- image: cimg/ruby:2.7
|
43
|
+
- image: cimg/redis:7.2
|
25
44
|
ruby24:
|
26
45
|
docker:
|
27
|
-
- image:
|
28
|
-
- image:
|
29
|
-
ruby23:
|
30
|
-
docker:
|
31
|
-
- image: circleci/ruby:2.3
|
32
|
-
- image: circleci/redis:alpine
|
46
|
+
- image: cimg/ruby:2.4
|
47
|
+
- image: cimg/redis:7.2
|
33
48
|
|
34
49
|
commands:
|
35
50
|
test:
|
36
51
|
steps:
|
37
|
-
- restore_cache:
|
38
|
-
keys:
|
39
|
-
- bundler-{{ checksum "Gemfile.lock" }}
|
40
|
-
|
41
52
|
- run:
|
42
53
|
name: Bundle Install
|
43
54
|
command: bundle check --path vendor/bundle || bundle install
|
44
55
|
|
45
|
-
- save_cache:
|
46
|
-
key: bundler-{{ checksum "Gemfile.lock" }}
|
47
|
-
paths:
|
48
|
-
- vendor/bundle
|
49
|
-
|
50
56
|
- run:
|
51
57
|
name: Run rspec
|
52
58
|
command: |
|
53
59
|
bundle exec rspec --format documentation --format RspecJunitFormatter --out test_results/rspec.xml
|
54
60
|
|
55
61
|
jobs:
|
56
|
-
|
57
|
-
executor:
|
62
|
+
ruby33:
|
63
|
+
executor: ruby33
|
58
64
|
steps:
|
59
65
|
- checkout
|
60
66
|
- test
|
@@ -70,26 +76,44 @@ jobs:
|
|
70
76
|
- store_test_results:
|
71
77
|
path: test_results
|
72
78
|
|
73
|
-
|
74
|
-
executor:
|
79
|
+
ruby32:
|
80
|
+
executor: ruby30
|
75
81
|
steps:
|
76
82
|
- checkout
|
77
83
|
- test
|
78
84
|
|
79
|
-
|
80
|
-
executor:
|
85
|
+
ruby31:
|
86
|
+
executor: ruby30
|
81
87
|
steps:
|
82
88
|
- checkout
|
83
89
|
- test
|
84
90
|
|
85
|
-
|
86
|
-
executor:
|
91
|
+
ruby30:
|
92
|
+
executor: ruby30
|
87
93
|
steps:
|
88
94
|
- checkout
|
89
95
|
- test
|
90
96
|
|
91
|
-
|
92
|
-
executor:
|
97
|
+
ruby27:
|
98
|
+
executor: ruby27
|
99
|
+
steps:
|
100
|
+
- checkout
|
101
|
+
- test
|
102
|
+
|
103
|
+
ruby26:
|
104
|
+
executor: ruby27
|
105
|
+
steps:
|
106
|
+
- checkout
|
107
|
+
- test
|
108
|
+
|
109
|
+
ruby25:
|
110
|
+
executor: ruby27
|
111
|
+
steps:
|
112
|
+
- checkout
|
113
|
+
- test
|
114
|
+
|
115
|
+
ruby24:
|
116
|
+
executor: ruby24
|
93
117
|
steps:
|
94
118
|
- checkout
|
95
119
|
- test
|
data/README.md
CHANGED
@@ -28,7 +28,7 @@ or even simpler
|
|
28
28
|
|
29
29
|
```ruby
|
30
30
|
require 'redis'
|
31
|
-
$rollout = Rollout.new(
|
31
|
+
$rollout = Rollout.new($redis) # Will use REDIS_URL env var or default redis url
|
32
32
|
```
|
33
33
|
|
34
34
|
|
@@ -161,6 +161,15 @@ deactivate them automatically when a threshold is reached to prevent service
|
|
161
161
|
failures from cascading. See https://github.com/jamesgolick/degrade for the
|
162
162
|
failure detection code.
|
163
163
|
|
164
|
+
## Check Rollout Feature
|
165
|
+
|
166
|
+
You can inspect the state of your feature using:
|
167
|
+
|
168
|
+
```ruby
|
169
|
+
>> $rollout.get(:chat)
|
170
|
+
=> #<Rollout::Feature:0x00007f99fa4ec528 @data={}, @groups=[:caretakers], @name=:chat, @options={}, @percentage=0.05, @users=["1"]>
|
171
|
+
```
|
172
|
+
|
164
173
|
## Namespacing
|
165
174
|
|
166
175
|
Rollout separates its keys from other keys in the data store using the
|
@@ -180,6 +189,7 @@ This example would use the "development:feature:chat:groups" key.
|
|
180
189
|
|
181
190
|
## Frontend / UI
|
182
191
|
|
192
|
+
* [rollout-ui](https://github.com/fetlife/rollout-ui)
|
183
193
|
* [Rollout-Dashboard](https://github.com/fiverr/rollout_dashboard/)
|
184
194
|
|
185
195
|
## Implementations in other languages
|
@@ -188,6 +198,7 @@ This example would use the "development:feature:chat:groups" key.
|
|
188
198
|
* PHP: https://github.com/opensoft/rollout
|
189
199
|
* Clojure: https://github.com/yeller/shoutout
|
190
200
|
* Perl: https://metacpan.org/pod/Toggle
|
201
|
+
* Golang: https://github.com/SalesLoft/gorollout
|
191
202
|
|
192
203
|
|
193
204
|
## Contributors
|
data/lib/rollout/feature.rb
CHANGED
@@ -5,12 +5,13 @@ class Rollout
|
|
5
5
|
attr_accessor :groups, :users, :percentage, :data
|
6
6
|
attr_reader :name, :options
|
7
7
|
|
8
|
-
def initialize(name,
|
9
|
-
@
|
10
|
-
@
|
8
|
+
def initialize(name, rollout:, state: nil, options: {})
|
9
|
+
@name = name
|
10
|
+
@rollout = rollout
|
11
|
+
@options = options
|
11
12
|
|
12
|
-
if
|
13
|
-
raw_percentage, raw_users, raw_groups, raw_data =
|
13
|
+
if state
|
14
|
+
raw_percentage, raw_users, raw_groups, raw_data = state.split('|', 4)
|
14
15
|
@percentage = raw_percentage.to_f
|
15
16
|
@users = users_from_string(raw_users)
|
16
17
|
@groups = groups_from_string(raw_groups)
|
@@ -48,12 +49,12 @@ class Rollout
|
|
48
49
|
@data = {}
|
49
50
|
end
|
50
51
|
|
51
|
-
def active?(
|
52
|
+
def active?(user)
|
52
53
|
if user
|
53
54
|
id = user_id(user)
|
54
55
|
user_in_percentage?(id) ||
|
55
56
|
user_in_active_users?(id) ||
|
56
|
-
user_in_active_group?(user
|
57
|
+
user_in_active_group?(user)
|
57
58
|
else
|
58
59
|
@percentage == 100
|
59
60
|
end
|
@@ -72,6 +73,14 @@ class Rollout
|
|
72
73
|
}
|
73
74
|
end
|
74
75
|
|
76
|
+
def deep_clone
|
77
|
+
c = self.clone
|
78
|
+
c.instance_variable_set('@rollout', nil)
|
79
|
+
c = Marshal.load(Marshal.dump(c))
|
80
|
+
c.instance_variable_set('@rollot', @rollout)
|
81
|
+
c
|
82
|
+
end
|
83
|
+
|
75
84
|
private
|
76
85
|
|
77
86
|
def user_id(user)
|
@@ -98,9 +107,9 @@ class Rollout
|
|
98
107
|
end
|
99
108
|
end
|
100
109
|
|
101
|
-
def user_in_active_group?(user
|
110
|
+
def user_in_active_group?(user)
|
102
111
|
@groups.any? do |g|
|
103
|
-
rollout.active_in_group?(g, user)
|
112
|
+
@rollout.active_in_group?(g, user)
|
104
113
|
end
|
105
114
|
end
|
106
115
|
|
data/lib/rollout/logging.rb
CHANGED
data/lib/rollout/version.rb
CHANGED
data/lib/rollout.rb
CHANGED
@@ -107,7 +107,7 @@ class Rollout
|
|
107
107
|
|
108
108
|
def active?(feature, user = nil)
|
109
109
|
feature = get(feature)
|
110
|
-
feature.active?(
|
110
|
+
feature.active?(user)
|
111
111
|
end
|
112
112
|
|
113
113
|
def user_in_active_users?(feature, user = nil)
|
@@ -138,7 +138,7 @@ class Rollout
|
|
138
138
|
|
139
139
|
def get(feature)
|
140
140
|
string = @storage.get(key(feature))
|
141
|
-
Feature.new(feature, string, @options)
|
141
|
+
Feature.new(feature, state: string, rollout: self, options: @options)
|
142
142
|
end
|
143
143
|
|
144
144
|
def set_feature_data(feature, data)
|
@@ -157,7 +157,13 @@ class Rollout
|
|
157
157
|
return [] if features.empty?
|
158
158
|
|
159
159
|
feature_keys = features.map { |feature| key(feature) }
|
160
|
-
|
160
|
+
|
161
|
+
@storage
|
162
|
+
.mget(*feature_keys)
|
163
|
+
.map
|
164
|
+
.with_index do |string, index|
|
165
|
+
Feature.new(features[index], state: string, rollout: self, options: @options)
|
166
|
+
end
|
161
167
|
end
|
162
168
|
|
163
169
|
def features
|
@@ -166,13 +172,13 @@ class Rollout
|
|
166
172
|
|
167
173
|
def feature_states(user = nil)
|
168
174
|
multi_get(*features).each_with_object({}) do |f, hash|
|
169
|
-
hash[f.name] = f.active?(
|
175
|
+
hash[f.name] = f.active?(user)
|
170
176
|
end
|
171
177
|
end
|
172
178
|
|
173
179
|
def active_features(user = nil)
|
174
180
|
multi_get(*features).select do |f|
|
175
|
-
f.active?(
|
181
|
+
f.active?(user)
|
176
182
|
end.map(&:name)
|
177
183
|
end
|
178
184
|
|
@@ -199,7 +205,7 @@ class Rollout
|
|
199
205
|
f = get(feature)
|
200
206
|
|
201
207
|
if count_observers > 0
|
202
|
-
before =
|
208
|
+
before = f.deep_clone
|
203
209
|
yield(f)
|
204
210
|
save(f)
|
205
211
|
changed
|
data/rollout.gemspec
CHANGED
@@ -20,11 +20,11 @@ Gem::Specification.new do |spec|
|
|
20
20
|
|
21
21
|
spec.required_ruby_version = '>= 2.3'
|
22
22
|
|
23
|
-
spec.add_dependency 'redis', '
|
23
|
+
spec.add_dependency 'redis', '>= 4.0', '< 6'
|
24
24
|
|
25
25
|
spec.add_development_dependency 'bundler', '>= 1.17'
|
26
26
|
spec.add_development_dependency 'pry'
|
27
|
-
spec.add_development_dependency 'rspec', '~> 3.
|
27
|
+
spec.add_development_dependency 'rspec', '~> 3.13'
|
28
28
|
spec.add_development_dependency 'rspec_junit_formatter', '~> 0.4'
|
29
29
|
spec.add_development_dependency 'rubocop', '~> 0.71'
|
30
30
|
spec.add_development_dependency 'simplecov', '0.17'
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe "Rollout::Feature" do
|
4
|
+
let(:rollout) { Rollout.new($redis) }
|
5
|
+
|
6
|
+
describe "#add_user" do
|
7
|
+
it "ids a user using id_user_by" do
|
8
|
+
user = double("User", email: "test@test.com")
|
9
|
+
feature = Rollout::Feature.new(:chat, state: nil, rollout: rollout, options: { id_user_by: :email })
|
10
|
+
feature.add_user(user)
|
11
|
+
expect(user).to have_received :email
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
describe "#initialize" do
|
16
|
+
describe "when string does not exist" do
|
17
|
+
it 'clears feature attributes when string is not given' do
|
18
|
+
feature = Rollout::Feature.new(:chat, rollout: rollout)
|
19
|
+
expect(feature.groups).to be_empty
|
20
|
+
expect(feature.users).to be_empty
|
21
|
+
expect(feature.percentage).to eq 0
|
22
|
+
expect(feature.data).to eq({})
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'clears feature attributes when string is nil' do
|
26
|
+
feature = Rollout::Feature.new(:chat, state: nil, rollout: rollout)
|
27
|
+
expect(feature.groups).to be_empty
|
28
|
+
expect(feature.users).to be_empty
|
29
|
+
expect(feature.percentage).to eq 0
|
30
|
+
expect(feature.data).to eq({})
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'clears feature attributes when string is empty string' do
|
34
|
+
feature = Rollout::Feature.new(:chat, state: "", rollout: rollout)
|
35
|
+
expect(feature.groups).to be_empty
|
36
|
+
expect(feature.users).to be_empty
|
37
|
+
expect(feature.percentage).to eq 0
|
38
|
+
expect(feature.data).to eq({})
|
39
|
+
end
|
40
|
+
|
41
|
+
describe "when there is no data" do
|
42
|
+
it 'sets @data to empty hash' do
|
43
|
+
feature = Rollout::Feature.new(:chat, state: "0||", rollout: rollout)
|
44
|
+
expect(feature.data).to eq({})
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'sets @data to empty hash' do
|
48
|
+
feature = Rollout::Feature.new(:chat, state: "||| ", rollout: rollout)
|
49
|
+
expect(feature.data).to eq({})
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|