diff --git a/airflow-core/src/airflow/ui/src/pages/Dashboard/HistoricalMetrics/DagRunMetrics.test.tsx b/airflow-core/src/airflow/ui/src/pages/Dashboard/HistoricalMetrics/DagRunMetrics.test.tsx
new file mode 100644
index 0000000000000..6315d42243592
--- /dev/null
+++ b/airflow-core/src/airflow/ui/src/pages/Dashboard/HistoricalMetrics/DagRunMetrics.test.tsx
@@ -0,0 +1,55 @@
+/*!
+ * 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 { render, screen } from "@testing-library/react";
+import { describe, expect, it } from "vitest";
+
+import { Wrapper } from "src/utils/Wrapper";
+
+import { DagRunMetrics } from "./DagRunMetrics";
+
+describe("DagRunMetrics", () => {
+ it("shows percentages when counts are not capped", () => {
+ render(
+ ,
+ { wrapper: Wrapper },
+ );
+
+ expect(screen.getByText("25.00%")).toBeInTheDocument();
+ expect(screen.getByText("75.00%")).toBeInTheDocument();
+ });
+
+ it("hides percentages when any state reaches the API cap", () => {
+ render(
+ ,
+ { wrapper: Wrapper },
+ );
+
+ expect(screen.getByText("1000+")).toBeInTheDocument();
+ expect(screen.queryByText("99.30%")).not.toBeInTheDocument();
+ expect(screen.queryByText("0.70%")).not.toBeInTheDocument();
+ });
+});
diff --git a/airflow-core/src/airflow/ui/src/pages/Dashboard/HistoricalMetrics/DagRunMetrics.tsx b/airflow-core/src/airflow/ui/src/pages/Dashboard/HistoricalMetrics/DagRunMetrics.tsx
index a6578c0f3839e..4484887cff59c 100644
--- a/airflow-core/src/airflow/ui/src/pages/Dashboard/HistoricalMetrics/DagRunMetrics.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/Dashboard/HistoricalMetrics/DagRunMetrics.tsx
@@ -35,6 +35,7 @@ const DAGRUN_STATES: Array = ["queued", "running", "success"
export const DagRunMetrics = ({ dagRunStates, endDate, startDate, stateCountLimit }: DagRunMetricsProps) => {
const { t: translate } = useTranslation();
const total = Object.values(dagRunStates).reduce((sum, count) => sum + count, 0);
+ const hasCappedState = Object.values(dagRunStates).some((count) => count >= stateCountLimit);
return (
@@ -51,6 +52,7 @@ export const DagRunMetrics = ({ dagRunStates, endDate, startDate, stateCountLimi
key={state}
kind="dag_runs"
runs={dagRunStates[state]}
+ showPercentages={!hasCappedState}
startDate={startDate}
state={state}
total={total}
diff --git a/airflow-core/src/airflow/ui/src/pages/Dashboard/HistoricalMetrics/MetricSection.tsx b/airflow-core/src/airflow/ui/src/pages/Dashboard/HistoricalMetrics/MetricSection.tsx
index e0cfff1f01c8a..b378fc6cba493 100644
--- a/airflow-core/src/airflow/ui/src/pages/Dashboard/HistoricalMetrics/MetricSection.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/Dashboard/HistoricalMetrics/MetricSection.tsx
@@ -32,6 +32,7 @@ type MetricSectionProps = {
readonly endDate?: string;
readonly kind: string;
readonly runs: number;
+ readonly showPercentages?: boolean;
readonly startDate: string;
readonly state: keyof TaskInstanceStateCount;
readonly total: number;
@@ -42,13 +43,15 @@ export const MetricSection = ({
endDate,
kind,
runs,
+ showPercentages = true,
startDate,
state,
total,
}: MetricSectionProps) => {
const stateWidth = capped ? BAR_WIDTH : total === 0 ? 0 : (runs / total) * BAR_WIDTH;
const remainingWidth = BAR_WIDTH - stateWidth;
- const statePercent = capped ? undefined : total === 0 ? 0 : ((runs / total) * 100).toFixed(2);
+ const statePercent =
+ showPercentages && !capped && total !== 0 ? ((runs / total) * 100).toFixed(2) : undefined;
const stateParam = kind === "task_instances" ? SearchParamsKeys.TASK_STATE : SearchParamsKeys.STATE;
const searchParams = new URLSearchParams(
@@ -74,23 +77,25 @@ export const MetricSection = ({
{statePercent === undefined ? undefined : {statePercent}% }
-
-
-
-
+ {showPercentages ? (
+
+
+
+
+ ) : undefined}
);
};
diff --git a/airflow-core/src/airflow/ui/src/pages/Dashboard/HistoricalMetrics/TaskInstanceMetrics.tsx b/airflow-core/src/airflow/ui/src/pages/Dashboard/HistoricalMetrics/TaskInstanceMetrics.tsx
index f36c0a4635b3d..0bc00eda691bc 100644
--- a/airflow-core/src/airflow/ui/src/pages/Dashboard/HistoricalMetrics/TaskInstanceMetrics.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/Dashboard/HistoricalMetrics/TaskInstanceMetrics.tsx
@@ -54,6 +54,7 @@ export const TaskInstanceMetrics = ({
}: TaskInstanceMetricsProps) => {
const { t: translate } = useTranslation();
const total = Object.values(taskInstanceStates).reduce((sum, count) => sum + count, 0);
+ const hasCappedState = Object.values(taskInstanceStates).some((count) => count >= stateCountLimit);
return (
@@ -73,6 +74,7 @@ export const TaskInstanceMetrics = ({
key={state}
kind="task_instances"
runs={taskInstanceStates[state]}
+ showPercentages={!hasCappedState}
startDate={startDate}
state={state as TaskInstanceState}
total={total}