mirror of https://github.com/apache/superset.git
[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 shortid from 'shortid';
|
||||
import { queries } from './fixtures';
|
||||
import { queries, queryWithBadColumns } from './fixtures';
|
||||
import { sqlLabReducer } from '../../../src/SqlLab/reducers';
|
||||
import * as actions from '../../../src/SqlLab/actions';
|
||||
import ExploreResultsButton from '../../../src/SqlLab/components/ExploreResultsButton';
|
||||
|
@ -60,19 +60,35 @@ describe('ExploreResultsButton', () => {
|
|||
requiresTime: true,
|
||||
value: 'bar',
|
||||
};
|
||||
const getExploreResultsButtonWrapper = () => (
|
||||
shallow(<ExploreResultsButton {...mockedProps} />, {
|
||||
const getExploreResultsButtonWrapper = (props = mockedProps) => (
|
||||
shallow(<ExploreResultsButton {...props} />, {
|
||||
context: { store },
|
||||
}).dive());
|
||||
|
||||
it('renders', () => {
|
||||
expect(React.isValidElement(<ExploreResultsButton />)).to.equal(true);
|
||||
});
|
||||
|
||||
it('renders with props', () => {
|
||||
expect(
|
||||
React.isValidElement(<ExploreResultsButton {...mockedProps} />),
|
||||
).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', () => {
|
||||
const wrapper = getExploreResultsButtonWrapper();
|
||||
expect(wrapper.find(Button)).to.have.length(1);
|
||||
|
|
|
@ -179,7 +179,7 @@ export const defaultQueryEditor = {
|
|||
export const queries = [
|
||||
{
|
||||
dbId: 1,
|
||||
sql: 'SELECT *FROM superset.slices',
|
||||
sql: 'SELECT * FROM superset.slices',
|
||||
sqlEditorId: 'SJ8YO72R',
|
||||
tab: 'Demo',
|
||||
runAsync: false,
|
||||
|
@ -257,6 +257,43 @@ export const queries = [
|
|||
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 = {
|
||||
result: [{
|
||||
allow_ctas: true,
|
||||
|
|
|
@ -33,12 +33,15 @@ class ExploreResultsButton extends React.PureComponent {
|
|||
};
|
||||
this.visualize = this.visualize.bind(this);
|
||||
this.onClick = this.onClick.bind(this);
|
||||
this.getInvalidColumns = this.getInvalidColumns.bind(this);
|
||||
this.renderInvalidColumnMessage = this.renderInvalidColumnMessage.bind(this);
|
||||
}
|
||||
onClick() {
|
||||
const timeout = this.props.timeout;
|
||||
const msg = this.renderInvalidColumnMessage();
|
||||
if (Math.round(this.getQueryDuration()) > timeout) {
|
||||
this.dialog.show({
|
||||
title: 'Explore',
|
||||
title: t('Explore'),
|
||||
body: this.renderTimeoutWarning(),
|
||||
actions: [
|
||||
Dialog.CancelAction(),
|
||||
|
@ -51,6 +54,19 @@ class ExploreResultsButton extends React.PureComponent {
|
|||
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 {
|
||||
this.visualize();
|
||||
}
|
||||
|
@ -65,6 +81,10 @@ class ExploreResultsButton extends React.PureComponent {
|
|||
getQueryDuration() {
|
||||
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() {
|
||||
const { query } = this.props;
|
||||
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.')}
|
||||
</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() {
|
||||
return (
|
||||
<Button
|
||||
|
|
Loading…
Reference in New Issue