Skip to content

Commit

Permalink
feat(db-connection-ui): Big Query Add Database Form (#14829)
Browse files Browse the repository at this point in the history
* fix(native-filters): Manage default value of filters by superset (#14785)

* fix:fix get permission function

* feat: manage default value in superset

* fix(native-filters): loop bug by simplify state handling (#14788)

* fix: set table name width to not hide icons when name is too long (#14489)

* fix: set table name width to now hide icons when name is too long

* fix: table style

Co-authored-by: einatnielsen <einat.bertenthal@nielsen.com>

* feat(explore): Remove default for time range filter and Metrics (#14661)

* feat(explore): Remove default for time range filter and Metrics

* Merge errors with same messages

* Fix e2e test

* Rename a variable

* Bump packages

* Fix unit tests

* feat: chart gallery search improvement (#14484)

* feat: chart gallery search improvement

* test: adding unit test for VizTypeControl

* fix: lint errors

Co-authored-by: einatnielsen <einat.bertenthal@nielsen.com>

* Update schemas.py

* Update bigquery.py

* Fix tests

* big query form is appearing on the screen

* add name to allow for actions to get picked up

* working post for saving db via paste

* working file upload

* switch to textare

* better styles

* add delete buttong

* save formating

* wrap encrypted_extra

* formatting component

* clear out file input before reloading

* remove default driver

* address comments

* fix things off rebase

* small refactore

* more patches

* checkout space file

* fix variable ref

Co-authored-by: simcha90 <56388545+simcha90@users.noreply.github.com>
Co-authored-by: Ville Brofeldt <33317356+villebro@users.noreply.github.com>
Co-authored-by: Einat Bertenthal <einatbar@users.noreply.github.com>
Co-authored-by: einatnielsen <einat.bertenthal@nielsen.com>
Co-authored-by: Kamil Gabryjelski <kamil.gabryjelski@gmail.com>
Co-authored-by: Beto Dealmeida <roberto@dealmeida.net>
  • Loading branch information
7 people authored Jun 5, 2021
1 parent 120866c commit 298f660
Show file tree
Hide file tree
Showing 4 changed files with 164 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,36 +16,130 @@
* specific language governing permissions and limitations
* under the License.
*/
import React, { FormEvent } from 'react';
import React, { FormEvent, useState } from 'react';
import { SupersetTheme, JsonObject } from '@superset-ui/core';
import { InputProps } from 'antd/lib/input';
import { Select, Button } from 'src/common/components';
import ValidatedInput from 'src/components/Form/LabeledErrorBoundInput';
import { DeleteFilled } from '@ant-design/icons';
import {
formScrollableStyles,
validatedFormStyles,
CredentialInfoForm,
StyledFormHeader,
} from './styles';
import { DatabaseForm, DatabaseObject } from '../types';

enum CredentialInfoOptions {
jsonUpload,
copyPaste,
}

export const FormFieldOrder = [
'host',
'port',
'database',
'username',
'password',
'database_name',
'credentials_info',
];

interface FieldPropTypes {
required: boolean;
changeMethods: { onParametersChange: (value: any) => string } & {
onChange: (value: any) => string;
};
} & { onParametersUploadFileChange: (value: any) => string };
validationErrors: JsonObject | null;
getValidation: () => void;
db?: DatabaseObject;
}

const credentialsInfo = ({
required,
changeMethods,
getValidation,
validationErrors,
}: FieldPropTypes) => {
const [uploadOption, setUploadOption] = useState<string>('upload');
const [fileToUpload, setFileToUpload] = useState<string>(null);
return (
<CredentialInfoForm>
<label className="label-select">
How do you want to enter service account credentials?
</label>
<Select
defaultValue={CredentialInfoOptions.jsonUpload}
style={{ width: '100%' }}
onChange={setUploadOption}
>
<Select.Option value={CredentialInfoOptions.jsonUpload}>
Upload JSON file
</Select.Option>
<Select.Option value={CredentialInfoOptions.copyPaste}>
Copy and Paste JSON credentials
</Select.Option>
</Select>
{uploadOption === 'paste' ? (
<div className="input-container" onChange={changeMethods.onChange}>
<span className="label-select">Service Account</span>
<textarea className="input-form" name="encrypted_extra" />
<span className="label-paste">
Copy and paste the entire service account .json file here
</span>
</div>
) : (
<div className="input-container">
<span className="label-select">Upload Credentials</span>
{!fileToUpload && (
<Button
className="input-upload-btn"
onClick={() => document.getElementById('selectedFile').click()}
>
Choose File
</Button>
)}
{fileToUpload && (
<div className="input-upload-current">
{fileToUpload}
<DeleteFilled
onClick={() => {
setFileToUpload(null);
changeMethods.onParametersChange({
target: {
name: 'encrypted_extra',
value: '',
},
});
}}
/>
</div>
)}

<input
id="selectedFile"
className="input-upload"
type="file"
onChange={async event => {
const file = event?.target?.files[0];
setFileToUpload(file.name);
changeMethods.onParametersChange({
target: {
type: null,
name: 'encrypted_extra',
value: await file.text(),
checked: false,
},
});
document.getElementById('selectedFile').value = null;
}}
/>
</div>
)}
</CredentialInfoForm>
);
};

const hostField = ({
required,
changeMethods,
Expand Down Expand Up @@ -173,12 +267,14 @@ const FORM_FIELD_MAP = {
username: usernameField,
password: passwordField,
database_name: displayField,
credentials_info: credentialsInfo,
};

const DatabaseConnectionForm = ({
dbModel: { name, parameters },
onParametersChange,
onChange,
onParametersUploadFileChange,
validationErrors,
getValidation,
db,
Expand All @@ -193,6 +289,9 @@ const DatabaseConnectionForm = ({
onChange: (
event: FormEvent<InputProps> | { target: HTMLInputElement },
) => void;
onParametersUploadFileChange: (
event: FormEvent<InputProps> | { target: HTMLInputElement },
) => void;
validationErrors: JsonObject | null;
getValidation: () => void;
}) => (
Expand All @@ -218,8 +317,12 @@ const DatabaseConnectionForm = ({
key === 'database_name',
).map(field =>
FORM_FIELD_MAP[field]({
required: parameters.required.includes(field),
changeMethods: { onParametersChange, onChange },
required: parameters.required?.includes(field),
changeMethods: {
onParametersChange,
onChange,
onParametersUploadFileChange,
},
validationErrors,
getValidation,
db,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ import {
DatabaseForm,
CONFIGURATION_METHOD,
} from 'src/views/CRUD/data/database/types';
import Label from 'src/components/Label';
import ExtraOptions from './ExtraOptions';
import SqlAlchemyForm from './SqlAlchemyForm';

Expand Down Expand Up @@ -132,6 +131,7 @@ function dbReducer(
const trimmedState = {
...(state || {}),
};

switch (action.type) {
case ActionType.inputChange:
if (action.payload.type === 'checkbox') {
Expand All @@ -145,6 +145,13 @@ 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: {
Expand Down Expand Up @@ -183,7 +190,6 @@ function dbReducer(
}

const DEFAULT_TAB_KEY = '1';
const FALSY_FORM_VALUES = [undefined, null, ''];

const DatabaseModal: FunctionComponent<DatabaseModalProps> = ({
addDangerToast,
Expand Down Expand Up @@ -262,6 +268,12 @@ const DatabaseModal: FunctionComponent<DatabaseModalProps> = ({
}
} else if (db) {
// Create
if (update.encrypted_extra) {
// wrap encrypted_extra in credentials_info
update.encrypted_extra = JSON.stringify({
credentials_info: JSON.parse(update.encrypted_extra),
});
}
const dbId = await createResource(update as DatabaseObject);
if (dbId) {
setHasConnectedDb(true);
Expand Down Expand Up @@ -406,16 +418,7 @@ const DatabaseModal: FunctionComponent<DatabaseModalProps> = ({
// TODO: we need a centralized engine in one place
available.engine === db?.engine || db?.backend,
) || {};
const disableSave =
!hasConnectedDb &&
(useSqlAlchemyForm
? !(db?.database_name?.trim() && db?.sqlalchemy_uri)
: // disable the button if there is no dbModel.parameters or if
// any required fields are falsy
!dbModel?.parameters ||
!!dbModel.parameters.required.filter(field =>
FALSY_FORM_VALUES.includes(db?.parameters?.[field]),
).length);

return useTabLayout ? (
<Modal
css={(theme: SupersetTheme) => [
Expand All @@ -425,7 +428,6 @@ const DatabaseModal: FunctionComponent<DatabaseModalProps> = ({
formHelperStyles(theme),
]}
name="database"
disablePrimaryButton={disableSave}
data-test="database-modal"
height="600px"
onHandledPrimaryAction={onSave}
Expand Down Expand Up @@ -565,7 +567,6 @@ const DatabaseModal: FunctionComponent<DatabaseModalProps> = ({
formStyles(theme),
]}
name="database"
disablePrimaryButton={disableSave}
height="600px"
onHandledPrimaryAction={onSave}
onHide={onClose}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,46 @@ export const EditHeaderSubtitle = styled.div`
font-weight: bold;
`;

export const CredentialInfoForm = styled.div`
.label-select {
text-transform: uppercase;
color: ${({ theme }) => theme.colors.grayscale.dark1};
font-size: 11px;
margin: 0 0 ${({ theme }) => theme.gridUnit * 2}px;
}
.label-paste {
color: ${({ theme }) => theme.colors.grayscale.light1};
font-size: 11px;
line-height: 16px;
}
.input-container {
margin: ${({ theme }) => theme.gridUnit * 7}px 0;
display: flex;
flex-direction: column;
}
}
.input-form {
height: 100px;
width: 100%;
border: 1px solid ${({ theme }) => theme.colors.grayscale.light2};
resize: vertical;
}
.input-container {
.input-upload {
display: none;
}
.input-upload-current {
display: flex;
justify-content: space-between;
}
.input-upload-btn {
width: ${({ theme }) => theme.gridUnit * 32}px
}
}`;

export const SelectDatabaseStyles = styled.div`
margin: 0 ${({ theme }) => theme.gridUnit * 8}px;
Expand Down
4 changes: 2 additions & 2 deletions superset/db_engine_specs/bigquery.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@

class BigQueryParametersSchema(Schema):
credentials_info = EncryptedField(
required=True, description="Contents of BigQuery JSON credentials.",
description="Contents of BigQuery JSON credentials.",
)


Expand Down Expand Up @@ -313,7 +313,7 @@ def build_sqlalchemy_uri(
project_id = encrypted_extra.get("credentials_info", {}).get("project_id")

if project_id:
return f"{cls.engine}+{cls.default_driver}://{project_id}"
return f"{cls.default_driver}://{project_id}"

raise SupersetGenericDBErrorException(
message="Big Query encrypted_extra is not available.",
Expand Down

0 comments on commit 298f660

Please sign in to comment.