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 20f90a6e105 UI: Keep Dags list table rows on a single line (#74002)
20f90a6e105 is described below

commit 20f90a6e10522dcbfc389e58ab7ed8ae2a566499
Author: Yuseok Jo <[email protected]>
AuthorDate: Tue Oct 6 00:08:21 2026 +0900

    UI: Keep Dags list table rows on a single line (#74002)
    
    * UI: Keep Dags list table rows on a single line
    
    * UI: Portal the schedule tooltip so the single-line cell does not affect it
---
 .../ui/src/pages/DagsList/DagsList.test.tsx        | 34 +++++++++++++
 .../src/airflow/ui/src/pages/DagsList/DagsList.tsx | 39 +++++++++------
 .../ui/src/pages/DagsList/Schedule.test.tsx        | 58 ++++++++++++++++++++++
 .../src/airflow/ui/src/pages/DagsList/Schedule.tsx |  2 +-
 4 files changed, 118 insertions(+), 15 deletions(-)

diff --git a/airflow-core/src/airflow/ui/src/pages/DagsList/DagsList.test.tsx 
b/airflow-core/src/airflow/ui/src/pages/DagsList/DagsList.test.tsx
index cc411788340..0605b8a6584 100644
--- a/airflow-core/src/airflow/ui/src/pages/DagsList/DagsList.test.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/DagsList/DagsList.test.tsx
@@ -324,3 +324,37 @@ describe("Dag sorting", () => {
     
expect(screen.getByTestId("sort-index-dag_display_name")).toHaveTextContent("2");
   });
 });
+
+describe("Dags table", () => {
+  it.each([
+    "dag_display_name",
+    "timetable_description",
+    "next_dagrun",
+    "last_run_run_after",
+    "tags",
+    "team_name",
+  ])("keeps the %s cell on one line", async (columnId) => {
+    server.use(
+      http.get("/ui/config", () => HttpResponse.json({ multi_team: true, 
page_size: 15 })),
+      http.get("/ui/dags", () =>
+        HttpResponse.json({
+          dags: [
+            {
+              ...successDag,
+              next_dagrun_run_after: "2025-01-14T00:00:00Z",
+              team_name: "team_a",
+              timetable_summary: "@daily",
+            },
+          ],
+          total_entries: 1,
+        }),
+      ),
+    );
+    localStorage.setItem(DAGS_LIST_DISPLAY_KEY, JSON.stringify("table"));
+    render(<AppWrapper initialEntries={["/dags"]} />);
+
+    const cell = await screen.findByTestId(`table-cell-${columnId}`);
+
+    await waitFor(() => expect(cell.firstElementChild).toHaveStyle({ 
whiteSpace: "nowrap" }));
+  });
+});
diff --git a/airflow-core/src/airflow/ui/src/pages/DagsList/DagsList.tsx 
b/airflow-core/src/airflow/ui/src/pages/DagsList/DagsList.tsx
index b1da1380424..1b2af0c160b 100644
--- a/airflow-core/src/airflow/ui/src/pages/DagsList/DagsList.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/DagsList/DagsList.tsx
@@ -120,7 +120,7 @@ const createColumns = (
   {
     accessorKey: "dag_display_name",
     cell: ({ row: { original } }) => (
-      <RouterLink fontWeight="bold" to={`/dags/${original.dag_id}`}>
+      <RouterLink fontWeight="bold" to={`/dags/${original.dag_id}`} 
whiteSpace="nowrap">
         {original.dag_display_name}
       </RouterLink>
     ),
@@ -129,13 +129,15 @@ const createColumns = (
   {
     accessorKey: "timetable_description",
     cell: ({ row: { original } }) => (
-      <Schedule
-        assetExpression={original.asset_expression}
-        dagId={original.dag_id}
-        timetableDescription={original.timetable_description}
-        timetablePartitioned={original.timetable_partitioned}
-        timetableSummary={original.timetable_summary}
-      />
+      <Box whiteSpace="nowrap">
+        <Schedule
+          assetExpression={original.asset_expression}
+          dagId={original.dag_id}
+          timetableDescription={original.timetable_description}
+          timetablePartitioned={original.timetable_partitioned}
+          timetableSummary={original.timetable_summary}
+        />
+      </Box>
     ),
     enableSorting: false,
     header: () => translate("dagDetails.schedule"),
@@ -146,10 +148,12 @@ const createColumns = (
       original.is_paused ? undefined : original.scheduling_state === 
"draining" ? (
         <DrainingBadge />
       ) : Boolean(original.next_dagrun_run_after) ? (
-        <DagRunInfo
-          logicalDate={original.next_dagrun_logical_date}
-          runAfter={original.next_dagrun_run_after as string}
-        />
+        <Box whiteSpace="nowrap">
+          <DagRunInfo
+            logicalDate={original.next_dagrun_logical_date}
+            runAfter={original.next_dagrun_run_after as string}
+          />
+        </Box>
       ) : undefined,
     header: () => translate("dagDetails.nextRun"),
   },
@@ -160,6 +164,7 @@ const createColumns = (
         <RouterLink
           fontWeight="bold"
           
to={`/dags/${original.dag_id}/runs/${original.latest_dag_runs[0].run_id}`}
+          whiteSpace="nowrap"
         >
           <DagRunInfo
             endDate={original.latest_dag_runs[0].end_date}
@@ -192,7 +197,11 @@ const createColumns = (
       row: {
         original: { tags },
       },
-    }) => <DagTags hideIcon tags={tags} />,
+    }) => (
+      <Box whiteSpace="nowrap">
+        <DagTags hideIcon tags={tags} />
+      </Box>
+    ),
     enableSorting: false,
     header: () => translate("dagDetails.tags"),
   },
@@ -201,7 +210,9 @@ const createColumns = (
         {
           accessorKey: "team_name",
           cell: ({ row: { original } }: { row: { original: 
DAGWithLatestDagRunsResponse } }) => (
-            <TeamName teamName={original.team_name} />
+            <Box whiteSpace="nowrap">
+              <TeamName teamName={original.team_name} />
+            </Box>
           ),
           enableSorting: false,
           header: () => translate("dagDetails.team"),
diff --git a/airflow-core/src/airflow/ui/src/pages/DagsList/Schedule.test.tsx 
b/airflow-core/src/airflow/ui/src/pages/DagsList/Schedule.test.tsx
new file mode 100644
index 00000000000..9731577b187
--- /dev/null
+++ b/airflow-core/src/airflow/ui/src/pages/DagsList/Schedule.test.tsx
@@ -0,0 +1,58 @@
+/*!
+ * 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 "@testing-library/jest-dom/vitest";
+import { act, fireEvent, render, screen } from "@testing-library/react";
+import { describe, expect, it, vi } from "vitest";
+
+import { BaseWrapper } from "src/utils/Wrapper";
+
+import { Schedule } from "./Schedule";
+
+describe("Schedule", () => {
+  it("renders the timetable description tooltip outside the cell, so the 
cell's nowrap does not apply", async () => {
+    vi.useFakeTimers();
+
+    try {
+      const { container } = render(
+        <Schedule
+          assetExpression={undefined}
+          dagId="my_dag"
+          timetableDescription="At 09:00, Monday through Friday"
+          timetablePartitioned={false}
+          timetableSummary="0 9 * * 1-5"
+        />,
+        { wrapper: BaseWrapper },
+      );
+      const trigger = screen.getByText("0 9 * * 1-5");
+
+      await act(async () => {
+        fireEvent.focus(trigger);
+        fireEvent.pointerEnter(trigger);
+        await vi.advanceTimersByTimeAsync(500);
+      });
+
+      const tooltip = screen.getByRole("tooltip");
+
+      expect(tooltip).toHaveTextContent("At 09:00, Monday through Friday");
+      expect(container).not.toContainElement(tooltip);
+    } finally {
+      vi.useRealTimers();
+    }
+  });
+});
diff --git a/airflow-core/src/airflow/ui/src/pages/DagsList/Schedule.tsx 
b/airflow-core/src/airflow/ui/src/pages/DagsList/Schedule.tsx
index dc8476a51fd..af942c5c1c4 100644
--- a/airflow-core/src/airflow/ui/src/pages/DagsList/Schedule.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/DagsList/Schedule.tsx
@@ -52,7 +52,7 @@ export const Schedule = ({
         timetableSummary={timetableSummary}
       />
     ) : (
-      <Tooltip content={timetableDescription}>
+      <Tooltip content={timetableDescription} portalled>
         <Text fontSize="sm">
           <FiCalendar style={{ display: "inline" }} /> {timetableSummary}
         </Text>

Reply via email to