Skip to content
CSA Loom — the Microsoft Fabric experience for Azure tenants where Fabric isn't yet available: lakehouses, warehouses, notebooks, semantic models, Activator rules, Data Agents, across Commercial, GCC, GCC-High, and DoD IL5

adf-pipeline — parity with Azure Data Factory pipeline (Author / Integrate)

Source UI: ADF Studio → Author → Pipelines (https://learn.microsoft.com/azure/data-factory/concepts-pipelines-activities) Backend factory: adf-loom-default-eastus2 via ARM REST (Microsoft.DataFactory/factories, api-version 2018-06-01).

The bug this fixes

The Loom pipeline item is a Cosmos GUID. The old routes passed that GUID straight to getPipeline() as the Azure pipeline name → ADF returned 404 PipelineNotFound, and the missing /runs route returned a 404 HTML page that crashed res.json(). Fixed by a resource-binding model: the Loom item binds to a real ADF pipeline (persisted in state.pipelineName); every route resolves the bound name via resolveBinding() instead of the route id.

Real ADF Studio pipeline-editor inventory (grounded in Learn)

Source: author-visually#authoring-canvas + concepts-pipelines-activities#creating-a-pipeline-with-ui + tutorial-pipeline-failure-error-handling. The real editor has four regions:

  1. Activities pane (left) — searchable, categorized tree of every activity (Move & transform, Orchestration/Synapse-Databricks, General, Iteration & conditionals, …). Drag onto canvas to add.
  2. Authoring canvas (center) — activity cards (icon + name + type); drag from palette to add, drag to reposition, click to select. Dependency arrows between cards in the four conditional-path colours: Upon Success (green), Upon Failure (red), Upon Completion (blue), Upon Skip (gray) — each activity exposes the four output ports.
  3. Bottom configuration panel — when an activity is selected, the panel at the bottom of the canvas edits it (General + activity-specific Source/Sink/Settings + User properties + Activity policy: timeout/retry/retryInterval/secureOutput).
  4. Pipeline configurations pane — Parameters, Variables, (General) Settings (concurrency, annotations, description), Output (run monitor).
  5. Toolbar — Save, Validate, Debug, Trigger now / Add trigger; canvas controls zoom in/out/fit/auto-align.

Loom coverage

ADF Studio capability Loom coverage Backend (real REST)
Pick a pipeline from the factory tree ✅ built — left-panel tree from listPipelines() GET factories/{f}/pipelines
Bind item to an existing pipeline ✅ built — bind picker (Dropdown + Bind) POST /api/items/adf-pipeline/[id]/bindpersistBinding (Cosmos)
Create a new pipeline ✅ built — "Create & bind" PUT factories/{f}/pipelines/{name} then bind
Activities pane — searchable categorized palette ✅ built — ActivityPalette: search + 3 collapsible groups, 24 activity types n/a (client)
Authoring canvas — cards, drag-to-add, drag-to-move, select ✅ built — PipelineCanvas + ActivityNode: absolute-positioned cards, palette drag-drop, node reposition, pan/zoom, minimap spec from GET .../pipelines/{name}
4 dependency conditions (success/failure/completion/skip) ✅ built — each card has 4 coloured output ports; drag a port → another card draws a dependsOn edge with that dependencyCondition; SVG arrows colour-coded persisted in dependsOn[] on Save
Bottom configuration panel for selected activity ✅ built — PropertiesPanel layout="dock": General / Source-Sink / Settings (policy: timeout, retry, retryInterval, secureOutput) / Parameters / User properties tabs PUT .../pipelines/{name}
Nested control-flow sub-canvases (ForEach / Until / If / Switch drill-in) ✅ built — pencil button on every container node + "Activities (n)" count; ForEach/Until also double-click to drill; breadcrumb trail (Pipeline > ForEach1 > True) pops back to any ancestor; the same palette / drag-drop / connect / properties operate on inner activities; If/Switch branch tabs (True/False, Default + add/remove cases); ADF nesting limits enforced (If/Switch not inside If/Switch; single-level loop nesting) with disabled palette tiles + tooltips inner activities round-trip in typeProperties.activities / ifTrue|ifFalseActivities / defaultActivities / cases[].activities on the same PUT .../pipelines/{name}
Pipeline Parameters pane ✅ built — ParametersPane: add/type/default/delete round-trips properties.parameters on PUT
Pipeline Variables pane ✅ built — VariablesPane: add/type/default/delete round-trips properties.variables on PUT
Pipeline Settings pane (concurrency, annotations, description) ✅ built — SettingsPane round-trips properties.{concurrency,annotations,description} on PUT
Canvas controls — zoom in/out/fit, auto-align ✅ built — CanvasToolbar (bottom-right) + toolbar Auto align / Zoom to fit n/a (client)
Canvas keyboard shortcuts (Fabric data-factory/keyboard-shortcuts) — I/O zoom, F fit, A auto-align, N toggle nested preview, Shift+arrows pan, Backspace = return to previous canvas ✅ built — PipelineCanvasInner.handleKeyDown on the focusable (tabIndex=0) canvas shell; ignores keys while a text control is focused n/a (client)
Inline nested-activity preview (Fabric "updated canvas experience", data-factory/pipeline-canvas-experience) — container nodes summarise their inner activities (branch name + count + mini tiles) on the parent canvas; N / a "Nested" toolbar toggle shows/hides it ✅ built — FlowActivityNode renders miniPreviewSections() (True/False, Default/Case, or Activities sections with +N more) when showNestedPreview is on n/a (client)
Large-graph scalability — 200-node pipeline pans smoothly ✅ built — shouldVirtualize() (≥80 nodes) enables React Flow onlyRenderVisibleElements + a compact ELK layout variant (LARGE_GRAPH_LAYOUT_OPTIONS) n/a (client)
Code (JSON) view ✅ built — Monaco JSON tab (round-trips to/from the canvas model) PUT .../pipelines/{name}
Save / Publish ✅ built — toolbar Save + Ctrl+S PUT factories/{f}/pipelines/{name} (createOrUpdate)
Validate ✅ built — toolbar Validate POST .../pipelines/{name}/validate or POST factories/{f}/validatePipeline
Debug ✅ built — toolbar Debug POST .../pipelines/{name}/createRun?isRecovery=false
Trigger now ✅ built — toolbar Trigger now POST .../pipelines/{name}/createRun
Add trigger / Triggers (list / create schedule / start / stop / delete) ✅ built — Add trigger dialog GET/PUT/POST .../triggers, .../triggers/{n}/start|stop
Output / Run history (Monitor) + window/status filter ✅ built — Output tab POST factories/{f}/queryPipelineRuns (filtered PipelineName)
Deep activity config needing a linked service / dataset that isn't provisioned ⚠️ honest-gate — Source/Sink + dataset reference fields render; the referenceName is empty with guidance; Fabric-only activities (Dataflow Gen2, Office 365) marked Save-only with remediation n/a
Infra-gate when factory not provisioned ⚠️ honest-gate — bind picker shows listError (e.g. "Missing env var: LOOM_ADF_NAME"); full UI still renders n/a

Zero ❌. Zero stub banners. The cards/edges ⇄ properties.activities[]/dependsOn[] round-trip, the palette catalog, the per-type default typeProperties, and the 4-condition connect/merge are covered by lib/components/pipeline/__tests__/activities-roundtrip.test.ts (Vitest). The nested control-flow drill model (branch read/write, level walk, immutable tree write-back, ADF nesting limits, popDrill back-navigation, and miniPreviewSections inline preview) is covered by lib/components/pipeline/__tests__/drill-path.test.ts (25 tests).

Nested control-flow sub-canvases (ADF concepts-nested-activities)

Source: concepts-nested-activities. ADF container activities edit their inner activities on an inner canvas reached via a pencil icon on the node (ForEach/Until also drill on double-click), with a breadcrumb back to ancestors. Loom mirrors this exactly:

  • Inner-activity storage (ADF JSON shape): ForEach/Until → typeProperties.activities; IfCondition → typeProperties.ifTrueActivities / ifFalseActivities; Switch → typeProperties.defaultActivities + typeProperties.cases[].activities.
  • Drill state: PipelineDesigner holds a drillPath: Array<{ name; branch? }>. getLevelActivities(tree, path) walks to the current level; setLevelActivities(tree, path, next) rebuilds the container chain immutably and emits the full top-level tree via onActivitiesChange — so PipelineEditorCore's properties.activities[] ⇄ JSON contract is unchanged.
  • Branch UI: IfCondition shows True/False tabs; Switch shows Default + a tab per case with inline add/remove case (real JSON mutation).
  • Nesting limits (drill-path.ts#canAddTypeAtLevel): If/Switch can be used inside ForEach/Until but not inside another If/Switch; ForEach/Until support a single level of nesting. Disallowed palette tiles are disabled (no drag, no click) with an explanatory tooltip, and the canvas drop handler enforces the same gate.
  • No new backend. Inner activities serialise inside the same pipeline definition; Save still does one PUT factories/{f}/pipelines/{name}. No new route, env var, role, Cosmos container, or bicep change.

Backend per control

  • Binding read/list/create: /api/items/adf-pipeline/[id]/bind (GET lists real pipelines, POST binds/creates).
  • Spec GET/PUT/DELETE: /api/items/adf-pipeline/[id]resolveBindingadf-client.{getPipeline,upsertPipeline,deletePipeline}.
  • Run/Debug: /api/items/adf-pipeline/[id]/run|debugrunPipeline/debugPipeline (createRun).
  • Runs: /api/items/adf-pipeline/[id]/runslistPipelineRuns(boundName) (queryPipelineRuns).
  • Validate: /api/items/adf-pipeline/[id]/validatevalidatePipeline.
  • Triggers: /api/items/adf-pipeline/[id]/triggerslistTriggers/upsertTrigger/start/stop/delete.

All routes 412 with {ok:false, code:'unbound'} when the item has no state.pipelineName, so the editor shows its bind picker. Every res.json() on the client goes through safePipelineJson (content-type guard) so a non-JSON error page never crashes the editor.