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";