diff --git a/airflow-core/src/airflow/ui/public/i18n/locales/en/browse.json b/airflow-core/src/airflow/ui/public/i18n/locales/en/browse.json
index 4a2ab97354b16..07259fd1e5825 100644
--- a/airflow-core/src/airflow/ui/public/i18n/locales/en/browse.json
+++ b/airflow-core/src/airflow/ui/public/i18n/locales/en/browse.json
@@ -10,6 +10,9 @@
"user": "User",
"when": "When"
},
+ "filters": {
+ "eventType": "Event Type"
+ },
"title": "Audit Log"
},
"xcom": {
diff --git a/airflow-core/src/airflow/ui/src/pages/DagsList/DagsFilters/ResetButton.tsx b/airflow-core/src/airflow/ui/src/components/ui/ResetButton.tsx
similarity index 100%
rename from airflow-core/src/airflow/ui/src/pages/DagsList/DagsFilters/ResetButton.tsx
rename to airflow-core/src/airflow/ui/src/components/ui/ResetButton.tsx
diff --git a/airflow-core/src/airflow/ui/src/components/ui/index.ts b/airflow-core/src/airflow/ui/src/components/ui/index.ts
index 2146daedb7ac0..b7695029c4ea8 100644
--- a/airflow-core/src/airflow/ui/src/components/ui/index.ts
+++ b/airflow-core/src/airflow/ui/src/components/ui/index.ts
@@ -34,3 +34,4 @@ export * from "./Breadcrumb";
export * from "./Clipboard";
export * from "./Popover";
export * from "./Checkbox";
+export * from "./ResetButton";
diff --git a/airflow-core/src/airflow/ui/src/constants/searchParams.ts b/airflow-core/src/airflow/ui/src/constants/searchParams.ts
index d333695251df4..05165124950e6 100644
--- a/airflow-core/src/airflow/ui/src/constants/searchParams.ts
+++ b/airflow-core/src/airflow/ui/src/constants/searchParams.ts
@@ -17,11 +17,17 @@
* under the License.
*/
export enum SearchParamsKeys {
+ AFTER = "after",
+ BEFORE = "before",
DAG_DISPLAY_NAME_PATTERN = "dag_display_name_pattern",
+ DAG_ID = "dag_id",
DAG_ID_PATTERN = "dag_id_pattern",
DEPENDENCIES = "dependencies",
END_DATE = "end_date",
+ EVENT_TYPE = "event_type",
+ EXCLUDED_EVENTS = "excluded_events",
FAVORITE = "favorite",
+ INCLUDED_EVENTS = "included_events",
KEY_PATTERN = "key_pattern",
LAST_DAG_RUN_STATE = "last_dag_run_state",
LIMIT = "limit",
@@ -37,6 +43,7 @@ export enum SearchParamsKeys {
RESPONSE_RECEIVED = "response_received",
RUN_AFTER_GTE = "run_after_gte",
RUN_AFTER_LTE = "run_after_lte",
+ RUN_ID = "run_id",
RUN_ID_PATTERN = "run_id_pattern",
RUN_TYPE = "run_type",
SORT = "sort",
@@ -45,9 +52,11 @@ export enum SearchParamsKeys {
STATE = "state",
TAGS = "tags",
TAGS_MATCH_MODE = "tags_match_mode",
+ TASK_ID = "task_id",
TASK_ID_PATTERN = "task_id_pattern",
TRIGGERING_USER_NAME_PATTERN = "triggering_user_name_pattern",
TRY_NUMBER = "try_number",
+ USER = "user",
VERSION_NUMBER = "version_number",
}
diff --git a/airflow-core/src/airflow/ui/src/pages/DagsList/DagsFilters/DagsFilters.tsx b/airflow-core/src/airflow/ui/src/pages/DagsList/DagsFilters/DagsFilters.tsx
index bb84ed99fcfd6..f4e6656a1b448 100644
--- a/airflow-core/src/airflow/ui/src/pages/DagsList/DagsFilters/DagsFilters.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/DagsList/DagsFilters/DagsFilters.tsx
@@ -22,13 +22,13 @@ import { useCallback, useState } from "react";
import { useSearchParams } from "react-router-dom";
import { useTableURLState } from "src/components/DataTable/useTableUrlState";
+import { ResetButton } from "src/components/ui";
import { SearchParamsKeys, type SearchParamsKeysType } from "src/constants/searchParams";
import { useConfig } from "src/queries/useConfig";
import { useDagTagsInfinite } from "src/queries/useDagTagsInfinite";
import { FavoriteFilter } from "./FavoriteFilter";
import { PausedFilter } from "./PausedFilter";
-import { ResetButton } from "./ResetButton";
import { StateFilters } from "./StateFilters";
import { TagFilter } from "./TagFilter";
diff --git a/airflow-core/src/airflow/ui/src/pages/Events/Events.tsx b/airflow-core/src/airflow/ui/src/pages/Events/Events.tsx
index d804ee30686e5..334f6c5e91773 100644
--- a/airflow-core/src/airflow/ui/src/pages/Events/Events.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/Events/Events.tsx
@@ -16,11 +16,12 @@
* specific language governing permissions and limitations
* under the License.
*/
-import { Box, ButtonGroup, Code, Flex, Heading, IconButton, useDisclosure } from "@chakra-ui/react";
+import { ButtonGroup, Code, Flex, Heading, IconButton, useDisclosure, VStack } from "@chakra-ui/react";
import type { ColumnDef } from "@tanstack/react-table";
+import dayjs from "dayjs";
import { useTranslation } from "react-i18next";
import { MdCompress, MdExpand } from "react-icons/md";
-import { useParams } from "react-router-dom";
+import { useParams, useSearchParams } from "react-router-dom";
import { useEventLogServiceGetEventLogs } from "openapi/queries";
import type { EventLogResponse } from "openapi/requests/types.gen";
@@ -29,6 +30,9 @@ import { useTableURLState } from "src/components/DataTable/useTableUrlState";
import { ErrorAlert } from "src/components/ErrorAlert";
import RenderedJsonField from "src/components/RenderedJsonField";
import Time from "src/components/Time";
+import { SearchParamsKeys, type SearchParamsKeysType } from "src/constants/searchParams";
+
+import { EventsFilters } from "./EventsFilters";
type EventsColumn = {
dagId?: string;
@@ -141,31 +145,66 @@ const eventsColumn = (
},
];
+const {
+ AFTER: AFTER_PARAM,
+ BEFORE: BEFORE_PARAM,
+ DAG_ID: DAG_ID_PARAM,
+ EVENT_TYPE: EVENT_TYPE_PARAM,
+ MAP_INDEX: MAP_INDEX_PARAM,
+ RUN_ID: RUN_ID_PARAM,
+ TASK_ID: TASK_ID_PARAM,
+ TRY_NUMBER: TRY_NUMBER_PARAM,
+ USER: USER_PARAM,
+}: SearchParamsKeysType = SearchParamsKeys;
+
export const Events = () => {
const { t: translate } = useTranslation("browse");
const { dagId, runId, taskId } = useParams();
+ const [searchParams] = useSearchParams();
const { setTableURLState, tableURLState } = useTableURLState();
const { pagination, sorting } = tableURLState;
const [sort] = sorting;
const { onClose, onOpen, open } = useDisclosure();
+ const afterFilter = searchParams.get(AFTER_PARAM);
+ const beforeFilter = searchParams.get(BEFORE_PARAM);
+ const dagIdFilter = searchParams.get(DAG_ID_PARAM);
+ const eventTypeFilter = searchParams.get(EVENT_TYPE_PARAM);
+ const mapIndexFilter = searchParams.get(MAP_INDEX_PARAM);
+ const runIdFilter = searchParams.get(RUN_ID_PARAM);
+ const taskIdFilter = searchParams.get(TASK_ID_PARAM);
+ const tryNumberFilter = searchParams.get(TRY_NUMBER_PARAM);
+ const userFilter = searchParams.get(USER_PARAM);
+
const orderBy = sort ? [`${sort.desc ? "-" : ""}${sort.id}`] : ["-when"];
+ // Convert string filters to appropriate types for API
+ const mapIndexNumber = mapIndexFilter === null ? undefined : parseInt(mapIndexFilter, 10);
+ const tryNumberNumber = tryNumberFilter === null ? undefined : parseInt(tryNumberFilter, 10);
+ // Handle date conversion - ensure valid ISO strings
+ const afterDate = afterFilter !== null && dayjs(afterFilter).isValid() ? afterFilter : undefined;
+ const beforeDate = beforeFilter !== null && dayjs(beforeFilter).isValid() ? beforeFilter : undefined;
const { data, error, isFetching, isLoading } = useEventLogServiceGetEventLogs(
{
- dagId,
+ after: afterDate,
+ before: beforeDate,
+ dagId: dagId ?? dagIdFilter ?? undefined,
+ event: eventTypeFilter ?? undefined,
limit: pagination.pageSize,
+ mapIndex: mapIndexNumber,
offset: pagination.pageIndex * pagination.pageSize,
orderBy,
- runId,
- taskId,
+ owner: userFilter ?? undefined,
+ runId: runId ?? runIdFilter ?? undefined,
+ taskId: taskId ?? taskIdFilter ?? undefined,
+ tryNumber: tryNumberNumber,
},
undefined,
{ enabled: !isNaN(pagination.pageSize) },
);
return (
-
+
{dagId === undefined && runId === undefined && taskId === undefined ? (
{translate("auditLog.title")}
@@ -191,10 +230,13 @@ export const Events = () => {
+
+
+
{
modelName={translate("auditLog.columns.event")}
onStateChange={setTableURLState}
skeletonCount={undefined}
- total={data ? data.total_entries : 0}
+ total={data?.total_entries ?? 0}
/>
-
+
);
};
diff --git a/airflow-core/src/airflow/ui/src/pages/Events/EventsFilters.tsx b/airflow-core/src/airflow/ui/src/pages/Events/EventsFilters.tsx
new file mode 100644
index 0000000000000..63707db09c2b8
--- /dev/null
+++ b/airflow-core/src/airflow/ui/src/pages/Events/EventsFilters.tsx
@@ -0,0 +1,232 @@
+/*!
+ * 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, HStack, VStack, Text } from "@chakra-ui/react";
+import { useCallback } from "react";
+import { useTranslation } from "react-i18next";
+import { useSearchParams } from "react-router-dom";
+
+import { useTableURLState } from "src/components/DataTable/useTableUrlState";
+import { DateTimeInput } from "src/components/DateTimeInput";
+import { SearchBar } from "src/components/SearchBar";
+import { ResetButton } from "src/components/ui";
+import { SearchParamsKeys } from "src/constants/searchParams";
+
+const {
+ AFTER: AFTER_PARAM,
+ BEFORE: BEFORE_PARAM,
+ DAG_ID: DAG_ID_PARAM,
+ EVENT_TYPE: EVENT_TYPE_PARAM,
+ MAP_INDEX: MAP_INDEX_PARAM,
+ RUN_ID: RUN_ID_PARAM,
+ TASK_ID: TASK_ID_PARAM,
+ TRY_NUMBER: TRY_NUMBER_PARAM,
+ USER: USER_PARAM,
+} = SearchParamsKeys;
+
+type EventsFiltersProps = {
+ readonly urlDagId?: string;
+ readonly urlRunId?: string;
+ readonly urlTaskId?: string;
+};
+
+export const EventsFilters = ({ urlDagId, urlRunId, urlTaskId }: EventsFiltersProps) => {
+ const { t: translate } = useTranslation(["browse", "common", "components"]);
+ const [searchParams, setSearchParams] = useSearchParams();
+ const { setTableURLState, tableURLState } = useTableURLState();
+
+ const { pagination, sorting } = tableURLState;
+
+ const resetPagination = useCallback(() => {
+ setTableURLState({
+ pagination: { ...pagination, pageIndex: 0 },
+ sorting,
+ });
+ }, [pagination, setTableURLState, sorting]);
+
+ const afterFilter = searchParams.get(AFTER_PARAM);
+ const beforeFilter = searchParams.get(BEFORE_PARAM);
+ const dagIdFilter = searchParams.get(DAG_ID_PARAM);
+ const eventTypeFilter = searchParams.get(EVENT_TYPE_PARAM);
+ const mapIndexFilter = searchParams.get(MAP_INDEX_PARAM);
+ const runIdFilter = searchParams.get(RUN_ID_PARAM);
+ const taskIdFilter = searchParams.get(TASK_ID_PARAM);
+ const tryNumberFilter = searchParams.get(TRY_NUMBER_PARAM);
+ const userFilter = searchParams.get(USER_PARAM);
+
+ const updateSearchParams = useCallback(
+ (paramName: string, value: string) => {
+ if (value) {
+ searchParams.set(paramName, value);
+ } else {
+ searchParams.delete(paramName);
+ }
+ resetPagination();
+ setSearchParams(searchParams);
+ },
+ [resetPagination, searchParams, setSearchParams],
+ );
+
+ const handleClearFilters = useCallback(() => {
+ // Clear all URL params
+ resetPagination();
+ setSearchParams(new URLSearchParams());
+ }, [resetPagination, setSearchParams]);
+
+ const handleSearchChange = useCallback(
+ (paramName: string) => (value: string) => {
+ updateSearchParams(paramName, value);
+ },
+ [updateSearchParams],
+ );
+
+ const handleDateTimeChange = useCallback(
+ (paramName: string) => (event: React.ChangeEvent) => {
+ const { value } = event.target;
+
+ updateSearchParams(paramName, value);
+ },
+ [updateSearchParams],
+ );
+
+ const filterCount = searchParams.size;
+
+ return (
+
+
+ {/* Timestamp Range Filters */}
+
+
+ {translate("components:backfill.dateRangeFrom")}
+
+
+
+
+
+ {translate("components:backfill.dateRangeTo")}
+
+
+
+
+ {/* Event Type Filter */}
+
+
+
+
+ {/* User Filter */}
+
+
+
+
+ {/* DAG ID Filter - Hide if URL already has dagId */}
+ {urlDagId === undefined && (
+
+
+
+ )}
+
+ {/* Task ID Filter - Hide if URL already has taskId */}
+ {urlTaskId === undefined && (
+
+
+
+ )}
+
+ {/* Run ID Filter - Hide if URL already has runId */}
+ {urlRunId === undefined && (
+
+
+
+ )}
+
+ {/* Map Index Filter */}
+
+
+
+
+ {/* Try Number Filter */}
+
+
+
+
+
+
+
+
+
+ );
+};