Skip to content

Commit

Permalink
fix: notebook collaborative (#347)
Browse files Browse the repository at this point in the history
* fix: notebook state

* chore: remove console log

* feat: datalayer collaboration

* chore: token for collaborative notebook
  • Loading branch information
echarles authored Jan 20, 2025
1 parent a035f88 commit 9fcc413
Show file tree
Hide file tree
Showing 9 changed files with 70 additions and 39 deletions.
4 changes: 2 additions & 2 deletions packages/react/public/index-local.html
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@
{
"jupyterServerUrl": "http://localhost:8686/api/jupyter-server",
"jupyterServerToken": "60c1661cc408f978c309d04157af55c9588ff9557c9380e4fb50785750703da6",
"runUrl": "https://oss.datalayer.run",
"runUrl": "https://prod1.datalayer.run",
"token": "",
"cpuEnvironment": "python-cpu-env",
"gpuEnvironment": "pytorch-cuda-env",
"gpuEnvironment": "pytorch-gpu-env",
"credits": 1
}
</script>
Expand Down
4 changes: 2 additions & 2 deletions packages/react/public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@
<title>Jupyter React Example</title>
<script id="datalayer-config-data" type="application/json">
{
"jupyterServerUrl": "https://oss.datalayer.run/api/jupyter-server",
"jupyterServerUrl": "https://prod1.datalayer.run/api/jupyter-server",
"jupyterServerToken": "60c1661cc408f978c309d04157af55c9588ff9557c9380e4fb50785750703da6",
"runUrl": "https://prod1.datalayer.run",
"token": "",
"cpuEnvironment": "python-cpu-env",
"gpuEnvironment": "pytorch-cuda-env",
"gpuEnvironment": "pytorch-gpu-env",
"credits": 1
}
</script>
Expand Down
68 changes: 44 additions & 24 deletions packages/react/src/components/notebook/Notebook.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ import { IRenderMime } from '@jupyterlab/rendermime-interfaces';
import { INotebookContent } from '@jupyterlab/nbformat';
import { ServiceManager, Kernel as JupyterKernel } from '@jupyterlab/services';
import { WebsocketProvider as YWebsocketProvider } from 'y-websocket';
import { useJupyter, Lite, Kernel, requestDocSession, COLLABORATION_ROOM_URL_PATH } from './../../jupyter';
import { useJupyter, Lite, Kernel, requestDocSession, ICollaborative, COLLABORATION_ROOM_URL_PATH } from './../../jupyter';
import { asObservable, Lumino } from '../lumino';
import { newUuid } from '../../utils';
import { OnSessionConnection, KernelTransfer } from '../../state';
import { OnSessionConnection, KernelTransfer, jupyterReactStore } from '../../state';
import { CellMetadataEditor } from './cell/metadata';
import { ICellSidebarProps } from './cell/sidebar';
import { INotebookToolbarProps } from './toolbar/NotebookToolbar';
Expand Down Expand Up @@ -52,7 +52,7 @@ export type INotebookProps = {
Toolbar?: (props: INotebookToolbarProps) => JSX.Element;
cellMetadataPanel: boolean;
cellSidebarMargin: number;
collaborative?: boolean;
collaborative?: ICollaborative;
extensions: DatalayerNotebookExtension[]
height?: string;
id: string;
Expand Down Expand Up @@ -118,7 +118,7 @@ export const Notebook = (props: INotebookProps) => {
const notebookStore = useNotebookStore();
const portals = notebookStore.selectNotebookPortals(id);
// Bootstrap the Notebook Adapter.
const bootstrapAdapter = async (collaborative: boolean, serviceManager?: ServiceManager.IManager, kernel?: Kernel) => {
const bootstrapAdapter = async (collaborative: ICollaborative, serviceManager?: ServiceManager.IManager, kernel?: Kernel) => {
const adapter = new NotebookAdapter({
...props,
id,
Expand All @@ -136,28 +136,48 @@ export const Notebook = (props: INotebookProps) => {
extension.createNew(adapter.notebookPanel!, adapter.context!);
setExtensionComponents(extensionComponents.concat(extension.component ?? <></>));
});
if (collaborative) {
// Setup Collaboration.
// Setup Collaboration.
if (collaborative == 'jupyter') {
const ydoc = (adapter.notebookPanel?.model?.sharedModel as any).ydoc;
const token = jupyterReactStore.getState().jupyterConfig?.jupyterServerToken;
const session = await requestDocSession("json", "notebook", path!);
const yWebsocketProvider = new YWebsocketProvider(
URLExt.join(serviceManager?.serverSettings.wsUrl!, COLLABORATION_ROOM_URL_PATH),
`${session.format}:${session.type}:${session.fileId}`,
ydoc,
const roomURL = URLExt.join(serviceManager?.serverSettings.wsUrl!, COLLABORATION_ROOM_URL_PATH);
const roomName = `${session.format}:${session.type}:${session.fileId}`;
const yWebsocketProvider = new YWebsocketProvider(roomURL, roomName, ydoc,
{
disableBc: true,
params: { sessionId: session.sessionId },
// awareness: this._awareness
params: {
sessionId: session.sessionId,
token: token!,
},
// awareness: this._awareness
}
);
console.log('Collaboration is setup with websocket provider.', yWebsocketProvider);
// Update the notebook state with the adapter.
notebookStore.update({ id, state: { adapter } });
// Update the notebook state further to events.
adapter.notebookPanel?.model?.contentChanged.connect((notebookModel, _) => {
notebookStore.changeModel({ id, notebookModel });
});
}
else if (collaborative == 'datalayer') {
const ydoc = (adapter.notebookPanel?.model?.sharedModel as any).ydoc;
const token = jupyterReactStore.getState().datalayerConfig?.token;
const runURL = jupyterReactStore.getState().datalayerConfig?.runUrl;
const roomName = id;
const roomURL = URLExt.join(runURL!.replace('http', 'ws'), `/api/spacer/v1/rooms`);
const yWebsocketProvider = new YWebsocketProvider(roomURL, roomName, ydoc,
{
disableBc: true,
params: {
token: token!,
}
// awareness: this._awareness
}
);
console.log('Collaboration is setup with websocket provider.', yWebsocketProvider);
}
// Update the notebook state with the adapter.
notebookStore.update({ id, state: { adapter } });
// Update the notebook state further to events.
adapter.notebookPanel?.model?.contentChanged.connect((notebookModel, _) => {
notebookStore.changeModel({ id, notebookModel });
});
/*
adapter.notebookPanel?.model?.sharedModel.changed.connect((_, notebookChange) => {
notebookStore.changeNotebook({ id, notebookChange });
Expand Down Expand Up @@ -206,7 +226,7 @@ export const Notebook = (props: INotebookProps) => {
});
}
//
const createAdapter = (collaborative: boolean, serviceManager?: ServiceManager.IManager, kernel?: Kernel) => {
const createAdapter = (collaborative: ICollaborative, serviceManager?: ServiceManager.IManager, kernel?: Kernel) => {
if (!kernel) {
bootstrapAdapter(collaborative, serviceManager, kernel);
} else {
Expand All @@ -227,10 +247,10 @@ export const Notebook = (props: INotebookProps) => {
// Mutation Effects.
useEffect(() => {
if (serviceManager && serverless) {
createAdapter(collaborative ?? false, serviceManager, kernel);
createAdapter(collaborative, serviceManager, kernel);
}
else if (serviceManager && kernel) {
createAdapter(collaborative ?? false, serviceManager, kernel);
createAdapter(collaborative, serviceManager, kernel);
}
}, [collaborative, serviceManager, kernel]);
useEffect(() => {
Expand All @@ -256,13 +276,13 @@ export const Notebook = (props: INotebookProps) => {
useEffect(() => {
if (adapter && path && adapter.path !== path) {
disposeAdapter();
createAdapter(collaborative ?? false, serviceManager);
createAdapter(collaborative, serviceManager);
}
}, [path]);
useEffect(() => {
if (adapter && url && adapter.url !== url) {
disposeAdapter();
createAdapter(collaborative ?? false, serviceManager);
createAdapter(collaborative, serviceManager);
}
}, [collaborative, url]);
// Dispose Effects.
Expand Down Expand Up @@ -360,7 +380,7 @@ export const Notebook = (props: INotebookProps) => {
Notebook.defaultProps = {
cellMetadataPanel: false,
cellSidebarMargin: 120,
collaborative: false,
collaborative: undefined,
extensions: [],
height: '100vh',
kernelClients: [],
Expand Down
18 changes: 9 additions & 9 deletions packages/react/src/components/notebook/NotebookState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -231,11 +231,11 @@ export const notebookStore = createStore<NotebookState>((set, get) => ({
set((state: NotebookState) => ({ notebooks }));
}
},
addPortals: (portalsId: ReactPortalsMutation) => {
addPortals: (portalsMutation: ReactPortalsMutation) => {
const notebooks = get().notebooks;
const notebook = notebooks.get(portalsId.id);
const notebook = notebooks.get(portalsMutation.id);
if (notebook) {
notebook.portals = notebook.portals.concat(portalsId.portals);
notebook.portals = notebook.portals.concat(portalsMutation.portals);
set((state: NotebookState) => ({ notebooks }));
}
},
Expand All @@ -248,19 +248,19 @@ export const notebookStore = createStore<NotebookState>((set, get) => ({
}
set((state: NotebookState) => ({ notebooks }));
},
setPortals: (portalsId: ReactPortalsMutation) => {
setPortals: (portalsMutation: ReactPortalsMutation) => {
const notebooks = get().notebooks;
const notebook = notebooks.get(portalsId.id);
const notebook = notebooks.get(portalsMutation.id);
if (notebook) {
notebook.portals = portalsId.portals;
notebook.portals = portalsMutation.portals;
set((state: NotebookState) => ({ notebooks }));
}
},
setPortalDisplay: (portalDisplayId: PortalDisplayMutation) => {
setPortalDisplay: (portalDisplayMutation: PortalDisplayMutation) => {
const notebooks = get().notebooks;
const notebook = notebooks.get(portalDisplayId.id);
const notebook = notebooks.get(portalDisplayMutation.id);
if (notebook) {
notebook.portalDisplay = portalDisplayId.portalDisplay;
notebook.portalDisplay = portalDisplayMutation.portalDisplay;
set((state: NotebookState) => ({ notebooks }));
}
},
Expand Down
2 changes: 1 addition & 1 deletion packages/react/src/examples/NotebookCollaborative.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const NotebookCollaborative = () => {
return (
<JupyterReactTheme>
<Notebook
collaborative
collaborative="datalayer"
path="collaboration.ipynb"
id="notebook-collaboration-id"
startDefaultKernel={true}
Expand Down
2 changes: 1 addition & 1 deletion packages/react/src/jupyter/JupyterConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ function loadDatalayerConfig(name?: string): any {
else {
console.log('No Datalayer config data found in page');
}
}
}
// @ts-expect-error IJupyterConfig does not have index signature
return name ? config[name] : config;
}
Expand Down
9 changes: 9 additions & 0 deletions packages/react/src/jupyter/collaboration/ICollaborative.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/*
* Copyright (c) 2021-2023 Datalayer, Inc.
*
* MIT License
*/

export type ICollaborative = 'jupyter' | 'datalayer' | undefined;

export default ICollaborative;
1 change: 1 addition & 0 deletions packages/react/src/jupyter/collaboration/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@
*/

export * from './DatalayerCollaboration';
export * from './ICollaborative';
export * from './JupyterCollaboration';
1 change: 1 addition & 0 deletions packages/react/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"forceConsistentCasingInFileNames": true,
"baseUrl": "/",
"rootDir": "./src",
"allowJs": false,
"allowSyntheticDefaultImports": true,
"composite": true,
"declaration": true,
Expand Down

0 comments on commit 9fcc413

Please sign in to comment.