WFDASH-WEBAPP/scripts/perf/dashboard-live-timing.ts
SJCoetz 12fa65a481 feat: add new order, invoice, and dashboard management tools
- Add server-side `/api/v1/dashboard` endpoint to reduce client-side payload and round trips
- Implement invoice upload and order-item SKU update APIs with complete test suites
- Refactor order and order-items list endpoints (including MCP tools) to support proper PocketBase filter and sort params
- Fix pagination validation for order-items route to clamp page ≥1 and perPage 1-500
- Add redirects in next.config.ts to map underscore collection paths to hyphenated API routes
- Improve buyer tasks page to avoid redundant tab count refetches when switching tabs
- Refactor collection confirm components with editable SKUs and updated accessibility labels
- Add cached admin PocketBase auth to minimize repeated authentication calls
- Add performance testing scripts for dashboard endpoint latency
- Update quotes list route to default to excluding archived counts
- Fix photo strip component text and labels for non-invoice documents
- Refactor sprint page to lazy load supplier/customer data for pricing modal
- Clean up unused imports in test files
- Add full test coverage for all new APIs, services, and components
2026-08-05 23:00:54 +02:00

94 lines
No EOL
4.1 KiB
TypeScript

// Quick live timing of the dashboard's PocketBase calls.
import { performance } from "node:perf_hooks";
import PocketBase from "pocketbase";
async function main() {
const url = process.env.NEXT_PUBLIC_POCKETBASE_URL || "http://127.0.0.1:8090";
const email = process.env.PB_ADMIN_EMAIL;
const password = process.env.PB_ADMIN_PASSWORD;
if (!email || !password) {
console.error("Missing PB_ADMIN_EMAIL / PB_ADMIN_PASSWORD");
process.exit(1);
}
const pb = new PocketBase(url);
await pb.collection("_superusers").authWithPassword(email, password);
console.log("PB:", url);
const twelveWeeksAgo = new Date(Date.now() - 84 * 24 * 60 * 60 * 1000).toISOString();
const startOfMonth = new Date(new Date().getFullYear(), new Date().getMonth(), 1).toISOString();
type Timed = { label: string; ms: number; count?: number };
const results: Timed[] = [];
const measure = async (label: string, fn: () => Promise<{ items?: unknown[]; totalItems?: number } | unknown[]>) => {
const a = performance.now();
try {
const r = (await fn()) as { items?: unknown[]; totalItems?: number } | unknown[];
const b = performance.now();
const count = Array.isArray(r)
? r.length
: Array.isArray(r?.items)
? (r.items as unknown[]).length
: r?.totalItems;
results.push({ label, ms: b - a, count });
} catch (e) {
const b = performance.now();
console.log(`!! ${label} ERR ${(e as { message?: string })?.message ?? ""}`);
results.push({ label, ms: b - a, count: -1 });
}
};
console.log("\n--- Sequential timing of each dashboard call ---");
await measure("customers total count", () => pb.collection("customers").getList(1, 1));
await measure("quotes active count", () =>
pb.collection("quotes").getList(1, 1, {
filter: '(status = "new" || status = "claimed" || status = "processed" || status = "emailed" || status = "approval_pending") && is_template != true',
})
);
await measure("orders this month count", () =>
pb.collection("orders").getList(1, 1, { filter: `created >= "${startOfMonth}"` })
);
await measure("follow-ups getFullList", () =>
pb.collection("quotes").getFullList({
filter: `follow_up_due != "" && follow_up_due <= "${new Date().toISOString()}" && status != "complete" && status != "cancelled" && status != "rejected" && is_template != true`,
sort: "follow_up_due",
expand: "customer",
})
);
await measure("quotes 500 + customer expand", () =>
pb.collection("quotes").getList(1, 500, { filter: "is_template != true", expand: "customer" })
);
await measure("orders 12w 500", () =>
pb.collection("orders").getList(1, 500, { sort: "-created", filter: `created >= "${twelveWeeksAgo}"` })
);
await measure("customers 12w 500", () =>
pb.collection("customers").getList(1, 500, { sort: "-created", filter: `created >= "${twelveWeeksAgo}"` })
);
await measure("order_items month 500", () =>
pb.collection("order_items").getList(1, 500, { filter: `created >= "${startOfMonth}"` })
);
await measure("order_items 12w 500", () =>
pb.collection("order_items").getList(1, 500, { filter: `created >= "${twelveWeeksAgo}"` })
);
await measure("quotes 12w 500", () =>
pb.collection("quotes").getList(1, 500, { filter: `created >= "${twelveWeeksAgo}" && is_template != true` })
);
await measure("stock_items FULL LIST", () => pb.collection("stock_items").getFullList());
console.log("\nlabel ms rows/total");
console.log("------------------------------------------------------ -------");
let totalMs = 0;
for (const r of results) {
totalMs += r.ms;
console.log(`${r.label.padEnd(45)} ${r.ms.toFixed(0).padStart(6)} ${String(r.count ?? "-").padStart(8)}`);
}
console.log("------------------------------------------------------ -------");
console.log(`TOTAL sequential: ${totalMs.toFixed(0).padStart(6)} ms`);
console.log("(In the page, Promise.all parallelises these; real wall-clock ≈ slowest single call.)");
}
main().catch((e) => {
console.error(e);
process.exit(1);
});