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 018f0c25057 UI: Share one tooltip root across Dag card run-state
badges (#69927)
018f0c25057 is described below
commit 018f0c25057ef69733a8fa64cf4992afbe3631ff
Author: Yuseok Jo <[email protected]>
AuthorDate: Thu Jul 16 23:34:12 2026 +0900
UI: Share one tooltip root across Dag card run-state badges (#69927)
---
.../ui/src/pages/DagsList/DagRunStateCounts.tsx | 84 ++++++++++++++--------
1 file changed, 56 insertions(+), 28 deletions(-)
diff --git
a/airflow-core/src/airflow/ui/src/pages/DagsList/DagRunStateCounts.tsx
b/airflow-core/src/airflow/ui/src/pages/DagsList/DagRunStateCounts.tsx
index 4a238795ce1..931aa28a9a4 100644
--- a/airflow-core/src/airflow/ui/src/pages/DagsList/DagRunStateCounts.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/DagsList/DagRunStateCounts.tsx
@@ -16,12 +16,12 @@
* specific language governing permissions and limitations
* under the License.
*/
-import { HStack, Skeleton } from "@chakra-ui/react";
+import { HStack, Portal, Skeleton, Tooltip } from "@chakra-ui/react";
import { useTranslation } from "react-i18next";
import type { DagRunState } from "openapi/requests/types.gen";
import { StateBadge } from "src/components/StateBadge";
-import { RouterLink, Tooltip } from "src/components/ui";
+import { RouterLink } from "src/components/ui";
import { SearchParamsKeys } from "src/constants/searchParams";
const DISPLAYED_STATES: ReadonlyArray<DagRunState> = ["success", "failed",
"running", "queued"];
@@ -55,33 +55,61 @@ export const DagRunStateCounts = ({ compact = false,
counts, dagId, isLoading, s
);
}
+ const describeState = (state: DagRunState) => {
+ const count = counts[state] ?? 0;
+ // A count that reached the API cap is only a lower bound; suffix it with
"+".
+ const isCapped = stateCountLimit !== undefined && count >= stateCountLimit;
+ const formattedCount = `${count}${isCapped ? "+" : ""}`;
+
+ return {
+ formattedCount,
+ isZero: count === 0,
+ label: translate("runStateCounts.tooltip", {
+ formattedCount,
+ state: translate(`common:states.${state}` as const),
+ }),
+ };
+ };
+
+ // One shared tooltip root per card instead of one per badge, so a page of
Dag
+ // cards doesn't mount hundreds of tooltip state machines (same pattern as
RecentRuns).
return (
- <HStack data-testid={`run-state-counts-${dagId}`} gap={gap}>
- {DISPLAYED_STATES.map((state) => {
- const count = counts[state] ?? 0;
- // A count that reached the API cap is only a lower bound; suffix it
with "+".
- const isCapped = stateCountLimit !== undefined && count >=
stateCountLimit;
- const suffix = isCapped ? "+" : "";
- const translatedState = translate(`common:states.${state}` as const);
- const tooltipContent = translate("runStateCounts.tooltip", {
- formattedCount: `${count}${suffix}`,
- state: translatedState,
- });
+ <Tooltip.Root>
+ <HStack data-testid={`run-state-counts-${dagId}`} gap={gap}>
+ {DISPLAYED_STATES.map((state) => {
+ const { formattedCount, isZero, label } = describeState(state);
+
+ return (
+ <Tooltip.Trigger asChild key={state} value={state}>
+ <RouterLink
+ aria-label={label}
+ data-testid={`run-state-count-${state}-${dagId}`}
+ to={`/dags/${dagId}/runs?${SearchParamsKeys.STATE}=${state}`}
+ >
+ <StateBadge fontSize={fontSize} opacity={isZero ? 0.4 : 1}
state={state}>
+ {formattedCount}
+ </StateBadge>
+ </RouterLink>
+ </Tooltip.Trigger>
+ );
+ })}
+ </HStack>
+ <Portal disabled>
+ <Tooltip.Positioner>
+ <Tooltip.Content>
+ <Tooltip.Arrow>
+ <Tooltip.ArrowTip />
+ </Tooltip.Arrow>
+ <Tooltip.Context>
+ {({ triggerValue }) => {
+ const state = DISPLAYED_STATES.find((displayed) => displayed
=== triggerValue);
- return (
- <Tooltip content={tooltipContent} key={state}>
- <RouterLink
- aria-label={tooltipContent}
- data-testid={`run-state-count-${state}-${dagId}`}
- to={`/dags/${dagId}/runs?${SearchParamsKeys.STATE}=${state}`}
- >
- <StateBadge fontSize={fontSize} opacity={count === 0 ? 0.4 : 1}
state={state}>
- {`${count}${suffix}`}
- </StateBadge>
- </RouterLink>
- </Tooltip>
- );
- })}
- </HStack>
+ return state === undefined ? undefined :
describeState(state).label;
+ }}
+ </Tooltip.Context>
+ </Tooltip.Content>
+ </Tooltip.Positioner>
+ </Portal>
+ </Tooltip.Root>
);
};