This is an automated email from the ASF dual-hosted git repository.
hugh pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/superset.git
The following commit(s) were added to refs/heads/master by this push:
new ff665fa feat: restyle database modal (#14092)
ff665fa is described below
commit ff665fa5a7552d0e00df213adaf5f039f0ab6b23
Author: Elizabeth Thompson <[email protected]>
AuthorDate: Tue Apr 20 15:21:07 2021 -0700
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
---
.../CRUD/data/database/DatabaseModal_spec.jsx | 112 +--
.../src/views/CRUD/data/database/DatabaseModal.tsx | 801 ++++++++++-----------
.../src/views/CRUD/data/database/styles.ts | 203 ++++++
3 files changed, 625 insertions(+), 491 deletions(-)
diff --git
a/superset-frontend/spec/javascripts/views/CRUD/data/database/DatabaseModal_spec.jsx
b/superset-frontend/spec/javascripts/views/CRUD/data/database/DatabaseModal_spec.jsx
index 9e200a5..89116a5 100644
---
a/superset-frontend/spec/javascripts/views/CRUD/data/database/DatabaseModal_spec.jsx
+++
b/superset-frontend/spec/javascripts/views/CRUD/data/database/DatabaseModal_spec.jsx
@@ -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 Advanced tab
+ const advancedTab = screen.getByRole('tab', {
+ name: /advanced/i,
+ });
+ userEvent.click(advancedTab);
- // Select SQL Lab settings tab
+ // 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 Advanced tab
+ const advancedTab = screen.getByRole('tab', {
+ name: /advanced/i,
+ });
+ userEvent.click(advancedTab);
- // Select SQL Lab settings tab
+ // 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 Advanced tab
+ const advancedTab = screen.getByRole('tab', {
+ name: /advanced/i,
+ });
+ userEvent.click(advancedTab);
- // Select SQL Lab settings tab
+ // 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 Advanced tab
+ const advancedTab = screen.getByRole('tab', {
+ name: /advanced/i,
+ });
+ userEvent.click(advancedTab);
- // Select SQL Lab settings tab
+ // 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
diff --git a/superset-frontend/src/views/CRUD/data/database/DatabaseModal.tsx
b/superset-frontend/src/views/CRUD/data/database/DatabaseModal.tsx
index 105b77e..693b87f 100644
--- a/superset-frontend/src/views/CRUD/data/database/DatabaseModal.tsx
+++ b/superset-frontend/src/views/CRUD/data/database/DatabaseModal.tsx
@@ -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')}
+ <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">
+ <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 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_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>
- <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
+ 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>
);
};
diff --git a/superset-frontend/src/views/CRUD/data/database/styles.ts
b/superset-frontend/src/views/CRUD/data/database/styles.ts
new file mode 100644
index 0000000..93ce119
--- /dev/null
+++ b/superset-frontend/src/views/CRUD/data/database/styles.ts
@@ -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;
+`;