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 />,

Reply via email to