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