featureflow 0.5.1 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 14f14953f195ceeb742c570f0bf89c40436dc068
4
- data.tar.gz: 4036b8cc67e22251bfcaf7d83d2cc412f0e50dbb
2
+ SHA256:
3
+ metadata.gz: 6e44302ab7573a1fe36b0fac9deaac9958ab96d01b73a865e3c98d00bd71ebb3
4
+ data.tar.gz: 8b641fa3bd07be005e7cf6f722a441da2544d2fa1141b40c35e1d3abb4df6db6
5
5
  SHA512:
6
- metadata.gz: d75e26abe09444b7ecdc9e35a281be54fa14bf4398a358a99d32e94774d8b25abd81a558425c584532e0431942863e64c223bfd553680ee869a11802c6786bb5
7
- data.tar.gz: 7238f9574262b630d372f97ddf1d18b6d7ec01e000f3d4fd27a7191ca8969d09036484c3bed01bce690dcad32296d82f14739a1e973cb741e91c996ed105358a
6
+ metadata.gz: dd22e33787e281e6a61ca62464f771c3e4d1778d6893f6689ff189c57dbdf3c433e7b49b6a881f73e113f5b5d21216c7b3a6dd67e313c8aeaf4e317ed4c858b9
7
+ data.tar.gz: 679d1c6f0efbefca2497da6467109c22f93bdde4a6bc165dd5e6b0d03b35daaa02888c5aeec0296dec6f962fd0aeea5a257b701e26798b15f2d394fe324aa544
data/.gitignore CHANGED
@@ -1 +1,2 @@
1
1
  pkg
2
+ .idea
@@ -1,4 +1,8 @@
1
1
  # Change log
2
+ ## [0.6.0] - 2018-12-17
3
+ ### Fixed
4
+ - Use User over Context matching all other SDKs
5
+ - Use events.featureflow.io endpoint
2
6
  ## [0.5.1] - 2017-07-17
3
7
  ### Fixed
4
8
  - Feature registration now compatible with Ruby 2.4
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- featureflow (0.5.0)
4
+ featureflow (0.6.0)
5
5
  excon (~> 0.57.0)
6
6
 
7
7
  GEM
@@ -20,7 +20,7 @@ GEM
20
20
  gherkin (~> 4.0)
21
21
  cucumber-wire (0.0.1)
22
22
  diff-lcs (1.3)
23
- excon (0.57.0)
23
+ excon (0.57.1)
24
24
  gherkin (4.1.3)
25
25
  multi_json (1.12.1)
26
26
  multi_test (0.1.2)
@@ -50,4 +50,4 @@ DEPENDENCIES
50
50
  rspec (~> 3.0)
51
51
 
52
52
  BUNDLED WITH
53
- 1.15.1
53
+ 1.15.3
data/README.md CHANGED
@@ -15,7 +15,7 @@ The easiest way to get started is to follow the [Featureflow quick start guides]
15
15
 
16
16
  ## Change Log
17
17
 
18
- Please see [CHANGELOG](https://github.com/featureflow/featureflow-node-sdk/blob/master/CHANGELOG.md).
18
+ Please see [CHANGELOG](https://github.com/featureflow/featureflow-ruby-sdk/blob/master/CHANGELOG.md).
19
19
 
20
20
  ## Usage
21
21
 
@@ -30,7 +30,7 @@ gem 'featureflow'
30
30
  ```
31
31
 
32
32
  Requiring `featureflow` in your ruby application will expose the classes
33
- `Featureflow::Client`, `Featuerflow::ContextBuilder` and `Featureflow::Feature`.
33
+ `Featureflow::Client`, `Featuerflow::UserBuilder` and `Featureflow::Feature`.
34
34
 
35
35
  The usage of each class is documented below.
36
36
 
@@ -79,45 +79,45 @@ end
79
79
 
80
80
 
81
81
 
82
- #### Defining Context
82
+ #### Defining a User
83
83
 
84
- Before evaluating a feature you must define a context for the current user.
85
- Featureflow uses context to target different user groups to specific feature variants.
86
- A featureflow context has a `key`, which should uniquely identify the current user, and optionally additional `values`.
87
- Featureflow requires the context `key` to be unique per user for gradual rollout of features.
84
+ Before evaluating a feature you must define a user.
85
+ Featureflow uses users to target different user groups to specific feature variants.
86
+ A featureflow user has an `id`, which should uniquely identify the current user, and optionally additional `attributes`.
87
+ Featureflow requires the user `id` to be unique per user for gradual rollout of features.
88
88
 
89
- There are two ways to define context:
89
+ There are two ways to define a user:
90
90
  ```ruby
91
91
  require 'featureflow'
92
- context_key = '<unique_user_identifier>'
92
+ user_id = '<unique_user_identifier>'
93
93
 
94
- # option 1, use the context builder
95
- context = Featureflow::ContextBuilder.new(context_key)
96
- .with_values(country: 'US',
94
+ # option 1, use the user builder
95
+ user = Featureflow::UserBuilder.new(user_id)
96
+ .with_attributes(country: 'US',
97
97
  roles: %w[USER_ADMIN, BETA_CUSTOMER])
98
98
  .build
99
99
 
100
100
  # option 2, use just a string
101
- context = context_key
101
+ user = user_id
102
102
  ```
103
103
 
104
104
  #### Evaluating Features
105
105
 
106
106
  In your code, you can test the value of your feature using something similar to below
107
- For these examples below, assume the feature `my-feature-key` is equal to `'on'` for the current `context`
107
+ For these examples below, assume the feature `my-feature-key` is equal to `'on'` for the current `user`
108
108
  ```ruby
109
- if featureflow.evaluate('my-feature-key', context).is? 'on'
110
- # this code will be run because 'my-feature-key' is set to 'on' for the given context
109
+ if featureflow.evaluate('my-feature-key', user).is? 'on'
110
+ # this code will be run because 'my-feature-key' is set to 'on' for the given user
111
111
  end
112
112
  ```
113
113
  Because the most common variants for a feature are `'on'` and `'off'`, we have provided two helper methods `.on?` and `.off?`
114
114
 
115
115
  ```ruby
116
- if featureflow.evaluate('my-feature-key', context).on?
116
+ if featureflow.evaluate('my-feature-key', user).on?
117
117
  # this feature code will be run because 'my-feature-key' is set to 'on'
118
118
  end
119
119
 
120
- if featureflow.evaluate('my-feature-key', context).off?
120
+ if featureflow.evaluate('my-feature-key', user).off?
121
121
  # this feature code won't be run because 'my-feature-key' is not set to 'off'
122
122
  end
123
123
  ```
@@ -147,9 +147,9 @@ featureflow = Featureflow::Client.new(api_key: FEATUREFLOW_SERVER_KEY,
147
147
  ])
148
148
 
149
149
  # ... app has been started offline
150
- featureflow.evaluate('key-one', context).on? # == true
151
- featureflow.evaluate('key-two', context).off? # == true
152
- featureflow.evaluate('key-three', context).is? 'custom' # == true
150
+ featureflow.evaluate('key-one', user).on? # == true
151
+ featureflow.evaluate('key-two', user).off? # == true
152
+ featureflow.evaluate('key-three', user).is? 'custom' # == true
153
153
 
154
154
  ```
155
155
 
@@ -170,4 +170,15 @@ Apache-2.0
170
170
  [rubygems-img]: https://badge.fury.io/rb/featureflow.png
171
171
 
172
172
  [dependency-url]: https://www.featureflow.io
173
- [dependency-img]: https://www.featureflow.io/wp-content/uploads/2016/12/featureflow-web.png
173
+ [dependency-img]: https://www.featureflow.io/wp-content/uploads/2016/12/featureflow-web.png
174
+
175
+ #Developer documentation
176
+
177
+ To build and test the SDK
178
+
179
+ ```
180
+ rvm install 2.5.1
181
+ rvm use --default 2.5.1
182
+ bundle install
183
+ ruby test.rb
184
+ ```
@@ -6,8 +6,8 @@ require 'featureflow/version'
6
6
  Gem::Specification.new do |spec|
7
7
  spec.name = "featureflow"
8
8
  spec.version = Featureflow::VERSION
9
- spec.authors = ["Henry Young"]
10
- spec.email = ["henry@featureflow.io"]
9
+ spec.authors = ["Oliver Oldfield-Hodge"]
10
+ spec.email = ["contact@featureflow.io"]
11
11
 
12
12
  spec.summary = "Ruby SDK for Featureflow"
13
13
  spec.description = "Connects your server with Featureflow. Create features at https://featureflow.io."
@@ -1,16 +1,38 @@
1
+ require 'logger'
1
2
  require 'featureflow/version'
3
+ require 'featureflow/configuration'
2
4
  require 'featureflow/client'
3
- require 'featureflow/context_builder'
5
+ require 'featureflow/user_builder'
4
6
  require 'featureflow/feature'
5
7
 
6
8
  module Featureflow
7
9
  class << self
8
- attr_writer :logger
10
+ def configure(config_hash = nil)
11
+ if config_hash
12
+ config_hash.each do |k, v|
13
+ configuration.send("#{k}=", v) rescue nil if configuration.respond_to?("#{k}=")
14
+ end
15
+ end
16
+
17
+ yield(configuration) if block_given?
18
+ end
19
+
20
+ def configuration
21
+ @configuration ||= Featureflow::Configuration.new
22
+ end
9
23
 
10
24
  def logger
11
- @logger ||= Logger.new($stderr).tap do |log|
12
- log.progname = self.name
13
- end
25
+ configuration.logger
26
+ end
27
+
28
+ def client
29
+ @client ||= Featureflow::Client.new(configuration)
30
+ end
31
+
32
+ alias featureflow client
33
+
34
+ def evaluate(*args)
35
+ client.evaluate(*args)
14
36
  end
15
37
  end
16
38
  end
@@ -1,83 +1,76 @@
1
- require 'logger'
2
- require 'time'
3
- require 'pp'
4
1
  require 'featureflow/evaluate'
5
2
  require 'featureflow/polling_client'
6
3
  require 'featureflow/events_client'
7
4
 
8
5
  module Featureflow
9
6
  class Client
10
- def initialize(config = {})
11
- api_key = config[:api_key] || ENV['FEATUREFLOW_SERVER_KEY']
12
- raise ArgumentError, "You have not defined either config[:api_key] or ENV[:FEATUREFLOW_SERVER_KEY]" unless api_key
13
- Featureflow.logger.info 'initializing client'
7
+ def initialize(configuration = nil)
8
+ @configuration = configuration || Featureflow.configuration
9
+ @configuration.validate!
14
10
  @features = {}
15
- @config = {
16
- api_key: api_key,
17
- url: 'https://app.featureflow.io',
18
- path: '/api/sdk/v1/features',
19
- with_features: [],
20
- disable_events: false
21
- }.merge(config)
22
11
 
23
- unless with_features_valid? @config[:with_features]
24
- raise ArgumentError, 'config[:with_features] must be an array of Feature hashes. Use Featureflow::Feature.create(key, failover_variant)'
25
- end
12
+ Featureflow.logger.info 'initializing client'
26
13
 
27
14
  @failover_variants = {}
28
- @config[:with_features].each do |feature|
15
+ @configuration.with_features.each do |feature|
29
16
  Featureflow.logger.info "Registering feature with key #{feature[:key]}"
30
17
  failover = feature[:failover_variant];
31
18
  @failover_variants[feature[:key]] = failover if failover.is_a?(String) && !failover.empty?
32
19
  end
33
20
 
34
- unless @config[:disable_events]
35
- @events_client = EventsClient.new @config[:url], @config[:api_key]
36
- @events_client.register_features @config[:with_features]
21
+ unless @configuration.disable_events
22
+ @events_client = EventsClient.new @configuration.event_endpoint, @configuration.api_key
23
+ @events_client.register_features @configuration.with_features
37
24
  end
38
25
 
39
- PollingClient.new(@config[:url] + @config[:path],
40
- @config[:api_key],
41
- delay: 30,
42
- timeout: 30) {|features| update_features(features)}
26
+ reload
43
27
 
44
28
  Featureflow.logger.info 'client initialized'
45
29
  end
46
30
 
47
- def feature(key)
48
- @features[key]
31
+ def reload
32
+ @polling_client.finish if @polling_client
33
+ @polling_client = build_polling_client
49
34
  end
50
35
 
51
- def evaluate(key, context)
52
- raise ArgumentError, 'key must be a string' unless key.is_a?(String)
53
- raise ArgumentError, 'context is required' unless context
54
- unless context.is_a?(String) || context.is_a?(Hash)
55
- raise ArgumentError, 'context must be either a string context key,' + \
56
- ' or a Hash built using Featureflow::ContextBuilder)'
57
- end
58
-
59
- context = ContextBuilder.new(context).build if context.is_a?(String)
60
-
61
- context = context.dup
62
- context[:values] = context[:values].merge('featureflow.key' => context[:key],
63
- 'featureflow.date' => Time.now.iso8601)
36
+ def build_polling_client
37
+ PollingClient.new(
38
+ @configuration.endpoint,
39
+ @configuration.api_key,
40
+ poll_interval: 10,
41
+ timeout: 30
42
+ )
43
+ end
64
44
 
65
- Evaluate.new key, feature(key), failover_variant(key), context, '1', @events_client
45
+ def feature(key)
46
+ @polling_client.feature(key)
66
47
  end
67
48
 
68
- private def failover_variant(key)
49
+ def failover_variant(key)
69
50
  @failover_variants[key]
70
51
  end
71
52
 
72
- private def update_features(features)
73
- Featureflow.logger.info "updating features"
74
- @features = features
75
- end
53
+ def evaluate(key, user)
54
+ raise ArgumentError, 'key must be a string' unless key.is_a?(String)
55
+ raise ArgumentError, 'user is required' unless user
56
+ unless user.is_a?(String) || user.is_a?(Hash)
57
+ raise ArgumentError, 'user must be either a string user id,' + \
58
+ ' or a Hash built using Featureflow::UserBuilder)'
59
+ end
60
+
61
+ user = UserBuilder.new(user).build if user.is_a?(String)
62
+
63
+ user = user.dup
64
+ user[:attributes] = user[:attributes].merge('featureflow.user.id' => user[:id])
76
65
 
77
- private def with_features_valid?(features)
78
- features.all? { |feature|
79
- feature[:key].is_a?(String) && feature[:failover_variant].is_a?(String) && feature[:variants].is_a?(Array)
80
- }
66
+ Evaluate.new(
67
+ feature_key: key,
68
+ feature: feature(key),
69
+ failover_variant: failover_variant(key),
70
+ user: user,
71
+ salt: '1',
72
+ events_client: @events_client
73
+ )
81
74
  end
82
75
  end
83
76
  end
@@ -0,0 +1,36 @@
1
+ class Featureflow::Configuration
2
+ attr_accessor :api_key
3
+ attr_accessor :endpoint
4
+ attr_accessor :event_endpoint
5
+ attr_accessor :disable_events
6
+ attr_accessor :with_features
7
+ attr_accessor :logger
8
+
9
+ DEFAULT_ENDPOINT = 'https://app.featureflow.io'
10
+ DEFAULT_EVENT_ENDPOINT = 'https://events.featureflow.io'
11
+
12
+ def initialize
13
+ self.api_key = ENV["FEATUREFLOW_SERVER_KEY"]
14
+ self.endpoint = DEFAULT_ENDPOINT
15
+ self.event_endpoint = DEFAULT_EVENT_ENDPOINT
16
+ self.disable_events = false
17
+ self.with_features = []
18
+
19
+ self.logger = Logger.new(STDOUT)
20
+ self.logger.level = Logger::WARN
21
+ end
22
+
23
+ def validate!
24
+ unless with_features_valid? @with_features
25
+ raise ArgumentError, 'with_features must be an array of Feature hashes. Use Featureflow::Feature.create(key, failover_variant)'
26
+ end
27
+ end
28
+
29
+ private
30
+
31
+ def with_features_valid?(features)
32
+ features.all? do |feature|
33
+ feature[:key].is_a?(String) && feature[:failover_variant].is_a?(String) && feature[:variants].is_a?(Array)
34
+ end
35
+ end
36
+ end
@@ -2,15 +2,22 @@ require 'featureflow/evaluate_helpers'
2
2
 
3
3
  module Featureflow
4
4
  class Evaluate
5
- def initialize(feature_key, feature, failover_variant, context, salt, events_client = nil)
6
- @evaluated_variant = calculate_variant feature_key, feature, failover_variant, context, salt
7
- @events_client = events_client
8
- @context = context
5
+ def initialize(feature_key:, feature:, failover_variant:, user:, salt:, events_client: nil)
9
6
  @key = feature_key
7
+ @feature = feature
8
+ @failover_variant = failover_variant
9
+ @user = user
10
+ @salt = salt
11
+ @events_client = events_client
12
+
13
+ @has_failover = @failover_variant.is_a?(String)
14
+ @failover_variant = 'off' unless @has_failover
15
+
16
+ @evaluated_variant = evaluate_variant
10
17
  end
11
18
 
12
19
  def is?(value)
13
- @events_client.evaluate @key, @evaluated_variant, value, @context unless @events_client.is_a?(NilClass)
20
+ @events_client.evaluate(@key, @evaluated_variant, value, @user) if @events_client
14
21
  @evaluated_variant == value
15
22
  end
16
23
 
@@ -28,22 +35,23 @@ module Featureflow
28
35
  @evaluated_variant
29
36
  end
30
37
 
31
- private def calculate_variant(feature_key, feature, failover_variant, context = {}, salt = '1')
32
- unless feature
33
- has_failover = failover_variant.is_a?(String)
34
- failover_variant = 'off' unless has_failover
35
- Featureflow.logger.info "Evaluating nil feature '#{feature_key}' using the "\
36
- "#{has_failover ? 'provided' : 'default'} failover '#{failover_variant}'"
37
- return failover_variant
38
+ private
39
+
40
+ def evaluate_variant
41
+ unless @feature
42
+ Featureflow.logger.info "Evaluating nil feature '#{@feature_key}' using the "\
43
+ "#{@has_failover ? 'provided' : 'default'} failover '#{@failover_variant}'"
44
+ return @failover_variant
38
45
  end
39
46
 
40
- return feature['offVariantKey'] unless feature['enabled']
41
- feature['rules'].each do |rule|
42
- next unless EvaluateHelpers.rule_matches rule, context
43
- hash = EvaluateHelpers.calculate_hash salt, feature['key'], context['key']
44
- variant_value = EvaluateHelpers.get_variant_value hash
45
- return EvaluateHelpers.get_variant_split_key rule['variantSplits'], variant_value
47
+ return @feature['offVariantKey'] unless @feature['enabled']
48
+
49
+ @feature['rules'].each do |rule|
50
+ next unless EvaluateHelpers.rule_matches(rule, @user)
51
+ hash = EvaluateHelpers.calculate_hash(@salt, @feature['key'], @user['id'])
52
+ variant_value = EvaluateHelpers.get_variant_value(hash)
53
+ return EvaluateHelpers.get_variant_split_key(rule['variantSplits'], variant_value)
46
54
  end
47
55
  end
48
56
  end
49
- end
57
+ end
@@ -3,15 +3,15 @@ require 'digest/sha1'
3
3
 
4
4
  module Featureflow
5
5
  class EvaluateHelpers
6
- def self.rule_matches(rule, context)
6
+ def self.rule_matches(rule, user)
7
7
  if rule['defaultRule']
8
8
  true # the default rule will always match true
9
9
  else
10
10
  rule['audience']['conditions'].all? do |condition|
11
- context_values = context[:values][condition['target']]
11
+ user_attributes = user[:attributes][condition['target']]
12
12
  # convert to array to work with test
13
- Array(context_values).any? do |value|
14
- Conditions.test condition['operator'], value, condition['values']
13
+ Array(user_attributes).any? do |attribute|
14
+ Conditions.test condition['operator'], attribute, condition['values']
15
15
  end
16
16
  end
17
17
  end
@@ -1,13 +1,31 @@
1
1
  require 'excon'
2
2
  require 'json'
3
+ require 'thread'
4
+ require 'time'
3
5
 
4
6
  module Featureflow
7
+ #LOCK = Mutex.new
8
+
5
9
  class EventsClient
6
10
  def initialize(url, api_key)
7
11
  @url = url
8
12
  @api_key = api_key
13
+ @eventsQueue = Queue.new
14
+ @scheduler = start_scheduler
15
+ end
16
+
17
+ def start_scheduler()
18
+ Thread.new do
19
+ loop do
20
+ begin
21
+ sleep 10
22
+ send_queue
23
+ end
24
+ end
25
+ end
9
26
  end
10
27
 
28
+ #register features are not queued and go straight out
11
29
  def register_features(with_features)
12
30
  Thread.new do
13
31
  features = []
@@ -16,28 +34,63 @@ module Featureflow
16
34
  variants: feature[:variants],
17
35
  failoverVariant: feature[:failover_variant])
18
36
  end
19
- send_event 'Register Features', :put, 'api/sdk/v1/register', features
37
+ send_event 'Register Features', :put, '/api/sdk/v1/register', features
20
38
  end
21
39
  end
22
40
 
23
- def evaluate(key, evaluated_variant, expected_variant, context)
41
+ def evaluate(key, evaluated_variant, expected_variant, user)
24
42
  Thread.new do
25
- send_event 'Evaluate Variant', :post, 'api/sdk/v1/events', [{
26
- featureKey: key,
27
- evaluatedVariant: evaluated_variant,
28
- expectedVaraint: expected_variant,
29
- context: context
30
- }]
43
+ timestamp = Time.now.iso8601
44
+ queue_event ({
45
+ featureKey: key,
46
+ evaluatedVariant: evaluated_variant,
47
+ expectedVaraint: expected_variant,
48
+ user: user,
49
+ timestamp: timestamp
50
+ })
31
51
  end
32
52
  end
33
53
 
34
- private def send_event(event_name, method, path, body)
54
+ def queue_event(event)
55
+ #add to queue
56
+
57
+ @eventsQueue.push(event)
58
+
59
+ if !@scheduler.alive?
60
+ @scheduler = start_scheduler
61
+ end
62
+
63
+ if @eventsQueue.length >= 10000
64
+ send_queue
65
+ end
66
+ #id queue = 10000 then send_queue
67
+ end
68
+
69
+ def send_queue()
70
+ events =[]
71
+ begin
72
+ loop do
73
+ events << @eventsQueue.pop(true)
74
+ end
75
+ rescue ThreadError
76
+ end
77
+
78
+ if !events.empty?
79
+ send_event 'Evaluate Variant', :post, '/api/sdk/v1/events', events
80
+ end
81
+
82
+ end
83
+
84
+ =begin
85
+ private def send_queue()
86
+
35
87
  connection = Excon.new(@url)
36
88
  response = connection.request(method: method,
37
89
  path: path,
38
90
  headers: {
39
91
  'Authorization' => "Bearer #{@api_key}",
40
- 'Content-Type' => 'application/json;charset=UTF-8'
92
+ 'Content-Type' => 'application/json;charset=UTF-8',
93
+ 'X-Featureflow-Client' => 'RubyClient/' + Featureflow::VERSION
41
94
  },
42
95
  omit_default_port: true,
43
96
  body: JSON.generate(body))
@@ -48,5 +101,27 @@ module Featureflow
48
101
  rescue => e
49
102
  Featureflow.logger.error e.inspect
50
103
  end
104
+ =end
105
+
106
+ private def send_event(event_name, method, path, body)
107
+ connection = Excon.new(@url)
108
+ response = connection.request(method: method,
109
+ path: path,
110
+ headers: {
111
+ 'Authorization' => "Bearer #{@api_key}",
112
+ 'Content-Type' => 'application/json;charset=UTF-8',
113
+ 'Accept' => 'Application/Json',
114
+ 'X-Featureflow-Client' => 'RubyClient/' + Featureflow::VERSION
115
+ },
116
+ omit_default_port: true,
117
+ body: JSON.generate(body))
118
+ if response.status >= 400
119
+ Featureflow.logger.error "unable to send event #{event_name} to #{@url+path}. Failed with response status #{response.status}"
120
+ Featureflow.logger.error response.to_s
121
+ end
122
+ rescue => e
123
+ Featureflow.logger.error e.inspect
124
+ end
125
+
51
126
  end
52
- end
127
+ end
@@ -7,15 +7,18 @@ module Featureflow
7
7
  poll_interval: 30,
8
8
  timeout: 30
9
9
  }.freeze
10
- def initialize(url, api_key, options = {}, &set_features)
10
+ LOCK = Mutex.new
11
+
12
+ def initialize(url, api_key, options = {})
11
13
  @etag = ''
12
14
  @url = url
13
15
  @api_key = api_key
14
16
  @options = DEFAULT_OPTIONS.merge(options)
15
- @set_features = set_features
17
+ @features = {}
16
18
 
17
19
  load_features
18
- Thread.new do
20
+
21
+ @thread = Thread.new do
19
22
  loop do
20
23
  sleep @options[:poll_interval]
21
24
  load_features
@@ -23,14 +26,30 @@ module Featureflow
23
26
  end
24
27
  end
25
28
 
29
+ def finish
30
+ @thread.exit
31
+ end
32
+
33
+ def feature(key)
34
+ LOCK.synchronize { @features[key] }
35
+ end
36
+
26
37
  def load_features
27
- response = Excon.get(@url, headers: {
38
+ response = Excon.get(@url + + '/api/sdk/v1/features', headers: {
28
39
  'Authorization' => "Bearer #{@api_key}",
29
- 'If-None-Match' => @etag
40
+ 'Accept' => 'Application/Json',
41
+ 'If-None-Match' => @etag,
42
+ 'X-Featureflow-Client' => 'RubyClient/' + Featureflow::VERSION
30
43
  }, omit_default_port: true, read_timeout: @options[:timeout])
44
+
31
45
  if response.status == 200
46
+ Featureflow.logger.debug "updating features"
47
+
32
48
  @etag = response.headers['ETag']
33
- @set_features.call(JSON.parse(response.body))
49
+
50
+ features = JSON.parse(response.body)
51
+
52
+ LOCK.synchronize { @features = features }
34
53
  elsif response.status >= 400
35
54
  Featureflow.logger.error "request for features failed with response status #{response.status}"
36
55
  Featureflow.logger.error response.to_s
@@ -5,15 +5,14 @@ module Featureflow
5
5
  def initialize(request)
6
6
  @request = request
7
7
  end
8
+
8
9
  def evaluate(key, context)
9
10
  request_context_values = {
10
11
  'featureflow.ip' => @request.remote_ip,
11
12
  'featureflow.url' => @request.original_url
12
13
  }
13
14
  context = {key: context, values: {}} if context.is_a?(String)
14
- Featureflow.featureflow.evaluate(key,
15
- key: context[:key],
16
- values: request_context_values.merge(context[:values]))
15
+ Featureflow.evaluate(key, key: context[:key], values: request_context_values.merge(context[:values]))
17
16
  end
18
17
  end
19
18
  end
@@ -1,42 +1,12 @@
1
1
  require 'featureflow/rails/rails_client'
2
- module Featureflow
3
- LOCK = Mutex.new
4
-
5
- class << self
6
- def configure(config_hash=nil)
7
- if config_hash
8
- configuration.merge!(config_hash)
9
- end
10
-
11
- yield(configuration) if block_given?
12
- yield(featureflow) if block_given?
13
- end
14
-
15
- # Configuration getters
16
- def configuration
17
- @configuration = nil unless defined?(@configuration)
18
- @configuration || LOCK.synchronize { @configuration ||= {} }
19
- end
20
-
21
- # Configuration getters
22
- def featureflow
23
- @featureflow = nil unless defined?(@featureflow)
24
- @featureflow || LOCK.synchronize { @featureflow ||= Featureflow::Client.new(configuration) }
25
- end
26
- end
27
-
28
- class FeatureflowRailtie < Rails::Railtie
29
-
30
- config.before_initialize do
31
- ActiveSupport.on_load(:action_controller) do
32
- # @client =
33
- ActionController::Base.class_eval do
34
- def featureflow
35
- Featureflow::RailsClient.new(request)
36
- end
2
+ class Featureflow::FeatureflowRailtie < Rails::Railtie
3
+ config.before_initialize do
4
+ ActiveSupport.on_load(:action_controller) do
5
+ ActionController::Base.class_eval do
6
+ def featureflow
7
+ @featureflow ||= Featureflow::RailsClient.new(request)
37
8
  end
38
9
  end
39
10
  end
40
-
41
11
  end
42
12
  end
@@ -1,32 +1,32 @@
1
1
  module Featureflow
2
- class ContextBuilder
3
- def initialize(key)
4
- raise ArgumentError, 'Parameter key must be a String' unless key.is_a?(String) && !key.empty?
5
- @context_key = key
6
- @values = {}
2
+ class UserBuilder
3
+ def initialize(id)
4
+ raise ArgumentError, 'Parameter id must be a String' unless id.is_a?(String) && !id.empty?
5
+ @user_id = id
6
+ @attributes = {}
7
7
  self
8
8
  end
9
9
 
10
- def with_values(hash)
10
+ def with_attributes(hash)
11
11
  raise ArgumentError, 'Parameter hash must be a Hash' unless hash.is_a?(Hash)
12
12
  hash = hash.dup
13
13
  hash.each do |k, v|
14
14
  raise ArgumentError, "Value for #{k} must be a valid 'primitive' JSON datatype" unless valid_value?(v)
15
15
  hash[k.to_s] = h.delete(k) unless k.is_a?(String)
16
16
  end
17
- @values = @values.merge(hash)
17
+ @attributes = @attributes.merge(hash)
18
18
  self
19
19
  end
20
20
 
21
21
  def build
22
22
  {
23
- key: @context_key,
24
- values: @values
23
+ id: @user_id,
24
+ attributes: @attributes
25
25
  }
26
26
  end
27
27
 
28
- private def valid_value?(values)
29
- Array(values).all? do |v|
28
+ private def valid_attribute?(attributes)
29
+ Array(attributes).all? do |v|
30
30
  [String, Numeric, TrueClass, FalseClass].any? { |type| v.is_a?(type) }
31
31
  end
32
32
  end
@@ -1,3 +1,3 @@
1
1
  module Featureflow
2
- VERSION = "0.5.1"
2
+ VERSION = "0.6.0"
3
3
  end
@@ -0,0 +1 @@
1
+ gem push
data/test.rb CHANGED
@@ -2,20 +2,32 @@ $:.unshift(File.dirname(__FILE__) + "/lib/")
2
2
 
3
3
  require 'featureflow'
4
4
  with_features = [
5
- Featureflow::Feature.create('default', 'variant')
5
+ Featureflow::Feature.create('default', 'variant'),
6
+ Featureflow::Feature.create('oli-f1', 'off')
6
7
  ]
7
8
 
8
9
 
9
10
 
10
- # api_key = 'srv-env-9b5fff890c724d119a334a64ed4d2eb2'
11
- api_key = 'srv-env-f472cfa8c2774ea2b3678fc5a3dbfe13'
12
- featureflow_client = Featureflow::Client.new(api_key: api_key, with_features: with_features, url: 'http://10.10.2.163:8081')
13
- context = Featureflow::ContextBuilder.new('user1').build
14
- puts('test-integration is on? ' + featureflow_client.evaluate('test-integration', context).on?.to_s)
15
- featureflow_client.evaluate('nooooo', context).on?
16
- featureflow_client.evaluate('default', context).on?
11
+ api_key = 'srv-env-'
12
+ config = Featureflow::Configuration.new
13
+ config.api_key = api_key
14
+ #config.endpoint = 'http://localhost:8081'
15
+ #config.event_endpoint = 'http://localhost:8081'
16
+ config.with_features = with_features
17
+
18
+ featureflow_client = Featureflow::Client.new(config)
19
+ =begin
20
+ featureflow_client = Featureflow::Client.new(api_key: api_key, with_features: with_features, url: 'http://localhost:8081')
21
+ =end
22
+ user = Featureflow::UserBuilder.new('user1').build
23
+ puts('test-integration is on? ' + featureflow_client.evaluate('test-integration', user).on?.to_s)
24
+ featureflow_client.evaluate('nooooo', user).on?
25
+ featureflow_client.evaluate('default', user).on?
17
26
 
18
27
  #
19
28
  loop do
20
- sleep 1
21
- end
29
+ sleep 10
30
+ puts(featureflow_client.evaluate('oli-f1', user).value)
31
+ puts(featureflow_client.evaluate('oli-f1', user).is?('extended'))
32
+ puts(featureflow_client.evaluate('default', user).on?)
33
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: featureflow
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.1
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
- - Henry Young
7
+ - Oliver Oldfield-Hodge
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2017-07-17 00:00:00.000000000 Z
11
+ date: 2018-12-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -82,7 +82,7 @@ dependencies:
82
82
  version: 0.57.0
83
83
  description: Connects your server with Featureflow. Create features at https://featureflow.io.
84
84
  email:
85
- - henry@featureflow.io
85
+ - contact@featureflow.io
86
86
  executables: []
87
87
  extensions: []
88
88
  extra_rdoc_files: []
@@ -99,7 +99,7 @@ files:
99
99
  - lib/featureflow.rb
100
100
  - lib/featureflow/client.rb
101
101
  - lib/featureflow/conditions.rb
102
- - lib/featureflow/context_builder.rb
102
+ - lib/featureflow/configuration.rb
103
103
  - lib/featureflow/evaluate.rb
104
104
  - lib/featureflow/evaluate_helpers.rb
105
105
  - lib/featureflow/events_client.rb
@@ -107,8 +107,10 @@ files:
107
107
  - lib/featureflow/polling_client.rb
108
108
  - lib/featureflow/rails/rails_client.rb
109
109
  - lib/featureflow/rails/railtie.rb
110
+ - lib/featureflow/user_builder.rb
110
111
  - lib/featureflow/version.rb
111
112
  - lib/generators/featureflow_generator.rb
113
+ - publish-gem.sh
112
114
  - test.rb
113
115
  homepage: https://github.com/featureflow/featureflow-ruby-sdk
114
116
  licenses:
@@ -130,7 +132,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
130
132
  version: '0'
131
133
  requirements: []
132
134
  rubyforge_project:
133
- rubygems_version: 2.6.8
135
+ rubygems_version: 2.7.8
134
136
  signing_key:
135
137
  specification_version: 4
136
138
  summary: Ruby SDK for Featureflow