Browse Source

added signin button color experiment. closes #1306 (#1320)

Danny Coates 5 years ago
parent
commit
23ecb632eb
9 changed files with 157 additions and 82 deletions
  1. 23 18
      app/experiments.js
  2. 71 0
      app/main.css
  3. 1 1
      app/main.js
  4. 12 5
      app/metrics.js
  5. 4 7
      app/storage.js
  6. 3 1
      app/ui/account.js
  7. 35 47
      package-lock.json
  8. 3 1
      server/amplitude.js
  9. 5 2
      tailwind.js

+ 23 - 18
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);
   }
 }

+ 71 - 0
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 */

+ 1 - 1
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');
 })();

+ 12 - 5
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,

+ 4 - 7
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() {

+ 3 - 1
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`
         <send-account>
           <button
-            class="p-2 md:p-4 border rounded-lg text-blue-dark border-blue-dark hover:text-white hover:bg-blue-dark focus:outline"
+            class="px-4 py-2 md:px-8 md:py-4 focus:outline signin ${this
+              .buttonClass}"
             onclick="${e => this.login(e)}"
             title="${translate('signInOnlyButton')}"
           >

+ 35 - 47
package-lock.json

@@ -1320,16 +1320,8 @@
       "dev": true,
       "requires": {
         "@dannycoates/elliptic": "^6.4.2",
-        "asmcrypto.js": "^0.22.0"
-      },
-      "dependencies": {
-        "webcrypto-core": {
-          "version": "github:dannycoates/webcrypto-core#8e0152a66d3ae6329cf080ccb3085eb06637070f",
-          "from": "github:dannycoates/webcrypto-core#8e0152a66d3ae6329cf080ccb3085eb06637070f",
-          "requires": {
-            "tslib": "^1.7.1"
-          }
-        }
+        "asmcrypto.js": "^0.22.0",
+        "webcrypto-core": "github:dannycoates/webcrypto-core#8e0152a66d3ae6329cf080ccb3085eb06637070f"
       }
     },
     "@fullhuman/postcss-purgecss": {
@@ -5818,16 +5810,8 @@
       "version": "github:dannycoates/express-ws#d0910a43b1802b22476362113557e20b18e185ba",
       "from": "github:dannycoates/express-ws",
       "requires": {
-        "esm": "^3.0.84"
-      },
-      "dependencies": {
-        "ws": {
-          "version": "github:dannycoates/ws#c83cbb3bce478122cedcb8c475d9e86e1112824a",
-          "from": "github:dannycoates/ws#c83cbb3bce478122cedcb8c475d9e86e1112824a",
-          "requires": {
-            "async-limiter": "~1.0.0"
-          }
-        }
+        "esm": "^3.0.84",
+        "ws": "github:dannycoates/ws#c83cbb3bce478122cedcb8c475d9e86e1112824a"
       }
     },
     "extend": {
@@ -6373,11 +6357,8 @@
       "resolved": "https://registry.npmjs.org/fluent-intl-polyfill/-/fluent-intl-polyfill-0.1.0.tgz",
       "integrity": "sha1-ETOUSrJHeINHOZVZaIPg05z4hc8=",
       "dev": true,
-      "dependencies": {
-        "intl-pluralrules": {
-          "version": "github:projectfluent/IntlPluralRules#94cb0fa1c23ad943bc5aafef43cea132fa51d68b",
-          "from": "github:projectfluent/IntlPluralRules#94cb0fa1c23ad943bc5aafef43cea132fa51d68b"
-        }
+      "requires": {
+        "intl-pluralrules": "github:projectfluent/IntlPluralRules#94cb0fa1c23ad943bc5aafef43cea132fa51d68b"
       }
     },
     "fluent-langneg": {
@@ -6583,15 +6564,13 @@
           "version": "1.0.0",
           "resolved": false,
           "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
-          "dev": true,
-          "optional": true
+          "dev": true
         },
         "brace-expansion": {
           "version": "1.1.11",
           "resolved": false,
           "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
           "dev": true,
-          "optional": true,
           "requires": {
             "balanced-match": "^1.0.0",
             "concat-map": "0.0.1"
@@ -6608,22 +6587,19 @@
           "version": "1.1.0",
           "resolved": false,
           "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=",
-          "dev": true,
-          "optional": true
+          "dev": true
         },
         "concat-map": {
           "version": "0.0.1",
           "resolved": false,
           "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
-          "dev": true,
-          "optional": true
+          "dev": true
         },
         "console-control-strings": {
           "version": "1.1.0",
           "resolved": false,
           "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=",
-          "dev": true,
-          "optional": true
+          "dev": true
         },
         "core-util-is": {
           "version": "1.0.2",
@@ -6754,8 +6730,7 @@
           "version": "2.0.3",
           "resolved": false,
           "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
-          "dev": true,
-          "optional": true
+          "dev": true
         },
         "ini": {
           "version": "1.3.5",
@@ -6769,7 +6744,6 @@
           "resolved": false,
           "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
           "dev": true,
-          "optional": true,
           "requires": {
             "number-is-nan": "^1.0.0"
           }
@@ -6786,7 +6760,6 @@
           "resolved": false,
           "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
           "dev": true,
-          "optional": true,
           "requires": {
             "brace-expansion": "^1.1.7"
           }
@@ -6795,15 +6768,13 @@
           "version": "0.0.8",
           "resolved": false,
           "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=",
-          "dev": true,
-          "optional": true
+          "dev": true
         },
         "minipass": {
           "version": "2.3.5",
           "resolved": false,
           "integrity": "sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA==",
           "dev": true,
-          "optional": true,
           "requires": {
             "safe-buffer": "^5.1.2",
             "yallist": "^3.0.0"
@@ -6824,7 +6795,6 @@
           "resolved": false,
           "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
           "dev": true,
-          "optional": true,
           "requires": {
             "minimist": "0.0.8"
           }
@@ -6913,8 +6883,7 @@
           "version": "1.0.1",
           "resolved": false,
           "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=",
-          "dev": true,
-          "optional": true
+          "dev": true
         },
         "object-assign": {
           "version": "4.1.1",
@@ -6928,7 +6897,6 @@
           "resolved": false,
           "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
           "dev": true,
-          "optional": true,
           "requires": {
             "wrappy": "1"
           }
@@ -7066,7 +7034,6 @@
           "resolved": false,
           "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
           "dev": true,
-          "optional": true,
           "requires": {
             "code-point-at": "^1.0.0",
             "is-fullwidth-code-point": "^1.0.0",
@@ -8292,6 +8259,11 @@
       "integrity": "sha512-mT34yGKMNceBQUoVn7iCDKDntA7SC6gycMAWzGx1z/CMCTV7b2AAtXlo3nRyHZ1FelRkQbQjprHSYGwzLtkVbw==",
       "dev": true
     },
+    "intl-pluralrules": {
+      "version": "github:projectfluent/IntlPluralRules#94cb0fa1c23ad943bc5aafef43cea132fa51d68b",
+      "from": "github:projectfluent/IntlPluralRules#module",
+      "dev": true
+    },
     "invariant": {
       "version": "2.2.4",
       "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
@@ -15766,7 +15738,8 @@
     "tslib": {
       "version": "1.9.3",
       "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz",
-      "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ=="
+      "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==",
+      "dev": true
     },
     "tty-browserify": {
       "version": "0.0.0",
@@ -16587,6 +16560,14 @@
         "object.assign": "^4.0.3"
       }
     },
+    "webcrypto-core": {
+      "version": "github:dannycoates/webcrypto-core#8e0152a66d3ae6329cf080ccb3085eb06637070f",
+      "from": "github:dannycoates/webcrypto-core",
+      "dev": true,
+      "requires": {
+        "tslib": "^1.7.1"
+      }
+    },
     "webdriverio": {
       "version": "4.14.4",
       "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-4.14.4.tgz",
@@ -17819,6 +17800,13 @@
       "integrity": "sha1-wlLXx8WxtAKJdjDjRTx7/mkNnKE=",
       "dev": true
     },
+    "ws": {
+      "version": "github:dannycoates/ws#c83cbb3bce478122cedcb8c475d9e86e1112824a",
+      "from": "github:dannycoates/ws",
+      "requires": {
+        "async-limiter": "~1.0.0"
+      }
+    },
     "x-is-string": {
       "version": "0.1.0",
       "resolved": "https://registry.npmjs.org/x-is-string/-/x-is-string-0.1.0.tgz",

+ 3 - 1
server/amplitude.js

@@ -118,7 +118,9 @@ function clientEvent(event, ua, language, session_id, deltaT, platform, ip) {
     utm_content: ep.utm_content,
     utm_medium: ep.utm_medium,
     utm_source: ep.utm_source,
-    utm_term: ep.utm_term
+    utm_term: ep.utm_term,
+    experiment: ep.experiment,
+    variant: ep.variant
   };
   const user_properties = {
     active_count: up.active_count,

+ 5 - 2
tailwind.js

@@ -127,7 +127,8 @@ const colors = {
   'pink-light': '#fa7ea8',
   'pink-lighter': '#ffbbca',
   'pink-lightest': '#ffebef',
-  cloud: 'rgba(255, 255, 255, 0.8)'
+  cloud: 'rgba(255, 255, 255, 0.8)',
+  violet: 'hsl(258, 57%, 35%)'
 };
 
 module.exports = {
@@ -737,7 +738,9 @@ module.exports = {
     inner: 'inset 0 2px 4px 0 rgba(0,0,0,0.06)',
     outline: '0 0 0 3px rgba(52,144,220,0.5)',
     none: 'none',
-    cloud: '0 0 5rem 5rem white'
+    cloud: '0 0 5rem 5rem white',
+    btn:
+      'inset 0 -6px 12px 0 rgba(0,70,144,0.25), 0 4px 6px 0 rgba(34,0,51,0.04), 0 1px 10px 0 rgba(7,48,114,0.12), 0 2px 8px -1px rgba(14,13,26,0.08)'
   },
 
   /*