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 f5f5200a909 Fix run task/run clearing (#45987)
f5f5200a909 is described below

commit f5f5200a909af434e4f31a2aa03567f051e4438c
Author: Brent Bovenzi <[email protected]>
AuthorDate: Fri Jan 24 11:46:17 2025 -0500

    Fix run task/run clearing (#45987)
    
    * Fix clear modals
    
    * Reduce clear task/run API calls
    
    * Fix and add tests to datatable
---
 airflow/ui/src/components/Clear/ClearAccordion.tsx |  2 +-
 .../ui/src/components/Clear/Run/ClearRunButton.tsx | 28 +------
 .../ui/src/components/Clear/Run/ClearRunDialog.tsx | 70 +++++++++--------
 .../Clear/TaskInstance/ClearTaskInstanceButton.tsx | 28 +------
 .../Clear/TaskInstance/ClearTaskInstanceDialog.tsx | 87 +++++++++++-----------
 .../ui/src/components/DataTable/DataTable.test.tsx | 22 +++++-
 airflow/ui/src/components/DataTable/DataTable.tsx  |  5 +-
 airflow/ui/src/components/ui/Button.tsx            |  2 +-
 airflow/ui/src/layouts/Details/DagVizModal.tsx     |  2 +-
 airflow/ui/src/queries/useClearDagRunDryRun.ts     | 49 ++++++++++++
 airflow/ui/src/queries/useClearRun.ts              | 32 +++-----
 airflow/ui/src/queries/useClearTaskInstances.ts    | 49 +++++-------
 .../ui/src/queries/useClearTaskInstancesDryRun.ts  | 56 ++++++++++++++
 13 files changed, 247 insertions(+), 185 deletions(-)

diff --git a/airflow/ui/src/components/Clear/ClearAccordion.tsx 
b/airflow/ui/src/components/Clear/ClearAccordion.tsx
index 74be262da75..8c5bc7e9184 100644
--- a/airflow/ui/src/components/Clear/ClearAccordion.tsx
+++ b/airflow/ui/src/components/Clear/ClearAccordion.tsx
@@ -35,7 +35,7 @@ type Props = {
 // 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 ClearAccordion = ({ affectedTasks, note, setNote }: Props) => (
-  <Accordion.Root collapsible defaultValue={["note"]} multiple={false} 
variant="enclosed">
+  <Accordion.Root collapsible defaultValue={["tasks"]} multiple={false} 
variant="enclosed">
     <Accordion.Item key="tasks" value="tasks">
       <Accordion.ItemTrigger>
         <Text fontWeight="bold">Affected Tasks: {affectedTasks?.total_entries 
?? 0}</Text>
diff --git a/airflow/ui/src/components/Clear/Run/ClearRunButton.tsx 
b/airflow/ui/src/components/Clear/Run/ClearRunButton.tsx
index 151e2f3bd9e..30a3f2aa3df 100644
--- a/airflow/ui/src/components/Clear/Run/ClearRunButton.tsx
+++ b/airflow/ui/src/components/Clear/Run/ClearRunButton.tsx
@@ -17,12 +17,10 @@
  * under the License.
  */
 import { Box, useDisclosure } from "@chakra-ui/react";
-import { useState } from "react";
 import { FiRefreshCw } from "react-icons/fi";
 
-import type { DAGRunResponse, TaskInstanceCollectionResponse } from 
"openapi/requests/types.gen";
+import type { DAGRunResponse } from "openapi/requests/types.gen";
 import ActionButton from "src/components/ui/ActionButton";
-import { useClearDagRun } from "src/queries/useClearRun";
 
 import ClearRunDialog from "./ClearRunDialog";
 
@@ -34,21 +32,6 @@ type Props = {
 const ClearRunButton = ({ dagRun, withText = true }: Props) => {
   const { onClose, onOpen, open } = useDisclosure();
 
-  const [affectedTasks, setAffectedTasks] = 
useState<TaskInstanceCollectionResponse>({
-    task_instances: [],
-    total_entries: 0,
-  });
-
-  const dagId = dagRun.dag_id;
-  const dagRunId = dagRun.dag_run_id;
-
-  const { isPending, mutate } = useClearDagRun({
-    dagId,
-    dagRunId,
-    onSuccessConfirm: onClose,
-    onSuccessDryRun: setAffectedTasks,
-  });
-
   return (
     <Box>
       <ActionButton
@@ -59,14 +42,7 @@ const ClearRunButton = ({ dagRun, withText = true }: Props) 
=> {
         withText={withText}
       />
 
-      <ClearRunDialog
-        affectedTasks={affectedTasks}
-        dagRun={dagRun}
-        isPending={isPending}
-        mutate={mutate}
-        onClose={onClose}
-        open={open}
-      />
+      <ClearRunDialog dagRun={dagRun} onClose={onClose} open={open} />
     </Box>
   );
 };
diff --git a/airflow/ui/src/components/Clear/Run/ClearRunDialog.tsx 
b/airflow/ui/src/components/Clear/Run/ClearRunDialog.tsx
index 4cb48669b55..47e9b95b47d 100644
--- a/airflow/ui/src/components/Clear/Run/ClearRunDialog.tsx
+++ b/airflow/ui/src/components/Clear/Run/ClearRunDialog.tsx
@@ -17,58 +17,59 @@
  * under the License.
  */
 import { Flex, Heading, VStack } from "@chakra-ui/react";
-import { useEffect, useState } from "react";
+import { useState } from "react";
 import { FiRefreshCw } from "react-icons/fi";
 
-import type {
-  DAGRunClearBody,
-  DAGRunResponse,
-  TaskInstanceCollectionResponse,
-} from "openapi/requests/types.gen";
+import type { DAGRunResponse } from "openapi/requests/types.gen";
 import { Button, Dialog } from "src/components/ui";
 import SegmentedControl from "src/components/ui/SegmentedControl";
+import { useClearDagRunDryRun } from "src/queries/useClearDagRunDryRun";
+import { useClearDagRun } from "src/queries/useClearRun";
 import { usePatchDagRun } from "src/queries/usePatchDagRun";
 
 import ClearAccordion from "../ClearAccordion";
 
 type Props = {
-  readonly affectedTasks: TaskInstanceCollectionResponse;
   readonly dagRun: DAGRunResponse;
-  readonly isPending: boolean;
-  readonly mutate: ({
-    dagId,
-    dagRunId,
-    requestBody,
-  }: {
-    dagId: string;
-    dagRunId: string;
-    requestBody: DAGRunClearBody;
-  }) => void;
   readonly onClose: () => void;
   readonly open: boolean;
 };
 
-const ClearRunDialog = ({ affectedTasks, dagRun, isPending, mutate, onClose, 
open }: Props) => {
+const ClearRunDialog = ({ dagRun, onClose, open }: Props) => {
   const [selectedOptions, setSelectedOptions] = useState<Array<string>>([]);
 
-  const onlyFailed = selectedOptions.includes("onlyFailed");
-
   const dagId = dagRun.dag_id;
   const dagRunId = dagRun.dag_run_id;
 
+  const { isPending, mutate } = useClearDagRun({
+    dagId,
+    dagRunId,
+    onSuccessConfirm: onClose,
+  });
+
+  const onlyFailed = selectedOptions.includes("onlyFailed");
+
   const [note, setNote] = useState<string | null>(dagRun.note);
   const { isPending: isPendingPatchDagRun, mutate: mutatePatchDagRun } = 
usePatchDagRun({ dagId, dagRunId });
 
-  useEffect(() => {
-    mutate({
-      dagId,
-      dagRunId,
-      requestBody: { dry_run: true, only_failed: onlyFailed },
-    });
-  }, [dagId, dagRunId, mutate, onlyFailed]);
+  const { data } = useClearDagRunDryRun({
+    dagId,
+    dagRunId,
+    options: {
+      enabled: open,
+    },
+    requestBody: {
+      only_failed: onlyFailed,
+    },
+  });
+
+  const affectedTasks = data ?? {
+    task_instances: [],
+    total_entries: 0,
+  };
 
   return (
-    <Dialog.Root onOpenChange={onClose} open={open} size="xl">
+    <Dialog.Root lazyMount onOpenChange={onClose} open={open} size="xl">
       <Dialog.Content backdrop>
         <Dialog.Header>
           <VStack align="start" gap={4}>
@@ -100,6 +101,7 @@ const ClearRunDialog = ({ affectedTasks, dagRun, isPending, 
mutate, onClose, ope
           <Flex justifyContent="end" mt={3}>
             <Button
               colorPalette="blue"
+              disabled={affectedTasks.total_entries === 0}
               loading={isPending || isPendingPatchDagRun}
               onClick={() => {
                 mutate({
@@ -107,11 +109,13 @@ const ClearRunDialog = ({ affectedTasks, dagRun, 
isPending, mutate, onClose, ope
                   dagRunId,
                   requestBody: { dry_run: false, only_failed: onlyFailed },
                 });
-                mutatePatchDagRun({
-                  dagId,
-                  dagRunId,
-                  requestBody: { note },
-                });
+                if (note !== dagRun.note) {
+                  mutatePatchDagRun({
+                    dagId,
+                    dagRunId,
+                    requestBody: { note },
+                  });
+                }
               }}
             >
               <FiRefreshCw /> Confirm
diff --git 
a/airflow/ui/src/components/Clear/TaskInstance/ClearTaskInstanceButton.tsx 
b/airflow/ui/src/components/Clear/TaskInstance/ClearTaskInstanceButton.tsx
index 8f4a6275667..fb39b45e0bd 100644
--- a/airflow/ui/src/components/Clear/TaskInstance/ClearTaskInstanceButton.tsx
+++ b/airflow/ui/src/components/Clear/TaskInstance/ClearTaskInstanceButton.tsx
@@ -17,12 +17,10 @@
  * under the License.
  */
 import { Box, useDisclosure } from "@chakra-ui/react";
-import { useState } from "react";
 import { FiRefreshCw } from "react-icons/fi";
 
-import type { TaskInstanceCollectionResponse, TaskInstanceResponse } from 
"openapi/requests/types.gen";
+import type { TaskInstanceResponse } from "openapi/requests/types.gen";
 import ActionButton from "src/components/ui/ActionButton";
-import { useClearTaskInstances } from "src/queries/useClearTaskInstances";
 
 import ClearTaskInstanceDialog from "./ClearTaskInstanceDialog";
 
@@ -34,21 +32,6 @@ type Props = {
 const ClearTaskInstanceButton = ({ taskInstance, withText = true }: Props) => {
   const { onClose, onOpen, open } = useDisclosure();
 
-  const [affectedTasks, setAffectedTasks] = 
useState<TaskInstanceCollectionResponse>({
-    task_instances: [],
-    total_entries: 0,
-  });
-
-  const dagId = taskInstance.dag_id;
-  const dagRunId = taskInstance.dag_run_id;
-
-  const { isPending, mutate } = useClearTaskInstances({
-    dagId,
-    dagRunId,
-    onSuccessConfirm: onClose,
-    onSuccessDryRun: setAffectedTasks,
-  });
-
   return (
     <Box>
       <ActionButton
@@ -59,14 +42,7 @@ const ClearTaskInstanceButton = ({ taskInstance, withText = 
true }: Props) => {
         withText={withText}
       />
 
-      <ClearTaskInstanceDialog
-        affectedTasks={affectedTasks}
-        isPending={isPending}
-        mutate={mutate}
-        onClose={onClose}
-        open={open}
-        taskInstance={taskInstance}
-      />
+      <ClearTaskInstanceDialog onClose={onClose} open={open} 
taskInstance={taskInstance} />
     </Box>
   );
 };
diff --git 
a/airflow/ui/src/components/Clear/TaskInstance/ClearTaskInstanceDialog.tsx 
b/airflow/ui/src/components/Clear/TaskInstance/ClearTaskInstanceDialog.tsx
index 45c5a1c2d0f..b2344bf90d2 100644
--- a/airflow/ui/src/components/Clear/TaskInstance/ClearTaskInstanceDialog.tsx
+++ b/airflow/ui/src/components/Clear/TaskInstance/ClearTaskInstanceDialog.tsx
@@ -17,43 +17,38 @@
  * under the License.
  */
 import { Flex, Heading, VStack } from "@chakra-ui/react";
-import { useEffect, useState } from "react";
+import { useState } from "react";
 import { FiRefreshCw } from "react-icons/fi";
 
-import type {
-  ClearTaskInstancesBody,
-  TaskInstanceCollectionResponse,
-  TaskInstanceResponse,
-} from "openapi/requests/types.gen";
+import type { TaskInstanceResponse } from "openapi/requests/types.gen";
 import Time from "src/components/Time";
 import { Button, Dialog } from "src/components/ui";
 import SegmentedControl from "src/components/ui/SegmentedControl";
+import { useClearTaskInstances } from "src/queries/useClearTaskInstances";
+import { useClearTaskInstancesDryRun } from 
"src/queries/useClearTaskInstancesDryRun";
 import { usePatchTaskInstance } from "src/queries/usePatchTaskInstance";
 
 import ClearAccordion from "../ClearAccordion";
 
 type Props = {
-  readonly affectedTasks: TaskInstanceCollectionResponse;
-  readonly isPending: boolean;
-  readonly mutate: ({ dagId, requestBody }: { dagId: string; requestBody: 
ClearTaskInstancesBody }) => void;
   readonly onClose: () => void;
   readonly open: boolean;
   readonly taskInstance: TaskInstanceResponse;
 };
 
-const ClearTaskInstanceDialog = ({
-  affectedTasks,
-  isPending,
-  mutate,
-  onClose,
-  open,
-  taskInstance,
-}: Props) => {
-  const dagId = taskInstance.dag_id;
-  const dagRunId = taskInstance.dag_run_id;
+const ClearTaskInstanceDialog = ({ onClose, open, taskInstance }: Props) => {
   const taskId = taskInstance.task_id;
   const mapIndex = taskInstance.map_index;
 
+  const dagId = taskInstance.dag_id;
+  const dagRunId = taskInstance.dag_run_id;
+
+  const { isPending, mutate } = useClearTaskInstances({
+    dagId,
+    dagRunId,
+    onSuccessConfirm: onClose,
+  });
+
   const [selectedOptions, setSelectedOptions] = useState<Array<string>>([]);
 
   const onlyFailed = selectedOptions.includes("onlyFailed");
@@ -70,24 +65,29 @@ const ClearTaskInstanceDialog = ({
     taskId,
   });
 
-  useEffect(() => {
-    mutate({
-      dagId,
-      requestBody: {
-        dag_run_id: dagRunId,
-        dry_run: true,
-        include_downstream: downstream,
-        include_future: future,
-        include_past: past,
-        include_upstream: upstream,
-        only_failed: onlyFailed,
-        task_ids: [taskId],
-      },
-    });
-  }, [dagId, dagRunId, downstream, future, mutate, onlyFailed, past, taskId, 
upstream]);
+  const { data } = useClearTaskInstancesDryRun({
+    dagId,
+    options: {
+      enabled: open,
+    },
+    requestBody: {
+      dag_run_id: dagRunId,
+      include_downstream: downstream,
+      include_future: future,
+      include_past: past,
+      include_upstream: upstream,
+      only_failed: onlyFailed,
+      task_ids: [taskId],
+    },
+  });
+
+  const affectedTasks = data ?? {
+    task_instances: [],
+    total_entries: 0,
+  };
 
   return (
-    <Dialog.Root onOpenChange={onClose} open={open} size="xl">
+    <Dialog.Root lazyMount onOpenChange={onClose} open={open} size="xl">
       <Dialog.Content backdrop>
         <Dialog.Header>
           <VStack align="start" gap={4}>
@@ -118,6 +118,7 @@ const ClearTaskInstanceDialog = ({
           <Flex justifyContent="end" mt={3}>
             <Button
               colorPalette="blue"
+              disabled={affectedTasks.total_entries === 0}
               loading={isPending || isPendingPatchDagRun}
               onClick={() => {
                 mutate({
@@ -133,13 +134,15 @@ const ClearTaskInstanceDialog = ({
                     task_ids: [taskId],
                   },
                 });
-                mutatePatchTaskInstance({
-                  dagId,
-                  dagRunId,
-                  mapIndex,
-                  requestBody: { note },
-                  taskId,
-                });
+                if (note !== taskInstance.note) {
+                  mutatePatchTaskInstance({
+                    dagId,
+                    dagRunId,
+                    mapIndex,
+                    requestBody: { note },
+                    taskId,
+                  });
+                }
               }}
             >
               <FiRefreshCw /> Confirm
diff --git a/airflow/ui/src/components/DataTable/DataTable.test.tsx 
b/airflow/ui/src/components/DataTable/DataTable.test.tsx
index 3d593869d85..e38bcc2de8a 100644
--- a/airflow/ui/src/components/DataTable/DataTable.test.tsx
+++ b/airflow/ui/src/components/DataTable/DataTable.test.tsx
@@ -67,7 +67,7 @@ describe("DataTable", () => {
     render(
       <DataTable
         columns={columns}
-        data={data}
+        data={[{ name: "John Doe" }]}
         initialState={{ pagination, sorting: [] }}
         onStateChange={onStateChange}
         total={2}
@@ -84,7 +84,7 @@ describe("DataTable", () => {
     render(
       <DataTable
         columns={columns}
-        data={data}
+        data={[{ name: "John Doe" }]}
         initialState={{
           pagination: { pageIndex: 0, pageSize: 10 },
           sorting: [],
@@ -100,6 +100,24 @@ describe("DataTable", () => {
     expect(screen.getByTestId("next")).toBeDisabled();
   });
 
+  it("renders no pagination when not needed", () => {
+    render(
+      <DataTable
+        columns={columns}
+        data={data}
+        initialState={{ pagination, sorting: [] }}
+        onStateChange={onStateChange}
+        total={2}
+      />,
+      {
+        wrapper: ChakraWrapper,
+      },
+    );
+
+    expect(screen.queryByTestId("prev")).toBeNull();
+    expect(screen.queryByTestId("next")).toBeNull();
+  });
+
   it("when isLoading renders skeleton columns", () => {
     render(<DataTable columns={columns} data={data} isLoading />, {
       wrapper: ChakraWrapper,
diff --git a/airflow/ui/src/components/DataTable/DataTable.tsx 
b/airflow/ui/src/components/DataTable/DataTable.tsx
index 4f02b57cb8a..86182edb0b8 100644
--- a/airflow/ui/src/components/DataTable/DataTable.tsx
+++ b/airflow/ui/src/components/DataTable/DataTable.tsx
@@ -116,6 +116,9 @@ export const DataTable = <TData,>({
 
   const display = displayMode === "card" && Boolean(cardDef) ? "card" : 
"table";
   const hasRows = rows.length > 0;
+  const hasPagination =
+    table.getState().pagination.pageIndex !== 0 ||
+    (table.getState().pagination.pageIndex === 0 && rows.length !== total);
 
   return (
     <>
@@ -127,7 +130,7 @@ export const DataTable = <TData,>({
         <CardList cardDef={cardDef} isLoading={isLoading} table={table} />
       ) : undefined}
       {!hasRows && !Boolean(isLoading) && <Text pt={1}>{noRowsMessage ?? `No 
${modelName}s found.`}</Text>}
-      {hasRows ? (
+      {hasPagination ? (
         <Pagination.Root
           count={table.getRowCount()}
           my={2}
diff --git a/airflow/ui/src/components/ui/Button.tsx 
b/airflow/ui/src/components/ui/Button.tsx
index d4d41836696..1e0372bc5a9 100644
--- a/airflow/ui/src/components/ui/Button.tsx
+++ b/airflow/ui/src/components/ui/Button.tsx
@@ -31,7 +31,7 @@ export const Button = React.forwardRef<HTMLButtonElement, 
ButtonProps>((props, r
   const { children, disabled, loading, loadingText, ...rest } = props;
 
   return (
-    <ChakraButton disabled={loading ?? disabled} ref={ref} {...rest}>
+    <ChakraButton disabled={disabled ?? loading} ref={ref} {...rest}>
       {loading && !Boolean(loadingText) ? (
         <>
           <AbsoluteCenter display="inline-flex">
diff --git a/airflow/ui/src/layouts/Details/DagVizModal.tsx 
b/airflow/ui/src/layouts/Details/DagVizModal.tsx
index 864be59ca5f..a4286c6f736 100644
--- a/airflow/ui/src/layouts/Details/DagVizModal.tsx
+++ b/airflow/ui/src/layouts/Details/DagVizModal.tsx
@@ -63,7 +63,7 @@ export const DagVizModal: React.FC<DAGVizModalProps> = ({ 
dagDisplayName, dagId,
   params.delete("modal");
 
   return (
-    <Dialog.Root motionPreset="none" onOpenChange={onClose} open={open} 
size="full">
+    <Dialog.Root lazyMount motionPreset="none" onOpenChange={onClose} 
open={open} size="full">
       <Dialog.Content backdrop ref={contentRef}>
         <Dialog.Header bg="blue.muted" pr={16}>
           <HStack>
diff --git a/airflow/ui/src/queries/useClearDagRunDryRun.ts 
b/airflow/ui/src/queries/useClearDagRunDryRun.ts
new file mode 100644
index 00000000000..a15099e69ec
--- /dev/null
+++ b/airflow/ui/src/queries/useClearDagRunDryRun.ts
@@ -0,0 +1,49 @@
+/*!
+ * 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 { DagRunService } from "openapi/requests/services.gen";
+import type { DAGRunClearBody, TaskInstanceCollectionResponse } from 
"openapi/requests/types.gen";
+
+type Props<TData, TError> = {
+  dagId: string;
+  dagRunId: string;
+  options?: Omit<UseQueryOptions<TData, TError>, "queryFn" | "queryKey">;
+  requestBody: DAGRunClearBody;
+};
+
+export const useClearDagRunDryRun = <TData = TaskInstanceCollectionResponse, 
TError = unknown>({
+  dagId,
+  dagRunId,
+  options,
+  requestBody,
+}: Props<TData, TError>) =>
+  useQuery<TData, TError>({
+    ...options,
+    queryFn: () =>
+      DagRunService.clearDagRun({
+        dagId,
+        dagRunId,
+        requestBody: {
+          dry_run: true,
+          ...requestBody,
+        },
+      }) as TData,
+    queryKey: ["clearDagRun", dagId, requestBody.only_failed],
+  });
diff --git a/airflow/ui/src/queries/useClearRun.ts 
b/airflow/ui/src/queries/useClearRun.ts
index 336b42e74d8..526662f0b3e 100644
--- a/airflow/ui/src/queries/useClearRun.ts
+++ b/airflow/ui/src/queries/useClearRun.ts
@@ -25,7 +25,6 @@ import {
   UseDagServiceGetDagDetailsKeyFn,
   useTaskInstanceServiceGetTaskInstancesKey,
 } from "openapi/queries";
-import type { DAGRunClearBody, TaskInstanceCollectionResponse } from 
"openapi/requests/types.gen";
 import { toaster } from "src/components/ui";
 
 const onError = () => {
@@ -40,37 +39,24 @@ export const useClearDagRun = ({
   dagId,
   dagRunId,
   onSuccessConfirm,
-  onSuccessDryRun,
 }: {
   dagId: string;
   dagRunId: string;
   onSuccessConfirm: () => void;
-  onSuccessDryRun: (date: TaskInstanceCollectionResponse) => void;
 }) => {
   const queryClient = useQueryClient();
 
-  const onSuccess = async (
-    data: TaskInstanceCollectionResponse,
-    variables: {
-      dagId: string;
-      dagRunId: string;
-      requestBody: DAGRunClearBody;
-    },
-  ) => {
-    if (variables.requestBody.dry_run) {
-      onSuccessDryRun(data);
-    } else {
-      const queryKeys = [
-        [useTaskInstanceServiceGetTaskInstancesKey],
-        UseDagServiceGetDagDetailsKeyFn({ dagId }),
-        UseDagRunServiceGetDagRunKeyFn({ dagId, dagRunId }),
-        [useDagRunServiceGetDagRunsKey],
-      ];
+  const onSuccess = async () => {
+    const queryKeys = [
+      [useTaskInstanceServiceGetTaskInstancesKey],
+      UseDagServiceGetDagDetailsKeyFn({ dagId }),
+      UseDagRunServiceGetDagRunKeyFn({ dagId, dagRunId }),
+      [useDagRunServiceGetDagRunsKey],
+    ];
 
-      await Promise.all(queryKeys.map((key) => queryClient.invalidateQueries({ 
queryKey: key })));
+    await Promise.all(queryKeys.map((key) => queryClient.invalidateQueries({ 
queryKey: key })));
 
-      onSuccessConfirm();
-    }
+    onSuccessConfirm();
   };
 
   return useDagRunServiceClearDagRun({
diff --git a/airflow/ui/src/queries/useClearTaskInstances.ts 
b/airflow/ui/src/queries/useClearTaskInstances.ts
index 3770a88616a..d03def7d693 100644
--- a/airflow/ui/src/queries/useClearTaskInstances.ts
+++ b/airflow/ui/src/queries/useClearTaskInstances.ts
@@ -40,49 +40,40 @@ export const useClearTaskInstances = ({
   dagId,
   dagRunId,
   onSuccessConfirm,
-  onSuccessDryRun,
 }: {
   dagId: string;
   dagRunId: string;
   onSuccessConfirm: () => void;
-  onSuccessDryRun: (date: TaskInstanceCollectionResponse) => void;
 }) => {
   const queryClient = useQueryClient();
 
   const onSuccess = async (
-    data: TaskInstanceCollectionResponse,
-    variables: {
-      dagId: string;
-      requestBody: ClearTaskInstancesBody;
-    },
+    _: TaskInstanceCollectionResponse,
+    variables: { dagId: string; requestBody: ClearTaskInstancesBody },
   ) => {
-    if (variables.requestBody.dry_run) {
-      onSuccessDryRun(data);
-    } else {
-      const taskInstanceKeys = (variables.requestBody.task_ids ?? [])
-        .map((taskId) => {
-          const runId = variables.requestBody.dag_run_id;
+    const taskInstanceKeys = (variables.requestBody.task_ids ?? [])
+      .map((taskId) => {
+        const runId = variables.requestBody.dag_run_id;
 
-          if (runId === null || runId === undefined) {
-            return undefined;
-          }
-          const params = { dagId, dagRunId: runId, taskId };
+        if (runId === null || runId === undefined) {
+          return undefined;
+        }
+        const params = { dagId, dagRunId: runId, taskId };
 
-          return UseTaskInstanceServiceGetTaskInstanceKeyFn(params);
-        })
-        .filter((key) => key !== undefined);
+        return UseTaskInstanceServiceGetTaskInstanceKeyFn(params);
+      })
+      .filter((key) => key !== undefined);
 
-      const queryKeys = [
-        [useTaskInstanceServiceGetTaskInstancesKey],
-        ...taskInstanceKeys,
-        UseDagRunServiceGetDagRunKeyFn({ dagId, dagRunId }),
-        [useDagRunServiceGetDagRunsKey],
-      ];
+    const queryKeys = [
+      [useTaskInstanceServiceGetTaskInstancesKey],
+      ...taskInstanceKeys,
+      UseDagRunServiceGetDagRunKeyFn({ dagId, dagRunId }),
+      [useDagRunServiceGetDagRunsKey],
+    ];
 
-      await Promise.all(queryKeys.map((key) => queryClient.invalidateQueries({ 
queryKey: key })));
+    await Promise.all(queryKeys.map((key) => queryClient.invalidateQueries({ 
queryKey: key })));
 
-      onSuccessConfirm();
-    }
+    onSuccessConfirm();
   };
 
   return useTaskInstanceServicePostClearTaskInstances({
diff --git a/airflow/ui/src/queries/useClearTaskInstancesDryRun.ts 
b/airflow/ui/src/queries/useClearTaskInstancesDryRun.ts
new file mode 100644
index 00000000000..e85fae59a57
--- /dev/null
+++ b/airflow/ui/src/queries/useClearTaskInstancesDryRun.ts
@@ -0,0 +1,56 @@
+/*!
+ * 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 { TaskInstanceService } from "openapi/requests/services.gen";
+import type { ClearTaskInstancesBody, PostClearTaskInstancesResponse } from 
"openapi/requests/types.gen";
+
+type Props<TData, TError> = {
+  dagId: string;
+  options?: Omit<UseQueryOptions<TData, TError>, "queryFn" | "queryKey">;
+  requestBody: ClearTaskInstancesBody;
+};
+
+export const useClearTaskInstancesDryRun = <TData = 
PostClearTaskInstancesResponse, TError = unknown>({
+  dagId,
+  options,
+  requestBody,
+}: Props<TData, TError>) =>
+  useQuery<TData, TError>({
+    ...options,
+    queryFn: () =>
+      TaskInstanceService.postClearTaskInstances({
+        dagId,
+        requestBody: {
+          dry_run: true,
+          ...requestBody,
+        },
+      }) as TData,
+    queryKey: [
+      "clearTaskInstance",
+      dagId,
+      requestBody.dag_run_id,
+      requestBody.only_failed,
+      requestBody.task_ids,
+      requestBody.include_downstream,
+      requestBody.include_future,
+      requestBody.include_past,
+      requestBody.include_upstream,
+    ],
+  });

Reply via email to