molasses 0.1.1 → 0.4.2

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 +143 -46
  4. data/spec/molasses_spec.rb +541 -232
  5. metadata +17 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d2d9cea9373b20e0129025da3220b21b5b8e5146ed35e6acabc972657d8d1259
4
- data.tar.gz: 78b3cb5ba295d6d1686f943933bc7e6f7cfba12b4145612d92637957e1fd574e
3
+ metadata.gz: ffa4e080550ec76be2ebafd0b43402f30757c3dfb99b04dd513cca321333e3e8
4
+ data.tar.gz: f07d34cadfc75714479ade49dba636b79cf55ee099b8355f2a9160656499be1a
5
5
  SHA512:
6
- metadata.gz: af49bc877c10a7896e680f0b09dbc9364219b37a5a1ebbe80948ff66d5fe825901f19b23bb9af5df27578bd77de28f0c70648e734421d57f265d2539ee40927b
7
- data.tar.gz: e0a8843a3802340adbbee06369eb901de8d802aa44c0d06cffd5f01a8c92f0e39c7558174bfba7eb7adb522dd55919895acee176ddf918157dbff3cce4ca6848
6
+ metadata.gz: e3964e86375dcec32653fc56de635e4e60e2c0beaf5f5d2fd9893d4ca8c17423bb9b0bd90bc23e114dea2b25dd1aad6e4616756a804e6a1d2b277ea158902d85
7
+ data.tar.gz: d4361b370982a5f56b847e8b0823712995b94481829e77c74c586aaa2ddc1d264f3dea12fffadfeffc5cf131b8304bcaef566a8bb2d4ba52dd217c01773e0675
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,34 @@
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
+ require "semantic"
7
+
5
8
  module Molasses
6
9
  class Client
7
- def initialize(api_key, send_events=true, base_url="")
10
+ def initialize(api_key, opts = {})
8
11
  @api_key = api_key
9
- @send_events = send_events
10
- if base_url != ""
11
- @base_url = base_url
12
- else
13
- @base_url = 'https://us-central1-molasses-36bff.cloudfunctions.net'
14
- end
12
+ @send_events = opts[:auto_send_events] || false
13
+ @base_url = opts[:base_url] || "https://sdk.molasses.app/v1"
14
+ @logger = opts[:logger] || get_default_logger
15
15
  @conn = Faraday.new(
16
16
  url: @base_url,
17
17
  headers: {
18
- 'Content-Type' => 'application/json',
19
- 'Authorization' => "Bearer #{@api_key}"
20
- }
18
+ "Content-Type" => "application/json",
19
+ "Authorization" => "Bearer #{@api_key}",
20
+ },
21
21
  )
22
22
  @feature_cache = {}
23
23
  @initialized = {}
24
+
24
25
  fetch_features
25
26
  @timer = Workers::PeriodicTimer.new(15) do
26
27
  fetch_features
27
28
  end
28
29
  end
29
30
 
30
- def is_active(key, user={})
31
+ def is_active(key, user = {})
31
32
  unless @initialized
32
33
  return false
33
34
  end
@@ -37,16 +38,17 @@ module Molasses
37
38
  result = user_active(feature, user)
38
39
  if @send_events && user && user.include?("id")
39
40
  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"
41
+ "event" => "experiment_started",
42
+ "tags" => user["params"],
43
+ "userId" => user["id"],
44
+ "featureId" => feature["id"],
45
+ "featureName" => key,
46
+ "testType" => result ? "experiment" : "control",
46
47
  })
47
48
  end
48
49
  return result
49
50
  else
51
+ @logger.info "Warning - feature flag #{key} not set in environment"
50
52
  return false
51
53
  end
52
54
  end
@@ -55,21 +57,57 @@ module Molasses
55
57
  @timer.cancel
56
58
  end
57
59
 
58
- def experiment_success(key, additional_details={}, user=nil)
59
- if !@initialized || !@send_events || user == nil || user.include?("id")
60
- return false
60
+ def experiment_started(key, user = nil, additional_details = {})
61
+ if !@initialized || user == nil || !user.include?("id")
62
+ return false
63
+ end
64
+ unless @feature_cache.include?(key)
65
+ @logger.info "Warning - feature flag #{key} not set in environment"
66
+ return false
61
67
  end
62
68
  feature = @feature_cache[key]
63
69
  result = is_active(feature, user)
64
70
  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"
71
+ "event" => "experiment_started",
72
+ "tags" => user.include?("params") ? user["params"].merge(additional_details) : additional_details,
73
+ "userId" => user["id"],
74
+ "featureId" => feature["id"],
75
+ "featureName" => key,
76
+ "testType" => result ? "experiment" : "control",
77
+ })
78
+ end
79
+
80
+ def experiment_success(key, user = nil, additional_details = {})
81
+ if !@initialized || user == nil || !user.include?("id")
82
+ return false
83
+ end
84
+ unless @feature_cache.include?(key)
85
+ @logger.info "Warning - feature flag #{key} not set in environment"
86
+ return false
87
+ end
88
+ feature = @feature_cache[key]
89
+ result = is_active(feature, user)
90
+ send_event({
91
+ "event" => "experiment_success",
92
+ "tags" => user.include?("params") ? user["params"].merge(additional_details) : additional_details,
93
+ "userId" => user["id"],
94
+ "featureId" => feature["id"],
95
+ "featureName" => key,
96
+ "testType" => result ? "experiment" : "control",
97
+ })
98
+ end
99
+
100
+ def track(key, user = nil, additional_details = {})
101
+ if user == nil || !user.include?("id")
102
+ return false
103
+ end
104
+ send_event({
105
+ "event" => key,
106
+ "tags" => user.include?("params") ? user["params"].merge(additional_details) : additional_details,
107
+ "userId" => user["id"],
71
108
  })
72
109
  end
110
+
73
111
  private
74
112
 
75
113
  def user_active(feature, user)
@@ -78,13 +116,13 @@ module Molasses
78
116
  end
79
117
 
80
118
  unless user && user.include?("id")
81
- return true
119
+ return true
82
120
  end
83
121
 
84
122
  segment_map = {}
85
123
  for feature_segment in feature["segments"]
86
- segment_type = feature_segment["segmentType"]
87
- segment_map[segment_type] = feature_segment
124
+ segment_type = feature_segment["segmentType"]
125
+ segment_map[segment_type] = feature_segment
88
126
  end
89
127
 
90
128
  if segment_map.include?("alwaysControl") and in_segment(user, segment_map["alwaysControl"])
@@ -97,7 +135,7 @@ module Molasses
97
135
  return user_percentage(user["id"], segment_map["everyoneElse"]["percentage"])
98
136
  end
99
137
 
100
- def user_percentage(id="", percentage = 0)
138
+ def user_percentage(id = "", percentage = 0)
101
139
  if percentage == 100
102
140
  return true
103
141
  end
@@ -114,7 +152,7 @@ module Molasses
114
152
  def in_segment(user, segment_map)
115
153
  user_constraints = segment_map["userConstraints"]
116
154
  constraints_length = user_constraints.length
117
- constraints_to_be_met = segment_map["constraint"] == 'any' ? 1 : constraints_length
155
+ constraints_to_be_met = segment_map["constraint"] == "any" ? 1 : constraints_length
118
156
  constraints_met = 0
119
157
 
120
158
  for constraint in user_constraints
@@ -137,51 +175,110 @@ module Molasses
137
175
  return constraints_met >= constraints_to_be_met
138
176
  end
139
177
 
178
+ def parse_number(user_value)
179
+ case user_value
180
+ when Numeric
181
+ return user_value
182
+ when TrueClass
183
+ return 1
184
+ when FalseClass
185
+ return 0
186
+ when String
187
+ return user_value.to_f
188
+ end
189
+ end
190
+
191
+ def parse_bool(user_value)
192
+ case user_value
193
+ when Numeric
194
+ return user_value == 1
195
+ when TrueClass, FalseClass
196
+ return user_value
197
+ when String
198
+ return user_value == "true"
199
+ end
200
+ end
201
+
140
202
  def meets_constraint(user_value, param_exists, constraint)
141
203
  operator = constraint["operator"]
142
204
  if param_exists == false
143
205
  return false
144
206
  end
145
207
 
208
+ constraint_value = constraint["values"]
209
+ case constraint["userParamType"]
210
+ when "number"
211
+ user_value = parse_number(user_value)
212
+ constraint_value = parse_number(constraint_value)
213
+ when "boolean"
214
+ user_value = parse_bool(user_value)
215
+ constraint_value = parse_bool(constraint_value)
216
+ when "semver"
217
+ user_value = Semantic::Version.new user_value
218
+ constraint_value = Semantic::Version.new constraint_value
219
+ else
220
+ user_value = user_value.to_s
221
+ end
222
+
146
223
  case operator
147
224
  when "in"
148
- list_values = constraint["values"].split(',')
225
+ list_values = constraint_value.split(",")
149
226
  return list_values.include? user_value
150
227
  when "nin"
151
- list_values = constraint["values"].split(',')
228
+ list_values = constraint_value.split(",")
152
229
  return !list_values.include?(user_value)
230
+ when "lt"
231
+ return user_value < constraint_value
232
+ when "lte"
233
+ return user_value <= constraint_value
234
+ when "gt"
235
+ return user_value > constraint_value
236
+ when "gte"
237
+ return user_value >= constraint_value
153
238
  when "equals"
154
- return user_value == constraint["values"]
239
+ return user_value == constraint_value
155
240
  when "doesNotEqual"
156
- return user_value != constraint["values"]
241
+ return user_value != constraint_value
157
242
  when "contains"
158
- return constraint["values"].include?(user_value)
243
+ return constraint_value.include?(user_value)
159
244
  when "doesNotContain"
160
- return !constraint["values"].include?(user_value)
245
+ return !constraint_value.include?(user_value)
161
246
  else
162
247
  return false
163
248
  end
164
249
  end
165
250
 
166
251
  def send_event(event_options)
167
- @conn.post('analytics', event_options.to_json)
252
+ @conn.post("analytics", event_options.to_json)
168
253
  end
169
254
 
170
255
  def fetch_features()
171
- response = @conn.get('get-features')
256
+ response = @conn.get("features")
172
257
  if response.status == 200
173
258
  data = JSON.parse(response.body)
174
259
  if data.include?("data") and data["data"].include?("features")
175
260
  features = data["data"]["features"]
176
- for feature in features do
261
+ for feature in features
177
262
  @feature_cache[feature["key"]] = feature
178
263
  end
264
+ unless @initialized
265
+ @logger.info "Molasses - connected and initialized"
266
+ end
179
267
  @initialized = true
180
268
  end
181
269
  else
182
- puts "Molasses - #{response.status} - #{response.body}"
270
+ @logger.info "Molasses - #{response.status} - #{response.body}"
183
271
  end
184
272
  end
185
273
 
274
+ def get_default_logger
275
+ if defined?(Rails) && Rails.respond_to?(:logger)
276
+ Rails.logger
277
+ else
278
+ log = ::Logger.new(STDOUT)
279
+ log.level = ::Logger::WARN
280
+ log
281
+ end
282
+ end
186
283
  end
187
- end
284
+ end
@@ -1,225 +1,358 @@
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": [
103
+ {
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
+
159
+ },
160
+ {
161
+ "constraint": "all",
162
+ "percentage": 50,
163
+ "segmentType": "everyoneElse",
164
+ "userConstraints": [],
165
+ },
166
+ ],
167
+ },
168
+ {
169
+ "id": "4",
170
+ "active": true,
171
+ "description": "bar",
172
+ "key": "semver",
173
+ "segments": [
174
+ {
175
+ "percentage": 100,
176
+ "segmentType": "alwaysExperiment",
177
+ "constraint": "any",
178
+ "userConstraints": [
179
+ {
180
+ "userParam": "lt",
181
+ "userParamType": "semver",
182
+ "operator": "lt",
183
+ "values": "1.2.0",
184
+ },
185
+ {
186
+ "userParam": "lte",
187
+ "userParamType": "semver",
188
+ "operator": "lte",
189
+ "values": "1.2.0",
190
+ },
191
+ {
192
+ "userParam": "gt",
193
+ "userParamType": "semver",
194
+ "operator": "gt",
195
+ "values": "1.2.0",
196
+ },
197
+ {
198
+ "userParam": "gte",
199
+ "userParamType": "semver",
200
+ "operator": "gte",
201
+ "values": "1.2.0",
202
+ },
203
+ {
204
+ "userParam": "equals",
205
+ "userParamType": "semver",
206
+ "operator": "equals",
207
+ "values": "1.2.0",
208
+ },
209
+ {
210
+ "userParam": "doesNotEqual",
211
+ "userParamType": "semver",
212
+ "operator": "doesNotEqual",
213
+ "values": "1.2.0",
214
+ },
215
+
216
+ ],
217
+
218
+ },
219
+ {
220
+ "constraint": "all",
221
+ "percentage": 0,
222
+ "segmentType": "everyoneElse",
223
+ "userConstraints": [],
224
+ },
225
+ ],
226
+ },
227
+ {
228
+ "id": "1",
229
+ "active": true,
230
+ "description": "foo",
231
+ "key": "FOO_TEST",
232
+ "segments": [
94
233
  {
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
- ],
234
+ "percentage": 100,
235
+ "segmentType": "alwaysControl",
236
+ "constraint": "all",
237
+ "userConstraints": [
238
+ {
239
+ "userParam": "isScaredUser",
240
+ "operator": "contains",
241
+ "values": "scared",
242
+ },
243
+ {
244
+ "userParam": "isDefinitelyScaredUser",
245
+ "operator": "contains",
246
+ "values": "scared",
247
+ },
248
+ {
249
+ "userParam": "isMostDefinitelyScaredUser",
250
+ "operator": "contains",
251
+ "values": "scared",
252
+ },
253
+ ],
146
254
  },
147
- ],
255
+ {
256
+ "percentage": 100,
257
+ "segmentType": "alwaysExperiment",
258
+ "constraint": "any",
259
+ "userConstraints": [
260
+ {
261
+ "userParam": "isBetaUser",
262
+ "operator": "doesNotContain",
263
+ "values": "fal",
264
+ },
265
+ {
266
+ "userParam": "isDefinitelyBetaUser",
267
+ "operator": "doesNotContain",
268
+ "values": "fal",
269
+ },
270
+ ],
271
+ },
272
+ {
273
+ "constraint": "all",
274
+ "percentage": 100,
275
+ "segmentType": "everyoneElse",
276
+ "userConstraints": [],
277
+ },
278
+ ],
279
+ },
280
+ ],
148
281
  },
149
282
  }
150
283
 
151
284
  responseD = {
152
285
  "data": {
153
- "features": [
154
- {
155
- "id": "1",
156
- "active": true,
157
- "description": "foo",
158
- "key": "FOO_TEST",
159
- "segments": [],
160
- },
286
+ "features": [
287
+ {
288
+ "id": "1",
289
+ "active": true,
290
+ "description": "foo",
291
+ "key": "FOO_TEST",
292
+ "segments": [],
293
+ },
294
+ {
295
+ "id": "2",
296
+ "active": false,
297
+ "description": "foo",
298
+ "key": "FOO_FALSE_TEST",
299
+ "segments": [],
300
+ },
301
+ {
302
+ "id": "3",
303
+ "active": true,
304
+ "description": "foo",
305
+ "key": "FOO_50_PERCENT_TEST",
306
+ "segments": [
161
307
  {
162
- "id": "2",
163
- "active": false,
164
- "description": "foo",
165
- "key": "FOO_FALSE_TEST",
166
- "segments": [],
308
+ "constraint": "all",
309
+ "segmentType": "everyoneElse",
310
+ "percentage": 50,
311
+ "userConstraints": [],
167
312
  },
313
+ ],
314
+ },
315
+ {
316
+ "id": "4",
317
+ "active": true,
318
+ "description": "foo",
319
+ "key": "FOO_0_PERCENT_TEST",
320
+ "segments": [
168
321
  {
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
- ],
322
+ "constraint": "all",
323
+ "segmentType": "everyoneElse",
324
+ "percentage": 0,
325
+ "userConstraints": [],
181
326
  },
327
+ ],
328
+ },
329
+ {
330
+ "id": "5",
331
+ "active": true,
332
+ "description": "foo",
333
+ "key": "FOO_ID_TEST",
334
+ "segments": [
182
335
  {
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
- ],
336
+ "constraint": "all",
337
+ "percentage": 100,
338
+ "segmentType": "alwaysControl",
339
+ "userConstraints": [
340
+ {
341
+ "userParam": "id",
342
+ "operator": "equals",
343
+ "values": "123",
344
+ },
345
+ ],
195
346
  },
196
347
  {
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
- ],
348
+ "constraint": "all",
349
+ "segmentType": "everyoneElse",
350
+ "percentage": 100,
351
+ "userConstraints": [],
221
352
  },
222
- ],
353
+ ],
354
+ },
355
+ ],
223
356
  },
224
357
  }
225
358
 
@@ -227,53 +360,229 @@ $client = nil
227
360
  $conn = nil
228
361
  $stubs = nil
229
362
  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) }
363
+ let(:stubs) { Faraday::Adapter::Test::Stubs.new }
364
+ let(:conn) { Faraday.new { |b| b.adapter(:test, stubs) } }
365
+ let(:client) { Molasses::Client.new("test_api_key") }
233
366
  it "can do basic molasses" do
234
- stub_request(:get, 'https://us-central1-molasses-36bff.cloudfunctions.net/get-features').
235
- to_return(body:responseA.to_json, headers: {
236
- 'Content-Type'=>'application/json',
237
- },)
367
+ stub_request(:get, "https://sdk.molasses.app/v1/features").
368
+ to_return(body: responseA.to_json, headers: {
369
+ "Content-Type" => "application/json",
370
+ })
238
371
  expect(client.is_active("FOO_TEST")).to be_truthy
239
- expect(client.is_active("FOO_TEST", {"foo"=>"foo"})).to be_truthy
372
+ expect(client.is_active("FOO_TEST", { "foo" => "foo" })).to be_truthy
240
373
  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
374
+ expect(client.is_active("FOO_TEST", { "id" => "foo", "params" => {} })).to be_truthy
375
+ expect(client.is_active("FOO_TEST", { "id" => "food", "params" => {
376
+ "isScaredUser" => "true",
377
+ } })).to be_falsy
378
+ expect(client.is_active("FOO_TEST", { "id" => "foodie", "params" => {
379
+ "isBetaUser" => "true",
380
+ } })).to be_truthy
246
381
  end
247
382
  it "can do advanced molasses" do
248
- stub_request(:get, 'https://us-central1-molasses-36bff.cloudfunctions.net/get-features').
249
- to_return(body:responseB.to_json, headers: {
250
- 'Content-Type'=>'application/json',
251
- },)
383
+ stub_request(:get, "https://sdk.molasses.app/v1/features").
384
+ to_return(body: responseB.to_json, headers: {
385
+ "Content-Type" => "application/json",
386
+ })
252
387
 
253
388
  expect(client.is_active("FOO_TEST")).to be_truthy
254
- expect(client.is_active("FOO_TEST", {"foo"=>"foo"})).to be_truthy
389
+ expect(client.is_active("FOO_TEST", { "foo" => "foo" })).to be_truthy
255
390
  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
391
+ expect(client.is_active("FOO_TEST", { "id" => "foo", "params" => {
392
+ "isBetaUser" => "false", "isScaredUser" => "false",
393
+ } })).to be_truthy
394
+ expect(client.is_active("FOO_TEST", { "id" => "food", "params" => {
395
+ "isScaredUser" => "true",
396
+ } })).to be_falsy
397
+ expect(client.is_active("FOO_TEST", { "id" => "foodie", "params" => {
398
+ "isBetaUser" => "true",
399
+ } })).to be_truthy
262
400
  end
263
401
  it "can do even more advanced molasses" do
264
- stub_request(:get, 'https://us-central1-molasses-36bff.cloudfunctions.net/get-features').
265
- to_return(body:responseC.to_json, headers: {
266
- 'Content-Type'=>'application/json',
267
- },)
402
+ stub_request(:get, "https://sdk.molasses.app/v1/features").
403
+ to_return(body: responseC.to_json, headers: {
404
+ "Content-Type" => "application/json",
405
+ })
406
+ stub_request(:post, "https://sdk.molasses.app/v1/analytics").
407
+ to_return(status: 200, body: "", headers: {})
408
+ expect(client.is_active("FOO_TEST", { "id" => "foo", "params" => {
409
+ "isScaredUser" => "scared",
410
+ "isDefinitelyScaredUser" => "scared",
411
+ "isMostDefinitelyScaredUser" => "scared",
412
+ } })).to be_falsy
413
+ expect(client.is_active("FOO_TEST", { "id" => "food", "params" => {
414
+ "isDefinitelyBetaUser" => "true", "isBetaUser" => "true",
415
+ } })).to be_truthy
416
+ expect(client.is_active("FOO_TEST", { "id" => "foodie", "params" => {
417
+ "isBetaUser" => "true",
418
+ } })).to be_truthy
419
+
420
+ expect(client.is_active("NUMBERS_BOOLS", {
421
+ "id" => "12341",
422
+ "params" => {
423
+ "lt" => true,
424
+ "lte" => "12",
425
+ "gt" => 14,
426
+ "gte" => 12,
427
+ "equals" => 12,
428
+ "doesNotEqual" => false,
429
+ "equalsBool" => true,
430
+ "doesNotEqualBool" => "true",
431
+ },
432
+ })).to be_falsy
433
+
434
+ expect(client.is_active("semver", {
435
+ "id" => "123444", # valid crc32 percentage
436
+ "params" => {
437
+ "lt" => "1.1.0",
438
+ },
439
+ })).to be_truthy
440
+
441
+ expect(client.is_active("semver", {
442
+ "id" => "123444", # valid crc32 percentage
443
+ "params" => {
444
+ "lt" => "1.2.0",
445
+ },
446
+ })).to be_falsy
447
+
448
+ expect(client.is_active("semver", {
449
+ "id" => "123444", # valid crc32 percentage
450
+ "params" => {
451
+ "gt" => "1.3.0",
452
+ },
453
+ })).to be_truthy
454
+
455
+ expect(client.is_active("semver", {
456
+ "id" => "123444", # valid crc32 percentage
457
+ "params" => {
458
+ "gt" => "1.2.0",
459
+ },
460
+ })).to be_falsy
461
+
462
+ expect(client.is_active("semver", {
463
+ "id" => "123444", # valid crc32 percentage
464
+ "params" => {
465
+ "lte" => "1.1.0",
466
+ },
467
+ })).to be_truthy
468
+
469
+ expect(client.is_active("semver", {
470
+ "id" => "123444", # valid crc32 percentage
471
+ "params" => {
472
+ "lte" => "1.2.0",
473
+ },
474
+ })).to be_truthy
475
+
476
+ expect(client.is_active("semver", {
477
+ "id" => "123444", # valid crc32 percentage
478
+ "params" => {
479
+ "lte" => "1.3.0",
480
+ },
481
+ })).to be_falsy
482
+
483
+ expect(client.is_active("semver", {
484
+ "id" => "123444", # valid crc32 percentage
485
+ "params" => {
486
+ "gte" => "1.3.0",
487
+ },
488
+ })).to be_truthy
489
+
490
+ expect(client.is_active("semver", {
491
+ "id" => "123444", # valid crc32 percentage
492
+ "params" => {
493
+ "gte" => "1.2.0",
494
+ },
495
+ })).to be_truthy
496
+
497
+ expect(client.is_active("semver", {
498
+ "id" => "123444", # valid crc32 percentage
499
+ "params" => {
500
+ "gte" => "0.1.0",
501
+ },
502
+ })).to be_falsy
503
+
504
+ expect(client.is_active("semver", {
505
+ "id" => "123444", # valid crc32 percentage
506
+ "params" => {
507
+ "equals" => "1.2.0",
508
+ },
509
+ })).to be_truthy
510
+
511
+ expect(client.is_active("semver", {
512
+ "id" => "123444", # valid crc32 percentage
513
+ "params" => {
514
+ "equals" => "1.3.0",
515
+ },
516
+ })).to be_falsy
517
+
518
+ expect(client.is_active("semver", {
519
+ "id" => "123444", # valid crc32 percentage
520
+ "params" => {
521
+ "doesNotEqual" => "1.3.0",
522
+ },
523
+ })).to be_truthy
524
+
525
+ expect(client.is_active("semver", {
526
+ "id" => "123444", # valid crc32 percentage
527
+ "params" => {
528
+ "doesNotEqual" => "1.2.0",
529
+ },
530
+ })).to be_falsy
268
531
 
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
532
+ expect(client.is_active("NUMBERS_BOOLS", {
533
+ "id" => "123444", # valid crc32 percentage
534
+ "params" => {
535
+ "lt" => true,
536
+ "lte" => "12",
537
+ "gt" => 14,
538
+ "gte" => 12,
539
+ "equals" => 12,
540
+ "doesNotEqual" => false,
541
+ "equalsBool" => 0,
542
+ "doesNotEqualBool" => "true",
543
+ },
544
+ })).to be_truthy
545
+ expect(client.experiment_started("NUMBERS_BOOLS", {
546
+ "id" => "123444", # valid crc32 percentage
547
+ "params" => {
548
+ "lt" => true,
549
+ "lte" => "12",
550
+ "gt" => 14,
551
+ "gte" => 12,
552
+ "equals" => 12,
553
+ "doesNotEqual" => false,
554
+ "equalsBool" => 0,
555
+ "doesNotEqualBool" => "true",
556
+ },
557
+ }))
558
+ expect(client.experiment_started("Clicked button", {
559
+ "id" => "123444", # v
560
+ }))
561
+ expect(client.track("Clicked button", {
562
+ "id" => "123444", # valid crc32 percentage
563
+ "params" => {
564
+ "lt" => true,
565
+ "lte" => "12",
566
+ "gt" => 14,
567
+ "gte" => 12,
568
+ "equals" => 12,
569
+ "doesNotEqual" => false,
570
+ "equalsBool" => 0,
571
+ "doesNotEqualBool" => "true",
572
+ },
573
+ }))
574
+ expect(client.experiment_success("NUMBERS_BOOLS", {
575
+ "id" => "123444", # valid crc32 percentage
576
+ "params" => {
577
+ "lt" => true,
578
+ "lte" => "12",
579
+ "gt" => 14,
580
+ "gte" => 12,
581
+ "equals" => 12,
582
+ "doesNotEqual" => false,
583
+ "equalsBool" => 0,
584
+ "doesNotEqualBool" => "true",
585
+ },
586
+ }))
278
587
  end
279
- end
588
+ 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.1.1
4
+ version: 0.4.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - James Hrisho
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-09-30 00:00:00.000000000 Z
11
+ date: 2021-07-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday
@@ -38,6 +38,20 @@ dependencies:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: 0.6.1
41
+ - !ruby/object:Gem::Dependency
42
+ name: semantic
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 1.6.1
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 1.6.1
41
55
  description: Ruby SDK for Molasses. Feature flags as a service
42
56
  email: james.hrisho@gmail.com
43
57
  executables: []
@@ -67,7 +81,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
67
81
  - !ruby/object:Gem::Version
68
82
  version: '0'
69
83
  requirements: []
70
- rubygems_version: 3.0.3
84
+ rubygems_version: 3.0.3.1
71
85
  signing_key:
72
86
  specification_version: 4
73
87
  summary: Ruby SDK for Molasses. Feature flags as a service