molasses 0.2.0 → 0.3.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/README.md +37 -7
- data/lib/molasses.rb +139 -46
- data/spec/molasses_spec.rb +384 -232
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5c3763a8c5f6b0813ea2832633f4482c1447d0d224b220000dc6ae870d8ebea6
|
4
|
+
data.tar.gz: 163f9337d2b2a38824effaf9379a7db8244bc702ecf7408581067fa4875e7299
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 70e47f0bbdc196728ae7476f72e6495d5dcfa5150a2b2785a64a3842a79b45a738419e2682b5904b500d047d44441374374dd4903e366d85ae9f025d90339861
|
7
|
+
data.tar.gz: c8f65c905af6bc5e8d43d21471a882e03e48fbdf61f65be8ad935abf8bf6f5da2929ee64821409dcf26b1f3d70bb1d9f6dd2f46af5eaedd5ccf436cc790d5455
|
data/README.md
CHANGED
@@ -28,10 +28,12 @@ client = Molasses::Client.new("test_api_key")
|
|
28
28
|
|
29
29
|
```
|
30
30
|
|
31
|
-
If you decide
|
31
|
+
If you decide you want to auto track experiments being viewed (experiment started events) you can turn that on by setting the `:auto_send_events` field to `true`
|
32
32
|
|
33
33
|
```go
|
34
|
-
client = Molasses::Client.new("test_api_key",
|
34
|
+
client = Molasses::Client.new("test_api_key", {
|
35
|
+
:auto_send_events => true
|
36
|
+
})
|
35
37
|
|
36
38
|
```
|
37
39
|
|
@@ -56,20 +58,48 @@ You can check if a feature is active for a user who is anonymous by just calling
|
|
56
58
|
client.is_active("TEST_FEATURE_FOR_USER")
|
57
59
|
```
|
58
60
|
|
59
|
-
### Experiments
|
61
|
+
### Tracking and Experiments
|
60
62
|
|
61
|
-
To track
|
63
|
+
To track any analytics event you can call `track`. experiment_success takes the event's name,the user, and any additional parameters for the event.
|
62
64
|
|
63
65
|
```ruby
|
64
|
-
client.
|
66
|
+
client.track("Button Clicked", {
|
67
|
+
"id"=>"foo",
|
68
|
+
"params"=>{
|
69
|
+
"isBetaUser"=>"false",
|
70
|
+
"isScaredUser"=>"false"
|
71
|
+
}
|
72
|
+
}, {
|
65
73
|
"version": "v2.3.0"
|
66
|
-
},
|
74
|
+
},)
|
75
|
+
```
|
76
|
+
|
77
|
+
To track whether an experiment was started you can call `experiment_started`. experiment_started takes the feature's name,the user, and any additional parameters for the event.
|
78
|
+
|
79
|
+
```ruby
|
80
|
+
client.experiment_started("GOOGLE_SSO", {
|
67
81
|
"id"=>"foo",
|
68
82
|
"params"=>{
|
69
83
|
"isBetaUser"=>"false",
|
70
84
|
"isScaredUser"=>"false"
|
71
85
|
}
|
72
|
-
}
|
86
|
+
}, {
|
87
|
+
"version": "v2.3.0"
|
88
|
+
},)
|
89
|
+
```
|
90
|
+
|
91
|
+
To track whether an experiment was successful you can call `experiment_success`. experiment_success takes the feature's name,the user, and any additional parameters for the event.
|
92
|
+
|
93
|
+
```ruby
|
94
|
+
client.experiment_success("GOOGLE_SSO", {
|
95
|
+
"id"=>"foo",
|
96
|
+
"params"=>{
|
97
|
+
"isBetaUser"=>"false",
|
98
|
+
"isScaredUser"=>"false"
|
99
|
+
}
|
100
|
+
}, {
|
101
|
+
"version": "v2.3.0"
|
102
|
+
},)
|
73
103
|
```
|
74
104
|
|
75
105
|
## Example
|
data/lib/molasses.rb
CHANGED
@@ -1,33 +1,33 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
require
|
4
|
-
require
|
1
|
+
require "logger"
|
2
|
+
require "workers"
|
3
|
+
require "faraday"
|
4
|
+
require "json"
|
5
|
+
require "zlib"
|
6
|
+
|
5
7
|
module Molasses
|
6
8
|
class Client
|
7
|
-
def initialize(api_key,
|
9
|
+
def initialize(api_key, opts = {})
|
8
10
|
@api_key = api_key
|
9
|
-
@send_events =
|
10
|
-
|
11
|
-
|
12
|
-
else
|
13
|
-
@base_url = 'https://sdk.molasses.app/v1'
|
14
|
-
end
|
11
|
+
@send_events = opts[:auto_send_events] || false
|
12
|
+
@base_url = opts[:base_url] || "https://sdk.molasses.app/v1"
|
13
|
+
@logger = opts[:logger] || get_default_logger
|
15
14
|
@conn = Faraday.new(
|
16
15
|
url: @base_url,
|
17
16
|
headers: {
|
18
|
-
|
19
|
-
|
20
|
-
}
|
17
|
+
"Content-Type" => "application/json",
|
18
|
+
"Authorization" => "Bearer #{@api_key}",
|
19
|
+
},
|
21
20
|
)
|
22
21
|
@feature_cache = {}
|
23
22
|
@initialized = {}
|
23
|
+
|
24
24
|
fetch_features
|
25
25
|
@timer = Workers::PeriodicTimer.new(15) do
|
26
26
|
fetch_features
|
27
27
|
end
|
28
28
|
end
|
29
29
|
|
30
|
-
def is_active(key, user={})
|
30
|
+
def is_active(key, user = {})
|
31
31
|
unless @initialized
|
32
32
|
return false
|
33
33
|
end
|
@@ -37,16 +37,17 @@ module Molasses
|
|
37
37
|
result = user_active(feature, user)
|
38
38
|
if @send_events && user && user.include?("id")
|
39
39
|
send_event({
|
40
|
-
"event"=> "experiment_started",
|
41
|
-
"tags"=> user["params"],
|
42
|
-
"userId"=> user["id"],
|
43
|
-
"featureId"=> feature["id"],
|
44
|
-
"featureName"=> key,
|
45
|
-
"testType"=> result ? "experiment" : "control"
|
40
|
+
"event" => "experiment_started",
|
41
|
+
"tags" => user["params"],
|
42
|
+
"userId" => user["id"],
|
43
|
+
"featureId" => feature["id"],
|
44
|
+
"featureName" => key,
|
45
|
+
"testType" => result ? "experiment" : "control",
|
46
46
|
})
|
47
47
|
end
|
48
48
|
return result
|
49
49
|
else
|
50
|
+
@logger.info "Warning - feature flag #{key} not set in environment"
|
50
51
|
return false
|
51
52
|
end
|
52
53
|
end
|
@@ -55,21 +56,57 @@ module Molasses
|
|
55
56
|
@timer.cancel
|
56
57
|
end
|
57
58
|
|
58
|
-
def
|
59
|
-
if !@initialized ||
|
60
|
-
|
59
|
+
def experiment_started(key, user = nil, additional_details = {})
|
60
|
+
if !@initialized || user == nil || !user.include?("id")
|
61
|
+
return false
|
62
|
+
end
|
63
|
+
unless @feature_cache.include?(key)
|
64
|
+
@logger.info "Warning - feature flag #{key} not set in environment"
|
65
|
+
return false
|
61
66
|
end
|
62
67
|
feature = @feature_cache[key]
|
63
68
|
result = is_active(feature, user)
|
64
69
|
send_event({
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
70
|
+
"event" => "experiment_started",
|
71
|
+
"tags" => user.include?("params") ? user["params"].merge(additional_details) : additional_details,
|
72
|
+
"userId" => user["id"],
|
73
|
+
"featureId" => feature["id"],
|
74
|
+
"featureName" => key,
|
75
|
+
"testType" => result ? "experiment" : "control",
|
76
|
+
})
|
77
|
+
end
|
78
|
+
|
79
|
+
def experiment_success(key, user = nil, additional_details = {})
|
80
|
+
if !@initialized || user == nil || !user.include?("id")
|
81
|
+
return false
|
82
|
+
end
|
83
|
+
unless @feature_cache.include?(key)
|
84
|
+
@logger.info "Warning - feature flag #{key} not set in environment"
|
85
|
+
return false
|
86
|
+
end
|
87
|
+
feature = @feature_cache[key]
|
88
|
+
result = is_active(feature, user)
|
89
|
+
send_event({
|
90
|
+
"event" => "experiment_success",
|
91
|
+
"tags" => user.include?("params") ? user["params"].merge(additional_details) : additional_details,
|
92
|
+
"userId" => user["id"],
|
93
|
+
"featureId" => feature["id"],
|
94
|
+
"featureName" => key,
|
95
|
+
"testType" => result ? "experiment" : "control",
|
96
|
+
})
|
97
|
+
end
|
98
|
+
|
99
|
+
def track(key, user = nil, additional_details = {})
|
100
|
+
if user == nil || !user.include?("id")
|
101
|
+
return false
|
102
|
+
end
|
103
|
+
send_event({
|
104
|
+
"event" => key,
|
105
|
+
"tags" => user.include?("params") ? user["params"].merge(additional_details) : additional_details,
|
106
|
+
"userId" => user["id"],
|
71
107
|
})
|
72
108
|
end
|
109
|
+
|
73
110
|
private
|
74
111
|
|
75
112
|
def user_active(feature, user)
|
@@ -78,13 +115,13 @@ module Molasses
|
|
78
115
|
end
|
79
116
|
|
80
117
|
unless user && user.include?("id")
|
81
|
-
|
118
|
+
return true
|
82
119
|
end
|
83
120
|
|
84
121
|
segment_map = {}
|
85
122
|
for feature_segment in feature["segments"]
|
86
|
-
|
87
|
-
|
123
|
+
segment_type = feature_segment["segmentType"]
|
124
|
+
segment_map[segment_type] = feature_segment
|
88
125
|
end
|
89
126
|
|
90
127
|
if segment_map.include?("alwaysControl") and in_segment(user, segment_map["alwaysControl"])
|
@@ -97,7 +134,7 @@ module Molasses
|
|
97
134
|
return user_percentage(user["id"], segment_map["everyoneElse"]["percentage"])
|
98
135
|
end
|
99
136
|
|
100
|
-
def user_percentage(id="", percentage = 0)
|
137
|
+
def user_percentage(id = "", percentage = 0)
|
101
138
|
if percentage == 100
|
102
139
|
return true
|
103
140
|
end
|
@@ -114,7 +151,7 @@ module Molasses
|
|
114
151
|
def in_segment(user, segment_map)
|
115
152
|
user_constraints = segment_map["userConstraints"]
|
116
153
|
constraints_length = user_constraints.length
|
117
|
-
constraints_to_be_met = segment_map["constraint"] ==
|
154
|
+
constraints_to_be_met = segment_map["constraint"] == "any" ? 1 : constraints_length
|
118
155
|
constraints_met = 0
|
119
156
|
|
120
157
|
for constraint in user_constraints
|
@@ -137,51 +174,107 @@ module Molasses
|
|
137
174
|
return constraints_met >= constraints_to_be_met
|
138
175
|
end
|
139
176
|
|
177
|
+
def parse_number(user_value)
|
178
|
+
case user_value
|
179
|
+
when Numeric
|
180
|
+
return user_value
|
181
|
+
when TrueClass
|
182
|
+
return 1
|
183
|
+
when FalseClass
|
184
|
+
return 0
|
185
|
+
when String
|
186
|
+
return user_value.to_f
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
def parse_bool(user_value)
|
191
|
+
case user_value
|
192
|
+
when Numeric
|
193
|
+
return user_value == 1
|
194
|
+
when TrueClass, FalseClass
|
195
|
+
return user_value
|
196
|
+
when String
|
197
|
+
return user_value == "true"
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
140
201
|
def meets_constraint(user_value, param_exists, constraint)
|
141
202
|
operator = constraint["operator"]
|
142
203
|
if param_exists == false
|
143
204
|
return false
|
144
205
|
end
|
145
206
|
|
207
|
+
constraint_value = constraint["values"]
|
208
|
+
case constraint["userParamType"]
|
209
|
+
when "number"
|
210
|
+
user_value = parse_number(user_value)
|
211
|
+
constraint_value = parse_number(constraint_value)
|
212
|
+
when "boolean"
|
213
|
+
user_value = parse_bool(user_value)
|
214
|
+
constraint_value = parse_bool(constraint_value)
|
215
|
+
else
|
216
|
+
user_value = user_value.to_s
|
217
|
+
end
|
218
|
+
|
146
219
|
case operator
|
147
220
|
when "in"
|
148
|
-
list_values =
|
221
|
+
list_values = constraint_value.split(",")
|
149
222
|
return list_values.include? user_value
|
150
223
|
when "nin"
|
151
|
-
list_values =
|
224
|
+
list_values = constraint_value.split(",")
|
152
225
|
return !list_values.include?(user_value)
|
226
|
+
when "lt"
|
227
|
+
return user_value < constraint_value
|
228
|
+
when "lte"
|
229
|
+
return user_value <= constraint_value
|
230
|
+
when "gt"
|
231
|
+
return user_value > constraint_value
|
232
|
+
when "gte"
|
233
|
+
return user_value >= constraint_value
|
153
234
|
when "equals"
|
154
|
-
return user_value ==
|
235
|
+
return user_value == constraint_value
|
155
236
|
when "doesNotEqual"
|
156
|
-
return user_value !=
|
237
|
+
return user_value != constraint_value
|
157
238
|
when "contains"
|
158
|
-
return
|
239
|
+
return constraint_value.include?(user_value)
|
159
240
|
when "doesNotContain"
|
160
|
-
return !
|
241
|
+
return !constraint_value.include?(user_value)
|
161
242
|
else
|
162
243
|
return false
|
163
244
|
end
|
164
245
|
end
|
165
246
|
|
166
247
|
def send_event(event_options)
|
167
|
-
@conn.post(
|
248
|
+
@conn.post("analytics", event_options.to_json)
|
168
249
|
end
|
169
250
|
|
170
251
|
def fetch_features()
|
171
|
-
response = @conn.get(
|
252
|
+
response = @conn.get("features")
|
172
253
|
if response.status == 200
|
173
254
|
data = JSON.parse(response.body)
|
174
255
|
if data.include?("data") and data["data"].include?("features")
|
175
256
|
features = data["data"]["features"]
|
176
|
-
for feature in features
|
257
|
+
for feature in features
|
177
258
|
@feature_cache[feature["key"]] = feature
|
178
259
|
end
|
260
|
+
unless @initialized
|
261
|
+
@logger.info "Molasses - connected and initialized"
|
262
|
+
end
|
179
263
|
@initialized = true
|
180
264
|
end
|
181
265
|
else
|
182
|
-
|
266
|
+
@logger.info "Molasses - #{response.status} - #{response.body}"
|
183
267
|
end
|
184
268
|
end
|
185
269
|
|
270
|
+
def get_default_logger
|
271
|
+
if defined?(Rails) && Rails.respond_to?(:logger)
|
272
|
+
Rails.logger
|
273
|
+
else
|
274
|
+
log = ::Logger.new(STDOUT)
|
275
|
+
log.level = ::Logger::WARN
|
276
|
+
log
|
277
|
+
end
|
278
|
+
end
|
186
279
|
end
|
187
|
-
end
|
280
|
+
end
|
data/spec/molasses_spec.rb
CHANGED
@@ -1,225 +1,299 @@
|
|
1
|
-
require
|
2
|
-
|
3
|
-
require
|
1
|
+
require "simplecov"
|
2
|
+
SimpleCov.start
|
3
|
+
require "faraday"
|
4
|
+
require "molasses"
|
5
|
+
require "webmock/rspec"
|
4
6
|
responseA = {
|
5
7
|
"data": {
|
6
|
-
|
8
|
+
"features": [
|
9
|
+
{
|
10
|
+
"active": true,
|
11
|
+
"description": "foo",
|
12
|
+
"key": "FOO_TEST",
|
13
|
+
"segments": [
|
7
14
|
{
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
"userParam": "isScaredUser",
|
19
|
-
"operator": "in",
|
20
|
-
"values": "true,maybe",
|
21
|
-
},
|
22
|
-
],
|
23
|
-
},
|
24
|
-
{
|
25
|
-
"constraint": "all",
|
26
|
-
"percentage": 100,
|
27
|
-
"segmentType": "alwaysExperiment",
|
28
|
-
"userConstraints": [
|
29
|
-
{
|
30
|
-
"userParam": "isBetaUser",
|
31
|
-
"operator": "equals",
|
32
|
-
"values": "true",
|
33
|
-
},
|
34
|
-
],
|
35
|
-
},
|
36
|
-
{
|
37
|
-
"constraint": "all",
|
38
|
-
"percentage": 100,
|
39
|
-
"segmentType": "everyoneElse",
|
40
|
-
"userConstraints": [],
|
41
|
-
},
|
42
|
-
],
|
15
|
+
"constraint": "all",
|
16
|
+
"percentage": 100,
|
17
|
+
"segmentType": "alwaysControl",
|
18
|
+
"userConstraints": [
|
19
|
+
{
|
20
|
+
"userParam": "isScaredUser",
|
21
|
+
"operator": "in",
|
22
|
+
"values": "true,maybe",
|
23
|
+
},
|
24
|
+
],
|
43
25
|
},
|
44
|
-
|
45
|
-
|
26
|
+
{
|
27
|
+
"constraint": "all",
|
28
|
+
"percentage": 100,
|
29
|
+
"segmentType": "alwaysExperiment",
|
30
|
+
"userConstraints": [
|
31
|
+
{
|
32
|
+
"userParam": "isBetaUser",
|
33
|
+
"operator": "equals",
|
34
|
+
"values": "true",
|
35
|
+
},
|
36
|
+
],
|
37
|
+
},
|
38
|
+
{
|
39
|
+
"constraint": "all",
|
40
|
+
"percentage": 100,
|
41
|
+
"segmentType": "everyoneElse",
|
42
|
+
"userConstraints": [],
|
43
|
+
},
|
44
|
+
],
|
45
|
+
},
|
46
|
+
],
|
47
|
+
},
|
48
|
+
}
|
46
49
|
responseB = {
|
47
50
|
"data": {
|
48
|
-
|
51
|
+
"features": [
|
52
|
+
{
|
53
|
+
"id": "1",
|
54
|
+
"active": true,
|
55
|
+
"description": "foo",
|
56
|
+
"key": "FOO_TEST",
|
57
|
+
"segments": [
|
49
58
|
{
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
{
|
61
|
-
"userParam": "isScaredUser",
|
62
|
-
"operator": "nin",
|
63
|
-
"values": "false,maybe",
|
64
|
-
},
|
65
|
-
],
|
66
|
-
},
|
67
|
-
{
|
68
|
-
"constraint": "all",
|
69
|
-
"percentage": 100,
|
70
|
-
"segmentType": "alwaysExperiment",
|
71
|
-
"userConstraints": [
|
72
|
-
{
|
73
|
-
"userParam": "isBetaUser",
|
74
|
-
"operator": "doesNotEqual",
|
75
|
-
"values": "false",
|
76
|
-
},
|
77
|
-
],
|
78
|
-
},
|
79
|
-
{
|
80
|
-
"constraint": "all",
|
81
|
-
"percentage": 100,
|
82
|
-
"segmentType": "everyoneElse",
|
83
|
-
"userConstraints": [],
|
84
|
-
},
|
85
|
-
],
|
59
|
+
"constraint": "all",
|
60
|
+
"percentage": 100,
|
61
|
+
"segmentType": "alwaysControl",
|
62
|
+
"userConstraints": [
|
63
|
+
{
|
64
|
+
"userParam": "isScaredUser",
|
65
|
+
"operator": "nin",
|
66
|
+
"values": "false,maybe",
|
67
|
+
},
|
68
|
+
],
|
86
69
|
},
|
87
|
-
|
70
|
+
{
|
71
|
+
"constraint": "all",
|
72
|
+
"percentage": 100,
|
73
|
+
"segmentType": "alwaysExperiment",
|
74
|
+
"userConstraints": [
|
75
|
+
{
|
76
|
+
"userParam": "isBetaUser",
|
77
|
+
"operator": "doesNotEqual",
|
78
|
+
"values": "false",
|
79
|
+
},
|
80
|
+
],
|
81
|
+
},
|
82
|
+
{
|
83
|
+
"constraint": "all",
|
84
|
+
"percentage": 100,
|
85
|
+
"segmentType": "everyoneElse",
|
86
|
+
"userConstraints": [],
|
87
|
+
},
|
88
|
+
],
|
89
|
+
},
|
90
|
+
],
|
88
91
|
},
|
89
92
|
}
|
90
93
|
|
91
94
|
responseC = {
|
92
95
|
"data": {
|
93
|
-
|
96
|
+
"features": [
|
97
|
+
{
|
98
|
+
"id": "2",
|
99
|
+
"active": true,
|
100
|
+
"description": "bar",
|
101
|
+
"key": "NUMBERS_BOOLS",
|
102
|
+
"segments": [
|
94
103
|
{
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
104
|
+
"percentage": 100,
|
105
|
+
"segmentType": "alwaysControl",
|
106
|
+
"constraint": "all",
|
107
|
+
"userConstraints": [
|
108
|
+
{
|
109
|
+
"userParam": "lt",
|
110
|
+
"userParamType": "number",
|
111
|
+
"operator": "lt",
|
112
|
+
"values": 12,
|
113
|
+
},
|
114
|
+
{
|
115
|
+
"userParam": "lte",
|
116
|
+
"userParamType": "number",
|
117
|
+
"operator": "lte",
|
118
|
+
"values": 12,
|
119
|
+
},
|
120
|
+
{
|
121
|
+
"userParam": "gt",
|
122
|
+
"userParamType": "number",
|
123
|
+
"operator": "gt",
|
124
|
+
"values": 12,
|
125
|
+
},
|
126
|
+
{
|
127
|
+
"userParam": "gte",
|
128
|
+
"userParamType": "number",
|
129
|
+
"operator": "gte",
|
130
|
+
"values": 12,
|
131
|
+
},
|
132
|
+
{
|
133
|
+
"userParam": "equals",
|
134
|
+
"userParamType": "number",
|
135
|
+
"operator": "equals",
|
136
|
+
"values": 12,
|
137
|
+
},
|
138
|
+
{
|
139
|
+
"userParam": "doesNotEqual",
|
140
|
+
"userParamType": "number",
|
141
|
+
"operator": "doesNotEqual",
|
142
|
+
"values": 12,
|
143
|
+
},
|
144
|
+
{
|
145
|
+
"userParam": "equalsBool",
|
146
|
+
"userParamType": "boolean",
|
147
|
+
"operator": "equals",
|
148
|
+
"values": true,
|
149
|
+
},
|
150
|
+
{
|
151
|
+
"userParam": "doesNotEqualBool",
|
152
|
+
"userParamType": "boolean",
|
153
|
+
"operator": "doesNotEqual",
|
154
|
+
"values": false,
|
155
|
+
},
|
156
|
+
|
157
|
+
],
|
158
|
+
|
146
159
|
},
|
147
|
-
|
160
|
+
{
|
161
|
+
"constraint": "all",
|
162
|
+
"percentage": 50,
|
163
|
+
"segmentType": "everyoneElse",
|
164
|
+
"userConstraints": [],
|
165
|
+
},
|
166
|
+
],
|
167
|
+
},
|
168
|
+
{
|
169
|
+
"id": "1",
|
170
|
+
"active": true,
|
171
|
+
"description": "foo",
|
172
|
+
"key": "FOO_TEST",
|
173
|
+
"segments": [
|
174
|
+
{
|
175
|
+
"percentage": 100,
|
176
|
+
"segmentType": "alwaysControl",
|
177
|
+
"constraint": "all",
|
178
|
+
"userConstraints": [
|
179
|
+
{
|
180
|
+
"userParam": "isScaredUser",
|
181
|
+
"operator": "contains",
|
182
|
+
"values": "scared",
|
183
|
+
},
|
184
|
+
{
|
185
|
+
"userParam": "isDefinitelyScaredUser",
|
186
|
+
"operator": "contains",
|
187
|
+
"values": "scared",
|
188
|
+
},
|
189
|
+
{
|
190
|
+
"userParam": "isMostDefinitelyScaredUser",
|
191
|
+
"operator": "contains",
|
192
|
+
"values": "scared",
|
193
|
+
},
|
194
|
+
],
|
195
|
+
},
|
196
|
+
{
|
197
|
+
"percentage": 100,
|
198
|
+
"segmentType": "alwaysExperiment",
|
199
|
+
"constraint": "any",
|
200
|
+
"userConstraints": [
|
201
|
+
{
|
202
|
+
"userParam": "isBetaUser",
|
203
|
+
"operator": "doesNotContain",
|
204
|
+
"values": "fal",
|
205
|
+
},
|
206
|
+
{
|
207
|
+
"userParam": "isDefinitelyBetaUser",
|
208
|
+
"operator": "doesNotContain",
|
209
|
+
"values": "fal",
|
210
|
+
},
|
211
|
+
],
|
212
|
+
},
|
213
|
+
{
|
214
|
+
"constraint": "all",
|
215
|
+
"percentage": 100,
|
216
|
+
"segmentType": "everyoneElse",
|
217
|
+
"userConstraints": [],
|
218
|
+
},
|
219
|
+
],
|
220
|
+
},
|
221
|
+
],
|
148
222
|
},
|
149
223
|
}
|
150
224
|
|
151
225
|
responseD = {
|
152
226
|
"data": {
|
153
|
-
|
227
|
+
"features": [
|
228
|
+
{
|
229
|
+
"id": "1",
|
230
|
+
"active": true,
|
231
|
+
"description": "foo",
|
232
|
+
"key": "FOO_TEST",
|
233
|
+
"segments": [],
|
234
|
+
},
|
235
|
+
{
|
236
|
+
"id": "2",
|
237
|
+
"active": false,
|
238
|
+
"description": "foo",
|
239
|
+
"key": "FOO_FALSE_TEST",
|
240
|
+
"segments": [],
|
241
|
+
},
|
242
|
+
{
|
243
|
+
"id": "3",
|
244
|
+
"active": true,
|
245
|
+
"description": "foo",
|
246
|
+
"key": "FOO_50_PERCENT_TEST",
|
247
|
+
"segments": [
|
154
248
|
{
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
"segments": [],
|
249
|
+
"constraint": "all",
|
250
|
+
"segmentType": "everyoneElse",
|
251
|
+
"percentage": 50,
|
252
|
+
"userConstraints": [],
|
160
253
|
},
|
254
|
+
],
|
255
|
+
},
|
256
|
+
{
|
257
|
+
"id": "4",
|
258
|
+
"active": true,
|
259
|
+
"description": "foo",
|
260
|
+
"key": "FOO_0_PERCENT_TEST",
|
261
|
+
"segments": [
|
161
262
|
{
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
"segments": [],
|
263
|
+
"constraint": "all",
|
264
|
+
"segmentType": "everyoneElse",
|
265
|
+
"percentage": 0,
|
266
|
+
"userConstraints": [],
|
167
267
|
},
|
268
|
+
],
|
269
|
+
},
|
270
|
+
{
|
271
|
+
"id": "5",
|
272
|
+
"active": true,
|
273
|
+
"description": "foo",
|
274
|
+
"key": "FOO_ID_TEST",
|
275
|
+
"segments": [
|
168
276
|
{
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
},
|
180
|
-
],
|
277
|
+
"constraint": "all",
|
278
|
+
"percentage": 100,
|
279
|
+
"segmentType": "alwaysControl",
|
280
|
+
"userConstraints": [
|
281
|
+
{
|
282
|
+
"userParam": "id",
|
283
|
+
"operator": "equals",
|
284
|
+
"values": "123",
|
285
|
+
},
|
286
|
+
],
|
181
287
|
},
|
182
288
|
{
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
"segments": [
|
188
|
-
{
|
189
|
-
"constraint": "all",
|
190
|
-
"segmentType": "everyoneElse",
|
191
|
-
"percentage": 0,
|
192
|
-
"userConstraints": [],
|
193
|
-
},
|
194
|
-
],
|
289
|
+
"constraint": "all",
|
290
|
+
"segmentType": "everyoneElse",
|
291
|
+
"percentage": 100,
|
292
|
+
"userConstraints": [],
|
195
293
|
},
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
"description": "foo",
|
200
|
-
"key": "FOO_ID_TEST",
|
201
|
-
"segments": [
|
202
|
-
{
|
203
|
-
"constraint": "all",
|
204
|
-
"percentage": 100,
|
205
|
-
"segmentType": "alwaysControl",
|
206
|
-
"userConstraints": [
|
207
|
-
{
|
208
|
-
"userParam": "id",
|
209
|
-
"operator": "equals",
|
210
|
-
"values": "123",
|
211
|
-
},
|
212
|
-
],
|
213
|
-
},
|
214
|
-
{
|
215
|
-
"constraint": "all",
|
216
|
-
"segmentType": "everyoneElse",
|
217
|
-
"percentage": 100,
|
218
|
-
"userConstraints": [],
|
219
|
-
},
|
220
|
-
],
|
221
|
-
},
|
222
|
-
],
|
294
|
+
],
|
295
|
+
},
|
296
|
+
],
|
223
297
|
},
|
224
298
|
}
|
225
299
|
|
@@ -227,53 +301,131 @@ $client = nil
|
|
227
301
|
$conn = nil
|
228
302
|
$stubs = nil
|
229
303
|
RSpec.describe Molasses::Client do
|
230
|
-
let(:stubs)
|
231
|
-
let(:conn)
|
232
|
-
let(:client) { Molasses::Client.new("test_api_key"
|
304
|
+
let(:stubs) { Faraday::Adapter::Test::Stubs.new }
|
305
|
+
let(:conn) { Faraday.new { |b| b.adapter(:test, stubs) } }
|
306
|
+
let(:client) { Molasses::Client.new("test_api_key") }
|
233
307
|
it "can do basic molasses" do
|
234
|
-
stub_request(:get,
|
235
|
-
to_return(body:responseA.to_json, headers:
|
236
|
-
|
237
|
-
|
308
|
+
stub_request(:get, "https://sdk.molasses.app/v1/features").
|
309
|
+
to_return(body: responseA.to_json, headers: {
|
310
|
+
"Content-Type" => "application/json",
|
311
|
+
})
|
238
312
|
expect(client.is_active("FOO_TEST")).to be_truthy
|
239
|
-
expect(client.is_active("FOO_TEST", {"foo"=>"foo"})).to be_truthy
|
313
|
+
expect(client.is_active("FOO_TEST", { "foo" => "foo" })).to be_truthy
|
240
314
|
expect(client.is_active("NOT_CHECKOUT")).to be_falsy
|
241
|
-
expect(client.is_active("FOO_TEST", {"id"=>"foo", "params"=>{}})).to be_truthy
|
242
|
-
expect(client.is_active("FOO_TEST", {"id"=>"food", "params"=>{
|
243
|
-
|
244
|
-
|
245
|
-
|
315
|
+
expect(client.is_active("FOO_TEST", { "id" => "foo", "params" => {} })).to be_truthy
|
316
|
+
expect(client.is_active("FOO_TEST", { "id" => "food", "params" => {
|
317
|
+
"isScaredUser" => "true",
|
318
|
+
} })).to be_falsy
|
319
|
+
expect(client.is_active("FOO_TEST", { "id" => "foodie", "params" => {
|
320
|
+
"isBetaUser" => "true",
|
321
|
+
} })).to be_truthy
|
246
322
|
end
|
247
323
|
it "can do advanced molasses" do
|
248
|
-
stub_request(:get,
|
249
|
-
to_return(body:responseB.to_json, headers:
|
250
|
-
|
251
|
-
|
324
|
+
stub_request(:get, "https://sdk.molasses.app/v1/features").
|
325
|
+
to_return(body: responseB.to_json, headers: {
|
326
|
+
"Content-Type" => "application/json",
|
327
|
+
})
|
252
328
|
|
253
329
|
expect(client.is_active("FOO_TEST")).to be_truthy
|
254
|
-
expect(client.is_active("FOO_TEST", {"foo"=>"foo"})).to be_truthy
|
330
|
+
expect(client.is_active("FOO_TEST", { "foo" => "foo" })).to be_truthy
|
255
331
|
expect(client.is_active("NOT_CHECKOUT")).to be_falsy
|
256
|
-
expect(client.is_active("FOO_TEST", {"id"=>"foo", "params"=>{
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
332
|
+
expect(client.is_active("FOO_TEST", { "id" => "foo", "params" => {
|
333
|
+
"isBetaUser" => "false", "isScaredUser" => "false",
|
334
|
+
} })).to be_truthy
|
335
|
+
expect(client.is_active("FOO_TEST", { "id" => "food", "params" => {
|
336
|
+
"isScaredUser" => "true",
|
337
|
+
} })).to be_falsy
|
338
|
+
expect(client.is_active("FOO_TEST", { "id" => "foodie", "params" => {
|
339
|
+
"isBetaUser" => "true",
|
340
|
+
} })).to be_truthy
|
262
341
|
end
|
263
342
|
it "can do even more advanced molasses" do
|
264
|
-
stub_request(:get,
|
265
|
-
to_return(body:responseC.to_json, headers:
|
266
|
-
|
267
|
-
|
343
|
+
stub_request(:get, "https://sdk.molasses.app/v1/features").
|
344
|
+
to_return(body: responseC.to_json, headers: {
|
345
|
+
"Content-Type" => "application/json",
|
346
|
+
})
|
347
|
+
stub_request(:post, "https://sdk.molasses.app/v1/analytics").
|
348
|
+
to_return(status: 200, body: "", headers: {})
|
349
|
+
expect(client.is_active("FOO_TEST", { "id" => "foo", "params" => {
|
350
|
+
"isScaredUser" => "scared",
|
351
|
+
"isDefinitelyScaredUser" => "scared",
|
352
|
+
"isMostDefinitelyScaredUser" => "scared",
|
353
|
+
} })).to be_falsy
|
354
|
+
expect(client.is_active("FOO_TEST", { "id" => "food", "params" => {
|
355
|
+
"isDefinitelyBetaUser" => "true", "isBetaUser" => "true",
|
356
|
+
} })).to be_truthy
|
357
|
+
expect(client.is_active("FOO_TEST", { "id" => "foodie", "params" => {
|
358
|
+
"isBetaUser" => "true",
|
359
|
+
} })).to be_truthy
|
360
|
+
|
361
|
+
expect(client.is_active("NUMBERS_BOOLS", {
|
362
|
+
"id" => "12341",
|
363
|
+
"params" => {
|
364
|
+
"lt" => true,
|
365
|
+
"lte" => "12",
|
366
|
+
"gt" => 14,
|
367
|
+
"gte" => 12,
|
368
|
+
"equals" => 12,
|
369
|
+
"doesNotEqual" => false,
|
370
|
+
"equalsBool" => true,
|
371
|
+
"doesNotEqualBool" => "true",
|
372
|
+
},
|
373
|
+
})).to be_falsy
|
268
374
|
|
269
|
-
expect(client.is_active("
|
270
|
-
|
271
|
-
|
272
|
-
"
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
375
|
+
expect(client.is_active("NUMBERS_BOOLS", {
|
376
|
+
"id" => "123444", # valid crc32 percentage
|
377
|
+
"params" => {
|
378
|
+
"lt" => true,
|
379
|
+
"lte" => "12",
|
380
|
+
"gt" => 14,
|
381
|
+
"gte" => 12,
|
382
|
+
"equals" => 12,
|
383
|
+
"doesNotEqual" => false,
|
384
|
+
"equalsBool" => 0,
|
385
|
+
"doesNotEqualBool" => "true",
|
386
|
+
},
|
387
|
+
})).to be_truthy
|
388
|
+
expect(client.experiment_started("NUMBERS_BOOLS", {
|
389
|
+
"id" => "123444", # valid crc32 percentage
|
390
|
+
"params" => {
|
391
|
+
"lt" => true,
|
392
|
+
"lte" => "12",
|
393
|
+
"gt" => 14,
|
394
|
+
"gte" => 12,
|
395
|
+
"equals" => 12,
|
396
|
+
"doesNotEqual" => false,
|
397
|
+
"equalsBool" => 0,
|
398
|
+
"doesNotEqualBool" => "true",
|
399
|
+
},
|
400
|
+
}))
|
401
|
+
expect(client.experiment_started("Clicked button", {
|
402
|
+
"id" => "123444", # v
|
403
|
+
}))
|
404
|
+
expect(client.track("Clicked button", {
|
405
|
+
"id" => "123444", # valid crc32 percentage
|
406
|
+
"params" => {
|
407
|
+
"lt" => true,
|
408
|
+
"lte" => "12",
|
409
|
+
"gt" => 14,
|
410
|
+
"gte" => 12,
|
411
|
+
"equals" => 12,
|
412
|
+
"doesNotEqual" => false,
|
413
|
+
"equalsBool" => 0,
|
414
|
+
"doesNotEqualBool" => "true",
|
415
|
+
},
|
416
|
+
}))
|
417
|
+
expect(client.experiment_success("NUMBERS_BOOLS", {
|
418
|
+
"id" => "123444", # valid crc32 percentage
|
419
|
+
"params" => {
|
420
|
+
"lt" => true,
|
421
|
+
"lte" => "12",
|
422
|
+
"gt" => 14,
|
423
|
+
"gte" => 12,
|
424
|
+
"equals" => 12,
|
425
|
+
"doesNotEqual" => false,
|
426
|
+
"equalsBool" => 0,
|
427
|
+
"doesNotEqualBool" => "true",
|
428
|
+
},
|
429
|
+
}))
|
278
430
|
end
|
279
|
-
end
|
431
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: molasses
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- James Hrisho
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-02-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: faraday
|