Update dependency react-select to v5 (#41604)

* Update dependency react-select to v5

* Remove @types/react-select and update types accordingly

* Fix all unit tests

* Add @ts-expect-error to individual errors, remove prefix as it doesn't seem to exist?

* Another minor typescript fix

* Apply fixes from torkel's PR

* Fix last typescript error

Co-authored-by: Renovate Bot <bot@renovateapp.com>
Co-authored-by: Ashley Harrison <ashley.harrison@grafana.com>
This commit is contained in:
renovate[bot]
2021-11-23 12:11:26 +00:00
committed by GitHub
parent 6b79393ccc
commit 035e676cad
24 changed files with 149 additions and 189 deletions

View File

@@ -126,7 +126,6 @@
"@types/react-loadable": "5.5.2", "@types/react-loadable": "5.5.2",
"@types/react-redux": "7.1.20", "@types/react-redux": "7.1.20",
"@types/react-router-dom": "^5.1.7", "@types/react-router-dom": "^5.1.7",
"@types/react-select": "4.0.13",
"@types/react-test-renderer": "17.0.1", "@types/react-test-renderer": "17.0.1",
"@types/react-transition-group": "4.4.0", "@types/react-transition-group": "4.4.0",
"@types/react-virtualized-auto-sizer": "1.0.0", "@types/react-virtualized-auto-sizer": "1.0.0",
@@ -331,7 +330,7 @@
"react-resizable": "3.0.4", "react-resizable": "3.0.4",
"react-reverse-portal": "^2.0.1", "react-reverse-portal": "^2.0.1",
"react-router-dom": "^5.2.0", "react-router-dom": "^5.2.0",
"react-select": "4.3.0", "react-select": "5.2.1",
"react-split-pane": "0.1.89", "react-split-pane": "0.1.89",
"react-transition-group": "4.4.1", "react-transition-group": "4.4.1",
"react-use": "17.2.4", "react-use": "17.2.4",

View File

@@ -76,7 +76,7 @@
"react-inlinesvg": "2.3.0", "react-inlinesvg": "2.3.0",
"react-popper": "2.2.4", "react-popper": "2.2.4",
"react-router-dom": "^5.2.0", "react-router-dom": "^5.2.0",
"react-select": "4.3.0", "react-select": "5.2.1",
"react-select-event": "^5.1.0", "react-select-event": "^5.1.0",
"react-table": "7.7.0", "react-table": "7.7.0",
"react-transition-group": "4.4.1", "react-transition-group": "4.4.1",
@@ -139,7 +139,6 @@
"@types/react-color": "3.0.1", "@types/react-color": "3.0.1",
"@types/react-dom": "17.0.11", "@types/react-dom": "17.0.11",
"@types/react-router-dom": "^5.1.7", "@types/react-router-dom": "^5.1.7",
"@types/react-select": "4.0.13",
"@types/react-table": "7.7.2", "@types/react-table": "7.7.2",
"@types/react-test-renderer": "17.0.1", "@types/react-test-renderer": "17.0.1",
"@types/react-transition-group": "4.4.0", "@types/react-transition-group": "4.4.0",

View File

@@ -1,18 +1,17 @@
import React from 'react'; import React from 'react';
import { components, OptionProps } from 'react-select'; import { components, NoticeProps, GroupBase } from 'react-select';
import { SelectableValue } from '@grafana/data';
export interface Props { export type Props<T> = NoticeProps<SelectableValue<T>, boolean, GroupBase<SelectableValue<T>>>;
children: Element;
}
export const NoOptionsMessage = (props: OptionProps<any, any>) => { export const NoOptionsMessage = <T extends unknown>(props: Props<T>) => {
const { children } = props; const { children } = props;
return ( return (
<components.Option {...props}> <components.NoOptionsMessage {...props}>
<div className="gf-form-select-box__desc-option"> <div className="gf-form-select-box__desc-option">
<div className="gf-form-select-box__desc-option__body">{children}</div> <div className="gf-form-select-box__desc-option__body">{children}</div>
</div> </div>
</components.Option> </components.NoOptionsMessage>
); );
}; };

View File

@@ -216,7 +216,6 @@ export class AsyncSelect<T> extends PureComponent<AsyncProps<T>> {
<WrapInTooltip onCloseMenu={onCloseMenu} onOpenMenu={onOpenMenu} tooltipContent={tooltipContent} isOpen={isOpen}> <WrapInTooltip onCloseMenu={onCloseMenu} onOpenMenu={onOpenMenu} tooltipContent={tooltipContent} isOpen={isOpen}>
{(onOpenMenuInternal, onCloseMenuInternal) => { {(onOpenMenuInternal, onCloseMenuInternal) => {
return ( return (
//@ts-expect-error
<ReactAsyncSelect <ReactAsyncSelect
captureMenuScroll={false} captureMenuScroll={false}
classNamePrefix="gf-form-select-box" classNamePrefix="gf-form-select-box"
@@ -229,14 +228,17 @@ export class AsyncSelect<T> extends PureComponent<AsyncProps<T>> {
}} }}
defaultValue={defaultValue} defaultValue={defaultValue}
value={value} value={value}
//@ts-expect-error
getOptionLabel={getOptionLabel} getOptionLabel={getOptionLabel}
getOptionValue={getOptionValue} getOptionValue={getOptionValue}
menuShouldScrollIntoView={false} menuShouldScrollIntoView={false}
//@ts-expect-error
onChange={onChange} onChange={onChange}
loadOptions={loadOptions} loadOptions={loadOptions}
isLoading={isLoading} isLoading={isLoading}
defaultOptions={defaultOptions} defaultOptions={defaultOptions}
placeholder={placeholder || 'Choose'} placeholder={placeholder || 'Choose'}
//@ts-expect-error
styles={resetSelectStyles()} styles={resetSelectStyles()}
loadingMessage={() => loadingMessage} loadingMessage={() => loadingMessage}
noOptionsMessage={noOptionsMessage} noOptionsMessage={noOptionsMessage}

View File

@@ -1,9 +1,8 @@
import React from 'react'; import React from 'react';
import renderer from 'react-test-renderer'; import renderer from 'react-test-renderer';
import SelectOption from './SelectOption'; import SelectOption from './SelectOption';
import { OptionProps } from 'react-select/src/components/Option'; import { OptionProps } from 'react-select';
// @ts-ignore
const model: OptionProps<any> = { const model: OptionProps<any> = {
data: jest.fn(), data: jest.fn(),
cx: jest.fn(), cx: jest.fn(),
@@ -14,12 +13,13 @@ const model: OptionProps<any> = {
isMulti: false, isMulti: false,
options: [], options: [],
selectOption: jest.fn(), selectOption: jest.fn(),
// @ts-ignore
selectProps: {}, selectProps: {},
setValue: jest.fn(), setValue: jest.fn(),
isDisabled: false, isDisabled: false,
isFocused: false, isFocused: false,
isSelected: false, isSelected: false,
innerRef: null, innerRef: jest.fn(),
innerProps: { innerProps: {
id: '', id: '',
key: '', key: '',

View File

@@ -2,6 +2,7 @@
exports[`SelectOption renders correctly 1`] = ` exports[`SelectOption renders correctly 1`] = `
<div <div
aria-disabled={false}
className="css-o8533i-Option" className="css-o8533i-Option"
id="" id=""
onClick={[MockFunction]} onClick={[MockFunction]}

View File

@@ -6,20 +6,15 @@ import { css, cx } from '@emotion/css';
import { stylesFactory } from '../../themes'; import { stylesFactory } from '../../themes';
import { GrafanaTheme2 } from '@grafana/data'; import { GrafanaTheme2 } from '@grafana/data';
import { focusCss } from '../../themes/mixins'; import { focusCss } from '../../themes/mixins';
import { components, ContainerProps, GroupTypeBase } from 'react-select'; import { components, ContainerProps, GroupBase } from 'react-select';
export const SelectContainer = <Option, isMulti extends boolean, Group extends GroupTypeBase<Option>>( export const SelectContainer = <Option, isMulti extends boolean, Group extends GroupBase<Option>>(
props: ContainerProps<Option, isMulti, Group> & { isFocused: boolean } props: ContainerProps<Option, isMulti, Group> & { isFocused: boolean }
) => { ) => {
const { const { isDisabled, isFocused, children } = props;
isDisabled,
isFocused,
children,
selectProps: { prefix },
} = props;
const theme = useTheme2(); const theme = useTheme2();
const styles = getSelectContainerStyles(theme, isFocused, isDisabled, !!prefix); const styles = getSelectContainerStyles(theme, isFocused, isDisabled);
return ( return (
<components.SelectContainer {...props} className={cx(styles.wrapper, props.className)}> <components.SelectContainer {...props} className={cx(styles.wrapper, props.className)}>
@@ -28,41 +23,35 @@ export const SelectContainer = <Option, isMulti extends boolean, Group extends G
); );
}; };
const getSelectContainerStyles = stylesFactory( const getSelectContainerStyles = stylesFactory((theme: GrafanaTheme2, focused: boolean, disabled: boolean) => {
(theme: GrafanaTheme2, focused: boolean, disabled: boolean, withPrefix: boolean) => { const styles = getInputStyles({ theme, invalid: false });
const styles = getInputStyles({ theme, invalid: false });
return { return {
wrapper: cx( wrapper: cx(
styles.wrapper, styles.wrapper,
sharedInputStyle(theme, false), sharedInputStyle(theme, false),
focused && focused &&
css`
${focusCss(theme.v1)}
`,
disabled && styles.inputDisabled,
css` css`
position: relative; ${focusCss(theme.v1)}
box-sizing: border-box;
display: flex;
flex-direction: row;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
min-height: 32px;
height: auto;
max-width: 100%;
/* Input padding is applied to the InputControl so the menu is aligned correctly */
padding: 0;
cursor: ${disabled ? 'not-allowed' : 'pointer'};
`, `,
withPrefix && disabled && styles.inputDisabled,
css` css`
padding-left: 0; position: relative;
` box-sizing: border-box;
), display: flex;
}; flex-direction: row;
} flex-wrap: wrap;
); align-items: center;
justify-content: space-between;
min-height: 32px;
height: auto;
max-width: 100%;
/* Input padding is applied to the InputControl so the menu is aligned correctly */
padding: 0;
cursor: ${disabled ? 'not-allowed' : 'pointer'};
`
),
};
});

View File

@@ -62,7 +62,7 @@ describe('SelectBase', () => {
describe('is provided', () => { describe('is provided', () => {
it('opens on focus', () => { it('opens on focus', () => {
render(<SelectBase menuShouldPortal onChange={onChangeHandler} openMenuOnFocus />); render(<SelectBase menuShouldPortal onChange={onChangeHandler} openMenuOnFocus />);
fireEvent.focus(screen.getByRole('textbox')); fireEvent.focus(screen.getByRole('combobox'));
expect(screen.queryByText(/no options found/i)).toBeVisible(); expect(screen.queryByText(/no options found/i)).toBeVisible();
}); });
}); });
@@ -74,8 +74,8 @@ describe('SelectBase', () => {
${' '} ${' '}
`('opens on arrow down/up or space', ({ key }) => { `('opens on arrow down/up or space', ({ key }) => {
render(<SelectBase menuShouldPortal onChange={onChangeHandler} />); render(<SelectBase menuShouldPortal onChange={onChangeHandler} />);
fireEvent.focus(screen.getByRole('textbox')); fireEvent.focus(screen.getByRole('combobox'));
fireEvent.keyDown(screen.getByRole('textbox'), { key }); fireEvent.keyDown(screen.getByRole('combobox'), { key });
expect(screen.queryByText(/no options found/i)).toBeVisible(); expect(screen.queryByText(/no options found/i)).toBeVisible();
}); });
}); });

View File

@@ -6,26 +6,21 @@ import { css, cx } from '@emotion/css';
import { stylesFactory } from '../../themes'; import { stylesFactory } from '../../themes';
import { GrafanaTheme2 } from '@grafana/data'; import { GrafanaTheme2 } from '@grafana/data';
import { focusCss } from '../../themes/mixins'; import { focusCss } from '../../themes/mixins';
import { components, ContainerProps as BaseContainerProps, GroupTypeBase } from 'react-select'; import { components, ContainerProps as BaseContainerProps, GroupBase } from 'react-select';
// isFocus prop is actually available, but its not in the types for the version we have. // isFocus prop is actually available, but its not in the types for the version we have.
export interface ContainerProps<Option, isMulti extends boolean, Group extends GroupTypeBase<Option>> export interface ContainerProps<Option, isMulti extends boolean, Group extends GroupBase<Option>>
extends BaseContainerProps<Option, isMulti, Group> { extends BaseContainerProps<Option, isMulti, Group> {
isFocused: boolean; isFocused: boolean;
} }
export const SelectContainer = <Option, isMulti extends boolean, Group extends GroupTypeBase<Option>>( export const SelectContainer = <Option, isMulti extends boolean, Group extends GroupBase<Option>>(
props: ContainerProps<Option, isMulti, Group> props: ContainerProps<Option, isMulti, Group>
) => { ) => {
const { const { isDisabled, isFocused, children } = props;
isDisabled,
isFocused,
children,
selectProps: { prefix },
} = props;
const theme = useTheme2(); const theme = useTheme2();
const styles = getSelectContainerStyles(theme, isFocused, isDisabled, !!prefix); const styles = getSelectContainerStyles(theme, isFocused, isDisabled);
return ( return (
<components.SelectContainer {...props} className={cx(styles.wrapper, props.className)}> <components.SelectContainer {...props} className={cx(styles.wrapper, props.className)}>
@@ -34,41 +29,35 @@ export const SelectContainer = <Option, isMulti extends boolean, Group extends G
); );
}; };
const getSelectContainerStyles = stylesFactory( const getSelectContainerStyles = stylesFactory((theme: GrafanaTheme2, focused: boolean, disabled: boolean) => {
(theme: GrafanaTheme2, focused: boolean, disabled: boolean, withPrefix: boolean) => { const styles = getInputStyles({ theme, invalid: false });
const styles = getInputStyles({ theme, invalid: false });
return { return {
wrapper: cx( wrapper: cx(
styles.wrapper, styles.wrapper,
sharedInputStyle(theme, false), sharedInputStyle(theme, false),
focused && focused &&
css`
${focusCss(theme.v1)}
`,
disabled && styles.inputDisabled,
css` css`
position: relative; ${focusCss(theme.v1)}
box-sizing: border-box;
/* The display property is set by the styles prop in SelectBase because it's dependant on the width prop */
flex-direction: row;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
min-height: 32px;
height: auto;
max-width: 100%;
/* Input padding is applied to the InputControl so the menu is aligned correctly */
padding: 0;
cursor: ${disabled ? 'not-allowed' : 'pointer'};
`, `,
withPrefix && disabled && styles.inputDisabled,
css` css`
padding-left: 0; position: relative;
` box-sizing: border-box;
), /* The display property is set by the styles prop in SelectBase because it's dependant on the width prop */
}; flex-direction: row;
} flex-wrap: wrap;
); align-items: center;
justify-content: space-between;
min-height: 32px;
height: auto;
max-width: 100%;
/* Input padding is applied to the InputControl so the menu is aligned correctly */
padding: 0;
cursor: ${disabled ? 'not-allowed' : 'pointer'};
`
),
};
});

View File

@@ -1,12 +1,12 @@
import React from 'react'; import React from 'react';
import { css, cx } from '@emotion/css'; import { css, cx } from '@emotion/css';
import { components, SingleValueProps } from 'react-select'; import { components, GroupBase, SingleValueProps } from 'react-select';
import { useDelayedSwitch } from '../../utils/useDelayedSwitch'; import { useDelayedSwitch } from '../../utils/useDelayedSwitch';
import { useStyles2 } from '../../themes'; import { useStyles2 } from '../../themes';
import { SlideOutTransition } from '../transitions/SlideOutTransition'; import { SlideOutTransition } from '../transitions/SlideOutTransition';
import { FadeTransition } from '../transitions/FadeTransition'; import { FadeTransition } from '../transitions/FadeTransition';
import { Spinner } from '../Spinner/Spinner'; import { Spinner } from '../Spinner/Spinner';
import { GrafanaTheme2 } from '@grafana/data'; import { GrafanaTheme2, SelectableValue } from '@grafana/data';
import tinycolor from 'tinycolor2'; import tinycolor from 'tinycolor2';
const getStyles = (theme: GrafanaTheme2) => { const getStyles = (theme: GrafanaTheme2) => {
@@ -44,27 +44,23 @@ const getStyles = (theme: GrafanaTheme2) => {
type StylesType = ReturnType<typeof getStyles>; type StylesType = ReturnType<typeof getStyles>;
interface Props export type Props<T> = SingleValueProps<SelectableValue<T>, boolean, GroupBase<SelectableValue<T>>>;
extends SingleValueProps<{
imgUrl?: string;
label?: string;
value: string;
loading?: boolean;
hideText?: boolean;
}> {
disabled?: boolean;
}
export const SingleValue = (props: Props) => { export const SingleValue = <T extends unknown>(props: Props<T>) => {
const { children, data, disabled } = props; const { children, data, isDisabled } = props;
const styles = useStyles2(getStyles); const styles = useStyles2(getStyles);
const loading = useDelayedSwitch(data.loading || false, { delay: 250, duration: 750 }); const loading = useDelayedSwitch(data.loading || false, { delay: 250, duration: 750 });
return ( return (
<components.SingleValue {...props}> <components.SingleValue {...props}>
<div className={cx(styles.singleValue, disabled && styles.disabled)}> <div className={cx(styles.singleValue, isDisabled && styles.disabled)}>
{data.imgUrl ? ( {data.imgUrl ? (
<FadeWithImage loading={loading} imgUrl={data.imgUrl} styles={styles} alt={data.label || data.value} /> <FadeWithImage
loading={loading}
imgUrl={data.imgUrl}
styles={styles}
alt={(data.label || data.value) as string}
/>
) : ( ) : (
<SlideOutTransition horizontal size={16} visible={loading} duration={150}> <SlideOutTransition horizontal size={16} visible={loading} duration={150}>
<div className={styles.container}> <div className={styles.container}>
@@ -78,7 +74,7 @@ export const SingleValue = (props: Props) => {
); );
}; };
const FadeWithImage = (props: { loading: boolean; imgUrl: string; styles: StylesType; alt: string }) => { const FadeWithImage = (props: { loading: boolean; imgUrl: string; styles: StylesType; alt?: string }) => {
return ( return (
<div className={props.styles.container}> <div className={props.styles.container}>
<FadeTransition duration={150} visible={props.loading}> <FadeTransition duration={150} visible={props.loading}>

View File

@@ -1,6 +1,6 @@
import { SelectableValue } from '@grafana/data'; import { SelectableValue } from '@grafana/data';
import React from 'react'; import React from 'react';
import { ActionMeta as SelectActionMeta } from 'react-select'; import { ActionMeta as SelectActionMeta, GroupBase, OptionsOrGroups } from 'react-select';
export type SelectValue<T> = T | SelectableValue<T> | T[] | Array<SelectableValue<T>>; export type SelectValue<T> = T | SelectableValue<T> | T[] | Array<SelectableValue<T>>;
export type ActionMeta = SelectActionMeta<{}>; export type ActionMeta = SelectActionMeta<{}>;
@@ -77,7 +77,7 @@ export interface SelectCommonProps<T> {
isValidNewOption?: ( isValidNewOption?: (
inputValue: string, inputValue: string,
value: SelectableValue<T> | null, value: SelectableValue<T> | null,
options: Readonly<Array<SelectableValue<T>>> options: OptionsOrGroups<unknown, GroupBase<unknown>>
) => boolean; ) => boolean;
} }

View File

@@ -74,9 +74,9 @@ describe('ReadonlyFolderPicker', () => {
it('then query is passed correctly to getFoldersAsOptions', async () => { it('then query is passed correctly to getFoldersAsOptions', async () => {
const { getFoldersAsOptionsSpy, selectors } = await getTestContext(); const { getFoldersAsOptionsSpy, selectors } = await getTestContext();
expect(within(selectors.container.get()).getByRole('textbox')).toBeInTheDocument(); expect(within(selectors.container.get()).getByRole('combobox')).toBeInTheDocument();
getFoldersAsOptionsSpy.mockClear(); getFoldersAsOptionsSpy.mockClear();
userEvent.type(within(selectors.container.get()).getByRole('textbox'), 'A'); userEvent.type(within(selectors.container.get()).getByRole('combobox'), 'A');
await waitFor(() => expect(getFoldersAsOptionsSpy).toHaveBeenCalledTimes(1)); await waitFor(() => expect(getFoldersAsOptionsSpy).toHaveBeenCalledTimes(1));
expect(getFoldersAsOptionsSpy).toHaveBeenCalledWith({ expect(getFoldersAsOptionsSpy).toHaveBeenCalledWith({

View File

@@ -3,7 +3,7 @@ import { css, cx } from '@emotion/css';
import { useTheme, stylesFactory } from '@grafana/ui'; import { useTheme, stylesFactory } from '@grafana/ui';
import { GrafanaTheme } from '@grafana/data'; import { GrafanaTheme } from '@grafana/data';
import { OptionProps } from 'react-select/src/components/Option'; import { OptionProps } from 'react-select';
import { TagBadge } from './TagBadge'; import { TagBadge } from './TagBadge';
// https://github.com/JedWatson/react-select/issues/3038 // https://github.com/JedWatson/react-select/issues/3038

View File

@@ -49,7 +49,7 @@ const ui = {
sourceButton: byText('See source'), sourceButton: byText('See source'),
matcherInput: byTestId('search-query-input'), matcherInput: byTestId('search-query-input'),
groupByContainer: byTestId('group-by-container'), groupByContainer: byTestId('group-by-container'),
groupByInput: byRole('textbox', { name: /group by label keys/i }), groupByInput: byRole('combobox', { name: /group by label keys/i }),
clearButton: byRole('button', { name: 'Clear filters' }), clearButton: byRole('button', { name: 'Clear filters' }),
}; };

View File

@@ -258,7 +258,7 @@ describe('AmRoutes', () => {
await clickSelectOption(receiverSelect, 'critical'); await clickSelectOption(receiverSelect, 'critical');
const groupSelect = ui.groupSelect.get(); const groupSelect = ui.groupSelect.get();
userEvent.type(byRole('textbox').get(groupSelect), 'namespace{enter}'); userEvent.type(byRole('combobox').get(groupSelect), 'namespace{enter}');
// configure timing intervals // configure timing intervals
userEvent.click(byText('Timing options').get(rootRouteContainer)); userEvent.click(byText('Timing options').get(rootRouteContainer));
@@ -317,8 +317,8 @@ describe('AmRoutes', () => {
await clickSelectOption(receiverSelect, 'default'); await clickSelectOption(receiverSelect, 'default');
const groupSelect = ui.groupSelect.get(); const groupSelect = ui.groupSelect.get();
userEvent.type(byRole('textbox').get(groupSelect), 'severity{enter}'); userEvent.type(byRole('combobox').get(groupSelect), 'severity{enter}');
userEvent.type(byRole('textbox').get(groupSelect), 'namespace{enter}'); userEvent.type(byRole('combobox').get(groupSelect), 'namespace{enter}');
//save //save
userEvent.click(ui.saveButton.get(rootRouteContainer)); userEvent.click(ui.saveButton.get(rootRouteContainer));
@@ -531,14 +531,14 @@ describe('AmRoutes', () => {
}); });
const clickSelectOption = async (selectElement: HTMLElement, optionText: string): Promise<void> => { const clickSelectOption = async (selectElement: HTMLElement, optionText: string): Promise<void> => {
userEvent.click(byRole('textbox').get(selectElement)); userEvent.click(byRole('combobox').get(selectElement));
await selectOptionInTest(selectElement, optionText); await selectOptionInTest(selectElement, optionText);
}; };
const updateTiming = async (selectElement: HTMLElement, value: string, timeUnit: string): Promise<void> => { const updateTiming = async (selectElement: HTMLElement, value: string, timeUnit: string): Promise<void> => {
const inputs = byRole('textbox').queryAll(selectElement); const input = byRole('textbox').get(selectElement);
expect(inputs).toHaveLength(2); const select = byRole('combobox').get(selectElement);
userEvent.type(inputs[0], value); userEvent.type(input, value);
userEvent.click(inputs[1]); userEvent.click(select);
await selectOptionInTest(selectElement, timeUnit); await selectOptionInTest(selectElement, timeUnit);
}; };

View File

@@ -113,7 +113,7 @@ const ui = {
}; };
const clickSelectOption = async (selectElement: HTMLElement, optionText: string): Promise<void> => { const clickSelectOption = async (selectElement: HTMLElement, optionText: string): Promise<void> => {
userEvent.click(byRole('textbox').get(selectElement)); userEvent.click(byRole('combobox').get(selectElement));
await selectOptionInTest(selectElement, optionText); await selectOptionInTest(selectElement, optionText);
}; };

View File

@@ -129,7 +129,7 @@ describe('RuleEditor', () => {
userEvent.type(await ui.inputs.name.find(), 'my great new rule'); userEvent.type(await ui.inputs.name.find(), 'my great new rule');
await clickSelectOption(ui.inputs.alertType.get(), /Cortex\/Loki managed alert/); await clickSelectOption(ui.inputs.alertType.get(), /Cortex\/Loki managed alert/);
const dataSourceSelect = ui.inputs.dataSource.get(); const dataSourceSelect = ui.inputs.dataSource.get();
userEvent.click(byRole('textbox').get(dataSourceSelect)); userEvent.click(byRole('combobox').get(dataSourceSelect));
await clickSelectOption(dataSourceSelect, 'Prom (default)'); await clickSelectOption(dataSourceSelect, 'Prom (default)');
await waitFor(() => expect(mocks.api.fetchRulerRules).toHaveBeenCalled()); await waitFor(() => expect(mocks.api.fetchRulerRules).toHaveBeenCalled());
await clickSelectOption(ui.inputs.namespace.get(), 'namespace2'); await clickSelectOption(ui.inputs.namespace.get(), 'namespace2');
@@ -270,7 +270,7 @@ describe('RuleEditor', () => {
userEvent.type(await ui.inputs.name.find(), 'my great new recording rule'); userEvent.type(await ui.inputs.name.find(), 'my great new recording rule');
await clickSelectOption(ui.inputs.alertType.get(), /Cortex\/Loki managed recording rule/); await clickSelectOption(ui.inputs.alertType.get(), /Cortex\/Loki managed recording rule/);
const dataSourceSelect = ui.inputs.dataSource.get(); const dataSourceSelect = ui.inputs.dataSource.get();
userEvent.click(byRole('textbox').get(dataSourceSelect)); userEvent.click(byRole('combobox').get(dataSourceSelect));
await clickSelectOption(dataSourceSelect, 'Prom (default)'); await clickSelectOption(dataSourceSelect, 'Prom (default)');
await waitFor(() => expect(mocks.api.fetchRulerRules).toHaveBeenCalled()); await waitFor(() => expect(mocks.api.fetchRulerRules).toHaveBeenCalled());
await clickSelectOption(ui.inputs.namespace.get(), 'namespace2'); await clickSelectOption(ui.inputs.namespace.get(), 'namespace2');
@@ -496,7 +496,7 @@ describe('RuleEditor', () => {
// check that only rules sources that have ruler available are there // check that only rules sources that have ruler available are there
const dataSourceSelect = ui.inputs.dataSource.get(); const dataSourceSelect = ui.inputs.dataSource.get();
userEvent.click(byRole('textbox').get(dataSourceSelect)); userEvent.click(byRole('combobox').get(dataSourceSelect));
expect(await byText('loki with ruler').query()).toBeInTheDocument(); expect(await byText('loki with ruler').query()).toBeInTheDocument();
expect(byText('cortex with ruler').query()).toBeInTheDocument(); expect(byText('cortex with ruler').query()).toBeInTheDocument();
expect(byText('loki with local rule store').query()).not.toBeInTheDocument(); expect(byText('loki with local rule store').query()).not.toBeInTheDocument();
@@ -507,6 +507,6 @@ describe('RuleEditor', () => {
}); });
const clickSelectOption = async (selectElement: HTMLElement, optionText: Matcher): Promise<void> => { const clickSelectOption = async (selectElement: HTMLElement, optionText: Matcher): Promise<void> => {
userEvent.click(byRole('textbox').get(selectElement)); userEvent.click(byRole('combobox').get(selectElement));
await selectOptionInTest(selectElement, optionText as string); await selectOptionInTest(selectElement, optionText as string);
}; };

View File

@@ -50,7 +50,7 @@ describe('General Settings', () => {
const { props } = setupTestContext({}); const { props } = setupTestContext({});
userEvent.click(screen.getByTestId(selectors.components.TimeZonePicker.containerV2)); userEvent.click(screen.getByTestId(selectors.components.TimeZonePicker.containerV2));
const timeZonePicker = screen.getByTestId(selectors.components.TimeZonePicker.containerV2); const timeZonePicker = screen.getByTestId(selectors.components.TimeZonePicker.containerV2);
userEvent.click(byRole('textbox').get(timeZonePicker)); userEvent.click(byRole('combobox').get(timeZonePicker));
await selectOptionInTest(timeZonePicker, 'Browser Time'); await selectOptionInTest(timeZonePicker, 'Browser Time');
expect(props.updateTimeZone).toHaveBeenCalledWith('browser'); expect(props.updateTimeZone).toHaveBeenCalledWith('browser');
expect(props.dashboard.timezone).toBe('browser'); expect(props.dashboard.timezone).toBe('browser');

View File

@@ -57,7 +57,7 @@ describe('InspectDataTab', () => {
render(<InspectDataTab {...createProps()} />); render(<InspectDataTab {...createProps()} />);
const dataOptions = screen.getByText(/Data options/i); const dataOptions = screen.getByText(/Data options/i);
userEvent.click(dataOptions); userEvent.click(dataOptions);
const dataFrameInput = screen.getByRole('textbox', { name: /Select dataframe/i }); const dataFrameInput = screen.getByRole('combobox', { name: /Select dataframe/i });
userEvent.click(dataFrameInput); userEvent.click(dataFrameInput);
expect(screen.getByText(/Second data frame/i)).toBeInTheDocument(); expect(screen.getByText(/Second data frame/i)).toBeInTheDocument();
}); });

View File

@@ -141,7 +141,7 @@ describe('LibraryPanelsSearch', () => {
expect(screen.getByPlaceholderText(/search by name/i)).toBeInTheDocument(); expect(screen.getByPlaceholderText(/search by name/i)).toBeInTheDocument();
expect(screen.getByText(/no library panels found./i)).toBeInTheDocument(); expect(screen.getByText(/no library panels found./i)).toBeInTheDocument();
expect(screen.getByRole('textbox', { name: /panel type filter/i })).toBeInTheDocument(); expect(screen.getByRole('combobox', { name: /panel type filter/i })).toBeInTheDocument();
}); });
describe('and user changes panel filter', () => { describe('and user changes panel filter', () => {
@@ -149,8 +149,8 @@ describe('LibraryPanelsSearch', () => {
const { getLibraryPanelsSpy } = await getTestContext({ showPanelFilter: true }); const { getLibraryPanelsSpy } = await getTestContext({ showPanelFilter: true });
getLibraryPanelsSpy.mockClear(); getLibraryPanelsSpy.mockClear();
userEvent.type(screen.getByRole('textbox', { name: /panel type filter/i }), 'Graph{enter}'); userEvent.type(screen.getByRole('combobox', { name: /panel type filter/i }), 'Graph{enter}');
userEvent.type(screen.getByRole('textbox', { name: /panel type filter/i }), 'Time Series{enter}'); userEvent.type(screen.getByRole('combobox', { name: /panel type filter/i }), 'Time Series{enter}');
await waitFor(() => expect(getLibraryPanelsSpy).toHaveBeenCalledTimes(1)); await waitFor(() => expect(getLibraryPanelsSpy).toHaveBeenCalledTimes(1));
expect(getLibraryPanelsSpy).toHaveBeenCalledWith({ expect(getLibraryPanelsSpy).toHaveBeenCalledWith({
searchString: '', searchString: '',
@@ -169,7 +169,7 @@ describe('LibraryPanelsSearch', () => {
expect(screen.getByPlaceholderText(/search by name/i)).toBeInTheDocument(); expect(screen.getByPlaceholderText(/search by name/i)).toBeInTheDocument();
expect(screen.getByText(/no library panels found./i)).toBeInTheDocument(); expect(screen.getByText(/no library panels found./i)).toBeInTheDocument();
expect(screen.getByRole('textbox', { name: /folder filter/i })).toBeInTheDocument(); expect(screen.getByRole('combobox', { name: /folder filter/i })).toBeInTheDocument();
}); });
describe('and user changes folder filter', () => { describe('and user changes folder filter', () => {
@@ -177,8 +177,8 @@ describe('LibraryPanelsSearch', () => {
const { getLibraryPanelsSpy } = await getTestContext({ showFolderFilter: true }); const { getLibraryPanelsSpy } = await getTestContext({ showFolderFilter: true });
getLibraryPanelsSpy.mockClear(); getLibraryPanelsSpy.mockClear();
userEvent.click(screen.getByRole('textbox', { name: /folder filter/i })); userEvent.click(screen.getByRole('combobox', { name: /folder filter/i }));
userEvent.type(screen.getByRole('textbox', { name: /folder filter/i }), '{enter}', { userEvent.type(screen.getByRole('combobox', { name: /folder filter/i }), '{enter}', {
skipClick: true, skipClick: true,
}); });
await waitFor(() => expect(getLibraryPanelsSpy).toHaveBeenCalledTimes(1)); await waitFor(() => expect(getLibraryPanelsSpy).toHaveBeenCalledTimes(1));

View File

@@ -30,7 +30,6 @@ const filterTableName = 'Filters';
export const applyFilterFromTable = (options: AdHocTableOptions): ThunkResult<void> => { export const applyFilterFromTable = (options: AdHocTableOptions): ThunkResult<void> => {
return async (dispatch, getState) => { return async (dispatch, getState) => {
let variable = getVariableByOptions(options, getState()); let variable = getVariableByOptions(options, getState());
console.log('getVariableByOptions', options, getState().templating.variables);
if (!variable) { if (!variable) {
dispatch(createAdHocVariable(options)); dispatch(createAdHocVariable(options));

View File

@@ -7,7 +7,7 @@ import {
} from '@grafana/ui/src/components/Select/SelectContainer'; } from '@grafana/ui/src/components/Select/SelectContainer';
import { SelectCommonProps } from '@grafana/ui/src/components/Select/types'; import { SelectCommonProps } from '@grafana/ui/src/components/Select/types';
import React, { useState } from 'react'; import React, { useState } from 'react';
import { GroupTypeBase } from 'react-select'; import { GroupBase } from 'react-select';
interface InlineSelectProps<T> extends SelectCommonProps<T> { interface InlineSelectProps<T> extends SelectCommonProps<T> {
label?: string; label?: string;
@@ -38,7 +38,7 @@ function InlineSelect<T>({ label: labelProp, ...props }: InlineSelectProps<T>) {
export default InlineSelect; export default InlineSelect;
const SelectContainer = <Option, isMulti extends boolean, Group extends GroupTypeBase<Option>>( const SelectContainer = <Option, isMulti extends boolean, Group extends GroupBase<Option>>(
props: ContainerProps<Option, isMulti, Group> props: ContainerProps<Option, isMulti, Group>
) => { ) => {
const { children } = props; const { children } = props;
@@ -53,7 +53,7 @@ const SelectContainer = <Option, isMulti extends boolean, Group extends GroupTyp
); );
}; };
const ValueContainer = <Option, isMulti extends boolean, Group extends GroupTypeBase<Option>>( const ValueContainer = <Option, isMulti extends boolean, Group extends GroupBase<Option>>(
props: ContainerProps<Option, isMulti, Group> props: ContainerProps<Option, isMulti, Group>
) => { ) => {
const { className, children } = props; const { className, children } = props;

View File

@@ -1,4 +1,5 @@
import React from 'react'; import React from 'react';
import { GroupBase, OptionsOrGroups } from 'react-select';
import { InlineField, Input, Select, TimeZonePicker } from '@grafana/ui'; import { InlineField, Input, Select, TimeZonePicker } from '@grafana/ui';
import { DateHistogram } from '../aggregations'; import { DateHistogram } from '../aggregations';
import { bucketAggregationConfig } from '../utils'; import { bucketAggregationConfig } from '../utils';
@@ -25,11 +26,11 @@ const hasValue = (searchValue: string) => ({ value }: SelectableValue<string>) =
const isValidNewOption = ( const isValidNewOption = (
inputValue: string, inputValue: string,
_: SelectableValue<string> | null, _: SelectableValue<string> | null,
options: Readonly<Array<SelectableValue<string>>> options: OptionsOrGroups<unknown, GroupBase<unknown>>
) => { ) => {
// TODO: would be extremely nice here to allow only template variables and values that are // TODO: would be extremely nice here to allow only template variables and values that are
// valid date histogram's Interval options // valid date histogram's Interval options
const valueExists = options.some(hasValue(inputValue)); const valueExists = (options as Array<SelectableValue<string>>).some(hasValue(inputValue));
// we also don't want users to create "empty" values // we also don't want users to create "empty" values
return !valueExists && inputValue.trim().length > 0; return !valueExists && inputValue.trim().length > 0;
}; };

View File

@@ -1995,7 +1995,7 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@emotion/cache@npm:^11.0.0, @emotion/cache@npm:^11.1.3, @emotion/cache@npm:^11.5.0": "@emotion/cache@npm:^11.1.3, @emotion/cache@npm:^11.4.0, @emotion/cache@npm:^11.5.0":
version: 11.5.0 version: 11.5.0
resolution: "@emotion/cache@npm:11.5.0" resolution: "@emotion/cache@npm:11.5.0"
dependencies: dependencies:
@@ -2761,7 +2761,6 @@ __metadata:
"@types/react-color": 3.0.1 "@types/react-color": 3.0.1
"@types/react-dom": 17.0.11 "@types/react-dom": 17.0.11
"@types/react-router-dom": ^5.1.7 "@types/react-router-dom": ^5.1.7
"@types/react-select": 4.0.13
"@types/react-table": 7.7.2 "@types/react-table": 7.7.2
"@types/react-test-renderer": 17.0.1 "@types/react-test-renderer": 17.0.1
"@types/react-transition-group": 4.4.0 "@types/react-transition-group": 4.4.0
@@ -2821,7 +2820,7 @@ __metadata:
react-inlinesvg: 2.3.0 react-inlinesvg: 2.3.0
react-popper: 2.2.4 react-popper: 2.2.4
react-router-dom: ^5.2.0 react-router-dom: ^5.2.0
react-select: 4.3.0 react-select: 5.2.1
react-select-event: ^5.1.0 react-select-event: ^5.1.0
react-table: 7.7.0 react-table: 7.7.0
react-test-renderer: 17.0.1 react-test-renderer: 17.0.1
@@ -8378,18 +8377,6 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@types/react-select@npm:4.0.13":
version: 4.0.13
resolution: "@types/react-select@npm:4.0.13"
dependencies:
"@emotion/serialize": ^1.0.0
"@types/react": "*"
"@types/react-dom": "*"
"@types/react-transition-group": "*"
checksum: 1a3fdd93a33e8b3fd59ba32c4e952cf057e0f745a3506ac556172ceba4c849e33494dfba1553783f6f55d864a530869f33feefc55306b212146758d612117dba
languageName: node
linkType: hard
"@types/react-syntax-highlighter@npm:11.0.5": "@types/react-syntax-highlighter@npm:11.0.5":
version: 11.0.5 version: 11.0.5
resolution: "@types/react-syntax-highlighter@npm:11.0.5" resolution: "@types/react-syntax-highlighter@npm:11.0.5"
@@ -8417,15 +8404,6 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@types/react-transition-group@npm:*":
version: 4.4.4
resolution: "@types/react-transition-group@npm:4.4.4"
dependencies:
"@types/react": "*"
checksum: 86e9ff9731798e12bc2afe0304678918769633b531dcf6397f86af81718fb7930ef8648e894eeb3718fc6eab6eb885cfb9b82a44d1d74e10951ee11ebc4643ae
languageName: node
linkType: hard
"@types/react-transition-group@npm:4.4.0": "@types/react-transition-group@npm:4.4.0":
version: 4.4.0 version: 4.4.0
resolution: "@types/react-transition-group@npm:4.4.0" resolution: "@types/react-transition-group@npm:4.4.0"
@@ -8435,6 +8413,15 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@types/react-transition-group@npm:^4.4.0":
version: 4.4.4
resolution: "@types/react-transition-group@npm:4.4.4"
dependencies:
"@types/react": "*"
checksum: 86e9ff9731798e12bc2afe0304678918769633b531dcf6397f86af81718fb7930ef8648e894eeb3718fc6eab6eb885cfb9b82a44d1d74e10951ee11ebc4643ae
languageName: node
linkType: hard
"@types/react-virtualized-auto-sizer@npm:1.0.0": "@types/react-virtualized-auto-sizer@npm:1.0.0":
version: 1.0.0 version: 1.0.0
resolution: "@types/react-virtualized-auto-sizer@npm:1.0.0" resolution: "@types/react-virtualized-auto-sizer@npm:1.0.0"
@@ -17980,7 +17967,6 @@ __metadata:
"@types/react-loadable": 5.5.2 "@types/react-loadable": 5.5.2
"@types/react-redux": 7.1.20 "@types/react-redux": 7.1.20
"@types/react-router-dom": ^5.1.7 "@types/react-router-dom": ^5.1.7
"@types/react-select": 4.0.13
"@types/react-test-renderer": 17.0.1 "@types/react-test-renderer": 17.0.1
"@types/react-transition-group": 4.4.0 "@types/react-transition-group": 4.4.0
"@types/react-virtualized-auto-sizer": 1.0.0 "@types/react-virtualized-auto-sizer": 1.0.0
@@ -18135,7 +18121,7 @@ __metadata:
react-resizable: 3.0.4 react-resizable: 3.0.4
react-reverse-portal: ^2.0.1 react-reverse-portal: ^2.0.1
react-router-dom: ^5.2.0 react-router-dom: ^5.2.0
react-select: 4.3.0 react-select: 5.2.1
react-select-event: ^5.1.0 react-select-event: ^5.1.0
react-split-pane: 0.1.89 react-split-pane: 0.1.89
react-test-renderer: 17.0.1 react-test-renderer: 17.0.1
@@ -27678,21 +27664,21 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"react-select@npm:4.3.0": "react-select@npm:5.2.1":
version: 4.3.0 version: 5.2.1
resolution: "react-select@npm:4.3.0" resolution: "react-select@npm:5.2.1"
dependencies: dependencies:
"@babel/runtime": ^7.12.0 "@babel/runtime": ^7.12.0
"@emotion/cache": ^11.0.0 "@emotion/cache": ^11.4.0
"@emotion/react": ^11.1.1 "@emotion/react": ^11.1.1
"@types/react-transition-group": ^4.4.0
memoize-one: ^5.0.0 memoize-one: ^5.0.0
prop-types: ^15.6.0 prop-types: ^15.6.0
react-input-autosize: ^3.0.0
react-transition-group: ^4.3.0 react-transition-group: ^4.3.0
peerDependencies: peerDependencies:
react: ^16.8.0 || ^17.0.0 react: ^16.8.0 || ^17.0.0
react-dom: ^16.8.0 || ^17.0.0 react-dom: ^16.8.0 || ^17.0.0
checksum: 00946ed4dfa46523bc44cd26aab11431396632097207081b471d1965fa53cf7f5a006ac11d89b8a65df05cc5376b1c4438edb1d5259323bdc6d3adf01c79d53e checksum: 014cde5d23ca466795bd7f02455769efda74522c41f11109ec2d6f0e3c77f401ea91ae23cdccce997efb653aa6ea88688a16f40e59bfdef2a2414d0cfd10f374
languageName: node languageName: node
linkType: hard linkType: hard