Merge pull request #6823 from prasaria/link-group-ownership-boxes-through-filtered-catalog-page
Link group ownership boxes through filtered catalog page
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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', () => {
|
||||
<OwnershipCard />
|
||||
</EntityProvider>
|
||||
</ApiProvider>,
|
||||
{
|
||||
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();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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<Entity>,
|
||||
kind: EntitiesKinds,
|
||||
type?: EntitiesTypes,
|
||||
) =>
|
||||
entities.filter(
|
||||
e => e.kind === kind && (type ? e?.spec?.type === type : true),
|
||||
);
|
||||
|
||||
const countEntitiesBy = (
|
||||
entities: Array<Entity>,
|
||||
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 (
|
||||
<Tooltip title={entityNames} arrow>
|
||||
<Link href={url} target="_blank" rel="noreferrer noopenner" variant="body2">
|
||||
<Box
|
||||
className={`${classes.card} ${classes[className]}`}
|
||||
className={`${classes.card} ${classes.entityTypeBox}`}
|
||||
display="flex"
|
||||
flexDirection="column"
|
||||
alignItems="center"
|
||||
@@ -149,10 +109,28 @@ const EntityCountTile = ({
|
||||
{name}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Tooltip>
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
|
||||
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 = ({
|
||||
<Grid item xs={6} md={6} lg={4} key={c.name}>
|
||||
<EntityCountTile
|
||||
counter={c.counter}
|
||||
className={c.className}
|
||||
entities={c.entities}
|
||||
type={c.type}
|
||||
name={c.name}
|
||||
url={generatePath(`${catalogLink()}/?${c.queryParams}`)}
|
||||
/>
|
||||
</Grid>
|
||||
))}
|
||||
|
||||
Reference in New Issue
Block a user