diff --git a/airflow/ui/src/layouts/Details/DagBreadcrumb.tsx b/airflow/ui/src/layouts/Details/DagBreadcrumb.tsx
index aa906a0b6ce34..4b656764a6510 100644
--- a/airflow/ui/src/layouts/Details/DagBreadcrumb.tsx
+++ b/airflow/ui/src/layouts/Details/DagBreadcrumb.tsx
@@ -19,12 +19,11 @@
import { HStack, Stat } from "@chakra-ui/react";
import type { ReactNode } from "react";
import { LiaSlashSolid } from "react-icons/lia";
-import { Link as RouterLink, useParams, useSearchParams } from "react-router-dom";
+import { Link as RouterLink, useParams } from "react-router-dom";
import {
useDagRunServiceGetDagRun,
useDagServiceGetDagDetails,
- useTaskInstanceServiceGetMappedTaskInstance,
useTaskServiceGetTask,
} from "openapi/queries";
import { StateBadge } from "src/components/StateBadge";
@@ -33,11 +32,7 @@ import { TogglePause } from "src/components/TogglePause";
import { Breadcrumb } from "src/components/ui";
export const DagBreadcrumb = () => {
- const { dagId = "", runId, taskId } = useParams();
-
- const [searchParams] = useSearchParams();
- const mapIndexParam = searchParams.get("map_index");
- const mapIndex = parseInt(mapIndexParam ?? "-1", 10);
+ const { dagId = "", mapIndex = "-1", runId, taskId } = useParams();
const { data: dag } = useDagServiceGetDagDetails({
dagId,
@@ -56,19 +51,6 @@ export const DagBreadcrumb = () => {
const { data: task } = useTaskServiceGetTask({ dagId, taskId }, undefined, { enabled: Boolean(taskId) });
- const { data: taskInstance } = useTaskInstanceServiceGetMappedTaskInstance(
- {
- dagId,
- dagRunId: runId ?? "",
- mapIndex,
- taskId: taskId ?? "",
- },
- undefined,
- {
- enabled: Boolean(runId) && Boolean(taskId),
- },
- );
-
const links: Array<{ label: ReactNode | string; labelExtra?: ReactNode; title?: string; value?: string }> =
[
{ label: "Dags", value: "/dags" },
@@ -99,7 +81,18 @@ export const DagBreadcrumb = () => {
// Add task breadcrumb
if (runId !== undefined && taskId !== undefined) {
- links.push({ label: taskInstance?.task_display_name ?? taskId, title: "Task" });
+ if (task?.is_mapped) {
+ links.push({
+ label: `${task.task_display_name ?? taskId} [ ]`,
+ title: "Task",
+ value: `/dags/${dagId}/runs/${runId}/tasks/${taskId}/mapped`,
+ });
+ } else {
+ links.push({
+ label: task?.task_display_name ?? taskId,
+ title: "Task",
+ });
+ }
}
if (runId === undefined && taskId !== undefined) {
@@ -107,8 +100,8 @@ export const DagBreadcrumb = () => {
links.push({ label: task?.task_display_name ?? taskId, title: "Task" });
}
- if (mapIndexParam !== null) {
- links.push({ label: mapIndexParam, title: "Map Index" });
+ if (mapIndex !== "-1") {
+ links.push({ label: mapIndex, title: "Map Index" });
}
return (
diff --git a/airflow/ui/src/layouts/Details/DetailsLayout.tsx b/airflow/ui/src/layouts/Details/DetailsLayout.tsx
index 0b2178e8f0a3d..e2fa8cf77b512 100644
--- a/airflow/ui/src/layouts/Details/DetailsLayout.tsx
+++ b/airflow/ui/src/layouts/Details/DetailsLayout.tsx
@@ -92,7 +92,7 @@ export const DetailsLayout = ({ children, error, isLoading, tabs }: Props) => {
{children}
-
+
diff --git a/airflow/ui/src/layouts/Details/Graph/TaskLink.tsx b/airflow/ui/src/layouts/Details/Graph/TaskLink.tsx
index dab512d822a84..e933329b02901 100644
--- a/airflow/ui/src/layouts/Details/Graph/TaskLink.tsx
+++ b/airflow/ui/src/layouts/Details/Graph/TaskLink.tsx
@@ -25,13 +25,13 @@ type Props = {
readonly id: string;
} & TaskNameProps;
-export const TaskLink = ({ id, isGroup, ...rest }: Props) => {
+export const TaskLink = ({ id, isGroup, isMapped, ...rest }: Props) => {
const { dagId = "", runId, taskId } = useParams();
const [searchParams] = useSearchParams();
// We don't have a task group details page to link to
if (isGroup) {
- return ;
+ return ;
}
return (
@@ -39,11 +39,11 @@ export const TaskLink = ({ id, isGroup, ...rest }: Props) => {
-
+
);
diff --git a/airflow/ui/src/layouts/Details/Grid/GridTI.tsx b/airflow/ui/src/layouts/Details/Grid/GridTI.tsx
index fc939eb8f2121..85a8342d8ac42 100644
--- a/airflow/ui/src/layouts/Details/Grid/GridTI.tsx
+++ b/airflow/ui/src/layouts/Details/Grid/GridTI.tsx
@@ -27,6 +27,7 @@ import { StateIcon } from "src/components/StateIcon";
type Props = {
readonly dagId: string;
readonly isGroup?: boolean;
+ readonly isMapped?: boolean | null;
readonly label: string;
readonly runId: string;
readonly search: string;
@@ -50,9 +51,9 @@ const onMouseLeave = (event: MouseEvent) => {
});
};
-const Instance = ({ dagId, runId, search, state, taskId }: Props) => (
+const Instance = ({ dagId, isGroup, isMapped, runId, search, state, taskId }: Props) => (
(
transition="background-color 0.2s"
zIndex={1}
>
-
+ {isGroup ? (
(
/>
)}
-
+ ) : (
+
+
+ {state === undefined ? undefined : (
+
+ )}
+
+
+ )}
);
diff --git a/airflow/ui/src/layouts/Details/Grid/TaskInstances.tsx b/airflow/ui/src/layouts/Details/Grid/TaskInstances.tsx
index 4e0929e71823e..7c763999bacff 100644
--- a/airflow/ui/src/layouts/Details/Grid/TaskInstances.tsx
+++ b/airflow/ui/src/layouts/Details/Grid/TaskInstances.tsx
@@ -47,6 +47,7 @@ export const TaskInstances = ({ nodes, runId, taskInstances }: Props) => {
;
};
-export const NavTabs = ({ keepSearch, tabs }: Props) => {
- const [searchParams] = useSearchParams();
-
+export const NavTabs = ({ tabs }: Props) => {
const containerRef = useRef(null);
const containerWidth = useContainerWidth(containerRef);
@@ -42,8 +39,6 @@ export const NavTabs = ({ keepSearch, tabs }: Props) => {
title={label}
to={{
pathname: value,
- // Preserve search params when navigating
- search: keepSearch ? searchParams.toString() : undefined,
}}
>
{({ isActive }) => (
diff --git a/airflow/ui/src/pages/Dashboard/HistoricalMetrics/AssetEvent.tsx b/airflow/ui/src/pages/Dashboard/HistoricalMetrics/AssetEvent.tsx
index 9acc43e8d311e..e5bf931d17382 100644
--- a/airflow/ui/src/pages/Dashboard/HistoricalMetrics/AssetEvent.tsx
+++ b/airflow/ui/src/pages/Dashboard/HistoricalMetrics/AssetEvent.tsx
@@ -58,7 +58,7 @@ export const AssetEvent = ({ event }: { readonly event: AssetEventResponse }) =>
Source:
{source === "" ? (
-1 ? `/mapped/${event.source_map_index}` : ""}`}
>
{event.source_dag_id}
diff --git a/airflow/ui/src/pages/MappedTaskInstance/Header.tsx b/airflow/ui/src/pages/MappedTaskInstance/Header.tsx
new file mode 100644
index 0000000000000..9396802a2d845
--- /dev/null
+++ b/airflow/ui/src/pages/MappedTaskInstance/Header.tsx
@@ -0,0 +1,64 @@
+/*!
+ * 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 } from "@chakra-ui/react";
+import type { ReactNode } from "react";
+import { MdOutlineTask } from "react-icons/md";
+
+import type { GridTaskInstanceSummary } from "openapi/requests/types.gen";
+import { HeaderCard } from "src/components/HeaderCard";
+import Time from "src/components/Time";
+import { getDuration } from "src/utils";
+
+export const Header = ({
+ isRefreshing,
+ taskInstance,
+}: {
+ readonly isRefreshing?: boolean;
+ readonly taskInstance: GridTaskInstanceSummary;
+}) => {
+ const entries: Array<{ label: string; value: number | ReactNode | string }> = [];
+
+ if (taskInstance.child_states !== null) {
+ Object.entries(taskInstance.child_states).forEach(([state, count]) => {
+ if (count > 0) {
+ entries.push({ label: `Total ${state}`, value: count });
+ }
+ });
+ }
+ const stats = [
+ { label: "Task Count", value: taskInstance.task_count },
+ ...entries,
+ { label: "Start", value: },
+ { label: "End", value: },
+ { label: "Duration", value: `${getDuration(taskInstance.start_date, taskInstance.end_date)}s` },
+ ];
+
+ return (
+
+ }
+ isRefreshing={isRefreshing}
+ state={taskInstance.state}
+ stats={stats}
+ subTitle={}
+ title={`${taskInstance.task_id} [ ]`}
+ />
+
+ );
+};
diff --git a/airflow/ui/src/pages/MappedTaskInstance/MappedTaskInstance.tsx b/airflow/ui/src/pages/MappedTaskInstance/MappedTaskInstance.tsx
new file mode 100644
index 0000000000000..fba902cd33be3
--- /dev/null
+++ b/airflow/ui/src/pages/MappedTaskInstance/MappedTaskInstance.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 { ReactFlowProvider } from "@xyflow/react";
+import { MdOutlineTask } from "react-icons/md";
+import { useParams } from "react-router-dom";
+
+import { useDagServiceGetDagDetails, useGridServiceGridData } from "openapi/queries";
+import { DetailsLayout } from "src/layouts/Details/DetailsLayout";
+import { isStatePending, useAutoRefresh } from "src/utils";
+
+import { Header } from "./Header";
+
+const tabs = [{ icon: , label: "Task Instances", value: "" }];
+
+export const MappedTaskInstance = () => {
+ const { dagId = "", runId = "", taskId = "" } = useParams();
+ const refetchInterval = useAutoRefresh({ dagId });
+
+ const {
+ data: dag,
+ error: dagError,
+ isLoading: isDagLoading,
+ } = useDagServiceGetDagDetails({
+ dagId,
+ });
+
+ const { data, error, isLoading } = useGridServiceGridData(
+ {
+ dagId,
+ },
+ undefined,
+ {
+ refetchInterval: (query) =>
+ query.state.data?.dag_runs.some((dr) => isStatePending(dr.state)) ? refetchInterval : false,
+ },
+ );
+
+ const taskInstance = data?.dag_runs
+ .find((dr) => dr.dag_run_id === runId)
+ ?.task_instances.find((ti) => ti.task_id === taskId);
+
+ return (
+
+
+ {taskInstance === undefined ? undefined : (
+
+ )}
+
+
+ );
+};
diff --git a/airflow/ui/src/pages/MappedTaskInstance/index.ts b/airflow/ui/src/pages/MappedTaskInstance/index.ts
new file mode 100644
index 0000000000000..efb943c636b3e
--- /dev/null
+++ b/airflow/ui/src/pages/MappedTaskInstance/index.ts
@@ -0,0 +1,20 @@
+/*!
+ * 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.
+ */
+
+export { MappedTaskInstance } from "./MappedTaskInstance";
diff --git a/airflow/ui/src/pages/TaskInstance/Details.tsx b/airflow/ui/src/pages/TaskInstance/Details.tsx
index 24aa9e31aa9dc..550120a1641e3 100644
--- a/airflow/ui/src/pages/TaskInstance/Details.tsx
+++ b/airflow/ui/src/pages/TaskInstance/Details.tsx
@@ -33,17 +33,15 @@ import { ExtraLinks } from "./ExtraLinks";
import { TriggererInfo } from "./TriggererInfo";
export const Details = () => {
- const { dagId = "", runId = "", taskId = "" } = useParams();
+ const { dagId = "", mapIndex = "-1", runId = "", taskId = "" } = useParams();
const [searchParams, setSearchParams] = useSearchParams();
- const mapIndexParam = searchParams.get("map_index");
const tryNumberParam = searchParams.get("try_number");
- const mapIndex = parseInt(mapIndexParam ?? "-1", 10);
const { data: taskInstance } = useTaskInstanceServiceGetMappedTaskInstance({
dagId,
dagRunId: runId,
- mapIndex,
+ mapIndex: parseInt(mapIndex, 10),
taskId,
});
@@ -64,7 +62,7 @@ export const Details = () => {
{
dagId,
dagRunId: runId,
- mapIndex,
+ mapIndex: parseInt(mapIndex, 10),
taskId,
taskTryNumber: tryNumber ?? 1,
},
diff --git a/airflow/ui/src/pages/TaskInstance/ExtraLinks.tsx b/airflow/ui/src/pages/TaskInstance/ExtraLinks.tsx
index 5990c6fdb5f59..e30c4b92d6e11 100644
--- a/airflow/ui/src/pages/TaskInstance/ExtraLinks.tsx
+++ b/airflow/ui/src/pages/TaskInstance/ExtraLinks.tsx
@@ -17,26 +17,23 @@
* under the License.
*/
import { Box, Button, Heading, HStack } from "@chakra-ui/react";
-import { useParams, useSearchParams } from "react-router-dom";
+import { useParams } from "react-router-dom";
import { useTaskInstanceServiceGetExtraLinks } from "openapi/queries";
export const ExtraLinks = () => {
- const { dagId = "", runId = "", taskId = "" } = useParams();
- const [searchParams] = useSearchParams();
- const mapIndexParam = searchParams.get("map_index");
- const mapIndex = parseInt(mapIndexParam ?? "-1", 10);
+ const { dagId = "", mapIndex = "-1", runId = "", taskId = "" } = useParams();
const { data } = useTaskInstanceServiceGetExtraLinks({
dagId,
dagRunId: runId,
- mapIndex,
+ mapIndex: parseInt(mapIndex, 10),
taskId,
});
return data && Object.keys(data).length > 0 ? (
- Extra Links
+ Extra Links
{Object.entries(data).map(([key, value], _) =>
value === null ? undefined : (
diff --git a/airflow/ui/src/pages/TaskInstance/Logs/Logs.tsx b/airflow/ui/src/pages/TaskInstance/Logs/Logs.tsx
index c062c7f4b2baa..340198a46b242 100644
--- a/airflow/ui/src/pages/TaskInstance/Logs/Logs.tsx
+++ b/airflow/ui/src/pages/TaskInstance/Logs/Logs.tsx
@@ -29,12 +29,10 @@ import { TaskLogContent } from "./TaskLogContent";
import { TaskLogHeader } from "./TaskLogHeader";
export const Logs = () => {
- const { dagId = "", runId = "", taskId = "" } = useParams();
+ const { dagId = "", mapIndex = "-1", runId = "", taskId = "" } = useParams();
const [searchParams, setSearchParams] = useSearchParams();
- const mapIndexParam = searchParams.get("map_index");
const tryNumberParam = searchParams.get("try_number");
- const mapIndex = parseInt(mapIndexParam ?? "-1", 10);
const {
data: taskInstance,
@@ -43,7 +41,7 @@ export const Logs = () => {
} = useTaskInstanceServiceGetMappedTaskInstance({
dagId,
dagRunId: runId,
- mapIndex,
+ mapIndex: parseInt(mapIndex, 10),
taskId,
});
diff --git a/airflow/ui/src/pages/TaskInstance/RenderedTemplates.tsx b/airflow/ui/src/pages/TaskInstance/RenderedTemplates.tsx
index 9d1a4324875fd..d8b6d7bf5a751 100644
--- a/airflow/ui/src/pages/TaskInstance/RenderedTemplates.tsx
+++ b/airflow/ui/src/pages/TaskInstance/RenderedTemplates.tsx
@@ -17,22 +17,18 @@
* under the License.
*/
import { Box, HStack, Table, Code } from "@chakra-ui/react";
-import { useParams, useSearchParams } from "react-router-dom";
+import { useParams } from "react-router-dom";
import { useTaskInstanceServiceGetMappedTaskInstance } from "openapi/queries";
import { ClipboardRoot, ClipboardIconButton } from "src/components/ui";
export const RenderedTemplates = () => {
- const { dagId = "", runId = "", taskId = "" } = useParams();
- const [searchParams] = useSearchParams();
-
- const mapIndexParam = searchParams.get("map_index");
- const mapIndex = parseInt(mapIndexParam ?? "-1", 10);
+ const { dagId = "", mapIndex = "-1", runId = "", taskId = "" } = useParams();
const { data: taskInstance } = useTaskInstanceServiceGetMappedTaskInstance({
dagId,
dagRunId: runId,
- mapIndex,
+ mapIndex: parseInt(mapIndex, 10),
taskId,
});
diff --git a/airflow/ui/src/pages/TaskInstance/TaskInstance.tsx b/airflow/ui/src/pages/TaskInstance/TaskInstance.tsx
index bcda0736dd14a..f6993b87b2731 100644
--- a/airflow/ui/src/pages/TaskInstance/TaskInstance.tsx
+++ b/airflow/ui/src/pages/TaskInstance/TaskInstance.tsx
@@ -20,7 +20,7 @@ import { ReactFlowProvider } from "@xyflow/react";
import { FiCode } from "react-icons/fi";
import { MdDetails, MdOutlineEventNote, MdReorder, MdSyncAlt } from "react-icons/md";
import { PiBracketsCurlyBold } from "react-icons/pi";
-import { useParams, useSearchParams } from "react-router-dom";
+import { useParams } from "react-router-dom";
import { useDagServiceGetDagDetails, useTaskInstanceServiceGetMappedTaskInstance } from "openapi/queries";
import { DetailsLayout } from "src/layouts/Details/DetailsLayout";
@@ -38,11 +38,7 @@ const tabs = [
];
export const TaskInstance = () => {
- const { dagId = "", runId = "", taskId = "" } = useParams();
- const [searchParams] = useSearchParams();
-
- const mapIndexParam = searchParams.get("map_index");
- const mapIndex = parseInt(mapIndexParam ?? "-1", 10);
+ const { dagId = "", mapIndex = "-1", runId = "", taskId = "" } = useParams();
const refetchInterval = useAutoRefresh({ dagId });
@@ -62,7 +58,7 @@ export const TaskInstance = () => {
{
dagId,
dagRunId: runId,
- mapIndex,
+ mapIndex: parseInt(mapIndex, 10),
taskId,
},
undefined,
diff --git a/airflow/ui/src/pages/TaskInstances.tsx b/airflow/ui/src/pages/TaskInstances.tsx
index 20dff17bb2e75..5424b0d3e9e71 100644
--- a/airflow/ui/src/pages/TaskInstances.tsx
+++ b/airflow/ui/src/pages/TaskInstances.tsx
@@ -1,3 +1,5 @@
+/* eslint-disable max-lines */
+
/*!
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
@@ -100,7 +102,16 @@ const taskInstanceColumns = (
},
{
accessorKey: "start_date",
- cell: ({ row: { original } }) => ,
+ cell: ({ row: { original } }) =>
+ Boolean(taskId) && Boolean(runId) ? (
+
+
+
+
+
+ ) : (
+
+ ),
header: "Start Date",
},
{
diff --git a/airflow/ui/src/pages/XCom/XCom.tsx b/airflow/ui/src/pages/XCom/XCom.tsx
index f309d2532da7e..2b10ce704d6cd 100644
--- a/airflow/ui/src/pages/XCom/XCom.tsx
+++ b/airflow/ui/src/pages/XCom/XCom.tsx
@@ -18,7 +18,7 @@
*/
import { Box } from "@chakra-ui/react";
import type { ColumnDef } from "@tanstack/react-table";
-import { useParams, useSearchParams } from "react-router-dom";
+import { useParams } from "react-router-dom";
import { useXcomServiceGetXcomEntries } from "openapi/queries";
import type { XComResponse } from "openapi/requests/types.gen";
@@ -50,10 +50,7 @@ const columns: Array> = [
];
export const XCom = () => {
- const { dagId = "~", runId = "~", taskId = "~" } = useParams();
- const [searchParams] = useSearchParams();
- const mapIndexParam = searchParams.get("map_index");
- const mapIndex = parseInt(mapIndexParam ?? "-1", 10);
+ const { dagId = "~", mapIndex = "-1", runId = "~", taskId = "~" } = useParams();
const { setTableURLState, tableURLState } = useTableURLState();
const { pagination } = tableURLState;
@@ -63,7 +60,7 @@ export const XCom = () => {
dagId,
dagRunId: runId,
limit: pagination.pageSize,
- mapIndex,
+ mapIndex: mapIndex === "-1" ? undefined : parseInt(mapIndex, 10),
offset: pagination.pageIndex * pagination.pageSize,
taskId,
},
diff --git a/airflow/ui/src/router.tsx b/airflow/ui/src/router.tsx
index c33feb6bbefd4..d8e0228a77b82 100644
--- a/airflow/ui/src/router.tsx
+++ b/airflow/ui/src/router.tsx
@@ -33,6 +33,7 @@ import { DagsList } from "src/pages/DagsList";
import { Dashboard } from "src/pages/Dashboard";
import { ErrorPage } from "src/pages/Error";
import { Events } from "src/pages/Events";
+import { MappedTaskInstance } from "src/pages/MappedTaskInstance";
import { Plugins } from "src/pages/Plugins";
import { Pools } from "src/pages/Pools";
import { Providers } from "src/pages/Providers";
@@ -49,6 +50,16 @@ import { XCom } from "src/pages/XCom";
import { queryClient } from "./queryClient";
+const taskInstanceRoutes = [
+ { element: , index: true },
+ { element: , path: "events" },
+ { element: , path: "xcom" },
+ { element: , path: "code" },
+ { element: , path: "details" },
+ { element: , path: "rendered_templates" },
+ { element: , path: "task_instances" },
+];
+
export const routerConfig = [
{
children: [
@@ -126,17 +137,20 @@ export const routerConfig = [
path: "dags/:dagId/runs/:runId",
},
{
- children: [
- { element: , index: true },
- { element: , path: "events" },
- { element: , path: "xcom" },
- { element: , path: "code" },
- { element: , path: "details" },
- { element: , path: "rendered_templates" },
- ],
+ children: taskInstanceRoutes,
element: ,
path: "dags/:dagId/runs/:runId/tasks/:taskId",
},
+ {
+ children: [{ element: , index: true }],
+ element: ,
+ path: "dags/:dagId/runs/:runId/tasks/:taskId/mapped",
+ },
+ {
+ children: taskInstanceRoutes,
+ element: ,
+ path: "dags/:dagId/runs/:runId/tasks/:taskId/mapped/:mapIndex",
+ },
{
children: [
{ element: , index: true },
diff --git a/airflow/ui/src/utils/links.ts b/airflow/ui/src/utils/links.ts
index 18be2bb642bb8..a4b70aa3e47f5 100644
--- a/airflow/ui/src/utils/links.ts
+++ b/airflow/ui/src/utils/links.ts
@@ -19,4 +19,4 @@
import type { TaskInstanceResponse } from "openapi/requests/types.gen";
export const getTaskInstanceLink = (ti: TaskInstanceResponse) =>
- `/dags/${ti.dag_id}/runs/${ti.dag_run_id}/tasks/${ti.task_id}${ti.map_index >= 0 ? `?map_index=${ti.map_index}` : ""}`;
+ `/dags/${ti.dag_id}/runs/${ti.dag_run_id}/tasks/${ti.task_id}${ti.map_index >= 0 ? `/mapped/${ti.map_index}` : ""}`;