mirror of
https://github.com/l0ng-ai/openidea.git
synced 2026-11-15 08:08:18 +00:00
fix(database): select the neighbouring result when closing the active tab (#3017)
Closing the active database result selected the rightmost remaining result, jumping away from where the user was. It now selects the left neighbour, then the right one, and Output when none remain. Closing an inactive or missing result keeps the current selection. Co-authored-by: Mr0Bread <55754540+Mr0Bread@users.noreply.github.com>
This commit is contained in:
@@ -120,6 +120,8 @@ SQL completion knows the connected schema: tables after `FROM`/`JOIN` (with join
|
||||
|
||||
Results appear in the **Services** tool window under the **Database** tab: an **Output** tab logs every statement, and each result set gets a **Result N** tab (up to 12).
|
||||
|
||||
Closing a selected result activates its left neighbor, or its right neighbor if there is no result on the left. When no results remain, **Output** is selected. Closing an inactive result keeps the current selection.
|
||||
|
||||
- **Table** or **Chart** view (bar or line, with X/Y columns and an aggregation).
|
||||
- Paging with page sizes 10 to 1,000; click the row count to count all rows.
|
||||
- **Search in results** and a per-column local filter on the loaded rows.
|
||||
|
||||
@@ -120,6 +120,8 @@ SQL 补全基于已连接的架构:`FROM`/`JOIN` 后补全表(并根据外
|
||||
|
||||
结果显示在 **服务** 工具窗口的 **数据库** 标签页中:**输出** 标签页记录每条语句,每个结果集一个 **Result N** 标签页(最多 12 个)。
|
||||
|
||||
关闭当前选中的结果标签页时,优先选中左侧相邻的结果;没有左侧结果时,选中右侧相邻结果。没有剩余结果时,选中 **输出**。关闭未选中的结果不会改变当前选择。
|
||||
|
||||
- **表** 或 **图表** 视图(柱状图或折线图,可选 X/Y 列和聚合方式)。
|
||||
- 分页,每页 10 到 1,000 行;点击行数可以统计总行数。
|
||||
- **在结果中搜索**,以及按列对已加载行进行本地筛选。
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
||||
import type { DataSource } from "@/platform/ipc";
|
||||
import { exportData, type ExportData } from "./export";
|
||||
import { isNumericColumn } from "./Grid";
|
||||
@@ -22,7 +22,7 @@ import {
|
||||
toCsv,
|
||||
toTsv,
|
||||
} from "./model";
|
||||
import { listsDatabases } from "./store";
|
||||
import { OUTPUT_TAB, closeResult, listsDatabases, useDatabase, type ResultTab } from "./store";
|
||||
import { VENDORS, sourceUrl, vendorById, vendorOf, vendorPreset } from "./vendors";
|
||||
|
||||
describe("clipboard formats", () => {
|
||||
@@ -443,3 +443,60 @@ describe("Options tab", () => {
|
||||
expect(colorVar(null)).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe("closing result tabs", () => {
|
||||
let previous: ReturnType<typeof useDatabase.getState>;
|
||||
const tabs = [11, 35, 72, 109].map((id): ResultTab => {
|
||||
const text = `SELECT ${id}`;
|
||||
return {
|
||||
id,
|
||||
title: `Result ${id}`,
|
||||
path: "console.sql",
|
||||
sourceId: "pg",
|
||||
outcome: {
|
||||
statement: { from: 0, to: text.length, text },
|
||||
error: null,
|
||||
result: {
|
||||
statement: text,
|
||||
columns: [{ name: "value", typeName: "int4" }],
|
||||
rows: [[String(id)]],
|
||||
affected: null,
|
||||
hasMore: false,
|
||||
elapsedMs: 1,
|
||||
executionMs: 1,
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
previous = useDatabase.getState();
|
||||
});
|
||||
|
||||
afterEach(() => useDatabase.setState(previous, true));
|
||||
|
||||
it("selects the immediate left neighbor rather than the rightmost result", () => {
|
||||
useDatabase.setState({ results: tabs, activeResult: 35 });
|
||||
closeResult(35);
|
||||
expect(useDatabase.getState().activeResult).toBe(11);
|
||||
expect(useDatabase.getState().results.map((r) => r.id)).toEqual([11, 72, 109]);
|
||||
});
|
||||
|
||||
it.each([
|
||||
["selects the right neighbor of the first result", [11, 35, 72, 109], 11, 11, 35, [35, 72, 109]],
|
||||
["selects the left neighbor of the last result", [11, 35, 72, 109], 109, 109, 72, [11, 35, 72]],
|
||||
["returns to Output after the only result closes", [11], 11, 11, OUTPUT_TAB, []],
|
||||
["keeps selection when an inactive result on the left closes", [11, 35, 72, 109], 72, 35, 72, [11, 72, 109]],
|
||||
["keeps selection when an inactive result on the right closes", [11, 35, 72, 109], 11, 109, 11, [11, 35, 72]],
|
||||
["keeps Output selected when a result closes", [11, 35, 72, 109], OUTPUT_TAB, 35, OUTPUT_TAB, [11, 72, 109]],
|
||||
["keeps an unset selection when a result closes", [11, 35, 72, 109], null, 11, null, [35, 72, 109]],
|
||||
["ignores an already closed result", [11, 35, 72, 109], 35, 999, 35, [11, 35, 72, 109]],
|
||||
["does not change a stale selection for a missing result", [11, 35, 72, 109], 999, 999, 999, [11, 35, 72, 109]],
|
||||
["does not close Output", [11, 35, 72, 109], OUTPUT_TAB, OUTPUT_TAB, OUTPUT_TAB, [11, 35, 72, 109]],
|
||||
] as const)("%s", (_name, ids, active, closed, expected, remaining) => {
|
||||
useDatabase.setState({ results: ids.map((id) => tabs.find((r) => r.id === id)!), activeResult: active });
|
||||
closeResult(closed);
|
||||
expect(useDatabase.getState().activeResult).toBe(expected);
|
||||
expect(useDatabase.getState().results.map((r) => r.id)).toEqual(remaining);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1230,7 +1230,12 @@ export function cancelConsole(path: string) {
|
||||
|
||||
export function closeResult(id: number) {
|
||||
useDatabase.setState((s) => {
|
||||
const index = s.results.findIndex((r) => r.id === id);
|
||||
if (index < 0) return s;
|
||||
const results = s.results.filter((r) => r.id !== id);
|
||||
return { results, activeResult: s.activeResult === id ? (results.at(-1)?.id ?? OUTPUT_TAB) : s.activeResult };
|
||||
return {
|
||||
results,
|
||||
activeResult: s.activeResult === id ? ((results[index - 1] ?? results[index])?.id ?? OUTPUT_TAB) : s.activeResult,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user