diff --git a/.changeset/cool-dolphins-join.md b/.changeset/cool-dolphins-join.md new file mode 100644 index 0000000000..496cc307e5 --- /dev/null +++ b/.changeset/cool-dolphins-join.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-org': patch +--- + +Link group ownership boxes through filtered catalog page diff --git a/plugins/org/package.json b/plugins/org/package.json index 59c86188a8..74bebe7a10 100644 --- a/plugins/org/package.json +++ b/plugins/org/package.json @@ -32,7 +32,8 @@ "react-dom": "^16.13.1", "react-router": "6.0.0-beta.0", "react-router-dom": "6.0.0-beta.0", - "react-use": "^17.2.4" + "react-use": "^17.2.4", + "qs": "^6.10.1" }, "devDependencies": { "@backstage/cli": "^0.7.9", diff --git a/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.test.tsx b/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.test.tsx index bfaca73bc0..eab3606f77 100644 --- a/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.test.tsx +++ b/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.test.tsx @@ -19,6 +19,7 @@ import { CatalogApi, catalogApiRef, EntityProvider, + catalogRouteRef, } from '@backstage/plugin-catalog-react'; import { renderInTestApp } from '@backstage/test-utils'; import { queryByText } from '@testing-library/react'; @@ -123,31 +124,24 @@ describe('OwnershipCard', () => { , + { + mountedRoutes: { + '/create': catalogRouteRef, + }, + }, ); - expect(getByText('Services')).toBeInTheDocument(); + expect(getByText('OPENAPI')).toBeInTheDocument(); expect( - queryByText(getByText('Services').parentElement!, '1'), + queryByText(getByText('OPENAPI').parentElement!, '1'), ).toBeInTheDocument(); - expect(getByText('Documentation')).toBeInTheDocument(); + expect(getByText('SERVICE')).toBeInTheDocument(); expect( - queryByText(getByText('Documentation').parentElement!, '0'), + queryByText(getByText('SERVICE').parentElement!, '1'), ).toBeInTheDocument(); - expect(getByText('APIs')).toBeInTheDocument(); + expect(getByText('LIBRARY')).toBeInTheDocument(); expect( - queryByText(getByText('APIs').parentElement!, '1'), - ).toBeInTheDocument(); - expect(getByText('Libraries')).toBeInTheDocument(); - expect( - queryByText(getByText('Libraries').parentElement!, '1'), - ).toBeInTheDocument(); - expect(getByText('Websites')).toBeInTheDocument(); - expect( - queryByText(getByText('Websites').parentElement!, '0'), - ).toBeInTheDocument(); - expect(getByText('Tools')).toBeInTheDocument(); - expect( - queryByText(getByText('Tools').parentElement!, '0'), + queryByText(getByText('LIBRARY').parentElement!, '1'), ).toBeInTheDocument(); }); }); diff --git a/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.tsx b/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.tsx index 1b82b58389..83768f1513 100644 --- a/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.tsx +++ b/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.tsx @@ -19,6 +19,7 @@ import { catalogApiRef, isOwnerOf, useEntity, + catalogRouteRef, } from '@backstage/plugin-catalog-react'; import { BackstageTheme, genPageTheme } from '@backstage/theme'; import { @@ -26,28 +27,26 @@ import { createStyles, Grid, makeStyles, - Tooltip, Typography, + Link, } from '@material-ui/core'; import React from 'react'; import { useAsync } from 'react-use'; - +import { generatePath } from 'react-router'; +import qs from 'qs'; import { InfoCard, InfoCardVariants, Progress, ResponseErrorPanel, } from '@backstage/core-components'; -import { useApi } from '@backstage/core-plugin-api'; +import { useApi, useRouteRef } from '@backstage/core-plugin-api'; -type EntitiesKinds = 'Component' | 'API'; -type EntitiesTypes = - | 'service' - | 'website' - | 'library' - | 'documentation' - | 'api' - | 'tool'; +type EntityTypeProps = { + kind: string; + type: string; + count: number; +}; const createPageTheme = ( theme: BackstageTheme, @@ -75,69 +74,30 @@ const useStyles = makeStyles((theme: BackstageTheme) => bold: { fontWeight: theme.typography.fontWeightBold, }, - service: { - background: createPageTheme(theme, 'home', 'service'), - }, - website: { - background: createPageTheme(theme, 'home', 'website'), - }, - library: { - background: createPageTheme(theme, 'home', 'library'), - }, - documentation: { - background: createPageTheme(theme, 'home', 'documentation'), - }, - api: { - background: createPageTheme(theme, 'home', 'home'), - }, - tool: { - background: createPageTheme(theme, 'home', 'tool'), + entityTypeBox: { + background: (props: { type: string }) => + createPageTheme(theme, props.type, props.type), }, }), ); -const listEntitiesBy = ( - entities: Array, - kind: EntitiesKinds, - type?: EntitiesTypes, -) => - entities.filter( - e => e.kind === kind && (type ? e?.spec?.type === type : true), - ); - -const countEntitiesBy = ( - entities: Array, - kind: EntitiesKinds, - type?: EntitiesTypes, -) => listEntitiesBy(entities, kind, type).length; - const EntityCountTile = ({ counter, - className, - entities, + type, name, + url, }: { counter: number; - className: EntitiesTypes; - entities: Entity[]; + type: string; name: string; + url: string; }) => { - let entityNames; - const classes = useStyles(); - - if (entities.length < 20) { - entityNames = entities.map(e => e.metadata.name).join(', '); - } else { - entityNames = `${entities - .map(e => e.metadata.name) - .slice(0, 20) - .join(', ')}, ...`; - } + const classes = useStyles({ type }); return ( - + - + ); }; +const getQueryParams = ( + owner: Entity, + selectedEntity: EntityTypeProps, +): string => { + const ownerName = owner.metadata.name; + const { kind, type } = selectedEntity; + const queryParams = qs.stringify({ + filters: { + kind, + type, + owners: ownerName, + user: 'all', + }, + }); + + return queryParams; +}; + export const OwnershipCard = ({ variant, }: { @@ -162,6 +140,8 @@ export const OwnershipCard = ({ }) => { const { entity } = useEntity(); const catalogApi = useApi(catalogApiRef); + const catalogLink = useRouteRef(catalogRouteRef); + const { loading, error, @@ -185,56 +165,40 @@ export const OwnershipCard = ({ isOwnerOf(entity, component), ); - return [ - { - counter: countEntitiesBy(ownedEntitiesList, 'Component', 'service'), - className: 'service', - entities: listEntitiesBy(ownedEntitiesList, 'Component', 'service'), - name: 'Services', + const counts = ownedEntitiesList.reduce( + (acc: EntityTypeProps[], ownedEntity) => { + if (typeof ownedEntity.spec?.type !== 'string') return acc; + + const match = acc.find( + x => x.kind === ownedEntity.kind && x.type === ownedEntity.spec?.type, + ); + if (match) { + match.count += 1; + } else { + acc.push({ + kind: ownedEntity.kind, + type: ownedEntity.spec?.type, + count: 1, + }); + } + return acc; }, - { - counter: countEntitiesBy( - ownedEntitiesList, - 'Component', - 'documentation', - ), - className: 'documentation', - entities: listEntitiesBy( - ownedEntitiesList, - 'Component', - 'documentation', - ), - name: 'Documentation', - }, - { - counter: countEntitiesBy(ownedEntitiesList, 'API'), - className: 'api', - entities: listEntitiesBy(ownedEntitiesList, 'API'), - name: 'APIs', - }, - { - counter: countEntitiesBy(ownedEntitiesList, 'Component', 'library'), - className: 'library', - entities: listEntitiesBy(ownedEntitiesList, 'Component', 'library'), - name: 'Libraries', - }, - { - counter: countEntitiesBy(ownedEntitiesList, 'Component', 'website'), - className: 'website', - entities: listEntitiesBy(ownedEntitiesList, 'Component', 'website'), - name: 'Websites', - }, - { - counter: countEntitiesBy(ownedEntitiesList, 'Component', 'tool'), - className: 'tool', - entities: listEntitiesBy(ownedEntitiesList, 'Component', 'tool'), - name: 'Tools', - }, - ] as Array<{ + [], + ); + + // 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; - className: EntitiesTypes; - entities: Entity[]; + type: string; name: string; + queryParams: string; }>; }, [catalogApi, entity]); @@ -251,9 +215,9 @@ export const OwnershipCard = ({ ))}