From 53715f2c06309c812fca7b898023f8ff6485aba4 Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Tue, 19 Aug 2025 02:12:57 +0000 Subject: [PATCH] fix: flow status reactivity improvement (#6402) --- frontend/src/lib/components/Dev.svelte | 49 +- .../src/lib/components/FirstStepInputs.svelte | 4 +- .../src/lib/components/FlowBuilder.svelte | 64 +- .../src/lib/components/FlowLogViewer.svelte | 39 +- .../components/FlowLogViewerWrapper.svelte | 5 +- .../FlowLoopIterationPreview.svelte | 6 +- .../lib/components/FlowPreviewContent.svelte | 25 +- .../lib/components/FlowPreviewResult.svelte | 13 +- .../lib/components/FlowStatusViewer.svelte | 71 +- .../components/FlowStatusViewerInner.svelte | 661 +++++++++--------- .../src/lib/components/FlowTimeline.svelte | 46 +- frontend/src/lib/components/JobLoader.svelte | 2 +- .../lib/components/ModulePreviewForm.svelte | 2 +- frontend/src/lib/components/ModuleTest.svelte | 34 +- .../display/AppJobIdFlowStatus.svelte | 6 +- .../apps/editor/AppJobsDrawer.svelte | 4 +- .../copilot/chat/AIChatManager.svelte.ts | 28 +- .../copilot/chat/flow/FlowAIChat.svelte | 10 +- frontend/src/lib/components/flow_builder.ts | 3 +- .../lib/components/flows/FlowEditor.svelte | 19 +- .../content/BranchPredicateEditor.svelte | 40 +- .../flows/content/FlowConstants.svelte | 6 +- .../flows/content/FlowEditorPanel.svelte | 6 +- .../components/flows/content/FlowLoop.svelte | 4 +- .../flows/content/FlowModuleComponent.svelte | 47 +- .../flows/content/FlowModuleEarlyStop.svelte | 4 +- .../flows/content/FlowModuleSkip.svelte | 2 +- .../flows/content/FlowModuleSleep.svelte | 2 +- .../flows/content/FlowModuleSuspend.svelte | 2 +- .../flows/content/FlowModuleWrapper.svelte | 6 +- .../flows/content/FlowResult.svelte | 4 +- .../flows/content/FlowWhileLoop.svelte | 2 +- .../src/lib/components/flows/flowState.ts | 9 +- .../components/flows/flowStateUtils.svelte.ts | 23 +- .../src/lib/components/flows/flowStore.ts | 4 +- .../flows/header/FlowPreviewButtons.svelte | 10 +- .../flows/map/FlowGraphPreviewButton.svelte | 2 +- .../components/flows/map/FlowJobsMenu.svelte | 4 +- .../flows/map/FlowModuleSchemaItem.svelte | 23 +- .../flows/map/FlowModuleSchemaMap.svelte | 40 +- .../lib/components/flows/previousResults.ts | 10 +- .../flows/propPicker/InputPickerInner.svelte | 4 +- frontend/src/lib/components/flows/types.ts | 40 +- frontend/src/lib/components/flows/utils.ts | 11 +- .../lib/components/graph/FlowGraphV2.svelte | 65 +- .../components/graph/graphBuilder.svelte.ts | 80 ++- frontend/src/lib/components/graph/model.ts | 15 +- .../graph/renderers/edges/BaseEdge.svelte | 8 +- .../renderers/nodes/BranchAllEndNode.svelte | 2 +- .../renderers/nodes/BranchAllStart.svelte | 2 +- .../renderers/nodes/BranchOneStart.svelte | 2 +- .../renderers/nodes/ForLoopEndNode.svelte | 4 +- .../renderers/nodes/ForLoopStartNode.svelte | 2 +- .../graph/renderers/nodes/ModuleNode.svelte | 43 +- .../renderers/nodes/branchOneEndNode.svelte | 2 +- .../src/lib/components/modulesTest.svelte.ts | 39 +- .../components/stepHistoryLoader.svelte.ts | 21 +- .../FlowBuilderTutorialBranchOne.svelte | 2 +- .../FlowBuilderTutorialForLoop.svelte | 8 +- .../(root)/(logged)/flows/add/+page.svelte | 4 +- .../flows/edit/[...path]/+page.svelte | 5 +- .../(root)/(logged)/run/[...run]/+page.svelte | 8 +- frontend/src/routes/flows/dev/+page.svelte | 4 +- .../src/routes/test_dev/sdk_flow/+page.svelte | 5 +- 64 files changed, 899 insertions(+), 818 deletions(-) diff --git a/frontend/src/lib/components/Dev.svelte b/frontend/src/lib/components/Dev.svelte index d36a557b3d..4eececb8ef 100644 --- a/frontend/src/lib/components/Dev.svelte +++ b/frontend/src/lib/components/Dev.svelte @@ -17,7 +17,7 @@ } from '$lib/gen' import { inferArgs } from '$lib/infer' import { setCopilotInfo, userStore, workspaceStore } from '$lib/stores' - import { emptySchema, readFieldsRecursively, sendUserToast } from '$lib/utils' + import { emptySchema, readFieldsRecursively, sendUserToast, type StateStore } from '$lib/utils' import { Pane, Splitpanes } from 'svelte-splitpanes' import { onDestroy, onMount, setContext, untrack } from 'svelte' import DarkModeToggle from '$lib/components/sidebar/DarkModeToggle.svelte' @@ -27,7 +27,7 @@ import FlowModuleSchemaMap from './flows/map/FlowModuleSchemaMap.svelte' import FlowEditorPanel from './flows/content/FlowEditorPanel.svelte' import { deepEqual } from 'fast-equals' - import { writable, type Writable } from 'svelte/store' + import { writable } from 'svelte/store' import type { FlowState } from './flows/flowState' import { initHistory } from '$lib/history.svelte' import type { FlowEditorContext, FlowInput, FlowInputEditorState } from './flows/types' @@ -51,7 +51,6 @@ import { TestSteps } from './flows/testSteps.svelte' import { ModulesTestStates } from './modulesTest.svelte' import type { GraphModuleState } from './graph' - import { updateDerivedModuleStatesFromTestJobs } from './flows/utils' let flowCopilotContext: FlowCopilotContext = { shouldUpdatePropertyType: writable<{ @@ -116,7 +115,6 @@ const flowPreviewContent = $derived(flowPreviewButtons?.getFlowPreviewContent()) const job: Job | undefined = $derived(flowPreviewContent?.getJob()) let showJobStatus = $state(false) - let testModuleId: string | undefined = $state(undefined) type LastEditScript = { content: string @@ -453,7 +451,7 @@ } } - const flowStateStore = writable({} as FlowState) + const flowStateStore = $state({ val: {} }) as StateStore const previewArgsStore = $state({ val: {} }) const scriptEditorDrawer = writable(undefined) @@ -464,8 +462,6 @@ const triggersCount = writable(undefined) const modulesTestStates = new ModulesTestStates((moduleId) => { // Update the derived store with test job states - delete $derivedModuleStates[moduleId] - testModuleId = moduleId showJobStatus = false }) const outputPickerOpenFns: Record void> = $state({}) @@ -538,11 +534,11 @@ } mod.value.input_transforms = input_transforms - if (!deepEqual(schema, $flowStateStore[mod.id]?.schema)) { - if (!$flowStateStore[mod.id]) { - $flowStateStore[mod.id] = { schema } + if (!deepEqual(schema, flowStateStore.val[mod.id]?.schema)) { + if (!flowStateStore.val[mod.id]) { + flowStateStore.val[mod.id] = { schema } } else { - $flowStateStore[mod.id].schema = schema + flowStateStore.val[mod.id].schema = schema } reload++ } @@ -586,25 +582,12 @@ $selectedIdStore && untrack(() => inferModuleArgs($selectedIdStore)) }) - const localModuleStates: Writable> = $derived( - flowPreviewContent?.getLocalModuleStates() ?? writable({}) - ) + let localModuleStates: Record = $state({}) - const suspendStatus: Writable> = $derived( - flowPreviewContent?.getSuspendStatus() ?? writable({}) - ) + let suspendStatus: StateStore> = $state({ val: {} }) // Create a derived store that only shows the module states when showModuleStatus is true // this store can also be updated - let derivedModuleStates = writable>({}) - $effect(() => { - derivedModuleStates.update((currentStates) => { - return showJobStatus ? $localModuleStates : currentStates - }) - }) - $effect(() => { - updateDerivedModuleStatesFromTestJobs(testModuleId, modulesTestStates, derivedModuleStates) - }) let flowModuleSchemaMap: FlowModuleSchemaMap | undefined = $state() function onJobDone() { @@ -639,14 +622,9 @@ } function resetModulesStates() { - derivedModuleStates.set({}) showJobStatus = false } - const individualStepTests = $derived( - !(showJobStatus && job) && Object.keys($derivedModuleStates).length > 0 - ) - const flowHasChanged = $derived(flowPreviewContent?.flowHasChanged()) @@ -785,7 +763,7 @@ bind:this={flowPreviewButtons} {onJobDone} onRunPreview={() => { - localModuleStates.set({}) + localModuleStates = {} showJobStatus = true }} /> @@ -800,19 +778,20 @@ disableTutorials smallErrorHandler={true} disableStaticInputs - localModuleStates={derivedModuleStates} + {localModuleStates} onTestUpTo={flowPreviewButtons?.testUpTo} + testModuleStates={modulesTestStates} isOwner={flowPreviewContent?.getIsOwner?.()} onTestFlow={flowPreviewButtons?.runPreview} isRunning={flowPreviewContent?.getIsRunning?.()} onCancelTestFlow={flowPreviewContent?.cancelTest} onOpenPreview={flowPreviewButtons?.openPreview} onHideJobStatus={resetModulesStates} - {individualStepTests} flowJob={job} {showJobStatus} onDelete={(id) => { - delete $derivedModuleStates[id] + delete localModuleStates[id] + delete modulesTestStates.states[id] }} {flowHasChanged} /> diff --git a/frontend/src/lib/components/FirstStepInputs.svelte b/frontend/src/lib/components/FirstStepInputs.svelte index bd71e6a4b3..775dccdda8 100644 --- a/frontend/src/lib/components/FirstStepInputs.svelte +++ b/frontend/src/lib/components/FirstStepInputs.svelte @@ -19,7 +19,7 @@ let mod: any | undefined = $state(undefined) async function loadSchema() { try { - const res = await getFirstStepSchema($flowStateStore, flowStore.val) + const res = await getFirstStepSchema(flowStateStore.val, flowStore.val) schema = res.schema mod = res.mod dispatch('connectFirstNode', { connectFirstNode: res.connectFirstNode }) @@ -28,7 +28,7 @@ } } $effect(() => { - flowStore.val && $flowStateStore && untrack(() => loadSchema()) + flowStore.val && flowStateStore && untrack(() => loadSchema()) }) function handleClick() { diff --git a/frontend/src/lib/components/FlowBuilder.svelte b/frontend/src/lib/components/FlowBuilder.svelte index 0eb3802a40..78a0363012 100644 --- a/frontend/src/lib/components/FlowBuilder.svelte +++ b/frontend/src/lib/components/FlowBuilder.svelte @@ -25,6 +25,7 @@ orderedJsonStringify, readFieldsRecursively, replaceFalseWithUndefined, + type StateStore, type Value } from '$lib/utils' import { sendUserToast } from '$lib/toast' @@ -33,7 +34,7 @@ import AIChangesWarningModal from '$lib/components/copilot/chat/flow/AIChangesWarningModal.svelte' import { onMount, setContext, untrack, type ComponentType } from 'svelte' - import { writable, type Writable } from 'svelte/store' + import { writable } from 'svelte/store' import CenteredPage from './CenteredPage.svelte' import { Badge, Button, UndoRedo } from './common' import FlowEditor from './flows/FlowEditor.svelte' @@ -42,7 +43,7 @@ import FlowImportExportMenu from './flows/header/FlowImportExportMenu.svelte' import FlowPreviewButtons from './flows/header/FlowPreviewButtons.svelte' import type { FlowEditorContext, FlowInput, FlowInputEditorState } from './flows/types' - import { cleanInputs, updateDerivedModuleStatesFromTestJobs } from './flows/utils' + import { cleanInputs } from './flows/utils' import { Calendar, Pen, @@ -577,13 +578,7 @@ } let insertButtonOpen = writable(false) - let testModuleId: string | undefined = $state(undefined) - let modulesTestStates = new ModulesTestStates((moduleId) => { - // Update the derived store with test job states - delete $derivedModuleStates[moduleId] - testModuleId = moduleId - showJobStatus = false - }) + let modulesTestStates = new ModulesTestStates() let outputPickerOpenFns: Record void> = $state({}) let flowEditor: FlowEditor | undefined = $state(undefined) @@ -933,33 +928,8 @@ } } - const localModuleStates: Writable> = $derived( - flowPreviewContent?.getLocalModuleStates() ?? writable({}) - ) - const suspendStatus: Writable> = $derived( - flowPreviewContent?.getSuspendStatus() ?? writable({}) - ) - - // Create a derived store that only shows the module states when showModuleStatus is true - // this store can also be updated - let derivedModuleStates = writable>({}) - $effect(() => { - derivedModuleStates.update((currentStates) => { - return showJobStatus ? $localModuleStates : currentStates - }) - }) - $effect(() => { - updateDerivedModuleStatesFromTestJobs(testModuleId, modulesTestStates, derivedModuleStates) - }) - - function resetModulesStates() { - derivedModuleStates.set({}) - showJobStatus = false - } - - const individualStepTests = $derived( - !(showJobStatus && job) && Object.keys($derivedModuleStates).length > 0 - ) + let localModuleStates: Record = $state({}) + let suspendStatus: StateStore> = $state({ val: {} }) const flowHasChanged = $derived(flowPreviewContent?.flowHasChanged()) @@ -1025,7 +995,7 @@ for (const mod of restoredModules) { if (mod) { try { - loadFlowModuleState(mod).then((state) => ($flowStateStore[mod.id] = state)) + loadFlowModuleState(mod).then((state) => (flowStateStore.val[mod.id] = state)) } catch (e) { console.error('Error loading state for restored node', e) } @@ -1155,10 +1125,12 @@ showCaptureHint.set(true) }} {onJobDone} + bind:localModuleStates bind:this={flowPreviewButtons} {loading} onRunPreview={() => { - localModuleStates.set({}) + modulesTestStates.hideJobsInGraph() + localModuleStates = {} showJobStatus = true }} /> @@ -1185,7 +1157,7 @@ - {#if $flowStateStore} + {#if flowStateStore} aiChatManager.toggleOpen()} onOpenPreview={flowPreviewButtons?.openPreview} - localModuleStates={derivedModuleStates} + localModuleStates={showJobStatus ? localModuleStates : {}} + {showJobStatus} + testModuleStates={modulesTestStates} isOwner={flowPreviewContent?.getIsOwner()} onTestFlow={flowPreviewButtons?.runPreview} isRunning={flowPreviewContent?.getIsRunning()} onCancelTestFlow={flowPreviewContent?.cancelTest} - onHideJobStatus={resetModulesStates} - {individualStepTests} + onHideJobStatus={() => { + modulesTestStates.hideJobsInGraph() + showJobStatus = false + }} {job} {suspendStatus} - {showJobStatus} onDelete={(id) => { - delete $derivedModuleStates[id] + delete localModuleStates[id] + delete modulesTestStates.states[id] }} {flowHasChanged} /> diff --git a/frontend/src/lib/components/FlowLogViewer.svelte b/frontend/src/lib/components/FlowLogViewer.svelte index 369578ff17..ef9ab3a863 100644 --- a/frontend/src/lib/components/FlowLogViewer.svelte +++ b/frontend/src/lib/components/FlowLogViewer.svelte @@ -21,13 +21,12 @@ import FlowJobsMenu from './flows/map/FlowJobsMenu.svelte' import BarsStaggered from './icons/BarsStaggered.svelte' import type { GraphModuleState } from './graph/model' - import type { Writable } from 'svelte/store' type RootJobData = Partial interface Props { modules: FlowModule[] - localModuleStates: Writable> + localModuleStates: Record rootJob: RootJobData flowStatus: FlowStatusModule['type'] | undefined expandedRows: Record @@ -122,7 +121,7 @@ } function hasEmptySubflow(stepId: string, stepType: FlowModuleValue['type'] | undefined): boolean { - const state = $localModuleStates[stepId] + const state = localModuleStates[stepId] if (!state || !stepType) return false return ( @@ -172,7 +171,7 @@ } // Check if this entry itself has an error (but don't flag it - only its parents) - const stepStatus = $localModuleStates[module.id]?.type + const stepStatus = localModuleStates[module.id]?.type if (stepStatus === 'Failure') { currentEntryHasError = true // Don't add the entry itself to parentsWithErrors @@ -402,7 +401,7 @@ {#if modules.length > 0} {#each modules as module (module.id)} {@const isLeafStep = !hasSubflows(module)} - {@const status = $localModuleStates[module.id]?.type} + {@const status = localModuleStates[module.id]?.type} {@const isRunning = status === 'InProgress' || status === 'WaitingForExecutor'} {@const hasEmptySubflowValue = hasEmptySubflow(module.id, module.value.type)} {@const isCollapsible = !hasEmptySubflowValue} @@ -479,7 +478,7 @@ {/if} - {#if !hasEmptySubflowValue && $localModuleStates[module.id]?.flow_jobs && (module.value.type === 'forloopflow' || module.value.type === 'whileloopflow')} + {#if !hasEmptySubflowValue && localModuleStates[module.id]?.flow_jobs && (module.value.type === 'forloopflow' || module.value.type === 'whileloopflow')} @@ -488,18 +487,18 @@ moduleId={module.id} id={module.id} {onSelectedIteration} - flowJobsSuccess={$localModuleStates[module.id] + flowJobsSuccess={localModuleStates[module.id] ?.flow_jobs_success} - flowJobs={$localModuleStates[module.id]?.flow_jobs} - selected={$localModuleStates[module.id]?.selectedForloopIndex ?? + flowJobs={localModuleStates[module.id]?.flow_jobs} + selected={localModuleStates[module.id]?.selectedForloopIndex ?? 0} - selectedManually={$localModuleStates[module.id] + selectedManually={localModuleStates[module.id] ?.selectedForLoopSetManually ?? false} showIcon={false} /> {#if module.value.type === 'forloopflow'} - {`/${$localModuleStates[module.id]?.iteration_total ?? 0}`} + {`/${localModuleStates[module.id]?.iteration_total ?? 0}`} {/if} {/if} @@ -507,7 +506,7 @@ {#if isLeafStep} - {@const jobId = $localModuleStates[module.id]?.job_id} + {@const jobId = localModuleStates[module.id]?.job_id} {#if isCollapsible && isExpanded(module.id, isRunning)} - {@const args = $localModuleStates[module.id]?.args} - {@const logs = $localModuleStates[module.id]?.logs} - {@const result = $localModuleStates[module.id]?.result} - {@const jobId = $localModuleStates[module.id]?.job_id} + {@const args = localModuleStates[module.id]?.args} + {@const logs = localModuleStates[module.id]?.logs} + {@const result = localModuleStates[module.id]?.result} + {@const jobId = localModuleStates[module.id]?.job_id}
{#each getSubflows(module) as subflow} {@const subflowJob = { id: jobId, type: - $localModuleStates[module.id]?.type === 'Failure' || - $localModuleStates[module.id]?.type === 'Success' + localModuleStates[module.id]?.type === 'Failure' || + localModuleStates[module.id]?.type === 'Success' ? 'CompletedJob' : ('QueuedJob' as Job['type']), logs, result, args, - success: $localModuleStates[module.id]?.type === 'Success' + success: localModuleStates[module.id]?.type === 'Success' }}
@@ -545,7 +544,7 @@ modules={subflow.modules} {localModuleStates} rootJob={subflowJob} - flowStatus={$localModuleStates[module.id]?.type} + flowStatus={localModuleStates[module.id]?.type} {expandedRows} {allExpanded} {showResultsInputs} diff --git a/frontend/src/lib/components/FlowLogViewerWrapper.svelte b/frontend/src/lib/components/FlowLogViewerWrapper.svelte index 3d5c09f460..f6d74651eb 100644 --- a/frontend/src/lib/components/FlowLogViewerWrapper.svelte +++ b/frontend/src/lib/components/FlowLogViewerWrapper.svelte @@ -1,6 +1,5 @@ { - let { job } = detail + onJobsLoaded={({ job, force }) => { if (job.script_path != lastScriptPath && job.script_path) { lastScriptPath = job.script_path loadOwner(lastScriptPath ?? '') } - dispatch('jobsLoaded', job) + onJobsLoaded?.({ job, force }) }} globalModuleStates={[]} - globalDurationStatuses={[]} - {localModuleStates} - {localDurationStatuses} + {globalIterationBounds} + bind:localModuleStates bind:selectedNode={selectedJobStep} - on:start - on:done + bind:localDurationStatuses + {onStart} + {onDone} bind:job {initialJob} {jobId} @@ -122,4 +139,6 @@ {customUi} graphTabOpen={true} isNodeSelected={true} + {refreshGlobal} + {updateGlobalRefresh} /> diff --git a/frontend/src/lib/components/FlowStatusViewerInner.svelte b/frontend/src/lib/components/FlowStatusViewerInner.svelte index 6800d7583d..dfeb72d272 100644 --- a/frontend/src/lib/components/FlowStatusViewerInner.svelte +++ b/frontend/src/lib/components/FlowStatusViewerInner.svelte @@ -8,18 +8,24 @@ type FlowStatus, type FlowModuleValue, type FlowModule, - ResourceService + ResourceService, + type CompletedJob } from '$lib/gen' import { workspaceStore } from '$lib/stores' import { base } from '$lib/base' import FlowJobResult from './FlowJobResult.svelte' import DisplayResult from './DisplayResult.svelte' - import { createEventDispatcher, getContext, setContext, tick, untrack } from 'svelte' + import { getContext, setContext, tick, untrack } from 'svelte' import { onDestroy } from 'svelte' import { Badge, Button, Skeleton, Tab } from './common' import Tabs from './common/tabs/Tabs.svelte' - import { type DurationStatus, type FlowStatusViewerContext, type GraphModuleState } from './graph' + import { + type DurationStatus, + type FlowStatusViewerContext, + type GlobalIterationBounds, + type GraphModuleState + } from './graph' import ModuleStatus from './ModuleStatus.svelte' import { clone, isScriptPreview, msToSec, readFieldsRecursively, truncateRev } from '$lib/utils' import JobArgs from './JobArgs.svelte' @@ -27,7 +33,6 @@ import { deepEqual } from 'fast-equals' import FlowTimeline from './FlowTimeline.svelte' import { dfs } from './flows/dfs' - import { get, writable, type Unsubscriber, type Writable } from 'svelte/store' import Alert from './common/alert/Alert.svelte' import FlowGraphViewerStep from './FlowGraphViewerStep.svelte' import FlowGraphV2 from './graph/FlowGraphV2.svelte' @@ -38,8 +43,7 @@ import type { FlowGraphAssetContext } from './flows/types' import { createState } from '$lib/svelte5Utils.svelte' import JobLoader from './JobLoader.svelte' - - const dispatch = createEventDispatcher() + import { writable } from 'svelte/store' let { flowStateStore, @@ -67,27 +71,30 @@ | undefined //only useful when forloops are optimized and the job doesn't contain the mod id anymore innerModule?: FlowModuleValue | undefined - globalRefreshes?: Record Promise> render?: boolean isOwner?: boolean selectedNode?: string | undefined - globalModuleStates: Writable>[] - globalDurationStatuses: Writable>[] - childFlow?: boolean + globalModuleStates: Record[] + globalDurationStatuses?: Record[] + globalIterationBounds: Record + isSelectedBranch?: boolean isSubflow?: boolean reducedPolling?: boolean wideResults?: boolean hideFlowResult?: boolean + topModuleStates?: Record workspace?: string | undefined prefix?: string | undefined - subflowParentsGlobalModuleStates?: Writable>[] - subflowParentsDurationStatuses?: Writable>[] + subflowParentsGlobalModuleStates?: Record[] + subflowParentsDurationStatuses?: Record[] isForloopSelected?: boolean - parentRecursiveRefresh?: Record Promise> + updateRecursiveRefreshFn?: (jobId: string, updateFn: (clear, root) => Promise) => void + refreshGlobal: (moduleId: string, clear: boolean, root: string) => Promise + updateGlobalRefresh: (moduleId: string, updateFn: (clear, root) => Promise) => void job?: (Job & { result_stream?: string }) | undefined rightColumnSelect?: 'timeline' | 'node_status' | 'node_definition' | 'user_states' - localModuleStates?: Writable> - localDurationStatuses?: Writable> + localModuleStates?: Record + localDurationStatuses?: Record onResultStreamUpdate?: ({ jobId, result_stream @@ -101,6 +108,9 @@ graphTabOpen: boolean isNodeSelected: boolean loadExtraLogs?: (logs: string) => void + onStart?: () => void + onJobsLoaded?: ({ job, force }: { job: Job; force: boolean }) => void + onDone?: ({ job }: { job: CompletedJob }) => void } let { @@ -109,34 +119,42 @@ workspaceId = undefined, flowJobIds = undefined, innerModule = undefined, - globalRefreshes = $bindable({}), render = true, isOwner = false, selectedNode = $bindable(undefined), globalModuleStates, - globalDurationStatuses, - childFlow = false, + globalDurationStatuses = [], + globalIterationBounds, + updateRecursiveRefreshFn = undefined, + isSelectedBranch = true, isSubflow = false, reducedPolling = false, wideResults = false, hideFlowResult = false, workspace = $workspaceStore, prefix = undefined, + topModuleStates = undefined, + refreshGlobal, + updateGlobalRefresh, subflowParentsGlobalModuleStates = [], subflowParentsDurationStatuses = [], isForloopSelected = false, - parentRecursiveRefresh = $bindable({}), job = $bindable(undefined), rightColumnSelect = $bindable('timeline'), - localModuleStates = writable({}), - localDurationStatuses = writable({}), + localModuleStates = $bindable({}), + localDurationStatuses = $bindable({}), customUi, onResultStreamUpdate = undefined, graphTabOpen, isNodeSelected, - loadExtraLogs = undefined + loadExtraLogs = undefined, + onStart = undefined, + onJobsLoaded = undefined, + onDone = undefined }: Props = $props() + let getTopModuleStates = $derived(topModuleStates ?? localModuleStates) + let resultStreams: Record = $state({}) if (onResultStreamUpdate == undefined) { @@ -152,6 +170,14 @@ } let recursiveRefresh: Record Promise> = $state({}) + let updateRecursiveRefreshInner = ( + childJobId: string, + updateFn: (clear, root) => Promise + ) => { + if (childJobId) { + recursiveRefresh[childJobId] = updateFn + } + } // Add support for the input args assets shown as an asset node const _flowGraphAssetsCtx = getContext('FlowGraphAssetContext') @@ -190,81 +216,79 @@ let expandedSubflows: Record = $state({}) - let selectedId: Writable = writable(selectedNode) + let selectedId = writable(selectedNode) function onFlowModuleId() { - if (globalRefreshes) { - let modId = flowJobIds?.moduleId - if (modId) { - globalRefreshes[buildSubflowKey(modId, prefix)] = async (clear, root) => { - await refresh(clear, root) // refresh(true, loopJob) - } - } + let modId = flowJobIds?.moduleId + if (modId) { + let prefixedId = buildSubflowKey(modId, prefix) + updateGlobalRefresh(prefixedId, async (clear, root) => { + // console.debug('updateGlobalRefreshInner refresh', prefixedId, clear, root) + await refresh(clear, root) // refresh(true, loopJob) + }) } } function updateModuleStates( - moduleState: Writable>, + moduleState: Record, key: string, newValue: GraphModuleState, keepType: boolean | undefined ) { - const state = get(moduleState) if ( newValue.selectedForloop != undefined && - state[key]?.selectedForloop != undefined && - newValue.selectedForloop != state[key].selectedForloop + moduleState[key]?.selectedForloop != undefined && + newValue.selectedForloop != moduleState[key].selectedForloop ) { + let newState = { ...moduleState[key] } if ( newValue.type == 'InProgress' && - state[key]?.type != 'InProgress' && - !(keepType && (state[key]?.type === 'Success' || state[key]?.type === 'Failure')) + moduleState[key]?.type != 'InProgress' && + !( + keepType && + (moduleState[key]?.type === 'Success' || moduleState[key]?.type === 'Failure') + ) ) { - moduleState.update((state) => { - state[key].type = 'InProgress' - return state - }) + newState.type = 'InProgress' + } else if (['Success', 'Failure'].includes(newValue.type)) { + newState.type = newValue.type } if ( - state[key]?.job_id != newValue.job_id || - !deepEqual(state[key]?.args, newValue.args) || - !deepEqual(state[key]?.result, newValue.result) + moduleState[key]?.job_id != newValue.job_id || + !deepEqual(moduleState[key]?.args, newValue.args) || + !deepEqual(moduleState[key]?.result, newValue.result) ) { - moduleState.update((state) => { - state[key].args = newValue.args - state[key].result = newValue.result - state[key].job_id = newValue.job_id - return state - }) + newState.args = newValue.args + newState.result = newValue.result + newState.job_id = newValue.job_id } + moduleState[key] = newState return } - if (state[key]?.selectedForLoopSetManually) { + if (moduleState[key]?.selectedForLoopSetManually) { if ( newValue.selectedForloop != undefined && - state[key]?.selectedForloop != newValue.selectedForloop + moduleState[key]?.selectedForloop != newValue.selectedForloop ) { - return state + return moduleState } else { newValue.selectedForLoopSetManually = true - newValue.selectedForloopIndex = state[key]?.selectedForloopIndex - newValue.selectedForloop = state[key]?.selectedForloop + newValue.selectedForloopIndex = moduleState[key]?.selectedForloopIndex + newValue.selectedForloop = moduleState[key]?.selectedForloop } - } else if (state[key]?.selectedForloopIndex != undefined) { - newValue.selectedForloopIndex = state[key]?.selectedForloopIndex - newValue.selectedForloop = state[key]?.selectedForloop + } else if (moduleState[key]?.selectedForloopIndex != undefined) { + newValue.selectedForloopIndex = moduleState[key]?.selectedForloopIndex + newValue.selectedForloop = moduleState[key]?.selectedForloop } - if (keepType && (state[key]?.type == 'Success' || state[key]?.type == 'Failure')) { - newValue.type = state[key].type + if (keepType && (moduleState[key]?.type == 'Success' || moduleState[key]?.type == 'Failure')) { + newValue.type = moduleState[key].type } - if (!deepEqual(state[key], newValue)) { - moduleState.update((state) => { - state[key] = newValue - return state - }) + if (!deepEqual(moduleState[key], newValue)) { + // console.debug('updateModuleStates 2', key, $state.snapshot(moduleState)) + moduleState[key] = newValue } } @@ -273,69 +297,72 @@ } async function refresh(clearLoop: boolean, rootJob: string | undefined) { + console.debug('refresh', clearLoop, rootJob) let modId = flowJobIds?.moduleId + let topModuleStates = getTopModuleStates if (clearLoop) { if (!rootJob) { - let topLevelModuleStates = globalModuleStates?.[globalModuleStates?.length - 1] - if (modId) { - topLevelModuleStates?.update((x) => { - if (modId) { - delete x[modId] - } - return x - }) + if (modId && topModuleStates) { + let prefixedId = buildSubflowKey(modId, prefix) + delete topModuleStates[prefixedId] // TODO: this is not working } if (subflowParentsGlobalModuleStates.length > 0) { - subflowParentsGlobalModuleStates?.[subflowParentsGlobalModuleStates?.length - 1]?.update( - (x) => { - for (let mod of innerModules ?? []) { - if (mod.id) { - delete x[buildSubflowKey(mod.id, prefix)] - } - } + let subflowModuleStates = + subflowParentsGlobalModuleStates?.[subflowParentsGlobalModuleStates?.length - 1] - return x + for (let mod of innerModules ?? []) { + if (mod.id) { + delete subflowModuleStates[buildSubflowKey(mod.id, prefix)] } - ) + } } else { - topLevelModuleStates?.update((x) => { - for (let mod of innerModules ?? []) { - if (mod.id) { - delete x[mod.id] - } + for (let mod of innerModules ?? []) { + if (mod.id && topModuleStates) { + let prefixedId = buildSubflowKey(mod.id, prefix) + delete topModuleStates[prefixedId] } - - return x - }) + } } } } else { - let state = modId ? getTopModuleStates()?.[modId] : undefined + let state = modId ? topModuleStates?.[buildSubflowKey(modId, prefix)] : undefined let loopjob = state?.selectedForloop let njob = flowJobIds && modId && loopjob ? storedListJobs?.[loopjob] : job + if (njob) { - dispatch('jobsLoaded', { job: njob, force: true }) + onJobsLoaded?.({ job: njob, force: true }) } } - for (let [k, rec] of Object.entries(recursiveRefresh)) { - if (rootJob != undefined && rootJob != k) { - continue - } + let callRec = async (rec: (clear, root) => Promise) => { await tick() await rec(clearLoop, undefined) } + if (rootJob) { + let rec = recursiveRefresh[rootJob] + if (rec) { + await callRec(rec) + // console.debug('refresh recursive 1', rec) + } else { + // console.debug('refresh recursive no rec', rootJob) + } + } else { + for (let rec of Object.values(recursiveRefresh)) { + await callRec(rec) + // console.debug('refresh recursive 2', rec) + } + } } function updateRecursiveRefresh(jobId: string) { if (jobId) { - parentRecursiveRefresh[jobId] = async (clear, root) => { + updateRecursiveRefreshFn?.(jobId, async (clear, root) => { if (globalModuleStates.length > 0 || isSubflow) { await refresh(clear, root) } - } + }) } } @@ -345,67 +372,55 @@ force?: boolean, keepType?: boolean ) { - let newValue = { ...($localModuleStates[key] ?? {}), ...value } - if (!deepEqual($localModuleStates[key], value) || force) { + let newValue = { ...(localModuleStates[key] ?? {}), ...value } + if (!deepEqual(localModuleStates[key], value) || force) { + // console.debug('setModuleState', key, force, keepType, $state.snapshot(value)) ;[localModuleStates, ...globalModuleStates].forEach((s) => { updateModuleStates(s, key, newValue, keepType) }) if (prefix) { - subflowParentsGlobalModuleStates.forEach((s) => - updateModuleStates(s, buildSubflowKey(key, prefix), newValue, keepType) - ) + let prefixedId = buildSubflowKey(key, prefix) + subflowParentsGlobalModuleStates.forEach((s) => { + updateModuleStates(s, prefixedId, newValue, keepType) + }) } } } function setDurationStatusByJob(key: string, id: string, value: any) { - if (!deepEqual($localDurationStatuses[key]?.byJob[id], value)) { - $localDurationStatuses[key].byJob[id] = value + if (!deepEqual(localDurationStatuses[key]?.byJob[id], value)) { + localDurationStatuses[key].byJob[id] = value globalDurationStatuses.forEach((s) => { - s.update((x) => { - x[key].byJob[id] = value - - return x - }) + s[key].byJob[id] = value }) if (prefix) { subflowParentsDurationStatuses.forEach((s) => { - s.update((x) => { - x[buildSubflowKey(key, prefix)].byJob[id] = value - return x - }) + s[buildSubflowKey(key, prefix)].byJob[id] = value }) } } } function initializeByJob(modId: string) { - if ($localDurationStatuses[modId] == undefined) { - $localDurationStatuses[modId] = { byJob: {} } + if (localDurationStatuses[modId] == undefined) { + localDurationStatuses[modId] = { byJob: {} } } - globalDurationStatuses.forEach((x) => - x.update((x) => { - if (x[modId] == undefined) { - x[modId] = { byJob: {} } - } - return x - }) - ) + globalDurationStatuses.forEach((x) => { + if (x[modId] == undefined) { + x[modId] = { byJob: {} } + } + }) if (prefix) { - subflowParentsDurationStatuses.forEach((x) => - x.update((x) => { - let key = buildSubflowKey(modId, prefix) - if (x[key] == undefined) { - x[key] = { byJob: {} } - } - return x - }) - ) + subflowParentsDurationStatuses.forEach((x) => { + let key = buildSubflowKey(modId, prefix) + if (x[key] == undefined) { + x[key] = { byJob: {} } + } + }) } } - let innerModules: FlowStatusModule[] = $state([]) - + let innerModules = $state(undefined) as FlowStatusModule[] | undefined function updateStatus(status: FlowStatus) { innerModules = status?.modules?.concat( @@ -418,29 +433,27 @@ let count = status.retry?.fail_count if (count) { - $retryStatus[jobId ?? ''] = count - } else if ($retryStatus[jobId ?? ''] != undefined) { - delete $retryStatus[jobId ?? ''] - $retryStatus = $retryStatus + retryStatus.val[jobId ?? ''] = count + } else if (retryStatus.val[jobId ?? ''] != undefined) { + delete retryStatus.val[jobId ?? ''] } let jobStatus = job?.flow_status?.modules?.[job?.flow_status.step] if (jobStatus && jobStatus.count != undefined) { - $suspendStatus[jobId ?? ''] = { nb: jobStatus.count, job: job! } - } else if ($suspendStatus[jobId ?? ''] != undefined) { - delete $suspendStatus[jobId ?? ''] - $suspendStatus = $suspendStatus + suspendStatus.val[jobId ?? ''] = { nb: jobStatus.count, job: job! } + } else if (suspendStatus.val[jobId ?? ''] != undefined) { + delete suspendStatus.val[jobId ?? ''] } } function updateInnerModules() { - if ($localModuleStates) { - innerModules.forEach((mod, i) => { + if (localModuleStates) { + innerModules?.forEach((mod, i) => { if (mod.type === 'WaitingForEvents' && innerModules?.[i - 1]?.type === 'Success') { setModuleState(mod.id ?? '', { type: mod.type, args: job?.args, tag: job?.tag }) } else if ( mod.type === 'WaitingForExecutor' && - $localModuleStates[mod.id ?? '']?.scheduled_for == undefined + localModuleStates[mod.id ?? '']?.scheduled_for == undefined ) { JobService.getJob({ workspace: workspaceId ?? $workspaceStore ?? '', @@ -466,7 +479,7 @@ } else if ( (mod.flow_jobs || mod.branch_chosen) && (mod.type == 'Success' || mod.type == 'Failure') && - !['Success', 'Failure'].includes($localModuleStates?.[mod.id ?? '']?.type) + !['Success', 'Failure'].includes(localModuleStates?.[mod.id ?? '']?.type) ) { let branchChosen = mod.branch_chosen ? { @@ -474,6 +487,7 @@ mod.branch_chosen.type == 'default' ? 0 : (mod.branch_chosen.branch ?? 0) + 1 } : {} + console.debug('updateInnerModules', mod.id, mod.type, branchChosen) setModuleState( mod.id ?? '', { @@ -486,9 +500,11 @@ setModuleState(mod.id ?? '', {}, true) } - if (mod.flow_jobs_success) { + if (mod.flow_jobs_success || mod.flow_jobs) { setModuleState(mod.id ?? '', { - flow_jobs_success: mod.flow_jobs_success + flow_jobs_success: mod.flow_jobs_success, + flow_jobs: mod.flow_jobs, + iteration_total: mod.iterator?.itered?.length ?? mod.flow_jobs?.length }) } }) @@ -519,20 +535,20 @@ let jobLoader: JobLoader | undefined = undefined function setJob(newJob: Job, force: boolean) { - if (!deepEqual(job, newJob) || isForloopSelected || force) { + if (!deepEqual(job, newJob) || isForloopSelected || force || innerModules == undefined) { job = newJob job?.flow_status && updateStatus(job?.flow_status) - dispatch('jobsLoaded', { job, force: false }) + onJobsLoaded?.({ job, force: false }) notAnonynmous = false if (job?.type == 'CompletedJob' && !destroyed) { - dispatch('done', job) + onDone?.({ job }) } } } async function loadJobInProgress() { if (!started) { started = true - dispatch('start') + onStart?.() } if (jobId != '00000000-0000-0000-0000-000000000000') { try { @@ -577,68 +593,46 @@ updateRecursiveRefresh(jobId) async function updateJobId() { - if (jobId !== job?.id) { - $localModuleStates = {} + if (jobId !== job?.id || innerModules == undefined) { + localModuleStates = {} flowTimeline?.reset() timeout && clearTimeout(timeout) - innerModules = [] + innerModules = undefined + console.log('updateJobId', jobId) if (flowJobIds) { let modId = flowJobIds?.moduleId ?? '' - let common = { + if (localDurationStatuses[modId] == undefined) { + localDurationStatuses[modId] = { byJob: {} } + } + let prefixed = buildSubflowKey(modId, prefix) + globalDurationStatuses.forEach((x) => { + if (x[prefixed] == undefined) { + x[prefixed] = { byJob: {} } + } + }) + + globalIterationBounds[prefixed] = { iteration_from: flowJobIds?.branchall ? 0 : Math.max(flowJobIds.flowJobs.length - 20, 0), - iteration_total: $localDurationStatuses?.[modId]?.iteration_total ?? flowJobIds?.length + iteration_total: flowJobIds?.length } - $localDurationStatuses[modId] = { - ...($localDurationStatuses[modId] ?? { byJob: {} }), - ...common - } - let prefixed = modId - globalDurationStatuses.forEach((x) => - x.update((x) => { - x[prefixed] = { ...(x[prefixed] ?? { byJob: {} }), ...common } - return x - }) - ) } else { - updateRecursiveRefresh(jobId) recursiveRefresh = {} - $localDurationStatuses = {} + localDurationStatuses = {} + updateRecursiveRefresh(jobId) } await loadJobInProgress() } } - function getTopModuleStates() { - return get(globalModuleStates?.[globalModuleStates?.length - 1]) - } - - let forloop_selected = $state(getTopModuleStates()?.[flowJobIds?.moduleId ?? '']?.selectedForloop) - - let sub: Unsubscriber | undefined = undefined - let timeoutForloopSelectedSub: NodeJS.Timeout | undefined = undefined - let timeoutForloopSelected: NodeJS.Timeout | undefined = undefined - - function onModuleIdChange() { - clearTimeout(timeoutForloopSelectedSub) - timeoutForloopSelectedSub = setTimeout(() => { - sub?.() - sub = globalModuleStates?.[globalModuleStates?.length - 1].subscribe((x) => { - const newForloopSelected = x[flowJobIds?.moduleId ?? '']?.selectedForloop - if (newForloopSelected != forloop_selected) { - clearTimeout(timeoutForloopSelected) - timeoutForloopSelected = setTimeout(() => { - forloop_selected = newForloopSelected - }, 200) - } - }) - }, 200) - } + let forloop_selected = $state( + getTopModuleStates?.[buildSubflowKey(flowJobIds?.moduleId ?? '', prefix)]?.selectedForloop + ) onDestroy(() => { destroyed = true timeout && clearTimeout(timeout) - sub?.() + // sub?.() }) function isSuccess(arg: any): boolean | undefined { @@ -649,25 +643,25 @@ } } - function onJobsLoaded(mod: FlowStatusModule, job: Job, force?: boolean): void { - if (mod.id && (mod.flow_jobs ?? []).length == 0) { - if (!childFlow) { - if ($flowStateStore?.[mod.id]) { - $flowStateStore[mod.id] = { - ...$flowStateStore[mod.id], - previewResult: job['result'], - previewArgs: job.args, - previewJobId: job.id, - previewWorkspaceId: job.workspace_id, - previewSuccess: job['success'] - } + function onJobsLoadedInner(mod: FlowStatusModule, job: Job, force?: boolean): void { + let id = mod.id + if (id && ((mod.flow_jobs ?? []).length == 0 || force)) { + // console.debug('onJobsLoadedInner', id, job.id, force) + if (flowStateStore) { + flowStateStore[buildSubflowKey(id, prefix)] = { + ...(flowStateStore?.[buildSubflowKey(id, prefix)] ?? {}), + previewResult: job['result'], + previewArgs: job.args, + previewJobId: job.id, + previewSuccess: job['success'] } } - initializeByJob(mod.id) + + initializeByJob(id) let started_at = job.started_at ? new Date(job.started_at).getTime() : undefined if (job.type == 'QueuedJob') { setModuleState( - mod.id, + id, { type: 'InProgress', job_id: job.id, @@ -679,7 +673,7 @@ }, force ) - setDurationStatusByJob(mod.id, job.id, { + setDurationStatusByJob(id, job.id, { created_at: job.created_at ? new Date(job.created_at).getTime() : undefined, started_at }) @@ -687,10 +681,10 @@ const parent_module = mod['parent_module'] // Delete existing failure node attached to the same parent module - removeFailureNode(mod.id, parent_module) + removeFailureNode(id, parent_module) setModuleState( - mod.id, + id, { args: job.args, type: job['success'] ? 'Success' : 'Failure', @@ -706,12 +700,12 @@ iteration_total: mod.iterator?.itered?.length, retries: mod?.failed_retries?.length, skipped: mod.skipped - // retries: $flowStateStore?.raw_flow + // retries: flowStateStore?.raw_flow }, force ) - setDurationStatusByJob(mod.id, job.id, { + setDurationStatusByJob(id, job.id, { created_at: job.created_at ? new Date(job.created_at).getTime() : undefined, started_at, duration_ms: job['duration_ms'] @@ -728,12 +722,13 @@ isForloop: boolean ) { if (modId) { - let globalState = globalModuleStates?.[globalModuleStates?.length - 1] - let globalStateGet = globalState ? get(globalState) : undefined - let state = globalStateGet?.[modId] + let prefixedId = buildSubflowKey(modId, prefix) + + let topModuleStates = getTopModuleStates + let state = topModuleStates?.[prefixedId] if (clicked && state?.selectedForloop) { - await globalRefreshes?.[modId]?.(true, state.selectedForloop) + await refreshGlobal?.(prefixedId, true, state.selectedForloop) } let manualOnce = state?.selectedForLoopSetManually if ( @@ -743,7 +738,7 @@ ) { let setManually = clicked || manualOnce - let newState = { + let newState: Partial = { ...(state ?? {}), selectedForloop: id, selectedForloopIndex: j, @@ -754,21 +749,20 @@ id != state?.selectedForloop || j != state?.selectedForloopIndex || setManually != state?.selectedForLoopSetManually + // console.debug('setIteration', selectedNotEqual, state, topModuleStates) if (selectedNotEqual) { - globalState?.update((topLevelModuleStates) => { - topLevelModuleStates[modId] = { + if (topModuleStates) { + topModuleStates[prefixedId] = { type: 'WaitingForPriorSteps', args: {}, ...newState } - return topLevelModuleStates - // clicked && callGlobRefresh(modId, {index: j, job: id, selectedManually: setManually ?? false}) - }) + } } } if (clicked) { - await globalRefreshes?.[modId]?.(false, id) + await refreshGlobal?.(prefixedId, false, id) } } } @@ -785,28 +779,33 @@ innerModule?.type == 'forloopflow' || innerModule?.type == 'whileloopflow' ) - if ($flowStateStore && $flowStateStore?.[modId] == undefined) { - $flowStateStore[modId] = { - ...(($flowStateStore[modId] as object) ?? {}), - previewResult: jobLoaded.args - } + let prefixedId = buildSubflowKey(modId, prefix) + + // if (flowStateStore) { + // flowStateStore[modId] = { + // ...((flowStateStore[modId] as object) ?? {}), + // previewResult: jobLoaded.args + // } + // } + if (flowStateStore && flowStateStore[prefixedId] == undefined) { + flowStateStore[prefixedId] = {} } - if ($flowStateStore?.[modId]) { - if (!childFlow) { - if ( - !$flowStateStore[modId].previewResult || - !Array.isArray($flowStateStore[modId]?.previewResult) - ) { - $flowStateStore[modId].previewResult = [] - } - $flowStateStore[modId].previewArgs = jobLoaded.args + if (flowStateStore) { + if ( + !flowStateStore?.[prefixedId]?.previewResult || + !Array.isArray(flowStateStore[prefixedId]?.previewResult) + ) { + flowStateStore[prefixedId].previewResult = [] } - if (jobLoaded.type == 'QueuedJob') { - jobResults[j] = 'Job in progress ...' - } else if (jobLoaded.type == 'CompletedJob') { - $flowStateStore[modId].previewResult[j] = jobLoaded.result - jobResults[j] = jobLoaded.result + flowStateStore[prefixedId].previewArgs = jobLoaded.args + } + if (jobLoaded.type == 'QueuedJob') { + jobResults[j] = 'Job in progress ...' + } else if (jobLoaded.type == 'CompletedJob') { + if (flowStateStore?.[prefixedId]) { + flowStateStore[prefixedId].previewResult[j] = jobLoaded.result } + jobResults[j] = jobLoaded.result } let started_at = jobLoaded.started_at ? new Date(jobLoaded.started_at).getTime() : undefined @@ -824,15 +823,15 @@ duration_ms: undefined } - let currentIndex = getTopModuleStates()?.[modId]?.selectedForloopIndex == j + let currentIndex = getTopModuleStates?.[prefixedId]?.selectedForloopIndex == j + if (currentIndex) { v.logs = jobLoaded.logs v.args = jobLoaded.args v.job_id = jobLoaded.id } - if (jobLoaded.type == 'QueuedJob') { - if (started_at && $localModuleStates[modId]?.type != 'InProgress') { + if (started_at && localModuleStates[modId]?.type != 'InProgress') { v.type = 'InProgress' } } else if (jobLoaded.type == 'CompletedJob') { @@ -842,7 +841,6 @@ } } setModuleState(modId, v, force, true) - if (jobLoaded.type == 'QueuedJob') { setDurationStatusByJob(modId, job_id, { created_at, @@ -865,7 +863,7 @@ id = innerModule?.modules?.[0]?.id } if (id) { - onJobsLoaded({ id } as FlowStatusModule, jobLoaded) + onJobsLoadedInner({ id } as FlowStatusModule, jobLoaded) } } } @@ -873,17 +871,12 @@ let flowTimeline: FlowTimeline | undefined = $state() - function loadPreviousIters(lenToAdd: number) { - let r = $localDurationStatuses[flowJobIds?.moduleId ?? ''] - if (r.iteration_from) { - r.iteration_from -= lenToAdd - $localDurationStatuses = $localDurationStatuses - globalDurationStatuses.forEach((x) => x.update((x) => x)) + function loadPreviousIters(innerKey: string, lenToAdd: number) { + let key = buildSubflowKey(innerKey, prefix) + if (globalIterationBounds[key]) { + globalIterationBounds[key].iteration_from = + (globalIterationBounds[key]?.iteration_from ?? 0) - lenToAdd } - jobResults = [ - ...[...new Array(lenToAdd).keys()].map((x) => 'not computed or loaded yet'), - ...jobResults - ] // updateSlicedListJobIds() } @@ -894,17 +887,14 @@ function removeFailureNode(id: string, parent_module: any) { if (id?.startsWith('failure-') && parent_module) { - ;[...globalModuleStates, localModuleStates].forEach((stateMapStore) => { - stateMapStore.update((stateMap) => { - if (id) { - Object.keys(stateMap).forEach((key) => { - if (stateMap[key]?.parent_module == parent_module) { - delete stateMap[key] - } - }) - } - return stateMap - }) + ;[...globalModuleStates, localModuleStates].forEach((stateMap) => { + if (id) { + Object.keys(stateMap).forEach((key) => { + if (stateMap[key]?.parent_module == parent_module) { + delete stateMap[key] + } + }) + } }) } } @@ -941,35 +931,49 @@ let subflowsSize = $state(500) + function setParentModuleState(modId: string, state: Partial) { + ;[localModuleStates, ...globalModuleStates].forEach((stateMap) => { + if (stateMap[modId]) { + stateMap[modId] = { ...stateMap[modId], ...state } + } + }) + if (prefix) { + let prefixedId = buildSubflowKey(modId, prefix) + subflowParentsGlobalModuleStates.forEach((stateMap) => { + if (stateMap[prefixedId]) { + stateMap[prefixedId] = { ...stateMap[prefixedId], ...state } + } + }) + } + } async function onSelectedIteration( detail: | { id: string; index: number; manuallySet: true; moduleId: string } | { manuallySet: false; moduleId: string } ) { + let prefixedId = buildSubflowKey(detail.moduleId, prefix) if (detail.manuallySet) { let rootJobId = detail.id await tick() - let previousId = $localModuleStates[detail.moduleId]?.selectedForloop + let previousId = getTopModuleStates?.[prefixedId]?.selectedForloop if (previousId) { - await globalRefreshes?.[detail.moduleId]?.(true, previousId) + await refreshGlobal?.(prefixedId, true, previousId) } - $localModuleStates[detail.moduleId] = { - ...$localModuleStates[detail.moduleId], + setParentModuleState(detail.moduleId, { selectedForloop: detail.id, selectedForloopIndex: detail.index, selectedForLoopSetManually: true - } + }) await tick() - await globalRefreshes?.[detail.moduleId]?.(false, rootJobId) + await refreshGlobal?.(prefixedId, false, rootJobId) } else { - $localModuleStates[detail.moduleId] = { - ...$localModuleStates[detail.moduleId], + setParentModuleState(detail.moduleId, { selectedForLoopSetManually: false - } + }) } } @@ -984,7 +988,17 @@ }) let isListJob = $derived(flowJobIds != undefined && Array.isArray(flowJobIds?.flowJobs)) $effect(() => { - flowJobIds?.moduleId && untrack(() => onModuleIdChange()) + if (isSelectedBranch) { + let modId = flowJobIds?.moduleId + if (modId) { + let selectedForloop = getTopModuleStates?.[buildSubflowKey(modId, prefix)]?.selectedForloop + untrack(() => { + if (selectedForloop != forloop_selected) { + forloop_selected = selectedForloop + } + }) + } + } }) let selected = $derived(isListJob ? 'sequence' : 'graph') as 'sequence' | 'graph' | 'logs' @@ -1006,16 +1020,17 @@
{/if} --> {#if isListJob} - {@const sliceFrom = $localDurationStatuses[flowJobIds?.moduleId ?? '']?.iteration_from ?? 0} + {@const sliceFrom = + globalIterationBounds[buildSubflowKey(flowJobIds?.moduleId ?? '', prefix)] + ?.iteration_from ?? 0} {@const lenToAdd = Math.min(20, sliceFrom)} - {#if (flowJobIds?.flowJobs.length ?? 0) > 20 && lenToAdd > 0} {@const allToAdd = (flowJobIds?.length ?? 0) - sliceFrom}

For performance reasons, only the last 20 items are shown by default @@ -1024,7 +1039,7 @@ @@ -1058,7 +1073,7 @@

{/if} {#if render} - {#if innerModules.length > 0 && !isListJob} + {#if innerModules && innerModules.length > 0 && !isListJob} Graph {#if isListJob} - {@const sliceFrom = $localDurationStatuses[flowJobIds?.moduleId ?? '']?.iteration_from ?? 0} + {@const sliceFrom = + globalIterationBounds[buildSubflowKey(flowJobIds?.moduleId ?? '', prefix)] + ?.iteration_from ?? 0}

Subflows ({flowJobIds?.flowJobs.length})

@@ -1125,9 +1142,10 @@
20)} {workspaceId} jobId={loopJobId} - on:jobsLoaded={(e) => { - let { job, force } = e.detail + onJobsLoaded={({ job, force }) => { storedListJobs[j] = job innerJobLoaded(job, j, false, force) }} {onResultStreamUpdate} graphTabOpen={selected == 'graph' && graphTabOpen} isNodeSelected={forloop_selected == loopJobId} + {globalIterationBounds} />
{/if} {/each}
- {:else if innerModules.length > 0 && (job.raw_flow?.modules.length ?? 0) > 0} + {:else if innerModules && innerModules.length > 0 && (job.raw_flow?.modules.length ?? 0) > 0} {@const hasPreprocessor = innerModules[0]?.id == 'preprocessor' ? 1 : 0}

    Step-by-step

    - {#each innerModules as mod, i} + {#each innerModules ?? [] as mod, i} {#if render}

    @@ -1217,9 +1236,11 @@
    {/each} @@ -1239,10 +1261,14 @@ {#if ['InProgress', 'Success', 'Failure'].includes(mod.type)} {#if job.raw_flow?.modules[i]?.value.type == 'flow'} { - let { force, job } = e.detail - onJobsLoaded(mod, job, force) + onJobsLoaded={({ job, force }) => { + onJobsLoadedInner(mod, job, force) }} {onResultStreamUpdate} graphTabOpen={selected == 'graph' && graphTabOpen} @@ -1272,16 +1296,18 @@
    no subflow (empty loop?)
    {:else} { - let { job, force } = e.detail - onJobsLoaded(mod, job, force) + onJobsLoaded={({ job, force }) => { + onJobsLoadedInner(mod, job, force) }} loadExtraLogs={(logs) => { setModuleState(mod.id ?? '', { @@ -1305,13 +1330,14 @@ }} {onResultStreamUpdate} graphTabOpen={selected == 'graph' && graphTabOpen} - isNodeSelected={$localModuleStates?.[selectedNode ?? '']?.job_id == mod.job} + isNodeSelected={localModuleStates?.[selectedNode ?? '']?.job_id == mod.job} + {globalIterationBounds} /> {/if} {:else} {/if} @@ -1337,14 +1363,14 @@
    - {#each Object.values($retryStatus) as count} + {#each Object.values(retryStatus?.val ?? {}) as count} {#if count} Retry in progress, # of failed attempts: {count} {/if} {/each} - {#each Object.values($suspendStatus) as count} + {#each Object.values(suspendStatus?.val ?? {}) as count} {#if count.nb} Flow suspended, waiting for {count.nb} events @@ -1359,7 +1385,7 @@ download={!hideDownloadInGraph} minHeight={wrapperHeight} success={jobId != undefined && isSuccess(job?.['success'])} - flowModuleStates={$localModuleStates} + flowModuleStates={localModuleStates} bind:expandedSubflows onSelect={(e) => { console.log('onSelect', e) @@ -1419,12 +1445,17 @@ job?.raw_flow?.modules ?? [], expandedSubflows ?? {} )} + buildSubflowKey={(key) => buildSubflowKey(key, prefix)} + {globalIterationBounds} durationStatuses={localDurationStatuses} + decreaseIterationFrom={(key, amount) => { + loadPreviousIters(key, amount) + }} /> {:else if rightColumnSelect == 'node_status'}
    {#if selectedNode} - {@const node = $localModuleStates[selectedNode]} + {@const node = localModuleStates[selectedNode]} {#if selectedNode == 'end'} - import { debounce, displayDate, msToSec } from '$lib/utils' + import { debounce, displayDate, msToSec, readFieldsRecursively } from '$lib/utils' import { onDestroy, untrack } from 'svelte' import { getDbClockNow } from '$lib/forLater' import { Loader2 } from 'lucide-svelte' import TimelineBar from './TimelineBar.svelte' - import type { Writable } from 'svelte/store' import WaitTimeWarning from './common/waitTimeWarning/WaitTimeWarning.svelte' + import type { GlobalIterationBounds } from './graph' interface Props { selfWaitTime?: number | undefined aggregateWaitTime?: number | undefined flowModules: string[] - durationStatuses: Writable< - Record< - string, - { - byJob: Record - iteration_from?: number - iteration_total?: number - } - > + durationStatuses: Record< + string, + { + byJob: Record + } > flowDone?: boolean + decreaseIterationFrom?: (key: string, amount: number) => void + buildSubflowKey: (key: string) => string + globalIterationBounds: Record } let { @@ -29,7 +28,10 @@ aggregateWaitTime = undefined, flowModules, durationStatuses, - flowDone = false + flowDone = false, + decreaseIterationFrom, + buildSubflowKey, + globalIterationBounds }: Props = $props() let min: undefined | number = $state(undefined) @@ -43,15 +45,16 @@ > | undefined = $state(undefined) - let { debounced, clearDebounce } = debounce(() => computeItems($durationStatuses), 30) + let { debounced, clearDebounce } = debounce(() => computeItems(durationStatuses), 30) $effect(() => { - flowDone != undefined && $durationStatuses && untrack(() => debounced()) + readFieldsRecursively(durationStatuses) + flowDone != undefined && durationStatuses && untrack(() => debounced()) }) export function reset() { min = undefined max = undefined - items = computeItems($durationStatuses) + items = computeItems(durationStatuses) } function computeItems( @@ -172,20 +175,17 @@
    {/if} {#each Object.values(flowModules) as k (k)} + {@const iterationFrom = globalIterationBounds[buildSubflowKey(k)]?.iteration_from ?? 0}
    - {#if ($durationStatuses?.[k]?.iteration_from ?? 0) > 0} + {#if iterationFrom > 0}
    {/if} diff --git a/frontend/src/lib/components/JobLoader.svelte b/frontend/src/lib/components/JobLoader.svelte index 618cb15984..9755aaef09 100644 --- a/frontend/src/lib/components/JobLoader.svelte +++ b/frontend/src/lib/components/JobLoader.svelte @@ -1,4 +1,4 @@ - diff --git a/frontend/src/lib/components/ModuleTest.svelte b/frontend/src/lib/components/ModuleTest.svelte index 550f8be5bf..362ba3ede5 100644 --- a/frontend/src/lib/components/ModuleTest.svelte +++ b/frontend/src/lib/components/ModuleTest.svelte @@ -35,14 +35,13 @@ } export function loadArgsAndRunTest() { - testSteps?.updateStepArgs(mod.id, $flowStateStore, flowStore?.val, previewArgs?.val) + testSteps?.updateStepArgs(mod.id, flowStateStore, flowStore?.val, previewArgs?.val) runTest(testSteps.getStepArgs(mod.id)?.value) } export async function runTest(args: any) { // Not defined if JobProgressBar not loaded if (jobProgressReset) jobProgressReset() - if (modulesTestStates.states[mod.id]) { modulesTestStates.states[mod.id].cancel = async () => { await jobLoader?.cancelJob() @@ -92,16 +91,19 @@ function jobDone(testJob: Job & { result?: any }) { if (testJob && !testJob.canceled && testJob.type == 'CompletedJob') { - if ($flowStateStore[mod.id]) { - $flowStateStore[mod.id].previewResult = testJob.result - $flowStateStore[mod.id].previewSuccess = testJob.success - $flowStateStore[mod.id].previewJobId = testJob.id - $flowStateStore[mod.id].previewWorkspaceId = testJob.workspace_id - $flowStateStore = $flowStateStore + if (flowStateStore.val[mod.id]) { + flowStateStore.val[mod.id] = { + ...flowStateStore.val[mod.id], + previewResult: testJob.result, + previewSuccess: testJob.success, + previewJobId: testJob.id + } } stepHistoryLoader?.resetInitial(mod.id) } - modulesTestStates.states[mod.id].testJob = undefined + if (modulesTestStates.states[mod.id]) { + modulesTestStates.states[mod.id].testJob = testJob + } } export function cancelJob() { @@ -110,16 +112,16 @@ $effect(() => { // Update testIsLoading to read the state from parent components - testIsLoading = modulesTestStates.states[mod.id]?.loading ?? false + testIsLoading = modulesTestStates.states?.[mod.id]?.loading ?? false }) $effect(() => { // Update testJob to read the state from parent components - testJob = modulesTestStates.states[mod.id]?.testJob + testJob = modulesTestStates.states?.[mod.id]?.testJob }) modulesTestStates.states[mod.id] = { - ...(modulesTestStates.states[mod.id] ?? { loading: false }), + ...(modulesTestStates.states?.[mod.id] ?? { loading: false }), loading: testIsLoading, testJob: testJob } @@ -134,13 +136,13 @@ () => modulesTestStates.states[mod.id]?.loading ?? false, (v) => { let newLoading = v ?? false - if (modulesTestStates.states[mod.id]?.loading !== newLoading) { + if (modulesTestStates.states && modulesTestStates.states?.[mod.id]?.loading !== newLoading) { modulesTestStates.states[mod.id] = { - ...(modulesTestStates.states[mod.id] ?? {}), - loading: newLoading + ...(modulesTestStates.states?.[mod.id] ?? {}), + loading: newLoading, + hiddenInGraph: false } } } } - bind:job={modulesTestStates.states[mod.id].testJob} /> diff --git a/frontend/src/lib/components/apps/components/display/AppJobIdFlowStatus.svelte b/frontend/src/lib/components/apps/components/display/AppJobIdFlowStatus.svelte index 2582c952ef..5d69a89267 100644 --- a/frontend/src/lib/components/apps/components/display/AppJobIdFlowStatus.svelte +++ b/frontend/src/lib/components/apps/components/display/AppJobIdFlowStatus.svelte @@ -98,13 +98,13 @@ { + onStart={() => { outputs?.jobId.set(jobId) outputs?.loading.set(true) }} - on:done={(e) => { + onDone={({ job }) => { outputs?.loading.set(false) - outputs?.result.set(e?.detail?.result) + outputs?.result.set(job?.result as any) }} /> {:else} diff --git a/frontend/src/lib/components/apps/editor/AppJobsDrawer.svelte b/frontend/src/lib/components/apps/editor/AppJobsDrawer.svelte index 83f7388675..16cf932e85 100644 --- a/frontend/src/lib/components/apps/editor/AppJobsDrawer.svelte +++ b/frontend/src/lib/components/apps/editor/AppJobsDrawer.svelte @@ -264,8 +264,8 @@ {#if job?.id} { - job = detail + onJobsLoaded={({ job: newJob }) => { + job = newJob }} /> {:else} diff --git a/frontend/src/lib/components/copilot/chat/AIChatManager.svelte.ts b/frontend/src/lib/components/copilot/chat/AIChatManager.svelte.ts index 67dcd2e8f8..8b5a88d5fa 100644 --- a/frontend/src/lib/components/copilot/chat/AIChatManager.svelte.ts +++ b/frontend/src/lib/components/copilot/chat/AIChatManager.svelte.ts @@ -117,7 +117,7 @@ class AIChatManager { return ( estimatedTokens > modelContextWindow - - Math.max(modelContextWindow * MAX_TOKENS_THRESHOLD_PERCENTAGE, MAX_TOKENS_HARD_LIMIT) + Math.max(modelContextWindow * MAX_TOKENS_THRESHOLD_PERCENTAGE, MAX_TOKENS_HARD_LIMIT) ) } @@ -547,8 +547,8 @@ class AIChatManager { onNewToken: (token: string) => { reply += token }, - onMessageEnd: () => {}, - setToolStatus: () => {} + onMessageEnd: () => { }, + setToolStatus: () => { } }, systemMessage } @@ -733,8 +733,8 @@ class AIChatManager { } else { // Create new tool message with metadata const newMessage: ToolDisplayMessage = { - role: 'tool', - tool_call_id: id, + role: 'tool', + tool_call_id: id, content: metadata?.content ?? metadata?.error ?? '', ...(metadata || {}) } @@ -923,20 +923,20 @@ class AIChatManager { const module = getModule(id) if (module && module.value.type === 'rawscript') { - const moduleState: FlowModuleState | undefined = flowStateStore[module.id] + const moduleState: FlowModuleState | undefined = flowStateStore.val[module.id] const editorRelated = currentEditor && currentEditor.type === 'script' && currentEditor.stepId === module.id ? { - diffMode: currentEditor.diffMode, - lastDeployedCode: currentEditor.lastDeployedCode, - lastSavedCode: undefined - } + diffMode: currentEditor.diffMode, + lastDeployedCode: currentEditor.lastDeployedCode, + lastSavedCode: undefined + } : { - diffMode: false, - lastDeployedCode: undefined, - lastSavedCode: undefined - } + diffMode: false, + lastDeployedCode: undefined, + lastSavedCode: undefined + } return { args: moduleState?.previewArgs ?? {}, diff --git a/frontend/src/lib/components/copilot/chat/flow/FlowAIChat.svelte b/frontend/src/lib/components/copilot/chat/flow/FlowAIChat.svelte index 5c54e31dcb..c0688bafd8 100644 --- a/frontend/src/lib/components/copilot/chat/flow/FlowAIChat.svelte +++ b/frontend/src/lib/components/copilot/chat/flow/FlowAIChat.svelte @@ -200,10 +200,10 @@ module.value.input_transforms = input_transforms refreshStateStore(flowStore) - if ($flowStateStore[id]) { - $flowStateStore[id].schema = schema + if (flowStateStore.val[id]) { + flowStateStore.val[id].schema = schema } else { - $flowStateStore[id] = { + flowStateStore.val[id] = { schema } } @@ -305,7 +305,6 @@ } if (location.type === 'preprocessor' || location.type === 'failure') { - $flowStateStore = $flowStateStore refreshStateStore(flowStore) setModuleStatus(location.type, 'added') @@ -322,7 +321,6 @@ await flowModuleSchemaMap?.addBranch(newModule.id) } - $flowStateStore = $flowStateStore refreshStateStore(flowStore) setModuleStatus(newModule.id, 'added') @@ -516,7 +514,7 @@ const cleanup = aiChatManager.listenForSelectedIdChanges( $selectedId, flowStore.val, - $flowStateStore, + flowStateStore, $currentEditor ) return cleanup diff --git a/frontend/src/lib/components/flow_builder.ts b/frontend/src/lib/components/flow_builder.ts index c133ef18b4..d847664fa7 100644 --- a/frontend/src/lib/components/flow_builder.ts +++ b/frontend/src/lib/components/flow_builder.ts @@ -1,6 +1,5 @@ import type { OpenFlow } from '$lib/gen' import type { StateStore } from '$lib/utils' -import type { Writable } from 'svelte/store' import type { FlowState } from './flows/flowState' import type { FlowWithDraftAndDraftTriggers, Trigger } from './triggers/utils' import type { DiffDrawerI } from './diff_drawer' @@ -16,7 +15,7 @@ export type FlowBuilderProps = { initialArgs?: Record loading?: boolean flowStore: StateStore - flowStateStore: Writable + flowStateStore: StateStore savedFlow?: FlowWithDraftAndDraftTriggers | undefined diffDrawer?: DiffDrawerI | undefined customUi?: FlowBuilderWhitelabelCustomUi diff --git a/frontend/src/lib/components/flows/FlowEditor.svelte b/frontend/src/lib/components/flows/FlowEditor.svelte index 419787e055..2e264d94ae 100644 --- a/frontend/src/lib/components/flows/FlowEditor.svelte +++ b/frontend/src/lib/components/flows/FlowEditor.svelte @@ -7,7 +7,7 @@ import { getContext, onDestroy, onMount, setContext } from 'svelte' import type { FlowEditorContext } from './types' - import { writable, type Writable } from 'svelte/store' + import { writable } from 'svelte/store' import type { PropPickerContext, FlowPropPickerConfig } from '$lib/components/prop_picker' import type { PickableProperties } from '$lib/components/flows/previousResults' import type { Flow, Job } from '$lib/gen' @@ -16,6 +16,8 @@ import { aiChatManager, AIMode } from '../copilot/chat/AIChatManager.svelte' import type { GraphModuleState } from '../graph' import { triggerableByAI } from '$lib/actions/triggerableByAI.svelte' + import type { ModulesTestStates } from '../modulesTest.svelte' + import type { StateStore } from '$lib/utils' const { flowStore } = getContext('FlowEditorContext') interface Props { @@ -27,6 +29,7 @@ disabledFlowInputs?: boolean smallErrorHandler?: boolean newFlow?: boolean + showJobStatus?: boolean savedFlow?: | (Flow & { draft?: Flow | undefined @@ -40,7 +43,8 @@ aiChatOpen?: boolean showFlowAiButton?: boolean toggleAiChat?: () => void - localModuleStates?: Writable> + localModuleStates?: Record + testModuleStates?: ModulesTestStates isOwner?: boolean onTestFlow?: () => void isRunning?: boolean @@ -49,8 +53,7 @@ onHideJobStatus?: () => void individualStepTests?: boolean job?: Job - suspendStatus?: Writable> - showJobStatus?: boolean + suspendStatus?: StateStore> onDelete?: (id: string) => void flowHasChanged?: boolean } @@ -63,6 +66,7 @@ disableSettings = false, disabledFlowInputs = false, smallErrorHandler = false, + showJobStatus = false, newFlow = false, savedFlow = undefined, onDeployTrigger = () => {}, @@ -70,7 +74,8 @@ onEditInput = undefined, forceTestTab, highlightArg, - localModuleStates = writable({}), + localModuleStates = {}, + testModuleStates = undefined, aiChatOpen, showFlowAiButton, toggleAiChat, @@ -83,7 +88,6 @@ individualStepTests = false, job, suspendStatus, - showJobStatus, onDelete, flowHasChanged }: Props = $props() @@ -134,6 +138,7 @@ {disableSettings} {smallErrorHandler} {newFlow} + {showJobStatus} on:reload on:generateStep={({ detail }) => { if (!aiChatManager.open) { @@ -144,6 +149,7 @@ {onTestUpTo} {onEditInput} {localModuleStates} + {testModuleStates} {aiChatOpen} {showFlowAiButton} {toggleAiChat} @@ -155,7 +161,6 @@ {onHideJobStatus} {individualStepTests} flowJob={job} - {showJobStatus} {suspendStatus} {onDelete} {flowHasChanged} diff --git a/frontend/src/lib/components/flows/content/BranchPredicateEditor.svelte b/frontend/src/lib/components/flows/content/BranchPredicateEditor.svelte index b3c362a2f3..df936c64b4 100644 --- a/frontend/src/lib/components/flows/content/BranchPredicateEditor.svelte +++ b/frontend/src/lib/components/flows/content/BranchPredicateEditor.svelte @@ -10,28 +10,34 @@ import { Pen } from 'lucide-svelte' import PredicateGen from '$lib/components/copilot/PredicateGen.svelte' - export let branch: { - summary?: string - expr: string - modules: Array + interface Props { + branch: { + summary?: string + expr: string + modules: Array + } + parentModule: FlowModule + previousModule: FlowModule | undefined + enableAi?: boolean } - export let parentModule: FlowModule - export let previousModule: FlowModule | undefined - export let enableAi = false + + let { branch = $bindable(), parentModule, previousModule, enableAi = false }: Props = $props() const { previewArgs, flowStateStore, flowStore } = getContext('FlowEditorContext') - let editor: SimpleEditor | undefined = undefined - let open = false - $: stepPropPicker = getStepPropPicker( - $flowStateStore, - parentModule, - previousModule, - parentModule.id, - flowStore.val, - previewArgs.val, - false + let editor: SimpleEditor | undefined = $state(undefined) + let open = $state(false) + let stepPropPicker = $derived( + getStepPropPicker( + flowStateStore.val, + parentModule, + previousModule, + parentModule.id, + flowStore.val, + previewArgs.val, + false + ) ) diff --git a/frontend/src/lib/components/flows/content/FlowConstants.svelte b/frontend/src/lib/components/flows/content/FlowConstants.svelte index 4ce1dc5086..a92efaf493 100644 --- a/frontend/src/lib/components/flows/content/FlowConstants.svelte +++ b/frontend/src/lib/components/flows/content/FlowConstants.svelte @@ -35,7 +35,7 @@ m.id, Object.entries(v.input_transforms) .map((x) => { - let schema = flowStateStore[m.id]?.schema + let schema = flowStateStore.val[m.id]?.schema let val: { argName: string; type: string } | undefined = undefined const [k, inputTransform] = x @@ -75,7 +75,7 @@ Object.entries(v.input_transforms) .filter((x) => { const shouldDisplay = hideOptional - ? $flowStateStore[m.id]?.schema?.required?.includes(x[0]) + ? flowStateStore.val[m.id]?.schema?.required?.includes(x[0]) : true return x[1].type == 'static' && shouldDisplay }) @@ -146,7 +146,7 @@ noDynamicToggle {filter} class="mt-2" - schema={$flowStateStore[m.id]?.schema ?? {}} + schema={flowStateStore.val[m.id]?.schema ?? {}} bind:args={steps[index][0]} />
    diff --git a/frontend/src/lib/components/flows/content/FlowEditorPanel.svelte b/frontend/src/lib/components/flows/content/FlowEditorPanel.svelte index 0978950f37..763de8e3fb 100644 --- a/frontend/src/lib/components/flows/content/FlowEditorPanel.svelte +++ b/frontend/src/lib/components/flows/content/FlowEditorPanel.svelte @@ -14,7 +14,7 @@ import { handleSelectTriggerFromKind, type Trigger } from '$lib/components/triggers/utils' import { computeMissingInputWarnings } from '../missingInputWarnings' import FlowResult from './FlowResult.svelte' - import type { Writable } from 'svelte/store' + import type { StateStore } from '$lib/utils' interface Props { noEditor?: boolean @@ -32,7 +32,7 @@ onTestFlow?: () => void job?: Job isOwner?: boolean - suspendStatus?: Writable> + suspendStatus?: StateStore> onOpenDetails?: () => void } @@ -78,7 +78,7 @@ } $effect(() => { - computeMissingInputWarnings(flowStore, $flowStateStore, flowInputsStore) + computeMissingInputWarnings(flowStore, flowStateStore, flowInputsStore) }) diff --git a/frontend/src/lib/components/flows/content/FlowLoop.svelte b/frontend/src/lib/components/flows/content/FlowLoop.svelte index a12576c282..038dce099c 100644 --- a/frontend/src/lib/components/flows/content/FlowLoop.svelte +++ b/frontend/src/lib/components/flows/content/FlowLoop.svelte @@ -55,7 +55,7 @@ let stepPropPicker = $derived( getStepPropPicker( - $flowStateStore, + flowStateStore.val, parentModule, previousModule, mod.id, @@ -72,7 +72,7 @@ let iteratorFieldFocused = $state(false) let iteratorGen: IteratorGen | undefined = $state(undefined) - let previewIterationArgs = $derived($flowStateStore[mod.id]?.previewArgs ?? {}) + let previewIterationArgs = $derived(flowStateStore.val[mod.id]?.previewArgs ?? {}) function setExpr(code: string) { if (mod.value.type === 'forloopflow') { diff --git a/frontend/src/lib/components/flows/content/FlowModuleComponent.svelte b/frontend/src/lib/components/flows/content/FlowModuleComponent.svelte index 1060a405c4..308324c4a3 100644 --- a/frontend/src/lib/components/flows/content/FlowModuleComponent.svelte +++ b/frontend/src/lib/components/flows/content/FlowModuleComponent.svelte @@ -169,11 +169,11 @@ } } await tick() - if (!deepEqual(schema, $flowStateStore[flowModule.id]?.schema)) { - if (!$flowStateStore[flowModule.id]) { - $flowStateStore[flowModule.id] = { schema } + if (!deepEqual(schema, flowStateStore.val[flowModule.id]?.schema)) { + if (!flowStateStore.val[flowModule.id]) { + flowStateStore.val[flowModule.id] = { schema } } else { - $flowStateStore[flowModule.id].schema = schema + flowStateStore.val[flowModule.id].schema = schema } } } catch (e) { @@ -195,7 +195,7 @@ let lastJobId: string | undefined = undefined function onSelectedIdChange() { - if (!$flowStateStore?.[$selectedId]?.schema && flowModule) { + if (!flowStateStore?.val?.[$selectedId]?.schema && flowModule) { reload(flowModule) } lastJobId = undefined @@ -203,31 +203,30 @@ async function getLastJob() { if ( - !$flowStateStore || + !flowStateStore || !flowModule.id || - $flowStateStore[flowModule.id]?.previewResult === 'never tested this far' || - !$flowStateStore[flowModule.id]?.previewJobId || - !$flowStateStore[flowModule.id]?.previewWorkspaceId + flowStateStore.val[flowModule.id]?.previewResult === 'never tested this far' || + !flowStateStore.val[flowModule.id]?.previewJobId ) { return } if ( - lastJobId == $flowStateStore[flowModule.id]?.previewJobId || - lastJob?.id == $flowStateStore[flowModule.id]?.previewJobId || - $flowStateStore[flowModule.id]?.previewSuccess == undefined + lastJobId == flowStateStore.val[flowModule.id]?.previewJobId || + lastJob?.id == flowStateStore.val[flowModule.id]?.previewJobId || + flowStateStore.val[flowModule.id]?.previewSuccess == undefined ) { return } - lastJobId = $flowStateStore[flowModule.id]?.previewJobId + lastJobId = flowStateStore.val[flowModule.id]?.previewJobId const job = await JobService.getJob({ - workspace: $flowStateStore[flowModule.id]?.previewWorkspaceId ?? '', - id: $flowStateStore[flowModule.id]?.previewJobId ?? '', + workspace: $workspaceStore ?? '', + id: flowStateStore.val[flowModule.id]?.previewJobId ?? '', noCode: true }) if (job && job.type === 'CompletedJob') { - lastJobId = $flowStateStore[flowModule.id]?.previewJobId + lastJobId = flowStateStore.val[flowModule.id]?.previewJobId lastJob = job } } @@ -251,9 +250,9 @@ let stepPropPicker = $derived( $executionCount != undefined && failureModule - ? getFailureStepPropPicker($flowStateStore, flowStore.val, previewArgs.val) + ? getFailureStepPropPicker(flowStateStore, flowStore.val, previewArgs.val) : getStepPropPicker( - $flowStateStore, + flowStateStore.val, parentModule, previousModule, flowModule.id, @@ -269,7 +268,7 @@ $effect(() => { if (testJob && testJob.type === 'CompletedJob') { lastJob = $state.snapshot(testJob) - } else if ($workspaceStore && $pathStore && flowModule?.id && $flowStateStore) { + } else if ($workspaceStore && $pathStore && flowModule?.id && flowStateStore) { untrack(() => getLastJob()) } }) @@ -361,7 +360,7 @@ on:fork={async () => { const [module, state] = await fork(flowModule) flowModule = module - $flowStateStore[module.id] = state + flowStateStore.val[module.id] = state }} on:reload={async () => { if (flowModule.value.type == 'script') { @@ -380,14 +379,14 @@ const [module, state] = await createScriptFromInlineScript( flowModule, $selectedId, - $flowStateStore[flowModule.id].schema, + flowStateStore.val[flowModule.id].schema, $pathStore ) if (flowModule.value.type == 'rawscript') { module.value.input_transforms = flowModule.value.input_transforms } flowModule = module - $flowStateStore[module.id] = state + flowStateStore.val[module.id] = state }} /> {/snippet} @@ -539,7 +538,7 @@ class="px-1 xl:px-2" bind:this={inputTransformSchemaForm} pickableProperties={stepPropPicker.pickableProperties} - schema={$flowStateStore[$selectedId]?.schema ?? {}} + schema={flowStateStore.val[$selectedId]?.schema ?? {}} previousModuleId={previousModule?.id} bind:args={ () => { @@ -567,7 +566,7 @@ bind:this={modulePreview} mod={flowModule} {noEditor} - schema={$flowStateStore[$selectedId]?.schema ?? {}} + schema={flowStateStore.val[$selectedId]?.schema ?? {}} bind:testJob bind:testIsLoading bind:scriptProgress diff --git a/frontend/src/lib/components/flows/content/FlowModuleEarlyStop.svelte b/frontend/src/lib/components/flows/content/FlowModuleEarlyStop.svelte index eb8837a039..8862ed4fe0 100644 --- a/frontend/src/lib/components/flows/content/FlowModuleEarlyStop.svelte +++ b/frontend/src/lib/components/flows/content/FlowModuleEarlyStop.svelte @@ -23,7 +23,7 @@ let editor: SimpleEditor | undefined = $state(undefined) let stepPropPicker = $derived( getStepPropPicker( - $flowStateStore, + flowStateStore.val, undefined, undefined, flowModule.id, @@ -55,7 +55,7 @@ let isBranchAll = $derived(flowModule.value.type === 'branchall') let isStopAfterIfEnabled = $derived(Boolean(flowModule.stop_after_if)) let isStopAfterAllIterationsEnabled = $derived(Boolean(flowModule.stop_after_all_iters_if)) - let result = $derived($flowStateStore[flowModule.id]?.previewResult ?? NEVER_TESTED_THIS_FAR) + let result = $derived(flowStateStore.val[flowModule.id]?.previewResult ?? NEVER_TESTED_THIS_FAR) let parentLoopId = $derived(checkIfParentLoop(flowStore.val)) diff --git a/frontend/src/lib/components/flows/content/FlowModuleSkip.svelte b/frontend/src/lib/components/flows/content/FlowModuleSkip.svelte index ba16f3ad73..4acfc75183 100644 --- a/frontend/src/lib/components/flows/content/FlowModuleSkip.svelte +++ b/frontend/src/lib/components/flows/content/FlowModuleSkip.svelte @@ -23,7 +23,7 @@ let editor: SimpleEditor | undefined = $state(undefined) let stepPropPicker = $derived( getStepPropPicker( - $flowStateStore, + flowStateStore.val, parentModule, previousModule, flowModule.id, diff --git a/frontend/src/lib/components/flows/content/FlowModuleSleep.svelte b/frontend/src/lib/components/flows/content/FlowModuleSleep.svelte index 3319f559c7..14e5bd48cf 100644 --- a/frontend/src/lib/components/flows/content/FlowModuleSleep.svelte +++ b/frontend/src/lib/components/flows/content/FlowModuleSleep.svelte @@ -28,7 +28,7 @@ let editor: SimpleEditor | undefined = $state(undefined) - const result = $flowStateStore[$selectedId]?.previewResult ?? {} + const result = flowStateStore.val[$selectedId]?.previewResult ?? {} let isSleepEnabled = $derived(Boolean(flowModule.sleep)) diff --git a/frontend/src/lib/components/flows/content/FlowModuleSuspend.svelte b/frontend/src/lib/components/flows/content/FlowModuleSuspend.svelte index c83348c35a..7355260f2b 100644 --- a/frontend/src/lib/components/flows/content/FlowModuleSuspend.svelte +++ b/frontend/src/lib/components/flows/content/FlowModuleSuspend.svelte @@ -21,7 +21,7 @@ import AddProperty from '$lib/components/schema/AddProperty.svelte' const { selectedId, flowStateStore } = getContext('FlowEditorContext') - const result = $flowStateStore[$selectedId]?.previewResult ?? {} + const result = flowStateStore.val[$selectedId]?.previewResult ?? {} let editor: SimpleEditor | undefined = $state(undefined) interface Props { diff --git a/frontend/src/lib/components/flows/content/FlowModuleWrapper.svelte b/frontend/src/lib/components/flows/content/FlowModuleWrapper.svelte index a61d1dbdb9..47b72304e0 100644 --- a/frontend/src/lib/components/flows/content/FlowModuleWrapper.svelte +++ b/frontend/src/lib/components/flows/content/FlowModuleWrapper.svelte @@ -106,7 +106,7 @@ } flowModule = module - $flowStateStore[module.id] = state + flowStateStore.val[module.id] = state } @@ -145,7 +145,7 @@ const [module, state] = await pickFlow(path, summary, flowModule.id) flowModule = module - $flowStateStore[module.id] = state + flowStateStore.val[module.id] = state }} /> {:else} @@ -182,7 +182,7 @@ } flowModule = module - $flowStateStore[module.id] = state + flowStateStore.val[module.id] = state }} failureModule={$selectedId === 'failure'} preprocessorModule={$selectedId === 'preprocessor'} diff --git a/frontend/src/lib/components/flows/content/FlowResult.svelte b/frontend/src/lib/components/flows/content/FlowResult.svelte index eeabfc707c..7dd3e20e9d 100644 --- a/frontend/src/lib/components/flows/content/FlowResult.svelte +++ b/frontend/src/lib/components/flows/content/FlowResult.svelte @@ -2,14 +2,14 @@ import FlowPreviewResult from '$lib/components/FlowPreviewResult.svelte' import type { Job } from '$lib/gen' import { workspaceStore } from '$lib/stores' - import type { Writable } from 'svelte/store' import FlowCard from '../common/FlowCard.svelte' import Button from '$lib/components/common/button/Button.svelte' + import type { StateStore } from '$lib/utils' interface Props { job?: Job isOwner?: boolean - suspendStatus?: Writable> + suspendStatus?: StateStore> noEditor: boolean onOpenDetails?: () => void } diff --git a/frontend/src/lib/components/flows/content/FlowWhileLoop.svelte b/frontend/src/lib/components/flows/content/FlowWhileLoop.svelte index dfa92b9c8d..d6ed0740bb 100644 --- a/frontend/src/lib/components/flows/content/FlowWhileLoop.svelte +++ b/frontend/src/lib/components/flows/content/FlowWhileLoop.svelte @@ -37,7 +37,7 @@ let jobId: string | undefined = $state(undefined) let job: Job | undefined = $state(undefined) - let previewIterationArgs = $derived($flowStateStore[mod.id]?.previewArgs ?? {}) + let previewIterationArgs = $derived(flowStateStore.val[mod.id]?.previewArgs ?? {}) diff --git a/frontend/src/lib/components/flows/flowState.ts b/frontend/src/lib/components/flows/flowState.ts index 490113b1c7..8ac15d32d5 100644 --- a/frontend/src/lib/components/flows/flowState.ts +++ b/frontend/src/lib/components/flows/flowState.ts @@ -1,15 +1,14 @@ import type { Schema } from '$lib/common' import type { Flow, FlowModule } from '$lib/gen' -import type { Writable } from 'svelte/store' import { loadFlowModuleState } from './flowStateUtils.svelte' import { emptyFlowModuleState } from './utils' +import type { StateStore } from '$lib/utils' export type FlowModuleState = { schema?: Schema previewResult?: any previewArgs?: any previewJobId?: string - previewWorkspaceId?: string previewSuccess?: boolean } @@ -21,7 +20,7 @@ export type FlowState = Record * We also hold the data of the results of a test job, ran by the user. */ -export async function initFlowState(flow: Flow, flowStateStore: Writable) { +export async function initFlowState(flow: Flow, flowStateStore: StateStore) { const modulesState: FlowState = {} await mapFlowModules(flow.value.modules, modulesState) @@ -30,10 +29,10 @@ export async function initFlowState(flow: Flow, flowStateStore: Writable) { - flowStateStore.update((fss) => { - delete fss[id] - return fss - }) +export function deleteFlowStateById(id: string, flowStateStore: FlowState) { + delete flowStateStore.val[id] } export function sliceModules( @@ -298,7 +295,7 @@ export function sliceModules( export async function insertNewPreprocessorModule( flowStore: StateStore, - flowStateStore: Writable, + flowStateStore: FlowState, inlineScript?: { language: RawScript['language'] }, @@ -323,15 +320,12 @@ export async function insertNewPreprocessorModule( flowStore.val.value.preprocessor_module = module - flowStateStore.update((fss) => { - fss[module.id] = state - return fss - }) + flowStateStore.val[module.id] = state } export async function insertNewFailureModule( flowStore: StateStore, - flowStateStore: Writable, + flowStateStore: FlowState, inlineScript?: { language: RawScript['language'] subkind: 'pgsql' | 'flow' @@ -361,8 +355,5 @@ export async function insertNewFailureModule( flowStore.val.value.failure_module = module - flowStateStore.update((fss) => { - fss[module.id] = state - return fss - }) + flowStateStore.val[module.id] = state } diff --git a/frontend/src/lib/components/flows/flowStore.ts b/frontend/src/lib/components/flows/flowStore.ts index 69d0667819..311958c7f3 100644 --- a/frontend/src/lib/components/flows/flowStore.ts +++ b/frontend/src/lib/components/flows/flowStore.ts @@ -1,5 +1,5 @@ import type { Flow, OpenFlow } from '$lib/gen' -import { writable, type Writable } from 'svelte/store' +import { writable } from 'svelte/store' import { initFlowState, type FlowState } from './flowState' import { sendUserToast } from '$lib/toast' import type { StateStore } from '$lib/utils' @@ -11,7 +11,7 @@ export const importFlowStore = writable(undefined) export async function initFlow( flow: Flow, flowStore: StateStore, - flowStateStore: Writable + flowStateStore: StateStore ) { await initFlowState(flow, flowStateStore) flowStore.val = flow diff --git a/frontend/src/lib/components/flows/header/FlowPreviewButtons.svelte b/frontend/src/lib/components/flows/header/FlowPreviewButtons.svelte index 576b73a754..b465efde77 100644 --- a/frontend/src/lib/components/flows/header/FlowPreviewButtons.svelte +++ b/frontend/src/lib/components/flows/header/FlowPreviewButtons.svelte @@ -9,14 +9,21 @@ import type { FlowEditorContext } from '../types' import { Play } from 'lucide-svelte' import { aiChatManager } from '$lib/components/copilot/chat/AIChatManager.svelte' + import type { GraphModuleState } from '$lib/components/graph' interface Props { loading?: boolean onRunPreview?: () => void onJobDone?: () => void + localModuleStates?: Record } - let { loading = false, onRunPreview, onJobDone }: Props = $props() + let { + loading = false, + onRunPreview, + onJobDone, + localModuleStates = $bindable({}) + }: Props = $props() const { selectedId } = getContext('FlowEditorContext') @@ -157,6 +164,7 @@ bind:selectedJobStepType bind:branchOrIterationN bind:rightColumnSelect + bind:localModuleStates on:close={() => { // keep the data in the preview content deferContent = true diff --git a/frontend/src/lib/components/flows/map/FlowGraphPreviewButton.svelte b/frontend/src/lib/components/flows/map/FlowGraphPreviewButton.svelte index 2634d793d2..e7faec4898 100644 --- a/frontend/src/lib/components/flows/map/FlowGraphPreviewButton.svelte +++ b/frontend/src/lib/components/flows/map/FlowGraphPreviewButton.svelte @@ -59,7 +59,7 @@ Test flow {/if} - {#if wide && (flowPreviewJob || individualStepTests)} + {#if wide && flowPreviewJob}
    { + filter isOpen && flowJobs && untrack(() => updateItems()) }) @@ -150,7 +151,8 @@ class={twMerge( 'text-primary text-xs w-full text-left py-1 pl-2 hover:bg-surface-hover whitespace-nowrap flex flex-row gap-2 items-center', items[idx].success == false ? 'text-red-400' : '', - 'data-[highlighted]:bg-surface-hover' + 'data-[highlighted]:bg-surface-hover', + items[idx].index == selected ? 'bg-surface-selected' : '' )} onClick={() => { onSelectedIteration({ diff --git a/frontend/src/lib/components/flows/map/FlowModuleSchemaItem.svelte b/frontend/src/lib/components/flows/map/FlowModuleSchemaItem.svelte index a20460e47b..6bf3819e96 100644 --- a/frontend/src/lib/components/flows/map/FlowModuleSchemaItem.svelte +++ b/frontend/src/lib/components/flows/map/FlowModuleSchemaItem.svelte @@ -164,31 +164,34 @@ connectingData = flowPropPickerConfig && pickableIds && Object.keys(pickableIds).includes(id) ? pickableIds[id] - : (flowStateStore?.[id]?.previewResult ?? {}) + : (flowStateStore?.val?.[id]?.previewResult ?? {}) } $effect(() => { - const args = [id, pickableIds, $flowPropPickerConfig, $flowStateStore] as const + const args = [id, pickableIds, $flowPropPickerConfig, flowStateStore] as const untrack(() => updateConnectingData(...args)) }) function updateLastJob(flowStateStore: any | undefined) { - if (!flowStateStore || !id || flowStateStore[id]?.previewResult === 'never tested this far') { + if ( + !flowStateStore || + !id || + flowStateStore.val[id]?.previewResult === 'never tested this far' + ) { return } lastJob = { - id: flowStateStore[id]?.previewJobId ?? '', - result: flowStateStore[id]?.previewResult, + id: flowStateStore.val[id]?.previewJobId ?? '', + result: flowStateStore.val[id]?.previewResult, type: 'CompletedJob' as const, - workspace_id: flowStateStore[id]?.previewWorkspaceId ?? '', - success: flowStateStore[id]?.previewSuccess ?? undefined + success: flowStateStore.val[id]?.previewSuccess ?? undefined } } $effect(() => { if (testJob && testJob.type === 'CompletedJob') { lastJob = $state.snapshot(testJob) - } else if (flowStateStore && $flowStateStore) { - untrack(() => updateLastJob($flowStateStore)) + } else if (flowStateStore && flowStateStore) { + untrack(() => updateLastJob(flowStateStore)) } }) @@ -268,7 +271,7 @@ {#if deletable && id && flowEditorContext?.flowStore && outputPickerVisible} {@const flowStore = flowEditorContext?.flowStore.val} {@const mod = flowStore?.value ? dfsPreviousResults(id, flowStore, false)[0] : undefined} - {#if mod && $flowStateStore?.[id]} + {#if mod && flowStateStore?.val?.[id]} {/if} {/if} diff --git a/frontend/src/lib/components/flows/map/FlowModuleSchemaMap.svelte b/frontend/src/lib/components/flows/map/FlowModuleSchemaMap.svelte index 30494e2876..4d54b2c775 100644 --- a/frontend/src/lib/components/flows/map/FlowModuleSchemaMap.svelte +++ b/frontend/src/lib/components/flows/map/FlowModuleSchemaMap.svelte @@ -36,9 +36,10 @@ import type { InlineScript, InsertKind } from '$lib/components/graph/graphBuilder.svelte' import { refreshStateStore } from '$lib/svelte5Utils.svelte' import type { GraphModuleState } from '$lib/components/graph' - import { writable, type Writable } from 'svelte/store' import FlowStickyNode from './FlowStickyNode.svelte' import { getStepHistoryLoaderContext } from '$lib/components/stepHistoryLoader.svelte' + import { ModulesTestStates } from '$lib/components/modulesTest.svelte' + import type { StateStore } from '$lib/utils' interface Props { sidebarSize?: number | undefined @@ -51,7 +52,8 @@ workspace?: string | undefined onTestUpTo?: ((id: string) => void) | undefined onEditInput?: (moduleId: string, key: string) => void - localModuleStates?: Writable> + localModuleStates?: Record + testModuleStates?: ModulesTestStates aiChatOpen?: boolean showFlowAiButton?: boolean toggleAiChat?: () => void @@ -64,7 +66,7 @@ individualStepTests?: boolean flowJob?: Job | undefined showJobStatus?: boolean - suspendStatus?: Writable> + suspendStatus?: StateStore> onDelete?: (id: string) => void flowHasChanged?: boolean } @@ -80,7 +82,8 @@ workspace = $workspaceStore, onTestUpTo, onEditInput, - localModuleStates = writable({}), + localModuleStates = {}, + testModuleStates = new ModulesTestStates(), aiChatOpen, showFlowAiButton, toggleAiChat, @@ -93,7 +96,7 @@ individualStepTests = false, flowJob = undefined, showJobStatus = false, - suspendStatus = writable({}), + suspendStatus = $bindable({ val: {} }), onDelete, flowHasChanged }: Props = $props() @@ -115,9 +118,9 @@ inlineScript?: InlineScript ): Promise { push(history, flowStore.val) - let module = emptyModule($flowStateStore, flowStore.val, kind == 'flow') + let module = emptyModule(flowStateStore.val, flowStore.val, kind == 'flow') let state = emptyFlowModuleState() - $flowStateStore[module.id] = state + flowStateStore.val[module.id] = state if (wsFlow) { ;[module, state] = await pickFlow(wsFlow.path, wsFlow.summary, module.id) } else if (wsScript) { @@ -139,14 +142,14 @@ } else if (inlineScript) { const { language, kind, subkind, summary } = inlineScript ;[module, state] = await createInlineScriptModule(language, kind, subkind, module.id, summary) - $flowStateStore[module.id] = state + flowStateStore.val[module.id] = state if (kind == 'trigger') { module.summary = 'Trigger' } else if (kind == 'approval') { module.summary = 'Approval' } } - $flowStateStore[module.id] = state + flowStateStore.val[module.id] = state if (kind == 'approval') { module.suspend = { required_events: 1, timeout: 1800 } @@ -303,18 +306,16 @@ id: previousJobId[0].id }) if ('result' in getJobResult) { - $flowStateStore[moduleId] = { - ...($flowStateStore[moduleId] ?? {}), + flowStateStore.val[moduleId] = { + ...(flowStateStore.val[moduleId] ?? {}), previewResult: getJobResult.result, previewJobId: previousJobId[0].id, - previewWorkspaceId: previousJobId[0].workspace_id, previewSuccess: getJobResult.success } if (stepHistoryLoader) { stepHistoryLoader.stepStates[moduleId].loadingJobs = false } } - $flowStateStore = $flowStateStore } } $effect(() => { @@ -388,12 +389,13 @@ editMode {onTestUpTo} {onEditInput} - flowModuleStates={$localModuleStates} + flowModuleStates={localModuleStates} + {testModuleStates} {isOwner} {individualStepTests} {flowJob} {showJobStatus} - {suspendStatus} + suspendStatus={suspendStatus.val} {flowHasChanged} onDelete={(id) => { dependents = getDependentComponents(id, flowStore.val) @@ -408,7 +410,7 @@ } refreshStateStore(flowStore) onDelete?.(id) - delete $flowStateStore[id] + delete flowStateStore.val[id] } if (Object.keys(dependents).length > 0) { @@ -520,7 +522,6 @@ if (['branchone', 'branchall'].includes(detail.kind)) { await addBranch(targetModules[detail.index ?? 0].id) } - $flowStateStore = $flowStateStore refreshStateStore(flowStore) dispatch('change') } @@ -570,9 +571,8 @@ mod.id = newId } }) - $flowStateStore[newId] = $flowStateStore[id] - delete $flowStateStore[id] - $flowStateStore = $flowStateStore + flowStateStore.val[newId] = flowStateStore.val[id] + delete flowStateStore.val[id] refreshStateStore(flowStore) $selectedId = newId }} diff --git a/frontend/src/lib/components/flows/previousResults.ts b/frontend/src/lib/components/flows/previousResults.ts index 806f1ec668..59fd466b80 100644 --- a/frontend/src/lib/components/flows/previousResults.ts +++ b/frontend/src/lib/components/flows/previousResults.ts @@ -70,7 +70,6 @@ function getFlowInput( const topFlowInput = schemaToObject(schema, args) const parentState = parentModule ? flowState[parentModule.id] : undefined - if (parentState && parentModule) { if ( parentState.previewArgs && @@ -276,8 +275,9 @@ declare const results = ${JSON.stringify(results)}; */ declare const previous_result: ${previousId ? JSON.stringify(results[previousId]) : 'any'}; -${resume - ? ` +${ + resume + ? ` /** * resume payload */ @@ -288,8 +288,8 @@ declare const resume: any */ declare const approvers: string ` - : '' - } + : '' +} ` } diff --git a/frontend/src/lib/components/flows/propPicker/InputPickerInner.svelte b/frontend/src/lib/components/flows/propPicker/InputPickerInner.svelte index 85849c663a..2b8746e6e6 100644 --- a/frontend/src/lib/components/flows/propPicker/InputPickerInner.svelte +++ b/frontend/src/lib/components/flows/propPicker/InputPickerInner.svelte @@ -17,7 +17,7 @@ getContext('FlowEditorContext') || {} onMount(() => { - testSteps?.updateStepArgs(id, $flowStateStore, flowStore?.val, previewArgs?.val) + testSteps?.updateStepArgs(id, flowStateStore, flowStore?.val, previewArgs?.val) }) const input = $derived(testSteps?.getStepArgs(id)?.value) @@ -44,7 +44,7 @@ {#if testSteps?.isArgManuallySet(id, key)}