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 (
|
return (
|
||||||
<>
|
<>
|
||||||
<Field
|
<Field
|
||||||
@ -309,6 +317,18 @@ const AsyncStory: StoryFn<PropsAndCustomArgs> = (args) => {
|
|||||||
/>
|
/>
|
||||||
</Field>
|
</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">
|
<Field label="Compared to AsyncSelect">
|
||||||
<AsyncSelect
|
<AsyncSelect
|
||||||
id="test-async-select"
|
id="test-async-select"
|
||||||
|
@ -274,5 +274,25 @@ describe('Combobox', () => {
|
|||||||
|
|
||||||
expect(customItem).toBeInTheDocument();
|
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 { Icon } from '../Icon/Icon';
|
||||||
import { AutoSizeInput } from '../Input/AutoSizeInput';
|
import { AutoSizeInput } from '../Input/AutoSizeInput';
|
||||||
import { Input, Props as InputProps } from '../Input/Input';
|
import { Input, Props as InputProps } from '../Input/Input';
|
||||||
|
import { Stack } from '../Layout/Stack/Stack';
|
||||||
|
import { Text } from '../Text/Text';
|
||||||
|
|
||||||
import { getComboboxStyles } from './getComboboxStyles';
|
import { getComboboxStyles } from './getComboboxStyles';
|
||||||
import { useComboboxFloat, OPTION_HEIGHT } from './useComboboxFloat';
|
import { useComboboxFloat, OPTION_HEIGHT } from './useComboboxFloat';
|
||||||
@ -94,6 +96,7 @@ export const Combobox = <T extends string | number>({
|
|||||||
const isAsync = typeof options === 'function';
|
const isAsync = typeof options === 'function';
|
||||||
const loadOptions = useLatestAsyncCall(isAsync ? options : asyncNoop); // loadOptions isn't called at all if not async
|
const loadOptions = useLatestAsyncCall(isAsync ? options : asyncNoop); // loadOptions isn't called at all if not async
|
||||||
const [asyncLoading, setAsyncLoading] = useState(false);
|
const [asyncLoading, setAsyncLoading] = useState(false);
|
||||||
|
const [asyncError, setAsyncError] = useState(false);
|
||||||
|
|
||||||
const [items, setItems] = useState(isAsync ? [] : options);
|
const [items, setItems] = useState(isAsync ? [] : options);
|
||||||
|
|
||||||
@ -143,10 +146,11 @@ export const Combobox = <T extends string | number>({
|
|||||||
.then((opts) => {
|
.then((opts) => {
|
||||||
setItems(customValueOption ? [customValueOption, ...opts] : opts);
|
setItems(customValueOption ? [customValueOption, ...opts] : opts);
|
||||||
setAsyncLoading(false);
|
setAsyncLoading(false);
|
||||||
|
setAsyncError(false);
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
if (!(err instanceof StaleResultError)) {
|
if (!(err instanceof StaleResultError)) {
|
||||||
// TODO: handle error
|
setAsyncError(true);
|
||||||
setAsyncLoading(false);
|
setAsyncLoading(false);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -217,12 +221,12 @@ export const Combobox = <T extends string | number>({
|
|||||||
.then((options) => {
|
.then((options) => {
|
||||||
setItems(options);
|
setItems(options);
|
||||||
setAsyncLoading(false);
|
setAsyncLoading(false);
|
||||||
|
setAsyncError(false);
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
if (!(err instanceof StaleResultError)) {
|
if (!(err instanceof StaleResultError)) {
|
||||||
// TODO: handle error
|
setAsyncError(true);
|
||||||
setAsyncLoading(false);
|
setAsyncLoading(false);
|
||||||
throw err;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
@ -307,7 +311,7 @@ export const Combobox = <T extends string | number>({
|
|||||||
'aria-labelledby': ariaLabelledBy,
|
'aria-labelledby': ariaLabelledBy,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
{isOpen && (
|
{isOpen && !asyncError && (
|
||||||
<ul style={{ height: rowVirtualizer.getTotalSize() }} className={styles.menuUlContainer}>
|
<ul style={{ height: rowVirtualizer.getTotalSize() }} className={styles.menuUlContainer}>
|
||||||
{rowVirtualizer.getVirtualItems().map((virtualRow) => {
|
{rowVirtualizer.getVirtualItems().map((virtualRow) => {
|
||||||
return (
|
return (
|
||||||
@ -341,6 +345,12 @@ export const Combobox = <T extends string | number>({
|
|||||||
})}
|
})}
|
||||||
</ul>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -98,5 +98,9 @@ export const getComboboxStyles = (theme: GrafanaTheme2) => {
|
|||||||
color: theme.colors.text.primary,
|
color: theme.colors.text.primary,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
warningIcon: css({
|
||||||
|
label: 'grafana-select-warning-icon',
|
||||||
|
color: theme.colors.text.secondary,
|
||||||
|
}),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -448,6 +448,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"combobox": {
|
"combobox": {
|
||||||
|
"async": {
|
||||||
|
"error": "An error occurred while loading options."
|
||||||
|
},
|
||||||
"clear": {
|
"clear": {
|
||||||
"title": "Clear value"
|
"title": "Clear value"
|
||||||
},
|
},
|
||||||
|
@ -448,6 +448,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"combobox": {
|
"combobox": {
|
||||||
|
"async": {
|
||||||
|
"error": "Åʼn ęřřőř őččūřřęđ ŵĥįľę ľőäđįʼnģ őpŧįőʼnş."
|
||||||
|
},
|
||||||
"clear": {
|
"clear": {
|
||||||
"title": "Cľęäř väľūę"
|
"title": "Cľęäř väľūę"
|
||||||
},
|
},
|
||||||
|
Loading…
Reference in New Issue
Block a user