diff --git a/packages/catalog-model/api-report.md b/packages/catalog-model/api-report.md index 0afe87e709..f27cfa5417 100644 --- a/packages/catalog-model/api-report.md +++ b/packages/catalog-model/api-report.md @@ -231,6 +231,7 @@ export type EntityRefContext = { export type EntityRelation = { type: string; target: EntityName; + targetRef: string; }; // @public diff --git a/plugins/api-docs/src/components/ApiExplorerPage/DefaultApiExplorerPage.test.tsx b/plugins/api-docs/src/components/ApiExplorerPage/DefaultApiExplorerPage.test.tsx index 18222eea3a..16b335b0a7 100644 --- a/plugins/api-docs/src/components/ApiExplorerPage/DefaultApiExplorerPage.test.tsx +++ b/plugins/api-docs/src/components/ApiExplorerPage/DefaultApiExplorerPage.test.tsx @@ -66,7 +66,8 @@ describe('DefaultApiExplorerPage', () => { relations: [ { type: RELATION_MEMBER_OF, - target: { namespace: 'default', kind: 'Group', name: 'tools' }, + targetRef: 'group:default/tools', + target: { namespace: 'default', kind: 'group', name: 'tools' }, }, ], }; diff --git a/plugins/api-docs/src/components/ApisCards/ConsumedApisCard.test.tsx b/plugins/api-docs/src/components/ApisCards/ConsumedApisCard.test.tsx index d04a36770d..f84f66cee8 100644 --- a/plugins/api-docs/src/components/ApisCards/ConsumedApisCard.test.tsx +++ b/plugins/api-docs/src/components/ApisCards/ConsumedApisCard.test.tsx @@ -95,10 +95,11 @@ describe('', () => { relations: [ { target: { - kind: 'API', + kind: 'api', namespace: 'my-namespace', name: 'target-name', }, + targetRef: 'api:my-namespace/target-name', type: RELATION_CONSUMES_API, }, ], diff --git a/plugins/api-docs/src/components/ApisCards/HasApisCard.test.tsx b/plugins/api-docs/src/components/ApisCards/HasApisCard.test.tsx index 050eeb8707..daa909c7f8 100644 --- a/plugins/api-docs/src/components/ApisCards/HasApisCard.test.tsx +++ b/plugins/api-docs/src/components/ApisCards/HasApisCard.test.tsx @@ -95,10 +95,11 @@ describe('', () => { relations: [ { target: { - kind: 'API', + kind: 'api', namespace: 'my-namespace', name: 'target-name', }, + targetRef: 'api:my-namespace/target-name', type: RELATION_HAS_PART, }, ], diff --git a/plugins/api-docs/src/components/ApisCards/ProvidedApisCard.test.tsx b/plugins/api-docs/src/components/ApisCards/ProvidedApisCard.test.tsx index 34043c519b..852386140c 100644 --- a/plugins/api-docs/src/components/ApisCards/ProvidedApisCard.test.tsx +++ b/plugins/api-docs/src/components/ApisCards/ProvidedApisCard.test.tsx @@ -95,10 +95,11 @@ describe('', () => { relations: [ { target: { - kind: 'API', + kind: 'api', namespace: 'my-namespace', name: 'target-name', }, + targetRef: 'api:my-namespace/target-name', type: RELATION_PROVIDES_API, }, ], diff --git a/plugins/api-docs/src/components/ComponentsCards/ConsumingComponentsCard.test.tsx b/plugins/api-docs/src/components/ComponentsCards/ConsumingComponentsCard.test.tsx index c81b4750bc..a62b2119c7 100644 --- a/plugins/api-docs/src/components/ComponentsCards/ConsumingComponentsCard.test.tsx +++ b/plugins/api-docs/src/components/ComponentsCards/ConsumingComponentsCard.test.tsx @@ -98,10 +98,11 @@ describe('', () => { relations: [ { target: { - kind: 'Component', + kind: 'component', namespace: 'my-namespace', name: 'target-name', }, + targetRef: 'component:my-namespace/target-name', type: RELATION_API_CONSUMED_BY, }, ], diff --git a/plugins/api-docs/src/components/ComponentsCards/ProvidingComponentsCard.test.tsx b/plugins/api-docs/src/components/ComponentsCards/ProvidingComponentsCard.test.tsx index 84e45dc058..1a0aae12c7 100644 --- a/plugins/api-docs/src/components/ComponentsCards/ProvidingComponentsCard.test.tsx +++ b/plugins/api-docs/src/components/ComponentsCards/ProvidingComponentsCard.test.tsx @@ -98,10 +98,11 @@ describe('', () => { relations: [ { target: { - kind: 'Component', + kind: 'component', namespace: 'my-namespace', name: 'target-name', }, + targetRef: 'component:my-namespace/target-name', type: RELATION_API_PROVIDED_BY, }, ], diff --git a/plugins/auth-backend/src/lib/catalog/CatalogIdentityClient.test.ts b/plugins/auth-backend/src/lib/catalog/CatalogIdentityClient.test.ts index 4388c2f82f..d2cee7bd89 100644 --- a/plugins/auth-backend/src/lib/catalog/CatalogIdentityClient.test.ts +++ b/plugins/auth-backend/src/lib/catalog/CatalogIdentityClient.test.ts @@ -79,8 +79,9 @@ describe('CatalogIdentityClient', () => { relations: [ { type: RELATION_MEMBER_OF, + targetRef: 'group:default/team-a', target: { - kind: 'Group', + kind: 'group', namespace: 'default', name: 'team-a', }, @@ -100,8 +101,9 @@ describe('CatalogIdentityClient', () => { relations: [ { type: RELATION_MEMBER_OF, + targetRef: 'group:reality/screen-actors-guild', target: { - kind: 'Group', + kind: 'group', namespace: 'reality', name: 'screen-actors-guild', }, diff --git a/plugins/auth-backend/src/lib/catalog/CatalogIdentityClient.ts b/plugins/auth-backend/src/lib/catalog/CatalogIdentityClient.ts index aecf018975..d948af2e05 100644 --- a/plugins/auth-backend/src/lib/catalog/CatalogIdentityClient.ts +++ b/plugins/auth-backend/src/lib/catalog/CatalogIdentityClient.ts @@ -120,11 +120,11 @@ export class CatalogIdentityClient { e => e!.relations ?.filter(r => r.type === RELATION_MEMBER_OF) - .map(r => r.target) ?? [], + .map(r => r.targetRef) ?? [], ); const newEntityRefs = [ - ...new Set(resolvedEntityRefs.concat(memberOf).map(stringifyEntityRef)), + ...new Set(resolvedEntityRefs.map(stringifyEntityRef).concat(memberOf)), ]; logger?.debug(`Found catalog membership: ${newEntityRefs.join()}`); diff --git a/plugins/auth-backend/src/lib/catalog/helpers.ts b/plugins/auth-backend/src/lib/catalog/helpers.ts index 61dc428b3f..db9b38e2a6 100644 --- a/plugins/auth-backend/src/lib/catalog/helpers.ts +++ b/plugins/auth-backend/src/lib/catalog/helpers.ts @@ -27,11 +27,9 @@ export function getEntityClaims(entity: UserEntity): TokenParams['claims'] { const membershipRefs = entity.relations ?.filter( - r => - r.type === RELATION_MEMBER_OF && - r.target.kind.toLocaleLowerCase('en-US') === 'group', + r => r.type === RELATION_MEMBER_OF && r.targetRef.startsWith('group:'), ) - .map(r => stringifyEntityRef(r.target)) ?? []; + .map(r => r.targetRef) ?? []; return { sub: userRef, diff --git a/plugins/catalog-backend/src/catalog/types.ts b/plugins/catalog-backend/src/catalog/types.ts index abf9e4d114..b575b96c6c 100644 --- a/plugins/catalog-backend/src/catalog/types.ts +++ b/plugins/catalog-backend/src/catalog/types.ts @@ -40,7 +40,7 @@ export type EntityPagination = { }; /** - * Matches rows in the entities_search table. + * Matches rows in the search table. * @public */ export type EntitiesSearchFilter = { diff --git a/plugins/catalog-backend/src/permissions/rules/isEntityOwner.test.ts b/plugins/catalog-backend/src/permissions/rules/isEntityOwner.test.ts index 282d2558ef..8e20655393 100644 --- a/plugins/catalog-backend/src/permissions/rules/isEntityOwner.test.ts +++ b/plugins/catalog-backend/src/permissions/rules/isEntityOwner.test.ts @@ -29,6 +29,7 @@ describe('isEntityOwner', () => { relations: [ { type: 'ownedBy', + targetRef: 'user:default/spiderman', target: { kind: 'user', namespace: 'default', @@ -52,6 +53,7 @@ describe('isEntityOwner', () => { relations: [ { type: 'ownedBy', + targetRef: 'user:default/green-goblin', target: { kind: 'user', namespace: 'default', diff --git a/plugins/catalog-backend/src/permissions/rules/isEntityOwner.ts b/plugins/catalog-backend/src/permissions/rules/isEntityOwner.ts index 3176b31b87..a6dda13c29 100644 --- a/plugins/catalog-backend/src/permissions/rules/isEntityOwner.ts +++ b/plugins/catalog-backend/src/permissions/rules/isEntityOwner.ts @@ -14,16 +14,13 @@ * limitations under the License. */ -import { - Entity, - RELATION_OWNED_BY, - stringifyEntityRef, -} from '@backstage/catalog-model'; +import { Entity, RELATION_OWNED_BY } from '@backstage/catalog-model'; import { createCatalogPermissionRule } from './util'; /** * A catalog {@link @backstage/plugin-permission-node#PermissionRule} which * filters for entities with a specified owner. + * * @public */ export const isEntityOwner = createCatalogPermissionRule({ @@ -36,7 +33,7 @@ export const isEntityOwner = createCatalogPermissionRule({ return resource.relations .filter(relation => relation.type === RELATION_OWNED_BY) - .some(relation => claims.includes(stringifyEntityRef(relation.target))); + .some(relation => claims.includes(relation.targetRef)); }, toQuery: (claims: string[]) => ({ key: 'relations.ownedBy', diff --git a/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.test.ts b/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.test.ts index 7094cfd1c2..43e208197f 100644 --- a/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.test.ts +++ b/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.test.ts @@ -469,6 +469,62 @@ describe('DefaultEntitiesCatalog', () => { expect(entities.length).toBe(0); }, ); + + it.each(databases.eachSupportedId())( + 'should return both target and targetRef for entities', + async databaseId => { + const { knex } = await createDatabase(databaseId); + await addEntity( + knex, + { + apiVersion: 'a', + kind: 'k', + metadata: { name: 'one' }, + spec: {}, + relations: [{ type: 'r', targetRef: 'x:y/z' } as any], + }, + [], + ); + await addEntity( + knex, + { + apiVersion: 'a', + kind: 'k', + metadata: { name: 'two' }, + spec: {}, + relations: [ + { + type: 'r', + target: { kind: 'x', namespace: 'y', name: 'z' }, + } as any, + ], + }, + [], + ); + const catalog = new DefaultEntitiesCatalog(knex); + + const { entities } = await catalog.entities(); + + expect( + entities.find(e => e.metadata.name === 'one')!.relations, + ).toEqual([ + { + type: 'r', + targetRef: 'x:y/z', + target: { kind: 'x', namespace: 'y', name: 'z' }, + }, + ]); + expect( + entities.find(e => e.metadata.name === 'two')!.relations, + ).toEqual([ + { + type: 'r', + targetRef: 'x:y/z', + target: { kind: 'x', namespace: 'y', name: 'z' }, + }, + ]); + }, + ); }); describe('removeEntityByUid', () => { diff --git a/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.ts b/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.ts index 1ac70bda30..3b20282d78 100644 --- a/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.ts +++ b/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.ts @@ -14,7 +14,11 @@ * limitations under the License. */ -import { Entity, stringifyEntityRef } from '@backstage/catalog-model'; +import { + Entity, + parseEntityRef, + stringifyEntityRef, +} from '@backstage/catalog-model'; import { InputError, NotFoundError } from '@backstage/errors'; import { Knex } from 'knex'; import lodash from 'lodash'; @@ -194,11 +198,32 @@ export class DefaultEntitiesCatalog implements EntitiesCatalog { }; } - const dbResponse = rows.map(e => JSON.parse(e.final_entity!)); + let entities: Entity[] = rows.map(e => JSON.parse(e.final_entity!)); - const entities = dbResponse.map(e => - request?.fields ? request.fields(e) : e, - ); + if (request?.fields) { + entities = entities.map(e => request.fields!(e)); + } + + // TODO(freben): This is added as a compatibility guarantee, until we can be + // sure that all adopters have re-stitched their entities so that the new + // targetRef field is present on them, and that they have stopped consuming + // the now-removed old field + for (const entity of entities) { + if (entity.relations) { + for (const relation of entity.relations) { + if (!relation.targetRef) { + // This is the case where an old-form entity, not yet stitched with + // the updated code, was in the database + relation.targetRef = stringifyEntityRef(relation.target); + } else if (!relation.target) { + // This is the case where a new-form entity, stitched with the + // updated code, was in the database but we still want to produce + // the old data shape as well for compatibility reasons + relation.target = parseEntityRef(relation.targetRef); + } + } + } + } return { entities, diff --git a/plugins/catalog-backend/src/stitching/Stitcher.test.ts b/plugins/catalog-backend/src/stitching/Stitcher.test.ts index be57ff7083..9df4bedd60 100644 --- a/plugins/catalog-backend/src/stitching/Stitcher.test.ts +++ b/plugins/catalog-backend/src/stitching/Stitcher.test.ts @@ -93,6 +93,7 @@ describe('Stitcher', () => { relations: [ { type: 'looksAt', + targetRef: 'k:ns/other', target: { kind: 'k', namespace: 'ns', @@ -159,6 +160,7 @@ describe('Stitcher', () => { relations: expect.arrayContaining([ { type: 'looksAt', + targetRef: 'k:ns/other', target: { kind: 'k', namespace: 'ns', @@ -167,6 +169,7 @@ describe('Stitcher', () => { }, { type: 'looksAt', + targetRef: 'k:ns/third', target: { kind: 'k', namespace: 'ns', diff --git a/plugins/catalog-backend/src/stitching/Stitcher.ts b/plugins/catalog-backend/src/stitching/Stitcher.ts index 9ec028ed35..018e223e46 100644 --- a/plugins/catalog-backend/src/stitching/Stitcher.ts +++ b/plugins/catalog-backend/src/stitching/Stitcher.ts @@ -186,6 +186,7 @@ export class Stitcher { .map(row => ({ type: row.relationType!, target: parseEntityRef(row.relationTarget!), + targetRef: row.relationTarget!, })); if (statusItems.length) { entity.status = { @@ -210,7 +211,7 @@ export class Stitcher { } // This may throw if the entity is invalid, so we call it before - // the final_entites write, even though we may end up not needing + // the final_entities write, even though we may end up not needing // to write the search index. const searchEntries = buildEntitySearch(entityId, entity); diff --git a/plugins/catalog-backend/src/stitching/buildEntitySearch.test.ts b/plugins/catalog-backend/src/stitching/buildEntitySearch.test.ts index 173538f18f..52e3392676 100644 --- a/plugins/catalog-backend/src/stitching/buildEntitySearch.test.ts +++ b/plugins/catalog-backend/src/stitching/buildEntitySearch.test.ts @@ -141,8 +141,16 @@ describe('buildEntitySearch', () => { it('adds relations', () => { const input: Entity = { relations: [ - { type: 't1', target: { kind: 'k', namespace: 'ns', name: 'a' } }, - { type: 't2', target: { kind: 'k', namespace: 'ns', name: 'b' } }, + { + type: 't1', + targetRef: 'k:ns/a', + target: { kind: 'k', namespace: 'ns', name: 'a' }, + }, + { + type: 't2', + targetRef: 'k:ns/b', + target: { kind: 'k', namespace: 'ns', name: 'b' }, + }, ], apiVersion: 'a', kind: 'b', @@ -182,8 +190,16 @@ describe('buildEntitySearch', () => { expect(() => buildEntitySearch('eid', { relations: [ - { type: 'dup', target: { kind: 'k', namespace: 'ns', name: 'a' } }, - { type: 'DUP', target: { kind: 'k', namespace: 'ns', name: 'b' } }, + { + type: 'dup', + targetRef: 'k:ns/a', + target: { kind: 'k', namespace: 'ns', name: 'a' }, + }, + { + type: 'DUP', + targetRef: 'k:ns/b', + target: { kind: 'k', namespace: 'ns', name: 'b' }, + }, ], apiVersion: 'a', kind: 'b', diff --git a/plugins/catalog-backend/src/stitching/buildEntitySearch.ts b/plugins/catalog-backend/src/stitching/buildEntitySearch.ts index d0506e8998..cd8a3735de 100644 --- a/plugins/catalog-backend/src/stitching/buildEntitySearch.ts +++ b/plugins/catalog-backend/src/stitching/buildEntitySearch.ts @@ -14,11 +14,7 @@ * limitations under the License. */ -import { - Entity, - DEFAULT_NAMESPACE, - stringifyEntityRef, -} from '@backstage/catalog-model'; +import { Entity, DEFAULT_NAMESPACE } from '@backstage/catalog-model'; import { InputError } from '@backstage/errors'; import { DbSearchRow } from '../database/tables'; @@ -181,7 +177,7 @@ export function buildEntitySearch( for (const relation of entity.relations ?? []) { raw.push({ key: `relations.${relation.type}`, - value: stringifyEntityRef(relation.target), + value: relation.targetRef, }); } diff --git a/plugins/catalog-graph/dev/index.tsx b/plugins/catalog-graph/dev/index.tsx index 3ff9285a81..0fceb87e02 100644 --- a/plugins/catalog-graph/dev/index.tsx +++ b/plugins/catalog-graph/dev/index.tsx @@ -118,6 +118,11 @@ const entities = ( }, relations: relations.map(([type, k, n]) => ({ target: { kind: k, name: n, namespace: DEFAULT_NAMESPACE }, + targetRef: stringifyEntityRef({ + kind: k, + namespace: DEFAULT_NAMESPACE, + name: n, + }), type, })), }; diff --git a/plugins/catalog-graph/src/components/CatalogGraphPage/CatalogGraphPage.test.tsx b/plugins/catalog-graph/src/components/CatalogGraphPage/CatalogGraphPage.test.tsx index f98aca0081..2f686f8f62 100644 --- a/plugins/catalog-graph/src/components/CatalogGraphPage/CatalogGraphPage.test.tsx +++ b/plugins/catalog-graph/src/components/CatalogGraphPage/CatalogGraphPage.test.tsx @@ -58,6 +58,7 @@ describe('', () => { relations: [ { type: RELATION_PART_OF, + targetRef: 'b:d/e', target: { kind: 'b', namespace: 'd', @@ -76,6 +77,7 @@ describe('', () => { relations: [ { type: RELATION_HAS_PART, + targetRef: 'b:d/c', target: { kind: 'b', namespace: 'd', diff --git a/plugins/catalog-graph/src/components/EntityRelationsGraph/EntityRelationsGraph.test.tsx b/plugins/catalog-graph/src/components/EntityRelationsGraph/EntityRelationsGraph.test.tsx index f16d06f809..36dc816444 100644 --- a/plugins/catalog-graph/src/components/EntityRelationsGraph/EntityRelationsGraph.test.tsx +++ b/plugins/catalog-graph/src/components/EntityRelationsGraph/EntityRelationsGraph.test.tsx @@ -54,6 +54,7 @@ describe('', () => { name: 'a1', namespace: 'd', }, + targetRef: 'k:d/a1', type: RELATION_OWNER_OF, }, { @@ -62,6 +63,7 @@ describe('', () => { name: 'c1', namespace: 'd', }, + targetRef: 'b:d/c1', type: RELATION_HAS_PART, }, ], @@ -80,6 +82,7 @@ describe('', () => { name: 'c', namespace: 'd', }, + targetRef: 'b:d/c', type: RELATION_OWNED_BY, }, { @@ -88,6 +91,7 @@ describe('', () => { name: 'c1', namespace: 'd', }, + targetRef: 'b:d/c1', type: RELATION_OWNED_BY, }, ], @@ -106,6 +110,7 @@ describe('', () => { name: 'c', namespace: 'd', }, + targetRef: 'b:d/c', type: RELATION_PART_OF, }, { @@ -114,6 +119,7 @@ describe('', () => { name: 'a1', namespace: 'd', }, + targetRef: 'k:d/a1', type: RELATION_OWNER_OF, }, { @@ -122,6 +128,7 @@ describe('', () => { name: 'c2', namespace: 'd', }, + targetRef: 'b:d/c2', type: RELATION_HAS_PART, }, ], @@ -140,6 +147,7 @@ describe('', () => { name: 'c1', namespace: 'd', }, + targetRef: 'b:d/c1', type: RELATION_PART_OF, }, ], @@ -225,6 +233,7 @@ describe('', () => { name: 'some-component', namespace: 'default', }, + targetRef: 'component:default/some-component', type: RELATION_OWNER_OF, }, ], diff --git a/plugins/catalog-graph/src/components/EntityRelationsGraph/useEntityRelationGraph.test.ts b/plugins/catalog-graph/src/components/EntityRelationsGraph/useEntityRelationGraph.test.ts index bf6fc9ee3f..7e0cd411ce 100644 --- a/plugins/catalog-graph/src/components/EntityRelationsGraph/useEntityRelationGraph.test.ts +++ b/plugins/catalog-graph/src/components/EntityRelationsGraph/useEntityRelationGraph.test.ts @@ -49,6 +49,7 @@ describe('useEntityRelationGraph', () => { name: 'a1', namespace: 'd', }, + targetRef: 'k:d/a1', type: RELATION_OWNER_OF, }, { @@ -57,6 +58,7 @@ describe('useEntityRelationGraph', () => { name: 'c1', namespace: 'd', }, + targetRef: 'b:d/c1', type: RELATION_HAS_PART, }, ], @@ -75,6 +77,7 @@ describe('useEntityRelationGraph', () => { name: 'c', namespace: 'd', }, + targetRef: 'b:d/c', type: RELATION_OWNED_BY, }, { @@ -83,6 +86,7 @@ describe('useEntityRelationGraph', () => { name: 'c1', namespace: 'd', }, + targetRef: 'b:d/c1', type: RELATION_OWNED_BY, }, ], @@ -101,6 +105,7 @@ describe('useEntityRelationGraph', () => { name: 'c', namespace: 'd', }, + targetRef: 'b:d/c', type: RELATION_PART_OF, }, { @@ -109,6 +114,7 @@ describe('useEntityRelationGraph', () => { name: 'a1', namespace: 'd', }, + targetRef: 'k:d/a1', type: RELATION_OWNER_OF, }, { @@ -117,6 +123,7 @@ describe('useEntityRelationGraph', () => { name: 'c2', namespace: 'd', }, + targetRef: 'b:d/c2', type: RELATION_HAS_PART, }, ], @@ -135,6 +142,7 @@ describe('useEntityRelationGraph', () => { name: 'c1', namespace: 'd', }, + targetRef: 'b:d/c1', type: RELATION_PART_OF, }, ], diff --git a/plugins/catalog-graph/src/components/EntityRelationsGraph/useEntityRelationGraph.ts b/plugins/catalog-graph/src/components/EntityRelationsGraph/useEntityRelationGraph.ts index e0d393168b..957f35dc25 100644 --- a/plugins/catalog-graph/src/components/EntityRelationsGraph/useEntityRelationGraph.ts +++ b/plugins/catalog-graph/src/components/EntityRelationsGraph/useEntityRelationGraph.ts @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { Entity, stringifyEntityRef } from '@backstage/catalog-model'; +import { Entity } from '@backstage/catalog-model'; import { useEffect } from 'react'; import { useEntityStore } from './useEntityStore'; @@ -64,13 +64,15 @@ export function useEntityRelationGraph({ if ( (!relations || relations.includes(rel.type)) && (!kinds || - kinds.includes(rel.target.kind.toLocaleLowerCase('en-US'))) + kinds.some(kind => + rel.targetRef.startsWith( + `${kind.toLocaleLowerCase('en-US')}:`, + ), + )) ) { - const relationEntityRef = stringifyEntityRef(rel.target); - - if (!processedEntityRefs.has(relationEntityRef)) { - nextDepthRefQueue.push(relationEntityRef); - expectedEntities.add(relationEntityRef); + if (!processedEntityRefs.has(rel.targetRef)) { + nextDepthRefQueue.push(rel.targetRef); + expectedEntities.add(rel.targetRef); } } } diff --git a/plugins/catalog-graph/src/components/EntityRelationsGraph/useEntityRelationNodesAndEdges.test.ts b/plugins/catalog-graph/src/components/EntityRelationsGraph/useEntityRelationNodesAndEdges.test.ts index 37073352d6..4715cb106a 100644 --- a/plugins/catalog-graph/src/components/EntityRelationsGraph/useEntityRelationNodesAndEdges.test.ts +++ b/plugins/catalog-graph/src/components/EntityRelationsGraph/useEntityRelationNodesAndEdges.test.ts @@ -49,6 +49,7 @@ describe('useEntityRelationNodesAndEdges', () => { name: 'a1', namespace: 'd', }, + targetRef: 'k:d/a1', type: RELATION_OWNER_OF, }, { @@ -57,6 +58,7 @@ describe('useEntityRelationNodesAndEdges', () => { name: 'c1', namespace: 'd', }, + targetRef: 'b:d/c1', type: RELATION_HAS_PART, }, ], @@ -75,6 +77,7 @@ describe('useEntityRelationNodesAndEdges', () => { name: 'c', namespace: 'd', }, + targetRef: 'b:d/c', type: RELATION_OWNED_BY, }, { @@ -83,6 +86,7 @@ describe('useEntityRelationNodesAndEdges', () => { name: 'c1', namespace: 'd', }, + targetRef: 'b:d/c1', type: RELATION_OWNED_BY, }, ], @@ -101,6 +105,7 @@ describe('useEntityRelationNodesAndEdges', () => { name: 'c', namespace: 'd', }, + targetRef: 'b:d/c', type: RELATION_PART_OF, }, { @@ -109,6 +114,7 @@ describe('useEntityRelationNodesAndEdges', () => { name: 'a1', namespace: 'd', }, + targetRef: 'k:d/a1', type: RELATION_OWNER_OF, }, { @@ -117,6 +123,7 @@ describe('useEntityRelationNodesAndEdges', () => { name: 'c2', namespace: 'd', }, + targetRef: 'b:d/c2', type: RELATION_HAS_PART, }, ], @@ -135,6 +142,7 @@ describe('useEntityRelationNodesAndEdges', () => { name: 'c1', namespace: 'd', }, + targetRef: 'b:d/c1', type: RELATION_PART_OF, }, ], diff --git a/plugins/catalog-graph/src/components/EntityRelationsGraph/useEntityRelationNodesAndEdges.ts b/plugins/catalog-graph/src/components/EntityRelationsGraph/useEntityRelationNodesAndEdges.ts index b272abc654..a1c3416d4f 100644 --- a/plugins/catalog-graph/src/components/EntityRelationsGraph/useEntityRelationNodesAndEdges.ts +++ b/plugins/catalog-graph/src/components/EntityRelationsGraph/useEntityRelationNodesAndEdges.ts @@ -13,10 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { - DEFAULT_NAMESPACE, - stringifyEntityRef, -} from '@backstage/catalog-model'; +import { DEFAULT_NAMESPACE } from '@backstage/catalog-model'; import { MouseEvent, useState } from 'react'; import useDebounce from 'react-use/lib/useDebounce'; import { RelationPairs, ALL_RELATION_PAIRS } from './relations'; @@ -100,11 +97,9 @@ export function useEntityRelationNodesAndEdges({ if (entity) { entity?.relations?.forEach(rel => { - const targetRef = stringifyEntityRef(rel.target); - // Check if the related entity should be displayed, if not, ignore // the relation too - if (!entities[targetRef]) { + if (!entities[rel.targetRef]) { return; } @@ -114,12 +109,14 @@ export function useEntityRelationNodesAndEdges({ if ( kinds && - !kinds.includes(rel.target.kind.toLocaleLowerCase('en-US')) + !kinds.some(kind => + rel.targetRef.startsWith(`${kind.toLocaleLowerCase('en-US')}:`), + ) ) { return; } - if (!unidirectional || !visitedNodes.has(targetRef)) { + if (!unidirectional || !visitedNodes.has(rel.targetRef)) { if (mergeRelations) { const pair = relationPairs.find( ([l, r]) => l === rel.type || r === rel.type, @@ -127,24 +124,24 @@ export function useEntityRelationNodesAndEdges({ const [left] = pair; edges.push({ - from: left === rel.type ? entityRef : targetRef, - to: left === rel.type ? targetRef : entityRef, + from: left === rel.type ? entityRef : rel.targetRef, + to: left === rel.type ? rel.targetRef : entityRef, relations: pair, label: 'visible', }); } else { edges.push({ from: entityRef, - to: targetRef, + to: rel.targetRef, relations: [rel.type], label: 'visible', }); } } - if (!visitedNodes.has(targetRef)) { - nodeQueue.push(targetRef); - visitedNodes.add(targetRef); + if (!visitedNodes.has(rel.targetRef)) { + nodeQueue.push(rel.targetRef); + visitedNodes.add(rel.targetRef); } }); } diff --git a/plugins/catalog-react/api-report.md b/plugins/catalog-react/api-report.md index 37e959f93b..15a22c98d7 100644 --- a/plugins/catalog-react/api-report.md +++ b/plugins/catalog-react/api-report.md @@ -266,7 +266,7 @@ export const EntityRefLink: (props: EntityRefLinkProps) => JSX.Element; // @public export type EntityRefLinkProps = { - entityRef: Entity | EntityName; + entityRef: Entity | EntityName | string; defaultKind?: string; title?: string; children?: React_2.ReactNode; @@ -592,10 +592,7 @@ export function useOwnUser(): AsyncState; // @public (undocumented) export function useRelatedEntities( entity: Entity, - { - type, - kind, - }: { + relationFilter: { type?: string; kind?: string; }, diff --git a/plugins/catalog-react/src/components/EntityOwnerPicker/EntityOwnerPicker.test.tsx b/plugins/catalog-react/src/components/EntityOwnerPicker/EntityOwnerPicker.test.tsx index 14abc358db..de21bfeee3 100644 --- a/plugins/catalog-react/src/components/EntityOwnerPicker/EntityOwnerPicker.test.tsx +++ b/plugins/catalog-react/src/components/EntityOwnerPicker/EntityOwnerPicker.test.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import { Entity } from '@backstage/catalog-model'; +import { Entity, parseEntityRef } from '@backstage/catalog-model'; import { fireEvent, render } from '@testing-library/react'; import React from 'react'; import { MockEntityListContextProvider } from '../../testUtils/providers'; @@ -31,6 +31,7 @@ const sampleEntities: Entity[] = [ relations: [ { type: 'ownedBy', + targetRef: 'group:default/some-owner', target: { name: 'some-owner', namespace: 'default', @@ -39,6 +40,7 @@ const sampleEntities: Entity[] = [ }, { type: 'ownedBy', + targetRef: 'group:default/some-owner-2', target: { name: 'some-owner-2', namespace: 'default', @@ -56,6 +58,7 @@ const sampleEntities: Entity[] = [ relations: [ { type: 'ownedBy', + targetRef: 'group:default/another-owner', target: { name: 'another-owner', namespace: 'default', @@ -73,6 +76,7 @@ const sampleEntities: Entity[] = [ relations: [ { type: 'ownedBy', + targetRef: 'group:default/some-owner', target: { name: 'some-owner', namespace: 'default', @@ -96,7 +100,7 @@ describe('', () => { fireEvent.click(rendered.getByTestId('owner-picker-expand')); sampleEntities - .flatMap(e => e.relations?.map(r => r.target.name)) + .flatMap(e => e.relations?.map(r => parseEntityRef(r.targetRef).name)) .forEach(owner => { expect(rendered.getByText(owner as string)).toBeInTheDocument(); }); diff --git a/plugins/catalog-react/src/components/EntityRefLink/EntityRefLink.tsx b/plugins/catalog-react/src/components/EntityRefLink/EntityRefLink.tsx index f2c67f1087..1e01402ddf 100644 --- a/plugins/catalog-react/src/components/EntityRefLink/EntityRefLink.tsx +++ b/plugins/catalog-react/src/components/EntityRefLink/EntityRefLink.tsx @@ -18,6 +18,7 @@ import { Entity, EntityName, DEFAULT_NAMESPACE, + parseEntityRef, } from '@backstage/catalog-model'; import React, { forwardRef } from 'react'; import { entityRouteRef } from '../../routes'; @@ -32,7 +33,7 @@ import { Tooltip } from '@material-ui/core'; * @public */ export type EntityRefLinkProps = { - entityRef: Entity | EntityName; + entityRef: Entity | EntityName | string; defaultKind?: string; title?: string; children?: React.ReactNode; @@ -52,7 +53,12 @@ export const EntityRefLink = forwardRef( let namespace; let name; - if ('metadata' in entityRef) { + if (typeof entityRef === 'string') { + const parsed = parseEntityRef(entityRef); + kind = parsed.kind; + namespace = parsed.namespace; + name = parsed.name; + } else if ('metadata' in entityRef) { kind = entityRef.kind; namespace = entityRef.metadata.namespace; name = entityRef.metadata.name; @@ -63,15 +69,13 @@ export const EntityRefLink = forwardRef( } kind = kind.toLocaleLowerCase('en-US'); + namespace = namespace?.toLocaleLowerCase('en-US') ?? DEFAULT_NAMESPACE; - const routeParams = { - kind, - namespace: namespace?.toLocaleLowerCase('en-US') ?? DEFAULT_NAMESPACE, - name, - }; - const formattedEntityRefTitle = formatEntityRefTitle(entityRef, { - defaultKind, - }); + const routeParams = { kind, namespace, name }; + const formattedEntityRefTitle = formatEntityRefTitle( + { kind, namespace, name }, + { defaultKind }, + ); const link = ( diff --git a/plugins/catalog-react/src/components/EntityTable/presets.test.tsx b/plugins/catalog-react/src/components/EntityTable/presets.test.tsx index a509b67477..f2fcd016f0 100644 --- a/plugins/catalog-react/src/components/EntityTable/presets.test.tsx +++ b/plugins/catalog-react/src/components/EntityTable/presets.test.tsx @@ -44,17 +44,19 @@ describe('systemEntityColumns', () => { relations: [ { type: RELATION_PART_OF, + targetRef: 'domain:my-namespace/my-domain', target: { - kind: 'Domain', + kind: 'domain', name: 'my-domain', namespace: 'my-namespace', }, }, { type: RELATION_OWNED_BY, + targetRef: 'group:default/test', target: { - kind: 'Group', - name: 'Test', + kind: 'group', + name: 'test', namespace: 'default', }, }, @@ -79,7 +81,7 @@ describe('systemEntityColumns', () => { await waitFor(() => { expect(getByText('my-namespace/my-system')).toBeInTheDocument(); expect(getByText('my-namespace/my-domain')).toBeInTheDocument(); - expect(getByText('Test')).toBeInTheDocument(); + expect(getByText('test')).toBeInTheDocument(); expect(getByText(/Some/)).toBeInTheDocument(); }); }); @@ -104,17 +106,19 @@ describe('componentEntityColumns', () => { relations: [ { type: RELATION_PART_OF, + targetRef: 'system:my-namespace/my-system', target: { - kind: 'System', + kind: 'system', name: 'my-system', namespace: 'my-namespace', }, }, { type: RELATION_OWNED_BY, + targetRef: 'group:default/test', target: { - kind: 'Group', - name: 'Test', + kind: 'group', + name: 'test', namespace: 'default', }, }, @@ -139,7 +143,7 @@ describe('componentEntityColumns', () => { await waitFor(() => { expect(getByText('my-namespace/my-component')).toBeInTheDocument(); expect(getByText('my-namespace/my-system')).toBeInTheDocument(); - expect(getByText('Test')).toBeInTheDocument(); + expect(getByText('test')).toBeInTheDocument(); expect(getByText('production')).toBeInTheDocument(); expect(getByText('service')).toBeInTheDocument(); expect(getByText(/Some/)).toBeInTheDocument(); diff --git a/plugins/catalog-react/src/components/EntityTable/presets.tsx b/plugins/catalog-react/src/components/EntityTable/presets.tsx index 971ecfed20..6e4ddb2081 100644 --- a/plugins/catalog-react/src/components/EntityTable/presets.tsx +++ b/plugins/catalog-react/src/components/EntityTable/presets.tsx @@ -15,9 +15,8 @@ */ import { ComponentEntity, SystemEntity } from '@backstage/catalog-model'; -import { columnFactories } from './columns'; - import { TableColumn } from '@backstage/core-components'; +import { columnFactories } from './columns'; export const systemEntityColumns: TableColumn[] = [ columnFactories.createEntityRefColumn({ defaultKind: 'system' }), diff --git a/plugins/catalog-react/src/components/InspectEntityDialog/components/EntityKindIcon.tsx b/plugins/catalog-react/src/components/InspectEntityDialog/components/EntityKindIcon.tsx index c2fc49aa0c..4bb41c91c1 100644 --- a/plugins/catalog-react/src/components/InspectEntityDialog/components/EntityKindIcon.tsx +++ b/plugins/catalog-react/src/components/InspectEntityDialog/components/EntityKindIcon.tsx @@ -14,23 +14,54 @@ * limitations under the License. */ +import { parseEntityRef } from '@backstage/catalog-model'; import { useApp } from '@backstage/core-plugin-api'; import WorkIcon from '@material-ui/icons/Work'; import React from 'react'; +const DEFAULT_ICON = WorkIcon; + +function getKind( + kind: string | undefined, + entityRef: string | undefined, +): string | undefined { + if (kind) { + return kind.toLocaleLowerCase('en-US'); + } + + if (entityRef) { + try { + return parseEntityRef(entityRef).kind.toLocaleLowerCase('en-US'); + } catch { + return undefined; + } + } + + return undefined; +} + +function useIcon(kind: string | undefined, entityRef: string | undefined) { + const app = useApp(); + + const actualKind = getKind(kind, entityRef); + if (!actualKind) { + return DEFAULT_ICON; + } + + const icon = app.getSystemIcon(`kind:${actualKind}`); + return icon || DEFAULT_ICON; +} + export function EntityKindIcon(props: { - kind: string; + kind?: string; + entityRef?: string; x?: number; y?: number; width?: number; height?: number; className?: string; }) { - const app = useApp(); - - const { kind, ...otherProps } = props; - const Icon = - app.getSystemIcon(`kind:${kind.toLocaleLowerCase('en-US')}`) ?? WorkIcon; - + const { kind, entityRef, ...otherProps } = props; + const Icon = useIcon(kind, entityRef); return ; } diff --git a/plugins/catalog-react/src/components/InspectEntityDialog/components/OverviewPage.tsx b/plugins/catalog-react/src/components/InspectEntityDialog/components/OverviewPage.tsx index dbb7399481..a4e8523beb 100644 --- a/plugins/catalog-react/src/components/InspectEntityDialog/components/OverviewPage.tsx +++ b/plugins/catalog-react/src/components/InspectEntityDialog/components/OverviewPage.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import { AlphaEntity, stringifyEntityRef } from '@backstage/catalog-model'; +import { AlphaEntity } from '@backstage/catalog-model'; import { Box, DialogContentText, @@ -56,7 +56,7 @@ export function OverviewPage(props: { entity: AlphaEntity }) { } = props.entity; const groupedRelations = groupBy( - sortBy(relations, r => stringifyEntityRef(r.target)), + sortBy(relations, r => r.targetRef), 'type', ); @@ -143,12 +143,14 @@ export function OverviewPage(props: { entity: AlphaEntity }) {
{type}}> {groupRelations.map(group => ( - + - + } + primary={ + + } /> ))} diff --git a/plugins/catalog-react/src/components/UserListPicker/UserListPicker.test.tsx b/plugins/catalog-react/src/components/UserListPicker/UserListPicker.test.tsx index 630a995bbe..316a0f2c06 100644 --- a/plugins/catalog-react/src/components/UserListPicker/UserListPicker.test.tsx +++ b/plugins/catalog-react/src/components/UserListPicker/UserListPicker.test.tsx @@ -103,6 +103,7 @@ const backendEntities: Entity[] = [ relations: [ { type: RELATION_OWNED_BY, + targetRef: 'user:default/testuser', target: { kind: 'User', namespace: 'default', name: 'testUser' }, }, ], @@ -136,6 +137,7 @@ const backendEntities: Entity[] = [ relations: [ { type: RELATION_OWNED_BY, + targetRef: 'user:default/testuser', target: { kind: 'User', namespace: 'default', name: 'testUser' }, }, ], diff --git a/plugins/catalog-react/src/hooks/useEntityListProvider.test.tsx b/plugins/catalog-react/src/hooks/useEntityListProvider.test.tsx index c0a90fa834..c8ca29b07f 100644 --- a/plugins/catalog-react/src/hooks/useEntityListProvider.test.tsx +++ b/plugins/catalog-react/src/hooks/useEntityListProvider.test.tsx @@ -45,6 +45,7 @@ const entities: Entity[] = [ relations: [ { type: 'ownedBy', + targetRef: 'user:default/guest', target: { name: 'guest', namespace: 'default', diff --git a/plugins/catalog-react/src/hooks/useEntityOwnership.test.tsx b/plugins/catalog-react/src/hooks/useEntityOwnership.test.tsx index 7f838163a2..17c35afecc 100644 --- a/plugins/catalog-react/src/hooks/useEntityOwnership.test.tsx +++ b/plugins/catalog-react/src/hooks/useEntityOwnership.test.tsx @@ -70,10 +70,12 @@ describe('useEntityOwnership', () => { relations: [ { type: RELATION_OWNED_BY, + targetRef: 'user:default/user1', target: { kind: 'User', namespace: 'default', name: 'user1' }, }, { type: RELATION_OWNED_BY, + targetRef: 'group:default/group1', target: { kind: 'Group', namespace: 'default', name: 'group1' }, }, ], @@ -92,6 +94,7 @@ describe('useEntityOwnership', () => { relations: [ { type: RELATION_MEMBER_OF, + targetRef: 'group:default/group1', target: { kind: 'Group', namespace: 'default', name: 'group1' }, }, ], diff --git a/plugins/catalog-react/src/hooks/useRelatedEntities.ts b/plugins/catalog-react/src/hooks/useRelatedEntities.ts index 56dfbe6403..4cf5690ac3 100644 --- a/plugins/catalog-react/src/hooks/useRelatedEntities.ts +++ b/plugins/catalog-react/src/hooks/useRelatedEntities.ts @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { Entity, EntityRelation } from '@backstage/catalog-model'; +import { Entity, parseEntityRef } from '@backstage/catalog-model'; import { useApi } from '@backstage/core-plugin-api'; import { chunk, groupBy } from 'lodash'; import useAsync from 'react-use/lib/useAsync'; @@ -24,28 +24,28 @@ const BATCH_SIZE = 20; /** @public */ export function useRelatedEntities( entity: Entity, - { type, kind }: { type?: string; kind?: string }, + relationFilter: { type?: string; kind?: string }, ): { entities: Entity[] | undefined; loading: boolean; error: Error | undefined; } { + const filterByTypeLower = relationFilter?.type?.toLocaleLowerCase('en-US'); + const filterByKindLower = relationFilter?.kind?.toLocaleLowerCase('en-US'); + const catalogApi = useApi(catalogApiRef); const { loading, value: entities, error, } = useAsync(async () => { - const relations = - entity.relations && - entity.relations.filter( + const relations = entity.relations + ?.map(r => ({ type: r.type, target: parseEntityRef(r.targetRef) })) + .filter( r => - (!type || - r.type.toLocaleLowerCase('en-US') === - type.toLocaleLowerCase('en-US')) && - (!kind || - r.target.kind.toLocaleLowerCase('en-US') === - kind.toLocaleLowerCase('en-US')), + (!filterByTypeLower || + r.type.toLocaleLowerCase('en-US') === filterByTypeLower) && + (!filterByKindLower || r.target.kind === filterByKindLower), ); if (!relations) { @@ -57,7 +57,7 @@ export function useRelatedEntities( // `filter=kind=component,namespace=default,name=example1&filter=kind=component,namespace=default,name=example2` // with grouping, we can generate a query a string like // `filter=kind=component,namespace=default,name=example1,example2` - const relationsByKindAndNamespace: EntityRelation[][] = Object.values( + const relationsByKindAndNamespace = Object.values( groupBy(relations, ({ target }) => { return `${target.kind}:${target.namespace}`.toLocaleLowerCase('en-US'); }), @@ -96,7 +96,7 @@ export function useRelatedEntities( ); return results.flatMap(r => r.items); - }, [entity, type]); + }, [entity, filterByTypeLower, filterByKindLower]); return { entities, diff --git a/plugins/catalog-react/src/utils/getEntityRelations.test.ts b/plugins/catalog-react/src/utils/getEntityRelations.test.ts index 22c695043f..ea0ba59399 100644 --- a/plugins/catalog-react/src/utils/getEntityRelations.test.ts +++ b/plugins/catalog-react/src/utils/getEntityRelations.test.ts @@ -31,17 +31,19 @@ describe('getEntityRelations', () => { relations: [ { type: RELATION_MEMBER_OF, - target: { kind: 'group', name: 'member' }, + targetRef: 'group:default/member', + target: { kind: 'group', namespace: 'default', name: 'member' }, }, { type: RELATION_CHILD_OF, - target: { kind: 'group', name: 'child' }, + targetRef: 'group:default/child', + target: { kind: 'group', namespace: 'default', name: 'child' }, }, ], } as Entity; expect(getEntityRelations(entity, RELATION_MEMBER_OF)).toEqual([ - { kind: 'group', name: 'member' }, + { kind: 'group', namespace: 'default', name: 'member' }, ]); }); @@ -50,17 +52,19 @@ describe('getEntityRelations', () => { relations: [ { type: RELATION_MEMBER_OF, - target: { kind: 'group', name: 'member' }, + targetRef: 'group:default/member', + target: { kind: 'group', namespace: 'default', name: 'member' }, }, { type: RELATION_MEMBER_OF, - target: { kind: 'user', name: 'child' }, + targetRef: 'user:default/child', + target: { kind: 'user', namespace: 'default', name: 'child' }, }, ], } as Entity; expect( getEntityRelations(entity, RELATION_MEMBER_OF, { kind: 'Group' }), - ).toEqual([{ kind: 'group', name: 'member' }]); + ).toEqual([{ kind: 'group', namespace: 'default', name: 'member' }]); }); }); diff --git a/plugins/catalog-react/src/utils/getEntityRelations.ts b/plugins/catalog-react/src/utils/getEntityRelations.ts index 25666a9960..45cbc6967f 100644 --- a/plugins/catalog-react/src/utils/getEntityRelations.ts +++ b/plugins/catalog-react/src/utils/getEntityRelations.ts @@ -14,10 +14,12 @@ * limitations under the License. */ -import { Entity, EntityName } from '@backstage/catalog-model'; +import { Entity, EntityName, parseEntityRef } from '@backstage/catalog-model'; +// TODO(freben): This should be returning entity refs instead /** * Get the related entity references. + * * @public */ export function getEntityRelations( @@ -28,10 +30,10 @@ export function getEntityRelations( let entityNames = entity?.relations ?.filter(r => r.type === relationType) - ?.map(r => r.target) || []; + .map(r => parseEntityRef(r.targetRef)) || []; if (filter?.kind) { - entityNames = entityNames?.filter( + entityNames = entityNames.filter( e => e.kind.toLocaleLowerCase('en-US') === filter.kind.toLocaleLowerCase('en-US'), diff --git a/plugins/catalog-react/src/utils/isOwnerOf.test.ts b/plugins/catalog-react/src/utils/isOwnerOf.test.ts index 2cd6cfafa3..d3053646d8 100644 --- a/plugins/catalog-react/src/utils/isOwnerOf.test.ts +++ b/plugins/catalog-react/src/utils/isOwnerOf.test.ts @@ -31,6 +31,7 @@ describe('isOwnerOf', () => { relations: [ { type: RELATION_OWNED_BY, + targetRef: 'user:default/user', target: { kind: 'user', namespace: 'default', name: 'user' }, }, ], @@ -46,6 +47,7 @@ describe('isOwnerOf', () => { relations: [ { type: RELATION_MEMBER_OF, + targetRef: 'group:default/group', target: { kind: 'group', namespace: 'default', name: 'group' }, }, ], @@ -54,6 +56,7 @@ describe('isOwnerOf', () => { relations: [ { type: RELATION_OWNED_BY, + targetRef: 'group:default/group', target: { kind: 'group', namespace: 'default', name: 'group' }, }, ], diff --git a/plugins/catalog/src/components/AboutCard/AboutCard.test.tsx b/plugins/catalog/src/components/AboutCard/AboutCard.test.tsx index 74c6490534..9a878e3b60 100644 --- a/plugins/catalog/src/components/AboutCard/AboutCard.test.tsx +++ b/plugins/catalog/src/components/AboutCard/AboutCard.test.tsx @@ -63,6 +63,7 @@ describe('', () => { relations: [ { type: RELATION_OWNED_BY, + targetRef: 'user:default/guest', target: { kind: 'user', name: 'guest', diff --git a/plugins/catalog/src/components/AboutCard/AboutContent.test.tsx b/plugins/catalog/src/components/AboutCard/AboutContent.test.tsx index 860ef2c532..cf9673eeeb 100644 --- a/plugins/catalog/src/components/AboutCard/AboutContent.test.tsx +++ b/plugins/catalog/src/components/AboutCard/AboutContent.test.tsx @@ -47,6 +47,7 @@ describe('', () => { relations: [ { type: RELATION_OWNED_BY, + targetRef: 'user:default/o', target: { kind: 'user', name: 'o', @@ -55,6 +56,7 @@ describe('', () => { }, { type: RELATION_PART_OF, + targetRef: 'system:default/s', target: { kind: 'system', name: 's', @@ -63,6 +65,7 @@ describe('', () => { }, { type: RELATION_PART_OF, + targetRef: 'domain:default/d', target: { kind: 'domain', name: 'd', @@ -152,6 +155,7 @@ describe('', () => { relations: [ { type: RELATION_OWNED_BY, + targetRef: 'user:default/guest', target: { kind: 'user', name: 'guest', @@ -160,6 +164,7 @@ describe('', () => { }, { type: RELATION_PART_OF, + targetRef: 'system:default/system', target: { kind: 'system', name: 'system', @@ -257,6 +262,7 @@ describe('', () => { relations: [ { type: RELATION_OWNED_BY, + targetRef: 'user:default/guest', target: { kind: 'user', name: 'guest', @@ -265,6 +271,7 @@ describe('', () => { }, { type: RELATION_PART_OF, + targetRef: 'system:default/system', target: { kind: 'system', name: 'system', @@ -273,6 +280,7 @@ describe('', () => { }, { type: RELATION_PART_OF, + targetRef: 'component:default/parent-software', target: { kind: 'component', name: 'parent-software', @@ -369,6 +377,7 @@ describe('', () => { relations: [ { type: RELATION_OWNED_BY, + targetRef: 'user:default/guest', target: { kind: 'user', name: 'guest', @@ -528,6 +537,7 @@ describe('', () => { relations: [ { type: RELATION_OWNED_BY, + targetRef: 'user:default/guest', target: { kind: 'user', name: 'guest', @@ -536,6 +546,7 @@ describe('', () => { }, { type: RELATION_PART_OF, + targetRef: 'system:default/system', target: { kind: 'system', name: 'system', @@ -625,6 +636,7 @@ describe('', () => { relations: [ { type: RELATION_OWNED_BY, + targetRef: 'user:default/guest', target: { kind: 'user', name: 'guest', @@ -633,6 +645,7 @@ describe('', () => { }, { type: RELATION_PART_OF, + targetRef: 'domain:default/domain', target: { kind: 'domain', name: 'domain', diff --git a/plugins/catalog/src/components/CatalogPage/DefaultCatalogPage.test.tsx b/plugins/catalog/src/components/CatalogPage/DefaultCatalogPage.test.tsx index 34aee0bbf3..dcf819f976 100644 --- a/plugins/catalog/src/components/CatalogPage/DefaultCatalogPage.test.tsx +++ b/plugins/catalog/src/components/CatalogPage/DefaultCatalogPage.test.tsx @@ -73,7 +73,8 @@ describe('DefaultCatalogPage', () => { relations: [ { type: RELATION_OWNED_BY, - target: { kind: 'Group', name: 'tools', namespace: 'default' }, + targetRef: 'group:default/tools', + target: { kind: 'group', name: 'tools', namespace: 'default' }, }, ], }, @@ -90,8 +91,9 @@ describe('DefaultCatalogPage', () => { relations: [ { type: RELATION_OWNED_BY, + targetRef: 'group:default/not-tools', target: { - kind: 'Group', + kind: 'group', name: 'not-tools', namespace: 'default', }, @@ -110,6 +112,7 @@ describe('DefaultCatalogPage', () => { relations: [ { type: RELATION_MEMBER_OF, + targetRef: 'group:default/tools', target: { namespace: 'default', kind: 'Group', name: 'tools' }, }, ], diff --git a/plugins/catalog/src/components/DependencyOfComponentsCard/DependencyOfComponentsCard.test.tsx b/plugins/catalog/src/components/DependencyOfComponentsCard/DependencyOfComponentsCard.test.tsx index e43104b67d..05d836870f 100644 --- a/plugins/catalog/src/components/DependencyOfComponentsCard/DependencyOfComponentsCard.test.tsx +++ b/plugins/catalog/src/components/DependencyOfComponentsCard/DependencyOfComponentsCard.test.tsx @@ -81,10 +81,11 @@ describe('', () => { relations: [ { target: { - kind: 'Component', + kind: 'component', namespace: 'my-namespace', name: 'target-name', }, + targetRef: 'component:my-namespace/target-name', type: RELATION_DEPENDENCY_OF, }, ], diff --git a/plugins/catalog/src/components/DependsOnComponentsCard/DependsOnComponentsCard.test.tsx b/plugins/catalog/src/components/DependsOnComponentsCard/DependsOnComponentsCard.test.tsx index b654136d18..b7ef594522 100644 --- a/plugins/catalog/src/components/DependsOnComponentsCard/DependsOnComponentsCard.test.tsx +++ b/plugins/catalog/src/components/DependsOnComponentsCard/DependsOnComponentsCard.test.tsx @@ -81,10 +81,11 @@ describe('', () => { relations: [ { target: { - kind: 'Component', + kind: 'component', namespace: 'my-namespace', name: 'target-name', }, + targetRef: 'component:my-namespace/target-name', type: RELATION_DEPENDS_ON, }, ], diff --git a/plugins/catalog/src/components/DependsOnResourcesCard/DependsOnResourcesCard.test.tsx b/plugins/catalog/src/components/DependsOnResourcesCard/DependsOnResourcesCard.test.tsx index 40685705aa..b8d2801e48 100644 --- a/plugins/catalog/src/components/DependsOnResourcesCard/DependsOnResourcesCard.test.tsx +++ b/plugins/catalog/src/components/DependsOnResourcesCard/DependsOnResourcesCard.test.tsx @@ -81,10 +81,11 @@ describe('', () => { relations: [ { target: { - kind: 'Resource', + kind: 'resource', namespace: 'my-namespace', name: 'target-name', }, + targetRef: 'resource:my-namespace/target-name', type: RELATION_DEPENDS_ON, }, ], diff --git a/plugins/catalog/src/components/HasComponentsCard/HasComponentsCard.test.tsx b/plugins/catalog/src/components/HasComponentsCard/HasComponentsCard.test.tsx index 45f7561d6e..fbb474825f 100644 --- a/plugins/catalog/src/components/HasComponentsCard/HasComponentsCard.test.tsx +++ b/plugins/catalog/src/components/HasComponentsCard/HasComponentsCard.test.tsx @@ -81,10 +81,11 @@ describe('', () => { relations: [ { target: { - kind: 'Component', + kind: 'component', namespace: 'my-namespace', name: 'target-name', }, + targetRef: 'component:my-namespace/target-name', type: RELATION_HAS_PART, }, ], diff --git a/plugins/catalog/src/components/HasResourcesCard/HasResourcesCard.test.tsx b/plugins/catalog/src/components/HasResourcesCard/HasResourcesCard.test.tsx index a01922d988..c75273e484 100644 --- a/plugins/catalog/src/components/HasResourcesCard/HasResourcesCard.test.tsx +++ b/plugins/catalog/src/components/HasResourcesCard/HasResourcesCard.test.tsx @@ -76,10 +76,11 @@ describe('', () => { relations: [ { target: { - kind: 'Resource', + kind: 'resource', namespace: 'my-namespace', name: 'target-name', }, + targetRef: 'resource:my-namespace/target-name', type: RELATION_HAS_PART, }, ], diff --git a/plugins/catalog/src/components/HasSubcomponentsCard/HasSubcomponentsCard.test.tsx b/plugins/catalog/src/components/HasSubcomponentsCard/HasSubcomponentsCard.test.tsx index e517bd38ae..e021fea2f3 100644 --- a/plugins/catalog/src/components/HasSubcomponentsCard/HasSubcomponentsCard.test.tsx +++ b/plugins/catalog/src/components/HasSubcomponentsCard/HasSubcomponentsCard.test.tsx @@ -81,10 +81,11 @@ describe('', () => { relations: [ { target: { - kind: 'Component', + kind: 'component', namespace: 'my-namespace', name: 'target-name', }, + targetRef: 'component:my-namespace/target-name', type: RELATION_HAS_PART, }, ], diff --git a/plugins/catalog/src/components/HasSystemsCard/HasSystemsCard.test.tsx b/plugins/catalog/src/components/HasSystemsCard/HasSystemsCard.test.tsx index 050550101f..6a2e2a0b7f 100644 --- a/plugins/catalog/src/components/HasSystemsCard/HasSystemsCard.test.tsx +++ b/plugins/catalog/src/components/HasSystemsCard/HasSystemsCard.test.tsx @@ -79,10 +79,11 @@ describe('', () => { relations: [ { target: { - kind: 'System', + kind: 'system', namespace: 'my-namespace', name: 'target-name', }, + targetRef: 'system:my-namespace/target-name', type: RELATION_HAS_PART, }, ], diff --git a/plugins/catalog/src/components/SystemDiagramCard/SystemDiagramCard.test.tsx b/plugins/catalog/src/components/SystemDiagramCard/SystemDiagramCard.test.tsx index e0ec48fbed..eb6b71c47e 100644 --- a/plugins/catalog/src/components/SystemDiagramCard/SystemDiagramCard.test.tsx +++ b/plugins/catalog/src/components/SystemDiagramCard/SystemDiagramCard.test.tsx @@ -103,10 +103,11 @@ describe('', () => { relations: [ { target: { - kind: 'Domain', + kind: 'domain', namespace: 'namespace', name: 'domain', }, + targetRef: 'domain:namespace/domain', type: RELATION_PART_OF, }, ], @@ -166,6 +167,7 @@ describe('', () => { namespace: 'namespace', name: 'alongdomainthatshouldgettruncated', }, + targetRef: 'domain:namespace/alongdomainthatshouldgettruncated', type: RELATION_PART_OF, }, ], diff --git a/plugins/fossa/dev/index.tsx b/plugins/fossa/dev/index.tsx index 018d3e3acb..1ce9ec58b2 100644 --- a/plugins/fossa/dev/index.tsx +++ b/plugins/fossa/dev/index.tsx @@ -42,7 +42,8 @@ const entity = (name?: string) => relations: [ { type: RELATION_OWNED_BY, - target: { kind: 'Group', namespace: 'default', name }, + targetRef: `group:default/${name}`, + target: { kind: 'group', namespace: 'default', name }, }, ], } as Entity); diff --git a/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.stories.tsx b/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.stories.tsx index dc90a46fdf..cdf6240fc0 100644 --- a/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.stories.tsx +++ b/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.stories.tsx @@ -28,6 +28,7 @@ export default { const dummyDepartment = { type: 'childOf', + targetRef: 'group:default/department-a', target: { namespace: 'default', kind: 'group', diff --git a/plugins/org/src/components/Cards/Group/MembersList/MembersListCard.stories.tsx b/plugins/org/src/components/Cards/Group/MembersList/MembersListCard.stories.tsx index b6b549d934..b0a7ed8c70 100644 --- a/plugins/org/src/components/Cards/Group/MembersList/MembersListCard.stories.tsx +++ b/plugins/org/src/components/Cards/Group/MembersList/MembersListCard.stories.tsx @@ -54,6 +54,7 @@ const makeUser = ({ relations: [ { type: 'memberOf', + targetRef: 'group:default/team-a', target: { namespace: 'default', kind: 'group', diff --git a/plugins/org/src/components/Cards/Group/MembersList/MembersListCard.test.tsx b/plugins/org/src/components/Cards/Group/MembersList/MembersListCard.test.tsx index 99be8571e0..cffe3e0228 100644 --- a/plugins/org/src/components/Cards/Group/MembersList/MembersListCard.test.tsx +++ b/plugins/org/src/components/Cards/Group/MembersList/MembersListCard.test.tsx @@ -59,6 +59,7 @@ describe('MemberTab Test', () => { relations: [ { type: 'memberOf', + targetRef: 'group:default/team-d', target: { kind: 'group', name: 'team-d', diff --git a/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.stories.tsx b/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.stories.tsx index a0536e1a74..c07815f39e 100644 --- a/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.stories.tsx +++ b/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.stories.tsx @@ -69,9 +69,10 @@ const makeComponent = ({ type, name }: { type: string; name: string }) => ({ relations: [ { type: 'ownedBy', + targetRef: 'group:default/team-a', target: { namespace: 'default', - kind: 'Group', + kind: 'group', name: 'team-a', }, }, diff --git a/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.test.tsx b/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.test.tsx index 2030f4c100..3fc770e6b5 100644 --- a/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.test.tsx +++ b/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.test.tsx @@ -43,10 +43,11 @@ const items = [ relations: [ { type: 'ownedBy', + targetRef: 'group:default/my-team', target: { name: 'my-team', namespace: 'default', - kind: 'Group', + kind: 'group', }, }, ], @@ -62,10 +63,11 @@ const items = [ relations: [ { type: 'ownedBy', + targetRef: 'group:default/my-team', target: { name: 'my-team', namespace: 'default', - kind: 'Group', + kind: 'group', }, }, ], @@ -82,10 +84,11 @@ const items = [ relations: [ { type: 'ownedBy', + targetRef: 'group:default/my-team', target: { name: 'my-team', namespace: 'default', - kind: 'Group', + kind: 'group', }, }, ], @@ -99,10 +102,11 @@ const items = [ relations: [ { type: 'ownedBy', + targetRef: 'group:default/my-team', target: { name: 'my-team', namespace: 'default', - kind: 'Group', + kind: 'group', }, }, ], @@ -135,9 +139,10 @@ describe('OwnershipCard', () => { relations: [ { type: 'memberOf', + targetRef: 'group:default/examplegroup', target: { kind: 'group', - name: 'ExampleGroup', + name: 'examplegroup', namespace: 'default', }, }, @@ -260,8 +265,9 @@ describe('OwnershipCard', () => { relations: [ { type: 'memberOf', + targetRef: 'group:default/my-team', target: { - kind: 'Group', + kind: 'group', name: 'my-team', namespace: 'default', }, diff --git a/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.stories.tsx b/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.stories.tsx index 0cb3987c77..0b2d2790a0 100644 --- a/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.stories.tsx +++ b/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.stories.tsx @@ -28,6 +28,7 @@ export default { const dummyGroup = { type: 'memberOf', + targetRef: 'group:default/team-a', target: { namespace: 'default', kind: 'group', diff --git a/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.test.tsx b/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.test.tsx index cf84381830..3e5e595eef 100644 --- a/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.test.tsx +++ b/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.test.tsx @@ -42,6 +42,7 @@ describe('UserSummary Test', () => { relations: [ { type: 'memberOf', + targetRef: 'group:default/examplegroup', target: { kind: 'group', name: 'ExampleGroup', @@ -70,9 +71,9 @@ describe('UserSummary Test', () => { 'src', 'https://example.com/staff/calum.jpeg', ); - expect(rendered.getByText('ExampleGroup')).toHaveAttribute( + expect(rendered.getByText('examplegroup')).toHaveAttribute( 'href', - '/catalog/default/group/ExampleGroup', + '/catalog/default/group/examplegroup', ); expect(rendered.getByText('Super awesome human')).toBeInTheDocument(); }); diff --git a/plugins/tech-insights-backend/src/service/fact/factRetrievers/entityMetadataFactRetriever.test.ts b/plugins/tech-insights-backend/src/service/fact/factRetrievers/entityMetadataFactRetriever.test.ts index a51ce0ab83..14d0728cf6 100644 --- a/plugins/tech-insights-backend/src/service/fact/factRetrievers/entityMetadataFactRetriever.test.ts +++ b/plugins/tech-insights-backend/src/service/fact/factRetrievers/entityMetadataFactRetriever.test.ts @@ -57,6 +57,7 @@ const defaultEntityListResponse: GetEntitiesResponse = { relations: [ { type: RELATION_OWNED_BY, + targetRef: 'group:default/my-team', target: { name: 'team-a', kind: 'group', namespace: 'default' }, }, ], diff --git a/plugins/tech-insights-backend/src/service/fact/factRetrievers/entityOwnershipFactRetriever.test.ts b/plugins/tech-insights-backend/src/service/fact/factRetrievers/entityOwnershipFactRetriever.test.ts index 2108379f06..374bda2569 100644 --- a/plugins/tech-insights-backend/src/service/fact/factRetrievers/entityOwnershipFactRetriever.test.ts +++ b/plugins/tech-insights-backend/src/service/fact/factRetrievers/entityOwnershipFactRetriever.test.ts @@ -57,6 +57,7 @@ const defaultEntityListResponse: GetEntitiesResponse = { relations: [ { type: RELATION_OWNED_BY, + targetRef: 'group:default/team-a', target: { name: 'team-a', kind: 'group', namespace: 'default' }, }, ], diff --git a/plugins/tech-insights-backend/src/service/fact/factRetrievers/techdocsFactRetriever.test.ts b/plugins/tech-insights-backend/src/service/fact/factRetrievers/techdocsFactRetriever.test.ts index a3604129b7..b4fa8ba67c 100644 --- a/plugins/tech-insights-backend/src/service/fact/factRetrievers/techdocsFactRetriever.test.ts +++ b/plugins/tech-insights-backend/src/service/fact/factRetrievers/techdocsFactRetriever.test.ts @@ -57,6 +57,7 @@ const defaultEntityListResponse: GetEntitiesResponse = { relations: [ { type: RELATION_OWNED_BY, + targetRef: 'group:default/team-a', target: { name: 'team-a', kind: 'group', namespace: 'default' }, }, ], diff --git a/plugins/techdocs-backend/src/search/DefaultTechDocsCollator.ts b/plugins/techdocs-backend/src/search/DefaultTechDocsCollator.ts index 86b70379ad..2c8786d92e 100644 --- a/plugins/techdocs-backend/src/search/DefaultTechDocsCollator.ts +++ b/plugins/techdocs-backend/src/search/DefaultTechDocsCollator.ts @@ -20,6 +20,7 @@ import { } from '@backstage/backend-common'; import { Entity, + parseEntityRef, RELATION_OWNED_BY, stringifyEntityRef, } from '@backstage/catalog-model'; @@ -154,9 +155,7 @@ export class DefaultTechDocsCollator implements DocumentCollator { entityTitle: entity.metadata.title, componentType: entity.spec?.type?.toString() || 'other', lifecycle: (entity.spec?.lifecycle as string) || '', - owner: - entity.relations?.find(r => r.type === RELATION_OWNED_BY) - ?.target?.name || '', + owner: getSimpleEntityOwnerString(entity), authorization: { resourceRef: stringifyEntityRef(entity), }, @@ -202,3 +201,14 @@ export class DefaultTechDocsCollator implements DocumentCollator { }, {} as EntityInfo); } } + +function getSimpleEntityOwnerString(entity: Entity): string { + if (entity.relations) { + const owner = entity.relations.find(r => r.type === RELATION_OWNED_BY); + if (owner) { + const { name } = parseEntityRef(owner.targetRef); + return name; + } + } + return ''; +} diff --git a/plugins/techdocs/src/home/components/DocsTable.test.tsx b/plugins/techdocs/src/home/components/DocsTable.test.tsx index 4564725db6..9586cfb2ef 100644 --- a/plugins/techdocs/src/home/components/DocsTable.test.tsx +++ b/plugins/techdocs/src/home/components/DocsTable.test.tsx @@ -66,6 +66,7 @@ describe('DocsTable test', () => { namespace: 'default', name: 'owned', }, + targetRef: 'user:default/owned', type: 'ownedBy', }, ], @@ -86,6 +87,7 @@ describe('DocsTable test', () => { namespace: 'default', name: 'not-owned', }, + targetRef: 'user:default/not-owned', type: 'ownedBy', }, ], @@ -137,6 +139,7 @@ describe('DocsTable test', () => { namespace: 'default', name: 'owned', }, + targetRef: 'user:default/owned', type: 'ownedBy', }, ],