gitlab-experiment 0.2.3 → 0.2.4
Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a6284349854da93da979695a6493df95c0247616e10d5adc047d56d5dbdbf324
|
4
|
+
data.tar.gz: 1e4456945313fce9fbf03250299e3a0eb1b406035e4cce0d0559eee1fb3cfe53
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: '08d5c04e817c2679d115a845df588a4b81358e1b7ac7ddc8282a4cdaa2a878f6a4111b318fa3155671fdd05c0cb950ab076210518142e5f8a04db89e7b164104'
|
7
|
+
data.tar.gz: 118de1ae6b9995edee4058a1b756030ee6659199bf19a29cf2c1446ddb87a1a8ff29f479cf9ff20f7af47ff7706bbc97256929ed800f803fc381450bcedbf82c
|
data/README.md
CHANGED
@@ -1,5 +1,6 @@
|
|
1
|
-
GitLab Experiment
|
2
|
-
|
1
|
+
# GitLab Experiment
|
2
|
+
|
3
|
+
<img alt="experiment" src="/uploads/60990b2dbf4c0406bbf8b7f998de2dea/experiment.png" align="right" width="40%">
|
3
4
|
|
4
5
|
Here at GitLab, we run experiments as A/B/n tests and review the data the experiment generates. From that data, we determine the best performing variant and promote it as the new default code path. Or revert back to the control if no variant outperformed it.
|
5
6
|
|
@@ -13,25 +14,7 @@ When we discuss the behavior of this gem, we'll use terms like experiment, conte
|
|
13
14
|
- `candidate` defines that there's one experimental code path.
|
14
15
|
- `variant(s)` is used when more than one experimental code path exists.
|
15
16
|
|
16
|
-
Candidate and variant are the same concept, but simplify how we speak about experimental paths
|
17
|
-
|
18
|
-
## Opinionated elevator pitch
|
19
|
-
|
20
|
-
The last time I travelled I went through a process at the airport when I arrived. My passport was checked, I was asked a series of questions about my visit, and was ushered to a digital kiosk where I was prompted with a few more questions on a touch screen.
|
21
|
-
|
22
|
-
It was Iceland, and for the record, it's a beautiful place and you should visit if you haven't yet.
|
23
|
-
|
24
|
-
Anyway, at various stages of this process travellers are presented with a physical emoji button interface, and are encouraged to rate their satisfaction within the various stage.
|
25
|
-
|
26
|
-
Running an experiment could be to change some aspect of the process for only some travelers, let's say by routing them down a different hallway. At various stages of both hallways we still have the emoji interfaces that can be happily tapped or angrily jabbed in their passing.
|
27
|
-
|
28
|
-
After a while we can compare the results and can evaluate which hallway had the better overall experience, based on the ratings provided at the various stages.
|
29
|
-
|
30
|
-
This library is about keeping track of which passports we send down which hallway, so we can consistently route them down the same hallway, and to know which hallway they're rating when they do.
|
31
|
-
|
32
|
-
In this model we don’t need to know anything about the passport holder unless we decide to "ask", as we determine which hallway to send them down initially.
|
33
|
-
|
34
|
-
This library doesn't provide a system of linking passports back to their passport holders, but it doesn't explicitly make doing so impossible. Doing so is often not a relevant detail on well defined and well executed experiments.
|
17
|
+
Candidate and variant are the same concept, but simplify how we speak about experimental paths.<br clear="all">
|
35
18
|
|
36
19
|
## Installation
|
37
20
|
|
@@ -51,11 +34,11 @@ $ rails generate gitlab-experiment:install
|
|
51
34
|
|
52
35
|
For the sake of an example let's make one up. Let's run an experiment on what we render for disabling desktop notifications.
|
53
36
|
|
54
|
-
In our control (current world) we show a simple toggle interface
|
37
|
+
In our control (current world) we show a simple toggle interface labeled, "Notifications." In our experiment we want a "Turn on/off desktop notifications" button with a confirmation.
|
55
38
|
|
56
|
-
The behavior will be the same, but the interface will be different and may involve more or
|
57
|
-
|
58
|
-
|
39
|
+
The behavior will be the same, but the interface will be different and may involve more or fewer steps.
|
40
|
+
|
41
|
+
Our hypothesis is that this will make the action more clear and will help the user in making a choice about if that's what they really want to do.
|
59
42
|
|
60
43
|
We'll name our experiment `notification_toggle`. This name is prefixed based on configuration. If you've set `config.name_prefix = 'gitlab'`, the experiment name would be `gitlab_notification_toggle` elsewhere.
|
61
44
|
|
@@ -63,8 +46,8 @@ When you implement an experiment you'll need to provide a name, and a context. T
|
|
63
46
|
|
64
47
|
A context "key" represents the unique id of a context. It allows us to give the same experience between different calls to the experiment and can be used in caching.
|
65
48
|
|
66
|
-
Now in our experiment we're going to render one of two views
|
67
|
-
|
49
|
+
Now in our experiment we're going to render one of two views: the control will be our current view, and the candidate will be the new toggle button with a confirmation flow.
|
50
|
+
|
68
51
|
```ruby
|
69
52
|
class SubscriptionsController < ApplicationController
|
70
53
|
def show
|
@@ -120,15 +103,15 @@ exp.track(:clicked_button)
|
|
120
103
|
|
121
104
|
```ruby
|
122
105
|
exp = Gitlab::Experiment.new(:notification_toggle, user_id: user.id)
|
123
|
-
#
|
124
|
-
# run or track.
|
106
|
+
# Additional context may be provided to the instance (exp) but must be
|
107
|
+
# finalized before calling run or track.
|
125
108
|
exp.context(project_id: project.id) # add the project id to the context
|
126
109
|
|
127
110
|
# Define the control and candidate variant.
|
128
111
|
exp.use { render_toggle } # control
|
129
112
|
exp.try { render_button } # candidate
|
130
113
|
|
131
|
-
# Run the experiment
|
114
|
+
# Run the experiment, returning the result.
|
132
115
|
exp.run
|
133
116
|
|
134
117
|
# Track an event on the experiment we've defined.
|
@@ -154,8 +137,8 @@ class NotificationExperiment < Gitlab::Experiment
|
|
154
137
|
end
|
155
138
|
|
156
139
|
exp = NotificationExperiment.new(user_id: user.id) do |e|
|
157
|
-
# Context may be
|
158
|
-
# run or track.
|
140
|
+
# Context may be provided within the block or to the instance (exp) but must
|
141
|
+
# be finalized before calling run or track.
|
159
142
|
e.context(project_id: project.id) # add the project id to the context
|
160
143
|
end
|
161
144
|
|
@@ -187,11 +170,22 @@ Or you can set the variant within the block. This allows using unique segmentati
|
|
187
170
|
|
188
171
|
```ruby
|
189
172
|
experiment(:notification_toggle, user_id: user.id) do |e|
|
173
|
+
# Variant selection must be done before calling run or track.
|
190
174
|
e.variant(:no_interface) # set the variant
|
191
175
|
# ...
|
192
176
|
end
|
193
177
|
```
|
194
178
|
|
179
|
+
Or it can be specified in the call to run if you call it from within the block.
|
180
|
+
|
181
|
+
```ruby
|
182
|
+
experiment(:notification_toggle, user_id: user.id) do |e|
|
183
|
+
# ...
|
184
|
+
# Variant selection can be specified when calling run.
|
185
|
+
e.run(:no_interface)
|
186
|
+
end
|
187
|
+
```
|
188
|
+
|
195
189
|
</details>
|
196
190
|
|
197
191
|
### Return value
|
@@ -235,25 +229,27 @@ end
|
|
235
229
|
|
236
230
|
There are times when we need to change context while an experiment is running. We make this possible by passing the migration data to the experiment.
|
237
231
|
|
238
|
-
Take for instance, that you might be using `version: 1` in your context currently. To migrate this `version: 2`, provide the context to change using a `migrated_with` option.
|
232
|
+
Take for instance, that you might be using `version: 1` in your context currently. To migrate this to `version: 2`, provide the portion of the context you wish to change using a `migrated_with` option.
|
239
233
|
|
240
234
|
In providing the context migration data, we can resolve an experience and its events all the way back. This can also help in keeping our cache relevant.
|
241
235
|
|
242
236
|
```ruby
|
237
|
+
# Migrate just the `:version` portion of the previous context, `{ user_id: 42, version: 1 }`:
|
243
238
|
experiment(:my_experiment, user_id: 42, version: 2, migrated_with: { version: 1 })
|
244
239
|
```
|
245
240
|
|
246
|
-
You can add or remove context by providing a `migrated_from` option. This approach expects a full context replacement -- e.
|
241
|
+
You can add or remove context by providing a `migrated_from` option. This approach expects a full context replacement -- i.e. what it was before you added or removed the new context key.
|
247
242
|
|
248
243
|
If you wanted to introduce a `version` to your context, provide the full previous context.
|
249
|
-
|
244
|
+
|
250
245
|
```ruby
|
246
|
+
# Migrate the full context from `{ user_id: 42 }` to `{ user_id: 42, version: 1 }`:
|
251
247
|
experiment(:my_experiment, user_id: 42, version: 1, migrated_from: { user_id: 42 })
|
252
248
|
```
|
253
249
|
|
254
|
-
This can impact an experience if you
|
250
|
+
This can impact an experience if you:
|
255
251
|
|
256
|
-
1. implemented the concept of migrations in your variant resolver
|
252
|
+
1. haven't implemented the concept of migrations in your variant resolver
|
257
253
|
1. haven't enabled a reasonable caching mechanism
|
258
254
|
|
259
255
|
### When there isn't a user (cookies)
|
@@ -272,20 +268,33 @@ You'll need to provide the `request` as an option to the experiment if it's outs
|
|
272
268
|
experiment(:my_experiment, user_id: user&.id, request: request)
|
273
269
|
```
|
274
270
|
|
275
|
-
The cookie isn't set if the identifying key isn't present in the context.
|
271
|
+
The cookie isn't set if the identifying key isn't present at all in the context. Using the default identifying key, when there is no `user_id` key provided, the cookie will not be set.
|
272
|
+
|
273
|
+
```ruby
|
274
|
+
# no user_id context key is present, so no cookie is set
|
275
|
+
experiment(:my_experiment, project_id: @project.id)
|
276
276
|
|
277
|
-
|
277
|
+
# user_id context key is present, but is set to nil, so the cookie is set & used instead
|
278
|
+
experiment(:my_experiment, user_id: nil, project_id: @project.id)
|
279
|
+
|
280
|
+
# user_id context key is present and set to a value, so no cookie is set
|
281
|
+
experiment(:my_experiment, user_id: @user.id, project_id: @project.id)
|
282
|
+
```
|
283
|
+
|
284
|
+
For edge cases, you can pass the cookie through by assigning it yourself -- e.g. `user_id: request.cookie_jar.signed['my_experiment_id']`. The cookie name is the full experiment name (including any configured prefix) with `_id` appended -- e.g. `gitlab_notification_toggle_id` for the `:notification_toggle` experiment key with a configured prefix of `gitlab`.
|
278
285
|
|
279
286
|
## Configuration
|
280
287
|
|
281
288
|
This gem needs to be configured before being used in a meaningful way.
|
282
289
|
|
283
|
-
The default configuration will always render the control
|
290
|
+
The default configuration will always render the control, so it's important to configure your own logic for resolving variants.
|
284
291
|
|
285
|
-
Yes, the most important aspect of the gem
|
292
|
+
Yes, the most important aspect of the gem -- that of determining which variant to render and when -- is up to you. Consider using [Unleash](https://github.com/Unleash/unleash-client-ruby) or [Flipper](https://github.com/jnunemaker/flipper) for this.
|
286
293
|
|
287
294
|
```ruby
|
288
295
|
Gitlab::Experiment.configure do |config|
|
296
|
+
# The block here is evaluated within the scope of the experiment instance,
|
297
|
+
# which is why we are able to access things like name and context.
|
289
298
|
config.variant_resolver = lambda do |requested_variant|
|
290
299
|
# Return the requested variant if a specific one has been provided in code.
|
291
300
|
return requested_variant unless requested_variant.nil?
|
@@ -304,14 +313,14 @@ More examples for configuration are available in the provided [rails initializer
|
|
304
313
|
|
305
314
|
This library doesn't attempt to provide any logic for the client layer.
|
306
315
|
|
307
|
-
Instead it allows you to do this yourself in configuration. Using Gon to publish your experiment information to the client layer is pretty simple.
|
316
|
+
Instead it allows you to do this yourself in configuration. Using [Gon](https://github.com/gazay/gon) to publish your experiment information to the client layer is pretty simple.
|
308
317
|
|
309
318
|
```ruby
|
310
319
|
Gitlab::Experiment.configure do |config|
|
311
320
|
config.publishing_behavior = lambda do |_result|
|
312
321
|
# Push the experiment knowledge into the front end. The signature contains
|
313
322
|
# the context key, and the variant that has been determined.
|
314
|
-
Gon.push(experiment: { name => signature })
|
323
|
+
Gon.push({ experiment: { name => signature } }, true)
|
315
324
|
end
|
316
325
|
end
|
317
326
|
```
|
@@ -322,6 +331,8 @@ In the client you can now access `window.gon.experiment.notificationToggle`.
|
|
322
331
|
|
323
332
|
Caching can be enabled in configuration, and is implemented towards the `Rails.cache` / `ActiveSupport::Cache::Store` interface. When you enable caching, any variant resolution will be cached. Migrating the cache through context migrations is handled automatically, and this helps ensure an experiment experience remains consistent.
|
324
333
|
|
334
|
+
It's important to understand that using caching can drastically change or override your rollout strategy logic.
|
335
|
+
|
325
336
|
```ruby
|
326
337
|
Gitlab::Experiment.configure do |config|
|
327
338
|
config.cache = Rails.cache
|
@@ -330,11 +341,11 @@ end
|
|
330
341
|
|
331
342
|
## Tracking, anonymity and GDPR
|
332
343
|
|
333
|
-
We generally try not to track things like user identifying values in our experimentation. What we can and do track is the "experiment experience" (a.k.a the context key).
|
344
|
+
We generally try not to track things like user identifying values in our experimentation. What we can and do track is the "experiment experience" (a.k.a. the context key).
|
334
345
|
|
335
346
|
We generate this key from the context passed to the experiment. This allows creating funnels without exposing any user information.
|
336
347
|
|
337
|
-
This library attempts to be non
|
348
|
+
This library attempts to be non-user-centric, in that a context can contain things like a user or a project.
|
338
349
|
|
339
350
|
If you only include a user, that user would get the same experience across every project they view. If you only include the project, every user who views that project would get the same experience.
|
340
351
|
|
@@ -26,7 +26,7 @@ Gitlab::Experiment.configure do |config|
|
|
26
26
|
#
|
27
27
|
# requested_variant || variant_names.first || 'control'
|
28
28
|
|
29
|
-
# Using
|
29
|
+
# Using Unleash to determine the variant:
|
30
30
|
#
|
31
31
|
# fallback = Unleash::Variant.new(name: requested_variant || 'control', enabled: true)
|
32
32
|
# Unleash.get_variant(name, context.value, fallback)
|
@@ -40,19 +40,17 @@ Gitlab::Experiment.configure do |config|
|
|
40
40
|
# Tracking behavior can be implemented to link an event to an experiment.
|
41
41
|
#
|
42
42
|
# Similar to the variant_resolver, this is called within the scope of the
|
43
|
-
# experiment instance and so can access any methods on the experiment
|
44
|
-
#
|
45
|
-
#
|
43
|
+
# experiment instance and so can access any methods on the experiment,
|
44
|
+
# such as name and signature.
|
46
45
|
config.tracking_behavior = lambda do |event, args|
|
47
46
|
# An example of using a generic logger to track events:
|
48
47
|
config.logger.info "Gitlab::Experiment[#{name}] #{event}: #{args.merge(signature: signature)}"
|
49
48
|
|
50
|
-
# Using something like snowplow to track events:
|
49
|
+
# Using something like snowplow to track events (in gitlab):
|
51
50
|
#
|
52
51
|
# Gitlab::Tracking.event(name, event, **args.merge(
|
53
52
|
# context: (args[:context] || []) << SnowplowTracker::SelfDescribingJson.new(
|
54
|
-
# 'iglu:com.gitlab/gitlab_experiment/jsonschema/0-2-0',
|
55
|
-
# signature: signature
|
53
|
+
# 'iglu:com.gitlab/gitlab_experiment/jsonschema/0-2-0', signature
|
56
54
|
# )
|
57
55
|
# ))
|
58
56
|
end
|
@@ -69,7 +67,7 @@ Gitlab::Experiment.configure do |config|
|
|
69
67
|
# Push the experiment knowledge into the front end. The signature contains
|
70
68
|
# the context key, and the variant that has been determined.
|
71
69
|
#
|
72
|
-
# Gon.push(experiment: { name => signature })
|
70
|
+
# Gon.push({ experiment: { name => signature } }, true)
|
73
71
|
|
74
72
|
# Log using our logging system, so the result (which can be large) can be
|
75
73
|
# reviewed later if we want to.
|
data/lib/gitlab/experiment.rb
CHANGED
metadata
CHANGED
@@ -1,35 +1,35 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: gitlab-experiment
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- GitLab
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-10-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: scientist
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - ">="
|
18
|
-
- !ruby/object:Gem::Version
|
19
|
-
version: 1.5.0
|
20
17
|
- - "~>"
|
21
18
|
- !ruby/object:Gem::Version
|
22
19
|
version: '1.5'
|
20
|
+
- - ">="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 1.5.0
|
23
23
|
type: :runtime
|
24
24
|
prerelease: false
|
25
25
|
version_requirements: !ruby/object:Gem::Requirement
|
26
26
|
requirements:
|
27
|
-
- - ">="
|
28
|
-
- !ruby/object:Gem::Version
|
29
|
-
version: 1.5.0
|
30
27
|
- - "~>"
|
31
28
|
- !ruby/object:Gem::Version
|
32
29
|
version: '1.5'
|
30
|
+
- - ">="
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 1.5.0
|
33
33
|
description:
|
34
34
|
email:
|
35
35
|
- gitlab_rubygems@gitlab.com
|
@@ -69,7 +69,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
69
69
|
- !ruby/object:Gem::Version
|
70
70
|
version: '0'
|
71
71
|
requirements: []
|
72
|
-
rubygems_version: 3.
|
72
|
+
rubygems_version: 3.1.4
|
73
73
|
signing_key:
|
74
74
|
specification_version: 4
|
75
75
|
summary: GitLab experiment library built on top of scientist.
|