From 626ea405bf2d46be4e5ac29f62299c6a90597673 Mon Sep 17 00:00:00 2001 From: Jvst Me Date: Wed, 10 Dec 2025 00:06:01 +0100 Subject: [PATCH] Add `EventTarget.project_name` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Return the project name for all targets in `/api/events/list`. Required for displaying event targets and building entity URLs in the UI. Implemented by joining event targets with projects. The performance effect of the join are tolerable — with a dataset of 1.8 million events, requests with various filters take about 0.05s with hot Postgres cache, and up to ~0.75s with cold cache, which is similar to previous measurements. --- src/dstack/_internal/core/models/events.py | 9 +++++++++ src/dstack/_internal/server/services/events.py | 8 +++++++- src/tests/_internal/server/routers/test_events.py | 3 +++ 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/dstack/_internal/core/models/events.py b/src/dstack/_internal/core/models/events.py index 3fb556101e..caf6d60e47 100644 --- a/src/dstack/_internal/core/models/events.py +++ b/src/dstack/_internal/core/models/events.py @@ -37,6 +37,15 @@ class EventTarget(CoreModel): ) ), ] + project_name: Annotated[ + Optional[str], + Field( + description=( + "Name of the project the target entity belongs to," + " or `null` for target types not bound to a project (e.g., users)" + ) + ), + ] id: Annotated[uuid.UUID, Field(description="ID of the target entity")] name: Annotated[str, Field(description="Name of the target entity")] diff --git a/src/dstack/_internal/server/services/events.py b/src/dstack/_internal/server/services/events.py index 34e7b72378..028570fb86 100644 --- a/src/dstack/_internal/server/services/events.py +++ b/src/dstack/_internal/server/services/events.py @@ -370,7 +370,12 @@ async def list_events( .order_by(*order_by) .limit(limit) .options( - joinedload(EventModel.targets), + ( + joinedload(EventModel.targets) + .joinedload(EventTargetModel.entity_project) + .load_only(ProjectModel.name) + .noload(ProjectModel.owner) + ), joinedload(EventModel.actor_user).load_only(UserModel.name), ) ) @@ -395,6 +400,7 @@ def event_model_to_event(event_model: EventModel) -> Event: EventTarget( type=target.entity_type, project_id=target.entity_project_id, + project_name=target.entity_project.name if target.entity_project else None, id=target.entity_id, name=target.entity_name, ) diff --git a/src/tests/_internal/server/routers/test_events.py b/src/tests/_internal/server/routers/test_events.py index 149f28ee3a..8cdf386661 100644 --- a/src/tests/_internal/server/routers/test_events.py +++ b/src/tests/_internal/server/routers/test_events.py @@ -79,6 +79,7 @@ async def test_response_format(self, session: AsyncSession, client: AsyncClient) { "type": "project", "project_id": str(project.id), + "project_name": "test_project", "id": str(project.id), "name": "test_project", }, @@ -94,12 +95,14 @@ async def test_response_format(self, session: AsyncSession, client: AsyncClient) { "type": "project", "project_id": str(project.id), + "project_name": "test_project", "id": str(project.id), "name": "test_project", }, { "type": "user", "project_id": None, + "project_name": None, "id": str(user.id), "name": "test_user", },