Refactor catalog collator, extracted abstract class and entity processor to make it possible to re-use functionality
Signed-off-by: Ilya Savich <isavich@box.com>
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-catalog-backend': patch
|
||||
---
|
||||
|
||||
Refactored catalog collator, extracted abstract class and entity processor to make it possible to re-use functionality
|
||||
@@ -167,6 +167,42 @@ export class CatalogBuilder {
|
||||
useLegacySingleProcessorValidation(): this;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export abstract class CatalogCollatorFactory
|
||||
implements DocumentCollatorFactory
|
||||
{
|
||||
protected constructor(options: CatalogCollatorFactoryOptions);
|
||||
// (undocumented)
|
||||
static fromConfig(
|
||||
_config: Config,
|
||||
_options: CatalogCollatorFactoryCreateOptions,
|
||||
): CatalogCollatorFactory;
|
||||
// (undocumented)
|
||||
getCollator(): Promise<Readable>;
|
||||
// (undocumented)
|
||||
readonly type: string;
|
||||
// (undocumented)
|
||||
readonly visibilityPermission: Permission;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export type CatalogCollatorFactoryCreateOptions = Omit<
|
||||
CatalogCollatorFactoryOptions,
|
||||
'entityProcessor' | 'type'
|
||||
>;
|
||||
|
||||
// @public (undocumented)
|
||||
export type CatalogCollatorFactoryOptions = {
|
||||
type: string;
|
||||
discovery: PluginEndpointDiscovery;
|
||||
tokenManager: TokenManager;
|
||||
entityProcessor: CatalogCollatorEntityProcessor;
|
||||
locationTemplate?: string;
|
||||
filter?: GetEntitiesRequest['filter'];
|
||||
batchSize?: number;
|
||||
catalogClient?: CatalogApi;
|
||||
};
|
||||
|
||||
// @alpha
|
||||
export const catalogConditions: Conditions<{
|
||||
hasAnnotation: PermissionRule<
|
||||
@@ -351,29 +387,17 @@ export class DefaultCatalogCollator {
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export class DefaultCatalogCollatorFactory implements DocumentCollatorFactory {
|
||||
export class DefaultCatalogCollatorFactory extends CatalogCollatorFactory {
|
||||
// (undocumented)
|
||||
static fromConfig(
|
||||
_config: Config,
|
||||
options: DefaultCatalogCollatorFactoryOptions,
|
||||
): DefaultCatalogCollatorFactory;
|
||||
// (undocumented)
|
||||
getCollator(): Promise<Readable>;
|
||||
// (undocumented)
|
||||
readonly type: string;
|
||||
// (undocumented)
|
||||
readonly visibilityPermission: Permission;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export type DefaultCatalogCollatorFactoryOptions = {
|
||||
discovery: PluginEndpointDiscovery;
|
||||
tokenManager: TokenManager;
|
||||
locationTemplate?: string;
|
||||
filter?: GetEntitiesRequest['filter'];
|
||||
batchSize?: number;
|
||||
catalogClient?: CatalogApi;
|
||||
};
|
||||
export type DefaultCatalogCollatorFactoryOptions =
|
||||
CatalogCollatorFactoryCreateOptions;
|
||||
|
||||
export { DeferredEntity };
|
||||
|
||||
@@ -583,4 +607,8 @@ export class UrlReaderProcessor implements CatalogProcessor {
|
||||
cache: CatalogProcessorCache,
|
||||
): Promise<boolean>;
|
||||
}
|
||||
|
||||
// Warnings were encountered during analysis:
|
||||
//
|
||||
// src/search/CatalogCollatorFactory.d.ts:14:5 - (ae-forgotten-export) The symbol "CatalogCollatorEntityProcessor" needs to be exported by the entry point index.d.ts
|
||||
```
|
||||
|
||||
+4
-11
@@ -14,16 +14,9 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Entity, isUserEntity, isGroupEntity } from '@backstage/catalog-model';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { CatalogEntityDocument } from '@backstage/plugin-catalog-common';
|
||||
|
||||
export function getDocumentText(entity: Entity): string {
|
||||
const documentTexts: string[] = [];
|
||||
documentTexts.push(entity.metadata.description || '');
|
||||
|
||||
if (isUserEntity(entity) || isGroupEntity(entity)) {
|
||||
if (entity.spec?.profile?.displayName) {
|
||||
documentTexts.push(entity.spec.profile.displayName);
|
||||
}
|
||||
}
|
||||
return documentTexts.join(' : ');
|
||||
export interface CatalogCollatorEntityProcessor {
|
||||
process(entity: Entity, locationTemplate: string): CatalogEntityDocument;
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
/*
|
||||
* 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 {
|
||||
PluginEndpointDiscovery,
|
||||
TokenManager,
|
||||
} from '@backstage/backend-common';
|
||||
import {
|
||||
CatalogApi,
|
||||
CatalogClient,
|
||||
GetEntitiesRequest,
|
||||
} from '@backstage/catalog-client';
|
||||
import { DocumentCollatorFactory } from '@backstage/plugin-search-common';
|
||||
import {
|
||||
catalogEntityReadPermission,
|
||||
CatalogEntityDocument,
|
||||
} from '@backstage/plugin-catalog-common';
|
||||
import { Permission } from '@backstage/plugin-permission-common';
|
||||
import { Readable } from 'stream';
|
||||
import { CatalogCollatorEntityProcessor } from './CatalogCollatorEntityProcessor';
|
||||
import { Config } from '@backstage/config';
|
||||
|
||||
/** @public */
|
||||
export type CatalogCollatorFactoryOptions = {
|
||||
type: string;
|
||||
discovery: PluginEndpointDiscovery;
|
||||
tokenManager: TokenManager;
|
||||
entityProcessor: CatalogCollatorEntityProcessor;
|
||||
locationTemplate?: string;
|
||||
filter?: GetEntitiesRequest['filter'];
|
||||
batchSize?: number;
|
||||
catalogClient?: CatalogApi;
|
||||
};
|
||||
|
||||
/** @public */
|
||||
export type CatalogCollatorFactoryCreateOptions = Omit<
|
||||
CatalogCollatorFactoryOptions,
|
||||
'entityProcessor' | 'type'
|
||||
>;
|
||||
|
||||
/** @public */
|
||||
export abstract class CatalogCollatorFactory
|
||||
implements DocumentCollatorFactory
|
||||
{
|
||||
public readonly type: string;
|
||||
public readonly visibilityPermission: Permission =
|
||||
catalogEntityReadPermission;
|
||||
|
||||
private locationTemplate: string;
|
||||
private filter?: GetEntitiesRequest['filter'];
|
||||
private batchSize: number;
|
||||
private readonly catalogClient: CatalogApi;
|
||||
private tokenManager: TokenManager;
|
||||
private entityProcessor: CatalogCollatorEntityProcessor;
|
||||
|
||||
static fromConfig(
|
||||
_config: Config,
|
||||
_options: CatalogCollatorFactoryCreateOptions,
|
||||
): CatalogCollatorFactory {
|
||||
throw new Error('Method should be implemented');
|
||||
}
|
||||
|
||||
protected constructor(options: CatalogCollatorFactoryOptions) {
|
||||
const {
|
||||
type,
|
||||
batchSize,
|
||||
discovery,
|
||||
locationTemplate,
|
||||
filter,
|
||||
catalogClient,
|
||||
tokenManager,
|
||||
entityProcessor,
|
||||
} = options;
|
||||
|
||||
this.type = type;
|
||||
this.locationTemplate =
|
||||
locationTemplate || '/catalog/:namespace/:kind/:name';
|
||||
this.filter = filter;
|
||||
this.batchSize = batchSize || 500;
|
||||
this.catalogClient =
|
||||
catalogClient || new CatalogClient({ discoveryApi: discovery });
|
||||
this.tokenManager = tokenManager;
|
||||
this.entityProcessor = entityProcessor;
|
||||
}
|
||||
|
||||
async getCollator(): Promise<Readable> {
|
||||
return Readable.from(this.execute());
|
||||
}
|
||||
|
||||
private async *execute(): AsyncGenerator<CatalogEntityDocument> {
|
||||
const { token } = await this.tokenManager.getToken();
|
||||
let entitiesRetrieved = 0;
|
||||
let moreEntitiesToGet = true;
|
||||
|
||||
// Offset/limit pagination is used on the Catalog Client in order to
|
||||
// limit (and allow some control over) memory used by the search backend
|
||||
// at index-time.
|
||||
while (moreEntitiesToGet) {
|
||||
const entities = (
|
||||
await this.catalogClient.getEntities(
|
||||
{
|
||||
filter: this.filter,
|
||||
limit: this.batchSize,
|
||||
offset: entitiesRetrieved,
|
||||
},
|
||||
{ token },
|
||||
)
|
||||
).items;
|
||||
|
||||
// Control looping through entity batches.
|
||||
moreEntitiesToGet = entities.length === this.batchSize;
|
||||
entitiesRetrieved += entities.length;
|
||||
|
||||
for (const entity of entities) {
|
||||
yield this.entityProcessor.process(entity, this.locationTemplate);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
/*
|
||||
* 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 { DefaultCatalogCollatorEntityProcessor } from './DefaultCatalogCollatorEntityProcessor';
|
||||
|
||||
const entity = {
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
kind: 'Component',
|
||||
metadata: {
|
||||
title: 'Test Entity',
|
||||
name: 'test-entity',
|
||||
description: 'The expected description',
|
||||
namespace: 'namespace',
|
||||
},
|
||||
spec: {
|
||||
type: 'some-type',
|
||||
lifecycle: 'experimental',
|
||||
owner: 'someone',
|
||||
},
|
||||
};
|
||||
|
||||
const userEntity = {
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
kind: 'User',
|
||||
metadata: {
|
||||
name: 'test-user-entity',
|
||||
description: 'The expected user description',
|
||||
},
|
||||
spec: {
|
||||
profile: {
|
||||
displayName: 'User 1',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const locationTemplate = '/catalog/:namespace/:kind/:name';
|
||||
|
||||
describe('DefaultCatalogCollatorEntityProcessor', () => {
|
||||
const entityProcessor = new DefaultCatalogCollatorEntityProcessor();
|
||||
|
||||
describe('process', () => {
|
||||
it('maps a returned entity', async () => {
|
||||
const document = entityProcessor.process(entity, locationTemplate);
|
||||
|
||||
expect(document).toMatchObject({
|
||||
title: entity.metadata.title,
|
||||
location: '/catalog/namespace/component/test-entity',
|
||||
text: entity.metadata.description,
|
||||
namespace: entity.metadata.namespace,
|
||||
componentType: entity.spec.type,
|
||||
lifecycle: entity.spec.lifecycle,
|
||||
owner: entity.spec.owner,
|
||||
authorization: {
|
||||
resourceRef: 'component:namespace/test-entity',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('maps a returned entity with default fallback', async () => {
|
||||
const entityWithoutTitle = {
|
||||
...entity,
|
||||
metadata: {
|
||||
...entity.metadata,
|
||||
title: undefined,
|
||||
namespace: undefined,
|
||||
},
|
||||
spec: {
|
||||
type: undefined,
|
||||
lifecycle: undefined,
|
||||
owner: undefined,
|
||||
},
|
||||
};
|
||||
|
||||
const document = entityProcessor.process(
|
||||
entityWithoutTitle,
|
||||
locationTemplate,
|
||||
);
|
||||
|
||||
expect(document).toMatchObject({
|
||||
title: entity.metadata.name,
|
||||
location: '/catalog/default/component/test-entity',
|
||||
text: entity.metadata.description,
|
||||
namespace: 'default',
|
||||
componentType: 'other',
|
||||
lifecycle: '',
|
||||
owner: '',
|
||||
authorization: {
|
||||
resourceRef: 'component:default/test-entity',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('maps a returned entity with custom locationTemplate', async () => {
|
||||
const document = entityProcessor.process(entity, '/catalog/:name');
|
||||
|
||||
expect(document).toMatchObject({
|
||||
title: entity.metadata.title,
|
||||
location: '/catalog/test-entity',
|
||||
text: entity.metadata.description,
|
||||
namespace: entity.metadata.namespace,
|
||||
componentType: entity.spec.type,
|
||||
lifecycle: entity.spec.lifecycle,
|
||||
owner: entity.spec.owner,
|
||||
authorization: {
|
||||
resourceRef: 'component:namespace/test-entity',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('maps a returned user entity', async () => {
|
||||
const document = entityProcessor.process(userEntity, locationTemplate);
|
||||
|
||||
expect(document).toMatchObject({
|
||||
title: userEntity.metadata.name,
|
||||
location: '/catalog/default/user/test-user-entity',
|
||||
text: `${userEntity.metadata.description} : ${userEntity.spec.profile.displayName}`,
|
||||
namespace: 'default',
|
||||
componentType: 'other',
|
||||
lifecycle: '',
|
||||
owner: '',
|
||||
authorization: {
|
||||
resourceRef: 'user:default/test-user-entity',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('maps a returned user entity without display name', async () => {
|
||||
const testEntity = {
|
||||
...userEntity,
|
||||
spec: undefined,
|
||||
};
|
||||
|
||||
const document = entityProcessor.process(testEntity, locationTemplate);
|
||||
|
||||
expect(document).toMatchObject({
|
||||
title: userEntity.metadata.name,
|
||||
location: '/catalog/default/user/test-user-entity',
|
||||
text: userEntity.metadata.description,
|
||||
namespace: 'default',
|
||||
componentType: 'other',
|
||||
lifecycle: '',
|
||||
owner: '',
|
||||
authorization: {
|
||||
resourceRef: 'user:default/test-user-entity',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('maps a returned group entity', async () => {
|
||||
const groupEntity = {
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
kind: 'Group',
|
||||
metadata: {
|
||||
name: 'test-group-entity',
|
||||
description: 'The expected group description',
|
||||
},
|
||||
spec: {
|
||||
profile: {
|
||||
displayName: 'Group 1',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const document = entityProcessor.process(groupEntity, locationTemplate);
|
||||
|
||||
expect(document).toMatchObject({
|
||||
title: groupEntity.metadata.name,
|
||||
location: '/catalog/default/group/test-group-entity',
|
||||
text: `${groupEntity.metadata.description} : ${groupEntity.spec.profile.displayName}`,
|
||||
namespace: 'default',
|
||||
componentType: 'other',
|
||||
lifecycle: '',
|
||||
owner: '',
|
||||
authorization: {
|
||||
resourceRef: 'group:default/test-group-entity',
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* 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,
|
||||
isGroupEntity,
|
||||
isUserEntity,
|
||||
stringifyEntityRef,
|
||||
} from '@backstage/catalog-model';
|
||||
import { CatalogEntityDocument } from '@backstage/plugin-catalog-common';
|
||||
import { CatalogCollatorEntityProcessor } from './CatalogCollatorEntityProcessor';
|
||||
|
||||
export class DefaultCatalogCollatorEntityProcessor
|
||||
implements CatalogCollatorEntityProcessor
|
||||
{
|
||||
public process(
|
||||
entity: Entity,
|
||||
locationTemplate: string,
|
||||
): CatalogEntityDocument {
|
||||
return {
|
||||
title: entity.metadata.title ?? entity.metadata.name,
|
||||
location: this.applyArgsToFormat(locationTemplate, {
|
||||
namespace: entity.metadata.namespace || 'default',
|
||||
kind: entity.kind,
|
||||
name: entity.metadata.name,
|
||||
}),
|
||||
text: this.getDocumentText(entity),
|
||||
componentType: entity.spec?.type?.toString() || 'other',
|
||||
type: entity.spec?.type?.toString() || 'other',
|
||||
namespace: entity.metadata.namespace || 'default',
|
||||
kind: entity.kind,
|
||||
lifecycle: (entity.spec?.lifecycle as string) || '',
|
||||
owner: (entity.spec?.owner as string) || '',
|
||||
authorization: {
|
||||
resourceRef: stringifyEntityRef(entity),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
private applyArgsToFormat(
|
||||
format: string,
|
||||
args: Record<string, string>,
|
||||
): string {
|
||||
let formatted = format;
|
||||
|
||||
for (const [key, value] of Object.entries(args)) {
|
||||
formatted = formatted.replace(`:${key}`, value);
|
||||
}
|
||||
|
||||
return formatted.toLowerCase();
|
||||
}
|
||||
|
||||
private getDocumentText(entity: Entity): string {
|
||||
const documentTexts: string[] = [];
|
||||
documentTexts.push(entity.metadata.description || '');
|
||||
|
||||
if (isUserEntity(entity) || isGroupEntity(entity)) {
|
||||
if (entity.spec?.profile?.displayName) {
|
||||
documentTexts.push(entity.spec.profile.displayName);
|
||||
}
|
||||
}
|
||||
|
||||
return documentTexts.join(' : ');
|
||||
}
|
||||
}
|
||||
@@ -14,133 +14,27 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
PluginEndpointDiscovery,
|
||||
TokenManager,
|
||||
} from '@backstage/backend-common';
|
||||
import {
|
||||
CatalogApi,
|
||||
CatalogClient,
|
||||
GetEntitiesRequest,
|
||||
} from '@backstage/catalog-client';
|
||||
import { stringifyEntityRef } from '@backstage/catalog-model';
|
||||
import { Config } from '@backstage/config';
|
||||
import { DocumentCollatorFactory } from '@backstage/plugin-search-common';
|
||||
import {
|
||||
catalogEntityReadPermission,
|
||||
CatalogEntityDocument,
|
||||
} from '@backstage/plugin-catalog-common';
|
||||
import { Permission } from '@backstage/plugin-permission-common';
|
||||
import { Readable } from 'stream';
|
||||
import { getDocumentText } from './util';
|
||||
CatalogCollatorFactory,
|
||||
CatalogCollatorFactoryCreateOptions,
|
||||
} from './CatalogCollatorFactory';
|
||||
import { DefaultCatalogCollatorEntityProcessor } from './DefaultCatalogCollatorEntityProcessor';
|
||||
|
||||
/** @public */
|
||||
export type DefaultCatalogCollatorFactoryOptions = {
|
||||
discovery: PluginEndpointDiscovery;
|
||||
tokenManager: TokenManager;
|
||||
locationTemplate?: string;
|
||||
filter?: GetEntitiesRequest['filter'];
|
||||
batchSize?: number;
|
||||
catalogClient?: CatalogApi;
|
||||
};
|
||||
export type DefaultCatalogCollatorFactoryOptions =
|
||||
CatalogCollatorFactoryCreateOptions;
|
||||
|
||||
/** @public */
|
||||
export class DefaultCatalogCollatorFactory implements DocumentCollatorFactory {
|
||||
public readonly type: string = 'software-catalog';
|
||||
public readonly visibilityPermission: Permission =
|
||||
catalogEntityReadPermission;
|
||||
|
||||
private locationTemplate: string;
|
||||
private filter?: GetEntitiesRequest['filter'];
|
||||
private batchSize: number;
|
||||
private readonly catalogClient: CatalogApi;
|
||||
private tokenManager: TokenManager;
|
||||
|
||||
export class DefaultCatalogCollatorFactory extends CatalogCollatorFactory {
|
||||
static fromConfig(
|
||||
_config: Config,
|
||||
options: DefaultCatalogCollatorFactoryOptions,
|
||||
) {
|
||||
return new DefaultCatalogCollatorFactory(options);
|
||||
}
|
||||
|
||||
private constructor(options: DefaultCatalogCollatorFactoryOptions) {
|
||||
const {
|
||||
batchSize,
|
||||
discovery,
|
||||
locationTemplate,
|
||||
filter,
|
||||
catalogClient,
|
||||
tokenManager,
|
||||
} = options;
|
||||
|
||||
this.locationTemplate =
|
||||
locationTemplate || '/catalog/:namespace/:kind/:name';
|
||||
this.filter = filter;
|
||||
this.batchSize = batchSize || 500;
|
||||
this.catalogClient =
|
||||
catalogClient || new CatalogClient({ discoveryApi: discovery });
|
||||
this.tokenManager = tokenManager;
|
||||
}
|
||||
|
||||
async getCollator(): Promise<Readable> {
|
||||
return Readable.from(this.execute());
|
||||
}
|
||||
|
||||
private applyArgsToFormat(
|
||||
format: string,
|
||||
args: Record<string, string>,
|
||||
): string {
|
||||
let formatted = format;
|
||||
for (const [key, value] of Object.entries(args)) {
|
||||
formatted = formatted.replace(`:${key}`, value);
|
||||
}
|
||||
return formatted.toLowerCase();
|
||||
}
|
||||
|
||||
private async *execute(): AsyncGenerator<CatalogEntityDocument> {
|
||||
const { token } = await this.tokenManager.getToken();
|
||||
let entitiesRetrieved = 0;
|
||||
let moreEntitiesToGet = true;
|
||||
|
||||
// Offset/limit pagination is used on the Catalog Client in order to
|
||||
// limit (and allow some control over) memory used by the search backend
|
||||
// at index-time.
|
||||
while (moreEntitiesToGet) {
|
||||
const entities = (
|
||||
await this.catalogClient.getEntities(
|
||||
{
|
||||
filter: this.filter,
|
||||
limit: this.batchSize,
|
||||
offset: entitiesRetrieved,
|
||||
},
|
||||
{ token },
|
||||
)
|
||||
).items;
|
||||
|
||||
// Control looping through entity batches.
|
||||
moreEntitiesToGet = entities.length === this.batchSize;
|
||||
entitiesRetrieved += entities.length;
|
||||
|
||||
for (const entity of entities) {
|
||||
yield {
|
||||
title: entity.metadata.title ?? entity.metadata.name,
|
||||
location: this.applyArgsToFormat(this.locationTemplate, {
|
||||
namespace: entity.metadata.namespace || 'default',
|
||||
kind: entity.kind,
|
||||
name: entity.metadata.name,
|
||||
}),
|
||||
text: getDocumentText(entity),
|
||||
componentType: entity.spec?.type?.toString() || 'other',
|
||||
type: entity.spec?.type?.toString() || 'other',
|
||||
namespace: entity.metadata.namespace || 'default',
|
||||
kind: entity.kind,
|
||||
lifecycle: (entity.spec?.lifecycle as string) || '',
|
||||
owner: (entity.spec?.owner as string) || '',
|
||||
authorization: {
|
||||
resourceRef: stringifyEntityRef(entity),
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
return new DefaultCatalogCollatorFactory({
|
||||
...options,
|
||||
type: 'software-catalog',
|
||||
entityProcessor: new DefaultCatalogCollatorEntityProcessor(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,11 @@
|
||||
|
||||
export { DefaultCatalogCollatorFactory } from './DefaultCatalogCollatorFactory';
|
||||
export type { DefaultCatalogCollatorFactoryOptions } from './DefaultCatalogCollatorFactory';
|
||||
export { CatalogCollatorFactory } from './CatalogCollatorFactory';
|
||||
export type {
|
||||
CatalogCollatorFactoryOptions,
|
||||
CatalogCollatorFactoryCreateOptions,
|
||||
} from './CatalogCollatorFactory';
|
||||
|
||||
/**
|
||||
* todo(backstage/techdocs-core): stop exporting this in a future release.
|
||||
|
||||
@@ -1,146 +0,0 @@
|
||||
/*
|
||||
* 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,
|
||||
GroupEntity,
|
||||
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('');
|
||||
});
|
||||
});
|
||||
|
||||
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',
|
||||
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',
|
||||
},
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user