unleash 4.4.4 → 4.6.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f59bcc61a5b2adc71f82f31ee80d02e8957854b86b85147fa33ad1f8bd3ee3ab
4
- data.tar.gz: 691a79a57aba051ab5111d71a038e6c8f2d21ee747ee39fdb6676108fc1ef777
3
+ metadata.gz: 5f7e5c9cc285cd8b5d030f115babe035a65540a63aea7f2c0500d845afe74af1
4
+ data.tar.gz: ec93948ce1eed18fc20ac93e8a9bb944ae6080a1740f58910d6689305d1cc72e
5
5
  SHA512:
6
- metadata.gz: c6d4c9efc598b2eae5958c7c68c362832988d6ed8cc4f51d47e0ede588b37599c0d104d4a7c08060761564653bdb68d1c6a20dedafc7b6d3bbb22c489ac8c4ec
7
- data.tar.gz: 003b0d345c5f1ccc22361ddce4213e40054ec57a7e3404ca8d99ce3b120fb072b506a75f1fe64053e7b57c67688ed0b6aa6532dd99f3f7bfaa729c520d2c290c
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.2.2 https://github.com/Unleash/client-specification.git client-specification
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
@@ -13,6 +13,8 @@ Metrics/ClassLength:
13
13
  Max: 135
14
14
  CountAsOne:
15
15
  - 'method_call'
16
+ Exclude:
17
+ - 'lib/unleash/feature_toggle.rb'
16
18
  Layout/LineLength:
17
19
  Max: 140
18
20
  Metrics/MethodLength:
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.4.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.2.2 https://github.com/Unleash/client-specification.git client-specification`
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](#Installation) in [README.md](README.md)
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.each{ |c| c.is_a?(Constraint) }
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 (contraints: #{constraints})"
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
@@ -41,7 +41,6 @@ module Unleash
41
41
  end
42
42
 
43
43
  toggle_as_hash = Unleash&.toggles&.select{ |toggle| toggle['name'] == feature }&.first
44
-
45
44
  if toggle_as_hash.nil?
46
45
  Unleash.logger.debug "Unleash::Client.is_enabled? feature: #{feature} not found"
47
46
  return default_value
@@ -53,7 +53,7 @@ module Unleash
53
53
  {
54
54
  'UNLEASH-INSTANCEID' => self.instance_id,
55
55
  'UNLEASH-APPNAME' => self.app_name,
56
- 'Unleash-Client-Spec' => '4.2.2'
56
+ 'Unleash-Client-Spec' => '4.5.1'
57
57
  }.merge!(generate_custom_http_headers)
58
58
  end
59
59
 
@@ -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
- toggle_enabled = am_enabled?(context)
41
- variant = resolve_variant(context, toggle_enabled)
43
+ evaluation_result = evaluate(context)
44
+
45
+ group_id = evaluation_result.strategy&.params.to_h['groupId'] || self.name
42
46
 
43
- choice = toggle_enabled ? :yes : :no
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, toggle_enabled)
55
- return Unleash::FeatureToggle.disabled_variant unless toggle_enabled
56
- return Unleash::FeatureToggle.disabled_variant if sum_variant_defs_weights <= 0
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) || variant_from_weights(context, resolve_stickiness)
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
- self.variant_definitions&.map(&:stickiness)&.compact&.first || "default"
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
- result =
68
- if self.enabled
69
- self.strategies.empty? ||
70
- self.strategies.any? do |s|
71
- strategy_enabled?(s, context) && strategy_constraint_matches?(s, context)
72
- end
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
- false
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 contraints returned #{result})"
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
- self.variant_definitions.map(&:weight).reduce(0, :+)
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
- variant = self.variant_definitions.find{ |vd| vd.override_matches_context?(context) }
115
- return nil if variant.nil?
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: variant.name, enabled: true, payload: variant.payload)
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(variant_salt(context, stickiness), self.name, sum_variant_defs_weights)
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 = self.variant_definitions
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")
@@ -1,3 +1,3 @@
1
1
  module Unleash
2
- VERSION = "4.4.4".freeze
2
+ VERSION = "4.6.0".freeze
3
3
  end
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.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-07-05 00:00:00.000000000 Z
11
+ date: 2023-10-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: murmurhash3