mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-13 00:03:57 +00:00
* feat: add flow group nodes core infrastructure Add group data model (start_id/end_id boundary pairs), GroupEditor for CRUD operations, groupDetectionUtils for membership computation and validation, GroupedModulesProxy for reactive sync, and compound layout support. Update openflow.openapi.yaml with group schema. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: add group UI components and rendering Add GroupOverlay with bounding box and z-ordering, GroupHeader with StepCountTab and ellipsis menu, GroupNodeCard, GroupNoteArea for inline markdown notes, CollapsedGroupNode/CollapsedSubflowNode for collapsed rendering, GroupEndNode/GroupHeadNode boundary markers, and group actions in NodeContextMenu and SelectionBoundingBox. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: integrate groups into flow graph, builder, and existing components Wire group support into FlowGraphV2 (overlays, collapsed rendering, group-aware layout), graphBuilder (GroupedModule tree, container collapse/expand, group boundary nodes), BaseEdge (drop targets for group operations), ModuleNode (collapsed container rendering), and flow map components (schema item grouping). Remove SubflowBound in favor of CollapsedSubflowNode. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: remove banned $bindable(default) pattern and dead ternary Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * refactor: decouple collapse state from grouped module tree Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: pass groups prop to FlowGraphV2 and use GroupDisplayState via graphContext Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * refactor: remove group membership system, compute nesting depth from visual bounds Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * refactor: simplify GroupOverlay bounds, remove unused headerY and showNotes prop Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: populate innerNodeIds for expanded subflow overlay Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * refactor: remove expanded subflow overlay feature for separate PR Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: flatten groups in getContainerModules to prevent crash on collapsed containers Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: add drag-to-move support for group nodes Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: derive group boundaries from expanded membership to prevent splitting existing groups Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: catch group validation errors and display as flow graph alert Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * test: add unit tests for group validation in buildGroupedModules Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: reject virtual nodes (Input, Result, Trigger) from groups Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * test: add virtual node rejection tests for buildGroupedModules Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: exclude preprocessor and failure module from groups Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: disable Create group button when preprocessor is selected Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: reject selection entirely when it contains excluded nodes Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * refactor: remove unnecessary excludeIds from buildGroupedModules Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: remove debug console.log from FlowGraphV2 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: use cross-browser CSS grid trick for group summary input auto-sizing Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: hide group boundary edges and reformat GroupNoteArea Hide edges between group header and first node, and between last node and group-end, keeping them in the DOM but visually hidden. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * refactor: stop FlowGraphV2 from reading groups via groupEditorContext Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: show module previews with status, selection, and suspend popover in collapsed groups Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * refactor: extract collapsible implicit containers to separate branch Remove collapse/expand functionality for implicit containers (forloops, while loops, branches) from this branch. Backed up as collapsible-implicit-containers-backup for later rebase. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * refactor: use original reactive modules for graph node data instead of proxy snapshots Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: prevent node loss when moving into forloop inside a group Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * refactor: replace GroupedModule proxy with structure-only FlowStructureNode tree Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: use "group-" prefix for group IDs instead of "note-" Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: update group boundaries when renaming a module ID When a module at a group boundary (start_id or end_id) is renamed, the group definitions now get updated before the reactive rebuild, preventing stale references that would break the flow structure. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: update graph layout when removing a group note Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: add opaque background behind test run button to prevent see-through Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: detect and reject duplicate group IDs Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: simplify group creation validation with early marker normalization Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: use $state.raw in MiniFlowGraph to avoid xyflow performance warning Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * refactor: address code review feedback - Revert backend traverse_modules change (not part of this feature) - Use Map for node lookup in GroupOverlay (O(1) vs O(n) per group) - Extract computeNodeExtraSpace to nodeExtraSpace.ts for testability Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * refactor: address PR review feedback - Compute group depths from structure tree O(n) instead of O(n²) bounds comparison - Remove unnecessary $derived(groups) in GroupOverlay - Remove unused collapsed field from container types in OpenAPI spec - Use NODE.width constant in GroupNodeCard instead of hardcoded 275px - Add comment explaining intentional stale preservation in rebuild() Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: preserve flow groups during dependency job re-serialization Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: resolve Svelte state_referenced_locally warnings in GroupHeader and FlowGraphV2 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: show subflow groups when expanding a subflow in the graph - Store both modules and groups when expanding a subflow - Pass groups to buildStructureTree so group nodes render - Include subflow groups in overlay rendering and collapse tracking - Clone modules for prefix rewriting to avoid state_unsafe_mutation - Register expanded subflow modules in moduleMap before prefix rewriting - Disable group editing in expanded subflows and read-only views Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: restore accidentally removed code from main - Restore subflowBound selection handling in selectionUtils - Restore comments in SelectionBoundingBox - Restore deletable={false} in FirstStepInputs Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * refactor: remove redundant adjacency check from MoveManager The disableMoveIds check already prevents all invalid drop targets, making the adjacencySourceId/adjacencyTargetId fields unnecessary. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * chore: regenerate auto-generated files after OpenAPI schema change Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * chore: regenerate cli skills after main merge Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: include groups in view_graph localStorage state Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * refactor: centralize canCreateGroup and replace group note with group creation - Add canCreateGroup StateStore to GroupEditorContext, computed in FlowGraphV2 - Replace "Create group note" with "Create group" in FlowSelectionPanel - Remove "Add note" from selection bounding box dropdown - Remove unused NodeContextMenu component - Wire createGroup through FlowModuleSchemaMap → FlowGraphV2 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: reject groups spanning parallel branches and surface ill-formed group errors Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * chore: regenerate auto-generated files after main merge Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: ensure modules appears before groups in YAML export Svelte 5's $state proxy registers groups as a tracked property before it's explicitly set, causing it to appear before modules in Object.keys iteration. Reorder the value object at export time for readable YAML. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * refactor: address second round of PR review feedback - Add comment explaining duplicateMultiple bypasses structure tree - Add warning log for inverted ranges in computeGroupModuleIds - Use NODE.width constant in CollapsedGroupNode instead of hardcoded 275px - Simplify redundant condition in getGroupsEmptiedBy Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * refactor: remove stored group ID, derive ephemeral key from start_id:end_id Groups no longer store an `id` field. Instead, a `groupKey(g)` helper derives an ephemeral key from `${start_id}:${end_id}` at read time. This simplifies the schema while preserving all runtime functionality. When boundaries shift (module deletion), runtime state (collapse, note heights) is remapped to the new key via GroupDisplayState.remapGroupKey. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: add note button, save/cancel hints, and rename collapsed_by_default to autocollapse Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: propagate selection from collapsed group badges to external listeners Pass eventHandlers to GroupModuleIcons so clicking a module badge calls both selectionManager.selectId (visual highlight) and eventHandlers.select (side panel propagation via onSelect). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * chore: regenerate auto-generated files after main merge Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: hide In/Out popovers and actions during click-to-move Replace isDragging with isMoving derived that covers both drag-move and click-move states, disabling popovers, delete button, and test run button during any move operation. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
202 lines
6.3 KiB
Svelte
202 lines
6.3 KiB
Svelte
<script lang="ts">
|
|
import { getContext } from 'svelte'
|
|
import type { FlowEditorContext } from '../types'
|
|
import FlowModuleWrapper from './FlowModuleWrapper.svelte'
|
|
import FlowSettings from './FlowSettings.svelte'
|
|
import FlowInput from './FlowInput.svelte'
|
|
import FlowFailureModule from './FlowFailureModule.svelte'
|
|
import FlowEnvironmentVariables from './FlowEnvironmentVariables.svelte'
|
|
import type { FlowModule, Flow, Job } from '$lib/gen'
|
|
import FlowPreprocessorModule from './FlowPreprocessorModule.svelte'
|
|
import type { TriggerContext } from '$lib/components/triggers'
|
|
import { insertNewPreprocessorModule } from '../flowStateUtils.svelte'
|
|
import TriggersEditor from '../../triggers/TriggersEditor.svelte'
|
|
import { handleSelectTriggerFromKind, type Trigger } from '$lib/components/triggers/utils'
|
|
import { computeMissingInputWarnings } from '../missingInputWarnings'
|
|
import FlowResult from './FlowResult.svelte'
|
|
import type { StateStore } from '$lib/utils'
|
|
import FlowSelectionPanel from './FlowSelectionPanel.svelte'
|
|
import {
|
|
resolveSelectedModuleIds,
|
|
locateModules,
|
|
areContiguousSiblings
|
|
} from '../multiSelectUtils'
|
|
|
|
interface Props {
|
|
noEditor?: boolean
|
|
enableAi?: boolean
|
|
newFlow?: boolean
|
|
disabledFlowInputs?: boolean
|
|
savedFlow?:
|
|
| (Flow & {
|
|
draft?: Flow | undefined
|
|
})
|
|
| undefined
|
|
onDeployTrigger?: (trigger: Trigger) => void
|
|
forceTestTab?: Record<string, boolean>
|
|
highlightArg?: Record<string, string | undefined>
|
|
onTestFlow?: (conversationId?: string) => Promise<string | undefined>
|
|
job?: Job
|
|
isOwner?: boolean
|
|
suspendStatus?: StateStore<Record<string, { job: Job; nb: number }>>
|
|
onOpenDetails?: () => void
|
|
previewOpen?: boolean
|
|
flowModuleSchemaMap?: import('../map/FlowModuleSchemaMap.svelte').default
|
|
}
|
|
|
|
let {
|
|
noEditor = false,
|
|
enableAi = false,
|
|
newFlow = false,
|
|
disabledFlowInputs = false,
|
|
savedFlow = undefined,
|
|
onDeployTrigger = () => {},
|
|
forceTestTab,
|
|
highlightArg,
|
|
onTestFlow,
|
|
job,
|
|
isOwner,
|
|
suspendStatus,
|
|
onOpenDetails,
|
|
previewOpen = false,
|
|
flowModuleSchemaMap = undefined
|
|
}: Props = $props()
|
|
|
|
const {
|
|
selectionManager,
|
|
flowStore,
|
|
flowStateStore,
|
|
flowInputsStore,
|
|
pathStore,
|
|
initialPathStore,
|
|
fakeInitialPath,
|
|
previewArgs,
|
|
flowInputEditorState,
|
|
stepsInputArgs
|
|
} = getContext<FlowEditorContext>('FlowEditorContext')
|
|
|
|
const selectedId = $derived(selectionManager.getSelectedId())
|
|
|
|
const { showCaptureHint, triggersState, triggersCount } =
|
|
getContext<TriggerContext>('TriggerContext')
|
|
|
|
function checkDup(modules: FlowModule[]): string | undefined {
|
|
let seenModules: string[] = []
|
|
for (const m of modules) {
|
|
if (seenModules.includes(m.id)) {
|
|
console.error(`Duplicate module id: ${m.id}`)
|
|
return m.id
|
|
}
|
|
seenModules.push(m.id)
|
|
}
|
|
}
|
|
|
|
$effect(() => {
|
|
computeMissingInputWarnings(flowStore, flowStateStore.val, flowInputsStore)
|
|
})
|
|
|
|
// Derived state for multi-select operations in the side panel
|
|
let resolvedModuleIds = $derived(
|
|
resolveSelectedModuleIds(selectionManager.selectedIds, flowStore.val.value.modules ?? [])
|
|
)
|
|
let canMoveSelected = $derived(
|
|
resolvedModuleIds.length > 0 &&
|
|
areContiguousSiblings(locateModules(resolvedModuleIds, flowStore.val.value.modules ?? []))
|
|
)
|
|
</script>
|
|
|
|
{#if selectionManager && selectionManager.selectedIds.length > 1}
|
|
<FlowSelectionPanel
|
|
{selectionManager}
|
|
{noEditor}
|
|
onDeleteSelected={() => flowModuleSchemaMap?.deleteMultiple(resolvedModuleIds)}
|
|
onDuplicateSelected={() => flowModuleSchemaMap?.duplicateMultiple(resolvedModuleIds)}
|
|
onMoveSelected={() => flowModuleSchemaMap?.moveMultiple(resolvedModuleIds)}
|
|
onCreateGroup={() => flowModuleSchemaMap?.createGroup(selectionManager.selectedIds)}
|
|
{canMoveSelected}
|
|
resolvedCount={resolvedModuleIds.length}
|
|
/>
|
|
{:else if selectedId?.startsWith('settings')}
|
|
<FlowSettings {enableAi} {noEditor} />
|
|
{:else if selectedId === 'Input'}
|
|
<FlowInput
|
|
{noEditor}
|
|
disabled={disabledFlowInputs}
|
|
on:openTriggers={(ev) => {
|
|
selectionManager.selectId('Trigger')
|
|
handleSelectTriggerFromKind(triggersState, triggersCount, savedFlow?.path, ev.detail.kind)
|
|
showCaptureHint.set(true)
|
|
}}
|
|
on:applyArgs
|
|
{onTestFlow}
|
|
{previewOpen}
|
|
{flowModuleSchemaMap}
|
|
/>
|
|
{:else if selectedId === 'Result'}
|
|
<FlowResult {noEditor} {job} {isOwner} {suspendStatus} {onOpenDetails} />
|
|
{:else if selectedId === 'constants'}
|
|
<FlowEnvironmentVariables {noEditor} />
|
|
{:else if selectedId === 'failure'}
|
|
<FlowFailureModule {noEditor} savedModule={savedFlow?.value.failure_module} />
|
|
{:else if selectedId === 'preprocessor'}
|
|
<FlowPreprocessorModule {noEditor} savedModule={savedFlow?.value.preprocessor_module} />
|
|
{:else if selectedId === 'Trigger'}
|
|
<TriggersEditor
|
|
on:applyArgs
|
|
on:addPreprocessor={async (e) => {
|
|
await insertNewPreprocessorModule(flowStore, flowStateStore, {
|
|
language: 'bun'
|
|
})
|
|
stepsInputArgs.setStepArgs('preprocessor', e.detail.args ?? {})
|
|
selectionManager.selectId('preprocessor')
|
|
}}
|
|
on:updateSchema={(e) => {
|
|
const { payloadData, redirect } = e.detail
|
|
if (payloadData) {
|
|
previewArgs.val = JSON.parse(JSON.stringify(payloadData))
|
|
}
|
|
if (redirect) {
|
|
selectionManager.selectId('Input')
|
|
$flowInputEditorState.selectedTab = 'captures'
|
|
$flowInputEditorState.payloadData = payloadData
|
|
}
|
|
}}
|
|
on:testWithArgs
|
|
currentPath={$pathStore}
|
|
initialPath={$initialPathStore}
|
|
{fakeInitialPath}
|
|
{noEditor}
|
|
newItem={newFlow}
|
|
isFlow={true}
|
|
hasPreprocessor={!!flowStore.val.value.preprocessor_module}
|
|
canHavePreprocessor={true}
|
|
args={previewArgs.val}
|
|
isDeployed={savedFlow && !savedFlow?.draft_only}
|
|
schema={flowStore.val.schema}
|
|
{onDeployTrigger}
|
|
/>
|
|
{:else if selectedId?.startsWith('subflow:')}
|
|
<div class="p-4"
|
|
>Selected step is witin an expanded subflow and is not directly editable in the flow editor</div
|
|
>
|
|
{:else}
|
|
{@const dup = checkDup(flowStore.val.value.modules)}
|
|
{#if dup}
|
|
<div class="text-red-600 text-xl p-2">There are duplicate modules in the flow at id: {dup}</div>
|
|
{:else}
|
|
{#key selectedId}
|
|
{#each flowStore.val.value.modules as flowModule, index (flowModule.id ?? index)}
|
|
<FlowModuleWrapper
|
|
{noEditor}
|
|
bind:flowModule={flowStore.val.value.modules[index]}
|
|
previousModule={flowStore.val.value.modules[index - 1]}
|
|
{enableAi}
|
|
savedModule={savedFlow?.value.modules[index]}
|
|
{forceTestTab}
|
|
{highlightArg}
|
|
/>
|
|
{/each}
|
|
{/key}
|
|
{/if}
|
|
{/if}
|