feat(devops): separate public page server timing diagnostics
CI / check (push) Successful in 2m23s
CI / deploy (push) Successful in 1m13s
CI / publish-container (push) Successful in 44s

This commit is contained in:
Simo committed 2026-09-11 10:49:08 +02:00
1 parent d5091d1a73
commit baeb54aeb5
15 files changed
+315 -29

No files matched your search

+37 -1
View File
@@ -1,4 +1,5 @@
import { createTranslator } from "next-intl";
import { createElement } from "react";
import { renderToStaticMarkup } from "react-dom/server";
import { expect, it, vi } from "vitest";
import messages from "@/messages/en.json";
@@ -18,7 +19,9 @@ vi.mock("next/navigation", () => ({
},
}));
vi.mock("@/lib/performance-store", () => ({ readPerformance: mocks.read }));
vi.mock("@/components/link", () => ({ default: () => null }));
vi.mock("@/components/link", () => ({
default: ({ href }: { href: string }) => createElement("a", { href }),
}));
vi.mock("next-intl/server", () => ({
getTranslations: async () =>
@@ -75,3 +78,36 @@ it("renders filtered slow records and explains incomplete streaming timings", as
expect(html).toContain("Streaming response");
expect(html).toContain("Local diagnostics");
});
it("separates public component outcomes from HTTP response codes", async () => {
mocks.allowed = true;
mocks.read.mockResolvedValue({
shared: false,
samples: [
{
kind: "public-page",
outcome: "redirect",
operationId: "public-op",
route: "/me",
method: "PAGE",
status: 200,
at: 1000,
streaming: false,
metrics: {
durationMs: 2,
database: { count: 0, durationMs: 0, errors: 0 },
external: { count: 0, durationMs: 0, errors: 0 },
},
},
],
});
const html = renderToStaticMarkup(
await Page({ searchParams: Promise.resolve({ kind: "public-page" }) }),
);
expect(mocks.read).toHaveBeenLastCalledWith("public-page");
expect(html).toContain("Redirect");
expect(html).toContain(
"/admin/devops/performance?kind=public-page&q=&sort=slowest&slow=0",
);
expect(html).not.toContain("HTTP 200");
expect(html).toContain("browser rendering");
});
+29 -4
View File
@@ -13,9 +13,16 @@ export default async function PerformancePage({
if (!canAccess(permissions, PERMS.DEVOPS_VIEW, session.user.rank))
redirect("/admin");
const t = await getTranslations("pages.admin.performance");
const { samples, shared } = await readPerformance();
const params = await searchParams;
const kind = params.kind === "public-page" ? "public-page" : "admin-api";
const { samples, shared } = await readPerformance(kind);
const query = (typeof params.q === "string" ? params.q : "").slice(0, 100);
const refreshParams = new URLSearchParams({
kind,
q: query,
sort: params.sort === "recent" ? "recent" : "slowest",
slow: params.slow === "1" ? "1" : "0",
});
const visible = samples.filter(
(sample) =>
`${sample.route} ${sample.operationId}`.includes(query) &&
@@ -36,7 +43,10 @@ export default async function PerformancePage({
<Link href="/admin/devops" className="btn btn-outline">
DevOps
</Link>
<Link href="/admin/devops/performance" className="btn btn-outline">
<Link
href={`/admin/devops/performance?${refreshParams}`}
className="btn btn-outline"
>
{t("refresh")}
</Link>
</div>
@@ -44,6 +54,15 @@ export default async function PerformancePage({
{t(shared ? "shared" : "local")} {t("retention")}
</p>
<form className="admin-card flex flex-wrap gap-3">
<select
name="kind"
defaultValue={kind}
aria-label={t("kind")}
className="input"
>
<option value="admin-api">{t("adminApi")}</option>
<option value="public-page">{t("publicPage")}</option>
</select>
<input
name="q"
defaultValue={query}
@@ -71,10 +90,13 @@ export default async function PerformancePage({
<option value="1">{t("slow")}</option>
</select>
<button className="btn btn-primary" type="submit">
{t("search")}
{t("applyFilters")}
</button>
</form>
<p className="text-sm text-muted-foreground">{t("explanation")}</p>
{kind === "public-page" && (
<p className="text-sm text-muted-foreground">{t("publicBoundary")}</p>
)}
{visible.length === 0 ? (
<p className="admin-card">{t("empty")}</p>
) : (
@@ -90,7 +112,10 @@ export default async function PerformancePage({
{sample.method} {sample.route}
</p>
<p className="text-sm text-muted-foreground">
{new Date(sample.at).toISOString()} · HTTP {sample.status}
{new Date(sample.at).toISOString()} ·{" "}
{sample.kind === "public-page"
? t(`outcomes.${sample.outcome ?? "completed"}`)
: `HTTP ${sample.status}`}
</p>
</div>
<strong