Skip to content

Commit 6c0e2f6

Browse files
committed
feat: add sidebar resource extension component for MongoDB data source
1 parent 54dbf2c commit 6c0e2f6

4 files changed

Lines changed: 268 additions & 213 deletions

File tree

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

Lines changed: 61 additions & 213 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,9 @@
11
<script setup lang="ts">
22
import { computed, ref, useTemplateRef } from 'vue'
33
import ContextMenu, { type ContextMenuMethods } from 'primevue/contextmenu'
4+
import type { MenuItem } from 'primevue/menuitem'
45
import { useConfirm } from 'primevue/useconfirm'
56
import { useToast } from 'primevue/usetoast'
6-
import MongoDbCreateCollectionDialog from '@/components/datasources/mongodb/MongoDbCreateCollectionDialog.vue'
7-
import {
8-
createMongoDbCollection,
9-
deleteMongoDbCollection,
10-
parseMongoPath,
11-
} from '@/datasources/mongodb/browser'
127
import SidebarSourceItemButton from '@/components/sidebar/source/SidebarSourceItemButton.vue'
138
import SidebarSourceSection from '@/components/sidebar/source/SidebarSourceSection.vue'
149
import { getRegisteredDataSourceDefinition, supportsQueryConsole } from '@/datasources/registry'
@@ -24,7 +19,6 @@ import { useTabsStore } from '@/stores/tabs-store.ts'
2419
import { useWorkspaceStore } from '@/stores/workspace-store.ts'
2520
import type { DataSourceBrowserTabData, DataSourceResourceItem } from '@/types/datasources'
2621
import type { DataSourceQueryTabData } from '@/types/query-console'
27-
import { isResourceBrowserTab } from '@/types/tabs'
2822
import type { DataSourceRecord } from '@/types/workspace'
2923
3024
const props = defineProps<{
@@ -45,10 +39,12 @@ const isExpanded = ref(false)
4539
const isLoading = ref(false)
4640
const items = ref<DataSourceResourceItem[]>([])
4741
const selectedItem = ref<DataSourceResourceItem | null>(null)
48-
const createCollectionVisible = ref(false)
49-
const createCollectionDatabase = ref('')
5042
const sourceMenu = useTemplateRef<ContextMenuMethods>('sourceMenu')
5143
const itemMenu = useTemplateRef<ContextMenuMethods>('itemMenu')
44+
const resourceExtension = useTemplateRef<{
45+
getSourceMenuItems: () => MenuItem[]
46+
getItemMenuItems: (item: DataSourceResourceItem) => MenuItem[]
47+
}>('resourceExtension')
5248
5349
const sourceDefinition = computed(() =>
5450
getRegisteredDataSourceDefinition(props.source.type, workspaceStore.serverInfo),
@@ -98,12 +94,52 @@ const canWriteSource = computed(() =>
9894
const primaryActionIcon = computed(() =>
9995
canQuerySource.value ? 'ti ti-terminal-2' : 'ti ti-folder-open',
10096
)
101-
const defaultMongoDatabase = computed(() =>
102-
props.source.type === 'mongodb' ? parseMongoPath(defaultBrowserPath.value).database : '',
103-
)
104-
const selectedMongoTarget = computed(() =>
105-
props.source.type === 'mongodb' ? parseMongoPath(selectedItem.value?.path) : null,
106-
)
97+
const sourceMenuItems = computed(() => [
98+
...(canQuerySource.value
99+
? [
100+
{
101+
label: 'Open console',
102+
icon: 'ti ti-terminal-2',
103+
command: () => openQueryConsole(),
104+
disabled: !canQuerySource.value,
105+
},
106+
]
107+
: []),
108+
{
109+
label: 'Open browser',
110+
icon: 'ti ti-folder-open',
111+
command: () => openBrowser(),
112+
disabled: !canBrowseSource.value,
113+
},
114+
{
115+
label: 'Edit datasource',
116+
icon: 'ti ti-settings-cog',
117+
command: () => emit('editDatasource'),
118+
disabled: !canManageSource.value,
119+
},
120+
...(resourceExtension.value?.getSourceMenuItems() ?? []),
121+
{
122+
label: 'Refresh resources',
123+
icon: 'ti ti-refresh',
124+
command: loadItems,
125+
},
126+
{ separator: true },
127+
{
128+
label: 'Delete datasource',
129+
icon: 'ti ti-trash',
130+
command: deleteDatasource,
131+
disabled: !canManageSource.value,
132+
},
133+
])
134+
const itemMenuItems = computed(() => [
135+
{
136+
label: selectedItem.value?.kind === 'container' ? 'Open' : 'Preview',
137+
icon: selectedItem.value?.kind === 'container' ? 'ti ti-folder-open' : 'ti ti-file-search',
138+
command: () => selectedItem.value && openBrowser(selectedItem.value.path),
139+
disabled: !canBrowseSource.value,
140+
},
141+
...(selectedItem.value ? resourceExtension.value?.getItemMenuItems(selectedItem.value) ?? [] : []),
142+
])
107143
108144
async function loadItems() {
109145
if (!workspaceStore.currentProjectId) {
@@ -191,120 +227,6 @@ async function deleteDatasource() {
191227
})
192228
}
193229
194-
function showMongoCreateCollectionDialog(database: string) {
195-
if (!database || !canWriteSource.value) {
196-
return
197-
}
198-
199-
createCollectionDatabase.value = database
200-
createCollectionVisible.value = true
201-
}
202-
203-
async function createMongoCollection(collectionName: string) {
204-
if (
205-
props.source.type !== 'mongodb' ||
206-
!canWriteSource.value ||
207-
!workspaceStore.currentProjectId ||
208-
!createCollectionDatabase.value
209-
) {
210-
return
211-
}
212-
213-
try {
214-
const client = await workspaceStore.getClient()
215-
await createMongoDbCollection({
216-
client,
217-
projectId: workspaceStore.currentProjectId,
218-
sourceId: props.source.id,
219-
database: createCollectionDatabase.value,
220-
collection: collectionName,
221-
})
222-
createCollectionVisible.value = false
223-
224-
if (isExpanded.value) {
225-
await loadItems()
226-
}
227-
228-
toast.add({
229-
severity: 'success',
230-
summary: 'Collection created',
231-
detail: `${collectionName} is now available in ${createCollectionDatabase.value}`,
232-
life: 2200,
233-
})
234-
235-
await openBrowser(`${createCollectionDatabase.value}/${collectionName}`)
236-
} catch (error) {
237-
toast.add({
238-
severity: 'error',
239-
summary: 'Collection create failed',
240-
detail: getErrorMessage(error, 'The MongoDB collection could not be created'),
241-
life: 3200,
242-
})
243-
}
244-
}
245-
246-
async function deleteMongoCollection(item: DataSourceResourceItem) {
247-
if (
248-
props.source.type !== 'mongodb' ||
249-
!canWriteSource.value ||
250-
!workspaceStore.currentProjectId
251-
) {
252-
return
253-
}
254-
255-
const parsed = parseMongoPath(item.path)
256-
if (!parsed.database || !parsed.collection) {
257-
return
258-
}
259-
260-
confirm.require({
261-
header: 'Delete Collection',
262-
message: `Delete collection ${parsed.collection}?`,
263-
acceptClass: 'p-button-danger',
264-
accept: async () => {
265-
try {
266-
const client = await workspaceStore.getClient()
267-
await deleteMongoDbCollection({
268-
client,
269-
projectId: workspaceStore.currentProjectId!,
270-
sourceId: props.source.id,
271-
database: parsed.database,
272-
collection: parsed.collection,
273-
})
274-
275-
tabsStore.closeTabsMatching(
276-
(tab) =>
277-
isResourceBrowserTab(tab) &&
278-
tab.data.sourceType === 'mongodb' &&
279-
tab.data.sourceId === props.source.id &&
280-
Boolean(
281-
tab.data.path === item.path ||
282-
tab.data.path?.startsWith(`${item.path}/_doc/`),
283-
),
284-
)
285-
286-
if (isExpanded.value) {
287-
await loadItems()
288-
}
289-
290-
toast.add({
291-
severity: 'success',
292-
summary: 'Collection deleted',
293-
detail: `${parsed.collection} has been removed`,
294-
life: 2200,
295-
})
296-
} catch (error) {
297-
toast.add({
298-
severity: 'error',
299-
summary: 'Collection delete failed',
300-
detail: getErrorMessage(error, 'The MongoDB collection could not be deleted'),
301-
life: 3200,
302-
})
303-
}
304-
},
305-
})
306-
}
307-
308230
async function openQueryConsole() {
309231
if (!canQuerySource.value || !workspaceStore.currentProjectId) {
310232
return
@@ -362,85 +284,6 @@ async function runPrimaryAction() {
362284
await openBrowser()
363285
}
364286
365-
const sourceMenuItems = computed(() => [
366-
...(canQuerySource.value
367-
? [
368-
{
369-
label: 'Open console',
370-
icon: 'ti ti-terminal-2',
371-
command: () => openQueryConsole(),
372-
disabled: !canQuerySource.value,
373-
},
374-
]
375-
: []),
376-
{
377-
label: 'Open browser',
378-
icon: 'ti ti-folder-open',
379-
command: () => openBrowser(),
380-
disabled: !canBrowseSource.value,
381-
},
382-
{
383-
label: 'Edit datasource',
384-
icon: 'ti ti-settings-cog',
385-
command: () => emit('editDatasource'),
386-
disabled: !canManageSource.value,
387-
},
388-
...(props.source.type === 'mongodb' && defaultMongoDatabase.value
389-
? [
390-
{
391-
label: 'Add collection',
392-
icon: 'ti ti-plus',
393-
command: () => showMongoCreateCollectionDialog(defaultMongoDatabase.value),
394-
disabled: !canWriteSource.value,
395-
},
396-
]
397-
: []),
398-
{
399-
label: 'Refresh resources',
400-
icon: 'ti ti-refresh',
401-
command: loadItems,
402-
},
403-
{ separator: true },
404-
{
405-
label: 'Delete datasource',
406-
icon: 'ti ti-trash',
407-
command: deleteDatasource,
408-
disabled: !canManageSource.value,
409-
},
410-
])
411-
412-
const itemMenuItems = computed(() => [
413-
{
414-
label: selectedItem.value?.kind === 'container' ? 'Open' : 'Preview',
415-
icon: selectedItem.value?.kind === 'container' ? 'ti ti-folder-open' : 'ti ti-file-search',
416-
command: () => selectedItem.value && openBrowser(selectedItem.value.path),
417-
disabled: !canBrowseSource.value,
418-
},
419-
...(props.source.type === 'mongodb' &&
420-
selectedMongoTarget.value?.database &&
421-
!selectedMongoTarget.value.collection
422-
? [
423-
{
424-
label: 'Add collection',
425-
icon: 'ti ti-plus',
426-
command: () => showMongoCreateCollectionDialog(selectedMongoTarget.value!.database),
427-
disabled: !canWriteSource.value,
428-
},
429-
]
430-
: []),
431-
...(props.source.type === 'mongodb' && selectedMongoTarget.value?.collection
432-
? [
433-
{ separator: true },
434-
{
435-
label: 'Delete collection',
436-
icon: 'ti ti-trash',
437-
command: () => selectedItem.value && deleteMongoCollection(selectedItem.value),
438-
disabled: !canWriteSource.value,
439-
},
440-
]
441-
: []),
442-
])
443-
444287
function showSourceMenu(event: MouseEvent) {
445288
sourceMenu.value?.show(event)
446289
}
@@ -486,11 +329,16 @@ function getItemIcon(item: DataSourceResourceItem) {
486329
<template #overlay>
487330
<ContextMenu ref="sourceMenu" :model="sourceMenuItems" />
488331
<ContextMenu ref="itemMenu" :model="itemMenuItems" />
489-
<MongoDbCreateCollectionDialog
490-
v-if="source.type === 'mongodb'"
491-
v-model:visible="createCollectionVisible"
492-
:database="createCollectionDatabase"
493-
@create="createMongoCollection"
332+
<component
333+
:is="sourceDefinition.sidebarResourceExtensionComponent"
334+
v-if="sourceDefinition.sidebarResourceExtensionComponent"
335+
ref="resourceExtension"
336+
:source="source"
337+
:default-browser-path="defaultBrowserPath"
338+
:expanded="isExpanded"
339+
:can-write-source="canWriteSource"
340+
:open-browser="openBrowser"
341+
:refresh-resources="loadItems"
494342
/>
495343
</template>
496344
</SidebarSourceSection>

0 commit comments

Comments
 (0)