Skip to content

Commit 2bc186d

Browse files
Merge branch 'master' into is5646/use-project-nodes-table-instead-of-workbench
2 parents 6558b5a + d1f879d commit 2bc186d

File tree

23 files changed

+505
-248
lines changed

23 files changed

+505
-248
lines changed

packages/models-library/src/models_library/api_schemas_webserver/projects.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,9 @@ class ProjectDocument(OutputSchema):
302302
model_config = ConfigDict(from_attributes=True, arbitrary_types_allowed=True)
303303

304304

305+
ProjectDocumentVersion: TypeAlias = int
306+
307+
305308
__all__: tuple[str, ...] = (
306309
"EmptyModel",
307310
"ProjectCopyOverride",

packages/service-library/src/servicelib/rest_constants.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,4 @@ class PydanticExportParametersDict(TypedDict):
2323

2424
# Headers keys
2525
X_PRODUCT_NAME_HEADER: Final[str] = "X-Simcore-Products-Name"
26+
X_CLIENT_SESSION_ID_HEADER: Final[str] = "X-Client-Session-Id"

services/dynamic-scheduler/src/simcore_service_dynamic_scheduler/services/notifier/_notifier.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
)
1212
from models_library.api_schemas_webserver.projects_nodes import NodeGet, NodeGetIdle
1313
from models_library.api_schemas_webserver.socketio import SocketIORoomStr
14-
from models_library.users import UserID
14+
from models_library.projects import ProjectID
1515
from servicelib.fastapi.app_state import SingletonInAppStateMixin
1616
from servicelib.services_utils import get_status_as_dict
1717

@@ -23,20 +23,22 @@ def __init__(self, sio_manager: socketio.AsyncAioPikaManager):
2323
self._sio_manager = sio_manager
2424

2525
async def notify_service_status(
26-
self, user_id: UserID, status: NodeGet | DynamicServiceGet | NodeGetIdle
26+
self, project_id: ProjectID, status: NodeGet | DynamicServiceGet | NodeGetIdle
2727
) -> None:
2828
await self._sio_manager.emit(
2929
SOCKET_IO_SERVICE_STATUS_EVENT,
3030
data=jsonable_encoder(get_status_as_dict(status)),
31-
room=SocketIORoomStr.from_user_id(user_id),
31+
room=SocketIORoomStr.from_project_id(project_id),
3232
)
3333

3434

3535
async def notify_service_status_change(
36-
app: FastAPI, user_id: UserID, status: NodeGet | DynamicServiceGet | NodeGetIdle
36+
app: FastAPI,
37+
project_id: ProjectID,
38+
status: NodeGet | DynamicServiceGet | NodeGetIdle,
3739
) -> None:
3840
notifier: Notifier = Notifier.get_from_app_state(app)
39-
await notifier.notify_service_status(user_id=user_id, status=status)
41+
await notifier.notify_service_status(project_id=project_id, status=status)
4042

4143

4244
async def lifespan(app: FastAPI) -> AsyncIterator[State]:

services/dynamic-scheduler/src/simcore_service_dynamic_scheduler/services/service_tracker/__init__.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
from ._api import (
22
NORMAL_RATE_POLL_INTERVAL,
33
get_all_tracked_services,
4+
get_project_id_for_service,
45
get_tracked_service,
5-
get_user_id_for_service,
66
remove_tracked_service,
77
set_frontend_notified_for_service,
88
set_if_status_changed_for_service,
@@ -17,11 +17,11 @@
1717

1818
__all__: tuple[str, ...] = (
1919
"get_all_tracked_services",
20+
"get_project_id_for_service",
2021
"get_tracked_service",
21-
"get_user_id_for_service",
22-
"service_tracker_lifespan",
2322
"NORMAL_RATE_POLL_INTERVAL",
2423
"remove_tracked_service",
24+
"service_tracker_lifespan",
2525
"set_frontend_notified_for_service",
2626
"set_if_status_changed_for_service",
2727
"set_request_as_running",

services/dynamic-scheduler/src/simcore_service_dynamic_scheduler/services/service_tracker/_api.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,16 @@
1111
DynamicServiceStop,
1212
)
1313
from models_library.api_schemas_webserver.projects_nodes import NodeGet, NodeGetIdle
14+
from models_library.projects import ProjectID
1415
from models_library.projects_nodes_io import NodeID
1516
from models_library.services_enums import ServiceState
16-
from models_library.users import UserID
1717
from servicelib.deferred_tasks import TaskUID
1818

19-
from ._models import SchedulerServiceState, TrackedServiceModel, UserRequestedState
19+
from ._models import (
20+
SchedulerServiceState,
21+
TrackedServiceModel,
22+
UserRequestedState,
23+
)
2024
from ._setup import get_tracker
2125

2226
_logger = logging.getLogger(__name__)
@@ -242,7 +246,7 @@ async def get_all_tracked_services(app: FastAPI) -> dict[NodeID, TrackedServiceM
242246
return await get_tracker(app).all()
243247

244248

245-
async def get_user_id_for_service(app: FastAPI, node_id: NodeID) -> UserID | None:
246-
"""returns user_id for the service"""
249+
async def get_project_id_for_service(app: FastAPI, node_id: NodeID) -> ProjectID | None:
250+
"""returns project_id for the service"""
247251
model: TrackedServiceModel | None = await get_tracker(app).load(node_id)
248-
return model.user_id if model else None
252+
return model.project_id if model else None

services/dynamic-scheduler/src/simcore_service_dynamic_scheduler/services/status_monitor/_deferred_get_status.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77
RunningDynamicServiceDetails,
88
)
99
from models_library.api_schemas_webserver.projects_nodes import NodeGet, NodeGetIdle
10+
from models_library.projects import ProjectID
1011
from models_library.projects_nodes_io import NodeID
11-
from models_library.users import UserID
1212
from servicelib.deferred_tasks import BaseDeferredHandler, TaskUID
1313
from servicelib.deferred_tasks._base_deferred_handler import DeferredContext
1414

@@ -69,15 +69,15 @@ async def on_result(
6969
if await service_tracker.should_notify_frontend_for_service(
7070
app, node_id, status_changed=status_changed
7171
):
72-
user_id: UserID | None = await service_tracker.get_user_id_for_service(
73-
app, node_id
72+
project_id: ProjectID | None = (
73+
await service_tracker.get_project_id_for_service(app, node_id)
7474
)
75-
if user_id:
76-
await notify_service_status_change(app, user_id, result)
75+
if project_id:
76+
await notify_service_status_change(app, project_id, result)
7777
await service_tracker.set_frontend_notified_for_service(app, node_id)
7878
else:
7979
_logger.info(
80-
"Did not find a user for '%s', skipping status delivery of: %s",
80+
"Did not find a project for '%s', skipping status delivery of: %s",
8181
node_id,
8282
result,
8383
)

services/web/server/src/simcore_service_webserver/db_listener/_db_comp_tasks_listening_task.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,12 @@ async def _update_project_state(
5252
node_errors: list[ErrorDict] | None,
5353
) -> None:
5454
project = await _projects_service.update_project_node_state(
55-
app, user_id, project_uuid, node_uuid, new_state
55+
app,
56+
user_id,
57+
project_uuid,
58+
node_uuid,
59+
new_state,
60+
client_session_id=None, # <-- The trigger for this update is not from the UI (its db listener)
5661
)
5762

5863
await _projects_service.notify_project_node_update(
@@ -95,6 +100,7 @@ async def _handle_db_notification(
95100
changed_row.run_hash,
96101
node_errors=changed_row.errors,
97102
ui_changed_keys=None,
103+
client_session_id=None, # <-- The trigger for this update is not from the UI (its db listener)
98104
)
99105

100106
if "state" in payload.changes and (changed_row.state is not None):

services/web/server/src/simcore_service_webserver/folders/_workspaces_repository.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from simcore_postgres_database.utils_repos import transaction_context
99

1010
from ..db.plugin import get_asyncpg_engine
11+
from ..models import ClientSessionID
1112
from ..projects import _folders_repository as projects_folders_repository
1213
from ..projects import _groups_repository as projects_groups_repository
1314
from ..projects._access_rights_service import check_user_project_permission
@@ -26,6 +27,7 @@ async def move_folder_into_workspace(
2627
folder_id: FolderID,
2728
workspace_id: WorkspaceID | None,
2829
product_name: ProductName,
30+
client_session_id: ClientSessionID | None = None,
2931
) -> None:
3032
# 1. User needs to have delete permission on source folder
3133
folder_db = await _folders_repository.get(
@@ -84,6 +86,7 @@ async def move_folder_into_workspace(
8486
project_uuid=project_id,
8587
patch_project_data={"workspace_id": workspace_id},
8688
user_primary_gid=user["primary_gid"],
89+
client_session_id=client_session_id,
8790
)
8891

8992
# 5. BATCH update of folders with workspace_id

services/web/server/src/simcore_service_webserver/folders/_workspaces_rest.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,14 @@
22

33
from aiohttp import web
44
from servicelib.aiohttp import status
5-
from servicelib.aiohttp.requests_validation import parse_request_path_parameters_as
5+
from servicelib.aiohttp.requests_validation import (
6+
parse_request_headers_as,
7+
parse_request_path_parameters_as,
8+
)
69

710
from .._meta import api_version_prefix as VTAG
811
from ..login.decorators import login_required
12+
from ..models import ClientSessionHeaderParams
913
from ..security.decorators import permission_required
1014
from . import _workspaces_repository
1115
from ._common.exceptions_handlers import handle_plugin_requests_exceptions
@@ -27,12 +31,14 @@
2731
async def move_folder_to_workspace(request: web.Request):
2832
req_ctx = FoldersRequestContext.model_validate(request)
2933
path_params = parse_request_path_parameters_as(FolderWorkspacesPathParams, request)
34+
header_params = parse_request_headers_as(ClientSessionHeaderParams, request)
3035

3136
await _workspaces_repository.move_folder_into_workspace(
3237
app=request.app,
3338
user_id=req_ctx.user_id,
3439
folder_id=path_params.folder_id,
3540
workspace_id=path_params.workspace_id,
3641
product_name=req_ctx.product_name,
42+
client_session_id=header_params.client_session_id,
3743
)
3844
return web.json_response(status=status.HTTP_204_NO_CONTENT)

services/web/server/src/simcore_service_webserver/models.py

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@
33
from models_library.products import ProductName
44
from models_library.rest_base import RequestParameters
55
from models_library.users import UserID
6-
from pydantic import ConfigDict, Field
6+
from pydantic import ConfigDict, Field, StringConstraints
77
from pydantic_extra_types.phone_numbers import PhoneNumberValidator
88
from servicelib.request_keys import RQT_USERID_KEY
9+
from servicelib.rest_constants import X_CLIENT_SESSION_ID_HEADER
910

1011
from .constants import RQ_PRODUCT_KEY
1112

@@ -16,6 +17,18 @@
1617
]
1718

1819

20+
ClientSessionID: TypeAlias = Annotated[
21+
str,
22+
StringConstraints(
23+
strip_whitespace=True,
24+
min_length=36,
25+
max_length=36,
26+
pattern=r"^[0-9a-fA-F\-]{36}$", # UUID format
27+
strict=True,
28+
),
29+
]
30+
31+
1932
class AuthenticatedRequestContext(RequestParameters):
2033
"""Fields expected in the request context for authenticated endpoints"""
2134

@@ -25,3 +38,20 @@ class AuthenticatedRequestContext(RequestParameters):
2538
model_config = ConfigDict(
2639
frozen=True # prevents modifications after middlewares creates this model
2740
)
41+
42+
43+
assert X_CLIENT_SESSION_ID_HEADER
44+
45+
46+
class ClientSessionHeaderParams(RequestParameters):
47+
"""Header parameters for client session tracking in collaborative features."""
48+
49+
client_session_id: ClientSessionID | None = Field(
50+
default=None,
51+
alias="X-Client-Session-Id", # X_CLIENT_SESSION_ID_HEADER,
52+
description="Client session identifier for collaborative features (UUID string)",
53+
)
54+
55+
model_config = ConfigDict(
56+
validate_by_name=True,
57+
)

0 commit comments

Comments
 (0)