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 09fc45decf4 Centralize localStorage keys into a shared constants
(#62310)
09fc45decf4 is described below
commit 09fc45decf409a99450cd2bc914ce93d0d7bd110
Author: Yeonguk Choo <[email protected]>
AuthorDate: Tue Feb 24 01:54:35 2026 +0900
Centralize localStorage keys into a shared constants (#62310)
---
.../src/components/DataTable/useTableUrlState.ts | 6 +--
.../src/airflow/ui/src/constants/localStorage.ts | 43 ++++++++++++++++++++++
.../src/context/openGroups/OpenGroupsProvider.tsx | 9 ++---
.../ui/src/context/timezone/TimezoneProvider.tsx | 4 +-
.../ui/src/layouts/Details/DetailsLayout.tsx | 23 ++++++++----
.../airflow/ui/src/layouts/Details/Graph/Graph.tsx | 5 ++-
.../ui/src/layouts/Details/PanelButtons.tsx | 5 ++-
.../ui/src/layouts/Nav/UserSettingsButton.tsx | 3 +-
.../airflow/ui/src/pages/Dag/Calendar/Calendar.tsx | 8 +++-
.../airflow/ui/src/pages/Dag/Overview/Overview.tsx | 3 +-
.../src/airflow/ui/src/pages/DagsList/DagsList.tsx | 5 +--
.../ui/src/pages/TaskInstance/Logs/Logs.tsx | 7 ++--
12 files changed, 89 insertions(+), 32 deletions(-)
diff --git
a/airflow-core/src/airflow/ui/src/components/DataTable/useTableUrlState.ts
b/airflow-core/src/airflow/ui/src/components/DataTable/useTableUrlState.ts
index 94021d04df5..a968a0c4cb4 100644
--- a/airflow-core/src/airflow/ui/src/components/DataTable/useTableUrlState.ts
+++ b/airflow-core/src/airflow/ui/src/components/DataTable/useTableUrlState.ts
@@ -20,6 +20,7 @@ import { useSearchParams } from "react-router-dom";
import { useLocation } from "react-router-dom";
import { useLocalStorage } from "usehooks-ts";
+import { tableSortKey } from "src/constants/localStorage";
import { useConfig } from "src/queries/useConfig";
import { searchParamsToState, stateToSearchParams } from "./searchParams";
@@ -30,10 +31,7 @@ export const useTableURLState = (defaultState?:
Partial<TableState>) => {
const location = useLocation();
const pageName = location.pathname;
- const [sorting, setSorting] = useLocalStorage<TableState["sorting"]>(
- `${pageName.replaceAll("/", "-").slice(1)}-table-sort`,
- [],
- );
+ const [sorting, setSorting] =
useLocalStorage<TableState["sorting"]>(tableSortKey(pageName), []);
const pageSize = useConfig("fallback_page_limit") as number;
diff --git a/airflow-core/src/airflow/ui/src/constants/localStorage.ts
b/airflow-core/src/airflow/ui/src/constants/localStorage.ts
new file mode 100644
index 00000000000..46482baddd3
--- /dev/null
+++ b/airflow-core/src/airflow/ui/src/constants/localStorage.ts
@@ -0,0 +1,43 @@
+/*!
+ * 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.
+ */
+
+// Global keys
+export const TIMEZONE_KEY = "timezone";
+export const DEFAULT_DAG_VIEW_KEY = "default_dag_view";
+export const DAGS_LIST_DISPLAY_KEY = "dags_list_display";
+export const CALENDAR_GRANULARITY_KEY = "calendar-granularity";
+export const CALENDAR_VIEW_MODE_KEY = "calendar-view-mode";
+export const LOG_WRAP_KEY = "log_wrap";
+export const LOG_SHOW_TIMESTAMP_KEY = "log_show_timestamp";
+export const LOG_SHOW_SOURCE_KEY = "log_show_source";
+
+// Dag-scoped keys
+export const dagViewKey = (dagId: string) => `dag_view-${dagId}`;
+export const dagRunsLimitKey = (dagId: string) => `dag_runs_limit-${dagId}`;
+export const runTypeFilterKey = (dagId: string) => `run_type_filter-${dagId}`;
+export const triggeringUserFilterKey = (dagId: string) =>
`triggering_user_filter-${dagId}`;
+export const dagRunStateFilterKey = (dagId: string) =>
`dag_run_state_filter-${dagId}`;
+export const showGanttKey = (dagId: string) => `show_gantt-${dagId}`;
+export const dependenciesKey = (dagId: string) => `dependencies-${dagId}`;
+export const directionKey = (dagId: string) => `direction-${dagId}`;
+export const openGroupsKey = (dagId: string) => `${dagId}/open-groups`;
+export const allGroupsKey = (dagId: string) => `${dagId}/all-groups`;
+
+// Page-scoped keys
+export const tableSortKey = (pageName: string) => `${pageName.replaceAll("/",
"-").slice(1)}-table-sort`;
diff --git
a/airflow-core/src/airflow/ui/src/context/openGroups/OpenGroupsProvider.tsx
b/airflow-core/src/airflow/ui/src/context/openGroups/OpenGroupsProvider.tsx
index 8ce91eb54d0..cdf9b4b8243 100644
--- a/airflow-core/src/airflow/ui/src/context/openGroups/OpenGroupsProvider.tsx
+++ b/airflow-core/src/airflow/ui/src/context/openGroups/OpenGroupsProvider.tsx
@@ -21,6 +21,7 @@ import { useDebouncedCallback } from "use-debounce";
import { useLocalStorage } from "usehooks-ts";
import { useStructureServiceStructureData } from "openapi/queries";
+import { allGroupsKey, dependenciesKey, openGroupsKey } from
"src/constants/localStorage";
import useSelectedVersion from "src/hooks/useSelectedVersion";
import { flattenGraphNodes } from "src/layouts/Details/Grid/utils";
@@ -31,10 +32,8 @@ type Props = {
} & PropsWithChildren;
export const OpenGroupsProvider = ({ children, dagId }: Props) => {
- const openGroupsKey = `${dagId}/open-groups`;
- const allGroupsKey = `${dagId}/all-groups`;
- const [openGroupIds, setOpenGroupIds] =
useLocalStorage<Array<string>>(openGroupsKey, []);
- const [allGroupIds, setAllGroupIds] =
useLocalStorage<Array<string>>(allGroupsKey, []);
+ const [openGroupIds, setOpenGroupIds] =
useLocalStorage<Array<string>>(openGroupsKey(dagId), []);
+ const [allGroupIds, setAllGroupIds] =
useLocalStorage<Array<string>>(allGroupsKey(dagId), []);
// use a ref to track the current allGroupIds without causing re-renders
const allGroupIdsRef = useRef(allGroupIds);
@@ -45,7 +44,7 @@ export const OpenGroupsProvider = ({ children, dagId }:
Props) => {
// For Graph view support: dependencies + selected version
const selectedVersion = useSelectedVersion();
- const [dependencies] = useLocalStorage<"all" | "immediate" |
"tasks">(`dependencies-${dagId}`, "tasks");
+ const [dependencies] = useLocalStorage<"all" | "immediate" |
"tasks">(dependenciesKey(dagId), "tasks");
// Fetch structure (minimal params if you want it lightweight for Grid)
const { data: structure = { edges: [], nodes: [] } } =
useStructureServiceStructureData(
diff --git
a/airflow-core/src/airflow/ui/src/context/timezone/TimezoneProvider.tsx
b/airflow-core/src/airflow/ui/src/context/timezone/TimezoneProvider.tsx
index 275b034b5c3..540868d19ff 100644
--- a/airflow-core/src/airflow/ui/src/context/timezone/TimezoneProvider.tsx
+++ b/airflow-core/src/airflow/ui/src/context/timezone/TimezoneProvider.tsx
@@ -19,9 +19,9 @@
import type { PropsWithChildren } from "react";
import { useLocalStorage } from "usehooks-ts";
-import { TimezoneContext, type TimezoneContextType } from "./Context";
+import { TIMEZONE_KEY } from "src/constants/localStorage";
-const TIMEZONE_KEY = "timezone";
+import { TimezoneContext, type TimezoneContextType } from "./Context";
export const TimezoneProvider = ({ children }: PropsWithChildren) => {
const systemTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
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 ffc687fd984..09d116a8370 100644
--- a/airflow-core/src/airflow/ui/src/layouts/Details/DetailsLayout.tsx
+++ b/airflow-core/src/airflow/ui/src/layouts/Details/DetailsLayout.tsx
@@ -41,6 +41,15 @@ import { TriggerDAGButton } from
"src/components/TriggerDag/TriggerDAGButton";
import { ProgressBar } from "src/components/ui";
import { Toaster } from "src/components/ui";
import { Tooltip } from "src/components/ui/Tooltip";
+import {
+ dagRunsLimitKey,
+ dagRunStateFilterKey,
+ dagViewKey,
+ DEFAULT_DAG_VIEW_KEY,
+ runTypeFilterKey,
+ showGanttKey,
+ triggeringUserFilterKey,
+} from "src/constants/localStorage";
import { HoverProvider } from "src/context/hover";
import { OpenGroupsProvider } from "src/context/openGroups";
@@ -61,24 +70,24 @@ export const DetailsLayout = ({ children, error, isLoading,
tabs }: Props) => {
const { t: translate } = useTranslation();
const { dagId = "", runId } = useParams();
const { data: dag } = useDagServiceGetDag({ dagId });
- const [defaultDagView] = useLocalStorage<"graph" |
"grid">("default_dag_view", "grid");
+ const [defaultDagView] = useLocalStorage<"graph" |
"grid">(DEFAULT_DAG_VIEW_KEY, "grid");
const panelGroupRef = useRef<ImperativePanelGroupHandle | null>(null);
- const [dagView, setDagView] = useLocalStorage<"graph" |
"grid">(`dag_view-${dagId}`, defaultDagView);
- const [limit, setLimit] = useLocalStorage<number>(`dag_runs_limit-${dagId}`,
10);
+ const [dagView, setDagView] = useLocalStorage<"graph" |
"grid">(dagViewKey(dagId), defaultDagView);
+ const [limit, setLimit] = useLocalStorage<number>(dagRunsLimitKey(dagId),
10);
const [runTypeFilter, setRunTypeFilter] = useLocalStorage<DagRunType |
undefined>(
- `run_type_filter-${dagId}`,
+ runTypeFilterKey(dagId),
undefined,
);
const [triggeringUserFilter, setTriggeringUserFilter] =
useLocalStorage<string | undefined>(
- `triggering_user_filter-${dagId}`,
+ triggeringUserFilterKey(dagId),
undefined,
);
const [dagRunStateFilter, setDagRunStateFilter] =
useLocalStorage<DagRunState | undefined>(
- `dag_run_state_filter-${dagId}`,
+ dagRunStateFilterKey(dagId),
undefined,
);
- const [showGantt, setShowGantt] =
useLocalStorage<boolean>(`show_gantt-${dagId}`, false);
+ const [showGantt, setShowGantt] =
useLocalStorage<boolean>(showGanttKey(dagId), false);
const { fitView, getZoom } = useReactFlow();
const { data: warningData } = useDagWarningServiceListDagWarnings({ dagId });
const { onClose, onOpen, open } = useDisclosure();
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 21593ff128b..0b69efb7d43 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
@@ -28,6 +28,7 @@ import { DownloadButton } from
"src/components/Graph/DownloadButton";
import { edgeTypes, nodeTypes } from "src/components/Graph/graphTypes";
import type { CustomNodeProps } from "src/components/Graph/reactflowUtils";
import { type Direction, useGraphLayout } from
"src/components/Graph/useGraphLayout";
+import { dependenciesKey, directionKey } from "src/constants/localStorage";
import { useColorMode } from "src/context/colorMode";
import { useOpenGroups } from "src/context/openGroups";
import useSelectedVersion from "src/hooks/useSelectedVersion";
@@ -85,8 +86,8 @@ export const Graph = () => {
const { allGroupIds, openGroupIds, setAllGroupIds } = useOpenGroups();
- const [dependencies] = useLocalStorage<"all" | "immediate" |
"tasks">(`dependencies-${dagId}`, "tasks");
- const [direction] = useLocalStorage<Direction>(`direction-${dagId}`,
"RIGHT");
+ const [dependencies] = useLocalStorage<"all" | "immediate" |
"tasks">(dependenciesKey(dagId), "tasks");
+ const [direction] = useLocalStorage<Direction>(directionKey(dagId), "RIGHT");
const selectedColor = colorMode === "dark" ? selectedDarkColor :
selectedLightColor;
const { data: graphData = { edges: [], nodes: [] } } =
useStructureServiceStructureData(
diff --git a/airflow-core/src/airflow/ui/src/layouts/Details/PanelButtons.tsx
b/airflow-core/src/airflow/ui/src/layouts/Details/PanelButtons.tsx
index 488b1b388b6..cdcaa90f3fc 100644
--- a/airflow-core/src/airflow/ui/src/layouts/Details/PanelButtons.tsx
+++ b/airflow-core/src/airflow/ui/src/layouts/Details/PanelButtons.tsx
@@ -50,6 +50,7 @@ import { StateBadge } from "src/components/StateBadge";
import { Tooltip } from "src/components/ui";
import { type ButtonGroupOption, ButtonGroupToggle } from
"src/components/ui/ButtonGroupToggle";
import { Checkbox } from "src/components/ui/Checkbox";
+import { dependenciesKey, directionKey } from "src/constants/localStorage";
import { dagRunTypeOptions, dagRunStateOptions } from
"src/constants/stateOptions";
import { useContainerWidth } from "src/utils/useContainerWidth";
@@ -126,10 +127,10 @@ export const PanelButtons = ({
const { fitView } = useReactFlow();
const shouldShowToggleButtons = Boolean(runId);
const [dependencies, setDependencies, removeDependencies] =
useLocalStorage<Dependency>(
- `dependencies-${dagId}`,
+ dependenciesKey(dagId),
"tasks",
);
- const [direction, setDirection] =
useLocalStorage<Direction>(`direction-${dagId}`, "RIGHT");
+ const [direction, setDirection] =
useLocalStorage<Direction>(directionKey(dagId), "RIGHT");
const containerRef = useRef<HTMLDivElement>(null);
const containerWidth = useContainerWidth(containerRef);
const handleLimitChange = (event: SelectValueChangeDetails<{ label: string;
value: Array<string> }>) => {
diff --git a/airflow-core/src/airflow/ui/src/layouts/Nav/UserSettingsButton.tsx
b/airflow-core/src/airflow/ui/src/layouts/Nav/UserSettingsButton.tsx
index 02bf017f6eb..2ad032457e7 100644
--- a/airflow-core/src/airflow/ui/src/layouts/Nav/UserSettingsButton.tsx
+++ b/airflow-core/src/airflow/ui/src/layouts/Nav/UserSettingsButton.tsx
@@ -35,6 +35,7 @@ import { useLocalStorage } from "usehooks-ts";
import { useAuthLinksServiceGetCurrentUserInfo } from "openapi/queries";
import { Menu } from "src/components/ui";
+import { DEFAULT_DAG_VIEW_KEY } from "src/constants/localStorage";
import { useColorMode } from "src/context/colorMode/useColorMode";
import type { NavItemResponse } from "src/utils/types";
@@ -77,7 +78,7 @@ export const UserSettingsButton = ({ externalViews }: {
readonly externalViews:
const { onClose: onCloseLogout, onOpen: onOpenLogout, open: isOpenLogout } =
useDisclosure();
const { onClose: onCloseLanguage, onOpen: onOpenLanguage, open:
isOpenLanguage } = useDisclosure();
- const [dagView, setDagView] = useLocalStorage<"graph" |
"grid">("default_dag_view", "grid");
+ const [dagView, setDagView] = useLocalStorage<"graph" |
"grid">(DEFAULT_DAG_VIEW_KEY, "grid");
const theme = selectedTheme ?? COLOR_MODES.SYSTEM;
diff --git a/airflow-core/src/airflow/ui/src/pages/Dag/Calendar/Calendar.tsx
b/airflow-core/src/airflow/ui/src/pages/Dag/Calendar/Calendar.tsx
index faa7b0956ba..41ac7beaba3 100644
--- a/airflow-core/src/airflow/ui/src/pages/Dag/Calendar/Calendar.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/Dag/Calendar/Calendar.tsx
@@ -28,6 +28,7 @@ import { useLocalStorage } from "usehooks-ts";
import { useCalendarServiceGetCalendar } from "openapi/queries";
import { ErrorAlert } from "src/components/ErrorAlert";
import { ButtonGroupToggle } from "src/components/ui/ButtonGroupToggle";
+import { CALENDAR_GRANULARITY_KEY, CALENDAR_VIEW_MODE_KEY } from
"src/constants/localStorage";
import { CalendarLegend } from "./CalendarLegend";
import { DailyCalendarView } from "./DailyCalendarView";
@@ -43,8 +44,11 @@ export const Calendar = () => {
const { dagId = "" } = useParams();
const { t: translate } = useTranslation("dag");
const [selectedDate, setSelectedDate] = useState(dayjs());
- const [granularity, setGranularity] = useLocalStorage<"daily" |
"hourly">("calendar-granularity", "hourly");
- const [viewMode, setViewMode] = useLocalStorage<"failed" |
"total">("calendar-view-mode", "total");
+ const [granularity, setGranularity] = useLocalStorage<"daily" | "hourly">(
+ CALENDAR_GRANULARITY_KEY,
+ "hourly",
+ );
+ const [viewMode, setViewMode] = useLocalStorage<"failed" |
"total">(CALENDAR_VIEW_MODE_KEY, "total");
const currentDate = dayjs();
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 8cebcc4ba0b..043ae4d1b15 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
@@ -33,6 +33,7 @@ import { DurationChart } from "src/components/DurationChart";
import { NeedsReviewButton } from "src/components/NeedsReviewButton";
import TimeRangeSelector from "src/components/TimeRangeSelector";
import { TrendCountButton } from "src/components/TrendCountButton";
+import { dagRunsLimitKey } from "src/constants/localStorage";
import { SearchParamsKeys } from "src/constants/searchParams";
import { useGridRuns } from "src/queries/useGridRuns.ts";
@@ -58,7 +59,7 @@ export const Overview = () => {
state: ["failed"],
});
- const [limit] = useLocalStorage<number>(`dag_runs_limit-${dagId}`, 10);
+ const [limit] = useLocalStorage<number>(dagRunsLimitKey(dagId ?? ""), 10);
const { data: failedRuns, isLoading: isLoadingFailedRuns } =
useDagRunServiceGetDagRuns({
dagId: dagId ?? "",
limit,
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 02c16c15530..92b6a4865e2 100644
--- a/airflow-core/src/airflow/ui/src/pages/DagsList/DagsList.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/DagsList/DagsList.tsx
@@ -44,6 +44,7 @@ import { NeedsReviewBadge } from
"src/components/NeedsReviewBadge";
import { SearchBar } from "src/components/SearchBar";
import { TogglePause } from "src/components/TogglePause";
import { TriggerDAGButton } from "src/components/TriggerDag/TriggerDAGButton";
+import { DAGS_LIST_DISPLAY_KEY } from "src/constants/localStorage";
import { SearchParamsKeys, type SearchParamsKeysType } from
"src/constants/searchParams";
import { DagsLayout } from "src/layouts/DagsLayout";
import { useConfig } from "src/queries/useConfig";
@@ -199,12 +200,10 @@ const cardDef: CardDef<DAGWithLatestDagRunsResponse> = {
},
};
-const DAGS_LIST_DISPLAY = "dags_list_display";
-
export const DagsList = () => {
const { t: translate } = useTranslation();
const [searchParams, setSearchParams] = useSearchParams();
- const [display, setDisplay] = useLocalStorage<"card" |
"table">(DAGS_LIST_DISPLAY, "card");
+ const [display, setDisplay] = useLocalStorage<"card" |
"table">(DAGS_LIST_DISPLAY_KEY, "card");
const dagRunsLimit = display === "card" ? 14 : 1;
const hidePausedDagsByDefault =
Boolean(useConfig("hide_paused_dags_by_default"));
diff --git a/airflow-core/src/airflow/ui/src/pages/TaskInstance/Logs/Logs.tsx
b/airflow-core/src/airflow/ui/src/pages/TaskInstance/Logs/Logs.tsx
index 37df6135463..d5fd32a8306 100644
--- a/airflow-core/src/airflow/ui/src/pages/TaskInstance/Logs/Logs.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/TaskInstance/Logs/Logs.tsx
@@ -26,6 +26,7 @@ import { useLocalStorage } from "usehooks-ts";
import { useTaskInstanceServiceGetMappedTaskInstance } from "openapi/queries";
import { renderStructuredLog } from "src/components/renderStructuredLog";
import { Dialog } from "src/components/ui";
+import { LOG_SHOW_SOURCE_KEY, LOG_SHOW_TIMESTAMP_KEY, LOG_WRAP_KEY } from
"src/constants/localStorage";
import { SearchParamsKeys } from "src/constants/searchParams";
import { useConfig } from "src/queries/useConfig";
import { useLogs } from "src/queries/useLogs";
@@ -75,9 +76,9 @@ export const Logs = () => {
const defaultWrap = Boolean(useConfig("default_wrap"));
- const [wrap, setWrap] = useLocalStorage<boolean>("log_wrap", defaultWrap);
- const [showTimestamp, setShowTimestamp] =
useLocalStorage<boolean>("log_show_timestamp", true);
- const [showSource, setShowSource] =
useLocalStorage<boolean>("log_show_source", false);
+ const [wrap, setWrap] = useLocalStorage<boolean>(LOG_WRAP_KEY, defaultWrap);
+ const [showTimestamp, setShowTimestamp] =
useLocalStorage<boolean>(LOG_SHOW_TIMESTAMP_KEY, true);
+ const [showSource, setShowSource] =
useLocalStorage<boolean>(LOG_SHOW_SOURCE_KEY, false);
const [fullscreen, setFullscreen] = useState(false);
const [expanded, setExpanded] = useState(false);