From 2566b48ebb6d303dfd9a8504c457333c50f4212d Mon Sep 17 00:00:00 2001 From: Prasetya Aria Wibawa Date: Thu, 17 Feb 2022 00:36:13 +0700 Subject: [PATCH 01/24] transitive ownership of entities for group kind Signed-off-by: Prasetya Aria Wibawa --- plugins/org/package.json | 1 + .../OwnershipCard/DirectRelationsGrid.tsx | 122 ++++++++ .../Cards/OwnershipCard/OwnershipCard.tsx | 261 ++++++------------ .../OwnershipCard/TransitiveRelationsGrid.tsx | 122 ++++++++ .../Cards/OwnershipCard/useDirectEntities.ts | 138 +++++++++ .../OwnershipCard/useTransitiveEntities.ts | 180 ++++++++++++ 6 files changed, 641 insertions(+), 183 deletions(-) create mode 100644 plugins/org/src/components/Cards/OwnershipCard/DirectRelationsGrid.tsx create mode 100644 plugins/org/src/components/Cards/OwnershipCard/TransitiveRelationsGrid.tsx create mode 100644 plugins/org/src/components/Cards/OwnershipCard/useDirectEntities.ts create mode 100644 plugins/org/src/components/Cards/OwnershipCard/useTransitiveEntities.ts diff --git a/plugins/org/package.json b/plugins/org/package.json index 11478bbe64..8b8c6709dc 100644 --- a/plugins/org/package.json +++ b/plugins/org/package.json @@ -34,6 +34,7 @@ "@material-ui/lab": "4.0.0-alpha.57", "pluralize": "^8.0.0", "qs": "^6.10.1", + "p-limit": "^3.1.0", "react-router": "6.0.0-beta.0", "react-router-dom": "6.0.0-beta.0", "react-use": "^17.2.4" diff --git a/plugins/org/src/components/Cards/OwnershipCard/DirectRelationsGrid.tsx b/plugins/org/src/components/Cards/OwnershipCard/DirectRelationsGrid.tsx new file mode 100644 index 0000000000..a7cd521ecc --- /dev/null +++ b/plugins/org/src/components/Cards/OwnershipCard/DirectRelationsGrid.tsx @@ -0,0 +1,122 @@ +/* + * Copyright 2020 The Backstage Authors + * + * 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 } from '@backstage/catalog-model'; +import { Link, Progress, ResponseErrorPanel } from '@backstage/core-components'; +import { useRouteRef } from '@backstage/core-plugin-api'; +import { BackstageTheme } from '@backstage/theme'; +import { + Box, + createStyles, + Grid, + makeStyles, + Typography, +} from '@material-ui/core'; +import React from 'react'; +import pluralize from 'pluralize'; +import { catalogIndexRouteRef } from '../../../routes'; +import { useDirectEntities } from './useDirectEntities'; + +const useStyles = makeStyles((theme: BackstageTheme) => + createStyles({ + card: { + border: `1px solid ${theme.palette.divider}`, + boxShadow: theme.shadows[2], + borderRadius: '4px', + padding: theme.spacing(2), + color: '#fff', + transition: `${theme.transitions.duration.standard}ms`, + '&:hover': { + boxShadow: theme.shadows[4], + }, + }, + bold: { + fontWeight: theme.typography.fontWeightBold, + }, + entityTypeBox: { + background: (props: { type: string }) => + theme.getPageTheme({ themeId: props.type }).backgroundImage, + }, + }), +); + +const EntityCountTile = ({ + counter, + type, + name, + url, +}: { + counter: number; + type: string; + name: string; + url: string; +}) => { + const classes = useStyles({ type }); + + return ( + + + + {counter} + + + {pluralize(name, counter)} + + + + ); +}; + +export const DirectRelationsGrid = ({ + entity, + entityFilterKind, +}: { + entity: Entity; + entityFilterKind?: string[]; +}) => { + const catalogLink = useRouteRef(catalogIndexRouteRef); + + const { componentsWithCounters, loading, error } = useDirectEntities( + entity, + entityFilterKind, + ); + + if (loading) { + return ; + } else if (error) { + return ; + } + + return ( + + {componentsWithCounters?.map(c => ( + + + + ))} + + ); +}; diff --git a/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.tsx b/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.tsx index fb49843e92..9ede02ce6e 100644 --- a/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.tsx +++ b/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.tsx @@ -14,122 +14,61 @@ * limitations under the License. */ -import { Entity, UserEntity } from '@backstage/catalog-model'; +import { Entity } from '@backstage/catalog-model'; +import { InfoCard, InfoCardVariants } from '@backstage/core-components'; +import { useEntity } from '@backstage/plugin-catalog-react'; import { - InfoCard, - InfoCardVariants, - Link, - Progress, - ResponseErrorPanel, -} from '@backstage/core-components'; -import { useApi, useRouteRef } from '@backstage/core-plugin-api'; -import { - catalogApiRef, - humanizeEntityRef, - isOwnerOf, - useEntity, -} from '@backstage/plugin-catalog-react'; -import { BackstageTheme } from '@backstage/theme'; -import { - Box, - createStyles, - Grid, + List, + ListItem, + ListItemSecondaryAction, + ListItemText, makeStyles, - Typography, + Switch, + Tooltip, } from '@material-ui/core'; -import qs from 'qs'; -import React from 'react'; -import pluralize from 'pluralize'; -import useAsync from 'react-use/lib/useAsync'; -import { catalogIndexRouteRef } from '../../../routes'; +import React, { useState } from 'react'; +import { DirectRelationsGrid } from './DirectRelationsGrid'; +import { TransitiveRelationsGrid } from './TransitiveRelationsGrid'; -type EntityTypeProps = { - kind: string; - type: string; - count: number; -}; - -const useStyles = makeStyles((theme: BackstageTheme) => - createStyles({ - card: { - border: `1px solid ${theme.palette.divider}`, - boxShadow: theme.shadows[2], - borderRadius: '4px', - padding: theme.spacing(2), - color: '#fff', - transition: `${theme.transitions.duration.standard}ms`, - '&:hover': { - boxShadow: theme.shadows[4], - }, +const useStyles = makeStyles(theme => ({ + list: { + [theme.breakpoints.down('xs')]: { + padding: `0 0 12px`, }, - bold: { - fontWeight: theme.typography.fontWeightBold, + }, + listItemText: { + [theme.breakpoints.down('xs')]: { + paddingRight: 0, + paddingLeft: 0, }, - entityTypeBox: { - background: (props: { type: string }) => - theme.getPageTheme({ themeId: props.type }).backgroundImage, + }, + listItemSecondaryAction: { + [theme.breakpoints.down('xs')]: { + width: '100%', + top: 'auto', + right: 'auto', + position: 'relative', + transform: 'unset', }, - }), -); - -const EntityCountTile = ({ - counter, - type, - name, - url, -}: { - counter: number; - type: string; - name: string; - url: string; -}) => { - const classes = useStyles({ type }); + }, +})); +const directRelationsGrid = (entity: Entity, entityFilterKind?: string[]) => { return ( - - - - {counter} - - - {pluralize(name, counter)} - - - + ); }; -const getQueryParams = ( - owner: Entity, - selectedEntity: EntityTypeProps, -): string => { - const ownerName = humanizeEntityRef(owner, { defaultKind: 'group' }); - const { kind, type } = selectedEntity; - const filters = { - kind, - type, - owners: [ownerName], - user: 'all', - }; - if (owner.kind === 'User') { - const user = owner as UserEntity; - filters.owners = [...filters.owners, ...(user.spec.memberOf ?? [])]; - } - const queryParams = qs.stringify( - { - filters, - }, - { - arrayFormat: 'repeat', - }, +const transitiveRelationsGrid = ( + entity: Entity, + entityFilterKind?: string[], +) => { + return ( + ); - - return queryParams; }; export const OwnershipCard = ({ @@ -139,90 +78,46 @@ export const OwnershipCard = ({ variant?: InfoCardVariants; entityFilterKind?: string[]; }) => { + const [relationsType, setRelationsType] = useState('direct'); + const classes = useStyles(); const { entity } = useEntity(); - const catalogApi = useApi(catalogApiRef); - const catalogLink = useRouteRef(catalogIndexRouteRef); - - const { - loading, - error, - value: componentsWithCounters, - } = useAsync(async () => { - const kinds = entityFilterKind ?? ['Component', 'API']; - const entitiesList = await catalogApi.getEntities({ - filter: { - kind: kinds, - }, - fields: [ - 'kind', - 'metadata.name', - 'metadata.namespace', - 'spec.type', - 'relations', - ], - }); - - const ownedEntitiesList = entitiesList.items.filter(component => - isOwnerOf(entity, component), - ); - - const counts = ownedEntitiesList.reduce( - (acc: EntityTypeProps[], ownedEntity) => { - const match = acc.find( - x => - x.kind === ownedEntity.kind && - x.type === (ownedEntity.spec?.type ?? ownedEntity.kind), - ); - if (match) { - match.count += 1; - } else { - acc.push({ - kind: ownedEntity.kind, - type: ownedEntity.spec?.type?.toString() ?? ownedEntity.kind, - count: 1, - }); - } - return acc; - }, - [], - ); - - // Return top N (six) entities to be displayed in ownership boxes - const topN = counts.sort((a, b) => b.count - a.count).slice(0, 6); - - return topN.map(topOwnedEntity => ({ - counter: topOwnedEntity.count, - type: topOwnedEntity.type, - name: topOwnedEntity.type.toLocaleUpperCase('en-US'), - queryParams: getQueryParams(entity, topOwnedEntity), - })) as Array<{ - counter: number; - type: string; - name: string; - queryParams: string; - }>; - }, [catalogApi, entity]); - - if (loading) { - return ; - } else if (error) { - return ; - } + const isGroup = entity.kind === 'Group'; + const renderedGrid = + relationsType !== 'direct' && isGroup + ? transitiveRelationsGrid(entity, entityFilterKind) + : directRelationsGrid(entity, entityFilterKind); return ( - - {componentsWithCounters?.map(c => ( - - - - ))} - + + + + + Direct Relations + + + relationsType === 'direct' + ? setRelationsType('transitive') + : setRelationsType('direct') + } + name="pin" + inputProps={{ 'aria-label': 'Pin Sidebar Switch' }} + disabled={!isGroup} + /> + + Transitive Relations + + + + {renderedGrid} ); }; diff --git a/plugins/org/src/components/Cards/OwnershipCard/TransitiveRelationsGrid.tsx b/plugins/org/src/components/Cards/OwnershipCard/TransitiveRelationsGrid.tsx new file mode 100644 index 0000000000..8ed4d1c413 --- /dev/null +++ b/plugins/org/src/components/Cards/OwnershipCard/TransitiveRelationsGrid.tsx @@ -0,0 +1,122 @@ +/* + * Copyright 2020 The Backstage Authors + * + * 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 } from '@backstage/catalog-model'; +import { Link, Progress, ResponseErrorPanel } from '@backstage/core-components'; +import { useRouteRef } from '@backstage/core-plugin-api'; +import { BackstageTheme } from '@backstage/theme'; +import { + Box, + createStyles, + Grid, + makeStyles, + Typography, +} from '@material-ui/core'; +import React from 'react'; +import pluralize from 'pluralize'; +import { catalogIndexRouteRef } from '../../../routes'; +import { useTransitiveEntities } from './useTransitiveEntities'; + +const useStyles = makeStyles((theme: BackstageTheme) => + createStyles({ + card: { + border: `1px solid ${theme.palette.divider}`, + boxShadow: theme.shadows[2], + borderRadius: '4px', + padding: theme.spacing(2), + color: '#fff', + transition: `${theme.transitions.duration.standard}ms`, + '&:hover': { + boxShadow: theme.shadows[4], + }, + }, + bold: { + fontWeight: theme.typography.fontWeightBold, + }, + entityTypeBox: { + background: (props: { type: string }) => + theme.getPageTheme({ themeId: props.type }).backgroundImage, + }, + }), +); + +const EntityCountTile = ({ + counter, + type, + name, + url, +}: { + counter: number; + type: string; + name: string; + url: string; +}) => { + const classes = useStyles({ type }); + + return ( + + + + {counter} + + + {pluralize(name, counter)} + + + + ); +}; + +// can only be used for group entity +export const TransitiveRelationsGrid = ({ + entity, + entityFilterKind, +}: { + entity: Entity; + entityFilterKind?: string[]; +}) => { + const catalogLink = useRouteRef(catalogIndexRouteRef); + const { componentsWithCounters, loading, error } = useTransitiveEntities( + entity, + entityFilterKind, + ); + + if (loading) { + return ; + } else if (error) { + return ; + } + + return ( + + {componentsWithCounters?.map(c => ( + + + + ))} + + ); +}; diff --git a/plugins/org/src/components/Cards/OwnershipCard/useDirectEntities.ts b/plugins/org/src/components/Cards/OwnershipCard/useDirectEntities.ts new file mode 100644 index 0000000000..5e12d839de --- /dev/null +++ b/plugins/org/src/components/Cards/OwnershipCard/useDirectEntities.ts @@ -0,0 +1,138 @@ +/* + * Copyright 2020 The Backstage Authors + * + * 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, UserEntity } from '@backstage/catalog-model'; +import { useApi } from '@backstage/core-plugin-api'; +import { + catalogApiRef, + formatEntityRefTitle, + isOwnerOf, +} from '@backstage/plugin-catalog-react'; +import qs from 'qs'; +import { useAsync } from 'react-use'; + +type EntityTypeProps = { + kind: string; + type: string; + count: number; +}; + +const getQueryParams = ( + owner: Entity, + selectedEntity: EntityTypeProps, +): string => { + const ownerName = formatEntityRefTitle(owner, { defaultKind: 'group' }); + const { kind, type } = selectedEntity; + const filters = { + kind, + type, + owners: [ownerName], + user: 'all', + }; + if (owner.kind === 'User') { + const user = owner as UserEntity; + filters.owners = [...filters.owners, ...user.spec.memberOf]; + } + const queryParams = qs.stringify({ + filters, + }); + + return queryParams; +}; + +export function useDirectEntities( + entity: Entity, + entityFilterKind?: string[], +): { + componentsWithCounters: + | { + counter: number; + type: string; + name: string; + queryParams: string; + }[] + | undefined; + loading: boolean; + error?: Error; +} { + const catalogApi = useApi(catalogApiRef); + + const { + loading, + error, + value: componentsWithCounters, + } = useAsync(async () => { + const kinds = entityFilterKind ?? ['Component', 'API', 'System']; + const entitiesList = await catalogApi.getEntities({ + filter: { + kind: kinds, + }, + fields: [ + 'kind', + 'metadata.name', + 'metadata.namespace', + 'spec.type', + 'relations', + ], + }); + + const ownedEntitiesList = entitiesList.items.filter(component => + isOwnerOf(entity, component), + ); + + const counts = ownedEntitiesList.reduce( + (acc: EntityTypeProps[], ownedEntity) => { + const match = acc.find( + x => + x.kind === ownedEntity.kind && + x.type === (ownedEntity.spec?.type ?? ownedEntity.kind), + ); + if (match) { + match.count += 1; + } else { + acc.push({ + kind: ownedEntity.kind, + type: ownedEntity.spec?.type?.toString() ?? ownedEntity.kind, + count: 1, + }); + } + return acc; + }, + [], + ); + + // Return top N (six) entities to be displayed in ownership boxes + const topN = counts.sort((a, b) => b.count - a.count).slice(0, 6); + + return topN.map(topOwnedEntity => ({ + counter: topOwnedEntity.count, + type: topOwnedEntity.type, + name: topOwnedEntity.type.toLocaleUpperCase('en-US'), + queryParams: getQueryParams(entity, topOwnedEntity), + })) as Array<{ + counter: number; + type: string; + name: string; + queryParams: string; + }>; + }, [catalogApi, entity]); + + return { + componentsWithCounters, + loading, + error, + }; +} diff --git a/plugins/org/src/components/Cards/OwnershipCard/useTransitiveEntities.ts b/plugins/org/src/components/Cards/OwnershipCard/useTransitiveEntities.ts new file mode 100644 index 0000000000..f2665ec798 --- /dev/null +++ b/plugins/org/src/components/Cards/OwnershipCard/useTransitiveEntities.ts @@ -0,0 +1,180 @@ +/* + * Copyright 2020 The Backstage Authors + * + * 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_PARENT_OF } from '@backstage/catalog-model'; +import { + catalogApiRef, + getEntityRelations, +} from '@backstage/plugin-catalog-react'; +import limiterFactory from 'p-limit'; +import { useApi } from '@backstage/core-plugin-api'; +import { useAsync } from 'react-use'; +import { CatalogListResponse } from '@backstage/catalog-client'; +import qs from 'qs'; + +const limiter = limiterFactory(10); + +type EntityTypeProps = { + kind: string; + type: string; + count: number; +}; + +const getQueryParams = ( + owners: string[], + selectedEntity: EntityTypeProps, +): string => { + const { kind, type } = selectedEntity; + const filters = { + kind, + type, + owners, + user: 'all', + }; + const queryParams = qs.stringify({ + filters, + }); + + return queryParams; +}; + +export function useTransitiveEntities( + entity: Entity, + entityFilterKind?: string[], +): { + componentsWithCounters: + | { + counter: number; + type: string; + name: string; + queryParams: string; + }[] + | undefined; + loading: boolean; + error?: Error; +} { + const catalogApi = useApi(catalogApiRef); + const requestedEntities: Entity[] = []; + const outstandingEntities = new Map>(); + const processedEntities = new Set(); + requestedEntities.push(entity); + let isLoop = true; + let currentEntity = entity; + const kinds = entityFilterKind ?? ['Component', 'API', 'System']; + + const { + loading, + error, + value: componentsWithCounters, + } = useAsync(async () => { + while (isLoop) { + const childRelations = getEntityRelations( + currentEntity, + RELATION_PARENT_OF, + { + kind: 'Group', + }, + ); + + await Promise.all( + childRelations.map(childGroup => + limiter(async () => { + const promise = catalogApi.getEntityByName({ + kind: 'Group', + namespace: 'default', + name: childGroup.name, + }); + + outstandingEntities.set(childGroup.name, promise); + try { + const processedEntity = await promise; + if (processedEntity) { + requestedEntities.push(processedEntity); + } + } finally { + outstandingEntities.delete(childGroup.name); + } + }), + ), + ); + requestedEntities.shift(); + processedEntities.add(currentEntity.metadata.name); + currentEntity = requestedEntities[0]; + if (requestedEntities.length === 0) isLoop = false; + } + + const owners = Array.from(processedEntities); + + const ownedAggregationEntitiesList: CatalogListResponse = + await catalogApi.getEntities({ + filter: [ + { + kind: kinds, + 'spec.owner': owners, + }, + ], + fields: [ + 'kind', + 'metadata.name', + 'metadata.namespace', + 'spec.type', + 'relations', + ], + }); + + const counts = ownedAggregationEntitiesList.items.reduce( + (acc: EntityTypeProps[], ownedEntity) => { + const match = acc.find( + x => + x.kind === ownedEntity.kind && + x.type === (ownedEntity.spec?.type ?? ownedEntity.kind), + ); + if (match) { + match.count += 1; + } else { + acc.push({ + kind: ownedEntity.kind, + type: ownedEntity.spec?.type?.toString() ?? ownedEntity.kind, + count: 1, + }); + } + return acc; + }, + [], + ); + + // Return top N (six) entities to be displayed in ownership boxes + const topN = counts.sort((a, b) => b.count - a.count).slice(0, 6); + + return topN.map(topOwnedEntity => ({ + counter: topOwnedEntity.count, + type: topOwnedEntity.type, + name: topOwnedEntity.type.toLocaleUpperCase('en-US'), + queryParams: getQueryParams(owners, topOwnedEntity), + })) as Array<{ + counter: number; + type: string; + name: string; + queryParams: string; + }>; + }, [catalogApi, entity]); + + return { + componentsWithCounters, + loading, + error, + }; +} From 19fe9bfafb6b22d67ad437d959fde8e57ea34a12 Mon Sep 17 00:00:00 2001 From: Prasetya Aria Wibawa Date: Thu, 17 Feb 2022 07:02:42 +0700 Subject: [PATCH 02/24] add checked props to switch component Signed-off-by: Prasetya Aria Wibawa --- .../org/src/components/Cards/OwnershipCard/OwnershipCard.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.tsx b/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.tsx index 9ede02ce6e..d524c25c51 100644 --- a/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.tsx +++ b/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.tsx @@ -78,10 +78,10 @@ export const OwnershipCard = ({ variant?: InfoCardVariants; entityFilterKind?: string[]; }) => { - const [relationsType, setRelationsType] = useState('direct'); const classes = useStyles(); const { entity } = useEntity(); const isGroup = entity.kind === 'Group'; + const [relationsType, setRelationsType] = useState('direct'); const renderedGrid = relationsType !== 'direct' && isGroup ? transitiveRelationsGrid(entity, entityFilterKind) @@ -102,7 +102,7 @@ export const OwnershipCard = ({ > relationsType === 'direct' ? setRelationsType('transitive') From 111995470d715054cfaeb0083a17897e6deccfeb Mon Sep 17 00:00:00 2001 From: Prasetya Aria Wibawa Date: Thu, 17 Feb 2022 07:15:49 +0700 Subject: [PATCH 03/24] add changeset Signed-off-by: Prasetya Aria Wibawa --- .changeset/mighty-suns-drop.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/mighty-suns-drop.md diff --git a/.changeset/mighty-suns-drop.md b/.changeset/mighty-suns-drop.md new file mode 100644 index 0000000000..4f34ad14ad --- /dev/null +++ b/.changeset/mighty-suns-drop.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-org': patch +--- + +add transtive ownership type for kind group in OwnershipCard From 539a0d754c88d08c6f49abbfc2af857b7f791477 Mon Sep 17 00:00:00 2001 From: Prasetya Aria Wibawa Date: Thu, 17 Feb 2022 07:30:18 +0700 Subject: [PATCH 04/24] remove catalogListResponse from import statement Signed-off-by: Prasetya Aria Wibawa --- .../OwnershipCard/useTransitiveEntities.ts | 32 +++++++++---------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/plugins/org/src/components/Cards/OwnershipCard/useTransitiveEntities.ts b/plugins/org/src/components/Cards/OwnershipCard/useTransitiveEntities.ts index f2665ec798..de03b4b617 100644 --- a/plugins/org/src/components/Cards/OwnershipCard/useTransitiveEntities.ts +++ b/plugins/org/src/components/Cards/OwnershipCard/useTransitiveEntities.ts @@ -22,7 +22,6 @@ import { import limiterFactory from 'p-limit'; import { useApi } from '@backstage/core-plugin-api'; import { useAsync } from 'react-use'; -import { CatalogListResponse } from '@backstage/catalog-client'; import qs from 'qs'; const limiter = limiterFactory(10); @@ -118,22 +117,21 @@ export function useTransitiveEntities( const owners = Array.from(processedEntities); - const ownedAggregationEntitiesList: CatalogListResponse = - await catalogApi.getEntities({ - filter: [ - { - kind: kinds, - 'spec.owner': owners, - }, - ], - fields: [ - 'kind', - 'metadata.name', - 'metadata.namespace', - 'spec.type', - 'relations', - ], - }); + const ownedAggregationEntitiesList = await catalogApi.getEntities({ + filter: [ + { + kind: kinds, + 'spec.owner': owners, + }, + ], + fields: [ + 'kind', + 'metadata.name', + 'metadata.namespace', + 'spec.type', + 'relations', + ], + }); const counts = ownedAggregationEntitiesList.items.reduce( (acc: EntityTypeProps[], ownedEntity) => { From b0dcb1289f16cd3103cd1d90ed7a33d33c6c61d8 Mon Sep 17 00:00:00 2001 From: Prasetya Aria Wibawa Date: Thu, 17 Feb 2022 07:42:14 +0700 Subject: [PATCH 05/24] fix typo comment in changeset Signed-off-by: Prasetya Aria Wibawa --- .changeset/mighty-suns-drop.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/mighty-suns-drop.md b/.changeset/mighty-suns-drop.md index 4f34ad14ad..80f7b88e2b 100644 --- a/.changeset/mighty-suns-drop.md +++ b/.changeset/mighty-suns-drop.md @@ -2,4 +2,4 @@ '@backstage/plugin-org': patch --- -add transtive ownership type for kind group in OwnershipCard +add transitive ownership type for kind group in OwnershipCard From 8e9ddcc5f04aecad14130ab73b8f033245ea0c12 Mon Sep 17 00:00:00 2001 From: Prasetya Aria Wibawa Date: Thu, 17 Feb 2022 08:59:53 +0700 Subject: [PATCH 06/24] change label props Signed-off-by: Prasetya Aria Wibawa --- .../org/src/components/Cards/OwnershipCard/OwnershipCard.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.tsx b/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.tsx index d524c25c51..18dd57cc1d 100644 --- a/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.tsx +++ b/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.tsx @@ -109,7 +109,7 @@ export const OwnershipCard = ({ : setRelationsType('direct') } name="pin" - inputProps={{ 'aria-label': 'Pin Sidebar Switch' }} + inputProps={{ 'aria-label': 'Ownership Type Switch' }} disabled={!isGroup} /> From 21f99270bbbc6b32387944dae816a944d59e95a7 Mon Sep 17 00:00:00 2001 From: Prasetya Aria Wibawa Date: Fri, 18 Feb 2022 16:52:33 +0700 Subject: [PATCH 07/24] change transitive word to aggregated Signed-off-by: Prasetya Aria Wibawa --- .changeset/mighty-suns-drop.md | 2 +- ...lationsGrid.tsx => AggregatedRelationsGrid.tsx} | 6 +++--- .../Cards/OwnershipCard/OwnershipCard.tsx | 14 +++++++------- ...nsitiveEntities.ts => useAggregatedEntities.ts} | 2 +- 4 files changed, 12 insertions(+), 12 deletions(-) rename plugins/org/src/components/Cards/OwnershipCard/{TransitiveRelationsGrid.tsx => AggregatedRelationsGrid.tsx} (94%) rename plugins/org/src/components/Cards/OwnershipCard/{useTransitiveEntities.ts => useAggregatedEntities.ts} (99%) diff --git a/.changeset/mighty-suns-drop.md b/.changeset/mighty-suns-drop.md index 80f7b88e2b..f8bb686aaf 100644 --- a/.changeset/mighty-suns-drop.md +++ b/.changeset/mighty-suns-drop.md @@ -2,4 +2,4 @@ '@backstage/plugin-org': patch --- -add transitive ownership type for kind group in OwnershipCard +add aggregated ownership type for kind group in OwnershipCard diff --git a/plugins/org/src/components/Cards/OwnershipCard/TransitiveRelationsGrid.tsx b/plugins/org/src/components/Cards/OwnershipCard/AggregatedRelationsGrid.tsx similarity index 94% rename from plugins/org/src/components/Cards/OwnershipCard/TransitiveRelationsGrid.tsx rename to plugins/org/src/components/Cards/OwnershipCard/AggregatedRelationsGrid.tsx index 8ed4d1c413..4839e4db56 100644 --- a/plugins/org/src/components/Cards/OwnershipCard/TransitiveRelationsGrid.tsx +++ b/plugins/org/src/components/Cards/OwnershipCard/AggregatedRelationsGrid.tsx @@ -28,7 +28,7 @@ import { import React from 'react'; import pluralize from 'pluralize'; import { catalogIndexRouteRef } from '../../../routes'; -import { useTransitiveEntities } from './useTransitiveEntities'; +import { useAggregatedEntities } from './useAggregatedEntities'; const useStyles = makeStyles((theme: BackstageTheme) => createStyles({ @@ -86,7 +86,7 @@ const EntityCountTile = ({ }; // can only be used for group entity -export const TransitiveRelationsGrid = ({ +export const AggregatedRelationsGrid = ({ entity, entityFilterKind, }: { @@ -94,7 +94,7 @@ export const TransitiveRelationsGrid = ({ entityFilterKind?: string[]; }) => { const catalogLink = useRouteRef(catalogIndexRouteRef); - const { componentsWithCounters, loading, error } = useTransitiveEntities( + const { componentsWithCounters, loading, error } = useAggregatedEntities( entity, entityFilterKind, ); diff --git a/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.tsx b/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.tsx index 18dd57cc1d..4612fce931 100644 --- a/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.tsx +++ b/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.tsx @@ -28,7 +28,7 @@ import { } from '@material-ui/core'; import React, { useState } from 'react'; import { DirectRelationsGrid } from './DirectRelationsGrid'; -import { TransitiveRelationsGrid } from './TransitiveRelationsGrid'; +import { AggregatedRelationsGrid } from './AggregatedRelationsGrid'; const useStyles = makeStyles(theme => ({ list: { @@ -59,12 +59,12 @@ const directRelationsGrid = (entity: Entity, entityFilterKind?: string[]) => { ); }; -const transitiveRelationsGrid = ( +const aggregatedRelationsGrid = ( entity: Entity, entityFilterKind?: string[], ) => { return ( - @@ -84,7 +84,7 @@ export const OwnershipCard = ({ const [relationsType, setRelationsType] = useState('direct'); const renderedGrid = relationsType !== 'direct' && isGroup - ? transitiveRelationsGrid(entity, entityFilterKind) + ? aggregatedRelationsGrid(entity, entityFilterKind) : directRelationsGrid(entity, entityFilterKind); return ( @@ -97,7 +97,7 @@ export const OwnershipCard = ({ relationsType === 'direct' - ? setRelationsType('transitive') + ? setRelationsType('aggregated') : setRelationsType('direct') } name="pin" @@ -113,7 +113,7 @@ export const OwnershipCard = ({ disabled={!isGroup} /> - Transitive Relations + Aggregated Relations diff --git a/plugins/org/src/components/Cards/OwnershipCard/useTransitiveEntities.ts b/plugins/org/src/components/Cards/OwnershipCard/useAggregatedEntities.ts similarity index 99% rename from plugins/org/src/components/Cards/OwnershipCard/useTransitiveEntities.ts rename to plugins/org/src/components/Cards/OwnershipCard/useAggregatedEntities.ts index de03b4b617..2707ba70e7 100644 --- a/plugins/org/src/components/Cards/OwnershipCard/useTransitiveEntities.ts +++ b/plugins/org/src/components/Cards/OwnershipCard/useAggregatedEntities.ts @@ -50,7 +50,7 @@ const getQueryParams = ( return queryParams; }; -export function useTransitiveEntities( +export function useAggregatedEntities( entity: Entity, entityFilterKind?: string[], ): { From fce26e8204035629396e95fe9a2397da92952999 Mon Sep 17 00:00:00 2001 From: Prasetya Aria Wibawa Date: Fri, 18 Feb 2022 22:05:40 +0700 Subject: [PATCH 08/24] change to use supported version of useAsync Signed-off-by: Prasetya Aria Wibawa --- .../src/components/Cards/OwnershipCard/useAggregatedEntities.ts | 2 +- .../org/src/components/Cards/OwnershipCard/useDirectEntities.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/org/src/components/Cards/OwnershipCard/useAggregatedEntities.ts b/plugins/org/src/components/Cards/OwnershipCard/useAggregatedEntities.ts index 2707ba70e7..e8fb2d52dc 100644 --- a/plugins/org/src/components/Cards/OwnershipCard/useAggregatedEntities.ts +++ b/plugins/org/src/components/Cards/OwnershipCard/useAggregatedEntities.ts @@ -21,7 +21,7 @@ import { } from '@backstage/plugin-catalog-react'; import limiterFactory from 'p-limit'; import { useApi } from '@backstage/core-plugin-api'; -import { useAsync } from 'react-use'; +import useAsync from 'react-use/lib/useAsync'; import qs from 'qs'; const limiter = limiterFactory(10); diff --git a/plugins/org/src/components/Cards/OwnershipCard/useDirectEntities.ts b/plugins/org/src/components/Cards/OwnershipCard/useDirectEntities.ts index 5e12d839de..48e6c9d74b 100644 --- a/plugins/org/src/components/Cards/OwnershipCard/useDirectEntities.ts +++ b/plugins/org/src/components/Cards/OwnershipCard/useDirectEntities.ts @@ -22,7 +22,7 @@ import { isOwnerOf, } from '@backstage/plugin-catalog-react'; import qs from 'qs'; -import { useAsync } from 'react-use'; +import useAsync from 'react-use/lib/useAsync'; type EntityTypeProps = { kind: string; From 3e1a4ed473b1253a4612e0c55dc220fb1b6b0a2b Mon Sep 17 00:00:00 2001 From: Prasetya Aria Wibawa Date: Fri, 18 Feb 2022 22:31:13 +0700 Subject: [PATCH 09/24] use additional kind system for OwnershipCard test Signed-off-by: Prasetya Aria Wibawa --- .../src/components/Cards/OwnershipCard/OwnershipCard.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.test.tsx b/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.test.tsx index 483cf9744f..c48e06d7a7 100644 --- a/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.test.tsx +++ b/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.test.tsx @@ -160,7 +160,7 @@ describe('OwnershipCard', () => { ); expect(catalogApi.getEntities).toHaveBeenCalledWith({ - filter: { kind: ['Component', 'API'] }, + filter: { kind: ['Component', 'API', 'System'] }, fields: [ 'kind', 'metadata.name', From 8183a00eae2bdf278b3129101f9ac62a33729ed5 Mon Sep 17 00:00:00 2001 From: Prasetya Aria Wibawa Date: Fri, 18 Feb 2022 22:52:04 +0700 Subject: [PATCH 10/24] change system test expectation for displays entity counts case Signed-off-by: Prasetya Aria Wibawa --- .../src/components/Cards/OwnershipCard/OwnershipCard.test.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.test.tsx b/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.test.tsx index c48e06d7a7..911dd2fb68 100644 --- a/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.test.tsx +++ b/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.test.tsx @@ -182,7 +182,9 @@ describe('OwnershipCard', () => { expect( queryByText(getByText('LIBRARY').parentElement!, '1'), ).toBeInTheDocument(); - expect(() => getByText('SYSTEM')).toThrowError(); + expect( + queryByText(getByText('SYSTEM').parentElement!, '1'), + ).toBeInTheDocument(); }); it('applies CustomFilterDefinition', async () => { From 5606d6065b07e1efc4debd31b7703df65b29f3e9 Mon Sep 17 00:00:00 2001 From: Prasetya Aria Wibawa Date: Wed, 2 Mar 2022 16:34:10 +0700 Subject: [PATCH 11/24] use getEntityRelations to get all memberOf groups Signed-off-by: Prasetya Aria Wibawa --- .../Cards/OwnershipCard/useDirectEntities.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/plugins/org/src/components/Cards/OwnershipCard/useDirectEntities.ts b/plugins/org/src/components/Cards/OwnershipCard/useDirectEntities.ts index 48e6c9d74b..9ad4843040 100644 --- a/plugins/org/src/components/Cards/OwnershipCard/useDirectEntities.ts +++ b/plugins/org/src/components/Cards/OwnershipCard/useDirectEntities.ts @@ -14,12 +14,13 @@ * limitations under the License. */ -import { Entity, UserEntity } from '@backstage/catalog-model'; +import { Entity, RELATION_MEMBER_OF } from '@backstage/catalog-model'; import { useApi } from '@backstage/core-plugin-api'; import { catalogApiRef, formatEntityRefTitle, isOwnerOf, + getEntityRelations, } from '@backstage/plugin-catalog-react'; import qs from 'qs'; import useAsync from 'react-use/lib/useAsync'; @@ -43,8 +44,11 @@ const getQueryParams = ( user: 'all', }; if (owner.kind === 'User') { - const user = owner as UserEntity; - filters.owners = [...filters.owners, ...user.spec.memberOf]; + const ownerGroups = getEntityRelations(owner, RELATION_MEMBER_OF, { + kind: 'Group', + }); + const ownerGroupsName = ownerGroups.map(ownerGroup => ownerGroup.name); + filters.owners = [...filters.owners, ...ownerGroupsName]; } const queryParams = qs.stringify({ filters, From b759a21c96ef61f1b70c5af1f48ac6e3a40359f2 Mon Sep 17 00:00:00 2001 From: Prasetya Aria Wibawa Date: Wed, 2 Mar 2022 16:56:32 +0700 Subject: [PATCH 12/24] change isOwnerOf to use filter relations.ownedBy Signed-off-by: Prasetya Aria Wibawa --- .../Cards/OwnershipCard/useDirectEntities.ts | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/plugins/org/src/components/Cards/OwnershipCard/useDirectEntities.ts b/plugins/org/src/components/Cards/OwnershipCard/useDirectEntities.ts index 9ad4843040..da50842fea 100644 --- a/plugins/org/src/components/Cards/OwnershipCard/useDirectEntities.ts +++ b/plugins/org/src/components/Cards/OwnershipCard/useDirectEntities.ts @@ -14,12 +14,15 @@ * limitations under the License. */ -import { Entity, RELATION_MEMBER_OF } from '@backstage/catalog-model'; +import { + Entity, + RELATION_MEMBER_OF, + stringifyEntityRef, +} from '@backstage/catalog-model'; import { useApi } from '@backstage/core-plugin-api'; import { catalogApiRef, formatEntityRefTitle, - isOwnerOf, getEntityRelations, } from '@backstage/plugin-catalog-react'; import qs from 'qs'; @@ -80,10 +83,13 @@ export function useDirectEntities( value: componentsWithCounters, } = useAsync(async () => { const kinds = entityFilterKind ?? ['Component', 'API', 'System']; - const entitiesList = await catalogApi.getEntities({ - filter: { - kind: kinds, - }, + const ownedEntitiesListResponse = await catalogApi.getEntities({ + filter: [ + { + kind: kinds, + 'relations.ownedBy': [stringifyEntityRef(entity)], + }, + ], fields: [ 'kind', 'metadata.name', @@ -93,11 +99,7 @@ export function useDirectEntities( ], }); - const ownedEntitiesList = entitiesList.items.filter(component => - isOwnerOf(entity, component), - ); - - const counts = ownedEntitiesList.reduce( + const counts = ownedEntitiesListResponse.items.reduce( (acc: EntityTypeProps[], ownedEntity) => { const match = acc.find( x => From d9497b9b6e62cbd11dfe063a3fde64d9bc9b5541 Mon Sep 17 00:00:00 2001 From: Prasetya Aria Wibawa Date: Wed, 2 Mar 2022 18:38:35 +0700 Subject: [PATCH 13/24] change filterKinds in OwnershipCard test Signed-off-by: Prasetya Aria Wibawa --- .../Cards/OwnershipCard/OwnershipCard.test.tsx | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.test.tsx b/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.test.tsx index 911dd2fb68..4314c280cf 100644 --- a/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.test.tsx +++ b/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.test.tsx @@ -112,9 +112,9 @@ const getEntitiesMock = ( request?: GetEntitiesRequest, ): Promise => { const filterKinds = - !Array.isArray(request?.filter) && Array.isArray(request?.filter?.kind) - ? request?.filter?.kind ?? [] - : []; // we expect the request to be like { filter: { kind: ['API','System'], .... }. If changed in OwnerShipCard, let's change in also here + Array.isArray(request?.filter) && Array.isArray(request?.filter[0].kind) + ? request?.filter[0].kind ?? [] + : []; // we expect the request to be like { filter: [{ kind: ['API','System'], 'relations.ownedBy': [group:default/my-team], .... }]. If changed in OwnerShipCard, let's change in also here return Promise.resolve({ items: items.filter(item => filterKinds.find(k => k === item.kind)), } as GetEntitiesResponse); @@ -160,7 +160,13 @@ describe('OwnershipCard', () => { ); expect(catalogApi.getEntities).toHaveBeenCalledWith({ - filter: { kind: ['Component', 'API', 'System'] }, + filter: [ + { + kind: ['Component', 'API', 'System'], + 'relations.ownedBy': ['group:default/my-team'], + }, + ], + // filter: { kind: ['Component', 'API', 'System'] }, fields: [ 'kind', 'metadata.name', @@ -182,6 +188,7 @@ describe('OwnershipCard', () => { expect( queryByText(getByText('LIBRARY').parentElement!, '1'), ).toBeInTheDocument(); + expect(getByText('SYSTEM')).toBeInTheDocument(); expect( queryByText(getByText('SYSTEM').parentElement!, '1'), ).toBeInTheDocument(); From 303bc1c7fbbecb333554500ae02e4ba4dc50720b Mon Sep 17 00:00:00 2001 From: Prasetya Aria Wibawa Date: Fri, 4 Mar 2022 09:22:11 +0700 Subject: [PATCH 14/24] use prettier Signed-off-by: Prasetya Aria Wibawa --- .../org/src/components/Cards/OwnershipCard/OwnershipCard.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.tsx b/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.tsx index 4612fce931..37541a786a 100644 --- a/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.tsx +++ b/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.tsx @@ -97,8 +97,9 @@ export const OwnershipCard = ({ Date: Fri, 4 Mar 2022 10:59:10 +0700 Subject: [PATCH 15/24] change to use getEntityByRef Signed-off-by: Prasetya Aria Wibawa --- .../Cards/OwnershipCard/useAggregatedEntities.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/plugins/org/src/components/Cards/OwnershipCard/useAggregatedEntities.ts b/plugins/org/src/components/Cards/OwnershipCard/useAggregatedEntities.ts index e8fb2d52dc..45595acb78 100644 --- a/plugins/org/src/components/Cards/OwnershipCard/useAggregatedEntities.ts +++ b/plugins/org/src/components/Cards/OwnershipCard/useAggregatedEntities.ts @@ -91,12 +91,7 @@ export function useAggregatedEntities( await Promise.all( childRelations.map(childGroup => limiter(async () => { - const promise = catalogApi.getEntityByName({ - kind: 'Group', - namespace: 'default', - name: childGroup.name, - }); - + const promise = catalogApi.getEntityByRef(childGroup); outstandingEntities.set(childGroup.name, promise); try { const processedEntity = await promise; From da70c0ef1a038c02f7d3e498bf8161593e98ded4 Mon Sep 17 00:00:00 2001 From: Prasetya Aria Wibawa Date: Fri, 4 Mar 2022 11:37:55 +0700 Subject: [PATCH 16/24] check looping from length of requestedEntities Signed-off-by: Prasetya Aria Wibawa --- .../components/Cards/OwnershipCard/useAggregatedEntities.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/plugins/org/src/components/Cards/OwnershipCard/useAggregatedEntities.ts b/plugins/org/src/components/Cards/OwnershipCard/useAggregatedEntities.ts index 45595acb78..e6ef55b32b 100644 --- a/plugins/org/src/components/Cards/OwnershipCard/useAggregatedEntities.ts +++ b/plugins/org/src/components/Cards/OwnershipCard/useAggregatedEntities.ts @@ -70,7 +70,6 @@ export function useAggregatedEntities( const outstandingEntities = new Map>(); const processedEntities = new Set(); requestedEntities.push(entity); - let isLoop = true; let currentEntity = entity; const kinds = entityFilterKind ?? ['Component', 'API', 'System']; @@ -79,7 +78,7 @@ export function useAggregatedEntities( error, value: componentsWithCounters, } = useAsync(async () => { - while (isLoop) { + while (requestedEntities.length > 0) { const childRelations = getEntityRelations( currentEntity, RELATION_PARENT_OF, @@ -106,12 +105,11 @@ export function useAggregatedEntities( ); requestedEntities.shift(); processedEntities.add(currentEntity.metadata.name); + // always set currentEntity to the first element of array requestedEntities currentEntity = requestedEntities[0]; - if (requestedEntities.length === 0) isLoop = false; } const owners = Array.from(processedEntities); - const ownedAggregationEntitiesList = await catalogApi.getEntities({ filter: [ { From 4a790094ee77817bfedf21ba06eba5a932c8e385 Mon Sep 17 00:00:00 2001 From: Prasetya Aria Wibawa Date: Fri, 4 Mar 2022 11:38:41 +0700 Subject: [PATCH 17/24] use humanizeEntityRef Signed-off-by: Prasetya Aria Wibawa --- .../Cards/OwnershipCard/useDirectEntities.ts | 29 +++++++++++++++---- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/plugins/org/src/components/Cards/OwnershipCard/useDirectEntities.ts b/plugins/org/src/components/Cards/OwnershipCard/useDirectEntities.ts index da50842fea..20d4c3a76d 100644 --- a/plugins/org/src/components/Cards/OwnershipCard/useDirectEntities.ts +++ b/plugins/org/src/components/Cards/OwnershipCard/useDirectEntities.ts @@ -22,8 +22,8 @@ import { import { useApi } from '@backstage/core-plugin-api'; import { catalogApiRef, - formatEntityRefTitle, getEntityRelations, + humanizeEntityRef, } from '@backstage/plugin-catalog-react'; import qs from 'qs'; import useAsync from 'react-use/lib/useAsync'; @@ -38,7 +38,7 @@ const getQueryParams = ( owner: Entity, selectedEntity: EntityTypeProps, ): string => { - const ownerName = formatEntityRefTitle(owner, { defaultKind: 'group' }); + const ownerName = humanizeEntityRef(owner, { defaultKind: 'group' }); const { kind, type } = selectedEntity; const filters = { kind, @@ -60,6 +60,24 @@ const getQueryParams = ( return queryParams; }; +const getOwnersEntityRef = (owner: Entity): string[] => { + let owners = [stringifyEntityRef(owner)]; + if (owner.kind === 'User') { + const ownerGroups = getEntityRelations(owner, RELATION_MEMBER_OF, { + kind: 'Group', + }); + const ownerGroupsName = ownerGroups.map(ownerGroup => + stringifyEntityRef({ + kind: ownerGroup.kind, + namespace: ownerGroup.namespace, + name: ownerGroup.name, + }), + ); + owners = [...owners, ...ownerGroupsName]; + } + return owners; +}; + export function useDirectEntities( entity: Entity, entityFilterKind?: string[], @@ -83,11 +101,12 @@ export function useDirectEntities( value: componentsWithCounters, } = useAsync(async () => { const kinds = entityFilterKind ?? ['Component', 'API', 'System']; - const ownedEntitiesListResponse = await catalogApi.getEntities({ + const owners = getOwnersEntityRef(entity); + const ownedEntitiesList = await catalogApi.getEntities({ filter: [ { kind: kinds, - 'relations.ownedBy': [stringifyEntityRef(entity)], + 'relations.ownedBy': owners, }, ], fields: [ @@ -99,7 +118,7 @@ export function useDirectEntities( ], }); - const counts = ownedEntitiesListResponse.items.reduce( + const counts = ownedEntitiesList.items.reduce( (acc: EntityTypeProps[], ownedEntity) => { const match = acc.find( x => From 80b47162be6cf8403c3aab7d42582481e5747e21 Mon Sep 17 00:00:00 2001 From: Prasetya Aria Wibawa Date: Fri, 4 Mar 2022 12:09:41 +0700 Subject: [PATCH 18/24] fix failing test for links to the catalog with group filter Signed-off-by: Prasetya Aria Wibawa --- .../components/Cards/OwnershipCard/OwnershipCard.test.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.test.tsx b/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.test.tsx index 4314c280cf..6c9b25fd08 100644 --- a/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.test.tsx +++ b/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.test.tsx @@ -166,7 +166,6 @@ describe('OwnershipCard', () => { 'relations.ownedBy': ['group:default/my-team'], }, ], - // filter: { kind: ['Component', 'API', 'System'] }, fields: [ 'kind', 'metadata.name', @@ -247,7 +246,7 @@ describe('OwnershipCard', () => { expect(getByText('OPENAPI').closest('a')).toHaveAttribute( 'href', - '/create/?filters%5Bkind%5D=API&filters%5Btype%5D=openapi&filters%5Bowners%5D=my-team&filters%5Buser%5D=all', + '/create/?filters%5Bkind%5D=API&filters%5Btype%5D=openapi&filters%5Bowners%5D%5B0%5D=my-team&filters%5Buser%5D=all', ); }); @@ -289,7 +288,7 @@ describe('OwnershipCard', () => { expect(getByText('OPENAPI').closest('a')).toHaveAttribute( 'href', - '/create/?filters%5Bkind%5D=API&filters%5Btype%5D=openapi&filters%5Bowners%5D=user%3Athe-user&filters%5Bowners%5D=my-team&filters%5Buser%5D=all', + '/create/?filters%5Bkind%5D=API&filters%5Btype%5D=openapi&filters%5Bowners%5D%5B0%5D=user%3Athe-user&filters%5Bowners%5D%5B1%5D=my-team&filters%5Buser%5D=all', ); }); }); From e4d5b8c7fff92b0520cc7b346632277c76febc8b Mon Sep 17 00:00:00 2001 From: Prasetya Aria Wibawa Date: Fri, 4 Mar 2022 15:13:45 +0700 Subject: [PATCH 19/24] rename owner to ownerEntity for getQueryParams Signed-off-by: Prasetya Aria Wibawa --- .../components/Cards/OwnershipCard/useDirectEntities.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/org/src/components/Cards/OwnershipCard/useDirectEntities.ts b/plugins/org/src/components/Cards/OwnershipCard/useDirectEntities.ts index 20d4c3a76d..6b8886a5e6 100644 --- a/plugins/org/src/components/Cards/OwnershipCard/useDirectEntities.ts +++ b/plugins/org/src/components/Cards/OwnershipCard/useDirectEntities.ts @@ -35,10 +35,10 @@ type EntityTypeProps = { }; const getQueryParams = ( - owner: Entity, + ownerEntity: Entity, selectedEntity: EntityTypeProps, ): string => { - const ownerName = humanizeEntityRef(owner, { defaultKind: 'group' }); + const ownerName = humanizeEntityRef(ownerEntity, { defaultKind: 'group' }); const { kind, type } = selectedEntity; const filters = { kind, @@ -46,8 +46,8 @@ const getQueryParams = ( owners: [ownerName], user: 'all', }; - if (owner.kind === 'User') { - const ownerGroups = getEntityRelations(owner, RELATION_MEMBER_OF, { + if (ownerEntity.kind === 'User') { + const ownerGroups = getEntityRelations(ownerEntity, RELATION_MEMBER_OF, { kind: 'Group', }); const ownerGroupsName = ownerGroups.map(ownerGroup => ownerGroup.name); From 69d652f3cc4b6071a004ba816c52a503812853ba Mon Sep 17 00:00:00 2001 From: Prasetya Aria Wibawa Date: Fri, 4 Mar 2022 15:15:17 +0700 Subject: [PATCH 20/24] use relations.ownedBy for filter params in getEntities Signed-off-by: Prasetya Aria Wibawa --- .../OwnershipCard/useAggregatedEntities.ts | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/plugins/org/src/components/Cards/OwnershipCard/useAggregatedEntities.ts b/plugins/org/src/components/Cards/OwnershipCard/useAggregatedEntities.ts index e6ef55b32b..52390cccef 100644 --- a/plugins/org/src/components/Cards/OwnershipCard/useAggregatedEntities.ts +++ b/plugins/org/src/components/Cards/OwnershipCard/useAggregatedEntities.ts @@ -14,7 +14,11 @@ * limitations under the License. */ -import { Entity, RELATION_PARENT_OF } from '@backstage/catalog-model'; +import { + Entity, + RELATION_PARENT_OF, + stringifyEntityRef, +} from '@backstage/catalog-model'; import { catalogApiRef, getEntityRelations, @@ -33,10 +37,12 @@ type EntityTypeProps = { }; const getQueryParams = ( - owners: string[], + ownerEntitiesRef: string[], selectedEntity: EntityTypeProps, ): string => { const { kind, type } = selectedEntity; + // removing 'group:default/' from the string entity ref 'group:default/team-a' + const owners = ownerEntitiesRef.map(owner => owner.split('/')[1]); const filters = { kind, type, @@ -104,7 +110,13 @@ export function useAggregatedEntities( ), ); requestedEntities.shift(); - processedEntities.add(currentEntity.metadata.name); + processedEntities.add( + stringifyEntityRef({ + kind: currentEntity.kind, + namespace: currentEntity.metadata.namespace, + name: currentEntity.metadata.name, + }), + ); // always set currentEntity to the first element of array requestedEntities currentEntity = requestedEntities[0]; } @@ -114,7 +126,7 @@ export function useAggregatedEntities( filter: [ { kind: kinds, - 'spec.owner': owners, + 'relations.ownedBy': owners, }, ], fields: [ From 103b72b704acfe9b7eb259e0b444e8eccdd517b9 Mon Sep 17 00:00:00 2001 From: Prasetya Aria Wibawa Date: Thu, 24 Mar 2022 14:20:20 +0700 Subject: [PATCH 21/24] update to just use one hooks and one component grid in ownership card Signed-off-by: Prasetya Aria Wibawa --- ...edRelationsGrid.tsx => ComponentsGrid.tsx} | 9 +- .../OwnershipCard/DirectRelationsGrid.tsx | 122 ------------ .../Cards/OwnershipCard/OwnershipCard.tsx | 32 +-- .../OwnershipCard/useAggregatedEntities.ts | 183 ------------------ ...useDirectEntities.ts => useGetEntities.ts} | 88 +++++++-- 5 files changed, 82 insertions(+), 352 deletions(-) rename plugins/org/src/components/Cards/OwnershipCard/{AggregatedRelationsGrid.tsx => ComponentsGrid.tsx} (93%) delete mode 100644 plugins/org/src/components/Cards/OwnershipCard/DirectRelationsGrid.tsx delete mode 100644 plugins/org/src/components/Cards/OwnershipCard/useAggregatedEntities.ts rename plugins/org/src/components/Cards/OwnershipCard/{useDirectEntities.ts => useGetEntities.ts} (62%) diff --git a/plugins/org/src/components/Cards/OwnershipCard/AggregatedRelationsGrid.tsx b/plugins/org/src/components/Cards/OwnershipCard/ComponentsGrid.tsx similarity index 93% rename from plugins/org/src/components/Cards/OwnershipCard/AggregatedRelationsGrid.tsx rename to plugins/org/src/components/Cards/OwnershipCard/ComponentsGrid.tsx index 4839e4db56..e62013d688 100644 --- a/plugins/org/src/components/Cards/OwnershipCard/AggregatedRelationsGrid.tsx +++ b/plugins/org/src/components/Cards/OwnershipCard/ComponentsGrid.tsx @@ -28,7 +28,7 @@ import { import React from 'react'; import pluralize from 'pluralize'; import { catalogIndexRouteRef } from '../../../routes'; -import { useAggregatedEntities } from './useAggregatedEntities'; +import { useGetEntities } from './useGetEntities'; const useStyles = makeStyles((theme: BackstageTheme) => createStyles({ @@ -86,16 +86,19 @@ const EntityCountTile = ({ }; // can only be used for group entity -export const AggregatedRelationsGrid = ({ +export const ComponentsGrid = ({ entity, + relationsType, entityFilterKind, }: { entity: Entity; + relationsType: string; entityFilterKind?: string[]; }) => { const catalogLink = useRouteRef(catalogIndexRouteRef); - const { componentsWithCounters, loading, error } = useAggregatedEntities( + const { componentsWithCounters, loading, error } = useGetEntities( entity, + relationsType, entityFilterKind, ); diff --git a/plugins/org/src/components/Cards/OwnershipCard/DirectRelationsGrid.tsx b/plugins/org/src/components/Cards/OwnershipCard/DirectRelationsGrid.tsx deleted file mode 100644 index a7cd521ecc..0000000000 --- a/plugins/org/src/components/Cards/OwnershipCard/DirectRelationsGrid.tsx +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * 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 } from '@backstage/catalog-model'; -import { Link, Progress, ResponseErrorPanel } from '@backstage/core-components'; -import { useRouteRef } from '@backstage/core-plugin-api'; -import { BackstageTheme } from '@backstage/theme'; -import { - Box, - createStyles, - Grid, - makeStyles, - Typography, -} from '@material-ui/core'; -import React from 'react'; -import pluralize from 'pluralize'; -import { catalogIndexRouteRef } from '../../../routes'; -import { useDirectEntities } from './useDirectEntities'; - -const useStyles = makeStyles((theme: BackstageTheme) => - createStyles({ - card: { - border: `1px solid ${theme.palette.divider}`, - boxShadow: theme.shadows[2], - borderRadius: '4px', - padding: theme.spacing(2), - color: '#fff', - transition: `${theme.transitions.duration.standard}ms`, - '&:hover': { - boxShadow: theme.shadows[4], - }, - }, - bold: { - fontWeight: theme.typography.fontWeightBold, - }, - entityTypeBox: { - background: (props: { type: string }) => - theme.getPageTheme({ themeId: props.type }).backgroundImage, - }, - }), -); - -const EntityCountTile = ({ - counter, - type, - name, - url, -}: { - counter: number; - type: string; - name: string; - url: string; -}) => { - const classes = useStyles({ type }); - - return ( - - - - {counter} - - - {pluralize(name, counter)} - - - - ); -}; - -export const DirectRelationsGrid = ({ - entity, - entityFilterKind, -}: { - entity: Entity; - entityFilterKind?: string[]; -}) => { - const catalogLink = useRouteRef(catalogIndexRouteRef); - - const { componentsWithCounters, loading, error } = useDirectEntities( - entity, - entityFilterKind, - ); - - if (loading) { - return ; - } else if (error) { - return ; - } - - return ( - - {componentsWithCounters?.map(c => ( - - - - ))} - - ); -}; diff --git a/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.tsx b/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.tsx index 37541a786a..f188e6de01 100644 --- a/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.tsx +++ b/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.tsx @@ -14,7 +14,6 @@ * limitations under the License. */ -import { Entity } from '@backstage/catalog-model'; import { InfoCard, InfoCardVariants } from '@backstage/core-components'; import { useEntity } from '@backstage/plugin-catalog-react'; import { @@ -27,8 +26,7 @@ import { Tooltip, } from '@material-ui/core'; import React, { useState } from 'react'; -import { DirectRelationsGrid } from './DirectRelationsGrid'; -import { AggregatedRelationsGrid } from './AggregatedRelationsGrid'; +import { ComponentsGrid } from './ComponentsGrid'; const useStyles = makeStyles(theme => ({ list: { @@ -53,24 +51,6 @@ const useStyles = makeStyles(theme => ({ }, })); -const directRelationsGrid = (entity: Entity, entityFilterKind?: string[]) => { - return ( - - ); -}; - -const aggregatedRelationsGrid = ( - entity: Entity, - entityFilterKind?: string[], -) => { - return ( - - ); -}; - export const OwnershipCard = ({ variant, entityFilterKind, @@ -82,10 +62,6 @@ export const OwnershipCard = ({ const { entity } = useEntity(); const isGroup = entity.kind === 'Group'; const [relationsType, setRelationsType] = useState('direct'); - const renderedGrid = - relationsType !== 'direct' && isGroup - ? aggregatedRelationsGrid(entity, entityFilterKind) - : directRelationsGrid(entity, entityFilterKind); return ( @@ -118,7 +94,11 @@ export const OwnershipCard = ({ - {renderedGrid} + ); }; diff --git a/plugins/org/src/components/Cards/OwnershipCard/useAggregatedEntities.ts b/plugins/org/src/components/Cards/OwnershipCard/useAggregatedEntities.ts deleted file mode 100644 index 52390cccef..0000000000 --- a/plugins/org/src/components/Cards/OwnershipCard/useAggregatedEntities.ts +++ /dev/null @@ -1,183 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * 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_PARENT_OF, - stringifyEntityRef, -} from '@backstage/catalog-model'; -import { - catalogApiRef, - getEntityRelations, -} from '@backstage/plugin-catalog-react'; -import limiterFactory from 'p-limit'; -import { useApi } from '@backstage/core-plugin-api'; -import useAsync from 'react-use/lib/useAsync'; -import qs from 'qs'; - -const limiter = limiterFactory(10); - -type EntityTypeProps = { - kind: string; - type: string; - count: number; -}; - -const getQueryParams = ( - ownerEntitiesRef: string[], - selectedEntity: EntityTypeProps, -): string => { - const { kind, type } = selectedEntity; - // removing 'group:default/' from the string entity ref 'group:default/team-a' - const owners = ownerEntitiesRef.map(owner => owner.split('/')[1]); - const filters = { - kind, - type, - owners, - user: 'all', - }; - const queryParams = qs.stringify({ - filters, - }); - - return queryParams; -}; - -export function useAggregatedEntities( - entity: Entity, - entityFilterKind?: string[], -): { - componentsWithCounters: - | { - counter: number; - type: string; - name: string; - queryParams: string; - }[] - | undefined; - loading: boolean; - error?: Error; -} { - const catalogApi = useApi(catalogApiRef); - const requestedEntities: Entity[] = []; - const outstandingEntities = new Map>(); - const processedEntities = new Set(); - requestedEntities.push(entity); - let currentEntity = entity; - const kinds = entityFilterKind ?? ['Component', 'API', 'System']; - - const { - loading, - error, - value: componentsWithCounters, - } = useAsync(async () => { - while (requestedEntities.length > 0) { - const childRelations = getEntityRelations( - currentEntity, - RELATION_PARENT_OF, - { - kind: 'Group', - }, - ); - - await Promise.all( - childRelations.map(childGroup => - limiter(async () => { - const promise = catalogApi.getEntityByRef(childGroup); - outstandingEntities.set(childGroup.name, promise); - try { - const processedEntity = await promise; - if (processedEntity) { - requestedEntities.push(processedEntity); - } - } finally { - outstandingEntities.delete(childGroup.name); - } - }), - ), - ); - requestedEntities.shift(); - processedEntities.add( - stringifyEntityRef({ - kind: currentEntity.kind, - namespace: currentEntity.metadata.namespace, - name: currentEntity.metadata.name, - }), - ); - // always set currentEntity to the first element of array requestedEntities - currentEntity = requestedEntities[0]; - } - - const owners = Array.from(processedEntities); - const ownedAggregationEntitiesList = await catalogApi.getEntities({ - filter: [ - { - kind: kinds, - 'relations.ownedBy': owners, - }, - ], - fields: [ - 'kind', - 'metadata.name', - 'metadata.namespace', - 'spec.type', - 'relations', - ], - }); - - const counts = ownedAggregationEntitiesList.items.reduce( - (acc: EntityTypeProps[], ownedEntity) => { - const match = acc.find( - x => - x.kind === ownedEntity.kind && - x.type === (ownedEntity.spec?.type ?? ownedEntity.kind), - ); - if (match) { - match.count += 1; - } else { - acc.push({ - kind: ownedEntity.kind, - type: ownedEntity.spec?.type?.toString() ?? ownedEntity.kind, - count: 1, - }); - } - return acc; - }, - [], - ); - - // Return top N (six) entities to be displayed in ownership boxes - const topN = counts.sort((a, b) => b.count - a.count).slice(0, 6); - - return topN.map(topOwnedEntity => ({ - counter: topOwnedEntity.count, - type: topOwnedEntity.type, - name: topOwnedEntity.type.toLocaleUpperCase('en-US'), - queryParams: getQueryParams(owners, topOwnedEntity), - })) as Array<{ - counter: number; - type: string; - name: string; - queryParams: string; - }>; - }, [catalogApi, entity]); - - return { - componentsWithCounters, - loading, - error, - }; -} diff --git a/plugins/org/src/components/Cards/OwnershipCard/useDirectEntities.ts b/plugins/org/src/components/Cards/OwnershipCard/useGetEntities.ts similarity index 62% rename from plugins/org/src/components/Cards/OwnershipCard/useDirectEntities.ts rename to plugins/org/src/components/Cards/OwnershipCard/useGetEntities.ts index 6b8886a5e6..d8a269c868 100644 --- a/plugins/org/src/components/Cards/OwnershipCard/useDirectEntities.ts +++ b/plugins/org/src/components/Cards/OwnershipCard/useGetEntities.ts @@ -17,16 +17,20 @@ import { Entity, RELATION_MEMBER_OF, + RELATION_PARENT_OF, stringifyEntityRef, } from '@backstage/catalog-model'; -import { useApi } from '@backstage/core-plugin-api'; import { + CatalogApi, catalogApiRef, getEntityRelations, - humanizeEntityRef, } from '@backstage/plugin-catalog-react'; -import qs from 'qs'; +import limiterFactory from 'p-limit'; +import { useApi } from '@backstage/core-plugin-api'; import useAsync from 'react-use/lib/useAsync'; +import qs from 'qs'; + +const limiter = limiterFactory(10); type EntityTypeProps = { kind: string; @@ -35,24 +39,17 @@ type EntityTypeProps = { }; const getQueryParams = ( - ownerEntity: Entity, + ownersEntityRef: string[], selectedEntity: EntityTypeProps, ): string => { - const ownerName = humanizeEntityRef(ownerEntity, { defaultKind: 'group' }); const { kind, type } = selectedEntity; + const owners = ownersEntityRef.map(owner => owner.split('/')[1]); const filters = { kind, type, - owners: [ownerName], + owners, user: 'all', }; - if (ownerEntity.kind === 'User') { - const ownerGroups = getEntityRelations(ownerEntity, RELATION_MEMBER_OF, { - kind: 'Group', - }); - const ownerGroupsName = ownerGroups.map(ownerGroup => ownerGroup.name); - filters.owners = [...filters.owners, ...ownerGroupsName]; - } const queryParams = qs.stringify({ filters, }); @@ -78,8 +75,59 @@ const getOwnersEntityRef = (owner: Entity): string[] => { return owners; }; -export function useDirectEntities( +const getAggregatedOwnersEntityRef = async ( + parentGroup: Entity, + catalogApi: CatalogApi, +): Promise => { + const requestedEntities: Entity[] = []; + const outstandingEntities = new Map>(); + const processedEntities = new Set(); + requestedEntities.push(parentGroup); + let currentEntity = parentGroup; + + while (requestedEntities.length > 0) { + const childRelations = getEntityRelations( + currentEntity, + RELATION_PARENT_OF, + { + kind: 'Group', + }, + ); + + await Promise.all( + childRelations.map(childGroup => + limiter(async () => { + const promise = catalogApi.getEntityByRef(childGroup); + outstandingEntities.set(childGroup.name, promise); + try { + const processedEntity = await promise; + if (processedEntity) { + requestedEntities.push(processedEntity); + } + } finally { + outstandingEntities.delete(childGroup.name); + } + }), + ), + ); + requestedEntities.shift(); + processedEntities.add( + stringifyEntityRef({ + kind: currentEntity.kind, + namespace: currentEntity.metadata.namespace, + name: currentEntity.metadata.name, + }), + ); + // always set currentEntity to the first element of array requestedEntities + currentEntity = requestedEntities[0]; + } + + return Array.from(processedEntities); +}; + +export function useGetEntities( entity: Entity, + relationsType: string, entityFilterKind?: string[], ): { componentsWithCounters: @@ -94,14 +142,18 @@ export function useDirectEntities( error?: Error; } { const catalogApi = useApi(catalogApiRef); + const kinds = entityFilterKind ?? ['Component', 'API', 'System']; + const isGroup = entity.kind === 'Group'; const { loading, error, value: componentsWithCounters, } = useAsync(async () => { - const kinds = entityFilterKind ?? ['Component', 'API', 'System']; - const owners = getOwnersEntityRef(entity); + const owners = + relationsType === 'aggregated' && isGroup + ? await getAggregatedOwnersEntityRef(entity, catalogApi) + : getOwnersEntityRef(entity); const ownedEntitiesList = await catalogApi.getEntities({ filter: [ { @@ -146,14 +198,14 @@ export function useDirectEntities( counter: topOwnedEntity.count, type: topOwnedEntity.type, name: topOwnedEntity.type.toLocaleUpperCase('en-US'), - queryParams: getQueryParams(entity, topOwnedEntity), + queryParams: getQueryParams(owners, topOwnedEntity), })) as Array<{ counter: number; type: string; name: string; queryParams: string; }>; - }, [catalogApi, entity]); + }, [catalogApi, entity, relationsType]); return { componentsWithCounters, From 102e6efea595e583ab99619a63b5abf58455c147 Mon Sep 17 00:00:00 2001 From: Prasetya Aria Wibawa Date: Thu, 24 Mar 2022 14:35:08 +0700 Subject: [PATCH 22/24] fix test ownership card Signed-off-by: Prasetya Aria Wibawa --- .../src/components/Cards/OwnershipCard/OwnershipCard.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.test.tsx b/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.test.tsx index 6c9b25fd08..dcaae2d4bf 100644 --- a/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.test.tsx +++ b/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.test.tsx @@ -288,7 +288,7 @@ describe('OwnershipCard', () => { expect(getByText('OPENAPI').closest('a')).toHaveAttribute( 'href', - '/create/?filters%5Bkind%5D=API&filters%5Btype%5D=openapi&filters%5Bowners%5D%5B0%5D=user%3Athe-user&filters%5Bowners%5D%5B1%5D=my-team&filters%5Buser%5D=all', + '/create/?filters%5Bkind%5D=API&filters%5Btype%5D=openapi&filters%5Bowners%5D%5B0%5D=the-user&filters%5Bowners%5D%5B1%5D=my-team&filters%5Buser%5D=all', ); }); }); From f12cb33bd57949c0f6844f61de3423ffce542a3b Mon Sep 17 00:00:00 2001 From: Prasetya Aria Wibawa Date: Thu, 24 Mar 2022 14:56:43 +0700 Subject: [PATCH 23/24] passing down isGroup value from ownershipCard to useGetEntities Signed-off-by: Prasetya Aria Wibawa --- .../org/src/components/Cards/OwnershipCard/ComponentsGrid.tsx | 3 +++ .../org/src/components/Cards/OwnershipCard/OwnershipCard.tsx | 1 + .../org/src/components/Cards/OwnershipCard/useGetEntities.ts | 2 +- 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/plugins/org/src/components/Cards/OwnershipCard/ComponentsGrid.tsx b/plugins/org/src/components/Cards/OwnershipCard/ComponentsGrid.tsx index e62013d688..4b14487a29 100644 --- a/plugins/org/src/components/Cards/OwnershipCard/ComponentsGrid.tsx +++ b/plugins/org/src/components/Cards/OwnershipCard/ComponentsGrid.tsx @@ -89,16 +89,19 @@ const EntityCountTile = ({ export const ComponentsGrid = ({ entity, relationsType, + isGroup, entityFilterKind, }: { entity: Entity; relationsType: string; + isGroup: boolean; entityFilterKind?: string[]; }) => { const catalogLink = useRouteRef(catalogIndexRouteRef); const { componentsWithCounters, loading, error } = useGetEntities( entity, relationsType, + isGroup, entityFilterKind, ); diff --git a/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.tsx b/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.tsx index f188e6de01..13e8502b7a 100644 --- a/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.tsx +++ b/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.tsx @@ -97,6 +97,7 @@ export const OwnershipCard = ({ diff --git a/plugins/org/src/components/Cards/OwnershipCard/useGetEntities.ts b/plugins/org/src/components/Cards/OwnershipCard/useGetEntities.ts index d8a269c868..48760002fa 100644 --- a/plugins/org/src/components/Cards/OwnershipCard/useGetEntities.ts +++ b/plugins/org/src/components/Cards/OwnershipCard/useGetEntities.ts @@ -128,6 +128,7 @@ const getAggregatedOwnersEntityRef = async ( export function useGetEntities( entity: Entity, relationsType: string, + isGroup: boolean, entityFilterKind?: string[], ): { componentsWithCounters: @@ -143,7 +144,6 @@ export function useGetEntities( } { const catalogApi = useApi(catalogApiRef); const kinds = entityFilterKind ?? ['Component', 'API', 'System']; - const isGroup = entity.kind === 'Group'; const { loading, From 0e5c9b2c8aaec700c7ce6d34b90d3999d65e98e3 Mon Sep 17 00:00:00 2001 From: Prasetya Aria Wibawa Date: Thu, 24 Mar 2022 16:14:16 +0700 Subject: [PATCH 24/24] remove unused comment Signed-off-by: Prasetya Aria Wibawa --- .../org/src/components/Cards/OwnershipCard/ComponentsGrid.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/org/src/components/Cards/OwnershipCard/ComponentsGrid.tsx b/plugins/org/src/components/Cards/OwnershipCard/ComponentsGrid.tsx index 4b14487a29..6f7680849c 100644 --- a/plugins/org/src/components/Cards/OwnershipCard/ComponentsGrid.tsx +++ b/plugins/org/src/components/Cards/OwnershipCard/ComponentsGrid.tsx @@ -85,7 +85,6 @@ const EntityCountTile = ({ ); }; -// can only be used for group entity export const ComponentsGrid = ({ entity, relationsType,