Skip to content

Commit

Permalink
feat: save database with new dynamic form (#14583)
Browse files Browse the repository at this point in the history
* split db modal file

* split db modal file

* hook up available databases

* add comment
  • Loading branch information
eschutho committed May 21, 2021
1 parent dd31853 commit c7aee4e
Show file tree
Hide file tree
Showing 10 changed files with 658 additions and 131 deletions.
19 changes: 11 additions & 8 deletions superset-frontend/src/views/CRUD/data/database/DatabaseList.tsx
Expand Up @@ -29,8 +29,8 @@ import { Tooltip } from 'src/components/Tooltip';
import Icons from 'src/components/Icons';
import ListView, { FilterOperator, Filters } from 'src/components/ListView';
import { commonMenuData } from 'src/views/CRUD/data/common';
import DatabaseModal from 'src/views/CRUD/data/database/DatabaseModal';
import ImportModelsModal from 'src/components/ImportModal/index';
import DatabaseModal from './DatabaseModal';
import { DatabaseObject } from './types';

const PAGE_SIZE = 25;
Expand Down Expand Up @@ -147,10 +147,13 @@ function DatabaseList({ addDangerToast, addSuccessToast }: DatabaseListProps) {
);
}

function handleDatabaseEdit(database: DatabaseObject) {
// Set database and open modal
function handleDatabaseEditModal({
database = null,
modalOpen = false,
}: { database?: DatabaseObject | null; modalOpen?: boolean } = {}) {
// Set database and modal
setCurrentDatabase(database);
setDatabaseModalOpen(true);
setDatabaseModalOpen(modalOpen);
}

const canCreate = hasPerm('can_write');
Expand All @@ -176,8 +179,7 @@ function DatabaseList({ addDangerToast, addSuccessToast }: DatabaseListProps) {
buttonStyle: 'primary',
onClick: () => {
// Ensure modal will be opened in add mode
setCurrentDatabase(null);
setDatabaseModalOpen(true);
handleDatabaseEditModal({ modalOpen: true });
},
},
];
Expand Down Expand Up @@ -298,7 +300,8 @@ function DatabaseList({ addDangerToast, addSuccessToast }: DatabaseListProps) {
},
{
Cell: ({ row: { original } }: any) => {
const handleEdit = () => handleDatabaseEdit(original);
const handleEdit = () =>
handleDatabaseEditModal({ database: original, modalOpen: true });
const handleDelete = () => openDatabaseDeleteModal(original);
const handleExport = () => handleDatabaseExport(original);
if (!canEdit && !canDelete && !canExport) {
Expand Down Expand Up @@ -416,7 +419,7 @@ function DatabaseList({ addDangerToast, addSuccessToast }: DatabaseListProps) {
<DatabaseModal
databaseId={currentDatabase?.id}
show={databaseModalOpen}
onHide={() => setDatabaseModalOpen(false)}
onHide={handleDatabaseEditModal}
onDatabaseAdd={() => {
refreshData();
}}
Expand Down
@@ -0,0 +1,158 @@
/**
* 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, { FormEvent } from 'react';
import cx from 'classnames';
import { InputProps } from 'antd/lib/input';
import { FormLabel, FormItem } from 'src/components/Form';
import { Input } from 'src/common/components';
import { StyledFormHeader, formScrollableStyles } from './styles';
import { DatabaseForm } from '../types';

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

const CHANGE_METHOD = {
onChange: 'onChange',
onPropertiesChange: 'onPropertiesChange',
};

const FORM_FIELD_MAP = {
host: {
description: 'Host',
type: 'text',
className: 'w-50',
placeholder: 'e.g. 127.0.0.1',
changeMethod: CHANGE_METHOD.onPropertiesChange,
},
port: {
description: 'Port',
type: 'text',
className: 'w-50',
placeholder: 'e.g. 5432',
changeMethod: CHANGE_METHOD.onPropertiesChange,
},
database: {
description: 'Database name',
type: 'text',
label:
'Copy the name of the PostgreSQL database you are trying to connect to.',
placeholder: 'e.g. world_population',
changeMethod: CHANGE_METHOD.onPropertiesChange,
},
username: {
description: 'Username',
type: 'text',
placeholder: 'e.g. Analytics',
changeMethod: CHANGE_METHOD.onPropertiesChange,
},
password: {
description: 'Password',
type: 'text',
placeholder: 'e.g. ********',
changeMethod: CHANGE_METHOD.onPropertiesChange,
},
database_name: {
description: 'Display Name',
type: 'text',
label: 'Pick a nickname for this database to display as in Superset.',
changeMethod: CHANGE_METHOD.onChange,
},
query: {
additionalProperties: {},
description: 'Additional parameters',
type: 'object',
changeMethod: CHANGE_METHOD.onPropertiesChange,
},
};

const DatabaseConnectionForm = ({
dbModel: { name, parameters },
onParametersChange,
onChange,
}: {
dbModel: DatabaseForm;
onParametersChange: (
event: FormEvent<InputProps> | { target: HTMLInputElement },
) => void;
onChange: (
event: FormEvent<InputProps> | { target: HTMLInputElement },
) => void;
}) => (
<>
<StyledFormHeader>
<h4>Enter the required {name} credentials</h4>
<p className="helper">
Need help? Learn more about connecting to {name}.
</p>
</StyledFormHeader>
<div css={formScrollableStyles}>
{parameters &&
FormFieldOrder.filter(
(key: string) =>
Object.keys(parameters.properties).includes(key) ||
key === 'database_name',
).map(field => {
const {
className,
description,
type,
placeholder,
label,
changeMethod,
} = FORM_FIELD_MAP[field];
const onEdit =
changeMethod === CHANGE_METHOD.onChange
? onChange
: onParametersChange;
return (
<FormItem
className={cx(className, `form-group-${className}`)}
key={field}
>
<FormLabel
htmlFor={field}
required={parameters.required.includes(field)}
>
{description}
</FormLabel>
<Input
name={field}
type={type}
id={field}
autoComplete="off"
placeholder={placeholder}
onChange={onEdit}
/>
<p className="helper">{label}</p>
</FormItem>
);
})}
</div>
</>
);

export const FormFieldMap = FORM_FIELD_MAP;

export default DatabaseConnectionForm;
Expand Up @@ -18,15 +18,16 @@
*/
import React, { ChangeEvent, EventHandler } from 'react';
import cx from 'classnames';
import { t } from '@superset-ui/core';
import { t, SupersetTheme } from '@superset-ui/core';
import InfoTooltip from 'src/components/InfoTooltip';
import IndeterminateCheckbox from 'src/components/IndeterminateCheckbox';
import Collapse from 'src/components/Collapse';
import {
StyledInputContainer,
StyledJsonEditor,
StyledExpandableForm,
} from 'src/views/CRUD/data/database/DatabaseModal/styles';
antdCollapseStyles,
} from './styles';
import { DatabaseObject } from '../types';

const defaultExtra =
Expand All @@ -48,7 +49,11 @@ const ExtraOptions = ({
const createAsOpen = !!(db?.allow_ctas || db?.allow_cvas);

return (
<Collapse expandIconPosition="right" accordion>
<Collapse
expandIconPosition="right"
accordion
css={(theme: SupersetTheme) => antdCollapseStyles(theme)}
>
<Collapse.Panel
header={
<div>
Expand Down
Expand Up @@ -17,9 +17,9 @@
* under the License.
*/
import React, { EventHandler, ChangeEvent, MouseEvent } from 'react';
import { t, supersetTheme } from '@superset-ui/core';
import { t, SupersetTheme } from '@superset-ui/core';
import Button from 'src/components/Button';
import { StyledInputContainer } from './styles';
import { StyledInputContainer, wideButton } from './styles';

import { DatabaseObject } from '../types';

Expand All @@ -45,7 +45,7 @@ const SqlAlchemyTab = ({
type="text"
name="database_name"
value={db?.database_name || ''}
placeholder={t('Name your dataset')}
placeholder={t('Name your database')}
onChange={onInputChange}
/>
</div>
Expand All @@ -71,25 +71,22 @@ const SqlAlchemyTab = ({
/>
</div>
<div className="helper">
{t('Refer to the ')}
{t('Refer to the')}{' '}
<a
href={conf?.SQLALCHEMY_DOCS_URL ?? ''}
target="_blank"
rel="noopener noreferrer"
>
{conf?.SQLALCHEMY_DISPLAY_TEXT ?? ''}
</a>
{t(' for more information on how to structure your URI.')}
</a>{' '}
{t('for more information on how to structure your URI.')}
</div>
</StyledInputContainer>
<Button
onClick={testConnection}
cta
buttonStyle="link"
style={{
width: '100%',
border: `1px solid ${supersetTheme.colors.primary.base}`,
}}
css={(theme: SupersetTheme) => wideButton(theme)}
>
{t('Test connection')}
</Button>
Expand Down

0 comments on commit c7aee4e

Please sign in to comment.