unleash 4.4.4 → 4.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/.github/workflows/pull_request.yml +1 -1
- data/.rubocop.yml +2 -0
- data/CHANGELOG.md +13 -0
- data/README.md +3 -3
- data/lib/unleash/activation_strategy.rb +17 -4
- data/lib/unleash/client.rb +0 -1
- data/lib/unleash/configuration.rb +1 -1
- data/lib/unleash/feature_toggle.rb +92 -31
- data/lib/unleash/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5f7e5c9cc285cd8b5d030f115babe035a65540a63aea7f2c0500d845afe74af1
|
4
|
+
data.tar.gz: ec93948ce1eed18fc20ac93e8a9bb944ae6080a1740f58910d6689305d1cc72e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cabaff808990418367c85b739b5994f3d0010ee61415b5d9e6dfcea9e3b808b3b9d9e6bd05d07a0fd9bacc9423d909e52393e1a42ea671a95acfa7e45b4c80f4
|
7
|
+
data.tar.gz: fdb3553cb10738d0db2be4a8f1bbd01181eb32abcb790420cbd6961c7f0df76841e7abfa88a29a829554d0baa3374db2f46fe87e068f380187bdbcc94d77fc03
|
@@ -50,7 +50,7 @@ jobs:
|
|
50
50
|
- name: Install dependencies
|
51
51
|
run: bundle install
|
52
52
|
- name: Download test cases
|
53
|
-
run: git clone --depth 5 --branch v4.
|
53
|
+
run: git clone --depth 5 --branch v4.5.1 https://github.com/Unleash/client-specification.git client-specification
|
54
54
|
- name: Run tests
|
55
55
|
run: bundle exec rake
|
56
56
|
env:
|
data/.rubocop.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -13,6 +13,19 @@ Note: These changes are not considered notable:
|
|
13
13
|
|
14
14
|
## [Unreleased]
|
15
15
|
|
16
|
+
## [4.6.0] - 2023-10-16
|
17
|
+
### Added
|
18
|
+
- dependant toggles (#155)
|
19
|
+
- client specification is [here](https://github.com/Unleash/client-specification/pull/63)
|
20
|
+
|
21
|
+
## [4.5.0] - 2023-07-05
|
22
|
+
### Added
|
23
|
+
- variants in strategies (#148)
|
24
|
+
- issue described here (#147)
|
25
|
+
|
26
|
+
### Fixed
|
27
|
+
- groupId override for variants
|
28
|
+
|
16
29
|
## [4.4.4] - 2023-07-05
|
17
30
|
### Fixed
|
18
31
|
- flexible rollout strategy without context (#146)
|
data/README.md
CHANGED
@@ -25,7 +25,7 @@ Leverage the [Unleash Server](https://github.com/Unleash/unleash) for powerful f
|
|
25
25
|
Add this line to your application's Gemfile:
|
26
26
|
|
27
27
|
```ruby
|
28
|
-
gem 'unleash', '~> 4.
|
28
|
+
gem 'unleash', '~> 4.6.0'
|
29
29
|
```
|
30
30
|
|
31
31
|
And then execute:
|
@@ -528,7 +528,7 @@ You can also run `bin/console` for an interactive prompt that will allow you to
|
|
528
528
|
This SDK is also built against the Unleash Client Specification tests.
|
529
529
|
To run the Ruby SDK against this test suite, you'll need to have a copy on your machine, you can clone the repository directly using:
|
530
530
|
|
531
|
-
`git clone --depth 5 --branch v4.
|
531
|
+
`git clone --depth 5 --branch v4.5.1 https://github.com/Unleash/client-specification.git client-specification`
|
532
532
|
|
533
533
|
After doing this, `rake spec` will also run the client specification tests.
|
534
534
|
|
@@ -539,7 +539,7 @@ To install this gem onto your local machine, run `bundle exec rake install`.
|
|
539
539
|
Choose a new version number following [Semantic Versioning](https://semver.org/spec/v2.0.0.html) semantics and then:
|
540
540
|
|
541
541
|
- update the version number in [./lib/unleash/version.rb](./lib/unleash/version.rb),
|
542
|
-
- if a major or minor version bump, update the [Installation section](#
|
542
|
+
- if a major or minor version bump, update the [Installation section](#installation) in [README.md](README.md)
|
543
543
|
- update [CHANGELOG.md](CHANGELOG.md) following the format on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/)
|
544
544
|
- commit with message `chore: bump version to x.y.z`
|
545
545
|
- then run `bundle exec rake release`
|
@@ -1,8 +1,8 @@
|
|
1
1
|
module Unleash
|
2
2
|
class ActivationStrategy
|
3
|
-
attr_accessor :name, :params, :constraints, :disabled
|
3
|
+
attr_accessor :name, :params, :constraints, :disabled, :variant_definitions
|
4
4
|
|
5
|
-
def initialize(name, params, constraints = [])
|
5
|
+
def initialize(name, params, constraints = [], variant_definitions = [])
|
6
6
|
self.name = name
|
7
7
|
self.disabled = false
|
8
8
|
|
@@ -15,17 +15,30 @@ module Unleash
|
|
15
15
|
self.params = {}
|
16
16
|
end
|
17
17
|
|
18
|
-
if constraints.is_a?(Array) && constraints.
|
18
|
+
if constraints.is_a?(Array) && constraints.all?{ |c| c.is_a?(Constraint) }
|
19
19
|
self.constraints = constraints
|
20
20
|
else
|
21
|
-
Unleash.logger.warn "Invalid constraints provided for ActivationStrategy (
|
21
|
+
Unleash.logger.warn "Invalid constraints provided for ActivationStrategy (constraints: #{constraints})"
|
22
22
|
self.disabled = true
|
23
23
|
self.constraints = []
|
24
24
|
end
|
25
|
+
|
26
|
+
self.variant_definitions = valid_variant_definitions(variant_definitions)
|
25
27
|
end
|
26
28
|
|
27
29
|
def matches_context?(context)
|
28
30
|
self.constraints.any?{ |c| c.matches_context? context }
|
29
31
|
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def valid_variant_definitions(variant_definitions)
|
36
|
+
if variant_definitions.is_a?(Array) && variant_definitions.all?{ |variant_definition| variant_definition.is_a?(VariantDefinition) }
|
37
|
+
variant_definitions
|
38
|
+
else
|
39
|
+
Unleash.logger.warn "Invalid variant_definitions provided for ActivationStrategy (variant_definitions: #{variant_definitions})"
|
40
|
+
[]
|
41
|
+
end
|
42
|
+
end
|
30
43
|
end
|
31
44
|
end
|
data/lib/unleash/client.rb
CHANGED
@@ -7,13 +7,16 @@ require 'securerandom'
|
|
7
7
|
|
8
8
|
module Unleash
|
9
9
|
class FeatureToggle
|
10
|
-
attr_accessor :name, :enabled, :strategies, :variant_definitions
|
10
|
+
attr_accessor :name, :enabled, :dependencies, :strategies, :variant_definitions
|
11
|
+
|
12
|
+
FeatureEvaluationResult = Struct.new(:enabled?, :strategy)
|
11
13
|
|
12
14
|
def initialize(params = {}, segment_map = {})
|
13
15
|
params = {} if params.nil?
|
14
16
|
|
15
17
|
self.name = params.fetch('name', nil)
|
16
18
|
self.enabled = params.fetch('enabled', false)
|
19
|
+
self.dependencies = params.fetch('dependencies', [])
|
17
20
|
|
18
21
|
self.strategies = initialize_strategies(params, segment_map)
|
19
22
|
self.variant_definitions = initialize_variant_definitions(params)
|
@@ -37,10 +40,13 @@ module Unleash
|
|
37
40
|
|
38
41
|
context = ensure_valid_context(context)
|
39
42
|
|
40
|
-
|
41
|
-
|
43
|
+
evaluation_result = evaluate(context)
|
44
|
+
|
45
|
+
group_id = evaluation_result.strategy&.params.to_h['groupId'] || self.name
|
42
46
|
|
43
|
-
|
47
|
+
variant = resolve_variant(context, evaluation_result, group_id)
|
48
|
+
|
49
|
+
choice = evaluation_result.enabled? ? :yes : :no
|
44
50
|
Unleash.toggle_metrics.increment_variant(self.name, choice, variant.name) unless Unleash.configuration.disable_metrics
|
45
51
|
variant
|
46
52
|
end
|
@@ -51,33 +57,69 @@ module Unleash
|
|
51
57
|
|
52
58
|
private
|
53
59
|
|
54
|
-
def resolve_variant(context,
|
55
|
-
|
56
|
-
|
60
|
+
def resolve_variant(context, evaluation_result, group_id)
|
61
|
+
variant_definitions = evaluation_result.strategy&.variant_definitions
|
62
|
+
variant_definitions = self.variant_definitions if variant_definitions.nil? || variant_definitions.empty?
|
63
|
+
return Unleash::FeatureToggle.disabled_variant unless evaluation_result.enabled?
|
64
|
+
return Unleash::FeatureToggle.disabled_variant if sum_variant_defs_weights(variant_definitions) <= 0
|
57
65
|
|
58
|
-
variant_from_override_match(context) ||
|
66
|
+
variant_from_override_match(context, variant_definitions) ||
|
67
|
+
variant_from_weights(context, resolve_stickiness(variant_definitions), variant_definitions, group_id)
|
59
68
|
end
|
60
69
|
|
61
|
-
def resolve_stickiness
|
62
|
-
|
70
|
+
def resolve_stickiness(variant_definitions)
|
71
|
+
variant_definitions&.map(&:stickiness)&.compact&.first || "default"
|
63
72
|
end
|
64
73
|
|
65
74
|
# only check if it is enabled, do not do metrics
|
66
75
|
def am_enabled?(context)
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
76
|
+
evaluate(context).enabled?
|
77
|
+
end
|
78
|
+
|
79
|
+
def parent_dependencies_satisfied?(context)
|
80
|
+
dependencies.empty? || dependencies.all?{ |parent| evaluate_parent(parent, context) }
|
81
|
+
end
|
82
|
+
|
83
|
+
def evaluate_parent(parent, context)
|
84
|
+
parent_toggle = get_parent(parent["feature"])
|
85
|
+
return false if parent_toggle.nil? || !parent_toggle.dependencies.empty?
|
86
|
+
|
87
|
+
evaluation_result = parent_toggle.is_enabled?(context)
|
88
|
+
return !evaluation_result if parent["enabled"] == false
|
89
|
+
|
90
|
+
return false unless evaluation_result
|
91
|
+
|
92
|
+
return evaluation_result if parent["variants"].nil? || parent["variants"].empty?
|
93
|
+
|
94
|
+
parent["variants"].include?(parent_toggle.get_variant(context).name)
|
95
|
+
end
|
96
|
+
|
97
|
+
def get_parent(feature)
|
98
|
+
toggle_as_hash = Unleash&.toggles&.find{ |toggle| toggle['name'] == feature }
|
99
|
+
if toggle_as_hash.nil?
|
100
|
+
Unleash.logger.debug "Unleash::Client.is_enabled? feature: #{feature} not found"
|
101
|
+
return nil
|
102
|
+
end
|
103
|
+
|
104
|
+
Unleash::FeatureToggle.new(toggle_as_hash, Unleash&.segment_cache)
|
105
|
+
end
|
106
|
+
|
107
|
+
def evaluate(context)
|
108
|
+
evaluation_result =
|
109
|
+
if !parent_dependencies_satisfied?(context)
|
110
|
+
FeatureEvaluationResult.new(false, nil)
|
111
|
+
elsif !self.enabled
|
112
|
+
FeatureEvaluationResult.new(false, nil)
|
113
|
+
elsif self.strategies.empty?
|
114
|
+
FeatureEvaluationResult.new(true, nil)
|
73
115
|
else
|
74
|
-
|
116
|
+
strategy = self.strategies.find{ |s| strategy_enabled?(s, context) && strategy_constraint_matches?(s, context) }
|
117
|
+
FeatureEvaluationResult.new(!strategy.nil?, strategy)
|
75
118
|
end
|
76
119
|
|
77
|
-
Unleash.logger.debug "Unleash::FeatureToggle (enabled:#{self.enabled} " \
|
78
|
-
"and Strategies combined with
|
79
|
-
|
80
|
-
result
|
120
|
+
Unleash.logger.debug "Unleash::FeatureToggle (enabled:#{self.enabled}) " \
|
121
|
+
"and Strategies combined with constraints returned #{evaluation_result})"
|
122
|
+
evaluation_result
|
81
123
|
end
|
82
124
|
|
83
125
|
def strategy_enabled?(strategy, context)
|
@@ -92,8 +134,8 @@ module Unleash
|
|
92
134
|
strategy.constraints.empty? || strategy.constraints.all?{ |c| c.matches_context?(context) }
|
93
135
|
end
|
94
136
|
|
95
|
-
def sum_variant_defs_weights
|
96
|
-
|
137
|
+
def sum_variant_defs_weights(variant_definitions)
|
138
|
+
variant_definitions.map(&:weight).reduce(0, :+)
|
97
139
|
end
|
98
140
|
|
99
141
|
def variant_salt(context, stickiness = "default")
|
@@ -110,18 +152,22 @@ module Unleash
|
|
110
152
|
SecureRandom.random_number
|
111
153
|
end
|
112
154
|
|
113
|
-
def variant_from_override_match(context)
|
114
|
-
|
115
|
-
return nil if
|
155
|
+
def variant_from_override_match(context, variant_definitions)
|
156
|
+
variant_definition = variant_definitions.find{ |vd| vd.override_matches_context?(context) }
|
157
|
+
return nil if variant_definition.nil?
|
116
158
|
|
117
|
-
Unleash::Variant.new(name:
|
159
|
+
Unleash::Variant.new(name: variant_definition.name, enabled: true, payload: variant_definition.payload)
|
118
160
|
end
|
119
161
|
|
120
|
-
def variant_from_weights(context, stickiness)
|
121
|
-
variant_weight = Unleash::Strategy::Util.get_normalized_number(
|
162
|
+
def variant_from_weights(context, stickiness, variant_definitions, group_id)
|
163
|
+
variant_weight = Unleash::Strategy::Util.get_normalized_number(
|
164
|
+
variant_salt(context, stickiness),
|
165
|
+
group_id,
|
166
|
+
sum_variant_defs_weights(variant_definitions)
|
167
|
+
)
|
122
168
|
prev_weights = 0
|
123
169
|
|
124
|
-
variant_definition =
|
170
|
+
variant_definition = variant_definitions
|
125
171
|
.find do |v|
|
126
172
|
res = (prev_weights + v.weight >= variant_weight)
|
127
173
|
prev_weights += v.weight
|
@@ -148,11 +194,26 @@ module Unleash
|
|
148
194
|
ActivationStrategy.new(
|
149
195
|
s['name'],
|
150
196
|
s['parameters'],
|
151
|
-
resolve_constraints(s, segment_map)
|
197
|
+
resolve_constraints(s, segment_map),
|
198
|
+
resolve_variants(s)
|
152
199
|
)
|
153
200
|
end || []
|
154
201
|
end
|
155
202
|
|
203
|
+
def resolve_variants(strategy)
|
204
|
+
strategy.fetch("variants", [])
|
205
|
+
.select{ |variant| variant.is_a?(Hash) && variant.has_key?("name") }
|
206
|
+
.map do |variant|
|
207
|
+
VariantDefinition.new(
|
208
|
+
variant.fetch("name", ""),
|
209
|
+
variant.fetch("weight", 0),
|
210
|
+
variant.fetch("payload", nil),
|
211
|
+
variant.fetch("stickiness", nil),
|
212
|
+
variant.fetch("overrides", [])
|
213
|
+
)
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
156
217
|
def resolve_constraints(strategy, segment_map)
|
157
218
|
segment_constraints = (strategy["segments"] || []).map do |segment_id|
|
158
219
|
segment_map[segment_id]&.fetch("constraints")
|
data/lib/unleash/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: unleash
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 4.
|
4
|
+
version: 4.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Renato Arruda
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-10-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: murmurhash3
|