Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
9 changes: 8 additions & 1 deletion cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,16 @@
"JVSC",
"millis",
"nbformat",
"nbinsx",
"numpy",
"pgsql",
"pids",
"Pids",
"plotly",
"PYTHONHOME",
"Reselecting",
"taskkill",
"toolsai",
"unconfigured",
"Unconfigured",
"unittests",
Expand All @@ -58,7 +61,11 @@
"venv's",
"Venv",
"venvs",
"vscode"
"vscode",
"xanchor",
"xaxis",
"yanchor",
"yaxis"
],
"useGitignore": true
}
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@
"engines": {
"vscode": "^1.95.0"
},
"extensionDependencies": [
"ms-toolsai.jupyter-renderers"
],
"l10n": "./l10n",
"extensionKind": [
"workspace"
Expand Down
11 changes: 11 additions & 0 deletions src/notebooks/deepnote/deepnoteDataConverter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,8 @@ export class DeepnoteDataConverter {
);
} else if (item.mime === 'application/vnd.vega.v5+json') {
data['application/vnd.vega.v5+json'] = JSON.parse(new TextDecoder().decode(item.data));
} else if (item.mime === 'application/vnd.plotly.v1+json') {
data['application/vnd.plotly.v1+json'] = JSON.parse(new TextDecoder().decode(item.data));
} else if (item.mime === 'application/vnd.deepnote.sql-output-metadata+json') {
data['application/vnd.deepnote.sql-output-metadata+json'] = JSON.parse(
new TextDecoder().decode(item.data)
Expand Down Expand Up @@ -342,6 +344,15 @@ export class DeepnoteDataConverter {
);
}

if (data['application/vnd.plotly.v1+json']) {
items.push(
NotebookCellOutputItem.json(
data['application/vnd.plotly.v1+json'],
'application/vnd.plotly.v1+json'
)
);
}

if (data['application/vnd.deepnote.sql-output-metadata+json']) {
items.push(
NotebookCellOutputItem.json(
Expand Down
108 changes: 108 additions & 0 deletions src/notebooks/deepnote/deepnoteDataConverter.unit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -513,6 +513,54 @@ suite('DeepnoteDataConverter', () => {
assert.strictEqual(new TextDecoder().decode(markdownItem!.data), markdownContent);
assert.strictEqual(new TextDecoder().decode(plainItem!.data), 'Result\n\nThis is formatted output.');
});

test('converts Plotly chart output', () => {
const plotlyData = {
data: [
{
type: 'bar',
x: ['A', 'B', 'C'],
y: [10, 20, 15]
}
],
layout: {
title: 'Sample Chart',
xaxis: { title: 'Category' },
yaxis: { title: 'Value' }
}
};

const deepnoteOutputs: DeepnoteOutput[] = [
{
output_type: 'execute_result',
execution_count: 1,
data: {
'application/vnd.plotly.v1+json': plotlyData
}
}
];

const blocks: DeepnoteBlock[] = [
{
blockGroup: 'test-group',
id: 'block1',
type: 'code',
content: 'fig.show()',
sortingKey: 'a0',
outputs: deepnoteOutputs
}
];

const cells = converter.convertBlocksToCells(blocks);
const outputs = cells[0].outputs!;

assert.strictEqual(outputs.length, 1);
assert.strictEqual(outputs[0].items.length, 1);
assert.strictEqual(outputs[0].items[0].mime, 'application/vnd.plotly.v1+json');

const outputData = JSON.parse(new TextDecoder().decode(outputs[0].items[0].data));
assert.deepStrictEqual(outputData, plotlyData);
});
});

suite('round trip conversion', () => {
Expand Down Expand Up @@ -597,6 +645,66 @@ suite('DeepnoteDataConverter', () => {
assert.deepStrictEqual(output.data?.['application/vnd.deepnote.sql-output-metadata+json'], sqlMetadata);
});

test('Plotly chart output round-trips correctly', () => {
const plotlyData = {
data: [
{
type: 'histogram',
x: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
nbinsx: 30,
opacity: 0.75
}
],
layout: {
title: 'Sessions per week by churn status',
xaxis: { title: 'Sessions per week' },
yaxis: { title: 'Users' },
legend: {
yanchor: 'top',
y: 1,
xanchor: 'left',
x: 1.02
}
}
};

const originalBlocks: DeepnoteBlock[] = [
{
blockGroup: 'test-group',
id: 'plotly-block',
type: 'code',
content: 'fig = px.histogram(df)\nfig.show()',
sortingKey: 'a0',
executionCount: 1,
metadata: {},
outputs: [
{
output_type: 'execute_result',
execution_count: 1,
data: {
'application/vnd.plotly.v1+json': plotlyData
}
}
]
}
];

const cells = converter.convertBlocksToCells(originalBlocks);
const roundTripBlocks = converter.convertCellsToBlocks(cells);

// The round-trip should preserve the Plotly chart output
assert.strictEqual(roundTripBlocks.length, 1);
assert.strictEqual(roundTripBlocks[0].id, 'plotly-block');
assert.strictEqual(roundTripBlocks[0].outputs?.length, 1);

const output = roundTripBlocks[0].outputs![0] as {
output_type: string;
data?: Record<string, unknown>;
};
assert.strictEqual(output.output_type, 'execute_result');
assert.deepStrictEqual(output.data?.['application/vnd.plotly.v1+json'], plotlyData);
});

test('real deepnote notebook round-trips without losing data', () => {
// Inline test data representing a real Deepnote notebook with various block types
// blockGroup is an optional field not in the DeepnoteBlock interface, so we cast as any
Expand Down