feature_flagger 0.7.3 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/README.md +20 -4
- data/lib/feature_flagger/configuration.rb +26 -0
- data/lib/feature_flagger/control.rb +11 -15
- data/lib/feature_flagger/manager.rb +18 -0
- data/lib/feature_flagger/model.rb +21 -14
- data/lib/feature_flagger/railtie.rb +9 -0
- data/lib/feature_flagger/storage/redis.rb +18 -0
- data/lib/feature_flagger/version.rb +1 -1
- data/lib/feature_flagger.rb +2 -0
- data/lib/tasks/cleanup.rake +10 -0
- metadata +8 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: a96951570dcb83956c8a817ac63a0928fdcd276e65fda789828fd4ef2a9b0234
|
4
|
+
data.tar.gz: 332306b6061c58cd5f215b3e51ad0084434eff4d82d77d7ae533b353176ecca6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1dd51d20a1e52136ee0609ca619c3bb11d6fbc086170f339e6b40c6c09ab49bd9a3ff55b5901ad9a4a09c9e2b840e3eda44ebedc504e07deeede38c9cfcedfb9
|
7
|
+
data.tar.gz: 97c93e48cb0a5ef452c3df7f17b35848c636151366bbdf329f91f40e19134491c2c6d441bb31e3308ccaea67bc2688788d5817b7c657ede7f7f1b20de93f809e
|
data/README.md
CHANGED
@@ -4,6 +4,14 @@
|
|
4
4
|
|
5
5
|
Partially release your features.
|
6
6
|
|
7
|
+
## Working with Docker
|
8
|
+
|
9
|
+
Open IRB
|
10
|
+
`docker-compose run feature_flagger`
|
11
|
+
|
12
|
+
Running tests
|
13
|
+
`docker-compose run feature_flagger rspec`
|
14
|
+
|
7
15
|
## Installation
|
8
16
|
|
9
17
|
Add this line to your application's Gemfile:
|
@@ -63,7 +71,7 @@ account.release(:email_marketing, :new_email_flow)
|
|
63
71
|
#=> true
|
64
72
|
|
65
73
|
# Check feature for a given account
|
66
|
-
account.
|
74
|
+
account.released?(:email_marketing, :new_email_flow)
|
67
75
|
#=> true
|
68
76
|
|
69
77
|
# Remove feature for given account
|
@@ -71,7 +79,7 @@ account.unrelease(:email_marketing, :new_email_flow)
|
|
71
79
|
#=> true
|
72
80
|
|
73
81
|
# If you try to check an inexistent rollout key it will raise an error.
|
74
|
-
account.
|
82
|
+
account.released?(:email_marketing, :new_email_flow)
|
75
83
|
FeatureFlagger::KeyNotFoundError: ["account", "email_marketing", "new_email_flo"]
|
76
84
|
|
77
85
|
# Check feature for a specific account id
|
@@ -95,7 +103,15 @@ Account.unrelease_to_all(:email_marketing, :new_email_flow)
|
|
95
103
|
Account.released_features_to_all
|
96
104
|
```
|
97
105
|
|
106
|
+
## Clean up action
|
107
|
+
|
108
|
+
By default when a key is removed from `rollout.yml` file, its data still in the storage.
|
109
|
+
|
110
|
+
To clean it up, execute or schedule the rake:
|
111
|
+
|
112
|
+
$ bundle exec rake feature_flagger:cleanup_removed_rollouts
|
113
|
+
|
98
114
|
## Contributing
|
99
115
|
|
100
|
-
Bug reports and pull requests are welcome
|
101
|
-
|
116
|
+
Bug reports and pull requests are welcome!
|
117
|
+
Please take a look at our guidelines [here](CONTRIBUTING.md).
|
@@ -11,10 +11,36 @@ module FeatureFlagger
|
|
11
11
|
@info ||= YAML.load_file(yaml_filepath) if yaml_filepath
|
12
12
|
end
|
13
13
|
|
14
|
+
def mapped_feature_keys(resource_name = nil)
|
15
|
+
info_filtered = resource_name ? info[resource_name] : info
|
16
|
+
[].tap do |keys|
|
17
|
+
make_keys_recursively(info_filtered).each { |key| keys.push(join_key(resource_name, key)) }
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
14
21
|
private
|
15
22
|
|
16
23
|
def default_yaml_filepath
|
17
24
|
"#{Rails.root}/config/rollout.yml" if defined?(Rails)
|
18
25
|
end
|
26
|
+
|
27
|
+
def make_keys_recursively(hash, keys = [], composed_key = [])
|
28
|
+
unless hash.values[0].is_a?(Hash)
|
29
|
+
keys.push(composed_key)
|
30
|
+
return
|
31
|
+
end
|
32
|
+
|
33
|
+
hash.each do |key, value|
|
34
|
+
composed_key_cloned = composed_key.clone
|
35
|
+
composed_key_cloned.push(key.to_sym)
|
36
|
+
make_keys_recursively(value, keys, composed_key_cloned)
|
37
|
+
end
|
38
|
+
keys
|
39
|
+
end
|
40
|
+
|
41
|
+
def join_key(resource_name, key)
|
42
|
+
key.unshift resource_name if resource_name
|
43
|
+
key.join(":")
|
44
|
+
end
|
19
45
|
end
|
20
46
|
end
|
@@ -8,7 +8,7 @@ module FeatureFlagger
|
|
8
8
|
@storage = storage
|
9
9
|
end
|
10
10
|
|
11
|
-
def
|
11
|
+
def released?(feature_key, resource_id)
|
12
12
|
@storage.has_value?(RELEASED_FEATURES, feature_key) || @storage.has_value?(feature_key, resource_id)
|
13
13
|
end
|
14
14
|
|
@@ -17,13 +17,7 @@ module FeatureFlagger
|
|
17
17
|
end
|
18
18
|
|
19
19
|
def release_to_all(feature_key)
|
20
|
-
@storage.
|
21
|
-
end
|
22
|
-
|
23
|
-
# <b>DEPRECATED:</b> Please use <tt>release</tt> instead.
|
24
|
-
def release!(feature_key, resource_id)
|
25
|
-
warn "[DEPRECATION] `release!` is deprecated. Please use `release` instead."
|
26
|
-
release(feature_key, resource_id)
|
20
|
+
@storage.add_all(RELEASED_FEATURES, feature_key)
|
27
21
|
end
|
28
22
|
|
29
23
|
def unrelease(feature_key, resource_id)
|
@@ -31,13 +25,7 @@ module FeatureFlagger
|
|
31
25
|
end
|
32
26
|
|
33
27
|
def unrelease_to_all(feature_key)
|
34
|
-
@storage.
|
35
|
-
end
|
36
|
-
|
37
|
-
# <b>DEPRECATED:</b> Please use <tt>unrelease</tt> instead.
|
38
|
-
def unrelease!(feature_key, resource_id)
|
39
|
-
warn "[DEPRECATION] `unrelease!` is deprecated. Please use `unrelease` instead."
|
40
|
-
unrelease(feature_key, resource_id)
|
28
|
+
@storage.remove_all(RELEASED_FEATURES, feature_key)
|
41
29
|
end
|
42
30
|
|
43
31
|
def resource_ids(feature_key)
|
@@ -47,5 +35,13 @@ module FeatureFlagger
|
|
47
35
|
def released_features_to_all
|
48
36
|
@storage.all_values(RELEASED_FEATURES)
|
49
37
|
end
|
38
|
+
|
39
|
+
def released_to_all?(feature_key)
|
40
|
+
@storage.has_value?(RELEASED_FEATURES, feature_key)
|
41
|
+
end
|
42
|
+
|
43
|
+
def search_keys(query)
|
44
|
+
@storage.search_keys(query)
|
45
|
+
end
|
50
46
|
end
|
51
47
|
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module FeatureFlagger
|
2
|
+
class Manager
|
3
|
+
|
4
|
+
def self.detached_feature_keys
|
5
|
+
persisted_features = FeatureFlagger.control.search_keys("*").to_a
|
6
|
+
mapped_feature_keys = FeatureFlagger.config.mapped_feature_keys
|
7
|
+
persisted_features - mapped_feature_keys
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.cleanup_detached(resource_name, *feature_key)
|
11
|
+
complete_feature_key = feature_key.map(&:to_s).insert(0, resource_name.to_s)
|
12
|
+
key_value = FeatureFlagger.config.info.dig(*complete_feature_key)
|
13
|
+
raise "key is still mapped" if key_value
|
14
|
+
FeatureFlagger.control.unrelease_to_all(complete_feature_key.join(':'))
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
@@ -12,26 +12,14 @@ module FeatureFlagger
|
|
12
12
|
base.extend ClassMethods
|
13
13
|
end
|
14
14
|
|
15
|
-
def
|
15
|
+
def released?(*feature_key)
|
16
16
|
self.class.released_id?(id, feature_key)
|
17
17
|
end
|
18
18
|
|
19
|
-
# <b>DEPRECATED:</b> Please use <tt>release</tt> instead.
|
20
|
-
def release!(*feature_key)
|
21
|
-
warn "[DEPRECATION] `release!` is deprecated. Please use `release` instead."
|
22
|
-
release(*feature_key)
|
23
|
-
end
|
24
|
-
|
25
19
|
def release(*feature_key)
|
26
20
|
self.class.release_id(id, *feature_key)
|
27
21
|
end
|
28
22
|
|
29
|
-
# <b>DEPRECATED:</b> Please use <tt>unrelease</tt> instead.
|
30
|
-
def unrelease!(*feature_key)
|
31
|
-
warn "[DEPRECATION] `unrelease!` is deprecated. Please use `unrelease` instead."
|
32
|
-
unrelease(*feature_key)
|
33
|
-
end
|
34
|
-
|
35
23
|
def unrelease(*feature_key)
|
36
24
|
resource_name = self.class.rollout_resource_name
|
37
25
|
feature = Feature.new(feature_key, resource_name)
|
@@ -39,9 +27,10 @@ module FeatureFlagger
|
|
39
27
|
end
|
40
28
|
|
41
29
|
module ClassMethods
|
30
|
+
|
42
31
|
def released_id?(resource_id, *feature_key)
|
43
32
|
feature = Feature.new(feature_key, rollout_resource_name)
|
44
|
-
FeatureFlagger.control.
|
33
|
+
FeatureFlagger.control.released?(feature.key, resource_id)
|
45
34
|
end
|
46
35
|
|
47
36
|
def release_id(resource_id, *feature_key)
|
@@ -69,6 +58,24 @@ module FeatureFlagger
|
|
69
58
|
FeatureFlagger.control.released_features_to_all
|
70
59
|
end
|
71
60
|
|
61
|
+
def released_to_all?(*feature_key)
|
62
|
+
feature = Feature.new(feature_key, rollout_resource_name)
|
63
|
+
FeatureFlagger.control.released_to_all?(feature.key)
|
64
|
+
end
|
65
|
+
|
66
|
+
def detached_feature_keys
|
67
|
+
persisted_features = FeatureFlagger.control.search_keys("#{rollout_resource_name}:*").to_a
|
68
|
+
mapped_feature_keys = FeatureFlagger.config.mapped_feature_keys(rollout_resource_name)
|
69
|
+
(persisted_features - mapped_feature_keys).map { |key| key.sub("#{rollout_resource_name}:",'') }
|
70
|
+
end
|
71
|
+
|
72
|
+
def cleanup_detached(*feature_key)
|
73
|
+
complete_feature_key = feature_key.map(&:to_s).insert(0, rollout_resource_name)
|
74
|
+
key_value = FeatureFlagger.config.info.dig(*complete_feature_key)
|
75
|
+
raise "key is still mapped" if key_value
|
76
|
+
FeatureFlagger.control.unrelease_to_all(complete_feature_key.join(':'))
|
77
|
+
end
|
78
|
+
|
72
79
|
def rollout_resource_name
|
73
80
|
klass_name = self.to_s
|
74
81
|
klass_name.gsub!(/::/, '_')
|
@@ -29,9 +29,27 @@ module FeatureFlagger
|
|
29
29
|
@redis.srem(key, value)
|
30
30
|
end
|
31
31
|
|
32
|
+
def remove_all(global_key, key)
|
33
|
+
@redis.multi do |redis|
|
34
|
+
redis.srem(global_key, key)
|
35
|
+
redis.del(key)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def add_all(global_key, key)
|
40
|
+
@redis.multi do |redis|
|
41
|
+
redis.sadd(global_key, key)
|
42
|
+
redis.del(key)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
32
46
|
def all_values(key)
|
33
47
|
@redis.smembers(key)
|
34
48
|
end
|
49
|
+
|
50
|
+
def search_keys(query)
|
51
|
+
@redis.scan_each(match: query)
|
52
|
+
end
|
35
53
|
end
|
36
54
|
end
|
37
55
|
end
|
data/lib/feature_flagger.rb
CHANGED
@@ -0,0 +1,10 @@
|
|
1
|
+
namespace :feature_flagger do
|
2
|
+
desc "cleaning up keys from storage that are no longer in the rollout.yml file"
|
3
|
+
task :cleanup_removed_rollouts => :environment do
|
4
|
+
keys = FeatureFlagger::Manager.detached_feature_keys
|
5
|
+
puts "Found keys to remove: #{keys}"
|
6
|
+
keys.each do |key|
|
7
|
+
FeatureFlagger::Manager.cleanup_detached key
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: feature_flagger
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nando Sousa
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2019-
|
12
|
+
date: 2019-05-06 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: redis
|
@@ -109,9 +109,12 @@ files:
|
|
109
109
|
- lib/feature_flagger/configuration.rb
|
110
110
|
- lib/feature_flagger/control.rb
|
111
111
|
- lib/feature_flagger/feature.rb
|
112
|
+
- lib/feature_flagger/manager.rb
|
112
113
|
- lib/feature_flagger/model.rb
|
114
|
+
- lib/feature_flagger/railtie.rb
|
113
115
|
- lib/feature_flagger/storage/redis.rb
|
114
116
|
- lib/feature_flagger/version.rb
|
117
|
+
- lib/tasks/cleanup.rake
|
115
118
|
homepage: http://github.com/ResultadosDigitais/feature_flagger
|
116
119
|
licenses:
|
117
120
|
- MIT
|
@@ -124,15 +127,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
124
127
|
requirements:
|
125
128
|
- - ">="
|
126
129
|
- !ruby/object:Gem::Version
|
127
|
-
version: '
|
130
|
+
version: '2.5'
|
128
131
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
129
132
|
requirements:
|
130
133
|
- - ">="
|
131
134
|
- !ruby/object:Gem::Version
|
132
|
-
version:
|
135
|
+
version: 2.0.0
|
133
136
|
requirements: []
|
134
137
|
rubyforge_project:
|
135
|
-
rubygems_version: 2.
|
138
|
+
rubygems_version: 2.7.8
|
136
139
|
signing_key:
|
137
140
|
specification_version: 4
|
138
141
|
summary: Partial release your features.
|