Skip to content

Commit 6463064

Browse files
committed
Merge branch 'feature/listenToProjectDocumentWS' of github.com:odeimaiz/osparc-simcore into feature/listenToProjectDocumentWS
2 parents af18867 + 641ddb4 commit 6463064

File tree

43 files changed

+1595
-1387
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+1595
-1387
lines changed

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import datetime
22
from typing import Annotated, TypeAlias
33

4-
from pydantic import Field
4+
from pydantic import Field, HttpUrl
55

66
from ..functions import (
77
Function,
@@ -116,6 +116,10 @@
116116

117117
class RegisteredSolverFunctionGet(RegisteredSolverFunction, OutputSchema):
118118
uid: Annotated[FunctionID, Field(alias="uuid")]
119+
created_at: Annotated[datetime.datetime, Field(alias="creationDate")]
120+
modified_at: Annotated[datetime.datetime, Field(alias="lastChangeDate")]
121+
access_rights: FunctionAccessRights | None = None
122+
thumbnail: HttpUrl | None = None
119123

120124

121125
class RegisteredProjectFunctionGet(RegisteredProjectFunction, OutputSchema):
@@ -124,7 +128,7 @@ class RegisteredProjectFunctionGet(RegisteredProjectFunction, OutputSchema):
124128
created_at: Annotated[datetime.datetime, Field(alias="creationDate")]
125129
modified_at: Annotated[datetime.datetime, Field(alias="lastChangeDate")]
126130
access_rights: FunctionAccessRights | None = None
127-
thumbnail: str | None = None
131+
thumbnail: HttpUrl | None = None
128132

129133

130134
class SolverFunctionToRegister(SolverFunction, InputSchema): ...

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
11
# mypy: disable-error-code=truthy-function
22
from typing import Annotated, Any, Literal, TypeAlias
33

4-
from models_library.groups import GroupID
5-
from models_library.projects import ProjectID
6-
from models_library.services_history import ServiceRelease
74
from pydantic import ConfigDict, Field
85

96
from ..access_rights import ExecutableAccessRights
107
from ..api_schemas_directorv2.dynamic_services import RetrieveDataOut
118
from ..basic_types import PortInt
9+
from ..groups import GroupID
10+
from ..projects import ProjectID
1211
from ..projects_nodes import InputID, InputsDict, PartialNode
1312
from ..projects_nodes_io import NodeID
1413
from ..services import ServiceKey, ServicePortKey, ServiceVersion
1514
from ..services_enums import ServiceState
15+
from ..services_history import ServiceRelease
1616
from ..services_resources import ServiceResourcesDict
1717
from ._base import InputSchemaWithoutCamelCase, OutputSchema
1818

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,17 @@
11
"""
2-
Ownership and access rights
2+
Ownership and access rights
33
"""
44

55
from enum import Enum
6+
from typing import Annotated
67

78
from pydantic import BaseModel, ConfigDict, Field
8-
from pydantic.types import PositiveInt
99

1010
from .basic_types import IDStr
11-
from .users import FirstNameStr, LastNameStr
11+
from .users import UserID
1212

1313

14-
class GroupIDStr(IDStr):
15-
...
14+
class GroupIDStr(IDStr): ...
1615

1716

1817
class AccessEnum(str, Enum):
@@ -22,26 +21,23 @@ class AccessEnum(str, Enum):
2221

2322

2423
class AccessRights(BaseModel):
25-
read: bool = Field(..., description="has read access")
26-
write: bool = Field(..., description="has write access")
27-
delete: bool = Field(..., description="has deletion rights")
24+
read: Annotated[bool, Field(description="has read access")]
25+
write: Annotated[bool, Field(description="has write access")]
26+
delete: Annotated[bool, Field(description="has deletion rights")]
2827

2928
model_config = ConfigDict(extra="forbid")
3029

3130

3231
class Owner(BaseModel):
33-
user_id: PositiveInt = Field(..., description="Owner's user id")
34-
first_name: FirstNameStr | None = Field(..., description="Owner's first name")
35-
last_name: LastNameStr | None = Field(..., description="Owner's last name")
32+
user_id: Annotated[UserID, Field(description="Owner's user id")]
3633

3734
model_config = ConfigDict(
3835
extra="forbid",
3936
json_schema_extra={
4037
"examples": [
41-
# NOTE: None and empty string are both defining an undefined value
42-
{"user_id": 1, "first_name": None, "last_name": None},
43-
{"user_id": 2, "first_name": "", "last_name": ""},
44-
{"user_id": 3, "first_name": "John", "last_name": "Smith"},
38+
{"user_id": 1},
39+
{"user_id": 42},
40+
{"user_id": 666},
4541
]
4642
},
4743
)

packages/models-library/src/models_library/projects_state.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -195,8 +195,6 @@ class ProjectLocked(BaseModel):
195195
"status": ProjectStatus.OPENED,
196196
"owner": {
197197
"user_id": 123,
198-
"first_name": "Johnny",
199-
"last_name": "Cash",
200198
},
201199
},
202200
]

packages/pytest-simcore/src/pytest_simcore/socketio_client.py

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import pytest
1010
import socketio
1111
from aiohttp.test_utils import TestClient
12+
from pytest_simcore.helpers.logging_tools import log_context
1213
from servicelib.aiohttp import status
1314
from yarl import URL
1415

@@ -56,18 +57,19 @@ async def create_socketio_connection(
5657
security_cookie_factory: Callable[[TestClient | None], Awaitable[str]],
5758
client_session_id_factory: Callable[[], str],
5859
) -> AsyncIterable[
59-
Callable[[str | None, TestClient | None], Awaitable[socketio.AsyncClient]]
60+
Callable[
61+
[str | None, TestClient | None], Awaitable[tuple[socketio.AsyncClient, str]]
62+
]
6063
]:
6164
clients: list[socketio.AsyncClient] = []
6265

6366
async def _connect(
6467
client_session_id: str | None = None, client: TestClient | None = None
65-
) -> socketio.AsyncClient:
68+
) -> tuple[socketio.AsyncClient, str]:
6669
if client_session_id is None:
6770
client_session_id = client_session_id_factory()
6871

6972
sio = socketio.AsyncClient(ssl_verify=False)
70-
# enginio 3.10.0 introduced ssl verification
7173
assert client_session_id
7274
url = str(
7375
URL(socketio_url_factory(client)).with_query(
@@ -80,21 +82,27 @@ async def _connect(
8082
# WARNING: engineio fails with empty cookies. Expects "key=value"
8183
headers.update({"Cookie": cookie})
8284

83-
print(f"--> Connecting socketio client to {url} ...")
84-
await sio.connect(url, headers=headers, wait_timeout=10)
85-
assert sio.sid
86-
print("... connection done")
85+
with log_context(logging.INFO, f"socketio_client: connecting to {url}"):
86+
print(f"--> Connecting socketio client to {url} ...")
87+
sio.on(
88+
"connect",
89+
handler=lambda: logger.info("Connected successfully with %s", sio.sid),
90+
)
91+
sio.on(
92+
"disconnect",
93+
handler=lambda: logger.info("Disconnected from %s", sio.sid),
94+
)
95+
await sio.connect(url, headers=headers, wait_timeout=10)
96+
assert sio.sid
8797
clients.append(sio)
88-
return sio
98+
return sio, client_session_id
8999

90100
yield _connect
91101

92102
# cleans up clients produce by _connect(*) calls
93103
for sio in clients:
94104
if sio.connected:
95-
print(f"<--Disconnecting socketio client {sio}")
96-
await sio.disconnect()
97-
await sio.wait()
98-
print(f"... disconnection from {sio} done.")
99-
assert not sio.connected
100-
assert not sio.sid
105+
with log_context(logging.INFO, f"socketio_client: disconnecting {sio}"):
106+
await sio.disconnect()
107+
await sio.wait()
108+
assert not sio.connected

services/static-webserver/client/source/class/osparc/conversation/Conversation.js

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ qx.Class.define("osparc.conversation.Conversation", {
6464
__messages: null,
6565
__nextRequestParams: null,
6666
__messagesTitle: null,
67+
__messageScroll: null,
6768
__messagesList: null,
6869
__loadMoreMessages: null,
6970

@@ -147,10 +148,15 @@ qx.Class.define("osparc.conversation.Conversation", {
147148
this.__messagesTitle = new qx.ui.basic.Label();
148149
this._add(this.__messagesTitle);
149150

151+
// add spacer to keep the messages list at the bottom
152+
this._add(new qx.ui.core.Spacer(), {
153+
flex: 100 // high number to keep even a one message list at the bottom
154+
});
155+
150156
this.__messagesList = new qx.ui.container.Composite(new qx.ui.layout.VBox(5)).set({
151157
alignY: "middle"
152158
});
153-
const scrollView = new qx.ui.container.Scroll();
159+
const scrollView = this.__messageScroll = new qx.ui.container.Scroll();
154160
scrollView.add(this.__messagesList);
155161
this._add(scrollView, {
156162
flex: 1
@@ -197,7 +203,8 @@ qx.Class.define("osparc.conversation.Conversation", {
197203

198204
__reloadMessages: function(removeMessages = true) {
199205
if (this.getConversationId() === null) {
200-
this.__messagesTitle.setValue(this.tr("No messages yet"));
206+
// temporary conversation page
207+
this.__messagesTitle.setValue(this.tr("No Messages yet"));
201208
this.__messagesList.hide();
202209
this.__loadMoreMessages.hide();
203210
return;
@@ -226,7 +233,9 @@ qx.Class.define("osparc.conversation.Conversation", {
226233

227234
__updateMessagesNumber: function() {
228235
const nMessages = this.__messages.filter(msg => msg["type"] === "MESSAGE").length;
229-
if (nMessages === 1) {
236+
if (nMessages === 0) {
237+
this.__messagesTitle.setValue(this.tr("No Messages yet"));
238+
} else if (nMessages === 1) {
230239
this.__messagesTitle.setValue(this.tr("1 Message"));
231240
} else if (nMessages > 1) {
232241
this.__messagesTitle.setValue(nMessages + this.tr(" Messages"));
@@ -243,9 +252,9 @@ qx.Class.define("osparc.conversation.Conversation", {
243252
return;
244253
}
245254

246-
// determine insertion index for most‐recent‐first order
255+
// determine insertion index for latest‐first order
247256
const newTime = new Date(message["created"]);
248-
let insertAt = this.__messages.findIndex(m => new Date(m["created"]) < newTime);
257+
let insertAt = this.__messages.findIndex(m => new Date(m["created"]) > newTime);
249258
if (insertAt === -1) {
250259
insertAt = this.__messages.length;
251260
}
@@ -270,6 +279,12 @@ qx.Class.define("osparc.conversation.Conversation", {
270279
this.__messagesList.addAt(control, insertAt);
271280
}
272281

282+
// scroll to bottom
283+
// add timeout to ensure the scroll happens after the UI is updated
284+
setTimeout(() => {
285+
this.__messageScroll.scrollToY(this.__messageScroll.getChildControl("pane").getScrollMaxY());
286+
}, 50);
287+
273288
this.__updateMessagesNumber();
274289
},
275290

services/static-webserver/client/source/class/osparc/study/Conversations.js

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,12 @@ qx.Class.define("osparc.study.Conversations", {
221221

222222
__addToPages: function(conversationPage) {
223223
const conversationsLayout = this.getChildControl("conversations-layout");
224+
if (conversationsLayout.getChildren().length === 1) {
225+
// remove the temporary conversation page
226+
if (conversationsLayout.getChildren()[0].getConversationId() === null) {
227+
conversationsLayout.remove(conversationsLayout.getChildren()[0]);
228+
}
229+
}
224230
conversationsLayout.add(conversationPage);
225231

226232
if (this.__newConversationButton === null) {
@@ -246,8 +252,11 @@ qx.Class.define("osparc.study.Conversations", {
246252
conversationsLayout.getChildControl("bar").add(newConversationButton);
247253
}
248254
// remove and add to move to last position
249-
conversationsLayout.getChildControl("bar").remove(this.__newConversationButton);
250-
conversationsLayout.getChildControl("bar").add(this.__newConversationButton);
255+
const bar = conversationsLayout.getChildControl("bar");
256+
if (bar.indexOf(this.__newConversationButton) > -1) {
257+
bar.remove(this.__newConversationButton);
258+
}
259+
bar.add(this.__newConversationButton);
251260
},
252261

253262
__removeConversationPage: function(conversationId, changeSelection = false) {

services/static-webserver/client/source/class/osparc/workbench/Annotation.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -277,7 +277,7 @@ qx.Class.define("osparc.workbench.Annotation", {
277277
type: this.getType(),
278278
attributes: this.getAttributes(),
279279
color: this.getColor(), // TYPES.NOTE and TYPES.CONVERSATION do not need a color but backend expects it
280-
}
280+
};
281281
return serializeData;
282282
}
283283
}

services/web/server/src/simcore_service_webserver/api/v0/openapi.yaml

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16078,6 +16078,9 @@ components:
1607816078
thumbnail:
1607916079
anyOf:
1608016080
- type: string
16081+
maxLength: 2083
16082+
minLength: 1
16083+
format: uri
1608116084
- type: 'null'
1608216085
title: Thumbnail
1608316086
type: object
@@ -16131,14 +16134,14 @@ components:
1613116134
type: string
1613216135
format: uuid
1613316136
title: Uuid
16134-
createdAt:
16137+
creationDate:
1613516138
type: string
1613616139
format: date-time
16137-
title: Createdat
16138-
modifiedAt:
16140+
title: Creationdate
16141+
lastChangeDate:
1613916142
type: string
1614016143
format: date-time
16141-
title: Modifiedat
16144+
title: Lastchangedate
1614216145
solverKey:
1614316146
type: string
1614416147
pattern: ^simcore/services/((comp|dynamic|frontend))/([a-z0-9][a-z0-9_.-]*/)*([a-z0-9-_]+[a-z0-9])$
@@ -16147,14 +16150,26 @@ components:
1614716150
type: string
1614816151
pattern: ^(0|[1-9]\d*)(\.(0|[1-9]\d*)){2}(-(0|[1-9]\d*|\d*[-a-zA-Z][-\da-zA-Z]*)(\.(0|[1-9]\d*|\d*[-a-zA-Z][-\da-zA-Z]*))*)?(\+[-\da-zA-Z]+(\.[-\da-zA-Z-]+)*)?$
1614916152
title: Solverversion
16153+
accessRights:
16154+
anyOf:
16155+
- $ref: '#/components/schemas/FunctionAccessRights'
16156+
- type: 'null'
16157+
thumbnail:
16158+
anyOf:
16159+
- type: string
16160+
maxLength: 2083
16161+
minLength: 1
16162+
format: uri
16163+
- type: 'null'
16164+
title: Thumbnail
1615016165
type: object
1615116166
required:
1615216167
- inputSchema
1615316168
- outputSchema
1615416169
- defaultInputs
1615516170
- uuid
16156-
- createdAt
16157-
- modifiedAt
16171+
- creationDate
16172+
- lastChangeDate
1615816173
- solverKey
1615916174
- solverVersion
1616016175
title: RegisteredSolverFunctionGet

services/web/server/src/simcore_service_webserver/dynamic_scheduler/api.py

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
from models_library.services import ServicePortKey
2525
from models_library.users import UserID
2626
from pydantic import NonNegativeInt
27-
from pydantic.types import PositiveInt
2827
from servicelib.progress_bar import ProgressBarData
2928
from servicelib.rabbitmq import RabbitMQClient, RPCServerError
3029
from servicelib.rabbitmq.rpc_interfaces.dynamic_scheduler import services
@@ -94,13 +93,13 @@ async def stop_dynamic_service(
9493

9594
async def _post_progress_message(
9695
rabbitmq_client: RabbitMQClient,
97-
user_id: PositiveInt,
98-
project_id: str,
96+
user_id: UserID,
97+
project_id: ProjectID,
9998
report: ProgressReport,
10099
) -> None:
101100
progress_message = ProgressRabbitMessageProject(
102101
user_id=user_id,
103-
project_id=ProjectID(project_id),
102+
project_id=project_id,
104103
progress_type=ProgressType.PROJECT_CLOSING,
105104
report=report,
106105
)
@@ -111,14 +110,14 @@ async def _post_progress_message(
111110
async def stop_dynamic_services_in_project(
112111
app: web.Application,
113112
*,
114-
user_id: PositiveInt,
115-
project_id: str,
113+
user_id: UserID,
114+
project_id: ProjectID,
116115
simcore_user_agent: str,
117116
save_state: bool,
118117
) -> None:
119118
"""Stops all dynamic services in the project"""
120119
running_dynamic_services = await list_dynamic_services(
121-
app, user_id=user_id, project_id=ProjectID(project_id)
120+
app, user_id=user_id, project_id=project_id
122121
)
123122

124123
async with AsyncExitStack() as stack:

0 commit comments

Comments
 (0)