fix: better error handling on failed imports

This commit is contained in:
zk
2026-05-18 17:12:38 +00:00
parent 97ae10d316
commit 2d4d4550ff
5 changed files with 94 additions and 41 deletions
+25 -9
View File
@@ -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."}.`,
);
}
}
@@ -30,7 +30,7 @@ export default function ImportStateRenderer({ state: s }: { state: ImportStates
have, this might take a while.
</div>
<Divider />
<div>{s.progressInfo.description ?? "Importing."}..</div>
<div>{s.progressInfo?.description ?? "Importing."}..</div>
</>
) : s.state === "done" ? (
<>
@@ -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.",
});
}
};
@@ -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.");
});
});
@@ -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.");