fix(structure): load indexes when tab becomes visible

This commit is contained in:
t8y2 committed 2026-08-03 14:49:35 +08:00
1 parent 3e3e6d04b8
commit 5325c42637
5 files changed
+106 -76

No files matched your search

@@ -383,4 +383,52 @@ describe("TableStructureEditor metadata loading", () => {
expect(mocks.loadObjectMetadataFacet.mock.calls.map((call) => call[1])).toEqual(expectedFacets);
expect(mocks.loadObjectDdl).not.toHaveBeenCalled();
});
it("loads index metadata when an initialized column draft opens the indexes tab", async () => {
mocks.connection.db_type = "postgres";
mocks.connection.name = "postgres";
mocks.connection.driver_label = "postgres";
mocks.ensureConnected.mockResolvedValue(undefined);
mocks.listDataTypes.mockResolvedValue([]);
mocks.buildTableStructureChangeSql.mockResolvedValue({ statements: [], warnings: [] });
mocks.loadObjectMetadataFacet.mockImplementation(async (_request, facet: string) => ({
value:
facet === "indexes"
? [
{
name: "idx_users_id",
columns: ["id"],
is_unique: false,
is_primary: false,
},
]
: facet === "comment"
? ""
: [],
cacheStatus: "remote",
}));
const root = document.createElement("div");
document.body.append(root);
const app = createApp(TableStructureEditor, {
connectionId: mocks.connection.id,
database: "test",
schema: "public",
tableName: "users",
initialTab: "indexes",
draft: {
...draft(),
loadedMetadataFacets: ["columns", "comment"],
},
});
mountedApps.push(app);
app.mount(root);
await nextTick();
await Promise.resolve();
await nextTick();
await vi.waitFor(() => expect(mocks.loadObjectMetadataFacet).toHaveBeenCalledTimes(1));
expect(mocks.loadObjectMetadataFacet.mock.calls.map((call) => call[1])).toEqual(["indexes"]);
await vi.waitFor(() => expect(root.querySelector('[data-index-row-index="0"]')).not.toBeNull());
});
});
@@ -35,7 +35,7 @@ import { getMysqlDataTypeHelp } from "@/lib/table/mysqlDataTypeHelp";
import { getPostgresDataTypeHelp } from "@/lib/table/postgresDataTypeHelp";
import { getSqliteDataTypeHelp } from "@/lib/table/sqliteDataTypeHelp";
import { getTableMetadataCapabilities, firstStructureMetadataTab, isStructureMetadataTabSupported } from "@/lib/table/tableMetadataCapabilities";
import { shouldLoadTableStructureTriggers, TRIGGERS_ONLY_REFRESH_SCOPE, visibleTableStructureRefreshScope, type TableStructureRefreshScope } from "@/lib/table/tableStructureMetadataLoading";
import { hasTableStructureRefreshWork, unloadedTableStructureRefreshScope, visibleTableStructureRefreshScope, type TableStructureRefreshScope } from "@/lib/table/tableStructureMetadataLoading";
import { canAddTableStructureColumn, getTableStructureCapabilities, hasLocalTableColumnOrderChange, isPhysicalTableColumnOrderChange, supportsLocalTableColumnReorder } from "@/lib/table/tableStructureCapabilities";
import { orderedColumnIndexes, uniqueDataGridColumnOrderKeys } from "@/lib/dataGrid/dataGridColumnOrder";
import { loadTableDataGridColumnOrder, notifyTableDataGridColumnOrderChanged, removeTableDataGridColumnOrder, saveTableDataGridColumnOrder, tableDataGridColumnOrderScopeKey } from "@/lib/dataGrid/dataGridColumnLayoutStorage";
@@ -925,6 +925,7 @@ function createCurrentDraft(initialized = true): TableStructureEditorDraft {
foreignKeys: cloneDraftValue(foreignKeys.value),
triggers: cloneDraftValue(triggers.value),
triggersLoaded: triggersLoaded.value,
loadedMetadataFacets: [...loadedMetadataFacets],
scrollPositions: cloneDraftValue(structureScrollPositions.value),
initialized,
};
@@ -951,6 +952,17 @@ function restoreDraft(draft: TableStructureEditorDraft) {
triggers.value = cloneDraftValue(draft.triggers || []);
// Drafts created before lazy trigger loading always contained live trigger metadata.
triggersLoaded.value = draft.triggersLoaded ?? true;
loadedMetadataFacets.clear();
if (draft.loadedMetadataFacets) {
for (const facet of draft.loadedMetadataFacets) loadedMetadataFacets.add(facet);
} else {
const activeScope = visibleTableStructureRefreshScope(draft.activeTab || "columns");
if (activeScope.columns) loadedMetadataFacets.add("columns");
if (activeScope.indexes || draft.indexes?.length) loadedMetadataFacets.add("indexes");
if (activeScope.foreignKeys || draft.foreignKeys?.length) loadedMetadataFacets.add("foreign-keys");
if (activeScope.triggers || triggersLoaded.value) loadedMetadataFacets.add("triggers");
if (activeScope.tableComment) loadedMetadataFacets.add("comment");
}
structureScrollPositions.value = cloneDraftValue(draft.scrollPositions || {});
restoringDraft = false;
draftHydrated = !needsColumnDraftMetadataHydration();
@@ -1244,15 +1256,6 @@ function setSecondaryMetadataLoading(scope: TableStructureRefreshScope, value: b
if (scope.triggers && tableMetadataCapabilities.value.triggers) triggersLoading.value = value;
}
function hasLoadedMetadataScope(scope: TableStructureRefreshScope): boolean {
if (scope.columns && !loadedMetadataFacets.has("columns")) return false;
if (scope.indexes && !loadedMetadataFacets.has("indexes")) return false;
if (scope.foreignKeys && !loadedMetadataFacets.has("foreign-keys")) return false;
if (scope.triggers && !loadedMetadataFacets.has("triggers")) return false;
if (scope.tableComment && !loadedMetadataFacets.has("comment")) return false;
return true;
}
async function fetchTableCommentValue(connectionId: string, database: string, schema: string, tableName: string, catalog?: string): Promise<string | undefined> {
try {
return (await api.getTableComment(connectionId, database, schema, tableName, catalog)) || "";
@@ -2420,7 +2423,10 @@ onMounted(() => {
}
structureEditorReady = true;
if (props.draft?.initialized) {
void hydrateRestoredDraftFromDatabase().then(() => applyInitialStructureTarget());
void hydrateRestoredDraftFromDatabase().then(() => {
applyInitialStructureTarget();
void loadActiveTableStructureMetadataIfNeeded();
});
} else if (isCreateMode.value) {
markDraftHydratedAndSync();
} else if (activeTab.value === "ddl") {
@@ -2436,7 +2442,10 @@ onActivated(() => {
if (props.draft?.initialized && !draftHydrated) {
restoreDraft(props.draft);
applyInitialStructureTarget();
void hydrateRestoredDraftFromDatabase().then(() => applyInitialStructureTarget());
void hydrateRestoredDraftFromDatabase().then(() => {
applyInitialStructureTarget();
void loadActiveTableStructureMetadataIfNeeded();
});
}
restoreStructureScrollPosition();
});
@@ -2542,25 +2551,6 @@ watch(activeTab, () => {
syncDraftToParent();
});
async function loadTriggersIfNeeded() {
if (
!shouldLoadTableStructureTriggers({
activeTab: activeTab.value,
isCreateMode: isCreateMode.value,
supported: tableMetadataCapabilities.value.triggers,
loaded: triggersLoaded.value,
loading: triggersLoading.value,
structureLoading: loading.value,
})
)
return;
await loadStructure(true, TRIGGERS_ONLY_REFRESH_SCOPE, true, { blockSecondaryMetadata: true, preserveDraft: true });
}
watch([activeTab, loading], () => {
void loadTriggersIfNeeded();
});
watch(
columns,
(items) => {
@@ -2595,21 +2585,20 @@ watch(refreshVersion, (version, previous) => {
void loadStructure(true, visibleTableStructureRefreshScope(activeTab.value));
});
watch(
activeTab,
(tab) => {
if (!structureEditorReady) return;
if (tab === "ddl") {
void fetchDdl();
} else if (!isCreateMode.value && !props.draft?.initialized && !loading.value) {
const scope = visibleTableStructureRefreshScope(tab);
if (!hasLoadedMetadataScope(scope)) {
void loadStructure(false, scope, true, { blockSecondaryMetadata: true }).then(() => applyInitialStructureTarget());
}
}
},
{ flush: "sync" },
);
async function loadActiveTableStructureMetadataIfNeeded() {
if (!structureEditorReady || isCreateMode.value) return;
if (activeTab.value === "ddl") {
await fetchDdl();
return;
}
if (loading.value || secondaryMetadataLoading.value) return;
const scope = unloadedTableStructureRefreshScope(activeTab.value, loadedMetadataFacets);
if (!hasTableStructureRefreshWork(scope)) return;
await loadStructure(true, scope, true, { blockSecondaryMetadata: true, preserveDraft: true });
applyInitialStructureTarget();
}
watch([activeTab, loading, secondaryMetadataLoading], () => void loadActiveTableStructureMetadataIfNeeded(), { flush: "sync" });
watch([activeTab, ddlLoading], ([tab, loading]) => {
if (tab === "ddl" && !loading) {