mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-25 08:00:59 +00:00
feat: Add flow input and current step in the prop picker (#236)
* Add flow input and current step in the prop picker * Fix step + correctly bind pickableProperties * Correctly make pickable properties + use popper to fix display issues * styling * Remove debugger * Simplify how search works by removing one store * preview Co-authored-by: Ruben Fiszel <ruben@rubenfiszel.com>
This commit is contained in:
@@ -54,7 +54,7 @@
|
||||
}
|
||||
|
||||
flowStore.subscribe((flow: Flow) => {
|
||||
setQueryWithoutLoad($page.url, 'state', btoa(JSON.stringify(flowToMode(flow, mode))))
|
||||
//setQueryWithoutLoad($page.url, 'state', btoa(JSON.stringify(flowToMode(flow, mode))))
|
||||
})
|
||||
|
||||
onMount(() => {
|
||||
|
||||
@@ -33,6 +33,8 @@
|
||||
let jobs = []
|
||||
let jobId: string
|
||||
|
||||
$: dispatch('change', jobs)
|
||||
|
||||
export async function runPreview(args) {
|
||||
viewPreview = true
|
||||
intervalId && clearInterval(intervalId)
|
||||
@@ -77,7 +79,6 @@
|
||||
//only CompletedJob has success property
|
||||
clearInterval(intervalId)
|
||||
}
|
||||
dispatch('change', job)
|
||||
} catch (err) {
|
||||
sendUserToast(err, true)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { FlowModuleValue, type FlowModule } from '$lib/gen'
|
||||
import { addPreviewResult, previewResults } from '$lib/stores'
|
||||
import { buildExtraLib, objectToTsType, schemaToObject, schemaToTsType } from '$lib/utils'
|
||||
import { previewResults } from '$lib/stores'
|
||||
import { buildExtraLib, objectToTsType, schemaToTsType } from '$lib/utils'
|
||||
import { faRobot } from '@fortawesome/free-solid-svg-icons'
|
||||
import Icon from 'svelte-awesome'
|
||||
import Editor from './Editor.svelte'
|
||||
@@ -17,6 +17,7 @@
|
||||
schemasStore,
|
||||
type FlowMode
|
||||
} from './flows/flowStore'
|
||||
import { getPickableProperties } from './flows/utils'
|
||||
import SchemaForm from './SchemaForm.svelte'
|
||||
import Tooltip from './Tooltip.svelte'
|
||||
|
||||
@@ -28,10 +29,11 @@
|
||||
|
||||
let editor: Editor
|
||||
let websocketAlive = { pyright: false, black: false, deno: false }
|
||||
let pickableProperties: Object | undefined = undefined
|
||||
|
||||
$: schema = $schemasStore[i]
|
||||
$: shouldPick = mod.value.path === '' && mod.value.language === undefined
|
||||
$: previousSchema = i === 0 ? schemaToObject($flowStore?.schema) : $previewResults[i]
|
||||
$: pickableProperties = getPickableProperties($flowStore?.schema, args, $previewResults, mode, i)
|
||||
$: extraLib = buildExtraLib(
|
||||
i === 0 ? schemaToTsType($flowStore?.schema) : objectToTsType($previewResults[i])
|
||||
)
|
||||
@@ -107,7 +109,7 @@
|
||||
{schema}
|
||||
{extraLib}
|
||||
{i}
|
||||
{previousSchema}
|
||||
bind:pickableProperties
|
||||
bind:args={mod.input_transform}
|
||||
/>
|
||||
{/if}
|
||||
@@ -123,19 +125,22 @@
|
||||
{mode}
|
||||
schemas={$schemasStore}
|
||||
on:change={(e) => {
|
||||
addPreviewResult(e.detail.result, i + 1)
|
||||
const results = e.detail.map((x) => x.result)
|
||||
previewResults.set(results)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<button class="w-full h-full" on:click={() => (open = -1)}>(-)</button>
|
||||
</div>
|
||||
{:else}
|
||||
<div>
|
||||
<button class="w-full h-full" on:click={() => (open = i)}>(+)</button>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
{#if open == i}
|
||||
<div>
|
||||
<button class="w-full h-full" on:click={() => (open = -1)}>(-)</button>
|
||||
</div>
|
||||
{:else}
|
||||
<div>
|
||||
<button class="w-full h-full" on:click={() => (open = i)}>(+)</button>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</li>
|
||||
{#if i == 0 && mode == 'pull'}
|
||||
@@ -144,7 +149,7 @@
|
||||
Starting from here, the flow for loop over items from step 1's result above <Tooltip
|
||||
>This flow being in 'Pull' mode, the rest of the flow will for loop over the list of items
|
||||
returned by the trigger script right above. Retrieve the item value using
|
||||
`previous_result._value`</Tooltip
|
||||
`flow_input._value`</Tooltip
|
||||
>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
export let isValid: boolean = true
|
||||
|
||||
export let i: number | undefined = undefined
|
||||
export let previousSchema: Object | undefined = undefined
|
||||
export let pickableProperties: Object | undefined = undefined
|
||||
|
||||
let inputCheck: { [id: string]: boolean } = {}
|
||||
$: isValid = allTrue(inputCheck) ?? false
|
||||
@@ -103,10 +103,10 @@
|
||||
|
||||
{#if propertiesTypes[argName] === undefined || propertiesTypes[argName] === InputTransform.type.STATIC}
|
||||
<OverlayPropertyPicker
|
||||
{previousSchema}
|
||||
bind:pickableProperties
|
||||
disabled={!hasOverlay(inputCats[argName])}
|
||||
on:select={(event) => {
|
||||
const toAppend = `\$\{previous_result.${event.detail}}`
|
||||
const toAppend = `\$\{${event.detail}}`
|
||||
args[argName].value = `${args[argName].value ?? ''}${toAppend}`
|
||||
setPropertyType(argName, args[argName].value, false)
|
||||
}}
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
import type { Schema } from '$lib/common'
|
||||
import {
|
||||
FlowModuleValue,
|
||||
InputTransform,
|
||||
ScriptService,
|
||||
type Flow,
|
||||
type FlowModule
|
||||
} from '$lib/gen'
|
||||
import { FlowModuleValue, InputTransform, type Flow, type FlowModule } from '$lib/gen'
|
||||
import { inferArgs } from '$lib/infer'
|
||||
import { loadSchema } from '$lib/scripts'
|
||||
import { emptySchema, getScriptByPath } from '$lib/utils'
|
||||
import { emptySchema, getScriptByPath, schemaToObject } from '$lib/utils'
|
||||
|
||||
import type { FlowMode } from './flowStore'
|
||||
|
||||
export function flowToMode(flow: Flow | any, mode: FlowMode): Flow {
|
||||
@@ -54,15 +49,12 @@ export function getTypeAsString(arg: any): string {
|
||||
if (arg === null) {
|
||||
return 'null'
|
||||
}
|
||||
if (arg === undefined) {
|
||||
return 'undefined'
|
||||
}
|
||||
return typeof arg
|
||||
}
|
||||
|
||||
export function formatValue(arg: any) {
|
||||
if (getTypeAsString(arg) === 'string') {
|
||||
return `"${arg}"`
|
||||
}
|
||||
return arg
|
||||
}
|
||||
|
||||
export async function getFirstStepSchema(flow: Flow): Promise<Schema> {
|
||||
const [firstModule] = flow.value.modules
|
||||
@@ -79,8 +71,6 @@ export async function getFirstStepSchema(flow: Flow): Promise<Schema> {
|
||||
return emptySchema()
|
||||
}
|
||||
|
||||
|
||||
|
||||
export async function createInlineScriptModuleFromPath(path: string): Promise<FlowModuleValue> {
|
||||
const { content, language } = await getScriptByPath(path)
|
||||
|
||||
@@ -174,3 +164,32 @@ export function getDefaultExpr(i: number, key: string = 'myfield') {
|
||||
|
||||
previous_result.${key}`
|
||||
}
|
||||
|
||||
export function getPickableProperties(
|
||||
schema: Schema,
|
||||
args: Record<string, any>,
|
||||
previewResults: Record<number, Object>,
|
||||
mode: FlowMode,
|
||||
i: number
|
||||
) {
|
||||
const flowInputAsObject = schemaToObject(schema, args)
|
||||
const flowInput =
|
||||
mode === 'pull'
|
||||
? Object.assign(
|
||||
{
|
||||
_value: 'The current value of the iteration.',
|
||||
_index: 'The current index of the iteration.'
|
||||
},
|
||||
flowInputAsObject
|
||||
)
|
||||
: flowInputAsObject
|
||||
|
||||
console.log(previewResults)
|
||||
const pickableProperties = {
|
||||
flow_input: flowInput,
|
||||
previous_result: i === 0 ? flowInput : previewResults[i - 1],
|
||||
step: i >= 1 ? Object.values(previewResults).slice(0, i) : []
|
||||
}
|
||||
|
||||
return pickableProperties
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { formatValue, getTypeAsString } from '../flows/utils'
|
||||
import { getTypeAsString } from '../flows/utils'
|
||||
import WarningMessage from './WarningMessage.svelte'
|
||||
|
||||
export let json: Object
|
||||
export let level = 0
|
||||
@@ -30,6 +31,9 @@
|
||||
|
||||
function computeKey(key: string) {
|
||||
if (isArray) {
|
||||
if (currentPath === 'step') {
|
||||
return `${currentPath}(${key})?`
|
||||
}
|
||||
return `${currentPath}[${key}]`
|
||||
} else {
|
||||
if (currentPath) {
|
||||
@@ -47,12 +51,14 @@
|
||||
|
||||
{#if keys.length > 0}
|
||||
<span class:hidden={collapsed}>
|
||||
<span class="cursor-pointer hover:bg-slate-200" on:click={collapse}>{openBracket}</span>
|
||||
<ul>
|
||||
<!-- <span class="cursor-pointer hover:bg-slate-200" on:click={collapse}>{openBracket}</span> -->
|
||||
<ul class="w-full">
|
||||
{#each keys as key, index}
|
||||
<li class={getTypeAsString(json[key]) !== 'object' ? 'hover:bg-sky-100 py-2' : 'py-2'}>
|
||||
<li class={getTypeAsString(json[key]) !== 'object' ? 'hover:bg-sky-100 pt-1' : 'pt-1'}>
|
||||
{#if !isArray}
|
||||
<span class="key">{key}:</span>
|
||||
<span class="key mr-1">{key}:</span>
|
||||
{:else}
|
||||
<span class="key mr-1">{index}:</span>
|
||||
{/if}
|
||||
|
||||
{#if getTypeAsString(json[key]) === 'object'}
|
||||
@@ -64,23 +70,19 @@
|
||||
on:select
|
||||
/>
|
||||
{:else}
|
||||
<span class="val {getTypeAsString(json[key])}">
|
||||
{formatValue(json[key])}
|
||||
{#if index < keys.length - 1}
|
||||
<span class="text-black">,</span>
|
||||
<button class="val {getTypeAsString(json[key])}" on:click={() => selectProp(key)}>
|
||||
{#if json[key] === undefined}
|
||||
<WarningMessage />
|
||||
{:else}
|
||||
<span> {JSON.stringify(json[key])}</span>
|
||||
<button class="ml-2 default-button-secondary py-0"> Select </button>
|
||||
{/if}
|
||||
<button class="default-button-secondary" on:click={() => selectProp(key)}>
|
||||
Select
|
||||
</button>
|
||||
</span>
|
||||
</button>
|
||||
{/if}
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
<span class="cursor-pointer hover:bg-slate-200" on:click={collapse}>{closeBracket}</span>
|
||||
{#if !isLast}
|
||||
<span class="text-black">,</span>
|
||||
{/if}
|
||||
<!-- <span class="cursor-pointer hover:bg-slate-200" on:click={collapse}>{closeBracket}</span> -->
|
||||
</span>
|
||||
<span class="cursor-pointer hover:bg-slate-200" class:hidden={!collapsed} on:click={collapse}>
|
||||
{openBracket}{collapsedSymbol}{closeBracket}
|
||||
@@ -88,6 +90,8 @@
|
||||
{#if !isLast && collapsed}
|
||||
<span class="text-black">,</span>
|
||||
{/if}
|
||||
{:else}
|
||||
<span class="text-black">{openBracket}{closeBracket}</span>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
@@ -104,6 +108,12 @@
|
||||
.val {
|
||||
@apply text-black;
|
||||
}
|
||||
.val.undefined {
|
||||
@apply text-red-500;
|
||||
}
|
||||
.val.null {
|
||||
@apply text-red-500;
|
||||
}
|
||||
.val.string {
|
||||
@apply text-lime-600;
|
||||
}
|
||||
|
||||
@@ -1,19 +1,21 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import Overlay from 'svelte-overlay'
|
||||
import { createPopperActions } from 'svelte-popperjs'
|
||||
import PropPicker from './PropPicker.svelte'
|
||||
import WarningMessage from './WarningMessage.svelte'
|
||||
|
||||
export let previousSchema: Object | undefined
|
||||
const [popperRef, popperContent] = createPopperActions({
|
||||
placement: 'bottom',
|
||||
strategy: 'fixed'
|
||||
})
|
||||
|
||||
export let pickableProperties: Object | undefined
|
||||
export let disabled = false
|
||||
let isOpen = false
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
function handleWindowKeyDown(event: { key: string }) {
|
||||
if (event.key === 'Escape') {
|
||||
isOpen = false
|
||||
}
|
||||
function toggle() {
|
||||
isOpen = !isOpen
|
||||
}
|
||||
|
||||
function onMouseLeave(mouseEvent: MouseEvent) {
|
||||
@@ -24,54 +26,44 @@
|
||||
const up = offsetY <= 0
|
||||
|
||||
const content = document.getElementsByClassName('content')
|
||||
const overlayBottom = content.item(0)?.classList.contains('bottom-right')
|
||||
const overlayBottom = content.item(0)?.getAttribute('data-popper-placement') === 'bottom'
|
||||
|
||||
if ((down && overlayBottom) || (up && !overlayBottom)) {
|
||||
return
|
||||
}
|
||||
isOpen = !isOpen
|
||||
toggle()
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if !disabled}
|
||||
<Overlay
|
||||
onWindowKeyDown={handleWindowKeyDown}
|
||||
closeOnClickOutside
|
||||
closeOnScroll
|
||||
bind:isOpen
|
||||
class="w-full"
|
||||
style="z-index:unset"
|
||||
>
|
||||
<div
|
||||
slot="parent"
|
||||
let:toggle
|
||||
on:mousemove={() => !isOpen && toggle()}
|
||||
on:mouseleave={onMouseLeave}
|
||||
>
|
||||
<div class="w-full">
|
||||
<div use:popperRef on:mousemove={() => !isOpen && toggle()} on:mouseleave={onMouseLeave}>
|
||||
<slot />
|
||||
</div>
|
||||
|
||||
<div slot="content" class="content" let:toggle on:mouseleave={toggle} let:close>
|
||||
{#if Boolean(previousSchema)}
|
||||
{#if isOpen}
|
||||
<div class="content" use:popperContent on:mouseleave={toggle}>
|
||||
<PropPicker
|
||||
props={previousSchema}
|
||||
bind:pickableProperties
|
||||
on:select={(event) => {
|
||||
isOpen = false
|
||||
dispatch('select', event.detail)
|
||||
}}
|
||||
/>
|
||||
{:else}
|
||||
<WarningMessage {close} />
|
||||
{/if}
|
||||
</div>
|
||||
</Overlay>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{:else}
|
||||
<slot />
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.content {
|
||||
@apply w-full;
|
||||
@apply drop-shadow-xl;
|
||||
@apply w-full;
|
||||
@apply max-w-4xl;
|
||||
|
||||
@apply px-6;
|
||||
@apply z-50;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,28 +1,26 @@
|
||||
<script lang="ts">
|
||||
import { derived, writable } from 'svelte/store'
|
||||
import ObjectViewer from './ObjectViewer.svelte'
|
||||
import { keepByKey } from './utils'
|
||||
|
||||
export let props: Object = {}
|
||||
export let pickableProperties: Object = {}
|
||||
|
||||
const EMPTY_STRING = ''
|
||||
const search = writable(EMPTY_STRING)
|
||||
let search = ''
|
||||
|
||||
$: propsFiltered = derived(search, ($search: string) => {
|
||||
if ($search === EMPTY_STRING) {
|
||||
return props
|
||||
}
|
||||
return keepByKey(props, $search)
|
||||
})
|
||||
$: propsFiltered =
|
||||
search === EMPTY_STRING ? pickableProperties : keepByKey(pickableProperties, search)
|
||||
</script>
|
||||
|
||||
<div class="relative">
|
||||
<div class="p-3 bg-white rounded-lg border border-gray-200 shadow-md">
|
||||
<!-- {JSON.stringify(propsFiltered)} -->
|
||||
<div
|
||||
class="relative p-3 bg-white rounded-lg border border-gray-200 shadow-md max-h-max overflow-y-scroll w-full"
|
||||
>
|
||||
<div class="overflow-y-auto max-h-96">
|
||||
<input
|
||||
bind:value={$search}
|
||||
bind:value={search}
|
||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg block w-full p-2 mb-2"
|
||||
placeholder="Search prop..."
|
||||
/>
|
||||
<ObjectViewer json={$propsFiltered} on:select />
|
||||
<ObjectViewer json={propsFiltered} on:select />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,44 +1,16 @@
|
||||
<script lang="ts">
|
||||
export let close: () => void
|
||||
</script>
|
||||
|
||||
<div class="relative">
|
||||
<div class="p-3 bg-white rounded-lg border border-gray-200 shadow-md">
|
||||
<div id="alert-4" class="flex p-4 bg-yellow-100 rounded-lg dark:bg-yellow-200" role="alert">
|
||||
<svg
|
||||
class="flex-shrink-0 w-5 h-5 text-yellow-700 dark:text-yellow-800"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 20 20"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
><path
|
||||
fill-rule="evenodd"
|
||||
d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z"
|
||||
clip-rule="evenodd"
|
||||
/></svg
|
||||
>
|
||||
<div class="ml-3 text-sm font-medium text-yellow-700 dark:text-yellow-800">
|
||||
The property picker is not available unless a preview is run on the previous step.
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
class="ml-auto -mx-1.5 -my-1.5 bg-yellow-100 text-yellow-500 rounded-lg focus:ring-2 focus:ring-yellow-400 p-1.5 hover:bg-yellow-200 inline-flex h-8 w-8 dark:bg-yellow-200 dark:text-yellow-600 dark:hover:bg-yellow-300"
|
||||
data-dismiss-target="#alert-4"
|
||||
aria-label="Close"
|
||||
on:click={close}
|
||||
>
|
||||
<span class="sr-only">Close</span>
|
||||
<svg
|
||||
class="w-5 h-5"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 20 20"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
><path
|
||||
fill-rule="evenodd"
|
||||
d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z"
|
||||
clip-rule="evenodd"
|
||||
/></svg
|
||||
>
|
||||
</button>
|
||||
</div>
|
||||
<div class="flex p-4 bg-yellow-100 rounded-lg dark:bg-yellow-200" role="alert">
|
||||
<svg
|
||||
class="flex-shrink-0 w-5 h-5 text-yellow-700 dark:text-yellow-800"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 20 20"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
><path
|
||||
fill-rule="evenodd"
|
||||
d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z"
|
||||
clip-rule="evenodd"
|
||||
/></svg
|
||||
>
|
||||
<div class="ml-3 text-sm font-medium text-yellow-700 dark:text-yellow-800">
|
||||
Previous results are not available unless a preview is run on the previous step.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -58,7 +58,7 @@ export async function main() {
|
||||
// return newState - state
|
||||
|
||||
// You may refer to each row/value returned by the trigger script using
|
||||
// previous_result._value
|
||||
// flow_input._value
|
||||
return [1,2,3]
|
||||
}
|
||||
`
|
||||
|
||||
@@ -58,10 +58,3 @@ export function clearPreviewResults() {
|
||||
previewResults.set({})
|
||||
}
|
||||
|
||||
export function addPreviewResult(res: Object, index: number) {
|
||||
previewResults.update((rec) => {
|
||||
rec[index] = res
|
||||
|
||||
return rec
|
||||
})
|
||||
}
|
||||
|
||||
@@ -339,7 +339,7 @@ export function schemaToTsType(schema: Schema): string {
|
||||
return `{ ${types} }`
|
||||
}
|
||||
|
||||
export function schemaToObject(schema: Schema): Object {
|
||||
export function schemaToObject(schema: Schema, args: Record<string, any>): Object {
|
||||
const object = {}
|
||||
|
||||
if (!schema) {
|
||||
@@ -348,8 +348,7 @@ export function schemaToObject(schema: Schema): Object {
|
||||
const propKeys = Object.keys(schema.properties)
|
||||
|
||||
propKeys.forEach((key: string) => {
|
||||
const prop = schema.properties[key]
|
||||
object[key] = prop.type
|
||||
object[key] = args[key] ?? null
|
||||
})
|
||||
|
||||
return object
|
||||
|
||||
Reference in New Issue
Block a user