From 23a1746b90d57433938bd589d39c5440e4b8101d Mon Sep 17 00:00:00 2001 From: Olivier Daneau Date: Thu, 2 Oct 2025 16:28:14 -0400 Subject: [PATCH 01/13] Implement a semantic tokens structure in theme.ts and update components to use it. --- .../ui/src/components/DurationChart.tsx | 6 +- .../ui/src/components/FilterBar/FilterBar.tsx | 2 +- .../ui/src/components/TrendCountChart.tsx | 44 +-- .../ui/src/layouts/Details/Gantt/Gantt.tsx | 39 ++- .../ui/src/layouts/Details/Graph/Graph.tsx | 12 +- .../src/layouts/Details/Grid/GridButton.tsx | 4 +- .../ui/src/layouts/Details/Grid/GridTI.tsx | 2 +- .../airflow/ui/src/pages/Asset/AssetGraph.tsx | 12 +- .../Dag/Calendar/CalendarTooltipContent.tsx | 70 +++++ .../ui/src/pages/Dag/Overview/Overview.tsx | 4 +- .../HistoricalMetrics/MetricSection.tsx | 2 +- .../Dashboard/Stats/PluginImportErrors.tsx | 4 +- .../ui/src/pages/Dashboard/Stats/Stats.tsx | 8 +- .../ui/src/pages/Task/Overview/Overview.tsx | 2 +- .../ManageVariable/DeleteVariableButton.tsx | 4 + airflow-core/src/airflow/ui/src/theme.ts | 259 ++++++++++++++++-- 16 files changed, 371 insertions(+), 103 deletions(-) create mode 100644 airflow-core/src/airflow/ui/src/pages/Dag/Calendar/CalendarTooltipContent.tsx diff --git a/airflow-core/src/airflow/ui/src/components/DurationChart.tsx b/airflow-core/src/airflow/ui/src/components/DurationChart.tsx index c0fd7cf796b97..7e6191c6341d4 100644 --- a/airflow-core/src/airflow/ui/src/components/DurationChart.tsx +++ b/airflow-core/src/airflow/ui/src/components/DurationChart.tsx @@ -35,7 +35,7 @@ import { useTranslation } from "react-i18next"; import { useNavigate } from "react-router-dom"; import type { TaskInstanceResponse, GridRunsResponse } from "openapi/requests/types.gen"; -import { getComputedCSSVariableValue } from "src/theme"; +import { resolveTokenValue } from "src/theme"; import { DEFAULT_DATETIME_FORMAT } from "src/utils/datetimeUtils"; ChartJS.register( @@ -86,7 +86,7 @@ export const DurationChart = ({ states.forEach((state, index) => { if (state) { - stateColorMap[state] = getComputedCSSVariableValue(stateColorTokens[index] ?? "oklch(0.5 0 0)"); + stateColorMap[state] = resolveTokenValue(stateColorTokens[index] ?? "oklch(0.5 0 0)"); } }); @@ -125,7 +125,7 @@ export const DurationChart = ({ data={{ datasets: [ { - backgroundColor: getComputedCSSVariableValue(queuedColorToken ?? "oklch(0.5 0 0)"), + backgroundColor: resolveTokenValue(queuedColorToken ?? "oklch(0.5 0 0)"), data: entries.map((entry: RunResponse) => { switch (kind) { case "Dag Run": { diff --git a/airflow-core/src/airflow/ui/src/components/FilterBar/FilterBar.tsx b/airflow-core/src/airflow/ui/src/components/FilterBar/FilterBar.tsx index 849047dde072f..e4880bf2bca08 100644 --- a/airflow-core/src/airflow/ui/src/components/FilterBar/FilterBar.tsx +++ b/airflow-core/src/airflow/ui/src/components/FilterBar/FilterBar.tsx @@ -165,7 +165,7 @@ export const FilterBar = ({ )} {filters.length > 0 && ( - diff --git a/airflow-core/src/airflow/ui/src/components/TrendCountChart.tsx b/airflow-core/src/airflow/ui/src/components/TrendCountChart.tsx index 68f86ae35d339..e0567107a1a7f 100644 --- a/airflow-core/src/airflow/ui/src/components/TrendCountChart.tsx +++ b/airflow-core/src/airflow/ui/src/components/TrendCountChart.tsx @@ -17,6 +17,7 @@ * under the License. */ import { Box, useToken } from "@chakra-ui/react"; +import { resolveTokenValue } from "src/theme"; import { Chart as ChartJS, CategoryScale, @@ -31,7 +32,6 @@ import dayjs from "dayjs"; import { useMemo, useRef, useEffect } from "react"; import { Line } from "react-chartjs-2"; -import { useColorMode } from "src/context/colorMode"; ChartJS.register(CategoryScale, LinearScale, PointElement, LineElement, Filler, Tooltip); @@ -97,46 +97,24 @@ type Props = { }; export const TrendCountChart = ({ endDate, events, startDate }: Props) => { - const { colorMode } = useColorMode(); const chartRef = useRef>(); - // Get raw color values instead of CSS variables - const [bgLightGreen, bgDarkGreen, lineLightGreen, lineDarkGreen] = useToken("colors", [ - "green.100", - "green.800", - "green.500", - "green.400", - ]); - - const [bgLightRed, bgDarkRed, lineLightRed, lineDarkRed] = useToken("colors", [ - "red.100", - "red.800", - "red.500", - "red.400", - ]); + // Get semantic token colors that automatically handle light/dark mode + const [successBg, successLine, failedBg, failedLine] = useToken("colors", [ + "success.subtle", + "success.solid", + "failed.subtle", + "failed.solid", + ]).map(token => resolveTokenValue(token || "oklch(0.5 0 0)")); const intervalData = useMemo( () => aggregateEventsIntoIntervals(events, startDate, endDate), [events, startDate, endDate], ); - const backgroundColor = - colorMode === "light" - ? intervalData.some((value) => value > 0) - ? bgLightRed - : bgLightGreen - : intervalData.some((value) => value > 0) - ? bgDarkRed - : bgDarkGreen; - - const lineColor = - colorMode === "light" - ? intervalData.some((value) => value > 0) - ? lineLightRed - : lineLightGreen - : intervalData.some((value) => value > 0) - ? lineDarkRed - : lineDarkGreen; + const hasFailures = intervalData.some((value) => value > 0); + const backgroundColor = hasFailures ? failedBg : successBg; + const lineColor = hasFailures ? failedLine : successLine; // Cleanup chart instance on unmount useEffect( 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 8a72f0f54d3bb..7c9c33ebbcb1b 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 @@ -41,7 +41,6 @@ import { useParams, useNavigate, useLocation } from "react-router-dom"; import { useTaskInstanceServiceGetTaskInstances } from "openapi/queries"; import type { DagRunState, DagRunType } from "openapi/requests/types.gen"; -import { useColorMode } from "src/context/colorMode"; import { useHover } from "src/context/hover"; import { useOpenGroups } from "src/context/openGroups"; import { useTimezone } from "src/context/timezone"; @@ -49,7 +48,7 @@ import { flattenNodes } from "src/layouts/Details/Grid/utils"; import { useGridRuns } from "src/queries/useGridRuns"; import { useGridStructure } from "src/queries/useGridStructure"; import { useGridTiSummaries } from "src/queries/useGridTISummaries"; -import { getComputedCSSVariableValue } from "src/theme"; +import { resolveTokenValue } from "src/theme"; import { isStatePending, useAutoRefresh } from "src/utils"; import { DEFAULT_DATETIME_FORMAT_WITH_TZ, formatDate } from "src/utils/datetimeUtils"; @@ -86,22 +85,15 @@ export const Gantt = ({ dagRunState, limit, runType, triggeringUser }: Props) => const deferredOpenGroupIds = useDeferredValue(openGroupIds); const { t: translate } = useTranslation("common"); const { selectedTimezone } = useTimezone(); - const { colorMode } = useColorMode(); const { hoveredTaskId, setHoveredTaskId } = useHover(); const navigate = useNavigate(); const location = useLocation(); - const [ - lightGridColor, - darkGridColor, - lightSelectedColor, - darkSelectedColor, - lightHoverColor, - darkHoverColor, - ] = useToken("colors", ["gray.200", "gray.800", "blue.200", "blue.800", "blue.100", "blue.900"]); - const gridColor = colorMode === "light" ? lightGridColor : darkGridColor; - const selectedItemColor = colorMode === "light" ? lightSelectedColor : darkSelectedColor; - const hoveredItemColor = colorMode === "light" ? lightHoverColor : darkHoverColor; + const [gridColor, selectedItemColor, hoveredItemColor] = useToken("colors", [ + "gantt.grid.color", + "gantt.selected.bg", + "gantt.hover.bg", + ]); const { data: gridRuns, isLoading: runsLoading } = useGridRuns({ dagRunState, @@ -202,18 +194,23 @@ export const Gantt = ({ dagRunState, limit, runType, triggeringUser }: Props) => .filter((item) => item !== undefined); }, [flatNodes, gridTiSummaries, taskInstancesData, selectedTimezone, isLoading, runId, currentTime]); - // Get all unique states and their colors + // Get all unique states from the data const states = [...new Set(data.map((item) => item.state ?? "none"))]; + + // Use Chakra's useToken hook to get color tokens for each state const stateColorTokens = useToken( "colors", states.map((state) => `${state}.solid`), ); - const stateColorMap = Object.fromEntries( - states.map((state, index) => [ - state, - getComputedCSSVariableValue(stateColorTokens[index] ?? "oklch(0.5 0 0)"), - ]), - ); + + // Create a mapping of state to computed color values for ChartJS + const stateColorMap: Record = {}; + + states.forEach((state, index) => { + if (state) { + stateColorMap[state] = resolveTokenValue(stateColorTokens[index] ?? "oklch(0.5 0 0)"); + } + }); const chartData = useMemo( () => ({ diff --git a/airflow-core/src/airflow/ui/src/layouts/Details/Graph/Graph.tsx b/airflow-core/src/airflow/ui/src/layouts/Details/Graph/Graph.tsx index 4c70510b7637e..f9734b950179c 100644 --- a/airflow-core/src/airflow/ui/src/layouts/Details/Graph/Graph.tsx +++ b/airflow-core/src/airflow/ui/src/layouts/Details/Graph/Graph.tsx @@ -19,6 +19,7 @@ import { useToken } from "@chakra-ui/react"; import { ReactFlow, Controls, Background, MiniMap, type Node as ReactFlowNode } from "@xyflow/react"; import "@xyflow/react/dist/style.css"; +import type { CSSProperties } from "react"; import { useEffect } from "react"; import { useParams } from "react-router-dom"; import { useLocalStorage } from "usehooks-ts"; @@ -34,7 +35,6 @@ import useSelectedVersion from "src/hooks/useSelectedVersion"; import { flattenGraphNodes } from "src/layouts/Details/Grid/utils.ts"; import { useDependencyGraph } from "src/queries/useDependencyGraph"; import { useGridTiSummaries } from "src/queries/useGridTISummaries.ts"; -import { getReactFlowThemeStyle } from "src/theme"; const nodeColor = ( { data: { depth, height, isOpen, taskInstance, width }, type }: ReactFlowNode, @@ -151,6 +151,14 @@ export const Graph = () => { }, })); + const reactFlowStyle: CSSProperties = { + "--xy-background-color": "var(--chakra-colors-graph-bg)", + "--xy-background-pattern-color": "var(--chakra-colors-graph-pattern)", + "--xy-controls-button-background-color": "var(--chakra-colors-graph-controls-bg)", + "--xy-controls-button-background-color-hover": "var(--chakra-colors-graph-controls-hover)", + "--xy-minimap-background-color": "var(--chakra-colors-graph-minimap-bg)", + } as CSSProperties; + return ( { nodesDraggable={false} nodeTypes={nodeTypes} onlyRenderVisibleElements - style={getReactFlowThemeStyle(colorMode)} + style={reactFlowStyle} > diff --git a/airflow-core/src/airflow/ui/src/layouts/Details/Grid/GridButton.tsx b/airflow-core/src/airflow/ui/src/layouts/Details/Grid/GridButton.tsx index f3dd0b3dc077f..c9035cf08a564 100644 --- a/airflow-core/src/airflow/ui/src/layouts/Details/Grid/GridButton.tsx +++ b/airflow-core/src/airflow/ui/src/layouts/Details/Grid/GridButton.tsx @@ -44,7 +44,7 @@ export const GridButton = ({ }: Props) => isGroup ? ( { const { assetId } = useParams(); @@ -61,6 +61,14 @@ export const AssetGraph = ({ asset }: { readonly asset?: AssetResponse }) => { }, })); + const reactFlowStyle: CSSProperties = { + "--xy-background-color": "var(--chakra-colors-asset-graph-bg)", + "--xy-background-pattern-color": "var(--chakra-colors-asset-graph-pattern)", + "--xy-controls-button-background-color": "var(--chakra-colors-asset-graph-controls-bg)", + "--xy-controls-button-background-color-hover": "var(--chakra-colors-asset-graph-controls-hover)", + "--xy-minimap-background-color": "var(--chakra-colors-asset-graph-minimap-bg)", + } as CSSProperties; + return ( { nodesDraggable={false} nodeTypes={nodeTypes} onlyRenderVisibleElements - style={getReactFlowThemeStyle(colorMode)} + style={reactFlowStyle} > diff --git a/airflow-core/src/airflow/ui/src/pages/Dag/Calendar/CalendarTooltipContent.tsx b/airflow-core/src/airflow/ui/src/pages/Dag/Calendar/CalendarTooltipContent.tsx new file mode 100644 index 0000000000000..08a1cae9f2a58 --- /dev/null +++ b/airflow-core/src/airflow/ui/src/pages/Dag/Calendar/CalendarTooltipContent.tsx @@ -0,0 +1,70 @@ +/*! + * 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 { Box, HStack, Text, VStack } from "@chakra-ui/react"; + +import type { CalendarCellData } from "./types"; +import { createTooltipContent } from "./calendarUtils"; + +const SQUARE_SIZE = "12px"; +const SQUARE_BORDER_RADIUS = "2px"; + +type Props = { + readonly cellData: CalendarCellData; +}; + +const STATE_COLOR_MAP: Record = { + failed: "failed.solid", + planned: "scheduled.solid", + queued: "queued.solid", + running: "running.solid", + success: "success.solid", +} as const; + +export const CalendarTooltipContent = ({ cellData }: Props) => { + const { date, hasRuns, states, total } = createTooltipContent(cellData); + + if (!hasRuns) { + return {date}: No runs; + } + + return ( + + + {date}: {total} runs + + + {states.map(({ count, state }) => ( + + + + {count} {state} + + + ))} + + + ); +}; diff --git a/airflow-core/src/airflow/ui/src/pages/Dag/Overview/Overview.tsx b/airflow-core/src/airflow/ui/src/pages/Dag/Overview/Overview.tsx index 8cebcc4ba0b8e..f2f6ffe0aa3cb 100644 --- a/airflow-core/src/airflow/ui/src/pages/Dag/Overview/Overview.tsx +++ b/airflow-core/src/airflow/ui/src/pages/Dag/Overview/Overview.tsx @@ -89,7 +89,7 @@ export const Overview = () => { ({ @@ -104,7 +104,7 @@ export const Overview = () => { startDate={startDate} /> ({ diff --git a/airflow-core/src/airflow/ui/src/pages/Dashboard/HistoricalMetrics/MetricSection.tsx b/airflow-core/src/airflow/ui/src/pages/Dashboard/HistoricalMetrics/MetricSection.tsx index 48c3fdd4e30a3..73570609d32dd 100644 --- a/airflow-core/src/airflow/ui/src/pages/Dashboard/HistoricalMetrics/MetricSection.tsx +++ b/airflow-core/src/airflow/ui/src/pages/Dashboard/HistoricalMetrics/MetricSection.tsx @@ -68,7 +68,7 @@ export const MetricSection = ({ endDate, kind, runs, startDate, state, total }: - + {importErrorsCount} diff --git a/airflow-core/src/airflow/ui/src/pages/Dashboard/Stats/Stats.tsx b/airflow-core/src/airflow/ui/src/pages/Dashboard/Stats/Stats.tsx index 4192ad7b88652..5bb1e09cb103b 100644 --- a/airflow-core/src/airflow/ui/src/pages/Dashboard/Stats/Stats.tsx +++ b/airflow-core/src/airflow/ui/src/pages/Dashboard/Stats/Stats.tsx @@ -55,7 +55,7 @@ export const Stats = () => { { {queuedDagsCount > 0 ? ( { ) : undefined} { /> } isLoading={isStatsLoading} diff --git a/airflow-core/src/airflow/ui/src/pages/Task/Overview/Overview.tsx b/airflow-core/src/airflow/ui/src/pages/Task/Overview/Overview.tsx index d003174b98752..bd7660f7d0805 100644 --- a/airflow-core/src/airflow/ui/src/pages/Task/Overview/Overview.tsx +++ b/airflow-core/src/airflow/ui/src/pages/Task/Overview/Overview.tsx @@ -84,7 +84,7 @@ export const Overview = () => { ({ diff --git a/airflow-core/src/airflow/ui/src/pages/Variables/ManageVariable/DeleteVariableButton.tsx b/airflow-core/src/airflow/ui/src/pages/Variables/ManageVariable/DeleteVariableButton.tsx index 5bce2200d7a7e..3793883a6b0b5 100644 --- a/airflow-core/src/airflow/ui/src/pages/Variables/ManageVariable/DeleteVariableButton.tsx +++ b/airflow-core/src/airflow/ui/src/pages/Variables/ManageVariable/DeleteVariableButton.tsx @@ -39,6 +39,10 @@ const DeleteVariableButton = ({ deleteKey: variableKey, disabled }: Props) => { return ( <> ({ solid: { value: `{colors.${color}.600}` }, @@ -33,6 +32,13 @@ const generateSemanticTokens = (color: string, darkContrast: string = "white") = focusRing: { value: { _light: `{colors.${color}.800}`, _dark: `{colors.${color}.200}` } }, }); +// Utility function to resolve CSS variables to their computed values +// See: https://github.com/chakra-ui/panda/discussions/2200 +export const resolveTokenValue = (variable: string): string => + getComputedStyle(document.documentElement) + .getPropertyValue(variable.slice(4, variable.length - 1)) + .trim(); + export const customConfig = defineConfig({ // See https://chakra-ui.com/docs/theming/colors for more information on the colors used here. theme: { @@ -392,35 +398,232 @@ export const customConfig = defineConfig({ zinc: generateSemanticTokens("zinc"), neutral: generateSemanticTokens("neutral"), stone: generateSemanticTokens("stone"), + + // COMPONENT-SPECIFIC SEMANTIC TOKENS + + // components/DataTable/DataTable.tsx + "data-table": { + header: { + bg: { value: { _light: "{colors.gray.50}", _dark: "{colors.gray.800}" } }, + text: { value: { _light: "{colors.gray.700}", _dark: "{colors.gray.300}" } }, + border: { value: { _light: "{colors.gray.200}", _dark: "{colors.gray.700}" } } + }, + row: { + bg: { value: { _light: "white", _dark: "{colors.gray.900}" } }, + hover: { value: { _light: "{colors.gray.50}", _dark: "{colors.gray.800}" } }, + border: { value: { _light: "{colors.gray.100}", _dark: "{colors.gray.800}" } } + }, + cell: { + text: { value: { _light: "{colors.gray.700}", _dark: "{colors.gray.300}" } }, + muted: { value: { _light: "{colors.gray.500}", _dark: "{colors.gray.500}" } } + } + }, + + // components/JsonEditor.tsx + "json-editor": { + container: { + bg: { value: { _light: "white", _dark: "{colors.gray.900}" } }, + border: { value: { _light: "{colors.gray.300}", _dark: "{colors.gray.600}" } }, + focus: { value: "{colors.brand.600}" } + }, + lineNumbers: { + bg: { value: { _light: "{colors.gray.50}", _dark: "{colors.gray.800}" } }, + text: { value: { _light: "{colors.gray.500}", _dark: "{colors.gray.500}" } } + }, + selection: { + bg: { value: { _light: "{colors.brand.100}", _dark: "{colors.brand.800/30}" } } + }, + syntax: { + keyword: { value: { _light: "{colors.purple.600}", _dark: "{colors.purple.400}" } }, + string: { value: { _light: "{colors.green.600}", _dark: "{colors.green.400}" } }, + number: { value: { _light: "{colors.blue.600}", _dark: "{colors.blue.400}" } }, + comment: { value: { _light: "{colors.gray.500}", _dark: "{colors.gray.500}" } } + } + }, + + // components/SearchBar.tsx + "search-bar": { + container: { + bg: { value: { _light: "white", _dark: "{colors.gray.800}" } }, + border: { value: { _light: "{colors.gray.300}", _dark: "{colors.gray.600}" } }, + focus: { value: "{colors.brand.600}" } + }, + input: { + bg: { value: "transparent" }, + text: { value: { _light: "{colors.gray.900}", _dark: "{colors.gray.100}" } }, + placeholder: { value: { _light: "{colors.gray.500}", _dark: "{colors.gray.500}" } } + }, + button: { + bg: { value: "transparent" }, + text: { value: { _light: "{colors.gray.700}", _dark: "{colors.gray.300}" } }, + hover: { value: { _light: "{colors.gray.100}", _dark: "{colors.gray.700}" } } + }, + kbd: { + bg: { value: { _light: "{colors.gray.100}", _dark: "{colors.gray.700}" } }, + text: { value: { _light: "{colors.gray.600}", _dark: "{colors.gray.400}" } }, + border: { value: { _light: "{colors.gray.200}", _dark: "{colors.gray.600}" } } + } + }, + + // components/ui/ActionButton.tsx + "action-button": { + primary: { + bg: { value: "{colors.brand.600}" }, + text: { value: "white" }, + hover: { value: "{colors.brand.700}" }, + focus: { value: "{colors.brand.600}" } + }, + secondary: { + bg: { value: "transparent" }, + text: { value: { _light: "{colors.gray.700}", _dark: "{colors.gray.300}" } }, + border: { value: { _light: "{colors.gray.300}", _dark: "{colors.gray.600}" } }, + hover: { value: { _light: "{colors.gray.100}", _dark: "{colors.gray.700}" } } + }, + ghost: { + bg: { value: "transparent" }, + text: { value: { _light: "{colors.gray.700}", _dark: "{colors.gray.300}" } }, + hover: { value: { _light: "{colors.gray.100}", _dark: "{colors.gray.700}" } } + }, + danger: { + bg: { value: "{colors.red.600}" }, + text: { value: "white" }, + hover: { value: "{colors.red.700}" } + } + }, + + // components/ui/Alert.tsx + alert: { + success: { + bg: { value: { _light: "{colors.green.50}", _dark: "{colors.green.900/20}" } }, + border: { value: { _light: "{colors.green.200}", _dark: "{colors.green.800/40}" } }, + text: { value: { _light: "{colors.green.700}", _dark: "{colors.green.300}" } }, + icon: { value: "{colors.green.500}" } + }, + error: { + bg: { value: { _light: "{colors.red.50}", _dark: "{colors.red.900/20}" } }, + border: { value: { _light: "{colors.red.200}", _dark: "{colors.red.800/40}" } }, + text: { value: { _light: "{colors.red.700}", _dark: "{colors.red.300}" } }, + icon: { value: "{colors.red.500}" } + }, + warning: { + bg: { value: { _light: "{colors.yellow.50}", _dark: "{colors.yellow.900/20}" } }, + border: { value: { _light: "{colors.yellow.200}", _dark: "{colors.yellow.800/40}" } }, + text: { value: { _light: "{colors.yellow.700}", _dark: "{colors.yellow.300}" } }, + icon: { value: "{colors.yellow.500}" } + }, + info: { + bg: { value: { _light: "{colors.blue.50}", _dark: "{colors.blue.900/20}" } }, + border: { value: { _light: "{colors.blue.200}", _dark: "{colors.blue.800/40}" } }, + text: { value: { _light: "{colors.blue.700}", _dark: "{colors.blue.300}" } }, + icon: { value: "{colors.blue.500}" } + } + }, + + // components/ui/Tooltip.tsx + tooltip: { + container: { + bg: { value: { _light: "{colors.gray.900}", _dark: "{colors.gray.100}" } }, + text: { value: { _light: "white", _dark: "{colors.gray.900}" } }, + border: { value: "transparent" }, + shadow: { value: "0 4px 6px rgba(0, 0, 0, 0.1)" } + }, + arrow: { + bg: { value: { _light: "{colors.gray.900}", _dark: "{colors.gray.100}" } } + } + }, + + // layouts/Details/Gantt/Gantt.tsx + gantt: { + grid: { + color: { value: { _light: "{colors.gray.200}", _dark: "{colors.gray.800}" } } + }, + selected: { + bg: { value: { _light: "{colors.blue.200}", _dark: "{colors.blue.800}" } } + }, + hover: { + bg: { value: { _light: "{colors.blue.100}", _dark: "{colors.blue.900}" } } + } + }, + + // layouts/Details/Graph/Graph.tsx + graph: { + node: { + success: { value: "{colors.green.500}" }, + failed: { value: "{colors.red.500}" }, + running: { value: "{colors.cyan.500}" }, + queued: { value: "{colors.yellow.500}" }, + skipped: { value: "{colors.gray.400}" }, + default: { value: { _light: "{colors.gray.300}", _dark: "{colors.gray.600}" } } + }, + edge: { + default: { value: { _light: "{colors.gray.300}", _dark: "{colors.gray.600}" } }, + active: { value: "{colors.brand.600}" } + }, + background: { value: { _light: "{colors.gray.50}", _dark: "{colors.gray.900}" } }, + bg: { value: { _light: "{colors.brand.50}", _dark: "{colors.brand.950}" } }, + pattern: { value: { _light: "{colors.gray.800}", _dark: "{colors.gray.200}" } }, + controls: { + bg: { value: { _light: "white", _dark: "{colors.gray.800}" } }, + hover: { value: { _light: "{colors.gray.100}", _dark: "{colors.gray.700}" } } + }, + minimap: { + bg: { value: { _light: "white", _dark: "{colors.gray.800}" } } + } + }, + + // pages/Asset/AssetGraph.tsx + "asset-graph": { + bg: { value: { _light: "{colors.brand.50}", _dark: "{colors.brand.950}" } }, + pattern: { value: { _light: "{colors.gray.800}", _dark: "{colors.gray.200}" } }, + controls: { + bg: { value: { _light: "white", _dark: "{colors.gray.800}" } }, + hover: { value: { _light: "{colors.gray.100}", _dark: "{colors.gray.700}" } } + }, + minimap: { + bg: { value: { _light: "white", _dark: "{colors.gray.800}" } } + } + }, + + // Generic tokens (used by HeaderCard, StatsCard, etc.) + card: { + default: { + bg: { value: { _light: "white", _dark: "{colors.gray.800}" } }, + border: { value: { _light: "{colors.gray.200}", _dark: "{colors.gray.700}" } }, + shadow: { value: { _light: "0 1px 3px rgba(0, 0, 0, 0.1)", _dark: "0 1px 3px rgba(0, 0, 0, 0.3)" } } + }, + elevated: { + bg: { value: { _light: "white", _dark: "{colors.gray.800}" } }, + border: { value: { _light: "{colors.gray.200}", _dark: "{colors.gray.700}" } }, + shadow: { value: { _light: "0 4px 6px rgba(0, 0, 0, 0.1)", _dark: "0 4px 6px rgba(0, 0, 0, 0.3)" } } + }, + header: { + bg: { value: { _light: "{colors.gray.50}", _dark: "{colors.gray.700}" } }, + text: { value: { _light: "{colors.gray.900}", _dark: "{colors.gray.100}" } }, + border: { value: { _light: "{colors.gray.200}", _dark: "{colors.gray.600}" } } + } + }, + + // Generic tokens (used by FlexibleForm and other forms) + form: { + input: { + bg: { value: { _light: "white", _dark: "{colors.gray.800}" } }, + border: { value: { _light: "{colors.gray.300}", _dark: "{colors.gray.600}" } }, + focus: { value: "{colors.brand.600}" }, + error: { value: "{colors.red.500}" }, + text: { value: { _light: "{colors.gray.900}", _dark: "{colors.gray.100}" } }, + placeholder: { value: { _light: "{colors.gray.500}", _dark: "{colors.gray.500}" } } + }, + label: { + text: { value: { _light: "{colors.gray.700}", _dark: "{colors.gray.300}" } }, + required: { value: "{colors.red.500}" } + }, + help: { + text: { value: { _light: "{colors.gray.600}", _dark: "{colors.gray.400}" } } + } + }, }, }, }, }); -export const system = createSystem(defaultConfig, customConfig); - -// Utility function to resolve CSS variables to their computed values -// See: https://github.com/chakra-ui/panda/discussions/2200 -export const getComputedCSSVariableValue = (variable: string): string => - getComputedStyle(document.documentElement) - .getPropertyValue(variable.slice(4, variable.length - 1)) - .trim(); - -// Returns ReactFlow style props using Chakra UI CSS variables -export const getReactFlowThemeStyle = (colorMode: "dark" | "light"): CSSProperties => - ({ - // Background - "--xy-background-color": - colorMode === "dark" ? "var(--chakra-colors-brand-950)" : "var(--chakra-colors-brand-50)", - "--xy-background-pattern-color": - colorMode === "dark" ? "var(--chakra-colors-gray-200)" : "var(--chakra-colors-gray-800)", - - // Controls - "--xy-controls-button-background-color": - colorMode === "dark" ? "var(--chakra-colors-gray-800)" : "var(--chakra-colors-white)", - "--xy-controls-button-background-color-hover": - colorMode === "dark" ? "var(--chakra-colors-gray-700)" : "var(--chakra-colors-gray-100)", - - // MiniMap - "--xy-minimap-background-color": "var(--chakra-colors-bg)", - }) as CSSProperties; +export const system = createSystem(defaultConfig, customConfig); \ No newline at end of file From 0aab8729e93ceaf297c76e122a8e379e37dba5ea Mon Sep 17 00:00:00 2001 From: Olivier Daneau Date: Fri, 3 Oct 2025 05:29:39 -0400 Subject: [PATCH 02/13] fix TrendCountChart.tsx --- .../airflow/ui/src/components/TrendCountChart.tsx | 12 +++++++----- airflow-core/src/airflow/ui/src/theme.ts | 12 ++++++++++++ 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/airflow-core/src/airflow/ui/src/components/TrendCountChart.tsx b/airflow-core/src/airflow/ui/src/components/TrendCountChart.tsx index e0567107a1a7f..7fad8edbdf822 100644 --- a/airflow-core/src/airflow/ui/src/components/TrendCountChart.tsx +++ b/airflow-core/src/airflow/ui/src/components/TrendCountChart.tsx @@ -99,12 +99,11 @@ type Props = { export const TrendCountChart = ({ endDate, events, startDate }: Props) => { const chartRef = useRef>(); - // Get semantic token colors that automatically handle light/dark mode const [successBg, successLine, failedBg, failedLine] = useToken("colors", [ - "success.subtle", - "success.solid", - "failed.subtle", - "failed.solid", + "trend-count-chart.success.bg", + "trend-count-chart.success.line", + "trend-count-chart.failed.bg", + "trend-count-chart.failed.line", ]).map(token => resolveTokenValue(token || "oklch(0.5 0 0)")); const intervalData = useMemo( @@ -112,6 +111,9 @@ export const TrendCountChart = ({ endDate, events, startDate }: Props) => { [events, startDate, endDate], ); + // TODO: Add a default/neutral state (neither green nor red) when no runs have happened yet. + // Currently shows green (success) when count is 0, but this is misleading when there are + // actually no runs at all (as opposed to runs with no failures). const hasFailures = intervalData.some((value) => value > 0); const backgroundColor = hasFailures ? failedBg : successBg; const lineColor = hasFailures ? failedLine : successLine; diff --git a/airflow-core/src/airflow/ui/src/theme.ts b/airflow-core/src/airflow/ui/src/theme.ts index fdb72e044ab40..75bcfc23ac4b5 100644 --- a/airflow-core/src/airflow/ui/src/theme.ts +++ b/airflow-core/src/airflow/ui/src/theme.ts @@ -465,6 +465,18 @@ export const customConfig = defineConfig({ } }, + // components/TrendCountChart.tsx + "trend-count-chart": { + success: { + bg: { value: { _light: "{colors.green.100}", _dark: "{colors.green.800}" } }, + line: { value: { _light: "{colors.green.500}", _dark: "{colors.green.400}" } } + }, + failed: { + bg: { value: { _light: "{colors.red.100}", _dark: "{colors.red.800}" } }, + line: { value: { _light: "{colors.red.500}", _dark: "{colors.red.400}" } } + } + }, + // components/ui/ActionButton.tsx "action-button": { primary: { From 6c63eb7b81cded9c6b64b91d15c3310ed54182ad Mon Sep 17 00:00:00 2001 From: Olivier Daneau Date: Fri, 3 Oct 2025 05:46:27 -0400 Subject: [PATCH 03/13] fix Gantt --- airflow-core/src/airflow/ui/src/layouts/Details/Gantt/Gantt.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 7c9c33ebbcb1b..c8985a13b3065 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 @@ -93,7 +93,7 @@ export const Gantt = ({ dagRunState, limit, runType, triggeringUser }: Props) => "gantt.grid.color", "gantt.selected.bg", "gantt.hover.bg", - ]); + ]).map(token => resolveTokenValue(token || "oklch(0.5 0 0)")); const { data: gridRuns, isLoading: runsLoading } = useGridRuns({ dagRunState, From e830adcb9b89dfc06f1781855c7762f4b4eb4961 Mon Sep 17 00:00:00 2001 From: Olivier Daneau Date: Fri, 3 Oct 2025 05:51:47 -0400 Subject: [PATCH 04/13] fix DurationChart --- .../src/airflow/ui/src/components/DurationChart.tsx | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/airflow-core/src/airflow/ui/src/components/DurationChart.tsx b/airflow-core/src/airflow/ui/src/components/DurationChart.tsx index 7e6191c6341d4..98a7b07f4d5a6 100644 --- a/airflow-core/src/airflow/ui/src/components/DurationChart.tsx +++ b/airflow-core/src/airflow/ui/src/components/DurationChart.tsx @@ -68,14 +68,15 @@ export const DurationChart = ({ }) => { const { t: translate } = useTranslation(["components", "common"]); const navigate = useNavigate(); - const [queuedColorToken] = useToken("colors", ["queued.solid"]); + const [queuedColor] = useToken("colors", ["queued.solid"]) + .map(token => resolveTokenValue(token || "oklch(0.5 0 0)")); // Get states and create color tokens for them const states = entries?.map((entry) => entry.state).filter(Boolean) ?? []; - const stateColorTokens = useToken( + const stateColors = useToken( "colors", states.map((state) => `${state}.solid`), - ); + ).map(token => resolveTokenValue(token || "oklch(0.5 0 0)")); if (!entries) { return undefined; @@ -85,8 +86,8 @@ export const DurationChart = ({ const stateColorMap: Record = {}; states.forEach((state, index) => { - if (state) { - stateColorMap[state] = resolveTokenValue(stateColorTokens[index] ?? "oklch(0.5 0 0)"); + if (state && stateColors[index]) { + stateColorMap[state] = stateColors[index]; } }); @@ -125,7 +126,7 @@ export const DurationChart = ({ data={{ datasets: [ { - backgroundColor: resolveTokenValue(queuedColorToken ?? "oklch(0.5 0 0)"), + backgroundColor: queuedColor, data: entries.map((entry: RunResponse) => { switch (kind) { case "Dag Run": { From cec0d9fa5bbfddb32519082d35e861fbc436a023 Mon Sep 17 00:00:00 2001 From: Olivier Daneau Date: Fri, 3 Oct 2025 05:51:58 -0400 Subject: [PATCH 05/13] fix GridTI --- airflow-core/src/airflow/ui/src/layouts/Details/Grid/GridTI.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/airflow-core/src/airflow/ui/src/layouts/Details/Grid/GridTI.tsx b/airflow-core/src/airflow/ui/src/layouts/Details/Grid/GridTI.tsx index 9efbeafd7704f..ddf294353cb70 100644 --- a/airflow-core/src/airflow/ui/src/layouts/Details/Grid/GridTI.tsx +++ b/airflow-core/src/airflow/ui/src/layouts/Details/Grid/GridTI.tsx @@ -139,7 +139,7 @@ const Instance = ({ dagId, instance, isGroup, isMapped, onClick, runId, taskId } Date: Fri, 3 Oct 2025 06:04:17 -0400 Subject: [PATCH 06/13] fix Graph and AssetGraph --- .../ui/src/layouts/Details/Graph/Graph.tsx | 36 +++++++++---------- .../airflow/ui/src/pages/Asset/AssetGraph.tsx | 23 +++++++----- airflow-core/src/airflow/ui/src/theme.ts | 12 ++++++- 3 files changed, 41 insertions(+), 30 deletions(-) diff --git a/airflow-core/src/airflow/ui/src/layouts/Details/Graph/Graph.tsx b/airflow-core/src/airflow/ui/src/layouts/Details/Graph/Graph.tsx index f9734b950179c..4c5d0b6ce262d 100644 --- a/airflow-core/src/airflow/ui/src/layouts/Details/Graph/Graph.tsx +++ b/airflow-core/src/airflow/ui/src/layouts/Details/Graph/Graph.tsx @@ -64,14 +64,15 @@ export const Graph = () => { const selectedVersion = useSelectedVersion(); - // corresponds to the "bg", "bg.emphasized", "border.inverted" semantic tokens - const [oddLight, oddDark, evenLight, evenDark, selectedDarkColor, selectedLightColor] = useToken("colors", [ - "bg", - "fg", - "bg.muted", - "bg.emphasized", - "bg.muted", - "bg.emphasized", + const [groupOdd, groupEven, selectedStroke, bg, pattern, controlsBg, controlsHover, minimapBg] = useToken("colors", [ + "graph.minimap.group.odd", + "graph.minimap.group.even", + "graph.selected.stroke", + "graph.bg", + "graph.pattern", + "graph.controls.bg", + "graph.controls.hover", + "graph.minimap.bg", ]); const { allGroupIds, openGroupIds, setAllGroupIds } = useOpenGroups(); @@ -79,7 +80,6 @@ export const Graph = () => { const [dependencies] = useLocalStorage<"all" | "immediate" | "tasks">(`dependencies-${dagId}`, "tasks"); const [direction] = useLocalStorage(`direction-${dagId}`, "RIGHT"); - const selectedColor = colorMode === "dark" ? selectedDarkColor : selectedLightColor; const { data: graphData = { edges: [], nodes: [] } } = useStructureServiceStructureData( { dagId, @@ -152,11 +152,11 @@ export const Graph = () => { })); const reactFlowStyle: CSSProperties = { - "--xy-background-color": "var(--chakra-colors-graph-bg)", - "--xy-background-pattern-color": "var(--chakra-colors-graph-pattern)", - "--xy-controls-button-background-color": "var(--chakra-colors-graph-controls-bg)", - "--xy-controls-button-background-color-hover": "var(--chakra-colors-graph-controls-hover)", - "--xy-minimap-background-color": "var(--chakra-colors-graph-minimap-bg)", + "--xy-background-color": bg, + "--xy-background-pattern-color": pattern, + "--xy-controls-button-background-color": controlsBg, + "--xy-controls-button-background-color-hover": controlsHover, + "--xy-minimap-background-color": minimapBg, } as CSSProperties; return ( @@ -179,14 +179,10 @@ export const Graph = () => { ) => - nodeColor( - node, - colorMode === "dark" ? evenDark : evenLight, - colorMode === "dark" ? oddDark : oddLight, - ) + nodeColor(node, groupEven, groupOdd) } nodeStrokeColor={(node: ReactFlowNode) => - node.data.isSelected && selectedColor !== undefined ? selectedColor : "" + node.data.isSelected && selectedStroke !== undefined ? selectedStroke : "" } nodeStrokeWidth={15} pannable diff --git a/airflow-core/src/airflow/ui/src/pages/Asset/AssetGraph.tsx b/airflow-core/src/airflow/ui/src/pages/Asset/AssetGraph.tsx index 4a59c06b9a071..b3f41acdcbd73 100644 --- a/airflow-core/src/airflow/ui/src/pages/Asset/AssetGraph.tsx +++ b/airflow-core/src/airflow/ui/src/pages/Asset/AssetGraph.tsx @@ -46,9 +46,14 @@ export const AssetGraph = ({ asset }: { readonly asset?: AssetResponse }) => { node.id === `asset:${assetId}` ? { ...node, data: { ...node.data, isSelected: true } } : node, ); - const [selectedDarkColor, selectedLightColor] = useToken("colors", ["bg.muted", "bg.emphasized"]); - - const selectedColor = colorMode === "dark" ? selectedDarkColor : selectedLightColor; + const [selectedStroke, bg, pattern, controlsBg, controlsHover, minimapBg] = useToken("colors", [ + "asset-graph.selected.stroke", + "asset-graph.bg", + "asset-graph.pattern", + "asset-graph.controls.bg", + "asset-graph.controls.hover", + "asset-graph.minimap.bg", + ]); const edges = (graphData?.edges ?? []).map((edge) => ({ ...edge, @@ -62,11 +67,11 @@ export const AssetGraph = ({ asset }: { readonly asset?: AssetResponse }) => { })); const reactFlowStyle: CSSProperties = { - "--xy-background-color": "var(--chakra-colors-asset-graph-bg)", - "--xy-background-pattern-color": "var(--chakra-colors-asset-graph-pattern)", - "--xy-controls-button-background-color": "var(--chakra-colors-asset-graph-controls-bg)", - "--xy-controls-button-background-color-hover": "var(--chakra-colors-asset-graph-controls-hover)", - "--xy-minimap-background-color": "var(--chakra-colors-asset-graph-minimap-bg)", + "--xy-background-color": bg, + "--xy-background-pattern-color": pattern, + "--xy-controls-button-background-color": controlsBg, + "--xy-controls-button-background-color-hover": controlsHover, + "--xy-minimap-background-color": minimapBg, } as CSSProperties; return ( @@ -89,7 +94,7 @@ export const AssetGraph = ({ asset }: { readonly asset?: AssetResponse }) => { ) => - node.data.isSelected && selectedColor !== undefined ? selectedColor : "" + node.data.isSelected && selectedStroke !== undefined ? selectedStroke : "" } nodeStrokeWidth={15} pannable diff --git a/airflow-core/src/airflow/ui/src/theme.ts b/airflow-core/src/airflow/ui/src/theme.ts index 75bcfc23ac4b5..b93b852ecb0e6 100644 --- a/airflow-core/src/airflow/ui/src/theme.ts +++ b/airflow-core/src/airflow/ui/src/theme.ts @@ -579,7 +579,14 @@ export const customConfig = defineConfig({ hover: { value: { _light: "{colors.gray.100}", _dark: "{colors.gray.700}" } } }, minimap: { - bg: { value: { _light: "white", _dark: "{colors.gray.800}" } } + bg: { value: { _light: "white", _dark: "{colors.gray.800}" } }, + group: { + odd: { value: { _light: "white", _dark: "{colors.gray.900}" } }, + even: { value: { _light: "{colors.gray.200}", _dark: "{colors.gray.300}" } } + } + }, + selected: { + stroke: { value: { _light: "{colors.gray.900}", _dark: "{colors.gray.100}" } } } }, @@ -593,6 +600,9 @@ export const customConfig = defineConfig({ }, minimap: { bg: { value: { _light: "white", _dark: "{colors.gray.800}" } } + }, + selected: { + stroke: { value: { _light: "{colors.gray.900}", _dark: "{colors.gray.100}" } } } }, From 5491b297d9ba3b76622b1a11df0e0185f8b958c7 Mon Sep 17 00:00:00 2001 From: Olivier Daneau Date: Fri, 3 Oct 2025 06:07:07 -0400 Subject: [PATCH 07/13] remove unused file --- .../Dag/Calendar/CalendarTooltipContent.tsx | 70 ------------------- 1 file changed, 70 deletions(-) delete mode 100644 airflow-core/src/airflow/ui/src/pages/Dag/Calendar/CalendarTooltipContent.tsx diff --git a/airflow-core/src/airflow/ui/src/pages/Dag/Calendar/CalendarTooltipContent.tsx b/airflow-core/src/airflow/ui/src/pages/Dag/Calendar/CalendarTooltipContent.tsx deleted file mode 100644 index 08a1cae9f2a58..0000000000000 --- a/airflow-core/src/airflow/ui/src/pages/Dag/Calendar/CalendarTooltipContent.tsx +++ /dev/null @@ -1,70 +0,0 @@ -/*! - * 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 { Box, HStack, Text, VStack } from "@chakra-ui/react"; - -import type { CalendarCellData } from "./types"; -import { createTooltipContent } from "./calendarUtils"; - -const SQUARE_SIZE = "12px"; -const SQUARE_BORDER_RADIUS = "2px"; - -type Props = { - readonly cellData: CalendarCellData; -}; - -const STATE_COLOR_MAP: Record = { - failed: "failed.solid", - planned: "scheduled.solid", - queued: "queued.solid", - running: "running.solid", - success: "success.solid", -} as const; - -export const CalendarTooltipContent = ({ cellData }: Props) => { - const { date, hasRuns, states, total } = createTooltipContent(cellData); - - if (!hasRuns) { - return {date}: No runs; - } - - return ( - - - {date}: {total} runs - - - {states.map(({ count, state }) => ( - - - - {count} {state} - - - ))} - - - ); -}; From a9a52eca11a5ff9b31427aa40f3d5720fd19a15f Mon Sep 17 00:00:00 2001 From: Olivier Daneau Date: Fri, 3 Oct 2025 06:08:32 -0400 Subject: [PATCH 08/13] restore MetricSection --- .../ui/src/pages/Dashboard/HistoricalMetrics/MetricSection.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/airflow-core/src/airflow/ui/src/pages/Dashboard/HistoricalMetrics/MetricSection.tsx b/airflow-core/src/airflow/ui/src/pages/Dashboard/HistoricalMetrics/MetricSection.tsx index 73570609d32dd..48c3fdd4e30a3 100644 --- a/airflow-core/src/airflow/ui/src/pages/Dashboard/HistoricalMetrics/MetricSection.tsx +++ b/airflow-core/src/airflow/ui/src/pages/Dashboard/HistoricalMetrics/MetricSection.tsx @@ -68,7 +68,7 @@ export const MetricSection = ({ endDate, kind, runs, startDate, state, total }: Date: Fri, 3 Oct 2025 06:14:29 -0400 Subject: [PATCH 09/13] fix Stats components --- airflow-core/src/airflow/ui/src/components/StatsCard.tsx | 6 +++--- .../ui/src/pages/Dashboard/Stats/DAGImportErrors.tsx | 2 +- .../ui/src/pages/Dashboard/Stats/PluginImportErrors.tsx | 4 ++-- .../src/airflow/ui/src/pages/Dashboard/Stats/Stats.tsx | 8 ++++---- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/airflow-core/src/airflow/ui/src/components/StatsCard.tsx b/airflow-core/src/airflow/ui/src/components/StatsCard.tsx index cbade7d249d0a..420e645d7a1cf 100644 --- a/airflow-core/src/airflow/ui/src/components/StatsCard.tsx +++ b/airflow-core/src/airflow/ui/src/components/StatsCard.tsx @@ -24,7 +24,7 @@ import type { TaskInstanceState } from "openapi/requests/types.gen"; import { StateBadge } from "src/components/StateBadge"; export const StatsCard = ({ - colorScheme, + colorPalette, count, icon, isLoading = false, @@ -34,7 +34,7 @@ export const StatsCard = ({ onClick, state, }: { - readonly colorScheme: string; + readonly colorPalette: string; readonly count: number; readonly icon?: React.ReactNode; readonly isLoading?: boolean; @@ -57,7 +57,7 @@ export const StatsCard = ({ cursor="pointer" p={2} > - + {icon} {count} diff --git a/airflow-core/src/airflow/ui/src/pages/Dashboard/Stats/DAGImportErrors.tsx b/airflow-core/src/airflow/ui/src/pages/Dashboard/Stats/DAGImportErrors.tsx index 87b9bf917e0c6..2dd6666db49d6 100644 --- a/airflow-core/src/airflow/ui/src/pages/Dashboard/Stats/DAGImportErrors.tsx +++ b/airflow-core/src/airflow/ui/src/pages/Dashboard/Stats/DAGImportErrors.tsx @@ -60,7 +60,7 @@ export const DAGImportErrors = ({ iconOnly = false }: { readonly iconOnly?: bool ) : ( } isLoading={isLoading} diff --git a/airflow-core/src/airflow/ui/src/pages/Dashboard/Stats/PluginImportErrors.tsx b/airflow-core/src/airflow/ui/src/pages/Dashboard/Stats/PluginImportErrors.tsx index 0f7f90cb1af26..ed9643c35c1da 100644 --- a/airflow-core/src/airflow/ui/src/pages/Dashboard/Stats/PluginImportErrors.tsx +++ b/airflow-core/src/airflow/ui/src/pages/Dashboard/Stats/PluginImportErrors.tsx @@ -53,7 +53,7 @@ export const PluginImportErrors = ({ iconOnly = false }: { readonly iconOnly?: b {iconOnly ? ( - + {importErrorsCount} diff --git a/airflow-core/src/airflow/ui/src/pages/Dashboard/Stats/Stats.tsx b/airflow-core/src/airflow/ui/src/pages/Dashboard/Stats/Stats.tsx index 5bb1e09cb103b..4559a3fcc45e5 100644 --- a/airflow-core/src/airflow/ui/src/pages/Dashboard/Stats/Stats.tsx +++ b/airflow-core/src/airflow/ui/src/pages/Dashboard/Stats/Stats.tsx @@ -55,7 +55,7 @@ export const Stats = () => { { {queuedDagsCount > 0 ? ( { ) : undefined} { /> } isLoading={isStatsLoading} From 22eb149205496d54b2f5f91b8229dc99bfba692f Mon Sep 17 00:00:00 2001 From: Olivier Daneau Date: Fri, 3 Oct 2025 06:54:52 -0400 Subject: [PATCH 10/13] revert DeleteVariableButton --- .../pages/Variables/ManageVariable/DeleteVariableButton.tsx | 4 ---- 1 file changed, 4 deletions(-) diff --git a/airflow-core/src/airflow/ui/src/pages/Variables/ManageVariable/DeleteVariableButton.tsx b/airflow-core/src/airflow/ui/src/pages/Variables/ManageVariable/DeleteVariableButton.tsx index 3793883a6b0b5..5bce2200d7a7e 100644 --- a/airflow-core/src/airflow/ui/src/pages/Variables/ManageVariable/DeleteVariableButton.tsx +++ b/airflow-core/src/airflow/ui/src/pages/Variables/ManageVariable/DeleteVariableButton.tsx @@ -39,10 +39,6 @@ const DeleteVariableButton = ({ deleteKey: variableKey, disabled }: Props) => { return ( <> Date: Sat, 4 Oct 2025 07:34:30 -0400 Subject: [PATCH 11/13] bulk changes --- .../DagActions/FavoriteDagButton.tsx | 1 + .../ui/src/components/DateTimeInput.tsx | 2 + .../src/components/EditableMarkdownButton.tsx | 5 +- .../ui/src/components/FilterBar/FilterBar.tsx | 5 +- .../src/components/FilterBar/FilterPill.tsx | 9 +- .../FilterBar/filters/SelectFilter.tsx | 13 +- .../src/components/Graph/DownloadButton.tsx | 1 + .../ui/src/components/NeedsReviewButton.tsx | 2 +- .../ui/src/components/QuickFilterButton.tsx | 4 +- .../airflow/ui/src/components/StatsCard.tsx | 5 + .../TriggerDag/TriggerDAGButton.tsx | 1 + .../components/TriggerDag/TriggerDAGForm.tsx | 2 +- .../ui/src/components/ui/ActionButton.tsx | 4 + .../ui/src/components/ui/InputWithAddon.tsx | 2 +- .../ui/src/components/ui/RadioCard.tsx | 14 +- .../ui/src/components/ui/Select/Trigger.tsx | 7 +- .../ui/src/components/ui/Toaster/Toaster.tsx | 2 +- .../ui/src/layouts/Details/DetailsLayout.tsx | 2 +- .../ui/src/layouts/Details/Gantt/Gantt.tsx | 4 +- .../ui/src/layouts/Details/Graph/Graph.tsx | 4 +- .../ui/src/layouts/Details/Grid/Bar.tsx | 5 +- .../src/airflow/ui/src/layouts/Nav/Nav.tsx | 2 +- .../airflow/ui/src/layouts/Nav/NavButton.tsx | 9 +- .../airflow/ui/src/pages/Asset/AssetGraph.tsx | 4 +- .../Connections/DeleteConnectionsButton.tsx | 4 +- .../Connections/TestConnectionButton.tsx | 4 +- .../pages/Dag/Calendar/HourlyCalendarView.tsx | 2 +- .../DagsList/DagsFilters/FavoriteFilter.tsx | 2 +- .../DagsList/DagsFilters/PausedFilter.tsx | 2 +- .../DagsList/DagsFilters/StateFilters.tsx | 7 +- .../TaskInstance/Logs/ExternalLogLink.tsx | 2 +- .../pages/Variables/ImportVariablesForm.tsx | 4 +- airflow-core/src/airflow/ui/src/theme.ts | 191 +++++++----------- 33 files changed, 157 insertions(+), 170 deletions(-) diff --git a/airflow-core/src/airflow/ui/src/components/DagActions/FavoriteDagButton.tsx b/airflow-core/src/airflow/ui/src/components/DagActions/FavoriteDagButton.tsx index 38024ecddde51..edbacd928280f 100644 --- a/airflow-core/src/airflow/ui/src/components/DagActions/FavoriteDagButton.tsx +++ b/airflow-core/src/airflow/ui/src/components/DagActions/FavoriteDagButton.tsx @@ -41,6 +41,7 @@ export const FavoriteDagButton = ({ dagId, isFavorite = false, withText = true } (({ onChange, va return ( onChange?.({ ...event, diff --git a/airflow-core/src/airflow/ui/src/components/EditableMarkdownButton.tsx b/airflow-core/src/airflow/ui/src/components/EditableMarkdownButton.tsx index 239720f61f219..519cf77993127 100644 --- a/airflow-core/src/airflow/ui/src/components/EditableMarkdownButton.tsx +++ b/airflow-core/src/airflow/ui/src/components/EditableMarkdownButton.tsx @@ -57,6 +57,7 @@ const EditableMarkdownButton = ({ { if (!isOpen) { @@ -70,7 +71,7 @@ const EditableMarkdownButton = ({ /> {Boolean(mdContent?.trim()) && ( - + {header} diff --git a/airflow-core/src/airflow/ui/src/components/FilterBar/FilterBar.tsx b/airflow-core/src/airflow/ui/src/components/FilterBar/FilterBar.tsx index e4880bf2bca08..245ec7c84b426 100644 --- a/airflow-core/src/airflow/ui/src/components/FilterBar/FilterBar.tsx +++ b/airflow-core/src/airflow/ui/src/components/FilterBar/FilterBar.tsx @@ -143,9 +143,10 @@ export const FilterBar = ({