From 2a2b9d11db1ea68f1c3d60a3342a6b9bc7cdb578 Mon Sep 17 00:00:00 2001 From: jankuca Date: Mon, 27 Oct 2025 15:34:18 +0100 Subject: [PATCH 01/34] Add Snowflake SQL integration support - Add Snowflake integration type with username+password and key-pair auth methods - Create SnowflakeForm UI component with auth method switcher - Add connection string generation for both auth methods - Add all localization strings for Snowflake form fields - Update integration type mappings and labels --- src/messageTypes.ts | 29 ++ .../integrations/integrationWebview.ts | 33 +- .../deepnote/sqlCellStatusBarProvider.ts | 2 + src/platform/common/utils/localize.ts | 37 +++ .../notebooks/deepnote/integrationTypes.ts | 38 ++- ...IntegrationEnvironmentVariablesProvider.ts | 69 +++- .../integrations/ConfigurationForm.tsx | 16 +- .../integrations/IntegrationItem.tsx | 2 + .../integrations/SnowflakeForm.tsx | 301 ++++++++++++++++++ .../webview-side/integrations/types.ts | 22 +- 10 files changed, 539 insertions(+), 10 deletions(-) create mode 100644 src/webviews/webview-side/integrations/SnowflakeForm.tsx diff --git a/src/messageTypes.ts b/src/messageTypes.ts index 57d628f170..f843f88b73 100644 --- a/src/messageTypes.ts +++ b/src/messageTypes.ts @@ -182,6 +182,7 @@ export type LocalizedMessages = { // Integration type labels integrationsPostgresTypeLabel: string; integrationsBigQueryTypeLabel: string; + integrationsSnowflakeTypeLabel: string; // PostgreSQL form strings integrationsPostgresNameLabel: string; integrationsPostgresNamePlaceholder: string; @@ -204,6 +205,34 @@ export type LocalizedMessages = { integrationsBigQueryCredentialsLabel: string; integrationsBigQueryCredentialsPlaceholder: string; integrationsBigQueryCredentialsRequired: string; + // Snowflake form strings + integrationsSnowflakeNameLabel: string; + integrationsSnowflakeNamePlaceholder: string; + integrationsSnowflakeAccountLabel: string; + integrationsSnowflakeAccountPlaceholder: string; + integrationsSnowflakeAuthMethodLabel: string; + integrationsSnowflakeAuthMethodSubLabel: string; + integrationsSnowflakeAuthMethodUsernamePassword: string; + integrationsSnowflakeAuthMethodKeyPair: string; + integrationsSnowflakeUsernameLabel: string; + integrationsSnowflakeUsernamePlaceholder: string; + integrationsSnowflakePasswordLabel: string; + integrationsSnowflakePasswordPlaceholder: string; + integrationsSnowflakeServiceAccountUsernameLabel: string; + integrationsSnowflakeServiceAccountUsernameHelp: string; + integrationsSnowflakeServiceAccountUsernamePlaceholder: string; + integrationsSnowflakePrivateKeyLabel: string; + integrationsSnowflakePrivateKeyHelp: string; + integrationsSnowflakePrivateKeyPlaceholder: string; + integrationsSnowflakePrivateKeyPassphraseLabel: string; + integrationsSnowflakePrivateKeyPassphraseHelp: string; + integrationsSnowflakePrivateKeyPassphrasePlaceholder: string; + integrationsSnowflakeDatabaseLabel: string; + integrationsSnowflakeDatabasePlaceholder: string; + integrationsSnowflakeRoleLabel: string; + integrationsSnowflakeRolePlaceholder: string; + integrationsSnowflakeWarehouseLabel: string; + integrationsSnowflakeWarehousePlaceholder: 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 2f5059d787..f6345bba1d 100644 --- a/src/notebooks/deepnote/integrations/integrationWebview.ts +++ b/src/notebooks/deepnote/integrations/integrationWebview.ts @@ -131,6 +131,7 @@ export class IntegrationWebviewProvider implements IIntegrationWebviewProvider { integrationsConfigureTitle: localize.Integrations.configureTitle, integrationsPostgresTypeLabel: localize.Integrations.postgresTypeLabel, integrationsBigQueryTypeLabel: localize.Integrations.bigQueryTypeLabel, + integrationsSnowflakeTypeLabel: localize.Integrations.snowflakeTypeLabel, integrationsCancel: localize.Integrations.cancel, integrationsSave: localize.Integrations.save, integrationsRequiredField: localize.Integrations.requiredField, @@ -154,7 +155,37 @@ export class IntegrationWebviewProvider implements IIntegrationWebviewProvider { integrationsBigQueryProjectIdPlaceholder: localize.Integrations.bigQueryProjectIdPlaceholder, integrationsBigQueryCredentialsLabel: localize.Integrations.bigQueryCredentialsLabel, integrationsBigQueryCredentialsPlaceholder: localize.Integrations.bigQueryCredentialsPlaceholder, - integrationsBigQueryCredentialsRequired: localize.Integrations.bigQueryCredentialsRequired + integrationsBigQueryCredentialsRequired: localize.Integrations.bigQueryCredentialsRequired, + integrationsSnowflakeNameLabel: localize.Integrations.snowflakeNameLabel, + integrationsSnowflakeNamePlaceholder: localize.Integrations.snowflakeNamePlaceholder, + integrationsSnowflakeAccountLabel: localize.Integrations.snowflakeAccountLabel, + integrationsSnowflakeAccountPlaceholder: localize.Integrations.snowflakeAccountPlaceholder, + integrationsSnowflakeAuthMethodLabel: localize.Integrations.snowflakeAuthMethodLabel, + integrationsSnowflakeAuthMethodSubLabel: localize.Integrations.snowflakeAuthMethodSubLabel, + integrationsSnowflakeAuthMethodUsernamePassword: localize.Integrations.snowflakeAuthMethodUsernamePassword, + integrationsSnowflakeAuthMethodKeyPair: localize.Integrations.snowflakeAuthMethodKeyPair, + integrationsSnowflakeUsernameLabel: localize.Integrations.snowflakeUsernameLabel, + integrationsSnowflakeUsernamePlaceholder: localize.Integrations.snowflakeUsernamePlaceholder, + integrationsSnowflakePasswordLabel: localize.Integrations.snowflakePasswordLabel, + integrationsSnowflakePasswordPlaceholder: localize.Integrations.snowflakePasswordPlaceholder, + integrationsSnowflakeServiceAccountUsernameLabel: + localize.Integrations.snowflakeServiceAccountUsernameLabel, + integrationsSnowflakeServiceAccountUsernameHelp: localize.Integrations.snowflakeServiceAccountUsernameHelp, + integrationsSnowflakeServiceAccountUsernamePlaceholder: + localize.Integrations.snowflakeServiceAccountUsernamePlaceholder, + integrationsSnowflakePrivateKeyLabel: localize.Integrations.snowflakePrivateKeyLabel, + integrationsSnowflakePrivateKeyHelp: localize.Integrations.snowflakePrivateKeyHelp, + integrationsSnowflakePrivateKeyPlaceholder: localize.Integrations.snowflakePrivateKeyPlaceholder, + integrationsSnowflakePrivateKeyPassphraseLabel: localize.Integrations.snowflakePrivateKeyPassphraseLabel, + integrationsSnowflakePrivateKeyPassphraseHelp: localize.Integrations.snowflakePrivateKeyPassphraseHelp, + integrationsSnowflakePrivateKeyPassphrasePlaceholder: + localize.Integrations.snowflakePrivateKeyPassphrasePlaceholder, + integrationsSnowflakeDatabaseLabel: localize.Integrations.snowflakeDatabaseLabel, + integrationsSnowflakeDatabasePlaceholder: localize.Integrations.snowflakeDatabasePlaceholder, + integrationsSnowflakeRoleLabel: localize.Integrations.snowflakeRoleLabel, + integrationsSnowflakeRolePlaceholder: localize.Integrations.snowflakeRolePlaceholder, + integrationsSnowflakeWarehouseLabel: localize.Integrations.snowflakeWarehouseLabel, + integrationsSnowflakeWarehousePlaceholder: localize.Integrations.snowflakeWarehousePlaceholder }; await this.currentPanel.webview.postMessage({ diff --git a/src/notebooks/deepnote/sqlCellStatusBarProvider.ts b/src/notebooks/deepnote/sqlCellStatusBarProvider.ts index 5ac805d243..b0ba8897ce 100644 --- a/src/notebooks/deepnote/sqlCellStatusBarProvider.ts +++ b/src/notebooks/deepnote/sqlCellStatusBarProvider.ts @@ -437,6 +437,8 @@ export class SqlCellStatusBarProvider implements NotebookCellStatusBarItemProvid return l10n.t('PostgreSQL'); case IntegrationType.BigQuery: return l10n.t('BigQuery'); + case IntegrationType.Snowflake: + return l10n.t('Snowflake'); default: return String(type); } diff --git a/src/platform/common/utils/localize.ts b/src/platform/common/utils/localize.ts index 3d34f5f221..28be3f435b 100644 --- a/src/platform/common/utils/localize.ts +++ b/src/platform/common/utils/localize.ts @@ -834,6 +834,7 @@ export namespace Integrations { // Integration type labels export const postgresTypeLabel = l10n.t('PostgreSQL'); export const bigQueryTypeLabel = l10n.t('BigQuery'); + export const snowflakeTypeLabel = l10n.t('Snowflake'); // PostgreSQL form strings export const postgresNameLabel = l10n.t('Name (optional)'); @@ -861,6 +862,42 @@ export namespace Integrations { export const bigQueryCredentialsRequired = l10n.t('Credentials are required'); export const bigQueryInvalidJson = (message: string) => l10n.t('Invalid JSON: {0}', message); export const bigQueryUnnamedIntegration = (id: string) => l10n.t('Unnamed BigQuery Integration ({0})', id); + + // Snowflake form strings + export const snowflakeNameLabel = l10n.t('Integration name'); + export const snowflakeNamePlaceholder = l10n.t('[Demo] Snowflake'); + export const snowflakeAccountLabel = l10n.t('Account name'); + export const snowflakeAccountPlaceholder = l10n.t('ptb34938.us-east-1'); + export const snowflakeAuthMethodLabel = l10n.t('Authentication'); + export const snowflakeAuthMethodSubLabel = l10n.t('Method'); + export const snowflakeAuthMethodUsernamePassword = l10n.t('Username & password'); + export const snowflakeAuthMethodKeyPair = l10n.t('Key-pair (service account)'); + export const snowflakeUsernameLabel = l10n.t('Username'); + export const snowflakeUsernamePlaceholder = l10n.t('WEBSITE_ANALYTICS_USER'); + export const snowflakePasswordLabel = l10n.t('Password'); + export const snowflakePasswordPlaceholder = l10n.t('••••••••••••••'); + export const snowflakeServiceAccountUsernameLabel = l10n.t('Service Account Username'); + export const snowflakeServiceAccountUsernameHelp = l10n.t( + 'The username of the service account that will be used to connect to Snowflake' + ); + export const snowflakeServiceAccountUsernamePlaceholder = l10n.t('WEBSITE_ANALYTICS_USER'); + export const snowflakePrivateKeyLabel = l10n.t('Private Key'); + export const snowflakePrivateKeyHelp = l10n.t( + 'The private key in PEM format. Make sure to include the entire key, including BEGIN and END markers.' + ); + export const snowflakePrivateKeyPlaceholder = l10n.t("Begins with '-----BEGIN PRIVATE KEY-----'"); + export const snowflakePrivateKeyPassphraseLabel = l10n.t('Private Key Passphrase (optional)'); + export const snowflakePrivateKeyPassphraseHelp = l10n.t( + 'If the private key is encrypted, provide the passphrase to decrypt it' + ); + export const snowflakePrivateKeyPassphrasePlaceholder = l10n.t('Private key passphrase (optional)'); + export const snowflakeDatabaseLabel = l10n.t('Database (optional)'); + export const snowflakeDatabasePlaceholder = l10n.t('DEEPNOTE'); + export const snowflakeRoleLabel = l10n.t('Role (optional)'); + export const snowflakeRolePlaceholder = l10n.t(''); + export const snowflakeWarehouseLabel = l10n.t('Warehouse (optional)'); + export const snowflakeWarehousePlaceholder = l10n.t(''); + export const snowflakeUnnamedIntegration = (id: string) => l10n.t('Unnamed Snowflake Integration ({0})', id); } export namespace Deprecated { diff --git a/src/platform/notebooks/deepnote/integrationTypes.ts b/src/platform/notebooks/deepnote/integrationTypes.ts index ac1d782154..4c32b9d19c 100644 --- a/src/platform/notebooks/deepnote/integrationTypes.ts +++ b/src/platform/notebooks/deepnote/integrationTypes.ts @@ -9,7 +9,8 @@ export const DATAFRAME_SQL_INTEGRATION_ID = 'deepnote-dataframe-sql'; */ export enum IntegrationType { Postgres = 'postgres', - BigQuery = 'bigquery' + BigQuery = 'bigquery', + Snowflake = 'snowflake' } /** @@ -17,7 +18,8 @@ export enum IntegrationType { */ export const INTEGRATION_TYPE_TO_DEEPNOTE = { [IntegrationType.Postgres]: 'pgsql', - [IntegrationType.BigQuery]: 'big-query' + [IntegrationType.BigQuery]: 'big-query', + [IntegrationType.Snowflake]: 'snowflake' } as const satisfies { [type in IntegrationType]: string }; export type RawIntegrationType = (typeof INTEGRATION_TYPE_TO_DEEPNOTE)[keyof typeof INTEGRATION_TYPE_TO_DEEPNOTE]; @@ -27,7 +29,8 @@ export type RawIntegrationType = (typeof INTEGRATION_TYPE_TO_DEEPNOTE)[keyof typ */ export const DEEPNOTE_TO_INTEGRATION_TYPE: Record = { pgsql: IntegrationType.Postgres, - 'big-query': IntegrationType.BigQuery + 'big-query': IntegrationType.BigQuery, + snowflake: IntegrationType.Snowflake }; /** @@ -61,10 +64,37 @@ export interface BigQueryIntegrationConfig extends BaseIntegrationConfig { credentials: string; // JSON string of service account credentials } +/** + * Snowflake authentication method + */ +export enum SnowflakeAuthMethod { + UsernamePassword = 'username_password', + KeyPair = 'key_pair' +} + +/** + * Snowflake integration configuration + */ +export interface SnowflakeIntegrationConfig extends BaseIntegrationConfig { + type: IntegrationType.Snowflake; + account: string; + authMethod: SnowflakeAuthMethod; + username: string; + // For username+password auth + password?: string; + // For key-pair auth + privateKey?: string; + privateKeyPassphrase?: string; + // Optional fields + database?: string; + warehouse?: string; + role?: string; +} + /** * Union type of all integration configurations */ -export type IntegrationConfig = PostgresIntegrationConfig | BigQueryIntegrationConfig; +export type IntegrationConfig = PostgresIntegrationConfig | BigQueryIntegrationConfig | SnowflakeIntegrationConfig; /** * Integration connection status diff --git a/src/platform/notebooks/deepnote/sqlIntegrationEnvironmentVariablesProvider.ts b/src/platform/notebooks/deepnote/sqlIntegrationEnvironmentVariablesProvider.ts index 13da2ab4a3..84de9efb05 100644 --- a/src/platform/notebooks/deepnote/sqlIntegrationEnvironmentVariablesProvider.ts +++ b/src/platform/notebooks/deepnote/sqlIntegrationEnvironmentVariablesProvider.ts @@ -6,7 +6,12 @@ import { EnvironmentVariables } from '../../common/variables/types'; import { BaseError } from '../../errors/types'; import { logger } from '../../logging'; import { IIntegrationStorage, ISqlIntegrationEnvVarsProvider } from './types'; -import { DATAFRAME_SQL_INTEGRATION_ID, IntegrationConfig, IntegrationType } from './integrationTypes'; +import { + DATAFRAME_SQL_INTEGRATION_ID, + IntegrationConfig, + IntegrationType, + SnowflakeAuthMethod +} from './integrationTypes'; /** * Error thrown when an unsupported integration type is encountered. @@ -73,6 +78,68 @@ function convertIntegrationConfigToJson(config: IntegrationConfig): string { }); } + case IntegrationType.Snowflake: { + // Build Snowflake connection URL + // Format depends on auth method: + // Username+password: snowflake://{username}:{password}@{account}/{database}?warehouse={warehouse}&role={role}&application=YourApp + // Key-pair: snowflake://{username}@{account}/{database}?warehouse={warehouse}&role={role}&authenticator=snowflake_jwt&application=YourApp + const encodedUsername = encodeURIComponent(config.username); + const encodedAccount = encodeURIComponent(config.account); + + let url: string; + const params: Record = {}; + + if (config.authMethod === SnowflakeAuthMethod.UsernamePassword) { + // Username+password authentication + const encodedPassword = encodeURIComponent(config.password || ''); + const database = config.database ? `/${encodeURIComponent(config.database)}` : ''; + url = `snowflake://${encodedUsername}:${encodedPassword}@${encodedAccount}${database}`; + + const queryParams: string[] = []; + if (config.warehouse) { + queryParams.push(`warehouse=${encodeURIComponent(config.warehouse)}`); + } + if (config.role) { + queryParams.push(`role=${encodeURIComponent(config.role)}`); + } + queryParams.push('application=Deepnote'); + + if (queryParams.length > 0) { + url += `?${queryParams.join('&')}`; + } + } else { + // Key-pair authentication + const database = config.database ? `/${encodeURIComponent(config.database)}` : ''; + url = `snowflake://${encodedUsername}@${encodedAccount}${database}`; + + const queryParams: string[] = []; + if (config.warehouse) { + queryParams.push(`warehouse=${encodeURIComponent(config.warehouse)}`); + } + if (config.role) { + queryParams.push(`role=${encodeURIComponent(config.role)}`); + } + queryParams.push('authenticator=snowflake_jwt'); + queryParams.push('application=Deepnote'); + + if (queryParams.length > 0) { + url += `?${queryParams.join('&')}`; + } + + // For key-pair auth, pass the private key and passphrase as params + params.private_key = config.privateKey || ''; + if (config.privateKeyPassphrase) { + params.private_key_passphrase = config.privateKeyPassphrase; + } + } + + return JSON.stringify({ + url: url, + params: params, + param_style: 'format' + }); + } + default: throw new UnsupportedIntegrationError((config as IntegrationConfig).type); } diff --git a/src/webviews/webview-side/integrations/ConfigurationForm.tsx b/src/webviews/webview-side/integrations/ConfigurationForm.tsx index ca6ea2e36d..d42ab144a8 100644 --- a/src/webviews/webview-side/integrations/ConfigurationForm.tsx +++ b/src/webviews/webview-side/integrations/ConfigurationForm.tsx @@ -2,6 +2,7 @@ import * as React from 'react'; import { getLocString } from '../react-common/locReactSide'; import { PostgresForm } from './PostgresForm'; import { BigQueryForm } from './BigQueryForm'; +import { SnowflakeForm } from './SnowflakeForm'; import { IntegrationConfig, IntegrationType } from './types'; export interface IConfigurationFormProps { @@ -22,7 +23,7 @@ export const ConfigurationForm: React.FC = ({ onCancel }) => { // Determine integration type from existing config, integration metadata from project, or ID - const getIntegrationType = (): 'postgres' | 'bigquery' => { + const getIntegrationType = (): 'postgres' | 'bigquery' | 'snowflake' => { if (existingConfig) { return existingConfig.type; } @@ -37,6 +38,9 @@ export const ConfigurationForm: React.FC = ({ if (integrationId.includes('bigquery')) { return 'bigquery'; } + if (integrationId.includes('snowflake')) { + return 'snowflake'; + } // Default to postgres return 'postgres'; }; @@ -67,7 +71,7 @@ export const ConfigurationForm: React.FC = ({ onSave={onSave} onCancel={onCancel} /> - ) : ( + ) : selectedIntegrationType === 'bigquery' ? ( = ({ onSave={onSave} onCancel={onCancel} /> + ) : ( + )} diff --git a/src/webviews/webview-side/integrations/IntegrationItem.tsx b/src/webviews/webview-side/integrations/IntegrationItem.tsx index f1472ceb38..73b97fd99e 100644 --- a/src/webviews/webview-side/integrations/IntegrationItem.tsx +++ b/src/webviews/webview-side/integrations/IntegrationItem.tsx @@ -14,6 +14,8 @@ const getIntegrationTypeLabel = (type: IntegrationType): string => { return getLocString('integrationsPostgresTypeLabel', 'PostgreSQL'); case 'bigquery': return getLocString('integrationsBigQueryTypeLabel', 'BigQuery'); + case 'snowflake': + return getLocString('integrationsSnowflakeTypeLabel', 'Snowflake'); default: return type; } diff --git a/src/webviews/webview-side/integrations/SnowflakeForm.tsx b/src/webviews/webview-side/integrations/SnowflakeForm.tsx new file mode 100644 index 0000000000..88b6213927 --- /dev/null +++ b/src/webviews/webview-side/integrations/SnowflakeForm.tsx @@ -0,0 +1,301 @@ +import * as React from 'react'; +import { format, getLocString } from '../react-common/locReactSide'; +import { SnowflakeIntegrationConfig, SnowflakeAuthMethod } from './types'; + +export interface ISnowflakeFormProps { + integrationId: string; + existingConfig: SnowflakeIntegrationConfig | null; + integrationName?: string; + onSave: (config: SnowflakeIntegrationConfig) => void; + onCancel: () => void; +} + +export const SnowflakeForm: React.FC = ({ + integrationId, + existingConfig, + integrationName, + onSave, + onCancel +}) => { + const [name, setName] = React.useState(existingConfig?.name || integrationName || ''); + const [account, setAccount] = React.useState(existingConfig?.account || ''); + const [authMethod, setAuthMethod] = React.useState( + existingConfig?.authMethod || 'username_password' + ); + const [username, setUsername] = React.useState(existingConfig?.username || ''); + const [password, setPassword] = React.useState(existingConfig?.password || ''); + const [privateKey, setPrivateKey] = React.useState(existingConfig?.privateKey || ''); + const [privateKeyPassphrase, setPrivateKeyPassphrase] = React.useState(existingConfig?.privateKeyPassphrase || ''); + const [database, setDatabase] = React.useState(existingConfig?.database || ''); + const [warehouse, setWarehouse] = React.useState(existingConfig?.warehouse || ''); + const [role, setRole] = React.useState(existingConfig?.role || ''); + + // Update form fields when existingConfig or integrationName changes + React.useEffect(() => { + if (existingConfig) { + setName(existingConfig.name || ''); + setAccount(existingConfig.account || ''); + setAuthMethod(existingConfig.authMethod || 'username_password'); + setUsername(existingConfig.username || ''); + setPassword(existingConfig.password || ''); + setPrivateKey(existingConfig.privateKey || ''); + setPrivateKeyPassphrase(existingConfig.privateKeyPassphrase || ''); + setDatabase(existingConfig.database || ''); + setWarehouse(existingConfig.warehouse || ''); + setRole(existingConfig.role || ''); + } else { + setName(integrationName || ''); + setAccount(''); + setAuthMethod('username_password'); + setUsername(''); + setPassword(''); + setPrivateKey(''); + setPrivateKeyPassphrase(''); + setDatabase(''); + setWarehouse(''); + setRole(''); + } + }, [existingConfig, integrationName]); + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + + const unnamedIntegration = format('Unnamed Snowflake Integration ({0})', integrationId); + + const config: SnowflakeIntegrationConfig = { + id: integrationId, + name: (name || unnamedIntegration).trim(), + type: 'snowflake', + account: account.trim(), + authMethod, + username: username.trim(), + password: authMethod === 'username_password' ? password.trim() : undefined, + privateKey: authMethod === 'key_pair' ? privateKey.trim() : undefined, + privateKeyPassphrase: authMethod === 'key_pair' ? privateKeyPassphrase.trim() : undefined, + database: database.trim() || undefined, + warehouse: warehouse.trim() || undefined, + role: role.trim() || undefined + }; + + onSave(config); + }; + + return ( +
+
+ + setName(e.target.value)} + placeholder={getLocString('integrationsSnowflakeNamePlaceholder', '[Demo] Snowflake')} + autoComplete="off" + /> +
+ +
+ + setAccount(e.target.value)} + placeholder={getLocString('integrationsSnowflakeAccountPlaceholder', 'abcd.us-east-1')} + autoComplete="off" + required + /> +
+ +
+ +
+ + +
+
+ + {authMethod === 'username_password' ? ( + <> +
+ + setUsername(e.target.value)} + placeholder={getLocString('integrationsSnowflakeUsernamePlaceholder', '')} + autoComplete="username" + required + /> +
+ +
+ + setPassword(e.target.value)} + placeholder={getLocString('integrationsSnowflakePasswordPlaceholder', '•••••')} + autoComplete="current-password" + required + /> +
+ + ) : ( + <> +
+ +

+ {getLocString( + 'integrationsSnowflakeServiceAccountUsernameHelp', + 'The username of the service account that will be used to connect to Snowflake' + )} +

+ setUsername(e.target.value)} + placeholder={getLocString('integrationsSnowflakeServiceAccountUsernamePlaceholder', '')} + autoComplete="username" + required + /> +
+ +
+ +

+ {getLocString( + 'integrationsSnowflakePrivateKeyHelp', + 'The private key in PEM format. Make sure to include the entire key, including BEGIN and END markers.' + )} +

+