Skip to content

feat(frontend): dynamically disable file-dependent actions in the node picker#22

Open
LukasHirt wants to merge 3 commits into
mainfrom
feat/flow-file-source-validation
Open

feat(frontend): dynamically disable file-dependent actions in the node picker#22
LukasHirt wants to merge 3 commits into
mainfrom
feat/flow-file-source-validation

Conversation

@LukasHirt

@LukasHirt LukasHirt commented Jul 24, 2026

Copy link
Copy Markdown
Collaborator

Backend investigation

Read backend/pkg/executor/executor.go end-to-end plus the callers of Executor.Run (backend/pkg/service/workflows.go, backend/pkg/scheduler/scheduler.go, backend/pkg/sse/manager.go):

  • vars["file.name"]/vars["file.content"] are only populated when resourcePath != "" at the top of Run() (executor.go:66-76). If GetContent fails or resourcePath is empty, those vars are simply never set — no error, no fallback.
  • runAction's tag/comment/move/copy/rename branches all require a non-empty currentPath (seeded from resourcePath) and return a hard runtime error ("...action needs both a target file and...") when it's empty. notify is the only action that doesn't touch currentPath at all.
  • Whether resourcePath is non-empty depends entirely on who calls Run, not on the graph itself:
    • scheduler.go:147s.executor.Run(ctx, authHeader, *wf, "schedule", "")always an empty string. A Schedule Trigger can never provide a file. This is structurally impossible, not just unlikely.
    • service/workflows.go Run handler (manual "Run now") — resourcePath comes from an optional free-text field in ExecutionsPanel.vue ("File to run against (WebDAV path)"). It can work — Manual Trigger + file action is a legitimate, common workflow shape — but nothing in the graph guarantees the user filled it in.
    • sse/manager.go:306 — passes the real path from the file event that fired. The File Event Trigger is the only trigger that reliably supplies a file on every run.

Decision (evolved across this PR)

  1. Started with a non-blocking warning in NodeDetailsPanel.vue.
  2. Replaced/supplemented that with pick-time restriction in NodePicker.vue: disable file-dependent action entries when the picker's source node's upstream trigger doesn't reliably provide a file.
  3. CI caught a real bug in step 2: build-workflow.spec.ts and run-workflow.spec.ts both build a Manual Trigger -> LLM -> Add Tag chain, and the picker was disabling "Add Tag" because the disable-check treated Manual Trigger the same as Schedule Trigger. That's too strict — Manual Trigger is only conditionally file-less (depends on what the user types into "Run now"), whereas Schedule Trigger is structurally file-less (the scheduler always calls Run with an empty resourcePath, full stop). Fixed by splitting the check in two:
    • hasUpstreamFileSource() keeps its original, stricter "only Event Trigger is reliable" semantics — still used for the informational NodeDetailsPanel nudge (fires for both Manual and Schedule).
    • canUpstreamProvideFile() is new and looser — only Schedule Trigger fails it — and is what NodePicker.vue now uses to decide what to hard-disable. Manual Trigger no longer blocks file-dependent actions in the picker.

What changed

  • frontend/src/utils/flowValidation.ts (new): pure, tested graph-walk.
    • hasUpstreamFileSource(nodeId, nodes, edges) — true only if the upstream trigger is event (or none is reachable at all). Used by NodeDetailsPanel's warning.
    • canUpstreamProvideFile(nodeId, nodes, edges) — true unless the upstream trigger is schedule (the only structurally-impossible case). Used by NodePicker's hard-disable.
    • isFileDependentActionType(actionType)tag/comment/move/copy/rename are file-dependent; notify is not.
  • frontend/src/components/NodePicker.vue: new sourceNodeId/nodes/edges props. Disables file-dependent entries (grayed out, inline reason + tooltip, click no-ops) only when the source's upstream trigger is a Schedule Trigger.
  • frontend/src/components/NodeDetailsPanel.vue: shows a non-blocking warning banner for file-dependent actions whose upstream trigger isn't an Event Trigger (Manual or Schedule) — kept as a secondary safety net, since Vue Flow lets users rewire an action node's incoming edge by dragging directly between existing nodes (bypassing the picker), and workflows created via the REST API never go through the picker at all.
  • frontend/src/views/WorkflowBuilder.vue: threads pickerConnectFrom/nodes/edges into NodePicker, and nodes/edges into NodeDetailsPanel.

Left the node picker's categorization, the "+" button, node positioning, config-modal OK-button/auto-open, and output hints untouched — those are owned by other in-flight changes.

Test plan

TDD throughout, including for the CI-driven follow-up fix (tests updated/added first, confirmed failing against the pre-fix code, then implementation, then confirmed passing):

  • frontend/tests/unit/flowValidation.spec.ts (14 cases): hasUpstreamFileSource covers direct manual→move flags, direct event→move doesn't, manual→llm→move still flags, event→llm→move doesn't, schedule-trigger case, orphan-node case. canUpstreamProvideFile covers the same shapes but asserts Manual Trigger is not flagged and only Schedule Trigger is.

  • frontend/tests/unit/NodePicker.spec.ts (5 cases): Move File not disabled for a manual-trigger source; disabled (with reason text) for a schedule-trigger source; not disabled for an event-trigger source; clicking a disabled entry doesn't emit select; non-file action (Send Notification) stays enabled regardless of source.

  • frontend/tests/unit/NodeDetailsPanel.spec.ts (3 cases): warns for manual→move, doesn't warn for event→move, doesn't warn for notify.

  • npm run test:unit — 5 test files, 26 tests, all passing

  • npm run check:types — clean

  • npm run lint — clean

  • CI (gh pr checks 22) re-run after the fix — see checks on this PR

@LukasHirt LukasHirt self-assigned this Jul 24, 2026
@LukasHirt
LukasHirt requested a review from a team as a code owner July 24, 2026 16:11
@LukasHirt LukasHirt changed the title feat(frontend): warn when a file action lacks an upstream file source feat(frontend): dynamically disable file-dependent actions in the node picker Jul 24, 2026
mzner
mzner previously approved these changes Jul 24, 2026
Executor.Run only populates vars["file.*"] (and the currentPath that file
actions require) when a non-empty resourcePath reaches it. The scheduler
always calls Run with an empty resourcePath, and the manual "Run now"
panel only sends one if the user optionally types it in — only the File
Event Trigger's SSE path reliably supplies a real file. Add a pure
hasUpstreamFileSource() graph-walk (frontend/src/utils/flowValidation.ts)
and surface a non-blocking warning in NodeDetailsPanel when a file
action (tag/comment/move/copy/rename) has no such trigger upstream.

Signed-off-by: Lukas Hirt <info@hirt.cz>
…e picker

Move the file-source guardrail from an after-the-fact warning to pick-time
restriction: NodePicker.vue now receives the picker's source node id plus
the current nodes/edges, reuses the existing hasUpstreamFileSource() BFS
to decide whether that source node's upstream trigger reliably provides a
file, and disables/grays out file-dependent action entries (tag, comment,
move, copy, rename) with an inline reason when it doesn't. WorkflowBuilder
threads nodes/edges/pickerConnectFrom into the picker to make this work.

Kept the NodeDetailsPanel warning as a secondary safety net: Vue Flow lets
users rewire an existing action node's incoming edge by dragging directly
between already-placed nodes, bypassing the picker entirely, and workflows
created via the REST API never go through the picker at all.

Signed-off-by: Lukas Hirt <info@hirt.cz>
CI's e2e suite caught a real regression: build-workflow.spec.ts (and
run-workflow.spec.ts) build a Manual Trigger -> LLM -> Add Tag chain, and
the picker was disabling "Add Tag" because hasUpstreamFileSource() treated
Manual Trigger the same as Schedule Trigger — both "not guaranteed", so
both got hard-blocked.

That's too strict for Manual Trigger specifically: unlike Schedule
Trigger (scheduler.go always calls Run with resourcePath=""), a Manual
Trigger's "Run now" flow can supply a file via its free-text WebDAV-path
field (ExecutionsPanel.vue / runRequest.ResourcePath) — Manual Trigger +
file action is a legitimate, common workflow shape, not a structural
impossibility.

Split the check in two: hasUpstreamFileSource() keeps its original,
stricter "only Event Trigger is *reliable*" semantics for the informational
NodeDetailsPanel warning (still nudges on both Manual and Schedule).
canUpstreamProvideFile() is new and looser — only Schedule Trigger fails
it — and NodePicker.vue now uses that one to decide what to hard-disable,
so Manual Trigger no longer blocks file-dependent actions in the picker.

Signed-off-by: Lukas Hirt <info@hirt.cz>
@LukasHirt
LukasHirt force-pushed the feat/flow-file-source-validation branch from 928fae3 to 87e125e Compare July 24, 2026 20:49
@mzner
mzner self-requested a review July 24, 2026 20:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants