Combobox: Handle error if async fails (#95740)

eleijonmarck/lbac-for-datasources/remove-sa-check-for-recommendations
Joao Silva 7 months ago committed by GitHub
parent 9d937725ad
commit afcd620d21
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 20
      packages/grafana-ui/src/components/Combobox/Combobox.story.tsx
  2. 20
      packages/grafana-ui/src/components/Combobox/Combobox.test.tsx
  3. 18
      packages/grafana-ui/src/components/Combobox/Combobox.tsx
  4. 4
      packages/grafana-ui/src/components/Combobox/getComboboxStyles.ts
  5. 3
      public/locales/en-US/grafana.json
  6. 3
      public/locales/pseudo-LOCALE/grafana.json

@ -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…
Cancel
Save