mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Combobox: Handle error if async fails (#95740)
This commit is contained in:
parent
9d937725ad
commit
afcd620d21
@ -273,6 +273,14 @@ const AsyncStory: StoryFn<PropsAndCustomArgs> = (args) => {
|
||||
);
|
||||
}, []);
|
||||
|
||||
const loadOptionsWithErrors = useCallback((inputValue: string) => {
|
||||
if (inputValue.length % 2 === 0) {
|
||||
return fakeSearchAPI(`http://example.com/search?query=${inputValue}`);
|
||||
} else {
|
||||
throw new Error('Could not retrieve options');
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Field
|
||||
@ -309,6 +317,18 @@ const AsyncStory: StoryFn<PropsAndCustomArgs> = (args) => {
|
||||
/>
|
||||
</Field>
|
||||
|
||||
<Field label="Async with error" description="An odd number of characters throws an error">
|
||||
<Combobox
|
||||
id="test-combobox-error"
|
||||
placeholder="Select an option"
|
||||
options={loadOptionsWithErrors}
|
||||
value={selectedOption}
|
||||
onChange={(val) => {
|
||||
action('onChange')(val);
|
||||
setSelectedOption(val);
|
||||
}}
|
||||
/>
|
||||
</Field>
|
||||
<Field label="Compared to AsyncSelect">
|
||||
<AsyncSelect
|
||||
id="test-async-select"
|
||||
|
@ -274,5 +274,25 @@ describe('Combobox', () => {
|
||||
|
||||
expect(customItem).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should display message when there is an error loading async options', async () => {
|
||||
const asyncOptions = jest.fn(() => {
|
||||
throw new Error('Could not retrieve options');
|
||||
});
|
||||
|
||||
render(<Combobox options={asyncOptions} value={null} onChange={onChangeHandler} />);
|
||||
|
||||
const input = screen.getByRole('combobox');
|
||||
await user.click(input);
|
||||
await user.type(input, 'test');
|
||||
|
||||
await act(async () => {
|
||||
jest.advanceTimersToNextTimer();
|
||||
});
|
||||
|
||||
const emptyMessage = screen.queryByText('An error occurred while loading options.');
|
||||
|
||||
expect(emptyMessage).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -9,6 +9,8 @@ import { t } from '../../utils/i18n';
|
||||
import { Icon } from '../Icon/Icon';
|
||||
import { AutoSizeInput } from '../Input/AutoSizeInput';
|
||||
import { Input, Props as InputProps } from '../Input/Input';
|
||||
import { Stack } from '../Layout/Stack/Stack';
|
||||
import { Text } from '../Text/Text';
|
||||
|
||||
import { getComboboxStyles } from './getComboboxStyles';
|
||||
import { useComboboxFloat, OPTION_HEIGHT } from './useComboboxFloat';
|
||||
@ -94,6 +96,7 @@ export const Combobox = <T extends string | number>({
|
||||
const isAsync = typeof options === 'function';
|
||||
const loadOptions = useLatestAsyncCall(isAsync ? options : asyncNoop); // loadOptions isn't called at all if not async
|
||||
const [asyncLoading, setAsyncLoading] = useState(false);
|
||||
const [asyncError, setAsyncError] = useState(false);
|
||||
|
||||
const [items, setItems] = useState(isAsync ? [] : options);
|
||||
|
||||
@ -143,10 +146,11 @@ export const Combobox = <T extends string | number>({
|
||||
.then((opts) => {
|
||||
setItems(customValueOption ? [customValueOption, ...opts] : opts);
|
||||
setAsyncLoading(false);
|
||||
setAsyncError(false);
|
||||
})
|
||||
.catch((err) => {
|
||||
if (!(err instanceof StaleResultError)) {
|
||||
// TODO: handle error
|
||||
setAsyncError(true);
|
||||
setAsyncLoading(false);
|
||||
}
|
||||
});
|
||||
@ -217,12 +221,12 @@ export const Combobox = <T extends string | number>({
|
||||
.then((options) => {
|
||||
setItems(options);
|
||||
setAsyncLoading(false);
|
||||
setAsyncError(false);
|
||||
})
|
||||
.catch((err) => {
|
||||
if (!(err instanceof StaleResultError)) {
|
||||
// TODO: handle error
|
||||
setAsyncError(true);
|
||||
setAsyncLoading(false);
|
||||
throw err;
|
||||
}
|
||||
});
|
||||
return;
|
||||
@ -307,7 +311,7 @@ export const Combobox = <T extends string | number>({
|
||||
'aria-labelledby': ariaLabelledBy,
|
||||
})}
|
||||
>
|
||||
{isOpen && (
|
||||
{isOpen && !asyncError && (
|
||||
<ul style={{ height: rowVirtualizer.getTotalSize() }} className={styles.menuUlContainer}>
|
||||
{rowVirtualizer.getVirtualItems().map((virtualRow) => {
|
||||
return (
|
||||
@ -341,6 +345,12 @@ export const Combobox = <T extends string | number>({
|
||||
})}
|
||||
</ul>
|
||||
)}
|
||||
{asyncError && (
|
||||
<Stack justifyContent="center" alignItems="center" height={8}>
|
||||
<Icon name="exclamation-triangle" size="md" className={styles.warningIcon} />
|
||||
<Text color="secondary">{t('combobox.async.error', 'An error occurred while loading options.')}</Text>
|
||||
</Stack>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -98,5 +98,9 @@ export const getComboboxStyles = (theme: GrafanaTheme2) => {
|
||||
color: theme.colors.text.primary,
|
||||
},
|
||||
}),
|
||||
warningIcon: css({
|
||||
label: 'grafana-select-warning-icon',
|
||||
color: theme.colors.text.secondary,
|
||||
}),
|
||||
};
|
||||
};
|
||||
|
@ -448,6 +448,9 @@
|
||||
}
|
||||
},
|
||||
"combobox": {
|
||||
"async": {
|
||||
"error": "An error occurred while loading options."
|
||||
},
|
||||
"clear": {
|
||||
"title": "Clear value"
|
||||
},
|
||||
|
@ -448,6 +448,9 @@
|
||||
}
|
||||
},
|
||||
"combobox": {
|
||||
"async": {
|
||||
"error": "Åʼn ęřřőř őččūřřęđ ŵĥįľę ľőäđįʼnģ őpŧįőʼnş."
|
||||
},
|
||||
"clear": {
|
||||
"title": "Cľęäř väľūę"
|
||||
},
|
||||
|
Loading…
Reference in New Issue
Block a user