Skip to content

Commit e269c97

Browse files
committed
Switch to a UI framework
Replace bespoke CSS with React Bootstrap for cleaner and more maintainable styling.
1 parent 6150497 commit e269c97

15 files changed

+2937
-3373
lines changed

package-lock.json

Lines changed: 2472 additions & 2620 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,8 @@
3535
},
3636
"devDependencies": {
3737
"@tsconfig/node18": "^18.2.0",
38-
"@types/react": "^18.2.18",
39-
"@types/react-dom": "^18.2.7",
38+
"@types/react": "^18.3.3",
39+
"@types/react-dom": "^18.3.0",
4040
"@types/react-router-dom": "^5.3.3",
4141
"@typescript-eslint/eslint-plugin": "^7.10.0",
4242
"@typescript-eslint/parser": "^7.10.0",
@@ -56,9 +56,11 @@
5656
},
5757
"dependencies": {
5858
"@mozilla/nimbus-schemas": "^2024.3.1",
59+
"bootstrap": "^5.3.3",
5960
"mozjexl": "github:mozilla/mozjexl",
6061
"normalize.css": "^8.0.1",
6162
"react": "^18.3.1",
63+
"react-bootstrap": "^2.10.3",
6264
"react-dom": "^18.3.1",
6365
"react-router": "^5.3.4",
6466
"react-router-dom": "^6.23.1"

src/ui/components/DropdownMenu.tsx

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { ChangeEvent, FC, useEffect, useState, useCallback } from "react";
2+
import { Form } from "react-bootstrap";
23

34
type DropdownMenuProps = {
45
onSelectFeatureConfigId: (featureId: string) => void;
@@ -23,18 +24,21 @@ const DropdownMenu: FC<DropdownMenuProps> = ({ onSelectFeatureConfigId }) => {
2324
}, []);
2425

2526
return (
26-
<div>
27-
<select className="dropdown-button" onChange={handleChange}>
27+
<Form.Group controlId="featureConfigSelect">
28+
<Form.Select
29+
onChange={handleChange}
30+
className="grey-border rounded mb-2 p-2 ps-3 fs-6 font-monospace"
31+
>
2832
<option key={0} value="">
2933
Select Feature
3034
</option>
31-
{featureConfigs.map((featureId: string, index) => (
35+
{featureConfigs.map((featureId: string, index: number) => (
3236
<option key={index + 1} value={featureId}>
3337
{featureId}
3438
</option>
3539
))}
36-
</select>
37-
</div>
40+
</Form.Select>
41+
</Form.Group>
3842
);
3943
};
4044

src/ui/components/ExperimentBrowserPage.tsx

Lines changed: 86 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { FC, useCallback, useEffect, useState } from "react";
22
import { NimbusExperiment } from "@mozilla/nimbus-schemas";
3+
import { Table, Button, Container, Form, Row, Col } from "react-bootstrap";
34

45
const PROD_URL =
56
"https://experimenter.services.mozilla.com/api/v6/experiments/";
@@ -78,98 +79,101 @@ const ExperimentBrowserPage: FC = () => {
7879
};
7980

8081
return (
81-
<div className="experiment-viewer">
82-
<h1 className="experiment-viewer__title">{status} Experiments</h1>
83-
<div className="experiment-viewer__controls">
84-
<label className="experiment-viewer-controls__title">
85-
Environment:
86-
<select
87-
className="experiment-viewer-controls__dropdown"
82+
<Container>
83+
<h1 className="primary-fg my-3 fw-bold fs-3">{status} Experiments</h1>
84+
<Row className="mb-3 text-center">
85+
<Col md={4} className="d-flex align-items-center">
86+
<Form.Label className="primary-fg fs-6 fw-bold me-2">
87+
Environment:
88+
</Form.Label>
89+
<Form.Select
8890
value={environment}
8991
onChange={(e) => setEnvironment(e.target.value as Environment)}
92+
className="grey-border rounded align-items-center"
9093
>
91-
<option value="prod">Prod</option>
94+
<option value="prod">Production</option>
9295
<option value="stage">Stage</option>
93-
</select>
94-
</label>
95-
<label className="experiment-viewer-controls__title">
96-
Status:
97-
<select
98-
className="experiment-viewer-controls__dropdown"
96+
</Form.Select>
97+
</Col>
98+
<Col md={3} className="d-flex align-items-center">
99+
<Form.Label className="primary-fg fs-6 fw-bold me-2">
100+
Status:
101+
</Form.Label>
102+
<Form.Select
99103
value={status}
100104
onChange={(e) => setStatus(e.target.value as Status)}
105+
className="grey-border rounded align-items-center"
101106
>
102107
<option value="Live">Live</option>
103108
<option value="Preview">Preview</option>
104-
</select>
105-
</label>
106-
<button
107-
className="experiment-viewer-controls__button"
108-
onClick={() => fetchExperiments(true)}
109-
>
110-
Refresh
111-
</button>
112-
</div>
113-
<div className="experiment-viewer__list">
114-
<table>
115-
<thead>
116-
<tr>
117-
<th>Experiment</th>
118-
<th>Channel</th>
119-
<th>Version</th>
120-
<th>Status</th>
121-
<th>Force Enroll</th>
122-
</tr>
123-
</thead>
124-
<tbody>
125-
{experiments.map((experiment) => (
126-
<tr key={experiment.id}>
127-
<td>
128-
{experiment.userFacingName}:{" "}
129-
{experiment.userFacingDescription}
130-
</td>
131-
<td>{experiment.channel}</td>
132-
<td>{experiment.schemaVersion}</td>
133-
<td>
134-
{experiment.isEnrollmentPaused
135-
? "Enrolling"
136-
: "Enrollment Paused"}
137-
</td>
138-
<td>
139-
<select
140-
className="experiment-viewer-list__dropdown"
141-
value={selectedBranches[experiment.id]}
142-
onChange={(e) =>
143-
handleBranchChange(experiment.id, e.target.value)
144-
}
145-
>
146-
<option key="" value="">
147-
Select branch
109+
</Form.Select>
110+
</Col>
111+
<Col md={3} className="d-flex align-items-start">
112+
<Button
113+
onClick={() => fetchExperiments(true)}
114+
className="option-button primary-fg mx-2 py-2 px-3 rounded small-font fw-bold grey-border light-bg"
115+
>
116+
Refresh
117+
</Button>
118+
</Col>
119+
</Row>
120+
<Table hover>
121+
<thead>
122+
<tr>
123+
<th className="text-center primary-fg light-bg">Experiment</th>
124+
<th className="text-center primary-fg light-bg">Channel</th>
125+
<th className="text-center primary-fg light-bg">Version</th>
126+
<th className="text-center primary-fg light-bg">Status</th>
127+
<th className="text-center primary-fg light-bg">Force Enroll</th>
128+
</tr>
129+
</thead>
130+
<tbody>
131+
{experiments.map((experiment) => (
132+
<tr key={experiment.id}>
133+
<td className="align-middle ps-0 py-3 w-50">
134+
<strong>{experiment.userFacingName}</strong>:{" "}
135+
{experiment.userFacingDescription}
136+
</td>
137+
<td className="text-center align-middle px-2">
138+
{experiment.channel}
139+
</td>
140+
<td className="text-center align-middle px-2">
141+
{experiment.schemaVersion}
142+
</td>
143+
<td className="text-center align-middle px-2">
144+
{experiment.isEnrollmentPaused
145+
? "Enrolling"
146+
: "Enrollment Paused"}
147+
</td>
148+
<td className="text-end align-middle wide-column">
149+
<Form.Select
150+
value={selectedBranches[experiment.id]}
151+
onChange={(e) =>
152+
handleBranchChange(experiment.id, e.target.value)
153+
}
154+
className="grey-border small-font rounded p-2 m-0 font-monospace"
155+
>
156+
<option value="">Select branch</option>
157+
{experiment.branches?.map((branch) => (
158+
<option key={branch.slug} value={branch.slug}>
159+
{branch.slug}
148160
</option>
149-
{experiment.branches?.map((branch) => (
150-
<option key={branch.slug} value={branch.slug}>
151-
{branch.slug}
152-
</option>
153-
))}
154-
</select>
155-
<button
156-
className="experiment-viewer-list__button"
157-
onClick={() =>
158-
handleEnroll(
159-
experiment.id,
160-
selectedBranches[experiment.id],
161-
)
162-
}
163-
>
164-
Enroll
165-
</button>
166-
</td>
167-
</tr>
168-
))}
169-
</tbody>
170-
</table>
171-
</div>
172-
</div>
161+
))}
162+
</Form.Select>
163+
<Button
164+
className="option-button primary-fg py-1 my-1 rounded small-font fw-bold grey-border light-bg"
165+
onClick={() =>
166+
handleEnroll(experiment.id, selectedBranches[experiment.id])
167+
}
168+
>
169+
Enroll
170+
</Button>
171+
</td>
172+
</tr>
173+
))}
174+
</tbody>
175+
</Table>
176+
</Container>
173177
);
174178
};
175179

src/ui/components/ExperimentStorePage.tsx

Lines changed: 50 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { FC, useCallback, useEffect, useState } from "react";
2+
import { Table, Button, Container } from "react-bootstrap";
23

34
interface NimbusEnrollment {
45
slug: string;
@@ -53,54 +54,56 @@ const ExperimentStorePage: FC = () => {
5354
);
5455

5556
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>
57+
<Container>
58+
<h1 className="my-3 fw-bold fs-3 primary-fg">Experiment Store</h1>
59+
<Table hover>
60+
<thead>
61+
<tr>
62+
<th className="text-center primary-fg light-bg">Experiment</th>
63+
<th className="text-center primary-fg light-bg">featureIds</th>
64+
<th className="text-center primary-fg light-bg">isRollout</th>
65+
<th className="text-center primary-fg light-bg">Status</th>
66+
<th className="text-center primary-fg light-bg">Options</th>
67+
</tr>
68+
</thead>
69+
<tbody>
70+
{experiments?.map((experiment: NimbusEnrollment, index: number) => (
71+
<tr key={index}>
72+
<td className="align-middle ps-0 py-3">
73+
<strong>{experiment.slug}</strong> <br /> <br />
74+
{experiment.userFacingName}: {experiment.userFacingDescription}
75+
</td>
76+
<td className="text-center align-middle px-4">
77+
{experiment.featureIds.join(", ")}
78+
</td>
79+
<td className="text-center align-middle px-4">
80+
{experiment.isRollout ? "Yes" : "No"}
81+
</td>
82+
<td className="text-center align-middle px-4">
83+
{experiment.active ? "Active" : "Inactive"}
84+
</td>
85+
<td className="text-center align-middle px-4">
86+
{experiment.active ? (
87+
<Button
88+
onClick={() => unenrollExperiment(experiment.slug)}
89+
className="option-button primary-fg px-2 py-1 rounded grey-border light-bg"
90+
>
91+
Unenroll
92+
</Button>
93+
) : (
94+
<Button
95+
onClick={() => deleteExperiment(experiment.slug)}
96+
className="option-button primary-fg px-2 py-1 rounded grey-border light-bg"
97+
>
98+
Delete
99+
</Button>
100+
)}
101+
</td>
67102
</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>
103+
))}
104+
</tbody>
105+
</Table>
106+
</Container>
104107
);
105108
};
106109

0 commit comments

Comments
 (0)