diff --git a/airflow/ui/src/components/DataTable/DataTable.tsx b/airflow/ui/src/components/DataTable/DataTable.tsx index d59d86bb59435..4bcb7c7d06626 100644 --- a/airflow/ui/src/components/DataTable/DataTable.tsx +++ b/airflow/ui/src/components/DataTable/DataTable.tsx @@ -30,7 +30,7 @@ import { } from "@tanstack/react-table"; import React, { type ReactNode, useCallback, useRef } from "react"; -import { ProgressBar, Pagination } from "../ui"; +import { ProgressBar, Pagination, Toaster } from "../ui"; import { CardList } from "./CardList"; import { TableList } from "./TableList"; import { createSkeletonMock } from "./skeleton"; @@ -128,6 +128,7 @@ export const DataTable = ({ Boolean(isFetching) && !Boolean(isLoading) ? "visible" : "hidden" } /> + {errorMessage} {!Boolean(isLoading) && !rows.length && ( {noRowsMessage ?? `No ${modelName}s found.`} diff --git a/airflow/ui/src/components/TriggerDag/TriggerDAGForm.tsx b/airflow/ui/src/components/TriggerDag/TriggerDAGForm.tsx index 9df1f208ed806..a11cad61aefd2 100644 --- a/airflow/ui/src/components/TriggerDag/TriggerDAGForm.tsx +++ b/airflow/ui/src/components/TriggerDag/TriggerDAGForm.tsx @@ -20,28 +20,50 @@ import { Input, Button, Box, Text, Spacer, HStack } from "@chakra-ui/react"; import { json } from "@codemirror/lang-json"; import { githubLight, githubDark } from "@uiw/codemirror-themes-all"; import CodeMirror from "@uiw/react-codemirror"; -import { useEffect, useState } from "react"; +import { useEffect, useMemo, useState } from "react"; import { useForm, Controller } from "react-hook-form"; import { FiPlay } from "react-icons/fi"; import { useColorMode } from "src/context/colorMode"; +import { useDagParams } from "src/queries/useDagParams"; +import { useTrigger } from "src/queries/useTrigger"; +import { ErrorAlert } from "../ErrorAlert"; import { Accordion } from "../ui"; -import type { DagParams } from "./TriggerDag"; type TriggerDAGFormProps = { - dagParams: DagParams; + dagId: string; onClose: () => void; - onTrigger: (updatedDagParams: DagParams) => void; - setDagParams: React.Dispatch>; + open: boolean; +}; + +export type DagRunTriggerParams = { + conf: string; + dagRunId: string; + dataIntervalEnd: string; + dataIntervalStart: string; + note: string; }; const TriggerDAGForm: React.FC = ({ - dagParams, - onTrigger, - setDagParams, + dagId, + onClose, + open, }) => { - const [jsonError, setJsonError] = useState(); + const [errors, setErrors] = useState<{ conf?: string; date?: string }>({}); + const conf = useDagParams(dagId, open); + const { error: errorTrigger, isPending, triggerDagRun } = useTrigger(onClose); + + const dagRunRequestBody: DagRunTriggerParams = useMemo( + () => ({ + conf, + dagRunId: "", + dataIntervalEnd: "", + dataIntervalStart: "", + note: "", + }), + [conf], + ); const { control, @@ -50,40 +72,47 @@ const TriggerDAGForm: React.FC = ({ reset, setValue, watch, - } = useForm({ - defaultValues: dagParams, - }); + } = useForm({ defaultValues: dagRunRequestBody }); const dataIntervalStart = watch("dataIntervalStart"); const dataIntervalEnd = watch("dataIntervalEnd"); useEffect(() => { - reset(dagParams); - }, [dagParams, reset]); - - const onSubmit = (data: DagParams) => { - onTrigger(data); - setDagParams(data); - setJsonError(undefined); - }; + reset(dagRunRequestBody); + }, [dagRunRequestBody, reset]); const validateAndPrettifyJson = (value: string) => { try { const parsedJson = JSON.parse(value) as JSON; - setJsonError(undefined); + setErrors((prev) => ({ ...prev, conf: undefined })); return JSON.stringify(parsedJson, undefined, 2); } catch (error) { const errorMessage = error instanceof Error ? error.message : "Unknown error occurred."; - setJsonError(`Invalid JSON format: ${errorMessage}`); + setErrors((prev) => ({ + ...prev, + conf: `Invalid JSON format: ${errorMessage}`, + })); return value; } }; + const onSubmit = (data: DagRunTriggerParams) => { + if (Boolean(data.dataIntervalStart) !== Boolean(data.dataIntervalEnd)) { + setErrors((prev) => ({ + ...prev, + date: "Either both Data Interval Start and End must be provided, or both must be empty.", + })); + + return; + } + triggerDagRun(dagId, data); + }; + const validateDates = ( fieldName: "dataIntervalEnd" | "dataIntervalStart", ) => { @@ -92,6 +121,8 @@ const TriggerDAGForm: React.FC = ({ : undefined; const endDate = dataIntervalEnd ? new Date(dataIntervalEnd) : undefined; + setErrors((prev) => ({ ...prev, date: undefined })); + if (startDate && endDate) { if (fieldName === "dataIntervalStart" && startDate > endDate) { setValue("dataIntervalStart", dataIntervalEnd); @@ -105,7 +136,8 @@ const TriggerDAGForm: React.FC = ({ return ( <> - + + Advanced Options @@ -153,7 +185,7 @@ const TriggerDAGForm: React.FC = ({ ( = ({ ( = ({ }} theme={colorMode === "dark" ? githubDark : githubLight} /> - {Boolean(jsonError) ? ( + {Boolean(errors.conf) && ( - {jsonError} + {errors.conf} - ) : undefined} + )} )} /> @@ -210,7 +242,7 @@ const TriggerDAGForm: React.FC = ({ ( )} @@ -219,7 +251,11 @@ const TriggerDAGForm: React.FC = ({ - + {Boolean(errors.date) && ( + + {errors.date} + + )} {isDirty ? ( @@ -230,7 +266,7 @@ const TriggerDAGForm: React.FC = ({