Flow: Input connect (#325)

* Fix EditorBar in the script editor

* Rework ArgInput

* Add a button to link a property

* Adapt style

* Clean up

* Clean up Toggle

* Clean up Toggleclear

* Fix editor

* Fix login test

* Fix login test

* Done

* Fix toggling issues
This commit is contained in:
Faton Ramadani
2022-08-03 19:51:58 +02:00
committed by GitHub
parent 6c71fac136
commit 72a6004380
20 changed files with 453 additions and 314 deletions
+43 -27
View File
@@ -1,23 +1,30 @@
<script lang="ts">
import Tooltip from './Tooltip.svelte'
import { slide } from 'svelte/transition'
import { faChevronDown, faChevronUp, faMinus, faPlus } from '@fortawesome/free-solid-svg-icons'
import {
faArrowRotateLeft,
faChevronDown,
faChevronUp,
faMinus,
faPlus
} from '@fortawesome/free-solid-svg-icons'
import StringTypeNarrowing from './StringTypeNarrowing.svelte'
import Icon from 'svelte-awesome'
import ResourcePicker from './ResourcePicker.svelte'
import ObjectTypeNarrowing from './ObjectTypeNarrowing.svelte'
import ObjectResourceInput from './ObjectResourceInput.svelte'
import FieldHeader from './FieldHeader.svelte'
import { createEventDispatcher } from 'svelte'
import { setInputCat as computeInputCat, type InputCat } from '$lib/utils'
import { Button, Tooltip } from 'flowbite-svelte'
import { createEventDispatcher } from 'svelte'
import Icon from 'svelte-awesome'
import Editor from './Editor.svelte'
import FieldHeader from './FieldHeader.svelte'
import ObjectResourceInput from './ObjectResourceInput.svelte'
import ObjectTypeNarrowing from './ObjectTypeNarrowing.svelte'
import ResourcePicker from './ResourcePicker.svelte'
import StringTypeNarrowing from './StringTypeNarrowing.svelte'
export let label: string = ''
export let value: any
export let defaultValue: any = undefined
export let description: string = ''
export let format: string = ''
export let contentEncoding: 'base64' | 'binary' | undefined = undefined
@@ -141,17 +148,15 @@
on:click={() => {
seeEditable = !seeEditable
}}
>Customize argument<Icon
class="ml-2"
data={seeEditable ? faChevronUp : faChevronDown}
scale={0.7}
/></span
>
Customize argument
<Icon class="ml-2" data={seeEditable ? faChevronUp : faChevronDown} scale={0.7} />
</span>
{#if seeEditable}
<div transition:slide class="mt-2">
<label class="text-gray-700"
>Description
<label class="text-gray-700">
Description
<textarea rows="1" bind:value={description} placeholder="Edit description" />
{#if type == 'string' && !contentEncoding && format != 'date-time'}
<StringTypeNarrowing bind:format bind:pattern bind:enum_ bind:contentEncoding />
@@ -182,7 +187,7 @@
{error === '' ? '...' : error}
</div>
</div>
<div class="flex flex-row gap-1">
<div class="flex space-x-1">
{#if inputCat == 'number' && !numberAsString}
<input
{disabled}
@@ -230,8 +235,10 @@
if (value.length == 0) {
value = undefined
}
}}><Icon data={faMinus} class="mb-1" /></button
}}
>
<Icon data={faMinus} class="mb-1" />
</button>
</div>
{/each}
</div>
@@ -242,9 +249,14 @@
value = []
}
value = value.concat('')
}}>Add item &nbsp;<Icon data={faPlus} class="mb-1" /></button
><span class="ml-2">{(value ?? []).length} item{(value ?? []).length > 1 ? 's' : ''}</span
}}
>
<Icon data={faPlus} class="mr-2" />
Add item
</button>
<span class="ml-2">
{(value ?? []).length} item{(value ?? []).length > 1 ? 's' : ''}
</span>
</div>
{:else if inputCat == 'resource-object'}
<ObjectResourceInput {format} bind:value />
@@ -306,15 +318,19 @@
/>
{/if}
{#if !required && inputCat != 'resource-object'}
<div class="flex flex-row-reverse">
<button
{disabled}
class="default-button-secondary items-center leading-4 py-0 my-px px-1 float-right"
<Tooltip placement="bottom" content="Reset to default value">
<Button
on:click={() => (value = undefined)}
>Reset&nbsp;<Tooltip>Reset to default value</Tooltip></button
{disabled}
color="alternative"
size="sm"
class="h-8"
>
</div>
<Icon data={faArrowRotateLeft} />
</Button>
</Tooltip>
{/if}
<slot name="actions" />
</div>
</div>
</div>
@@ -132,14 +132,14 @@
<div class="justify-between flex flex-row w-full">
<Breadcrumb>
<BreadcrumbItem>
<button on:click={() => changeStep(1)} class={step === 1 ? 'font-bold' : null}
>Flow Editor</button
>
<button on:click={() => changeStep(1)} class={step === 1 ? 'font-bold' : null}>
Flow Editor
</button>
</BreadcrumbItem>
<BreadcrumbItem>
<button on:click={() => changeStep(2)} class={step === 2 ? 'font-bold' : null}
>UI customisation</button
>
<button on:click={() => changeStep(2)} class={step === 2 ? 'font-bold' : null}>
UI customisation
</button>
</BreadcrumbItem>
</Breadcrumb>
<div class="flex flex-row-reverse ml-2">
@@ -0,0 +1,225 @@
<script lang="ts">
import type { Schema } from '$lib/common'
import type { InputTransform } from '$lib/gen'
import type { InputCat } from '$lib/utils'
import { faChain } from '@fortawesome/free-solid-svg-icons'
import { Button, Tooltip } from 'flowbite-svelte'
import Icon from 'svelte-awesome'
import ArgInput from './ArgInput.svelte'
import Editor from './Editor.svelte'
import FieldHeader from './FieldHeader.svelte'
import DynamicInputHelpBox from './flows/DynamicInputHelpBox.svelte'
import { codeToStaticTemplate } from './flows/flowStore'
import { getCodeInjectionExpr, getDefaultExpr, isCodeInjection } from './flows/utils'
import ClickablePropertyPicker from './propertyPicker/ClickablePropertyPicker.svelte'
import OverlayPropertyPicker from './propertyPicker/OverlayPropertyPicker.svelte'
import Toggle from './Toggle.svelte'
export let schema: Schema
export let arg: InputTransform | any
export let argName: string
export let extraLib: string = 'missing extraLib'
export let inputCheck: { [id: string]: boolean }
export let i: number | undefined = undefined
export let pickableProperties: Object | undefined = undefined
let overlays: { [id: string]: OverlayPropertyPicker } = {}
let monacos: { [id: string]: Editor } = {}
let inputCats: { [id: string]: InputCat } = {}
let propertyType = getPropertyType(arg)
function getPropertyType(arg: InputTransform | any): 'static' | 'javascript' {
let type: 'static' | 'javascript' = arg.type
if (type == 'javascript') {
if (codeToStaticTemplate(arg.expr)) {
type = 'static'
}
}
return type
}
function setPropertyType(id: string, rawValue: string, isRaw: boolean) {
if (!arg) {
return
}
if (isCodeInjection(rawValue)) {
arg.expr = getCodeInjectionExpr(rawValue, isRaw)
arg.type = 'javascript'
propertyType = 'static'
} else {
if (arg.type === 'javascript' && propertyType === 'static') {
arg.type = 'static'
if (inputCats[id] == 'number') {
arg.value = Number(arg.value)
}
}
if (arg.type) {
propertyType = arg.type
}
}
}
function isStaticTemplate(inputCat: InputCat) {
return inputCat === 'string' || inputCat === 'number' || inputCat === 'sql'
}
function focusProp(argName: string) {
Object.keys(overlays).forEach((k) => {
if (k == argName) {
overlays[k].focus()
} else {
overlays[k].unfocus()
}
})
}
function onPropertyLink(argName: string, rawValue: string) {
if (isStaticTemplate(inputCats[argName])) {
arg.value = `\$\{${rawValue}}`
setPropertyType(argName, arg.value, false)
} else {
arg.expr = getDefaultExpr(i ?? -1, undefined, rawValue)
arg.type = 'javascript'
propertyType = 'javascript'
}
if (monacos[argName]) {
monacos[argName].setCode(arg.value)
}
}
$: checked = propertyType == 'javascript'
</script>
{#if arg != undefined}
<div class="flex justify-between items-center">
<div class="flex items-center">
<FieldHeader
label={argName}
format={schema.properties[argName].format}
contentEncoding={schema.properties[argName].contentEncoding}
required={schema.required.includes(argName)}
type={schema.properties[argName].type}
itemsType={schema.properties[argName].items}
/>
{#if !checked && arg.type === 'javascript'}
<span class="bg-blue-100 text-blue-800 text-sm font-medium mr-2 px-2.5 py-0.5 rounded ml-2">
{'${...}'}
</span>
{/if}
</div>
<Toggle
bind:checked
options={{
right: 'Raw Javascript Editor'
}}
on:change={(e) => {
const type = e.detail ? 'javascript' : 'static'
const staticTemplate = isStaticTemplate(inputCats[argName])
if (type === 'javascript') {
arg.expr = getDefaultExpr(
i ?? -1,
argName,
staticTemplate ? `\`${arg.value ?? ''}\`` : undefined
)
arg.value = undefined
propertyType = 'javascript'
} else {
arg.value = staticTemplate ? codeToStaticTemplate(arg.expr) : undefined
arg.expr = undefined
propertyType = 'static'
}
arg.type = type
}}
/>
</div>
<div class="max-w-xs" />
{#if propertyType === undefined || !checked}
<OverlayPropertyPicker
bind:this={overlays[argName]}
{pickableProperties}
disabled={!isStaticTemplate(inputCats[argName])}
on:select={(event) => {
const toAppend = `\$\{${event.detail}}`
arg.value = `${arg.value ?? ''}${toAppend}`
if (monacos[argName]) {
monacos[argName].setCode(arg.value)
}
setPropertyType(argName, arg.value, false)
}}
>
<ArgInput
on:focus={() => focusProp(argName)}
label={argName}
bind:editor={monacos[argName]}
bind:description={schema.properties[argName].description}
bind:value={arg.value}
type={schema.properties[argName].type}
required={schema.required.includes(argName)}
bind:pattern={schema.properties[argName].pattern}
bind:valid={inputCheck[argName]}
defaultValue={schema.properties[argName].default}
bind:enum_={schema.properties[argName].enum}
bind:format={schema.properties[argName].format}
contentEncoding={schema.properties[argName].contentEncoding}
bind:itemsType={schema.properties[argName].items}
displayHeader={false}
bind:inputCat={inputCats[argName]}
numberAsString={true}
on:input={(e) => {
if (isStaticTemplate(inputCats[argName])) {
setPropertyType(argName, e.detail.rawValue, e.detail.isRaw)
}
}}
>
<div slot="actions">
<ClickablePropertyPicker
bind:pickableProperties
on:select={(event) => onPropertyLink(argName, event.detail)}
>
<Tooltip placement="bottom" content="Input connect">
<Button color="blue" size="sm" class="h-8">
<Icon data={faChain} />
</Button>
</Tooltip>
</ClickablePropertyPicker>
</div>
</ArgInput>
</OverlayPropertyPicker>
{:else if checked}
{#if arg.expr != undefined}
<OverlayPropertyPicker
bind:this={overlays[argName]}
{pickableProperties}
on:select={(event) => {
monacos[argName].insertAtCursor(event.detail)
}}
>
<div class="border rounded p-2 mt-2 border-gray-300">
<Editor
bind:this={monacos[argName]}
on:focus={() => focusProp(argName)}
bind:code={arg.expr}
lang="javascript"
class="few-lines-editor"
{extraLib}
extraLibPath="file:///node_modules/@types/windmill@{i}/index.d.ts"
/>
</div>
</OverlayPropertyPicker>
<DynamicInputHelpBox />
{/if}
{:else}
<p>Not recognized arg type {arg.type}</p>
{/if}
{:else}
<p>Arg at {argName} is undefined</p>
{/if}
@@ -1,5 +1,5 @@
<script lang="ts">
import { type Resource, ResourceService } from '$lib/gen'
import { ResourceService, type Resource } from '$lib/gen'
import { workspaceStore } from '$lib/stores'
import { createEventDispatcher } from 'svelte'
import ResourceEditor from './ResourceEditor.svelte'
@@ -27,7 +27,7 @@
</script>
<ResourceEditor bind:this={resourceEditor} on:refresh={() => loadResources(resourceType)} />
<select class="mt-1" bind:value placeholder="Pick a resource {resourceType}">
<select bind:value placeholder="Pick a resource {resourceType}">
<option value={undefined} />
{#each resources as r}
<option value={r.path}>{r.path}{r.description ? ' | ' + r.description : ''}</option>
+12 -13
View File
@@ -1,14 +1,14 @@
<script lang="ts">
import { page } from '$app/stores'
import type { Script, Flow } from '$lib/gen'
import type { Flow, Script } from '$lib/gen'
import { decodeState, getToday } from '$lib/utils'
import { slide } from 'svelte/transition'
import { faChevronDown, faChevronUp } from '@fortawesome/free-solid-svg-icons'
import Icon from 'svelte-awesome'
import Tooltip from './Tooltip.svelte'
import SvelteMarkdown from 'svelte-markdown'
import SchemaForm from './SchemaForm.svelte'
import Tooltip from './Tooltip.svelte'
export let runnable: Script | Flow | undefined
export let runAction: (scheduledForStr: string | undefined, args: Record<string, any>) => void
@@ -60,17 +60,16 @@
{#if runnable?.schema}
{#if detailed}
<h2>
Arguments <Tooltip
>The optional fields, if left blank, will use the placeholder value as default.</Tooltip
>
Arguments
<Tooltip>
The optional fields, if left blank, will use the placeholder value as default.
</Tooltip>
</h2>
{/if}
{#if !runnable.schema.properties || Object.keys(runnable.schema.properties).length === 0}
<div class="text-sm p-4">No arguments</div>
{:else}
<div
class="bg-gray-50 border border shadow-md shadow-blue-100 shadow-inner rounded border-gray-300 p-6"
>
<div class="bg-gray-50 border shadow-blue-100 shadow-inner rounded border-gray-300 p-6">
<SchemaForm schema={runnable.schema} bind:isValid bind:args />
</div>
{/if}
@@ -97,8 +96,10 @@
class="default-button-secondary mx-2 mb-1"
on:click={() => {
scheduledForStr = undefined
}}>clear</button
}}
>
Clear
</button>
</div>
</div>
</div>
@@ -113,10 +114,8 @@
>
{#if schedulable}
<div>
schedule to run later <Icon
data={viewOptions ? faChevronUp : faChevronDown}
scale={0.5}
/>
Schedule to run later
<Icon data={viewOptions ? faChevronUp : faChevronDown} scale={0.5} />
</div>
{/if}
</button>
@@ -2,10 +2,11 @@
import type { Schema, SchemaProperty } from '$lib/common'
import { emptySchema, sendUserToast } from '$lib/utils'
import { faPen, faPlus, faTrash } from '@fortawesome/free-solid-svg-icons'
import { Badge, Button } from 'flowbite-svelte'
import { Button } from 'flowbite-svelte'
import { createEventDispatcher } from 'svelte'
import Icon from 'svelte-awesome'
import Editor from './Editor.svelte'
import SchemaEditorProperty from './SchemaEditorProperty.svelte'
import type { ModalSchemaProperty } from './SchemaModal.svelte'
import SchemaModal, { DEFAULT_PROPERTY, schemaToModal } from './SchemaModal.svelte'
import TableCustom from './TableCustom.svelte'
@@ -153,8 +154,7 @@
<Toggle
on:change={() => switchTab()}
options={{
left: { label: '', value: false },
right: { label: 'Json Schema Editor', value: true }
right: 'Json Schema Editor'
}}
/>
<div class="ml-2">
@@ -185,11 +185,7 @@
<tr>
<td class="font-bold">{name}</td>
<td>
{#if !property.type}
<Badge color="red">ANY</Badge>
{:else}
<Badge color="blue">{property.type.toLocaleUpperCase()}</Badge>
{/if}
<SchemaEditorProperty {property} />
</td>
<td>{property.description}</td>
<td>{JSON.stringify(property.default) ?? ''}</td>
@@ -0,0 +1,14 @@
<script lang="ts">
import type { SchemaProperty } from '$lib/common'
import { Badge } from 'flowbite-svelte'
export let property: SchemaProperty
</script>
<Badge color="blue">{property.type?.toUpperCase() ?? 'ANY'}</Badge>
{#if property.format}
<Badge color="green">{property.format?.toUpperCase()}</Badge>
{/if}
{#if property.contentEncoding}
<Badge color="indigo">{property.contentEncoding?.toUpperCase()}</Badge>
{/if}
+14 -184
View File
@@ -1,206 +1,36 @@
<script lang="ts">
import type { Schema } from '$lib/common'
import type { InputTransform } from '$lib/gen'
import { logout } from '$lib/logout'
import { allTrue, type InputCat } from '$lib/utils'
import { allTrue } from '$lib/utils'
import ArgInput from './ArgInput.svelte'
import Editor from './Editor.svelte'
import FieldHeader from './FieldHeader.svelte'
import DynamicInputHelpBox from './flows/DynamicInputHelpBox.svelte'
import { codeToStaticTemplate } from './flows/flowStore'
import { getCodeInjectionExpr, getDefaultExpr, isCodeInjection } from './flows/utils'
import OverlayPropertyPicker from './propertyPicker/OverlayPropertyPicker.svelte'
import Toggle from './Toggle.svelte'
import InputTransformForm from './InputTransformForm.svelte'
export let inputTransform = false
export let schema: Schema
export let args: Record<string, InputTransform | any> = {}
export let editableSchema = false
export let extraLib: string = 'missing extraLib'
export let isValid: boolean = true
export let i: number | undefined = undefined
export let pickableProperties: Object | undefined = undefined
export let extraLib: string = 'missing extraLib'
export let i: number | undefined = undefined
let inputCheck: { [id: string]: boolean } = {}
let overlays: { [id: string]: OverlayPropertyPicker } = {}
let monacos: { [id: string]: Editor } = {}
$: isValid = allTrue(inputCheck) ?? false
let propertiesTypes: { [id: string]: 'javascript' | 'static' } = inputTransform
? Object.keys(args).reduce((acc, key) => {
let type = args[key].type
if (type == 'javascript') {
if (codeToStaticTemplate(args[key].expr)) {
type = 'static'
}
}
acc[key] = type
return acc
}, {})
: {}
let inputCats: { [id: string]: InputCat } = {}
function setPropertyType(id: string, rawValue: string, isRaw: boolean) {
const arg = args[id]
if (!arg) {
return
}
if (isCodeInjection(rawValue)) {
args[id].expr = getCodeInjectionExpr(rawValue, isRaw)
args[id].type = 'javascript'
propertiesTypes[id] = 'static'
} else {
if (args[id].type === 'javascript' && propertiesTypes[id] === 'static') {
args[id].type = 'static'
if (inputCats[id] == 'number') {
args[id].value = Number(args[id].value)
}
}
if (arg.type) {
propertiesTypes[id] = arg.type
}
}
}
function isStaticTemplate(inputCat: InputCat) {
return inputCat === 'string' || inputCat === 'number' || inputCat === 'sql'
}
function focusProp(argName: string) {
Object.keys(overlays).forEach((k) => {
if (k == argName) {
overlays[k].focus()
} else {
overlays[k].unfocus()
}
})
}
</script>
<div class="w-full">
{#if Object.keys(schema?.properties ?? {}).length > 0}
{#each Object.keys(schema?.properties ?? {}) as argName, index}
{#each Object.keys(schema?.properties ?? {}) as argName}
{#if inputTransform}
{#if args[argName] != undefined}
<div class={index > 0 ? 'mt-8' : ''} />
<div class="flex justify-between items-center">
<div class="flex items-center">
<FieldHeader
label={argName}
format={schema.properties[argName].format}
contentEncoding={schema.properties[argName].contentEncoding}
required={schema.required.includes(argName)}
type={schema.properties[argName].type}
itemsType={schema.properties[argName].items}
/>
{#if propertiesTypes[argName] === 'static' && args[argName].type === 'javascript'}
<span
class="bg-blue-100 text-blue-800 text-sm font-medium mr-2 px-2.5 py-0.5 rounded ml-2"
>
{'${...}'}
</span>
{/if}
</div>
<Toggle
options={{
left: { label: '', value: 'static' },
right: { label: 'Raw Javascript Editor', value: 'javascript' }
}}
bind:value={propertiesTypes[argName]}
on:change={(e) => {
const staticTemplate = isStaticTemplate(inputCats[argName])
if (e.detail === 'javascript') {
args[argName].expr = getDefaultExpr(
i ?? -1,
argName,
staticTemplate ? args[argName].value : undefined
)
args[argName].value = undefined
} else {
args[argName].value = staticTemplate
? codeToStaticTemplate(args[argName].expr)
: undefined
args[argName].expr = undefined
}
args[argName].type = e.detail
}}
/>
</div>
<div class="max-w-xs" />
{#if propertiesTypes[argName] === undefined || propertiesTypes[argName] === 'static'}
<OverlayPropertyPicker
bind:this={overlays[argName]}
{pickableProperties}
disabled={!isStaticTemplate(inputCats[argName])}
on:select={(event) => {
const toAppend = `\$\{${event.detail}}`
args[argName].value = `${args[argName].value ?? ''}${toAppend}`
if (monacos[argName]) {
monacos[argName].setCode(args[argName].value)
}
setPropertyType(argName, args[argName].value, false)
}}
>
<ArgInput
on:focus={() => focusProp(argName)}
label={argName}
bind:editor={monacos[argName]}
bind:description={schema.properties[argName].description}
bind:value={args[argName].value}
type={schema.properties[argName].type}
required={schema.required.includes(argName)}
bind:pattern={schema.properties[argName].pattern}
bind:valid={inputCheck[argName]}
defaultValue={schema.properties[argName].default}
bind:enum_={schema.properties[argName].enum}
bind:format={schema.properties[argName].format}
contentEncoding={schema.properties[argName].contentEncoding}
bind:itemsType={schema.properties[argName].items}
displayHeader={false}
bind:inputCat={inputCats[argName]}
numberAsString={true}
on:input={(e) => {
if (isStaticTemplate(inputCats[argName])) {
setPropertyType(argName, e.detail.rawValue, e.detail.isRaw)
}
}}
/>
</OverlayPropertyPicker>
{:else if propertiesTypes[argName] === 'javascript'}
{#if args[argName].expr != undefined}
<OverlayPropertyPicker
bind:this={overlays[argName]}
{pickableProperties}
on:select={(event) => {
monacos[argName].insertAtCursor(event.detail)
}}
>
<div class="border rounded p-2 mt-2 border-gray-300">
<Editor
bind:this={monacos[argName]}
on:focus={() => focusProp(argName)}
bind:code={args[argName].expr}
lang="javascript"
class="few-lines-editor"
{extraLib}
extraLibPath="file:///node_modules/@types/windmill@{i}/index.d.ts"
/>
</div>
</OverlayPropertyPicker>
<DynamicInputHelpBox />
{/if}
{:else}
<p>Not recognized arg type {args[argName].type}</p>
{/if}
{:else}
<p>Arg at {argName} is undefined</p>
{/if}
<InputTransformForm
bind:arg={args[argName]}
bind:schema
bind:argName
bind:inputCheck
bind:pickableProperties
bind:extraLib
bind:i
/>
{:else}
<ArgInput
label={argName}
@@ -433,9 +433,9 @@
<tbody slot="body">
{#each pastPreviews as { id, created_at, success, result }}
<tr class="">
<td class="text-xs"
><a class="pr-3" href="/run/{id}" target="_blank">{id.substring(30)}</a></td
>
<td class="text-xs">
<a class="pr-3" href="/run/{id}" target="_blank">{id.substring(30)}</a>
</td>
<td class="text-xs">{displayDate(created_at)}</td>
<td class="text-xs">
{#if success}
@@ -452,9 +452,10 @@
modalViewerContent = result
modalViewerMode = 'result'
modalViewer.openModal()
}}>{JSON.stringify(result).substring(0, 30)}...</a
></td
>
}}
>{JSON.stringify(result).substring(0, 30)}...
</a>
</td>
<td class="text-xs"
><a
href="#code"
@@ -12,7 +12,7 @@
</script>
<div class="mt-8 flex flex-col {$$props.class}">
<div class="inline-block min-w-full py-2 align-middle md:px-6 lg:px-8">
<div class="inline-block min-w-full align-middle">
<table class="min-w-full divide-y divide-gray-300 table-auto">
<thead>
<tr class={twTextSize}>
+11 -15
View File
@@ -1,23 +1,20 @@
<script lang="ts">
import { createEventDispatcher } from 'svelte'
export let value: any = undefined
export let options:
| {
left: { label?: string; value: any }
right: { label?: string; value: any }
}
| undefined = undefined
export let checked = options?.right.value === value
export let options: {
left?: string
right?: string
} = {}
export let checked: boolean = false
const id = (Math.random() + 1).toString(36).substring(10)
const dispatch = createEventDispatcher()
</script>
<span>
<label for={id} class="inline-flex items-center cursor-pointer mt-2">
{#if Boolean(options?.left.label)}
<span class="mr-2 text-sm font-medium text-gray-900">{options?.left.label}</span>
{#if Boolean(options?.left)}
<span class="mr-2 text-sm font-medium text-gray-900">{options?.left}</span>
{/if}
<div class="relative">
<input
@@ -27,16 +24,15 @@
class="sr-only peer"
bind:checked
on:change={() => {
value = options ? (checked ? options?.right.value : options?.left.value) : checked
dispatch('change', value ?? checked)
dispatch('change', checked)
}}
/>
<div
class="w-11 h-6 bg-gray-200 rounded-full peer peer-focus:ring-4 peer-focus:ring-blue-300 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-0.5 after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-blue-600"
/>
</div>
{#if Boolean(options?.right.label)}
<span class="ml-2 text-sm font-medium text-gray-900">{options?.right.label}</span>
{#if Boolean(options?.right)}
<span class="ml-2 text-sm font-medium text-gray-900">{options?.right}</span>
{/if}
</label>
</span>
@@ -165,8 +165,8 @@
<Toggle
bind:checked={scheduleEnabled}
options={{
left: { label: 'disabled', value: false },
right: { label: 'enabled', value: true }
left: 'disabled',
right: 'enabled'
}}
/>
<div class="p-2 mt-2 rounded" class:bg-gray-300={!scheduleEnabled}>
@@ -55,6 +55,7 @@ export function codeToStaticTemplate(code?: string): string | undefined {
}
return undefined
}
export function flattenForloopFlows(flow: Flow): Flow {
let newFlow: Flow = JSON.parse(JSON.stringify(flow))
const mod = newFlow.value.modules[1]?.value
+15 -15
View File
@@ -1,5 +1,5 @@
import type { Schema } from '$lib/common'
import type { FlowModuleValue, InputTransform, Flow, FlowModule, RawScript } from '$lib/gen'
import type { Flow, FlowModule, FlowModuleValue, InputTransform, RawScript } from '$lib/gen'
import { inferArgs } from '$lib/infer'
import { loadSchema } from '$lib/scripts'
import { emptySchema, getScriptByPath, schemaToObject } from '$lib/utils'
@@ -22,7 +22,6 @@ export function flowToMode(flow: Flow | any, mode: FlowMode): Flow {
})
if (mode == 'pull') {
const triggerModule = newFlow.value.modules[0]
const oldModules = newFlow.value.modules.slice(1)
@@ -49,7 +48,6 @@ export function flowToMode(flow: Flow | any, mode: FlowMode): Flow {
return newFlow
}
export function getTypeAsString(arg: any): string {
if (arg === null) {
return 'null'
@@ -60,7 +58,6 @@ export function getTypeAsString(arg: any): string {
return typeof arg
}
export async function getFirstStepSchema(flow: Flow): Promise<Schema> {
const [firstModule] = flow.value.modules
if (firstModule.value.type === 'rawscript') {
@@ -116,8 +113,7 @@ export async function loadSchemaFromModule(module: FlowModule): Promise<{
let input_transform = module.input_transform
if (
JSON.stringify(keys.sort()) !==
JSON.stringify(Object.keys(module.input_transform).sort())
JSON.stringify(keys.sort()) !== JSON.stringify(Object.keys(module.input_transform).sort())
) {
input_transform = keys.reduce((accu, key) => {
let nv = module.input_transform[key] ?? {
@@ -128,6 +124,7 @@ export async function loadSchemaFromModule(module: FlowModule): Promise<{
return accu
}, {})
}
return {
input_transform: input_transform,
schema: schema ?? emptySchema()
@@ -162,11 +159,12 @@ export function getCodeInjectionExpr(code: string, isRaw: boolean): string {
expr = `JSON.parse(${expr})`
}
return `import { previous_result, flow_input, step, variable, resource, params } from 'windmill'
${expr}`
}
export function getDefaultExpr(i: number, key: string = 'myfield', previousExpr?: string) {
const expr = previousExpr ? `\`${previousExpr}\`` : `previous_result.${key}`
const expr = previousExpr ?? `previous_result.${key}`
return `import { previous_result, flow_input, step, variable, resource, params } from 'windmill@${i}'
${expr}`
@@ -182,16 +180,18 @@ export function getPickableProperties(
const flowInputAsObject = schemaToObject(schema, args)
const flowInput =
mode === 'pull' && i >= 1
? Object.assign(Object.assign(
{
_value: 'The current value of the iteration as an object',
_index: 'The current index of the iteration as a number'
},
flowInputAsObject), previewResults[0])
? Object.assign(
Object.assign(
{
_value: 'The current value of the iteration as an object',
_index: 'The current index of the iteration as a number'
},
flowInputAsObject
),
previewResults[0]
)
: flowInputAsObject
let previous_result
if (i === 0 || (i == 1 && mode == 'pull')) {
previous_result = flowInput
@@ -0,0 +1,52 @@
<script lang="ts">
import { createEventDispatcher } from 'svelte'
import { createPopperActions } from 'svelte-popperjs'
import PropPicker from './PropPicker.svelte'
const [popperRef, popperContent] = createPopperActions({
placement: 'bottom-end',
strategy: 'fixed'
})
export let pickableProperties: Object | undefined
export let disabled = false
let isOpen = false
function toggle() {
isOpen = !isOpen
}
const dispatch = createEventDispatcher()
</script>
{#if !disabled}
<div class="w-full">
<div use:popperRef on:click={toggle}>
<slot />
</div>
{#if isOpen}
<div class="content" use:popperContent>
<PropPicker
bind:pickableProperties
on:select={(event) => {
isOpen = false
dispatch('select', event.detail)
}}
/>
</div>
{/if}
</div>
{:else}
<slot />
{/if}
<style>
.content {
@apply drop-shadow-xl;
@apply w-full;
@apply max-w-4xl;
@apply px-6;
@apply z-50;
}
</style>
@@ -39,7 +39,7 @@
{#if !disabled}
<div class="w-full">
<div use:popperRef on:mouseenter={open} on:mouseleave={close}>
<div use:popperRef on:mouseleave={close}>
<slot />
</div>
+5 -2
View File
@@ -1,5 +1,5 @@
import type { Schema, SchemaProperty } from './common.js'
import { ScriptService, type MainArgSignature } from '$lib/gen'
import type { Schema, SchemaProperty } from './common.js'
export async function inferArgs(
language: 'python3' | 'deno',
@@ -38,7 +38,10 @@ export async function inferArgs(
}
}
function argSigToJsonSchemaType(t: string | { resource: string } | { list: string }, s: SchemaProperty): void {
function argSigToJsonSchemaType(
t: string | { resource: string } | { list: string },
s: SchemaProperty
): void {
if (t === 'int') {
s.type = 'integer'
} else if (t === 'float') {
+15 -13
View File
@@ -7,20 +7,21 @@
</script>
<script lang="ts">
import { canWrite } from '$lib/utils'
import { GroupService } from '$lib/gen'
import type { Group } from '$lib/gen'
import { GroupService } from '$lib/gen'
import { canWrite } from '$lib/utils'
import PageHeader from '$lib/components/PageHeader.svelte'
import TableCustom from '$lib/components/TableCustom.svelte'
import Dropdown from '$lib/components/Dropdown.svelte'
import ShareModal from '$lib/components/ShareModal.svelte'
import SharedBadge from '$lib/components/SharedBadge.svelte'
import { faEdit, faPlus, faShare } from '@fortawesome/free-solid-svg-icons'
import { userStore, workspaceStore } from '$lib/stores'
import CenteredPage from '$lib/components/CenteredPage.svelte'
import Icon from 'svelte-awesome'
import Dropdown from '$lib/components/Dropdown.svelte'
import GroupModal from '$lib/components/GroupModal.svelte'
import PageHeader from '$lib/components/PageHeader.svelte'
import SharedBadge from '$lib/components/SharedBadge.svelte'
import ShareModal from '$lib/components/ShareModal.svelte'
import TableCustom from '$lib/components/TableCustom.svelte'
import { userStore, workspaceStore } from '$lib/stores'
import { faEdit, faPlus, faShare } from '@fortawesome/free-solid-svg-icons'
import { Alert } from 'flowbite-svelte'
import Icon from 'svelte-awesome'
type GroupW = Group & { canWrite: boolean }
@@ -84,13 +85,14 @@
</div>
</PageHeader>
<div class="bg-blue-100 border-l-4 border-blue-600 text-blue-700 p-4 m-4" role="alert">
<p class="font-bold">Groups are a team or enterprise feature - Unlimited during beta</p>
<Alert accent rounded={false}>
<p class="font-bold text-lg">Groups are a team or enterprise feature - Unlimited during beta</p>
<p>
Groups are a team or enterprise feature and the feature might be significantly different after
beta in the community edition
</p>
</div>
</Alert>
<div class="relative">
<TableCustom>
<tr slot="header-row">
+8 -7
View File
@@ -7,12 +7,13 @@
</script>
<script lang="ts">
import { WorkerService, type WorkerPing } from '$lib/gen'
import { onDestroy, onMount } from 'svelte'
import { displayDate, elapsedSinceSecs, groupBy, sendUserToast } from '$lib/utils'
import CenteredPage from '$lib/components/CenteredPage.svelte'
import PageHeader from '$lib/components/PageHeader.svelte'
import TableCustom from '$lib/components/TableCustom.svelte'
import CenteredPage from '$lib/components/CenteredPage.svelte'
import { WorkerService, type WorkerPing } from '$lib/gen'
import { displayDate, elapsedSinceSecs, groupBy, sendUserToast } from '$lib/utils'
import { Badge } from 'flowbite-svelte'
import { onDestroy, onMount } from 'svelte'
let workers: WorkerPing[] = []
let filteredWorkers: WorkerPing[] = []
@@ -53,9 +54,9 @@
<p>No workers seems to be available</p>
{/if}
{#each groupedWorkers as [section, workers]}
<h2 class="m-5">
Instance: {section} | Ip:
<span class="text-gray-500 bg-gray-100 font-mono">{workers[0].ip}</span>
<h2 class="my-5">
Instance: {section} | IP:
<Badge large color="gray">{workers[0].ip}</Badge>
</h2>
<TableCustom>
+15 -12
View File
@@ -1,19 +1,22 @@
import { chromium, FullConfig } from '@playwright/test'
async function globalSetup(config: FullConfig) {
console.log('start global setup!')
const browser = await chromium.launch()
const page = await browser.newPage()
await page.goto('http://localhost:8000/user/login')
await page.locator('text=login without third-party').click()
await page.locator('#email').fill('admin@windmill.dev')
await page.locator('input[type="password"]').fill('changeme')
await page.locator('text="Login"').click()
await page.waitForResponse('http://localhost:8000/api/auth/login')
await page.context().storageState({ path: 'storageState.json' })
await browser.close()
console.log('end global setup!')
console.log('start global setup!')
const browser = await chromium.launch()
const page = await browser.newPage()
await page.goto('http://localhost:8000/user/login')
if (!(await page.locator('#email').isVisible())) {
page.locator('text=login without third-party').click()
}
await page.locator('#email').fill('admin@windmill.dev')
await page.locator('input[type="password"]').fill('changeme')
await page.locator('text="Login"').click()
await page.waitForResponse('http://localhost:8000/api/auth/login')
await page.context().storageState({ path: 'storageState.json' })
await browser.close()
console.log('end global setup!')
}
export default globalSetup