feat: reworking the picker slightly to just use groups and decorate the meta for now
Signed-off-by: blam <ben@blam.sh>
This commit is contained in:
+98
-31
@@ -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('<OwnershipEntityRefPicker />', () => {
|
||||
const required = false;
|
||||
|
||||
const catalogApi: jest.Mocked<CatalogApi> = {
|
||||
getEntities: jest.fn(async () => ({ items: entities })),
|
||||
getEntitiesByRefs: jest.fn(async () => ({ items: entities })),
|
||||
} as any;
|
||||
|
||||
const mockErrorApi: jest.Mocked<ErrorApi> = {
|
||||
post: jest.fn(),
|
||||
error$: jest.fn(),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
entities = [
|
||||
{
|
||||
@@ -78,7 +88,7 @@ describe('<OwnershipEntityRefPicker />', () => {
|
||||
];
|
||||
|
||||
onChange.mockClear();
|
||||
catalogApi.getEntities.mockClear();
|
||||
catalogApi.getEntitiesByRefs.mockClear();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -93,7 +103,7 @@ describe('<OwnershipEntityRefPicker />', () => {
|
||||
entity.spec.members.includes('Bob'),
|
||||
);
|
||||
|
||||
catalogApi.getEntities.mockResolvedValue({ items: userGroups });
|
||||
catalogApi.getEntitiesByRefs.mockResolvedValue({ items: userGroups });
|
||||
|
||||
const props = {
|
||||
onChange,
|
||||
@@ -106,6 +116,7 @@ describe('<OwnershipEntityRefPicker />', () => {
|
||||
apis={[
|
||||
[identityApiRef, mockIdentityApi],
|
||||
[catalogApiRef, catalogApi],
|
||||
[errorApiRef, mockErrorApi],
|
||||
]}
|
||||
>
|
||||
<OwnershipEntityRefPicker {...props} />
|
||||
@@ -113,38 +124,35 @@ describe('<OwnershipEntityRefPicker />', () => {
|
||||
);
|
||||
|
||||
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('<OwnershipEntityRefPicker />', () => {
|
||||
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('<OwnershipEntityRefPicker />', () => {
|
||||
apis={[
|
||||
[identityApiRef, mockIdentityApi],
|
||||
[catalogApiRef, catalogApi],
|
||||
[errorApiRef, mockErrorApi],
|
||||
]}
|
||||
>
|
||||
<OwnershipEntityRefPicker {...props} />
|
||||
@@ -183,7 +192,7 @@ describe('<OwnershipEntityRefPicker />', () => {
|
||||
);
|
||||
|
||||
await waitFor(() =>
|
||||
expect(catalogApi.getEntities).toHaveBeenCalledTimes(1),
|
||||
expect(catalogApi.getEntitiesByRefs).toHaveBeenCalledTimes(1),
|
||||
);
|
||||
|
||||
// Simulate user input
|
||||
@@ -202,4 +211,62 @@ describe('<OwnershipEntityRefPicker />', () => {
|
||||
// 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<any>;
|
||||
|
||||
const { getByRole } = render(
|
||||
<TestApiProvider
|
||||
apis={[
|
||||
[identityApiRef, mockIdentityApi],
|
||||
[catalogApiRef, catalogApi],
|
||||
[errorApiRef, mockErrorApi],
|
||||
]}
|
||||
>
|
||||
<OwnershipEntityRefPicker {...props} />
|
||||
</TestApiProvider>,
|
||||
);
|
||||
|
||||
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');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+48
-24
@@ -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<string[]>([]);
|
||||
const [selectedGroup, setSelectedGroup] = useState<string | null>(null);
|
||||
const errorApi = useApi(errorApiRef);
|
||||
const [groups, setGroups] = useState<
|
||||
{
|
||||
label: string;
|
||||
ref: string;
|
||||
}[]
|
||||
>([]);
|
||||
const [selectedGroup, setSelectedGroup] = useState<null | {
|
||||
label: string;
|
||||
ref: string;
|
||||
}>(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 (
|
||||
<FormControl
|
||||
@@ -73,8 +97,8 @@ export const OwnershipEntityRefPicker = (
|
||||
id="OwnershipEntityRefPicker-dropdown"
|
||||
options={groups || []}
|
||||
value={selectedGroup}
|
||||
onChange={(_, value) => setSelectedGroup(value || '')}
|
||||
getOptionLabel={group => group}
|
||||
onChange={updateChange}
|
||||
getOptionLabel={group => group.label}
|
||||
renderInput={params => (
|
||||
<TextField
|
||||
{...params}
|
||||
|
||||
Reference in New Issue
Block a user