Merge pull request #25497 from alexef/techdocs-tags

Techdocs: allow custom collation of mkdocs search document
This commit is contained in:
Alex Lorenzi
2024-10-09 13:32:56 -04:00
committed by GitHub
12 changed files with 217 additions and 97 deletions
@@ -5,6 +5,7 @@
```ts
import { BackendFeature } from '@backstage/backend-plugin-api';
import { ExtensionPoint } from '@backstage/backend-plugin-api';
import { TechDocsCollatorDocumentTransformer } from '@backstage/plugin-search-backend-module-techdocs';
import { TechDocsCollatorEntityTransformer } from '@backstage/plugin-search-backend-module-techdocs';
// @alpha
@@ -13,6 +14,10 @@ export default _default;
// @alpha (undocumented)
export interface TechDocsCollatorEntityTransformerExtensionPoint {
// (undocumented)
setDocumentTransformer(
transformer: TechDocsCollatorDocumentTransformer,
): void;
// (undocumented)
setTransformer(transformer: TechDocsCollatorEntityTransformer): void;
}
@@ -24,6 +29,7 @@ export const techdocsCollatorEntityTransformerExtensionPoint: ExtensionPoint<Tec
//
// src/alpha.d.ts:3:1 - (ae-undocumented) Missing documentation for "TechDocsCollatorEntityTransformerExtensionPoint".
// src/alpha.d.ts:4:5 - (ae-undocumented) Missing documentation for "setTransformer".
// src/alpha.d.ts:5:5 - (ae-undocumented) Missing documentation for "setDocumentTransformer".
// (No @packageDocumentation comment for this package)
```
@@ -36,10 +36,38 @@ export class DefaultTechDocsCollatorFactory implements DocumentCollatorFactory {
readonly visibilityPermission: Permission;
}
// @public (undocumented)
export interface MkSearchIndexDoc {
// (undocumented)
location: string;
// (undocumented)
tags?: string[];
// (undocumented)
text: string;
// (undocumented)
title: string;
}
// @public (undocumented)
export type TechDocsCollatorDocumentTransformer = (
doc: MkSearchIndexDoc,
) => Partial<
Omit<
TechDocsDocument,
| 'location'
| 'authorization'
| 'kind'
| 'namespace'
| 'name'
| 'lifecycle'
| 'owner'
>
>;
// @public (undocumented)
export type TechDocsCollatorEntityTransformer = (
entity: Entity,
) => Omit<TechDocsDocument, 'location' | 'authorization'>;
) => Partial<Omit<TechDocsDocument, 'location' | 'authorization'>>;
// @public @deprecated
export type TechDocsCollatorFactoryOptions = {
@@ -53,14 +81,21 @@ export type TechDocsCollatorFactoryOptions = {
parallelismLimit?: number;
legacyPathCasing?: boolean;
entityTransformer?: TechDocsCollatorEntityTransformer;
documentTransformer?: TechDocsCollatorDocumentTransformer;
};
// Warnings were encountered during analysis:
//
// src/collators/DefaultTechDocsCollatorFactory.d.ts:36:5 - (ae-undocumented) Missing documentation for "type".
// src/collators/DefaultTechDocsCollatorFactory.d.ts:37:5 - (ae-undocumented) Missing documentation for "visibilityPermission".
// src/collators/DefaultTechDocsCollatorFactory.d.ts:47:5 - (ae-undocumented) Missing documentation for "fromConfig".
// src/collators/DefaultTechDocsCollatorFactory.d.ts:48:5 - (ae-undocumented) Missing documentation for "getCollator".
// src/collators/DefaultTechDocsCollatorFactory.d.ts:38:5 - (ae-undocumented) Missing documentation for "type".
// src/collators/DefaultTechDocsCollatorFactory.d.ts:39:5 - (ae-undocumented) Missing documentation for "visibilityPermission".
// src/collators/DefaultTechDocsCollatorFactory.d.ts:50:5 - (ae-undocumented) Missing documentation for "fromConfig".
// src/collators/DefaultTechDocsCollatorFactory.d.ts:51:5 - (ae-undocumented) Missing documentation for "getCollator".
// src/collators/TechDocsCollatorDocumentTransformer.d.ts:3:1 - (ae-undocumented) Missing documentation for "MkSearchIndexDoc".
// src/collators/TechDocsCollatorDocumentTransformer.d.ts:4:5 - (ae-undocumented) Missing documentation for "title".
// src/collators/TechDocsCollatorDocumentTransformer.d.ts:5:5 - (ae-undocumented) Missing documentation for "text".
// src/collators/TechDocsCollatorDocumentTransformer.d.ts:6:5 - (ae-undocumented) Missing documentation for "location".
// src/collators/TechDocsCollatorDocumentTransformer.d.ts:7:5 - (ae-undocumented) Missing documentation for "tags".
// src/collators/TechDocsCollatorDocumentTransformer.d.ts:10:1 - (ae-undocumented) Missing documentation for "TechDocsCollatorDocumentTransformer".
// src/collators/TechDocsCollatorEntityTransformer.d.ts:4:1 - (ae-undocumented) Missing documentation for "TechDocsCollatorEntityTransformer".
// src/collators/defaultTechDocsCollatorEntityTransformer.d.ts:3:22 - (ae-undocumented) Missing documentation for "defaultTechDocsCollatorEntityTransformer".
```
@@ -28,6 +28,7 @@ import {
import { catalogServiceRef } from '@backstage/plugin-catalog-node/alpha';
import {
DefaultTechDocsCollatorFactory,
TechDocsCollatorDocumentTransformer,
TechDocsCollatorEntityTransformer,
} from '@backstage/plugin-search-backend-module-techdocs';
import { searchIndexRegistryExtensionPoint } from '@backstage/plugin-search-backend-node/alpha';
@@ -35,6 +36,9 @@ import { searchIndexRegistryExtensionPoint } from '@backstage/plugin-search-back
/** @alpha */
export interface TechDocsCollatorEntityTransformerExtensionPoint {
setTransformer(transformer: TechDocsCollatorEntityTransformer): void;
setDocumentTransformer(
transformer: TechDocsCollatorDocumentTransformer,
): void;
}
/**
@@ -55,18 +59,27 @@ export default createBackendModule({
pluginId: 'search',
moduleId: 'techdocs-collator',
register(env) {
let transformer: TechDocsCollatorEntityTransformer | undefined;
let entityTransformer: TechDocsCollatorEntityTransformer | undefined;
let documentTransformer: TechDocsCollatorDocumentTransformer | undefined;
env.registerExtensionPoint(
techdocsCollatorEntityTransformerExtensionPoint,
{
setTransformer(newTransformer) {
if (transformer) {
if (entityTransformer) {
throw new Error(
'TechDocs collator entity transformer may only be set once',
);
}
transformer = newTransformer;
entityTransformer = newTransformer;
},
setDocumentTransformer(newTransformer) {
if (documentTransformer) {
throw new Error(
'TechDocs collator document transformer may only be set once',
);
}
documentTransformer = newTransformer;
},
},
);
@@ -112,7 +125,8 @@ export default createBackendModule({
httpAuth,
logger,
catalogClient: catalog,
entityTransformer: transformer,
entityTransformer,
documentTransformer,
}),
});
},
@@ -24,9 +24,12 @@ import { rest } from 'msw';
import { setupServer } from 'msw/node';
import { Readable } from 'stream';
import { DefaultTechDocsCollatorFactory } from './DefaultTechDocsCollatorFactory';
import { defaultTechDocsCollatorEntityTransformer } from './defaultTechDocsCollatorEntityTransformer';
import { TechDocsCollatorEntityTransformer } from './TechDocsCollatorEntityTransformer';
import { DiscoveryService } from '@backstage/backend-plugin-api';
import {
MkSearchIndexDoc,
TechDocsCollatorDocumentTransformer,
} from './TechDocsCollatorDocumentTransformer';
const logger = mockServices.logger.mock();
@@ -254,11 +257,11 @@ describe('DefaultTechDocsCollatorFactory', () => {
});
it('should transform the entity using the entityTransformer function', async () => {
// @ts-ignore
const entityTransformer: TechDocsCollatorEntityTransformer = (
entity: Entity,
) => {
return {
...defaultTechDocsCollatorEntityTransformer(entity),
tags: entity.metadata.tags,
};
};
@@ -289,5 +292,42 @@ describe('DefaultTechDocsCollatorFactory', () => {
});
});
});
it('should transform the doc using the documentTransformer function', async () => {
// @ts-ignore
const documentTransformer: TechDocsCollatorDocumentTransformer = (
_: MkSearchIndexDoc,
) => {
return {
tags: ['static-tag'],
};
};
factory = DefaultTechDocsCollatorFactory.fromConfig(config, {
...options,
documentTransformer,
});
collator = await factory.getCollator();
const pipeline = TestPipeline.fromCollator(collator);
const { documents } = await pipeline.execute();
const entity = expectedEntities[0];
documents.forEach((document, idx) => {
expect(document).toMatchObject({
title: mockSearchDocIndex.docs[idx].title,
location: `/docs/default/component/${entity.metadata.name}/${mockSearchDocIndex.docs[idx].location}`,
text: mockSearchDocIndex.docs[idx].text,
namespace: 'default',
entityTitle: entity!.metadata.title,
componentType: entity!.spec!.type,
lifecycle: entity!.spec!.lifecycle,
owner: '',
kind: entity.kind.toLocaleLowerCase('en-US'),
name: entity.metadata.name,
tags: ['static-tag'],
});
});
});
});
});
@@ -34,12 +34,16 @@ import { catalogEntityReadPermission } from '@backstage/plugin-catalog-common/al
import { Permission } from '@backstage/plugin-permission-common';
import { DocumentCollatorFactory } from '@backstage/plugin-search-common';
import { TechDocsDocument } from '@backstage/plugin-techdocs-node';
import unescape from 'lodash/unescape';
import fetch from 'node-fetch';
import pLimit from 'p-limit';
import { Readable } from 'stream';
import { TechDocsCollatorEntityTransformer } from './TechDocsCollatorEntityTransformer';
import {
MkSearchIndexDoc,
TechDocsCollatorDocumentTransformer,
} from './TechDocsCollatorDocumentTransformer';
import { defaultTechDocsCollatorEntityTransformer } from './defaultTechDocsCollatorEntityTransformer';
import { defaultTechDocsCollatorDocumentTransformer } from './defaultTechDocsCollatorDocumentTransformer';
import {
AuthService,
DiscoveryService,
@@ -47,12 +51,6 @@ import {
LoggerService,
} from '@backstage/backend-plugin-api';
interface MkSearchIndexDoc {
title: string;
text: string;
location: string;
}
/**
* Options to configure the TechDocs collator factory
*
@@ -70,6 +68,7 @@ export type TechDocsCollatorFactoryOptions = {
parallelismLimit?: number;
legacyPathCasing?: boolean;
entityTransformer?: TechDocsCollatorEntityTransformer;
documentTransformer?: TechDocsCollatorDocumentTransformer;
};
type EntityInfo = {
@@ -98,6 +97,7 @@ export class DefaultTechDocsCollatorFactory implements DocumentCollatorFactory {
private readonly parallelismLimit: number;
private readonly legacyPathCasing: boolean;
private entityTransformer: TechDocsCollatorEntityTransformer;
private documentTransformer: TechDocsCollatorDocumentTransformer;
private constructor(options: TechDocsCollatorFactoryOptions) {
this.discovery = options.discovery;
@@ -109,8 +109,8 @@ export class DefaultTechDocsCollatorFactory implements DocumentCollatorFactory {
new CatalogClient({ discoveryApi: options.discovery });
this.parallelismLimit = options.parallelismLimit ?? 10;
this.legacyPathCasing = options.legacyPathCasing ?? false;
this.entityTransformer =
options.entityTransformer ?? defaultTechDocsCollatorEntityTransformer;
this.entityTransformer = options.entityTransformer ?? (() => ({}));
this.documentTransformer = options.documentTransformer ?? (() => ({}));
this.auth = createLegacyAuthAdapters({
auth: options.auth,
@@ -224,9 +224,10 @@ export class DefaultTechDocsCollatorFactory implements DocumentCollatorFactory {
]);
return searchIndex.docs.map((doc: MkSearchIndexDoc) => ({
...defaultTechDocsCollatorEntityTransformer(entity),
...defaultTechDocsCollatorDocumentTransformer(doc),
...this.entityTransformer(entity),
title: unescape(doc.title),
text: unescape(doc.text || ''),
...this.documentTransformer(doc),
location: this.applyArgsToFormat(
this.locationTemplate || '/docs/:namespace/:kind/:name/:path',
{
@@ -234,7 +235,6 @@ export class DefaultTechDocsCollatorFactory implements DocumentCollatorFactory {
path: doc.location,
},
),
path: doc.location,
...entityInfo,
entityTitle: entity.metadata.title,
componentType: entity.spec?.type?.toString() || 'other',
@@ -0,0 +1,40 @@
/*
* Copyright 2024 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 { TechDocsDocument } from '@backstage/plugin-techdocs-node';
/** @public */
export interface MkSearchIndexDoc {
title: string;
text: string;
location: string;
tags?: string[];
}
/** @public */
export type TechDocsCollatorDocumentTransformer = (
doc: MkSearchIndexDoc,
) => Partial<
Omit<
TechDocsDocument,
| 'location'
| 'authorization'
| 'kind'
| 'namespace'
| 'name'
| 'lifecycle'
| 'owner'
>
>;
@@ -20,4 +20,4 @@ import { TechDocsDocument } from '@backstage/plugin-techdocs-node';
/** @public */
export type TechDocsCollatorEntityTransformer = (
entity: Entity,
) => Omit<TechDocsDocument, 'location' | 'authorization'>;
) => Partial<Omit<TechDocsDocument, 'location' | 'authorization'>>;
@@ -0,0 +1,30 @@
/*
* Copyright 2024 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 unescape from 'lodash/unescape';
import {
TechDocsCollatorDocumentTransformer,
MkSearchIndexDoc,
} from './TechDocsCollatorDocumentTransformer';
/** @public */
export const defaultTechDocsCollatorDocumentTransformer: TechDocsCollatorDocumentTransformer =
(doc: MkSearchIndexDoc) => {
return {
title: unescape(doc.title),
text: unescape(doc.text || ''),
path: doc.location,
};
};
@@ -21,3 +21,8 @@ export type { TechDocsCollatorFactoryOptions } from './DefaultTechDocsCollatorFa
export { defaultTechDocsCollatorEntityTransformer } from './defaultTechDocsCollatorEntityTransformer';
export type { TechDocsCollatorEntityTransformer } from './TechDocsCollatorEntityTransformer';
export type {
TechDocsCollatorDocumentTransformer,
MkSearchIndexDoc,
} from './TechDocsCollatorDocumentTransformer';