molasses 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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