mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-07-11 23:42:22 +08:00
🐛 fix(tabs): 修复对象编辑页关闭后未回到来源标签
- 为 SQL 超链接打开的对象标签记录来源标签 - 关闭当前对象标签时优先回到来源标签 - 补充表对象与对象编辑标签关闭回退测试
This commit is contained in:
@@ -1842,6 +1842,7 @@ describe('QueryEditor external SQL save', () => {
|
||||
initialViewMode: 'fields',
|
||||
initialViewModeRequestId: expect.any(String),
|
||||
objectType: 'table',
|
||||
returnToTabId: 'tab-1',
|
||||
});
|
||||
expect((window as any).dispatchEvent).not.toHaveBeenCalledWith(expect.objectContaining({
|
||||
type: 'gonavi:locate-sidebar-object',
|
||||
@@ -1889,6 +1890,7 @@ describe('QueryEditor external SQL save', () => {
|
||||
initialViewMode: 'fields',
|
||||
initialViewModeRequestId: expect.any(String),
|
||||
objectType: 'table',
|
||||
returnToTabId: 'tab-1',
|
||||
}));
|
||||
expect((window as any).dispatchEvent).not.toHaveBeenCalledWith(expect.objectContaining({
|
||||
type: 'gonavi:locate-sidebar-object',
|
||||
@@ -1958,6 +1960,7 @@ describe('QueryEditor external SQL save', () => {
|
||||
connectionId: 'conn-1',
|
||||
dbName: 'main',
|
||||
queryMode: 'object-edit',
|
||||
returnToTabId: 'tab-1',
|
||||
query: expect.stringContaining('CREATE OR REPLACE PROCEDURE reporting.refresh_stats()'),
|
||||
}));
|
||||
expect((window as any).dispatchEvent).not.toHaveBeenCalledWith(expect.objectContaining({
|
||||
@@ -4297,6 +4300,7 @@ describe('QueryEditor external SQL save', () => {
|
||||
connectionId: 'conn-1',
|
||||
dbName: 'main',
|
||||
queryMode: 'object-edit',
|
||||
returnToTabId: 'tab-1',
|
||||
query: expect.stringContaining('CREATE OR REPLACE VIEW reporting.active_users AS'),
|
||||
}));
|
||||
expect((window as any).dispatchEvent).not.toHaveBeenCalledWith(expect.objectContaining({
|
||||
@@ -4371,6 +4375,7 @@ describe('QueryEditor external SQL save', () => {
|
||||
connectionId: 'conn-1',
|
||||
dbName: 'main',
|
||||
queryMode: 'object-edit',
|
||||
returnToTabId: 'tab-1',
|
||||
query: expect.stringContaining('CREATE TRIGGER audit.users_bi'),
|
||||
}));
|
||||
expect(storeState.addTab).toHaveBeenCalledWith(expect.objectContaining({
|
||||
@@ -4380,6 +4385,7 @@ describe('QueryEditor external SQL save', () => {
|
||||
connectionId: 'conn-1',
|
||||
dbName: 'main',
|
||||
queryMode: 'object-edit',
|
||||
returnToTabId: 'tab-1',
|
||||
query: expect.stringContaining('CREATE OR REPLACE PROCEDURE reporting.refresh_stats()'),
|
||||
}));
|
||||
expect((window as any).dispatchEvent).not.toHaveBeenCalledWith(expect.objectContaining({
|
||||
|
||||
@@ -774,6 +774,7 @@ const QueryEditor: React.FC<{ tab: TabData; isActive?: boolean }> = ({ tab, isAc
|
||||
const addTab = useStore(state => state.addTab);
|
||||
const setActiveContext = useStore(state => state.setActiveContext);
|
||||
const updateQueryTabDraft = useStore(state => state.updateQueryTabDraft);
|
||||
const activeTabId = useStore(state => state.activeTabId);
|
||||
const savedQueries = useStore(state => state.savedQueries);
|
||||
const currentConnectionIdRef = useRef(currentConnectionId);
|
||||
const currentDbRef = useRef(currentDb);
|
||||
@@ -2115,8 +2116,9 @@ const QueryEditor: React.FC<{ tab: TabData; isActive?: boolean }> = ({ tab, isAc
|
||||
dbName: targetDbName,
|
||||
query: editSql,
|
||||
queryMode: 'object-edit',
|
||||
returnToTabId: activeTabId || undefined,
|
||||
});
|
||||
}, [addTab]);
|
||||
}, [activeTabId, addTab]);
|
||||
|
||||
const openDefinitionObjectEditTab = useCallback(async (
|
||||
navigationTarget: Extract<QueryEditorNavigationTarget, { type: 'view' | 'materialized-view' | 'sequence' | 'package' }>,
|
||||
@@ -2204,8 +2206,9 @@ const QueryEditor: React.FC<{ tab: TabData; isActive?: boolean }> = ({ tab, isAc
|
||||
objectLabel,
|
||||
),
|
||||
queryMode: 'object-edit',
|
||||
returnToTabId: activeTabId || undefined,
|
||||
});
|
||||
}, [addTab]);
|
||||
}, [activeTabId, addTab]);
|
||||
|
||||
const openTriggerObjectEditTab = useCallback(async (
|
||||
navigationTarget: Extract<QueryEditorNavigationTarget, { type: 'trigger' }>,
|
||||
@@ -2240,8 +2243,9 @@ const QueryEditor: React.FC<{ tab: TabData; isActive?: boolean }> = ({ tab, isAc
|
||||
dbName: targetDbName,
|
||||
query: buildEditableTriggerSql(targetTriggerName, latestDefinition, { translate }),
|
||||
queryMode: 'object-edit',
|
||||
returnToTabId: activeTabId || undefined,
|
||||
});
|
||||
}, [addTab]);
|
||||
}, [activeTabId, addTab]);
|
||||
|
||||
// Setup Autocomplete and Editor
|
||||
const handleEditorDidMount: OnMount = (editor, monaco) => {
|
||||
@@ -2603,6 +2607,7 @@ const QueryEditor: React.FC<{ tab: TabData; isActive?: boolean }> = ({ tab, isAc
|
||||
initialViewMode: 'fields',
|
||||
initialViewModeRequestId: String(Date.now()),
|
||||
objectType: 'table',
|
||||
returnToTabId: activeTabId || undefined,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1312,6 +1312,93 @@ describe('store appearance persistence', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('returns to the source tab after closing an object edit tab opened from a hyperlink', async () => {
|
||||
const { useStore } = await importStore();
|
||||
|
||||
useStore.getState().addTab({
|
||||
id: 'query-source',
|
||||
title: '查询 1',
|
||||
type: 'query',
|
||||
connectionId: 'conn-1',
|
||||
dbName: 'sys',
|
||||
query: 'select * from users;',
|
||||
});
|
||||
useStore.getState().addTab({
|
||||
id: 'query-other-1',
|
||||
title: '查询 2',
|
||||
type: 'query',
|
||||
connectionId: 'conn-1',
|
||||
dbName: 'sys',
|
||||
query: 'select 2;',
|
||||
});
|
||||
useStore.getState().addTab({
|
||||
id: 'query-other-2',
|
||||
title: '查询 3',
|
||||
type: 'query',
|
||||
connectionId: 'conn-1',
|
||||
dbName: 'sys',
|
||||
query: 'select 3;',
|
||||
});
|
||||
useStore.getState().setActiveTab('query-source');
|
||||
useStore.getState().addTab({
|
||||
id: 'query-edit-object',
|
||||
title: '修改对象',
|
||||
type: 'query',
|
||||
connectionId: 'conn-1',
|
||||
dbName: 'sys',
|
||||
query: 'CREATE OR REPLACE VIEW users_view AS SELECT * FROM users;',
|
||||
queryMode: 'object-edit',
|
||||
returnToTabId: 'query-source',
|
||||
});
|
||||
|
||||
expect(useStore.getState().activeTabId).toBe('query-edit-object');
|
||||
|
||||
useStore.getState().closeTab('query-edit-object');
|
||||
|
||||
expect(useStore.getState().activeTabId).toBe('query-source');
|
||||
expect(useStore.getState().activeContext).toEqual({
|
||||
connectionId: 'conn-1',
|
||||
dbName: 'sys',
|
||||
});
|
||||
});
|
||||
|
||||
it('keeps the existing close fallback when the object edit source tab is gone', async () => {
|
||||
const { useStore } = await importStore();
|
||||
|
||||
useStore.getState().addTab({
|
||||
id: 'query-source',
|
||||
title: '查询 1',
|
||||
type: 'query',
|
||||
connectionId: 'conn-1',
|
||||
dbName: 'sys',
|
||||
query: 'select 1;',
|
||||
});
|
||||
useStore.getState().addTab({
|
||||
id: 'query-other',
|
||||
title: '查询 2',
|
||||
type: 'query',
|
||||
connectionId: 'conn-1',
|
||||
dbName: 'sys',
|
||||
query: 'select 2;',
|
||||
});
|
||||
useStore.getState().addTab({
|
||||
id: 'query-edit-object',
|
||||
title: '修改对象',
|
||||
type: 'query',
|
||||
connectionId: 'conn-1',
|
||||
dbName: 'sys',
|
||||
query: 'CREATE OR REPLACE VIEW users_view AS SELECT 1;',
|
||||
queryMode: 'object-edit',
|
||||
returnToTabId: 'query-source',
|
||||
});
|
||||
useStore.getState().closeTab('query-source');
|
||||
useStore.getState().setActiveTab('query-edit-object');
|
||||
|
||||
useStore.getState().closeTab('query-edit-object');
|
||||
|
||||
expect(useStore.getState().activeTabId).toBe('query-other');
|
||||
});
|
||||
|
||||
it('reuses the same table-export tab for the same connection and table identity', async () => {
|
||||
const { useStore } = await importStore();
|
||||
|
||||
|
||||
@@ -1748,6 +1748,17 @@ const sanitizeActiveTabId = (activeTabId: unknown, tabs: TabData[]): string | nu
|
||||
return tabs[0]?.id || null;
|
||||
};
|
||||
|
||||
const resolveCloseTabActiveTabId = (
|
||||
closedTab: TabData | undefined,
|
||||
newTabs: TabData[],
|
||||
): string | null => {
|
||||
const returnToTabId = toTrimmedString(closedTab?.returnToTabId);
|
||||
if (returnToTabId && newTabs.some((tab) => tab.id === returnToTabId)) {
|
||||
return returnToTabId;
|
||||
}
|
||||
return newTabs.length > 0 ? newTabs[newTabs.length - 1].id : null;
|
||||
};
|
||||
|
||||
const resolveActiveContextFromTab = (
|
||||
tab: TabData | null | undefined,
|
||||
): { connectionId: string; dbName: string } | null => {
|
||||
@@ -2918,11 +2929,11 @@ export const useStore = create<AppState>()(
|
||||
|
||||
closeTab: (id) =>
|
||||
set((state) => {
|
||||
const closedTab = state.tabs.find((t) => t.id === id);
|
||||
const newTabs = state.tabs.filter((t) => t.id !== id);
|
||||
let newActiveId = state.activeTabId;
|
||||
if (state.activeTabId === id) {
|
||||
newActiveId =
|
||||
newTabs.length > 0 ? newTabs[newTabs.length - 1].id : null;
|
||||
newActiveId = resolveCloseTabActiveTabId(closedTab, newTabs);
|
||||
}
|
||||
return {
|
||||
tabs: newTabs,
|
||||
|
||||
@@ -468,6 +468,7 @@ export interface TabData {
|
||||
query?: string;
|
||||
resultPanelVisible?: boolean;
|
||||
queryMode?: "standard" | "object-edit";
|
||||
returnToTabId?: string;
|
||||
filePath?: string;
|
||||
initialTab?: string;
|
||||
initialViewMode?: "table" | "json" | "text" | "fields" | "ddl" | "er" | "sqlLog";
|
||||
|
||||
Reference in New Issue
Block a user