unleash 5.1.1 → 6.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,117 +0,0 @@
1
- require 'date'
2
- module Unleash
3
- class Constraint
4
- attr_accessor :context_name, :operator, :value, :inverted, :case_insensitive
5
-
6
- OPERATORS = {
7
- IN: ->(context_v, constraint_v){ constraint_v.include? context_v.to_s },
8
- NOT_IN: ->(context_v, constraint_v){ !constraint_v.include? context_v.to_s },
9
- STR_STARTS_WITH: ->(context_v, constraint_v){ constraint_v.any?{ |v| context_v.start_with? v } },
10
- STR_ENDS_WITH: ->(context_v, constraint_v){ constraint_v.any?{ |v| context_v.end_with? v } },
11
- STR_CONTAINS: ->(context_v, constraint_v){ constraint_v.any?{ |v| context_v.include? v } },
12
- NUM_EQ: ->(context_v, constraint_v){ on_valid_float(constraint_v, context_v){ |x, y| (x - y).abs < Float::EPSILON } },
13
- NUM_LT: ->(context_v, constraint_v){ on_valid_float(constraint_v, context_v){ |x, y| (x > y) } },
14
- NUM_LTE: ->(context_v, constraint_v){ on_valid_float(constraint_v, context_v){ |x, y| (x >= y) } },
15
- NUM_GT: ->(context_v, constraint_v){ on_valid_float(constraint_v, context_v){ |x, y| (x < y) } },
16
- NUM_GTE: ->(context_v, constraint_v){ on_valid_float(constraint_v, context_v){ |x, y| (x <= y) } },
17
- DATE_AFTER: ->(context_v, constraint_v){ on_valid_date(constraint_v, context_v){ |x, y| (x < y) } },
18
- DATE_BEFORE: ->(context_v, constraint_v){ on_valid_date(constraint_v, context_v){ |x, y| (x > y) } },
19
- SEMVER_EQ: ->(context_v, constraint_v){ on_valid_version(constraint_v, context_v){ |x, y| (x == y) } },
20
- SEMVER_GT: ->(context_v, constraint_v){ on_valid_version(constraint_v, context_v){ |x, y| (x < y) } },
21
- SEMVER_LT: ->(context_v, constraint_v){ on_valid_version(constraint_v, context_v){ |x, y| (x > y) } },
22
- FALLBACK_VALIDATOR: ->(_context_v, _constraint_v){ false }
23
- }.freeze
24
-
25
- STRING_OPERATORS = [:STR_STARTS_WITH, :STR_ENDS_WITH, :STR_CONTAINS].freeze
26
-
27
- LIST_OPERATORS = [:IN, :NOT_IN, :STR_STARTS_WITH, :STR_ENDS_WITH, :STR_CONTAINS].freeze
28
-
29
- def initialize(context_name, operator, value = [], inverted: false, case_insensitive: false)
30
- raise ArgumentError, "context_name is not a String" unless context_name.is_a?(String)
31
-
32
- unless OPERATORS.include? operator.to_sym
33
- Unleash.logger.warn "Operator #{operator} is not a supported operator, " \
34
- "falling back to FALLBACK_VALIDATOR which skips this constraint."
35
- operator = "FALLBACK_VALIDATOR"
36
- end
37
- self.log_inconsistent_constraint_configuration(operator.to_sym, value)
38
-
39
- self.context_name = context_name
40
- self.operator = operator.to_sym
41
- self.value = value
42
- self.inverted = !!inverted
43
- self.case_insensitive = !!case_insensitive
44
- end
45
-
46
- def matches_context?(context)
47
- Unleash.logger.debug "Unleash::Constraint matches_context? value: #{self.value} context.get_by_name(#{self.context_name})"
48
- return false if context.nil?
49
-
50
- match = matches_constraint?(context)
51
- self.inverted ? !match : match
52
- rescue KeyError
53
- Unleash.logger.warn "Attemped to resolve a context key during constraint resolution: #{self.context_name} but it wasn't \
54
- found on the context"
55
- false
56
- end
57
-
58
- def self.on_valid_date(val1, val2)
59
- val1 = DateTime.parse(val1)
60
- val2 = val2.is_a?(DateTime) ? val2 : DateTime.parse(val2)
61
- yield(val1, val2)
62
- rescue ArgumentError, TypeError
63
- Unleash.logger.warn "Unleash::ConstraintMatcher unable to parse either context_value (#{val1}) \
64
- or constraint_value (#{val2}) into a date. Returning false!"
65
- false
66
- end
67
-
68
- def self.on_valid_float(val1, val2)
69
- val1 = Float(val1)
70
- val2 = Float(val2)
71
- yield(val1, val2)
72
- rescue ArgumentError
73
- Unleash.logger.warn "Unleash::ConstraintMatcher unable to parse either context_value (#{val1}) \
74
- or constraint_value (#{val2}) into a number. Returning false!"
75
- false
76
- end
77
-
78
- def self.on_valid_version(val1, val2)
79
- val1 = Gem::Version.new(val1)
80
- val2 = Gem::Version.new(val2)
81
- yield(val1, val2)
82
- rescue ArgumentError
83
- Unleash.logger.warn "Unleash::ConstraintMatcher unable to parse either context_value (#{val1}) \
84
- or constraint_value (#{val2}) into a version. Return false!"
85
- false
86
- end
87
-
88
- # This should be a private method but for some reason this fails on Ruby 2.5
89
- def log_inconsistent_constraint_configuration(operator, value)
90
- Unleash.logger.warn "value is a String, operator is expecting an Array" if LIST_OPERATORS.include?(operator) && value.is_a?(String)
91
- Unleash.logger.warn "value is an Array, operator is expecting a String" if !LIST_OPERATORS.include?(operator) && value.is_a?(Array)
92
- end
93
-
94
- private
95
-
96
- def matches_constraint?(context)
97
- Unleash.logger.debug "Unleash::Constraint matches_constraint? value: #{self.value} operator: #{self.operator} " \
98
- " context.get_by_name(#{self.context_name})"
99
-
100
- unless OPERATORS.include?(self.operator)
101
- Unleash.logger.warn "Invalid constraint operator: #{self.operator}, this should be unreachable. Always returning false."
102
- false
103
- end
104
-
105
- # when the operator is NOT_IN and there is no data, return true. In all other cases the operator doesn't match.
106
- return self.operator == :NOT_IN unless context.include?(self.context_name)
107
-
108
- v = self.value.dup
109
- context_value = context.get_by_name(self.context_name)
110
-
111
- # always return false, if we are comparing a non string with a string operator:
112
- return false if !context_value.is_a?(String) && STRING_OPERATORS.include?(self.operator)
113
-
114
- OPERATORS[self.operator].call(*self.case_insensitive ? [context_value.upcase, v.map(&:upcase)] : [context_value, v])
115
- end
116
- end
117
- end
@@ -1,253 +0,0 @@
1
- require 'unleash/activation_strategy'
2
- require 'unleash/constraint'
3
- require 'unleash/variant_definition'
4
- require 'unleash/variant'
5
- require 'unleash/strategy/util'
6
- require 'securerandom'
7
-
8
- module Unleash
9
- class FeatureToggle
10
- attr_accessor :name, :enabled, :dependencies, :strategies, :variant_definitions
11
-
12
- FeatureEvaluationResult = Struct.new(:enabled?, :strategy)
13
-
14
- def initialize(params = {}, segment_map = {})
15
- params = {} if params.nil?
16
-
17
- self.name = params.fetch('name', nil)
18
- self.enabled = params.fetch('enabled', false)
19
- self.dependencies = params.fetch('dependencies', [])
20
-
21
- self.strategies = initialize_strategies(params, segment_map)
22
- self.variant_definitions = initialize_variant_definitions(params)
23
- end
24
-
25
- def to_s
26
- "<FeatureToggle: name=#{name},enabled=#{enabled},strategies=#{strategies},variant_definitions=#{variant_definitions}>"
27
- end
28
-
29
- def is_enabled?(context)
30
- result = am_enabled?(context)
31
-
32
- choice = result ? :yes : :no
33
- Unleash.toggle_metrics.increment(name, choice) unless Unleash.configuration.disable_metrics
34
-
35
- result
36
- end
37
-
38
- def get_variant(context, fallback_variant = Unleash::FeatureToggle.disabled_variant)
39
- raise ArgumentError, "Provided fallback_variant is not of type Unleash::Variant" if fallback_variant.class.name != 'Unleash::Variant'
40
-
41
- context = ensure_valid_context(context)
42
-
43
- evaluation_result = evaluate(context)
44
-
45
- group_id = evaluation_result.strategy&.params.to_h['groupId'] || self.name
46
-
47
- variant = resolve_variant(context, evaluation_result, group_id)
48
-
49
- choice = evaluation_result.enabled? ? :yes : :no
50
- Unleash.toggle_metrics.increment_variant(self.name, choice, variant.name) unless Unleash.configuration.disable_metrics
51
-
52
- variant.feature_enabled = evaluation_result.enabled?
53
-
54
- variant
55
- end
56
-
57
- def self.disabled_variant
58
- Unleash::Variant.new(name: 'disabled', enabled: false, feature_enabled: false)
59
- end
60
-
61
- private
62
-
63
- def resolve_variant(context, evaluation_result, group_id)
64
- variant_strategy_stickiness = evaluation_result.strategy&.params.to_h['stickiness'] || 'default'
65
- variant_definitions = evaluation_result.strategy&.variant_definitions
66
- variant_definitions = self.variant_definitions if variant_definitions.nil? || variant_definitions.empty?
67
- return Unleash::FeatureToggle.disabled_variant unless evaluation_result.enabled?
68
- return Unleash::FeatureToggle.disabled_variant if sum_variant_defs_weights(variant_definitions) <= 0
69
-
70
- variant_from_override_match(context, variant_definitions) ||
71
- variant_from_weights(context, resolve_stickiness(variant_definitions, variant_strategy_stickiness), variant_definitions, group_id)
72
- end
73
-
74
- def resolve_stickiness(variant_definitions, variant_strategy_stickiness)
75
- variant_definitions&.map(&:stickiness)&.compact&.first || variant_strategy_stickiness
76
- end
77
-
78
- # only check if it is enabled, do not do metrics
79
- def am_enabled?(context)
80
- evaluate(context).enabled?
81
- end
82
-
83
- def parent_dependencies_satisfied?(context)
84
- dependencies.empty? || dependencies.all?{ |parent| evaluate_parent(parent, context) }
85
- end
86
-
87
- def evaluate_parent(parent, context)
88
- parent_toggle = get_parent(parent["feature"])
89
- return false if parent_toggle.nil? || !parent_toggle.dependencies.empty?
90
-
91
- evaluation_result = parent_toggle.is_enabled?(context)
92
- return !evaluation_result if parent["enabled"] == false
93
-
94
- return false unless evaluation_result
95
-
96
- return evaluation_result if parent["variants"].nil? || parent["variants"].empty?
97
-
98
- parent["variants"].include?(parent_toggle.get_variant(context).name)
99
- end
100
-
101
- def get_parent(feature)
102
- toggle_as_hash = Unleash&.toggles&.find{ |toggle| toggle['name'] == feature }
103
- if toggle_as_hash.nil?
104
- Unleash.logger.debug "Unleash::Client.is_enabled? feature: #{feature} not found"
105
- return nil
106
- end
107
-
108
- Unleash::FeatureToggle.new(toggle_as_hash, Unleash&.segment_cache)
109
- end
110
-
111
- def evaluate(context)
112
- evaluation_result =
113
- if !parent_dependencies_satisfied?(context)
114
- FeatureEvaluationResult.new(false, nil)
115
- elsif !self.enabled
116
- FeatureEvaluationResult.new(false, nil)
117
- elsif self.strategies.empty?
118
- FeatureEvaluationResult.new(true, nil)
119
- else
120
- strategy = self.strategies.find{ |s| strategy_enabled?(s, context) && strategy_constraint_matches?(s, context) }
121
- FeatureEvaluationResult.new(!strategy.nil?, strategy)
122
- end
123
-
124
- Unleash.logger.debug "Unleash::FeatureToggle (enabled:#{self.enabled}) " \
125
- "and Strategies combined with constraints returned #{evaluation_result})"
126
- evaluation_result
127
- end
128
-
129
- def strategy_enabled?(strategy, context)
130
- r = Unleash.strategies.fetch(strategy.name).is_enabled?(strategy.params, context)
131
- Unleash.logger.debug "Unleash::FeatureToggle.strategy_enabled? Strategy #{strategy.name} returned #{r} with context: #{context}"
132
- r
133
- end
134
-
135
- def strategy_constraint_matches?(strategy, context)
136
- return false if strategy.disabled
137
-
138
- strategy.constraints.empty? || strategy.constraints.all?{ |c| c.matches_context?(context) }
139
- end
140
-
141
- def sum_variant_defs_weights(variant_definitions)
142
- variant_definitions.map(&:weight).reduce(0, :+)
143
- end
144
-
145
- def variant_salt(context, stickiness = "default")
146
- begin
147
- return context.get_by_name(stickiness) if !context.nil? && stickiness != "default"
148
- rescue KeyError
149
- Unleash.logger.warn "Custom stickiness key (#{stickiness}) not found in the provided context #{context}. " \
150
- "Falling back to default behavior."
151
- end
152
- return context.user_id unless context&.user_id.to_s.empty?
153
- return context.session_id unless context&.session_id.to_s.empty?
154
- return context.remote_address unless context&.remote_address.to_s.empty?
155
-
156
- SecureRandom.random_number
157
- end
158
-
159
- def variant_from_override_match(context, variant_definitions)
160
- variant_definition = variant_definitions.find{ |vd| vd.override_matches_context?(context) }
161
- return nil if variant_definition.nil?
162
-
163
- Unleash::Variant.new(name: variant_definition.name, enabled: true, payload: variant_definition.payload)
164
- end
165
-
166
- def variant_from_weights(context, stickiness, variant_definitions, group_id)
167
- variant_weight = Unleash::Strategy::Util.get_normalized_number(
168
- variant_salt(context, stickiness),
169
- group_id,
170
- Unleash::Strategy::Util::VARIANT_NORMALIZER_SEED,
171
- sum_variant_defs_weights(variant_definitions)
172
- )
173
- prev_weights = 0
174
-
175
- variant_definition = variant_definitions
176
- .find do |v|
177
- res = (prev_weights + v.weight >= variant_weight)
178
- prev_weights += v.weight
179
- res
180
- end
181
- return self.disabled_variant if variant_definition.nil?
182
-
183
- Unleash::Variant.new(name: variant_definition.name, enabled: true, payload: variant_definition.payload)
184
- end
185
-
186
- def ensure_valid_context(context)
187
- unless ['NilClass', 'Unleash::Context'].include? context.class.name
188
- Unleash.logger.error "Provided context is not of the correct type #{context.class.name}, " \
189
- "please use Unleash::Context. Context set to nil."
190
- context = nil
191
- end
192
- context
193
- end
194
-
195
- def initialize_strategies(params, segment_map)
196
- (params.fetch('strategies', []) || [])
197
- .select{ |s| s.has_key?('name') && Unleash.strategies.includes?(s['name']) }
198
- .map do |s|
199
- ActivationStrategy.new(
200
- s['name'],
201
- s['parameters'],
202
- resolve_constraints(s, segment_map),
203
- resolve_variants(s)
204
- )
205
- end || []
206
- end
207
-
208
- def resolve_variants(strategy)
209
- (strategy.fetch("variants", []) || [])
210
- .select{ |variant| variant.is_a?(Hash) && variant.has_key?("name") }
211
- .map do |variant|
212
- VariantDefinition.new(
213
- variant.fetch("name", ""),
214
- variant.fetch("weight", 0),
215
- variant.fetch("payload", nil),
216
- variant.fetch("stickiness", nil),
217
- variant.fetch("overrides", [])
218
- )
219
- end
220
- end
221
-
222
- def resolve_constraints(strategy, segment_map)
223
- segment_constraints = (strategy["segments"] || []).map do |segment_id|
224
- segment_map[segment_id]&.fetch("constraints")
225
- end
226
- (strategy.fetch("constraints", []) + segment_constraints).flatten.map do |constraint|
227
- return nil if constraint.nil?
228
-
229
- Constraint.new(
230
- constraint.fetch('contextName'),
231
- constraint.fetch('operator'),
232
- constraint.fetch('value', nil) || constraint.fetch('values', nil),
233
- inverted: constraint.fetch('inverted', false),
234
- case_insensitive: constraint.fetch('caseInsensitive', false)
235
- )
236
- end
237
- end
238
-
239
- def initialize_variant_definitions(params)
240
- (params.fetch('variants', []) || [])
241
- .select{ |v| v.is_a?(Hash) && v.has_key?('name') }
242
- .map do |v|
243
- VariantDefinition.new(
244
- v.fetch('name', ''),
245
- v.fetch('weight', 0),
246
- v.fetch('payload', nil),
247
- v.fetch('stickiness', nil),
248
- v.fetch('overrides', [])
249
- )
250
- end || []
251
- end
252
- end
253
- end
@@ -1,41 +0,0 @@
1
- module Unleash
2
- class Metrics
3
- attr_accessor :features, :features_lock
4
-
5
- def initialize
6
- self.features = {}
7
- self.features_lock = Mutex.new
8
- end
9
-
10
- def to_s
11
- self.features_lock.synchronize do
12
- return self.features.to_json
13
- end
14
- end
15
-
16
- def increment(feature, choice)
17
- raise "InvalidArgument choice must be :yes or :no" unless [:yes, :no].include? choice
18
-
19
- self.features_lock.synchronize do
20
- self.features[feature] = { yes: 0, no: 0 } unless self.features.include? feature
21
- self.features[feature][choice] += 1
22
- end
23
- end
24
-
25
- def increment_variant(feature, choice, variant)
26
- self.features_lock.synchronize do
27
- self.features[feature] = { yes: 0, no: 0 } unless self.features.include? feature
28
- self.features[feature][choice] += 1
29
- self.features[feature]['variants'] = {} unless self.features[feature].include? 'variants'
30
- self.features[feature]['variants'][variant] = 0 unless self.features[feature]['variants'].include? variant
31
- self.features[feature]['variants'][variant] += 1
32
- end
33
- end
34
-
35
- def reset
36
- self.features_lock.synchronize do
37
- self.features = {}
38
- end
39
- end
40
- end
41
- end
@@ -1,26 +0,0 @@
1
- require 'socket'
2
-
3
- module Unleash
4
- module Strategy
5
- class ApplicationHostname < Base
6
- attr_accessor :hostname
7
-
8
- PARAM = 'hostnames'.freeze
9
-
10
- def initialize
11
- self.hostname = Socket.gethostname || 'undefined'
12
- end
13
-
14
- def name
15
- 'applicationHostname'
16
- end
17
-
18
- # need: :params['hostnames']
19
- def is_enabled?(params = {}, _context = nil)
20
- return false unless params.is_a?(Hash) && params.has_key?(PARAM)
21
-
22
- params[PARAM].split(",").map(&:strip).map(&:downcase).include?(self.hostname)
23
- end
24
- end
25
- end
26
- end
@@ -1,16 +0,0 @@
1
- module Unleash
2
- module Strategy
3
- class NotImplemented < RuntimeError
4
- end
5
-
6
- class Base
7
- def name
8
- raise NotImplemented, "Strategy is not implemented"
9
- end
10
-
11
- def is_enabled?(_params = {}, _context = nil)
12
- raise NotImplemented, "Strategy is not implemented"
13
- end
14
- end
15
- end
16
- end
@@ -1,13 +0,0 @@
1
- module Unleash
2
- module Strategy
3
- class Default < Base
4
- def name
5
- 'default'
6
- end
7
-
8
- def is_enabled?(_params = {}, _context = nil)
9
- true
10
- end
11
- end
12
- end
13
- end
@@ -1,64 +0,0 @@
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
-
14
- stickiness = params.fetch('stickiness', 'default')
15
- return false if context_invalid?(stickiness, context)
16
-
17
- stickiness_id = resolve_stickiness(stickiness, context)
18
-
19
- begin
20
- percentage = Integer(params.fetch('rollout', 0))
21
- percentage = 0 if percentage > 100 || percentage.negative?
22
- rescue ArgumentError
23
- return false
24
- end
25
-
26
- group_id = params.fetch('groupId', '')
27
- normalized_number = Util.get_normalized_number(stickiness_id, group_id, 0)
28
-
29
- return false if stickiness_id.nil?
30
-
31
- (percentage.positive? && normalized_number <= percentage)
32
- end
33
-
34
- private
35
-
36
- def context_invalid?(stickiness, context)
37
- return false if ['random', 'default'].include?(stickiness)
38
-
39
- !context.instance_of?(Unleash::Context)
40
- end
41
-
42
- def random
43
- Random.rand(0..10_000)
44
- end
45
-
46
- def resolve_stickiness(stickiness, context)
47
- case stickiness
48
- when 'random'
49
- random
50
- when 'default'
51
- return random unless context.instance_of?(Unleash::Context)
52
-
53
- context&.user_id || context&.session_id || random
54
- else
55
- begin
56
- context.get_by_name(stickiness)
57
- rescue KeyError
58
- nil
59
- end
60
- end
61
- end
62
- end
63
- end
64
- end
@@ -1,24 +0,0 @@
1
- require 'unleash/strategy/util'
2
-
3
- module Unleash
4
- module Strategy
5
- class GradualRolloutRandom < Base
6
- def name
7
- 'gradualRolloutRandom'
8
- end
9
-
10
- # need: params['percentage']
11
- def is_enabled?(params = {}, _context = nil)
12
- return false unless params.is_a?(Hash) && params.has_key?('percentage')
13
-
14
- begin
15
- percentage = Integer(params['percentage'] || 0)
16
- rescue ArgumentError
17
- return false
18
- end
19
-
20
- (percentage >= Random.rand(1..100))
21
- end
22
- end
23
- end
24
- end
@@ -1,21 +0,0 @@
1
- require 'unleash/strategy/util'
2
-
3
- module Unleash
4
- module Strategy
5
- class GradualRolloutSessionId < Base
6
- def name
7
- 'gradualRolloutSessionId'
8
- end
9
-
10
- # need: params['percentage'], params['groupId'], context.user_id,
11
- def is_enabled?(params = {}, context = nil)
12
- return false unless params.is_a?(Hash) && params.has_key?('percentage')
13
- return false unless context.instance_of?(Unleash::Context)
14
- return false if context.session_id.nil? || context.session_id.empty?
15
-
16
- percentage = Integer(params['percentage'] || 0)
17
- (percentage.positive? && Util.get_normalized_number(context.session_id, params['groupId'] || "", 0) <= percentage)
18
- end
19
- end
20
- end
21
- end
@@ -1,21 +0,0 @@
1
- require 'unleash/strategy/util'
2
-
3
- module Unleash
4
- module Strategy
5
- class GradualRolloutUserId < Base
6
- def name
7
- 'gradualRolloutUserId'
8
- end
9
-
10
- # need: params['percentage'], params['groupId'], context.user_id,
11
- def is_enabled?(params = {}, context = nil, _constraints = [])
12
- return false unless params.is_a?(Hash) && params.has_key?('percentage')
13
- return false unless context.instance_of?(Unleash::Context)
14
- return false if context.user_id.nil? || context.user_id.empty?
15
-
16
- percentage = Integer(params['percentage'] || 0)
17
- (percentage.positive? && Util.get_normalized_number(context.user_id, params['groupId'] || "", 0) <= percentage)
18
- end
19
- end
20
- end
21
- end
@@ -1,36 +0,0 @@
1
- module Unleash
2
- module Strategy
3
- class RemoteAddress < Base
4
- PARAM = 'IPs'.freeze
5
-
6
- def name
7
- 'remoteAddress'
8
- end
9
-
10
- # need: params['IPs'], context.remote_address
11
- def is_enabled?(params = {}, context = nil)
12
- return false unless params.is_a?(Hash) && params.has_key?(PARAM)
13
- return false unless params.fetch(PARAM, nil).is_a? String
14
- return false unless context.instance_of?(Unleash::Context)
15
-
16
- remote_address = ipaddr_or_nil_from_str(context.remote_address)
17
-
18
- params[PARAM]
19
- .split(',')
20
- .map(&:strip)
21
- .map{ |ipblock| ipaddr_or_nil_from_str(ipblock) }
22
- .compact
23
- .map{ |ipb| ipb.include? remote_address }
24
- .any?
25
- end
26
-
27
- private
28
-
29
- def ipaddr_or_nil_from_str(ip)
30
- IPAddr.new(ip)
31
- rescue StandardError
32
- nil
33
- end
34
- end
35
- end
36
- end
@@ -1,20 +0,0 @@
1
- module Unleash
2
- module Strategy
3
- class UserWithId < Base
4
- PARAM = 'userIds'.freeze
5
-
6
- def name
7
- 'userWithId'
8
- end
9
-
10
- # requires: params['userIds'], context.user_id,
11
- def is_enabled?(params = {}, context = nil)
12
- return false unless params.is_a?(Hash) && params.has_key?(PARAM)
13
- return false unless params.fetch(PARAM, nil).is_a? String
14
- return false unless context.instance_of?(Unleash::Context)
15
-
16
- params[PARAM].split(",").map(&:strip).include?(context.user_id)
17
- end
18
- end
19
- end
20
- end
@@ -1,17 +0,0 @@
1
- require 'murmurhash3'
2
-
3
- module Unleash
4
- module Strategy
5
- module Util
6
- module_function
7
-
8
- NORMALIZER = 100
9
- VARIANT_NORMALIZER_SEED = 86_028_157
10
-
11
- # convert the two strings () into a number between 1 and base (100 by default)
12
- def get_normalized_number(identifier, group_id, seed, base = NORMALIZER)
13
- MurmurHash3::V32.str_hash("#{group_id}:#{identifier}", seed) % base + 1
14
- end
15
- end
16
- end
17
- end