diff --git a/frontend/src/lib/components/EditorBar.svelte b/frontend/src/lib/components/EditorBar.svelte index c48b519f72..b5e5281665 100644 --- a/frontend/src/lib/components/EditorBar.svelte +++ b/frontend/src/lib/components/EditorBar.svelte @@ -29,6 +29,7 @@ import { DiffIcon, DollarSign, + File, History, Library, Link, @@ -38,7 +39,7 @@ Save, Users } from 'lucide-svelte' - import { capitalize, toCamel } from '$lib/utils' + import { capitalize, formatS3Object, toCamel } from '$lib/utils' import type { Schema, SchemaProperty, SupportedLanguage } from '$lib/common' import ScriptVersionHistory from './ScriptVersionHistory.svelte' import ScriptGen from './copilot/ScriptGen.svelte' @@ -48,6 +49,7 @@ import ResourceEditorDrawer from './ResourceEditorDrawer.svelte' import type { EditorBarUi } from './custom_ui' import EditorSettings from './EditorSettings.svelte' + import S3FilePicker from './S3FilePicker.svelte' interface Props { lang: SupportedLanguage | 'bunnative' | undefined @@ -109,6 +111,7 @@ let resourceTypePicker: ItemPicker | undefined = $state() let variableEditor: VariableEditor | undefined = $state() let resourceEditor: ResourceEditorDrawer | undefined = $state() + let s3FilePicker: S3FilePicker | undefined = $state() let showContextVarPicker = $derived( [ @@ -168,6 +171,8 @@ ].includes(lang ?? '') ) + let showS3Picker = $derived(['duckdb'].includes(lang ?? '')) + let showResourceTypePicker = $derived( ['typescript', 'javascript'].includes(scriptLangToEditorLang(lang)) || lang === 'python3' || @@ -582,8 +587,14 @@ JsonNode ${windmillPathToCamelCaseName(path)} = JsonNode.Parse(await client.GetS editor.insertAtCursor(`(Wmill.getResource("${path}"))`) // for related places search: ADD_NEW_LANG } else if (lang == 'duckdb') { - let t = resType == 'postgresql' ? 'postgres' : resType - editor.insertAtCursor(`ATTACH '$res:${path}' AS db (TYPE ${t});`) + let t = { postgresql: 'postgres', mysql: 'mysql', bigquery: 'bigquery' }[resType] + if (!t) { + sendUserToast(`Resource type ${resType} is not supported in DuckDB`, true) + editor.insertAtCursor(`'$res:${path}'`) + return + } else { + editor.insertAtCursor(`ATTACH '$res:${path}' AS db (TYPE ${t});`) + } } sendUserToast(`${path} inserted at cursor`) @@ -630,6 +641,20 @@ JsonNode ${windmillPathToCamelCaseName(path)} = JsonNode.Parse(await client.GetS + { + if (lang === 'duckdb') { + let s = `'${formatS3Object(s3obj.detail)}'` + if (s3obj.detail?.s3.endsWith('.json')) s = `read_json(${s})` + if (s3obj.detail?.s3.endsWith('.csv')) s = `read_csv(${s})` + if (s3obj.detail?.s3.endsWith('.parquet')) s = `read_parquet(${s})` + editor?.insertAtCursor(s) + } + }} +/> + {/if} + {#if showS3Picker && customUi?.s3object != false} + s3FilePicker?.open()} + size="xs" + btnClasses="!font-medium text-tertiary" + spacingSize="md" + startIcon={{ icon: File }} + {iconOnly} + >+S3 Object + + {/if} + {#if showResourceTypePicker && customUi?.type != false} () let drawer: Drawer | undefined = $state() @@ -423,6 +424,9 @@ } async function selectAndClose() { + if (selectedFileKey?.s3) { + dispatch('selectAndClose', { s3: selectedFileKey.s3, storage }) + } drawer?.closeDrawer?.() } diff --git a/frontend/src/lib/components/custom_ui.ts b/frontend/src/lib/components/custom_ui.ts index f5ca4c3948..0c6bbb6631 100644 --- a/frontend/src/lib/components/custom_ui.ts +++ b/frontend/src/lib/components/custom_ui.ts @@ -65,6 +65,7 @@ export type EditorBarUi = { library?: boolean useVsCode?: boolean diffMode?: boolean + s3object?: boolean } export type EditableSchemaFormUi = { diff --git a/frontend/src/lib/utils.ts b/frontend/src/lib/utils.ts index 65dc8a2383..993ff53363 100644 --- a/frontend/src/lib/utils.ts +++ b/frontend/src/lib/utils.ts @@ -1454,6 +1454,11 @@ export function parseS3Object(s3Object: S3Object): { s3: string; storage?: strin return { storage: match?.[1] || undefined, s3: match?.[2] ?? '' } } +export function formatS3Object(s3Object: S3Object): S3Uri { + if (typeof s3Object === 'object') return `s3://${s3Object.storage ?? ''}/${s3Object.s3}` + return s3Object +} + export function isS3Uri(uri: string): uri is S3Uri { const match = uri.match(/^s3:\/\/([^/]*)\/(.*)$/) return !!match && match.length === 3