Refactor the hooks and also support users owning components

This commit is contained in:
Dominik Henneke
2020-11-30 18:29:16 +01:00
parent ebf37bbae8
commit 6e2290a630
6 changed files with 237 additions and 44 deletions
@@ -37,8 +37,8 @@ import { ResultsFilter } from '../ResultsFilter/ResultsFilter';
import CatalogLayout from './CatalogLayout';
import { CatalogTabs, LabeledComponentType } from './CatalogTabs';
import { WelcomeBanner } from './WelcomeBanner';
import { useUserGroups } from '../useUserGroups';
import { RELATION_OWNED_BY } from '@backstage/catalog-model';
import { useOwnUser } from '../useOwnUser';
import { isOwnerOf } from '../isOwnerOf';
const useStyles = makeStyles(theme => ({
contentWrapper: {
@@ -112,7 +112,7 @@ const CatalogPageContents = () => {
[],
);
const { groups } = useUserGroups();
const { value: user } = useOwnUser();
const filterGroups = useMemo<ButtonGroup[]>(
() => [
@@ -123,16 +123,7 @@ const CatalogPageContents = () => {
id: 'owned',
label: 'Owned',
icon: SettingsIcon,
filterFn: entity => {
const ownerRelation = entity.relations?.find(
r =>
r.type === RELATION_OWNED_BY &&
r.target.kind.toLowerCase() === 'group',
);
return (
!!ownerRelation && groups.includes(ownerRelation.target.name)
);
},
filterFn: entity => user !== undefined && isOwnerOf(user, entity),
},
{
id: 'starred',
@@ -153,7 +144,7 @@ const CatalogPageContents = () => {
],
},
],
[isStarredEntity, orgName, groups],
[isStarredEntity, orgName, user],
);
const showAddExampleEntities =
@@ -0,0 +1,66 @@
/*
* Copyright 2020 Spotify AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { getEntityRelations } from './getEntityRelations';
import {
Entity,
RELATION_CHILD_OF,
RELATION_MEMBER_OF,
} from '@backstage/catalog-model';
describe('getEntityRelations', () => {
it('should handle undefined value', () => {
expect(getEntityRelations(undefined, RELATION_MEMBER_OF)).toEqual([]);
});
it('should extract correct relation', () => {
const entity = {
relations: [
{
type: RELATION_MEMBER_OF,
target: { kind: 'group', name: 'member' },
},
{
type: RELATION_CHILD_OF,
target: { kind: 'group', name: 'child' },
},
],
} as Entity;
expect(getEntityRelations(entity, RELATION_MEMBER_OF)).toEqual([
{ kind: 'group', name: 'member' },
]);
});
it('should filter relation by type', () => {
const entity = {
relations: [
{
type: RELATION_MEMBER_OF,
target: { kind: 'group', name: 'member' },
},
{
type: RELATION_MEMBER_OF,
target: { kind: 'user', name: 'child' },
},
],
} as Entity;
expect(
getEntityRelations(entity, RELATION_MEMBER_OF, { kind: 'group' }),
).toEqual([{ kind: 'group', name: 'member' }]);
});
});
@@ -0,0 +1,39 @@
/*
* Copyright 2020 Spotify AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Entity, EntityName } from '@backstage/catalog-model';
/**
* Get the related entity references.
*/
export function getEntityRelations(
entity: Entity | undefined,
relationType: string,
filter?: { kind: string },
): EntityName[] {
let entityNames =
entity?.relations
?.filter(r => r.type === relationType)
?.map(r => r.target) || [];
if (filter?.kind) {
entityNames = entityNames?.filter(
e => e.kind.toLowerCase() === filter.kind,
);
}
return entityNames;
}
@@ -0,0 +1,64 @@
/*
* Copyright 2020 Spotify AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
Entity,
RELATION_MEMBER_OF,
RELATION_OWNED_BY,
} from '@backstage/catalog-model';
import { isOwnerOf } from './isOwnerOf';
describe('isOwnerOf', () => {
it('should be owned by user', () => {
const ownerEntity = {
kind: 'User',
metadata: { name: 'user' },
} as Entity;
const ownedEntity = {
relations: [
{
type: RELATION_OWNED_BY,
target: { kind: 'user', namespace: 'default', name: 'user' },
},
],
} as Entity;
expect(isOwnerOf(ownerEntity, ownedEntity)).toBe(true);
});
it('should be owned by group', () => {
const ownerEntity = {
kind: 'User',
metadata: { name: 'user' },
relations: [
{
type: RELATION_MEMBER_OF,
target: { kind: 'group', namespace: 'default', name: 'group' },
},
],
} as Entity;
const ownedEntity = {
relations: [
{
type: RELATION_OWNED_BY,
target: { kind: 'group', namespace: 'default', name: 'group' },
},
],
} as Entity;
expect(isOwnerOf(ownerEntity, ownedEntity)).toBe(true);
});
});
@@ -0,0 +1,51 @@
/*
* Copyright 2020 Spotify AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
Entity,
EntityName,
getEntityName,
RELATION_MEMBER_OF,
RELATION_OWNED_BY,
} from '@backstage/catalog-model';
import { getEntityRelations } from './getEntityRelations';
/**
* Get the related entity references.
*/
export function isOwnerOf(owner: Entity, owned: Entity) {
const possibleOwners: EntityName[] = [
...getEntityRelations(owner, RELATION_MEMBER_OF, { kind: 'group' }),
...(owner ? [getEntityName(owner)] : []),
];
const owners = getEntityRelations(owned, RELATION_OWNED_BY);
for (const owner of owners) {
if (
possibleOwners.find(
o =>
owner.kind.toLowerCase() === o.kind.toLowerCase() &&
owner.namespace === o.namespace &&
owner.name === o.name,
) !== undefined
) {
return true;
}
}
return false;
}
@@ -14,46 +14,28 @@
* limitations under the License.
*/
import { UserEntity } from '@backstage/catalog-model';
import { useAsync } from 'react-use';
import { useMemo } from 'react';
import { RELATION_MEMBER_OF } from '@backstage/catalog-model';
import { identityApiRef, useApi } from '@backstage/core';
import { catalogApiRef } from '../plugin';
/**
* Get the group memberships of the logged-in user.
*/
export const useUserGroups: () => {
groups: string[];
export function useOwnUser(): {
value?: UserEntity;
loading: boolean;
error?: Error;
} = () => {
} {
const catalogApi = useApi(catalogApiRef);
const userId = useApi(identityApiRef).getUserId();
const identityApi = useApi(identityApiRef);
// TODO: should the identityApiRef already include the entity? or at least a full EntityName?
const { value: user, loading, error } = useAsync(async () => {
return await catalogApi.getEntityByName({
// TODO: get the full entity (or at least the full entity name) from the identityApi
return useAsync(async () => {
return (await catalogApi.getEntityByName({
kind: 'User',
namespace: 'default',
name: userId,
});
}, [catalogApi, userId]);
// calculate the group memberships
const groups = useMemo<string[]>(() => {
if (user && user.relations) {
return user.relations
.filter(
r =>
r.type === RELATION_MEMBER_OF &&
r.target.kind.toLowerCase() === 'group',
)
.map(r => r.target.name);
}
return [];
}, [user]);
return { groups, loading, error };
};
name: identityApi.getUserId(),
})) as UserEntity;
}, [catalogApi, identityApi]);
}