Improve ops monitoring and chart loading
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
|
||||
function assert(condition: unknown, message: string) {
|
||||
if (!condition) throw new Error(message);
|
||||
}
|
||||
|
||||
export function runTests() {
|
||||
const projectRoot = process.cwd();
|
||||
const opsApi = fs.readFileSync(path.join(projectRoot, "lib", "ops-api.ts"), "utf8");
|
||||
const analyticsPage = fs.readFileSync(
|
||||
path.join(projectRoot, "components", "ops", "analytics", "AnalyticsPageClient.tsx"),
|
||||
"utf8",
|
||||
);
|
||||
const overviewPage = fs.readFileSync(
|
||||
path.join(projectRoot, "components", "ops", "overview", "OverviewPageClient.tsx"),
|
||||
"utf8",
|
||||
);
|
||||
const appAnalytics = fs.readFileSync(path.join(projectRoot, "lib", "app-analytics.ts"), "utf8");
|
||||
const analyticsRoute = fs.readFileSync(
|
||||
path.join(projectRoot, "app", "api", "analytics", "events", "route.ts"),
|
||||
"utf8",
|
||||
);
|
||||
const authMeRoute = fs.readFileSync(path.join(projectRoot, "app", "api", "auth", "me", "route.ts"), "utf8");
|
||||
|
||||
assert(
|
||||
opsApi.includes('"landing_view", "enter_terminal", "login_start", "signup_success", "trial_created", "payment_start", "payment_success"') &&
|
||||
opsApi.includes("diagnostics?:") &&
|
||||
opsApi.includes("traffic?:") &&
|
||||
opsApi.includes("uniqueActors"),
|
||||
"ops funnel API client must preserve the full standard funnel and expose diagnostics/traffic dimensions",
|
||||
);
|
||||
assert(
|
||||
analyticsPage.includes("落地页访问") &&
|
||||
analyticsPage.includes("进入终端") &&
|
||||
analyticsPage.includes("注册成功") &&
|
||||
analyticsPage.includes("鉴权降级") &&
|
||||
analyticsPage.includes("来源与设备") &&
|
||||
analyticsPage.includes("paymentSuccess?.count") &&
|
||||
!analyticsPage.includes("总注册") &&
|
||||
!analyticsPage.includes("点击高级功能"),
|
||||
"ops analytics page must show the real funnel semantics instead of stale index-based labels",
|
||||
);
|
||||
assert(
|
||||
overviewPage.includes("stepByKey.payment_success?.count") &&
|
||||
!overviewPage.includes("steps[5]?.count"),
|
||||
"ops overview must derive paid conversion from payment_success by key, not from a brittle funnel index",
|
||||
);
|
||||
assert(
|
||||
appAnalytics.includes("referrer: document.referrer") &&
|
||||
appAnalytics.includes("device_type: getDeviceType()") &&
|
||||
analyticsRoute.includes("cf-ipcountry") &&
|
||||
analyticsRoute.includes("user_agent"),
|
||||
"client analytics events must carry source, country, and device metadata for acquisition analysis",
|
||||
);
|
||||
assert(
|
||||
authMeRoute.includes('event_type: "degraded_auth_profile"') &&
|
||||
authMeRoute.includes("response_mode") &&
|
||||
authMeRoute.includes("trackAuthDiagnosticEvent"),
|
||||
"auth/me fallback paths must emit degraded_auth_profile diagnostics for ops monitoring",
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
|
||||
function assert(condition: unknown, message: string) {
|
||||
if (!condition) throw new Error(message);
|
||||
}
|
||||
|
||||
export function runTests() {
|
||||
const projectRoot = process.cwd();
|
||||
const opsApi = fs.readFileSync(path.join(projectRoot, "lib", "ops-api.ts"), "utf8");
|
||||
const paymentsPage = fs.readFileSync(
|
||||
path.join(projectRoot, "components", "ops", "payments", "PaymentsPageClient.tsx"),
|
||||
"utf8",
|
||||
);
|
||||
const billingRiskRoute = fs.readFileSync(
|
||||
path.join(projectRoot, "app", "api", "ops", "billing-risk", "route.ts"),
|
||||
"utf8",
|
||||
);
|
||||
const paymentsRoute = fs.readFileSync(
|
||||
path.join(projectRoot, "app", "api", "ops", "payments", "route.ts"),
|
||||
"utf8",
|
||||
);
|
||||
|
||||
assert(
|
||||
opsApi.includes("billingRisk") &&
|
||||
opsApi.includes("/api/ops/billing-risk") &&
|
||||
opsApi.includes("/api/ops/payments"),
|
||||
"ops client must expose billing risk and successful payments endpoints",
|
||||
);
|
||||
assert(
|
||||
paymentsPage.includes("支付与邀请风控流水") &&
|
||||
paymentsPage.includes("试用漏开") &&
|
||||
paymentsPage.includes("Intent 卡住") &&
|
||||
paymentsPage.includes("积分异常") &&
|
||||
paymentsPage.includes("推荐奖励结算") &&
|
||||
paymentsPage.includes("月度邀请封顶"),
|
||||
"ops payment page must surface trial, stuck intent, referral, points, and monthly-cap risk signals",
|
||||
);
|
||||
assert(
|
||||
billingRiskRoute.includes("requireOpsProxyAuth") &&
|
||||
billingRiskRoute.includes("/api/ops/billing-risk") &&
|
||||
billingRiskRoute.includes("no-store"),
|
||||
"billing risk proxy must stay ops-admin protected and uncached",
|
||||
);
|
||||
assert(
|
||||
paymentsRoute.includes("requireOpsProxyAuth") &&
|
||||
paymentsRoute.includes("/api/ops/payments") &&
|
||||
paymentsRoute.includes("no-store"),
|
||||
"ops payments proxy must stay ops-admin protected and uncached",
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
|
||||
function assert(condition: unknown, message: string) {
|
||||
if (!condition) throw new Error(message);
|
||||
}
|
||||
|
||||
export function runTests() {
|
||||
const projectRoot = process.cwd();
|
||||
const shell = fs.readFileSync(
|
||||
path.join(projectRoot, "components", "ops", "layout", "AdminShell.tsx"),
|
||||
"utf8",
|
||||
);
|
||||
const sidebar = fs.readFileSync(
|
||||
path.join(projectRoot, "components", "ops", "layout", "AdminSidebar.tsx"),
|
||||
"utf8",
|
||||
);
|
||||
const css = fs.readFileSync(
|
||||
path.join(projectRoot, "components", "ops", "layout", "AdminShell.module.css"),
|
||||
"utf8",
|
||||
);
|
||||
|
||||
assert(
|
||||
shell.includes("AdminShell.module.css") &&
|
||||
shell.includes("styles.root") &&
|
||||
shell.includes("styles.main") &&
|
||||
shell.includes("styles.content"),
|
||||
"ops shell must use scoped terminal-style admin theme classes",
|
||||
);
|
||||
assert(
|
||||
sidebar.includes("bg-white") &&
|
||||
sidebar.includes("bg-blue-50") &&
|
||||
sidebar.includes("shadow-[inset_3px_0_0_#2563eb]"),
|
||||
"ops sidebar must use the light terminal navigation treatment with blue active state",
|
||||
);
|
||||
assert(
|
||||
css.includes("#eef2f7") &&
|
||||
css.includes("[class~=\"text-white\"]") &&
|
||||
css.includes("[class*=\"bg-[#0f172a]\"]") &&
|
||||
css.includes("[class~=\"rounded-3xl\"]") &&
|
||||
css.includes("[class*=\"border-white/\"]") &&
|
||||
css.includes(".recharts-cartesian-grid line"),
|
||||
"ops scoped CSS must map legacy dark ops utility classes to the light terminal workbench palette",
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
|
||||
function assert(condition: unknown, message: string) {
|
||||
if (!condition) throw new Error(message);
|
||||
}
|
||||
|
||||
export function runTests() {
|
||||
const projectRoot = process.cwd();
|
||||
const opsApi = fs.readFileSync(path.join(projectRoot, "lib", "ops-api.ts"), "utf8");
|
||||
const systemPage = fs.readFileSync(
|
||||
path.join(projectRoot, "components", "ops", "system", "SystemPageClient.tsx"),
|
||||
"utf8",
|
||||
);
|
||||
const nextRoute = fs.readFileSync(
|
||||
path.join(projectRoot, "app", "api", "ops", "source-health", "route.ts"),
|
||||
"utf8",
|
||||
);
|
||||
|
||||
assert(
|
||||
opsApi.includes("sourceHealth") &&
|
||||
opsApi.includes("/api/ops/source-health"),
|
||||
"ops client must expose the city source health endpoint",
|
||||
);
|
||||
assert(
|
||||
systemPage.includes("城市数据源健康") &&
|
||||
systemPage.includes("sourceHealth") &&
|
||||
systemPage.includes("MGM、KNMI、IMS") &&
|
||||
systemPage.includes("断线") &&
|
||||
systemPage.includes("延迟"),
|
||||
"ops system page must show source latency/disconnect status for operational city sources",
|
||||
);
|
||||
assert(
|
||||
nextRoute.includes("requireOpsProxyAuth") &&
|
||||
nextRoute.includes("/api/ops/source-health") &&
|
||||
nextRoute.includes("no-store"),
|
||||
"source health proxy must stay ops-admin protected and uncached",
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user