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 e4177217f46 Feature/aip 38 add dag warning (#49236)
e4177217f46 is described below
commit e4177217f46cd70c89e5cc818405f2b5efbf5516
Author: Aritra Basu <[email protected]>
AuthorDate: Tue Apr 15 19:48:15 2025 +0530
Feature/aip 38 add dag warning (#49236)
* AIP-38 :: created new dag warning banner added to dag details page
* AIP-38 :: replaced type with existing response type
* AIP-38 :: updated key with timestamp to make unique
* AIP-38 :: removed console log
* AIP-38 :: adding warnings & errors to accordion
* AIP-38 :: overflow scroll support for accordion
* Updated warning to be in a modal
* Updated margin
* Updating heading of dialog and button name
---------
Co-authored-by: Tyrell C <[email protected]>
---
.../src/airflow/ui/src/components/WarningAlert.tsx | 39 ++++++++++++
.../ui/src/components/ui/DagWarningsModal.tsx | 70 ++++++++++++++++++++++
.../ui/src/layouts/Details/DetailsLayout.tsx | 36 +++++++++--
3 files changed, 141 insertions(+), 4 deletions(-)
diff --git a/airflow-core/src/airflow/ui/src/components/WarningAlert.tsx
b/airflow-core/src/airflow/ui/src/components/WarningAlert.tsx
new file mode 100644
index 00000000000..69cbe4e4186
--- /dev/null
+++ b/airflow-core/src/airflow/ui/src/components/WarningAlert.tsx
@@ -0,0 +1,39 @@
+/*!
+ * 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 { Flex } from "@chakra-ui/react";
+
+import { Alert } from "./ui";
+
+type Props = {
+ readonly warning?: {
+ message: string;
+ };
+};
+
+export const WarningAlert = ({ warning }: Props) => {
+ if (!Boolean(warning)) {
+ return undefined;
+ }
+
+ return (
+ <Alert status="warning">
+ <Flex align="center">{warning?.message}</Flex>
+ </Alert>
+ );
+};
diff --git a/airflow-core/src/airflow/ui/src/components/ui/DagWarningsModal.tsx
b/airflow-core/src/airflow/ui/src/components/ui/DagWarningsModal.tsx
new file mode 100644
index 00000000000..80ff7c63304
--- /dev/null
+++ b/airflow-core/src/airflow/ui/src/components/ui/DagWarningsModal.tsx
@@ -0,0 +1,70 @@
+/*!
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import { Heading, HStack, Spacer, VStack } from "@chakra-ui/react";
+import { LuFileWarning } from "react-icons/lu";
+
+import type { DAGWarningResponse } from "openapi/requests/types.gen";
+import { Dialog } from "src/components/ui";
+import { pluralize } from "src/utils";
+
+import { ErrorAlert } from "../ErrorAlert";
+import { WarningAlert } from "../WarningAlert";
+
+type ImportDAGErrorModalProps = {
+ error?: unknown;
+ onClose: () => void;
+ open: boolean;
+ warnings?: Array<DAGWarningResponse>;
+};
+
+export const DAGWarningsModal: React.FC<ImportDAGErrorModalProps> = ({ error,
onClose, open, warnings }) => (
+ <Dialog.Root onOpenChange={onClose} open={open} scrollBehavior="inside"
size="xl">
+ <Dialog.Content backdrop>
+ <Dialog.Header>
+ <HStack fontSize="xl">
+ <LuFileWarning />
+ <Heading>
+ {Boolean(error) ? "1 Error" : ""}
+ {Boolean(error) && warnings?.length !== undefined &&
warnings.length > 0 ? " and " : ""}
+ {warnings?.length !== undefined && warnings.length > 0
+ ? pluralize("Warning", warnings.length)
+ : ""}
+ </Heading>
+ </HStack>
+ </Dialog.Header>
+
+ <Dialog.CloseTrigger />
+
+ <Dialog.Body>
+ {Boolean(error) && (
+ <VStack>
+ <ErrorAlert error={error} />
+ <Spacer />
+ </VStack>
+ )}
+ {warnings?.map((warning) => (
+ <VStack key={warning.message}>
+ <WarningAlert warning={warning} />
+ <Spacer />
+ </VStack>
+ ))}
+ </Dialog.Body>
+ </Dialog.Content>
+ </Dialog.Root>
+);
diff --git a/airflow-core/src/airflow/ui/src/layouts/Details/DetailsLayout.tsx
b/airflow-core/src/airflow/ui/src/layouts/Details/DetailsLayout.tsx
index 560c78be7d1..69d32034fc4 100644
--- a/airflow-core/src/airflow/ui/src/layouts/Details/DetailsLayout.tsx
+++ b/airflow-core/src/airflow/ui/src/layouts/Details/DetailsLayout.tsx
@@ -16,21 +16,23 @@
* specific language governing permissions and limitations
* under the License.
*/
-import { Box, HStack, Flex } from "@chakra-ui/react";
+import { Box, HStack, Flex, useDisclosure } from "@chakra-ui/react";
import { useReactFlow } from "@xyflow/react";
import type { PropsWithChildren, ReactNode } from "react";
+import { LuFileWarning } from "react-icons/lu";
import { Panel, PanelGroup, PanelResizeHandle } from "react-resizable-panels";
import { Outlet, useParams } from "react-router-dom";
import { useLocalStorage } from "usehooks-ts";
-import { useDagServiceGetDag } from "openapi/queries";
+import { useDagServiceGetDag, useDagWarningServiceListDagWarnings } from
"openapi/queries";
import type { DAGResponse } from "openapi/requests/types.gen";
import BackfillBanner from "src/components/Banner/BackfillBanner";
-import { ErrorAlert } from "src/components/ErrorAlert";
import { SearchDagsButton } from "src/components/SearchDags";
import TriggerDAGButton from "src/components/TriggerDag/TriggerDAGButton";
import { ProgressBar } from "src/components/ui";
import { Toaster } from "src/components/ui";
+import ActionButton from "src/components/ui/ActionButton";
+import { DAGWarningsModal } from "src/components/ui/DagWarningsModal";
import { OpenGroupsProvider } from "src/context/openGroups";
import { DagBreadcrumb } from "./DagBreadcrumb";
@@ -56,6 +58,11 @@ export const DetailsLayout = ({ children, error, isLoading,
tabs }: Props) => {
const { fitView, getZoom } = useReactFlow();
+ const { data: warningData } = useDagWarningServiceListDagWarnings({
+ dagId,
+ });
+ const { onClose, onOpen, open } = useDisclosure();
+
return (
<OpenGroupsProvider dagId={dagId}>
<HStack justifyContent="space-between" mb={2}>
@@ -90,7 +97,28 @@ export const DetailsLayout = ({ children, error, isLoading,
tabs }: Props) => {
<Panel defaultSize={dagView === "graph" ? 30 : 80} minSize={20}>
<Box display="flex" flexDirection="column" h="100%">
{children}
- <ErrorAlert error={error} />
+ {Boolean(error) || (warningData?.dag_warnings.length ?? 0) > 0 ?
(
+ <>
+ <ActionButton
+ actionName="Dag warnings/errors"
+ colorPalette={Boolean(error) ? "red" : "orange"}
+ icon={<LuFileWarning />}
+ margin="2"
+ marginBottom="-1"
+ onClick={onOpen}
+ rounded="full"
+ text={String(warningData?.total_entries ?? 0 +
Number(error))}
+ variant="solid"
+ />
+
+ <DAGWarningsModal
+ error={error}
+ onClose={onClose}
+ open={open}
+ warnings={warningData?.dag_warnings}
+ />
+ </>
+ ) : undefined}
<ProgressBar size="xs" visibility={isLoading ? "visible" :
"hidden"} />
<NavTabs tabs={tabs} />
<Box h="100%" overflow="auto" px={2}>