Skip to content
Merged
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
7 changes: 3 additions & 4 deletions .github/workflows/check-licenses.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
name: Check Licenses
on:
push:
branches: ["main"]
branches: ['main']
pull_request:
branches: ["*"]
branches: ['**']

concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
Expand All @@ -18,7 +18,6 @@ jobs:
uses: actions/checkout@v4
- name: Base Setup
uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1

- name: Check Licenses
run: yarn check-licenses

2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ The `@deepnote/blocks` package is published on GitHub Packages. To install it, y
- Select the `read:packages` scope
- Generate and copy the token

2. Set the `GITHUB_TOKEN` environment variable to ensure `jlpm` (which is a wrapper around Yarn) can download the `@deepnote/blocks` package from the GitHub package registry. You can set the variable in `.zshrc` or manually like:
2. Set the `GITHUB_TOKEN` environment variable to ensure `jlpm` (which is a wrapper around Yarn) can download the `@deepnote/blocks` package from the GitHub package registry. You can export the variable in `.zshrc` (or by reading a `~/.env` file):
```shell
export GITHUB_TOKEN=your_token_here
```
Expand Down
48 changes: 29 additions & 19 deletions src/components/NotebookPicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React from 'react';
import { ReactWidget } from '@jupyterlab/apputils';
import { NotebookPanel } from '@jupyterlab/notebook';
import { HTMLSelect } from '@jupyterlab/ui-components';
import { deepnoteMetadataSchema } from '../types';

export class NotebookPicker extends ReactWidget {
private selected: string | null = null;
Expand Down Expand Up @@ -31,22 +32,30 @@ export class NotebookPicker extends ReactWidget {

const selected = event.target.value;
const deepnoteMetadata = this.panel.context.model.getMetadata('deepnote');
const notebooks = deepnoteMetadata?.notebooks;
const deepnoteMetadataValidated =
deepnoteMetadataSchema.safeParse(deepnoteMetadata);

if (notebooks && selected in notebooks) {
// clone the notebook JSON
const newModelData = { ...notebooks[selected] };
if (!deepnoteMetadataValidated.success) {
console.error(
'Invalid deepnote metadata:',
deepnoteMetadataValidated.error
);
return;
}

// preserve deepnote metadata *without* re-inserting all notebooks
newModelData.metadata = {
...(newModelData.metadata ?? {}),
deepnote: {
notebook_names: deepnoteMetadata?.notebook_names ?? [],
notebooks: deepnoteMetadata?.notebooks ?? {}
}
};
const notebooks = deepnoteMetadataValidated.data.notebooks;

model.fromJSON(newModelData);
if (selected in notebooks) {
model.fromJSON({
cells: notebooks[selected]?.cells ?? [],
metadata: {
deepnote: {
notebooks
}
},
nbformat: 4,
nbformat_minor: 0
});
model.dirty = false;
}

Expand All @@ -56,12 +65,13 @@ export class NotebookPicker extends ReactWidget {

render(): JSX.Element {
const deepnoteMetadata = this.panel.context.model.getMetadata('deepnote');
const metadataNames = deepnoteMetadata?.notebook_names;
const names =
Array.isArray(metadataNames) &&
metadataNames.every(n => typeof n === 'string')
? metadataNames
: [];

const deepnoteMetadataValidated =
deepnoteMetadataSchema.safeParse(deepnoteMetadata);

const names = deepnoteMetadataValidated.success
? Object.values(deepnoteMetadataValidated.data.notebooks).map(n => n.name)
: [];

return (
<HTMLSelect
Expand Down
5 changes: 3 additions & 2 deletions src/deepnote-content-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { transformDeepnoteYamlToNotebookContent } from './transform-deepnote-yam

export const deepnoteContentProviderName = 'deepnote-content-provider';

const deepnoteNotebookSchema = z.object({
const deepnoteFileFromServerSchema = z.object({
cells: z.array(z.any()), // or refine further with nbformat
metadata: z.object({
deepnote: z.object({
Expand All @@ -29,7 +29,7 @@ export class DeepnoteContentProvider extends RestContentProvider {
return model;
}

const validatedModelContent = deepnoteNotebookSchema.safeParse(
const validatedModelContent = deepnoteFileFromServerSchema.safeParse(
model.content
);

Expand All @@ -43,6 +43,7 @@ export class DeepnoteContentProvider extends RestContentProvider {
return model;
}

// Transform the Deepnote YAML to Jupyter notebook content
const transformedModelContent =
await transformDeepnoteYamlToNotebookContent(
validatedModelContent.data.metadata.deepnote.rawYamlString
Expand Down
3 changes: 1 addition & 2 deletions src/fallback-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,7 @@ export const blankDeepnoteNotebookContent: IDeepnoteNotebookContent = {
],
metadata: {
deepnote: {
rawYamlString: null,
deepnoteFile: null
notebooks: {}
}
},
nbformat: 4,
Expand Down
24 changes: 21 additions & 3 deletions src/transform-deepnote-yaml-to-notebook-content.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { IDeepnoteNotebookContent } from './types';
import { IDeepnoteNotebookContent, IDeepnoteNotebookMetadata } from './types';
import { blankCodeCell, blankDeepnoteNotebookContent } from './fallback-data';
import { deserializeDeepnoteFile } from '@deepnote/blocks';
import { convertDeepnoteBlockToJupyterCell } from './convert-deepnote-block-to-jupyter-cell';
Expand All @@ -9,6 +9,18 @@ export async function transformDeepnoteYamlToNotebookContent(
try {
const deepnoteFile = await deserializeDeepnoteFile(yamlString);

const notebooks = deepnoteFile.project.notebooks.reduce(
(acc, notebook) => {
acc[notebook.name] = {
id: notebook.id,
name: notebook.name,
cells: notebook.blocks.map(convertDeepnoteBlockToJupyterCell)
};
return acc;
},
{} as IDeepnoteNotebookMetadata['deepnote']['notebooks']
);

const selectedNotebook = deepnoteFile.project.notebooks[0];

if (!selectedNotebook) {
Expand All @@ -28,8 +40,14 @@ export async function transformDeepnoteYamlToNotebookContent(
);

return {
...blankDeepnoteNotebookContent,
cells
cells,
metadata: {
deepnote: {
notebooks
}
},
nbformat: 4,
nbformat_minor: 0
};
} catch (error) {
console.error('Failed to deserialize Deepnote file:', error);
Expand Down
31 changes: 27 additions & 4 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,33 @@
import { INotebookContent, INotebookMetadata } from '@jupyterlab/nbformat';
import type { DeepnoteFile } from '@deepnote/blocks';
import {
ICodeCell,
IMarkdownCell,
INotebookContent,
INotebookMetadata
} from '@jupyterlab/nbformat';

import { z } from 'zod';

export const deepnoteMetadataSchema = z.object({
notebooks: z.record(
z.string(),
z.object({
id: z.string(),
name: z.string(),
cells: z.array(z.any())
})
)
});

export interface IDeepnoteNotebookMetadata extends INotebookMetadata {
deepnote: {
rawYamlString: string | null;
deepnoteFile: DeepnoteFile | null;
notebooks: Record<
string,
{
id: string;
name: string;
cells: Array<ICodeCell | IMarkdownCell>;
}
>;
};
}

Expand Down