Skip to content

Commit 265df16

Browse files
jzhang20133Jialin Zhang
andauthored
adding awareness event when open and close websockets (#246)
Co-authored-by: Jialin Zhang <[email protected]>
1 parent fab7f19 commit 265df16

File tree

5 files changed

+104
-3
lines changed

5 files changed

+104
-3
lines changed

jupyter_collaboration/app.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from .handlers import DocSessionHandler, YDocWebSocketHandler
1212
from .loaders import FileLoaderMapping
1313
from .stores import SQLiteYStore
14-
from .utils import EVENTS_SCHEMA_PATH
14+
from .utils import AWARENESS_EVENTS_SCHEMA_PATH, EVENTS_SCHEMA_PATH
1515
from .websocketserver import JupyterWebsocketServer
1616

1717

@@ -60,6 +60,7 @@ class YDocExtension(ExtensionApp):
6060
def initialize(self):
6161
super().initialize()
6262
self.serverapp.event_logger.register_event_schema(EVENTS_SCHEMA_PATH)
63+
self.serverapp.event_logger.register_event_schema(AWARENESS_EVENTS_SCHEMA_PATH)
6364

6465
def initialize_settings(self):
6566
self.settings.update(
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
"$id": https://schema.jupyter.org/jupyter_collaboration/awareness/v1
2+
"$schema": "http://json-schema.org/draft-07/schema"
3+
version: 1
4+
title: Collaborative awareness events
5+
personal-data: true
6+
description: |
7+
Awareness events emitted from server-side during a collaborative session.
8+
type: object
9+
required:
10+
- roomid
11+
- username
12+
- action
13+
properties:
14+
roomid:
15+
type: string
16+
description: |
17+
Room ID. Usually composed by the file type, format and ID.
18+
username:
19+
type: string
20+
description: |
21+
The name of the user who joined or left room.
22+
action:
23+
enum:
24+
- join
25+
- leave
26+
description: |
27+
Possible values:
28+
1. join
29+
2. leave
30+
msg:
31+
type: string
32+
description: |
33+
Optional event message.

jupyter_collaboration/handlers.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
from .loaders import FileLoaderMapping
2222
from .rooms import DocumentRoom, TransientRoom
2323
from .utils import (
24+
JUPYTER_COLLABORATION_AWARENESS_EVENTS_URI,
2425
JUPYTER_COLLABORATION_EVENTS_URI,
2526
LogLevel,
2627
MessageType,
@@ -184,6 +185,7 @@ async def open(self, room_id):
184185
try:
185186
# Initialize the room
186187
await self.room.initialize()
188+
self._emit_awareness_event(self.current_user.username, "join")
187189
except Exception as e:
188190
_, _, file_id = decode_file_path(self._room_id)
189191
file = self._file_loaders[file_id]
@@ -205,6 +207,9 @@ async def open(self, room_id):
205207
await self._clean_room()
206208

207209
self._emit(LogLevel.INFO, "initialize", "New client connected.")
210+
else:
211+
if self.room.room_id != "JupyterLab:globalAwareness":
212+
self._emit_awareness_event(self.current_user.username, "join")
208213

209214
async def send(self, message):
210215
"""
@@ -284,6 +289,8 @@ def on_close(self) -> None:
284289
# keep the document for a while in case someone reconnects
285290
self.log.info("Cleaning room: %s", self._room_id)
286291
self.room.cleaner = asyncio.create_task(self._clean_room())
292+
if self.room.room_id != "JupyterLab:globalAwareness":
293+
self._emit_awareness_event(self.current_user.username, "leave")
287294

288295
def _emit(self, level: LogLevel, action: str | None = None, msg: str | None = None) -> None:
289296
_, _, file_id = decode_file_path(self._room_id)
@@ -297,6 +304,13 @@ def _emit(self, level: LogLevel, action: str | None = None, msg: str | None = No
297304

298305
self.event_logger.emit(schema_id=JUPYTER_COLLABORATION_EVENTS_URI, data=data)
299306

307+
def _emit_awareness_event(self, username: str, action: str, msg: str | None = None) -> None:
308+
data = {"roomid": self._room_id, "username": username, "action": action}
309+
if msg:
310+
data["msg"] = msg
311+
312+
self.event_logger.emit(schema_id=JUPYTER_COLLABORATION_AWARENESS_EVENTS_URI, data=data)
313+
300314
async def _clean_room(self) -> None:
301315
"""
302316
Async task for cleaning up the resources.

jupyter_collaboration/utils.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
# Copyright (c) Jupyter Development Team.
22
# Distributed under the terms of the Modified BSD License.
33

4-
import pathlib
54
from enum import Enum, IntEnum
5+
from pathlib import Path
66
from typing import Tuple
77

8+
EVENTS_FOLDER_PATH = Path(__file__).parent / "events"
89
JUPYTER_COLLABORATION_EVENTS_URI = "https://schema.jupyter.org/jupyter_collaboration/session/v1"
9-
EVENTS_SCHEMA_PATH = pathlib.Path(__file__).parent / "events" / "session.yaml"
10+
EVENTS_SCHEMA_PATH = EVENTS_FOLDER_PATH / "session.yaml"
11+
JUPYTER_COLLABORATION_AWARENESS_EVENTS_URI = (
12+
"https://schema.jupyter.org/jupyter_collaboration/awareness/v1"
13+
)
14+
AWARENESS_EVENTS_SCHEMA_PATH = EVENTS_FOLDER_PATH / "awareness.yaml"
1015

1116

1217
class MessageType(IntEnum):

tests/test_handlers.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from asyncio import Event, sleep
88
from typing import Any
99

10+
from jupyter_events.logger import EventLogger
1011
from jupyter_ydoc import YUnicode
1112
from pycrdt_websocket import WebsocketProvider
1213

@@ -83,3 +84,50 @@ def _on_document_change(target: str, e: Any) -> None:
8384
await sleep(0.1)
8485

8586
assert doc.source == content
87+
88+
89+
async def test_room_handler_doc_client_should_emit_awareness_event(
90+
rtc_create_file, rtc_connect_doc_client, jp_serverapp
91+
):
92+
path, content = await rtc_create_file("test.txt", "test")
93+
94+
event = Event()
95+
96+
def _on_document_change(target: str, e: Any) -> None:
97+
if target == "source":
98+
event.set()
99+
100+
doc = YUnicode()
101+
doc.observe(_on_document_change)
102+
103+
listener_was_called = False
104+
collected_data = []
105+
106+
async def my_listener(logger: EventLogger, schema_id: str, data: dict) -> None:
107+
nonlocal listener_was_called
108+
collected_data.append(data)
109+
listener_was_called = True
110+
111+
event_logger = jp_serverapp.event_logger
112+
event_logger.add_listener(
113+
schema_id="https://schema.jupyter.org/jupyter_collaboration/awareness/v1",
114+
listener=my_listener,
115+
)
116+
117+
async with await rtc_connect_doc_client("text", "file", path) as ws, WebsocketProvider(
118+
doc.ydoc, ws
119+
):
120+
await event.wait()
121+
await sleep(0.1)
122+
123+
fim = jp_serverapp.web_app.settings["file_id_manager"]
124+
125+
assert doc.source == content
126+
assert listener_was_called is True
127+
assert len(collected_data) == 2
128+
assert collected_data[0]["action"] == "join"
129+
assert collected_data[0]["roomid"] == "text:file:" + fim.get_id("test.txt")
130+
assert collected_data[0]["username"] is not None
131+
assert collected_data[1]["action"] == "leave"
132+
assert collected_data[1]["roomid"] == "text:file:" + fim.get_id("test.txt")
133+
assert collected_data[1]["username"] is not None

0 commit comments

Comments
 (0)