Repository: apache/airflow
Pull request: Feature/aip 38 add dag warning
closes: https://github.com/apache/airflow/issues/47430
<!--
 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.
 -->

<!--
Thank you for contributing! Please make sure that your code changes
are covered with tests. And in case of new features or big changes
remember to adjust the documentation.

Feel free to ping committers for the review!

In case of an existing issue, reference it using one of the following:

closes: #ISSUE
related: #ISSUE

How to write a good git commit message:
http://chris.beams.io/posts/git-commit/
-->



<!-- Please keep an empty line above the dashes. -->
---
**^ Add meaningful description above**
Read the **[Pull Request Guidelines](https://github.com/apache/airflow/blob/main/contributing-docs/05_pull_requests.rst#pull-request-guidelines)** for more information.
In case of fundamental code changes, an Airflow Improvement Proposal ([AIP](https://cwiki.apache.org/confluence/display/AIRFLOW/Airflow+Improvement+Proposals)) is needed.
In case of a new dependency, check compliance with the [ASF 3rd Party License Policy](https://www.apache.org/legal/resolved.html#category-x).
In case of backwards incompatible changes please leave a note in a newsfragment file, named `{pr_number}.significant.rst` or `{issue_number}.significant.rst`, in [airflow-core/newsfragments](https://github.com/apache/airflow/tree/main/airflow-core/newsfragments).


Problem statement:
AIP-38 | Add Dag Warning banner to Dag Details page
### Body

Use the `public/dagWarnings` API endpoint to see if there are any warnings for a dag_id and display a banner on that Dag's detail page

### Committer

- [x] I acknowledge that I am a maintainer/committer of the Apache Airflow project.

Hints from issue discussion:
I want to work on this issue if I could be assigned it. I am looking for a first-issue contribution to an OSS software project and am a senior CS student
@prchoward Go for it. Let me know if you have any questions
I would appreciate the opportunity to work on this issue if it can be assigned to me. As a senior Computer Science student, I am seeking my first contribution to an open-source project and believe this would be a great starting point.
@prchoward @bbovenzi  Is this Issue resolved?, I would appreciate the opportunity to work on this issue if it assigned to me.

I haven't heard from @prchoward so @JagadishRamakrishnan  you can take it.
Hello Sir, This is my first open source project and also I am fresher .So It will take some additional time to resolve. 

 @bbovenzi 

We are in a time crunch for releasing Airflow 3. So if you can't get it done by March 17th. Just let me know.
Sorry to say this ,This is too short period for me .And I am Quit this issue
@bbovenzi 
Hey @bbovenzi, I know you're in a time crunch and this issue is pretty close to hitting the deadline, but I'd love to try to tackle this as my first issue. Do you mind if I take a stab? I have my local env set up. 
Hey @bbovenzi, decided to tackle this tonight, PR is here ready for review: https://github.com/apache/airflow/pull/47821

Commit message: Updated warning to be in a modal

Patch to review:
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 0000000000000..69cbe4e418611
--- /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 0000000000000..5ba99ff902ff9
--- /dev/null
+++ b/airflow-core/src/airflow/ui/src/components/ui/DagWarningsModal.tsx
@@ -0,0 +1,53 @@
+/*!
+ * 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 } 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 { 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>Dag Warnings / Errors</Heading>
+        </HStack>
+      </Dialog.Header>
+
+      <Dialog.CloseTrigger />
+
+      <Dialog.Body>
+        {Boolean(error) && <ErrorAlert error={error} />}
+        {warnings?.map((warning) => <WarningAlert key={warning.message} warning={warning} />)}
+      </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 560c78be7d113..ddb447a57e3f7 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="Trigger Dag"
+                    colorPalette={Boolean(error) ? "red" : "orange"}
+                    icon={<LuFileWarning />}
+                    marginBottom="-1"
+                    marginTop="2"
+                    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}>


Code review comments:
diff_hunk: @@ -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="Trigger Dag"
path: airflow-core/src/airflow/ui/src/layouts/Details/DetailsLayout.tsx
text: @user1:
wrong name

@author:
Oops, missed that while copying the action button😅

@author:
done

diff_hunk: @@ -0,0 +1,53 @@
+/*!
+ * 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 } 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 { 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>Dag Warnings / Errors</Heading>
path: airflow-core/src/airflow/ui/src/components/ui/DagWarningsModal.tsx
text: @user1:
Let's make this header a real summary, ex `1 Error and 3 Warnings`.

@author:
Ack

@author:
done