diff --git a/backend/.sqlx/query-081dc94a7d0fdaade77cfb593a025d8c48d7eab3dbb30ca0b43fb1ef45d8d8bd.json b/backend/.sqlx/query-081dc94a7d0fdaade77cfb593a025d8c48d7eab3dbb30ca0b43fb1ef45d8d8bd.json new file mode 100644 index 0000000000..e303ef51b7 --- /dev/null +++ b/backend/.sqlx/query-081dc94a7d0fdaade77cfb593a025d8c48d7eab3dbb30ca0b43fb1ef45d8d8bd.json @@ -0,0 +1,25 @@ +{ + "db_name": "PostgreSQL", + "query": "INSERT INTO flow (\n workspace_id, path, summary, description,\n dependency_job, lock_error_logs, draft_only, tag,\n dedicated_worker, visible_to_runner_only, on_behalf_of_email,\n value, schema, edited_by, edited_at\n ) VALUES (\n $1, $2, $3, $4,\n NULL, '', $5, $6,\n $7, $8, $9,\n $10, $11::text::json, $12, now()\n )", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Varchar", + "Varchar", + "Text", + "Text", + "Bool", + "Varchar", + "Bool", + "Bool", + "Text", + "Jsonb", + "Text", + "Varchar" + ] + }, + "nullable": [] + }, + "hash": "081dc94a7d0fdaade77cfb593a025d8c48d7eab3dbb30ca0b43fb1ef45d8d8bd" +} diff --git a/backend/.sqlx/query-2b9607ed838c8c62eb0f2856420389f7be648f52edbb875ff52c96219ed3ba84.json b/backend/.sqlx/query-2b9607ed838c8c62eb0f2856420389f7be648f52edbb875ff52c96219ed3ba84.json deleted file mode 100644 index d04e105953..0000000000 --- a/backend/.sqlx/query-2b9607ed838c8c62eb0f2856420389f7be648f52edbb875ff52c96219ed3ba84.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE flow SET path = $1, summary = $2, description = $3,dependency_job = NULL, lock_error_logs = '', draft_only = NULL, tag = $4, dedicated_worker = $5, visible_to_runner_only = $6, on_behalf_of_email = $7, value = $8, schema = $9::text::json, edited_by = $10, edited_at = now()\n WHERE path = $11 AND workspace_id = $12", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Varchar", - "Text", - "Text", - "Varchar", - "Bool", - "Bool", - "Text", - "Jsonb", - "Text", - "Varchar", - "Text", - "Text" - ] - }, - "nullable": [] - }, - "hash": "2b9607ed838c8c62eb0f2856420389f7be648f52edbb875ff52c96219ed3ba84" -} diff --git a/backend/.sqlx/query-5a219a2532517869578c4504ff3153c43903f929ae5d62fbba12610f89c36d55.json b/backend/.sqlx/query-5a219a2532517869578c4504ff3153c43903f929ae5d62fbba12610f89c36d55.json index 36ddb8ab9f..713ccb9dd3 100644 --- a/backend/.sqlx/query-5a219a2532517869578c4504ff3153c43903f929ae5d62fbba12610f89c36d55.json +++ b/backend/.sqlx/query-5a219a2532517869578c4504ff3153c43903f929ae5d62fbba12610f89c36d55.json @@ -15,7 +15,7 @@ ] }, "nullable": [ - true + null ] }, "hash": "5a219a2532517869578c4504ff3153c43903f929ae5d62fbba12610f89c36d55" diff --git a/backend/.sqlx/query-d2acc380c45df31f741bbd420c2f4a5b962f7803def9ecd6e33d2aec6abce772.json b/backend/.sqlx/query-d2acc380c45df31f741bbd420c2f4a5b962f7803def9ecd6e33d2aec6abce772.json new file mode 100644 index 0000000000..a5329c6a9b --- /dev/null +++ b/backend/.sqlx/query-d2acc380c45df31f741bbd420c2f4a5b962f7803def9ecd6e33d2aec6abce772.json @@ -0,0 +1,25 @@ +{ + "db_name": "PostgreSQL", + "query": "\n UPDATE \n flow \n SET\n path = $1,\n summary = $2,\n description = $3,\n dependency_job = NULL,\n lock_error_logs = '',\n draft_only = NULL,\n tag = $4,\n dedicated_worker = $5,\n visible_to_runner_only = $6,\n on_behalf_of_email = $7,\n value = $8,\n schema = $9::text::json,\n edited_by = $10,\n edited_at = now()\n WHERE \n path = $11 AND workspace_id = $12", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Varchar", + "Text", + "Text", + "Varchar", + "Bool", + "Bool", + "Text", + "Jsonb", + "Text", + "Varchar", + "Text", + "Text" + ] + }, + "nullable": [] + }, + "hash": "d2acc380c45df31f741bbd420c2f4a5b962f7803def9ecd6e33d2aec6abce772" +} diff --git a/backend/.sqlx/query-e4f1ee1568ce3c186b569421c6c8a3039f73d04fc53c67c70e67371f06416ef3.json b/backend/.sqlx/query-e4f1ee1568ce3c186b569421c6c8a3039f73d04fc53c67c70e67371f06416ef3.json deleted file mode 100644 index 3e61e808ce..0000000000 --- a/backend/.sqlx/query-e4f1ee1568ce3c186b569421c6c8a3039f73d04fc53c67c70e67371f06416ef3.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "INSERT INTO flow (workspace_id, path, summary, description, dependency_job, lock_error_logs, draft_only, tag, dedicated_worker, visible_to_runner_only, on_behalf_of_email, value, schema, edited_by, edited_at) \n VALUES ($1, $2, $3, $4, NULL, '', $5, $6, $7, $8, $9, $10, $11::text::json, $12, now())", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Varchar", - "Varchar", - "Text", - "Text", - "Bool", - "Varchar", - "Bool", - "Bool", - "Text", - "Jsonb", - "Text", - "Varchar" - ] - }, - "nullable": [] - }, - "hash": "e4f1ee1568ce3c186b569421c6c8a3039f73d04fc53c67c70e67371f06416ef3" -} diff --git a/backend/windmill-api/openapi.yaml b/backend/windmill-api/openapi.yaml index b599d39009..5eabd44a2c 100644 --- a/backend/windmill-api/openapi.yaml +++ b/backend/windmill-api/openapi.yaml @@ -16732,6 +16732,7 @@ components: additionalProperties: type: boolean + FlowMetadata: type: object properties: diff --git a/backend/windmill-api/src/flows.rs b/backend/windmill-api/src/flows.rs index f9d7827341..a54c3655f5 100644 --- a/backend/windmill-api/src/flows.rs +++ b/backend/windmill-api/src/flows.rs @@ -409,11 +409,18 @@ async fn create_flow( check_schedule_conflict(&mut tx, &w_id, &nf.path).await?; let schema_str = nf.schema.and_then(|x| serde_json::to_string(&x.0).ok()); - sqlx::query!( - "INSERT INTO flow (workspace_id, path, summary, description, \ - dependency_job, lock_error_logs, draft_only, tag, dedicated_worker, visible_to_runner_only, on_behalf_of_email, value, schema, edited_by, edited_at) - VALUES ($1, $2, $3, $4, NULL, '', $5, $6, $7, $8, $9, $10, $11::text::json, $12, now())", + r#"INSERT INTO flow ( + workspace_id, path, summary, description, + dependency_job, lock_error_logs, draft_only, tag, + dedicated_worker, visible_to_runner_only, on_behalf_of_email, + value, schema, edited_by, edited_at + ) VALUES ( + $1, $2, $3, $4, + NULL, '', $5, $6, + $7, $8, $9, + $10, $11::text::json, $12, now() + )"#, w_id, nf.path, nf.summary, @@ -744,11 +751,27 @@ async fn update_flow( let schema_str = schema.and_then(|x| serde_json::to_string(&x).ok()); sqlx::query!( - "UPDATE flow SET path = $1, summary = $2, description = $3,\ - dependency_job = NULL, lock_error_logs = '', draft_only = NULL, tag = $4, dedicated_worker = $5, visible_to_runner_only = $6, on_behalf_of_email = $7, \ - value = $8, schema = $9::text::json, edited_by = $10, edited_at = now() - WHERE path = $11 AND workspace_id = $12", - if is_new_path { flow_path } else { &nf.path }, // if new path, do not rename directly (to avoid flow_version foreign key constraint) + " + UPDATE + flow + SET + path = $1, + summary = $2, + description = $3, + dependency_job = NULL, + lock_error_logs = '', + draft_only = NULL, + tag = $4, + dedicated_worker = $5, + visible_to_runner_only = $6, + on_behalf_of_email = $7, + value = $8, + schema = $9::text::json, + edited_by = $10, + edited_at = now() + WHERE + path = $11 AND workspace_id = $12", + if is_new_path { flow_path } else { &nf.path }, nf.summary, nf.description.unwrap_or_else(String::new), nf.tag, @@ -761,12 +784,15 @@ async fn update_flow( }, nf.value, schema_str, - &authed.username, + authed.username, flow_path, w_id, ) .execute(&mut *tx) - .await.map_err(|e| error::Error::internal_err(format!("Error updating flow due to flow update: {e:#}")))?; + .await + .map_err(|e| { + error::Error::internal_err(format!("Error updating flow due to flow update: {e:#}")) + })?; if is_new_path { // if new path, must clone flow to new path and delete old flow for flow_version foreign key constraint @@ -1047,36 +1073,80 @@ async fn get_flow_by_path( let path = path.to_path(); check_scopes(&authed, || format!("flows:read:{}", path))?; let mut tx = user_db.begin(&authed).await?; - let flow_o = if query.with_starred_info.unwrap_or(false) { sqlx::query_as::<_, FlowWithStarred>( - "SELECT flow.workspace_id, flow.path, flow.lock_error_logs, flow.summary, flow.description, flow.archived, flow.extra_perms, flow.draft_only, flow.dedicated_worker, flow.tag, flow.ws_error_handler_muted, flow.timeout, flow.visible_to_runner_only, flow.on_behalf_of_email, flow_version.schema, flow_version.value, flow_version.created_at as edited_at, flow_version.created_by as edited_by, favorite.path IS NOT NULL as starred - FROM flow - LEFT JOIN favorite + r#" + SELECT + flow.workspace_id, + flow.path, + flow.lock_error_logs, + flow.summary, + flow.description, + flow.archived, + flow.extra_perms, + flow.draft_only, + flow.dedicated_worker, + flow.tag, + flow.ws_error_handler_muted, + flow.timeout, + flow.visible_to_runner_only, + flow.on_behalf_of_email, + flow_version.schema, + flow_version.value, + flow_version.created_at AS edited_at, + flow_version.created_by AS edited_by, + favorite.path IS NOT NULL AS starred + FROM flow + LEFT JOIN favorite ON favorite.favorite_kind = 'flow' - AND favorite.workspace_id = flow.workspace_id - AND favorite.path = flow.path - AND favorite.usr = $3 - LEFT JOIN flow_version ON flow_version.id = flow.versions[array_upper(flow.versions, 1)] - WHERE flow.path = $1 AND flow.workspace_id = $2" + AND favorite.workspace_id = flow.workspace_id + AND favorite.path = flow.path + AND favorite.usr = $3 + LEFT JOIN flow_version + ON flow_version.id = flow.versions[array_upper(flow.versions, 1)] + WHERE flow.path = $1 AND flow.workspace_id = $2 + "#, ) - .bind(path) - .bind(w_id) - .bind(&authed.username) - .fetch_optional(&mut *tx) - .await? + .bind(path) + .bind(w_id) + .bind(&authed.username) + .fetch_optional(&mut *tx) + .await? } else { sqlx::query_as::<_, FlowWithStarred>( - "SELECT flow.workspace_id, flow.path, flow.lock_error_logs, flow.summary, flow.description, flow.archived, flow.extra_perms, flow.draft_only, flow.dedicated_worker, flow.tag, flow.ws_error_handler_muted, flow.timeout, flow.visible_to_runner_only, flow.on_behalf_of_email, flow_version.schema, flow_version.value, flow_version.created_at as edited_at, flow_version.created_by as edited_by, NULL as starred - FROM flow - LEFT JOIN flow_version ON flow_version.id = flow.versions[array_upper(flow.versions, 1)] - WHERE flow.path = $1 AND flow.workspace_id = $2" + r#" + SELECT + flow.workspace_id, + flow.path, + flow.lock_error_logs, + flow.summary, + flow.description, + flow.archived, + flow.extra_perms, + flow.draft_only, + flow.dedicated_worker, + flow.tag, + flow.ws_error_handler_muted, + flow.timeout, + flow.visible_to_runner_only, + flow.on_behalf_of_email, + flow_version.schema, + flow_version.value, + flow_version.created_at AS edited_at, + flow_version.created_by AS edited_by, + NULL AS starred + FROM flow + LEFT JOIN flow_version + ON flow_version.id = flow.versions[array_upper(flow.versions, 1)] + WHERE flow.path = $1 AND flow.workspace_id = $2 + "#, ) - .bind(path) - .bind(w_id) - .fetch_optional(&mut *tx) - .await? + .bind(path) + .bind(w_id) + .fetch_optional(&mut *tx) + .await? }; + tx.commit().await?; let flow = not_found_if_none(flow_o, "Flow", path)?; @@ -1115,20 +1185,36 @@ async fn get_flow_by_path_w_draft( let path = path.to_path(); check_scopes(&authed, || format!("flows:read:{}", path))?; let mut tx = user_db.begin(&authed).await?; - let flow_o = sqlx::query_as::<_, FlowWDraft>( - "SELECT flow.path, flow.summary, flow.description, flow_version.schema, flow_version.value, flow.extra_perms, flow.draft_only, flow.ws_error_handler_muted, flow.dedicated_worker, draft.value as draft, flow.tag, flow.visible_to_runner_only, flow.on_behalf_of_email - FROM flow + "SELECT + flow.path, + flow.summary, + flow.description, + flow_version.schema, + flow_version.value, + flow.extra_perms, + flow.draft_only, + flow.ws_error_handler_muted, + flow.dedicated_worker, + draft.value AS draft, + flow.tag, + flow.visible_to_runner_only, + flow.on_behalf_of_email + FROM flow LEFT JOIN draft - ON flow.path = draft.path AND draft.workspace_id = $2 AND draft.typ = 'flow' - LEFT JOIN flow_version + ON flow.path = draft.path + AND draft.workspace_id = $2 + AND draft.typ = 'flow' + LEFT JOIN flow_version ON flow_version.id = flow.versions[array_upper(flow.versions, 1)] - WHERE flow.path = $1 AND flow.workspace_id = $2", + WHERE flow.path = $1 + AND flow.workspace_id = $2", ) .bind(path) .bind(w_id) .fetch_optional(&mut *tx) .await?; + tx.commit().await?; let flow = not_found_if_none(flow_o, "Flow", path)?; diff --git a/backend/windmill-common/src/flows.rs b/backend/windmill-common/src/flows.rs index 4a4cb200a8..52146213d0 100644 --- a/backend/windmill-common/src/flows.rs +++ b/backend/windmill-common/src/flows.rs @@ -27,7 +27,7 @@ use crate::{ DB, }; -#[derive(Serialize, Deserialize, sqlx::FromRow)] +#[derive(Debug, Serialize, Deserialize, sqlx::FromRow)] pub struct Flow { pub workspace_id: String, pub path: String, @@ -91,7 +91,7 @@ pub struct ListableFlow { pub deployment_msg: Option, } -#[derive(Deserialize, sqlx::FromRow)] +#[derive(Debug, Deserialize, sqlx::FromRow)] pub struct NewFlow { pub path: String, pub summary: String, diff --git a/frontend/src/lib/components/ArgInput.svelte b/frontend/src/lib/components/ArgInput.svelte index 0af19e9868..1a9e7c0efc 100644 --- a/frontend/src/lib/components/ArgInput.svelte +++ b/frontend/src/lib/components/ArgInput.svelte @@ -7,7 +7,8 @@ setInputCat as computeInputCat, debounce, emptyString, - getSchemaFromProperties + getSchemaFromProperties, + type DynamicSelect } from '$lib/utils' import { DollarSign, Plus, X, Check, Loader2, ExternalLink } from 'lucide-svelte' import { createEventDispatcher, onDestroy, onMount, tick, untrack } from 'svelte' @@ -35,7 +36,6 @@ import SchemaForm from './SchemaForm.svelte' import { deepEqual } from 'fast-equals' import DynSelect from './DynSelect.svelte' - import type { Script } from '$lib/gen' import type { SchemaDiff } from '$lib/components/schema/schemaUtils.svelte' import type { ComponentCustomCSS } from './apps/types' import MultiSelect from './select/MultiSelect.svelte' @@ -94,10 +94,7 @@ orderEditable?: boolean shouldDispatchChanges?: boolean noDefaultOnSelectFirst?: boolean - helperScript?: - | { type: 'inline'; path?: string; lang: Script['language']; code: string } - | { type: 'hash'; hash: string } - | undefined + helperScript?: DynamicSelect.HelperScript otherArgs?: Record lightHeader?: boolean diffStatus?: SchemaDiff | undefined @@ -866,7 +863,7 @@ {:else if inputCat == 'dynselect'} {#if helperScript} diff --git a/frontend/src/lib/components/EditableSchemaForm.svelte b/frontend/src/lib/components/EditableSchemaForm.svelte index b19325f45b..555b78a888 100644 --- a/frontend/src/lib/components/EditableSchemaForm.svelte +++ b/frontend/src/lib/components/EditableSchemaForm.svelte @@ -3,7 +3,7 @@ const bubble = createBubbler() import type { Schema } from '$lib/common' - import { VariableService } from '$lib/gen' + import { VariableService, type ScriptLang } from '$lib/gen' import { workspaceStore } from '$lib/stores' import { Button } from './common' import ItemPicker from './ItemPicker.svelte' @@ -20,13 +20,15 @@ import Label from './Label.svelte' import { sendUserToast } from '$lib/toast' import Toggle from './Toggle.svelte' - import { emptyString } from '$lib/utils' + import { DynamicSelect, emptyString } from '$lib/utils' import Popover from './meltComponents/Popover.svelte' import SchemaFormDnd from './schema/SchemaFormDND.svelte' import { deepEqual } from 'fast-equals' import { tweened } from 'svelte/motion' import type { SchemaDiff } from '$lib/components/schema/schemaUtils.svelte' import type { EditableSchemaFormUi } from '$lib/components/custom_ui' + import Section from '$lib/components/Section.svelte' + import Editor from './Editor.svelte' // export let openEditTab: () => void = () => {} const dispatch = createEventDispatcher() @@ -64,6 +66,9 @@ customUi?: EditableSchemaFormUi | undefined pannelExtraButtonWidth?: number class?: string + dynSelectCode?: string | undefined + dynSelectLang?: ScriptLang | undefined + showDynSelectOpt?: boolean openEditTab?: import('svelte').Snippet addProperty?: import('svelte').Snippet runButton?: import('svelte').Snippet @@ -96,6 +101,9 @@ customUi = undefined, pannelExtraButtonWidth = 0, class: clazz = '', + dynSelectCode = $bindable(), + dynSelectLang = $bindable(), + showDynSelectOpt = false, openEditTab, addProperty, runButton, @@ -106,6 +114,24 @@ if (args == undefined) { args = {} } + if (dynSelectLang === undefined) { + dynSelectLang = schema?.['x-windmill-dyn-select-lang'] || 'bun' + } + if (dynSelectCode === undefined) { + dynSelectCode = schema?.['x-windmill-dyn-select-code'] || '' + } + }) + + $effect(() => { + if (schema && dynSelectCode !== undefined && dynSelectLang !== undefined) { + if (dynSelectCode && dynSelectCode.trim()) { + schema['x-windmill-dyn-select-code'] = dynSelectCode.trim() + schema['x-windmill-dyn-select-lang'] = dynSelectLang + } else { + delete schema['x-windmill-dyn-select-code'] + delete schema['x-windmill-dyn-select-lang'] + } + } }) export function setDefaults() { @@ -183,9 +209,11 @@ ? property.type : property.format === 'resource-s3_object' ? 'S3' - : property.oneOf && property.oneOf.length >= 2 - ? 'oneOf' - : 'object' + : property.format?.startsWith('dynselect-') + ? 'dynselect' + : property.oneOf && property.oneOf.length >= 2 + ? 'oneOf' + : 'object' } export function openField(key: string) { @@ -248,7 +276,7 @@ let schemaString: string = $state(JSON.stringify(schema, null, '\t')) let error: string | undefined = $state(undefined) let editor: SimpleEditor | undefined = $state(undefined) - + let dynamicSelectEditor: Editor | undefined = $state(undefined) export function updateJson() { schemaString = JSON.stringify(schema, null, '\t') editor?.setCode(schemaString) @@ -297,6 +325,45 @@ $effect(() => { !!editTab ? openEditTabFn() : closeEditTab() }) + + let dynSelectFunctions = $derived( + Object.entries(schema?.properties ?? {}) + .filter(([_, property]) => { + const props = property as any + return ( + props.type === 'object' && + (props.format?.startsWith('dynselect-') || props.format?.startsWith('dynselect_')) + ) + }) + .map(([fieldName, _]) => fieldName.replace(/\s+/g, '_')) + ) + + let typeOptions = [ + ['String', 'string'], + ['Number', 'number'], + ['Integer', 'integer'], + ['Object', 'object'], + ['OneOf', 'oneOf'], + ['Array', 'array'], + ['Boolean', 'boolean'], + ['S3 Object', 'S3'] + ] + if (showDynSelectOpt) { + typeOptions.push(['DynSelect', 'dynselect']) + } + + function initDynSelectFn(lang: ScriptLang) { + const generateFn = DynamicSelect.getGenerateTemplateFn(lang) + return Object.entries(schema?.properties ?? {}) + .map(([functionName]) => generateFn(functionName)) + .join('') + } + + function updateDynSelectCode(functionName: string, lang: ScriptLang = 'bun') { + const generateFn = DynamicSelect.getGenerateTemplateFn(lang) + const code = generateFn(functionName) + dynSelectCode = dynSelectCode ? dynSelectCode.concat(code) : code + }
@@ -355,6 +422,11 @@ } tick().then(() => dispatch('change', schema)) }} + helperScript={{ + type: 'inline', + code: dynSelectCode!, + lang: dynSelectLang! + }} prettifyHeader={isAppInput} disabled={!!previewSchema} {diff} @@ -369,6 +441,57 @@ /> {@render runButton?.()} + +
+ {#if dynSelectFunctions.length > 0} +
+
+ {#if dynSelectFunctions.length > 0} +
+
+ Expected Functions for Dynamic Select Fields: +
+
    + {#each dynSelectFunctions as functionName} +
  • + {functionName}() +
  • + {/each} +
+
+ {/if} + { + dynSelectCode = initDynSelectFn(detail) + }} + > + {#snippet children({ item })} + + + {/snippet} + + {#key dynSelectLang} +
+ +
+ {/key} +
+
+ {/if} +
@@ -530,6 +653,7 @@ (v) => { const isS3 = v == 'S3' const isOneOf = v == 'oneOf' + const isDynSelect = v == 'dynselect' const emptyProperty = { contentEncoding: undefined, @@ -555,6 +679,15 @@ type: 'object', format: 'resource-s3_object' } + } else if (isDynSelect) { + const functionName = argName.replace(/\s+/g, '_') + schema.properties[argName] = { + ...emptyProperty, + type: 'object', + format: 'dynselect-' + functionName + } + updateDynSelectCode(argName, dynSelectLang) + dynamicSelectEditor?.setCode(dynSelectCode || '') } else if (isOneOf) { schema.properties[argName] = { ...emptyProperty, @@ -604,7 +737,7 @@ }} > {#snippet children({ item })} - {#each [['String', 'string'], ['Number', 'number'], ['Integer', 'integer'], ['Object', 'object'], ['OneOf', 'oneOf'], ['Array', 'array'], ['Boolean', 'boolean'], ['S3 Object', 'S3']] as x} + {#each typeOptions as x} {/each} {/snippet} diff --git a/frontend/src/lib/components/FlowPreviewContent.svelte b/frontend/src/lib/components/FlowPreviewContent.svelte index d88023ba6a..79176001b7 100644 --- a/frontend/src/lib/components/FlowPreviewContent.svelte +++ b/frontend/src/lib/components/FlowPreviewContent.svelte @@ -1,7 +1,7 @@
@@ -284,7 +285,7 @@ }} /> {/if} - {:else if type === 'object' && format !== 'resource-s3_object'} + {:else if type === 'object' && format !== 'resource-s3_object' && !isDynSelect} { @@ -325,7 +326,7 @@ {/if} - {#if !(type === 'object' && oneOf && oneOf.length >= 2) && !(type == 'object' && initialObjectSelected == 'custom-object')} + {#if !(type === 'object' && oneOf && oneOf.length >= 2) && !(type == 'object' && initialObjectSelected == 'custom-object') && !isDynSelect}
{/if} - {:else if type == 'object' && format?.startsWith('dynselect-')} -
No settings available for Dynamic Select
{:else if type == 'object' && !format?.startsWith('resource-') && !isFlowInput && !isAppInput}
void } = $props() - let disabled = $derived(_disabled || loading) + let disabled = $derived(_disabled || (loading && !value)) let inputEl: HTMLInputElement | undefined = $state() @@ -121,12 +121,14 @@ {disabled} type="text" bind:value={() => filterText, (v) => (filterText = v)} - placeholder={loading ? 'Loading...' : (valueEntry?.label ?? getLabel({ value }) ?? placeholder)} + placeholder={loading && !value + ? 'Loading...' + : (valueEntry?.label ?? getLabel({ value }) ?? placeholder)} style={containerStyle} class={twMerge( '!bg-surface text-ellipsis', open ? '' : 'cursor-pointer', - value && !loading ? '!placeholder-primary' : '', + !loading ? '!placeholder-primary' : '', (clearable || RightIcon) && !disabled && value ? '!pr-8' : '', inputClass ?? '' )} diff --git a/frontend/src/lib/utils.ts b/frontend/src/lib/utils.ts index 30788bf1bc..35c1f79273 100644 --- a/frontend/src/lib/utils.ts +++ b/frontend/src/lib/utils.ts @@ -172,9 +172,9 @@ export function displayDate( } const dateChoices: Intl.DateTimeFormatOptions = displayDate ? { - day: 'numeric', - month: 'numeric' - } + day: 'numeric', + month: 'numeric' + } : {} return date.toLocaleString(undefined, { ...timeChoices, @@ -593,6 +593,47 @@ export type InputCat = | 'oneOf' | 'dynselect' +export namespace DynamicSelect { + export type HelperScript = + | { type: 'inline'; path?: string; lang: Script['language']; code: string } + | { type: 'hash'; hash: string } + + export const generatePythonFnTemplate = (functionName: string): string => { + return ` +def ${functionName}(): + return [ + { "label": "Foo", "value": "foo" }, + { "label": "Bar", "value": "bar" } + ] + +` + } + + export const generateJsFnTemplate = (functionName: string): string => { + return ` +// you can use filterText to filter the results from the backend +// you can refer to other args directly as parameters (e.g. foobar: string) +export function ${functionName}(filterText: string) { + return [ + { label: 'Foo', value: 'foo' }, + { label: 'Bar', value: 'bar' } + ]; +} + +` + } + + export const generateDefaultTemplateFn = (functionName: string, lang: ScriptLang): string => { + return lang === 'bun' + ? generateJsFnTemplate(functionName) + : generatePythonFnTemplate(functionName) + } + + export const getGenerateTemplateFn = (lang: ScriptLang) => { + return lang === 'bun' ? generateJsFnTemplate : generatePythonFnTemplate + } +} + export function setInputCat( type: string | undefined, format: string | undefined, @@ -608,7 +649,10 @@ export function setInputCat( return 'list' } else if (type == 'object' && format?.startsWith('resource')) { return 'resource-object' - } else if (type == 'object' && format?.startsWith('dynselect-')) { + } else if ( + type == 'object' && + (format?.startsWith('dynselect-') || format?.startsWith('dynselect_')) + ) { return 'dynselect' } else if (!type || type == 'object' || type == 'array') { return 'object' @@ -981,7 +1025,7 @@ export async function tryEvery({ try { await tryCode() break - } catch (err) {} + } catch (err) { } i++ } if (i >= times) { @@ -1248,7 +1292,7 @@ export function conditionalMelt(node: HTMLElement, meltItem: AnyMeltElement | un if (meltItem) { return meltItem(node) } - return { destroy: () => {} } + return { destroy: () => { } } } export type Item = { @@ -1453,9 +1497,9 @@ export type S3Uri = `s3://${string}/${string}` export type S3Object = | S3Uri | { - s3: string - storage?: string - } + s3: string + storage?: string + } export function parseS3Object(s3Object: S3Object): { s3: string; storage?: string } { if (typeof s3Object === 'object') return s3Object