mirror of
https://github.com/dagu-org/dagu.git
synced 2025-12-28 06:34:22 +00:00
feat(spec): allow call alias for child DAG steps (#1335)
This commit is contained in:
parent
7b3fdb7d0f
commit
f4ef28c890
@ -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
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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"
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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"
|
||||
|
||||
|
||||
@ -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"
|
||||
```
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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}"
|
||||
|
||||
@ -202,7 +202,7 @@ Access outputs from nested workflows:
|
||||
|
||||
```yaml
|
||||
steps:
|
||||
- run: etl-workflow
|
||||
- call: etl-workflow
|
||||
params: "DATE=${TODAY}"
|
||||
output: ETL_RESULT
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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}`:
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -116,7 +116,7 @@ steps:
|
||||
```yaml
|
||||
steps:
|
||||
- parallel: [file1, file2, file3]
|
||||
run: process-file
|
||||
call: process-file
|
||||
params: "FILE=${ITEM}"
|
||||
|
||||
---
|
||||
|
||||
@ -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}
|
||||
```
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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
|
||||
`,
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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"`
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
`)
|
||||
|
||||
|
||||
@ -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}
|
||||
|
||||
@ -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
|
||||
|
||||
---
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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())"
|
||||
`,
|
||||
}
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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{
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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": [
|
||||
|
||||
@ -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 */
|
||||
|
||||
@ -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 });
|
||||
}
|
||||
|
||||
@ -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}
|
||||
|
||||
@ -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>
|
||||
)}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -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];
|
||||
|
||||
@ -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}`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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 */}
|
||||
|
||||
@ -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',
|
||||
{
|
||||
|
||||
Loading…
Reference in New Issue
Block a user