Skip to content

Commit efda515

Browse files
authored
feat: add get all project migrations (#740)
1 parent 8ec73fd commit efda515

File tree

6 files changed

+83
-1
lines changed

6 files changed

+83
-1
lines changed

components/renku_data_services/project/api.spec.yaml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,26 @@ paths:
148148
$ref: "#/components/responses/Error"
149149
tags:
150150
- projects
151+
/renku_v1_projects/migrations:
152+
get:
153+
summary: Return list of projects migrated from v1
154+
responses:
155+
"200":
156+
description: List of project migrations
157+
content:
158+
application/json:
159+
schema:
160+
$ref: "#/components/schemas/ProjectMigrationList"
161+
"404":
162+
description: No exist project migration
163+
content:
164+
application/json:
165+
schema:
166+
$ref: "#/components/schemas/ErrorResponse"
167+
default:
168+
$ref: "#/components/responses/Error"
169+
tags:
170+
- projects_migrations
151171
/renku_v1_projects/{v1_id}/migrations:
152172
get:
153173
summary: Check if a v1 project has been migrated to v2
@@ -1011,6 +1031,12 @@ components:
10111031
description: A flag to filter projects where the user is a direct member.
10121032
type: boolean
10131033
default: false
1034+
ProjectMigrationList:
1035+
description: A list of project migrations
1036+
type: array
1037+
items:
1038+
$ref: "#/components/schemas/ProjectMigrationInfo"
1039+
minItems: 0
10141040
ProjectMigrationInfo:
10151041
description: Information if a project is a migrated project
10161042
type: object

components/renku_data_services/project/apispec.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# generated by datamodel-codegen:
22
# filename: api.spec.yaml
3-
# timestamp: 2025-05-05T09:34:43+00:00
3+
# timestamp: 2025-05-06T08:33:41+00:00
44

55
from __future__ import annotations
66

@@ -249,6 +249,12 @@ class ProjectGetQuery(PaginationRequest):
249249
)
250250

251251

252+
class ProjectMigrationList(RootModel[List[ProjectMigrationInfo]]):
253+
root: List[ProjectMigrationInfo] = Field(
254+
..., description="A list of project migrations", min_length=0
255+
)
256+
257+
252258
class SessionSecretSlot(BaseAPISpec):
253259
model_config = ConfigDict(
254260
extra="forbid",

components/renku_data_services/project/blueprints.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,22 @@ async def _post(_: Request, user: base_models.APIUser, body: apispec.ProjectPost
8989

9090
return "/projects", ["POST"], _post
9191

92+
def get_all_migrations(self) -> BlueprintFactoryResponse:
93+
"""List all project migrations."""
94+
95+
@authenticate(self.authenticator)
96+
@only_authenticated
97+
async def _get_all_migrations(_: Request, user: base_models.APIUser) -> JSONResponse:
98+
project_migrations = self.project_migration_repo.get_project_migrations(user=user)
99+
100+
migrations_list = []
101+
async for migration in project_migrations:
102+
migrations_list.append(self._dump_project_migration(migration))
103+
104+
return validated_json(apispec.ProjectMigrationList, migrations_list)
105+
106+
return "/renku_v1_projects/migrations", ["GET"], _get_all_migrations
107+
92108
def get_migration(self) -> BlueprintFactoryResponse:
93109
"""Get project migration by project v1 id."""
94110

@@ -381,6 +397,16 @@ def _dump_project(project: project_models.Project, with_documentation: bool = Fa
381397
result = dict(result, documentation=project.documentation)
382398
return result
383399

400+
@staticmethod
401+
def _dump_project_migration(project_migration: project_models.ProjectMigrationInfo) -> dict[str, Any]:
402+
"""Dumps a project migration for API responses."""
403+
result = dict(
404+
project_id=project_migration.project_id,
405+
v1_id=project_migration.v1_id,
406+
launcher_id=project_migration.launcher_id,
407+
)
408+
return result
409+
384410

385411
@dataclass(kw_only=True)
386412
class ProjectSessionSecretBP(CustomBlueprint):

components/renku_data_services/project/db.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -925,6 +925,19 @@ def __init__(
925925
self.event_repo: EventRepository = event_repo
926926
self.session_repo = session_repo
927927

928+
async def get_project_migrations(
929+
self,
930+
user: base_models.APIUser,
931+
) -> AsyncGenerator[models.ProjectMigrationInfo, None]:
932+
"""Get all project migrations from the database."""
933+
project_ids = await self.authz.resources_with_permission(user, user.id, ResourceType.project, Scope.READ)
934+
935+
async with self.session_maker() as session:
936+
stmt = select(schemas.ProjectMigrationsORM).where(schemas.ProjectMigrationsORM.project_id.in_(project_ids))
937+
result = await session.stream_scalars(stmt)
938+
async for migration in result:
939+
yield migration.dump()
940+
928941
@with_db_transaction
929942
@Authz.authz_change(AuthzOperation.create, ResourceType.project)
930943
@dispatch_message(avro_schema_v2.ProjectCreated)

components/renku_data_services/project/orm.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,3 +246,9 @@ class ProjectMigrationsORM(BaseORM):
246246

247247
launcher_id: Mapped[Optional[ULID]] = mapped_column(ULIDType, nullable=True, default=None)
248248
"""Stores the launcher ID without enforcing a foreign key."""
249+
250+
def dump(self) -> models.ProjectMigrationInfo:
251+
"""Create a project model from the ProjectMigrationInfoORM."""
252+
return models.ProjectMigrationInfo(
253+
project_id=self.project_id, v1_id=self.project_v1_id, launcher_id=self.launcher_id
254+
)

test/bases/renku_data_services/data_api/test_projects.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1902,3 +1902,8 @@ async def test_migrate_v1_project(
19021902
migrated_project = response.json
19031903
assert migrated_project["v1_id"] == v1_id
19041904
assert migrated_project["project_id"] == project_id
1905+
1906+
_, response = await sanic_client.get("/api/data/renku_v1_projects/migrations", headers=user_headers)
1907+
assert response.status_code == 200, response.text
1908+
migrated_projects = response.json
1909+
assert {project_migration["v1_id"] for project_migration in migrated_projects} == {1122}

0 commit comments

Comments
 (0)