determinator 2.5.1 → 2.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/gem-push.yml +33 -0
- data/CHANGELOG.md +29 -0
- data/determinator.gemspec +4 -4
- data/examples/determinator-rails/Gemfile +1 -2
- data/examples/determinator-rails/Gemfile.lock +93 -89
- data/examples/determinator-rails/app/controllers/index_controller.rb +6 -2
- data/examples/determinator-rails/config/initializers/determinator.rb +4 -2
- data/examples/determinator-rails/config/initializers/new_framework_defaults.rb +0 -1
- data/examples/determinator-rails/example_features/colloquial_welcome +14 -0
- data/examples/determinator-rails/example_features/welcome_emoji +19 -0
- data/lib/determinator.rb +3 -3
- data/lib/determinator/control.rb +22 -6
- data/lib/determinator/feature.rb +10 -10
- data/lib/determinator/serializers/json.rb +1 -0
- data/lib/determinator/version.rb +1 -1
- data/lib/rspec/determinator.rb +5 -2
- metadata +17 -15
- data/examples/determinator-rails/.env +0 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: dffc736e777d38ceafbe8ce45d0a01f960341a542a2b93b6ba13b77f1479d291
|
4
|
+
data.tar.gz: 7be7ecbd850faac8009e031d41eea1d9679d5f2c43923cbc8ad688c6c1411f3a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5b959c8460720151fd7455f1bbc556af6f61f327c7092bad148d52bc9141c3e5144c17a7c6cb45c483322f9dc65ee047ceccc1a048eea15e46544f6aa4fb06e1
|
7
|
+
data.tar.gz: b8b08c82db627c37d8630dd56111d0361b3c5a5874286d447015ac98d0d25a2421631f589f4438b26d71bc9ed58f5141d8084563dd37c24c7f5ca71faac62156
|
@@ -0,0 +1,33 @@
|
|
1
|
+
name: Ruby Gem
|
2
|
+
|
3
|
+
on:
|
4
|
+
push:
|
5
|
+
branches: [ master ]
|
6
|
+
|
7
|
+
jobs:
|
8
|
+
build:
|
9
|
+
name: Build + Publish
|
10
|
+
runs-on: ubuntu-latest
|
11
|
+
|
12
|
+
steps:
|
13
|
+
- uses: actions/checkout@v2
|
14
|
+
- name: Set git user
|
15
|
+
run: |
|
16
|
+
git config user.email "rooci@deliveroo.co.uk"
|
17
|
+
git config user.name "Determinator Release Github Action"
|
18
|
+
- name: Set up Ruby 2.6
|
19
|
+
uses: actions/setup-ruby@v1
|
20
|
+
with:
|
21
|
+
ruby-version: 2.6.x
|
22
|
+
|
23
|
+
- name: Publish to RubyGems
|
24
|
+
run: |
|
25
|
+
mkdir -p $HOME/.gem
|
26
|
+
touch $HOME/.gem/credentials
|
27
|
+
chmod 0600 $HOME/.gem/credentials
|
28
|
+
printf -- "---\n:rubygems_api_key: ${GEM_HOST_API_KEY}\n" > $HOME/.gem/credentials
|
29
|
+
gem install bundler
|
30
|
+
bundle install --jobs 4 --retry 3
|
31
|
+
rake release
|
32
|
+
env:
|
33
|
+
GEM_HOST_API_KEY: "${{secrets.RUBYGEMS_AUTH_TOKEN}}"
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,32 @@
|
|
1
|
+
# 2.7.0
|
2
|
+
|
3
|
+
⚠️ This release includes breaking changes ⚠️
|
4
|
+
|
5
|
+
Interface change:
|
6
|
+
- Constraints which are not arrays of strings are no longer accepted; if present, the library returns false and logs an error.
|
7
|
+
|
8
|
+
# 2.6.0
|
9
|
+
|
10
|
+
Interface change:
|
11
|
+
- A `feature_cache` is now required to use Determinator. See the `examples/determinator-rails/config/initializers/determinator.rb` for a quick start.
|
12
|
+
|
13
|
+
# 2.5.4
|
14
|
+
|
15
|
+
Bug fix:
|
16
|
+
- Apply app_version logic to structured request.app_version too
|
17
|
+
|
18
|
+
# 2.5.3
|
19
|
+
|
20
|
+
Bug fix:
|
21
|
+
- Avoid errors when updating the gem and using persistent cache resulting in null fixed_determinations
|
22
|
+
|
23
|
+
# 2.5.2
|
24
|
+
|
25
|
+
Feature:
|
26
|
+
- Add structured_bucket to Feature
|
27
|
+
- Add `#retrieve` method to the Control
|
28
|
+
- Add optional `feature` argument to `feature_flag_on?` and `which_variant`, to reuse an existing feature
|
29
|
+
|
1
30
|
# 2.5.1
|
2
31
|
|
3
32
|
Feature:
|
data/determinator.gemspec
CHANGED
@@ -23,12 +23,12 @@ Gem::Specification.new do |spec|
|
|
23
23
|
spec.add_runtime_dependency "faraday"
|
24
24
|
spec.add_runtime_dependency "semantic", "~> 1.6"
|
25
25
|
|
26
|
-
spec.add_development_dependency "bundler"
|
27
|
-
spec.add_development_dependency "rake", "~>
|
26
|
+
spec.add_development_dependency "bundler"
|
27
|
+
spec.add_development_dependency "rake", "~> 12.0"
|
28
28
|
spec.add_development_dependency "rspec", "~> 3.0"
|
29
29
|
spec.add_development_dependency "rspec-its", "~> 1.2"
|
30
30
|
spec.add_development_dependency "guard-rspec", "~> 4.7"
|
31
|
-
spec.add_development_dependency "
|
31
|
+
spec.add_development_dependency "factory_bot", "~> 4.8"
|
32
32
|
spec.add_development_dependency 'webmock'
|
33
|
-
spec.add_development_dependency
|
33
|
+
spec.add_development_dependency 'sidekiq'
|
34
34
|
end
|
@@ -1,140 +1,144 @@
|
|
1
1
|
PATH
|
2
2
|
remote: ../..
|
3
3
|
specs:
|
4
|
-
determinator (
|
4
|
+
determinator (2.6.0)
|
5
5
|
faraday
|
6
|
+
semantic (~> 1.6)
|
6
7
|
|
7
8
|
GEM
|
8
9
|
remote: https://rubygems.org/
|
9
10
|
specs:
|
10
|
-
actioncable (5.
|
11
|
-
actionpack (= 5.
|
12
|
-
nio4r (
|
13
|
-
websocket-driver (
|
14
|
-
actionmailer (5.
|
15
|
-
actionpack (= 5.
|
16
|
-
actionview (= 5.
|
17
|
-
activejob (= 5.
|
11
|
+
actioncable (5.2.4.4)
|
12
|
+
actionpack (= 5.2.4.4)
|
13
|
+
nio4r (~> 2.0)
|
14
|
+
websocket-driver (>= 0.6.1)
|
15
|
+
actionmailer (5.2.4.4)
|
16
|
+
actionpack (= 5.2.4.4)
|
17
|
+
actionview (= 5.2.4.4)
|
18
|
+
activejob (= 5.2.4.4)
|
18
19
|
mail (~> 2.5, >= 2.5.4)
|
19
20
|
rails-dom-testing (~> 2.0)
|
20
|
-
actionpack (5.
|
21
|
-
actionview (= 5.
|
22
|
-
activesupport (= 5.
|
23
|
-
rack (~> 2.0)
|
24
|
-
rack-test (
|
21
|
+
actionpack (5.2.4.4)
|
22
|
+
actionview (= 5.2.4.4)
|
23
|
+
activesupport (= 5.2.4.4)
|
24
|
+
rack (~> 2.0, >= 2.0.8)
|
25
|
+
rack-test (>= 0.6.3)
|
25
26
|
rails-dom-testing (~> 2.0)
|
26
27
|
rails-html-sanitizer (~> 1.0, >= 1.0.2)
|
27
|
-
actionview (5.
|
28
|
-
activesupport (= 5.
|
28
|
+
actionview (5.2.4.4)
|
29
|
+
activesupport (= 5.2.4.4)
|
29
30
|
builder (~> 3.1)
|
30
|
-
|
31
|
+
erubi (~> 1.4)
|
31
32
|
rails-dom-testing (~> 2.0)
|
32
33
|
rails-html-sanitizer (~> 1.0, >= 1.0.3)
|
33
|
-
activejob (5.
|
34
|
-
activesupport (= 5.
|
34
|
+
activejob (5.2.4.4)
|
35
|
+
activesupport (= 5.2.4.4)
|
35
36
|
globalid (>= 0.3.6)
|
36
|
-
activemodel (5.
|
37
|
-
activesupport (= 5.
|
38
|
-
activerecord (5.
|
39
|
-
activemodel (= 5.
|
40
|
-
activesupport (= 5.
|
41
|
-
arel (
|
42
|
-
|
37
|
+
activemodel (5.2.4.4)
|
38
|
+
activesupport (= 5.2.4.4)
|
39
|
+
activerecord (5.2.4.4)
|
40
|
+
activemodel (= 5.2.4.4)
|
41
|
+
activesupport (= 5.2.4.4)
|
42
|
+
arel (>= 9.0)
|
43
|
+
activestorage (5.2.4.4)
|
44
|
+
actionpack (= 5.2.4.4)
|
45
|
+
activerecord (= 5.2.4.4)
|
46
|
+
marcel (~> 0.3.1)
|
47
|
+
activesupport (5.2.4.4)
|
43
48
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
44
49
|
i18n (>= 0.7, < 2)
|
45
50
|
minitest (~> 5.1)
|
46
51
|
tzinfo (~> 1.1)
|
47
|
-
arel (
|
48
|
-
builder (3.2.
|
49
|
-
byebug (
|
50
|
-
concurrent-ruby (1.
|
51
|
-
|
52
|
-
|
53
|
-
dotenv (2.
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
52
|
+
arel (9.0.0)
|
53
|
+
builder (3.2.4)
|
54
|
+
byebug (11.1.3)
|
55
|
+
concurrent-ruby (1.1.8)
|
56
|
+
crass (1.0.6)
|
57
|
+
dotenv (2.7.6)
|
58
|
+
dotenv-rails (2.7.6)
|
59
|
+
dotenv (= 2.7.6)
|
60
|
+
railties (>= 3.2)
|
61
|
+
erubi (1.10.0)
|
62
|
+
faraday (1.3.0)
|
63
|
+
faraday-net_http (~> 1.0)
|
59
64
|
multipart-post (>= 1.2, < 3)
|
60
|
-
|
65
|
+
ruby2_keywords
|
66
|
+
faraday-net_http (1.0.1)
|
67
|
+
globalid (0.4.2)
|
61
68
|
activesupport (>= 4.2.0)
|
62
|
-
i18n (1.
|
69
|
+
i18n (1.8.7)
|
63
70
|
concurrent-ruby (~> 1.0)
|
64
|
-
loofah (2.
|
71
|
+
loofah (2.9.0)
|
65
72
|
crass (~> 1.0.2)
|
66
73
|
nokogiri (>= 1.5.9)
|
67
|
-
mail (2.7.
|
74
|
+
mail (2.7.1)
|
68
75
|
mini_mime (>= 0.1.1)
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
rack
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
76
|
+
marcel (0.3.3)
|
77
|
+
mimemagic (~> 0.3.2)
|
78
|
+
method_source (1.0.0)
|
79
|
+
mimemagic (0.3.5)
|
80
|
+
mini_mime (1.0.2)
|
81
|
+
minitest (5.14.3)
|
82
|
+
multipart-post (2.1.1)
|
83
|
+
nio4r (2.5.4)
|
84
|
+
nokogiri (1.11.1-x86_64-darwin)
|
85
|
+
racc (~> 1.4)
|
86
|
+
puma (3.12.6)
|
87
|
+
racc (1.5.2)
|
88
|
+
rack (2.2.3)
|
89
|
+
rack-test (1.1.0)
|
90
|
+
rack (>= 1.0, < 3)
|
91
|
+
rails (5.2.4.4)
|
92
|
+
actioncable (= 5.2.4.4)
|
93
|
+
actionmailer (= 5.2.4.4)
|
94
|
+
actionpack (= 5.2.4.4)
|
95
|
+
actionview (= 5.2.4.4)
|
96
|
+
activejob (= 5.2.4.4)
|
97
|
+
activemodel (= 5.2.4.4)
|
98
|
+
activerecord (= 5.2.4.4)
|
99
|
+
activestorage (= 5.2.4.4)
|
100
|
+
activesupport (= 5.2.4.4)
|
92
101
|
bundler (>= 1.3.0)
|
93
|
-
railties (= 5.
|
102
|
+
railties (= 5.2.4.4)
|
94
103
|
sprockets-rails (>= 2.0.0)
|
95
104
|
rails-dom-testing (2.0.3)
|
96
105
|
activesupport (>= 4.2.0)
|
97
106
|
nokogiri (>= 1.6)
|
98
|
-
rails-html-sanitizer (1.0
|
99
|
-
loofah (~> 2.
|
100
|
-
railties (5.
|
101
|
-
actionpack (= 5.
|
102
|
-
activesupport (= 5.
|
107
|
+
rails-html-sanitizer (1.3.0)
|
108
|
+
loofah (~> 2.3)
|
109
|
+
railties (5.2.4.4)
|
110
|
+
actionpack (= 5.2.4.4)
|
111
|
+
activesupport (= 5.2.4.4)
|
103
112
|
method_source
|
104
113
|
rake (>= 0.8.7)
|
105
|
-
thor (>= 0.
|
106
|
-
rake (
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
connection_pool (~> 2.2, >= 2.2.0)
|
111
|
-
rack-protection (>= 1.5.0)
|
112
|
-
redis (>= 3.3.5, < 5)
|
113
|
-
sprockets (3.7.1)
|
114
|
+
thor (>= 0.19.0, < 2.0)
|
115
|
+
rake (13.0.3)
|
116
|
+
ruby2_keywords (0.0.4)
|
117
|
+
semantic (1.6.1)
|
118
|
+
sprockets (4.0.2)
|
114
119
|
concurrent-ruby (~> 1.0)
|
115
120
|
rack (> 1, < 3)
|
116
|
-
sprockets-rails (3.2.
|
121
|
+
sprockets-rails (3.2.2)
|
117
122
|
actionpack (>= 4.0)
|
118
123
|
activesupport (>= 4.0)
|
119
124
|
sprockets (>= 3.0.0)
|
120
|
-
thor (
|
125
|
+
thor (1.1.0)
|
121
126
|
thread_safe (0.3.6)
|
122
|
-
tzinfo (1.2.
|
127
|
+
tzinfo (1.2.9)
|
123
128
|
thread_safe (~> 0.1)
|
124
|
-
websocket-driver (0.
|
129
|
+
websocket-driver (0.7.3)
|
125
130
|
websocket-extensions (>= 0.1.0)
|
126
|
-
websocket-extensions (0.1.
|
131
|
+
websocket-extensions (0.1.5)
|
127
132
|
|
128
133
|
PLATFORMS
|
129
|
-
|
134
|
+
x86_64-darwin-19
|
130
135
|
|
131
136
|
DEPENDENCIES
|
132
137
|
byebug
|
133
138
|
determinator!
|
134
139
|
dotenv-rails
|
135
140
|
puma (~> 3.0)
|
136
|
-
rails (~> 5.
|
137
|
-
sidekiq
|
141
|
+
rails (~> 5.1)
|
138
142
|
|
139
143
|
BUNDLED WITH
|
140
|
-
|
144
|
+
2.2.7
|
@@ -5,9 +5,13 @@ class IndexController < ApplicationController
|
|
5
5
|
|
6
6
|
message = [
|
7
7
|
is_colloquial ? "hi world" : "hello world",
|
8
|
-
emoji
|
8
|
+
(emoji if emoji)
|
9
9
|
].compact.join(" ")
|
10
10
|
|
11
|
-
|
11
|
+
explain = "An experiment and a feature flag are being checked for the user with guid #{guid}. "
|
12
|
+
explain += "The feature flag (colloquial_welcome) is #{is_colloquial ? 'on' : 'off'}. "
|
13
|
+
explain += "The experiment (welcome_emoji) returned #{emoji}#{", so is omitted" unless emoji}."
|
14
|
+
|
15
|
+
render json: { welcome: message, explanation: explain }
|
12
16
|
end
|
13
17
|
end
|
@@ -1,7 +1,9 @@
|
|
1
|
-
require 'determinator/retrieve/
|
1
|
+
require 'determinator/retrieve/file'
|
2
2
|
require 'active_support/cache'
|
3
3
|
|
4
|
-
|
4
|
+
# File retriever just for example; use a Dynaconf retriever in your app
|
5
|
+
# retrieval = Determinator::Retrieve::Dynaconf.new(base_url: ENV['DYNACONF_URL'], service_name: 'determinator-rails')
|
6
|
+
retrieval = Determinator::Retrieve::File.new(root: File.join(__dir__, "../../example_features"))
|
5
7
|
feature_cache = Determinator::Cache::FetchWrapper.new(
|
6
8
|
ActiveSupport::Cache::MemoryStore.new(expires_in: 1.minute)
|
7
9
|
)
|
@@ -0,0 +1,14 @@
|
|
1
|
+
{
|
2
|
+
"id": "colloquial_welcome",
|
3
|
+
"identifier": "colloquial_welcome",
|
4
|
+
"name": "Whether a colloquial welcome should be used",
|
5
|
+
"active": true,
|
6
|
+
"bucket_type": "guid",
|
7
|
+
"target_groups": [
|
8
|
+
{
|
9
|
+
"name": "50% of users",
|
10
|
+
"rollout": 32768,
|
11
|
+
"constraints": {}
|
12
|
+
}
|
13
|
+
]
|
14
|
+
}
|
@@ -0,0 +1,19 @@
|
|
1
|
+
{
|
2
|
+
"id": "welcome_emoji",
|
3
|
+
"identifier": "welcome_emoji",
|
4
|
+
"name": "What emoji should be used for the example hello",
|
5
|
+
"active": true,
|
6
|
+
"bucket_type": "guid",
|
7
|
+
"variants": {
|
8
|
+
"👋": 1,
|
9
|
+
"🎉": 1
|
10
|
+
},
|
11
|
+
"winning_variant": null,
|
12
|
+
"target_groups": [
|
13
|
+
{
|
14
|
+
"name": "50% of users",
|
15
|
+
"rollout": 32768,
|
16
|
+
"constraints": {}
|
17
|
+
}
|
18
|
+
]
|
19
|
+
}
|
data/lib/determinator.rb
CHANGED
@@ -15,13 +15,13 @@ module Determinator
|
|
15
15
|
class << self
|
16
16
|
attr_reader :feature_cache, :retrieval
|
17
17
|
# @param :retrieval [Determinator::Retrieve::Routemaster] A retrieval instance for Features
|
18
|
+
# @param :feature_cache [#call] a caching proc, accepting a feature name, which will return the named feature or yield (and store) if not available
|
18
19
|
# @param :errors [#call, nil] a proc, accepting an error, which will be called with any errors which occur while determinating
|
19
20
|
# @param :missing_feature [#call, nil] a proc, accepting a feature name, which will be called any time a feature is requested but isn't available
|
20
|
-
|
21
|
-
def configure(retrieval:, errors: nil, missing_feature: nil, feature_cache: nil)
|
21
|
+
def configure(retrieval:, feature_cache:, errors: nil, missing_feature: nil)
|
22
22
|
self.on_error(&errors) if errors
|
23
23
|
self.on_missing_feature(&missing_feature) if missing_feature
|
24
|
-
@feature_cache = feature_cache
|
24
|
+
@feature_cache = feature_cache
|
25
25
|
@retrieval = retrieval
|
26
26
|
@instance = Control.new(retrieval: retrieval)
|
27
27
|
end
|
data/lib/determinator/control.rb
CHANGED
@@ -30,10 +30,11 @@ module Determinator
|
|
30
30
|
# @param :id [#to_s] The id of the actor being determinated for
|
31
31
|
# @param :guid [#to_s] The Anonymous id of the actor being determinated for
|
32
32
|
# @param :properties [Hash<Symbol,String>] The properties of this actor which will be used for including this actor or not
|
33
|
+
# @param :feature [Feature] The feature to use instead of retrieving one
|
33
34
|
# @raise [ArgumentError] When the arguments given to this method aren't ever going to produce a useful response
|
34
35
|
# @return [true,false] Whether the feature is on (true) or off (false) for this actor
|
35
|
-
def feature_flag_on?(name, id: nil, guid: nil, properties: {})
|
36
|
-
determinate_and_notice(name, id: id, guid: guid, properties: properties) do |feature|
|
36
|
+
def feature_flag_on?(name, id: nil, guid: nil, properties: {}, feature: nil)
|
37
|
+
determinate_and_notice(name, id: id, guid: guid, properties: properties, feature: feature) do |feature|
|
37
38
|
feature.feature_flag?
|
38
39
|
end
|
39
40
|
end
|
@@ -44,10 +45,11 @@ module Determinator
|
|
44
45
|
# @param :id [#to_s] The id of the actor being determinated for
|
45
46
|
# @param :guid [#to_s] The Anonymous id of the actor being determinated for
|
46
47
|
# @param :properties [Hash<Symbol,String>] The properties of this actor which will be used for including this actor or not
|
48
|
+
# @param :feature [Feature] The feature to use instead of retrieving one
|
47
49
|
# @raise [ArgumentError] When the arguments given to this method aren't ever going to produce a useful response
|
48
50
|
# @return [false,String] Returns false, if the actor is not in this experiment, or otherwise the variant name.
|
49
|
-
def which_variant(name, id: nil, guid: nil, properties: {})
|
50
|
-
determinate_and_notice(name, id: id, guid: guid, properties: properties) do |feature|
|
51
|
+
def which_variant(name, id: nil, guid: nil, properties: {}, feature: nil)
|
52
|
+
determinate_and_notice(name, id: id, guid: guid, properties: properties, feature: feature) do |feature|
|
51
53
|
feature.experiment?
|
52
54
|
end
|
53
55
|
end
|
@@ -58,6 +60,14 @@ module Determinator
|
|
58
60
|
end
|
59
61
|
end
|
60
62
|
|
63
|
+
# Uses the retrieval (and a cache if set on the Determinator config) to fetch a feature definition.
|
64
|
+
#
|
65
|
+
# @param name [#to_s] The name of the experiment being checked
|
66
|
+
# @return [Feature, MissingResponse] Returns the Feature object, or MissingResponse if the feature is not found.
|
67
|
+
def retrieve(name)
|
68
|
+
Determinator.with_retrieval_cache(name) { retrieval.retrieve(name) }
|
69
|
+
end
|
70
|
+
|
61
71
|
def inspect
|
62
72
|
'#<Determinator::Control>'
|
63
73
|
end
|
@@ -66,8 +76,8 @@ module Determinator
|
|
66
76
|
|
67
77
|
Indicators = Struct.new(:rollout, :variant)
|
68
78
|
|
69
|
-
def determinate_and_notice(name, id:, guid:, properties:)
|
70
|
-
feature
|
79
|
+
def determinate_and_notice(name, id:, guid:, properties:, feature: nil)
|
80
|
+
feature ||= retrieve(name)
|
71
81
|
|
72
82
|
if feature.nil? || feature.is_a?(ErrorResponse) || feature.is_a?(MissingResponse)
|
73
83
|
Determinator.notice_missing_feature(name)
|
@@ -164,6 +174,8 @@ module Determinator
|
|
164
174
|
end
|
165
175
|
|
166
176
|
def choose_fixed_determination(feature, properties)
|
177
|
+
return unless feature.fixed_determinations
|
178
|
+
|
167
179
|
# Keys and values must be strings
|
168
180
|
normalised_properties = normalise_properties(properties)
|
169
181
|
|
@@ -207,6 +219,9 @@ module Determinator
|
|
207
219
|
end
|
208
220
|
|
209
221
|
def matches_constraints(normalised_properties, constraints)
|
222
|
+
unless constraints.all?{ |k, v| k.is_a?(String) && v.all?{ |vv| vv.is_a?(String) } }
|
223
|
+
raise "Constraints must by arrays of strings"
|
224
|
+
end
|
210
225
|
constraints.reduce(true) do |fit, (scope, *required)|
|
211
226
|
present = [*normalised_properties[scope]]
|
212
227
|
fit && matches_requirements?(scope, required, present)
|
@@ -216,6 +231,7 @@ module Determinator
|
|
216
231
|
def matches_requirements?(scope, required, present)
|
217
232
|
case scope
|
218
233
|
when "app_version" then has_any_app_version?(required, present)
|
234
|
+
when "request.app_version" then has_any_app_version?(required, present)
|
219
235
|
else has_any?(required, present)
|
220
236
|
end
|
221
237
|
end
|
data/lib/determinator/feature.rb
CHANGED
@@ -3,9 +3,9 @@ module Determinator
|
|
3
3
|
#
|
4
4
|
# @attr_reader [nil,Hash<String,Integer>] variants The variants for this experiment, with the name of the variant as the key and the weight as the value. Will be nil for non-experiments.
|
5
5
|
class Feature
|
6
|
-
attr_reader :name, :identifier, :bucket_type, :variants, :target_groups, :fixed_determinations, :active, :winning_variant
|
6
|
+
attr_reader :name, :identifier, :bucket_type, :structured_bucket, :variants, :target_groups, :fixed_determinations, :active, :winning_variant
|
7
7
|
|
8
|
-
def initialize(name:, identifier:, bucket_type:, target_groups:, fixed_determinations: [], variants: {}, overrides: {}, active: false, winning_variant: nil)
|
8
|
+
def initialize(name:, identifier:, bucket_type:, target_groups:, structured_bucket: nil, fixed_determinations: [], variants: {}, overrides: {}, active: false, winning_variant: nil)
|
9
9
|
@name = name.to_s
|
10
10
|
@identifier = identifier.to_s
|
11
11
|
@variants = variants
|
@@ -14,6 +14,7 @@ module Determinator
|
|
14
14
|
@winning_variant = parse_outcome(winning_variant, allow_exclusion: false)
|
15
15
|
@active = active
|
16
16
|
@bucket_type = bucket_type.to_sym
|
17
|
+
@structured_bucket = structured_bucket
|
17
18
|
|
18
19
|
# To prevent confusion between actor id data types
|
19
20
|
@overrides = overrides.each_with_object({}) do |(identifier, outcome), hash|
|
@@ -36,6 +37,11 @@ module Determinator
|
|
36
37
|
variants.empty?
|
37
38
|
end
|
38
39
|
|
40
|
+
# @return [true,false] Is this feature using structured identification?
|
41
|
+
def structured?
|
42
|
+
!!structured_bucket && !structured_bucket.empty?
|
43
|
+
end
|
44
|
+
|
39
45
|
# Is this feature overridden for the given actor id?
|
40
46
|
#
|
41
47
|
# @return [true,false] Whether this feature is overridden for this actor
|
@@ -78,7 +84,7 @@ module Determinator
|
|
78
84
|
TargetGroup.new(
|
79
85
|
name: target_group['name'],
|
80
86
|
rollout: target_group['rollout'].to_i,
|
81
|
-
constraints:
|
87
|
+
constraints: constraints
|
82
88
|
)
|
83
89
|
|
84
90
|
# Invalid target groups are ignored
|
@@ -105,17 +111,11 @@ module Determinator
|
|
105
111
|
name: fixed_determination['name'],
|
106
112
|
feature_on: fixed_determination['feature_on'],
|
107
113
|
variant: variant,
|
108
|
-
constraints:
|
114
|
+
constraints: constraints
|
109
115
|
)
|
110
116
|
# Invalid fixed determinations are ignored
|
111
117
|
rescue
|
112
118
|
nil
|
113
119
|
end
|
114
|
-
|
115
|
-
def parse_constraints(constraints)
|
116
|
-
constraints.each_with_object({}) do |(key, value), hash|
|
117
|
-
hash[key.to_s] = [*value].map(&:to_s)
|
118
|
-
end
|
119
|
-
end
|
120
120
|
end
|
121
121
|
end
|
@@ -15,6 +15,7 @@ module Determinator
|
|
15
15
|
name: obj['name'],
|
16
16
|
identifier: obj['identifier'],
|
17
17
|
bucket_type: obj['bucket_type'],
|
18
|
+
structured_bucket: obj['structured_bucket'],
|
18
19
|
active: (obj['active'] === true),
|
19
20
|
target_groups: obj['target_groups'],
|
20
21
|
fixed_determinations: obj['fixed_determinations'].to_a,
|
data/lib/determinator/version.rb
CHANGED
data/lib/rspec/determinator.rb
CHANGED
@@ -3,6 +3,9 @@ require_relative '../determinator/retrieve/in_memory_retriever'
|
|
3
3
|
|
4
4
|
module RSpec
|
5
5
|
module Determinator
|
6
|
+
|
7
|
+
DO_NOT_USE_IN_PRODUCTION_CODE_NULL_FEATURE_CACHE = -> (name, &block) { block.call(name) }
|
8
|
+
|
6
9
|
def self.included(by)
|
7
10
|
by.extend(DSL)
|
8
11
|
|
@@ -12,10 +15,10 @@ module RSpec
|
|
12
15
|
old_retriever = ::Determinator.instance.retrieval
|
13
16
|
begin
|
14
17
|
fake_retriever.clear!
|
15
|
-
::Determinator.configure(retrieval: fake_retriever)
|
18
|
+
::Determinator.configure(retrieval: fake_retriever, feature_cache: DO_NOT_USE_IN_PRODUCTION_CODE_NULL_FEATURE_CACHE)
|
16
19
|
example.run
|
17
20
|
ensure
|
18
|
-
::Determinator.configure(retrieval: old_retriever)
|
21
|
+
::Determinator.configure(retrieval: old_retriever, feature_cache: DO_NOT_USE_IN_PRODUCTION_CODE_NULL_FEATURE_CACHE)
|
19
22
|
end
|
20
23
|
end
|
21
24
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: determinator
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.7.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- JP Hastings-Spital
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-04-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: faraday
|
@@ -42,30 +42,30 @@ dependencies:
|
|
42
42
|
name: bundler
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
|
-
- - "
|
45
|
+
- - ">="
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version:
|
47
|
+
version: '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: '0'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
56
|
name: rake
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
59
|
- - "~>"
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version: '
|
61
|
+
version: '12.0'
|
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: '
|
68
|
+
version: '12.0'
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: rspec
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
@@ -109,7 +109,7 @@ dependencies:
|
|
109
109
|
- !ruby/object:Gem::Version
|
110
110
|
version: '4.7'
|
111
111
|
- !ruby/object:Gem::Dependency
|
112
|
-
name:
|
112
|
+
name: factory_bot
|
113
113
|
requirement: !ruby/object:Gem::Requirement
|
114
114
|
requirements:
|
115
115
|
- - "~>"
|
@@ -150,7 +150,7 @@ dependencies:
|
|
150
150
|
- - ">="
|
151
151
|
- !ruby/object:Gem::Version
|
152
152
|
version: '0'
|
153
|
-
description:
|
153
|
+
description:
|
154
154
|
email:
|
155
155
|
- jp@deliveroo.co.uk
|
156
156
|
executables: []
|
@@ -159,6 +159,7 @@ extra_rdoc_files: []
|
|
159
159
|
files:
|
160
160
|
- ".circleci/config.yml"
|
161
161
|
- ".circleci/config.yml.erb"
|
162
|
+
- ".github/workflows/gem-push.yml"
|
162
163
|
- ".gitignore"
|
163
164
|
- ".gitmodules"
|
164
165
|
- ".rspec"
|
@@ -175,7 +176,6 @@ files:
|
|
175
176
|
- docs/background.md
|
176
177
|
- docs/img/determinator.jpg
|
177
178
|
- docs/local_development.md
|
178
|
-
- examples/determinator-rails/.env
|
179
179
|
- examples/determinator-rails/.gitignore
|
180
180
|
- examples/determinator-rails/Gemfile
|
181
181
|
- examples/determinator-rails/Gemfile.lock
|
@@ -202,6 +202,8 @@ files:
|
|
202
202
|
- examples/determinator-rails/config/puma.rb
|
203
203
|
- examples/determinator-rails/config/routes.rb
|
204
204
|
- examples/determinator-rails/config/secrets.yml
|
205
|
+
- examples/determinator-rails/example_features/colloquial_welcome
|
206
|
+
- examples/determinator-rails/example_features/welcome_emoji
|
205
207
|
- examples/determinator-rails/public/favicon.ico
|
206
208
|
- examples/determinator-rails/public/robots.txt
|
207
209
|
- lib/determinator.rb
|
@@ -234,7 +236,7 @@ homepage: https://github.com/deliveroo/determinator
|
|
234
236
|
licenses:
|
235
237
|
- MIT
|
236
238
|
metadata: {}
|
237
|
-
post_install_message:
|
239
|
+
post_install_message:
|
238
240
|
rdoc_options: []
|
239
241
|
require_paths:
|
240
242
|
- lib
|
@@ -249,8 +251,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
249
251
|
- !ruby/object:Gem::Version
|
250
252
|
version: '0'
|
251
253
|
requirements: []
|
252
|
-
rubygems_version: 3.0.
|
253
|
-
signing_key:
|
254
|
+
rubygems_version: 3.0.3
|
255
|
+
signing_key:
|
254
256
|
specification_version: 4
|
255
257
|
summary: Determine which experiments and features a specific actor should see.
|
256
258
|
test_files: []
|
@@ -1,7 +0,0 @@
|
|
1
|
-
ROUTEMASTER_DRAIN_TOKENS=demo
|
2
|
-
ROUTEMASTER_DRAIN_REDIS=redis://localhost/0/routemaster/drain
|
3
|
-
ROUTEMASTER_CACHE_REDIS=redis://localhost/0/routemaster/cache
|
4
|
-
ROUTEMASTER_CACHE_AUTH=
|
5
|
-
ROUTEMASTER_QUEUE_ADAPTER=sidekiq
|
6
|
-
ROUTEMASTER_QUEUE_NAME=routemaster
|
7
|
-
ROUTEMASTER_CALLBACK_URL=https://determinator-example.dev/events
|