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

jscheffl 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 e4957ff3827 feat: handel remove dags (#49504)
e4957ff3827 is described below

commit e4957ff3827e0aea0465026023dd58288c5b1299
Author: Guan Ming(Wesley) Chiu <[email protected]>
AuthorDate: Tue Apr 22 00:04:06 2025 +0800

    feat: handel remove dags (#49504)
---
 .../src/components/DagActions/DeleteDagButton.tsx  | 87 ++++++++++++++++++++++
 .../src/airflow/ui/src/pages/DagsList/DagCard.tsx  |  9 ++-
 .../src/airflow/ui/src/pages/DagsList/DagsList.tsx | 13 ++++
 .../src/airflow/ui/src/queries/useDeleteDag.ts     | 60 +++++++++++++++
 4 files changed, 168 insertions(+), 1 deletion(-)

diff --git 
a/airflow-core/src/airflow/ui/src/components/DagActions/DeleteDagButton.tsx 
b/airflow-core/src/airflow/ui/src/components/DagActions/DeleteDagButton.tsx
new file mode 100644
index 00000000000..78cb556557d
--- /dev/null
+++ b/airflow-core/src/airflow/ui/src/components/DagActions/DeleteDagButton.tsx
@@ -0,0 +1,87 @@
+/*!
+ * 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 { Text, Heading, HStack, useDisclosure } from "@chakra-ui/react";
+import { FiTrash2 } from "react-icons/fi";
+import { useNavigate } from "react-router-dom";
+
+import { Button, Dialog } from "src/components/ui";
+import ActionButton from "src/components/ui/ActionButton";
+import { useDeleteDag } from "src/queries/useDeleteDag";
+
+type DeleteDagButtonProps = {
+  readonly dagDisplayName: string;
+  readonly dagId: string;
+  readonly withText?: boolean;
+};
+
+const DeleteDagButton = ({ dagDisplayName, dagId, withText = true }: 
DeleteDagButtonProps) => {
+  const { onClose, onOpen, open } = useDisclosure();
+  const navigate = useNavigate();
+
+  const { isPending, mutate: deleteDag } = useDeleteDag({
+    dagId,
+    onSuccessConfirm: () => {
+      onClose();
+      navigate("/dags");
+    },
+  });
+
+  return (
+    <>
+      <ActionButton
+        actionName="Delete DAG"
+        colorPalette="red"
+        icon={<FiTrash2 />}
+        onClick={onOpen}
+        text="Delete DAG"
+        variant="solid"
+        withText={withText}
+      />
+
+      <Dialog.Root lazyMount onOpenChange={onClose} open={open} size="md" 
unmountOnExit>
+        <Dialog.Content backdrop>
+          <Dialog.Header>
+            <Heading size="lg">Delete DAG</Heading>
+          </Dialog.Header>
+          <Dialog.CloseTrigger />
+          <Dialog.Body>
+            <Text>
+              Are you sure you want to delete 
<strong>{dagDisplayName}</strong>? This action cannot be undone.
+            </Text>
+            <Text color="red.500" fontWeight="bold" mt={4}>
+              This will remove all metadata related to the DAG, including DAG 
Runs and Tasks.
+            </Text>
+          </Dialog.Body>
+          <Dialog.Footer>
+            <HStack justifyContent="flex-end" width="100%">
+              <Button onClick={onClose} variant="outline">
+                Cancel
+              </Button>
+              <Button colorPalette="red" loading={isPending} onClick={() => 
deleteDag({ dagId })}>
+                <FiTrash2 style={{ marginRight: "8px" }} /> Delete
+              </Button>
+            </HStack>
+          </Dialog.Footer>
+        </Dialog.Content>
+      </Dialog.Root>
+    </>
+  );
+};
+
+export default DeleteDagButton;
diff --git a/airflow-core/src/airflow/ui/src/pages/DagsList/DagCard.tsx 
b/airflow-core/src/airflow/ui/src/pages/DagsList/DagCard.tsx
index 55bd2ebb613..3edc8864765 100644
--- a/airflow-core/src/airflow/ui/src/pages/DagsList/DagCard.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/DagsList/DagCard.tsx
@@ -20,6 +20,7 @@ import { Box, Flex, HStack, SimpleGrid, Link, Spinner } from 
"@chakra-ui/react";
 import { Link as RouterLink } from "react-router-dom";
 
 import type { DAGWithLatestDagRunsResponse } from "openapi/requests/types.gen";
+import DeleteDagButton from "src/components/DagActions/DeleteDagButton";
 import DagRunInfo from "src/components/DagRunInfo";
 import { Stat } from "src/components/Stat";
 import { TogglePause } from "src/components/TogglePause";
@@ -52,8 +53,14 @@ export const DagCard = ({ dag }: Props) => {
           <DagTags tags={dag.tags} />
         </HStack>
         <HStack>
-          <TogglePause dagDisplayName={dag.dag_display_name} 
dagId={dag.dag_id} isPaused={dag.is_paused} />
+          <TogglePause
+            dagDisplayName={dag.dag_display_name}
+            dagId={dag.dag_id}
+            isPaused={dag.is_paused}
+            pr={2}
+          />
           <TriggerDAGButton dag={dag} withText={false} />
+          <DeleteDagButton dagDisplayName={dag.dag_display_name} 
dagId={dag.dag_id} withText={false} />
         </HStack>
       </Flex>
       <SimpleGrid columns={4} gap={1} height={20} px={3}>
diff --git a/airflow-core/src/airflow/ui/src/pages/DagsList/DagsList.tsx 
b/airflow-core/src/airflow/ui/src/pages/DagsList/DagsList.tsx
index 0a176661291..e3c5efdd4f5 100644
--- a/airflow-core/src/airflow/ui/src/pages/DagsList/DagsList.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/DagsList/DagsList.tsx
@@ -31,6 +31,7 @@ import { Link as RouterLink, useSearchParams } from 
"react-router-dom";
 import { useLocalStorage } from "usehooks-ts";
 
 import type { DagRunState, DAGWithLatestDagRunsResponse } from 
"openapi/requests/types.gen";
+import DeleteDagButton from "src/components/DagActions/DeleteDagButton";
 import DagRunInfo from "src/components/DagRunInfo";
 import { DataTable } from "src/components/DataTable";
 import { ToggleTableDisplay } from 
"src/components/DataTable/ToggleTableDisplay";
@@ -129,6 +130,18 @@ const columns: 
Array<ColumnDef<DAGWithLatestDagRunsResponse>> = [
     enableSorting: false,
     header: "",
   },
+  {
+    accessorKey: "delete",
+    cell: ({ row }) => (
+      <DeleteDagButton
+        dagDisplayName={row.original.dag_display_name}
+        dagId={row.original.dag_id}
+        withText={false}
+      />
+    ),
+    enableSorting: false,
+    header: "",
+  },
 ];
 
 const {
diff --git a/airflow-core/src/airflow/ui/src/queries/useDeleteDag.ts 
b/airflow-core/src/airflow/ui/src/queries/useDeleteDag.ts
new file mode 100644
index 00000000000..23ef5b14d2b
--- /dev/null
+++ b/airflow-core/src/airflow/ui/src/queries/useDeleteDag.ts
@@ -0,0 +1,60 @@
+/*!
+ * 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 { useQueryClient } from "@tanstack/react-query";
+
+import { useDagServiceDeleteDag } from "openapi/queries";
+import { useDagServiceGetDagKey, useDagServiceGetDagsKey } from 
"openapi/queries";
+import { toaster } from "src/components/ui";
+
+const onError = () => {
+  toaster.create({
+    description: "Delete DAG request failed",
+    title: "Failed to delete DAG",
+    type: "error",
+  });
+};
+
+export const useDeleteDag = ({
+  dagId,
+  onSuccessConfirm,
+}: {
+  dagId: string;
+  onSuccessConfirm: () => void;
+}) => {
+  const queryClient = useQueryClient();
+
+  const onSuccess = async () => {
+    const queryKeys = [[useDagServiceGetDagsKey], [useDagServiceGetDagKey, { 
dagId }]];
+
+    await Promise.all(queryKeys.map((key) => queryClient.invalidateQueries({ 
queryKey: key })));
+
+    toaster.create({
+      description: "The DAG deletion request was successful.",
+      title: "DAG Deleted Successfully",
+      type: "success",
+    });
+
+    onSuccessConfirm();
+  };
+
+  return useDagServiceDeleteDag({
+    onError,
+    onSuccess,
+  });
+};

Reply via email to