Skip to content

Commit bed6e2a

Browse files
committed
Merge branch 'main' into oleh/grn-4778-support-chart-blocks
2 parents f15fed6 + cddc017 commit bed6e2a

35 files changed

+3546
-23
lines changed

build/esbuild/build.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ const commonExternals = [
4848
'vscode',
4949
'commonjs',
5050
'node:crypto',
51+
'node:fs/promises',
52+
'node:path',
5153
'vscode-jsonrpc', // Used by a few modules, might as well pull this out, instead of duplicating it in separate bundles.
5254
// Ignore telemetry specific packages that are not required.
5355
'applicationinsights-native-metrics',
@@ -348,6 +350,11 @@ async function buildAll() {
348350
path.join(extensionFolder, 'src', 'webviews', 'webview-side', 'data-explorer', 'index.tsx'),
349351
path.join(extensionFolder, 'dist', 'webviews', 'webview-side', 'viewers', 'dataExplorer.js'),
350352
{ target: 'web', watch: watchAll }
353+
),
354+
build(
355+
path.join(extensionFolder, 'src', 'webviews', 'webview-side', 'integrations', 'index.tsx'),
356+
path.join(extensionFolder, 'dist', 'webviews', 'webview-side', 'integrations', 'index.js'),
357+
{ target: 'web', watch: watchAll }
351358
)
352359
);
353360

package-lock.json

Lines changed: 478 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,30 @@
8585
"category": "Deepnote",
8686
"icon": "$(reveal)"
8787
},
88+
{
89+
"command": "deepnote.manageIntegrations",
90+
"title": "%deepnote.commands.manageIntegrations.title%",
91+
"category": "Deepnote",
92+
"icon": "$(plug)"
93+
},
94+
{
95+
"command": "deepnote.newProject",
96+
"title": "New project",
97+
"category": "Deepnote",
98+
"icon": "$(new-file)"
99+
},
100+
{
101+
"command": "deepnote.importNotebook",
102+
"title": "Import notebook",
103+
"category": "Deepnote",
104+
"icon": "$(folder-opened)"
105+
},
106+
{
107+
"command": "deepnote.importJupyterNotebook",
108+
"title": "Import Jupyter notebook",
109+
"category": "Deepnote",
110+
"icon": "$(notebook)"
111+
},
88112
{
89113
"command": "dataScience.ClearCache",
90114
"title": "%jupyter.command.dataScience.clearCache.title%",
@@ -707,6 +731,11 @@
707731
}
708732
],
709733
"notebook/toolbar": [
734+
{
735+
"command": "deepnote.manageIntegrations",
736+
"group": "navigation@0",
737+
"when": "notebookType == 'deepnote'"
738+
},
710739
{
711740
"command": "jupyter.restartkernel",
712741
"group": "navigation/execute@5",
@@ -1851,14 +1880,19 @@
18511880
{
18521881
"id": "deepnoteExplorer",
18531882
"name": "%deepnote.views.explorer.name%",
1854-
"when": "workspaceFolderCount != 0",
18551883
"iconPath": {
18561884
"light": "./resources/light/deepnote-icon.svg",
18571885
"dark": "./resources/dark/deepnote-icon.svg"
18581886
}
18591887
}
18601888
]
18611889
},
1890+
"viewsWelcome": [
1891+
{
1892+
"view": "deepnoteExplorer",
1893+
"contents": "Welcome to Deepnote for VS Code!\nExplore your data with SQL and Python. Build interactive notebooks, collaborate with your team, and share your insights.\n\n\n\n[$(new-file) New Project](command:deepnote.newProject)\n[$(folder-opened) Import Notebook](command:deepnote.importNotebook)"
1894+
}
1895+
],
18621896
"debuggers": [
18631897
{
18641898
"type": "Python Kernel Debug Adapter",
@@ -2120,6 +2154,7 @@
21202154
"dependencies": {
21212155
"@c4312/evt": "^0.1.1",
21222156
"@deepnote/blocks": "^1.2.0",
2157+
"@deepnote/convert": "^1.1.0",
21232158
"@enonic/fnv-plus": "^1.3.0",
21242159
"@jupyter-widgets/base": "^6.0.8",
21252160
"@jupyter-widgets/controls": "^5.0.9",

package.nls.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,11 @@
249249
"deepnote.commands.openNotebook.title": "Open Notebook",
250250
"deepnote.commands.openFile.title": "Open File",
251251
"deepnote.commands.revealInExplorer.title": "Reveal in Explorer",
252+
"deepnote.commands.manageIntegrations.title": "Manage Integrations",
253+
"deepnote.commands.newProject.title": "New Project",
254+
"deepnote.commands.importNotebook.title": "Import Notebook",
255+
"deepnote.commands.importJupyterNotebook.title": "Import Jupyter Notebook",
252256
"deepnote.views.explorer.name": "Explorer",
257+
"deepnote.views.explorer.welcome": "No Deepnote notebooks found in this workspace.",
253258
"deepnote.command.selectNotebook.title": "Select Notebook"
254259
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { NotebookCellData, NotebookCellKind } from 'vscode';
2+
3+
import type { BlockConverter } from './blockConverter';
4+
import type { DeepnoteBlock } from '../deepnoteTypes';
5+
6+
/**
7+
* Converter for SQL blocks.
8+
*
9+
* SQL blocks are rendered as code cells with SQL language for proper syntax highlighting.
10+
* The SQL source code is stored in the cell content and displayed in the code editor.
11+
*
12+
* During execution, the createPythonCode function from @deepnote/blocks will generate
13+
* the appropriate Python code to execute the SQL query based on the block's metadata
14+
* (which includes the sql_integration_id and other SQL-specific settings).
15+
*/
16+
export class SqlBlockConverter implements BlockConverter {
17+
applyChangesToBlock(block: DeepnoteBlock, cell: NotebookCellData): void {
18+
// Store the SQL source code from the cell editor back to the block
19+
block.content = cell.value || '';
20+
}
21+
22+
canConvert(blockType: string): boolean {
23+
return blockType.toLowerCase() === 'sql';
24+
}
25+
26+
convertToCell(block: DeepnoteBlock): NotebookCellData {
27+
// Create a code cell with SQL language for syntax highlighting
28+
// The SQL source code is displayed in the editor
29+
const cell = new NotebookCellData(NotebookCellKind.Code, block.content || '', 'sql');
30+
31+
return cell;
32+
}
33+
34+
getSupportedTypes(): string[] {
35+
return ['sql'];
36+
}
37+
}
Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
import { assert } from 'chai';
2+
import { NotebookCellData, NotebookCellKind } from 'vscode';
3+
4+
import type { DeepnoteBlock } from '../deepnoteTypes';
5+
import { SqlBlockConverter } from './sqlBlockConverter';
6+
import dedent from 'dedent';
7+
8+
suite('SqlBlockConverter', () => {
9+
let converter: SqlBlockConverter;
10+
11+
setup(() => {
12+
converter = new SqlBlockConverter();
13+
});
14+
15+
suite('canConvert', () => {
16+
test('returns true for "sql" type', () => {
17+
assert.strictEqual(converter.canConvert('sql'), true);
18+
});
19+
20+
test('returns true for "SQL" type (case insensitive)', () => {
21+
assert.strictEqual(converter.canConvert('SQL'), true);
22+
});
23+
24+
test('returns false for other types', () => {
25+
assert.strictEqual(converter.canConvert('code'), false);
26+
assert.strictEqual(converter.canConvert('markdown'), false);
27+
assert.strictEqual(converter.canConvert('text-cell-h1'), false);
28+
});
29+
});
30+
31+
suite('getSupportedTypes', () => {
32+
test('returns array with "sql"', () => {
33+
const types = converter.getSupportedTypes();
34+
35+
assert.deepStrictEqual(types, ['sql']);
36+
});
37+
});
38+
39+
suite('convertToCell', () => {
40+
test('converts SQL block to code cell with sql language', () => {
41+
const block: DeepnoteBlock = {
42+
blockGroup: 'test-group',
43+
content: 'SELECT * FROM users WHERE age > 18',
44+
id: 'sql-block-123',
45+
sortingKey: 'a0',
46+
type: 'sql'
47+
};
48+
49+
const cell = converter.convertToCell(block);
50+
51+
assert.strictEqual(cell.kind, NotebookCellKind.Code);
52+
assert.strictEqual(cell.value, 'SELECT * FROM users WHERE age > 18');
53+
assert.strictEqual(cell.languageId, 'sql');
54+
});
55+
56+
test('handles empty SQL content', () => {
57+
const block: DeepnoteBlock = {
58+
blockGroup: 'test-group',
59+
content: '',
60+
id: 'sql-block-456',
61+
sortingKey: 'a1',
62+
type: 'sql'
63+
};
64+
65+
const cell = converter.convertToCell(block);
66+
67+
assert.strictEqual(cell.kind, NotebookCellKind.Code);
68+
assert.strictEqual(cell.value, '');
69+
assert.strictEqual(cell.languageId, 'sql');
70+
});
71+
72+
test('handles undefined SQL content', () => {
73+
const block: DeepnoteBlock = {
74+
blockGroup: 'test-group',
75+
id: 'sql-block-789',
76+
sortingKey: 'a2',
77+
type: 'sql'
78+
};
79+
80+
const cell = converter.convertToCell(block);
81+
82+
assert.strictEqual(cell.kind, NotebookCellKind.Code);
83+
assert.strictEqual(cell.value, '');
84+
assert.strictEqual(cell.languageId, 'sql');
85+
});
86+
87+
test('preserves multi-line SQL queries', () => {
88+
const sqlQuery = dedent`
89+
SELECT
90+
u.name,
91+
u.email,
92+
COUNT(o.id) as order_count
93+
FROM users u
94+
LEFT JOIN orders o ON u.id = o.user_id
95+
WHERE u.created_at > '2024-01-01'
96+
GROUP BY u.id, u.name, u.email
97+
ORDER BY order_count DESC
98+
LIMIT 10
99+
`;
100+
101+
const block: DeepnoteBlock = {
102+
blockGroup: 'test-group',
103+
content: sqlQuery,
104+
id: 'sql-block-complex',
105+
sortingKey: 'a3',
106+
type: 'sql'
107+
};
108+
109+
const cell = converter.convertToCell(block);
110+
111+
assert.strictEqual(cell.kind, NotebookCellKind.Code);
112+
assert.strictEqual(cell.value, sqlQuery);
113+
assert.strictEqual(cell.languageId, 'sql');
114+
});
115+
116+
test('preserves SQL block with metadata', () => {
117+
const block: DeepnoteBlock = {
118+
blockGroup: 'test-group',
119+
content: 'SELECT * FROM products',
120+
id: 'sql-block-with-metadata',
121+
metadata: {
122+
sql_integration_id: 'postgres-prod',
123+
table_state_spec: '{"pageSize": 50}'
124+
},
125+
sortingKey: 'a4',
126+
type: 'sql'
127+
};
128+
129+
const cell = converter.convertToCell(block);
130+
131+
assert.strictEqual(cell.kind, NotebookCellKind.Code);
132+
assert.strictEqual(cell.value, 'SELECT * FROM products');
133+
assert.strictEqual(cell.languageId, 'sql');
134+
// Metadata is handled by the data converter, not the block converter
135+
});
136+
});
137+
138+
suite('applyChangesToBlock', () => {
139+
test('updates block content from cell value', () => {
140+
const block: DeepnoteBlock = {
141+
blockGroup: 'test-group',
142+
content: 'SELECT * FROM old_table',
143+
id: 'sql-block-123',
144+
sortingKey: 'a0',
145+
type: 'sql'
146+
};
147+
const cell = new NotebookCellData(
148+
NotebookCellKind.Code,
149+
'SELECT * FROM new_table WHERE active = true',
150+
'sql'
151+
);
152+
153+
converter.applyChangesToBlock(block, cell);
154+
155+
assert.strictEqual(block.content, 'SELECT * FROM new_table WHERE active = true');
156+
});
157+
158+
test('handles empty cell value', () => {
159+
const block: DeepnoteBlock = {
160+
blockGroup: 'test-group',
161+
content: 'SELECT * FROM users',
162+
id: 'sql-block-456',
163+
sortingKey: 'a1',
164+
type: 'sql'
165+
};
166+
const cell = new NotebookCellData(NotebookCellKind.Code, '', 'sql');
167+
168+
converter.applyChangesToBlock(block, cell);
169+
170+
assert.strictEqual(block.content, '');
171+
});
172+
173+
test('does not modify other block properties', () => {
174+
const block: DeepnoteBlock = {
175+
blockGroup: 'test-group',
176+
content: 'SELECT * FROM old_query',
177+
id: 'sql-block-789',
178+
metadata: {
179+
sql_integration_id: 'postgres-prod',
180+
custom: 'value'
181+
},
182+
sortingKey: 'a2',
183+
type: 'sql'
184+
};
185+
const cell = new NotebookCellData(NotebookCellKind.Code, 'SELECT * FROM new_query', 'sql');
186+
187+
converter.applyChangesToBlock(block, cell);
188+
189+
assert.strictEqual(block.content, 'SELECT * FROM new_query');
190+
assert.strictEqual(block.id, 'sql-block-789');
191+
assert.strictEqual(block.type, 'sql');
192+
assert.strictEqual(block.sortingKey, 'a2');
193+
assert.deepStrictEqual(block.metadata, {
194+
sql_integration_id: 'postgres-prod',
195+
custom: 'value'
196+
});
197+
});
198+
});
199+
});

src/notebooks/deepnote/deepnoteActivationService.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { IExtensionContext } from '../../platform/common/types';
55
import { IDeepnoteNotebookManager } from '../types';
66
import { DeepnoteNotebookSerializer } from './deepnoteSerializer';
77
import { DeepnoteExplorerView } from './deepnoteExplorerView';
8+
import { IIntegrationManager } from './integrations/types';
89

910
/**
1011
* Service responsible for activating and configuring Deepnote notebook support in VS Code.
@@ -13,12 +14,18 @@ import { DeepnoteExplorerView } from './deepnoteExplorerView';
1314
@injectable()
1415
export class DeepnoteActivationService implements IExtensionSyncActivationService {
1516
private explorerView: DeepnoteExplorerView;
17+
18+
private integrationManager: IIntegrationManager;
19+
1620
private serializer: DeepnoteNotebookSerializer;
1721

1822
constructor(
1923
@inject(IExtensionContext) private extensionContext: IExtensionContext,
20-
@inject(IDeepnoteNotebookManager) private readonly notebookManager: IDeepnoteNotebookManager
21-
) {}
24+
@inject(IDeepnoteNotebookManager) private readonly notebookManager: IDeepnoteNotebookManager,
25+
@inject(IIntegrationManager) integrationManager: IIntegrationManager
26+
) {
27+
this.integrationManager = integrationManager;
28+
}
2229

2330
/**
2431
* Activates Deepnote support by registering serializers and commands.
@@ -31,5 +38,6 @@ export class DeepnoteActivationService implements IExtensionSyncActivationServic
3138
this.extensionContext.subscriptions.push(workspace.registerNotebookSerializer('deepnote', this.serializer));
3239

3340
this.explorerView.activate();
41+
this.integrationManager.activate();
3442
}
3543
}

0 commit comments

Comments
 (0)