Skip to content

Commit 56d03a2

Browse files
committed
[dashboard] Fix UI experiments
1 parent cf882f1 commit 56d03a2

File tree

2 files changed

+45
-34
lines changed

2 files changed

+45
-34
lines changed

components/dashboard/src/Analytics.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ interface TrackPathChanged {
4747
}
4848

4949
interface TrackUIExperiments {
50-
ui_experiments?: string[],
50+
ui_experiments?: {},
5151
}
5252

5353
//call this to track all events outside of button and anchor clicks
@@ -56,7 +56,7 @@ export const trackEvent = (event: Event, properties: EventProperties) => {
5656
}
5757

5858
const trackEventInternal = (event: InternalEvent, properties: InternalEventProperties, userKnown?: boolean) => {
59-
properties.ui_experiments = Experiment.getAsArray();
59+
properties.ui_experiments = Experiment.get();
6060

6161
getGitpodService().server.trackEvent({
6262
//if the user is authenticated, let server determine the id. else, pass anonymousId explicitly.
@@ -141,7 +141,7 @@ export const trackLocation = async (userKnown: boolean) => {
141141
path: window.location.pathname,
142142
host: window.location.hostname,
143143
url: window.location.href,
144-
ui_experiments: Experiment.getAsArray(),
144+
ui_experiments: Experiment.get(),
145145
};
146146

147147
getGitpodService().server.trackLocation({

components/dashboard/src/experiments.ts

Lines changed: 42 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -27,59 +27,70 @@ const Experiments = {
2727
/**
2828
* Experiment "example" will be activate on login for 10% of all clients.
2929
*/
30-
// "example": 0.1,
30+
"example": 0.1,
3131
};
32-
const ExperimentsSet = new Set(Object.keys(Experiments)) as Set<Experiment>;
32+
type Experiments = Partial<{ [e in Experiment]: boolean }>;
3333
export type Experiment = keyof (typeof Experiments);
3434

3535
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)));
36+
/**
37+
* Randomly decides what the set of Experiments is the user participates in
38+
* @param keepCurrent
39+
* @returns Experiments
40+
*/
41+
export function seed(keepCurrent: boolean): Experiments {
42+
const result = keepCurrent ? get() || {} : {};
4143

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);
44+
for (const experiment of Object.keys(Experiments) as Experiment[]) {
45+
if (!(experiment in result)) {
46+
result[experiment] = Math.random() < Experiments[experiment];
4747
}
4848
}
4949

5050
return result;
5151
}
5252

53-
export function set(set: Set<Experiment>): void {
53+
export function set(set: Experiments): void {
5454
try {
55-
const arr = Array.from(set);
56-
window.localStorage.setItem(UI_EXPERIMENTS_KEY, JSON.stringify(arr));
55+
window.localStorage.setItem(UI_EXPERIMENTS_KEY, JSON.stringify(set));
5756
} catch (err) {
58-
console.error(`error setting ${UI_EXPERIMENTS_KEY}`, err);
57+
console.warn(`error setting ${UI_EXPERIMENTS_KEY}`, err);
5958
}
6059
}
6160

6261
export function has(experiment: Experiment): boolean {
63-
const set = get();
64-
if (!set) {
62+
try {
63+
const set = get();
64+
if (!set) {
65+
return false;
66+
}
67+
return set[experiment] === true;
68+
} catch (err) {
69+
console.warn(`error checking experiment '${experiment}'`, err);
6570
return false;
6671
}
67-
return set.has(experiment);
6872
}
6973

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-
}
74+
/** Retrieves all currently valid Experiments from localStorage */
75+
export function get(): Experiments | undefined {
76+
try {
77+
const objStr = window.localStorage.getItem(UI_EXPERIMENTS_KEY);
78+
if (objStr === null) {
79+
return undefined;
80+
}
7781

78-
export function getAsArray(): Experiment[] {
79-
const set = get();
80-
if (!set) {
81-
return [];
82+
const obj = JSON.parse(objStr) as Experiments;
83+
// trim to contain only known keys so we're type-safe
84+
for (const e of Object.keys(obj)) {
85+
if (!(e in Experiments)) {
86+
delete (obj as any)[e];
87+
}
88+
}
89+
return obj;
90+
} catch (err) {
91+
// we definitely don't want to break anybody because of weird errors
92+
console.warn(`error getting ${UI_EXPERIMENTS_KEY}`, err);
93+
return undefined;
8294
}
83-
return Array.from(set);
8495
}
8596
}

0 commit comments

Comments
 (0)