Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions INTEGRATIONS_CREDENTIALS.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ The extension supports all 18 database integration types from the `@deepnote/dat
- `'pgsql'` - PostgreSQL
- `'mysql'` - MySQL
- `'mariadb'` - MariaDB
- `'alloydb'` - Google Cloud AlloyDB
- `'alloydb'` - Google AlloyDB
- `'clickhouse'` - ClickHouse
- `'materialize'` - Materialize
- `'mindsdb'` - MindsDB
Expand All @@ -51,7 +51,7 @@ The extension supports all 18 database integration types from the `@deepnote/dat

- `'big-query'` - Google BigQuery (service account JSON)
- `'snowflake'` - Snowflake (password or key-pair auth)
- `'spanner'` - Google Cloud Spanner (service account JSON)
- `'spanner'` - Google Spanner (service account JSON)

**Cloud Databases (AWS credentials):**

Expand Down
9 changes: 9 additions & 0 deletions src/messageTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -173,12 +173,20 @@ export type LocalizedMessages = {
integrationsConfigure: string;
integrationsReconfigure: string;
integrationsReset: string;
integrationsDelete: string;
integrationsConfirmResetTitle: string;
integrationsConfirmResetMessage: string;
integrationsConfirmResetDetails: string;
integrationsConfirmDeleteTitle: string;
integrationsConfirmDeleteMessage: string;
integrationsConfirmDeleteDetails: string;
integrationsConfigureTitle: string;
integrationsCancel: string;
integrationsSave: string;
integrationsAddNewIntegration: string;
integrationsDatabase: string;
integrationsDataWarehousesLakes: string;
integrationsDatabases: string;
// Integration type labels
integrationsPostgresTypeLabel: string;
integrationsBigQueryTypeLabel: string;
Expand Down Expand Up @@ -442,6 +450,7 @@ export type LocalizedMessages = {
integrationsRequiredField: string;
integrationsOptionalField: string;
integrationsUnnamedIntegration: string;
integrationsDefaultName: string;
integrationsUnsupportedIntegrationType: string;
// Select input settings strings
selectInputSettingsTitle: string;
Expand Down
86 changes: 69 additions & 17 deletions src/notebooks/deepnote/integrations/integrationWebview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,10 +123,18 @@ export class IntegrationWebviewProvider implements IIntegrationWebviewProvider {
integrationsConfigure: localize.Integrations.configure,
integrationsReconfigure: localize.Integrations.reconfigure,
integrationsReset: localize.Integrations.reset,
integrationsDelete: localize.Integrations.deleteIntegration,
integrationsConfirmResetTitle: localize.Integrations.confirmResetTitle,
integrationsConfirmResetMessage: localize.Integrations.confirmResetMessage,
integrationsConfirmResetDetails: localize.Integrations.confirmResetDetails,
integrationsConfirmDeleteTitle: localize.Integrations.confirmDeleteTitle,
integrationsConfirmDeleteMessage: localize.Integrations.confirmDeleteMessage,
integrationsConfirmDeleteDetails: localize.Integrations.confirmDeleteDetails,
integrationsConfigureTitle: localize.Integrations.configureTitle,
integrationsAddNewIntegration: localize.Integrations.addNewIntegration,
integrationsDatabase: localize.Integrations.database,
integrationsDataWarehousesLakes: localize.Integrations.dataWarehousesLakes,
integrationsDatabases: localize.Integrations.databases,
integrationsPostgresTypeLabel: localize.Integrations.postgresTypeLabel,
integrationsBigQueryTypeLabel: localize.Integrations.bigQueryTypeLabel,
integrationsSnowflakeTypeLabel: localize.Integrations.snowflakeTypeLabel,
Expand Down Expand Up @@ -373,6 +381,7 @@ export class IntegrationWebviewProvider implements IIntegrationWebviewProvider {
integrationsCaCertificateText: localize.Integrations.caCertificateText,
integrationsCaCertificateTextPlaceholder: localize.Integrations.caCertificateTextPlaceholder,
integrationsUnnamedIntegration: localize.Integrations.unnamedIntegration('{0}'),
integrationsDefaultName: localize.Integrations.defaultName('{0}'),
integrationsUnsupportedIntegrationType: localize.Integrations.unsupportedIntegrationType('{0}')
};

Expand Down Expand Up @@ -400,8 +409,16 @@ export class IntegrationWebviewProvider implements IIntegrationWebviewProvider {
}));
logger.debug(`IntegrationWebviewProvider: Sending ${integrationsData.length} integrations to webview`);

// Get the project name from the notebook manager
let projectName: string | undefined;
if (this.projectId) {
const project = this.notebookManager.getOriginalProject(this.projectId);
projectName = project?.project.name;
}

await this.currentPanel.webview.postMessage({
integrations: integrationsData,
projectName,
type: 'update'
});
}
Expand All @@ -425,6 +442,11 @@ export class IntegrationWebviewProvider implements IIntegrationWebviewProvider {
await this.saveConfiguration(message.integrationId, message.config);
}
break;
case 'reset':
if (message.integrationId) {
await this.resetConfiguration(message.integrationId);
}
break;
case 'delete':
if (message.integrationId) {
await this.deleteConfiguration(message.integrationId);
Expand Down Expand Up @@ -464,9 +486,20 @@ export class IntegrationWebviewProvider implements IIntegrationWebviewProvider {
// Update local state
const integration = this.integrations.get(integrationId);
if (integration) {
// Existing integration - update it
integration.config = config;
integration.status = IntegrationStatus.Connected;
integration.integrationName = config.name;
integration.integrationType = config.type;
this.integrations.set(integrationId, integration);
} else {
// New integration - add it to the map
this.integrations.set(integrationId, {
config,
status: IntegrationStatus.Connected,
integrationName: config.name,
integrationType: config.type
});
}

// Update the project's integrations list
Expand All @@ -490,9 +523,9 @@ export class IntegrationWebviewProvider implements IIntegrationWebviewProvider {
}

/**
* Delete the configuration for an integration
* Reset the configuration for an integration (clears credentials but keeps the integration entry)
*/
private async deleteConfiguration(integrationId: string): Promise<void> {
private async resetConfiguration(integrationId: string): Promise<void> {
try {
await this.integrationStorage.delete(integrationId);

Expand All @@ -509,14 +542,44 @@ export class IntegrationWebviewProvider implements IIntegrationWebviewProvider {

await this.updateWebview();
await this.currentPanel?.webview.postMessage({
message: l10n.t('Configuration deleted successfully'),
message: l10n.t('Configuration reset successfully'),
type: 'success'
});
} catch (error) {
logger.error('Failed to reset integration configuration', error);
await this.currentPanel?.webview.postMessage({
message: l10n.t(
'Failed to reset configuration: {0}',
error instanceof Error ? error.message : 'Unknown error'
),
type: 'error'
});
}
}

/**
* Delete the integration completely (removes credentials and integration entry)
*/
private async deleteConfiguration(integrationId: string): Promise<void> {
try {
await this.integrationStorage.delete(integrationId);

// Remove from local state
this.integrations.delete(integrationId);

// Update the project's integrations list
await this.updateProjectIntegrationsList();

await this.updateWebview();
await this.currentPanel?.webview.postMessage({
message: l10n.t('Integration deleted successfully'),
type: 'success'
});
} catch (error) {
logger.error('Failed to delete integration configuration', error);
logger.error('Failed to delete integration', error);
await this.currentPanel?.webview.postMessage({
message: l10n.t(
'Failed to delete configuration: {0}',
'Failed to delete integration: {0}',
error instanceof Error ? error.message : 'Unknown error'
),
type: 'error'
Expand Down Expand Up @@ -590,16 +653,6 @@ export class IntegrationWebviewProvider implements IIntegrationWebviewProvider {
'index.js'
)
);
const styleUri = webview.asWebviewUri(
Uri.joinPath(
this.extensionContext.extensionUri,
'dist',
'webviews',
'webview-side',
'integrations',
'integrations.css'
)
);
const codiconUri = webview.asWebviewUri(
Uri.joinPath(
this.extensionContext.extensionUri,
Expand All @@ -617,9 +670,8 @@ export class IntegrationWebviewProvider implements IIntegrationWebviewProvider {
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src ${webview.cspSource} 'unsafe-inline'; script-src 'nonce-${nonce}'; font-src ${webview.cspSource};">
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src ${webview.cspSource} data:; style-src ${webview.cspSource} 'unsafe-inline'; script-src 'nonce-${nonce}'; font-src ${webview.cspSource};">
<link rel="stylesheet" href="${codiconUri}">
<link rel="stylesheet" href="${styleUri}">
<title>Deepnote Integrations</title>
</head>
<body>
Expand Down
63 changes: 21 additions & 42 deletions src/notebooks/deepnote/sqlCellStatusBarProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,26 @@ interface LocalQuickPickItem extends QuickPickItem {
id: string;
}

const integrationTypeLabels: Record<ConfigurableDatabaseIntegrationType, string> = {
alloydb: l10n.t('Google AlloyDB'),
athena: l10n.t('Amazon Athena'),
'big-query': l10n.t('Google BigQuery'),
clickhouse: l10n.t('ClickHouse'),
databricks: l10n.t('Databricks'),
dremio: l10n.t('Dremio'),
mariadb: l10n.t('MariaDB'),
materialize: l10n.t('Materialize'),
mindsdb: l10n.t('MindsDB'),
mongodb: l10n.t('MongoDB'),
mysql: l10n.t('MySQL'),
pgsql: l10n.t('PostgreSQL'),
redshift: l10n.t('Amazon Redshift'),
snowflake: l10n.t('Snowflake'),
spanner: l10n.t('Google Spanner'),
'sql-server': l10n.t('Microsoft SQL Server'),
trino: l10n.t('Trino')
};

/**
* Provides status bar items for SQL cells showing the integration name and variable name
*/
Expand Down Expand Up @@ -354,7 +374,7 @@ export class SqlCellStatusBarProvider implements NotebookCellStatusBarItemProvid

const typeLabel =
integrationType && (databaseIntegrationTypes as readonly string[]).includes(integrationType)
? this.getIntegrationTypeLabel(integrationType)
? integrationTypeLabels[integrationType] ?? integrationType
: projectIntegration.type;

const item: LocalQuickPickItem = {
Expand Down Expand Up @@ -437,45 +457,4 @@ export class SqlCellStatusBarProvider implements NotebookCellStatusBarItemProvid
// Trigger status bar update
this._onDidChangeCellStatusBarItems.fire();
}

private getIntegrationTypeLabel(type: ConfigurableDatabaseIntegrationType): string {
switch (type) {
case 'alloydb':
return l10n.t('AlloyDB');
case 'athena':
return l10n.t('Amazon Athena');
case 'big-query':
return l10n.t('BigQuery');
case 'clickhouse':
return l10n.t('ClickHouse');
case 'databricks':
return l10n.t('Databricks');
case 'dremio':
return l10n.t('Dremio');
case 'mariadb':
return l10n.t('MariaDB');
case 'materialize':
return l10n.t('Materialize');
case 'mindsdb':
return l10n.t('MindsDB');
case 'mongodb':
return l10n.t('MongoDB');
case 'mysql':
return l10n.t('MySQL');
case 'pgsql':
return l10n.t('PostgreSQL');
case 'redshift':
return l10n.t('Amazon Redshift');
case 'snowflake':
return l10n.t('Snowflake');
case 'spanner':
return l10n.t('Google Cloud Spanner');
case 'sql-server':
return l10n.t('SQL Server');
case 'trino':
return l10n.t('Trino');
default:
return String(type);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -996,7 +996,7 @@ suite('SqlCellStatusBarProvider', () => {
assert.strictEqual(duckDbItem.label, 'DataFrame SQL (DuckDB)');
});

test('shows BigQuery type label for BigQuery integrations', async () => {
test('shows BigQuery type label for Google BigQuery integrations', async () => {
const notebookMetadata = { deepnoteProjectId: 'project-1' };
const cell = createMockCell('sql', {}, notebookMetadata);
let quickPickItems: any[] = [];
Expand All @@ -1006,7 +1006,7 @@ suite('SqlCellStatusBarProvider', () => {
integrations: [
{
id: 'bigquery-integration',
name: 'My BigQuery',
name: 'My Google BigQuery',
type: 'big-query'
}
]
Expand All @@ -1021,8 +1021,8 @@ suite('SqlCellStatusBarProvider', () => {
await switchIntegrationHandler(cell);

const bigQueryItem = quickPickItems.find((item) => item.id === 'bigquery-integration');
assert.isDefined(bigQueryItem, 'BigQuery integration should be in quick pick items');
assert.strictEqual(bigQueryItem.description, 'BigQuery');
assert.isDefined(bigQueryItem, 'Google BigQuery integration should be in quick pick items');
assert.strictEqual(bigQueryItem.description, 'Google BigQuery');
});

test('shows raw type for unknown integration types', async () => {
Expand Down
19 changes: 15 additions & 4 deletions src/platform/common/utils/localize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -822,22 +822,33 @@ export namespace Integrations {
export const configure = l10n.t('Configure');
export const reconfigure = l10n.t('Reconfigure');
export const reset = l10n.t('Reset');
export const deleteIntegration = l10n.t('Delete');
export const confirmResetTitle = l10n.t('Confirm Reset');
export const confirmResetMessage = l10n.t('Are you sure you want to reset this integration configuration?');
export const confirmResetDetails = l10n.t('This will remove the stored credentials. You can reconfigure it later.');
export const confirmDeleteTitle = l10n.t('Confirm Delete');
export const confirmDeleteMessage = l10n.t('Are you sure you want to permanently delete this integration?');
export const confirmDeleteDetails = l10n.t(
'This will permanently remove the integration from your project. This action cannot be undone.'
);
export const configureTitle = l10n.t('Configure Integration: {0}');
export const cancel = l10n.t('Cancel');
export const save = l10n.t('Save');
export const addNewIntegration = l10n.t('Add New Integration');
export const database = l10n.t('Database');
export const dataWarehousesLakes = l10n.t('Data Warehouses & Lakes');
export const databases = l10n.t('Databases');
export const requiredField = l10n.t('*');
export const optionalField = l10n.t('(optional)');
export const unnamedIntegration = (id: string) => l10n.t('Unnamed Integration ({0})', id);
export const defaultName = (type: string) => l10n.t('My {0} integration', type);
export const unsupportedIntegrationType = (type: string) => l10n.t('Unsupported integration type: {0}', type);

// Integration type labels
export const postgresTypeLabel = l10n.t('PostgreSQL');
export const bigQueryTypeLabel = l10n.t('BigQuery');
export const bigQueryTypeLabel = l10n.t('Google BigQuery');
export const snowflakeTypeLabel = l10n.t('Snowflake');
export const alloyDBTypeLabel = l10n.t('AlloyDB');
export const alloyDBTypeLabel = l10n.t('Google AlloyDB');
export const athenaTypeLabel = l10n.t('Amazon Athena');
export const clickHouseTypeLabel = l10n.t('ClickHouse');
export const databricksTypeLabel = l10n.t('Databricks');
Expand All @@ -849,8 +860,8 @@ export namespace Integrations {
export const mySQLTypeLabel = l10n.t('MySQL');
export const duckDBTypeLabel = l10n.t('DuckDB');
export const redshiftTypeLabel = l10n.t('Amazon Redshift');
export const spannerTypeLabel = l10n.t('Google Cloud Spanner');
export const sqlServerTypeLabel = l10n.t('SQL Server');
export const spannerTypeLabel = l10n.t('Google Spanner');
export const sqlServerTypeLabel = l10n.t('Microsoft SQL Server');
export const trinoTypeLabel = l10n.t('Trino');

// PostgreSQL form strings
Expand Down
7 changes: 3 additions & 4 deletions src/webviews/webview-side/integrations/AlloyDBForm.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import * as React from 'react';
import { format, getLocString } from '../react-common/locReactSide';
import { getLocString } from '../react-common/locReactSide';
import { DatabaseIntegrationConfig } from '@deepnote/database-integrations';
import { SshOptionsFields } from './SshOptionsFields';
import { CaCertificateFields } from './CaCertificateFields';
import { getDefaultIntegrationName } from './integrationUtils';

export interface IAlloyDBFormProps {
integrationId: string;
Expand All @@ -16,11 +17,9 @@ function createEmptyAlloyDBConfig(params: {
id: string;
name?: string;
}): Extract<DatabaseIntegrationConfig, { type: 'alloydb' }> {
const unnamedIntegration = getLocString('integrationsUnnamedIntegration', 'Unnamed Integration ({0})');

return {
id: params.id,
name: (params.name || format(unnamedIntegration, params.id)).trim(),
name: (params.name || getDefaultIntegrationName('alloydb')).trim(),
type: 'alloydb',
metadata: {
host: '',
Expand Down
Loading
Loading