mirror of
https://github.com/apache/superset.git
synced 2024-09-06 13:57:40 -04:00
chore: Remove actions prop and refactor code in SQL Lab (#22231)
This commit is contained in:
parent
b2d909f529
commit
f3bf3ec2ed
@ -296,6 +296,7 @@ export type Query = {
|
|||||||
errorMessage: string | null;
|
errorMessage: string | null;
|
||||||
extra: {
|
extra: {
|
||||||
progress: string | null;
|
progress: string | null;
|
||||||
|
errors?: SupersetError[];
|
||||||
};
|
};
|
||||||
id: string;
|
id: string;
|
||||||
isDataPreview: boolean;
|
isDataPreview: boolean;
|
||||||
|
@ -20,16 +20,8 @@ import React from 'react';
|
|||||||
import { render, screen } from 'spec/helpers/testing-library';
|
import { render, screen } from 'spec/helpers/testing-library';
|
||||||
import QueryHistory from 'src/SqlLab/components/QueryHistory';
|
import QueryHistory from 'src/SqlLab/components/QueryHistory';
|
||||||
|
|
||||||
const NOOP = () => {};
|
|
||||||
const mockedProps = {
|
const mockedProps = {
|
||||||
queries: [],
|
queries: [],
|
||||||
actions: {
|
|
||||||
queryEditorSetAndSaveSql: NOOP,
|
|
||||||
cloneQueryToNewTab: NOOP,
|
|
||||||
fetchQueryResults: NOOP,
|
|
||||||
clearQueryResults: NOOP,
|
|
||||||
removeQuery: NOOP,
|
|
||||||
},
|
|
||||||
displayLimit: 1000,
|
displayLimit: 1000,
|
||||||
latestQueryId: 'yhMUZCGb',
|
latestQueryId: 'yhMUZCGb',
|
||||||
};
|
};
|
||||||
|
@ -23,13 +23,6 @@ import QueryTable from 'src/SqlLab/components/QueryTable';
|
|||||||
|
|
||||||
interface QueryHistoryProps {
|
interface QueryHistoryProps {
|
||||||
queries: QueryResponse[];
|
queries: QueryResponse[];
|
||||||
actions: {
|
|
||||||
queryEditorSetAndSaveSql: Function;
|
|
||||||
cloneQueryToNewTab: Function;
|
|
||||||
fetchQueryResults: Function;
|
|
||||||
clearQueryResults: Function;
|
|
||||||
removeQuery: Function;
|
|
||||||
};
|
|
||||||
displayLimit: number;
|
displayLimit: number;
|
||||||
latestQueryId: string | undefined;
|
latestQueryId: string | undefined;
|
||||||
}
|
}
|
||||||
@ -47,7 +40,6 @@ const StyledEmptyStateWrapper = styled.div`
|
|||||||
|
|
||||||
const QueryHistory = ({
|
const QueryHistory = ({
|
||||||
queries,
|
queries,
|
||||||
actions,
|
|
||||||
displayLimit,
|
displayLimit,
|
||||||
latestQueryId,
|
latestQueryId,
|
||||||
}: QueryHistoryProps) =>
|
}: QueryHistoryProps) =>
|
||||||
@ -64,7 +56,6 @@ const QueryHistory = ({
|
|||||||
'actions',
|
'actions',
|
||||||
]}
|
]}
|
||||||
queries={queries}
|
queries={queries}
|
||||||
actions={actions}
|
|
||||||
displayLimit={displayLimit}
|
displayLimit={displayLimit}
|
||||||
latestQueryId={latestQueryId}
|
latestQueryId={latestQueryId}
|
||||||
/>
|
/>
|
||||||
|
@ -43,7 +43,6 @@ fetchMock.get(DATABASE_ENDPOINT, []);
|
|||||||
|
|
||||||
describe('QuerySearch', () => {
|
describe('QuerySearch', () => {
|
||||||
const mockedProps = {
|
const mockedProps = {
|
||||||
actions: { addDangerToast: jest.fn() },
|
|
||||||
displayLimit: 50,
|
displayLimit: 50,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -17,6 +17,9 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { useDispatch } from 'react-redux';
|
||||||
|
|
||||||
|
import { setDatabases, addDangerToast } from 'src/SqlLab/actions/sqlLab';
|
||||||
import Button from 'src/components/Button';
|
import Button from 'src/components/Button';
|
||||||
import Select from 'src/components/DeprecatedSelect';
|
import Select from 'src/components/DeprecatedSelect';
|
||||||
import { styled, t, SupersetClient, QueryResponse } from '@superset-ui/core';
|
import { styled, t, SupersetClient, QueryResponse } from '@superset-ui/core';
|
||||||
@ -33,15 +36,6 @@ import { STATUS_OPTIONS, TIME_OPTIONS } from 'src/SqlLab/constants';
|
|||||||
import QueryTable from '../QueryTable';
|
import QueryTable from '../QueryTable';
|
||||||
|
|
||||||
interface QuerySearchProps {
|
interface QuerySearchProps {
|
||||||
actions: {
|
|
||||||
addDangerToast: (msg: string) => void;
|
|
||||||
setDatabases: (data: Record<string, any>) => Record<string, any>;
|
|
||||||
queryEditorSetAndSaveSql: Function;
|
|
||||||
cloneQueryToNewTab: Function;
|
|
||||||
fetchQueryResults: Function;
|
|
||||||
clearQueryResults: Function;
|
|
||||||
removeQuery: Function;
|
|
||||||
};
|
|
||||||
displayLimit: number;
|
displayLimit: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -77,7 +71,10 @@ const TableStyles = styled.div`
|
|||||||
const StyledTableStylesContainer = styled.div`
|
const StyledTableStylesContainer = styled.div`
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
`;
|
`;
|
||||||
function QuerySearch({ actions, displayLimit }: QuerySearchProps) {
|
|
||||||
|
const QuerySearch = ({ displayLimit }: QuerySearchProps) => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
const [databaseId, setDatabaseId] = useState<string>('');
|
const [databaseId, setDatabaseId] = useState<string>('');
|
||||||
const [userId, setUserId] = useState<string>('');
|
const [userId, setUserId] = useState<string>('');
|
||||||
const [searchText, setSearchText] = useState<string>('');
|
const [searchText, setSearchText] = useState<string>('');
|
||||||
@ -133,7 +130,7 @@ function QuerySearch({ actions, displayLimit }: QuerySearchProps) {
|
|||||||
const queries = Object.values(response.json);
|
const queries = Object.values(response.json);
|
||||||
setQueriesArray(queries);
|
setQueriesArray(queries);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
actions.addDangerToast(t('An error occurred when refreshing queries'));
|
dispatch(addDangerToast(t('An error occurred when refreshing queries')));
|
||||||
} finally {
|
} finally {
|
||||||
setQueriesLoading(false);
|
setQueriesLoading(false);
|
||||||
}
|
}
|
||||||
@ -178,10 +175,10 @@ function QuerySearch({ actions, displayLimit }: QuerySearchProps) {
|
|||||||
value: id,
|
value: id,
|
||||||
label: database_name,
|
label: database_name,
|
||||||
}));
|
}));
|
||||||
actions.setDatabases(result);
|
dispatch(setDatabases(result));
|
||||||
if (result.length === 0) {
|
if (result.length === 0) {
|
||||||
actions.addDangerToast(
|
dispatch(
|
||||||
t("It seems you don't have access to any database"),
|
addDangerToast(t("It seems you don't have access to any database")),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return options;
|
return options;
|
||||||
@ -280,7 +277,6 @@ function QuerySearch({ actions, displayLimit }: QuerySearchProps) {
|
|||||||
onUserClicked={onUserClicked}
|
onUserClicked={onUserClicked}
|
||||||
onDbClicked={onDbClicked}
|
onDbClicked={onDbClicked}
|
||||||
queries={queriesArray}
|
queries={queriesArray}
|
||||||
actions={actions}
|
|
||||||
displayLimit={displayLimit}
|
displayLimit={displayLimit}
|
||||||
/>
|
/>
|
||||||
</TableStyles>
|
</TableStyles>
|
||||||
@ -288,5 +284,6 @@ function QuerySearch({ actions, displayLimit }: QuerySearchProps) {
|
|||||||
</StyledTableStylesContainer>
|
</StyledTableStylesContainer>
|
||||||
</TableWrapper>
|
</TableWrapper>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
export default QuerySearch;
|
export default QuerySearch;
|
||||||
|
@ -25,13 +25,11 @@ import TableView from 'src/components/TableView';
|
|||||||
import TableCollection from 'src/components/TableCollection';
|
import TableCollection from 'src/components/TableCollection';
|
||||||
import { Provider } from 'react-redux';
|
import { Provider } from 'react-redux';
|
||||||
import { queries, user } from 'src/SqlLab/fixtures';
|
import { queries, user } from 'src/SqlLab/fixtures';
|
||||||
import * as actions from 'src/SqlLab/actions/sqlLab';
|
|
||||||
|
|
||||||
describe('QueryTable', () => {
|
describe('QueryTable', () => {
|
||||||
const mockedProps = {
|
const mockedProps = {
|
||||||
queries,
|
queries,
|
||||||
displayLimit: 100,
|
displayLimit: 100,
|
||||||
actions,
|
|
||||||
latestQueryId: 'ryhMUZCGb',
|
latestQueryId: 'ryhMUZCGb',
|
||||||
};
|
};
|
||||||
it('is valid', () => {
|
it('is valid', () => {
|
||||||
|
@ -22,7 +22,15 @@ import Card from 'src/components/Card';
|
|||||||
import ProgressBar from 'src/components/ProgressBar';
|
import ProgressBar from 'src/components/ProgressBar';
|
||||||
import Label from 'src/components/Label';
|
import Label from 'src/components/Label';
|
||||||
import { t, useTheme, QueryResponse } from '@superset-ui/core';
|
import { t, useTheme, QueryResponse } from '@superset-ui/core';
|
||||||
import { useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
|
|
||||||
|
import {
|
||||||
|
queryEditorSetAndSaveSql,
|
||||||
|
cloneQueryToNewTab,
|
||||||
|
fetchQueryResults,
|
||||||
|
clearQueryResults,
|
||||||
|
removeQuery,
|
||||||
|
} from 'src/SqlLab/actions/sqlLab';
|
||||||
import TableView from 'src/components/TableView';
|
import TableView from 'src/components/TableView';
|
||||||
import Button from 'src/components/Button';
|
import Button from 'src/components/Button';
|
||||||
import { fDuration } from 'src/utils/dates';
|
import { fDuration } from 'src/utils/dates';
|
||||||
@ -45,13 +53,6 @@ interface QueryTableQuery
|
|||||||
|
|
||||||
interface QueryTableProps {
|
interface QueryTableProps {
|
||||||
columns?: string[];
|
columns?: string[];
|
||||||
actions: {
|
|
||||||
queryEditorSetAndSaveSql: Function;
|
|
||||||
cloneQueryToNewTab: Function;
|
|
||||||
fetchQueryResults: Function;
|
|
||||||
clearQueryResults: Function;
|
|
||||||
removeQuery: Function;
|
|
||||||
};
|
|
||||||
queries?: QueryResponse[];
|
queries?: QueryResponse[];
|
||||||
onUserClicked?: Function;
|
onUserClicked?: Function;
|
||||||
onDbClicked?: Function;
|
onDbClicked?: Function;
|
||||||
@ -66,7 +67,6 @@ const openQuery = (id: number) => {
|
|||||||
|
|
||||||
const QueryTable = ({
|
const QueryTable = ({
|
||||||
columns = ['started', 'duration', 'rows'],
|
columns = ['started', 'duration', 'rows'],
|
||||||
actions,
|
|
||||||
queries = [],
|
queries = [],
|
||||||
onUserClicked = () => undefined,
|
onUserClicked = () => undefined,
|
||||||
onDbClicked = () => undefined,
|
onDbClicked = () => undefined,
|
||||||
@ -74,6 +74,7 @@ const QueryTable = ({
|
|||||||
latestQueryId,
|
latestQueryId,
|
||||||
}: QueryTableProps) => {
|
}: QueryTableProps) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
const setHeaders = (column: string) => {
|
const setHeaders = (column: string) => {
|
||||||
if (column === 'sql') {
|
if (column === 'sql') {
|
||||||
@ -93,25 +94,17 @@ const QueryTable = ({
|
|||||||
|
|
||||||
const user = useSelector<SqlLabRootState, User>(state => state.sqlLab.user);
|
const user = useSelector<SqlLabRootState, User>(state => state.sqlLab.user);
|
||||||
|
|
||||||
const {
|
|
||||||
queryEditorSetAndSaveSql,
|
|
||||||
cloneQueryToNewTab,
|
|
||||||
fetchQueryResults,
|
|
||||||
clearQueryResults,
|
|
||||||
removeQuery,
|
|
||||||
} = actions;
|
|
||||||
|
|
||||||
const data = useMemo(() => {
|
const data = useMemo(() => {
|
||||||
const restoreSql = (query: QueryResponse) => {
|
const restoreSql = (query: QueryResponse) => {
|
||||||
queryEditorSetAndSaveSql({ id: query.sqlEditorId }, query.sql);
|
dispatch(queryEditorSetAndSaveSql({ id: query.sqlEditorId }, query.sql));
|
||||||
};
|
};
|
||||||
|
|
||||||
const openQueryInNewTab = (query: QueryResponse) => {
|
const openQueryInNewTab = (query: QueryResponse) => {
|
||||||
cloneQueryToNewTab(query, true);
|
dispatch(cloneQueryToNewTab(query, true));
|
||||||
};
|
};
|
||||||
|
|
||||||
const openAsyncResults = (query: QueryResponse, displayLimit: number) => {
|
const openAsyncResults = (query: QueryResponse, displayLimit: number) => {
|
||||||
fetchQueryResults(query, displayLimit);
|
dispatch(fetchQueryResults(query, displayLimit));
|
||||||
};
|
};
|
||||||
|
|
||||||
const statusAttributes = {
|
const statusAttributes = {
|
||||||
@ -239,7 +232,7 @@ const QueryTable = ({
|
|||||||
}
|
}
|
||||||
modalTitle={t('Data preview')}
|
modalTitle={t('Data preview')}
|
||||||
beforeOpen={() => openAsyncResults(query, displayLimit)}
|
beforeOpen={() => openAsyncResults(query, displayLimit)}
|
||||||
onExit={() => clearQueryResults(query)}
|
onExit={() => dispatch(clearQueryResults(query))}
|
||||||
modalBody={
|
modalBody={
|
||||||
<ResultSet
|
<ResultSet
|
||||||
showSql
|
showSql
|
||||||
@ -293,7 +286,7 @@ const QueryTable = ({
|
|||||||
{q.id !== latestQueryId && (
|
{q.id !== latestQueryId && (
|
||||||
<StyledTooltip
|
<StyledTooltip
|
||||||
tooltip={t('Remove query from log')}
|
tooltip={t('Remove query from log')}
|
||||||
onClick={() => removeQuery(query)}
|
onClick={() => dispatch(removeQuery(query))}
|
||||||
>
|
>
|
||||||
<Icons.Trash iconSize="xl" />
|
<Icons.Trash iconSize="xl" />
|
||||||
</StyledTooltip>
|
</StyledTooltip>
|
||||||
@ -303,19 +296,7 @@ const QueryTable = ({
|
|||||||
return q;
|
return q;
|
||||||
})
|
})
|
||||||
.reverse();
|
.reverse();
|
||||||
}, [
|
}, [queries, onUserClicked, onDbClicked, user, displayLimit]);
|
||||||
queries,
|
|
||||||
onUserClicked,
|
|
||||||
onDbClicked,
|
|
||||||
user,
|
|
||||||
displayLimit,
|
|
||||||
actions,
|
|
||||||
clearQueryResults,
|
|
||||||
cloneQueryToNewTab,
|
|
||||||
fetchQueryResults,
|
|
||||||
queryEditorSetAndSaveSql,
|
|
||||||
removeQuery,
|
|
||||||
]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="QueryTable">
|
<div className="QueryTable">
|
||||||
|
@ -23,15 +23,15 @@ import { DropdownButton } from 'src/components/DropdownButton';
|
|||||||
import Button from 'src/components/Button';
|
import Button from 'src/components/Button';
|
||||||
import { DropdownButtonProps } from 'antd/lib/dropdown';
|
import { DropdownButtonProps } from 'antd/lib/dropdown';
|
||||||
|
|
||||||
interface Props {
|
interface SaveDatasetActionButtonProps {
|
||||||
setShowSave: (arg0: boolean) => void;
|
setShowSave: (arg0: boolean) => void;
|
||||||
overlayMenu: JSX.Element | null;
|
overlayMenu: JSX.Element | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function SaveDatasetActionButton({
|
const SaveDatasetActionButton = ({
|
||||||
setShowSave,
|
setShowSave,
|
||||||
overlayMenu,
|
overlayMenu,
|
||||||
}: Props) {
|
}: SaveDatasetActionButtonProps) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
const StyledDropdownButton = styled(
|
const StyledDropdownButton = styled(
|
||||||
@ -80,4 +80,6 @@ export default function SaveDatasetActionButton({
|
|||||||
{t('Save')}
|
{t('Save')}
|
||||||
</StyledDropdownButton>
|
</StyledDropdownButton>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export default SaveDatasetActionButton;
|
||||||
|
@ -19,47 +19,30 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import configureStore from 'redux-mock-store';
|
import configureStore from 'redux-mock-store';
|
||||||
import thunk from 'redux-thunk';
|
import thunk from 'redux-thunk';
|
||||||
import { styledShallow as shallow } from 'spec/helpers/theming';
|
import { render, screen, waitFor } from 'spec/helpers/testing-library';
|
||||||
import { render, screen, act } from 'spec/helpers/testing-library';
|
import SouthPane from 'src/SqlLab/components/SouthPane';
|
||||||
import SouthPaneContainer from 'src/SqlLab/components/SouthPane/state';
|
|
||||||
import ResultSet from 'src/SqlLab/components/ResultSet';
|
|
||||||
import '@testing-library/jest-dom/extend-expect';
|
import '@testing-library/jest-dom/extend-expect';
|
||||||
import { STATUS_OPTIONS } from 'src/SqlLab/constants';
|
import { STATUS_OPTIONS } from 'src/SqlLab/constants';
|
||||||
import { initialState, table, defaultQueryEditor } from 'src/SqlLab/fixtures';
|
import { initialState, table, defaultQueryEditor } from 'src/SqlLab/fixtures';
|
||||||
import { UserWithPermissionsAndRoles } from 'src/types/bootstrapTypes';
|
|
||||||
|
|
||||||
const NOOP = () => {};
|
|
||||||
|
|
||||||
const mockedProps = {
|
const mockedProps = {
|
||||||
queryEditorId: defaultQueryEditor.id,
|
queryEditorId: defaultQueryEditor.id,
|
||||||
latestQueryId: 'LCly_kkIN',
|
latestQueryId: 'LCly_kkIN',
|
||||||
actions: {},
|
|
||||||
activeSouthPaneTab: '',
|
|
||||||
height: 1,
|
height: 1,
|
||||||
displayLimit: 1,
|
displayLimit: 1,
|
||||||
databases: {},
|
|
||||||
defaultQueryLimit: 100,
|
defaultQueryLimit: 100,
|
||||||
};
|
};
|
||||||
|
|
||||||
const mockedEmptyProps = {
|
const mockedEmptyProps = {
|
||||||
queryEditorId: 'random_id',
|
queryEditorId: 'random_id',
|
||||||
latestQueryId: '',
|
latestQueryId: '',
|
||||||
actions: {
|
|
||||||
queryEditorSetAndSaveSql: NOOP,
|
|
||||||
cloneQueryToNewTab: NOOP,
|
|
||||||
fetchQueryResults: NOOP,
|
|
||||||
clearQueryResults: NOOP,
|
|
||||||
removeQuery: NOOP,
|
|
||||||
setActiveSouthPaneTab: NOOP,
|
|
||||||
},
|
|
||||||
activeSouthPaneTab: '',
|
|
||||||
height: 100,
|
height: 100,
|
||||||
databases: '',
|
|
||||||
displayLimit: 100,
|
displayLimit: 100,
|
||||||
user: UserWithPermissionsAndRoles,
|
|
||||||
defaultQueryLimit: 100,
|
defaultQueryLimit: 100,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const latestQueryProgressMsg = 'LATEST QUERY MESSAGE - LCly_kkIN';
|
||||||
|
|
||||||
const middlewares = [thunk];
|
const middlewares = [thunk];
|
||||||
const mockStore = configureStore(middlewares);
|
const mockStore = configureStore(middlewares);
|
||||||
const store = mockStore({
|
const store = mockStore({
|
||||||
@ -84,6 +67,7 @@ const store = mockStore({
|
|||||||
id: 'LCly_kkIN',
|
id: 'LCly_kkIN',
|
||||||
startDttm: Date.now(),
|
startDttm: Date.now(),
|
||||||
sqlEditorId: defaultQueryEditor.id,
|
sqlEditorId: defaultQueryEditor.id,
|
||||||
|
extra: { progress: latestQueryProgressMsg },
|
||||||
},
|
},
|
||||||
lXJa7F9_r: {
|
lXJa7F9_r: {
|
||||||
cached: false,
|
cached: false,
|
||||||
@ -115,48 +99,40 @@ const store = mockStore({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const setup = (overrides = {}) => (
|
const setup = (props, store) =>
|
||||||
<SouthPaneContainer store={store} {...mockedProps} {...overrides} />
|
render(<SouthPane {...props} />, {
|
||||||
);
|
useRedux: true,
|
||||||
|
...(store && { store }),
|
||||||
describe('SouthPane - Enzyme', () => {
|
|
||||||
const getWrapper = () => shallow(setup()).dive();
|
|
||||||
|
|
||||||
let wrapper;
|
|
||||||
|
|
||||||
it('should render offline when the state is offline', () => {
|
|
||||||
wrapper = getWrapper().dive();
|
|
||||||
wrapper.setProps({ offline: true });
|
|
||||||
expect(wrapper.childAt(0).text()).toBe(STATUS_OPTIONS.offline);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should pass latest query down to ResultSet component', () => {
|
describe('SouthPane', () => {
|
||||||
wrapper = getWrapper().dive();
|
const renderAndWait = (props, store) =>
|
||||||
expect(wrapper.find(ResultSet)).toExist();
|
waitFor(async () => setup(props, store));
|
||||||
// for editorQueries
|
|
||||||
expect(wrapper.find(ResultSet).first().props().query.id).toEqual(
|
|
||||||
mockedProps.latestQueryId,
|
|
||||||
);
|
|
||||||
// for dataPreviewQueries
|
|
||||||
expect(wrapper.find(ResultSet).last().props().query.id).toEqual(
|
|
||||||
'2g2_iRFMl',
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('SouthPane - RTL', () => {
|
|
||||||
const renderAndWait = overrides => {
|
|
||||||
const mounted = act(async () => {
|
|
||||||
render(setup(overrides));
|
|
||||||
});
|
|
||||||
|
|
||||||
return mounted;
|
|
||||||
};
|
|
||||||
it('Renders an empty state for results', async () => {
|
it('Renders an empty state for results', async () => {
|
||||||
await renderAndWait(mockedEmptyProps);
|
await renderAndWait(mockedEmptyProps, store);
|
||||||
|
|
||||||
const emptyStateText = screen.getByText(/run a query to display results/i);
|
const emptyStateText = screen.getByText(/run a query to display results/i);
|
||||||
|
|
||||||
expect(emptyStateText).toBeVisible();
|
expect(emptyStateText).toBeVisible();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should render offline when the state is offline', async () => {
|
||||||
|
await renderAndWait(
|
||||||
|
mockedEmptyProps,
|
||||||
|
mockStore({
|
||||||
|
...initialState,
|
||||||
|
sqlLab: {
|
||||||
|
...initialState.sqlLab,
|
||||||
|
offline: true,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByText(STATUS_OPTIONS.offline)).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should pass latest query down to ResultSet component', async () => {
|
||||||
|
await renderAndWait(mockedProps, store);
|
||||||
|
|
||||||
|
expect(screen.getByText(latestQueryProgressMsg)).toBeVisible();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -17,16 +17,18 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import React, { createRef } from 'react';
|
import React, { createRef } from 'react';
|
||||||
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
import shortid from 'shortid';
|
import shortid from 'shortid';
|
||||||
import Alert from 'src/components/Alert';
|
import Alert from 'src/components/Alert';
|
||||||
import Tabs from 'src/components/Tabs';
|
import Tabs from 'src/components/Tabs';
|
||||||
import { EmptyStateMedium } from 'src/components/EmptyState';
|
import { EmptyStateMedium } from 'src/components/EmptyState';
|
||||||
import { t, styled } from '@superset-ui/core';
|
import { t, styled } from '@superset-ui/core';
|
||||||
|
|
||||||
|
import { setActiveSouthPaneTab } from 'src/SqlLab/actions/sqlLab';
|
||||||
import { isFeatureEnabled, FeatureFlag } from 'src/featureFlags';
|
import { isFeatureEnabled, FeatureFlag } from 'src/featureFlags';
|
||||||
|
|
||||||
import Label from 'src/components/Label';
|
import Label from 'src/components/Label';
|
||||||
import { UserWithPermissionsAndRoles } from 'src/types/bootstrapTypes';
|
import { SqlLabRootState } from 'src/SqlLab/types';
|
||||||
import QueryHistory from '../QueryHistory';
|
import QueryHistory from '../QueryHistory';
|
||||||
import ResultSet from '../ResultSet';
|
import ResultSet from '../ResultSet';
|
||||||
import {
|
import {
|
||||||
@ -39,27 +41,13 @@ const TAB_HEIGHT = 140;
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
editorQueries are queries executed by users passed from SqlEditor component
|
editorQueries are queries executed by users passed from SqlEditor component
|
||||||
dataPrebiewQueries are all queries executed for preview of table data (from SqlEditorLeft)
|
dataPreviewQueries are all queries executed for preview of table data (from SqlEditorLeft)
|
||||||
*/
|
*/
|
||||||
export interface SouthPanePropTypes {
|
export interface SouthPaneProps {
|
||||||
queryEditorId: string;
|
queryEditorId: string;
|
||||||
editorQueries: any[];
|
|
||||||
latestQueryId?: string;
|
latestQueryId?: string;
|
||||||
dataPreviewQueries: any[];
|
|
||||||
actions: {
|
|
||||||
queryEditorSetAndSaveSql: Function;
|
|
||||||
cloneQueryToNewTab: Function;
|
|
||||||
fetchQueryResults: Function;
|
|
||||||
clearQueryResults: Function;
|
|
||||||
removeQuery: Function;
|
|
||||||
setActiveSouthPaneTab: Function;
|
|
||||||
};
|
|
||||||
activeSouthPaneTab?: string;
|
|
||||||
height: number;
|
height: number;
|
||||||
databases: Record<string, any>;
|
|
||||||
offline?: boolean;
|
|
||||||
displayLimit: number;
|
displayLimit: number;
|
||||||
user: UserWithPermissionsAndRoles;
|
|
||||||
defaultQueryLimit: number;
|
defaultQueryLimit: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -111,23 +99,49 @@ const StyledEmptyStateWrapper = styled.div`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export default function SouthPane({
|
const SouthPane = ({
|
||||||
editorQueries,
|
queryEditorId,
|
||||||
latestQueryId,
|
latestQueryId,
|
||||||
dataPreviewQueries,
|
|
||||||
actions,
|
|
||||||
activeSouthPaneTab = 'Results',
|
|
||||||
height,
|
height,
|
||||||
databases,
|
|
||||||
offline = false,
|
|
||||||
displayLimit,
|
displayLimit,
|
||||||
user,
|
|
||||||
defaultQueryLimit,
|
defaultQueryLimit,
|
||||||
}: SouthPanePropTypes) {
|
}: SouthPaneProps) => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
|
const { editorQueries, dataPreviewQueries, databases, offline, user } =
|
||||||
|
useSelector(({ sqlLab }: SqlLabRootState) => {
|
||||||
|
const { databases, offline, user, queries, tables } = sqlLab;
|
||||||
|
const dataPreviewQueries = tables
|
||||||
|
.filter(
|
||||||
|
({ dataPreviewQueryId, queryEditorId: qeId }) =>
|
||||||
|
dataPreviewQueryId &&
|
||||||
|
queryEditorId === qeId &&
|
||||||
|
queries[dataPreviewQueryId],
|
||||||
|
)
|
||||||
|
.map(({ name, dataPreviewQueryId }) => ({
|
||||||
|
...queries[dataPreviewQueryId],
|
||||||
|
tableName: name,
|
||||||
|
}));
|
||||||
|
const editorQueries = Object.values(queries).filter(
|
||||||
|
({ sqlEditorId }) => sqlEditorId === queryEditorId,
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
editorQueries,
|
||||||
|
dataPreviewQueries,
|
||||||
|
databases,
|
||||||
|
offline: offline ?? false,
|
||||||
|
user,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const activeSouthPaneTab =
|
||||||
|
useSelector<SqlLabRootState, string>(
|
||||||
|
state => state.sqlLab.activeSouthPaneTab as string,
|
||||||
|
) ?? 'Results';
|
||||||
const innerTabContentHeight = height - TAB_HEIGHT;
|
const innerTabContentHeight = height - TAB_HEIGHT;
|
||||||
const southPaneRef = createRef<HTMLDivElement>();
|
const southPaneRef = createRef<HTMLDivElement>();
|
||||||
const switchTab = (id: string) => {
|
const switchTab = (id: string) => {
|
||||||
actions.setActiveSouthPaneTab(id);
|
dispatch(setActiveSouthPaneTab(id));
|
||||||
};
|
};
|
||||||
const renderOfflineStatus = () => (
|
const renderOfflineStatus = () => (
|
||||||
<Label className="m-r-3" type={STATE_TYPE_MAP[STATUS_OPTIONS.offline]}>
|
<Label className="m-r-3" type={STATE_TYPE_MAP[STATUS_OPTIONS.offline]}>
|
||||||
@ -209,7 +223,12 @@ export default function SouthPane({
|
|||||||
return offline ? (
|
return offline ? (
|
||||||
renderOfflineStatus()
|
renderOfflineStatus()
|
||||||
) : (
|
) : (
|
||||||
<StyledPane className="SouthPane" height={height} ref={southPaneRef}>
|
<StyledPane
|
||||||
|
data-test="south-pane"
|
||||||
|
className="SouthPane"
|
||||||
|
height={height}
|
||||||
|
ref={southPaneRef}
|
||||||
|
>
|
||||||
<Tabs
|
<Tabs
|
||||||
activeKey={activeSouthPaneTab}
|
activeKey={activeSouthPaneTab}
|
||||||
className="SouthPaneTabs"
|
className="SouthPaneTabs"
|
||||||
@ -224,7 +243,6 @@ export default function SouthPane({
|
|||||||
<Tabs.TabPane tab={t('Query history')} key="History">
|
<Tabs.TabPane tab={t('Query history')} key="History">
|
||||||
<QueryHistory
|
<QueryHistory
|
||||||
queries={editorQueries}
|
queries={editorQueries}
|
||||||
actions={actions}
|
|
||||||
displayLimit={displayLimit}
|
displayLimit={displayLimit}
|
||||||
latestQueryId={latestQueryId}
|
latestQueryId={latestQueryId}
|
||||||
/>
|
/>
|
||||||
@ -233,4 +251,6 @@ export default function SouthPane({
|
|||||||
</Tabs>
|
</Tabs>
|
||||||
</StyledPane>
|
</StyledPane>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export default SouthPane;
|
||||||
|
@ -1,61 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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 { connect } from 'react-redux';
|
|
||||||
import { bindActionCreators, Dispatch } from 'redux';
|
|
||||||
import * as Actions from 'src/SqlLab/actions/sqlLab';
|
|
||||||
import { SqlLabRootState } from 'src/SqlLab/types';
|
|
||||||
import SouthPane, { SouthPanePropTypes } from '.';
|
|
||||||
|
|
||||||
function mapStateToProps(
|
|
||||||
{ sqlLab }: SqlLabRootState,
|
|
||||||
{ queryEditorId }: SouthPanePropTypes,
|
|
||||||
) {
|
|
||||||
const { databases, activeSouthPaneTab, offline, user, queries, tables } =
|
|
||||||
sqlLab;
|
|
||||||
const dataPreviewQueries = tables
|
|
||||||
.filter(
|
|
||||||
({ dataPreviewQueryId, queryEditorId: qeId }) =>
|
|
||||||
dataPreviewQueryId &&
|
|
||||||
queryEditorId === qeId &&
|
|
||||||
queries[dataPreviewQueryId],
|
|
||||||
)
|
|
||||||
.map(({ name, dataPreviewQueryId }) => ({
|
|
||||||
...queries[dataPreviewQueryId],
|
|
||||||
tableName: name,
|
|
||||||
}));
|
|
||||||
const editorQueries = Object.values(queries).filter(
|
|
||||||
({ sqlEditorId }) => sqlEditorId === queryEditorId,
|
|
||||||
);
|
|
||||||
return {
|
|
||||||
editorQueries,
|
|
||||||
dataPreviewQueries,
|
|
||||||
activeSouthPaneTab,
|
|
||||||
databases,
|
|
||||||
offline,
|
|
||||||
user,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function mapDispatchToProps(dispatch: Dispatch) {
|
|
||||||
return {
|
|
||||||
actions: bindActionCreators<any, any>(Actions, dispatch),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export default connect<any>(mapStateToProps, mapDispatchToProps)(SouthPane);
|
|
@ -30,14 +30,9 @@ import {
|
|||||||
SQL_TOOLBAR_HEIGHT,
|
SQL_TOOLBAR_HEIGHT,
|
||||||
} from 'src/SqlLab/constants';
|
} from 'src/SqlLab/constants';
|
||||||
import AceEditorWrapper from 'src/SqlLab/components/AceEditorWrapper';
|
import AceEditorWrapper from 'src/SqlLab/components/AceEditorWrapper';
|
||||||
import ConnectedSouthPane from 'src/SqlLab/components/SouthPane/state';
|
import SouthPane from 'src/SqlLab/components/SouthPane';
|
||||||
import SqlEditor from 'src/SqlLab/components/SqlEditor';
|
import SqlEditor from 'src/SqlLab/components/SqlEditor';
|
||||||
import QueryProvider from 'src/views/QueryProvider';
|
import QueryProvider from 'src/views/QueryProvider';
|
||||||
import {
|
|
||||||
queryEditorSetFunctionNames,
|
|
||||||
queryEditorSetSelectedText,
|
|
||||||
queryEditorSetSchemaOptions,
|
|
||||||
} from 'src/SqlLab/actions/sqlLab';
|
|
||||||
import waitForComponentToPaint from 'spec/helpers/waitForComponentToPaint';
|
import waitForComponentToPaint from 'spec/helpers/waitForComponentToPaint';
|
||||||
import {
|
import {
|
||||||
initialState,
|
initialState,
|
||||||
@ -97,13 +92,6 @@ const setup = (props = {}, store) =>
|
|||||||
|
|
||||||
describe('SqlEditor', () => {
|
describe('SqlEditor', () => {
|
||||||
const mockedProps = {
|
const mockedProps = {
|
||||||
actions: {
|
|
||||||
queryEditorSetFunctionNames,
|
|
||||||
queryEditorSetSelectedText,
|
|
||||||
queryEditorSetSchemaOptions,
|
|
||||||
addDangerToast: jest.fn(),
|
|
||||||
removeDataPreview: jest.fn(),
|
|
||||||
},
|
|
||||||
queryEditor: initialState.sqlLab.queryEditors[0],
|
queryEditor: initialState.sqlLab.queryEditors[0],
|
||||||
latestQuery: queries[0],
|
latestQuery: queries[0],
|
||||||
tables: [table],
|
tables: [table],
|
||||||
@ -238,7 +226,7 @@ describe('SqlEditor', () => {
|
|||||||
await waitForComponentToPaint(wrapper);
|
await waitForComponentToPaint(wrapper);
|
||||||
const totalSize =
|
const totalSize =
|
||||||
parseFloat(wrapper.find(AceEditorWrapper).props().height) +
|
parseFloat(wrapper.find(AceEditorWrapper).props().height) +
|
||||||
wrapper.find(ConnectedSouthPane).props().height +
|
wrapper.find(SouthPane).props().height +
|
||||||
SQL_TOOLBAR_HEIGHT +
|
SQL_TOOLBAR_HEIGHT +
|
||||||
SQL_EDITOR_GUTTER_MARGIN * 2 +
|
SQL_EDITOR_GUTTER_MARGIN * 2 +
|
||||||
SQL_EDITOR_GUTTER_HEIGHT;
|
SQL_EDITOR_GUTTER_HEIGHT;
|
||||||
@ -252,7 +240,7 @@ describe('SqlEditor', () => {
|
|||||||
await waitForComponentToPaint(wrapper);
|
await waitForComponentToPaint(wrapper);
|
||||||
const totalSize =
|
const totalSize =
|
||||||
parseFloat(wrapper.find(AceEditorWrapper).props().height) +
|
parseFloat(wrapper.find(AceEditorWrapper).props().height) +
|
||||||
wrapper.find(ConnectedSouthPane).props().height +
|
wrapper.find(SouthPane).props().height +
|
||||||
SQL_TOOLBAR_HEIGHT +
|
SQL_TOOLBAR_HEIGHT +
|
||||||
SQL_EDITOR_GUTTER_MARGIN * 2 +
|
SQL_EDITOR_GUTTER_MARGIN * 2 +
|
||||||
SQL_EDITOR_GUTTER_HEIGHT;
|
SQL_EDITOR_GUTTER_HEIGHT;
|
||||||
|
@ -76,7 +76,7 @@ import { FeatureFlag, isFeatureEnabled } from 'src/featureFlags';
|
|||||||
import { EmptyStateBig } from 'src/components/EmptyState';
|
import { EmptyStateBig } from 'src/components/EmptyState';
|
||||||
import { isEmpty } from 'lodash';
|
import { isEmpty } from 'lodash';
|
||||||
import TemplateParamsEditor from '../TemplateParamsEditor';
|
import TemplateParamsEditor from '../TemplateParamsEditor';
|
||||||
import ConnectedSouthPane from '../SouthPane/state';
|
import SouthPane from '../SouthPane';
|
||||||
import SaveQuery from '../SaveQuery';
|
import SaveQuery from '../SaveQuery';
|
||||||
import ScheduleQueryButton from '../ScheduleQueryButton';
|
import ScheduleQueryButton from '../ScheduleQueryButton';
|
||||||
import EstimateQueryCostButton from '../EstimateQueryCostButton';
|
import EstimateQueryCostButton from '../EstimateQueryCostButton';
|
||||||
@ -135,7 +135,6 @@ const StyledSidebar = styled.div`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
const propTypes = {
|
const propTypes = {
|
||||||
actions: PropTypes.object.isRequired,
|
|
||||||
tables: PropTypes.array.isRequired,
|
tables: PropTypes.array.isRequired,
|
||||||
queryEditor: PropTypes.object.isRequired,
|
queryEditor: PropTypes.object.isRequired,
|
||||||
defaultQueryLimit: PropTypes.number.isRequired,
|
defaultQueryLimit: PropTypes.number.isRequired,
|
||||||
@ -146,7 +145,6 @@ const propTypes = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const SqlEditor = ({
|
const SqlEditor = ({
|
||||||
actions,
|
|
||||||
tables,
|
tables,
|
||||||
queryEditor,
|
queryEditor,
|
||||||
defaultQueryLimit,
|
defaultQueryLimit,
|
||||||
@ -617,10 +615,9 @@ const SqlEditor = ({
|
|||||||
/>
|
/>
|
||||||
{renderEditorBottomBar(hotkeys)}
|
{renderEditorBottomBar(hotkeys)}
|
||||||
</div>
|
</div>
|
||||||
<ConnectedSouthPane
|
<SouthPane
|
||||||
queryEditorId={queryEditor.id}
|
queryEditorId={queryEditor.id}
|
||||||
latestQueryId={latestQuery?.id}
|
latestQueryId={latestQuery?.id}
|
||||||
actions={actions}
|
|
||||||
height={southPaneHeight}
|
height={southPaneHeight}
|
||||||
displayLimit={displayLimit}
|
displayLimit={displayLimit}
|
||||||
defaultQueryLimit={defaultQueryLimit}
|
defaultQueryLimit={defaultQueryLimit}
|
||||||
|
@ -264,7 +264,6 @@ class TabbedSqlEditors extends React.PureComponent {
|
|||||||
<SqlEditor
|
<SqlEditor
|
||||||
tables={this.props.tables.filter(xt => xt.queryEditorId === qe.id)}
|
tables={this.props.tables.filter(xt => xt.queryEditorId === qe.id)}
|
||||||
queryEditor={qe}
|
queryEditor={qe}
|
||||||
actions={this.props.actions}
|
|
||||||
defaultQueryLimit={this.props.defaultQueryLimit}
|
defaultQueryLimit={this.props.defaultQueryLimit}
|
||||||
maxRow={this.props.maxRow}
|
maxRow={this.props.maxRow}
|
||||||
displayLimit={this.props.displayLimit}
|
displayLimit={this.props.displayLimit}
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
* specific language governing permissions and limitations
|
* specific language governing permissions and limitations
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import { JsonObject, Query, QueryResponse } from '@superset-ui/core';
|
import { JsonObject, QueryResponse } from '@superset-ui/core';
|
||||||
import { SupersetError } from 'src/components/ErrorMessage/types';
|
import { SupersetError } from 'src/components/ErrorMessage/types';
|
||||||
import { UserWithPermissionsAndRoles } from 'src/types/bootstrapTypes';
|
import { UserWithPermissionsAndRoles } from 'src/types/bootstrapTypes';
|
||||||
import { ToastType } from 'src/components/MessageToasts/types';
|
import { ToastType } from 'src/components/MessageToasts/types';
|
||||||
@ -69,7 +69,7 @@ export type SqlLabRootState = {
|
|||||||
databases: Record<string, any>;
|
databases: Record<string, any>;
|
||||||
dbConnect: boolean;
|
dbConnect: boolean;
|
||||||
offline: boolean;
|
offline: boolean;
|
||||||
queries: Record<string, Query>;
|
queries: Record<string, QueryResponse>;
|
||||||
queryEditors: QueryEditor[];
|
queryEditors: QueryEditor[];
|
||||||
tabHistory: string[]; // default is activeTab ? [activeTab.id.toString()] : []
|
tabHistory: string[]; // default is activeTab ? [activeTab.id.toString()] : []
|
||||||
tables: Record<string, any>[];
|
tables: Record<string, any>[];
|
||||||
|
Loading…
Reference in New Issue
Block a user