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 <[email protected]>
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 <[email protected]>
    
    * fix: add missing conf preset
    
    Co-authored-by: Brent Bovenzi <[email protected]>
    
    ---------
    
    Co-authored-by: Brent Bovenzi <[email protected]>
    (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>
   );
 };
 

Reply via email to