+
Service Account
Copy and paste the entire service account .json file here
) : (
-
-
Upload Credentials
+
infoTooltip(theme)}
+ >
+
+ Upload Credentials{' '}
+
+
+
{!fileToUpload && (
{
setFileToUpload(null);
changeMethods.onParametersChange({
target: {
- name: 'encrypted_extra',
+ name: 'credentials_info',
value: '',
},
});
@@ -146,7 +163,7 @@ const CredentialsInfo = ({ changeMethods, isEditMode, db }: FieldPropTypes) => {
changeMethods.onParametersChange({
target: {
type: null,
- name: 'encrypted_extra',
+ name: 'credentials_info',
value: await file?.text(),
checked: false,
},
@@ -174,6 +191,10 @@ const hostField = ({
name="host"
value={db?.parameters?.host}
required={required}
+ hasTooltip
+ tooltipText={t(
+ 'This can be either an IP address (e.g. 127.0.0.1) or a domain name (e.g. mydatabase.com).',
+ )}
validationMethods={{ onBlur: getValidation }}
errorMessage={validationErrors?.host}
placeholder="e.g. 127.0.0.1"
@@ -329,8 +350,8 @@ const forceSSLField = ({
/>
SSL
);
diff --git a/superset-frontend/src/views/CRUD/data/database/DatabaseModal/ExtraOptions.tsx b/superset-frontend/src/views/CRUD/data/database/DatabaseModal/ExtraOptions.tsx
index a4d5f68db074c..a2a49b2f707b9 100644
--- a/superset-frontend/src/views/CRUD/data/database/DatabaseModal/ExtraOptions.tsx
+++ b/superset-frontend/src/views/CRUD/data/database/DatabaseModal/ExtraOptions.tsx
@@ -341,10 +341,8 @@ const ExtraOptions = ({
{t(
- 'JSON string containing additional connection configuration. ' +
- 'This is used to provide connection information for systems ' +
- 'like Hive, Presto and BigQuery whih do not conform to the ' +
- 'username:password syntax normally used by SQLAlchemy.',
+ 'Optional CA_BUNDLE contents to validate HTTPS requests. Only ' +
+ 'available on certain database engines.',
)}
diff --git a/superset-frontend/src/views/CRUD/data/database/DatabaseModal/ModalHeader.tsx b/superset-frontend/src/views/CRUD/data/database/DatabaseModal/ModalHeader.tsx
index ac8e2ad04d98a..610f96bddb4dc 100644
--- a/superset-frontend/src/views/CRUD/data/database/DatabaseModal/ModalHeader.tsx
+++ b/superset-frontend/src/views/CRUD/data/database/DatabaseModal/ModalHeader.tsx
@@ -24,6 +24,7 @@ import {
CreateHeaderTitle,
CreateHeaderSubtitle,
StyledFormHeader,
+ StyledStickyHeader,
} from './styles';
import { DatabaseForm, DatabaseObject } from '../types';
@@ -50,6 +51,7 @@ const ModalHeader = ({
db,
dbName,
dbModel,
+ editNewDb,
}: {
isLoading: boolean;
isEditMode: boolean;
@@ -58,16 +60,17 @@ const ModalHeader = ({
db: Partial
| null;
dbName: string;
dbModel: DatabaseForm;
+ editNewDb?: boolean;
}) => {
const isEditHeader = (
- <>
+
{db?.backend}
{dbName}
- >
+
);
const useSqlAlchemyFormHeader = (
- <>
- Step 2 of 2
+
+ STEP 2 OF 2
Enter Primary Credentials
Need help? Learn how to connect your database{' '}
@@ -76,11 +79,11 @@ const ModalHeader = ({
.
- >
+
);
const hasConnectedDbHeader = (
- Step 3 of 3
+ STEP 3 OF 3
Your database was successfully connected! Here are some optional
settings for your database
@@ -98,18 +101,27 @@ const ModalHeader = ({
);
const hasDbHeader = (
-
- Step 2 of 3
- Enter the required {dbModel.name} credentials
-
- Need help? Learn more about connecting to {dbModel.name}.
-
-
+
+
+ Step 2 of 3
+ Enter the required {dbModel.name} credentials
+
+ Need help? Learn more about{' '}
+
+ connecting to {dbModel.name}.
+
+
+
+
);
const noDbHeader = (
-
Step 1 of 3
+
STEP 1 OF 3
Select a database to connect
@@ -122,10 +134,10 @@ const ModalHeader = ({
if (useSqlAlchemyForm) {
return useSqlAlchemyFormHeader;
}
- if (hasConnectedDb) {
+ if (hasConnectedDb && !editNewDb) {
return hasConnectedDbHeader;
}
- if (db) {
+ if (db || editNewDb) {
return hasDbHeader;
}
return noDbHeader;
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 86699fe2b2759..6f106198676c5 100644
--- a/superset-frontend/src/views/CRUD/data/database/DatabaseModal/index.tsx
+++ b/superset-frontend/src/views/CRUD/data/database/DatabaseModal/index.tsx
@@ -30,10 +30,12 @@ import React, {
Reducer,
} from 'react';
import Tabs from 'src/components/Tabs';
-import { Alert, Select } from 'src/common/components';
+import { Select } from 'src/common/components';
+import Alert from 'src/components/Alert';
import Modal from 'src/components/Modal';
import Button from 'src/components/Button';
import IconButton from 'src/components/IconButton';
+import InfoTooltip from 'src/components/InfoTooltip';
import withToasts from 'src/messageToasts/enhancers/withToasts';
import {
testDatabaseConnection,
@@ -66,6 +68,7 @@ import {
formStyles,
StyledBasicTab,
SelectDatabaseStyles,
+ infoTooltip,
StyledFooterButton,
StyledStickyHeader,
} from './styles';
@@ -193,13 +196,6 @@ function dbReducer(
[action.payload.name]: action.payload.value,
};
case ActionType.parametersChange:
- if (action.payload.name === 'encrypted_extra') {
- return {
- ...trimmedState,
- encrypted_extra: action.payload.value,
- parameters: {},
- };
- }
return {
...trimmedState,
parameters: {
@@ -243,6 +239,21 @@ function dbReducer(
).toString();
}
+ if (action.payload.backend === 'bigquery') {
+ return {
+ ...action.payload,
+ engine: trimmedState.engine,
+ configuration_method: action.payload.configuration_method,
+ extra_json: deserializeExtraJSON,
+ parameters: {
+ query,
+ credentials_info: JSON.stringify(
+ action.payload?.parameters?.credentials_info || '',
+ ),
+ },
+ };
+ }
+
return {
...action.payload,
engine: trimmedState.engine,
@@ -251,9 +262,6 @@ function dbReducer(
parameters: {
...action.payload.parameters,
query,
- credentials_info: JSON.stringify(
- action.payload?.parameters?.credentials_info || '',
- ),
},
};
case ActionType.dbSelected:
@@ -292,6 +300,7 @@ const DatabaseModal: FunctionComponent = ({
] = useDatabaseValidation();
const [hasConnectedDb, setHasConnectedDb] = useState(false);
const [dbName, setDbName] = useState('');
+ const [editNewDb, setEditNewDb] = useState(false);
const [isLoading, setLoading] = useState(false);
const conf = useCommonConf();
const dbImages = getDatabaseImages();
@@ -346,80 +355,86 @@ const DatabaseModal: FunctionComponent = ({
setDB({ type: ActionType.reset });
setHasConnectedDb(false);
setValidationErrors(null); // reset validation errors on close
+ setEditNewDb(false);
onHide();
};
const onSave = async () => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { id, ...update } = db || {};
- if (update?.parameters?.query) {
- // convert query params into dictionary
- update.parameters.query = JSON.parse(
- `{"${decodeURI((update?.parameters?.query as string) || '')
- .replace(/"/g, '\\"')
- .replace(/&/g, '","')
- .replace(/=/g, '":"')}"}`,
- );
+
+ // Clone DB object
+ const dbToUpdate = JSON.parse(JSON.stringify(update));
+ if (dbToUpdate.configuration_method === CONFIGURATION_METHOD.DYNAMIC_FORM) {
+ if (dbToUpdate?.parameters?.query) {
+ // convert query params into dictionary
+ dbToUpdate.parameters.query = JSON.parse(
+ `{"${decodeURI((dbToUpdate?.parameters?.query as string) || '')
+ .replace(/"/g, '\\"')
+ .replace(/&/g, '","')
+ .replace(/=/g, '":"')}"}`,
+ );
+ } else if (dbToUpdate?.parameters?.query === '') {
+ dbToUpdate.parameters.query = {};
+ }
+
+ const engine = dbToUpdate.backend || dbToUpdate.engine;
+ if (engine === 'bigquery' && dbToUpdate.parameters?.credentials_info) {
+ // wrap encrypted_extra in credentials_info only for BigQuery
+ dbToUpdate.encrypted_extra = JSON.stringify({
+ credentials_info: JSON.parse(dbToUpdate.parameters?.credentials_info),
+ });
+ }
}
if (db?.id) {
- if (update?.extra_json) {
+ if (dbToUpdate?.extra_json) {
// convert extra_json to back to string
- update.extra = JSON.stringify({
- ...update.extra_json,
+ dbToUpdate.extra = JSON.stringify({
+ ...dbToUpdate.extra_json,
metadata_params: JSON.parse(
- update?.extra_json?.metadata_params as string,
+ dbToUpdate?.extra_json?.metadata_params as string,
),
engine_params: JSON.parse(
- update?.extra_json?.engine_params as string,
+ dbToUpdate?.extra_json?.engine_params as string,
),
schemas_allowed_for_csv_upload: JSON.parse(
- update?.extra_json?.schemas_allowed_for_csv_upload as string,
+ dbToUpdate?.extra_json?.schemas_allowed_for_csv_upload as string,
),
});
}
setLoading(true);
const result = await updateResource(
db.id as number,
- update as DatabaseObject,
+ dbToUpdate as DatabaseObject,
);
if (result) {
if (onDatabaseAdd) {
onDatabaseAdd();
}
- onClose();
+ if (!editNewDb) {
+ onClose();
+ }
}
} else if (db) {
// Create
- if (
- update.engine === 'bigquery' &&
- update.configuration_method === CONFIGURATION_METHOD.DYNAMIC_FORM &&
- update.encrypted_extra
- ) {
- // wrap encrypted_extra in credentials_info only for BigQuery
- update.encrypted_extra = JSON.stringify({
- credentials_info: JSON.parse(update.encrypted_extra),
- });
- }
-
- if (update?.extra_json) {
+ if (dbToUpdate?.extra_json) {
// convert extra_json to back to string
- update.extra = JSON.stringify({
- ...update.extra_json,
+ dbToUpdate.extra = JSON.stringify({
+ ...dbToUpdate.extra_json,
metadata_params: JSON.parse(
- update?.extra_json?.metadata_params as string,
+ dbToUpdate?.extra_json?.metadata_params as string,
),
engine_params: JSON.parse(
- update?.extra_json?.engine_params as string,
+ dbToUpdate?.extra_json?.engine_params as string,
),
schemas_allowed_for_csv_upload: JSON.parse(
- update?.extra_json?.schemas_allowed_for_csv_upload as string,
+ dbToUpdate?.extra_json?.schemas_allowed_for_csv_upload as string,
),
});
}
setLoading(true);
- await getValidation(db);
- const dbId = await createResource(update as DatabaseObject);
+ const dbId = await createResource(dbToUpdate as DatabaseObject);
if (dbId) {
setHasConnectedDb(true);
if (onDatabaseAdd) {
@@ -432,6 +447,7 @@ const DatabaseModal: FunctionComponent = ({
}
}
}
+ setEditNewDb(false);
setLoading(false);
};
@@ -496,6 +512,7 @@ const DatabaseModal: FunctionComponent = ({
antDAlertStyles(theme)}
type="info"
message={t('Want to add a new database?')}
@@ -508,8 +525,9 @@ const DatabaseModal: FunctionComponent = ({
target="_blank"
rel="noopener noreferrer"
>
- here.
+ here
+ .
>
}
/>
@@ -531,10 +549,19 @@ const DatabaseModal: FunctionComponent = ({
);
- const renderModalFooter = () =>
- db // if db show back + connect
- ? [
- !hasConnectedDb && (
+ const handleBackButtonOnFinish = () => {
+ if (dbFetched) {
+ fetchResource(dbFetched.id as number);
+ }
+ setEditNewDb(true);
+ };
+
+ const renderModalFooter = () => {
+ if (db) {
+ // if db show back + connenct
+ if (!hasConnectedDb || editNewDb) {
+ return (
+ <>