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;