unleash 3.2.0 → 3.2.4
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/.gitignore +1 -0
- data/.rubocop.yml +13 -7
- data/.travis.yml +3 -4
- data/README.md +3 -3
- data/examples/simple.rb +2 -2
- data/lib/unleash/activation_strategy.rb +16 -3
- data/lib/unleash/client.rb +11 -3
- data/lib/unleash/constraint.rb +26 -0
- data/lib/unleash/context.rb +18 -5
- data/lib/unleash/feature_toggle.rb +44 -17
- data/lib/unleash/strategy/flexible_rollout.rb +55 -0
- data/lib/unleash/strategy/gradual_rollout_sessionid.rb +1 -1
- data/lib/unleash/strategy/gradual_rollout_userid.rb +2 -2
- data/lib/unleash/toggle_fetcher.rb +2 -1
- data/lib/unleash/version.rb +1 -1
- data/lib/unleash.rb +1 -1
- data/unleash-client.gemspec +7 -7
- metadata +19 -17
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 2cf913a6af33742c1001e328f96a5382eb0666b99280d008d6960e105681ded2
         | 
| 4 | 
            +
              data.tar.gz: e6cbc6db182f1212e4580e8781b7ac6444b12f26116dc922a4568d728299399a
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: b6499b2f136c8dcf6b7f115b29ad762a336a3e8eac669f4f87577f542242cc9a2cdcb41b36101d9483111217274afc6e07a01d3217393fc7fcd76854866fd74d
         | 
| 7 | 
            +
              data.tar.gz: e3d5d385c49552035aa9700db64ccb44320e3265cb415d03f9a1d135ab09d126bb10bb0834b72e3b9d7fec4a5340f612d63024e7259ca85d0024684eedd6230f
         | 
    
        data/.gitignore
    CHANGED
    
    
    
        data/.rubocop.yml
    CHANGED
    
    | @@ -1,12 +1,15 @@ | |
| 1 1 | 
             
            # inherit_from: .rubocop_todo.yml
         | 
| 2 2 |  | 
| 3 | 
            +
            AllCops:
         | 
| 4 | 
            +
              TargetRubyVersion: 2.5
         | 
| 5 | 
            +
             | 
| 3 6 | 
             
            Naming/PredicateName:
         | 
| 4 | 
            -
               | 
| 7 | 
            +
              AllowedMethods:
         | 
| 5 8 | 
             
                - is_enabled?
         | 
| 6 9 |  | 
| 7 10 | 
             
            Metrics/ClassLength:
         | 
| 8 11 | 
             
              Max: 120
         | 
| 9 | 
            -
             | 
| 12 | 
            +
            Layout/LineLength:
         | 
| 10 13 | 
             
              Max: 140
         | 
| 11 14 | 
             
            Metrics/MethodLength:
         | 
| 12 15 | 
             
              Max: 20
         | 
| @@ -19,9 +22,9 @@ Metrics/BlockLength: | |
| 19 22 | 
             
            Metrics/AbcSize:
         | 
| 20 23 | 
             
              Max: 25
         | 
| 21 24 | 
             
            Metrics/CyclomaticComplexity:
         | 
| 22 | 
            -
              Max:  | 
| 25 | 
            +
              Max: 9
         | 
| 23 26 | 
             
            Metrics/PerceivedComplexity:
         | 
| 24 | 
            -
              Max:  | 
| 27 | 
            +
              Max: 9
         | 
| 25 28 |  | 
| 26 29 | 
             
            Style/Documentation:
         | 
| 27 30 | 
             
              Enabled: false
         | 
| @@ -42,9 +45,12 @@ Style/FrozenStringLiteralComment: | |
| 42 45 | 
             
            Style/GuardClause:
         | 
| 43 46 | 
             
              MinBodyLength: 8
         | 
| 44 47 |  | 
| 45 | 
            -
            Style/ | 
| 46 | 
            -
               | 
| 47 | 
            -
             | 
| 48 | 
            +
            Style/HashEachMethods:
         | 
| 49 | 
            +
              Enabled: true
         | 
| 50 | 
            +
            Style/HashTransformKeys:
         | 
| 51 | 
            +
              Enabled: true
         | 
| 52 | 
            +
            Style/HashTransformValues:
         | 
| 53 | 
            +
              Enabled: true
         | 
| 48 54 |  | 
| 49 55 | 
             
            Style/IfInsideElse:
         | 
| 50 56 | 
             
              Exclude:
         | 
    
        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 | 
            -
            - gem install bundler -v 2. | 
| 11 | 
            -
            - git clone --depth 5 --branch v3. | 
| 12 | 
            -
              client-specification
         | 
| 10 | 
            +
            - gem install bundler -v 2.1.4
         | 
| 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. | 
| 24 | 
            +
            gem 'unleash', '~> 3.2.4'
         | 
| 25 25 | 
             
            ```
         | 
| 26 26 |  | 
| 27 27 | 
             
            And then execute:
         | 
| @@ -66,7 +66,7 @@ Argument | Description | Required? |  Type |  Default Value| | |
| 66 66 | 
             
            `disable_metrics` | Disables sending metrics to Unleash server. | N | Boolean | `false` |
         | 
| 67 67 | 
             
            `custom_http_headers` | Custom headers to send to Unleash. | N | Hash | {} |
         | 
| 68 68 | 
             
            `timeout` | How long to wait for the connection to be established or wait in reading state (open_timeout/read_timeout) | N | Integer | 30 |
         | 
| 69 | 
            -
            `retry_limit` | How many consecutive failures in connecting to the Unleash server are allowed before giving up. | N |  | 
| 69 | 
            +
            `retry_limit` | How many consecutive failures in connecting to the Unleash server are allowed before giving up. Use `Float::INFINITY` if you would like it to never give up. | N | Numeric | 5 |
         | 
| 70 70 | 
             
            `backup_file` | Filename to store the last known state from the Unleash server. Best to not change this from the default. | N | String | `Dir.tmpdir + "/unleash-#{app_name}-repo.json` |
         | 
| 71 71 | 
             
            `logger` | Specify a custom `Logger` class to handle logs for the Unleash client. | N | Class | `Logger.new(STDOUT)` |
         | 
| 72 72 | 
             
            `log_level` | Change the log level for the `Logger` class. Constant from `Logger::Severity`. | N | Constant | `Logger::ERROR` |
         | 
    
        data/examples/simple.rb
    CHANGED
    
    | @@ -17,13 +17,13 @@ puts ">> START simple.rb" | |
| 17 17 | 
             
            # or:
         | 
| 18 18 |  | 
| 19 19 | 
             
            @unleash = Unleash::Client.new(
         | 
| 20 | 
            -
              url: ' | 
| 20 | 
            +
              url: 'https://app.unleash-hosted.com/demo/api',
         | 
| 21 21 | 
             
              app_name: 'simple-test',
         | 
| 22 22 | 
             
              instance_id: 'local-test-cli',
         | 
| 23 23 | 
             
              refresh_interval: 2,
         | 
| 24 24 | 
             
              metrics_interval: 2,
         | 
| 25 25 | 
             
              retry_limit: 2,
         | 
| 26 | 
            -
               | 
| 26 | 
            +
              custom_http_headers: {'Authorization': '943ca9171e2c884c545c5d82417a655fb77cec970cc3b78a8ff87f4406b495d0'},
         | 
| 27 27 | 
             
            )
         | 
| 28 28 |  | 
| 29 29 | 
             
            # feature_name = "AwesomeFeature"
         | 
| @@ -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  | 
| 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
         | 
    
        data/lib/unleash/client.rb
    CHANGED
    
    | @@ -60,7 +60,7 @@ module Unleash | |
| 60 60 | 
             
                  Unleash.logger.debug "Unleash::Client.get_variant for feature: #{feature} with context #{context}"
         | 
| 61 61 |  | 
| 62 62 | 
             
                  if Unleash.configuration.disable_client
         | 
| 63 | 
            -
                    Unleash.logger.debug "unleash_client is disabled! Always returning #{ | 
| 63 | 
            +
                    Unleash.logger.debug "unleash_client is disabled! Always returning #{fallback_variant} for feature #{feature}!"
         | 
| 64 64 | 
             
                    return fallback_variant || Unleash::FeatureToggle.disabled_variant
         | 
| 65 65 | 
             
                  end
         | 
| 66 66 |  | 
| @@ -116,7 +116,11 @@ module Unleash | |
| 116 116 |  | 
| 117 117 | 
             
                def start_toggle_fetcher
         | 
| 118 118 | 
             
                  Unleash.toggle_fetcher = Unleash::ToggleFetcher.new
         | 
| 119 | 
            -
                  self.fetcher_scheduled_executor = Unleash::ScheduledExecutor.new( | 
| 119 | 
            +
                  self.fetcher_scheduled_executor = Unleash::ScheduledExecutor.new(
         | 
| 120 | 
            +
                    'ToggleFetcher',
         | 
| 121 | 
            +
                    Unleash.configuration.refresh_interval,
         | 
| 122 | 
            +
                    Unleash.configuration.retry_limit
         | 
| 123 | 
            +
                  )
         | 
| 120 124 | 
             
                  self.fetcher_scheduled_executor.run do
         | 
| 121 125 | 
             
                    Unleash.toggle_fetcher.fetch
         | 
| 122 126 | 
             
                  end
         | 
| @@ -125,7 +129,11 @@ module Unleash | |
| 125 129 | 
             
                def start_metrics
         | 
| 126 130 | 
             
                  Unleash.toggle_metrics = Unleash::Metrics.new
         | 
| 127 131 | 
             
                  Unleash.reporter = Unleash::MetricsReporter.new
         | 
| 128 | 
            -
                  self.metrics_scheduled_executor = Unleash::ScheduledExecutor.new( | 
| 132 | 
            +
                  self.metrics_scheduled_executor = Unleash::ScheduledExecutor.new(
         | 
| 133 | 
            +
                    'MetricsReporter',
         | 
| 134 | 
            +
                    Unleash.configuration.metrics_interval,
         | 
| 135 | 
            +
                    Unleash.configuration.retry_limit
         | 
| 136 | 
            +
                  )
         | 
| 129 137 | 
             
                  self.metrics_scheduled_executor.run do
         | 
| 130 138 | 
             
                    Unleash.reporter.send
         | 
| 131 139 | 
             
                  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
         | 
    
        data/lib/unleash/context.rb
    CHANGED
    
    | @@ -1,6 +1,8 @@ | |
| 1 1 | 
             
            module Unleash
         | 
| 2 2 | 
             
              class Context
         | 
| 3 | 
            -
                 | 
| 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=#{ | 
| 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. | 
| 21 | 
            -
             | 
| 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? | 
| 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}" | 
| 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)
         | 
| @@ -48,7 +48,7 @@ module Unleash | |
| 48 48 | 
             
                  self.etag = response['ETag']
         | 
| 49 49 | 
             
                  response_hash = JSON.parse(response.body)
         | 
| 50 50 |  | 
| 51 | 
            -
                  if response_hash['version']  | 
| 51 | 
            +
                  if response_hash['version'] >= 1
         | 
| 52 52 | 
             
                    features = response_hash['features']
         | 
| 53 53 | 
             
                  else
         | 
| 54 54 | 
             
                    raise NotImplemented, "Version of features provided by unleash server" \
         | 
| @@ -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
         | 
    
        data/lib/unleash/version.rb
    CHANGED
    
    
    
        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
         | 
    
        data/unleash-client.gemspec
    CHANGED
    
    | @@ -21,16 +21,16 @@ 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 = " | 
| 24 | 
            +
              spec.required_ruby_version = ">= 2.5"
         | 
| 25 25 |  | 
| 26 26 | 
             
              spec.add_dependency "murmurhash3", "~> 0.1.6"
         | 
| 27 27 |  | 
| 28 | 
            -
              spec.add_development_dependency "bundler", "~> 2. | 
| 29 | 
            -
              spec.add_development_dependency "rake", "~>  | 
| 30 | 
            -
              spec.add_development_dependency "rspec", "~> 3. | 
| 31 | 
            -
              spec.add_development_dependency "rspec-json_expectations", "~> 2. | 
| 32 | 
            -
              spec.add_development_dependency "webmock", "~> 3. | 
| 28 | 
            +
              spec.add_development_dependency "bundler", "~> 2.1"
         | 
| 29 | 
            +
              spec.add_development_dependency "rake", "~> 12.3"
         | 
| 30 | 
            +
              spec.add_development_dependency "rspec", "~> 3.9"
         | 
| 31 | 
            +
              spec.add_development_dependency "rspec-json_expectations", "~> 2.2"
         | 
| 32 | 
            +
              spec.add_development_dependency "webmock", "~> 3.8"
         | 
| 33 33 |  | 
| 34 34 | 
             
              spec.add_development_dependency "coveralls", "~> 0.8"
         | 
| 35 | 
            -
              spec.add_development_dependency "rubocop", "~> 0. | 
| 35 | 
            +
              spec.add_development_dependency "rubocop", "~> 0.80"
         | 
| 36 36 | 
             
            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: 3.2. | 
| 4 | 
            +
              version: 3.2.4
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - Renato Arruda
         | 
| 8 8 | 
             
            autorequire: 
         | 
| 9 9 | 
             
            bindir: bin
         | 
| 10 10 | 
             
            cert_chain: []
         | 
| 11 | 
            -
            date:  | 
| 11 | 
            +
            date: 2021-09-20 00:00:00.000000000 Z
         | 
| 12 12 | 
             
            dependencies:
         | 
| 13 13 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 14 14 | 
             
              name: murmurhash3
         | 
| @@ -30,70 +30,70 @@ dependencies: | |
| 30 30 | 
             
                requirements:
         | 
| 31 31 | 
             
                - - "~>"
         | 
| 32 32 | 
             
                  - !ruby/object:Gem::Version
         | 
| 33 | 
            -
                    version: '2. | 
| 33 | 
            +
                    version: '2.1'
         | 
| 34 34 | 
             
              type: :development
         | 
| 35 35 | 
             
              prerelease: false
         | 
| 36 36 | 
             
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 37 37 | 
             
                requirements:
         | 
| 38 38 | 
             
                - - "~>"
         | 
| 39 39 | 
             
                  - !ruby/object:Gem::Version
         | 
| 40 | 
            -
                    version: '2. | 
| 40 | 
            +
                    version: '2.1'
         | 
| 41 41 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 42 42 | 
             
              name: rake
         | 
| 43 43 | 
             
              requirement: !ruby/object:Gem::Requirement
         | 
| 44 44 | 
             
                requirements:
         | 
| 45 45 | 
             
                - - "~>"
         | 
| 46 46 | 
             
                  - !ruby/object:Gem::Version
         | 
| 47 | 
            -
                    version: ' | 
| 47 | 
            +
                    version: '12.3'
         | 
| 48 48 | 
             
              type: :development
         | 
| 49 49 | 
             
              prerelease: false
         | 
| 50 50 | 
             
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 51 51 | 
             
                requirements:
         | 
| 52 52 | 
             
                - - "~>"
         | 
| 53 53 | 
             
                  - !ruby/object:Gem::Version
         | 
| 54 | 
            -
                    version: ' | 
| 54 | 
            +
                    version: '12.3'
         | 
| 55 55 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 56 56 | 
             
              name: rspec
         | 
| 57 57 | 
             
              requirement: !ruby/object:Gem::Requirement
         | 
| 58 58 | 
             
                requirements:
         | 
| 59 59 | 
             
                - - "~>"
         | 
| 60 60 | 
             
                  - !ruby/object:Gem::Version
         | 
| 61 | 
            -
                    version: '3. | 
| 61 | 
            +
                    version: '3.9'
         | 
| 62 62 | 
             
              type: :development
         | 
| 63 63 | 
             
              prerelease: false
         | 
| 64 64 | 
             
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 65 65 | 
             
                requirements:
         | 
| 66 66 | 
             
                - - "~>"
         | 
| 67 67 | 
             
                  - !ruby/object:Gem::Version
         | 
| 68 | 
            -
                    version: '3. | 
| 68 | 
            +
                    version: '3.9'
         | 
| 69 69 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 70 70 | 
             
              name: rspec-json_expectations
         | 
| 71 71 | 
             
              requirement: !ruby/object:Gem::Requirement
         | 
| 72 72 | 
             
                requirements:
         | 
| 73 73 | 
             
                - - "~>"
         | 
| 74 74 | 
             
                  - !ruby/object:Gem::Version
         | 
| 75 | 
            -
                    version: '2. | 
| 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. | 
| 82 | 
            +
                    version: '2.2'
         | 
| 83 83 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 84 84 | 
             
              name: webmock
         | 
| 85 85 | 
             
              requirement: !ruby/object:Gem::Requirement
         | 
| 86 86 | 
             
                requirements:
         | 
| 87 87 | 
             
                - - "~>"
         | 
| 88 88 | 
             
                  - !ruby/object:Gem::Version
         | 
| 89 | 
            -
                    version: '3. | 
| 89 | 
            +
                    version: '3.8'
         | 
| 90 90 | 
             
              type: :development
         | 
| 91 91 | 
             
              prerelease: false
         | 
| 92 92 | 
             
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 93 93 | 
             
                requirements:
         | 
| 94 94 | 
             
                - - "~>"
         | 
| 95 95 | 
             
                  - !ruby/object:Gem::Version
         | 
| 96 | 
            -
                    version: '3. | 
| 96 | 
            +
                    version: '3.8'
         | 
| 97 97 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 98 98 | 
             
              name: coveralls
         | 
| 99 99 | 
             
              requirement: !ruby/object:Gem::Requirement
         | 
| @@ -114,14 +114,14 @@ dependencies: | |
| 114 114 | 
             
                requirements:
         | 
| 115 115 | 
             
                - - "~>"
         | 
| 116 116 | 
             
                  - !ruby/object:Gem::Version
         | 
| 117 | 
            -
                    version: '0. | 
| 117 | 
            +
                    version: '0.80'
         | 
| 118 118 | 
             
              type: :development
         | 
| 119 119 | 
             
              prerelease: false
         | 
| 120 120 | 
             
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 121 121 | 
             
                requirements:
         | 
| 122 122 | 
             
                - - "~>"
         | 
| 123 123 | 
             
                  - !ruby/object:Gem::Version
         | 
| 124 | 
            -
                    version: '0. | 
| 124 | 
            +
                    version: '0.80'
         | 
| 125 125 | 
             
            description: |-
         | 
| 126 126 | 
             
              This is the ruby client for Unleash, a powerful feature toggle system
         | 
| 127 127 | 
             
                  that gives you a great overview over all feature toggles across all your applications and services.
         | 
| @@ -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
         | 
| @@ -179,16 +181,16 @@ 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. | 
| 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. | 
| 193 | 
            +
            rubygems_version: 3.2.3
         | 
| 192 194 | 
             
            signing_key: 
         | 
| 193 195 | 
             
            specification_version: 4
         | 
| 194 196 | 
             
            summary: Unleash feature toggle client.
         |