Skip to content

Commit f59aad4

Browse files
authored
Merge branch 'main' into jk/feat/input-block-ux
2 parents e5bdfa7 + 065b4c0 commit f59aad4

File tree

2 files changed

+275
-13
lines changed

2 files changed

+275
-13
lines changed

src/notebooks/deepnote/deepnoteSerializer.ts

Lines changed: 38 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { l10n, workspace, type CancellationToken, type NotebookData, type Notebo
55
import { logger } from '../../platform/logging';
66
import { IDeepnoteNotebookManager } from '../types';
77
import { DeepnoteDataConverter } from './deepnoteDataConverter';
8-
import type { DeepnoteProject } from '../../platform/deepnote/deepnoteTypes';
8+
import type { DeepnoteFile, DeepnoteNotebook } from '../../platform/deepnote/deepnoteTypes';
99

1010
export { DeepnoteBlock, DeepnoteNotebook, DeepnoteOutput, DeepnoteFile } from '../../platform/deepnote/deepnoteTypes';
1111

@@ -44,20 +44,24 @@ export class DeepnoteNotebookSerializer implements NotebookSerializer {
4444

4545
try {
4646
const contentString = new TextDecoder('utf-8').decode(content);
47-
const deepnoteProject = yaml.load(contentString) as DeepnoteProject;
47+
const deepnoteFile = yaml.load(contentString) as DeepnoteFile;
4848

49-
if (!deepnoteProject.project?.notebooks) {
49+
if (!deepnoteFile.project?.notebooks) {
5050
throw new Error('Invalid Deepnote file: no notebooks found');
5151
}
5252

53-
const projectId = deepnoteProject.project.id;
53+
const projectId = deepnoteFile.project.id;
5454
const notebookId = this.findCurrentNotebookId(projectId);
5555

5656
logger.debug(`DeepnoteSerializer: Project ID: ${projectId}, Selected notebook ID: ${notebookId}`);
5757

58+
if (deepnoteFile.project.notebooks.length === 0) {
59+
throw new Error('Deepnote project contains no notebooks.');
60+
}
61+
5862
const selectedNotebook = notebookId
59-
? deepnoteProject.project.notebooks.find((nb) => nb.id === notebookId)
60-
: deepnoteProject.project.notebooks[0];
63+
? deepnoteFile.project.notebooks.find((nb) => nb.id === notebookId)
64+
: this.findDefaultNotebook(deepnoteFile);
6165

6266
if (!selectedNotebook) {
6367
throw new Error(l10n.t('No notebook selected or found'));
@@ -67,17 +71,17 @@ export class DeepnoteNotebookSerializer implements NotebookSerializer {
6771

6872
logger.debug(`DeepnoteSerializer: Converted ${cells.length} cells from notebook blocks`);
6973

70-
this.notebookManager.storeOriginalProject(deepnoteProject.project.id, deepnoteProject, selectedNotebook.id);
74+
this.notebookManager.storeOriginalProject(deepnoteFile.project.id, deepnoteFile, selectedNotebook.id);
7175
logger.debug(`DeepnoteSerializer: Stored project ${projectId} in notebook manager`);
7276

7377
return {
7478
cells,
7579
metadata: {
76-
deepnoteProjectId: deepnoteProject.project.id,
77-
deepnoteProjectName: deepnoteProject.project.name,
80+
deepnoteProjectId: deepnoteFile.project.id,
81+
deepnoteProjectName: deepnoteFile.project.name,
7882
deepnoteNotebookId: selectedNotebook.id,
7983
deepnoteNotebookName: selectedNotebook.name,
80-
deepnoteVersion: deepnoteProject.version,
84+
deepnoteVersion: deepnoteFile.version,
8185
name: selectedNotebook.name,
8286
display_name: selectedNotebook.name
8387
}
@@ -110,7 +114,7 @@ export class DeepnoteNotebookSerializer implements NotebookSerializer {
110114
throw new Error('Missing Deepnote project ID in notebook metadata');
111115
}
112116

113-
const originalProject = this.notebookManager.getOriginalProject(projectId) as DeepnoteProject | undefined;
117+
const originalProject = this.notebookManager.getOriginalProject(projectId) as DeepnoteFile | undefined;
114118

115119
if (!originalProject) {
116120
throw new Error('Original Deepnote project not found. Cannot save changes.');
@@ -131,7 +135,7 @@ export class DeepnoteNotebookSerializer implements NotebookSerializer {
131135
throw new Error(`Notebook with ID ${notebookId} not found in project`);
132136
}
133137

134-
const updatedProject = JSON.parse(JSON.stringify(originalProject)) as DeepnoteProject;
138+
const updatedProject = JSON.parse(JSON.stringify(originalProject)) as DeepnoteFile;
135139

136140
const updatedBlocks = this.converter.convertCellsToBlocks(data.cells);
137141

@@ -178,4 +182,26 @@ export class DeepnoteNotebookSerializer implements NotebookSerializer {
178182

179183
return activeNotebook?.metadata?.deepnoteNotebookId;
180184
}
185+
186+
/**
187+
* Finds the default notebook to open when no selection is made.
188+
* @param file
189+
* @returns
190+
*/
191+
private findDefaultNotebook(file: DeepnoteFile): DeepnoteNotebook | undefined {
192+
if (file.project.notebooks.length === 0) {
193+
return undefined;
194+
}
195+
196+
const sortedNotebooks = file.project.notebooks.slice().sort((a, b) => a.name.localeCompare(b.name));
197+
const sortedNotebooksWithoutInit = file.project.initNotebookId
198+
? sortedNotebooks.filter((nb) => nb.id !== file.project.initNotebookId)
199+
: sortedNotebooks;
200+
201+
if (sortedNotebooksWithoutInit.length > 0) {
202+
return sortedNotebooksWithoutInit[0];
203+
}
204+
205+
return sortedNotebooks[0];
206+
}
181207
}

src/notebooks/deepnote/deepnoteSerializer.unit.test.ts

Lines changed: 237 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import { assert } from 'chai';
2+
import * as yaml from 'js-yaml';
23

34
import { DeepnoteNotebookSerializer } from './deepnoteSerializer';
45
import { DeepnoteNotebookManager } from './deepnoteNotebookManager';
56
import { DeepnoteDataConverter } from './deepnoteDataConverter';
6-
import type { DeepnoteProject } from '../../platform/deepnote/deepnoteTypes';
7+
import type { DeepnoteFile, DeepnoteProject } from '../../platform/deepnote/deepnoteTypes';
78

89
suite('DeepnoteNotebookSerializer', () => {
910
let serializer: DeepnoteNotebookSerializer;
@@ -59,6 +60,14 @@ suite('DeepnoteNotebookSerializer', () => {
5960
serializer = new DeepnoteNotebookSerializer(manager);
6061
});
6162

63+
/**
64+
* Helper function to convert a DeepnoteProject object with version to YAML format
65+
*/
66+
function projectToYaml(projectData: DeepnoteFile): Uint8Array {
67+
const yamlString = yaml.dump(projectData);
68+
return new TextEncoder().encode(yamlString);
69+
}
70+
6271
suite('deserializeNotebook', () => {
6372
test('should deserialize valid project with selected notebook', async () => {
6473
// Set up the manager to select the first notebook
@@ -313,4 +322,231 @@ project:
313322
assert.instanceOf(testManager, DeepnoteNotebookManager);
314323
});
315324
});
325+
326+
suite('default notebook selection', () => {
327+
test('should not select Init notebook when other notebooks are available', async () => {
328+
const projectData: DeepnoteFile = {
329+
version: '1.0',
330+
metadata: {
331+
createdAt: '2023-01-01T00:00:00Z',
332+
modifiedAt: '2023-01-02T00:00:00Z'
333+
},
334+
project: {
335+
id: 'project-with-init',
336+
name: 'Project with Init',
337+
initNotebookId: 'init-notebook',
338+
notebooks: [
339+
{
340+
id: 'init-notebook',
341+
name: 'Init',
342+
blocks: [
343+
{
344+
id: 'block-init',
345+
content: 'print("init")',
346+
sortingKey: 'a0',
347+
type: 'code'
348+
}
349+
],
350+
executionMode: 'block',
351+
isModule: false
352+
},
353+
{
354+
id: 'main-notebook',
355+
name: 'Main',
356+
blocks: [
357+
{
358+
id: 'block-main',
359+
content: 'print("main")',
360+
sortingKey: 'a0',
361+
type: 'code'
362+
}
363+
],
364+
executionMode: 'block',
365+
isModule: false
366+
}
367+
],
368+
settings: {}
369+
}
370+
};
371+
372+
const content = projectToYaml(projectData);
373+
const result = await serializer.deserializeNotebook(content, {} as any);
374+
375+
// Should select the Main notebook, not the Init notebook
376+
assert.strictEqual(result.metadata?.deepnoteNotebookId, 'main-notebook');
377+
assert.strictEqual(result.metadata?.deepnoteNotebookName, 'Main');
378+
});
379+
380+
test('should select Init notebook when it is the only notebook', async () => {
381+
const projectData: DeepnoteFile = {
382+
version: '1.0',
383+
metadata: {
384+
createdAt: '2023-01-01T00:00:00Z',
385+
modifiedAt: '2023-01-02T00:00:00Z'
386+
},
387+
project: {
388+
id: 'project-only-init',
389+
name: 'Project with only Init',
390+
initNotebookId: 'init-notebook',
391+
notebooks: [
392+
{
393+
id: 'init-notebook',
394+
name: 'Init',
395+
blocks: [
396+
{
397+
id: 'block-init',
398+
content: 'print("init")',
399+
sortingKey: 'a0',
400+
type: 'code'
401+
}
402+
],
403+
executionMode: 'block',
404+
isModule: false
405+
}
406+
],
407+
settings: {}
408+
}
409+
};
410+
411+
const content = projectToYaml(projectData);
412+
const result = await serializer.deserializeNotebook(content, {} as any);
413+
414+
// Should select the Init notebook since it's the only one
415+
assert.strictEqual(result.metadata?.deepnoteNotebookId, 'init-notebook');
416+
assert.strictEqual(result.metadata?.deepnoteNotebookName, 'Init');
417+
});
418+
419+
test('should select alphabetically first notebook when no initNotebookId', async () => {
420+
const projectData: DeepnoteFile = {
421+
version: '1.0',
422+
metadata: {
423+
createdAt: '2023-01-01T00:00:00Z',
424+
modifiedAt: '2023-01-02T00:00:00Z'
425+
},
426+
project: {
427+
id: 'project-alphabetical',
428+
name: 'Project Alphabetical',
429+
notebooks: [
430+
{
431+
id: 'zebra-notebook',
432+
name: 'Zebra Notebook',
433+
blocks: [
434+
{
435+
id: 'block-z',
436+
content: 'print("zebra")',
437+
sortingKey: 'a0',
438+
type: 'code'
439+
}
440+
],
441+
executionMode: 'block',
442+
isModule: false
443+
},
444+
{
445+
id: 'alpha-notebook',
446+
name: 'Alpha Notebook',
447+
blocks: [
448+
{
449+
id: 'block-a',
450+
content: 'print("alpha")',
451+
sortingKey: 'a0',
452+
type: 'code'
453+
}
454+
],
455+
executionMode: 'block',
456+
isModule: false
457+
},
458+
{
459+
id: 'bravo-notebook',
460+
name: 'Bravo Notebook',
461+
blocks: [
462+
{
463+
id: 'block-b',
464+
content: 'print("bravo")',
465+
sortingKey: 'a0',
466+
type: 'code'
467+
}
468+
],
469+
executionMode: 'block',
470+
isModule: false
471+
}
472+
],
473+
settings: {}
474+
}
475+
};
476+
477+
const content = projectToYaml(projectData);
478+
const result = await serializer.deserializeNotebook(content, {} as any);
479+
480+
// Should select the alphabetically first notebook
481+
assert.strictEqual(result.metadata?.deepnoteNotebookId, 'alpha-notebook');
482+
assert.strictEqual(result.metadata?.deepnoteNotebookName, 'Alpha Notebook');
483+
});
484+
485+
test('should sort Init notebook last when multiple notebooks exist', async () => {
486+
const projectData: DeepnoteFile = {
487+
version: '1.0',
488+
metadata: {
489+
createdAt: '2023-01-01T00:00:00Z',
490+
modifiedAt: '2023-01-02T00:00:00Z'
491+
},
492+
project: {
493+
id: 'project-multiple',
494+
name: 'Project with Multiple',
495+
initNotebookId: 'init-notebook',
496+
notebooks: [
497+
{
498+
id: 'charlie-notebook',
499+
name: 'Charlie',
500+
blocks: [
501+
{
502+
id: 'block-c',
503+
content: 'print("charlie")',
504+
sortingKey: 'a0',
505+
type: 'code'
506+
}
507+
],
508+
executionMode: 'block',
509+
isModule: false
510+
},
511+
{
512+
id: 'init-notebook',
513+
name: 'Init',
514+
blocks: [
515+
{
516+
id: 'block-init',
517+
content: 'print("init")',
518+
sortingKey: 'a0',
519+
type: 'code'
520+
}
521+
],
522+
executionMode: 'block',
523+
isModule: false
524+
},
525+
{
526+
id: 'alpha-notebook',
527+
name: 'Alpha',
528+
blocks: [
529+
{
530+
id: 'block-a',
531+
content: 'print("alpha")',
532+
sortingKey: 'a0',
533+
type: 'code'
534+
}
535+
],
536+
executionMode: 'block',
537+
isModule: false
538+
}
539+
],
540+
settings: {}
541+
}
542+
};
543+
544+
const content = projectToYaml(projectData);
545+
const result = await serializer.deserializeNotebook(content, {} as any);
546+
547+
// Should select Alpha, not Init even though "Init" comes before "Alpha" alphabetically when in upper case
548+
assert.strictEqual(result.metadata?.deepnoteNotebookId, 'alpha-notebook');
549+
assert.strictEqual(result.metadata?.deepnoteNotebookName, 'Alpha');
550+
});
551+
});
316552
});

0 commit comments

Comments
 (0)