catalog-react: humanize display selected entities
Signed-off-by: Vincenzo Scamporlino <vincenzos@spotify.com>
This commit is contained in:
@@ -111,8 +111,12 @@ const ownerEntitiesBatch2: Entity[] = [
|
||||
const mockedQueryEntities: jest.MockedFn<CatalogApi['queryEntities']> =
|
||||
jest.fn();
|
||||
|
||||
const mockedGetEntitiesByRef: jest.MockedFn<CatalogApi['getEntitiesByRefs']> =
|
||||
jest.fn();
|
||||
|
||||
const mockCatalogApi: Partial<CatalogApi> = {
|
||||
queryEntities: mockedQueryEntities,
|
||||
getEntitiesByRefs: mockedGetEntitiesByRef,
|
||||
};
|
||||
|
||||
const mockErrorApi = new MockErrorApi();
|
||||
@@ -146,7 +150,7 @@ describe('<EntityOwnerPicker/>', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('renders all owners', async () => {
|
||||
it('renders all users and groups', async () => {
|
||||
await renderWithEffects(
|
||||
<ApiProvider apis={mockApis}>
|
||||
<MockEntityListContextProvider value={{}}>
|
||||
@@ -171,6 +175,7 @@ describe('<EntityOwnerPicker/>', () => {
|
||||
});
|
||||
|
||||
expect(mockedQueryEntities).toHaveBeenCalledTimes(1);
|
||||
expect(mockedGetEntitiesByRef).not.toHaveBeenCalled();
|
||||
|
||||
fireEvent.scroll(screen.getByTestId('owner-picker-listbox'));
|
||||
|
||||
@@ -205,11 +210,71 @@ describe('<EntityOwnerPicker/>', () => {
|
||||
</ApiProvider>,
|
||||
);
|
||||
|
||||
expect(mockedGetEntitiesByRef).toHaveBeenCalledWith({
|
||||
entityRefs: ['another-owner'],
|
||||
});
|
||||
expect(updateFilters).toHaveBeenLastCalledWith({
|
||||
owners: new EntityOwnerFilter(['group:default/another-owner']),
|
||||
});
|
||||
});
|
||||
|
||||
it('should display the selected owners as humanized entities', async () => {
|
||||
const updateFilters = jest.fn();
|
||||
const queryParameters = { owners: ['another-owner'] };
|
||||
|
||||
mockedGetEntitiesByRef.mockResolvedValue({
|
||||
items: [
|
||||
{
|
||||
metadata: {
|
||||
name: 'another-owner',
|
||||
title: 'Beautiful display name',
|
||||
namespace: 'default',
|
||||
},
|
||||
apiVersion: '1',
|
||||
kind: 'group',
|
||||
},
|
||||
],
|
||||
});
|
||||
await renderWithEffects(
|
||||
<ApiProvider apis={mockApis}>
|
||||
<MockEntityListContextProvider
|
||||
value={{
|
||||
updateFilters,
|
||||
queryParameters,
|
||||
}}
|
||||
>
|
||||
<EntityOwnerPicker />
|
||||
</MockEntityListContextProvider>
|
||||
</ApiProvider>,
|
||||
);
|
||||
|
||||
await waitFor(() =>
|
||||
expect(
|
||||
screen.getByRole('button', {
|
||||
name: 'Beautiful display name',
|
||||
}),
|
||||
).toBeInTheDocument(),
|
||||
);
|
||||
|
||||
expect(mockedGetEntitiesByRef).toHaveBeenCalledWith({
|
||||
entityRefs: ['another-owner'],
|
||||
});
|
||||
|
||||
fireEvent.click(screen.getByTestId('owner-picker-expand'));
|
||||
await waitFor(() => screen.getByText('Some Owner 2'));
|
||||
fireEvent.click(screen.getByText('Some Owner 2'));
|
||||
|
||||
expect(mockedGetEntitiesByRef).toHaveBeenCalledTimes(1);
|
||||
|
||||
await waitFor(() =>
|
||||
expect(
|
||||
screen.getByRole('button', {
|
||||
name: 'Some Owner 2',
|
||||
}),
|
||||
).toBeInTheDocument(),
|
||||
);
|
||||
});
|
||||
|
||||
it('adds owners to filters', async () => {
|
||||
const updateFilters = jest.fn();
|
||||
await renderWithEffects(
|
||||
@@ -223,6 +288,7 @@ describe('<EntityOwnerPicker/>', () => {
|
||||
</MockEntityListContextProvider>
|
||||
</ApiProvider>,
|
||||
);
|
||||
expect(mockedGetEntitiesByRef).not.toHaveBeenCalled();
|
||||
expect(updateFilters).toHaveBeenLastCalledWith({
|
||||
owners: undefined,
|
||||
});
|
||||
@@ -250,6 +316,9 @@ describe('<EntityOwnerPicker/>', () => {
|
||||
</MockEntityListContextProvider>
|
||||
</ApiProvider>,
|
||||
);
|
||||
expect(mockedGetEntitiesByRef).toHaveBeenCalledWith({
|
||||
entityRefs: ['group:default/some-owner'],
|
||||
});
|
||||
expect(updateFilters).toHaveBeenLastCalledWith({
|
||||
owners: new EntityOwnerFilter(['group:default/some-owner']),
|
||||
});
|
||||
@@ -279,6 +348,9 @@ describe('<EntityOwnerPicker/>', () => {
|
||||
</MockEntityListContextProvider>
|
||||
</ApiProvider>,
|
||||
);
|
||||
expect(mockedGetEntitiesByRef).toHaveBeenCalledWith({
|
||||
entityRefs: ['team-a'],
|
||||
});
|
||||
expect(updateFilters).toHaveBeenLastCalledWith({
|
||||
owners: new EntityOwnerFilter(['group:default/team-a']),
|
||||
});
|
||||
|
||||
@@ -26,11 +26,12 @@ import CheckBoxIcon from '@material-ui/icons/CheckBox';
|
||||
import CheckBoxOutlineBlankIcon from '@material-ui/icons/CheckBoxOutlineBlank';
|
||||
import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
|
||||
import { Autocomplete } from '@material-ui/lab';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useEntityList } from '../../hooks/useEntityListProvider';
|
||||
import { EntityOwnerFilter } from '../../filters';
|
||||
import { useApi } from '@backstage/core-plugin-api';
|
||||
import { catalogApiRef } from '../../api';
|
||||
import useAsync from 'react-use/lib/useAsync';
|
||||
import useAsyncFn from 'react-use/lib/useAsyncFn';
|
||||
import { useDebouncedEffect } from '@react-hookz/web';
|
||||
import PersonIcon from '@material-ui/icons/Person';
|
||||
@@ -102,6 +103,8 @@ export const EntityOwnerPicker = () => {
|
||||
queryParamOwners.length ? queryParamOwners : filters.owners?.values ?? [],
|
||||
);
|
||||
|
||||
const { getEntity, setEntity } = useSelectedOwners(selectedOwners);
|
||||
|
||||
// Set selected owners on query parameter updates; this happens at initial page load and from
|
||||
// external updates to the page location.
|
||||
useEffect(() => {
|
||||
@@ -143,12 +146,26 @@ export const EntityOwnerPicker = () => {
|
||||
}
|
||||
return o === v;
|
||||
}}
|
||||
getOptionLabel={o => {
|
||||
const entity = typeof o === 'string' ? getEntity(o) || o : o;
|
||||
|
||||
return typeof entity === 'string'
|
||||
? entity
|
||||
: humanizeEntity(entity, entity.metadata.name);
|
||||
}}
|
||||
onChange={(_: object, owners) => {
|
||||
setText('');
|
||||
setSelectedOwners(
|
||||
owners.map(e =>
|
||||
typeof e === 'string' ? e : stringifyEntityRef(e),
|
||||
),
|
||||
owners.map(e => {
|
||||
const entityRef =
|
||||
typeof e === 'string' ? e : stringifyEntityRef(e);
|
||||
|
||||
if (typeof e !== 'string') {
|
||||
setEntity(e);
|
||||
}
|
||||
|
||||
return entityRef;
|
||||
}),
|
||||
);
|
||||
}}
|
||||
filterOptions={x => x}
|
||||
@@ -214,3 +231,36 @@ export const EntityOwnerPicker = () => {
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Hook used for storing the full entity of the specified owners
|
||||
* in order to display users and group using the information contained on each entity.
|
||||
* When a component is rendered for the first time, it loads the content of the entities
|
||||
* specified by `initialSelectedOwnersRefs` and export the `getEntity` and `setEntity`
|
||||
* utilities, used to retrieve and modify the owners.
|
||||
*/
|
||||
function useSelectedOwners(initialSelectedOwnersRefs: string[]) {
|
||||
const allEntities = useRef<Record<string, Entity>>({});
|
||||
const catalogApi = useApi(catalogApiRef);
|
||||
|
||||
useAsync(async () => {
|
||||
if (initialSelectedOwnersRefs.length === 0) {
|
||||
return;
|
||||
}
|
||||
const initialSelectedEntities = await catalogApi.getEntitiesByRefs({
|
||||
entityRefs: initialSelectedOwnersRefs,
|
||||
});
|
||||
initialSelectedEntities.items.forEach(e => {
|
||||
if (e) {
|
||||
allEntities.current[stringifyEntityRef(e)] = e;
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
|
||||
return {
|
||||
getEntity: (entityRef: string) => allEntities.current[entityRef],
|
||||
setEntity: (entity: Entity) => {
|
||||
allEntities.current[stringifyEntityRef(entity)] = entity;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user