search - Removed legacy backend support

Signed-off-by: Andre Wanlin <awanlin@spotify.com>

Fixed typo

Signed-off-by: Andre Wanlin <awanlin@spotify.com>

Updated API report

Signed-off-by: Andre Wanlin <awanlin@spotify.com>

Moved types

Signed-off-by: Andre Wanlin <awanlin@spotify.com>

Updated changesets

Signed-off-by: Andre Wanlin <awanlin@spotify.com>

Fixed type error

Signed-off-by: Andre Wanlin <awanlin@spotify.com>

Updated changeset based on feedback

Signed-off-by: Andre Wanlin <awanlin@spotify.com>
This commit is contained in:
Andre Wanlin
2025-03-01 17:13:57 -06:00
parent 85df833fe3
commit d5c4a9db09
27 changed files with 40 additions and 800 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-techdocs-backend': major
---
**BREAKING** Removed deprecated code `DefaultTechDocsCollatorFactory`. Use the `@backstage/plugin-search-backend-module-techdocs` for this instead.
+7
View File
@@ -0,0 +1,7 @@
---
'@backstage/plugin-search-backend-module-techdocs': minor
'@backstage/plugin-search-backend-module-explore': minor
'@backstage/plugin-search-backend': major
---
**BREAKING** Removed support for the legacy backend system and references to `@backstage/backend-common`, please migrate to the new backend system.
-3
View File
@@ -43,7 +43,6 @@ import catalog from './plugins/catalog';
import events from './plugins/events';
import kubernetes from './plugins/kubernetes';
import scaffolder from './plugins/scaffolder';
import search from './plugins/search';
import techdocs from './plugins/techdocs';
import permission from './plugins/permission';
import { PluginEnvironment } from './types';
@@ -133,7 +132,6 @@ async function main() {
const catalogEnv = useHotMemoize(module, () => createEnv('catalog'));
const scaffolderEnv = useHotMemoize(module, () => createEnv('scaffolder'));
const authEnv = useHotMemoize(module, () => createEnv('auth'));
const searchEnv = useHotMemoize(module, () => createEnv('search'));
const techdocsEnv = useHotMemoize(module, () => createEnv('techdocs'));
const kubernetesEnv = useHotMemoize(module, () => createEnv('kubernetes'));
const permissionEnv = useHotMemoize(module, () => createEnv('permission'));
@@ -144,7 +142,6 @@ async function main() {
apiRouter.use('/events', await events(eventsEnv));
apiRouter.use('/scaffolder', await scaffolder(scaffolderEnv));
apiRouter.use('/auth', await authPlugin(authEnv));
apiRouter.use('/search', await search(searchEnv));
apiRouter.use('/techdocs', await techdocs(techdocsEnv));
apiRouter.use('/kubernetes', await kubernetes(kubernetesEnv));
apiRouter.use('/permission', await permission(permissionEnv));
@@ -1,101 +0,0 @@
/*
* Copyright 2021 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 { useHotCleanup } from '@backstage/backend-common';
import { ToolDocumentCollatorFactory } from '@backstage/plugin-search-backend-module-explore';
import { createRouter } from '@backstage/plugin-search-backend';
import { ElasticSearchSearchEngine } from '@backstage/plugin-search-backend-module-elasticsearch';
import { PgSearchEngine } from '@backstage/plugin-search-backend-module-pg';
import {
IndexBuilder,
SearchEngine,
LunrSearchEngine,
} from '@backstage/plugin-search-backend-node';
import { DefaultTechDocsCollatorFactory } from '@backstage/plugin-search-backend-module-techdocs';
import { Router } from 'express';
import { PluginEnvironment } from '../types';
async function createSearchEngine(
env: PluginEnvironment,
): Promise<SearchEngine> {
if (env.config.has('search.elasticsearch')) {
return await ElasticSearchSearchEngine.fromConfig({
logger: env.logger,
config: env.config,
});
}
if (await PgSearchEngine.supported(env.database)) {
return await PgSearchEngine.fromConfig(env.config, {
database: env.database,
logger: env.logger,
});
}
return new LunrSearchEngine({ logger: env.logger });
}
export default async function createPlugin(
env: PluginEnvironment,
): Promise<Router> {
// Initialize a connection to a search engine.
const searchEngine = await createSearchEngine(env);
const indexBuilder = new IndexBuilder({
logger: env.logger,
searchEngine,
});
const schedule = env.scheduler.createScheduledTaskRunner({
frequency: { minutes: 10 },
timeout: { minutes: 15 },
// A 3 second delay gives the backend server a chance to initialize before
// any collators are executed, which may attempt requests against the API.
initialDelay: { seconds: 3 },
});
indexBuilder.addCollator({
schedule,
factory: DefaultTechDocsCollatorFactory.fromConfig(env.config, {
discovery: env.discovery,
logger: env.logger,
tokenManager: env.tokenManager,
}),
});
indexBuilder.addCollator({
schedule,
factory: ToolDocumentCollatorFactory.fromConfig(env.config, {
discovery: env.discovery,
logger: env.logger,
}),
});
// The scheduler controls when documents are gathered from collators and sent
// to the search engine for indexing.
const { scheduler } = await indexBuilder.build();
scheduler.start();
useHotCleanup(module, () => scheduler.stop());
return await createRouter({
engine: indexBuilder.getSearchEngine(),
types: indexBuilder.getDocumentTypes(),
discovery: env.discovery,
permissions: env.permissions,
config: env.config,
logger: env.logger,
});
}
@@ -49,7 +49,6 @@
},
"dependencies": {
"@backstage-community/plugin-explore-common": "^0.0.7",
"@backstage/backend-common": "^0.25.0",
"@backstage/backend-plugin-api": "workspace:^",
"@backstage/config": "workspace:^",
"@backstage/plugin-search-backend-node": "workspace:^",
@@ -3,16 +3,9 @@
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
```ts
import { AuthService } from '@backstage/backend-plugin-api';
import { BackendFeature } from '@backstage/backend-plugin-api';
import { Config } from '@backstage/config';
import { DiscoveryService } from '@backstage/backend-plugin-api';
import { DocumentCollatorFactory } from '@backstage/plugin-search-common';
import { ExploreTool } from '@backstage-community/plugin-explore-common';
import { IndexableDocument } from '@backstage/plugin-search-common';
import { LoggerService } from '@backstage/backend-plugin-api';
import { Readable } from 'stream';
import { TokenManager } from '@backstage/backend-common';
// @public
const _default: BackendFeature;
@@ -20,27 +13,4 @@ export default _default;
// @public
export interface ToolDocument extends IndexableDocument, ExploreTool {}
// @public @deprecated
export class ToolDocumentCollatorFactory implements DocumentCollatorFactory {
// (undocumented)
execute(): AsyncGenerator<ToolDocument>;
// (undocumented)
static fromConfig(
_config: Config,
options: ToolDocumentCollatorFactoryOptions,
): ToolDocumentCollatorFactory;
// (undocumented)
getCollator(): Promise<Readable>;
// (undocumented)
readonly type: string;
}
// @public @deprecated (undocumented)
export type ToolDocumentCollatorFactoryOptions = {
discovery: DiscoveryService;
logger: LoggerService;
tokenManager?: TokenManager;
auth?: AuthService;
};
```
@@ -61,6 +61,7 @@ describe('ToolDocumentCollatorFactory', () => {
const options = {
discovery: mockDiscoveryApi,
logger,
auth: mockServices.auth(),
};
it('has expected type', () => {
@@ -101,6 +102,7 @@ describe('ToolDocumentCollatorFactory', () => {
factory = ToolDocumentCollatorFactory.fromConfig(config, {
discovery: mockDiscoveryApi,
logger,
auth: mockServices.auth(),
});
collator = await factory.getCollator();
@@ -14,10 +14,6 @@
* limitations under the License.
*/
import {
createLegacyAuthAdapters,
TokenManager,
} from '@backstage/backend-common';
import {
AuthService,
DiscoveryService,
@@ -39,21 +35,18 @@ import { Readable } from 'stream';
export interface ToolDocument extends IndexableDocument, ExploreTool {}
/**
* @public
* @deprecated This type is deprecated along with the {@link ToolDocumentCollatorFactory}.
* @internal
*/
export type ToolDocumentCollatorFactoryOptions = {
discovery: DiscoveryService;
logger: LoggerService;
tokenManager?: TokenManager;
auth?: AuthService;
auth: AuthService;
};
/**
* Search collator responsible for collecting explore tools to index.
*
* @public
* @deprecated Migrate to the {@link https://backstage.io/docs/backend-system/building-backends/migrating | new backend system} and install this collator via module instead (see {@link https://github.com/backstage/backstage/blob/nbs10/search-deprecate-create-router/plugins/search-backend-module-explore/README.md#installation | here} for more installation details).
* @internal
*/
export class ToolDocumentCollatorFactory implements DocumentCollatorFactory {
public readonly type: string = 'tools';
@@ -65,7 +58,7 @@ export class ToolDocumentCollatorFactory implements DocumentCollatorFactory {
private constructor(options: ToolDocumentCollatorFactoryOptions) {
this.discovery = options.discovery;
this.logger = options.logger;
this.auth = createLegacyAuthAdapters(options).auth;
this.auth = options.auth;
}
static fromConfig(
@@ -14,9 +14,4 @@
* limitations under the License.
*/
export { ToolDocumentCollatorFactory } from './ToolDocumentCollatorFactory';
export type {
ToolDocument,
ToolDocumentCollatorFactoryOptions,
} from './ToolDocumentCollatorFactory';
export type { ToolDocument } from './ToolDocumentCollatorFactory';
@@ -25,8 +25,7 @@ import {
readSchedulerServiceTaskScheduleDefinitionFromConfig,
} from '@backstage/backend-plugin-api';
import { searchIndexRegistryExtensionPoint } from '@backstage/plugin-search-backend-node/alpha';
import { ToolDocumentCollatorFactory } from '@backstage/plugin-search-backend-module-explore';
import { ToolDocumentCollatorFactory } from './collators/ToolDocumentCollatorFactory';
/**
* Search backend module for the Explore index.
@@ -48,7 +48,6 @@
"test": "backstage-cli package test"
},
"dependencies": {
"@backstage/backend-common": "^0.25.0",
"@backstage/backend-plugin-api": "workspace:^",
"@backstage/catalog-client": "workspace:^",
"@backstage/catalog-model": "workspace:^",
@@ -3,23 +3,11 @@
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
```ts
import { AuthService } from '@backstage/backend-plugin-api';
import { BackendFeature } from '@backstage/backend-plugin-api';
import { CatalogApi } from '@backstage/catalog-client';
import { Config } from '@backstage/config';
import { DiscoveryService } from '@backstage/backend-plugin-api';
import { DocumentCollatorFactory } from '@backstage/plugin-search-common';
import { Entity } from '@backstage/catalog-model';
import { EntityFilterQuery } from '@backstage/catalog-client';
import { ExtensionPoint } from '@backstage/backend-plugin-api';
import { HttpAuthService } from '@backstage/backend-plugin-api';
import { LoggerService } from '@backstage/backend-plugin-api';
import { Permission } from '@backstage/plugin-permission-common';
import { Readable } from 'stream';
import { TechDocsCollatorDocumentTransformer as TechDocsCollatorDocumentTransformer_2 } from '@backstage/plugin-search-backend-module-techdocs';
import { TechDocsCollatorEntityTransformer as TechDocsCollatorEntityTransformer_2 } from '@backstage/plugin-search-backend-module-techdocs';
import { TechDocsDocument } from '@backstage/plugin-techdocs-node';
import { TokenManager } from '@backstage/backend-common';
// @public
const _default: BackendFeature;
@@ -28,21 +16,6 @@ export default _default;
// @public (undocumented)
export const defaultTechDocsCollatorEntityTransformer: TechDocsCollatorEntityTransformer;
// @public @deprecated
export class DefaultTechDocsCollatorFactory implements DocumentCollatorFactory {
// (undocumented)
static fromConfig(
config: Config,
options: TechDocsCollatorFactoryOptions,
): DefaultTechDocsCollatorFactory;
// (undocumented)
getCollator(): Promise<Readable>;
// (undocumented)
readonly type: string;
// (undocumented)
readonly visibilityPermission: Permission;
}
// @public (undocumented)
export interface MkSearchIndexDoc {
// (undocumented)
@@ -93,29 +66,12 @@ export type TechDocsCollatorEntityTransformer = (
export interface TechDocsCollatorEntityTransformerExtensionPoint {
// (undocumented)
setDocumentTransformer(
transformer: TechDocsCollatorDocumentTransformer_2,
transformer: TechDocsCollatorDocumentTransformer,
): void;
// (undocumented)
setTransformer(transformer: TechDocsCollatorEntityTransformer_2): void;
setTransformer(transformer: TechDocsCollatorEntityTransformer): void;
}
// @public
export const techdocsCollatorEntityTransformerExtensionPoint: ExtensionPoint<TechDocsCollatorEntityTransformerExtensionPoint>;
// @public @deprecated
export type TechDocsCollatorFactoryOptions = {
discovery: DiscoveryService;
logger: LoggerService;
tokenManager?: TokenManager;
auth?: AuthService;
httpAuth?: HttpAuthService;
locationTemplate?: string;
catalogClient?: CatalogApi;
parallelismLimit?: number;
legacyPathCasing?: boolean;
entityTransformer?: TechDocsCollatorEntityTransformer;
documentTransformer?: TechDocsCollatorDocumentTransformer;
entityFilterFunction?: (entity: Entity[]) => Entity[];
customCatalogApiFilters?: EntityFilterQuery;
};
```
@@ -88,6 +88,7 @@ describe('DefaultTechDocsCollatorFactory', () => {
const options = {
logger,
discovery: mockDiscoveryApi,
auth: mockServices.auth(),
};
it('has expected type', () => {
@@ -182,6 +183,7 @@ describe('DefaultTechDocsCollatorFactory', () => {
factory = DefaultTechDocsCollatorFactory.fromConfig(_config, {
discovery: mockDiscoveryApi,
logger,
auth: mockServices.auth(),
});
collator = await factory.getCollator();
@@ -14,10 +14,6 @@
* limitations under the License.
*/
import {
createLegacyAuthAdapters,
TokenManager,
} from '@backstage/backend-common';
import {
CATALOG_FILTER_EXISTS,
CatalogApi,
@@ -47,22 +43,18 @@ import { defaultTechDocsCollatorDocumentTransformer } from './defaultTechDocsCol
import {
AuthService,
DiscoveryService,
HttpAuthService,
LoggerService,
} from '@backstage/backend-plugin-api';
/**
* Options to configure the TechDocs collator factory
*
* @public
* @deprecated This type is deprecated along with the {@link DefaultTechDocsCollatorFactory}.
* @internal
*/
export type TechDocsCollatorFactoryOptions = {
discovery: DiscoveryService;
logger: LoggerService;
tokenManager?: TokenManager;
auth?: AuthService;
httpAuth?: HttpAuthService;
auth: AuthService;
locationTemplate?: string;
catalogClient?: CatalogApi;
parallelismLimit?: number;
@@ -83,8 +75,7 @@ type EntityInfo = {
* A search collator factory responsible for gathering and transforming
* TechDocs documents.
*
* @public
* @deprecated Migrate to the {@link https://backstage.io/docs/backend-system/building-backends/migrating | new backend system} and install this collator via module instead (see {@link https://github.com/backstage/backstage/blob/nbs10/search-deprecate-create-router/plugins/search-backend-module-techdocs/README.md#installation | here} for more installation details).
* @internal
*/
export class DefaultTechDocsCollatorFactory implements DocumentCollatorFactory {
public readonly type: string = 'techdocs';
@@ -117,12 +108,7 @@ export class DefaultTechDocsCollatorFactory implements DocumentCollatorFactory {
this.documentTransformer = options.documentTransformer ?? (() => ({}));
this.entityFilterFunction = options.entityFilterFunction;
this.customCatalogApiFilters = options.customCatalogApiFilters;
this.auth = createLegacyAuthAdapters({
auth: options.auth,
discovery: options.discovery,
tokenManager: options.tokenManager,
}).auth;
this.auth = options.auth;
}
static fromConfig(config: Config, options: TechDocsCollatorFactoryOptions) {
@@ -14,10 +14,6 @@
* limitations under the License.
*/
export { DefaultTechDocsCollatorFactory } from './DefaultTechDocsCollatorFactory';
export type { TechDocsCollatorFactoryOptions } from './DefaultTechDocsCollatorFactory';
export { defaultTechDocsCollatorEntityTransformer } from './defaultTechDocsCollatorEntityTransformer';
export type { TechDocsCollatorEntityTransformer } from './TechDocsCollatorEntityTransformer';
@@ -28,12 +28,12 @@ import {
import { EntityFilterQuery } from '@backstage/catalog-client';
import { Entity } from '@backstage/catalog-model';
import { catalogServiceRef } from '@backstage/plugin-catalog-node/alpha';
import { searchIndexRegistryExtensionPoint } from '@backstage/plugin-search-backend-node/alpha';
import { DefaultTechDocsCollatorFactory } from './collators/DefaultTechDocsCollatorFactory';
import {
DefaultTechDocsCollatorFactory,
TechDocsCollatorDocumentTransformer,
TechDocsCollatorEntityTransformer,
} from '@backstage/plugin-search-backend-module-techdocs';
import { searchIndexRegistryExtensionPoint } from '@backstage/plugin-search-backend-node/alpha';
} from './collators';
/** @public */
export interface TechDocsCollatorEntityTransformerExtensionPoint {
@@ -130,7 +130,6 @@ export default createBackendModule({
config: coreServices.rootConfig,
logger: coreServices.logger,
auth: coreServices.auth,
httpAuth: coreServices.httpAuth,
discovery: coreServices.discovery,
scheduler: coreServices.scheduler,
catalog: catalogServiceRef,
@@ -140,7 +139,6 @@ export default createBackendModule({
config,
logger,
auth,
httpAuth,
discovery,
scheduler,
catalog,
@@ -163,7 +161,6 @@ export default createBackendModule({
factory: DefaultTechDocsCollatorFactory.fromConfig(config, {
discovery,
auth,
httpAuth,
logger,
catalogClient: catalog,
entityTransformer,
+1 -2
View File
@@ -56,7 +56,6 @@
"test": "backstage-cli package test"
},
"dependencies": {
"@backstage/backend-common": "^0.25.0",
"@backstage/backend-defaults": "workspace:^",
"@backstage/backend-openapi-utils": "workspace:^",
"@backstage/backend-plugin-api": "workspace:^",
@@ -67,7 +66,6 @@
"@backstage/plugin-search-backend-node": "workspace:^",
"@backstage/plugin-search-common": "workspace:^",
"@backstage/types": "workspace:^",
"@types/express": "^4.17.6",
"dataloader": "^2.0.0",
"express": "^4.17.1",
"lodash": "^4.17.21",
@@ -79,6 +77,7 @@
"@backstage/backend-test-utils": "workspace:^",
"@backstage/cli": "workspace:^",
"@backstage/repo-tools": "workspace:^",
"@types/express": "^4.17.6",
"@types/supertest": "^2.0.8",
"supertest": "^7.0.0"
},
-25
View File
@@ -3,34 +3,9 @@
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
```ts
import { AuthService } from '@backstage/backend-plugin-api';
import { BackendFeature } from '@backstage/backend-plugin-api';
import { Config } from '@backstage/config';
import { DiscoveryService } from '@backstage/backend-plugin-api';
import { DocumentTypeInfo } from '@backstage/plugin-search-common';
import express from 'express';
import { HttpAuthService } from '@backstage/backend-plugin-api';
import { LoggerService } from '@backstage/backend-plugin-api';
import { PermissionAuthorizer } from '@backstage/plugin-permission-common';
import { PermissionEvaluator } from '@backstage/plugin-permission-common';
import { SearchEngine } from '@backstage/plugin-search-backend-node';
// @public @deprecated (undocumented)
export function createRouter(options: RouterOptions): Promise<express.Router>;
// @public
const _default: BackendFeature;
export default _default;
// @public @deprecated (undocumented)
export type RouterOptions = {
engine: SearchEngine;
types: Record<string, DocumentTypeInfo>;
discovery?: DiscoveryService;
permissions: PermissionEvaluator | PermissionAuthorizer;
config: Config;
logger: LoggerService;
auth?: AuthService;
httpAuth?: HttpAuthService;
};
```
-1
View File
@@ -21,4 +21,3 @@
*/
export { default } from './plugin';
export * from './service/router';
@@ -278,6 +278,8 @@ describe('createRouter', () => {
permissions: mockPermissionEvaluator,
discovery,
logger,
auth: mockServices.auth(),
httpAuth: mockServices.httpAuth(),
});
app = express().use(router);
});
+6 -16
View File
@@ -16,7 +16,6 @@
import express from 'express';
import { z } from 'zod';
import { createLegacyAuthAdapters } from '@backstage/backend-common';
import { InputError } from '@backstage/errors';
import { Config } from '@backstage/config';
import { JsonObject, JsonValue } from '@backstage/types';
@@ -39,7 +38,6 @@ import {
HttpAuthService,
LoggerService,
} from '@backstage/backend-plugin-api';
import { HostDiscovery } from '@backstage/backend-defaults/discovery';
const jsonObjectSchema: z.ZodSchema<JsonObject> = z.lazy(() => {
const jsonValueSchema: z.ZodSchema<JsonValue> = z.lazy(() =>
@@ -57,8 +55,7 @@ const jsonObjectSchema: z.ZodSchema<JsonObject> = z.lazy(() => {
});
/**
* @public
* @deprecated Please migrate to the new backend system as this will be removed in the future.
* @internal
*/
export type RouterOptions = {
engine: SearchEngine;
@@ -67,9 +64,8 @@ export type RouterOptions = {
permissions: PermissionEvaluator | PermissionAuthorizer;
config: Config;
logger: LoggerService;
// TODO: Make "auth" and "httpAuth" required once we remove the usage of "tokenManager"
auth?: AuthService;
httpAuth?: HttpAuthService;
auth: AuthService;
httpAuth: HttpAuthService;
};
const defaultMaxPageLimit = 100;
@@ -77,8 +73,7 @@ const defaultMaxTermLength = 100;
const allowedLocationProtocols = ['http:', 'https:'];
/**
* @public
* @deprecated Please migrate to the new backend system as this will be removed in the future.
* @internal
*/
export async function createRouter(
options: RouterOptions,
@@ -90,15 +85,10 @@ export async function createRouter(
permissions,
config,
logger,
discovery = HostDiscovery.fromConfig(config),
auth,
httpAuth,
} = options;
// TODO: stop using this adapter when the "tokenManager" is removed
const { auth, httpAuth } = createLegacyAuthAdapters({
...options,
discovery,
});
const maxPageLimit =
config.getOptionalNumber('search.maxPageLimit') ?? defaultMaxPageLimit;
-42
View File
@@ -7,7 +7,6 @@ import { AuthService } from '@backstage/backend-plugin-api';
import { BackendFeature } from '@backstage/backend-plugin-api';
import { CatalogApi } from '@backstage/catalog-client';
import { Config } from '@backstage/config';
import { DefaultTechDocsCollatorFactory as DefaultTechDocsCollatorFactory_2 } from '@backstage/plugin-search-backend-module-techdocs';
import { DiscoveryService } from '@backstage/backend-plugin-api';
import { DocsBuildStrategy as DocsBuildStrategy_2 } from '@backstage/plugin-techdocs-node';
import { Entity } from '@backstage/catalog-model';
@@ -15,42 +14,15 @@ import express from 'express';
import { GeneratorBuilder } from '@backstage/plugin-techdocs-node';
import { HttpAuthService } from '@backstage/backend-plugin-api';
import { Knex } from 'knex';
import { Logger } from 'winston';
import { Permission } from '@backstage/plugin-permission-common';
import { PluginCacheManager } from '@backstage/backend-common';
import { PreparerBuilder } from '@backstage/plugin-techdocs-node';
import { PublisherBase } from '@backstage/plugin-techdocs-node';
import type { TechDocsCollatorFactoryOptions as TechDocsCollatorFactoryOptions_2 } from '@backstage/plugin-search-backend-module-techdocs';
import { TechDocsDocument as TechDocsDocument_2 } from '@backstage/plugin-techdocs-node';
import { TokenManager } from '@backstage/backend-common';
import * as winston from 'winston';
// @public @deprecated
export function createRouter(options: RouterOptions): Promise<express.Router>;
// @public @deprecated
export class DefaultTechDocsCollator {
// (undocumented)
protected applyArgsToFormat(
format: string,
args: Record<string, string>,
): string;
// (undocumented)
execute(): Promise<TechDocsDocument_2[]>;
// (undocumented)
static fromConfig(
config: Config,
options: TechDocsCollatorOptions,
): DefaultTechDocsCollator;
// (undocumented)
readonly type: string;
// (undocumented)
readonly visibilityPermission: Permission;
}
// @public @deprecated (undocumented)
export const DefaultTechDocsCollatorFactory: typeof DefaultTechDocsCollatorFactory_2;
// @public @deprecated (undocumented)
export type DocsBuildStrategy = DocsBuildStrategy_2;
@@ -95,20 +67,6 @@ export type ShouldBuildParameters = {
entity: Entity;
};
// @public @deprecated (undocumented)
export type TechDocsCollatorFactoryOptions = TechDocsCollatorFactoryOptions_2;
// @public
export type TechDocsCollatorOptions = {
discovery: DiscoveryService;
logger: Logger;
tokenManager: TokenManager;
locationTemplate?: string;
catalogClient?: CatalogApi;
parallelismLimit?: number;
legacyPathCasing?: boolean;
};
// @public @deprecated (undocumented)
export type TechDocsDocument = TechDocsDocument_2;
-9
View File
@@ -34,15 +34,6 @@ export type {
OutOfTheBoxDeploymentOptions,
} from './service';
export {
DefaultTechDocsCollator,
DefaultTechDocsCollatorFactory,
} from './search';
export type {
TechDocsCollatorFactoryOptions,
TechDocsCollatorOptions,
} from './search';
/**
* @public
* @deprecated import from `@backstage/plugin-techdocs-node` instead
@@ -1,215 +0,0 @@
/*
* Copyright 2021 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 { TokenManager, loggerToWinstonLogger } from '@backstage/backend-common';
import { Entity } from '@backstage/catalog-model';
import { DefaultTechDocsCollator } from './DefaultTechDocsCollator';
import {
mockServices,
registerMswTestHooks,
} from '@backstage/backend-test-utils';
import { setupServer } from 'msw/node';
import { http, HttpResponse } from 'msw';
import { ConfigReader } from '@backstage/config';
import { TECHDOCS_ANNOTATION } from '@backstage/plugin-techdocs-common';
const logger = loggerToWinstonLogger(mockServices.logger.mock());
const mockSearchDocIndex = {
config: {
lang: ['en'],
min_search_length: 3,
prebuild_index: false,
separator: '[\\s\\-]+',
},
docs: [
{
location: '',
text: 'docs docs docs',
title: 'Home',
},
{
location: 'local-development/',
text: 'Docs for first subtitle',
title: 'Local development',
},
{
location: 'local-development/#development',
text: 'Docs for sub-subtitle',
title: 'Development',
},
],
};
const expectedEntities: Entity[] = [
{
apiVersion: 'backstage.io/v1alpha1',
kind: 'Component',
metadata: {
title: 'Test Entity with Docs!',
name: 'test-entity-with-docs',
description: 'Documented description',
annotations: {
[TECHDOCS_ANNOTATION]: './',
},
},
spec: {
type: 'dog',
lifecycle: 'experimental',
owner: 'someone',
},
},
];
describe('TechDocs Collator', () => {
const worker = setupServer();
registerMswTestHooks(worker);
describe('DefaultTechDocsCollator with legacyPathCasing configuration', () => {
const mockDiscoveryApi = mockServices.discovery.mock({
getBaseUrl: async () => 'http://test-backend',
});
let mockTokenManager: jest.Mocked<TokenManager>;
let collator: DefaultTechDocsCollator;
beforeEach(() => {
jest.clearAllMocks();
mockTokenManager = {
getToken: jest.fn().mockResolvedValue({ token: '' }),
authenticate: jest.fn(),
};
const mockConfig = new ConfigReader({
techdocs: {
legacyUseCaseSensitiveTripletPaths: true,
},
});
collator = DefaultTechDocsCollator.fromConfig(mockConfig, {
discovery: mockDiscoveryApi,
tokenManager: mockTokenManager,
logger,
legacyPathCasing: true,
});
worker.use(
http.get(
'http://test-backend/static/docs/default/Component/test-entity-with-docs/search/search_index.json',
() => HttpResponse.json(mockSearchDocIndex),
),
http.get('http://test-backend/entities', () =>
HttpResponse.json(expectedEntities),
),
);
});
it('fetches from the configured catalog and tech docs services', async () => {
const documents = await collator.execute();
expect(mockDiscoveryApi.getBaseUrl).toHaveBeenCalledWith('catalog');
expect(mockDiscoveryApi.getBaseUrl).toHaveBeenCalledWith('techdocs');
expect(documents).toHaveLength(mockSearchDocIndex.docs.length);
});
it('should create documents for each tech docs search index', async () => {
const documents = await collator.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,
name: entity.metadata.name,
});
});
});
});
describe('DefaultTechDocsCollator', () => {
const mockDiscoveryApi = mockServices.discovery.mock({
getBaseUrl: async () => 'http://test-backend',
});
let mockTokenManager: jest.Mocked<TokenManager>;
let collator: DefaultTechDocsCollator;
beforeEach(() => {
mockTokenManager = {
getToken: jest.fn().mockResolvedValue({ token: '' }),
authenticate: jest.fn(),
};
collator = DefaultTechDocsCollator.fromConfig(new ConfigReader({}), {
discovery: mockDiscoveryApi,
tokenManager: mockTokenManager,
logger,
});
worker.use(
http.get(
'http://test-backend/static/docs/default/component/test-entity-with-docs/search/search_index.json',
() => HttpResponse.json(mockSearchDocIndex),
),
http.get('http://test-backend/entities', () =>
HttpResponse.json(expectedEntities),
),
);
});
it('should create documents for each tech docs search index', async () => {
const documents = await collator.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.toLocaleLowerCase('en-US'),
authorization: {
resourceRef: `component:default/${entity.metadata.name}`,
},
});
});
});
it('maps a returned entity with a custom locationTemplate', async () => {
const mockConfig = new ConfigReader({
techdocs: {
legacyUseCaseSensitiveTripletPaths: true,
},
});
// Provide an alternate location template.
collator = DefaultTechDocsCollator.fromConfig(mockConfig, {
discovery: mockDiscoveryApi,
tokenManager: mockTokenManager,
locationTemplate: '/software/:name',
logger,
});
const documents = await collator.execute();
expect(documents[0]).toMatchObject({
location: '/software/test-entity-with-docs',
});
});
});
});
@@ -1,222 +0,0 @@
/*
* Copyright 2021 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 { TokenManager } from '@backstage/backend-common';
import {
Entity,
parseEntityRef,
RELATION_OWNED_BY,
stringifyEntityRef,
} from '@backstage/catalog-model';
import unescape from 'lodash/unescape';
import { Logger } from 'winston';
import pLimit from 'p-limit';
import { Config } from '@backstage/config';
import { catalogEntityReadPermission } from '@backstage/plugin-catalog-common/alpha';
import { Permission } from '@backstage/plugin-permission-common';
import {
CatalogApi,
CatalogClient,
CATALOG_FILTER_EXISTS,
} from '@backstage/catalog-client';
import { TechDocsDocument } from '@backstage/plugin-techdocs-node';
import { TECHDOCS_ANNOTATION } from '@backstage/plugin-techdocs-common';
import { DiscoveryService } from '@backstage/backend-plugin-api';
interface MkSearchIndexDoc {
title: string;
text: string;
location: string;
}
/**
* Options to configure the TechDocs collator
*
* @public
*/
export type TechDocsCollatorOptions = {
discovery: DiscoveryService;
logger: Logger;
tokenManager: TokenManager;
locationTemplate?: string;
catalogClient?: CatalogApi;
parallelismLimit?: number;
legacyPathCasing?: boolean;
};
type EntityInfo = {
name: string;
namespace: string;
kind: string;
};
/**
* A search collator responsible for gathering and transforming TechDocs documents.
*
* @public
* @deprecated Upgrade to a more recent `@backstage/plugin-search-backend-node` and
* use `DefaultTechDocsCollatorFactory` instead.
*/
export class DefaultTechDocsCollator {
public readonly type: string = 'techdocs';
public readonly visibilityPermission: Permission =
catalogEntityReadPermission;
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(legacyPathCasing, options);
}
async execute() {
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(
{
filter: {
[`metadata.annotations.${TECHDOCS_ANNOTATION}`]:
CATALOG_FILTER_EXISTS,
},
fields: [
'kind',
'namespace',
'metadata.annotations',
'metadata.name',
'metadata.title',
'metadata.namespace',
'spec.type',
'spec.lifecycle',
'relations',
],
},
{ token },
);
const docPromises = entities.items.map((entity: Entity) =>
limit(async (): Promise<TechDocsDocument[]> => {
const entityInfo = DefaultTechDocsCollator.handleEntityInfoCasing(
this.legacyPathCasing ?? false,
{
kind: entity.kind,
namespace: entity.metadata.namespace || 'default',
name: entity.metadata.name,
},
);
try {
const { token: newToken } = await tokenManager.getToken();
const searchIndexResponse = await fetch(
DefaultTechDocsCollator.constructDocsIndexUrl(
techDocsBaseUrl,
entityInfo,
),
{
headers: {
Authorization: `Bearer ${newToken}`,
},
},
);
const searchIndex = await searchIndexResponse.json();
return searchIndex.docs.map((doc: MkSearchIndexDoc) => ({
title: unescape(doc.title),
text: unescape(doc.text || ''),
location: this.applyArgsToFormat(
locationTemplate || '/docs/:namespace/:kind/:name/:path',
{
...entityInfo,
path: doc.location,
},
),
path: doc.location,
...entityInfo,
entityTitle: entity.metadata.title,
componentType: entity.spec?.type?.toString() || 'other',
lifecycle: (entity.spec?.lifecycle as string) || '',
owner: getSimpleEntityOwnerString(entity),
authorization: {
resourceRef: stringifyEntityRef(entity),
},
}));
} catch (e) {
logger.debug(
`Failed to retrieve tech docs search index for entity ${entityInfo.namespace}/${entityInfo.kind}/${entityInfo.name}`,
e,
);
return [];
}
}),
);
return (await Promise.all(docPromises)).flat();
}
protected 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;
}
private static constructDocsIndexUrl(
techDocsBaseUrl: string,
entityInfo: { kind: string; namespace: string; name: string },
) {
return `${techDocsBaseUrl}/static/docs/${entityInfo.namespace}/${entityInfo.kind}/${entityInfo.name}/search/search_index.json`;
}
private static handleEntityInfoCasing(
legacyPaths: boolean,
entityInfo: EntityInfo,
): EntityInfo {
return legacyPaths
? entityInfo
: Object.entries(entityInfo).reduce((acc, [key, value]) => {
return { ...acc, [key]: value.toLocaleLowerCase('en-US') };
}, {} as EntityInfo);
}
}
function getSimpleEntityOwnerString(entity: Entity): string {
if (entity.relations) {
const owner = entity.relations.find(r => r.type === RELATION_OWNED_BY);
if (owner) {
const { name } = parseEntityRef(owner.targetRef);
return name;
}
}
return '';
}
@@ -1,36 +0,0 @@
/*
* Copyright 2021 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.
*/
/**
* todo(backstage/techdocs-core): stop exporting these in a future release.
*/
export { DefaultTechDocsCollator } from './DefaultTechDocsCollator';
export type { TechDocsCollatorOptions } from './DefaultTechDocsCollator';
import { DefaultTechDocsCollatorFactory as _DefaultTechDocsCollatorFactory } from '@backstage/plugin-search-backend-module-techdocs';
import type { TechDocsCollatorFactoryOptions as _TechDocsCollatorFactoryOptions } from '@backstage/plugin-search-backend-module-techdocs';
/**
* @public
* @deprecated import from `@backstage/plugin-search-backend-module-techdocs` instead
*/
export type TechDocsCollatorFactoryOptions = _TechDocsCollatorFactoryOptions;
/**
* @public
* @deprecated import from `@backstage/plugin-search-backend-module-techdocs` instead
*/
export const DefaultTechDocsCollatorFactory = _DefaultTechDocsCollatorFactory;
-3
View File
@@ -8093,7 +8093,6 @@ __metadata:
resolution: "@backstage/plugin-search-backend-module-explore@workspace:plugins/search-backend-module-explore"
dependencies:
"@backstage-community/plugin-explore-common": ^0.0.7
"@backstage/backend-common": ^0.25.0
"@backstage/backend-plugin-api": "workspace:^"
"@backstage/backend-test-utils": "workspace:^"
"@backstage/cli": "workspace:^"
@@ -8139,7 +8138,6 @@ __metadata:
version: 0.0.0-use.local
resolution: "@backstage/plugin-search-backend-module-techdocs@workspace:plugins/search-backend-module-techdocs"
dependencies:
"@backstage/backend-common": ^0.25.0
"@backstage/backend-plugin-api": "workspace:^"
"@backstage/backend-test-utils": "workspace:^"
"@backstage/catalog-client": "workspace:^"
@@ -8183,7 +8181,6 @@ __metadata:
version: 0.0.0-use.local
resolution: "@backstage/plugin-search-backend@workspace:plugins/search-backend"
dependencies:
"@backstage/backend-common": ^0.25.0
"@backstage/backend-defaults": "workspace:^"
"@backstage/backend-openapi-utils": "workspace:^"
"@backstage/backend-plugin-api": "workspace:^"