mirror of
https://github.com/apache/superset.git
synced 2024-09-12 00:29:39 -04:00
feat: Better return messages in SQL Editor (#14381)
* Sqllab limit * Add migration script * Set default values * initial push * revisions * moving migration to separate PR * revisions * Fix apply_limit_to_sql * all but tests * added unit tests * result set * first draft * revisions * made user required prop, added it to all places ResultSet is imported * changed QueryTable test to allow for useSelector * Query Table working * working with heights * fixed scrolling * got rid of animated * fixed tests, revisions * revisions * revisions * heights * fun with heights * alert state * aaron helped me fix this * better alert messages * fixed result set test Co-authored-by: Beto Dealmeida <roberto@dealmeida.net>
This commit is contained in:
parent
5776dcb61a
commit
a7a011cce5
@ -26,9 +26,12 @@ import { supersetTheme, ThemeProvider } from '@superset-ui/core';
|
|||||||
import { fireEvent, render, screen, act } from '@testing-library/react';
|
import { fireEvent, render, screen, act } from '@testing-library/react';
|
||||||
import '@testing-library/jest-dom/extend-expect';
|
import '@testing-library/jest-dom/extend-expect';
|
||||||
import userEvent from '@testing-library/user-event';
|
import userEvent from '@testing-library/user-event';
|
||||||
|
import { user } from './fixtures';
|
||||||
|
|
||||||
const mockStore = configureStore([thunk]);
|
const mockStore = configureStore([thunk]);
|
||||||
const store = mockStore({});
|
const store = mockStore({
|
||||||
|
sqlLab: user,
|
||||||
|
});
|
||||||
|
|
||||||
const SEARCH_ENDPOINT = 'glob:*/superset/search_queries?*';
|
const SEARCH_ENDPOINT = 'glob:*/superset/search_queries?*';
|
||||||
const USER_ENDPOINT = 'glob:*/api/v1/query/related/user';
|
const USER_ENDPOINT = 'glob:*/api/v1/query/related/user';
|
||||||
|
@ -17,11 +17,14 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { shallow } from 'enzyme';
|
import thunk from 'redux-thunk';
|
||||||
|
import configureStore from 'redux-mock-store';
|
||||||
|
import { styledMount as mount } from 'spec/helpers/theming';
|
||||||
import QueryTable from 'src/SqlLab/components/QueryTable';
|
import QueryTable from 'src/SqlLab/components/QueryTable';
|
||||||
import TableView from 'src/components/TableView';
|
import TableView from 'src/components/TableView';
|
||||||
import { TableCollection } from 'src/components/dataViewCommon';
|
import { TableCollection } from 'src/components/dataViewCommon';
|
||||||
import { queries } from './fixtures';
|
import { Provider } from 'react-redux';
|
||||||
|
import { queries, user } from './fixtures';
|
||||||
|
|
||||||
describe('QueryTable', () => {
|
describe('QueryTable', () => {
|
||||||
const mockedProps = {
|
const mockedProps = {
|
||||||
@ -35,12 +38,18 @@ describe('QueryTable', () => {
|
|||||||
expect(React.isValidElement(<QueryTable {...mockedProps} />)).toBe(true);
|
expect(React.isValidElement(<QueryTable {...mockedProps} />)).toBe(true);
|
||||||
});
|
});
|
||||||
it('renders a proper table', () => {
|
it('renders a proper table', () => {
|
||||||
const wrapper = shallow(<QueryTable {...mockedProps} />);
|
const mockStore = configureStore([thunk]);
|
||||||
const tableWrapper = wrapper
|
const store = mockStore({
|
||||||
.find(TableView)
|
sqlLab: user,
|
||||||
.shallow()
|
});
|
||||||
.find(TableCollection)
|
|
||||||
.shallow();
|
const wrapper = mount(
|
||||||
|
<Provider store={store}>
|
||||||
|
<QueryTable {...mockedProps} />
|
||||||
|
</Provider>,
|
||||||
|
);
|
||||||
|
const tableWrapper = wrapper.find(TableView).find(TableCollection);
|
||||||
|
|
||||||
expect(wrapper.find(TableView)).toExist();
|
expect(wrapper.find(TableView)).toExist();
|
||||||
expect(tableWrapper.find('table')).toExist();
|
expect(tableWrapper.find('table')).toExist();
|
||||||
expect(tableWrapper.find('table').find('thead').find('tr')).toHaveLength(1);
|
expect(tableWrapper.find('table').find('thead').find('tr')).toHaveLength(1);
|
||||||
|
@ -37,6 +37,7 @@ import {
|
|||||||
runningQuery,
|
runningQuery,
|
||||||
stoppedQuery,
|
stoppedQuery,
|
||||||
initialState,
|
initialState,
|
||||||
|
user,
|
||||||
} from './fixtures';
|
} from './fixtures';
|
||||||
|
|
||||||
const mockStore = configureStore([thunk]);
|
const mockStore = configureStore([thunk]);
|
||||||
@ -54,8 +55,10 @@ describe('ResultSet', () => {
|
|||||||
},
|
},
|
||||||
cache: true,
|
cache: true,
|
||||||
query: queries[0],
|
query: queries[0],
|
||||||
height: 0,
|
height: 140,
|
||||||
database: { allows_virtual_table_explore: true },
|
database: { allows_virtual_table_explore: true },
|
||||||
|
user,
|
||||||
|
defaultQueryLimit: 1000,
|
||||||
};
|
};
|
||||||
const stoppedQueryProps = { ...mockedProps, query: stoppedQuery };
|
const stoppedQueryProps = { ...mockedProps, query: stoppedQuery };
|
||||||
const runningQueryProps = { ...mockedProps, query: runningQuery };
|
const runningQueryProps = { ...mockedProps, query: runningQuery };
|
||||||
|
@ -208,6 +208,7 @@ export const queries = [
|
|||||||
executedSql: null,
|
executedSql: null,
|
||||||
changed_on: '2016-10-19T20:56:06',
|
changed_on: '2016-10-19T20:56:06',
|
||||||
rows: 42,
|
rows: 42,
|
||||||
|
queryLimit: 100,
|
||||||
endDttm: 1476910566798,
|
endDttm: 1476910566798,
|
||||||
limit_reached: false,
|
limit_reached: false,
|
||||||
schema: 'test_schema',
|
schema: 'test_schema',
|
||||||
@ -461,6 +462,18 @@ export const runningQuery = {
|
|||||||
};
|
};
|
||||||
export const cachedQuery = { ...queries[0], cached: true };
|
export const cachedQuery = { ...queries[0], cached: true };
|
||||||
|
|
||||||
|
export const user = {
|
||||||
|
createdOn: '2021-04-27T18:12:38.952304',
|
||||||
|
email: 'admin',
|
||||||
|
firstName: 'admin',
|
||||||
|
isActive: true,
|
||||||
|
lastName: 'admin',
|
||||||
|
permissions: {},
|
||||||
|
roles: { Admin: Array(173) },
|
||||||
|
userId: 1,
|
||||||
|
username: 'admin',
|
||||||
|
};
|
||||||
|
|
||||||
export const initialState = {
|
export const initialState = {
|
||||||
sqlLab: {
|
sqlLab: {
|
||||||
offline: false,
|
offline: false,
|
||||||
@ -473,6 +486,7 @@ export const initialState = {
|
|||||||
workspaceQueries: [],
|
workspaceQueries: [],
|
||||||
queriesLastUpdate: 0,
|
queriesLastUpdate: 0,
|
||||||
activeSouthPaneTab: 'Results',
|
activeSouthPaneTab: 'Results',
|
||||||
|
user: { user },
|
||||||
},
|
},
|
||||||
messageToasts: [],
|
messageToasts: [],
|
||||||
common: {
|
common: {
|
||||||
|
@ -22,8 +22,8 @@ import moment from 'moment';
|
|||||||
import Card from 'src/components/Card';
|
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 } from '@superset-ui/core';
|
import { t, css } from '@superset-ui/core';
|
||||||
|
import { useSelector } from 'react-redux';
|
||||||
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/modules/dates';
|
import { fDuration } from 'src/modules/dates';
|
||||||
@ -53,6 +53,10 @@ const openQuery = id => {
|
|||||||
window.open(url);
|
window.open(url);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const StaticPosition = css`
|
||||||
|
position: static;
|
||||||
|
`;
|
||||||
|
|
||||||
const QueryTable = props => {
|
const QueryTable = props => {
|
||||||
const columns = useMemo(
|
const columns = useMemo(
|
||||||
() =>
|
() =>
|
||||||
@ -64,6 +68,8 @@ const QueryTable = props => {
|
|||||||
[props.columns],
|
[props.columns],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const user = useSelector(({ sqlLab: { user } }) => user);
|
||||||
|
|
||||||
const data = useMemo(() => {
|
const data = useMemo(() => {
|
||||||
const restoreSql = query => {
|
const restoreSql = query => {
|
||||||
props.actions.queryEditorSetSql({ id: query.sqlEditorId }, query.sql);
|
props.actions.queryEditorSetSql({ id: query.sqlEditorId }, query.sql);
|
||||||
@ -129,7 +135,7 @@ const QueryTable = props => {
|
|||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
q.sql = (
|
q.sql = (
|
||||||
<Card>
|
<Card css={[StaticPosition]}>
|
||||||
<HighlightedSql
|
<HighlightedSql
|
||||||
sql={q.sql}
|
sql={q.sql}
|
||||||
rawSql={q.executedSql}
|
rawSql={q.executedSql}
|
||||||
@ -153,6 +159,7 @@ const QueryTable = props => {
|
|||||||
modalBody={
|
modalBody={
|
||||||
<ResultSet
|
<ResultSet
|
||||||
showSql
|
showSql
|
||||||
|
user={user}
|
||||||
query={query}
|
query={query}
|
||||||
actions={props.actions}
|
actions={props.actions}
|
||||||
height={400}
|
height={400}
|
||||||
|
@ -30,6 +30,7 @@ import { debounce } from 'lodash';
|
|||||||
import ErrorMessageWithStackTrace from 'src/components/ErrorMessage/ErrorMessageWithStackTrace';
|
import ErrorMessageWithStackTrace from 'src/components/ErrorMessage/ErrorMessageWithStackTrace';
|
||||||
import { SaveDatasetModal } from 'src/SqlLab/components/SaveDatasetModal';
|
import { SaveDatasetModal } from 'src/SqlLab/components/SaveDatasetModal';
|
||||||
import { put as updateDatset } from 'src/api/dataset';
|
import { put as updateDatset } from 'src/api/dataset';
|
||||||
|
import { UserWithPermissionsAndRoles } from 'src/types/bootstrapTypes';
|
||||||
import Loading from '../../components/Loading';
|
import Loading from '../../components/Loading';
|
||||||
import ExploreCtasResultsButton from './ExploreCtasResultsButton';
|
import ExploreCtasResultsButton from './ExploreCtasResultsButton';
|
||||||
import ExploreResultsButton from './ExploreResultsButton';
|
import ExploreResultsButton from './ExploreResultsButton';
|
||||||
@ -42,8 +43,6 @@ import { exploreChart } from '../../explore/exploreUtils';
|
|||||||
import { CtasEnum } from '../actions/sqlLab';
|
import { CtasEnum } from '../actions/sqlLab';
|
||||||
import { Query } from '../types';
|
import { Query } from '../types';
|
||||||
|
|
||||||
const SEARCH_HEIGHT = 46;
|
|
||||||
|
|
||||||
enum DatasetRadioState {
|
enum DatasetRadioState {
|
||||||
SAVE_NEW = 1,
|
SAVE_NEW = 1,
|
||||||
OVERWRITE_DATASET = 2,
|
OVERWRITE_DATASET = 2,
|
||||||
@ -56,6 +55,13 @@ const EXPLORE_CHART_DEFAULT = {
|
|||||||
viz_type: 'table',
|
viz_type: 'table',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum LIMITING_FACTOR {
|
||||||
|
QUERY = 'QUERY',
|
||||||
|
QUERY_AND_DROPDOWN = 'QUERY_AND_DROPDOWN',
|
||||||
|
DROPDOWN = 'DROPDOWN',
|
||||||
|
NOT_LIMITED = 'NOT_LIMITED',
|
||||||
|
}
|
||||||
|
|
||||||
const LOADING_STYLES: CSSProperties = { position: 'relative', minHeight: 100 };
|
const LOADING_STYLES: CSSProperties = { position: 'relative', minHeight: 100 };
|
||||||
|
|
||||||
interface DatasetOptionAutocomplete {
|
interface DatasetOptionAutocomplete {
|
||||||
@ -75,6 +81,8 @@ interface ResultSetProps {
|
|||||||
search?: boolean;
|
search?: boolean;
|
||||||
showSql?: boolean;
|
showSql?: boolean;
|
||||||
visualize?: boolean;
|
visualize?: boolean;
|
||||||
|
user: UserWithPermissionsAndRoles;
|
||||||
|
defaultQueryLimit: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ResultSetState {
|
interface ResultSetState {
|
||||||
@ -88,6 +96,7 @@ interface ResultSetState {
|
|||||||
datasetToOverwrite: Record<string, any>;
|
datasetToOverwrite: Record<string, any>;
|
||||||
saveModalAutocompleteValue: string;
|
saveModalAutocompleteValue: string;
|
||||||
userDatasetOptions: DatasetOptionAutocomplete[];
|
userDatasetOptions: DatasetOptionAutocomplete[];
|
||||||
|
alertIsOpen: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Making text render line breaks/tabs as is as monospace,
|
// Making text render line breaks/tabs as is as monospace,
|
||||||
@ -103,6 +112,10 @@ const MonospaceDiv = styled.div`
|
|||||||
const ReturnedRows = styled.div`
|
const ReturnedRows = styled.div`
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
line-height: 24px;
|
line-height: 24px;
|
||||||
|
.limitMessage {
|
||||||
|
color: ${({ theme }) => theme.colors.secondary.light1};
|
||||||
|
margin-left: ${({ theme }) => theme.gridUnit * 2}px;
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
const ResultSetControls = styled.div`
|
const ResultSetControls = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -146,8 +159,8 @@ export default class ResultSet extends React.PureComponent<
|
|||||||
datasetToOverwrite: {},
|
datasetToOverwrite: {},
|
||||||
saveModalAutocompleteValue: '',
|
saveModalAutocompleteValue: '',
|
||||||
userDatasetOptions: [],
|
userDatasetOptions: [],
|
||||||
|
alertIsOpen: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.changeSearch = this.changeSearch.bind(this);
|
this.changeSearch = this.changeSearch.bind(this);
|
||||||
this.fetchResults = this.fetchResults.bind(this);
|
this.fetchResults = this.fetchResults.bind(this);
|
||||||
this.popSelectStar = this.popSelectStar.bind(this);
|
this.popSelectStar = this.popSelectStar.bind(this);
|
||||||
@ -207,6 +220,14 @@ export default class ResultSet extends React.PureComponent<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
calculateAlertRefHeight = (alertElement: HTMLElement | null) => {
|
||||||
|
if (alertElement) {
|
||||||
|
this.setState({ alertIsOpen: true });
|
||||||
|
} else {
|
||||||
|
this.setState({ alertIsOpen: false });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
getDefaultDatasetName = () =>
|
getDefaultDatasetName = () =>
|
||||||
`${this.props.query.tab} ${moment().format('MM/DD/YYYY HH:mm:ss')}`;
|
`${this.props.query.tab} ${moment().format('MM/DD/YYYY HH:mm:ss')}`;
|
||||||
|
|
||||||
@ -321,12 +342,8 @@ export default class ResultSet extends React.PureComponent<
|
|||||||
getUserDatasets = async (searchText = '') => {
|
getUserDatasets = async (searchText = '') => {
|
||||||
// Making sure that autocomplete input has a value before rendering the dropdown
|
// Making sure that autocomplete input has a value before rendering the dropdown
|
||||||
// Transforming the userDatasetsOwned data for SaveModalComponent)
|
// Transforming the userDatasetsOwned data for SaveModalComponent)
|
||||||
const appContainer = document.getElementById('app');
|
const { userId } = this.props.user;
|
||||||
const bootstrapData = JSON.parse(
|
if (userId) {
|
||||||
appContainer?.getAttribute('data-bootstrap') || '{}',
|
|
||||||
);
|
|
||||||
|
|
||||||
if (bootstrapData.user && bootstrapData.user.userId) {
|
|
||||||
const queryParams = rison.encode({
|
const queryParams = rison.encode({
|
||||||
filters: [
|
filters: [
|
||||||
{
|
{
|
||||||
@ -337,7 +354,7 @@ export default class ResultSet extends React.PureComponent<
|
|||||||
{
|
{
|
||||||
col: 'owners',
|
col: 'owners',
|
||||||
opr: 'rel_m_m',
|
opr: 'rel_m_m',
|
||||||
value: bootstrapData.user.userId,
|
value: userId,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
order_column: 'changed_on_delta_humanized',
|
order_column: 'changed_on_delta_humanized',
|
||||||
@ -501,25 +518,105 @@ export default class ResultSet extends React.PureComponent<
|
|||||||
return <div />;
|
return <div />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onAlertClose = () => {
|
||||||
|
this.setState({ alertIsOpen: false });
|
||||||
|
};
|
||||||
|
|
||||||
renderRowsReturned() {
|
renderRowsReturned() {
|
||||||
const { results, rows, queryLimit } = this.props.query;
|
const { results, rows, queryLimit, limitingFactor } = this.props.query;
|
||||||
|
let limitMessage;
|
||||||
const limitReached = results?.displayLimitReached;
|
const limitReached = results?.displayLimitReached;
|
||||||
|
const isAdmin = !!this.props.user?.roles.Admin;
|
||||||
|
const displayMaxRowsReachedMessage = {
|
||||||
|
withAdmin: t(
|
||||||
|
`The number of results displayed is limited to %(rows)d by the configuration DISPLAY_MAX_ROWS. `,
|
||||||
|
{ rows },
|
||||||
|
).concat(
|
||||||
|
t(
|
||||||
|
`Please add additional limits/filters or download to csv to see more rows up to the`,
|
||||||
|
),
|
||||||
|
t(`the %(queryLimit)d limit.`, { queryLimit }),
|
||||||
|
),
|
||||||
|
withoutAdmin: t(
|
||||||
|
`The number of results displayed is limited to %(rows)d. `,
|
||||||
|
{ rows },
|
||||||
|
).concat(
|
||||||
|
t(
|
||||||
|
`Please add additional limits/filters, download to csv, or contact an admin`,
|
||||||
|
),
|
||||||
|
t(`to see more rows up to the the %(queryLimit)d limit.`, {
|
||||||
|
queryLimit,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
const shouldUseDefaultDropdownAlert =
|
||||||
|
queryLimit === this.props.defaultQueryLimit &&
|
||||||
|
limitingFactor === LIMITING_FACTOR.DROPDOWN;
|
||||||
|
|
||||||
|
if (limitingFactor === LIMITING_FACTOR.QUERY && this.props.csv) {
|
||||||
|
limitMessage = (
|
||||||
|
<span className="limitMessage">
|
||||||
|
{t(
|
||||||
|
`The number of rows displayed is limited to %(rows)d by the query`,
|
||||||
|
{ rows },
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
} else if (
|
||||||
|
limitingFactor === LIMITING_FACTOR.DROPDOWN &&
|
||||||
|
!shouldUseDefaultDropdownAlert
|
||||||
|
) {
|
||||||
|
limitMessage = (
|
||||||
|
<span className="limitMessage">
|
||||||
|
{t(
|
||||||
|
`The number of rows displayed is limited to %(rows)d by the limit dropdown.`,
|
||||||
|
{ rows },
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
} else if (limitingFactor === LIMITING_FACTOR.QUERY_AND_DROPDOWN) {
|
||||||
|
limitMessage = (
|
||||||
|
<span className="limitMessage">
|
||||||
|
{t(
|
||||||
|
`The number of rows displayed is limited to %(rows)d by the query and limit dropdown.`,
|
||||||
|
{ rows },
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<ReturnedRows>
|
<ReturnedRows>
|
||||||
{!limitReached && (
|
{!limitReached && !shouldUseDefaultDropdownAlert && (
|
||||||
<Alert type="warning" message={t(`%s rows returned`, rows)} />
|
<span>
|
||||||
|
{t(`%(rows)d rows returned`, { rows })} {limitMessage}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
{!limitReached && shouldUseDefaultDropdownAlert && (
|
||||||
|
<div ref={this.calculateAlertRefHeight}>
|
||||||
|
<Alert
|
||||||
|
type="warning"
|
||||||
|
message={t(`%(rows)d rows returned`, { rows })}
|
||||||
|
onClose={this.onAlertClose}
|
||||||
|
description={t(
|
||||||
|
`The number of rows displayed is limited to %s by the dropdown.`,
|
||||||
|
rows,
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
{limitReached && (
|
{limitReached && (
|
||||||
<Alert
|
<div ref={this.calculateAlertRefHeight}>
|
||||||
type="warning"
|
<Alert
|
||||||
message={t(
|
type="warning"
|
||||||
`The number of results displayed is limited to %s. Please add
|
onClose={this.onAlertClose}
|
||||||
additional limits/filters or download to csv to see more rows up to
|
message={t(`%(rows)d rows returned`, { rows })}
|
||||||
the %s limit.`,
|
description={
|
||||||
rows,
|
isAdmin
|
||||||
queryLimit,
|
? displayMaxRowsReachedMessage.withAdmin
|
||||||
)}
|
: displayMaxRowsReachedMessage.withoutAdmin
|
||||||
/>
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</ReturnedRows>
|
</ReturnedRows>
|
||||||
);
|
);
|
||||||
@ -527,10 +624,6 @@ export default class ResultSet extends React.PureComponent<
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { query } = this.props;
|
const { query } = this.props;
|
||||||
const height = Math.max(
|
|
||||||
0,
|
|
||||||
this.props.search ? this.props.height - SEARCH_HEIGHT : this.props.height,
|
|
||||||
);
|
|
||||||
let sql;
|
let sql;
|
||||||
let exploreDBId = query.dbId;
|
let exploreDBId = query.dbId;
|
||||||
if (this.props.database && this.props.database.explore_database_id) {
|
if (this.props.database && this.props.database.explore_database_id) {
|
||||||
@ -601,6 +694,9 @@ export default class ResultSet extends React.PureComponent<
|
|||||||
}
|
}
|
||||||
if (query.state === 'success' && query.results) {
|
if (query.state === 'success' && query.results) {
|
||||||
const { results } = query;
|
const { results } = query;
|
||||||
|
const height = this.state.alertIsOpen
|
||||||
|
? this.props.height - 70
|
||||||
|
: this.props.height;
|
||||||
let data;
|
let data;
|
||||||
if (this.props.cache && query.cached) {
|
if (this.props.cache && query.cached) {
|
||||||
({ data } = this.state);
|
({ data } = this.state);
|
||||||
|
@ -25,6 +25,7 @@ import { t, styled } from '@superset-ui/core';
|
|||||||
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 QueryHistory from '../QueryHistory';
|
import QueryHistory from '../QueryHistory';
|
||||||
import ResultSet from '../ResultSet';
|
import ResultSet from '../ResultSet';
|
||||||
import {
|
import {
|
||||||
@ -33,7 +34,7 @@ import {
|
|||||||
LOCALSTORAGE_MAX_QUERY_AGE_MS,
|
LOCALSTORAGE_MAX_QUERY_AGE_MS,
|
||||||
} from '../../constants';
|
} from '../../constants';
|
||||||
|
|
||||||
const TAB_HEIGHT = 90;
|
const TAB_HEIGHT = 140;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
editorQueries are queries executed by users passed from SqlEditor component
|
editorQueries are queries executed by users passed from SqlEditor component
|
||||||
@ -49,6 +50,8 @@ interface SouthPanePropTypes {
|
|||||||
databases: Record<string, any>;
|
databases: Record<string, any>;
|
||||||
offline?: boolean;
|
offline?: boolean;
|
||||||
displayLimit: number;
|
displayLimit: number;
|
||||||
|
user: UserWithPermissionsAndRoles;
|
||||||
|
defaultQueryLimit: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const StyledPane = styled.div`
|
const StyledPane = styled.div`
|
||||||
@ -61,6 +64,16 @@ const StyledPane = styled.div`
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
.scrollable {
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.ant-tabs-tabpane {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
.scrollable {
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.tab-content {
|
.tab-content {
|
||||||
.alert {
|
.alert {
|
||||||
@ -83,13 +96,14 @@ export default function SouthPane({
|
|||||||
databases,
|
databases,
|
||||||
offline = false,
|
offline = false,
|
||||||
displayLimit,
|
displayLimit,
|
||||||
|
user,
|
||||||
|
defaultQueryLimit,
|
||||||
}: SouthPanePropTypes) {
|
}: SouthPanePropTypes) {
|
||||||
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);
|
actions.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]}>
|
||||||
{STATUS_OPTIONS.offline}
|
{STATUS_OPTIONS.offline}
|
||||||
@ -127,9 +141,11 @@ export default function SouthPane({
|
|||||||
search
|
search
|
||||||
query={latestQuery}
|
query={latestQuery}
|
||||||
actions={actions}
|
actions={actions}
|
||||||
|
user={user}
|
||||||
height={innerTabContentHeight}
|
height={innerTabContentHeight}
|
||||||
database={databases[latestQuery.dbId]}
|
database={databases[latestQuery.dbId]}
|
||||||
displayLimit={displayLimit}
|
displayLimit={displayLimit}
|
||||||
|
defaultQueryLimit={defaultQueryLimit}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -153,8 +169,10 @@ export default function SouthPane({
|
|||||||
csv={false}
|
csv={false}
|
||||||
actions={actions}
|
actions={actions}
|
||||||
cache
|
cache
|
||||||
|
user={user}
|
||||||
height={innerTabContentHeight}
|
height={innerTabContentHeight}
|
||||||
displayLimit={displayLimit}
|
displayLimit={displayLimit}
|
||||||
|
defaultQueryLimit={defaultQueryLimit}
|
||||||
/>
|
/>
|
||||||
</Tabs.TabPane>
|
</Tabs.TabPane>
|
||||||
));
|
));
|
||||||
|
@ -26,6 +26,7 @@ function mapStateToProps({ sqlLab }: Record<string, any>) {
|
|||||||
activeSouthPaneTab: sqlLab.activeSouthPaneTab,
|
activeSouthPaneTab: sqlLab.activeSouthPaneTab,
|
||||||
databases: sqlLab.databases,
|
databases: sqlLab.databases,
|
||||||
offline: sqlLab.offline,
|
offline: sqlLab.offline,
|
||||||
|
user: sqlLab.user,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -483,6 +483,7 @@ class SqlEditor extends React.PureComponent {
|
|||||||
actions={this.props.actions}
|
actions={this.props.actions}
|
||||||
height={southPaneHeight}
|
height={southPaneHeight}
|
||||||
displayLimit={this.props.displayLimit}
|
displayLimit={this.props.displayLimit}
|
||||||
|
defaultQueryLimit={this.props.defaultQueryLimit}
|
||||||
/>
|
/>
|
||||||
</Split>
|
</Split>
|
||||||
);
|
);
|
||||||
|
@ -294,7 +294,7 @@ div.Workspace {
|
|||||||
.queryPane {
|
.queryPane {
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
padding-left: 10px;
|
padding-left: 10px;
|
||||||
overflow-y: visible;
|
overflow-y: none;
|
||||||
overflow-x: scroll;
|
overflow-x: scroll;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -436,6 +436,10 @@ div.tablePopover {
|
|||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.QueryTable .ant-btn {
|
||||||
|
position: static;
|
||||||
|
}
|
||||||
|
|
||||||
.ResultsModal .ant-modal-body {
|
.ResultsModal .ant-modal-body {
|
||||||
min-height: 560px;
|
min-height: 560px;
|
||||||
}
|
}
|
||||||
|
@ -83,8 +83,10 @@ const JSON_TREE_THEME = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const StyledFilterableTable = styled.div`
|
const StyledFilterableTable = styled.div`
|
||||||
|
height: 100%;
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
margin-top: ${({ theme }) => theme.gridUnit * 2}px;
|
margin-top: ${({ theme }) => theme.gridUnit * 2}px;
|
||||||
|
overflow-y: hidden;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
// when more than MAX_COLUMNS_FOR_TABLE are returned, switch from table to grid view
|
// when more than MAX_COLUMNS_FOR_TABLE are returned, switch from table to grid view
|
||||||
@ -466,7 +468,6 @@ export default class FilterableTable extends PureComponent<
|
|||||||
<ScrollSync>
|
<ScrollSync>
|
||||||
{({ onScroll, scrollTop }) => (
|
{({ onScroll, scrollTop }) => (
|
||||||
<div
|
<div
|
||||||
style={{ height }}
|
|
||||||
className="filterable-table-container Table"
|
className="filterable-table-container Table"
|
||||||
data-test="filterable-table-container"
|
data-test="filterable-table-container"
|
||||||
ref={this.container}
|
ref={this.container}
|
||||||
@ -559,7 +560,6 @@ export default class FilterableTable extends PureComponent<
|
|||||||
this.getDatum(sortedAndFilteredList, index);
|
this.getDatum(sortedAndFilteredList, index);
|
||||||
return (
|
return (
|
||||||
<StyledFilterableTable
|
<StyledFilterableTable
|
||||||
style={{ height }}
|
|
||||||
className="filterable-table-container"
|
className="filterable-table-container"
|
||||||
ref={this.container}
|
ref={this.container}
|
||||||
>
|
>
|
||||||
|
@ -2905,7 +2905,7 @@ class Superset(BaseSupersetView): # pylint: disable=too-many-public-methods
|
|||||||
except json.JSONDecodeError:
|
except json.JSONDecodeError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
payload["user"] = bootstrap_user_data(g.user)
|
payload["user"] = bootstrap_user_data(g.user, include_perms=True)
|
||||||
bootstrap_data = json.dumps(
|
bootstrap_data = json.dumps(
|
||||||
payload, default=utils.pessimistic_json_iso_dttm_ser
|
payload, default=utils.pessimistic_json_iso_dttm_ser
|
||||||
)
|
)
|
||||||
|
Loading…
Reference in New Issue
Block a user