From 6548d50841fead18d1b66fd10a76a8b94b3e50b0 Mon Sep 17 00:00:00 2001 From: LIU ZHE YOU <68415893+jason810496@users.noreply.github.com> Date: Tue, 5 Nov 2024 18:51:22 +0800 Subject: [PATCH] AIP-84 Get Event Logs (#43407) * AIP-84 Get Event Logs * fix: add http execption docs for router * refactor: remove `FilterParam` out of this PR --- .../endpoints/event_log_endpoint.py | 1 + .../core_api/openapi/v1-generated.yaml | 164 ++++++++++++++++++ .../core_api/routes/public/event_logs.py | 97 ++++++++++- .../core_api/serializers/event_logs.py | 7 + airflow/ui/openapi-gen/queries/common.ts | 62 +++++++ airflow/ui/openapi-gen/queries/prefetch.ts | 90 ++++++++++ airflow/ui/openapi-gen/queries/queries.ts | 99 +++++++++++ airflow/ui/openapi-gen/queries/suspense.ts | 99 +++++++++++ .../ui/openapi-gen/requests/schemas.gen.ts | 20 +++ .../ui/openapi-gen/requests/services.gen.ts | 53 ++++++ airflow/ui/openapi-gen/requests/types.gen.ts | 50 ++++++ .../core_api/routes/public/test_event_logs.py | 130 ++++++++++++++ 12 files changed, 871 insertions(+), 1 deletion(-) diff --git a/airflow/api_connexion/endpoints/event_log_endpoint.py b/airflow/api_connexion/endpoints/event_log_endpoint.py index 8084c2ecab674..93b951a35888f 100644 --- a/airflow/api_connexion/endpoints/event_log_endpoint.py +++ b/airflow/api_connexion/endpoints/event_log_endpoint.py @@ -52,6 +52,7 @@ def get_event_log(*, event_log_id: int, session: Session = NEW_SESSION) -> APIRe return event_log_schema.dump(event_log) +@mark_fastapi_migration_done @security.requires_access_dag("GET", DagAccessEntity.AUDIT_LOG) @format_parameters({"limit": check_limit}) @provide_session diff --git a/airflow/api_fastapi/core_api/openapi/v1-generated.yaml b/airflow/api_fastapi/core_api/openapi/v1-generated.yaml index 3dcff4b2d066f..e844cbceeb474 100644 --- a/airflow/api_fastapi/core_api/openapi/v1-generated.yaml +++ b/airflow/api_fastapi/core_api/openapi/v1-generated.yaml @@ -1390,6 +1390,154 @@ paths: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' + /public/eventLogs/: + get: + tags: + - Event Log + summary: Get Event Logs + description: Get all Event Logs. + operationId: get_event_logs + parameters: + - name: dag_id + in: query + required: false + schema: + anyOf: + - type: string + - type: 'null' + title: Dag Id + - name: task_id + in: query + required: false + schema: + anyOf: + - type: string + - type: 'null' + title: Task Id + - name: run_id + in: query + required: false + schema: + anyOf: + - type: string + - type: 'null' + title: Run Id + - name: map_index + in: query + required: false + schema: + anyOf: + - type: integer + - type: 'null' + title: Map Index + - name: try_number + in: query + required: false + schema: + anyOf: + - type: integer + - type: 'null' + title: Try Number + - name: owner + in: query + required: false + schema: + anyOf: + - type: string + - type: 'null' + title: Owner + - name: event + in: query + required: false + schema: + anyOf: + - type: string + - type: 'null' + title: Event + - name: excluded_events + in: query + required: false + schema: + anyOf: + - type: array + items: + type: string + - type: 'null' + title: Excluded Events + - name: included_events + in: query + required: false + schema: + anyOf: + - type: array + items: + type: string + - type: 'null' + title: Included Events + - name: before + in: query + required: false + schema: + anyOf: + - type: string + format: date-time + - type: 'null' + title: Before + - name: after + in: query + required: false + schema: + anyOf: + - type: string + format: date-time + - type: 'null' + title: After + - name: limit + in: query + required: false + schema: + type: integer + default: 100 + title: Limit + - name: offset + in: query + required: false + schema: + type: integer + default: 0 + title: Offset + - name: order_by + in: query + required: false + schema: + type: string + default: id + title: Order By + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/EventLogCollectionResponse' + '401': + content: + application/json: + schema: + $ref: '#/components/schemas/HTTPExceptionResponse' + description: Unauthorized + '403': + content: + application/json: + schema: + $ref: '#/components/schemas/HTTPExceptionResponse' + description: Forbidden + '422': + description: Validation Error + content: + application/json: + schema: + $ref: '#/components/schemas/HTTPValidationError' /public/monitor/health: get: tags: @@ -3149,6 +3297,22 @@ components: This is the set of allowable values for the ``warning_type`` field in the DagWarning model.' + EventLogCollectionResponse: + properties: + event_logs: + items: + $ref: '#/components/schemas/EventLogResponse' + type: array + title: Event Logs + total_entries: + type: integer + title: Total Entries + type: object + required: + - event_logs + - total_entries + title: EventLogCollectionResponse + description: Event Log Collection Response. EventLogResponse: properties: event_log_id: 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 537bb5ffe4df9..462c266969575 100644 --- a/airflow/api_fastapi/core_api/routes/public/event_logs.py +++ b/airflow/api_fastapi/core_api/routes/public/event_logs.py @@ -16,17 +16,26 @@ # under the License. from __future__ import annotations -from fastapi import Depends, HTTPException, status +from datetime import datetime + +from fastapi import Depends, HTTPException, Query, status from sqlalchemy import select from sqlalchemy.orm import Session from typing_extensions import Annotated from airflow.api_fastapi.common.db.common import ( get_session, + paginated_select, +) +from airflow.api_fastapi.common.parameters import ( + QueryLimit, + QueryOffset, + SortParam, ) from airflow.api_fastapi.common.router import AirflowRouter from airflow.api_fastapi.core_api.openapi.exceptions import create_openapi_http_exception_doc from airflow.api_fastapi.core_api.serializers.event_logs import ( + EventLogCollectionResponse, EventLogResponse, ) from airflow.models import Log @@ -51,3 +60,89 @@ async def get_event_log( event_log, from_attributes=True, ) + + +@event_logs_router.get( + "/", + responses=create_openapi_http_exception_doc([status.HTTP_401_UNAUTHORIZED, status.HTTP_403_FORBIDDEN]), +) +async def get_event_logs( + limit: QueryLimit, + offset: QueryOffset, + session: Annotated[Session, Depends(get_session)], + order_by: Annotated[ + SortParam, + Depends( + SortParam( + [ + "id", # event_log_id + "dttm", # when + "dag_id", + "task_id", + "run_id", + "event", + "execution_date", # logical_date + "owner", + "extra", + ], + Log, + ).dynamic_depends() + ), + ], + dag_id: str | None = None, + task_id: str | None = None, + run_id: str | None = None, + map_index: int | None = None, + try_number: int | None = None, + owner: str | None = None, + event: str | None = None, + excluded_events: list[str] | None = Query(None), + included_events: list[str] | None = Query(None), + before: datetime | None = None, + after: datetime | None = None, +) -> EventLogCollectionResponse: + """Get all Event Logs.""" + base_select = select(Log).group_by(Log.id) + # TODO: Refactor using the `FilterParam` class in commit `574b72e41cc5ed175a2bbf4356522589b836bb11` + if dag_id is not None: + base_select = base_select.where(Log.dag_id == dag_id) + if task_id is not None: + base_select = base_select.where(Log.task_id == task_id) + if run_id is not None: + base_select = base_select.where(Log.run_id == run_id) + if map_index is not None: + base_select = base_select.where(Log.map_index == map_index) + if try_number is not None: + base_select = base_select.where(Log.try_number == try_number) + if owner is not None: + base_select = base_select.where(Log.owner == owner) + if event is not None: + base_select = base_select.where(Log.event == event) + if excluded_events is not None: + base_select = base_select.where(Log.event.notin_(excluded_events)) + if included_events is not None: + base_select = base_select.where(Log.event.in_(included_events)) + if before is not None: + base_select = base_select.where(Log.dttm < before) + if after is not None: + base_select = base_select.where(Log.dttm > after) + event_logs_select, total_entries = paginated_select( + base_select, + [], + order_by, + offset, + limit, + session, + ) + event_logs = session.scalars(event_logs_select).all() + + return EventLogCollectionResponse( + event_logs=[ + EventLogResponse.model_validate( + event_log, + from_attributes=True, + ) + for event_log in event_logs + ], + total_entries=total_entries, + ) diff --git a/airflow/api_fastapi/core_api/serializers/event_logs.py b/airflow/api_fastapi/core_api/serializers/event_logs.py index e295dc35061fb..f70e5bd15834d 100644 --- a/airflow/api_fastapi/core_api/serializers/event_logs.py +++ b/airflow/api_fastapi/core_api/serializers/event_logs.py @@ -38,3 +38,10 @@ class EventLogResponse(BaseModel): extra: str | None model_config = ConfigDict(populate_by_name=True) + + +class EventLogCollectionResponse(BaseModel): + """Event Log Collection Response.""" + + event_logs: list[EventLogResponse] + total_entries: int diff --git a/airflow/ui/openapi-gen/queries/common.ts b/airflow/ui/openapi-gen/queries/common.ts index 75a343ce74c7c..1248a77ce188d 100644 --- a/airflow/ui/openapi-gen/queries/common.ts +++ b/airflow/ui/openapi-gen/queries/common.ts @@ -359,6 +359,68 @@ export const UseEventLogServiceGetEventLogKeyFn = ( }, queryKey?: Array, ) => [useEventLogServiceGetEventLogKey, ...(queryKey ?? [{ eventLogId }])]; +export type EventLogServiceGetEventLogsDefaultResponse = Awaited< + ReturnType +>; +export type EventLogServiceGetEventLogsQueryResult< + TData = EventLogServiceGetEventLogsDefaultResponse, + TError = unknown, +> = UseQueryResult; +export const useEventLogServiceGetEventLogsKey = "EventLogServiceGetEventLogs"; +export const UseEventLogServiceGetEventLogsKeyFn = ( + { + after, + before, + dagId, + event, + excludedEvents, + includedEvents, + limit, + mapIndex, + offset, + orderBy, + owner, + runId, + taskId, + tryNumber, + }: { + after?: string; + before?: string; + dagId?: string; + event?: string; + excludedEvents?: string[]; + includedEvents?: string[]; + limit?: number; + mapIndex?: number; + offset?: number; + orderBy?: string; + owner?: string; + runId?: string; + taskId?: string; + tryNumber?: number; + } = {}, + queryKey?: Array, +) => [ + useEventLogServiceGetEventLogsKey, + ...(queryKey ?? [ + { + after, + before, + dagId, + event, + excludedEvents, + includedEvents, + limit, + mapIndex, + offset, + orderBy, + owner, + runId, + taskId, + tryNumber, + }, + ]), +]; export type MonitorServiceGetHealthDefaultResponse = Awaited< ReturnType >; diff --git a/airflow/ui/openapi-gen/queries/prefetch.ts b/airflow/ui/openapi-gen/queries/prefetch.ts index 63e8d4b43132a..bf6ad800be012 100644 --- a/airflow/ui/openapi-gen/queries/prefetch.ts +++ b/airflow/ui/openapi-gen/queries/prefetch.ts @@ -453,6 +453,96 @@ export const prefetchUseEventLogServiceGetEventLog = ( queryKey: Common.UseEventLogServiceGetEventLogKeyFn({ eventLogId }), queryFn: () => EventLogService.getEventLog({ eventLogId }), }); +/** + * Get Event Logs + * Get all Event Logs. + * @param data The data for the request. + * @param data.dagId + * @param data.taskId + * @param data.runId + * @param data.mapIndex + * @param data.tryNumber + * @param data.owner + * @param data.event + * @param data.excludedEvents + * @param data.includedEvents + * @param data.before + * @param data.after + * @param data.limit + * @param data.offset + * @param data.orderBy + * @returns EventLogCollectionResponse Successful Response + * @throws ApiError + */ +export const prefetchUseEventLogServiceGetEventLogs = ( + queryClient: QueryClient, + { + after, + before, + dagId, + event, + excludedEvents, + includedEvents, + limit, + mapIndex, + offset, + orderBy, + owner, + runId, + taskId, + tryNumber, + }: { + after?: string; + before?: string; + dagId?: string; + event?: string; + excludedEvents?: string[]; + includedEvents?: string[]; + limit?: number; + mapIndex?: number; + offset?: number; + orderBy?: string; + owner?: string; + runId?: string; + taskId?: string; + tryNumber?: number; + } = {}, +) => + queryClient.prefetchQuery({ + queryKey: Common.UseEventLogServiceGetEventLogsKeyFn({ + after, + before, + dagId, + event, + excludedEvents, + includedEvents, + limit, + mapIndex, + offset, + orderBy, + owner, + runId, + taskId, + tryNumber, + }), + queryFn: () => + EventLogService.getEventLogs({ + after, + before, + dagId, + event, + excludedEvents, + includedEvents, + limit, + mapIndex, + offset, + orderBy, + owner, + runId, + taskId, + tryNumber, + }), + }); /** * Get Health * @returns HealthInfoSchema Successful Response diff --git a/airflow/ui/openapi-gen/queries/queries.ts b/airflow/ui/openapi-gen/queries/queries.ts index 5f8f649372db6..70796be401719 100644 --- a/airflow/ui/openapi-gen/queries/queries.ts +++ b/airflow/ui/openapi-gen/queries/queries.ts @@ -572,6 +572,105 @@ export const useEventLogServiceGetEventLog = < queryFn: () => EventLogService.getEventLog({ eventLogId }) as TData, ...options, }); +/** + * Get Event Logs + * Get all Event Logs. + * @param data The data for the request. + * @param data.dagId + * @param data.taskId + * @param data.runId + * @param data.mapIndex + * @param data.tryNumber + * @param data.owner + * @param data.event + * @param data.excludedEvents + * @param data.includedEvents + * @param data.before + * @param data.after + * @param data.limit + * @param data.offset + * @param data.orderBy + * @returns EventLogCollectionResponse Successful Response + * @throws ApiError + */ +export const useEventLogServiceGetEventLogs = < + TData = Common.EventLogServiceGetEventLogsDefaultResponse, + TError = unknown, + TQueryKey extends Array = unknown[], +>( + { + after, + before, + dagId, + event, + excludedEvents, + includedEvents, + limit, + mapIndex, + offset, + orderBy, + owner, + runId, + taskId, + tryNumber, + }: { + after?: string; + before?: string; + dagId?: string; + event?: string; + excludedEvents?: string[]; + includedEvents?: string[]; + limit?: number; + mapIndex?: number; + offset?: number; + orderBy?: string; + owner?: string; + runId?: string; + taskId?: string; + tryNumber?: number; + } = {}, + queryKey?: TQueryKey, + options?: Omit, "queryKey" | "queryFn">, +) => + useQuery({ + queryKey: Common.UseEventLogServiceGetEventLogsKeyFn( + { + after, + before, + dagId, + event, + excludedEvents, + includedEvents, + limit, + mapIndex, + offset, + orderBy, + owner, + runId, + taskId, + tryNumber, + }, + queryKey, + ), + queryFn: () => + EventLogService.getEventLogs({ + after, + before, + dagId, + event, + excludedEvents, + includedEvents, + limit, + mapIndex, + offset, + orderBy, + owner, + runId, + taskId, + tryNumber, + }) as TData, + ...options, + }); /** * Get Health * @returns HealthInfoSchema Successful Response diff --git a/airflow/ui/openapi-gen/queries/suspense.ts b/airflow/ui/openapi-gen/queries/suspense.ts index 1222b7f5536c2..4f75c2ba0ce47 100644 --- a/airflow/ui/openapi-gen/queries/suspense.ts +++ b/airflow/ui/openapi-gen/queries/suspense.ts @@ -558,6 +558,105 @@ export const useEventLogServiceGetEventLogSuspense = < queryFn: () => EventLogService.getEventLog({ eventLogId }) as TData, ...options, }); +/** + * Get Event Logs + * Get all Event Logs. + * @param data The data for the request. + * @param data.dagId + * @param data.taskId + * @param data.runId + * @param data.mapIndex + * @param data.tryNumber + * @param data.owner + * @param data.event + * @param data.excludedEvents + * @param data.includedEvents + * @param data.before + * @param data.after + * @param data.limit + * @param data.offset + * @param data.orderBy + * @returns EventLogCollectionResponse Successful Response + * @throws ApiError + */ +export const useEventLogServiceGetEventLogsSuspense = < + TData = Common.EventLogServiceGetEventLogsDefaultResponse, + TError = unknown, + TQueryKey extends Array = unknown[], +>( + { + after, + before, + dagId, + event, + excludedEvents, + includedEvents, + limit, + mapIndex, + offset, + orderBy, + owner, + runId, + taskId, + tryNumber, + }: { + after?: string; + before?: string; + dagId?: string; + event?: string; + excludedEvents?: string[]; + includedEvents?: string[]; + limit?: number; + mapIndex?: number; + offset?: number; + orderBy?: string; + owner?: string; + runId?: string; + taskId?: string; + tryNumber?: number; + } = {}, + queryKey?: TQueryKey, + options?: Omit, "queryKey" | "queryFn">, +) => + useSuspenseQuery({ + queryKey: Common.UseEventLogServiceGetEventLogsKeyFn( + { + after, + before, + dagId, + event, + excludedEvents, + includedEvents, + limit, + mapIndex, + offset, + orderBy, + owner, + runId, + taskId, + tryNumber, + }, + queryKey, + ), + queryFn: () => + EventLogService.getEventLogs({ + after, + before, + dagId, + event, + excludedEvents, + includedEvents, + limit, + mapIndex, + offset, + orderBy, + owner, + runId, + taskId, + tryNumber, + }) as TData, + ...options, + }); /** * Get Health * @returns HealthInfoSchema Successful Response diff --git a/airflow/ui/openapi-gen/requests/schemas.gen.ts b/airflow/ui/openapi-gen/requests/schemas.gen.ts index 906bb43df988a..53272eae2e779 100644 --- a/airflow/ui/openapi-gen/requests/schemas.gen.ts +++ b/airflow/ui/openapi-gen/requests/schemas.gen.ts @@ -1502,6 +1502,26 @@ This is the set of allowable values for the \`\`warning_type\`\` field in the DagWarning model.`, } as const; +export const $EventLogCollectionResponse = { + properties: { + event_logs: { + items: { + $ref: "#/components/schemas/EventLogResponse", + }, + type: "array", + title: "Event Logs", + }, + total_entries: { + type: "integer", + title: "Total Entries", + }, + }, + type: "object", + required: ["event_logs", "total_entries"], + title: "EventLogCollectionResponse", + description: "Event Log Collection Response.", +} as const; + export const $EventLogResponse = { properties: { event_log_id: { diff --git a/airflow/ui/openapi-gen/requests/services.gen.ts b/airflow/ui/openapi-gen/requests/services.gen.ts index e82d0c5146fa8..fa5c7739c909b 100644 --- a/airflow/ui/openapi-gen/requests/services.gen.ts +++ b/airflow/ui/openapi-gen/requests/services.gen.ts @@ -51,6 +51,8 @@ import type { GetDagSourceResponse, GetEventLogData, GetEventLogResponse, + GetEventLogsData, + GetEventLogsResponse, GetHealthResponse, ListDagWarningsData, ListDagWarningsResponse, @@ -810,6 +812,57 @@ export class EventLogService { }, }); } + + /** + * Get Event Logs + * Get all Event Logs. + * @param data The data for the request. + * @param data.dagId + * @param data.taskId + * @param data.runId + * @param data.mapIndex + * @param data.tryNumber + * @param data.owner + * @param data.event + * @param data.excludedEvents + * @param data.includedEvents + * @param data.before + * @param data.after + * @param data.limit + * @param data.offset + * @param data.orderBy + * @returns EventLogCollectionResponse Successful Response + * @throws ApiError + */ + public static getEventLogs( + data: GetEventLogsData = {}, + ): CancelablePromise { + return __request(OpenAPI, { + method: "GET", + url: "/public/eventLogs/", + query: { + dag_id: data.dagId, + task_id: data.taskId, + run_id: data.runId, + map_index: data.mapIndex, + try_number: data.tryNumber, + owner: data.owner, + event: data.event, + excluded_events: data.excludedEvents, + included_events: data.includedEvents, + before: data.before, + after: data.after, + limit: data.limit, + offset: data.offset, + order_by: data.orderBy, + }, + errors: { + 401: "Unauthorized", + 403: "Forbidden", + 422: "Validation Error", + }, + }); + } } export class MonitorService { diff --git a/airflow/ui/openapi-gen/requests/types.gen.ts b/airflow/ui/openapi-gen/requests/types.gen.ts index afe771e5a1f65..909b78dd627e1 100644 --- a/airflow/ui/openapi-gen/requests/types.gen.ts +++ b/airflow/ui/openapi-gen/requests/types.gen.ts @@ -347,6 +347,14 @@ export type DagTagPydantic = { */ export type DagWarningType = "asset conflict" | "non-existent pool"; +/** + * Event Log Collection Response. + */ +export type EventLogCollectionResponse = { + event_logs: Array; + total_entries: number; +}; + /** * Event Log Response. */ @@ -851,6 +859,25 @@ export type GetEventLogData = { export type GetEventLogResponse = EventLogResponse; +export type GetEventLogsData = { + after?: string | null; + before?: string | null; + dagId?: string | null; + event?: string | null; + excludedEvents?: Array | null; + includedEvents?: Array | null; + limit?: number; + mapIndex?: number | null; + offset?: number; + orderBy?: string; + owner?: string | null; + runId?: string | null; + taskId?: string | null; + tryNumber?: number | null; +}; + +export type GetEventLogsResponse = EventLogCollectionResponse; + export type GetHealthResponse = HealthInfoSchema; export type ListDagWarningsData = { @@ -1601,6 +1628,29 @@ export type $OpenApiTs = { }; }; }; + "/public/eventLogs/": { + get: { + req: GetEventLogsData; + res: { + /** + * Successful Response + */ + 200: EventLogCollectionResponse; + /** + * Unauthorized + */ + 401: HTTPExceptionResponse; + /** + * Forbidden + */ + 403: HTTPExceptionResponse; + /** + * Validation Error + */ + 422: HTTPValidationError; + }; + }; + }; "/public/monitor/health": { get: { res: { diff --git a/tests/api_fastapi/core_api/routes/public/test_event_logs.py b/tests/api_fastapi/core_api/routes/public/test_event_logs.py index c329015b9f9e0..347f587bf5c29 100644 --- a/tests/api_fastapi/core_api/routes/public/test_event_logs.py +++ b/tests/api_fastapi/core_api/routes/public/test_event_logs.py @@ -175,3 +175,133 @@ def test_get_event_log(self, test_client, setup, event_log_key, expected_status_ } assert response.json() == expected_json + + +class TestGetEventLogs(TestEventLogsEndpoint): + @pytest.mark.parametrize( + "query_params, expected_status_code, expected_total_entries, expected_events", + [ + ( + {}, + 200, + 4, + [EVENT_NORMAL, EVENT_WITH_OWNER, TASK_INSTANCE_EVENT, EVENT_WITH_OWNER_AND_TASK_INSTANCE], + ), + # offset, limit + ( + {"offset": 1, "limit": 2}, + 200, + 4, + [EVENT_WITH_OWNER, TASK_INSTANCE_EVENT], + ), + # equal filter + ( + {"event": EVENT_NORMAL}, + 200, + 1, + [EVENT_NORMAL], + ), + ( + {"event": EVENT_WITH_OWNER}, + 200, + 1, + [EVENT_WITH_OWNER], + ), + ( + {"task_id": TASK_ID}, + 200, + 2, + [TASK_INSTANCE_EVENT, EVENT_WITH_OWNER_AND_TASK_INSTANCE], + ), + # multiple equal filters + ( + {"event": EVENT_WITH_OWNER, "owner": OWNER}, + 200, + 1, + [EVENT_WITH_OWNER], + ), + ( + {"event": EVENT_WITH_OWNER_AND_TASK_INSTANCE, "task_id": TASK_ID, "run_id": DAG_RUN_ID}, + 200, + 1, + [EVENT_WITH_OWNER_AND_TASK_INSTANCE], + ), + # list filter + ( + {"excluded_events": [EVENT_NORMAL, EVENT_WITH_OWNER]}, + 200, + 2, + [TASK_INSTANCE_EVENT, EVENT_WITH_OWNER_AND_TASK_INSTANCE], + ), + ( + {"included_events": [EVENT_NORMAL, EVENT_WITH_OWNER]}, + 200, + 2, + [EVENT_NORMAL, EVENT_WITH_OWNER], + ), + # multiple list filters + ( + {"excluded_events": [EVENT_NORMAL], "included_events": [EVENT_WITH_OWNER]}, + 200, + 1, + [EVENT_WITH_OWNER], + ), + # before, after filters + ( + {"before": "2024-06-15T00:00:00Z"}, + 200, + 0, + [], + ), + ( + {"after": "2024-06-15T00:00:00Z"}, + 200, + 4, + [EVENT_NORMAL, EVENT_WITH_OWNER, TASK_INSTANCE_EVENT, EVENT_WITH_OWNER_AND_TASK_INSTANCE], + ), + # order_by + ( + {"order_by": "-id"}, + 200, + 4, + [EVENT_WITH_OWNER_AND_TASK_INSTANCE, TASK_INSTANCE_EVENT, EVENT_WITH_OWNER, EVENT_NORMAL], + ), + ( + {"order_by": "execution_date"}, + 200, + 4, + [TASK_INSTANCE_EVENT, EVENT_WITH_OWNER_AND_TASK_INSTANCE, EVENT_NORMAL, EVENT_WITH_OWNER], + ), + # combination of query parameters + ( + {"offset": 1, "excluded_events": ["non_existed_event"], "order_by": "event"}, + 200, + 4, + [EVENT_WITH_OWNER_AND_TASK_INSTANCE, EVENT_NORMAL, TASK_INSTANCE_EVENT], + ), + ( + {"excluded_events": [EVENT_NORMAL], "included_events": [EVENT_WITH_OWNER], "order_by": "-id"}, + 200, + 1, + [EVENT_WITH_OWNER], + ), + ( + {"map_index": -1, "try_number": 0, "order_by": "event", "limit": 1}, + 200, + 2, + [EVENT_WITH_OWNER_AND_TASK_INSTANCE], + ), + ], + ) + def test_get_event_logs( + self, test_client, query_params, expected_status_code, expected_total_entries, expected_events + ): + response = test_client.get("/public/eventLogs/", params=query_params) + assert response.status_code == expected_status_code + if expected_status_code != 200: + return + + resp_json = response.json() + assert resp_json["total_entries"] == expected_total_entries + for event_log, expected_event in zip(resp_json["event_logs"], expected_events): + assert event_log["event"] == expected_event