Skip to content

Commit d367314

Browse files
committed
feat: enhance MongoDB document handling with improved normalization, mutation payload building, and UI updates
1 parent d9992f9 commit d367314

16 files changed

Lines changed: 716 additions & 461 deletions

File tree

packages/backend/src/datasources/mongodb/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ export const mongodbDataSourceModule = {
77
type: 'mongodb',
88
kind: 'resource',
99
label: 'MongoDB',
10-
icon: 'database',
10+
icon: 'brand-mongodb',
1111
capabilities: {
1212
sqlQuery: false,
1313
tableBrowser: false,

packages/backend/src/datasources/postgres/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ export const postgresDataSourceModule = {
2525
type: 'postgres',
2626
kind: 'sql',
2727
label: 'Postgres',
28-
icon: 'brand-postgresql',
28+
icon: 'database',
2929
capabilities: {
3030
sqlQuery: true,
3131
tableBrowser: true,

packages/frontend/src/assets/base.css

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,13 @@
111111
.sidenav-elements{
112112
background-color: var(--app-bg);
113113
}
114-
.electron-mac .app-bg-no-mac,
114+
.electron-mac .app-bg-no-mac {
115+
background-color: transparent;
116+
}
117+
115118
.electron-mac .sidenav-elements {
119+
background-color: #FFFFFF88;
120+
}
121+
.dark.electron-mac .sidenav-elements {
116122
background-color: transparent;
117123
}

packages/frontend/src/components/common/DataExportButton.vue

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,23 @@ import { computed } from 'vue'
33
import SplitButton from 'primevue/splitbutton'
44
import { triggerDataExport, type DataExportFormat } from '@/services/data-export'
55
6-
const props = defineProps<{
7-
fileBaseName: string
8-
columns: string[]
9-
rows: Record<string, unknown>[]
10-
tableName?: string
11-
disabled?: boolean
12-
}>()
6+
const props = withDefaults(
7+
defineProps<{
8+
fileBaseName: string
9+
columns: string[]
10+
rows: Record<string, unknown>[]
11+
tableName?: string
12+
disabled?: boolean
13+
smaller?: boolean
14+
severity?: 'primary' | 'secondary' | 'success' | 'info' | 'warning' | 'help' | 'danger'
15+
label?: string
16+
}>(),
17+
{
18+
label: 'Export',
19+
severity: 'secondary',
20+
smaller: true,
21+
},
22+
)
1323
1424
const exportFormats = [
1525
{ label: 'CSV', format: 'csv' },
@@ -44,11 +54,25 @@ function exportAs(format: DataExportFormat) {
4454

4555
<template>
4656
<SplitButton
47-
label="Export"
57+
:label="props.label"
4858
icon="ti ti-download"
4959
size="small"
50-
severity="secondary"
60+
:severity="props.severity"
5161
outlined
62+
:pt="
63+
props.smaller
64+
? {
65+
pcButton: {
66+
root: 'pl-2 pr-3 py-1 text-sm',
67+
},
68+
pcDropdown: {
69+
root: 'p-0',
70+
dropdownicon: 'size-1',
71+
menubuttonicon: 'size-1',
72+
},
73+
}
74+
: {}
75+
"
5276
:disabled="!canExport"
5377
:model="exportItems"
5478
@click="exportAs('csv')"

packages/frontend/src/components/datasources/mongodb/MongoDbDocumentViewer.vue

Lines changed: 6 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<script setup lang="ts">
2-
import { computed, useTemplateRef, watch } from 'vue'
2+
import { computed } from 'vue'
33
import Button from 'primevue/button'
44
import Message from 'primevue/message'
55
import JsonEditor from '@/components/editors/JsonEditor.vue'
@@ -13,41 +13,27 @@ const props = withDefaults(
1313
mode: 'new' | 'selected'
1414
selectedLabel?: string | null
1515
canWrite?: boolean
16-
canSave?: boolean
1716
canDelete?: boolean
1817
loading?: boolean
18+
errorMessage?: string | null
1919
}>(),
2020
{
2121
selectedLabel: null,
2222
canWrite: false,
23-
canSave: false,
2423
canDelete: false,
2524
loading: false,
25+
errorMessage: null,
2626
},
2727
)
2828
2929
const emit = defineEmits<{
30-
save: []
3130
reset: []
32-
createNew: []
3331
deleteDocument: []
3432
}>()
3533
3634
const headerLabel = computed(() =>
3735
props.mode === 'new' ? 'New document' : props.selectedLabel || 'Selected document',
3836
)
39-
40-
const jsonEditor = useTemplateRef<InstanceType<typeof JsonEditor>>('jsonEditor')
41-
42-
watch(
43-
() => props.mode,
44-
() => {
45-
requestAnimationFrame(() => {
46-
jsonEditor.value?.focusEditor()
47-
})
48-
},
49-
{ immediate: true },
50-
)
5137
</script>
5238

5339
<template>
@@ -59,14 +45,6 @@ watch(
5945
</div>
6046

6147
<div class="flex items-center gap-1">
62-
<Button
63-
icon="ti ti-plus"
64-
label="New"
65-
text
66-
severity="secondary"
67-
size="small"
68-
@click="emit('createNew')"
69-
/>
7048
<Button
7149
icon="ti ti-restore"
7250
label="Reset"
@@ -85,31 +63,23 @@ watch(
8563
:disabled="!canWrite || !canDelete || loading"
8664
@click="emit('deleteDocument')"
8765
/>
88-
<Button
89-
icon="ti ti-device-floppy"
90-
label="Save"
91-
size="small"
92-
:disabled="!canWrite || !canSave || loading"
93-
:loading="loading"
94-
@click="emit('save')"
95-
/>
9666
</div>
9767
</div>
9868

9969
<div class="min-h-0 flex-1 overflow-hidden">
10070
<Message v-if="!canWrite" severity="warn" class="m-3 mb-0">
10171
This account can browse MongoDB documents but cannot create, edit or delete them.
10272
</Message>
73+
<Message v-else-if="errorMessage" severity="error" class="m-3 mb-0">
74+
{{ errorMessage }}
75+
</Message>
10376

10477
<JsonEditor
105-
ref="jsonEditor"
10678
v-model="documentJson"
10779
class="h-full"
10880
height="100%"
10981
min-height="100%"
110-
:autofocus="true"
11182
placeholder="{\n}"
112-
@submit="emit('save')"
11383
/>
11484
</div>
11585
</div>

packages/frontend/src/components/datasources/mongodb/MongoDbResultsTable.vue

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,11 @@ import { ref, useTemplateRef, watch } from 'vue'
33
import Message from 'primevue/message'
44
import LogoLoadingSpinner from '@/components/LogoLoadingSpinner.vue'
55
import ExtendedDataTable from '@/components/table/ExtendedDataTable.vue'
6-
import { buildMongoDbResultTable } from '@/datasources/mongodb/browser'
6+
import {
7+
buildMongoDbResultTable,
8+
buildMongoDocumentFromRow,
9+
createMongoDraftRow,
10+
} from '@/datasources/mongodb/browser'
711
import type { MongoDbQueryResult } from '@/types/datasources'
812
import type { SQLTableColumn, SQLTableRowDraft } from '@/types/sql'
913
@@ -55,6 +59,41 @@ watch(
5559
)
5660
5761
defineExpose({
62+
addRow: () => {
63+
const row = createMongoDraftRow(columns.value)
64+
rows.value.push(row)
65+
const nextIndex = rows.value.length - 1
66+
focusedRowIndex.value = nextIndex
67+
requestAnimationFrame(() => {
68+
extendedDataTable.value?.focusRow?.(nextIndex)
69+
})
70+
return nextIndex
71+
},
72+
duplicateSelectedRows: () => {
73+
const selectedRows =
74+
(extendedDataTable.value?.getSelectedRowIndexes?.() ?? [])
75+
.map((index: number) => rows.value[index])
76+
.filter((row: SQLTableRowDraft | undefined): row is SQLTableRowDraft => !!row && row.state !== 'deleted')
77+
78+
if (!selectedRows.length) {
79+
return null
80+
}
81+
82+
const duplicates = selectedRows.map((row: SQLTableRowDraft) => {
83+
const document = buildMongoDocumentFromRow(row, columns.value) ?? {}
84+
delete document._id
85+
return createMongoDraftRow(columns.value, document)
86+
})
87+
88+
rows.value.push(...duplicates)
89+
const nextIndex = rows.value.length - duplicates.length
90+
focusedRowIndex.value = nextIndex
91+
requestAnimationFrame(() => {
92+
extendedDataTable.value?.focusRow?.(nextIndex)
93+
})
94+
return nextIndex
95+
},
96+
deleteSelectedRows: () => extendedDataTable.value?.deleteSelectedRows?.(),
5897
getSelectedRows: () => {
5998
const indexes = extendedDataTable.value?.getSelectedRowIndexes?.() ?? []
6099
return indexes
@@ -84,7 +123,7 @@ defineExpose({
84123
v-model:columns="columns"
85124
v-model:rows="rows"
86125
v-model:focused-row-index="focusedRowIndex"
87-
:can-edit="false"
126+
:can-edit="true"
88127
class="h-full"
89128
/>
90129

packages/frontend/src/components/sidebar/CreateDataSourceDialog.vue

Lines changed: 44 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,9 @@ const type = ref<DataSourceType>('mysql')
3737
const config = ref<Record<string, unknown>>({})
3838
const redactedSecretFields = ref<string[]>([])
3939
const definitions = computed(() => listRegisteredDataSourceDefinitions(workspaceStore.serverInfo))
40-
const availableTypes = computed<DataSourceType[]>(() => definitions.value.map((definition) => definition.type))
40+
const availableTypes = computed<DataSourceType[]>(() =>
41+
definitions.value.map((definition) => definition.type),
42+
)
4143
const isEditing = computed(() => Boolean(props.source))
4244
const typeOptions = computed(() =>
4345
definitions.value.map((definition) => ({
@@ -49,19 +51,20 @@ const currentDefinition = computed<RegisteredDataSourceDefinition | null>(
4951
() => definitions.value.find((definition) => definition.type === type.value) ?? null,
5052
)
5153
const currentFormComponent = computed(() => currentDefinition.value?.formComponent ?? null)
52-
const currentFormProps = computed(() =>
53-
currentDefinition.value?.getFormProps?.({
54-
definition: currentDefinition.value,
55-
redactedSecretFields: redactedSecretFields.value,
56-
source: props.source,
57-
}) ?? {},
54+
const currentFormProps = computed(
55+
() =>
56+
currentDefinition.value?.getFormProps?.({
57+
definition: currentDefinition.value,
58+
redactedSecretFields: redactedSecretFields.value,
59+
source: props.source,
60+
}) ?? {},
5861
)
5962
6063
watch(
6164
() => visible.value,
6265
(nextVisible) => {
6366
if (!nextVisible) return
64-
const initialType = props.source?.type ?? (availableTypes.value[0] ?? 'mysql')
67+
const initialType = props.source?.type ?? availableTypes.value[0] ?? 'mysql'
6568
const state = createDataSourceFormState(initialType, props.source)
6669
name.value = state.name
6770
type.value = state.type
@@ -106,7 +109,12 @@ const canSubmit = computed(() =>
106109
</script>
107110

108111
<template>
109-
<Dialog v-model:visible="visible" modal :header="isEditing ? 'Edit Datasource' : 'Add Datasource'" :style="{ width: '36rem' }">
112+
<Dialog
113+
v-model:visible="visible"
114+
modal
115+
:header="isEditing ? 'Edit Datasource' : 'Add Datasource'"
116+
:style="{ width: '36rem' }"
117+
>
110118
<div class="flex flex-col gap-4">
111119
<div class="flex flex-col gap-2">
112120
<label class="text-sm opacity-70">Datasource name</label>
@@ -115,7 +123,27 @@ const canSubmit = computed(() =>
115123

116124
<div class="flex flex-col gap-2">
117125
<label class="text-sm opacity-70">Type</label>
118-
<Select size="small" v-model="type" :options="typeOptions" option-label="label" option-value="value" fluid />
126+
<Select
127+
size="small"
128+
v-model="type"
129+
:options="typeOptions"
130+
option-label="label"
131+
option-value="value"
132+
fluid
133+
>
134+
<template #value>
135+
<span class="flex gap-2 items-center">
136+
<i :class="`ti ti-${definitions.find((s) => s.type === type)?.icon}`" />
137+
<span>{{ definitions.find((s) => s.type === type)?.label || '-' }}</span>
138+
</span>
139+
</template>
140+
<template #option="props">
141+
<span class="flex gap-2 items-center text-sm">
142+
<i :class="`ti ti-${definitions.find((s) => s.type === props.option.value)?.icon}`" />
143+
<span>{{ props.option.label }}</span>
144+
</span>
145+
</template>
146+
</Select>
119147
</div>
120148

121149
<Component
@@ -125,12 +153,16 @@ const canSubmit = computed(() =>
125153
v-bind="currentFormProps"
126154
/>
127155

128-
<div v-if="currentDefinition" class="rounded-xl border app-border px-3 py-2 text-xs opacity-65">
156+
<div
157+
v-if="currentDefinition"
158+
class="rounded-xl border app-border px-3 py-2 text-xs opacity-65"
159+
>
129160
{{ currentDefinition.capabilities.sqlQuery ? 'SQL datasource' : 'Resource datasource' }}
130161
</div>
131162

132163
<div class="flex justify-end">
133-
<Button size="small"
164+
<Button
165+
size="small"
134166
:label="isEditing ? 'Save datasource' : 'Create datasource'"
135167
:icon="isEditing ? 'ti ti-device-floppy' : 'ti ti-database-plus'"
136168
:disabled="!canSubmit"

packages/frontend/src/components/sidebar/SidebarResourceSource.vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ const sourceDefinition = computed(() =>
4444
)
4545
const sourceIcon = computed(() => sourceDefinition.value.icon)
4646
const defaultBrowserPath = computed(() => {
47+
console.log(sourceDefinition.value)
4748
if (props.source.type === 'mongodb') {
4849
const database = props.source.config?.database
4950
return typeof database === 'string' && database.trim()

0 commit comments

Comments
 (0)