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

ephraimanierobi pushed a commit to branch v3-1-test
in repository https://gitbox.apache.org/repos/asf/airflow.git

commit 0ce9a55844376b2f427e9b1b5c5ad8a62ccc6081
Author: github-actions[bot] 
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Tue Jan 13 10:45:55 2026 -0500

    [v3-1-test] Fix gantt chart styling (#60347) (#60457)
    
    * Fix gantt chart styling
    
    * remove eslint-disable
    (cherry picked from commit fcd767971ecbedfb2ee5468d05755ed6c6f702b6)
    
    Co-authored-by: Brent Bovenzi <[email protected]>
---
 .../ui/src/layouts/Details/DetailsLayout.tsx       |  2 +-
 .../airflow/ui/src/layouts/Details/Gantt/Gantt.tsx | 14 ++++++++--
 .../airflow/ui/src/layouts/Details/Gantt/utils.ts  |  9 +++++-
 .../airflow/ui/src/layouts/Details/Grid/Grid.tsx   | 28 +++++++++++--------
 .../ui/src/layouts/Details/Grid/constants.ts       | 32 ++++++++++++++++++++++
 5 files changed, 69 insertions(+), 16 deletions(-)

diff --git a/airflow-core/src/airflow/ui/src/layouts/Details/DetailsLayout.tsx 
b/airflow-core/src/airflow/ui/src/layouts/Details/DetailsLayout.tsx
index 9fbc27bb421..6fe28bbe753 100644
--- a/airflow-core/src/airflow/ui/src/layouts/Details/DetailsLayout.tsx
+++ b/airflow-core/src/airflow/ui/src/layouts/Details/DetailsLayout.tsx
@@ -146,7 +146,7 @@ export const DetailsLayout = ({ children, error, isLoading, 
tabs }: Props) => {
                 {dagView === "graph" ? (
                   <Graph />
                 ) : (
-                  <HStack alignItems="flex-end" gap={0}>
+                  <HStack alignItems="flex-start" gap={0}>
                     <Grid
                       limit={limit}
                       runType={runTypeFilter}
diff --git a/airflow-core/src/airflow/ui/src/layouts/Details/Gantt/Gantt.tsx 
b/airflow-core/src/airflow/ui/src/layouts/Details/Gantt/Gantt.tsx
index 41fe8d6b011..54d2c476b41 100644
--- a/airflow-core/src/airflow/ui/src/layouts/Details/Gantt/Gantt.tsx
+++ b/airflow-core/src/airflow/ui/src/layouts/Details/Gantt/Gantt.tsx
@@ -45,6 +45,7 @@ import { useColorMode } from "src/context/colorMode";
 import { useHover } from "src/context/hover";
 import { useOpenGroups } from "src/context/openGroups";
 import { useTimezone } from "src/context/timezone";
+import { GRID_BODY_OFFSET_PX } from "src/layouts/Details/Grid/constants";
 import { flattenNodes } from "src/layouts/Details/Grid/utils";
 import { useGridRuns } from "src/queries/useGridRuns";
 import { useGridStructure } from "src/queries/useGridStructure";
@@ -90,6 +91,7 @@ export const Gantt = ({ limit, runType, triggeringUser }: 
Props) => {
   const navigate = useNavigate();
   const location = useLocation();
 
+  // Corresponds to border, brand.emphasized, and brand.muted
   const [
     lightGridColor,
     darkGridColor,
@@ -97,7 +99,8 @@ export const Gantt = ({ limit, runType, triggeringUser }: 
Props) => {
     darkSelectedColor,
     lightHoverColor,
     darkHoverColor,
-  ] = useToken("colors", ["gray.200", "gray.800", "blue.200", "blue.800", 
"blue.100", "blue.900"]);
+  ] = useToken("colors", ["gray.200", "gray.800", "brand.300", "brand.700", 
"brand.200", "brand.800"]);
+
   const gridColor = colorMode === "light" ? lightGridColor : darkGridColor;
   const selectedItemColor = colorMode === "light" ? lightSelectedColor : 
darkSelectedColor;
   const hoveredItemColor = colorMode === "light" ? lightHoverColor : 
darkHoverColor;
@@ -282,7 +285,14 @@ export const Gantt = ({ limit, runType, triggeringUser }: 
Props) => {
   };
 
   return (
-    <Box height={`${fixedHeight}px`} minW="250px" ml={-2} 
onMouseLeave={handleChartMouseLeave} w="100%">
+    <Box
+      height={`${fixedHeight}px`}
+      minW="250px"
+      ml={-2}
+      mt={`${GRID_BODY_OFFSET_PX}px`}
+      onMouseLeave={handleChartMouseLeave}
+      w="100%"
+    >
       <Bar
         data={chartData}
         options={chartOptions}
diff --git a/airflow-core/src/airflow/ui/src/layouts/Details/Gantt/utils.ts 
b/airflow-core/src/airflow/ui/src/layouts/Details/Gantt/utils.ts
index d3cab195c19..5cb5c25c660 100644
--- a/airflow-core/src/airflow/ui/src/layouts/Details/Gantt/utils.ts
+++ b/airflow-core/src/airflow/ui/src/layouts/Details/Gantt/utils.ts
@@ -243,7 +243,14 @@ export const createChartOptions = ({
             : formatDate(effectiveEndDate, selectedTimezone),
         min:
           data.length > 0
-            ? Math.min(...data.map((item) => new Date(item.x[0] ?? 
"").getTime()))
+            ? (() => {
+                const maxTime = Math.max(...data.map((item) => new 
Date(item.x[1] ?? "").getTime()));
+                const minTime = Math.min(...data.map((item) => new 
Date(item.x[0] ?? "").getTime()));
+                const totalDuration = maxTime - minTime;
+
+                // subtract 2% from min time so background color shows before 
data
+                return minTime - totalDuration * 0.02;
+              })()
             : formatDate(selectedRun?.start_date, selectedTimezone),
         position: "top" as const,
         stacked: true,
diff --git a/airflow-core/src/airflow/ui/src/layouts/Details/Grid/Grid.tsx 
b/airflow-core/src/airflow/ui/src/layouts/Details/Grid/Grid.tsx
index eab0d4f3238..6cacada2c1b 100644
--- a/airflow-core/src/airflow/ui/src/layouts/Details/Grid/Grid.tsx
+++ b/airflow-core/src/airflow/ui/src/layouts/Details/Grid/Grid.tsx
@@ -37,12 +37,16 @@ import { DurationAxis } from "./DurationAxis";
 import { DurationTick } from "./DurationTick";
 import { TaskInstancesColumn } from "./TaskInstancesColumn";
 import { TaskNames } from "./TaskNames";
+import {
+  GRID_HEADER_HEIGHT_PX,
+  GRID_HEADER_PADDING_PX,
+  GRID_OUTER_PADDING_PX,
+  ROW_HEIGHT,
+} from "./constants";
 import { flattenNodes } from "./utils";
 
 dayjs.extend(dayjsDuration);
 
-const ROW_HEIGHT = 20;
-
 type Props = {
   readonly limit: number;
   readonly runType?: DagRunType | undefined;
@@ -116,7 +120,7 @@ export const Grid = ({ limit, runType, showGantt, 
triggeringUser }: Props) => {
       flexDirection="column"
       justifyContent="flex-start"
       position="relative"
-      pt={16}
+      pt={`${GRID_OUTER_PADDING_PX}px`}
       ref={gridRef}
       tabIndex={0}
       width={showGantt ? "1/2" : "full"}
@@ -124,20 +128,20 @@ export const Grid = ({ limit, runType, showGantt, 
triggeringUser }: Props) => {
       {/* Grid scroll container */}
       <Box
         height="calc(100vh - 140px)"
-        marginRight={1}
+        marginRight={showGantt ? 0 : 1}
         overflow="auto"
-        paddingRight={4}
+        paddingRight={showGantt ? 0 : 4}
         position="relative"
         ref={scrollContainerRef}
       >
         {/* Grid header, both bgs are needed to hide elements during 
horizontal and vertical scroll */}
-        <Flex bg="bg" display="flex" position="sticky" pt={2} top={0} 
zIndex={2}>
+        <Flex bg="bg" display="flex" position="sticky" 
pt={`${GRID_HEADER_PADDING_PX}px`} top={0} zIndex={2}>
           <Box bg="bg" flexGrow={1} left={0} minWidth="200px" 
position="sticky" zIndex={1}>
-            <Flex flexDirection="column-reverse" height="100px" 
position="relative">
+            <Flex flexDirection="column-reverse" 
height={`${GRID_HEADER_HEIGHT_PX}px`} position="relative">
               {Boolean(gridRuns?.length) && (
                 <>
-                  <DurationTick bottom="92px" duration={max} />
-                  <DurationTick bottom="46px" duration={max / 2} />
+                  <DurationTick bottom={`${GRID_HEADER_HEIGHT_PX - 8}px`} 
duration={max} />
+                  <DurationTick bottom={`${GRID_HEADER_HEIGHT_PX / 2 - 4}px`} 
duration={max / 2} />
                 </>
               )}
             </Flex>
@@ -145,8 +149,8 @@ export const Grid = ({ limit, runType, showGantt, 
triggeringUser }: Props) => {
           {/* Duration bars */}
           <Flex flexDirection="row-reverse" flexShrink={0}>
             <Flex flexShrink={0} position="relative">
-              <DurationAxis top="100px" />
-              <DurationAxis top="50px" />
+              <DurationAxis top={`${GRID_HEADER_HEIGHT_PX}px`} />
+              <DurationAxis top={`${GRID_HEADER_HEIGHT_PX / 2}px`} />
               <DurationAxis top="4px" />
               <Flex flexDirection="row-reverse">
                 {gridRuns?.map((dr: GridRunsResponse) => (
@@ -157,7 +161,7 @@ export const Grid = ({ limit, runType, showGantt, 
triggeringUser }: Props) => {
                 <Link to={`/dags/${dagId}`}>
                   <IconButton
                     aria-label={translate("grid.buttons.resetToLatest")}
-                    height="98px"
+                    height={`${GRID_HEADER_HEIGHT_PX - 2}px`}
                     loading={isLoading}
                     minW={0}
                     ml={1}
diff --git a/airflow-core/src/airflow/ui/src/layouts/Details/Grid/constants.ts 
b/airflow-core/src/airflow/ui/src/layouts/Details/Grid/constants.ts
new file mode 100644
index 00000000000..7286818def7
--- /dev/null
+++ b/airflow-core/src/airflow/ui/src/layouts/Details/Grid/constants.ts
@@ -0,0 +1,32 @@
+/*!
+ * 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.
+ */
+
+// Grid layout constants - shared between Grid and Gantt for alignment
+export const ROW_HEIGHT = 20;
+export const GRID_OUTER_PADDING_PX = 64; // pt={16} = 16 * 4 = 64px
+export const GRID_HEADER_PADDING_PX = 8; // pt={2} = 2 * 4 = 8px
+export const GRID_HEADER_HEIGHT_PX = 100; // height="100px" for duration bars
+
+// Gantt chart's x-axis height (time labels at top of chart)
+export const GANTT_AXIS_HEIGHT_PX = 36;
+
+// Total offset from top of Grid component to where task rows begin,
+// minus the Gantt axis height since the chart includes its own top axis
+export const GRID_BODY_OFFSET_PX =
+  GRID_OUTER_PADDING_PX + GRID_HEADER_PADDING_PX + GRID_HEADER_HEIGHT_PX - 
GANTT_AXIS_HEIGHT_PX;

Reply via email to