diff --git a/typescript/bot/src/utils/api-requests.ts b/typescript/bot/src/utils/api-requests.ts
index 7cf4449ab..6c374358c 100644
--- a/typescript/bot/src/utils/api-requests.ts
+++ b/typescript/bot/src/utils/api-requests.ts
@@ -126,7 +126,7 @@ export async function PerformScoreImport(
authToken,
);
- if (pollRes.success) {
+ if (pollRes.success && pollRes.statusCode < 400) {
if (pollRes.body.importStatus === "completed") {
// is there even a nice way around this --
// why *are* we nested so deeply?
@@ -138,15 +138,27 @@ export async function PerformScoreImport(
return pollRes.body.import;
}
- if (interaction) {
- void interaction.editReply(
- `Importing Scores: ${pollRes.body.progress.description}..`,
- );
- }
+ if (pollRes.body.importStatus === "ongoing") {
+ const progress = pollRes.body.progress;
+ const description =
+ progress &&
+ typeof progress === "object" &&
+ "description" in progress &&
+ typeof progress.description === "string"
+ ? progress.description
+ : "Importing.";
- // eslint-disable-next-line no-await-in-loop
- await Sleep(1000);
- } else {
+ if (interaction) {
+ void interaction.editReply(`Importing Scores: ${description}..`);
+ }
+
+ // eslint-disable-next-line no-await-in-loop
+ await Sleep(1000);
+ continue;
+ }
+ }
+
+ if (!pollRes.success || pollRes.statusCode >= 400) {
// silly kai bug they won't ever fix. hacking around it in the bot here.
if (/attempting reauthentication/u.exec(pollRes.description)) {
throw new Error(`Failed to import scores.
@@ -157,6 +169,10 @@ Please go to ${Env.TACHI_SERVER_LOCATION}/u/me/integrations/services to un-link
throw new Error(`Failed to import scores. ${pollRes.description}.`);
}
+
+ throw new Error(
+ `Failed to import scores. ${pollRes.description ?? "Unexpected poll response."}.`,
+ );
}
}
diff --git a/typescript/client/src/components/imports/ImportStateRenderer.tsx b/typescript/client/src/components/imports/ImportStateRenderer.tsx
index 3acbdce0c..9ec627a35 100644
--- a/typescript/client/src/components/imports/ImportStateRenderer.tsx
+++ b/typescript/client/src/components/imports/ImportStateRenderer.tsx
@@ -30,7 +30,7 @@ export default function ImportStateRenderer({ state: s }: { state: ImportStates
have, this might take a while.
-
{s.progressInfo.description ?? "Importing."}..
+ {s.progressInfo?.description ?? "Importing."}..
>
) : s.state === "done" ? (
<>
diff --git a/typescript/client/src/components/util/import/useImport.ts b/typescript/client/src/components/util/import/useImport.ts
index 28d44b112..01889a84b 100644
--- a/typescript/client/src/components/util/import/useImport.ts
+++ b/typescript/client/src/components/util/import/useImport.ts
@@ -64,27 +64,44 @@ export default function useImport(url: string, options: RequestInit) {
`/imports/${initRes.body.importID}/poll-status`,
);
- if (pollRes.success) {
- if (pollRes.body.importStatus === "completed") {
- isImportFinished = true;
- if (importState.state === "done") {
- setImportState({ ...importState, import: pollRes.body.import });
- } else {
- setImportState({ state: "done", import: pollRes.body.import });
- }
- } else {
- setImportState({
- state: "waiting_processing",
- progressInfo: pollRes.body.progress,
- });
-
- await Sleep(1000);
- }
- } else {
+ if (!pollRes.success || pollRes.statusCode >= 400) {
setImportState({ state: "failed", error: pollRes.description });
isImportFinished = true;
+ continue;
+ }
+
+ if (pollRes.body.importStatus === "completed") {
+ isImportFinished = true;
+ setImportState({ state: "done", import: pollRes.body.import });
+ } else if (pollRes.body.importStatus === "ongoing") {
+ const progress = pollRes.body.progress;
+ const description =
+ progress &&
+ typeof progress === "object" &&
+ "description" in progress &&
+ typeof progress.description === "string"
+ ? progress.description
+ : "Importing.";
+
+ setImportState({
+ state: "waiting_processing",
+ progressInfo: { description },
+ });
+
+ await Sleep(1000);
+ } else {
+ setImportState({
+ state: "failed",
+ error: pollRes.description ?? "Import failed.",
+ });
+ isImportFinished = true;
}
}
+ } else {
+ setImportState({
+ state: "failed",
+ error: initRes.description ?? "Import failed.",
+ });
}
};
diff --git a/typescript/server/src/server/router/api/v1/imports/router.test.ts b/typescript/server/src/server/router/api/v1/imports/router.test.ts
index 97fdbef50..64dc5bcaa 100644
--- a/typescript/server/src/server/router/api/v1/imports/router.test.ts
+++ b/typescript/server/src/server/router/api/v1/imports/router.test.ts
@@ -285,4 +285,25 @@ describe("GET /api/v1/imports/:importID/poll-status", () => {
expect(res.body.body.importStatus).toBe("completed");
expect(res.body.body.import.importID).toBe(FakeImport.importID);
});
+
+ it("returns 400 with success false when import_tracker records a failure", async () => {
+ const { id: userID } = await seedUser({ username: "poll_failed_user" });
+
+ await DB.insertInto("import_tracker")
+ .values({
+ import_id: "failed-import",
+ user_id: userID,
+ import_type: "file/batch-manual",
+ user_intent: true,
+ time_started: new Date().toISOString(),
+ error: { message: "Invalid score file.", statusCode: 400 },
+ })
+ .execute();
+
+ const res = await mockApi.get("/api/v1/imports/failed-import/poll-status");
+
+ expect(res.status).toBe(400);
+ expect(res.body.success).toBe(false);
+ expect(res.body.description).toBe("Invalid score file.");
+ });
});
diff --git a/typescript/server/src/server/router/api/v1/imports/router.ts b/typescript/server/src/server/router/api/v1/imports/router.ts
index e55e1eb93..a0859c775 100644
--- a/typescript/server/src/server/router/api/v1/imports/router.ts
+++ b/typescript/server/src/server/router/api/v1/imports/router.ts
@@ -210,15 +210,16 @@ API_V1_ROUTER.add("GET /imports/:importID/poll-status", async ({ params }) => {
switch (tracker.type) {
case "ONGOING":
- return success("Import is ongoing.", { importStatus: "ongoing", progress: 0 });
+ return success("Import is ongoing.", {
+ importStatus: "ongoing",
+ progress: { description: "Importing scores." },
+ });
case "FAILED":
- return {
- $status: tracker.error.statusCode ?? 500,
- body: {},
- description: tracker.error.message,
- success: true as const,
- };
+ throw new ExpectedErr(
+ tracker.error.statusCode ?? 500,
+ tracker.error.message,
+ );
default:
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -242,15 +243,13 @@ API_V1_ROUTER.add("GET /imports/:importID/poll-status", async ({ params }) => {
const tracker = await GetImportTrackerByImportId(params.importID);
if (tracker?.type === "FAILED") {
- return {
- $status: tracker.error.statusCode ?? 500,
- body: {},
- description: tracker.error.message,
- success: true as const,
- };
+ throw new ExpectedErr(tracker.error.statusCode ?? 500, tracker.error.message);
}
- return success("Import is ongoing.", { importStatus: "ongoing", progress: 0 });
+ return success("Import is ongoing.", {
+ importStatus: "ongoing",
+ progress: { description: "Importing scores." },
+ });
}
throw new ExpectedErr(500, "Unrecognised job queue state.");