Skip to content

Commit 4047b95

Browse files
committed
Add search and sort fields to list_functions
1 parent 81d1bd9 commit 4047b95

File tree

5 files changed

+234
-45
lines changed

5 files changed

+234
-45
lines changed

packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/webserver/functions/functions_rpc_interface.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,15 @@
1717
RegisteredFunctionJobCollection,
1818
)
1919
from models_library.functions import (
20+
FunctionClass,
2021
FunctionJobStatus,
2122
FunctionOutputs,
2223
FunctionUserAccessRights,
2324
FunctionUserApiAccessRights,
2425
)
2526
from models_library.products import ProductName
2627
from models_library.rabbitmq_basic_types import RPCMethodName
28+
from models_library.rest_ordering import OrderBy
2729
from models_library.rest_pagination import PageMetaInfoLimitOffset
2830
from models_library.users import UserID
2931
from pydantic import TypeAdapter
@@ -135,6 +137,10 @@ async def list_functions(
135137
product_name: ProductName,
136138
pagination_offset: int,
137139
pagination_limit: int,
140+
order_by: OrderBy | None = None,
141+
filter_by_function_class: FunctionClass | None = None,
142+
search_by_function_title: str | None = None,
143+
search_by_multi_columns: str | None = None,
138144
) -> tuple[list[RegisteredFunction], PageMetaInfoLimitOffset]:
139145
result: tuple[list[RegisteredFunction], PageMetaInfoLimitOffset] = (
140146
await rabbitmq_rpc_client.request(
@@ -144,6 +150,10 @@ async def list_functions(
144150
pagination_limit=pagination_limit,
145151
user_id=user_id,
146152
product_name=product_name,
153+
order_by=order_by,
154+
filter_by_function_class=filter_by_function_class,
155+
search_by_function_title=search_by_function_title,
156+
search_by_multi_columns=search_by_multi_columns,
147157
)
148158
)
149159
return TypeAdapter(

services/web/server/src/simcore_service_webserver/functions/_controller/_functions_rpc.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from models_library.functions import (
44
Function,
55
FunctionAccessRights,
6+
FunctionClass,
67
FunctionID,
78
FunctionInputs,
89
FunctionInputSchema,
@@ -40,6 +41,7 @@
4041
UnsupportedFunctionJobClassError,
4142
)
4243
from models_library.products import ProductName
44+
from models_library.rest_ordering import OrderBy
4345
from models_library.rest_pagination import PageMetaInfoLimitOffset
4446
from models_library.users import UserID
4547
from servicelib.rabbitmq import RPCRouter
@@ -176,13 +178,21 @@ async def list_functions(
176178
product_name: ProductName,
177179
pagination_limit: int,
178180
pagination_offset: int,
181+
order_by: OrderBy | None = None,
182+
filter_by_function_class: FunctionClass | None = None,
183+
search_by_function_title: str | None = None,
184+
search_by_multi_columns: str | None = None,
179185
) -> tuple[list[RegisteredFunction], PageMetaInfoLimitOffset]:
180186
return await _functions_service.list_functions(
181187
app=app,
182188
user_id=user_id,
183189
product_name=product_name,
184190
pagination_limit=pagination_limit,
185191
pagination_offset=pagination_offset,
192+
order_by=order_by,
193+
filter_by_function_class=filter_by_function_class,
194+
search_by_function_title=search_by_function_title,
195+
search_by_multi_columns=search_by_multi_columns,
186196
)
187197

188198

services/web/server/src/simcore_service_webserver/functions/_functions_repository.py

Lines changed: 78 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
import sqlalchemy
88
from aiohttp import web
9+
from models_library.basic_types import IDStr
910
from models_library.functions import (
1011
FunctionAccessRightsDB,
1112
FunctionClass,
@@ -54,6 +55,7 @@
5455
)
5556
from models_library.groups import GroupID
5657
from models_library.products import ProductName
58+
from models_library.rest_ordering import OrderBy, OrderDirection
5759
from models_library.rest_pagination import PageMetaInfoLimitOffset
5860
from models_library.users import UserID
5961
from pydantic import TypeAdapter
@@ -84,10 +86,10 @@
8486
pass_or_acquire_connection,
8587
transaction_context,
8688
)
87-
from sqlalchemy import Text, cast
89+
from sqlalchemy import String, Text, cast
8890
from sqlalchemy.engine.row import Row
8991
from sqlalchemy.ext.asyncio import AsyncConnection
90-
from sqlalchemy.sql import func
92+
from sqlalchemy.sql import ColumnElement, func
9193

9294
from ..db.plugin import get_asyncpg_engine
9395
from ..groups.api import list_all_user_groups_ids
@@ -110,6 +112,8 @@
110112
function_job_collections_access_rights_table, FunctionJobCollectionAccessRightsDB
111113
)
112114

115+
DEFAULT_ORDER_BY = OrderBy(field=IDStr("modified"), direction=OrderDirection.DESC)
116+
113117

114118
async def create_function( # noqa: PLR0913
115119
app: web.Application,
@@ -347,6 +351,39 @@ async def get_function(
347351
return RegisteredFunctionDB.model_validate(row)
348352

349353

354+
@staticmethod
355+
def _create_list_functions_attributes_filters(
356+
*,
357+
filter_by_function_class: FunctionClass | None,
358+
search_by_multi_columns: str | None,
359+
search_by_function_title: str | None,
360+
) -> list[ColumnElement]:
361+
attributes_filters: list[ColumnElement] = []
362+
363+
if filter_by_function_class is not None:
364+
attributes_filters.append(
365+
functions_table.c.function_class == filter_by_function_class.value
366+
)
367+
368+
if search_by_multi_columns is not None:
369+
attributes_filters.append(
370+
(functions_table.c.title.ilike(f"%{search_by_multi_columns}%"))
371+
| (functions_table.c.description.ilike(f"%{search_by_multi_columns}%"))
372+
| (
373+
cast(functions_table.c.uuid, String).ilike(
374+
f"%{search_by_multi_columns}%"
375+
)
376+
)
377+
)
378+
379+
if search_by_function_title is not None:
380+
attributes_filters.append(
381+
functions_table.c.title.like(f"%{search_by_function_title}%")
382+
)
383+
384+
return attributes_filters
385+
386+
350387
async def list_functions(
351388
app: web.Application,
352389
connection: AsyncConnection | None = None,
@@ -355,7 +392,12 @@ async def list_functions(
355392
product_name: ProductName,
356393
pagination_limit: int,
357394
pagination_offset: int,
395+
order_by: OrderBy | None = None,
396+
filter_by_function_class: FunctionClass | None = None,
397+
search_by_multi_columns: str | None = None,
398+
search_by_function_title: str | None = None,
358399
) -> tuple[list[RegisteredFunctionDB], PageMetaInfoLimitOffset]:
400+
359401
async with pass_or_acquire_connection(get_asyncpg_engine(app), connection) as conn:
360402
await check_user_api_access_rights(
361403
app,
@@ -365,39 +407,59 @@ async def list_functions(
365407
api_access_rights=[FunctionsApiAccessRights.READ_FUNCTIONS],
366408
)
367409
user_groups = await list_all_user_groups_ids(app, user_id=user_id)
410+
attributes_filters = _create_list_functions_attributes_filters(
411+
filter_by_function_class=filter_by_function_class,
412+
search_by_multi_columns=search_by_multi_columns,
413+
search_by_function_title=search_by_function_title,
414+
)
368415

369-
subquery = (
370-
functions_access_rights_table.select()
371-
.with_only_columns(functions_access_rights_table.c.function_uuid)
416+
# Build the base query with join to access rights table
417+
base_query = (
418+
functions_table.select()
419+
.join(
420+
functions_access_rights_table,
421+
functions_table.c.uuid == functions_access_rights_table.c.function_uuid,
422+
)
372423
.where(
373424
functions_access_rights_table.c.group_id.in_(user_groups),
374425
functions_access_rights_table.c.product_name == product_name,
375426
functions_access_rights_table.c.read,
427+
*attributes_filters,
376428
)
377429
)
378430

379-
total_count_result = await conn.scalar(
380-
func.count()
381-
.select()
382-
.select_from(functions_table)
383-
.where(functions_table.c.uuid.in_(subquery))
431+
# Get total count
432+
total_count = await conn.scalar(
433+
func.count().select().select_from(base_query.subquery())
384434
)
385-
if total_count_result == 0:
435+
if total_count == 0:
386436
return [], PageMetaInfoLimitOffset(
387437
total=0, offset=pagination_offset, limit=pagination_limit, count=0
388438
)
439+
440+
if order_by is None:
441+
order_by = DEFAULT_ORDER_BY
442+
# Apply ordering and pagination
443+
if order_by.direction == OrderDirection.ASC:
444+
base_query = base_query.order_by(
445+
sqlalchemy.asc(getattr(functions_table.c, order_by.field)),
446+
functions_table.c.uuid,
447+
)
448+
else:
449+
base_query = base_query.order_by(
450+
sqlalchemy.desc(getattr(functions_table.c, order_by.field)),
451+
functions_table.c.uuid,
452+
)
453+
389454
function_rows = [
390455
RegisteredFunctionDB.model_validate(row)
391456
async for row in await conn.stream(
392-
functions_table.select()
393-
.where(functions_table.c.uuid.in_(subquery))
394-
.offset(pagination_offset)
395-
.limit(pagination_limit)
457+
base_query.offset(pagination_offset).limit(pagination_limit)
396458
)
397459
]
398460

399461
return function_rows, PageMetaInfoLimitOffset(
400-
total=total_count_result,
462+
total=total_count,
401463
offset=pagination_offset,
402464
limit=pagination_limit,
403465
count=len(function_rows),

services/web/server/src/simcore_service_webserver/functions/_functions_service.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from aiohttp import web
2+
from models_library.basic_types import IDStr
23
from models_library.functions import (
34
Function,
45
FunctionClass,
@@ -37,6 +38,7 @@
3738
)
3839
from models_library.groups import GroupID
3940
from models_library.products import ProductName
41+
from models_library.rest_ordering import OrderBy
4042
from models_library.rest_pagination import PageMetaInfoLimitOffset
4143
from models_library.users import UserID
4244
from servicelib.rabbitmq import RPCRouter
@@ -185,13 +187,28 @@ async def list_functions(
185187
product_name: ProductName,
186188
pagination_limit: int,
187189
pagination_offset: int,
190+
order_by: OrderBy | None = None,
191+
filter_by_function_class: FunctionClass | None = None,
192+
search_by_function_title: str | None = None,
193+
search_by_multi_columns: str | None = None,
188194
) -> tuple[list[RegisteredFunction], PageMetaInfoLimitOffset]:
189195
returned_functions, page = await _functions_repository.list_functions(
190196
app=app,
191197
user_id=user_id,
192198
product_name=product_name,
193199
pagination_limit=pagination_limit,
194200
pagination_offset=pagination_offset,
201+
order_by=(
202+
OrderBy(
203+
field=IDStr("uuid") if order_by.field == "uid" else order_by.field,
204+
direction=order_by.direction,
205+
)
206+
if order_by
207+
else None
208+
),
209+
filter_by_function_class=filter_by_function_class,
210+
search_by_function_title=search_by_function_title,
211+
search_by_multi_columns=search_by_multi_columns,
195212
)
196213
return [
197214
_decode_function(returned_function) for returned_function in returned_functions

0 commit comments

Comments
 (0)