From 5b618ec1d05741bd42496dc88971e5ccc409900c Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Sun, 27 Nov 2022 09:49:16 +0100 Subject: [PATCH] feat(frontend): flow status viewer include a graph --- .../lib/components/FlowPreviewContent.svelte | 118 ++++---- .../lib/components/FlowStatusViewer.svelte | 271 +++++++++++------- .../lib/components/common/menu/Menu.svelte | 4 +- .../flows/content/FlowInputs.svelte | 2 +- .../flows/content/FlowModuleHeader.svelte | 5 +- .../flows/content/FlowModuleSuspend.svelte | 5 +- .../src/lib/components/flows/flowState.ts | 2 +- .../flows/map/FlowModuleSchemaItem.svelte | 3 +- .../lib/components/flows/map/MapItem.svelte | 3 +- .../src/lib/components/flows/scheduleUtils.ts | 2 +- .../src/lib/components/graph/FlowGraph.svelte | 27 +- 11 files changed, 255 insertions(+), 187 deletions(-) diff --git a/frontend/src/lib/components/FlowPreviewContent.svelte b/frontend/src/lib/components/FlowPreviewContent.svelte index a61356fb74..0a5eb03910 100644 --- a/frontend/src/lib/components/FlowPreviewContent.svelte +++ b/frontend/src/lib/components/FlowPreviewContent.svelte @@ -2,7 +2,7 @@ import { Job, JobService, type Flow, type FlowModule } from '$lib/gen' import { workspaceStore } from '$lib/stores' import { faClose, faPlay, faRefresh } from '@fortawesome/free-solid-svg-icons' - import { Button } from './common' + import { Button, Kbd } from './common' import { createEventDispatcher, getContext } from 'svelte' import Icon from 'svelte-awesome' import { dfs, flowStore } from './flows/flowStore' @@ -96,12 +96,12 @@
-
+
-
-
- -
- -

{ + isRunning = false + try { + jobId && + (await JobService.cancelQueuedJob({ + workspace: $workspaceStore ?? '', + id: jobId, + requestBody: {} + })) + } catch {} + jobId = undefined + }} + size="md" + btnClasses="w-full" > -
- Test {previewMode === 'upTo' - ? `up to step ${$selectedId.split('-').join(',')}` - : ' whole flow'} -
-

-
+ Cancel + + {:else} + + {/if} Fill test args from a request
-
+ + +
-
- {#if isRunning} - - {:else} - - {/if} - - -
- {#if jobId} - onJobsLoaded(detail)} - /> - {/if} +
+ {#if jobId} + onJobsLoaded(detail)} + /> + {:else} +
Flow status will be displayed here
+ {/if} +
diff --git a/frontend/src/lib/components/FlowStatusViewer.svelte b/frontend/src/lib/components/FlowStatusViewer.svelte index e460555dfb..da5f5278d0 100644 --- a/frontend/src/lib/components/FlowStatusViewer.svelte +++ b/frontend/src/lib/components/FlowStatusViewer.svelte @@ -8,8 +8,10 @@ import { createEventDispatcher } from 'svelte' import { onDestroy } from 'svelte' import type { FlowState } from './flows/flowState' - import { Button } from './common' + import { Button, Tab } from './common' import DisplayResult from './DisplayResult.svelte' + import Tabs from './common/tabs/Tabs.svelte' + import { FlowGraph } from './graph' const dispatch = createEventDispatcher() @@ -23,12 +25,28 @@ } | undefined = undefined export let job: Job | undefined = undefined + export let flowModuleStates: Record = {} + + let localFlowModuleStates: Record = {} let jobResults: any[] = [] + let jobFailures: boolean[] = [] let forloop_selected = '' let timeout: NodeJS.Timeout + $: localFlowModuleStates && updateFlowModuleStates() + + function updateFlowModuleStates() { + Object.entries(localFlowModuleStates).forEach(([moduleId, state]) => { + if ( + flowModuleStates[moduleId] !== state && + flowModuleStates[moduleId] !== FlowStatusModule.type.FAILURE + ) { + flowModuleStates[moduleId] = state + } + }) + } let lastSize = 0 $: { let len = (flowJobIds?.flowJobs ?? []).length @@ -70,6 +88,7 @@ function updateJobId() { if (jobId !== job?.id) { + localFlowModuleStates = {} loadJobInProgress() } } @@ -81,10 +100,12 @@ onDestroy(() => { timeout && clearTimeout(timeout) }) + + let selected: 'graph' | 'sequence' = 'graph' {#if job} -
+
{#if innerModules.length > 0}

Flow result

{/if} @@ -107,119 +128,153 @@
{/if} - {#if isListJob} -

- Embedded flows: ({flowJobIds?.flowJobs.length} items) -

- {#each flowJobIds?.flowJobs ?? [] as loopJobId, j} - -
- { - if (flowJobIds?.moduleId) { - if (flowState) { - if ( - !flowState[flowJobIds.moduleId].previewResult || - !Array.isArray(flowState[flowJobIds.moduleId]?.previewResult) - ) { - flowState[flowJobIds.moduleId].previewResult = [] - } - flowState[flowJobIds.moduleId].previewResult[j] = e.detail.result - flowState[flowJobIds.moduleId].previewArgs = e.detail.args - jobResults[j] = e.detail.result == null ? 'Job in progress ...' : e.detail.result - } +
+ {#if isListJob} +

+ Embedded flows: ({flowJobIds?.flowJobs.length} items) +

+ {#each flowJobIds?.flowJobs ?? [] as loopJobId, j} +
- {/each} - {:else if innerModules.length > 0} -
    -

    - Step-by-step results -

    + > + + #{j + 1}: {loopJobId} + - {#each innerModules as mod, i} -
    -

    - {#if job?.raw_flow?.modules && i < job?.raw_flow?.modules.length} - Step - - {i + 1} - - out of - {job?.raw_flow?.modules.length} - {#if job.raw_flow?.modules[i]?.summary} - : - {job.raw_flow?.modules[i]?.summary ?? ''} + + +
    + { + if (flowJobIds?.moduleId) { + if (flowState) { + if ( + !flowState[flowJobIds.moduleId].previewResult || + !Array.isArray(flowState[flowJobIds.moduleId]?.previewResult) + ) { + flowState[flowJobIds.moduleId].previewResult = [] + } + flowState[flowJobIds.moduleId].previewResult[j] = e.detail.result + flowState[flowJobIds.moduleId].previewArgs = e.detail.args + jobResults[j] = + e.detail.result == null ? 'Job in progress ...' : e.detail.result + jobFailures[j] = e.detail.success === false + } + } + }} + /> +
    + {/each} + {:else if innerModules.length > 0} +
      +

      + Step-by-step results +

      + + {#each innerModules as mod, i} +
      +

      + {#if job?.raw_flow?.modules && i < job?.raw_flow?.modules.length} + Step + + {i + 1} + + out of + {job?.raw_flow?.modules.length} + {#if job.raw_flow?.modules[i]?.summary} + : + {job.raw_flow?.modules[i]?.summary ?? ''} + + {/if} + {:else} +

      Failure module

      + {/if} +

    +
    +
  • + {#if [FlowStatusModule.type.IN_PROGRESS, FlowStatusModule.type.SUCCESS, FlowStatusModule.type.FAILURE].includes(mod.type)} + { + if (mod.id && (mod.flow_jobs ?? []).length == 0) { + if (flowState && flowState[mod.id]) { + flowState[mod.id].previewResult = e.detail.result + flowState[mod.id].previewArgs = e.detail.args + } + if (e.detail.type == 'QueuedJob') { + localFlowModuleStates[mod.id] = FlowStatusModule.type.IN_PROGRESS + } else { + localFlowModuleStates[mod.id] = e.detail.success + ? FlowStatusModule.type.SUCCESS + : FlowStatusModule.type.FAILURE + } + } + }} + /> + {:else} + + + + {#if mod.type == FlowStatusModule.type.WAITING_FOR_EVENT} + Waiting to be resumed by receivent events such as approvals + {:else if mod.type == FlowStatusModule.type.WAITING_FOR_PRIOR_STEPS} + Waiting for prior steps to complete + {:else if mod.type == FlowStatusModule.type.WAITING_FOR_EXECUTOR} + Job is ready to be executed and will be picked up by the next available worker + {/if} {/if} - {:else} -

    Failure module

    - {/if} - -
    -
  • - {#if [FlowStatusModule.type.IN_PROGRESS, FlowStatusModule.type.SUCCESS, FlowStatusModule.type.FAILURE].includes(mod.type)} - { - if (mod.id && (mod.flow_jobs ?? []).length == 0) { - if (flowState && flowState[mod.id]) { - flowState[mod.id].previewResult = e.detail.result - flowState[mod.id].previewArgs = e.detail.args - } - } - }} - /> - {:else} - - - - {#if mod.type == FlowStatusModule.type.WAITING_FOR_EVENT} - Waiting to be resumed by receivent events such as approvals - {:else if mod.type == FlowStatusModule.type.WAITING_FOR_PRIOR_STEPS} - Waiting for prior steps to complete - {:else if mod.type == FlowStatusModule.type.WAITING_FOR_EXECUTOR} - Job is ready to be executed and will be picked up by the next available worker - {/if} - - {/if} -
  • - {/each} -
- {/if} + + {/each} + + {/if} +
+ {#if job.raw_flow && !isListJob} +
+ +
+ {/if} {:else} Job loading... {/if} diff --git a/frontend/src/lib/components/common/menu/Menu.svelte b/frontend/src/lib/components/common/menu/Menu.svelte index 3d529e7796..7dc5136970 100644 --- a/frontend/src/lib/components/common/menu/Menu.svelte +++ b/frontend/src/lib/components/common/menu/Menu.svelte @@ -48,8 +48,8 @@
-
(show = !show)}> - +
(show = !show)} class="cursor-pointer hover:bg-gray-100/30"> +
{#if show} diff --git a/frontend/src/lib/components/flows/content/FlowInputs.svelte b/frontend/src/lib/components/flows/content/FlowInputs.svelte index ba370acd7c..e60dc62780 100644 --- a/frontend/src/lib/components/flows/content/FlowInputs.svelte +++ b/frontend/src/lib/components/flows/content/FlowInputs.svelte @@ -74,7 +74,7 @@ dispatch('new', { language: RawScript.language.PYTHON3, kind, - subkind: kind + subkind: 'flow' }) }} /> diff --git a/frontend/src/lib/components/flows/content/FlowModuleHeader.svelte b/frontend/src/lib/components/flows/content/FlowModuleHeader.svelte index 36c25765c6..a288c0636f 100644 --- a/frontend/src/lib/components/flows/content/FlowModuleHeader.svelte +++ b/frontend/src/lib/components/flows/content/FlowModuleHeader.svelte @@ -40,7 +40,10 @@ dispatch('toggleSuspend')} > diff --git a/frontend/src/lib/components/flows/content/FlowModuleSuspend.svelte b/frontend/src/lib/components/flows/content/FlowModuleSuspend.svelte index 61569586cc..36aa72910e 100644 --- a/frontend/src/lib/components/flows/content/FlowModuleSuspend.svelte +++ b/frontend/src/lib/components/flows/content/FlowModuleSuspend.svelte @@ -73,8 +73,9 @@

Sleep - If defined, at the end of the step, the flow will sleep for a number of seconds before being - resumed. Sleeping is passive and does not consume any resources.

{/if} - {#if suspend} + {#if suspend || sleep}
diff --git a/frontend/src/lib/components/flows/map/MapItem.svelte b/frontend/src/lib/components/flows/map/MapItem.svelte index a2f665ba00..e042e91e20 100644 --- a/frontend/src/lib/components/flows/map/MapItem.svelte +++ b/frontend/src/lib/components/flows/map/MapItem.svelte @@ -28,7 +28,8 @@ selected: $selectedId === mod.id, retry: mod.retry?.constant != undefined || mod.retry?.exponential != undefined, earlyStop: mod.stop_after_if != undefined, - suspend: Boolean(mod.suspend) + suspend: Boolean(mod.suspend), + sleep: Boolean(mod.sleep) } function onDelete(event: CustomEvent) { diff --git a/frontend/src/lib/components/flows/scheduleUtils.ts b/frontend/src/lib/components/flows/scheduleUtils.ts index 2fd1193ef0..a1590287cb 100644 --- a/frontend/src/lib/components/flows/scheduleUtils.ts +++ b/frontend/src/lib/components/flows/scheduleUtils.ts @@ -14,7 +14,7 @@ export async function loadFlowSchedule(path: string, workspace: string = ''): Pr }) if (!existsSchedule) { - throw new Error(`Flow at path: ${path} doesn't exit`) + throw new Error(`Flow at path: ${path} doesn't exist`) } const schedule = await ScheduleService.getSchedule({ diff --git a/frontend/src/lib/components/graph/FlowGraph.svelte b/frontend/src/lib/components/graph/FlowGraph.svelte index e6ad024d3a..1476095782 100644 --- a/frontend/src/lib/components/graph/FlowGraph.svelte +++ b/frontend/src/lib/components/graph/FlowGraph.svelte @@ -1,7 +1,7 @@