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 } }) =>