fix code queue stats panel

This commit is contained in:
Codex
2026-05-20 05:54:09 +00:00
parent bd334bab4a
commit 47350d283c
5 changed files with 572 additions and 102 deletions
+238 -13
View File
@@ -155,6 +155,8 @@ const FRONTEND_CHECK_NAMES = [
"frontend:code-queue-trace-full-load",
"frontend:code-queue-judge-wrap",
"frontend:code-queue-error-red-markers",
"frontend:code-queue-stats-visible",
"frontend:code-queue-stats-degraded-visible",
"frontend:code-queue-retry-attempt-trace-current",
"frontend:code-queue-step-missing-diagnostic",
"frontend:code-queue-judge-feedback-attempt-order",
@@ -184,6 +186,10 @@ const CODE_QUEUE_RETRY_TRACE_FIXTURE_CHECK_NAMES = [
"frontend:code-queue-step-missing-diagnostic",
"frontend:code-queue-judge-feedback-attempt-order",
] as const;
const CODE_QUEUE_STATS_FIXTURE_CHECK_NAMES = [
"frontend:code-queue-stats-visible",
"frontend:code-queue-stats-degraded-visible",
] as const;
const ALL_E2E_CHECK_NAMES = [
...NETWORK_CHECK_NAMES,
@@ -511,8 +517,9 @@ function wantsPrefix(options: E2ERunOptions, prefix: string): boolean {
function publicUrls(config: UniDeskConfig): PublicUrls {
const frontendTarget = String(process.env.UNIDESK_E2E_FRONTEND || "prod").trim().toLowerCase();
const frontendPort = frontendTarget === "dev" ? config.network.devFrontend.port : config.network.frontend.port;
const frontendUrlOverride = String(process.env.UNIDESK_E2E_FRONTEND_URL || "").trim();
return {
frontendUrl: `http://${config.network.publicHost}:${frontendPort}`,
frontendUrl: frontendUrlOverride || `http://${config.network.publicHost}:${frontendPort}`,
providerIngressHealthUrl: `http://${config.network.publicHost}:${config.network.providerIngress.port}/health`,
providerIngressWsUrl: `ws://${config.network.publicHost}:${config.network.providerIngress.port}/ws/provider`,
blockedCoreUrl: `http://${config.network.publicHost}:${config.network.core.port}`,
@@ -1274,6 +1281,187 @@ async function runCodeQueueRetryTraceFixture(page: Page, frontendUrl: string): P
}
}
function codeQueueStatsFixtureOverview(statistics: any, taskId: string): any {
const now = "2026-05-20T05:20:00.000Z";
const task = {
id: taskId,
queueId: "issue13-stats-fixture",
status: "succeeded",
providerId: "D601-dev",
executionMode: "default",
model: "gpt-5.5",
cwd: "/workspace-dev",
createdAt: "2026-05-20T04:00:00.000Z",
startedAt: "2026-05-20T04:01:00.000Z",
finishedAt: "2026-05-20T04:08:00.000Z",
updatedAt: now,
displayPromptPreview: "Issue 13 stats fixture task",
currentAttempt: 1,
maxAttempts: 99,
attemptCount: 1,
stepCount: 2,
outputCount: 2,
terminalUnread: false,
summaryOnly: true,
lastJudge: { decision: "complete", confidence: 1, reason: "fixture", source: "fixture" },
timing: { durationMs: 420000, totalElapsedMs: 480000 },
};
return {
ok: true,
queue: {
activeTaskId: "",
activeTaskIds: [],
total: 1,
unreadTerminal: 0,
counts: { succeeded: 1, queued: 0, running: 0, retry_wait: 0, judging: 0 },
queues: [{ id: "issue13-stats-fixture", name: "Issue 13 Stats Fixture", total: 1, counts: { succeeded: 1 } }],
mainProviderId: "D601-dev",
defaultProviderId: "D601-dev",
defaultWorkdir: "/workspace-dev",
defaultWorkdirByProvider: { "D601-dev": "/workspace-dev" },
codeModels: ["gpt-5.5", "gpt-5.4-mini", "gpt-5.4"],
executionProviders: [{ id: "D601-dev", label: "D601 dev", defaultWorkdir: "/workspace-dev" }],
executionModes: [{ id: "default", label: "默认容器/本机" }],
},
statistics,
tasks: [task],
selected: null,
pagination: { returned: 1, total: 1, hasMore: false },
};
}
async function runCodeQueueStatsFixture(page: Page, frontendUrl: string, mode: "visible" | "degraded"): Promise<any> {
const taskId = `codex_issue13_stats_${mode}`;
const visibleStats = {
generatedAt: "2026-05-20T05:20:00.000Z",
timezone: "Asia/Shanghai",
source: "fixture-dailyBuckets",
range: { startDate: "2026-05-18", endDate: "2026-05-20" },
totals: { executedTasks: 9, completedTasks: 7, retryAttempts: 3, avgDurationMs: 420000, durationSamples: 7 },
dailyBuckets: [
{ date: "2026-05-18", tasks: 2, terminalCount: 2, retries: 1, averageDurationMs: 300000, samples: 2 },
{ date: "2026-05-19", tasks: 3, terminalCount: 2, retries: 0, averageDurationMs: 360000, samples: 2 },
{ date: "2026-05-20", tasks: 4, terminalCount: 3, retries: 2, averageDurationMs: 540000, samples: 3 },
],
};
const degradedStats = {
generatedAt: "2026-05-20T05:20:00.000Z",
timezone: "Asia/Shanghai",
source: "fixture-stats-api",
degraded: true,
unavailable: true,
reason: "fixture stats API timeout: database projection unavailable",
range: { startDate: "2026-05-18", endDate: "2026-05-20" },
totals: { executedTasks: 0, completedTasks: 0, retryAttempts: 0, avgDurationMs: null, durationSamples: 0 },
daily: [],
};
const statistics = mode === "visible" ? visibleStats : degradedStats;
const overview = codeQueueStatsFixtureOverview(statistics, taskId);
const routePattern = "**/api/microservices/code-queue/proxy/api/**";
const requests: string[] = [];
const handler = async (route: any, request: any): Promise<void> => {
if (request.method() !== "GET") {
await route.continue();
return;
}
const url = new URL(request.url());
const path = url.pathname.replace(/^\/api\/microservices\/code-queue\/proxy/u, "");
requests.push(`${path}${url.search}`);
if (path === "/api/tasks/overview") {
await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify(overview) });
return;
}
if (path === "/api/tasks/stats") {
if (mode === "degraded") {
await route.fulfill({ status: 503, contentType: "application/json", body: JSON.stringify({ ok: false, error: degradedStats.reason, statistics: degradedStats }) });
} else {
await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ ok: true, statistics, queue: overview.queue }) });
}
return;
}
if (path === "/api/workdirs") {
await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ ok: true, workdirs: [{ providerId: "D601-dev", executionMode: "default", path: "/workspace-dev", source: "fixture" }] }) });
return;
}
if (path === "/api/queues") {
await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ ok: true, queues: overview.queue.queues, summary: overview.queue }) });
return;
}
if (path === `/api/tasks/${encodeURIComponent(taskId)}/trace-summary` || path === `/api/tasks/${taskId}/trace-summary`) {
await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ ok: true, summary: { ...overview.tasks[0], finalResponse: "Fixture complete", attempts: [], prompt: {}, execution: { durationMs: 420000, traceLineCount: 2, stepCount: 2 } } }) });
return;
}
await route.continue();
};
await page.route(routePattern, handler);
try {
await page.goto(`${frontendUrl}/app/code-queue/`, { waitUntil: "domcontentloaded", timeout: 15000 });
await page.waitForSelector('[data-testid="code-queue-page"]', { timeout: 15000, state: "attached" });
await page.waitForSelector('[data-testid="codex-stats-chart"]', { timeout: 15000 });
if (mode === "visible") {
await page.waitForSelector('[data-testid="codex-stats-day-2026-05-20"]', { timeout: 15000 });
} else {
await page.waitForSelector('[data-testid="codex-stats-diagnostic"]', { timeout: 15000 });
}
const metrics = await page.evaluate(() => {
const hero = document.querySelector('[data-testid="codex-stats-panel"]') as HTMLElement | null;
const chart = document.querySelector('[data-testid="codex-stats-chart"]') as HTMLElement | null;
const list = document.querySelector('[data-testid="codex-stats-daily-list"]') as HTMLElement | null;
const diagnostic = document.querySelector('[data-testid="codex-stats-diagnostic"]') as HTMLElement | null;
const statsPanel = document.querySelector(".codex-stats-panel") as HTMLElement | null;
const svg = chart?.querySelector("svg") as SVGElement | null;
const chartRect = chart?.getBoundingClientRect();
return {
heroText: hero?.innerText || hero?.textContent || "",
state: hero?.getAttribute("data-stats-state") || "",
degraded: hero?.getAttribute("data-stats-degraded") || "",
chartVisible: Boolean(chart),
chartEmpty: chart?.getAttribute("data-empty") || "",
chartHeight: Math.round(chartRect?.height || 0),
polylineCount: svg?.querySelectorAll("polyline.stat-line").length ?? 0,
pointCount: svg?.querySelectorAll(".stat-point-group").length ?? 0,
dailyRows: list?.querySelectorAll('[data-testid^="codex-stats-day-"]:not([data-testid="codex-stats-day-empty"])').length ?? 0,
dailyText: list?.textContent || "",
diagnosticText: diagnostic?.textContent || "",
emptyText: statsPanel?.querySelector(".empty-state")?.textContent || "",
summaryText: statsPanel?.textContent || "",
};
});
return {
checked: true,
mode,
taskId,
fixture: {
statsKeys: Object.keys(statistics),
bucketKey: mode === "visible" ? "dailyBuckets" : "daily",
totals: statistics.totals,
},
requests,
...metrics,
ok: mode === "visible"
? metrics.state === "ready"
&& metrics.chartVisible === true
&& metrics.chartEmpty === "false"
&& metrics.dailyRows >= 3
&& metrics.pointCount >= 9
&& metrics.heroText.includes("9 tasks / 3 retries")
&& metrics.dailyText.includes("4 exec")
&& !metrics.emptyText.includes("暂无统计")
: metrics.state === "unavailable"
&& metrics.degraded === "true"
&& metrics.chartVisible === true
&& metrics.chartHeight >= 180
&& metrics.diagnosticText.includes("fixture stats API timeout")
&& metrics.summaryText.includes("统计不可用")
&& metrics.chartEmpty === "true"
&& metrics.heroText.includes("0 tasks / 0 retries"),
};
} finally {
await page.unroute(routePattern, handler).catch(() => undefined);
}
}
function runPsql(config: UniDeskConfig, sql: string): { ok: boolean; stdout: string; stderr: string; exitCode: number | null } {
const result = runCommand([
"docker",
@@ -2181,6 +2369,8 @@ async function frontendCheck(config: UniDeskConfig, urls: PublicUrls, checks: E2
"frontend:code-queue-trace-full-load",
"frontend:code-queue-judge-wrap",
"frontend:code-queue-error-red-markers",
"frontend:code-queue-stats-visible",
"frontend:code-queue-stats-degraded-visible",
"frontend:code-queue-retry-attempt-trace-current",
"frontend:code-queue-step-missing-diagnostic",
"frontend:code-queue-judge-feedback-attempt-order",
@@ -2193,6 +2383,8 @@ async function frontendCheck(config: UniDeskConfig, urls: PublicUrls, checks: E2
"frontend:code-queue-trace-full-load",
"frontend:code-queue-judge-wrap",
"frontend:code-queue-error-red-markers",
"frontend:code-queue-stats-visible",
"frontend:code-queue-stats-degraded-visible",
]);
const needCodeQueueRetryTraceFixture = wantsAny([
"frontend:code-queue-retry-attempt-trace-current",
@@ -2306,9 +2498,11 @@ async function frontendCheck(config: UniDeskConfig, urls: PublicUrls, checks: E2
let codeQueueScrollbarMetrics: any = { transcriptThin: false, toolHorizontalHidden: true };
let codexInitialPromptFullMetrics: any = { candidateFound: false };
let codexTraceFullMetrics: any = { candidateFound: false };
let codexJudgeWrapMetrics: any = { checked: false };
let codeQueueErrorHighlightMetrics: any = { checked: false, candidateFound: false };
let codeQueueRetryTraceFixtureMetrics: any = { checked: false };
let codexJudgeWrapMetrics: any = { checked: false };
let codeQueueErrorHighlightMetrics: any = { checked: false, candidateFound: false };
let codeQueueStatsVisibleMetrics: any = { checked: false };
let codeQueueStatsDegradedMetrics: any = { checked: false };
let codeQueueRetryTraceFixtureMetrics: any = { checked: false };
let claudeqqText = "";
let routeDeepLinkText = "";
let routeInitialPath = "";
@@ -3223,8 +3417,14 @@ async function frontendCheck(config: UniDeskConfig, urls: PublicUrls, checks: E2
};
}
}
if (wants("frontend:code-queue-stats-visible")) {
codeQueueStatsVisibleMetrics = await runCodeQueueStatsFixture(page, urls.frontendUrl, "visible");
}
if (wants("frontend:code-queue-stats-degraded-visible")) {
codeQueueStatsDegradedMetrics = await runCodeQueueStatsFixture(page, urls.frontendUrl, "degraded");
}
codeQueueOutputText = needCodeQueueFullSurface ? await page.locator('[data-testid="codex-output"]').innerText({ timeout: 5000 }) : "";
codeQueueText = await page.locator('[data-testid="code-queue-page"]').innerText({ timeout: 5000 });
codeQueueText = await page.locator('[data-testid="code-queue-page"]').innerText({ timeout: 5000 });
codeQueueHtmlGuard = await page.evaluate(async () => {
const root = document.getElementById("root");
const overviewAttr = root?.getAttribute("data-codex-overview") || "";
@@ -3969,10 +4169,18 @@ async function frontendCheck(config: UniDeskConfig, urls: PublicUrls, checks: E2
&& codexTraceFullMetrics.loadPartial !== "true"
),
{ codexTraceFullMetrics });
addSelectedCheck(checks, options, "frontend:code-queue-judge-wrap",
codexJudgeWrapMetrics.checked === true && codexJudgeWrapMetrics.ok === true,
{ codexJudgeWrapMetrics });
addSelectedCheck(checks, options, "frontend:code-queue-retry-attempt-trace-current",
addSelectedCheck(checks, options, "frontend:code-queue-judge-wrap",
codexJudgeWrapMetrics.checked === true && codexJudgeWrapMetrics.ok === true,
{ codexJudgeWrapMetrics });
addSelectedCheck(checks, options, "frontend:code-queue-stats-visible",
codeQueueStatsVisibleMetrics.checked === true
&& codeQueueStatsVisibleMetrics.ok === true,
{ codeQueueStatsVisibleMetrics });
addSelectedCheck(checks, options, "frontend:code-queue-stats-degraded-visible",
codeQueueStatsDegradedMetrics.checked === true
&& codeQueueStatsDegradedMetrics.ok === true,
{ codeQueueStatsDegradedMetrics });
addSelectedCheck(checks, options, "frontend:code-queue-retry-attempt-trace-current",
codeQueueRetryTraceFixtureMetrics.checked === true
&& codeQueueRetryTraceFixtureMetrics.ok === true
&& codeQueueRetryTraceFixtureMetrics.beforeExpand?.cardAttemptVisible === true
@@ -4132,8 +4340,15 @@ export async function runE2E(
const onlyRetryTraceFixture = options.only.length > 0
&& selectedChecks.length > 0
&& selectedChecks.every((name) => (CODE_QUEUE_RETRY_TRACE_FIXTURE_CHECK_NAMES as readonly string[]).includes(name));
const needFrontend = wantsPrefix(options, "frontend") && !onlyRetryTraceFixture;
const onlyCodeQueueBrowserFixtures = options.only.length > 0
&& selectedChecks.length > 0
&& selectedChecks.every((name) => [
...(CODE_QUEUE_RETRY_TRACE_FIXTURE_CHECK_NAMES as readonly string[]),
...(CODE_QUEUE_STATS_FIXTURE_CHECK_NAMES as readonly string[]),
].includes(name));
const needFrontend = wantsPrefix(options, "frontend") && !onlyRetryTraceFixture && !onlyCodeQueueBrowserFixtures;
const needRetryTraceFixture = wantsAnyCheck(options, [...CODE_QUEUE_RETRY_TRACE_FIXTURE_CHECK_NAMES]);
const needStatsFixture = wantsAnyCheck(options, [...CODE_QUEUE_STATS_FIXTURE_CHECK_NAMES]);
const needCodeQueueFixtures = wantsAnyCheck(options, [...CODE_QUEUE_FIXTURE_CHECK_NAMES]);
const executedSections: string[] = [];
@@ -4161,8 +4376,8 @@ export async function runE2E(
if (needFrontend) {
executedSections.push("frontend");
frontend = await frontendCheck(config, urls, checks, options);
} else if (needRetryTraceFixture) {
executedSections.push("frontend-retry-trace-fixture");
} else if (needRetryTraceFixture || needStatsFixture) {
executedSections.push("frontend-code-queue-browser-fixtures");
const browser = await chromium.launch({ headless: true });
try {
const page = await browser.newPage({ viewport: { width: 1440, height: 920 } });
@@ -4176,7 +4391,17 @@ export async function runE2E(
await page.fill('input[name="password"]', auth.password);
await page.click('button[type="submit"]');
await page.waitForSelector('[data-testid="app-shell"]', { timeout: 10000 });
const codeQueueRetryTraceFixtureMetrics = await runCodeQueueRetryTraceFixture(page, urls.frontendUrl);
const codeQueueRetryTraceFixtureMetrics = needRetryTraceFixture ? await runCodeQueueRetryTraceFixture(page, urls.frontendUrl) : { checked: false };
const codeQueueStatsVisibleMetrics = wantsCheck(options, "frontend:code-queue-stats-visible") ? await runCodeQueueStatsFixture(page, urls.frontendUrl, "visible") : { checked: false };
const codeQueueStatsDegradedMetrics = wantsCheck(options, "frontend:code-queue-stats-degraded-visible") ? await runCodeQueueStatsFixture(page, urls.frontendUrl, "degraded") : { checked: false };
addSelectedCheck(checks, options, "frontend:code-queue-stats-visible",
codeQueueStatsVisibleMetrics.checked === true
&& codeQueueStatsVisibleMetrics.ok === true,
{ codeQueueStatsVisibleMetrics });
addSelectedCheck(checks, options, "frontend:code-queue-stats-degraded-visible",
codeQueueStatsDegradedMetrics.checked === true
&& codeQueueStatsDegradedMetrics.ok === true,
{ codeQueueStatsDegradedMetrics });
addSelectedCheck(checks, options, "frontend:code-queue-retry-attempt-trace-current",
codeQueueRetryTraceFixtureMetrics.checked === true
&& codeQueueRetryTraceFixtureMetrics.ok === true