147 lines
3.7 KiB
TypeScript
147 lines
3.7 KiB
TypeScript
import {
|
|
FatalError,
|
|
RetryableError,
|
|
getWorkflowMetadata,
|
|
sleep,
|
|
} from "workflow";
|
|
import { admin, required } from "./supabase";
|
|
import {
|
|
LinearSupportClient,
|
|
LinearPermissionError,
|
|
} from "./linear-support-client";
|
|
import { purgeClosedBugReport } from "./bug-retention";
|
|
|
|
const linearClient = () =>
|
|
new LinearSupportClient(
|
|
process.env.LINEAR_API_KEY!,
|
|
process.env.LINEAR_TEAM_ID!,
|
|
process.env.LINEAR_PROJECT_ID,
|
|
);
|
|
async function record(id: string) {
|
|
const result = await admin()
|
|
.from("bug_reports")
|
|
.select("*")
|
|
.eq("id", id)
|
|
.maybeSingle();
|
|
if (result.error) required(result);
|
|
return result.data;
|
|
}
|
|
|
|
export async function cleanupDoneBugReport(
|
|
id: string,
|
|
closedAt: string,
|
|
claim: string,
|
|
) {
|
|
"use workflow";
|
|
const run = getWorkflowMetadata().workflowRunId;
|
|
try {
|
|
const deadline = await markLinearDone(id, closedAt);
|
|
if (deadline) {
|
|
await sleep(new Date(deadline));
|
|
await purgeDone(id, closedAt);
|
|
}
|
|
} catch {
|
|
await releaseCleanup(id, closedAt, claim, run);
|
|
throw new Error("Bug report cleanup paused; the saved report will retry.");
|
|
}
|
|
}
|
|
async function markLinearDone(
|
|
id: string,
|
|
closedAt: string,
|
|
): Promise<string | null> {
|
|
"use step";
|
|
try {
|
|
const report = await record(id);
|
|
if (!report || report.state !== "done" || report.closed_at !== closedAt)
|
|
return null;
|
|
if (!report.linear_done_synced) {
|
|
await linearClient().done(id);
|
|
required(
|
|
await admin()
|
|
.from("bug_reports")
|
|
.update({ linear_done_synced: true, cleanup_error: null })
|
|
.eq("id", id)
|
|
.eq("state", "done")
|
|
.eq("closed_at", closedAt)
|
|
.select("id"),
|
|
);
|
|
}
|
|
return report.delete_after ?? null;
|
|
} catch (error) {
|
|
if (error instanceof LinearPermissionError)
|
|
throw new FatalError("The Linear key needs Write permission.");
|
|
throw new RetryableError(
|
|
"Could not move the Linear report to Done. Check the API key's Write permission.",
|
|
{ retryAfter: "1m" },
|
|
);
|
|
}
|
|
}
|
|
markLinearDone.maxRetries = 10;
|
|
|
|
async function purgeDone(id: string, closedAt: string) {
|
|
"use step";
|
|
try {
|
|
await purgeClosedBugReport(id, closedAt, {
|
|
record: () => record(id),
|
|
linear: linearClient(),
|
|
begin: async () =>
|
|
required(
|
|
await admin().rpc("begin_bug_purge", {
|
|
p_report: id,
|
|
p_closed_at: closedAt,
|
|
}),
|
|
),
|
|
reopen: async () => {
|
|
required(
|
|
await admin().rpc("set_bug_report_done", {
|
|
p_report: id,
|
|
p_done: false,
|
|
}),
|
|
);
|
|
},
|
|
removeFiles: async (paths) => {
|
|
required(await admin().storage.from("minegeo-support").remove(paths));
|
|
},
|
|
finish: async () => {
|
|
required(
|
|
await admin().rpc("finish_bug_purge", {
|
|
p_report: id,
|
|
p_closed_at: closedAt,
|
|
}),
|
|
);
|
|
},
|
|
});
|
|
} catch {
|
|
throw new RetryableError(
|
|
"Report deletion could not be confirmed. The saved record will retry.",
|
|
{ retryAfter: "5m" },
|
|
);
|
|
}
|
|
}
|
|
purgeDone.maxRetries = 10;
|
|
|
|
async function releaseCleanup(
|
|
id: string,
|
|
closedAt: string,
|
|
claim: string,
|
|
run: string,
|
|
) {
|
|
"use step";
|
|
required(
|
|
await admin()
|
|
.from("bug_reports")
|
|
.update({
|
|
cleanup_run_id: null,
|
|
cleanup_lease_until: new Date(Date.now() + 60000).toISOString(),
|
|
cleanup_error:
|
|
"Support cleanup paused. Check Linear Write permission and retry.",
|
|
})
|
|
.eq("id", id)
|
|
.eq("closed_at", closedAt)
|
|
.in("cleanup_run_id", [claim, run])
|
|
.select("id"),
|
|
);
|
|
console.error("Bug report cleanup paused", { id, run });
|
|
}
|
|
releaseCleanup.maxRetries = 10;
|