Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -185,14 +185,14 @@ async def _page_projects(
limit: int,
offset: int,
show_hidden: bool,
search: str | None = None,
project_name_search: str | None = None,
) -> Page[ProjectGet]:
assert 1 <= limit <= MAXIMUM_NUMBER_OF_ITEMS_PER_PAGE # nosec
assert offset >= 0 # nosec

optional: dict[str, Any] = {}
if search is not None:
optional["search"] = search
if project_name_search is not None:
optional["filters"] = {"project_name_search": project_name_search}

with service_exception_handler(
service_name="Webserver",
Expand Down Expand Up @@ -353,9 +353,7 @@ async def get_projects_w_solver_page(
limit=limit,
offset=offset,
show_hidden=True,
# WARNING: better way to match jobs with projects (Next PR if this works fine!)
# WARNING: search text has a limit that I needed to increase for the example!
search=solver_name,
project_name_search=solver_name,
)

async def get_projects_page(self, *, limit: int, offset: int):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,12 +60,14 @@ async def list_projects( # pylint: disable=too-many-arguments
project_type: ProjectTypeAPI,
show_hidden: bool,
trashed: bool | None,
# search
multi_column_search: str | None = None,
project_name_search: str | None = None,
# pagination
offset: NonNegativeInt,
limit: int,
# ordering
order_by: OrderBy,
# search
search: str | None,
) -> tuple[list[ProjectDict], int]:
app = request.app
db = ProjectDBAPI.get_from_app_context(app)
Expand Down Expand Up @@ -116,7 +118,8 @@ async def list_projects( # pylint: disable=too-many-arguments
filter_trashed=trashed,
filter_hidden=show_hidden,
# composed attrs
filter_by_text=search,
multi_column_search=multi_column_search,
project_name_search=project_name_search,
# pagination
offset=offset,
limit=limit,
Expand Down Expand Up @@ -154,7 +157,8 @@ async def list_projects_full_depth(
limit: int,
order_by: OrderBy,
# search
text: str | None,
multi_column_search: str | None,
project_name_search: str | None,
) -> tuple[list[ProjectDict], int]:
db = ProjectDBAPI.get_from_app_context(request.app)

Expand All @@ -169,9 +173,10 @@ async def list_projects_full_depth(
folder_query=FolderQuery(folder_scope=FolderScope.ALL),
filter_trashed=trashed,
filter_by_services=user_available_services,
filter_by_text=text,
filter_tag_ids_list=tag_ids_list,
filter_by_project_type=ProjectType.STANDARD,
multi_column_search=multi_column_search,
project_name_search=project_name_search,
offset=offset,
limit=limit,
order_by=order_by,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -203,12 +203,13 @@ async def list_projects(request: web.Request):
project_type=query_params.project_type,
show_hidden=query_params.show_hidden,
trashed=query_params.filters.trashed,
limit=query_params.limit,
offset=query_params.offset,
search=query_params.search,
order_by=OrderBy.model_construct(**query_params.order_by.model_dump()),
folder_id=query_params.folder_id,
workspace_id=query_params.workspace_id,
multi_column_search=query_params.search,
project_name_search=query_params.filters.project_name_search,
offset=query_params.offset,
limit=query_params.limit,
order_by=OrderBy.model_construct(**query_params.order_by.model_dump()),
)

page = Page[ProjectDict].model_validate(
Expand Down Expand Up @@ -246,10 +247,11 @@ async def list_projects_full_search(request: web.Request):
product_name=req_ctx.product_name,
trashed=query_params.filters.trashed,
tag_ids_list=tag_ids_list,
multi_column_search=query_params.text,
project_name_search=None,
offset=query_params.offset,
limit=query_params.limit,
order_by=OrderBy.model_construct(**query_params.order_by.model_dump()),
text=query_params.text,
)

page = Page[ProjectDict].model_validate(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,10 @@ class ProjectFilters(Filters):
default=False,
description="Set to true to list trashed, false to list non-trashed (default), None to list all",
)
project_name_search: str | None = Field(
default=None,
description="A search query to filter projects by their name. This field performs a case-insensitive partial match against the project name field.",
)


ProjectsListOrderParams = create_ordering_query_model_class(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -372,8 +372,10 @@ async def list_projects( # pylint: disable=too-many-arguments,too-many-statemen
filter_published: bool | None = False,
filter_hidden: bool | None = False,
filter_trashed: bool | None = False,
filter_by_text: str | None = None,
filter_tag_ids_list: list[int] | None = None,
# search
multi_column_search: str | None = None,
project_name_search: str | None = None,
# pagination
offset: int | None = 0,
limit: int | None = None,
Expand Down Expand Up @@ -464,7 +466,7 @@ async def list_projects( # pylint: disable=too-many-arguments,too-many-statemen
& (projects_to_products.c.product_name == product_name)
)
)
if filter_by_text is not None:
if multi_column_search is not None:
private_workspace_query = private_workspace_query.join(
users, users.c.id == projects.c.prj_owner, isouter=True
)
Expand Down Expand Up @@ -538,7 +540,7 @@ async def list_projects( # pylint: disable=too-many-arguments,too-many-statemen
== workspace_query.workspace_id # <-- Specific shared workspace
)

if filter_by_text is not None:
if multi_column_search is not None:
# NOTE: fields searched with text include user's email
shared_workspace_query = shared_workspace_query.join(
users, users.c.id == projects.c.prj_owner, isouter=True
Expand Down Expand Up @@ -574,12 +576,16 @@ async def list_projects( # pylint: disable=too-many-arguments,too-many-statemen
# not marked as trashed
else projects.c.trashed.is_(None)
)
if filter_by_text is not None:
if multi_column_search is not None:
attributes_filters.append(
(projects.c.name.ilike(f"%{filter_by_text}%"))
| (projects.c.description.ilike(f"%{filter_by_text}%"))
| (projects.c.uuid.ilike(f"%{filter_by_text}%"))
| (users.c.name.ilike(f"%{filter_by_text}%"))
(projects.c.name.ilike(f"%{multi_column_search}%"))
| (projects.c.description.ilike(f"%{multi_column_search}%"))
| (projects.c.uuid.ilike(f"%{multi_column_search}%"))
| (users.c.name.ilike(f"%{multi_column_search}%"))
)
if project_name_search is not None:
attributes_filters.append(
projects.c.name.like(f"%{project_name_search}%")
)
if filter_tag_ids_list:
attributes_filters.append(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,27 @@ async def test_list_projects_with_search_parameter(
data, 1, 0, 1, "/v0/projects?search=nAmE+5&offset=0&limit=20", 1
)

# Now we will test specific project name search (used by the API server)
query_parameters = {"filters": '{"project_name_search": "Yoda"}'}
url = base_url.with_query(**query_parameters)
assert (
f"{url}"
== f"/{api_version_prefix}/projects?filters=%7B%22project_name_search%22:+%22Yoda%22%7D"
)

resp = await client.get(f"{url}")
data = await resp.json()

assert resp.status == 200
_assert_response_data(
data,
1,
0,
1,
"/v0/projects?filters=%7B%22project_name_search%22:+%22Yoda%22%7D&offset=0&limit=20",
1,
)

# Now we will test part of uuid search
query_parameters = {"search": "2-fe1b-11ed-b038-cdb1"}
url = base_url.with_query(**query_parameters)
Expand Down
Loading