absmartly-sdk 0.1.2 → 1.0.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +2 -2
- data/README.md +253 -0
- data/absmartly.gemspec +40 -0
- data/example/example.rb +3 -1
- data/lib/a_b_smartly.rb +1 -1
- data/lib/absmartly/version.rb +1 -1
- data/lib/context.rb +32 -12
- data/lib/context_config.rb +8 -0
- data/lib/context_event_logger.rb +12 -4
- data/lib/context_event_logger_callback.rb +13 -0
- data/lib/json/exposure.rb +1 -1
- metadata +7 -5
- data/README +0 -180
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 85cc6f9a1f752cedc113e346ddd7fbbe885e18a571d94cb4696fbeb65afcfaf3
|
4
|
+
data.tar.gz: 7a16ba9a23c51f82030beb5cc25e0bff6da063bacdecfef24d506a843d63c29d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7ce160a8e6898a17504f4292f87443ba8c8b18c15f953464ba04e16388a98dc091c73854cebc7e18fa0d6cce02be73969a5b3979d9a2e281913ce6753e592bca
|
7
|
+
data.tar.gz: 6e057f2dcdaa1a7d1b5916baf6f28eaec66b8240433d6c28ea776238e87b39f179ca0084f30ed7bef1dda372d7e8dc0a42b3e5bac090ebe8ad1ad6ea1edb0cc7
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
absmartly (
|
4
|
+
absmartly-sdk (1.0.5)
|
5
5
|
|
6
6
|
GEM
|
7
7
|
remote: https://rubygems.org/
|
@@ -78,7 +78,7 @@ PLATFORMS
|
|
78
78
|
x86_64-darwin-20
|
79
79
|
|
80
80
|
DEPENDENCIES
|
81
|
-
absmartly!
|
81
|
+
absmartly-sdk!
|
82
82
|
arraybuffer
|
83
83
|
byebug
|
84
84
|
faraday
|
data/README.md
ADDED
@@ -0,0 +1,253 @@
|
|
1
|
+
# A/B Smartly SDK
|
2
|
+
|
3
|
+
A/B Smartly Ruby SDK
|
4
|
+
|
5
|
+
## Compatibility
|
6
|
+
|
7
|
+
The A/B Smartly Ruby SDK is compatible with Ruby versions 2.7 and later. For the best performance and code readability, Ruby 3 or later is recommended. This SDK is being constantly tested with the nightly builds of Ruby, to ensure it is compatible with the latest Ruby version.
|
8
|
+
|
9
|
+
|
10
|
+
## Getting Started
|
11
|
+
|
12
|
+
### Install the SDK
|
13
|
+
|
14
|
+
Install the gem and add to the application's Gemfile by executing:
|
15
|
+
|
16
|
+
$ bundle add absmartly-sdk
|
17
|
+
|
18
|
+
If bundler is not being used to manage dependencies, install the gem by executing:
|
19
|
+
|
20
|
+
$ gem install absmartly-sdk
|
21
|
+
|
22
|
+
## Import and Initialize the SDK
|
23
|
+
|
24
|
+
Once the SDK is installed, it can be initialized in your project.
|
25
|
+
|
26
|
+
```ruby
|
27
|
+
Absmartly.configure_client do |config|
|
28
|
+
config.endpoint = "https://your-company.absmartly.io/v1"
|
29
|
+
config.api_key = "YOUR-API-KEY"
|
30
|
+
config.application = "website"
|
31
|
+
config.environment = "development"
|
32
|
+
end
|
33
|
+
```
|
34
|
+
|
35
|
+
**SDK Options**
|
36
|
+
|
37
|
+
| Config | Type | Required? | Default | Description |
|
38
|
+
| :---------- | :----------------------------------- | :-------: | :-------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
39
|
+
| endpoint | `string` | ✅ | `undefined` | The URL to your API endpoint. Most commonly `"your-company.absmartly.io"` |
|
40
|
+
| apiKey | `string` | ✅ | `undefined` | Your API key which can be found on the Web Console. |
|
41
|
+
| environment | `"production"` or `"development"` | ✅ | `undefined` | The environment of the platform where the SDK is installed. Environments are created on the Web Console and should match the available environments in your infrastructure. |
|
42
|
+
| application | `string` | ✅ | `undefined` | The name of the application where the SDK is installed. Applications are created on the Web Console and should match the applications where your experiments will be running. |
|
43
|
+
| retries | `number` | ❌ | `5` | The number of retries before the SDK stops trying to connect. |
|
44
|
+
| timeout | `number` | ❌ | `3000` | An amount of time, in milliseconds, before the SDK will stop trying to connect. |
|
45
|
+
| eventLogger | `(context, eventName, data) => void` | ❌ | See "Using a Custom Event Logger" below | A callback function which runs after SDK events. |
|
46
|
+
|
47
|
+
### Using a Custom Event Logger
|
48
|
+
|
49
|
+
The A/B Smartly SDK can be instantiated with an event logger used for all
|
50
|
+
contexts. In addition, an event logger can be specified when creating a
|
51
|
+
particular context, in the `[CONTEXT_CONFIG_VARIABLE]`.
|
52
|
+
|
53
|
+
```
|
54
|
+
Custom Event Logger Code
|
55
|
+
```
|
56
|
+
|
57
|
+
The data parameter depends on the type of event. Currently, the SDK logs the
|
58
|
+
following events:
|
59
|
+
|
60
|
+
| eventName | when | data |
|
61
|
+
| ------------ | ------------------------------------------------------- | -------------------------------------------- |
|
62
|
+
| `"error"` | `Context` receives an error | error object thrown |
|
63
|
+
| `"ready"` | `Context` turns ready | data used to initialize the context |
|
64
|
+
| `"refresh"` | `Context.refresh()` method succeeds | data used to refresh the context |
|
65
|
+
| `"publish"` | `Context.publish()` method succeeds | data sent to the A/B Smartly event collector |
|
66
|
+
| `"exposure"` | `Context.treatment()` method succeeds on first exposure | exposure data enqueued for publishing |
|
67
|
+
| `"goal"` | `Context.track()` method succeeds | goal data enqueued for publishing |
|
68
|
+
| `"close"` | `Context.close()` method succeeds the first time | nil |
|
69
|
+
|
70
|
+
## Create a New Context Request
|
71
|
+
|
72
|
+
|
73
|
+
```ruby
|
74
|
+
context_config = Absmartly.create_context_config
|
75
|
+
```
|
76
|
+
|
77
|
+
**With Prefetched Data**
|
78
|
+
|
79
|
+
```ruby
|
80
|
+
client_config = ClientConfig.new(
|
81
|
+
endpoint: 'https://your-company.absmartly.io/v1',
|
82
|
+
api_key: 'YOUR-API-KEY',
|
83
|
+
application: 'website',
|
84
|
+
environment: 'development')
|
85
|
+
|
86
|
+
sdk_config = ABSmartlyConfig.create
|
87
|
+
sdk_config.client = Client.create(client_config)
|
88
|
+
|
89
|
+
sdk = Absmartly.create(sdk_config)
|
90
|
+
```
|
91
|
+
|
92
|
+
**Refreshing the Context with Fresh Experiment Data**
|
93
|
+
|
94
|
+
For long-running contexts, the context is usually created once when the
|
95
|
+
application is first started. However, any experiments being tracked in your
|
96
|
+
production code, but started after the context was created, will not be
|
97
|
+
triggered.
|
98
|
+
|
99
|
+
Alternatively, the `refresh` method can be called manually. The
|
100
|
+
`refresh` method pulls updated experiment data from the A/B
|
101
|
+
Smartly collector and will trigger recently started experiments when
|
102
|
+
`treatment` is called again.
|
103
|
+
|
104
|
+
**Setting Extra Units**
|
105
|
+
|
106
|
+
You can add additional units to a context by calling the `set_unit()` or
|
107
|
+
`set_units()` methods. These methods may be used, for example, when a user
|
108
|
+
logs in to your application and you want to use the new unit type in the
|
109
|
+
context.
|
110
|
+
|
111
|
+
Please note, you cannot override an already set unit type as that would be
|
112
|
+
a change of identity and would throw an exception. In this case, you must
|
113
|
+
create a new context instead. The `set_unit()` and
|
114
|
+
`set_units()` methods can be called before the context is ready.
|
115
|
+
|
116
|
+
```ruby
|
117
|
+
context_config.set_unit('session_id', 'bf06d8cb5d8137290c4abb64155584fbdb64d8')
|
118
|
+
context_config.set_unit('user_id', '123456')
|
119
|
+
context = Absmartly.create_context(context_config)
|
120
|
+
```
|
121
|
+
or
|
122
|
+
```ruby
|
123
|
+
context_config.set_units(
|
124
|
+
session_id: 'bf06d8cb5d8137290c4abb64155584fbdb64d8',
|
125
|
+
user_id: '123456'
|
126
|
+
)
|
127
|
+
context = Absmartly.create_context(context_config)
|
128
|
+
```
|
129
|
+
|
130
|
+
## Basic Usage
|
131
|
+
|
132
|
+
### Selecting A Treatment
|
133
|
+
|
134
|
+
```ruby
|
135
|
+
treatment = context.treatment('exp_test_experiment')
|
136
|
+
|
137
|
+
if treatment.zero?
|
138
|
+
# user is in control group (variant 0)
|
139
|
+
else
|
140
|
+
# user is in treatment group
|
141
|
+
end
|
142
|
+
```
|
143
|
+
|
144
|
+
### Treatment Variables
|
145
|
+
|
146
|
+
```ruby
|
147
|
+
default_button_color_value = 'red'
|
148
|
+
|
149
|
+
context.variable_value('experiment_name', default_button_color_value)
|
150
|
+
```
|
151
|
+
|
152
|
+
### Peek at Treatment Variants
|
153
|
+
|
154
|
+
Although generally not recommended, it is sometimes necessary to peek at
|
155
|
+
a treatment or variable without triggering an exposure. The A/B Smartly
|
156
|
+
SDK provides a `peek_treatment()` method for that.
|
157
|
+
|
158
|
+
```ruby
|
159
|
+
treatment = context.peek_treatment('exp_test_experiment')
|
160
|
+
```
|
161
|
+
|
162
|
+
#### Peeking at variables
|
163
|
+
|
164
|
+
```ruby
|
165
|
+
treatment = context.peek_variable_value('exp_test_experiment')
|
166
|
+
```
|
167
|
+
|
168
|
+
### Overriding Treatment Variants
|
169
|
+
|
170
|
+
During development, for example, it is useful to force a treatment for an
|
171
|
+
experiment. This can be achieved with the `set_override()` and/or `set_overrides()`
|
172
|
+
methods. These methods can be called before the context is ready.
|
173
|
+
|
174
|
+
```ruby
|
175
|
+
context.set_override("exp_test_experiment", 1) # force variant 1 of treatment
|
176
|
+
|
177
|
+
context.set_overrides(
|
178
|
+
'exp_test_experiment' => 1,
|
179
|
+
'exp_another_experiment' => 0,
|
180
|
+
)
|
181
|
+
```
|
182
|
+
|
183
|
+
## Advanced
|
184
|
+
|
185
|
+
### Context Attributes
|
186
|
+
|
187
|
+
Attributes are used to pass meta-data about the user and/or the request.
|
188
|
+
They can be used later in the Web Console to create segments or audiences.
|
189
|
+
They can be set using the `set_attribute()` or `set_attributes()`
|
190
|
+
methods, before or after the context is ready.
|
191
|
+
|
192
|
+
```ruby
|
193
|
+
context.set_attribute('session_id', session_id)
|
194
|
+
context.set_attributes(
|
195
|
+
'customer_age' => 'new_customer'
|
196
|
+
)
|
197
|
+
```
|
198
|
+
|
199
|
+
### Custom Assignments
|
200
|
+
|
201
|
+
Sometimes it may be necessary to override the automatic selection of a
|
202
|
+
variant. For example, if you wish to have your variant chosen based on
|
203
|
+
data from an API call. This can be accomplished using the
|
204
|
+
`set_custom_assignment()` method.
|
205
|
+
|
206
|
+
```ruby
|
207
|
+
chosen_variant = 1
|
208
|
+
context.set_custom_assignment('experiment_name', chosen_variant)
|
209
|
+
```
|
210
|
+
|
211
|
+
If you are running multiple experiments and need to choose different
|
212
|
+
custom assignments for each one, you can do so using the
|
213
|
+
`set_custom_assignments()` method.
|
214
|
+
|
215
|
+
```ruby
|
216
|
+
assignments = [
|
217
|
+
'experiment_name' => 1,
|
218
|
+
'another_experiment_name' => 0,
|
219
|
+
'a_third_experiment_name' => 2
|
220
|
+
]
|
221
|
+
|
222
|
+
context.set_custom_assignments(assignments)
|
223
|
+
```
|
224
|
+
|
225
|
+
### Publish
|
226
|
+
|
227
|
+
Sometimes it is necessary to ensure all events have been published to the
|
228
|
+
A/B Smartly collector, before proceeding. You can explicitly call the
|
229
|
+
`publish()` methods.
|
230
|
+
|
231
|
+
```
|
232
|
+
context.publish
|
233
|
+
```
|
234
|
+
|
235
|
+
### Finalize
|
236
|
+
|
237
|
+
The `close()` method will ensure all events have been
|
238
|
+
published to the A/B Smartly collector, like `publish()`, and will also
|
239
|
+
"seal" the context, throwing an error if any method that could generate
|
240
|
+
an event is called.
|
241
|
+
|
242
|
+
```
|
243
|
+
context.close
|
244
|
+
```
|
245
|
+
|
246
|
+
### Tracking Goals
|
247
|
+
|
248
|
+
```ruby
|
249
|
+
context.track(
|
250
|
+
'payment',
|
251
|
+
{ item_count: 1, total_amount: 1999.99 }
|
252
|
+
)
|
253
|
+
```
|
data/absmartly.gemspec
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# $:.push File.expand_path("../lib", __FILE__)
|
4
|
+
require File.expand_path("lib/absmartly/version", __dir__)
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "absmartly-sdk"
|
7
|
+
spec.version = Absmartly::VERSION
|
8
|
+
spec.authors = ["absmartly"]
|
9
|
+
spec.email = ["sdks@absmartly.com"]
|
10
|
+
|
11
|
+
spec.summary = "Absmartly gem"
|
12
|
+
spec.description = "Absmartly gem"
|
13
|
+
|
14
|
+
spec.homepage = "https://github.com/absmartly/ruby-sdk"
|
15
|
+
|
16
|
+
spec.license = "MIT"
|
17
|
+
spec.required_ruby_version = ">= 2.7.0"
|
18
|
+
spec.extra_rdoc_files = ["README.md"]
|
19
|
+
|
20
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
21
|
+
spec.metadata["source_code_uri"] = "https://github.com/absmartly/ruby-sdk"
|
22
|
+
spec.metadata["changelog_uri"] = "https://github.com/absmartly/ruby-sdk"
|
23
|
+
|
24
|
+
# Specify which files should be added to the gem when it is released.
|
25
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
26
|
+
spec.files = Dir.chdir(__dir__) do
|
27
|
+
`git ls-files -z`.split("\x0").reject do |f|
|
28
|
+
(f == __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
|
29
|
+
end
|
30
|
+
end
|
31
|
+
spec.bindir = "exe"
|
32
|
+
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
33
|
+
spec.require_paths = ["lib"]
|
34
|
+
|
35
|
+
# Uncomment to register a new dependency of your gem
|
36
|
+
# spec.add_dependency "example-gem", "~> 1.0"
|
37
|
+
|
38
|
+
# For more information and examples about making a new gem, check out our
|
39
|
+
# guide at: https://bundler.io/guides/creating_gem.html
|
40
|
+
end
|
data/example/example.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require_relative "../lib/absmartly"
|
2
4
|
|
3
5
|
# config file
|
@@ -25,7 +27,7 @@ treatment3 = ctx.treatment("test")
|
|
25
27
|
puts(treatment3) # 1
|
26
28
|
|
27
29
|
ctx.set_unit("db_user_id", 1000013)
|
28
|
-
ctx.set_units(db_user_id2: 1000013,
|
30
|
+
ctx.set_units(db_user_id2: 1000013, session_id2: 12311)
|
29
31
|
|
30
32
|
ctx.set_attribute("user_agent", "Chrome 2022")
|
31
33
|
ctx.set_attributes(
|
data/lib/a_b_smartly.rb
CHANGED
@@ -58,7 +58,7 @@ class ABSmartly
|
|
58
58
|
|
59
59
|
def create_context(config)
|
60
60
|
validate_params(config)
|
61
|
-
Context.create(get_utc_format, config, @
|
61
|
+
Context.create(get_utc_format, config, @context_data_provider.context_data,
|
62
62
|
@context_data_provider, @context_event_handler, @context_event_logger, @variable_parser,
|
63
63
|
AudienceMatcher.new(@audience_deserializer))
|
64
64
|
end
|
data/lib/absmartly/version.rb
CHANGED
data/lib/context.rb
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
require_relative "hashing"
|
4
4
|
require_relative "variant_assigner"
|
5
|
+
require_relative "context_event_logger"
|
5
6
|
require_relative "json/unit"
|
6
7
|
require_relative "json/attribute"
|
7
8
|
require_relative "json/exposure"
|
@@ -11,13 +12,13 @@ require_relative "json/goal_achievement"
|
|
11
12
|
class Context
|
12
13
|
attr_reader :data, :pending_count
|
13
14
|
|
14
|
-
def self.create(clock, config,
|
15
|
+
def self.create(clock, config, data_future, data_provider,
|
15
16
|
event_handler, event_logger, variable_parser, audience_matcher)
|
16
|
-
Context.new(clock, config,
|
17
|
+
Context.new(clock, config, data_future, data_provider,
|
17
18
|
event_handler, event_logger, variable_parser, audience_matcher)
|
18
19
|
end
|
19
20
|
|
20
|
-
def initialize(clock, config,
|
21
|
+
def initialize(clock, config, data_future, data_provider,
|
21
22
|
event_handler, event_logger, variable_parser, audience_matcher)
|
22
23
|
@index = []
|
23
24
|
@achievements = []
|
@@ -31,7 +32,6 @@ class Context
|
|
31
32
|
@data_provider = data_provider
|
32
33
|
@variable_parser = variable_parser
|
33
34
|
@audience_matcher = audience_matcher
|
34
|
-
@scheduler = scheduler
|
35
35
|
@closed = false
|
36
36
|
|
37
37
|
@units = {}
|
@@ -49,8 +49,10 @@ class Context
|
|
49
49
|
set_custom_assignments(config.custom_assignments) if config.custom_assignments
|
50
50
|
if data_future.success?
|
51
51
|
assign_data(data_future.data_future)
|
52
|
+
log_event(ContextEventLogger::EVENT_TYPE::READY, data_future.data_future)
|
52
53
|
else
|
53
54
|
set_data_failed(data_future.exception)
|
55
|
+
log_error(data_future.exception)
|
54
56
|
end
|
55
57
|
end
|
56
58
|
|
@@ -157,11 +159,11 @@ class Context
|
|
157
159
|
assignment.exposed = true
|
158
160
|
|
159
161
|
exposure = Exposure.new
|
160
|
-
exposure.id = assignment.id
|
162
|
+
exposure.id = assignment.id || 0
|
161
163
|
exposure.name = assignment.name
|
162
164
|
exposure.unit = assignment.unit_type
|
163
165
|
exposure.variant = assignment.variant
|
164
|
-
exposure.exposed_at = @clock
|
166
|
+
exposure.exposed_at = @clock.to_i
|
165
167
|
exposure.assigned = assignment.assigned
|
166
168
|
exposure.eligible = assignment.eligible
|
167
169
|
exposure.overridden = assignment.overridden
|
@@ -171,6 +173,7 @@ class Context
|
|
171
173
|
|
172
174
|
@pending_count += 1
|
173
175
|
@exposures.push(exposure)
|
176
|
+
log_event(ContextEventLogger::EVENT_TYPE::EXPOSURE, exposure)
|
174
177
|
end
|
175
178
|
end
|
176
179
|
|
@@ -221,6 +224,7 @@ class Context
|
|
221
224
|
|
222
225
|
@pending_count += 1
|
223
226
|
@achievements.push(achievement)
|
227
|
+
log_event(ContextEventLogger::EVENT_TYPE::GOAL, achievement)
|
224
228
|
end
|
225
229
|
|
226
230
|
def publish
|
@@ -236,8 +240,10 @@ class Context
|
|
236
240
|
data_future = @data_provider.context_data
|
237
241
|
if data_future.success?
|
238
242
|
assign_data(data_future.data_future)
|
243
|
+
log_event(ContextEventLogger::EVENT_TYPE::REFRESH, data_future.data_future)
|
239
244
|
else
|
240
245
|
set_data_failed(data_future.exception)
|
246
|
+
log_error(data_future.exception)
|
241
247
|
end
|
242
248
|
end
|
243
249
|
end
|
@@ -248,6 +254,7 @@ class Context
|
|
248
254
|
flush
|
249
255
|
end
|
250
256
|
@closed = true
|
257
|
+
log_event(ContextEventLogger::EVENT_TYPE::CLOSE, nil)
|
251
258
|
end
|
252
259
|
end
|
253
260
|
|
@@ -282,18 +289,20 @@ class Context
|
|
282
289
|
event.hashed = true
|
283
290
|
event.published_at = @clock.to_i
|
284
291
|
event.units = @units.map do |key, value|
|
285
|
-
Unit.new(key, unit_hash(key, value))
|
292
|
+
Unit.new(key.to_s, unit_hash(key, value))
|
286
293
|
end
|
287
|
-
event.attributes = @attributes.empty? ? nil : @attributes
|
288
294
|
event.exposures = exposures
|
289
|
-
event.
|
290
|
-
|
295
|
+
event.attributes = @attributes unless @attributes.empty?
|
296
|
+
event.goals = achievements unless achievements.nil?
|
297
|
+
log_event(ContextEventLogger::EVENT_TYPE::PUBLISH, event)
|
298
|
+
@event_handler.publish(self, event)
|
291
299
|
end
|
292
300
|
end
|
293
301
|
else
|
294
302
|
@exposures = []
|
295
303
|
@achievements = []
|
296
304
|
@pending_count = 0
|
305
|
+
@data_failed
|
297
306
|
end
|
298
307
|
end
|
299
308
|
|
@@ -365,7 +374,7 @@ class Context
|
|
365
374
|
hash
|
366
375
|
end
|
367
376
|
match = @audience_matcher.evaluate(experiment.data.audience, attrs)
|
368
|
-
if match
|
377
|
+
if match && !match.result
|
369
378
|
assignment.audience_mismatch = true
|
370
379
|
end
|
371
380
|
end
|
@@ -471,6 +480,18 @@ class Context
|
|
471
480
|
@failed = true
|
472
481
|
end
|
473
482
|
|
483
|
+
def log_event(event, data)
|
484
|
+
unless @event_logger.nil?
|
485
|
+
@event_logger.handle_event(event, data)
|
486
|
+
end
|
487
|
+
end
|
488
|
+
|
489
|
+
def log_error(error)
|
490
|
+
unless @event_logger.nil?
|
491
|
+
@event_logger.handle_event(ContextEventLogger::EVENT_TYPE::ERROR, error.message)
|
492
|
+
end
|
493
|
+
end
|
494
|
+
|
474
495
|
attr_accessor :clock,
|
475
496
|
:publish_delay,
|
476
497
|
:event_handler,
|
@@ -478,7 +499,6 @@ class Context
|
|
478
499
|
:data_provider,
|
479
500
|
:variable_parser,
|
480
501
|
:audience_matcher,
|
481
|
-
:scheduler,
|
482
502
|
:units,
|
483
503
|
:failed,
|
484
504
|
:data_lock,
|
data/lib/context_config.rb
CHANGED
data/lib/context_event_logger.rb
CHANGED
@@ -1,9 +1,17 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
class ContextEventLogger
|
4
|
-
EVENT_TYPE
|
5
|
-
|
6
|
-
|
7
|
-
|
4
|
+
module EVENT_TYPE
|
5
|
+
ERROR = "error"
|
6
|
+
READY = "ready"
|
7
|
+
REFRESH = "refresh"
|
8
|
+
PUBLISH = "publish"
|
9
|
+
EXPOSURE = "exposure"
|
10
|
+
GOAL = "goal"
|
11
|
+
CLOSE = "close"
|
12
|
+
end
|
13
|
+
|
14
|
+
def handle_event(event, data)
|
15
|
+
raise NotImplementedError.new("You must implement handle_event method.")
|
8
16
|
end
|
9
17
|
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class ContextEventLoggerCallback < ContextEventLogger
|
4
|
+
attr_accessor :callable
|
5
|
+
|
6
|
+
def initialize(callable)
|
7
|
+
@callable = callable
|
8
|
+
end
|
9
|
+
|
10
|
+
def handle_event(event, data)
|
11
|
+
@callable.call(event, data) if @callable.present?
|
12
|
+
end
|
13
|
+
end
|
data/lib/json/exposure.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: absmartly-sdk
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- absmartly
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2023-04-06 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: Absmartly gem
|
14
14
|
email:
|
@@ -16,7 +16,7 @@ email:
|
|
16
16
|
executables: []
|
17
17
|
extensions: []
|
18
18
|
extra_rdoc_files:
|
19
|
-
- README
|
19
|
+
- README.md
|
20
20
|
files:
|
21
21
|
- ".rspec"
|
22
22
|
- ".rubocop.yml"
|
@@ -26,8 +26,9 @@ files:
|
|
26
26
|
- Gemfile
|
27
27
|
- Gemfile.lock
|
28
28
|
- LICENSE.txt
|
29
|
-
- README
|
29
|
+
- README.md
|
30
30
|
- Rakefile
|
31
|
+
- absmartly.gemspec
|
31
32
|
- example/example.rb
|
32
33
|
- lib/a_b_smartly.rb
|
33
34
|
- lib/a_b_smartly_config.rb
|
@@ -45,6 +46,7 @@ files:
|
|
45
46
|
- lib/context_data_provider.rb
|
46
47
|
- lib/context_event_handler.rb
|
47
48
|
- lib/context_event_logger.rb
|
49
|
+
- lib/context_event_logger_callback.rb
|
48
50
|
- lib/context_event_serializer.rb
|
49
51
|
- lib/default_audience_deserializer.rb
|
50
52
|
- lib/default_context_data_deserializer.rb
|
@@ -113,7 +115,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
113
115
|
- !ruby/object:Gem::Version
|
114
116
|
version: '0'
|
115
117
|
requirements: []
|
116
|
-
rubygems_version: 3.
|
118
|
+
rubygems_version: 3.4.10
|
117
119
|
signing_key:
|
118
120
|
specification_version: 4
|
119
121
|
summary: Absmartly gem
|
data/README
DELETED
@@ -1,180 +0,0 @@
|
|
1
|
-
# A/B Smartly SDK
|
2
|
-
|
3
|
-
A/B Smartly Ruby SDK
|
4
|
-
|
5
|
-
## Compatibility
|
6
|
-
|
7
|
-
The A/B Smartly Ruby SDK is compatible with Ruby versions 2.7 and later. For the best performance and code readability, Ruby 3 or later is recommended. This SDK is being constantly tested with the nightly builds of Ruby, to ensure it is compatible with the latest Ruby version.
|
8
|
-
|
9
|
-
## Getting Started
|
10
|
-
|
11
|
-
### Install the SDK
|
12
|
-
|
13
|
-
Install the gem and add to the application's Gemfile by executing:
|
14
|
-
|
15
|
-
$ bundle add absmartly
|
16
|
-
|
17
|
-
If bundler is not being used to manage dependencies, install the gem by executing:
|
18
|
-
|
19
|
-
$ gem install absmartly
|
20
|
-
|
21
|
-
## Basic Usage
|
22
|
-
|
23
|
-
Once the SDK is installed, it can be initialized in your project.
|
24
|
-
|
25
|
-
You can create an SDK instance using the API key, application name, environment, and the endpoint URL obtained from A/B Smartly.
|
26
|
-
|
27
|
-
```Ruby
|
28
|
-
require 'absmartly'
|
29
|
-
|
30
|
-
Absmartly.configure_client do |config|
|
31
|
-
config.endpoint = "https://your-company.absmartly.io/v1"
|
32
|
-
config.api_key = "YOUR-API-KEY"
|
33
|
-
config.application = "website"
|
34
|
-
config.environment = "development"
|
35
|
-
end
|
36
|
-
```
|
37
|
-
#### Creating a new Context with raw promises
|
38
|
-
|
39
|
-
```Ruby
|
40
|
-
# define a new context request
|
41
|
-
context_config = Absmartly.create_context_config
|
42
|
-
context_config.set_unit("session_id", "bf06d8cb5d8137290c4abb64155584fbdb64d8")
|
43
|
-
context_config.set_unit("user_id", "123456")
|
44
|
-
|
45
|
-
context = Absmartly.create_context(context_config)
|
46
|
-
```
|
47
|
-
|
48
|
-
### Selecting A Treatment
|
49
|
-
|
50
|
-
```Ruby
|
51
|
-
treatment = context.treatment('exp_test_experiment')
|
52
|
-
|
53
|
-
if treatment.zero?
|
54
|
-
# user is in control group (variant 0)
|
55
|
-
else
|
56
|
-
# user is in treatment group
|
57
|
-
end
|
58
|
-
```
|
59
|
-
|
60
|
-
### Treatment Variables
|
61
|
-
|
62
|
-
```Ruby
|
63
|
-
default_button_color_value = 'red'
|
64
|
-
button_color = context.variable_value('button.color')
|
65
|
-
```
|
66
|
-
|
67
|
-
### Peek at Treatment Variants
|
68
|
-
|
69
|
-
Although generally not recommended, it is sometimes necessary to peek at a treatment or variable without triggering an exposure. The A/B Smartly SDK provides a `Context.peek_treatment()` method for that.
|
70
|
-
|
71
|
-
```Ruby
|
72
|
-
treatment = context.peek_treatment('exp_test_experiment')
|
73
|
-
|
74
|
-
if treatment.zero?
|
75
|
-
# user is in control group (variant 0)
|
76
|
-
else
|
77
|
-
# user is in treatment group
|
78
|
-
end
|
79
|
-
```
|
80
|
-
|
81
|
-
#### Peeking at variables
|
82
|
-
|
83
|
-
```Ruby
|
84
|
-
button_color = context.peek_variable_value('button.color', 'red')
|
85
|
-
```
|
86
|
-
|
87
|
-
### Overriding Treatment Variants
|
88
|
-
|
89
|
-
During development, for example, it is useful to force a treatment for an
|
90
|
-
experiment. This can be achieved with the `Context.set_override()` and/or `Context.set_overrides()` methods. These methods can be called before the context is ready.
|
91
|
-
|
92
|
-
```Ruby
|
93
|
-
context.set_override("exp_test_experiment", 1) # force variant 1 of treatment
|
94
|
-
|
95
|
-
context.set_overrides(
|
96
|
-
'exp_test_experiment' => 1,
|
97
|
-
'exp_another_experiment' => 0,
|
98
|
-
)
|
99
|
-
```
|
100
|
-
|
101
|
-
## Advanced
|
102
|
-
|
103
|
-
### Context Attributes
|
104
|
-
|
105
|
-
Attributes are used to pass meta-data about the user and/or the request.
|
106
|
-
They can be used later in the Web Console to create segments or audiences.
|
107
|
-
They can be set using the `context.set_attribute()` or `context.set_attributes()` methods, before or after the context is ready.
|
108
|
-
|
109
|
-
```Ruby
|
110
|
-
context.set_attribute('session_id', session_id)
|
111
|
-
context.set_attributes(
|
112
|
-
'customer_age' => 'new_customer'
|
113
|
-
)
|
114
|
-
```
|
115
|
-
|
116
|
-
### Custom Assignments
|
117
|
-
|
118
|
-
Sometimes it may be necessary to override the automatic selection of a variant. For example, if you wish to have your variant chosen based on data from an API call. This can be accomplished using the `Context.set_custom_assignment()` method.
|
119
|
-
|
120
|
-
```Ruby
|
121
|
-
chosen_variant = 1
|
122
|
-
context.set_custom_assignment("experiment_name", chosen_variant)
|
123
|
-
```
|
124
|
-
|
125
|
-
If you are running multiple experiments and need to choose different custom assignments for each one, you can do so using the `Context->setCustomAssignments()` method.
|
126
|
-
|
127
|
-
```Ruby
|
128
|
-
assignments = [
|
129
|
-
"experiment_name" => 1,
|
130
|
-
"another_experiment_name" => 0,
|
131
|
-
"a_third_experiment_name" => 2
|
132
|
-
]
|
133
|
-
|
134
|
-
context.set_custom_assignments(assignments)
|
135
|
-
```
|
136
|
-
|
137
|
-
### Publish
|
138
|
-
|
139
|
-
Sometimes it is necessary to ensure all events have been published to the A/B Smartly collector, before proceeding. You can explicitly call the `context.publish()` method.
|
140
|
-
|
141
|
-
```Ruby
|
142
|
-
context.publish
|
143
|
-
```
|
144
|
-
|
145
|
-
### Finalize
|
146
|
-
|
147
|
-
The `close()` method will ensure all events have been published to the A/B Smartly collector, like `context.publish()`, and will also "seal" the context, throwing an error if any method that could generate an event is called.
|
148
|
-
|
149
|
-
```Ruby
|
150
|
-
context.close
|
151
|
-
```
|
152
|
-
|
153
|
-
### Tracking Goals
|
154
|
-
|
155
|
-
```Ruby
|
156
|
-
context.track(
|
157
|
-
'payment',
|
158
|
-
{ item_count: 1, total_amount: 1999.99 }
|
159
|
-
)
|
160
|
-
```
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
## Development
|
165
|
-
|
166
|
-
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
167
|
-
|
168
|
-
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
169
|
-
|
170
|
-
## Contributing
|
171
|
-
|
172
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/omairazam/absmartly. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/omairazam/absmartly/blob/master/CODE_OF_CONDUCT.md).
|
173
|
-
|
174
|
-
## License
|
175
|
-
|
176
|
-
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
177
|
-
|
178
|
-
## Code of Conduct
|
179
|
-
|
180
|
-
Everyone interacting in the Absmartly project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/omairazam/absmartly/blob/master/CODE_OF_CONDUCT.md).
|