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],
+ });