Skip to content

Commit d83956a

Browse files
committed
feat: add integration link to each SQL block
1 parent 35cc511 commit d83956a

File tree

8 files changed

+319
-6
lines changed

8 files changed

+319
-6
lines changed

src/notebooks/deepnote/integrations/integrationManager.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,11 @@ export class IntegrationManager implements IIntegrationManager {
2626

2727
public activate(): void {
2828
// Register the manage integrations command
29+
// The command can optionally receive an integration ID to select/configure
2930
this.extensionContext.subscriptions.push(
30-
commands.registerCommand(Commands.ManageIntegrations, () => this.showIntegrationsUI())
31+
commands.registerCommand(Commands.ManageIntegrations, (integrationId?: string) =>
32+
this.showIntegrationsUI(integrationId)
33+
)
3134
);
3235

3336
// Listen for active notebook changes to update context
@@ -95,8 +98,9 @@ export class IntegrationManager implements IIntegrationManager {
9598

9699
/**
97100
* Show the integrations management UI
101+
* @param selectedIntegrationId Optional integration ID to select/configure immediately
98102
*/
99-
private async showIntegrationsUI(): Promise<void> {
103+
private async showIntegrationsUI(selectedIntegrationId?: string): Promise<void> {
100104
const activeNotebook = window.activeNotebookEditor?.notebook;
101105

102106
if (!activeNotebook || activeNotebook.notebookType !== 'deepnote') {
@@ -130,8 +134,8 @@ export class IntegrationManager implements IIntegrationManager {
130134
return;
131135
}
132136

133-
// Show the webview
134-
await this.webviewProvider.show(integrations);
137+
// Show the webview with optional selected integration
138+
await this.webviewProvider.show(integrations, selectedIntegrationId);
135139
}
136140

137141
/**

src/notebooks/deepnote/integrations/integrationStorage.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { inject, injectable } from 'inversify';
2+
import { EventEmitter } from 'vscode';
23

34
import { IEncryptedStorage } from '../../../platform/common/application/types';
45
import { logger } from '../../../platform/logging';
@@ -17,6 +18,10 @@ export class IntegrationStorage {
1718

1819
private cacheLoaded = false;
1920

21+
private readonly _onDidChangeIntegrations = new EventEmitter<void>();
22+
23+
public readonly onDidChangeIntegrations = this._onDidChangeIntegrations.event;
24+
2025
constructor(@inject(IEncryptedStorage) private readonly encryptedStorage: IEncryptedStorage) {}
2126

2227
/**
@@ -35,6 +40,15 @@ export class IntegrationStorage {
3540
return this.cache.get(integrationId);
3641
}
3742

43+
/**
44+
* Get integration configuration for a specific project and integration
45+
* Note: Currently integrations are stored globally, not per-project,
46+
* so this method ignores the projectId parameter
47+
*/
48+
async getIntegrationConfig(_projectId: string, integrationId: string): Promise<IntegrationConfig | undefined> {
49+
return this.get(integrationId);
50+
}
51+
3852
/**
3953
* Get all integrations of a specific type
4054
*/
@@ -58,6 +72,9 @@ export class IntegrationStorage {
5872

5973
// Update the index
6074
await this.updateIndex();
75+
76+
// Fire change event
77+
this._onDidChangeIntegrations.fire();
6178
}
6279

6380
/**
@@ -74,6 +91,9 @@ export class IntegrationStorage {
7491

7592
// Update the index
7693
await this.updateIndex();
94+
95+
// Fire change event
96+
this._onDidChangeIntegrations.fire();
7797
}
7898

7999
/**

src/notebooks/deepnote/integrations/integrationWebview.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,10 @@ export class IntegrationWebviewProvider implements IIntegrationWebviewProvider {
2424

2525
/**
2626
* Show the integration management webview
27+
* @param integrations Map of integration IDs to their status
28+
* @param selectedIntegrationId Optional integration ID to select/configure immediately
2729
*/
28-
public async show(integrations: Map<string, IntegrationWithStatus>): Promise<void> {
30+
public async show(integrations: Map<string, IntegrationWithStatus>, selectedIntegrationId?: string): Promise<void> {
2931
// Update the stored integrations with the latest data
3032
this.integrations = integrations;
3133

@@ -35,6 +37,11 @@ export class IntegrationWebviewProvider implements IIntegrationWebviewProvider {
3537
if (this.currentPanel) {
3638
this.currentPanel.reveal(column);
3739
await this.updateWebview();
40+
41+
// If a specific integration was requested, show its configuration form
42+
if (selectedIntegrationId) {
43+
await this.showConfigurationForm(selectedIntegrationId);
44+
}
3845
return;
3946
}
4047

@@ -76,6 +83,11 @@ export class IntegrationWebviewProvider implements IIntegrationWebviewProvider {
7683
);
7784

7885
await this.updateWebview();
86+
87+
// If a specific integration was requested, show its configuration form
88+
if (selectedIntegrationId) {
89+
await this.showConfigurationForm(selectedIntegrationId);
90+
}
7991
}
8092

8193
/**

src/notebooks/deepnote/integrations/types.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,21 @@
1+
import { Event } from 'vscode';
12
import { IntegrationConfig, IntegrationWithStatus } from './integrationTypes';
23

34
export const IIntegrationStorage = Symbol('IIntegrationStorage');
45
export interface IIntegrationStorage {
6+
/**
7+
* Event fired when integrations change
8+
*/
9+
readonly onDidChangeIntegrations: Event<void>;
10+
511
getAll(): Promise<IntegrationConfig[]>;
612
get(integrationId: string): Promise<IntegrationConfig | undefined>;
13+
14+
/**
15+
* Get integration configuration for a specific project and integration
16+
*/
17+
getIntegrationConfig(projectId: string, integrationId: string): Promise<IntegrationConfig | undefined>;
18+
719
save(config: IntegrationConfig): Promise<void>;
820
delete(integrationId: string): Promise<void>;
921
exists(integrationId: string): Promise<boolean>;
@@ -27,8 +39,10 @@ export const IIntegrationWebviewProvider = Symbol('IIntegrationWebviewProvider')
2739
export interface IIntegrationWebviewProvider {
2840
/**
2941
* Show the integration management webview
42+
* @param integrations Map of integration IDs to their status
43+
* @param selectedIntegrationId Optional integration ID to select/configure immediately
3044
*/
31-
show(integrations: Map<string, IntegrationWithStatus>): Promise<void>;
45+
show(integrations: Map<string, IntegrationWithStatus>, selectedIntegrationId?: string): Promise<void>;
3246
}
3347

3448
export const IIntegrationManager = Symbol('IIntegrationManager');
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import {
2+
CancellationToken,
3+
EventEmitter,
4+
NotebookCell,
5+
NotebookCellStatusBarItem,
6+
NotebookCellStatusBarItemProvider,
7+
NotebookDocument,
8+
ProviderResult,
9+
notebooks
10+
} from 'vscode';
11+
import { inject, injectable } from 'inversify';
12+
13+
import { IExtensionSyncActivationService } from '../../platform/activation/types';
14+
import { IDisposableRegistry } from '../../platform/common/types';
15+
import { Commands } from '../../platform/common/constants';
16+
import { IIntegrationStorage } from './integrations/types';
17+
import { DATAFRAME_SQL_INTEGRATION_ID } from './integrations/integrationTypes';
18+
19+
/**
20+
* Provides status bar items for SQL cells showing the integration name
21+
*/
22+
@injectable()
23+
export class SqlCellStatusBarProvider implements NotebookCellStatusBarItemProvider, IExtensionSyncActivationService {
24+
private readonly _onDidChangeCellStatusBarItems = new EventEmitter<void>();
25+
26+
public readonly onDidChangeCellStatusBarItems = this._onDidChangeCellStatusBarItems.event;
27+
28+
constructor(
29+
@inject(IDisposableRegistry) private readonly disposables: IDisposableRegistry,
30+
@inject(IIntegrationStorage) private readonly integrationStorage: IIntegrationStorage
31+
) {}
32+
33+
public activate(): void {
34+
// Register the status bar provider for Deepnote notebooks
35+
this.disposables.push(notebooks.registerNotebookCellStatusBarItemProvider('deepnote', this));
36+
37+
// Listen for integration configuration changes to update status bar
38+
this.disposables.push(
39+
this.integrationStorage.onDidChangeIntegrations(() => {
40+
this._onDidChangeCellStatusBarItems.fire();
41+
})
42+
);
43+
}
44+
45+
public provideCellStatusBarItems(
46+
cell: NotebookCell,
47+
_token: CancellationToken
48+
): ProviderResult<NotebookCellStatusBarItem | NotebookCellStatusBarItem[]> {
49+
// Only show status bar for SQL cells
50+
if (cell.document.languageId !== 'sql') {
51+
return undefined;
52+
}
53+
54+
// Get the integration ID from cell metadata
55+
const integrationId = this.getIntegrationId(cell);
56+
if (!integrationId) {
57+
return undefined;
58+
}
59+
60+
// Don't show status bar for the internal DuckDB integration
61+
if (integrationId === DATAFRAME_SQL_INTEGRATION_ID) {
62+
return undefined;
63+
}
64+
65+
return this.createStatusBarItem(cell.notebook, integrationId);
66+
}
67+
68+
private getIntegrationId(cell: NotebookCell): string | undefined {
69+
// Check cell metadata for sql_integration_id
70+
const metadata = cell.metadata;
71+
if (metadata && typeof metadata === 'object') {
72+
const integrationId = (metadata as Record<string, unknown>).sql_integration_id;
73+
if (typeof integrationId === 'string') {
74+
return integrationId;
75+
}
76+
}
77+
78+
return undefined;
79+
}
80+
81+
private async createStatusBarItem(
82+
notebook: NotebookDocument,
83+
integrationId: string
84+
): Promise<NotebookCellStatusBarItem | undefined> {
85+
const projectId = notebook.metadata?.deepnoteProjectId;
86+
if (!projectId) {
87+
return undefined;
88+
}
89+
90+
// Get integration configuration to display the name
91+
const config = await this.integrationStorage.getIntegrationConfig(projectId, integrationId);
92+
const displayName = config?.name || integrationId;
93+
94+
// Create a status bar item that opens the integration management UI
95+
return {
96+
text: `$(database) ${displayName}`,
97+
alignment: 1, // NotebookCellStatusBarAlignment.Left
98+
tooltip: `SQL Integration: ${displayName}\nClick to configure`,
99+
command: {
100+
title: 'Configure Integration',
101+
command: Commands.ManageIntegrations,
102+
arguments: [integrationId]
103+
}
104+
};
105+
}
106+
}

0 commit comments

Comments
 (0)