From 659713b45bfb8ffa3790a092475425852e70151f Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Tue, 19 Nov 2024 10:27:33 +0100 Subject: [PATCH] fix: improve flow status viewer for iterations (#4744) * fix: improve flow status viewer iteration picker * check * check * nit rm console log * progress * progress * progress --- .../components/FlowStatusViewerInner.svelte | 403 +++++++++++------- .../lib/components/common/menu/MenuV2.svelte | 2 +- .../flows/content/FlowInputsQuick.svelte | 2 +- .../components/flows/map/FlowJobsMenu.svelte | 32 +- .../lib/components/flows/map/MapItem.svelte | 7 +- frontend/src/lib/components/graph/model.ts | 1 + .../renderers/nodes/BranchOneStart.svelte | 1 + .../renderers/nodes/ForLoopStartNode.svelte | 14 +- .../graph/renderers/nodes/ModuleNode.svelte | 8 +- 9 files changed, 300 insertions(+), 170 deletions(-) diff --git a/frontend/src/lib/components/FlowStatusViewerInner.svelte b/frontend/src/lib/components/FlowStatusViewerInner.svelte index 2c9aa54461..c45512f03a 100644 --- a/frontend/src/lib/components/FlowStatusViewerInner.svelte +++ b/frontend/src/lib/components/FlowStatusViewerInner.svelte @@ -11,7 +11,7 @@ import { base } from '$lib/base' import FlowJobResult from './FlowJobResult.svelte' import FlowPreviewStatus from './preview/FlowPreviewStatus.svelte' - import { createEventDispatcher, getContext } from 'svelte' + import { createEventDispatcher, getContext, tick } from 'svelte' import { onDestroy } from 'svelte' import { Badge, Button, Tab } from './common' import DisplayResult from './DisplayResult.svelte' @@ -25,7 +25,7 @@ import { deepEqual } from 'fast-equals' import FlowTimeline from './FlowTimeline.svelte' import { dfs } from './flows/dfs' - import { writable, type Writable } from 'svelte/store' + 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' @@ -57,7 +57,10 @@ //only useful when forloops are optimized and the job doesn't contain the mod id anymore export let innerModule: FlowModuleValue | undefined = undefined - + export let globalRefreshes: Record< + string, + (clear, root) => Promise + > = {} export let render = true export let isOwner = false @@ -66,11 +69,9 @@ export let globalModuleStates: Writable>[] export let globalDurationStatuses: Writable>[] - export let globalRefreshes: Record< - string, - (loopJob: { index: number; job: string }) => Promise - > = {} + export let childFlow: boolean = false + export let isSubflow: boolean = false export let reducedPolling = false export let wideResults = false @@ -79,6 +80,13 @@ export let prefix: string | undefined = undefined export let subflowParentsGlobalModuleStates: Writable>[] = [] export let subflowParentsDurationStatuses: Writable>[] = [] + export let isForloopSelected = false + export let parentRecursiveRefresh: Record void> = {} + export let job: Job | undefined = undefined + + + let recursiveRefresh: Record void> = {} + let jobResults: any[] = flowJobIds?.flowJobs?.map((x, id) => `iter #${id + 1} not loaded by frontend yet`) ?? [] @@ -90,7 +98,18 @@ let localDurationStatuses: Writable> = writable({}) let expandedSubflows: Record = {} - export let job: Job | undefined = undefined + $: flowJobIds?.moduleId && onFlowModuleId() + + 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 lastSize = 0 // $: { @@ -107,9 +126,33 @@ keepType: boolean | undefined ) { moduleState.update((x) => { + + if (newValue.selectedForloop != undefined && x[key]?.selectedForloop != undefined && newValue.selectedForloop != x[key].selectedForloop) { + return x + } + + if (x[key]?.selectedForLoopSetManually) { + if (newValue.selectedForloop != undefined && x[key]?.selectedForloop != newValue.selectedForloop) { + return x + } else { + newValue.selectedForLoopSetManually = true + newValue.selectedForloopIndex = x[key]?.selectedForloopIndex + newValue.selectedForloop = x[key]?.selectedForloop + + } + } else if (x[key]?.selectedForloopIndex != undefined) { + newValue.selectedForloopIndex = x[key]?.selectedForloopIndex + newValue.selectedForloop = x[key]?.selectedForloop + + } + + if (keepType && (x[key]?.type == 'Success' || x[key]?.type == 'Failure')) { newValue.type = x[key].type } + + + x[key] = newValue return x }) @@ -118,6 +161,53 @@ function buildSubflowKey(key: string, prefix: string | undefined) { return prefix ? 'subflow:' + prefix + key : key } + + async function refresh( + clearLoop: boolean, + root: boolean + ) { + + let modId = flowJobIds?.moduleId + + + if (clearLoop) { + if (modId && !root) { + globalModuleStates?.[globalModuleStates?.length - 1]?.update((x) => { + if (modId) { + delete x[modId] + } + return x + }) + } + } else { + + let state = modId ? getTopModuleStates()?.[modId] : undefined + let loopjob = state?.selectedForloop + let njob = flowJobIds && modId && loopjob ? storedListJobs?.[loopjob] : job + if (njob) { + dispatch('jobsLoaded', { job: njob, force: true }) + } + } + + await Promise.all(Object.entries(recursiveRefresh).map(async ([key, v]) => { + await tick() + await v(clearLoop, false) + })) + + + + } + + function updateRecursiveRefresh(jobId: string) { + if (jobId) { + parentRecursiveRefresh[jobId] = async (clear, root) => { + (globalModuleStates.length > 0 || isSubflow) && (await refresh(clear, root)) + } + } + } + + + function setModuleState( key: string, value: Partial, @@ -212,6 +302,7 @@ } } + function updateInnerModules() { if ($localModuleStates) { innerModules.forEach((mod, i) => { @@ -256,7 +347,17 @@ }, true ) + } else if (isForloopSelected) { + setModuleState(mod.id ?? '', {}, true) } + + // if (isForloopSelected && mod?.flow_jobs) { + // let states = getTopModuleStates() + // if (states) { + // states[mod.id ?? ''] = $localModuleStates[mod.id ?? ''] + // } + // } + if (mod.branch_chosen) { setModuleState( mod.id ?? '', @@ -284,63 +385,32 @@ } } - let recursiveRefresh: Record Promise> = {} - export async function refresh( - root: boolean, - loopJob: { index: number; job: string } | undefined - ) { - let modId = flowJobIds?.moduleId - if (!loopJob) { - loopJob = { - index: $localModuleStates[modId ?? '']?.selectedForloopIndex ?? 0, - job: $localModuleStates[modId ?? '']?.selectedForloop ?? '' - } - } + $: isForloopSelected && globalModuleStates && loadJobInProgress() - let last = root ? undefined : flowJobIds?.flowJobs?.[flowJobIds?.flowJobs.length - 1] - - // console.log(innerModule, modId) - - Object.entries(recursiveRefresh).forEach(([key, v]) => { - if (modId) { - if ((root && key == loopJob?.job) || key == last) { - v(false) - } - } else { - v(false) - } - }) - - let njob = flowJobIds - ? root && modId - ? storedListJobs?.[loopJob.job] - : storedListJobs?.[flowJobIds.length - 1] - : job - - if (njob) { - dispatch('jobsLoaded', { job: njob, force: true }) + async function getNewJob(jobId: string, initialJob: Job | undefined) { + if (jobId == initialJob?.id && + initialJob?.id != undefined && + initialJob?.type === 'CompletedJob') { + return initialJob; + } else { + return await JobService.getJob({ + workspace: workspaceId ?? $workspaceStore ?? '', + id: jobId ?? '', + noLogs: true + }); } } - + let errorCount = 0 let notAnonynmous = false async function loadJobInProgress() { dispatch('start') if (jobId != '00000000-0000-0000-0000-000000000000') { try { - const newJob = - jobId == initialJob?.id && - initialJob?.id != undefined && - initialJob?.type === 'CompletedJob' - ? initialJob - : await JobService.getJob({ - workspace: workspaceId ?? $workspaceStore ?? '', - id: jobId ?? '', - noLogs: true - }) - if (!deepEqual(job, newJob)) { + const newJob = await getNewJob(jobId, initialJob) + if (!deepEqual(job, newJob) || isForloopSelected) { job = newJob job?.flow_status && updateStatus(job?.flow_status) dispatch('jobsLoaded', { job, force: false }) @@ -366,8 +436,14 @@ } let destroyed = false + + + updateRecursiveRefresh(jobId) + + async function updateJobId() { if (jobId !== job?.id) { + $localModuleStates = {} flowTimeline?.reset() timeout && clearTimeout(timeout) @@ -393,33 +469,39 @@ }) ) } else { + updateRecursiveRefresh(jobId) + recursiveRefresh = {} $localDurationStatuses = {} } await loadJobInProgress() - } + } } $: jobId && updateJobId() $: isListJob = flowJobIds != undefined && Array.isArray(flowJobIds?.flowJobs) - $: flowJobIds?.moduleId && onFlowJobFlowStatus() - function onFlowJobFlowStatus() { - if (globalRefreshes) { - let modId = flowJobIds?.moduleId - if (modId) { - globalRefreshes[buildSubflowKey(modId, prefix)] = async (loopJob) => { - setIteration(loopJob.index, loopJob.job, false, modId ?? '') - refresh(true, loopJob) - } - } - } + function getTopModuleStates() { + return get(globalModuleStates?.[globalModuleStates?.length - 1]) } + let forloop_selected = getTopModuleStates()?.[flowJobIds?.moduleId ?? '']?.selectedForloop + + let sub: Unsubscriber | undefined = undefined + $: if (flowJobIds?.moduleId) { + sub?.() + sub = globalModuleStates?.[globalModuleStates?.length - 1].subscribe((x) => { + forloop_selected = x[flowJobIds?.moduleId ?? '']?.selectedForloop + }) + } + + onDestroy(() => { destroyed = true timeout && clearTimeout(timeout) + sub?.() + }) $: selected = isListJob ? 'sequence' : 'graph' @@ -500,46 +582,48 @@ } } - function setIteration(j: number, id: string, clicked: boolean, modId: string) { - if (modId) { - if (!$localModuleStates?.[modId]) { - $localModuleStates[modId] = { - type: 'InProgress', - args: undefined - } - } - let state = $localModuleStates?.[modId] - if (state) { - if (state.selectedForloop == id && clicked) { - setModuleState( - modId, - { - selectedForloop: undefined, - selectedForloopIndex: -1 - }, - false, - true - ) - } else { - setModuleState( - modId, - { + + + + async function setIteration(j: number, id: string, clicked: boolean, modId: string, isForloop: boolean) { + if (modId) { + + if (clicked) { + await globalRefreshes?.[modId]?.(true, true) + + } + globalModuleStates?.[globalModuleStates?.length - 1]?.update((topLevelModuleStates) => { + let state = topLevelModuleStates?.[modId] + if (state) { + let manualOnce = state.selectedForLoopSetManually + if (clicked || (!manualOnce && (state == undefined || !isForloop || j >= (state.selectedForloopIndex ?? -1)) )){ + let setManually = clicked || manualOnce + + topLevelModuleStates[modId] = { + ...(topLevelModuleStates[modId] ?? {}), selectedForloop: id, - selectedForloopIndex: j - }, - false, - true - ) - clicked && refresh(true, undefined) + selectedForloopIndex: j, + selectedForLoopSetManually: setManually ?? false + } + + // clicked && callGlobRefresh(modId, {index: j, job: id, selectedManually: setManually ?? false}) + } } - } - } + return topLevelModuleStates + }) + + if (clicked) { + await globalRefreshes?.[modId]?.(false, true) + } + } } + function innerJobLoaded(jobLoaded: Job, j: number, clicked: boolean, force: boolean) { let modId = flowJobIds?.moduleId + if (modId) { - setIteration(j, jobLoaded.id, clicked, modId) + setIteration(j, jobLoaded.id, clicked, modId, innerModule?.type == 'forloopflow' || innerModule?.type == 'whileloopflow') if ($flowStateStore && $flowStateStore?.[modId] == undefined) { $flowStateStore[modId] = { @@ -572,48 +656,46 @@ let job_id = jobLoaded.id initializeByJob(modId) - if (jobLoaded.type == 'QueuedJob') { - if ($localModuleStates[modId]?.selectedForloopIndex == j) { - setModuleState( - modId, - { - started_at, - logs: jobLoaded.logs, - job_id, - args: jobLoaded.args, - flow_jobs: flowJobIds?.flowJobs, - flow_jobs_success: flowJobIds?.flowJobsSuccess, - iteration_total: flowJobIds?.length, - duration_ms: undefined - }, - force, - true - ) + let v: Partial = { + started_at, + flow_jobs: flowJobIds?.flowJobs, + flow_jobs_success: flowJobIds?.flowJobsSuccess, + iteration_total: flowJobIds?.length, + duration_ms: undefined } + + let currentIndex = getTopModuleStates()?.[modId]?.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') { + v.type = 'InProgress' + } + } else if (jobLoaded.type == 'CompletedJob') { + v.flow_jobs_results = jobResults + if (currentIndex) { + v.result = jobLoaded.result + } + } + setModuleState( + modId, + v, + force, + true + ) + + if (jobLoaded.type == 'QueuedJob') { + setDurationStatusByJob(modId, job_id, { created_at, started_at }) } else if (jobLoaded.type == 'CompletedJob') { - if ($localModuleStates[modId]?.selectedForloopIndex == j) { - setModuleState( - modId, - { - started_at, - args: jobLoaded.args, - result: jobLoaded.result, - flow_jobs_results: jobResults, - job_id, - flow_jobs: flowJobIds?.flowJobs, - flow_jobs_success: flowJobIds?.flowJobsSuccess, - iteration_total: flowJobIds?.length, - duration_ms: undefined, - isListJob: true - }, - force, - true - ) - } + setDurationStatusByJob(modId, job_id, { created_at, started_at, @@ -702,6 +784,8 @@ return rec(ids, undefined) } + + {#if notAnonynmous} @@ -826,8 +910,6 @@
{#if isListJob} {@const sliceFrom = $localDurationStatuses[flowJobIds?.moduleId ?? '']?.iteration_from ?? 0} - {@const forloop_selected = - $localModuleStates?.[flowJobIds?.moduleId ?? '']?.selectedForloop}

Subflows ({flowJobIds?.flowJobs.length})

@@ -865,20 +947,23 @@ {/if} {#if j >= sliceFrom || forloop_selected == loopJobId} + {@const forloopIsSelected = forloop_selected == loopJobId || (innerModule?.type != 'forloopflow' && innerModule?.type != 'whileloopflow')}
20} + isForloopSelected={forloop_selected == loopJobId && (innerModule?.type == 'forloopflow' || innerModule?.type == 'whileloopflow')} + reducedPolling={reducedPolling || (flowJobIds?.flowJobs.length && flowJobIds?.flowJobs.length > 20)} {workspaceId} jobId={loopJobId} on:jobsLoaded={(e) => { @@ -950,7 +1035,7 @@
@@ -969,7 +1054,7 @@ {#if job.raw_flow?.modules[i]?.value.type == 'flow'} { let { force, job } = e.detail @@ -997,7 +1084,7 @@ {:else} { + on:selectedIteration={async (e) => { let detail = e.detail + if (detail.manuallySet) { + await globalRefreshes?.[detail.moduleId]?.(true, true) - setModuleState(detail.moduleId, { - selectedForloop: detail.id, - selectedForloopIndex: detail.index - }) - globalRefreshes[detail.moduleId]?.({ job: detail.id, index: detail.index }) - // console.log('selectedIteration', prefix, detail.moduleId, globalRefreshes) + $localModuleStates[detail.moduleId] = { + ...$localModuleStates[detail.moduleId], + selectedForloop: detail.id, + selectedForloopIndex: detail.index, + selectedForLoopSetManually: true + } + + await globalRefreshes?.[detail.moduleId]?.(false, true) + + } else { + $localModuleStates[detail.moduleId] = { + ...$localModuleStates[detail.moduleId], + selectedForLoopSetManually: false + } + } }} modules={job.raw_flow?.modules ?? []} failureModule={job.raw_flow?.failure_module} diff --git a/frontend/src/lib/components/common/menu/MenuV2.svelte b/frontend/src/lib/components/common/menu/MenuV2.svelte index 6354ed4cb1..c4a79f5429 100644 --- a/frontend/src/lib/components/common/menu/MenuV2.svelte +++ b/frontend/src/lib/components/common/menu/MenuV2.svelte @@ -18,7 +18,7 @@ }) - +
diff --git a/frontend/src/lib/components/flows/content/FlowInputsQuick.svelte b/frontend/src/lib/components/flows/content/FlowInputsQuick.svelte index a85814a5e1..13c3814eba 100644 --- a/frontend/src/lib/components/flows/content/FlowInputsQuick.svelte +++ b/frontend/src/lib/components/flows/content/FlowInputsQuick.svelte @@ -217,7 +217,7 @@ {#if ['script', 'trigger', 'approval', 'preprocessor', 'failure'].includes(selectedKind)} {#if (preFilter === 'all' && owners.length > 0) || preFilter === 'workspace'} {#if preFilter !== 'workspace'} -
Workspace Folders
+
Folders
{/if} {#if owners.length > 0} diff --git a/frontend/src/lib/components/flows/map/FlowJobsMenu.svelte b/frontend/src/lib/components/flows/map/FlowJobsMenu.svelte index c16b602c98..b204218a99 100644 --- a/frontend/src/lib/components/flows/map/FlowJobsMenu.svelte +++ b/frontend/src/lib/components/flows/map/FlowJobsMenu.svelte @@ -1,15 +1,16 @@ - +{#if selectedManually} + + +
(buttonHover = true)} on:mouseleave={() => (buttonHover = false)} on:click={(e) => { + buttonHover = false + dispatch('selectedIteration', { manuallySet: false })} + } + class="absolute top-1.5 right-12 cursor-pointer"> + {#if buttonHover} + + {:else} + + {/if} +
+{/if} + +