diff --git a/.changeset/lazy-gorillas-tell.md b/.changeset/lazy-gorillas-tell.md new file mode 100644 index 0000000000..672d8c221b --- /dev/null +++ b/.changeset/lazy-gorillas-tell.md @@ -0,0 +1,7 @@ +--- +'@backstage/core-plugin-api': minor +--- + +Removed previously deprecated exports: `PluginHooks`, `PluginOutput`, and `FeatureFlagOutput`. + +The deprecated `register` method of `PluginConfig` has been removed, as well as the deprecated `output` method of `BackstagePlugin`. diff --git a/.changeset/rotten-files-wink.md b/.changeset/rotten-files-wink.md new file mode 100644 index 0000000000..65bd92bcc5 --- /dev/null +++ b/.changeset/rotten-files-wink.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-components': patch +--- + +Updated `ResponseErrorPanel` to not use the deprecated `data` property of `ResponseError`. diff --git a/.changeset/silver-mice-bake.md b/.changeset/silver-mice-bake.md new file mode 100644 index 0000000000..6cba2fdb0a --- /dev/null +++ b/.changeset/silver-mice-bake.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-app-api': minor +--- + +Removed previously deprecated `ApiRegistry` export. diff --git a/.changeset/violet-dingos-relate.md b/.changeset/violet-dingos-relate.md new file mode 100644 index 0000000000..c8b0c03e2c --- /dev/null +++ b/.changeset/violet-dingos-relate.md @@ -0,0 +1,7 @@ +--- +'@backstage/errors': minor +--- + +Removed the deprecated exports `ErrorResponse` and `parseErrorResponse`. + +Removed the deprecated `constructor` and the deprecated `data` property of `ResponseError`. diff --git a/packages/backend-common/src/middleware/errorHandler.ts b/packages/backend-common/src/middleware/errorHandler.ts index 47c9285b18..179d86e4c2 100644 --- a/packages/backend-common/src/middleware/errorHandler.ts +++ b/packages/backend-common/src/middleware/errorHandler.ts @@ -17,7 +17,7 @@ import { AuthenticationError, ConflictError, - ErrorResponse, + ErrorResponseBody, InputError, NotAllowedError, NotFoundError, @@ -89,7 +89,7 @@ export function errorHandler( return; } - const body: ErrorResponse = { + const body: ErrorResponseBody = { error: serializeError(error, { includeStack: showStackTraces }), request: { method: req.method, url: req.url }, response: { statusCode }, diff --git a/packages/core-app-api/api-report.md b/packages/core-app-api/api-report.md index f94979f975..7a6b515255 100644 --- a/packages/core-app-api/api-report.md +++ b/packages/core-app-api/api-report.md @@ -50,7 +50,6 @@ import { oktaAuthApiRef } from '@backstage/core-plugin-api'; import { oneloginAuthApiRef } from '@backstage/core-plugin-api'; import { OpenIdConnectApi } from '@backstage/core-plugin-api'; import { PendingOAuthRequest } from '@backstage/core-plugin-api'; -import { PluginOutput } from '@backstage/core-plugin-api'; import { ProfileInfo } from '@backstage/core-plugin-api'; import { ProfileInfoApi } from '@backstage/core-plugin-api'; import { PropsWithChildren } from 'react'; @@ -129,21 +128,6 @@ export type ApiProviderProps = { children: ReactNode; }; -// @public @deprecated -export class ApiRegistry implements ApiHolder { - constructor(apis: Map); - // Warning: (ae-forgotten-export) The symbol "ApiRegistryBuilder" needs to be exported by the entry point index.d.ts - // - // (undocumented) - static builder(): ApiRegistryBuilder; - // Warning: (ae-forgotten-export) The symbol "ApiImpl" needs to be exported by the entry point index.d.ts - static from(apis: ApiImpl[]): ApiRegistry; - // (undocumented) - get(api: ApiRef): T | undefined; - static with(api: ApiRef, impl: T): ApiRegistry; - with(api: ApiRef, impl: T): ApiRegistry; -} - // @public export class ApiResolver implements ApiHolder { constructor(factories: ApiFactoryHolder); @@ -208,14 +192,19 @@ export type AppOptions = { icons: AppIcons & { [key in string]: IconComponent; }; - plugins?: (Omit, 'output'> & { - output(): ( - | PluginOutput - | { - type: string; - } - )[]; - })[]; + plugins?: Array< + BackstagePlugin & { + output?(): Array< + | { + type: 'feature-flag'; + name: string; + } + | { + type: string; + } + >; + } + >; components: AppComponents; themes: (Partial & Omit)[]; configLoader?: AppConfigLoader; diff --git a/packages/core-app-api/src/apis/system/ApiRegistry.ts b/packages/core-app-api/src/apis/system/ApiRegistry.ts index e433381dd1..d4990a04c5 100644 --- a/packages/core-app-api/src/apis/system/ApiRegistry.ts +++ b/packages/core-app-api/src/apis/system/ApiRegistry.ts @@ -18,6 +18,7 @@ import { ApiRef, ApiHolder } from '@backstage/core-plugin-api'; type ApiImpl = readonly [ApiRef, T]; +/** @internal */ class ApiRegistryBuilder { private apis: [string, unknown][] = []; @@ -35,8 +36,7 @@ class ApiRegistryBuilder { /** * A registry for utility APIs. * - * @public - * @deprecated Will be removed, use {@link @backstage/test-utils#TestApiProvider} or {@link @backstage/test-utils#TestApiRegistry} instead. + * @internal */ export class ApiRegistry implements ApiHolder { static builder() { diff --git a/packages/core-app-api/src/apis/system/index.ts b/packages/core-app-api/src/apis/system/index.ts index 56c42f1e2a..3c0b715249 100644 --- a/packages/core-app-api/src/apis/system/index.ts +++ b/packages/core-app-api/src/apis/system/index.ts @@ -16,7 +16,6 @@ export { ApiProvider } from './ApiProvider'; export type { ApiProviderProps } from './ApiProvider'; -export { ApiRegistry } from './ApiRegistry'; export { ApiResolver } from './ApiResolver'; export { ApiFactoryRegistry } from './ApiFactoryRegistry'; export type { ApiFactoryScope } from './ApiFactoryRegistry'; diff --git a/packages/core-app-api/src/app/AppManager.test.tsx b/packages/core-app-api/src/app/AppManager.test.tsx index 20872b26f0..b09d424737 100644 --- a/packages/core-app-api/src/app/AppManager.test.tsx +++ b/packages/core-app-api/src/app/AppManager.test.tsx @@ -316,7 +316,7 @@ describe('Integration Test', () => { plugins: [ createPlugin({ id: 'test', - register: p => p.featureFlags.register('name'), + featureFlags: [{ name: 'name' }], }), ], components, @@ -384,8 +384,24 @@ describe('Integration Test', () => { name: 'foo', }, ], - register: p => p.featureFlags.register('name'), }), + // We still support consuming the old feature flag API for a little while longer + { + getId() { + return 'old-test'; + }, + getApis() { + return []; + }, + output() { + return [ + { + type: 'feature-flag', + name: 'old-feature-flag', + }, + ]; + }, + } as any, ], components, configLoader: async () => [], @@ -412,12 +428,12 @@ describe('Integration Test', () => { ); expect(storageFlags.registerFlag).toHaveBeenCalledWith({ - name: 'name', + name: 'foo', pluginId: 'test', }); expect(storageFlags.registerFlag).toHaveBeenCalledWith({ - name: 'foo', - pluginId: 'test', + name: 'old-feature-flag', + pluginId: 'old-test', }); }); diff --git a/packages/core-app-api/src/app/AppManager.tsx b/packages/core-app-api/src/app/AppManager.tsx index 7cdc06a094..ef5fe43b8b 100644 --- a/packages/core-app-api/src/app/AppManager.tsx +++ b/packages/core-app-api/src/app/AppManager.tsx @@ -27,7 +27,6 @@ import { Route, Routes } from 'react-router-dom'; import useAsync from 'react-use/lib/useAsync'; import { ApiProvider, - ApiRegistry, AppThemeSelector, ConfigReader, LocalStorageFeatureFlags, @@ -80,6 +79,13 @@ import { } from './types'; import { AppThemeProvider } from './AppThemeProvider'; import { defaultConfigLoader } from './defaultConfigLoader'; +import { ApiRegistry } from '../apis/system/ApiRegistry'; + +type CompatiblePlugin = + | BackstagePlugin + | (Omit, 'getFeatureFlags'> & { + output(): Array<{ type: 'feature-flag'; name: string }>; + }); export function generateBoundRoutes(bindRoutes: AppOptions['bindRoutes']) { const result = new Map(); @@ -149,7 +155,7 @@ function useConfigLoader( if (noConfigNode) { return { node: ( - + {noConfigNode} ), @@ -183,7 +189,7 @@ export class AppManager implements BackstageApp { private readonly apis: Iterable; private readonly icons: NonNullable; - private readonly plugins: Set>; + private readonly plugins: Set; private readonly components: AppComponents; private readonly themes: AppTheme[]; private readonly configLoader?: AppConfigLoader; @@ -196,9 +202,7 @@ export class AppManager implements BackstageApp { constructor(options: AppOptions) { this.apis = options.apis ?? []; this.icons = options.icons; - this.plugins = new Set( - (options.plugins as BackstagePlugin[]) ?? [], - ); + this.plugins = new Set((options.plugins as CompatiblePlugin[]) ?? []); this.components = options.components; this.themes = options.themes as AppTheme[]; this.configLoader = options.configLoader ?? defaultConfigLoader; @@ -208,7 +212,7 @@ export class AppManager implements BackstageApp { } getPlugins(): BackstagePlugin[] { - return Array.from(this.plugins); + return Array.from(this.plugins) as BackstagePlugin[]; } getSystemIcon(key: string): IconComponent | undefined { @@ -282,16 +286,11 @@ export class AppManager implements BackstageApp { } } else { for (const output of plugin.output()) { - switch (output.type) { - case 'feature-flag': { - featureFlagsApi.registerFlag({ - name: output.name, - pluginId: plugin.getId(), - }); - break; - } - default: - break; + if (output.type === 'feature-flag') { + featureFlagsApi.registerFlag({ + name: output.name, + pluginId: plugin.getId(), + }); } } } @@ -468,7 +467,7 @@ export class AppManager implements BackstageApp { return this.apiHolder; } - private verifyPlugins(plugins: Iterable) { + private verifyPlugins(plugins: Iterable) { const pluginIds = new Set(); for (const plugin of plugins) { diff --git a/packages/core-app-api/src/app/types.ts b/packages/core-app-api/src/app/types.ts index 4b8d2741cb..bacaf83fbd 100644 --- a/packages/core-app-api/src/app/types.ts +++ b/packages/core-app-api/src/app/types.ts @@ -24,7 +24,6 @@ import { RouteRef, SubRouteRef, ExternalRouteRef, - PluginOutput, IdentityApi, } from '@backstage/core-plugin-api'; import { AppConfig } from '@backstage/config'; @@ -232,9 +231,13 @@ export type AppOptions = { /** * A list of all plugins to include in the app. */ - plugins?: (Omit, 'output'> & { - output(): (PluginOutput | { type: string })[]; - })[]; + plugins?: Array< + BackstagePlugin & { + output?(): Array< + { type: 'feature-flag'; name: string } | { type: string } + >; // support for old plugins + } + >; /** * Supply components to the app to override the default ones. diff --git a/packages/core-components/src/components/ResponseErrorPanel/ResponseErrorPanel.tsx b/packages/core-components/src/components/ResponseErrorPanel/ResponseErrorPanel.tsx index 68d7360400..9600a3f179 100644 --- a/packages/core-components/src/components/ResponseErrorPanel/ResponseErrorPanel.tsx +++ b/packages/core-components/src/components/ResponseErrorPanel/ResponseErrorPanel.tsx @@ -61,14 +61,14 @@ export function ResponseErrorPanel(props: ErrorPanelProps) { ); } - const { data, cause } = error as ResponseError; - const { request, response } = data; + const { body, cause } = error as ResponseError; + const { request, response } = body; const errorString = `${response.statusCode}: ${cause.name}`; const requestString = request && `${request.method} ${request.url}`; const messageString = cause.message.replace(/\\n/g, '\n'); const stackString = cause.stack?.replace(/\\n/g, '\n'); - const jsonString = JSON.stringify(data, undefined, 2); + const jsonString = JSON.stringify(body, undefined, 2); return ( = { getId(): string; - output(): PluginOutput[]; getApis(): Iterable; getFeatureFlags(): Iterable; provide(extension: Extension): T; @@ -446,12 +445,6 @@ export type FeatureFlag = { pluginId: string; }; -// @public @deprecated -export type FeatureFlagOutput = { - type: 'feature-flag'; - name: string; -}; - // @public export interface FeatureFlagsApi { getRegisteredFlags(): FeatureFlag[]; @@ -687,7 +680,6 @@ export type PluginConfig< > = { id: string; apis?: Iterable; - register?(hooks: PluginHooks): void; routes?: Routes; externalRoutes?: ExternalRoutes; featureFlags?: PluginFeatureFlagConfig[]; @@ -698,14 +690,6 @@ export type PluginFeatureFlagConfig = { name: string; }; -// @public @deprecated -export type PluginHooks = { - featureFlags: FeatureFlagsHooks; -}; - -// @public @deprecated -export type PluginOutput = FeatureFlagOutput; - // @public export type ProfileInfo = { email?: string; diff --git a/packages/core-plugin-api/src/plugin/Plugin.test.tsx b/packages/core-plugin-api/src/plugin/Plugin.test.tsx index d3758543a1..b66235e210 100644 --- a/packages/core-plugin-api/src/plugin/Plugin.test.tsx +++ b/packages/core-plugin-api/src/plugin/Plugin.test.tsx @@ -28,62 +28,7 @@ describe('Plugin Feature Flag', () => { expect( createPlugin({ id: 'test', - register({ featureFlags }) { - featureFlags.register('blob'); - }, }).getFeatureFlags(), - ).toEqual([{ name: 'blob' }]); - - expect( - createPlugin({ - id: 'test', - register({ featureFlags }) { - featureFlags.register('blob'); - }, - featureFlags: [{ name: 'test' }], - }).getFeatureFlags(), - ).toEqual([{ name: 'test' }, { name: 'blob' }]); - - expect( - createPlugin({ - id: 'test', - }).getFeatureFlags(), - ).toEqual([]); - - /* deprecated tests */ - - expect( - createPlugin({ - id: 'test', - featureFlags: [{ name: 'test' }], - }).output(), - ).toEqual([{ name: 'test', type: 'feature-flag' }]); - - expect( - createPlugin({ - id: 'test', - register({ featureFlags }) { - featureFlags.register('blob'); - }, - }).output(), - ).toEqual([{ name: 'blob', type: 'feature-flag' }]); - expect( - createPlugin({ - id: 'test', - register({ featureFlags }) { - featureFlags.register('blob'); - }, - featureFlags: [{ name: 'test' }], - }).output(), - ).toEqual([ - { name: 'test', type: 'feature-flag' }, - { name: 'blob', type: 'feature-flag' }, - ]); - - expect( - createPlugin({ - id: 'test', - }).output(), ).toEqual([]); }); }); diff --git a/packages/core-plugin-api/src/plugin/Plugin.tsx b/packages/core-plugin-api/src/plugin/Plugin.tsx index 0fb7574cd7..d97554b27b 100644 --- a/packages/core-plugin-api/src/plugin/Plugin.tsx +++ b/packages/core-plugin-api/src/plugin/Plugin.tsx @@ -16,7 +16,6 @@ import { PluginConfig, - PluginOutput, BackstagePlugin, Extension, AnyRoutes, @@ -33,8 +32,6 @@ export class PluginImpl< ExternalRoutes extends AnyExternalRoutes, > implements BackstagePlugin { - private storedOutput?: PluginOutput[]; - constructor(private readonly config: PluginConfig) {} getId(): string { @@ -46,11 +43,7 @@ export class PluginImpl< } getFeatureFlags(): Iterable { - const registeredFlags = this.output() - .filter(({ type }) => type === 'feature-flag') - .map(({ name }) => ({ name })); - - return registeredFlags; + return this.config.featureFlags?.slice() ?? []; } get routes(): Routes { @@ -61,34 +54,6 @@ export class PluginImpl< return this.config.externalRoutes ?? ({} as ExternalRoutes); } - output(): PluginOutput[] { - if (this.storedOutput) { - return this.storedOutput; - } - const outputs = new Array(); - this.storedOutput = outputs; - - if (this.config.featureFlags) { - for (const flag of this.config.featureFlags) { - outputs.push({ type: 'feature-flag', name: flag.name }); - } - } - - if (!this.config.register) { - return outputs; - } - - this.config.register({ - featureFlags: { - register(name) { - outputs.push({ type: 'feature-flag', name }); - }, - }, - }); - - return this.storedOutput; - } - provide(extension: Extension): T { return extension.expose(this); } diff --git a/packages/core-plugin-api/src/plugin/index.ts b/packages/core-plugin-api/src/plugin/index.ts index 4f7609fd9c..95f5a6b219 100644 --- a/packages/core-plugin-api/src/plugin/index.ts +++ b/packages/core-plugin-api/src/plugin/index.ts @@ -20,10 +20,7 @@ export type { AnyRoutes, BackstagePlugin, Extension, - FeatureFlagOutput, FeatureFlagsHooks, PluginConfig, - PluginHooks, - PluginOutput, PluginFeatureFlagConfig, } from './types'; diff --git a/packages/core-plugin-api/src/plugin/types.ts b/packages/core-plugin-api/src/plugin/types.ts index 0f8d3404d8..d639327838 100644 --- a/packages/core-plugin-api/src/plugin/types.ts +++ b/packages/core-plugin-api/src/plugin/types.ts @@ -17,24 +17,6 @@ import { RouteRef, SubRouteRef, ExternalRouteRef } from '../routing'; import { AnyApiFactory } from '../apis/system'; -/** - * Replace with using {@link RouteRef}s. - * @deprecated will be removed - * @public - */ -export type FeatureFlagOutput = { - type: 'feature-flag'; - name: string; -}; - -/** - * {@link FeatureFlagOutput} type. - * - * @public - * @deprecated Use {@link BackstagePlugin.getFeatureFlags} instead. - */ -export type PluginOutput = FeatureFlagOutput; - /** * Plugin extension type. * @@ -72,10 +54,6 @@ export type BackstagePlugin< ExternalRoutes extends AnyExternalRoutes = {}, > = { getId(): string; - /** - * @deprecated use getFeatureFlags instead. - * */ - output(): PluginOutput[]; getApis(): Iterable; /** * Returns all registered feature flags for this plugin. @@ -107,23 +85,11 @@ export type PluginConfig< > = { id: string; apis?: Iterable; - /** @deprecated use featureFlags property instead for defining feature flags */ - register?(hooks: PluginHooks): void; routes?: Routes; externalRoutes?: ExternalRoutes; featureFlags?: PluginFeatureFlagConfig[]; }; -/** - * Holds hooks registered by the plugin. - * - * @deprecated - feature flags are now registered in plugin config under featureFlags - * @public - */ -export type PluginHooks = { - featureFlags: FeatureFlagsHooks; -}; - /** * Interface for registering feature flags hooks. * diff --git a/packages/errors/api-report.md b/packages/errors/api-report.md index 97b011bad6..a01581135b 100644 --- a/packages/errors/api-report.md +++ b/packages/errors/api-report.md @@ -33,9 +33,6 @@ export type ErrorLike = { [unknownKeys: string]: unknown; }; -// @public @deprecated -export type ErrorResponse = ErrorResponseBody; - // @public export type ErrorResponseBody = { error: SerializedError; @@ -68,9 +65,6 @@ export class NotFoundError extends CustomErrorBase {} // @public export class NotModifiedError extends CustomErrorBase {} -// @public @deprecated -export function parseErrorResponse(response: Response): Promise; - // @public export function parseErrorResponseBody( response: Response, @@ -78,17 +72,8 @@ export function parseErrorResponseBody( // @public export class ResponseError extends Error { - // @deprecated - constructor(props: { - message: string; - response: Response; - data: ErrorResponseBody; - cause: Error; - }); readonly body: ErrorResponseBody; readonly cause: Error; - // @deprecated - get data(): ErrorResponseBody; static fromResponse(response: Response): Promise; readonly response: Response; } diff --git a/packages/errors/src/errors/ResponseError.test.ts b/packages/errors/src/errors/ResponseError.test.ts index 52a308dbb8..94f2e850d9 100644 --- a/packages/errors/src/errors/ResponseError.test.ts +++ b/packages/errors/src/errors/ResponseError.test.ts @@ -14,12 +14,12 @@ * limitations under the License. */ -import { ErrorResponse } from '../serialization'; +import { ErrorResponseBody } from '../serialization'; import { ResponseError } from './ResponseError'; describe('ResponseError', () => { it('constructs itself from a response', async () => { - const body: ErrorResponse = { + const body: ErrorResponseBody = { error: { name: 'Fours', message: 'Expected fives', stack: 'lines' }, request: { method: 'GET', url: '/' }, response: { statusCode: 444 }, diff --git a/packages/errors/src/errors/ResponseError.ts b/packages/errors/src/errors/ResponseError.ts index a20035cd8c..84c8137bb5 100644 --- a/packages/errors/src/errors/ResponseError.ts +++ b/packages/errors/src/errors/ResponseError.ts @@ -75,10 +75,7 @@ export class ResponseError extends Error { }); } - /** - * @deprecated will be removed. - **/ - constructor(props: { + private constructor(props: { message: string; response: Response; data: ErrorResponseBody; @@ -90,13 +87,4 @@ export class ResponseError extends Error { this.body = props.data; this.cause = props.cause; } - /** - * The parsed JSON error body, as sent by the server. - * @deprecated use body instead. - */ - get data(): ErrorResponseBody { - // eslint-disable-next-line no-console - console.warn('ErrorResponse.data is deprecated, use .body instead.'); - return this.body; - } } diff --git a/packages/errors/src/serialization/index.ts b/packages/errors/src/serialization/index.ts index b86661d496..b03514f749 100644 --- a/packages/errors/src/serialization/index.ts +++ b/packages/errors/src/serialization/index.ts @@ -16,5 +16,5 @@ export { deserializeError, serializeError, stringifyError } from './error'; export type { SerializedError } from './error'; -export { parseErrorResponse, parseErrorResponseBody } from './response'; -export type { ErrorResponse, ErrorResponseBody } from './response'; +export { parseErrorResponseBody } from './response'; +export type { ErrorResponseBody } from './response'; diff --git a/packages/errors/src/serialization/response.ts b/packages/errors/src/serialization/response.ts index 4a1731646d..3580220525 100644 --- a/packages/errors/src/serialization/response.ts +++ b/packages/errors/src/serialization/response.ts @@ -16,14 +16,6 @@ import { SerializedError } from './error'; -/** - * A standard shape of JSON data returned as the body of backend errors. - * - * @public - * @deprecated - Use {@link ErrorResponseBody} instead. - */ -export type ErrorResponse = ErrorResponseBody; - /** * A standard shape of JSON data returned as the body of backend errors. * @@ -48,25 +40,6 @@ export type ErrorResponseBody = { }; }; -/** - * Attempts to construct an ErrorResponse out of a failed server request. - * Assumes that the response has already been checked to be not ok. This - * function consumes the body of the response, and assumes that it hasn't - * been consumed before. - * - * The code is forgiving, and constructs a useful synthetic body as best it can - * if the response body wasn't on the expected form. - * - * @public - * @param response - The response of a failed request - * @deprecated - Use {@link parseErrorResponseBody} instead. - */ -export async function parseErrorResponse( - response: Response, -): Promise { - return parseErrorResponseBody(response); -} - /** * Attempts to construct an ErrorResponseBody out of a failed server request. * Assumes that the response has already been checked to be not ok. This