diff --git a/superset-frontend/src/SqlLab/actions/sqlLab.js b/superset-frontend/src/SqlLab/actions/sqlLab.js index 3666d50d4d533..568dbb9b1f997 100644 --- a/superset-frontend/src/SqlLab/actions/sqlLab.js +++ b/superset-frontend/src/SqlLab/actions/sqlLab.js @@ -631,6 +631,21 @@ export function setActiveQueryEditor(queryEditor) { }; } +export function switchQueryEditor(goBackward = false) { + return function (dispatch, getState) { + const { sqlLab } = getState(); + const { queryEditors, tabHistory } = sqlLab; + const qeid = tabHistory[tabHistory.length - 1]; + const currentIndex = queryEditors.findIndex(qe => qe.id === qeid); + const nextIndex = goBackward + ? currentIndex - 1 + queryEditors.length + : currentIndex + 1; + const newQueryEditor = queryEditors[nextIndex % queryEditors.length]; + + dispatch(setActiveQueryEditor(newQueryEditor)); + }; +} + export function loadQueryEditor(queryEditor) { return { type: LOAD_QUERY_EDITOR, queryEditor }; } diff --git a/superset-frontend/src/SqlLab/actions/sqlLab.test.js b/superset-frontend/src/SqlLab/actions/sqlLab.test.js index 3e8ec7399ea85..142c6768d1b20 100644 --- a/superset-frontend/src/SqlLab/actions/sqlLab.test.js +++ b/superset-frontend/src/SqlLab/actions/sqlLab.test.js @@ -526,6 +526,85 @@ describe('async actions', () => { expect(store.getActions()).toEqual(expectedActions); }); + describe('swithQueryEditor', () => { + it('switch to the next tab editor', () => { + const store = mockStore(initialState); + const expectedActions = [ + { + type: actions.SET_ACTIVE_QUERY_EDITOR, + queryEditor: initialState.sqlLab.queryEditors[1], + }, + ]; + store.dispatch(actions.switchQueryEditor()); + + expect(store.getActions()).toEqual(expectedActions); + }); + + it('switch to the first tab editor once it reaches the rightmost tab', () => { + const store = mockStore({ + ...initialState, + sqlLab: { + ...initialState.sqlLab, + tabHistory: [ + initialState.sqlLab.queryEditors[ + initialState.sqlLab.queryEditors.length - 1 + ].id, + ], + }, + }); + const expectedActions = [ + { + type: actions.SET_ACTIVE_QUERY_EDITOR, + queryEditor: initialState.sqlLab.queryEditors[0], + }, + ]; + store.dispatch(actions.switchQueryEditor()); + + expect(store.getActions()).toEqual(expectedActions); + }); + + it('switch to the previous tab editor', () => { + const store = mockStore({ + ...initialState, + sqlLab: { + ...initialState.sqlLab, + tabHistory: [initialState.sqlLab.queryEditors[1].id], + }, + }); + const expectedActions = [ + { + type: actions.SET_ACTIVE_QUERY_EDITOR, + queryEditor: initialState.sqlLab.queryEditors[0], + }, + ]; + store.dispatch(actions.switchQueryEditor(true)); + + expect(store.getActions()).toEqual(expectedActions); + }); + + it('switch to the last tab editor once it reaches the leftmost tab', () => { + const store = mockStore({ + ...initialState, + sqlLab: { + ...initialState.sqlLab, + tabHistory: [initialState.sqlLab.queryEditors[0].id], + }, + }); + const expectedActions = [ + { + type: actions.SET_ACTIVE_QUERY_EDITOR, + queryEditor: + initialState.sqlLab.queryEditors[ + initialState.sqlLab.queryEditors.length - 1 + ], + }, + ]; + store.dispatch(actions.switchQueryEditor(true)); + + expect(store.getActions()).toEqual(expectedActions); + }); + }); + describe('backend sync', () => { const updateTabStateEndpoint = 'glob:*/tabstateview/*'; fetchMock.put(updateTabStateEndpoint, {}); diff --git a/superset-frontend/src/SqlLab/components/KeyboardShortcutButton/index.tsx b/superset-frontend/src/SqlLab/components/KeyboardShortcutButton/index.tsx index d3c54933126d4..fbc71065bcfcf 100644 --- a/superset-frontend/src/SqlLab/components/KeyboardShortcutButton/index.tsx +++ b/superset-frontend/src/SqlLab/components/KeyboardShortcutButton/index.tsx @@ -38,6 +38,8 @@ export enum KeyboardShortcut { CtrlF = 'ctrl+f', CtrlH = 'ctrl+h', CtrlShiftF = 'ctrl+shift+f', + CtrlLeft = 'ctrl+[', + CtrlRight = 'ctrl+]', } export const KEY_MAP = { @@ -51,6 +53,8 @@ export const KEY_MAP = { [KeyboardShortcut.CtrlT]: userOS !== 'Windows' ? t('New tab') : undefined, [KeyboardShortcut.CtrlP]: t('Previous Line'), [KeyboardShortcut.CtrlShiftF]: t('Format SQL'), + [KeyboardShortcut.CtrlLeft]: t('Switch to the previous tab'), + [KeyboardShortcut.CtrlRight]: t('Switch to the next tab'), // default ace editor shortcuts [KeyboardShortcut.CmdF]: userOS === 'MacOS' ? t('Find') : undefined, [KeyboardShortcut.CtrlF]: userOS !== 'MacOS' ? t('Find') : undefined, diff --git a/superset-frontend/src/SqlLab/components/SqlEditor/index.tsx b/superset-frontend/src/SqlLab/components/SqlEditor/index.tsx index a0e8ff4f5f02e..e5b24b72ad473 100644 --- a/superset-frontend/src/SqlLab/components/SqlEditor/index.tsx +++ b/superset-frontend/src/SqlLab/components/SqlEditor/index.tsx @@ -80,6 +80,7 @@ import { updateSavedQuery, formatQuery, fetchQueryEditor, + switchQueryEditor, } from 'src/SqlLab/actions/sqlLab'; import { STATE_TYPE_MAP, @@ -445,6 +446,22 @@ const SqlEditor: FC = ({ formatCurrentQuery(true); }, }, + { + name: 'switchTabToLeft', + key: KeyboardShortcut.CtrlLeft, + descr: KEY_MAP[KeyboardShortcut.CtrlLeft], + func: () => { + dispatch(switchQueryEditor(true)); + }, + }, + { + name: 'switchTabToRight', + key: KeyboardShortcut.CtrlRight, + descr: KEY_MAP[KeyboardShortcut.CtrlRight], + func: () => { + dispatch(switchQueryEditor(false)); + }, + }, ]; }, [dispatch, queryEditor.sql, startQuery, stopQuery, formatCurrentQuery]);