fix code queue stats panel
This commit is contained in:
+238
-13
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user