| /** |
| * 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. |
| */ |
| /* eslint-disable jsx-a11y/anchor-is-valid */ |
| /* eslint-disable jsx-a11y/no-static-element-interactions */ |
| import React, { |
| useState, |
| useEffect, |
| useMemo, |
| useRef, |
| useCallback, |
| } from 'react'; |
| import useEffectEvent from 'src/hooks/useEffectEvent'; |
| import { CSSTransition } from 'react-transition-group'; |
| import { shallowEqual, useDispatch, useSelector } from 'react-redux'; |
| import PropTypes from 'prop-types'; |
| import Split from 'react-split'; |
| import { |
| css, |
| FeatureFlag, |
| styled, |
| t, |
| useTheme, |
| getExtensionsRegistry, |
| } from '@superset-ui/core'; |
| import debounce from 'lodash/debounce'; |
| import throttle from 'lodash/throttle'; |
| import Modal from 'src/components/Modal'; |
| import Mousetrap from 'mousetrap'; |
| import Button from 'src/components/Button'; |
| import Timer from 'src/components/Timer'; |
| import ResizableSidebar from 'src/components/ResizableSidebar'; |
| import { AntdDropdown, AntdSwitch } from 'src/components'; |
| import { Input } from 'src/components/Input'; |
| import { Menu } from 'src/components/Menu'; |
| import Icons from 'src/components/Icons'; |
| import { detectOS } from 'src/utils/common'; |
| import { |
| addNewQueryEditor, |
| CtasEnum, |
| estimateQueryCost, |
| persistEditorHeight, |
| postStopQuery, |
| queryEditorSetAutorun, |
| queryEditorSetSql, |
| queryEditorSetAndSaveSql, |
| queryEditorSetTemplateParams, |
| runQueryFromSqlEditor, |
| saveQuery, |
| addSavedQueryToTabState, |
| scheduleQuery, |
| setActiveSouthPaneTab, |
| updateSavedQuery, |
| } from 'src/SqlLab/actions/sqlLab'; |
| import { |
| STATE_TYPE_MAP, |
| SQL_EDITOR_GUTTER_HEIGHT, |
| SQL_EDITOR_GUTTER_MARGIN, |
| SQL_TOOLBAR_HEIGHT, |
| SQL_EDITOR_LEFTBAR_WIDTH, |
| SQL_EDITOR_PADDING, |
| INITIAL_NORTH_PERCENT, |
| INITIAL_SOUTH_PERCENT, |
| SET_QUERY_EDITOR_SQL_DEBOUNCE_MS, |
| WINDOW_RESIZE_THROTTLE_MS, |
| } from 'src/SqlLab/constants'; |
| import { |
| getItem, |
| LocalStorageKeys, |
| setItem, |
| } from 'src/utils/localStorageHelpers'; |
| import { isFeatureEnabled } from 'src/featureFlags'; |
| import { EmptyStateBig } from 'src/components/EmptyState'; |
| import getBootstrapData from 'src/utils/getBootstrapData'; |
| import { isEmpty } from 'lodash'; |
| import TemplateParamsEditor from '../TemplateParamsEditor'; |
| import SouthPane from '../SouthPane'; |
| import SaveQuery from '../SaveQuery'; |
| import ScheduleQueryButton from '../ScheduleQueryButton'; |
| import EstimateQueryCostButton from '../EstimateQueryCostButton'; |
| import ShareSqlLabQuery from '../ShareSqlLabQuery'; |
| import SqlEditorLeftBar from '../SqlEditorLeftBar'; |
| import AceEditorWrapper from '../AceEditorWrapper'; |
| import RunQueryActionButton from '../RunQueryActionButton'; |
| import QueryLimitSelect from '../QueryLimitSelect'; |
| |
| const bootstrapData = getBootstrapData(); |
| const scheduledQueriesConf = bootstrapData?.common?.conf?.SCHEDULED_QUERIES; |
| |
| const StyledToolbar = styled.div` |
| padding: ${({ theme }) => theme.gridUnit * 2}px; |
| background: ${({ theme }) => theme.colors.grayscale.light5}; |
| display: flex; |
| justify-content: space-between; |
| border: 1px solid ${({ theme }) => theme.colors.grayscale.light2}; |
| border-top: 0; |
| |
| form { |
| margin-block-end: 0; |
| } |
| |
| .leftItems, |
| .rightItems { |
| display: flex; |
| align-items: center; |
| & > span { |
| margin-right: ${({ theme }) => theme.gridUnit * 2}px; |
| display: inline-block; |
| |
| &:last-child { |
| margin-right: 0; |
| } |
| } |
| } |
| |
| .limitDropdown { |
| white-space: nowrap; |
| } |
| `; |
| |
| const StyledSidebar = styled.div` |
| flex: 0 0 ${({ width }) => width}px; |
| width: ${({ width }) => width}px; |
| padding: ${({ theme, hide }) => (hide ? 0 : theme.gridUnit * 2.5)}px; |
| border-right: 1px solid |
| ${({ theme, hide }) => |
| hide ? 'transparent' : theme.colors.grayscale.light2}; |
| `; |
| |
| const StyledSqlEditor = styled.div` |
| ${({ theme }) => css` |
| display: flex; |
| flex-direction: row; |
| height: 100%; |
| |
| .schemaPane { |
| transition: transform ${theme.transitionTiming}s ease-in-out; |
| } |
| |
| .queryPane { |
| flex: 1 1 auto; |
| padding: ${theme.gridUnit * 2}px; |
| overflow-y: auto; |
| overflow-x: scroll; |
| } |
| |
| .schemaPane-enter-done, |
| .schemaPane-exit { |
| transform: translateX(0); |
| z-index: 7; |
| } |
| |
| .schemaPane-exit-active { |
| transform: translateX(-120%); |
| } |
| |
| .schemaPane-enter-active { |
| transform: translateX(0); |
| max-width: ${theme.gridUnit * 75}px; |
| } |
| |
| .schemaPane-enter, |
| .schemaPane-exit-done { |
| max-width: 0; |
| transform: translateX(-120%); |
| overflow: hidden; |
| } |
| |
| .schemaPane-exit-done + .queryPane { |
| margin-left: 0; |
| } |
| |
| .gutter { |
| border-top: 1px solid ${theme.colors.grayscale.light2}; |
| border-bottom: 1px solid ${theme.colors.grayscale.light2}; |
| width: 3%; |
| margin: ${SQL_EDITOR_GUTTER_MARGIN}px 47%; |
| } |
| |
| .gutter.gutter-vertical { |
| cursor: row-resize; |
| } |
| `} |
| `; |
| |
| const propTypes = { |
| tables: PropTypes.array.isRequired, |
| queryEditor: PropTypes.object.isRequired, |
| defaultQueryLimit: PropTypes.number.isRequired, |
| maxRow: PropTypes.number.isRequired, |
| displayLimit: PropTypes.number.isRequired, |
| saveQueryWarning: PropTypes.string, |
| scheduleQueryWarning: PropTypes.string, |
| }; |
| |
| const extensionsRegistry = getExtensionsRegistry(); |
| |
| const SqlEditor = ({ |
| tables, |
| queryEditor, |
| defaultQueryLimit, |
| maxRow, |
| displayLimit, |
| saveQueryWarning, |
| scheduleQueryWarning = null, |
| }) => { |
| const theme = useTheme(); |
| const dispatch = useDispatch(); |
| |
| const { database, latestQuery, hideLeftBar } = useSelector( |
| ({ sqlLab: { unsavedQueryEditor, databases, queries } }) => { |
| let { dbId, latestQueryId, hideLeftBar } = queryEditor; |
| if (unsavedQueryEditor.id === queryEditor.id) { |
| dbId = unsavedQueryEditor.dbId || dbId; |
| latestQueryId = unsavedQueryEditor.latestQueryId || latestQueryId; |
| hideLeftBar = unsavedQueryEditor.hideLeftBar || hideLeftBar; |
| } |
| return { |
| database: databases[dbId], |
| latestQuery: queries[latestQueryId], |
| hideLeftBar, |
| }; |
| }, |
| shallowEqual, |
| ); |
| |
| const [height, setHeight] = useState(0); |
| const [autorun, setAutorun] = useState(queryEditor.autorun); |
| const [ctas, setCtas] = useState(''); |
| const [northPercent, setNorthPercent] = useState( |
| queryEditor.northPercent || INITIAL_NORTH_PERCENT, |
| ); |
| const [southPercent, setSouthPercent] = useState( |
| queryEditor.southPercent || INITIAL_SOUTH_PERCENT, |
| ); |
| const [autocompleteEnabled, setAutocompleteEnabled] = useState( |
| getItem(LocalStorageKeys.sqllab__is_autocomplete_enabled, true), |
| ); |
| const [showCreateAsModal, setShowCreateAsModal] = useState(false); |
| const [createAs, setCreateAs] = useState(''); |
| const [showEmptyState, setShowEmptyState] = useState(false); |
| |
| const sqlEditorRef = useRef(null); |
| const northPaneRef = useRef(null); |
| |
| const SqlFormExtension = extensionsRegistry.get('sqleditor.extension.form'); |
| |
| const startQuery = useCallback( |
| (ctasArg = false, ctas_method = CtasEnum.TABLE) => { |
| if (!database) { |
| return; |
| } |
| |
| dispatch( |
| runQueryFromSqlEditor( |
| database, |
| queryEditor, |
| defaultQueryLimit, |
| ctasArg ? ctas : '', |
| ctasArg, |
| ctas_method, |
| ), |
| ); |
| dispatch(setActiveSouthPaneTab('Results')); |
| }, |
| [ctas, database, defaultQueryLimit, dispatch, queryEditor], |
| ); |
| |
| const stopQuery = useCallback(() => { |
| if (latestQuery && ['running', 'pending'].indexOf(latestQuery.state) >= 0) { |
| dispatch(postStopQuery(latestQuery)); |
| } |
| return false; |
| }, [dispatch, latestQuery]); |
| |
| const runQuery = () => { |
| if (database) { |
| startQuery(); |
| } |
| }; |
| |
| useEffect(() => { |
| if (autorun) { |
| setAutorun(false); |
| dispatch(queryEditorSetAutorun(queryEditor, false)); |
| startQuery(); |
| } |
| }, [autorun, dispatch, queryEditor, startQuery]); |
| |
| // One layer of abstraction for easy spying in unit tests |
| const getSqlEditorHeight = () => |
| sqlEditorRef.current |
| ? sqlEditorRef.current.clientHeight - SQL_EDITOR_PADDING * 2 |
| : 0; |
| |
| const getHotkeyConfig = useCallback(() => { |
| // Get the user's OS |
| const userOS = detectOS(); |
| const base = [ |
| { |
| name: 'runQuery1', |
| key: 'ctrl+r', |
| descr: t('Run query'), |
| func: () => { |
| if (queryEditor.sql.trim() !== '') { |
| startQuery(); |
| } |
| }, |
| }, |
| { |
| name: 'runQuery2', |
| key: 'ctrl+enter', |
| descr: t('Run query'), |
| func: () => { |
| if (queryEditor.sql.trim() !== '') { |
| startQuery(); |
| } |
| }, |
| }, |
| { |
| name: 'newTab', |
| key: userOS === 'Windows' ? 'ctrl+q' : 'ctrl+t', |
| descr: t('New tab'), |
| func: () => { |
| dispatch(addNewQueryEditor()); |
| }, |
| }, |
| { |
| name: 'stopQuery', |
| key: userOS === 'MacOS' ? 'ctrl+x' : 'ctrl+e', |
| descr: t('Stop query'), |
| func: stopQuery, |
| }, |
| ]; |
| |
| if (userOS === 'MacOS') { |
| base.push({ |
| name: 'previousLine', |
| key: 'ctrl+p', |
| descr: t('Previous Line'), |
| func: editor => { |
| editor.navigateUp(1); |
| }, |
| }); |
| } |
| |
| return base; |
| }, [dispatch, queryEditor.sql, startQuery, stopQuery]); |
| |
| const onBeforeUnload = useEffectEvent(event => { |
| if ( |
| database?.extra_json?.cancel_query_on_windows_unload && |
| latestQuery?.state === 'running' |
| ) { |
| event.preventDefault(); |
| stopQuery(); |
| } |
| }); |
| |
| useEffect(() => { |
| // We need to measure the height of the sql editor post render to figure the height of |
| // the south pane so it gets rendered properly |
| setHeight(getSqlEditorHeight()); |
| const handleWindowResizeWithThrottle = throttle( |
| () => setHeight(getSqlEditorHeight()), |
| WINDOW_RESIZE_THROTTLE_MS, |
| ); |
| |
| window.addEventListener('resize', handleWindowResizeWithThrottle); |
| window.addEventListener('beforeunload', onBeforeUnload); |
| |
| return () => { |
| window.removeEventListener('resize', handleWindowResizeWithThrottle); |
| window.removeEventListener('beforeunload', onBeforeUnload); |
| }; |
| // TODO: Remove useEffectEvent deps once https://github.com/facebook/react/pull/25881 is released |
| }, [onBeforeUnload]); |
| |
| useEffect(() => { |
| if (!database || isEmpty(database)) { |
| setShowEmptyState(true); |
| } |
| }, [database]); |
| |
| useEffect(() => { |
| // setup hotkeys |
| Mousetrap.reset(); |
| const hotkeys = getHotkeyConfig(); |
| hotkeys.forEach(keyConfig => { |
| Mousetrap.bind([keyConfig.key], keyConfig.func); |
| }); |
| }, [getHotkeyConfig, latestQuery]); |
| |
| const onResizeStart = () => { |
| // Set the heights on the ace editor and the ace content area after drag starts |
| // to smooth out the visual transition to the new heights when drag ends |
| document.getElementsByClassName('ace_content')[0].style.height = '100%'; |
| }; |
| |
| const onResizeEnd = ([northPercent, southPercent]) => { |
| setNorthPercent(northPercent); |
| setSouthPercent(southPercent); |
| |
| if (northPaneRef.current?.clientHeight) { |
| dispatch(persistEditorHeight(queryEditor, northPercent, southPercent)); |
| } |
| }; |
| |
| const setQueryEditorAndSaveSql = useCallback( |
| sql => { |
| dispatch(queryEditorSetAndSaveSql(queryEditor, sql)); |
| }, |
| [dispatch, queryEditor], |
| ); |
| |
| const setQueryEditorAndSaveSqlWithDebounce = useMemo( |
| () => debounce(setQueryEditorAndSaveSql, SET_QUERY_EDITOR_SQL_DEBOUNCE_MS), |
| [setQueryEditorAndSaveSql], |
| ); |
| |
| const onSqlChanged = sql => { |
| dispatch(queryEditorSetSql(queryEditor, sql)); |
| setQueryEditorAndSaveSqlWithDebounce(sql); |
| }; |
| |
| // Return the heights for the ace editor and the south pane as an object |
| // given the height of the sql editor, north pane percent and south pane percent. |
| const getAceEditorAndSouthPaneHeights = ( |
| height, |
| northPercent, |
| southPercent, |
| ) => ({ |
| aceEditorHeight: |
| (height * northPercent) / (theme.gridUnit * 25) - |
| (SQL_EDITOR_GUTTER_HEIGHT / 2 + SQL_EDITOR_GUTTER_MARGIN) - |
| SQL_TOOLBAR_HEIGHT, |
| southPaneHeight: |
| (height * southPercent) / (theme.gridUnit * 25) - |
| (SQL_EDITOR_GUTTER_HEIGHT / 2 + SQL_EDITOR_GUTTER_MARGIN), |
| }); |
| |
| const getQueryCostEstimate = () => { |
| if (database) { |
| dispatch(estimateQueryCost(queryEditor)); |
| } |
| }; |
| |
| const handleToggleAutocompleteEnabled = () => { |
| setItem( |
| LocalStorageKeys.sqllab__is_autocomplete_enabled, |
| !autocompleteEnabled, |
| ); |
| setAutocompleteEnabled(!autocompleteEnabled); |
| }; |
| |
| const elementStyle = (dimension, elementSize, gutterSize) => ({ |
| [dimension]: `calc(${elementSize}% - ${ |
| gutterSize + SQL_EDITOR_GUTTER_MARGIN |
| }px)`, |
| }); |
| |
| const createTableAs = () => { |
| startQuery(true, CtasEnum.TABLE); |
| setShowCreateAsModal(false); |
| setCtas(''); |
| }; |
| |
| const createViewAs = () => { |
| startQuery(true, CtasEnum.VIEW); |
| setShowCreateAsModal(false); |
| setCtas(''); |
| }; |
| |
| const ctasChanged = event => { |
| setCtas(event.target.value); |
| }; |
| |
| const renderDropdown = () => { |
| const qe = queryEditor; |
| const successful = latestQuery?.state === 'success'; |
| const scheduleToolTip = successful |
| ? t('Schedule the query periodically') |
| : t('You must run the query successfully first'); |
| return ( |
| <Menu css={{ width: theme.gridUnit * 44 }}> |
| <Menu.Item css={{ display: 'flex', justifyContent: 'space-between' }}> |
| {' '} |
| <span>{t('Autocomplete')}</span>{' '} |
| <AntdSwitch |
| checked={autocompleteEnabled} |
| onChange={handleToggleAutocompleteEnabled} |
| name="autocomplete-switch" |
| />{' '} |
| </Menu.Item> |
| {isFeatureEnabled(FeatureFlag.ENABLE_TEMPLATE_PROCESSING) && ( |
| <Menu.Item> |
| <TemplateParamsEditor |
| language="json" |
| onChange={params => { |
| dispatch(queryEditorSetTemplateParams(qe, params)); |
| }} |
| queryEditorId={qe.id} |
| /> |
| </Menu.Item> |
| )} |
| {!isEmpty(scheduledQueriesConf) && ( |
| <Menu.Item> |
| <ScheduleQueryButton |
| defaultLabel={qe.name} |
| sql={qe.sql} |
| onSchedule={query => dispatch(scheduleQuery(query))} |
| schema={qe.schema} |
| dbId={qe.dbId} |
| scheduleQueryWarning={scheduleQueryWarning} |
| tooltip={scheduleToolTip} |
| disabled={!successful} |
| /> |
| </Menu.Item> |
| )} |
| </Menu> |
| ); |
| }; |
| |
| const onSaveQuery = async (query, clientId) => { |
| const savedQuery = await dispatch(saveQuery(query, clientId)); |
| dispatch(addSavedQueryToTabState(queryEditor, savedQuery)); |
| }; |
| |
| const renderEditorBottomBar = () => { |
| const { allow_ctas: allowCTAS, allow_cvas: allowCVAS } = database || {}; |
| |
| const showMenu = allowCTAS || allowCVAS; |
| const runMenuBtn = ( |
| <Menu> |
| {allowCTAS && ( |
| <Menu.Item |
| onClick={() => { |
| setShowCreateAsModal(true); |
| setCreateAs(CtasEnum.TABLE); |
| }} |
| key="1" |
| > |
| {t('CREATE TABLE AS')} |
| </Menu.Item> |
| )} |
| {allowCVAS && ( |
| <Menu.Item |
| onClick={() => { |
| setShowCreateAsModal(true); |
| setCreateAs(CtasEnum.VIEW); |
| }} |
| key="2" |
| > |
| {t('CREATE VIEW AS')} |
| </Menu.Item> |
| )} |
| </Menu> |
| ); |
| |
| return ( |
| <StyledToolbar className="sql-toolbar" id="js-sql-toolbar"> |
| <div className="leftItems"> |
| <span> |
| <RunQueryActionButton |
| allowAsync={database ? database.allow_run_async : false} |
| queryEditorId={queryEditor.id} |
| queryState={latestQuery?.state} |
| runQuery={runQuery} |
| stopQuery={stopQuery} |
| overlayCreateAsMenu={showMenu ? runMenuBtn : null} |
| /> |
| </span> |
| {isFeatureEnabled(FeatureFlag.ESTIMATE_QUERY_COST) && |
| database?.allows_cost_estimate && ( |
| <span> |
| <EstimateQueryCostButton |
| getEstimate={getQueryCostEstimate} |
| queryEditorId={queryEditor.id} |
| tooltip={t('Estimate the cost before running a query')} |
| /> |
| </span> |
| )} |
| <span> |
| <QueryLimitSelect |
| queryEditorId={queryEditor.id} |
| maxRow={maxRow} |
| defaultQueryLimit={defaultQueryLimit} |
| /> |
| </span> |
| {latestQuery && ( |
| <Timer |
| startTime={latestQuery.startDttm} |
| endTime={latestQuery.endDttm} |
| state={STATE_TYPE_MAP[latestQuery.state]} |
| isRunning={latestQuery.state === 'running'} |
| /> |
| )} |
| </div> |
| <div className="rightItems"> |
| <span> |
| <SaveQuery |
| queryEditorId={queryEditor.id} |
| columns={latestQuery?.results?.columns || []} |
| onSave={onSaveQuery} |
| onUpdate={(query, remoteId, id) => |
| dispatch(updateSavedQuery(query, remoteId, id)) |
| } |
| saveQueryWarning={saveQueryWarning} |
| database={database} |
| /> |
| </span> |
| <span> |
| <ShareSqlLabQuery queryEditorId={queryEditor.id} /> |
| </span> |
| <AntdDropdown overlay={renderDropdown()} trigger="click"> |
| <Icons.MoreHoriz iconColor={theme.colors.grayscale.base} /> |
| </AntdDropdown> |
| </div> |
| </StyledToolbar> |
| ); |
| }; |
| |
| const queryPane = () => { |
| const hotkeys = getHotkeyConfig(); |
| const { aceEditorHeight, southPaneHeight } = |
| getAceEditorAndSouthPaneHeights(height, northPercent, southPercent); |
| return ( |
| <Split |
| expandToMin |
| className="queryPane" |
| sizes={[northPercent, southPercent]} |
| elementStyle={elementStyle} |
| minSize={200} |
| direction="vertical" |
| gutterSize={SQL_EDITOR_GUTTER_HEIGHT} |
| onDragStart={onResizeStart} |
| onDragEnd={onResizeEnd} |
| > |
| <div ref={northPaneRef} className="north-pane"> |
| {SqlFormExtension && ( |
| <SqlFormExtension |
| queryEditorId={queryEditor.id} |
| setQueryEditorAndSaveSqlWithDebounce={ |
| setQueryEditorAndSaveSqlWithDebounce |
| } |
| startQuery={startQuery} |
| /> |
| )} |
| <AceEditorWrapper |
| autocomplete={autocompleteEnabled} |
| onBlur={setQueryEditorAndSaveSql} |
| onChange={onSqlChanged} |
| queryEditorId={queryEditor.id} |
| database={database} |
| extendedTables={tables} |
| height={`${aceEditorHeight}px`} |
| hotkeys={hotkeys} |
| /> |
| {renderEditorBottomBar(hotkeys)} |
| </div> |
| <SouthPane |
| queryEditorId={queryEditor.id} |
| latestQueryId={latestQuery?.id} |
| height={southPaneHeight} |
| displayLimit={displayLimit} |
| defaultQueryLimit={defaultQueryLimit} |
| /> |
| </Split> |
| ); |
| }; |
| |
| const createViewModalTitle = |
| createAs === CtasEnum.VIEW ? 'CREATE VIEW AS' : 'CREATE TABLE AS'; |
| |
| const createModalPlaceHolder = |
| createAs === CtasEnum.VIEW |
| ? t('Specify name to CREATE VIEW AS schema in: public') |
| : t('Specify name to CREATE TABLE AS schema in: public'); |
| |
| const leftBarStateClass = hideLeftBar |
| ? 'schemaPane-exit-done' |
| : 'schemaPane-enter-done'; |
| return ( |
| <StyledSqlEditor ref={sqlEditorRef} className="SqlEditor"> |
| <CSSTransition classNames="schemaPane" in={!hideLeftBar} timeout={300}> |
| <ResizableSidebar |
| id={`sqllab:${queryEditor.id}`} |
| minWidth={SQL_EDITOR_LEFTBAR_WIDTH} |
| initialWidth={SQL_EDITOR_LEFTBAR_WIDTH} |
| enable={!hideLeftBar} |
| > |
| {adjustedWidth => ( |
| <StyledSidebar |
| className={`schemaPane ${leftBarStateClass}`} |
| width={adjustedWidth} |
| hide={hideLeftBar} |
| > |
| <SqlEditorLeftBar |
| database={database} |
| queryEditorId={queryEditor.id} |
| tables={tables} |
| setEmptyState={bool => setShowEmptyState(bool)} |
| /> |
| </StyledSidebar> |
| )} |
| </ResizableSidebar> |
| </CSSTransition> |
| {showEmptyState ? ( |
| <EmptyStateBig |
| image="vector.svg" |
| title={t('Select a database to write a query')} |
| description={t( |
| 'Choose one of the available databases from the panel on the left.', |
| )} |
| /> |
| ) : ( |
| queryPane() |
| )} |
| <Modal |
| visible={showCreateAsModal} |
| title={t(createViewModalTitle)} |
| onHide={() => setShowCreateAsModal(false)} |
| footer={ |
| <> |
| <Button onClick={() => setShowCreateAsModal(false)}> |
| {t('Cancel')} |
| </Button> |
| {createAs === CtasEnum.TABLE && ( |
| <Button |
| buttonStyle="primary" |
| disabled={ctas.length === 0} |
| onClick={createTableAs} |
| > |
| {t('Create')} |
| </Button> |
| )} |
| {createAs === CtasEnum.VIEW && ( |
| <Button |
| buttonStyle="primary" |
| disabled={ctas.length === 0} |
| onClick={createViewAs} |
| > |
| {t('Create')} |
| </Button> |
| )} |
| </> |
| } |
| > |
| <span>{t('Name')}</span> |
| <Input placeholder={createModalPlaceHolder} onChange={ctasChanged} /> |
| </Modal> |
| </StyledSqlEditor> |
| ); |
| }; |
| |
| SqlEditor.propTypes = propTypes; |
| |
| export default SqlEditor; |