featureflow 0.6.0 → 0.7.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 +4 -4
- data/CHANGELOG.md +22 -0
- data/README.md +11 -1
- data/lib/featureflow/client.rb +33 -2
- data/lib/featureflow/conditions.rb +81 -10
- data/lib/featureflow/evaluate.rb +1 -1
- data/lib/featureflow/rails/rails_client.rb +24 -5
- data/lib/featureflow/user_builder.rb +4 -4
- data/lib/featureflow/version.rb +1 -1
- data/lib/generators/featureflow_generator.rb +1 -1
- metadata +41 -32
- data/.gitignore +0 -2
- data/.rubocop.yml +0 -5
- data/Gemfile +0 -4
- data/Gemfile.lock +0 -53
- data/Rakefile +0 -6
- data/featureflow.gemspec +0 -38
- data/publish-gem.sh +0 -1
- data/test.rb +0 -33
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2865ebc3fed1dfffb613a9a459ce6e0ac8d6e5cd05d758b4e93944cbc8d8d372
|
|
4
|
+
data.tar.gz: 1b5a7d4b01a2ff98501ca2818e3d00a4d4523525e9e71cd4cda1a847ef009867
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8c1fb252dcd6beac50457f7d8328198b7512d4bad761824d665490dc0a8fa59026aa55901adf0555dbfce12599ecbf1b611695801d150b29a76ee747abb1aadd
|
|
7
|
+
data.tar.gz: 712e342d59b58327b4ec1bf98770946ef255f39dbecc2093dc50fa786252b82f6aed33b06a1faacbd5c7e9f60aa65f750a37e454c4ec6f03f8a8cb9f96bb87c2
|
data/CHANGELOG.md
CHANGED
|
@@ -1,4 +1,26 @@
|
|
|
1
1
|
# Change log
|
|
2
|
+
## [0.7.0] - 2026-08-11
|
|
3
|
+
### Fixed
|
|
4
|
+
- Condition operators fail closed instead of raising into the host application: an
|
|
5
|
+
invalid `matches` regex, a non-string attribute against a string operator, or a
|
|
6
|
+
string/number type mismatch on a comparison operator is now a no-match, never an
|
|
7
|
+
exception.
|
|
8
|
+
- `before`/`after` compare parsed instants rather than strings, so rule values with
|
|
9
|
+
arbitrary UTC offsets compare correctly; date-only values are read as UTC midnight
|
|
10
|
+
on every host, not local midnight.
|
|
11
|
+
- Rollout bucketing hashed every user to the same bucket (`user['id']` vs
|
|
12
|
+
`user[:id]`); percentage splits now distribute correctly.
|
|
13
|
+
- `RailsClient#evaluate` and `UserBuilder#with_attributes` were broken; both work
|
|
14
|
+
again, and the Rails generator accepts current `sdk-srv-env-` keys.
|
|
15
|
+
### Added
|
|
16
|
+
- `Featureflow::Client.new(api_key: '...')` accepts a configuration Hash; unknown
|
|
17
|
+
options raise rather than being silently dropped.
|
|
18
|
+
- Implicit `featureflow.date` / `featureflow.hourofday` attributes are injected at
|
|
19
|
+
evaluation time, matching the other server SDKs.
|
|
20
|
+
### Changed
|
|
21
|
+
- Requires Ruby >= 3.1 (excon ~> 1.5).
|
|
22
|
+
- The packaged gem now contains only `lib/`, README, CHANGELOG and LICENSE.
|
|
23
|
+
|
|
2
24
|
## [0.6.0] - 2018-12-17
|
|
3
25
|
### Fixed
|
|
4
26
|
- Use User over Context matching all other SDKs
|
data/README.md
CHANGED
|
@@ -181,4 +181,14 @@ rvm install 2.5.1
|
|
|
181
181
|
rvm use --default 2.5.1
|
|
182
182
|
bundle install
|
|
183
183
|
ruby test.rb
|
|
184
|
-
```
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
## Manual test harness (example server)
|
|
187
|
+
|
|
188
|
+
To try the SDK against a real Featureflow environment without writing any code, run the small local harness in [examples/harness](examples/harness):
|
|
189
|
+
|
|
190
|
+
```
|
|
191
|
+
FEATUREFLOW_SERVER_KEY=sdk-srv-env-<your_key> bundle exec ruby examples/harness/server.rb
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
Then open `http://127.0.0.1:3456/` to evaluate features in the browser, or hit `GET /api/evaluate?feature=<key>&userId=<id>` directly. See [examples/harness/README.md](examples/harness/README.md) for all endpoints and environment variables (including pointing at staging via `FEATUREFLOW_BASE_URL`).
|
data/lib/featureflow/client.rb
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
require 'time'
|
|
1
2
|
require 'featureflow/evaluate'
|
|
2
3
|
require 'featureflow/polling_client'
|
|
3
4
|
require 'featureflow/events_client'
|
|
@@ -5,7 +6,7 @@ require 'featureflow/events_client'
|
|
|
5
6
|
module Featureflow
|
|
6
7
|
class Client
|
|
7
8
|
def initialize(configuration = nil)
|
|
8
|
-
@configuration = configuration
|
|
9
|
+
@configuration = build_configuration(configuration)
|
|
9
10
|
@configuration.validate!
|
|
10
11
|
@features = {}
|
|
11
12
|
|
|
@@ -61,7 +62,16 @@ module Featureflow
|
|
|
61
62
|
user = UserBuilder.new(user).build if user.is_a?(String)
|
|
62
63
|
|
|
63
64
|
user = user.dup
|
|
64
|
-
|
|
65
|
+
# Implicit attributes injected at evaluation time (matching the Node SDK's
|
|
66
|
+
# EvaluateHelpers): featureflow.date/hourofday let rules target the current
|
|
67
|
+
# moment; sdk-server's partial evaluation defers these to the JS client, but
|
|
68
|
+
# server SDKs evaluate them locally against "now".
|
|
69
|
+
now = Time.now
|
|
70
|
+
user[:attributes] = user[:attributes].merge(
|
|
71
|
+
'featureflow.user.id' => user[:id],
|
|
72
|
+
'featureflow.date' => now.utc.iso8601,
|
|
73
|
+
'featureflow.hourofday' => now.hour
|
|
74
|
+
)
|
|
65
75
|
|
|
66
76
|
Evaluate.new(
|
|
67
77
|
feature_key: key,
|
|
@@ -72,5 +82,26 @@ module Featureflow
|
|
|
72
82
|
events_client: @events_client
|
|
73
83
|
)
|
|
74
84
|
end
|
|
85
|
+
|
|
86
|
+
# Accepts either a Featureflow::Configuration, or a Hash of configuration
|
|
87
|
+
# options — `Featureflow::Client.new(api_key: '...')` is the form the README
|
|
88
|
+
# leads with and the natural Ruby idiom. Passing nothing keeps using the
|
|
89
|
+
# process-wide Featureflow.configuration (which reads FEATUREFLOW_SERVER_KEY).
|
|
90
|
+
private def build_configuration(configuration)
|
|
91
|
+
return Featureflow.configuration if configuration.nil?
|
|
92
|
+
return configuration unless configuration.is_a?(Hash)
|
|
93
|
+
|
|
94
|
+
config = Configuration.new
|
|
95
|
+
configuration.each do |option, value|
|
|
96
|
+
setter = "#{option}="
|
|
97
|
+
# Fail loudly rather than silently dropping a mistyped option — a
|
|
98
|
+
# misspelt `endpoint:` would otherwise quietly poll production.
|
|
99
|
+
unless config.respond_to?(setter)
|
|
100
|
+
raise ArgumentError, "Unknown Featureflow configuration option: #{option}"
|
|
101
|
+
end
|
|
102
|
+
config.public_send(setter, value)
|
|
103
|
+
end
|
|
104
|
+
config
|
|
105
|
+
end
|
|
75
106
|
end
|
|
76
107
|
end
|
|
@@ -1,33 +1,104 @@
|
|
|
1
|
+
require 'date'
|
|
2
|
+
require 'time'
|
|
3
|
+
|
|
1
4
|
module Featureflow
|
|
2
5
|
class Conditions
|
|
6
|
+
# Mirrors the Node SDK's dateParse: dates compare by instant, not by string,
|
|
7
|
+
# so ISO values with different UTC offsets (e.g. "+04:00" vs "Z") compare correctly.
|
|
8
|
+
#
|
|
9
|
+
# A value carrying no timezone information is read as UTC, per the SDK contract
|
|
10
|
+
# ("A date-only value denotes UTC midnight"): the dashboard's date picker emits
|
|
11
|
+
# date-only values like "2026-07-03", and reading those as *local* midnight would
|
|
12
|
+
# make a scheduled rollout fire at a different instant on every host in a fleet.
|
|
13
|
+
# Values with an explicit offset or "Z" keep their instant, unshifted.
|
|
14
|
+
def self.to_epoch(value)
|
|
15
|
+
case value
|
|
16
|
+
when Time then value.to_f
|
|
17
|
+
when Numeric then value.to_f
|
|
18
|
+
when String
|
|
19
|
+
begin
|
|
20
|
+
parsed = Time.parse(value)
|
|
21
|
+
# Time.parse silently falls back to the process's timezone when the value
|
|
22
|
+
# carries no zone; adding back the offset it applied re-reads the same
|
|
23
|
+
# wall-clock components as UTC. Date._parse only reports :offset when the
|
|
24
|
+
# value actually stated a zone.
|
|
25
|
+
parsed += parsed.utc_offset unless Date._parse(value).key?(:offset)
|
|
26
|
+
parsed.to_f
|
|
27
|
+
rescue ArgumentError, TypeError, RangeError
|
|
28
|
+
nil
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Both operands must be strings for a string operator to mean anything.
|
|
34
|
+
def self.both_strings?(a, b)
|
|
35
|
+
a.is_a?(String) && b.is_a?(String)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Numeric-to-numeric or string-to-string only. Mixing the two raises in Ruby, and
|
|
39
|
+
# coercing would answer version comparisons wrongly.
|
|
40
|
+
def self.comparable?(a, b)
|
|
41
|
+
(a.is_a?(Numeric) && b.is_a?(Numeric)) || (a.is_a?(String) && b.is_a?(String))
|
|
42
|
+
end
|
|
43
|
+
|
|
3
44
|
def self.test(op, a, b)
|
|
4
45
|
case op
|
|
5
46
|
when 'equals'
|
|
6
47
|
a.eql? b[0]
|
|
48
|
+
# The string operators assume both sides are strings. A numeric attribute against
|
|
49
|
+
# `contains` raised NoMethodError straight into the host application — the same
|
|
50
|
+
# fail-closed violation as the invalid regex below, and reachable the same way,
|
|
51
|
+
# from a rule somebody typed into the dashboard. Python already guards these with
|
|
52
|
+
# an explicit type check; this brings Ruby into line. A type mismatch is a
|
|
53
|
+
# no-match, not an exception.
|
|
7
54
|
when 'contains'
|
|
8
|
-
a.include?
|
|
55
|
+
both_strings?(a, b[0]) && a.include?(b[0])
|
|
9
56
|
when 'startsWith'
|
|
10
|
-
a.start_with?
|
|
57
|
+
both_strings?(a, b[0]) && a.start_with?(b[0])
|
|
11
58
|
when 'endsWith'
|
|
12
|
-
a.end_with?
|
|
59
|
+
both_strings?(a, b[0]) && a.end_with?(b[0])
|
|
13
60
|
when 'matches'
|
|
14
|
-
a
|
|
61
|
+
# The pattern is untrusted configuration, not code: it comes from a targeting
|
|
62
|
+
# rule somebody typed into the dashboard, so a malformed one like "[unclosed"
|
|
63
|
+
# is a user's mistake rather than a bug in the SDK. The contract
|
|
64
|
+
# (featureflow-client-sdk-testbed/CONTRACT.md, "Operators") therefore requires
|
|
65
|
+
# an invalid regex to return false — a flag SDK must degrade rather than take
|
|
66
|
+
# the host application down with it.
|
|
67
|
+
#
|
|
68
|
+
# Only the regex failure is rescued, never everything: RegexpError for a
|
|
69
|
+
# pattern that will not compile, TypeError for a value that is not a string at
|
|
70
|
+
# all. A genuine bug anywhere else still surfaces.
|
|
71
|
+
begin
|
|
72
|
+
a.match? Regexp.new(b[0])
|
|
73
|
+
rescue RegexpError, TypeError
|
|
74
|
+
false
|
|
75
|
+
end
|
|
15
76
|
when 'in'
|
|
16
77
|
b.include? a
|
|
17
78
|
when 'notIn'
|
|
18
79
|
!b.include? a
|
|
80
|
+
# Comparing a String with a Numeric raises ArgumentError in Ruby, so an attribute
|
|
81
|
+
# whose type does not match the rule's took the host application down. The
|
|
82
|
+
# contract requires a numeric operator to return false on non-numeric input
|
|
83
|
+
# rather than coercing — "1.10.0" > "1.9.0" must not silently succeed — so a
|
|
84
|
+
# mismatch is simply a no-match. String-to-string comparison is kept, since Ruby
|
|
85
|
+
# orders strings meaningfully and the server behaves the same way.
|
|
19
86
|
when 'greaterThan'
|
|
20
|
-
a > b[0]
|
|
87
|
+
comparable?(a, b[0]) && a > b[0]
|
|
21
88
|
when 'greaterThanOrEqual'
|
|
22
|
-
a >= b[0]
|
|
89
|
+
comparable?(a, b[0]) && a >= b[0]
|
|
23
90
|
when 'lessThan'
|
|
24
|
-
a < b[0]
|
|
91
|
+
comparable?(a, b[0]) && a < b[0]
|
|
25
92
|
when 'lessThanOrEqual'
|
|
26
|
-
a <= b[0]
|
|
93
|
+
comparable?(a, b[0]) && a <= b[0]
|
|
27
94
|
when 'before'
|
|
28
|
-
|
|
95
|
+
a_time = to_epoch(a)
|
|
96
|
+
b_time = to_epoch(b[0])
|
|
97
|
+
!!(a_time && b_time && a_time < b_time)
|
|
29
98
|
when 'after'
|
|
30
|
-
|
|
99
|
+
a_time = to_epoch(a)
|
|
100
|
+
b_time = to_epoch(b[0])
|
|
101
|
+
!!(a_time && b_time && a_time > b_time)
|
|
31
102
|
else
|
|
32
103
|
false
|
|
33
104
|
end
|
data/lib/featureflow/evaluate.rb
CHANGED
|
@@ -48,7 +48,7 @@ module Featureflow
|
|
|
48
48
|
|
|
49
49
|
@feature['rules'].each do |rule|
|
|
50
50
|
next unless EvaluateHelpers.rule_matches(rule, @user)
|
|
51
|
-
hash = EvaluateHelpers.calculate_hash(@salt, @feature['key'], @user[
|
|
51
|
+
hash = EvaluateHelpers.calculate_hash(@salt, @feature['key'], @user[:id])
|
|
52
52
|
variant_value = EvaluateHelpers.get_variant_value(hash)
|
|
53
53
|
return EvaluateHelpers.get_variant_split_key(rule['variantSplits'], variant_value)
|
|
54
54
|
end
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
require 'featureflow/client'
|
|
2
|
+
require 'featureflow/user_builder'
|
|
2
3
|
|
|
3
4
|
module Featureflow
|
|
4
5
|
class RailsClient
|
|
@@ -6,13 +7,31 @@ module Featureflow
|
|
|
6
7
|
@request = request
|
|
7
8
|
end
|
|
8
9
|
|
|
9
|
-
|
|
10
|
-
|
|
10
|
+
# `user` is whatever Client#evaluate accepts: a String user id, or a user
|
|
11
|
+
# Hash built with Featureflow::UserBuilder ({id:, attributes:}). The current
|
|
12
|
+
# request's ip and url are merged in as implicit attributes so targeting
|
|
13
|
+
# rules can match on them; anything the caller set explicitly wins.
|
|
14
|
+
def evaluate(key, user)
|
|
15
|
+
Featureflow.evaluate(key, with_request_attributes(user))
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
def with_request_attributes(user)
|
|
21
|
+
user = UserBuilder.new(user).build if user.is_a?(String)
|
|
22
|
+
# Anything else is left alone so Client#evaluate raises its own ArgumentError.
|
|
23
|
+
return user unless user.is_a?(Hash)
|
|
24
|
+
|
|
25
|
+
user.merge(attributes: request_attributes.merge(user[:attributes] || {}))
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Attribute keys are Strings throughout the evaluation path (UserBuilder
|
|
29
|
+
# normalises them), so the request attributes are keyed as Strings too.
|
|
30
|
+
def request_attributes
|
|
31
|
+
{
|
|
11
32
|
'featureflow.ip' => @request.remote_ip,
|
|
12
33
|
'featureflow.url' => @request.original_url
|
|
13
34
|
}
|
|
14
|
-
context = {key: context, values: {}} if context.is_a?(String)
|
|
15
|
-
Featureflow.evaluate(key, key: context[:key], values: request_context_values.merge(context[:values]))
|
|
16
35
|
end
|
|
17
36
|
end
|
|
18
|
-
end
|
|
37
|
+
end
|
|
@@ -9,12 +9,12 @@ module Featureflow
|
|
|
9
9
|
|
|
10
10
|
def with_attributes(hash)
|
|
11
11
|
raise ArgumentError, 'Parameter hash must be a Hash' unless hash.is_a?(Hash)
|
|
12
|
-
|
|
12
|
+
normalized = {}
|
|
13
13
|
hash.each do |k, v|
|
|
14
|
-
raise ArgumentError, "Value for #{k} must be a valid 'primitive' JSON datatype" unless
|
|
15
|
-
|
|
14
|
+
raise ArgumentError, "Value for #{k} must be a valid 'primitive' JSON datatype" unless valid_attribute?(v)
|
|
15
|
+
normalized[k.to_s] = v
|
|
16
16
|
end
|
|
17
|
-
@attributes = @attributes.merge(
|
|
17
|
+
@attributes = @attributes.merge(normalized)
|
|
18
18
|
self
|
|
19
19
|
end
|
|
20
20
|
|
data/lib/featureflow/version.rb
CHANGED
|
@@ -9,7 +9,7 @@ class FeatureflowGenerator < Rails::Generators::Base
|
|
|
9
9
|
desc "Configures the featureflow with your API key"
|
|
10
10
|
|
|
11
11
|
def create_initializer_file
|
|
12
|
-
unless /^srv-env-[a-f0-9]{32}$/ =~ api_key
|
|
12
|
+
unless /^sdk-srv-env-[a-f0-9]{32}$/ =~ api_key
|
|
13
13
|
raise Thor::Error, "Invalid featureflow environment api key #{api_key.inspect}\nYou can find your environment api key on your featureflow dashboard at https://[APP-NAME].featureflow.io/"
|
|
14
14
|
end
|
|
15
15
|
|
metadata
CHANGED
|
@@ -1,85 +1,99 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: featureflow
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.7.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Oliver Oldfield-Hodge
|
|
8
|
-
autorequire:
|
|
8
|
+
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2026-08-11 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
|
-
name:
|
|
14
|
+
name: rake
|
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
|
16
16
|
requirements:
|
|
17
17
|
- - "~>"
|
|
18
18
|
- !ruby/object:Gem::Version
|
|
19
|
-
version: '
|
|
19
|
+
version: '13.0'
|
|
20
20
|
type: :development
|
|
21
21
|
prerelease: false
|
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
|
23
23
|
requirements:
|
|
24
24
|
- - "~>"
|
|
25
25
|
- !ruby/object:Gem::Version
|
|
26
|
-
version: '
|
|
26
|
+
version: '13.0'
|
|
27
27
|
- !ruby/object:Gem::Dependency
|
|
28
|
-
name:
|
|
28
|
+
name: rspec
|
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
|
30
30
|
requirements:
|
|
31
31
|
- - "~>"
|
|
32
32
|
- !ruby/object:Gem::Version
|
|
33
|
-
version: '
|
|
33
|
+
version: '3.0'
|
|
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: '
|
|
40
|
+
version: '3.0'
|
|
41
41
|
- !ruby/object:Gem::Dependency
|
|
42
|
-
name:
|
|
42
|
+
name: cucumber
|
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
|
44
44
|
requirements:
|
|
45
45
|
- - "~>"
|
|
46
46
|
- !ruby/object:Gem::Version
|
|
47
|
-
version: '
|
|
47
|
+
version: '11.0'
|
|
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: '11.0'
|
|
55
55
|
- !ruby/object:Gem::Dependency
|
|
56
|
-
name:
|
|
56
|
+
name: webrick
|
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - "~>"
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '1.8'
|
|
62
|
+
type: :development
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - "~>"
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '1.8'
|
|
69
|
+
- !ruby/object:Gem::Dependency
|
|
70
|
+
name: debug
|
|
57
71
|
requirement: !ruby/object:Gem::Requirement
|
|
58
72
|
requirements:
|
|
59
73
|
- - "~>"
|
|
60
74
|
- !ruby/object:Gem::Version
|
|
61
|
-
version:
|
|
75
|
+
version: '1.9'
|
|
62
76
|
type: :development
|
|
63
77
|
prerelease: false
|
|
64
78
|
version_requirements: !ruby/object:Gem::Requirement
|
|
65
79
|
requirements:
|
|
66
80
|
- - "~>"
|
|
67
81
|
- !ruby/object:Gem::Version
|
|
68
|
-
version:
|
|
82
|
+
version: '1.9'
|
|
69
83
|
- !ruby/object:Gem::Dependency
|
|
70
84
|
name: excon
|
|
71
85
|
requirement: !ruby/object:Gem::Requirement
|
|
72
86
|
requirements:
|
|
73
87
|
- - "~>"
|
|
74
88
|
- !ruby/object:Gem::Version
|
|
75
|
-
version:
|
|
89
|
+
version: '1.5'
|
|
76
90
|
type: :runtime
|
|
77
91
|
prerelease: false
|
|
78
92
|
version_requirements: !ruby/object:Gem::Requirement
|
|
79
93
|
requirements:
|
|
80
94
|
- - "~>"
|
|
81
95
|
- !ruby/object:Gem::Version
|
|
82
|
-
version:
|
|
96
|
+
version: '1.5'
|
|
83
97
|
description: Connects your server with Featureflow. Create features at https://featureflow.io.
|
|
84
98
|
email:
|
|
85
99
|
- contact@featureflow.io
|
|
@@ -87,15 +101,9 @@ executables: []
|
|
|
87
101
|
extensions: []
|
|
88
102
|
extra_rdoc_files: []
|
|
89
103
|
files:
|
|
90
|
-
- ".gitignore"
|
|
91
|
-
- ".rubocop.yml"
|
|
92
104
|
- CHANGELOG.md
|
|
93
|
-
- Gemfile
|
|
94
|
-
- Gemfile.lock
|
|
95
105
|
- LICENSE
|
|
96
106
|
- README.md
|
|
97
|
-
- Rakefile
|
|
98
|
-
- featureflow.gemspec
|
|
99
107
|
- lib/featureflow.rb
|
|
100
108
|
- lib/featureflow/client.rb
|
|
101
109
|
- lib/featureflow/conditions.rb
|
|
@@ -110,13 +118,15 @@ files:
|
|
|
110
118
|
- lib/featureflow/user_builder.rb
|
|
111
119
|
- lib/featureflow/version.rb
|
|
112
120
|
- lib/generators/featureflow_generator.rb
|
|
113
|
-
- publish-gem.sh
|
|
114
|
-
- test.rb
|
|
115
121
|
homepage: https://github.com/featureflow/featureflow-ruby-sdk
|
|
116
122
|
licenses:
|
|
117
|
-
-
|
|
118
|
-
metadata:
|
|
119
|
-
|
|
123
|
+
- Apache-2.0
|
|
124
|
+
metadata:
|
|
125
|
+
homepage_uri: https://github.com/featureflow/featureflow-ruby-sdk
|
|
126
|
+
source_code_uri: https://github.com/featureflow/featureflow-ruby-sdk
|
|
127
|
+
changelog_uri: https://github.com/featureflow/featureflow-ruby-sdk/blob/master/CHANGELOG.md
|
|
128
|
+
bug_tracker_uri: https://github.com/featureflow/featureflow-ruby-sdk/issues
|
|
129
|
+
post_install_message:
|
|
120
130
|
rdoc_options: []
|
|
121
131
|
require_paths:
|
|
122
132
|
- lib
|
|
@@ -124,16 +134,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
124
134
|
requirements:
|
|
125
135
|
- - ">="
|
|
126
136
|
- !ruby/object:Gem::Version
|
|
127
|
-
version: '
|
|
137
|
+
version: '3.1'
|
|
128
138
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
129
139
|
requirements:
|
|
130
140
|
- - ">="
|
|
131
141
|
- !ruby/object:Gem::Version
|
|
132
142
|
version: '0'
|
|
133
143
|
requirements: []
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
signing_key:
|
|
144
|
+
rubygems_version: 3.5.22
|
|
145
|
+
signing_key:
|
|
137
146
|
specification_version: 4
|
|
138
147
|
summary: Ruby SDK for Featureflow
|
|
139
148
|
test_files: []
|
data/.gitignore
DELETED
data/.rubocop.yml
DELETED
data/Gemfile
DELETED
data/Gemfile.lock
DELETED
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
PATH
|
|
2
|
-
remote: .
|
|
3
|
-
specs:
|
|
4
|
-
featureflow (0.6.0)
|
|
5
|
-
excon (~> 0.57.0)
|
|
6
|
-
|
|
7
|
-
GEM
|
|
8
|
-
remote: https://rubygems.org/
|
|
9
|
-
specs:
|
|
10
|
-
builder (3.2.3)
|
|
11
|
-
cucumber (2.4.0)
|
|
12
|
-
builder (>= 2.1.2)
|
|
13
|
-
cucumber-core (~> 1.5.0)
|
|
14
|
-
cucumber-wire (~> 0.0.1)
|
|
15
|
-
diff-lcs (>= 1.1.3)
|
|
16
|
-
gherkin (~> 4.0)
|
|
17
|
-
multi_json (>= 1.7.5, < 2.0)
|
|
18
|
-
multi_test (>= 0.1.2)
|
|
19
|
-
cucumber-core (1.5.0)
|
|
20
|
-
gherkin (~> 4.0)
|
|
21
|
-
cucumber-wire (0.0.1)
|
|
22
|
-
diff-lcs (1.3)
|
|
23
|
-
excon (0.57.1)
|
|
24
|
-
gherkin (4.1.3)
|
|
25
|
-
multi_json (1.12.1)
|
|
26
|
-
multi_test (0.1.2)
|
|
27
|
-
rake (10.5.0)
|
|
28
|
-
rspec (3.6.0)
|
|
29
|
-
rspec-core (~> 3.6.0)
|
|
30
|
-
rspec-expectations (~> 3.6.0)
|
|
31
|
-
rspec-mocks (~> 3.6.0)
|
|
32
|
-
rspec-core (3.6.0)
|
|
33
|
-
rspec-support (~> 3.6.0)
|
|
34
|
-
rspec-expectations (3.6.0)
|
|
35
|
-
diff-lcs (>= 1.2.0, < 2.0)
|
|
36
|
-
rspec-support (~> 3.6.0)
|
|
37
|
-
rspec-mocks (3.6.0)
|
|
38
|
-
diff-lcs (>= 1.2.0, < 2.0)
|
|
39
|
-
rspec-support (~> 3.6.0)
|
|
40
|
-
rspec-support (3.6.0)
|
|
41
|
-
|
|
42
|
-
PLATFORMS
|
|
43
|
-
ruby
|
|
44
|
-
|
|
45
|
-
DEPENDENCIES
|
|
46
|
-
bundler (~> 1.15)
|
|
47
|
-
cucumber (~> 2.4.0)
|
|
48
|
-
featureflow!
|
|
49
|
-
rake (~> 10.0)
|
|
50
|
-
rspec (~> 3.0)
|
|
51
|
-
|
|
52
|
-
BUNDLED WITH
|
|
53
|
-
1.15.3
|
data/Rakefile
DELETED
data/featureflow.gemspec
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
# coding: utf-8
|
|
2
|
-
lib = File.expand_path('../lib', __FILE__)
|
|
3
|
-
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
|
-
require 'featureflow/version'
|
|
5
|
-
|
|
6
|
-
Gem::Specification.new do |spec|
|
|
7
|
-
spec.name = "featureflow"
|
|
8
|
-
spec.version = Featureflow::VERSION
|
|
9
|
-
spec.authors = ["Oliver Oldfield-Hodge"]
|
|
10
|
-
spec.email = ["contact@featureflow.io"]
|
|
11
|
-
|
|
12
|
-
spec.summary = "Ruby SDK for Featureflow"
|
|
13
|
-
spec.description = "Connects your server with Featureflow. Create features at https://featureflow.io."
|
|
14
|
-
spec.homepage = "https://github.com/featureflow/featureflow-ruby-sdk"
|
|
15
|
-
spec.license = "MIT"
|
|
16
|
-
|
|
17
|
-
# Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
|
|
18
|
-
# to allow pushing to a single host or delete this section to allow pushing to any host.
|
|
19
|
-
# if spec.respond_to?(:metadata)
|
|
20
|
-
# spec.metadata["allowed_push_host"] = "TODO: Set to 'http://mygemserver.com'"
|
|
21
|
-
# else
|
|
22
|
-
# raise "RubyGems 2.0 or newer is required to protect against " \
|
|
23
|
-
# "public gem pushes."
|
|
24
|
-
# end
|
|
25
|
-
|
|
26
|
-
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
|
27
|
-
f.match(%r{^(test|spec|features)/})
|
|
28
|
-
end
|
|
29
|
-
spec.bindir = "exe"
|
|
30
|
-
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
|
31
|
-
spec.require_paths = ["lib"]
|
|
32
|
-
|
|
33
|
-
spec.add_development_dependency "bundler", "~> 1.15"
|
|
34
|
-
spec.add_development_dependency "rake", "~> 10.0"
|
|
35
|
-
spec.add_development_dependency "rspec", "~> 3.0"
|
|
36
|
-
spec.add_development_dependency "cucumber", "~> 2.4.0"
|
|
37
|
-
spec.add_dependency "excon", "~> 0.57.0"
|
|
38
|
-
end
|
data/publish-gem.sh
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
gem push
|
data/test.rb
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
$:.unshift(File.dirname(__FILE__) + "/lib/")
|
|
2
|
-
|
|
3
|
-
require 'featureflow'
|
|
4
|
-
with_features = [
|
|
5
|
-
Featureflow::Feature.create('default', 'variant'),
|
|
6
|
-
Featureflow::Feature.create('oli-f1', 'off')
|
|
7
|
-
]
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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?
|
|
26
|
-
|
|
27
|
-
#
|
|
28
|
-
loop do
|
|
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
|