From 3ca561b4b4d212165e03d216f1b202d2e8ed9189 Mon Sep 17 00:00:00 2001 From: Romein van Buren Date: Fri, 21 Jul 2023 21:40:08 +0200 Subject: [PATCH] Added collection copy feature (fixes #63) --- CHANGELOG.md | 1 + frontend/src/lib/icons.json | 3 +- frontend/src/lib/stores/hosttree.js | 26 +++++- .../collection/dialogs/duplicate.svelte | 90 +++++++++++++++++++ .../src/organisms/connection/hosttree.svelte | 2 + frontend/wailsjs/go/app/App.d.ts | 2 + frontend/wailsjs/go/app/App.js | 4 + internal/app/collection.go | 73 +++++++++++++++ 8 files changed, 199 insertions(+), 2 deletions(-) create mode 100644 frontend/src/organisms/connection/collection/dialogs/duplicate.svelte diff --git a/CHANGELOG.md b/CHANGELOG.md index c17bf28..3e1a979 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ * Added log view (#53, #54). * Added a shell script editor (#37), plus export/import feature. +* Added collection duplication feature (#63). * Find view: paste ID and press Enter (#55). * Preserve state after switching to another tab (#56). * Find view: ask for confirmation before negligently deleting documents when the user has clicked the '-' button (#58). diff --git a/frontend/src/lib/icons.json b/frontend/src/lib/icons.json index 4a9f780..2ad41fc 100644 --- a/frontend/src/lib/icons.json +++ b/frontend/src/lib/icons.json @@ -6,7 +6,8 @@ "chev-u": "", "chevs-l": "", "chevs-r": "", - "arr-d": " ", + "arr-d": "", + "arr-r": "", "db": "", "x": "", "+": "", diff --git a/frontend/src/lib/stores/hosttree.js b/frontend/src/lib/stores/hosttree.js index 41c0e4b..128c574 100644 --- a/frontend/src/lib/stores/hosttree.js +++ b/frontend/src/lib/stores/hosttree.js @@ -10,12 +10,14 @@ import IndexDetailDialog from '$organisms/connection/collection/dialogs/indexdet import QueryChooserDialog from '$organisms/connection/collection/dialogs/querychooser.svelte'; import DumpDialog from '$organisms/connection/database/dialogs/dump.svelte'; import HostDetailDialog from '$organisms/connection/host/dialogs/hostdetail.svelte'; +import DuplicateDialog from '$organisms/connection/collection/dialogs/duplicate.svelte'; import { CreateIndex, DropCollection, DropDatabase, DropIndex, + DuplicateCollection, ExecuteShellScript, GetIndexes, HostLogs, @@ -30,6 +32,7 @@ import { TruncateCollection } from '$wails/go/app/App'; + const { set, subscribe } = writable({}); const getValue = () => get({ subscribe }); let hostTreeInited = false; @@ -120,9 +123,30 @@ async function refresh() { } }; + collection.duplicate = async function() { + const dialog = dialogs.new(DuplicateDialog, { host, dbKey, collKey }); + return new Promise(resolve => { + dialog.$on('duplicate', async event => { + const success = await DuplicateCollection( + hostKey, + dbKey, + collKey, + event.detail.newHost, + event.detail.newDb, + event.detail.newColl + ); + + if (success) { + await refresh(); + dialog.$close(); + resolve(); + } + }); + }); + }; + collection.export = function(query) { const dialog = dialogs.new(ExportDialog, { collection, query }); - return new Promise(resolve => { dialog.$on('export', async event => { const success = await PerformFindExport(hostKey, dbKey, collKey, JSON.stringify(event.detail.exportInfo)); diff --git a/frontend/src/organisms/connection/collection/dialogs/duplicate.svelte b/frontend/src/organisms/connection/collection/dialogs/duplicate.svelte new file mode 100644 index 0000000..9b10b0a --- /dev/null +++ b/frontend/src/organisms/connection/collection/dialogs/duplicate.svelte @@ -0,0 +1,90 @@ + + + +
+
+
+ {host.name || host.uri} + +
+
+ {dbKey} + +
+
+ {collKey} + +
+
+ +
+ +
+ +
+ + + +
+
+ + + + +
+ + diff --git a/frontend/src/organisms/connection/hosttree.svelte b/frontend/src/organisms/connection/hosttree.svelte index 5fc4a11..1035c44 100644 --- a/frontend/src/organisms/connection/hosttree.svelte +++ b/frontend/src/organisms/connection/hosttree.svelte @@ -33,6 +33,8 @@ { label: 'Dump collection (BSON via mongodump)…', fn: collection.dump }, { separator: true }, { label: 'Rename collection…', fn: collection.rename }, + { label: 'Duplicate collection…', fn: collection.duplicate }, + { separator: true }, { label: 'Truncate collection…', fn: collection.truncate }, { label: 'Drop collection…', fn: collection.drop }, { separator: true }, diff --git a/frontend/wailsjs/go/app/App.d.ts b/frontend/wailsjs/go/app/App.d.ts index f6d9fd5..e81ed40 100755 --- a/frontend/wailsjs/go/app/App.d.ts +++ b/frontend/wailsjs/go/app/App.d.ts @@ -20,6 +20,8 @@ export function DropDatabase(arg1:string,arg2:string):Promise; export function DropIndex(arg1:string,arg2:string,arg3:string,arg4:string):Promise; +export function DuplicateCollection(arg1:string,arg2:string,arg3:string,arg4:string,arg5:string,arg6:string):Promise; + export function Environment():Promise; export function ExecuteShellScript(arg1:string,arg2:string,arg3:string,arg4:string):Promise; diff --git a/frontend/wailsjs/go/app/App.js b/frontend/wailsjs/go/app/App.js index 281e574..f8fb15c 100755 --- a/frontend/wailsjs/go/app/App.js +++ b/frontend/wailsjs/go/app/App.js @@ -30,6 +30,10 @@ export function DropIndex(arg1, arg2, arg3, arg4) { return window['go']['app']['App']['DropIndex'](arg1, arg2, arg3, arg4); } +export function DuplicateCollection(arg1, arg2, arg3, arg4, arg5, arg6) { + return window['go']['app']['App']['DuplicateCollection'](arg1, arg2, arg3, arg4, arg5, arg6); +} + export function Environment() { return window['go']['app']['App']['Environment'](); } diff --git a/internal/app/collection.go b/internal/app/collection.go index b8a86a1..9dd294e 100644 --- a/internal/app/collection.go +++ b/internal/app/collection.go @@ -1,10 +1,12 @@ package app import ( + "context" "fmt" "github.com/wailsapp/wails/v2/pkg/runtime" "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/mongo" ) type OpenCollectionResult struct { @@ -55,6 +57,77 @@ func (a *App) RenameCollection(hostKey, dbKey, collKey, newCollKey string) bool return true } +func (a *App) DuplicateCollection(origHostKey, origDbKey, origCollKey, destHostKey, destDbKey, destCollKey string) bool { + runtime.LogDebugf(a.ctx, "Duplicating collection %s:%s.%s to %s:%s.%s", origHostKey, origDbKey, origCollKey, destHostKey, destDbKey, destCollKey) + + if (origHostKey == destHostKey) && (origDbKey == destDbKey) && (origCollKey == destCollKey) { + runtime.LogInfof(a.ctx, "Duplicating collection: cannot duplicate to the same collection") + runtime.MessageDialog(a.ctx, runtime.MessageDialogOptions{ + Type: runtime.WarningDialog, + Title: "Error while duplicating collection", + Message: "The new collection name cannot equal the old one!", + }) + return false + } + + origHost, origCtx, origClose, err := a.connectToHost(origHostKey) + if err != nil { + return false + } + defer origClose() + + var destHost *mongo.Client + var destCtx context.Context + + if origHostKey != destHostKey { + var destClose func() + destHost, destCtx, destClose, err = a.connectToHost(destHostKey) + + if err != nil { + return false + } + + defer destClose() + } else { + destHost = origHost + destCtx = origCtx + } + + destColl := destHost.Database(destDbKey).Collection(destCollKey) + origCur, err := origHost.Database(origDbKey).Collection(origCollKey).Find(origCtx, bson.D{}) + + if err != nil { + runtime.LogInfof(a.ctx, "Duplicating collection: could not create origin cursor: %s", err.Error()) + runtime.MessageDialog(a.ctx, runtime.MessageDialogOptions{ + Type: runtime.WarningDialog, + Title: "Error while duplicating collection", + Message: "Could not create origin cursor: " + err.Error(), + }) + return false + } + + var n int64 = 0 + var ok = true + + for origCur.Next(origCtx) { + _, err := destColl.InsertOne(destCtx, origCur.Current) + + if err != nil { + ok = false + runtime.LogInfof(a.ctx, "Duplicating collection: could not insert item %d: %s", n, err.Error()) + runtime.MessageDialog(a.ctx, runtime.MessageDialogOptions{ + Type: runtime.WarningDialog, + Title: "Error while duplicating collection", + Message: fmt.Sprintf("Could not insert item %d: %s", n, err.Error()), + }) + } + + n += 1 + } + + return ok +} + func (a *App) TruncateCollection(hostKey, dbKey, collKey string) bool { choice, _ := runtime.MessageDialog(a.ctx, runtime.MessageDialogOptions{ Title: "Confirm",