flow vscode extension improvements (#2536)

* flow dev

* vscode flow extension improvements
This commit is contained in:
Ruben Fiszel
2023-11-01 13:10:10 +01:00
committed by GitHub
parent 270d871039
commit d88096c84c
40 changed files with 305 additions and 195 deletions
+36 -26
View File
@@ -8,7 +8,8 @@
type PathScript,
ScriptService,
Script,
type HubScriptKind
type HubScriptKind,
type OpenFlow
} from '$lib/gen'
import { initHistory, push, redo, undo } from '$lib/history'
import {
@@ -61,10 +62,11 @@
import { ignoredTutorials } from './tutorials/ignoredTutorials'
export let initialPath: string = ''
export let newFlow: boolean
export let selectedId: string | undefined
export let initialArgs: Record<string, any> = {}
export let loading = false
export let flowStore: Writable<Flow>
export let flowStore: Writable<OpenFlow>
export let flowStateStore: Writable<FlowState>
const dispatch = createEventDispatcher()
@@ -100,13 +102,13 @@
$dirtyStore = false
localStorage.removeItem('flow')
localStorage.removeItem(`flow-${flow.path}`)
localStorage.removeItem(`flow-${$pathStore}`)
if (initialPath == '') {
if (newFlow) {
await FlowService.createFlow({
workspace: $workspaceStore!,
requestBody: {
path: flow.path,
path: $pathStore,
summary: flow.summary,
description: flow.description ?? '',
value: flow.value,
@@ -119,11 +121,15 @@
}
await DraftService.createDraft({
workspace: $workspaceStore!,
requestBody: { path: initialPath == '' ? flow.path : initialPath, typ: 'flow', value: flow }
requestBody: {
path: newFlow ? $pathStore : initialPath,
typ: 'flow',
value: flow
}
})
if (initialPath == '') {
if (newFlow) {
$dirtyStore = false
dispatch('saveInitial')
dispatch('saveInitial', $pathStore)
}
sendUserToast('Saved as draft')
} catch (error) {
@@ -149,13 +155,13 @@
// return
const { cron, timezone, args, enabled } = $scheduleStore
$dirtyStore = false
if (initialPath === '') {
if (newFlow) {
localStorage.removeItem('flow')
localStorage.removeItem(`flow-${flow.path}`)
localStorage.removeItem(`flow-${$pathStore}`)
await FlowService.createFlow({
workspace: $workspaceStore!,
requestBody: {
path: flow.path,
path: $pathStore,
summary: flow.summary,
description: flow.description ?? '',
value: flow.value,
@@ -164,7 +170,7 @@
}
})
if (enabled) {
await createSchedule(flow.path)
await createSchedule($pathStore)
}
} else {
localStorage.removeItem(`flow-${initialPath}`)
@@ -172,7 +178,7 @@
workspace: $workspaceStore!,
path: initialPath,
requestBody: {
path: flow.path,
path: $pathStore,
summary: flow.summary,
description: flow.description ?? '',
value: flow.value,
@@ -183,17 +189,17 @@
})
const scheduleExists = await ScheduleService.existsSchedule({
workspace: $workspaceStore ?? '',
path: flow.path
path: $pathStore
})
if (scheduleExists) {
const schedule = await ScheduleService.getSchedule({
workspace: $workspaceStore ?? '',
path: flow.path
path: $pathStore
})
if (JSON.stringify(schedule.args) != JSON.stringify(args) || schedule.schedule != cron) {
await ScheduleService.updateSchedule({
workspace: $workspaceStore ?? '',
path: flow.path,
path: $pathStore,
requestBody: {
schedule: formatCron(cron),
timezone,
@@ -204,17 +210,17 @@
if (enabled != schedule.enabled) {
await ScheduleService.setScheduleEnabled({
workspace: $workspaceStore ?? '',
path: flow.path,
path: $pathStore,
requestBody: { enabled }
})
}
} else if (enabled) {
await createSchedule(flow.path)
await createSchedule($pathStore)
}
}
loadingSave = false
$dirtyStore = false
dispatch('deploy')
dispatch('deploy', $pathStore)
} catch (err) {
sendUserToast(`The flow could not be saved: ${err.body}`, true)
loadingSave = false
@@ -234,7 +240,7 @@
timeout = setTimeout(() => {
try {
localStorage.setItem(
initialPath ? `flow-${initialPath}` : 'flow',
initialPath && initialPath != '' ? `flow-${initialPath}` : 'flow',
encodeState({
flow: $flowStore,
selectedId: $selectedIdStore
@@ -262,6 +268,9 @@
const scriptEditorDrawer = writable<ScriptEditorDrawer | undefined>(undefined)
const moving = writable<{ module: FlowModule; modules: FlowModule[] } | undefined>(undefined)
const history = initHistory($flowStore)
const pathStore = writable<string>(initialPath)
$: $pathStore = initialPath
const testStepStore = writable<Record<string, any>>({})
@@ -278,6 +287,7 @@
history,
flowStateStore,
flowStore,
pathStore,
testStepStore,
saveDraft,
initialPath
@@ -300,7 +310,7 @@
$: selectedId && select(selectedId)
$: initialPath && $workspaceStore && loadSchedule()
$: initialPath && initialPath != '' && $workspaceStore && loadSchedule()
function onKeyDown(event: KeyboardEvent) {
let classes = event.target?.['className']
@@ -366,11 +376,11 @@
}> = [
{
label: 'Exit & see details',
onClick: () => dispatch('details')
onClick: () => dispatch('details', $pathStore)
}
]
if (initialPath != '') {
if (!newFlow) {
dropdownItems.push({
label: 'Fork',
onClick: () => window.open(`/flows/add?template=${initialPath}`)
@@ -950,7 +960,7 @@
<input
type="text"
readonly
value={$flowStore.path && $flowStore.path != '' ? $flowStore.path : 'Choose a path'}
value={$pathStore && $pathStore != '' ? $pathStore : 'Choose a path'}
class="font-mono !text-xs !min-w-[96px] !max-w-[300px] !w-full !h-[28px] !my-0 !py-0 !border-l-0 !rounded-l-none"
on:focus={({ currentTarget }) => {
currentTarget.select()
@@ -959,7 +969,7 @@
</div>
</div>
<div class="flex flex-row space-x-2">
{#if $enterpriseLicense && initialPath != ''}
{#if $enterpriseLicense && !newFlow}
<Awareness />
{/if}
<FlowBuilderTutorials
@@ -992,7 +1002,7 @@
size="xs"
startIcon={{ icon: faSave }}
on:click={() => saveFlow()}
dropdownItems={initialPath != '' ? dropdownItems : undefined}
dropdownItems={!newFlow ? dropdownItems : undefined}
>
Deploy
</Button>
@@ -1,5 +1,12 @@
<script lang="ts">
import { Job, JobService, type Flow, type FlowModule, type RestartedFrom } from '$lib/gen'
import {
Job,
JobService,
type Flow,
type FlowModule,
type RestartedFrom,
type OpenFlow
} from '$lib/gen'
import { workspaceStore } from '$lib/stores'
import { faClose, faPlay, faRefresh } from '@fortawesome/free-solid-svg-icons'
import { Badge, Button, Drawer, Kbd, Popup } from './common'
@@ -33,7 +40,7 @@
let isRunning: boolean = false
let jobProgressReset: () => void
const { selectedId, previewArgs, flowStateStore, flowStore, initialPath } =
const { selectedId, previewArgs, flowStateStore, flowStore, pathStore, initialPath } =
getContext<FlowEditorContext>('FlowEditorContext')
const dispatch = createEventDispatcher()
@@ -62,7 +69,7 @@
})
}
function extractFlow(previewMode: 'upTo' | 'whole'): Flow {
function extractFlow(previewMode: 'upTo' | 'whole'): OpenFlow {
if (previewMode === 'whole') {
return $flowStore
} else {
@@ -83,7 +90,7 @@
) {
jobProgressReset()
const newFlow = extractFlow(previewMode)
jobId = await runFlowPreview(args, newFlow, restartedFrom)
jobId = await runFlowPreview(args, newFlow, $pathStore, restartedFrom)
isRunning = true
}
@@ -28,7 +28,7 @@
export let editor: Editor
export let diffEditor: DiffEditor
const { flowStore, flowStateStore, testStepStore } =
const { flowStore, flowStateStore, testStepStore, pathStore } =
getContext<FlowEditorContext>('FlowEditorContext')
// Test
@@ -54,7 +54,7 @@
// let jobId: string | undefined = undefined
if (val.type == 'rawscript') {
await testJobLoader?.runPreview(
val.path ?? ($flowStore?.path ?? '') + '/' + mod.id,
val.path ?? ($pathStore ?? '') + '/' + mod.id,
val.content,
val.language,
args,
@@ -76,7 +76,6 @@
code = ncode
if (editor) {
editor.setValue(ncode)
console.log(editor, ncode)
}
}
@@ -150,10 +150,29 @@
{#each result ?? [] as value, index}
<div class="overflow-auto w-full">
<ListWrapper
onInputsChange={() => {
on:set={(e) => {
const { id, value } = e.detail
if (!inputs[id]) {
inputs[id] = { [index]: value }
} else {
inputs[id] = { ...inputs[id], [index]: value }
}
outputs?.inputs.set(inputs, true)
}}
on:remove={(e) => {
const id = e.detail
if (inputs?.[id] == undefined) {
return
}
if (index == 0) {
delete inputs[id]
inputs = { ...inputs }
} else {
delete inputs[id][index]
inputs[id] = { ...inputs[id] }
}
outputs?.inputs.set(inputs, true)
}}
bind:inputs
{value}
{index}
>
@@ -177,7 +196,7 @@
</Carousel>
{/key}
{:else}
<ListWrapper onInputsChange={() => {}} disabled value={undefined} index={0}>
<ListWrapper disabled value={undefined} index={0}>
<SubGridEditor visible={false} {id} subGridId={`${id}-0`} />
</ListWrapper>
{#if !Array.isArray(result)}
@@ -87,6 +87,11 @@
if (noBackend && componentInput?.type == 'runnable') {
result = componentInput?.['value']
}
if (noBackend) {
initializing = false
}
onMount(() => {
$staticExporter[id] = () => {
return result
@@ -1,5 +1,5 @@
<script lang="ts">
import { Button } from '$lib/components/common'
import { Badge, Button } from '$lib/components/common'
import Menu from '$lib/components/details/Menu.svelte'
import MenuItem from '$lib/components/common/menu/MenuItem.svelte'
@@ -23,6 +23,7 @@
export let mainButtons: MainButton[] = []
export let menuItems: MenuItemButton[] = []
export let title: string
export let tag: string | undefined
export let errorHandlerKind: 'flow' | 'script'
export let scriptOrFlowPath: string
@@ -33,7 +34,9 @@
<div class="mx-auto">
<div class="flex w-full flex-wrap md:flex-nowrap justify-end gap-x-2 gap-y-4 h-8 items-center">
<div class="grow px-2 sm:w-full inline-flex items-center gap-4">
<div class="text-lg min-w-24 font-bold truncate">{title}</div>
<div class="text-lg min-w-24 font-bold truncate">{title}</div>{#if tag}
<Badge>tag: {tag}</Badge>
{/if}
<slot />
</div>
<div class="flex gap-1 md:gap-2 items-center">
@@ -260,6 +260,7 @@ done`
</div>
{#key token}
<!-- svelte-ignore a11y-click-events-have-key-events -->
<!-- svelte-ignore a11y-no-static-element-interactions -->
<Tabs selected="rest">
<Tab value="rest" size="xs">REST</Tab>
{#if SCRIPT_VIEW_SHOW_EXAMPLE_CURL}
@@ -4,9 +4,13 @@
import FlowCardHeader from './FlowCardHeader.svelte'
export let title: string | undefined = undefined
export let flowModule: FlowModule | undefined = undefined
export let noEditor: boolean
</script>
<FlowCardHeader {title} bind:flowModule>
<slot name="header" />
</FlowCardHeader>
{#if !noEditor}
<FlowCardHeader {title} bind:flowModule>
<slot name="header" />
</FlowCardHeader>
{/if}
<slot />
@@ -27,7 +27,7 @@
</script>
<div
class="overflow-x-auto scrollbar-hidden flex items-center justify-between px-4 py-1 py-space-x-2 flex-nowrap"
class="overflow-x-auto scrollbar-hidden flex items-center justify-between px-4 py-1 py-space-x-2 flex-nowrap gap-x-2"
>
{#if flowModule}
<span class="text-sm w-full mr-4">
@@ -18,7 +18,7 @@
import { faClipboard } from '@fortawesome/free-solid-svg-icons'
import SchemaForm from '$lib/components/SchemaForm.svelte'
const { previewArgs, flowStore } = getContext<FlowEditorContext>('FlowEditorContext')
const { previewArgs, flowStore, pathStore } = getContext<FlowEditorContext>('FlowEditorContext')
let drawer: Drawer
let interval: NodeJS.Timeout | undefined = undefined
@@ -32,14 +32,14 @@
async function startCapturePoint() {
await CaptureService.createCapture({
workspace: $workspaceStore!,
path: $flowStore.path
path: $pathStore
})
}
async function getCaptureInput() {
const capture = await CaptureService.getCapture({
workspace: $workspaceStore!,
path: $flowStore.path
path: $pathStore
})
captureInput = capture
jsonSchema = { required: [], properties: {}, ...convert(capture) }
@@ -65,10 +65,10 @@
class="text-2xl"
on:click={(e) => {
e.preventDefault()
copyToClipboard(`${hostname}/api/w/${$workspaceStore}/capture_u/${$flowStore.path}`)
copyToClipboard(`${hostname}/api/w/${$workspaceStore}/capture_u/${$pathStore}`)
}}
href="{hostname}/api/w/{$workspaceStore}/capture_u/{$flowStore.path}"
>{hostname}/api/w/{$workspaceStore}/capture_u/{$flowStore.path}
href="{hostname}/api/w/{$workspaceStore}/capture_u/{$pathStore}"
>{hostname}/api/w/{$workspaceStore}/capture_u/{$pathStore}
<Icon data={faClipboard} /></a
>
</div>
@@ -76,7 +76,7 @@
<div class="text-xs box mb-4 b">
<pre class="overflow-auto"
>{`curl -X POST ${hostname}/api/w/${$workspaceStore}/capture_u/${$flowStore.path} \\
>{`curl -X POST ${hostname}/api/w/${$workspaceStore}/capture_u/${$pathStore} \\
-H 'Content-Type: application/json' \\
-d '{"foo": 42}'`}</pre
>
@@ -3,6 +3,7 @@
import type { FlowModule } from '$lib/gen'
import FlowCard from '../common/FlowCard.svelte'
export let noEditor: boolean
export let branch: {
summary?: string
skip_failure?: boolean
@@ -11,7 +12,7 @@
</script>
<div class="h-full flex flex-col">
<FlowCard title="Branch">
<FlowCard {noEditor} title="Branch">
<div slot="header" class="grow">
<input bind:value={branch.summary} placeholder={'Summary'} />
</div>
@@ -11,10 +11,11 @@
}
export let parentModule: FlowModule
export let previousModule: FlowModule | undefined
export let noEditor: boolean
</script>
<div class="h-full flex flex-col">
<FlowCard title="Branch">
<FlowCard {noEditor} title="Branch">
<div slot="header" class="grow">
<input bind:value={branch.summary} placeholder={'Summary'} />
</div>
@@ -13,6 +13,7 @@
import FlowModuleSuspend from './FlowModuleSuspend.svelte'
import FlowModuleMock from './FlowModuleMock.svelte'
export let noEditor: boolean
export let flowModule: FlowModule
export let previousModule: FlowModule | undefined
@@ -23,7 +24,7 @@
</script>
<div class="h-full flex flex-col w-full" id="flow-editor-branch-all-wrapper">
<FlowCard title={value.type == 'branchall' ? 'Run all branches' : 'Run one branch'}>
<FlowCard {noEditor} title={value.type == 'branchall' ? 'Run all branches' : 'Run one branch'}>
<SplitPanesWrapper>
<Splitpanes horizontal>
<Pane size={flowModule ? 60 : 100}>
@@ -16,6 +16,7 @@
export let flowModule: FlowModule
export let previousModule: FlowModule | undefined
export let noEditor: boolean
let value = flowModule.value as BranchOne
$: value = flowModule.value as BranchOne
@@ -24,7 +25,7 @@
</script>
<div class="h-full" id="flow-editor-branch-one-wrapper">
<FlowCard title="Run one branch">
<FlowCard {noEditor} title="Run one branch">
<SplitPanesWrapper>
<Splitpanes horizontal>
<Pane size={flowModule ? 60 : 100}>
@@ -10,6 +10,8 @@
import InputTransformSchemaForm from '$lib/components/InputTransformSchemaForm.svelte'
import type { FlowEditorContext } from '../types'
export let noEditor: boolean
let hideOptional = false
const { flowStateStore, flowStore } = getContext<FlowEditorContext>('FlowEditorContext')
@@ -82,7 +84,7 @@
</script>
<div class="min-h-full">
<FlowCard title="All Static Inputs">
<FlowCard {noEditor} title="All Static Inputs">
<Toggle slot="header" bind:checked={hideOptional} options={{ left: 'Hide optional inputs' }} />
<div class="min-h-full flex-1">
<Alert type="info" title="Static Inputs" class="m-4"
@@ -9,6 +9,8 @@
import FlowConstants from './FlowConstants.svelte'
import type { FlowModule } from '$lib/gen'
export let noEditor = false
const { selectedId, flowStore } = getContext<FlowEditorContext>('FlowEditorContext')
function checkDup(modules: FlowModule[]): string | undefined {
@@ -24,15 +26,15 @@
</script>
{#if $selectedId?.startsWith('settings')}
<FlowSettings />
<FlowSettings {noEditor} />
{:else if $selectedId === 'Input'}
<FlowInput />
<FlowInput {noEditor} />
{:else if $selectedId === 'Result'}
<p class="p-4 text-secondary">Nothing to show about the result node. Happy flow building!</p>
{:else if $selectedId === 'constants'}
<FlowConstants />
<FlowConstants {noEditor} />
{:else if $selectedId === 'failure'}
<FlowFailureModule />
<FlowFailureModule {noEditor} />
{:else}
{@const dup = checkDup($flowStore.value.modules)}
{#if dup}
@@ -40,7 +42,11 @@
{:else}
{#key $selectedId}
{#each $flowStore.value.modules as flowModule, index (flowModule.id ?? index)}
<FlowModuleWrapper bind:flowModule previousModule={$flowStore.value.modules[index - 1]} />
<FlowModuleWrapper
{noEditor}
bind:flowModule
previousModule={$flowStore.value.modules[index - 1]}
/>
{/each}
{/key}
{/if}
@@ -3,11 +3,14 @@
import type { FlowEditorContext } from '../types'
import FlowModuleWrapper from './FlowModuleWrapper.svelte'
export let noEditor = false
const { flowStore } = getContext<FlowEditorContext>('FlowEditorContext')
</script>
{#if $flowStore.value.failure_module}
<FlowModuleWrapper
{noEditor}
bind:flowModule={$flowStore.value.failure_module}
on:delete={() => {
$flowStore.value.failure_module = undefined
@@ -14,6 +14,8 @@
import { sendUserToast } from '$lib/toast'
import SavedInputs from '$lib/components/SavedInputs.svelte'
export let noEditor: boolean
const { flowStore, flowStateStore, previewArgs, initialPath } =
getContext<FlowEditorContext>('FlowEditorContext')
@@ -37,7 +39,7 @@
</script>
<CapturePayload bind:this={capturePayload} />
<FlowCard title="Flow Input">
<FlowCard {noEditor} title="Flow Input">
<div class="p-6">
<div class="flex flex-row items-center gap-2 pb-2 border-b border-gray-400">
<div>Copy input's schema from</div>
@@ -23,6 +23,7 @@
export let mod: FlowModule
export let parentModule: FlowModule | undefined
export let previousModule: FlowModule | undefined
export let noEditor: boolean
let editor: SimpleEditor | undefined = undefined
let selected: string = 'early-stop'
@@ -39,7 +40,7 @@
</script>
<div class="h-full flex flex-col">
<FlowCard title="For loop">
<FlowCard {noEditor} title="For loop">
<div slot="header" class="grow">
<input bind:value={mod.summary} placeholder={'Summary'} />
</div>
@@ -45,16 +45,16 @@
import { isCloudHosted } from '$lib/cloud'
import { loadSchemaFromModule } from '../flowInfers'
const { selectedId, previewArgs, flowStateStore, flowStore, saveDraft } =
const { selectedId, previewArgs, flowStateStore, flowStore, pathStore, saveDraft } =
getContext<FlowEditorContext>('FlowEditorContext')
export let flowModule: FlowModule
export let failureModule: boolean = false
export let parentModule: FlowModule | undefined = undefined
export let previousModule: FlowModule | undefined
export let scriptKind: 'script' | 'trigger' | 'approval' = 'script'
export let scriptTemplate: 'pgsql' | 'mysql' | 'script' | 'docker' | 'powershell' = 'script'
export let noEditor: boolean
let editor: Editor
let diffEditor: DiffEditor
@@ -159,7 +159,7 @@
let forceReload = 0
let editorPanelSize = flowModule.value.type == 'script' ? 30 : 50
let editorPanelSize = noEditor ? 0 : flowModule.value.type == 'script' ? 30 : 50
let editorSettingsPanelSize = 100 - editorPanelSize
</script>
@@ -167,7 +167,7 @@
{#if flowModule.value}
<div class="h-full" bind:this={wrapper} bind:clientWidth={width}>
<FlowCard bind:flowModule>
<FlowCard {noEditor} bind:flowModule>
<svelte:fragment slot="header">
<FlowModuleHeader
bind:module={flowModule}
@@ -197,7 +197,8 @@
flowModule,
$selectedId,
$flowStateStore[flowModule.id].schema,
$flowStore
$flowStore,
$pathStore
)
flowModule = module
$flowStateStore[module.id] = state
@@ -205,7 +206,7 @@
/>
</svelte:fragment>
{#if flowModule.value.type === 'rawscript'}
{#if flowModule.value.type === 'rawscript' && !noEditor}
<div class="border-b-2 shadow-sm px-1">
<EditorBar
{validCode}
@@ -233,59 +234,63 @@
<Splitpanes horizontal>
<Pane bind:size={editorPanelSize} minSize={20}>
{#if flowModule.value.type === 'rawscript'}
{#key flowModule.id}
<Editor
folding
path={flowModule.value.path}
bind:websocketAlive
bind:this={editor}
class="h-full relative"
bind:code={flowModule.value.content}
deno={flowModule.value.language === RawScript.language.DENO}
lang={scriptLangToEditorLang(flowModule.value.language)}
automaticLayout={true}
cmdEnterAction={async () => {
selected = 'test'
if ($selectedId == flowModule.id) {
{#if !noEditor}
{#key flowModule.id}
<Editor
folding
path={flowModule.value.path}
bind:websocketAlive
bind:this={editor}
class="h-full relative"
bind:code={flowModule.value.content}
deno={flowModule.value.language === RawScript.language.DENO}
lang={scriptLangToEditorLang(flowModule.value.language)}
automaticLayout={true}
cmdEnterAction={async () => {
selected = 'test'
if ($selectedId == flowModule.id) {
if (flowModule.value.type === 'rawscript') {
flowModule.value.content = editor.getCode()
}
await reload(flowModule)
modulePreview?.runTestWithStepArgs()
}
}}
on:change={async (event) => {
if (flowModule.value.type === 'rawscript') {
flowModule.value.content = editor.getCode()
flowModule.value.content = event.detail
}
await reload(flowModule)
modulePreview?.runTestWithStepArgs()
}
}}
on:change={async (event) => {
if (flowModule.value.type === 'rawscript') {
flowModule.value.content = event.detail
}
await reload(flowModule)
}}
formatAction={() => {
reload(flowModule)
saveDraft()
}}
fixedOverflowWidgets={true}
args={Object.entries(flowModule.value.input_transforms).reduce(
(acc, [key, obj]) => {
acc[key] = obj.type === 'static' ? obj.value : undefined
return acc
},
{}
)}
/>
<DiffEditor
bind:this={diffEditor}
automaticLayout
fixedOverflowWidgets
class="hidden h-full"
/>
{/key}
{:else if flowModule.value.type === 'script'}
<div class="border-t">
{#key forceReload}
<FlowModuleScript path={flowModule.value.path} hash={flowModule.value.hash} />
}}
formatAction={() => {
reload(flowModule)
saveDraft()
}}
fixedOverflowWidgets={true}
args={Object.entries(flowModule.value.input_transforms).reduce(
(acc, [key, obj]) => {
acc[key] = obj.type === 'static' ? obj.value : undefined
return acc
},
{}
)}
/>
<DiffEditor
bind:this={diffEditor}
automaticLayout
fixedOverflowWidgets
class="hidden h-full"
/>
{/key}
</div>
{/if}
{:else if flowModule.value.type === 'script'}
{#if !noEditor}
<div class="border-t">
{#key forceReload}
<FlowModuleScript path={flowModule.value.path} hash={flowModule.value.hash} />
{/key}
</div>
{/if}
{:else if flowModule.value.type === 'flow'}
<FlowPathViewer path={flowModule.value.path} />
{/if}
@@ -18,10 +18,12 @@
import FlowBranchesAllWrapper from './FlowBranchesAllWrapper.svelte'
import FlowBranchesOneWrapper from './FlowBranchesOneWrapper.svelte'
export let flowModule: FlowModule
export let noEditor: boolean = false
const { selectedId, schedule, flowStateStore } =
getContext<FlowEditorContext>('FlowEditorContext')
export let flowModule: FlowModule
let scriptKind: 'script' | 'trigger' | 'approval' = 'script'
let scriptTemplate: 'pgsql' | 'mysql' | 'script' | 'docker' | 'powershell' = 'script'
@@ -34,11 +36,11 @@
{#if flowModule.id === $selectedId}
{#if flowModule.value.type === 'forloopflow'}
<FlowLoop bind:mod={flowModule} {parentModule} {previousModule} />
<FlowLoop {noEditor} bind:mod={flowModule} {parentModule} {previousModule} />
{:else if flowModule.value.type === 'branchone'}
<FlowBranchesOneWrapper {previousModule} bind:flowModule />
<FlowBranchesOneWrapper {noEditor} {previousModule} bind:flowModule />
{:else if flowModule.value.type === 'branchall'}
<FlowBranchesAllWrapper {previousModule} bind:flowModule />
<FlowBranchesAllWrapper {noEditor} {previousModule} bind:flowModule />
{:else if flowModule.value.type === 'identity'}
{#if $selectedId == 'failure'}
<div class="p-4">
@@ -123,6 +125,7 @@
{/if}
{:else if flowModule.value.type === 'rawscript' || flowModule.value.type === 'script' || flowModule.value.type === 'flow'}
<FlowModuleComponent
{noEditor}
bind:flowModule
{parentModule}
{previousModule}
@@ -156,7 +159,7 @@
{/if}
{#each flowModule.value.branches as branch, branchIndex (branchIndex)}
{#if $selectedId === `${flowModule?.id}-branch-${branchIndex}`}
<FlowBranchOneWrapper bind:branch parentModule={flowModule} {previousModule} />
<FlowBranchOneWrapper {noEditor} bind:branch parentModule={flowModule} {previousModule} />
{:else}
{#each branch.modules as submodule, index}
<svelte:self
@@ -170,7 +173,7 @@
{:else if flowModule.value.type === 'branchall'}
{#each flowModule.value.branches as branch, branchIndex (branchIndex)}
{#if $selectedId === `${flowModule?.id}-branch-${branchIndex}`}
<FlowBranchAllWrapper bind:branch />
<FlowBranchAllWrapper {noEditor} bind:branch />
{:else}
{#each branch.modules as submodule, index}
<svelte:self
@@ -28,7 +28,9 @@
import Label from '$lib/components/Label.svelte'
import ErrorHandlerToggleButton from '$lib/components/details/ErrorHandlerToggleButton.svelte'
const { selectedId, flowStore, initialPath, previewArgs } =
export let noEditor: boolean
const { selectedId, flowStore, initialPath, previewArgs, pathStore } =
getContext<FlowEditorContext>('FlowEditorContext')
async function loadWorkerGroups() {
@@ -38,8 +40,8 @@
}
let hostname = BROWSER ? window.location.protocol + '//' + window.location.host : 'SSR'
$: url = `${hostname}/api/w/${$workspaceStore}/jobs/run/f/${$flowStore?.path}`
$: syncedUrl = `${hostname}/api/w/${$workspaceStore}/jobs/run_wait_result/f/${$flowStore?.path}`
$: url = `${hostname}/api/w/${$workspaceStore}/jobs/run/f/${$pathStore}`
$: syncedUrl = `${hostname}/api/w/${$workspaceStore}/jobs/run_wait_result/f/${$pathStore}`
$: if ($selectedId == 'settings-worker-group') {
$workerTags = undefined
@@ -55,11 +57,13 @@
</script>
<div class="h-full overflow-hidden">
<FlowCard title="Settings">
<FlowCard {noEditor} title="Settings">
<div class="h-full flex-1">
<Tabs bind:selected={$selectedId}>
<Tab value="settings-metadata">Metadata</Tab>
<Tab value="settings-schedule">Schedule</Tab>
{#if !noEditor}
<Tab value="settings-schedule">Schedule</Tab>
{/if}
<Tab value="settings-same-worker">Shared Directory</Tab>
<Tab value="settings-early-stop">Early Stop</Tab>
<Tab value="settings-worker-group">Worker Group</Tab>
@@ -90,17 +94,19 @@
/>
</Label>
<Label label="Path">
<Path
autofocus={false}
bind:this={path}
bind:dirty={dirtyPath}
bind:path={$flowStore.path}
{initialPath}
namePlaceholder="flow"
kind="flow"
/>
</Label>
{#if !noEditor}
<Label label="Path">
<Path
autofocus={false}
bind:this={path}
bind:dirty={dirtyPath}
bind:path={$pathStore}
{initialPath}
namePlaceholder="flow"
kind="flow"
/>
</Label>
{/if}
<Label label="Description">
<textarea
@@ -155,7 +161,7 @@
<div class="flex flex-row items-center gap-1">
<ErrorHandlerToggleButton
kind="flow"
scriptOrFlowPath={$flowStore.path}
scriptOrFlowPath={$pathStore}
bind:errorHandlerMuted={$flowStore.ws_error_handler_muted}
iconOnly={false}
/>
@@ -1,4 +1,4 @@
import type { Flow, FlowModule, InputTransform } from '$lib/gen'
import type { FlowModule, InputTransform, OpenFlow } from '$lib/gen'
type ModuleBranches = FlowModule[][]
@@ -46,7 +46,7 @@ function exprsOfInputTransforms(x: Record<string, InputTransform>): string[] {
.flat()
}
export function getDependentComponents(id: string, flow: Flow): Record<string, string[]> {
export function getDependentComponents(id: string, flow: OpenFlow): Record<string, string[]> {
let modules = getAllModules(flow.value.modules, flow.value.failure_module)
return filterDependentComponents(modules, id)
}
@@ -1,10 +1,10 @@
import type { Flow } from '$lib/gen/models/Flow'
import type { OpenFlow } from '$lib/gen'
import { dfs } from './dfs'
import type { FlowState } from './flowState'
import { charsToNumber, numberToChars } from './idUtils'
// Computes the next available id
export function nextId(flowState: FlowState, fullFlow: Flow): string {
export function nextId(flowState: FlowState, fullFlow: OpenFlow): string {
const allIds = dfs(fullFlow.value.modules, (fm) => fm.id)
const max = allIds.concat(Object.keys(flowState)).reduce((acc, key) => {
if (key === 'failure' || key.includes('branch') || key.includes('loop')) {
@@ -2,11 +2,11 @@ import type { Schema } from '$lib/common'
import {
Script,
ScriptService,
type Flow,
type FlowModule,
type PathFlow,
type PathScript,
type RawScript
type RawScript,
type OpenFlow
} from '$lib/gen'
import { initialCode } from '$lib/script_helpers'
import { userStore, workspaceStore } from '$lib/stores'
@@ -175,7 +175,7 @@ async function createInlineScriptModuleFromPath(
}
}
export function emptyModule(flowState: FlowState, fullFlow: Flow, flow?: boolean): FlowModule {
export function emptyModule(flowState: FlowState, fullFlow: OpenFlow, flow?: boolean): FlowModule {
return {
id: nextId(flowState, fullFlow),
value: { type: 'identity', flow }
@@ -186,7 +186,8 @@ export async function createScriptFromInlineScript(
flowModule: FlowModule,
suffix: string,
schema: Schema,
flow: Flow
flow: OpenFlow,
flowPath: string
): Promise<[FlowModule & { value: PathScript }, FlowModuleState]> {
const user = get(userStore)
@@ -202,9 +203,9 @@ export async function createScriptFromInlineScript(
suffix = others.join('/')
}
const path = `${flow.path}/${suffix}`
const path = `${flowPath}/${suffix}`
const forkedDescription = wasForked ? `as a fork of ${originalScriptPath}` : ''
const description = `This script was edited in place of flow ${flow.path} ${forkedDescription} by ${user?.username}.`
const description = `This script was edited in place of flow ${flowPath} ${forkedDescription} by ${user?.username}.`
const availablePath = await findNextAvailablePath(path)
@@ -1,4 +1,4 @@
import type { Flow } from '$lib/gen'
import type { Flow, OpenFlow } from '$lib/gen'
import { writable, type Writable } from 'svelte/store'
import { initFlowState, type FlowState } from './flowState'
@@ -15,7 +15,7 @@ export async function initFlow(
flowStore.set(flow)
}
export async function copyFirstStepSchema(flowState: FlowState, flowStore: Writable<Flow>) {
export async function copyFirstStepSchema(flowState: FlowState, flowStore: Writable<OpenFlow>) {
flowStore.update((flow) => {
const firstModuleId = flow.value.modules[0]?.id
@@ -31,7 +31,9 @@
'inputs',
'schedules',
'failure',
'constants'
'constants',
'Result',
'Input'
].includes($selectedId) ||
$selectedId?.includes('branch')
</script>
@@ -48,8 +50,8 @@
}}
startIcon={{ icon: faPlay }}
>
Test up to
<Badge baseClass="ml-1" color="indigo">
Test up to&nbsp;
<Badge baseClass="ml-1" small color="indigo">
{$selectedId}
</Badge>
</Button>
@@ -141,11 +141,13 @@
'bg-surface border'
)}
>
<svelte:component
this={APP_TO_ICON_COMPONENT[item['app']]}
height={18}
width={18}
/>
{#if item['app'] in APP_TO_ICON_COMPONENT}
<svelte:component
this={APP_TO_ICON_COMPONENT[item['app']]}
height={18}
width={18}
/>
{/if}
</div>
<div class="w-full text-left font-normal">
@@ -1,5 +1,5 @@
import type { Schema } from '$lib/common'
import type { Flow, FlowModule } from '$lib/gen'
import type { FlowModule, OpenFlow } from '$lib/gen'
import { schemaToObject } from '$lib/schema'
import { getAllSubmodules, getSubModules } from './flowExplorer'
import type { FlowState } from './flowState'
@@ -18,7 +18,7 @@ type StepPropPicker = {
type ModuleBranches = FlowModule[][]
export function dfs(id: string | undefined, flow: Flow, getParents: boolean = true): FlowModule[] {
export function dfs(id: string | undefined, flow: OpenFlow, getParents: boolean = true): FlowModule[] {
if (id === undefined) {
return []
}
@@ -85,7 +85,7 @@ function getFlowInput(
}
}
export function getPreviousIds(id: string, flow: Flow, include_node: boolean): string[] {
export function getPreviousIds(id: string, flow: OpenFlow, include_node: boolean): string[] {
const df = dfs(id, flow, false)
if (!include_node) {
df.shift()
@@ -110,7 +110,7 @@ export function getStepPropPicker(
parentModule: FlowModule | undefined,
previousModule: FlowModule | undefined,
id: string,
flow: Flow,
flow: OpenFlow,
args: any,
include_node: boolean
): StepPropPicker {
+4 -3
View File
@@ -1,4 +1,4 @@
import type { Flow, FlowModule } from '$lib/gen'
import type { FlowModule, OpenFlow } from '$lib/gen'
import type { History } from '$lib/history'
import type { Writable } from 'svelte/store'
import type ScriptEditorDrawer from './content/ScriptEditorDrawer.svelte'
@@ -11,8 +11,9 @@ export type FlowEditorContext = {
schedule: Writable<Schedule>
previewArgs: Writable<Record<string, any>>
scriptEditorDrawer: Writable<ScriptEditorDrawer | undefined>
history: History<Flow>
flowStore: Writable<Flow>
history: History<OpenFlow>
pathStore: Writable<string>
flowStore: Writable<OpenFlow & { tag?: string, ws_error_handler_muted?: boolean}>
flowStateStore: Writable<FlowState>
testStepStore: Writable<Record<string, any>>
saveDraft: () => void
+5 -4
View File
@@ -5,7 +5,8 @@ import {
type FlowModule,
type InputTransform,
type Job,
type RestartedFrom
type RestartedFrom,
type OpenFlow
} from '$lib/gen'
import { workspaceStore } from '$lib/stores'
import { cleanExpr, emptySchema } from '$lib/utils'
@@ -68,7 +69,7 @@ export function evalValue(
return v
}
export function cleanInputs(flow: Flow | any): Flow {
export function cleanInputs(flow: OpenFlow | any): OpenFlow & {tag?: string, ws_error_handler_muted?: boolean}{
const newFlow: Flow = JSON.parse(JSON.stringify(flow))
newFlow.value.modules.forEach((mod) => {
if (mod.value.type == 'rawscript' || mod.value.type == 'script') {
@@ -109,14 +110,14 @@ export function jobsToResults(jobs: Job[]) {
})
}
export async function runFlowPreview(args: Record<string, any>, flow: Flow, restartedFrom: RestartedFrom | undefined) {
export async function runFlowPreview(args: Record<string, any>, flow: OpenFlow & { tag?: string }, path: string, restartedFrom: RestartedFrom | undefined) {
const newFlow = flow
return await JobService.runFlowPreview({
workspace: get(workspaceStore) ?? '',
requestBody: {
args,
value: newFlow.value,
path: newFlow.path,
path: path,
tag: newFlow.tag,
restarted_from: restartedFrom,
}
@@ -55,6 +55,7 @@
<span>
{#if level != 0}
<!-- svelte-ignore a11y-click-events-have-key-events -->
<!-- svelte-ignore a11y-no-static-element-interactions -->
<span class="cursor-pointer border hover:bg-surface-hover px-1 rounded" on:click={collapse}>
-
</span>
@@ -122,6 +123,7 @@
{/if}
<!-- svelte-ignore a11y-click-events-have-key-events -->
<!-- svelte-ignore a11y-no-static-element-interactions -->
<span
class="border border-blue-600 rounded px-1 cursor-pointer hover:bg-gray-200"
class:hidden={!collapsed}
@@ -48,6 +48,7 @@
</Portal>
<!-- svelte-ignore a11y-click-events-have-key-events -->
<!-- svelte-ignore a11y-no-static-element-interactions -->
<div
class={twMerge(
'hover:bg-surface-hover cursor-pointer',
@@ -1,4 +1,4 @@
import type { Flow, FlowModule } from '$lib/gen'
import type { FlowModule, OpenFlow } from '$lib/gen'
import { findGridItem } from '../apps/editor/appUtils'
import type { App } from '../apps/types'
@@ -55,7 +55,7 @@ export function selectOptionsBySelector(selector: string, value: string) {
}
}
export function isFlowTainted(flow: Flow) {
export function isFlowTainted(flow: OpenFlow) {
return flow.value.modules.length > 0 || Object.keys(flow?.schema?.properties).length > 0
}
@@ -64,7 +64,7 @@ export function isAppTainted(app: App) {
}
export function updateFlowModuleById(
flow: Flow,
flow: OpenFlow,
id: string,
callback: (module: FlowModule) => void
) {
@@ -27,6 +27,8 @@
let selectedId: string = 'settings-metadata'
let loading = false
let initialPath: string | undefined = undefined
export const flowStore = writable<Flow>({
summary: '',
value: { modules: [] },
@@ -85,15 +87,16 @@
path: templatePath
})
Object.assign(flow, template)
const oldPath = flow.path.split('/')
flow.path = `u/${$userStore?.username.split('@')[0]}/${oldPath[oldPath.length - 1]}_fork`
const oldPath = templatePath.split('/')
console.log(oldPath)
initialPath = `u/${$userStore?.username.split('@')[0]}/${oldPath[oldPath.length - 1]}_fork`
flow = flow
goto('?', { replaceState: true })
selectedId = 'settings-metadata'
} else if (hubId) {
const hub = await FlowService.getHubFlowById({ id: Number(hubId) })
delete hub['comments']
flow.path = `u/${$userStore?.username}/flow_${hubId}`
initialPath = `u/${$userStore?.username}/flow_${hubId}`
Object.assign(flow, hub.flow)
flow = flow
goto('?', { replaceState: true })
@@ -120,17 +123,19 @@
<UnsavedConfirmationModal />
<FlowBuilder
on:saveInitial={() => {
goto(`/flows/edit/${$flowStore.path}?selected=${getSelectedId?.()}`)
on:saveInitial={(e) => {
goto(`/flows/edit/${e.detail}?selected=${getSelectedId?.()}`)
}}
on:deploy={() => {
goto(`/flows/get/${$flowStore.path}?workspace=${$workspaceStore}`)
on:deploy={(e) => {
goto(`/flows/get/${e.detail}?workspace=${$workspaceStore}`)
}}
on:details={() => {
goto(`/flows/get/${$flowStore.path}?workspace=${$workspaceStore}`)
on:details={(e) => {
goto(`/flows/get/${e.detail}?workspace=${$workspaceStore}`)
}}
{initialPath}
bind:getSelectedId
bind:this={flowBuilder}
newFlow
{flowStore}
{flowStateStore}
{selectedId}
@@ -13,6 +13,7 @@
import type { FlowState } from '$lib/components/flows/flowState'
import FlowModuleSchemaMap from '$lib/components/flows/map/FlowModuleSchemaMap.svelte'
import FlowEditorPanel from '$lib/components/flows/content/FlowEditorPanel.svelte'
import { deepEqual } from 'fast-equals'
let testJobLoader: TestJobLoader
@@ -30,6 +31,8 @@
extra_perms: {},
schema: emptySchema()
} as Flow)
let initialCode = JSON.stringify($flowStore, null, 4)
const flowStateStore = writable({} as FlowState)
const scheduleStore = writable({
args: {},
@@ -43,7 +46,7 @@
const history = initHistory($flowStore)
const testStepStore = writable<Record<string, any>>({})
const selectedIdStore = writable('')
const selectedIdStore = writable('settings-metadata')
// function select(selectedId: string) {
// selectedIdStore.set(selectedId)
@@ -56,6 +59,7 @@
scriptEditorDrawer,
moving,
history,
pathStore: writable(''),
flowStateStore,
flowStore,
testStepStore,
@@ -165,6 +169,14 @@
// }
}
let editor: SimpleEditor
$: updateCode(editor, $flowStore)
function updateCode(editor: SimpleEditor, flow: Flow) {
if (editor && !deepEqual(flow, JSON.parse(editor.getCode()))) {
editor.setCode(JSON.stringify(flow, null, 4))
}
}
</script>
<svelte:window on:keydown={onKeyDown} />
@@ -175,28 +187,28 @@
<div class="h-full w-full grid grid-cols-2">
<SimpleEditor
bind:this={editor}
code={JSON.stringify($flowStore, null, 4)}
code={initialCode}
lang="json"
on:change={(e) => {
const code = e.detail.code
try {
$flowStore = JSON.parse(code)
if (!deepEqual(JSON.parse(code), $flowStore)) {
$flowStore = JSON.parse(code)
}
} catch (e) {
console.error('issue parsing new change:', code, e)
}
}}
/>
<div class="flex flex-col h-full relative">
<div class="flex justify-center pt-1 absolute right-2 top-2">
<div class="flex flex-col max-h-screen h-full relative">
<div class="flex justify-center pt-1 z-50 absolute right-2 top-2 gap-2">
<FlowPreviewButtons />
</div>
<Splitpanes horizontal class="h-full">
<Splitpanes horizontal class="h-full max-h-screen grow">
<Pane size={33}>
{#if $flowStore?.value?.modules}
<FlowModuleSchemaMap
disableHeader
bind:modules={$flowStore.value.modules}
on:change={() => editor?.setCode(JSON.stringify($flowStore, null, 4))}
disableAi
disableTutorials
/>
@@ -205,7 +217,7 @@
{/if}
</Pane>
<Pane size={67}>
<FlowEditorPanel />
<FlowEditorPanel noEditor />
</Pane>
</Splitpanes>
</div>
@@ -96,15 +96,16 @@
<UnsavedConfirmationModal />
<FlowBuilder
on:deploy={() => {
goto(`/flows/get/${$flowStore.path}?workspace=${$workspaceStore}`)
on:deploy={(e) => {
goto(`/flows/get/${e.detail}?workspace=${$workspaceStore}`)
}}
on:details={() => {
goto(`/flows/get/${$flowStore.path}?workspace=${$workspaceStore}`)
on:details={(e) => {
goto(`/flows/get/${e.detail}?workspace=${$workspaceStore}`)
}}
{flowStore}
{flowStateStore}
initialPath={$page.params.path}
newFlow={false}
{selectedId}
{initialArgs}
{loading}
@@ -255,6 +255,7 @@
bind:errorHandlerMuted={flow.ws_error_handler_muted}
scriptOrFlowPath={flow.path}
errorHandlerKind="flow"
tag={flow.tag}
>
{#if flow?.value?.priority != undefined}
<div class="hidden md:block">
@@ -414,6 +414,7 @@
bind:errorHandlerMuted={script.ws_error_handler_muted}
errorHandlerKind="script"
scriptOrFlowPath={script.path}
tag={script.tag}
>
{#if script?.priority != undefined}
<div class="hidden md:block">