Skip to content

Commit

Permalink
Add Dag Runs list to new UI (apache#44269)
Browse files Browse the repository at this point in the history
* Create dag graph with nested groups and join_ids

* move opengroup logic to a local context provider

* Add dag runs list, details, and failed runs button

* Refactor tabs to use custom styled NavLinks

* Remove note from table
  • Loading branch information
bbovenzi authored Dec 4, 2024
1 parent 5b5f8f4 commit 2e8f456
Show file tree
Hide file tree
Showing 17 changed files with 555 additions and 131 deletions.
10 changes: 2 additions & 8 deletions airflow/ui/src/components/DagRunInfo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -82,10 +79,7 @@ const DagRunInfo = ({
<HStack fontSize="sm">
<Time datetime={dataIntervalStart} showTooltip={false} />
{state === undefined ? undefined : (
<>
<StateCircle state={state} />
<Text color={stateColor[state]}>{state}</Text>
</>
<Status state={state}>{state}</Status>
)}
</HStack>
</Tooltip>
Expand Down
48 changes: 48 additions & 0 deletions airflow/ui/src/components/RunTypeIcon.tsx
Original file line number Diff line number Diff line change
@@ -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 <HiDatabase style={iconStyle} {...rest} />;
case "backfill":
return <RiArrowGoBackFill style={iconStyle} {...rest} />;
case "manual":
return <MdPlayArrow style={iconStyle} {...rest} />;
case "scheduled":
return <MdOutlineSchedule style={iconStyle} {...rest} />;
default:
return undefined;
}
};
1 change: 0 additions & 1 deletion airflow/ui/src/components/TimeRangeSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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" },
Expand Down
70 changes: 70 additions & 0 deletions airflow/ui/src/components/TrendCountButton.tsx
Original file line number Diff line number Diff line change
@@ -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<ChartEvent>;
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 ? (
<Skeleton borderRadius={4} height="45px" width="350px" />
) : (
<Link to={route}>
<HStack borderRadius={4} borderWidth={1} p={3} width="max-content">
<Badge borderRadius="50%" colorPalette={colorPalette} variant="solid">
{count}
</Badge>
<Text fontSize="sm" fontWeight="bold">
{pluralize(label, count, undefined, true)}
</Text>
<TrendCountChart
endDate={endDate}
events={events}
startDate={startDate}
/>
</HStack>
</Link>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,10 @@ ChartJS.register(
Tooltip,
);

type Event = { timestamp: string };
export type ChartEvent = { timestamp: string };

const aggregateEventsIntoIntervals = (
events: Array<Event>,
events: Array<ChartEvent>,
startDate: string,
endDate: string,
) => {
Expand Down Expand Up @@ -103,11 +103,11 @@ const options = {

type Props = {
readonly endDate: string;
readonly events: Array<Event>;
readonly events: Array<ChartEvent>;
readonly startDate: string;
};

export const Chart = ({ endDate, events, startDate }: Props) => {
export const TrendCountChart = ({ endDate, events, startDate }: Props) => {
const { colorMode } = useColorMode();
const chartRef = useRef<ChartJS<"line">>();

Expand Down
45 changes: 45 additions & 0 deletions airflow/ui/src/components/ui/Status.tsx
Original file line number Diff line number Diff line change
@@ -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<HTMLDivElement, StatusProps>(
({ children, state, ...rest }, ref) => {
const colorPalette = state === undefined ? "info" : stateColor[state];

return (
<ChakraStatus.Root ref={ref} {...rest}>
<ChakraStatus.Indicator bg={colorPalette} />
{children}
</ChakraStatus.Root>
);
},
);
1 change: 1 addition & 0 deletions airflow/ui/src/components/ui/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,4 @@ export * from "./Tooltip";
export * from "./ProgressBar";
export * from "./Menu";
export * from "./Accordion";
export * from "./Status";
21 changes: 12 additions & 9 deletions airflow/ui/src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand Down Expand Up @@ -61,13 +62,15 @@ axios.interceptors.response.use(
);

createRoot(document.querySelector("#root") as HTMLDivElement).render(
<ChakraProvider value={defaultSystem}>
<ColorModeProvider>
<QueryClientProvider client={queryClient}>
<TimezoneProvider>
<RouterProvider router={router} />
</TimezoneProvider>
</QueryClientProvider>
</ColorModeProvider>
</ChakraProvider>,
<StrictMode>
<ChakraProvider value={defaultSystem}>
<ColorModeProvider>
<QueryClientProvider client={queryClient}>
<TimezoneProvider>
<RouterProvider router={router} />
</TimezoneProvider>
</QueryClientProvider>
</ColorModeProvider>
</ChakraProvider>
</StrictMode>,
);
84 changes: 47 additions & 37 deletions airflow/ui/src/pages/DagsList/Dag/Overview/Overview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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 (
Expand All @@ -62,35 +69,38 @@ export const Overview = () => {
startDate={startDate}
/>
</Box>
{failedTasks?.total_entries !== undefined &&
failedTasks.total_entries > 0 ? (
// TODO: make sure url params pass correctly
<Link to={`${location.pathname}/tasks?state=failed`}>
<HStack borderRadius={4} borderWidth={1} p={3} width="max-content">
<Badge
borderRadius="50%"
colorPalette={stateColor.failed}
variant="solid"
>
{failedTasks.total_entries}
</Badge>
<Text fontSize="sm" fontWeight="bold">
Failed{" "}
{pluralize("Task", failedTasks.total_entries, undefined, true)}
</Text>
<Chart
endDate={endDate}
events={failedTasks.task_instances.map((ti) => ({
timestamp: ti.start_date ?? ti.logical_date,
}))}
startDate={startDate}
/>
</HStack>
</Link>
) : undefined}
{isLoading ? (
<Skeleton borderRadius={4} height="45px" width="350px" />
) : undefined}
<HStack>
<TrendCountButton
colorPalette={stateColor.failed}
count={failedTasks?.total_entries ?? 0}
endDate={endDate}
events={(failedTasks?.task_instances ?? []).map((ti) => ({
timestamp: ti.start_date ?? ti.logical_date,
}))}
isLoading={isLoading}
label="Failed Task"
route={{
pathname: "tasks",
search: "state=failed",
}}
startDate={startDate}
/>
<TrendCountButton
colorPalette={stateColor.failed}
count={failedRuns?.total_entries ?? 0}
endDate={endDate}
events={(failedRuns?.dag_runs ?? []).map((dr) => ({
timestamp: dr.start_date ?? dr.logical_date ?? "",
}))}
isLoading={isLoadingRuns}
label="Failed Run"
route={{
pathname: "runs",
search: "state=failed",
}}
startDate={startDate}
/>
</HStack>
</Box>
);
};
Loading

0 comments on commit 2e8f456

Please sign in to comment.