diff --git a/superset-frontend/src/SqlLab/components/QueryTable/index.tsx b/superset-frontend/src/SqlLab/components/QueryTable/index.tsx index cc42719032d..38333ab88ef 100644 --- a/superset-frontend/src/SqlLab/components/QueryTable/index.tsx +++ b/superset-frontend/src/SqlLab/components/QueryTable/index.tsx @@ -302,7 +302,6 @@ const QueryTable = ({ diff --git a/superset-frontend/src/SqlLab/components/ResultSet/index.tsx b/superset-frontend/src/SqlLab/components/ResultSet/index.tsx index 63aa41ef216..926ee4758bc 100644 --- a/superset-frontend/src/SqlLab/components/ResultSet/index.tsx +++ b/superset-frontend/src/SqlLab/components/ResultSet/index.tsx @@ -104,7 +104,6 @@ export interface ResultSetProps { csv?: boolean; database?: Record; displayLimit: number; - height: number; queryId: string; search?: boolean; showSql?: boolean; @@ -176,7 +175,6 @@ const ResultSet = ({ csv = true, database = {}, displayLimit, - height, queryId, search = true, showSql = false, diff --git a/superset-frontend/src/SqlLab/components/SouthPane/Results.tsx b/superset-frontend/src/SqlLab/components/SouthPane/Results.tsx index 06b7033c254..e64dd74be8c 100644 --- a/superset-frontend/src/SqlLab/components/SouthPane/Results.tsx +++ b/superset-frontend/src/SqlLab/components/SouthPane/Results.tsx @@ -25,11 +25,8 @@ import { SqlLabRootState } from 'src/SqlLab/types'; import ResultSet from '../ResultSet'; import { LOCALSTORAGE_MAX_QUERY_AGE_MS } from '../../constants'; -const EXTRA_HEIGHT_RESULTS = 8; // we need extra height in RESULTS tab. because the height from props was calculated based on PREVIEW tab. - type Props = { latestQueryId?: string; - height: number; displayLimit: number; defaultQueryLimit: number; }; @@ -47,7 +44,6 @@ const StyledEmptyStateWrapper = styled.div` const Results: FC = ({ latestQueryId, - height, displayLimit, defaultQueryLimit, }) => { @@ -92,7 +88,6 @@ const Results: FC = ({ ` +const StyledPane = styled.div` width: 100%; - height: ${props => props.height}px; + height: 100%; .ant-tabs .ant-tabs-content-holder { overflow: visible; } @@ -93,7 +86,6 @@ const StyledPane = styled.div` const SouthPane = ({ queryEditorId, latestQueryId, - height, displayLimit, defaultQueryLimit, }: SouthPaneProps) => { @@ -127,7 +119,6 @@ const SouthPane = ({ ), [pinnedTables], ); - const innerTabContentHeight = height - TAB_HEIGHT; const southPaneRef = createRef(); const switchTab = (id: string) => { dispatch(setActiveSouthPaneTab(id)); @@ -159,7 +150,6 @@ const SouthPane = ({ label: t('Results'), children: ( + + ({ + children, + }: { + children: (params: { height: number }) => React.ReactChild; + }) => + children({ height: 500 }), +); jest.mock('@superset-ui/core/components/AsyncAceEditor', () => ({ ...jest.requireActual('@superset-ui/core/components/AsyncAceEditor'), FullSQLEditor: ({ diff --git a/superset-frontend/src/SqlLab/components/SqlEditor/index.tsx b/superset-frontend/src/SqlLab/components/SqlEditor/index.tsx index 3dc291ce747..e20282f84e2 100644 --- a/superset-frontend/src/SqlLab/components/SqlEditor/index.tsx +++ b/superset-frontend/src/SqlLab/components/SqlEditor/index.tsx @@ -30,9 +30,8 @@ import { import type AceEditor from 'react-ace'; import useEffectEvent from 'src/hooks/useEffectEvent'; -import { CSSTransition } from 'react-transition-group'; import { shallowEqual, useDispatch, useSelector } from 'react-redux'; -import Split from 'react-split'; +import AutoSizer from 'react-virtualized-auto-sizer'; import { css, FeatureFlag, @@ -50,7 +49,7 @@ import type { CursorPosition, } from 'src/SqlLab/types'; import type { DatabaseObject } from 'src/features/databases/types'; -import { debounce, isEmpty } from 'lodash'; +import { debounce, isEmpty, noop } from 'lodash'; import Mousetrap from 'mousetrap'; import { Alert, @@ -61,7 +60,8 @@ import { Modal, Timer, } from '@superset-ui/core/components'; -import ResizableSidebar from 'src/components/ResizableSidebar'; +import useStoredSidebarWidth from 'src/components/ResizableSidebar/useStoredSidebarWidth'; +import { Splitter } from 'src/components/Splitter'; import { Skeleton } from '@superset-ui/core/components/Skeleton'; import { Switch } from '@superset-ui/core/components/Switch'; import { Menu, MenuItemType } from '@superset-ui/core/components/Menu'; @@ -87,16 +87,13 @@ import { formatQuery, fetchQueryEditor, switchQueryEditor, + toggleLeftBar, } 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, } from 'src/SqlLab/constants'; import { @@ -167,12 +164,8 @@ const StyledToolbar = styled.div` } `; -const StyledSidebar = styled.div<{ width: number; hide: boolean | undefined }>` - flex: 0 0 ${({ width }) => width}px; - width: ${({ width }) => width}px; - padding: ${({ theme, hide }) => (hide ? 0 : theme.sizeUnit * 2.5)}px; - border-right: 1px solid - ${({ theme, hide }) => (hide ? 'transparent' : theme.colorBorder)}; +const StyledSidebar = styled.div` + padding: ${({ theme }) => theme.sizeUnit * 2.5}px; `; const StyledSqlEditor = styled.div` @@ -186,47 +179,26 @@ const StyledSqlEditor = styled.div` } .queryPane { - flex: 1 1 auto; padding: ${theme.sizeUnit * 2}px; - overflow-y: auto; - overflow-x: scroll; + + .ant-splitter-bar .ant-splitter-bar-dragger { + &::before { + background: transparent; + } + &::after { + height: ${SQL_EDITOR_GUTTER_HEIGHT}px; + background: transparent; + border-top: 1px solid ${theme.colorBorder}; + border-bottom: 1px solid ${theme.colorBorder}; + } + } } - .schemaPane-enter-done, - .schemaPane-exit { - transform: translateX(0); - z-index: 7; + .north-pane { + height: 100%; } - .schemaPane-exit-active { - transform: translateX(-120%); - } - - .schemaPane-enter-active { - transform: translateX(0); - max-width: ${theme.sizeUnit * 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.colorBorder}; - border-bottom: 1px solid ${theme.colorBorder}; - width: 3%; - margin: ${SQL_EDITOR_GUTTER_MARGIN}px 47%; - } - - .gutter.gutter-vertical { - cursor: row-resize; + .sql-container { + flex: 1 1 auto; } `} `; @@ -242,16 +214,6 @@ export type Props = { scheduleQueryWarning: string | null; }; -const elementStyle = ( - dimension: string, - elementSize: number, - gutterSize: number, -) => ({ - [dimension]: `calc(${elementSize}% - ${ - gutterSize + SQL_EDITOR_GUTTER_MARGIN - }px)`, -}); - const SqlEditor: FC = ({ queryEditor, defaultQueryLimit, @@ -304,9 +266,6 @@ const SqlEditor: FC = ({ const [northPercent, setNorthPercent] = useState( queryEditor.northPercent || INITIAL_NORTH_PERCENT, ); - const [southPercent, setSouthPercent] = useState( - queryEditor.southPercent || INITIAL_SOUTH_PERCENT, - ); const [autocompleteEnabled, setAutocompleteEnabled] = useState( getItem(LocalStorageKeys.SqllabIsAutocompleteEnabled, true), ); @@ -322,7 +281,6 @@ const SqlEditor: FC = ({ ); const sqlEditorRef = useRef(null); - const northPaneRef = useRef(null); const SqlFormExtension = extensionsRegistry.get('sqleditor.extension.form'); @@ -380,12 +338,6 @@ const SqlEditor: FC = ({ } }, [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(); @@ -618,13 +570,12 @@ const SqlEditor: FC = ({ } }; - const onResizeEnd = ([northPercent, southPercent]: number[]) => { - setNorthPercent(northPercent); - setSouthPercent(southPercent); + const onResizeEnd = ([nHeight, sHeight]: number[]) => { + const northPercent = Math.round((nHeight * 100) / (nHeight + sHeight)); + const southPercent = 100 - northPercent; - if (northPaneRef.current?.clientHeight) { - dispatch(persistEditorHeight(queryEditor, northPercent, southPercent)); - } + setNorthPercent(northPercent); + dispatch(persistEditorHeight(queryEditor, northPercent, southPercent)); }; const setQueryEditorAndSaveSql = useCallback( @@ -644,22 +595,6 @@ const SqlEditor: FC = ({ dispatch(queryEditorSetSql(queryEditor, 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: number, - northPercent: number, - southPercent: number, - ) => ({ - aceEditorHeight: - (height * northPercent) / (theme.sizeUnit * 25) - - (SQL_EDITOR_GUTTER_HEIGHT / 2 + SQL_EDITOR_GUTTER_MARGIN) - - SQL_TOOLBAR_HEIGHT, - southPaneHeight: - (height * southPercent) / (theme.sizeUnit * 25) - - (SQL_EDITOR_GUTTER_HEIGHT / 2 + SQL_EDITOR_GUTTER_MARGIN), - }); - const getQueryCostEstimate = () => { logAction(LOG_ACTIONS_SQLLAB_ESTIMATE_QUERY_COST, { shortcut: false }); if (database) { @@ -946,6 +881,7 @@ const SqlEditor: FC = ({ font-size: ${theme.fontSize}px; font-weight: ${theme.fontWeightStrong}; color: ${theme.colorPrimaryText}; + margin: 0px; `} > {' '} @@ -957,6 +893,7 @@ const SqlEditor: FC = ({ font-size: ${theme.fontSize}px; font-weight: ${theme.fontWeightStrong}; color: ${theme.colorPrimaryText}; + margin: 0px; `} > {t( @@ -970,23 +907,18 @@ const SqlEditor: FC = ({ /> ); - const queryPane = () => { - const height = getSqlEditorHeight(); - const { aceEditorHeight, southPaneHeight } = - getAceEditorAndSouthPaneHeights(height, northPercent, southPercent); - return ( - ( + + -
+
{SqlFormExtension && ( = ({ /> )} {queryEditor.isDataset && renderDatasetWarning()} - {isActive && ( - - )} +
+ + {({ height }) => + isActive && ( + + ) + } + +
{renderEditorBottomBar(showEmptyState)}
+ + - - ); - }; + + + ); const createViewModalTitle = createAs === CtasEnum.View ? 'CREATE VIEW AS' : 'CREATE TABLE AS'; @@ -1029,54 +970,68 @@ const SqlEditor: FC = ({ ? 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'; + const [width, setWidth] = useStoredSidebarWidth( + `sqllab:${queryEditor.id}`, + SQL_EDITOR_LEFTBAR_WIDTH, + ); + + const onSidebarChange = useCallback( + (sizes: number[]) => { + const [updatedWidth] = sizes; + if (hideLeftBar || updatedWidth === 0) { + dispatch(toggleLeftBar({ id: queryEditor.id, hideLeftBar })); + if (hideLeftBar) { + // Due to a bug in the splitter, the width must be changed + // in order to properly restore the previous size + setWidth(width + 0.01); + } + } else { + setWidth(updatedWidth); + } + }, + [dispatch, hideLeftBar], + ); + return ( - - + - {adjustedWidth => ( - + + + + + {shouldLoadQueryEditor ? ( +
- - + +
+ ) : showEmptyState && !hasSqlStatement ? ( + + ) : ( + queryPane() )} -
-
- {shouldLoadQueryEditor ? ( -
- -
- ) : showEmptyState && !hasSqlStatement ? ( - - ) : ( - queryPane() - )} + + = ({ dbId, catalog, schema, tableName }) => { visualize={false} csv={false} cache - height={ - height - TAB_HEADER_HEIGHT - PREVIEW_TOP_ACTION_HEIGHT - } displayLimit={PREVIEW_QUERY_LIMIT} defaultQueryLimit={PREVIEW_QUERY_LIMIT} /> diff --git a/superset-frontend/src/SqlLab/constants.ts b/superset-frontend/src/SqlLab/constants.ts index a2f19c96055..97667325d2b 100644 --- a/superset-frontend/src/SqlLab/constants.ts +++ b/superset-frontend/src/SqlLab/constants.ts @@ -66,13 +66,9 @@ export const TIME_OPTIONS = [ ]; // SqlEditor layout constants -export const SQL_EDITOR_GUTTER_HEIGHT = 5; -export const SQL_EDITOR_GUTTER_MARGIN = 3; -export const SQL_TOOLBAR_HEIGHT = 51; +export const SQL_EDITOR_GUTTER_HEIGHT = 4; export const SQL_EDITOR_LEFTBAR_WIDTH = 400; -export const SQL_EDITOR_PADDING = 10; export const INITIAL_NORTH_PERCENT = 30; -export const INITIAL_SOUTH_PERCENT = 70; export const SET_QUERY_EDITOR_SQL_DEBOUNCE_MS = 2000; export const VALIDATION_DEBOUNCE_MS = 600; export const WINDOW_RESIZE_THROTTLE_MS = 100; diff --git a/superset-frontend/src/components/Splitter/index.tsx b/superset-frontend/src/components/Splitter/index.tsx new file mode 100644 index 00000000000..5e6ad213899 --- /dev/null +++ b/superset-frontend/src/components/Splitter/index.tsx @@ -0,0 +1,21 @@ +/** + * 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. + */ + +export { Splitter } from 'antd'; +export type { SplitterProps } from 'antd/es/splitter';