Skip to content

Commit 93d41f1

Browse files
committed
[dashboard] Enable UI experiments
1 parent 5bd176b commit 93d41f1

File tree

3 files changed

+92
-0
lines changed

3 files changed

+92
-0
lines changed

components/dashboard/src/App.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { useHistory } from 'react-router-dom';
2020
import { trackButtonOrAnchor, trackPathChange, trackLocation } from './Analytics';
2121
import { User } from '@gitpod/gitpod-protocol';
2222
import * as GitpodCookie from '@gitpod/gitpod-protocol/lib/util/gitpod-cookie';
23+
import { Experiment } from './experiments';
2324

2425
const Setup = React.lazy(() => import(/* webpackPrefetch: true */ './Setup'));
2526
const Workspaces = React.lazy(() => import(/* webpackPrefetch: true */ './workspaces/Workspaces'));
@@ -167,6 +168,11 @@ function App() {
167168
}, []);
168169

169170
// listen and notify Segment of client-side path updates
171+
useEffect(() => {
172+
// Choose which experiments to run for this session/user
173+
Experiment.set(Experiment.seed(true));
174+
})
175+
170176
useEffect(() => {
171177
return history.listen((location: any) => {
172178
const path = window.location.pathname;

components/dashboard/src/Login.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ export function Login() {
5959

6060
const authorizeSuccessful = async (payload?: string) => {
6161
updateUser().catch(console.error);
62+
6263
// Check for a valid returnTo in payload
6364
const safeReturnTo = getSafeURLRedirect(payload);
6465
if (safeReturnTo) {
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
/**
2+
* Copyright (c) 2021 Gitpod GmbH. All rights reserved.
3+
* Licensed under the GNU Affero General Public License (AGPL).
4+
* See License-AGPL.txt in the project root for license information.
5+
*/
6+
7+
const UI_EXPERIMENTS_KEY = "gitpod-ui-experiments";
8+
9+
/**
10+
* This enables UI-experiments: Dashboard-local changes that we'd like to try out and get some feedback on/validate
11+
* our assumptions on via mixpanel before we roll them out to everyone. The motivation is to make UI development more
12+
* data-driven then the current approach.
13+
*
14+
* An experiment is supposed to:
15+
* - only be applied to a subset of users, so we are guaranteed to always have a control group (configurable per experiment)
16+
* - is dashboard/component local: it's not a mechnanism to cross the API boundary, mostly even component-local
17+
* - is stable per session/client
18+
* - is defined in code (see below): adding/deprecating experiments requires a deployment
19+
* It is NOT supposed to:
20+
* - big a way to roll-out big, completly new features
21+
* - have a lot of different experiments in parallel (too noisy)
22+
*
23+
* Questions:
24+
* - multiple experiments per user/time
25+
*/
26+
const Experiments = {
27+
/**
28+
* Experiment "example" will be activate on login for 10% of all clients.
29+
*/
30+
// "example": 0.1,
31+
};
32+
const ExperimentsSet = new Set(Object.keys(Experiments)) as Set<Experiment>;
33+
export type Experiment = keyof (typeof Experiments);
34+
35+
export namespace Experiment {
36+
export function seed(keepCurrent: boolean): Set<Experiment> {
37+
const current = keepCurrent ? get() : undefined;
38+
39+
// add all current experiments to ensure stability
40+
const result = new Set<Experiment>([...(current || [])].filter(e => ExperimentsSet.has(e)));
41+
42+
// identify all new experiments and add if random
43+
const newExperiment = new Set<Experiment>([...ExperimentsSet].filter(e => !result.has(e)));
44+
for (const e of newExperiment) {
45+
if (Math.random() < Experiments[e]) {
46+
result.add(e);
47+
}
48+
}
49+
50+
return result;
51+
}
52+
53+
export function set(set: Set<Experiment>): void {
54+
try {
55+
const arr = Array.from(set);
56+
window.localStorage.setItem(UI_EXPERIMENTS_KEY, JSON.stringify(arr));
57+
} catch (err) {
58+
console.error(`error setting ${UI_EXPERIMENTS_KEY}`, err);
59+
}
60+
}
61+
62+
export function has(experiment: Experiment): boolean {
63+
const set = get();
64+
if (!set) {
65+
return false;
66+
}
67+
return set.has(experiment);
68+
}
69+
70+
export function get(): Set<Experiment> | undefined {
71+
const arr = window.localStorage.getItem(UI_EXPERIMENTS_KEY);
72+
if (arr === null) {
73+
return undefined;
74+
}
75+
return new Set(arr) as Set<Experiment>;
76+
}
77+
78+
export function getAsArray(): Experiment[] {
79+
const set = get();
80+
if (!set) {
81+
return [];
82+
}
83+
return Array.from(set);
84+
}
85+
}

0 commit comments

Comments
 (0)