Skip to content

Commit 5405b46

Browse files
authored
🎨🐛 web-api: projects search can query filters and fixes on projects repo (#7004)
1 parent e8046a8 commit 5405b46

File tree

9 files changed

+247
-31
lines changed

9 files changed

+247
-31
lines changed

packages/models-library/src/models_library/workspaces.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ def validate_workspace_id(cls, value, info: ValidationInfo):
3636
if scope == WorkspaceScope.SHARED and value is None:
3737
msg = f"workspace_id must be provided when workspace_scope is SHARED. Got {scope=}, {value=}"
3838
raise ValueError(msg)
39+
3940
if scope != WorkspaceScope.SHARED and value is not None:
4041
msg = f"workspace_id should be None when workspace_scope is not SHARED. Got {scope=}, {value=}"
4142
raise ValueError(msg)

services/web/server/src/simcore_service_webserver/api/v0/openapi.yaml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3964,6 +3964,16 @@ paths:
39643964
summary: List Projects Full Search
39653965
operationId: list_projects_full_search
39663966
parameters:
3967+
- name: filters
3968+
in: query
3969+
required: false
3970+
schema:
3971+
anyOf:
3972+
- type: string
3973+
contentMediaType: application/json
3974+
contentSchema: {}
3975+
- type: 'null'
3976+
title: Filters
39673977
- name: order_by
39683978
in: query
39693979
required: false

services/web/server/src/simcore_service_webserver/folders/_folders_api.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -196,8 +196,9 @@ async def list_folders(
196196
)
197197

198198

199-
async def list_folders_full_search(
199+
async def list_folders_full_depth(
200200
app: web.Application,
201+
*,
201202
user_id: UserID,
202203
product_name: ProductName,
203204
text: str | None,

services/web/server/src/simcore_service_webserver/folders/_folders_handlers.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ async def list_folders_full_search(request: web.Request):
112112
if not query_params.filters:
113113
query_params.filters = FolderFilters()
114114

115-
folders: FolderGetPage = await _folders_api.list_folders_full_search(
115+
folders: FolderGetPage = await _folders_api.list_folders_full_depth(
116116
app=request.app,
117117
user_id=req_ctx.user_id,
118118
product_name=req_ctx.product_name,

services/web/server/src/simcore_service_webserver/projects/_crud_api_read.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -144,28 +144,33 @@ async def list_projects( # pylint: disable=too-many-arguments
144144
return projects, total_number_projects
145145

146146

147-
async def list_projects_full_search(
147+
async def list_projects_full_depth(
148148
request,
149149
*,
150150
user_id: UserID,
151151
product_name: str,
152+
# attrs filter
153+
trashed: bool | None,
154+
tag_ids_list: list[int],
155+
# pagination
152156
offset: NonNegativeInt,
153157
limit: int,
154-
text: str | None,
155158
order_by: OrderBy,
156-
tag_ids_list: list[int],
159+
# search
160+
text: str | None,
157161
) -> tuple[list[ProjectDict], int]:
158162
db = ProjectDBAPI.get_from_app_context(request.app)
159163

160164
user_available_services: list[dict] = await get_services_for_user_in_product(
161165
request.app, user_id, product_name, only_key_versions=True
162166
)
163167

164-
(db_projects, db_project_types, total_number_projects,) = await db.list_projects(
168+
db_projects, db_project_types, total_number_projects = await db.list_projects(
165169
product_name=product_name,
166170
user_id=user_id,
167171
workspace_query=WorkspaceQuery(workspace_scope=WorkspaceScope.ALL),
168172
folder_query=FolderQuery(folder_scope=FolderScope.ALL),
173+
filter_trashed=trashed,
169174
filter_by_services=user_available_services,
170175
filter_by_text=text,
171176
filter_tag_ids_list=tag_ids_list,

services/web/server/src/simcore_service_webserver/projects/_crud_handlers.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -232,17 +232,21 @@ async def list_projects_full_search(request: web.Request):
232232
query_params: ProjectsSearchQueryParams = parse_request_query_parameters_as(
233233
ProjectsSearchQueryParams, request
234234
)
235+
if not query_params.filters:
236+
query_params.filters = ProjectFilters()
237+
235238
tag_ids_list = query_params.tag_ids_list()
236239

237-
projects, total_number_of_projects = await _crud_api_read.list_projects_full_search(
240+
projects, total_number_of_projects = await _crud_api_read.list_projects_full_depth(
238241
request,
239242
user_id=req_ctx.user_id,
240243
product_name=req_ctx.product_name,
241-
limit=query_params.limit,
244+
trashed=query_params.filters.trashed,
245+
tag_ids_list=tag_ids_list,
242246
offset=query_params.offset,
243-
text=query_params.text,
247+
limit=query_params.limit,
244248
order_by=OrderBy.model_construct(**query_params.order_by.model_dump()),
245-
tag_ids_list=tag_ids_list,
249+
text=query_params.text,
246250
)
247251

248252
page = Page[ProjectDict].model_validate(

services/web/server/src/simcore_service_webserver/projects/_crud_handlers_models.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,10 @@ class ProjectActiveQueryParams(BaseModel):
163163
client_session_id: str
164164

165165

166-
class ProjectSearchExtraQueryParams(PageQueryParameters):
166+
class ProjectSearchExtraQueryParams(
167+
PageQueryParameters,
168+
FiltersQueryParameters[ProjectFilters],
169+
):
167170
text: str | None = Field(
168171
default=None,
169172
description="Multi column full text search, across all folders and workspaces",

services/web/server/src/simcore_service_webserver/projects/db.py

Lines changed: 25 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
import logging
99
from contextlib import AsyncExitStack
10-
from typing import Any, cast
10+
from typing import Any, Self, cast
1111
from uuid import uuid1
1212

1313
import sqlalchemy as sa
@@ -43,6 +43,7 @@
4343
from simcore_postgres_database.models.projects_tags import projects_tags
4444
from simcore_postgres_database.models.projects_to_folders import projects_to_folders
4545
from simcore_postgres_database.models.projects_to_products import projects_to_products
46+
from simcore_postgres_database.models.projects_to_wallet import projects_to_wallet
4647
from simcore_postgres_database.models.wallets import wallets
4748
from simcore_postgres_database.models.workspaces_access_rights import (
4849
workspaces_access_rights,
@@ -64,7 +65,6 @@
6465
from tenacity.asyncio import AsyncRetrying
6566
from tenacity.retry import retry_if_exception_type
6667

67-
from ..db.models import projects_tags, projects_to_wallet
6868
from ..utils import now_str
6969
from ._comments_db import (
7070
create_project_comment,
@@ -104,6 +104,10 @@
104104
APP_PROJECT_DBAPI = __name__ + ".ProjectDBAPI"
105105
ANY_USER = ANY_USER_ID_SENTINEL
106106

107+
DEFAULT_ORDER_BY = OrderBy(
108+
field=IDStr("last_change_date"), direction=OrderDirection.DESC
109+
)
110+
107111
# pylint: disable=too-many-public-methods
108112
# NOTE: https://github.com/ITISFoundation/osparc-simcore/issues/3516
109113

@@ -121,16 +125,16 @@ def _init_engine(self) -> None:
121125
raise ValueError(msg)
122126

123127
@classmethod
124-
def get_from_app_context(cls, app: web.Application) -> "ProjectDBAPI":
125-
db: "ProjectDBAPI" = app[APP_PROJECT_DBAPI]
128+
def get_from_app_context(cls, app: web.Application) -> Self:
129+
db = app[APP_PROJECT_DBAPI]
130+
assert isinstance(db, cls) # nosec
126131
return db
127132

128133
@classmethod
129-
def set_once_in_app_context(cls, app: web.Application) -> "ProjectDBAPI":
134+
def set_once_in_app_context(cls, app: web.Application) -> Self:
130135
if app.get(APP_PROJECT_DBAPI) is None:
131-
app[APP_PROJECT_DBAPI] = ProjectDBAPI(app)
132-
db: ProjectDBAPI = app[APP_PROJECT_DBAPI]
133-
return db
136+
app[APP_PROJECT_DBAPI] = cls(app)
137+
return cls.get_from_app_context(app)
134138

135139
@property
136140
def engine(self) -> Engine:
@@ -374,10 +378,9 @@ async def list_projects( # pylint: disable=too-many-arguments,too-many-statemen
374378
offset: int | None = 0,
375379
limit: int | None = None,
376380
# order
377-
order_by: OrderBy = OrderBy(
378-
field=IDStr("last_change_date"), direction=OrderDirection.DESC
379-
),
381+
order_by: OrderBy = DEFAULT_ORDER_BY,
380382
) -> tuple[list[dict[str, Any]], list[ProjectType], int]:
383+
381384
if filter_tag_ids_list is None:
382385
filter_tag_ids_list = []
383386

@@ -473,10 +476,11 @@ async def list_projects( # pylint: disable=too-many-arguments,too-many-statemen
473476
###
474477

475478
if workspace_query.workspace_scope is not WorkspaceScope.PRIVATE:
476-
assert workspace_query.workspace_scope in (
479+
480+
assert workspace_query.workspace_scope in ( # nosec
477481
WorkspaceScope.SHARED,
478482
WorkspaceScope.ALL,
479-
) # nosec
483+
)
480484

481485
shared_workspace_query = (
482486
sa.select(
@@ -525,20 +529,21 @@ async def list_projects( # pylint: disable=too-many-arguments,too-many-statemen
525529
None
526530
) # <-- All shared workspaces
527531
)
528-
if filter_by_text is not None:
529-
shared_workspace_query = shared_workspace_query.join(
530-
users, users.c.id == projects.c.prj_owner, isouter=True
531-
)
532-
533532
else:
534-
assert (
533+
assert ( # nosec
535534
workspace_query.workspace_scope == WorkspaceScope.SHARED
536-
) # nosec
535+
)
537536
shared_workspace_query = shared_workspace_query.where(
538537
projects.c.workspace_id
539538
== workspace_query.workspace_id # <-- Specific shared workspace
540539
)
541540

541+
if filter_by_text is not None:
542+
# NOTE: fields searched with text include user's email
543+
shared_workspace_query = shared_workspace_query.join(
544+
users, users.c.id == projects.c.prj_owner, isouter=True
545+
)
546+
542547
else:
543548
shared_workspace_query = None
544549

0 commit comments

Comments
 (0)