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

ephraimanierobi pushed a commit to branch v2-6-test
in repository https://gitbox.apache.org/repos/asf/airflow.git

commit 2a4fd5bdce4ae99a93a2b9ec69860f4a181b6990
Author: Brent Bovenzi <[email protected]>
AuthorDate: Fri Apr 14 12:51:53 2023 -0400

    Improve task & run actions ux in grid view (#30373)
    
    * update run clear+mark, update task clear
    
    * add mark as tasks and include list of affected tasks
    
    * Add support for mapped tasks, add shared modal component
    
    * Clean up styling, restore warning for past/future tg clear
    
    (cherry picked from commit c5b685e88dd6ecf56d96ef4fefa6c409f28e2b22)
---
 airflow/www/static/js/api/index.ts                 |   4 +-
 airflow/www/static/js/api/useClearTask.ts          |   7 +-
 .../api/{useClearTask.ts => useClearTaskDryRun.ts} |  70 +++---
 airflow/www/static/js/api/useMarkFailedTask.ts     |   8 +-
 airflow/www/static/js/api/useMarkSuccessTask.ts    |   8 +-
 ...{useConfirmMarkTask.ts => useMarkTaskDryRun.ts} |  45 ++--
 airflow/www/static/js/components/ConfirmDialog.tsx | 103 --------
 .../www/static/js/dag/details/dagRun/ClearRun.tsx  |  81 ++++---
 .../static/js/dag/details/dagRun/MarkFailedRun.tsx |  73 ------
 .../www/static/js/dag/details/dagRun/MarkRunAs.tsx |  90 +++++++
 .../js/dag/details/dagRun/MarkSuccessRun.tsx       |  77 ------
 .../www/static/js/dag/details/dagRun/QueueRun.tsx  |  76 ------
 airflow/www/static/js/dag/details/dagRun/index.tsx |  19 --
 airflow/www/static/js/dag/details/index.tsx        |  37 ++-
 .../static/js/dag/details/taskInstance/index.tsx   |  20 --
 .../taskInstance/taskActions/ActionButton.tsx      |   4 +-
 .../taskInstance/taskActions/ActionModal.tsx       | 112 +++++++++
 .../dag/details/taskInstance/taskActions/Clear.tsx | 174 --------------
 .../taskInstance/taskActions/ClearInstance.tsx     | 235 ++++++++++++++++++
 .../taskInstance/taskActions/MarkFailed.tsx        | 141 -----------
 .../taskInstance/taskActions/MarkInstanceAs.tsx    | 267 +++++++++++++++++++++
 .../taskInstance/taskActions/MarkSuccess.tsx       | 141 -----------
 .../dag/details/taskInstance/taskActions/index.tsx |  84 -------
 .../dag/details/taskInstance/taskActions/types.ts  |  29 ---
 24 files changed, 866 insertions(+), 1039 deletions(-)

diff --git a/airflow/www/static/js/api/index.ts 
b/airflow/www/static/js/api/index.ts
index 2bbfe10b66..018f4e97b6 100644
--- a/airflow/www/static/js/api/index.ts
+++ b/airflow/www/static/js/api/index.ts
@@ -28,7 +28,7 @@ import useClearTask from "./useClearTask";
 import useMarkFailedTask from "./useMarkFailedTask";
 import useMarkSuccessTask from "./useMarkSuccessTask";
 import useExtraLinks from "./useExtraLinks";
-import useConfirmMarkTask from "./useConfirmMarkTask";
+import useMarkTaskDryRun from "./useMarkTaskDryRun";
 import useGraphData from "./useGraphData";
 import useGridData from "./useGridData";
 import useMappedInstances from "./useMappedInstances";
@@ -50,7 +50,7 @@ axios.defaults.headers.common.Accept = "application/json";
 export {
   useClearRun,
   useClearTask,
-  useConfirmMarkTask,
+  useMarkTaskDryRun,
   useDataset,
   useDatasetDependencies,
   useDatasetEvents,
diff --git a/airflow/www/static/js/api/useClearTask.ts 
b/airflow/www/static/js/api/useClearTask.ts
index ebe3b14b28..b4f80e5a7b 100644
--- a/airflow/www/static/js/api/useClearTask.ts
+++ b/airflow/www/static/js/api/useClearTask.ts
@@ -63,7 +63,7 @@ export default function useClearTask({
       recursive: boolean;
       failed: boolean;
       confirmed: boolean;
-      mapIndexes: number[];
+      mapIndexes?: number[];
     }) => {
       const params = new URLSearchParamsWrapper({
         csrf_token: csrfToken,
@@ -105,10 +105,13 @@ export default function useClearTask({
             runId,
             taskId,
           ]);
+          queryClient.invalidateQueries(["clearTask", dagId, runId, taskId]);
           startRefresh();
         }
       },
-      onError: (error: Error) => errorToast({ error }),
+      onError: (error: Error, { confirmed }) => {
+        if (confirmed) errorToast({ error });
+      },
     }
   );
 }
diff --git a/airflow/www/static/js/api/useClearTask.ts 
b/airflow/www/static/js/api/useClearTaskDryRun.ts
similarity index 65%
copy from airflow/www/static/js/api/useClearTask.ts
copy to airflow/www/static/js/api/useClearTaskDryRun.ts
index ebe3b14b28..cea46723f4 100644
--- a/airflow/www/static/js/api/useClearTask.ts
+++ b/airflow/www/static/js/api/useClearTaskDryRun.ts
@@ -18,58 +18,60 @@
  */
 
 import axios, { AxiosResponse } from "axios";
-import { useMutation, useQueryClient } from "react-query";
+import { useQuery } from "react-query";
 import URLSearchParamsWrapper from "src/utils/URLSearchParamWrapper";
 import { getMetaValue } from "../utils";
-import { useAutoRefresh } from "../context/autorefresh";
-import useErrorToast from "../utils/useErrorToast";
 
 const csrfToken = getMetaValue("csrf_token");
 const clearUrl = getMetaValue("clear_url");
 
-export default function useClearTask({
+const useClearTaskDryRun = ({
   dagId,
   runId,
   taskId,
   executionDate,
   isGroup,
+  past,
+  future,
+  upstream,
+  downstream,
+  recursive,
+  failed,
+  mapIndexes = [],
 }: {
   dagId: string;
   runId: string;
   taskId: string;
   executionDate: string;
   isGroup: boolean;
-}) {
-  const queryClient = useQueryClient();
-  const errorToast = useErrorToast();
-  const { startRefresh } = useAutoRefresh();
-
-  return useMutation(
-    ["clearTask", dagId, runId, taskId],
-    ({
+  past: boolean;
+  future: boolean;
+  upstream: boolean;
+  downstream: boolean;
+  recursive: boolean;
+  failed: boolean;
+  mapIndexes?: number[];
+}) =>
+  useQuery(
+    [
+      "clearTask",
+      dagId,
+      runId,
+      taskId,
+      mapIndexes,
       past,
       future,
       upstream,
       downstream,
       recursive,
       failed,
-      confirmed,
-      mapIndexes = [],
-    }: {
-      past: boolean;
-      future: boolean;
-      upstream: boolean;
-      downstream: boolean;
-      recursive: boolean;
-      failed: boolean;
-      confirmed: boolean;
-      mapIndexes: number[];
-    }) => {
+    ],
+    () => {
       const params = new URLSearchParamsWrapper({
         csrf_token: csrfToken,
         dag_id: dagId,
         dag_run_id: runId,
-        confirmed,
+        confirmed: false,
         execution_date: executionDate,
         past,
         future,
@@ -94,21 +96,7 @@ export default function useClearTask({
           "Content-Type": "application/x-www-form-urlencoded",
         },
       });
-    },
-    {
-      onSuccess: (_, { confirmed }) => {
-        if (confirmed) {
-          queryClient.invalidateQueries("gridData");
-          queryClient.invalidateQueries([
-            "mappedInstances",
-            dagId,
-            runId,
-            taskId,
-          ]);
-          startRefresh();
-        }
-      },
-      onError: (error: Error) => errorToast({ error }),
     }
   );
-}
+
+export default useClearTaskDryRun;
diff --git a/airflow/www/static/js/api/useMarkFailedTask.ts 
b/airflow/www/static/js/api/useMarkFailedTask.ts
index 23bbda60ab..c74c86e632 100644
--- a/airflow/www/static/js/api/useMarkFailedTask.ts
+++ b/airflow/www/static/js/api/useMarkFailedTask.ts
@@ -52,7 +52,7 @@ export default function useMarkFailedTask({
       future: boolean;
       upstream: boolean;
       downstream: boolean;
-      mapIndexes: number[];
+      mapIndexes?: number[];
     }) => {
       const params = new URLSearchParamsWrapper({
         csrf_token: csrfToken,
@@ -85,6 +85,12 @@ export default function useMarkFailedTask({
           runId,
           taskId,
         ]);
+        queryClient.invalidateQueries([
+          "confirmStateChange",
+          dagId,
+          runId,
+          taskId,
+        ]);
         startRefresh();
       },
       onError: (error: Error) => errorToast({ error }),
diff --git a/airflow/www/static/js/api/useMarkSuccessTask.ts 
b/airflow/www/static/js/api/useMarkSuccessTask.ts
index 2605a92526..4c18aa9dc2 100644
--- a/airflow/www/static/js/api/useMarkSuccessTask.ts
+++ b/airflow/www/static/js/api/useMarkSuccessTask.ts
@@ -52,7 +52,7 @@ export default function useMarkSuccessTask({
       future: boolean;
       upstream: boolean;
       downstream: boolean;
-      mapIndexes: number[];
+      mapIndexes?: number[];
     }) => {
       const params = new URLSearchParamsWrapper({
         csrf_token: csrfToken,
@@ -85,6 +85,12 @@ export default function useMarkSuccessTask({
           runId,
           taskId,
         ]);
+        queryClient.invalidateQueries([
+          "confirmStateChange",
+          dagId,
+          runId,
+          taskId,
+        ]);
         startRefresh();
       },
       onError: (error: Error) => errorToast({ error }),
diff --git a/airflow/www/static/js/api/useConfirmMarkTask.ts 
b/airflow/www/static/js/api/useMarkTaskDryRun.ts
similarity index 76%
rename from airflow/www/static/js/api/useConfirmMarkTask.ts
rename to airflow/www/static/js/api/useMarkTaskDryRun.ts
index 4e69875ffb..8e872ed8ea 100644
--- a/airflow/www/static/js/api/useConfirmMarkTask.ts
+++ b/airflow/www/static/js/api/useMarkTaskDryRun.ts
@@ -18,41 +18,48 @@
  */
 
 import axios, { AxiosResponse } from "axios";
-import { useMutation } from "react-query";
+import { useQuery } from "react-query";
 import type { TaskState } from "src/types";
 import URLSearchParamsWrapper from "src/utils/URLSearchParamWrapper";
 import { getMetaValue } from "../utils";
-import useErrorToast from "../utils/useErrorToast";
 
 const confirmUrl = getMetaValue("confirm_url");
 
-export default function useConfirmMarkTask({
+const useMarkTaskDryRun = ({
   dagId,
   runId,
   taskId,
   state,
+  past,
+  future,
+  upstream,
+  downstream,
+  mapIndexes = [],
 }: {
   dagId: string;
   runId: string;
   taskId: string;
   state: TaskState;
-}) {
-  const errorToast = useErrorToast();
-  return useMutation(
-    ["confirmStateChange", dagId, runId, taskId, state],
-    ({
+  past: boolean;
+  future: boolean;
+  upstream: boolean;
+  downstream: boolean;
+  mapIndexes?: number[];
+}) =>
+  useQuery(
+    [
+      "confirmStateChange",
+      dagId,
+      runId,
+      taskId,
+      state,
       past,
       future,
       upstream,
       downstream,
-      mapIndexes = [],
-    }: {
-      past: boolean;
-      future: boolean;
-      upstream: boolean;
-      downstream: boolean;
-      mapIndexes: number[];
-    }) => {
+      mapIndexes,
+    ],
+    () => {
       const params = new URLSearchParamsWrapper({
         dag_id: dagId,
         dag_run_id: runId,
@@ -68,9 +75,7 @@ export default function useConfirmMarkTask({
         params.append("map_index", mi.toString());
       });
       return axios.get<AxiosResponse, string[]>(confirmUrl, { params });
-    },
-    {
-      onError: (error: Error) => errorToast({ error }),
     }
   );
-}
+
+export default useMarkTaskDryRun;
diff --git a/airflow/www/static/js/components/ConfirmDialog.tsx 
b/airflow/www/static/js/components/ConfirmDialog.tsx
deleted file mode 100644
index 4826f11ff5..0000000000
--- a/airflow/www/static/js/components/ConfirmDialog.tsx
+++ /dev/null
@@ -1,103 +0,0 @@
-/*!
- * 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 React, { PropsWithChildren, useRef } from "react";
-import {
-  AlertDialog,
-  AlertDialogBody,
-  AlertDialogFooter,
-  AlertDialogHeader,
-  AlertDialogContent,
-  AlertDialogOverlay,
-  Button,
-  Code,
-  Text,
-} from "@chakra-ui/react";
-
-import { useContainerRef } from "src/context/containerRef";
-
-interface Props extends PropsWithChildren {
-  isOpen: boolean;
-  onClose: () => void;
-  title?: string;
-  description: string;
-  affectedTasks: string[];
-  onConfirm: () => void;
-  isLoading?: boolean;
-}
-
-const ConfirmDialog = ({
-  isOpen,
-  onClose,
-  title = "Wait a minute",
-  description,
-  affectedTasks,
-  onConfirm,
-  isLoading = false,
-  children,
-}: Props) => {
-  const initialFocusRef = useRef<HTMLButtonElement>(null);
-  const containerRef = useContainerRef();
-
-  return (
-    <AlertDialog
-      isOpen={isOpen}
-      // Since we are not deleting, we can focus on the confirm button
-      leastDestructiveRef={initialFocusRef}
-      onClose={onClose}
-      portalProps={{ containerRef }}
-      size="6xl"
-      blockScrollOnMount={false}
-    >
-      <AlertDialogOverlay>
-        <AlertDialogContent maxHeight="90vh">
-          <AlertDialogHeader fontSize="4xl" fontWeight="bold">
-            {title}
-          </AlertDialogHeader>
-
-          <AlertDialogBody overflowY="auto">
-            {children}
-            <Text mb={2}>{description}</Text>
-            {affectedTasks.map((ti) => (
-              <Code width="100%" key={ti} fontSize="lg">
-                {ti}
-              </Code>
-            ))}
-            {!affectedTasks.length && <Text>No task instances to 
change.</Text>}
-          </AlertDialogBody>
-
-          <AlertDialogFooter>
-            <Button onClick={onClose}>Cancel</Button>
-            <Button
-              colorScheme="blue"
-              onClick={onConfirm}
-              ml={3}
-              ref={initialFocusRef}
-              isLoading={isLoading}
-            >
-              Confirm
-            </Button>
-          </AlertDialogFooter>
-        </AlertDialogContent>
-      </AlertDialogOverlay>
-    </AlertDialog>
-  );
-};
-
-export default ConfirmDialog;
diff --git a/airflow/www/static/js/dag/details/dagRun/ClearRun.tsx 
b/airflow/www/static/js/dag/details/dagRun/ClearRun.tsx
index d943bbeb13..dbc369799b 100644
--- a/airflow/www/static/js/dag/details/dagRun/ClearRun.tsx
+++ b/airflow/www/static/js/dag/details/dagRun/ClearRun.tsx
@@ -17,51 +17,68 @@
  * under the License.
  */
 
-import React, { useState } from "react";
-import { Button, useDisclosure } from "@chakra-ui/react";
-
-import { useClearRun } from "src/api";
+import React from "react";
+import {
+  Flex,
+  Button,
+  Menu,
+  MenuButton,
+  MenuItem,
+  MenuList,
+  MenuButtonProps,
+} from "@chakra-ui/react";
+import { MdArrowDropDown } from "react-icons/md";
 import { getMetaValue } from "src/utils";
-import ConfirmDialog from "src/components/ConfirmDialog";
+import { useClearRun, useQueueRun } from "src/api";
 
 const canEdit = getMetaValue("can_edit") === "True";
+const dagId = getMetaValue("dag_id");
 
-interface Props {
-  dagId: string;
+interface Props extends MenuButtonProps {
   runId: string;
 }
 
-const ClearRun = ({ dagId, runId }: Props) => {
-  const [affectedTasks, setAffectedTasks] = useState<string[]>([]);
-  const { isOpen, onOpen, onClose } = useDisclosure();
-  const { mutateAsync: onClear, isLoading } = useClearRun(dagId, runId);
+const ClearRun = ({ runId, ...otherProps }: Props) => {
+  const { mutateAsync: onClear, isLoading: isClearLoading } = useClearRun(
+    dagId,
+    runId
+  );
+
+  const { mutateAsync: onQueue, isLoading: isQueueLoading } = useQueueRun(
+    dagId,
+    runId
+  );
 
-  const onClick = async () => {
-    const data = await onClear({ confirmed: false });
-    setAffectedTasks(data);
-    onOpen();
+  const clearExistingTasks = () => {
+    onClear({ confirmed: true });
   };
 
-  const onConfirm = async () => {
-    await onClear({ confirmed: true });
-    setAffectedTasks([]);
-    onClose();
+  const queueNewTasks = () => {
+    onQueue({ confirmed: true });
   };
 
+  const clearLabel = "Clear tasks or add new tasks";
   return (
-    <>
-      <Button onClick={onClick} isLoading={isLoading} isDisabled={!canEdit}>
-        Clear existing tasks
-      </Button>
-      <ConfirmDialog
-        isOpen={isOpen}
-        onClose={onClose}
-        onConfirm={onConfirm}
-        isLoading={isLoading}
-        description="Task instances you are about to clear:"
-        affectedTasks={affectedTasks}
-      />
-    </>
+    <Menu>
+      <MenuButton
+        as={Button}
+        colorScheme="blue"
+        transition="all 0.2s"
+        title={clearLabel}
+        aria-label={clearLabel}
+        disabled={!canEdit || isClearLoading || isQueueLoading}
+        {...otherProps}
+      >
+        <Flex>
+          Clear
+          <MdArrowDropDown size="16px" />
+        </Flex>
+      </MenuButton>
+      <MenuList>
+        <MenuItem onClick={clearExistingTasks}>Clear existing tasks</MenuItem>
+        <MenuItem onClick={queueNewTasks}>Queue up new tasks</MenuItem>
+      </MenuList>
+    </Menu>
   );
 };
 
diff --git a/airflow/www/static/js/dag/details/dagRun/MarkFailedRun.tsx 
b/airflow/www/static/js/dag/details/dagRun/MarkFailedRun.tsx
deleted file mode 100644
index cd403de69f..0000000000
--- a/airflow/www/static/js/dag/details/dagRun/MarkFailedRun.tsx
+++ /dev/null
@@ -1,73 +0,0 @@
-/*!
- * 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 React, { useState } from "react";
-import { Button, useDisclosure } from "@chakra-ui/react";
-
-import { useMarkFailedRun } from "src/api";
-import { getMetaValue } from "src/utils";
-import ConfirmDialog from "src/components/ConfirmDialog";
-
-const canEdit = getMetaValue("can_edit") === "True";
-
-interface Props {
-  dagId: string;
-  runId: string;
-}
-
-const MarkFailedRun = ({ dagId, runId }: Props) => {
-  const [affectedTasks, setAffectedTasks] = useState<string[]>([]);
-  const { isOpen, onOpen, onClose } = useDisclosure();
-  const { mutateAsync: markFailed, isLoading } = useMarkFailedRun(dagId, 
runId);
-
-  const onClick = async () => {
-    const data = await markFailed({ confirmed: false });
-    setAffectedTasks(data);
-    onOpen();
-  };
-
-  const onConfirm = () => {
-    markFailed({ confirmed: true });
-    setAffectedTasks([]);
-    onClose();
-  };
-
-  return (
-    <>
-      <Button
-        onClick={onClick}
-        colorScheme="red"
-        isLoading={isLoading}
-        isDisabled={!canEdit}
-      >
-        Mark Failed
-      </Button>
-      <ConfirmDialog
-        isOpen={isOpen}
-        onClose={onClose}
-        onConfirm={onConfirm}
-        isLoading={isLoading}
-        description="Task instances you are about to mark as failed or 
skipped:"
-        affectedTasks={affectedTasks}
-      />
-    </>
-  );
-};
-
-export default MarkFailedRun;
diff --git a/airflow/www/static/js/dag/details/dagRun/MarkRunAs.tsx 
b/airflow/www/static/js/dag/details/dagRun/MarkRunAs.tsx
new file mode 100644
index 0000000000..276e9a6f05
--- /dev/null
+++ b/airflow/www/static/js/dag/details/dagRun/MarkRunAs.tsx
@@ -0,0 +1,90 @@
+/*!
+ * 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 React from "react";
+import {
+  Flex,
+  Button,
+  Menu,
+  MenuButton,
+  MenuItem,
+  MenuList,
+  MenuButtonProps,
+} from "@chakra-ui/react";
+import { MdArrowDropDown } from "react-icons/md";
+import { getMetaValue } from "src/utils";
+import { useMarkFailedRun, useMarkSuccessRun } from "src/api";
+import type { RunState } from "src/types";
+
+import { SimpleStatus } from "../../StatusBox";
+
+const canEdit = getMetaValue("can_edit") === "True";
+const dagId = getMetaValue("dag_id");
+
+interface Props extends MenuButtonProps {
+  runId: string;
+  state?: RunState;
+}
+
+const MarkRunAs = ({ runId, state, ...otherProps }: Props) => {
+  const { mutateAsync: markFailed, isLoading: isMarkFailedLoading } =
+    useMarkFailedRun(dagId, runId);
+  const { mutateAsync: markSuccess, isLoading: isMarkSuccessLoading } =
+    useMarkSuccessRun(dagId, runId);
+
+  const markAsFailed = () => {
+    markFailed({ confirmed: true });
+  };
+
+  const markAsSuccess = () => {
+    markSuccess({ confirmed: true });
+  };
+
+  const markLabel = "Manually set dag run state";
+  return (
+    <Menu>
+      <MenuButton
+        as={Button}
+        colorScheme="blue"
+        transition="all 0.2s"
+        title={markLabel}
+        aria-label={markLabel}
+        disabled={!canEdit || isMarkFailedLoading || isMarkSuccessLoading}
+        {...otherProps}
+      >
+        <Flex>
+          Mark state as...
+          <MdArrowDropDown size="16px" />
+        </Flex>
+      </MenuButton>
+      <MenuList>
+        <MenuItem onClick={markAsFailed} isDisabled={state === "failed"}>
+          <SimpleStatus state="failed" mr={2} />
+          failed
+        </MenuItem>
+        <MenuItem onClick={markAsSuccess} isDisabled={state === "success"}>
+          <SimpleStatus state="success" mr={2} />
+          success
+        </MenuItem>
+      </MenuList>
+    </Menu>
+  );
+};
+
+export default MarkRunAs;
diff --git a/airflow/www/static/js/dag/details/dagRun/MarkSuccessRun.tsx 
b/airflow/www/static/js/dag/details/dagRun/MarkSuccessRun.tsx
deleted file mode 100644
index 17b1f679b7..0000000000
--- a/airflow/www/static/js/dag/details/dagRun/MarkSuccessRun.tsx
+++ /dev/null
@@ -1,77 +0,0 @@
-/*!
- * 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 React, { useState } from "react";
-import { Button, useDisclosure } from "@chakra-ui/react";
-
-import { useMarkSuccessRun } from "src/api";
-import ConfirmDialog from "src/components/ConfirmDialog";
-import { getMetaValue } from "src/utils";
-
-const canEdit = getMetaValue("can_edit") === "True";
-
-interface Props {
-  dagId: string;
-  runId: string;
-}
-
-const MarkSuccessRun = ({ dagId, runId }: Props) => {
-  const [affectedTasks, setAffectedTasks] = useState<string[]>([]);
-  const { isOpen, onOpen, onClose } = useDisclosure();
-  const { mutateAsync: markSuccess, isLoading } = useMarkSuccessRun(
-    dagId,
-    runId
-  );
-
-  const onClick = async () => {
-    const data = await markSuccess({ confirmed: false });
-    setAffectedTasks(data);
-    onOpen();
-  };
-
-  const onConfirm = async () => {
-    await markSuccess({ confirmed: true });
-    setAffectedTasks([]);
-    onClose();
-  };
-
-  return (
-    <>
-      <Button
-        ml={2}
-        onClick={onClick}
-        colorScheme="green"
-        isLoading={isLoading}
-        isDisabled={!canEdit}
-      >
-        Mark Success
-      </Button>
-      <ConfirmDialog
-        isOpen={isOpen}
-        onClose={onClose}
-        onConfirm={onConfirm}
-        isLoading={isLoading}
-        description="Task instances you are about to mark as success:"
-        affectedTasks={affectedTasks}
-      />
-    </>
-  );
-};
-
-export default MarkSuccessRun;
diff --git a/airflow/www/static/js/dag/details/dagRun/QueueRun.tsx 
b/airflow/www/static/js/dag/details/dagRun/QueueRun.tsx
deleted file mode 100644
index 8e8a054c40..0000000000
--- a/airflow/www/static/js/dag/details/dagRun/QueueRun.tsx
+++ /dev/null
@@ -1,76 +0,0 @@
-/*!
- * 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 React, { useState } from "react";
-import { Button, useDisclosure } from "@chakra-ui/react";
-
-import { useQueueRun } from "src/api";
-import ConfirmDialog from "src/components/ConfirmDialog";
-import { getMetaValue } from "src/utils";
-
-const canEdit = getMetaValue("can_edit") === "True";
-
-interface Props {
-  dagId: string;
-  runId: string;
-}
-
-const QueueRun = ({ dagId, runId }: Props) => {
-  const [affectedTasks, setAffectedTasks] = useState<string[]>([]);
-  const { isOpen, onOpen, onClose } = useDisclosure();
-  const { mutateAsync: onQueue, isLoading } = useQueueRun(dagId, runId);
-
-  // Get what the changes will be and show it in a modal
-  const onClick = async () => {
-    const data = await onQueue({ confirmed: false });
-    setAffectedTasks(data);
-    onOpen();
-  };
-
-  // Confirm changes
-  const onConfirm = async () => {
-    await onQueue({ confirmed: true });
-    setAffectedTasks([]);
-    onClose();
-  };
-
-  return (
-    <>
-      <Button
-        onClick={onClick}
-        isLoading={isLoading}
-        ml={2}
-        title="Queue up new tasks to make the DAG run up-to-date with any DAG 
file changes."
-        isDisabled={!canEdit}
-      >
-        Queue up new tasks
-      </Button>
-      <ConfirmDialog
-        isOpen={isOpen}
-        onClose={onClose}
-        onConfirm={onConfirm}
-        isLoading={isLoading}
-        description="Task instances you are about to queue:"
-        affectedTasks={affectedTasks}
-      />
-    </>
-  );
-};
-
-export default QueueRun;
diff --git a/airflow/www/static/js/dag/details/dagRun/index.tsx 
b/airflow/www/static/js/dag/details/dagRun/index.tsx
index 289da0dc64..ed9dce266f 100644
--- a/airflow/www/static/js/dag/details/dagRun/index.tsx
+++ b/airflow/www/static/js/dag/details/dagRun/index.tsx
@@ -19,7 +19,6 @@
 import React, { useRef } from "react";
 import {
   Flex,
-  Text,
   Box,
   Button,
   Divider,
@@ -43,10 +42,6 @@ import Time from "src/components/Time";
 import RunTypeIcon from "src/components/RunTypeIcon";
 import NotesAccordion from "src/dag/details/NotesAccordion";
 
-import MarkFailedRun from "./MarkFailedRun";
-import MarkSuccessRun from "./MarkSuccessRun";
-import QueueRun from "./QueueRun";
-import ClearRun from "./ClearRun";
 import DatasetTriggerEvents from "./DatasetTriggerEvents";
 
 const dagId = getMetaValue("dag_id");
@@ -94,20 +89,6 @@ const DagRun = ({ runId }: Props) => {
       overflowY="auto"
       pb={4}
     >
-      <Flex justifyContent="space-between" alignItems="center">
-        <Flex>
-          <MarkFailedRun dagId={dagId} runId={runId} />
-          <MarkSuccessRun dagId={dagId} runId={runId} />
-        </Flex>
-        <Flex justifyContent="flex-end" alignItems="center">
-          <Text fontWeight="bold" mr={2}>
-            Re-run:
-          </Text>
-          <ClearRun dagId={dagId} runId={runId} />
-          <QueueRun dagId={dagId} runId={runId} />
-        </Flex>
-      </Flex>
-      <Divider my={3} />
       <Box px={4}>
         <NotesAccordion
           dagId={dagId}
diff --git a/airflow/www/static/js/dag/details/index.tsx 
b/airflow/www/static/js/dag/details/index.tsx
index 65cba38bbc..1cf9a55632 100644
--- a/airflow/www/static/js/dag/details/index.tsx
+++ b/airflow/www/static/js/dag/details/index.tsx
@@ -46,6 +46,10 @@ import MappedInstances from "./taskInstance/MappedInstances";
 import Logs from "./taskInstance/Logs";
 import BackToTaskSummary from "./taskInstance/BackToTaskSummary";
 import FilterTasks from "./FilterTasks";
+import ClearRun from "./dagRun/ClearRun";
+import MarkRunAs from "./dagRun/MarkRunAs";
+import ClearInstance from "./taskInstance/taskActions/ClearInstance";
+import MarkInstanceAs from "./taskInstance/taskActions/MarkInstanceAs";
 
 const dagId = getMetaValue("dag_id")!;
 
@@ -149,7 +153,38 @@ const Details = ({ openGroupIds, onToggleGroups, 
hoveredTaskState }: Props) => {
     <Flex flexDirection="column" pl={3} height="100%">
       <Flex alignItems="center" justifyContent="space-between">
         <Header />
-        <Flex>{taskId && runId && <FilterTasks taskId={taskId} />}</Flex>
+        <Flex>
+          {runId && !taskId && (
+            <>
+              <ClearRun runId={runId} mr={2} />
+              <MarkRunAs runId={runId} state={run?.state} />
+            </>
+          )}
+          {runId && taskId && (
+            <>
+              <ClearInstance
+                taskId={taskId}
+                runId={runId}
+                executionDate={run?.executionDate || ""}
+                isGroup={isGroup}
+                isMapped={isMapped}
+                mapIndex={mapIndex}
+                mr={2}
+              />
+              {!isGroup && (
+                <MarkInstanceAs
+                  taskId={taskId}
+                  runId={runId}
+                  state={instance?.state}
+                  isMapped={isMapped}
+                  mapIndex={mapIndex}
+                  mr={2}
+                />
+              )}
+            </>
+          )}
+          {taskId && runId && <FilterTasks taskId={taskId} />}
+        </Flex>
       </Flex>
       <Divider my={2} />
       <Tabs
diff --git a/airflow/www/static/js/dag/details/taskInstance/index.tsx 
b/airflow/www/static/js/dag/details/taskInstance/index.tsx
index 7d584aa07c..e22e346610 100644
--- a/airflow/www/static/js/dag/details/taskInstance/index.tsx
+++ b/airflow/www/static/js/dag/details/taskInstance/index.tsx
@@ -28,7 +28,6 @@ import NotesAccordion from "src/dag/details/NotesAccordion";
 import TaskNav from "./Nav";
 import ExtraLinks from "./ExtraLinks";
 import Details from "./Details";
-import TaskActions from "./taskActions";
 
 const dagId = getMetaValue("dag_id")!;
 
@@ -42,7 +41,6 @@ const TaskInstance = ({ taskId, runId, mapIndex }: Props) => {
   const taskInstanceRef = useRef<HTMLDivElement>(null);
   const offsetTop = useOffsetTop(taskInstanceRef);
   const isMapIndexDefined = !(mapIndex === undefined);
-  const actionsMapIndexes = isMapIndexDefined ? [mapIndex] : [];
   const {
     data: { dagRuns, groups },
   } = useGridData();
@@ -74,13 +72,6 @@ const TaskInstance = ({ taskId, runId, mapIndex }: Props) => 
{
 
   const { executionDate } = run;
 
-  let taskActionsTitle = `${isGroup ? "Task Group" : "Task"} Actions`;
-  if (isMapped) {
-    taskActionsTitle += ` for ${actionsMapIndexes.length || "all"} mapped 
task${
-      actionsMapIndexes.length !== 1 ? "s" : ""
-    }`;
-  }
-
   return (
     <Box
       py="4px"
@@ -108,17 +99,6 @@ const TaskInstance = ({ taskId, runId, mapIndex }: Props) 
=> {
           key={dagId + runId + taskId + instance.mapIndex}
         />
       )}
-      <Box mb={8}>
-        <TaskActions
-          title={taskActionsTitle}
-          runId={runId}
-          taskId={taskId}
-          dagId={dagId}
-          executionDate={executionDate}
-          mapIndexes={actionsMapIndexes}
-          isGroup={isGroup}
-        />
-      </Box>
       {!isMapped && group.extraLinks && (
         <ExtraLinks
           taskId={taskId}
diff --git 
a/airflow/www/static/js/dag/details/taskInstance/taskActions/ActionButton.tsx 
b/airflow/www/static/js/dag/details/taskInstance/taskActions/ActionButton.tsx
index 3e89fd44a4..7dcd406dda 100644
--- 
a/airflow/www/static/js/dag/details/taskInstance/taskActions/ActionButton.tsx
+++ 
b/airflow/www/static/js/dag/details/taskInstance/taskActions/ActionButton.tsx
@@ -25,7 +25,7 @@ const titleMap = {
   future: "Also include future task instances when clearing this one",
   upstream: "Also include upstream dependencies",
   downstream: "Also include downstream dependencies",
-  recursive: "",
+  recursive: "Include subdags and parent dags",
   failed: "Only consider failed task instances when clearing this one",
 };
 
@@ -34,7 +34,7 @@ type KeysOfTitleMap = keyof typeof titleMap;
 type Props = ButtonProps & { name: Capitalize<KeysOfTitleMap> };
 const ActionButton = ({ name, ...rest }: Props) => (
   <Button title={titleMap[name.toLowerCase() as KeysOfTitleMap]} {...rest}>
-    {name}
+    {name === "Failed" ? "Only Failed" : name}
   </Button>
 );
 
diff --git 
a/airflow/www/static/js/dag/details/taskInstance/taskActions/ActionModal.tsx 
b/airflow/www/static/js/dag/details/taskInstance/taskActions/ActionModal.tsx
new file mode 100644
index 0000000000..ef89681150
--- /dev/null
+++ b/airflow/www/static/js/dag/details/taskInstance/taskActions/ActionModal.tsx
@@ -0,0 +1,112 @@
+/*!
+ * 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 React, { ReactNode } from "react";
+import {
+  Button,
+  Modal,
+  ModalBody,
+  ModalCloseButton,
+  ModalContent,
+  ModalFooter,
+  ModalHeader,
+  ModalOverlay,
+  ModalProps,
+  Box,
+  Text,
+  Accordion,
+  AccordionButton,
+  AccordionPanel,
+  AccordionItem,
+  AccordionIcon,
+  Code,
+} from "@chakra-ui/react";
+
+import { useContainerRef } from "src/context/containerRef";
+
+interface Props extends ModalProps {
+  affectedTasks?: string[];
+  header: ReactNode | string;
+  subheader?: ReactNode | string;
+  submitButton: ReactNode;
+}
+
+const ActionModal = ({
+  isOpen,
+  onClose,
+  children,
+  header,
+  subheader,
+  affectedTasks = [],
+  submitButton,
+  ...otherProps
+}: Props) => {
+  const containerRef = useContainerRef();
+  return (
+    <Modal
+      size="6xl"
+      isOpen={isOpen}
+      onClose={onClose}
+      portalProps={{ containerRef }}
+      blockScrollOnMount={false}
+      {...otherProps}
+    >
+      <ModalOverlay />
+      <ModalContent>
+        <ModalHeader>{header}</ModalHeader>
+        <ModalCloseButton />
+        <ModalBody>
+          <Box mb={3}>{subheader}</Box>
+          <Box>
+            {children}
+            <Accordion allowToggle my={3}>
+              <AccordionItem>
+                <AccordionButton>
+                  <Box flex="1" textAlign="left">
+                    <Text as="strong" size="lg">
+                      Affected Tasks: {affectedTasks?.length || 0}
+                    </Text>
+                  </Box>
+                  <AccordionIcon />
+                </AccordionButton>
+                <AccordionPanel>
+                  <Box maxHeight="400px" overflowY="auto">
+                    {(affectedTasks || []).map((ti) => (
+                      <Code width="100%" key={ti} fontSize="lg">
+                        {ti}
+                      </Code>
+                    ))}
+                  </Box>
+                </AccordionPanel>
+              </AccordionItem>
+            </Accordion>
+          </Box>
+        </ModalBody>
+        <ModalFooter justifyContent="space-between">
+          <Button colorScheme="gray" onClick={onClose}>
+            Cancel
+          </Button>
+          {submitButton}
+        </ModalFooter>
+      </ModalContent>
+    </Modal>
+  );
+};
+
+export default ActionModal;
diff --git 
a/airflow/www/static/js/dag/details/taskInstance/taskActions/Clear.tsx 
b/airflow/www/static/js/dag/details/taskInstance/taskActions/Clear.tsx
deleted file mode 100644
index e42f419014..0000000000
--- a/airflow/www/static/js/dag/details/taskInstance/taskActions/Clear.tsx
+++ /dev/null
@@ -1,174 +0,0 @@
-/*!
- * 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 React, { useState } from "react";
-import {
-  Button,
-  Flex,
-  ButtonGroup,
-  useDisclosure,
-  Alert,
-  AlertIcon,
-} from "@chakra-ui/react";
-
-import ConfirmDialog from "src/components/ConfirmDialog";
-import { useClearTask } from "src/api";
-import { getMetaValue } from "src/utils";
-
-import ActionButton from "./ActionButton";
-import type { CommonActionProps } from "./types";
-
-const canEdit = getMetaValue("can_edit") === "True";
-
-const Run = ({
-  dagId,
-  runId,
-  taskId,
-  executionDate,
-  mapIndexes,
-  isGroup,
-}: CommonActionProps) => {
-  const [affectedTasks, setAffectedTasks] = useState<string[]>([]);
-
-  // Options check/unchecked
-  const [past, setPast] = useState(false);
-  const onTogglePast = () => setPast(!past);
-
-  const [future, setFuture] = useState(false);
-  const onToggleFuture = () => setFuture(!future);
-
-  const [upstream, setUpstream] = useState(false);
-  const onToggleUpstream = () => setUpstream(!upstream);
-
-  const [downstream, setDownstream] = useState(true);
-  const onToggleDownstream = () => setDownstream(!downstream);
-
-  const [recursive, setRecursive] = useState(true);
-  const onToggleRecursive = () => setRecursive(!recursive);
-
-  const [failed, setFailed] = useState(false);
-  const onToggleFailed = () => setFailed(!failed);
-
-  // Confirm dialog open/close
-  const { isOpen, onOpen, onClose } = useDisclosure();
-
-  const { mutateAsync: clearTask, isLoading } = useClearTask({
-    dagId,
-    runId,
-    taskId,
-    executionDate,
-    isGroup: !!isGroup,
-  });
-
-  const onClick = async () => {
-    const data = await clearTask({
-      past,
-      future,
-      upstream,
-      downstream,
-      recursive,
-      failed,
-      confirmed: false,
-      mapIndexes,
-    });
-    setAffectedTasks(data);
-    onOpen();
-  };
-
-  const onConfirm = async () => {
-    await clearTask({
-      past,
-      future,
-      upstream,
-      downstream,
-      recursive,
-      failed,
-      confirmed: true,
-      mapIndexes,
-    });
-    setAffectedTasks([]);
-    onClose();
-  };
-
-  return (
-    <Flex justifyContent="space-between" width="100%">
-      <ButtonGroup isAttached variant="outline" isDisabled={!canEdit}>
-        <ActionButton
-          bg={past ? "gray.100" : undefined}
-          onClick={onTogglePast}
-          name="Past"
-        />
-        <ActionButton
-          bg={future ? "gray.100" : undefined}
-          onClick={onToggleFuture}
-          name="Future"
-        />
-        <ActionButton
-          bg={upstream ? "gray.100" : undefined}
-          onClick={onToggleUpstream}
-          name="Upstream"
-        />
-        <ActionButton
-          bg={downstream ? "gray.100" : undefined}
-          onClick={onToggleDownstream}
-          name="Downstream"
-        />
-        <ActionButton
-          bg={recursive ? "gray.100" : undefined}
-          onClick={onToggleRecursive}
-          name="Recursive"
-        />
-        <ActionButton
-          bg={failed ? "gray.100" : undefined}
-          onClick={onToggleFailed}
-          name="Failed"
-        />
-      </ButtonGroup>
-      <Button
-        colorScheme="blue"
-        onClick={onClick}
-        isLoading={isLoading}
-        isDisabled={!canEdit}
-        title="Clearing deletes the previous state of the task instance, 
allowing it to get re-triggered by the scheduler or a backfill command"
-      >
-        Clear
-      </Button>
-      <ConfirmDialog
-        isOpen={isOpen}
-        onClose={onClose}
-        onConfirm={onConfirm}
-        isLoading={isLoading}
-        description={`Task instances you are about to clear 
(${affectedTasks.length}):`}
-        affectedTasks={affectedTasks}
-      >
-        {isGroup && (past || future) && (
-          <Alert status="warning" mb={3}>
-            <AlertIcon />
-            Clearing a TaskGroup in the future and/or past will affect all the
-            tasks of this group across multiple dag runs.
-            <br />
-            This can take a while to complete.
-          </Alert>
-        )}
-      </ConfirmDialog>
-    </Flex>
-  );
-};
-
-export default Run;
diff --git 
a/airflow/www/static/js/dag/details/taskInstance/taskActions/ClearInstance.tsx 
b/airflow/www/static/js/dag/details/taskInstance/taskActions/ClearInstance.tsx
new file mode 100644
index 0000000000..d16856255f
--- /dev/null
+++ 
b/airflow/www/static/js/dag/details/taskInstance/taskActions/ClearInstance.tsx
@@ -0,0 +1,235 @@
+/*!
+ * 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 React, { useState } from "react";
+import {
+  Alert,
+  AlertIcon,
+  Box,
+  Button,
+  ButtonGroup,
+  ButtonProps,
+  Text,
+  useDisclosure,
+} from "@chakra-ui/react";
+
+import { getMetaValue } from "src/utils";
+import { useClearTask } from "src/api";
+import useClearTaskDryRun from "src/api/useClearTaskDryRun";
+
+import ActionButton from "./ActionButton";
+import ActionModal from "./ActionModal";
+
+const canEdit = getMetaValue("can_edit") === "True";
+const dagId = getMetaValue("dag_id");
+
+interface Props extends ButtonProps {
+  runId: string;
+  taskId: string;
+  executionDate: string;
+  isGroup?: boolean;
+  isMapped?: boolean;
+  mapIndex?: number;
+}
+
+const ClearInstance = ({
+  runId,
+  taskId,
+  mapIndex,
+  executionDate,
+  isGroup,
+  isMapped,
+  ...otherProps
+}: Props) => {
+  const { onOpen, onClose, isOpen } = useDisclosure();
+
+  const [past, setPast] = useState(false);
+  const onTogglePast = () => setPast(!past);
+
+  const [future, setFuture] = useState(false);
+  const onToggleFuture = () => setFuture(!future);
+
+  const [upstream, setUpstream] = useState(false);
+  const onToggleUpstream = () => setUpstream(!upstream);
+
+  const [downstream, setDownstream] = useState(false);
+  const onToggleDownstream = () => setDownstream(!downstream);
+
+  const [recursive, setRecursive] = useState(true);
+  const onToggleRecursive = () => setRecursive(!recursive);
+
+  const [failed, setFailed] = useState(false);
+  const onToggleFailed = () => setFailed(!failed);
+
+  const mapIndexes =
+    mapIndex !== undefined && mapIndex !== -1 ? [mapIndex] : undefined;
+
+  const { data: affectedTasks, isLoading: isLoadingDryRun } =
+    useClearTaskDryRun({
+      dagId,
+      runId,
+      taskId,
+      executionDate,
+      isGroup: !!isGroup,
+      past,
+      future,
+      upstream,
+      downstream,
+      recursive,
+      failed,
+      mapIndexes,
+    });
+
+  const { mutateAsync: clearTask, isLoading } = useClearTask({
+    dagId,
+    runId,
+    taskId,
+    executionDate,
+    isGroup: !!isGroup,
+  });
+
+  const resetModal = () => {
+    onClose();
+    setDownstream(false);
+    setUpstream(false);
+    setPast(false);
+    setFuture(false);
+    setRecursive(false);
+    setFailed(false);
+  };
+
+  const onClear = () => {
+    clearTask({
+      confirmed: true,
+      past,
+      future,
+      upstream,
+      downstream,
+      recursive,
+      failed,
+      mapIndexes,
+    });
+    resetModal();
+  };
+
+  const clearLabel = "Clear and retry task.";
+
+  return (
+    <>
+      <Button
+        title={clearLabel}
+        aria-label={clearLabel}
+        ml={2}
+        isDisabled={!canEdit}
+        colorScheme="blue"
+        onClick={onOpen}
+        {...otherProps}
+      >
+        Clear task
+      </Button>
+      <ActionModal
+        isOpen={isOpen}
+        onClose={resetModal}
+        header="Clear and Retry"
+        subheader={
+          <>
+            <Text>
+              <Text as="strong" mr={1}>
+                Task:
+              </Text>
+              {taskId}
+            </Text>
+            <Text>
+              <Text as="strong" mr={1}>
+                Run:
+              </Text>
+              {runId}
+            </Text>
+            {isMapped && (
+              <Text>
+                <Text as="strong" mr={1}>
+                  Map Index:
+                </Text>
+                {mapIndex !== undefined ? mapIndex : `All mapped tasks`}
+              </Text>
+            )}
+          </>
+        }
+        affectedTasks={affectedTasks}
+        submitButton={
+          <Button
+            colorScheme="blue"
+            isLoading={isLoading || isLoadingDryRun}
+            isDisabled={!affectedTasks?.length}
+            onClick={onClear}
+          >
+            Clear
+          </Button>
+        }
+      >
+        <Box>
+          <Text>Include: </Text>
+          <ButtonGroup isAttached variant="outline" isDisabled={!canEdit}>
+            <ActionButton
+              bg={past ? "gray.100" : undefined}
+              onClick={onTogglePast}
+              name="Past"
+            />
+            <ActionButton
+              bg={future ? "gray.100" : undefined}
+              onClick={onToggleFuture}
+              name="Future"
+            />
+            <ActionButton
+              bg={upstream ? "gray.100" : undefined}
+              onClick={onToggleUpstream}
+              name="Upstream"
+            />
+            <ActionButton
+              bg={downstream ? "gray.100" : undefined}
+              onClick={onToggleDownstream}
+              name="Downstream"
+            />
+            <ActionButton
+              bg={recursive ? "gray.100" : undefined}
+              onClick={onToggleRecursive}
+              name="Recursive"
+            />
+            <ActionButton
+              bg={failed ? "gray.100" : undefined}
+              onClick={onToggleFailed}
+              name="Failed"
+            />
+          </ButtonGroup>
+        </Box>
+        {isGroup && (past || future) && (
+          <Alert status="warning" my={3}>
+            <AlertIcon />
+            Clearing a TaskGroup in the future and/or past will affect all the
+            tasks of this group across multiple dag runs.
+            <br />
+            This can take a while to complete.
+          </Alert>
+        )}
+      </ActionModal>
+    </>
+  );
+};
+
+export default ClearInstance;
diff --git 
a/airflow/www/static/js/dag/details/taskInstance/taskActions/MarkFailed.tsx 
b/airflow/www/static/js/dag/details/taskInstance/taskActions/MarkFailed.tsx
deleted file mode 100644
index 730cadc48d..0000000000
--- a/airflow/www/static/js/dag/details/taskInstance/taskActions/MarkFailed.tsx
+++ /dev/null
@@ -1,141 +0,0 @@
-/*!
- * 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 React, { useState } from "react";
-import { Button, Flex, ButtonGroup, useDisclosure } from "@chakra-ui/react";
-
-import { useConfirmMarkTask, useMarkFailedTask } from "src/api";
-import ConfirmDialog from "src/components/ConfirmDialog";
-import { getMetaValue } from "src/utils";
-
-import ActionButton from "./ActionButton";
-
-const canEdit = getMetaValue("can_edit") === "True";
-
-interface Props {
-  dagId: string;
-  runId: string;
-  taskId: string;
-  mapIndexes: number[];
-}
-
-const MarkFailed = ({ dagId, runId, taskId, mapIndexes }: Props) => {
-  const [affectedTasks, setAffectedTasks] = useState<string[]>([]);
-
-  // Options check/unchecked
-  const [past, setPast] = useState(false);
-  const onTogglePast = () => setPast(!past);
-
-  const [future, setFuture] = useState(false);
-  const onToggleFuture = () => setFuture(!future);
-
-  const [upstream, setUpstream] = useState(false);
-  const onToggleUpstream = () => setUpstream(!upstream);
-
-  const [downstream, setDownstream] = useState(false);
-  const onToggleDownstream = () => setDownstream(!downstream);
-
-  // Confirm dialog open/close
-  const { isOpen, onOpen, onClose } = useDisclosure();
-
-  const { mutateAsync: markFailedMutation, isLoading: isMarkLoading } =
-    useMarkFailedTask({
-      dagId,
-      runId,
-      taskId,
-    });
-  const { mutateAsync: confirmChangeMutation, isLoading: isConfirmLoading } =
-    useConfirmMarkTask({
-      dagId,
-      runId,
-      taskId,
-      state: "failed",
-    });
-
-  const onClick = async () => {
-    const data = await confirmChangeMutation({
-      past,
-      future,
-      upstream,
-      downstream,
-      mapIndexes,
-    });
-    setAffectedTasks(data);
-    onOpen();
-  };
-
-  const onConfirm = async () => {
-    await markFailedMutation({
-      past,
-      future,
-      upstream,
-      downstream,
-      mapIndexes,
-    });
-    setAffectedTasks([]);
-    onClose();
-  };
-
-  const isLoading = isMarkLoading || isConfirmLoading;
-
-  return (
-    <Flex justifyContent="space-between" width="100%">
-      <ButtonGroup isAttached variant="outline" isDisabled={!canEdit}>
-        <ActionButton
-          bg={past ? "gray.100" : undefined}
-          onClick={onTogglePast}
-          name="Past"
-        />
-        <ActionButton
-          bg={future ? "gray.100" : undefined}
-          onClick={onToggleFuture}
-          name="Future"
-        />
-        <ActionButton
-          bg={upstream ? "gray.100" : undefined}
-          onClick={onToggleUpstream}
-          name="Upstream"
-        />
-        <ActionButton
-          bg={downstream ? "gray.100" : undefined}
-          onClick={onToggleDownstream}
-          name="Downstream"
-        />
-      </ButtonGroup>
-      <Button
-        colorScheme="red"
-        onClick={onClick}
-        isLoading={isLoading}
-        isDisabled={!canEdit}
-      >
-        Mark Failed
-      </Button>
-      <ConfirmDialog
-        isOpen={isOpen}
-        onClose={onClose}
-        onConfirm={onConfirm}
-        isLoading={isLoading}
-        description="Task instances you are about to mark as failed:"
-        affectedTasks={affectedTasks}
-      />
-    </Flex>
-  );
-};
-
-export default MarkFailed;
diff --git 
a/airflow/www/static/js/dag/details/taskInstance/taskActions/MarkInstanceAs.tsx 
b/airflow/www/static/js/dag/details/taskInstance/taskActions/MarkInstanceAs.tsx
new file mode 100644
index 0000000000..600aaeb64e
--- /dev/null
+++ 
b/airflow/www/static/js/dag/details/taskInstance/taskActions/MarkInstanceAs.tsx
@@ -0,0 +1,267 @@
+/*!
+ * 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 React, { useState } from "react";
+import {
+  Flex,
+  Button,
+  Menu,
+  MenuButton,
+  MenuItem,
+  MenuList,
+  MenuButtonProps,
+  useDisclosure,
+  ButtonGroup,
+  Box,
+  Text,
+} from "@chakra-ui/react";
+import { MdArrowDropDown } from "react-icons/md";
+import { capitalize } from "lodash";
+
+import { getMetaValue } from "src/utils";
+import type { TaskState } from "src/types";
+import {
+  useMarkFailedTask,
+  useMarkSuccessTask,
+  useMarkTaskDryRun,
+} from "src/api";
+
+import { SimpleStatus } from "../../../StatusBox";
+import ActionButton from "./ActionButton";
+import ActionModal from "./ActionModal";
+
+const canEdit = getMetaValue("can_edit") === "True";
+const dagId = getMetaValue("dag_id");
+
+interface Props extends MenuButtonProps {
+  runId: string;
+  taskId: string;
+  state?: TaskState;
+  mapIndex?: number;
+  isMapped?: boolean;
+}
+
+const MarkInstanceAs = ({
+  runId,
+  taskId,
+  mapIndex,
+  isMapped,
+  state: currentState,
+  ...otherProps
+}: Props) => {
+  const { onOpen, onClose, isOpen } = useDisclosure();
+
+  const [newState, setNewState] = useState<"failed" | "success">("success");
+
+  const [past, setPast] = useState(false);
+  const onTogglePast = () => setPast(!past);
+
+  const [future, setFuture] = useState(false);
+  const onToggleFuture = () => setFuture(!future);
+
+  const [upstream, setUpstream] = useState(false);
+  const onToggleUpstream = () => setUpstream(!upstream);
+
+  const [downstream, setDownstream] = useState(false);
+  const onToggleDownstream = () => setDownstream(!downstream);
+
+  const markAsFailed = () => {
+    setNewState("failed");
+    onOpen();
+  };
+
+  const markAsSuccess = () => {
+    setNewState("success");
+    onOpen();
+  };
+
+  const mapIndexes =
+    mapIndex !== undefined && mapIndex !== -1 ? [mapIndex] : undefined;
+
+  const { data: affectedTasks, isLoading: isLoadingDryRun } = 
useMarkTaskDryRun(
+    {
+      dagId,
+      runId,
+      taskId,
+      state: newState,
+      past,
+      future,
+      upstream,
+      downstream,
+      mapIndexes,
+    }
+  );
+
+  const { mutateAsync: markFailedMutation, isLoading: isMarkFailedLoading } =
+    useMarkFailedTask({
+      dagId,
+      runId,
+      taskId,
+    });
+
+  const { mutateAsync: markSuccessMutation, isLoading: isMarkSuccessLoading } =
+    useMarkSuccessTask({
+      dagId,
+      runId,
+      taskId,
+    });
+
+  const resetModal = () => {
+    onClose();
+    setDownstream(false);
+    setUpstream(false);
+    setPast(false);
+    setFuture(false);
+  };
+
+  const onMarkState = () => {
+    if (newState === "success") {
+      markSuccessMutation({
+        past,
+        future,
+        upstream,
+        downstream,
+        mapIndexes,
+      });
+    } else if (newState === "failed") {
+      markFailedMutation({
+        past,
+        future,
+        upstream,
+        downstream,
+        mapIndexes,
+      });
+    }
+    resetModal();
+  };
+
+  const markLabel = "Manually set task instance state";
+  const isMappedSummary = isMapped && mapIndex === undefined;
+
+  return (
+    <>
+      <Menu>
+        <MenuButton
+          as={Button}
+          colorScheme="blue"
+          transition="all 0.2s"
+          title={markLabel}
+          aria-label={markLabel}
+          disabled={!canEdit}
+          {...otherProps}
+        >
+          <Flex>
+            Mark state as…
+            <MdArrowDropDown size="16px" />
+          </Flex>
+        </MenuButton>
+        <MenuList>
+          <MenuItem
+            onClick={markAsFailed}
+            isDisabled={!isMappedSummary && currentState === "failed"}
+          >
+            <SimpleStatus state="failed" mr={2} />
+            failed
+          </MenuItem>
+          <MenuItem
+            onClick={markAsSuccess}
+            isDisabled={!isMappedSummary && currentState === "success"}
+          >
+            <SimpleStatus state="success" mr={2} />
+            success
+          </MenuItem>
+        </MenuList>
+      </Menu>
+      <ActionModal
+        isOpen={isOpen}
+        onClose={resetModal}
+        header={`Mark as ${capitalize(newState)}`}
+        subheader={
+          <>
+            <Text>
+              <Text as="strong" mr={1}>
+                Task:
+              </Text>
+              {taskId}
+            </Text>
+            <Text>
+              <Text as="strong" mr={1}>
+                Run:
+              </Text>
+              {runId}
+            </Text>
+            {isMapped && (
+              <Text>
+                <Text as="strong" mr={1}>
+                  Map Index:
+                </Text>
+                {mapIndex !== undefined ? mapIndex : `All mapped tasks`}
+              </Text>
+            )}
+          </>
+        }
+        affectedTasks={affectedTasks}
+        submitButton={
+          <Button
+            colorScheme={
+              (newState === "success" && "green") ||
+              (newState === "failed" && "red") ||
+              "grey"
+            }
+            isLoading={
+              isLoadingDryRun || isMarkSuccessLoading || isMarkFailedLoading
+            }
+            isDisabled={!affectedTasks?.length || !newState}
+            onClick={onMarkState}
+          >
+            Mark as {newState}
+          </Button>
+        }
+      >
+        <Box>
+          <Text>Include: </Text>
+          <ButtonGroup isAttached variant="outline" isDisabled={!canEdit}>
+            <ActionButton
+              bg={past ? "gray.100" : undefined}
+              onClick={onTogglePast}
+              name="Past"
+            />
+            <ActionButton
+              bg={future ? "gray.100" : undefined}
+              onClick={onToggleFuture}
+              name="Future"
+            />
+            <ActionButton
+              bg={upstream ? "gray.100" : undefined}
+              onClick={onToggleUpstream}
+              name="Upstream"
+            />
+            <ActionButton
+              bg={downstream ? "gray.100" : undefined}
+              onClick={onToggleDownstream}
+              name="Downstream"
+            />
+          </ButtonGroup>
+        </Box>
+      </ActionModal>
+    </>
+  );
+};
+
+export default MarkInstanceAs;
diff --git 
a/airflow/www/static/js/dag/details/taskInstance/taskActions/MarkSuccess.tsx 
b/airflow/www/static/js/dag/details/taskInstance/taskActions/MarkSuccess.tsx
deleted file mode 100644
index 7b1b9af7ba..0000000000
--- a/airflow/www/static/js/dag/details/taskInstance/taskActions/MarkSuccess.tsx
+++ /dev/null
@@ -1,141 +0,0 @@
-/*!
- * 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 React, { useState } from "react";
-import { Button, Flex, ButtonGroup, useDisclosure } from "@chakra-ui/react";
-
-import ConfirmDialog from "src/components/ConfirmDialog";
-import { useMarkSuccessTask, useConfirmMarkTask } from "src/api";
-import { getMetaValue } from "src/utils";
-
-import ActionButton from "./ActionButton";
-
-const canEdit = getMetaValue("can_edit") === "True";
-
-interface Props {
-  dagId: string;
-  runId: string;
-  taskId: string;
-  mapIndexes: number[];
-}
-
-const MarkSuccess = ({ dagId, runId, taskId, mapIndexes }: Props) => {
-  const [affectedTasks, setAffectedTasks] = useState<string[]>([]);
-
-  // Options check/unchecked
-  const [past, setPast] = useState(false);
-  const onTogglePast = () => setPast(!past);
-
-  const [future, setFuture] = useState(false);
-  const onToggleFuture = () => setFuture(!future);
-
-  const [upstream, setUpstream] = useState(false);
-  const onToggleUpstream = () => setUpstream(!upstream);
-
-  const [downstream, setDownstream] = useState(false);
-  const onToggleDownstream = () => setDownstream(!downstream);
-
-  // Confirm dialog open/close
-  const { isOpen, onOpen, onClose } = useDisclosure();
-
-  const { mutateAsync: markSuccessMutation, isLoading: isMarkLoading } =
-    useMarkSuccessTask({
-      dagId,
-      runId,
-      taskId,
-    });
-  const { mutateAsync: confirmChangeMutation, isLoading: isConfirmLoading } =
-    useConfirmMarkTask({
-      dagId,
-      runId,
-      taskId,
-      state: "success",
-    });
-
-  const onClick = async () => {
-    const data = await confirmChangeMutation({
-      past,
-      future,
-      upstream,
-      downstream,
-      mapIndexes,
-    });
-    setAffectedTasks(data);
-    onOpen();
-  };
-
-  const onConfirm = async () => {
-    await markSuccessMutation({
-      past,
-      future,
-      upstream,
-      downstream,
-      mapIndexes,
-    });
-    setAffectedTasks([]);
-    onClose();
-  };
-
-  const isLoading = isMarkLoading || isConfirmLoading;
-
-  return (
-    <Flex justifyContent="space-between" width="100%">
-      <ButtonGroup isAttached variant="outline" isDisabled={!canEdit}>
-        <ActionButton
-          bg={past ? "gray.100" : undefined}
-          onClick={onTogglePast}
-          name="Past"
-        />
-        <ActionButton
-          bg={future ? "gray.100" : undefined}
-          onClick={onToggleFuture}
-          name="Future"
-        />
-        <ActionButton
-          bg={upstream ? "gray.100" : undefined}
-          onClick={onToggleUpstream}
-          name="Upstream"
-        />
-        <ActionButton
-          bg={downstream ? "gray.100" : undefined}
-          onClick={onToggleDownstream}
-          name="Downstream"
-        />
-      </ButtonGroup>
-      <Button
-        colorScheme="green"
-        onClick={onClick}
-        isLoading={isLoading}
-        isDisabled={!canEdit}
-      >
-        Mark Success
-      </Button>
-      <ConfirmDialog
-        isOpen={isOpen}
-        onClose={onClose}
-        onConfirm={onConfirm}
-        isLoading={isLoading}
-        description="Task instances you are about to mark as success:"
-        affectedTasks={affectedTasks}
-      />
-    </Flex>
-  );
-};
-
-export default MarkSuccess;
diff --git 
a/airflow/www/static/js/dag/details/taskInstance/taskActions/index.tsx 
b/airflow/www/static/js/dag/details/taskInstance/taskActions/index.tsx
deleted file mode 100644
index 92e471bc26..0000000000
--- a/airflow/www/static/js/dag/details/taskInstance/taskActions/index.tsx
+++ /dev/null
@@ -1,84 +0,0 @@
-/*!
- * 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 React from "react";
-import { Box, VStack, Divider, StackDivider, Text } from "@chakra-ui/react";
-
-import type { CommonActionProps } from "./types";
-import ClearAction from "./Clear";
-import MarkFailedAction from "./MarkFailed";
-import MarkSuccessAction from "./MarkSuccess";
-
-type Props = {
-  title: string;
-} & CommonActionProps;
-
-const TaskActions = ({
-  title,
-  runId,
-  taskId,
-  dagId,
-  executionDate,
-  mapIndexes,
-  isGroup,
-}: Props) => (
-  <Box my={3}>
-    <Text as="strong" size="lg">
-      {title}
-    </Text>
-    <Divider my={2} />
-    {/* For now only ClearAction is supported for groups */}
-    {isGroup ? (
-      <ClearAction
-        runId={runId}
-        taskId={taskId}
-        dagId={dagId}
-        executionDate={executionDate}
-        mapIndexes={mapIndexes}
-        isGroup={isGroup}
-      />
-    ) : (
-      <VStack justifyContent="center" divider={<StackDivider my={3} />}>
-        <ClearAction
-          runId={runId}
-          taskId={taskId}
-          dagId={dagId}
-          executionDate={executionDate}
-          mapIndexes={mapIndexes}
-          isGroup={!!isGroup}
-        />
-        <MarkFailedAction
-          runId={runId}
-          taskId={taskId}
-          dagId={dagId}
-          mapIndexes={mapIndexes}
-        />
-        <MarkSuccessAction
-          runId={runId}
-          taskId={taskId}
-          dagId={dagId}
-          mapIndexes={mapIndexes}
-        />
-      </VStack>
-    )}
-    <Divider my={2} />
-  </Box>
-);
-
-export default TaskActions;
diff --git 
a/airflow/www/static/js/dag/details/taskInstance/taskActions/types.ts 
b/airflow/www/static/js/dag/details/taskInstance/taskActions/types.ts
deleted file mode 100644
index 2ba75d1ed8..0000000000
--- a/airflow/www/static/js/dag/details/taskInstance/taskActions/types.ts
+++ /dev/null
@@ -1,29 +0,0 @@
-/*!
- * 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 type { Dag, DagRun, TaskInstance } from "src/types";
-
-export interface CommonActionProps {
-  runId: DagRun["runId"];
-  taskId: TaskInstance["taskId"];
-  dagId: Dag["id"];
-  executionDate: DagRun["executionDate"];
-  mapIndexes: number[];
-  isGroup?: boolean;
-}

Reply via email to