Merge pull request #1731 from pikasTech/fix/1729-workbench-session-order
修复 Workbench session list 默认排序
This commit is contained in:
@@ -369,7 +369,7 @@ test("workbench read model exposes session, messages, turn, and trace without wr
|
||||
assert.equal(sessions.body.sessions[0].turnSummary.durationMs, 3000);
|
||||
assert.equal(factQueries[0].projectId, undefined);
|
||||
assert.equal(factQueries[0].limit, 21);
|
||||
assert.equal(factQueries[0].sessionOrder, "updated_desc");
|
||||
assert.equal(factQueries[0].sessionsOrder, "updated_desc");
|
||||
assert.deepEqual(factQueries[0].families, ["sessions"]);
|
||||
assert.deepEqual(factQueries[1].families, ["messages", "parts", "turns"]);
|
||||
assert.deepEqual(factQueries[1].sessionIds, [session.id]);
|
||||
@@ -1032,7 +1032,7 @@ test("workbench session list overlaps include lookup with the page query", async
|
||||
const runtimeStore = {
|
||||
async queryWorkbenchFacts(params = {}) {
|
||||
queries.push({ ...params, pageReleased });
|
||||
if (params.sessionOrder === "updated_desc") await pageGate;
|
||||
if (params.sessionsOrder === "updated_desc") await pageGate;
|
||||
const filtered = filterFacts(facts, params);
|
||||
return {
|
||||
facts: filtered,
|
||||
@@ -1047,7 +1047,7 @@ test("workbench session list overlaps include lookup with the page query", async
|
||||
try {
|
||||
const { port } = server.address();
|
||||
const pending = getJson(port, `/v1/workbench/sessions?includeSessionId=${encodeURIComponent(included.id)}&limit=1`);
|
||||
await waitForCondition(() => queries.some((query) => query.sessionId === included.id) && queries.some((query) => query.sessionOrder === "updated_desc"));
|
||||
await waitForCondition(() => queries.some((query) => query.sessionId === included.id) && queries.some((query) => query.sessionsOrder === "updated_desc"));
|
||||
const includeIndex = queries.findIndex((query) => query.sessionId === included.id);
|
||||
assert.ok(includeIndex >= 0);
|
||||
assert.equal(queries[includeIndex].pageReleased, false);
|
||||
@@ -1057,7 +1057,7 @@ test("workbench session list overlaps include lookup with the page query", async
|
||||
assert.equal(sessions.status, 200);
|
||||
assert.equal(sessions.body.sessions[0].sessionId, included.id);
|
||||
assert.equal(sessions.body.hasMore, true);
|
||||
assert.equal(queries.find((query) => query.sessionOrder === "updated_desc")?.sessionProjection, "summary");
|
||||
assert.equal(queries.find((query) => query.sessionsOrder === "updated_desc")?.sessionProjection, "summary");
|
||||
assert.equal(queries.find((query) => query.sessionId === included.id)?.sessionProjection, undefined);
|
||||
} finally {
|
||||
pageReleased = true;
|
||||
@@ -1621,7 +1621,7 @@ function filterFacts(facts, params = {}) {
|
||||
.sort((left, right) => Number(left.projectedSeq) - Number(right.projectedSeq)) : [],
|
||||
checkpoints: families.has("checkpoints") ? facts.checkpoints.filter((record) => matchesFact(record, params, ["traceId", "sessionId", "turnId", "runId", "commandId", "projectionStatus", "projectionHealth"])) : []
|
||||
};
|
||||
if (params.sessionOrder === "updated_desc") {
|
||||
if (workbenchTestFactOrder(params, "sessions") === "updated_desc") {
|
||||
filtered.sessions.sort((left, right) => String(right.updatedAt ?? "").localeCompare(String(left.updatedAt ?? "")) || String(right.sessionId ?? "").localeCompare(String(left.sessionId ?? "")));
|
||||
}
|
||||
const limit = Number.parseInt(params.limit ?? "", 10);
|
||||
@@ -1629,6 +1629,11 @@ function filterFacts(facts, params = {}) {
|
||||
return Object.fromEntries(Object.entries(filtered).map(([key, value]) => [key, value.slice(0, limit)]));
|
||||
}
|
||||
|
||||
function workbenchTestFactOrder(params = {}, family = "") {
|
||||
const value = params[`${family}Order`] ?? params.order;
|
||||
return value === "updated_desc" ? "updated_desc" : "updated_asc";
|
||||
}
|
||||
|
||||
function matchesFact(record, params, fields) {
|
||||
return fields.every((field) => {
|
||||
if (params[field] !== undefined && params[field] !== null && record[field] !== params[field]) return false;
|
||||
|
||||
@@ -322,7 +322,7 @@ async function handleWorkbenchSessionList(response, url, options, actor) {
|
||||
const includeSessionId = safeSessionId(url.searchParams.get("includeSessionId"));
|
||||
const includeRouteId = includeSessionId ?? safeConversationId(url.searchParams.get("includeSessionId"));
|
||||
const readModel = createWorkbenchReadModel(options, actor);
|
||||
const pageQuery = { ownerUserId: actor.role === "admin" ? undefined : actor.id, limit: limit + offset + 1, families: WORKBENCH_SESSION_LIST_PAGE_FAMILIES, sessionOrder: "updated_desc", sessionProjection: "summary" };
|
||||
const pageQuery = { ownerUserId: actor.role === "admin" ? undefined : actor.id, limit: limit + offset + 1, families: WORKBENCH_SESSION_LIST_PAGE_FAMILIES, sessionsOrder: "updated_desc", sessionProjection: "summary" };
|
||||
const includeQueryPromise = includeRouteId ? queryWorkbenchSessionInclude(readModel, includeRouteId, perf) : null;
|
||||
const pageResult = await (perf ? perf.measure("workbench_session_page_query", () => readModel.queryFacts(pageQuery)) : readModel.queryFacts(pageQuery));
|
||||
if (pageResult.error) return sendJson(response, 503, workbenchProjectionStoreError(pageResult.error));
|
||||
|
||||
@@ -919,6 +919,35 @@ test("configured postgres runtime can query thin Workbench session summaries", a
|
||||
assert.ok(readCall.sql.includes("providerProfile"));
|
||||
});
|
||||
|
||||
test("configured postgres runtime orders Workbench sessions by sessionsOrder updated_desc", async () => {
|
||||
const queryClient = createFakePostgresClient({ migrationReady: true });
|
||||
const store = createConfiguredCloudRuntimeStore({
|
||||
env: {
|
||||
HWLAB_CLOUD_RUNTIME_ADAPTER: "postgres",
|
||||
HWLAB_CLOUD_RUNTIME_DURABLE: "true"
|
||||
},
|
||||
dbUrl: "postgres://hwlab_redacted@db.example.invalid:5432/hwlab",
|
||||
queryClient,
|
||||
now: () => "2026-06-20T10:00:00.000Z"
|
||||
});
|
||||
await store.writeWorkbenchFacts({
|
||||
facts: {
|
||||
sessions: [
|
||||
{ sessionId: "ses_order_old", ownerUserId: "usr_order", status: "running", updatedAt: "2026-06-15T10:00:00.000Z" },
|
||||
{ sessionId: "ses_order_new", ownerUserId: "usr_order", status: "running", updatedAt: "2026-06-20T10:00:00.000Z" },
|
||||
{ sessionId: "ses_order_middle", ownerUserId: "usr_order", status: "running", updatedAt: "2026-06-18T10:00:00.000Z" }
|
||||
]
|
||||
}
|
||||
});
|
||||
queryClient.calls.length = 0;
|
||||
|
||||
const loaded = await store.queryWorkbenchFacts({ families: ["sessions"], sessionsOrder: "updated_desc", limit: 10 });
|
||||
const readCall = queryClient.calls.find((call) => call.sql.startsWith("SELECT session_json FROM workbench_sessions"));
|
||||
|
||||
assert.deepEqual(loaded.facts.sessions.map((session) => session.sessionId), ["ses_order_new", "ses_order_middle", "ses_order_old"]);
|
||||
assert.match(readCall?.sql ?? "", /ORDER BY updated_at DESC/u);
|
||||
});
|
||||
|
||||
test("configured postgres runtime queries requested workbench fact families concurrently", async () => {
|
||||
const expectedReads = 4;
|
||||
let started = 0;
|
||||
@@ -1357,9 +1386,10 @@ function workbenchFactRows(map, jsonColumn, sql, params, readErrorCode) {
|
||||
error.code = readErrorCode;
|
||||
throw error;
|
||||
}
|
||||
const direction = sql.includes("ORDER BY updated_at DESC") ? -1 : 1;
|
||||
const rows = [...map.values()]
|
||||
.filter((record) => sqlRecordMatches(record, sql, params))
|
||||
.sort((left, right) => String(left.updated_at).localeCompare(String(right.updated_at)) || String(left.id ?? left.session_id ?? left.message_id ?? left.part_id ?? left.turn_id ?? left.trace_id).localeCompare(String(right.id ?? right.session_id ?? right.message_id ?? right.part_id ?? right.turn_id ?? right.trace_id)))
|
||||
.sort((left, right) => direction * (String(left.updated_at).localeCompare(String(right.updated_at)) || String(left.id ?? left.session_id ?? left.message_id ?? left.part_id ?? left.turn_id ?? left.trace_id).localeCompare(String(right.id ?? right.session_id ?? right.message_id ?? right.part_id ?? right.turn_id ?? right.trace_id))))
|
||||
.map((record) => ({ [jsonColumn]: record[jsonColumn] }));
|
||||
const limit = sql.includes(" LIMIT $") ? Number(params.at(-1)) : null;
|
||||
return { rows: Number.isInteger(limit) && limit > 0 ? rows.slice(0, limit) : rows };
|
||||
|
||||
Reference in New Issue
Block a user