feat(hk): connect delivery failures to precise diagnostic records
CI / check (push) Successful in 3m31s
CI / deploy (push) Successful in 18s
CI / publish-container (push) Successful in 1m12s

This commit is contained in:
Simo committed 2026-09-13 20:25:03 +02:00
1 parent 275a574203
commit 60e49c1ec9
17 files changed
+253 -20

No files matched your search

@@ -1,3 +1,4 @@
import { renderToStaticMarkup } from "react-dom/server";
import { expect, it, vi } from "vitest";
const mocks = vi.hoisted(() => ({ read: vi.fn(), access: vi.fn() }));
@@ -21,6 +22,14 @@ vi.mock("@/lib/error-monitor", () => ({
}));
vi.mock("@/components/link", () => ({ default: () => null }));
vi.mock("next-intl/server", () => ({
getTranslations: async () => (key: string) => key,
}));
vi.mock("./actions", () => ({
assignCmsError: vi.fn(),
resolveCmsError: vi.fn(),
}));
import Page from "./page";
it("rejects access before reading diagnostic data", async () => {
@@ -30,3 +39,39 @@ it("rejects access before reading diagnostic data", async () => {
);
expect(mocks.read).not.toHaveBeenCalled();
});
it("shows the referenced occurrence while retaining the current group resolution", async () => {
mocks.access.mockImplementation(
(_permissions, permission) => permission === "devops.view",
);
const old = {
id: "old-reference",
fingerprint: "same-group",
at: "2026-09-10T10:00:00Z",
release: "old-release",
source: "server",
event: "Delivery failed",
message: "Failure",
stack: "OLDER_ERROR_STACK",
context: { deliveryId: "older-delivery" },
resolved: false,
};
const newer = {
...old,
id: "new-reference",
at: "2026-09-11T10:00:00Z",
release: "new-release",
stack: "NEWER_ERROR_STACK",
context: { deliveryId: "newer-delivery" },
resolved: true,
};
mocks.read.mockResolvedValue({ records: [old, newer], truncated: false });
const html = renderToStaticMarkup(
await Page({ searchParams: Promise.resolve({ q: "old-reference" }) }),
);
expect(html).toContain("OLDER_ERROR_STACK");
expect(html).toContain("older-delivery");
expect(html).not.toContain("NEWER_ERROR_STACK");
expect(html).not.toContain("newer-delivery");
expect(html).toContain("resolved");
});
+8 -3
View File
@@ -159,9 +159,14 @@ export default async function CmsErrorsPage({
.map(([fingerprint, events]) => {
const metrics = summarizeErrorGroup(events);
const r = latestByGroup.get(fingerprint);
if (!r) return null;
const groupLatest = latestByGroup.get(fingerprint);
if (!groupLatest || !metrics.latest) return null;
// Details follow the selected occurrence; triage belongs to the whole group.
const r = {
...metrics.latest,
resolved: groupLatest.resolved,
assignment: groupLatest.assignment,
};
return (
<details key={fingerprint} className="admin-card overflow-hidden">
+27 -3
View File
@@ -3,10 +3,18 @@ import { getTranslations } from "next-intl/server";
import Link from "@/components/link";
import { DeliveryCard } from "@/features/operations/delivery-card";
import { deliveryContext } from "@/features/operations/delivery-context";
import {
deliveryDiagnosticId,
isDeliveryReference,
} from "@/features/operations/delivery-diagnostics";
import { DeliveryRetry } from "@/features/operations/retry-button";
import { listEffects } from "@/features/operations/server";
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
export default async function DeliveryPage() {
export default async function DeliveryPage({
searchParams,
}: {
searchParams: Promise<Record<string, string | string[] | undefined>>;
}) {
const { session, permissions } = await getAdminContext();
if (!canAccess(permissions, PERMS.DEVOPS_VIEW, session.user.rank))
redirect("/admin");
@@ -16,9 +24,17 @@ export default async function DeliveryPage() {
catalog: canAccess(permissions, PERMS.CATALOG_VIEW, session.user.rank),
};
const t = await getTranslations("pages.admin.deliveries");
let selectedId: string | undefined;
let items: Awaited<ReturnType<typeof listEffects>> | null = null;
try {
items = await listEffects();
const params = await searchParams;
selectedId =
typeof params.id === "string"
? params.id
: params.id === undefined
? undefined
: "invalid";
items = await listEffects(selectedId);
} catch {
/* Preserve the distinction between unavailable and empty history. */
}
@@ -29,7 +45,14 @@ export default async function DeliveryPage() {
<h1 className="text-2xl font-semibold">{t("title")}</h1>
<p className="text-muted-foreground">{t("scope")}</p>
<div className="flex flex-wrap gap-3">
<Link href="/admin/devops/deliveries" className="btn btn-outline">
<Link
href={
isDeliveryReference(selectedId)
? `/admin/devops/deliveries?id=${selectedId}`
: "/admin/devops/deliveries"
}
className="btn btn-outline"
>
{t("refresh")}
</Link>
{canAccess(permissions, PERMS.ASSETS_IMPORT, session.user.rank) && (
@@ -59,6 +82,7 @@ export default async function DeliveryPage() {
createdAt: timestamp(item.createdAt),
availableAt: timestamp(item.availableAt),
hasError: !!item.lastError,
errorId: deliveryDiagnosticId(item.lastError),
context: deliveryContext(item.kind, item.resultJson, access),
}}
retry={