Skip to content

Commit 623a9d9

Browse files
committed
Experiment Store Browser
Added a table that includes the details of enrollments in the experiment store with the option to delete inactive enrollments and unenroll from active ones.
1 parent 5459d6b commit 623a9d9

File tree

6 files changed

+184
-0
lines changed

6 files changed

+184
-0
lines changed

src/apis/nimbus.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,33 @@ var nimbus = class extends ExtensionAPI {
243243
throw error;
244244
}
245245
},
246+
247+
async getExperimentStore() {
248+
try {
249+
return await ExperimentManager.store.getAll();
250+
} catch (error) {
251+
console.error(error);
252+
throw error;
253+
}
254+
},
255+
256+
async unenroll(slug) {
257+
try {
258+
return await ExperimentManager.unenroll(slug, "nimbus-devtools");
259+
} catch (error) {
260+
console.error(error);
261+
throw error;
262+
}
263+
},
264+
265+
async deleteInactiveEnrollment(slug) {
266+
try {
267+
return await ExperimentManager.store._deleteForTests(slug);
268+
} catch (error) {
269+
console.error(error);
270+
throw error;
271+
}
272+
},
246273
},
247274
},
248275
};

src/apis/nimbus.json

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,42 @@
134134
"description": "The specific branch slug of the experiment to enroll into."
135135
}
136136
]
137+
},
138+
139+
{
140+
"name": "getExperimentStore",
141+
"type": "function",
142+
"description": "Get all enrollments from the experiment store.",
143+
"async": true,
144+
"parameters": []
145+
},
146+
147+
{
148+
"name": "unenroll",
149+
"type": "function",
150+
"description": "Unenroll from an active experiment in the store.",
151+
"async": true,
152+
"parameters": [
153+
{
154+
"name": "slug",
155+
"type": "string",
156+
"description": "The slug of the experiment to unenroll from."
157+
}
158+
]
159+
},
160+
161+
{
162+
"name": "deleteInactiveEnrollment",
163+
"type": "function",
164+
"description": "Delete an inactive experiment from the store.",
165+
"async": true,
166+
"parameters": [
167+
{
168+
"name": "slug",
169+
"type": "string",
170+
"description": "The slug of the experiment to delete from the store."
171+
}
172+
]
137173
}
138174
],
139175
"events": []

src/types/global.d.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,4 +79,10 @@ declare namespace browser.experiments.nimbus {
7979
function updateRecipes(forceSync: boolean): Promise<void>;
8080

8181
function forceEnroll(recipe: object, branchSlug: string): Promise<boolean>;
82+
83+
function getExperimentStore(): Promise<object[]>;
84+
85+
function unenroll(slug: string): Promise<void>;
86+
87+
function deleteInactiveEnrollment(slug: string): Promise<void>;
8288
}
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import { FC, useCallback, useEffect, useState } from "react";
2+
3+
interface NimbusEnrollment {
4+
slug: string;
5+
userFacingName: string;
6+
userFacingDescription: string;
7+
isRollout: boolean;
8+
featureIds: string[];
9+
active: boolean;
10+
}
11+
12+
const ExperimentStorePage: FC = () => {
13+
const [experiments, setExperiments] = useState<NimbusEnrollment[]>([]);
14+
15+
const fetchExperiments = useCallback(async () => {
16+
try {
17+
const experimentStore =
18+
await browser.experiments.nimbus.getExperimentStore();
19+
setExperiments(experimentStore as NimbusEnrollment[]);
20+
} catch (error) {
21+
console.error("Error fetching experiments:", error);
22+
}
23+
}, [experiments]);
24+
25+
useEffect(() => {
26+
void fetchExperiments();
27+
}, [fetchExperiments]);
28+
29+
const unenrollExperiment = useCallback(
30+
async (slug: string) => {
31+
try {
32+
await browser.experiments.nimbus.unenroll(slug);
33+
alert("Unenrollment successful");
34+
await fetchExperiments();
35+
} catch (error) {
36+
console.error(`Error unenrolling from experiment ${slug}:`, error);
37+
}
38+
},
39+
[fetchExperiments],
40+
);
41+
42+
const deleteExperiment = useCallback(
43+
async (slug: string) => {
44+
try {
45+
await browser.experiments.nimbus.deleteInactiveEnrollment(slug);
46+
alert("Deletion successful");
47+
await fetchExperiments();
48+
} catch (error) {
49+
console.error(`Error deleting experiment ${slug}:`, error);
50+
}
51+
},
52+
[fetchExperiments],
53+
);
54+
55+
return (
56+
<div className="experiment-viewer">
57+
<h1 className="experiment-viewer__title">Experiment Store</h1>
58+
<div className="experiment-viewer__list">
59+
<table>
60+
<thead>
61+
<tr>
62+
<th>Experiment</th>
63+
<th>featureIds</th>
64+
<th>isRollout</th>
65+
<th>Status</th>
66+
<th>Options</th>
67+
</tr>
68+
</thead>
69+
<tbody>
70+
{experiments?.map((experiment: NimbusEnrollment, index: number) => (
71+
<tr key={index}>
72+
<td>
73+
<strong>{experiment.slug}</strong> <br />
74+
<br />
75+
{experiment.userFacingName}:{" "}
76+
{experiment.userFacingDescription}
77+
</td>
78+
<td>{experiment.featureIds.join(", ")}</td>
79+
<td>{experiment.isRollout ? "Yes" : "No"}</td>
80+
<td>{experiment.active ? "Active" : "Inactive"}</td>
81+
<td>
82+
{experiment.active ? (
83+
<button
84+
className="experiment-viewer-list__button"
85+
onClick={() => unenrollExperiment(experiment.slug)}
86+
>
87+
Unenroll
88+
</button>
89+
) : (
90+
<button
91+
className="experiment-viewer-list__button"
92+
onClick={() => deleteExperiment(experiment.slug)}
93+
>
94+
Delete
95+
</button>
96+
)}
97+
</td>
98+
</tr>
99+
))}
100+
</tbody>
101+
</table>
102+
</div>
103+
</div>
104+
);
105+
};
106+
107+
export default ExperimentStorePage;

src/ui/components/Sidebar.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ const Sidebar: FC = () => {
1313
<Link to="/jexl-debugger" className="sidebar__link">
1414
JEXL Debugger
1515
</Link>
16+
<Link to="/experiment-store" className="sidebar__link">
17+
Experiment Store
18+
</Link>
1619
<Link to="/experiment-browser" className="sidebar__link">
1720
Experiment Browser
1821
</Link>

src/ui/index.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import FeatureConfigPage from "./components/FeatureConfigPage";
1414
import SettingsPage from "./components/SettingsPage";
1515
import JEXLDebuggerPage from "./components/JEXLDebuggerPage";
1616
import ExperimentBrowserPage from "./components/ExperimentBrowserPage";
17+
import ExperimentStorePage from "./components/ExperimentStorePage";
1718

1819
document.addEventListener("DOMContentLoaded", () => {
1920
document.querySelectorAll("#app").forEach((el) => {
@@ -32,6 +33,10 @@ document.addEventListener("DOMContentLoaded", () => {
3233
element={<FeatureConfigPage />}
3334
/>
3435
<Route path="/jexl-debugger" element={<JEXLDebuggerPage />} />
36+
<Route
37+
path="/experiment-store"
38+
element={<ExperimentStorePage />}
39+
/>
3540
<Route
3641
path="/experiment-browser"
3742
element={<ExperimentBrowserPage />}

0 commit comments

Comments
 (0)