diff --git a/airflow/ui/src/pages/Dashboard/Dashboard.tsx b/airflow/ui/src/pages/Dashboard/Dashboard.tsx
index 0b8d265772227..38727b0809e33 100644
--- a/airflow/ui/src/pages/Dashboard/Dashboard.tsx
+++ b/airflow/ui/src/pages/Dashboard/Dashboard.tsx
@@ -19,7 +19,7 @@
import { Box, Heading } from "@chakra-ui/react";
import { Health } from "./Health";
-import { Metrics } from "./Metrics";
+import { HistoricalMetrics } from "./HistoricalMetrics";
export const Dashboard = () => (
@@ -28,7 +28,7 @@ export const Dashboard = () => (
-
+
);
diff --git a/airflow/ui/src/pages/Dashboard/HistoricalMetrics/DagRunMetrics.tsx b/airflow/ui/src/pages/Dashboard/HistoricalMetrics/DagRunMetrics.tsx
new file mode 100644
index 0000000000000..4b9730f544722
--- /dev/null
+++ b/airflow/ui/src/pages/Dashboard/HistoricalMetrics/DagRunMetrics.tsx
@@ -0,0 +1,52 @@
+/*!
+ * 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, Heading, HStack } from "@chakra-ui/react";
+import type { DAGRunStates } from "openapi-gen/requests/types.gen";
+
+import { MetricSection } from "./MetricSection";
+import { MetricsBadge } from "./MetricsBadge";
+
+type DagRunMetricsProps = {
+ readonly dagRunStates: DAGRunStates;
+ readonly total: number;
+};
+
+const DAGRUN_STATES: Array = [
+ "queued",
+ "running",
+ "success",
+ "failed",
+];
+
+export const DagRunMetrics = ({ dagRunStates, total }: DagRunMetricsProps) => (
+
+
+
+ Dag Runs
+
+ {DAGRUN_STATES.map((state) => (
+
+ ))}
+
+);
diff --git a/airflow/ui/src/pages/Dashboard/HistoricalMetrics/HistoricalMetrics.tsx b/airflow/ui/src/pages/Dashboard/HistoricalMetrics/HistoricalMetrics.tsx
new file mode 100644
index 0000000000000..06ff59b4b09ff
--- /dev/null
+++ b/airflow/ui/src/pages/Dashboard/HistoricalMetrics/HistoricalMetrics.tsx
@@ -0,0 +1,127 @@
+/*!
+ * 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,
+ HStack,
+ VStack,
+ Text,
+ createListCollection,
+ type SelectValueChangeDetails,
+} from "@chakra-ui/react";
+import dayjs from "dayjs";
+import { useState } from "react";
+import { FiCalendar } from "react-icons/fi";
+
+import { useDashboardServiceHistoricalMetrics } from "openapi/queries";
+import { ErrorAlert } from "src/components/ErrorAlert";
+import Time from "src/components/Time";
+import { Select } from "src/components/ui";
+
+import { DagRunMetrics } from "./DagRunMetrics";
+import { TaskInstanceMetrics } from "./TaskInstanceMetrics";
+
+const timeOptions = createListCollection({
+ items: [
+ { label: "Last 1 hour", value: "1" },
+ { label: "Last 8 hours", value: "8" },
+ { label: "Last 12 hours", value: "12" },
+ { label: "Last 24 hours", value: "24" },
+ ],
+});
+
+export const HistoricalMetrics = () => {
+ const defaultHour = "8";
+ const now = dayjs();
+ const [startDate, setStartDate] = useState(
+ now.subtract(Number(defaultHour), "hour").toISOString(),
+ );
+ const [endDate, setEndDate] = useState(now.toISOString());
+
+ const { data, error, isLoading } = useDashboardServiceHistoricalMetrics({
+ endDate,
+ startDate,
+ });
+
+ const dagRunTotal = data
+ ? Object.values(data.dag_run_states).reduce(
+ (partialSum, value) => partialSum + value,
+ 0,
+ )
+ : 0;
+
+ const taskRunTotal = data
+ ? Object.values(data.task_instance_states).reduce(
+ (partialSum, value) => partialSum + value,
+ 0,
+ )
+ : 0;
+
+ const handleTimeChange = ({
+ value,
+ }: SelectValueChangeDetails>) => {
+ const cnow = dayjs();
+
+ setStartDate(cnow.subtract(Number(value[0]), "hour").toISOString());
+ setEndDate(cnow.toISOString());
+ };
+
+ return (
+
+
+
+
+
+
+
+
+
+
+ {timeOptions.items.map((option) => (
+
+ {option.label}
+
+ ))}
+
+
+
+ -
+
+
+ {!isLoading && data !== undefined && (
+
+
+
+
+ )}
+
+
+ );
+};
diff --git a/airflow/ui/src/pages/Dashboard/HistoricalMetrics/MetricSection.tsx b/airflow/ui/src/pages/Dashboard/HistoricalMetrics/MetricSection.tsx
new file mode 100644
index 0000000000000..0dd41b6abc668
--- /dev/null
+++ b/airflow/ui/src/pages/Dashboard/HistoricalMetrics/MetricSection.tsx
@@ -0,0 +1,73 @@
+/*!
+ * 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, HStack, VStack, Text } from "@chakra-ui/react";
+
+import { capitalize } from "src/utils";
+import { stateColor } from "src/utils/stateColor";
+
+import { MetricsBadge } from "./MetricsBadge";
+
+const BAR_WIDTH = 100;
+const BAR_HEIGHT = 5;
+
+type MetricSectionProps = {
+ readonly runs: number;
+ readonly state: string;
+ readonly total: number;
+};
+
+export const MetricSection = ({ runs, state, total }: MetricSectionProps) => {
+ // Calculate the given state as a percentage of total and draw a bar
+ // in state's color with width as state's percentage and remaining width filed as gray
+ const statePercent = total === 0 ? 0 : ((runs / total) * 100).toFixed(2);
+ const stateWidth = total === 0 ? 0 : (runs / total) * BAR_WIDTH;
+ const remainingWidth = BAR_WIDTH - stateWidth;
+
+ return (
+
+
+
+
+ {capitalize(state)}
+
+ {statePercent}%
+
+
+
+
+
+
+ );
+};
diff --git a/airflow/ui/src/pages/Dashboard/HistoricalMetrics/MetricsBadge.tsx b/airflow/ui/src/pages/Dashboard/HistoricalMetrics/MetricsBadge.tsx
new file mode 100644
index 0000000000000..26dd3f1767f54
--- /dev/null
+++ b/airflow/ui/src/pages/Dashboard/HistoricalMetrics/MetricsBadge.tsx
@@ -0,0 +1,30 @@
+/*!
+ * 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 { Badge } from "@chakra-ui/react";
+
+type MetricBadgeProps = {
+ readonly color: string;
+ readonly runs: number;
+};
+
+export const MetricsBadge = ({ color, runs }: MetricBadgeProps) => (
+
+ {runs}
+
+);
diff --git a/airflow/ui/src/pages/Dashboard/HistoricalMetrics/TaskInstanceMetrics.tsx b/airflow/ui/src/pages/Dashboard/HistoricalMetrics/TaskInstanceMetrics.tsx
new file mode 100644
index 0000000000000..507a0eb3b50c1
--- /dev/null
+++ b/airflow/ui/src/pages/Dashboard/HistoricalMetrics/TaskInstanceMetrics.tsx
@@ -0,0 +1,57 @@
+/*!
+ * 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, Heading, HStack } from "@chakra-ui/react";
+import type { TaskInstanceStateCount } from "openapi-gen/requests/types.gen";
+
+import { MetricSection } from "./MetricSection";
+import { MetricsBadge } from "./MetricsBadge";
+
+type TaskInstanceMetricsProps = {
+ readonly taskInstanceStates: TaskInstanceStateCount;
+ readonly total: number;
+};
+
+const TASK_STATES: Array = [
+ "queued",
+ "running",
+ "success",
+ "failed",
+ "skipped",
+];
+
+export const TaskInstanceMetrics = ({
+ taskInstanceStates,
+ total,
+}: TaskInstanceMetricsProps) => (
+
+
+
+ Task Instances
+
+
+ {TASK_STATES.map((state) => (
+
+ ))}
+
+);
diff --git a/airflow/ui/src/pages/Dashboard/HistoricalMetrics/index.tsx b/airflow/ui/src/pages/Dashboard/HistoricalMetrics/index.tsx
new file mode 100644
index 0000000000000..e96bf8d5f75bd
--- /dev/null
+++ b/airflow/ui/src/pages/Dashboard/HistoricalMetrics/index.tsx
@@ -0,0 +1,20 @@
+/*!
+ * 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.
+ */
+
+export { HistoricalMetrics } from "./HistoricalMetrics";
diff --git a/airflow/ui/src/pages/Dashboard/Metrics.tsx b/airflow/ui/src/pages/Dashboard/Metrics.tsx
deleted file mode 100644
index d05af7cc285d1..0000000000000
--- a/airflow/ui/src/pages/Dashboard/Metrics.tsx
+++ /dev/null
@@ -1,244 +0,0 @@
-/*!
- * 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,
- VStack,
- Text,
- createListCollection,
- type SelectValueChangeDetails,
-} from "@chakra-ui/react";
-import dayjs from "dayjs";
-import type {
- TaskInstanceStateCount,
- DAGRunStates,
-} from "openapi-gen/requests/types.gen";
-import { useState } from "react";
-import { FiCalendar } from "react-icons/fi";
-
-import { useDashboardServiceHistoricalMetrics } from "openapi/queries";
-import { ErrorAlert } from "src/components/ErrorAlert";
-import Time from "src/components/Time";
-import { Select } from "src/components/ui";
-import { capitalize } from "src/utils";
-import { stateColor } from "src/utils/stateColor";
-
-const BAR_WIDTH = 100;
-const BAR_HEIGHT = 5;
-const DAGRUN_STATES: Array = [
- "queued",
- "running",
- "success",
- "failed",
-];
-const TASK_STATES: Array = [
- "queued",
- "running",
- "success",
- "failed",
- "skipped",
-];
-const timeOptions = createListCollection({
- items: [
- { label: "Last 1 hour", value: "1" },
- { label: "Last 8 hours", value: "8" },
- { label: "Last 12 hours", value: "12" },
- { label: "Last 24 hours", value: "24" },
- ],
-});
-
-type Props = {
- readonly runs: number;
- readonly state: string;
- readonly total: number;
-};
-
-type DagRunStateInfoProps = {
- readonly dagRunStates: DAGRunStates;
- readonly total: number;
-};
-
-type TaskRunStateInfoProps = {
- readonly taskRunStates: TaskInstanceStateCount;
- readonly total: number;
-};
-
-const StateInfo = ({ runs, state, total }: Props) => {
- // Calculate the given state as a percentage of total and draw a bar
- // in state's color with width as state's percentage and remaining width filed as gray
- const statePercent = total === 0 ? 0 : ((runs / total) * 100).toFixed(2);
- const stateWidth = total === 0 ? 0 : (runs / total) * BAR_WIDTH;
- const remainingWidth = BAR_WIDTH - stateWidth;
-
- return (
-
-
-
-
- {runs}
-
- {capitalize(state)}
-
- {statePercent}%
-
-
-
-
-
-
- );
-};
-
-const DagRunStateInfo = ({ dagRunStates, total }: DagRunStateInfoProps) =>
- DAGRUN_STATES.map((state) => (
-
- ));
-
-const TaskRunStateInfo = ({ taskRunStates, total }: TaskRunStateInfoProps) =>
- TASK_STATES.map((state) => (
-
- ));
-
-export const Metrics = () => {
- const defaultHour = "8";
- const now = dayjs();
- const [startDate, setStartDate] = useState(
- now.subtract(Number(defaultHour), "hour").toISOString(),
- );
- const [endDate, setEndDate] = useState(now.toISOString());
-
- const { data, error, isLoading } = useDashboardServiceHistoricalMetrics({
- endDate,
- startDate,
- });
-
- const dagRunTotal = data
- ? Object.values(data.dag_run_states).reduce(
- (partialSum, value) => partialSum + value,
- 0,
- )
- : 0;
-
- const taskRunTotal = data
- ? Object.values(data.task_instance_states).reduce(
- (partialSum, value) => partialSum + value,
- 0,
- )
- : 0;
-
- const handleTimeChange = ({
- value,
- }: SelectValueChangeDetails>) => {
- const cnow = dayjs();
-
- setStartDate(cnow.subtract(Number(value[0]), "hour").toISOString());
- setEndDate(cnow.toISOString());
- };
-
- return (
-
-
-
-
-
-
-
-
-
-
- {timeOptions.items.map((option) => (
-
- {option.label}
-
- ))}
-
-
-
- -
-
-
- {!isLoading && data !== undefined && (
-
-
-
-
- {dagRunTotal}
-
- Dag Runs
-
-
-
-
-
-
- {taskRunTotal}
-
- Task Instances
-
-
-
-
- )}
-
-
- );
-};