feat(mongodb): add Compass-style collection import/export

This commit is contained in:
onenewcode authored and GitHub committed 2026-09-12 21:59:29 +08:00
1 parent bf4874906c
commit 8ff56b4837
47 files changed
+5626 -52

No files matched your search

@@ -391,21 +391,22 @@ watch(
{ flush: "post" },
);
const { copyStructureAs, copyStructureDocText, copyStructurePreview, exportData, exportDataXlsx, exportStructure, saveStructurePreview, selectTextareaContent } = useSidebarTreeExportRuntime({
const { copyStructureAs, copyStructureDocText, copyStructurePreview, exportData, exportDataXlsx, exportMongoCollection, exportStructure, saveStructurePreview, selectTextareaContent } = useSidebarTreeExportRuntime({
activeNode,
connectionStore,
settingsStore,
acceptedSelectionIds: () => acceptedSelectionIds,
});
const { openAllDatabasesExport, openDataCompare, openDatabaseExport, openDatabaseSearch, openDiagram, openDocs, openFieldLineage, openScheduledBackups, openSchemaDiff, openSchemaDiffForRoutine, openSqlFileExecution, openStructureEditor, openTableImport, openTransfer } = useSidebarTreeToolRuntime({
activeNode,
connectionStore,
queryStore,
settingsStore,
tableChildObjectName: tableChildDropObjectName,
acceptedSelectionIds: () => acceptedSelectionIds,
});
const { openAllDatabasesExport, openDataCompare, openDatabaseExport, openDatabaseSearch, openDiagram, openDocs, openFieldLineage, openMongoImport, openScheduledBackups, openSchemaDiff, openSchemaDiffForRoutine, openSqlFileExecution, openStructureEditor, openTableImport, openTransfer } =
useSidebarTreeToolRuntime({
activeNode,
connectionStore,
queryStore,
settingsStore,
tableChildObjectName: tableChildDropObjectName,
acceptedSelectionIds: () => acceptedSelectionIds,
});
const emit = defineEmits<{
"rename-started": [];
@@ -5932,6 +5933,15 @@ function buildSpecialSidebarMenu(context: SidebarMenuFactoryContext): boolean {
if (canCloneMongoCollection.value) {
items.push({ label: t("contextMenu.cloneCollection"), action: openCloneMongoCollectionDialog, icon: CopyPlus });
}
items.push({ label: t("contextMenu.importData"), action: openMongoImport, icon: Download });
items.push({
label: t("contextMenu.exportData"),
icon: Upload,
children: [
{ label: "CSV", action: () => void exportMongoCollection("csv") },
{ label: "NDJSON", action: () => void exportMongoCollection("ndjson") },
],
});
if (canDropMongoCollection.value) {
items.push({ label: "", separator: true });
items.push({ label: t("contextMenu.dropCollection"), action: dropMongoCollection, icon: Trash2, shortcut: shortcutDelete, variant: "destructive" as const });
@@ -0,0 +1,39 @@
import { readFileSync } from "node:fs";
import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { describe, expect, it } from "vitest";
const here = dirname(fileURLToPath(import.meta.url));
function read(relativePath: string) {
return readFileSync(resolve(here, relativePath), "utf8");
}
describe("mongo collection import/export UI", () => {
it("adds table-style import/export to the original mongo-collection menu", () => {
const menu = read("../SidebarTreeRuntimeHost.vue");
const start = menu.indexOf('if (node.type === "mongo-collection") {\n items.push({ label: t("contextMenu.copyName")');
expect(start).toBeGreaterThan(-1);
const end = menu.indexOf("return true;", start);
const block = menu.slice(start, end);
expect(block).toContain('t("contextMenu.viewData")');
expect(block).toContain('t("contextMenu.cloneCollection")');
expect(block.indexOf('t("contextMenu.cloneCollection")')).toBeLessThan(block.indexOf('t("contextMenu.importData")'));
expect(block).toContain("openMongoImport");
expect(block).toContain('t("contextMenu.exportData")');
expect(block).toContain('exportMongoCollection("csv")');
expect(block).toContain('exportMongoCollection("ndjson")');
expect(block).not.toContain("openMongoExport");
});
it("opens import through AppDialogs like table import, without an export setup dialog", () => {
const dialogs = read("../../layout/AppDialogs.vue");
expect(dialogs).toContain("MongoImportDialog");
expect(dialogs).not.toContain("MongoExportDialog");
expect(dialogs).toContain("dialogs.showMongoImportDialog.value");
const sources = read("../../../composables/useDialogSources.ts");
expect(sources).toContain("connectionStore.mongoImportSource");
expect(sources).not.toContain("connectionStore.mongoExportSource");
});
});