Skip to content

Add conditional workflow connectors based on job artifacts (#505)#561

Open
fernandorocagonzalez wants to merge 3 commits into
ctrliq:mainfrom
fernandorocagonzalez:feat-505-conditional-workflow-connectors
Open

Add conditional workflow connectors based on job artifacts (#505)#561
fernandorocagonzalez wants to merge 3 commits into
ctrliq:mainfrom
fernandorocagonzalez:feat-505-conditional-workflow-connectors

Conversation

@fernandorocagonzalez

@fernandorocagonzalez fernandorocagonzalez commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Summary

Implements #505: a fourth workflow connector type, On Condition, that routes workflow execution based on the artifacts (set_stats data) produced by the parent node.

A conditional edge stores four properties:

  • trigger — parent outcome required before evaluating: success (default), failure or always
  • artifact_key — the artifact to inspect
  • operatoreq / ne
  • expected_value — parsed as JSON when possible (true, 3), otherwise compared as a plain string

The edge activates the downstream node only when the parent finishes with the selected outcome and the condition evaluates to true against the parent's effective artifacts (its ancestor_artifacts merged with its own job's artifacts — the same data already passed downstream as extra_vars). A missing key never matches.

Design notes

  • New condition_nodes m2m on WorkflowJobTemplateNode/WorkflowJobNode, backed by through models that carry the per-edge condition (backward-compatible migration; existing workflows untouched).
  • The DAG engine evaluates conditions in bfs_nodes_to_run, DNR marking and ALL-convergence. A condition edge only counts as an error-handling path for a failed node when it will actually fire, keeping failure_nodes semantics.
  • API: /workflow_job_template_nodes/N/condition_nodes/ (and the workflow job node mirror). Association POST accepts the condition fields; re-posting an existing link updates its condition. Node serializers expose condition_nodes + condition_edges (prefetched, no N+1).
  • Condition data survives template-to-job instantiation, relaunch (including relaunch-from-failed carry-forward) and WFJT deep copy.
  • awxkit knows the new endpoints, so awx export / awx import round-trips conditional edges together with their condition. Without this an exported workflow would come back with the condition children as unconditional root nodes.
  • UI: new link type in the visualizer (link modal + Add Node wizard), amber edge color, tooltip showing the condition, legend entry. Strings added to the i18n catalogs (Spanish translations included).

Also fixed along the way

  • Adding any node posted a blank identifier (and other wizard-internal fields) in the node create body, which 400s on save.

This PR originally also carried a fix for the PF6 Add Node wizard advancing two steps per click, but #559 fixed that same bug upstream, so the branch is now rebased on current main and that commit was dropped.

Screenshots

Editing a conditional link:

edit link modal

Add Node wizard with the new run type:

wizard run step

Visualizer with conditional connectors and the updated legend:

visualizer with legend

The same workflow routing on its artifacts — environment: staging run vs environment: production run (the parent playbook publishes the value via set_stats):

staging run

production run

Testing

  • Unit: DAG engine scenarios (trigger/outcome matrix, DNR, ALL-convergence, error-handling paths, carried-forward relaunch nodes, JSON/string/bool comparison edge cases).
  • Functional: association API (validation, re-POST update, cycle/duplicate rejection, RBAC), template-to-job copy, WFJT deep copy.
  • UI: reducer, link modals, wizard and visualizer suites updated/extended; full suite green.
  • Verified end-to-end on a live instance: the same workflow routes to different branches purely based on set_stats output (screenshots above).
  • Export/import round-trip verified on a live instance: awx export includes the condition data, the import recreates the edges, and re-importing after editing a condition updates the link in place.

Known follow-ups (happy to send separate PRs)

@cigamit

cigamit commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

I may have already fixed the Wizard Step Skipping in #559

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new “On Condition” workflow connector type across the backend workflow DAG engine, REST API, and the legacy React workflow visualizer so workflow routing can depend on parent node artifacts (set_stats), and also fixes issues in the Add Node wizard flow/body.

Changes:

  • Backend: introduces condition-edge through models, condition evaluation logic, DAG traversal updates, deep-copy/link inheritance support, and migrations.
  • API: adds /condition_nodes/ association endpoint for WFJT nodes and exposes condition_nodes / condition_edges in node serializers.
  • UI: adds condition link creation/editing (modal + wizard), renders condition edge styling, and updates reducer/link persistence logic.

Reviewed changes

Copilot reviewed 38 out of 54 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
awx/ui/src/screens/Template/WorkflowJobTemplateVisualizer/VisualizerLink.js Colors conditional links with a warning stroke token.
awx/ui/src/screens/Template/WorkflowJobTemplateVisualizer/Visualizer.js Persists condition edges via new associate/disassociate API calls and detects condition updates.
awx/ui/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/useWorkflowNodeSteps.js Prevents wizard nav from skipping blocked steps; persists condition params in query.
awx/ui/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/useRunTypeStep.js Adds condition initial values and blocks Next until artifact key is set for condition links.
awx/ui/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/RunStep.js Adds “On Condition” card and condition-editing fields (trigger/key/operator/value).
awx/ui/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeNextButton.test.js Updates tests to assert single-advance behavior and disabled state.
awx/ui/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeNextButton.js Simplifies Next behavior to call onNext directly (wizard step advance fix).
awx/ui/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeModal.test.js Updates wizard mock to avoid double-advance; adds condition defaults.
awx/ui/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeModal.js Removes prior triggerNext plumbing; seeds condition fields in Formik initialValues.
awx/ui/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeEditModal.js Plumbs condition fields through edit modal value extraction.
awx/ui/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeAddModal.js Builds linkCondition payload and strips wizard-only fields from node create POST body.
awx/ui/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkModal.test.js Adds coverage for condition link fields and callback payload shape.
awx/ui/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkModal.js Adds condition option, condition form fields, and passes condition data to callbacks.
awx/ui/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkEditModal.test.js Updates expected reducer payload to include linkCondition: null when not conditional.
awx/ui/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkEditModal.js Dispatches UPDATE_LINK with condition payload.
awx/ui/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkAddModal.test.js Updates expected reducer payload to include linkCondition: null.
awx/ui/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkAddModal.js Dispatches CREATE_LINK with condition payload.
awx/ui/src/screens/Job/WorkflowOutput/WorkflowOutputLink.js Colors conditional links in workflow output view.
awx/ui/src/components/Workflow/workflowReducer.test.js Adds reducer tests for storing/generating condition links and clearing condition data.
awx/ui/src/components/Workflow/workflowReducer.js Stores linkCondition for conditional links; generates condition links from API node shape.
awx/ui/src/components/Workflow/WorkflowLinkHelp.js Shows trigger + condition expression in tooltip/help panel.
awx/ui/src/components/Workflow/WorkflowLegend.js Adds legend entry for condition links.
awx/ui/src/api/models/WorkflowJobTemplateNodes.js Adds associateConditionNode / disassociateConditionNode client helpers.
awx/main/tests/unit/scheduler/test_dag_workflow.py Adds unit coverage for condition evaluation and DAG traversal/ALL-convergence semantics.
awx/main/tests/unit/models/test_workflow_unit.py Updates mocks to include condition link reverse relation.
awx/main/tests/functional/test_copy.py Verifies WFJT deep copy preserves condition link through data.
awx/main/tests/functional/models/test_workflow.py Updates DAG init query count; tests WFJT→WFJ inheritance of condition links.
awx/main/tests/functional/api/test_workflow_node.py Adds functional tests for WFJT condition link association API.
awx/main/tasks/system.py Extends deep copy relationship reconstruction to clone custom-through M2M rows.
awx/main/scheduler/dag_workflow.py Loads condition links into DAG, evaluates passing edges, and adjusts traversal/DNR/failure semantics.
awx/main/models/workflow.py Adds condition link through models + evaluate_artifact_condition; includes condition parents in parent discovery and inheritance.
awx/main/models/init.py Exports new condition link models.
awx/main/migrations/0201_workflowjobnodeconditionlink_and_more.py Adds DB schema for condition link through models + condition_nodes M2Ms.
awx/main/access.py Prefetches condition relationships; allows attach/unattach for condition_nodes on WFJT nodes.
awx/api/views/init.py Adds WFJT condition_nodes association view with validation and through-row updates; includes condition relationship in cross-rel validation.
awx/api/urls/workflow_job_template_node.py Exposes /condition_nodes/ endpoint for WFJT nodes.
awx/api/urls/workflow_job_node.py Exposes /condition_nodes/ endpoint for WFJ nodes (read-only sublist).
awx/api/serializers.py Exposes condition_nodes and condition_edges on WFJT/WFJ node serializers.
Comments suppressed due to low confidence (1)

awx/ui/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkModal.js:10

  • LinkModal.js's import block uses tab-indented multi-line imports that are inconsistent with the surrounding UI code style (e.g., LinkAddModal.js:1-3 uses standard spaced, single-line imports). This makes the file harder to read and is likely to fight any automated formatting/linting.

Reformat the imports to match the prevailing style (single-line named imports, spaces, trailing commas where applicable).

Implements the backend for ctrliq#505: a fourth workflow connector type that
only activates the downstream node when the parent job finishes with the
selected outcome AND a condition over its artifacts (set_stats data)
evaluates to true.

- New condition_nodes m2m on template and job workflow nodes, backed by
  through models storing trigger (success/failure/always, default
  success), artifact_key, operator (eq, ne) and expected_value
- Workflow DAG engine evaluates conditions against the parent node's
  ancestor_artifacts merged with its own job artifacts (same semantics as
  artifact propagation to downstream extra_vars); the edge only fires
  when the parent outcome matches the trigger; nodes whose unified job
  template was deleted are treated as failures for condition routing
- A condition edge only counts as an error handling path for a failed
  node when it will actually fire (trigger covers the failure and the
  condition holds); otherwise the workflow is marked failed, matching
  failure_nodes semantics
- Artifact comparison parses expected_value as JSON with a bool/int
  cross-type guard (True does not match 1), falling back to string
  comparison
- API sublists /workflow_job_template_nodes/N/condition_nodes/ and
  /workflow_job_nodes/N/condition_nodes/; association POST accepts
  trigger, artifact_key, operator and expected_value; re-posting updates
  the condition; node serializers expose condition_nodes and
  condition_edges (through rows prefetched to avoid N+1 on node lists)
- Condition data is preserved on template-to-job instantiation, relaunch
  (including relaunch-from-failed carry-forward) and WFJT deep copy; the
  deep-copy through-row clone is idempotent and direction-aware
- RBAC: connection management requires WFJT admin, same as other edges
- Link add/edit modal offers an On Condition run type with an Evaluate on
  selector (On Success by default, On Failure, Always) plus artifact key,
  operator (equals / not equals) and expected value fields
- Add Node wizard Run step gains an On Condition card with the same
  fields; the in-flight condition values survive the launch-config
  resetForm, and wizard nav can no longer jump past a step whose Next
  button is disabled
- Visualizer loads condition edges from condition_nodes/condition_edges,
  saves them through the condition_nodes association endpoint (re-posting
  updates the stored condition) and disassociates them like other links
- Condition links render in warning color in the visualizer and the
  workflow job output view; the link tooltip shows the trigger and the
  condition; the legend documents the new connector
- Node-level fields (identifier, nodeType, nodeResource) are stripped
  from promptValues when adding a node, fixing a pre-existing 400 from
  posting a blank identifier on node creation
- New strings extracted to the i18n catalogs with Spanish translations
Register the condition_nodes endpoints so awx export includes the
fourth edge type instead of silently dropping it (an imported
workflow would otherwise run condition children unconditionally as
root nodes). Each exported edge keeps the target node natural key
under 'id' plus its trigger, artifact_key, operator and
expected_value, and the import posts the full association.
Re-importing an existing link updates its condition in place, so
export/import round-trips stay idempotent.

Also adds WorkflowJobTemplateNode.add_condition_node for parity with
the other add_*_node helpers.
@fernandorocagonzalez fernandorocagonzalez force-pushed the feat-505-conditional-workflow-connectors branch from 5ab8398 to 05ca813 Compare July 4, 2026 08:28
@fernandorocagonzalez

Copy link
Copy Markdown
Contributor Author

You did indeed, thanks for the heads up. I rebased the branch on current main and dropped my wizard fix commit since #559 covers the same bug, so the PR is down to the 3 feature commits (backend, UI, awxkit) and is mergeable again.

Quick summary of what changed with the rebase:

  • The double step advance fix now comes from Fix multiple usability issues after PF6 upgrade #559, nothing in this PR touches NodeNextButton.js anymore (which also makes the Copilot remark about type="submit" obsolete here, that code is untouched upstream code now).
  • The small identifier fix (blank identifier leaking into the node create body and causing a 400 on save) is still included, it lives in the UI commit.
  • Re-ran the visualizer test suites on top of your fix, all green (26 suites, 119 tests), and the PR description was updated accordingly.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 40 out of 56 changed files in this pull request and generated 1 comment.

Comment on lines 434 to 435
log.error("endpoint: %s, id: %s", endpoint.endpoint, rel_page['id'])
self._has_error = True

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The log.error and self._has_error = True after the successful post are not part of this change, that code is already on main exactly like that. The only new lines here are the post_data.update(extra_fields) and the lookup handling above it. It does look like leftover debugging upstream (a plain 2xx without body raises exc.NoContent, so hitting that branch means the endpoint returned a body), but I'd rather not fold that fix into this PR to keep the scope tight. Happy to send it separately if there's interest.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fix this small issue and I think this one is ready to merge.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

3 participants