Merge pull request #6955 from bolcom/default-catalog-collator-filter
Allow the entities indexed by DefaultCatalogCollator to be filtered by kind
This commit is contained in:
@@ -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) {
|
||||
```
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-catalog-backend': patch
|
||||
---
|
||||
|
||||
Allow the catalog search collator to filter the entities that it indexes
|
||||
@@ -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({
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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, string>,
|
||||
): 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<CatalogEntityDocument[]>;
|
||||
// (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;
|
||||
|
||||
@@ -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<PluginEndpointDiscovery>;
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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, {
|
||||
|
||||
@@ -14,4 +14,4 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export * from './DefaultCatalogCollator';
|
||||
export { DefaultCatalogCollator } from './DefaultCatalogCollator';
|
||||
|
||||
Reference in New Issue
Block a user