Skip to content

Commit 3af6c10

Browse files
committed
fix: rebase
1 parent 2d8f1ef commit 3af6c10

13 files changed

Lines changed: 262 additions & 260 deletions

webapp/src/api/experiments.ts

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -19,17 +19,10 @@ export async function createExperiment(
1919

2020
export async function getExperiments(projectId: string): Promise<Experiment[]> {
2121
try {
22-
const result = await fetchApi(
22+
return await fetchApi(
2323
`/projects/${projectId}/experiments`,
2424
ExperimentSchema.array(),
2525
);
26-
// Drop experiments that somehow lack a usable id — they cannot be
27-
// selected, fetched, or rendered downstream. Keeping them would
28-
// surface as unselectable rows whose click silently clears the
29-
// selection.
30-
return result.filter(
31-
(e) => typeof e.id === "string" && e.id.length > 0,
32-
);
3326
} catch (error) {
3427
console.error("[getExperiments] failed", error);
3528
return [];
@@ -54,12 +47,7 @@ export async function getProjectEmissionsByExperiment(
5447
}
5548

5649
try {
57-
const result = await fetchApi(url, ExperimentReportSchema.array());
58-
return result.filter(
59-
(r) =>
60-
typeof r.experiment_id === "string" &&
61-
r.experiment_id.length > 0,
62-
);
50+
return await fetchApi(url, ExperimentReportSchema.array());
6351
} catch (error) {
6452
console.error("[getProjectEmissionsByExperiment] failed", error);
6553
return [];

webapp/src/api/mock/index.ts

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ export function installMockFetch(): void {
1414

1515
const apiBase = (import.meta.env.VITE_API_URL ?? "").replace(/\/$/, "");
1616
const apiPathPrefix = apiBase
17-
? new URL(apiBase).pathname.replace(/\/$/, "")
17+
? (safeUrl(apiBase)?.pathname.replace(/\/$/, "") ?? "")
1818
: "";
1919
const realFetch = window.fetch.bind(window);
2020

@@ -29,16 +29,26 @@ export function installMockFetch(): void {
2929
? input.toString()
3030
: input.url;
3131

32-
const isApiCall = apiBase ? rawUrl.startsWith(apiBase) : false;
32+
// With an explicit VITE_API_URL we only intercept requests aimed at
33+
// it. With no VITE_API_URL (the default dev/mock setup), every
34+
// same-origin fetch is treated as an API call — Vite's static
35+
// assets are loaded via <script>/<link>/<img>, not fetch(), so
36+
// this interception is safe.
37+
const absoluteUrl = new URL(rawUrl, window.location.origin);
38+
const isApiCall = apiBase
39+
? rawUrl.startsWith(apiBase)
40+
: absoluteUrl.origin === window.location.origin;
3341

3442
if (!isApiCall) return realFetch(input, init);
3543

36-
const url = new URL(rawUrl);
3744
const relPath =
38-
apiPathPrefix && url.pathname.startsWith(apiPathPrefix)
39-
? url.pathname.slice(apiPathPrefix.length) || "/"
40-
: url.pathname;
41-
const relUrl = new URL(relPath + url.search, "http://mock.local");
45+
apiPathPrefix && absoluteUrl.pathname.startsWith(apiPathPrefix)
46+
? absoluteUrl.pathname.slice(apiPathPrefix.length) || "/"
47+
: absoluteUrl.pathname;
48+
const relUrl = new URL(
49+
relPath + absoluteUrl.search,
50+
"http://mock.local",
51+
);
4252
const method = (init?.method ?? "GET").toUpperCase();
4353
const parsedBody = parseBody(init?.body);
4454
const result = resolveMock(relUrl, method, parsedBody);
@@ -61,6 +71,14 @@ export function installMockFetch(): void {
6171
);
6272
}
6373

74+
function safeUrl(value: string): URL | null {
75+
try {
76+
return new URL(value);
77+
} catch {
78+
return null;
79+
}
80+
}
81+
6482
function parseBody(body: BodyInit | null | undefined): unknown {
6583
if (typeof body !== "string") return undefined;
6684
try {

webapp/src/api/runs.ts

Lines changed: 2 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,10 @@ import { z } from "zod";
22
import { fetchApi } from "./client";
33
import {
44
Emission,
5-
EmissionSchema,
65
EmissionsTimeSeries,
76
RunMetadata,
87
RunMetadataSchema,
98
RunReport,
10-
RunReportSchema,
119
} from "./schemas";
1210

1311
export async function getRunMetadata(
@@ -85,39 +83,12 @@ export async function getEmissionsTimeSeries(
8583
),
8684
]);
8785

88-
const metadata: RunMetadata = {
89-
timestamp: runMetadataData.timestamp,
90-
experiment_id: runMetadataData.experiment_id,
91-
os: runMetadataData.os,
92-
python_version: runMetadataData.python_version,
93-
codecarbon_version: runMetadataData.codecarbon_version,
94-
cpu_count: runMetadataData.cpu_count,
95-
cpu_model: runMetadataData.cpu_model,
96-
gpu_count: runMetadataData.gpu_count,
97-
gpu_model: runMetadataData.gpu_model,
98-
longitude: runMetadataData.longitude,
99-
latitude: runMetadataData.latitude,
100-
region: runMetadataData.region,
101-
provider: runMetadataData.provider,
102-
ram_total_size: runMetadataData.ram_total_size,
103-
tracking_mode: runMetadataData.tracking_mode,
104-
};
105-
10686
const emissions: Emission[] = emissionsData.items.map((item) => ({
87+
...item,
10788
emission_id: item.run_id,
108-
timestamp: item.timestamp,
109-
emissions_sum: item.emissions_sum,
110-
emissions_rate: item.emissions_rate,
111-
cpu_power: item.cpu_power,
112-
gpu_power: item.gpu_power,
113-
ram_power: item.ram_power,
114-
cpu_energy: item.cpu_energy,
115-
gpu_energy: item.gpu_energy,
116-
ram_energy: item.ram_energy,
117-
energy_consumed: item.energy_consumed,
11889
}));
11990

120-
return { runId, emissions, metadata };
91+
return { runId, emissions, metadata: runMetadataData };
12192
} catch (error) {
12293
console.error("[getEmissionsTimeSeries] failed", error);
12394
return { runId, emissions: [], metadata: null };

webapp/src/api/schemas.ts

Lines changed: 25 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -57,21 +57,22 @@ export const ProjectTokenSchema = z.object({
5757
});
5858
export type IProjectToken = z.infer<typeof ProjectTokenSchema>;
5959

60-
// `id` is required when reading from the API (the backend never returns
61-
// experiments without one). It is allowed to be absent only on the
62-
// create path — see `ExperimentInputSchema` below.
60+
// Backend's `Optional[...]` fields are serialized as JSON `null` (Pydantic
61+
// default, no `exclude_none`). Zod's `.optional()` accepts `undefined`
62+
// only — `.nullish()` accepts `null | undefined`, which is what we need
63+
// for every column the backend marks as nullable.
6364
export const ExperimentSchema = z.object({
6465
id: z.string().min(1),
65-
timestamp: z.string().optional(),
66+
timestamp: z.string().nullish(),
6667
name: z.string(),
6768
description: z.string(),
68-
on_cloud: z.boolean().optional(),
69+
on_cloud: z.boolean().nullish(),
6970
project_id: z.string(),
70-
country_name: z.string().optional(),
71-
country_iso_code: z.string().optional(),
72-
region: z.string().optional(),
73-
cloud_provider: z.string().optional(),
74-
cloud_region: z.string().optional(),
71+
country_name: z.string().nullish(),
72+
country_iso_code: z.string().nullish(),
73+
region: z.string().nullish(),
74+
cloud_provider: z.string().nullish(),
75+
cloud_region: z.string().nullish(),
7576
});
7677
export type Experiment = z.infer<typeof ExperimentSchema>;
7778

@@ -86,7 +87,7 @@ export const ExperimentReportSchema = z.object({
8687
emissions: z.number(),
8788
energy_consumed: z.number(),
8889
duration: z.number(),
89-
description: z.string().optional(),
90+
description: z.string().nullish(),
9091
});
9192
export type ExperimentReport = z.infer<typeof ExperimentReportSchema>;
9293

@@ -117,19 +118,19 @@ export type Emission = z.infer<typeof EmissionSchema>;
117118
export const RunMetadataSchema = z.object({
118119
timestamp: z.string(),
119120
experiment_id: z.string(),
120-
os: z.string(),
121-
python_version: z.string(),
122-
codecarbon_version: z.string(),
123-
cpu_count: z.number(),
124-
cpu_model: z.string(),
125-
gpu_count: z.number(),
126-
gpu_model: z.string(),
127-
longitude: z.number(),
128-
latitude: z.number(),
129-
region: z.string(),
130-
provider: z.string(),
131-
ram_total_size: z.number(),
132-
tracking_mode: z.string(),
121+
os: z.string().nullish(),
122+
python_version: z.string().nullish(),
123+
codecarbon_version: z.string().nullish(),
124+
cpu_count: z.number().nullish(),
125+
cpu_model: z.string().nullish(),
126+
gpu_count: z.number().nullish(),
127+
gpu_model: z.string().nullish(),
128+
longitude: z.number().nullish(),
129+
latitude: z.number().nullish(),
130+
region: z.string().nullish(),
131+
provider: z.string().nullish(),
132+
ram_total_size: z.number().nullish(),
133+
tracking_mode: z.string().nullish(),
133134
});
134135
export type RunMetadata = z.infer<typeof RunMetadataSchema>;
135136

webapp/src/components/createOrganizationModal.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ const CreateOrganizationModal: React.FC<ModalProps> = ({
106106
<Label htmlFor="org-description">
107107
Organization Description
108108
</Label>
109-
</div>
109+
</div>
110110
<div className="space-y-2">
111111
<Label htmlFor="org-description">
112112
Organization Description

webapp/src/components/createProjectModal.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ const CreateProjectModal: React.FC<ModalProps> = ({
102102
<Label htmlFor="project-description">
103103
Project Description
104104
</Label>
105-
</div>
105+
</div>
106106
<div className="space-y-2">
107107
<Label htmlFor="project-description">
108108
Project Description

webapp/src/components/navbar.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,13 @@ export default function NavBar({
3636
const [selected, setSelected] = useState<string | null>(null);
3737
const navigate = useNavigate();
3838
const [isCollapsed, setIsCollapsed] = useState(false);
39-
const [selectedOrg, setSelectedOrg] = useState<string | null>(null);
39+
const [selectedOrg, setSelectedOrg] = useState<string | null>(() => {
40+
try {
41+
return localStorage.getItem("organizationId");
42+
} catch {
43+
return null;
44+
}
45+
});
4046
const iconStyles = "h-4 w-4 flex-shrink-0 text-muted-foreground";
4147
const { pathname } = useLocation();
4248
const newOrgModal = useModal();
@@ -156,6 +162,7 @@ export default function NavBar({
156162
<NavItem
157163
isSelected={selected === "projects"}
158164
onClick={() => {
165+
if (!selectedOrg) return;
159166
setSelected("projects");
160167
setSheetOpened?.(false);
161168
navigate(`/${selectedOrg}/projects`);
@@ -168,6 +175,7 @@ export default function NavBar({
168175
<NavItem
169176
isSelected={selected === "members"}
170177
onClick={() => {
178+
if (!selectedOrg) return;
171179
setSelected("members");
172180
setSheetOpened?.(false);
173181
navigate(`/${selectedOrg}/members`);

0 commit comments

Comments
 (0)