You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@airflow.apache.org by ka...@apache.org on 2025/05/06 13:25:14 UTC
(airflow) 03/25: Add `dag_run_conf` to `RunBackfillForm` (#49763)
This is an automated email from the ASF dual-hosted git repository.
kaxilnaik pushed a commit to branch v3-0-test
in repository https://gitbox.apache.org/repos/asf/airflow.git
commit 93960d8d194ded926b212c90dcc9bdd7c5c9bded
Author: Guan Ming(Wesley) Chiu <10...@users.noreply.github.com>
AuthorDate: Thu May 1 01:49:30 2025 +0800
Add `dag_run_conf` to `RunBackfillForm` (#49763)
* feat: add `dag_run_conf` to `RunBackfillForm`
Co-authored-by: Brent Bovenzi <46...@users.noreply.github.com>
* fix: add missing conf preset
Co-authored-by: Brent Bovenzi <46...@users.noreply.github.com>
---------
Co-authored-by: Brent Bovenzi <46...@users.noreply.github.com>
(cherry picked from commit 6ca99d898e0a65a41b47e0188255ad1cb4068ca9)
---
.../src/airflow/ui/src/components/ConfigForm.tsx | 118 ++++++++++++++
.../src/components/DagActions/RunBackfillForm.tsx | 37 ++++-
.../src/components/TriggerDag/TriggerDAGForm.tsx | 174 +++++++--------------
3 files changed, 206 insertions(+), 123 deletions(-)
diff --git a/airflow-core/src/airflow/ui/src/components/ConfigForm.tsx b/airflow-core/src/airflow/ui/src/components/ConfigForm.tsx
new file mode 100644
index 00000000000..a216b22de60
--- /dev/null
+++ b/airflow-core/src/airflow/ui/src/components/ConfigForm.tsx
@@ -0,0 +1,118 @@
+/*!
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import { Accordion, Box, Field } from "@chakra-ui/react";
+import { type Control, type FieldValues, type Path, Controller } from "react-hook-form";
+
+import type { ParamsSpec } from "src/queries/useDagParams";
+import { useParamStore } from "src/queries/useParamStore";
+
+import { FlexibleForm, flexibleFormDefaultSection } from "./FlexibleForm";
+import { JsonEditor } from "./JsonEditor";
+
+type ConfigFormProps<T extends FieldValues = FieldValues> = {
+ readonly children?: React.ReactNode;
+ readonly control: Control<T>;
+ readonly errors: {
+ conf?: string;
+ date?: unknown;
+ };
+ readonly initialParamsDict: { paramsDict: ParamsSpec };
+ readonly setErrors: React.Dispatch<
+ React.SetStateAction<{
+ conf?: string;
+ date?: unknown;
+ }>
+ >;
+};
+
+const ConfigForm = <T extends FieldValues = FieldValues>({
+ children,
+ control,
+ errors,
+ initialParamsDict,
+ setErrors,
+}: ConfigFormProps<T>) => {
+ const { conf, setConf } = useParamStore();
+
+ const validateAndPrettifyJson = (value: string) => {
+ try {
+ const parsedJson = JSON.parse(value) as JSON;
+
+ setErrors((prev) => ({ ...prev, conf: undefined }));
+
+ const formattedJson = JSON.stringify(parsedJson, undefined, 2);
+
+ if (formattedJson !== conf) {
+ setConf(formattedJson); // Update only if the value is different
+ }
+
+ return formattedJson;
+ } catch (error) {
+ const errorMessage = error instanceof Error ? error.message : "Unknown error occurred.";
+
+ setErrors((prev) => ({
+ ...prev,
+ conf: `Invalid JSON format: ${errorMessage}`,
+ }));
+
+ return value;
+ }
+ };
+
+ return (
+ <Accordion.Root
+ collapsible
+ defaultValue={[flexibleFormDefaultSection]}
+ mb={4}
+ size="lg"
+ variant="enclosed"
+ >
+ <FlexibleForm
+ flexibleFormDefaultSection={flexibleFormDefaultSection}
+ initialParamsDict={initialParamsDict}
+ />
+ <Accordion.Item key="advancedOptions" value="advancedOptions">
+ <Accordion.ItemTrigger cursor="button">Advanced Options</Accordion.ItemTrigger>
+ <Accordion.ItemContent>
+ <Box p={4}>
+ {children}
+ <Controller
+ control={control}
+ name={"conf" as Path<T>}
+ render={({ field }) => (
+ <Field.Root invalid={Boolean(errors.conf)} mt={6}>
+ <Field.Label fontSize="md">Configuration JSON</Field.Label>
+ <JsonEditor
+ {...field}
+ onBlur={() => {
+ field.onChange(validateAndPrettifyJson(field.value as string));
+ }}
+ />
+ {Boolean(errors.conf) ? <Field.ErrorText>{errors.conf}</Field.ErrorText> : undefined}
+ </Field.Root>
+ )}
+ />
+ </Box>
+ </Accordion.ItemContent>
+ </Accordion.Item>
+ </Accordion.Root>
+ );
+};
+
+export default ConfigForm;
diff --git a/airflow-core/src/airflow/ui/src/components/DagActions/RunBackfillForm.tsx b/airflow-core/src/airflow/ui/src/components/DagActions/RunBackfillForm.tsx
index 4ab35b2f466..2079de6c604 100644
--- a/airflow-core/src/airflow/ui/src/components/DagActions/RunBackfillForm.tsx
+++ b/airflow-core/src/airflow/ui/src/components/DagActions/RunBackfillForm.tsx
@@ -25,11 +25,15 @@ import { Button } from "src/components/ui";
import { reprocessBehaviors } from "src/constants/reprocessBehaviourParams";
import { useCreateBackfill } from "src/queries/useCreateBackfill";
import { useCreateBackfillDryRun } from "src/queries/useCreateBackfillDryRun";
+import { useDagParams } from "src/queries/useDagParams";
+import { useParamStore } from "src/queries/useParamStore";
import { useTogglePause } from "src/queries/useTogglePause";
import { pluralize } from "src/utils";
+import ConfigForm from "../ConfigForm";
import { DateTimeInput } from "../DateTimeInput";
import { ErrorAlert } from "../ErrorAlert";
+import type { DagRunTriggerParams } from "../TriggerDag/TriggerDAGForm";
import { Checkbox } from "../ui/Checkbox";
import { RadioCardItem, RadioCardLabel, RadioCardRoot } from "../ui/RadioCard";
@@ -39,14 +43,17 @@ type RunBackfillFormProps = {
};
const today = new Date().toISOString().slice(0, 16);
+type BackfillFormProps = DagRunTriggerParams & Omit<BackfillPostBody, "dag_run_conf">;
+
const RunBackfillForm = ({ dag, onClose }: RunBackfillFormProps) => {
const [errors, setErrors] = useState<{ conf?: string; date?: unknown }>({});
const [unpause, setUnpause] = useState(true);
-
- const { control, handleSubmit, reset, watch } = useForm<BackfillPostBody>({
+ const initialParamsDict = useDagParams(dag.dag_id, true);
+ const { conf } = useParamStore();
+ const { control, handleSubmit, reset, watch } = useForm<BackfillFormProps>({
defaultValues: {
+ conf,
dag_id: dag.dag_id,
- dag_run_conf: {},
from_date: "",
max_active_runs: 1,
reprocess_behavior: "none",
@@ -55,7 +62,7 @@ const RunBackfillForm = ({ dag, onClose }: RunBackfillFormProps) => {
},
mode: "onBlur",
});
- const values = useWatch<BackfillPostBody>({
+ const values = useWatch<BackfillFormProps>({
control,
});
@@ -85,10 +92,16 @@ const RunBackfillForm = ({ dag, onClose }: RunBackfillFormProps) => {
}
}, [dateValidationError]);
+ useEffect(() => {
+ if (conf) {
+ reset({ conf });
+ }
+ }, [conf, reset]);
+
const dataIntervalStart = watch("from_date");
const dataIntervalEnd = watch("to_date");
- const onSubmit = (fdata: BackfillPostBody) => {
+ const onSubmit = (fdata: BackfillFormProps) => {
if (unpause && dag.is_paused) {
togglePause({
dagId: dag.dag_id,
@@ -98,11 +111,14 @@ const RunBackfillForm = ({ dag, onClose }: RunBackfillFormProps) => {
});
}
createBackfill({
- requestBody: fdata,
+ requestBody: {
+ ...fdata,
+ dag_run_conf: JSON.parse(fdata.conf) as Record<string, unknown>,
+ },
});
};
- const onCancel = (fdata: BackfillPostBody) => {
+ const onCancel = (fdata: BackfillFormProps) => {
reset(fdata);
onClose();
};
@@ -237,6 +253,13 @@ const RunBackfillForm = ({ dag, onClose }: RunBackfillFormProps) => {
<Spacer />
</>
) : undefined}
+
+ <ConfigForm
+ control={control}
+ errors={errors}
+ initialParamsDict={initialParamsDict}
+ setErrors={setErrors}
+ />
</VStack>
<Box as="footer" display="flex" justifyContent="flex-end" mt={4}>
<HStack w="full">
diff --git a/airflow-core/src/airflow/ui/src/components/TriggerDag/TriggerDAGForm.tsx b/airflow-core/src/airflow/ui/src/components/TriggerDag/TriggerDAGForm.tsx
index b3d470ba417..fdeae4bdb69 100644
--- a/airflow-core/src/airflow/ui/src/components/TriggerDag/TriggerDAGForm.tsx
+++ b/airflow-core/src/airflow/ui/src/components/TriggerDag/TriggerDAGForm.tsx
@@ -16,10 +16,10 @@
* specific language governing permissions and limitations
* under the License.
*/
-import { Input, Button, Box, Spacer, HStack, Field, Stack } from "@chakra-ui/react";
+import { Button, Box, Spacer, HStack, Input, Field, Stack } from "@chakra-ui/react";
import dayjs from "dayjs";
import { useEffect, useState } from "react";
-import { useForm, Controller } from "react-hook-form";
+import { Controller, useForm } from "react-hook-form";
import { FiPlay } from "react-icons/fi";
import { useDagParams } from "src/queries/useDagParams";
@@ -27,11 +27,9 @@ import { useParamStore } from "src/queries/useParamStore";
import { useTogglePause } from "src/queries/useTogglePause";
import { useTrigger } from "src/queries/useTrigger";
+import ConfigForm from "../ConfigForm";
import { DateTimeInput } from "../DateTimeInput";
import { ErrorAlert } from "../ErrorAlert";
-import { FlexibleForm, flexibleFormDefaultSection } from "../FlexibleForm";
-import { JsonEditor } from "../JsonEditor";
-import { Accordion } from "../ui";
import { Checkbox } from "../ui/Checkbox";
import EditableMarkdown from "./EditableMarkdown";
@@ -53,7 +51,7 @@ const TriggerDAGForm = ({ dagId, isPaused, onClose, open }: TriggerDAGFormProps)
const [errors, setErrors] = useState<{ conf?: string; date?: unknown }>({});
const initialParamsDict = useDagParams(dagId, open);
const { error: errorTrigger, isPending, triggerDagRun } = useTrigger({ dagId, onSuccessConfirm: onClose });
- const { conf, setConf } = useParamStore();
+ const { conf } = useParamStore();
const [unpause, setUnpause] = useState(true);
const { mutate: togglePause } = useTogglePause({ dagId });
@@ -75,6 +73,10 @@ const TriggerDAGForm = ({ dagId, isPaused, onClose, open }: TriggerDAGFormProps)
}
}, [conf, reset]);
+ const resetDateError = () => {
+ setErrors((prev) => ({ ...prev, date: undefined }));
+ };
+
const onSubmit = (data: DagRunTriggerParams) => {
if (unpause && isPaused) {
togglePause({
@@ -87,119 +89,59 @@ const TriggerDAGForm = ({ dagId, isPaused, onClose, open }: TriggerDAGFormProps)
triggerDagRun(data);
};
- const validateAndPrettifyJson = (value: string) => {
- try {
- const parsedJson = JSON.parse(value) as JSON;
-
- setErrors((prev) => ({ ...prev, conf: undefined }));
-
- const formattedJson = JSON.stringify(parsedJson, undefined, 2);
-
- if (formattedJson !== conf) {
- setConf(formattedJson); // Update only if the value is different
- }
-
- return formattedJson;
- } catch (error) {
- const errorMessage = error instanceof Error ? error.message : "Unknown error occurred.";
-
- setErrors((prev) => ({
- ...prev,
- conf: `Invalid JSON format: ${errorMessage}`,
- }));
-
- return value;
- }
- };
-
- const resetDateError = () => {
- setErrors((prev) => ({ ...prev, date: undefined }));
- };
-
return (
- <>
- <Accordion.Root
- collapsible
- defaultValue={[flexibleFormDefaultSection]}
- mb={4}
- mt={8}
- size="lg"
- variant="enclosed"
+ <Box mt={8}>
+ <ConfigForm
+ control={control}
+ errors={errors}
+ initialParamsDict={initialParamsDict}
+ setErrors={setErrors}
>
- <FlexibleForm
- flexibleFormDefaultSection={flexibleFormDefaultSection}
- initialParamsDict={initialParamsDict}
+ <Controller
+ control={control}
+ name="logicalDate"
+ render={({ field }) => (
+ <Field.Root invalid={Boolean(errors.date)} orientation="horizontal">
+ <Stack>
+ <Field.Label fontSize="md" style={{ flexBasis: "30%" }}>
+ Logical Date
+ </Field.Label>
+ </Stack>
+ <Stack css={{ flexBasis: "70%" }}>
+ <DateTimeInput {...field} onBlur={resetDateError} size="sm" />
+ </Stack>
+ </Field.Root>
+ )}
/>
- <Accordion.Item key="advancedOptions" value="advancedOptions">
- <Accordion.ItemTrigger cursor="button">Advanced Options</Accordion.ItemTrigger>
- <Accordion.ItemContent>
- <Box p={4}>
- <Controller
- control={control}
- name="logicalDate"
- render={({ field }) => (
- <Field.Root invalid={Boolean(errors.date)} orientation="horizontal">
- <Stack>
- <Field.Label fontSize="md" style={{ flexBasis: "30%" }}>
- Logical Date
- </Field.Label>
- </Stack>
- <Stack css={{ flexBasis: "70%" }}>
- <DateTimeInput {...field} onBlur={resetDateError} size="sm" />
- </Stack>
- </Field.Root>
- )}
- />
-
- <Controller
- control={control}
- name="dagRunId"
- render={({ field }) => (
- <Field.Root mt={6} orientation="horizontal">
- <Stack>
- <Field.Label fontSize="md" style={{ flexBasis: "30%" }}>
- Run ID
- </Field.Label>
- </Stack>
- <Stack css={{ flexBasis: "70%" }}>
- <Input {...field} size="sm" />
- <Field.HelperText>Optional - will be generated if not provided</Field.HelperText>
- </Stack>
- </Field.Root>
- )}
- />
-
- <Controller
- control={control}
- name="conf"
- render={({ field }) => (
- <Field.Root invalid={Boolean(errors.conf)} mt={6}>
- <Field.Label fontSize="md">Configuration JSON</Field.Label>
- <JsonEditor
- {...field}
- onBlur={() => {
- field.onChange(validateAndPrettifyJson(field.value));
- }}
- />
- {Boolean(errors.conf) ? <Field.ErrorText>{errors.conf}</Field.ErrorText> : undefined}
- </Field.Root>
- )}
- />
- <Controller
- control={control}
- name="note"
- render={({ field }) => (
- <Field.Root mt={6}>
- <Field.Label fontSize="md">Dag Run Notes</Field.Label>
- <EditableMarkdown field={field} placeholder="Click to add note" />
- </Field.Root>
- )}
- />
- </Box>
- </Accordion.ItemContent>
- </Accordion.Item>
- </Accordion.Root>
+ <Controller
+ control={control}
+ name="dagRunId"
+ render={({ field }) => (
+ <Field.Root mt={6} orientation="horizontal">
+ <Stack>
+ <Field.Label fontSize="md" style={{ flexBasis: "30%" }}>
+ Run ID
+ </Field.Label>
+ </Stack>
+ <Stack css={{ flexBasis: "70%" }}>
+ <Input {...field} size="sm" />
+ <Field.HelperText>Optional - will be generated if not provided</Field.HelperText>
+ </Stack>
+ </Field.Root>
+ )}
+ />
+ <Controller
+ control={control}
+ name="note"
+ render={({ field }) => (
+ <Field.Root mt={6}>
+ <Field.Label fontSize="md">Dag Run Notes</Field.Label>
+ <EditableMarkdown field={field} placeholder="Click to add note" />
+ </Field.Root>
+ )}
+ />
+ </ConfigForm>
{isPaused ? (
<Checkbox checked={unpause} colorPalette="blue" onChange={() => setUnpause(!unpause)}>
Unpause {dagId} on trigger
@@ -218,7 +160,7 @@ const TriggerDAGForm = ({ dagId, isPaused, onClose, open }: TriggerDAGFormProps)
</Button>
</HStack>
</Box>
- </>
+ </Box>
);
};