This is an automated email from the ASF dual-hosted git repository.

elizabeth pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/superset.git


The following commit(s) were added to refs/heads/master by this push:
     new cf395ac2d8 feat(ssh_tunnel): SSH Tunnel Switch extension (#22967)
cf395ac2d8 is described below

commit cf395ac2d8e04782cffc93e8a0a0b28678c407fe
Author: Antonio Rivero Martinez 
<[email protected]>
AuthorDate: Fri Feb 3 20:34:54 2023 -0300

    feat(ssh_tunnel): SSH Tunnel Switch extension (#22967)
---
 .../src/ui-overrides/ExtensionsRegistry.ts         |  11 +
 .../data/database/DatabaseModal/SSHTunnelForm.tsx  | 317 +++++++++------------
 .../database/DatabaseModal/SSHTunnelSwitch.tsx     |  58 ++++
 .../data/database/DatabaseModal/index.test.tsx     |  32 +++
 .../CRUD/data/database/DatabaseModal/index.tsx     |  54 +++-
 5 files changed, 281 insertions(+), 191 deletions(-)

diff --git 
a/superset-frontend/packages/superset-ui-core/src/ui-overrides/ExtensionsRegistry.ts
 
b/superset-frontend/packages/superset-ui-core/src/ui-overrides/ExtensionsRegistry.ts
index d630525064..a411c41d08 100644
--- 
a/superset-frontend/packages/superset-ui-core/src/ui-overrides/ExtensionsRegistry.ts
+++ 
b/superset-frontend/packages/superset-ui-core/src/ui-overrides/ExtensionsRegistry.ts
@@ -49,6 +49,16 @@ interface MenuObjectChildProps {
   disable?: boolean;
 }
 
+export interface SwitchProps {
+  isEditMode: boolean;
+  dbFetched: any;
+  disableSSHTunnelingForEngine?: boolean;
+  useSSHTunneling: boolean;
+  setUseSSHTunneling: React.Dispatch<React.SetStateAction<boolean>>;
+  setDB: React.Dispatch<any>;
+  isSSHTunneling: boolean;
+}
+
 type ConfigDetailsProps = {
   embeddedId: string;
 };
@@ -69,6 +79,7 @@ export type Extensions = Partial<{
   'welcome.message': React.ComponentType;
   'welcome.banner': React.ComponentType;
   'welcome.main.replacement': React.ComponentType;
+  'ssh_tunnel.form.switch': React.ComponentType<SwitchProps>;
 }>;
 
 /**
diff --git 
a/superset-frontend/src/views/CRUD/data/database/DatabaseModal/SSHTunnelForm.tsx
 
b/superset-frontend/src/views/CRUD/data/database/DatabaseModal/SSHTunnelForm.tsx
index 49dbc4e0e0..6cc0312b52 100644
--- 
a/superset-frontend/src/views/CRUD/data/database/DatabaseModal/SSHTunnelForm.tsx
+++ 
b/superset-frontend/src/views/CRUD/data/database/DatabaseModal/SSHTunnelForm.tsx
@@ -17,16 +17,13 @@
  * under the License.
  */
 import React, { EventHandler, ChangeEvent, useState } from 'react';
-import { t, SupersetTheme, styled } from '@superset-ui/core';
-import { AntdForm, AntdSwitch, Col, Row } from 'src/components';
-import InfoTooltip from 'src/components/InfoTooltip';
+import { t, styled } from '@superset-ui/core';
+import { AntdForm, Col, Row } from 'src/components';
 import { Form, FormLabel } from 'src/components/Form';
 import { Radio } from 'src/components/Radio';
 import { Input, TextArea } from 'src/components/Input';
 import { Input as AntdInput, Tooltip } from 'antd';
 import { EyeInvisibleOutlined, EyeOutlined } from '@ant-design/icons';
-import { isEmpty } from 'lodash';
-import { infoTooltip, toggleStyle } from './styles';
 
 import { DatabaseObject } from '../types';
 import { AuthType } from '.';
@@ -54,79 +51,143 @@ const StyledInputPassword = styled(AntdInput.Password)`
 
 const SSHTunnelForm = ({
   db,
-  dbFetched,
-  isEditMode,
-  isSSHTunneling,
   onSSHTunnelParametersChange,
   setSSHTunnelLoginMethod,
-  removeSSHTunnelConfig,
 }: {
   db: DatabaseObject | null;
-  dbFetched: DatabaseObject | null;
-  isEditMode: boolean;
-  isSSHTunneling: boolean;
   onSSHTunnelParametersChange: EventHandler<
     ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
   >;
   setSSHTunnelLoginMethod: (method: AuthType) => void;
-  removeSSHTunnelConfig: () => void;
 }) => {
-  const [useSSHTunneling, setUseSSHTunneling] = useState<boolean>(
-    !isEmpty(db?.ssh_tunnel),
-  );
   const [usePassword, setUsePassword] = useState<AuthType>(AuthType.password);
 
   return (
     <Form>
-      <div css={(theme: SupersetTheme) => infoTooltip(theme)}>
-        <AntdSwitch
-          disabled={
-            !isSSHTunneling || (isEditMode && !isEmpty(dbFetched?.ssh_tunnel))
-          }
-          checked={useSSHTunneling}
-          onChange={changed => {
-            setUseSSHTunneling(changed);
-            if (!changed) removeSSHTunnelConfig();
-          }}
-          data-test="ssh-tunnel-switch"
-        />
-        <span css={toggleStyle}>SSH Tunnel</span>
-        <InfoTooltip
-          tooltip={t('SSH Tunnel configuration parameters')}
-          placement="right"
-          viewBox="0 -5 24 24"
-        />
-      </div>
-      {useSSHTunneling && (
+      <StyledRow gutter={16}>
+        <Col xs={24} md={12}>
+          <StyledDiv>
+            <FormLabel htmlFor="server_address" required>
+              {t('SSH Host')}
+            </FormLabel>
+            <Input
+              name="server_address"
+              type="text"
+              placeholder={t('e.g. 127.0.0.1')}
+              value={db?.ssh_tunnel?.server_address || ''}
+              onChange={onSSHTunnelParametersChange}
+              data-test="ssh-tunnel-server_address-input"
+            />
+          </StyledDiv>
+        </Col>
+        <Col xs={24} md={12}>
+          <StyledDiv>
+            <FormLabel htmlFor="server_port" required>
+              {t('SSH Port')}
+            </FormLabel>
+            <Input
+              name="server_port"
+              type="text"
+              placeholder={t('22')}
+              value={db?.ssh_tunnel?.server_port || ''}
+              onChange={onSSHTunnelParametersChange}
+              data-test="ssh-tunnel-server_port-input"
+            />
+          </StyledDiv>
+        </Col>
+      </StyledRow>
+      <StyledRow gutter={16}>
+        <Col xs={24}>
+          <StyledDiv>
+            <FormLabel htmlFor="username" required>
+              {t('Username')}
+            </FormLabel>
+            <Input
+              name="username"
+              type="text"
+              placeholder={t('e.g. Analytics')}
+              value={db?.ssh_tunnel?.username || ''}
+              onChange={onSSHTunnelParametersChange}
+              data-test="ssh-tunnel-username-input"
+            />
+          </StyledDiv>
+        </Col>
+      </StyledRow>
+      <StyledRow gutter={16}>
+        <Col xs={24}>
+          <StyledDiv>
+            <FormLabel htmlFor="use_password" required>
+              {t('Login with')}
+            </FormLabel>
+            <StyledFormItem name="use_password" initialValue={usePassword}>
+              <Radio.Group
+                onChange={({ target: { value } }) => {
+                  setUsePassword(value);
+                  setSSHTunnelLoginMethod(value);
+                }}
+              >
+                <Radio
+                  value={AuthType.password}
+                  data-test="ssh-tunnel-use_password-radio"
+                >
+                  {t('Password')}
+                </Radio>
+                <Radio
+                  value={AuthType.privateKey}
+                  data-test="ssh-tunnel-use_private_key-radio"
+                >
+                  {t('Private Key & Password')}
+                </Radio>
+              </Radio.Group>
+            </StyledFormItem>
+          </StyledDiv>
+        </Col>
+      </StyledRow>
+      {usePassword === AuthType.password && (
+        <StyledRow gutter={16}>
+          <Col xs={24}>
+            <StyledDiv>
+              <FormLabel htmlFor="password" required>
+                {t('SSH Password')}
+              </FormLabel>
+              <StyledInputPassword
+                name="password"
+                placeholder={t('e.g. ********')}
+                value={db?.ssh_tunnel?.password || ''}
+                onChange={onSSHTunnelParametersChange}
+                data-test="ssh-tunnel-password-input"
+                iconRender={visible =>
+                  visible ? (
+                    <Tooltip title="Hide password.">
+                      <EyeInvisibleOutlined />
+                    </Tooltip>
+                  ) : (
+                    <Tooltip title="Show password.">
+                      <EyeOutlined />
+                    </Tooltip>
+                  )
+                }
+                role="textbox"
+              />
+            </StyledDiv>
+          </Col>
+        </StyledRow>
+      )}
+      {usePassword === AuthType.privateKey && (
         <>
           <StyledRow gutter={16}>
-            <Col xs={24} md={12}>
-              <StyledDiv>
-                <FormLabel htmlFor="server_address" required>
-                  {t('SSH Host')}
-                </FormLabel>
-                <Input
-                  name="server_address"
-                  type="text"
-                  placeholder={t('e.g. 127.0.0.1')}
-                  value={db?.ssh_tunnel?.server_address || ''}
-                  onChange={onSSHTunnelParametersChange}
-                  data-test="ssh-tunnel-server_address-input"
-                />
-              </StyledDiv>
-            </Col>
-            <Col xs={24} md={12}>
+            <Col xs={24}>
               <StyledDiv>
-                <FormLabel htmlFor="server_port" required>
-                  {t('SSH Port')}
+                <FormLabel htmlFor="private_key" required>
+                  {t('Private Key')}
                 </FormLabel>
-                <Input
-                  name="server_port"
-                  type="text"
-                  placeholder={t('22')}
-                  value={db?.ssh_tunnel?.server_port || ''}
+                <TextArea
+                  name="private_key"
+                  placeholder={t('Paste Private Key here')}
+                  value={db?.ssh_tunnel?.private_key || ''}
                   onChange={onSSHTunnelParametersChange}
-                  data-test="ssh-tunnel-server_port-input"
+                  data-test="ssh-tunnel-private_key-input"
+                  rows={4}
                 />
               </StyledDiv>
             </Col>
@@ -134,129 +195,31 @@ const SSHTunnelForm = ({
           <StyledRow gutter={16}>
             <Col xs={24}>
               <StyledDiv>
-                <FormLabel htmlFor="username" required>
-                  {t('Username')}
+                <FormLabel htmlFor="private_key_password" required>
+                  {t('Private Key Password')}
                 </FormLabel>
-                <Input
-                  name="username"
-                  type="text"
-                  placeholder={t('e.g. Analytics')}
-                  value={db?.ssh_tunnel?.username || ''}
+                <StyledInputPassword
+                  name="private_key_password"
+                  placeholder={t('e.g. ********')}
+                  value={db?.ssh_tunnel?.private_key_password || ''}
                   onChange={onSSHTunnelParametersChange}
-                  data-test="ssh-tunnel-username-input"
+                  data-test="ssh-tunnel-private_key_password-input"
+                  iconRender={visible =>
+                    visible ? (
+                      <Tooltip title="Hide password.">
+                        <EyeInvisibleOutlined />
+                      </Tooltip>
+                    ) : (
+                      <Tooltip title="Show password.">
+                        <EyeOutlined />
+                      </Tooltip>
+                    )
+                  }
+                  role="textbox"
                 />
               </StyledDiv>
             </Col>
           </StyledRow>
-          <StyledRow gutter={16}>
-            <Col xs={24}>
-              <StyledDiv>
-                <FormLabel htmlFor="use_password" required>
-                  {t('Login with')}
-                </FormLabel>
-                <StyledFormItem name="use_password" initialValue={usePassword}>
-                  <Radio.Group
-                    onChange={({ target: { value } }) => {
-                      setUsePassword(value);
-                      setSSHTunnelLoginMethod(value);
-                    }}
-                  >
-                    <Radio
-                      value={AuthType.password}
-                      data-test="ssh-tunnel-use_password-radio"
-                    >
-                      {t('Password')}
-                    </Radio>
-                    <Radio
-                      value={AuthType.privateKey}
-                      data-test="ssh-tunnel-use_private_key-radio"
-                    >
-                      {t('Private Key & Password')}
-                    </Radio>
-                  </Radio.Group>
-                </StyledFormItem>
-              </StyledDiv>
-            </Col>
-          </StyledRow>
-          {usePassword === AuthType.password && (
-            <StyledRow gutter={16}>
-              <Col xs={24}>
-                <StyledDiv>
-                  <FormLabel htmlFor="password" required>
-                    {t('SSH Password')}
-                  </FormLabel>
-                  <StyledInputPassword
-                    name="password"
-                    placeholder={t('e.g. ********')}
-                    value={db?.ssh_tunnel?.password || ''}
-                    onChange={onSSHTunnelParametersChange}
-                    data-test="ssh-tunnel-password-input"
-                    iconRender={visible =>
-                      visible ? (
-                        <Tooltip title={t('Hide password.')}>
-                          <EyeInvisibleOutlined />
-                        </Tooltip>
-                      ) : (
-                        <Tooltip title={t('Show password.')}>
-                          <EyeOutlined />
-                        </Tooltip>
-                      )
-                    }
-                    role="textbox"
-                  />
-                </StyledDiv>
-              </Col>
-            </StyledRow>
-          )}
-          {usePassword === AuthType.privateKey && (
-            <>
-              <StyledRow gutter={16}>
-                <Col xs={24}>
-                  <StyledDiv>
-                    <FormLabel htmlFor="private_key" required>
-                      {t('Private Key')}
-                    </FormLabel>
-                    <TextArea
-                      name="private_key"
-                      placeholder={t('Paste Private Key here')}
-                      value={db?.ssh_tunnel?.private_key || ''}
-                      onChange={onSSHTunnelParametersChange}
-                      data-test="ssh-tunnel-private_key-input"
-                      rows={4}
-                    />
-                  </StyledDiv>
-                </Col>
-              </StyledRow>
-              <StyledRow gutter={16}>
-                <Col xs={24}>
-                  <StyledDiv>
-                    <FormLabel htmlFor="private_key_password" required>
-                      {t('Private Key Password')}
-                    </FormLabel>
-                    <StyledInputPassword
-                      name="private_key_password"
-                      placeholder={t('e.g. ********')}
-                      value={db?.ssh_tunnel?.private_key_password || ''}
-                      onChange={onSSHTunnelParametersChange}
-                      data-test="ssh-tunnel-private_key_password-input"
-                      iconRender={visible =>
-                        visible ? (
-                          <Tooltip title="Hide password.">
-                            <EyeInvisibleOutlined />
-                          </Tooltip>
-                        ) : (
-                          <Tooltip title="Show password.">
-                            <EyeOutlined />
-                          </Tooltip>
-                        )
-                      }
-                      role="textbox"
-                    />
-                  </StyledDiv>
-                </Col>
-              </StyledRow>
-            </>
-          )}
         </>
       )}
     </Form>
diff --git 
a/superset-frontend/src/views/CRUD/data/database/DatabaseModal/SSHTunnelSwitch.tsx
 
b/superset-frontend/src/views/CRUD/data/database/DatabaseModal/SSHTunnelSwitch.tsx
new file mode 100644
index 0000000000..a3cc38ca37
--- /dev/null
+++ 
b/superset-frontend/src/views/CRUD/data/database/DatabaseModal/SSHTunnelSwitch.tsx
@@ -0,0 +1,58 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import React from 'react';
+import { t, SupersetTheme, SwitchProps } from '@superset-ui/core';
+import { AntdSwitch } from 'src/components';
+import InfoTooltip from 'src/components/InfoTooltip';
+import { isEmpty } from 'lodash';
+import { ActionType } from '.';
+import { infoTooltip, toggleStyle } from './styles';
+
+const SSHTunnelSwitch = ({
+  isEditMode,
+  dbFetched,
+  useSSHTunneling,
+  setUseSSHTunneling,
+  setDB,
+  isSSHTunneling,
+}: SwitchProps) =>
+  isSSHTunneling ? (
+    <div css={(theme: SupersetTheme) => infoTooltip(theme)}>
+      <AntdSwitch
+        disabled={isEditMode && !isEmpty(dbFetched?.ssh_tunnel)}
+        checked={useSSHTunneling}
+        onChange={changed => {
+          setUseSSHTunneling(changed);
+          if (!changed) {
+            setDB({
+              type: ActionType.removeSSHTunnelConfig,
+            });
+          }
+        }}
+        data-test="ssh-tunnel-switch"
+      />
+      <span css={toggleStyle}>{t('SSH Tunnel')}</span>
+      <InfoTooltip
+        tooltip={t('SSH Tunnel configuration parameters')}
+        placement="right"
+        viewBox="0 -5 24 24"
+      />
+    </div>
+  ) : null;
+export default SSHTunnelSwitch;
diff --git 
a/superset-frontend/src/views/CRUD/data/database/DatabaseModal/index.test.tsx 
b/superset-frontend/src/views/CRUD/data/database/DatabaseModal/index.test.tsx
index 8f2e4cda37..d644b2f3e1 100644
--- 
a/superset-frontend/src/views/CRUD/data/database/DatabaseModal/index.test.tsx
+++ 
b/superset-frontend/src/views/CRUD/data/database/DatabaseModal/index.test.tsx
@@ -31,6 +31,8 @@ import {
   DatabaseObject,
   CONFIGURATION_METHOD,
 } from 'src/views/CRUD/data/database/types';
+import { getExtensionsRegistry } from '@superset-ui/core';
+import setupExtensions from 'src/setup/setupExtensions';
 import * as hooks from 'src/views/CRUD/hooks';
 import DatabaseModal, {
   dbReducer,
@@ -1588,6 +1590,36 @@ describe('DatabaseModal', () => {
       expect(errorTitleMessage).toBeVisible();
     });
   });
+
+  describe('DatabaseModal w Extensions', () => {
+    const renderAndWait = async () => {
+      const extensionsRegistry = getExtensionsRegistry();
+
+      extensionsRegistry.set('ssh_tunnel.form.switch', () => (
+        <>ssh_tunnel.form.switch extension component</>
+      ));
+
+      setupExtensions();
+
+      const mounted = act(async () => {
+        render(<DatabaseModal {...dbProps} dbEngine="SQLite" />, {
+          useRedux: true,
+        });
+      });
+
+      return mounted;
+    };
+
+    beforeEach(async () => {
+      await renderAndWait();
+    });
+
+    test('should render an extension component if one is supplied', () => {
+      expect(
+        screen.getByText('ssh_tunnel.form.switch extension component'),
+      ).toBeInTheDocument();
+    });
+  });
 });
 
 describe('dbReducer', () => {
diff --git 
a/superset-frontend/src/views/CRUD/data/database/DatabaseModal/index.tsx 
b/superset-frontend/src/views/CRUD/data/database/DatabaseModal/index.tsx
index e7e0447e61..418b14f113 100644
--- a/superset-frontend/src/views/CRUD/data/database/DatabaseModal/index.tsx
+++ b/superset-frontend/src/views/CRUD/data/database/DatabaseModal/index.tsx
@@ -22,6 +22,7 @@ import {
   SupersetTheme,
   FeatureFlag,
   isFeatureEnabled,
+  getExtensionsRegistry,
 } from '@superset-ui/core';
 import React, {
   FunctionComponent,
@@ -63,7 +64,7 @@ import {
   ExtraJson,
 } from 'src/views/CRUD/data/database/types';
 import Loading from 'src/components/Loading';
-import { pick } from 'lodash';
+import { isEmpty, pick } from 'lodash';
 import ExtraOptions from './ExtraOptions';
 import SqlAlchemyForm from './SqlAlchemyForm';
 import DatabaseConnectionForm from './DatabaseConnectionForm';
@@ -90,6 +91,9 @@ import {
 } from './styles';
 import ModalHeader, { DOCUMENTATION_LINK } from './ModalHeader';
 import SSHTunnelForm from './SSHTunnelForm';
+import SSHTunnelSwitch from './SSHTunnelSwitch';
+
+const extensionsRegistry = getExtensionsRegistry();
 
 const DEFAULT_EXTRA = JSON.stringify({ allows_virtual_table_explore: true });
 
@@ -557,6 +561,11 @@ const DatabaseModal: FunctionComponent<DatabaseModalProps> 
= ({
   const [importingErrorMessage, setImportingErrorMessage] = useState<string>();
   const [passwordFields, setPasswordFields] = useState<string[]>([]);
 
+  const SSHTunnelSwitchComponent =
+    extensionsRegistry.get('ssh_tunnel.form.switch') ?? SSHTunnelSwitch;
+
+  const [useSSHTunneling, setUseSSHTunneling] = useState<boolean>(false);
+
   const conf = useCommonConf();
   const dbImages = getDatabaseImages();
   const connectionAlert = getConnectionAlert();
@@ -572,7 +581,7 @@ const DatabaseModal: FunctionComponent<DatabaseModalProps> 
= ({
   )?.engine_information?.disable_ssh_tunneling;
   const isSSHTunneling =
     isFeatureEnabled(FeatureFlag.SSH_TUNNELING) &&
-    disableSSHTunnelingForEngine !== undefined;
+    !disableSSHTunnelingForEngine;
   const hasAlert =
     connectionAlert || !!(db?.engine && engineSpecificAlertMapping[db.engine]);
   const useSqlAlchemyForm =
@@ -583,8 +592,6 @@ const DatabaseModal: FunctionComponent<DatabaseModalProps> 
= ({
       (DB: DatabaseObject) => DB.backend === engine || DB.engine === engine,
     )?.parameters !== undefined;
   const showDBError = validationErrors || dbErrors;
-  const isEmpty = (data?: Object | null) =>
-    !data || (data && Object.keys(data).length === 0);
 
   const dbModel: DatabaseForm =
     availableDbs?.databases?.find(
@@ -652,6 +659,7 @@ const DatabaseModal: FunctionComponent<DatabaseModalProps> 
= ({
     setPasswordFields([]);
     setPasswords({});
     setConfirmedOverwrite(false);
+    setUseSSHTunneling(false);
     onHide();
   };
 
@@ -1145,6 +1153,12 @@ const DatabaseModal: 
FunctionComponent<DatabaseModalProps> = ({
     setPasswordFields([...passwordsNeeded]);
   }, [passwordsNeeded]);
 
+  useEffect(() => {
+    if (db) {
+      setUseSSHTunneling(!isEmpty(db?.ssh_tunnel));
+    }
+  }, [db]);
+
   const onDbImport = async (info: UploadChangeParam) => {
     setImportingErrorMessage('');
     setPasswordFields([]);
@@ -1325,10 +1339,7 @@ const DatabaseModal: 
FunctionComponent<DatabaseModalProps> = ({
 
   const renderSSHTunnelForm = () => (
     <SSHTunnelForm
-      isEditMode={isEditMode}
-      isSSHTunneling={isSSHTunneling}
       db={db as DatabaseObject}
-      dbFetched={dbFetched as DatabaseObject}
       onSSHTunnelParametersChange={({
         target,
       }: {
@@ -1346,11 +1357,6 @@ const DatabaseModal: 
FunctionComponent<DatabaseModalProps> = ({
           payload: { login_method: method },
         })
       }
-      removeSSHTunnelConfig={() =>
-        setDB({
-          type: ActionType.removeSSHTunnelConfig,
-        })
-      }
     />
   );
 
@@ -1558,7 +1564,16 @@ const DatabaseModal: 
FunctionComponent<DatabaseModalProps> = ({
                 testConnection={testConnection}
                 testInProgress={testInProgress}
               >
-                {isSSHTunneling && renderSSHTunnelForm()}
+                <SSHTunnelSwitchComponent
+                  isEditMode={isEditMode}
+                  dbFetched={dbFetched}
+                  disableSSHTunnelingForEngine={disableSSHTunnelingForEngine}
+                  useSSHTunneling={useSSHTunneling}
+                  setUseSSHTunneling={setUseSSHTunneling}
+                  setDB={setDB}
+                  isSSHTunneling={isSSHTunneling}
+                />
+                {useSSHTunneling && renderSSHTunnelForm()}
               </SqlAlchemyForm>
               {isDynamic(db?.backend || db?.engine) && !isEditMode && (
                 <div css={(theme: SupersetTheme) => infoTooltip(theme)}>
@@ -1832,7 +1847,18 @@ const DatabaseModal: 
FunctionComponent<DatabaseModalProps> = ({
                   validationErrors={validationErrors}
                   getPlaceholder={getPlaceholder}
                 />
-                {isSSHTunneling && (
+                <SSHTunnelContainer>
+                  <SSHTunnelSwitchComponent
+                    isEditMode={isEditMode}
+                    dbFetched={dbFetched}
+                    disableSSHTunnelingForEngine={disableSSHTunnelingForEngine}
+                    useSSHTunneling={useSSHTunneling}
+                    setUseSSHTunneling={setUseSSHTunneling}
+                    setDB={setDB}
+                    isSSHTunneling={isSSHTunneling}
+                  />
+                </SSHTunnelContainer>
+                {useSSHTunneling && (
                   <SSHTunnelContainer>
                     {renderSSHTunnelForm()}
                   </SSHTunnelContainer>

Reply via email to