This is an automated email from the ASF dual-hosted git repository.

potiuk 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 a0e0ae1d068 Reduce DAG card tooltip controller overhead (#69681)
a0e0ae1d068 is described below

commit a0e0ae1d0685466c70c259fee889dff097ec1a63
Author: Shivam Rastogi <[email protected]>
AuthorDate: Sun Jul 12 09:09:53 2026 -0700

    Reduce DAG card tooltip controller overhead (#69681)
---
 .../airflow/ui/src/pages/DagsList/DagCard.test.tsx |  32 ++++-
 .../airflow/ui/src/pages/DagsList/RecentRuns.tsx   | 135 ++++++++++++---------
 2 files changed, 109 insertions(+), 58 deletions(-)

diff --git a/airflow-core/src/airflow/ui/src/pages/DagsList/DagCard.test.tsx 
b/airflow-core/src/airflow/ui/src/pages/DagsList/DagCard.test.tsx
index ad163ae19f1..95a35080aed 100644
--- a/airflow-core/src/airflow/ui/src/pages/DagsList/DagCard.test.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/DagsList/DagCard.test.tsx
@@ -17,7 +17,7 @@
  * under the License.
  */
 import "@testing-library/jest-dom/vitest";
-import { render, screen } from "@testing-library/react";
+import { act, fireEvent, render, screen } from "@testing-library/react";
 import i18n from "i18next";
 import type { DagTagResponse, DAGWithLatestDagRunsResponse } from 
"openapi-gen/requests/types.gen";
 import type { PropsWithChildren } from "react";
@@ -209,6 +209,36 @@ describe("DagCard", () => {
     expect(latestRunElement).toHaveTextContent("2025-09-19 19:22:00");
   });
 
+  it("DagCard should share one tooltip controller across recent runs", async 
() => {
+    vi.useFakeTimers();
+    renderCard(mockDag);
+
+    const recentRuns = screen.getAllByTestId("recent-run");
+    const tooltipOwners = new Set(recentRuns.map((run) => 
run.getAttribute("data-ownedby")));
+    const secondRecentRun = recentRuns.at(1);
+
+    try {
+      expect(recentRuns).toHaveLength(mockDag.latest_dag_runs.length);
+      expect(tooltipOwners.size).toBe(1);
+      expect(tooltipOwners.has(null)).toBe(false);
+      expect(secondRecentRun).toBeDefined();
+
+      if (secondRecentRun === undefined) {
+        throw new Error("Expected at least two recent runs");
+      }
+
+      await act(async () => {
+        fireEvent.focus(secondRecentRun);
+        fireEvent.pointerEnter(secondRecentRun);
+        await vi.advanceTimersByTimeAsync(500);
+      });
+
+      expect(screen.getByRole("tooltip")).toHaveTextContent("2025-09-19 
19:21:00");
+    } finally {
+      vi.useRealTimers();
+    }
+  });
+
   it("DagCard should render next run section with timestamp", () => {
     renderCard(mockDag);
     const nextRunElement = screen.getByTestId("next-run");
diff --git a/airflow-core/src/airflow/ui/src/pages/DagsList/RecentRuns.tsx 
b/airflow-core/src/airflow/ui/src/pages/DagsList/RecentRuns.tsx
index 1bcd6eb663b..f327006f2d3 100644
--- a/airflow-core/src/airflow/ui/src/pages/DagsList/RecentRuns.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/DagsList/RecentRuns.tsx
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-import { Flex, Box, Text } from "@chakra-ui/react";
+import { Flex, Box, Portal, Text, Tooltip } from "@chakra-ui/react";
 import dayjs from "dayjs";
 import duration from "dayjs/plugin/duration";
 import { useTranslation } from "react-i18next";
@@ -25,20 +25,47 @@ import { Link } from "react-router-dom";
 import type { DAGWithLatestDagRunsResponse } from "openapi/requests/types.gen";
 import { StateIcon } from "src/components/StateIcon";
 import Time from "src/components/Time";
-import { Tooltip } from "src/components/ui";
 import { renderDuration } from "src/utils";
 
 dayjs.extend(duration);
 
 const BAR_HEIGHT = 65;
 
+type LatestRun = DAGWithLatestDagRunsResponse["latest_dag_runs"][number];
+
+const RecentRunTooltipContent = ({ run }: { readonly run: LatestRun }) => {
+  const { t: translate } = useTranslation();
+
+  return (
+    <Box>
+      <Text>
+        {translate("state")}: {translate(`common:states.${run.state}`)}
+      </Text>
+      <Text>
+        {translate("dagRun.runAfter")}: <Time datetime={run.run_after} />
+      </Text>
+      {run.start_date === null ? undefined : (
+        <Text>
+          {translate("startDate")}: <Time datetime={run.start_date} />
+        </Text>
+      )}
+      {run.end_date === null ? undefined : (
+        <Text>
+          {translate("endDate")}: <Time datetime={run.end_date} />
+        </Text>
+      )}
+      <Text>
+        {translate("duration")}: {renderDuration(run.duration)}
+      </Text>
+    </Box>
+  );
+};
+
 export const RecentRuns = ({
   latestRuns,
 }: {
   readonly latestRuns: DAGWithLatestDagRunsResponse["latest_dag_runs"];
 }) => {
-  const { t: translate } = useTranslation();
-
   if (!latestRuns.length) {
     return undefined;
   }
@@ -49,58 +76,52 @@ export const RecentRuns = ({
   );
 
   return (
-    <Flex alignItems="flex-end" flexDirection="row-reverse" gap={[0.5, 0.5, 
0.5, 1]} pb={1}>
-      {latestRuns.map((run) => (
-        <Tooltip
-          content={
-            <Box>
-              <Text>
-                {translate("state")}: {translate(`common:states.${run.state}`)}
-              </Text>
-              <Text>
-                {translate("dagRun.runAfter")}: <Time datetime={run.run_after} 
/>
-              </Text>
-              {run.start_date === null ? undefined : (
-                <Text>
-                  {translate("startDate")}: <Time datetime={run.start_date} />
-                </Text>
-              )}
-              {run.end_date === null ? undefined : (
-                <Text>
-                  {translate("endDate")}: <Time datetime={run.end_date} />
-                </Text>
-              )}
-              <Text>
-                {translate("duration")}: {renderDuration(run.duration)}
-              </Text>
-            </Box>
-          }
-          key={run.run_id}
-          positioning={{
-            offset: {
-              crossAxis: 5,
-              mainAxis: 5,
-            },
-            placement: "bottom-start",
-          }}
-        >
-          <Link to={`/dags/${run.dag_id}/runs/${run.run_id}/`}>
-            <Flex
-              alignItems="center"
-              bg={`${run.state}.solid`}
-              borderRadius="4px"
-              flexDir="column"
-              fontSize="12px"
-              height={`${run.duration === null ? 1 : (run.duration / max) * 
BAR_HEIGHT}px`}
-              justifyContent="flex-end"
-              minHeight="12px"
-              width="12px"
-            >
-              <StateIcon color="white" state={run.state} />
-            </Flex>
-          </Link>
-        </Tooltip>
-      ))}
-    </Flex>
+    <Tooltip.Root
+      positioning={{
+        offset: {
+          crossAxis: 5,
+          mainAxis: 5,
+        },
+        placement: "bottom-start",
+      }}
+    >
+      <Flex alignItems="flex-end" flexDirection="row-reverse" gap={[0.5, 0.5, 
0.5, 1]} pb={1}>
+        {latestRuns.map((run) => (
+          <Tooltip.Trigger asChild key={run.run_id} value={run.run_id}>
+            <Link data-testid="recent-run" 
to={`/dags/${run.dag_id}/runs/${run.run_id}/`}>
+              <Flex
+                alignItems="center"
+                bg={`${run.state}.solid`}
+                borderRadius="4px"
+                flexDir="column"
+                fontSize="12px"
+                height={`${run.duration === null ? 1 : (run.duration / max) * 
BAR_HEIGHT}px`}
+                justifyContent="flex-end"
+                minHeight="12px"
+                width="12px"
+              >
+                <StateIcon color="white" state={run.state} />
+              </Flex>
+            </Link>
+          </Tooltip.Trigger>
+        ))}
+      </Flex>
+      <Portal disabled>
+        <Tooltip.Positioner>
+          <Tooltip.Content>
+            <Tooltip.Arrow>
+              <Tooltip.ArrowTip />
+            </Tooltip.Arrow>
+            <Tooltip.Context>
+              {({ triggerValue }) => {
+                const run = latestRuns.find(({ run_id: runId }) => runId === 
triggerValue);
+
+                return run === undefined ? undefined : 
<RecentRunTooltipContent run={run} />;
+              }}
+            </Tooltip.Context>
+          </Tooltip.Content>
+        </Tooltip.Positioner>
+      </Portal>
+    </Tooltip.Root>
   );
 };

Reply via email to