diff --git a/airflow-core/src/airflow/ui/src/components/ActionAccordion/ActionAccordion.tsx b/airflow-core/src/airflow/ui/src/components/ActionAccordion/ActionAccordion.tsx index f9c6c42e7063e..a08b9744b51b0 100644 --- a/airflow-core/src/airflow/ui/src/components/ActionAccordion/ActionAccordion.tsx +++ b/airflow-core/src/airflow/ui/src/components/ActionAccordion/ActionAccordion.tsx @@ -18,6 +18,7 @@ */ import { Box, Editable, Text, VStack } from "@chakra-ui/react"; import type { ChangeEvent } from "react"; +import { useMemo } from "react"; import { useTranslation } from "react-i18next"; import type { DAGRunResponse, TaskInstanceCollectionResponse } from "openapi/requests/types.gen"; @@ -39,6 +40,8 @@ const ActionAccordion = ({ affectedTasks, note, setNote }: Props) => { const showTaskSection = affectedTasks !== undefined; const { t: translate } = useTranslation(); + const columns = useMemo(() => getColumns(translate), [translate]); + return ( { void; readonly taskInstance?: TaskInstanceResponse; readonly withText?: boolean; }; @@ -38,17 +40,25 @@ type Props = { const ClearTaskInstanceButton = ({ groupTaskInstance, isHotkeyEnabled = false, + onOpen, taskInstance, withText = true, }: Props) => { - const { onClose, onOpen, open } = useDisclosure(); + const { onClose, onOpen: onOpenInternal, open } = useDisclosure(); const { t: translate } = useTranslation(); const isGroup = groupTaskInstance && !taskInstance; + const useInternalDialog = !Boolean(onOpen); + + const selectedInstance = taskInstance ?? groupTaskInstance; useHotkeys( "shift+c", () => { - onOpen(); + if (onOpen && selectedInstance) { + onOpen(selectedInstance); + } else { + onOpenInternal(); + } }, { enabled: isHotkeyEnabled }, ); @@ -66,18 +76,18 @@ const ClearTaskInstanceButton = ({ type: translate("taskInstance_one"), })} icon={} - onClick={onOpen} + onClick={() => (onOpen && selectedInstance ? onOpen(selectedInstance) : onOpenInternal())} text={translate("dags:runAndTaskActions.clear.button", { type: translate(isGroup ? "taskGroup" : "taskInstance_one"), })} withText={withText} /> - {open && isGroup ? ( + {useInternalDialog && open && isGroup ? ( ) : undefined} - {open && !isGroup && taskInstance ? ( + {useInternalDialog && open && !isGroup && taskInstance ? ( ) : undefined} diff --git a/airflow-core/src/airflow/ui/src/pages/AssetsList/AssetsList.tsx b/airflow-core/src/airflow/ui/src/pages/AssetsList/AssetsList.tsx index 9aa92b6ce0bd9..1a7c94d64277b 100644 --- a/airflow-core/src/airflow/ui/src/pages/AssetsList/AssetsList.tsx +++ b/airflow-core/src/airflow/ui/src/pages/AssetsList/AssetsList.tsx @@ -18,6 +18,7 @@ */ import { Box, Heading, Link, VStack } from "@chakra-ui/react"; import type { ColumnDef } from "@tanstack/react-table"; +import { useMemo } from "react"; import { useTranslation } from "react-i18next"; import { useSearchParams, Link as RouterLink } from "react-router-dom"; @@ -111,6 +112,8 @@ export const AssetsList = () => { orderBy, }); + const columns = useMemo(() => createColumns(translate), [translate]); + const handleSearchChange = (value: string) => { setTableURLState({ pagination: { ...pagination, pageIndex: 0 }, @@ -140,7 +143,7 @@ export const AssetsList = () => { } initialState={tableURLState} diff --git a/airflow-core/src/airflow/ui/src/pages/Dag/Backfills/Backfills.tsx b/airflow-core/src/airflow/ui/src/pages/Dag/Backfills/Backfills.tsx index 62bfdaf4b3247..983daf46c4f2f 100644 --- a/airflow-core/src/airflow/ui/src/pages/Dag/Backfills/Backfills.tsx +++ b/airflow-core/src/airflow/ui/src/pages/Dag/Backfills/Backfills.tsx @@ -18,6 +18,7 @@ */ import { Box, Heading, Text } from "@chakra-ui/react"; import type { ColumnDef } from "@tanstack/react-table"; +import { useMemo } from "react"; import { useTranslation } from "react-i18next"; import { useParams } from "react-router-dom"; @@ -117,6 +118,8 @@ export const Backfills = () => { offset: pagination.pageIndex * pagination.pageSize, }); + const columns = useMemo(() => getColumns(translate), [translate]); + return ( @@ -124,7 +127,7 @@ export const Backfills = () => { {translate("backfill", { count: data ? data.total_entries : 0 })} { }, [pagination, searchParams, setSearchParams, setTableURLState, sorting], ); + const columns = useMemo(() => runColumns(translate, dagId), [translate, dagId]); return ( <> @@ -378,7 +379,7 @@ export const DagRuns = () => { } initialState={tableURLState} diff --git a/airflow-core/src/airflow/ui/src/pages/Events/Events.tsx b/airflow-core/src/airflow/ui/src/pages/Events/Events.tsx index 2a3bbefbb3a74..e94bf74bdb254 100644 --- a/airflow-core/src/airflow/ui/src/pages/Events/Events.tsx +++ b/airflow-core/src/airflow/ui/src/pages/Events/Events.tsx @@ -19,6 +19,7 @@ import { Code, Flex, Heading, useDisclosure, VStack } from "@chakra-ui/react"; import type { ColumnDef } from "@tanstack/react-table"; import dayjs from "dayjs"; +import { useMemo } from "react"; import { useTranslation } from "react-i18next"; import { useParams, useSearchParams } from "react-router-dom"; @@ -207,6 +208,11 @@ export const Events = () => { undefined, ); + const columns = useMemo( + () => eventsColumn({ dagId, open, runId, taskId }, translate), + [dagId, open, runId, taskId, translate], + ); + return ( {dagId === undefined && runId === undefined && taskId === undefined ? ( @@ -224,7 +230,7 @@ export const Events = () => { { setSearchParams(searchParams); }, [pagination, searchParams, setSearchParams, setTableURLState, sorting]); + const columns = useMemo( + () => + taskInstanceColumns({ + dagId, + runId, + taskId, + translate, + }), + [dagId, runId, taskId, translate], + ); + return ( {!Boolean(dagId) && !Boolean(runId) && !Boolean(taskId) ? ( @@ -188,12 +199,7 @@ export const HITLTaskInstances = () => { ) : undefined} } initialState={tableURLState} diff --git a/airflow-core/src/airflow/ui/src/pages/TaskInstance/Header.tsx b/airflow-core/src/airflow/ui/src/pages/TaskInstance/Header.tsx index 0698baeaddc4c..2238a33029b3d 100644 --- a/airflow-core/src/airflow/ui/src/pages/TaskInstance/Header.tsx +++ b/airflow-core/src/airflow/ui/src/pages/TaskInstance/Header.tsx @@ -23,6 +23,7 @@ import { MdOutlineTask } from "react-icons/md"; import type { TaskInstanceResponse } from "openapi/requests/types.gen"; import { ClearTaskInstanceButton } from "src/components/Clear"; +import ClearTaskInstanceDialog from "src/components/Clear/TaskInstance/ClearTaskInstanceDialog"; import { DagVersion } from "src/components/DagVersion"; import EditableMarkdownButton from "src/components/EditableMarkdownButton"; import { HeaderCard } from "src/components/HeaderCard"; @@ -93,6 +94,9 @@ export const Header = ({ setNote(taskInstance.note ?? ""); }; + // Stable dialog state at header/page level + const [clearOpen, setClearOpen] = useState(false); + return ( setClearOpen(true)} taskInstance={taskInstance} withText={containerWidth > 700} /> @@ -127,6 +132,13 @@ export const Header = ({ stats={stats} title={`${taskInstance.task_display_name}${taskInstance.map_index > -1 ? ` [${taskInstance.rendered_map_index ?? taskInstance.map_index}]` : ""}`} /> + {clearOpen ? ( + setClearOpen(false)} + open={clearOpen} + taskInstance={taskInstance} + /> + ) : undefined} ); }; diff --git a/airflow-core/src/airflow/ui/src/pages/TaskInstances/TaskInstances.tsx b/airflow-core/src/airflow/ui/src/pages/TaskInstances/TaskInstances.tsx index 446c490322a67..c51430b993575 100644 --- a/airflow-core/src/airflow/ui/src/pages/TaskInstances/TaskInstances.tsx +++ b/airflow-core/src/airflow/ui/src/pages/TaskInstances/TaskInstances.tsx @@ -21,7 +21,7 @@ import { Flex, Link } from "@chakra-ui/react"; import type { ColumnDef } from "@tanstack/react-table"; import type { TFunction } from "i18next"; -import { useState } from "react"; +import { useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; import { Link as RouterLink, useParams, useSearchParams } from "react-router-dom"; @@ -261,6 +261,17 @@ export const TaskInstances = () => { }, ); + const columns = useMemo( + () => + taskInstanceColumns({ + dagId, + runId, + taskId: Boolean(groupId) ? undefined : taskId, + translate, + }), + [dagId, runId, groupId, taskId, translate], + ); + return ( <> { taskDisplayNamePattern={taskDisplayNamePattern} /> } initialState={tableURLState} diff --git a/airflow-core/src/airflow/ui/src/pages/XCom/XCom.tsx b/airflow-core/src/airflow/ui/src/pages/XCom/XCom.tsx index 9a1fc1a22a15b..fc0e8045b3f5e 100644 --- a/airflow-core/src/airflow/ui/src/pages/XCom/XCom.tsx +++ b/airflow-core/src/airflow/ui/src/pages/XCom/XCom.tsx @@ -18,6 +18,7 @@ */ import { Box, Heading, Link, Flex, useDisclosure } from "@chakra-ui/react"; import type { ColumnDef } from "@tanstack/react-table"; +import { useMemo } from "react"; import { useTranslation } from "react-i18next"; import { Link as RouterLink, useParams, useSearchParams } from "react-router-dom"; @@ -161,6 +162,8 @@ export const XCom = () => { const { data, error, isFetching, isLoading } = useXcomServiceGetXcomEntries(apiParams, undefined); + const memoizedColumns = useMemo(() => columns(translate, open), [translate, open]); + return ( {dagId === "~" && runId === "~" && taskId === "~" ? ( @@ -179,7 +182,7 @@ export const XCom = () => {