Merge pull request #24970 from brianphillips/custom-member-relationship
Add relationType option to EntityMembersListCard component
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
---
|
||||
'@backstage/plugin-org': patch
|
||||
---
|
||||
|
||||
Added `relationType` property to EntityMembersListCard component that allows for display users related to a group via some other relationship aside from `memberOf`.
|
||||
|
||||
Also, as a side effect, the `relationsType` property has been deprecated in favor of a more accurately named `relationAggregation` property.
|
||||
@@ -23,7 +23,9 @@ export const EntityMembersListCard: (props: {
|
||||
memberDisplayTitle?: string | undefined;
|
||||
pageSize?: number | undefined;
|
||||
showAggregateMembersToggle?: boolean | undefined;
|
||||
relationType?: string | undefined;
|
||||
relationsType?: EntityRelationAggregation | undefined;
|
||||
relationAggregation?: EntityRelationAggregation | undefined;
|
||||
}) => JSX_2.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
@@ -32,6 +34,7 @@ export const EntityOwnershipCard: (props: {
|
||||
entityFilterKind?: string[] | undefined;
|
||||
hideRelationsToggle?: boolean | undefined;
|
||||
relationsType?: EntityRelationAggregation | undefined;
|
||||
relationAggregation?: EntityRelationAggregation | undefined;
|
||||
entityLimit?: number | undefined;
|
||||
}) => JSX_2.Element;
|
||||
|
||||
@@ -55,7 +58,9 @@ export const MembersListCard: (props: {
|
||||
memberDisplayTitle?: string;
|
||||
pageSize?: number;
|
||||
showAggregateMembersToggle?: boolean;
|
||||
relationType?: string;
|
||||
relationsType?: EntityRelationAggregation;
|
||||
relationAggregation?: EntityRelationAggregation;
|
||||
}) => React_2.JSX.Element;
|
||||
|
||||
// @public
|
||||
@@ -82,6 +87,7 @@ export const OwnershipCard: (props: {
|
||||
entityFilterKind?: string[];
|
||||
hideRelationsToggle?: boolean;
|
||||
relationsType?: EntityRelationAggregation;
|
||||
relationAggregation?: EntityRelationAggregation;
|
||||
entityLimit?: number;
|
||||
}) => React_2.JSX.Element;
|
||||
|
||||
|
||||
@@ -99,6 +99,7 @@ describe('MemberTab Test', () => {
|
||||
] as Entity[],
|
||||
}),
|
||||
};
|
||||
const getEntitiesSpy = jest.spyOn(catalogApi, 'getEntities');
|
||||
|
||||
it('Display Profile Card', async () => {
|
||||
await renderInTestApp(
|
||||
@@ -115,6 +116,12 @@ describe('MemberTab Test', () => {
|
||||
},
|
||||
},
|
||||
);
|
||||
expect(getEntitiesSpy).toHaveBeenCalledWith({
|
||||
filter: {
|
||||
kind: 'User',
|
||||
'relations.memberof': ['group:default/team-d'],
|
||||
},
|
||||
});
|
||||
|
||||
expect(screen.getByAltText('Tara MacGovern')).toHaveAttribute(
|
||||
'src',
|
||||
@@ -149,6 +156,29 @@ describe('MemberTab Test', () => {
|
||||
expect(screen.getByText('Testers (1)')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Can query a different relationship', async () => {
|
||||
await renderInTestApp(
|
||||
<TestApiProvider apis={[[catalogApiRef, catalogApi]]}>
|
||||
<EntityProvider entity={groupEntity}>
|
||||
<MembersListCard relationType="leaderOf" />
|
||||
</EntityProvider>
|
||||
</TestApiProvider>,
|
||||
{
|
||||
mountedRoutes: {
|
||||
'/catalog/:namespace/:kind/:name': entityRouteRef,
|
||||
'/catalog': rootRouteRef,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
expect(getEntitiesSpy).toHaveBeenCalledWith({
|
||||
filter: {
|
||||
kind: 'User',
|
||||
'relations.leaderof': ['group:default/team-d'],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
describe('Aggregate members toggle', () => {
|
||||
it('Does not show the aggregate members toggle if the showAggregateMembersToggle prop is undefined', async () => {
|
||||
await renderInTestApp(
|
||||
@@ -346,7 +376,7 @@ describe('MemberTab Test', () => {
|
||||
<EntityLayout.Route path="/" title="Title">
|
||||
<MembersListCard
|
||||
showAggregateMembersToggle
|
||||
relationsType="aggregated"
|
||||
relationAggregation="aggregated"
|
||||
/>
|
||||
</EntityLayout.Route>
|
||||
</EntityLayout>
|
||||
@@ -384,7 +414,7 @@ describe('MemberTab Test', () => {
|
||||
<EntityProvider entity={groupA}>
|
||||
<EntityLayout>
|
||||
<EntityLayout.Route path="/" title="Title">
|
||||
<MembersListCard relationsType="aggregated" />
|
||||
<MembersListCard relationAggregation="aggregated" />
|
||||
</EntityLayout.Route>
|
||||
</EntityLayout>
|
||||
</EntityProvider>
|
||||
|
||||
@@ -138,14 +138,19 @@ export const MembersListCard = (props: {
|
||||
memberDisplayTitle?: string;
|
||||
pageSize?: number;
|
||||
showAggregateMembersToggle?: boolean;
|
||||
relationType?: string;
|
||||
/** @deprecated Please use `relationAggregation` instead */
|
||||
relationsType?: EntityRelationAggregation;
|
||||
relationAggregation?: EntityRelationAggregation;
|
||||
}) => {
|
||||
const {
|
||||
memberDisplayTitle = 'Members',
|
||||
pageSize = 50,
|
||||
showAggregateMembersToggle,
|
||||
relationsType = 'direct',
|
||||
relationType = 'memberof',
|
||||
} = props;
|
||||
const relationAggregation =
|
||||
props.relationAggregation ?? props.relationsType ?? 'direct';
|
||||
const classes = useListStyles();
|
||||
|
||||
const { entity: groupEntity } = useEntity<GroupEntity>();
|
||||
@@ -165,7 +170,7 @@ export const MembersListCard = (props: {
|
||||
};
|
||||
|
||||
const [showAggregateMembers, setShowAggregateMembers] = useState(
|
||||
relationsType === 'aggregated',
|
||||
relationAggregation === 'aggregated',
|
||||
);
|
||||
|
||||
const { loading: loadingDescendantMembers, value: descendantMembers } =
|
||||
@@ -177,6 +182,7 @@ export const MembersListCard = (props: {
|
||||
return await getAllDesendantMembersForGroupEntity(
|
||||
groupEntity,
|
||||
catalogApi,
|
||||
relationType,
|
||||
);
|
||||
}, [catalogApi, groupEntity, showAggregateMembers]);
|
||||
const {
|
||||
@@ -187,7 +193,7 @@ export const MembersListCard = (props: {
|
||||
const membersList = await catalogApi.getEntities({
|
||||
filter: {
|
||||
kind: 'User',
|
||||
'relations.memberof': [
|
||||
[`relations.${relationType.toLocaleLowerCase('en-US')}`]: [
|
||||
stringifyEntityRef({
|
||||
kind: 'group',
|
||||
namespace: groupNamespace.toLocaleLowerCase('en-US'),
|
||||
|
||||
@@ -114,19 +114,27 @@ export const ComponentsGrid = ({
|
||||
className,
|
||||
entity,
|
||||
relationsType,
|
||||
relationAggregation,
|
||||
entityFilterKind,
|
||||
entityLimit = 6,
|
||||
}: {
|
||||
className?: string;
|
||||
entity: Entity;
|
||||
relationsType: EntityRelationAggregation;
|
||||
/** @deprecated Please use relationAggregation instead */
|
||||
relationsType?: EntityRelationAggregation;
|
||||
relationAggregation?: EntityRelationAggregation;
|
||||
entityFilterKind?: string[];
|
||||
entityLimit?: number;
|
||||
}) => {
|
||||
const catalogLink = useRouteRef(catalogIndexRouteRef);
|
||||
if (!relationsType && !relationAggregation) {
|
||||
throw new Error(
|
||||
'The relationAggregation property must be set as an EntityRelationAggregation type.',
|
||||
);
|
||||
}
|
||||
const { componentsWithCounters, loading, error } = useGetEntities(
|
||||
entity,
|
||||
relationsType,
|
||||
(relationAggregation ?? relationsType)!, // we can safely use the non-null assertion here because of the run-time check above
|
||||
entityFilterKind,
|
||||
entityLimit,
|
||||
);
|
||||
|
||||
@@ -288,7 +288,7 @@ describe('OwnershipCard', () => {
|
||||
const { getByText } = await renderInTestApp(
|
||||
<TestApiProvider apis={[[catalogApiRef, catalogApi]]}>
|
||||
<EntityProvider entity={userEntity}>
|
||||
<OwnershipCard relationsType="aggregated" />
|
||||
<OwnershipCard relationAggregation="aggregated" />
|
||||
</EntityProvider>
|
||||
</TestApiProvider>,
|
||||
{
|
||||
|
||||
@@ -67,31 +67,34 @@ export const OwnershipCard = (props: {
|
||||
variant?: InfoCardVariants;
|
||||
entityFilterKind?: string[];
|
||||
hideRelationsToggle?: boolean;
|
||||
/** @deprecated Please use relationAggregation instead */
|
||||
relationsType?: EntityRelationAggregation;
|
||||
relationAggregation?: EntityRelationAggregation;
|
||||
entityLimit?: number;
|
||||
}) => {
|
||||
const {
|
||||
variant,
|
||||
entityFilterKind,
|
||||
hideRelationsToggle,
|
||||
relationsType,
|
||||
entityLimit = 6,
|
||||
} = props;
|
||||
const relationAggregation = props.relationAggregation ?? props.relationsType;
|
||||
const relationsToggle =
|
||||
hideRelationsToggle === undefined ? false : hideRelationsToggle;
|
||||
const classes = useStyles();
|
||||
const { entity } = useEntity();
|
||||
|
||||
const defaultRelationsType = entity.kind === 'User' ? 'aggregated' : 'direct';
|
||||
const [getRelationsType, setRelationsType] = useState(
|
||||
relationsType ?? defaultRelationsType,
|
||||
const defaultRelationAggregation =
|
||||
entity.kind === 'User' ? 'aggregated' : 'direct';
|
||||
const [getRelationAggregation, setRelationAggregation] = useState(
|
||||
relationAggregation ?? defaultRelationAggregation,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!relationsType) {
|
||||
setRelationsType(defaultRelationsType);
|
||||
if (!relationAggregation) {
|
||||
setRelationAggregation(defaultRelationAggregation);
|
||||
}
|
||||
}, [setRelationsType, defaultRelationsType, relationsType]);
|
||||
}, [setRelationAggregation, defaultRelationAggregation, relationAggregation]);
|
||||
|
||||
return (
|
||||
<InfoCard
|
||||
@@ -112,16 +115,18 @@ export const OwnershipCard = (props: {
|
||||
placement="top"
|
||||
arrow
|
||||
title={`${
|
||||
getRelationsType === 'direct' ? 'Direct' : 'Aggregated'
|
||||
getRelationAggregation === 'direct' ? 'Direct' : 'Aggregated'
|
||||
} Relations`}
|
||||
>
|
||||
<Switch
|
||||
color="primary"
|
||||
checked={getRelationsType !== 'direct'}
|
||||
checked={getRelationAggregation !== 'direct'}
|
||||
onChange={() => {
|
||||
const updatedRelationsType =
|
||||
getRelationsType === 'direct' ? 'aggregated' : 'direct';
|
||||
setRelationsType(updatedRelationsType);
|
||||
const updatedRelationAggregation =
|
||||
getRelationAggregation === 'direct'
|
||||
? 'aggregated'
|
||||
: 'direct';
|
||||
setRelationAggregation(updatedRelationAggregation);
|
||||
}}
|
||||
name="pin"
|
||||
inputProps={{ 'aria-label': 'Ownership Type Switch' }}
|
||||
@@ -136,7 +141,7 @@ export const OwnershipCard = (props: {
|
||||
className={classes.grid}
|
||||
entity={entity}
|
||||
entityLimit={entityLimit}
|
||||
relationsType={getRelationsType}
|
||||
relationAggregation={getRelationAggregation}
|
||||
entityFilterKind={entityFilterKind}
|
||||
/>
|
||||
</InfoCard>
|
||||
|
||||
@@ -64,7 +64,7 @@ describe('useGetEntities', () => {
|
||||
]),
|
||||
});
|
||||
|
||||
describe('given aggregated relationsType', () => {
|
||||
describe('given aggregated relationAggregation', () => {
|
||||
const whenHookIsCalledWith = async (_entity: Entity) => {
|
||||
const { result } = renderHook(
|
||||
({ entity }) => useGetEntities(entity, 'aggregated'),
|
||||
@@ -205,7 +205,7 @@ describe('useGetEntities', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('given direct relationsType', () => {
|
||||
describe('given direct relationAggregation', () => {
|
||||
const whenHookIsCalledWith = async (_entity: Entity) => {
|
||||
const { result } = renderHook(
|
||||
({ entity }) => useGetEntities(entity, 'direct'),
|
||||
|
||||
@@ -125,11 +125,11 @@ const getChildOwnershipEntityRefs = async (
|
||||
|
||||
const getOwners = async (
|
||||
entity: Entity,
|
||||
relations: EntityRelationAggregation,
|
||||
relationAggregation: EntityRelationAggregation,
|
||||
catalogApi: CatalogApi,
|
||||
): Promise<string[]> => {
|
||||
const isGroup = entity.kind === 'Group';
|
||||
const isAggregated = relations === 'aggregated';
|
||||
const isAggregated = relationAggregation === 'aggregated';
|
||||
const isUserEntity = entity.kind === 'User';
|
||||
|
||||
if (isAggregated && isGroup) {
|
||||
@@ -166,7 +166,7 @@ const getOwnedEntitiesByOwners = (
|
||||
|
||||
export function useGetEntities(
|
||||
entity: Entity,
|
||||
relations: EntityRelationAggregation,
|
||||
relationAggregation: EntityRelationAggregation,
|
||||
entityFilterKind?: string[],
|
||||
entityLimit = 6,
|
||||
): {
|
||||
@@ -189,7 +189,7 @@ export function useGetEntities(
|
||||
error,
|
||||
value: componentsWithCounters,
|
||||
} = useAsync(async () => {
|
||||
const owners = await getOwners(entity, relations, catalogApi);
|
||||
const owners = await getOwners(entity, relationAggregation, catalogApi);
|
||||
|
||||
const ownedEntitiesList = await getOwnedEntitiesByOwners(
|
||||
owners,
|
||||
@@ -230,7 +230,7 @@ export function useGetEntities(
|
||||
kind: string;
|
||||
queryParams: string;
|
||||
}>;
|
||||
}, [catalogApi, entity, relations]);
|
||||
}, [catalogApi, entity, relationAggregation]);
|
||||
|
||||
return {
|
||||
componentsWithCounters,
|
||||
|
||||
@@ -31,6 +31,7 @@ import {
|
||||
export const getMembersFromGroups = async (
|
||||
groups: CompoundEntityRef[],
|
||||
catalogApi: CatalogApi,
|
||||
relationship = 'memberof',
|
||||
) => {
|
||||
const membersList =
|
||||
groups.length === 0
|
||||
@@ -38,13 +39,14 @@ export const getMembersFromGroups = async (
|
||||
: await catalogApi.getEntities({
|
||||
filter: {
|
||||
kind: 'User',
|
||||
'relations.memberof': groups.map(group =>
|
||||
stringifyEntityRef({
|
||||
kind: 'group',
|
||||
namespace: group.namespace.toLocaleLowerCase('en-US'),
|
||||
name: group.name.toLocaleLowerCase('en-US'),
|
||||
}),
|
||||
),
|
||||
[`relations.${relationship.toLocaleLowerCase('en-US')}`]:
|
||||
groups.map(group =>
|
||||
stringifyEntityRef({
|
||||
kind: 'group',
|
||||
namespace: group.namespace.toLocaleLowerCase('en-US'),
|
||||
name: group.name.toLocaleLowerCase('en-US'),
|
||||
}),
|
||||
),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -99,10 +101,12 @@ export const getDescendantGroupsFromGroup = async (
|
||||
export const getAllDesendantMembersForGroupEntity = async (
|
||||
groupEntity: GroupEntity,
|
||||
catalogApi: CatalogApi,
|
||||
relationship = 'memberof',
|
||||
) =>
|
||||
getMembersFromGroups(
|
||||
await getDescendantGroupsFromGroup(groupEntity, catalogApi),
|
||||
catalogApi,
|
||||
relationship,
|
||||
);
|
||||
|
||||
export const removeDuplicateEntitiesFrom = (entityArray: Entity[]) => {
|
||||
|
||||
Reference in New Issue
Block a user