mirror of
https://github.com/apache/superset.git
synced 2026-04-18 23:55:00 +00:00
refactor: rewrite and enhance chart control withVerification (#11435)
* refactor: rewrite and enhance chart control withVerification * Add toasts for failed messages; fixes popover render
This commit is contained in:
@@ -0,0 +1,142 @@
|
||||
/**
|
||||
* 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 { mount, ReactWrapper } from 'enzyme';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
|
||||
import MetricsControl from 'src/explore/components/controls/MetricsControl';
|
||||
import withAsyncVerification, {
|
||||
ControlPropsWithExtras,
|
||||
WithAsyncVerificationOptions,
|
||||
} from 'src/explore/components/controls/withAsyncVerification';
|
||||
import { ExtraControlProps } from '@superset-ui/chart-controls';
|
||||
|
||||
const VALID_METRIC = {
|
||||
metric_name: 'sum__value',
|
||||
expression: 'SUM(energy_usage.value)',
|
||||
};
|
||||
|
||||
const mockSetControlValue = jest.fn();
|
||||
|
||||
const defaultProps = {
|
||||
name: 'metrics',
|
||||
label: 'Metrics',
|
||||
value: undefined,
|
||||
multi: true,
|
||||
needAsyncVerification: true,
|
||||
actions: { setControlValue: mockSetControlValue },
|
||||
onChange: () => {},
|
||||
columns: [
|
||||
{ type: 'VARCHAR(255)', column_name: 'source' },
|
||||
{ type: 'VARCHAR(255)', column_name: 'target' },
|
||||
{ type: 'DOUBLE', column_name: 'value' },
|
||||
],
|
||||
savedMetrics: [
|
||||
VALID_METRIC,
|
||||
{ metric_name: 'avg__value', expression: 'AVG(energy_usage.value)' },
|
||||
],
|
||||
datasourceType: 'sqla',
|
||||
};
|
||||
|
||||
function verify(sourceProp: string) {
|
||||
const mock = jest.fn();
|
||||
mock.mockImplementation(async (props: ControlPropsWithExtras) => {
|
||||
return { [sourceProp]: props.validMetrics || [VALID_METRIC] };
|
||||
});
|
||||
return mock;
|
||||
}
|
||||
|
||||
async function setup({
|
||||
extraProps,
|
||||
baseControl = MetricsControl,
|
||||
onChange,
|
||||
}: Partial<WithAsyncVerificationOptions> & {
|
||||
extraProps?: ExtraControlProps;
|
||||
} = {}) {
|
||||
const props = {
|
||||
...defaultProps,
|
||||
...extraProps,
|
||||
};
|
||||
const verifier = verify('savedMetrics');
|
||||
const VerifiedControl = withAsyncVerification({
|
||||
baseControl,
|
||||
verify: verifier,
|
||||
onChange,
|
||||
});
|
||||
type Wrapper = ReactWrapper<typeof props & ExtraControlProps>;
|
||||
let wrapper: Wrapper | undefined;
|
||||
await act(async () => {
|
||||
wrapper = mount(<VerifiedControl {...props} />);
|
||||
});
|
||||
return { props, wrapper: wrapper as Wrapper, onChange, verifier };
|
||||
}
|
||||
|
||||
describe('VerifiedMetricsControl', () => {
|
||||
it('should calls verify correctly', async () => {
|
||||
expect.assertions(5);
|
||||
const { wrapper, verifier, props } = await setup();
|
||||
|
||||
expect(wrapper.find(MetricsControl).length).toBe(1);
|
||||
|
||||
expect(verifier).toBeCalledTimes(1);
|
||||
expect(verifier).toBeCalledWith(
|
||||
expect.objectContaining({ savedMetrics: props.savedMetrics }),
|
||||
);
|
||||
|
||||
// should call verifier with new props when props are updated.
|
||||
await act(async () => {
|
||||
wrapper.setProps({ validMetric: ['abc'] });
|
||||
});
|
||||
|
||||
expect(verifier).toBeCalledTimes(2);
|
||||
expect(verifier).toBeCalledWith(
|
||||
expect.objectContaining({ validMetric: ['abc'] }),
|
||||
);
|
||||
});
|
||||
|
||||
it('should trigger onChange event', async () => {
|
||||
expect.assertions(3);
|
||||
const mockOnChange = jest.fn();
|
||||
const { wrapper } = await setup({
|
||||
// should allow specify baseControl with control component name
|
||||
baseControl: 'MetricsControl',
|
||||
onChange: mockOnChange,
|
||||
});
|
||||
|
||||
const child = wrapper.find(MetricsControl);
|
||||
child.props().onChange(['abc']);
|
||||
|
||||
expect(child.length).toBe(1);
|
||||
expect(mockOnChange).toBeCalledTimes(1);
|
||||
expect(mockOnChange).toBeCalledWith(['abc'], {
|
||||
actions: defaultProps.actions,
|
||||
columns: defaultProps.columns,
|
||||
datasourceType: defaultProps.datasourceType,
|
||||
label: defaultProps.label,
|
||||
multi: defaultProps.multi,
|
||||
name: defaultProps.name,
|
||||
// in real life, `onChange` should have been called with the updated
|
||||
// props (both savedMetrics and value should have beend updated), but
|
||||
// because of the limitation of enzyme (it cannot get props updated from
|
||||
// useEffect hooks), we are not able to check that here.
|
||||
savedMetrics: defaultProps.savedMetrics,
|
||||
value: undefined,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,127 +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 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, validMetric) {
|
||||
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*',
|
||||
validMetric || `["${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);
|
||||
});
|
||||
|
||||
it('Returns no verified options if none are valid', () => {
|
||||
const { wrapper } = setup({}, []);
|
||||
setTimeout(() => {
|
||||
expect(fetchMock.calls(defaultProps.getEndpoint())).toHaveLength(1);
|
||||
const child = wrapper.find(MetricsControl);
|
||||
expect(child.props().savedMetrics).toEqual([]);
|
||||
fetchMock.reset();
|
||||
}, 0);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user