feat(spec): allow call alias for child DAG steps (#1335)

This commit is contained in:
YotaHamada 2025-10-21 19:17:03 +09:00 committed by GitHub
parent 7b3fdb7d0f
commit f4ef28c890
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
45 changed files with 974 additions and 831 deletions

View File

@ -788,6 +788,9 @@ type Step struct {
// Args List of arguments to pass to the command
Args *[]string `json:"args,omitempty"`
// Call The name of the DAG to execute as a child DAG-run
Call *string `json:"call,omitempty"`
// CmdWithArgs Complete command string including arguments to execute
CmdWithArgs *string `json:"cmdWithArgs,omitempty"`
@ -833,9 +836,6 @@ type Step struct {
// RepeatPolicy Configuration for step repeat behavior
RepeatPolicy *RepeatPolicy `json:"repeatPolicy,omitempty"`
// Run The name of the DAG to run as a child DAG-run
Run *string `json:"run,omitempty"`
// Script Script content if the step executes a script file
Script *string `json:"script,omitempty"`
@ -6616,86 +6616,86 @@ var swaggerSpec = []string{
"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",
}
// GetSwagger returns the content of the embedded swagger specification file

View File

@ -2241,9 +2241,9 @@ components:
description: "List of arguments to pass to the command"
items:
type: string
run:
call:
type: string
description: "The name of the DAG to run as a child DAG-run"
description: "The name of the DAG to execute as a child DAG-run"
params:
type: string
description: "Parameters to pass to the child DAG-run in JSON format"

View File

@ -210,7 +210,7 @@ Capture outputs from nested workflows:
```yaml
# parent.yaml
steps:
- run: etl-workflow
- call: etl-workflow
params: "DATE=${TODAY}"
output: ETL_RESULT
@ -241,7 +241,7 @@ Access outputs from deeply nested workflows:
```yaml
steps:
- run: main-pipeline
- call: main-pipeline
output: PIPELINE
- |
@ -256,7 +256,7 @@ When running parallel executions, outputs are aggregated:
```yaml
steps:
- run: region-processor
- call: region-processor
parallel:
items: ["us-east", "us-west", "eu-central"]
output: RESULTS

View File

@ -88,7 +88,7 @@ Use `workerSelector` in your DAG definitions to route tasks:
```yaml
steps:
# This task requires GPU
- run: train-model
- call: train-model
workerSelector:
gpu: "true"

View File

@ -10,7 +10,7 @@ Execute the same workflow with different parameters in parallel.
```yaml
steps:
- run: file-processor
- call: file-processor
parallel:
items:
- "file1.csv"
@ -30,7 +30,7 @@ steps:
```yaml
steps:
- run: file-processor
- call: file-processor
parallel:
items: ${FILE_LIST}
maxConcurrent: 2 # Process max 2 files at a time
@ -44,7 +44,7 @@ steps:
- command: find /data -name "*.csv" -type f
output: CSV_FILES
- run: file-processor
- call: file-processor
parallel: ${CSV_FILES}
params: "FILE=${ITEM}"
```
@ -53,7 +53,7 @@ steps:
```yaml
steps:
- run: task-processor
- call: task-processor
parallel:
items: [1, 2, 3]
output: RESULTS

View File

@ -122,9 +122,9 @@ otel:
steps:
- echo "Starting parent workflow"
- run: child-etl.yaml
- call: child-etl.yaml
params: "SOURCE=production DATE=2024-01-01"
- run: child-analytics.yaml
- call: child-analytics.yaml
params: "INPUT=${run-etl.output}"
- echo "Parent workflow complete"

View File

@ -96,7 +96,7 @@ When a step executes another DAG, the trace context is automatically propagated:
```yaml
steps:
- run: workflows/child-workflow.yaml
- call: workflows/child-workflow.yaml
params: "PARAM1=value1"
```

View File

@ -52,7 +52,7 @@ Specify `workerSelector` on any step to route it to workers with matching labels
```yaml
steps:
# This task will only run on workers with gpu=true label
- run: train
- call: train
---
name: train
@ -81,8 +81,8 @@ dagu worker --worker.labels cpu=true
# DAG
steps:
- run: gpu-task
- run: cpu-task
- call: gpu-task
- call: cpu-task
---
# Run on a worker with gpu

View File

@ -162,7 +162,7 @@ steps:
- command: python extract.py --date=${DATE}
output: RAW_DATA
- run: transform-data
- call: transform-data
parallel:
items: [customers, orders, products]
params: "TYPE=${ITEM} INPUT=${RAW_DATA}"

View File

@ -202,7 +202,7 @@ Access outputs from nested workflows:
```yaml
steps:
- run: etl-workflow
- call: etl-workflow
params: "DATE=${TODAY}"
output: ETL_RESULT

View File

@ -379,7 +379,7 @@ Each step in the `steps` array can have these fields:
| `name` | string | Step name (optional - auto-generated if not provided) | Auto-generated |
| `command` | string | Command to execute. Multi-line strings run as inline scripts (honors shebang) | - |
| `script` | string | Inline script (alternative to command). Honors shebang when no shell is set. | - |
| `run` | string | Run another DAG | - |
| `run` (legacy) | string | Deprecated alias for `call` | - |
| `depends` | string/array | Step dependencies | - |
### Step Definition Formats
@ -426,9 +426,10 @@ In the nested array format:
| `stderr` | string | Redirect stderr to file | - |
| `output` | string | Capture output to variable | - |
| `env` | array/object | Step-specific environment variables (overrides DAG-level) | - |
| `call` | string | Name of a DAG to execute as a child DAG-run | - |
| `params` | string/object | Parameters passed to child DAGs (`run`) or executor-specific inputs (e.g., GitHub Actions `with:` map) | - |
When targeting the experimental GitHub Actions executor, author `params` as a YAML map so keys align with the action's `with` inputs. The same field continues to support string-form parameters for child DAG execution.
> The legacy `run` field is still accepted for backward compatibility until v1.24.0, but it will be removed in a future release. Prefer `call` for new workflows.
### Parallel Execution
@ -439,7 +440,7 @@ When targeting the experimental GitHub Actions executor, author `params` as a YA
```yaml
steps:
- run: file-processor
- call: file-processor
parallel:
items: [file1.csv, file2.csv, file3.csv]
maxConcurrent: 2
@ -588,7 +589,7 @@ When using distributed execution, specify `workerSelector` to route tasks to wor
```yaml
steps:
- run: gpu-training
- call: gpu-training
---
# Run on a worker with gpu
name: gpu-training
@ -768,7 +769,7 @@ steps:
limit: 3
intervalSec: 300
- run: transform-module
- call: transform-module
parallel:
items: [customers, orders, products]
maxConcurrent: 2

View File

@ -62,7 +62,7 @@ steps:
type: http
config:
url: https://api.example.com
- run: child-workflow # Auto-named: dag_5
- call: child-workflow # Auto-named: dag_5
```
Auto-generated names follow the pattern `{type}_{number}`:

View File

@ -39,13 +39,12 @@ Run other workflows as steps and compose them hierarchically.
```yaml
steps:
- run: workflows/extract.yaml
- call: workflows/extract.yaml
params: "SOURCE=production"
- run: workflows/transform.yaml
- call: workflows/transform.yaml
params: "INPUT=${extract.output}"
- run: workflows/load.yaml
- call: workflows/load.yaml
params: "DATA=${transform.output}"
```
@ -55,7 +54,7 @@ Define multiple DAGs separated by `---` and call by name.
```yaml
steps:
- run: data-processor
- call: data-processor
params: "TYPE=daily"
---
@ -77,8 +76,7 @@ steps:
- command: |
echo '["file1.csv","file2.csv","file3.csv"]'
output: TASK_LIST
- run: worker
- call: worker
parallel:
items: ${TASK_LIST}
maxConcurrent: 1
@ -102,8 +100,7 @@ steps:
- command: |
echo '["chunk1","chunk2","chunk3"]'
output: CHUNKS
- run: worker
- call: worker
parallel:
items: ${CHUNKS}
maxConcurrent: 3

View File

@ -209,7 +209,7 @@ Examples:
```yaml
steps:
- run: sub_workflow
- call: sub_workflow
output: SUB_RESULT
- echo "The result is ${SUB_RESULT.outputs.finalValue}"
```
@ -270,7 +270,7 @@ The result of the sub workflow will be available from the standard output of the
```yaml
steps:
- run: sub_workflow
- call: sub_workflow
params: "FOO=BAR"
output: SUB_RESULT
- echo $SUB_RESULT

View File

@ -66,7 +66,7 @@ graph TD
```yaml
steps:
- run: processor
- call: processor
parallel:
items: [A, B, C]
maxConcurrent: 2
@ -412,9 +412,9 @@ stateDiagram-v2
```yaml
steps:
- run: etl.yaml
- call: etl.yaml
params: "ENV=prod DATE=today"
- run: analyze.yaml
- call: analyze.yaml
```
```mermaid
@ -453,7 +453,7 @@ graph TD
```yaml
steps:
- run: data-processor
- call: data-processor
params: "TYPE=daily"
---
@ -468,7 +468,7 @@ steps:
```mermaid
graph TD
M[Main] --> DP{{run: data-processor}}
M[Main] --> DP{{call: data-processor}}
subgraph data-processor
E["Extract TYPE data"] --> T[Transform]
end
@ -490,8 +490,8 @@ graph TD
```yaml
steps:
- python prepare_dataset.py
- run: train-model
- run: evaluate-model
- call: train-model
- call: evaluate-model
---
name: train-model
@ -537,7 +537,7 @@ steps:
- wget https://data.example.com/dataset.tar.gz
# Must run on specific worker type
- run: process-on-gpu
- call: process-on-gpu
# Runs locally (no selector)
- echo "Processing complete"
@ -563,7 +563,7 @@ steps:
steps:
- command: python split_data.py --chunks=10
output: CHUNKS
- run: chunk-processor
- call: chunk-processor
parallel:
items: ${CHUNKS}
maxConcurrent: 5
@ -881,7 +881,7 @@ steps:
```yaml
steps:
- run: worker
- call: worker
parallel:
items: [east, west, eu]
params: "REGION=${ITEM}"
@ -979,7 +979,7 @@ steps:
```yaml
steps:
- run: sub_workflow
- call: sub_workflow
output: SUB_RESULT
- echo "Result: ${SUB_RESULT.outputs.finalValue}"
```
@ -1761,7 +1761,7 @@ otel:
steps:
- echo "Fetching data"
- python process.py
- run: pipelines/transform
- call: pipelines/transform
```
Enable OpenTelemetry tracing for observability.

View File

@ -116,7 +116,7 @@ steps:
```yaml
steps:
- parallel: [file1, file2, file3]
run: process-file
call: process-file
params: "FILE=${ITEM}"
---

View File

@ -32,7 +32,7 @@ steps:
- command: ./load.sh
```
Each handler is a normal step definition. You can use `command`, `script`, `run`, `executor`, containers, timeouts, or any other step field that makes sense for a single task.
Each handler is a normal step definition. You can use `command`, `script`, `call` (or the legacy `run`), `executor`, containers, timeouts, or any other step field that makes sense for a single task.
## Execution Model
@ -67,7 +67,7 @@ handlerOn:
```yaml
handlerOn:
success:
run: sync-reporting
call: sync-reporting
params: |
parent_run_id: ${DAG_RUN_ID}
```

View File

@ -105,7 +105,9 @@ type DAG struct {
// OTel contains the OpenTelemetry configuration for the DAG.
OTel *OTelConfig `json:"otel,omitempty"`
// BuildErrors contains any errors encountered while building the DAG.
BuildErrors []error
BuildErrors []error `json:"-"`
// BuildWarnings contains non-fatal warnings detected while building the DAG.
BuildWarnings []string `json:"-"`
// LocalDAGs contains DAGs defined in the same file, keyed by DAG name
LocalDAGs map[string]*DAG `json:"localDAGs,omitempty"`
// YamlData contains the raw YAML data of the DAG.

View File

@ -27,7 +27,7 @@ func TestBuildParallel(t *testing.T) {
yaml: `
steps:
- name: process
run: workflows/processor
call: workflows/processor
parallel: ${ITEMS}
`,
wantItems: 0,
@ -39,7 +39,7 @@ steps:
yaml: `
steps:
- name: process
run: workflows/processor
call: workflows/processor
parallel:
- item1
- item2
@ -54,7 +54,7 @@ steps:
yaml: `
steps:
- name: process
run: workflows/processor
call: workflows/processor
parallel:
- SOURCE: s3://customers
- SOURCE: s3://products
@ -68,7 +68,7 @@ steps:
yaml: `
steps:
- name: process
run: workflows/processor
call: workflows/processor
parallel:
maxConcurrent: 2
items:
@ -84,7 +84,7 @@ steps:
yaml: `
steps:
- name: process
run: workflows/processor
call: workflows/processor
parallel:
items: ${ITEMS}
maxConcurrent: 5
@ -98,7 +98,7 @@ steps:
yaml: `
steps:
- name: process
run: workflows/processor
call: workflows/processor
parallel:
items:
- item1
@ -134,7 +134,7 @@ steps:
yaml: `
steps:
- name: process
run: workflows/processor
call: workflows/processor
parallel:
items: [1, 2, 3]
maxConcurrent: 0
@ -147,7 +147,7 @@ steps:
yaml: `
steps:
- name: process
run: workflows/processor
call: workflows/processor
parallel:
items: []
maxConcurrent: 5
@ -208,7 +208,7 @@ func TestParallelWithChildDAG(t *testing.T) {
yaml := `
steps:
- name: process-regions
run: workflows/deploy
call: workflows/deploy
parallel:
- REGION: us-east-1
- REGION: eu-west-1
@ -254,7 +254,7 @@ env:
- REGIONS: '["us-east-1", "eu-west-1"]'
steps:
- name: deploy-all
run: workflows/deploy
call: workflows/deploy
parallel: ${REGIONS}
`,
},
@ -267,7 +267,7 @@ steps:
output: ITEMS
- name: process-all
run: workflows/processor
call: workflows/processor
parallel: ${ITEMS}
depends: get-items
`,

View File

@ -11,6 +11,7 @@ import (
"time"
"github.com/dagu-org/dagu/internal/common/collections"
"github.com/dagu-org/dagu/internal/common/logger"
"github.com/dagu-org/dagu/internal/common/signal"
"github.com/dagu-org/dagu/internal/core"
"github.com/go-viper/mapstructure/v2"
@ -1433,9 +1434,6 @@ func buildStepEnvs(ctx StepBuildContext, def stepDef, step *core.Step) error {
return nil
}
// maxStepNameLen is the maximum length of a step name.
const maxStepNameLen = 40
func buildStepPrecondition(ctx StepBuildContext, def stepDef, step *core.Step) error {
// Parse both `preconditions` and `precondition` fields.
conditions, err := parsePrecondition(ctx.BuildContext, def.Preconditions)
@ -1465,7 +1463,18 @@ func buildSignalOnStop(_ StepBuildContext, def stepDef, step *core.Step) error {
// buildChildDAG parses the child core.DAG definition and sets up the step to run a child DAG.
func buildChildDAG(ctx StepBuildContext, def stepDef, step *core.Step) error {
name := strings.TrimSpace(def.Run)
name := strings.TrimSpace(def.Call)
if name == "" {
// TODO: remove legacy support in future major version
if legacyName := strings.TrimSpace(def.Run); legacyName != "" {
name = legacyName
message := "Step field `run` is deprecated; use `call` instead"
logger.Warn(ctx.ctx, message)
if ctx.dag != nil {
ctx.dag.BuildWarnings = append(ctx.dag.BuildWarnings, message)
}
}
}
// if the run field is not set, return nil.
if name == "" {
@ -1500,9 +1509,9 @@ func buildChildDAG(ctx StepBuildContext, def stepDef, step *core.Step) error {
step.ExecutorConfig.Type = core.ExecutorTypeDAG
}
step.Command = "run"
step.Command = "call"
step.Args = []string{name, paramsStr}
step.CmdWithArgs = fmt.Sprintf("%s %s", name, paramsStr)
step.CmdWithArgs = strings.TrimSpace(fmt.Sprintf("%s %s", name, paramsStr))
return nil
}

View File

@ -1043,7 +1043,7 @@ steps:
data := []byte(`
steps:
- name: execute a sub-dag
run: sub_dag
call: sub_dag
params: "param1=value1 param2=value2"
`)
dag, err := spec.LoadYAML(context.Background(), data)
@ -1051,11 +1051,30 @@ steps:
th := DAG{t: t, DAG: dag}
assert.Len(t, th.Steps, 1)
assert.Equal(t, "dag", th.Steps[0].ExecutorConfig.Type)
assert.Equal(t, "run", th.Steps[0].Command)
assert.Equal(t, "call", th.Steps[0].Command)
assert.Equal(t, []string{
"sub_dag",
"param1=\"value1\" param2=\"value2\"",
}, th.Steps[0].Args)
assert.Equal(t, "sub_dag param1=\"value1\" param2=\"value2\"", th.Steps[0].CmdWithArgs)
assert.Empty(t, dag.BuildWarnings)
// Legacy run field is still accepted
dataLegacy := []byte(`
steps:
- name: legacy sub-dag
run: sub_dag_legacy
`)
dagLegacy, err := spec.LoadYAML(context.Background(), dataLegacy)
require.NoError(t, err)
thLegacy := DAG{t: t, DAG: dagLegacy}
assert.Len(t, thLegacy.Steps, 1)
assert.Equal(t, "dag", thLegacy.Steps[0].ExecutorConfig.Type)
assert.Equal(t, "call", thLegacy.Steps[0].Command)
assert.Equal(t, []string{"sub_dag_legacy", ""}, thLegacy.Steps[0].Args)
assert.Equal(t, "sub_dag_legacy", thLegacy.Steps[0].CmdWithArgs)
require.Len(t, dagLegacy.BuildWarnings, 1)
assert.Contains(t, dagLegacy.BuildWarnings[0], "Step field `run` is deprecated")
})
t.Run("ContinueOn", func(t *testing.T) {
t.Parallel()
@ -2015,7 +2034,7 @@ steps:
type: http
config:
url: https://example.com
- run: child-dag
- call: child-dag
- executor:
type: docker
config:

View File

@ -149,7 +149,10 @@ type stepDef struct {
// When it is empty, the same signal as the parent process is sent.
// It can be KILL when the process does not stop over the timeout.
SignalOnStop *string `yaml:"signalOnStop,omitempty"`
// Call is the name of a DAG to run as a child dag-run.
Call string `yaml:"call,omitempty"`
// Run is the name of a DAG to run as a child dag-run.
// Deprecated: use Call instead.
Run string `yaml:"run,omitempty"`
// Params specifies the parameters for the child dag-run.
Params any `yaml:"params,omitempty"`

View File

@ -253,9 +253,9 @@ func TestMultiDAGFile(t *testing.T) {
// Create a temporary multi-DAG YAML file
multiDAGContent := `steps:
- name: process
run: transform-data
call: transform-data
- name: archive
run: archive-results
call: archive-results
---
name: transform-data
@ -471,10 +471,10 @@ steps:
schedule: "0 2 * * *"
steps:
- name: extract
run: extract-module
call: extract-module
params: "SOURCE=customers TABLE=users"
- name: transform
run: transform-module
call: transform-module
---
name: extract-module

View File

@ -21,7 +21,7 @@ func TestRetryChildDAGRun(t *testing.T) {
th.CreateDAGFile(t, "parent.yaml", `
steps:
- name: parent
run: child_1
call: child_1
params: "PARAM=FOO"
`)
@ -29,7 +29,7 @@ steps:
params: "PARAM=BAR"
steps:
- name: child_2
run: child_2
call: child_2
params: "PARAM=$PARAM"
`)
@ -131,7 +131,7 @@ func TestRetryPolicyChildDAGRunWithOutputCapture(t *testing.T) {
th.CreateDAGFile(t, "parent_retry.yaml", `
steps:
- name: call_child
run: child_retry
call: child_retry
output: CHILD_OUTPUT
`)
@ -204,7 +204,7 @@ func TestBasicChildDAGOutputCapture(t *testing.T) {
th.CreateDAGFile(t, "parent_basic.yaml", `
steps:
- name: call_child
run: child_basic
call: child_basic
output: CHILD_OUTPUT
`)

View File

@ -518,7 +518,7 @@ func TestCallSubDAG(t *testing.T) {
// Use multi-document YAML to include both parent and sub DAG
dagContent := `steps:
- run: sub
- call: sub
params: "SUB_P1=foo"
output: OUT1
- command: echo "${OUT1.outputs.OUT}"
@ -540,6 +540,26 @@ steps:
})
}
func TestLegacyRunSubDAG(t *testing.T) {
th := test.Setup(t)
dagContent := `steps:
- run: legacy-sub
output: OUT
---
name: legacy-sub
steps:
- command: echo "legacy works"
output: LEGACY_OUT
`
dag := th.DAG(t, dagContent)
agent := dag.Agent()
agent.RunSuccess(t)
dag.AssertOutputs(t, map[string]any{
"OUT": []test.Contains{"legacy works"},
})
}
func TestNestedThreeLevelDAG(t *testing.T) {
th := test.Setup(t)
@ -554,7 +574,7 @@ steps:
// Create parent and child DAGs using multi-document YAML
dagContent := `steps:
- run: nested_child
- call: nested_child
params: "PARAM=123"
output: CHILD_OUTPUT
- command: echo ${CHILD_OUTPUT.outputs.OUTPUT}
@ -564,7 +584,7 @@ name: nested_child
params:
PARAM: VALUE
steps:
- run: nested_grand_child
- call: nested_grand_child
params: "PARAM=${PARAM}"
output: GRAND_CHILD_OUTPUT
- command: echo ${GRAND_CHILD_OUTPUT.outputs.OUTPUT}

View File

@ -18,7 +18,7 @@ func TestDistributedLocalDAGExecution(t *testing.T) {
yamlContent := `
steps:
- name: run-local-on-worker
run: local-child
call: local-child
output: RESULT
---
@ -79,7 +79,7 @@ steps:
yamlContent := `
steps:
- name: run-on-nonexistent-worker
run: local-child
call: local-child
output: RESULT
---
@ -114,7 +114,7 @@ steps:
yamlContent := `
steps:
- name: run-local-on-worker
run: local-child
call: local-child
output: RESULT
---

View File

@ -21,7 +21,7 @@ func TestParallelDistributedExecution(t *testing.T) {
yamlContent := `
steps:
- name: process-items
run: child-worker
call: child-worker
parallel:
items:
- "item1"
@ -130,7 +130,7 @@ steps:
yamlContent := `
steps:
- name: process-regions
run: child-regional
call: child-regional
parallel:
items:
- "us-east"
@ -219,7 +219,7 @@ steps:
yamlContent := `
steps:
- name: process-items
run: child-nonexistent
call: child-nonexistent
parallel:
items: ["a", "b", "c"]
output: RESULTS
@ -258,7 +258,7 @@ func TestParallelDistributedCancellation(t *testing.T) {
yamlContent := `
steps:
- name: process-items
run: child-sleep
call: child-sleep
parallel:
items:
- "1"
@ -387,12 +387,12 @@ steps:
yamlContent := `
steps:
- name: local-execution
run: child-local
call: child-local
parallel:
items: ["1", "1"]
output: LOCAL_RESULTS
- name: distributed-execution
run: child-distributed
call: child-distributed
parallel:
items: ["1", "1"]
output: DISTRIBUTED_RESULTS
@ -506,7 +506,7 @@ steps:
yamlContent := `
steps:
- name: high-concurrency
run: child-task
call: child-task
parallel:
items:
- "task1"

View File

@ -15,7 +15,7 @@ func TestLocalDAGExecution(t *testing.T) {
yamlContent := `
steps:
- name: run-local-child
run: local-child
call: local-child
params: "NAME=World"
output: CHILD_RESULT
@ -66,7 +66,7 @@ steps:
yamlContent := `
steps:
- name: parallel-tasks
run: worker-dag
call: worker-dag
parallel:
items:
- TASK_ID=1 TASK_NAME=alpha
@ -115,7 +115,7 @@ steps:
yamlContent := `
steps:
- name: run-middle-dag
run: middle-dag
call: middle-dag
params: "ROOT_PARAM=FromRoot"
---
@ -128,7 +128,7 @@ steps:
output: MIDDLE_OUTPUT
- name: run-leaf-dag
run: leaf-dag
call: leaf-dag
params: "MIDDLE_PARAM=${MIDDLE_OUTPUT} LEAF_PARAM=FromMiddle"
---
@ -175,13 +175,13 @@ steps:
output: ENV_TYPE
- name: run-prod-dag
run: production-dag
call: production-dag
preconditions:
- condition: "${ENV_TYPE}"
expected: "production"
- name: run-dev-dag
run: development-dag
call: development-dag
preconditions:
- condition: "${ENV_TYPE}"
expected: "development"
@ -234,11 +234,11 @@ steps:
yamlContent := `
steps:
- name: generate-data
run: generator-dag
call: generator-dag
output: GEN_OUTPUT
- name: process-data
run: processor-dag
call: processor-dag
params: "INPUT_DATA=${GEN_OUTPUT.outputs.DATA}"
---
@ -290,7 +290,7 @@ steps:
yamlContent := `
steps:
- name: run-missing-dag
run: non-existent-dag
call: non-existent-dag
---
@ -330,12 +330,12 @@ steps:
output: SETUP_STATUS
- name: task1
run: task-dag
call: task-dag
params: "TASK_NAME=Task1 SETUP=${SETUP_STATUS}"
output: TASK1_RESULT
- name: task2
run: task-dag
call: task-dag
params: "TASK_NAME=Task2 SETUP=${SETUP_STATUS}"
output: TASK2_RESULT
@ -397,7 +397,7 @@ steps:
yamlContent := `
steps:
- name: parallel-tasks
run: worker-dag
call: worker-dag
parallel:
items:
- TASK_ID=1 TASK_NAME=alpha
@ -433,7 +433,7 @@ steps:
yamlContent := `
steps:
- name: parallel-tasks
run: worker-dag
call: worker-dag
---
name: worker-dag

View File

@ -47,7 +47,7 @@ steps:
{
name: "simple items",
dag: `steps:
- run: child-echo
- call: child-echo
parallel:
items:
- "item1"
@ -62,7 +62,7 @@ steps:
{
name: "object items",
dag: `steps:
- run: child-process
- call: child-process
parallel:
items:
- REGION: us-east-1
@ -88,7 +88,7 @@ steps:
dag: `params:
- ITEMS: '["alpha", "beta", "gamma", "delta"]'
steps:
- run: child-echo
- call: child-echo
parallel: ${ITEMS}
` + childEcho,
expectedNodes: 1,
@ -100,7 +100,7 @@ steps:
dag: `env:
- SERVERS: "server1 server2 server3"
steps:
- run: child-echo
- call: child-echo
parallel: ${SERVERS}
` + childEcho,
expectedNodes: 1,
@ -112,7 +112,7 @@ steps:
dag: `env:
- ITEMS: '["task1", "task2", "task3"]'
steps:
- run: child-with-output
- call: child-with-output
parallel: $ITEMS
- name: aggregate-results
command: echo "Completed parallel tasks"
@ -167,7 +167,7 @@ steps:
func TestParallelExecution_WithOutput(t *testing.T) {
const dagContent = `steps:
- run: child-with-output
- call: child-with-output
parallel:
items:
- "A"
@ -236,7 +236,7 @@ steps:
func TestParallelExecution_DeterministicIDs(t *testing.T) {
const dagContent = `steps:
- run: child-echo
- call: child-echo
parallel:
items:
- "test1"
@ -282,7 +282,7 @@ steps:
func TestParallelExecution_PartialFailure(t *testing.T) {
const dagContent = `steps:
- run: child-conditional-fail
- call: child-conditional-fail
parallel:
items:
- "ok1"
@ -321,7 +321,7 @@ steps:
func TestParallelExecution_OutputCaptureWithFailures(t *testing.T) {
const dagContent = `steps:
- run: child-output-fail
- call: child-output-fail
parallel:
items:
- "success"
@ -376,7 +376,7 @@ func TestParallelExecution_OutputCaptureWithRetry(t *testing.T) {
th := test.Setup(t)
dag := th.DAG(t, fmt.Sprintf(`steps:
- run: child-retry-simple
- call: child-retry-simple
parallel:
items:
- "item1"
@ -431,7 +431,7 @@ steps:
func TestParallelExecution_MinimalRetry(t *testing.T) {
const dagContent = `steps:
- run: child-fail
- call: child-fail
parallel:
items:
- "item1"
@ -462,7 +462,7 @@ steps:
func TestParallelExecution_RetryAndContinueOn(t *testing.T) {
const dagContent = `steps:
- run: child-fail-both
- call: child-fail-both
parallel:
items:
- "item1"
@ -506,7 +506,7 @@ steps:
func TestParallelExecution_OutputsArray(t *testing.T) {
const dagContent = `steps:
- run: child-with-output
- call: child-with-output
parallel:
items: ["task1", "task2", "task3"]
output: RESULTS
@ -573,7 +573,7 @@ func TestParallelExecution_ExceedsMaxLimit(t *testing.T) {
th := test.Setup(t)
dag := th.DAG(t, fmt.Sprintf(`steps:
- run: child-echo
- call: child-echo
parallel:
items:
%s
@ -604,7 +604,7 @@ func TestParallelExecution_ExactlyMaxLimit(t *testing.T) {
}
dag := helper.DAG(t, fmt.Sprintf(`steps:
- run: child-echo
- call: child-echo
parallel:
items:
%s
@ -648,7 +648,7 @@ func TestParallelExecution_ObjectItemProperties(t *testing.T) {
]'
output: CONFIGS
- run: sync-data
- call: sync-data
parallel:
items: ${CONFIGS}
maxConcurrent: 2
@ -737,7 +737,7 @@ steps:
- command: find %s -name "*.csv" -type f
output: FILES
- run: process-file
- call: process-file
parallel: ${FILES}
params:
- ITEM: ${ITEM}
@ -784,7 +784,7 @@ steps:
func TestParallelExecution_StaticObjectItems(t *testing.T) {
const dagContent = `steps:
- run: deploy-service
- call: deploy-service
parallel:
maxConcurrent: 3
items:
@ -871,7 +871,7 @@ func TestIssue1274_ParallelJSONSingleItem(t *testing.T) {
echo '{"file": "params.txt", "config": "env"}'
output: jsonList
- run: issue-1274-worker
- call: issue-1274-worker
parallel:
items: ${jsonList}
maxConcurrent: 1
@ -917,7 +917,7 @@ func TestIssue1274_ParallelJSONMultipleItems(t *testing.T) {
echo '{"file": "file3.txt", "config": "dev"}'
output: jsonList
- run: issue-1274-worker-multi
- call: issue-1274-worker-multi
parallel:
items: ${jsonList}
maxConcurrent: 1

View File

@ -8,11 +8,9 @@ var exampleDAGs = map[string]string{
description: Execute steps one after another
steps:
- command: echo "Step 1 - Starting workflow"
- command: echo "Step 2 - Processing data"
- command: echo "Step 3 - Workflow complete"
- echo "Step 1 - Starting workflow"
- echo "Step 2 - Processing data"
- echo "Step 3 - Workflow complete"
`,
"example-02-parallel-execution.yaml": `# Parallel Execution
@ -74,11 +72,11 @@ description: Example of a scheduled workflow
histRetentionDays: 7 # Keep 7 days of history
steps:
- command: |
- |
#!env sh
echo "Running scheduled task"
echo "Current time: $(date)"
- command: echo "Cleaning up old data"
- echo "Cleaning up old data"
`,
"example-04-nested-workflows.yaml": `# Nested Workflows
@ -87,12 +85,10 @@ steps:
description: Example of nested workflows
steps:
- command: echo "Preparing data for sub-workflows"
- run: sub-workflow
- echo "Preparing data for sub-workflows"
- call: sub-workflow
params: "TASK_ID=123"
- command: echo "Main workflow completed"
- echo "Main workflow completed"
---
# Sub-workflow definition
@ -102,9 +98,8 @@ params:
- TASK_ID: "000"
steps:
- command: echo "Sub-workflow executing with TASK_ID=${TASK_ID}"
- command: echo "Sub-workflow step 2"
- echo "Sub-workflow executing with TASK_ID=${TASK_ID}"
- echo "Sub-workflow step 2"
`,
"example-05-container-workflow.yaml": `# Container-based Workflow
@ -118,12 +113,12 @@ container:
- /tmp/data:/data
steps:
- # write data to a file
command: |
python -c "with open('/data/output.txt', 'w') as f: f.write('Hello from Dagu!')"
# write data to a file
- |
python -c "with open('/data/output.txt', 'w') as f: f.write('Hello from Dagu!')"
- # read data from the file
command: |
python -c "with open('/data/output.txt') as f: print(f.read())"
# read data from the file
- |
python -c "with open('/data/output.txt') as f: print(f.read())"
`,
}

View File

@ -103,7 +103,7 @@ func TestManager(t *testing.T) {
dag := th.DAG(t, `
steps:
- name: "1"
run: tree_child
call: tree_child
---
name: tree_child
steps:

View File

@ -172,11 +172,11 @@ func (a *API) GetDAGSpec(ctx context.Context, request api.GetDAGSpecRequestObjec
if err != nil {
errs = append(errs, err.Error())
}
} else if len(dag.BuildErrors) > 0 {
// Extract build errors from the DAG
} else {
for _, buildErr := range dag.BuildErrors {
errs = append(errs, buildErr.Error())
}
errs = append(errs, dag.BuildWarnings...)
}
return &api.GetDAGSpec200JSONResponse{

View File

@ -63,7 +63,7 @@ func toStep(obj core.Step) api.Step {
}
if obj.ChildDAG != nil {
step.Run = ptrOf(obj.ChildDAG.Name)
step.Call = ptrOf(obj.ChildDAG.Name)
step.Params = ptrOf(obj.ChildDAG.Params)
}
if obj.Parallel != nil {

View File

@ -770,9 +770,13 @@
"type": "string",
"description": "Signal to send when stopping this step (e.g., SIGINT). If empty, uses same signal as parent process."
},
"call": {
"type": "string",
"description": "Name of a DAG to execute as a child dag-run."
},
"run": {
"type": "string",
"description": "Name of a DAG to run."
"description": "Legacy field for DAG execution. Deprecated: use call instead."
},
"params": {
"oneOf": [

View File

@ -1010,8 +1010,8 @@ export interface components {
output?: string;
/** @description List of arguments to pass to the command */
args?: string[];
/** @description The name of the DAG to run as a child DAG-run */
run?: string;
/** @description The name of the DAG to execute as a child DAG-run */
call?: string;
/** @description Parameters to pass to the child DAG-run in JSON format */
params?: string;
/** @description Configuration for parallel execution of the step */

View File

@ -7,7 +7,11 @@ import { useClient } from '../../../hooks/api';
import { DAGContext } from '../contexts/DAGContext';
import { getEventHandlers } from '../lib/getEventHandlers';
import { DAGStatusOverview, NodeStatusTable } from './dag-details';
import { LogViewer, ParallelExecutionModal, StatusUpdateModal } from './dag-execution';
import {
LogViewer,
ParallelExecutionModal,
StatusUpdateModal,
} from './dag-execution';
import { DAGGraph } from './visualization';
type Props = {
@ -96,10 +100,14 @@ function DAGStatus({ dagRun, fileName }: Props) {
(n) => n.step.name.replace(/[-\s]/g, 'dagutmp') == id
);
if (n && n.step.run) {
const childDAGName = n?.step?.call;
if (n && childDAGName) {
// Combine both regular children and repeated children
const allChildren = [...(n.children || []), ...(n.childrenRepeated || [])];
const allChildren = [
...(n.children || []),
...(n.childrenRepeated || []),
];
// Check if there are multiple child runs (parallel execution or repeated)
if (allChildren.length > 1) {
// Show modal to select which execution to view
@ -118,20 +126,28 @@ function DAGStatus({ dagRun, fileName }: Props) {
// Helper function to navigate to a specific child DAG run
const navigateToChildDagRun = React.useCallback(
(node: components['schemas']['Node'], childIndex: number, openInNewTab?: boolean) => {
(
node: components['schemas']['Node'],
childIndex: number,
openInNewTab?: boolean
) => {
// Combine both regular children and repeated children
const allChildren = [...(node.children || []), ...(node.childrenRepeated || [])];
const allChildren = [
...(node.children || []),
...(node.childrenRepeated || []),
];
const childDAGRun = allChildren[childIndex];
if (childDAGRun && childDAGRun.dagRunId) {
// Navigate to the child DAG-run status page
const dagRunId = dagRun.rootDAGRunId || dagRun.dagRunId;
// Check if we're in a dagRun context or a DAG context
const currentPath = window.location.pathname;
const isModal = document.querySelector('.dagRun-modal-content') !== null;
const isModal =
document.querySelector('.dagRun-modal-content') !== null;
const isDAGRunContext = currentPath.startsWith('/dag-runs/') || isModal;
let url: string;
if (isDAGRunContext) {
// For DAG runs, use query parameters to navigate to the DAG-run details page
@ -148,7 +164,7 @@ function DAGStatus({ dagRun, fileName }: Props) {
}
searchParams.set('step', node.step.name);
// Determine root DAG name
const rootDAGName = dagRun.rootDAGRunName || dagRun.name;
url = `/dag-runs/${rootDAGName}/${dagRunId}?${searchParams.toString()}`;
@ -197,7 +213,11 @@ function DAGStatus({ dagRun, fileName }: Props) {
const handlers = getEventHandlers(dagRun);
// Handler for opening log viewer
const handleViewLog = (stepName: string, dagRunId: string, node?: components['schemas']['Node']) => {
const handleViewLog = (
stepName: string,
dagRunId: string,
node?: components['schemas']['Node']
) => {
// Check if this is a stderr log (indicated by _stderr suffix)
const isStderr = stepName.endsWith('_stderr');
const actualStepName = isStderr ? stepName.slice(0, -7) : stepName; // Remove '_stderr' suffix
@ -218,9 +238,7 @@ function DAGStatus({ dagRun, fileName }: Props) {
{dagRun.nodes && dagRun.nodes.length > 0 && (
<div className="bg-card rounded-2xl border border-border shadow-sm hover:shadow-md transition-shadow duration-200 overflow-hidden">
<div className="border-b border-border bg-muted/30 px-6 py-4">
<h2 className="text-lg font-semibold text-foreground">
Graph
</h2>
<h2 className="text-lg font-semibold text-foreground">Graph</h2>
</div>
<div className="p-6">
<DAGGraph
@ -377,13 +395,17 @@ function DAGStatus({ dagRun, fileName }: Props) {
isOpen={parallelExecutionModal.isOpen}
onClose={() => setParallelExecutionModal({ isOpen: false })}
stepName={parallelExecutionModal.node.step.name}
childDAGName={parallelExecutionModal.node.step.run || ''}
childDAGName={parallelExecutionModal.node.step.call || ''}
children={[
...(parallelExecutionModal.node.children || []),
...(parallelExecutionModal.node.childrenRepeated || [])
...(parallelExecutionModal.node.childrenRepeated || []),
]}
onSelectChild={(childIndex, openInNewTab) => {
navigateToChildDagRun(parallelExecutionModal.node!, childIndex, openInNewTab);
navigateToChildDagRun(
parallelExecutionModal.node!,
childIndex,
openInNewTab
);
if (!openInNewTab) {
setParallelExecutionModal({ isOpen: false });
}

View File

@ -1,14 +1,10 @@
import { Button } from '@/components/ui/button';
import {
Dialog,
DialogTrigger,
} from '@/components/ui/dialog';
import { Plus } from 'lucide-react';
import { useContext, useState } from 'react';
import { DAGNameInputModal } from '../../../../components/DAGNameInputModal';
import { AppBarContext } from '../../../../contexts/AppBarContext';
import { useConfig } from '../../../../contexts/ConfigContext';
import { useClient } from '../../../../hooks/api';
import { DAGNameInputModal } from '../../../../components/DAGNameInputModal';
/**
* CreateDAGModal displays a button that opens a modal to create a new DAG
@ -75,7 +71,7 @@ function CreateDAGModal() {
<Plus className="w-3.5 h-3.5" aria-hidden="true" />
<span>New</span>
</Button>
<DAGNameInputModal
isOpen={isOpen}
onClose={handleClose}

View File

@ -3,12 +3,12 @@
*
* @module features/dags/components/dag-details
*/
import { CommandDisplay } from '@/components/ui/command-display';
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from '@/components/ui/tooltip';
import { CommandDisplay } from '@/components/ui/command-display';
import {
ArrowRight,
FileText,
@ -35,6 +35,7 @@ type Props = {
* DAGStepTableRow displays information about a single step in a DAG
*/
function DAGStepTableRow({ step, index }: Props) {
const childDagName = step.call;
// Format preconditions as a list
const preconditions = step.preconditions?.map((c, index) => (
<div
@ -72,11 +73,11 @@ function DAGStepTableRow({ step, index }: Props) {
{step.description}
</div>
)}
{step.run && (
{childDagName && (
<div className="mt-1 flex w-fit items-center gap-1.5 rounded-md bg-purple-50 px-1.5 py-0.5 text-xs dark:bg-purple-900/20">
<GitBranch className="h-3.5 w-3.5 text-purple-500 dark:text-purple-400" />
<span className="font-medium text-purple-600 dark:text-purple-400">
Sub-DAG: {step.run}
Sub-DAG: {childDagName}
</span>
</div>
)}
@ -159,14 +160,17 @@ function DAGStepTableRow({ step, index }: Props) {
step.repeatPolicy.repeat === 'while'
? 'bg-cyan-50 dark:bg-cyan-900/20 text-cyan-600 dark:text-cyan-400 border-cyan-200 dark:border-cyan-800'
: step.repeatPolicy.repeat === 'until'
? 'bg-purple-50 dark:bg-purple-900/20 text-purple-600 dark:text-purple-400 border-purple-200 dark:border-purple-800'
: 'bg-blue-50 dark:bg-blue-900/20 text-blue-600 dark:text-blue-400 border-blue-200 dark:border-blue-800'
? 'bg-purple-50 dark:bg-purple-900/20 text-purple-600 dark:text-purple-400 border-purple-200 dark:border-purple-800'
: 'bg-blue-50 dark:bg-blue-900/20 text-blue-600 dark:text-blue-400 border-blue-200 dark:border-blue-800'
}`}
>
<RefreshCw className="h-3 w-3" />
<span className="font-medium uppercase tracking-wider text-[10px]">
{step.repeatPolicy.repeat === 'while' ? 'WHILE' :
step.repeatPolicy.repeat === 'until' ? 'UNTIL' : 'REPEAT'}
{step.repeatPolicy.repeat === 'while'
? 'WHILE'
: step.repeatPolicy.repeat === 'until'
? 'UNTIL'
: 'REPEAT'}
</span>
{step.repeatPolicy.interval && (
<span className="text-[10px] opacity-75">
@ -179,19 +183,25 @@ function DAGStepTableRow({ step, index }: Props) {
</span>
)}
</Badge>
{/* Repeat Condition */}
{step.repeatPolicy.condition && (
<div className="text-[10px] bg-slate-100 dark:bg-slate-800 rounded px-1.5 py-0.5 font-mono">
<span className="text-slate-500 dark:text-slate-400">
{step.repeatPolicy.repeat === 'while' ? '↻ while' : '↻ until'}:
{step.repeatPolicy.repeat === 'while'
? '↻ while'
: '↻ until'}
:
</span>{' '}
<span className="text-slate-700 dark:text-slate-300">
{step.repeatPolicy.condition.condition}
</span>
{step.repeatPolicy.condition.expected && (
<>
<span className="text-slate-500 dark:text-slate-400"> = </span>
<span className="text-slate-500 dark:text-slate-400">
{' '}
={' '}
</span>
<span className="text-emerald-600 dark:text-emerald-400">
{step.repeatPolicy.condition.expected}
</span>
@ -199,18 +209,19 @@ function DAGStepTableRow({ step, index }: Props) {
)}
</div>
)}
{/* Exit Codes */}
{step.repeatPolicy.exitCode && step.repeatPolicy.exitCode.length > 0 && (
<div className="text-[10px] bg-slate-100 dark:bg-slate-800 rounded px-1.5 py-0.5">
<span className="text-slate-500 dark:text-slate-400">
exit codes:
</span>{' '}
<span className="font-mono text-amber-600 dark:text-amber-400">
[{step.repeatPolicy.exitCode.join(', ')}]
</span>
</div>
)}
{step.repeatPolicy.exitCode &&
step.repeatPolicy.exitCode.length > 0 && (
<div className="text-[10px] bg-slate-100 dark:bg-slate-800 rounded px-1.5 py-0.5">
<span className="text-slate-500 dark:text-slate-400">
exit codes:
</span>{' '}
<span className="font-mono text-amber-600 dark:text-amber-400">
[{step.repeatPolicy.exitCode.join(', ')}]
</span>
</div>
)}
</div>
)}

View File

@ -277,7 +277,7 @@ function DAGHistoryTable({ fileName, gridData, dagRuns }: HistoryTableProps) {
(n) => n.step.name.replace(/[-\s]/g, 'dagutmp') == id
);
if (!n || !n.step.run) return;
if (!n || !n.step.call) return;
// If it's a child dagRun, navigate to its details
const childDAGRun = n.children?.[0];

View File

@ -145,20 +145,22 @@ const Graph: React.FC<Props> = ({
const id = step.name.replace(/[\s-]/g, 'dagutmp'); // Replace spaces and dashes with 'x'
const c = graphStatusMap[status] || '';
// Check if this is a child dagRun node (has a call property)
const childDAGName = step.call;
// Check if this is a child dagRun node (has a 'run' property)
const isChildDAGRun = !!step.run;
const isChildDAGRun = !!step.call;
const hasParallelExecutions = !!step.parallel;
// Add indicator for child dagRun nodes in the label only
// Escape any special characters in the label to prevent Mermaid parsing errors
let label = step.name;
if (isChildDAGRun && step.run) {
if (isChildDAGRun && childDAGName) {
if (hasParallelExecutions && node?.children) {
// Show parallel execution count in the label - avoid brackets in stadium nodes
label = `${step.name}${step.run} x${node.children.length}`;
label = `${step.name}${childDAGName} x${node.children.length}`;
} else {
// Single child DAG run
label = `${step.name}${step.run}`;
label = `${step.name}${childDAGName}`;
}
}

View File

@ -1,5 +1,5 @@
import { ChevronDown, ChevronUp, Clock, Server } from 'lucide-react';
import React from 'react';
import { Clock, Server, Activity, ChevronDown, ChevronUp } from 'lucide-react';
import { cn } from '../../../lib/utils';
interface ServiceInstance {
@ -18,30 +18,36 @@ interface ServiceCardProps {
error?: string;
}
function ServiceCard({ title, instances, icon, isLoading, error }: ServiceCardProps) {
function ServiceCard({
title,
instances,
icon,
isLoading,
error,
}: ServiceCardProps) {
const [isExpanded, setIsExpanded] = React.useState(false);
// Calculate overall status
const activeCount = instances.filter(i => i.status === 'active').length;
const activeCount = instances.filter((i) => i.status === 'active').length;
const hasActive = activeCount > 0;
const allActive = activeCount === instances.length && instances.length > 0;
const getStatusColor = () => {
if (error) return 'text-red-500';
if (!hasActive) return 'text-yellow-500';
if (allActive) return 'text-green-500';
return 'text-green-500';
};
const getUptime = (startedAt: string): string => {
const start = new Date(startedAt);
const now = new Date();
const diff = now.getTime() - start.getTime();
const days = Math.floor(diff / (1000 * 60 * 60 * 24));
const hours = Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60));
if (days > 0) {
return `${days}d ${hours}h`;
} else if (hours > 0) {
@ -50,7 +56,7 @@ function ServiceCard({ title, instances, icon, isLoading, error }: ServiceCardPr
return `${minutes}m`;
}
};
return (
<div className="border rounded-lg bg-card">
<div className="p-3">
@ -69,34 +75,42 @@ function ServiceCard({ title, instances, icon, isLoading, error }: ServiceCardPr
</button>
)}
</div>
{/* Status Summary */}
<div className="flex items-center gap-3 text-xs">
<div className="flex items-center gap-1.5">
<div className="relative">
<div className={cn(
"w-2 h-2 rounded-full transition-colors",
getStatusColor()
)} />
{hasActive && !error && (
<div className={cn(
"absolute inset-0 rounded-full animate-ping opacity-75",
<div
className={cn(
'w-2 h-2 rounded-full transition-colors',
getStatusColor()
)} />
)}
/>
{hasActive && !error && (
<div
className={cn(
'absolute inset-0 rounded-full animate-ping opacity-75',
getStatusColor()
)}
/>
)}
</div>
<span className={cn("font-medium", getStatusColor())}>
{error ? 'Error' : activeCount > 0 ? `${activeCount} Active` : 'Inactive'}
<span className={cn('font-medium', getStatusColor())}>
{error
? 'Error'
: activeCount > 0
? `${activeCount} Active`
: 'Inactive'}
</span>
</div>
{instances.length > 0 && (
<div className="text-muted-foreground">
{instances.length} instance{instances.length !== 1 ? 's' : ''}
</div>
)}
</div>
{/* Primary Instance Info (always visible) */}
{instances.length > 0 && instances[0] && !error && (
<div className="mt-2 pt-2 border-t space-y-1">
@ -117,18 +131,26 @@ function ServiceCard({ title, instances, icon, isLoading, error }: ServiceCardPr
)}
</div>
)}
{/* Expanded Instance List */}
{isExpanded && instances.length > 1 && (
<div className="mt-2 pt-2 border-t space-y-2">
{instances.slice(1).map((instance) => (
<div key={instance.instanceId} className="pl-3 border-l-2 border-muted">
<div
key={instance.instanceId}
className="pl-3 border-l-2 border-muted"
>
<div className="flex items-center gap-2 text-xs">
<div className={cn(
"w-1.5 h-1.5 rounded-full",
instance.status === 'active' ? 'bg-green-500' :
instance.status === 'inactive' ? 'bg-yellow-500' : 'bg-gray-500'
)} />
<div
className={cn(
'w-1.5 h-1.5 rounded-full',
instance.status === 'active'
? 'bg-green-500'
: instance.status === 'inactive'
? 'bg-yellow-500'
: 'bg-gray-500'
)}
/>
<span className="text-muted-foreground">
{instance.host}
{instance.port ? `:${instance.port}` : ''}
@ -146,23 +168,17 @@ function ServiceCard({ title, instances, icon, isLoading, error }: ServiceCardPr
))}
</div>
)}
{/* Error Message */}
{error && (
<div className="mt-2 text-xs text-red-500">
{error}
</div>
)}
{error && <div className="mt-2 text-xs text-red-500">{error}</div>}
{/* Loading State */}
{isLoading && (
<div className="mt-2 text-xs text-muted-foreground">
Loading...
</div>
<div className="mt-2 text-xs text-muted-foreground">Loading...</div>
)}
</div>
</div>
);
}
export default ServiceCard;
export default ServiceCard;

View File

@ -1,7 +1,6 @@
import React from 'react';
import { Heart, Clock, Package, Activity, AlertCircle } from 'lucide-react';
import { cn } from '../../../lib/utils';
import { Activity, AlertCircle, Clock, Heart, Package } from 'lucide-react';
import type { components } from '../../../api/v2/schema';
import { cn } from '../../../lib/utils';
type HealthResponse = components['schemas']['HealthResponse'];
@ -14,20 +13,18 @@ interface SystemOverviewProps {
error?: string;
}
function SystemOverview({
health,
totalDAGs = 0,
activeRuns = 0,
function SystemOverview({
health,
totalDAGs = 0,
activeRuns = 0,
recentErrors = 0,
isLoading,
error
error,
}: SystemOverviewProps) {
const formatUptime = (seconds: number): string => {
const days = Math.floor(seconds / (60 * 60 * 24));
const hours = Math.floor((seconds % (60 * 60 * 24)) / (60 * 60));
const minutes = Math.floor((seconds % (60 * 60)) / 60);
if (days > 0) {
return `${days}d ${hours}h ${minutes}m`;
} else if (hours > 0) {
@ -36,35 +33,43 @@ function SystemOverview({
return `${minutes}m`;
}
};
const getHealthColor = () => {
if (error || !health) return 'text-red-500';
return health.status === 'healthy' ? 'text-green-500' : 'text-yellow-500';
};
return (
<div className="border rounded-lg bg-card p-4">
<div className="flex items-center justify-between mb-3">
<h2 className="text-base font-semibold">System Overview</h2>
<div className="flex items-center gap-2">
<div className="relative">
<div className={cn(
"w-2 h-2 rounded-full transition-colors",
getHealthColor()
)} />
{health?.status === 'healthy' && !error && (
<div className={cn(
"absolute inset-0 rounded-full animate-ping opacity-75",
<div
className={cn(
'w-2 h-2 rounded-full transition-colors',
getHealthColor()
)} />
)}
/>
{health?.status === 'healthy' && !error && (
<div
className={cn(
'absolute inset-0 rounded-full animate-ping opacity-75',
getHealthColor()
)}
/>
)}
</div>
<span className={cn("text-xs font-medium", getHealthColor())}>
{error ? 'Error' : health?.status === 'healthy' ? 'Healthy' : 'Degraded'}
<span className={cn('text-xs font-medium', getHealthColor())}>
{error
? 'Error'
: health?.status === 'healthy'
? 'Healthy'
: 'Degraded'}
</span>
</div>
</div>
<div className="grid grid-cols-2 md:grid-cols-4 gap-3">
{/* Server Health */}
<div className="space-y-1">
@ -79,7 +84,7 @@ function SystemOverview({
v{health?.version || 'Unknown'}
</div>
</div>
{/* Uptime */}
<div className="space-y-1">
<div className="flex items-center gap-1.5 text-xs text-muted-foreground">
@ -89,25 +94,19 @@ function SystemOverview({
<div className="text-sm font-medium">
{health?.uptime ? formatUptime(health.uptime) : 'N/A'}
</div>
<div className="text-xs text-muted-foreground">
Since startup
</div>
<div className="text-xs text-muted-foreground">Since startup</div>
</div>
{/* Total DAGs */}
<div className="space-y-1">
<div className="flex items-center gap-1.5 text-xs text-muted-foreground">
<Package className="h-3 w-3" />
<span>Total DAGs</span>
</div>
<div className="text-sm font-medium">
{totalDAGs}
</div>
<div className="text-xs text-muted-foreground">
Definitions
</div>
<div className="text-sm font-medium">{totalDAGs}</div>
<div className="text-xs text-muted-foreground">Definitions</div>
</div>
{/* Active Runs */}
<div className="space-y-1">
<div className="flex items-center gap-1.5 text-xs text-muted-foreground">
@ -115,9 +114,7 @@ function SystemOverview({
<span>Active Runs</span>
</div>
<div className="flex items-center gap-2">
<div className="text-sm font-medium">
{activeRuns}
</div>
<div className="text-sm font-medium">{activeRuns}</div>
{recentErrors > 0 && (
<div className="flex items-center gap-1 text-xs text-red-500">
<AlertCircle className="h-3 w-3" />
@ -125,19 +122,15 @@ function SystemOverview({
</div>
)}
</div>
<div className="text-xs text-muted-foreground">
Running now
</div>
<div className="text-xs text-muted-foreground">Running now</div>
</div>
</div>
{error && (
<div className="mt-3 pt-3 border-t text-xs text-red-500">
{error}
</div>
<div className="mt-3 pt-3 border-t text-xs text-red-500">{error}</div>
)}
</div>
);
}
export default SystemOverview;
export default SystemOverview;

View File

@ -1,10 +1,21 @@
import logoDark from '@/assets/images/logo_dark.png';
import { FeedbackDialog } from '@/components/FeedbackDialog';
import { useConfig } from '@/contexts/ConfigContext';
import { cn } from '@/lib/utils'; // Assuming cn utility is available
import { BarChart2, GitBranch, List, Search, Server, PanelLeft, Github, MessageSquare, Users, Activity, Layers } from 'lucide-react';
import {
Activity,
BarChart2,
GitBranch,
Github,
Layers,
List,
MessageSquare,
PanelLeft,
Search,
Server,
} from 'lucide-react';
import * as React from 'react';
import { Link, useLocation } from 'react-router-dom';
import { FeedbackDialog } from '@/components/FeedbackDialog';
// Discord SVG Icon component
function DiscordIcon({ className }: { className?: string }) {
@ -79,7 +90,10 @@ export const mainListItems = React.forwardRef<
aria-label="Toggle sidebar"
>
{isHovered ? (
<PanelLeft size={20} className="text-primary-foreground hover:text-primary-foreground/70" />
<PanelLeft
size={20}
className="text-primary-foreground hover:text-primary-foreground/70"
/>
) : (
<img
src={logoDark}
@ -107,9 +121,9 @@ export const mainListItems = React.forwardRef<
</div>
<button
onClick={() => {
setIsHovered(false);
onToggle?.();
}}
setIsHovered(false);
onToggle?.();
}}
className="absolute right-3 top-1/2 transform -translate-y-1/2 w-6 h-6 flex items-center justify-center z-10 text-primary-foreground/40 hover:text-primary-foreground/70 transition-all duration-200 cursor-pointer"
aria-label="Toggle sidebar"
>
@ -221,7 +235,9 @@ export const mainListItems = React.forwardRef<
title="Send Feedback"
>
<MessageSquare size={18} />
{isOpen && <span className="ml-3 text-xs font-medium">Send Feedback</span>}
{isOpen && (
<span className="ml-3 text-xs font-medium">Send Feedback</span>
)}
</button>
</div>
{/* Discord Community link */}

View File

@ -1,8 +1,11 @@
import { debounce } from 'lodash';
import React from 'react';
import { useLocation } from 'react-router-dom';
import { components, PathsDagsGetParametersQuerySort, PathsDagsGetParametersQueryOrder } from '../../api/v2/schema';
import { RefreshButton } from '../../components/ui/refresh-button';
import {
components,
PathsDagsGetParametersQueryOrder,
PathsDagsGetParametersQuerySort,
} from '../../api/v2/schema';
import { AppBarContext } from '../../contexts/AppBarContext';
import { useUserPreferences } from '../../contexts/UserPreference';
import { DAGErrors } from '../../features/dags/components/dag-editor';
@ -32,8 +35,6 @@ function DAGs() {
updatePreference('pageLimit', newLimit);
};
const { data, mutate, isLoading } = useQuery(
'/dags',
{