This is an automated email from the ASF dual-hosted git repository.

bbovenzi pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git


The following commit(s) were added to refs/heads/main by this push:
     new 8f05f99e0ed Creating run backfill (#46348)
8f05f99e0ed is described below

commit 8f05f99e0ed28293c6755c6eadca6eb66c14af46
Author: Aritra Basu <[email protected]>
AuthorDate: Thu Feb 13 19:30:30 2025 +0530

    Creating run backfill (#46348)
    
    * WIP: Creating run backfill
    
    * ui changes, some temp code to be filled in
    
    * Fixing up a bunch of UI
    
    Starting to call the APIs still has some issues
    
    * remove null note in ActionAccordion
    
    * Updated UI
    
    Added an alert for number of backfills, fixed the dryrun request,
    fixed up a bit of the UI.
    
    * Resolved review comments
---
 .../components/ActionAccordion/ActionAccordion.tsx |  73 +++----
 airflow/ui/src/components/Menu/MenuButton.tsx      |  44 +++++
 .../ui/src/components/Menu/RunBackfillButton.tsx   |  44 +++++
 airflow/ui/src/components/Menu/RunBackfillForm.tsx | 217 +++++++++++++++++++++
 .../ui/src/components/Menu/RunBackfillModal.tsx    |  47 +++++
 .../ui/src/constants/reprocessBehaviourParams.ts   |  23 +++
 airflow/ui/src/pages/Dag/Header.tsx                |   2 +
 airflow/ui/src/queries/useCreateBackfill.ts        |  86 ++++++++
 airflow/ui/src/queries/useCreateBackfillDryRun.ts  |  42 ++++
 9 files changed, 544 insertions(+), 34 deletions(-)

diff --git a/airflow/ui/src/components/ActionAccordion/ActionAccordion.tsx 
b/airflow/ui/src/components/ActionAccordion/ActionAccordion.tsx
index 3995b789308..cc00a5495fc 100644
--- a/airflow/ui/src/components/ActionAccordion/ActionAccordion.tsx
+++ b/airflow/ui/src/components/ActionAccordion/ActionAccordion.tsx
@@ -29,13 +29,14 @@ import { columns } from "./columns";
 type Props = {
   readonly affectedTasks?: TaskInstanceCollectionResponse;
   readonly note: DAGRunResponse["note"];
-  readonly setNote: (value: string) => void;
+  readonly setNote: ((value: string) => void) | undefined;
 };
 
 // Table is in memory, pagination and sorting are disabled.
 // TODO: Make a front-end only unconnected table component with client side 
ordering and pagination
 const ActionAccordion = ({ affectedTasks, note, setNote }: Props) => {
   const showTaskSection = affectedTasks !== undefined;
+  const showNoteSection = note !== null;
 
   return (
     <Accordion.Root
@@ -62,40 +63,44 @@ const ActionAccordion = ({ affectedTasks, note, setNote }: 
Props) => {
           </Accordion.ItemContent>
         </Accordion.Item>
       ) : undefined}
-      <Accordion.Item key="note" value="note">
-        <Accordion.ItemTrigger>
-          <Text fontWeight="bold">Note</Text>
-        </Accordion.ItemTrigger>
-        <Accordion.ItemContent>
-          <Editable.Root
-            onChange={(event: ChangeEvent<HTMLInputElement>) => 
setNote(event.target.value)}
-            value={note ?? ""}
-          >
-            <Editable.Preview
-              _hover={{ backgroundColor: "transparent" }}
-              alignItems="flex-start"
-              as={VStack}
-              gap="0"
-              height="200px"
-              overflowY="auto"
-              width="100%"
+      {showNoteSection ? (
+        <Accordion.Item key="note" value="note">
+          <Accordion.ItemTrigger>
+            <Text fontWeight="bold">Note</Text>
+          </Accordion.ItemTrigger>
+          <Accordion.ItemContent>
+            <Editable.Root
+              onChange={(event: ChangeEvent<HTMLInputElement>) =>
+                setNote ? setNote(event.target.value) : undefined
+              }
+              value={note}
             >
-              {Boolean(note) ? (
-                <ReactMarkdown>{note}</ReactMarkdown>
-              ) : (
-                <Text color="fg.subtle">Add a note...</Text>
-              )}
-            </Editable.Preview>
-            <Editable.Textarea
-              data-testid="notes-input"
-              height="200px"
-              overflowY="auto"
-              placeholder="Add a note..."
-              resize="none"
-            />
-          </Editable.Root>
-        </Accordion.ItemContent>
-      </Accordion.Item>
+              <Editable.Preview
+                _hover={{ backgroundColor: "transparent" }}
+                alignItems="flex-start"
+                as={VStack}
+                gap="0"
+                height="200px"
+                overflowY="auto"
+                width="100%"
+              >
+                {Boolean(note) ? (
+                  <ReactMarkdown>{note}</ReactMarkdown>
+                ) : (
+                  <Text color="fg.subtle">Add a note...</Text>
+                )}
+              </Editable.Preview>
+              <Editable.Textarea
+                data-testid="notes-input"
+                height="200px"
+                overflowY="auto"
+                placeholder="Add a note..."
+                resize="none"
+              />
+            </Editable.Root>
+          </Accordion.ItemContent>
+        </Accordion.Item>
+      ) : undefined}
     </Accordion.Root>
   );
 };
diff --git a/airflow/ui/src/components/Menu/MenuButton.tsx 
b/airflow/ui/src/components/Menu/MenuButton.tsx
new file mode 100644
index 00000000000..261bf13bfd7
--- /dev/null
+++ b/airflow/ui/src/components/Menu/MenuButton.tsx
@@ -0,0 +1,44 @@
+/*!
+ * 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 { MdMoreHoriz } from "react-icons/md";
+
+import type { DAGResponse, DAGWithLatestDagRunsResponse } from 
"openapi/requests/types.gen";
+import { Menu } from "src/components/ui";
+
+import ActionButton from "../ui/ActionButton";
+import RunBackfillButton from "./RunBackfillButton";
+
+type Props = {
+  readonly dag: DAGResponse | DAGWithLatestDagRunsResponse;
+};
+
+const MenuButton: React.FC<Props> = ({ dag }) => (
+  <Menu.Root positioning={{ placement: "bottom" }}>
+    <Menu.Trigger asChild>
+      <ActionButton actionName="" icon={<MdMoreHoriz />} text="" />
+    </Menu.Trigger>
+    <Menu.Content>
+      <Menu.Item value="Run Backfill">
+        <RunBackfillButton dag={dag} />
+      </Menu.Item>
+    </Menu.Content>
+  </Menu.Root>
+);
+
+export default MenuButton;
diff --git a/airflow/ui/src/components/Menu/RunBackfillButton.tsx 
b/airflow/ui/src/components/Menu/RunBackfillButton.tsx
new file mode 100644
index 00000000000..e4749a7b509
--- /dev/null
+++ b/airflow/ui/src/components/Menu/RunBackfillButton.tsx
@@ -0,0 +1,44 @@
+/*!
+ * 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 { Box } from "@chakra-ui/react";
+import { useDisclosure } from "@chakra-ui/react";
+
+import type { DAGResponse, DAGWithLatestDagRunsResponse } from 
"openapi/requests/types.gen";
+
+import { Button } from "../ui";
+import RunBackfillModal from "./RunBackfillModal";
+
+type Props = {
+  readonly dag: DAGResponse | DAGWithLatestDagRunsResponse;
+};
+
+const RunBackfillButton: React.FC<Props> = ({ dag }) => {
+  const { onClose, onOpen, open } = useDisclosure();
+
+  return (
+    <Box>
+      <Button aria-label="Run Backfill" border="none" height={5} 
onClick={onOpen} variant="ghost">
+        Run Backfill
+      </Button>
+      <RunBackfillModal dag={dag} onClose={onClose} open={open} />
+    </Box>
+  );
+};
+
+export default RunBackfillButton;
diff --git a/airflow/ui/src/components/Menu/RunBackfillForm.tsx 
b/airflow/ui/src/components/Menu/RunBackfillForm.tsx
new file mode 100644
index 00000000000..063b1c8e292
--- /dev/null
+++ b/airflow/ui/src/components/Menu/RunBackfillForm.tsx
@@ -0,0 +1,217 @@
+/*!
+ * 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 { Input, Box, Spacer, HStack, Field, VStack, Flex, Text } from 
"@chakra-ui/react";
+import { useEffect, useState } from "react";
+import { useForm, Controller, useWatch } from "react-hook-form";
+
+import type { DAGResponse, DAGWithLatestDagRunsResponse, BackfillPostBody } 
from "openapi/requests/types.gen";
+import { Alert, Button } from "src/components/ui";
+import { reprocessBehaviors } from "src/constants/reprocessBehaviourParams";
+import { useCreateBackfill } from "src/queries/useCreateBackfill";
+import { useCreateBackfillDryRun } from "src/queries/useCreateBackfillDryRun";
+
+import { ErrorAlert } from "../ErrorAlert";
+import { Checkbox } from "../ui/Checkbox";
+import { RadioCardItem, RadioCardLabel, RadioCardRoot } from "../ui/RadioCard";
+
+type RunBackfillFormProps = {
+  readonly dag: DAGResponse | DAGWithLatestDagRunsResponse;
+  readonly onClose: () => void;
+};
+const today = new Date().toISOString().slice(0, 16);
+
+const RunBackfillForm = ({ dag, onClose }: RunBackfillFormProps) => {
+  const [errors, setErrors] = useState<{ conf?: string; date?: unknown }>({});
+
+  const { control, handleSubmit, reset, watch } = useForm<BackfillPostBody>({
+    defaultValues: {
+      dag_id: dag.dag_id,
+      dag_run_conf: {},
+      from_date: undefined,
+      max_active_runs: undefined,
+      reprocess_behavior: undefined,
+      run_backwards: undefined,
+      to_date: undefined,
+    },
+    mode: "onBlur",
+  });
+  const values = useWatch<BackfillPostBody>({
+    control,
+  });
+
+  const { data, isPending: isPendingDryRun } = useCreateBackfillDryRun({
+    requestBody: {
+      requestBody: {
+        dag_id: dag.dag_id,
+        dag_run_conf: undefined,
+        from_date: values.from_date ?? today,
+        max_active_runs: values.max_active_runs ?? 0,
+        reprocess_behavior: values.reprocess_behavior,
+        run_backwards: values.run_backwards ?? false,
+        to_date: values.to_date ?? today,
+      },
+    },
+  });
+
+  const { createBackfill, dateValidationError, error, isPending } = 
useCreateBackfill({
+    onSuccessConfirm: onClose,
+  });
+
+  useEffect(() => {
+    if (Boolean(dateValidationError)) {
+      setErrors((prev) => ({ ...prev, date: dateValidationError }));
+    }
+  }, [dateValidationError]);
+
+  const dataIntervalStart = watch("from_date");
+  const dataIntervalEnd = watch("to_date");
+
+  const onSubmit = (fdata: BackfillPostBody) => {
+    createBackfill({
+      requestBody: fdata,
+    });
+  };
+
+  const onCancel = (fdata: BackfillPostBody) => {
+    reset(fdata);
+    onClose();
+  };
+
+  const resetDateError = () => {
+    setErrors((prev) => ({ ...prev, date: undefined }));
+  };
+
+  const affectedTasks = data ?? {
+    backfills: [],
+    total_entries: 0,
+  };
+
+  return (
+    <>
+      <VStack alignItems="stretch" gap={2} p={10}>
+        <Box>
+          <Text fontSize="md" fontWeight="medium" mb={1}>
+            Date Range
+          </Text>
+          <HStack w="full">
+            <Controller
+              control={control}
+              name="from_date"
+              render={({ field }) => (
+                <Field.Root invalid={Boolean(errors.date)}>
+                  <Input
+                    {...field}
+                    max={dataIntervalEnd || today}
+                    onBlur={resetDateError}
+                    size="sm"
+                    type="datetime-local"
+                  />
+                </Field.Root>
+              )}
+            />
+            <Controller
+              control={control}
+              name="to_date"
+              render={({ field }) => (
+                <Field.Root invalid={Boolean(errors.date)}>
+                  <Input
+                    {...field}
+                    max={today}
+                    min={dataIntervalStart || undefined}
+                    onBlur={resetDateError}
+                    size="sm"
+                    type="datetime-local"
+                  />
+                </Field.Root>
+              )}
+            />
+          </HStack>
+        </Box>
+        <Spacer />
+        <Controller
+          control={control}
+          name="reprocess_behavior"
+          render={({ field }) => (
+            <RadioCardRoot
+              defaultValue={field.value}
+              onChange={(event) => {
+                field.onChange(event);
+              }}
+            >
+              <RadioCardLabel fontSize="md">Reprocess 
Behaviour</RadioCardLabel>
+              <HStack>
+                {reprocessBehaviors.map((item) => (
+                  <RadioCardItem
+                    colorPalette="blue"
+                    indicatorPlacement="start"
+                    key={item.value}
+                    label={item.label}
+                    value={item.value}
+                  />
+                ))}
+              </HStack>
+            </RadioCardRoot>
+          )}
+        />
+        <Spacer />
+        <Controller
+          control={control}
+          name="run_backwards"
+          render={({ field }) => (
+            <Checkbox checked={field.value} colorPalette="blue" 
onChange={field.onChange}>
+              Run Backwards
+            </Checkbox>
+          )}
+        />
+        <Spacer />
+        <Controller
+          control={control}
+          name="max_active_runs"
+          render={({ field }) => (
+            <HStack>
+              <Input {...field} placeholder="" type="number" width={24} />
+              <Flex>Max Active Runs</Flex>
+            </HStack>
+          )}
+        />
+        <Spacer />
+        {affectedTasks.total_entries > 0 ? (
+          <Alert>{affectedTasks.total_entries} runs will be triggered</Alert>
+        ) : undefined}
+      </VStack>
+      <ErrorAlert error={errors.date ?? error} />
+      <Box as="footer" display="flex" justifyContent="flex-end" mt={4}>
+        <HStack w="full">
+          <Spacer />
+          <Button onClick={() => void handleSubmit(onCancel)()}>Cancel</Button>
+          <Button
+            colorPalette="blue"
+            disabled={Boolean(errors.date) || isPendingDryRun || 
affectedTasks.total_entries === 0}
+            loading={isPending}
+            onClick={() => void handleSubmit(onSubmit)()}
+          >
+            Run Backfill
+          </Button>
+        </HStack>
+      </Box>
+    </>
+  );
+};
+
+export default RunBackfillForm;
diff --git a/airflow/ui/src/components/Menu/RunBackfillModal.tsx 
b/airflow/ui/src/components/Menu/RunBackfillModal.tsx
new file mode 100644
index 00000000000..210bcf4407d
--- /dev/null
+++ b/airflow/ui/src/components/Menu/RunBackfillModal.tsx
@@ -0,0 +1,47 @@
+/*!
+ * 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 { Heading } from "@chakra-ui/react";
+import React from "react";
+
+import type { DAGResponse, DAGWithLatestDagRunsResponse } from 
"openapi/requests/types.gen";
+import { Dialog } from "src/components/ui";
+
+import RunBackfillForm from "./RunBackfillForm";
+
+type RunBackfillModalProps = {
+  dag: DAGResponse | DAGWithLatestDagRunsResponse;
+  onClose: () => void;
+  open: boolean;
+};
+
+const RunBackfillModal: React.FC<RunBackfillModalProps> = ({ dag, onClose, 
open }) => (
+  <Dialog.Root lazyMount onOpenChange={onClose} open={open} size="xl" 
unmountOnExit>
+    <Dialog.Content backdrop>
+      <Dialog.Header bg="blue.muted">
+        <Heading size="xl">Run Backfill</Heading>
+      </Dialog.Header>
+      <Dialog.CloseTrigger />
+      <Dialog.Body>
+        <RunBackfillForm dag={dag} onClose={onClose} />
+      </Dialog.Body>
+    </Dialog.Content>
+  </Dialog.Root>
+);
+
+export default RunBackfillModal;
diff --git a/airflow/ui/src/constants/reprocessBehaviourParams.ts 
b/airflow/ui/src/constants/reprocessBehaviourParams.ts
new file mode 100644
index 00000000000..36649eceb52
--- /dev/null
+++ b/airflow/ui/src/constants/reprocessBehaviourParams.ts
@@ -0,0 +1,23 @@
+/*!
+ * 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.
+ */
+export const reprocessBehaviors = [
+  { label: "Missing Runs", value: "failed" },
+  { label: "Missing and Errored Runs", value: "completed" },
+  { label: "All Runs", value: "none" },
+];
diff --git a/airflow/ui/src/pages/Dag/Header.tsx 
b/airflow/ui/src/pages/Dag/Header.tsx
index bc4b295d643..618bc8a4f66 100644
--- a/airflow/ui/src/pages/Dag/Header.tsx
+++ b/airflow/ui/src/pages/Dag/Header.tsx
@@ -24,6 +24,7 @@ import type { DAGDetailsResponse, 
DAGWithLatestDagRunsResponse } from "openapi/r
 import { DagIcon } from "src/assets/DagIcon";
 import DagRunInfo from "src/components/DagRunInfo";
 import DisplayMarkdownButton from "src/components/DisplayMarkdownButton";
+import MenuButton from "src/components/Menu/MenuButton";
 import ParseDag from "src/components/ParseDag";
 import { Stat } from "src/components/Stat";
 import { TogglePause } from "src/components/TogglePause";
@@ -74,6 +75,7 @@ export const Header = ({
                 )}
                 <ParseDag dagId={dag.dag_id} fileToken={dag.file_token} />
                 <TriggerDAGButton dag={dag} />
+                <MenuButton dag={dag} />
               </HStack>
             ) : undefined}
           </Flex>
diff --git a/airflow/ui/src/queries/useCreateBackfill.ts 
b/airflow/ui/src/queries/useCreateBackfill.ts
new file mode 100644
index 00000000000..2a96ecd84d8
--- /dev/null
+++ b/airflow/ui/src/queries/useCreateBackfill.ts
@@ -0,0 +1,86 @@
+/*!
+ * 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 { useState } from "react";
+
+import { useBackfillServiceCreateBackfill } from "openapi/queries";
+import type { CreateBackfillData } from "openapi/requests/types.gen";
+import { toaster } from "src/components/ui";
+
+export const useCreateBackfill = ({ onSuccessConfirm }: { onSuccessConfirm: () 
=> void }) => {
+  const [dateValidationError, setDateValidationError] = 
useState<unknown>(undefined);
+  const [error, setError] = useState<unknown>(undefined);
+
+  const onSuccess = () => {
+    toaster.create({
+      description: "Backfill jobs have been successfully triggered.",
+      title: "Backfill generated",
+      type: "success",
+    });
+    onSuccessConfirm();
+  };
+
+  const onError = (_error: unknown) => {
+    setError(_error);
+  };
+
+  const { isPending, mutate } = useBackfillServiceCreateBackfill({ onError, 
onSuccess });
+
+  const createBackfill = (data: CreateBackfillData) => {
+    const dataIntervalStart = new Date(data.requestBody.from_date);
+    const dataIntervalEnd = new Date(data.requestBody.to_date);
+    const dagId = data.requestBody.dag_id;
+
+    if (!Boolean(dataIntervalStart) || !Boolean(dataIntervalEnd)) {
+      setDateValidationError({
+        body: {
+          detail: "Both Data Interval Start Date and End Date must be 
provided.",
+        },
+      });
+
+      return;
+    }
+
+    if (dataIntervalStart > dataIntervalEnd) {
+      setDateValidationError({
+        body: {
+          detail: "Data Interval Start Date must be less than or equal to Data 
Interval End Date.",
+        },
+      });
+
+      return;
+    }
+
+    const formattedDataIntervalStart = dataIntervalStart.toISOString();
+    const formattedDataIntervalEnd = dataIntervalEnd.toISOString();
+
+    mutate({
+      requestBody: {
+        dag_id: dagId,
+        dag_run_conf: {},
+        from_date: formattedDataIntervalStart,
+        max_active_runs: data.requestBody.max_active_runs,
+        reprocess_behavior: data.requestBody.reprocess_behavior,
+        run_backwards: false,
+        to_date: formattedDataIntervalEnd,
+      },
+    });
+  };
+
+  return { createBackfill, dateValidationError, error, isPending };
+};
diff --git a/airflow/ui/src/queries/useCreateBackfillDryRun.ts 
b/airflow/ui/src/queries/useCreateBackfillDryRun.ts
new file mode 100644
index 00000000000..8c6e7c9dd51
--- /dev/null
+++ b/airflow/ui/src/queries/useCreateBackfillDryRun.ts
@@ -0,0 +1,42 @@
+/*!
+ * 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 { useQuery, type UseQueryOptions } from "@tanstack/react-query";
+
+import { BackfillService } from "openapi/requests/services.gen";
+import type { CreateBackfillDryRunData, DryRunBackfillCollectionResponse } 
from "openapi/requests/types.gen";
+
+type Props<TData, TError> = {
+  options?: Omit<UseQueryOptions<TData, TError>, "queryFn" | "queryKey">;
+  requestBody: CreateBackfillDryRunData;
+};
+
+const useCreateBackfillDryRunKey = "useCreateBackfillDryRunKey";
+
+export const useCreateBackfillDryRun = <TData = 
DryRunBackfillCollectionResponse, TError = unknown>({
+  options,
+  requestBody,
+}: Props<TData, TError>) =>
+  useQuery<TData, TError>({
+    ...options,
+    queryFn: () =>
+      BackfillService.createBackfillDryRun({
+        ...requestBody,
+      }) as TData,
+    queryKey: [useCreateBackfillDryRunKey, requestBody],
+  });

Reply via email to