Skip to content

Commit 4c80ff3

Browse files
Distinguish pending and processing in frontend
1 parent e4b50c3 commit 4c80ff3

9 files changed

Lines changed: 85 additions & 43 deletions

File tree

frontend/src/api/api.ts

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,6 @@ type ApiRequestInit<Model = never> = Omit<RequestInit, "method"> & {
1616
| { responseModel?: never; responseReturn?: never }
1717
);
1818

19-
export class ResultIsPending extends Error {}
20-
2119
async function apiRequest(
2220
method: "GET" | "DELETE",
2321
path: string,
@@ -112,13 +110,7 @@ export const startSimulation = async (modelInput: ModelInput): Promise<string> =
112110
};
113111

114112
export const getResultForSimulation = async (simulationId: string): Promise<SimulationResults> => {
115-
const data = await apiRequest("GET", `/simulations/${simulationId}/result`, { responseModel: SimulationResults });
116-
117-
if (data.status === "pending" || data.status === "processing") {
118-
throw new ResultIsPending();
119-
}
120-
121-
return data;
113+
return await apiRequest("GET", `/simulations/${simulationId}/result`, { responseModel: SimulationResults });
122114
};
123115

124116
export const startGridSimulation = async (grid: CreateGridSimulation): Promise<string> => {

frontend/src/components/GridSimulation/GridResultStep.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@ const GridResultStep: React.FC<GridResultStepProps> = ({ result, isLoading, erro
2626
if (result === undefined) {
2727
return <NoResults />;
2828
}
29-
if (result.simulations.every((sim) => sim.status === "pending")) {
30-
return <Working />;
29+
if (result.simulations.every((sim) => sim.status === "pending" || sim.status === "processing")) {
30+
return <Working status={result.status} />;
3131
}
3232
return <GridResults result={result} />;
3333
};

frontend/src/components/GridSimulation/GridResults.tsx

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import DownloadButton from "@/components/DownloadButton";
1515
import { useAvailableModels } from "@/contexts/ModelContext";
1616
import { buildModelSections, phaseLabel } from "@/utils/modelUtils";
1717
import ModelAccordionLayout, { AccordionItem } from "@/components/ModelAccordionLayout";
18+
import { getCurrentModelId } from "@/dto/SimulationResults";
1819

1920
interface GridResultsProps {
2021
result: GridSimulationResult;
@@ -94,7 +95,7 @@ const GridPhaseChart: React.FC<GridPhaseChartProps> = ({ result, modelIndex, pha
9495
))}
9596
{selectedSubstances.map((substance) => (
9697
<Table.Cell key={substance}>
97-
{sim.status === "done"
98+
{sim.results[modelIndex]
9899
? formatConcentration(pointOutput(sim, substance, modelIndex, phaseKind) ?? 0)
99100
: sim.status === "error"
100101
? "error"
@@ -145,7 +146,21 @@ const GridResults: React.FC<GridResultsProps> = ({ result }) => {
145146
const sections = buildModelSections(inputModels, models);
146147

147148
const erroredSimulations = simulations.filter((sim) => sim.status === "error");
148-
const pendingSimulations = simulations.filter((sim) => sim.status === "pending");
149+
const pendingSims = simulations.filter((sim) => sim.status === "pending");
150+
const processingSims = simulations.filter((sim) => sim.status === "processing");
151+
const remainingCount = pendingSims.length + processingSims.length;
152+
153+
const countByModel = (sims: typeof simulations): Map<string, number> => {
154+
const counts = new Map<string, number>();
155+
for (const sim of sims) {
156+
const id = getCurrentModelId(sim) ?? "unknown";
157+
counts.set(id, (counts.get(id) ?? 0) + 1);
158+
}
159+
return counts;
160+
};
161+
162+
const formatModelCounts = (byModel: Map<string, number>): string =>
163+
[...byModel.entries()].map(([id, count]) => `${count} ${id}`).join(", ");
149164

150165
const xAxisSubstance = axes[0]?.substance ?? "Unknown";
151166
const modelLabel = inputModels.map((model) => model.modelId).join(" → ") || "Unknown model";
@@ -165,13 +180,14 @@ const GridResults: React.FC<GridResultsProps> = ({ result }) => {
165180
<strong>{modelLabel}</strong>.
166181
</Typography>
167182

168-
{pendingSimulations.length > 0 && (
183+
{remainingCount > 0 && (
169184
<Banner style={{ marginBottom: "1rem" }}>
170185
<Banner.Icon variant="info"></Banner.Icon>
171186
<Banner.Message>
172-
Showing partial results. {simulations.length - pendingSimulations.length} of{" "}
173-
{simulations.length} runs have finished; the remaining {pendingSimulations.length} appear as
174-
they complete.
187+
Showing partial results. {simulations.length - remainingCount - erroredSimulations.length} of{" "}
188+
{simulations.length} runs have finished.
189+
{processingSims.length > 0 && <> Processing: {formatModelCounts(countByModel(processingSims))}.</>}
190+
{pendingSims.length > 0 && <> Pending: {formatModelCounts(countByModel(pendingSims))}.</>}
175191
</Banner.Message>
176192
</Banner>
177193
)}

frontend/src/components/Simulation/ResultStep.tsx

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { convertSimulationQueriesResultToTabulatedData, convertTabulatedDataToCSVFormat } from "@/functions/Formatting";
22
import { Banner } from "@equinor/eds-core-react";
33
import DownloadButton from "../DownloadButton";
4-
import { SimulationResults } from "@/dto/SimulationResults";
4+
import { SimulationResults, getCurrentModelId } from "@/dto/SimulationResults";
55
import Working from "./Working";
66
import NoResults from "./NoResults";
77
import Results from "./Results";
@@ -20,7 +20,7 @@ const ResultStep: React.FC<ResultStepProps> = ({ simulationResults, isLoading, e
2020
</Banner>
2121
);
2222
}
23-
if (isLoading) {
23+
if (isLoading && !simulationResults) {
2424
return <Working />;
2525
} else if (simulationResults === undefined) {
2626
return <NoResults />;
@@ -31,10 +31,19 @@ const ResultStep: React.FC<ResultStepProps> = ({ simulationResults, isLoading, e
3131
<Banner.Message>{simulationResults.error ?? "Simulation failed with an unknown error"}</Banner.Message>
3232
</Banner>
3333
);
34-
} else {
35-
return (
36-
<>
34+
}
35+
36+
const isInProgress = simulationResults.status === "pending" || simulationResults.status === "processing";
37+
38+
return (
39+
<>
40+
{isInProgress && (
41+
<Working status={simulationResults.status} currentModelId={getCurrentModelId(simulationResults)} />
42+
)}
43+
{simulationResults.results.length > 0 && (
3744
<Results simulationResults={simulationResults} />
45+
)}
46+
{simulationResults.status === "done" && (
3847
<div
3948
style={{
4049
display: "flex",
@@ -50,12 +59,12 @@ const ResultStep: React.FC<ResultStepProps> = ({ simulationResults, isLoading, e
5059
}),
5160
])}
5261
fileName={`AcidWatch-ModelResults-${new Date().toISOString().replace(/[:.]/g, "-")}.csv`}
53-
isLoading={isLoading}
62+
isLoading={false}
5463
/>
5564
</div>
56-
</>
57-
);
58-
}
65+
)}
66+
</>
67+
);
5968
};
6069

6170
export default ResultStep;

frontend/src/components/Simulation/Working.tsx

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,21 @@ const BlendedWorking = styled(CenteredImage)`
66
mix-blend-mode: multiply;
77
`;
88

9-
const Working = () => <BlendedWorking src={icon} caption="Working..." />;
9+
const statusLabels: Record<string, string> = {
10+
pending: "Pending — waiting in queue",
11+
processing: "Processing — simulation running",
12+
};
13+
14+
const Working: React.FC<{ status?: string; currentModelId?: string }> = ({ status, currentModelId }) => {
15+
let caption = "Working...";
16+
if (status) {
17+
caption = statusLabels[status] ?? "Working...";
18+
if (currentModelId) {
19+
caption += ` (${currentModelId})`;
20+
}
21+
caption += "…";
22+
}
23+
return <BlendedWorking src={icon} caption={caption} />;
24+
};
1025

1126
export default Working;

frontend/src/dto/SimulationResults.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,3 +54,6 @@ export const SimulationResults = z.object({
5454
error: z.nullable(z.string()).optional(),
5555
});
5656
export type SimulationResults = z.infer<typeof SimulationResults>;
57+
58+
export const getCurrentModelId = (sim: SimulationResults): string | undefined =>
59+
sim.input.models[sim.results.length]?.modelId;

frontend/src/hooks/useSimulationQueriesResult.ts

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import { useMutation, useQueryClient } from "@tanstack/react-query";
1+
import { useMutation } from "@tanstack/react-query";
22
import { useAvailableModels } from "@/contexts/ModelContext";
3-
import { getResultForSimulation, ResultIsPending, startSimulation } from "@/api/api";
3+
import { getResultForSimulation, startSimulation } from "@/api/api";
44
import { ExperimentResult } from "@/dto/ExperimentResult";
55
import { SimulationResults } from "@/dto/SimulationResults";
66
import { useState } from "react";
@@ -13,15 +13,23 @@ export type Status = {
1313
result?: SimulationResults;
1414
};
1515

16+
const pollUntilDone = async (simulationId: string, interval = 2000): Promise<SimulationResults> => {
17+
while (true) {
18+
const result = await getResultForSimulation(simulationId);
19+
if (result.status === "done" || result.status === "error") {
20+
return result;
21+
}
22+
await new Promise((resolve) => setTimeout(resolve, interval));
23+
}
24+
};
25+
1626
export const useSimulationQueries = (): {
1727
startExperiment: (experiment: ExperimentResult, modelId: string) => void;
1828
statuses: Record<string, Status>;
1929
} => {
2030
const { models } = useAvailableModels();
2131
const [statuses, setStatuses] = useState<Record<string, Status>>({});
2232

23-
const queryClient = useQueryClient();
24-
2533
const updateStatus = (key: string, update: Partial<Status>) =>
2634
setStatuses((prev) => ({ ...prev, [key]: { ...prev[key], ...update } }));
2735

@@ -76,11 +84,7 @@ export const useSimulationQueries = (): {
7684
if (!simulationId) return;
7785

7886
try {
79-
const result = await queryClient.fetchQuery({
80-
queryKey: ["simulation", simulationId],
81-
queryFn: () => getResultForSimulation(simulationId),
82-
retry: (_retryCount, error) => error instanceof ResultIsPending,
83-
});
87+
const result = await pollUntilDone(simulationId);
8488
updateStatus(key, { status: "done", result });
8589
} catch (error) {
8690
updateStatus(key, { status: "failed", error });

frontend/src/pages/Compare.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import React from "react";
22
import { useSearchParams } from "react-router-dom";
33
import { useQueries } from "@tanstack/react-query";
4-
import { getResultForSimulation, ResultIsPending } from "@/api/api";
4+
import { getResultForSimulation } from "@/api/api";
55
import { MainContainer } from "@/components/styles";
66
import { Typography, CircularProgress, Banner } from "@equinor/eds-core-react";
77
import { SimulationResults, getCo2RichConcentrations } from "@/dto/SimulationResults";
@@ -36,8 +36,10 @@ const CompareSimulations: React.FC<{ simulationIds: string[] }> = ({ simulationI
3636
queries: simulationIds.map((id) => ({
3737
queryKey: ["simulation", id],
3838
queryFn: () => getResultForSimulation(id),
39-
retry: (_count: number, error: Error) => error instanceof ResultIsPending,
40-
retryDelay: () => 2000,
39+
refetchInterval: (query: { state: { data?: SimulationResults } }) => {
40+
const status = query.state.data?.status;
41+
return status === "pending" || status === "processing" ? 2000 : false;
42+
},
4143
})),
4244
});
4345

frontend/src/pages/Models.tsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import { useMutation, useQuery } from "@tanstack/react-query";
66
import {
77
getResultForSimulation,
88
getGridSimulationResult,
9-
ResultIsPending,
109
startSimulation,
1110
startGridSimulation,
1211
} from "@/api/api";
@@ -76,8 +75,10 @@ const Models: React.FC = () => {
7675
queryKey: ["simulation", simulationId],
7776
queryFn: () => getResultForSimulation(simulationId!),
7877
enabled: simulationId !== undefined,
79-
retry: (_count, error) => error instanceof ResultIsPending,
80-
retryDelay: () => 2000,
78+
refetchInterval: (query) => {
79+
const status = query.state.data?.status;
80+
return status === "pending" || status === "processing" ? 2000 : false;
81+
},
8182
});
8283

8384
const {
@@ -88,7 +89,7 @@ const Models: React.FC = () => {
8889
queryKey: ["grid-simulation", gridId],
8990
queryFn: () => getGridSimulationResult(gridId!),
9091
enabled: gridId !== undefined,
91-
refetchInterval: (query) => (query.state.data?.status === "pending" ? 2000 : false),
92+
refetchInterval: (query) => (query.state.data?.status !== "done" ? 2000 : false),
9293
});
9394

9495
useEffect(() => {

0 commit comments

Comments
 (0)