Skip to content

feat(all): Add support for multiple-level nested local DAGs#1493

Merged
yohamta0 merged 11 commits into
mainfrom
multi-nest-dag
Dec 20, 2025
Merged

feat(all): Add support for multiple-level nested local DAGs#1493
yohamta0 merged 11 commits into
mainfrom
multi-nest-dag

Conversation

@yohamta0

@yohamta0 yohamta0 commented Dec 20, 2025

Copy link
Copy Markdown
Collaborator

Summary by CodeRabbit

  • New Features

    • Optional parentSubDAGRunId query to fetch nested sub-DAG runs; sub-run lists and modal show root/parent DAG context, status filters with counts, and improved keyboard navigation.
  • Bug Fixes / UX

    • Consolidated local DAG handling for multi-level execution, improved error responses for missing runs, and small UI spacing tidy-ups.
  • Tests

    • Reorganized and expanded integration/unit tests for multi-level sub-DAG scenarios and temp-DAG file handling.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai

coderabbitai Bot commented Dec 20, 2025

Copy link
Copy Markdown

Walkthrough

Adds an optional query parameter parentSubDAGRunId to sub-DAG runs APIs and client/UI, consolidates multiple local DAG YAMLs into a single temp file for sub-DAG execution, updates backend lookup to accept a parent sub-run context, and significantly rewrites and expands integration/unit tests alongside UI filtering and prop wiring.

Changes

Cohort / File(s) Summary
API Contract & Generated Code
api/v2/api.yaml, api/v2/api.gen.go, ui/src/api/v2/schema.ts
Adds optional query parameter parentSubDAGRunId to /dag-runs/{name}/{dagRunId}/sub-dag-runs; updates generated API code, embedded swagger payload, and UI client schema.
Frontend Handler
internal/service/frontend/api/v2/dagruns.go
GetSubDAGRuns now accepts parentSubDAGRunId; looks up that sub-DAG run via FindSubDAGRunStatus, returns 404 if missing, and uses the root DAG run reference for subsequent detail retrieval and iteration.
Executor — Local DAG Consolidation
internal/runtime/executor/dag_runner.go
createTempDAGFile signature extended to accept localDAGs map[string]*core.DAG; writes the primary DAG YAML then appends other local DAG YAMLs separated by ---; failures remove the temp file and return errors. NewSubDAGExecutor passes LocalDAGs.
Executor Tests
internal/runtime/executor/dag_runner_test.go
Updated TestCreateTempDAGFile to new signature; added TestCreateTempDAGFile_WithLocalDAGs validating that multiple DAGs are combined with --- and file metadata.
Integration Tests (subdag / localdag)
internal/integration/subdag_test.go, internal/integration/localdag_test.go
Rewrote/expanded sub-DAG integration tests into TestInlineSubDAG with many subtests; removed localdag_test.go (deleted entire local-DAG integration suite).
UI — Sub-DAG Lists & Node Row
ui/src/features/dags/components/dag-details/SubDAGRunsList.tsx, ui/src/features/dags/components/dag-details/NodeStatusTableRow.tsx
SubDAGRunsList now accepts rootDagName and rootDagRunId, optionally passes parentSubDAGRunId to API, adds status-based filtering, per-status counts, filtered rendering, and no-match UI; call sites updated to pass root-level fields.
UI — Parallel Execution Modal & Caller
ui/src/features/dags/components/dag-execution/ParallelExecutionModal.tsx, ui/src/features/dags/components/DAGStatus.tsx
ParallelExecutionModal props extended with rootDagName, rootDagRunId, parentDagRunId?; conditionally queries with parentSubDAGRunId, adds status filtering, keyboard navigation and index mapping for filtered lists, auto-scroll, and selection reset on filter change. DAGStatus passes new props into the modal.
UI — Sidebar & Minor Cleanup
ui/src/menu.tsx, ui/src/features/dags/components/dag-details/DAGStatusOverview.tsx
mainListItems converted to forwardRef<HTMLDivElement, MainListItemsProps> with public props (isOpen?, onNavItemClick?, onToggle?); minor blank-line cleanups in DAGStatusOverview.tsx.
Test / Misc
internal/test/server.go
Request.Send now logs expected vs actual HTTP status prior to assertion.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant Browser as Browser (UI)
    participant UI as Frontend Components
    participant Client as Frontend API Client
    participant FE as Frontend Server (GetSubDAGRuns)
    participant Store as Status Store / DB

    Browser->>UI: open ParallelExecutionModal / SubDAGRunsList (rootDagName, rootDagRunId[, parentDagRunId])
    UI->>Client: GET /dag-runs/{name}/{dagRunId}/sub-dag-runs?parentSubDAGRunId=...
    Client->>FE: request with parentSubDAGRunId
    FE->>Store: FindSubDAGRunStatus(parentSubDAGRunId) [if provided]
    Store-->>FE: sub-dag-run status or not found
    alt parent found
        FE->>Store: GetSavedStatus(rootDagName, rootDagRunId)
        Store-->>FE: root status + sub-run details
    else parent not provided
        FE->>Store: GetSavedStatus(rootDagName, rootDagRunId)
        Store-->>FE: root status + sub-run details
    end
    FE-->>Client: aggregated sub-run list (includes statuses)
    Client-->>UI: sub-run payload
    UI->>UI: compute status counts, apply filter, render filtered list (map filtered index → original index)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

  • Areas needing focused review:
    • internal/service/frontend/api/v2/dagruns.go — parent lookup, fallback to root context, 404 messages, and detail-lookup changes.
    • internal/runtime/executor/dag_runner.go — createTempDAGFile signature, append/cleanup/error paths and NewSubDAGExecutor changes.
    • ui/src/features/dags/components/dag-execution/ParallelExecutionModal.tsx — conditional query with parentSubDAGRunId, filtering logic, keyboard navigation, index mapping, and scrolling behavior.
    • ui/src/features/dags/components/dag-details/SubDAGRunsList.tsx — status-count computation, filtering, and edge cases (no-match, counts).
    • internal/integration/* tests — large rewritten test suite and deleted localdag tests (verify intent, coverage, and flakiness).

Possibly related PRs

Poem

🐰 I stitched the YAMLs, three dashes in a row,
Parents and children now better to know,
Counts and filters, a hopping delight,
Sub-runs found by day or by night,
Thump-thump — ready to run, off I go! 🥕

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 23.08% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'feat(all): Add support for multiple-level nested local DAGs' accurately reflects the main objective: enabling support for multiple-level nested local DAGs across the codebase.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch multi-nest-dag

📜 Recent review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 77658e8 and 5b9bc2c.

📒 Files selected for processing (2)
  • internal/service/frontend/api/v2/dags.go (1 hunks)
  • internal/test/server.go (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.go

📄 CodeRabbit inference engine (AGENTS.md)

**/*.go: Backend entrypoint in cmd/ orchestrates the scheduler and CLI; runtime, persistence, and service layers sit under internal/* (for example internal/runtime, internal/persistence)
Keep Go files gofmt/goimports clean; use tabs, PascalCase for exported symbols (SchedulerClient), lowerCamelCase for locals, and Err... names for package-level errors
Repository linting relies on golangci-lint; prefer idiomatic Go patterns, minimal global state, and structured logging helpers in internal/common

Files:

  • internal/test/server.go
  • internal/service/frontend/api/v2/dags.go
🧠 Learnings (1)
📚 Learning: 2025-12-04T10:34:17.062Z
Learnt from: CR
Repo: dagu-org/dagu PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-04T10:34:17.062Z
Learning: Applies to **/*_test.go : Use `stretchr/testify/require` and shared fixtures from `internal/test` instead of duplicating mocks

Applied to files:

  • internal/test/server.go
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Build
  • GitHub Check: Test on ubuntu-latest
🔇 Additional comments (2)
internal/service/frontend/api/v2/dags.go (1)

534-540: LGTM! Improved error handling for missing DAG runs.

This change appropriately distinguishes between missing DAG runs and other errors by returning a specific 404 response when execution.ErrNoStatusData is encountered. The use of errors.Is is idiomatic Go, and the error message clearly communicates which DAG run was not found.

internal/test/server.go (1)

216-216: LGTM! Helpful debugging addition.

The log statement provides quick visibility into expected vs. actual status codes before the assertion, which is useful for debugging test failures—especially relevant given the expanded sub-DAG integration tests in this PR. Since t.Logf only outputs in verbose mode or on failure, it won't pollute normal test runs.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
internal/runtime/executor/dag_runner.go (1)

503-539: Handle write errors consistently when aggregating local DAG YAML

createTempDAGFile correctly aggregates all local DAG YAMLs into a single temp file, but it ignores the error from tempFile.WriteString("---\n"). In the (rare) case of an I/O failure, you’d silently produce a malformed file without propagating the error, which can make failures harder to diagnose.

You’re already doing the right thing for the subsequent Write; applying the same pattern to the separator keeps behavior consistent and safer.

Proposed fix for separator write error handling
 func createTempDAGFile(dagName string, yamlData []byte, localDAGs map[string]*core.DAG) (string, error) {
@@
 	// Write the YAML data
 	if _, err := tempFile.Write(yamlData); err != nil {
 		_ = os.Remove(tempFile.Name())
 		return "", fmt.Errorf("failed to write YAML data: %w", err)
 	}
 
 	// Add other local DAG data to the temporary file
 	for _, localDAG := range localDAGs {
 		if localDAG.Name == dagName {
 			continue
 		}
-		tempFile.WriteString("---\n")
-		if _, err := tempFile.Write(localDAG.YamlData); err != nil {
+		if _, err := tempFile.WriteString("---\n"); err != nil {
+			_ = os.Remove(tempFile.Name())
+			return "", fmt.Errorf("failed to write YAML separator: %w", err)
+		}
+		if _, err := tempFile.Write(localDAG.YamlData); err != nil {
 			_ = os.Remove(tempFile.Name())
 			return "", fmt.Errorf("failed to write YAML data: %w", err)
 		}
 	}
 
 	return tempFile.Name(), nil
 }
ui/src/features/dags/components/dag-details/NodeStatusTableRow.tsx (2)

963-974: Same fallback inconsistency as desktop view.

The mobile view has the same inconsistent fallback handling as identified in the desktop view (lines 588-600). Line 967 provides a fallback for rootDagRunId but Line 966 does not provide one for rootDagName, and Line 965 removed the fallback for dagRunId.

Apply the same fix as suggested for the desktop view to maintain consistency between both views.

Suggested fix
             <SubDAGRunsList
               dagName={dagRun.name}
-              dagRunId={dagRun.dagRunId}
-              rootDagName={dagRun.rootDAGRunName}
+              dagRunId={dagRun.dagRunId || ''}
+              rootDagName={dagRun.rootDAGRunName || dagRun.name}
               rootDagRunId={dagRun.rootDAGRunId || ''}
               subDagName={subDagName || ''}
               allSubRuns={allSubRuns}

588-600: Inconsistent fallback handling for root DAG and DAG run properties.

Props passed to SubDAGRunsList are inconsistent. Line 593 provides a fallback for rootDagRunId (dagRun.rootDAGRunId || '') but line 592 does not provide a fallback for rootDagName (dagRun.rootDAGRunName), even though both are required string props in the component's type definition. Similarly, line 591 passes dagRunId={dagRun.dagRunId} without a fallback.

While the schema marks these as required fields, the defensive check at line 424 suggests they can be undefined at runtime. Add consistent fallbacks to ensure type safety:

  <SubDAGRunsList
    dagName={dagRun.name}
-   dagRunId={dagRun.dagRunId}
-   rootDagName={dagRun.rootDAGRunName}
+   dagRunId={dagRun.dagRunId || ''}
+   rootDagName={dagRun.rootDAGRunName || dagRun.name}
    rootDagRunId={dagRun.rootDAGRunId || ''}
    subDagName={subDagName || ''}
    allSubRuns={allSubRuns}
ui/src/features/dags/components/dag-execution/ParallelExecutionModal.tsx (1)

166-175: Edge case: modulo with zero when no sub-runs match the filter.

When filteredSubRuns.length is 0, the modulo operation causes unexpected behavior (NaN or Infinity). Add a guard to prevent navigation when the list is empty.

Suggested fix
       case 'ArrowDown':
         e.preventDefault();
+        if (filteredSubRuns.length === 0) break;
         setSelectedIndex((prev) => prev === null ? 0 : (prev + 1) % filteredSubRuns.length);
         break;
       case 'ArrowUp':
         e.preventDefault();
+        if (filteredSubRuns.length === 0) break;
         setSelectedIndex((prev) => prev === null ? filteredSubRuns.length - 1 : (prev - 1 + filteredSubRuns.length) % filteredSubRuns.length);
         break;
🧹 Nitpick comments (7)
ui/src/menu.tsx (1)

44-52: Redundant conditional: both branches apply the same class.

The ternary on lines 47-48 evaluates to text-primary-foreground regardless of isActive. Either remove the conditional or apply distinct styling for the active state if differentiation is intended.

🔎 Proposed fix
     <span
       className={cn(
-        'flex items-center justify-center w-5 h-5 transform-none text-primary-foreground',
-        isActive ? 'text-primary-foreground' : 'text-primary-foreground'
+        'flex items-center justify-center w-5 h-5 transform-none text-primary-foreground'
       )}
     >
       {children}
     </span>
api/v2/api.yaml (1)

1485-1498: Sub‑DAG filtering parameter looks consistent and well‑documented

The new parentSubDAGRunId query parameter and the updated description clearly convey the multi‑level nested behavior and match existing naming patterns. This is a clean extension of the public API surface.

If you end up reusing this parameter on other endpoints in the future, consider promoting it into components.parameters for reuse, but it’s fine as-is for a single use site.

ui/src/api/v2/schema.ts (1)

575-586: TS client update correctly reflects the new OpenAPI parameter

The updated JSDoc on /dag-runs/{name}/{dagRunId}/sub-dag-runs and the parentSubDAGRunId?: string addition to getSubDAGRuns.parameters.query are in sync with the OpenAPI spec and keep the client surface straightforward to consume.

Just ensure this file continues to be regenerated from api/v2/api.yaml (rather than hand‑edited) so it stays aligned with future spec changes.

Also applies to: 3256-3263

internal/runtime/executor/dag_runner.go (1)

60-84: Local sub‑DAG execution now correctly carries all inline DAG definitions

Passing rCtx.DAG.LocalDAGs into createTempDAGFile ensures the temp file for a local sub‑DAG contains not just the child DAG’s YAML but also its sibling inline DAGs, which is what enables the 3‑level nesting scenarios you’re testing.

One thing to be aware of: in the distributed path (BuildCoordinatorTask / dispatchToCoordinator), you still send string(e.DAG.YamlData) only. That means multi‑level nesting for inline DAGs is currently guaranteed only for the local execution path; if you ever attach a WorkerSelector to an inline DAG and rely on distributed sub‑DAG runs there, you’ll likely need a similar YAML aggregation strategy for the task payload as well.

Also applies to: 171-185

ui/src/features/dags/components/dag-details/SubDAGRunsList.tsx (1)

131-142: Unstable dependency: nodeSubRunIds Set recreated every render.

nodeSubRunIds is a new Set instance on every render (line 97), causing statusLabelMap to recompute unnecessarily. Consider memoizing the Set or using a stable representation.

Suggested fix
-  // Create a map of dagRunIds that belong to THIS node
-  const nodeSubRunIds = new Set(allSubRuns.map((sr) => sr.dagRunId));
+  // Create a map of dagRunIds that belong to THIS node (memoized for stable reference)
+  const nodeSubRunIds = useMemo(
+    () => new Set(allSubRuns.map((sr) => sr.dagRunId)),
+    [allSubRuns]
+  );
ui/src/features/dags/components/dag-execution/ParallelExecutionModal.tsx (2)

15-30: Consider extracting shared constants to reduce duplication.

StatusFilterValue type and STATUS_DISPLAY_LABELS constant are duplicated between this file and SubDAGRunsList.tsx. Consider extracting to a shared location for maintainability.


198-212: Auto-scroll may reference wrong child element after filtering.

container.children[selectedIndex] assumes children match filteredSubRuns indices. If the container has additional child elements (like the empty state message), this could scroll to the wrong element or fail silently.

Consider using data-index attributes and querySelector for more robust element targeting:

Suggested fix
   React.useEffect(() => {
     if (selectedIndex !== null && scrollContainerRef.current) {
       const container = scrollContainerRef.current;
-      const selectedElement = container.children[selectedIndex] as HTMLElement;
+      const selectedElement = container.querySelector(`[data-index="${selectedIndex}"]`) as HTMLElement;
       
       if (selectedElement) {
         selectedElement.scrollIntoView({
           block: 'nearest',
           behavior: 'smooth'
         });
       }
     }
   }, [selectedIndex]);

Then add data-index={displayIndex} to the rendered items at line 272.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 887355f and cbcbc44.

⛔ Files ignored due to path filters (2)
  • ui/favicon.ico is excluded by !**/*.ico
  • ui/src/assets/images/logo_dark.png is excluded by !**/*.png
📒 Files selected for processing (12)
  • api/v2/api.gen.go (3 hunks)
  • api/v2/api.yaml (1 hunks)
  • internal/integration/subdag_test.go (1 hunks)
  • internal/runtime/executor/dag_runner.go (3 hunks)
  • internal/service/frontend/api/v2/dagruns.go (3 hunks)
  • ui/src/api/v2/schema.ts (2 hunks)
  • ui/src/features/dags/components/DAGStatus.tsx (1 hunks)
  • ui/src/features/dags/components/dag-details/DAGStatusOverview.tsx (0 hunks)
  • ui/src/features/dags/components/dag-details/NodeStatusTableRow.tsx (2 hunks)
  • ui/src/features/dags/components/dag-details/SubDAGRunsList.tsx (7 hunks)
  • ui/src/features/dags/components/dag-execution/ParallelExecutionModal.tsx (5 hunks)
  • ui/src/menu.tsx (3 hunks)
💤 Files with no reviewable changes (1)
  • ui/src/features/dags/components/dag-details/DAGStatusOverview.tsx
🧰 Additional context used
📓 Path-based instructions (5)
{api/v1,api/v2}/**/*.{proto,yaml,yml,json}

📄 CodeRabbit inference engine (AGENTS.md)

API definitions live in api/v1 and api/v2; generated server stubs land in internal/service, while matching TypeScript clients flow into ui/src/api

Files:

  • api/v2/api.yaml
**/*.go

📄 CodeRabbit inference engine (AGENTS.md)

**/*.go: Backend entrypoint in cmd/ orchestrates the scheduler and CLI; runtime, persistence, and service layers sit under internal/* (for example internal/runtime, internal/persistence)
Keep Go files gofmt/goimports clean; use tabs, PascalCase for exported symbols (SchedulerClient), lowerCamelCase for locals, and Err... names for package-level errors
Repository linting relies on golangci-lint; prefer idiomatic Go patterns, minimal global state, and structured logging helpers in internal/common

Files:

  • api/v2/api.gen.go
  • internal/runtime/executor/dag_runner.go
  • internal/service/frontend/api/v2/dagruns.go
  • internal/integration/subdag_test.go
ui/**/*.{ts,tsx,jsx,js}

📄 CodeRabbit inference engine (ui/CLAUDE.md)

ui/**/*.{ts,tsx,jsx,js}: Use developer-centric UI design with high information density, minimal whitespace, compact components, and no unnecessary decorations
Support both light and dark modes for all UI components using Tailwind CSS class pairs like dark:bg-slate-700
NEVER use full-page loading overlays or LoadingIndicator components that hide content - show stale data while fetching updates instead
Use compact modal design with small headers, minimal padding (p-2 or p-3), tight spacing, and support keyboard navigation (arrows, enter, escape)
Use small heights for form elements: select boxes h-7 or smaller, buttons h-7 or h-8, inputs with compact padding (py-0.5 or py-1)
Minimize row heights in tables and lists while maintaining readability, merge related columns, and always handle long text with whitespace-normal break-words
Use consistent metadata styling with bg-slate-200 dark:bg-slate-700 backgrounds and maintain text hierarchy with primary/secondary/muted text colors
Use flexbox-first layouts with min-h-0 and overflow-hidden to prevent layout breaks, account for fixed elements when setting heights
Maintain keyboard navigation support in all interactive components with appropriate focus indicators and ARIA labels

Files:

  • ui/src/features/dags/components/DAGStatus.tsx
  • ui/src/api/v2/schema.ts
  • ui/src/features/dags/components/dag-execution/ParallelExecutionModal.tsx
  • ui/src/features/dags/components/dag-details/SubDAGRunsList.tsx
  • ui/src/features/dags/components/dag-details/NodeStatusTableRow.tsx
  • ui/src/menu.tsx
ui/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

ui/**/*.{ts,tsx}: The React + TypeScript frontend resides in ui/, with production bundles copied to internal/service/frontend/assets by make ui
UI code follows ESLint + Prettier (2-space indent) and Tailwind utilities; name React components in PascalCase (JobList.tsx) and hooks with use* (useJobs.ts)

Files:

  • ui/src/features/dags/components/DAGStatus.tsx
  • ui/src/api/v2/schema.ts
  • ui/src/features/dags/components/dag-execution/ParallelExecutionModal.tsx
  • ui/src/features/dags/components/dag-details/SubDAGRunsList.tsx
  • ui/src/features/dags/components/dag-details/NodeStatusTableRow.tsx
  • ui/src/menu.tsx
**/*_test.go

📄 CodeRabbit inference engine (AGENTS.md)

**/*_test.go: Co-locate Go tests as *_test.go; favour table-driven cases and cover failure paths
Use stretchr/testify/require and shared fixtures from internal/test instead of duplicating mocks

Files:

  • internal/integration/subdag_test.go
🧠 Learnings (4)
📚 Learning: 2025-12-04T10:34:17.062Z
Learnt from: CR
Repo: dagu-org/dagu PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-04T10:34:17.062Z
Learning: Applies to **/*_test.go : Co-locate Go tests as `*_test.go`; favour table-driven cases and cover failure paths

Applied to files:

  • internal/integration/subdag_test.go
📚 Learning: 2025-12-04T10:34:17.062Z
Learnt from: CR
Repo: dagu-org/dagu PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-04T10:34:17.062Z
Learning: `make test` (or `make test-coverage`) executes the Go suite via `gotestsum`; append `TEST_TARGET=./internal/...` to focus packages

Applied to files:

  • internal/integration/subdag_test.go
📚 Learning: 2025-12-04T10:34:17.062Z
Learnt from: CR
Repo: dagu-org/dagu PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-04T10:34:17.062Z
Learning: Applies to ui/**/*.{ts,tsx} : The React + TypeScript frontend resides in `ui/`, with production bundles copied to `internal/service/frontend/assets` by `make ui`

Applied to files:

  • ui/src/menu.tsx
📚 Learning: 2025-12-04T10:34:01.045Z
Learnt from: CR
Repo: dagu-org/dagu PR: 0
File: ui/CLAUDE.md:0-0
Timestamp: 2025-12-04T10:34:01.045Z
Learning: Applies to ui/**/*.{ts,tsx,jsx,js} : Minimize row heights in tables and lists while maintaining readability, merge related columns, and always handle long text with `whitespace-normal break-words`

Applied to files:

  • ui/src/menu.tsx
🧬 Code graph analysis (4)
api/v2/api.gen.go (1)
api/v1/api.gen.go (1)
  • InvalidParamFormatError (1166-1169)
internal/runtime/executor/dag_runner.go (1)
internal/core/dag.go (1)
  • DAG (33-135)
internal/service/frontend/api/v2/dagruns.go (3)
api/v2/api.gen.go (3)
  • GetSubDAGRunsRequestObject (5444-5448)
  • GetSubDAGRunsResponseObject (5450-5452)
  • GetSubDAGRuns404JSONResponse (5465-5465)
internal/core/execution/dagrun.go (1)
  • NewDAGRunRef (168-173)
internal/core/execution/runstatus.go (1)
  • DAGRunStatus (35-57)
internal/integration/subdag_test.go (3)
internal/test/helper.go (1)
  • Setup (81-225)
internal/core/dag.go (1)
  • DAG (33-135)
internal/runtime/agent/agent.go (1)
  • Agent (49-128)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Build
  • GitHub Check: Test on ubuntu-latest
🔇 Additional comments (11)
ui/src/menu.tsx (3)

2-2: LGTM!

The import of useConfig from ConfigContext appropriately enables dynamic title rendering from the application configuration.


96-98: LGTM!

The collapsed state correctly displays the first letter of the configured title with an appropriate fallback to 'Dagu'.


103-109: LGTM!

The expanded state correctly displays the full configured title with consistent fallback handling.

api/v2/api.gen.go (3)

1291-1297: New ParentSubDAGRunId query param wiring looks correct

Optional ParentSubDAGRunId *string is added with consistent form/json tags and a clear doc comment; this matches how other query params are modeled and should integrate cleanly with the rest of the handler stack.


3053-3067: parentSubDAGRunId binding matches existing error‑handling pattern

The new BindQueryParameter call for parentSubDAGRunId follows the same style and error‑handling (InvalidParamFormatError) as other optional query params in this file. No issues with required/optional semantics or naming.


8473-8666: Regenerated swaggerSpec blob is expected for the API surface change

The base64‑encoded, gzipped swaggerSpec chunk has been updated, which is expected after modifying api/v2/api.yaml. As long as this file was regenerated via oapi-codegen from the updated spec (rather than edited manually), this change looks good.

internal/service/frontend/api/v2/dagruns.go (2)

871-889: Correct: Using root reference for all sub-DAG run detail lookups.

Using rootRef for getSubDAGRunDetail calls ensures consistent lookup from the root DAG's storage, which is the correct approach for multi-level nested DAGs where all sub-runs are stored under the root.


828-860: Implementation correctly supports multi-level nested DAG lookups.

The GetSubDAGRuns function properly branches based on the ParentSubDAGRunId parameter to handle both root DAG runs and nested sub-DAG runs. The GetSubDAGRunsParams struct includes the required ParentSubDAGRunId field, and error messages appropriately reflect the root/DAG context when performing status lookups. The function correctly iterates through both regular and repeated sub-runs, passing rootRef to getSubDAGRunDetail for consistent storage access.

ui/src/features/dags/components/dag-details/SubDAGRunsList.tsx (2)

63-78: Good: API call correctly uses root DAG context with optional parent sub-DAG run ID.

The implementation correctly:

  • Uses rootDagName and rootDagRunId for API path parameters
  • Conditionally passes parentSubDAGRunId only for nested sub-DAGs
  • Aligns with the backend changes for multi-level nested DAG support

248-278: Filter UI follows design guidelines.

The status filter buttons use compact styling (px-1.5 py-0.5), support both light and dark modes, and only appear when there are multiple status types to filter. Good use of conditional rendering to reduce UI clutter.

ui/src/features/dags/components/dag-execution/ParallelExecutionModal.tsx (1)

64-86: API integration correctly handles nested sub-DAG context.

The useQuery call properly:

  • Uses root DAG identifiers for the API path
  • Conditionally passes parentSubDAGRunId for nested scenarios
  • Pauses fetching when modal is closed
  • Uses a reasonable 3-second refresh interval

Comment thread internal/integration/subdag_test.go Outdated
Comment on lines +401 to +403
rootDagName={dagRun.rootDAGRunName}
rootDagRunId={dagRun.rootDAGRunId}
parentDagRunId={dagRun.dagRunId}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Potential type mismatch: optional values passed to required props.

dagRun.rootDAGRunName and dagRun.rootDAGRunId may be undefined for root DAG runs, but ParallelExecutionModal declares rootDagName and rootDagRunId as required string props. This could lead to runtime issues or TypeScript errors.

Consider providing fallback values:

Suggested fix
-          rootDagName={dagRun.rootDAGRunName}
-          rootDagRunId={dagRun.rootDAGRunId}
+          rootDagName={dagRun.rootDAGRunName || dagRun.name}
+          rootDagRunId={dagRun.rootDAGRunId || dagRun.dagRunId}
           parentDagRunId={dagRun.dagRunId}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
rootDagName={dagRun.rootDAGRunName}
rootDagRunId={dagRun.rootDAGRunId}
parentDagRunId={dagRun.dagRunId}
rootDagName={dagRun.rootDAGRunName || dagRun.name}
rootDagRunId={dagRun.rootDAGRunId || dagRun.dagRunId}
parentDagRunId={dagRun.dagRunId}
🤖 Prompt for AI Agents
In ui/src/features/dags/components/DAGStatus.tsx around lines 401 to 403,
dagRun.rootDAGRunName and dagRun.rootDAGRunId can be undefined for root DAG runs
while ParallelExecutionModal requires string props; fix by either passing safe
fallbacks (e.g., use the nullish coalescing operator to pass '' or
dagRun.dagRunId when root values are missing) or update ParallelExecutionModal
prop types to accept optional strings and handle undefined inside the
component—pick one approach and apply consistently where the modal is invoked.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
internal/integration/localdag_test.go (1)

111-165: LGTM! Multi-level nested local DAG support verified.

The test correctly validates that multi-level nested local DAGs (root → middle-dag → leaf-dag) now execute successfully. The assertions verify:

  • Successful execution without errors
  • Root node status is NodeSucceeded
  • Middle-dag created a sub-run

The implicit verification (overall success proves leaf-dag executed) is acceptable for this integration test level.

Optional: Consider verifying leaf-dag execution explicitly

For more thorough verification of the 3-level nesting, you could optionally retrieve and verify the middle-dag's sub-run to confirm leaf-dag executed:

 // Verify middle-dag has a sub-run
 require.Len(t, dagRunStatus.Nodes[0].SubRuns, 1, "middle-dag should have one sub-run")
+
+// Optionally verify the leaf-dag execution within middle-dag's sub-run
+middleDAGSubRun := dagRunStatus.Nodes[0].SubRuns[0]
+require.Equal(t, core.Succeeded, middleDAGSubRun.Status, "middle-dag sub-run should succeed")
+// Could further verify leaf-dag is in middle-dag's sub-runs if that level of detail is tracked

However, the current level of verification is sufficient for confirming the feature works end-to-end.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cbcbc44 and f3237de.

📒 Files selected for processing (2)
  • internal/integration/localdag_test.go (2 hunks)
  • internal/runtime/executor/dag_runner_test.go (2 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.go

📄 CodeRabbit inference engine (AGENTS.md)

**/*.go: Backend entrypoint in cmd/ orchestrates the scheduler and CLI; runtime, persistence, and service layers sit under internal/* (for example internal/runtime, internal/persistence)
Keep Go files gofmt/goimports clean; use tabs, PascalCase for exported symbols (SchedulerClient), lowerCamelCase for locals, and Err... names for package-level errors
Repository linting relies on golangci-lint; prefer idiomatic Go patterns, minimal global state, and structured logging helpers in internal/common

Files:

  • internal/integration/localdag_test.go
  • internal/runtime/executor/dag_runner_test.go
**/*_test.go

📄 CodeRabbit inference engine (AGENTS.md)

**/*_test.go: Co-locate Go tests as *_test.go; favour table-driven cases and cover failure paths
Use stretchr/testify/require and shared fixtures from internal/test instead of duplicating mocks

Files:

  • internal/integration/localdag_test.go
  • internal/runtime/executor/dag_runner_test.go
🧠 Learnings (1)
📚 Learning: 2025-12-04T10:34:17.062Z
Learnt from: CR
Repo: dagu-org/dagu PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-04T10:34:17.062Z
Learning: Applies to **/*_test.go : Co-locate Go tests as `*_test.go`; favour table-driven cases and cover failure paths

Applied to files:

  • internal/integration/localdag_test.go
🧬 Code graph analysis (2)
internal/integration/localdag_test.go (1)
internal/core/status.go (3)
  • Succeeded (11-11)
  • NodeSucceeded (57-57)
  • Status (4-4)
internal/runtime/executor/dag_runner_test.go (1)
internal/core/dag.go (1)
  • DAG (33-135)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Build
  • GitHub Check: Test on ubuntu-latest
🔇 Additional comments (2)
internal/runtime/executor/dag_runner_test.go (2)

302-321: LGTM! Backward compatibility maintained.

The update correctly adapts the test to the new three-parameter createTempDAGFile signature by passing nil for localDAGs. The comment clearly documents this is for single-DAG scenarios, and all original assertions remain valid.


323-361: LGTM! Comprehensive test for multi-DAG aggregation.

This new test thoroughly verifies the core feature of aggregating multiple local DAG YAMLs into a single temporary file. The test correctly:

  • Creates a localDAGs map with parent and child DAGs
  • Verifies the output file contains both DAG definitions
  • Confirms the YAML document separator (---) is present
  • Validates the filename pattern

The test structure follows coding guidelines and provides good coverage for the new functionality.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
internal/runtime/executor/dag_runner.go (1)

504-548: Verify if deterministic local DAG order matters.

The function correctly consolidates multiple local DAGs into a single multi-document YAML file. However, the iteration over the localDAGs map (lines 529-541) produces a non-deterministic order since Go maps have random iteration order.

If the DAG loader or execution logic is sensitive to the order of local DAG definitions, this could cause reproducibility or debugging issues.

Optional: ensure deterministic order by sorting

If deterministic order is required, consider sorting the local DAG names before iteration:

// Add other local DAG data to the temporary file
var names []string
for name := range localDAGs {
	if name != dagName {
		names = append(names, name)
	}
}
sort.Strings(names)

for _, name := range names {
	localDAG := localDAGs[name]
	if _, err := tempFile.WriteString("---\n"); err != nil {
		writeErr = err
		goto Fail
	}
	if _, err := tempFile.Write(localDAG.YamlData); err != nil {
		writeErr = err
		goto Fail
	}
}

This would require importing sort at the top of the file.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f3237de and 8cdb188.

📒 Files selected for processing (1)
  • internal/runtime/executor/dag_runner.go (3 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.go

📄 CodeRabbit inference engine (AGENTS.md)

**/*.go: Backend entrypoint in cmd/ orchestrates the scheduler and CLI; runtime, persistence, and service layers sit under internal/* (for example internal/runtime, internal/persistence)
Keep Go files gofmt/goimports clean; use tabs, PascalCase for exported symbols (SchedulerClient), lowerCamelCase for locals, and Err... names for package-level errors
Repository linting relies on golangci-lint; prefer idiomatic Go patterns, minimal global state, and structured logging helpers in internal/common

Files:

  • internal/runtime/executor/dag_runner.go
🧬 Code graph analysis (1)
internal/runtime/executor/dag_runner.go (1)
internal/core/dag.go (1)
  • DAG (33-135)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Test on ubuntu-latest
  • GitHub Check: Build
🔇 Additional comments (1)
internal/runtime/executor/dag_runner.go (1)

66-66: LGTM: Local DAGs propagation for multi-level nesting.

Passing rCtx.DAG.LocalDAGs to createTempDAGFile correctly enables consolidation of all local DAG definitions into the temporary file, which is essential for multi-level nested DAG support.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
internal/integration/subdag_test.go (1)

427-452: Unused params declaration in worker-dag.

The worker-dag declares TASK_ID and TASK_NAME params (lines 438-439) but they're never passed when calling it (line 433). This appears to be leftover from copying the ParallelExecution test. Consider removing the unused params for clarity.

🔎 Proposed fix
 	t.Run("PartialSuccessSubDAG", func(t *testing.T) {
 		th := test.Setup(t)

 		testDAG := th.DAG(t, `
 steps:
   - name: parallel-tasks
     call: worker-dag
 ---

 name: worker-dag
-params:
-  - TASK_ID
-  - TASK_NAME
 steps:
   - command: exit 1
     continueOn:
       failure: true

   - exit 0
 `)
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8cdb188 and 77658e8.

📒 Files selected for processing (2)
  • internal/integration/localdag_test.go (0 hunks)
  • internal/integration/subdag_test.go (3 hunks)
💤 Files with no reviewable changes (1)
  • internal/integration/localdag_test.go
🧰 Additional context used
📓 Path-based instructions (2)
**/*.go

📄 CodeRabbit inference engine (AGENTS.md)

**/*.go: Backend entrypoint in cmd/ orchestrates the scheduler and CLI; runtime, persistence, and service layers sit under internal/* (for example internal/runtime, internal/persistence)
Keep Go files gofmt/goimports clean; use tabs, PascalCase for exported symbols (SchedulerClient), lowerCamelCase for locals, and Err... names for package-level errors
Repository linting relies on golangci-lint; prefer idiomatic Go patterns, minimal global state, and structured logging helpers in internal/common

Files:

  • internal/integration/subdag_test.go
**/*_test.go

📄 CodeRabbit inference engine (AGENTS.md)

**/*_test.go: Co-locate Go tests as *_test.go; favour table-driven cases and cover failure paths
Use stretchr/testify/require and shared fixtures from internal/test instead of duplicating mocks

Files:

  • internal/integration/subdag_test.go
🧠 Learnings (2)
📚 Learning: 2025-12-04T10:34:17.062Z
Learnt from: CR
Repo: dagu-org/dagu PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-04T10:34:17.062Z
Learning: Applies to **/*_test.go : Co-locate Go tests as `*_test.go`; favour table-driven cases and cover failure paths

Applied to files:

  • internal/integration/subdag_test.go
📚 Learning: 2025-12-04T10:34:17.062Z
Learnt from: CR
Repo: dagu-org/dagu PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-04T10:34:17.062Z
Learning: `make test` (or `make test-coverage`) executes the Go suite via `gotestsum`; append `TEST_TARGET=./internal/...` to focus packages

Applied to files:

  • internal/integration/subdag_test.go
🧬 Code graph analysis (1)
internal/integration/subdag_test.go (4)
internal/test/helper.go (1)
  • Setup (81-225)
internal/core/status.go (6)
  • Succeeded (11-11)
  • NodeSucceeded (57-57)
  • NodeSkipped (58-58)
  • Failed (9-9)
  • NodeFailed (55-55)
  • PartiallySucceeded (13-13)
internal/core/step.go (1)
  • Step (13-77)
internal/core/execution/dagrun.go (3)
  • NewDAGRunRef (168-173)
  • DAGRunStore (23-48)
  • DAGRunAttempt (131-153)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Test on ubuntu-latest
  • GitHub Check: Build
🔇 Additional comments (3)
internal/integration/subdag_test.go (3)

17-453: Good test coverage with well-structured subtests.

The TestInlineSubDAG function provides comprehensive coverage for multi-level nested local DAGs including:

  • Simple execution with output capture
  • 2-level and 3-level nesting scenarios
  • Parallel execution
  • Conditional/precondition-based execution
  • Output passing between DAGs
  • Failure path for non-existent references
  • Partial success scenarios

The use of t.Run subtests and require assertions aligns well with the coding guidelines.


455-678: Thorough external sub-DAG and retry tests.

The TestExternalSubDAG tests provide good coverage for:

  • Basic output capture from external sub-DAGs
  • Retry functionality across nested sub-DAG hierarchies with status manipulation
  • Retry policy with output capture verification

The retry simulation using counter files and manual status updates effectively tests the retry flow.


681-769: LGTM!

The TestRetryPolicy tests properly cover both the retry success path with output capture and the baseline case without retry. Good use of counter files for simulating retry behavior.

@ghansham

Copy link
Copy Markdown

We need to take care of those cases where the multiple dags defined within a single dag does not contain a dag whose name matches with another dag yaml or a dag defined in another file containing multiple dags. Orvis it that these multiple subdags defined within a single subdag are qualified by the dagyaml name to avoid name conflict (like namespaces in c++ and packages in java). If it is not so, either we should do that or we should a avoid multiple dag definitions within a single dag . I hope there is no confusion.

@yohamta0 yohamta0 merged commit f04786c into main Dec 20, 2025
6 checks passed
@yohamta0 yohamta0 deleted the multi-nest-dag branch December 20, 2025 11:56
@codecov

codecov Bot commented Dec 20, 2025

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 37.50000% with 10 lines in your changes missing coverage. Please review.
✅ Project coverage is 59.91%. Comparing base (eef457b) to head (5b9bc2c).
⚠️ Report is 2 commits behind head on main.

Files with missing lines Patch % Lines
internal/runtime/executor/dag_runner.go 37.50% 8 Missing and 2 partials ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #1493      +/-   ##
==========================================
- Coverage   59.93%   59.91%   -0.02%     
==========================================
  Files         196      196              
  Lines       22001    22013      +12     
==========================================
+ Hits        13187    13190       +3     
- Misses       7402     7408       +6     
- Partials     1412     1415       +3     
Files with missing lines Coverage Δ
internal/runtime/executor/dag_runner.go 33.12% <37.50%> (+<0.01%) ⬆️

... and 2 files with indirect coverage changes


Continue to review full report in Codecov by Sentry.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 887355f...5b9bc2c. Read the comment docs.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

yohamta0 added a commit that referenced this pull request Dec 20, 2025
* **New Features**
* Optional parentSubDAGRunId query to fetch nested sub-DAG runs; sub-run
lists and modal show root/parent DAG context, status filters with
counts, and improved keyboard navigation.

* **Bug Fixes / UX**
* Consolidated local DAG handling for multi-level execution, improved
error responses for missing runs, and small UI spacing tidy-ups.

* **Tests**
* Reorganized and expanded integration/unit tests for multi-level
sub-DAG scenarios and temp-DAG file handling.
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