unleash 3.2.1 → 3.2.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 81f398d199ef891fbd322726d73702101bd22e78827c258d90861558662c7c26
4
- data.tar.gz: a6166d04ac912a1766d6f905f1bc86a6ed41bca15cf23f420feb93c8ca81a6e7
3
+ metadata.gz: da9995687fc28b71318711bd1575d9f5493fb9514cc3f276966323e4a73e15f8
4
+ data.tar.gz: e4a29a197e0faeaeaf093bb5a67103ed36aba352facf9633a6845739b17ff58c
5
5
  SHA512:
6
- metadata.gz: 4a4cbbdfd1f5cfef172b7afc4c4ee4e3fd850dadedec443b4b571b74ff8a9aa7cc76b48fe768fef0c9f4ea990876254e86b0650fce7d063aa6dbc172831c8884
7
- data.tar.gz: 7a247e76361465b7716db8dea33e065ad4064eaa3f8b6ac553d6cdb40caf0fcf7a4ae71873a0d7dc6e756514609d3aefb1989973fd3d83dcd7f2abf18536e000
6
+ metadata.gz: a7831894183515cbe05fb10520542b93d8a8cd521eab6ff33e9c7a4f88540519b479a76fb360a085b4f1ee059be1d03d0b531eb0daa4ff3db0147380b6f5db52
7
+ data.tar.gz: 93ce839564b8ef8af502ca148bd3e369f5463d32cd42af1d947be5d751dda88c52d8da71ee5a456658de899afe7fff159655f7f8579cf8030c81ff62655f1804
data/.rubocop.yml CHANGED
@@ -1,7 +1,7 @@
1
1
  # inherit_from: .rubocop_todo.yml
2
2
 
3
3
  AllCops:
4
- TargetRubyVersion: 2.4
4
+ TargetRubyVersion: 2.5
5
5
 
6
6
  Naming/PredicateName:
7
7
  AllowedMethods:
@@ -9,7 +9,7 @@ Naming/PredicateName:
9
9
 
10
10
  Metrics/ClassLength:
11
11
  Max: 120
12
- Metrics/LineLength:
12
+ Layout/LineLength:
13
13
  Max: 140
14
14
  Metrics/MethodLength:
15
15
  Max: 20
@@ -22,9 +22,9 @@ Metrics/BlockLength:
22
22
  Metrics/AbcSize:
23
23
  Max: 25
24
24
  Metrics/CyclomaticComplexity:
25
- Max: 8
25
+ Max: 9
26
26
  Metrics/PerceivedComplexity:
27
- Max: 8
27
+ Max: 9
28
28
 
29
29
  Style/Documentation:
30
30
  Enabled: false
data/.travis.yml CHANGED
@@ -2,14 +2,13 @@ sudo: false
2
2
  language: ruby
3
3
  rvm:
4
4
  - jruby
5
+ - 3.0
5
6
  - 2.7
6
7
  - 2.6
7
8
  - 2.5
8
- - 2.4
9
9
  before_install:
10
10
  - gem install bundler -v 2.1.4
11
- - git clone --depth 5 --branch v3.2.0 https://github.com/Unleash/client-specification.git
12
- client-specification
11
+ - git clone --depth 5 --branch v3.3.0 https://github.com/Unleash/client-specification.git client-specification
13
12
 
14
13
  notifications:
15
14
  slack:
data/README.md CHANGED
@@ -10,10 +10,10 @@ Leverage the [Unleash Server](https://github.com/Unleash/unleash) for powerful f
10
10
 
11
11
  ## Supported Ruby Interpreters
12
12
 
13
+ * MRI 3.0
13
14
  * MRI 2.7
14
15
  * MRI 2.6
15
16
  * MRI 2.5
16
- * MRI 2.4
17
17
  * jruby
18
18
 
19
19
  ## Installation
@@ -21,7 +21,7 @@ Leverage the [Unleash Server](https://github.com/Unleash/unleash) for powerful f
21
21
  Add this line to your application's Gemfile:
22
22
 
23
23
  ```ruby
24
- gem 'unleash', '~> 3.2.1'
24
+ gem 'unleash', '~> 3.2.2'
25
25
  ```
26
26
 
27
27
  And then execute:
data/lib/unleash.rb CHANGED
@@ -5,7 +5,7 @@ require 'unleash/context'
5
5
  require 'unleash/client'
6
6
  require 'logger'
7
7
 
8
- Gem.find_files('unleash/strategy/**/*.rb').each{ |path| require path }
8
+ Gem.find_files('unleash/strategy/**/*.rb').each{ |path| require path unless path.end_with? '_spec.rb' }
9
9
 
10
10
  module Unleash
11
11
  TIME_RESOLUTION = 3
@@ -1,16 +1,29 @@
1
1
  module Unleash
2
2
  class ActivationStrategy
3
- attr_accessor :name, :params
3
+ attr_accessor :name, :params, :constraints
4
4
 
5
- def initialize(name, params)
5
+ def initialize(name, params, constraints = [])
6
6
  self.name = name
7
7
 
8
8
  if params.is_a?(Hash)
9
9
  self.params = params
10
+ elsif params.nil?
11
+ self.params = {}
10
12
  else
11
- Unleash.logger.warn "Invalid params provided for ActivationStrategy #{params}"
13
+ Unleash.logger.warn "Invalid params provided for ActivationStrategy (params:#{params})"
12
14
  self.params = {}
13
15
  end
16
+
17
+ if constraints.is_a?(Array) && constraints.each{ |c| c.is_a?(Constraint) }
18
+ self.constraints = constraints
19
+ else
20
+ Unleash.logger.warn "Invalid constraints provided for ActivationStrategy (contraints: #{constraints})"
21
+ self.constraints = []
22
+ end
23
+ end
24
+
25
+ def matches_context?(context)
26
+ self.constraints.any?{ |c| c.matches_context? context }
14
27
  end
15
28
  end
16
29
  end
@@ -0,0 +1,26 @@
1
+ module Unleash
2
+ class Constraint
3
+ attr_accessor :context_name, :operator, :values
4
+
5
+ VALID_OPERATORS = ['IN', 'NOT_IN'].freeze
6
+
7
+ def initialize(context_name, operator, values = [])
8
+ raise ArgumentError, "context_name is not a String" unless context_name.is_a?(String)
9
+ raise ArgumentError, "operator does not hold a valid value:" + VALID_OPERATORS unless VALID_OPERATORS.include? operator
10
+ raise ArgumentError, "values does not hold an Array" unless values.is_a?(Array)
11
+
12
+ self.context_name = context_name
13
+ self.operator = operator
14
+ self.values = values
15
+ end
16
+
17
+ def matches_context?(context)
18
+ Unleash.logger.debug "Unleash::Constraint matches_context? values: #{self.values} context.get_by_name(#{self.context_name})" \
19
+ " #{context.get_by_name(self.context_name)} "
20
+
21
+ is_included = self.values.include? context.get_by_name(self.context_name)
22
+
23
+ operator == 'IN' ? is_included : !is_included
24
+ end
25
+ end
26
+ end
@@ -1,6 +1,8 @@
1
1
  module Unleash
2
2
  class Context
3
- attr_accessor :app_name, :environment, :user_id, :session_id, :remote_address, :properties
3
+ ATTRS = [:app_name, :environment, :user_id, :session_id, :remote_address].freeze
4
+
5
+ attr_accessor(*[ATTRS, :properties].flatten)
4
6
 
5
7
  def initialize(params = {})
6
8
  raise ArgumentError, "Unleash::Context must be initialized with a hash." unless params.is_a?(Hash)
@@ -12,18 +14,29 @@ module Unleash
12
14
  self.remote_address = value_for('remoteAddress', params)
13
15
 
14
16
  properties = value_for('properties', params)
15
- self.properties = properties.is_a?(Hash) ? properties : {}
17
+ self.properties = properties.is_a?(Hash) ? properties.transform_keys(&:to_sym) : {}
16
18
  end
17
19
 
18
20
  def to_s
19
- "<Context: user_id=#{self.user_id},session_id=#{self.session_id},remote_address=#{self.remote_address},properties=#{self.properties}>"
21
+ "<Context: user_id=#{@user_id},session_id=#{@session_id},remote_address=#{@remote_address},properties=#{@properties}" \
22
+ ",app_name=#{@app_name},environment=#{@environment}>"
23
+ end
24
+
25
+ def get_by_name(name)
26
+ normalized_name = underscore(name).to_sym
27
+
28
+ if ATTRS.include? normalized_name
29
+ self.send(normalized_name)
30
+ else
31
+ self.properties.fetch(normalized_name)
32
+ end
20
33
  end
21
34
 
22
35
  private
23
36
 
24
37
  # Method to fetch values from hash for two types of keys: string in camelCase and symbol in snake_case
25
- def value_for(key, params, default_value = '')
26
- params.values_at(key, underscore(key).to_sym).compact.first || default_value
38
+ def value_for(key, params, default_value = nil)
39
+ params.values_at(key, key.to_sym, underscore(key), underscore(key).to_sym).compact.first || default_value
27
40
  end
28
41
 
29
42
  # converts CamelCase to snake_case
@@ -1,4 +1,5 @@
1
1
  require 'unleash/activation_strategy'
2
+ require 'unleash/constraint'
2
3
  require 'unleash/variant_definition'
3
4
  require 'unleash/variant'
4
5
  require 'unleash/strategy/util'
@@ -13,20 +14,9 @@ module Unleash
13
14
 
14
15
  self.name = params.fetch('name', nil)
15
16
  self.enabled = params.fetch('enabled', false)
16
- self.strategies = params.fetch('strategies', [])
17
- .select{ |s| s.has_key?('name') && Unleash::STRATEGIES.has_key?(s['name'].to_sym) }
18
- .map{ |s| ActivationStrategy.new(s['name'], s['parameters'] || {}) } || []
19
17
 
20
- self.variant_definitions = (params.fetch('variants', []) || [])
21
- .select{ |v| v.is_a?(Hash) && v.has_key?('name') }
22
- .map do |v|
23
- VariantDefinition.new(
24
- v.fetch('name', ''),
25
- v.fetch('weight', 0),
26
- v.fetch('payload', nil),
27
- v.fetch('overrides', [])
28
- )
29
- end || []
18
+ self.strategies = initialize_strategies(params)
19
+ self.variant_definitions = initialize_variant_definitions(params)
30
20
  end
31
21
 
32
22
  def to_s
@@ -64,23 +54,29 @@ module Unleash
64
54
  result =
65
55
  if self.enabled
66
56
  self.strategies.empty? ||
67
- self.strategies.any?{ |s| strategy_enabled?(s, context) }
57
+ self.strategies.any? do |s|
58
+ strategy_enabled?(s, context) && strategy_constraint_matches?(s, context)
59
+ end
68
60
  else
69
61
  default_result
70
62
  end
71
63
 
72
- Unleash.logger.debug "FeatureToggle (enabled:#{self.enabled} default_result:#{default_result} " \
73
- "and Strategies combined returned #{result})"
64
+ Unleash.logger.debug "Unleash::FeatureToggle (enabled:#{self.enabled} default_result:#{default_result} " \
65
+ "and Strategies combined with contraints returned #{result})"
74
66
 
75
67
  result
76
68
  end
77
69
 
78
70
  def strategy_enabled?(strategy, context)
79
71
  r = Unleash::STRATEGIES.fetch(strategy.name.to_sym, :unknown).is_enabled?(strategy.params, context)
80
- Unleash.logger.debug "Strategy #{strategy.name} returned #{r} with context: #{context}" # "for params #{strategy.params} "
72
+ Unleash.logger.debug "Unleash::FeatureToggle.strategy_enabled? Strategy #{strategy.name} returned #{r} with context: #{context}"
81
73
  r
82
74
  end
83
75
 
76
+ def strategy_constraint_matches?(strategy, context)
77
+ strategy.constraints.empty? || strategy.constraints.all?{ |c| c.matches_context?(context) }
78
+ end
79
+
84
80
  def disabled_variant
85
81
  Unleash::Variant.new(name: 'disabled', enabled: false)
86
82
  end
@@ -127,5 +123,36 @@ module Unleash
127
123
  end
128
124
  context
129
125
  end
126
+
127
+ def initialize_strategies(params)
128
+ params.fetch('strategies', [])
129
+ .select{ |s| s.has_key?('name') && Unleash::STRATEGIES.has_key?(s['name'].to_sym) }
130
+ .map do |s|
131
+ ActivationStrategy.new(
132
+ s['name'],
133
+ s['parameters'],
134
+ (s['constraints'] || []).map do |c|
135
+ Constraint.new(
136
+ c.fetch('contextName'),
137
+ c.fetch('operator'),
138
+ c.fetch('values')
139
+ )
140
+ end
141
+ )
142
+ end || []
143
+ end
144
+
145
+ def initialize_variant_definitions(params)
146
+ (params.fetch('variants', []) || [])
147
+ .select{ |v| v.is_a?(Hash) && v.has_key?('name') }
148
+ .map do |v|
149
+ VariantDefinition.new(
150
+ v.fetch('name', ''),
151
+ v.fetch('weight', 0),
152
+ v.fetch('payload', nil),
153
+ v.fetch('overrides', [])
154
+ )
155
+ end || []
156
+ end
130
157
  end
131
158
  end
@@ -0,0 +1,55 @@
1
+ require 'unleash/strategy/util'
2
+
3
+ module Unleash
4
+ module Strategy
5
+ class FlexibleRollout < Base
6
+ def name
7
+ 'flexibleRollout'
8
+ end
9
+
10
+ # need: params['percentage']
11
+ def is_enabled?(params = {}, context = nil)
12
+ return false unless params.is_a?(Hash)
13
+ return false unless context.class.name == 'Unleash::Context'
14
+
15
+ stickiness = params.fetch('stickiness', 'default')
16
+ stickiness_id = resolve_stickiness(stickiness, context)
17
+
18
+ begin
19
+ percentage = Integer(params.fetch('rollout', 0))
20
+ percentage = 0 if percentage > 100 || percentage.negative?
21
+ rescue ArgumentError
22
+ return false
23
+ end
24
+
25
+ group_id = params.fetch('groupId', '')
26
+ normalized_number = Util.get_normalized_number(stickiness_id, group_id)
27
+
28
+ return false if stickiness_id.nil?
29
+
30
+ (percentage.positive? && normalized_number <= percentage)
31
+ end
32
+
33
+ private
34
+
35
+ def random
36
+ Random.rand(0..100)
37
+ end
38
+
39
+ def resolve_stickiness(stickiness, context)
40
+ case stickiness
41
+ when 'userId'
42
+ context.user_id
43
+ when 'sessionId'
44
+ context.session_id
45
+ when 'random'
46
+ random
47
+ when 'default'
48
+ context.user_id || context.session_id || random
49
+ else
50
+ nil
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -11,7 +11,7 @@ module Unleash
11
11
  def is_enabled?(params = {}, context = nil)
12
12
  return false unless params.is_a?(Hash) && params.has_key?('percentage')
13
13
  return false unless context.class.name == 'Unleash::Context'
14
- return false if context.session_id.empty?
14
+ return false if context.session_id.nil? || context.session_id.empty?
15
15
 
16
16
  percentage = Integer(params['percentage'] || 0)
17
17
  (percentage.positive? && Util.get_normalized_number(context.session_id, params['groupId'] || "") <= percentage)
@@ -8,10 +8,10 @@ module Unleash
8
8
  end
9
9
 
10
10
  # need: params['percentage'], params['groupId'], context.user_id,
11
- def is_enabled?(params = {}, context = nil)
11
+ def is_enabled?(params = {}, context = nil, _constraints = [])
12
12
  return false unless params.is_a?(Hash) && params.has_key?('percentage')
13
13
  return false unless context.class.name == 'Unleash::Context'
14
- return false if context.user_id.empty?
14
+ return false if context.user_id.nil? || context.user_id.empty?
15
15
 
16
16
  percentage = Integer(params['percentage'] || 0)
17
17
  (percentage.positive? && Util.get_normalized_number(context.user_id, params['groupId'] || "") <= percentage)
@@ -71,6 +71,7 @@ module Unleash
71
71
  self.toggle_lock.synchronize do
72
72
  file = File.open(backup_file_tmp, "w")
73
73
  file.write(self.toggle_cache.to_json)
74
+ file.close
74
75
  File.rename(backup_file_tmp, backup_file)
75
76
  end
76
77
  rescue StandardError => e
@@ -1,3 +1,3 @@
1
1
  module Unleash
2
- VERSION = "3.2.1".freeze
2
+ VERSION = "3.2.2".freeze
3
3
  end
@@ -21,14 +21,14 @@ Gem::Specification.new do |spec|
21
21
  spec.bindir = 'bin'
22
22
  spec.executables = spec.files.grep(%r{^bin/unleash}) { |f| File.basename(f) }
23
23
  spec.require_paths = ["lib"]
24
- spec.required_ruby_version = "~> 2.4"
24
+ spec.required_ruby_version = ">= 2.5"
25
25
 
26
26
  spec.add_dependency "murmurhash3", "~> 0.1.6"
27
27
 
28
28
  spec.add_development_dependency "bundler", "~> 2.1"
29
29
  spec.add_development_dependency "rake", "~> 12.3"
30
30
  spec.add_development_dependency "rspec", "~> 3.9"
31
- spec.add_development_dependency "rspec-json_expectations", "~> 2.1"
31
+ spec.add_development_dependency "rspec-json_expectations", "~> 2.2"
32
32
  spec.add_development_dependency "webmock", "~> 3.8"
33
33
 
34
34
  spec.add_development_dependency "coveralls", "~> 0.8"
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: 3.2.1
4
+ version: 3.2.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Renato Arruda
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-05-05 00:00:00.000000000 Z
11
+ date: 2021-03-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: murmurhash3
@@ -72,14 +72,14 @@ dependencies:
72
72
  requirements:
73
73
  - - "~>"
74
74
  - !ruby/object:Gem::Version
75
- version: '2.1'
75
+ version: '2.2'
76
76
  type: :development
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
80
  - - "~>"
81
81
  - !ruby/object:Gem::Version
82
- version: '2.1'
82
+ version: '2.2'
83
83
  - !ruby/object:Gem::Dependency
84
84
  name: webmock
85
85
  requirement: !ruby/object:Gem::Requirement
@@ -148,6 +148,7 @@ files:
148
148
  - lib/unleash/activation_strategy.rb
149
149
  - lib/unleash/client.rb
150
150
  - lib/unleash/configuration.rb
151
+ - lib/unleash/constraint.rb
151
152
  - lib/unleash/context.rb
152
153
  - lib/unleash/feature_toggle.rb
153
154
  - lib/unleash/metrics.rb
@@ -156,6 +157,7 @@ files:
156
157
  - lib/unleash/strategy/application_hostname.rb
157
158
  - lib/unleash/strategy/base.rb
158
159
  - lib/unleash/strategy/default.rb
160
+ - lib/unleash/strategy/flexible_rollout.rb
159
161
  - lib/unleash/strategy/gradual_rollout_random.rb
160
162
  - lib/unleash/strategy/gradual_rollout_sessionid.rb
161
163
  - lib/unleash/strategy/gradual_rollout_userid.rb
@@ -173,23 +175,23 @@ homepage: https://github.com/unleash/unleash-client-ruby
173
175
  licenses:
174
176
  - Apache-2.0
175
177
  metadata: {}
176
- post_install_message:
178
+ post_install_message:
177
179
  rdoc_options: []
178
180
  require_paths:
179
181
  - lib
180
182
  required_ruby_version: !ruby/object:Gem::Requirement
181
183
  requirements:
182
- - - "~>"
184
+ - - ">="
183
185
  - !ruby/object:Gem::Version
184
- version: '2.4'
186
+ version: '2.5'
185
187
  required_rubygems_version: !ruby/object:Gem::Requirement
186
188
  requirements:
187
189
  - - ">="
188
190
  - !ruby/object:Gem::Version
189
191
  version: '0'
190
192
  requirements: []
191
- rubygems_version: 3.1.2
192
- signing_key:
193
+ rubygems_version: 3.2.3
194
+ signing_key:
193
195
  specification_version: 4
194
196
  summary: Unleash feature toggle client.
195
197
  test_files: []