Skip to content

Commit d57e3b7

Browse files
fix: resolve merge conflicts with main
- Restore integration-related files and imports from main - Accept main's new integration panel localization strings - Restore SqlCellStatusBarProvider registration - Restore missing integrationDetector.ts file
2 parents 3f9173b + 7271532 commit d57e3b7

21 files changed

+2152
-464
lines changed

package-lock.json

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

src/messageTypes.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,52 @@ export type LocalizedMessages = {
158158
selectedImageListLabel: string;
159159
selectedImageLabel: string;
160160
dvDeprecationWarning: string;
161+
dataframeRowsColumns: string;
162+
dataframePerPage: string;
163+
dataframePreviousPage: string;
164+
dataframeNextPage: string;
165+
dataframePageOf: string;
166+
dataframeCopyTable: string;
167+
dataframeExportTable: string;
168+
// Integration panel strings
169+
integrationsTitle: string;
170+
integrationsNoIntegrationsFound: string;
171+
integrationsConnected: string;
172+
integrationsNotConfigured: string;
173+
integrationsConfigure: string;
174+
integrationsReconfigure: string;
175+
integrationsReset: string;
176+
integrationsConfirmResetTitle: string;
177+
integrationsConfirmResetMessage: string;
178+
integrationsConfirmResetDetails: string;
179+
integrationsConfigureTitle: string;
180+
integrationsCancel: string;
181+
integrationsSave: string;
182+
// PostgreSQL form strings
183+
integrationsPostgresNameLabel: string;
184+
integrationsPostgresNamePlaceholder: string;
185+
integrationsPostgresHostLabel: string;
186+
integrationsPostgresHostPlaceholder: string;
187+
integrationsPostgresPortLabel: string;
188+
integrationsPostgresPortPlaceholder: string;
189+
integrationsPostgresDatabaseLabel: string;
190+
integrationsPostgresDatabasePlaceholder: string;
191+
integrationsPostgresUsernameLabel: string;
192+
integrationsPostgresUsernamePlaceholder: string;
193+
integrationsPostgresPasswordLabel: string;
194+
integrationsPostgresPasswordPlaceholder: string;
195+
integrationsPostgresSslLabel: string;
196+
// BigQuery form strings
197+
integrationsBigQueryNameLabel: string;
198+
integrationsBigQueryNamePlaceholder: string;
199+
integrationsBigQueryProjectIdLabel: string;
200+
integrationsBigQueryProjectIdPlaceholder: string;
201+
integrationsBigQueryCredentialsLabel: string;
202+
integrationsBigQueryCredentialsPlaceholder: string;
203+
integrationsBigQueryCredentialsRequired: string;
204+
// Common form strings
205+
integrationsRequiredField: string;
206+
integrationsOptionalField: string;
161207
};
162208
// Map all messages to specific payloads
163209
export class IInteractiveWindowMapping {
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import { inject, injectable } from 'inversify';
2+
3+
import { logger } from '../../../platform/logging';
4+
import { IDeepnoteNotebookManager } from '../../types';
5+
import { IntegrationStatus, IntegrationWithStatus } from './integrationTypes';
6+
import { IIntegrationDetector, IIntegrationStorage } from './types';
7+
import { BlockWithIntegration, scanBlocksForIntegrations } from './integrationUtils';
8+
9+
/**
10+
* Service for detecting integrations used in Deepnote notebooks
11+
*/
12+
@injectable()
13+
export class IntegrationDetector implements IIntegrationDetector {
14+
constructor(
15+
@inject(IIntegrationStorage) private readonly integrationStorage: IIntegrationStorage,
16+
@inject(IDeepnoteNotebookManager) private readonly notebookManager: IDeepnoteNotebookManager
17+
) {}
18+
19+
/**
20+
* Detect all integrations used in the given project
21+
*/
22+
async detectIntegrations(projectId: string): Promise<Map<string, IntegrationWithStatus>> {
23+
// Get the project
24+
const project = this.notebookManager.getOriginalProject(projectId);
25+
if (!project) {
26+
logger.warn(
27+
`IntegrationDetector: No project found for ID: ${projectId}. The project may not have been loaded yet.`
28+
);
29+
return new Map();
30+
}
31+
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+
}
54+
}
55+
}
56+
57+
// Use the shared utility to scan blocks and build the status map
58+
return scanBlocksForIntegrations(blocksWithIntegrations, this.integrationStorage, 'IntegrationDetector');
59+
}
60+
61+
/**
62+
* Check if a project has any unconfigured integrations
63+
*/
64+
async hasUnconfiguredIntegrations(projectId: string): Promise<boolean> {
65+
const integrations = await this.detectIntegrations(projectId);
66+
67+
for (const integration of integrations.values()) {
68+
if (integration.status === IntegrationStatus.Disconnected) {
69+
return true;
70+
}
71+
}
72+
73+
return false;
74+
}
75+
}
Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
import { inject, injectable } from 'inversify';
2+
import { commands, l10n, NotebookDocument, window, workspace } from 'vscode';
3+
4+
import { IExtensionContext } from '../../../platform/common/types';
5+
import { Commands } from '../../../platform/common/constants';
6+
import { logger } from '../../../platform/logging';
7+
import { IIntegrationDetector, IIntegrationManager, IIntegrationStorage, IIntegrationWebviewProvider } from './types';
8+
import { IntegrationStatus, IntegrationWithStatus } from './integrationTypes';
9+
import { BlockWithIntegration, scanBlocksForIntegrations } from './integrationUtils';
10+
11+
/**
12+
* Manages integration UI and commands for Deepnote notebooks
13+
*/
14+
@injectable()
15+
export class IntegrationManager implements IIntegrationManager {
16+
private hasIntegrationsContext = 'deepnote.hasIntegrations';
17+
18+
private hasUnconfiguredIntegrationsContext = 'deepnote.hasUnconfiguredIntegrations';
19+
20+
constructor(
21+
@inject(IExtensionContext) private readonly extensionContext: IExtensionContext,
22+
@inject(IIntegrationDetector) private readonly integrationDetector: IIntegrationDetector,
23+
@inject(IIntegrationStorage) private readonly integrationStorage: IIntegrationStorage,
24+
@inject(IIntegrationWebviewProvider) private readonly webviewProvider: IIntegrationWebviewProvider
25+
) {}
26+
27+
public activate(): void {
28+
// Register the manage integrations command
29+
// The command can optionally receive an integration ID to select/configure
30+
// Note: When invoked from a notebook cell status bar, VSCode passes context object first,
31+
// then the actual arguments from the command definition
32+
this.extensionContext.subscriptions.push(
33+
commands.registerCommand(Commands.ManageIntegrations, (...args: unknown[]) => {
34+
logger.debug(`IntegrationManager: Command invoked with args:`, args);
35+
36+
// Find the integration ID from the arguments
37+
// It could be the first arg (if called directly) or in the args array (if called from UI)
38+
let integrationId: string | undefined;
39+
40+
for (const arg of args) {
41+
if (typeof arg === 'string') {
42+
integrationId = arg;
43+
break;
44+
}
45+
}
46+
47+
logger.debug(`IntegrationManager: Extracted integrationId: ${integrationId}`);
48+
return this.showIntegrationsUI(integrationId);
49+
})
50+
);
51+
52+
// Listen for active notebook changes to update context
53+
this.extensionContext.subscriptions.push(
54+
window.onDidChangeActiveNotebookEditor(() =>
55+
this.updateContext().catch((err) =>
56+
logger.error('IntegrationManager: Failed to update context on notebook editor change', err)
57+
)
58+
)
59+
);
60+
61+
// Listen for notebook document changes
62+
this.extensionContext.subscriptions.push(
63+
workspace.onDidOpenNotebookDocument(() =>
64+
this.updateContext().catch((err) =>
65+
logger.error('IntegrationManager: Failed to update context on notebook open', err)
66+
)
67+
)
68+
);
69+
70+
this.extensionContext.subscriptions.push(
71+
workspace.onDidCloseNotebookDocument(() =>
72+
this.updateContext().catch((err) =>
73+
logger.error('IntegrationManager: Failed to update context on notebook close', err)
74+
)
75+
)
76+
);
77+
78+
// Initial context update
79+
this.updateContext().catch((err) =>
80+
logger.error('IntegrationManager: Failed to update context on activation', err)
81+
);
82+
}
83+
84+
/**
85+
* Update the context keys based on the active notebook
86+
*/
87+
private async updateContext(): Promise<void> {
88+
const activeNotebook = window.activeNotebookEditor?.notebook;
89+
90+
if (!activeNotebook || activeNotebook.notebookType !== 'deepnote') {
91+
await commands.executeCommand('setContext', this.hasIntegrationsContext, false);
92+
await commands.executeCommand('setContext', this.hasUnconfiguredIntegrationsContext, false);
93+
return;
94+
}
95+
96+
// Get the project ID from the notebook metadata
97+
const projectId = activeNotebook.metadata?.deepnoteProjectId;
98+
if (!projectId) {
99+
await commands.executeCommand('setContext', this.hasIntegrationsContext, false);
100+
await commands.executeCommand('setContext', this.hasUnconfiguredIntegrationsContext, false);
101+
return;
102+
}
103+
104+
// Detect integrations in the project
105+
const integrations = await this.integrationDetector.detectIntegrations(projectId);
106+
const hasIntegrations = integrations.size > 0;
107+
const hasUnconfigured = Array.from(integrations.values()).some(
108+
(integration) => integration.status === IntegrationStatus.Disconnected
109+
);
110+
111+
await commands.executeCommand('setContext', this.hasIntegrationsContext, hasIntegrations);
112+
await commands.executeCommand('setContext', this.hasUnconfiguredIntegrationsContext, hasUnconfigured);
113+
}
114+
115+
/**
116+
* Show the integrations management UI
117+
* @param selectedIntegrationId Optional integration ID to select/configure immediately
118+
*/
119+
private async showIntegrationsUI(selectedIntegrationId?: string): Promise<void> {
120+
const activeNotebook = window.activeNotebookEditor?.notebook;
121+
122+
if (!activeNotebook || activeNotebook.notebookType !== 'deepnote') {
123+
void window.showErrorMessage(l10n.t('No active Deepnote notebook'));
124+
return;
125+
}
126+
127+
const projectId = activeNotebook.metadata?.deepnoteProjectId;
128+
if (!projectId) {
129+
void window.showErrorMessage(l10n.t('Cannot determine project ID'));
130+
return;
131+
}
132+
133+
logger.debug(`IntegrationManager: Project ID: ${projectId}`);
134+
logger.trace(`IntegrationManager: Notebook metadata:`, activeNotebook.metadata);
135+
136+
// First try to detect integrations from the stored project
137+
let integrations = await this.integrationDetector.detectIntegrations(projectId);
138+
139+
// If no integrations found in stored project, scan cells directly
140+
// This handles the case where the notebook was already open when the extension loaded
141+
if (integrations.size === 0) {
142+
logger.debug(`IntegrationManager: No integrations found in stored project, scanning cells directly`);
143+
integrations = await this.detectIntegrationsFromCells(activeNotebook);
144+
}
145+
146+
logger.debug(`IntegrationManager: Found ${integrations.size} integrations`);
147+
148+
// If a specific integration was requested (e.g., from status bar click),
149+
// ensure it's in the map even if not detected from the project
150+
if (selectedIntegrationId && !integrations.has(selectedIntegrationId)) {
151+
logger.debug(`IntegrationManager: Adding requested integration ${selectedIntegrationId} to the map`);
152+
const config = await this.integrationStorage.get(selectedIntegrationId);
153+
integrations.set(selectedIntegrationId, {
154+
config: config || null,
155+
status: config ? IntegrationStatus.Connected : IntegrationStatus.Disconnected
156+
});
157+
}
158+
159+
if (integrations.size === 0) {
160+
void window.showInformationMessage(l10n.t('No integrations found in this project.'));
161+
return;
162+
}
163+
164+
// Show the webview with optional selected integration
165+
await this.webviewProvider.show(integrations, selectedIntegrationId);
166+
}
167+
168+
/**
169+
* Detect integrations by scanning cells directly (fallback method)
170+
* This is used when the project isn't stored in the notebook manager
171+
*/
172+
private async detectIntegrationsFromCells(notebook: NotebookDocument): Promise<Map<string, IntegrationWithStatus>> {
173+
// Collect all cells with SQL integration metadata
174+
const blocksWithIntegrations: BlockWithIntegration[] = [];
175+
176+
for (const cell of notebook.getCells()) {
177+
const metadata = cell.metadata;
178+
logger.trace(`IntegrationManager: Cell ${cell.index} metadata:`, metadata);
179+
180+
// Check cell metadata for sql_integration_id
181+
if (metadata && typeof metadata === 'object') {
182+
const integrationId = (metadata as Record<string, unknown>).sql_integration_id;
183+
if (typeof integrationId === 'string') {
184+
logger.debug(`IntegrationManager: Found integration ${integrationId} in cell ${cell.index}`);
185+
blocksWithIntegrations.push({
186+
id: `cell-${cell.index}`,
187+
sql_integration_id: integrationId
188+
});
189+
}
190+
}
191+
}
192+
193+
logger.debug(`IntegrationManager: Found ${blocksWithIntegrations.length} cells with integrations`);
194+
195+
// Use the shared utility to scan blocks and build the status map
196+
return scanBlocksForIntegrations(blocksWithIntegrations, this.integrationStorage, 'IntegrationManager');
197+
}
198+
}

0 commit comments

Comments
 (0)