Skip to content

Commit 59b90a9

Browse files
committed
feat: replace integration listing from blocks with integration list from project
1 parent 8597009 commit 59b90a9

File tree

14 files changed

+364
-105
lines changed

14 files changed

+364
-105
lines changed

src/notebooks/deepnote/deepnoteNotebookManager.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,32 @@ export class DeepnoteNotebookManager implements IDeepnoteNotebookManager {
7575
this.currentNotebookId.set(projectId, notebookId);
7676
}
7777

78+
/**
79+
* Updates the integrations list in the project data.
80+
* This modifies the stored project to reflect changes in configured integrations.
81+
* @param projectId Project identifier
82+
* @param integrations Array of integration metadata to store in the project
83+
*/
84+
updateProjectIntegrations(
85+
projectId: string,
86+
integrations: Array<{ id: string; name: string; type: string }>
87+
): void {
88+
const project = this.originalProjects.get(projectId);
89+
90+
if (!project) {
91+
return;
92+
}
93+
94+
const updatedProject = JSON.parse(JSON.stringify(project)) as DeepnoteProject;
95+
updatedProject.project.integrations = integrations;
96+
97+
const currentNotebookId = this.currentNotebookId.get(projectId);
98+
99+
if (currentNotebookId) {
100+
this.storeOriginalProject(projectId, updatedProject, currentNotebookId);
101+
}
102+
}
103+
78104
/**
79105
* Checks if the init notebook has already been run for a project.
80106
* @param projectId Project identifier

src/notebooks/deepnote/integrations/integrationDetector.ts

Lines changed: 43 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,13 @@ import { inject, injectable } from 'inversify';
22

33
import { logger } from '../../../platform/logging';
44
import { IDeepnoteNotebookManager } from '../../types';
5-
import { IntegrationStatus, IntegrationWithStatus } from '../../../platform/notebooks/deepnote/integrationTypes';
5+
import {
6+
DATAFRAME_SQL_INTEGRATION_ID,
7+
IntegrationStatus,
8+
IntegrationWithStatus,
9+
mapDeepnoteIntegrationType
10+
} from '../../../platform/notebooks/deepnote/integrationTypes';
611
import { IIntegrationDetector, IIntegrationStorage } from './types';
7-
import { BlockWithIntegration, scanBlocksForIntegrations } from './integrationUtils';
812

913
/**
1014
* Service for detecting integrations used in Deepnote notebooks
@@ -17,7 +21,8 @@ export class IntegrationDetector implements IIntegrationDetector {
1721
) {}
1822

1923
/**
20-
* Detect all integrations used in the given project
24+
* Detect all integrations used in the given project.
25+
* Uses the project's integrations field as the source of truth.
2126
*/
2227
async detectIntegrations(projectId: string): Promise<Map<string, IntegrationWithStatus>> {
2328
// Get the project
@@ -29,33 +34,44 @@ export class IntegrationDetector implements IIntegrationDetector {
2934
return new Map();
3035
}
3136

32-
logger.debug(
33-
`IntegrationDetector: Scanning project ${projectId} with ${project.project.notebooks.length} notebooks`
34-
);
35-
36-
// Collect all blocks with SQL integration metadata from all notebooks
37-
const blocksWithIntegrations: BlockWithIntegration[] = [];
38-
for (const notebook of project.project.notebooks) {
39-
logger.trace(`IntegrationDetector: Scanning notebook ${notebook.id} with ${notebook.blocks.length} blocks`);
40-
41-
for (const block of notebook.blocks) {
42-
// Check if this is a code block with SQL integration metadata
43-
if (block.type === 'code' && block.metadata?.sql_integration_id) {
44-
blocksWithIntegrations.push({
45-
id: block.id,
46-
sql_integration_id: block.metadata.sql_integration_id
47-
});
48-
} else if (block.type === 'code') {
49-
logger.trace(
50-
`IntegrationDetector: Block ${block.id} has no sql_integration_id. Metadata:`,
51-
block.metadata
52-
);
53-
}
37+
logger.debug(`IntegrationDetector: Scanning project ${projectId} for integrations`);
38+
39+
const integrations = new Map<string, IntegrationWithStatus>();
40+
41+
// Use the project's integrations field as the source of truth
42+
const projectIntegrations = project.project.integrations || [];
43+
logger.debug(`IntegrationDetector: Found ${projectIntegrations.length} integrations in project.integrations`);
44+
45+
for (const projectIntegration of projectIntegrations) {
46+
const integrationId = projectIntegration.id;
47+
48+
// Skip the internal DuckDB integration
49+
if (integrationId === DATAFRAME_SQL_INTEGRATION_ID) {
50+
continue;
5451
}
52+
53+
logger.debug(`IntegrationDetector: Found integration: ${integrationId} (${projectIntegration.type})`);
54+
55+
// Check if the integration is configured
56+
const config = await this.integrationStorage.getIntegrationConfig(integrationId);
57+
58+
// Map the Deepnote integration type to our IntegrationType
59+
const integrationType = mapDeepnoteIntegrationType(projectIntegration.type);
60+
61+
const status: IntegrationWithStatus = {
62+
config: config || null,
63+
status: config ? IntegrationStatus.Connected : IntegrationStatus.Disconnected,
64+
// Include project metadata for prefilling when config is null
65+
projectName: projectIntegration.name,
66+
projectType: integrationType
67+
};
68+
69+
integrations.set(integrationId, status);
5570
}
5671

57-
// Use the shared utility to scan blocks and build the status map
58-
return scanBlocksForIntegrations(blocksWithIntegrations, this.integrationStorage, 'IntegrationDetector');
72+
logger.debug(`IntegrationDetector: Found ${integrations.size} integrations`);
73+
74+
return integrations;
5975
}
6076

6177
/**

src/notebooks/deepnote/integrations/integrationManager.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ export class IntegrationManager implements IIntegrationManager {
162162
}
163163

164164
// Show the webview with optional selected integration
165-
await this.webviewProvider.show(integrations, selectedIntegrationId);
165+
await this.webviewProvider.show(projectId, integrations, selectedIntegrationId);
166166
}
167167

168168
/**

src/notebooks/deepnote/integrations/integrationWebview.ts

Lines changed: 67 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@ import { IExtensionContext } from '../../../platform/common/types';
55
import * as localize from '../../../platform/common/utils/localize';
66
import { logger } from '../../../platform/logging';
77
import { LocalizedMessages, SharedMessages } from '../../../messageTypes';
8+
import { IDeepnoteNotebookManager } from '../../types';
89
import { IIntegrationStorage, IIntegrationWebviewProvider } from './types';
910
import {
1011
IntegrationConfig,
1112
IntegrationStatus,
12-
IntegrationWithStatus
13+
IntegrationWithStatus,
14+
mapToDeepnoteIntegrationType
1315
} from '../../../platform/notebooks/deepnote/integrationTypes';
1416

1517
/**
@@ -23,18 +25,27 @@ export class IntegrationWebviewProvider implements IIntegrationWebviewProvider {
2325

2426
private integrations: Map<string, IntegrationWithStatus> = new Map();
2527

28+
private projectId: string | undefined;
29+
2630
constructor(
2731
@inject(IExtensionContext) private readonly extensionContext: IExtensionContext,
28-
@inject(IIntegrationStorage) private readonly integrationStorage: IIntegrationStorage
32+
@inject(IIntegrationStorage) private readonly integrationStorage: IIntegrationStorage,
33+
@inject(IDeepnoteNotebookManager) private readonly notebookManager: IDeepnoteNotebookManager
2934
) {}
3035

3136
/**
3237
* Show the integration management webview
38+
* @param projectId The Deepnote project ID
3339
* @param integrations Map of integration IDs to their status
3440
* @param selectedIntegrationId Optional integration ID to select/configure immediately
3541
*/
36-
public async show(integrations: Map<string, IntegrationWithStatus>, selectedIntegrationId?: string): Promise<void> {
37-
// Update the stored integrations with the latest data
42+
public async show(
43+
projectId: string,
44+
integrations: Map<string, IntegrationWithStatus>,
45+
selectedIntegrationId?: string
46+
): Promise<void> {
47+
// Update the stored integrations and project ID with the latest data
48+
this.projectId = projectId;
3849
this.integrations = integrations;
3950

4051
const column = window.activeTextEditor ? window.activeTextEditor.viewColumn : ViewColumn.One;
@@ -161,6 +172,8 @@ export class IntegrationWebviewProvider implements IIntegrationWebviewProvider {
161172
const integrationsData = Array.from(this.integrations.entries()).map(([id, integration]) => ({
162173
config: integration.config,
163174
id,
175+
projectName: integration.projectName,
176+
projectType: integration.projectType,
164177
status: integration.status
165178
}));
166179
logger.debug(`IntegrationWebviewProvider: Sending ${integrationsData.length} integrations to webview`);
@@ -210,6 +223,8 @@ export class IntegrationWebviewProvider implements IIntegrationWebviewProvider {
210223
await this.currentPanel?.webview.postMessage({
211224
config: integration.config,
212225
integrationId,
226+
projectName: integration.projectName,
227+
projectType: integration.projectType,
213228
type: 'showForm'
214229
});
215230
}
@@ -229,6 +244,9 @@ export class IntegrationWebviewProvider implements IIntegrationWebviewProvider {
229244
this.integrations.set(integrationId, integration);
230245
}
231246

247+
// Update the project's integrations list
248+
await this.updateProjectIntegrationsList();
249+
232250
await this.updateWebview();
233251
await this.currentPanel?.webview.postMessage({
234252
message: l10n.t('Configuration saved successfully'),
@@ -261,6 +279,9 @@ export class IntegrationWebviewProvider implements IIntegrationWebviewProvider {
261279
this.integrations.set(integrationId, integration);
262280
}
263281

282+
// Update the project's integrations list
283+
await this.updateProjectIntegrationsList();
284+
264285
await this.updateWebview();
265286
await this.currentPanel?.webview.postMessage({
266287
message: l10n.t('Configuration deleted successfully'),
@@ -278,6 +299,48 @@ export class IntegrationWebviewProvider implements IIntegrationWebviewProvider {
278299
}
279300
}
280301

302+
/**
303+
* Update the project's integrations list based on current integrations
304+
*/
305+
private async updateProjectIntegrationsList(): Promise<void> {
306+
if (!this.projectId) {
307+
logger.warn('IntegrationWebviewProvider: No project ID available, skipping project update');
308+
return;
309+
}
310+
311+
// Build the integrations list from current integrations
312+
const projectIntegrations = Array.from(this.integrations.entries())
313+
.map(([id, integration]) => {
314+
// Get the integration type from config or project metadata
315+
const type = integration.config?.type || integration.projectType;
316+
if (!type) {
317+
logger.warn(`IntegrationWebviewProvider: No type found for integration ${id}, skipping`);
318+
return null;
319+
}
320+
321+
// Map to Deepnote integration type
322+
const deepnoteType = mapToDeepnoteIntegrationType(type);
323+
if (!deepnoteType) {
324+
logger.warn(`IntegrationWebviewProvider: Cannot map type ${type} for integration ${id}, skipping`);
325+
return null;
326+
}
327+
328+
return {
329+
id,
330+
name: integration.config?.name || integration.projectName || id,
331+
type: deepnoteType
332+
};
333+
})
334+
.filter((integration): integration is { id: string; name: string; type: string } => integration !== null);
335+
336+
logger.debug(
337+
`IntegrationWebviewProvider: Updating project ${this.projectId} with ${projectIntegrations.length} integrations`
338+
);
339+
340+
// Update the project in the notebook manager
341+
this.notebookManager.updateProjectIntegrations(this.projectId, projectIntegrations);
342+
}
343+
281344
/**
282345
* Get the HTML content for the webview (React-based)
283346
*/

src/notebooks/deepnote/integrations/types.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,15 @@ export const IIntegrationWebviewProvider = Symbol('IIntegrationWebviewProvider')
2020
export interface IIntegrationWebviewProvider {
2121
/**
2222
* Show the integration management webview
23+
* @param projectId The Deepnote project ID
2324
* @param integrations Map of integration IDs to their status
2425
* @param selectedIntegrationId Optional integration ID to select/configure immediately
2526
*/
26-
show(integrations: Map<string, IntegrationWithStatus>, selectedIntegrationId?: string): Promise<void>;
27+
show(
28+
projectId: string,
29+
integrations: Map<string, IntegrationWithStatus>,
30+
selectedIntegrationId?: string
31+
): Promise<void>;
2732
}
2833

2934
export const IIntegrationManager = Symbol('IIntegrationManager');

src/notebooks/deepnote/sqlCellStatusBarProvider.ts

Lines changed: 39 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,12 @@ import { IExtensionSyncActivationService } from '../../platform/activation/types
2222
import { IDisposableRegistry } from '../../platform/common/types';
2323
import { IIntegrationStorage } from './integrations/types';
2424
import { Commands } from '../../platform/common/constants';
25-
import { DATAFRAME_SQL_INTEGRATION_ID, IntegrationType } from '../../platform/notebooks/deepnote/integrationTypes';
25+
import {
26+
DATAFRAME_SQL_INTEGRATION_ID,
27+
IntegrationType,
28+
mapDeepnoteIntegrationType
29+
} from '../../platform/notebooks/deepnote/integrationTypes';
30+
import { IDeepnoteNotebookManager } from '../types';
2631

2732
/**
2833
* QuickPick item with an integration ID
@@ -42,7 +47,8 @@ export class SqlCellStatusBarProvider implements NotebookCellStatusBarItemProvid
4247

4348
constructor(
4449
@inject(IDisposableRegistry) private readonly disposables: IDisposableRegistry,
45-
@inject(IIntegrationStorage) private readonly integrationStorage: IIntegrationStorage
50+
@inject(IIntegrationStorage) private readonly integrationStorage: IIntegrationStorage,
51+
@inject(IDeepnoteNotebookManager) private readonly notebookManager: IDeepnoteNotebookManager
4652
) {}
4753

4854
public activate(): void {
@@ -285,17 +291,30 @@ export class SqlCellStatusBarProvider implements NotebookCellStatusBarItemProvid
285291
private async switchIntegration(cell: NotebookCell): Promise<void> {
286292
const currentIntegrationId = this.getIntegrationId(cell);
287293

288-
// Get all available integrations
289-
const allIntegrations = await this.integrationStorage.getAll();
294+
// Get the project ID from the notebook metadata
295+
const projectId = cell.notebook.metadata?.deepnoteProjectId;
296+
if (!projectId) {
297+
void window.showErrorMessage(l10n.t('Cannot determine project ID'));
298+
return;
299+
}
300+
301+
// Get the project to access its integrations list
302+
const project = this.notebookManager.getOriginalProject(projectId);
303+
if (!project) {
304+
void window.showErrorMessage(l10n.t('Project not found'));
305+
return;
306+
}
290307

291-
// Build quick pick items
308+
// Build quick pick items from project integrations
292309
const items: (QuickPickItem | LocalQuickPickItem)[] = [];
293310

294-
// Check if current integration is unknown (not in the list)
311+
const projectIntegrations = project.project.integrations || [];
312+
313+
// Check if current integration is unknown (not in the project's list)
295314
const isCurrentIntegrationUnknown =
296315
currentIntegrationId &&
297316
currentIntegrationId !== DATAFRAME_SQL_INTEGRATION_ID &&
298-
!allIntegrations.some((i) => i.id === currentIntegrationId);
317+
!projectIntegrations.some((i) => i.id === currentIntegrationId);
299318

300319
// Add current unknown integration first if it exists
301320
if (isCurrentIntegrationUnknown && currentIntegrationId) {
@@ -308,15 +327,21 @@ export class SqlCellStatusBarProvider implements NotebookCellStatusBarItemProvid
308327
items.push(item);
309328
}
310329

311-
// Add all configured integrations
312-
for (const integration of allIntegrations) {
313-
const typeLabel = this.getIntegrationTypeLabel(integration.type);
330+
// Add all project integrations
331+
for (const projectIntegration of projectIntegrations) {
332+
// Skip the internal DuckDB integration
333+
if (projectIntegration.id === DATAFRAME_SQL_INTEGRATION_ID) {
334+
continue;
335+
}
336+
337+
const integrationType = mapDeepnoteIntegrationType(projectIntegration.type);
338+
const typeLabel = integrationType ? this.getIntegrationTypeLabel(integrationType) : projectIntegration.type;
339+
314340
const item: LocalQuickPickItem = {
315-
label: integration.name || integration.id,
341+
label: projectIntegration.name || projectIntegration.id,
316342
description: typeLabel,
317-
detail: integration.id === currentIntegrationId ? l10n.t('Currently selected') : undefined,
318-
// Store the integration ID in a custom property
319-
id: integration.id
343+
detail: projectIntegration.id === currentIntegrationId ? l10n.t('Currently selected') : undefined,
344+
id: projectIntegration.id
320345
};
321346
items.push(item);
322347
}

0 commit comments

Comments
 (0)