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.
Files changed (5) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +37 -7
  3. data/lib/molasses.rb +139 -46
  4. data/spec/molasses_spec.rb +384 -232
  5. metadata +2 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6bee138ba8251caae8600f56a1ed7516ed6e1d9917b6b65f8f6073b778877b95
4
- data.tar.gz: f9371eb4ae9c3af3da69b35e99c893debbe1ac0d607aaf9962908423b7269fb3
3
+ metadata.gz: 5c3763a8c5f6b0813ea2832633f4482c1447d0d224b220000dc6ae870d8ebea6
4
+ data.tar.gz: 163f9337d2b2a38824effaf9379a7db8244bc702ecf7408581067fa4875e7299
5
5
  SHA512:
6
- metadata.gz: 03c03e1c64c638a1cd0cdf5ef621d05db9c0ad38625f42c39c044cc56f0ffd331f0da2fd90593fc830f2e87eb91143075ef2344570c4fdfcc09a369533d43927
7
- data.tar.gz: 966c6ac094b09cdcc44e60ca3f8127407bed2976764ae63901db31b2ffb473f91ee63b3952cf553304cab6997ee79aac41c33164e6ce5742792ba5e5d136110a
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 not to track analytics events (experiment started, experiment success) you can turn them off by setting the `send_events` field to `false`
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", false)
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 whether an experiment was successful you can call `experiment_success`. experiment_success takes the feature's name, any additional parameters for the event and the user.
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.experiment_success("GOOGLE_SSO",{
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 'workers'
2
- require 'faraday'
3
- require 'json'
4
- require 'zlib'
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, send_events=true, base_url="")
9
+ def initialize(api_key, opts = {})
8
10
  @api_key = api_key
9
- @send_events = send_events
10
- if base_url != ""
11
- @base_url = base_url
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
- 'Content-Type' => 'application/json',
19
- 'Authorization' => "Bearer #{@api_key}"
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 experiment_success(key, additional_details={}, user=nil)
59
- if !@initialized || !@send_events || user == nil || user.include?("id")
60
- return false
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
- "event"=> "experiment_success",
66
- "tags"=> user["params"].merge(additional_details),
67
- "userId"=> user["id"],
68
- "featureId"=> feature["id"],
69
- "featureName"=> key,
70
- "testType"=> result ? "experiment" : "control"
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
- return true
118
+ return true
82
119
  end
83
120
 
84
121
  segment_map = {}
85
122
  for feature_segment in feature["segments"]
86
- segment_type = feature_segment["segmentType"]
87
- segment_map[segment_type] = feature_segment
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"] == 'any' ? 1 : constraints_length
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 = constraint["values"].split(',')
221
+ list_values = constraint_value.split(",")
149
222
  return list_values.include? user_value
150
223
  when "nin"
151
- list_values = constraint["values"].split(',')
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 == constraint["values"]
235
+ return user_value == constraint_value
155
236
  when "doesNotEqual"
156
- return user_value != constraint["values"]
237
+ return user_value != constraint_value
157
238
  when "contains"
158
- return constraint["values"].include?(user_value)
239
+ return constraint_value.include?(user_value)
159
240
  when "doesNotContain"
160
- return !constraint["values"].include?(user_value)
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('analytics', event_options.to_json)
248
+ @conn.post("analytics", event_options.to_json)
168
249
  end
169
250
 
170
251
  def fetch_features()
171
- response = @conn.get('features')
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 do
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
- puts "Molasses - #{response.status} - #{response.body}"
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
@@ -1,225 +1,299 @@
1
- require 'faraday'
2
- require 'molasses'
3
- require 'webmock/rspec'
1
+ require "simplecov"
2
+ SimpleCov.start
3
+ require "faraday"
4
+ require "molasses"
5
+ require "webmock/rspec"
4
6
  responseA = {
5
7
  "data": {
6
- "features": [
8
+ "features": [
9
+ {
10
+ "active": true,
11
+ "description": "foo",
12
+ "key": "FOO_TEST",
13
+ "segments": [
7
14
  {
8
- "active": true,
9
- "description": "foo",
10
- "key": "FOO_TEST",
11
- "segments": [
12
- {
13
- "constraint": "all",
14
- "percentage": 100,
15
- "segmentType": "alwaysControl",
16
- "userConstraints": [
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
- "features": [
51
+ "features": [
52
+ {
53
+ "id": "1",
54
+ "active": true,
55
+ "description": "foo",
56
+ "key": "FOO_TEST",
57
+ "segments": [
49
58
  {
50
- "id": "1",
51
- "active": true,
52
- "description": "foo",
53
- "key": "FOO_TEST",
54
- "segments": [
55
- {
56
- "constraint": "all",
57
- "percentage": 100,
58
- "segmentType": "alwaysControl",
59
- "userConstraints": [
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
- "features": [
96
+ "features": [
97
+ {
98
+ "id": "2",
99
+ "active": true,
100
+ "description": "bar",
101
+ "key": "NUMBERS_BOOLS",
102
+ "segments": [
94
103
  {
95
- "id": "1",
96
- "active": true,
97
- "description": "foo",
98
- "key": "FOO_TEST",
99
- "segments": [
100
- {
101
- "percentage": 100,
102
- "segmentType": "alwaysControl",
103
- "constraint": "all",
104
- "userConstraints": [
105
- {
106
- "userParam": "isScaredUser",
107
- "operator": "contains",
108
- "values": "scared",
109
- },
110
- {
111
- "userParam": "isDefinitelyScaredUser",
112
- "operator": "contains",
113
- "values": "scared",
114
- },
115
- {
116
- "userParam": "isMostDefinitelyScaredUser",
117
- "operator": "contains",
118
- "values": "scared",
119
- },
120
- ],
121
- },
122
- {
123
- "percentage": 100,
124
- "segmentType": "alwaysExperiment",
125
- "constraint": "any",
126
- "userConstraints": [
127
- {
128
- "userParam": "isBetaUser",
129
- "operator": "doesNotContain",
130
- "values": "fal",
131
- },
132
- {
133
- "userParam": "isDefinitelyBetaUser",
134
- "operator": "doesNotContain",
135
- "values": "fal",
136
- },
137
- ],
138
- },
139
- {
140
- "constraint": "all",
141
- "percentage": 100,
142
- "segmentType": "everyoneElse",
143
- "userConstraints": [],
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
- "features": [
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
- "id": "1",
156
- "active": true,
157
- "description": "foo",
158
- "key": "FOO_TEST",
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
- "id": "2",
163
- "active": false,
164
- "description": "foo",
165
- "key": "FOO_FALSE_TEST",
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
- "id": "3",
170
- "active": true,
171
- "description": "foo",
172
- "key": "FOO_50_PERCENT_TEST",
173
- "segments": [
174
- {
175
- "constraint": "all",
176
- "segmentType": "everyoneElse",
177
- "percentage": 50,
178
- "userConstraints": [],
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
- "id": "4",
184
- "active": true,
185
- "description": "foo",
186
- "key": "FOO_0_PERCENT_TEST",
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
- "id": "5",
198
- "active": true,
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) { Faraday::Adapter::Test::Stubs.new }
231
- let(:conn) { Faraday.new { |b| b.adapter(:test, stubs) } }
232
- let(:client) { Molasses::Client.new("test_api_key", false) }
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, 'https://sdk.molasses.app/v1/features').
235
- to_return(body:responseA.to_json, headers: {
236
- 'Content-Type'=>'application/json',
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
- "isScaredUser"=>"true"}})).to be_falsy
244
- expect(client.is_active("FOO_TEST", {"id"=>"foodie", "params"=>{
245
- "isBetaUser"=>"true"}})).to be_truthy
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, 'https://sdk.molasses.app/v1/features').
249
- to_return(body:responseB.to_json, headers: {
250
- 'Content-Type'=>'application/json',
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
- "isBetaUser"=>"false", "isScaredUser"=>"false"}})).to be_truthy
258
- expect(client.is_active("FOO_TEST", {"id"=>"food", "params"=>{
259
- "isScaredUser"=>"true"}})).to be_falsy
260
- expect(client.is_active("FOO_TEST", {"id"=>"foodie", "params"=>{
261
- "isBetaUser"=>"true"}})).to be_truthy
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, 'https://sdk.molasses.app/v1/features').
265
- to_return(body:responseC.to_json, headers: {
266
- 'Content-Type'=>'application/json',
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("FOO_TEST", {"id"=> "foo", "params" => {
270
- "isScaredUser"=> "scared",
271
- "isDefinitelyScaredUser"=> "scared",
272
- "isMostDefinitelyScaredUser"=> "scared",
273
- }})).to be_falsy
274
- expect(client.is_active("FOO_TEST", {"id"=>"food", "params"=>{
275
- "isDefinitelyBetaUser"=>"true", "isBetaUser"=>"true"}})).to be_truthy
276
- expect(client.is_active("FOO_TEST", {"id"=>"foodie", "params"=>{
277
- "isBetaUser"=>"true"}})).to be_truthy
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.2.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-01-13 00:00:00.000000000 Z
11
+ date: 2021-02-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday