feat(devops): separate public page server timing diagnostics
This commit is contained in:
1 parent
d5091d1a73
commit
baeb54aeb5
15 files changed
+315
-29
No files matched your search
@@ -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");
|
||||
});
|
||||
@@ -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
|
||||
|
||||
Reference in new issue
Block a user