mirror of
https://github.com/apache/superset.git
synced 2024-09-12 08:39:45 -04:00
[sql lab] visualization flow to detect unaliased columns (#5579)
* [sql lab] visualization flow to detect unaliased columns * Addressing comments
This commit is contained in:
parent
a8f4849911
commit
f4b45f07c3
@ -9,7 +9,7 @@ import sinon from 'sinon';
|
|||||||
|
|
||||||
import $ from 'jquery';
|
import $ from 'jquery';
|
||||||
import shortid from 'shortid';
|
import shortid from 'shortid';
|
||||||
import { queries } from './fixtures';
|
import { queries, queryWithBadColumns } from './fixtures';
|
||||||
import { sqlLabReducer } from '../../../src/SqlLab/reducers';
|
import { sqlLabReducer } from '../../../src/SqlLab/reducers';
|
||||||
import * as actions from '../../../src/SqlLab/actions';
|
import * as actions from '../../../src/SqlLab/actions';
|
||||||
import ExploreResultsButton from '../../../src/SqlLab/components/ExploreResultsButton';
|
import ExploreResultsButton from '../../../src/SqlLab/components/ExploreResultsButton';
|
||||||
@ -60,19 +60,35 @@ describe('ExploreResultsButton', () => {
|
|||||||
requiresTime: true,
|
requiresTime: true,
|
||||||
value: 'bar',
|
value: 'bar',
|
||||||
};
|
};
|
||||||
const getExploreResultsButtonWrapper = () => (
|
const getExploreResultsButtonWrapper = (props = mockedProps) => (
|
||||||
shallow(<ExploreResultsButton {...mockedProps} />, {
|
shallow(<ExploreResultsButton {...props} />, {
|
||||||
context: { store },
|
context: { store },
|
||||||
}).dive());
|
}).dive());
|
||||||
|
|
||||||
it('renders', () => {
|
it('renders', () => {
|
||||||
expect(React.isValidElement(<ExploreResultsButton />)).to.equal(true);
|
expect(React.isValidElement(<ExploreResultsButton />)).to.equal(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders with props', () => {
|
it('renders with props', () => {
|
||||||
expect(
|
expect(
|
||||||
React.isValidElement(<ExploreResultsButton {...mockedProps} />),
|
React.isValidElement(<ExploreResultsButton {...mockedProps} />),
|
||||||
).to.equal(true);
|
).to.equal(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('detects bad columns', () => {
|
||||||
|
const wrapper = getExploreResultsButtonWrapper({
|
||||||
|
database,
|
||||||
|
show: true,
|
||||||
|
query: queryWithBadColumns,
|
||||||
|
});
|
||||||
|
|
||||||
|
const badCols = wrapper.instance().getInvalidColumns();
|
||||||
|
expect(badCols).to.deep.equal(['COUNT(*)', '1', '123', 'CASE WHEN 1=1 THEN 1 ELSE 0 END']);
|
||||||
|
|
||||||
|
const msgWrapper = shallow(wrapper.instance().renderInvalidColumnMessage());
|
||||||
|
expect(msgWrapper.find('div')).to.have.length(1);
|
||||||
|
});
|
||||||
|
|
||||||
it('renders a Button', () => {
|
it('renders a Button', () => {
|
||||||
const wrapper = getExploreResultsButtonWrapper();
|
const wrapper = getExploreResultsButtonWrapper();
|
||||||
expect(wrapper.find(Button)).to.have.length(1);
|
expect(wrapper.find(Button)).to.have.length(1);
|
||||||
|
@ -179,7 +179,7 @@ export const defaultQueryEditor = {
|
|||||||
export const queries = [
|
export const queries = [
|
||||||
{
|
{
|
||||||
dbId: 1,
|
dbId: 1,
|
||||||
sql: 'SELECT *FROM superset.slices',
|
sql: 'SELECT * FROM superset.slices',
|
||||||
sqlEditorId: 'SJ8YO72R',
|
sqlEditorId: 'SJ8YO72R',
|
||||||
tab: 'Demo',
|
tab: 'Demo',
|
||||||
runAsync: false,
|
runAsync: false,
|
||||||
@ -257,6 +257,43 @@ export const queries = [
|
|||||||
results: null,
|
results: null,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
export const queryWithBadColumns = {
|
||||||
|
...queries[0],
|
||||||
|
results: {
|
||||||
|
data: queries[0].results.data,
|
||||||
|
columns: [{
|
||||||
|
is_date: true,
|
||||||
|
is_dim: false,
|
||||||
|
name: 'COUNT(*)',
|
||||||
|
type: 'STRING',
|
||||||
|
}, {
|
||||||
|
is_date: false,
|
||||||
|
is_dim: true,
|
||||||
|
name: 'this_col_is_ok',
|
||||||
|
type: 'STRING',
|
||||||
|
}, {
|
||||||
|
is_date: false,
|
||||||
|
is_dim: true,
|
||||||
|
name: 'a',
|
||||||
|
type: 'STRING',
|
||||||
|
}, {
|
||||||
|
is_date: false,
|
||||||
|
is_dim: true,
|
||||||
|
name: '1',
|
||||||
|
type: 'STRING',
|
||||||
|
}, {
|
||||||
|
is_date: false,
|
||||||
|
is_dim: true,
|
||||||
|
name: '123',
|
||||||
|
type: 'STRING',
|
||||||
|
}, {
|
||||||
|
is_date: false,
|
||||||
|
is_dim: true,
|
||||||
|
name: 'CASE WHEN 1=1 THEN 1 ELSE 0 END',
|
||||||
|
type: 'STRING',
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
};
|
||||||
export const databases = {
|
export const databases = {
|
||||||
result: [{
|
result: [{
|
||||||
allow_ctas: true,
|
allow_ctas: true,
|
||||||
|
@ -33,12 +33,15 @@ class ExploreResultsButton extends React.PureComponent {
|
|||||||
};
|
};
|
||||||
this.visualize = this.visualize.bind(this);
|
this.visualize = this.visualize.bind(this);
|
||||||
this.onClick = this.onClick.bind(this);
|
this.onClick = this.onClick.bind(this);
|
||||||
|
this.getInvalidColumns = this.getInvalidColumns.bind(this);
|
||||||
|
this.renderInvalidColumnMessage = this.renderInvalidColumnMessage.bind(this);
|
||||||
}
|
}
|
||||||
onClick() {
|
onClick() {
|
||||||
const timeout = this.props.timeout;
|
const timeout = this.props.timeout;
|
||||||
|
const msg = this.renderInvalidColumnMessage();
|
||||||
if (Math.round(this.getQueryDuration()) > timeout) {
|
if (Math.round(this.getQueryDuration()) > timeout) {
|
||||||
this.dialog.show({
|
this.dialog.show({
|
||||||
title: 'Explore',
|
title: t('Explore'),
|
||||||
body: this.renderTimeoutWarning(),
|
body: this.renderTimeoutWarning(),
|
||||||
actions: [
|
actions: [
|
||||||
Dialog.CancelAction(),
|
Dialog.CancelAction(),
|
||||||
@ -51,6 +54,19 @@ class ExploreResultsButton extends React.PureComponent {
|
|||||||
dialog.hide();
|
dialog.hide();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
} else if (msg) {
|
||||||
|
this.dialog.show({
|
||||||
|
title: t('Explore'),
|
||||||
|
body: msg,
|
||||||
|
actions: [
|
||||||
|
Dialog.DefaultAction('Ok', () => {}, 'btn-danger'),
|
||||||
|
],
|
||||||
|
bsSize: 'large',
|
||||||
|
bsStyle: 'warning',
|
||||||
|
onHide: (dialog) => {
|
||||||
|
dialog.hide();
|
||||||
|
},
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
this.visualize();
|
this.visualize();
|
||||||
}
|
}
|
||||||
@ -65,6 +81,10 @@ class ExploreResultsButton extends React.PureComponent {
|
|||||||
getQueryDuration() {
|
getQueryDuration() {
|
||||||
return moment.duration(this.props.query.endDttm - this.props.query.startDttm).asSeconds();
|
return moment.duration(this.props.query.endDttm - this.props.query.startDttm).asSeconds();
|
||||||
}
|
}
|
||||||
|
getInvalidColumns() {
|
||||||
|
const re = /^[A-Za-z_]\w*$/;
|
||||||
|
return this.props.query.results.columns.map(col => col.name).filter(col => !re.test(col));
|
||||||
|
}
|
||||||
datasourceName() {
|
datasourceName() {
|
||||||
const { query } = this.props;
|
const { query } = this.props;
|
||||||
const uniqueId = shortid.generate();
|
const uniqueId = shortid.generate();
|
||||||
@ -126,6 +146,22 @@ class ExploreResultsButton extends React.PureComponent {
|
|||||||
{t('feature to store a summarized data set that you can then explore.')}
|
{t('feature to store a summarized data set that you can then explore.')}
|
||||||
</Alert>);
|
</Alert>);
|
||||||
}
|
}
|
||||||
|
renderInvalidColumnMessage() {
|
||||||
|
const invalidColumns = this.getInvalidColumns();
|
||||||
|
if (invalidColumns.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{t('Column name(s) ')}
|
||||||
|
<code><strong>{invalidColumns.join(', ')} </strong></code>
|
||||||
|
{t('cannot be used as a column name. Please use aliases (as in ')}
|
||||||
|
<code>SELECT count(*)
|
||||||
|
<strong>AS my_alias</strong>
|
||||||
|
</code>){' '}
|
||||||
|
{t('limited to alphanumeric characters and underscores')}
|
||||||
|
</div>);
|
||||||
|
}
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
|
Loading…
Reference in New Issue
Block a user