feat: restyle database modal (#14092)

* restyle database modal

* change name of tab to Basic

* update test with RTL better RTL render statement

* change color and position of required asterisk

* refactor db logic
This commit is contained in:
Elizabeth Thompson 2021-04-20 15:21:07 -07:00 committed by GitHub
parent 97e086051c
commit ff665fa5a7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 626 additions and 492 deletions

View File

@ -23,7 +23,6 @@ import * as redux from 'react-redux';
import { styledMount as mount } from 'spec/helpers/theming';
import { render, screen } from 'spec/helpers/testing-library';
import userEvent from '@testing-library/user-event';
import { supersetTheme, ThemeProvider } from '@superset-ui/core';
import { Provider } from 'react-redux';
import DatabaseModal from 'src/views/CRUD/data/database/DatabaseModal';
import Modal from 'src/components/Modal';
@ -87,9 +86,9 @@ describe('DatabaseModal', () => {
it('renders a Tabs menu', () => {
expect(wrapper.find(Tabs)).toExist();
});
it('renders five TabPanes', () => {
expect(wrapper.find(Tabs.TabPane)).toExist();
expect(wrapper.find(Tabs.TabPane)).toHaveLength(5);
it('renders two TabPanes', () => {
expect(wrapper.find('.ant-tabs-tab')).toExist();
expect(wrapper.find('.ant-tabs-tab')).toHaveLength(2);
});
it('renders input elements for Connection section', () => {
expect(wrapper.find('input[name="database_name"]')).toExist();
@ -101,22 +100,24 @@ describe('DatabaseModal', () => {
describe('initial load', () => {
it('hides the forms from the db when not selected', () => {
render(
<ThemeProvider theme={supersetTheme}>
<Provider store={store}>
<DatabaseModal
show
database={{
expose_in_sqllab: false,
allow_ctas: false,
allow_cvas: false,
}}
/>
</Provider>
</ThemeProvider>,
<DatabaseModal
show
database={{
expose_in_sqllab: false,
allow_ctas: false,
allow_cvas: false,
}}
/>,
{ useRedux: true },
);
// Select SQL Lab settings tab
// Select Advanced tab
const advancedTab = screen.getByRole('tab', {
name: /advanced/i,
});
userEvent.click(advancedTab);
// Select SQL Lab tab
const sqlLabSettingsTab = screen.getByRole('tab', {
name: /sql lab settings/i,
name: /sql lab/i,
});
userEvent.click(sqlLabSettingsTab);
@ -129,21 +130,22 @@ describe('DatabaseModal', () => {
});
});
it('renders all settings when "Expose in SQL Lab" is checked', () => {
render(
<ThemeProvider theme={supersetTheme}>
<Provider store={store}>
<DatabaseModal {...dbProps} />
</Provider>
</ThemeProvider>,
);
render(<DatabaseModal {...dbProps} />, { useRedux: true });
// Select SQL Lab settings tab
// Select Advanced tab
const advancedTab = screen.getByRole('tab', {
name: /advanced/i,
});
userEvent.click(advancedTab);
// Select SQL Lab tab
const sqlLabSettingsTab = screen.getByRole('tab', {
name: /sql lab settings/i,
name: /sql lab/i,
});
userEvent.click(sqlLabSettingsTab);
// Grab all SQL Lab Settings by their labels
// Grab all SQL Lab settings by their labels
// const exposeInSqlLab = screen.getByText('Expose in SQL Lab');
const exposeInSqlLab = screen.getByRole('checkbox', {
name: /expose in sql lab/i,
@ -165,17 +167,17 @@ describe('DatabaseModal', () => {
});
it('renders the schema field when allowCTAS is checked', () => {
render(
<ThemeProvider theme={supersetTheme}>
<Provider store={store}>
<DatabaseModal {...dbProps} />
</Provider>
</ThemeProvider>,
);
render(<DatabaseModal {...dbProps} />, { useRedux: true });
// Select SQL Lab settings tab
// Select Advanced tab
const advancedTab = screen.getByRole('tab', {
name: /advanced/i,
});
userEvent.click(advancedTab);
// Select SQL Lab tab
const sqlLabSettingsTab = screen.getByRole('tab', {
name: /sql lab settings/i,
name: /sql lab/i,
});
userEvent.click(sqlLabSettingsTab);
// Grab CTAS & schema field by their labels
@ -195,17 +197,17 @@ describe('DatabaseModal', () => {
});
it('renders the schema field when allowCVAS is checked', () => {
render(
<ThemeProvider theme={supersetTheme}>
<Provider store={store}>
<DatabaseModal {...dbProps} />
</Provider>
</ThemeProvider>,
);
render(<DatabaseModal {...dbProps} />, { useRedux: true });
// Select SQL Lab settings tab
// Select Advanced tab
const advancedTab = screen.getByRole('tab', {
name: /advanced/i,
});
userEvent.click(advancedTab);
// Select SQL Lab tab
const sqlLabSettingsTab = screen.getByRole('tab', {
name: /sql lab settings/i,
name: /sql lab/i,
});
userEvent.click(sqlLabSettingsTab);
// Grab CVAS by it's label & schema field
@ -225,17 +227,17 @@ describe('DatabaseModal', () => {
});
it('renders the schema field when both allowCTAS and allowCVAS are checked', () => {
render(
<ThemeProvider theme={supersetTheme}>
<Provider store={store}>
<DatabaseModal {...dbProps} />
</Provider>
</ThemeProvider>,
);
render(<DatabaseModal {...dbProps} />, { useRedux: true });
// Select SQL Lab settings tab
// Select Advanced tab
const advancedTab = screen.getByRole('tab', {
name: /advanced/i,
});
userEvent.click(advancedTab);
// Select SQL Lab tab
const sqlLabSettingsTab = screen.getByRole('tab', {
name: /sql lab settings/i,
name: /sql lab/i,
});
userEvent.click(sqlLabSettingsTab);
// Grab CTAS and CVAS by their labels, & schema field

View File

@ -17,21 +17,27 @@
* under the License.
*/
import React, { FunctionComponent, useState, useEffect } from 'react';
import { styled, t } from '@superset-ui/core';
import cx from 'classnames';
import InfoTooltip from 'src/components/InfoTooltip';
import { t, supersetTheme } from '@superset-ui/core';
import {
useSingleViewResource,
testDatabaseConnection,
} from 'src/views/CRUD/hooks';
import withToasts from 'src/messageToasts/enhancers/withToasts';
import Icon from 'src/components/Icon';
import Modal from 'src/components/Modal';
import Tabs from 'src/common/components/Tabs';
import Button from 'src/components/Button';
import IndeterminateCheckbox from 'src/components/IndeterminateCheckbox';
import { JsonEditor } from 'src/components/AsyncAceEditor';
import Collapse from 'src/components/Collapse';
import { DatabaseObject } from './types';
import { useCommonConf } from './state';
import {
StyledModal,
StyledInputContainer,
StyledJsonEditor,
StyledExpandableForm,
StyledRequiredTab,
} from './styles';
interface DatabaseModalProps {
addDangerToast: (msg: string) => void;
@ -43,118 +49,6 @@ interface DatabaseModalProps {
}
const DEFAULT_TAB_KEY = '1';
const EXPOSE_SQLLAB_FORM_HEIGHT = '270px';
const CTAS_CVAS_SCHEMA_FORM_HEIGHT = '94px';
const StyledIcon = styled(Icon)`
margin: auto ${({ theme }) => theme.gridUnit * 2}px auto 0;
`;
const StyledInputContainer = styled.div`
margin-bottom: ${({ theme }) => theme.gridUnit * 2}px;
&.extra-container {
padding-top: 8px;
}
&.expandable {
height: 0;
overflow: hidden;
transition: height 0.25s;
margin-left: ${({ theme }) => theme.gridUnit * 8}px;
padding: 0;
&.open {
height: ${CTAS_CVAS_SCHEMA_FORM_HEIGHT};
}
}
.helper {
display: block;
padding: ${({ theme }) => theme.gridUnit}px 0;
color: ${({ theme }) => theme.colors.grayscale.base};
font-size: ${({ theme }) => theme.typography.sizes.s - 1}px;
text-align: left;
.required {
margin-left: ${({ theme }) => theme.gridUnit / 2}px;
color: ${({ theme }) => theme.colors.error.base};
}
}
.input-container {
display: flex;
align-items: top;
label {
display: flex;
margin-left: ${({ theme }) => theme.gridUnit * 2}px;
margin-top: ${({ theme }) => theme.gridUnit * 0.75}px;
font-family: ${({ theme }) => theme.typography.families.sansSerif};
font-size: ${({ theme }) => theme.typography.sizes.m}px;
}
i {
margin: 0 ${({ theme }) => theme.gridUnit}px;
}
}
input,
textarea {
flex: 1 1 auto;
}
textarea {
height: 160px;
resize: none;
}
input::placeholder,
textarea::placeholder {
color: ${({ theme }) => theme.colors.grayscale.light1};
}
textarea,
input[type='text'],
input[type='number'] {
padding: ${({ theme }) => theme.gridUnit * 1.5}px
${({ theme }) => theme.gridUnit * 2}px;
border-style: none;
border: 1px solid ${({ theme }) => theme.colors.grayscale.light2};
border-radius: ${({ theme }) => theme.gridUnit}px;
&[name='name'] {
flex: 0 1 auto;
width: 40%;
}
&[name='sqlalchemy_uri'] {
margin-right: ${({ theme }) => theme.gridUnit * 3}px;
}
}
`;
const StyledJsonEditor = styled(JsonEditor)`
flex: 1 1 auto;
border: 1px solid ${({ theme }) => theme.colors.grayscale.light2};
border-radius: ${({ theme }) => theme.gridUnit}px;
`;
const StyledExpandableForm = styled.div`
padding-top: ${({ theme }) => theme.gridUnit}px;
.input-container {
padding-top: ${({ theme }) => theme.gridUnit}px;
padding-bottom: ${({ theme }) => theme.gridUnit}px;
}
&.expandable {
height: 0;
overflow: hidden;
transition: height 0.25s;
margin-left: ${({ theme }) => theme.gridUnit * 7}px;
&.open {
height: ${EXPOSE_SQLLAB_FORM_HEIGHT};
}
}
`;
const DatabaseModal: FunctionComponent<DatabaseModalProps> = ({
addDangerToast,
@ -196,14 +90,11 @@ const DatabaseModal: FunctionComponent<DatabaseModalProps> = ({
const connection = {
sqlalchemy_uri: db ? db.sqlalchemy_uri : '',
database_name:
db && db.database_name.trim().length
? db.database_name.trim()
: undefined,
impersonate_user: db ? db.impersonate_user || undefined : undefined,
extra: db && db.extra && db.extra.length ? db.extra : undefined,
encrypted_extra: db ? db.encrypted_extra || undefined : undefined,
server_cert: db ? db.server_cert || undefined : undefined,
database_name: db?.database_name?.trim() || undefined,
impersonate_user: db?.impersonate_user || undefined,
extra: db?.extra || undefined,
encrypted_extra: db?.encrypted_extra || undefined,
server_cert: db?.server_cert || undefined,
};
testDatabaseConnection(connection, addDangerToast, addSuccessToast);
@ -219,8 +110,8 @@ const DatabaseModal: FunctionComponent<DatabaseModalProps> = ({
if (isEditMode) {
// Edit
const update: DatabaseObject = {
database_name: db ? db.database_name.trim() : '',
sqlalchemy_uri: db ? db.sqlalchemy_uri : '',
database_name: db?.database_name.trim() || '',
sqlalchemy_uri: db?.sqlalchemy_uri || '',
...db,
};
@ -296,12 +187,7 @@ const DatabaseModal: FunctionComponent<DatabaseModalProps> = ({
};
const validate = () => {
if (
db &&
db.database_name.trim().length &&
db.sqlalchemy_uri &&
db.sqlalchemy_uri.length
) {
if (db?.database_name?.trim() && db?.sqlalchemy_uri) {
setDisableSave(false);
} else {
setDisableSave(true);
@ -356,39 +242,27 @@ const DatabaseModal: FunctionComponent<DatabaseModalProps> = ({
const createAsOpen = !!(db?.allow_ctas || db?.allow_cvas);
return (
<Modal
<StyledModal
name="database"
className="database-modal"
disablePrimaryButton={disableSave}
height="600px"
onHandledPrimaryAction={onSave}
onHide={hide}
primaryButtonName={isEditMode ? t('Save') : t('Add')}
width="750px"
width="500px"
show={show}
title={
<h4>
<StyledIcon name="database" />
{isEditMode ? t('Edit database') : t('Add database')}
</h4>
}
title={<h4>{isEditMode ? t('Edit database') : t('Add database')}</h4>}
>
<Tabs
defaultActiveKey={DEFAULT_TAB_KEY}
activeKey={tabKey}
onTabClick={tabChange}
>
<Tabs.TabPane
tab={
<span>
{t('Connection')}
<span className="required">*</span>
</span>
}
key="1"
>
<StyledRequiredTab tab={<span>{t('Basic')}</span>} key="1">
<StyledInputContainer>
<div className="control-label">
{t('Database name')}
{t('Display Name')}
<span className="required">*</span>
</div>
<div className="input-container">
@ -400,6 +274,9 @@ const DatabaseModal: FunctionComponent<DatabaseModalProps> = ({
onChange={onInputChange}
/>
</div>
<div className="helper">
{t('Pick a name to help you identify this database.')}
</div>
</StyledInputContainer>
<StyledInputContainer>
<div className="control-label">
@ -417,9 +294,6 @@ const DatabaseModal: FunctionComponent<DatabaseModalProps> = ({
)}
onChange={onInputChange}
/>
<Button buttonStyle="primary" onClick={testConnection} cta>
{t('Test connection')}
</Button>
</div>
<div className="helper">
{t('Refer to the ')}
@ -433,300 +307,355 @@ const DatabaseModal: FunctionComponent<DatabaseModalProps> = ({
{t(' for more information on how to structure your URI.')}
</div>
</StyledInputContainer>
</Tabs.TabPane>
<Tabs.TabPane tab={<span>{t('Performance')}</span>} key="2">
<StyledInputContainer>
<div className="control-label">{t('Chart cache timeout')}</div>
<div className="input-container">
<input
type="number"
name="cache_timeout"
value={db?.cache_timeout || ''}
placeholder={t('Chart cache timeout')}
onChange={onInputChange}
/>
</div>
<div className="helper">
{t(
'Duration (in seconds) of the caching timeout for charts of this database.' +
' A timeout of 0 indicates that the cache never expires.' +
' Note this defaults to the global timeout if undefined.',
)}
</div>
</StyledInputContainer>
<StyledInputContainer>
<div className="input-container">
<IndeterminateCheckbox
id="allow_run_async"
indeterminate={false}
checked={!!db?.allow_run_async}
onChange={onInputChange}
labelText={t('Asynchronous query execution')}
/>
<InfoTooltip
tooltip={t(
'Operate the database in asynchronous mode, meaning that the queries ' +
'are executed on remote workers as opposed to on the web server itself. ' +
'This assumes that you have a Celery worker setup as well as a results ' +
'backend. Refer to the installation docs for more information.',
)}
/>
</div>
</StyledInputContainer>
</Tabs.TabPane>
<Tabs.TabPane tab={<span>{t('SQL Lab settings')}</span>} key="3">
<StyledInputContainer>
<StyledInputContainer>
<div className="input-container">
<IndeterminateCheckbox
id="expose_in_sqllab"
indeterminate={false}
checked={!!db?.expose_in_sqllab}
onChange={onInputChange}
labelText={t('Expose in SQL Lab')}
/>
<InfoTooltip
tooltip={t('Allow this database to be queried in SQL Lab')}
/>
</div>
<StyledExpandableForm
className={`expandable ${expandableModalIsOpen ? 'open' : ''}`}
>
<StyledInputContainer>
<div className="input-container">
<IndeterminateCheckbox
id="allow_ctas"
indeterminate={false}
checked={!!db?.allow_ctas}
onChange={onInputChange}
labelText={t('Allow CREATE TABLE AS')}
/>
<InfoTooltip
tooltip={t(
'Allow creation of new tables based on queries',
)}
/>
</div>
</StyledInputContainer>
<StyledInputContainer>
<div className="input-container">
<IndeterminateCheckbox
id="allow_cvas"
indeterminate={false}
checked={!!db?.allow_cvas}
onChange={onInputChange}
labelText={t('Allow CREATE VIEW AS')}
/>
<InfoTooltip
tooltip={t(
'Allow creation of new views based on queries',
)}
/>
</div>
<StyledInputContainer
className={`expandable ${createAsOpen ? 'open' : ''}`}
>
<div className="control-label">
{t('CTAS & CVAS SCHEMA')}
</div>
<Button
onClick={testConnection}
cta
buttonStyle="link"
style={{
width: '100%',
border: `1px solid ${supersetTheme.colors.primary.base}`,
}}
>
{t('Test connection')}
</Button>
</StyledRequiredTab>
<Tabs.TabPane tab={<span>{t('Advanced')}</span>} key="2">
<Collapse expandIconPosition="right" accordion>
<Collapse.Panel
header={
<div>
<h4>SQL Lab</h4>
<p className="helper">
Configure how this database will function in SQL Lab.
</p>
</div>
}
key="1"
>
<StyledInputContainer className="mb-0">
<div className="input-container">
<IndeterminateCheckbox
id="expose_in_sqllab"
indeterminate={false}
checked={!!db?.expose_in_sqllab}
onChange={onInputChange}
labelText={t('Expose in SQL Lab')}
/>
<InfoTooltip
tooltip={t('Allow this database to be queried in SQL Lab')}
/>
</div>
<StyledExpandableForm
className={cx('expandable', {
open: expandableModalIsOpen,
'ctas-open': createAsOpen,
})}
>
<StyledInputContainer className="mb-0">
<div className="input-container">
<input
type="text"
name="force_ctas_schema"
value={db?.force_ctas_schema || ''}
placeholder={t('Search or select schema')}
<IndeterminateCheckbox
id="allow_ctas"
indeterminate={false}
checked={!!db?.allow_ctas}
onChange={onInputChange}
labelText={t('Allow CREATE TABLE AS')}
/>
<InfoTooltip
tooltip={t(
'Allow creation of new tables based on queries',
)}
/>
</div>
<div className="helper">
{t(
'When allowing CREATE TABLE AS option in SQL Lab, this option ' +
'forces the table to be created in this schema.',
)}
</StyledInputContainer>
<StyledInputContainer className="mb-0">
<div className="input-container">
<IndeterminateCheckbox
id="allow_cvas"
indeterminate={false}
checked={!!db?.allow_cvas}
onChange={onInputChange}
labelText={t('Allow CREATE VIEW AS')}
/>
<InfoTooltip
tooltip={t(
'Allow creation of new views based on queries',
)}
/>
</div>
<StyledInputContainer
className={cx('expandable', { open: createAsOpen })}
>
<div className="control-label">
{t('CTAS & CVAS SCHEMA')}
</div>
<div className="input-container">
<input
type="text"
name="force_ctas_schema"
value={db?.force_ctas_schema || ''}
placeholder={t('Search or select schema')}
onChange={onInputChange}
/>
</div>
<div className="helper">
{t(
'When allowing CREATE TABLE AS option in SQL Lab, this option ' +
'forces the table to be created in this schema.',
)}
</div>
</StyledInputContainer>
</StyledInputContainer>
<StyledInputContainer className="mb-0">
<div className="input-container">
<IndeterminateCheckbox
id="allow_dml"
indeterminate={false}
checked={!!db?.allow_dml}
onChange={onInputChange}
labelText={t('Allow DML')}
/>
<InfoTooltip
tooltip={t(
'Allow manipulation of the database using non-SELECT statements such as UPDATE, DELETE, CREATE, etc.',
)}
/>
</div>
</StyledInputContainer>
</StyledInputContainer>
<StyledInputContainer>
<div className="input-container">
<IndeterminateCheckbox
id="allow_dml"
indeterminate={false}
checked={!!db?.allow_dml}
onChange={onInputChange}
labelText={t('Allow DML')}
/>
<InfoTooltip
tooltip={t(
'Allow manipulation of the database using non-SELECT statements such as UPDATE, DELETE, CREATE, etc.',
)}
/>
<StyledInputContainer>
<div className="input-container">
<IndeterminateCheckbox
id="allow_multi_schema_metadata_fetch"
indeterminate={false}
checked={!!db?.allow_multi_schema_metadata_fetch}
onChange={onInputChange}
labelText={t('Allow multi schema metadata fetch')}
/>
<InfoTooltip
tooltip={t(
'Allow SQL Lab to fetch a list of all tables and all views across all database ' +
'schemas. For large data warehouse with thousands of tables, this can be ' +
'expensive and put strain on the system.',
)}
/>
</div>
</StyledInputContainer>
</StyledExpandableForm>
</StyledInputContainer>
</Collapse.Panel>
<Collapse.Panel
header={
<div>
<h4>Performance</h4>
<p className="helper">
Adjust settings that will impact the performance of this
database.
</p>
</div>
}
key="2"
>
<StyledInputContainer className="mb-8">
<div className="control-label">{t('Chart cache timeout')}</div>
<div className="input-container">
<input
type="number"
name="cache_timeout"
value={db?.cache_timeout || ''}
placeholder={t('Chart cache timeout')}
onChange={onInputChange}
/>
</div>
<div className="helper">
{t(
'Duration (in seconds) of the caching timeout for charts of this database.' +
' A timeout of 0 indicates that the cache never expires.' +
' Note this defaults to the global timeout if undefined.',
)}
</div>
</StyledInputContainer>
<StyledInputContainer className="mb-0">
<div className="input-container">
<IndeterminateCheckbox
id="allow_run_async"
indeterminate={false}
checked={!!db?.allow_run_async}
onChange={onInputChange}
labelText={t('Asynchronous query execution')}
/>
<InfoTooltip
tooltip={t(
'Operate the database in asynchronous mode, meaning that the queries ' +
'are executed on remote workers as opposed to on the web server itself. ' +
'This assumes that you have a Celery worker setup as well as a results ' +
'backend. Refer to the installation docs for more information.',
)}
/>
</div>
</StyledInputContainer>
</Collapse.Panel>
<Collapse.Panel
header={
<div>
<h4>Security</h4>
<p className="helper">
Add connection information for other systems.
</p>
</div>
}
key="3"
>
<StyledInputContainer>
<div className="control-label">{t('Secure extra')}</div>
<div className="input-container">
<StyledJsonEditor
name="encrypted_extra"
value={db?.encrypted_extra || ''}
placeholder={t('Secure extra')}
onChange={(json: string) =>
onEditorChange(json, 'encrypted_extra')
}
width="100%"
height="160px"
/>
</div>
<div className="helper">
<div>
{t(
'JSON string containing additional connection configuration.',
)}
</div>
</StyledInputContainer>
<StyledInputContainer>
<div className="input-container">
<IndeterminateCheckbox
id="allow_multi_schema_metadata_fetch"
indeterminate={false}
checked={!!db?.allow_multi_schema_metadata_fetch}
onChange={onInputChange}
labelText={t('Allow multi schema metadata fetch')}
/>
<InfoTooltip
tooltip={t(
'Allow SQL Lab to fetch a list of all tables and all views across all database ' +
'schemas. For large data warehouse with thousands of tables, this can be ' +
'expensive and put strain on the system.',
)}
/>
<div>
{t(
'This is used to provide connection information for systems like Hive, ' +
'Presto, and BigQuery, which do not conform to the username:password syntax ' +
'normally used by SQLAlchemy.',
)}
</div>
</StyledInputContainer>
</StyledExpandableForm>
</StyledInputContainer>
</StyledInputContainer>
</Tabs.TabPane>
<Tabs.TabPane tab={<span>{t('Security')}</span>} key="4">
<StyledInputContainer>
<div className="control-label">{t('Secure extra')}</div>
<div className="input-container">
<StyledJsonEditor
name="encrypted_extra"
value={db?.encrypted_extra || ''}
placeholder={t('Secure extra')}
onChange={(json: string) =>
onEditorChange(json, 'encrypted_extra')
}
width="100%"
height="160px"
/>
</div>
<div className="helper">
<div>
{t(
'JSON string containing additional connection configuration.',
)}
</div>
<div>
{t(
'This is used to provide connection information for systems like Hive, ' +
'Presto, and BigQuery, which do not conform to the username:password syntax ' +
'normally used by SQLAlchemy.',
)}
</div>
</div>
</StyledInputContainer>
<StyledInputContainer>
<div className="control-label">{t('Root certificate')}</div>
<div className="input-container">
<textarea
name="server_cert"
value={db?.server_cert || ''}
placeholder={t('Root certificate')}
onChange={onTextChange}
/>
</div>
<div className="helper">
{t(
'Optional CA_BUNDLE contents to validate HTTPS requests. Only available on ' +
'certain database engines.',
)}
</div>
</StyledInputContainer>
</Tabs.TabPane>
<Tabs.TabPane tab={<span>{t('Extra')}</span>} key="5">
<StyledInputContainer>
<div className="input-container">
<IndeterminateCheckbox
id="impersonate_user"
indeterminate={false}
checked={!!db?.impersonate_user}
onChange={onInputChange}
labelText={t('Impersonate Logged In User (Presto & Hive)')}
/>
<InfoTooltip
tooltip={t(
'If Presto, all the queries in SQL Lab are going to be executed as the ' +
'currently logged on user who must have permission to run them. If Hive ' +
'and hive.server2.enable.doAs is enabled, will run the queries as ' +
'service account, but impersonate the currently logged on user via ' +
'hive.server2.proxy.user property.',
)}
/>
</div>
</StyledInputContainer>
<StyledInputContainer>
<div className="input-container">
<IndeterminateCheckbox
id="allow_csv_upload"
indeterminate={false}
checked={!!db?.allow_csv_upload}
onChange={onInputChange}
labelText={t('Allow data upload')}
/>
<InfoTooltip
tooltip={t(
'If selected, please set the schemas allowed for data upload in Extra.',
)}
/>
</div>
</StyledInputContainer>
<StyledInputContainer className="extra-container">
<div className="control-label">{t('Extra')}</div>
<div className="input-container">
<StyledJsonEditor
name="extra"
value={db?.extra ?? defaultExtra}
placeholder={t('Secure extra')}
onChange={(json: string) => onEditorChange(json, 'extra')}
width="100%"
height="160px"
/>
</div>
<div className="helper">
<div>
{t('JSON string containing extra configuration elements.')}
</div>
<div>
{t(
'1. The engine_params object gets unpacked into the sqlalchemy.create_engine ' +
'call, while the metadata_params gets unpacked into the sqlalchemy.MetaData ' +
'call.',
)}
</div>
<div>
{t(
'2. The metadata_cache_timeout is a cache timeout setting in seconds for ' +
'metadata fetch of this database. Specify it as "metadata_cache_timeout": ' +
'{"schema_cache_timeout": 600, "table_cache_timeout": 600}. If unset, cache ' +
'will not be enabled for the functionality. A timeout of 0 indicates that ' +
'the cache never expires.',
)}
</div>
<div>
{t(
'3. The schemas_allowed_for_csv_upload is a comma separated list of schemas ' +
'that CSVs are allowed to upload to. Specify it as ' +
'"schemas_allowed_for_csv_upload": ["public", "csv_upload"]. If database ' +
'flavor does not support schema or any schema is allowed to be accessed, ' +
'just leave the list empty.',
)}
</div>
<div>
{t(
"4. The version field is a string specifying this db's version. This " +
'should be used with Presto DBs so that the syntax is correct.',
)}
</div>
<div>
{t(
'5. The allows_virtual_table_explore field is a boolean specifying whether ' +
'or not the Explore button in SQL Lab results is shown.',
)}
</div>
</div>
</StyledInputContainer>
</div>
</StyledInputContainer>
<StyledInputContainer>
<div className="control-label">{t('Root certificate')}</div>
<div className="input-container">
<textarea
name="server_cert"
value={db?.server_cert || ''}
placeholder={t('Root certificate')}
onChange={onTextChange}
/>
</div>
<div className="helper">
{t(
'Optional CA_BUNDLE contents to validate HTTPS requests. Only available on ' +
'certain database engines.',
)}
</div>
</StyledInputContainer>
</Collapse.Panel>
<Collapse.Panel
header={
<div>
<h4>Other</h4>
<p className="helper">Additional settings.</p>
</div>
}
key="4"
>
<StyledInputContainer className="mb-0">
<div className="input-container">
<IndeterminateCheckbox
id="impersonate_user"
indeterminate={false}
checked={!!db?.impersonate_user}
onChange={onInputChange}
labelText={t('Impersonate Logged In User (Presto & Hive)')}
/>
<InfoTooltip
tooltip={t(
'If Presto, all the queries in SQL Lab are going to be executed as the ' +
'currently logged on user who must have permission to run them. If Hive ' +
'and hive.server2.enable.doAs is enabled, will run the queries as ' +
'service account, but impersonate the currently logged on user via ' +
'hive.server2.proxy.user property.',
)}
/>
</div>
</StyledInputContainer>
<StyledInputContainer className="mb-0">
<div className="input-container">
<IndeterminateCheckbox
id="allow_csv_upload"
indeterminate={false}
checked={!!db?.allow_csv_upload}
onChange={onInputChange}
labelText={t('Allow data upload')}
/>
<InfoTooltip
tooltip={t(
'If selected, please set the schemas allowed for data upload in Extra.',
)}
/>
</div>
</StyledInputContainer>
<StyledInputContainer className="extra-container">
<div className="control-label">{t('Extra')}</div>
<div className="input-container">
<StyledJsonEditor
name="extra"
value={db?.extra ?? defaultExtra}
placeholder={t('Secure extra')}
onChange={(json: string) => onEditorChange(json, 'extra')}
width="100%"
height="160px"
/>
</div>
<div className="helper">
<div>
{t('JSON string containing extra configuration elements.')}
</div>
<div>
{t(
'1. The engine_params object gets unpacked into the sqlalchemy.create_engine ' +
'call, while the metadata_params gets unpacked into the sqlalchemy.MetaData ' +
'call.',
)}
</div>
<div>
{t(
'2. The metadata_cache_timeout is a cache timeout setting in seconds for ' +
'metadata fetch of this database. Specify it as "metadata_cache_timeout": ' +
'{"schema_cache_timeout": 600, "table_cache_timeout": 600}. If unset, cache ' +
'will not be enabled for the functionality. A timeout of 0 indicates that ' +
'the cache never expires.',
)}
</div>
<div>
{t(
'3. The schemas_allowed_for_csv_upload is a comma separated list of schemas ' +
'that CSVs are allowed to upload to. Specify it as ' +
'"schemas_allowed_for_csv_upload": ["public", "csv_upload"]. If database ' +
'flavor does not support schema or any schema is allowed to be accessed, ' +
'just leave the list empty.',
)}
</div>
<div>
{t(
"4. The version field is a string specifying this db's version. This " +
'should be used with Presto DBs so that the syntax is correct.',
)}
</div>
<div>
{t(
'5. The allows_virtual_table_explore field is a boolean specifying whether ' +
'or not the Explore button in SQL Lab results is shown.',
)}
</div>
</div>
</StyledInputContainer>
</Collapse.Panel>
</Collapse>
</Tabs.TabPane>
</Tabs>
</Modal>
</StyledModal>
);
};

View File

@ -0,0 +1,203 @@
/**
* 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 { styled } from '@superset-ui/core';
import Modal from 'src/components/Modal';
import { JsonEditor } from 'src/components/AsyncAceEditor';
import Tabs from 'src/common/components/Tabs';
const CTAS_CVAS_SCHEMA_FORM_HEIGHT = 102;
const EXPOSE_IN_SQLLAB_FORM_HEIGHT = CTAS_CVAS_SCHEMA_FORM_HEIGHT + 52;
const EXPOSE_ALL_FORM_HEIGHT = EXPOSE_IN_SQLLAB_FORM_HEIGHT + 102;
const anticonHeight = 12;
export const StyledModal = styled(Modal)`
.ant-collapse {
.ant-collapse-header {
padding-top: ${({ theme }) => theme.gridUnit * 3.5}px;
padding-bottom: ${({ theme }) => theme.gridUnit * 2.5}px;
.anticon.ant-collapse-arrow {
top: calc(50% - ${anticonHeight / 2}px);
}
.helper {
color: ${({ theme }) => theme.colors.grayscale.base};
}
}
h4 {
font-size: 16px;
font-weight: bold;
margin-top: 0;
margin-bottom: ${({ theme }) => theme.gridUnit}px;
}
p.helper {
margin-bottom: 0;
padding: 0;
}
}
.ant-modal-header {
padding: 18px 16px 16px;
}
.ant-modal-body {
padding-left: 0;
padding-right: 0;
}
.ant-tabs-top > .ant-tabs-nav {
margin-bottom: 0;
}
.ant-modal-close-x .close {
color: ${({ theme }) => theme.colors.grayscale.dark1};
opacity: 1;
}
.required {
margin-left: ${({ theme }) => theme.gridUnit / 2}px;
color: ${({ theme }) => theme.colors.error.base};
}
.helper {
display: block;
padding: ${({ theme }) => theme.gridUnit}px 0;
color: ${({ theme }) => theme.colors.grayscale.light1};
font-size: ${({ theme }) => theme.typography.sizes.s - 1}px;
text-align: left;
}
.ant-modal-title > h4 {
font-weight: bold;
}
`;
export const StyledInputContainer = styled.div`
margin-bottom: ${({ theme }) => theme.gridUnit * 6}px;
&.mb-0 {
margin-bottom: 0;
}
&.mb-8 {
margin-bottom: ${({ theme }) => theme.gridUnit * 2}px;
}
.control-label {
color: ${({ theme }) => theme.colors.grayscale.dark1};
font-size: ${({ theme }) => theme.typography.sizes.s - 1}px;
margin-bottom: ${({ theme }) => theme.gridUnit * 2}px;
}
&.extra-container {
padding-top: 8px;
}
.input-container {
display: flex;
align-items: top;
label {
display: flex;
margin-left: ${({ theme }) => theme.gridUnit * 2}px;
margin-top: ${({ theme }) => theme.gridUnit * 0.75}px;
font-family: ${({ theme }) => theme.typography.families.sansSerif};
font-size: ${({ theme }) => theme.typography.sizes.m}px;
}
i {
margin: 0 ${({ theme }) => theme.gridUnit}px;
}
}
input,
textarea {
flex: 1 1 auto;
}
textarea {
height: 160px;
resize: none;
}
input::placeholder,
textarea::placeholder {
color: ${({ theme }) => theme.colors.grayscale.light1};
}
textarea,
input[type='text'],
input[type='number'] {
padding: ${({ theme }) => theme.gridUnit * 1.5}px
${({ theme }) => theme.gridUnit * 2}px;
border-style: none;
border: 1px solid ${({ theme }) => theme.colors.grayscale.light2};
border-radius: ${({ theme }) => theme.gridUnit}px;
&[name='name'] {
flex: 0 1 auto;
width: 40%;
}
&[name='sqlalchemy_uri'] {
margin-right: ${({ theme }) => theme.gridUnit * 3}px;
}
}
&.expandable {
height: 0;
overflow: hidden;
transition: height 0.25s;
margin-left: ${({ theme }) => theme.gridUnit * 8}px;
margin-bottom: 0;
padding: 0;
.control-label {
margin-bottom: 0;
}
&.open {
height: ${CTAS_CVAS_SCHEMA_FORM_HEIGHT}px;
padding-right: ${({ theme }) => theme.gridUnit * 5}px;
}
}
`;
export const StyledJsonEditor = styled(JsonEditor)`
flex: 1 1 auto;
border: 1px solid ${({ theme }) => theme.colors.grayscale.light2};
border-radius: ${({ theme }) => theme.gridUnit}px;
`;
export const StyledExpandableForm = styled.div`
padding-top: ${({ theme }) => theme.gridUnit}px;
.input-container {
padding-top: ${({ theme }) => theme.gridUnit}px;
padding-bottom: ${({ theme }) => theme.gridUnit}px;
}
&.expandable {
height: 0;
overflow: hidden;
transition: height 0.25s;
margin-left: ${({ theme }) => theme.gridUnit * 7}px;
&.open {
height: ${EXPOSE_IN_SQLLAB_FORM_HEIGHT}px;
&.ctas-open {
height: ${EXPOSE_ALL_FORM_HEIGHT}px;
}
}
}
`;
export const StyledRequiredTab = styled(Tabs.TabPane)`
padding-left: ${({ theme }) => theme.gridUnit * 4}px;
padding-right: ${({ theme }) => theme.gridUnit * 4}px;
margin-top: ${({ theme }) => theme.gridUnit * 4}px;
`;