From 9d27f2bf0e9479db06b88d69ec29e9e885f3902a Mon Sep 17 00:00:00 2001 From: David Brochart Date: Wed, 30 Oct 2024 15:46:51 +0100 Subject: [PATCH] Add fork title and description --- .../jupyter_server_ydoc/events/fork.yaml | 32 ++++++++++++--- .../jupyter_server_ydoc/handlers.py | 36 +++++++++------- .../jupyter_server_ydoc/pytest_plugin.py | 15 ++++++- tests/test_handlers.py | 41 +++++++++++++------ 4 files changed, 90 insertions(+), 34 deletions(-) diff --git a/projects/jupyter-server-ydoc/jupyter_server_ydoc/events/fork.yaml b/projects/jupyter-server-ydoc/jupyter_server_ydoc/events/fork.yaml index 832f1eb7..0125fdf2 100644 --- a/projects/jupyter-server-ydoc/jupyter_server_ydoc/events/fork.yaml +++ b/projects/jupyter-server-ydoc/jupyter_server_ydoc/events/fork.yaml @@ -7,19 +7,41 @@ description: | Fork events emitted from server-side during a collaborative session. type: object required: - - root_roomid - fork_roomid + - fork_info - username - action properties: - root_roomid: - type: string - description: | - Root room ID. Usually composed by the file type, format and ID. fork_roomid: type: string description: | Fork root room ID. + fork_info: + type: object + description: | + Fork root room information. + required: + - root_roomid + - synchronize + - title + - description + properties: + root_roomid: + type: string + description: | + Root room ID. Usually composed by the file type, format and ID. + synchronize: + type: boolean + description: | + Whether the fork is kept in sync with the root. + title: + type: string + description: | + The title of the fork. + description: + type: string + description: | + The description of the fork. username: type: string description: | diff --git a/projects/jupyter-server-ydoc/jupyter_server_ydoc/handlers.py b/projects/jupyter-server-ydoc/jupyter_server_ydoc/handlers.py index 193c6fa1..b76666d4 100644 --- a/projects/jupyter-server-ydoc/jupyter_server_ydoc/handlers.py +++ b/projects/jupyter-server-ydoc/jupyter_server_ydoc/handlers.py @@ -40,7 +40,7 @@ SERVER_SESSION = str(uuid.uuid4()) FORK_DOCUMENTS = {} -FORK_ROOMS: dict[str, str] = {} +FORK_ROOMS: dict[str, dict[str, str]] = {} class YDocWebSocketHandler(WebSocketHandler, JupyterHandler): @@ -624,15 +624,13 @@ def initialize( @authorized async def get(self, root_roomid): """ - Returns a dictionary of given root room ID to the root's fork room IDs. + Returns a dictionary of fork room ID to fork room information for the given root room ID. """ self.write( { - root_roomid: [ - fork_roomid - for fork_roomid, _root_roomid in FORK_ROOMS.items() - if _root_roomid == root_roomid - ] + fork_roomid: fork_info + for fork_roomid, fork_info in FORK_ROOMS.items() + if fork_info["root_roomid"] == root_roomid } ) @@ -644,22 +642,29 @@ async def put(self, root_roomid): Optionally keeps the fork in sync with the root. """ fork_roomid = uuid4().hex - FORK_ROOMS[fork_roomid] = root_roomid root_room = await self._websocket_server.get_room(root_roomid) update = root_room.ydoc.get_update() fork_ydoc = Doc() fork_ydoc.apply_update(update) model = self.get_json_body() - if model.get("sync"): + synchronize = model.get("synchronize", False) + if synchronize: root_room.ydoc.observe(lambda event: fork_ydoc.apply_update(event.update)) + FORK_ROOMS[fork_roomid] = fork_info = { + "root_roomid": root_roomid, + "synchronize": synchronize, + "title": model.get("title", ""), + "description": model.get("description", ""), + } fork_room = YRoom(ydoc=fork_ydoc) self._websocket_server.rooms[fork_roomid] = fork_room await self._websocket_server.start_room(fork_room) - self._emit_fork_event(self.current_user.username, root_roomid, fork_roomid, "create") + self._emit_fork_event(self.current_user.username, fork_roomid, fork_info, "create") data = json.dumps( { "sessionId": SERVER_SESSION, - "roomId": fork_roomid, + "fork_roomid": fork_roomid, + "fork_info": fork_info, } ) self.set_status(201) @@ -671,7 +676,8 @@ async def delete(self, fork_roomid): """ Deletes a forked document, and optionally merges it back in the root document. """ - root_roomid = FORK_ROOMS[fork_roomid] + fork_info = FORK_ROOMS[fork_roomid] + root_roomid = fork_info["root_roomid"] del FORK_ROOMS[fork_roomid] if self.get_query_argument("merge") == "true": root_room = await self._websocket_server.get_room(root_roomid) @@ -681,16 +687,16 @@ async def delete(self, fork_roomid): fork_update = fork_ydoc.get_update() root_ydoc.apply_update(fork_update) await self._websocket_server.delete_room(name=fork_roomid) - self._emit_fork_event(self.current_user.username, root_roomid, fork_roomid, "delete") + self._emit_fork_event(self.current_user.username, fork_roomid, fork_info, "delete") self.set_status(200) def _emit_fork_event( - self, username: str, root_roomid: str, fork_roomid: str, action: str + self, username: str, fork_roomid: str, fork_info: dict[str, str], action: str ) -> None: data = { "username": username, - "root_roomid": root_roomid, "fork_roomid": fork_roomid, + "fork_info": fork_info, "action": action, } self.event_logger.emit(schema_id=JUPYTER_COLLABORATION_FORK_EVENTS_URI, data=data) diff --git a/projects/jupyter-server-ydoc/jupyter_server_ydoc/pytest_plugin.py b/projects/jupyter-server-ydoc/jupyter_server_ydoc/pytest_plugin.py index dd652aaf..008236c4 100644 --- a/projects/jupyter-server-ydoc/jupyter_server_ydoc/pytest_plugin.py +++ b/projects/jupyter-server-ydoc/jupyter_server_ydoc/pytest_plugin.py @@ -169,12 +169,23 @@ async def _inner(root_roomid: str) -> Any: @pytest.fixture def rtc_create_fork_client(jp_fetch): - async def _inner(root_roomid: str, sync: bool) -> Any: + async def _inner( + root_roomid: str, + synchronize: bool, + title: str | None = None, + description: str | None = None, + ) -> Any: return await jp_fetch( "/api/collaboration/fork", root_roomid, method="PUT", - body=json.dumps({"sync": sync}), + body=json.dumps( + { + "synchronize": synchronize, + "title": title, + "description": description, + } + ), ) return _inner diff --git a/tests/test_handlers.py b/tests/test_handlers.py index 739c6cfa..17f3c95b 100644 --- a/tests/test_handlers.py +++ b/tests/test_handlers.py @@ -261,36 +261,53 @@ def _on_root_change(topic: str, event: Any) -> None: ): await root_connect_event.wait() - resp = await rtc_create_fork_client(root_roomid, False) + resp = await rtc_create_fork_client(root_roomid, False, "my fork0", "is awesome0") data = json.loads(resp.body.decode("utf-8")) - fork_roomid0 = data["roomId"] + fork_roomid0 = data["fork_roomid"] resp = await rtc_get_forks_client(root_roomid) data = json.loads(resp.body.decode("utf-8")) - assert data == {root_roomid: [fork_roomid0]} + expected_data0 = { + fork_roomid0: { + "root_roomid": root_roomid, + "synchronize": False, + "title": "my fork0", + "description": "is awesome0", + } + } + assert data == expected_data0 assert collected_data == [ { "username": IsStr(), - "root_roomid": root_roomid, "fork_roomid": fork_roomid0, + "fork_info": expected_data0[fork_roomid0], "action": "create", } ] - resp = await rtc_create_fork_client(root_roomid, True) + resp = await rtc_create_fork_client(root_roomid, True, "my fork1", "is awesome1") data = json.loads(resp.body.decode("utf-8")) - fork_roomid1 = data["roomId"] + fork_roomid1 = data["fork_roomid"] resp = await rtc_get_forks_client(root_roomid) data = json.loads(resp.body.decode("utf-8")) - assert data == {root_roomid: [fork_roomid0, fork_roomid1]} + expected_data1 = { + fork_roomid1: { + "root_roomid": root_roomid, + "synchronize": True, + "title": "my fork1", + "description": "is awesome1", + } + } + expected_data = dict(**expected_data0, **expected_data1) + assert data == expected_data assert len(collected_data) == 2 assert collected_data[1] == { "username": IsStr(), - "root_roomid": root_roomid, "fork_roomid": fork_roomid1, + "fork_info": expected_data[fork_roomid1], "action": "create", } @@ -322,12 +339,12 @@ def _on_fork_change(topic: str, event: Any) -> None: assert str(root_text) == "Hello, World!" resp = await rtc_get_forks_client(root_roomid) data = json.loads(resp.body.decode("utf-8")) - assert data == {root_roomid: [fork_roomid1]} + assert data == expected_data1 assert len(collected_data) == 3 assert collected_data[2] == { "username": IsStr(), - "root_roomid": root_roomid, "fork_roomid": fork_roomid0, + "fork_info": expected_data[fork_roomid0], "action": "delete", } @@ -336,11 +353,11 @@ def _on_fork_change(topic: str, event: Any) -> None: assert str(root_text) == "Hello, World! Hi!" resp = await rtc_get_forks_client(root_roomid) data = json.loads(resp.body.decode("utf-8")) - assert data == {root_roomid: []} + assert data == {} assert len(collected_data) == 4 assert collected_data[3] == { "username": IsStr(), - "root_roomid": root_roomid, "fork_roomid": fork_roomid1, + "fork_info": expected_data[fork_roomid1], "action": "delete", }