You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by aa...@apache.org on 2022/02/08 15:58:58 UTC

[superset] branch master updated: refactor: migrate ExploreResultsButton component to FC & tsx (#18143)

This is an automated email from the ASF dual-hosted git repository.

aafghahi pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/superset.git


The following commit(s) were added to refs/heads/master by this push:
     new cdfcbba  refactor: migrate ExploreResultsButton component to FC & tsx (#18143)
cdfcbba is described below

commit cdfcbbaf30bc770fd33426c4c31122982825b193
Author: EugeneTorap <ev...@gmail.com>
AuthorDate: Tue Feb 8 18:57:08 2022 +0300

    refactor: migrate ExploreResultsButton component to FC & tsx (#18143)
    
    * Move ExploreResultsButton to FC & tsx
    
    * Refactoring
    
    * Refactoring
    
    * Refactoring
    
    * Refactoring
    
    * Refactoring
    
    * Refactoring
    
    * Refactoring
    
    * Fix test
---
 .../ExploreResultsButton.test.jsx                  | 138 +--------------
 .../components/ExploreResultsButton/index.jsx      | 193 ---------------------
 .../components/ExploreResultsButton/index.tsx      |  53 ++++++
 .../src/SqlLab/components/ResultSet/index.tsx      |   6 +-
 4 files changed, 61 insertions(+), 329 deletions(-)

diff --git a/superset-frontend/src/SqlLab/components/ExploreResultsButton/ExploreResultsButton.test.jsx b/superset-frontend/src/SqlLab/components/ExploreResultsButton/ExploreResultsButton.test.jsx
index b524235..e9f1740 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(<ExploreResultsButton store={store} {...props} />)
+    shallow(
+      <ThemeProvider theme={supersetTheme}>
+        <ExploreResultsButton store={store} {...props} />
+      </ThemeProvider>,
+    )
       .dive()
       .dive();
 
-  it('renders', () => {
-    expect(React.isValidElement(<ExploreResultsButton />)).toBe(true);
-  });
-
   it('renders with props', () => {
     expect(
       React.isValidElement(<ExploreResultsButton {...mockedProps} />),
     ).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(
-      <ExploreResultsButton store={store} {...props} />,
-    )
-      .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 969a725..0000000
--- 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 (
-      <Alert
-        type="warning"
-        message={
-          <>
-            {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 ')}
-            <strong>CREATE TABLE AS </strong>
-            {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 (
-      <div>
-        {t('Column name(s) ')}
-        <code>
-          <strong>{invalidColumns.join(', ')} </strong>
-        </code>
-        {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.`)}
-      </div>
-    );
-  }
-
-  render() {
-    const allowsSubquery =
-      this.props.database && this.props.database.allows_subquery;
-    return (
-      <>
-        <Button
-          buttonSize="small"
-          onClick={this.props.onClick}
-          disabled={!allowsSubquery}
-          tooltip={t('Explore the result set in the data exploration view')}
-        >
-          <InfoTooltipWithTrigger
-            icon="line-chart"
-            placement="top"
-            label="explore"
-          />{' '}
-          {t('Explore')}
-        </Button>
-      </>
-    );
-  }
-}
-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 0000000..7c19a4d
--- /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 (
+    <Button
+      buttonSize="small"
+      onClick={onClick}
+      disabled={!allowsSubquery}
+      tooltip={t('Explore the result set in the data exploration view')}
+    >
+      <InfoTooltipWithTrigger
+        icon="line-chart"
+        placement="top"
+        label="explore"
+      />{' '}
+      {t('Explore')}
+    </Button>
+  );
+};
+
+export default ExploreResultsButton;
diff --git a/superset-frontend/src/SqlLab/components/ResultSet/index.tsx b/superset-frontend/src/SqlLab/components/ResultSet/index.tsx
index a341e82..f015b56 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<
           />
           <ResultSetButtons>
             {this.props.visualize &&
-              this.props.database &&
-              this.props.database.allows_virtual_table_explore && (
+              this.props.database?.allows_virtual_table_explore && (
                 <ExploreResultsButton
-                  // @ts-ignore Redux types are difficult to work with, ignoring for now
-                  query={this.props.query}
                   database={this.props.database}
-                  actions={this.props.actions}
                   onClick={this.handleExploreBtnClick}
                 />
               )}