Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 15 additions & 5 deletions jupyter_server_documents/outputs/handlers.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
import json

from tornado import web

from jupyter_server.auth.decorator import authorized
from jupyter_server.base.handlers import APIHandler
from jupyter_server.utils import url_path_join


class OutputsAPIHandler(APIHandler):
Expand All @@ -18,7 +15,7 @@ def outputs(self):

@web.authenticated
@authorized
async def get(self, file_id=None, cell_id=None, output_index=None):
async def get(self, file_id, cell_id=None, output_index=None):
try:
if output_index:
output = self.outputs.get_output(file_id, cell_id, output_index)
Expand All @@ -35,6 +32,19 @@ async def get(self, file_id=None, cell_id=None, output_index=None):
self.write(output)
self.finish(set_content_type=content_type)

@web.authenticated
@authorized
async def delete(self, file_id, cell_id=None, output_index=None):
# output_index is accepted but ignored as we clear all cell outputs regardless
try:
self.outputs.clear(file_id, cell_id)
except (FileNotFoundError):
self.set_status(404)
self.finish({"error": "Output not found."})
else:
self.set_status(200)
self.finish()


class StreamAPIHandler(APIHandler):
"""An outputs service API handler."""
Expand Down Expand Up @@ -69,7 +79,7 @@ async def get(self, file_id=None, cell_id=None):

_file_id_regex = r"(?P<file_id>[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12})"
# In nbformat, cell_ids follow this format, compatible with uuid4
_cell_id_regex = rf"(?P<cell_id>[a-zA-Z0-9_-]+)"
_cell_id_regex = r"(?P<cell_id>[a-zA-Z0-9_-]+)"

# non-negative integers
_output_index_regex = r"(?P<output_index>0|[1-9]\d*)"
Expand Down
4 changes: 3 additions & 1 deletion jupyter_server_documents/rooms/yroom.py
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,9 @@ def _init_awareness(self, ydoc: pycrdt.Doc) -> pycrdt.Awareness:
`awareness.unobserve(self._awareness_subscription)`.
"""
self._awareness = pycrdt.Awareness(ydoc=ydoc)
if self.room_id != "JupyterLab:globalAwareness":
file_format, file_type, file_id = self.room_id.split(":")
self._awareness.set_local_state_field("file_id", file_id)
self._awareness_subscription = self._awareness.observe(
self._on_awareness_update
)
Expand Down Expand Up @@ -325,7 +328,6 @@ def _init_jupyter_ydoc(self, ydoc: pycrdt.Doc, awareness: pycrdt.Awareness) -> Y
self._jupyter_ydoc.observe(self._on_jupyter_ydoc_update)
return self._jupyter_ydoc


@property
def clients(self) -> YjsClientGroup:
"""
Expand Down
40 changes: 39 additions & 1 deletion src/notebook-factory/notebook-factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
ICodeCellModel
} from '@jupyterlab/cells';
import { IChangedArgs } from '@jupyterlab/coreutils';
import { Notebook, NotebookPanel } from '@jupyterlab/notebook';
import { Notebook, NotebookPanel, NotebookActions } from '@jupyterlab/notebook';
import { CellChange, createMutex, ISharedCodeCell } from '@jupyter/ydoc';
import { IOutputAreaModel, OutputAreaModel } from '@jupyterlab/outputarea';
import { requestAPI } from '../handler';
Expand Down Expand Up @@ -237,7 +237,7 @@

// Look through all client states for cell execution states
let hasAnyExecutionStates = false;
for (const [_, clientState] of awarenessStates) {

Check warning on line 240 in src/notebook-factory/notebook-factory.ts

View workflow job for this annotation

GitHub Actions / build

'_' is assigned a value but never used
if (clientState && 'cell_execution_states' in clientState) {
const cellStates = clientState['cell_execution_states'];
hasAnyExecutionStates = true;
Expand Down Expand Up @@ -340,3 +340,41 @@
return new ResettableNotebook(options);
}
}

// Add a handler for the outputCleared signal
NotebookActions.outputCleared.connect((sender, args) => {
const { notebook, cell } = args;
const cellId = cell.model.sharedModel.getId();
const awareness = notebook.model?.sharedModel.awareness;
const awarenessStates = awareness?.getStates();

if (awarenessStates?.size === 0) {
console.log('Could not delete cell output, awareness is not present');
}

let fileId = null;
for (const [_, state] of awarenessStates || []) {

Check warning on line 356 in src/notebook-factory/notebook-factory.ts

View workflow job for this annotation

GitHub Actions / build

'_' is assigned a value but never used
if (state && 'file_id' in state) {
fileId = state['file_id'];
}
}

if (fileId === null) {
console.error('No fileId found in awareness');
}

try {
// Send a request to clear the outputs
requestAPI(`/api/outputs/${fileId}/${cellId}`, {
method: 'DELETE'
})
.then(() => {
console.debug(`Successfully cleared outputs for cell ${cellId}`);
})
.catch((error: Error) => {
console.error(`Failed to clear outputs for cell ${cellId}:`, error);
});
} catch (error: unknown) {
console.error('Error in output clearing process:', error);
}
});
Loading