diff --git a/app/experiments.js b/app/experiments.js index 28b4e851..8b7a19ee 100644 --- a/app/experiments.js +++ b/app/experiments.js @@ -1,6 +1,22 @@ import hash from 'string-hash'; +import Account from './ui/account'; -const experiments = {}; +const experiments = { + signin_button_color: { + eligible: function() { + return true; + }, + variant: function() { + return ['white-blue', 'blue', 'white-violet', 'violet'][ + Math.floor(Math.random() * 4) + ]; + }, + run: function(variant, state) { + const account = state.cache(Account, 'account'); + account.buttonClass = variant; + } + } +}; //Returns a number between 0 and 1 // eslint-disable-next-line no-unused-vars @@ -25,23 +41,12 @@ export default function initialize(state, emitter) { xp.run(+state.query.v, state, emitter); } }); - - if (!state.storage.get('testpilot_ga__cid')) { - // first ever visit. check again after cid is assigned. - emitter.on('DOMContentLoaded', () => { - checkExperiments(state, emitter); - }); + const enrolled = state.storage.enrolled; + // single experiment per session for now + const id = Object.keys(enrolled)[0]; + if (Object.keys(experiments).includes(id)) { + experiments[id].run(enrolled[id], state, emitter); } else { - const enrolled = state.storage.enrolled.filter(([id, variant]) => { - const xp = experiments[id]; - if (xp) { - xp.run(variant, state, emitter); - } - return !!xp; - }); - // single experiment per session for now - if (enrolled.length === 0) { - checkExperiments(state, emitter); - } + checkExperiments(state, emitter); } } diff --git a/app/main.css b/app/main.css index 6027dce3..a7205005 100644 --- a/app/main.css +++ b/app/main.css @@ -8,6 +8,14 @@ user-select: none; } +:root { + --violet-gradient: linear-gradient( + -180deg, + rgba(144, 89, 255, 0.8) 0%, + rgba(144, 89, 255, 0.4) 100% + ); +} + a { color: inherit; text-decoration: none; @@ -300,3 +308,66 @@ select { .word-break-all { word-break: break-all; } + +.signin { + border-radius: 6px; + transition-property: transform, background-color; + transition-duration: 250ms; + transition-timing-function: cubic-bezier(0.07, 0.95, 0, 1); +} + +.signin:hover, +.signin:focus { + @apply shadow-btn; + + transform: scale(1.0625); +} + +.signin:hover:active { + transform: scale(0.9375); +} + +/* begin signin button color experiment */ + +.white-blue { + @apply border-blue-dark; + @apply border-2; + @apply text-blue-dark; +} + +.white-blue:hover, +.white-blue:focus { + @apply bg-blue-dark; + @apply text-white; +} + +.blue { + @apply bg-blue-dark; + @apply text-white; +} + +.white-violet { + @apply border-violet; + @apply border-2; + @apply text-violet; +} + +.white-violet:hover, +.white-violet:focus { + @apply bg-violet; + @apply text-white; + + background-image: var(--violet-gradient); +} + +.violet { + @apply bg-violet; + @apply text-white; +} + +.violet:hover, +.violet:focus { + background-image: var(--violet-gradient); +} + +/* end signin button color experiment */ diff --git a/app/main.js b/app/main.js index 40b3af57..1f654f2e 100644 --- a/app/main.js +++ b/app/main.js @@ -63,10 +63,10 @@ if (process.env.NODE_ENV === 'production') { const app = routes(choo()); window.app = app; + app.use(experiments); app.use(metrics); app.use(controller); app.use(dragManager); - app.use(experiments); app.use(pasteManager); app.mount('body'); })(); diff --git a/app/metrics.js b/app/metrics.js index f348a4bd..0d7fb995 100644 --- a/app/metrics.js +++ b/app/metrics.js @@ -3,7 +3,7 @@ import { platform, locale } from './utils'; import { sendMetrics } from './api'; let appState = null; -// let experiment = null; +let experiment = null; const HOUR = 1000 * 60 * 60; const events = []; let session_id = Date.now(); @@ -11,11 +11,13 @@ const lang = locale(); export default function initialize(state, emitter) { appState = state; - if (!appState.user.firstAction) { - appState.user.firstAction = appState.route === '/' ? 'upload' : 'download'; - } + emitter.on('DOMContentLoaded', () => { - // experiment = storage.enrolled[0]; + experiment = storage.enrolled; + if (!appState.user.firstAction) { + appState.user.firstAction = + appState.route === '/' ? 'upload' : 'download'; + } const query = appState.query; addEvent('client_visit', { entrypoint: appState.route === '/' ? 'upload' : 'download', @@ -59,6 +61,11 @@ function submitEvents() { async function addEvent(event_type, event_properties) { const user_id = await appState.user.metricId(); const device_id = await appState.user.deviceId(); + const ab_id = Object.keys(experiment)[0]; + if (ab_id) { + event_properties.experiment = ab_id; + event_properties.variant = experiment[ab_id]; + } events.push({ device_id, event_properties, diff --git a/app/storage.js b/app/storage.js index d66391e0..304759ea 100644 --- a/app/storage.js +++ b/app/storage.js @@ -86,16 +86,13 @@ class Storage { this.engine.setItem('referrer', str); } get enrolled() { - return JSON.parse(this.engine.getItem('experiments') || '[]'); + return JSON.parse(this.engine.getItem('ab_experiments') || '{}'); } enroll(id, variant) { - const enrolled = this.enrolled; - // eslint-disable-next-line no-unused-vars - if (!enrolled.find(([i, v]) => i === id)) { - enrolled.push([id, variant]); - this.engine.setItem('experiments', JSON.stringify(enrolled)); - } + const enrolled = {}; + enrolled[id] = variant; + this.engine.setItem('ab_experiments', JSON.stringify(enrolled)); } get files() { diff --git a/app/ui/account.js b/app/ui/account.js index 19fdda44..26b98772 100644 --- a/app/ui/account.js +++ b/app/ui/account.js @@ -8,6 +8,7 @@ class Account extends Component { this.emit = emit; this.enabled = state.capabilities.account; this.local = state.components[name] = {}; + this.buttonClass = ''; this.setState(); } @@ -62,7 +63,8 @@ class Account extends Component { return html`