diff --git a/frontend/src/lib/components/FlowPreviewContent.svelte b/frontend/src/lib/components/FlowPreviewContent.svelte index a91cabf88e..270cef9446 100644 --- a/frontend/src/lib/components/FlowPreviewContent.svelte +++ b/frontend/src/lib/components/FlowPreviewContent.svelte @@ -143,10 +143,10 @@ })) } catch {} }} - size="md" + size="sm" btnClasses="w-full max-w-lg" > - + Cancel {:else} @@ -199,7 +199,13 @@
{#if jobId} - + { + job = 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 b431f665a6..d6a12bbe26 100644 --- a/frontend/src/lib/components/FlowStatusViewer.svelte +++ b/frontend/src/lib/components/FlowStatusViewer.svelte @@ -1,596 +1,62 @@ -{#if job} -
- {#if innerModules.length > 0} -

Flow result

- {/if} - {#if isListJob} - {#if (flowJobIds?.flowJobs.length ?? 0) > 20} -

- For performance reasons, only the last 20 items are shown. -

- {/if} - {#if render} -
- -
- {/if} - {:else if render} -
0 ? 'border rounded-md shadow p-2' : ''}> - - {#if `result` in job} -
- -
- {:else if job.flow_status?.modules?.[job?.flow_status?.step]?.type === FlowStatusModule.type.WAITING_FOR_EVENTS} - - {:else if job.logs} -
-
{job.logs}
-
- {:else if innerModules?.length > 0} -
- {#each innerModules as mod, i (mod.id)} - {#if mod.type == FlowStatusModule.type.IN_PROGRESS} - {@const rawMod = job.raw_flow?.modules[i]} - -
{mod.id} - - {#if !emptyString(rawMod?.summary)} - {rawMod?.summary ?? ''} - {:else if rawMod?.value.type == 'script'} - {rawMod.value.path ?? ''} - {:else if rawMod?.value.type} - {rawMod?.value.type} - {/if} - - -
- {/if} - {/each} -
- {/if} -
- {/if} - {#if render} - {#if innerModules.length > 0 && !isListJob} - - Graph - - Details - - {/if} - {/if} - {#if render && selected == 'timeline'} - - {/if} -
- {#if isListJob} -

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

- {#if (flowJobIds?.flowJobs.length ?? 0) > 20} -

- For performance reasons, only the last 20 items are shown. -

- {/if} - {#each (flowJobIds?.flowJobs.length ?? 0) > 20 ? flowJobIds?.flowJobs?.slice(showEmbeddeds) ?? [] : flowJobIds?.flowJobs ?? [] as loopJobId, j} - {#if render} - - {/if} -
- { - if (flowJobIds?.moduleId) { - if (flowState?.[flowJobIds.moduleId]) { - 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.type == 'QueuedJob' ? 'Job in progress ...' : e.detail.result - jobFailures[j] = e.detail.success === false - } - if (e.detail.type == 'QueuedJob') { - localFlowModuleStates[flowJobIds.moduleId] = { - type: FlowStatusModule.type.IN_PROGRESS, - started_at: e.detail.started_at - ? new Date(e.detail.started_at).getTime() - : undefined, - logs: e.detail.logs, - job_id: e.detail.id, - args: e.detail.args, - iteration_total: flowJobIds?.flowJobs.length - } - } else { - localFlowModuleStates[flowJobIds.moduleId] = { - started_at: e.detail.started_at - ? new Date(e.detail.started_at).getTime() - : undefined, - args: e.detail.args, - type: e.detail.success - ? FlowStatusModule.type.SUCCESS - : FlowStatusModule.type.FAILURE, - logs: 'All jobs completed', - result: jobResults, - job_id: e.detail.id, - iteration_total: flowJobIds?.flowJobs.length, - duration_ms: e.detail.duration_ms - } - } - } - }} - /> -
- {/each} - {:else if innerModules.length > 0} -
    -

    - Step-by-step results -

    - - {#each innerModules as mod, i} - {#if render} -
    -

    - {#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} -
  • - {#if [FlowStatusModule.type.IN_PROGRESS, FlowStatusModule.type.SUCCESS, FlowStatusModule.type.FAILURE].includes(mod.type)} - {#if job.raw_flow?.modules[i]?.value.type == 'flow'} - onJobsLoaded(mod, e.detail)} - /> - {:else} - onJobsLoaded(mod, e.detail)} - /> - {/if} - {:else} - - {/if} -
  • - {/each} -
- {/if} -
-
- {#if render} - {#if job.raw_flow && !isListJob} -
-
-
-
- {#each Object.values(retry_status) as count} - - Retry in progress, # of failed attempts: {count} - - {/each} - {#if suspend_status} - - Flow suspended, waiting for {pluralize(suspend_status, 'approval')} - - {/if} -
- - { - if (typeof e.detail == 'string') { - if (e.detail == 'Input') { - selectedNode = 'start' - } else if (e.detail == 'Result') { - selectedNode = 'end' - } else { - selectedNode = e.detail - } - } else { - selectedNode = e.detail.id - } - }} - modules={job.raw_flow?.modules ?? []} - failureModule={job.raw_flow?.failure_module} - /> -
-
- {#if selectedNode} - {@const node = localFlowModuleStates[selectedNode]} - {#if selectedNode == 'end'} - - {:else if selectedNode == 'start'} - {#if job.args} -
- -
- {:else} -

No arguments

- {/if} - {:else if node} -
- - {#if node.duration_ms} - - - {msToSec(node.duration_ms)} s - - {/if} - {#if node.job_id} - - {/if} -
-
- -
- - - {:else} -

The execution of this node has no information attached to it. The job likely did - not run yet

- {/if} - {:else}

Select a node to see its details here

{/if} -
-
-
- {/if} - {/if} -{:else} - Job loading... -{/if} - - + { + if (detail.script_path != lastScriptPath && detail.script_path) { + lastScriptPath = detail.script_path + loadOwner(lastScriptPath ?? '') + } + dispatch('jobsLoaded', detail) + }} + {jobId} + {workspaceId} + {isOwner} +/> diff --git a/frontend/src/lib/components/FlowStatusViewerInner.svelte b/frontend/src/lib/components/FlowStatusViewerInner.svelte new file mode 100644 index 0000000000..3cb9efc08e --- /dev/null +++ b/frontend/src/lib/components/FlowStatusViewerInner.svelte @@ -0,0 +1,624 @@ + + +{#if job} +
+ + {#if isListJob} + {#if (flowJobIds?.flowJobs.length ?? 0) > 20} +

+ For performance reasons, only the last 20 items are shown. +

+ {/if} + {#if render} +
+ +
+ {/if} + {:else if render} +
+ + {#if !job} +
+ +
+ {:else if `result` in job} +
+ +
+ {:else if job.flow_status?.modules?.[job?.flow_status?.step]?.type === FlowStatusModule.type.WAITING_FOR_EVENTS} + + {:else if job.logs} +
+
{job.logs}
+
+ {:else if innerModules?.length > 0} +
+ {#each innerModules as mod, i (mod.id)} + {#if mod.type == FlowStatusModule.type.IN_PROGRESS} + {@const rawMod = job.raw_flow?.modules[i]} + +
{mod.id} + + {#if !emptyString(rawMod?.summary)} + {rawMod?.summary ?? ''} + {:else if rawMod?.value.type == 'script'} + {rawMod.value.path ?? ''} + {:else if rawMod?.value.type} + {rawMod?.value.type} + {/if} + + +
+ {/if} + {/each} +
+ {/if} +
+ {/if} + {#if render} + {#if innerModules.length > 0 && !isListJob} + + Graph + Details + + {/if} + {/if} +
+ {#if isListJob} +

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

+ {#if (flowJobIds?.flowJobs.length ?? 0) > 20} +

+ For performance reasons, only the last 20 items are shown. +

+ {/if} + {#each (flowJobIds?.flowJobs.length ?? 0) > 20 ? flowJobIds?.flowJobs?.slice(showEmbeddeds) ?? [] : flowJobIds?.flowJobs ?? [] as loopJobId, j} + {#if render} + + {/if} +
+ { + let modId = flowJobIds?.moduleId + if (modId) { + if ($flowStateStore?.[modId]) { + if ( + !$flowStateStore[modId].previewResult || + !Array.isArray($flowStateStore[modId]?.previewResult) + ) { + $flowStateStore[modId].previewResult = [] + } + $flowStateStore[modId].previewResult[j] = e.detail.result + $flowStateStore[modId].previewArgs = e.detail.args + if (e.detail.type == 'QueuedJob') { + jobResults[j] = 'Job in progress ...' + } else { + jobResults[j] = e.detail.result + jobFailures[j] = e.detail.success === false + } + } + + let started_at = e.detail.started_at + ? new Date(e.detail.started_at).getTime() + : undefined + + let job_id = e.detail.id + if ($durationStatuses[modId] == undefined) { + $durationStatuses[modId] = {} + } + if (e.detail.type == 'QueuedJob') { + $flowModuleStates[modId] = { + type: FlowStatusModule.type.IN_PROGRESS, + started_at, + logs: e.detail.logs, + job_id, + args: e.detail.args, + iteration_total: flowJobIds?.flowJobs.length, + duration_ms: undefined + } + + $durationStatuses[modId][job_id] = { started_at } + console.log('C', started_at) + } else { + $flowModuleStates[modId] = { + started_at, + args: e.detail.args, + type: e.detail.success + ? FlowStatusModule.type.SUCCESS + : FlowStatusModule.type.FAILURE, + logs: 'All jobs completed', + result: jobResults, + job_id, + iteration_total: flowJobIds?.flowJobs.length, + duration_ms: e.detail.duration_ms, + isListJob: true + } + console.log('D', started_at) + $durationStatuses[modId][job_id] = { + started_at, + duration_ms: e.detail.duration_ms + } + } + } + }} + /> +
+ {/each} + {:else if innerModules.length > 0} +
    +

    + Step-by-step results +

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

    + {#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} +
  • + {#if [FlowStatusModule.type.IN_PROGRESS, FlowStatusModule.type.SUCCESS, FlowStatusModule.type.FAILURE].includes(mod.type)} + {#if job.raw_flow?.modules[i]?.value.type == 'flow'} + onJobsLoaded(mod, e.detail)} + /> + {:else} + onJobsLoaded(mod, e.detail)} + /> + {/if} + {:else} + + {/if} +
  • + {/each} +
+ {/if} +
+
+ {#if render} + {#if job.raw_flow && !isListJob} +
+
+
+
+ {#each Object.values($retryStatus) as count} + {#if count} + + Retry in progress, # of failed attempts: {count} + + {/if} + {/each} + {#each Object.values($suspendStatus) as count} + {#if count} + + Flow suspended, waiting for {count} events + + {/if} + {/each} +
+ + { + rightColumnSelect = 'detail' + if (typeof e.detail == 'string') { + if (e.detail == 'Input') { + selectedNode = 'start' + } else if (e.detail == 'Result') { + selectedNode = 'end' + } else { + selectedNode = e.detail + } + } else { + selectedNode = e.detail.id + } + }} + modules={job.raw_flow?.modules ?? []} + failureModule={job.raw_flow?.failure_module} + /> +
+
+ + Timeline + Details + + {#if rightColumnSelect == 'timeline'} + x.id)} + durationStatuses={$durationStatuses} + /> + {:else if rightColumnSelect == 'detail'} +
+ {#if selectedNode} + {@const node = $flowModuleStates[selectedNode]} + + {#if selectedNode == 'end'} + + {:else if selectedNode == 'start'} + {#if job.args} +
+ +
+ {:else} +

No arguments

+ {/if} + {:else if node} +
+ + {#if node.duration_ms} + + + {msToSec(node.duration_ms)} s + + {/if} + {#if node.job_id} + + {/if} +
+ {#if !node.isListJob} +
+ +
+ {/if} + + + {:else} +

The execution of this node has no information attached to it. The job likely + did not run yet

+ {/if} + {:else}

Select a node to see its details here

{/if} +
+ {/if} +
+
+
+ {/if} + {/if} +{:else} + +{/if} + + diff --git a/frontend/src/lib/components/FlowStatusWaitingForEvents.svelte b/frontend/src/lib/components/FlowStatusWaitingForEvents.svelte index 8c322df5c7..edace3a75b 100644 --- a/frontend/src/lib/components/FlowStatusWaitingForEvents.svelte +++ b/frontend/src/lib/components/FlowStatusWaitingForEvents.svelte @@ -5,7 +5,7 @@ import Tooltip from './Tooltip.svelte' import { Button } from './common' - export let is_owner: boolean + export let isOwner: boolean export let workspaceId: string | undefined export let job: Job @@ -31,7 +31,7 @@

Waiting to be resumed

- {#if is_owner} + {#if isOwner}
{/if} diff --git a/frontend/src/lib/components/TimelineBar.svelte b/frontend/src/lib/components/TimelineBar.svelte new file mode 100644 index 0000000000..e43f9bbab7 --- /dev/null +++ b/frontend/src/lib/components/TimelineBar.svelte @@ -0,0 +1,33 @@ + + +{#if min && started_at} +
+
+ + {id} + {#if len}{msToSec(len, 1)}s{/if} + +
+{/if} diff --git a/frontend/src/lib/components/graph/FlowGraph.svelte b/frontend/src/lib/components/graph/FlowGraph.svelte index 47cac19ba3..f5e10ce566 100644 --- a/frontend/src/lib/components/graph/FlowGraph.svelte +++ b/frontend/src/lib/components/graph/FlowGraph.svelte @@ -767,6 +767,7 @@ +
+} + +export type FlowStatusViewerContext = { + flowStateStore?: Writable, + flowModuleStates: Writable> + retryStatus: Writable> + suspendStatus: Writable>, + durationStatuses: Writable>> +} export type GraphModuleState = { type: FlowStatusModule.type args: any @@ -37,6 +51,8 @@ export type GraphModuleState = { retries?: number duration_ms?: number started_at?: number + suspend_count?: number + isListJob?: boolean } export type NestedNodes = GraphItem[] diff --git a/frontend/src/lib/components/jobs/JobPreview.svelte b/frontend/src/lib/components/jobs/JobPreview.svelte index af65247973..79f976e719 100644 --- a/frontend/src/lib/components/jobs/JobPreview.svelte +++ b/frontend/src/lib/components/jobs/JobPreview.svelte @@ -86,6 +86,7 @@ {/if} +
{ return new Promise((resolve) => setTimeout(resolve, ms)) } +export function addIfNotExists(e: T, arr: Array | undefined): Array { + if (!arr) { + return [e] + } else if (arr.includes(e)) { + return arr + } else { + return arr.concat([e]) + } +} + export function validatePassword(password: string): boolean { const re = /^(?=.*[\d])(?=.*[!@#$%^&*])[\w!@#$%^&*]{8,30}$/ return re.test(password)