Files
windmill/frontend/src/lib/components/flows/FlowAssetsHandler.svelte
T
Diego Imbert 3affbb3321 feat: type-checked data tables v0 (#7381)
* 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

* Sql query details in TS asset parser

* code transformation with type parameter in Editor

* Custom Language Worker, code substition works !

* Error marker mapping works

* hover info is correct

* completions work correctly

* other overrides

* type inference kinda works

* Position mapping tests

* refactor prepare_queries

* Refactor PgDatabase to share common code

* Pgdatabase in prepare_queries

* TokioPgConnection refactor

* refactor prepare_queries

* type parameter to sql function

* Fix deadlock

* nit fix

* Fix worker async call freezing because of svelte Proxy

* Force worker to recompute when we set queries

* nit refactor

* nits console logs

* wait that ts worker initialize

* monaco change file version

* update diagnostics

* Refactor for errors

* Show SQL errors in Monaco

* improve sdk

* cleaning refactor + MapResource + usePreparedAssetSqlQueries

* Fixes

* Fix error position mapping

* cache in typescript worker

* fix insert no values

* don't inject type if already present

* Support schema in prepare queries

* update parsers

* ChangeOnDeepInequality

* inferAsset ScriptEditor usage refactor

* sql query typecheck work in flow editor

* Assets and SQL Query check in Raw App Inline Editor

* pkg lock

* Fix DatatableSqlTemplateFunction nit

* prepare query schema nit

* duplicate diagnostics

* nit getScriptVersion mock

* Reprepare queries when switching workspaces

* nit fix

* nit fix

* fetch_one_scalar and execute in python client

* limit pg_connections

* -- prepare flag in postgres

* skip serializing

* fix destructuring undefined

* Prepare queries in workers instead of backend

* nit

* Execute search_path instructions normally

* nit fix

* Fix SET search_path issue in prepare

* only support preparing single-statement queries for now

* update parsers

* safety

* better remove_comments

* Fix getQueryStmtCountHeuristic

* getQueryStmtCountHeuristic tests

* comment out failing tests

* Fix getQueryStmtCountHeuristic impl

* only datatable
2025-12-20 08:51:07 +00:00

182 lines
6.0 KiB
Svelte

<script lang="ts" module>
export function initFlowGraphAssetsCtx({
getModules
}: {
getModules: () => FlowModule[]
}): FlowGraphAssetContext {
let s = $state({
val: {
selectedAsset: undefined,
dbManagerDrawer: undefined,
s3FilePicker: undefined,
resourceEditorDrawer: undefined,
resourceMetadataCache: {},
additionalAssetsMap: {},
computeAssetsCount: (asset) => {
return getAllModules(getModules())
.flatMap((m) => getFlowModuleAssets(m, s.val.additionalAssetsMap) ?? [])
.filter((a) => assetEq(asset, a)).length
},
sqlQueries: {}
}
} satisfies FlowGraphAssetContext)
return s
}
</script>
<script lang="ts">
import { inferAssets } from '$lib/infer'
import {
assetEq,
getFlowModuleAssets,
type AssetWithAccessType,
type AssetWithAltAccessType
} from '../assets/lib'
import { getAllModules } from './flowExplorer'
import { getContext, untrack } from 'svelte'
import type { FlowEditorContext, FlowGraphAssetContext } from './types'
import {
AssetService,
ResourceService,
type AssetUsageKind,
type FlowModule,
type RawScript
} from '$lib/gen'
import { deepEqual } from 'fast-equals'
import { workspaceStore } from '$lib/stores'
import S3FilePicker from '../S3FilePicker.svelte'
import DbManagerDrawer from '../DBManagerDrawer.svelte'
import ResourceEditorDrawer from '../ResourceEditorDrawer.svelte'
import { watch } from 'runed'
let {
modules,
enableParser = false,
enableDbExplore = false,
enablePathScriptAndFlowAssets = false
}: {
modules: FlowModule[]
enableParser?: boolean
enableDbExplore?: boolean
enablePathScriptAndFlowAssets?: boolean
} = $props()
const flowGraphAssetsCtx = getContext<FlowGraphAssetContext | undefined>('FlowGraphAssetContext')
const { selectionManager } = getContext<FlowEditorContext>('FlowEditorContext') || {}
let selectedId = $derived(selectionManager?.getSelectedId())
let allModules = $derived(getAllModules(modules))
// Fetch resource metadata for the ExploreAssetButton
const resMetadataCache = $derived(flowGraphAssetsCtx?.val.resourceMetadataCache)
$effect(() => {
if (!resMetadataCache || !enableDbExplore) return
const assets: AssetWithAccessType[] =
allModules.flatMap(
(m) => getFlowModuleAssets(m, flowGraphAssetsCtx?.val.additionalAssetsMap) ?? []
) ?? []
for (const asset of assets) {
if (asset.kind == 'resource') {
let truncatedPath = asset.path.split('/').slice(0, 3).join('/')
if (truncatedPath in resMetadataCache) continue
resMetadataCache[truncatedPath] = undefined // avoid fetching multiple times because of async
ResourceService.getResource({ path: truncatedPath, workspace: $workspaceStore! })
.then((r) => (resMetadataCache[truncatedPath] = { resource_type: r.resource_type }))
.catch((err) => console.error("Couldn't fetch resource", truncatedPath, err))
}
}
})
// Fetch transitive assets (path scripts and flows)
$effect(() => {
if (!$workspaceStore || !flowGraphAssetsCtx || !enablePathScriptAndFlowAssets) return
let usages: { path: string; kind: AssetUsageKind }[] = []
let modIds: string[] = []
for (const mod of allModules) {
if (mod.id in flowGraphAssetsCtx.val.additionalAssetsMap) continue
flowGraphAssetsCtx.val.additionalAssetsMap[mod.id] = [] // avoid fetching multiple times because of async
if (mod.value.type === 'flow' || mod.value.type === 'script') {
usages.push({ path: mod.value.path, kind: mod.value.type })
modIds.push(mod.id)
}
}
if (usages.length) {
AssetService.listAssetsByUsage({
workspace: $workspaceStore,
requestBody: { usages }
}).then((result) => {
result.forEach((assets, idx) => {
flowGraphAssetsCtx.val.additionalAssetsMap[modIds[idx]] = assets
})
})
}
})
// Prune all additionalAssetsMap entries from deleted modules
$effect(() => {
if (!flowGraphAssetsCtx) return
const modulesSet = new Set(allModules.map((m) => m.id))
for (const key of Object.keys(flowGraphAssetsCtx.val.additionalAssetsMap)) {
if (!modulesSet.has(key)) {
delete flowGraphAssetsCtx.val.additionalAssetsMap[key]
}
}
})
async function parseAndUpdateRawScriptModule(v: RawScript, modId: string) {
console.log('Parsing assets for RawScript module', modId)
let inferAssetsResult = await inferAssets(v.language, v.content)
if (inferAssetsResult.status === 'error') return
if (flowGraphAssetsCtx) flowGraphAssetsCtx.val.sqlQueries[modId] = inferAssetsResult.sql_queries
let newAssets = inferAssetsResult.assets as AssetWithAltAccessType[]
for (const asset of newAssets) {
const old = v.assets?.find((a) => assetEq(a, asset))
if (old?.alt_access_type) asset.alt_access_type = old.alt_access_type
}
if (!deepEqual(v.assets, newAssets)) v.assets = newAssets
}
// Check for raw script modules whose assets were not parsed. Useful for flows created
// before the assets feature was introduced.
$effect(() => {
if (!enableParser) return
untrack(() => {
setTimeout(() => {
for (const mod of allModules) {
if (mod.value.type === 'rawscript' && mod.value.assets === undefined) {
console.log('RawScript module', mod.id, 'without assets field, parsing')
parseAndUpdateRawScriptModule(mod.value, mod.id)
}
}
}, 500) // ensure modules are loaded
})
})
$effect(() => {
if (!enableParser) return
for (const mod of allModules) {
const modValue = mod.value
if (modValue.type === 'rawscript') {
// Recompute any raw script module assets when its content changes
watch(
[() => modValue.content],
() => {
parseAndUpdateRawScriptModule(modValue, mod.id)
},
{ lazy: true }
)
// Also recompute if the module is selected
watch([() => selectedId === mod.id], () => {
if (selectedId === mod.id) parseAndUpdateRawScriptModule(modValue, mod.id)
})
}
}
})
</script>
{#if flowGraphAssetsCtx}
<S3FilePicker bind:this={flowGraphAssetsCtx.val.s3FilePicker} readOnlyMode />
<DbManagerDrawer bind:this={flowGraphAssetsCtx.val.dbManagerDrawer} />
<ResourceEditorDrawer bind:this={flowGraphAssetsCtx.val.resourceEditorDrawer} />
{/if}