diff --git a/superset-frontend/src/SqlLab/components/ExploreResultsButton/ExploreResultsButton.test.jsx b/superset-frontend/src/SqlLab/components/ExploreResultsButton/ExploreResultsButton.test.jsx
index b524235dc290a..e9f1740517e0f 100644
--- a/superset-frontend/src/SqlLab/components/ExploreResultsButton/ExploreResultsButton.test.jsx
+++ b/superset-frontend/src/SqlLab/components/ExploreResultsButton/ExploreResultsButton.test.jsx
@@ -20,14 +20,10 @@ import React from 'react';
import configureStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import { shallow } from 'enzyme';
-import sinon from 'sinon';
-import fetchMock from 'fetch-mock';
-import shortid from 'shortid';
import sqlLabReducer from 'src/SqlLab/reducers/index';
import ExploreResultsButton from 'src/SqlLab/components/ExploreResultsButton';
-import * as exploreUtils from 'src/explore/exploreUtils';
import Button from 'src/components/Button';
-import { queries, queryWithBadColumns } from 'src/SqlLab/fixtures';
+import { supersetTheme, ThemeProvider } from '@superset-ui/core';
describe('ExploreResultsButton', () => {
const middlewares = [thunk];
@@ -46,146 +42,26 @@ describe('ExploreResultsButton', () => {
const store = mockStore(initialState);
const mockedProps = {
database,
- show: true,
- query: queries[0],
onClick() {},
};
- const mockColumns = {
- ds: {
- is_date: true,
- name: 'ds',
- type: 'STRING',
- },
- gender: {
- is_date: false,
- name: 'gender',
- type: 'STRING',
- },
- };
- const mockChartTypeBarChart = {
- label: 'Distribution - Bar Chart',
- value: 'dist_bar',
- };
- const mockChartTypeTB = {
- label: 'Time Series - Bar Chart',
- value: 'bar',
- };
+
const getExploreResultsButtonWrapper = (props = mockedProps) =>
- shallow()
+ shallow(
+
+
+ ,
+ )
.dive()
.dive();
- it('renders', () => {
- expect(React.isValidElement()).toBe(true);
- });
-
it('renders with props', () => {
expect(
React.isValidElement(),
).toBe(true);
});
- it('detects bad columns', () => {
- const wrapper = getExploreResultsButtonWrapper({
- database,
- show: true,
- query: queryWithBadColumns,
- onClick() {},
- });
-
- const badCols = wrapper.instance().getInvalidColumns();
- expect(badCols).toEqual(['my_dupe_col__2', '__timestamp', '__TIMESTAMP']);
-
- const msgWrapper = shallow(wrapper.instance().renderInvalidColumnMessage());
- expect(msgWrapper.find('div')).toHaveLength(1);
- });
-
it('renders a Button', () => {
const wrapper = getExploreResultsButtonWrapper();
expect(wrapper.find(Button)).toExist();
});
-
- describe('datasourceName', () => {
- let wrapper;
- let stub;
- beforeEach(() => {
- wrapper = getExploreResultsButtonWrapper();
- stub = sinon.stub(shortid, 'generate').returns('abcd');
- });
- afterEach(() => {
- stub.restore();
- });
-
- it('should generate data source name from query', () => {
- const sampleQuery = queries[0];
- const name = wrapper.instance().datasourceName();
- expect(name).toBe(`${sampleQuery.user}-${sampleQuery.tab}-abcd`);
- });
- it('should generate data source name with empty query', () => {
- wrapper.setProps({ query: {} });
- const name = wrapper.instance().datasourceName();
- expect(name).toBe('undefined-abcd');
- });
-
- it('should build viz options', () => {
- wrapper.setState({ chartType: mockChartTypeTB });
- const spy = sinon.spy(wrapper.instance(), 'buildVizOptions');
- wrapper.instance().buildVizOptions();
- expect(spy.returnValues[0]).toEqual({
- schema: 'test_schema',
- sql: wrapper.instance().props.query.sql,
- dbId: wrapper.instance().props.query.dbId,
- columns: Object.values(mockColumns),
- templateParams: undefined,
- datasourceName: 'admin-Demo-abcd',
- });
- });
- });
-
- it('should build visualize advise for long query', () => {
- const longQuery = { ...queries[0], endDttm: 1476910666798 };
- const props = {
- show: true,
- query: longQuery,
- database,
- onClick() {},
- };
- const longQueryWrapper = shallow(
- ,
- )
- .dive()
- .dive();
- const inst = longQueryWrapper.instance();
- expect(inst.getQueryDuration()).toBe(100.7050400390625);
- });
-
- describe('visualize', () => {
- const wrapper = getExploreResultsButtonWrapper();
- const mockOptions = { attr: 'mockOptions' };
- wrapper.setState({
- chartType: mockChartTypeBarChart,
- datasourceName: 'mockDatasourceName',
- });
-
- const visualizeURL = '/superset/sqllab_viz/';
- const visualizeEndpoint = `glob:*${visualizeURL}`;
- const visualizationPayload = { table_id: 107 };
- fetchMock.post(visualizeEndpoint, visualizationPayload);
-
- beforeEach(() => {
- sinon.stub(exploreUtils, 'getExploreUrl').callsFake(() => 'mockURL');
- sinon.spy(exploreUtils, 'exportChart');
- sinon.spy(exploreUtils, 'exploreChart');
- sinon
- .stub(wrapper.instance(), 'buildVizOptions')
- .callsFake(() => mockOptions);
- });
- afterEach(() => {
- exploreUtils.getExploreUrl.restore();
- exploreUtils.exploreChart.restore();
- exploreUtils.exportChart.restore();
- wrapper.instance().buildVizOptions.restore();
- fetchMock.reset();
- });
- });
});
diff --git a/superset-frontend/src/SqlLab/components/ExploreResultsButton/index.jsx b/superset-frontend/src/SqlLab/components/ExploreResultsButton/index.jsx
deleted file mode 100644
index 969a7255a2340..0000000000000
--- a/superset-frontend/src/SqlLab/components/ExploreResultsButton/index.jsx
+++ /dev/null
@@ -1,193 +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 moment from 'moment';
-import React from 'react';
-import PropTypes from 'prop-types';
-import { bindActionCreators } from 'redux';
-import { connect } from 'react-redux';
-import Alert from 'src/components/Alert';
-import { t } from '@superset-ui/core';
-import { InfoTooltipWithTrigger } from '@superset-ui/chart-controls';
-import shortid from 'shortid';
-import Button from 'src/components/Button';
-import * as actions from 'src/SqlLab/actions/sqlLab';
-
-const propTypes = {
- actions: PropTypes.object.isRequired,
- query: PropTypes.object,
- errorMessage: PropTypes.string,
- timeout: PropTypes.number,
- database: PropTypes.object.isRequired,
- onClick: PropTypes.func.isRequired,
-};
-const defaultProps = {
- query: {},
-};
-
-class ExploreResultsButton extends React.PureComponent {
- constructor(props) {
- super(props);
- this.getInvalidColumns = this.getInvalidColumns.bind(this);
- this.renderInvalidColumnMessage =
- this.renderInvalidColumnMessage.bind(this);
- }
-
- getColumns() {
- const { props } = this;
- if (
- props.query &&
- props.query.results &&
- props.query.results.selected_columns
- ) {
- return props.query.results.selected_columns;
- }
- return [];
- }
-
- getQueryDuration() {
- return moment
- .duration(this.props.query.endDttm - this.props.query.startDttm)
- .asSeconds();
- }
-
- getInvalidColumns() {
- const re1 = /__\d+$/; // duplicate column name pattern
- const re2 = /^__timestamp/i; // reserved temporal column alias
-
- return this.props.query.results.selected_columns
- .map(col => col.name)
- .filter(col => re1.test(col) || re2.test(col));
- }
-
- datasourceName() {
- const { query } = this.props;
- const uniqueId = shortid.generate();
- let datasourceName = uniqueId;
- if (query) {
- datasourceName = query.user ? `${query.user}-` : '';
- datasourceName += `${query.tab}-${uniqueId}`;
- }
- return datasourceName;
- }
-
- buildVizOptions() {
- const { schema, sql, dbId, templateParams } = this.props.query;
- return {
- dbId,
- schema,
- sql,
- templateParams,
- datasourceName: this.datasourceName(),
- columns: this.getColumns(),
- };
- }
-
- renderTimeoutWarning() {
- return (
-
- {t(
- 'This query took %s seconds to run, ',
- Math.round(this.getQueryDuration()),
- ) +
- t(
- 'and the explore view times out at %s seconds ',
- this.props.timeout,
- ) +
- t(
- 'following this flow will most likely lead to your query timing out. ',
- ) +
- t(
- 'We recommend your summarize your data further before following that flow. ',
- ) +
- t('If activated you can use the ')}
- CREATE TABLE AS
- {t(
- 'feature to store a summarized data set that you can then explore.',
- )}
- >
- }
- />
- );
- }
-
- renderInvalidColumnMessage() {
- const invalidColumns = this.getInvalidColumns();
- if (invalidColumns.length === 0) {
- return null;
- }
- return (
-
- {t('Column name(s) ')}
-
- {invalidColumns.join(', ')}
-
- {t(`cannot be used as a column name. The column name/alias "__timestamp"
- is reserved for the main temporal expression, and column aliases ending with
- double underscores followed by a numeric value (e.g. "my_col__1") are reserved
- for deduplicating duplicate column names. Please use aliases to rename the
- invalid column names.`)}
-
- );
- }
-
- render() {
- const allowsSubquery =
- this.props.database && this.props.database.allows_subquery;
- return (
- <>
-
- >
- );
- }
-}
-ExploreResultsButton.propTypes = propTypes;
-ExploreResultsButton.defaultProps = defaultProps;
-
-function mapStateToProps({ sqlLab, common }) {
- return {
- errorMessage: sqlLab.errorMessage,
- timeout: common.conf ? common.conf.SUPERSET_WEBSERVER_TIMEOUT : null,
- };
-}
-
-function mapDispatchToProps(dispatch) {
- return {
- actions: bindActionCreators(actions, dispatch),
- };
-}
-
-export default connect(
- mapStateToProps,
- mapDispatchToProps,
-)(ExploreResultsButton);
diff --git a/superset-frontend/src/SqlLab/components/ExploreResultsButton/index.tsx b/superset-frontend/src/SqlLab/components/ExploreResultsButton/index.tsx
new file mode 100644
index 0000000000000..7c19a4d3b4afe
--- /dev/null
+++ b/superset-frontend/src/SqlLab/components/ExploreResultsButton/index.tsx
@@ -0,0 +1,53 @@
+/**
+ * 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 React from 'react';
+import { t } from '@superset-ui/core';
+import { InfoTooltipWithTrigger } from '@superset-ui/chart-controls';
+import Button, { OnClickHandler } from 'src/components/Button';
+
+interface ExploreResultsButtonProps {
+ database?: {
+ allows_subquery?: boolean;
+ };
+ onClick: OnClickHandler;
+}
+
+const ExploreResultsButton = ({
+ database,
+ onClick,
+}: ExploreResultsButtonProps) => {
+ const allowsSubquery = database?.allows_subquery ?? false;
+ return (
+
+ );
+};
+
+export default ExploreResultsButton;
diff --git a/superset-frontend/src/SqlLab/components/ResultSet/index.tsx b/superset-frontend/src/SqlLab/components/ResultSet/index.tsx
index a341e82e9f33a..f015b56070a75 100644
--- a/superset-frontend/src/SqlLab/components/ResultSet/index.tsx
+++ b/superset-frontend/src/SqlLab/components/ResultSet/index.tsx
@@ -527,13 +527,9 @@ export default class ResultSet extends React.PureComponent<
/>
{this.props.visualize &&
- this.props.database &&
- this.props.database.allows_virtual_table_explore && (
+ this.props.database?.allows_virtual_table_explore && (
)}