feat(hk): connect delivery failures to precise diagnostic records
This commit is contained in:
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");
|
||||
});
|
||||
@@ -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">
|
||||
|
||||
@@ -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={
|
||||
|
||||
Reference in new issue
Block a user