diff --git a/airflow-core/src/airflow/ui/src/hooks/navigation/useNavigation.ts b/airflow-core/src/airflow/ui/src/hooks/navigation/useNavigation.ts index f8a63bc29eea2..0c831ecb3094a 100644 --- a/airflow-core/src/airflow/ui/src/hooks/navigation/useNavigation.ts +++ b/airflow-core/src/airflow/ui/src/hooks/navigation/useNavigation.ts @@ -21,7 +21,7 @@ import { useLocation, useNavigate, useParams } from "react-router-dom"; import type { GridRunsResponse } from "openapi/requests"; import type { GridTask } from "src/layouts/Details/Grid/utils"; -import { getTaskInstanceAdditionalPath } from "src/utils/links"; +import { buildTaskInstanceUrl } from "src/utils/links"; import type { NavigationDirection, @@ -72,7 +72,6 @@ const buildPath = (params: { }): string => { const { dagId, mapIndex = "-1", mode, pathname, run, task } = params; const groupPath = task.isGroup ? "group/" : ""; - const additionalPath = getTaskInstanceAdditionalPath(pathname); switch (mode) { case "run": @@ -80,16 +79,15 @@ const buildPath = (params: { case "task": return `/dags/${dagId}/tasks/${groupPath}${task.id}`; case "TI": - if (task.is_mapped ?? false) { - if (mapIndex !== "-1") { - // For mapped tasks with specific map index, we need to construct the path manually - return `/dags/${dagId}/runs/${run.run_id}/tasks/${groupPath}${task.id}/mapped/${mapIndex}${additionalPath}`; - } - - return `/dags/${dagId}/runs/${run.run_id}/tasks/${groupPath}${task.id}/mapped${additionalPath}`; - } - - return `/dags/${dagId}/runs/${run.run_id}/tasks/${groupPath}${task.id}${additionalPath}`; + return buildTaskInstanceUrl({ + currentPathname: pathname, + dagId, + isGroup: task.isGroup, + isMapped: task.is_mapped ?? false, + mapIndex, + runId: run.run_id, + taskId: task.id, + }); default: return `/dags/${dagId}`; } diff --git a/airflow-core/src/airflow/ui/src/utils/links.test.ts b/airflow-core/src/airflow/ui/src/utils/links.test.ts index 2f4720897eeed..47dd032ebc519 100644 --- a/airflow-core/src/airflow/ui/src/utils/links.test.ts +++ b/airflow-core/src/airflow/ui/src/utils/links.test.ts @@ -232,7 +232,18 @@ describe("buildTaskInstanceUrl", () => { }), ).toBe("/dags/my_dag/runs/run_123/tasks/mapped_task/mapped"); - // Complex: group + mapped + sub-routes + // Groups should never preserve tabs (only have "Task Instances" tab) + expect( + buildTaskInstanceUrl({ + currentPathname: "/dags/old/runs/old/tasks/old_task/rendered_templates", + dagId: "new_dag", + isGroup: true, + runId: "new_run", + taskId: "new_group", + }), + ).toBe("/dags/new_dag/runs/new_run/tasks/group/new_group"); + + // Groups should never preserve tabs even for mapped groups expect( buildTaskInstanceUrl({ currentPathname: "/dags/old/runs/old/tasks/group/old_group/events", @@ -243,6 +254,6 @@ describe("buildTaskInstanceUrl", () => { runId: "new_run", taskId: "new_group", }), - ).toBe("/dags/new_dag/runs/new_run/tasks/group/new_group/mapped/3/events"); + ).toBe("/dags/new_dag/runs/new_run/tasks/group/new_group/mapped/3"); }); }); diff --git a/airflow-core/src/airflow/ui/src/utils/links.ts b/airflow-core/src/airflow/ui/src/utils/links.ts index 80041700860f8..3beafb06afea1 100644 --- a/airflow-core/src/airflow/ui/src/utils/links.ts +++ b/airflow-core/src/airflow/ui/src/utils/links.ts @@ -84,7 +84,8 @@ export const buildTaskInstanceUrl = (params: { }): string => { const { currentPathname, dagId, isGroup = false, isMapped = false, mapIndex, runId, taskId } = params; const groupPath = isGroup ? "group/" : ""; - const additionalPath = getTaskInstanceAdditionalPath(currentPathname); + // Task groups only have "Task Instances" tab, so never preserve tabs for groups + const additionalPath = isGroup ? "" : getTaskInstanceAdditionalPath(currentPathname); let basePath = `/dags/${dagId}/runs/${runId}/tasks/${groupPath}${taskId}`;