From 2d4593254fcfe625001ad025bf9250aaa0572d16 Mon Sep 17 00:00:00 2001 From: Iain Billett Date: Fri, 12 Nov 2021 10:20:25 +0000 Subject: [PATCH] Dynamic annotations Signed-off-by: Iain Billett --- .../entityFactRetriever.test.ts | 38 +++++++++++++++- .../factRetrievers/entityFactRetriever.ts | 45 ++++++++++++++----- 2 files changed, 72 insertions(+), 11 deletions(-) diff --git a/plugins/tech-insights-backend/src/service/fact/factRetrievers/entityFactRetriever.test.ts b/plugins/tech-insights-backend/src/service/fact/factRetrievers/entityFactRetriever.test.ts index 62769288fd..a1741c22ee 100644 --- a/plugins/tech-insights-backend/src/service/fact/factRetrievers/entityFactRetriever.test.ts +++ b/plugins/tech-insights-backend/src/service/fact/factRetrievers/entityFactRetriever.test.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { entityFactRetriever } from './entityFactRetriever'; +import { createEntityFactRetriever } from './entityFactRetriever'; import { Entity, RELATION_OWNED_BY } from '@backstage/catalog-model'; import { PluginEndpointDiscovery, @@ -44,6 +44,9 @@ const defaultEntityListResponse: CatalogListResponse = { name: 'service-with-owner', description: 'service with an owner', tags: ['a-tag'], + annotations: { + 'backstage.io/techdocs-ref': 'dir:.', + }, }, kind: 'Component', spec: { @@ -103,6 +106,10 @@ const handlerContext = { config: ConfigReader.fromConfigs([]), }; +const entityFactRetriever = createEntityFactRetriever({ + annotations: ['backstage.io/techdocs-ref'], +}); + describe('entityFactRetriever', () => { beforeEach(() => { getEntitiesMock.mockResolvedValue(defaultEntityListResponse); @@ -248,4 +255,33 @@ describe('entityFactRetriever', () => { }); }); }); + + describe('dynamic annotation facts', () => { + describe('where the retriever is configured to check for the techdocs annotation', () => { + describe('where the entity has the techdocs annotation', () => { + it('returns true for hasAnnotationBackstageIoTechdocsRef', async () => { + const facts = await entityFactRetriever.handler(handlerContext); + expect( + facts.find(it => it.entity.name === 'service-with-owner'), + ).toMatchObject({ + facts: { + hasAnnotationBackstageIoTechdocsRef: true, + }, + }); + }); + }); + describe('where the entity is missing the techdocs annotation', () => { + it('returns false for hasAnnotationBackstageIoTechdocsRef', async () => { + const facts = await entityFactRetriever.handler(handlerContext); + expect( + facts.find(it => it.entity.name === 'service-with-incomplete-data'), + ).toMatchObject({ + facts: { + hasAnnotationBackstageIoTechdocsRef: false, + }, + }); + }); + }); + }); + }); }); diff --git a/plugins/tech-insights-backend/src/service/fact/factRetrievers/entityFactRetriever.ts b/plugins/tech-insights-backend/src/service/fact/factRetrievers/entityFactRetriever.ts index 7d6a3987e0..66501ab1ec 100644 --- a/plugins/tech-insights-backend/src/service/fact/factRetrievers/entityFactRetriever.ts +++ b/plugins/tech-insights-backend/src/service/fact/factRetrievers/entityFactRetriever.ts @@ -18,8 +18,14 @@ import { FactRetrieverContext } from '@backstage/plugin-tech-insights-node'; import { CatalogClient } from '@backstage/catalog-client'; import { Entity } from '@backstage/catalog-model'; import isEmpty from 'lodash/isEmpty'; +import camelCase from 'lodash/camelCase'; +import { get } from 'lodash'; -export const entityFactRetriever = { +export const createEntityFactRetriever = ({ + annotations = [], +}: { + annotations?: string[]; +}) => ({ id: 'entityRetriever', version: '0.0.1', schema: { @@ -35,6 +41,15 @@ export const entityFactRetriever = { type: 'boolean', description: 'The entity has an owned_by relation', }, + ...annotations.reduce((acc: object, it: string) => { + return { + ...acc, + [camelCase(`hasAnnotation-${it}`)]: { + type: 'boolean', + description: `The entity has the annotation: ${it} `, + }, + }; + }, {}), }, handler: async ({ discovery }: FactRetrieverContext) => { const catalogClient = new CatalogClient({ @@ -42,22 +57,32 @@ export const entityFactRetriever = { }); const entities = await catalogClient.getEntities(); - return entities.items.map((it: Entity) => { + return entities.items.map((entity: Entity) => { return { entity: { - namespace: it.metadata.namespace!!, - kind: it.kind, - name: it.metadata.name, + namespace: entity.metadata.namespace!!, + kind: entity.kind, + name: entity.metadata.name, }, facts: { - hasOwner: Boolean(it.spec?.owner), + hasOwner: Boolean(entity.spec?.owner), hasGroupOwner: Boolean( - it.spec?.owner && (it.spec?.owner as string).startsWith('group:'), + entity.spec?.owner && + (entity.spec?.owner as string).startsWith('group:'), + ), + hasDescription: Boolean(entity.metadata?.description), + hasTags: !isEmpty(entity.metadata?.tags), + ...annotations.reduce( + (acc: object, annotation: string) => ({ + ...acc, + [camelCase(`hasAnnotation-${annotation}`)]: Boolean( + get(entity, ['metadata', 'annotations', annotation]), + ), + }), + {}, ), - hasDescription: Boolean(it.metadata?.description), - hasTags: !isEmpty(it.metadata?.tags), }, }; }); }, -}; +});