From 5fc53cb29861d8b414a3f1a8d3c0fb42918be4a2 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sat, 22 Nov 2025 17:26:48 +0100 Subject: [PATCH] {core,frontend}-plugin-api: inverse dependency Signed-off-by: Patrik Oldsberg --- .../TranslationApi/I18nextTranslationApi.ts | 4 +- packages/core-plugin-api/package.json | 2 +- packages/core-plugin-api/src/alpha.ts | 25 +- .../src/analytics/useAnalytics.test.tsx | 7 +- .../src/analytics/useAnalytics.tsx | 8 +- packages/core-plugin-api/src/apis/alpha.ts | 16 - .../src/apis/definitions/AlertApi.ts | 45 +- .../src/apis/definitions/AnalyticsApi.ts | 2 +- .../src/apis/definitions/AppLanguageApi.ts | 36 -- .../src/apis/definitions/AppThemeApi.ts | 76 +-- .../src/apis/definitions/ConfigApi.ts | 19 +- .../src/apis/definitions/DiscoveryApi.ts | 43 +- .../src/apis/definitions/ErrorApi.ts | 81 +-- .../src/apis/definitions/FeatureFlagsApi.ts | 117 +--- .../src/apis/definitions/FetchApi.ts | 36 +- .../src/apis/definitions/IdentityApi.ts | 44 +- .../src/apis/definitions/OAuthRequestApi.ts | 122 +---- .../src/apis/definitions/StorageApi.ts | 99 +--- .../src/apis/definitions/alpha.ts | 22 - .../src/apis/definitions/auth.ts | 515 +---------------- .../core-plugin-api/src/apis/system/ApiRef.ts | 50 +- .../src/apis/system/helpers.ts | 59 +- .../core-plugin-api/src/apis/system/types.ts | 66 +-- .../src/apis/system/useApi.tsx | 79 +-- packages/core-plugin-api/src/icons/types.ts | 22 +- .../core-plugin-api/src/translation/index.ts | 32 -- packages/frontend-plugin-api/package.json | 18 +- .../src/analytics/useAnalytics.test.tsx | 2 +- .../src/analytics/useAnalytics.tsx | 2 +- .../src/apis/definitions/AlertApi.ts | 45 +- .../src/apis/definitions/AnalyticsApi.ts | 3 +- .../src/apis/definitions/AppLanguageApi.ts | 24 +- .../src/apis/definitions/AppThemeApi.ts | 76 ++- .../src/apis/definitions/AppTreeApi.ts | 2 +- .../src/apis/definitions/ConfigApi.ts | 19 +- .../src/apis/definitions/DialogApi.ts | 2 +- .../src/apis/definitions/DiscoveryApi.ts | 40 +- .../src/apis/definitions/ErrorApi.ts | 81 ++- .../src/apis/definitions/FeatureFlagsApi.ts | 117 +++- .../src/apis/definitions/FetchApi.ts | 36 +- .../src/apis/definitions/IconsApi.ts | 2 +- .../src/apis/definitions/IdentityApi.ts | 41 +- .../src/apis/definitions/OAuthRequestApi.ts | 122 ++++- .../apis/definitions/RouteResolutionApi.ts | 2 +- .../src/apis/definitions/StorageApi.ts | 99 +++- .../definitions/SwappableComponentsApi.ts | 2 +- .../apis/definitions/TranslationApi.test.tsx | 0 .../src/apis/definitions/TranslationApi.ts | 12 +- .../src/apis/definitions/alpha.ts | 17 - .../src/apis/definitions/auth.ts | 517 +++++++++++++++++- .../src/apis/definitions/index.ts | 2 + .../src/apis/system/ApiRef.test.ts | 50 ++ .../src/apis/system/ApiRef.ts | 53 +- .../src/apis/system/helpers.ts | 61 ++- .../src/apis/system/index.ts | 2 +- .../src/apis/system/types.ts | 68 ++- .../src/apis/system/useApi.test.tsx | 40 ++ .../src/apis/system/useApi.tsx | 81 ++- .../src/blueprints/ApiBlueprint.test.ts | 2 +- .../src/blueprints/NavItemBlueprint.ts | 2 +- .../src/blueprints/ThemeBlueprint.test.ts | 2 +- .../src/blueprints/ThemeBlueprint.ts | 2 +- .../src/components/ExtensionBoundary.test.tsx | 13 +- .../src/components/ExtensionBoundary.tsx | 2 +- .../src/components/ExtensionSuspense.test.tsx | 2 +- .../src/components/ExtensionSuspense.tsx | 5 +- .../translation/TranslationMessages.test.ts | 0 .../src/translation/TranslationMessages.ts | 8 +- .../src/translation/TranslationRef.test.ts | 0 .../src/translation/TranslationRef.ts | 6 +- .../translation/TranslationResource.test.ts | 0 .../src/translation/TranslationResource.ts | 12 +- .../translation/__fixtures__/counting-de.ts | 0 .../translation/__fixtures__/counting-sv.json | 0 .../translation/__fixtures__/fruits-de.json | 0 .../src/translation/__fixtures__/fruits-sv.ts | 0 .../src/translation/__fixtures__/refs.ts | 0 .../src/translation/index.ts | 12 +- .../translation/useTranslationRef.test.tsx | 4 +- .../src/translation/useTranslationRef.ts | 4 +- .../apis/TranslationApi/MockTranslationApi.ts | 2 +- 81 files changed, 1669 insertions(+), 1674 deletions(-) delete mode 100644 packages/core-plugin-api/src/apis/alpha.ts delete mode 100644 packages/core-plugin-api/src/apis/definitions/AppLanguageApi.ts delete mode 100644 packages/core-plugin-api/src/apis/definitions/alpha.ts delete mode 100644 packages/core-plugin-api/src/translation/index.ts rename packages/{core-plugin-api => frontend-plugin-api}/src/apis/definitions/TranslationApi.test.tsx (100%) rename packages/{core-plugin-api => frontend-plugin-api}/src/apis/definitions/TranslationApi.ts (98%) delete mode 100644 packages/frontend-plugin-api/src/apis/definitions/alpha.ts create mode 100644 packages/frontend-plugin-api/src/apis/system/ApiRef.test.ts create mode 100644 packages/frontend-plugin-api/src/apis/system/useApi.test.tsx rename packages/{core-plugin-api => frontend-plugin-api}/src/translation/TranslationMessages.test.ts (100%) rename packages/{core-plugin-api => frontend-plugin-api}/src/translation/TranslationMessages.ts (95%) rename packages/{core-plugin-api => frontend-plugin-api}/src/translation/TranslationRef.test.ts (100%) rename packages/{core-plugin-api => frontend-plugin-api}/src/translation/TranslationRef.ts (99%) rename packages/{core-plugin-api => frontend-plugin-api}/src/translation/TranslationResource.test.ts (100%) rename packages/{core-plugin-api => frontend-plugin-api}/src/translation/TranslationResource.ts (94%) rename packages/{core-plugin-api => frontend-plugin-api}/src/translation/__fixtures__/counting-de.ts (100%) rename packages/{core-plugin-api => frontend-plugin-api}/src/translation/__fixtures__/counting-sv.json (100%) rename packages/{core-plugin-api => frontend-plugin-api}/src/translation/__fixtures__/fruits-de.json (100%) rename packages/{core-plugin-api => frontend-plugin-api}/src/translation/__fixtures__/fruits-sv.ts (100%) rename packages/{core-plugin-api => frontend-plugin-api}/src/translation/__fixtures__/refs.ts (100%) rename packages/{core-plugin-api => frontend-plugin-api}/src/translation/useTranslationRef.test.tsx (98%) rename packages/{core-plugin-api => frontend-plugin-api}/src/translation/useTranslationRef.ts (98%) diff --git a/packages/core-app-api/src/apis/implementations/TranslationApi/I18nextTranslationApi.ts b/packages/core-app-api/src/apis/implementations/TranslationApi/I18nextTranslationApi.ts index de21990cb3..2d4bb82668 100644 --- a/packages/core-app-api/src/apis/implementations/TranslationApi/I18nextTranslationApi.ts +++ b/packages/core-app-api/src/apis/implementations/TranslationApi/I18nextTranslationApi.ts @@ -37,12 +37,12 @@ import ObservableImpl from 'zen-observable'; import { toInternalTranslationResource, InternalTranslationResourceLoader, -} from '../../../../../core-plugin-api/src/translation/TranslationResource'; +} from '../../../../../frontend-plugin-api/src/translation/TranslationResource'; // eslint-disable-next-line @backstage/no-relative-monorepo-imports import { toInternalTranslationRef, InternalTranslationRef, -} from '../../../../../core-plugin-api/src/translation/TranslationRef'; +} from '../../../../../frontend-plugin-api/src/translation/TranslationRef'; import { Observable } from '@backstage/types'; import { DEFAULT_LANGUAGE } from '../AppLanguageApi/AppLanguageSelector'; import { createElement, Fragment, ReactNode, isValidElement } from 'react'; diff --git a/packages/core-plugin-api/package.json b/packages/core-plugin-api/package.json index 877abb6114..a13d2171d3 100644 --- a/packages/core-plugin-api/package.json +++ b/packages/core-plugin-api/package.json @@ -51,6 +51,7 @@ "dependencies": { "@backstage/config": "workspace:^", "@backstage/errors": "workspace:^", + "@backstage/frontend-plugin-api": "workspace:^", "@backstage/types": "workspace:^", "@backstage/version-bridge": "workspace:^", "history": "^5.0.0", @@ -59,7 +60,6 @@ "devDependencies": { "@backstage/cli": "workspace:^", "@backstage/core-app-api": "workspace:^", - "@backstage/frontend-plugin-api": "workspace:^", "@backstage/test-utils": "workspace:^", "@testing-library/dom": "^10.0.0", "@testing-library/jest-dom": "^6.0.0", diff --git a/packages/core-plugin-api/src/alpha.ts b/packages/core-plugin-api/src/alpha.ts index ebcbcf34c0..f823b909b1 100644 --- a/packages/core-plugin-api/src/alpha.ts +++ b/packages/core-plugin-api/src/alpha.ts @@ -14,5 +14,26 @@ * limitations under the License. */ -export * from './translation'; -export * from './apis/alpha'; +// Translation exports +export { + type TranslationMessages, + type TranslationMessagesOptions, + createTranslationMessages, + type TranslationResource, + type TranslationResourceOptions, + createTranslationResource, + type TranslationRef, + type TranslationRefOptions, + createTranslationRef, + useTranslationRef, +} from '@backstage/frontend-plugin-api'; + +// API definition exports +export { + appLanguageApiRef, + type AppLanguageApi, + translationApiRef, + type TranslationApi, + type TranslationFunction, + type TranslationSnapshot, +} from '@backstage/frontend-plugin-api'; diff --git a/packages/core-plugin-api/src/analytics/useAnalytics.test.tsx b/packages/core-plugin-api/src/analytics/useAnalytics.test.tsx index 037e585430..45483ecb83 100644 --- a/packages/core-plugin-api/src/analytics/useAnalytics.test.tsx +++ b/packages/core-plugin-api/src/analytics/useAnalytics.test.tsx @@ -16,9 +16,12 @@ import { renderHook } from '@testing-library/react'; import { useAnalytics } from './useAnalytics'; -import { useApi } from '../apis'; +import { useApi } from '@backstage/frontend-plugin-api'; -jest.mock('../apis'); +jest.mock('@backstage/frontend-plugin-api', () => ({ + ...jest.requireActual('@backstage/frontend-plugin-api'), + useApi: jest.fn(), +})); const mocked = (f: Function) => f as jest.Mock; diff --git a/packages/core-plugin-api/src/analytics/useAnalytics.tsx b/packages/core-plugin-api/src/analytics/useAnalytics.tsx index 50cec8f790..6297f81aa2 100644 --- a/packages/core-plugin-api/src/analytics/useAnalytics.tsx +++ b/packages/core-plugin-api/src/analytics/useAnalytics.tsx @@ -15,12 +15,8 @@ */ import { useAnalyticsContext } from './AnalyticsContext'; -import { - analyticsApiRef, - AnalyticsTracker, - AnalyticsApi, - useApi, -} from '../apis'; +import { analyticsApiRef, AnalyticsTracker, AnalyticsApi } from '../apis'; +import { useApi } from '@backstage/frontend-plugin-api'; import { useRef } from 'react'; import { Tracker } from './Tracker'; diff --git a/packages/core-plugin-api/src/apis/alpha.ts b/packages/core-plugin-api/src/apis/alpha.ts deleted file mode 100644 index b22d3df574..0000000000 --- a/packages/core-plugin-api/src/apis/alpha.ts +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Copyright 2023 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. - */ -export * from './definitions/alpha'; diff --git a/packages/core-plugin-api/src/apis/definitions/AlertApi.ts b/packages/core-plugin-api/src/apis/definitions/AlertApi.ts index afdcb6a617..a451195476 100644 --- a/packages/core-plugin-api/src/apis/definitions/AlertApi.ts +++ b/packages/core-plugin-api/src/apis/definitions/AlertApi.ts @@ -14,43 +14,8 @@ * limitations under the License. */ -import { createApiRef, ApiRef } from '../system'; -import { Observable } from '@backstage/types'; - -/** - * Message handled by the {@link AlertApi}. - * - * @public - */ -export type AlertMessage = { - message: string; - // Severity will default to success since that is what material ui defaults the value to. - severity?: 'success' | 'info' | 'warning' | 'error'; - display?: 'permanent' | 'transient'; -}; - -/** - * The alert API is used to report alerts to the app, and display them to the user. - * - * @public - */ -export type AlertApi = { - /** - * Post an alert for handling by the application. - */ - post(alert: AlertMessage): void; - - /** - * Observe alerts posted by other parts of the application. - */ - alert$(): Observable; -}; - -/** - * The {@link ApiRef} of {@link AlertApi}. - * - * @public - */ -export const alertApiRef: ApiRef = createApiRef({ - id: 'core.alert', -}); +export { + type AlertApi, + type AlertMessage, + alertApiRef, +} from '@backstage/frontend-plugin-api'; diff --git a/packages/core-plugin-api/src/apis/definitions/AnalyticsApi.ts b/packages/core-plugin-api/src/apis/definitions/AnalyticsApi.ts index ef190d6c21..c6f4eadd05 100644 --- a/packages/core-plugin-api/src/apis/definitions/AnalyticsApi.ts +++ b/packages/core-plugin-api/src/apis/definitions/AnalyticsApi.ts @@ -122,7 +122,7 @@ export type AnalyticsApi = { }; /** - * The {@link ApiRef} of {@link AnalyticsApi}. + * The `ApiRef` of {@link AnalyticsApi}. * * @public */ diff --git a/packages/core-plugin-api/src/apis/definitions/AppLanguageApi.ts b/packages/core-plugin-api/src/apis/definitions/AppLanguageApi.ts deleted file mode 100644 index 1207e84755..0000000000 --- a/packages/core-plugin-api/src/apis/definitions/AppLanguageApi.ts +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2023 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 { ApiRef, createApiRef } from '@backstage/core-plugin-api'; -import { Observable } from '@backstage/types'; - -/** @alpha */ -export type AppLanguageApi = { - getAvailableLanguages(): { languages: string[] }; - - setLanguage(language?: string): void; - - getLanguage(): { language: string }; - - language$(): Observable<{ language: string }>; -}; - -/** - * @alpha - */ -export const appLanguageApiRef: ApiRef = createApiRef({ - id: 'core.applanguage', -}); diff --git a/packages/core-plugin-api/src/apis/definitions/AppThemeApi.ts b/packages/core-plugin-api/src/apis/definitions/AppThemeApi.ts index e771fad597..2ad399d301 100644 --- a/packages/core-plugin-api/src/apis/definitions/AppThemeApi.ts +++ b/packages/core-plugin-api/src/apis/definitions/AppThemeApi.ts @@ -14,74 +14,8 @@ * limitations under the License. */ -import { ReactNode } from 'react'; -import { ApiRef, createApiRef } from '../system'; -import { Observable } from '@backstage/types'; - -/** - * Describes a theme provided by the app. - * - * @public - */ -export type AppTheme = { - /** - * ID used to remember theme selections. - */ - id: string; - - /** - * Title of the theme - */ - title: string; - - /** - * Theme variant - */ - variant: 'light' | 'dark'; - - /** - * An Icon for the theme mode setting. - */ - icon?: React.ReactElement; - - Provider(props: { children: ReactNode }): JSX.Element | null; -}; - -/** - * The AppThemeApi gives access to the current app theme, and allows switching - * to other options that have been registered as a part of the App. - * - * @public - */ -export type AppThemeApi = { - /** - * Get a list of available themes. - */ - getInstalledThemes(): AppTheme[]; - - /** - * Observe the currently selected theme. A value of undefined means no specific theme has been selected. - */ - activeThemeId$(): Observable; - - /** - * Get the current theme ID. Returns undefined if no specific theme is selected. - */ - getActiveThemeId(): string | undefined; - - /** - * Set a specific theme to use in the app, overriding the default theme selection. - * - * Clear the selection by passing in undefined. - */ - setActiveThemeId(themeId?: string): void; -}; - -/** - * The {@link ApiRef} of {@link AppThemeApi}. - * - * @public - */ -export const appThemeApiRef: ApiRef = createApiRef({ - id: 'core.apptheme', -}); +export { + type AppTheme, + type AppThemeApi, + appThemeApiRef, +} from '@backstage/frontend-plugin-api'; diff --git a/packages/core-plugin-api/src/apis/definitions/ConfigApi.ts b/packages/core-plugin-api/src/apis/definitions/ConfigApi.ts index d3bada9e46..e5e8b4559c 100644 --- a/packages/core-plugin-api/src/apis/definitions/ConfigApi.ts +++ b/packages/core-plugin-api/src/apis/definitions/ConfigApi.ts @@ -13,22 +13,5 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { ApiRef, createApiRef } from '../system'; -import { Config } from '@backstage/config'; -/** - * The Config API is used to provide a mechanism to access the - * runtime configuration of the system. - * - * @public - */ -export type ConfigApi = Config; - -/** - * The {@link ApiRef} of {@link ConfigApi}. - * - * @public - */ -export const configApiRef: ApiRef = createApiRef({ - id: 'core.config', -}); +export { type ConfigApi, configApiRef } from '@backstage/frontend-plugin-api'; diff --git a/packages/core-plugin-api/src/apis/definitions/DiscoveryApi.ts b/packages/core-plugin-api/src/apis/definitions/DiscoveryApi.ts index d23fe3db6c..20c8639340 100644 --- a/packages/core-plugin-api/src/apis/definitions/DiscoveryApi.ts +++ b/packages/core-plugin-api/src/apis/definitions/DiscoveryApi.ts @@ -13,43 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { ApiRef, createApiRef } from '../system'; -/** - * The discovery API is used to provide a mechanism for plugins to - * discover the endpoint to use to talk to their backend counterpart. - * - * @remarks - * - * The purpose of the discovery API is to allow for many different deployment - * setups and routing methods through a central configuration, instead - * of letting each individual plugin manage that configuration. - * - * Implementations of the discovery API can be a simple as a URL pattern - * using the pluginId, but could also have overrides for individual plugins, - * or query a separate discovery service. - * - * @public - */ -export type DiscoveryApi = { - /** - * Returns the HTTP base backend URL for a given plugin, without a trailing slash. - * - * This method must always be called just before making a request, as opposed to - * fetching the URL when constructing an API client. That is to ensure that more - * flexible routing patterns can be supported. - * - * For example, asking for the URL for `auth` may return something - * like `https://backstage.example.com/api/auth` - */ - getBaseUrl(pluginId: string): Promise; -}; - -/** - * The {@link ApiRef} of {@link DiscoveryApi}. - * - * @public - */ -export const discoveryApiRef: ApiRef = createApiRef({ - id: 'core.discovery', -}); +export { + type DiscoveryApi, + discoveryApiRef, +} from '@backstage/frontend-plugin-api'; diff --git a/packages/core-plugin-api/src/apis/definitions/ErrorApi.ts b/packages/core-plugin-api/src/apis/definitions/ErrorApi.ts index 9c73d94cac..8f3f9632eb 100644 --- a/packages/core-plugin-api/src/apis/definitions/ErrorApi.ts +++ b/packages/core-plugin-api/src/apis/definitions/ErrorApi.ts @@ -14,78 +14,9 @@ * limitations under the License. */ -import { ApiRef, createApiRef } from '../system'; -import { Observable } from '@backstage/types'; - -/** - * Mirrors the JavaScript Error class, for the purpose of - * providing documentation and optional fields. - * - * @public - */ -export type ErrorApiError = { - name: string; - message: string; - stack?: string; -}; - -/** - * Provides additional information about an error that was posted to the application. - * - * @public - */ -export type ErrorApiErrorContext = { - /** - * If set to true, this error should not be displayed to the user. - * - * Hidden errors are typically not displayed in the UI, but the ErrorApi - * implementation may still report them to error tracking services - * or other utilities that care about all errors. - * - * @defaultValue false - */ - hidden?: boolean; -}; - -/** - * The error API is used to report errors to the app, and display them to the user. - * - * @remarks - * - * Plugins can use this API as a method of displaying errors to the user, but also - * to report errors for collection by error reporting services. - * - * If an error can be displayed inline, e.g. as feedback in a form, that should be - * preferred over relying on this API to display the error. The main use of this API - * for displaying errors should be for asynchronous errors, such as a failing background process. - * - * Even if an error is displayed inline, it should still be reported through this API - * if it would be useful to collect or log it for debugging purposes, but with - * the hidden flag set. For example, an error arising from form field validation - * should probably not be reported, while a failed REST call would be useful to report. - * - * @public - */ -export type ErrorApi = { - /** - * Post an error for handling by the application. - */ - post(error: ErrorApiError, context?: ErrorApiErrorContext): void; - - /** - * Observe errors posted by other parts of the application. - */ - error$(): Observable<{ - error: ErrorApiError; - context?: ErrorApiErrorContext; - }>; -}; - -/** - * The {@link ApiRef} of {@link ErrorApi}. - * - * @public - */ -export const errorApiRef: ApiRef = createApiRef({ - id: 'core.error', -}); +export { + type ErrorApiError, + type ErrorApiErrorContext, + type ErrorApi, + errorApiRef, +} from '@backstage/frontend-plugin-api'; diff --git a/packages/core-plugin-api/src/apis/definitions/FeatureFlagsApi.ts b/packages/core-plugin-api/src/apis/definitions/FeatureFlagsApi.ts index d4429975cc..9b192e784d 100644 --- a/packages/core-plugin-api/src/apis/definitions/FeatureFlagsApi.ts +++ b/packages/core-plugin-api/src/apis/definitions/FeatureFlagsApi.ts @@ -13,114 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -/* We want to maintain the same information as an enum, so we disable the redeclaration warning */ -/* eslint-disable @typescript-eslint/no-redeclare */ -import { ApiRef, createApiRef } from '../system'; - -/** - * Feature flag descriptor. - * - * @public - */ -export type FeatureFlag = { - name: string; - pluginId: string; - description?: string; -}; - -/** - * Enum representing the state of a feature flag (inactive/active). - * - * @public - */ -export const FeatureFlagState = { - /** - * Feature flag inactive (disabled). - */ - None: 0, - /** - * Feature flag active (enabled). - */ - Active: 1, -} as const; - -/** - * @public - */ -export type FeatureFlagState = - (typeof FeatureFlagState)[keyof typeof FeatureFlagState]; - -/** - * @public - */ -export namespace FeatureFlagState { - export type None = typeof FeatureFlagState.None; - export type Active = typeof FeatureFlagState.Active; -} - -/** - * Options to use when saving feature flags. - * - * @public - */ -export type FeatureFlagsSaveOptions = { - /** - * The new feature flag states to save. - */ - states: Record; - - /** - * Whether the saves states should be merged into the existing ones, or replace them. - * - * Defaults to false. - */ - merge?: boolean; -}; - -/** - * The feature flags API is used to toggle functionality to users across plugins and Backstage. - * - * @remarks - * - * Plugins can use this API to register feature flags that they have available - * for users to enable/disable, and this API will centralize the current user's - * state of which feature flags they would like to enable. - * - * This is ideal for Backstage plugins, as well as your own App, to trial incomplete - * or unstable upcoming features. Although there will be a common interface for users - * to enable and disable feature flags, this API acts as another way to enable/disable. - * - * @public - */ -export interface FeatureFlagsApi { - /** - * Registers a new feature flag. Once a feature flag has been registered it - * can be toggled by users, and read back to enable or disable features. - */ - registerFlag(flag: FeatureFlag): void; - - /** - * Get a list of all registered flags. - */ - getRegisteredFlags(): FeatureFlag[]; - - /** - * Whether the feature flag with the given name is currently activated for the user. - */ - isActive(name: string): boolean; - - /** - * Save the user's choice of feature flag states. - */ - save(options: FeatureFlagsSaveOptions): void; -} - -/** - * The {@link ApiRef} of {@link FeatureFlagsApi}. - * - * @public - */ -export const featureFlagsApiRef: ApiRef = createApiRef({ - id: 'core.featureflags', -}); +export { + type FeatureFlag, + type FeatureFlagsApi, + type FeatureFlagsSaveOptions, + FeatureFlagState, + featureFlagsApiRef, +} from '@backstage/frontend-plugin-api'; diff --git a/packages/core-plugin-api/src/apis/definitions/FetchApi.ts b/packages/core-plugin-api/src/apis/definitions/FetchApi.ts index aba0e53bb7..7fdf949e58 100644 --- a/packages/core-plugin-api/src/apis/definitions/FetchApi.ts +++ b/packages/core-plugin-api/src/apis/definitions/FetchApi.ts @@ -14,38 +14,4 @@ * limitations under the License. */ -import { ApiRef, createApiRef } from '../system'; - -/** - * A wrapper for the fetch API, that has additional behaviors such as the - * ability to automatically inject auth information where necessary. - * - * @public - */ -export type FetchApi = { - /** - * The `fetch` implementation. - */ - fetch: typeof fetch; -}; - -/** - * The {@link ApiRef} of {@link FetchApi}. - * - * @remarks - * - * This is a wrapper for the fetch API, that has additional behaviors such as - * the ability to automatically inject auth information where necessary. - * - * Note that the default behavior of this API (unless overridden by your org), - * is to require that the user is already signed in so that it has auth - * information to inject. Therefore, using the default implementation of this - * utility API e.g. on the `SignInPage` or similar, would cause issues. In - * special circumstances like those, you can use the regular system `fetch` - * instead. - * - * @public - */ -export const fetchApiRef: ApiRef = createApiRef({ - id: 'core.fetch', -}); +export { type FetchApi, fetchApiRef } from '@backstage/frontend-plugin-api'; diff --git a/packages/core-plugin-api/src/apis/definitions/IdentityApi.ts b/packages/core-plugin-api/src/apis/definitions/IdentityApi.ts index 1b127a1971..0648f96059 100644 --- a/packages/core-plugin-api/src/apis/definitions/IdentityApi.ts +++ b/packages/core-plugin-api/src/apis/definitions/IdentityApi.ts @@ -13,44 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { ApiRef, createApiRef } from '../system'; -import { BackstageUserIdentity, ProfileInfo } from './auth'; -/** - * The Identity API used to identify and get information about the signed in user. - * - * @public - */ -export type IdentityApi = { - /** - * The profile of the signed in user. - */ - getProfileInfo(): Promise; - - /** - * User identity information within Backstage. - */ - getBackstageIdentity(): Promise; - - /** - * Provides credentials in the form of a token which proves the identity of the signed in user. - * - * The token will be undefined if the signed in user does not have a verified - * identity, such as a demo user or mocked user for e2e tests. - */ - getCredentials(): Promise<{ token?: string }>; - - /** - * Sign out the current user - */ - signOut(): Promise; -}; - -/** - * The {@link ApiRef} of {@link IdentityApi}. - * - * @public - */ -export const identityApiRef: ApiRef = createApiRef({ - id: 'core.identity', -}); +export { + type IdentityApi, + identityApiRef, +} from '@backstage/frontend-plugin-api'; diff --git a/packages/core-plugin-api/src/apis/definitions/OAuthRequestApi.ts b/packages/core-plugin-api/src/apis/definitions/OAuthRequestApi.ts index 75bf3a3864..c217b77fb5 100644 --- a/packages/core-plugin-api/src/apis/definitions/OAuthRequestApi.ts +++ b/packages/core-plugin-api/src/apis/definitions/OAuthRequestApi.ts @@ -14,118 +14,10 @@ * limitations under the License. */ -import { Observable } from '@backstage/types'; -import { ApiRef, createApiRef } from '../system'; -import { AuthProviderInfo } from './auth'; - -/** - * Describes how to handle auth requests. Both how to show them to the user, and what to do when - * the user accesses the auth request. - * - * @public - */ -export type OAuthRequesterOptions = { - /** - * Information about the auth provider, which will be forwarded to auth requests. - */ - provider: AuthProviderInfo; - - /** - * Implementation of the auth flow, which will be called synchronously when - * trigger() is called on an auth requests. - */ - onAuthRequest(scopes: Set): Promise; -}; - -/** - * Function used to trigger new auth requests for a set of scopes. - * - * @remarks - * - * The returned promise will resolve to the same value returned by the onAuthRequest in the - * {@link OAuthRequesterOptions}. Or rejected, if the request is rejected. - * - * This function can be called multiple times before the promise resolves. All calls - * will be merged into one request, and the scopes forwarded to the onAuthRequest will be the - * union of all requested scopes. - * - * @public - */ -export type OAuthRequester = ( - scopes: Set, -) => Promise; - -/** - * An pending auth request for a single auth provider. The request will remain in this pending - * state until either reject() or trigger() is called. - * - * @remarks - * - * Any new requests for the same provider are merged into the existing pending request, meaning - * there will only ever be a single pending request for a given provider. - * - * @public - */ -export type PendingOAuthRequest = { - /** - * Information about the auth provider, as given in the AuthRequesterOptions - */ - provider: AuthProviderInfo; - - /** - * Rejects the request, causing all pending AuthRequester calls to fail with "RejectedError". - */ - reject(): void; - - /** - * Trigger the auth request to continue the auth flow, by for example showing a popup. - * - * Synchronously calls onAuthRequest with all scope currently in the request. - */ - trigger(): Promise; -}; - -/** - * Provides helpers for implemented OAuth login flows within Backstage. - * - * @public - */ -export type OAuthRequestApi = { - /** - * A utility for showing login popups or similar things, and merging together multiple requests for - * different scopes into one request that includes all scopes. - * - * The passed in options provide information about the login provider, and how to handle auth requests. - * - * The returned AuthRequester function is used to request login with new scopes. These requests - * are merged together and forwarded to the auth handler, as soon as a consumer of auth requests - * triggers an auth flow. - * - * See AuthRequesterOptions, AuthRequester, and handleAuthRequests for more info. - */ - createAuthRequester( - options: OAuthRequesterOptions, - ): OAuthRequester; - - /** - * Observers pending auth requests. The returned observable will emit all - * current active auth request, at most one for each created auth requester. - * - * Each request has its own info about the login provider, forwarded from the auth requester options. - * - * Depending on user interaction, the request should either be rejected, or used to trigger the auth handler. - * If the request is rejected, all pending AuthRequester calls will fail with a "RejectedError". - * If a auth is triggered, and the auth handler resolves successfully, then all currently pending - * AuthRequester calls will resolve to the value returned by the onAuthRequest call. - */ - authRequest$(): Observable; -}; - -/** - * The {@link ApiRef} of {@link OAuthRequestApi}. - * - * @public - */ -export const oauthRequestApiRef: ApiRef = createApiRef({ - id: 'core.oauthrequest', -}); +export { + type OAuthRequesterOptions, + type OAuthRequester, + type PendingOAuthRequest, + type OAuthRequestApi, + oauthRequestApiRef, +} from '@backstage/frontend-plugin-api'; diff --git a/packages/core-plugin-api/src/apis/definitions/StorageApi.ts b/packages/core-plugin-api/src/apis/definitions/StorageApi.ts index 1506e5f143..4f3af7b611 100644 --- a/packages/core-plugin-api/src/apis/definitions/StorageApi.ts +++ b/packages/core-plugin-api/src/apis/definitions/StorageApi.ts @@ -14,97 +14,8 @@ * limitations under the License. */ -import { ApiRef, createApiRef } from '../system'; -import { JsonValue, Observable } from '@backstage/types'; - -/** - * A snapshot in time of the current known value of a storage key. - * - * @public - */ -export type StorageValueSnapshot = - | { - key: string; - presence: 'unknown' | 'absent'; - value?: undefined; - } - | { - key: string; - presence: 'present'; - value: TValue; - }; - -/** - * Provides a key-value persistence API. - * - * @public - */ -export interface StorageApi { - /** - * Create a bucket to store data in. - * - * @param name - Namespace for the storage to be stored under, - * will inherit previous namespaces too - */ - forBucket(name: string): StorageApi; - - /** - * Remove persistent data. - * - * @param key - Unique key associated with the data. - */ - remove(key: string): Promise; - - /** - * Save persistent data, and emit messages to anyone that is using - * {@link StorageApi.observe$} for this key. - * - * @param key - Unique key associated with the data. - * @param data - The data to be stored under the key. - */ - set(key: string, data: T): Promise; - - /** - * Observe the value over time for a particular key in the current bucket. - * - * @remarks - * - * The observable will only emit values when the value changes in the underlying - * storage, although multiple values with the same shape may be emitted in a row. - * - * If a {@link StorageApi.snapshot} of a key is retrieved and the presence is - * `'unknown'`, then you are guaranteed to receive a snapshot with a known - * presence, as long as you observe the key within the same tick. - * - * Since the emitted values are shared across all subscribers, it is important - * not to mutate the returned values. The values may be frozen as a precaution. - * - * @param key - Unique key associated with the data - */ - observe$( - key: string, - ): Observable>; - - /** - * Returns an immediate snapshot value for the given key, if possible. - * - * @remarks - * - * Combine with {@link StorageApi.observe$} to get notified of value changes. - * - * Note that this method is synchronous, and some underlying storages may be - * unable to retrieve a value using this method - the result may or may not - * consistently have a presence of 'unknown'. Use {@link StorageApi.observe$} - * to be sure to receive an actual value eventually. - */ - snapshot(key: string): StorageValueSnapshot; -} - -/** - * The {@link ApiRef} of {@link StorageApi}. - * - * @public - */ -export const storageApiRef: ApiRef = createApiRef({ - id: 'core.storage', -}); +export { + type StorageValueSnapshot, + type StorageApi, + storageApiRef, +} from '@backstage/frontend-plugin-api'; diff --git a/packages/core-plugin-api/src/apis/definitions/alpha.ts b/packages/core-plugin-api/src/apis/definitions/alpha.ts deleted file mode 100644 index e7f6f13fef..0000000000 --- a/packages/core-plugin-api/src/apis/definitions/alpha.ts +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright 2023 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. - */ -export { - translationApiRef, - type TranslationApi, - type TranslationFunction, - type TranslationSnapshot, -} from './TranslationApi'; -export { appLanguageApiRef, type AppLanguageApi } from './AppLanguageApi'; diff --git a/packages/core-plugin-api/src/apis/definitions/auth.ts b/packages/core-plugin-api/src/apis/definitions/auth.ts index 9a0ddcd958..a60c84c635 100644 --- a/packages/core-plugin-api/src/apis/definitions/auth.ts +++ b/packages/core-plugin-api/src/apis/definitions/auth.ts @@ -16,493 +16,28 @@ /* We want to maintain the same information as an enum, so we disable the redeclaration warning */ /* eslint-disable @typescript-eslint/no-redeclare */ -import { ApiRef, createApiRef } from '../system'; -import { IconComponent } from '../../icons/types'; -import { Observable } from '@backstage/types'; - -/** - * This file contains declarations for common interfaces of auth-related APIs. - * The declarations should be used to signal which type of authentication and - * authorization methods each separate auth provider supports. - * - * For example, a Google OAuth provider that supports OAuth 2 and OpenID Connect, - * would be declared as follows: - * - * const googleAuthApiRef = createApiRef({ ... }) - */ - -/** - * Information about the auth provider. - * - * @remarks - * - * This information is used both to connect the correct auth provider in the backend, as - * well as displaying the provider to the user. - * - * @public - */ -export type AuthProviderInfo = { - /** - * The ID of the auth provider. This should match with ID of the provider in the `@backstage/auth-backend`. - */ - id: string; - - /** - * Title for the auth provider, for example "GitHub" - */ - title: string; - - /** - * Icon for the auth provider. - */ - icon: IconComponent; - - /** - * Optional user friendly messaage to display for the auth provider. - */ - message?: string; -}; - -/** - * An array of scopes, or a scope string formatted according to the - * auth provider, which is typically a space separated list. - * - * @remarks - * - * See the documentation for each auth provider for the list of scopes - * supported by each provider. - * - * @public - */ -export type OAuthScope = string | string[]; - -/** - * Configuration of an authentication request. - * - * @public - */ -export type AuthRequestOptions = { - /** - * If this is set to true, the user will not be prompted to log in, - * and an empty response will be returned if there is no existing session. - * - * This can be used to perform a check whether the user is logged in, or if you don't - * want to force a user to be logged in, but provide functionality if they already are. - * - * @defaultValue false - */ - optional?: boolean; - - /** - * If this is set to true, the request will bypass the regular oauth login modal - * and open the login popup directly. - * - * The method must be called synchronously from a user action for this to work in all browsers. - * - * @defaultValue false - */ - instantPopup?: boolean; -}; - -/** - * This API provides access to OAuth 2 credentials. It lets you request access tokens, - * which can be used to act on behalf of the user when talking to APIs. - * - * @public - */ -export type OAuthApi = { - /** - * Requests an OAuth 2 Access Token, optionally with a set of scopes. The access token allows - * you to make requests on behalf of the user, and the copes may grant you broader access, depending - * on the auth provider. - * - * Each auth provider has separate handling of scope, so you need to look at the documentation - * for each one to know what scope you need to request. - * - * This method is cheap and should be called each time an access token is used. Do not for example - * store the access token in React component state, as that could cause the token to expire. Instead - * fetch a new access token for each request. - * - * Be sure to include all required scopes when requesting an access token. When testing your implementation - * it is best to log out the Backstage session and then visit your plugin page directly, as - * you might already have some required scopes in your existing session. Not requesting the correct - * scopes can lead to 403 or other authorization errors, which can be tricky to debug. - * - * If the user has not yet granted access to the provider and the set of requested scopes, the user - * will be prompted to log in. The returned promise will not resolve until the user has - * successfully logged in. The returned promise can be rejected, but only if the user rejects the login request. - */ - getAccessToken( - scope?: OAuthScope, - options?: AuthRequestOptions, - ): Promise; -}; - -/** - * This API provides access to OpenID Connect credentials. It lets you request ID tokens, - * which can be passed to backend services to prove the user's identity. - * - * @public - */ -export type OpenIdConnectApi = { - /** - * Requests an OpenID Connect ID Token. - * - * This method is cheap and should be called each time an ID token is used. Do not for example - * store the id token in React component state, as that could cause the token to expire. Instead - * fetch a new id token for each request. - * - * If the user has not yet logged in to Google inside Backstage, the user will be prompted - * to log in. The returned promise will not resolve until the user has successfully logged in. - * The returned promise can be rejected, but only if the user rejects the login request. - */ - getIdToken(options?: AuthRequestOptions): Promise; -}; - -/** - * This API provides access to profile information of the user from an auth provider. - * - * @public - */ -export type ProfileInfoApi = { - /** - * Get profile information for the user as supplied by this auth provider. - * - * If the optional flag is not set, a session is guaranteed to be returned, while if - * the optional flag is set, the session may be undefined. See {@link AuthRequestOptions} for more details. - */ - getProfile(options?: AuthRequestOptions): Promise; -}; - -/** - * This API provides access to the user's identity within Backstage. - * - * @remarks - * - * An auth provider that implements this interface can be used to sign-in to backstage. It is - * not intended to be used directly from a plugin, but instead serves as a connection between - * this authentication method and the app's {@link IdentityApi} - * - * @public - */ -export type BackstageIdentityApi = { - /** - * Get the user's identity within Backstage. This should normally not be called directly, - * use the {@link IdentityApi} instead. - * - * If the optional flag is not set, a session is guaranteed to be returned, while if - * the optional flag is set, the session may be undefined. See {@link AuthRequestOptions} for more details. - */ - getBackstageIdentity( - options?: AuthRequestOptions, - ): Promise; -}; - -/** - * User identity information within Backstage. - * - * @public - */ -export type BackstageUserIdentity = { - /** - * The type of identity that this structure represents. In the frontend app - * this will currently always be 'user'. - */ - type: 'user'; - - /** - * The entityRef of the user in the catalog. - * For example User:default/sandra - */ - userEntityRef: string; - - /** - * The user and group entities that the user claims ownership through - */ - ownershipEntityRefs: string[]; -}; - -/** - * Token and Identity response, with the users claims in the Identity. - * - * @public - */ -export type BackstageIdentityResponse = { - /** - * The token used to authenticate the user within Backstage. - */ - token: string; - - /** - * The time at which the token expires. If not set, it can be assumed that the token does not expire. - */ - expiresAt?: Date; - - /** - * Identity information derived from the token. - */ - identity: BackstageUserIdentity; -}; - -/** - * Profile information of the user. - * - * @public - */ -export type ProfileInfo = { - /** - * Email ID. - */ - email?: string; - - /** - * Display name that can be presented to the user. - */ - displayName?: string; - - /** - * URL to an avatar image of the user. - */ - picture?: string; -}; - -/** - * Session state values passed to subscribers of the SessionApi. - * - * @public - */ -export const SessionState = { - /** - * User signed in. - */ - SignedIn: 'SignedIn', - /** - * User not signed in. - */ - SignedOut: 'SignedOut', -} as const; - -/** - * @public - */ -export type SessionState = (typeof SessionState)[keyof typeof SessionState]; - -/** - * @public - */ -export namespace SessionState { - export type SignedIn = typeof SessionState.SignedIn; - export type SignedOut = typeof SessionState.SignedOut; -} - -/** - * The SessionApi provides basic controls for any auth provider that is tied to a persistent session. - * - * @public - */ -export type SessionApi = { - /** - * Sign in with a minimum set of permissions. - */ - signIn(): Promise; - - /** - * Sign out from the current session. This will reload the page. - */ - signOut(): Promise; - - /** - * Observe the current state of the auth session. Emits the current state on subscription. - */ - sessionState$(): Observable; -}; - -/** - * Provides authentication towards Google APIs and identities. - * - * @public - * @remarks - * - * See {@link https://developers.google.com/identity/protocols/googlescopes} for a full list of supported scopes. - * - * Note that the ID token payload is only guaranteed to contain the user's numerical Google ID, - * email and expiration information. Do not rely on any other fields, as they might not be present. - */ -export const googleAuthApiRef: ApiRef< - OAuthApi & - OpenIdConnectApi & - ProfileInfoApi & - BackstageIdentityApi & - SessionApi -> = createApiRef({ - id: 'core.auth.google', -}); - -/** - * Provides authentication towards GitHub APIs. - * - * @public - * @remarks - * - * See {@link https://developer.github.com/apps/building-oauth-apps/understanding-scopes-for-oauth-apps/} - * for a full list of supported scopes. - */ -export const githubAuthApiRef: ApiRef< - OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionApi -> = createApiRef({ - id: 'core.auth.github', -}); - -/** - * Provides authentication towards Okta APIs. - * - * @public - * @remarks - * - * See {@link https://developer.okta.com/docs/guides/implement-oauth-for-okta/scopes/} - * for a full list of supported scopes. - */ -export const oktaAuthApiRef: ApiRef< - OAuthApi & - OpenIdConnectApi & - ProfileInfoApi & - BackstageIdentityApi & - SessionApi -> = createApiRef({ - id: 'core.auth.okta', -}); - -/** - * Provides authentication towards GitLab APIs. - * - * @public - * @remarks - * - * See {@link https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html#limiting-scopes-of-a-personal-access-token} - * for a full list of supported scopes. - */ -export const gitlabAuthApiRef: ApiRef< - OAuthApi & - OpenIdConnectApi & - ProfileInfoApi & - BackstageIdentityApi & - SessionApi -> = createApiRef({ - id: 'core.auth.gitlab', -}); - -/** - * Provides authentication towards Microsoft APIs and identities. - * - * @public - * @remarks - * - * For more info and a full list of supported scopes, see: - * - {@link https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-permissions-and-consent} - * - {@link https://docs.microsoft.com/en-us/graph/permissions-reference} - */ -export const microsoftAuthApiRef: ApiRef< - OAuthApi & - OpenIdConnectApi & - ProfileInfoApi & - BackstageIdentityApi & - SessionApi -> = createApiRef({ - id: 'core.auth.microsoft', -}); - -/** - * Provides authentication towards OneLogin APIs. - * - * @public - */ -export const oneloginAuthApiRef: ApiRef< - OAuthApi & - OpenIdConnectApi & - ProfileInfoApi & - BackstageIdentityApi & - SessionApi -> = createApiRef({ - id: 'core.auth.onelogin', -}); - -/** - * Provides authentication towards Bitbucket APIs. - * - * @public - * @remarks - * - * See {@link https://support.atlassian.com/bitbucket-cloud/docs/use-oauth-on-bitbucket-cloud/} - * for a full list of supported scopes. - */ -export const bitbucketAuthApiRef: ApiRef< - OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionApi -> = createApiRef({ - id: 'core.auth.bitbucket', -}); - -/** - * Provides authentication towards Bitbucket Server APIs. - * - * @public - * @remarks - * - * See {@link https://confluence.atlassian.com/bitbucketserver/bitbucket-oauth-2-0-provider-api-1108483661.html#BitbucketOAuth2.0providerAPI-scopes} - * for a full list of supported scopes. - */ -export const bitbucketServerAuthApiRef: ApiRef< - OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionApi -> = createApiRef({ - id: 'core.auth.bitbucket-server', -}); - -/** - * Provides authentication towards Atlassian APIs. - * - * @public - * @remarks - * - * See {@link https://developer.atlassian.com/cloud/jira/platform/scopes-for-connect-and-oauth-2-3LO-apps/} - * for a full list of supported scopes. - */ -export const atlassianAuthApiRef: ApiRef< - OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionApi -> = createApiRef({ - id: 'core.auth.atlassian', -}); - -/** - * Provides authentication towards VMware Cloud APIs and identities. - * - * @public - * @remarks - * - * For more info about VMware Cloud identity and access management: - * - {@link https://docs.vmware.com/en/VMware-Cloud-services/services/Using-VMware-Cloud-Services/GUID-53D39337-D93A-4B84-BD18-DDF43C21479A.html} - */ -export const vmwareCloudAuthApiRef: ApiRef< - OAuthApi & - OpenIdConnectApi & - ProfileInfoApi & - BackstageIdentityApi & - SessionApi -> = createApiRef({ - id: 'core.auth.vmware-cloud', -}); - -/** - * Provides authentication towards OpenShift APIs and identities. - * - * @public - * @remarks - * - * See {@link https://docs.redhat.com/en/documentation/openshift_container_platform/latest/html/authentication_and_authorization/configuring-oauth-clients} - * on how to configure the OAuth clients and - * {@link https://docs.redhat.com/en/documentation/openshift_container_platform/latest/html-single/authentication_and_authorization/index#tokens-scoping-about_configuring-internal-oauth} - * for available scopes. - */ -export const openshiftAuthApiRef: ApiRef< - OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionApi -> = createApiRef({ - id: 'core.auth.openshift', -}); +export { + type AuthProviderInfo, + type OAuthScope, + type AuthRequestOptions, + type OAuthApi, + type OpenIdConnectApi, + type ProfileInfoApi, + type BackstageIdentityApi, + type BackstageUserIdentity, + type BackstageIdentityResponse, + type ProfileInfo, + SessionState, + type SessionApi, + googleAuthApiRef, + githubAuthApiRef, + oktaAuthApiRef, + gitlabAuthApiRef, + microsoftAuthApiRef, + oneloginAuthApiRef, + bitbucketAuthApiRef, + bitbucketServerAuthApiRef, + atlassianAuthApiRef, + vmwareCloudAuthApiRef, + openshiftAuthApiRef, +} from '@backstage/frontend-plugin-api'; diff --git a/packages/core-plugin-api/src/apis/system/ApiRef.ts b/packages/core-plugin-api/src/apis/system/ApiRef.ts index adedff9f73..ffd074b9a7 100644 --- a/packages/core-plugin-api/src/apis/system/ApiRef.ts +++ b/packages/core-plugin-api/src/apis/system/ApiRef.ts @@ -14,51 +14,5 @@ * limitations under the License. */ -import type { ApiRef } from './types'; - -/** - * API reference configuration - holds an ID of the referenced API. - * - * @public - */ -export type ApiRefConfig = { - id: string; -}; - -class ApiRefImpl implements ApiRef { - constructor(private readonly config: ApiRefConfig) { - const valid = config.id - .split('.') - .flatMap(part => part.split('-')) - .every(part => part.match(/^[a-z][a-z0-9]*$/)); - if (!valid) { - throw new Error( - `API id must only contain period separated lowercase alphanum tokens with dashes, got '${config.id}'`, - ); - } - } - - get id(): string { - return this.config.id; - } - - // Utility for getting type of an api, using `typeof apiRef.T` - get T(): T { - throw new Error(`tried to read ApiRef.T of ${this}`); - } - - toString() { - return `apiRef{${this.config.id}}`; - } -} - -/** - * Creates a reference to an API. - * - * @param config - The descriptor of the API to reference. - * @returns An API reference. - * @public - */ -export function createApiRef(config: ApiRefConfig): ApiRef { - return new ApiRefImpl(config); -} +export { createApiRef } from '@backstage/frontend-plugin-api'; +export type { ApiRefConfig } from '@backstage/frontend-plugin-api'; diff --git a/packages/core-plugin-api/src/apis/system/helpers.ts b/packages/core-plugin-api/src/apis/system/helpers.ts index eaffed3003..07acaa6a53 100644 --- a/packages/core-plugin-api/src/apis/system/helpers.ts +++ b/packages/core-plugin-api/src/apis/system/helpers.ts @@ -14,61 +14,4 @@ * limitations under the License. */ -import { ApiRef, ApiFactory, TypesToApiRefs } from './types'; - -/** - * Used to infer types for a standalone {@link ApiFactory} that isn't immediately passed - * to another function. - * - * @remarks - * - * This function doesn't actually do anything, it's only used to infer types. - * - * @public - */ -export function createApiFactory< - Api, - Impl extends Api, - Deps extends { [name in string]: unknown }, ->(factory: ApiFactory): ApiFactory; -/** - * Used to infer types for a standalone {@link ApiFactory} that isn't immediately passed - * to another function. - * - * @param api - Ref of the API that will be produced by the factory. - * @param instance - Implementation of the API to use. - * @public - */ -export function createApiFactory( - api: ApiRef, - instance: Impl, -): ApiFactory; -/** - * Used to infer types for a standalone {@link ApiFactory} that isn't immediately passed - * to another function. - * - * @remarks - * - * Creates factory from {@link ApiRef} or returns the factory itself if provided. - * - * @param factory - Existing factory or {@link ApiRef}. - * @param instance - The instance to be returned by the factory. - * @public - */ -export function createApiFactory< - Api, - Impl extends Api, - Deps extends { [name in string]: unknown }, ->( - factory: ApiFactory | ApiRef, - instance?: Impl, -): ApiFactory { - if ('id' in factory) { - return { - api: factory, - deps: {} as TypesToApiRefs, - factory: () => instance!, - }; - } - return factory; -} +export { createApiFactory } from '@backstage/frontend-plugin-api'; diff --git a/packages/core-plugin-api/src/apis/system/types.ts b/packages/core-plugin-api/src/apis/system/types.ts index 96614c320c..3b4ba449d3 100644 --- a/packages/core-plugin-api/src/apis/system/types.ts +++ b/packages/core-plugin-api/src/apis/system/types.ts @@ -14,61 +14,11 @@ * limitations under the License. */ -/** - * API reference. - * - * @public - */ -export type ApiRef = { - id: string; - T: T; -}; - -/** - * Catch-all {@link ApiRef} type. - * - * @public - */ -export type AnyApiRef = ApiRef; - -/** - * Wraps a type with API properties into a type holding their respective {@link ApiRef}s. - * - * @public - */ -export type TypesToApiRefs = { [key in keyof T]: ApiRef }; - -/** - * Provides lookup of APIs through their {@link ApiRef}s. - * - * @public - */ -export type ApiHolder = { - get(api: ApiRef): T | undefined; -}; - -/** - * Describes type returning API implementations. - * - * @public - */ -export type ApiFactory< - Api, - Impl extends Api, - Deps extends { [name in string]: unknown }, -> = { - api: ApiRef; - deps: TypesToApiRefs; - factory(deps: Deps): Impl; -}; - -/** - * Catch-all {@link ApiFactory} type. - * - * @public - */ -export type AnyApiFactory = ApiFactory< - unknown, - unknown, - { [key in string]: unknown } ->; +export type { + ApiRef, + AnyApiRef, + TypesToApiRefs, + ApiHolder, + ApiFactory, + AnyApiFactory, +} from '@backstage/frontend-plugin-api'; diff --git a/packages/core-plugin-api/src/apis/system/useApi.tsx b/packages/core-plugin-api/src/apis/system/useApi.tsx index 30e39033f7..a2caec8aea 100644 --- a/packages/core-plugin-api/src/apis/system/useApi.tsx +++ b/packages/core-plugin-api/src/apis/system/useApi.tsx @@ -14,81 +14,4 @@ * limitations under the License. */ -import { ComponentType, PropsWithChildren } from 'react'; -import { ApiRef, ApiHolder, TypesToApiRefs } from './types'; -import { useVersionedContext } from '@backstage/version-bridge'; -import { NotImplementedError } from '@backstage/errors'; - -/** - * React hook for retrieving {@link ApiHolder}, an API catalog. - * - * @public - */ -export function useApiHolder(): ApiHolder { - const versionedHolder = useVersionedContext<{ 1: ApiHolder }>('api-context'); - if (!versionedHolder) { - throw new NotImplementedError('API context is not available'); - } - - const apiHolder = versionedHolder.atVersion(1); - if (!apiHolder) { - throw new NotImplementedError('ApiContext v1 not available'); - } - return apiHolder; -} - -/** - * React hook for retrieving APIs. - * - * @param apiRef - Reference of the API to use. - * @public - */ -export function useApi(apiRef: ApiRef): T { - const apiHolder = useApiHolder(); - - const api = apiHolder.get(apiRef); - if (!api) { - throw new NotImplementedError(`No implementation available for ${apiRef}`); - } - return api; -} - -/** - * Wrapper for giving component an API context. - * - * @param apis - APIs for the context. - * @public - */ -export function withApis(apis: TypesToApiRefs) { - return function withApisWrapper( - WrappedComponent: ComponentType, - ) { - const Hoc = (props: PropsWithChildren>) => { - const apiHolder = useApiHolder(); - - const impls = {} as T; - - for (const key in apis) { - if (apis.hasOwnProperty(key)) { - const ref = apis[key]; - - const api = apiHolder.get(ref); - if (!api) { - throw new NotImplementedError( - `No implementation available for ${ref}`, - ); - } - impls[key] = api; - } - } - - return ; - }; - const displayName = - WrappedComponent.displayName || WrappedComponent.name || 'Component'; - - Hoc.displayName = `withApis(${displayName})`; - - return Hoc; - }; -} +export { useApiHolder, useApi, withApis } from '@backstage/frontend-plugin-api'; diff --git a/packages/core-plugin-api/src/icons/types.ts b/packages/core-plugin-api/src/icons/types.ts index 2b00e1456f..2e64828584 100644 --- a/packages/core-plugin-api/src/icons/types.ts +++ b/packages/core-plugin-api/src/icons/types.ts @@ -14,24 +14,4 @@ * limitations under the License. */ -import { ComponentType } from 'react'; - -/** - * IconComponent is the common icon type used throughout Backstage when - * working with and rendering generic icons, including the app system icons. - * - * @remarks - * - * The type is based on SvgIcon from Material UI, but we do not want the plugin-api - * package to have a dependency on Material UI, nor do we want the props to be as broad - * as the SvgIconProps interface. - * - * If you have the need to forward additional props from SvgIconProps, you can - * open an issue or submit a PR to the main Backstage repo. When doing so please - * also describe your use-case and reasoning of the addition. - * - * @public - */ -export type IconComponent = ComponentType<{ - fontSize?: 'medium' | 'large' | 'small' | 'inherit'; -}>; +export type { IconComponent } from '@backstage/frontend-plugin-api'; diff --git a/packages/core-plugin-api/src/translation/index.ts b/packages/core-plugin-api/src/translation/index.ts deleted file mode 100644 index 121ec62c72..0000000000 --- a/packages/core-plugin-api/src/translation/index.ts +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 2023 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. - */ - -export { - type TranslationMessages, - type TranslationMessagesOptions, - createTranslationMessages, -} from './TranslationMessages'; -export { - type TranslationResource, - type TranslationResourceOptions, - createTranslationResource, -} from './TranslationResource'; -export { - type TranslationRef, - type TranslationRefOptions, - createTranslationRef, -} from './TranslationRef'; -export { useTranslationRef } from './useTranslationRef'; diff --git a/packages/frontend-plugin-api/package.json b/packages/frontend-plugin-api/package.json index d5ee6427ad..be2b0bc5f3 100644 --- a/packages/frontend-plugin-api/package.json +++ b/packages/frontend-plugin-api/package.json @@ -5,9 +5,7 @@ "role": "web-library" }, "publishConfig": { - "access": "public", - "main": "dist/index.esm.js", - "types": "dist/index.d.ts" + "access": "public" }, "repository": { "type": "git", @@ -16,8 +14,19 @@ }, "license": "Apache-2.0", "sideEffects": false, + "exports": { + ".": "./src/index.ts", + "./package.json": "./package.json" + }, "main": "src/index.ts", "types": "src/index.ts", + "typesVersions": { + "*": { + "package.json": [ + "package.json" + ] + } + }, "files": [ "dist" ], @@ -31,8 +40,9 @@ "test": "backstage-cli package test" }, "dependencies": { + "@backstage/config": "workspace:^", "@backstage/core-components": "workspace:^", - "@backstage/core-plugin-api": "workspace:^", + "@backstage/errors": "workspace:^", "@backstage/types": "workspace:^", "@backstage/version-bridge": "workspace:^", "@material-ui/core": "^4.12.4", diff --git a/packages/frontend-plugin-api/src/analytics/useAnalytics.test.tsx b/packages/frontend-plugin-api/src/analytics/useAnalytics.test.tsx index 547c78a7fc..18fe64af30 100644 --- a/packages/frontend-plugin-api/src/analytics/useAnalytics.test.tsx +++ b/packages/frontend-plugin-api/src/analytics/useAnalytics.test.tsx @@ -16,7 +16,7 @@ import { renderHook } from '@testing-library/react'; import { useAnalytics } from './useAnalytics'; -import { analyticsApiRef } from '@backstage/core-plugin-api'; +import { analyticsApiRef } from '../apis/definitions/AnalyticsApi'; import { TestApiProvider } from '@backstage/test-utils'; describe('useAnalytics', () => { diff --git a/packages/frontend-plugin-api/src/analytics/useAnalytics.tsx b/packages/frontend-plugin-api/src/analytics/useAnalytics.tsx index 397906d1d1..5c5f2ad60a 100644 --- a/packages/frontend-plugin-api/src/analytics/useAnalytics.tsx +++ b/packages/frontend-plugin-api/src/analytics/useAnalytics.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import { useApi } from '@backstage/core-plugin-api'; +import { useApi } from '../apis/system'; import { useAnalyticsContext } from './AnalyticsContext'; import { analyticsApiRef, AnalyticsTracker, AnalyticsApi } from '../apis'; import { useRef } from 'react'; diff --git a/packages/frontend-plugin-api/src/apis/definitions/AlertApi.ts b/packages/frontend-plugin-api/src/apis/definitions/AlertApi.ts index 649a930b50..afdcb6a617 100644 --- a/packages/frontend-plugin-api/src/apis/definitions/AlertApi.ts +++ b/packages/frontend-plugin-api/src/apis/definitions/AlertApi.ts @@ -14,8 +14,43 @@ * limitations under the License. */ -export { - type AlertApi, - type AlertMessage, - alertApiRef, -} from '@backstage/core-plugin-api'; +import { createApiRef, ApiRef } from '../system'; +import { Observable } from '@backstage/types'; + +/** + * Message handled by the {@link AlertApi}. + * + * @public + */ +export type AlertMessage = { + message: string; + // Severity will default to success since that is what material ui defaults the value to. + severity?: 'success' | 'info' | 'warning' | 'error'; + display?: 'permanent' | 'transient'; +}; + +/** + * The alert API is used to report alerts to the app, and display them to the user. + * + * @public + */ +export type AlertApi = { + /** + * Post an alert for handling by the application. + */ + post(alert: AlertMessage): void; + + /** + * Observe alerts posted by other parts of the application. + */ + alert$(): Observable; +}; + +/** + * The {@link ApiRef} of {@link AlertApi}. + * + * @public + */ +export const alertApiRef: ApiRef = createApiRef({ + id: 'core.alert', +}); diff --git a/packages/frontend-plugin-api/src/apis/definitions/AnalyticsApi.ts b/packages/frontend-plugin-api/src/apis/definitions/AnalyticsApi.ts index 2040966464..aa1f08ffbe 100644 --- a/packages/frontend-plugin-api/src/apis/definitions/AnalyticsApi.ts +++ b/packages/frontend-plugin-api/src/apis/definitions/AnalyticsApi.ts @@ -14,9 +14,8 @@ * limitations under the License. */ -import { ApiRef, createApiRef } from '@backstage/core-plugin-api'; +import { ApiRef, createApiRef } from '../system'; import { AnalyticsContextValue } from '../../analytics/types'; -import type { AnalyticsImplementationBlueprint } from '../../blueprints/'; /** * Represents an event worth tracking in an analytics system that could inform diff --git a/packages/frontend-plugin-api/src/apis/definitions/AppLanguageApi.ts b/packages/frontend-plugin-api/src/apis/definitions/AppLanguageApi.ts index 37ffcfa820..36b97f9fff 100644 --- a/packages/frontend-plugin-api/src/apis/definitions/AppLanguageApi.ts +++ b/packages/frontend-plugin-api/src/apis/definitions/AppLanguageApi.ts @@ -14,7 +14,23 @@ * limitations under the License. */ -export { - type AppLanguageApi, - appLanguageApiRef, -} from '@backstage/core-plugin-api/alpha'; +import { ApiRef, createApiRef } from '../system'; +import { Observable } from '@backstage/types'; + +/** @public */ +export type AppLanguageApi = { + getAvailableLanguages(): { languages: string[] }; + + setLanguage(language?: string): void; + + getLanguage(): { language: string }; + + language$(): Observable<{ language: string }>; +}; + +/** + * @public + */ +export const appLanguageApiRef: ApiRef = createApiRef({ + id: 'core.applanguage', +}); diff --git a/packages/frontend-plugin-api/src/apis/definitions/AppThemeApi.ts b/packages/frontend-plugin-api/src/apis/definitions/AppThemeApi.ts index ce62cd72c3..e771fad597 100644 --- a/packages/frontend-plugin-api/src/apis/definitions/AppThemeApi.ts +++ b/packages/frontend-plugin-api/src/apis/definitions/AppThemeApi.ts @@ -14,8 +14,74 @@ * limitations under the License. */ -export { - type AppTheme, - type AppThemeApi, - appThemeApiRef, -} from '@backstage/core-plugin-api'; +import { ReactNode } from 'react'; +import { ApiRef, createApiRef } from '../system'; +import { Observable } from '@backstage/types'; + +/** + * Describes a theme provided by the app. + * + * @public + */ +export type AppTheme = { + /** + * ID used to remember theme selections. + */ + id: string; + + /** + * Title of the theme + */ + title: string; + + /** + * Theme variant + */ + variant: 'light' | 'dark'; + + /** + * An Icon for the theme mode setting. + */ + icon?: React.ReactElement; + + Provider(props: { children: ReactNode }): JSX.Element | null; +}; + +/** + * The AppThemeApi gives access to the current app theme, and allows switching + * to other options that have been registered as a part of the App. + * + * @public + */ +export type AppThemeApi = { + /** + * Get a list of available themes. + */ + getInstalledThemes(): AppTheme[]; + + /** + * Observe the currently selected theme. A value of undefined means no specific theme has been selected. + */ + activeThemeId$(): Observable; + + /** + * Get the current theme ID. Returns undefined if no specific theme is selected. + */ + getActiveThemeId(): string | undefined; + + /** + * Set a specific theme to use in the app, overriding the default theme selection. + * + * Clear the selection by passing in undefined. + */ + setActiveThemeId(themeId?: string): void; +}; + +/** + * The {@link ApiRef} of {@link AppThemeApi}. + * + * @public + */ +export const appThemeApiRef: ApiRef = createApiRef({ + id: 'core.apptheme', +}); diff --git a/packages/frontend-plugin-api/src/apis/definitions/AppTreeApi.ts b/packages/frontend-plugin-api/src/apis/definitions/AppTreeApi.ts index 6c318d782a..89902d2727 100644 --- a/packages/frontend-plugin-api/src/apis/definitions/AppTreeApi.ts +++ b/packages/frontend-plugin-api/src/apis/definitions/AppTreeApi.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { createApiRef } from '@backstage/core-plugin-api'; +import { createApiRef } from '../system'; import { FrontendPlugin, Extension, ExtensionDataRef } from '../../wiring'; import { ExtensionAttachTo } from '../../wiring/resolveExtensionDefinition'; diff --git a/packages/frontend-plugin-api/src/apis/definitions/ConfigApi.ts b/packages/frontend-plugin-api/src/apis/definitions/ConfigApi.ts index 968a2843ba..d3bada9e46 100644 --- a/packages/frontend-plugin-api/src/apis/definitions/ConfigApi.ts +++ b/packages/frontend-plugin-api/src/apis/definitions/ConfigApi.ts @@ -13,5 +13,22 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { ApiRef, createApiRef } from '../system'; +import { Config } from '@backstage/config'; -export { type ConfigApi, configApiRef } from '@backstage/core-plugin-api'; +/** + * The Config API is used to provide a mechanism to access the + * runtime configuration of the system. + * + * @public + */ +export type ConfigApi = Config; + +/** + * The {@link ApiRef} of {@link ConfigApi}. + * + * @public + */ +export const configApiRef: ApiRef = createApiRef({ + id: 'core.config', +}); diff --git a/packages/frontend-plugin-api/src/apis/definitions/DialogApi.ts b/packages/frontend-plugin-api/src/apis/definitions/DialogApi.ts index 801ec88c69..85151b05f6 100644 --- a/packages/frontend-plugin-api/src/apis/definitions/DialogApi.ts +++ b/packages/frontend-plugin-api/src/apis/definitions/DialogApi.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { createApiRef } from '@backstage/core-plugin-api'; +import { createApiRef } from '../system'; /** * A handle for an open dialog that can be used to interact with it. diff --git a/packages/frontend-plugin-api/src/apis/definitions/DiscoveryApi.ts b/packages/frontend-plugin-api/src/apis/definitions/DiscoveryApi.ts index 98b3f0bbf0..d23fe3db6c 100644 --- a/packages/frontend-plugin-api/src/apis/definitions/DiscoveryApi.ts +++ b/packages/frontend-plugin-api/src/apis/definitions/DiscoveryApi.ts @@ -13,5 +13,43 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { ApiRef, createApiRef } from '../system'; -export { type DiscoveryApi, discoveryApiRef } from '@backstage/core-plugin-api'; +/** + * The discovery API is used to provide a mechanism for plugins to + * discover the endpoint to use to talk to their backend counterpart. + * + * @remarks + * + * The purpose of the discovery API is to allow for many different deployment + * setups and routing methods through a central configuration, instead + * of letting each individual plugin manage that configuration. + * + * Implementations of the discovery API can be a simple as a URL pattern + * using the pluginId, but could also have overrides for individual plugins, + * or query a separate discovery service. + * + * @public + */ +export type DiscoveryApi = { + /** + * Returns the HTTP base backend URL for a given plugin, without a trailing slash. + * + * This method must always be called just before making a request, as opposed to + * fetching the URL when constructing an API client. That is to ensure that more + * flexible routing patterns can be supported. + * + * For example, asking for the URL for `auth` may return something + * like `https://backstage.example.com/api/auth` + */ + getBaseUrl(pluginId: string): Promise; +}; + +/** + * The {@link ApiRef} of {@link DiscoveryApi}. + * + * @public + */ +export const discoveryApiRef: ApiRef = createApiRef({ + id: 'core.discovery', +}); diff --git a/packages/frontend-plugin-api/src/apis/definitions/ErrorApi.ts b/packages/frontend-plugin-api/src/apis/definitions/ErrorApi.ts index 57782a05dc..9c73d94cac 100644 --- a/packages/frontend-plugin-api/src/apis/definitions/ErrorApi.ts +++ b/packages/frontend-plugin-api/src/apis/definitions/ErrorApi.ts @@ -14,9 +14,78 @@ * limitations under the License. */ -export { - type ErrorApiError, - type ErrorApiErrorContext, - type ErrorApi, - errorApiRef, -} from '@backstage/core-plugin-api'; +import { ApiRef, createApiRef } from '../system'; +import { Observable } from '@backstage/types'; + +/** + * Mirrors the JavaScript Error class, for the purpose of + * providing documentation and optional fields. + * + * @public + */ +export type ErrorApiError = { + name: string; + message: string; + stack?: string; +}; + +/** + * Provides additional information about an error that was posted to the application. + * + * @public + */ +export type ErrorApiErrorContext = { + /** + * If set to true, this error should not be displayed to the user. + * + * Hidden errors are typically not displayed in the UI, but the ErrorApi + * implementation may still report them to error tracking services + * or other utilities that care about all errors. + * + * @defaultValue false + */ + hidden?: boolean; +}; + +/** + * The error API is used to report errors to the app, and display them to the user. + * + * @remarks + * + * Plugins can use this API as a method of displaying errors to the user, but also + * to report errors for collection by error reporting services. + * + * If an error can be displayed inline, e.g. as feedback in a form, that should be + * preferred over relying on this API to display the error. The main use of this API + * for displaying errors should be for asynchronous errors, such as a failing background process. + * + * Even if an error is displayed inline, it should still be reported through this API + * if it would be useful to collect or log it for debugging purposes, but with + * the hidden flag set. For example, an error arising from form field validation + * should probably not be reported, while a failed REST call would be useful to report. + * + * @public + */ +export type ErrorApi = { + /** + * Post an error for handling by the application. + */ + post(error: ErrorApiError, context?: ErrorApiErrorContext): void; + + /** + * Observe errors posted by other parts of the application. + */ + error$(): Observable<{ + error: ErrorApiError; + context?: ErrorApiErrorContext; + }>; +}; + +/** + * The {@link ApiRef} of {@link ErrorApi}. + * + * @public + */ +export const errorApiRef: ApiRef = createApiRef({ + id: 'core.error', +}); diff --git a/packages/frontend-plugin-api/src/apis/definitions/FeatureFlagsApi.ts b/packages/frontend-plugin-api/src/apis/definitions/FeatureFlagsApi.ts index 820efc0a25..d4429975cc 100644 --- a/packages/frontend-plugin-api/src/apis/definitions/FeatureFlagsApi.ts +++ b/packages/frontend-plugin-api/src/apis/definitions/FeatureFlagsApi.ts @@ -13,11 +13,114 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +/* We want to maintain the same information as an enum, so we disable the redeclaration warning */ +/* eslint-disable @typescript-eslint/no-redeclare */ -export { - type FeatureFlag, - type FeatureFlagState, - type FeatureFlagsSaveOptions, - type FeatureFlagsApi, - featureFlagsApiRef, -} from '@backstage/core-plugin-api'; +import { ApiRef, createApiRef } from '../system'; + +/** + * Feature flag descriptor. + * + * @public + */ +export type FeatureFlag = { + name: string; + pluginId: string; + description?: string; +}; + +/** + * Enum representing the state of a feature flag (inactive/active). + * + * @public + */ +export const FeatureFlagState = { + /** + * Feature flag inactive (disabled). + */ + None: 0, + /** + * Feature flag active (enabled). + */ + Active: 1, +} as const; + +/** + * @public + */ +export type FeatureFlagState = + (typeof FeatureFlagState)[keyof typeof FeatureFlagState]; + +/** + * @public + */ +export namespace FeatureFlagState { + export type None = typeof FeatureFlagState.None; + export type Active = typeof FeatureFlagState.Active; +} + +/** + * Options to use when saving feature flags. + * + * @public + */ +export type FeatureFlagsSaveOptions = { + /** + * The new feature flag states to save. + */ + states: Record; + + /** + * Whether the saves states should be merged into the existing ones, or replace them. + * + * Defaults to false. + */ + merge?: boolean; +}; + +/** + * The feature flags API is used to toggle functionality to users across plugins and Backstage. + * + * @remarks + * + * Plugins can use this API to register feature flags that they have available + * for users to enable/disable, and this API will centralize the current user's + * state of which feature flags they would like to enable. + * + * This is ideal for Backstage plugins, as well as your own App, to trial incomplete + * or unstable upcoming features. Although there will be a common interface for users + * to enable and disable feature flags, this API acts as another way to enable/disable. + * + * @public + */ +export interface FeatureFlagsApi { + /** + * Registers a new feature flag. Once a feature flag has been registered it + * can be toggled by users, and read back to enable or disable features. + */ + registerFlag(flag: FeatureFlag): void; + + /** + * Get a list of all registered flags. + */ + getRegisteredFlags(): FeatureFlag[]; + + /** + * Whether the feature flag with the given name is currently activated for the user. + */ + isActive(name: string): boolean; + + /** + * Save the user's choice of feature flag states. + */ + save(options: FeatureFlagsSaveOptions): void; +} + +/** + * The {@link ApiRef} of {@link FeatureFlagsApi}. + * + * @public + */ +export const featureFlagsApiRef: ApiRef = createApiRef({ + id: 'core.featureflags', +}); diff --git a/packages/frontend-plugin-api/src/apis/definitions/FetchApi.ts b/packages/frontend-plugin-api/src/apis/definitions/FetchApi.ts index bcc9bc6a91..aba0e53bb7 100644 --- a/packages/frontend-plugin-api/src/apis/definitions/FetchApi.ts +++ b/packages/frontend-plugin-api/src/apis/definitions/FetchApi.ts @@ -14,4 +14,38 @@ * limitations under the License. */ -export { type FetchApi, fetchApiRef } from '@backstage/core-plugin-api'; +import { ApiRef, createApiRef } from '../system'; + +/** + * A wrapper for the fetch API, that has additional behaviors such as the + * ability to automatically inject auth information where necessary. + * + * @public + */ +export type FetchApi = { + /** + * The `fetch` implementation. + */ + fetch: typeof fetch; +}; + +/** + * The {@link ApiRef} of {@link FetchApi}. + * + * @remarks + * + * This is a wrapper for the fetch API, that has additional behaviors such as + * the ability to automatically inject auth information where necessary. + * + * Note that the default behavior of this API (unless overridden by your org), + * is to require that the user is already signed in so that it has auth + * information to inject. Therefore, using the default implementation of this + * utility API e.g. on the `SignInPage` or similar, would cause issues. In + * special circumstances like those, you can use the regular system `fetch` + * instead. + * + * @public + */ +export const fetchApiRef: ApiRef = createApiRef({ + id: 'core.fetch', +}); diff --git a/packages/frontend-plugin-api/src/apis/definitions/IconsApi.ts b/packages/frontend-plugin-api/src/apis/definitions/IconsApi.ts index aa5b7f4fac..fbc9928dc1 100644 --- a/packages/frontend-plugin-api/src/apis/definitions/IconsApi.ts +++ b/packages/frontend-plugin-api/src/apis/definitions/IconsApi.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { createApiRef } from '@backstage/core-plugin-api'; +import { createApiRef } from '../system'; import { IconComponent } from '../../icons'; /** diff --git a/packages/frontend-plugin-api/src/apis/definitions/IdentityApi.ts b/packages/frontend-plugin-api/src/apis/definitions/IdentityApi.ts index 03503a8fba..1b127a1971 100644 --- a/packages/frontend-plugin-api/src/apis/definitions/IdentityApi.ts +++ b/packages/frontend-plugin-api/src/apis/definitions/IdentityApi.ts @@ -13,5 +13,44 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { ApiRef, createApiRef } from '../system'; +import { BackstageUserIdentity, ProfileInfo } from './auth'; -export { type IdentityApi, identityApiRef } from '@backstage/core-plugin-api'; +/** + * The Identity API used to identify and get information about the signed in user. + * + * @public + */ +export type IdentityApi = { + /** + * The profile of the signed in user. + */ + getProfileInfo(): Promise; + + /** + * User identity information within Backstage. + */ + getBackstageIdentity(): Promise; + + /** + * Provides credentials in the form of a token which proves the identity of the signed in user. + * + * The token will be undefined if the signed in user does not have a verified + * identity, such as a demo user or mocked user for e2e tests. + */ + getCredentials(): Promise<{ token?: string }>; + + /** + * Sign out the current user + */ + signOut(): Promise; +}; + +/** + * The {@link ApiRef} of {@link IdentityApi}. + * + * @public + */ +export const identityApiRef: ApiRef = createApiRef({ + id: 'core.identity', +}); diff --git a/packages/frontend-plugin-api/src/apis/definitions/OAuthRequestApi.ts b/packages/frontend-plugin-api/src/apis/definitions/OAuthRequestApi.ts index ff7a0f2852..75bf3a3864 100644 --- a/packages/frontend-plugin-api/src/apis/definitions/OAuthRequestApi.ts +++ b/packages/frontend-plugin-api/src/apis/definitions/OAuthRequestApi.ts @@ -14,10 +14,118 @@ * limitations under the License. */ -export { - type OAuthRequesterOptions, - type OAuthRequester, - type PendingOAuthRequest, - type OAuthRequestApi, - oauthRequestApiRef, -} from '@backstage/core-plugin-api'; +import { Observable } from '@backstage/types'; +import { ApiRef, createApiRef } from '../system'; +import { AuthProviderInfo } from './auth'; + +/** + * Describes how to handle auth requests. Both how to show them to the user, and what to do when + * the user accesses the auth request. + * + * @public + */ +export type OAuthRequesterOptions = { + /** + * Information about the auth provider, which will be forwarded to auth requests. + */ + provider: AuthProviderInfo; + + /** + * Implementation of the auth flow, which will be called synchronously when + * trigger() is called on an auth requests. + */ + onAuthRequest(scopes: Set): Promise; +}; + +/** + * Function used to trigger new auth requests for a set of scopes. + * + * @remarks + * + * The returned promise will resolve to the same value returned by the onAuthRequest in the + * {@link OAuthRequesterOptions}. Or rejected, if the request is rejected. + * + * This function can be called multiple times before the promise resolves. All calls + * will be merged into one request, and the scopes forwarded to the onAuthRequest will be the + * union of all requested scopes. + * + * @public + */ +export type OAuthRequester = ( + scopes: Set, +) => Promise; + +/** + * An pending auth request for a single auth provider. The request will remain in this pending + * state until either reject() or trigger() is called. + * + * @remarks + * + * Any new requests for the same provider are merged into the existing pending request, meaning + * there will only ever be a single pending request for a given provider. + * + * @public + */ +export type PendingOAuthRequest = { + /** + * Information about the auth provider, as given in the AuthRequesterOptions + */ + provider: AuthProviderInfo; + + /** + * Rejects the request, causing all pending AuthRequester calls to fail with "RejectedError". + */ + reject(): void; + + /** + * Trigger the auth request to continue the auth flow, by for example showing a popup. + * + * Synchronously calls onAuthRequest with all scope currently in the request. + */ + trigger(): Promise; +}; + +/** + * Provides helpers for implemented OAuth login flows within Backstage. + * + * @public + */ +export type OAuthRequestApi = { + /** + * A utility for showing login popups or similar things, and merging together multiple requests for + * different scopes into one request that includes all scopes. + * + * The passed in options provide information about the login provider, and how to handle auth requests. + * + * The returned AuthRequester function is used to request login with new scopes. These requests + * are merged together and forwarded to the auth handler, as soon as a consumer of auth requests + * triggers an auth flow. + * + * See AuthRequesterOptions, AuthRequester, and handleAuthRequests for more info. + */ + createAuthRequester( + options: OAuthRequesterOptions, + ): OAuthRequester; + + /** + * Observers pending auth requests. The returned observable will emit all + * current active auth request, at most one for each created auth requester. + * + * Each request has its own info about the login provider, forwarded from the auth requester options. + * + * Depending on user interaction, the request should either be rejected, or used to trigger the auth handler. + * If the request is rejected, all pending AuthRequester calls will fail with a "RejectedError". + * If a auth is triggered, and the auth handler resolves successfully, then all currently pending + * AuthRequester calls will resolve to the value returned by the onAuthRequest call. + */ + authRequest$(): Observable; +}; + +/** + * The {@link ApiRef} of {@link OAuthRequestApi}. + * + * @public + */ +export const oauthRequestApiRef: ApiRef = createApiRef({ + id: 'core.oauthrequest', +}); diff --git a/packages/frontend-plugin-api/src/apis/definitions/RouteResolutionApi.ts b/packages/frontend-plugin-api/src/apis/definitions/RouteResolutionApi.ts index 6ae25a814e..0c3ca9c4cf 100644 --- a/packages/frontend-plugin-api/src/apis/definitions/RouteResolutionApi.ts +++ b/packages/frontend-plugin-api/src/apis/definitions/RouteResolutionApi.ts @@ -20,7 +20,7 @@ import { SubRouteRef, ExternalRouteRef, } from '../../routing'; -import { createApiRef } from '@backstage/core-plugin-api'; +import { createApiRef } from '../system'; /** * TS magic for handling route parameters. diff --git a/packages/frontend-plugin-api/src/apis/definitions/StorageApi.ts b/packages/frontend-plugin-api/src/apis/definitions/StorageApi.ts index 2309d2414c..1506e5f143 100644 --- a/packages/frontend-plugin-api/src/apis/definitions/StorageApi.ts +++ b/packages/frontend-plugin-api/src/apis/definitions/StorageApi.ts @@ -14,8 +14,97 @@ * limitations under the License. */ -export { - type StorageValueSnapshot, - type StorageApi, - storageApiRef, -} from '@backstage/core-plugin-api'; +import { ApiRef, createApiRef } from '../system'; +import { JsonValue, Observable } from '@backstage/types'; + +/** + * A snapshot in time of the current known value of a storage key. + * + * @public + */ +export type StorageValueSnapshot = + | { + key: string; + presence: 'unknown' | 'absent'; + value?: undefined; + } + | { + key: string; + presence: 'present'; + value: TValue; + }; + +/** + * Provides a key-value persistence API. + * + * @public + */ +export interface StorageApi { + /** + * Create a bucket to store data in. + * + * @param name - Namespace for the storage to be stored under, + * will inherit previous namespaces too + */ + forBucket(name: string): StorageApi; + + /** + * Remove persistent data. + * + * @param key - Unique key associated with the data. + */ + remove(key: string): Promise; + + /** + * Save persistent data, and emit messages to anyone that is using + * {@link StorageApi.observe$} for this key. + * + * @param key - Unique key associated with the data. + * @param data - The data to be stored under the key. + */ + set(key: string, data: T): Promise; + + /** + * Observe the value over time for a particular key in the current bucket. + * + * @remarks + * + * The observable will only emit values when the value changes in the underlying + * storage, although multiple values with the same shape may be emitted in a row. + * + * If a {@link StorageApi.snapshot} of a key is retrieved and the presence is + * `'unknown'`, then you are guaranteed to receive a snapshot with a known + * presence, as long as you observe the key within the same tick. + * + * Since the emitted values are shared across all subscribers, it is important + * not to mutate the returned values. The values may be frozen as a precaution. + * + * @param key - Unique key associated with the data + */ + observe$( + key: string, + ): Observable>; + + /** + * Returns an immediate snapshot value for the given key, if possible. + * + * @remarks + * + * Combine with {@link StorageApi.observe$} to get notified of value changes. + * + * Note that this method is synchronous, and some underlying storages may be + * unable to retrieve a value using this method - the result may or may not + * consistently have a presence of 'unknown'. Use {@link StorageApi.observe$} + * to be sure to receive an actual value eventually. + */ + snapshot(key: string): StorageValueSnapshot; +} + +/** + * The {@link ApiRef} of {@link StorageApi}. + * + * @public + */ +export const storageApiRef: ApiRef = createApiRef({ + id: 'core.storage', +}); diff --git a/packages/frontend-plugin-api/src/apis/definitions/SwappableComponentsApi.ts b/packages/frontend-plugin-api/src/apis/definitions/SwappableComponentsApi.ts index ed2b20ce80..09dff04f43 100644 --- a/packages/frontend-plugin-api/src/apis/definitions/SwappableComponentsApi.ts +++ b/packages/frontend-plugin-api/src/apis/definitions/SwappableComponentsApi.ts @@ -15,7 +15,7 @@ */ import { SwappableComponentRef } from '../../components'; -import { createApiRef } from '@backstage/core-plugin-api'; +import { createApiRef } from '../system'; /** * API for looking up components based on component refs. diff --git a/packages/core-plugin-api/src/apis/definitions/TranslationApi.test.tsx b/packages/frontend-plugin-api/src/apis/definitions/TranslationApi.test.tsx similarity index 100% rename from packages/core-plugin-api/src/apis/definitions/TranslationApi.test.tsx rename to packages/frontend-plugin-api/src/apis/definitions/TranslationApi.test.tsx diff --git a/packages/core-plugin-api/src/apis/definitions/TranslationApi.ts b/packages/frontend-plugin-api/src/apis/definitions/TranslationApi.ts similarity index 98% rename from packages/core-plugin-api/src/apis/definitions/TranslationApi.ts rename to packages/frontend-plugin-api/src/apis/definitions/TranslationApi.ts index 5cfb14abb2..2578eaf7c4 100644 --- a/packages/core-plugin-api/src/apis/definitions/TranslationApi.ts +++ b/packages/frontend-plugin-api/src/apis/definitions/TranslationApi.ts @@ -14,9 +14,9 @@ * limitations under the License. */ -import { ApiRef, createApiRef } from '@backstage/core-plugin-api'; +import { ApiRef, createApiRef } from '../system'; import { Expand, ExpandRecursive, Observable } from '@backstage/types'; -import { TranslationRef } from '../../translation/TranslationRef'; +import { TranslationRef } from '../../translation'; import { JSX } from 'react'; /** @@ -305,7 +305,7 @@ type TranslationFunctionOptions< > >; -/** @alpha */ +/** @public */ export type TranslationFunction = CollapsedMessages extends infer IMessages extends { [key in string]: string; @@ -340,11 +340,11 @@ export type TranslationFunction = } : never; -/** @alpha */ +/** @public */ export type TranslationSnapshot = { ready: false } | { ready: true; t: TranslationFunction }; -/** @alpha */ +/** @public */ export type TranslationApi = { getTranslation( translationRef: TranslationRef, @@ -356,7 +356,7 @@ export type TranslationApi = { }; /** - * @alpha + * @public */ export const translationApiRef: ApiRef = createApiRef({ id: 'core.translation', diff --git a/packages/frontend-plugin-api/src/apis/definitions/alpha.ts b/packages/frontend-plugin-api/src/apis/definitions/alpha.ts deleted file mode 100644 index a4ccde1170..0000000000 --- a/packages/frontend-plugin-api/src/apis/definitions/alpha.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright 2023 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. - */ - -export { appLanguageApiRef, type AppLanguageApi } from './AppLanguageApi'; diff --git a/packages/frontend-plugin-api/src/apis/definitions/auth.ts b/packages/frontend-plugin-api/src/apis/definitions/auth.ts index 5a31c1603d..9a0ddcd958 100644 --- a/packages/frontend-plugin-api/src/apis/definitions/auth.ts +++ b/packages/frontend-plugin-api/src/apis/definitions/auth.ts @@ -13,29 +13,496 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +/* We want to maintain the same information as an enum, so we disable the redeclaration warning */ +/* eslint-disable @typescript-eslint/no-redeclare */ -export { - type BackstageIdentityApi, - type BackstageIdentityResponse, - type BackstageUserIdentity, - type AuthProviderInfo, - type AuthRequestOptions, - type OAuthScope, - type OAuthApi, - type OpenIdConnectApi, - type ProfileInfoApi, - type ProfileInfo, - type SessionApi, - SessionState, - atlassianAuthApiRef, - bitbucketAuthApiRef, - bitbucketServerAuthApiRef, - githubAuthApiRef, - gitlabAuthApiRef, - googleAuthApiRef, - oktaAuthApiRef, - microsoftAuthApiRef, - oneloginAuthApiRef, - vmwareCloudAuthApiRef, - openshiftAuthApiRef, -} from '@backstage/core-plugin-api'; +import { ApiRef, createApiRef } from '../system'; +import { IconComponent } from '../../icons/types'; +import { Observable } from '@backstage/types'; + +/** + * This file contains declarations for common interfaces of auth-related APIs. + * The declarations should be used to signal which type of authentication and + * authorization methods each separate auth provider supports. + * + * For example, a Google OAuth provider that supports OAuth 2 and OpenID Connect, + * would be declared as follows: + * + * const googleAuthApiRef = createApiRef({ ... }) + */ + +/** + * Information about the auth provider. + * + * @remarks + * + * This information is used both to connect the correct auth provider in the backend, as + * well as displaying the provider to the user. + * + * @public + */ +export type AuthProviderInfo = { + /** + * The ID of the auth provider. This should match with ID of the provider in the `@backstage/auth-backend`. + */ + id: string; + + /** + * Title for the auth provider, for example "GitHub" + */ + title: string; + + /** + * Icon for the auth provider. + */ + icon: IconComponent; + + /** + * Optional user friendly messaage to display for the auth provider. + */ + message?: string; +}; + +/** + * An array of scopes, or a scope string formatted according to the + * auth provider, which is typically a space separated list. + * + * @remarks + * + * See the documentation for each auth provider for the list of scopes + * supported by each provider. + * + * @public + */ +export type OAuthScope = string | string[]; + +/** + * Configuration of an authentication request. + * + * @public + */ +export type AuthRequestOptions = { + /** + * If this is set to true, the user will not be prompted to log in, + * and an empty response will be returned if there is no existing session. + * + * This can be used to perform a check whether the user is logged in, or if you don't + * want to force a user to be logged in, but provide functionality if they already are. + * + * @defaultValue false + */ + optional?: boolean; + + /** + * If this is set to true, the request will bypass the regular oauth login modal + * and open the login popup directly. + * + * The method must be called synchronously from a user action for this to work in all browsers. + * + * @defaultValue false + */ + instantPopup?: boolean; +}; + +/** + * This API provides access to OAuth 2 credentials. It lets you request access tokens, + * which can be used to act on behalf of the user when talking to APIs. + * + * @public + */ +export type OAuthApi = { + /** + * Requests an OAuth 2 Access Token, optionally with a set of scopes. The access token allows + * you to make requests on behalf of the user, and the copes may grant you broader access, depending + * on the auth provider. + * + * Each auth provider has separate handling of scope, so you need to look at the documentation + * for each one to know what scope you need to request. + * + * This method is cheap and should be called each time an access token is used. Do not for example + * store the access token in React component state, as that could cause the token to expire. Instead + * fetch a new access token for each request. + * + * Be sure to include all required scopes when requesting an access token. When testing your implementation + * it is best to log out the Backstage session and then visit your plugin page directly, as + * you might already have some required scopes in your existing session. Not requesting the correct + * scopes can lead to 403 or other authorization errors, which can be tricky to debug. + * + * If the user has not yet granted access to the provider and the set of requested scopes, the user + * will be prompted to log in. The returned promise will not resolve until the user has + * successfully logged in. The returned promise can be rejected, but only if the user rejects the login request. + */ + getAccessToken( + scope?: OAuthScope, + options?: AuthRequestOptions, + ): Promise; +}; + +/** + * This API provides access to OpenID Connect credentials. It lets you request ID tokens, + * which can be passed to backend services to prove the user's identity. + * + * @public + */ +export type OpenIdConnectApi = { + /** + * Requests an OpenID Connect ID Token. + * + * This method is cheap and should be called each time an ID token is used. Do not for example + * store the id token in React component state, as that could cause the token to expire. Instead + * fetch a new id token for each request. + * + * If the user has not yet logged in to Google inside Backstage, the user will be prompted + * to log in. The returned promise will not resolve until the user has successfully logged in. + * The returned promise can be rejected, but only if the user rejects the login request. + */ + getIdToken(options?: AuthRequestOptions): Promise; +}; + +/** + * This API provides access to profile information of the user from an auth provider. + * + * @public + */ +export type ProfileInfoApi = { + /** + * Get profile information for the user as supplied by this auth provider. + * + * If the optional flag is not set, a session is guaranteed to be returned, while if + * the optional flag is set, the session may be undefined. See {@link AuthRequestOptions} for more details. + */ + getProfile(options?: AuthRequestOptions): Promise; +}; + +/** + * This API provides access to the user's identity within Backstage. + * + * @remarks + * + * An auth provider that implements this interface can be used to sign-in to backstage. It is + * not intended to be used directly from a plugin, but instead serves as a connection between + * this authentication method and the app's {@link IdentityApi} + * + * @public + */ +export type BackstageIdentityApi = { + /** + * Get the user's identity within Backstage. This should normally not be called directly, + * use the {@link IdentityApi} instead. + * + * If the optional flag is not set, a session is guaranteed to be returned, while if + * the optional flag is set, the session may be undefined. See {@link AuthRequestOptions} for more details. + */ + getBackstageIdentity( + options?: AuthRequestOptions, + ): Promise; +}; + +/** + * User identity information within Backstage. + * + * @public + */ +export type BackstageUserIdentity = { + /** + * The type of identity that this structure represents. In the frontend app + * this will currently always be 'user'. + */ + type: 'user'; + + /** + * The entityRef of the user in the catalog. + * For example User:default/sandra + */ + userEntityRef: string; + + /** + * The user and group entities that the user claims ownership through + */ + ownershipEntityRefs: string[]; +}; + +/** + * Token and Identity response, with the users claims in the Identity. + * + * @public + */ +export type BackstageIdentityResponse = { + /** + * The token used to authenticate the user within Backstage. + */ + token: string; + + /** + * The time at which the token expires. If not set, it can be assumed that the token does not expire. + */ + expiresAt?: Date; + + /** + * Identity information derived from the token. + */ + identity: BackstageUserIdentity; +}; + +/** + * Profile information of the user. + * + * @public + */ +export type ProfileInfo = { + /** + * Email ID. + */ + email?: string; + + /** + * Display name that can be presented to the user. + */ + displayName?: string; + + /** + * URL to an avatar image of the user. + */ + picture?: string; +}; + +/** + * Session state values passed to subscribers of the SessionApi. + * + * @public + */ +export const SessionState = { + /** + * User signed in. + */ + SignedIn: 'SignedIn', + /** + * User not signed in. + */ + SignedOut: 'SignedOut', +} as const; + +/** + * @public + */ +export type SessionState = (typeof SessionState)[keyof typeof SessionState]; + +/** + * @public + */ +export namespace SessionState { + export type SignedIn = typeof SessionState.SignedIn; + export type SignedOut = typeof SessionState.SignedOut; +} + +/** + * The SessionApi provides basic controls for any auth provider that is tied to a persistent session. + * + * @public + */ +export type SessionApi = { + /** + * Sign in with a minimum set of permissions. + */ + signIn(): Promise; + + /** + * Sign out from the current session. This will reload the page. + */ + signOut(): Promise; + + /** + * Observe the current state of the auth session. Emits the current state on subscription. + */ + sessionState$(): Observable; +}; + +/** + * Provides authentication towards Google APIs and identities. + * + * @public + * @remarks + * + * See {@link https://developers.google.com/identity/protocols/googlescopes} for a full list of supported scopes. + * + * Note that the ID token payload is only guaranteed to contain the user's numerical Google ID, + * email and expiration information. Do not rely on any other fields, as they might not be present. + */ +export const googleAuthApiRef: ApiRef< + OAuthApi & + OpenIdConnectApi & + ProfileInfoApi & + BackstageIdentityApi & + SessionApi +> = createApiRef({ + id: 'core.auth.google', +}); + +/** + * Provides authentication towards GitHub APIs. + * + * @public + * @remarks + * + * See {@link https://developer.github.com/apps/building-oauth-apps/understanding-scopes-for-oauth-apps/} + * for a full list of supported scopes. + */ +export const githubAuthApiRef: ApiRef< + OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionApi +> = createApiRef({ + id: 'core.auth.github', +}); + +/** + * Provides authentication towards Okta APIs. + * + * @public + * @remarks + * + * See {@link https://developer.okta.com/docs/guides/implement-oauth-for-okta/scopes/} + * for a full list of supported scopes. + */ +export const oktaAuthApiRef: ApiRef< + OAuthApi & + OpenIdConnectApi & + ProfileInfoApi & + BackstageIdentityApi & + SessionApi +> = createApiRef({ + id: 'core.auth.okta', +}); + +/** + * Provides authentication towards GitLab APIs. + * + * @public + * @remarks + * + * See {@link https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html#limiting-scopes-of-a-personal-access-token} + * for a full list of supported scopes. + */ +export const gitlabAuthApiRef: ApiRef< + OAuthApi & + OpenIdConnectApi & + ProfileInfoApi & + BackstageIdentityApi & + SessionApi +> = createApiRef({ + id: 'core.auth.gitlab', +}); + +/** + * Provides authentication towards Microsoft APIs and identities. + * + * @public + * @remarks + * + * For more info and a full list of supported scopes, see: + * - {@link https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-permissions-and-consent} + * - {@link https://docs.microsoft.com/en-us/graph/permissions-reference} + */ +export const microsoftAuthApiRef: ApiRef< + OAuthApi & + OpenIdConnectApi & + ProfileInfoApi & + BackstageIdentityApi & + SessionApi +> = createApiRef({ + id: 'core.auth.microsoft', +}); + +/** + * Provides authentication towards OneLogin APIs. + * + * @public + */ +export const oneloginAuthApiRef: ApiRef< + OAuthApi & + OpenIdConnectApi & + ProfileInfoApi & + BackstageIdentityApi & + SessionApi +> = createApiRef({ + id: 'core.auth.onelogin', +}); + +/** + * Provides authentication towards Bitbucket APIs. + * + * @public + * @remarks + * + * See {@link https://support.atlassian.com/bitbucket-cloud/docs/use-oauth-on-bitbucket-cloud/} + * for a full list of supported scopes. + */ +export const bitbucketAuthApiRef: ApiRef< + OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionApi +> = createApiRef({ + id: 'core.auth.bitbucket', +}); + +/** + * Provides authentication towards Bitbucket Server APIs. + * + * @public + * @remarks + * + * See {@link https://confluence.atlassian.com/bitbucketserver/bitbucket-oauth-2-0-provider-api-1108483661.html#BitbucketOAuth2.0providerAPI-scopes} + * for a full list of supported scopes. + */ +export const bitbucketServerAuthApiRef: ApiRef< + OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionApi +> = createApiRef({ + id: 'core.auth.bitbucket-server', +}); + +/** + * Provides authentication towards Atlassian APIs. + * + * @public + * @remarks + * + * See {@link https://developer.atlassian.com/cloud/jira/platform/scopes-for-connect-and-oauth-2-3LO-apps/} + * for a full list of supported scopes. + */ +export const atlassianAuthApiRef: ApiRef< + OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionApi +> = createApiRef({ + id: 'core.auth.atlassian', +}); + +/** + * Provides authentication towards VMware Cloud APIs and identities. + * + * @public + * @remarks + * + * For more info about VMware Cloud identity and access management: + * - {@link https://docs.vmware.com/en/VMware-Cloud-services/services/Using-VMware-Cloud-Services/GUID-53D39337-D93A-4B84-BD18-DDF43C21479A.html} + */ +export const vmwareCloudAuthApiRef: ApiRef< + OAuthApi & + OpenIdConnectApi & + ProfileInfoApi & + BackstageIdentityApi & + SessionApi +> = createApiRef({ + id: 'core.auth.vmware-cloud', +}); + +/** + * Provides authentication towards OpenShift APIs and identities. + * + * @public + * @remarks + * + * See {@link https://docs.redhat.com/en/documentation/openshift_container_platform/latest/html/authentication_and_authorization/configuring-oauth-clients} + * on how to configure the OAuth clients and + * {@link https://docs.redhat.com/en/documentation/openshift_container_platform/latest/html-single/authentication_and_authorization/index#tokens-scoping-about_configuring-internal-oauth} + * for available scopes. + */ +export const openshiftAuthApiRef: ApiRef< + OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionApi +> = createApiRef({ + id: 'core.auth.openshift', +}); diff --git a/packages/frontend-plugin-api/src/apis/definitions/index.ts b/packages/frontend-plugin-api/src/apis/definitions/index.ts index 7533481d01..5e7f713fcf 100644 --- a/packages/frontend-plugin-api/src/apis/definitions/index.ts +++ b/packages/frontend-plugin-api/src/apis/definitions/index.ts @@ -33,6 +33,7 @@ export { export * from './auth'; export * from './AlertApi'; +export * from './AppLanguageApi'; export * from './AppThemeApi'; export * from './SwappableComponentsApi'; export * from './ConfigApi'; @@ -47,3 +48,4 @@ export * from './OAuthRequestApi'; export * from './RouteResolutionApi'; export * from './StorageApi'; export * from './AnalyticsApi'; +export * from './TranslationApi'; diff --git a/packages/frontend-plugin-api/src/apis/system/ApiRef.test.ts b/packages/frontend-plugin-api/src/apis/system/ApiRef.test.ts new file mode 100644 index 0000000000..dab872236f --- /dev/null +++ b/packages/frontend-plugin-api/src/apis/system/ApiRef.test.ts @@ -0,0 +1,50 @@ +/* + * Copyright 2020 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 { createApiRef } from './ApiRef'; + +describe('ApiRef', () => { + it('should be created', () => { + const ref = createApiRef({ id: 'abc' }); + expect(ref.id).toBe('abc'); + expect(String(ref)).toBe('apiRef{abc}'); + expect(() => ref.T).toThrow('tried to read ApiRef.T of apiRef{abc}'); + }); + + it('should reject invalid ids', () => { + for (const id of ['a', 'abc', 'ab-c', 'a.b.c', 'a-b.c', 'abc.a-b-c.abc3']) { + expect(createApiRef({ id }).id).toBe(id); + } + + for (const id of [ + '123', + 'ab-3', + 'ab_c', + '.', + '2ac', + 'ab.3a', + '.abc', + 'abc.', + 'ab..s', + '', + '_', + ]) { + expect(() => createApiRef({ id }).id).toThrow( + `API id must only contain period separated lowercase alphanum tokens with dashes, got '${id}'`, + ); + } + }); +}); diff --git a/packages/frontend-plugin-api/src/apis/system/ApiRef.ts b/packages/frontend-plugin-api/src/apis/system/ApiRef.ts index 0d1f8312cb..adedff9f73 100644 --- a/packages/frontend-plugin-api/src/apis/system/ApiRef.ts +++ b/packages/frontend-plugin-api/src/apis/system/ApiRef.ts @@ -14,8 +14,51 @@ * limitations under the License. */ -export { - type ApiRef, - type ApiRefConfig, - createApiRef, -} from '@backstage/core-plugin-api'; +import type { ApiRef } from './types'; + +/** + * API reference configuration - holds an ID of the referenced API. + * + * @public + */ +export type ApiRefConfig = { + id: string; +}; + +class ApiRefImpl implements ApiRef { + constructor(private readonly config: ApiRefConfig) { + const valid = config.id + .split('.') + .flatMap(part => part.split('-')) + .every(part => part.match(/^[a-z][a-z0-9]*$/)); + if (!valid) { + throw new Error( + `API id must only contain period separated lowercase alphanum tokens with dashes, got '${config.id}'`, + ); + } + } + + get id(): string { + return this.config.id; + } + + // Utility for getting type of an api, using `typeof apiRef.T` + get T(): T { + throw new Error(`tried to read ApiRef.T of ${this}`); + } + + toString() { + return `apiRef{${this.config.id}}`; + } +} + +/** + * Creates a reference to an API. + * + * @param config - The descriptor of the API to reference. + * @returns An API reference. + * @public + */ +export function createApiRef(config: ApiRefConfig): ApiRef { + return new ApiRefImpl(config); +} diff --git a/packages/frontend-plugin-api/src/apis/system/helpers.ts b/packages/frontend-plugin-api/src/apis/system/helpers.ts index 5de265be99..eaffed3003 100644 --- a/packages/frontend-plugin-api/src/apis/system/helpers.ts +++ b/packages/frontend-plugin-api/src/apis/system/helpers.ts @@ -1,5 +1,5 @@ /* - * Copyright 2023 The Backstage Authors + * Copyright 2020 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. @@ -14,4 +14,61 @@ * limitations under the License. */ -export { createApiFactory } from '@backstage/core-plugin-api'; +import { ApiRef, ApiFactory, TypesToApiRefs } from './types'; + +/** + * Used to infer types for a standalone {@link ApiFactory} that isn't immediately passed + * to another function. + * + * @remarks + * + * This function doesn't actually do anything, it's only used to infer types. + * + * @public + */ +export function createApiFactory< + Api, + Impl extends Api, + Deps extends { [name in string]: unknown }, +>(factory: ApiFactory): ApiFactory; +/** + * Used to infer types for a standalone {@link ApiFactory} that isn't immediately passed + * to another function. + * + * @param api - Ref of the API that will be produced by the factory. + * @param instance - Implementation of the API to use. + * @public + */ +export function createApiFactory( + api: ApiRef, + instance: Impl, +): ApiFactory; +/** + * Used to infer types for a standalone {@link ApiFactory} that isn't immediately passed + * to another function. + * + * @remarks + * + * Creates factory from {@link ApiRef} or returns the factory itself if provided. + * + * @param factory - Existing factory or {@link ApiRef}. + * @param instance - The instance to be returned by the factory. + * @public + */ +export function createApiFactory< + Api, + Impl extends Api, + Deps extends { [name in string]: unknown }, +>( + factory: ApiFactory | ApiRef, + instance?: Impl, +): ApiFactory { + if ('id' in factory) { + return { + api: factory, + deps: {} as TypesToApiRefs, + factory: () => instance!, + }; + } + return factory; +} diff --git a/packages/frontend-plugin-api/src/apis/system/index.ts b/packages/frontend-plugin-api/src/apis/system/index.ts index b1bd3ac2b3..964750474e 100644 --- a/packages/frontend-plugin-api/src/apis/system/index.ts +++ b/packages/frontend-plugin-api/src/apis/system/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2023 The Backstage Authors + * Copyright 2020 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. diff --git a/packages/frontend-plugin-api/src/apis/system/types.ts b/packages/frontend-plugin-api/src/apis/system/types.ts index cc44e19e3f..96614c320c 100644 --- a/packages/frontend-plugin-api/src/apis/system/types.ts +++ b/packages/frontend-plugin-api/src/apis/system/types.ts @@ -1,5 +1,5 @@ /* - * Copyright 2023 The Backstage Authors + * Copyright 2020 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. @@ -14,11 +14,61 @@ * limitations under the License. */ -export type { - ApiRef, - AnyApiRef, - TypesToApiRefs, - ApiHolder, - ApiFactory, - AnyApiFactory, -} from '@backstage/core-plugin-api'; +/** + * API reference. + * + * @public + */ +export type ApiRef = { + id: string; + T: T; +}; + +/** + * Catch-all {@link ApiRef} type. + * + * @public + */ +export type AnyApiRef = ApiRef; + +/** + * Wraps a type with API properties into a type holding their respective {@link ApiRef}s. + * + * @public + */ +export type TypesToApiRefs = { [key in keyof T]: ApiRef }; + +/** + * Provides lookup of APIs through their {@link ApiRef}s. + * + * @public + */ +export type ApiHolder = { + get(api: ApiRef): T | undefined; +}; + +/** + * Describes type returning API implementations. + * + * @public + */ +export type ApiFactory< + Api, + Impl extends Api, + Deps extends { [name in string]: unknown }, +> = { + api: ApiRef; + deps: TypesToApiRefs; + factory(deps: Deps): Impl; +}; + +/** + * Catch-all {@link ApiFactory} type. + * + * @public + */ +export type AnyApiFactory = ApiFactory< + unknown, + unknown, + { [key in string]: unknown } +>; diff --git a/packages/frontend-plugin-api/src/apis/system/useApi.test.tsx b/packages/frontend-plugin-api/src/apis/system/useApi.test.tsx new file mode 100644 index 0000000000..4b9d80fb62 --- /dev/null +++ b/packages/frontend-plugin-api/src/apis/system/useApi.test.tsx @@ -0,0 +1,40 @@ +/* + * Copyright 2020 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 { renderHook } from '@testing-library/react'; +import { createVersionedContextForTesting } from '@backstage/version-bridge'; +import { createApiRef } from './ApiRef'; +import { useApi } from './useApi'; + +describe('useApi', () => { + const context = createVersionedContextForTesting('api-context'); + + afterEach(() => { + context.reset(); + }); + + it('should resolve routes', () => { + const get = jest.fn(() => 'my-api-impl'); + context.set({ 1: { get } }); + + const apiRef = createApiRef({ id: 'x' }); + const renderedHook = renderHook(() => useApi(apiRef)); + + const value = renderedHook.result.current; + expect(value).toBe('my-api-impl'); + expect(get).toHaveBeenCalledWith(apiRef); + }); +}); diff --git a/packages/frontend-plugin-api/src/apis/system/useApi.tsx b/packages/frontend-plugin-api/src/apis/system/useApi.tsx index d2efa49fa1..30e39033f7 100644 --- a/packages/frontend-plugin-api/src/apis/system/useApi.tsx +++ b/packages/frontend-plugin-api/src/apis/system/useApi.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2023 The Backstage Authors + * Copyright 2020 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. @@ -14,4 +14,81 @@ * limitations under the License. */ -export { useApiHolder, useApi, withApis } from '@backstage/core-plugin-api'; +import { ComponentType, PropsWithChildren } from 'react'; +import { ApiRef, ApiHolder, TypesToApiRefs } from './types'; +import { useVersionedContext } from '@backstage/version-bridge'; +import { NotImplementedError } from '@backstage/errors'; + +/** + * React hook for retrieving {@link ApiHolder}, an API catalog. + * + * @public + */ +export function useApiHolder(): ApiHolder { + const versionedHolder = useVersionedContext<{ 1: ApiHolder }>('api-context'); + if (!versionedHolder) { + throw new NotImplementedError('API context is not available'); + } + + const apiHolder = versionedHolder.atVersion(1); + if (!apiHolder) { + throw new NotImplementedError('ApiContext v1 not available'); + } + return apiHolder; +} + +/** + * React hook for retrieving APIs. + * + * @param apiRef - Reference of the API to use. + * @public + */ +export function useApi(apiRef: ApiRef): T { + const apiHolder = useApiHolder(); + + const api = apiHolder.get(apiRef); + if (!api) { + throw new NotImplementedError(`No implementation available for ${apiRef}`); + } + return api; +} + +/** + * Wrapper for giving component an API context. + * + * @param apis - APIs for the context. + * @public + */ +export function withApis(apis: TypesToApiRefs) { + return function withApisWrapper( + WrappedComponent: ComponentType, + ) { + const Hoc = (props: PropsWithChildren>) => { + const apiHolder = useApiHolder(); + + const impls = {} as T; + + for (const key in apis) { + if (apis.hasOwnProperty(key)) { + const ref = apis[key]; + + const api = apiHolder.get(ref); + if (!api) { + throw new NotImplementedError( + `No implementation available for ${ref}`, + ); + } + impls[key] = api; + } + } + + return ; + }; + const displayName = + WrappedComponent.displayName || WrappedComponent.name || 'Component'; + + Hoc.displayName = `withApis(${displayName})`; + + return Hoc; + }; +} diff --git a/packages/frontend-plugin-api/src/blueprints/ApiBlueprint.test.ts b/packages/frontend-plugin-api/src/blueprints/ApiBlueprint.test.ts index 988c5a15c0..6d16a215e1 100644 --- a/packages/frontend-plugin-api/src/blueprints/ApiBlueprint.test.ts +++ b/packages/frontend-plugin-api/src/blueprints/ApiBlueprint.test.ts @@ -16,7 +16,7 @@ import { createExtensionInput } from '../wiring'; import { ApiBlueprint } from './ApiBlueprint'; -import { createApiRef } from '@backstage/core-plugin-api'; +import { createApiRef } from '../apis/system'; describe('ApiBlueprint', () => { it('should create an extension with sensible defaults', () => { diff --git a/packages/frontend-plugin-api/src/blueprints/NavItemBlueprint.ts b/packages/frontend-plugin-api/src/blueprints/NavItemBlueprint.ts index 197f72da73..ce59a9439b 100644 --- a/packages/frontend-plugin-api/src/blueprints/NavItemBlueprint.ts +++ b/packages/frontend-plugin-api/src/blueprints/NavItemBlueprint.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { IconComponent } from '@backstage/core-plugin-api'; +import { IconComponent } from '../icons/types'; import { RouteRef } from '../routing'; import { createExtensionBlueprint, createExtensionDataRef } from '../wiring'; diff --git a/packages/frontend-plugin-api/src/blueprints/ThemeBlueprint.test.ts b/packages/frontend-plugin-api/src/blueprints/ThemeBlueprint.test.ts index 5ec164fa8b..7a53f28fa3 100644 --- a/packages/frontend-plugin-api/src/blueprints/ThemeBlueprint.test.ts +++ b/packages/frontend-plugin-api/src/blueprints/ThemeBlueprint.test.ts @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { AppTheme } from '@backstage/core-plugin-api'; +import { AppTheme } from '../apis/definitions/AppThemeApi'; import { ThemeBlueprint } from './ThemeBlueprint'; import { createExtensionTester } from '@backstage/frontend-test-utils'; diff --git a/packages/frontend-plugin-api/src/blueprints/ThemeBlueprint.ts b/packages/frontend-plugin-api/src/blueprints/ThemeBlueprint.ts index 323078bc4e..aaf4f32608 100644 --- a/packages/frontend-plugin-api/src/blueprints/ThemeBlueprint.ts +++ b/packages/frontend-plugin-api/src/blueprints/ThemeBlueprint.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { AppTheme } from '@backstage/core-plugin-api'; +import { AppTheme } from '../apis/definitions/AppThemeApi'; import { createExtensionBlueprint, createExtensionDataRef } from '../wiring'; const themeDataRef = createExtensionDataRef().with({ diff --git a/packages/frontend-plugin-api/src/components/ExtensionBoundary.test.tsx b/packages/frontend-plugin-api/src/components/ExtensionBoundary.test.tsx index 6cce114d4d..ae741f8922 100644 --- a/packages/frontend-plugin-api/src/components/ExtensionBoundary.test.tsx +++ b/packages/frontend-plugin-api/src/components/ExtensionBoundary.test.tsx @@ -16,14 +16,11 @@ import { useEffect } from 'react'; import { act, screen, waitFor } from '@testing-library/react'; -import { - mockApis, - TestApiProvider, - withLogCollector, -} from '@backstage/test-utils'; +import { TestApiProvider, withLogCollector } from '@backstage/test-utils'; import { ExtensionBoundary } from './ExtensionBoundary'; import { coreExtensionData, createExtension } from '../wiring'; -import { analyticsApiRef, useAnalytics } from '@backstage/core-plugin-api'; +import { analyticsApiRef } from '../apis/definitions/AnalyticsApi'; +import { useAnalytics } from '../analytics'; import { createRouteRef } from '../routing'; import { createExtensionTester, @@ -93,7 +90,7 @@ describe('ExtensionBoundary', () => { it('should wrap children with analytics context', async () => { const action = 'render'; const subject = 'analytics'; - const analyticsApiMock = mockApis.analytics(); + const analyticsApiMock = { captureEvent: jest.fn() }; const AnalyticsComponent = () => { const analytics = useAnalytics(); @@ -134,7 +131,7 @@ describe('ExtensionBoundary', () => { }); return null; }; - const analyticsApiMock = mockApis.analytics(); + const analyticsApiMock = { captureEvent: jest.fn() }; await act(async () => { renderInTestApp( diff --git a/packages/frontend-plugin-api/src/components/ExtensionBoundary.tsx b/packages/frontend-plugin-api/src/components/ExtensionBoundary.tsx index 848ea64a2c..421f41be56 100644 --- a/packages/frontend-plugin-api/src/components/ExtensionBoundary.tsx +++ b/packages/frontend-plugin-api/src/components/ExtensionBoundary.tsx @@ -21,7 +21,7 @@ import { useEffect, lazy as reactLazy, } from 'react'; -import { AnalyticsContext, useAnalytics } from '@backstage/core-plugin-api'; +import { AnalyticsContext, useAnalytics } from '../analytics'; import { ErrorBoundary } from './ErrorBoundary'; // eslint-disable-next-line @backstage/no-relative-monorepo-imports import { routableExtensionRenderedEvent } from '../../../core-plugin-api/src/analytics/Tracker'; diff --git a/packages/frontend-plugin-api/src/components/ExtensionSuspense.test.tsx b/packages/frontend-plugin-api/src/components/ExtensionSuspense.test.tsx index 91aaeb2a25..ee1136fca3 100644 --- a/packages/frontend-plugin-api/src/components/ExtensionSuspense.test.tsx +++ b/packages/frontend-plugin-api/src/components/ExtensionSuspense.test.tsx @@ -31,7 +31,7 @@ describe('ExtensionSuspense', () => { ), ); - expect(screen.getByTestId('progress')).toBeInTheDocument(); + expect(screen.getByTestId('core-progress')).toBeInTheDocument(); }); it('should render the lazy loaded children component', async () => { diff --git a/packages/frontend-plugin-api/src/components/ExtensionSuspense.tsx b/packages/frontend-plugin-api/src/components/ExtensionSuspense.tsx index 0ae8b413e4..9439cafed8 100644 --- a/packages/frontend-plugin-api/src/components/ExtensionSuspense.tsx +++ b/packages/frontend-plugin-api/src/components/ExtensionSuspense.tsx @@ -15,7 +15,7 @@ */ import { ReactNode, Suspense } from 'react'; -import { useApp } from '@backstage/core-plugin-api'; +import { Progress } from './DefaultSwappableComponents'; /** @public */ export interface ExtensionSuspenseProps { @@ -26,8 +26,5 @@ export interface ExtensionSuspenseProps { export function ExtensionSuspense(props: ExtensionSuspenseProps) { const { children } = props; - const app = useApp(); - const { Progress } = app.getComponents(); - return }>{children}; } diff --git a/packages/core-plugin-api/src/translation/TranslationMessages.test.ts b/packages/frontend-plugin-api/src/translation/TranslationMessages.test.ts similarity index 100% rename from packages/core-plugin-api/src/translation/TranslationMessages.test.ts rename to packages/frontend-plugin-api/src/translation/TranslationMessages.test.ts diff --git a/packages/core-plugin-api/src/translation/TranslationMessages.ts b/packages/frontend-plugin-api/src/translation/TranslationMessages.ts similarity index 95% rename from packages/core-plugin-api/src/translation/TranslationMessages.ts rename to packages/frontend-plugin-api/src/translation/TranslationMessages.ts index 0108fb9f18..83dd2ef557 100644 --- a/packages/core-plugin-api/src/translation/TranslationMessages.ts +++ b/packages/frontend-plugin-api/src/translation/TranslationMessages.ts @@ -14,12 +14,12 @@ * limitations under the License. */ -import { TranslationRef } from '@backstage/core-plugin-api/alpha'; +import { TranslationRef } from './TranslationRef'; /** * Represents a collection of messages to be provided for a given translation ref. * - * @alpha + * @public * @remarks * * This collection of messages can either be used directly as an override for the @@ -43,7 +43,7 @@ export interface TranslationMessages< /** * Options for {@link createTranslationMessages}. * - * @alpha + * @public */ export interface TranslationMessagesOptions< TId extends string, @@ -62,7 +62,7 @@ export interface TranslationMessagesOptions< /** * Creates a collection of messages for a given translation ref. * - * @alpha + * @public */ export function createTranslationMessages< TId extends string, diff --git a/packages/core-plugin-api/src/translation/TranslationRef.test.ts b/packages/frontend-plugin-api/src/translation/TranslationRef.test.ts similarity index 100% rename from packages/core-plugin-api/src/translation/TranslationRef.test.ts rename to packages/frontend-plugin-api/src/translation/TranslationRef.test.ts diff --git a/packages/core-plugin-api/src/translation/TranslationRef.ts b/packages/frontend-plugin-api/src/translation/TranslationRef.ts similarity index 99% rename from packages/core-plugin-api/src/translation/TranslationRef.ts rename to packages/frontend-plugin-api/src/translation/TranslationRef.ts index c2ac7a5ce3..7686e68343 100644 --- a/packages/core-plugin-api/src/translation/TranslationRef.ts +++ b/packages/frontend-plugin-api/src/translation/TranslationRef.ts @@ -19,7 +19,7 @@ import { TranslationResource, } from './TranslationResource'; -/** @alpha */ +/** @public */ export interface TranslationRef< TId extends string = string, TMessages extends { [key in string]: string } = { [key in string]: string }, @@ -83,7 +83,7 @@ export interface InternalTranslationRef< getDefaultResource(): TranslationResource | undefined; } -/** @alpha */ +/** @public */ export interface TranslationRefOptions< TId extends string, TNestedMessages extends AnyNestedMessages, @@ -164,7 +164,7 @@ class TranslationRefImpl< } } -/** @alpha */ +/** @public */ export function createTranslationRef< TId extends string, const TNestedMessages extends AnyNestedMessages, diff --git a/packages/core-plugin-api/src/translation/TranslationResource.test.ts b/packages/frontend-plugin-api/src/translation/TranslationResource.test.ts similarity index 100% rename from packages/core-plugin-api/src/translation/TranslationResource.test.ts rename to packages/frontend-plugin-api/src/translation/TranslationResource.test.ts diff --git a/packages/core-plugin-api/src/translation/TranslationResource.ts b/packages/frontend-plugin-api/src/translation/TranslationResource.ts similarity index 94% rename from packages/core-plugin-api/src/translation/TranslationResource.ts rename to packages/frontend-plugin-api/src/translation/TranslationResource.ts index 0184c55b98..c317bda381 100644 --- a/packages/core-plugin-api/src/translation/TranslationResource.ts +++ b/packages/frontend-plugin-api/src/translation/TranslationResource.ts @@ -14,12 +14,10 @@ * limitations under the License. */ -import { - TranslationMessages, - TranslationRef, -} from '@backstage/core-plugin-api/alpha'; +import { TranslationMessages } from './TranslationMessages'; +import { TranslationRef } from './TranslationRef'; -/** @alpha */ +/** @public */ export interface TranslationResource { $$type: '@backstage/TranslationResource'; id: TId; @@ -55,7 +53,7 @@ export function toInternalTranslationResource( return r; } -/** @alpha */ +/** @public */ export interface TranslationResourceOptions< TId extends string, TMessages extends { [key in string]: string }, @@ -72,7 +70,7 @@ export interface TranslationResourceOptions< translations: TTranslations; } -/** @alpha */ +/** @public */ export function createTranslationResource< TId extends string, TMessages extends { [key in string]: string }, diff --git a/packages/core-plugin-api/src/translation/__fixtures__/counting-de.ts b/packages/frontend-plugin-api/src/translation/__fixtures__/counting-de.ts similarity index 100% rename from packages/core-plugin-api/src/translation/__fixtures__/counting-de.ts rename to packages/frontend-plugin-api/src/translation/__fixtures__/counting-de.ts diff --git a/packages/core-plugin-api/src/translation/__fixtures__/counting-sv.json b/packages/frontend-plugin-api/src/translation/__fixtures__/counting-sv.json similarity index 100% rename from packages/core-plugin-api/src/translation/__fixtures__/counting-sv.json rename to packages/frontend-plugin-api/src/translation/__fixtures__/counting-sv.json diff --git a/packages/core-plugin-api/src/translation/__fixtures__/fruits-de.json b/packages/frontend-plugin-api/src/translation/__fixtures__/fruits-de.json similarity index 100% rename from packages/core-plugin-api/src/translation/__fixtures__/fruits-de.json rename to packages/frontend-plugin-api/src/translation/__fixtures__/fruits-de.json diff --git a/packages/core-plugin-api/src/translation/__fixtures__/fruits-sv.ts b/packages/frontend-plugin-api/src/translation/__fixtures__/fruits-sv.ts similarity index 100% rename from packages/core-plugin-api/src/translation/__fixtures__/fruits-sv.ts rename to packages/frontend-plugin-api/src/translation/__fixtures__/fruits-sv.ts diff --git a/packages/core-plugin-api/src/translation/__fixtures__/refs.ts b/packages/frontend-plugin-api/src/translation/__fixtures__/refs.ts similarity index 100% rename from packages/core-plugin-api/src/translation/__fixtures__/refs.ts rename to packages/frontend-plugin-api/src/translation/__fixtures__/refs.ts diff --git a/packages/frontend-plugin-api/src/translation/index.ts b/packages/frontend-plugin-api/src/translation/index.ts index ef3ccccbe3..121ec62c72 100644 --- a/packages/frontend-plugin-api/src/translation/index.ts +++ b/packages/frontend-plugin-api/src/translation/index.ts @@ -17,12 +17,16 @@ export { type TranslationMessages, type TranslationMessagesOptions, + createTranslationMessages, +} from './TranslationMessages'; +export { type TranslationResource, type TranslationResourceOptions, + createTranslationResource, +} from './TranslationResource'; +export { type TranslationRef, type TranslationRefOptions, - createTranslationMessages, - createTranslationResource, createTranslationRef, - useTranslationRef, -} from '@backstage/core-plugin-api/alpha'; +} from './TranslationRef'; +export { useTranslationRef } from './useTranslationRef'; diff --git a/packages/core-plugin-api/src/translation/useTranslationRef.test.tsx b/packages/frontend-plugin-api/src/translation/useTranslationRef.test.tsx similarity index 98% rename from packages/core-plugin-api/src/translation/useTranslationRef.test.tsx rename to packages/frontend-plugin-api/src/translation/useTranslationRef.test.tsx index b1fbd8a904..a7f5bd41ed 100644 --- a/packages/core-plugin-api/src/translation/useTranslationRef.test.tsx +++ b/packages/frontend-plugin-api/src/translation/useTranslationRef.test.tsx @@ -27,11 +27,11 @@ import { useTranslationRef } from './useTranslationRef'; import { I18nextTranslationApi } from '../../../core-app-api/src/apis/implementations/TranslationApi'; // eslint-disable-next-line @backstage/no-relative-monorepo-imports import { AppLanguageSelector } from '../../..//core-app-api/src/apis/implementations/AppLanguageApi'; +import { createTranslationResource } from './TranslationResource'; import { - createTranslationResource, TranslationApi, translationApiRef, -} from '../alpha'; +} from '../apis/definitions/TranslationApi'; import { ErrorApi, errorApiRef } from '../apis'; const plainRef = createTranslationRef({ diff --git a/packages/core-plugin-api/src/translation/useTranslationRef.ts b/packages/frontend-plugin-api/src/translation/useTranslationRef.ts similarity index 98% rename from packages/core-plugin-api/src/translation/useTranslationRef.ts rename to packages/frontend-plugin-api/src/translation/useTranslationRef.ts index b874ac93d8..2e029f82fa 100644 --- a/packages/core-plugin-api/src/translation/useTranslationRef.ts +++ b/packages/frontend-plugin-api/src/translation/useTranslationRef.ts @@ -20,13 +20,13 @@ import { translationApiRef, TranslationFunction, TranslationSnapshot, -} from '../apis/alpha'; +} from '../apis/definitions/TranslationApi'; import { TranslationRef } from './TranslationRef'; // Make sure we don't fill the logs with loading errors for the same ref const loggedRefs = new WeakSet>(); -/** @alpha */ +/** @public */ export const useTranslationRef = < TMessages extends { [key in string]: string }, >( diff --git a/packages/test-utils/src/testUtils/apis/TranslationApi/MockTranslationApi.ts b/packages/test-utils/src/testUtils/apis/TranslationApi/MockTranslationApi.ts index 344679355a..2989207743 100644 --- a/packages/test-utils/src/testUtils/apis/TranslationApi/MockTranslationApi.ts +++ b/packages/test-utils/src/testUtils/apis/TranslationApi/MockTranslationApi.ts @@ -25,7 +25,7 @@ import ObservableImpl from 'zen-observable'; import { Observable } from '@backstage/types'; // Internal import to avoid code duplication, this will lead to duplication in build output // eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { toInternalTranslationRef } from '../../../../../core-plugin-api/src/translation/TranslationRef'; +import { toInternalTranslationRef } from '../../../../../frontend-plugin-api/src/translation/TranslationRef'; // eslint-disable-next-line @backstage/no-relative-monorepo-imports import { JsxInterpolator } from '../../../../../core-app-api/src/apis/implementations/TranslationApi/I18nextTranslationApi';