From 5e32f77884825ba6cb166fc8ea5b7d58861b38d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Fri, 3 Apr 2026 22:53:27 +0200 Subject: [PATCH 1/9] Migrate techdocs plugins from alpha catalogServiceRef to stable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Migrated `@backstage/plugin-techdocs-backend` and `@backstage/plugin-search-backend-module-techdocs` to use the stable `catalogServiceRef` from `@backstage/plugin-catalog-node` instead of the deprecated one from `@backstage/plugin-catalog-node/alpha`. This also updates `CachedEntityLoader`, `DefaultTechDocsCollatorFactory`, and the TechDocs router to use `CatalogService` (credentials-based) instead of `CatalogApi` (token-based). Signed-off-by: Fredrik Adelöw Made-with: Cursor --- .../migrate-techdocs-catalog-service-ref.md | 6 +++ .../DefaultTechDocsCollatorFactory.test.ts | 30 ++---------- .../DefaultTechDocsCollatorFactory.ts | 21 ++++----- .../src/module.ts | 2 +- plugins/techdocs-backend/src/plugin.ts | 2 +- .../src/service/CachedEntityLoader.test.ts | 20 ++++---- .../src/service/CachedEntityLoader.ts | 9 ++-- .../src/service/router.test.ts | 5 +- .../techdocs-backend/src/service/router.ts | 46 ++++++------------- 9 files changed, 52 insertions(+), 89 deletions(-) create mode 100644 .changeset/migrate-techdocs-catalog-service-ref.md diff --git a/.changeset/migrate-techdocs-catalog-service-ref.md b/.changeset/migrate-techdocs-catalog-service-ref.md new file mode 100644 index 0000000000..c8db66d42a --- /dev/null +++ b/.changeset/migrate-techdocs-catalog-service-ref.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-search-backend-module-techdocs': patch +'@backstage/plugin-techdocs-backend': patch +--- + +Migrated internal usage of the deprecated `catalogServiceRef` from `@backstage/plugin-catalog-node/alpha` to the stable `catalogServiceRef` from `@backstage/plugin-catalog-node`. diff --git a/plugins/search-backend-module-techdocs/src/collators/DefaultTechDocsCollatorFactory.test.ts b/plugins/search-backend-module-techdocs/src/collators/DefaultTechDocsCollatorFactory.test.ts index c3793917e5..d4f3e33d60 100644 --- a/plugins/search-backend-module-techdocs/src/collators/DefaultTechDocsCollatorFactory.test.ts +++ b/plugins/search-backend-module-techdocs/src/collators/DefaultTechDocsCollatorFactory.test.ts @@ -21,6 +21,7 @@ import { mockServices, registerMswTestHooks, } from '@backstage/backend-test-utils'; +import { catalogServiceMock } from '@backstage/plugin-catalog-node/testUtils'; import { rest } from 'msw'; import { setupServer } from 'msw/node'; import { Readable } from 'node:stream'; @@ -85,10 +86,12 @@ describe('DefaultTechDocsCollatorFactory', () => { const mockDiscoveryApi = mockServices.discovery.mock({ getBaseUrl: async () => 'http://test-backend', }); + const mockCatalog = catalogServiceMock({ entities: expectedEntities }); const options = { logger, discovery: mockDiscoveryApi, auth: mockServices.auth(), + catalogClient: mockCatalog, }; it('has expected type', () => { @@ -112,31 +115,6 @@ describe('DefaultTechDocsCollatorFactory', () => { 'http://test-backend/static/docs/default/Component/test-entity-with-docs/search/search_index.json', (_, res, ctx) => res(ctx.status(200), ctx.json(mockSearchDocIndex)), ), - rest.get('http://test-backend/entities', (req, res, ctx) => { - // Imitate offset/limit pagination. - const offset = parseInt( - req.url.searchParams.get('offset') || '0', - 10, - ); - const limit = parseInt( - req.url.searchParams.get('limit') || '500', - 10, - ); - - // Limit 50 corresponds to a case testing pagination. - if (limit === 50) { - // Return 50 copies of invalid entities on the first request. - if (offset === 0) { - return res(ctx.status(200), ctx.json(Array(50).fill({}))); - } - // Then just the regular 2 on the second. - return res(ctx.status(200), ctx.json(expectedEntities)); - } - return res( - ctx.status(200), - ctx.json(expectedEntities.slice(offset, limit + offset)), - ); - }), ); }); @@ -147,7 +125,6 @@ describe('DefaultTechDocsCollatorFactory', () => { it('fetches from the configured catalog and tech docs services', async () => { const pipeline = TestPipeline.fromCollator(collator); const { documents } = await pipeline.execute(); - expect(mockDiscoveryApi.getBaseUrl).toHaveBeenCalledWith('catalog'); expect(mockDiscoveryApi.getBaseUrl).toHaveBeenCalledWith('techdocs'); expect(documents).toHaveLength(mockSearchDocIndex.docs.length); }); @@ -184,6 +161,7 @@ describe('DefaultTechDocsCollatorFactory', () => { discovery: mockDiscoveryApi, logger, auth: mockServices.auth(), + catalogClient: mockCatalog, }); collator = await factory.getCollator(); diff --git a/plugins/search-backend-module-techdocs/src/collators/DefaultTechDocsCollatorFactory.ts b/plugins/search-backend-module-techdocs/src/collators/DefaultTechDocsCollatorFactory.ts index 53957b08c4..1a2e4af099 100644 --- a/plugins/search-backend-module-techdocs/src/collators/DefaultTechDocsCollatorFactory.ts +++ b/plugins/search-backend-module-techdocs/src/collators/DefaultTechDocsCollatorFactory.ts @@ -16,8 +16,6 @@ import { CATALOG_FILTER_EXISTS, - CatalogApi, - CatalogClient, EntityFilterQuery, } from '@backstage/catalog-client'; import { @@ -45,6 +43,7 @@ import { DiscoveryService, LoggerService, } from '@backstage/backend-plugin-api'; +import { CatalogService } from '@backstage/plugin-catalog-node'; /** * Options to configure the TechDocs collator factory @@ -56,7 +55,7 @@ export type TechDocsCollatorFactoryOptions = { logger: LoggerService; auth: AuthService; locationTemplate?: string; - catalogClient?: CatalogApi; + catalogClient?: CatalogService; parallelismLimit?: number; legacyPathCasing?: boolean; entityTransformer?: TechDocsCollatorEntityTransformer; @@ -86,7 +85,7 @@ export class DefaultTechDocsCollatorFactory implements DocumentCollatorFactory { private locationTemplate: string; private readonly logger: LoggerService; private readonly auth: AuthService; - private readonly catalogClient: CatalogApi; + private readonly catalogClient: CatalogService; private readonly parallelismLimit: number; private readonly legacyPathCasing: boolean; private entityTransformer: TechDocsCollatorEntityTransformer; @@ -99,9 +98,10 @@ export class DefaultTechDocsCollatorFactory implements DocumentCollatorFactory { this.locationTemplate = options.locationTemplate || '/docs/:namespace/:kind/:name/:path'; this.logger = options.logger.child({ documentType: this.type }); - this.catalogClient = - options.catalogClient || - new CatalogClient({ discoveryApi: options.discovery }); + if (!options.catalogClient) { + throw new Error('catalogClient is required'); + } + this.catalogClient = options.catalogClient; this.parallelismLimit = options.parallelismLimit ?? 10; this.legacyPathCasing = options.legacyPathCasing ?? false; this.entityTransformer = options.entityTransformer ?? (() => ({})); @@ -147,10 +147,7 @@ export class DefaultTechDocsCollatorFactory implements DocumentCollatorFactory { // parallelism limit to simplify configuration. const batchSize = this.parallelismLimit * 50; while (moreEntitiesToGet) { - const { token: catalogToken } = await this.auth.getPluginRequestToken({ - onBehalfOf: await this.auth.getOwnServiceCredentials(), - targetPluginId: 'catalog', - }); + const credentials = await this.auth.getOwnServiceCredentials(); const entities = ( await this.catalogClient.getEntities( @@ -163,7 +160,7 @@ export class DefaultTechDocsCollatorFactory implements DocumentCollatorFactory { limit: batchSize, offset: entitiesRetrieved, }, - { token: catalogToken }, + { credentials }, ) ).items; diff --git a/plugins/search-backend-module-techdocs/src/module.ts b/plugins/search-backend-module-techdocs/src/module.ts index db419169f9..050c075ea2 100644 --- a/plugins/search-backend-module-techdocs/src/module.ts +++ b/plugins/search-backend-module-techdocs/src/module.ts @@ -27,7 +27,7 @@ import { } from '@backstage/backend-plugin-api'; import { EntityFilterQuery } from '@backstage/catalog-client'; import { Entity } from '@backstage/catalog-model'; -import { catalogServiceRef } from '@backstage/plugin-catalog-node/alpha'; +import { catalogServiceRef } from '@backstage/plugin-catalog-node'; import { searchIndexRegistryExtensionPoint } from '@backstage/plugin-search-backend-node/alpha'; import { DefaultTechDocsCollatorFactory } from './collators/DefaultTechDocsCollatorFactory'; import { diff --git a/plugins/techdocs-backend/src/plugin.ts b/plugins/techdocs-backend/src/plugin.ts index ac16e43dab..543d017ae1 100644 --- a/plugins/techdocs-backend/src/plugin.ts +++ b/plugins/techdocs-backend/src/plugin.ts @@ -34,7 +34,7 @@ import { techdocsPreparerExtensionPoint, techdocsPublisherExtensionPoint, } from '@backstage/plugin-techdocs-node'; -import { catalogServiceRef } from '@backstage/plugin-catalog-node/alpha'; +import { catalogServiceRef } from '@backstage/plugin-catalog-node'; import * as winston from 'winston'; import { createRouter } from './service/router'; diff --git a/plugins/techdocs-backend/src/service/CachedEntityLoader.test.ts b/plugins/techdocs-backend/src/service/CachedEntityLoader.test.ts index df5c038dce..82bbfd27d0 100644 --- a/plugins/techdocs-backend/src/service/CachedEntityLoader.test.ts +++ b/plugins/techdocs-backend/src/service/CachedEntityLoader.test.ts @@ -39,8 +39,6 @@ describe('CachedEntityLoader', () => { }, }; - const token = 'test-token'; - const userCredentials: BackstageCredentials = { $$type: '@backstage/BackstageCredentials', principal: { @@ -67,7 +65,7 @@ describe('CachedEntityLoader', () => { auth.isPrincipal.mockReturnValue(true); const loader = new CachedEntityLoader({ auth, catalog, cache }); - const result = await loader.load(userCredentials, entityName, token); + const result = await loader.load(userCredentials, entityName); expect(result).toEqual(entity); expect(cache.set).toHaveBeenCalledWith( @@ -84,7 +82,7 @@ describe('CachedEntityLoader', () => { auth.isPrincipal.mockReturnValue(true); const loader = new CachedEntityLoader({ auth, catalog, cache }); - const result = await loader.load(userCredentials, entityName, token); + const result = await loader.load(userCredentials, entityName); expect(result).toEqual(entity); expect(catalog.getEntityByRef).not.toHaveBeenCalled(); @@ -96,7 +94,7 @@ describe('CachedEntityLoader', () => { auth.isPrincipal.mockReturnValue(true); const loader = new CachedEntityLoader({ auth, catalog, cache }); - const result = await loader.load(userCredentials, entityName, token); + const result = await loader.load(userCredentials, entityName); expect(result).toBeUndefined(); expect(cache.set).not.toHaveBeenCalled(); @@ -108,7 +106,7 @@ describe('CachedEntityLoader', () => { auth.isPrincipal.mockReturnValueOnce(false).mockReturnValueOnce(true); const loader = new CachedEntityLoader({ auth, catalog, cache }); - const result = await loader.load(pluginCredentials, entityName, undefined); + const result = await loader.load(pluginCredentials, entityName); expect(result).toEqual(entity); expect(cache.set).toHaveBeenCalledWith( @@ -131,7 +129,7 @@ describe('CachedEntityLoader', () => { auth.isPrincipal.mockReturnValue(true); const loader = new CachedEntityLoader({ auth, catalog, cache }); - const result = await loader.load(userCredentials, entityName, token); + const result = await loader.load(userCredentials, entityName); expect(result).toEqual(entity); }); @@ -151,8 +149,8 @@ describe('CachedEntityLoader', () => { }, }; - await loader.load(userCredentials, entityName, token); - await loader.load(anotherUserCredentials, entityName, token); + await loader.load(userCredentials, entityName); + await loader.load(anotherUserCredentials, entityName); expect(cache.set).toHaveBeenCalledWith( 'catalog:component:default/test:user:default/test-user', @@ -172,7 +170,7 @@ describe('CachedEntityLoader', () => { auth.isPrincipal.mockReturnValueOnce(false).mockReturnValueOnce(true); const loader = new CachedEntityLoader({ auth, catalog, cache }); - const result = await loader.load(pluginCredentials, entityName, token); + const result = await loader.load(pluginCredentials, entityName); expect(result).toEqual(entity); expect(cache.set).toHaveBeenCalledWith( @@ -197,7 +195,7 @@ describe('CachedEntityLoader', () => { }; const loader = new CachedEntityLoader({ auth, catalog, cache }); - const result = await loader.load(unknownCredentials, entityName, token); + const result = await loader.load(unknownCredentials, entityName); expect(result).toEqual(entity); expect(cache.set).toHaveBeenCalledWith( diff --git a/plugins/techdocs-backend/src/service/CachedEntityLoader.ts b/plugins/techdocs-backend/src/service/CachedEntityLoader.ts index 9f97ed2cd4..8e19bb2411 100644 --- a/plugins/techdocs-backend/src/service/CachedEntityLoader.ts +++ b/plugins/techdocs-backend/src/service/CachedEntityLoader.ts @@ -19,22 +19,22 @@ import { BackstageCredentials, CacheService, } from '@backstage/backend-plugin-api'; -import { CatalogApi } from '@backstage/catalog-client'; import { Entity, CompoundEntityRef, stringifyEntityRef, } from '@backstage/catalog-model'; +import { CatalogService } from '@backstage/plugin-catalog-node'; export type CachedEntityLoaderOptions = { auth: AuthService; - catalog: CatalogApi; + catalog: CatalogService; cache: CacheService; }; export class CachedEntityLoader { private readonly auth: AuthService; - private readonly catalog: CatalogApi; + private readonly catalog: CatalogService; private readonly cache: CacheService; private readonly readTimeout = 1000; @@ -47,7 +47,6 @@ export class CachedEntityLoader { async load( credentials: BackstageCredentials, entityRef: CompoundEntityRef, - token: string | undefined, ): Promise { const cacheKey = this.getCacheKey(entityRef, credentials); let result = await this.getFromCache(cacheKey); @@ -56,7 +55,7 @@ export class CachedEntityLoader { return result; } - result = await this.catalog.getEntityByRef(entityRef, { token }); + result = await this.catalog.getEntityByRef(entityRef, { credentials }); if (result) { this.cache.set(cacheKey, result, { ttl: 5000 }); diff --git a/plugins/techdocs-backend/src/service/router.test.ts b/plugins/techdocs-backend/src/service/router.test.ts index 89c1009c3f..5b409960d0 100644 --- a/plugins/techdocs-backend/src/service/router.test.ts +++ b/plugins/techdocs-backend/src/service/router.test.ts @@ -28,8 +28,8 @@ import { CachedEntityLoader } from './CachedEntityLoader'; import { createEventStream, createRouter, RouterOptions } from './router'; import { TechDocsCache } from '../cache'; import { mockErrorHandler, mockServices } from '@backstage/backend-test-utils'; +import { catalogServiceMock } from '@backstage/plugin-catalog-node/testUtils'; -jest.mock('@backstage/catalog-client'); jest.mock('./CachedEntityLoader'); jest.mock('./DocsSynchronizer'); jest.mock('../cache/TechDocsCache'); @@ -107,6 +107,7 @@ describe('createRouter', () => { const docsBuildStrategy: jest.Mocked = { shouldBuild: jest.fn(), }; + const mockCatalogService = catalogServiceMock(); const outOfTheBoxOptions = { preparers, generators, @@ -124,6 +125,7 @@ describe('createRouter', () => { docsBuildStrategy, auth: mockServices.auth(), httpAuth: mockServices.httpAuth(), + catalogClient: mockCatalogService, }; const recommendedOptions = { publisher, @@ -134,6 +136,7 @@ describe('createRouter', () => { docsBuildStrategy, auth: mockServices.auth(), httpAuth: mockServices.httpAuth(), + catalogClient: mockCatalogService, }; beforeEach(() => { diff --git a/plugins/techdocs-backend/src/service/router.ts b/plugins/techdocs-backend/src/service/router.ts index 4312e1a5af..b05eefff79 100644 --- a/plugins/techdocs-backend/src/service/router.ts +++ b/plugins/techdocs-backend/src/service/router.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import { CatalogApi, CatalogClient } from '@backstage/catalog-client'; import { stringifyEntityRef } from '@backstage/catalog-model'; import { Config, readDurationFromConfig } from '@backstage/config'; import { NotFoundError } from '@backstage/errors'; @@ -41,6 +40,7 @@ import { HttpAuthService, LoggerService, } from '@backstage/backend-plugin-api'; +import { CatalogService } from '@backstage/plugin-catalog-node'; import { durationToMilliseconds } from '@backstage/types'; /** @@ -60,7 +60,7 @@ export type OutOfTheBoxDeploymentOptions = { cache: CacheService; docsBuildStrategy?: DocsBuildStrategy; buildLogTransport?: winston.transport; - catalogClient?: CatalogApi; + catalogClient?: CatalogService; httpAuth: HttpAuthService; auth: AuthService; }; @@ -79,7 +79,7 @@ export type RecommendedDeploymentOptions = { cache: CacheService; docsBuildStrategy?: DocsBuildStrategy; buildLogTransport?: winston.transport; - catalogClient?: CatalogApi; + catalogClient?: CatalogService; httpAuth: HttpAuthService; auth: AuthService; }; @@ -116,8 +116,10 @@ export async function createRouter( const router = Router(); const { publisher, config, logger, discovery, httpAuth, auth } = options; - const catalogClient = - options.catalogClient ?? new CatalogClient({ discoveryApi: discovery }); + if (!options.catalogClient) { + throw new Error('catalogClient is required'); + } + const catalogClient = options.catalogClient; const docsBuildStrategy = options.docsBuildStrategy ?? DefaultDocsBuildStrategy.fromConfig(config); const buildLogTransport = options.buildLogTransport; @@ -163,13 +165,8 @@ export async function createRouter( const credentials = await httpAuth.credentials(req); - const { token } = await auth.getPluginRequestToken({ - onBehalfOf: credentials, - targetPluginId: 'catalog', - }); - // Verify that the related entity exists and the current user has permission to view it. - const entity = await entityLoader.load(credentials, entityName, token); + const entity = await entityLoader.load(credentials, entityName); if (!entity) { throw new NotFoundError( @@ -202,12 +199,7 @@ export async function createRouter( const credentials = await httpAuth.credentials(req); - const { token } = await auth.getPluginRequestToken({ - onBehalfOf: credentials, - targetPluginId: 'catalog', - }); - - const entity = await entityLoader.load(credentials, entityName, token); + const entity = await entityLoader.load(credentials, entityName); if (!entity) { throw new NotFoundError( @@ -240,17 +232,12 @@ export async function createRouter( const credentials = await httpAuth.credentials(req); - const { token } = await auth.getPluginRequestToken({ - onBehalfOf: credentials, - targetPluginId: 'catalog', + const entity = await entityLoader.load(credentials, { + kind, + namespace, + name, }); - const entity = await entityLoader.load( - credentials, - { kind, namespace, name }, - token, - ); - if (!entity?.metadata?.uid) { throw new NotFoundError('Entity metadata UID missing'); } @@ -315,12 +302,7 @@ export async function createRouter( allowLimitedAccess: true, }); - const { token } = await auth.getPluginRequestToken({ - onBehalfOf: credentials, - targetPluginId: 'catalog', - }); - - const entity = await entityLoader.load(credentials, entityName, token); + const entity = await entityLoader.load(credentials, entityName); if (!entity) { throw new NotFoundError( From 31b6cc68b6cfe689de31833990db105dafe5590e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Fri, 3 Apr 2026 22:54:57 +0200 Subject: [PATCH 2/9] Rename TechDocsCollatorFactoryOptions.catalogClient to .catalog MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw Made-with: Cursor --- .../collators/DefaultTechDocsCollatorFactory.test.ts | 4 ++-- .../src/collators/DefaultTechDocsCollatorFactory.ts | 12 ++++++------ plugins/search-backend-module-techdocs/src/module.ts | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/plugins/search-backend-module-techdocs/src/collators/DefaultTechDocsCollatorFactory.test.ts b/plugins/search-backend-module-techdocs/src/collators/DefaultTechDocsCollatorFactory.test.ts index d4f3e33d60..cbddb1cadd 100644 --- a/plugins/search-backend-module-techdocs/src/collators/DefaultTechDocsCollatorFactory.test.ts +++ b/plugins/search-backend-module-techdocs/src/collators/DefaultTechDocsCollatorFactory.test.ts @@ -91,7 +91,7 @@ describe('DefaultTechDocsCollatorFactory', () => { logger, discovery: mockDiscoveryApi, auth: mockServices.auth(), - catalogClient: mockCatalog, + catalog: mockCatalog, }; it('has expected type', () => { @@ -161,7 +161,7 @@ describe('DefaultTechDocsCollatorFactory', () => { discovery: mockDiscoveryApi, logger, auth: mockServices.auth(), - catalogClient: mockCatalog, + catalog: mockCatalog, }); collator = await factory.getCollator(); diff --git a/plugins/search-backend-module-techdocs/src/collators/DefaultTechDocsCollatorFactory.ts b/plugins/search-backend-module-techdocs/src/collators/DefaultTechDocsCollatorFactory.ts index 1a2e4af099..c275224ba2 100644 --- a/plugins/search-backend-module-techdocs/src/collators/DefaultTechDocsCollatorFactory.ts +++ b/plugins/search-backend-module-techdocs/src/collators/DefaultTechDocsCollatorFactory.ts @@ -55,7 +55,7 @@ export type TechDocsCollatorFactoryOptions = { logger: LoggerService; auth: AuthService; locationTemplate?: string; - catalogClient?: CatalogService; + catalog?: CatalogService; parallelismLimit?: number; legacyPathCasing?: boolean; entityTransformer?: TechDocsCollatorEntityTransformer; @@ -85,7 +85,7 @@ export class DefaultTechDocsCollatorFactory implements DocumentCollatorFactory { private locationTemplate: string; private readonly logger: LoggerService; private readonly auth: AuthService; - private readonly catalogClient: CatalogService; + private readonly catalog: CatalogService; private readonly parallelismLimit: number; private readonly legacyPathCasing: boolean; private entityTransformer: TechDocsCollatorEntityTransformer; @@ -98,10 +98,10 @@ export class DefaultTechDocsCollatorFactory implements DocumentCollatorFactory { this.locationTemplate = options.locationTemplate || '/docs/:namespace/:kind/:name/:path'; this.logger = options.logger.child({ documentType: this.type }); - if (!options.catalogClient) { - throw new Error('catalogClient is required'); + if (!options.catalog) { + throw new Error('catalog is required'); } - this.catalogClient = options.catalogClient; + this.catalog = options.catalog; this.parallelismLimit = options.parallelismLimit ?? 10; this.legacyPathCasing = options.legacyPathCasing ?? false; this.entityTransformer = options.entityTransformer ?? (() => ({})); @@ -150,7 +150,7 @@ export class DefaultTechDocsCollatorFactory implements DocumentCollatorFactory { const credentials = await this.auth.getOwnServiceCredentials(); const entities = ( - await this.catalogClient.getEntities( + await this.catalog.getEntities( { filter: { 'metadata.annotations.backstage.io/techdocs-ref': diff --git a/plugins/search-backend-module-techdocs/src/module.ts b/plugins/search-backend-module-techdocs/src/module.ts index 050c075ea2..d6149ca81a 100644 --- a/plugins/search-backend-module-techdocs/src/module.ts +++ b/plugins/search-backend-module-techdocs/src/module.ts @@ -162,7 +162,7 @@ export default createBackendModule({ discovery, auth, logger, - catalogClient: catalog, + catalog, entityTransformer, documentTransformer, customCatalogApiFilters, From 3588c3f57f8d6ccb0b0d2e89f5a2b0ffb18650f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Fri, 3 Apr 2026 22:55:36 +0200 Subject: [PATCH 3/9] Make TechDocsCollatorFactoryOptions.catalog required MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw Made-with: Cursor --- .../src/collators/DefaultTechDocsCollatorFactory.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/plugins/search-backend-module-techdocs/src/collators/DefaultTechDocsCollatorFactory.ts b/plugins/search-backend-module-techdocs/src/collators/DefaultTechDocsCollatorFactory.ts index c275224ba2..06af1632d6 100644 --- a/plugins/search-backend-module-techdocs/src/collators/DefaultTechDocsCollatorFactory.ts +++ b/plugins/search-backend-module-techdocs/src/collators/DefaultTechDocsCollatorFactory.ts @@ -55,7 +55,7 @@ export type TechDocsCollatorFactoryOptions = { logger: LoggerService; auth: AuthService; locationTemplate?: string; - catalog?: CatalogService; + catalog: CatalogService; parallelismLimit?: number; legacyPathCasing?: boolean; entityTransformer?: TechDocsCollatorEntityTransformer; @@ -98,9 +98,6 @@ export class DefaultTechDocsCollatorFactory implements DocumentCollatorFactory { this.locationTemplate = options.locationTemplate || '/docs/:namespace/:kind/:name/:path'; this.logger = options.logger.child({ documentType: this.type }); - if (!options.catalog) { - throw new Error('catalog is required'); - } this.catalog = options.catalog; this.parallelismLimit = options.parallelismLimit ?? 10; this.legacyPathCasing = options.legacyPathCasing ?? false; From da3f5356ec96d6789775b4425413ad20a78af27c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Fri, 3 Apr 2026 22:57:23 +0200 Subject: [PATCH 4/9] Rename router catalogClient options to catalog and make mandatory MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw Made-with: Cursor --- plugins/techdocs-backend/src/plugin.ts | 2 +- plugins/techdocs-backend/src/service/router.test.ts | 4 ++-- plugins/techdocs-backend/src/service/router.ts | 9 +++------ 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/plugins/techdocs-backend/src/plugin.ts b/plugins/techdocs-backend/src/plugin.ts index 543d017ae1..ad26967804 100644 --- a/plugins/techdocs-backend/src/plugin.ts +++ b/plugins/techdocs-backend/src/plugin.ts @@ -166,7 +166,7 @@ export const techdocsPlugin = createBackendPlugin({ discovery, httpAuth, auth, - catalogClient: catalog, + catalog, }), ); diff --git a/plugins/techdocs-backend/src/service/router.test.ts b/plugins/techdocs-backend/src/service/router.test.ts index 5b409960d0..40e8fe450d 100644 --- a/plugins/techdocs-backend/src/service/router.test.ts +++ b/plugins/techdocs-backend/src/service/router.test.ts @@ -125,7 +125,7 @@ describe('createRouter', () => { docsBuildStrategy, auth: mockServices.auth(), httpAuth: mockServices.httpAuth(), - catalogClient: mockCatalogService, + catalog: mockCatalogService, }; const recommendedOptions = { publisher, @@ -136,7 +136,7 @@ describe('createRouter', () => { docsBuildStrategy, auth: mockServices.auth(), httpAuth: mockServices.httpAuth(), - catalogClient: mockCatalogService, + catalog: mockCatalogService, }; beforeEach(() => { diff --git a/plugins/techdocs-backend/src/service/router.ts b/plugins/techdocs-backend/src/service/router.ts index b05eefff79..48b8afaf53 100644 --- a/plugins/techdocs-backend/src/service/router.ts +++ b/plugins/techdocs-backend/src/service/router.ts @@ -60,7 +60,7 @@ export type OutOfTheBoxDeploymentOptions = { cache: CacheService; docsBuildStrategy?: DocsBuildStrategy; buildLogTransport?: winston.transport; - catalogClient?: CatalogService; + catalog: CatalogService; httpAuth: HttpAuthService; auth: AuthService; }; @@ -79,7 +79,7 @@ export type RecommendedDeploymentOptions = { cache: CacheService; docsBuildStrategy?: DocsBuildStrategy; buildLogTransport?: winston.transport; - catalogClient?: CatalogService; + catalog: CatalogService; httpAuth: HttpAuthService; auth: AuthService; }; @@ -116,10 +116,7 @@ export async function createRouter( const router = Router(); const { publisher, config, logger, discovery, httpAuth, auth } = options; - if (!options.catalogClient) { - throw new Error('catalogClient is required'); - } - const catalogClient = options.catalogClient; + const catalogClient = options.catalog; const docsBuildStrategy = options.docsBuildStrategy ?? DefaultDocsBuildStrategy.fromConfig(config); const buildLogTransport = options.buildLogTransport; From 7d9d18565426915800884e53f37da681a8d9d62e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Fri, 3 Apr 2026 22:58:20 +0200 Subject: [PATCH 5/9] Inline catalog from options directly in createRouter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw Made-with: Cursor --- plugins/techdocs-backend/src/service/router.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/techdocs-backend/src/service/router.ts b/plugins/techdocs-backend/src/service/router.ts index 48b8afaf53..53e9d2003a 100644 --- a/plugins/techdocs-backend/src/service/router.ts +++ b/plugins/techdocs-backend/src/service/router.ts @@ -114,9 +114,9 @@ export async function createRouter( options: RouterOptions, ): Promise { const router = Router(); - const { publisher, config, logger, discovery, httpAuth, auth } = options; + const { publisher, config, logger, discovery, httpAuth, auth, catalog } = + options; - const catalogClient = options.catalog; const docsBuildStrategy = options.docsBuildStrategy ?? DefaultDocsBuildStrategy.fromConfig(config); const buildLogTransport = options.buildLogTransport; @@ -125,7 +125,7 @@ export async function createRouter( // when loading a single techdocs page. const entityLoader = new CachedEntityLoader({ auth, - catalog: catalogClient, + catalog, cache: options.cache, }); From 52a45c308b00220ea8b3bf90fff2fc491c4a7c0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Fri, 3 Apr 2026 23:05:02 +0200 Subject: [PATCH 6/9] Address review feedback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Move getOwnServiceCredentials() before the pagination loop in DefaultTechDocsCollatorFactory - Fix stale test comment that referenced the deleted MSW /entities handler Signed-off-by: Fredrik Adelöw Made-with: Cursor --- .../src/collators/DefaultTechDocsCollatorFactory.test.ts | 6 +++--- .../src/collators/DefaultTechDocsCollatorFactory.ts | 3 +-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/plugins/search-backend-module-techdocs/src/collators/DefaultTechDocsCollatorFactory.test.ts b/plugins/search-backend-module-techdocs/src/collators/DefaultTechDocsCollatorFactory.test.ts index cbddb1cadd..e7c0f01b2d 100644 --- a/plugins/search-backend-module-techdocs/src/collators/DefaultTechDocsCollatorFactory.test.ts +++ b/plugins/search-backend-module-techdocs/src/collators/DefaultTechDocsCollatorFactory.test.ts @@ -186,9 +186,9 @@ describe('DefaultTechDocsCollatorFactory', () => { }); it('paginates through catalog entities using batchSize', async () => { - // A parallelismLimit of 1 is a catalog limit of 50 per request. Code - // above in the /entities handler ensures valid entities are only - // returned on the second page. + // A parallelismLimit of 1 results in a batchSize of 50 per request. + // The catalog returns fewer entities than the batchSize, so the loop + // exits after a single page, producing 3 documents (1 entity × 3 search index docs). const _config = new ConfigReader({ ...config.get(), search: { diff --git a/plugins/search-backend-module-techdocs/src/collators/DefaultTechDocsCollatorFactory.ts b/plugins/search-backend-module-techdocs/src/collators/DefaultTechDocsCollatorFactory.ts index 06af1632d6..0c13aadbc6 100644 --- a/plugins/search-backend-module-techdocs/src/collators/DefaultTechDocsCollatorFactory.ts +++ b/plugins/search-backend-module-techdocs/src/collators/DefaultTechDocsCollatorFactory.ts @@ -143,9 +143,8 @@ export class DefaultTechDocsCollatorFactory implements DocumentCollatorFactory { // at index-time. The batchSize is calculated as a factor of the given // parallelism limit to simplify configuration. const batchSize = this.parallelismLimit * 50; + const credentials = await this.auth.getOwnServiceCredentials(); while (moreEntitiesToGet) { - const credentials = await this.auth.getOwnServiceCredentials(); - const entities = ( await this.catalog.getEntities( { From 999f308ad550edc20af8393a100a8fd944ec5fa3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Fri, 3 Apr 2026 23:06:47 +0200 Subject: [PATCH 7/9] Call getOwnServiceCredentials per request, not once per loop MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw Made-with: Cursor --- .../src/collators/DefaultTechDocsCollatorFactory.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/search-backend-module-techdocs/src/collators/DefaultTechDocsCollatorFactory.ts b/plugins/search-backend-module-techdocs/src/collators/DefaultTechDocsCollatorFactory.ts index 0c13aadbc6..7d81b681ba 100644 --- a/plugins/search-backend-module-techdocs/src/collators/DefaultTechDocsCollatorFactory.ts +++ b/plugins/search-backend-module-techdocs/src/collators/DefaultTechDocsCollatorFactory.ts @@ -143,8 +143,8 @@ export class DefaultTechDocsCollatorFactory implements DocumentCollatorFactory { // at index-time. The batchSize is calculated as a factor of the given // parallelism limit to simplify configuration. const batchSize = this.parallelismLimit * 50; - const credentials = await this.auth.getOwnServiceCredentials(); while (moreEntitiesToGet) { + const credentials = await this.auth.getOwnServiceCredentials(); const entities = ( await this.catalog.getEntities( { From e7fdf2997f15164b0820594396eaee1afebc53dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Fri, 3 Apr 2026 23:23:42 +0200 Subject: [PATCH 8/9] Address Copilot review: fix pagination test and add credentials assertion MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw Made-with: Cursor --- .../DefaultTechDocsCollatorFactory.test.ts | 23 ++++++++++++------- .../src/service/CachedEntityLoader.test.ts | 4 ++++ 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/plugins/search-backend-module-techdocs/src/collators/DefaultTechDocsCollatorFactory.test.ts b/plugins/search-backend-module-techdocs/src/collators/DefaultTechDocsCollatorFactory.test.ts index e7c0f01b2d..76fd9a08e1 100644 --- a/plugins/search-backend-module-techdocs/src/collators/DefaultTechDocsCollatorFactory.test.ts +++ b/plugins/search-backend-module-techdocs/src/collators/DefaultTechDocsCollatorFactory.test.ts @@ -186,9 +186,9 @@ describe('DefaultTechDocsCollatorFactory', () => { }); it('paginates through catalog entities using batchSize', async () => { - // A parallelismLimit of 1 results in a batchSize of 50 per request. - // The catalog returns fewer entities than the batchSize, so the loop - // exits after a single page, producing 3 documents (1 entity × 3 search index docs). + // parallelismLimit of 1 → batchSize of 50 per request. + // First page returns exactly 50 (no techdocs annotation) triggering a + // second request; second page returns the real entity with annotation. const _config = new ConfigReader({ ...config.get(), search: { @@ -199,17 +199,24 @@ describe('DefaultTechDocsCollatorFactory', () => { }, }, }); - factory = DefaultTechDocsCollatorFactory.fromConfig(_config, options); + const paginationCatalog = catalogServiceMock({ entities: [] }); + jest + .spyOn(paginationCatalog, 'getEntities') + .mockResolvedValueOnce({ items: Array(50).fill({}) }) + .mockResolvedValueOnce({ items: expectedEntities }); + factory = DefaultTechDocsCollatorFactory.fromConfig(_config, { + ...options, + catalog: paginationCatalog, + }); collator = await factory.getCollator(); const pipeline = TestPipeline.fromCollator(collator); const { documents } = await pipeline.execute(); - // Only 1 entity with TechDocs configured multiplied by 3 pages. + expect(paginationCatalog.getEntities).toHaveBeenCalledTimes(2); + // First page: 50 entities with no techdocs annotation → 0 docs + // Second page: 1 entity × 3 search index docs → 3 docs expect(documents).toHaveLength(3); - expect(_config.get('search.collators.techdocs.parallelismLimit')).toEqual( - 1, - ); }); describe('with legacyPathCasing configuration', () => { diff --git a/plugins/techdocs-backend/src/service/CachedEntityLoader.test.ts b/plugins/techdocs-backend/src/service/CachedEntityLoader.test.ts index 82bbfd27d0..7f90df3714 100644 --- a/plugins/techdocs-backend/src/service/CachedEntityLoader.test.ts +++ b/plugins/techdocs-backend/src/service/CachedEntityLoader.test.ts @@ -62,12 +62,16 @@ describe('CachedEntityLoader', () => { it('writes entities to cache for user credentials', async () => { cache.get.mockResolvedValue(undefined); const catalog = catalogServiceMock({ entities: [entity] }); + jest.spyOn(catalog, 'getEntityByRef'); auth.isPrincipal.mockReturnValue(true); const loader = new CachedEntityLoader({ auth, catalog, cache }); const result = await loader.load(userCredentials, entityName); expect(result).toEqual(entity); + expect(catalog.getEntityByRef).toHaveBeenCalledWith(entityName, { + credentials: userCredentials, + }); expect(cache.set).toHaveBeenCalledWith( 'catalog:component:default/test:user:default/test-user', entity, From 323da94780ebd47089765210b98dd36504cffb4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Sat, 4 Apr 2026 13:52:58 +0200 Subject: [PATCH 9/9] Use mockCredentials in CachedEntityLoader tests and simplify cache key MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace hand-crafted BackstageCredentials objects with mockCredentials from @backstage/backend-test-utils, which have proper toString() methods. Simplify getCacheKey to use String(credentials) directly and remove the auth service dependency from CachedEntityLoader. Signed-off-by: Fredrik Adelöw Made-with: Cursor --- .../src/service/CachedEntityLoader.test.ts | 106 ++++-------------- .../src/service/CachedEntityLoader.ts | 20 +--- .../techdocs-backend/src/service/router.ts | 1 - 3 files changed, 25 insertions(+), 102 deletions(-) diff --git a/plugins/techdocs-backend/src/service/CachedEntityLoader.test.ts b/plugins/techdocs-backend/src/service/CachedEntityLoader.test.ts index 7f90df3714..104efcefcf 100644 --- a/plugins/techdocs-backend/src/service/CachedEntityLoader.test.ts +++ b/plugins/techdocs-backend/src/service/CachedEntityLoader.test.ts @@ -16,13 +16,11 @@ import { CachedEntityLoader } from './CachedEntityLoader'; import { CompoundEntityRef } from '@backstage/catalog-model'; -import { mockServices } from '@backstage/backend-test-utils'; +import { mockCredentials, mockServices } from '@backstage/backend-test-utils'; import { catalogServiceMock } from '@backstage/plugin-catalog-node/testUtils'; -import { BackstageCredentials } from '@backstage/backend-plugin-api'; describe('CachedEntityLoader', () => { const cache = mockServices.cache.mock(); - const auth = mockServices.auth.mock(); const entityName: CompoundEntityRef = { kind: 'component', @@ -39,21 +37,8 @@ describe('CachedEntityLoader', () => { }, }; - const userCredentials: BackstageCredentials = { - $$type: '@backstage/BackstageCredentials', - principal: { - type: 'user', - userEntityRef: 'user:default/test-user', - }, - }; - - const pluginCredentials: BackstageCredentials = { - $$type: '@backstage/BackstageCredentials', - principal: { - type: 'plugin', - subject: 'plugin:test-plugin', - }, - }; + const userCredentials = mockCredentials.user('user:default/test-user'); + const serviceCredentials = mockCredentials.service('plugin:test-plugin'); afterEach(() => { jest.resetAllMocks(); @@ -63,9 +48,8 @@ describe('CachedEntityLoader', () => { cache.get.mockResolvedValue(undefined); const catalog = catalogServiceMock({ entities: [entity] }); jest.spyOn(catalog, 'getEntityByRef'); - auth.isPrincipal.mockReturnValue(true); - const loader = new CachedEntityLoader({ auth, catalog, cache }); + const loader = new CachedEntityLoader({ catalog, cache }); const result = await loader.load(userCredentials, entityName); expect(result).toEqual(entity); @@ -73,7 +57,7 @@ describe('CachedEntityLoader', () => { credentials: userCredentials, }); expect(cache.set).toHaveBeenCalledWith( - 'catalog:component:default/test:user:default/test-user', + `catalog:component:default/test:${userCredentials}`, entity, { ttl: 5000 }, ); @@ -83,9 +67,8 @@ describe('CachedEntityLoader', () => { const catalog = catalogServiceMock(); jest.spyOn(catalog, 'getEntityByRef'); cache.get.mockResolvedValue(entity); - auth.isPrincipal.mockReturnValue(true); - const loader = new CachedEntityLoader({ auth, catalog, cache }); + const loader = new CachedEntityLoader({ catalog, cache }); const result = await loader.load(userCredentials, entityName); expect(result).toEqual(entity); @@ -95,33 +78,14 @@ describe('CachedEntityLoader', () => { it('does not cache missing entities', async () => { const catalog = catalogServiceMock({ entities: [] }); cache.get.mockResolvedValue(undefined); - auth.isPrincipal.mockReturnValue(true); - const loader = new CachedEntityLoader({ auth, catalog, cache }); + const loader = new CachedEntityLoader({ catalog, cache }); const result = await loader.load(userCredentials, entityName); expect(result).toBeUndefined(); expect(cache.set).not.toHaveBeenCalled(); }); - it('uses entity ref as cache key for service credentials', async () => { - const catalog = catalogServiceMock({ entities: [entity] }); - cache.get.mockResolvedValue(undefined); - auth.isPrincipal.mockReturnValueOnce(false).mockReturnValueOnce(true); - - const loader = new CachedEntityLoader({ auth, catalog, cache }); - const result = await loader.load(pluginCredentials, entityName); - - expect(result).toEqual(entity); - expect(cache.set).toHaveBeenCalledWith( - 'catalog:component:default/test:plugin:test-plugin', - entity, - { - ttl: 5000, - }, - ); - }); - it('calls the catalog if the cache read takes too long', async () => { cache.get.mockImplementation( () => @@ -130,80 +94,48 @@ describe('CachedEntityLoader', () => { }), ); const catalog = catalogServiceMock({ entities: [entity] }); - auth.isPrincipal.mockReturnValue(true); - const loader = new CachedEntityLoader({ auth, catalog, cache }); + const loader = new CachedEntityLoader({ catalog, cache }); const result = await loader.load(userCredentials, entityName); expect(result).toEqual(entity); }); - it('creates different cache keys for different users', async () => { + it('creates different cache keys for different credentials', async () => { const catalog = catalogServiceMock({ entities: [entity] }); cache.get.mockResolvedValue(undefined); - auth.isPrincipal.mockReturnValue(true); - const loader = new CachedEntityLoader({ auth, catalog, cache }); + const loader = new CachedEntityLoader({ catalog, cache }); - const anotherUserCredentials: BackstageCredentials = { - $$type: '@backstage/BackstageCredentials', - principal: { - type: 'user', - userEntityRef: 'user:default/another-user', - }, - }; + const anotherUserCredentials = mockCredentials.user( + 'user:default/another-user', + ); await loader.load(userCredentials, entityName); await loader.load(anotherUserCredentials, entityName); expect(cache.set).toHaveBeenCalledWith( - 'catalog:component:default/test:user:default/test-user', + `catalog:component:default/test:${userCredentials}`, entity, { ttl: 5000 }, ); expect(cache.set).toHaveBeenCalledWith( - 'catalog:component:default/test:user:default/another-user', + `catalog:component:default/test:${anotherUserCredentials}`, entity, { ttl: 5000 }, ); }); - it('creates cache key with service subject for service credentials', async () => { + it('uses service credentials as cache key for service credentials', async () => { const catalog = catalogServiceMock({ entities: [entity] }); cache.get.mockResolvedValue(undefined); - auth.isPrincipal.mockReturnValueOnce(false).mockReturnValueOnce(true); - const loader = new CachedEntityLoader({ auth, catalog, cache }); - const result = await loader.load(pluginCredentials, entityName); + const loader = new CachedEntityLoader({ catalog, cache }); + const result = await loader.load(serviceCredentials, entityName); expect(result).toEqual(entity); expect(cache.set).toHaveBeenCalledWith( - 'catalog:component:default/test:plugin:test-plugin', - entity, - { ttl: 5000 }, - ); - expect(auth.isPrincipal).toHaveBeenCalledWith(pluginCredentials, 'user'); - expect(auth.isPrincipal).toHaveBeenCalledWith(pluginCredentials, 'service'); - }); - - it('handles credentials that are neither user nor service', async () => { - const catalog = catalogServiceMock({ entities: [entity] }); - cache.get.mockResolvedValue(undefined); - auth.isPrincipal.mockReturnValue(false); - - const unknownCredentials: BackstageCredentials = { - $$type: '@backstage/BackstageCredentials', - principal: { - type: 'unknown' as any, - }, - }; - - const loader = new CachedEntityLoader({ auth, catalog, cache }); - const result = await loader.load(unknownCredentials, entityName); - - expect(result).toEqual(entity); - expect(cache.set).toHaveBeenCalledWith( - 'catalog:component:default/test', + `catalog:component:default/test:${serviceCredentials}`, entity, { ttl: 5000 }, ); diff --git a/plugins/techdocs-backend/src/service/CachedEntityLoader.ts b/plugins/techdocs-backend/src/service/CachedEntityLoader.ts index 8e19bb2411..da3ad513e8 100644 --- a/plugins/techdocs-backend/src/service/CachedEntityLoader.ts +++ b/plugins/techdocs-backend/src/service/CachedEntityLoader.ts @@ -15,7 +15,6 @@ */ import { - AuthService, BackstageCredentials, CacheService, } from '@backstage/backend-plugin-api'; @@ -27,19 +26,16 @@ import { import { CatalogService } from '@backstage/plugin-catalog-node'; export type CachedEntityLoaderOptions = { - auth: AuthService; catalog: CatalogService; cache: CacheService; }; export class CachedEntityLoader { - private readonly auth: AuthService; private readonly catalog: CatalogService; private readonly cache: CacheService; private readonly readTimeout = 1000; - constructor({ auth, catalog, cache }: CachedEntityLoaderOptions) { - this.auth = auth; + constructor({ catalog, cache }: CachedEntityLoaderOptions) { this.catalog = catalog; this.cache = cache; } @@ -77,14 +73,10 @@ export class CachedEntityLoader { entityName: CompoundEntityRef, credentials: BackstageCredentials, ): string { - const key = ['catalog', stringifyEntityRef(entityName)]; - - if (this.auth.isPrincipal(credentials, 'user')) { - key.push(credentials.principal.userEntityRef); - } else if (this.auth.isPrincipal(credentials, 'service')) { - key.push(credentials.principal.subject); - } - - return key.join(':'); + return [ + 'catalog', + stringifyEntityRef(entityName), + String(credentials), // these have a well defined toString method + ].join(':'); } } diff --git a/plugins/techdocs-backend/src/service/router.ts b/plugins/techdocs-backend/src/service/router.ts index 53e9d2003a..30a279ea27 100644 --- a/plugins/techdocs-backend/src/service/router.ts +++ b/plugins/techdocs-backend/src/service/router.ts @@ -124,7 +124,6 @@ export async function createRouter( // Entities are cached to optimize the /static/docs request path, which can be called many times // when loading a single techdocs page. const entityLoader = new CachedEntityLoader({ - auth, catalog, cache: options.cache, });