Skip to content

Commit

Permalink
Add optional check for experiment cookie to persist previously seen v…
Browse files Browse the repository at this point in the history
…ariations (Fixes #93)
  • Loading branch information
alexgibson committed Jul 31, 2024
1 parent c6a3406 commit 6a93aa6
Show file tree
Hide file tree
Showing 19 changed files with 357 additions and 37 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# HEAD

- **js:** Add optional check for experiment cookie to persist previously seen variations (#93).

# 3.0.0

- **js:** Remove use of cookies (#56).
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ Most of the content experiments on [mozilla.org](https://www.mozilla.org) simply
In contrast to third-party options (e.g. [Optimizely](https://www.optimizely.com/)), Traffic Cop offers:

1. **Security** — Many third-party options require loading JS from their site, which is a potential [XSS](https://en.wikipedia.org/wiki/Cross-site_scripting) vector. Traffic Cop can (and should) be served from your site/CDN.
2. **Privacy** - Traffic Cop does not use cookies of any kind (unlike most third-party solutions), nor does it store or send any experiment data itself (that part is up to you and your consent management solution).
2. **Privacy** - Traffic Cop does not set cookies of any kind (unlike most third-party solutions), nor does it store or send any experiment data itself (that part is up to you and your consent management solution).
3. **Performance** — Traffic Cop is light and has zero dependencies, resulting in less than 2KB of JS when minified. (In our experience, Optimizely's JS bundle was regularly above 200KB.)
4. **Your workflow** — Traffic Cop offers great flexibility in when and how you write and load variation code. No need to type jQuery in a text box on a third-party web page.
5. **Savings** — No need to pay for a third-party service.
Expand Down
11 changes: 11 additions & 0 deletions demo/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions demo/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"author": "Mozilla",
"license": "MPL-2.0",
"dependencies": {
"@mozmeao/cookie-helper": "^1.1.0",
"express": "^4.19.2",
"nunjucks": "^3.2.4",
"path": "^0.12.7",
Expand Down
1 change: 1 addition & 0 deletions demo/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ app.use(
'/uncompressed-src',
express.static(path.join(__dirname, '/../../src/'))
);
app.use('/libs', express.static(path.join(__dirname, '/../libs/')));

nunjucks.configure('src/views', {
autoescape: true,
Expand Down
8 changes: 8 additions & 0 deletions demo/src/libs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
const CookieHelper = require('@mozmeao/cookie-helper');

// create namespace
if (typeof window.Mozilla === 'undefined') {
window.Mozilla = {};
}

window.Mozilla.Cookies = CookieHelper;
23 changes: 21 additions & 2 deletions demo/src/public/experiment-page1.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,25 @@
(function () {
'use strict';

var eddie = new window.TrafficCop({
function setVariationCookie(exp) {
// set cookie to expire in 24 hours
var date = new Date();
date.setTime(date.getTime() + 1 * 24 * 60 * 60 * 1000);
var expires = date.toUTCString();

window.Mozilla.Cookies.setItem(
exp.id,
exp.chosenVariation,
expires,
undefined,
undefined,
false,
'lax'
);
}

var cop = new window.TrafficCop({
id: 'my-experiment-id-1',
variations: {
'v=1': 40.5,
'v=2': 20.3,
Expand All @@ -11,6 +29,7 @@
'v=6': 0.1
}
});
cop.init();

eddie.init();
setVariationCookie(cop);
})();
20 changes: 20 additions & 0 deletions demo/src/public/experiment-page2.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,23 @@
c: 30
};

function setVariationCookie(exp) {
// set cookie to expire in 24 hours
var date = new Date();
date.setTime(date.getTime() + 1 * 24 * 60 * 60 * 1000);
var expires = date.toUTCString();

window.Mozilla.Cookies.setItem(
exp.id,
exp.chosenVariation,
expires,
undefined,
undefined,
false,
'lax'
);
}

function handleVariation(variation) {
// wait until DOM is ready to be manipulated...
domReady(function () {
Expand All @@ -28,9 +45,12 @@
}

var wiggum = new window.TrafficCop({
id: 'my-experiment-id-2',
customCallback: handleVariation,
variations: variants
});

wiggum.init();

setVariationCookie(wiggum);
})();
20 changes: 20 additions & 0 deletions demo/src/public/experiment-page3-customcallback.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,23 @@
b: 40
};

function setVariationCookie(exp) {
// set cookie to expire in 24 hours
var date = new Date();
date.setTime(date.getTime() + 1 * 24 * 60 * 60 * 1000);
var expires = date.toUTCString();

window.Mozilla.Cookies.setItem(
exp.id,
exp.chosenVariation,
expires,
undefined,
undefined,
false,
'lax'
);
}

function handleVariation(variation) {
if (Object.prototype.hasOwnProperty.call(variants, variation)) {
var target = document.getElementById('var-' + variation);
Expand All @@ -14,9 +31,12 @@
}

var wiggum = new window.TrafficCop({
id: 'my-experiment-id-3',
customCallback: handleVariation,
variations: variants
});

wiggum.init();

setVariationCookie(wiggum);
})();
20 changes: 20 additions & 0 deletions demo/src/public/experiment-page3-redirect.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,32 @@
(function () {
'use strict';

function setVariationCookie(exp) {
// set cookie to expire in 24 hours
var date = new Date();
date.setTime(date.getTime() + 1 * 24 * 60 * 60 * 1000);
var expires = date.toUTCString();

window.Mozilla.Cookies.setItem(
exp.id,
exp.chosenVariation,
expires,
undefined,
undefined,
false,
'lax'
);
}

var lou = new window.TrafficCop({
id: 'my-experiment-id-4',
variations: {
'v=a': 40,
'v=b': 40
}
});

lou.init();

setVariationCookie(lou);
})();
5 changes: 5 additions & 0 deletions demo/src/views/pages/page1.njk
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{% extends "../index.njk" %}

{% block experiment_js %}
<script src="/libs/index.js"></script>
<script src="/trafficcop/index.js"></script>
<script src="/experiment-page1.js"></script>
{% endblock %}
Expand Down Expand Up @@ -61,5 +62,9 @@
this line. If yes, do. I love you all.
</p>
</article>

<aside>
<p>Note: to clear the variation, you might need to clear cookies for this page and refresh.</p>
</aside>
</div>
{% endblock %}
5 changes: 5 additions & 0 deletions demo/src/views/pages/page2.njk
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{% extends "../index.njk" %}

{% block experiment_js %}
<script src="/libs/index.js"></script>
<script src="/trafficcop/index.js"></script>
<script src="/experiment-page2.js"></script>
{% endblock %}
Expand Down Expand Up @@ -39,5 +40,9 @@
No.
</p>
</article>

<aside>
<p>Note: to clear the variation, you might need to clear cookies for this page and refresh.</p>
</aside>
</div>
{% endblock %}
5 changes: 5 additions & 0 deletions demo/src/views/pages/page3.njk
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{% extends "../index.njk" %}

{% block experiment_js %}
<script src="/libs/index.js"></script>
<script src="/trafficcop/index.js"></script>
<script src="/experiment-page3-redirect.js"></script>
{% endblock %}
Expand Down Expand Up @@ -45,6 +46,10 @@
<span class="hidden" id="var-b">Herman Blume (customCallback b)</span>
</p>
</article>

<aside>
<p>Note: to clear the variation, you might need to clear cookies for this page and refresh.</p>
</aside>
</div>
{% endblock %}

Expand Down
27 changes: 24 additions & 3 deletions documentation.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,7 @@ Traffic Cop places visitors into A/B/x cohorts, and either performs a redirect o

After verifying the supplied configuration, Traffic Cop then generates a random number to choose a variation based on the supplied cohort percentages. If the supplied variations do not target 100% of visitors, the `no-variation` value may be chosen.

Traffic Cop does not use cookies to record or store data of any kind. It also does not store, send or transmit any kind of experiment data for analysis. It simply performs the task of displaying different experiment variations upon page load. It is up to you to record experiment data using whatever your standard website analytics tools may be.

**Note that because Traffic Cop does not use cookies, this does mean that repeat visits to the canonical URL might see a different variation to the one they saw previously during the course of an experiment running.**
Traffic Cop does not set cookies to record or store data of any kind. It also does not store, send or transmit any kind of experiment data for analysis. It simply performs the task of displaying different experiment variations upon page load. It is up to you to record experiment data using whatever your standard website analytics tools may be. Likewise, if you want to set a cookie on redirect to remember which variation a website visitor has seen, it is up to you to handle cookie consent appropriately before doing so.

## Type A: Callback

Expand Down Expand Up @@ -121,6 +119,29 @@ eddie.init();

In the above example, the test will have 3 variations and will target a total of 23.78% of visitors. There will also be a 76.22% chance that `no-variation` is chosen.

### Remembering which variation a visitor has seen previously.

If you would like to try and ensure website visitors see the same experiment variation on repeat page visits, you can pass an optional experiment cookie ID to Traffic Cop when initializing an experiment:

```javascript
var eddie = new TrafficCop({
id: 'my-experiment-cookie-id',
variations: {
'v=1': 12.2,
'v=2': 0.13,
'v=3': 11.45
}
});

eddie.init();
```

Once Traffic Cop is initialized with an `id`, you can then set a cookie in your website code to store which variation was chosen. Traffic Cop will then check for existence of this cookie before deciding which variation to show on repeat visits.

The cookie ID should match the value of `eddie.id`, and the cookie value should match the value of `eddie.chosenVariation`. If you need help setting cookies, see [https://github.com/mozmeao/cookie-helper](https://github.com/mozmeao/cookie-helper).

Note: it is a website's responsibility to check for cookie consent before setting non-essential cookies!

## Implementation

Traffic Cop requires two JavaScript files:
Expand Down
12 changes: 11 additions & 1 deletion eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,17 @@ const rules = {

// Disallow the use of `console`
// https://eslint.org/docs/rules/no-console
'no-console': 'error'
'no-console': 'error',

// Allow unused vars in caught errors for older
// browsers that don't support optional catch binding
// https://eslint.org/docs/latest/rules/no-unused-vars#options
'no-unused-vars': [
'error',
{
caughtErrors: 'none'
}
]
};

module.exports = [
Expand Down
7 changes: 7 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"devDependencies": {
"@babel/core": "^7.24.6",
"@babel/preset-env": "^7.24.7",
"@mozmeao/cookie-helper": "^1.1.0",
"babel-loader": "^9.1.3",
"clean-webpack-plugin": "^4.0.0",
"copy-webpack-plugin": "^12.0.2",
Expand Down
Loading

0 comments on commit 6a93aa6

Please sign in to comment.