mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-05 16:03:47 +00:00
218 lines
6.3 KiB
Svelte
218 lines
6.3 KiB
Svelte
<script lang="ts">
|
|
import type { Job } from '$lib/gen'
|
|
import { writable, type Writable } from 'svelte/store'
|
|
import type { GraphModuleState } from './graph'
|
|
import FlowLogViewer from './FlowLogViewer.svelte'
|
|
import { graphBuilder, type NodeLayout } from './graph/graphBuilder.svelte'
|
|
import type { FlowLogEntry } from './FlowLogUtils'
|
|
import { untrack } from 'svelte'
|
|
import { ChangeTracker } from '$lib/svelte5Utils.svelte'
|
|
import { readFieldsRecursively } from '$lib/utils'
|
|
|
|
interface Props {
|
|
job: Job
|
|
localModuleStates: Writable<Record<string, GraphModuleState>>
|
|
workspaceId: string | undefined
|
|
render: boolean
|
|
onSelectedIteration: (
|
|
detail:
|
|
| { id: string; index: number; manuallySet: true; moduleId: string }
|
|
| { manuallySet: false; moduleId: string }
|
|
) => Promise<void>
|
|
}
|
|
|
|
let { job, localModuleStates, workspaceId, render, onSelectedIteration }: Props = $props()
|
|
|
|
// State for tracking expanded rows - using Record to allow explicit control
|
|
let expandedRows: Record<string, boolean> = $state({})
|
|
let allExpanded = $state(false)
|
|
let showResultsInputs = $state(true)
|
|
|
|
let emptyEventHandler = {
|
|
deleteBranch: () => {},
|
|
insert: () => {},
|
|
select: () => {},
|
|
changeId: () => {},
|
|
delete: () => {},
|
|
newBranch: () => {},
|
|
move: () => {},
|
|
selectedIteration: () => {},
|
|
simplifyFlow: () => {},
|
|
expandSubflow: () => {},
|
|
minimizeSubflow: () => {},
|
|
updateMock: () => {},
|
|
testUpTo: () => {},
|
|
editInput: () => {},
|
|
testFlow: () => {},
|
|
cancelTestFlow: () => {},
|
|
openPreview: () => {},
|
|
hideJobStatus: () => {}
|
|
}
|
|
|
|
let modules = $derived(job.raw_flow?.modules ?? [])
|
|
let moduleTracker = new ChangeTracker($state.snapshot(job.raw_flow?.modules ?? []))
|
|
$effect(() => {
|
|
readFieldsRecursively(modules)
|
|
untrack(() => moduleTracker.track($state.snapshot(modules)))
|
|
})
|
|
|
|
let nodes: NodeLayout[] | undefined = $derived.by(() => {
|
|
moduleTracker.counter
|
|
const graph = graphBuilder(
|
|
untrack(() => modules),
|
|
{
|
|
disableAi: false,
|
|
insertable: false,
|
|
flowModuleStates: undefined,
|
|
selectedId: undefined,
|
|
path: undefined,
|
|
newFlow: false,
|
|
cache: false,
|
|
earlyStop: false,
|
|
editMode: false,
|
|
isOwner: false,
|
|
isRunning: false,
|
|
individualStepTests: false,
|
|
flowJob: undefined,
|
|
showJobStatus: false,
|
|
suspendStatus: writable({}),
|
|
flowHasChanged: false
|
|
},
|
|
untrack(() => job.raw_flow?.failure_module),
|
|
untrack(() => job.raw_flow?.preprocessor_module),
|
|
emptyEventHandler, // eventHandler - empty for logs view
|
|
undefined,
|
|
false, // useDataflow
|
|
undefined, // selectedId
|
|
undefined, // moving
|
|
undefined, // simplifiableFlow
|
|
undefined, // triggerNode path
|
|
{} // expandedSubflows
|
|
)
|
|
return graph.error ? undefined : graph.nodes
|
|
})
|
|
|
|
function toggleExpanded(id: string) {
|
|
// If not in record, use opposite of allExpanded as new state
|
|
// If in record, toggle the current state
|
|
const currentState = expandedRows[id] ?? allExpanded
|
|
expandedRows[id] = !currentState
|
|
}
|
|
|
|
function getSelectedIteration(stepId: string): number {
|
|
return $localModuleStates[stepId]?.selectedForloopIndex ?? 0
|
|
}
|
|
|
|
function toggleExpandAll() {
|
|
allExpanded = !allExpanded
|
|
expandedRows = {}
|
|
}
|
|
|
|
// Build tree structure from modules using bottom-up traversal from result node
|
|
function buildFlowTree(nodes: NodeLayout[]): FlowLogEntry[] {
|
|
// Index nodes for quick access
|
|
const nodeById: Record<string, NodeLayout> = {}
|
|
for (const n of nodes) nodeById[n.id] = n
|
|
|
|
function traverseFromId(id: string): FlowLogEntry[] {
|
|
const entries: FlowLogEntry[] = []
|
|
const currentNode = nodeById[id]
|
|
|
|
if (currentNode.type === 'module') {
|
|
entries.push({
|
|
id: currentNode.id,
|
|
stepId: currentNode.id,
|
|
stepNumber: 0,
|
|
summary: currentNode.data.module.summary ?? '',
|
|
stepType: currentNode.data.module.value.type
|
|
})
|
|
}
|
|
|
|
if (
|
|
currentNode.type === 'whileLoopStart' ||
|
|
currentNode.type === 'branchOneStart' ||
|
|
currentNode.type === 'branchAllStart'
|
|
) {
|
|
// Reaching the end of a subflow
|
|
return entries
|
|
}
|
|
|
|
let nextParentId = currentNode.parentIds?.[0]
|
|
if (!nextParentId) {
|
|
// Reached the root of the flow
|
|
return entries
|
|
}
|
|
|
|
if (
|
|
currentNode.type === 'forLoopEnd' ||
|
|
currentNode.type === 'whileLoopEnd' ||
|
|
currentNode.type === 'branchOneEnd'
|
|
) {
|
|
const subflow = traverseFromId(nextParentId)
|
|
const subflowId = currentNode.id.slice(0, -4) // Remove '-end' suffix
|
|
const subflowNode = nodeById[subflowId]
|
|
const subflowSummary = currentNode.type === 'branchOneEnd' ? 'branch' : 'iteration'
|
|
if (subflowNode.type === 'module') {
|
|
entries.push({
|
|
id: subflowId,
|
|
stepId: subflowId,
|
|
subflows: [subflow],
|
|
stepType:
|
|
currentNode.type === 'forLoopEnd'
|
|
? 'forloopflow'
|
|
: currentNode.type === 'whileLoopEnd'
|
|
? 'whileloopflow'
|
|
: 'branchone',
|
|
summary: subflowNode.data.module.summary ?? '',
|
|
subflowsSummary: [subflowSummary]
|
|
})
|
|
nextParentId = subflowNode.parentIds?.[0] ?? ''
|
|
}
|
|
} else if (currentNode.type === 'branchAllEnd') {
|
|
const subflowId = currentNode.id.slice(0, -4) // Remove '-end' suffix
|
|
const subflowNode = nodeById[subflowId]
|
|
if (subflowNode.type === 'module' && subflowNode.data.module.value.type === 'branchall') {
|
|
const subflows = currentNode.parentIds?.map((id) => traverseFromId(id)) ?? []
|
|
const subflowsSummary = subflowNode.data.module.value.branches.map((b) => b.summary ?? '')
|
|
entries.push({
|
|
id: subflowId,
|
|
stepId: subflowId,
|
|
subflows: subflows,
|
|
stepType: 'branchall',
|
|
subflowsSummary
|
|
})
|
|
nextParentId = subflowNode.parentIds?.[0] ?? '' // a module can only have one parent
|
|
}
|
|
}
|
|
|
|
// Get entries from parent nodes
|
|
const parentEntries = traverseFromId(nextParentId)
|
|
return [...parentEntries, ...entries]
|
|
}
|
|
|
|
// Start from the result node and traverse backwards
|
|
return traverseFromId('result')
|
|
}
|
|
|
|
let logEntries = $derived(nodes ? buildFlowTree(nodes) : [])
|
|
</script>
|
|
|
|
<div class="w-full rounded-md overflow-hidden border">
|
|
<FlowLogViewer
|
|
{logEntries}
|
|
{localModuleStates}
|
|
rootJob={job}
|
|
{expandedRows}
|
|
{allExpanded}
|
|
{showResultsInputs}
|
|
{toggleExpanded}
|
|
{toggleExpandAll}
|
|
{onSelectedIteration}
|
|
{workspaceId}
|
|
{render}
|
|
{getSelectedIteration}
|
|
flowId="root"
|
|
flowStatus={undefined}
|
|
/>
|
|
</div>
|