You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by mi...@apache.org on 2019/05/22 23:39:12 UTC

[incubator-superset] branch master updated: Adding controls for verifying options (#7468)

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

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


The following commit(s) were added to refs/heads/master by this push:
     new 421183d  Adding controls for verifying options (#7468)
421183d is described below

commit 421183d3f46c48215e88e9d7d285f2dc6c7ccfe6
Author: michellethomas <mi...@gmail.com>
AuthorDate: Wed May 22 16:39:01 2019 -0700

    Adding controls for verifying options (#7468)
    
    * Creating withVerification HOC
    
    * Updating to use componentDidMount and componentDidUpdate and adding propTypes
    
    * Adding tests to withVerification
    
    * Adding documentation to withVerification
---
 .../explore/components/withVerification_spec.jsx   | 106 +++++++++++++++++++++
 .../src/explore/components/controls/index.js       |   4 +
 .../components/controls/withVerification.jsx       |  88 +++++++++++++++++
 3 files changed, 198 insertions(+)

diff --git a/superset/assets/spec/javascripts/explore/components/withVerification_spec.jsx b/superset/assets/spec/javascripts/explore/components/withVerification_spec.jsx
new file mode 100644
index 0000000..44377ea
--- /dev/null
+++ b/superset/assets/spec/javascripts/explore/components/withVerification_spec.jsx
@@ -0,0 +1,106 @@
+/**
+ * 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 sinon from 'sinon';
+import { shallow } from 'enzyme';
+import fetchMock from 'fetch-mock';
+
+import MetricsControl from '../../../../src/explore/components/controls/MetricsControl';
+import withVerification from '../../../../src/explore/components/controls/withVerification';
+
+const defaultProps = {
+  name: 'metrics',
+  label: 'Metrics',
+  value: undefined,
+  multi: true,
+  columns: [
+    { type: 'VARCHAR(255)', column_name: 'source' },
+    { type: 'VARCHAR(255)', column_name: 'target' },
+    { type: 'DOUBLE', column_name: 'value' },
+  ],
+  savedMetrics: [
+    { metric_name: 'sum__value', expression: 'SUM(energy_usage.value)' },
+    { metric_name: 'avg__value', expression: 'AVG(energy_usage.value)' },
+  ],
+  datasourceType: 'sqla',
+  getEndpoint: controlValues => `valid_metrics?data=${controlValues}`,
+};
+
+const VALID_METRIC = { metric_name: 'sum__value', expression: 'SUM(energy_usage.value)' };
+
+function setup(overrides) {
+  const onChange = sinon.spy();
+  const props = {
+    onChange,
+    ...defaultProps,
+    ...overrides,
+  };
+  const VerifiedControl = withVerification(MetricsControl, 'metric_name', 'savedMetrics');
+  const wrapper = shallow(<VerifiedControl {...props} />);
+  fetchMock.mock('glob:*/valid_metrics*', `["${VALID_METRIC.metric_name}"]`);
+  return { props, wrapper, onChange };
+}
+
+afterEach(fetchMock.restore);
+
+describe('VerifiedMetricsControl', () => {
+
+  it('Gets valid options', () => {
+    const { wrapper } = setup();
+    setTimeout(() => {
+      expect(fetchMock.calls(defaultProps.getEndpoint())).toHaveLength(1);
+      expect(wrapper.state('validOptions')).toEqual([VALID_METRIC]);
+      fetchMock.reset();
+    }, 0);
+  });
+
+  it('Returns verified options', () => {
+    const { wrapper } = setup();
+    setTimeout(() => {
+      expect(fetchMock.calls(defaultProps.getEndpoint())).toHaveLength(1);
+      const child = wrapper.find(MetricsControl);
+      expect(child.props().savedMetrics).toEqual([VALID_METRIC]);
+      fetchMock.reset();
+    }, 0);
+  });
+
+  it('Makes no calls if endpoint is not set', () => {
+    const { wrapper } = setup({
+      getEndpoint: () => null,
+    });
+    setTimeout(() => {
+      expect(fetchMock.calls(defaultProps.getEndpoint())).toHaveLength(0);
+      expect(wrapper.state('validOptions')).toEqual(new Set());
+      fetchMock.reset();
+    }, 0);
+  });
+
+  it('Calls endpoint if control values change', () => {
+    const { props, wrapper } = setup({ controlValues: { metrics: 'sum__value' } });
+    setTimeout(() => {
+      expect(fetchMock.calls(defaultProps.getEndpoint())).toHaveLength(1);
+      fetchMock.reset();
+    }, 0);
+    wrapper.setProps({ ...props, controlValues: { metrics: 'avg__value' } });
+    setTimeout(() => {
+      expect(fetchMock.calls(defaultProps.getEndpoint())).toHaveLength(1);
+      fetchMock.reset();
+    }, 0);
+  });
+});
diff --git a/superset/assets/src/explore/components/controls/index.js b/superset/assets/src/explore/components/controls/index.js
index 32a8d44..a5800f2 100644
--- a/superset/assets/src/explore/components/controls/index.js
+++ b/superset/assets/src/explore/components/controls/index.js
@@ -40,6 +40,7 @@ import MetricsControl from './MetricsControl';
 import AdhocFilterControl from './AdhocFilterControl';
 import FilterPanel from './FilterPanel';
 import FilterBoxItemControl from './FilterBoxItemControl';
+import withVerification from './withVerification';
 
 const controlMap = {
   AnnotationLayerControl,
@@ -66,5 +67,8 @@ const controlMap = {
   AdhocFilterControl,
   FilterPanel,
   FilterBoxItemControl,
+  MetricsControlVerifiedOptions: withVerification(MetricsControl, 'metric_name', 'savedMetrics'),
+  SelectControlVerifiedOptions: withVerification(SelectControl, 'column_name', 'options'),
+  AdhocFilterControlVerifiedOptions: withVerification(AdhocFilterControl, 'column_name', 'columns'),
 };
 export default controlMap;
diff --git a/superset/assets/src/explore/components/controls/withVerification.jsx b/superset/assets/src/explore/components/controls/withVerification.jsx
new file mode 100644
index 0000000..8f1e549
--- /dev/null
+++ b/superset/assets/src/explore/components/controls/withVerification.jsx
@@ -0,0 +1,88 @@
+/**
+ * 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 { SupersetClient } from '@superset-ui/connection';
+
+import { isEqual } from 'lodash';
+
+export default function withVerification(WrappedComponent, optionLabel, optionsName) {
+  /*
+   * This function will verify control options before passing them to the control by calling an
+   * endpoint on mount and when the controlValues change. controlValues should be set in
+   * mapStateToProps that can be added as a control override along with getEndpoint.
+   */
+  class withVerificationComponent extends React.Component {
+    constructor(props) {
+      super(props);
+      this.state = {
+        validOptions: new Set(),
+        hasRunVerification: false,
+      };
+
+      this.getValidOptions = this.getValidOptions.bind(this);
+    }
+
+    componentDidMount() {
+      this.getValidOptions();
+    }
+
+    componentDidUpdate(prevProps) {
+      const { hasRunVerification } = this.state;
+      if (!isEqual(this.props.controlValues, prevProps.controlValues) || !hasRunVerification) {
+        this.getValidOptions();
+      }
+    }
+
+    getValidOptions() {
+      const endpoint = this.props.getEndpoint(this.props.controlValues);
+      if (endpoint) {
+        SupersetClient.get({
+          endpoint,
+        }).then(({ json }) => {
+          if (Array.isArray(json)) {
+            this.setState({ validOptions: new Set(json) || new Set() });
+          }
+        }).catch(error => console.log(error));
+
+        if (!this.state.hasRunVerification) {
+          this.setState({ hasRunVerification: true });
+        }
+      }
+    }
+
+    render() {
+      const { validOptions } = this.state;
+      const options = this.props[optionsName];
+      const verifiedOptions = validOptions.size ?
+        options.filter(o => (validOptions.has(o[optionLabel]))) :
+        options;
+
+      const newProps = { ...this.props, [optionsName]: verifiedOptions };
+
+      return (
+        <WrappedComponent
+          {...newProps}
+        />
+      );
+    }
+  }
+  withVerificationComponent.propTypes = WrappedComponent.propTypes;
+  return withVerificationComponent;
+}
+