/** * 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 { ChangeEvent, EventHandler, useState, useEffect } from 'react'; import cx from 'classnames'; import { t, DatabaseConnectionExtension, isFeatureEnabled, useTheme, FeatureFlag, } from '@superset-ui/core'; import { Input, Checkbox, Collapse, InfoTooltip, CollapseLabelInModal, type CheckboxChangeEvent, } from '@superset-ui/core/components'; import { useJsonValidation } from '@superset-ui/core/components/AsyncAceEditor'; import { StyledInputContainer, StyledJsonEditor, StyledExpandableForm, no_margin_bottom, } from './styles'; import { DatabaseObject, ExtraJson } from '../types'; const ExtraOptions = ({ db, onInputChange, onTextChange, onEditorChange, onExtraInputChange, onExtraEditorChange, extraExtension, }: { db: DatabaseObject | null; onInputChange: ( e: CheckboxChangeEvent | React.ChangeEvent, ) => void; onTextChange: EventHandler>; onEditorChange: Function; onExtraInputChange: ( e: CheckboxChangeEvent | ChangeEvent, ) => void; onExtraEditorChange: Function; extraExtension: DatabaseConnectionExtension | undefined; }) => { const expandableModalIsOpen = !!db?.expose_in_sqllab; const createAsOpen = !!(db?.allow_ctas || db?.allow_cvas); const isFileUploadSupportedByEngine = db?.engine_information?.supports_file_upload; const supportsDynamicCatalog = db?.engine_information?.supports_dynamic_catalog; // JSON.parse will deep parse engine_params // if it's an object, and we want to keep it a string const extraJson: ExtraJson = JSON.parse(db?.extra || '{}', (key, value) => { if (key === 'engine_params' && typeof value === 'object') { // keep this as a string return JSON.stringify(value); } return value; }); // JSON validation hooks for the three editors const secureExtraAnnotations = useJsonValidation(db?.masked_encrypted_extra, { errorPrefix: 'Invalid secure extra JSON', }); const metadataParamsValue = !Object.keys(extraJson?.metadata_params || {}) .length ? '' : typeof extraJson?.metadata_params === 'string' ? extraJson?.metadata_params : JSON.stringify(extraJson?.metadata_params); const metadataParamsAnnotations = useJsonValidation(metadataParamsValue, { errorPrefix: 'Invalid metadata parameters JSON', }); const engineParamsValue = !Object.keys(extraJson?.engine_params || {}).length ? '' : typeof extraJson?.engine_params === 'string' ? extraJson?.engine_params : JSON.stringify(extraJson?.engine_params); const engineParamsAnnotations = useJsonValidation(engineParamsValue, { errorPrefix: 'Invalid engine parameters JSON', }); const theme = useTheme(); const ExtraExtensionComponent = extraExtension?.component; const ExtraExtensionLogo = extraExtension?.logo; const ExtensionDescription = extraExtension?.description; const allowRunAsync = isFeatureEnabled(FeatureFlag.ForceSqlLabRunAsync) ? true : !!db?.allow_run_async; const isAllowRunAsyncDisabled = isFeatureEnabled( FeatureFlag.ForceSqlLabRunAsync, ); const [activeKey, setActiveKey] = useState(); useEffect(() => { if (!expandableModalIsOpen && activeKey !== undefined) { setActiveKey(undefined); } // See issue #34630 for why we omit `activeKey` from the dependency array }, [expandableModalIsOpen]); return ( setActiveKey(key)} items={[ { key: 'sql-lab', label: ( ), children: ( <>
{t('Expose database in SQL Lab')}
{t('Allow CREATE TABLE AS')}
{t('Allow CREATE VIEW AS')}
{t('CTAS & CVAS SCHEMA')}
{t( 'Force all tables and views to be created in this schema when clicking CTAS or CVAS in SQL Lab.', )}
{t('Allow DDL and DML')}
{t('Enable query cost estimation')}
{t('Allow this database to be explored')}
{t('Disable SQL Lab data preview queries')}
{t('Enable row expansion in schemas')}
), }, { key: 'performance', label: ( ), children: ( <>
{t('Chart cache timeout')}
{t( 'Duration (in seconds) of the caching timeout for charts of this database.' + ' A timeout of 0 indicates that the cache never expires, and -1 bypasses the cache.' + ' Note this defaults to the global timeout if undefined.', )}
{t('Schema cache timeout')}
{t( 'Duration (in seconds) of the metadata caching timeout for schemas of ' + 'this database. If left unset, the cache never expires.', )}
{t('Table cache timeout')}
{t( 'Duration (in seconds) of the metadata caching timeout for tables of ' + 'this database. If left unset, the cache never expires. ', )}
{t('Asynchronous query execution')} {isAllowRunAsyncDisabled && ( )}
{t('Cancel query on window unload event')}
), }, { key: 'security', label: ( ), children: ( <>
{t('Per user caching')}
{t( 'Impersonate logged in user (Presto, Trino, Drill, Hive, and Google Sheets)', )}
{isFileUploadSupportedByEngine && (
{t('Allow file uploads to database')}
)} {isFileUploadSupportedByEngine && !!db?.allow_file_upload && (
{t('Schemas allowed for File upload')}
{t( 'A comma-separated list of schemas that files are allowed to upload to.', )}
)}
{t('Secure extra')}
onEditorChange({ json, name: 'masked_encrypted_extra' }) } width="100%" height="160px" annotations={secureExtraAnnotations} />
{t( 'JSON string containing additional connection configuration. ' + '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.', )}
{t('Root certificate')}
{t( 'Optional CA_BUNDLE contents to validate HTTPS requests. Only ' + 'available on certain database engines.', )}
), }, ...(extraExtension && ExtraExtensionComponent && ExtensionDescription ? [ { key: extraExtension?.title, collapsible: extraExtension.enabled?.() ? ('icon' as const) : ('disabled' as const), label: ( {ExtraExtensionLogo && } {extraExtension?.title} } subtitle={} /> ), children: ( ), }, ] : []), { key: 'other', label: ( ), children: ( <>
{t('Metadata Parameters')}
onExtraEditorChange({ json, name: 'metadata_params' }) } width="100%" height="160px" value={ !Object.keys(extraJson?.metadata_params || {}).length ? '' : typeof extraJson?.metadata_params === 'string' ? extraJson?.metadata_params : JSON.stringify(extraJson?.metadata_params) } annotations={metadataParamsAnnotations} />
{t( 'The metadata_params object gets unpacked into the sqlalchemy.MetaData call.', )}
{t('Engine Parameters')}
onExtraEditorChange({ json, name: 'engine_params' }) } width="100%" height="160px" value={ !Object.keys(extraJson?.engine_params || {}).length ? '' : extraJson?.engine_params } annotations={engineParamsAnnotations} />
{t( 'The engine_params object gets unpacked into the sqlalchemy.create_engine call.', )}
{t('Version')}
{t( 'Specify the database version. This is used with Presto for query cost ' + 'estimation, and Dremio for syntax changes, among others.', )}
{t('Disable drill to detail')}
{supportsDynamicCatalog && (
{t('Allow changing catalogs')}
)} ), }, ]} /> ); }; export default ExtraOptions;