diff --git a/packages/grafana-ui/src/components/Select/Select.tsx b/packages/grafana-ui/src/components/Select/Select.tsx index ccdd24b8180..8c0ed3d2c9f 100644 --- a/packages/grafana-ui/src/components/Select/Select.tsx +++ b/packages/grafana-ui/src/components/Select/Select.tsx @@ -4,7 +4,13 @@ import { SelectableValue } from '@grafana/data'; import { SelectBase } from './SelectBase'; import { SelectContainer, SelectContainerProps } from './SelectContainer'; -import { SelectCommonProps, MultiSelectCommonProps, SelectAsyncProps, VirtualizedSelectProps } from './types'; +import { + SelectCommonProps, + MultiSelectCommonProps, + SelectAsyncProps, + VirtualizedSelectProps, + VirtualizedSelectAsyncProps, +} from './types'; export function Select(props: SelectCommonProps) { return ; @@ -28,6 +34,10 @@ export function VirtualizedSelect(props: VirtualizedSelectProps) { return ; } +export function AsyncVirtualizedSelect(props: VirtualizedSelectAsyncProps) { + return ; +} + interface AsyncMultiSelectProps extends Omit, 'options'>, SelectAsyncProps { // AsyncSelect has options stored internally. We cannot enable plain values as we don't have access to the fetched options value?: Array>; diff --git a/packages/grafana-ui/src/components/Select/types.ts b/packages/grafana-ui/src/components/Select/types.ts index 2747ae597cb..3e6d5c4fbfe 100644 --- a/packages/grafana-ui/src/components/Select/types.ts +++ b/packages/grafana-ui/src/components/Select/types.ts @@ -112,6 +112,11 @@ export interface VirtualizedSelectProps extends Omit, 'v options?: Array, 'label' | 'value'>>; } +/** The AsyncVirtualizedSelect component uses a slightly different SelectableValue, description and other props are not supported */ +export interface VirtualizedSelectAsyncProps + extends Omit, 'virtualized'>, + SelectAsyncProps {} + export interface MultiSelectCommonProps extends Omit, 'onChange' | 'isMulti' | 'value'> { value?: Array> | T[]; onChange: (item: Array>) => {} | void; diff --git a/public/app/core/components/Select/FolderPicker.tsx b/public/app/core/components/Select/FolderPicker.tsx index 6087f482732..dc615f0f326 100644 --- a/public/app/core/components/Select/FolderPicker.tsx +++ b/public/app/core/components/Select/FolderPicker.tsx @@ -5,7 +5,7 @@ import { useAsync } from 'react-use'; import { AppEvents, SelectableValue, GrafanaTheme2 } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; -import { useStyles2, ActionMeta, AsyncSelect, Input, InputActionMeta } from '@grafana/ui'; +import { useStyles2, ActionMeta, Input, InputActionMeta, AsyncVirtualizedSelect } from '@grafana/ui'; import appEvents from 'app/core/app_events'; import { t } from 'app/core/internationalization'; import { contextSrv } from 'app/core/services/context_srv'; @@ -90,7 +90,8 @@ export function FolderPicker(props: Props) { const getOptions = useCallback( async (query: string) => { const searchHits = await searchFolders(query, permissionLevel, accessControlMetadata); - const options: Array> = mapSearchHitsToOptions(searchHits, filter); + const resultsAfterMapAndFilter = mapSearchHitsToOptions(searchHits, filter); + const options: Array> = resultsAfterMapAndFilter; const hasAccess = contextSrv.hasAccess(AccessControlAction.DashboardsWrite, contextSrv.isEditor) || @@ -322,7 +323,7 @@ export function FolderPicker(props: Props) { return (
- { const groupOptions = useMemo(() => { const groupsForFolderResult: Array> = groupfoldersForGrafana @@ -104,22 +106,20 @@ export function FolderAndGroup({ initialFolder }: FolderAndGroupProps) { formState: { errors }, watch, control, - setValue, } = useFormContext(); const styles = useStyles2(getStyles); const dispatch = useDispatch(); const folderFilter = useRuleFolderFilter(initialFolder); - const [isAddingGroup, setIsAddingGroup] = useState(false); const folder = watch('folder'); const group = watch('group'); - const [selectedGroup, setSelectedGroup] = useState(group); + const [selectedGroup, setSelectedGroup] = useState>({ label: group, title: group }); const initialRender = useRef(true); const { groupOptions, loading } = useGetGroupOptionsFromFolder(folder?.title ?? ''); - useEffect(() => setSelectedGroup(group), [group, setSelectedGroup]); + useEffect(() => setSelectedGroup({ label: group, title: group }), [group, setSelectedGroup]); useEffect(() => { dispatch(fetchRulerRulesIfNotFetchedYet(GRAFANA_RULES_SOURCE_NAME)); @@ -127,21 +127,37 @@ export function FolderAndGroup({ initialFolder }: FolderAndGroupProps) { const resetGroup = useCallback(() => { if (group && !initialRender.current && folder?.title) { - setSelectedGroup(''); + setSelectedGroup({ label: '', title: '' }); } initialRender.current = false; }, [group, folder?.title]); - useEffect(() => { - setValue('group', selectedGroup); - }, [selectedGroup, setValue]); - const groupIsInGroupOptions = useCallback( (group_: string) => { return groupOptions.includes((groupInList: SelectableValue) => groupInList.label === group_); }, [groupOptions] ); + const sliceResults = (list: Array>) => list.slice(0, SLICE_GROUP_RESULTS_TO); + + const getOptions = useCallback( + async (query: string) => { + const results = query + ? sliceResults( + groupOptions.filter((el) => { + const label = el.label ?? ''; + return label.toLowerCase().includes(query.toLowerCase()); + }) + ) + : sliceResults(groupOptions); + return results; + }, + [groupOptions] + ); + + const debouncedSearch = useMemo(() => { + return debounce(getOptions, 300, { leading: true }); + }, [getOptions]); return (
@@ -171,11 +187,9 @@ export function FolderAndGroup({ initialFolder }: FolderAndGroupProps) { enableCreateNew={contextSrv.hasPermission(AccessControlAction.FoldersCreate)} enableReset={true} filter={folderFilter} - dissalowSlashes={true} onChange={({ title, uid }) => { field.onChange({ title, uid }); - if (!groupIsInGroupOptions(selectedGroup)) { - setIsAddingGroup(false); + if (!groupIsInGroupOptions(selectedGroup.value ?? '')) { resetGroup(); } }} @@ -204,20 +218,23 @@ export function FolderAndGroup({ initialFolder }: FolderAndGroupProps) { loading ? ( ) : ( - ) => `${option.label}`} - value={selectedGroup} - custom={isAddingGroup} - onCustomChange={(custom: boolean) => setIsAddingGroup(custom)} - placeholder={isAddingGroup ? 'New evaluation group name' : 'Evaluation group name'} - onChange={(value: string) => { - field.onChange(value); - setSelectedGroup(value); + placeholder={'Evaluation group name'} + onChange={(value) => { + field.onChange(value.label ?? ''); }} + value={selectedGroup} + allowCustomValue + formatCreateLabel={(_) => '+ Add new '} /> ) } diff --git a/public/app/features/alerting/unified/components/rule-editor/RuleFolderPicker.tsx b/public/app/features/alerting/unified/components/rule-editor/RuleFolderPicker.tsx index 1d9e825bab4..51e0c24c2c6 100644 --- a/public/app/features/alerting/unified/components/rule-editor/RuleFolderPicker.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/RuleFolderPicker.tsx @@ -16,7 +16,6 @@ export interface Folder { export interface RuleFolderPickerProps extends Omit { value?: Folder; - dissalowSlashes: boolean; } const SlashesWarning = () => { diff --git a/public/app/features/manage-dashboards/state/actions.ts b/public/app/features/manage-dashboards/state/actions.ts index 640ab53a8f0..0969bad4959 100644 --- a/public/app/features/manage-dashboards/state/actions.ts +++ b/public/app/features/manage-dashboards/state/actions.ts @@ -286,6 +286,8 @@ export function createFolder(payload: any) { return getBackendSrv().post('/api/folders', payload); } +export const SLICE_FOLDER_RESULTS_TO = 1000; + export function searchFolders( query: any, permission?: PermissionLevelString, @@ -296,6 +298,7 @@ export function searchFolders( type: 'dash-folder', permission, accesscontrol: withAccessControl, + limit: SLICE_FOLDER_RESULTS_TO, }); }