Skip to content

Commit 67dbc0d

Browse files
authored
Ychat update messages (#81)
* Allow to update a message from the backend * insert a new message from backend at the correct position regarding to the timestamp * Add tests on the YChat * Add a dedicated tests for pytest
1 parent f30fe91 commit 67dbc0d

File tree

6 files changed

+173
-24
lines changed

6 files changed

+173
-24
lines changed

.github/workflows/build.yml

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,27 @@ jobs:
3939
set -eux
4040
jlpm run test
4141
42+
test_extensions:
43+
runs-on: ubuntu-latest
44+
needs: build_jupyter-chat
45+
name: Python test on extensions
46+
47+
steps:
48+
- name: Checkout
49+
uses: actions/checkout@v3
50+
51+
- name: Base Setup
52+
uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1
53+
54+
- name: Install dependencies
55+
run: python -m pip install -U "jupyterlab>=4.0.0,<5"
56+
57+
- name: Build the extensions
58+
run: |
59+
set -eux
60+
./scripts/install.sh
61+
pytest -vv -r ap --cov
62+
4263
build_extensions:
4364
runs-on: ubuntu-latest
4465
needs: build_jupyter-chat
@@ -57,7 +78,7 @@ jobs:
5778
- name: Install dependencies
5879
run: python -m pip install -U "jupyterlab>=4.0.0,<5"
5980

60-
- name: Build package
81+
- name: Package the extensions
6182
run: |
6283
jlpm install
6384
jlpm build:${{ matrix.extension }}

conftest.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
# Copyright (c) Jupyter Development Team.
22
# Distributed under the terms of the Modified BSD License.
3-
43
import pytest
54

65
pytest_plugins = ("pytest_jupyter.jupyter_server", )

python/jupyterlab-collaborative-chat/conftest.py

Lines changed: 0 additions & 11 deletions
This file was deleted.

python/jupyterlab-collaborative-chat/jupyterlab_collaborative_chat/tests/test_handlers.py

Lines changed: 0 additions & 4 deletions
This file was deleted.
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
# Copyright (c) Jupyter Development Team.
2+
# Distributed under the terms of the Modified BSD License.
3+
4+
# import jupyter_ydoc before YChat to avoid circular error
5+
import jupyter_ydoc
6+
7+
import pytest
8+
import time
9+
from copy import deepcopy
10+
from uuid import uuid4
11+
from ..ychat import YChat
12+
13+
USER = {
14+
"username": str(uuid4()),
15+
"name": "Test user",
16+
"display_name": "Test user"
17+
}
18+
19+
USER2 = {
20+
"username": str(uuid4()),
21+
"name": "Test user 2",
22+
"display_name": "Test user 2"
23+
}
24+
25+
26+
def create_message():
27+
return {
28+
"type": "msg",
29+
"id": str(uuid4()),
30+
"body": "This is a test message",
31+
"time": time.time(),
32+
"sender": USER["username"]
33+
}
34+
35+
36+
def test_initialize_ychat():
37+
chat = YChat()
38+
assert chat.get_messages() == []
39+
assert chat.get_users() == {}
40+
assert chat.get_metadata() == {}
41+
42+
43+
def test_add_user():
44+
chat = YChat()
45+
chat.set_user(USER)
46+
assert USER["username"] in chat.get_users().keys()
47+
assert chat.get_users()[USER["username"]] == USER
48+
49+
50+
def test_get_user_by_name():
51+
chat = YChat()
52+
chat.set_user(USER)
53+
chat.set_user(USER2)
54+
assert chat.get_user_by_name(USER["name"]) == USER
55+
assert chat.get_user_by_name(USER2["name"]) == USER2
56+
assert chat.get_user_by_name(str(uuid4())) == None
57+
58+
59+
def test_add_message():
60+
chat = YChat()
61+
msg = create_message()
62+
chat.add_message(msg)
63+
assert len(chat.get_messages()) == 1
64+
assert chat.get_messages()[0] == msg
65+
66+
67+
def test_set_message_should_add():
68+
chat = YChat()
69+
msg = create_message()
70+
chat.set_message(msg)
71+
assert len(chat.get_messages()) == 1
72+
assert chat.get_messages()[0] == msg
73+
74+
75+
def test_set_message_should_update():
76+
chat = YChat()
77+
msg = create_message()
78+
index = chat.add_message(msg)
79+
msg["body"] = "Updated content"
80+
chat.set_message(msg, index)
81+
assert len(chat.get_messages()) == 1
82+
assert chat.get_messages()[0] == msg
83+
84+
85+
def test_set_message_should_add_with_new_id():
86+
chat = YChat()
87+
msg = create_message()
88+
index = chat.add_message(msg)
89+
new_msg = deepcopy(msg)
90+
new_msg["id"] = str(uuid4())
91+
new_msg["body"] = "Updated content"
92+
chat.set_message(new_msg, index)
93+
assert len(chat.get_messages()) == 2
94+
assert chat.get_messages()[0] == msg
95+
assert chat.get_messages()[1] == new_msg
96+
97+
98+
def test_set_message_should_update_with_wrong_index():
99+
chat = YChat()
100+
msg = create_message()
101+
chat.add_message(msg)
102+
new_msg = create_message()
103+
new_msg["body"] = "New content"
104+
index = chat.add_message(new_msg)
105+
assert index == 1
106+
new_msg["body"] = "Updated content"
107+
chat.set_message(new_msg, 0)
108+
assert len(chat.get_messages()) == 2
109+
assert chat.get_messages()[0] == msg
110+
assert chat.get_messages()[1] == new_msg
111+

python/jupyterlab-collaborative-chat/jupyterlab_collaborative_chat/ychat.py

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -81,13 +81,13 @@ def set_user(self, user: dict[str, str]) -> None:
8181
with self._ydoc.transaction():
8282
self._yusers.update({user["username"]: user})
8383

84-
def get_message(self, id: str) -> dict | None:
84+
def get_message(self, id: str) -> tuple[dict | None, int | None]:
8585
"""
86-
Returns a message from its id, or None
86+
Returns a message and its index from its id, or None
8787
"""
8888
return next(
89-
(msg for msg in self.get_messages() if msg["id"] == id),
90-
None
89+
((msg, i) for i, msg in enumerate(self.get_messages()) if msg["id"] == id),
90+
(None, None)
9191
)
9292

9393
def get_messages(self) -> list[dict]:
@@ -97,12 +97,45 @@ def get_messages(self) -> list[dict]:
9797
"""
9898
return self._ymessages.to_py()
9999

100-
def add_message(self, message: dict) -> None:
100+
def add_message(self, message: dict) -> int:
101+
"""
102+
Append a message to the document.
103+
"""
104+
timestamp: float = time.time()
105+
message["time"] = timestamp
106+
with self._ydoc.transaction():
107+
index = len(self._ymessages) - next((i for i, v in enumerate(self.get_messages()[::-1]) if v["time"] < timestamp), len(self._ymessages))
108+
self._ymessages.insert(index, message)
109+
return index
110+
111+
def update_message(self, message: dict, index: int, append: bool = False):
101112
"""
102-
Appends a message to the document.
113+
Update a message of the document.
114+
If append is True, the content will be append to the previous content.
103115
"""
104116
with self._ydoc.transaction():
105-
self._ymessages.append(message)
117+
initial_message = self._ymessages.pop(index)
118+
if append:
119+
message["body"] = initial_message["body"] + message["body"]
120+
self._ymessages.insert(index, message)
121+
122+
def set_message(self, message: dict, index: int | None = None, append: bool = False):
123+
"""
124+
Update or append a message.
125+
"""
126+
127+
if index is not None and 0 <= index < len(self._ymessages):
128+
initial_message = self._ymessages[index]
129+
else:
130+
return self.add_message(message)
131+
132+
if not initial_message["id"] == message["id"]:
133+
initial_message, index = self.get_message(message["id"])
134+
if initial_message is None:
135+
return self.add_message(message)
136+
137+
self.update_message(message, index, append)
138+
return index
106139

107140
def get_single_metadata(self, name) -> dict:
108141
"""

0 commit comments

Comments
 (0)