Skip to content

Commit de34548

Browse files
shubhamraj-gitLefteris Gilmaz
authored and
Lefteris Gilmaz
committed
Integrate API with Trigger Dag Run (apache#44850)
* conf * conf check * basic trigger api * retrieve params * refactor the api * fix * fix details page * refactor the logic * refactor * reviews * error on form * data date validation * remove empty runid and note
1 parent 46e0da4 commit de34548

File tree

6 files changed

+254
-142
lines changed

6 files changed

+254
-142
lines changed

airflow/ui/src/components/DataTable/DataTable.tsx

+2-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ import {
3030
} from "@tanstack/react-table";
3131
import React, { type ReactNode, useCallback, useRef } from "react";
3232

33-
import { ProgressBar, Pagination } from "../ui";
33+
import { ProgressBar, Pagination, Toaster } from "../ui";
3434
import { CardList } from "./CardList";
3535
import { TableList } from "./TableList";
3636
import { createSkeletonMock } from "./skeleton";
@@ -128,6 +128,7 @@ export const DataTable = <TData,>({
128128
Boolean(isFetching) && !Boolean(isLoading) ? "visible" : "hidden"
129129
}
130130
/>
131+
<Toaster />
131132
{errorMessage}
132133
{!Boolean(isLoading) && !rows.length && (
133134
<Text pt={1}>{noRowsMessage ?? `No ${modelName}s found.`}</Text>

airflow/ui/src/components/TriggerDag/TriggerDAGForm.tsx

+67-31
Original file line numberDiff line numberDiff line change
@@ -20,28 +20,50 @@ import { Input, Button, Box, Text, Spacer, HStack } from "@chakra-ui/react";
2020
import { json } from "@codemirror/lang-json";
2121
import { githubLight, githubDark } from "@uiw/codemirror-themes-all";
2222
import CodeMirror from "@uiw/react-codemirror";
23-
import { useEffect, useState } from "react";
23+
import { useEffect, useMemo, useState } from "react";
2424
import { useForm, Controller } from "react-hook-form";
2525
import { FiPlay } from "react-icons/fi";
2626

2727
import { useColorMode } from "src/context/colorMode";
28+
import { useDagParams } from "src/queries/useDagParams";
29+
import { useTrigger } from "src/queries/useTrigger";
2830

31+
import { ErrorAlert } from "../ErrorAlert";
2932
import { Accordion } from "../ui";
30-
import type { DagParams } from "./TriggerDag";
3133

3234
type TriggerDAGFormProps = {
33-
dagParams: DagParams;
35+
dagId: string;
3436
onClose: () => void;
35-
onTrigger: (updatedDagParams: DagParams) => void;
36-
setDagParams: React.Dispatch<React.SetStateAction<DagParams>>;
37+
open: boolean;
38+
};
39+
40+
export type DagRunTriggerParams = {
41+
conf: string;
42+
dagRunId: string;
43+
dataIntervalEnd: string;
44+
dataIntervalStart: string;
45+
note: string;
3746
};
3847

3948
const TriggerDAGForm: React.FC<TriggerDAGFormProps> = ({
40-
dagParams,
41-
onTrigger,
42-
setDagParams,
49+
dagId,
50+
onClose,
51+
open,
4352
}) => {
44-
const [jsonError, setJsonError] = useState<string | undefined>();
53+
const [errors, setErrors] = useState<{ conf?: string; date?: string }>({});
54+
const conf = useDagParams(dagId, open);
55+
const { error: errorTrigger, isPending, triggerDagRun } = useTrigger(onClose);
56+
57+
const dagRunRequestBody: DagRunTriggerParams = useMemo(
58+
() => ({
59+
conf,
60+
dagRunId: "",
61+
dataIntervalEnd: "",
62+
dataIntervalStart: "",
63+
note: "",
64+
}),
65+
[conf],
66+
);
4567

4668
const {
4769
control,
@@ -50,40 +72,47 @@ const TriggerDAGForm: React.FC<TriggerDAGFormProps> = ({
5072
reset,
5173
setValue,
5274
watch,
53-
} = useForm<DagParams>({
54-
defaultValues: dagParams,
55-
});
75+
} = useForm<DagRunTriggerParams>({ defaultValues: dagRunRequestBody });
5676

5777
const dataIntervalStart = watch("dataIntervalStart");
5878
const dataIntervalEnd = watch("dataIntervalEnd");
5979

6080
useEffect(() => {
61-
reset(dagParams);
62-
}, [dagParams, reset]);
63-
64-
const onSubmit = (data: DagParams) => {
65-
onTrigger(data);
66-
setDagParams(data);
67-
setJsonError(undefined);
68-
};
81+
reset(dagRunRequestBody);
82+
}, [dagRunRequestBody, reset]);
6983

7084
const validateAndPrettifyJson = (value: string) => {
7185
try {
7286
const parsedJson = JSON.parse(value) as JSON;
7387

74-
setJsonError(undefined);
88+
setErrors((prev) => ({ ...prev, conf: undefined }));
7589

7690
return JSON.stringify(parsedJson, undefined, 2);
7791
} catch (error) {
7892
const errorMessage =
7993
error instanceof Error ? error.message : "Unknown error occurred.";
8094

81-
setJsonError(`Invalid JSON format: ${errorMessage}`);
95+
setErrors((prev) => ({
96+
...prev,
97+
conf: `Invalid JSON format: ${errorMessage}`,
98+
}));
8299

83100
return value;
84101
}
85102
};
86103

104+
const onSubmit = (data: DagRunTriggerParams) => {
105+
if (Boolean(data.dataIntervalStart) !== Boolean(data.dataIntervalEnd)) {
106+
setErrors((prev) => ({
107+
...prev,
108+
date: "Either both Data Interval Start and End must be provided, or both must be empty.",
109+
}));
110+
111+
return;
112+
}
113+
triggerDagRun(dagId, data);
114+
};
115+
87116
const validateDates = (
88117
fieldName: "dataIntervalEnd" | "dataIntervalStart",
89118
) => {
@@ -92,6 +121,8 @@ const TriggerDAGForm: React.FC<TriggerDAGFormProps> = ({
92121
: undefined;
93122
const endDate = dataIntervalEnd ? new Date(dataIntervalEnd) : undefined;
94123

124+
setErrors((prev) => ({ ...prev, date: undefined }));
125+
95126
if (startDate && endDate) {
96127
if (fieldName === "dataIntervalStart" && startDate > endDate) {
97128
setValue("dataIntervalStart", dataIntervalEnd);
@@ -105,7 +136,8 @@ const TriggerDAGForm: React.FC<TriggerDAGFormProps> = ({
105136

106137
return (
107138
<>
108-
<Accordion.Root collapsible size="lg" variant="enclosed">
139+
<ErrorAlert error={errorTrigger} />
140+
<Accordion.Root collapsible mt={4} size="lg" variant="enclosed">
109141
<Accordion.Item key="advancedOptions" value="advancedOptions">
110142
<Accordion.ItemTrigger cursor="button">
111143
Advanced Options
@@ -153,7 +185,7 @@ const TriggerDAGForm: React.FC<TriggerDAGFormProps> = ({
153185
</Text>
154186
<Controller
155187
control={control}
156-
name="runId"
188+
name="dagRunId"
157189
render={({ field }) => (
158190
<Input
159191
{...field}
@@ -168,7 +200,7 @@ const TriggerDAGForm: React.FC<TriggerDAGFormProps> = ({
168200
</Text>
169201
<Controller
170202
control={control}
171-
name="configJson"
203+
name="conf"
172204
render={({ field }) => (
173205
<Box mb={4}>
174206
<CodeMirror
@@ -196,11 +228,11 @@ const TriggerDAGForm: React.FC<TriggerDAGFormProps> = ({
196228
}}
197229
theme={colorMode === "dark" ? githubDark : githubLight}
198230
/>
199-
{Boolean(jsonError) ? (
231+
{Boolean(errors.conf) && (
200232
<Text color="red.500" fontSize="sm" mt={2}>
201-
{jsonError}
233+
{errors.conf}
202234
</Text>
203-
) : undefined}
235+
)}
204236
</Box>
205237
)}
206238
/>
@@ -210,7 +242,7 @@ const TriggerDAGForm: React.FC<TriggerDAGFormProps> = ({
210242
</Text>
211243
<Controller
212244
control={control}
213-
name="notes"
245+
name="note"
214246
render={({ field }) => (
215247
<Input {...field} placeholder="Optional" size="sm" />
216248
)}
@@ -219,7 +251,11 @@ const TriggerDAGForm: React.FC<TriggerDAGFormProps> = ({
219251
</Accordion.ItemContent>
220252
</Accordion.Item>
221253
</Accordion.Root>
222-
254+
{Boolean(errors.date) && (
255+
<Text color="red.500" fontSize="sm" mt={2}>
256+
{errors.date}
257+
</Text>
258+
)}
223259
<Box as="footer" display="flex" justifyContent="flex-end" mt={4}>
224260
<HStack w="full">
225261
{isDirty ? (
@@ -230,7 +266,7 @@ const TriggerDAGForm: React.FC<TriggerDAGFormProps> = ({
230266
<Spacer />
231267
<Button
232268
colorPalette="blue"
233-
disabled={Boolean(jsonError)}
269+
disabled={Boolean(errors.conf) || Boolean(errors.date) || isPending}
234270
onClick={() => void handleSubmit(onSubmit)()}
235271
>
236272
<FiPlay /> Trigger

airflow/ui/src/components/TriggerDag/TriggerDAGModal.tsx

+31-66
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,12 @@
1717
* under the License.
1818
*/
1919
import { Heading, VStack } from "@chakra-ui/react";
20-
import React, { useCallback, useEffect, useMemo, useState } from "react";
20+
import React from "react";
2121

2222
import { Alert, Dialog } from "src/components/ui";
2323

2424
import { TogglePause } from "../TogglePause";
2525
import TriggerDAGForm from "./TriggerDAGForm";
26-
import type { DagParams } from "./TriggerDag";
27-
import { TriggerDag as triggerDag } from "./TriggerDag";
2826

2927
type TriggerDAGModalProps = {
3028
dagDisplayName: string;
@@ -40,70 +38,37 @@ const TriggerDAGModal: React.FC<TriggerDAGModalProps> = ({
4038
isPaused,
4139
onClose,
4240
open,
43-
}) => {
44-
const initialDagParams = useMemo(
45-
() => ({
46-
configJson: "{}",
47-
dagId,
48-
dataIntervalEnd: "",
49-
dataIntervalStart: "",
50-
notes: "",
51-
runId: "",
52-
}),
53-
[dagId],
54-
);
41+
}) => (
42+
<Dialog.Root
43+
lazyMount
44+
onOpenChange={onClose}
45+
open={open}
46+
size="xl"
47+
unmountOnExit
48+
>
49+
<Dialog.Content backdrop>
50+
<Dialog.Header>
51+
<VStack align="start" gap={4}>
52+
<Heading size="xl">
53+
Trigger DAG - {dagDisplayName}{" "}
54+
<TogglePause dagId={dagId} isPaused={isPaused} skipConfirm />
55+
</Heading>
56+
{isPaused ? (
57+
<Alert status="warning" title="Paused DAG">
58+
Triggering will create a DAG run, but it will not start until the
59+
DAG is unpaused.
60+
</Alert>
61+
) : undefined}
62+
</VStack>
63+
</Dialog.Header>
5564

56-
const [dagParams, setDagParams] = useState<DagParams>(initialDagParams);
65+
<Dialog.CloseTrigger />
5766

58-
const handleTrigger = useCallback(
59-
(updatedDagParams: DagParams) => {
60-
triggerDag(updatedDagParams);
61-
onClose();
62-
},
63-
[onClose],
64-
);
65-
66-
useEffect(() => {
67-
if (!open) {
68-
setDagParams(initialDagParams);
69-
}
70-
}, [open, initialDagParams]);
71-
72-
return (
73-
<Dialog.Root onOpenChange={onClose} open={open} size="xl">
74-
<Dialog.Content backdrop>
75-
<Dialog.Header>
76-
<VStack align="start" gap={4}>
77-
<Heading size="xl">
78-
Trigger DAG - {dagDisplayName}{" "}
79-
<TogglePause
80-
dagId={dagParams.dagId}
81-
isPaused={isPaused}
82-
skipConfirm
83-
/>
84-
</Heading>
85-
{isPaused ? (
86-
<Alert status="warning" title="Paused DAG">
87-
Triggering will create a DAG run, but it will not start until
88-
the DAG is unpaused.
89-
</Alert>
90-
) : undefined}
91-
</VStack>
92-
</Dialog.Header>
93-
94-
<Dialog.CloseTrigger />
95-
96-
<Dialog.Body>
97-
<TriggerDAGForm
98-
dagParams={dagParams}
99-
onClose={onClose}
100-
onTrigger={handleTrigger}
101-
setDagParams={setDagParams}
102-
/>
103-
</Dialog.Body>
104-
</Dialog.Content>
105-
</Dialog.Root>
106-
);
107-
};
67+
<Dialog.Body>
68+
<TriggerDAGForm dagId={dagId} onClose={onClose} open={open} />
69+
</Dialog.Body>
70+
</Dialog.Content>
71+
</Dialog.Root>
72+
);
10873

10974
export default TriggerDAGModal;

airflow/ui/src/components/TriggerDag/TriggerDag.tsx

-44
This file was deleted.

0 commit comments

Comments
 (0)