mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-21 16:02:28 +00:00
feat(frontend): flow status viewer include a graph
This commit is contained in:
@@ -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 @@
|
||||
<svelte:window on:keydown={onKeyDown} />
|
||||
|
||||
<div class="flex divide-y flex-col space-y-2 h-screen bg-white px-6 py-2 w-full">
|
||||
<div class="flex flex-row justify-between w-full items-center gap-x-1">
|
||||
<div class="flex flex-row justify-between w-full items-center gap-x-2">
|
||||
<Button
|
||||
variant="border"
|
||||
size="lg"
|
||||
color="dark"
|
||||
btnClasses="!p-0 !w-8 !h-8"
|
||||
btnClasses="!p-0 !w-16 !h-full"
|
||||
on:click={() => {
|
||||
dispatch('close')
|
||||
}}
|
||||
@@ -109,80 +109,70 @@
|
||||
<Icon data={faClose} />
|
||||
</Button>
|
||||
|
||||
<div class="flex flex-row justify-center items-center">
|
||||
<div class="flex justify-center p-2 w-8 h-8 bg-blue-200 rounded-lg mr-2 ">
|
||||
<Icon data={faPlay} scale={1} class="text-blue-500" />
|
||||
</div>
|
||||
|
||||
<h3
|
||||
class="text-lg leading-6 font-bold text-gray-900 inline-flex flex-row justify-between w-full"
|
||||
{#if isRunning}
|
||||
<Button
|
||||
disabled={!isValid}
|
||||
color="red"
|
||||
on:click={async () => {
|
||||
isRunning = false
|
||||
try {
|
||||
jobId &&
|
||||
(await JobService.cancelQueuedJob({
|
||||
workspace: $workspaceStore ?? '',
|
||||
id: jobId,
|
||||
requestBody: {}
|
||||
}))
|
||||
} catch {}
|
||||
jobId = undefined
|
||||
}}
|
||||
size="md"
|
||||
btnClasses="w-full"
|
||||
>
|
||||
<div>
|
||||
Test {previewMode === 'upTo'
|
||||
? `up to step ${$selectedId.split('-').join(',')}`
|
||||
: ' whole flow'}
|
||||
</div>
|
||||
</h3>
|
||||
</div>
|
||||
Cancel
|
||||
</Button>
|
||||
{:else}
|
||||
<Button
|
||||
variant="contained"
|
||||
startIcon={{ icon: isRunning ? faRefresh : faPlay }}
|
||||
color="blue"
|
||||
size="sm"
|
||||
btnClasses="w-full"
|
||||
disabled={!isValid}
|
||||
on:click={() => runPreview($previewArgs)}
|
||||
>
|
||||
Test flow <Kbd class="ml-2">Ctrl+Enter</Kbd>
|
||||
</Button>
|
||||
{/if}
|
||||
<Button
|
||||
btnClasses="ml-2"
|
||||
btnClasses="h-full"
|
||||
size="sm"
|
||||
variant="border"
|
||||
on:click={() => {
|
||||
capturePayload.openDrawer()
|
||||
}}>Fill test args from payload</Button
|
||||
}}>Fill test args from a request</Button
|
||||
>
|
||||
</div>
|
||||
<div class="h-full grow pb-20 max-h-60 overflow-auto">
|
||||
<FlowProgressBar {job} bind:reset={jobProgressReset} />
|
||||
|
||||
<div class="overflow-y-auto grow flex-col flex divide-y divide-gray-600 ">
|
||||
<SchemaForm
|
||||
class="h-full pt-4"
|
||||
compact
|
||||
class="py-4"
|
||||
schema={$flowStore.schema}
|
||||
bind:isValid
|
||||
bind:args={$previewArgs}
|
||||
/>
|
||||
</div>
|
||||
{#if isRunning}
|
||||
<Button
|
||||
disabled={!isValid}
|
||||
color="red"
|
||||
on:click={async () => {
|
||||
isRunning = false
|
||||
try {
|
||||
jobId &&
|
||||
(await JobService.cancelQueuedJob({
|
||||
workspace: $workspaceStore ?? '',
|
||||
id: jobId,
|
||||
requestBody: {}
|
||||
}))
|
||||
} catch {}
|
||||
jobId = undefined
|
||||
}}
|
||||
size="md"
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
{:else}
|
||||
<Button
|
||||
variant="contained"
|
||||
endIcon={{ icon: isRunning ? faRefresh : faPlay }}
|
||||
color="blue"
|
||||
btnClasses="w-full"
|
||||
disabled={!isValid}
|
||||
on:click={() => runPreview($previewArgs)}
|
||||
>
|
||||
Test flow (Ctrl/Cmd + Enter)
|
||||
</Button>
|
||||
{/if}
|
||||
|
||||
<FlowProgressBar {job} bind:reset={jobProgressReset} class="py-4" />
|
||||
|
||||
<div class="h-full overflow-y-auto mb-16 pt-4 grow">
|
||||
{#if jobId}
|
||||
<FlowStatusViewer
|
||||
bind:flowState={$flowStateStore}
|
||||
{jobId}
|
||||
on:jobsLoaded={({ detail }) => onJobsLoaded(detail)}
|
||||
/>
|
||||
{/if}
|
||||
<div class="h-full pt-4 grow">
|
||||
{#if jobId}
|
||||
<FlowStatusViewer
|
||||
bind:flowState={$flowStateStore}
|
||||
{jobId}
|
||||
on:jobsLoaded={({ detail }) => onJobsLoaded(detail)}
|
||||
/>
|
||||
{:else}
|
||||
<div class="italic text-gray-500 h-full grow"> Flow status will be displayed here </div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -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<string, FlowStatusModule.type> = {}
|
||||
|
||||
let localFlowModuleStates: Record<string, FlowStatusModule.type> = {}
|
||||
|
||||
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'
|
||||
</script>
|
||||
|
||||
{#if job}
|
||||
<div class="flow-root w-full space-y-4 ">
|
||||
<div class="flow-root w-full space-y-4">
|
||||
{#if innerModules.length > 0}
|
||||
<h3 class="text-md leading-6 font-bold text-gray-900 border-b pb-2">Flow result</h3>
|
||||
{/if}
|
||||
@@ -107,119 +128,153 @@
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if isListJob}
|
||||
<h3 class="text-md leading-6 font-bold text-gray-600 border-b mb-4">
|
||||
Embedded flows: ({flowJobIds?.flowJobs.length} items)
|
||||
</h3>
|
||||
{#each flowJobIds?.flowJobs ?? [] as loopJobId, j}
|
||||
<Button
|
||||
variant={forloop_selected === loopJobId ? 'contained' : 'border'}
|
||||
color={forloop_selected === loopJobId ? 'dark' : 'light'}
|
||||
btnClasses="w-full flex justify-start"
|
||||
on:click={() => {
|
||||
if (forloop_selected == loopJobId) {
|
||||
forloop_selected = ''
|
||||
} else {
|
||||
forloop_selected = loopJobId
|
||||
}
|
||||
}}
|
||||
>
|
||||
<span class="truncate">
|
||||
#{j + 1}: {loopJobId}
|
||||
</span>
|
||||
{#if innerModules.length > 0 && !isListJob}
|
||||
<Tabs bind:selected>
|
||||
<Tab value="graph"><span class="font-semibold text-md">Graph</span></Tab>
|
||||
<Tab value="sequence"><span class="font-semibold">Details</span></Tab>
|
||||
</Tabs>
|
||||
{/if}
|
||||
|
||||
<Icon
|
||||
class="ml-2"
|
||||
data={forloop_selected == loopJobId ? faChevronUp : faChevronDown}
|
||||
scale={0.8}
|
||||
/>
|
||||
</Button>
|
||||
<div class="border p-6" class:hidden={forloop_selected != loopJobId}>
|
||||
<svelte:self
|
||||
{flowState}
|
||||
jobId={loopJobId}
|
||||
on:jobsLoaded={(e) => {
|
||||
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
|
||||
}
|
||||
<div class="{selected == 'graph' && !isListJob ? 'hidden' : ''} ">
|
||||
{#if isListJob}
|
||||
<h3 class="text-md leading-6 font-bold text-gray-600 border-b mb-4">
|
||||
Embedded flows: ({flowJobIds?.flowJobs.length} items)
|
||||
</h3>
|
||||
{#each flowJobIds?.flowJobs ?? [] as loopJobId, j}
|
||||
<Button
|
||||
variant={forloop_selected === loopJobId ? 'contained' : 'border'}
|
||||
color={jobFailures[j] === true
|
||||
? 'red'
|
||||
: forloop_selected === loopJobId
|
||||
? 'dark'
|
||||
: 'light'}
|
||||
btnClasses="w-full flex justify-start"
|
||||
on:click={() => {
|
||||
if (forloop_selected == loopJobId) {
|
||||
forloop_selected = ''
|
||||
} else {
|
||||
forloop_selected = loopJobId
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{/each}
|
||||
{:else if innerModules.length > 0}
|
||||
<ul class="w-full">
|
||||
<h3 class="text-md leading-6 font-bold text-gray-900 border-b mb-4 py-2">
|
||||
Step-by-step results
|
||||
</h3>
|
||||
>
|
||||
<span class="truncate">
|
||||
#{j + 1}: {loopJobId}
|
||||
</span>
|
||||
|
||||
{#each innerModules as mod, i}
|
||||
<div class="line w-8 h-10" />
|
||||
<h3 class="text-gray-500 mb-2 w-full">
|
||||
{#if job?.raw_flow?.modules && i < job?.raw_flow?.modules.length}
|
||||
Step
|
||||
<span class="font-medium text-gray-900">
|
||||
{i + 1}
|
||||
</span>
|
||||
out of
|
||||
<span class="font-medium text-gray-900">{job?.raw_flow?.modules.length}</span>
|
||||
{#if job.raw_flow?.modules[i]?.summary}
|
||||
: <span class="font-medium text-gray-900">
|
||||
{job.raw_flow?.modules[i]?.summary ?? ''}
|
||||
<Icon
|
||||
class="ml-2"
|
||||
data={forloop_selected == loopJobId ? faChevronUp : faChevronDown}
|
||||
scale={0.8}
|
||||
/>
|
||||
</Button>
|
||||
<div class="border p-6" class:hidden={forloop_selected != loopJobId}>
|
||||
<svelte:self
|
||||
bind:flowState
|
||||
bind:flowModuleStates={localFlowModuleStates}
|
||||
jobId={loopJobId}
|
||||
on:jobsLoaded={(e) => {
|
||||
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
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{/each}
|
||||
{:else if innerModules.length > 0}
|
||||
<ul class="w-full">
|
||||
<h3 class="text-md leading-6 font-bold text-gray-900 border-b mb-4 py-2">
|
||||
Step-by-step results
|
||||
</h3>
|
||||
|
||||
{#each innerModules as mod, i}
|
||||
<div class="line w-8 h-10" />
|
||||
<h3 class="text-gray-500 mb-2 w-full">
|
||||
{#if job?.raw_flow?.modules && i < job?.raw_flow?.modules.length}
|
||||
Step
|
||||
<span class="font-medium text-gray-900">
|
||||
{i + 1}
|
||||
</span>
|
||||
out of
|
||||
<span class="font-medium text-gray-900">{job?.raw_flow?.modules.length}</span>
|
||||
{#if job.raw_flow?.modules[i]?.summary}
|
||||
: <span class="font-medium text-gray-900">
|
||||
{job.raw_flow?.modules[i]?.summary ?? ''}
|
||||
</span>
|
||||
{/if}
|
||||
{:else}
|
||||
<h3>Failure module</h3>
|
||||
{/if}
|
||||
</h3>
|
||||
<div class="line w-8 h-10" />
|
||||
<li class="w-full border border-gray-600 p-6 space-y-2 bg-blue-50/50">
|
||||
{#if [FlowStatusModule.type.IN_PROGRESS, FlowStatusModule.type.SUCCESS, FlowStatusModule.type.FAILURE].includes(mod.type)}
|
||||
<svelte:self
|
||||
bind:flowState
|
||||
bind:flowModuleStates={localFlowModuleStates}
|
||||
jobId={mod.job}
|
||||
flowJobIds={mod.flow_jobs
|
||||
? {
|
||||
moduleId: mod.id,
|
||||
flowJobs: mod.flow_jobs
|
||||
}
|
||||
: undefined}
|
||||
on:jobsLoaded={(e) => {
|
||||
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}
|
||||
<span class="italic text-gray-600">
|
||||
<Icon data={faHourglassHalf} class="mr-2" />
|
||||
|
||||
{#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}
|
||||
</span>
|
||||
{/if}
|
||||
{:else}
|
||||
<h3>Failure module</h3>
|
||||
{/if}
|
||||
</h3>
|
||||
<div class="line w-8 h-10" />
|
||||
<li class="w-full border border-gray-600 p-6 space-y-2 bg-blue-50/50">
|
||||
{#if [FlowStatusModule.type.IN_PROGRESS, FlowStatusModule.type.SUCCESS, FlowStatusModule.type.FAILURE].includes(mod.type)}
|
||||
<svelte:self
|
||||
{flowState}
|
||||
jobId={mod.job}
|
||||
flowJobIds={mod.flow_jobs
|
||||
? {
|
||||
moduleId: mod.id,
|
||||
flowJobs: mod.flow_jobs
|
||||
}
|
||||
: undefined}
|
||||
on:jobsLoaded={(e) => {
|
||||
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}
|
||||
<span class="italic text-gray-600">
|
||||
<Icon data={faHourglassHalf} class="mr-2" />
|
||||
|
||||
{#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}
|
||||
</span>
|
||||
{/if}
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
{/if}
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{#if job.raw_flow && !isListJob}
|
||||
<div class="{selected != 'graph' ? 'hidden' : ''} lg:mx-40 mx-10 border border-gray-400 mt-4">
|
||||
<FlowGraph
|
||||
flowModuleStates={localFlowModuleStates}
|
||||
notSelectable
|
||||
modules={job.raw_flow?.modules ?? []}
|
||||
failureModule={job.raw_flow?.failure_module}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
{:else}
|
||||
Job loading...
|
||||
{/if}
|
||||
|
||||
@@ -48,8 +48,8 @@
|
||||
|
||||
<div class="relative" bind:this={menu}>
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div on:click={() => (show = !show)}>
|
||||
<slot name="trigger" />
|
||||
<div on:click={() => (show = !show)} class="cursor-pointer hover:bg-gray-100/30">
|
||||
<slot class="triggerable" name="trigger" />
|
||||
</div>
|
||||
|
||||
{#if show}
|
||||
|
||||
@@ -74,7 +74,7 @@
|
||||
dispatch('new', {
|
||||
language: RawScript.language.PYTHON3,
|
||||
kind,
|
||||
subkind: kind
|
||||
subkind: 'flow'
|
||||
})
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -40,7 +40,10 @@
|
||||
<Icon data={faArrowRotateForward} scale={0.8} />
|
||||
</span>
|
||||
<span
|
||||
class={classNames('badge', Boolean(module.suspend) ? 'badge-on' : 'badge-off')}
|
||||
class={classNames(
|
||||
'badge',
|
||||
Boolean(module.suspend) || Boolean(module.sleep) ? 'badge-on' : 'badge-off'
|
||||
)}
|
||||
on:click={() => dispatch('toggleSuspend')}
|
||||
>
|
||||
<Icon data={faBed} scale={0.8} />
|
||||
|
||||
@@ -73,8 +73,9 @@
|
||||
|
||||
<h2 class="mt-4"
|
||||
>Sleep<Tooltip>
|
||||
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.</Tooltip
|
||||
If defined, at the end of the step, the flow will sleep for a number of seconds before
|
||||
scheduling the next job (if any, no effect if the step is the last one). Sleeping is passive and
|
||||
does not consume any resources.</Tooltip
|
||||
></h2
|
||||
>
|
||||
<Toggle
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { Schema } from '$lib/common'
|
||||
import type { Flow, FlowModule } from '$lib/gen'
|
||||
import type { Flow, FlowModule, FlowStatusModule } from '$lib/gen'
|
||||
import { writable } from 'svelte/store'
|
||||
import { loadFlowModuleState } from './flowStateUtils'
|
||||
import { emptyFlowModuleState, isEmptyFlowModule } from './utils'
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
export let retry: boolean = false
|
||||
export let earlyStop: boolean = false
|
||||
export let suspend: boolean = false
|
||||
export let sleep: boolean = false
|
||||
export let bold: boolean = false
|
||||
export let id: string | undefined = undefined
|
||||
export let label: string
|
||||
@@ -48,7 +49,7 @@
|
||||
<Icon scale={0.8} data={faStop} />
|
||||
</div>
|
||||
{/if}
|
||||
{#if suspend}
|
||||
{#if suspend || sleep}
|
||||
<div class="bg-white rounded border text-gray-600 px-1">
|
||||
<Icon scale={0.8} data={faBed} />
|
||||
</div>
|
||||
|
||||
@@ -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<MouseEvent>) {
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import Svelvet, { type Edge } from 'svelvet'
|
||||
import { sugiyama, dagStratify, decrossOpt, coordGreedy, coordCenter } from 'd3-dag'
|
||||
import type { FlowModule, ForloopFlow, RawScript } from '../../gen'
|
||||
import { sugiyama, dagStratify, decrossOpt, coordCenter } from 'd3-dag'
|
||||
import { type FlowModule, FlowStatusModule, type ForloopFlow, RawScript } from '../../gen'
|
||||
import {
|
||||
NODE,
|
||||
createIdGenerator,
|
||||
@@ -15,14 +15,16 @@
|
||||
type NestedNodes,
|
||||
type ModuleHost
|
||||
} from '.'
|
||||
import { defaultIfEmptyString, truncate, truncateRev } from '$lib/utils'
|
||||
import { defaultIfEmptyString, truncateRev } from '$lib/utils'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { numberToChars } from '../flows/utils'
|
||||
import type { FlowState } from '../flows/flowState'
|
||||
|
||||
export let modules: FlowModule[] | undefined = []
|
||||
export let failureModule: FlowModule | undefined = undefined
|
||||
export let minHeight: number = 0
|
||||
export let notSelectable = false
|
||||
export let flowModuleStates: Record<string, FlowStatusModule.type> | undefined = undefined
|
||||
|
||||
let selectedNode: string | undefined = undefined
|
||||
|
||||
@@ -35,7 +37,7 @@
|
||||
let dispatch = createEventDispatcher()
|
||||
|
||||
$: {
|
||||
width && height && minHeight && selectedNode
|
||||
width && height && minHeight && selectedNode && flowModuleStates
|
||||
if (modules) {
|
||||
createGraph(modules, failureModule)
|
||||
} else {
|
||||
@@ -137,6 +139,18 @@
|
||||
return []
|
||||
}
|
||||
|
||||
function getStateColor(state: FlowStatusModule.type | undefined): string {
|
||||
switch (state) {
|
||||
case FlowStatusModule.type.SUCCESS:
|
||||
return 'rgb(34 197 94)'
|
||||
case FlowStatusModule.type.FAILURE:
|
||||
return 'rgb(248 113 113)'
|
||||
case FlowStatusModule.type.IN_PROGRESS:
|
||||
return 'rgb(253 224 71)'
|
||||
default:
|
||||
return '#fff'
|
||||
}
|
||||
}
|
||||
function flowModuleToNode(
|
||||
parentIds: string[],
|
||||
id: string,
|
||||
@@ -182,7 +196,10 @@
|
||||
width: NODE.width,
|
||||
height: NODE.height,
|
||||
borderColor: selectedNode == onClickDetail.id ? 'black' : '#999',
|
||||
bgColor: selectedNode == onClickDetail.id ? '#f5f5f5' : 'white',
|
||||
bgColor:
|
||||
selectedNode == onClickDetail.id
|
||||
? '#f5f5f5'
|
||||
: getStateColor(flowModuleStates?.[onClickDetail.id]),
|
||||
parentIds,
|
||||
clickCallback: (node) => {
|
||||
if (!notSelectable) {
|
||||
|
||||
Reference in New Issue
Block a user