diff --git a/airflow-core/src/airflow/api_fastapi/auth/managers/models/base_user.py b/airflow-core/src/airflow/api_fastapi/auth/managers/models/base_user.py index 7f060f3121e8c..c94ee0c1d8d94 100644 --- a/airflow-core/src/airflow/api_fastapi/auth/managers/models/base_user.py +++ b/airflow-core/src/airflow/api_fastapi/auth/managers/models/base_user.py @@ -28,3 +28,12 @@ def get_id(self) -> str: ... @abstractmethod def get_name(self) -> str: ... + + def get_display_name(self) -> str: + """ + Return a human-friendly display name for the user. + + Not abstract for backward compatibility reasons: auth managers written + before this method existed keep working without any change. + """ + return self.get_name() diff --git a/airflow-core/src/airflow/api_fastapi/core_api/openapi/v2-rest-api-generated.yaml b/airflow-core/src/airflow/api_fastapi/core_api/openapi/v2-rest-api-generated.yaml index d5807bfec278b..fea2773828ced 100644 --- a/airflow-core/src/airflow/api_fastapi/core_api/openapi/v2-rest-api-generated.yaml +++ b/airflow-core/src/airflow/api_fastapi/core_api/openapi/v2-rest-api-generated.yaml @@ -4644,6 +4644,20 @@ paths: description: "Case-insensitive substring match (SQL `ILIKE`). Slower than\ \ `owner_prefix_pattern` on large tables \u2014 see \"Filtering with pattern\ \ parameters\"." + - name: owner_display_name_pattern + in: query + required: false + schema: + anyOf: + - type: string + - type: 'null' + description: "Case-insensitive substring match (SQL `ILIKE`). Slower than\ + \ `owner_display_name_prefix_pattern` on large tables \u2014 see \"Filtering\ + \ with pattern parameters\"." + title: Owner Display Name Pattern + description: "Case-insensitive substring match (SQL `ILIKE`). Slower than\ + \ `owner_display_name_prefix_pattern` on large tables \u2014 see \"Filtering\ + \ with pattern parameters\"." - name: event_pattern in: query required: false @@ -4706,6 +4720,18 @@ paths: title: Owner Prefix Pattern description: Case-sensitive, index-friendly prefix match. See "Filtering with pattern parameters". + - name: owner_display_name_prefix_pattern + in: query + required: false + schema: + anyOf: + - type: string + - type: 'null' + description: Case-sensitive, index-friendly prefix match. See "Filtering + with pattern parameters". + title: Owner Display Name Prefix Pattern + description: Case-sensitive, index-friendly prefix match. See "Filtering with + pattern parameters". - name: event_prefix_pattern in: query required: false diff --git a/airflow-core/src/airflow/api_fastapi/core_api/routes/public/event_logs.py b/airflow-core/src/airflow/api_fastapi/core_api/routes/public/event_logs.py index 881f9f5db1a3d..eb0c4009814c1 100644 --- a/airflow-core/src/airflow/api_fastapi/core_api/routes/public/event_logs.py +++ b/airflow-core/src/airflow/api_fastapi/core_api/routes/public/event_logs.py @@ -141,6 +141,10 @@ def get_event_logs( task_id_pattern: Annotated[_SearchParam, Depends(search_param_factory(Log.task_id, "task_id_pattern"))], run_id_pattern: Annotated[_SearchParam, Depends(search_param_factory(Log.run_id, "run_id_pattern"))], owner_pattern: Annotated[_SearchParam, Depends(search_param_factory(Log.owner, "owner_pattern"))], + owner_display_name_pattern: Annotated[ + _SearchParam, + Depends(search_param_factory(Log.owner_display_name, "owner_display_name_pattern")), + ], event_pattern: Annotated[_SearchParam, Depends(search_param_factory(Log.event, "event_pattern"))], # Prefix pattern search filters (index-friendly, case-sensitive) dag_id_prefix_pattern: Annotated[ @@ -159,6 +163,10 @@ def get_event_logs( _PrefixSearchParam, Depends(prefix_search_param_factory(Log.owner, "owner_prefix_pattern")), ], + owner_display_name_prefix_pattern: Annotated[ + _PrefixSearchParam, + Depends(prefix_search_param_factory(Log.owner_display_name, "owner_display_name_prefix_pattern")), + ], event_prefix_pattern: Annotated[ _PrefixSearchParam, Depends(prefix_search_param_factory(Log.event, "event_prefix_pattern")), @@ -202,6 +210,8 @@ def get_event_logs( run_id_prefix_pattern, owner_pattern, owner_prefix_pattern, + owner_display_name_pattern, + owner_display_name_prefix_pattern, event_pattern, event_prefix_pattern, # Permission diff --git a/airflow-core/src/airflow/api_fastapi/logging/decorators.py b/airflow-core/src/airflow/api_fastapi/logging/decorators.py index f32a2bffc707f..7c32c70a5ca3d 100644 --- a/airflow-core/src/airflow/api_fastapi/logging/decorators.py +++ b/airflow-core/src/airflow/api_fastapi/logging/decorators.py @@ -98,7 +98,7 @@ async def log_action( user_display = "" else: user_name = user.get_name() - user_display = user.get_name() + user_display = user.get_display_name() has_json_body = "application/json" in request.headers.get("content-type", "") and await request.body() request_body = {} diff --git a/airflow-core/src/airflow/ui/openapi-gen/queries/common.ts b/airflow-core/src/airflow/ui/openapi-gen/queries/common.ts index 05a4b47ec1bb3..2761037fcc2fd 100644 --- a/airflow-core/src/airflow/ui/openapi-gen/queries/common.ts +++ b/airflow-core/src/airflow/ui/openapi-gen/queries/common.ts @@ -389,7 +389,7 @@ export const UseEventLogServiceGetEventLogKeyFn = ({ eventLogId }: { export type EventLogServiceGetEventLogsDefaultResponse = Awaited>; export type EventLogServiceGetEventLogsQueryResult = UseQueryResult; export const useEventLogServiceGetEventLogsKey = "EventLogServiceGetEventLogs"; -export const UseEventLogServiceGetEventLogsKeyFn = ({ after, before, dagId, dagIdPattern, dagIdPrefixPattern, event, eventPattern, eventPrefixPattern, excludedEvents, includedEvents, limit, mapIndex, offset, orderBy, owner, ownerPattern, ownerPrefixPattern, runId, runIdPattern, runIdPrefixPattern, taskId, taskIdPattern, taskIdPrefixPattern, tryNumber }: { +export const UseEventLogServiceGetEventLogsKeyFn = ({ after, before, dagId, dagIdPattern, dagIdPrefixPattern, event, eventPattern, eventPrefixPattern, excludedEvents, includedEvents, limit, mapIndex, offset, orderBy, owner, ownerDisplayNamePattern, ownerDisplayNamePrefixPattern, ownerPattern, ownerPrefixPattern, runId, runIdPattern, runIdPrefixPattern, taskId, taskIdPattern, taskIdPrefixPattern, tryNumber }: { after?: string; before?: string; dagId?: string; @@ -405,6 +405,8 @@ export const UseEventLogServiceGetEventLogsKeyFn = ({ after, before, dagId, dagI offset?: number; orderBy?: string[]; owner?: string; + ownerDisplayNamePattern?: string; + ownerDisplayNamePrefixPattern?: string; ownerPattern?: string; ownerPrefixPattern?: string; runId?: string; @@ -414,7 +416,7 @@ export const UseEventLogServiceGetEventLogsKeyFn = ({ after, before, dagId, dagI taskIdPattern?: string; taskIdPrefixPattern?: string; tryNumber?: number; -} = {}, queryKey?: Array) => [useEventLogServiceGetEventLogsKey, ...(queryKey ?? [{ after, before, dagId, dagIdPattern, dagIdPrefixPattern, event, eventPattern, eventPrefixPattern, excludedEvents, includedEvents, limit, mapIndex, offset, orderBy, owner, ownerPattern, ownerPrefixPattern, runId, runIdPattern, runIdPrefixPattern, taskId, taskIdPattern, taskIdPrefixPattern, tryNumber }])]; +} = {}, queryKey?: Array) => [useEventLogServiceGetEventLogsKey, ...(queryKey ?? [{ after, before, dagId, dagIdPattern, dagIdPrefixPattern, event, eventPattern, eventPrefixPattern, excludedEvents, includedEvents, limit, mapIndex, offset, orderBy, owner, ownerDisplayNamePattern, ownerDisplayNamePrefixPattern, ownerPattern, ownerPrefixPattern, runId, runIdPattern, runIdPrefixPattern, taskId, taskIdPattern, taskIdPrefixPattern, tryNumber }])]; export type ExtraLinksServiceGetExtraLinksDefaultResponse = Awaited>; export type ExtraLinksServiceGetExtraLinksQueryResult = UseQueryResult; export const useExtraLinksServiceGetExtraLinksKey = "ExtraLinksServiceGetExtraLinks"; diff --git a/airflow-core/src/airflow/ui/openapi-gen/queries/ensureQueryData.ts b/airflow-core/src/airflow/ui/openapi-gen/queries/ensureQueryData.ts index a97bde3062b0d..f435ab903023e 100644 --- a/airflow-core/src/airflow/ui/openapi-gen/queries/ensureQueryData.ts +++ b/airflow-core/src/airflow/ui/openapi-gen/queries/ensureQueryData.ts @@ -758,16 +758,18 @@ export const ensureUseEventLogServiceGetEventLogData = (queryClient: QueryClient * @param data.taskIdPattern Case-insensitive substring match (SQL `ILIKE`). Slower than `task_id_prefix_pattern` on large tables — see "Filtering with pattern parameters". * @param data.runIdPattern Case-insensitive substring match (SQL `ILIKE`). Slower than `run_id_prefix_pattern` on large tables — see "Filtering with pattern parameters". * @param data.ownerPattern Case-insensitive substring match (SQL `ILIKE`). Slower than `owner_prefix_pattern` on large tables — see "Filtering with pattern parameters". +* @param data.ownerDisplayNamePattern Case-insensitive substring match (SQL `ILIKE`). Slower than `owner_display_name_prefix_pattern` on large tables — see "Filtering with pattern parameters". * @param data.eventPattern Case-insensitive substring match (SQL `ILIKE`). Slower than `event_prefix_pattern` on large tables — see "Filtering with pattern parameters". * @param data.dagIdPrefixPattern Case-sensitive, index-friendly prefix match. See "Filtering with pattern parameters". * @param data.taskIdPrefixPattern Case-sensitive, index-friendly prefix match. See "Filtering with pattern parameters". * @param data.runIdPrefixPattern Case-sensitive, index-friendly prefix match. See "Filtering with pattern parameters". * @param data.ownerPrefixPattern Case-sensitive, index-friendly prefix match. See "Filtering with pattern parameters". +* @param data.ownerDisplayNamePrefixPattern Case-sensitive, index-friendly prefix match. See "Filtering with pattern parameters". * @param data.eventPrefixPattern Case-sensitive, index-friendly prefix match. See "Filtering with pattern parameters". * @returns EventLogCollectionResponse Successful Response * @throws ApiError */ -export const ensureUseEventLogServiceGetEventLogsData = (queryClient: QueryClient, { after, before, dagId, dagIdPattern, dagIdPrefixPattern, event, eventPattern, eventPrefixPattern, excludedEvents, includedEvents, limit, mapIndex, offset, orderBy, owner, ownerPattern, ownerPrefixPattern, runId, runIdPattern, runIdPrefixPattern, taskId, taskIdPattern, taskIdPrefixPattern, tryNumber }: { +export const ensureUseEventLogServiceGetEventLogsData = (queryClient: QueryClient, { after, before, dagId, dagIdPattern, dagIdPrefixPattern, event, eventPattern, eventPrefixPattern, excludedEvents, includedEvents, limit, mapIndex, offset, orderBy, owner, ownerDisplayNamePattern, ownerDisplayNamePrefixPattern, ownerPattern, ownerPrefixPattern, runId, runIdPattern, runIdPrefixPattern, taskId, taskIdPattern, taskIdPrefixPattern, tryNumber }: { after?: string; before?: string; dagId?: string; @@ -783,6 +785,8 @@ export const ensureUseEventLogServiceGetEventLogsData = (queryClient: QueryClien offset?: number; orderBy?: string[]; owner?: string; + ownerDisplayNamePattern?: string; + ownerDisplayNamePrefixPattern?: string; ownerPattern?: string; ownerPrefixPattern?: string; runId?: string; @@ -792,7 +796,7 @@ export const ensureUseEventLogServiceGetEventLogsData = (queryClient: QueryClien taskIdPattern?: string; taskIdPrefixPattern?: string; tryNumber?: number; -} = {}) => queryClient.ensureQueryData({ queryKey: Common.UseEventLogServiceGetEventLogsKeyFn({ after, before, dagId, dagIdPattern, dagIdPrefixPattern, event, eventPattern, eventPrefixPattern, excludedEvents, includedEvents, limit, mapIndex, offset, orderBy, owner, ownerPattern, ownerPrefixPattern, runId, runIdPattern, runIdPrefixPattern, taskId, taskIdPattern, taskIdPrefixPattern, tryNumber }), queryFn: () => EventLogService.getEventLogs({ after, before, dagId, dagIdPattern, dagIdPrefixPattern, event, eventPattern, eventPrefixPattern, excludedEvents, includedEvents, limit, mapIndex, offset, orderBy, owner, ownerPattern, ownerPrefixPattern, runId, runIdPattern, runIdPrefixPattern, taskId, taskIdPattern, taskIdPrefixPattern, tryNumber }) }); +} = {}) => queryClient.ensureQueryData({ queryKey: Common.UseEventLogServiceGetEventLogsKeyFn({ after, before, dagId, dagIdPattern, dagIdPrefixPattern, event, eventPattern, eventPrefixPattern, excludedEvents, includedEvents, limit, mapIndex, offset, orderBy, owner, ownerDisplayNamePattern, ownerDisplayNamePrefixPattern, ownerPattern, ownerPrefixPattern, runId, runIdPattern, runIdPrefixPattern, taskId, taskIdPattern, taskIdPrefixPattern, tryNumber }), queryFn: () => EventLogService.getEventLogs({ after, before, dagId, dagIdPattern, dagIdPrefixPattern, event, eventPattern, eventPrefixPattern, excludedEvents, includedEvents, limit, mapIndex, offset, orderBy, owner, ownerDisplayNamePattern, ownerDisplayNamePrefixPattern, ownerPattern, ownerPrefixPattern, runId, runIdPattern, runIdPrefixPattern, taskId, taskIdPattern, taskIdPrefixPattern, tryNumber }) }); /** * Get Extra Links * Get extra links for task instance. diff --git a/airflow-core/src/airflow/ui/openapi-gen/queries/prefetch.ts b/airflow-core/src/airflow/ui/openapi-gen/queries/prefetch.ts index 05732b1907f1e..a9e1144261ad4 100644 --- a/airflow-core/src/airflow/ui/openapi-gen/queries/prefetch.ts +++ b/airflow-core/src/airflow/ui/openapi-gen/queries/prefetch.ts @@ -758,16 +758,18 @@ export const prefetchUseEventLogServiceGetEventLog = (queryClient: QueryClient, * @param data.taskIdPattern Case-insensitive substring match (SQL `ILIKE`). Slower than `task_id_prefix_pattern` on large tables — see "Filtering with pattern parameters". * @param data.runIdPattern Case-insensitive substring match (SQL `ILIKE`). Slower than `run_id_prefix_pattern` on large tables — see "Filtering with pattern parameters". * @param data.ownerPattern Case-insensitive substring match (SQL `ILIKE`). Slower than `owner_prefix_pattern` on large tables — see "Filtering with pattern parameters". +* @param data.ownerDisplayNamePattern Case-insensitive substring match (SQL `ILIKE`). Slower than `owner_display_name_prefix_pattern` on large tables — see "Filtering with pattern parameters". * @param data.eventPattern Case-insensitive substring match (SQL `ILIKE`). Slower than `event_prefix_pattern` on large tables — see "Filtering with pattern parameters". * @param data.dagIdPrefixPattern Case-sensitive, index-friendly prefix match. See "Filtering with pattern parameters". * @param data.taskIdPrefixPattern Case-sensitive, index-friendly prefix match. See "Filtering with pattern parameters". * @param data.runIdPrefixPattern Case-sensitive, index-friendly prefix match. See "Filtering with pattern parameters". * @param data.ownerPrefixPattern Case-sensitive, index-friendly prefix match. See "Filtering with pattern parameters". +* @param data.ownerDisplayNamePrefixPattern Case-sensitive, index-friendly prefix match. See "Filtering with pattern parameters". * @param data.eventPrefixPattern Case-sensitive, index-friendly prefix match. See "Filtering with pattern parameters". * @returns EventLogCollectionResponse Successful Response * @throws ApiError */ -export const prefetchUseEventLogServiceGetEventLogs = (queryClient: QueryClient, { after, before, dagId, dagIdPattern, dagIdPrefixPattern, event, eventPattern, eventPrefixPattern, excludedEvents, includedEvents, limit, mapIndex, offset, orderBy, owner, ownerPattern, ownerPrefixPattern, runId, runIdPattern, runIdPrefixPattern, taskId, taskIdPattern, taskIdPrefixPattern, tryNumber }: { +export const prefetchUseEventLogServiceGetEventLogs = (queryClient: QueryClient, { after, before, dagId, dagIdPattern, dagIdPrefixPattern, event, eventPattern, eventPrefixPattern, excludedEvents, includedEvents, limit, mapIndex, offset, orderBy, owner, ownerDisplayNamePattern, ownerDisplayNamePrefixPattern, ownerPattern, ownerPrefixPattern, runId, runIdPattern, runIdPrefixPattern, taskId, taskIdPattern, taskIdPrefixPattern, tryNumber }: { after?: string; before?: string; dagId?: string; @@ -783,6 +785,8 @@ export const prefetchUseEventLogServiceGetEventLogs = (queryClient: QueryClient, offset?: number; orderBy?: string[]; owner?: string; + ownerDisplayNamePattern?: string; + ownerDisplayNamePrefixPattern?: string; ownerPattern?: string; ownerPrefixPattern?: string; runId?: string; @@ -792,7 +796,7 @@ export const prefetchUseEventLogServiceGetEventLogs = (queryClient: QueryClient, taskIdPattern?: string; taskIdPrefixPattern?: string; tryNumber?: number; -} = {}) => queryClient.prefetchQuery({ queryKey: Common.UseEventLogServiceGetEventLogsKeyFn({ after, before, dagId, dagIdPattern, dagIdPrefixPattern, event, eventPattern, eventPrefixPattern, excludedEvents, includedEvents, limit, mapIndex, offset, orderBy, owner, ownerPattern, ownerPrefixPattern, runId, runIdPattern, runIdPrefixPattern, taskId, taskIdPattern, taskIdPrefixPattern, tryNumber }), queryFn: () => EventLogService.getEventLogs({ after, before, dagId, dagIdPattern, dagIdPrefixPattern, event, eventPattern, eventPrefixPattern, excludedEvents, includedEvents, limit, mapIndex, offset, orderBy, owner, ownerPattern, ownerPrefixPattern, runId, runIdPattern, runIdPrefixPattern, taskId, taskIdPattern, taskIdPrefixPattern, tryNumber }) }); +} = {}) => queryClient.prefetchQuery({ queryKey: Common.UseEventLogServiceGetEventLogsKeyFn({ after, before, dagId, dagIdPattern, dagIdPrefixPattern, event, eventPattern, eventPrefixPattern, excludedEvents, includedEvents, limit, mapIndex, offset, orderBy, owner, ownerDisplayNamePattern, ownerDisplayNamePrefixPattern, ownerPattern, ownerPrefixPattern, runId, runIdPattern, runIdPrefixPattern, taskId, taskIdPattern, taskIdPrefixPattern, tryNumber }), queryFn: () => EventLogService.getEventLogs({ after, before, dagId, dagIdPattern, dagIdPrefixPattern, event, eventPattern, eventPrefixPattern, excludedEvents, includedEvents, limit, mapIndex, offset, orderBy, owner, ownerDisplayNamePattern, ownerDisplayNamePrefixPattern, ownerPattern, ownerPrefixPattern, runId, runIdPattern, runIdPrefixPattern, taskId, taskIdPattern, taskIdPrefixPattern, tryNumber }) }); /** * Get Extra Links * Get extra links for task instance. diff --git a/airflow-core/src/airflow/ui/openapi-gen/queries/queries.ts b/airflow-core/src/airflow/ui/openapi-gen/queries/queries.ts index 8e643f182d4a2..57a9970cdb72a 100644 --- a/airflow-core/src/airflow/ui/openapi-gen/queries/queries.ts +++ b/airflow-core/src/airflow/ui/openapi-gen/queries/queries.ts @@ -758,16 +758,18 @@ export const useEventLogServiceGetEventLog = = unknown[]>({ after, before, dagId, dagIdPattern, dagIdPrefixPattern, event, eventPattern, eventPrefixPattern, excludedEvents, includedEvents, limit, mapIndex, offset, orderBy, owner, ownerPattern, ownerPrefixPattern, runId, runIdPattern, runIdPrefixPattern, taskId, taskIdPattern, taskIdPrefixPattern, tryNumber }: { +export const useEventLogServiceGetEventLogs = = unknown[]>({ after, before, dagId, dagIdPattern, dagIdPrefixPattern, event, eventPattern, eventPrefixPattern, excludedEvents, includedEvents, limit, mapIndex, offset, orderBy, owner, ownerDisplayNamePattern, ownerDisplayNamePrefixPattern, ownerPattern, ownerPrefixPattern, runId, runIdPattern, runIdPrefixPattern, taskId, taskIdPattern, taskIdPrefixPattern, tryNumber }: { after?: string; before?: string; dagId?: string; @@ -783,6 +785,8 @@ export const useEventLogServiceGetEventLogs = , "queryKey" | "queryFn">) => useQuery({ queryKey: Common.UseEventLogServiceGetEventLogsKeyFn({ after, before, dagId, dagIdPattern, dagIdPrefixPattern, event, eventPattern, eventPrefixPattern, excludedEvents, includedEvents, limit, mapIndex, offset, orderBy, owner, ownerPattern, ownerPrefixPattern, runId, runIdPattern, runIdPrefixPattern, taskId, taskIdPattern, taskIdPrefixPattern, tryNumber }, queryKey), queryFn: () => EventLogService.getEventLogs({ after, before, dagId, dagIdPattern, dagIdPrefixPattern, event, eventPattern, eventPrefixPattern, excludedEvents, includedEvents, limit, mapIndex, offset, orderBy, owner, ownerPattern, ownerPrefixPattern, runId, runIdPattern, runIdPrefixPattern, taskId, taskIdPattern, taskIdPrefixPattern, tryNumber }) as TData, ...options }); +} = {}, queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn">) => useQuery({ queryKey: Common.UseEventLogServiceGetEventLogsKeyFn({ after, before, dagId, dagIdPattern, dagIdPrefixPattern, event, eventPattern, eventPrefixPattern, excludedEvents, includedEvents, limit, mapIndex, offset, orderBy, owner, ownerDisplayNamePattern, ownerDisplayNamePrefixPattern, ownerPattern, ownerPrefixPattern, runId, runIdPattern, runIdPrefixPattern, taskId, taskIdPattern, taskIdPrefixPattern, tryNumber }, queryKey), queryFn: () => EventLogService.getEventLogs({ after, before, dagId, dagIdPattern, dagIdPrefixPattern, event, eventPattern, eventPrefixPattern, excludedEvents, includedEvents, limit, mapIndex, offset, orderBy, owner, ownerDisplayNamePattern, ownerDisplayNamePrefixPattern, ownerPattern, ownerPrefixPattern, runId, runIdPattern, runIdPrefixPattern, taskId, taskIdPattern, taskIdPrefixPattern, tryNumber }) as TData, ...options }); /** * Get Extra Links * Get extra links for task instance. diff --git a/airflow-core/src/airflow/ui/openapi-gen/queries/suspense.ts b/airflow-core/src/airflow/ui/openapi-gen/queries/suspense.ts index 209b45e0200c3..acd2583f742bf 100644 --- a/airflow-core/src/airflow/ui/openapi-gen/queries/suspense.ts +++ b/airflow-core/src/airflow/ui/openapi-gen/queries/suspense.ts @@ -758,16 +758,18 @@ export const useEventLogServiceGetEventLogSuspense = = unknown[]>({ after, before, dagId, dagIdPattern, dagIdPrefixPattern, event, eventPattern, eventPrefixPattern, excludedEvents, includedEvents, limit, mapIndex, offset, orderBy, owner, ownerPattern, ownerPrefixPattern, runId, runIdPattern, runIdPrefixPattern, taskId, taskIdPattern, taskIdPrefixPattern, tryNumber }: { +export const useEventLogServiceGetEventLogsSuspense = = unknown[]>({ after, before, dagId, dagIdPattern, dagIdPrefixPattern, event, eventPattern, eventPrefixPattern, excludedEvents, includedEvents, limit, mapIndex, offset, orderBy, owner, ownerDisplayNamePattern, ownerDisplayNamePrefixPattern, ownerPattern, ownerPrefixPattern, runId, runIdPattern, runIdPrefixPattern, taskId, taskIdPattern, taskIdPrefixPattern, tryNumber }: { after?: string; before?: string; dagId?: string; @@ -783,6 +785,8 @@ export const useEventLogServiceGetEventLogsSuspense = , "queryKey" | "queryFn">) => useSuspenseQuery({ queryKey: Common.UseEventLogServiceGetEventLogsKeyFn({ after, before, dagId, dagIdPattern, dagIdPrefixPattern, event, eventPattern, eventPrefixPattern, excludedEvents, includedEvents, limit, mapIndex, offset, orderBy, owner, ownerPattern, ownerPrefixPattern, runId, runIdPattern, runIdPrefixPattern, taskId, taskIdPattern, taskIdPrefixPattern, tryNumber }, queryKey), queryFn: () => EventLogService.getEventLogs({ after, before, dagId, dagIdPattern, dagIdPrefixPattern, event, eventPattern, eventPrefixPattern, excludedEvents, includedEvents, limit, mapIndex, offset, orderBy, owner, ownerPattern, ownerPrefixPattern, runId, runIdPattern, runIdPrefixPattern, taskId, taskIdPattern, taskIdPrefixPattern, tryNumber }) as TData, ...options }); +} = {}, queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn">) => useSuspenseQuery({ queryKey: Common.UseEventLogServiceGetEventLogsKeyFn({ after, before, dagId, dagIdPattern, dagIdPrefixPattern, event, eventPattern, eventPrefixPattern, excludedEvents, includedEvents, limit, mapIndex, offset, orderBy, owner, ownerDisplayNamePattern, ownerDisplayNamePrefixPattern, ownerPattern, ownerPrefixPattern, runId, runIdPattern, runIdPrefixPattern, taskId, taskIdPattern, taskIdPrefixPattern, tryNumber }, queryKey), queryFn: () => EventLogService.getEventLogs({ after, before, dagId, dagIdPattern, dagIdPrefixPattern, event, eventPattern, eventPrefixPattern, excludedEvents, includedEvents, limit, mapIndex, offset, orderBy, owner, ownerDisplayNamePattern, ownerDisplayNamePrefixPattern, ownerPattern, ownerPrefixPattern, runId, runIdPattern, runIdPrefixPattern, taskId, taskIdPattern, taskIdPrefixPattern, tryNumber }) as TData, ...options }); /** * Get Extra Links * Get extra links for task instance. diff --git a/airflow-core/src/airflow/ui/openapi-gen/requests/services.gen.ts b/airflow-core/src/airflow/ui/openapi-gen/requests/services.gen.ts index 4466ec9d7daad..94215ebd52ee3 100644 --- a/airflow-core/src/airflow/ui/openapi-gen/requests/services.gen.ts +++ b/airflow-core/src/airflow/ui/openapi-gen/requests/services.gen.ts @@ -2078,11 +2078,13 @@ export class EventLogService { * @param data.taskIdPattern Case-insensitive substring match (SQL `ILIKE`). Slower than `task_id_prefix_pattern` on large tables — see "Filtering with pattern parameters". * @param data.runIdPattern Case-insensitive substring match (SQL `ILIKE`). Slower than `run_id_prefix_pattern` on large tables — see "Filtering with pattern parameters". * @param data.ownerPattern Case-insensitive substring match (SQL `ILIKE`). Slower than `owner_prefix_pattern` on large tables — see "Filtering with pattern parameters". + * @param data.ownerDisplayNamePattern Case-insensitive substring match (SQL `ILIKE`). Slower than `owner_display_name_prefix_pattern` on large tables — see "Filtering with pattern parameters". * @param data.eventPattern Case-insensitive substring match (SQL `ILIKE`). Slower than `event_prefix_pattern` on large tables — see "Filtering with pattern parameters". * @param data.dagIdPrefixPattern Case-sensitive, index-friendly prefix match. See "Filtering with pattern parameters". * @param data.taskIdPrefixPattern Case-sensitive, index-friendly prefix match. See "Filtering with pattern parameters". * @param data.runIdPrefixPattern Case-sensitive, index-friendly prefix match. See "Filtering with pattern parameters". * @param data.ownerPrefixPattern Case-sensitive, index-friendly prefix match. See "Filtering with pattern parameters". + * @param data.ownerDisplayNamePrefixPattern Case-sensitive, index-friendly prefix match. See "Filtering with pattern parameters". * @param data.eventPrefixPattern Case-sensitive, index-friendly prefix match. See "Filtering with pattern parameters". * @returns EventLogCollectionResponse Successful Response * @throws ApiError @@ -2110,11 +2112,13 @@ export class EventLogService { task_id_pattern: data.taskIdPattern, run_id_pattern: data.runIdPattern, owner_pattern: data.ownerPattern, + owner_display_name_pattern: data.ownerDisplayNamePattern, event_pattern: data.eventPattern, dag_id_prefix_pattern: data.dagIdPrefixPattern, task_id_prefix_pattern: data.taskIdPrefixPattern, run_id_prefix_pattern: data.runIdPrefixPattern, owner_prefix_pattern: data.ownerPrefixPattern, + owner_display_name_prefix_pattern: data.ownerDisplayNamePrefixPattern, event_prefix_pattern: data.eventPrefixPattern }, errors: { diff --git a/airflow-core/src/airflow/ui/openapi-gen/requests/types.gen.ts b/airflow-core/src/airflow/ui/openapi-gen/requests/types.gen.ts index 37a29af6adddc..8455306b581d9 100644 --- a/airflow-core/src/airflow/ui/openapi-gen/requests/types.gen.ts +++ b/airflow-core/src/airflow/ui/openapi-gen/requests/types.gen.ts @@ -3603,6 +3603,14 @@ export type GetEventLogsData = { */ orderBy?: Array<(string)>; owner?: string | null; + /** + * Case-insensitive substring match (SQL `ILIKE`). Slower than `owner_display_name_prefix_pattern` on large tables — see "Filtering with pattern parameters". + */ + ownerDisplayNamePattern?: string | null; + /** + * Case-sensitive, index-friendly prefix match. See "Filtering with pattern parameters". + */ + ownerDisplayNamePrefixPattern?: string | null; /** * Case-insensitive substring match (SQL `ILIKE`). Slower than `owner_prefix_pattern` on large tables — see "Filtering with pattern parameters". */ 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 9737a4a48ca59..e193ac00dd6fc 100644 --- a/airflow-core/src/airflow/ui/src/pages/Events/Events.tsx +++ b/airflow-core/src/airflow/ui/src/pages/Events/Events.tsx @@ -66,7 +66,7 @@ const eventsColumn = ( }, { accessorKey: "owner", - cell: ({ row: { original } }) => original.owner_display_name ?? original.owner, + cell: ({ row: { original } }) => original.owner_display_name, enableSorting: true, header: translate("auditLog.columns.user"), meta: { @@ -204,8 +204,8 @@ export const Events = () => { value: eventTypeFilter, }); const ownerArg = useAdvancedSearchArg({ - patternApiKey: "ownerPattern", - prefixApiKey: "ownerPrefixPattern", + patternApiKey: "ownerDisplayNamePattern", + prefixApiKey: "ownerDisplayNamePrefixPattern", storageKey: USER_PARAM, value: userFilter, }); diff --git a/airflow-core/tests/unit/api_fastapi/auth/managers/models/__init__.py b/airflow-core/tests/unit/api_fastapi/auth/managers/models/__init__.py new file mode 100644 index 0000000000000..13a83393a9124 --- /dev/null +++ b/airflow-core/tests/unit/api_fastapi/auth/managers/models/__init__.py @@ -0,0 +1,16 @@ +# 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. diff --git a/airflow-core/tests/unit/api_fastapi/auth/managers/models/test_base_user.py b/airflow-core/tests/unit/api_fastapi/auth/managers/models/test_base_user.py new file mode 100644 index 0000000000000..51ad5028dc94b --- /dev/null +++ b/airflow-core/tests/unit/api_fastapi/auth/managers/models/test_base_user.py @@ -0,0 +1,33 @@ +# 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. +from __future__ import annotations + +from airflow.api_fastapi.auth.managers.models.base_user import BaseUser + + +class UserWithoutDisplayName(BaseUser): + """An auth manager user predating get_display_name (only get_id / get_name defined).""" + + def get_id(self) -> str: + return "user-id" + + def get_name(self) -> str: + return "user-name" + + +def test_get_display_name_defaults_to_get_name(): + assert UserWithoutDisplayName().get_display_name() == "user-name" diff --git a/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_event_logs.py b/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_event_logs.py index 564c383a1fffe..871692f98d299 100644 --- a/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_event_logs.py +++ b/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_event_logs.py @@ -403,6 +403,20 @@ def test_get_event_logs_falls_back_to_owner_when_display_name_is_unavailable(sel assert event_log["owner"] == OWNER_AIRFLOW assert event_log["owner_display_name"] == OWNER_AIRFLOW + def test_get_event_logs_filters_by_owner_display_name_pattern(self, test_client): + response = test_client.get("/eventLogs", params={"owner_display_name_pattern": "est Own"}) + + assert response.status_code == 200 + events = {event_log["event"] for event_log in response.json()["event_logs"]} + assert events == {EVENT_WITH_OWNER, EVENT_WITH_OWNER_AND_TASK_INSTANCE} + + def test_get_event_logs_filters_by_owner_display_name_prefix_pattern(self, test_client): + response = test_client.get("/eventLogs", params={"owner_display_name_prefix_pattern": "Test"}) + + assert response.status_code == 200 + events = {event_log["event"] for event_log in response.json()["event_logs"]} + assert events == {EVENT_WITH_OWNER, EVENT_WITH_OWNER_AND_TASK_INSTANCE} + # Ordering of nulls values is DB specific. @pytest.mark.backend("sqlite") @pytest.mark.parametrize( diff --git a/airflow-core/tests/unit/api_fastapi/logging/test_decorators.py b/airflow-core/tests/unit/api_fastapi/logging/test_decorators.py index 41b9883d6d313..2adffbe454494 100644 --- a/airflow-core/tests/unit/api_fastapi/logging/test_decorators.py +++ b/airflow-core/tests/unit/api_fastapi/logging/test_decorators.py @@ -16,14 +16,20 @@ # under the License. from __future__ import annotations +import asyncio import json +from unittest.mock import MagicMock import pytest +from fastapi import Request +from sqlalchemy.orm import Session +from airflow.api_fastapi.auth.managers.models.base_user import BaseUser from airflow.api_fastapi.logging.decorators import ( _mask_connection_fields, _mask_variable_fields, _sanitize_for_stdlib_log, + action_logging, ) @@ -122,3 +128,28 @@ def test_masks_val_alias(self): def test_value_without_key_is_still_masked(self): result = _mask_variable_fields({"value": "secretval"}) assert result == {"value": "***"} + + +class TestActionLoggingUserFields: + """The audit Log records the raw identifier in ``owner`` and the human-friendly + ``get_display_name()`` in ``owner_display_name``.""" + + def test_owner_and_display_name_use_the_matching_user_methods(self): + class FakeUser(BaseUser): + def get_id(self) -> str: + return "id" + + def get_name(self) -> str: + return "jdoe" + + def get_display_name(self) -> str: + return "Jane Doe" + + request = Request({"type": "http", "method": "GET", "headers": [], "query_string": b""}) + session = MagicMock(spec=Session) + + asyncio.run(action_logging(event="test_event")(request=request, session=session, user=FakeUser())) + + (logged,) = session.add.call_args.args + assert logged.owner == "jdoe" + assert logged.owner_display_name == "Jane Doe" diff --git a/providers/fab/src/airflow/providers/fab/auth_manager/models/__init__.py b/providers/fab/src/airflow/providers/fab/auth_manager/models/__init__.py index 598f13b221940..ecdac36f42127 100644 --- a/providers/fab/src/airflow/providers/fab/auth_manager/models/__init__.py +++ b/providers/fab/src/airflow/providers/fab/auth_manager/models/__init__.py @@ -363,6 +363,9 @@ def get_name(self) -> str: def get_full_name(self): return f"{self.first_name} {self.last_name}" + def get_display_name(self) -> str: + return self.get_full_name() + def __repr__(self): return self.get_full_name() diff --git a/providers/fab/tests/unit/fab/auth_manager/models/test_user_model.py b/providers/fab/tests/unit/fab/auth_manager/models/test_user_model.py index f780871676e86..c054463c3032b 100644 --- a/providers/fab/tests/unit/fab/auth_manager/models/test_user_model.py +++ b/providers/fab/tests/unit/fab/auth_manager/models/test_user_model.py @@ -36,3 +36,10 @@ def test_get_id_returns_str(user_id: int, expected_id: str) -> None: result = user.get_id() assert isinstance(result, str), f"Expected str, got {type(result)}" assert result == expected_id + + +def test_get_display_name_uses_full_name() -> None: + user = User() + user.first_name = "Jane" + user.last_name = "Doe" + assert user.get_display_name() == "Jane Doe"