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 51244221c38 Initialize dashboard page with health (#43090)
51244221c38 is described below

commit 51244221c38402ab0359df01099b573993524b63
Author: Brent Bovenzi <[email protected]>
AuthorDate: Wed Oct 16 21:06:55 2024 +0100

    Initialize dashboard page with health (#43090)
---
 airflow/ui/src/App.tsx                             |  5 +-
 airflow/ui/src/layouts/Nav/Nav.tsx                 | 17 +-----
 airflow/ui/src/pages/DagsList/DagsList.tsx         |  2 +-
 .../src/{App.tsx => pages/Dashboard/Dashboard.tsx} | 19 +++---
 airflow/ui/src/pages/Dashboard/Health.tsx          | 67 ++++++++++++++++++++++
 airflow/ui/src/pages/Dashboard/HealthSection.tsx   | 59 +++++++++++++++++++
 airflow/ui/src/pages/Dashboard/HealthTag.tsx       | 59 +++++++++++++++++++
 .../ui/src/{App.tsx => pages/Dashboard/index.tsx}  | 14 +----
 airflow/ui/src/{App.tsx => utils/capitalize.ts}    | 17 ++----
 airflow/ui/src/{App.tsx => utils/index.ts}         | 15 +----
 10 files changed, 207 insertions(+), 67 deletions(-)

diff --git a/airflow/ui/src/App.tsx b/airflow/ui/src/App.tsx
index 3c5e9d866f0..b8c73c35c7f 100644
--- a/airflow/ui/src/App.tsx
+++ b/airflow/ui/src/App.tsx
@@ -16,16 +16,17 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-import { Navigate, Route, Routes } from "react-router-dom";
+import { Route, Routes } from "react-router-dom";
 
 import { DagsList } from "src/pages/DagsList";
+import { Dashboard } from "src/pages/Dashboard";
 
 import { BaseLayout } from "./layouts/BaseLayout";
 
 export const App = () => (
   <Routes>
     <Route element={<BaseLayout />} path="/">
-      <Route element={<Navigate to="dags" />} index />
+      <Route element={<Dashboard />} index />
       <Route element={<DagsList />} path="dags" />
     </Route>
   </Routes>
diff --git a/airflow/ui/src/layouts/Nav/Nav.tsx 
b/airflow/ui/src/layouts/Nav/Nav.tsx
index 84ab23497cf..71b0843831a 100644
--- a/airflow/ui/src/layouts/Nav/Nav.tsx
+++ b/airflow/ui/src/layouts/Nav/Nav.tsx
@@ -16,14 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-import {
-  Box,
-  Flex,
-  Icon,
-  Link,
-  useColorModeValue,
-  VStack,
-} from "@chakra-ui/react";
+import { Box, Flex, Icon, useColorModeValue, VStack } from "@chakra-ui/react";
 import { motion } from "framer-motion";
 import {
   FiBarChart2,
@@ -68,12 +61,7 @@ export const Nav = () => {
         >
           <Icon as={AirflowPin} height="35px" width="35px" />
         </Box>
-        <NavButton
-          icon={<FiHome size="1.75rem" />}
-          isDisabled
-          title="Home"
-          to="/"
-        />
+        <NavButton icon={<FiHome size="1.75rem" />} title="Home" to="/" />
         <NavButton
           icon={<DagIcon height={7} width={7} />}
           title="DAGs"
@@ -106,7 +94,6 @@ export const Nav = () => {
       </Flex>
       <Flex flexDir="column">
         <NavButton
-          as={Link}
           icon={<FiCornerUpLeft size="1.5rem" />}
           title="Return to legacy UI"
           to={import.meta.env.VITE_LEGACY_API_URL}
diff --git a/airflow/ui/src/pages/DagsList/DagsList.tsx 
b/airflow/ui/src/pages/DagsList/DagsList.tsx
index 623b8a3b4ba..5e132487329 100644
--- a/airflow/ui/src/pages/DagsList/DagsList.tsx
+++ b/airflow/ui/src/pages/DagsList/DagsList.tsx
@@ -46,7 +46,7 @@ import {
   SearchParamsKeys,
   type SearchParamsKeysType,
 } from "src/constants/searchParams";
-import { pluralize } from "src/utils/pluralize";
+import { pluralize } from "src/utils";
 
 import { DagCard } from "./DagCard";
 import { DagsFilters } from "./DagsFilters";
diff --git a/airflow/ui/src/App.tsx 
b/airflow/ui/src/pages/Dashboard/Dashboard.tsx
similarity index 68%
copy from airflow/ui/src/App.tsx
copy to airflow/ui/src/pages/Dashboard/Dashboard.tsx
index 3c5e9d866f0..f7ef15013bb 100644
--- a/airflow/ui/src/App.tsx
+++ b/airflow/ui/src/pages/Dashboard/Dashboard.tsx
@@ -16,17 +16,16 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-import { Navigate, Route, Routes } from "react-router-dom";
+import { Box, Heading } from "@chakra-ui/react";
 
-import { DagsList } from "src/pages/DagsList";
+import { Health } from "./Health";
 
-import { BaseLayout } from "./layouts/BaseLayout";
+export const Dashboard = () => (
+  <Box>
+    <Heading mb={4}>Welcome</Heading>
 
-export const App = () => (
-  <Routes>
-    <Route element={<BaseLayout />} path="/">
-      <Route element={<Navigate to="dags" />} index />
-      <Route element={<DagsList />} path="dags" />
-    </Route>
-  </Routes>
+    <Box>
+      <Health />
+    </Box>
+  </Box>
 );
diff --git a/airflow/ui/src/pages/Dashboard/Health.tsx 
b/airflow/ui/src/pages/Dashboard/Health.tsx
new file mode 100644
index 00000000000..dfad9d75c05
--- /dev/null
+++ b/airflow/ui/src/pages/Dashboard/Health.tsx
@@ -0,0 +1,67 @@
+/*!
+ * 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 { Box, Flex, Heading, HStack } from "@chakra-ui/react";
+import { FiClipboard } from "react-icons/fi";
+
+import { useMonitorServiceGetHealth } from "openapi/queries";
+import { ErrorAlert } from "src/components/ErrorAlert";
+
+import { HealthTag } from "./HealthTag";
+
+export const Health = () => {
+  const { data, error, isLoading } = useMonitorServiceGetHealth();
+
+  return (
+    <Box>
+      <Flex color="gray.500" mb={2}>
+        <FiClipboard />
+        <Heading ml={1} size="xs">
+          Health
+        </Heading>
+      </Flex>
+      <ErrorAlert error={error} />
+      <HStack alignItems="center" spacing={2}>
+        <HealthTag
+          isLoading={isLoading}
+          status={data?.metadatabase.status}
+          title="MetaDatabase"
+        />
+        <HealthTag
+          isLoading={isLoading}
+          latestHeartbeat={data?.scheduler.latest_scheduler_heartbeat}
+          status={data?.scheduler.status}
+          title="Scheduler"
+        />
+        <HealthTag
+          isLoading={isLoading}
+          latestHeartbeat={data?.triggerer.latest_triggerer_heartbeat}
+          status={data?.triggerer.status}
+          title="Triggerer"
+        />
+        {/* TODO add is_standalone to API to determine when to render this */}
+        <HealthTag
+          isLoading={isLoading}
+          latestHeartbeat={data?.dag_processor.latest_dag_processor_heartbeat}
+          status={data?.dag_processor.status}
+          title="Dag Processor"
+        />
+      </HStack>
+    </Box>
+  );
+};
diff --git a/airflow/ui/src/pages/Dashboard/HealthSection.tsx 
b/airflow/ui/src/pages/Dashboard/HealthSection.tsx
new file mode 100644
index 00000000000..85e545c6341
--- /dev/null
+++ b/airflow/ui/src/pages/Dashboard/HealthSection.tsx
@@ -0,0 +1,59 @@
+/*!
+ * 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 { Skeleton, Tag, TagLabel, Text, Tooltip } from "@chakra-ui/react";
+
+export const HealthSection = ({
+  isLoading,
+  latestHeartbeat,
+  status,
+  title,
+}: {
+  readonly isLoading: boolean;
+  readonly latestHeartbeat?: null | string;
+  readonly status?: null | string;
+  readonly title: string;
+}) => {
+  if (isLoading) {
+    return <Skeleton borderRadius="full" height={34} width={100} />;
+  }
+
+  return (
+    <Tooltip
+      hasArrow
+      isDisabled={!Boolean(latestHeartbeat)}
+      label={
+        <div>
+          <Text>Status: {status}</Text>
+          <Text>Last Heartbeat: {latestHeartbeat}</Text>
+        </div>
+      }
+      shouldWrapChildren
+    >
+      <Tag
+        borderColor={status === "healthy" ? "success.100" : "error.100"}
+        borderRadius="full"
+        borderWidth={1}
+        colorScheme={status === "healthy" ? "success" : "error"}
+        size="lg"
+      >
+        <TagLabel>{title}</TagLabel>
+      </Tag>
+    </Tooltip>
+  );
+};
diff --git a/airflow/ui/src/pages/Dashboard/HealthTag.tsx 
b/airflow/ui/src/pages/Dashboard/HealthTag.tsx
new file mode 100644
index 00000000000..335d2f91216
--- /dev/null
+++ b/airflow/ui/src/pages/Dashboard/HealthTag.tsx
@@ -0,0 +1,59 @@
+/*!
+ * 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 { Skeleton, Tag, TagLabel, Text, Tooltip } from "@chakra-ui/react";
+
+import { capitalize } from "src/utils";
+
+export const HealthTag = ({
+  isLoading,
+  latestHeartbeat,
+  status,
+  title,
+}: {
+  readonly isLoading: boolean;
+  readonly latestHeartbeat?: null | string;
+  readonly status?: null | string;
+  readonly title: string;
+}) => {
+  if (isLoading) {
+    return <Skeleton borderRadius="full" height={8} width={24} />;
+  }
+
+  return (
+    <Tooltip
+      hasArrow
+      isDisabled={!Boolean(latestHeartbeat)}
+      label={
+        <div>
+          <Text>Status: {capitalize(status)}</Text>
+          <Text>Last Heartbeat: {latestHeartbeat}</Text>
+        </div>
+      }
+      shouldWrapChildren
+    >
+      <Tag
+        borderRadius="full"
+        colorScheme={status === "healthy" ? "green" : "red"}
+        size="lg"
+      >
+        <TagLabel>{title}</TagLabel>
+      </Tag>
+    </Tooltip>
+  );
+};
diff --git a/airflow/ui/src/App.tsx b/airflow/ui/src/pages/Dashboard/index.tsx
similarity index 68%
copy from airflow/ui/src/App.tsx
copy to airflow/ui/src/pages/Dashboard/index.tsx
index 3c5e9d866f0..bf6b0036cde 100644
--- a/airflow/ui/src/App.tsx
+++ b/airflow/ui/src/pages/Dashboard/index.tsx
@@ -16,17 +16,5 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-import { Navigate, Route, Routes } from "react-router-dom";
 
-import { DagsList } from "src/pages/DagsList";
-
-import { BaseLayout } from "./layouts/BaseLayout";
-
-export const App = () => (
-  <Routes>
-    <Route element={<BaseLayout />} path="/">
-      <Route element={<Navigate to="dags" />} index />
-      <Route element={<DagsList />} path="dags" />
-    </Route>
-  </Routes>
-);
+export { Dashboard } from "./Dashboard";
diff --git a/airflow/ui/src/App.tsx b/airflow/ui/src/utils/capitalize.ts
similarity index 68%
copy from airflow/ui/src/App.tsx
copy to airflow/ui/src/utils/capitalize.ts
index 3c5e9d866f0..bd941c46677 100644
--- a/airflow/ui/src/App.tsx
+++ b/airflow/ui/src/utils/capitalize.ts
@@ -16,17 +16,8 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-import { Navigate, Route, Routes } from "react-router-dom";
 
-import { DagsList } from "src/pages/DagsList";
-
-import { BaseLayout } from "./layouts/BaseLayout";
-
-export const App = () => (
-  <Routes>
-    <Route element={<BaseLayout />} path="/">
-      <Route element={<Navigate to="dags" />} index />
-      <Route element={<DagsList />} path="dags" />
-    </Route>
-  </Routes>
-);
+// eslint-disable-next-line perfectionist/sort-union-types
+export const capitalize = (string: string | null | undefined) =>
+  // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
+  string ? string.charAt(0).toUpperCase() + string.slice(1).toLowerCase() : "";
diff --git a/airflow/ui/src/App.tsx b/airflow/ui/src/utils/index.ts
similarity index 68%
copy from airflow/ui/src/App.tsx
copy to airflow/ui/src/utils/index.ts
index 3c5e9d866f0..7083089c2b1 100644
--- a/airflow/ui/src/App.tsx
+++ b/airflow/ui/src/utils/index.ts
@@ -16,17 +16,6 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-import { Navigate, Route, Routes } from "react-router-dom";
 
-import { DagsList } from "src/pages/DagsList";
-
-import { BaseLayout } from "./layouts/BaseLayout";
-
-export const App = () => (
-  <Routes>
-    <Route element={<BaseLayout />} path="/">
-      <Route element={<Navigate to="dags" />} index />
-      <Route element={<DagsList />} path="dags" />
-    </Route>
-  </Routes>
-);
+export { capitalize } from "./capitalize";
+export { pluralize } from "./pluralize";

Reply via email to