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,