Reuse the EntityPicker inside the OwnerPicker

Signed-off-by: Oliver Sand <oliver.sand@sda-se.com>
This commit is contained in:
Oliver Sand
2021-05-19 17:47:09 +02:00
parent d9f7774ec4
commit 221ddf1d90
9 changed files with 53 additions and 105 deletions
@@ -35,12 +35,12 @@ import StarIcon from '@material-ui/icons/Star';
import React, { useEffect, useMemo, useState } from 'react';
import { Link as RouterLink } from 'react-router-dom';
import { EntityFilterGroupsProvider, useFilteredEntities } from '../../filter';
import { registerComponentRouteRef } from '../../routes';
import { ResultsFilter } from '../ResultsFilter/ResultsFilter';
import { ScaffolderFilter } from '../ScaffolderFilter';
import { ButtonGroup } from '../ScaffolderFilter/ScaffolderFilter';
import SearchToolbar from '../SearchToolbar/SearchToolbar';
import { TemplateCard } from '../TemplateCard';
import { registerComponentRouteRef } from '../../routes';
const useStyles = makeStyles(theme => ({
contentWrapper: {
@@ -174,7 +174,7 @@ export const ScaffolderPageContents = () => {
<ItemCardGrid>
{matchingEntities &&
matchingEntities?.length > 0 &&
matchingEntities.map(template => (
matchingEntities.map((template, i) => (
<TemplateCard
key={i}
template={template}
@@ -21,18 +21,18 @@ import {
import { TextField } from '@material-ui/core';
import FormControl from '@material-ui/core/FormControl';
import Autocomplete from '@material-ui/lab/Autocomplete';
import { Field } from '@rjsf/core';
import { FieldProps } from '@rjsf/core';
import React from 'react';
import { useAsync } from 'react-use';
export const EntityPicker: Field = ({
export const EntityPicker = ({
onChange,
schema: { title = 'Entity', description = 'An entity from the catalog' },
required,
uiSchema,
rawErrors,
formData,
}) => {
}: FieldProps<string>) => {
const allowedKinds = uiSchema['ui:options']?.allowedKinds as string[];
const defaultKind = uiSchema['ui:options']?.defaultKind as string | undefined;
const catalogApi = useApi(catalogApiRef);
@@ -13,4 +13,4 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export * from './EntityPicker';
export { EntityPicker } from './EntityPicker';
@@ -14,14 +14,12 @@
* limitations under the License.
*/
import React from 'react';
import { Entity } from '@backstage/catalog-model';
import { ApiProvider, ApiRegistry } from '@backstage/core';
import { CatalogApi, catalogApiRef } from '@backstage/plugin-catalog-react';
import { renderInTestApp } from '@backstage/test-utils';
import userEvent from '@testing-library/user-event';
import { Entity } from '@backstage/catalog-model';
import { FieldProps } from '@rjsf/core';
import React from 'react';
import { OwnerPicker } from './OwnerPicker';
const makeEntity = (kind: string, namespace: string, name: string): Entity => ({
@@ -93,20 +91,6 @@ describe('<OwnerPicker />', () => {
},
});
});
it('updates even if there is not an exact match', async () => {
const { getByLabelText } = await renderInTestApp(
<Wrapper>
<OwnerPicker {...props} />
</Wrapper>,
);
const input = getByLabelText('Owner');
userEvent.type(input, 'squ');
input.blur();
expect(onChange).toHaveBeenCalledWith('squ');
});
});
describe('with allowedKinds', () => {
@@ -13,81 +13,31 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from 'react';
import { FieldProps } from '@rjsf/core';
import { catalogApiRef } from '@backstage/plugin-catalog-react';
import { useApi } from '@backstage/core';
import { useAsync } from 'react-use';
import Autocomplete from '@material-ui/lab/Autocomplete';
import FormControl from '@material-ui/core/FormControl';
import { Entity } from '@backstage/catalog-model';
import { TextField } from '@material-ui/core';
const entityRef = (entity: Entity | undefined): string => {
if (!entity) {
return '';
}
const {
kind,
metadata: { namespace, name },
} = entity;
const namespacePart =
!namespace || namespace === 'default' ? '' : `${namespace}/`;
const kindPart = kind.toLowerCase() === 'group' ? '' : `${kind}:`;
return `${kindPart}${namespacePart}${name}`;
};
import React from 'react';
import { EntityPicker } from '../EntityPicker';
export const OwnerPicker = ({
onChange,
schema: { title = 'Owner', description = 'The owner of the component' },
required,
uiSchema,
rawErrors,
formData,
...props
}: FieldProps<string>) => {
const allowedKinds = (uiSchema['ui:options']?.allowedKinds || [
'Group',
'User',
]) as string[];
const catalogApi = useApi(catalogApiRef);
const { value: owners, loading } = useAsync(() =>
catalogApi.getEntities({ filter: { kind: allowedKinds } }),
);
const ownerRefs = owners?.items.map(entityRef);
const onSelect = (_: any, value: string | null) => {
onChange(value || '');
const ownerUiSchema = {
...uiSchema,
'ui:options': {
allowedKinds: (uiSchema['ui:options']?.allowedKinds || [
'Group',
'User',
]) as string[],
defaultKind: 'Group',
},
};
return (
<FormControl
margin="normal"
required={required}
error={rawErrors?.length > 0 && !formData}
>
<Autocomplete
value={(formData as string) || ''}
loading={loading}
onChange={onSelect}
options={ownerRefs || []}
autoSelect
freeSolo
renderInput={params => (
<TextField
{...params}
label={title}
margin="normal"
helperText={description}
variant="outlined"
required={required}
InputProps={params.InputProps}
/>
)}
/>
</FormControl>
<EntityPicker
{...props}
schema={{ title, description }}
uiSchema={ownerUiSchema}
/>
);
};
@@ -13,4 +13,4 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export * from './OwnerPicker';
export { OwnerPicker } from './OwnerPicker';
+6 -1
View File
@@ -13,14 +13,19 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { EntityPicker } from '../components/fields/EntityPicker';
import { OwnerPicker } from '../components/fields/OwnerPicker';
import {
RepoUrlPicker,
repoPickerValidation,
RepoUrlPicker,
} from '../components/fields/RepoUrlPicker';
import { FieldExtensionOptions } from './types';
export const DEFAULT_SCAFFOLDER_FIELD_EXTENSIONS: FieldExtensionOptions[] = [
{
component: EntityPicker,
name: 'EntityPicker',
},
{
component: RepoUrlPicker,
name: 'RepoUrlPicker',
+7 -6
View File
@@ -14,13 +14,14 @@
* limitations under the License.
*/
export { scaffolderApiRef, ScaffolderClient } from './api';
export type { ScaffolderApi } from './api';
export { ScaffolderFieldExtensions } from './extensions';
export {
scaffolderPlugin,
scaffolderPlugin as plugin,
ScaffolderPage,
EntityPickerFieldExtension,
OwnerPickerFieldExtension,
RepoUrlPickerFieldExtension,
ScaffolderPage,
scaffolderPlugin as plugin,
scaffolderPlugin,
} from './plugin';
export { ScaffolderFieldExtensions } from './extensions';
export type { ScaffolderApi } from './api';
export { ScaffolderClient, scaffolderApiRef } from './api';
+14 -6
View File
@@ -21,15 +21,16 @@ import {
discoveryApiRef,
identityApiRef,
} from '@backstage/core';
import { OwnerPicker } from './components/fields/OwnerPicker';
import {
RepoUrlPicker,
repoPickerValidation,
} from './components/fields/RepoUrlPicker';
import { scmIntegrationsApiRef } from '@backstage/integration-react';
import { scaffolderApiRef, ScaffolderClient } from './api';
import { EntityPicker } from './components/fields/EntityPicker';
import { OwnerPicker } from './components/fields/OwnerPicker';
import {
repoPickerValidation,
RepoUrlPicker,
} from './components/fields/RepoUrlPicker';
import { createScaffolderFieldExtension } from './extensions';
import { rootRouteRef, registerComponentRouteRef } from './routes';
import { registerComponentRouteRef, rootRouteRef } from './routes';
export const scaffolderPlugin = createPlugin({
id: 'scaffolder',
@@ -53,6 +54,13 @@ export const scaffolderPlugin = createPlugin({
},
});
export const EntityPickerFieldExtension = scaffolderPlugin.provide(
createScaffolderFieldExtension({
component: EntityPicker,
name: 'EntityPicker',
}),
);
export const RepoUrlPickerFieldExtension = scaffolderPlugin.provide(
createScaffolderFieldExtension({
component: RepoUrlPicker,