diff --git a/plugins/scaffolder/src/components/fields/OwnershipEntityRefPicker/OwnershipEntityRefPicker.test.tsx b/plugins/scaffolder/src/components/fields/OwnershipEntityRefPicker/OwnershipEntityRefPicker.test.tsx
index 81bbb21438..40e7b09d8f 100644
--- a/plugins/scaffolder/src/components/fields/OwnershipEntityRefPicker/OwnershipEntityRefPicker.test.tsx
+++ b/plugins/scaffolder/src/components/fields/OwnershipEntityRefPicker/OwnershipEntityRefPicker.test.tsx
@@ -22,7 +22,12 @@ import { OwnershipEntityRefPicker } from './OwnershipEntityRefPicker';
import { TestApiProvider } from '@backstage/test-utils';
import { catalogApiRef } from '@backstage/plugin-catalog-react';
import { Entity } from '@backstage/catalog-model';
-import { IdentityApi, identityApiRef } from '@backstage/core-plugin-api';
+import {
+ ErrorApi,
+ IdentityApi,
+ errorApiRef,
+ identityApiRef,
+} from '@backstage/core-plugin-api';
import userEvent from '@testing-library/user-event';
// Create a mock IdentityApi
@@ -52,9 +57,14 @@ describe('', () => {
const required = false;
const catalogApi: jest.Mocked = {
- getEntities: jest.fn(async () => ({ items: entities })),
+ getEntitiesByRefs: jest.fn(async () => ({ items: entities })),
} as any;
+ const mockErrorApi: jest.Mocked = {
+ post: jest.fn(),
+ error$: jest.fn(),
+ };
+
beforeEach(() => {
entities = [
{
@@ -78,7 +88,7 @@ describe('', () => {
];
onChange.mockClear();
- catalogApi.getEntities.mockClear();
+ catalogApi.getEntitiesByRefs.mockClear();
});
afterEach(() => {
@@ -93,7 +103,7 @@ describe('', () => {
entity.spec.members.includes('Bob'),
);
- catalogApi.getEntities.mockResolvedValue({ items: userGroups });
+ catalogApi.getEntitiesByRefs.mockResolvedValue({ items: userGroups });
const props = {
onChange,
@@ -106,6 +116,7 @@ describe('', () => {
apis={[
[identityApiRef, mockIdentityApi],
[catalogApiRef, catalogApi],
+ [errorApiRef, mockErrorApi],
]}
>
@@ -113,38 +124,35 @@ describe('', () => {
);
await waitFor(() =>
- expect(catalogApi.getEntities).toHaveBeenCalledTimes(1),
+ expect(catalogApi.getEntitiesByRefs).toHaveBeenCalledTimes(1),
);
- expect(catalogApi.getEntities).toHaveBeenCalledWith({
- filter: {
- kind: ['Group'],
- 'relations.hasMember': ['group:default/group1', 'group:default/group2'],
- },
+ expect(catalogApi.getEntitiesByRefs).toHaveBeenCalledWith({
+ entityRefs: ['group:default/group1', 'group:default/group2'],
});
// Check that getEntities was set up to return the correct data
- await expect(catalogApi.getEntities.mock.results[0].value).resolves.toEqual(
- {
- items: [
- {
- apiVersion: 'backstage.io/v1alpha1',
- kind: 'Group',
- metadata: { name: 'group1' },
- spec: { members: ['Bob'] },
- },
- {
- apiVersion: 'backstage.io/v1alpha1',
- kind: 'Group',
- metadata: { name: 'group2' },
- spec: { members: ['Bob'] },
- },
- ],
- },
- );
+ await expect(
+ catalogApi.getEntitiesByRefs.mock.results[0].value,
+ ).resolves.toEqual({
+ items: [
+ {
+ apiVersion: 'backstage.io/v1alpha1',
+ kind: 'Group',
+ metadata: { name: 'group1' },
+ spec: { members: ['Bob'] },
+ },
+ {
+ apiVersion: 'backstage.io/v1alpha1',
+ kind: 'Group',
+ metadata: { name: 'group2' },
+ spec: { members: ['Bob'] },
+ },
+ ],
+ });
await expect(
- catalogApi.getEntities.mock.results[0].value,
+ catalogApi.getEntitiesByRefs.mock.results[0].value,
).resolves.not.toEqual(
expect.objectContaining({
items: expect.arrayContaining([
@@ -163,7 +171,7 @@ describe('', () => {
Array.isArray(entity.spec.members) &&
entity.spec.members.includes('Bob'),
);
- catalogApi.getEntities.mockResolvedValue({ items: userGroups });
+ catalogApi.getEntitiesByRefs.mockResolvedValue({ items: userGroups });
const props = {
onChange,
@@ -176,6 +184,7 @@ describe('', () => {
apis={[
[identityApiRef, mockIdentityApi],
[catalogApiRef, catalogApi],
+ [errorApiRef, mockErrorApi],
]}
>
@@ -183,7 +192,7 @@ describe('', () => {
);
await waitFor(() =>
- expect(catalogApi.getEntities).toHaveBeenCalledTimes(1),
+ expect(catalogApi.getEntitiesByRefs).toHaveBeenCalledTimes(1),
);
// Simulate user input
@@ -202,4 +211,62 @@ describe('', () => {
// Assert that 'group3' is not rendered in the component
expect(queryByText('group3')).not.toBeInTheDocument();
});
+
+ it('should call the onChange handler with the correct entityRef and and use a nice display name', async () => {
+ const userGroups = [
+ {
+ apiVersion: 'backstage.io/v1alpha1',
+ kind: 'Group',
+ metadata: { name: 'group1', title: 'My First Group' },
+ spec: { members: ['Bob'] },
+ },
+ {
+ apiVersion: 'backstage.io/v1alpha1',
+ kind: 'Group',
+ metadata: { name: 'group2', title: 'My Second Group' },
+ spec: { members: ['Bob'] },
+ },
+ ];
+
+ catalogApi.getEntitiesByRefs.mockResolvedValue({ items: userGroups });
+
+ const props = {
+ onChange,
+ schema,
+ required,
+ } as unknown as FieldProps;
+
+ const { getByRole } = render(
+
+
+ ,
+ );
+
+ await waitFor(() =>
+ expect(catalogApi.getEntitiesByRefs).toHaveBeenCalledTimes(1),
+ );
+
+ const inputField = getByRole('combobox');
+ userEvent.click(inputField);
+ userEvent.type(inputField, 'group');
+
+ await waitFor(() => {
+ const option = getByRole('option', { name: 'My First Group' });
+ expect(option).toBeInTheDocument();
+ });
+
+ const option = getByRole('option', { name: 'My First Group' });
+ userEvent.click(option);
+
+ await waitFor(() => {
+ expect(onChange).toHaveBeenCalledTimes(1);
+ expect(onChange).toHaveBeenCalledWith('group:default/group1');
+ });
+ });
});
diff --git a/plugins/scaffolder/src/components/fields/OwnershipEntityRefPicker/OwnershipEntityRefPicker.tsx b/plugins/scaffolder/src/components/fields/OwnershipEntityRefPicker/OwnershipEntityRefPicker.tsx
index f89c6ff789..299e8e66d7 100644
--- a/plugins/scaffolder/src/components/fields/OwnershipEntityRefPicker/OwnershipEntityRefPicker.tsx
+++ b/plugins/scaffolder/src/components/fields/OwnershipEntityRefPicker/OwnershipEntityRefPicker.tsx
@@ -14,8 +14,12 @@
* limitations under the License.
*/
-import React, { useEffect, useState } from 'react';
-import { identityApiRef, useApi } from '@backstage/core-plugin-api';
+import React, { useState } from 'react';
+import {
+ errorApiRef,
+ identityApiRef,
+ useApi,
+} from '@backstage/core-plugin-api';
import { TextField, FormControl } from '@material-ui/core';
import {
OwnershipEntityRefPickerProps,
@@ -24,6 +28,8 @@ import {
import { Autocomplete } from '@material-ui/lab';
import { catalogApiRef } from '@backstage/plugin-catalog-react';
import { NotFoundError } from '@backstage/errors';
+import useAsync from 'react-use/lib/useAsync';
+import { Entity, stringifyEntityRef } from '@backstage/catalog-model';
export { OwnershipEntityRefPickerSchema };
@@ -34,34 +40,52 @@ export const OwnershipEntityRefPicker = (
schema: { title, description },
required,
rawErrors,
+ onChange,
} = props;
const identityApi = useApi(identityApiRef);
const catalogApi = useApi(catalogApiRef);
- const [groups, setGroups] = useState([]);
- const [selectedGroup, setSelectedGroup] = useState(null);
+ const errorApi = useApi(errorApiRef);
+ const [groups, setGroups] = useState<
+ {
+ label: string;
+ ref: string;
+ }[]
+ >([]);
+ const [selectedGroup, setSelectedGroup] = useState(null);
- useEffect(() => {
- const fetchUserGroups = async () => {
- const identity = await identityApi.getBackstageIdentity();
- const userIdentity = identity.ownershipEntityRefs;
+ useAsync(async () => {
+ const { ownershipEntityRefs } = await identityApi.getBackstageIdentity();
- if (!userIdentity) {
- throw new NotFoundError('No ownership entity refs found');
- }
+ if (!ownershipEntityRefs || !ownershipEntityRefs.length) {
+ errorApi.post(new NotFoundError('No ownership entity refs found'));
+ return;
+ }
- const userOwnedGroups = await catalogApi.getEntities({
- filter: {
- kind: ['Group'],
- 'relations.hasMember': userIdentity,
- },
- });
- const groupValues = userOwnedGroups.items.map(item => item.metadata.name);
- setGroups(groupValues);
- };
+ const { items } = await catalogApi.getEntitiesByRefs({
+ entityRefs: ownershipEntityRefs,
+ });
- fetchUserGroups();
- }, [identityApi, catalogApi]);
+ const groupValues = items
+ .filter((e): e is Entity => Boolean(e))
+ .map(item => ({
+ label: item.metadata.title ?? item.metadata.name,
+ ref: stringifyEntityRef(item),
+ }));
+
+ setGroups(groupValues);
+ });
+
+ const updateChange = (
+ _: React.ChangeEvent<{}>,
+ value: { label: string; ref: string } | null,
+ ) => {
+ setSelectedGroup(value);
+ onChange(value?.ref ?? '');
+ };
return (
setSelectedGroup(value || '')}
- getOptionLabel={group => group}
+ onChange={updateChange}
+ getOptionLabel={group => group.label}
renderInput={params => (