diff --git a/airflow/ui/src/components/DagRunInfo.tsx b/airflow/ui/src/components/DagRunInfo.tsx index 0d30e9c7667c4..2cd7cac433721 100644 --- a/airflow/ui/src/components/DagRunInfo.tsx +++ b/airflow/ui/src/components/DagRunInfo.tsx @@ -21,10 +21,7 @@ import dayjs from "dayjs"; import type { DAGRunResponse } from "openapi/requests/types.gen"; import Time from "src/components/Time"; -import { Tooltip } from "src/components/ui"; -import { stateColor } from "src/utils/stateColor"; - -import { StateCircle } from "./StateCircle"; +import { Tooltip, Status } from "src/components/ui"; type Props = { readonly dataIntervalEnd?: string | null; @@ -82,10 +79,7 @@ const DagRunInfo = ({ diff --git a/airflow/ui/src/components/RunTypeIcon.tsx b/airflow/ui/src/components/RunTypeIcon.tsx new file mode 100644 index 0000000000000..f465fbf298f2b --- /dev/null +++ b/airflow/ui/src/components/RunTypeIcon.tsx @@ -0,0 +1,48 @@ +/*! + * 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 type { IconBaseProps } from "react-icons"; +import { HiDatabase } from "react-icons/hi"; +import { MdPlayArrow, MdOutlineSchedule } from "react-icons/md"; +import { RiArrowGoBackFill } from "react-icons/ri"; + +import type { DAGRunResponse } from "openapi/requests/types.gen"; + +type Props = { + readonly runType: DAGRunResponse["run_type"]; +} & IconBaseProps; + +const iconStyle = { + display: "inline", + verticalAlign: "bottom", +}; + +export const RunTypeIcon = ({ runType, ...rest }: Props) => { + switch (runType) { + case "asset_triggered": + return ; + case "backfill": + return ; + case "manual": + return ; + case "scheduled": + return ; + default: + return undefined; + } +}; diff --git a/airflow/ui/src/components/TimeRangeSelector.tsx b/airflow/ui/src/components/TimeRangeSelector.tsx index 8ce7bef53a871..93f8d7674deb5 100644 --- a/airflow/ui/src/components/TimeRangeSelector.tsx +++ b/airflow/ui/src/components/TimeRangeSelector.tsx @@ -39,7 +39,6 @@ type Props = { const defaultTimeOptions = createListCollection({ items: [ { label: "Last 1 hour", value: "1" }, - { label: "Last 8 hours", value: "8" }, { label: "Last 12 hours", value: "12" }, { label: "Last 24 hours", value: "24" }, { label: "Last week", value: "168" }, diff --git a/airflow/ui/src/components/TrendCountButton.tsx b/airflow/ui/src/components/TrendCountButton.tsx new file mode 100644 index 0000000000000..18dcfa7241d3a --- /dev/null +++ b/airflow/ui/src/components/TrendCountButton.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 { HStack, Badge, Text, Skeleton } from "@chakra-ui/react"; +import { Link, type To } from "react-router-dom"; + +import { pluralize } from "src/utils"; + +import { TrendCountChart, type ChartEvent } from "./TrendCountChart"; + +type Props = { + readonly colorPalette: string; + readonly count: number; + readonly endDate: string; + readonly events: Array; + readonly isLoading?: boolean; + readonly label: string; + readonly route: To; + readonly startDate: string; +}; + +export const TrendCountButton = ({ + colorPalette, + count, + endDate, + events, + isLoading, + label, + route, + startDate, +}: Props) => { + if (count === 0 && !isLoading) { + return undefined; + } + + return isLoading ? ( + + ) : ( + + + + {count} + + + {pluralize(label, count, undefined, true)} + + + + + ); +}; diff --git a/airflow/ui/src/pages/DagsList/Dag/Overview/Chart.tsx b/airflow/ui/src/components/TrendCountChart.tsx similarity index 94% rename from airflow/ui/src/pages/DagsList/Dag/Overview/Chart.tsx rename to airflow/ui/src/components/TrendCountChart.tsx index b67796a7a684f..0e7c011499276 100644 --- a/airflow/ui/src/pages/DagsList/Dag/Overview/Chart.tsx +++ b/airflow/ui/src/components/TrendCountChart.tsx @@ -42,10 +42,10 @@ ChartJS.register( Tooltip, ); -type Event = { timestamp: string }; +export type ChartEvent = { timestamp: string }; const aggregateEventsIntoIntervals = ( - events: Array, + events: Array, startDate: string, endDate: string, ) => { @@ -103,11 +103,11 @@ const options = { type Props = { readonly endDate: string; - readonly events: Array; + readonly events: Array; readonly startDate: string; }; -export const Chart = ({ endDate, events, startDate }: Props) => { +export const TrendCountChart = ({ endDate, events, startDate }: Props) => { const { colorMode } = useColorMode(); const chartRef = useRef>(); diff --git a/airflow/ui/src/components/ui/Status.tsx b/airflow/ui/src/components/ui/Status.tsx new file mode 100644 index 0000000000000..7c408b931d378 --- /dev/null +++ b/airflow/ui/src/components/ui/Status.tsx @@ -0,0 +1,45 @@ +/*! + * 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 { Status as ChakraStatus } from "@chakra-ui/react"; +import * as React from "react"; + +import type { + DagRunState, + TaskInstanceState, +} from "openapi/requests/types.gen"; +import { stateColor } from "src/utils/stateColor"; + +type StatusValue = DagRunState | TaskInstanceState; + +export type StatusProps = { + state?: StatusValue; +} & ChakraStatus.RootProps; + +export const Status = React.forwardRef( + ({ children, state, ...rest }, ref) => { + const colorPalette = state === undefined ? "info" : stateColor[state]; + + return ( + + + {children} + + ); + }, +); diff --git a/airflow/ui/src/components/ui/index.ts b/airflow/ui/src/components/ui/index.ts index f596effa12a93..b6fa2898e9e72 100644 --- a/airflow/ui/src/components/ui/index.ts +++ b/airflow/ui/src/components/ui/index.ts @@ -31,3 +31,4 @@ export * from "./Tooltip"; export * from "./ProgressBar"; export * from "./Menu"; export * from "./Accordion"; +export * from "./Status"; diff --git a/airflow/ui/src/main.tsx b/airflow/ui/src/main.tsx index 524efdb6695ce..964e6e32b9b5a 100644 --- a/airflow/ui/src/main.tsx +++ b/airflow/ui/src/main.tsx @@ -19,6 +19,7 @@ import { ChakraProvider, defaultSystem } from "@chakra-ui/react"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import axios, { type AxiosError } from "axios"; +import { StrictMode } from "react"; import { createRoot } from "react-dom/client"; import { RouterProvider } from "react-router-dom"; @@ -61,13 +62,15 @@ axios.interceptors.response.use( ); createRoot(document.querySelector("#root") as HTMLDivElement).render( - - - - - - - - - , + + + + + + + + + + + , ); diff --git a/airflow/ui/src/pages/DagsList/Dag/Overview/Overview.tsx b/airflow/ui/src/pages/DagsList/Dag/Overview/Overview.tsx index be94a01d8edc2..dfe118bd18e10 100644 --- a/airflow/ui/src/pages/DagsList/Dag/Overview/Overview.tsx +++ b/airflow/ui/src/pages/DagsList/Dag/Overview/Overview.tsx @@ -16,19 +16,20 @@ * specific language governing permissions and limitations * under the License. */ -import { Box, HStack, Badge, Text, Skeleton } from "@chakra-ui/react"; +import { Box, HStack } from "@chakra-ui/react"; import dayjs from "dayjs"; import { useState } from "react"; -import { Link, useLocation, useParams } from "react-router-dom"; +import { useParams } from "react-router-dom"; -import { useTaskInstanceServiceGetTaskInstances } from "openapi/queries"; +import { + useDagRunServiceGetDagRuns, + useTaskInstanceServiceGetTaskInstances, +} from "openapi/queries"; import TimeRangeSelector from "src/components/TimeRangeSelector"; -import { pluralize } from "src/utils"; +import { TrendCountButton } from "src/components/TrendCountButton"; import { stateColor } from "src/utils/stateColor"; -import { Chart } from "./Chart"; - -const defaultHour = "8"; +const defaultHour = "12"; export const Overview = () => { const { dagId } = useParams(); @@ -48,7 +49,13 @@ export const Overview = () => { state: ["failed"], }); - const location = useLocation(); + const { data: failedRuns, isLoading: isLoadingRuns } = + useDagRunServiceGetDagRuns({ + dagId: dagId ?? "", + logicalDateGte: startDate, + logicalDateLte: endDate, + state: ["failed"], + }); // TODO actually link to task instances list return ( @@ -62,35 +69,38 @@ export const Overview = () => { startDate={startDate} /> - {failedTasks?.total_entries !== undefined && - failedTasks.total_entries > 0 ? ( - // TODO: make sure url params pass correctly - - - - {failedTasks.total_entries} - - - Failed{" "} - {pluralize("Task", failedTasks.total_entries, undefined, true)} - - ({ - timestamp: ti.start_date ?? ti.logical_date, - }))} - startDate={startDate} - /> - - - ) : undefined} - {isLoading ? ( - - ) : undefined} + + ({ + timestamp: ti.start_date ?? ti.logical_date, + }))} + isLoading={isLoading} + label="Failed Task" + route={{ + pathname: "tasks", + search: "state=failed", + }} + startDate={startDate} + /> + ({ + timestamp: dr.start_date ?? dr.logical_date ?? "", + }))} + isLoading={isLoadingRuns} + label="Failed Run" + route={{ + pathname: "runs", + search: "state=failed", + }} + startDate={startDate} + /> + ); }; diff --git a/airflow/ui/src/pages/DagsList/Dag/Runs/Runs.tsx b/airflow/ui/src/pages/DagsList/Dag/Runs/Runs.tsx new file mode 100644 index 0000000000000..02c157d90576a --- /dev/null +++ b/airflow/ui/src/pages/DagsList/Dag/Runs/Runs.tsx @@ -0,0 +1,197 @@ +/*! + * 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, + createListCollection, + Flex, + HStack, + Link, + type SelectValueChangeDetails, + Text, +} from "@chakra-ui/react"; +import type { ColumnDef } from "@tanstack/react-table"; +import dayjs from "dayjs"; +import { useCallback } from "react"; +import { + useParams, + Link as RouterLink, + useSearchParams, +} from "react-router-dom"; + +import { useDagRunServiceGetDagRuns } from "openapi/queries"; +import type { DAGRunResponse, DagRunState } 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 { RunTypeIcon } from "src/components/RunTypeIcon"; +import Time from "src/components/Time"; +import { Select, Status } from "src/components/ui"; +import { capitalize } from "src/utils"; + +const columns: Array> = [ + { + accessorKey: "run_id", + cell: ({ row: { original } }) => ( + + + {original.dag_run_id} + + + ), + enableSorting: false, + header: "Run ID", + }, + { + accessorKey: "state", + cell: ({ + row: { + original: { state }, + }, + }) => {state}, + header: () => "State", + }, + { + accessorKey: "run_type", + cell: ({ row: { original } }) => ( + + + {original.run_type} + + ), + enableSorting: false, + header: "Run Type", + }, + { + accessorKey: "start_date", + cell: ({ row: { original } }) =>