feat: support for datatables in App Db studio (#7930)

This commit is contained in:
Diego Imbert
2026-02-12 19:45:07 +01:00
committed by GitHub
parent d1596c5603
commit 91ffb5ce54
10 changed files with 119 additions and 16 deletions

View File

@@ -0,0 +1,55 @@
<script lang="ts">
import { WorkspaceService } from '$lib/gen'
import { globalDbManagerDrawer, workspaceStore } from '$lib/stores'
import Select from './select/Select.svelte'
import ExploreAssetButton, { assetCanBeExplored } from './ExploreAssetButton.svelte'
import { usePromise } from '$lib/svelte5Utils.svelte'
interface Props {
value?: string | undefined
disabled?: boolean
disablePortal?: boolean
showSchemaExplorer?: boolean
placeholder?: string | undefined
selectInputClass?: string
class?: string
onClear?: () => void
}
let {
value = $bindable(undefined),
disabled = false,
disablePortal = false,
showSchemaExplorer = false,
placeholder = undefined,
selectInputClass = '',
class: className = '',
onClear = undefined
}: Props = $props()
let datatables = usePromise(() =>
WorkspaceService.listDataTables({ workspace: $workspaceStore ?? '' })
)
let dbManagerDrawer = $derived(globalDbManagerDrawer.val)
</script>
<div class={className}>
<Select
items={datatables.value?.map((d) => ({ value: d })) ?? []}
bind:value
loading={datatables.status === 'loading'}
{disabled}
{disablePortal}
{placeholder}
inputClass={selectInputClass}
{onClear}
/>
{#if showSchemaExplorer && value && assetCanBeExplored({ kind: 'datatable', path: value })}
<ExploreAssetButton
class="mt-1 w-fit"
asset={{ kind: 'datatable', path: value }}
{dbManagerDrawer}
/>
{/if}
</div>

View File

@@ -128,9 +128,11 @@
let buttonContainerHeight: number | undefined = $state(undefined)
let dbPath = $derived(
resolvedConfig.type.selected !== 'ducklake'
? resolvedConfig.type.configuration?.[resolvedConfig.type.selected]?.resource
: resolvedConfig.type.configuration?.[resolvedConfig.type.selected]?.ducklake
resolvedConfig.type.selected === 'ducklake'
? resolvedConfig.type.configuration?.[resolvedConfig.type.selected]?.ducklake
: resolvedConfig.type.selected === 'datatable'
? resolvedConfig.type.configuration?.[resolvedConfig.type.selected]?.datatable
: resolvedConfig.type.configuration?.[resolvedConfig.type.selected]?.resource
)
let dbInput: DbInput = $derived(
resolvedConfig.type.selected === 'ducklake'
@@ -140,12 +142,19 @@
}
: {
type: 'database',
resourcePath: dbPath.split('$res:')[1],
resourceType: resolvedConfig.type.selected as DbType
resourcePath: dbPath.split('$res:')[1] ?? dbPath,
resourceType:
resolvedConfig.type.selected === 'datatable'
? 'postgresql'
: (resolvedConfig.type.selected as DbType)
}
)
let dbtype = $derived(
resolvedConfig.type.selected === 'ducklake' ? ('duckdb' as const) : resolvedConfig.type.selected
resolvedConfig.type.selected === 'ducklake'
? ('duckdb' as const)
: resolvedConfig.type.selected === 'datatable'
? 'postgresql'
: resolvedConfig.type.selected
)
function onUpdate(
@@ -234,9 +243,11 @@
ducklake: dbPath.split('ducklake://')[1]
})
} else {
const resourcePath = dbPath.split('$res:')[1]
const resourcePath = dbPath.split('$res:')[1] ?? dbPath
dbSchemas[resourcePath] = await getDbSchemas(
resolvedConfig?.type?.selected,
resolvedConfig?.type?.selected === 'datatable'
? 'postgresql'
: resolvedConfig?.type?.selected,
resourcePath,
$workspaceStore,
() => {},
@@ -376,7 +387,7 @@
}
: {
type: 'database',
resourcePath: dbPath.split('$res:')[1],
resourcePath: dbPath.split('$res:')[1] ?? dbPath,
resourceType: dbtype
},
$workspaceStore,
@@ -508,7 +519,6 @@
async function insert(args: object) {
try {
const selected = resolvedConfig.type.selected
await insertRowRunnable?.insertRow(
dbInput,
$workspaceStore,

View File

@@ -191,7 +191,9 @@ export function getCountInput(
? {
database: {
type: 'static',
value: `$res:${dbInput.resourcePath}`,
value: dbInput.resourcePath.startsWith('datatable://')
? dbInput.resourcePath
: `$res:${dbInput.resourcePath}`,
fieldType: 'object',
format: `resource-${dbType}`
}

View File

@@ -103,7 +103,9 @@ export function getDeleteInput(
? {
database: {
type: 'static',
value: `$res:${dbInput.resourcePath}`,
value: dbInput.resourcePath.startsWith('datatable://')
? dbInput.resourcePath
: `$res:${dbInput.resourcePath}`,
fieldType: 'object',
format: `resource-${dbType}`
}

View File

@@ -130,7 +130,9 @@ export function getInsertInput(dbInput: DbInput, table: string, columns: ColumnD
? {
database: {
type: 'static',
value: `$res:${dbInput.resourcePath}`,
value: dbInput.resourcePath.startsWith('datatable://')
? dbInput.resourcePath
: `$res:${dbInput.resourcePath}`,
fieldType: 'object',
format: `resource-${dbType}`
}

View File

@@ -337,7 +337,9 @@ export function getSelectInput(
? {
database: {
type: 'static',
value: `$res:${dbInput.resourcePath}`,
value: dbInput.resourcePath.startsWith('datatable://')
? dbInput.resourcePath
: `$res:${dbInput.resourcePath}`,
fieldType: 'object',
format: `resource-${dbType}`
}

View File

@@ -115,7 +115,9 @@ export function getUpdateInput(
? {
database: {
type: 'static',
value: `$res:${dbInput.resourcePath}`,
value: dbInput.resourcePath.startsWith('datatable://')
? dbInput.resourcePath
: `$res:${dbInput.resourcePath}`,
fieldType: 'object',
format: `resource-${dbType}`
}

View File

@@ -4158,7 +4158,8 @@ See date-fns format for more information. By default, it is 'dd.MM.yyyy HH:mm'
snowflake: 'Snowflake',
bigquery: 'BigQuery',
snowflake_oauth: 'Snowflake OAuth',
ducklake: 'Ducklake'
ducklake: 'Ducklake',
datatable: 'Data table'
},
configuration: {
postgresql: {
@@ -4252,6 +4253,21 @@ See date-fns format for more information. By default, it is 'dd.MM.yyyy HH:mm'
selectOptions: [],
value: undefined
}
},
datatable: {
datatable: {
type: 'static',
fieldType: 'datatable',
subFieldType: 'datatable',
value: ''
} as StaticAppInput,
table: {
fieldType: 'select',
subFieldType: 'db-table',
type: 'static',
selectOptions: [],
value: undefined
}
}
}
} as const,

View File

@@ -30,6 +30,7 @@
import FileUpload from '$lib/components/common/fileUpload/FileUpload.svelte'
import DucklakePicker from '$lib/components/DucklakePicker.svelte'
import type SimpleEditor from '$lib/components/SimpleEditor.svelte'
import DatatablePicker from '$lib/components/DatatablePicker.svelte'
interface Props {
componentInput: StaticInput<any> | undefined
@@ -159,6 +160,15 @@
}
showSchemaExplorer
/>
{:else if fieldType === 'datatable'}
<DatatablePicker
class="w-full"
bind:value={
() => componentInput?.value?.split('datatable://')?.[1],
(v) => componentInput && (componentInput.value = v ? `datatable://${v}` : undefined)
}
showSchemaExplorer
/>
{:else if fieldType === 'labeledresource'}
{#if componentInput?.value && typeof componentInput?.value == 'object' && 'label' in componentInput?.value && (componentInput.value?.['value'] == undefined || typeof componentInput.value?.['value'] == 'string')}
<div class="flex flex-col gap-1 w-full">

View File

@@ -43,6 +43,7 @@ export type InputType =
| 'bigquery'
| 'oracledb'
| 'ducklake'
| 'datatable'
| 'app-path'
// Connection to an output of another component
@@ -270,6 +271,7 @@ export type AppInput =
| AppInputSpec<'resource', string, 'bigquery'>
| AppInputSpec<'resource', string, 'oracledb'>
| AppInputSpec<'ducklake', string, 'ducklake'>
| AppInputSpec<'datatable', string, 'datatable'>
| AppInputSpec<'array', object[], 'number-tuple'>
| AppInputSpec<'app-path', string>