This is an automated email from the ASF dual-hosted git repository.
bbovenzi pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git
The following commit(s) were added to refs/heads/main by this push:
new ddbdf2e3537 Add global events page to browse along with support to
display only events for the dag. (#43793)
ddbdf2e3537 is described below
commit ddbdf2e3537b9a2f5f38b0dc25448885c22aa527
Author: Karthikeyan Singaravelan <[email protected]>
AuthorDate: Mon Nov 11 20:33:26 2024 +0530
Add global events page to browse along with support to display only events
for the dag. (#43793)
* Add global events page to browse along with support to display only
events for the dag.
* Move column definition to a separate function outside of component due to
eslint rule.
---
airflow/api_fastapi/common/parameters.py | 8 +-
.../core_api/routes/public/event_logs.py | 1 +
airflow/ui/src/layouts/Nav/BrowseButton.tsx | 48 +++++++
airflow/ui/src/layouts/Nav/Nav.tsx | 16 +--
airflow/ui/src/pages/Events/Events.tsx | 143 +++++++++++++++++++++
airflow/ui/src/pages/Events/index.tsx | 20 +++
airflow/ui/src/router.tsx | 7 +-
7 files changed, 226 insertions(+), 17 deletions(-)
diff --git a/airflow/api_fastapi/common/parameters.py
b/airflow/api_fastapi/common/parameters.py
index 18630e473d7..1c701b26c73 100644
--- a/airflow/api_fastapi/common/parameters.py
+++ b/airflow/api_fastapi/common/parameters.py
@@ -177,13 +177,12 @@ class SortParam(BaseParam[str]):
}
def __init__(
- self,
- allowed_attrs: list[str],
- model: Base,
+ self, allowed_attrs: list[str], model: Base, to_replace: dict[str,
str] | None = None
) -> None:
super().__init__()
self.allowed_attrs = allowed_attrs
self.model = model
+ self.to_replace = to_replace
def to_orm(self, select: Select) -> Select:
if self.skip_none is False:
@@ -193,6 +192,9 @@ class SortParam(BaseParam[str]):
return select
lstriped_orderby = self.value.lstrip("-")
+ if self.to_replace:
+ lstriped_orderby = self.to_replace.get(lstriped_orderby,
lstriped_orderby)
+
if self.allowed_attrs and lstriped_orderby not in self.allowed_attrs:
raise HTTPException(
400,
diff --git a/airflow/api_fastapi/core_api/routes/public/event_logs.py
b/airflow/api_fastapi/core_api/routes/public/event_logs.py
index 1e63167c00d..007da65f28c 100644
--- a/airflow/api_fastapi/core_api/routes/public/event_logs.py
+++ b/airflow/api_fastapi/core_api/routes/public/event_logs.py
@@ -86,6 +86,7 @@ def get_event_logs(
"extra",
],
Log,
+ to_replace={"when": "dttm", "event_log_id": "id"},
).dynamic_depends()
),
],
diff --git a/airflow/ui/src/layouts/Nav/BrowseButton.tsx
b/airflow/ui/src/layouts/Nav/BrowseButton.tsx
new file mode 100644
index 00000000000..37e16a00332
--- /dev/null
+++ b/airflow/ui/src/layouts/Nav/BrowseButton.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 { Link } from "@chakra-ui/react";
+import { FiGlobe } from "react-icons/fi";
+
+import { Menu } from "src/components/ui";
+
+import { NavButton } from "./NavButton";
+
+const links = [
+ {
+ href: `/webapp/events`,
+ title: "Events",
+ },
+];
+
+export const BrowseButton = () => (
+ <Menu.Root positioning={{ placement: "right" }}>
+ <Menu.Trigger asChild>
+ <NavButton icon={<FiGlobe size="1.75rem" />} title="Browse" />
+ </Menu.Trigger>
+ <Menu.Content>
+ {links.map((link) => (
+ <Menu.Item asChild key={link.title} value={link.title}>
+ <Link aria-label={link.title} href={link.href}>
+ {link.title}
+ </Link>
+ </Menu.Item>
+ ))}
+ </Menu.Content>
+ </Menu.Root>
+);
diff --git a/airflow/ui/src/layouts/Nav/Nav.tsx
b/airflow/ui/src/layouts/Nav/Nav.tsx
index 4876f83303f..fb3559af970 100644
--- a/airflow/ui/src/layouts/Nav/Nav.tsx
+++ b/airflow/ui/src/layouts/Nav/Nav.tsx
@@ -17,17 +17,12 @@
* under the License.
*/
import { Box, Flex, VStack } from "@chakra-ui/react";
-import {
- FiCornerUpLeft,
- FiDatabase,
- FiGlobe,
- FiHome,
- FiSettings,
-} from "react-icons/fi";
+import { FiCornerUpLeft, FiDatabase, FiHome, FiSettings } from
"react-icons/fi";
import { AirflowPin } from "src/assets/AirflowPin";
import { DagIcon } from "src/assets/DagIcon";
+import { BrowseButton } from "./BrowseButton";
import { DocsButton } from "./DocsButton";
import { NavButton } from "./NavButton";
import { UserSettingsButton } from "./UserSettingsButton";
@@ -61,12 +56,7 @@ export const Nav = () => (
title="Assets"
to="assets"
/>
- <NavButton
- disabled
- icon={<FiGlobe size="1.75rem" />}
- title="Browse"
- to="browse"
- />
+ <BrowseButton />
<NavButton
disabled
icon={<FiSettings size="1.75rem" />}
diff --git a/airflow/ui/src/pages/Events/Events.tsx
b/airflow/ui/src/pages/Events/Events.tsx
new file mode 100644
index 00000000000..028a388894e
--- /dev/null
+++ b/airflow/ui/src/pages/Events/Events.tsx
@@ -0,0 +1,143 @@
+/*!
+ * 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";
+import type { ColumnDef } from "@tanstack/react-table";
+import { useParams } from "react-router-dom";
+
+import { useEventLogServiceGetEventLogs } from "openapi/queries";
+import type { EventLogResponse } 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";
+
+const eventsColumn = (
+ dagId: string | undefined,
+): Array<ColumnDef<EventLogResponse>> => [
+ {
+ accessorKey: "when",
+ cell: ({ row: { original } }) => <Time datetime={original.when} />,
+ enableSorting: true,
+ header: "When",
+ meta: {
+ skeletonWidth: 10,
+ },
+ },
+ ...(Boolean(dagId)
+ ? []
+ : [
+ {
+ accessorKey: "dag_id",
+ enableSorting: true,
+ header: "Dag ID",
+ meta: {
+ skeletonWidth: 10,
+ },
+ },
+ ]),
+ {
+ accessorKey: "run_id",
+ enableSorting: true,
+ header: "Run ID",
+ meta: {
+ skeletonWidth: 10,
+ },
+ },
+ {
+ accessorKey: "task_id",
+ enableSorting: true,
+ header: "Task ID",
+ meta: {
+ skeletonWidth: 10,
+ },
+ },
+ {
+ accessorKey: "map_index",
+ enableSorting: false,
+ header: "Map Index",
+ meta: {
+ skeletonWidth: 10,
+ },
+ },
+ {
+ accessorKey: "try_number",
+ enableSorting: false,
+ header: "Try Number",
+ meta: {
+ skeletonWidth: 10,
+ },
+ },
+ {
+ accessorKey: "event",
+ enableSorting: true,
+ header: "Event",
+ meta: {
+ skeletonWidth: 10,
+ },
+ },
+ {
+ accessorKey: "owner",
+ enableSorting: true,
+ header: "User",
+ meta: {
+ skeletonWidth: 10,
+ },
+ },
+];
+
+export const Events = () => {
+ const { dagId } = useParams();
+ const { setTableURLState, tableURLState } = useTableURLState({
+ sorting: [{ desc: true, id: "when" }],
+ });
+ const { pagination, sorting } = tableURLState;
+ const [sort] = sorting;
+
+ const orderBy = sort ? `${sort.desc ? "-" : ""}${sort.id}` : undefined;
+
+ const {
+ data,
+ error: EventsError,
+ isFetching,
+ isLoading,
+ } = useEventLogServiceGetEventLogs({
+ dagId,
+ limit: pagination.pageSize,
+ offset: pagination.pageIndex * pagination.pageSize,
+ orderBy,
+ });
+
+ return (
+ <Box>
+ <ErrorAlert error={EventsError} />
+ <DataTable
+ columns={eventsColumn(dagId)}
+ data={data ? data.event_logs : []}
+ displayMode="table"
+ initialState={tableURLState}
+ isFetching={isFetching}
+ isLoading={isLoading}
+ modelName="Event"
+ onStateChange={setTableURLState}
+ skeletonCount={undefined}
+ total={data ? data.total_entries : 0}
+ />
+ </Box>
+ );
+};
diff --git a/airflow/ui/src/pages/Events/index.tsx
b/airflow/ui/src/pages/Events/index.tsx
new file mode 100644
index 00000000000..42556eaf925
--- /dev/null
+++ b/airflow/ui/src/pages/Events/index.tsx
@@ -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 { Events } from "./Events";
diff --git a/airflow/ui/src/router.tsx b/airflow/ui/src/router.tsx
index 39c85b647a1..e8585e1811e 100644
--- a/airflow/ui/src/router.tsx
+++ b/airflow/ui/src/router.tsx
@@ -25,6 +25,7 @@ import { BaseLayout } from "./layouts/BaseLayout";
import { Dag } from "./pages/DagsList/Dag";
import { Code } from "./pages/DagsList/Dag/Code";
import { ErrorPage } from "./pages/Error";
+import { Events } from "./pages/Events";
export const router = createBrowserRouter(
[
@@ -38,12 +39,16 @@ export const router = createBrowserRouter(
element: <DagsList />,
path: "dags",
},
+ {
+ element: <Events />,
+ path: "events",
+ },
{
children: [
{ element: <div>Overview</div>, path: "" },
{ element: <div>Runs</div>, path: "runs" },
{ element: <div>Tasks</div>, path: "tasks" },
- { element: <div>Events</div>, path: "events" },
+ { element: <Events />, path: "events" },
{ element: <Code />, path: "code" },
],
element: <Dag />,