Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add optional field group to organize DAGs on Web UI #182

Merged
merged 13 commits into from
Jun 23, 2022
2 changes: 1 addition & 1 deletion .goreleaser.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -35,5 +35,5 @@ brews:
name: homebrew-tap
folder: Formula
homepage: 'https://github.com/yohamta/dagu'
description: 'A standalone Low-Code Workflow Executor that runs DAGs defined in a simple, declarative YAML format that is similar to GitHub Actions or Argo Workflows with built-in Web UI'
description: 'A No-code workflow executor that runs DAGs defined in a simple YAML format'
license: "GNU General Public License v3.0"
65 changes: 33 additions & 32 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
[![GoDoc](https://godoc.org/github.com/yohamta/dagu?status.svg)](https://godoc.org/github.com/yohamta/dagu)
![Test](https://github.com/yohamta/dagu/actions/workflows/test.yaml/badge.svg)

**A No-code workflow executor with built-in web UI**
**A No-code DAG executor with built-in web UI**

It runs [DAGs (Directed acyclic graph)](https://en.wikipedia.org/wiki/Directed_acyclic_graph) defined in a simple, declarative YAML format.

Expand All @@ -24,9 +24,9 @@ It runs [DAGs (Directed acyclic graph)](https://en.wikipedia.org/wiki/Directed_a
- [via GitHub Release Page](#via-github-release-page)
- [️Quick start](#️quick-start)
- [1. Launch the Web UI](#1-launch-the-web-ui)
- [2. Create a new workflow](#2-create-a-new-workflow)
- [3. Edit the workflow](#3-edit-the-workflow)
- [4. Execute the workflow](#4-execute-the-workflow)
- [2. Create a new DAG](#2-create-a-new-dag)
- [3. Edit the DAG](#3-edit-the-dag)
- [4. Execute the DAG](#4-execute-the-dag)
- [Command Line User Interface](#command-line-user-interface)
- [Web User Interface](#web-user-interface)
- [YAML format](#yaml-format)
Expand All @@ -51,7 +51,7 @@ It runs [DAGs (Directed acyclic graph)](https://en.wikipedia.org/wiki/Directed_a
- [Where is the history data stored?](#where-is-the-history-data-stored)
- [Where are the log files stored?](#where-are-the-log-files-stored)
- [How long will the history data be stored?](#how-long-will-the-history-data-be-stored)
- [How can I retry a workflow from a specific task?](#how-can-i-retry-a-workflow-from-a-specific-task)
- [How can I retry a DAG from a specific task?](#how-can-i-retry-a-dag-from-a-specific-task)
- [Does it provide sucheduler daemon?](#does-it-provide-sucheduler-daemon)
- [How does it track running processes without DBMS?](#how-does-it-track-running-processes-without-dbms)
- [License](#license)
Expand All @@ -63,7 +63,7 @@ In the projects I worked on, our ETL pipeline had **many problems**. There were

## Why not existing tools, like Airflow?

There are many popular workflow engines such as Airflow, Prefect, etc. They are powerful and valuable tools, but they require writing code such as Python to run workflows. In many situations like above, there are already hundreds of thousands of existing lines of code in other languages such as shell scripts or Perl. Adding another layer of Python on top of these would make it more complicated. So we developed Dagu. It is easy-to-use and self-contained, making it ideal for smaller projects with fewer people.
There are many popular workflow engines such as Airflow, Prefect, etc. They are powerful and valuable tools, but they require writing code such as Python to run DAGs. In many situations like above, there are already hundreds of thousands of existing lines of code in other languages such as shell scripts or Perl. Adding another layer of Python on top of these would make it more complicated. So we developed Dagu. It is easy-to-use and self-contained, making it ideal for smaller projects with fewer people.

## How does it work?

Expand Down Expand Up @@ -100,57 +100,57 @@ Download the latest binary from the [Releases page](https://github.com/yohamta/d

Start the server with `dagu server` and browse to `http://127.0.0.1:8080` to explore the Web UI.

### 2. Create a new workflow
### 2. Create a new DAG

Create a workflow by clicking the `New DAG` button on the top page of the web UI. Input `example.yaml` in the dialog.
Create a DAG by clicking the `New DAG` button on the top page of the web UI. Input `example.yaml` in the dialog.

### 3. Edit the workflow
### 3. Edit the DAG

Go to the workflow detail page and click the `Edit` button in the `Config` Tab. Copy and paste from this [example YAML](https://github.com/yohamta/dagu/blob/main/examples/example.yaml) and click the `Save` button.
Go to the DAG detail page and click the `Edit` button in the `Config` Tab. Copy and paste from this [example YAML](https://github.com/yohamta/dagu/blob/main/examples/example.yaml) and click the `Save` button.

### 4. Execute the workflow
### 4. Execute the DAG

You can execute the example by pressing the `Start` button.

![example](assets/images/example.gif?raw=true)

## Command Line User Interface

- `dagu start [--params=<params>] <file>` - Runs the workflow
- `dagu status <file>` - Displays the current status of the workflow
- `dagu retry --req=<request-id> <file>` - Re-runs the specified workflow run
- `dagu stop <file>` - Stops the workflow execution by sending TERM signals
- `dagu dry [--params=<params>] <file>` - Dry-runs the workflow
- `dagu start [--params=<params>] <file>` - Runs the DAG
- `dagu status <file>` - Displays the current status of the DAG
- `dagu retry --req=<request-id> <file>` - Re-runs the specified DAG run
- `dagu stop <file>` - Stops the DAG execution by sending TERM signals
- `dagu dry [--params=<params>] <file>` - Dry-runs the DAG
- `dagu server` - Starts the web server for web UI
- `dagu version` - Shows the current binary version

## Web User Interface

- **Dashboard**: It shows the overall status and executions timeline of the day.

![Workflows](assets/images/ui-dashboard.png?raw=true)
![DAGs](assets/images/ui-dashboard.png?raw=true)

- **Workflows**: It shows all workflows and the real-time status.
- **DAGs**: It shows all DAGs and the real-time status.

![Workflows](assets/images/ui-workflows.png?raw=true)
![DAGs](assets/images/ui-dags.png?raw=true)

- **Workflow Details**: It shows the real-time status, logs, and workflow configurations. You can edit workflow configurations on a browser.
- **DAG Details**: It shows the real-time status, logs, and DAG configurations. You can edit DAG configurations on a browser.

![Details](assets/images/ui-details.png?raw=true)

- **Execution History**: It shows past execution results and logs.

![History](assets/images/ui-history.png?raw=true)

- **Workflow Execution Log**: It shows the detail log and standard output of each execution and steps.
- **DAG Execution Log**: It shows the detail log and standard output of each execution and steps.

![Workflow Log](assets/images/ui-logoutput.png?raw=true)
![DAG Log](assets/images/ui-logoutput.png?raw=true)

## YAML format

### Minimal Definition

Minimal workflow definition is as simple as follows:
Minimal DAG definition is as simple as follows:

```yaml
steps:
Expand Down Expand Up @@ -229,7 +229,7 @@ steps:

### Conditional Logic

Sometimes you have parts of a workflow that you only want to run under certain conditions. You can use the `precondition` field to add conditional branches to your workflow.
Sometimes you have parts of a DAG that you only want to run under certain conditions. You can use the `precondition` field to add conditional branches to your DAG.

For example, the below task only runs on the first date of each month.

Expand All @@ -242,7 +242,7 @@ steps:
expected: "01"
```

If you want the workflow to continue to the next step regardless of the step's conditional check result, you can use the `continueOn` field:
If you want the DAG to continue to the next step regardless of the step's conditional check result, you can use the `continueOn` field:

```yaml
steps:
Expand Down Expand Up @@ -279,7 +279,7 @@ steps:

### State Handlers

It is often desirable to take action when a specific event happens, for example, when a workflow fails. To achieve this, you can use `handlerOn` fields.
It is often desirable to take action when a specific event happens, for example, when a DAG fails. To achieve this, you can use `handlerOn` fields.

```yaml
handlerOn:
Expand Down Expand Up @@ -307,12 +307,13 @@ steps:

### All Available Fields

Combining these settings gives you granular control over how the workflow runs.
Combining these settings gives you granular control over how the DAG runs.

```yaml
name: all configuration # name (optional, default is filename)
description: run a DAG # description
tags: daily job # Free tags (separated by comma)
name: all configuration # Name (optional, default is filename)
description: run a DAG # Description
group: DailyJobs # Group name to organize DAGs (optional)
tags: example # Free tags (separated by comma)
env: # Environment variables
- LOG_DIR: ${HOME}/logs
- PATH: /usr/local/bin:${PATH}
Expand Down Expand Up @@ -433,9 +434,9 @@ It will store log files in the `DAGU__LOGS` environment variable path. The defau

The default retention period for execution history is seven days. However, you can override the setting by the `histRetentionDays` field in a YAML file.

### How can I retry a workflow from a specific task?
### How can I retry a DAG from a specific task?

You can change the status of any task to a `failed` state. Then, when you retry the workflow, it will execute the failed one and any subsequent.
You can change the status of any task to a `failed` state. Then, when you retry the DAG, it will execute the failed one and any subsequent.

### Does it provide sucheduler daemon?

Expand Down
10 changes: 5 additions & 5 deletions admin/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import DashboardLayout from "./DashboardLayout";
import Dashboard from "./pages/Dashboard";
import View from "./pages/View";
import ViewList from "./pages/ViewList";
import WorkflowDetail from "./pages/WorkflowDetails";
import WorkflowList from "./pages/WorkflowList";
import DAGDetails from "./pages/DAGDetails";
import DAGs from "./pages/DAGs";

type Config = {
title: string;
Expand All @@ -22,11 +22,11 @@ function App({ config }: Props) {
<DashboardLayout {...config}>
<Routes>
<Route path="/" element={<Dashboard />} />
<Route path="" element={<WorkflowList />} />
<Route path="" element={<DAGs />} />
<Route path="/views" element={<ViewList />} />
<Route path="/views/:name" element={<View />} />
<Route path="/dags/" element={<WorkflowList />} />
<Route path="/dags/:name" element={<WorkflowDetail />} />
<Route path="/dags/" element={<DAGs />} />
<Route path="/dags/:name" element={<DAGDetails />} />
</Routes>
</DashboardLayout>
</BrowserRouter>
Expand Down
2 changes: 1 addition & 1 deletion admin/src/DashboardLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ function DashboardContent({
</Link>
{[
["/", "Dashboard"],
["/dags", "Workflows"],
["/dags", "DAGs"],
["/views", "Views"],
].map((v) => (
<Link
Expand Down
3 changes: 1 addition & 2 deletions admin/src/api/Workflow.tsx → admin/src/api/DAG.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,14 @@ import { DAG } from "../models/Dag";
import { Node, NodeStatus } from "../models/Node";
import { StatusFile } from "../models/StatusFile";

export type GetWorkflowResponse = {
export type GetDAGResponse = {
Title: string;
Charset: string;
DAG?: DAG;
Graph: string;
Definition: string;
LogData: LogData;
LogUrl: string;
Group: string;
StepLog?: LogFile;
ScLog?: LogFile;
Errors: string[];
Expand Down
2 changes: 1 addition & 1 deletion admin/src/api/List.tsx → admin/src/api/DAGs.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { DAG } from "../models/Dag";
import { Group } from "../models/Group";

export type GetListResponse = {
export type GetDAGsResponse = {
Title: string;
Charset: string;
DAGs: DAG[];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,16 @@ import { SchedulerStatus, Status } from "../models/Status";

type Props = {
status?: Status;
group: string;
name: string;
label?: boolean;
refresh?: () => any;
};

function WorkflowActions({
status,
group,
name,
refresh = () => {},
label = true,
}: Props) {
function DAGActions({ status, name, refresh = () => {}, label = true }: Props) {
const onSubmit = React.useCallback(
async (
warn: string,
params: {
group: string;
name: string;
action: string;
requestId?: string;
Expand All @@ -31,7 +23,6 @@ function WorkflowActions({
return;
}
const form = new FormData();
form.set("group", params.group);
form.set("action", params.action);
if (params.requestId) {
form.set("request-id", params.requestId);
Expand Down Expand Up @@ -71,8 +62,7 @@ function WorkflowActions({
}
disabled={!buttonState["start"]}
onClick={() =>
onSubmit("Do you really want to start the workflow?", {
group: group,
onSubmit("Do you really want to start the DAG?", {
name: name,
action: "start",
})
Expand All @@ -89,8 +79,7 @@ function WorkflowActions({
}
disabled={!buttonState["stop"]}
onClick={() =>
onSubmit("Do you really want to cancel the workflow?", {
group: group,
onSubmit("Do you really want to cancel the DAG?", {
name: name,
action: "stop",
})
Expand All @@ -110,7 +99,6 @@ function WorkflowActions({
onSubmit(
`Do you really want to rerun the last execution (${status?.RequestId}) ?`,
{
group: group,
name: name,
requestId: status?.RequestId,
action: "retry",
Expand All @@ -123,7 +111,7 @@ function WorkflowActions({
</Stack>
);
}
export default WorkflowActions;
export default DAGActions;

interface ActionButtonProps {
children: string;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Box, Button, Paper, Stack, Typography } from "@mui/material";
import React from "react";
import { GetWorkflowResponse } from "../api/Workflow";
import { WorkflowContext } from "../contexts/WorkflowContext";
import { GetDAGResponse } from "../api/DAG";
import { DAGContext } from "../contexts/DAGContext";
import { Config } from "../models/Config";
import { Step } from "../models/Step";
import ConfigEditor from "./ConfigEditor";
Expand All @@ -11,18 +11,18 @@ import Graph from "./Graph";
import ConfigStepTable from "./ConfigStepTable";

type Props = {
data: GetWorkflowResponse;
data: GetDAGResponse;
};

function WorkflowConfig({ data }: Props) {
function DAGConfig({ data }: Props) {
const [editing, setEditing] = React.useState(false);
const [currentValue, setCurrentValue] = React.useState(data.Definition);
const handlers = getHandlersFromConfig(data.DAG?.Config);
if (data.DAG?.Config == null) {
return null;
}
return (
<WorkflowContext.Consumer>
<DAGContext.Consumer>
{(props) =>
data.DAG &&
data.DAG.Config && (
Expand Down Expand Up @@ -161,10 +161,10 @@ function WorkflowConfig({ data }: Props) {
</React.Fragment>
)
}
</WorkflowContext.Consumer>
</DAGContext.Consumer>
);
}
export default WorkflowConfig;
export default DAGConfig;

function getHandlersFromConfig(cfg?: Config) {
const r: Step[] = [];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ type Props = {
refresh: () => void;
};

function CreateWorkflowButton({ refresh }: Props) {
function DAGCreationButton({ refresh }: Props) {
return (
<Button
variant="contained"
Expand Down Expand Up @@ -49,4 +49,4 @@ function CreateWorkflowButton({ refresh }: Props) {
</Button>
);
}
export default CreateWorkflowButton;
export default DAGCreationButton;
Loading