Files
windmill/frontend/src/lib/components/select/Select.svelte
Diego Imbert 75fdc2cdc9 feat: data table schemas (#7353)
* data tables settings ui

* install runed

* zod 4 fixes

* use new toJSONSchema

* Migrate ducklake catalogs to more generic custom instance databases

* fix compilation

* Safety conversion for old duckdb ffi

* data tables settings

* ts client basis

* inline run works

* datatables work

* Revert "datatables work"

This reverts commit 6e1588d59e.

* datatables work (without leaking pg credentials)

* println

* separate sqlUtils.ts

* nit

* Separate custom instance db Select and Wizard components

* nit

* nit wording

* add tags to custom instance dbs

* error when trying to use ducklake as datatable or opposite

* show status in dropdown

* data table instance setup works

* sqk function for ducklake

* factorize logic

* fix temp reactivity

* Data table assetexplore

* Migrate S3 permissions to modal

* Revert "Migrate S3 permissions to modal"

This reverts commit 0631d03cb0.

* nit query -> fetch

* Custom instance setup new look

* run_language_executor separate fn

* run_inline param

* nit wording

* Better typed client

* Data tables display as assets in frontend

* asset db icon

* nit

* cleaner errors

* nit

* Fix sed calls in mac

* run_inline_script_preview in python client

* basic python datatable client

* datatable and datalake parser in python

* ducklake client python

* nit fix

* Fix migration producing NULL instead of {} when no custom databases

* merge conflict fail

* python ducklake client arg fix

* parse or infer sql types in ts client

* ts asset parser, detect datatable & ducklake R/W

* fix sql repl for other read ops than select

* export type SqlTemplateFunction

* rename list_custom_instance_pg_databases

* typecheck datatable and ducklake name in Typescript

* Fix typecheck datatable and ducklake in TS

* declare module overriding instead of extending

* infer_sql_type in python client

* SqlQuery object in python

* fix merge conflicts

* update const_format

* CI fix

* factor out to var_identifiers

* sqlx prepare

* unnecessary security (admin is required)

* clearer comment

* ee repo ref

* nit snake case

* claude step 1: detect var declarations

* move detect_sql_access_type to common mod

* claude step 2: detect when saved vars are queried

* Revert "claude step 2: detect when saved vars are queried"

This reverts commit 1e1f930568.

* Revert "claude step 1: detect var declarations"

This reverts commit f866f4819d.

* remove ducklake/datatable and default

* detect data table assigns in var_identifiers

* Python parser successfully infers R/W/RW from ducklake / datatable

* still register ducklake/datatable if not used as unknown R/W

* Go to settings button in Assets Dropdown on not found

* nit

* sqlx prepare fail

* manual fix, somehow sqlx prepare won't do it

* fix frontend ci

* ee repo ref

* ducklake_user doesnt exist in unit tests

* nit fix

* ui nit

* nit

* nit missing clone

* fork ducklakes and datatables

* fix surface hover bug

* stupid mistake

* better deeply reactive mutable derived

* Ducklake picker

* Editor bar data tables

* DuckDB supports datatables

* datatable in duckdb asset parser

* duckdb asset parser var_identifiers

* Revert "duckdb asset parser var_identifiers"

This reverts commit 88068b1a77.

* sqlx prepare

* Box pin in test_workflow_as_code to fix stack overflow

* stash

* sql asset parser parses most s3 literals

* nit

* Detect attach + handle returning RW

* detect assets used with dot notation

* detect implicit access with USE dl; syntax

* Add assets as unknown if var was never used

* Support default ducklake/datatable main in parser

* ignore asset parsing errors in frontend (avoid flow layout shift)

* super weird duplication (merge conflict ?)

* nits

* fix duckdb parser detecting too much as asset when RW ctx is unknown

* fix transparent assets btn

* missing arg

* nit styling

* asset parser specific table parsing

* fix resource specific table parsing

* More concise asset display in flows + better icons

* fix assets page filtering out resources with added table

* Fix frontend to support specific table assets

* Open DB Manager to specific table

* Specific table parser in Python and TS + unit tests

* Fix UPDATE setting access to None

* fix flow edge rendering on top of output picker

* python parser fix var override bug

* add ts test

* fix compilation

* sqlx prepare

* update parsers version

* fix missing schema key onDelete

* Grant permission to create schemas in custom instance databases

* Update pg query to return empty schemas

* Create schema

* Select nits

* support schemas in sql parser

* ts parser handle schema with sql parser result

* detect .schema() syntax

* detect schema syntax in python

* support .schema() in ts and py SDK

* open db manager to specific schema

* support reassignment in ts parser

* nit better unitest

* : syntax in ts

* datatable:schema syntax in python

* fix client py

* nit select dropdown darkmode

* object | null fetchOne

* ts client nits

* parse_sql_client_name fn

* getImportWmillTsStatement refactor in EditorBar

* text to json() in python client

* update parser versions

* pkg lock
2025-12-16 21:16:42 +00:00

205 lines
5.2 KiB
Svelte

<script
lang="ts"
generics="Item extends { label?: string; value: any; subtitle?: string; disabled?: boolean }"
>
import { clickOutside } from '$lib/utils'
import { twMerge } from 'tailwind-merge'
import CloseButton from '../common/CloseButton.svelte'
import { Loader2 } from 'lucide-svelte'
import { untrack, type Snippet } from 'svelte'
import { getLabel, processItems, type ProcessedItem } from './utils.svelte'
import SelectDropdown from './SelectDropdown.svelte'
import { deepEqual } from 'fast-equals'
import {
inputBaseClass,
inputBorderClass,
inputSizeClasses
} from '../text_input/TextInput.svelte'
import { ButtonType } from '../common/button/model'
type Value = Item['value']
let {
items,
placeholder = 'Please select',
value = $bindable(),
filterText = $bindable(''),
class: className = '',
clearable = false,
listAutoWidth = true,
disabled: _disabled = false,
containerStyle = '',
inputClass = '',
disablePortal = false,
loading = false,
error = false,
autofocus,
RightIcon,
createText,
noItemsMsg,
open = $bindable(false),
id,
itemLabelWrapperClasses,
itemButtonWrapperClasses,
size = 'md',
showPlaceholderOnOpen = false,
transformInputSelectedText,
groupBy,
sortBy,
onFocus,
onBlur,
onClear,
onCreateItem,
startSnippet,
endSnippet,
bottomSnippet
}: {
items?: Item[]
value: Value | undefined
placeholder?: string
class?: string
clearable?: boolean
filterText?: string
disabled?: boolean
listAutoWidth?: boolean
containerStyle?: string
inputClass?: string
disablePortal?: boolean
loading?: boolean
error?: boolean
autofocus?: boolean
RightIcon?: any
createText?: string
noItemsMsg?: string
open?: boolean
id?: string
itemLabelWrapperClasses?: string
itemButtonWrapperClasses?: string
size?: 'sm' | 'md' | 'lg'
showPlaceholderOnOpen?: boolean
transformInputSelectedText?: (text: string) => string
groupBy?: (item: Item) => string
sortBy?: (a: Item, b: Item) => number
onFocus?: () => void
onBlur?: () => void
onClear?: () => void
onCreateItem?: (value: string) => void
startSnippet?: Snippet<[{ item: ProcessedItem<Value>; close: () => void }]>
endSnippet?: Snippet<[{ item: ProcessedItem<Value>; close: () => void }]>
bottomSnippet?: Snippet<[{ close: () => void }]>
} = $props()
let disabled = $derived(_disabled || (loading && !value))
let iconSize = $derived(ButtonType.UnifiedIconSizes[size])
let inputEl: HTMLInputElement | undefined = $state()
let processedItems: ProcessedItem<Value>[] = $derived.by(() => {
let args = { items, createText, filterText, groupBy, onCreateItem, sortBy }
return untrack(() => processItems(args))
})
$effect(() => {
if (filterText) open = true
})
$effect(() => {
if (!open) filterText = ''
})
let valueEntry = $derived(value && processedItems?.find((item) => deepEqual(item.value, value)))
function setValue(item: ProcessedItem<Value>) {
if (item.__is_create && onCreateItem) {
onCreateItem(item.value)
} else {
value = item.value
}
filterText = ''
open = false
}
function clearValue() {
filterText = ''
if (onClear) onClear()
else value = undefined
}
let inputText = $derived.by(() => {
let text = valueEntry?.label ?? getLabel({ value }) ?? ''
return transformInputSelectedText?.(text) ?? text
})
</script>
<div
class={`relative ${className}`}
use:clickOutside={{ onClickOutside: () => (open = false) }}
onpointerdown={() => onFocus?.()}
onfocus={() => onFocus?.()}
onblur={() => onBlur?.()}
>
{#if loading}
<div class="absolute z-10 right-2 h-full flex items-center">
<Loader2 size={iconSize} class="animate-spin" />
</div>
{:else if clearable && !disabled && value}
<div class="absolute z-10 right-2 h-full flex items-center">
<CloseButton
class="bg-transparent text-secondary hover:text-primary"
noBg
small
on:close={clearValue}
/>
</div>
{:else if RightIcon}
<div class="absolute z-10 right-2 h-full flex items-center">
<RightIcon size={iconSize} class="text-secondary" />
</div>
{/if}
<!-- svelte-ignore a11y_autofocus -->
<input
{autofocus}
{disabled}
type="text"
bind:value={() => (open ? filterText : inputText), (v) => open && (filterText = v)}
placeholder={loading && !value
? 'Loading...'
: value && !showPlaceholderOnOpen
? inputText
: placeholder}
style={containerStyle}
class={twMerge(
inputBaseClass,
inputSizeClasses[size],
ButtonType.UnifiedHeightClasses[size],
inputBorderClass({ error, forceFocus: open }),
'w-full',
open ? '' : 'cursor-pointer',
// Show value as placeholder when opening the dropdown and the search is empty
!value ? 'placeholder-hint' : '!placeholder-primary',
(clearable || RightIcon) && !disabled && value ? 'pr-8' : '',
inputClass ?? ''
)}
autocomplete="off"
onpointerdown={() => (open = true)}
bind:this={inputEl}
{id}
/>
<SelectDropdown
{disablePortal}
onSelectValue={setValue}
{open}
{processedItems}
{value}
{disabled}
{filterText}
getInputRect={inputEl && (() => inputEl!.getBoundingClientRect())}
{listAutoWidth}
{noItemsMsg}
{itemLabelWrapperClasses}
{itemButtonWrapperClasses}
{startSnippet}
{endSnippet}
{bottomSnippet}
/>
</div>