From d32bdc47facbe8030a7e1a38b2d80cdfc7b4ea49 Mon Sep 17 00:00:00 2001 From: Bond Yan Date: Mon, 10 Feb 2025 10:52:00 -0500 Subject: [PATCH 1/5] catalog filter extension point Signed-off-by: Bond Yan --- .changeset/blue-camels-fry.md | 5 + .../report.api.md | 10 ++ .../DefaultTechDocsCollatorFactory.test.ts | 12 ++ .../DefaultTechDocsCollatorFactory.ts | 157 ++++++++++-------- .../src/module.ts | 27 +++ 5 files changed, 138 insertions(+), 73 deletions(-) create mode 100644 .changeset/blue-camels-fry.md diff --git a/.changeset/blue-camels-fry.md b/.changeset/blue-camels-fry.md new file mode 100644 index 0000000000..1f24a62b5b --- /dev/null +++ b/.changeset/blue-camels-fry.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-search-backend-module-techdocs': patch +--- + +Added an extension point that allows for custom entity filtering during document collation. diff --git a/plugins/search-backend-module-techdocs/report.api.md b/plugins/search-backend-module-techdocs/report.api.md index ef041e6900..1365af4545 100644 --- a/plugins/search-backend-module-techdocs/report.api.md +++ b/plugins/search-backend-module-techdocs/report.api.md @@ -72,6 +72,15 @@ export type TechDocsCollatorDocumentTransformer = ( > >; +// @public (undocumented) +export interface TechDocsCollatorEntityFilterExtensionPoint { + // (undocumented) + setEntityFilter(filterFunction: (entities: Entity[]) => Entity[]): void; +} + +// @public +export const techDocsCollatorEntityFilterExtensionPoint: ExtensionPoint; + // @public (undocumented) export type TechDocsCollatorEntityTransformer = ( entity: Entity, @@ -103,5 +112,6 @@ export type TechDocsCollatorFactoryOptions = { legacyPathCasing?: boolean; entityTransformer?: TechDocsCollatorEntityTransformer; documentTransformer?: TechDocsCollatorDocumentTransformer; + entityFilter?: (entity: Entity[]) => Entity[]; }; ``` diff --git a/plugins/search-backend-module-techdocs/src/collators/DefaultTechDocsCollatorFactory.test.ts b/plugins/search-backend-module-techdocs/src/collators/DefaultTechDocsCollatorFactory.test.ts index 2b8ec17b0d..b32cf7bd7a 100644 --- a/plugins/search-backend-module-techdocs/src/collators/DefaultTechDocsCollatorFactory.test.ts +++ b/plugins/search-backend-module-techdocs/src/collators/DefaultTechDocsCollatorFactory.test.ts @@ -193,6 +193,18 @@ describe('DefaultTechDocsCollatorFactory', () => { }); }); + it('should filter catalog entities when a custom filter is set', async () => { + factory = DefaultTechDocsCollatorFactory.fromConfig(config, { + ...options, + entityFilter: entities => + entities.filter(entity => entity.kind !== 'Component'), + }); + collator = await factory.getCollator(); + const pipeline = TestPipeline.fromCollator(collator); + const { documents } = await pipeline.execute(); + expect(documents).toHaveLength(0); + }); + it('paginates through catalog entities using batchSize', async () => { // A parallelismLimit of 1 is a catalog limit of 50 per request. Code // above in the /entities handler ensures valid entities are only diff --git a/plugins/search-backend-module-techdocs/src/collators/DefaultTechDocsCollatorFactory.ts b/plugins/search-backend-module-techdocs/src/collators/DefaultTechDocsCollatorFactory.ts index 3a586fd306..fb8393898a 100644 --- a/plugins/search-backend-module-techdocs/src/collators/DefaultTechDocsCollatorFactory.ts +++ b/plugins/search-backend-module-techdocs/src/collators/DefaultTechDocsCollatorFactory.ts @@ -68,6 +68,7 @@ export type TechDocsCollatorFactoryOptions = { legacyPathCasing?: boolean; entityTransformer?: TechDocsCollatorEntityTransformer; documentTransformer?: TechDocsCollatorDocumentTransformer; + entityFilter?: (entity: Entity[]) => Entity[]; }; type EntityInfo = { @@ -97,6 +98,7 @@ export class DefaultTechDocsCollatorFactory implements DocumentCollatorFactory { private readonly legacyPathCasing: boolean; private entityTransformer: TechDocsCollatorEntityTransformer; private documentTransformer: TechDocsCollatorDocumentTransformer; + private entityFilter: Function | undefined; private constructor(options: TechDocsCollatorFactoryOptions) { this.discovery = options.discovery; @@ -110,6 +112,7 @@ export class DefaultTechDocsCollatorFactory implements DocumentCollatorFactory { this.legacyPathCasing = options.legacyPathCasing ?? false; this.entityTransformer = options.entityTransformer ?? (() => ({})); this.documentTransformer = options.documentTransformer ?? (() => ({})); + this.entityFilter = options.entityFilter; this.auth = createLegacyAuthAdapters({ auth: options.auth, @@ -177,81 +180,83 @@ export class DefaultTechDocsCollatorFactory implements DocumentCollatorFactory { moreEntitiesToGet = entities.length === batchSize; entitiesRetrieved += entities.length; - const docPromises = entities - .filter(it => it.metadata?.annotations?.['backstage.io/techdocs-ref']) - .map((entity: Entity) => - limit(async (): Promise => { - const entityInfo = - DefaultTechDocsCollatorFactory.handleEntityInfoCasing( - this.legacyPathCasing, + const filteredEntities = this.entityFilter + ? this.entityFilter(entities) + : this.defaultFilteringFunction(entities); + + const docPromises = filteredEntities.map((entity: Entity) => + limit(async (): Promise => { + const entityInfo = + DefaultTechDocsCollatorFactory.handleEntityInfoCasing( + this.legacyPathCasing, + { + kind: entity.kind, + namespace: entity.metadata.namespace || 'default', + name: entity.metadata.name, + }, + ); + + try { + const { token: techdocsToken } = + await this.auth.getPluginRequestToken({ + onBehalfOf: await this.auth.getOwnServiceCredentials(), + targetPluginId: 'techdocs', + }); + + const searchIndexResponse = await fetch( + DefaultTechDocsCollatorFactory.constructDocsIndexUrl( + techDocsBaseUrl, + entityInfo, + ), + { + headers: { + Authorization: `Bearer ${techdocsToken}`, + }, + }, + ); + + // todo(@backstage/techdocs-core): remove Promise.race() when node-fetch is 3.x+ + // workaround for fetch().json() hanging in node-fetch@2.x.x, fixed in 3.x.x + // https://github.com/node-fetch/node-fetch/issues/665 + const searchIndex = await Promise.race([ + searchIndexResponse.json(), + new Promise((_resolve, reject) => { + setTimeout(() => { + reject('Could not parse JSON in 5 seconds.'); + }, 5000); + }), + ]); + + return searchIndex.docs.map((doc: MkSearchIndexDoc) => ({ + ...defaultTechDocsCollatorEntityTransformer(entity), + ...defaultTechDocsCollatorDocumentTransformer(doc), + ...this.entityTransformer(entity), + ...this.documentTransformer(doc), + location: this.applyArgsToFormat( + this.locationTemplate || '/docs/:namespace/:kind/:name/:path', { - kind: entity.kind, - namespace: entity.metadata.namespace || 'default', - name: entity.metadata.name, + ...entityInfo, + path: doc.location, }, - ); - - try { - const { token: techdocsToken } = - await this.auth.getPluginRequestToken({ - onBehalfOf: await this.auth.getOwnServiceCredentials(), - targetPluginId: 'techdocs', - }); - - const searchIndexResponse = await fetch( - DefaultTechDocsCollatorFactory.constructDocsIndexUrl( - techDocsBaseUrl, - entityInfo, - ), - { - headers: { - Authorization: `Bearer ${techdocsToken}`, - }, - }, - ); - - // todo(@backstage/techdocs-core): remove Promise.race() when node-fetch is 3.x+ - // workaround for fetch().json() hanging in node-fetch@2.x.x, fixed in 3.x.x - // https://github.com/node-fetch/node-fetch/issues/665 - const searchIndex = await Promise.race([ - searchIndexResponse.json(), - new Promise((_resolve, reject) => { - setTimeout(() => { - reject('Could not parse JSON in 5 seconds.'); - }, 5000); - }), - ]); - - return searchIndex.docs.map((doc: MkSearchIndexDoc) => ({ - ...defaultTechDocsCollatorEntityTransformer(entity), - ...defaultTechDocsCollatorDocumentTransformer(doc), - ...this.entityTransformer(entity), - ...this.documentTransformer(doc), - location: this.applyArgsToFormat( - this.locationTemplate || '/docs/:namespace/:kind/:name/:path', - { - ...entityInfo, - path: doc.location, - }, - ), - ...entityInfo, - entityTitle: entity.metadata.title, - componentType: entity.spec?.type?.toString() || 'other', - lifecycle: (entity.spec?.lifecycle as string) || '', - owner: getSimpleEntityOwnerString(entity), - authorization: { - resourceRef: stringifyEntityRef(entity), - }, - })); - } catch (e) { - this.logger.debug( - `Failed to retrieve tech docs search index for entity ${entityInfo.namespace}/${entityInfo.kind}/${entityInfo.name}`, - e, - ); - return []; - } - }), - ); + ), + ...entityInfo, + entityTitle: entity.metadata.title, + componentType: entity.spec?.type?.toString() || 'other', + lifecycle: (entity.spec?.lifecycle as string) || '', + owner: getSimpleEntityOwnerString(entity), + authorization: { + resourceRef: stringifyEntityRef(entity), + }, + })); + } catch (e) { + this.logger.debug( + `Failed to retrieve tech docs search index for entity ${entityInfo.namespace}/${entityInfo.kind}/${entityInfo.name}`, + e, + ); + return []; + } + }), + ); yield* (await Promise.all(docPromises)).flat(); } } @@ -267,6 +272,12 @@ export class DefaultTechDocsCollatorFactory implements DocumentCollatorFactory { return formatted; } + private defaultFilteringFunction(entities: Entity[]): Entity[] { + return entities.filter( + entity => entity.metadata?.annotations?.['backstage.io/techdocs-ref'], + ); + } + private static constructDocsIndexUrl( techDocsBaseUrl: string, entityInfo: { kind: string; namespace: string; name: string }, diff --git a/plugins/search-backend-module-techdocs/src/module.ts b/plugins/search-backend-module-techdocs/src/module.ts index 0114b88c2d..68749a21ab 100644 --- a/plugins/search-backend-module-techdocs/src/module.ts +++ b/plugins/search-backend-module-techdocs/src/module.ts @@ -25,6 +25,7 @@ import { createExtensionPoint, readSchedulerServiceTaskScheduleDefinitionFromConfig, } from '@backstage/backend-plugin-api'; +import { Entity } from '@backstage/catalog-model'; import { catalogServiceRef } from '@backstage/plugin-catalog-node/alpha'; import { DefaultTechDocsCollatorFactory, @@ -51,6 +52,21 @@ export const techdocsCollatorEntityTransformerExtensionPoint = id: 'search.techdocsCollator.transformer', }); +/** @public */ +export interface TechDocsCollatorEntityFilterExtensionPoint { + setEntityFilter(filterFunction: (entities: Entity[]) => Entity[]): void; +} + +/** + * Extension point used to filter the entities that the collator will use. + * + * @public + */ +export const techDocsCollatorEntityFilterExtensionPoint = + createExtensionPoint({ + id: 'search.techdocsCollator.entityFilter', + }); + /** * @public * Search backend module for the TechDocs index. @@ -61,6 +77,7 @@ export default createBackendModule({ register(env) { let entityTransformer: TechDocsCollatorEntityTransformer | undefined; let documentTransformer: TechDocsCollatorDocumentTransformer | undefined; + let entityFilter: ((e: Entity[]) => Entity[]) | undefined; env.registerExtensionPoint( techdocsCollatorEntityTransformerExtensionPoint, @@ -84,6 +101,15 @@ export default createBackendModule({ }, ); + env.registerExtensionPoint(techDocsCollatorEntityFilterExtensionPoint, { + setEntityFilter(newEntityFilter) { + if (entityFilter) { + throw new Error('TechDocs entity filters may only be set once'); + } + entityFilter = newEntityFilter; + }, + }); + env.registerInit({ deps: { config: coreServices.rootConfig, @@ -127,6 +153,7 @@ export default createBackendModule({ catalogClient: catalog, entityTransformer, documentTransformer, + entityFilter, }), }); }, From e8df7baa63c43f5e23db4e8133331633e26fdd3f Mon Sep 17 00:00:00 2001 From: Bond Yan Date: Mon, 10 Feb 2025 17:12:20 -0500 Subject: [PATCH 2/5] more filter options Signed-off-by: Bond Yan --- .../DefaultTechDocsCollatorFactory.test.ts | 2 +- .../DefaultTechDocsCollatorFactory.ts | 15 ++++++---- .../src/module.ts | 30 ++++++++++++++----- 3 files changed, 34 insertions(+), 13 deletions(-) diff --git a/plugins/search-backend-module-techdocs/src/collators/DefaultTechDocsCollatorFactory.test.ts b/plugins/search-backend-module-techdocs/src/collators/DefaultTechDocsCollatorFactory.test.ts index b32cf7bd7a..3aff8cbbb7 100644 --- a/plugins/search-backend-module-techdocs/src/collators/DefaultTechDocsCollatorFactory.test.ts +++ b/plugins/search-backend-module-techdocs/src/collators/DefaultTechDocsCollatorFactory.test.ts @@ -196,7 +196,7 @@ describe('DefaultTechDocsCollatorFactory', () => { it('should filter catalog entities when a custom filter is set', async () => { factory = DefaultTechDocsCollatorFactory.fromConfig(config, { ...options, - entityFilter: entities => + entityFilterFunction: entities => entities.filter(entity => entity.kind !== 'Component'), }); collator = await factory.getCollator(); diff --git a/plugins/search-backend-module-techdocs/src/collators/DefaultTechDocsCollatorFactory.ts b/plugins/search-backend-module-techdocs/src/collators/DefaultTechDocsCollatorFactory.ts index fb8393898a..2890610c03 100644 --- a/plugins/search-backend-module-techdocs/src/collators/DefaultTechDocsCollatorFactory.ts +++ b/plugins/search-backend-module-techdocs/src/collators/DefaultTechDocsCollatorFactory.ts @@ -22,6 +22,7 @@ import { CATALOG_FILTER_EXISTS, CatalogApi, CatalogClient, + EntityFilterQuery, } from '@backstage/catalog-client'; import { Entity, @@ -68,7 +69,8 @@ export type TechDocsCollatorFactoryOptions = { legacyPathCasing?: boolean; entityTransformer?: TechDocsCollatorEntityTransformer; documentTransformer?: TechDocsCollatorDocumentTransformer; - entityFilter?: (entity: Entity[]) => Entity[]; + entityFilterFunction?: (entity: Entity[]) => Entity[]; + customCatalogApiFilters?: EntityFilterQuery; }; type EntityInfo = { @@ -98,7 +100,8 @@ export class DefaultTechDocsCollatorFactory implements DocumentCollatorFactory { private readonly legacyPathCasing: boolean; private entityTransformer: TechDocsCollatorEntityTransformer; private documentTransformer: TechDocsCollatorDocumentTransformer; - private entityFilter: Function | undefined; + private entityFilterFunction: Function | undefined; + private customCatalogApiFilters: EntityFilterQuery | undefined; private constructor(options: TechDocsCollatorFactoryOptions) { this.discovery = options.discovery; @@ -112,7 +115,8 @@ export class DefaultTechDocsCollatorFactory implements DocumentCollatorFactory { this.legacyPathCasing = options.legacyPathCasing ?? false; this.entityTransformer = options.entityTransformer ?? (() => ({})); this.documentTransformer = options.documentTransformer ?? (() => ({})); - this.entityFilter = options.entityFilter; + this.entityFilterFunction = options.entityFilterFunction; + this.customCatalogApiFilters = options.customCatalogApiFilters; this.auth = createLegacyAuthAdapters({ auth: options.auth, @@ -168,6 +172,7 @@ export class DefaultTechDocsCollatorFactory implements DocumentCollatorFactory { filter: { 'metadata.annotations.backstage.io/techdocs-ref': CATALOG_FILTER_EXISTS, + ...this.customCatalogApiFilters, }, limit: batchSize, offset: entitiesRetrieved, @@ -180,8 +185,8 @@ export class DefaultTechDocsCollatorFactory implements DocumentCollatorFactory { moreEntitiesToGet = entities.length === batchSize; entitiesRetrieved += entities.length; - const filteredEntities = this.entityFilter - ? this.entityFilter(entities) + const filteredEntities = this.entityFilterFunction + ? this.entityFilterFunction(entities) : this.defaultFilteringFunction(entities); const docPromises = filteredEntities.map((entity: Entity) => diff --git a/plugins/search-backend-module-techdocs/src/module.ts b/plugins/search-backend-module-techdocs/src/module.ts index 68749a21ab..2888b9527b 100644 --- a/plugins/search-backend-module-techdocs/src/module.ts +++ b/plugins/search-backend-module-techdocs/src/module.ts @@ -25,6 +25,7 @@ import { createExtensionPoint, readSchedulerServiceTaskScheduleDefinitionFromConfig, } from '@backstage/backend-plugin-api'; +import { EntityFilterQuery } from '@backstage/catalog-client'; import { Entity } from '@backstage/catalog-model'; import { catalogServiceRef } from '@backstage/plugin-catalog-node/alpha'; import { @@ -54,7 +55,10 @@ export const techdocsCollatorEntityTransformerExtensionPoint = /** @public */ export interface TechDocsCollatorEntityFilterExtensionPoint { - setEntityFilter(filterFunction: (entities: Entity[]) => Entity[]): void; + setEntityFilterFunction( + filterFunction: (entities: Entity[]) => Entity[], + ): void; + setCustomCatalogApiFilters(apiFilters: EntityFilterQuery): void; } /** @@ -77,7 +81,8 @@ export default createBackendModule({ register(env) { let entityTransformer: TechDocsCollatorEntityTransformer | undefined; let documentTransformer: TechDocsCollatorDocumentTransformer | undefined; - let entityFilter: ((e: Entity[]) => Entity[]) | undefined; + let entityFilterFunction: ((e: Entity[]) => Entity[]) | undefined; + let customCatalogApiFilters: EntityFilterQuery | undefined; env.registerExtensionPoint( techdocsCollatorEntityTransformerExtensionPoint, @@ -102,11 +107,21 @@ export default createBackendModule({ ); env.registerExtensionPoint(techDocsCollatorEntityFilterExtensionPoint, { - setEntityFilter(newEntityFilter) { - if (entityFilter) { - throw new Error('TechDocs entity filters may only be set once'); + setEntityFilterFunction(newEntityFilterFunction) { + if (entityFilterFunction) { + throw new Error( + 'TechDocs entity filter functions may only be set once', + ); } - entityFilter = newEntityFilter; + entityFilterFunction = newEntityFilterFunction; + }, + setCustomCatalogApiFilters(newCatalogApiFilters) { + if (customCatalogApiFilters) { + throw new Error( + 'TechDocs catalog entity filters may only be set once', + ); + } + customCatalogApiFilters = newCatalogApiFilters; }, }); @@ -153,7 +168,8 @@ export default createBackendModule({ catalogClient: catalog, entityTransformer, documentTransformer, - entityFilter, + customCatalogApiFilters, + entityFilterFunction, }), }); }, From 1922ed986fbdf5b57aa506a27e51b349356a7ebd Mon Sep 17 00:00:00 2001 From: Bond Yan Date: Mon, 10 Feb 2025 17:17:19 -0500 Subject: [PATCH 3/5] new api report Signed-off-by: Bond Yan --- plugins/search-backend-module-techdocs/report.api.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/plugins/search-backend-module-techdocs/report.api.md b/plugins/search-backend-module-techdocs/report.api.md index 1365af4545..ecfa434eea 100644 --- a/plugins/search-backend-module-techdocs/report.api.md +++ b/plugins/search-backend-module-techdocs/report.api.md @@ -12,6 +12,7 @@ import { Config } from '@backstage/config'; import { DiscoveryService } from '@backstage/backend-plugin-api'; import { DocumentCollatorFactory } from '@backstage/plugin-search-common'; import { Entity } from '@backstage/catalog-model'; +import { EntityFilterQuery } from '@backstage/catalog-client'; import { ExtensionPoint } from '@backstage/backend-plugin-api'; import { HttpAuthService } from '@backstage/backend-plugin-api'; import { LoggerService } from '@backstage/backend-plugin-api'; @@ -75,7 +76,11 @@ export type TechDocsCollatorDocumentTransformer = ( // @public (undocumented) export interface TechDocsCollatorEntityFilterExtensionPoint { // (undocumented) - setEntityFilter(filterFunction: (entities: Entity[]) => Entity[]): void; + setCustomCatalogApiFilters(apiFilters: EntityFilterQuery): void; + // (undocumented) + setEntityFilterFunction( + filterFunction: (entities: Entity[]) => Entity[], + ): void; } // @public @@ -112,6 +117,7 @@ export type TechDocsCollatorFactoryOptions = { legacyPathCasing?: boolean; entityTransformer?: TechDocsCollatorEntityTransformer; documentTransformer?: TechDocsCollatorDocumentTransformer; - entityFilter?: (entity: Entity[]) => Entity[]; + entityFilterFunction?: (entity: Entity[]) => Entity[]; + customCatalogApiFilters?: EntityFilterQuery; }; ``` From 03d34525bedd41100f57bc6d62103f7042e599be Mon Sep 17 00:00:00 2001 From: Bond Yan Date: Fri, 14 Feb 2025 18:25:25 -0500 Subject: [PATCH 4/5] removed node-fetch workaround Signed-off-by: Bond Yan --- .../collators/DefaultTechDocsCollatorFactory.ts | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/plugins/search-backend-module-techdocs/src/collators/DefaultTechDocsCollatorFactory.ts b/plugins/search-backend-module-techdocs/src/collators/DefaultTechDocsCollatorFactory.ts index 2890610c03..5331cd9105 100644 --- a/plugins/search-backend-module-techdocs/src/collators/DefaultTechDocsCollatorFactory.ts +++ b/plugins/search-backend-module-techdocs/src/collators/DefaultTechDocsCollatorFactory.ts @@ -208,7 +208,7 @@ export class DefaultTechDocsCollatorFactory implements DocumentCollatorFactory { targetPluginId: 'techdocs', }); - const searchIndexResponse = await fetch( + const searchIndex = await fetch( DefaultTechDocsCollatorFactory.constructDocsIndexUrl( techDocsBaseUrl, entityInfo, @@ -218,19 +218,7 @@ export class DefaultTechDocsCollatorFactory implements DocumentCollatorFactory { Authorization: `Bearer ${techdocsToken}`, }, }, - ); - - // todo(@backstage/techdocs-core): remove Promise.race() when node-fetch is 3.x+ - // workaround for fetch().json() hanging in node-fetch@2.x.x, fixed in 3.x.x - // https://github.com/node-fetch/node-fetch/issues/665 - const searchIndex = await Promise.race([ - searchIndexResponse.json(), - new Promise((_resolve, reject) => { - setTimeout(() => { - reject('Could not parse JSON in 5 seconds.'); - }, 5000); - }), - ]); + ).then(res => res.json()); return searchIndex.docs.map((doc: MkSearchIndexDoc) => ({ ...defaultTechDocsCollatorEntityTransformer(entity), From e7a3b2ed2e0037d0ddc0a445b80453ba20082744 Mon Sep 17 00:00:00 2001 From: Bond Yan Date: Wed, 5 Mar 2025 15:19:25 -0500 Subject: [PATCH 5/5] added extension point documentation Signed-off-by: Bond Yan --- docs/features/search/collators.md | 32 +++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/docs/features/search/collators.md b/docs/features/search/collators.md index 834a14437a..d87258f65b 100644 --- a/docs/features/search/collators.md +++ b/docs/features/search/collators.md @@ -134,6 +134,38 @@ search: timeout: { minutes: 3 } ``` +### Filtering through the catalog collator + +The TechDocs collator by default filters through catalog entities where the annotation `metadata.annotations.backstage.io/techdocs-ref` exists. If you wish to further filter out entities, there are two ways to do so through the `techDocsCollatorEntityFilterExtensionPoint`. + +```typescript +export const exampleCustomCatalogFiltering = createBackendModule({ + pluginId: 'search', + moduleId: 'search-techdocs-collator-entity-filter', + register(reg) { + reg.registerInit({ + deps: { + customCollatorFilter: techDocsCollatorEntityFilterExtensionPoint, + }, + async init({ customCollatorFilter }) { + /* filtering by catalog params */ + customCollatorFilter.setCustomCatalogApiFilters([ + { kind: ['API', 'Component', ...] }, + { metadata: ['...more filters'] }, + ]); + + /* filtering by a custom function */ + customCollatorFilter.setEntityFilterFunction((entities: Entity[]) => + entities.filter( + entity => entity.metadata?.annotations?.abc === 'xyz', + ), + ); + }, + }); + }, +}); +``` + ## Community Collators Here are some of the known Search Collators available in from the Backstage Community: