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/common/parameters.py b/airflow/api_fastapi/common/parameters.py index 7137a0a124847..df8b749fa759f 100644 --- a/airflow/api_fastapi/common/parameters.py +++ b/airflow/api_fastapi/common/parameters.py @@ -19,7 +19,7 @@ from abc import ABC, abstractmethod from datetime import datetime -from typing import TYPE_CHECKING, Any, Callable, Generic, List, TypeVar +from typing import TYPE_CHECKING, Any, Callable, Generic, List, Literal, TypeVar from fastapi import Depends, HTTPException, Query from pendulum.parsing.exceptions import ParserError @@ -219,6 +219,55 @@ def inner(order_by: str = self.get_primary_key_string()) -> SortParam: return inner +_filter_options = Literal["in", "not_in", "eq", "ne", "lt", "le", "gt", "ge"] + + +class FilterParam(BaseParam[T]): + """Filter on attribute.""" + + def __init__( + self, + attribute: ColumnElement, + value: T | None = None, + filter_option: _filter_options = "eq", + skip_none: bool = True, + ) -> None: + super().__init__(skip_none) + self.attribute: ColumnElement = attribute + self.value: T | None = value + self.filter_option: _filter_options = filter_option + + def to_orm(self, select: Select) -> Select: + if self.value is None and self.skip_none: + return select + + if isinstance(self.value, list): + if self.filter_option == "in": + return select.where(self.attribute.in_(self.value)) + if self.filter_option == "not_in": + return select.where(self.attribute.notin_(self.value)) + raise ValueError(f"Invalid filter option {self.filter_option} for list value {self.value}") + + if self.filter_option == "eq": + return select.where(self.attribute == self.value) + if self.filter_option == "ne": + return select.where(self.attribute != self.value) + if self.filter_option == "lt": + return select.where(self.attribute < self.value) + if self.filter_option == "le": + return select.where(self.attribute <= self.value) + if self.filter_option == "gt": + return select.where(self.attribute > self.value) + if self.filter_option == "ge": + return select.where(self.attribute >= self.value) + raise ValueError(f"Invalid filter option {self.filter_option} for value {self.value}") + + def depends(self, *args: Any, **kwargs: Any) -> Self: + raise NotImplementedError( + "Construct FilterParam directly within the router handler, depends is not implemented." + ) + + class _TagsFilter(BaseParam[List[str]]): """Filter on tags.""" diff --git a/airflow/api_fastapi/core_api/openapi/v1-generated.yaml b/airflow/api_fastapi/core_api/openapi/v1-generated.yaml index 55bee131c70a7..94ac108f6d12d 100644 --- a/airflow/api_fastapi/core_api/openapi/v1-generated.yaml +++ b/airflow/api_fastapi/core_api/openapi/v1-generated.yaml @@ -1532,6 +1532,142 @@ 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' + '422': + description: Validation Error + content: + application/json: + schema: + $ref: '#/components/schemas/HTTPValidationError' components: schemas: AppBuilderMenuItemResponse: @@ -2462,6 +2598,22 @@ components: title: DagTagPydantic description: Serializable representation of the DagTag ORM SqlAlchemyModel used by internal API. + 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 012797623d89c..f8975cb45f594 100644 --- a/airflow/api_fastapi/core_api/routes/public/event_logs.py +++ b/airflow/api_fastapi/core_api/routes/public/event_logs.py @@ -16,16 +16,26 @@ # under the License. from __future__ import annotations -from fastapi import Depends, HTTPException +from datetime import datetime + +from fastapi import Depends, HTTPException, Query 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 ( + FilterParam, + QueryLimit, + QueryOffset, + SortParam, ) from airflow.api_fastapi.common.router import AirflowRouter from airflow.api_fastapi.core_api.serializers.event_logs import ( + EventLogCollectionResponse, EventLogResponse, ) from airflow.models import Log @@ -45,3 +55,75 @@ async def get_event_log( event_log, from_attributes=True, ) + + +@event_logs_router.get("/") +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) + event_logs_select, total_entries = paginated_select( + base_select, + [ + FilterParam(Log.dag_id, dag_id), + FilterParam(Log.task_id, task_id), + FilterParam(Log.run_id, run_id), + FilterParam(Log.map_index, map_index), + FilterParam(Log.event, event), + FilterParam(Log.try_number, try_number), + FilterParam(Log.owner, owner), + FilterParam(Log.event, excluded_events, "not_in"), + FilterParam(Log.event, included_events, "in"), + FilterParam(Log.dttm, before, "lt"), + FilterParam(Log.dttm, after, "gt"), + ], + 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 959b476718d22..44d05f190674b 100644 --- a/airflow/ui/openapi-gen/queries/common.ts +++ b/airflow/ui/openapi-gen/queries/common.ts @@ -426,6 +426,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 VariableServicePostVariableMutationResult = Awaited< ReturnType >; diff --git a/airflow/ui/openapi-gen/queries/prefetch.ts b/airflow/ui/openapi-gen/queries/prefetch.ts index 350f9deddcf70..5b6c45061aa9b 100644 --- a/airflow/ui/openapi-gen/queries/prefetch.ts +++ b/airflow/ui/openapi-gen/queries/prefetch.ts @@ -532,3 +532,93 @@ 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, + }), + }); diff --git a/airflow/ui/openapi-gen/queries/queries.ts b/airflow/ui/openapi-gen/queries/queries.ts index a3aed2e793718..811e6edc37baf 100644 --- a/airflow/ui/openapi-gen/queries/queries.ts +++ b/airflow/ui/openapi-gen/queries/queries.ts @@ -692,6 +692,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, + }); /** * Post Variable * Create a variable. diff --git a/airflow/ui/openapi-gen/queries/suspense.ts b/airflow/ui/openapi-gen/queries/suspense.ts index 16f8ca0030385..a6db35de581cc 100644 --- a/airflow/ui/openapi-gen/queries/suspense.ts +++ b/airflow/ui/openapi-gen/queries/suspense.ts @@ -681,3 +681,102 @@ 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, + }); diff --git a/airflow/ui/openapi-gen/requests/schemas.gen.ts b/airflow/ui/openapi-gen/requests/schemas.gen.ts index eeab557ca9540..d342e34176209 100644 --- a/airflow/ui/openapi-gen/requests/schemas.gen.ts +++ b/airflow/ui/openapi-gen/requests/schemas.gen.ts @@ -1488,6 +1488,26 @@ export const $DagTagPydantic = { "Serializable representation of the DagTag ORM SqlAlchemyModel used by internal API.", } 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 e7a52e1637ee8..cb6828847ae9c 100644 --- a/airflow/ui/openapi-gen/requests/services.gen.ts +++ b/airflow/ui/openapi-gen/requests/services.gen.ts @@ -61,6 +61,8 @@ import type { GetVersionResponse, GetEventLogData, GetEventLogResponse, + GetEventLogsData, + GetEventLogsResponse, } from "./types.gen"; export class AssetService { @@ -919,4 +921,53 @@ 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: { + 422: "Validation Error", + }, + }); + } } diff --git a/airflow/ui/openapi-gen/requests/types.gen.ts b/airflow/ui/openapi-gen/requests/types.gen.ts index 20e9823c167d6..4d36e537fffde 100644 --- a/airflow/ui/openapi-gen/requests/types.gen.ts +++ b/airflow/ui/openapi-gen/requests/types.gen.ts @@ -297,6 +297,14 @@ export type DagTagPydantic = { dag_id: string; }; +/** + * Event Log Collection Response. + */ +export type EventLogCollectionResponse = { + event_logs: Array; + total_entries: number; +}; + /** * Event Log Response. */ @@ -741,6 +749,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 $OpenApiTs = { "/ui/next_run_assets/{dag_id}": { get: { @@ -1438,4 +1465,19 @@ export type $OpenApiTs = { }; }; }; + "/public/eventLogs/": { + get: { + req: GetEventLogsData; + res: { + /** + * Successful Response + */ + 200: EventLogCollectionResponse; + /** + * Validation Error + */ + 422: HTTPValidationError; + }; + }; + }; }; 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 0204fc5b8f2f5..0b4f1a1ded59e 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 @@ -183,3 +183,133 @@ def test_get_event_log(self, test_client, setup, event_log_key, expected_status_ expected_json[key] = value assert resp_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