This is an automated email from the ASF dual-hosted git repository.
enzomartellucci pushed a commit to branch
enxdev/feat/enhance-database-modal-validation
in repository https://gitbox.apache.org/repos/asf/superset.git
The following commit(s) were added to
refs/heads/enxdev/feat/enhance-database-modal-validation by this push:
new ad92ec683b wip: add validation loading state to SSH tunnel form fields
ad92ec683b is described below
commit ad92ec683becd3d8cc4a4508b3de8639c4553a34
Author: Enzo Martellucci <[email protected]>
AuthorDate: Wed Dec 31 17:35:50 2025 +0100
wip: add validation loading state to SSH tunnel form fields
---
.../databases/DatabaseModal/SSHTunnelForm.tsx | 140 +++++++++++----------
.../src/features/databases/DatabaseModal/index.tsx | 4 +-
2 files changed, 77 insertions(+), 67 deletions(-)
diff --git
a/superset-frontend/src/features/databases/DatabaseModal/SSHTunnelForm.tsx
b/superset-frontend/src/features/databases/DatabaseModal/SSHTunnelForm.tsx
index 79e2a6fab7..3df9ddd2a2 100644
--- a/superset-frontend/src/features/databases/DatabaseModal/SSHTunnelForm.tsx
+++ b/superset-frontend/src/features/databases/DatabaseModal/SSHTunnelForm.tsx
@@ -17,19 +17,18 @@
* under the License.
*/
import { useState } from 'react';
-import { t } from '@superset-ui/core';
+import { t, JsonObject } from '@superset-ui/core';
import { styled } from '@apache-superset/core/ui';
import {
Form,
FormLabel,
Col,
Row,
- Tooltip,
+ LabeledErrorBoundInput,
} from '@superset-ui/core/components';
import { Input } from '@superset-ui/core/components/Input';
import { Radio } from '@superset-ui/core/components/Radio';
-import { Icons } from '@superset-ui/core/components/Icons';
-import { DatabaseObject, FieldPropTypes } from '../types';
+import { DatabaseObject, CustomEventHandlerType } from '../types';
import { AuthType } from '.';
const StyledDiv = styled.div`
@@ -48,50 +47,60 @@ const StyledFormItem = styled(Form.Item)`
margin-bottom: 0 !important;
`;
-const StyledInputPassword = styled(Input.Password)`
- margin: ${({ theme }) => `${theme.sizeUnit}px 0 ${theme.sizeUnit * 2}px`};
-`;
+interface SSHTunnelFormProps {
+ db: DatabaseObject | null;
+ onSSHTunnelParametersChange: CustomEventHandlerType;
+ setSSHTunnelLoginMethod: (method: AuthType) => void;
+ isValidating?: boolean;
+ validationErrors?: JsonObject | null;
+ getValidation?: () => void;
+}
const SSHTunnelForm = ({
db,
onSSHTunnelParametersChange,
setSSHTunnelLoginMethod,
-}: {
- db: DatabaseObject | null;
- onSSHTunnelParametersChange:
FieldPropTypes['changeMethods']['onSSHTunnelParametersChange'];
- setSSHTunnelLoginMethod: (method: AuthType) => void;
-}) => {
+ isValidating = false,
+ validationErrors,
+ getValidation,
+}: SSHTunnelFormProps) => {
const [usePassword, setUsePassword] = useState<AuthType>(AuthType.Password);
+ const sshErrors = validationErrors?.ssh_tunnel || {};
return (
<Form>
<StyledRow gutter={16}>
<Col xs={24} md={12}>
<StyledDiv>
- <FormLabel htmlFor="server_address" required>
- {t('SSH Host')}
- </FormLabel>
- <Input
+ <LabeledErrorBoundInput
+ id="server_address"
name="server_address"
- type="text"
+ label={t('SSH Host')}
+ required
placeholder={t('e.g. 127.0.0.1')}
value={db?.ssh_tunnel?.server_address || ''}
onChange={onSSHTunnelParametersChange}
+ validationMethods={{ onBlur: getValidation }}
+ errorMessage={sshErrors?.server_address}
+ isValidating={isValidating}
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
+ <LabeledErrorBoundInput
+ id="server_port"
name="server_port"
+ label={t('SSH Port')}
+ required
placeholder={t('22')}
type="number"
value={db?.ssh_tunnel?.server_port}
onChange={onSSHTunnelParametersChange}
+ validationMethods={{ onBlur: getValidation }}
+ errorMessage={sshErrors?.server_port}
+ isValidating={isValidating}
data-test="ssh-tunnel-server_port-input"
/>
</StyledDiv>
@@ -100,15 +109,17 @@ const SSHTunnelForm = ({
<StyledRow gutter={16}>
<Col xs={24}>
<StyledDiv>
- <FormLabel htmlFor="username" required>
- {t('Username')}
- </FormLabel>
- <Input
+ <LabeledErrorBoundInput
+ id="username"
name="username"
- type="text"
+ label={t('Username')}
+ required
placeholder={t('e.g. Analytics')}
value={db?.ssh_tunnel?.username || ''}
onChange={onSSHTunnelParametersChange}
+ validationMethods={{ onBlur: getValidation }}
+ errorMessage={sshErrors?.username}
+ isValidating={isValidating}
data-test="ssh-tunnel-username-input"
/>
</StyledDiv>
@@ -148,27 +159,19 @@ const SSHTunnelForm = ({
<StyledRow gutter={16}>
<Col xs={24}>
<StyledDiv>
- <FormLabel htmlFor="password" required>
- {t('SSH Password')}
- </FormLabel>
- <StyledInputPassword
+ <LabeledErrorBoundInput
+ id="password"
name="password"
+ label={t('SSH Password')}
+ required
+ visibilityToggle
placeholder={t('e.g. ********')}
value={db?.ssh_tunnel?.password || ''}
onChange={onSSHTunnelParametersChange}
+ validationMethods={{ onBlur: getValidation }}
+ errorMessage={sshErrors?.password}
+ isValidating={isValidating}
data-test="ssh-tunnel-password-input"
- iconRender={visible =>
- visible ? (
- <Tooltip title="Hide password.">
- <Icons.EyeInvisibleOutlined />
- </Tooltip>
- ) : (
- <Tooltip title="Show password.">
- <Icons.EyeOutlined />
- </Tooltip>
- )
- }
- role="textbox"
/>
</StyledDiv>
</Col>
@@ -182,41 +185,46 @@ const SSHTunnelForm = ({
<FormLabel htmlFor="private_key" required>
{t('Private Key')}
</FormLabel>
- <Input.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}
- />
+ <StyledFormItem
+ validateStatus={
+ isValidating
+ ? 'validating'
+ : sshErrors?.private_key
+ ? 'error'
+ : 'success'
+ }
+ help={sshErrors?.private_key}
+ hasFeedback={isValidating || !!sshErrors?.private_key}
+ >
+ <Input.TextArea
+ name="private_key"
+ placeholder={t('Paste Private Key here')}
+ value={db?.ssh_tunnel?.private_key || ''}
+ onChange={onSSHTunnelParametersChange}
+ onBlur={getValidation}
+ data-test="ssh-tunnel-private_key-input"
+ rows={4}
+ />
+ </StyledFormItem>
</StyledDiv>
</Col>
</StyledRow>
<StyledRow gutter={16}>
<Col xs={24}>
<StyledDiv>
- <FormLabel htmlFor="private_key_password" required>
- {t('Private Key Password')}
- </FormLabel>
- <StyledInputPassword
+ <LabeledErrorBoundInput
+ id="private_key_password"
name="private_key_password"
+ label={t('Private Key Password')}
+ required
+ visibilityToggle
placeholder={t('e.g. ********')}
value={db?.ssh_tunnel?.private_key_password || ''}
onChange={onSSHTunnelParametersChange}
+ validationMethods={{ onBlur: getValidation }}
+ errorMessage={sshErrors?.private_key_password}
+ isValidating={isValidating}
data-test="ssh-tunnel-private_key_password-input"
- iconRender={visible =>
- visible ? (
- <Tooltip title="Hide password.">
- <Icons.EyeInvisibleOutlined />
- </Tooltip>
- ) : (
- <Tooltip title="Show password.">
- <Icons.EyeOutlined />
- </Tooltip>
- )
- }
- role="textbox"
/>
</StyledDiv>
</Col>
diff --git a/superset-frontend/src/features/databases/DatabaseModal/index.tsx
b/superset-frontend/src/features/databases/DatabaseModal/index.tsx
index 425d867689..f77ce16db9 100644
--- a/superset-frontend/src/features/databases/DatabaseModal/index.tsx
+++ b/superset-frontend/src/features/databases/DatabaseModal/index.tsx
@@ -1730,7 +1730,6 @@ const DatabaseModal:
FunctionComponent<DatabaseModalProps> = ({
name: target.name,
value: target.value,
});
- handleClearValidationErrors();
}}
setSSHTunnelLoginMethod={(method: AuthType) =>
setDB({
@@ -1738,6 +1737,9 @@ const DatabaseModal:
FunctionComponent<DatabaseModalProps> = ({
payload: { login_method: method },
})
}
+ isValidating={isValidating}
+ validationErrors={validationErrors}
+ getValidation={() => getValidation(db)}
/>
);