From eeb2c90a38159adccb25de83b292954c45b1350e Mon Sep 17 00:00:00 2001 From: Bret Hubbard Date: Wed, 30 Mar 2022 10:19:42 -0600 Subject: [PATCH 1/4] catalog-backend: extract getDocumentText to improve testability Co-authored-by: Jason Nguyen Signed-off-by: Bret Hubbard --- .../search/DefaultCatalogCollatorFactory.ts | 27 +----- .../catalog-backend/src/search/util.test.ts | 95 +++++++++++++++++++ plugins/catalog-backend/src/search/util.ts | 35 +++++++ 3 files changed, 133 insertions(+), 24 deletions(-) create mode 100644 plugins/catalog-backend/src/search/util.test.ts create mode 100644 plugins/catalog-backend/src/search/util.ts diff --git a/plugins/catalog-backend/src/search/DefaultCatalogCollatorFactory.ts b/plugins/catalog-backend/src/search/DefaultCatalogCollatorFactory.ts index 98bcfcd8d4..cf082c271c 100644 --- a/plugins/catalog-backend/src/search/DefaultCatalogCollatorFactory.ts +++ b/plugins/catalog-backend/src/search/DefaultCatalogCollatorFactory.ts @@ -23,11 +23,7 @@ import { CatalogClient, GetEntitiesRequest, } from '@backstage/catalog-client'; -import { - Entity, - stringifyEntityRef, - UserEntity, -} from '@backstage/catalog-model'; +import { stringifyEntityRef } from '@backstage/catalog-model'; import { Config } from '@backstage/config'; import { DocumentCollatorFactory } from '@backstage/plugin-search-common'; import { @@ -36,6 +32,7 @@ import { } from '@backstage/plugin-catalog-common'; import { Permission } from '@backstage/plugin-permission-common'; import { Readable } from 'stream'; +import { getDocumentText } from './util'; /** @public */ export type DefaultCatalogCollatorFactoryOptions = { @@ -100,24 +97,6 @@ export class DefaultCatalogCollatorFactory implements DocumentCollatorFactory { return formatted.toLowerCase(); } - private isUserEntity(entity: Entity): entity is UserEntity { - return entity.kind.toLocaleUpperCase('en-US') === 'USER'; - } - - private getDocumentText(entity: Entity): string { - let documentText = entity.metadata.description || ''; - if (this.isUserEntity(entity)) { - if (entity.spec?.profile?.displayName && documentText) { - // combine displayName and description - const displayName = entity.spec?.profile?.displayName; - documentText = displayName.concat(' : ', documentText); - } else { - documentText = entity.spec?.profile?.displayName || documentText; - } - } - return documentText; - } - private async *execute(): AsyncGenerator { const { token } = await this.tokenManager.getToken(); let entitiesRetrieved = 0; @@ -150,7 +129,7 @@ export class DefaultCatalogCollatorFactory implements DocumentCollatorFactory { kind: entity.kind, name: entity.metadata.name, }), - text: this.getDocumentText(entity), + text: getDocumentText(entity), componentType: entity.spec?.type?.toString() || 'other', type: entity.spec?.type?.toString() || 'other', namespace: entity.metadata.namespace || 'default', diff --git a/plugins/catalog-backend/src/search/util.test.ts b/plugins/catalog-backend/src/search/util.test.ts new file mode 100644 index 0000000000..0444dc2209 --- /dev/null +++ b/plugins/catalog-backend/src/search/util.test.ts @@ -0,0 +1,95 @@ +/* + * Copyright 2022 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 { ComponentEntity, UserEntity } from '@backstage/catalog-model'; +import { getDocumentText } from './util'; + +describe('getDocumentText', () => { + describe('kind is not User or Group', () => { + test('contains description if set', () => { + const entity = createComponent(); + entity.metadata.description = 'The expected description'; + const actual = getDocumentText(entity); + expect(actual).toContain(entity.metadata.description); + }); + + test('is empty if description is not set', () => { + const entity = createComponent(); + const actual = getDocumentText(entity); + expect(actual).toEqual(''); + }); + }); + + describe('kind is User', () => { + test('contains display name if set', () => { + const entity = createUser(); + const actual = getDocumentText(entity); + expect(actual).toContain(entity.spec.profile?.displayName); + }); + + test('contains description if set', () => { + const entity = createUser(); + const actual = getDocumentText(entity); + expect(actual).toContain(entity.metadata.description); + }); + + test('contains both description and display name if both are set', () => { + const entity = createUser(); + const actual = getDocumentText(entity); + expect(actual).toContain(entity.spec.profile?.displayName); + expect(actual).toContain(entity.metadata.description); + }); + + test('is empty if description and display name are not set', () => { + const entity = createUser(); + delete entity.metadata.description; + delete entity.spec.profile?.displayName; + const actual = getDocumentText(entity); + expect(actual).toEqual(''); + }); + }); +}); + +function createUser(): UserEntity { + return { + apiVersion: 'backstage.io/v1alpha1', + kind: 'User', + metadata: { + name: 'user-1', + description: 'The expected description', + }, + spec: { + profile: { + displayName: 'User 1', + }, + }, + }; +} + +function createComponent(): ComponentEntity { + return { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + name: 'component-1', + }, + spec: { + lifecycle: 'experimental', + owner: 'someone', + type: 'service', + }, + }; +} diff --git a/plugins/catalog-backend/src/search/util.ts b/plugins/catalog-backend/src/search/util.ts new file mode 100644 index 0000000000..4e6bb8aea0 --- /dev/null +++ b/plugins/catalog-backend/src/search/util.ts @@ -0,0 +1,35 @@ +/* + * Copyright 2022 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, UserEntity } from '@backstage/catalog-model'; + +function isUserEntity(entity: Entity): entity is UserEntity { + return entity.kind.toLocaleUpperCase('en-US') === 'USER'; +} + +export function getDocumentText(entity: Entity): string { + let documentText = entity.metadata.description || ''; + if (isUserEntity(entity)) { + if (entity.spec?.profile?.displayName && documentText) { + // combine displayName and description + const displayName = entity.spec.profile.displayName; + documentText = displayName.concat(' : ', documentText); + } else { + documentText = entity.spec?.profile?.displayName || documentText; + } + } + return documentText; +} From 22394f6e42d358ae90303c11c181852cc680e598 Mon Sep 17 00:00:00 2001 From: Bret Hubbard Date: Wed, 30 Mar 2022 10:30:11 -0600 Subject: [PATCH 2/4] catalog-backend: include group displayName in search index Co-authored-by: Jason Nguyen Signed-off-by: Bret Hubbard --- .../catalog-backend/src/search/util.test.ts | 53 ++++++++++++++++++- plugins/catalog-backend/src/search/util.ts | 6 ++- 2 files changed, 57 insertions(+), 2 deletions(-) diff --git a/plugins/catalog-backend/src/search/util.test.ts b/plugins/catalog-backend/src/search/util.test.ts index 0444dc2209..d2d1776338 100644 --- a/plugins/catalog-backend/src/search/util.test.ts +++ b/plugins/catalog-backend/src/search/util.test.ts @@ -14,7 +14,11 @@ * limitations under the License. */ -import { ComponentEntity, UserEntity } from '@backstage/catalog-model'; +import { + ComponentEntity, + GroupEntity, + UserEntity, +} from '@backstage/catalog-model'; import { getDocumentText } from './util'; describe('getDocumentText', () => { @@ -61,8 +65,55 @@ describe('getDocumentText', () => { expect(actual).toEqual(''); }); }); + + describe('kind is Group', () => { + test('contains display name if set', () => { + const entity = createGroup(); + const actual = getDocumentText(entity); + expect(actual).toContain(entity.spec.profile?.displayName); + }); + + test('contains description if set', () => { + const entity = createGroup(); + const actual = getDocumentText(entity); + expect(actual).toContain(entity.metadata.description); + }); + + test('contains both description and display name if both are set', () => { + const entity = createGroup(); + const actual = getDocumentText(entity); + expect(actual).toContain(entity.spec.profile?.displayName); + expect(actual).toContain(entity.metadata.description); + }); + + test('is empty if description and display name are not set', () => { + const entity = createGroup(); + delete entity.metadata.description; + delete entity.spec.profile?.displayName; + const actual = getDocumentText(entity); + expect(actual).toEqual(''); + }); + }); }); +function createGroup(): GroupEntity { + return { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Group', + metadata: { + name: 'group-1', + description: 'The expected description', + }, + spec: { + type: 'team', + profile: { + displayName: 'Group 1', + }, + children: [], + }, + }; +} + function createUser(): UserEntity { return { apiVersion: 'backstage.io/v1alpha1', diff --git a/plugins/catalog-backend/src/search/util.ts b/plugins/catalog-backend/src/search/util.ts index 4e6bb8aea0..47a5570899 100644 --- a/plugins/catalog-backend/src/search/util.ts +++ b/plugins/catalog-backend/src/search/util.ts @@ -20,9 +20,13 @@ function isUserEntity(entity: Entity): entity is UserEntity { return entity.kind.toLocaleUpperCase('en-US') === 'USER'; } +function isGroupEntity(entity: Entity): entity is UserEntity { + return entity.kind.toLocaleUpperCase('en-US') === 'GROUP'; +} + export function getDocumentText(entity: Entity): string { let documentText = entity.metadata.description || ''; - if (isUserEntity(entity)) { + if (isUserEntity(entity) || isGroupEntity(entity)) { if (entity.spec?.profile?.displayName && documentText) { // combine displayName and description const displayName = entity.spec.profile.displayName; From 48405ed232722d041b6123b066f6992eacdfaef9 Mon Sep 17 00:00:00 2001 From: Bret Hubbard Date: Wed, 30 Mar 2022 10:37:45 -0600 Subject: [PATCH 3/4] add changeset Signed-off-by: Bret Hubbard --- .changeset/many-cameras-search.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/many-cameras-search.md diff --git a/.changeset/many-cameras-search.md b/.changeset/many-cameras-search.md new file mode 100644 index 0000000000..6998dfd713 --- /dev/null +++ b/.changeset/many-cameras-search.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend': patch +--- + +Added `spec.profile.displayName` to search index for Group kinds From 67dbe016713b457aa3def29ac2439d181a99443b Mon Sep 17 00:00:00 2001 From: Jason Nguyen Date: Fri, 8 Apr 2022 09:26:23 -0600 Subject: [PATCH 4/4] catalog-backend: simplify getDocumentText Signed-off-by: Jason Nguyen --- plugins/catalog-backend/src/search/util.ts | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/plugins/catalog-backend/src/search/util.ts b/plugins/catalog-backend/src/search/util.ts index 47a5570899..7e7ce7064e 100644 --- a/plugins/catalog-backend/src/search/util.ts +++ b/plugins/catalog-backend/src/search/util.ts @@ -25,15 +25,13 @@ function isGroupEntity(entity: Entity): entity is UserEntity { } export function getDocumentText(entity: Entity): string { - let documentText = entity.metadata.description || ''; + const documentTexts: string[] = []; + documentTexts.push(entity.metadata.description || ''); + if (isUserEntity(entity) || isGroupEntity(entity)) { - if (entity.spec?.profile?.displayName && documentText) { - // combine displayName and description - const displayName = entity.spec.profile.displayName; - documentText = displayName.concat(' : ', documentText); - } else { - documentText = entity.spec?.profile?.displayName || documentText; + if (entity.spec?.profile?.displayName) { + documentTexts.push(entity.spec.profile.displayName); } } - return documentText; + return documentTexts.join(' : '); }