fix code queue enqueue view preservation
This commit is contained in:
+55
-2
@@ -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 });
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
Reference in New Issue
Block a user