Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🎨🐛 web-api: projects search can query filters and fixes on projects repo #7004

Merged
merged 8 commits into from
Jan 7, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/models-library/src/models_library/workspaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ def validate_workspace_id(cls, value, info: ValidationInfo):
if scope == WorkspaceScope.SHARED and value is None:
msg = f"workspace_id must be provided when workspace_scope is SHARED. Got {scope=}, {value=}"
raise ValueError(msg)

if scope != WorkspaceScope.SHARED and value is not None:
msg = f"workspace_id should be None when workspace_scope is not SHARED. Got {scope=}, {value=}"
raise ValueError(msg)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3964,6 +3964,16 @@ paths:
summary: List Projects Full Search
operationId: list_projects_full_search
parameters:
- name: filters
in: query
required: false
schema:
anyOf:
- type: string
contentMediaType: application/json
contentSchema: {}
- type: 'null'
title: Filters
- name: order_by
in: query
required: false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -196,8 +196,9 @@ async def list_folders(
)


async def list_folders_full_search(
async def list_folders_full_depth(
app: web.Application,
*,
user_id: UserID,
product_name: ProductName,
text: str | None,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ async def list_folders_full_search(request: web.Request):
if not query_params.filters:
query_params.filters = FolderFilters()

folders: FolderGetPage = await _folders_api.list_folders_full_search(
folders: FolderGetPage = await _folders_api.list_folders_full_depth(
app=request.app,
user_id=req_ctx.user_id,
product_name=req_ctx.product_name,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,28 +144,33 @@ async def list_projects( # pylint: disable=too-many-arguments
return projects, total_number_projects


async def list_projects_full_search(
async def list_projects_full_depth(
request,
*,
user_id: UserID,
product_name: str,
# attrs filter
trashed: bool | None,
tag_ids_list: list[int],
# pagination
offset: NonNegativeInt,
limit: int,
text: str | None,
order_by: OrderBy,
tag_ids_list: list[int],
# search
text: str | None,
) -> tuple[list[ProjectDict], int]:
db = ProjectDBAPI.get_from_app_context(request.app)

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

(db_projects, db_project_types, total_number_projects,) = await db.list_projects(
db_projects, db_project_types, total_number_projects = await db.list_projects(
product_name=product_name,
user_id=user_id,
workspace_query=WorkspaceQuery(workspace_scope=WorkspaceScope.ALL),
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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -232,17 +232,21 @@ async def list_projects_full_search(request: web.Request):
query_params: ProjectsSearchQueryParams = parse_request_query_parameters_as(
ProjectsSearchQueryParams, request
)
if not query_params.filters:
query_params.filters = ProjectFilters()

tag_ids_list = query_params.tag_ids_list()

projects, total_number_of_projects = await _crud_api_read.list_projects_full_search(
projects, total_number_of_projects = await _crud_api_read.list_projects_full_depth(
request,
user_id=req_ctx.user_id,
product_name=req_ctx.product_name,
limit=query_params.limit,
trashed=query_params.filters.trashed,
tag_ids_list=tag_ids_list,
offset=query_params.offset,
text=query_params.text,
limit=query_params.limit,
order_by=OrderBy.model_construct(**query_params.order_by.model_dump()),
tag_ids_list=tag_ids_list,
text=query_params.text,
)

page = Page[ProjectDict].model_validate(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,10 @@ class ProjectActiveQueryParams(BaseModel):
client_session_id: str


class ProjectSearchExtraQueryParams(PageQueryParameters):
class ProjectSearchExtraQueryParams(
PageQueryParameters,
FiltersQueryParameters[ProjectFilters],
):
text: str | None = Field(
default=None,
description="Multi column full text search, across all folders and workspaces",
Expand Down
45 changes: 25 additions & 20 deletions services/web/server/src/simcore_service_webserver/projects/db.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import logging
from contextlib import AsyncExitStack
from typing import Any, cast
from typing import Any, Self, cast
from uuid import uuid1

import sqlalchemy as sa
Expand Down Expand Up @@ -43,6 +43,7 @@
from simcore_postgres_database.models.projects_tags import projects_tags
from simcore_postgres_database.models.projects_to_folders import projects_to_folders
from simcore_postgres_database.models.projects_to_products import projects_to_products
from simcore_postgres_database.models.projects_to_wallet import projects_to_wallet
from simcore_postgres_database.models.wallets import wallets
from simcore_postgres_database.models.workspaces_access_rights import (
workspaces_access_rights,
Expand All @@ -64,7 +65,6 @@
from tenacity.asyncio import AsyncRetrying
from tenacity.retry import retry_if_exception_type

from ..db.models import projects_tags, projects_to_wallet
from ..utils import now_str
from ._comments_db import (
create_project_comment,
Expand Down Expand Up @@ -104,6 +104,10 @@
APP_PROJECT_DBAPI = __name__ + ".ProjectDBAPI"
ANY_USER = ANY_USER_ID_SENTINEL

DEFAULT_ORDER_BY = OrderBy(
field=IDStr("last_change_date"), direction=OrderDirection.DESC
)

# pylint: disable=too-many-public-methods
# NOTE: https://github.com/ITISFoundation/osparc-simcore/issues/3516

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

@classmethod
def get_from_app_context(cls, app: web.Application) -> "ProjectDBAPI":
db: "ProjectDBAPI" = app[APP_PROJECT_DBAPI]
def get_from_app_context(cls, app: web.Application) -> Self:
db = app[APP_PROJECT_DBAPI]
assert isinstance(db, cls) # nosec
return db

@classmethod
def set_once_in_app_context(cls, app: web.Application) -> "ProjectDBAPI":
def set_once_in_app_context(cls, app: web.Application) -> Self:
if app.get(APP_PROJECT_DBAPI) is None:
app[APP_PROJECT_DBAPI] = ProjectDBAPI(app)
db: ProjectDBAPI = app[APP_PROJECT_DBAPI]
return db
app[APP_PROJECT_DBAPI] = cls(app)
return cls.get_from_app_context(app)

@property
def engine(self) -> Engine:
Expand Down Expand Up @@ -374,10 +378,9 @@ async def list_projects( # pylint: disable=too-many-arguments,too-many-statemen
offset: int | None = 0,
limit: int | None = None,
# order
order_by: OrderBy = OrderBy(
field=IDStr("last_change_date"), direction=OrderDirection.DESC
),
order_by: OrderBy = DEFAULT_ORDER_BY,
) -> tuple[list[dict[str, Any]], list[ProjectType], int]:

if filter_tag_ids_list is None:
filter_tag_ids_list = []

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

if workspace_query.workspace_scope is not WorkspaceScope.PRIVATE:
assert workspace_query.workspace_scope in (

assert workspace_query.workspace_scope in ( # nosec
WorkspaceScope.SHARED,
WorkspaceScope.ALL,
) # nosec
)

shared_workspace_query = (
sa.select(
Expand Down Expand Up @@ -525,20 +529,21 @@ async def list_projects( # pylint: disable=too-many-arguments,too-many-statemen
None
) # <-- All shared workspaces
)
if filter_by_text is not None:
shared_workspace_query = shared_workspace_query.join(
users, users.c.id == projects.c.prj_owner, isouter=True
)

else:
assert (
assert ( # nosec
workspace_query.workspace_scope == WorkspaceScope.SHARED
) # nosec
)
shared_workspace_query = shared_workspace_query.where(
projects.c.workspace_id
== workspace_query.workspace_id # <-- Specific shared workspace
)

if filter_by_text 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
)

else:
shared_workspace_query = None

Expand Down
Loading
Loading