From fd5e3eade297b51816db06846faf93d961803e50 Mon Sep 17 00:00:00 2001 From: Nicolas Brichet Date: Fri, 13 Sep 2024 13:48:55 +0200 Subject: [PATCH 1/4] Allow to update a message from the backend --- .../jupyterlab_collaborative_chat/ychat.py | 42 ++++++++++++++++--- 1 file changed, 36 insertions(+), 6 deletions(-) diff --git a/python/jupyterlab-collaborative-chat/jupyterlab_collaborative_chat/ychat.py b/python/jupyterlab-collaborative-chat/jupyterlab_collaborative_chat/ychat.py index ffde8b6..6dd8b50 100644 --- a/python/jupyterlab-collaborative-chat/jupyterlab_collaborative_chat/ychat.py +++ b/python/jupyterlab-collaborative-chat/jupyterlab_collaborative_chat/ychat.py @@ -81,13 +81,13 @@ def set_user(self, user: dict[str, str]) -> None: with self._ydoc.transaction(): self._yusers.update({user["username"]: user}) - def get_message(self, id: str) -> dict | None: + def get_message(self, id: str) -> tuple[dict | None, int | None]: """ - Returns a message from its id, or None + Returns a message and its index from its id, or None """ return next( - (msg for msg in self.get_messages() if msg["id"] == id), - None + ((msg, i) for i, msg in enumerate(self.get_messages()) if msg["id"] == id), + (None, None) ) def get_messages(self) -> list[dict]: @@ -97,12 +97,42 @@ def get_messages(self) -> list[dict]: """ return self._ymessages.to_py() - def add_message(self, message: dict) -> None: + def add_message(self, message: dict) -> int: """ - Appends a message to the document. + Append a message to the document. """ with self._ydoc.transaction(): self._ymessages.append(message) + return len(self._ymessages) - 1 + + def update_message(self, message: dict, index: int, append: bool = False): + """ + Update a message of the document. + If append is True, the content will be append to the previous content. + """ + with self._ydoc.transaction(): + initial_message = self._ymessages.pop(index) + if append: + message["body"] = initial_message["body"] + message["body"] + self._ymessages.insert(index, message) + + def set_message(self, message: dict, index: int | None = None, append: bool = False): + """ + Update or append a message. + """ + + if index is not None and 0 <= index < len(self._ymessages): + initial_message = self._ymessages[index] + else: + return self.add_message(message) + + if not initial_message["id"] == message["id"]: + initial_message, index = self.get_message(message["id"]) + if initial_message is None: + return self.add_message(message) + + self.update_message(message, index, append) + return index def get_single_metadata(self, name) -> dict: """ From 41b4a0cbffb0b973e7dcad1cb44627f759cfcd1e Mon Sep 17 00:00:00 2001 From: Nicolas Brichet Date: Fri, 13 Sep 2024 16:17:28 +0200 Subject: [PATCH 2/4] insert a new message from backend at the correct position regarding to the timestamp --- .../jupyterlab_collaborative_chat/ychat.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/python/jupyterlab-collaborative-chat/jupyterlab_collaborative_chat/ychat.py b/python/jupyterlab-collaborative-chat/jupyterlab_collaborative_chat/ychat.py index 6dd8b50..b3cdc17 100644 --- a/python/jupyterlab-collaborative-chat/jupyterlab_collaborative_chat/ychat.py +++ b/python/jupyterlab-collaborative-chat/jupyterlab_collaborative_chat/ychat.py @@ -101,9 +101,12 @@ def add_message(self, message: dict) -> int: """ Append a message to the document. """ + timestamp: float = time.time() + message["time"] = timestamp with self._ydoc.transaction(): - self._ymessages.append(message) - return len(self._ymessages) - 1 + index = len(self._ymessages) - next((i for i, v in enumerate(self.get_messages()[::-1]) if v["time"] < timestamp), len(self._ymessages)) + self._ymessages.insert(index, message) + return index def update_message(self, message: dict, index: int, append: bool = False): """ From 2db8c017d95581269dc8d0aa8997f480d4b79ea4 Mon Sep 17 00:00:00 2001 From: Nicolas Brichet Date: Fri, 13 Sep 2024 16:17:51 +0200 Subject: [PATCH 3/4] Add tests on the YChat --- .github/workflows/build.yml | 8 +- conftest.py | 1 - .../jupyterlab-collaborative-chat/conftest.py | 11 -- .../tests/test_handlers.py | 4 - .../tests/test_ychat.py | 111 ++++++++++++++++++ 5 files changed, 118 insertions(+), 17 deletions(-) delete mode 100644 python/jupyterlab-collaborative-chat/conftest.py delete mode 100644 python/jupyterlab-collaborative-chat/jupyterlab_collaborative_chat/tests/test_handlers.py create mode 100644 python/jupyterlab-collaborative-chat/jupyterlab_collaborative_chat/tests/test_ychat.py diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 48b28a8..9d74ec4 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -57,7 +57,13 @@ jobs: - name: Install dependencies run: python -m pip install -U "jupyterlab>=4.0.0,<5" - - name: Build package + - name: Build the extensions + run: | + set -eux + ./scripts/install.sh ${{ matrix.extension }} + pytest -vv -r ap --cov python/jupyterlab-${{ matrix.extension }}-chat + + - name: Package the extensions run: | jlpm install jlpm build:${{ matrix.extension }} diff --git a/conftest.py b/conftest.py index 83bd5b7..5987622 100644 --- a/conftest.py +++ b/conftest.py @@ -1,6 +1,5 @@ # Copyright (c) Jupyter Development Team. # Distributed under the terms of the Modified BSD License. - import pytest pytest_plugins = ("pytest_jupyter.jupyter_server", ) diff --git a/python/jupyterlab-collaborative-chat/conftest.py b/python/jupyterlab-collaborative-chat/conftest.py deleted file mode 100644 index 1eea6b2..0000000 --- a/python/jupyterlab-collaborative-chat/conftest.py +++ /dev/null @@ -1,11 +0,0 @@ -# Copyright (c) Jupyter Development Team. -# Distributed under the terms of the Modified BSD License. - -import pytest - -pytest_plugins = ("pytest_jupyter.jupyter_server", ) - - -@pytest.fixture -def jp_server_config(jp_server_config): - return {"ServerApp": {"jpserver_extensions": {"jupyterlab_collaborative_chat": True}}} diff --git a/python/jupyterlab-collaborative-chat/jupyterlab_collaborative_chat/tests/test_handlers.py b/python/jupyterlab-collaborative-chat/jupyterlab_collaborative_chat/tests/test_handlers.py deleted file mode 100644 index 902af76..0000000 --- a/python/jupyterlab-collaborative-chat/jupyterlab_collaborative_chat/tests/test_handlers.py +++ /dev/null @@ -1,4 +0,0 @@ -# Copyright (c) Jupyter Development Team. -# Distributed under the terms of the Modified BSD License. - -### TODO #### diff --git a/python/jupyterlab-collaborative-chat/jupyterlab_collaborative_chat/tests/test_ychat.py b/python/jupyterlab-collaborative-chat/jupyterlab_collaborative_chat/tests/test_ychat.py new file mode 100644 index 0000000..f5934ab --- /dev/null +++ b/python/jupyterlab-collaborative-chat/jupyterlab_collaborative_chat/tests/test_ychat.py @@ -0,0 +1,111 @@ +# Copyright (c) Jupyter Development Team. +# Distributed under the terms of the Modified BSD License. + +# import jupyter_ydoc before YChat to avoid circular error +import jupyter_ydoc + +import pytest +import time +from copy import deepcopy +from uuid import uuid4 +from ..ychat import YChat + +USER = { + "username": str(uuid4()), + "name": "Test user", + "display_name": "Test user" +} + +USER2 = { + "username": str(uuid4()), + "name": "Test user 2", + "display_name": "Test user 2" +} + + +def create_message(): + return { + "type": "msg", + "id": str(uuid4()), + "body": "This is a test message", + "time": time.time(), + "sender": USER["username"] +} + + +def test_initialize_ychat(): + chat = YChat() + assert chat.get_messages() == [] + assert chat.get_users() == {} + assert chat.get_metadata() == {} + + +def test_add_user(): + chat = YChat() + chat.set_user(USER) + assert USER["username"] in chat.get_users().keys() + assert chat.get_users()[USER["username"]] == USER + + +def test_get_user_by_name(): + chat = YChat() + chat.set_user(USER) + chat.set_user(USER2) + assert chat.get_user_by_name(USER["name"]) == USER + assert chat.get_user_by_name(USER2["name"]) == USER2 + assert chat.get_user_by_name(str(uuid4())) == None + + +def test_add_message(): + chat = YChat() + msg = create_message() + chat.add_message(msg) + assert len(chat.get_messages()) == 1 + assert chat.get_messages()[0] == msg + + +def test_set_message_should_add(): + chat = YChat() + msg = create_message() + chat.set_message(msg) + assert len(chat.get_messages()) == 1 + assert chat.get_messages()[0] == msg + + +def test_set_message_should_update(): + chat = YChat() + msg = create_message() + index = chat.add_message(msg) + msg["body"] = "Updated content" + chat.set_message(msg, index) + assert len(chat.get_messages()) == 1 + assert chat.get_messages()[0] == msg + + +def test_set_message_should_add_with_new_id(): + chat = YChat() + msg = create_message() + index = chat.add_message(msg) + new_msg = deepcopy(msg) + new_msg["id"] = str(uuid4()) + new_msg["body"] = "Updated content" + chat.set_message(new_msg, index) + assert len(chat.get_messages()) == 2 + assert chat.get_messages()[0] == msg + assert chat.get_messages()[1] == new_msg + + +def test_set_message_should_update_with_wrong_index(): + chat = YChat() + msg = create_message() + chat.add_message(msg) + new_msg = create_message() + new_msg["body"] = "New content" + index = chat.add_message(new_msg) + assert index == 1 + new_msg["body"] = "Updated content" + chat.set_message(new_msg, 0) + assert len(chat.get_messages()) == 2 + assert chat.get_messages()[0] == msg + assert chat.get_messages()[1] == new_msg + From 70ff1f28763835a2f6f42b74034a80f505509686 Mon Sep 17 00:00:00 2001 From: Nicolas Brichet Date: Fri, 13 Sep 2024 16:33:09 +0200 Subject: [PATCH 4/4] Add a dedicated tests for pytest --- .github/workflows/build.yml | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9d74ec4..2a64431 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -39,6 +39,27 @@ jobs: set -eux jlpm run test + test_extensions: + runs-on: ubuntu-latest + needs: build_jupyter-chat + name: Python test on extensions + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Base Setup + uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 + + - name: Install dependencies + run: python -m pip install -U "jupyterlab>=4.0.0,<5" + + - name: Build the extensions + run: | + set -eux + ./scripts/install.sh + pytest -vv -r ap --cov + build_extensions: runs-on: ubuntu-latest needs: build_jupyter-chat @@ -57,12 +78,6 @@ jobs: - name: Install dependencies run: python -m pip install -U "jupyterlab>=4.0.0,<5" - - name: Build the extensions - run: | - set -eux - ./scripts/install.sh ${{ matrix.extension }} - pytest -vv -r ap --cov python/jupyterlab-${{ matrix.extension }}-chat - - name: Package the extensions run: | jlpm install