feat(frontend): add status page for loading dataset
This commit is contained in:
111
frontend/src/pages/DatasetStatus.tsx
Normal file
111
frontend/src/pages/DatasetStatus.tsx
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
import { useEffect, useMemo, useState } from "react";
|
||||||
|
import axios from "axios";
|
||||||
|
import { useNavigate, useParams } from "react-router-dom";
|
||||||
|
import StatsStyling from "../styles/stats_styling";
|
||||||
|
|
||||||
|
type DatasetStatusResponse = {
|
||||||
|
status?: "processing" | "complete" | "error";
|
||||||
|
status_message?: string | null;
|
||||||
|
completed_at?: string | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const styles = StatsStyling;
|
||||||
|
const API_BASE_URL = "http://localhost:5000";
|
||||||
|
|
||||||
|
const DatasetStatusPage = () => {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const { datasetId } = useParams<{ datasetId: string }>();
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [status, setStatus] = useState<DatasetStatusResponse["status"]>("processing");
|
||||||
|
const [statusMessage, setStatusMessage] = useState("");
|
||||||
|
const parsedDatasetId = useMemo(() => Number(datasetId), [datasetId]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!Number.isInteger(parsedDatasetId) || parsedDatasetId <= 0) {
|
||||||
|
setLoading(false);
|
||||||
|
setStatus("error");
|
||||||
|
setStatusMessage("Invalid dataset id.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let pollTimer: number | undefined;
|
||||||
|
|
||||||
|
const pollStatus = async () => {
|
||||||
|
try {
|
||||||
|
const response = await axios.get<DatasetStatusResponse>(
|
||||||
|
`${API_BASE_URL}/dataset/${parsedDatasetId}/status`
|
||||||
|
);
|
||||||
|
|
||||||
|
const nextStatus = response.data.status ?? "processing";
|
||||||
|
setStatus(nextStatus);
|
||||||
|
setStatusMessage(String(response.data.status_message ?? ""));
|
||||||
|
setLoading(false);
|
||||||
|
|
||||||
|
if (nextStatus === "complete") {
|
||||||
|
window.setTimeout(() => {
|
||||||
|
navigate("/stats", { replace: true });
|
||||||
|
}, 800);
|
||||||
|
}
|
||||||
|
} catch (error: unknown) {
|
||||||
|
setLoading(false);
|
||||||
|
setStatus("error");
|
||||||
|
if (axios.isAxiosError(error)) {
|
||||||
|
const message = String(error.response?.data?.error || error.message || "Request failed");
|
||||||
|
setStatusMessage(message);
|
||||||
|
} else {
|
||||||
|
setStatusMessage("Unable to fetch dataset status.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
void pollStatus();
|
||||||
|
pollTimer = window.setInterval(() => {
|
||||||
|
if (status !== "complete" && status !== "error") {
|
||||||
|
void pollStatus();
|
||||||
|
}
|
||||||
|
}, 2000);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (pollTimer) {
|
||||||
|
window.clearInterval(pollTimer);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [navigate, parsedDatasetId, status]);
|
||||||
|
|
||||||
|
const isProcessing = loading || status === "processing";
|
||||||
|
const isError = status === "error";
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={styles.page}>
|
||||||
|
<div style={{ ...styles.container, maxWidth: 720 }}>
|
||||||
|
<div style={{ ...styles.card, marginTop: 28 }}>
|
||||||
|
<h1 style={{ margin: 0, fontSize: 28, color: "#111827" }}>
|
||||||
|
{isProcessing ? "Processing dataset..." : isError ? "Dataset processing failed" : "Dataset ready"}
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<p style={{ ...styles.sectionSubtitle, marginTop: 10 }}>
|
||||||
|
{isProcessing &&
|
||||||
|
"Your dataset is being analyzed. This page will redirect to stats automatically once complete."}
|
||||||
|
{isError && "There was an issue while processing your dataset. Please review the error details."}
|
||||||
|
{status === "complete" && "Processing complete. Redirecting to your stats now..."}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
...styles.card,
|
||||||
|
marginTop: 12,
|
||||||
|
borderColor: isError ? "rgba(185, 28, 28, 0.28)" : "rgba(0,0,0,0.06)",
|
||||||
|
background: isError ? "#fff5f5" : "#ffffff",
|
||||||
|
color: isError ? "#991b1b" : "#374151",
|
||||||
|
boxShadow: "none",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{statusMessage || (isProcessing ? "Waiting for updates from the worker queue..." : "No details provided.")}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DatasetStatusPage;
|
||||||
@@ -46,12 +46,14 @@ const UploadPage = () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const datasetId = Number(response.data.dataset_id);
|
||||||
|
|
||||||
setReturnMessage(
|
setReturnMessage(
|
||||||
`Upload queued successfully (dataset #${response.data.dataset_id}). Redirecting to insights...`
|
`Upload queued successfully (dataset #${datasetId}). Redirecting to processing status...`
|
||||||
);
|
);
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
navigate("/stats");
|
navigate(`/dataset/${datasetId}/status`);
|
||||||
}, 400);
|
}, 400);
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
setHasError(true);
|
setHasError(true);
|
||||||
|
|||||||
Reference in New Issue
Block a user