diff --git a/.changeset/swift-fishes-taste.md b/.changeset/swift-fishes-taste.md new file mode 100644 index 0000000000..8003e67698 --- /dev/null +++ b/.changeset/swift-fishes-taste.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend': patch +--- + +Ensure that emitted relations are deduplicated diff --git a/.github/styles/vocab.txt b/.github/styles/vocab.txt index 8b5522b4a3..0fd00e5737 100644 --- a/.github/styles/vocab.txt +++ b/.github/styles/vocab.txt @@ -57,6 +57,7 @@ deadnaming debounce Debounce declaratively +deduplicated deps destructured dev diff --git a/plugins/catalog-backend/src/next/stitching/Stitcher.test.ts b/plugins/catalog-backend/src/next/stitching/Stitcher.test.ts index e749fe712d..96af65fffb 100644 --- a/plugins/catalog-backend/src/next/stitching/Stitcher.test.ts +++ b/plugins/catalog-backend/src/next/stitching/Stitcher.test.ts @@ -74,6 +74,13 @@ describe('Stitcher', () => { type: 'looksAt', target_entity_ref: 'k:ns/other', }, + // handles and ignores duplicates + { + originating_entity_id: 'my-id', + source_entity_ref: 'k:ns/n', + type: 'looksAt', + target_entity_ref: 'k:ns/other', + }, ]); await stitcher.stitch(new Set(['k:ns/n'])); diff --git a/plugins/catalog-backend/src/next/stitching/Stitcher.ts b/plugins/catalog-backend/src/next/stitching/Stitcher.ts index bdfa76e5fc..0aa39f5ac8 100644 --- a/plugins/catalog-backend/src/next/stitching/Stitcher.ts +++ b/plugins/catalog-backend/src/next/stitching/Stitcher.ts @@ -22,6 +22,7 @@ import { } from '@backstage/catalog-model'; import { SerializedError } from '@backstage/errors'; import { Knex } from 'knex'; +import { uniqBy } from 'lodash'; import { v4 as uuid } from 'uuid'; import { Logger } from 'winston'; import { @@ -168,7 +169,11 @@ export class Stitcher { // TODO: entityRef is lower case and should be uppercase in the final // result - entity.relations = result + const uniqueRelationRows = uniqBy( + result, + r => `${r.relationType}:${r.relationTarget}`, + ); + entity.relations = uniqueRelationRows .filter(row => row.relationType /* exclude null row, if relevant */) .map(row => ({ type: row.relationType!,