mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-20 00:02:19 +00:00
feat(frontend): support array of objects in schema (#4106)
* feat(frontend): Support array of objets * feat(frontend): add support for LightweightArgInput * feat(frontend): support objects and arrays of objects in LightweightArgInput
This commit is contained in:
@@ -30,6 +30,7 @@ export interface SchemaProperty {
|
||||
contentEncoding?: 'base64'
|
||||
enum?: string[]
|
||||
resourceType?: string
|
||||
properties?: { [name: string]: SchemaProperty }
|
||||
}
|
||||
min?: number
|
||||
max?: number
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
<script lang="ts">
|
||||
import type { EnumType, SchemaProperty } from '$lib/common'
|
||||
import { setInputCat as computeInputCat, debounce, emptyString } from '$lib/utils'
|
||||
import {
|
||||
setInputCat as computeInputCat,
|
||||
debounce,
|
||||
emptyString,
|
||||
getSchemaFromProperties
|
||||
} from '$lib/utils'
|
||||
import { DollarSign, Pipette, Plus, X } from 'lucide-svelte'
|
||||
import { createEventDispatcher, tick } from 'svelte'
|
||||
import Multiselect from 'svelte-multiselect'
|
||||
@@ -54,6 +59,7 @@
|
||||
enum?: string[]
|
||||
multiselect?: string[]
|
||||
resourceType?: string
|
||||
properties?: { [name: string]: SchemaProperty }
|
||||
}
|
||||
| undefined = undefined
|
||||
|
||||
@@ -381,7 +387,7 @@
|
||||
on:change={(x) => fileChanged(x, (val) => (value[i] = val))}
|
||||
multiple={false}
|
||||
/>
|
||||
{:else if itemsType?.type == 'object' && itemsType?.resourceType === undefined}
|
||||
{:else if itemsType?.type == 'object' && itemsType?.resourceType === undefined && itemsType?.properties === undefined}
|
||||
<JsonEditor code={JSON.stringify(v, null, 2)} bind:value={v} />
|
||||
{:else if Array.isArray(itemsType?.enum)}
|
||||
<ArgEnum
|
||||
@@ -403,6 +409,17 @@
|
||||
/>
|
||||
{:else if itemsType?.type == 'resource' && itemsType?.resourceType}
|
||||
<ResourcePicker bind:value={v} resourceType={itemsType?.resourceType} />
|
||||
{:else if itemsType?.type === 'object' && itemsType?.properties}
|
||||
<div class="p-8 border rounded-md w-full">
|
||||
<SchemaForm
|
||||
{onlyMaskPassword}
|
||||
{disablePortal}
|
||||
{disabled}
|
||||
noDelete
|
||||
schema={getSchemaFromProperties(itemsType?.properties)}
|
||||
bind:args={v}
|
||||
/>
|
||||
</div>
|
||||
{:else}
|
||||
<input type="text" bind:value={v} id="arg-input-array" />
|
||||
{/if}
|
||||
@@ -623,7 +640,7 @@
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
{:else if properties && Object.keys(properties).length > 0}
|
||||
{:else if properties && Object.keys(properties).length > 0 && inputCat !== 'list'}
|
||||
<div class="p-4 pl-8 border rounded-md w-full">
|
||||
{#if orderEditable}
|
||||
<SchemaFormDnd
|
||||
|
||||
@@ -56,7 +56,7 @@
|
||||
<option value="bytes">Items are bytes</option>
|
||||
</select>
|
||||
</Label>
|
||||
{:else}
|
||||
{:else if itemsType?.resourceType}
|
||||
<Label label="Resource type">
|
||||
<Badge color="blue">
|
||||
{itemsType?.resourceType}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { setInputCat as computeInputCat, emptyString } from '$lib/utils'
|
||||
import { setInputCat as computeInputCat, emptyString, getSchemaFromProperties } from '$lib/utils'
|
||||
import { Button } from './common'
|
||||
import { createEventDispatcher, tick } from 'svelte'
|
||||
import FieldHeader from './FieldHeader.svelte'
|
||||
@@ -45,6 +45,7 @@
|
||||
enum?: string[]
|
||||
multiselect?: string[]
|
||||
resourceType?: string
|
||||
properties?: { [name: string]: SchemaProperty }
|
||||
}
|
||||
| undefined = undefined
|
||||
export let displayHeader = true
|
||||
@@ -115,9 +116,14 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Only used for object inputCat
|
||||
export function evalValueToRaw() {
|
||||
if (value) {
|
||||
rawValue = JSON.stringify(value, null, 4)
|
||||
} else {
|
||||
// If value is undefined, set rawValue to empty object
|
||||
// This is to prevent the textarea from being empty
|
||||
rawValue = '{}'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -202,6 +208,18 @@
|
||||
valid = true
|
||||
error = ''
|
||||
}
|
||||
|
||||
function addItemByItemsType() {
|
||||
if (value == undefined || !Array.isArray(value)) {
|
||||
value = []
|
||||
}
|
||||
|
||||
if (itemsType?.type === 'object') {
|
||||
value = value.concat({})
|
||||
} else {
|
||||
value = value.concat('')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if render}
|
||||
@@ -335,12 +353,19 @@
|
||||
bind:value={v}
|
||||
resourceType={itemsType?.resourceType}
|
||||
/>
|
||||
{:else if itemsType?.type === 'object' && itemsType?.properties}
|
||||
<div class="p-8 border rounded-md w-full">
|
||||
<LightweightSchemaForm
|
||||
schema={getSchemaFromProperties(itemsType?.properties)}
|
||||
bind:args={v}
|
||||
/>
|
||||
</div>
|
||||
{:else}
|
||||
<input type="text" bind:value={v} />
|
||||
{/if}
|
||||
<button
|
||||
transition:fade|local={{ duration: 100 }}
|
||||
class="rounded-full p-1 bg-surface-secondary duration-200 hover:bg-surface-hover ml-2"
|
||||
class="rounded-full p-1 bg-surface-secondary duration-200 hover:bg-surface-hover ml-2 flex items-center h-6"
|
||||
aria-label="Clear"
|
||||
on:click={() => {
|
||||
value = value.filter((el) => el != v)
|
||||
@@ -364,10 +389,7 @@
|
||||
size="sm"
|
||||
btnClasses="mt-1"
|
||||
on:click={() => {
|
||||
if (value == undefined || !Array.isArray(value)) {
|
||||
value = []
|
||||
}
|
||||
value = value.concat('')
|
||||
addItemByItemsType()
|
||||
}}
|
||||
startIcon={{ icon: Plus }}
|
||||
>
|
||||
|
||||
@@ -4,8 +4,14 @@ export function argSigToJsonSchemaType(
|
||||
t:
|
||||
| string
|
||||
| { resource: string | null }
|
||||
| {
|
||||
list:
|
||||
| (string | { object: { key: string; typ: any }[] })
|
||||
| { str: any }
|
||||
| { object: { key: string; typ: any }[] }
|
||||
| null
|
||||
}
|
||||
| { dynselect: string }
|
||||
| { list: string | { str: any } | { object: { key: string; typ: any }[] } | null }
|
||||
| { str: string[] | null }
|
||||
| { object: { key: string; typ: any }[] }
|
||||
| {
|
||||
@@ -107,6 +113,21 @@ export function argSigToJsonSchemaType(
|
||||
newS.items = { type: 'string', enum: t.list.str }
|
||||
} else if (t.list && typeof t.list == 'object' && 'resource' in t.list && t.list.resource) {
|
||||
newS.items = { type: 'resource', resourceType: t.list.resource as string }
|
||||
} else if (
|
||||
t.list &&
|
||||
typeof t.list == 'object' &&
|
||||
'object' in t.list &&
|
||||
t.list.object &&
|
||||
t.list.object.length > 0
|
||||
) {
|
||||
const properties = {}
|
||||
for (const prop of t.list.object) {
|
||||
properties[prop.key] = { description: '', type: '' }
|
||||
|
||||
argSigToJsonSchemaType(prop.typ, properties[prop.key])
|
||||
}
|
||||
|
||||
newS.items = { type: 'object', properties: properties }
|
||||
} else {
|
||||
newS.items = { type: 'object' }
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import YAML from 'yaml'
|
||||
import type { UserExt } from './stores'
|
||||
import { sendUserToast } from './toast'
|
||||
import type { Script } from './gen'
|
||||
import type { EnumType } from './common'
|
||||
import type { EnumType, SchemaProperty } from './common'
|
||||
import type { Schema } from './common'
|
||||
export { sendUserToast }
|
||||
|
||||
@@ -927,3 +927,13 @@ export function shouldDisplayPlaceholder(
|
||||
|
||||
return type === undefined
|
||||
}
|
||||
|
||||
export function getSchemaFromProperties(properties: { [name: string]: SchemaProperty }): Schema {
|
||||
return {
|
||||
properties: Object.fromEntries(Object.entries(properties).filter(([k, v]) => k !== 'label')),
|
||||
required: Object.keys(properties).filter((k) => properties[k].required),
|
||||
$schema: '',
|
||||
type: 'object',
|
||||
order: Object.keys(properties).filter((k) => k !== 'label')
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user