Skip to content

Commit bcc8e34

Browse files
🐛 Add project name search parameter for project listing for the API server usecase. (#7066)
1 parent 61eb77b commit bcc8e34

File tree

6 files changed

+63
-24
lines changed

6 files changed

+63
-24
lines changed

services/api-server/src/simcore_service_api_server/services_http/webserver.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# pylint: disable=too-many-public-methods
22

3+
import json
34
import logging
45
import urllib.parse
56
from collections.abc import Mapping
@@ -185,14 +186,16 @@ async def _page_projects(
185186
limit: int,
186187
offset: int,
187188
show_hidden: bool,
188-
search: str | None = None,
189+
search_by_project_name: str | None = None,
189190
) -> Page[ProjectGet]:
190191
assert 1 <= limit <= MAXIMUM_NUMBER_OF_ITEMS_PER_PAGE # nosec
191192
assert offset >= 0 # nosec
192193

193194
optional: dict[str, Any] = {}
194-
if search is not None:
195-
optional["search"] = search
195+
if search_by_project_name is not None:
196+
filters_dict = {"search_by_project_name": search_by_project_name}
197+
filters_json = json.dumps(filters_dict)
198+
optional["filters"] = filters_json
196199

197200
with service_exception_handler(
198201
service_name="Webserver",
@@ -353,9 +356,7 @@ async def get_projects_w_solver_page(
353356
limit=limit,
354357
offset=offset,
355358
show_hidden=True,
356-
# WARNING: better way to match jobs with projects (Next PR if this works fine!)
357-
# WARNING: search text has a limit that I needed to increase for the example!
358-
search=solver_name,
359+
search_by_project_name=solver_name,
359360
)
360361

361362
async def get_projects_page(self, *, limit: int, offset: int):

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

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -62,12 +62,14 @@ async def list_projects( # pylint: disable=too-many-arguments
6262
project_type: ProjectTypeAPI,
6363
show_hidden: bool,
6464
trashed: bool | None,
65+
# search
66+
search_by_multi_columns: str | None = None,
67+
search_by_project_name: str | None = None,
6568
# pagination
6669
offset: NonNegativeInt,
6770
limit: int,
71+
# ordering
6872
order_by: OrderBy,
69-
# search
70-
search: str | None,
7173
) -> tuple[list[ProjectDict], int]:
7274
app = request.app
7375
db = ProjectDBAPI.get_from_app_context(app)
@@ -118,7 +120,8 @@ async def list_projects( # pylint: disable=too-many-arguments
118120
filter_trashed=trashed,
119121
filter_hidden=show_hidden,
120122
# composed attrs
121-
filter_by_text=search,
123+
search_by_multi_columns=search_by_multi_columns,
124+
search_by_project_name=search_by_project_name,
122125
# pagination
123126
offset=offset,
124127
limit=limit,
@@ -157,7 +160,8 @@ async def list_projects_full_depth(
157160
limit: int,
158161
order_by: OrderBy,
159162
# search
160-
text: str | None,
163+
search_by_multi_columns: str | None,
164+
search_by_project_name: str | None,
161165
) -> tuple[list[ProjectDict], int]:
162166
db = ProjectDBAPI.get_from_app_context(request.app)
163167

@@ -172,9 +176,10 @@ async def list_projects_full_depth(
172176
folder_query=FolderQuery(folder_scope=FolderScope.ALL),
173177
filter_trashed=trashed,
174178
filter_by_services=user_available_services,
175-
filter_by_text=text,
176179
filter_tag_ids_list=tag_ids_list,
177180
filter_by_project_type=ProjectType.STANDARD,
181+
search_by_multi_columns=search_by_multi_columns,
182+
search_by_project_name=search_by_project_name,
178183
offset=offset,
179184
limit=limit,
180185
order_by=order_by,

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

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -201,12 +201,13 @@ async def list_projects(request: web.Request):
201201
project_type=query_params.project_type,
202202
show_hidden=query_params.show_hidden,
203203
trashed=query_params.filters.trashed,
204-
limit=query_params.limit,
205-
offset=query_params.offset,
206-
search=query_params.search,
207-
order_by=OrderBy.model_construct(**query_params.order_by.model_dump()),
208204
folder_id=query_params.folder_id,
209205
workspace_id=query_params.workspace_id,
206+
search_by_multi_columns=query_params.search,
207+
search_by_project_name=query_params.filters.search_by_project_name,
208+
offset=query_params.offset,
209+
limit=query_params.limit,
210+
order_by=OrderBy.model_construct(**query_params.order_by.model_dump()),
210211
)
211212

212213
page = Page[ProjectDict].model_validate(
@@ -244,10 +245,11 @@ async def list_projects_full_search(request: web.Request):
244245
product_name=req_ctx.product_name,
245246
trashed=query_params.filters.trashed,
246247
tag_ids_list=tag_ids_list,
248+
search_by_multi_columns=query_params.text,
249+
search_by_project_name=query_params.filters.search_by_project_name,
247250
offset=query_params.offset,
248251
limit=query_params.limit,
249252
order_by=OrderBy.model_construct(**query_params.order_by.model_dump()),
250-
text=query_params.text,
251253
)
252254

253255
page = Page[ProjectDict].model_validate(

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,10 @@ class ProjectFilters(Filters):
9898
default=False,
9999
description="Set to true to list trashed, false to list non-trashed (default), None to list all",
100100
)
101+
search_by_project_name: str | None = Field(
102+
default=None,
103+
description="A search query to filter projects by their name. This field performs a case-insensitive partial match against the project name field.",
104+
)
101105

102106

103107
ProjectsListOrderParams = create_ordering_query_model_class(

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

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -372,8 +372,10 @@ async def list_projects( # pylint: disable=too-many-arguments,too-many-statemen
372372
filter_published: bool | None = False,
373373
filter_hidden: bool | None = False,
374374
filter_trashed: bool | None = False,
375-
filter_by_text: str | None = None,
376375
filter_tag_ids_list: list[int] | None = None,
376+
# search
377+
search_by_multi_columns: str | None = None,
378+
search_by_project_name: str | None = None,
377379
# pagination
378380
offset: int | None = 0,
379381
limit: int | None = None,
@@ -464,7 +466,7 @@ async def list_projects( # pylint: disable=too-many-arguments,too-many-statemen
464466
& (projects_to_products.c.product_name == product_name)
465467
)
466468
)
467-
if filter_by_text is not None:
469+
if search_by_multi_columns is not None:
468470
private_workspace_query = private_workspace_query.join(
469471
users, users.c.id == projects.c.prj_owner, isouter=True
470472
)
@@ -538,7 +540,7 @@ async def list_projects( # pylint: disable=too-many-arguments,too-many-statemen
538540
== workspace_query.workspace_id # <-- Specific shared workspace
539541
)
540542

541-
if filter_by_text is not None:
543+
if search_by_multi_columns is not None:
542544
# NOTE: fields searched with text include user's email
543545
shared_workspace_query = shared_workspace_query.join(
544546
users, users.c.id == projects.c.prj_owner, isouter=True
@@ -574,12 +576,16 @@ async def list_projects( # pylint: disable=too-many-arguments,too-many-statemen
574576
# not marked as trashed
575577
else projects.c.trashed_at.is_(None)
576578
)
577-
if filter_by_text is not None:
579+
if search_by_multi_columns is not None:
578580
attributes_filters.append(
579-
(projects.c.name.ilike(f"%{filter_by_text}%"))
580-
| (projects.c.description.ilike(f"%{filter_by_text}%"))
581-
| (projects.c.uuid.ilike(f"%{filter_by_text}%"))
582-
| (users.c.name.ilike(f"%{filter_by_text}%"))
581+
(projects.c.name.ilike(f"%{search_by_multi_columns}%"))
582+
| (projects.c.description.ilike(f"%{search_by_multi_columns}%"))
583+
| (projects.c.uuid.ilike(f"%{search_by_multi_columns}%"))
584+
| (users.c.name.ilike(f"%{search_by_multi_columns}%"))
585+
)
586+
if search_by_project_name is not None:
587+
attributes_filters.append(
588+
projects.c.name.like(f"%{search_by_project_name}%")
583589
)
584590
if filter_tag_ids_list:
585591
attributes_filters.append(

services/web/server/tests/unit/with_dbs/02/test_projects_crud_handlers__list_with_query_params.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,27 @@ async def test_list_projects_with_search_parameter(
184184
data, 1, 0, 1, "/v0/projects?search=nAmE+5&offset=0&limit=20", 1
185185
)
186186

187+
# Now we will test specific project name search (used by the API server)
188+
query_parameters = {"filters": '{"search_by_project_name": "Yoda"}'}
189+
url = base_url.with_query(**query_parameters)
190+
assert (
191+
f"{url}"
192+
== f"/{api_version_prefix}/projects?filters=%7B%22search_by_project_name%22:+%22Yoda%22%7D"
193+
)
194+
195+
resp = await client.get(f"{url}")
196+
data = await resp.json()
197+
198+
assert resp.status == 200
199+
_assert_response_data(
200+
data,
201+
1,
202+
0,
203+
1,
204+
"/v0/projects?filters=%7B%22search_by_project_name%22:+%22Yoda%22%7D&offset=0&limit=20",
205+
1,
206+
)
207+
187208
# Now we will test part of uuid search
188209
query_parameters = {"search": "2-fe1b-11ed-b038-cdb1"}
189210
url = base_url.with_query(**query_parameters)

0 commit comments

Comments
 (0)