Skip to content

Commit

Permalink
Merge pull request #295 from intellica-tech/feature/trino-connection
Browse files Browse the repository at this point in the history
feat: trino connection
  • Loading branch information
Cartales authored Nov 14, 2024
2 parents e5c6349 + c650021 commit fd71b28
Show file tree
Hide file tree
Showing 7 changed files with 238 additions and 61 deletions.
3 changes: 3 additions & 0 deletions requirements/base.txt
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,9 @@ sqlalchemy==1.4.36
# marshmallow-sqlalchemy
# shillelagh
# sqlalchemy-utils
sqlalchemy-trino==0.5.0
# via
# trino
sqlalchemy-utils==0.38.3
# via
# apache-superset
Expand Down
8 changes: 7 additions & 1 deletion superset-frontend/.eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -145,8 +145,14 @@ module.exports = {
'react/require-default-props': 0,
'react/sort-comp': 0, // TODO: re-enable in separate PR
'react/static-property-placement': 0, // re-enable up for discussion
'prettier/prettier': 'error',
'prettier/prettier': [
'error',
{
endOfLine: 'auto',
},
],
'file-progress/activate': 1,
'linebreak-style': 0,
},
settings: {
'import/resolver': {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { useState } from 'react';

interface FormValues {
[key: string]: any;
}

interface FormErrors {
[key: string]: string;
}

interface ValidationRules {
[key: string]: (value: string | boolean) => string | undefined;
}

const useFormValidationForSQLAlchemyURI = (
initialValues: FormValues,
validationRules: ValidationRules,
) => {
const [sqlAlchemyValues, setValues] = useState<FormValues>(initialValues);
const [sqlAlchemyErrors, setErrors] = useState<FormErrors>({});

const sqlAlchemyHandleChange = (name: string, value: any) => {
setValues({
...sqlAlchemyValues,
[name]: value,
});
};

const sqlAlchemyValidateForm = () => {
const formErrors: FormErrors = {};

Object.keys(validationRules).forEach(fieldName => {
const value = sqlAlchemyValues[fieldName];
const validate = validationRules[fieldName];
const error = validate(value);
if (error) {
formErrors[fieldName] = error;
}
});

setErrors(formErrors);
return Object.keys(formErrors).length === 0;
};

return {
sqlAlchemyValues,
sqlAlchemyErrors,
sqlAlchemyHandleChange,
sqlAlchemyValidateForm,
};
};

export default useFormValidationForSQLAlchemyURI;
161 changes: 109 additions & 52 deletions superset-frontend/src/dvt-modal/body/connection-add/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { dvtConnectionEditSuccessStatus } from 'src/dvt-redux/dvt-connectionRedu
import useFetch from 'src/dvt-hooks/useFetch';
import {
DvtConnectionData,
OtherConnectionDataOptionProps,
OtherOptions,
advancedData,
} from 'src/dvt-modal/dvtConnectionData';
Expand All @@ -21,6 +22,8 @@ import DvtCheckbox from 'src/components/DvtCheckbox';
import DvtJsonEditor from 'src/components/DvtJsonEditor';
import useFormValidation from 'src/dvt-hooks/useFormValidation';
import connectionCreateValidation from 'src/dvt-validation/dvt-connection-create-validation';
import useFormValidationForSQLAlchemyURI from 'src/dvt-hooks/useFormValidationForSQLAlchemyURI';
import connectionCreateValidationForSQLAlchemyURI from 'src/dvt-validation/dvt-sqlalchemy-connection-create-validation';
import {
StyledConnectionAdd,
StyledConnectionAddBody,
Expand All @@ -47,7 +50,9 @@ import {
const DvtConnectionAdd = ({ meta, onClose }: ModalProps) => {
const dispatch = useDispatch();
const [step, setStep] = useState<number>(1);
const [supporedDatabase, setSupporedDatabase] = useState<string>();
const [supporedDatabase, setSupporedDatabase] = useState<
OtherConnectionDataOptionProps | string
>();
const [selectedFile, setSelectedFile] = useState<File | null>(null);
const fileInputRef = useRef<HTMLInputElement>(null);
const [apiUrl, setApiUrl] = useState<string>('');
Expand Down Expand Up @@ -120,11 +125,28 @@ const DvtConnectionAdd = ({ meta, onClose }: ModalProps) => {
switch: meta?.isEdit ? meta.result.parameters.encryption : false,
};

const initialValuesForSQLAlchemyURI = {
display_name: meta?.isEdit
? meta.result.database_name
: selectedConnectionType,
sqlalchemy_uri: meta?.isEdit ? meta.result.sqlalchemy_uri : '',
};

const { values, errors, handleChange, validateForm } = useFormValidation(
initialValues,
connectionCreateValidation,
);

const {
sqlAlchemyValues,
sqlAlchemyErrors,
sqlAlchemyHandleChange,
sqlAlchemyValidateForm,
} = useFormValidationForSQLAlchemyURI(
initialValuesForSQLAlchemyURI,
connectionCreateValidationForSQLAlchemyURI,
);

const [checkbox, setcheckbox] = useState<{
expose: boolean;
createTable: boolean;
Expand Down Expand Up @@ -173,9 +195,12 @@ const DvtConnectionAdd = ({ meta, onClose }: ModalProps) => {
}
};

const handleSupportedDatabaseSelect = (selectedValue: string) => {
const handleSupportedDatabaseSelect = (
selectedValue: OtherConnectionDataOptionProps,
) => {
if (selectedValue) {
setSupporedDatabase(selectedValue);
setSelectedConnectionType(selectedValue?.label);
setTimeout(() => {
setStep(2);
}, 800);
Expand All @@ -192,30 +217,41 @@ const DvtConnectionAdd = ({ meta, onClose }: ModalProps) => {
if (meta?.isEdit) {
DvtConnectionData.find(
connection =>
connection.driver === meta.result.driver &&
setSelectedConnectionType(connection.databaseType),
(connection.engine === meta.result.engine ||
connection.driver === meta.result.driver ||
connection.engine === meta.result.backend) &&
setSelectedConnectionType(connection.databaseMetaType),
);
}
}, []);

const ConnectionDataFindType = DvtConnectionData.find(
connection => connection.databaseType === selectedConnectionType,
connection => connection.databaseMetaType === selectedConnectionType,
);

const connectionAddData = useFetch({
url: apiUrl,
method: step === 3 && meta?.isEdit ? 'PUT' : 'POST',
method:
(step === 3 || (step === 2 && selectedConnectionType === 'Trino')) &&
meta?.isEdit
? 'PUT'
: 'POST',
body: {
configuration_method:
selectedConnectionType === 'PostgreSQL' ||
selectedConnectionType === 'MySQL'
? 'dynamic_form'
: 'sqlalchemy_form',
database_name: values.display_name,
database_name:
selectedConnectionType === 'Trino'
? sqlAlchemyValues.display_name
: values.display_name,
driver: ConnectionDataFindType?.driver,
engine: ConnectionDataFindType?.engine,
engine_information: ConnectionDataFindType?.engine_information,
expose_in_sqllab: true,
extra: ConnectionDataFindType?.extra,
sqlalchemy_uri: sqlAlchemyValues.sqlalchemy_uri,
parameters: {
database: values.database_name,
host: values.host,
Expand All @@ -230,7 +266,7 @@ const DvtConnectionAdd = ({ meta, onClose }: ModalProps) => {
useEffect(() => {
if (step === 2 && connectionAddData.data?.message === 'OK') {
setStep(3);
} else if (step === 3 && connectionAddData.data?.id) {
} else if ((step === 3 || step === 2) && connectionAddData.data?.id) {
onClose();
dispatch(dvtConnectionEditSuccessStatus('connection'));
}
Expand Down Expand Up @@ -269,9 +305,20 @@ const DvtConnectionAdd = ({ meta, onClose }: ModalProps) => {
}, [connectionAddData.loading]);

const handleSubmit = () => {
const isValid = validateForm();
if (isValid) {
setApiUrl('database/validate_parameters/');
if (
selectedConnectionType === 'PostgreSQL' ||
selectedConnectionType === 'MySQL'
) {
const isValid = validateForm();
if (isValid) {
setApiUrl('database/validate_parameters/');
}
} else if (selectedConnectionType === 'Trino') {
const isValid = sqlAlchemyValidateForm();
if (isValid) {
if (meta?.isEdit) setApiUrl(`database/${meta.id}`);
else setApiUrl('database/');
}
}
};

Expand All @@ -285,19 +332,27 @@ const DvtConnectionAdd = ({ meta, onClose }: ModalProps) => {
Select a database to connect
</StyledConnectionAddLabel>
<StyledConnectionAddDatabaseIcons>
{DvtConnectionData.map((connection, index) => (
<StyledConnectionAddDatabaseIcon
key={index}
onClick={() =>
handleConnectionTypeClick(connection.databaseType)
}
>
<Icon fileName="nav_data" style={{ fontSize: '80px' }} />
<StyledConnectionAddDatabaseType>
{connection.databaseType}
</StyledConnectionAddDatabaseType>
</StyledConnectionAddDatabaseIcon>
))}
{DvtConnectionData.map((connection, index) => {
if (
connection.databaseType === 'PostgreSQL' ||
connection.databaseType === 'MySQL'
) {
return (
<StyledConnectionAddDatabaseIcon
key={index}
onClick={() =>
handleConnectionTypeClick(connection.databaseType)
}
>
<Icon fileName="nav_data" style={{ fontSize: '80px' }} />
<StyledConnectionAddDatabaseType>
{connection.databaseType}
</StyledConnectionAddDatabaseType>
</StyledConnectionAddDatabaseIcon>
);
}
return null;
})}
</StyledConnectionAddDatabaseIcons>
<StyledConnectionAddLabel>
{t('Or choose from a list of other databases we support: ')}
Expand All @@ -306,7 +361,9 @@ const DvtConnectionAdd = ({ meta, onClose }: ModalProps) => {
label={t('SUPPORTED DATABASES')}
placeholder="Choose a database..."
selectedValue={supporedDatabase}
setSelectedValue={handleSupportedDatabaseSelect}
setSelectedValue={database =>
handleSupportedDatabaseSelect(database)
}
data={OtherOptions}
typeDesign="form"
/>
Expand Down Expand Up @@ -427,33 +484,33 @@ const DvtConnectionAdd = ({ meta, onClose }: ModalProps) => {
</StyledConnectionAddLabel>
Enter Primary Credentials Need help? Learn how to connect your
database here.
<DvtInput
value={input.display_name}
onChange={text =>
setInput({ ...input, display_name: text })
}
label={t('DISPLAY NAME')}
popoverLabel={t('Cannot be empty')}
popoverDirection="right"
importantLabel={t(
'Pick a name to help you identify this database.',
)}
/>
<DvtInput
value={input.Sqlalchemy_Uri}
onChange={text =>
setInput({ ...input, Sqlalchemy_Uri: text })
}
label={t('SQLALCHEMY URI')}
importantLabel={t(
'Refer to the for more information on how to structure your URI.',
)}
placeholder={t(
'dialect+driver://username:password@host:port/database',
)}
popoverLabel={t('Cannot be empty')}
popoverDirection="right"
/>
{ConnectionDataFindType?.data.map(
(
data: {
title: string;
value: any;
type: string;
importantLabel: string;
placeholder: string;
popoverLabel: string;
},
index,
) => (
<DvtInput
key={index}
value={sqlAlchemyValues[data.value]}
onChange={text =>
sqlAlchemyHandleChange(data.value, text)
}
label={data.title}
importantLabel={data.importantLabel}
placeholder={data.placeholder}
popoverLabel={data.popoverLabel}
popoverDirection="right"
error={sqlAlchemyErrors[data.value]}
/>
),
)}
<DvtButton
bold
label={t('TEST CONNECTION')}
Expand Down
Loading

0 comments on commit fd71b28

Please sign in to comment.