gitlab-experiment 0.2.2 → 0.4.0

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: '08b63f640a90ed463a4b2bf58c2e0b1ef041bbd8f80c2d43bf88960ba9aac2a9'
4
- data.tar.gz: 01a8a227c467d097b9dbb9dc9644a1d70012fb3989f2e189e3e63a41e7f39cfd
3
+ metadata.gz: 8524dff7f908e481923e3131f3dd67c34f426e31ed10171896ea10a209e0b74a
4
+ data.tar.gz: 151f2e7f7bbf9692350c02f46f0c4c8cfa6f9c7ec05fdcb0fda50e34324c06fb
5
5
  SHA512:
6
- metadata.gz: b161004e60bbbdb724aeb685013ce4b787d0ced9526bdd241e52be327adbc3461ea60eb3a279f25b4f89d46ee37825ae1d571501d0196a8505a9ff4eb8b3d214
7
- data.tar.gz: 8730e755e09ec85fa88b2721af42ec0919d6534449a91d82bc0d2f6ae0055f70e4ad783d045d20dc09c44496c63465656abfe33d6759e3956a153d793f57a2ab
6
+ metadata.gz: 3f23698aabf3968c77f49cdb924125879663670c12d22c390a47f7c791687f87fb1211adf432777f66012e3be1db2e90d5c33a1dc0a156332bb8926938c4e587
7
+ data.tar.gz: 51973494136edef02672c9086970fec2ecb1fb3a6716edb10d1cfe758172f8d256ecd6ef1b5cefdd50133898637e022af527fc7e20df2212b1f7210db2dd087e
data/README.md CHANGED
@@ -1,73 +1,82 @@
1
- GitLab Experiment
2
- =================
1
+ # GitLab Experiment
3
2
 
4
- Here at GitLab, experiments are run as A/B/n tests and are evaluated by the data the experiment generates. The team reviews the data, determines the variant that performed most effectively, and promotes that variant as the new default code path (or reverts back to the control).
3
+ <img alt="experiment" src="/uploads/60990b2dbf4c0406bbf8b7f998de2dea/experiment.png" align="right" width="40%">
5
4
 
6
- When we discuss the behavior of this gem, we'll use terms like experiment, control, candidate, and variant. It's worth defining these terms so they're more understood.
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.
6
+
7
+ This library provides a clean and elegant DSL (domain specific language) to define, run, and track your GitLab experiment.
8
+
9
+ When we discuss the behavior of this gem, we'll use terms like experiment, context, control, candidate, and variant. It's worth defining these terms so they're more understood.
7
10
 
8
11
  - `experiment` is any deviation of code paths we want to run sometimes and not others.
12
+ - `context` is used to identify a consistent experience we'll provide in an experiment.
9
13
  - `control` is the default, or "original" code path.
10
- - `candidate` is used if there's one experimental code path.
14
+ - `candidate` defines that there's one experimental code path.
11
15
  - `variant(s)` is used when more than one experimental code path exists.
12
16
 
13
- Candidate and variant are referencing the same concept, but can simplify how we speak about the available code paths of a given experiment.
17
+ Candidate and variant are the same concept, but simplify how we speak about experimental paths.<br clear="all">
14
18
 
15
19
  ## Installation
16
20
 
17
- Add the gem to your Gemfile and then bundle install.
21
+ Add the gem to your Gemfile and then `bundle install`.
18
22
 
19
23
  ```ruby
20
24
  gem 'gitlab-experiment'
21
25
  ```
22
26
 
23
- If you're using Rails, you can install an initializer that provides basic configuration by running the install generator.
27
+ If you're using Rails, you can install the initializer. It provides basic configuration and documentation.
24
28
 
25
29
  ```shell
26
- $ rails generate gitlab-experiment:install
30
+ $ rails generate gitlab:experiment:install
27
31
  ```
28
32
 
29
33
  ## Implementing an experiment
30
34
 
31
- For the sake of our example, let's say we want to run an experiment for the interface we render for subscription cancellation.
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.
36
+
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.
38
+
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 in making a choice about if that's what the user really wants to do.
32
42
 
33
- In our control (current world) we show a simple toggle interface that reads "Auto-renew", and in our experiment candidate we want to show a "Cancel subscription" button with a confirmation dialog. Ultimately the behavior will be the same, but the interface will be considerably different and may involve more or less steps.
34
-
35
- We hypothesize that making it more clear what the action is will help people in making a choice about taking that action or not.
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.
36
44
 
37
- We'll name our experiment `subscription_cancellation`. It's important to understand this name may be prefixed based on your configuration, so if you've set `config.name_prefix = "gitlab"` it would be `gitlab_subscription_cancellation`.
45
+ When you implement an experiment you'll need to provide a name, and a context. The name can show up in tracking calls, and potentially other aspects. The context determines the variant assigned, and should be consistent between calls. We'll discuss migrating context in later examples.
38
46
 
39
- When you implement and run an experiment you'll need to provide the name you've given it, and a context hash. The context hash is used to determine which variant to provide, and is expected to be consistent between calls to your experiment, so a context hash key can be generated and cached -- which is used to consistently render the same experience given the same context.
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.
48
+
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.
40
50
 
41
- In our experiment we're going to render one of two views. Control will be our current one, and candidate will be the new view with a cancel button and javascript confirm call.
42
-
43
51
  ```ruby
44
52
  class SubscriptionsController < ApplicationController
45
- include Gitlab::Experiment::Dsl
46
-
47
53
  def show
48
- experiment(:subscription_cancellation, user_id: user.id) do |e|
49
- e.use { render_toggle_button } # control
50
- e.try { render_cancel_button } # candidate
54
+ experiment(:notification_toggle, actor: user) do |e|
55
+ e.use { render_toggle } # control
56
+ e.try { render_button } # candidate
51
57
  end
52
58
  end
53
59
  end
54
60
  ```
55
61
 
56
- You can also provide different variant names if you want to. In our case we might want to include the confirmation dialog step or not, to see which one behaves better within our wider experiment.
62
+ You can define the experiment using simple control/candidate paths, or provide named variants.
63
+
64
+ Handling multi-variant experiments is up to the configuration you provide around resolving variants. But in our example we may want to try with and without the confirmation. We can run any number of variations in our experiments this way.
57
65
 
58
66
  ```ruby
59
- experiment(:subscription_cancellation, user_id: user.id) do |e|
60
- e.use { render_toggle_button } # control
61
- e.try(:variant_one) { render_cancel_button(confirmation: true) }
62
- e.try(:variant_two) { render_cancel_button(confirmation: false) }
67
+ experiment(:notification_toggle, actor: user) do |e|
68
+ e.use { render_toggle } # control
69
+ e.try(:variant_one) { render_button(confirmation: true) }
70
+ e.try(:variant_two) { render_button(confirmation: false) }
63
71
  end
64
72
  ```
65
73
 
66
- Later, and elsewhere in code you can use the same `experiment` call to track events on the experiment. You can use this consistent event concept to build out funnels from the data that's being tracked. The important detail here is to use the same context between your calls to `experiment`. If the context is the same, we're able to consistently track the event in a way that associates it to which variant is being presented.
74
+ Understanding how an experiment can change behavior is important in evaluating its performance.
75
+
76
+ To this end, we track events that are important by calling the same experiment elsewhere in code. By using the same context, you'll have consistent behavior and the ability to track events to it.
67
77
 
68
78
  ```ruby
69
- exp = experiment(:subscription_cancellation, user_id: user.id)
70
- exp.track('clicked_button')
79
+ experiment(:notification_toggle, actor: user).track(:clicked_button)
71
80
  ```
72
81
 
73
82
  <details>
@@ -76,14 +85,14 @@ exp.track('clicked_button')
76
85
  ### Class level interface using `.run`
77
86
 
78
87
  ```ruby
79
- exp = Gitlab::Experiment.run(:subscription_cancellation, user_id: user.id) do |e|
80
- # Context may be passed in the block, but must be finalized before calling
88
+ exp = Gitlab::Experiment.run(:notification_toggle, actor: user) do |e|
89
+ # Context may be passed in the block, but must be finalized before calling
81
90
  # run or track.
82
- e.context(project_id: project.id) # add the project id to the context
83
- e.variant(:candidate) # always run the candidate
91
+ e.context(project: project) # add the project to the context
92
+
84
93
  # Define the control and candidate variant.
85
- e.use { toggle_button_interface } # control
86
- e.try { cancel_button_interface } # candidate
94
+ e.use { render_toggle } # control
95
+ e.try { render_button } # candidate
87
96
  end
88
97
 
89
98
  # Track an event on the experiment we've defined.
@@ -93,16 +102,16 @@ exp.track(:clicked_button)
93
102
  ### Instance level interface
94
103
 
95
104
  ```ruby
96
- exp = Gitlab::Experiment.new(:subscription_cancellation, user_id: user.id)
97
- # Context may be passed in the block, but must be finalized before calling
98
- # run or track.
99
- exp.context(project_id: project.id) # add the project id to the context
105
+ exp = Gitlab::Experiment.new(:notification_toggle, actor: user)
106
+ # Additional context may be provided to the instance (exp) but must be
107
+ # finalized before calling run or track.
108
+ exp.context(project: project) # add the project id to the context
100
109
 
101
110
  # Define the control and candidate variant.
102
- exp.use { toggle_button_interface } # control
103
- exp.try { cancel_button_interface } # candidate
111
+ exp.use { render_toggle } # control
112
+ exp.try { render_button } # candidate
104
113
 
105
- # Run the experiment -- returning the result.
114
+ # Run the experiment, returning the result.
106
115
  exp.run
107
116
 
108
117
  # Track an event on the experiment we've defined.
@@ -112,25 +121,25 @@ exp.track(:clicked_button)
112
121
  </details>
113
122
 
114
123
  <details>
115
- <summary>You can define use custom classes...</summary>
124
+ <summary>You can define and use custom classes...</summary>
116
125
 
117
126
  ### Custom class
118
127
 
119
128
  ```ruby
120
- class CancellationExperiment < Gitlab::Experiment
129
+ class NotificationExperiment < Gitlab::Experiment
121
130
  def initialize(variant_name = nil, **context, &block)
122
- super(:subscription_cancellation, variant_name, **context, &block)
131
+ super(:notification_toggle, variant_name, **context, &block)
123
132
 
124
133
  # Define the control and candidate variant.
125
- use { toggle_button_interface } # control
126
- try { cancel_button_interface } # candidate
134
+ use { render_toggle } # control
135
+ try { render_button } # candidate
127
136
  end
128
137
  end
129
138
 
130
- exp = CancellationExperiment.new(user_id: user.id) do |e|
131
- # Context may be passed in the block, but must be finalized before calling
132
- # run or track.
133
- e.context(project_id: project.id) # add the project id to the context
139
+ exp = NotificationExperiment.new(actor: user) do |e|
140
+ # Context may be provided within the block or to the instance (exp) but must
141
+ # be finalized before calling run or track.
142
+ e.context(project: project) # add the project id to the context
134
143
  end
135
144
 
136
145
  # Run the experiment -- returning the result.
@@ -143,76 +152,203 @@ exp.track(:clicked_button)
143
152
  </details>
144
153
 
145
154
  <details>
146
- <summary>You can also hard specify the variant to use...</summary>
155
+ <summary>You can also specify the variant to use...</summary>
147
156
 
148
- ### Specifying which variant to use
157
+ ### Specifying variant
149
158
 
150
- This should generally be discouraged, as it can change the experience users have during rollout, and may confuse generating reports from the tracking calls. It is possible however, and may be useful if you understand the implications.
159
+ You can hardcode the variant if you want. It's important to know what this might do to your data during rollout, so use this with consideration.
151
160
 
152
161
  ```ruby
153
- experiment(:subscription_cancellation, :no_interface, user_id: user.id) do |e|
154
- e.use { toggle_button_interface } # control
155
- e.try { cancel_button_interface } # candidate
162
+ experiment(:notification_toggle, :no_interface, actor: user) do |e|
163
+ e.use { render_toggle } # control
164
+ e.try { render_button } # candidate
156
165
  e.try(:no_interface) { no_interface! } # variant
157
166
  end
158
167
  ```
159
168
 
160
- Or you can set the variant within the block -- potentially using some unique or different segmentation strategy that you've written specifically for the experiment at hand.
169
+ Or you can set the variant within the block. This allows using unique segmentation logic or variant resolution if you need it.
161
170
 
162
171
  ```ruby
163
- experiment(:subscription_cancellation, user_id: user.id) do |e|
172
+ experiment(:notification_toggle, actor: user) do |e|
173
+ # Variant selection must be done before calling run or track.
164
174
  e.variant(:no_interface) # set the variant
165
175
  # ...
166
176
  end
167
177
  ```
168
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, actor: user) do |e|
183
+ # ...
184
+ # Variant selection can be specified when calling run.
185
+ e.run(:no_interface)
186
+ end
187
+ ```
188
+
169
189
  </details>
170
190
 
171
- The `experiment` method, and the underlying `Gitlab::Experiment` instance is an implementation on top of [Scientist](https://github.com/github/scientist). Generally speaking you can use the DSL that Scientist defines, but for experiments we use `experiment` instead of `science`, and specify the variant on initialization (or via `#variant` and not in the call to `#run`. The interface is otherwise the same, even though not every aspect of Scientist makes sense for experiments.
191
+ ### Return value
192
+
193
+ By default the return value is a `Gitlab::Experiment` instance. In simple cases you may want only the results of the experiment though. You can call `run` within the block to get the return value of the assigned variant.
194
+
195
+ ```ruby
196
+ experiment(:notification_toggle) do |e|
197
+ e.use { 'A' }
198
+ e.try { 'B' }
199
+ e.run
200
+ end # => 'A'
201
+ ```
202
+
203
+ ### Including the DSL
204
+
205
+ By default, `Gitlab::Experiment` injects itself into the controller and view layers. This exposes the `experiment` method application wide in those layers.
206
+
207
+ Some experiments may extend outside of those layers, so you may want to include it elsewhere. For instance in a mailer, service object, background job, or similar.
208
+
209
+ Note: In a lot of these contexts you may not have a reference to the request (unless you pass it in, or provide access to it) which may be needed if you want to enable cookie behaviors and track that through to user conversion.
210
+
211
+ ```ruby
212
+ class WelcomeMailer < ApplicationMailer
213
+ include Gitlab::Experiment::Dsl # include the `experiment` method
214
+
215
+ def welcome
216
+ @user = params[:user]
217
+
218
+ ex = experiment(:project_suggestions, actor: @user) do |e|
219
+ e.use { 'welcome' }
220
+ e.try { 'welcome_with_project_suggestions' }
221
+ end
222
+
223
+ mail(to: @user.email, subject: 'Welcome!', template: ex.run)
224
+ end
225
+ end
226
+ ```
172
227
 
173
228
  ### Context migrations
174
229
 
175
- There are times when we may need to change something that we're providing in the context while an experiment is running, or even add or remove contexts. We make this possible by passing the migration data to the experiment.
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.
231
+
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.
233
+
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.
235
+
236
+ ```ruby
237
+ # Migrate just the `:version` portion of the previous context, `{ actor: project, version: 1 }`:
238
+ experiment(:my_experiment, actor: project, version: 2, migrated_with: { version: 1 })
239
+ ```
240
+
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.
176
242
 
177
- Take for instance, that you might be using `version: 1` in your context currently. If you want to migrate this to `version: 2`, you just need to provide the context that you want to change using a `migrated_with` option. In doing this, a given experience (variant rendered/events tracked) can be resolved back through any number of migrations, and can be cached/resolved by using the context key value that was already already generated.
243
+ If you wanted to introduce a `version` to your context, provide the full previous context.
178
244
 
179
245
  ```ruby
180
- experiment(:my_experiment, user_id: 42, version: 2, migrated_with: { version: 1 })
246
+ # Migrate the full context from `{ actor: project }` to `{ actor: project, version: 1 }`:
247
+ experiment(:my_experiment, actor: project, version: 1, migrated_from: { actor: project })
181
248
  ```
182
249
 
183
- If you're adding or removing a new a value from the context, you'll need to use `migrated_from`, which expects a full context replacement -- e.g. what it was before you added or removed the new context key. For instance, if you wanted to introduce the `version: 1` concept to your context, you would need to use something like the following.
184
-
250
+ This can impact an experience if you:
251
+
252
+ 1. haven't implemented the concept of migrations in your variant resolver
253
+ 1. haven't enabled a reasonable caching mechanism
254
+
255
+ ### When there isn't an actor (cookie fallback)
256
+
257
+ When there isn't an identifying key in the context (this is `actor` by default), we fall back to cookies to provide a consistent experience for the client viewing them.
258
+
259
+ Once we assign a certain variant to a context, we need to always provide the same experience. We achieve this by setting a cookie for the experiment in question, but only when needed.
260
+
261
+ This cookie is a temporary, randomized uuid and isn't associated with a user. When we can finally provide an actor, the context is auto migrated from the cookie to that actor.
262
+
263
+ To read and write cookies, we provide the `request` from within the controller and views. The cookie migration will happen automatically if the experiment is within those layers.
264
+
265
+ You'll need to provide the `request` as an option to the experiment if it's outside of the controller and views.
266
+
185
267
  ```ruby
186
- experiment(:my_experiment, user_id: 42, version: 1, migrated_from: { user_id: 42 })
268
+ experiment(:my_experiment, actor: user, request: request)
187
269
  ```
188
270
 
189
- It's important to understand that this can bucket a user in a new experience (depending on the logic in determining in the variant), so you should investigate how this might impact your experiment before using it and that your experiment is implemented in a way that supports migrations.
271
+ The cookie isn't set if the `actor` key isn't present at all in the context. Meaning that when no `actor` key is provided, the cookie will not be set.
272
+
273
+ ```ruby
274
+ # actor is not present, so no cookie is set
275
+ experiment(:my_experiment, project: project)
276
+
277
+ # actor is present and is nil, so the cookie is set and used
278
+ experiment(:my_experiment, actor: nil, project: project)
279
+
280
+ # actor is present and set to a value, so no cookie is set
281
+ experiment(:my_experiment, actor: user, project: project)
282
+ ```
283
+
284
+ For edge cases, you can pass the cookie through by assigning it yourself -- e.g. `actor: request.cookie_jar.signed['my_experiment_actor']`. The cookie name is the full experiment name (including any configured prefix) with `_actor` appended -- e.g. `gitlab_notification_toggle_actor` for the `:notification_toggle` experiment key with a configured prefix of `gitlab`.
285
+
286
+ ## Configuration
287
+
288
+ This gem needs to be configured before being used in a meaningful way.
289
+
290
+ The default configuration will always render the control, so it's important to configure your own logic for resolving variants.
291
+
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.
293
+
294
+ ```ruby
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.
298
+ config.variant_resolver = lambda do |requested_variant|
299
+ # Return the requested variant if a specific one has been provided in code.
300
+ return requested_variant unless requested_variant.nil?
301
+
302
+ # Ask Unleash to determine the variant, given the context we've built,
303
+ # using the control as the fallback.
304
+ fallback = Unleash::Variant.new(name: 'control', enabled: true)
305
+ UNLEASH.get_variant(name, context.value, fallback)
306
+ end
307
+ end
308
+ ```
190
309
 
191
- ### When there isn't a user (cookies)
310
+ More examples for configuration are available in the provided [rails initializer](lib/generators/gitlab/experiment/install/templates/initializer.rb).
192
311
 
193
- When there isn't a user we typically have to fall back to another concept to provide a consistent experience. What this means, is that once we assign someone a certain variant, we want to always give them the same experience, and we do this by setting a cookie for the experiment we're currently checking on. This cookie value is a random uuid, which we auto migrate to a user_id when our experiment context is finally provided that information. This means that you really only need to provide the request as an option if you're wanting to have an experiment that flows from not-signed-in (or not registered) users to eventually signed-in users.
312
+ ### Client layer / JavaScript
194
313
 
195
- This is considered a temporary cookie value, and isn't used for tracking purposes other than to give a given "user" (in this case it's actually the browser), a consistent experience after we've assigned one.
314
+ This library doesn't attempt to provide any logic for the client layer.
196
315
 
197
- To read and write cookies, we allow for passing the `request` as an option. This allows us to read, write, and even clean up a cookie when appropriate. We've provided this by default in the ActionController interface though, so this is primarily useful if you're trying to have an experiment span controller and model layers.
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.
198
317
 
199
318
  ```ruby
200
- experiment(:subscription_cancellation, user_id: user.id, request: request) do |e|
201
- e.use { render_toggle_button } # control
202
- e.try { render_cancel_button } # candidate
319
+ Gitlab::Experiment.configure do |config|
320
+ config.publishing_behavior = lambda do |_result|
321
+ # Push the experiment knowledge into the front end. The signature contains
322
+ # the context key, and the variant that has been determined.
323
+ Gon.push({ experiment: { name => signature } }, true)
324
+ end
203
325
  end
204
- ```
326
+ ```
205
327
 
206
- If needed, for edge cases (like in a background job), you can manually pass in the cookie value. Passing it in as `user_id: request.cookie_jar.signed['subscription_cancellation_id']`. The cookie name is the experiment name (prefixed if configured to be) with `_id` appended.
328
+ In the client you can now access `window.gon.experiment.notificationToggle`.
207
329
 
208
- ## Configuration
330
+ ### Caching
209
331
 
210
- The gem is meant to be configured before being used. The default configuration will always render the control behavior, so it's important to implement your own logic for this or you will always get the control of an experiment.
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.
211
333
 
212
- The most important aspect of the gem, determining which variant to render and when, is up to you, and you may want to consider using [Unleash](https://github.com/Unleash/unleash-client-ruby) (which has the concept of multi-variants built in), or [Flipper](https://github.com/jnunemaker/flipper) in helping with this.
334
+ It's important to understand that using caching can drastically change or override your rollout strategy logic.
213
335
 
214
- Examples for configuration are available in the provided install generator, or in the source code configuration.rb file itself.
336
+ ```ruby
337
+ Gitlab::Experiment.configure do |config|
338
+ config.cache = Rails.cache
339
+ end
340
+ ```
215
341
 
216
342
  ## Tracking, anonymity and GDPR
217
343
 
218
- We intentionally don't, and shouldn't, track things like user ids on our experiments. What we can and do track is what we consider an "experiment experience" key. This key is generated from the context we pass to the experiment and has the concept of migrating through different versions of context. If we consistently pass the same context to an experiment, we're able to consistently track events generated in that experience. A context can contain things like user, or project -- so, if you only included a user in the context that user would get the same experience across all projects they view, but if you include the currently viewed project in the context the user would potentially have a different experience on each of their projects. Each can be desirable given the objectives of the experiment.
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).
345
+
346
+ We generate this key from the context passed to the experiment. This allows creating funnels without exposing any user information.
347
+
348
+ This library attempts to be non-user-centric, in that a context can contain things like a user or a project.
349
+
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.
351
+
352
+ Each of these approaches could be desirable given the objectives of your experiment.
353
+
354
+ ### Make code not war
@@ -0,0 +1,17 @@
1
+ Description:
2
+ Stubs out a new experiment and its variants. Pass the experiment name,
3
+ either CamelCased or under_scored, and a list of variants as arguments.
4
+
5
+ To create an experiment within a module, specify the experiment name as a
6
+ path like 'parent_module/experiment_name'.
7
+
8
+ This generates an experiment class in app/experiments and invokes feature
9
+ flag, and test framework generators.
10
+
11
+ Example:
12
+ `rails generate gitlab:experiment NullHypothesis control candidate alt_variant`
13
+
14
+ NullHypothesis experiment with default variants.
15
+ Experiment: app/experiments/null_hypothesis_experiment.rb
16
+ Feature Flag: config/feature_flags/experiment/null_hypothesis.yaml
17
+ Test: test/experiments/null_hypothesis_experiment_test.rb