Skip to content

Commit

Permalink
🎨🐛 web-api: projects search can query filters and fixes on projects r…
Browse files Browse the repository at this point in the history
  • Loading branch information
pcrespov authored Jan 7, 2025
1 parent e8046a8 commit 5405b46
Show file tree
Hide file tree
Showing 9 changed files with 247 additions and 31 deletions.
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

0 comments on commit 5405b46

Please sign in to comment.