diff --git a/airflow/ui/package.json b/airflow/ui/package.json
index 6a4dfaec8312e..728babb74f6a3 100644
--- a/airflow/ui/package.json
+++ b/airflow/ui/package.json
@@ -37,7 +37,7 @@
"react-chartjs-2": "^5.2.0",
"react-dom": "^18.3.1",
"react-hook-form": "^7.20.0",
- "react-icons": "^5.3.0",
+ "react-icons": "^5.4.0",
"react-markdown": "^9.0.1",
"react-router-dom": "^6.26.2",
"react-syntax-highlighter": "^15.5.6",
diff --git a/airflow/ui/pnpm-lock.yaml b/airflow/ui/pnpm-lock.yaml
index 524a88ac9120e..afb2b276b4086 100644
--- a/airflow/ui/pnpm-lock.yaml
+++ b/airflow/ui/pnpm-lock.yaml
@@ -72,8 +72,8 @@ importers:
specifier: ^7.20.0
version: 7.53.1(react@18.3.1)
react-icons:
- specifier: ^5.3.0
- version: 5.3.0(react@18.3.1)
+ specifier: ^5.4.0
+ version: 5.4.0(react@18.3.1)
react-markdown:
specifier: ^9.0.1
version: 9.0.1(@types/react@18.3.5)(react@18.3.1)
@@ -3195,8 +3195,8 @@ packages:
peerDependencies:
react: ^16.8.0 || ^17 || ^18 || ^19
- react-icons@5.3.0:
- resolution: {integrity: sha512-DnUk8aFbTyQPSkCfF8dbX6kQjXA9DktMeJqfjrg6cK9vwQVMxmcA3BfP4QoiztVmEHtwlTgLFsPuH2NskKT6eg==}
+ react-icons@5.4.0:
+ resolution: {integrity: sha512-7eltJxgVt7X64oHh6wSWNwwbKTCtMfK35hcjvJS0yxEAhPM8oUKdS3+kqaW1vicIltw+kR2unHaa12S9pPALoQ==}
peerDependencies:
react: '*'
@@ -7769,7 +7769,7 @@ snapshots:
dependencies:
react: 18.3.1
- react-icons@5.3.0(react@18.3.1):
+ react-icons@5.4.0(react@18.3.1):
dependencies:
react: 18.3.1
diff --git a/airflow/ui/src/components/Stat.tsx b/airflow/ui/src/components/Stat.tsx
new file mode 100644
index 0000000000000..7f39a092ea2fc
--- /dev/null
+++ b/airflow/ui/src/components/Stat.tsx
@@ -0,0 +1,33 @@
+/*!
+ * 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 { Heading, VStack } from "@chakra-ui/react";
+import type { PropsWithChildren } from "react";
+
+type Props = {
+ readonly label: string;
+} & PropsWithChildren;
+
+export const Stat = ({ children, label }: Props) => (
+
+
+ {label}
+
+ {children}
+
+);
diff --git a/airflow/ui/src/components/ui/Breadcrumb/Root.tsx b/airflow/ui/src/components/ui/Breadcrumb/Root.tsx
new file mode 100644
index 0000000000000..3f8af3d498433
--- /dev/null
+++ b/airflow/ui/src/components/ui/Breadcrumb/Root.tsx
@@ -0,0 +1,55 @@
+/*!
+ * 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 { Breadcrumb, type SystemStyleObject } from "@chakra-ui/react";
+import React from "react";
+
+export type BreadcrumbRootProps = {
+ separator?: React.ReactNode;
+ separatorGap?: SystemStyleObject["gap"];
+} & Breadcrumb.RootProps;
+
+export const Root = React.forwardRef(
+ (props, ref) => {
+ const { children, separator, separatorGap, ...rest } = props;
+
+ const validChildren = React.Children.toArray(children).filter(
+ React.isValidElement,
+ );
+
+ return (
+
+
+ {validChildren.map((child, index) => {
+ const last = index === validChildren.length - 1;
+
+ return (
+ // eslint-disable-next-line react/no-array-index-key
+
+ {child}
+ {!last && (
+ {separator}
+ )}
+
+ );
+ })}
+
+
+ );
+ },
+);
diff --git a/airflow/ui/src/components/ui/Breadcrumb/index.ts b/airflow/ui/src/components/ui/Breadcrumb/index.ts
new file mode 100644
index 0000000000000..fc812dea64a86
--- /dev/null
+++ b/airflow/ui/src/components/ui/Breadcrumb/index.ts
@@ -0,0 +1,26 @@
+/*!
+ * 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 { Breadcrumb as ChakraBreadcrumb } from "@chakra-ui/react";
+
+import { Root } from "./Root";
+
+export const Breadcrumb = {
+ ...ChakraBreadcrumb,
+ Root,
+};
diff --git a/airflow/ui/src/components/ui/Status.tsx b/airflow/ui/src/components/ui/Status.tsx
index 7c408b931d378..f614f6a0def9e 100644
--- a/airflow/ui/src/components/ui/Status.tsx
+++ b/airflow/ui/src/components/ui/Status.tsx
@@ -28,12 +28,13 @@ import { stateColor } from "src/utils/stateColor";
type StatusValue = DagRunState | TaskInstanceState;
export type StatusProps = {
- state?: StatusValue;
+ state: StatusValue | null;
} & ChakraStatus.RootProps;
export const Status = React.forwardRef(
({ children, state, ...rest }, ref) => {
- const colorPalette = state === undefined ? "info" : stateColor[state];
+ // "null" is actually a string on stateColor
+ const colorPalette = stateColor[state ?? "null"];
return (
diff --git a/airflow/ui/src/components/ui/index.ts b/airflow/ui/src/components/ui/index.ts
index f88340b42d79b..add5c4b355038 100644
--- a/airflow/ui/src/components/ui/index.ts
+++ b/airflow/ui/src/components/ui/index.ts
@@ -34,3 +34,4 @@ export * from "./Accordion";
export * from "./Status";
export * from "./Button";
export * from "./Toaster";
+export * from "./Breadcrumb";
diff --git a/airflow/ui/src/layouts/Details/DagVizModal.tsx b/airflow/ui/src/layouts/Details/DagVizModal.tsx
new file mode 100644
index 0000000000000..36b5d63d65993
--- /dev/null
+++ b/airflow/ui/src/layouts/Details/DagVizModal.tsx
@@ -0,0 +1,105 @@
+/*!
+ * 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 { Button, Heading, HStack } from "@chakra-ui/react";
+import { FaChartGantt } from "react-icons/fa6";
+import { FiGrid } from "react-icons/fi";
+import { Link as RouterLink, useSearchParams } from "react-router-dom";
+
+import type { DAGResponse } from "openapi/requests/types.gen";
+import { DagIcon } from "src/assets/DagIcon";
+import { Dialog } from "src/components/ui";
+import { capitalize } from "src/utils";
+
+import { Gantt } from "./Gantt";
+import { Graph } from "./Graph";
+import { Grid } from "./Grid";
+
+type TriggerDAGModalProps = {
+ dagDisplayName?: DAGResponse["dag_display_name"];
+ dagId?: DAGResponse["dag_id"];
+ onClose: () => void;
+ open: boolean;
+};
+
+const visualizationOptions = [
+ {
+ component: ,
+ icon: ,
+ value: "gantt",
+ },
+ {
+ component: ,
+ icon: ,
+ value: "graph",
+ },
+ { component: , icon: , value: "grid" },
+];
+
+export const DagVizModal: React.FC = ({
+ dagDisplayName,
+ dagId,
+ onClose,
+ open,
+}) => {
+ const [searchParams] = useSearchParams();
+
+ const activeViz = searchParams.get("modal") ?? "graph";
+ const params = new URLSearchParams(searchParams);
+
+ params.delete("modal");
+
+ return (
+
+
+
+
+
+ {dagDisplayName ?? dagId}
+
+ {visualizationOptions.map(({ icon, value }) => (
+
+
+
+ ))}
+
+
+
+
+ {dagId === undefined
+ ? undefined
+ : visualizationOptions.find((viz) => viz.value === activeViz)
+ ?.component}
+
+
+
+ );
+};
diff --git a/airflow/ui/src/pages/DagsList/Dag/Dag.tsx b/airflow/ui/src/layouts/Details/DetailsLayout.tsx
similarity index 55%
rename from airflow/ui/src/pages/DagsList/Dag/Dag.tsx
rename to airflow/ui/src/layouts/Details/DetailsLayout.tsx
index 2a8c0a42159b2..1302bd2dacc00 100644
--- a/airflow/ui/src/pages/DagsList/Dag/Dag.tsx
+++ b/airflow/ui/src/layouts/Details/DetailsLayout.tsx
@@ -17,62 +17,70 @@
* under the License.
*/
import { Box, Button } from "@chakra-ui/react";
+import type { PropsWithChildren } from "react";
import { FiChevronsLeft } from "react-icons/fi";
-import { Outlet, Link as RouterLink, useParams } from "react-router-dom";
-
import {
- useDagServiceGetDagDetails,
- useDagsServiceRecentDagRuns,
-} from "openapi/queries";
+ Outlet,
+ Link as RouterLink,
+ useParams,
+ useSearchParams,
+} from "react-router-dom";
+
+import type { DAGResponse } from "openapi/requests/types.gen";
import { ErrorAlert } from "src/components/ErrorAlert";
import { ProgressBar } from "src/components/ui";
import { Toaster } from "src/components/ui";
import { OpenGroupsProvider } from "src/context/openGroups";
-import { Header } from "./Header";
-import { DagTabs } from "./Tabs";
+import { DagVizModal } from "./DagVizModal";
+import { NavTabs } from "./NavTabs";
+
+type Props = {
+ readonly dag?: DAGResponse;
+ readonly error?: unknown;
+ readonly isLoading?: boolean;
+ readonly tabs: Array<{ label: string; value: string }>;
+} & PropsWithChildren;
-export const Dag = () => {
- const { dagId } = useParams();
+export const DetailsLayout = ({
+ children,
+ dag,
+ error,
+ isLoading,
+ tabs,
+}: Props) => {
+ const { dagId = "" } = useParams();
- const {
- data: dag,
- error,
- isLoading,
- } = useDagServiceGetDagDetails({
- dagId: dagId ?? "",
- });
+ const [searchParams, setSearchParams] = useSearchParams();
- // TODO: replace with with a list dag runs by dag id request
- const {
- data: runsData,
- error: runsError,
- isLoading: isLoadingRuns,
- } = useDagsServiceRecentDagRuns({ dagIdPattern: dagId ?? "" }, undefined, {
- enabled: Boolean(dagId),
- });
+ const modal = searchParams.get("modal");
- const runs =
- runsData?.dags.find((dagWithRuns) => dagWithRuns.dag_id === dagId)
- ?.latest_dag_runs ?? [];
+ const isModalOpen = modal !== null;
+ const onClose = () => {
+ searchParams.delete("modal");
+ setSearchParams(searchParams);
+ };
return (
-
+
-
-
-
-
+ {children}
+
+
+
+
-
diff --git a/airflow/ui/src/layouts/Details/Gantt/Gantt.tsx b/airflow/ui/src/layouts/Details/Gantt/Gantt.tsx
new file mode 100644
index 0000000000000..90530c4b7b026
--- /dev/null
+++ b/airflow/ui/src/layouts/Details/Gantt/Gantt.tsx
@@ -0,0 +1,21 @@
+/*!
+ * 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";
+
+export const Gantt = () => gantt;
diff --git a/airflow/ui/src/layouts/Details/Gantt/index.ts b/airflow/ui/src/layouts/Details/Gantt/index.ts
new file mode 100644
index 0000000000000..24f6dabe4cfe7
--- /dev/null
+++ b/airflow/ui/src/layouts/Details/Gantt/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 * from "./Gantt";
diff --git a/airflow/ui/src/pages/DagsList/Dag/Graph/Edge.tsx b/airflow/ui/src/layouts/Details/Graph/Edge.tsx
similarity index 100%
rename from airflow/ui/src/pages/DagsList/Dag/Graph/Edge.tsx
rename to airflow/ui/src/layouts/Details/Graph/Edge.tsx
diff --git a/airflow/ui/src/pages/DagsList/Dag/Graph/Graph.tsx b/airflow/ui/src/layouts/Details/Graph/Graph.tsx
similarity index 94%
rename from airflow/ui/src/pages/DagsList/Dag/Graph/Graph.tsx
rename to airflow/ui/src/layouts/Details/Graph/Graph.tsx
index 69ee51cc89217..f7dbc6b2d3016 100644
--- a/airflow/ui/src/pages/DagsList/Dag/Graph/Graph.tsx
+++ b/airflow/ui/src/layouts/Details/Graph/Graph.tsx
@@ -19,9 +19,9 @@
import { Flex } from "@chakra-ui/react";
import { ReactFlow, Controls, Background, MiniMap } from "@xyflow/react";
import "@xyflow/react/dist/style.css";
+import { useParams } from "react-router-dom";
import { useStructureServiceStructureData } from "openapi/queries";
-import type { DAGResponse } from "openapi/requests/types.gen";
import { useColorMode } from "src/context/colorMode";
import { useOpenGroups } from "src/context/openGroups";
@@ -36,8 +36,9 @@ const nodeTypes = {
};
const edgeTypes = { custom: Edge };
-export const Graph = ({ dagId }: { readonly dagId: DAGResponse["dag_id"] }) => {
+export const Graph = () => {
const { colorMode } = useColorMode();
+ const { dagId = "" } = useParams();
const { openGroupIds } = useOpenGroups();
diff --git a/airflow/ui/src/pages/DagsList/Dag/Graph/JoinNode.tsx b/airflow/ui/src/layouts/Details/Graph/JoinNode.tsx
similarity index 100%
rename from airflow/ui/src/pages/DagsList/Dag/Graph/JoinNode.tsx
rename to airflow/ui/src/layouts/Details/Graph/JoinNode.tsx
diff --git a/airflow/ui/src/pages/DagsList/Dag/Graph/NodeWrapper.tsx b/airflow/ui/src/layouts/Details/Graph/NodeWrapper.tsx
similarity index 100%
rename from airflow/ui/src/pages/DagsList/Dag/Graph/NodeWrapper.tsx
rename to airflow/ui/src/layouts/Details/Graph/NodeWrapper.tsx
diff --git a/airflow/ui/src/pages/DagsList/Dag/Graph/TaskName.tsx b/airflow/ui/src/layouts/Details/Graph/TaskName.tsx
similarity index 100%
rename from airflow/ui/src/pages/DagsList/Dag/Graph/TaskName.tsx
rename to airflow/ui/src/layouts/Details/Graph/TaskName.tsx
diff --git a/airflow/ui/src/pages/DagsList/Dag/Graph/TaskNode.tsx b/airflow/ui/src/layouts/Details/Graph/TaskNode.tsx
similarity index 100%
rename from airflow/ui/src/pages/DagsList/Dag/Graph/TaskNode.tsx
rename to airflow/ui/src/layouts/Details/Graph/TaskNode.tsx
diff --git a/airflow/ui/src/pages/DagsList/Dag/Graph/index.ts b/airflow/ui/src/layouts/Details/Graph/index.ts
similarity index 100%
rename from airflow/ui/src/pages/DagsList/Dag/Graph/index.ts
rename to airflow/ui/src/layouts/Details/Graph/index.ts
diff --git a/airflow/ui/src/pages/DagsList/Dag/Graph/reactflowUtils.ts b/airflow/ui/src/layouts/Details/Graph/reactflowUtils.ts
similarity index 100%
rename from airflow/ui/src/pages/DagsList/Dag/Graph/reactflowUtils.ts
rename to airflow/ui/src/layouts/Details/Graph/reactflowUtils.ts
diff --git a/airflow/ui/src/pages/DagsList/Dag/Graph/useGraphLayout.ts b/airflow/ui/src/layouts/Details/Graph/useGraphLayout.ts
similarity index 100%
rename from airflow/ui/src/pages/DagsList/Dag/Graph/useGraphLayout.ts
rename to airflow/ui/src/layouts/Details/Graph/useGraphLayout.ts
diff --git a/airflow/ui/src/layouts/Details/Grid/Grid.tsx b/airflow/ui/src/layouts/Details/Grid/Grid.tsx
new file mode 100644
index 0000000000000..8717fef1d4340
--- /dev/null
+++ b/airflow/ui/src/layouts/Details/Grid/Grid.tsx
@@ -0,0 +1,21 @@
+/*!
+ * 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";
+
+export const Grid = () => grid;
diff --git a/airflow/ui/src/layouts/Details/Grid/index.ts b/airflow/ui/src/layouts/Details/Grid/index.ts
new file mode 100644
index 0000000000000..dccae200d1c23
--- /dev/null
+++ b/airflow/ui/src/layouts/Details/Grid/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 * from "./Grid";
diff --git a/airflow/ui/src/layouts/Details/NavTabs.tsx b/airflow/ui/src/layouts/Details/NavTabs.tsx
new file mode 100644
index 0000000000000..0d4da41b2c8a4
--- /dev/null
+++ b/airflow/ui/src/layouts/Details/NavTabs.tsx
@@ -0,0 +1,90 @@
+/*!
+ * 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 { Button, Center, Flex } from "@chakra-ui/react";
+import { FaChartGantt } from "react-icons/fa6";
+import { FiGrid } from "react-icons/fi";
+import { NavLink, Link as RouterLink, useSearchParams } from "react-router-dom";
+
+import { DagIcon } from "src/assets/DagIcon";
+
+type Props = {
+ readonly tabs: Array<{ label: string; value: string }>;
+};
+
+export const NavTabs = ({ tabs }: Props) => {
+ const [searchParams] = useSearchParams();
+
+ return (
+
+
+ {tabs.map(({ label, value }) => (
+
+ {({ isActive }) => (
+
+ {label}
+
+ )}
+
+ ))}
+
+
+
+
+
+
+
+ );
+};
diff --git a/airflow/ui/src/pages/DagsList/Dag/Code/Code.tsx b/airflow/ui/src/pages/Dag/Code/Code.tsx
similarity index 100%
rename from airflow/ui/src/pages/DagsList/Dag/Code/Code.tsx
rename to airflow/ui/src/pages/Dag/Code/Code.tsx
diff --git a/airflow/ui/src/pages/DagsList/Dag/Code/index.ts b/airflow/ui/src/pages/Dag/Code/index.ts
similarity index 100%
rename from airflow/ui/src/pages/DagsList/Dag/Code/index.ts
rename to airflow/ui/src/pages/Dag/Code/index.ts
diff --git a/airflow/ui/src/pages/Dag/Dag.tsx b/airflow/ui/src/pages/Dag/Dag.tsx
new file mode 100644
index 0000000000000..87283b077d1e6
--- /dev/null
+++ b/airflow/ui/src/pages/Dag/Dag.tsx
@@ -0,0 +1,71 @@
+/*!
+ * 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 { useParams } from "react-router-dom";
+
+import {
+ useDagServiceGetDagDetails,
+ useDagsServiceRecentDagRuns,
+} from "openapi/queries";
+import { DetailsLayout } from "src/layouts/Details/DetailsLayout";
+
+import { Header } from "./Header";
+
+const tabs = [
+ { label: "Overview", value: "" },
+ { label: "Runs", value: "runs" },
+ { label: "Tasks", value: "tasks" },
+ { label: "Events", value: "events" },
+ { label: "Code", value: "code" },
+];
+
+export const Dag = () => {
+ const { dagId = "" } = useParams();
+
+ const {
+ data: dag,
+ error,
+ isLoading,
+ } = useDagServiceGetDagDetails({
+ dagId,
+ });
+
+ // TODO: replace with with a list dag runs by dag id request
+ const {
+ data: runsData,
+ error: runsError,
+ isLoading: isLoadingRuns,
+ } = useDagsServiceRecentDagRuns({ dagIdPattern: dagId }, undefined, {
+ enabled: Boolean(dagId),
+ });
+
+ const runs =
+ runsData?.dags.find((dagWithRuns) => dagWithRuns.dag_id === dagId)
+ ?.latest_dag_runs ?? [];
+
+ return (
+
+
+
+ );
+};
diff --git a/airflow/ui/src/pages/DagsList/Dag/Header.tsx b/airflow/ui/src/pages/Dag/Header.tsx
similarity index 84%
rename from airflow/ui/src/pages/DagsList/Dag/Header.tsx
rename to airflow/ui/src/pages/Dag/Header.tsx
index 39145f25197fa..c7a4c0654e034 100644
--- a/airflow/ui/src/pages/DagsList/Dag/Header.tsx
+++ b/airflow/ui/src/pages/Dag/Header.tsx
@@ -16,15 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
-import {
- Box,
- Flex,
- Heading,
- HStack,
- SimpleGrid,
- Text,
- VStack,
-} from "@chakra-ui/react";
+import { Box, Flex, Heading, HStack, SimpleGrid, Text } from "@chakra-ui/react";
import { FiCalendar } from "react-icons/fi";
import type {
@@ -35,11 +27,12 @@ import { DagIcon } from "src/assets/DagIcon";
import DagDocumentation from "src/components/DagDocumentation";
import DagRunInfo from "src/components/DagRunInfo";
import ParseDag from "src/components/ParseDag";
+import { Stat } from "src/components/Stat";
import { TogglePause } from "src/components/TogglePause";
import TriggerDAGTextButton from "src/components/TriggerDag/TriggerDAGTextButton";
import { Tooltip } from "src/components/ui";
-import { DagTags } from "../DagTags";
+import { DagTags } from "../DagsList/DagTags";
export const Header = ({
dag,
@@ -50,7 +43,7 @@ export const Header = ({
readonly dagId?: string;
readonly latestRun?: DAGRunResponse;
}) => (
-
+
@@ -77,10 +70,7 @@ export const Header = ({
-
-
- Schedule
-
+
{Boolean(dag?.timetable_summary) ? (
@@ -89,11 +79,8 @@ export const Header = ({
) : undefined}
-
-
-
- Last Run
-
+
+
{Boolean(latestRun) && latestRun !== undefined ? (
) : undefined}
-
-
-
- Next Run
-
+
+
{Boolean(dag?.next_dagrun) && dag !== undefined ? (
) : undefined}
-
+
diff --git a/airflow/ui/src/pages/DagsList/Dag/Overview/Overview.tsx b/airflow/ui/src/pages/Dag/Overview/Overview.tsx
similarity index 100%
rename from airflow/ui/src/pages/DagsList/Dag/Overview/Overview.tsx
rename to airflow/ui/src/pages/Dag/Overview/Overview.tsx
diff --git a/airflow/ui/src/pages/DagsList/Dag/Overview/index.ts b/airflow/ui/src/pages/Dag/Overview/index.ts
similarity index 100%
rename from airflow/ui/src/pages/DagsList/Dag/Overview/index.ts
rename to airflow/ui/src/pages/Dag/Overview/index.ts
diff --git a/airflow/ui/src/pages/DagsList/Dag/Runs/Runs.tsx b/airflow/ui/src/pages/Dag/Runs/Runs.tsx
similarity index 100%
rename from airflow/ui/src/pages/DagsList/Dag/Runs/Runs.tsx
rename to airflow/ui/src/pages/Dag/Runs/Runs.tsx
diff --git a/airflow/ui/src/pages/DagsList/Dag/Runs/index.ts b/airflow/ui/src/pages/Dag/Runs/index.ts
similarity index 100%
rename from airflow/ui/src/pages/DagsList/Dag/Runs/index.ts
rename to airflow/ui/src/pages/Dag/Runs/index.ts
diff --git a/airflow/ui/src/pages/DagsList/Dag/Tasks/TaskCard.tsx b/airflow/ui/src/pages/Dag/Tasks/TaskCard.tsx
similarity index 100%
rename from airflow/ui/src/pages/DagsList/Dag/Tasks/TaskCard.tsx
rename to airflow/ui/src/pages/Dag/Tasks/TaskCard.tsx
diff --git a/airflow/ui/src/pages/DagsList/Dag/Tasks/TaskRecentRuns.tsx b/airflow/ui/src/pages/Dag/Tasks/TaskRecentRuns.tsx
similarity index 100%
rename from airflow/ui/src/pages/DagsList/Dag/Tasks/TaskRecentRuns.tsx
rename to airflow/ui/src/pages/Dag/Tasks/TaskRecentRuns.tsx
diff --git a/airflow/ui/src/pages/DagsList/Dag/Tasks/Tasks.tsx b/airflow/ui/src/pages/Dag/Tasks/Tasks.tsx
similarity index 100%
rename from airflow/ui/src/pages/DagsList/Dag/Tasks/Tasks.tsx
rename to airflow/ui/src/pages/Dag/Tasks/Tasks.tsx
diff --git a/airflow/ui/src/pages/DagsList/Dag/Tasks/index.ts b/airflow/ui/src/pages/Dag/Tasks/index.ts
similarity index 100%
rename from airflow/ui/src/pages/DagsList/Dag/Tasks/index.ts
rename to airflow/ui/src/pages/Dag/Tasks/index.ts
diff --git a/airflow/ui/src/pages/DagsList/Dag/index.ts b/airflow/ui/src/pages/Dag/index.ts
similarity index 100%
rename from airflow/ui/src/pages/DagsList/Dag/index.ts
rename to airflow/ui/src/pages/Dag/index.ts
diff --git a/airflow/ui/src/pages/DagsList/Dag/DagVizModal.tsx b/airflow/ui/src/pages/DagsList/Dag/DagVizModal.tsx
deleted file mode 100644
index 9ab868b8f8d28..0000000000000
--- a/airflow/ui/src/pages/DagsList/Dag/DagVizModal.tsx
+++ /dev/null
@@ -1,52 +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 { Heading } from "@chakra-ui/react";
-
-import type { DAGResponse } from "openapi/requests/types.gen";
-import { Dialog } from "src/components/ui";
-
-import { Graph } from "./Graph";
-
-type TriggerDAGModalProps = {
- dagDisplayName?: DAGResponse["dag_display_name"];
- dagId?: DAGResponse["dag_id"];
- onClose: () => void;
- open: boolean;
-};
-
-export const DagVizModal: React.FC = ({
- dagDisplayName,
- dagId,
- onClose,
- open,
-}) => (
-
-
-
-
- {Boolean(dagDisplayName) ? dagDisplayName : "Dag Undefined"}
-
-
-
-
- {dagId === undefined ? undefined : }
-
-
-
-);
diff --git a/airflow/ui/src/pages/DagsList/Dag/Tabs.tsx b/airflow/ui/src/pages/DagsList/Dag/Tabs.tsx
deleted file mode 100644
index c418c24094f1e..0000000000000
--- a/airflow/ui/src/pages/DagsList/Dag/Tabs.tsx
+++ /dev/null
@@ -1,92 +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 { Button, Center, Flex } from "@chakra-ui/react";
-import { NavLink, useSearchParams } from "react-router-dom";
-
-import type { DAGDetailsResponse } from "openapi/requests/types.gen";
-import { DagIcon } from "src/assets/DagIcon";
-import { capitalize } from "src/utils";
-
-import { DagVizModal } from "./DagVizModal";
-
-const tabs = ["overview", "runs", "tasks", "events", "code"];
-
-const MODAL = "modal";
-
-export const DagTabs = ({ dag }: { readonly dag?: DAGDetailsResponse }) => {
- const [searchParams, setSearchParams] = useSearchParams();
-
- const modal = searchParams.get(MODAL);
-
- const isGraphOpen = modal === "graph";
-
- const onClose = () => {
- searchParams.delete(MODAL);
- setSearchParams(searchParams);
- };
-
- const onOpen = () => {
- searchParams.set(MODAL, "graph");
- setSearchParams(searchParams);
- };
-
- return (
- <>
-
-
- {tabs.map((tab) => (
-
- {({ isActive }) => (
-
- {capitalize(tab)}
-
- )}
-
- ))}
-
-
-
-
-
-
- >
- );
-};
diff --git a/airflow/ui/src/pages/DagsList/DagCard.tsx b/airflow/ui/src/pages/DagsList/DagCard.tsx
index 418a169d393b6..0896e0bacf695 100644
--- a/airflow/ui/src/pages/DagsList/DagCard.tsx
+++ b/airflow/ui/src/pages/DagsList/DagCard.tsx
@@ -16,19 +16,12 @@
* specific language governing permissions and limitations
* under the License.
*/
-import {
- Box,
- Flex,
- HStack,
- Heading,
- SimpleGrid,
- VStack,
- Link,
-} from "@chakra-ui/react";
+import { Box, Flex, HStack, SimpleGrid, Link } from "@chakra-ui/react";
import { Link as RouterLink } from "react-router-dom";
import type { DAGWithLatestDagRunsResponse } from "openapi/requests/types.gen";
import DagRunInfo from "src/components/DagRunInfo";
+import { Stat } from "src/components/Stat";
import { TogglePause } from "src/components/TogglePause";
import TriggerDAGIconButton from "src/components/TriggerDag/TriggerDAGIconButton";
import { Tooltip } from "src/components/ui";
@@ -82,16 +75,10 @@ export const DagCard = ({ dag }: Props) => {
-
-
- Schedule
-
+
-
-
-
- Latest Run
-
+
+
{latestRun ? (
{
state={latestRun.state}
/>
) : undefined}
-
-
-
- Next Run
-
+
+
{Boolean(dag.next_dagrun) ? (
{
nextDagrunCreateAfter={dag.next_dagrun_create_after}
/>
) : undefined}
-
+
diff --git a/airflow/ui/src/pages/DagsList/Run/Run.tsx b/airflow/ui/src/pages/DagsList/Run/Run.tsx
deleted file mode 100644
index 4011eeda3cbd2..0000000000000
--- a/airflow/ui/src/pages/DagsList/Run/Run.tsx
+++ /dev/null
@@ -1,57 +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, Button, Heading } from "@chakra-ui/react";
-import { FiChevronsLeft } from "react-icons/fi";
-import { useParams, Link as RouterLink } from "react-router-dom";
-
-import { useDagRunServiceGetDagRun } from "openapi/queries";
-import { ErrorAlert } from "src/components/ErrorAlert";
-import { ProgressBar, Status } from "src/components/ui";
-
-export const Run = () => {
- const { dagId = "", runId = "" } = useParams();
-
- const {
- data: dagRun,
- error,
- isLoading,
- } = useDagRunServiceGetDagRun({
- dagId,
- dagRunId: runId,
- });
-
- return (
-
-
-
- {dagId}.{runId}
-
-
-
- {dagRun === undefined ? undefined : (
- {dagRun.state}
- )}
-
- );
-};
diff --git a/airflow/ui/src/pages/Events/Events.tsx b/airflow/ui/src/pages/Events/Events.tsx
index 028a388894edc..703cfe7d6006b 100644
--- a/airflow/ui/src/pages/Events/Events.tsx
+++ b/airflow/ui/src/pages/Events/Events.tsx
@@ -28,7 +28,9 @@ import { ErrorAlert } from "src/components/ErrorAlert";
import Time from "src/components/Time";
const eventsColumn = (
- dagId: string | undefined,
+ dagId?: string,
+ runId?: string,
+ taskId?: string,
): Array> => [
{
accessorKey: "when",
@@ -51,22 +53,30 @@ const eventsColumn = (
},
},
]),
- {
- accessorKey: "run_id",
- enableSorting: true,
- header: "Run ID",
- meta: {
- skeletonWidth: 10,
- },
- },
- {
- accessorKey: "task_id",
- enableSorting: true,
- header: "Task ID",
- meta: {
- skeletonWidth: 10,
- },
- },
+ ...(Boolean(runId)
+ ? []
+ : [
+ {
+ accessorKey: "run_id",
+ enableSorting: true,
+ header: "Run ID",
+ meta: {
+ skeletonWidth: 10,
+ },
+ },
+ ]),
+ ...(Boolean(taskId)
+ ? []
+ : [
+ {
+ accessorKey: "task_id",
+ enableSorting: true,
+ header: "Task ID",
+ meta: {
+ skeletonWidth: 10,
+ },
+ },
+ ]),
{
accessorKey: "map_index",
enableSorting: false,
@@ -102,7 +112,7 @@ const eventsColumn = (
];
export const Events = () => {
- const { dagId } = useParams();
+ const { dagId, runId, taskId } = useParams();
const { setTableURLState, tableURLState } = useTableURLState({
sorting: [{ desc: true, id: "when" }],
});
@@ -121,13 +131,15 @@ export const Events = () => {
limit: pagination.pageSize,
offset: pagination.pageIndex * pagination.pageSize,
orderBy,
+ runId,
+ taskId,
});
return (
(
+
+
+
+
+
+ Run:
+ {dagRun.dag_run_id}
+
+ {dagRun.state}
+
+
+
+
+
+ {dagRun.note === null || dagRun.note.length === 0 ? undefined : (
+
+
+
+ {dagRun.note}
+
+
+ )}
+
+
+
+
+ {dagRun.run_type}
+
+
+
+
+
+
+
+
+
+ {dayjs
+ .duration(dayjs(dagRun.end_date).diff(dagRun.start_date))
+ .asSeconds()
+ .toFixed(2)}
+ s
+
+
+
+);
diff --git a/airflow/ui/src/pages/Run/Run.tsx b/airflow/ui/src/pages/Run/Run.tsx
new file mode 100644
index 0000000000000..5fea67519276b
--- /dev/null
+++ b/airflow/ui/src/pages/Run/Run.tsx
@@ -0,0 +1,78 @@
+/*!
+ * 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 { LiaSlashSolid } from "react-icons/lia";
+import { useParams, Link as RouterLink } from "react-router-dom";
+
+import {
+ useDagRunServiceGetDagRun,
+ useDagServiceGetDagDetails,
+} from "openapi/queries";
+import { Breadcrumb } from "src/components/ui";
+import { DetailsLayout } from "src/layouts/Details/DetailsLayout";
+
+import { Header } from "./Header";
+
+const tabs = [
+ { label: "Task Instances", value: "" },
+ { label: "Events", value: "events" },
+ { label: "Code", value: "code" },
+];
+
+export const Run = () => {
+ const { dagId = "", runId = "" } = useParams();
+
+ const {
+ data: dagRun,
+ error,
+ isLoading,
+ } = useDagRunServiceGetDagRun({
+ dagId,
+ dagRunId: runId,
+ });
+
+ const {
+ data: dag,
+ error: dagError,
+ isLoading: isLoadinDag,
+ } = useDagServiceGetDagDetails({
+ dagId,
+ });
+
+ return (
+
+ }>
+
+ Dags
+
+
+
+ {dag?.dag_display_name ?? dagId}
+
+
+ {runId}
+
+ {dagRun === undefined ? undefined : }
+
+ );
+};
diff --git a/airflow/ui/src/pages/Run/TaskInstances.tsx b/airflow/ui/src/pages/Run/TaskInstances.tsx
new file mode 100644
index 0000000000000..9503b2e20ad2e
--- /dev/null
+++ b/airflow/ui/src/pages/Run/TaskInstances.tsx
@@ -0,0 +1,117 @@
+/*!
+ * 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, Link } from "@chakra-ui/react";
+import type { ColumnDef } from "@tanstack/react-table";
+import dayjs from "dayjs";
+import { Link as RouterLink, useParams } from "react-router-dom";
+
+import { useTaskInstanceServiceGetTaskInstances } from "openapi/queries";
+import type { TaskInstanceResponse } from "openapi/requests/types.gen";
+import { DataTable } from "src/components/DataTable";
+import { useTableURLState } from "src/components/DataTable/useTableUrlState";
+import { ErrorAlert } from "src/components/ErrorAlert";
+import Time from "src/components/Time";
+import { Status } from "src/components/ui";
+
+const columns: Array> = [
+ {
+ accessorKey: "task_display_name",
+ cell: ({ row: { original } }) => (
+
+ -1 ? `?map_index=${original.map_index}` : ""}`}
+ >
+ {original.task_display_name}
+
+
+ ),
+ header: "Task ID",
+ },
+ {
+ accessorKey: "map_index",
+ header: "Map Index",
+ },
+ {
+ accessorKey: "try_number",
+ enableSorting: false,
+ header: "Try Number",
+ },
+ {
+ accessorKey: "state",
+ cell: ({
+ row: {
+ original: { state },
+ },
+ }) => {state},
+ header: () => "State",
+ },
+ {
+ accessorKey: "operator",
+ enableSorting: false,
+ header: "Operator",
+ },
+ {
+ accessorKey: "start_date",
+ cell: ({ row: { original } }) => ,
+ header: "Start Date",
+ },
+ {
+ accessorKey: "end_date",
+ cell: ({ row: { original } }) => ,
+ header: "End Date",
+ },
+ {
+ cell: ({ row: { original } }) =>
+ `${dayjs.duration(dayjs(original.end_date).diff(original.start_date)).asSeconds().toFixed(2)}s`,
+ header: "Duration",
+ },
+];
+
+export const TaskInstances = () => {
+ const { dagId = "", runId = "" } = useParams();
+ const { setTableURLState, tableURLState } = useTableURLState();
+ const { pagination, sorting } = tableURLState;
+ const [sort] = sorting;
+ const orderBy = sort ? `${sort.desc ? "-" : ""}${sort.id}` : undefined;
+
+ const { data, error, isFetching, isLoading } =
+ useTaskInstanceServiceGetTaskInstances({
+ dagId,
+ dagRunId: runId,
+ limit: pagination.pageSize,
+ offset: pagination.pageIndex * pagination.pageSize,
+ orderBy,
+ });
+
+ return (
+
+ }
+ initialState={tableURLState}
+ isFetching={isFetching}
+ isLoading={isLoading}
+ modelName="Task Instance"
+ onStateChange={setTableURLState}
+ total={data?.total_entries}
+ />
+
+ );
+};
diff --git a/airflow/ui/src/pages/DagsList/Run/index.ts b/airflow/ui/src/pages/Run/index.ts
similarity index 100%
rename from airflow/ui/src/pages/DagsList/Run/index.ts
rename to airflow/ui/src/pages/Run/index.ts
diff --git a/airflow/ui/src/pages/TaskInstance/Header.tsx b/airflow/ui/src/pages/TaskInstance/Header.tsx
new file mode 100644
index 0000000000000..87f7e35ef161b
--- /dev/null
+++ b/airflow/ui/src/pages/TaskInstance/Header.tsx
@@ -0,0 +1,80 @@
+/*!
+ * 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, Flex, Heading, HStack, SimpleGrid, Text } from "@chakra-ui/react";
+import dayjs from "dayjs";
+import { MdOutlineModeComment, MdOutlineTask } from "react-icons/md";
+
+import type { TaskInstanceResponse } from "openapi/requests/types.gen";
+import { Stat } from "src/components/Stat";
+import Time from "src/components/Time";
+import { Status } from "src/components/ui";
+
+export const Header = ({
+ taskInstance,
+}: {
+ readonly taskInstance: TaskInstanceResponse;
+}) => (
+
+
+
+
+
+ Task Instance:
+ {taskInstance.task_display_name}{" "}
+
+
+ {taskInstance.state}
+
+
+
+
+
+ {taskInstance.note === null ||
+ taskInstance.note.length === 0 ? undefined : (
+
+
+
+ {taskInstance.note}
+
+
+ )}
+
+ {taskInstance.operator}
+ {taskInstance.map_index > -1 ? (
+ {taskInstance.map_index}
+ ) : undefined}
+ {taskInstance.try_number > 1 ? (
+ {taskInstance.try_number}
+ ) : undefined}
+
+
+
+
+
+
+
+ {dayjs
+ .duration(dayjs(taskInstance.end_date).diff(taskInstance.start_date))
+ .asSeconds()
+ .toFixed(2)}
+ s
+
+
+
+);
diff --git a/airflow/ui/src/pages/TaskInstance/TaskInstance.tsx b/airflow/ui/src/pages/TaskInstance/TaskInstance.tsx
new file mode 100644
index 0000000000000..f665bbdd4fae4
--- /dev/null
+++ b/airflow/ui/src/pages/TaskInstance/TaskInstance.tsx
@@ -0,0 +1,113 @@
+/*!
+ * 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 { LiaSlashSolid } from "react-icons/lia";
+import {
+ useParams,
+ Link as RouterLink,
+ useSearchParams,
+} from "react-router-dom";
+
+import {
+ useDagServiceGetDagDetails,
+ useTaskInstanceServiceGetMappedTaskInstance,
+} from "openapi/queries";
+import { Breadcrumb } from "src/components/ui";
+import { DetailsLayout } from "src/layouts/Details/DetailsLayout";
+
+import { Header } from "./Header";
+
+const tabs = [
+ { label: "Logs", value: "" },
+ { label: "Events", value: "events" },
+ { label: "XCom", value: "xcom" },
+ { label: "Code", value: "code" },
+ { label: "Details", value: "details" },
+];
+
+export const TaskInstance = () => {
+ const { dagId = "", runId = "", taskId = "" } = useParams();
+ const [searchParams] = useSearchParams();
+
+ const mapIndexParam = searchParams.get("map_index");
+ const mapIndex = parseInt(mapIndexParam ?? "-1", 10);
+
+ const {
+ data: taskInstance,
+ error,
+ isLoading,
+ } = useTaskInstanceServiceGetMappedTaskInstance({
+ dagId,
+ dagRunId: runId,
+ mapIndex,
+ taskId,
+ });
+
+ const {
+ data: dag,
+ error: dagError,
+ isLoading: isDagLoading,
+ } = useDagServiceGetDagDetails({
+ dagId,
+ });
+
+ const links = [
+ { label: "Dags", value: "/dags" },
+ { label: dag?.dag_display_name ?? dagId, value: `/dags/${dagId}` },
+ { label: runId, value: `/dags/${dagId}/runs/${runId}` },
+ { label: taskInstance?.task_display_name ?? taskId },
+ ];
+
+ if (mapIndexParam !== null) {
+ links.push({ label: mapIndexParam });
+ }
+
+ return (
+
+ }>
+ {links.map((link, index) => {
+ if (index === links.length - 1) {
+ return (
+
+ {link.label}
+
+ );
+ }
+
+ return link.value === undefined ? (
+
+ {link.label}
+
+ ) : (
+
+ {link.label}
+
+ );
+ })}
+
+ {taskInstance === undefined ? undefined : (
+
+ )}
+
+ );
+};
diff --git a/airflow/ui/src/pages/TaskInstance/index.ts b/airflow/ui/src/pages/TaskInstance/index.ts
new file mode 100644
index 0000000000000..2a7667ea7db04
--- /dev/null
+++ b/airflow/ui/src/pages/TaskInstance/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 * from "./TaskInstance";
diff --git a/airflow/ui/src/router.tsx b/airflow/ui/src/router.tsx
index 4fae695a5bc51..938bc91c8413a 100644
--- a/airflow/ui/src/router.tsx
+++ b/airflow/ui/src/router.tsx
@@ -19,16 +19,18 @@
import { createBrowserRouter } from "react-router-dom";
import { BaseLayout } from "src/layouts/BaseLayout";
+import { Dag } from "src/pages/Dag";
+import { Code } from "src/pages/Dag/Code";
+import { Overview } from "src/pages/Dag/Overview";
+import { Runs } from "src/pages/Dag/Runs";
+import { Tasks } from "src/pages/Dag/Tasks";
import { DagsList } from "src/pages/DagsList";
-import { Dag } from "src/pages/DagsList/Dag";
-import { Code } from "src/pages/DagsList/Dag/Code";
-import { Overview } from "src/pages/DagsList/Dag/Overview";
-import { Runs } from "src/pages/DagsList/Dag/Runs";
-import { Tasks } from "src/pages/DagsList/Dag/Tasks";
-import { Run } from "src/pages/DagsList/Run";
import { Dashboard } from "src/pages/Dashboard";
import { ErrorPage } from "src/pages/Error";
import { Events } from "src/pages/Events";
+import { Run } from "src/pages/Run";
+import { TaskInstances } from "src/pages/Run/TaskInstances";
+import { TaskInstance } from "src/pages/TaskInstance";
import { Variables } from "./pages/Variables";
@@ -63,7 +65,26 @@ export const router = createBrowserRouter(
element: ,
path: "dags/:dagId",
},
- { element: , path: "dags/:dagId/runs/:runId" },
+ {
+ children: [
+ { element: , index: true },
+ { element: , path: "events" },
+ { element: , path: "code" },
+ ],
+ element: ,
+ path: "dags/:dagId/runs/:runId",
+ },
+ {
+ children: [
+ { element: