Skip to content
Merged
Show file tree
Hide file tree
Changes from 15 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
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
78 changes: 61 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 @@ -425,6 +434,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 +478,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 +515,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 +534,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 +645,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 +662,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('AlloyDB'),
athena: l10n.t('Amazon Athena'),
'big-query': l10n.t('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 Cloud 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);
}
}
}
13 changes: 12 additions & 1 deletion src/platform/common/utils/localize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -822,15 +822,26 @@ 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
Expand All @@ -850,7 +861,7 @@ export namespace Integrations {
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 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
7 changes: 3 additions & 4 deletions src/webviews/webview-side/integrations/AthenaForm.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
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 { getDefaultIntegrationName } from './integrationUtils';

function createEmptyAthenaConfig(params: {
id: string;
name?: string;
}): Extract<DatabaseIntegrationConfig, { type: 'athena' }> {
const unnamedIntegration = getLocString('integrationsUnnamedIntegration', 'Unnamed Integration ({0})');

return {
id: params.id,
name: (params.name || format(unnamedIntegration, params.id)).trim(),
name: (params.name || getDefaultIntegrationName('athena')).trim(),
type: 'athena',
metadata: {
access_key_id: '',
Expand Down
5 changes: 2 additions & 3 deletions src/webviews/webview-side/integrations/BigQueryForm.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
import * as React from 'react';
import { format, getLocString } from '../react-common/locReactSide';
import { BigQueryAuthMethods, DatabaseIntegrationConfig } from '@deepnote/database-integrations';
import { getDefaultIntegrationName } from './integrationUtils';

type BigQueryConfig = Extract<DatabaseIntegrationConfig, { type: 'big-query' }>;

function createEmptyBigQueryConfig(params: { id: string; name?: string }): BigQueryConfig {
const unnamedIntegration = getLocString('integrationsUnnamedIntegration', 'Unnamed Integration ({0})');

return {
id: params.id,
name: (params.name || format(unnamedIntegration, params.id)).trim(),
name: (params.name || getDefaultIntegrationName('big-query')).trim(),
type: 'big-query',
metadata: {
authMethod: BigQueryAuthMethods.ServiceAccount,
Expand Down
7 changes: 3 additions & 4 deletions src/webviews/webview-side/integrations/ClickHouseForm.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 IClickHouseFormProps {
integrationId: string;
Expand All @@ -16,11 +17,9 @@ function createEmptyClickHouseConfig(params: {
id: string;
name?: string;
}): Extract<DatabaseIntegrationConfig, { type: 'clickhouse' }> {
const unnamedIntegration = getLocString('integrationsUnnamedIntegration', 'Unnamed Integration ({0})');

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