featureflow 0.5.1 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- 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