diff --git a/.changeset/calm-jeans-ring.md b/.changeset/calm-jeans-ring.md new file mode 100644 index 0000000000..3d1efcf7bd --- /dev/null +++ b/.changeset/calm-jeans-ring.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder': minor +--- + +Use virtualization with `EntityPicker` as done earlier with `MultiEntityPicker` to fix performance issues with large data sets. `VirtualizedListbox` extracted into reusable component. diff --git a/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.tsx b/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.tsx index 176f6e9ce0..9eca3e85c5 100644 --- a/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.tsx +++ b/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.tsx @@ -43,6 +43,7 @@ import { EntityPickerUiOptions, EntityPickerFilterQuery, } from './schema'; +import { VirtualizedListbox } from '../VirtualizedListbox'; export { EntityPickerSchema } from './schema'; @@ -205,6 +206,7 @@ export const EntityPicker = (props: EntityPickerProps) => { entities?.entityRefToPresentation.get(stringifyEntityRef(option)) ?.primaryTitle!, })} + ListboxComponent={VirtualizedListbox} /> ); diff --git a/plugins/scaffolder/src/components/fields/MultiEntityPicker/MultiEntityPicker.tsx b/plugins/scaffolder/src/components/fields/MultiEntityPicker/MultiEntityPicker.tsx index 84a050622a..d800c9b372 100644 --- a/plugins/scaffolder/src/components/fields/MultiEntityPicker/MultiEntityPicker.tsx +++ b/plugins/scaffolder/src/components/fields/MultiEntityPicker/MultiEntityPicker.tsx @@ -35,7 +35,6 @@ import Autocomplete, { AutocompleteChangeReason, } from '@material-ui/lab/Autocomplete'; import React, { useCallback, useEffect } from 'react'; -import { FixedSizeList, ListChildComponentProps } from 'react-window'; import useAsync from 'react-use/esm/useAsync'; import { FieldValidation } from '@rjsf/utils'; import { @@ -44,41 +43,10 @@ import { MultiEntityPickerUiOptions, MultiEntityPickerFilterQuery, } from './schema'; +import { VirtualizedListbox } from '../VirtualizedListbox'; export { MultiEntityPickerSchema } from './schema'; -const renderRow = (props: ListChildComponentProps) => { - const { data, index, style } = props; - return React.cloneElement(data[index], { style }); -}; - -const ListboxComponent = React.forwardRef< - HTMLDivElement, - { children?: React.ReactNode } ->((props, ref) => { - const itemData = React.Children.toArray(props.children); - const itemCount = itemData.length; - - const itemSize = 36; - - const itemsToShow = Math.min(10, itemCount); - const height = Math.max(itemSize, itemsToShow * itemSize - 0.5 * itemSize); - - return ( -
- - {renderRow} - -
- ); -}); - /** * The underlying component that is rendered in the form for the `MultiEntityPicker` * field extension. @@ -210,7 +178,7 @@ export const MultiEntityPicker = (props: MultiEntityPickerProps) => { }} /> )} - ListboxComponent={ListboxComponent} + ListboxComponent={VirtualizedListbox} /> ); diff --git a/plugins/scaffolder/src/components/fields/VirtualizedListbox.tsx b/plugins/scaffolder/src/components/fields/VirtualizedListbox.tsx new file mode 100644 index 0000000000..b0920b626a --- /dev/null +++ b/plugins/scaffolder/src/components/fields/VirtualizedListbox.tsx @@ -0,0 +1,50 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React from 'react'; +import { FixedSizeList, ListChildComponentProps } from 'react-window'; + +const renderRow = (props: ListChildComponentProps) => { + const { data, index, style } = props; + return React.cloneElement(data[index], { style }); +}; + +export const VirtualizedListbox = React.forwardRef< + HTMLDivElement, + { children?: React.ReactNode } +>((props, ref) => { + const itemData = React.Children.toArray(props.children); + const itemCount = itemData.length; + + const itemSize = 36; + + const itemsToShow = Math.min(10, itemCount); + const height = Math.max(itemSize, itemsToShow * itemSize - 0.5 * itemSize); + + return ( +
+ + {renderRow} + +
+ ); +});