From 5d0b560f6f0f86eb1c72af604257b87a6944d592 Mon Sep 17 00:00:00 2001 From: jankuca Date: Sun, 2 Nov 2025 15:27:06 +0100 Subject: [PATCH 01/13] introduce narrower ConfigurableDatabaseIntegration* types --- .../deepnoteNotebookManager.unit.test.ts | 9 ++++--- .../integrations/integrationDetector.ts | 15 ++++++++--- .../integrations/integrationManager.ts | 5 ++++ .../integrations/integrationWebview.ts | 20 +++++++------- .../deepnote/sqlCellStatusBarProvider.ts | 18 +++++++++---- src/notebooks/types.ts | 3 ++- .../notebooks/deepnote/integrationStorage.ts | 26 ++++++++----------- .../deepnote/integrationStorage.unit.test.ts | 4 +-- .../notebooks/deepnote/integrationTypes.ts | 11 ++++++-- ...IntegrationEnvironmentVariablesProvider.ts | 4 +-- src/platform/notebooks/deepnote/types.ts | 10 +++---- .../integrations/ConfigurationForm.tsx | 8 +++--- .../integrations/IntegrationItem.tsx | 5 ++-- .../integrations/IntegrationPanel.tsx | 18 ++++++++----- .../webview-side/integrations/types.ts | 12 ++++++--- 15 files changed, 100 insertions(+), 68 deletions(-) diff --git a/src/notebooks/deepnote/deepnoteNotebookManager.unit.test.ts b/src/notebooks/deepnote/deepnoteNotebookManager.unit.test.ts index f81edcaa07..820953d063 100644 --- a/src/notebooks/deepnote/deepnoteNotebookManager.unit.test.ts +++ b/src/notebooks/deepnote/deepnoteNotebookManager.unit.test.ts @@ -2,6 +2,7 @@ import * as assert from 'assert'; import { DeepnoteNotebookManager } from './deepnoteNotebookManager'; import type { DeepnoteProject } from '../../platform/deepnote/deepnoteTypes'; +import { ProjectIntegration } from '../types'; suite('DeepnoteNotebookManager', () => { let manager: DeepnoteNotebookManager; @@ -187,7 +188,7 @@ suite('DeepnoteNotebookManager', () => { test('should update integrations list for existing project and return true', () => { manager.storeOriginalProject('project-123', mockProject, 'notebook-456'); - const integrations = [ + const integrations: ProjectIntegration[] = [ { id: 'int-1', name: 'PostgreSQL', type: 'pgsql' }, { id: 'int-2', name: 'BigQuery', type: 'big-query' } ]; @@ -211,7 +212,7 @@ suite('DeepnoteNotebookManager', () => { manager.storeOriginalProject('project-123', projectWithIntegrations, 'notebook-456'); - const newIntegrations = [ + const newIntegrations: ProjectIntegration[] = [ { id: 'new-int-1', name: 'New Integration 1', type: 'pgsql' }, { id: 'new-int-2', name: 'New Integration 2', type: 'big-query' } ]; @@ -257,7 +258,7 @@ suite('DeepnoteNotebookManager', () => { test('should preserve other project properties and return true', () => { manager.storeOriginalProject('project-123', mockProject, 'notebook-456'); - const integrations = [{ id: 'int-1', name: 'PostgreSQL', type: 'pgsql' }]; + const integrations: ProjectIntegration[] = [{ id: 'int-1', name: 'PostgreSQL', type: 'pgsql' }]; const result = manager.updateProjectIntegrations('project-123', integrations); @@ -275,7 +276,7 @@ suite('DeepnoteNotebookManager', () => { manager.storeOriginalProject('project-123', mockProject, 'notebook-456'); manager.updateCurrentNotebookId('project-123', undefined as any); - const integrations = [ + const integrations: ProjectIntegration[] = [ { id: 'int-1', name: 'PostgreSQL', type: 'pgsql' }, { id: 'int-2', name: 'BigQuery', type: 'big-query' } ]; diff --git a/src/notebooks/deepnote/integrations/integrationDetector.ts b/src/notebooks/deepnote/integrations/integrationDetector.ts index a17fc2ba30..fe64c64a7e 100644 --- a/src/notebooks/deepnote/integrations/integrationDetector.ts +++ b/src/notebooks/deepnote/integrations/integrationDetector.ts @@ -2,9 +2,13 @@ import { inject, injectable } from 'inversify'; import { logger } from '../../../platform/logging'; import { IDeepnoteNotebookManager } from '../../types'; -import { IntegrationStatus, IntegrationWithStatus } from '../../../platform/notebooks/deepnote/integrationTypes'; +import { + ConfigurableDatabaseIntegrationType, + IntegrationStatus, + IntegrationWithStatus +} from '../../../platform/notebooks/deepnote/integrationTypes'; import { IIntegrationDetector, IIntegrationStorage } from './types'; -import { DatabaseIntegrationType, databaseIntegrationTypes } from '@deepnote/database-integrations'; +import { databaseIntegrationTypes } from '@deepnote/database-integrations'; /** * Service for detecting integrations used in Deepnote notebooks @@ -41,7 +45,10 @@ export class IntegrationDetector implements IIntegrationDetector { for (const projectIntegration of projectIntegrations) { const integrationId = projectIntegration.id; const integrationType = projectIntegration.type; - if (!(databaseIntegrationTypes as readonly string[]).includes(integrationType)) { + if ( + !(databaseIntegrationTypes as readonly string[]).includes(integrationType) || + integrationType === 'pandas-dataframe' + ) { logger.debug(`IntegrationDetector: Skipping unsupported integration type: ${integrationType}`); continue; } @@ -53,7 +60,7 @@ export class IntegrationDetector implements IIntegrationDetector { status: config ? IntegrationStatus.Connected : IntegrationStatus.Disconnected, // Include integration metadata from project for prefilling when config is null integrationName: projectIntegration.name, - integrationType: integrationType as DatabaseIntegrationType + integrationType: integrationType as ConfigurableDatabaseIntegrationType }; integrations.set(integrationId, status); diff --git a/src/notebooks/deepnote/integrations/integrationManager.ts b/src/notebooks/deepnote/integrations/integrationManager.ts index 24456c59b7..3ad0bc592f 100644 --- a/src/notebooks/deepnote/integrations/integrationManager.ts +++ b/src/notebooks/deepnote/integrations/integrationManager.ts @@ -161,6 +161,11 @@ export class IntegrationManager implements IIntegrationManager { integrationType = projectIntegration.type as DatabaseIntegrationType; } + if (integrationType === 'pandas-dataframe') { + logger.debug(`IntegrationManager: Skipping internal DuckDB integration ${selectedIntegrationId}`); + return; + } + integrations.set(selectedIntegrationId, { config: config || null, status: config ? IntegrationStatus.Connected : IntegrationStatus.Disconnected, diff --git a/src/notebooks/deepnote/integrations/integrationWebview.ts b/src/notebooks/deepnote/integrations/integrationWebview.ts index d6be608a77..5c82d1090b 100644 --- a/src/notebooks/deepnote/integrations/integrationWebview.ts +++ b/src/notebooks/deepnote/integrations/integrationWebview.ts @@ -7,8 +7,11 @@ import { logger } from '../../../platform/logging'; import { LocalizedMessages, SharedMessages } from '../../../messageTypes'; import { IDeepnoteNotebookManager, ProjectIntegration } from '../../types'; import { IIntegrationStorage, IIntegrationWebviewProvider } from './types'; -import { IntegrationStatus, IntegrationWithStatus } from '../../../platform/notebooks/deepnote/integrationTypes'; -import { DatabaseIntegrationConfig } from '@deepnote/database-integrations'; +import { + ConfigurableDatabaseIntegrationConfig, + IntegrationStatus, + IntegrationWithStatus +} from '../../../platform/notebooks/deepnote/integrationTypes'; /** * Manages the webview panel for integration configuration @@ -217,7 +220,7 @@ export class IntegrationWebviewProvider implements IIntegrationWebviewProvider { private async handleMessage(message: { type: string; integrationId?: string; - config?: DatabaseIntegrationConfig; + config?: ConfigurableDatabaseIntegrationConfig; }): Promise { switch (message.type) { case 'configure': @@ -259,7 +262,10 @@ export class IntegrationWebviewProvider implements IIntegrationWebviewProvider { /** * Save the configuration for an integration */ - private async saveConfiguration(integrationId: string, config: DatabaseIntegrationConfig): Promise { + private async saveConfiguration( + integrationId: string, + config: ConfigurableDatabaseIntegrationConfig + ): Promise { try { await this.integrationStorage.save(config); @@ -345,12 +351,6 @@ export class IntegrationWebviewProvider implements IIntegrationWebviewProvider { return null; } - // Skip DuckDB integration (internal, not a real Deepnote integration) - if (type === 'pandas-dataframe') { - logger.trace(`IntegrationWebviewProvider: Skipping internal DuckDB integration ${id}`); - return null; - } - return { id, name: integration.config?.name || integration.integrationName || id, diff --git a/src/notebooks/deepnote/sqlCellStatusBarProvider.ts b/src/notebooks/deepnote/sqlCellStatusBarProvider.ts index 20ca8f45ec..7d905b2ed7 100644 --- a/src/notebooks/deepnote/sqlCellStatusBarProvider.ts +++ b/src/notebooks/deepnote/sqlCellStatusBarProvider.ts @@ -22,7 +22,10 @@ import { IExtensionSyncActivationService } from '../../platform/activation/types import { IDisposableRegistry } from '../../platform/common/types'; import { IIntegrationStorage } from './integrations/types'; import { Commands } from '../../platform/common/constants'; -import { DATAFRAME_SQL_INTEGRATION_ID } from '../../platform/notebooks/deepnote/integrationTypes'; +import { + ConfigurableDatabaseIntegrationType, + DATAFRAME_SQL_INTEGRATION_ID +} from '../../platform/notebooks/deepnote/integrationTypes'; import { IDeepnoteNotebookManager } from '../types'; import { DatabaseIntegrationType, databaseIntegrationTypes } from '@deepnote/database-integrations'; @@ -338,15 +341,20 @@ export class SqlCellStatusBarProvider implements NotebookCellStatusBarItemProvid // Add all project integrations for (const projectIntegration of projectIntegrations) { + const integrationType = + projectIntegration.type && + (databaseIntegrationTypes as readonly string[]).includes(projectIntegration.type) + ? (projectIntegration.type as DatabaseIntegrationType) + : null; + // Skip the internal DuckDB integration - if (projectIntegration.id === DATAFRAME_SQL_INTEGRATION_ID) { + if (projectIntegration.id === DATAFRAME_SQL_INTEGRATION_ID || integrationType === 'pandas-dataframe') { continue; } - const integrationType = projectIntegration.type; const typeLabel = integrationType && (databaseIntegrationTypes as readonly string[]).includes(integrationType) - ? this.getIntegrationTypeLabel(integrationType as DatabaseIntegrationType) + ? this.getIntegrationTypeLabel(integrationType) : projectIntegration.type; const item: LocalQuickPickItem = { @@ -430,7 +438,7 @@ export class SqlCellStatusBarProvider implements NotebookCellStatusBarItemProvid this._onDidChangeCellStatusBarItems.fire(); } - private getIntegrationTypeLabel(type: DatabaseIntegrationType): string { + private getIntegrationTypeLabel(type: ConfigurableDatabaseIntegrationType): string { switch (type) { case 'pgsql': return l10n.t('PostgreSQL'); diff --git a/src/notebooks/types.ts b/src/notebooks/types.ts index a83797fc8a..a12dacf52a 100644 --- a/src/notebooks/types.ts +++ b/src/notebooks/types.ts @@ -5,6 +5,7 @@ import { NotebookDocument, NotebookEditor, Uri, type Event } from 'vscode'; import { Resource } from '../platform/common/types'; import type { EnvironmentPath } from '@vscode/python-extension'; import { DeepnoteProject } from '../platform/deepnote/deepnoteTypes'; +import { ConfigurableDatabaseIntegrationType } from '../platform/notebooks/deepnote/integrationTypes'; export interface IEmbedNotebookEditorProvider { findNotebookEditor(resource: Resource): NotebookEditor | undefined; @@ -31,7 +32,7 @@ export interface INotebookPythonEnvironmentService { export interface ProjectIntegration { id: string; name: string; - type: string; + type: ConfigurableDatabaseIntegrationType; } export const IDeepnoteNotebookManager = Symbol('IDeepnoteNotebookManager'); diff --git a/src/platform/notebooks/deepnote/integrationStorage.ts b/src/platform/notebooks/deepnote/integrationStorage.ts index 2d57fbf132..23a21c1e52 100644 --- a/src/platform/notebooks/deepnote/integrationStorage.ts +++ b/src/platform/notebooks/deepnote/integrationStorage.ts @@ -6,17 +6,13 @@ import { IAsyncDisposableRegistry } from '../../common/types'; import { logger } from '../../logging'; import { IIntegrationStorage } from './types'; import { upgradeLegacyIntegrationConfig } from './legacyIntegrationConfigUtils'; -import { - DatabaseIntegrationConfig, - databaseIntegrationTypes, - databaseMetadataSchemasByType -} from '@deepnote/database-integrations'; -import { DATAFRAME_SQL_INTEGRATION_ID } from './integrationTypes'; +import { databaseIntegrationTypes, databaseMetadataSchemasByType } from '@deepnote/database-integrations'; +import { ConfigurableDatabaseIntegrationConfig, DATAFRAME_SQL_INTEGRATION_ID } from './integrationTypes'; const INTEGRATION_SERVICE_NAME = 'deepnote-integrations'; // NOTE: We need a way to upgrade existing configurations to the new format of deepnote/database-integrations. -type VersionedDatabaseIntegrationConfig = DatabaseIntegrationConfig & { version: 1 }; +type VersionedDatabaseIntegrationConfig = ConfigurableDatabaseIntegrationConfig & { version: 1 }; function storeEncryptedIntegrationConfig( encryptedStorage: IEncryptedStorage, @@ -33,7 +29,7 @@ function storeEncryptedIntegrationConfig( */ @injectable() export class IntegrationStorage implements IIntegrationStorage { - private readonly cache: Map = new Map(); + private readonly cache: Map = new Map(); private cacheLoaded = false; @@ -52,7 +48,7 @@ export class IntegrationStorage implements IIntegrationStorage { /** * Get all stored integration configurations. */ - async getAll(): Promise { + async getAll(): Promise { await this.ensureCacheLoaded(); return Array.from(this.cache.values()); } @@ -60,7 +56,7 @@ export class IntegrationStorage implements IIntegrationStorage { /** * Get a specific integration configuration by ID */ - async getIntegrationConfig(integrationId: string): Promise { + async getIntegrationConfig(integrationId: string): Promise { await this.ensureCacheLoaded(); return this.cache.get(integrationId); } @@ -73,15 +69,15 @@ export class IntegrationStorage implements IIntegrationStorage { async getProjectIntegrationConfig( _projectId: string, integrationId: string - ): Promise { + ): Promise { return this.getIntegrationConfig(integrationId); } /** * Save or update an integration configuration */ - async save(config: DatabaseIntegrationConfig): Promise { - if (config.type === 'pandas-dataframe' || config.id === DATAFRAME_SQL_INTEGRATION_ID) { + async save(config: ConfigurableDatabaseIntegrationConfig): Promise { + if ((config.type as string) === 'pandas-dataframe' || config.id === DATAFRAME_SQL_INTEGRATION_ID) { logger.warn(`IntegrationStorage: Skipping save for internal DuckDB integration ${config.id}`); return; } @@ -210,7 +206,7 @@ export class IntegrationStorage implements IIntegrationStorage { const config = databaseIntegrationTypes.includes(rawConfig.type) && rawConfig.type !== 'pandas-dataframe' - ? (rawConfig as DatabaseIntegrationConfig) + ? (rawConfig as ConfigurableDatabaseIntegrationConfig) : null; const validMetadata = config ? databaseMetadataSchemasByType[config.type].safeParse(config.metadata).data @@ -219,7 +215,7 @@ export class IntegrationStorage implements IIntegrationStorage { this.cache.set( id, // NOTE: We must cast here because there is no union-wide schema parser at the moment. - { ...config, metadata: validMetadata } as DatabaseIntegrationConfig + { ...config, metadata: validMetadata } as ConfigurableDatabaseIntegrationConfig ); } else { logger.warn(`Invalid integration config for ${id}, marking for deletion`); diff --git a/src/platform/notebooks/deepnote/integrationStorage.unit.test.ts b/src/platform/notebooks/deepnote/integrationStorage.unit.test.ts index 6808269ed2..356add1040 100644 --- a/src/platform/notebooks/deepnote/integrationStorage.unit.test.ts +++ b/src/platform/notebooks/deepnote/integrationStorage.unit.test.ts @@ -197,7 +197,7 @@ suite('IntegrationStorage', () => { metadata: {} }; - await storage.save(config); + await storage.save(config as any); const result = await storage.getIntegrationConfig('dataframe-1'); assert.strictEqual(result, undefined); @@ -211,7 +211,7 @@ suite('IntegrationStorage', () => { metadata: {} }; - await storage.save(config); + await storage.save(config as any); const result = await storage.getIntegrationConfig(DATAFRAME_SQL_INTEGRATION_ID); assert.strictEqual(result, undefined); diff --git a/src/platform/notebooks/deepnote/integrationTypes.ts b/src/platform/notebooks/deepnote/integrationTypes.ts index 19da67bcdf..3176e39027 100644 --- a/src/platform/notebooks/deepnote/integrationTypes.ts +++ b/src/platform/notebooks/deepnote/integrationTypes.ts @@ -136,6 +136,13 @@ export type LegacyIntegrationConfig = | LegacySnowflakeIntegrationConfig | LegacyDuckDBIntegrationConfig; +export type ConfigurableDatabaseIntegrationConfig = Extract< + DatabaseIntegrationConfig, + { type: ConfigurableDatabaseIntegrationType } +>; + +export type ConfigurableDatabaseIntegrationType = Exclude; + /** * Integration connection status */ @@ -149,7 +156,7 @@ export enum IntegrationStatus { * Integration with its current status */ export interface IntegrationWithStatus { - config: DatabaseIntegrationConfig | null; + config: ConfigurableDatabaseIntegrationConfig | null; status: IntegrationStatus; error?: string; /** @@ -159,5 +166,5 @@ export interface IntegrationWithStatus { /** * Type from the project's integrations list (used for prefilling when config is null) */ - integrationType?: DatabaseIntegrationType; + integrationType?: ConfigurableDatabaseIntegrationType; } diff --git a/src/platform/notebooks/deepnote/sqlIntegrationEnvironmentVariablesProvider.ts b/src/platform/notebooks/deepnote/sqlIntegrationEnvironmentVariablesProvider.ts index 54f2a69295..57c449cff2 100644 --- a/src/platform/notebooks/deepnote/sqlIntegrationEnvironmentVariablesProvider.ts +++ b/src/platform/notebooks/deepnote/sqlIntegrationEnvironmentVariablesProvider.ts @@ -11,7 +11,7 @@ import { IPlatformDeepnoteNotebookManager } from './types'; import { DATAFRAME_SQL_INTEGRATION_ID } from './integrationTypes'; -import { getEnvironmentVariablesForIntegrations } from '@deepnote/database-integrations'; +import { DatabaseIntegrationConfig, getEnvironmentVariablesForIntegrations } from '@deepnote/database-integrations'; /** * Provides environment variables for SQL integrations. @@ -88,7 +88,7 @@ export class SqlIntegrationEnvironmentVariablesProvider implements ISqlIntegrati `SqlIntegrationEnvironmentVariablesProvider: Found ${projectIntegrations.length} integrations in project` ); - const projectIntegrationConfigs = ( + const projectIntegrationConfigs: Array = ( await Promise.all( projectIntegrations.map((integration) => { return this.integrationStorage.getIntegrationConfig(integration.id); diff --git a/src/platform/notebooks/deepnote/types.ts b/src/platform/notebooks/deepnote/types.ts index 2632483c99..cb51200e37 100644 --- a/src/platform/notebooks/deepnote/types.ts +++ b/src/platform/notebooks/deepnote/types.ts @@ -2,7 +2,7 @@ import { CancellationToken, Event, NotebookDocument, Uri } from 'vscode'; import { IDisposable, Resource } from '../../common/types'; import { EnvironmentVariables } from '../../common/variables/types'; import { DeepnoteProject } from '../../deepnote/deepnoteTypes'; -import { DatabaseIntegrationConfig } from '@deepnote/database-integrations'; +import { ConfigurableDatabaseIntegrationConfig } from './integrationTypes'; /** * Settings for select input blocks @@ -31,7 +31,7 @@ export interface IIntegrationStorage extends IDisposable { */ readonly onDidChangeIntegrations: Event; - getAll(): Promise; + getAll(): Promise; /** * Retrieves the global (non-project-scoped) integration configuration by integration ID. @@ -49,7 +49,7 @@ export interface IIntegrationStorage extends IDisposable { * - The `IntegrationConfig` object if a global configuration exists for the given ID * - `undefined` if no global configuration exists for the given integration ID */ - getIntegrationConfig(integrationId: string): Promise; + getIntegrationConfig(integrationId: string): Promise; /** * Get integration configuration for a specific project and integration @@ -57,9 +57,9 @@ export interface IIntegrationStorage extends IDisposable { getProjectIntegrationConfig( projectId: string, integrationId: string - ): Promise; + ): Promise; - save(config: DatabaseIntegrationConfig): Promise; + save(config: ConfigurableDatabaseIntegrationConfig): Promise; delete(integrationId: string): Promise; exists(integrationId: string): Promise; clear(): Promise; diff --git a/src/webviews/webview-side/integrations/ConfigurationForm.tsx b/src/webviews/webview-side/integrations/ConfigurationForm.tsx index 597935450e..ac8dfc256e 100644 --- a/src/webviews/webview-side/integrations/ConfigurationForm.tsx +++ b/src/webviews/webview-side/integrations/ConfigurationForm.tsx @@ -3,14 +3,14 @@ import { format, getLocString } from '../react-common/locReactSide'; import { PostgresForm } from './PostgresForm'; import { BigQueryForm } from './BigQueryForm'; import { SnowflakeForm } from './SnowflakeForm'; -import { DatabaseIntegrationConfig, DatabaseIntegrationType } from '@deepnote/database-integrations'; +import { ConfigurableDatabaseIntegrationConfig, ConfigurableDatabaseIntegrationType } from './types'; export interface IConfigurationFormProps { integrationId: string; - existingConfig: DatabaseIntegrationConfig | null; + existingConfig: ConfigurableDatabaseIntegrationConfig | null; defaultName?: string; - integrationType: DatabaseIntegrationType; - onSave: (config: DatabaseIntegrationConfig) => void; + integrationType: ConfigurableDatabaseIntegrationType; + onSave: (config: ConfigurableDatabaseIntegrationConfig) => void; onCancel: () => void; } diff --git a/src/webviews/webview-side/integrations/IntegrationItem.tsx b/src/webviews/webview-side/integrations/IntegrationItem.tsx index 83944830a6..2eaad767b1 100644 --- a/src/webviews/webview-side/integrations/IntegrationItem.tsx +++ b/src/webviews/webview-side/integrations/IntegrationItem.tsx @@ -1,7 +1,6 @@ import * as React from 'react'; import { getLocString } from '../react-common/locReactSide'; -import { IntegrationWithStatus } from './types'; -import { DatabaseIntegrationType } from '@deepnote/database-integrations'; +import { ConfigurableDatabaseIntegrationType, IntegrationWithStatus } from './types'; export interface IIntegrationItemProps { integration: IntegrationWithStatus; @@ -9,7 +8,7 @@ export interface IIntegrationItemProps { onDelete: (integrationId: string) => void; } -const getIntegrationTypeLabel = (type: DatabaseIntegrationType): string => { +const getIntegrationTypeLabel = (type: ConfigurableDatabaseIntegrationType): string => { switch (type) { case 'pgsql': return getLocString('integrationsPostgresTypeLabel', 'PostgreSQL'); diff --git a/src/webviews/webview-side/integrations/IntegrationPanel.tsx b/src/webviews/webview-side/integrations/IntegrationPanel.tsx index fbcc43481f..e493f30ab5 100644 --- a/src/webviews/webview-side/integrations/IntegrationPanel.tsx +++ b/src/webviews/webview-side/integrations/IntegrationPanel.tsx @@ -3,8 +3,12 @@ import { IVsCodeApi } from '../react-common/postOffice'; import { getLocString, storeLocStrings } from '../react-common/locReactSide'; import { IntegrationList } from './IntegrationList'; import { ConfigurationForm } from './ConfigurationForm'; -import { IntegrationWithStatus, WebviewMessage } from './types'; -import { DatabaseIntegrationConfig, DatabaseIntegrationType } from '@deepnote/database-integrations'; +import { + ConfigurableDatabaseIntegrationConfig, + ConfigurableDatabaseIntegrationType, + IntegrationWithStatus, + WebviewMessage +} from './types'; export interface IIntegrationPanelProps { baseTheme: string; @@ -14,13 +18,13 @@ export interface IIntegrationPanelProps { export const IntegrationPanel: React.FC = ({ baseTheme, vscodeApi }) => { const [integrations, setIntegrations] = React.useState([]); const [selectedIntegrationId, setSelectedIntegrationId] = React.useState(null); - const [selectedConfig, setSelectedConfig] = React.useState(null); + const [selectedConfig, setSelectedConfig] = React.useState(null); const [selectedIntegrationDefaultName, setSelectedIntegrationDefaultName] = React.useState( undefined ); - const [selectedIntegrationType, setSelectedIntegrationType] = React.useState( - undefined - ); + const [selectedIntegrationType, setSelectedIntegrationType] = React.useState< + ConfigurableDatabaseIntegrationType | undefined + >(undefined); const [message, setMessage] = React.useState<{ type: 'success' | 'error'; text: string } | null>(null); const [confirmDelete, setConfirmDelete] = React.useState(null); @@ -126,7 +130,7 @@ export const IntegrationPanel: React.FC = ({ baseTheme, setConfirmDelete(null); }; - const handleSave = (config: DatabaseIntegrationConfig) => { + const handleSave = (config: ConfigurableDatabaseIntegrationConfig) => { vscodeApi.postMessage({ type: 'save', integrationId: config.id, diff --git a/src/webviews/webview-side/integrations/types.ts b/src/webviews/webview-side/integrations/types.ts index 80ab12f2a3..2312282ee7 100644 --- a/src/webviews/webview-side/integrations/types.ts +++ b/src/webviews/webview-side/integrations/types.ts @@ -1,13 +1,17 @@ import { DatabaseIntegrationConfig, type DatabaseIntegrationType } from '@deepnote/database-integrations'; +export type ConfigurableDatabaseIntegrationType = Exclude; + +export type ConfigurableDatabaseIntegrationConfig = Exclude; + export type IntegrationStatus = 'connected' | 'disconnected' | 'error'; export interface IntegrationWithStatus { id: string; - config: DatabaseIntegrationConfig | null; + config: ConfigurableDatabaseIntegrationConfig | null; status: IntegrationStatus; integrationName?: string; - integrationType?: DatabaseIntegrationType; + integrationType?: ConfigurableDatabaseIntegrationType; } export interface IVsCodeMessage { @@ -24,9 +28,9 @@ export interface UpdateMessage { export interface ShowFormMessage { type: 'showForm'; integrationId: string; - config: DatabaseIntegrationConfig | null; + config: ConfigurableDatabaseIntegrationConfig | null; integrationName?: string; - integrationType?: DatabaseIntegrationType; + integrationType?: ConfigurableDatabaseIntegrationType; } export interface StatusMessage { From 1a7f341164e79e61b0785e369ea0010e509476ad Mon Sep 17 00:00:00 2001 From: jankuca Date: Sun, 2 Nov 2025 15:27:28 +0100 Subject: [PATCH 02/13] add support and configuration forms for all remaining database integrations --- INTEGRATIONS_CREDENTIALS.md | 338 ++++++++++++++++-- src/messageTypes.ts | 185 ++++++++++ .../integrations/integrationWebview.ts | 172 +++++++++ .../deepnote/sqlCellStatusBarProvider.ts | 32 +- src/platform/common/utils/localize.ts | 209 +++++++++++ .../webview-side/integrations/AlloyDBForm.tsx | 196 ++++++++++ .../webview-side/integrations/AthenaForm.tsx | 184 ++++++++++ .../integrations/ClickHouseForm.tsx | 191 ++++++++++ .../integrations/ConfigurationForm.tsx | 156 +++++++- .../integrations/DatabricksForm.tsx | 197 ++++++++++ .../webview-side/integrations/DremioForm.tsx | 163 +++++++++ .../integrations/IntegrationItem.tsx | 32 +- .../webview-side/integrations/MariaDBForm.tsx | 184 ++++++++++ .../integrations/MaterializeForm.tsx | 223 ++++++++++++ .../webview-side/integrations/MindsDBForm.tsx | 196 ++++++++++ .../webview-side/integrations/MongoDBForm.tsx | 112 ++++++ .../webview-side/integrations/MySQLForm.tsx | 182 ++++++++++ .../integrations/RedshiftForm.tsx | 238 ++++++++++++ .../integrations/SQLServerForm.tsx | 201 +++++++++++ .../webview-side/integrations/SpannerForm.tsx | 207 +++++++++++ .../webview-side/integrations/TrinoForm.tsx | 193 ++++++++++ 21 files changed, 3762 insertions(+), 29 deletions(-) create mode 100644 src/webviews/webview-side/integrations/AlloyDBForm.tsx create mode 100644 src/webviews/webview-side/integrations/AthenaForm.tsx create mode 100644 src/webviews/webview-side/integrations/ClickHouseForm.tsx create mode 100644 src/webviews/webview-side/integrations/DatabricksForm.tsx create mode 100644 src/webviews/webview-side/integrations/DremioForm.tsx create mode 100644 src/webviews/webview-side/integrations/MariaDBForm.tsx create mode 100644 src/webviews/webview-side/integrations/MaterializeForm.tsx create mode 100644 src/webviews/webview-side/integrations/MindsDBForm.tsx create mode 100644 src/webviews/webview-side/integrations/MongoDBForm.tsx create mode 100644 src/webviews/webview-side/integrations/MySQLForm.tsx create mode 100644 src/webviews/webview-side/integrations/RedshiftForm.tsx create mode 100644 src/webviews/webview-side/integrations/SQLServerForm.tsx create mode 100644 src/webviews/webview-side/integrations/SpannerForm.tsx create mode 100644 src/webviews/webview-side/integrations/TrinoForm.tsx diff --git a/INTEGRATIONS_CREDENTIALS.md b/INTEGRATIONS_CREDENTIALS.md index c68ed7244b..ae7be6861f 100644 --- a/INTEGRATIONS_CREDENTIALS.md +++ b/INTEGRATIONS_CREDENTIALS.md @@ -33,10 +33,43 @@ This ensures consistency between the VSCode extension and Deepnote's cloud platf **Supported Integration Types:** +The extension supports all 18 database integration types from the `@deepnote/database-integrations` package: + +**SQL Databases (standard authentication):** + - `'pgsql'` - PostgreSQL -- `'big-query'` - BigQuery -- `'snowflake'` - Snowflake -- `'pandas-dataframe'` - DuckDB (internal) +- `'mysql'` - MySQL +- `'mariadb'` - MariaDB +- `'alloydb'` - Google Cloud AlloyDB +- `'clickhouse'` - ClickHouse +- `'materialize'` - Materialize +- `'mindsdb'` - MindsDB +- `'sql-server'` - Microsoft SQL Server +- `'trino'` - Trino + +**Cloud Databases (service account/key-based auth):** + +- `'big-query'` - Google BigQuery (service account JSON) +- `'snowflake'` - Snowflake (password or key-pair auth) +- `'spanner'` - Google Cloud Spanner (service account JSON) + +**Cloud Databases (AWS credentials):** + +- `'athena'` - Amazon Athena (access key and secret) +- `'redshift'` - Amazon Redshift (username/password or IAM) + +**Cloud Databases (token-based auth):** + +- `'databricks'` - Databricks (personal access token) +- `'dremio'` - Dremio (personal access token) + +**NoSQL:** + +- `'mongodb'` - MongoDB (connection string) + +**Internal:** + +- `'pandas-dataframe'` - DuckDB (automatically configured, not user-editable) ### Core Components @@ -85,12 +118,124 @@ The system uses `DatabaseIntegrationConfig` from `@deepnote/database-integration host: string; port: string; database: string; - username: string; + user: string; password: string; sslEnabled: boolean; } } +// MySQL (type: 'mysql') +{ + id: string; + name: string; + type: 'mysql'; + metadata: { + host: string; + port: string; + database: string; + user: string; + password: string; + } +} + +// MariaDB (type: 'mariadb') +{ + id: string; + name: string; + type: 'mariadb'; + metadata: { + host: string; + port: string; + database: string; + user: string; + password: string; + } +} + +// AlloyDB (type: 'alloydb') +{ + id: string; + name: string; + type: 'alloydb'; + metadata: { + host: string; + port: string; + database: string; + user: string; + password: string; + } +} + +// ClickHouse (type: 'clickhouse') +{ + id: string; + name: string; + type: 'clickhouse'; + metadata: { + host: string; + port: string; + database: string; + user: string; + password: string; + } +} + +// Materialize (type: 'materialize') +{ + id: string; + name: string; + type: 'materialize'; + metadata: { + host: string; + port: string; + database: string; + user: string; + password: string; + } +} + +// MindsDB (type: 'mindsdb') +{ + id: string; + name: string; + type: 'mindsdb'; + metadata: { + host: string; + port: string; + database: string; + user: string; + password: string; + } +} + +// SQL Server (type: 'sql-server') +{ + id: string; + name: string; + type: 'sql-server'; + metadata: { + host: string; + port: string; + database: string; + user: string; + password: string; + } +} + +// Trino (type: 'trino') +{ + id: string; + name: string; + type: 'trino'; + metadata: { + host: string; + port: string; + database: string; + user: string; + password: string; + } +} + // BigQuery (type: 'big-query') { id: string; @@ -122,8 +267,92 @@ The system uses `DatabaseIntegrationConfig` from `@deepnote/database-integration privateKeyPassphrase?: string; } } + +// Athena (type: 'athena') +{ + id: string; + name: string; + type: 'athena'; + metadata: { + access_key_id: string; + secret_access_key: string; + region: string; + s3_output_path: string; + workgroup?: string; + } +} + +// Databricks (type: 'databricks') +{ + id: string; + name: string; + type: 'databricks'; + metadata: { + host: string; + port: string; + httpPath: string; + token: string; + schema?: string; + catalog?: string; + } +} + +// Dremio (type: 'dremio') +{ + id: string; + name: string; + type: 'dremio'; + metadata: { + host: string; + port: string; + schema: string; + token: string; + } +} + +// MongoDB (type: 'mongodb') +{ + id: string; + name: string; + type: 'mongodb'; + metadata: { + connection_string: string; + } +} + +// Redshift (type: 'redshift') +{ + id: string; + name: string; + type: 'redshift'; + metadata: { + authMethod: 'username-and-password' | 'individual-credentials'; + host: string; + port?: string; + database: string; + // For username-and-password auth: + user: string; + password: string; + // For individual-credentials auth (uses AWS credentials from environment) + } +} + +// Spanner (type: 'spanner') +{ + id: string; + name: string; + type: 'spanner'; + metadata: { + instance: string; + database: string; + service_account: string; // JSON string + dataBoostEnabled: boolean; + } +} ``` +**Note:** The `pandas-dataframe` type is an internal integration that is automatically configured and cannot be modified by users. + **Legacy Config Upgrade:** When loading configurations from storage, the system automatically detects and upgrades legacy configs (pre-`@deepnote/database-integrations`) using `upgradeLegacyIntegrationConfig()`. Invalid or unsupported configs are filtered out during loading. @@ -273,27 +502,41 @@ Main React component that manages the webview UI state. 5. Extension removes credentials 6. Panel updates status to "Disconnected" -#### 6. **Configuration Forms** (`PostgresForm.tsx`, `BigQueryForm.tsx`) +#### 6. **Configuration Forms** Type-specific forms for entering integration credentials. -**PostgreSQL Form Fields:** +**Standard Database Forms** (`PostgresForm.tsx`, `MySQLForm.tsx`, `MariaDBForm.tsx`, `GenericDatabaseForm.tsx`): + +Most SQL databases use a standard form with these fields: - Name (display name) - Host -- Port (default: 5432) +- Port (with database-specific defaults) - Database - Username - Password -- SSL (checkbox) +- SSL (PostgreSQL only) + +Supported databases with standard forms: + +- PostgreSQL (port 5432, with SSL option) +- MySQL (port 3306) +- MariaDB (port 3306) +- AlloyDB (port 5432) +- ClickHouse (port 8123) +- Materialize (port 6875) +- MindsDB (port 47334) +- SQL Server (port 1433) +- Trino (port 8080) -**BigQuery Form Fields:** +**BigQuery Form** (`BigQueryForm.tsx`): - Name (display name) - Project ID - Service Account Credentials (JSON textarea) -**Snowflake Form Fields:** +**Snowflake Form** (`SnowflakeForm.tsx`): - Name (display name) - Account Name @@ -310,6 +553,24 @@ Type-specific forms for entering integration credentials. - Private Key (textarea) - Private Key Passphrase (optional) +**AWS Integration Forms** (`AthenaForm.tsx`, `RedshiftForm.tsx`): + +- **Athena**: AWS Access Key ID, Secret Access Key, Region, S3 Output Path, Workgroup (optional) +- **Redshift**: Authentication Method (username/password or IAM), Cluster Endpoint, Port, Database, Username/Password (for username/password auth) + +**Token-Based Forms** (`DatabricksForm.tsx`, `DremioForm.tsx`): + +- **Databricks**: Server Hostname, HTTP Path, Access Token, Port, Catalog (optional), Schema (optional) +- **Dremio**: Host, Port, Schema, Personal Access Token + +**NoSQL Forms** (`MongoDBForm.tsx`): + +- **MongoDB**: Connection String (supports mongodb:// and mongodb+srv:// formats) + +**Google Cloud Forms** (`SpannerForm.tsx`): + +- **Spanner**: Instance ID, Database, Service Account JSON, Data Boost Enabled (checkbox) + **Validation:** - All required fields must be filled @@ -556,7 +817,7 @@ The system was refactored to use the `@deepnote/database-integrations` package ( ## Adding New Integration Types -To add a new integration type (e.g., MySQL): +The extension now supports all 18 integration types from the `@deepnote/database-integrations` package (v1.1.1). To add support for a new integration type in the future: 1. **Add support to `@deepnote/database-integrations` package** (if not already supported): @@ -564,32 +825,61 @@ To add a new integration type (e.g., MySQL): - Add conversion logic for environment variables - This is the source of truth for integration types -2. **Create UI form component** (`MySQLForm.tsx`): +2. **Determine the form type needed**: - - Follow the pattern of `PostgresForm.tsx` or `BigQueryForm.tsx` - - Use the metadata structure from `@deepnote/database-integrations` - - Validate inputs according to the package's schema + - **Standard database** (host, port, database, user, password): Use `GenericDatabaseForm` + - **Complex authentication**: Create a custom form component or use `UnsupportedIntegrationForm` + +3. **For standard databases using `GenericDatabaseForm`**: -3. **Update `ConfigurationForm.tsx`** to render the new form: + Update `ConfigurationForm.tsx` to add a case: ```typescript - case 'mysql': - return ; + case 'new-database': + return ( + + ); ``` -4. **Update webview types** (`src/webviews/webview-side/integrations/types.ts`): +4. **For complex authentication**: + + Create a custom form component following the pattern of `BigQueryForm.tsx` or `SnowflakeForm.tsx`: + + - Use the metadata structure from `@deepnote/database-integrations` + - Validate inputs according to the package's schema + - Add the form to `ConfigurationForm.tsx` + +5. **Update type labels**: - - Import types from `@deepnote/database-integrations` - - Add any UI-specific types needed + - Add case to `getIntegrationTypeLabel()` in `sqlCellStatusBarProvider.ts` + - Add case to `getIntegrationTypeLabel()` in `IntegrationItem.tsx` -5. **Add localization strings** for the new integration type: +6. **Add localization strings** for the new integration type: - Integration name - Form field labels - Error messages -6. **Add tests**: - - Unit tests for the form component +7. **Update documentation** (`integrations_credentials.md`): + + - Add to supported integration types list + - Add metadata schema example + - Update configuration forms section + +8. **Add tests**: + - Unit tests for the form component (if custom) - Integration tests for storage and environment variable generation **Note:** The credential-to-environment-variable conversion is handled automatically by `@deepnote/database-integrations`, so no manual conversion logic is needed in the VSCode extension. diff --git a/src/messageTypes.ts b/src/messageTypes.ts index 6c90a08cbd..d24d6bc735 100644 --- a/src/messageTypes.ts +++ b/src/messageTypes.ts @@ -183,6 +183,21 @@ export type LocalizedMessages = { integrationsPostgresTypeLabel: string; integrationsBigQueryTypeLabel: string; integrationsSnowflakeTypeLabel: string; + integrationsAlloyDBTypeLabel: string; + integrationsAthenaTypeLabel: string; + integrationsClickHouseTypeLabel: string; + integrationsDatabricksTypeLabel: string; + integrationsDremioTypeLabel: string; + integrationsMariaDBTypeLabel: string; + integrationsMaterializeTypeLabel: string; + integrationsMindsDBTypeLabel: string; + integrationsMongoDBTypeLabel: string; + integrationsMySQLTypeLabel: string; + integrationsDuckDBTypeLabel: string; + integrationsRedshiftTypeLabel: string; + integrationsSpannerTypeLabel: string; + integrationsSQLServerTypeLabel: string; + integrationsTrinoTypeLabel: string; // PostgreSQL form strings integrationsPostgresNameLabel: string; integrationsPostgresNamePlaceholder: string; @@ -231,6 +246,176 @@ export type LocalizedMessages = { integrationsSnowflakeRolePlaceholder: string; integrationsSnowflakeWarehouseLabel: string; integrationsSnowflakeWarehousePlaceholder: string; + // MySQL form strings + integrationsMySQLNameLabel: string; + integrationsMySQLNamePlaceholder: string; + integrationsMySQLHostLabel: string; + integrationsMySQLHostPlaceholder: string; + integrationsMySQLPortLabel: string; + integrationsMySQLDatabaseLabel: string; + integrationsMySQLDatabasePlaceholder: string; + integrationsMySQLUsernameLabel: string; + integrationsMySQLUsernamePlaceholder: string; + integrationsMySQLPasswordLabel: string; + integrationsMySQLPasswordPlaceholder: string; + // MariaDB form strings + integrationsMariaDBNameLabel: string; + integrationsMariaDBNamePlaceholder: string; + integrationsMariaDBHostLabel: string; + integrationsMariaDBHostPlaceholder: string; + integrationsMariaDBPortLabel: string; + integrationsMariaDBDatabaseLabel: string; + integrationsMariaDBDatabasePlaceholder: string; + integrationsMariaDBUsernameLabel: string; + integrationsMariaDBUsernamePlaceholder: string; + integrationsMariaDBPasswordLabel: string; + integrationsMariaDBPasswordPlaceholder: string; + // Athena form strings + integrationsAthenaNameLabel: string; + integrationsAthenaNamePlaceholder: string; + integrationsAthenaAccessKeyIdLabel: string; + integrationsAthenaAccessKeyIdPlaceholder: string; + integrationsAthenaSecretAccessKeyLabel: string; + integrationsAthenaSecretAccessKeyPlaceholder: string; + integrationsAthenaRegionLabel: string; + integrationsAthenaRegionPlaceholder: string; + integrationsAthenaS3OutputPathLabel: string; + integrationsAthenaS3OutputPathPlaceholder: string; + integrationsAthenaWorkgroupLabel: string; + integrationsAthenaWorkgroupPlaceholder: string; + // Databricks form strings + integrationsDatabricksNameLabel: string; + integrationsDatabricksNamePlaceholder: string; + integrationsDatabricksHostLabel: string; + integrationsDatabricksHostPlaceholder: string; + integrationsDatabricksHttpPathLabel: string; + integrationsDatabricksHttpPathPlaceholder: string; + integrationsDatabricksTokenLabel: string; + integrationsDatabricksTokenPlaceholder: string; + integrationsDatabricksPortLabel: string; + integrationsDatabricksCatalogLabel: string; + integrationsDatabricksCatalogPlaceholder: string; + integrationsDatabricksSchemaLabel: string; + integrationsDatabricksSchemaPlaceholder: string; + // Dremio form strings + integrationsDremioNameLabel: string; + integrationsDremioNamePlaceholder: string; + integrationsDremioHostLabel: string; + integrationsDremioHostPlaceholder: string; + integrationsDremioPortLabel: string; + integrationsDremioSchemaLabel: string; + integrationsDremioSchemaPlaceholder: string; + integrationsDremioTokenLabel: string; + integrationsDremioTokenPlaceholder: string; + // MongoDB form strings + integrationsMongoDBNameLabel: string; + integrationsMongoDBNamePlaceholder: string; + integrationsMongoDBConnectionStringLabel: string; + integrationsMongoDBConnectionStringPlaceholder: string; + integrationsMongoDBConnectionStringHelp: string; + // Redshift form strings + integrationsRedshiftNameLabel: string; + integrationsRedshiftNamePlaceholder: string; + integrationsRedshiftAuthMethodLabel: string; + integrationsRedshiftAuthMethodUsernamePassword: string; + integrationsRedshiftAuthMethodIndividualCredentials: string; + integrationsRedshiftAuthMethodHelp: string; + integrationsRedshiftHostLabel: string; + integrationsRedshiftHostPlaceholder: string; + integrationsRedshiftPortLabel: string; + integrationsRedshiftDatabaseLabel: string; + integrationsRedshiftDatabasePlaceholder: string; + integrationsRedshiftUsernameLabel: string; + integrationsRedshiftUsernamePlaceholder: string; + integrationsRedshiftPasswordLabel: string; + integrationsRedshiftPasswordPlaceholder: string; + // Spanner form strings + integrationsSpannerNameLabel: string; + integrationsSpannerNamePlaceholder: string; + integrationsSpannerInstanceLabel: string; + integrationsSpannerInstancePlaceholder: string; + integrationsSpannerDatabaseLabel: string; + integrationsSpannerDatabasePlaceholder: string; + integrationsSpannerServiceAccountLabel: string; + integrationsSpannerServiceAccountPlaceholder: string; + integrationsSpannerServiceAccountHelp: string; + integrationsSpannerServiceAccountInvalidJson: string; + integrationsSpannerDataBoostLabel: string; + integrationsSpannerDataBoostHelp: string; + // AlloyDB form strings + integrationsAlloyDBNameLabel: string; + integrationsAlloyDBNamePlaceholder: string; + integrationsAlloyDBHostLabel: string; + integrationsAlloyDBHostPlaceholder: string; + integrationsAlloyDBPortLabel: string; + integrationsAlloyDBDatabaseLabel: string; + integrationsAlloyDBDatabasePlaceholder: string; + integrationsAlloyDBUsernameLabel: string; + integrationsAlloyDBUsernamePlaceholder: string; + integrationsAlloyDBPasswordLabel: string; + integrationsAlloyDBPasswordPlaceholder: string; + // ClickHouse form strings + integrationsClickHouseNameLabel: string; + integrationsClickHouseNamePlaceholder: string; + integrationsClickHouseHostLabel: string; + integrationsClickHouseHostPlaceholder: string; + integrationsClickHousePortLabel: string; + integrationsClickHouseDatabaseLabel: string; + integrationsClickHouseDatabasePlaceholder: string; + integrationsClickHouseUsernameLabel: string; + integrationsClickHouseUsernamePlaceholder: string; + integrationsClickHousePasswordLabel: string; + integrationsClickHousePasswordPlaceholder: string; + // Materialize form strings + integrationsMaterializeNameLabel: string; + integrationsMaterializeNamePlaceholder: string; + integrationsMaterializeHostLabel: string; + integrationsMaterializeHostPlaceholder: string; + integrationsMaterializePortLabel: string; + integrationsMaterializeDatabaseLabel: string; + integrationsMaterializeDatabasePlaceholder: string; + integrationsMaterializeClusterLabel: string; + integrationsMaterializeClusterPlaceholder: string; + integrationsMaterializeUsernameLabel: string; + integrationsMaterializeUsernamePlaceholder: string; + integrationsMaterializePasswordLabel: string; + integrationsMaterializePasswordPlaceholder: string; + // MindsDB form strings + integrationsMindsDBNameLabel: string; + integrationsMindsDBNamePlaceholder: string; + integrationsMindsDBHostLabel: string; + integrationsMindsDBHostPlaceholder: string; + integrationsMindsDBPortLabel: string; + integrationsMindsDBDatabaseLabel: string; + integrationsMindsDBDatabasePlaceholder: string; + integrationsMindsDBUsernameLabel: string; + integrationsMindsDBUsernamePlaceholder: string; + integrationsMindsDBPasswordLabel: string; + integrationsMindsDBPasswordPlaceholder: string; + // SQL Server form strings + integrationsSQLServerNameLabel: string; + integrationsSQLServerNamePlaceholder: string; + integrationsSQLServerHostLabel: string; + integrationsSQLServerHostPlaceholder: string; + integrationsSQLServerPortLabel: string; + integrationsSQLServerDatabaseLabel: string; + integrationsSQLServerDatabasePlaceholder: string; + integrationsSQLServerUsernameLabel: string; + integrationsSQLServerUsernamePlaceholder: string; + integrationsSQLServerPasswordLabel: string; + integrationsSQLServerPasswordPlaceholder: string; + // Trino form strings + integrationsTrinoNameLabel: string; + integrationsTrinoNamePlaceholder: string; + integrationsTrinoHostLabel: string; + integrationsTrinoHostPlaceholder: string; + integrationsTrinoPortLabel: string; + integrationsTrinoDatabaseLabel: string; + integrationsTrinoDatabasePlaceholder: string; + integrationsTrinoUsernameLabel: string; + integrationsTrinoUsernamePlaceholder: string; + integrationsTrinoPasswordLabel: string; + integrationsTrinoPasswordPlaceholder: string; // Common form strings integrationsRequiredField: string; integrationsOptionalField: string; diff --git a/src/notebooks/deepnote/integrations/integrationWebview.ts b/src/notebooks/deepnote/integrations/integrationWebview.ts index 5c82d1090b..671674e819 100644 --- a/src/notebooks/deepnote/integrations/integrationWebview.ts +++ b/src/notebooks/deepnote/integrations/integrationWebview.ts @@ -130,6 +130,21 @@ export class IntegrationWebviewProvider implements IIntegrationWebviewProvider { integrationsPostgresTypeLabel: localize.Integrations.postgresTypeLabel, integrationsBigQueryTypeLabel: localize.Integrations.bigQueryTypeLabel, integrationsSnowflakeTypeLabel: localize.Integrations.snowflakeTypeLabel, + integrationsAlloyDBTypeLabel: localize.Integrations.alloyDBTypeLabel, + integrationsAthenaTypeLabel: localize.Integrations.athenaTypeLabel, + integrationsClickHouseTypeLabel: localize.Integrations.clickHouseTypeLabel, + integrationsDatabricksTypeLabel: localize.Integrations.databricksTypeLabel, + integrationsDremioTypeLabel: localize.Integrations.dremioTypeLabel, + integrationsMariaDBTypeLabel: localize.Integrations.mariaDBTypeLabel, + integrationsMaterializeTypeLabel: localize.Integrations.materializeTypeLabel, + integrationsMindsDBTypeLabel: localize.Integrations.mindsDBTypeLabel, + integrationsMongoDBTypeLabel: localize.Integrations.mongoDBTypeLabel, + integrationsMySQLTypeLabel: localize.Integrations.mySQLTypeLabel, + integrationsDuckDBTypeLabel: localize.Integrations.duckDBTypeLabel, + integrationsRedshiftTypeLabel: localize.Integrations.redshiftTypeLabel, + integrationsSpannerTypeLabel: localize.Integrations.spannerTypeLabel, + integrationsSQLServerTypeLabel: localize.Integrations.sqlServerTypeLabel, + integrationsTrinoTypeLabel: localize.Integrations.trinoTypeLabel, integrationsCancel: localize.Integrations.cancel, integrationsSave: localize.Integrations.save, integrationsRequiredField: localize.Integrations.requiredField, @@ -180,6 +195,163 @@ export class IntegrationWebviewProvider implements IIntegrationWebviewProvider { integrationsSnowflakeRolePlaceholder: localize.Integrations.snowflakeRolePlaceholder, integrationsSnowflakeWarehouseLabel: localize.Integrations.snowflakeWarehouseLabel, integrationsSnowflakeWarehousePlaceholder: localize.Integrations.snowflakeWarehousePlaceholder, + integrationsMySQLNameLabel: localize.Integrations.mySQLNameLabel, + integrationsMySQLNamePlaceholder: localize.Integrations.mySQLNamePlaceholder, + integrationsMySQLHostLabel: localize.Integrations.mySQLHostLabel, + integrationsMySQLHostPlaceholder: localize.Integrations.mySQLHostPlaceholder, + integrationsMySQLPortLabel: localize.Integrations.mySQLPortLabel, + integrationsMySQLDatabaseLabel: localize.Integrations.mySQLDatabaseLabel, + integrationsMySQLDatabasePlaceholder: localize.Integrations.mySQLDatabasePlaceholder, + integrationsMySQLUsernameLabel: localize.Integrations.mySQLUsernameLabel, + integrationsMySQLUsernamePlaceholder: localize.Integrations.mySQLUsernamePlaceholder, + integrationsMySQLPasswordLabel: localize.Integrations.mySQLPasswordLabel, + integrationsMySQLPasswordPlaceholder: localize.Integrations.mySQLPasswordPlaceholder, + integrationsMariaDBNameLabel: localize.Integrations.mariaDBNameLabel, + integrationsMariaDBNamePlaceholder: localize.Integrations.mariaDBNamePlaceholder, + integrationsMariaDBHostLabel: localize.Integrations.mariaDBHostLabel, + integrationsMariaDBHostPlaceholder: localize.Integrations.mariaDBHostPlaceholder, + integrationsMariaDBPortLabel: localize.Integrations.mariaDBPortLabel, + integrationsMariaDBDatabaseLabel: localize.Integrations.mariaDBDatabaseLabel, + integrationsMariaDBDatabasePlaceholder: localize.Integrations.mariaDBDatabasePlaceholder, + integrationsMariaDBUsernameLabel: localize.Integrations.mariaDBUsernameLabel, + integrationsMariaDBUsernamePlaceholder: localize.Integrations.mariaDBUsernamePlaceholder, + integrationsMariaDBPasswordLabel: localize.Integrations.mariaDBPasswordLabel, + integrationsMariaDBPasswordPlaceholder: localize.Integrations.mariaDBPasswordPlaceholder, + integrationsAthenaNameLabel: localize.Integrations.athenaNameLabel, + integrationsAthenaNamePlaceholder: localize.Integrations.athenaNamePlaceholder, + integrationsAthenaAccessKeyIdLabel: localize.Integrations.athenaAccessKeyIdLabel, + integrationsAthenaAccessKeyIdPlaceholder: localize.Integrations.athenaAccessKeyIdPlaceholder, + integrationsAthenaSecretAccessKeyLabel: localize.Integrations.athenaSecretAccessKeyLabel, + integrationsAthenaSecretAccessKeyPlaceholder: localize.Integrations.athenaSecretAccessKeyPlaceholder, + integrationsAthenaRegionLabel: localize.Integrations.athenaRegionLabel, + integrationsAthenaRegionPlaceholder: localize.Integrations.athenaRegionPlaceholder, + integrationsAthenaS3OutputPathLabel: localize.Integrations.athenaS3OutputPathLabel, + integrationsAthenaS3OutputPathPlaceholder: localize.Integrations.athenaS3OutputPathPlaceholder, + integrationsAthenaWorkgroupLabel: localize.Integrations.athenaWorkgroupLabel, + integrationsAthenaWorkgroupPlaceholder: localize.Integrations.athenaWorkgroupPlaceholder, + integrationsDatabricksNameLabel: localize.Integrations.databricksNameLabel, + integrationsDatabricksNamePlaceholder: localize.Integrations.databricksNamePlaceholder, + integrationsDatabricksHostLabel: localize.Integrations.databricksHostLabel, + integrationsDatabricksHostPlaceholder: localize.Integrations.databricksHostPlaceholder, + integrationsDatabricksHttpPathLabel: localize.Integrations.databricksHttpPathLabel, + integrationsDatabricksHttpPathPlaceholder: localize.Integrations.databricksHttpPathPlaceholder, + integrationsDatabricksTokenLabel: localize.Integrations.databricksTokenLabel, + integrationsDatabricksTokenPlaceholder: localize.Integrations.databricksTokenPlaceholder, + integrationsDatabricksPortLabel: localize.Integrations.databricksPortLabel, + integrationsDatabricksCatalogLabel: localize.Integrations.databricksCatalogLabel, + integrationsDatabricksCatalogPlaceholder: localize.Integrations.databricksCatalogPlaceholder, + integrationsDatabricksSchemaLabel: localize.Integrations.databricksSchemaLabel, + integrationsDatabricksSchemaPlaceholder: localize.Integrations.databricksSchemaPlaceholder, + integrationsDremioNameLabel: localize.Integrations.dremioNameLabel, + integrationsDremioNamePlaceholder: localize.Integrations.dremioNamePlaceholder, + integrationsDremioHostLabel: localize.Integrations.dremioHostLabel, + integrationsDremioHostPlaceholder: localize.Integrations.dremioHostPlaceholder, + integrationsDremioPortLabel: localize.Integrations.dremioPortLabel, + integrationsDremioSchemaLabel: localize.Integrations.dremioSchemaLabel, + integrationsDremioSchemaPlaceholder: localize.Integrations.dremioSchemaPlaceholder, + integrationsDremioTokenLabel: localize.Integrations.dremioTokenLabel, + integrationsDremioTokenPlaceholder: localize.Integrations.dremioTokenPlaceholder, + integrationsMongoDBNameLabel: localize.Integrations.mongoDBNameLabel, + integrationsMongoDBNamePlaceholder: localize.Integrations.mongoDBNamePlaceholder, + integrationsMongoDBConnectionStringLabel: localize.Integrations.mongoDBConnectionStringLabel, + integrationsMongoDBConnectionStringPlaceholder: localize.Integrations.mongoDBConnectionStringPlaceholder, + integrationsMongoDBConnectionStringHelp: localize.Integrations.mongoDBConnectionStringHelp, + integrationsRedshiftNameLabel: localize.Integrations.redshiftNameLabel, + integrationsRedshiftNamePlaceholder: localize.Integrations.redshiftNamePlaceholder, + integrationsRedshiftAuthMethodLabel: localize.Integrations.redshiftAuthMethodLabel, + integrationsRedshiftAuthMethodUsernamePassword: localize.Integrations.redshiftAuthMethodUsernamePassword, + integrationsRedshiftAuthMethodIndividualCredentials: + localize.Integrations.redshiftAuthMethodIndividualCredentials, + integrationsRedshiftAuthMethodHelp: localize.Integrations.redshiftAuthMethodHelp, + integrationsRedshiftHostLabel: localize.Integrations.redshiftHostLabel, + integrationsRedshiftHostPlaceholder: localize.Integrations.redshiftHostPlaceholder, + integrationsRedshiftPortLabel: localize.Integrations.redshiftPortLabel, + integrationsRedshiftDatabaseLabel: localize.Integrations.redshiftDatabaseLabel, + integrationsRedshiftDatabasePlaceholder: localize.Integrations.redshiftDatabasePlaceholder, + integrationsRedshiftUsernameLabel: localize.Integrations.redshiftUsernameLabel, + integrationsRedshiftUsernamePlaceholder: localize.Integrations.redshiftUsernamePlaceholder, + integrationsRedshiftPasswordLabel: localize.Integrations.redshiftPasswordLabel, + integrationsRedshiftPasswordPlaceholder: localize.Integrations.redshiftPasswordPlaceholder, + integrationsSpannerNameLabel: localize.Integrations.spannerNameLabel, + integrationsSpannerNamePlaceholder: localize.Integrations.spannerNamePlaceholder, + integrationsSpannerInstanceLabel: localize.Integrations.spannerInstanceLabel, + integrationsSpannerInstancePlaceholder: localize.Integrations.spannerInstancePlaceholder, + integrationsSpannerDatabaseLabel: localize.Integrations.spannerDatabaseLabel, + integrationsSpannerDatabasePlaceholder: localize.Integrations.spannerDatabasePlaceholder, + integrationsSpannerServiceAccountLabel: localize.Integrations.spannerServiceAccountLabel, + integrationsSpannerServiceAccountPlaceholder: localize.Integrations.spannerServiceAccountPlaceholder, + integrationsSpannerServiceAccountHelp: localize.Integrations.spannerServiceAccountHelp, + integrationsSpannerServiceAccountInvalidJson: localize.Integrations.spannerServiceAccountInvalidJson, + integrationsSpannerDataBoostLabel: localize.Integrations.spannerDataBoostLabel, + integrationsSpannerDataBoostHelp: localize.Integrations.spannerDataBoostHelp, + integrationsAlloyDBNameLabel: localize.Integrations.alloyDBNameLabel, + integrationsAlloyDBNamePlaceholder: localize.Integrations.alloyDBNamePlaceholder, + integrationsAlloyDBHostLabel: localize.Integrations.alloyDBHostLabel, + integrationsAlloyDBHostPlaceholder: localize.Integrations.alloyDBHostPlaceholder, + integrationsAlloyDBPortLabel: localize.Integrations.alloyDBPortLabel, + integrationsAlloyDBDatabaseLabel: localize.Integrations.alloyDBDatabaseLabel, + integrationsAlloyDBDatabasePlaceholder: localize.Integrations.alloyDBDatabasePlaceholder, + integrationsAlloyDBUsernameLabel: localize.Integrations.alloyDBUsernameLabel, + integrationsAlloyDBUsernamePlaceholder: localize.Integrations.alloyDBUsernamePlaceholder, + integrationsAlloyDBPasswordLabel: localize.Integrations.alloyDBPasswordLabel, + integrationsAlloyDBPasswordPlaceholder: localize.Integrations.alloyDBPasswordPlaceholder, + integrationsClickHouseNameLabel: localize.Integrations.clickHouseNameLabel, + integrationsClickHouseNamePlaceholder: localize.Integrations.clickHouseNamePlaceholder, + integrationsClickHouseHostLabel: localize.Integrations.clickHouseHostLabel, + integrationsClickHouseHostPlaceholder: localize.Integrations.clickHouseHostPlaceholder, + integrationsClickHousePortLabel: localize.Integrations.clickHousePortLabel, + integrationsClickHouseDatabaseLabel: localize.Integrations.clickHouseDatabaseLabel, + integrationsClickHouseDatabasePlaceholder: localize.Integrations.clickHouseDatabasePlaceholder, + integrationsClickHouseUsernameLabel: localize.Integrations.clickHouseUsernameLabel, + integrationsClickHouseUsernamePlaceholder: localize.Integrations.clickHouseUsernamePlaceholder, + integrationsClickHousePasswordLabel: localize.Integrations.clickHousePasswordLabel, + integrationsClickHousePasswordPlaceholder: localize.Integrations.clickHousePasswordPlaceholder, + integrationsMaterializeNameLabel: localize.Integrations.materializeNameLabel, + integrationsMaterializeNamePlaceholder: localize.Integrations.materializeNamePlaceholder, + integrationsMaterializeHostLabel: localize.Integrations.materializeHostLabel, + integrationsMaterializeHostPlaceholder: localize.Integrations.materializeHostPlaceholder, + integrationsMaterializePortLabel: localize.Integrations.materializePortLabel, + integrationsMaterializeDatabaseLabel: localize.Integrations.materializeDatabaseLabel, + integrationsMaterializeDatabasePlaceholder: localize.Integrations.materializeDatabasePlaceholder, + integrationsMaterializeClusterLabel: localize.Integrations.materializeClusterLabel, + integrationsMaterializeClusterPlaceholder: localize.Integrations.materializeClusterPlaceholder, + integrationsMaterializeUsernameLabel: localize.Integrations.materializeUsernameLabel, + integrationsMaterializeUsernamePlaceholder: localize.Integrations.materializeUsernamePlaceholder, + integrationsMaterializePasswordLabel: localize.Integrations.materializePasswordLabel, + integrationsMaterializePasswordPlaceholder: localize.Integrations.materializePasswordPlaceholder, + integrationsMindsDBNameLabel: localize.Integrations.mindsDBNameLabel, + integrationsMindsDBNamePlaceholder: localize.Integrations.mindsDBNamePlaceholder, + integrationsMindsDBHostLabel: localize.Integrations.mindsDBHostLabel, + integrationsMindsDBHostPlaceholder: localize.Integrations.mindsDBHostPlaceholder, + integrationsMindsDBPortLabel: localize.Integrations.mindsDBPortLabel, + integrationsMindsDBDatabaseLabel: localize.Integrations.mindsDBDatabaseLabel, + integrationsMindsDBDatabasePlaceholder: localize.Integrations.mindsDBDatabasePlaceholder, + integrationsMindsDBUsernameLabel: localize.Integrations.mindsDBUsernameLabel, + integrationsMindsDBUsernamePlaceholder: localize.Integrations.mindsDBUsernamePlaceholder, + integrationsMindsDBPasswordLabel: localize.Integrations.mindsDBPasswordLabel, + integrationsMindsDBPasswordPlaceholder: localize.Integrations.mindsDBPasswordPlaceholder, + integrationsSQLServerNameLabel: localize.Integrations.sqlServerNameLabel, + integrationsSQLServerNamePlaceholder: localize.Integrations.sqlServerNamePlaceholder, + integrationsSQLServerHostLabel: localize.Integrations.sqlServerHostLabel, + integrationsSQLServerHostPlaceholder: localize.Integrations.sqlServerHostPlaceholder, + integrationsSQLServerPortLabel: localize.Integrations.sqlServerPortLabel, + integrationsSQLServerDatabaseLabel: localize.Integrations.sqlServerDatabaseLabel, + integrationsSQLServerDatabasePlaceholder: localize.Integrations.sqlServerDatabasePlaceholder, + integrationsSQLServerUsernameLabel: localize.Integrations.sqlServerUsernameLabel, + integrationsSQLServerUsernamePlaceholder: localize.Integrations.sqlServerUsernamePlaceholder, + integrationsSQLServerPasswordLabel: localize.Integrations.sqlServerPasswordLabel, + integrationsSQLServerPasswordPlaceholder: localize.Integrations.sqlServerPasswordPlaceholder, + integrationsTrinoNameLabel: localize.Integrations.trinoNameLabel, + integrationsTrinoNamePlaceholder: localize.Integrations.trinoNamePlaceholder, + integrationsTrinoHostLabel: localize.Integrations.trinoHostLabel, + integrationsTrinoHostPlaceholder: localize.Integrations.trinoHostPlaceholder, + integrationsTrinoPortLabel: localize.Integrations.trinoPortLabel, + integrationsTrinoDatabaseLabel: localize.Integrations.trinoDatabaseLabel, + integrationsTrinoDatabasePlaceholder: localize.Integrations.trinoDatabasePlaceholder, + integrationsTrinoUsernameLabel: localize.Integrations.trinoUsernameLabel, + integrationsTrinoUsernamePlaceholder: localize.Integrations.trinoUsernamePlaceholder, + integrationsTrinoPasswordLabel: localize.Integrations.trinoPasswordLabel, + integrationsTrinoPasswordPlaceholder: localize.Integrations.trinoPasswordPlaceholder, integrationsUnnamedIntegration: localize.Integrations.unnamedIntegration('{0}'), integrationsUnsupportedIntegrationType: localize.Integrations.unsupportedIntegrationType('{0}') }; diff --git a/src/notebooks/deepnote/sqlCellStatusBarProvider.ts b/src/notebooks/deepnote/sqlCellStatusBarProvider.ts index 7d905b2ed7..b7fade350f 100644 --- a/src/notebooks/deepnote/sqlCellStatusBarProvider.ts +++ b/src/notebooks/deepnote/sqlCellStatusBarProvider.ts @@ -440,12 +440,40 @@ export class SqlCellStatusBarProvider implements NotebookCellStatusBarItemProvid private getIntegrationTypeLabel(type: ConfigurableDatabaseIntegrationType): string { switch (type) { - case 'pgsql': - return l10n.t('PostgreSQL'); + 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); } diff --git a/src/platform/common/utils/localize.ts b/src/platform/common/utils/localize.ts index 73ae5d2c09..ad22d9bd38 100644 --- a/src/platform/common/utils/localize.ts +++ b/src/platform/common/utils/localize.ts @@ -837,6 +837,21 @@ export namespace Integrations { export const postgresTypeLabel = l10n.t('PostgreSQL'); export const bigQueryTypeLabel = l10n.t('BigQuery'); export const snowflakeTypeLabel = l10n.t('Snowflake'); + export const alloyDBTypeLabel = l10n.t('AlloyDB'); + export const athenaTypeLabel = l10n.t('Amazon Athena'); + export const clickHouseTypeLabel = l10n.t('ClickHouse'); + export const databricksTypeLabel = l10n.t('Databricks'); + export const dremioTypeLabel = l10n.t('Dremio'); + export const mariaDBTypeLabel = l10n.t('MariaDB'); + export const materializeTypeLabel = l10n.t('Materialize'); + export const mindsDBTypeLabel = l10n.t('MindsDB'); + export const mongoDBTypeLabel = l10n.t('MongoDB'); + 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 trinoTypeLabel = l10n.t('Trino'); // PostgreSQL form strings export const postgresNameLabel = l10n.t('Name (optional)'); @@ -899,6 +914,200 @@ export namespace Integrations { export const snowflakeRolePlaceholder = l10n.t(''); export const snowflakeWarehouseLabel = l10n.t('Warehouse (optional)'); export const snowflakeWarehousePlaceholder = l10n.t(''); + + // MySQL form strings + export const mySQLNameLabel = l10n.t('Name (optional)'); + export const mySQLNamePlaceholder = l10n.t('My MySQL Database'); + export const mySQLHostLabel = l10n.t('Host'); + export const mySQLHostPlaceholder = l10n.t('localhost'); + export const mySQLPortLabel = l10n.t('Port'); + export const mySQLDatabaseLabel = l10n.t('Database'); + export const mySQLDatabasePlaceholder = l10n.t('my_database'); + export const mySQLUsernameLabel = l10n.t('Username'); + export const mySQLUsernamePlaceholder = l10n.t('username'); + export const mySQLPasswordLabel = l10n.t('Password'); + export const mySQLPasswordPlaceholder = l10n.t('••••••••'); + + // MariaDB form strings + export const mariaDBNameLabel = l10n.t('Name (optional)'); + export const mariaDBNamePlaceholder = l10n.t('My MariaDB Database'); + export const mariaDBHostLabel = l10n.t('Host'); + export const mariaDBHostPlaceholder = l10n.t('localhost'); + export const mariaDBPortLabel = l10n.t('Port'); + export const mariaDBDatabaseLabel = l10n.t('Database'); + export const mariaDBDatabasePlaceholder = l10n.t('my_database'); + export const mariaDBUsernameLabel = l10n.t('Username'); + export const mariaDBUsernamePlaceholder = l10n.t('username'); + export const mariaDBPasswordLabel = l10n.t('Password'); + export const mariaDBPasswordPlaceholder = l10n.t('••••••••'); + + // Athena form strings + export const athenaNameLabel = l10n.t('Name (optional)'); + export const athenaNamePlaceholder = l10n.t('My Athena Database'); + export const athenaAccessKeyIdLabel = l10n.t('AWS Access Key ID'); + export const athenaAccessKeyIdPlaceholder = l10n.t('AKIAIOSFODNN7EXAMPLE'); + export const athenaSecretAccessKeyLabel = l10n.t('AWS Secret Access Key'); + export const athenaSecretAccessKeyPlaceholder = l10n.t('••••••••'); + export const athenaRegionLabel = l10n.t('AWS Region'); + export const athenaRegionPlaceholder = l10n.t('us-east-1'); + export const athenaS3OutputPathLabel = l10n.t('S3 Output Path'); + export const athenaS3OutputPathPlaceholder = l10n.t('s3://my-bucket/path/'); + export const athenaWorkgroupLabel = l10n.t('Workgroup (optional)'); + export const athenaWorkgroupPlaceholder = l10n.t('primary'); + + // Databricks form strings + export const databricksNameLabel = l10n.t('Name (optional)'); + export const databricksNamePlaceholder = l10n.t('My Databricks Workspace'); + export const databricksHostLabel = l10n.t('Server Hostname'); + export const databricksHostPlaceholder = l10n.t('dbc-1234abcd-5678.cloud.databricks.com'); + export const databricksHttpPathLabel = l10n.t('HTTP Path'); + export const databricksHttpPathPlaceholder = l10n.t('/sql/1.0/warehouses/abc123'); + export const databricksTokenLabel = l10n.t('Access Token'); + export const databricksTokenPlaceholder = l10n.t('••••••••'); + export const databricksPortLabel = l10n.t('Port'); + export const databricksCatalogLabel = l10n.t('Catalog (optional)'); + export const databricksCatalogPlaceholder = l10n.t('main'); + export const databricksSchemaLabel = l10n.t('Schema (optional)'); + export const databricksSchemaPlaceholder = l10n.t('default'); + + // Dremio form strings + export const dremioNameLabel = l10n.t('Name (optional)'); + export const dremioNamePlaceholder = l10n.t('My Dremio Instance'); + export const dremioHostLabel = l10n.t('Host'); + export const dremioHostPlaceholder = l10n.t('dremio.example.com'); + export const dremioPortLabel = l10n.t('Port'); + export const dremioSchemaLabel = l10n.t('Schema'); + export const dremioSchemaPlaceholder = l10n.t('my_schema'); + export const dremioTokenLabel = l10n.t('Personal Access Token'); + export const dremioTokenPlaceholder = l10n.t('••••••••'); + + // MongoDB form strings + export const mongoDBNameLabel = l10n.t('Name (optional)'); + export const mongoDBNamePlaceholder = l10n.t('My MongoDB Database'); + export const mongoDBConnectionStringLabel = l10n.t('Connection String'); + export const mongoDBConnectionStringPlaceholder = l10n.t('mongodb://username:password@host:port/database'); + export const mongoDBConnectionStringHelp = l10n.t( + 'Enter your MongoDB connection string. Example: mongodb://user:pass@host:27017/mydb or mongodb+srv://user:pass@cluster.mongodb.net/mydb' + ); + + // Redshift form strings + export const redshiftNameLabel = l10n.t('Name (optional)'); + export const redshiftNamePlaceholder = l10n.t('My Redshift Cluster'); + export const redshiftAuthMethodLabel = l10n.t('Authentication Method'); + export const redshiftAuthMethodUsernamePassword = l10n.t('Username and Password'); + export const redshiftAuthMethodIndividualCredentials = l10n.t('Individual Credentials (IAM)'); + export const redshiftAuthMethodHelp = l10n.t( + 'Individual Credentials uses your AWS credentials configured in the environment.' + ); + export const redshiftHostLabel = l10n.t('Cluster Endpoint'); + export const redshiftHostPlaceholder = l10n.t('my-cluster.abc123.us-east-1.redshift.amazonaws.com'); + export const redshiftPortLabel = l10n.t('Port'); + export const redshiftDatabaseLabel = l10n.t('Database'); + export const redshiftDatabasePlaceholder = l10n.t('dev'); + export const redshiftUsernameLabel = l10n.t('Username'); + export const redshiftUsernamePlaceholder = l10n.t('admin'); + export const redshiftPasswordLabel = l10n.t('Password'); + export const redshiftPasswordPlaceholder = l10n.t('••••••••'); + + // Spanner form strings + export const spannerNameLabel = l10n.t('Name (optional)'); + export const spannerNamePlaceholder = l10n.t('My Spanner Database'); + export const spannerInstanceLabel = l10n.t('Instance ID'); + export const spannerInstancePlaceholder = l10n.t('my-instance'); + export const spannerDatabaseLabel = l10n.t('Database'); + export const spannerDatabasePlaceholder = l10n.t('my-database'); + export const spannerServiceAccountLabel = l10n.t('Service Account JSON'); + export const spannerServiceAccountPlaceholder = l10n.t( + '{\n "type": "service_account",\n "project_id": "...",\n ...\n}' + ); + export const spannerServiceAccountHelp = l10n.t( + 'Paste the contents of your Google Cloud service account JSON key file.' + ); + export const spannerServiceAccountInvalidJson = l10n.t('Invalid JSON format'); + export const spannerDataBoostLabel = l10n.t('Enable Data Boost'); + export const spannerDataBoostHelp = l10n.t( + 'Data Boost provides independent compute resources for analytics queries.' + ); + + // AlloyDB form strings + export const alloyDBNameLabel = l10n.t('Name (optional)'); + export const alloyDBNamePlaceholder = l10n.t('My AlloyDB Database'); + export const alloyDBHostLabel = l10n.t('Host'); + export const alloyDBHostPlaceholder = l10n.t('localhost'); + export const alloyDBPortLabel = l10n.t('Port'); + export const alloyDBDatabaseLabel = l10n.t('Database'); + export const alloyDBDatabasePlaceholder = l10n.t('my_database'); + export const alloyDBUsernameLabel = l10n.t('Username'); + export const alloyDBUsernamePlaceholder = l10n.t('username'); + export const alloyDBPasswordLabel = l10n.t('Password'); + export const alloyDBPasswordPlaceholder = l10n.t('••••••••'); + + // ClickHouse form strings + export const clickHouseNameLabel = l10n.t('Name (optional)'); + export const clickHouseNamePlaceholder = l10n.t('My ClickHouse Database'); + export const clickHouseHostLabel = l10n.t('Host'); + export const clickHouseHostPlaceholder = l10n.t('localhost'); + export const clickHousePortLabel = l10n.t('Port'); + export const clickHouseDatabaseLabel = l10n.t('Database'); + export const clickHouseDatabasePlaceholder = l10n.t('my_database'); + export const clickHouseUsernameLabel = l10n.t('Username'); + export const clickHouseUsernamePlaceholder = l10n.t('username'); + export const clickHousePasswordLabel = l10n.t('Password'); + export const clickHousePasswordPlaceholder = l10n.t('••••••••'); + + // Materialize form strings + export const materializeNameLabel = l10n.t('Name (optional)'); + export const materializeNamePlaceholder = l10n.t('My Materialize Database'); + export const materializeHostLabel = l10n.t('Host'); + export const materializeHostPlaceholder = l10n.t('localhost'); + export const materializePortLabel = l10n.t('Port'); + export const materializeDatabaseLabel = l10n.t('Database'); + export const materializeDatabasePlaceholder = l10n.t('my_database'); + export const materializeClusterLabel = l10n.t('Cluster'); + export const materializeClusterPlaceholder = l10n.t('quickstart'); + export const materializeUsernameLabel = l10n.t('Username'); + export const materializeUsernamePlaceholder = l10n.t('username'); + export const materializePasswordLabel = l10n.t('Password'); + export const materializePasswordPlaceholder = l10n.t('••••••••'); + + // MindsDB form strings + export const mindsDBNameLabel = l10n.t('Name (optional)'); + export const mindsDBNamePlaceholder = l10n.t('My MindsDB Database'); + export const mindsDBHostLabel = l10n.t('Host'); + export const mindsDBHostPlaceholder = l10n.t('localhost'); + export const mindsDBPortLabel = l10n.t('Port'); + export const mindsDBDatabaseLabel = l10n.t('Database'); + export const mindsDBDatabasePlaceholder = l10n.t('my_database'); + export const mindsDBUsernameLabel = l10n.t('Username'); + export const mindsDBUsernamePlaceholder = l10n.t('username'); + export const mindsDBPasswordLabel = l10n.t('Password'); + export const mindsDBPasswordPlaceholder = l10n.t('••••••••'); + + // SQL Server form strings + export const sqlServerNameLabel = l10n.t('Name (optional)'); + export const sqlServerNamePlaceholder = l10n.t('My SQL Server Database'); + export const sqlServerHostLabel = l10n.t('Host'); + export const sqlServerHostPlaceholder = l10n.t('localhost'); + export const sqlServerPortLabel = l10n.t('Port'); + export const sqlServerDatabaseLabel = l10n.t('Database'); + export const sqlServerDatabasePlaceholder = l10n.t('my_database'); + export const sqlServerUsernameLabel = l10n.t('Username'); + export const sqlServerUsernamePlaceholder = l10n.t('username'); + export const sqlServerPasswordLabel = l10n.t('Password'); + export const sqlServerPasswordPlaceholder = l10n.t('••••••••'); + + // Trino form strings + export const trinoNameLabel = l10n.t('Name (optional)'); + export const trinoNamePlaceholder = l10n.t('My Trino Database'); + export const trinoHostLabel = l10n.t('Host'); + export const trinoHostPlaceholder = l10n.t('localhost'); + export const trinoPortLabel = l10n.t('Port'); + export const trinoDatabaseLabel = l10n.t('Database'); + export const trinoDatabasePlaceholder = l10n.t('my_database'); + export const trinoUsernameLabel = l10n.t('Username'); + export const trinoUsernamePlaceholder = l10n.t('username'); + export const trinoPasswordLabel = l10n.t('Password'); + export const trinoPasswordPlaceholder = l10n.t('••••••••'); } export namespace SelectInputSettings { diff --git a/src/webviews/webview-side/integrations/AlloyDBForm.tsx b/src/webviews/webview-side/integrations/AlloyDBForm.tsx new file mode 100644 index 0000000000..fbdc65318a --- /dev/null +++ b/src/webviews/webview-side/integrations/AlloyDBForm.tsx @@ -0,0 +1,196 @@ +import * as React from 'react'; +import { format, getLocString } from '../react-common/locReactSide'; +import { DatabaseIntegrationConfig } from '@deepnote/database-integrations'; + +export interface IAlloyDBFormProps { + integrationId: string; + existingConfig: Extract | null; + defaultName?: string; + onSave: (config: Extract) => void; + onCancel: () => void; +} + +function createEmptyAlloyDBConfig( + integrationId: string, + defaultName?: string +): Extract { + const unnamedIntegration = getLocString('integrationsUnnamedIntegration', 'Unnamed Integration ({0})'); + + return { + id: integrationId, + name: (defaultName || format(unnamedIntegration, integrationId)).trim(), + type: 'alloydb', + metadata: { + host: '', + user: '', + password: '', + database: '' + } + }; +} + +export const AlloyDBForm: React.FC = ({ + integrationId, + existingConfig, + defaultName, + onSave, + onCancel +}) => { + const [pendingConfig, setPendingConfig] = React.useState>( + () => existingConfig || createEmptyAlloyDBConfig(integrationId, defaultName) + ); + + React.useEffect(() => { + if (existingConfig) { + setPendingConfig(existingConfig); + } + }, [existingConfig]); + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + onSave(pendingConfig); + }; + + const handleNameChange = (e: React.ChangeEvent) => { + setPendingConfig({ ...pendingConfig, name: e.target.value }); + }; + + const handleHostChange = (e: React.ChangeEvent) => { + setPendingConfig({ + ...pendingConfig, + metadata: { ...pendingConfig.metadata, host: e.target.value } + }); + }; + + const handlePortChange = (e: React.ChangeEvent) => { + setPendingConfig({ + ...pendingConfig, + metadata: { ...pendingConfig.metadata, port: e.target.value || undefined } + }); + }; + + const handleDatabaseChange = (e: React.ChangeEvent) => { + setPendingConfig({ + ...pendingConfig, + metadata: { ...pendingConfig.metadata, database: e.target.value } + }); + }; + + const handleUsernameChange = (e: React.ChangeEvent) => { + setPendingConfig({ + ...pendingConfig, + metadata: { ...pendingConfig.metadata, user: e.target.value } + }); + }; + + const handlePasswordChange = (e: React.ChangeEvent) => { + setPendingConfig({ + ...pendingConfig, + metadata: { ...pendingConfig.metadata, password: e.target.value } + }); + }; + + return ( +
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+
+ ); +}; + diff --git a/src/webviews/webview-side/integrations/AthenaForm.tsx b/src/webviews/webview-side/integrations/AthenaForm.tsx new file mode 100644 index 0000000000..7026a2c84a --- /dev/null +++ b/src/webviews/webview-side/integrations/AthenaForm.tsx @@ -0,0 +1,184 @@ +import * as React from 'react'; +import { format, getLocString } from '../react-common/locReactSide'; +import { DatabaseIntegrationConfig } from '@deepnote/database-integrations'; + +function createEmptyAthenaConfig(params: { + id: string; + name?: string; +}): Extract { + const unnamedIntegration = getLocString('integrationsUnnamedIntegration', 'Unnamed Integration ({0})'); + + return { + id: params.id, + name: (params.name || format(unnamedIntegration, params.id)).trim(), + type: 'athena', + metadata: { + access_key_id: '', + secret_access_key: '', + region: '', + s3_output_path: '', + workgroup: '' + } + }; +} + +export interface IAthenaFormProps { + integrationId: string; + existingConfig: Extract | null; + defaultName?: string; + onSave: (config: Extract) => void; + onCancel: () => void; +} + +export const AthenaForm: React.FC = ({ + integrationId, + existingConfig, + defaultName, + onSave, + onCancel +}) => { + const [pendingConfig, setPendingConfig] = React.useState>( + existingConfig + ? structuredClone(existingConfig) + : createEmptyAthenaConfig({ id: integrationId, name: defaultName }) + ); + + React.useEffect(() => { + setPendingConfig( + existingConfig + ? structuredClone(existingConfig) + : createEmptyAthenaConfig({ id: integrationId, name: defaultName }) + ); + }, [existingConfig, integrationId, defaultName]); + + const handleNameChange = (e: React.ChangeEvent) => { + setPendingConfig((prev) => ({ ...prev, name: e.target.value })); + }; + + const handleAccessKeyIdChange = (e: React.ChangeEvent) => { + setPendingConfig((prev) => ({ ...prev, metadata: { ...prev.metadata, access_key_id: e.target.value } })); + }; + + const handleSecretAccessKeyChange = (e: React.ChangeEvent) => { + setPendingConfig((prev) => ({ ...prev, metadata: { ...prev.metadata, secret_access_key: e.target.value } })); + }; + + const handleRegionChange = (e: React.ChangeEvent) => { + setPendingConfig((prev) => ({ ...prev, metadata: { ...prev.metadata, region: e.target.value } })); + }; + + const handleS3OutputPathChange = (e: React.ChangeEvent) => { + setPendingConfig((prev) => ({ ...prev, metadata: { ...prev.metadata, s3_output_path: e.target.value } })); + }; + + const handleWorkgroupChange = (e: React.ChangeEvent) => { + setPendingConfig((prev) => ({ ...prev, metadata: { ...prev.metadata, workgroup: e.target.value } })); + }; + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + onSave(pendingConfig); + }; + + return ( +
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+
+ ); +}; + diff --git a/src/webviews/webview-side/integrations/ClickHouseForm.tsx b/src/webviews/webview-side/integrations/ClickHouseForm.tsx new file mode 100644 index 0000000000..f5a1caadf4 --- /dev/null +++ b/src/webviews/webview-side/integrations/ClickHouseForm.tsx @@ -0,0 +1,191 @@ +import * as React from 'react'; +import { format, getLocString } from '../react-common/locReactSide'; +import { DatabaseIntegrationConfig } from '@deepnote/database-integrations'; + +export interface IClickHouseFormProps { + integrationId: string; + existingConfig: Extract | null; + defaultName?: string; + onSave: (config: Extract) => void; + onCancel: () => void; +} + +function createEmptyClickHouseConfig( + integrationId: string, + defaultName?: string +): Extract { + const unnamedIntegration = getLocString('integrationsUnnamedIntegration', 'Unnamed Integration ({0})'); + + return { + id: integrationId, + name: (defaultName || format(unnamedIntegration, integrationId)).trim(), + type: 'clickhouse', + metadata: { + host: '', + user: '', + database: '' + } + }; +} + +export const ClickHouseForm: React.FC = ({ + integrationId, + existingConfig, + defaultName, + onSave, + onCancel +}) => { + const [pendingConfig, setPendingConfig] = React.useState>( + () => existingConfig || createEmptyClickHouseConfig(integrationId, defaultName) + ); + + React.useEffect(() => { + if (existingConfig) { + setPendingConfig(existingConfig); + } + }, [existingConfig]); + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + onSave(pendingConfig); + }; + + const handleNameChange = (e: React.ChangeEvent) => { + setPendingConfig({ ...pendingConfig, name: e.target.value }); + }; + + const handleHostChange = (e: React.ChangeEvent) => { + setPendingConfig({ + ...pendingConfig, + metadata: { ...pendingConfig.metadata, host: e.target.value } + }); + }; + + const handlePortChange = (e: React.ChangeEvent) => { + setPendingConfig({ + ...pendingConfig, + metadata: { ...pendingConfig.metadata, port: e.target.value || undefined } + }); + }; + + const handleDatabaseChange = (e: React.ChangeEvent) => { + setPendingConfig({ + ...pendingConfig, + metadata: { ...pendingConfig.metadata, database: e.target.value } + }); + }; + + const handleUsernameChange = (e: React.ChangeEvent) => { + setPendingConfig({ + ...pendingConfig, + metadata: { ...pendingConfig.metadata, user: e.target.value } + }); + }; + + const handlePasswordChange = (e: React.ChangeEvent) => { + setPendingConfig({ + ...pendingConfig, + metadata: { ...pendingConfig.metadata, password: e.target.value || undefined } + }); + }; + + return ( +
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+
+ ); +}; + diff --git a/src/webviews/webview-side/integrations/ConfigurationForm.tsx b/src/webviews/webview-side/integrations/ConfigurationForm.tsx index ac8dfc256e..62e4fc94b8 100644 --- a/src/webviews/webview-side/integrations/ConfigurationForm.tsx +++ b/src/webviews/webview-side/integrations/ConfigurationForm.tsx @@ -1,8 +1,22 @@ import * as React from 'react'; import { format, getLocString } from '../react-common/locReactSide'; -import { PostgresForm } from './PostgresForm'; +import { AlloyDBForm } from './AlloyDBForm'; +import { AthenaForm } from './AthenaForm'; import { BigQueryForm } from './BigQueryForm'; +import { ClickHouseForm } from './ClickHouseForm'; +import { DatabricksForm } from './DatabricksForm'; +import { DremioForm } from './DremioForm'; +import { MariaDBForm } from './MariaDBForm'; +import { MaterializeForm } from './MaterializeForm'; +import { MindsDBForm } from './MindsDBForm'; +import { MongoDBForm } from './MongoDBForm'; +import { MySQLForm } from './MySQLForm'; +import { PostgresForm } from './PostgresForm'; +import { RedshiftForm } from './RedshiftForm'; import { SnowflakeForm } from './SnowflakeForm'; +import { SpannerForm } from './SpannerForm'; +import { SQLServerForm } from './SQLServerForm'; +import { TrinoForm } from './TrinoForm'; import { ConfigurableDatabaseIntegrationConfig, ConfigurableDatabaseIntegrationType } from './types'; export interface IConfigurationFormProps { @@ -70,6 +84,146 @@ export const ConfigurationForm: React.FC = ({ onCancel={onCancel} /> ); + case 'mysql': + return ( + + ); + case 'mariadb': + return ( + + ); + case 'alloydb': + return ( + + ); + case 'clickhouse': + return ( + + ); + case 'materialize': + return ( + + ); + case 'mindsdb': + return ( + + ); + case 'sql-server': + return ( + + ); + case 'trino': + return ( + + ); + case 'athena': + return ( + + ); + case 'databricks': + return ( + + ); + case 'dremio': + return ( + + ); + case 'mongodb': + return ( + + ); + case 'redshift': + return ( + + ); + case 'spanner': + return ( + + ); default: { const unsupportedMessage = getLocString( 'integrationsUnsupportedIntegrationType', diff --git a/src/webviews/webview-side/integrations/DatabricksForm.tsx b/src/webviews/webview-side/integrations/DatabricksForm.tsx new file mode 100644 index 0000000000..03d9c92612 --- /dev/null +++ b/src/webviews/webview-side/integrations/DatabricksForm.tsx @@ -0,0 +1,197 @@ +import * as React from 'react'; +import { format, getLocString } from '../react-common/locReactSide'; +import { DatabaseIntegrationConfig } from '@deepnote/database-integrations'; + +function createEmptyDatabricksConfig(params: { + id: string; + name?: string; +}): Extract { + const unnamedIntegration = getLocString('integrationsUnnamedIntegration', 'Unnamed Integration ({0})'); + + return { + id: params.id, + name: (params.name || format(unnamedIntegration, params.id)).trim(), + type: 'databricks', + metadata: { + host: '', + port: '443', + httpPath: '', + token: '', + schema: '', + catalog: '' + } + }; +} + +export interface IDatabricksFormProps { + integrationId: string; + existingConfig: Extract | null; + defaultName?: string; + onSave: (config: Extract) => void; + onCancel: () => void; +} + +export const DatabricksForm: React.FC = ({ + integrationId, + existingConfig, + defaultName, + onSave, + onCancel +}) => { + const [pendingConfig, setPendingConfig] = React.useState>( + existingConfig + ? structuredClone(existingConfig) + : createEmptyDatabricksConfig({ id: integrationId, name: defaultName }) + ); + + React.useEffect(() => { + setPendingConfig( + existingConfig + ? structuredClone(existingConfig) + : createEmptyDatabricksConfig({ id: integrationId, name: defaultName }) + ); + }, [existingConfig, integrationId, defaultName]); + + const handleNameChange = (e: React.ChangeEvent) => { + setPendingConfig((prev) => ({ ...prev, name: e.target.value })); + }; + + const handleHostChange = (e: React.ChangeEvent) => { + setPendingConfig((prev) => ({ ...prev, metadata: { ...prev.metadata, host: e.target.value } })); + }; + + const handlePortChange = (e: React.ChangeEvent) => { + setPendingConfig((prev) => ({ ...prev, metadata: { ...prev.metadata, port: e.target.value } })); + }; + + const handleHttpPathChange = (e: React.ChangeEvent) => { + setPendingConfig((prev) => ({ ...prev, metadata: { ...prev.metadata, httpPath: e.target.value } })); + }; + + const handleTokenChange = (e: React.ChangeEvent) => { + setPendingConfig((prev) => ({ ...prev, metadata: { ...prev.metadata, token: e.target.value } })); + }; + + const handleSchemaChange = (e: React.ChangeEvent) => { + setPendingConfig((prev) => ({ ...prev, metadata: { ...prev.metadata, schema: e.target.value } })); + }; + + const handleCatalogChange = (e: React.ChangeEvent) => { + setPendingConfig((prev) => ({ ...prev, metadata: { ...prev.metadata, catalog: e.target.value } })); + }; + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + onSave(pendingConfig); + }; + + return ( +
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+
+ ); +}; + diff --git a/src/webviews/webview-side/integrations/DremioForm.tsx b/src/webviews/webview-side/integrations/DremioForm.tsx new file mode 100644 index 0000000000..f7e140cd80 --- /dev/null +++ b/src/webviews/webview-side/integrations/DremioForm.tsx @@ -0,0 +1,163 @@ +import * as React from 'react'; +import { format, getLocString } from '../react-common/locReactSide'; +import { DatabaseIntegrationConfig } from '@deepnote/database-integrations'; + +function createEmptyDremioConfig(params: { + id: string; + name?: string; +}): Extract { + const unnamedIntegration = getLocString('integrationsUnnamedIntegration', 'Unnamed Integration ({0})'); + + return { + id: params.id, + name: (params.name || format(unnamedIntegration, params.id)).trim(), + type: 'dremio', + metadata: { + host: '', + port: '9047', + schema: '', + token: '' + } + }; +} + +export interface IDremioFormProps { + integrationId: string; + existingConfig: Extract | null; + defaultName?: string; + onSave: (config: Extract) => void; + onCancel: () => void; +} + +export const DremioForm: React.FC = ({ + integrationId, + existingConfig, + defaultName, + onSave, + onCancel +}) => { + const [pendingConfig, setPendingConfig] = React.useState>( + existingConfig + ? structuredClone(existingConfig) + : createEmptyDremioConfig({ id: integrationId, name: defaultName }) + ); + + React.useEffect(() => { + setPendingConfig( + existingConfig + ? structuredClone(existingConfig) + : createEmptyDremioConfig({ id: integrationId, name: defaultName }) + ); + }, [existingConfig, integrationId, defaultName]); + + const handleNameChange = (e: React.ChangeEvent) => { + setPendingConfig((prev) => ({ ...prev, name: e.target.value })); + }; + + const handleHostChange = (e: React.ChangeEvent) => { + setPendingConfig((prev) => ({ ...prev, metadata: { ...prev.metadata, host: e.target.value } })); + }; + + const handlePortChange = (e: React.ChangeEvent) => { + setPendingConfig((prev) => ({ ...prev, metadata: { ...prev.metadata, port: e.target.value } })); + }; + + const handleSchemaChange = (e: React.ChangeEvent) => { + setPendingConfig((prev) => ({ ...prev, metadata: { ...prev.metadata, schema: e.target.value } })); + }; + + const handleTokenChange = (e: React.ChangeEvent) => { + setPendingConfig((prev) => ({ ...prev, metadata: { ...prev.metadata, token: e.target.value } })); + }; + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + onSave(pendingConfig); + }; + + return ( +
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+
+ ); +}; + diff --git a/src/webviews/webview-side/integrations/IntegrationItem.tsx b/src/webviews/webview-side/integrations/IntegrationItem.tsx index 2eaad767b1..20332bddb6 100644 --- a/src/webviews/webview-side/integrations/IntegrationItem.tsx +++ b/src/webviews/webview-side/integrations/IntegrationItem.tsx @@ -10,12 +10,40 @@ export interface IIntegrationItemProps { const getIntegrationTypeLabel = (type: ConfigurableDatabaseIntegrationType): string => { switch (type) { - case 'pgsql': - return getLocString('integrationsPostgresTypeLabel', 'PostgreSQL'); + case 'alloydb': + return getLocString('integrationsAlloyDBTypeLabel', 'AlloyDB'); + case 'athena': + return getLocString('integrationsAthenaTypeLabel', 'Amazon Athena'); case 'big-query': return getLocString('integrationsBigQueryTypeLabel', 'BigQuery'); + case 'clickhouse': + return getLocString('integrationsClickHouseTypeLabel', 'ClickHouse'); + case 'databricks': + return getLocString('integrationsDatabricksTypeLabel', 'Databricks'); + case 'dremio': + return getLocString('integrationsDremioTypeLabel', 'Dremio'); + case 'mariadb': + return getLocString('integrationsMariaDBTypeLabel', 'MariaDB'); + case 'materialize': + return getLocString('integrationsMaterializeTypeLabel', 'Materialize'); + case 'mindsdb': + return getLocString('integrationsMindsDBTypeLabel', 'MindsDB'); + case 'mongodb': + return getLocString('integrationsMongoDBTypeLabel', 'MongoDB'); + case 'mysql': + return getLocString('integrationsMySQLTypeLabel', 'MySQL'); + case 'pgsql': + return getLocString('integrationsPostgresTypeLabel', 'PostgreSQL'); + case 'redshift': + return getLocString('integrationsRedshiftTypeLabel', 'Amazon Redshift'); case 'snowflake': return getLocString('integrationsSnowflakeTypeLabel', 'Snowflake'); + case 'spanner': + return getLocString('integrationsSpannerTypeLabel', 'Google Cloud Spanner'); + case 'sql-server': + return getLocString('integrationsSQLServerTypeLabel', 'SQL Server'); + case 'trino': + return getLocString('integrationsTrinoTypeLabel', 'Trino'); default: return type; } diff --git a/src/webviews/webview-side/integrations/MariaDBForm.tsx b/src/webviews/webview-side/integrations/MariaDBForm.tsx new file mode 100644 index 0000000000..aeab80a964 --- /dev/null +++ b/src/webviews/webview-side/integrations/MariaDBForm.tsx @@ -0,0 +1,184 @@ +import * as React from 'react'; +import { format, getLocString } from '../react-common/locReactSide'; +import { DatabaseIntegrationConfig } from '@deepnote/database-integrations'; + +function createEmptyMariaDBConfig(params: { + id: string; + name?: string; +}): Extract { + const unnamedIntegration = getLocString('integrationsUnnamedIntegration', 'Unnamed Integration ({0})'); + + return { + id: params.id, + name: (params.name || format(unnamedIntegration, params.id)).trim(), + type: 'mariadb', + metadata: { + host: '', + port: '3306', + database: '', + user: '', + password: '' + } + }; +} + +export interface IMariaDBFormProps { + integrationId: string; + existingConfig: Extract | null; + defaultName?: string; + onSave: (config: Extract) => void; + onCancel: () => void; +} + +export const MariaDBForm: React.FC = ({ + integrationId, + existingConfig, + defaultName, + onSave, + onCancel +}) => { + const [pendingConfig, setPendingConfig] = React.useState>( + existingConfig + ? structuredClone(existingConfig) + : createEmptyMariaDBConfig({ id: integrationId, name: defaultName }) + ); + + React.useEffect(() => { + setPendingConfig( + existingConfig + ? structuredClone(existingConfig) + : createEmptyMariaDBConfig({ id: integrationId, name: defaultName }) + ); + }, [existingConfig, integrationId, defaultName]); + + const handleNameChange = (e: React.ChangeEvent) => { + setPendingConfig((prev) => ({ ...prev, name: e.target.value })); + }; + + const handleHostChange = (e: React.ChangeEvent) => { + setPendingConfig((prev) => ({ ...prev, metadata: { ...prev.metadata, host: e.target.value } })); + }; + + const handlePortChange = (e: React.ChangeEvent) => { + setPendingConfig((prev) => ({ ...prev, metadata: { ...prev.metadata, port: e.target.value } })); + }; + + const handleDatabaseChange = (e: React.ChangeEvent) => { + setPendingConfig((prev) => ({ ...prev, metadata: { ...prev.metadata, database: e.target.value } })); + }; + + const handleUsernameChange = (e: React.ChangeEvent) => { + setPendingConfig((prev) => ({ ...prev, metadata: { ...prev.metadata, user: e.target.value } })); + }; + + const handlePasswordChange = (e: React.ChangeEvent) => { + setPendingConfig((prev) => ({ ...prev, metadata: { ...prev.metadata, password: e.target.value } })); + }; + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + onSave(pendingConfig); + }; + + return ( +
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+
+ ); +}; + diff --git a/src/webviews/webview-side/integrations/MaterializeForm.tsx b/src/webviews/webview-side/integrations/MaterializeForm.tsx new file mode 100644 index 0000000000..9934f9a123 --- /dev/null +++ b/src/webviews/webview-side/integrations/MaterializeForm.tsx @@ -0,0 +1,223 @@ +import * as React from 'react'; +import { format, getLocString } from '../react-common/locReactSide'; +import { DatabaseIntegrationConfig } from '@deepnote/database-integrations'; + +export interface IMaterializeFormProps { + integrationId: string; + existingConfig: Extract | null; + defaultName?: string; + onSave: (config: Extract) => void; + onCancel: () => void; +} + +function createEmptyMaterializeConfig( + integrationId: string, + defaultName?: string +): Extract { + const unnamedIntegration = getLocString('integrationsUnnamedIntegration', 'Unnamed Integration ({0})'); + + return { + id: integrationId, + name: (defaultName || format(unnamedIntegration, integrationId)).trim(), + type: 'materialize', + metadata: { + host: '', + user: '', + password: '', + database: '', + cluster: '' + } + }; +} + +export const MaterializeForm: React.FC = ({ + integrationId, + existingConfig, + defaultName, + onSave, + onCancel +}) => { + const [pendingConfig, setPendingConfig] = React.useState>( + () => existingConfig || createEmptyMaterializeConfig(integrationId, defaultName) + ); + + React.useEffect(() => { + if (existingConfig) { + setPendingConfig(existingConfig); + } + }, [existingConfig]); + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + onSave(pendingConfig); + }; + + const handleNameChange = (e: React.ChangeEvent) => { + setPendingConfig({ ...pendingConfig, name: e.target.value }); + }; + + const handleHostChange = (e: React.ChangeEvent) => { + setPendingConfig({ + ...pendingConfig, + metadata: { ...pendingConfig.metadata, host: e.target.value } + }); + }; + + const handlePortChange = (e: React.ChangeEvent) => { + setPendingConfig({ + ...pendingConfig, + metadata: { ...pendingConfig.metadata, port: e.target.value || undefined } + }); + }; + + const handleDatabaseChange = (e: React.ChangeEvent) => { + setPendingConfig({ + ...pendingConfig, + metadata: { ...pendingConfig.metadata, database: e.target.value } + }); + }; + + const handleClusterChange = (e: React.ChangeEvent) => { + setPendingConfig({ + ...pendingConfig, + metadata: { ...pendingConfig.metadata, cluster: e.target.value } + }); + }; + + const handleUsernameChange = (e: React.ChangeEvent) => { + setPendingConfig({ + ...pendingConfig, + metadata: { ...pendingConfig.metadata, user: e.target.value } + }); + }; + + const handlePasswordChange = (e: React.ChangeEvent) => { + setPendingConfig({ + ...pendingConfig, + metadata: { ...pendingConfig.metadata, password: e.target.value } + }); + }; + + return ( +
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+
+ ); +}; + diff --git a/src/webviews/webview-side/integrations/MindsDBForm.tsx b/src/webviews/webview-side/integrations/MindsDBForm.tsx new file mode 100644 index 0000000000..d4032de4cd --- /dev/null +++ b/src/webviews/webview-side/integrations/MindsDBForm.tsx @@ -0,0 +1,196 @@ +import * as React from 'react'; +import { format, getLocString } from '../react-common/locReactSide'; +import { DatabaseIntegrationConfig } from '@deepnote/database-integrations'; + +export interface IMindsDBFormProps { + integrationId: string; + existingConfig: Extract | null; + defaultName?: string; + onSave: (config: Extract) => void; + onCancel: () => void; +} + +function createEmptyMindsDBConfig( + integrationId: string, + defaultName?: string +): Extract { + const unnamedIntegration = getLocString('integrationsUnnamedIntegration', 'Unnamed Integration ({0})'); + + return { + id: integrationId, + name: (defaultName || format(unnamedIntegration, integrationId)).trim(), + type: 'mindsdb', + metadata: { + host: '', + user: '', + password: '', + database: '' + } + }; +} + +export const MindsDBForm: React.FC = ({ + integrationId, + existingConfig, + defaultName, + onSave, + onCancel +}) => { + const [pendingConfig, setPendingConfig] = React.useState>( + () => existingConfig || createEmptyMindsDBConfig(integrationId, defaultName) + ); + + React.useEffect(() => { + if (existingConfig) { + setPendingConfig(existingConfig); + } + }, [existingConfig]); + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + onSave(pendingConfig); + }; + + const handleNameChange = (e: React.ChangeEvent) => { + setPendingConfig({ ...pendingConfig, name: e.target.value }); + }; + + const handleHostChange = (e: React.ChangeEvent) => { + setPendingConfig({ + ...pendingConfig, + metadata: { ...pendingConfig.metadata, host: e.target.value } + }); + }; + + const handlePortChange = (e: React.ChangeEvent) => { + setPendingConfig({ + ...pendingConfig, + metadata: { ...pendingConfig.metadata, port: e.target.value || undefined } + }); + }; + + const handleDatabaseChange = (e: React.ChangeEvent) => { + setPendingConfig({ + ...pendingConfig, + metadata: { ...pendingConfig.metadata, database: e.target.value } + }); + }; + + const handleUsernameChange = (e: React.ChangeEvent) => { + setPendingConfig({ + ...pendingConfig, + metadata: { ...pendingConfig.metadata, user: e.target.value } + }); + }; + + const handlePasswordChange = (e: React.ChangeEvent) => { + setPendingConfig({ + ...pendingConfig, + metadata: { ...pendingConfig.metadata, password: e.target.value } + }); + }; + + return ( +
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+
+ ); +}; + diff --git a/src/webviews/webview-side/integrations/MongoDBForm.tsx b/src/webviews/webview-side/integrations/MongoDBForm.tsx new file mode 100644 index 0000000000..468999589e --- /dev/null +++ b/src/webviews/webview-side/integrations/MongoDBForm.tsx @@ -0,0 +1,112 @@ +import * as React from 'react'; +import { format, getLocString } from '../react-common/locReactSide'; +import { DatabaseIntegrationConfig } from '@deepnote/database-integrations'; + +function createEmptyMongoDBConfig(params: { + id: string; + name?: string; +}): Extract { + const unnamedIntegration = getLocString('integrationsUnnamedIntegration', 'Unnamed Integration ({0})'); + + return { + id: params.id, + name: (params.name || format(unnamedIntegration, params.id)).trim(), + type: 'mongodb', + metadata: { + connection_string: '' + } + }; +} + +export interface IMongoDBFormProps { + integrationId: string; + existingConfig: Extract | null; + defaultName?: string; + onSave: (config: Extract) => void; + onCancel: () => void; +} + +export const MongoDBForm: React.FC = ({ + integrationId, + existingConfig, + defaultName, + onSave, + onCancel +}) => { + const [pendingConfig, setPendingConfig] = React.useState>( + existingConfig + ? structuredClone(existingConfig) + : createEmptyMongoDBConfig({ id: integrationId, name: defaultName }) + ); + + React.useEffect(() => { + setPendingConfig( + existingConfig + ? structuredClone(existingConfig) + : createEmptyMongoDBConfig({ id: integrationId, name: defaultName }) + ); + }, [existingConfig, integrationId, defaultName]); + + const handleNameChange = (e: React.ChangeEvent) => { + setPendingConfig((prev) => ({ ...prev, name: e.target.value })); + }; + + const handleConnectionStringChange = (e: React.ChangeEvent) => { + setPendingConfig((prev) => ({ ...prev, metadata: { ...prev.metadata, connection_string: e.target.value } })); + }; + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + onSave(pendingConfig); + }; + + return ( +
+
+ + +
+ +
+ +