mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
React: refactor away from unsafe lifecycle methods (#21291)
* refactor aliasBy and PopoverCtrl components * refactor Aggregations component * refactor MetricSelect component * refactor UserProvider * popoverCtr: remove redundant logic * simplified the MetricsSelect a bit. * skipping testing of internal component logic. * changed to componentWillMount. * changed elapsed time to a functional component. * rewrote the tests. * fixed missing test title. * fixed a tiny issue with elapsed time. * rename of field. Co-authored-by: Marcus Andersson <marcus.andersson@grafana.com>
This commit is contained in:
48
public/app/core/components/Select/MetricSelect.test.tsx
Normal file
48
public/app/core/components/Select/MetricSelect.test.tsx
Normal file
@@ -0,0 +1,48 @@
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
import { MetricSelect } from './MetricSelect';
|
||||
import { LegacyForms } from '@grafana/ui';
|
||||
const { Select } = LegacyForms;
|
||||
|
||||
describe('MetricSelect', () => {
|
||||
describe('When receive props', () => {
|
||||
it('should pass correct set of props to Select component', () => {
|
||||
const props: any = {
|
||||
placeholder: 'Select Reducer',
|
||||
className: 'width-15',
|
||||
options: [],
|
||||
variables: [],
|
||||
};
|
||||
const wrapper = shallow(<MetricSelect {...props} />);
|
||||
expect(wrapper.find(Select).props()).toMatchObject({
|
||||
className: 'width-15',
|
||||
isMulti: false,
|
||||
isClearable: false,
|
||||
backspaceRemovesValue: false,
|
||||
isSearchable: true,
|
||||
maxMenuHeight: 500,
|
||||
placeholder: 'Select Reducer',
|
||||
});
|
||||
});
|
||||
it('should pass callbacks correctly to the Select component', () => {
|
||||
const spyOnChange = jest.fn();
|
||||
const props: any = {
|
||||
onChange: spyOnChange,
|
||||
options: [],
|
||||
variables: [],
|
||||
};
|
||||
const wrapper = shallow(<MetricSelect {...props} />);
|
||||
wrapper
|
||||
.find(Select)
|
||||
.props()
|
||||
.onChange({ value: 'foo' });
|
||||
expect(
|
||||
wrapper
|
||||
.find(Select)
|
||||
.props()
|
||||
.noOptionsMessage()
|
||||
).toEqual('No options found');
|
||||
expect(spyOnChange).toHaveBeenCalledWith('foo');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import React, { useMemo, useCallback, FC } from 'react';
|
||||
import _ from 'lodash';
|
||||
|
||||
import { LegacyForms } from '@grafana/ui';
|
||||
@@ -16,75 +16,51 @@ export interface Props {
|
||||
variables?: Variable[];
|
||||
}
|
||||
|
||||
interface State {
|
||||
options: Array<SelectableValue<string>>;
|
||||
}
|
||||
export const MetricSelect: FC<Props> = props => {
|
||||
const { value, placeholder, className, isSearchable, onChange } = props;
|
||||
const options = useSelectOptions(props);
|
||||
const selected = useSelectedOption(options, value);
|
||||
const onChangeValue = useCallback((selectable: SelectableValue<string>) => onChange(selectable.value), [onChange]);
|
||||
|
||||
export class MetricSelect extends React.Component<Props, State> {
|
||||
static defaultProps: Partial<Props> = {
|
||||
variables: [],
|
||||
options: [],
|
||||
isSearchable: true,
|
||||
};
|
||||
return (
|
||||
<Select
|
||||
className={className}
|
||||
isMulti={false}
|
||||
isClearable={false}
|
||||
backspaceRemovesValue={false}
|
||||
onChange={onChangeValue}
|
||||
options={options}
|
||||
isSearchable={isSearchable}
|
||||
maxMenuHeight={500}
|
||||
placeholder={placeholder}
|
||||
noOptionsMessage={() => 'No options found'}
|
||||
value={selected}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
this.state = { options: [] };
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.setState({ options: this.buildOptions(this.props) });
|
||||
}
|
||||
|
||||
UNSAFE_componentWillReceiveProps(nextProps: Props) {
|
||||
if (nextProps.options.length > 0 || nextProps.variables?.length) {
|
||||
this.setState({ options: this.buildOptions(nextProps) });
|
||||
const useSelectOptions = ({ variables = [], options }: Props): Array<SelectableValue<string>> => {
|
||||
return useMemo(() => {
|
||||
if (!Array.isArray(variables) || variables.length === 0) {
|
||||
return options;
|
||||
}
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps: Props) {
|
||||
const nextOptions = this.buildOptions(nextProps);
|
||||
return nextProps.value !== this.props.value || !_.isEqual(nextOptions, this.state.options);
|
||||
}
|
||||
return [
|
||||
{
|
||||
label: 'Template Variables',
|
||||
options: variables.map(({ name }) => ({
|
||||
label: `$${name}`,
|
||||
value: `$${name}`,
|
||||
})),
|
||||
},
|
||||
...options,
|
||||
];
|
||||
}, [variables, options]);
|
||||
};
|
||||
|
||||
buildOptions({ variables = [], options }: Props) {
|
||||
return variables.length > 0 ? [this.getVariablesGroup(), ...options] : options;
|
||||
}
|
||||
|
||||
getVariablesGroup() {
|
||||
return {
|
||||
label: 'Template Variables',
|
||||
options: this.props.variables?.map(v => ({
|
||||
label: `$${v.name}`,
|
||||
value: `$${v.name}`,
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
getSelectedOption() {
|
||||
const { options } = this.state;
|
||||
const useSelectedOption = (options: Array<SelectableValue<string>>, value: string): SelectableValue<string> => {
|
||||
return useMemo(() => {
|
||||
const allOptions = options.every(o => o.options) ? _.flatten(options.map(o => o.options)) : options;
|
||||
return allOptions.find(option => option.value === this.props.value);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { placeholder, className, isSearchable, onChange } = this.props;
|
||||
const { options } = this.state;
|
||||
const selectedOption = this.getSelectedOption();
|
||||
return (
|
||||
<Select
|
||||
className={className}
|
||||
isMulti={false}
|
||||
isClearable={false}
|
||||
backspaceRemovesValue={false}
|
||||
onChange={item => onChange(item.value ?? '')}
|
||||
options={options}
|
||||
isSearchable={isSearchable}
|
||||
maxMenuHeight={500}
|
||||
placeholder={placeholder}
|
||||
noOptionsMessage={() => 'No options found'}
|
||||
value={selectedOption}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
return allOptions.find(option => option.value === value);
|
||||
}, [options, value]);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user