Refactor pipeline task handling and UI flows
This commit is contained in:
113
convex/audits.ts
113
convex/audits.ts
@@ -1,9 +1,11 @@
|
||||
import { v } from "convex/values";
|
||||
|
||||
import { normalizeListLimit } from "./domain";
|
||||
import { internal } from "./_generated/api";
|
||||
import { internalMutation, mutation, query } from "./_generated/server";
|
||||
import type { Doc, Id } from "./_generated/dataModel";
|
||||
import type { MutationCtx, QueryCtx } from "./_generated/server";
|
||||
import { getAuditProgressForStep } from "../lib/audits/progress";
|
||||
|
||||
export const AUDIT_REVIEW_NOTICE_AFTER_MS = 30 * 24 * 60 * 60 * 1000;
|
||||
const DETAIL_EVIDENCE_LIMIT = 50;
|
||||
@@ -63,12 +65,27 @@ type AuditDashboardRow =
|
||||
id: Id<"agentRuns">;
|
||||
runId: Id<"agentRuns">;
|
||||
leadId: Id<"leads"> | null;
|
||||
runType: Doc<"agentRuns">["type"];
|
||||
title: string;
|
||||
checkedDomain: string;
|
||||
status: Doc<"agentRuns">["status"];
|
||||
latestStage: string;
|
||||
stageStatus: Doc<"agentRuns">["status"];
|
||||
errorSummary: string | null;
|
||||
progress: {
|
||||
step: number;
|
||||
total: number;
|
||||
label: string;
|
||||
percent: number;
|
||||
};
|
||||
retry: {
|
||||
attempt: number;
|
||||
maxAttempts: number;
|
||||
isRetrying: boolean;
|
||||
lastRetryReason: string | null;
|
||||
canRetry: boolean;
|
||||
};
|
||||
canRetry: boolean;
|
||||
pageCount: number;
|
||||
checkedPages: string[];
|
||||
createdAt: number;
|
||||
@@ -104,6 +121,38 @@ const latestGenerationStage = (stages: Doc<"auditGenerations">[]) => {
|
||||
return [...stages].sort((a, b) => b.updatedAt - a.updatedAt)[0] ?? null;
|
||||
};
|
||||
|
||||
const progressForRun = (
|
||||
run: Doc<"agentRuns">,
|
||||
latestStage: Doc<"auditGenerations"> | null,
|
||||
) => {
|
||||
const fallback = getAuditProgressForStep(latestStage?.stage ?? run.currentStep);
|
||||
|
||||
return {
|
||||
step: run.progressStep ?? fallback.step,
|
||||
total: run.progressTotal ?? fallback.total,
|
||||
label: run.progressLabel ?? fallback.label,
|
||||
percent: run.progressPercent ?? fallback.percent,
|
||||
};
|
||||
};
|
||||
|
||||
const retryForRun = (run: Doc<"agentRuns">) => {
|
||||
const attempt = run.attempt ?? 1;
|
||||
const maxAttempts = run.maxAttempts ?? 3;
|
||||
const canRetry =
|
||||
run.type === "audit" &&
|
||||
(run.status === "failed" || run.status === "canceled") &&
|
||||
attempt < maxAttempts;
|
||||
|
||||
return {
|
||||
attempt,
|
||||
maxAttempts,
|
||||
isRetrying:
|
||||
(run.status === "pending" || run.status === "running") && attempt > 1,
|
||||
lastRetryReason: run.lastRetryReason ?? null,
|
||||
canRetry,
|
||||
};
|
||||
};
|
||||
|
||||
const normalizeComparableAuditUrl = (value: string | null | undefined) => {
|
||||
const trimmed = value?.trim();
|
||||
if (!trimmed) {
|
||||
@@ -727,6 +776,31 @@ export const list = query({
|
||||
},
|
||||
});
|
||||
|
||||
export const retryAuditRun = mutation({
|
||||
args: {
|
||||
runId: v.id("agentRuns"),
|
||||
},
|
||||
handler: async (ctx, args) => {
|
||||
await requireOperator(ctx);
|
||||
|
||||
const run = await ctx.db.get(args.runId);
|
||||
if (!run || run.type !== "audit") {
|
||||
throw new Error("Audit-Run wurde nicht gefunden.");
|
||||
}
|
||||
|
||||
const status = run.status;
|
||||
if (status !== "failed" && status !== "canceled") {
|
||||
throw new Error("Nur final fehlgeschlagene Audits können neu gestartet werden.");
|
||||
}
|
||||
|
||||
await ctx.scheduler.runAfter(0, internal.auditWorkflow.restartAuditWorkflow, {
|
||||
runId: args.runId,
|
||||
});
|
||||
|
||||
return { runId: args.runId };
|
||||
},
|
||||
});
|
||||
|
||||
export const listDashboardRows = query({
|
||||
args: {
|
||||
limit: v.optional(v.number()),
|
||||
@@ -771,29 +845,50 @@ export const listDashboardRows = query({
|
||||
}
|
||||
}
|
||||
|
||||
const rootAuditRuns = await ctx.db
|
||||
.query("agentRuns")
|
||||
.withIndex("by_type", (q) => q.eq("type", "audit"))
|
||||
.order("desc")
|
||||
.take(limit);
|
||||
const rootAuditRunLeadIds = new Set(
|
||||
rootAuditRuns
|
||||
.map((run) => run.leadId)
|
||||
.filter((leadId): leadId is Id<"leads"> => leadId !== undefined),
|
||||
);
|
||||
|
||||
const generationRuns = await ctx.db
|
||||
.query("agentRuns")
|
||||
.withIndex("by_type", (q) => q.eq("type", "audit_generation"))
|
||||
.order("desc")
|
||||
.take(limit);
|
||||
|
||||
for (const run of generationRuns) {
|
||||
for (const run of [...rootAuditRuns, ...generationRuns]) {
|
||||
if (!run.leadId) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (
|
||||
run.type === "audit_generation" &&
|
||||
rootAuditRunLeadIds.has(run.leadId)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const directFinalAudit = run.auditId ? await ctx.db.get(run.auditId) : null;
|
||||
const leadFinalAudits = await ctx.db
|
||||
.query("audits")
|
||||
.withIndex("by_leadId", (q) => q.eq("leadId", run.leadId as Id<"leads">))
|
||||
.take(1);
|
||||
|
||||
const shouldHideBehindFinalAudit =
|
||||
run.status === "succeeded" || run.type === "audit_generation";
|
||||
|
||||
if (
|
||||
finalAuditRunIds.has(run._id) ||
|
||||
(run.auditId && finalAuditIds.has(run.auditId)) ||
|
||||
directFinalAudit ||
|
||||
finalAuditLeadIds.has(run.leadId) ||
|
||||
leadFinalAudits.length > 0
|
||||
(shouldHideBehindFinalAudit && finalAuditRunIds.has(run._id)) ||
|
||||
(shouldHideBehindFinalAudit && run.auditId && finalAuditIds.has(run.auditId)) ||
|
||||
(shouldHideBehindFinalAudit && directFinalAudit) ||
|
||||
(shouldHideBehindFinalAudit && finalAuditLeadIds.has(run.leadId)) ||
|
||||
(shouldHideBehindFinalAudit && leadFinalAudits.length > 0)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
@@ -806,18 +901,24 @@ export const listDashboardRows = query({
|
||||
const latestStage = latestGenerationStage(stages);
|
||||
const lead = await ctx.db.get(run.leadId);
|
||||
const checkedDomain = domainFromLead(lead);
|
||||
const progress = progressForRun(run, latestStage);
|
||||
const retry = retryForRun(run);
|
||||
|
||||
rows.push({
|
||||
kind: "generation",
|
||||
id: run._id,
|
||||
runId: run._id,
|
||||
leadId: run.leadId,
|
||||
runType: run.type,
|
||||
title: lead?.companyName ?? checkedDomain,
|
||||
checkedDomain,
|
||||
status: run.status,
|
||||
latestStage: latestStage?.stage ?? run.currentStep ?? "audit_generation",
|
||||
stageStatus: latestStage?.status ?? run.status,
|
||||
errorSummary: run.errorSummary ?? latestStage?.errorSummary ?? null,
|
||||
progress,
|
||||
retry,
|
||||
canRetry: retry.canRetry,
|
||||
pageCount: 0,
|
||||
checkedPages: [],
|
||||
createdAt: run.createdAt,
|
||||
|
||||
Reference in New Issue
Block a user