feat: support for datatables in App Db studio (#7930)
This commit is contained in:
55
frontend/src/lib/components/DatatablePicker.svelte
Normal file
55
frontend/src/lib/components/DatatablePicker.svelte
Normal 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>
|
||||
@@ -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,
|
||||
|
||||
@@ -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}`
|
||||
}
|
||||
|
||||
@@ -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}`
|
||||
}
|
||||
|
||||
@@ -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}`
|
||||
}
|
||||
|
||||
@@ -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}`
|
||||
}
|
||||
|
||||
@@ -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}`
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user