fix code queue enqueue view preservation

This commit is contained in:
Codex
2026-05-17 01:22:33 +00:00
parent 260ccefac3
commit 1e0140fdcd
2 changed files with 152 additions and 5 deletions
+55 -2
View File
@@ -564,12 +564,14 @@ function isCodeQueueTaskEnqueueRequest(url: string, method: string): boolean {
async function runCodeQueueEnqueueAwaitSmoke(page: Page): Promise<any> { async function runCodeQueueEnqueueAwaitSmoke(page: Page): Promise<any> {
const marker = `e2e-await-enqueue-${Date.now()}-${Math.random().toString(16).slice(2, 8)}`; const marker = `e2e-await-enqueue-${Date.now()}-${Math.random().toString(16).slice(2, 8)}`;
const submitQueueId = "e2e-submit-smoke";
const prompt = [ const prompt = [
`Code Queue await enqueue smoke ${marker}`, `Code Queue await enqueue smoke ${marker}`,
"", "",
"This task is created by the frontend E2E smoke test to verify that the enqueue submit path awaits the backend response before unlocking the form.", "This task is created by the frontend E2E smoke test to verify that the enqueue submit path awaits the backend response before unlocking the form.",
].join("\n"); ].join("\n");
let delayedPostCount = 0; let delayedPostCount = 0;
const taskOverviewRequests: string[] = [];
const routePattern = "**/api/microservices/code-queue/proxy/api/tasks**"; const routePattern = "**/api/microservices/code-queue/proxy/api/tasks**";
const routeHandler = async (route: any, request: any): Promise<void> => { const routeHandler = async (route: any, request: any): Promise<void> => {
if (!isCodeQueueTaskEnqueueRequest(request.url(), request.method())) { if (!isCodeQueueTaskEnqueueRequest(request.url(), request.method())) {
@@ -580,11 +582,39 @@ async function runCodeQueueEnqueueAwaitSmoke(page: Page): Promise<any> {
await new Promise((resolve) => setTimeout(resolve, 900)); await new Promise((resolve) => setTimeout(resolve, 900));
await route.continue(); await route.continue();
}; };
const onRequest = (request: any): void => {
try {
const parsed = new URL(request.url());
if (request.method() === "GET" && parsed.pathname === "/api/microservices/code-queue/proxy/api/tasks/overview") {
taskOverviewRequests.push(parsed.search);
}
} catch {
// ignore non-URL requests
}
};
page.on("request", onRequest);
await page.route(routePattern, routeHandler); await page.route(routePattern, routeHandler);
try { try {
await page.getByTestId("code-queue-filter-select").selectOption("__all__").catch(() => undefined); await page.getByTestId("code-queue-filter-select").selectOption("__all__").catch(() => undefined);
await page.getByTestId("code-queue-id-select").selectOption("default").catch(() => undefined); page.once("dialog", (dialog) => { void dialog.accept(submitQueueId); });
await page.getByTestId("codex-create-queue-button").click();
await page.waitForFunction((queueId) => {
const select = document.querySelector('[data-testid="code-queue-id-select"]') as HTMLSelectElement | null;
const filter = document.querySelector('[data-testid="code-queue-filter-select"]') as HTMLSelectElement | null;
return select?.value === queueId && filter?.value === queueId && Array.from(select?.options || []).some((option) => option.value === queueId);
}, submitQueueId, { timeout: 10000 });
await page.getByTestId("code-queue-filter-select").selectOption("__all__").catch(() => undefined);
await page.getByTestId("code-queue-id-select").selectOption(submitQueueId);
taskOverviewRequests.length = 0;
const beforeSubmitView = await page.evaluate(() => {
const filter = document.querySelector('[data-testid="code-queue-filter-select"]') as HTMLSelectElement | null;
const submitQueue = document.querySelector('[data-testid="code-queue-id-select"]') as HTMLSelectElement | null;
return {
filterValue: filter?.value || "",
submitQueueValue: submitQueue?.value || "",
};
});
await page.getByTestId("codex-max-attempts-input").fill("1"); await page.getByTestId("codex-max-attempts-input").fill("1");
await page.getByTestId("codex-repeat-count-input").fill("1"); await page.getByTestId("codex-repeat-count-input").fill("1");
await page.locator('[data-testid="code-queue-task-form"] textarea').fill(prompt); await page.locator('[data-testid="code-queue-task-form"] textarea').fill(prompt);
@@ -626,6 +656,10 @@ async function runCodeQueueEnqueueAwaitSmoke(page: Page): Promise<any> {
const textarea = document.querySelector('[data-testid="code-queue-task-form"] textarea') as HTMLTextAreaElement | null; const textarea = document.querySelector('[data-testid="code-queue-task-form"] textarea') as HTMLTextAreaElement | null;
const notice = document.querySelector('[data-testid="codex-create-success"]') as HTMLElement | null; const notice = document.querySelector('[data-testid="codex-create-success"]') as HTMLElement | null;
const card = document.querySelector(`[data-testid="codex-task-${CSS.escape(String(id))}"]`) as HTMLElement | null; const card = document.querySelector(`[data-testid="codex-task-${CSS.escape(String(id))}"]`) as HTMLElement | null;
const filter = document.querySelector('[data-testid="code-queue-filter-select"]') as HTMLSelectElement | null;
const submitQueue = document.querySelector('[data-testid="code-queue-id-select"]') as HTMLSelectElement | null;
const sessionPanel = document.querySelector('.codex-output-panel') as HTMLElement | null;
const cards = Array.from(document.querySelectorAll('[data-testid^="codex-task-codex_"]')).map((node) => (node as HTMLElement).innerText);
return { return {
formBusy: form?.getAttribute("aria-busy") === "true", formBusy: form?.getAttribute("aria-busy") === "true",
waitMissing: wait === null, waitMissing: wait === null,
@@ -634,6 +668,10 @@ async function runCodeQueueEnqueueAwaitSmoke(page: Page): Promise<any> {
textareaEmpty: (textarea?.value || "") === "", textareaEmpty: (textarea?.value || "") === "",
noticeText: notice?.textContent || "", noticeText: notice?.textContent || "",
cardVisible: Boolean(card && card.offsetParent !== null), cardVisible: Boolean(card && card.offsetParent !== null),
filterValue: filter?.value || "",
submitQueueValue: submitQueue?.value || "",
selectedTraceHasTask: Boolean(sessionPanel && (sessionPanel.textContent || "").includes(String(id))),
allVisibleCardsRespectFilter: filter?.value === "__all__" || cards.every((text) => text.includes(`queue=${filter?.value || ""}`)),
}; };
}, taskId); }, taskId);
const storedTask = await page.evaluate(async (id) => { const storedTask = await page.evaluate(async (id) => {
@@ -663,11 +701,15 @@ async function runCodeQueueEnqueueAwaitSmoke(page: Page): Promise<any> {
} }
})(); })();
await page.getByTestId("codex-max-attempts-input").fill("99").catch(() => undefined); await page.getByTestId("codex-max-attempts-input").fill("99").catch(() => undefined);
const afterSubmitOverviewRequests = taskOverviewRequests.slice();
return { return {
checked: true, checked: true,
marker, marker,
submitQueueId,
delayedPostCount, delayedPostCount,
requestBody, requestBody,
afterSubmitOverviewRequests,
postSubmitSubmitQueueOverviewRequestCount: afterSubmitOverviewRequests.filter((search) => search.includes(`queueId=${encodeURIComponent(submitQueueId)}`)).length,
responseStatus: response.status(), responseStatus: response.status(),
responseOk: response.ok(), responseOk: response.ok(),
responseBody: { responseBody: {
@@ -675,6 +717,7 @@ async function runCodeQueueEnqueueAwaitSmoke(page: Page): Promise<any> {
taskIds: Array.isArray(responseBody?.tasks) ? responseBody.tasks.map((task: any) => String(task?.id || "")).filter(Boolean) : [], taskIds: Array.isArray(responseBody?.tasks) ? responseBody.tasks.map((task: any) => String(task?.id || "")).filter(Boolean) : [],
}, },
taskId, taskId,
beforeSubmitView,
duringAwait, duringAwait,
afterAwait, afterAwait,
storedTask: { storedTask: {
@@ -695,6 +738,7 @@ async function runCodeQueueEnqueueAwaitSmoke(page: Page): Promise<any> {
}; };
} finally { } finally {
await page.unroute(routePattern, routeHandler).catch(() => undefined); await page.unroute(routePattern, routeHandler).catch(() => undefined);
page.off("request", onRequest);
} }
} }
@@ -2800,6 +2844,7 @@ async function frontendCheck(config: UniDeskConfig, urls: PublicUrls, checks: E2
&& codeQueueEnqueueAwaitSmoke.responseOk === true && codeQueueEnqueueAwaitSmoke.responseOk === true
&& codeQueueEnqueueAwaitSmoke.responseStatus === 202 && codeQueueEnqueueAwaitSmoke.responseStatus === 202
&& /^codex_\d+_[A-Za-z0-9_-]+$/u.test(String(codeQueueEnqueueAwaitSmoke.taskId || "")) && /^codex_\d+_[A-Za-z0-9_-]+$/u.test(String(codeQueueEnqueueAwaitSmoke.taskId || ""))
&& codeQueueEnqueueAwaitSmoke.submitQueueId === "e2e-submit-smoke"
&& codeQueueEnqueueAwaitSmoke.duringAwait?.formBusy === true && codeQueueEnqueueAwaitSmoke.duringAwait?.formBusy === true
&& codeQueueEnqueueAwaitSmoke.duringAwait?.waitVisible === true && codeQueueEnqueueAwaitSmoke.duringAwait?.waitVisible === true
&& codeQueueEnqueueAwaitSmoke.duringAwait?.buttonDisabled === true && codeQueueEnqueueAwaitSmoke.duringAwait?.buttonDisabled === true
@@ -2807,9 +2852,17 @@ async function frontendCheck(config: UniDeskConfig, urls: PublicUrls, checks: E2
&& codeQueueEnqueueAwaitSmoke.afterAwait?.formBusy === false && codeQueueEnqueueAwaitSmoke.afterAwait?.formBusy === false
&& codeQueueEnqueueAwaitSmoke.afterAwait?.waitMissing === true && codeQueueEnqueueAwaitSmoke.afterAwait?.waitMissing === true
&& codeQueueEnqueueAwaitSmoke.afterAwait?.textareaEmpty === true && codeQueueEnqueueAwaitSmoke.afterAwait?.textareaEmpty === true
&& codeQueueEnqueueAwaitSmoke.beforeSubmitView?.filterValue === "__all__"
&& codeQueueEnqueueAwaitSmoke.afterAwait?.filterValue === codeQueueEnqueueAwaitSmoke.beforeSubmitView?.filterValue
&& codeQueueEnqueueAwaitSmoke.afterAwait?.submitQueueValue === codeQueueEnqueueAwaitSmoke.beforeSubmitView?.submitQueueValue
&& codeQueueEnqueueAwaitSmoke.beforeSubmitView?.submitQueueValue === codeQueueEnqueueAwaitSmoke.submitQueueId
&& Number(codeQueueEnqueueAwaitSmoke.postSubmitSubmitQueueOverviewRequestCount || 0) === 0
&& codeQueueEnqueueAwaitSmoke.afterAwait?.cardVisible === true
&& codeQueueEnqueueAwaitSmoke.afterAwait?.selectedTraceHasTask === true
&& codeQueueEnqueueAwaitSmoke.afterAwait?.allVisibleCardsRespectFilter === true
&& codeQueueEnqueueAwaitSmoke.storedTask?.ok === true && codeQueueEnqueueAwaitSmoke.storedTask?.ok === true
&& codeQueueEnqueueAwaitSmoke.storedTask?.id === codeQueueEnqueueAwaitSmoke.taskId && codeQueueEnqueueAwaitSmoke.storedTask?.id === codeQueueEnqueueAwaitSmoke.taskId
&& codeQueueEnqueueAwaitSmoke.storedTask?.queueId === "default" && codeQueueEnqueueAwaitSmoke.storedTask?.queueId === codeQueueEnqueueAwaitSmoke.submitQueueId
&& codeQueueEnqueueAwaitSmoke.storedTask?.promptIncludesMarker === true && codeQueueEnqueueAwaitSmoke.storedTask?.promptIncludesMarker === true
&& (codeQueueEnqueueAwaitSmoke.interrupt?.ok === true || codeQueueEnqueueAwaitSmoke.interrupt?.status === 409), && (codeQueueEnqueueAwaitSmoke.interrupt?.ok === true || codeQueueEnqueueAwaitSmoke.interrupt?.status === 409),
{ codeQueueEnqueueAwaitSmoke }); { codeQueueEnqueueAwaitSmoke });
+97 -3
View File
@@ -2215,6 +2215,99 @@ export function CodeQueuePage({ microservices, onRaw, apiBaseUrl = "/api", initi
}; };
} }
function taskVisibleInQueueFilter(task: any, queueFilterId: string): boolean {
return isAllQueues(queueFilterId) || taskQueueLabel(task) === queueFilterId;
}
function taskMatchesCurrentSearch(task: any): boolean {
const query = normalizedSearchQuery.toLowerCase();
if (query.length === 0) return true;
const haystack = [
task?.id,
task?.status,
task?.queueId,
task?.providerId,
task?.model,
task?.cwd,
task?.displayPrompt,
task?.basePrompt,
task?.prompt,
task?.finalResponse,
task?.lastError?.message,
].map((value) => String(value || "").toLowerCase()).join("\n");
return haystack.includes(query);
}
function mergeCreatedTasksIntoCurrentView(createdTasks: any[], queuePatch: any): void {
const rows = createdTasks.filter((task) => String(task?.id || "").length > 0);
if (rows.length === 0 && !queuePatch) return;
const firstTask = rows[0] || null;
const firstId = String(firstTask?.id || "");
const filteredRows = rows.filter((task) => taskVisibleInQueueFilter(task, selectedQueueId));
const searchableRows = filteredRows.filter(taskMatchesCurrentSearch);
const activeSortId = String(queuePatch?.activeTaskId || activeTaskIds(queuePatch)[0] || firstId || activeTaskId || "");
for (const task of rows) {
const taskId = String(task?.id || "");
if (!taskId) continue;
const transcript = Array.isArray(task?.transcript) ? task.transcript : [];
sessionCacheRef.current.set(taskId, {
...(sessionCacheRef.current.get(taskId) || {}),
task: {
...task,
_summaryLoaded: true,
_detailLoaded: transcript.length > 0,
_transcriptComplete: false,
_transcriptPreview: false,
},
maxSeq: transcriptMaxSeq(transcript),
complete: false,
completeUpdatedAt: "",
});
}
setTasksData((previous: any) => {
if (!previous && (filteredRows.length === 0 || !queuePatch)) return previous;
const previousRows = taskRows(previous);
const mergedRows = applyLocalReadStateToRows(mergeTaskRowsPreferLatest([previousRows, filteredRows], activeSortId));
return {
...(previous || {}),
queue: queuePatch || previous?.queue,
tasks: mergedRows,
pagination: previous?.pagination ? { ...taskPagination(previous), returned: mergedRows.length } : previous?.pagination,
};
});
if (searchActive) {
setSearchTasksData((previous: any) => {
if (!previous || searchableRows.length === 0) return previous;
const mergedRows = applyLocalReadStateToRows(mergeTaskRowsPreferLatest([taskRows(previous), searchableRows], activeSortId));
return {
...previous,
queue: queuePatch || previous.queue,
tasks: mergedRows,
pagination: previous.pagination ? { ...taskPagination(previous), returned: mergedRows.length } : previous.pagination,
};
});
}
if (firstTask && taskVisibleInQueueFilter(firstTask, selectedQueueId) && taskMatchesCurrentSearch(firstTask)) {
detailLoadTokenRef.current += 1;
selectedIdRef.current = firstId;
setSelectedId(firstId);
setSelectedTask(sessionCacheRef.current.get(firstId)?.task || firstTask);
setSelectedDetailLoading(false);
setLoadStats({
phase: "complete",
taskId: firstId,
queueMs: 0,
detailMs: 0,
totalMs: 0,
chunks: 1,
transcriptRows: Array.isArray(firstTask?.transcript) ? firstTask.transcript.length : 0,
partial: true,
completedAt: new Date(),
});
}
setRefreshedAt(new Date());
}
function changeSubmitProvider(nextProviderId: string): void { function changeSubmitProvider(nextProviderId: string): void {
const next = String(nextProviderId || queue?.mainProviderId || "D601").trim() || "D601"; const next = String(nextProviderId || queue?.mainProviderId || "D601").trim() || "D601";
setProviderId(next); setProviderId(next);
@@ -3194,15 +3287,16 @@ export function CodeQueuePage({ microservices, onRaw, apiBaseUrl = "/api", initi
const firstId = result?.tasks?.[0]?.id || ""; const firstId = result?.tasks?.[0]?.id || "";
const ids = Array.isArray(result?.tasks) ? result.tasks.map((task: any) => String(task?.id || "")).filter(Boolean) : []; const ids = Array.isArray(result?.tasks) ? result.tasks.map((task: any) => String(task?.id || "")).filter(Boolean) : [];
const msg = `已创建 ${ids.length || submittingItems.length} 个任务${ids.length > 0 ? `${ids.join(" / ")}` : ""}`; const msg = `已创建 ${ids.length || submittingItems.length} 个任务${ids.length > 0 ? `${ids.join(" / ")}` : ""}`;
mergeCreatedTasksIntoCurrentView(Array.isArray(result?.tasks) ? result.tasks : [], result?.queue || null);
setNotice(msg); setNotice(msg);
addNotification("success", msg); addNotification("success", msg);
setPrompt(""); setPrompt("");
setReferenceTaskId(""); setReferenceTaskId("");
setBatchConfirmed(false); setBatchConfirmed(false);
selectedIdRef.current = firstId;
if (selectedQueueId !== submitQueueId) setTasksData(null);
setQueueId(submitQueueId); setQueueId(submitQueueId);
await load(firstId, true, submitQueueId); if (firstId && taskVisibleInQueueFilter(result?.tasks?.[0], selectedQueueId) && taskMatchesCurrentSearch(result?.tasks?.[0])) {
void ensureTraceSummary(String(firstId), false).catch((err) => setError(errorText(err, "加载 Codex Trace Summary 失败")));
}
}, "Codex 任务入队失败"); }, "Codex 任务入队失败");
enqueueInFlightRef.current = false; enqueueInFlightRef.current = false;
setSubmitting(false); setSubmitting(false);