Skip to content

Commit a13cf50

Browse files
author
Andrei Neagu
committed
Merge remote-tracking branch 'upstream/master' into pr-osparc-long-running-tasks-refactor-6
2 parents f883dfe + ef83d12 commit a13cf50

File tree

37 files changed

+1285
-1289
lines changed

37 files changed

+1285
-1289
lines changed

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: 21 additions & 6 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;
@@ -217,7 +224,7 @@ qx.Class.define("osparc.conversation.Conversation", {
217224
const messages = resp["data"];
218225
messages.forEach(message => this.addMessage(message));
219226
this.__nextRequestParams = resp["_links"]["next"];
220-
if (this.__nextRequestParams === null) {
227+
if (this.__nextRequestParams === null && this.__loadMoreMessages) {
221228
this.__loadMoreMessages.exclude();
222229
}
223230
})
@@ -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/data/model/Node.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -718,7 +718,7 @@ qx.Class.define("osparc.data.model.Node", {
718718
});
719719
} else {
720720
this.fireDataEvent("projectDocumentChanged", {
721-
"op": "delete",
721+
"op": "remove",
722722
"path": `/ui/workbench/${this.getNodeId()}/marker`,
723723
"osparc-resource": "ui",
724724
});

services/static-webserver/client/source/class/osparc/data/model/Study.js

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -573,19 +573,21 @@ qx.Class.define("osparc.data.model.Study", {
573573
// Do not listen to output related backend updates if the node is a frontend node.
574574
// The frontend controls its output values, progress and states.
575575
// If a File Picker is uploading a file, the backend could override the current state with some older state.
576-
if (node && nodeData && !osparc.data.model.Node.isFrontend(node.getMetaData())) {
577-
node.setOutputData(nodeData.outputs);
578-
if ("progress" in nodeData) {
579-
const progress = Number.parseInt(nodeData["progress"]);
580-
node.getStatus().setProgress(progress);
576+
if (node) {
577+
if (nodeData && !osparc.data.model.Node.isFrontend(node.getMetaData())) {
578+
node.setOutputData(nodeData.outputs);
579+
if ("progress" in nodeData) {
580+
const progress = Number.parseInt(nodeData["progress"]);
581+
node.getStatus().setProgress(progress);
582+
}
583+
node.populateStates(nodeData);
584+
}
585+
if ("errors" in nodeUpdatedData) {
586+
const errors = nodeUpdatedData["errors"];
587+
node.setErrors(errors);
588+
} else {
589+
node.setErrors([]);
581590
}
582-
node.populateStates(nodeData);
583-
}
584-
if (node && "errors" in nodeUpdatedData) {
585-
const errors = nodeUpdatedData["errors"];
586-
node.setErrors(errors);
587-
} else {
588-
node.setErrors([]);
589591
}
590592
},
591593

services/static-webserver/client/source/class/osparc/data/model/StudyUI.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ qx.Class.define("osparc.data.model.StudyUI", {
143143
if (annotationId in this.getAnnotations()) {
144144
const annotation = this.getAnnotations()[annotationId]
145145
this.fireDataEvent("projectDocumentChanged", {
146-
"op": "delete",
146+
"op": "remove",
147147
"path": `/ui/annotations/${annotation.getId()}`,
148148
"osparc-resource": "study-ui",
149149
});

services/static-webserver/client/source/class/osparc/data/model/Workbench.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -320,7 +320,7 @@ qx.Class.define("osparc.data.model.Workbench", {
320320
this.__addNode(node);
321321

322322
node.populateNodeData();
323-
this.giveUniqueNameToNode(node, node.getLabel());
323+
this.__giveUniqueNameToNode(node, node.getLabel());
324324
node.checkState();
325325

326326
return node;
@@ -653,15 +653,15 @@ qx.Class.define("osparc.data.model.Workbench", {
653653
return false;
654654
},
655655

656-
giveUniqueNameToNode: function(node, label, suffix = 2) {
656+
__giveUniqueNameToNode: function(node, label, suffix = 2) {
657657
const newLabel = label + "_" + suffix;
658658
const allModels = this.getNodes();
659659
const nodes = Object.values(allModels);
660660
for (const node2 of nodes) {
661661
if (node2.getNodeId() !== node.getNodeId() &&
662662
node2.getLabel().localeCompare(node.getLabel()) === 0) {
663663
node.setLabel(newLabel);
664-
this.giveUniqueNameToNode(node, label, suffix+1);
664+
this.__giveUniqueNameToNode(node, label, suffix+1);
665665
}
666666
}
667667
},
@@ -720,7 +720,7 @@ qx.Class.define("osparc.data.model.Workbench", {
720720

721721
nodeIds.forEach(nodeId => {
722722
const node = this.getNode(nodeId);
723-
this.giveUniqueNameToNode(node, node.getLabel());
723+
this.__giveUniqueNameToNode(node, node.getLabel());
724724
});
725725
});
726726
},

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) {

0 commit comments

Comments
 (0)