chore(sqllab): Typescript for SqlEditor component (#25228)
This commit is contained in:
parent
c81c60c91f
commit
34f99708d4
|
|
@ -191,6 +191,7 @@
|
|||
"@types/jquery": "^3.5.8",
|
||||
"@types/js-levenshtein": "^1.1.0",
|
||||
"@types/json-bigint": "^1.0.1",
|
||||
"@types/mousetrap": "^1.6.11",
|
||||
"@types/react": "^16.9.43",
|
||||
"@types/react-dom": "^16.9.8",
|
||||
"@types/react-gravatar": "^2.6.8",
|
||||
|
|
@ -19514,6 +19515,12 @@
|
|||
"integrity": "sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/mousetrap": {
|
||||
"version": "1.6.11",
|
||||
"resolved": "https://registry.npmjs.org/@types/mousetrap/-/mousetrap-1.6.11.tgz",
|
||||
"integrity": "sha512-F0oAily9Q9QQpv9JKxKn0zMKfOo36KHCW7myYsmUyf2t0g+sBTbG3UleTPoguHdE1z3GLFr3p7/wiOio52QFjQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/ms": {
|
||||
"version": "0.7.31",
|
||||
"resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz",
|
||||
|
|
@ -79317,6 +79324,12 @@
|
|||
"integrity": "sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/mousetrap": {
|
||||
"version": "1.6.11",
|
||||
"resolved": "https://registry.npmjs.org/@types/mousetrap/-/mousetrap-1.6.11.tgz",
|
||||
"integrity": "sha512-F0oAily9Q9QQpv9JKxKn0zMKfOo36KHCW7myYsmUyf2t0g+sBTbG3UleTPoguHdE1z3GLFr3p7/wiOio52QFjQ==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/ms": {
|
||||
"version": "0.7.31",
|
||||
"resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz",
|
||||
|
|
|
|||
|
|
@ -256,6 +256,7 @@
|
|||
"@types/jquery": "^3.5.8",
|
||||
"@types/js-levenshtein": "^1.1.0",
|
||||
"@types/json-bigint": "^1.0.1",
|
||||
"@types/mousetrap": "^1.6.11",
|
||||
"@types/react": "^16.9.43",
|
||||
"@types/react-dom": "^16.9.8",
|
||||
"@types/react-gravatar": "^2.6.8",
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
* under the License.
|
||||
*/
|
||||
import React, { useState, useEffect, useRef } from 'react';
|
||||
import type { IAceEditor } from 'react-ace/lib/types';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { css, styled, usePrevious } from '@superset-ui/core';
|
||||
|
||||
|
|
@ -30,7 +31,7 @@ type HotKey = {
|
|||
key: string;
|
||||
descr: string;
|
||||
name: string;
|
||||
func: () => void;
|
||||
func: (aceEditor: IAceEditor) => void;
|
||||
};
|
||||
|
||||
type AceEditorWrapperProps = {
|
||||
|
|
|
|||
|
|
@ -19,7 +19,6 @@
|
|||
import React, { useMemo } from 'react';
|
||||
import { t, styled, useTheme } from '@superset-ui/core';
|
||||
|
||||
import { Menu } from 'src/components/Menu';
|
||||
import Button from 'src/components/Button';
|
||||
import Icons from 'src/components/Icons';
|
||||
import { DropdownButton } from 'src/components/DropdownButton';
|
||||
|
|
@ -33,7 +32,7 @@ export interface RunQueryActionButtonProps {
|
|||
queryState?: string;
|
||||
runQuery: (c?: boolean) => void;
|
||||
stopQuery: () => void;
|
||||
overlayCreateAsMenu: typeof Menu | null;
|
||||
overlayCreateAsMenu: React.ReactElement | null;
|
||||
}
|
||||
|
||||
const buildText = (
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
* under the License.
|
||||
*/
|
||||
import React, { useState, useEffect, useMemo } from 'react';
|
||||
import type { DatabaseObject } from 'src/features/databases/types';
|
||||
import { Row, Col } from 'src/components';
|
||||
import { Input, TextArea } from 'src/components/Input';
|
||||
import { t, styled } from '@superset-ui/core';
|
||||
|
|
@ -39,10 +40,10 @@ interface SaveQueryProps {
|
|||
onSave: (arg0: QueryPayload, id: string) => void;
|
||||
onUpdate: (arg0: QueryPayload, id: string) => void;
|
||||
saveQueryWarning: string | null;
|
||||
database: Record<string, any>;
|
||||
database: Partial<DatabaseObject> | undefined;
|
||||
}
|
||||
|
||||
type QueryPayload = {
|
||||
export type QueryPayload = {
|
||||
name: string;
|
||||
description?: string;
|
||||
id?: string;
|
||||
|
|
@ -65,7 +66,7 @@ const SaveQuery = ({
|
|||
queryEditorId,
|
||||
onSave = () => {},
|
||||
onUpdate,
|
||||
saveQueryWarning = null,
|
||||
saveQueryWarning,
|
||||
database,
|
||||
columns,
|
||||
}: SaveQueryProps) => {
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@ interface ScheduleQueryButtonProps {
|
|||
defaultLabel?: string;
|
||||
sql: string;
|
||||
schema?: string;
|
||||
dbId: number;
|
||||
dbId?: number;
|
||||
animation?: boolean;
|
||||
onSchedule?: Function;
|
||||
scheduleQueryWarning: string | null;
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ import { act } from 'react-dom/test-utils';
|
|||
import { fireEvent, render, waitFor } from 'spec/helpers/testing-library';
|
||||
import fetchMock from 'fetch-mock';
|
||||
import reducers from 'spec/helpers/reducerIndex';
|
||||
import SqlEditor from 'src/SqlLab/components/SqlEditor';
|
||||
import { setupStore } from 'src/views/store';
|
||||
import {
|
||||
initialState,
|
||||
|
|
@ -34,10 +33,20 @@ import ResultSet from 'src/SqlLab/components/ResultSet';
|
|||
import { api } from 'src/hooks/apiResources/queryApi';
|
||||
import { getExtensionsRegistry } from '@superset-ui/core';
|
||||
import setupExtensions from 'src/setup/setupExtensions';
|
||||
import type { Action, Middleware, Store } from 'redux';
|
||||
import SqlEditor, { Props } from '.';
|
||||
|
||||
jest.mock('src/components/AsyncAceEditor', () => ({
|
||||
...jest.requireActual('src/components/AsyncAceEditor'),
|
||||
FullSQLEditor: ({ onChange, onBlur, value }) => (
|
||||
FullSQLEditor: ({
|
||||
onChange,
|
||||
onBlur,
|
||||
value,
|
||||
}: {
|
||||
onChange: (value: string) => void;
|
||||
onBlur: React.FocusEventHandler<HTMLTextAreaElement>;
|
||||
value: string;
|
||||
}) => (
|
||||
<textarea
|
||||
data-test="react-ace"
|
||||
onChange={evt => onChange(evt.target.value)}
|
||||
|
|
@ -56,8 +65,8 @@ fetchMock.get('glob:*/api/v1/database/*', { result: [] });
|
|||
fetchMock.get('glob:*/api/v1/database/*/tables/*', { options: [] });
|
||||
fetchMock.post('glob:*/sqllab/execute/*', { result: [] });
|
||||
|
||||
let store;
|
||||
let actions;
|
||||
let store: Store;
|
||||
let actions: Action[];
|
||||
const latestQuery = {
|
||||
...queries[0],
|
||||
sqlEditorId: defaultQueryEditor.id,
|
||||
|
|
@ -91,13 +100,13 @@ const mockInitialState = {
|
|||
},
|
||||
};
|
||||
|
||||
const setup = (props = {}, store) =>
|
||||
const setup = (props: Props, store: Store) =>
|
||||
render(<SqlEditor {...props} />, {
|
||||
useRedux: true,
|
||||
...(store && { store }),
|
||||
});
|
||||
|
||||
const logAction = () => next => action => {
|
||||
const logAction: Middleware = () => next => action => {
|
||||
if (typeof action === 'function') {
|
||||
return next(action);
|
||||
}
|
||||
|
|
@ -105,9 +114,9 @@ const logAction = () => next => action => {
|
|||
return next(action);
|
||||
};
|
||||
|
||||
const createStore = initState =>
|
||||
const createStore = (initState: object) =>
|
||||
setupStore({
|
||||
disableDegugger: true,
|
||||
disableDebugger: true,
|
||||
initialState: initState,
|
||||
rootReducers: reducers,
|
||||
middleware: getDefaultMiddleware =>
|
||||
|
|
@ -124,18 +133,22 @@ describe('SqlEditor', () => {
|
|||
defaultQueryLimit: 1000,
|
||||
maxRow: 100000,
|
||||
displayLimit: 100,
|
||||
saveQueryWarning: '',
|
||||
scheduleQueryWarning: '',
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
store = createStore(mockInitialState);
|
||||
actions = [];
|
||||
|
||||
SqlEditorLeftBar.mockClear();
|
||||
SqlEditorLeftBar.mockImplementation(() => (
|
||||
(SqlEditorLeftBar as jest.Mock).mockClear();
|
||||
(SqlEditorLeftBar as jest.Mock).mockImplementation(() => (
|
||||
<div data-test="mock-sql-editor-left-bar" />
|
||||
));
|
||||
ResultSet.mockClear();
|
||||
ResultSet.mockImplementation(() => <div data-test="mock-result-set" />);
|
||||
(ResultSet as jest.Mock).mockClear();
|
||||
(ResultSet as jest.Mock).mockImplementation(() => (
|
||||
<div data-test="mock-result-set" />
|
||||
));
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
|
@ -168,8 +181,8 @@ describe('SqlEditor', () => {
|
|||
const { findByTestId } = setup(mockedProps, store);
|
||||
const editor = await findByTestId('react-ace');
|
||||
const sql = 'select *';
|
||||
const renderCount = SqlEditorLeftBar.mock.calls.length;
|
||||
const renderCountForSouthPane = ResultSet.mock.calls.length;
|
||||
const renderCount = (SqlEditorLeftBar as jest.Mock).mock.calls.length;
|
||||
const renderCountForSouthPane = (ResultSet as jest.Mock).mock.calls.length;
|
||||
expect(SqlEditorLeftBar).toHaveBeenCalledTimes(renderCount);
|
||||
expect(ResultSet).toHaveBeenCalledTimes(renderCountForSouthPane);
|
||||
fireEvent.change(editor, { target: { value: sql } });
|
||||
|
|
@ -24,11 +24,12 @@ import React, {
|
|||
useMemo,
|
||||
useRef,
|
||||
useCallback,
|
||||
ChangeEvent,
|
||||
} from 'react';
|
||||
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 PropTypes from 'prop-types';
|
||||
import Split from 'react-split';
|
||||
import {
|
||||
css,
|
||||
|
|
@ -38,7 +39,11 @@ import {
|
|||
t,
|
||||
useTheme,
|
||||
getExtensionsRegistry,
|
||||
QueryResponse,
|
||||
Query,
|
||||
} from '@superset-ui/core';
|
||||
import type { QueryEditor, SqlLabRootState } from 'src/SqlLab/types';
|
||||
import type { DatabaseObject } from 'src/features/databases/types';
|
||||
import debounce from 'lodash/debounce';
|
||||
import throttle from 'lodash/throttle';
|
||||
import Modal from 'src/components/Modal';
|
||||
|
|
@ -90,11 +95,11 @@ import getBootstrapData from 'src/utils/getBootstrapData';
|
|||
import { isEmpty } from 'lodash';
|
||||
import TemplateParamsEditor from '../TemplateParamsEditor';
|
||||
import SouthPane from '../SouthPane';
|
||||
import SaveQuery from '../SaveQuery';
|
||||
import SaveQuery, { QueryPayload } from '../SaveQuery';
|
||||
import ScheduleQueryButton from '../ScheduleQueryButton';
|
||||
import EstimateQueryCostButton from '../EstimateQueryCostButton';
|
||||
import ShareSqlLabQuery from '../ShareSqlLabQuery';
|
||||
import SqlEditorLeftBar from '../SqlEditorLeftBar';
|
||||
import SqlEditorLeftBar, { ExtendedTable } from '../SqlEditorLeftBar';
|
||||
import AceEditorWrapper from '../AceEditorWrapper';
|
||||
import RunQueryActionButton from '../RunQueryActionButton';
|
||||
import QueryLimitSelect from '../QueryLimitSelect';
|
||||
|
|
@ -133,7 +138,7 @@ const StyledToolbar = styled.div`
|
|||
}
|
||||
`;
|
||||
|
||||
const StyledSidebar = 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.gridUnit * 2.5)}px;
|
||||
|
|
@ -198,46 +203,60 @@ const StyledSqlEditor = styled.div`
|
|||
`}
|
||||
`;
|
||||
|
||||
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 = ({
|
||||
export type Props = {
|
||||
tables: ExtendedTable[];
|
||||
queryEditor: QueryEditor;
|
||||
defaultQueryLimit: number;
|
||||
maxRow: number;
|
||||
displayLimit: number;
|
||||
saveQueryWarning: string | null;
|
||||
scheduleQueryWarning: string | null;
|
||||
};
|
||||
|
||||
const elementStyle = (
|
||||
dimension: string,
|
||||
elementSize: number,
|
||||
gutterSize: number,
|
||||
) => ({
|
||||
[dimension]: `calc(${elementSize}% - ${
|
||||
gutterSize + SQL_EDITOR_GUTTER_MARGIN
|
||||
}px)`,
|
||||
});
|
||||
|
||||
const SqlEditor: React.FC<Props> = ({
|
||||
tables,
|
||||
queryEditor,
|
||||
defaultQueryLimit,
|
||||
maxRow,
|
||||
displayLimit,
|
||||
saveQueryWarning,
|
||||
scheduleQueryWarning = null,
|
||||
scheduleQueryWarning,
|
||||
}) => {
|
||||
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 { database, latestQuery, hideLeftBar } = useSelector<
|
||||
SqlLabRootState,
|
||||
{
|
||||
database?: DatabaseObject;
|
||||
latestQuery?: QueryResponse;
|
||||
hideLeftBar?: boolean;
|
||||
}
|
||||
>(({ 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);
|
||||
|
|
@ -255,8 +274,8 @@ const SqlEditor = ({
|
|||
const [createAs, setCreateAs] = useState('');
|
||||
const [showEmptyState, setShowEmptyState] = useState(false);
|
||||
|
||||
const sqlEditorRef = useRef(null);
|
||||
const northPaneRef = useRef(null);
|
||||
const sqlEditorRef = useRef<HTMLDivElement>(null);
|
||||
const northPaneRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const SqlFormExtension = extensionsRegistry.get('sqleditor.extension.form');
|
||||
|
||||
|
|
@ -311,7 +330,7 @@ const SqlEditor = ({
|
|||
const getHotkeyConfig = useCallback(() => {
|
||||
// Get the user's OS
|
||||
const userOS = detectOS();
|
||||
const base = [
|
||||
return [
|
||||
{
|
||||
name: 'runQuery1',
|
||||
key: 'ctrl+r',
|
||||
|
|
@ -332,68 +351,6 @@ const SqlEditor = ({
|
|||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'runQuery3',
|
||||
key: 'ctrl+shift+enter',
|
||||
descr: t('Run current query'),
|
||||
func: editor => {
|
||||
if (!editor.getValue().trim()) {
|
||||
return;
|
||||
}
|
||||
const session = editor.getSession();
|
||||
const cursorPosition = editor.getCursorPosition();
|
||||
const totalLine = session.getLength();
|
||||
const currentRow = editor.getFirstVisibleRow();
|
||||
let end = editor.find(';', {
|
||||
backwards: false,
|
||||
skipCurrent: true,
|
||||
start: cursorPosition,
|
||||
})?.end;
|
||||
if (!end || end.row < cursorPosition.row) {
|
||||
end = {
|
||||
row: totalLine + 1,
|
||||
column: 0,
|
||||
};
|
||||
}
|
||||
let start = editor.find(';', {
|
||||
backwards: true,
|
||||
skipCurrent: true,
|
||||
start: cursorPosition,
|
||||
})?.end;
|
||||
let currentLine = editor.find(';', {
|
||||
backwards: true,
|
||||
skipCurrent: true,
|
||||
start: cursorPosition,
|
||||
})?.end?.row;
|
||||
if (
|
||||
!currentLine ||
|
||||
currentLine > cursorPosition.row ||
|
||||
(currentLine === cursorPosition.row &&
|
||||
start?.column > cursorPosition.column)
|
||||
) {
|
||||
currentLine = 0;
|
||||
}
|
||||
let content =
|
||||
currentLine === start?.row
|
||||
? session.getLine(currentLine).slice(start.column).trim()
|
||||
: session.getLine(currentLine).trim();
|
||||
while (!content && currentLine < totalLine) {
|
||||
currentLine += 1;
|
||||
content = session.getLine(currentLine).trim();
|
||||
}
|
||||
if (currentLine !== start?.row) {
|
||||
start = { row: currentLine, column: 0 };
|
||||
}
|
||||
editor.selection.setRange({
|
||||
start: start ?? { row: 0, column: 0 },
|
||||
end,
|
||||
});
|
||||
startQuery();
|
||||
editor.selection.clearSelection();
|
||||
editor.moveCursorToPosition(cursorPosition);
|
||||
editor.scrollToRow(currentRow);
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'newTab',
|
||||
key: userOS === 'Windows' ? 'ctrl+q' : 'ctrl+t',
|
||||
|
|
@ -409,20 +366,84 @@ const SqlEditor = ({
|
|||
func: stopQuery,
|
||||
},
|
||||
];
|
||||
}, [dispatch, queryEditor.sql, startQuery, stopQuery]);
|
||||
|
||||
const hotkeys = useMemo(() => {
|
||||
// Get all hotkeys including ace editor hotkeys
|
||||
// Get the user's OS
|
||||
const userOS = detectOS();
|
||||
const base = [
|
||||
...getHotkeyConfig(),
|
||||
{
|
||||
name: 'runQuery3',
|
||||
key: 'ctrl+shift+enter',
|
||||
descr: t('Run current query'),
|
||||
func: (editor: AceEditor['editor']) => {
|
||||
if (!editor.getValue().trim()) {
|
||||
return;
|
||||
}
|
||||
const session = editor.getSession();
|
||||
const cursorPosition = editor.getCursorPosition();
|
||||
const totalLine = session.getLength();
|
||||
const currentRow = editor.getFirstVisibleRow();
|
||||
let end = editor.find(';', {
|
||||
backwards: false,
|
||||
skipCurrent: true,
|
||||
})?.end;
|
||||
if (!end || end.row < cursorPosition.row) {
|
||||
end = {
|
||||
row: totalLine + 1,
|
||||
column: 0,
|
||||
};
|
||||
}
|
||||
let start = editor.find(';', {
|
||||
backwards: true,
|
||||
skipCurrent: true,
|
||||
})?.end;
|
||||
let currentLine = start?.row;
|
||||
if (
|
||||
!currentLine ||
|
||||
currentLine > cursorPosition.row ||
|
||||
(currentLine === cursorPosition.row &&
|
||||
(start?.column || 0) > cursorPosition.column)
|
||||
) {
|
||||
currentLine = 0;
|
||||
}
|
||||
let content =
|
||||
currentLine === start?.row
|
||||
? session.getLine(currentLine).slice(start.column).trim()
|
||||
: session.getLine(currentLine).trim();
|
||||
while (!content && currentLine < totalLine) {
|
||||
currentLine += 1;
|
||||
content = session.getLine(currentLine).trim();
|
||||
}
|
||||
if (currentLine !== start?.row) {
|
||||
start = { row: currentLine, column: 0 };
|
||||
}
|
||||
editor.selection.setSelectionRange({
|
||||
start: start ?? { row: 0, column: 0 },
|
||||
end,
|
||||
});
|
||||
startQuery();
|
||||
editor.selection.clearSelection();
|
||||
editor.moveCursorToPosition(cursorPosition);
|
||||
editor.scrollToRow(currentRow);
|
||||
},
|
||||
},
|
||||
];
|
||||
if (userOS === 'MacOS') {
|
||||
base.push({
|
||||
name: 'previousLine',
|
||||
key: 'ctrl+p',
|
||||
descr: t('Previous Line'),
|
||||
func: editor => {
|
||||
editor.navigateUp(1);
|
||||
editor.navigateUp();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return base;
|
||||
}, [dispatch, queryEditor.sql, startQuery, stopQuery]);
|
||||
}, [getHotkeyConfig, startQuery]);
|
||||
|
||||
const onBeforeUnload = useEffectEvent(event => {
|
||||
if (
|
||||
|
|
@ -461,20 +482,29 @@ const SqlEditor = ({
|
|||
|
||||
useEffect(() => {
|
||||
// setup hotkeys
|
||||
Mousetrap.reset();
|
||||
const hotkeys = getHotkeyConfig();
|
||||
hotkeys.forEach(keyConfig => {
|
||||
Mousetrap.bind([keyConfig.key], keyConfig.func);
|
||||
});
|
||||
return () => {
|
||||
hotkeys.forEach(keyConfig => {
|
||||
Mousetrap.unbind(keyConfig.key);
|
||||
});
|
||||
};
|
||||
}, [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 editorEl = document.getElementsByClassName(
|
||||
'ace_content',
|
||||
)[0] as HTMLElement;
|
||||
if (editorEl) {
|
||||
editorEl.style.height = '100%';
|
||||
}
|
||||
};
|
||||
|
||||
const onResizeEnd = ([northPercent, southPercent]) => {
|
||||
const onResizeEnd = ([northPercent, southPercent]: number[]) => {
|
||||
setNorthPercent(northPercent);
|
||||
setSouthPercent(southPercent);
|
||||
|
||||
|
|
@ -495,7 +525,7 @@ const SqlEditor = ({
|
|||
[setQueryEditorAndSaveSql],
|
||||
);
|
||||
|
||||
const onSqlChanged = sql => {
|
||||
const onSqlChanged = (sql: string) => {
|
||||
dispatch(queryEditorSetSql(queryEditor, sql));
|
||||
setQueryEditorAndSaveSqlWithDebounce(sql);
|
||||
};
|
||||
|
|
@ -503,9 +533,9 @@ const SqlEditor = ({
|
|||
// 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,
|
||||
height: number,
|
||||
northPercent: number,
|
||||
southPercent: number,
|
||||
) => ({
|
||||
aceEditorHeight:
|
||||
(height * northPercent) / (theme.gridUnit * 25) -
|
||||
|
|
@ -530,12 +560,6 @@ const SqlEditor = ({
|
|||
setAutocompleteEnabled(!autocompleteEnabled);
|
||||
};
|
||||
|
||||
const elementStyle = (dimension, elementSize, gutterSize) => ({
|
||||
[dimension]: `calc(${elementSize}% - ${
|
||||
gutterSize + SQL_EDITOR_GUTTER_MARGIN
|
||||
}px)`,
|
||||
});
|
||||
|
||||
const createTableAs = () => {
|
||||
startQuery(true, CtasEnum.TABLE);
|
||||
setShowCreateAsModal(false);
|
||||
|
|
@ -548,7 +572,7 @@ const SqlEditor = ({
|
|||
setCtas('');
|
||||
};
|
||||
|
||||
const ctasChanged = event => {
|
||||
const ctasChanged = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
setCtas(event.target.value);
|
||||
};
|
||||
|
||||
|
|
@ -566,7 +590,6 @@ const SqlEditor = ({
|
|||
<AntdSwitch
|
||||
checked={autocompleteEnabled}
|
||||
onChange={handleToggleAutocompleteEnabled}
|
||||
name="autocomplete-switch"
|
||||
/>{' '}
|
||||
</Menu.Item>
|
||||
{isFeatureEnabled(FeatureFlag.ENABLE_TEMPLATE_PROCESSING) && (
|
||||
|
|
@ -585,7 +608,7 @@ const SqlEditor = ({
|
|||
<ScheduleQueryButton
|
||||
defaultLabel={qe.name}
|
||||
sql={qe.sql}
|
||||
onSchedule={query => dispatch(scheduleQuery(query))}
|
||||
onSchedule={(query: Query) => dispatch(scheduleQuery(query))}
|
||||
schema={qe.schema}
|
||||
dbId={qe.dbId}
|
||||
scheduleQueryWarning={scheduleQueryWarning}
|
||||
|
|
@ -598,7 +621,7 @@ const SqlEditor = ({
|
|||
);
|
||||
};
|
||||
|
||||
const onSaveQuery = async (query, clientId) => {
|
||||
const onSaveQuery = async (query: QueryPayload, clientId: string) => {
|
||||
const savedQuery = await dispatch(saveQuery(query, clientId));
|
||||
dispatch(addSavedQueryToTabState(queryEditor, savedQuery));
|
||||
};
|
||||
|
|
@ -639,7 +662,7 @@ const SqlEditor = ({
|
|||
<div className="leftItems">
|
||||
<span>
|
||||
<RunQueryActionButton
|
||||
allowAsync={database ? database.allow_run_async : false}
|
||||
allowAsync={database?.allow_run_async === true}
|
||||
queryEditorId={queryEditor.id}
|
||||
queryState={latestQuery?.state}
|
||||
runQuery={runQuery}
|
||||
|
|
@ -668,7 +691,7 @@ const SqlEditor = ({
|
|||
<Timer
|
||||
startTime={latestQuery.startDttm}
|
||||
endTime={latestQuery.endDttm}
|
||||
state={STATE_TYPE_MAP[latestQuery.state]}
|
||||
status={STATE_TYPE_MAP[latestQuery.state]}
|
||||
isRunning={latestQuery.state === 'running'}
|
||||
/>
|
||||
)}
|
||||
|
|
@ -679,8 +702,8 @@ const SqlEditor = ({
|
|||
queryEditorId={queryEditor.id}
|
||||
columns={latestQuery?.results?.columns || []}
|
||||
onSave={onSaveQuery}
|
||||
onUpdate={(query, remoteId, id) =>
|
||||
dispatch(updateSavedQuery(query, remoteId, id))
|
||||
onUpdate={(query, remoteId) =>
|
||||
dispatch(updateSavedQuery(query, remoteId))
|
||||
}
|
||||
saveQueryWarning={saveQueryWarning}
|
||||
database={database}
|
||||
|
|
@ -689,7 +712,7 @@ const SqlEditor = ({
|
|||
<span>
|
||||
<ShareSqlLabQuery queryEditorId={queryEditor.id} />
|
||||
</span>
|
||||
<AntdDropdown overlay={renderDropdown()} trigger="click">
|
||||
<AntdDropdown overlay={renderDropdown()} trigger={['click']}>
|
||||
<Icons.MoreHoriz iconColor={theme.colors.grayscale.base} />
|
||||
</AntdDropdown>
|
||||
</div>
|
||||
|
|
@ -698,7 +721,6 @@ const SqlEditor = ({
|
|||
};
|
||||
|
||||
const queryPane = () => {
|
||||
const hotkeys = getHotkeyConfig();
|
||||
const { aceEditorHeight, southPaneHeight } =
|
||||
getAceEditorAndSouthPaneHeights(height, northPercent, southPercent);
|
||||
return (
|
||||
|
|
@ -731,7 +753,7 @@ const SqlEditor = ({
|
|||
height={`${aceEditorHeight}px`}
|
||||
hotkeys={hotkeys}
|
||||
/>
|
||||
{renderEditorBottomBar(hotkeys)}
|
||||
{renderEditorBottomBar()}
|
||||
</div>
|
||||
<SouthPane
|
||||
queryEditorId={queryEditor.id}
|
||||
|
|
@ -792,7 +814,7 @@ const SqlEditor = ({
|
|||
queryPane()
|
||||
)}
|
||||
<Modal
|
||||
visible={showCreateAsModal}
|
||||
show={showCreateAsModal}
|
||||
title={t(createViewModalTitle)}
|
||||
onHide={() => setShowCreateAsModal(false)}
|
||||
footer={
|
||||
|
|
@ -828,6 +850,4 @@ const SqlEditor = ({
|
|||
);
|
||||
};
|
||||
|
||||
SqlEditor.propTypes = propTypes;
|
||||
|
||||
export default SqlEditor;
|
||||
|
|
@ -46,7 +46,7 @@ import Icons from 'src/components/Icons';
|
|||
import { TableSelectorMultiple } from 'src/components/TableSelector';
|
||||
import { IconTooltip } from 'src/components/IconTooltip';
|
||||
import useQueryEditor from 'src/SqlLab/hooks/useQueryEditor';
|
||||
import { DatabaseObject } from 'src/components/DatabaseSelector';
|
||||
import type { DatabaseObject } from 'src/components/DatabaseSelector';
|
||||
import { emptyStateComponent } from 'src/components/EmptyState';
|
||||
import {
|
||||
getItem,
|
||||
|
|
@ -55,7 +55,7 @@ import {
|
|||
} from 'src/utils/localStorageHelpers';
|
||||
import TableElement from '../TableElement';
|
||||
|
||||
interface ExtendedTable extends Table {
|
||||
export interface ExtendedTable extends Table {
|
||||
expanded: boolean;
|
||||
}
|
||||
|
||||
|
|
@ -63,7 +63,7 @@ interface SqlEditorLeftBarProps {
|
|||
queryEditorId: string;
|
||||
height?: number;
|
||||
tables?: ExtendedTable[];
|
||||
database: DatabaseObject;
|
||||
database?: DatabaseObject;
|
||||
setEmptyState: Dispatch<SetStateAction<boolean>>;
|
||||
}
|
||||
|
||||
|
|
@ -134,7 +134,9 @@ const SqlEditorLeftBar = ({
|
|||
if (bool && userSelected) {
|
||||
setUserSelected(userSelected);
|
||||
setItem(LocalStorageKeys.db, null);
|
||||
} else setUserSelected(database);
|
||||
} else if (database) {
|
||||
setUserSelected(database);
|
||||
}
|
||||
}, [database]);
|
||||
|
||||
const onEmptyResults = (searchText?: string) => {
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ const StyledConfigEditor = styled(ConfigEditor)`
|
|||
export type TemplateParamsEditorProps = {
|
||||
queryEditorId: string;
|
||||
language: 'yaml' | 'json';
|
||||
onChange: () => void;
|
||||
onChange: (params: any) => void;
|
||||
};
|
||||
|
||||
const TemplateParamsEditor = ({
|
||||
|
|
|
|||
|
|
@ -17,8 +17,9 @@
|
|||
* under the License.
|
||||
*/
|
||||
import { t } from '@superset-ui/core';
|
||||
import type { Type } from 'src/components/Label';
|
||||
|
||||
export const STATE_TYPE_MAP = {
|
||||
export const STATE_TYPE_MAP: Record<string, Type> = {
|
||||
offline: 'danger',
|
||||
failed: 'danger',
|
||||
pending: 'info',
|
||||
|
|
|
|||
|
|
@ -46,6 +46,8 @@ export interface QueryEditor {
|
|||
description?: string;
|
||||
loaded?: boolean;
|
||||
inLocalStorage?: boolean;
|
||||
northPercent?: number;
|
||||
southPercent?: number;
|
||||
}
|
||||
|
||||
export type toastState = {
|
||||
|
|
|
|||
|
|
@ -74,13 +74,13 @@ type DatabaseValue = {
|
|||
value: number;
|
||||
id: number;
|
||||
database_name: string;
|
||||
backend: string;
|
||||
backend?: string;
|
||||
};
|
||||
|
||||
export type DatabaseObject = {
|
||||
id: number;
|
||||
database_name: string;
|
||||
backend: string;
|
||||
backend?: string;
|
||||
};
|
||||
|
||||
export interface DatabaseSelectorProps {
|
||||
|
|
@ -102,11 +102,11 @@ const SelectLabel = ({
|
|||
backend,
|
||||
databaseName,
|
||||
}: {
|
||||
backend: string;
|
||||
backend?: string;
|
||||
databaseName: string;
|
||||
}) => (
|
||||
<LabelStyle>
|
||||
<Label className="backend">{backend}</Label>
|
||||
<Label className="backend">{backend || ''}</Label>
|
||||
<span className="name" title={databaseName}>
|
||||
{databaseName}
|
||||
</span>
|
||||
|
|
|
|||
|
|
@ -302,6 +302,7 @@ fetchMock.post(VALIDATE_PARAMS_ENDPOINT, {
|
|||
});
|
||||
|
||||
const databaseFixture: DatabaseObject = {
|
||||
id: 123,
|
||||
backend: 'postgres',
|
||||
configuration_method: CONFIGURATION_METHOD.DYNAMIC_FORM,
|
||||
database_name: 'Postgres',
|
||||
|
|
@ -2012,6 +2013,7 @@ describe('dbReducer', () => {
|
|||
const currentState = dbReducer({}, action);
|
||||
|
||||
expect(currentState).toEqual({
|
||||
id: db.id,
|
||||
database_name: db.database_name,
|
||||
engine: backend,
|
||||
configuration_method: db.configuration_method,
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ export type DatabaseObject = {
|
|||
driver: string;
|
||||
engine?: string;
|
||||
extra?: string;
|
||||
id?: number;
|
||||
id: number;
|
||||
uuid?: null | string;
|
||||
name: string; // synonym to database_name
|
||||
paramProperties?: Record<string, any>;
|
||||
|
|
@ -78,11 +78,14 @@ export type DatabaseObject = {
|
|||
allow_run_async?: boolean;
|
||||
|
||||
// SQL Lab
|
||||
allows_cost_estimate?: boolean;
|
||||
allow_ctas?: boolean;
|
||||
allow_cvas?: boolean;
|
||||
allow_dml?: boolean;
|
||||
allows_virtual_table_explore?: boolean;
|
||||
expose_in_sqllab?: boolean;
|
||||
force_ctas_schema?: string;
|
||||
extra_json?: ExtraJson;
|
||||
|
||||
// Security
|
||||
allow_file_upload?: boolean;
|
||||
|
|
|
|||
Loading…
Reference in New Issue