Skip to content

Commit a3007ac

Browse files
committed
Merge remote-tracking branch 'origin/main' into tomaskislan/grn-4762-support-big-number-blocks
2 parents 6649ca8 + 0ac510e commit a3007ac

31 files changed

+2160
-15
lines changed

build/esbuild/build.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,11 @@ async function buildAll() {
355355
path.join(extensionFolder, 'src', 'webviews', 'webview-side', 'data-explorer', 'index.tsx'),
356356
path.join(extensionFolder, 'dist', 'webviews', 'webview-side', 'viewers', 'dataExplorer.js'),
357357
{ target: 'web', watch: watchAll }
358+
),
359+
build(
360+
path.join(extensionFolder, 'src', 'webviews', 'webview-side', 'integrations', 'index.tsx'),
361+
path.join(extensionFolder, 'dist', 'webviews', 'webview-side', 'integrations', 'index.js'),
362+
{ target: 'web', watch: watchAll }
358363
)
359364
);
360365

package.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,12 @@
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+
},
8894
{
8995
"command": "dataScience.ClearCache",
9096
"title": "%jupyter.command.dataScience.clearCache.title%",
@@ -707,6 +713,11 @@
707713
}
708714
],
709715
"notebook/toolbar": [
716+
{
717+
"command": "deepnote.manageIntegrations",
718+
"group": "navigation@0",
719+
"when": "notebookType == 'deepnote'"
720+
},
710721
{
711722
"command": "jupyter.restartkernel",
712723
"group": "navigation/execute@5",

package.nls.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,7 @@
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",
252253
"deepnote.views.explorer.name": "Explorer",
253254
"deepnote.command.selectNotebook.title": "Select Notebook"
254255
}
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 '../../../platform/deepnote/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 '../../../platform/deepnote/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
}

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

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,26 @@ import { assert } from 'chai';
33
import { DeepnoteActivationService } from './deepnoteActivationService';
44
import { DeepnoteNotebookManager } from './deepnoteNotebookManager';
55
import { IExtensionContext } from '../../platform/common/types';
6+
import { IIntegrationManager } from './integrations/types';
67

78
suite('DeepnoteActivationService', () => {
89
let activationService: DeepnoteActivationService;
910
let mockExtensionContext: IExtensionContext;
1011
let manager: DeepnoteNotebookManager;
12+
let mockIntegrationManager: IIntegrationManager;
1113

1214
setup(() => {
1315
mockExtensionContext = {
1416
subscriptions: []
1517
} as any;
1618

1719
manager = new DeepnoteNotebookManager();
18-
activationService = new DeepnoteActivationService(mockExtensionContext, manager);
20+
mockIntegrationManager = {
21+
activate: () => {
22+
return;
23+
}
24+
};
25+
activationService = new DeepnoteActivationService(mockExtensionContext, manager, mockIntegrationManager);
1926
});
2027

2128
suite('constructor', () => {
@@ -75,8 +82,18 @@ suite('DeepnoteActivationService', () => {
7582

7683
const manager1 = new DeepnoteNotebookManager();
7784
const manager2 = new DeepnoteNotebookManager();
78-
const service1 = new DeepnoteActivationService(context1, manager1);
79-
const service2 = new DeepnoteActivationService(context2, manager2);
85+
const mockIntegrationManager1: IIntegrationManager = {
86+
activate: () => {
87+
return;
88+
}
89+
};
90+
const mockIntegrationManager2: IIntegrationManager = {
91+
activate: () => {
92+
return;
93+
}
94+
};
95+
const service1 = new DeepnoteActivationService(context1, manager1, mockIntegrationManager1);
96+
const service2 = new DeepnoteActivationService(context2, manager2, mockIntegrationManager2);
8097

8198
// Verify each service has its own context
8299
assert.strictEqual((service1 as any).extensionContext, context1);
@@ -101,8 +118,18 @@ suite('DeepnoteActivationService', () => {
101118

102119
const manager1 = new DeepnoteNotebookManager();
103120
const manager2 = new DeepnoteNotebookManager();
104-
new DeepnoteActivationService(context1, manager1);
105-
new DeepnoteActivationService(context2, manager2);
121+
const mockIntegrationManager1: IIntegrationManager = {
122+
activate: () => {
123+
return;
124+
}
125+
};
126+
const mockIntegrationManager2: IIntegrationManager = {
127+
activate: () => {
128+
return;
129+
}
130+
};
131+
new DeepnoteActivationService(context1, manager1, mockIntegrationManager1);
132+
new DeepnoteActivationService(context2, manager2, mockIntegrationManager2);
106133

107134
assert.strictEqual(context1.subscriptions.length, 0);
108135
assert.strictEqual(context2.subscriptions.length, 1);

0 commit comments

Comments
 (0)