diff --git a/.changeset/fast-wasps-matter.md b/.changeset/fast-wasps-matter.md new file mode 100644 index 0000000000..6b6a682a37 --- /dev/null +++ b/.changeset/fast-wasps-matter.md @@ -0,0 +1,23 @@ +--- +'@backstage/create-app': patch +--- + +Updated the search configuration class to use the static `fromConfig`-based constructor for the `DefaultCatalogCollator`. + +To apply this change to an existing app, replace the following line in `search.ts`: + +```diff +-collator: new DefaultCatalogCollator({ discovery }) ++collator: DefaultCatalogCollator.fromConfig(config, { discovery }) +``` + +The `config` parameter was not needed before, so make sure you also add that in the signature of `createPlugin` +in `search.ts`: + +```diff +export default async function createPlugin({ + logger, + discovery, ++ config, +}: PluginEnvironment) { +``` diff --git a/.changeset/happy-cups-tap.md b/.changeset/happy-cups-tap.md new file mode 100644 index 0000000000..051f9def1c --- /dev/null +++ b/.changeset/happy-cups-tap.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend': patch +--- + +Allow the catalog search collator to filter the entities that it indexes diff --git a/packages/backend/src/plugins/search.ts b/packages/backend/src/plugins/search.ts index 3470110408..2da108608e 100644 --- a/packages/backend/src/plugins/search.ts +++ b/packages/backend/src/plugins/search.ts @@ -68,7 +68,7 @@ export default async function createPlugin({ // particular collator gathers entities from the software catalog. indexBuilder.addCollator({ defaultRefreshIntervalSeconds: 600, - collator: new DefaultCatalogCollator({ discovery }), + collator: DefaultCatalogCollator.fromConfig(config, { discovery }), }); indexBuilder.addCollator({ diff --git a/packages/create-app/templates/default-app/packages/backend/src/plugins/search.ts b/packages/create-app/templates/default-app/packages/backend/src/plugins/search.ts index 248ed37ea8..7fc317d23d 100644 --- a/packages/create-app/templates/default-app/packages/backend/src/plugins/search.ts +++ b/packages/create-app/templates/default-app/packages/backend/src/plugins/search.ts @@ -10,6 +10,7 @@ import { DefaultCatalogCollator } from '@backstage/plugin-catalog-backend'; export default async function createPlugin({ logger, discovery, + config, }: PluginEnvironment) { // Initialize a connection to a search engine. const searchEngine = new LunrSearchEngine({ logger }); @@ -19,7 +20,7 @@ export default async function createPlugin({ // particular collator gathers entities from the software catalog. indexBuilder.addCollator({ defaultRefreshIntervalSeconds: 600, - collator: new DefaultCatalogCollator({ discovery }), + collator: DefaultCatalogCollator.fromConfig(config, { discovery }), }); // The scheduler controls when documents are gathered from collators and sent diff --git a/plugins/catalog-backend/api-report.md b/plugins/catalog-backend/api-report.md index a7a100db65..01a824d049 100644 --- a/plugins/catalog-backend/api-report.md +++ b/plugins/catalog-backend/api-report.md @@ -7,6 +7,8 @@ import { Account } from 'aws-sdk/clients/organizations'; import { BitbucketIntegration } from '@backstage/integration'; +import { CatalogApi } from '@backstage/catalog-client'; +import { CatalogEntitiesRequest } from '@backstage/catalog-client'; import { Config } from '@backstage/config'; import { DocumentCollator } from '@backstage/search-common'; import { Entity } from '@backstage/catalog-model'; @@ -204,22 +206,6 @@ export class CatalogBuilder { ): CatalogBuilder; } -// Warning: (ae-missing-release-tag) "CatalogEntityDocument" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) -export interface CatalogEntityDocument extends IndexableDocument { - // (undocumented) - componentType: string; - // (undocumented) - kind: string; - // (undocumented) - lifecycle: string; - // (undocumented) - namespace: string; - // (undocumented) - owner: string; -} - // Warning: (ae-missing-release-tag) "CatalogProcessingOrchestrator" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -581,9 +567,13 @@ export class DefaultCatalogCollator implements DocumentCollator { constructor({ discovery, locationTemplate, + filter, + catalogClient, }: { discovery: PluginEndpointDiscovery; locationTemplate?: string; + filter?: CatalogEntitiesRequest['filter']; + catalogClient?: CatalogApi; }); // (undocumented) protected applyArgsToFormat( @@ -591,10 +581,24 @@ export class DefaultCatalogCollator implements DocumentCollator { args: Record, ): string; // (undocumented) + protected readonly catalogClient: CatalogApi; + // (undocumented) protected discovery: PluginEndpointDiscovery; + // Warning: (ae-forgotten-export) The symbol "CatalogEntityDocument" needs to be exported by the entry point index.d.ts + // // (undocumented) execute(): Promise; // (undocumented) + protected filter?: CatalogEntitiesRequest['filter']; + // (undocumented) + static fromConfig( + _config: Config, + options: { + discovery: PluginEndpointDiscovery; + filter?: CatalogEntitiesRequest['filter']; + }, + ): DefaultCatalogCollator; + // (undocumented) protected locationTemplate: string; // (undocumented) readonly type: string; diff --git a/plugins/catalog-backend/src/search/DefaultCatalogCollator.test.ts b/plugins/catalog-backend/src/search/DefaultCatalogCollator.test.ts index 2b9745298c..bb556fc49f 100644 --- a/plugins/catalog-backend/src/search/DefaultCatalogCollator.test.ts +++ b/plugins/catalog-backend/src/search/DefaultCatalogCollator.test.ts @@ -17,6 +17,11 @@ import { PluginEndpointDiscovery } from '@backstage/backend-common'; import { Entity } from '@backstage/catalog-model'; import { DefaultCatalogCollator } from './DefaultCatalogCollator'; +import { setupServer } from 'msw/node'; +import { rest } from 'msw'; +import { ConfigReader } from '@backstage/config'; + +const server = setupServer(); const expectedEntities: Entity[] = [ { @@ -34,27 +39,37 @@ const expectedEntities: Entity[] = [ }, ]; -jest.mock('cross-fetch', () => ({ - __esModule: true, - default: async () => { - return { - json: async () => { - return expectedEntities; - }, - }; - }, -})); - describe('DefaultCatalogCollator', () => { let mockDiscoveryApi: jest.Mocked; let collator: DefaultCatalogCollator; - beforeEach(() => { + beforeAll(() => { mockDiscoveryApi = { - getBaseUrl: jest.fn().mockResolvedValueOnce('http://localhost:7000'), + getBaseUrl: jest.fn().mockResolvedValue('http://localhost:7000'), getExternalBaseUrl: jest.fn(), }; collator = new DefaultCatalogCollator({ discovery: mockDiscoveryApi }); + server.listen(); + }); + beforeEach(() => { + server.use( + rest.get('http://localhost:7000/entities', (req, res, ctx) => { + if (req.url.searchParams.has('filter')) { + const filter = req.url.searchParams.get('filter'); + if (filter === 'kind=Foo,kind=Bar') { + // When filtering on the 'Foo,Bar' kinds we simply return no items, to simulate a filter + return res(ctx.json([])); + } + throw new Error('Unexpected filter parameter'); + } + return res(ctx.json(expectedEntities)); + }), + ); + }); + afterEach(() => server.resetHandlers()); + afterAll(() => { + server.close(); + jest.useRealTimers(); }); it('fetches from the configured catalog service', async () => { @@ -88,4 +103,18 @@ describe('DefaultCatalogCollator', () => { location: '/software/test-entity', }); }); + + it('allows filtering of the retrieved catalog entities', async () => { + // Provide an alternate location template. + collator = DefaultCatalogCollator.fromConfig(new ConfigReader({}), { + discovery: mockDiscoveryApi, + filter: { + kind: ['Foo', 'Bar'], + }, + }); + + const documents = await collator.execute(); + // The simulated 'Foo,Bar' filter should return in an empty list + expect(documents).toHaveLength(0); + }); }); diff --git a/plugins/catalog-backend/src/search/DefaultCatalogCollator.ts b/plugins/catalog-backend/src/search/DefaultCatalogCollator.ts index 1f9c68e549..6292b2a833 100644 --- a/plugins/catalog-backend/src/search/DefaultCatalogCollator.ts +++ b/plugins/catalog-backend/src/search/DefaultCatalogCollator.ts @@ -17,7 +17,12 @@ import { PluginEndpointDiscovery } from '@backstage/backend-common'; import { Entity } from '@backstage/catalog-model'; import { IndexableDocument, DocumentCollator } from '@backstage/search-common'; -import fetch from 'cross-fetch'; +import { Config } from '@backstage/config'; +import { + CatalogApi, + CatalogClient, + CatalogEntitiesRequest, +} from '@backstage/catalog-client'; export interface CatalogEntityDocument extends IndexableDocument { componentType: string; @@ -30,18 +35,39 @@ export interface CatalogEntityDocument extends IndexableDocument { export class DefaultCatalogCollator implements DocumentCollator { protected discovery: PluginEndpointDiscovery; protected locationTemplate: string; + protected filter?: CatalogEntitiesRequest['filter']; + protected readonly catalogClient: CatalogApi; public readonly type: string = 'software-catalog'; + static fromConfig( + _config: Config, + options: { + discovery: PluginEndpointDiscovery; + filter?: CatalogEntitiesRequest['filter']; + }, + ) { + return new DefaultCatalogCollator({ + ...options, + }); + } + constructor({ discovery, locationTemplate, + filter, + catalogClient, }: { discovery: PluginEndpointDiscovery; locationTemplate?: string; + filter?: CatalogEntitiesRequest['filter']; + catalogClient?: CatalogApi; }) { this.discovery = discovery; this.locationTemplate = locationTemplate || '/catalog/:namespace/:kind/:name'; + this.filter = filter; + this.catalogClient = + catalogClient || new CatalogClient({ discoveryApi: discovery }); } protected applyArgsToFormat( @@ -56,10 +82,10 @@ export class DefaultCatalogCollator implements DocumentCollator { } async execute() { - const baseUrl = await this.discovery.getBaseUrl('catalog'); - const res = await fetch(`${baseUrl}/entities`); - const entities: Entity[] = await res.json(); - return entities.map((entity: Entity): CatalogEntityDocument => { + const response = await this.catalogClient.getEntities({ + filter: this.filter, + }); + return response.items.map((entity: Entity): CatalogEntityDocument => { return { title: entity.metadata.name, location: this.applyArgsToFormat(this.locationTemplate, { diff --git a/plugins/catalog-backend/src/search/index.ts b/plugins/catalog-backend/src/search/index.ts index 7e13999864..dfe1cf541e 100644 --- a/plugins/catalog-backend/src/search/index.ts +++ b/plugins/catalog-backend/src/search/index.ts @@ -14,4 +14,4 @@ * limitations under the License. */ -export * from './DefaultCatalogCollator'; +export { DefaultCatalogCollator } from './DefaultCatalogCollator';