diff --git a/.changeset/techdocs-nervous-tables-invent.md b/.changeset/techdocs-nervous-tables-invent.md new file mode 100644 index 0000000000..577de4994f --- /dev/null +++ b/.changeset/techdocs-nervous-tables-invent.md @@ -0,0 +1,19 @@ +--- +'@backstage/plugin-techdocs-backend': minor +--- + +BREAKING: constructor based initialization of DefaultTechDocsCollator now deprecated. Use static fromConfig method instead. + +```diff +indexBuilder.addCollator({ + defaultRefreshIntervalSeconds: 600, +- collator: new DefaultTechDocsCollator({ ++ collator: DefaultTechDocsCollator.fromConfig(config, { + discovery, + logger, + tokenManager, + }), +}); +``` + +Note: in an upcoming release, TechDocs backend's /sync/:namespace/:kind/:name endpoint will only respond to text/event-stream-based requests. Update any custom code at your organization accordingly. diff --git a/plugins/techdocs-backend/api-report.md b/plugins/techdocs-backend/api-report.md index cefb7d937b..5d106cfafb 100644 --- a/plugins/techdocs-backend/api-report.md +++ b/plugins/techdocs-backend/api-report.md @@ -18,25 +18,17 @@ import { PublisherBase } from '@backstage/techdocs-common'; import { TechDocsDocument } from '@backstage/techdocs-common'; import { TokenManager } from '@backstage/backend-common'; -// Warning: (ae-missing-release-tag) "createRouter" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public export function createRouter(options: RouterOptions): Promise; -// Warning: (ae-missing-release-tag) "DefaultTechDocsCollator" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public export class DefaultTechDocsCollator implements DocumentCollator { - // @deprecated - constructor(options: TechDocsCollatorOptions); // (undocumented) protected applyArgsToFormat( format: string, args: Record, ): string; // (undocumented) - protected discovery: PluginEndpointDiscovery; - // (undocumented) execute(): Promise; // (undocumented) static fromConfig( @@ -44,15 +36,11 @@ export class DefaultTechDocsCollator implements DocumentCollator { options: TechDocsCollatorOptions, ): DefaultTechDocsCollator; // (undocumented) - protected locationTemplate: string; - // (undocumented) readonly type: string; // (undocumented) readonly visibilityPermission: Permission; } -// Warning: (ae-missing-release-tag) "OutOfTheBoxDeploymentOptions" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public export type OutOfTheBoxDeploymentOptions = { preparers: PreparerBuilder; @@ -65,8 +53,6 @@ export type OutOfTheBoxDeploymentOptions = { cache: PluginCacheManager; }; -// Warning: (ae-missing-release-tag) "RecommendedDeploymentOptions" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public export type RecommendedDeploymentOptions = { publisher: PublisherBase; @@ -76,16 +62,12 @@ export type RecommendedDeploymentOptions = { cache: PluginCacheManager; }; -// Warning: (ae-missing-release-tag) "RouterOptions" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public export type RouterOptions = | RecommendedDeploymentOptions | OutOfTheBoxDeploymentOptions; -// Warning: (ae-missing-release-tag) "TechDocsCollatorOptions" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public export type TechDocsCollatorOptions = { discovery: PluginEndpointDiscovery; logger: Logger_2; diff --git a/plugins/techdocs-backend/src/search/DefaultTechDocsCollator.test.ts b/plugins/techdocs-backend/src/search/DefaultTechDocsCollator.test.ts index e81fc1e7a9..d9f75767a7 100644 --- a/plugins/techdocs-backend/src/search/DefaultTechDocsCollator.test.ts +++ b/plugins/techdocs-backend/src/search/DefaultTechDocsCollator.test.ts @@ -208,8 +208,13 @@ describe('DefaultTechDocsCollator', () => { }); it('maps a returned entity with a custom locationTemplate', async () => { + const mockConfig = new ConfigReader({ + techdocs: { + legacyUseCaseSensitiveTripletPaths: true, + }, + }); // Provide an alternate location template. - collator = new DefaultTechDocsCollator({ + collator = DefaultTechDocsCollator.fromConfig(mockConfig, { discovery: mockDiscoveryApi, tokenManager: mockTokenManager, locationTemplate: '/software/:name', diff --git a/plugins/techdocs-backend/src/search/DefaultTechDocsCollator.ts b/plugins/techdocs-backend/src/search/DefaultTechDocsCollator.ts index 987e8c6b15..86b70379ad 100644 --- a/plugins/techdocs-backend/src/search/DefaultTechDocsCollator.ts +++ b/plugins/techdocs-backend/src/search/DefaultTechDocsCollator.ts @@ -39,6 +39,11 @@ interface MkSearchIndexDoc { location: string; } +/** + * Options to configure the TechDocs collator + * + * @public + */ export type TechDocsCollatorOptions = { discovery: PluginEndpointDiscovery; logger: Logger; @@ -55,46 +60,43 @@ type EntityInfo = { kind: string; }; +/** + * A search collator responsible for gathering and transforming TechDocs documents. + * + * @public + */ export class DefaultTechDocsCollator implements DocumentCollator { - protected discovery: PluginEndpointDiscovery; - protected locationTemplate: string; - private readonly logger: Logger; - private readonly catalogClient: CatalogApi; - private readonly tokenManager: TokenManager; - private readonly parallelismLimit: number; - private readonly legacyPathCasing: boolean; public readonly type: string = 'techdocs'; public readonly visibilityPermission = catalogEntityReadPermission; - /** - * @deprecated use static fromConfig method instead. - */ - constructor(options: TechDocsCollatorOptions) { - this.discovery = options.discovery; - this.locationTemplate = - options.locationTemplate || '/docs/:namespace/:kind/:name/:path'; - this.logger = options.logger; - this.catalogClient = - options.catalogClient || - new CatalogClient({ discoveryApi: options.discovery }); - this.parallelismLimit = options.parallelismLimit ?? 10; - this.legacyPathCasing = options.legacyPathCasing ?? false; - this.tokenManager = options.tokenManager; - } + private constructor( + private readonly legacyPathCasing: boolean, + private readonly options: TechDocsCollatorOptions, + ) {} static fromConfig(config: Config, options: TechDocsCollatorOptions) { const legacyPathCasing = config.getOptionalBoolean( 'techdocs.legacyUseCaseSensitiveTripletPaths', ) || false; - return new DefaultTechDocsCollator({ ...options, legacyPathCasing }); + return new DefaultTechDocsCollator(legacyPathCasing, options); } async execute() { - const limit = pLimit(this.parallelismLimit); - const techDocsBaseUrl = await this.discovery.getBaseUrl('techdocs'); - const { token } = await this.tokenManager.getToken(); - const entities = await this.catalogClient.getEntities( + const { + parallelismLimit, + discovery, + tokenManager, + catalogClient, + locationTemplate, + logger, + } = this.options; + const limit = pLimit(parallelismLimit ?? 10); + const techDocsBaseUrl = await discovery.getBaseUrl('techdocs'); + const { token } = await tokenManager.getToken(); + const entities = await ( + catalogClient ?? new CatalogClient({ discoveryApi: discovery }) + ).getEntities( { fields: [ 'kind', @@ -115,7 +117,7 @@ export class DefaultTechDocsCollator implements DocumentCollator { .map((entity: Entity) => limit(async (): Promise => { const entityInfo = DefaultTechDocsCollator.handleEntityInfoCasing( - this.legacyPathCasing, + this.legacyPathCasing ?? false, { kind: entity.kind, namespace: entity.metadata.namespace || 'default', @@ -140,10 +142,13 @@ export class DefaultTechDocsCollator implements DocumentCollator { return searchIndex.docs.map((doc: MkSearchIndexDoc) => ({ title: unescape(doc.title), text: unescape(doc.text || ''), - location: this.applyArgsToFormat(this.locationTemplate, { - ...entityInfo, - path: doc.location, - }), + location: this.applyArgsToFormat( + locationTemplate || '/docs/:namespace/:kind/:name/:path', + { + ...entityInfo, + path: doc.location, + }, + ), path: doc.location, ...entityInfo, entityTitle: entity.metadata.title, @@ -157,7 +162,7 @@ export class DefaultTechDocsCollator implements DocumentCollator { }, })); } catch (e) { - this.logger.debug( + logger.debug( `Failed to retrieve tech docs search index for entity ${entityInfo.namespace}/${entityInfo.kind}/${entityInfo.name}`, e, ); diff --git a/plugins/techdocs-backend/src/service/router.ts b/plugins/techdocs-backend/src/service/router.ts index 141ce93f79..5877dda099 100644 --- a/plugins/techdocs-backend/src/service/router.ts +++ b/plugins/techdocs-backend/src/service/router.ts @@ -37,8 +37,10 @@ import { createCacheMiddleware, TechDocsCache } from '../cache'; import { CachedEntityLoader } from './CachedEntityLoader'; /** - * All of the required dependencies for running TechDocs in the "out-of-the-box" + * Required dependencies for running TechDocs in the "out-of-the-box" * deployment configuration (prepare/generate/publish all in the Backend). + * + * @public */ export type OutOfTheBoxDeploymentOptions = { preparers: PreparerBuilder; @@ -54,6 +56,8 @@ export type OutOfTheBoxDeploymentOptions = { /** * Required dependencies for running TechDocs in the "recommended" deployment * configuration (prepare/generate handled externally in CI/CD). + * + * @public */ export type RecommendedDeploymentOptions = { publisher: PublisherBase; @@ -65,6 +69,8 @@ export type RecommendedDeploymentOptions = { /** * One of the two deployment configurations must be provided. + * + * @public */ export type RouterOptions = | RecommendedDeploymentOptions @@ -73,6 +79,8 @@ export type RouterOptions = /** * Typeguard to help createRouter() understand when we are in a "recommended" * deployment vs. when we are in an out-of-the-box deployment configuration. + * + * * @public */ function isOutOfTheBoxOption( opt: RouterOptions, @@ -80,6 +88,11 @@ function isOutOfTheBoxOption( return (opt as OutOfTheBoxDeploymentOptions).preparers !== undefined; } +/** + * Creates a techdocs router. + * + * @public + */ export async function createRouter( options: RouterOptions, ): Promise { @@ -323,12 +336,8 @@ export function createEventStream( } /** - * Create a HTTP response. This is used for the legacy non-event-stream implementation of the sync endpoint. - * - * @param res - the response to write the event-stream to - * @returns A tuple of callbacks to emit messages. A call to 'error' or 'finish' - * will close the event-stream. - */ + * @deprecated use event-stream implementation of the sync endpoint + * */ export function createHttpResponse( res: Response, ): DocsSynchronizerSyncOpts {