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 fcd767971ec Fix gantt chart styling (#60347)
fcd767971ec is described below
commit fcd767971ecbedfb2ee5468d05755ed6c6f702b6
Author: Brent Bovenzi <[email protected]>
AuthorDate: Tue Jan 13 09:39:52 2026 -0500
Fix gantt chart styling (#60347)
* Fix gantt chart styling
* remove eslint-disable
---
.../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 58497aa05d1..3b73205566b 100644
--- a/airflow-core/src/airflow/ui/src/layouts/Details/DetailsLayout.tsx
+++ b/airflow-core/src/airflow/ui/src/layouts/Details/DetailsLayout.tsx
@@ -157,7 +157,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
dagRunState={dagRunStateFilter}
limit={limit}
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 d44e0fd138c..1afbb7adb27 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";
@@ -91,6 +92,7 @@ export const Gantt = ({ dagRunState, limit, runType,
triggeringUser }: Props) =>
const navigate = useNavigate();
const location = useLocation();
+ // Corresponds to border, brand.emphasized, and brand.muted
const [
lightGridColor,
darkGridColor,
@@ -98,7 +100,8 @@ export const Gantt = ({ dagRunState, 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;
@@ -258,7 +261,14 @@ export const Gantt = ({ dagRunState, 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 57241224825..87757a7fcf9 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
@@ -245,7 +245,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 e81b0e21421..2d79f17d505 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 dagRunState?: DagRunState | undefined;
readonly limit: number;
@@ -126,7 +130,7 @@ export const Grid = ({ dagRunState, limit, runType,
showGantt, triggeringUser }:
flexDirection="column"
justifyContent="flex-start"
position="relative"
- pt={16}
+ pt={`${GRID_OUTER_PADDING_PX}px`}
ref={gridRef}
tabIndex={0}
width={showGantt ? "1/2" : "full"}
@@ -134,20 +138,20 @@ export const Grid = ({ dagRunState, limit, runType,
showGantt, triggeringUser }:
{/* 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>
@@ -155,8 +159,8 @@ export const Grid = ({ dagRunState, limit, runType,
showGantt, triggeringUser }:
{/* 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) => (
@@ -167,7 +171,7 @@ export const Grid = ({ dagRunState, limit, runType,
showGantt, triggeringUser }:
<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;