Skip to content

Commit

Permalink
Flesh out a basic task instance details page
Browse files Browse the repository at this point in the history
  • Loading branch information
bbovenzi committed Dec 4, 2024
1 parent a0d2d9c commit 249d05a
Show file tree
Hide file tree
Showing 7 changed files with 226 additions and 28 deletions.
2 changes: 1 addition & 1 deletion airflow/ui/src/pages/DagsList/Dag/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export const Header = ({
readonly dagId?: string;
readonly latestRun?: DAGRunResponse;
}) => (
<Box borderColor="border" borderRadius={8} borderWidth={1} overflow="hidden">
<Box borderColor="border" borderRadius={8} borderWidth={1}>
<Box p={2}>
<Flex alignItems="center" justifyContent="space-between">
<HStack alignItems="center" gap={2}>
Expand Down
2 changes: 1 addition & 1 deletion airflow/ui/src/pages/DagsList/Run/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import Time from "src/components/Time";
import { Status } from "src/components/ui";

export const Header = ({ dagRun }: { readonly dagRun: DAGRunResponse }) => (
<Box borderColor="border" borderRadius={8} borderWidth={1} overflow="hidden">
<Box borderColor="border" borderRadius={8} borderWidth={1}>
<Box p={2}>
<Flex alignItems="center" justifyContent="space-between" mb={2}>
<HStack alignItems="center" gap={2}>
Expand Down
86 changes: 86 additions & 0 deletions airflow/ui/src/pages/DagsList/TaskInstance/Header.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/*!
* 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;
}) => (
<Box borderColor="border" borderRadius={8} borderWidth={1}>
<Box p={2}>
<Flex alignItems="center" justifyContent="space-between" mb={2}>
<HStack alignItems="center" gap={2}>
<MdOutlineTask size="1.75rem" />
<Heading size="lg">
<strong>Task Instance: </strong>
{taskInstance.task_display_name}{" "}
<Time datetime={taskInstance.start_date} />
</Heading>
<Status state={taskInstance.state ?? undefined}>
{taskInstance.state}
</Status>
<Flex>
<div />
</Flex>
</HStack>
</Flex>
{taskInstance.note === null ||
taskInstance.note.length === 0 ? undefined : (
<Flex alignItems="flex-start" justifyContent="space-between" mr={16}>
<MdOutlineModeComment size="3rem" />
<Text fontSize="sm" ml={3}>
{taskInstance.note}
</Text>
</Flex>
)}
<SimpleGrid columns={4} gap={4} my={2}>
<Stat label="Operator">{taskInstance.operator}</Stat>
{taskInstance.map_index > -1 ? (
<Stat label="Map Index">{taskInstance.map_index}</Stat>
) : undefined}
{taskInstance.try_number > 1 ? (
<Stat label="Try Number">{taskInstance.try_number}</Stat>
) : undefined}
<Stat label="Start">
<Time datetime={taskInstance.start_date} />
</Stat>
<Stat label="End">
<Time datetime={taskInstance.end_date} />
</Stat>
<Stat label="Duration">
{dayjs
.duration(
dayjs(taskInstance.end_date).diff(taskInstance.start_date),
)
.asSeconds()
.toFixed(2)}
s
</Stat>
</SimpleGrid>
</Box>
</Box>
);
72 changes: 72 additions & 0 deletions airflow/ui/src/pages/DagsList/TaskInstance/Tabs.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/*!
* 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 } from "@chakra-ui/react";
import { useSearchParams } from "react-router-dom";

import type { DAGResponse } from "openapi/requests/types.gen";
import { DagIcon } from "src/assets/DagIcon";
import { DagVizModal } from "src/components/DagVizModal";
import { NavTabs } from "src/components/NavTabs";

const tabs = [
{ label: "Logs", value: "" },
{ label: "Events", value: "events" },
{ label: "XCom", value: "xcom" },
{ label: "Code", value: "code" },
{ label: "Details", value: "details" },
];

const MODAL = "modal";

export const TaskInstanceTabs = ({ dag }: { readonly dag?: DAGResponse }) => {
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 (
<>
<NavTabs
rightButtons={
<Button colorPalette="blue" onClick={onOpen} variant="ghost">
<DagIcon height={5} width={5} />
Graph
</Button>
}
tabs={tabs}
/>
<DagVizModal
dagDisplayName={dag?.dag_display_name}
dagId={dag?.dag_id}
onClose={onClose}
open={isGraphOpen}
/>
</>
);
};
56 changes: 42 additions & 14 deletions airflow/ui/src/pages/DagsList/TaskInstance/TaskInstance.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,26 +16,54 @@
* specific language governing permissions and limitations
* under the License.
*/
import { Box } from "@chakra-ui/react";
import { LiaSlashSolid } from "react-icons/lia";
import { useParams, Link as RouterLink } from "react-router-dom";
import { useParams, Link as RouterLink, Outlet } from "react-router-dom";

import {
useDagServiceGetDagDetails,
useTaskInstanceServiceGetTaskInstance,
} from "openapi/queries";
import { Breadcrumb } from "src/components/ui";
import { OpenGroupsProvider } from "src/context/openGroups";

import { Header } from "./Header";
import { TaskInstanceTabs } from "./Tabs";

export const TaskInstance = () => {
const { dagId, runId, taskId } = useParams();
const { dagId = "", runId = "", taskId = "" } = useParams();

const { data: dag } = useDagServiceGetDagDetails({
dagId,
});

const { data: taskInstance } = useTaskInstanceServiceGetTaskInstance({
dagId,
dagRunId: runId,
taskId,
});

return (
<Breadcrumb.Root mb={3} separator={<LiaSlashSolid />}>
<Breadcrumb.Link asChild color="fg.info">
<RouterLink to="/dags">Dags</RouterLink>
</Breadcrumb.Link>
<Breadcrumb.Link asChild color="fg.info">
<RouterLink to={`/dags/${dagId}`}>{dagId}</RouterLink>
</Breadcrumb.Link>
<Breadcrumb.Link asChild color="fg.info">
<RouterLink to={`/dags/${dagId}/runs/${runId}`}>{runId}</RouterLink>
</Breadcrumb.Link>
<Breadcrumb.CurrentLink>{taskId}</Breadcrumb.CurrentLink>
</Breadcrumb.Root>
<OpenGroupsProvider dagId={dagId}>
<Breadcrumb.Root mb={3} separator={<LiaSlashSolid />}>
<Breadcrumb.Link asChild color="fg.info">
<RouterLink to="/dags">Dags</RouterLink>
</Breadcrumb.Link>
<Breadcrumb.Link asChild color="fg.info">
<RouterLink to={`/dags/${dagId}`}>{dagId}</RouterLink>
</Breadcrumb.Link>
<Breadcrumb.Link asChild color="fg.info">
<RouterLink to={`/dags/${dagId}/runs/${runId}`}>{runId}</RouterLink>
</Breadcrumb.Link>
<Breadcrumb.CurrentLink>{taskId}</Breadcrumb.CurrentLink>
</Breadcrumb.Root>
{taskInstance === undefined ? undefined : (
<Header taskInstance={taskInstance} />
)}
<TaskInstanceTabs dag={dag} />
<Box overflow="auto">
<Outlet />
</Box>
</OpenGroupsProvider>
);
};
29 changes: 17 additions & 12 deletions airflow/ui/src/pages/Events/Events.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,9 @@ import { ErrorAlert } from "src/components/ErrorAlert";
import Time from "src/components/Time";

const eventsColumn = (
dagId: string | undefined,
runId: string | undefined,
dagId?: string,
runId?: string,
taskId?: string,
): Array<ColumnDef<EventLogResponse>> => [
{
accessorKey: "when",
Expand Down Expand Up @@ -64,14 +65,18 @@ const eventsColumn = (
},
},
]),
{
accessorKey: "task_id",
enableSorting: true,
header: "Task ID",
meta: {
skeletonWidth: 10,
},
},
...(Boolean(taskId)
? []
: [
{
accessorKey: "task_id",
enableSorting: true,
header: "Task ID",
meta: {
skeletonWidth: 10,
},
},
]),
{
accessorKey: "map_index",
enableSorting: false,
Expand Down Expand Up @@ -107,7 +112,7 @@ const eventsColumn = (
];

export const Events = () => {
const { dagId, runId } = useParams();
const { dagId, runId, taskId } = useParams();
const { setTableURLState, tableURLState } = useTableURLState({
sorting: [{ desc: true, id: "when" }],
});
Expand All @@ -133,7 +138,7 @@ export const Events = () => {
<Box>
<ErrorAlert error={EventsError} />
<DataTable
columns={eventsColumn(dagId, runId)}
columns={eventsColumn(dagId, runId, taskId)}
data={data ? data.event_logs : []}
displayMode="table"
initialState={tableURLState}
Expand Down
7 changes: 7 additions & 0 deletions airflow/ui/src/router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,13 @@ export const router = createBrowserRouter(
path: "dags/:dagId/runs/:runId",
},
{
children: [
{ element: <div>Logs</div>, index: true },
{ element: <Events />, path: "events" },
{ element: <div>Xcom</div>, path: "xcom" },
{ element: <Code />, path: "code" },
{ element: <div>Details</div>, path: "details" },
],
element: <TaskInstance />,
path: "dags/:dagId/runs/:runId/tasks/:taskId",
},
Expand Down

0 comments on commit 249d05a

Please sign in to comment.