From 1e0230e30494d1a63be9efa2b0b45eb08916d465 Mon Sep 17 00:00:00 2001 From: Gasan Guseinov Date: Thu, 5 Dec 2024 19:15:43 +0100 Subject: [PATCH 001/109] custom AuthConnector for OAuth2 Signed-off-by: Gasan Guseinov --- .changeset/brave-pandas-beam.md | 5 ++ .../implementations/auth/oauth2/OAuth2.ts | 47 ++++++++++++++----- packages/core-app-api/src/index.ts | 4 ++ 3 files changed, 43 insertions(+), 13 deletions(-) create mode 100644 .changeset/brave-pandas-beam.md diff --git a/.changeset/brave-pandas-beam.md b/.changeset/brave-pandas-beam.md new file mode 100644 index 0000000000..3defff8aa6 --- /dev/null +++ b/.changeset/brave-pandas-beam.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-app-api': patch +--- + +custom AuthConnector for OAuth2 diff --git a/packages/core-app-api/src/apis/implementations/auth/oauth2/OAuth2.ts b/packages/core-app-api/src/apis/implementations/auth/oauth2/OAuth2.ts index 4963330724..9162896ebe 100644 --- a/packages/core-app-api/src/apis/implementations/auth/oauth2/OAuth2.ts +++ b/packages/core-app-api/src/apis/implementations/auth/oauth2/OAuth2.ts @@ -15,6 +15,7 @@ */ import { + AuthConnector, DefaultAuthConnector, PopupOptions, } from '../../../../lib/AuthConnector'; @@ -43,6 +44,9 @@ import { OAuthApiCreateOptions } from '../types'; export type OAuth2CreateOptions = OAuthApiCreateOptions & { scopeTransform?: (scopes: string[]) => string[]; popupOptions?: PopupOptions; + authConnectorFactory?: ( + opts: OAuth2CreateOptions, + ) => AuthConnector; }; export type OAuth2Response = { @@ -79,23 +83,22 @@ export default class OAuth2 BackstageIdentityApi, SessionApi { - static create(options: OAuth2CreateOptions) { + private static createDefaultAuthConnector(options: OAuth2CreateOptions) { const { - configApi, - discoveryApi, - environment = 'development', - provider = DEFAULT_PROVIDER, - oauthRequestApi, - defaultScopes = [], - scopeTransform = x => x, - popupOptions, - } = options; - - const connector = new DefaultAuthConnector({ configApi, discoveryApi, environment, provider, + oauthRequestApi, + scopeTransform, + popupOptions, + } = options; + + return new DefaultAuthConnector({ + configApi, + discoveryApi, + environment: environment!, + provider: provider!, oauthRequestApi: oauthRequestApi, sessionTransform({ backstageIdentity, @@ -107,7 +110,7 @@ export default class OAuth2 idToken: res.providerInfo.idToken, accessToken: res.providerInfo.accessToken, scopes: OAuth2.normalizeScopes( - scopeTransform, + scopeTransform!, res.providerInfo.scope, ), expiresAt: res.providerInfo.expiresInSeconds @@ -128,6 +131,24 @@ export default class OAuth2 }, popupOptions, }); + } + + static create(options: OAuth2CreateOptions) { + const { + environment = 'development', + provider = DEFAULT_PROVIDER, + defaultScopes = [], + scopeTransform = x => x, + authConnectorFactory = (opts: OAuth2CreateOptions) => + OAuth2.createDefaultAuthConnector(opts), + } = options; + + const connector = authConnectorFactory({ + ...options, + scopeTransform, + environment, + provider, + }); const sessionManager = new RefreshingAuthSessionManager({ connector, diff --git a/packages/core-app-api/src/index.ts b/packages/core-app-api/src/index.ts index a5e6b649e9..9033622b9d 100644 --- a/packages/core-app-api/src/index.ts +++ b/packages/core-app-api/src/index.ts @@ -23,3 +23,7 @@ export * from './apis'; export * from './app'; export * from './routing'; +export type { AuthConnector } from './lib'; +export type { CreateSessionOptions } from './lib'; +export { showLoginPopup } from './lib'; +export type { LoginPopupOptions } from './lib'; From 976b4fedd829ef97313116a2775c2415fe2996c8 Mon Sep 17 00:00:00 2001 From: Gasan Guseinov Date: Mon, 9 Dec 2024 15:33:44 +0100 Subject: [PATCH 002/109] update api specification Signed-off-by: Gasan Guseinov --- packages/core-app-api/report.api.md | 3 +++ packages/core-app-api/src/lib/AuthConnector/types.ts | 5 +++++ packages/core-app-api/src/lib/loginPopup.ts | 4 ++++ 3 files changed, 12 insertions(+) diff --git a/packages/core-app-api/report.api.md b/packages/core-app-api/report.api.md index 3d2c44a148..0cec898bfd 100644 --- a/packages/core-app-api/report.api.md +++ b/packages/core-app-api/report.api.md @@ -551,6 +551,9 @@ export class OAuth2 export type OAuth2CreateOptions = OAuthApiCreateOptions & { scopeTransform?: (scopes: string[]) => string[]; popupOptions?: PopupOptions; + authConnectorFactory?: ( + opts: OAuth2CreateOptions, + ) => AuthConnector; }; // @public diff --git a/packages/core-app-api/src/lib/AuthConnector/types.ts b/packages/core-app-api/src/lib/AuthConnector/types.ts index 3c01bb254f..1ba96c6cb8 100644 --- a/packages/core-app-api/src/lib/AuthConnector/types.ts +++ b/packages/core-app-api/src/lib/AuthConnector/types.ts @@ -14,6 +14,9 @@ * limitations under the License. */ +/** + * @public + */ export type CreateSessionOptions = { scopes: Set; instantPopup?: boolean; @@ -22,6 +25,8 @@ export type CreateSessionOptions = { /** * An AuthConnector is responsible for realizing auth session actions * by for example communicating with a backend or interacting with the user. + * + * @public */ export type AuthConnector = { createSession(options: CreateSessionOptions): Promise; diff --git a/packages/core-app-api/src/lib/loginPopup.ts b/packages/core-app-api/src/lib/loginPopup.ts index b6c14d60c9..9ab75501b0 100644 --- a/packages/core-app-api/src/lib/loginPopup.ts +++ b/packages/core-app-api/src/lib/loginPopup.ts @@ -16,6 +16,8 @@ /** * Options used to open a login popup. + * + * @public */ export type LoginPopupOptions = { /** @@ -65,6 +67,8 @@ type AuthResult = * to the app window. The message posted to the app must match the AuthResult type. * * The returned promise resolves to the response of the message that was posted from the auth popup. + * + * @public */ export function showLoginPopup(options: LoginPopupOptions): Promise { return new Promise((resolve, reject) => { From fe015869b39fbf6ac1c416a8132fff95ecef0409 Mon Sep 17 00:00:00 2001 From: Gasan Guseinov Date: Mon, 9 Dec 2024 16:07:37 +0100 Subject: [PATCH 003/109] update api specification Signed-off-by: Gasan Guseinov --- packages/core-app-api/report.api.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/packages/core-app-api/report.api.md b/packages/core-app-api/report.api.md index 0cec898bfd..73d940d1c4 100644 --- a/packages/core-app-api/report.api.md +++ b/packages/core-app-api/report.api.md @@ -294,6 +294,13 @@ export type AuthApiCreateOptions = { configApi?: ConfigApi; }; +// @public +export type AuthConnector = { + createSession(options: CreateSessionOptions): Promise; + refreshSession(scopes?: Set): Promise; + removeSession(): Promise; +}; + // @public export type BackstageApp = { getPlugins(): BackstagePlugin[]; @@ -353,6 +360,12 @@ export function createFetchApi(options: { middleware?: FetchMiddleware | FetchMiddleware[] | undefined; }): FetchApi; +// @public (undocumented) +export type CreateSessionOptions = { + scopes: Set; + instantPopup?: boolean; +}; + // @public export function createSpecializedApp(options: AppOptions): BackstageApp; @@ -478,6 +491,15 @@ export class LocalStorageFeatureFlags implements FeatureFlagsApi { save(options: FeatureFlagsSaveOptions): void; } +// @public +export type LoginPopupOptions = { + url: string; + name: string; + origin: string; + width?: number; + height?: number; +}; + // @public export class MicrosoftAuth { // (undocumented) @@ -640,6 +662,9 @@ export class SamlAuth signOut(): Promise; } +// @public +export function showLoginPopup(options: LoginPopupOptions): Promise; + // @public export type SignInPageProps = PropsWithChildren<{ onSignInSuccess(identityApi: IdentityApi): void; From 0553465f7d6e9d60af5884368adfe23b9a43b9b2 Mon Sep 17 00:00:00 2001 From: Gasan Guseinov Date: Wed, 8 Jan 2025 18:36:16 +0100 Subject: [PATCH 004/109] updates after code review Signed-off-by: Gasan Guseinov --- .changeset/brave-pandas-beam.md | 16 ++- packages/core-app-api/report.api.md | 54 +++++---- .../implementations/auth/oauth2/OAuth2.ts | 109 ++++++++---------- packages/core-app-api/src/index.ts | 11 +- .../DefaultAuthConnector.test.ts | 19 ++- .../lib/AuthConnector/DefaultAuthConnector.ts | 22 ++-- .../lib/AuthConnector/DirectAuthConnector.ts | 7 +- .../src/lib/AuthConnector/types.ts | 17 ++- .../RefreshingAuthSessionManager.test.ts | 37 +++--- .../RefreshingAuthSessionManager.ts | 6 +- .../core-app-api/src/lib/loginPopup.test.ts | 42 +++---- packages/core-app-api/src/lib/loginPopup.ts | 15 ++- 12 files changed, 192 insertions(+), 163 deletions(-) diff --git a/.changeset/brave-pandas-beam.md b/.changeset/brave-pandas-beam.md index 3defff8aa6..8dd525d749 100644 --- a/.changeset/brave-pandas-beam.md +++ b/.changeset/brave-pandas-beam.md @@ -1,5 +1,17 @@ --- -'@backstage/core-app-api': patch +'@backstage/core-app-api': minor --- -custom AuthConnector for OAuth2 +Support custom `AuthConnector` for `OAuth2`. + +A user can pass their own `AuthConnector` implementation in `OAuth2` constructor. +In which case the session manager will use that instead of the `DefaultAuthConnector` to interact with the +authentication provider. + +A custom `AuthConnector` may call the authentication provider from the front-end, store and retrieve tokens +in the session storage, for example, and otherwise send custom requests to the authentication provider and +handle its responses. + +Note, that if the custom `AuthConnector` transforms scopes returned from the authentication provider, +the transformation must be the same as `OAuth2CreateOptions#scopeTransform` passed to `OAuth2` constructor. +See creating `DefaultAuthConnector` in `OAuth2#create(...)` for an example. diff --git a/packages/core-app-api/report.api.md b/packages/core-app-api/report.api.md index 73d940d1c4..ccec5ca4ce 100644 --- a/packages/core-app-api/report.api.md +++ b/packages/core-app-api/report.api.md @@ -296,11 +296,26 @@ export type AuthApiCreateOptions = { // @public export type AuthConnector = { - createSession(options: CreateSessionOptions): Promise; - refreshSession(scopes?: Set): Promise; + createSession( + options: AuthConnectorCreateSessionOptions, + ): Promise; + refreshSession( + options?: AuthConnectorRefreshSessionOptions, + ): Promise; removeSession(): Promise; }; +// @public (undocumented) +export type AuthConnectorCreateSessionOptions = { + scopes: Set; + instantPopup?: boolean; +}; + +// @public (undocumented) +export type AuthConnectorRefreshSessionOptions = { + scopes: Set; +}; + // @public export type BackstageApp = { getPlugins(): BackstagePlugin[]; @@ -360,12 +375,6 @@ export function createFetchApi(options: { middleware?: FetchMiddleware | FetchMiddleware[] | undefined; }): FetchApi; -// @public (undocumented) -export type CreateSessionOptions = { - scopes: Set; - instantPopup?: boolean; -}; - // @public export function createSpecializedApp(options: AppOptions): BackstageApp; @@ -491,15 +500,6 @@ export class LocalStorageFeatureFlags implements FeatureFlagsApi { save(options: FeatureFlagsSaveOptions): void; } -// @public -export type LoginPopupOptions = { - url: string; - name: string; - origin: string; - width?: number; - height?: number; -}; - // @public export class MicrosoftAuth { // (undocumented) @@ -573,9 +573,7 @@ export class OAuth2 export type OAuth2CreateOptions = OAuthApiCreateOptions & { scopeTransform?: (scopes: string[]) => string[]; popupOptions?: PopupOptions; - authConnectorFactory?: ( - opts: OAuth2CreateOptions, - ) => AuthConnector; + authConnector?: AuthConnector; }; // @public @@ -627,6 +625,19 @@ export type OneLoginAuthCreateOptions = { provider?: AuthProviderInfo; }; +// @public +export function openLoginPopup( + options: OpenLoginPopupOptions, +): Promise; + +// @public +export type OpenLoginPopupOptions = { + url: string; + name: string; + width?: number; + height?: number; +}; + // @public export type PopupOptions = { size?: @@ -662,9 +673,6 @@ export class SamlAuth signOut(): Promise; } -// @public -export function showLoginPopup(options: LoginPopupOptions): Promise; - // @public export type SignInPageProps = PropsWithChildren<{ onSignInSuccess(identityApi: IdentityApi): void; diff --git a/packages/core-app-api/src/apis/implementations/auth/oauth2/OAuth2.ts b/packages/core-app-api/src/apis/implementations/auth/oauth2/OAuth2.ts index 9162896ebe..fb3aead36c 100644 --- a/packages/core-app-api/src/apis/implementations/auth/oauth2/OAuth2.ts +++ b/packages/core-app-api/src/apis/implementations/auth/oauth2/OAuth2.ts @@ -44,9 +44,7 @@ import { OAuthApiCreateOptions } from '../types'; export type OAuth2CreateOptions = OAuthApiCreateOptions & { scopeTransform?: (scopes: string[]) => string[]; popupOptions?: PopupOptions; - authConnectorFactory?: ( - opts: OAuth2CreateOptions, - ) => AuthConnector; + authConnector?: AuthConnector; }; export type OAuth2Response = { @@ -83,72 +81,61 @@ export default class OAuth2 BackstageIdentityApi, SessionApi { - private static createDefaultAuthConnector(options: OAuth2CreateOptions) { - const { - configApi, - discoveryApi, - environment, - provider, - oauthRequestApi, - scopeTransform, - popupOptions, - } = options; - - return new DefaultAuthConnector({ - configApi, - discoveryApi, - environment: environment!, - provider: provider!, - oauthRequestApi: oauthRequestApi, - sessionTransform({ - backstageIdentity, - ...res - }: OAuth2Response): OAuth2Session { - const session: OAuth2Session = { - ...res, - providerInfo: { - idToken: res.providerInfo.idToken, - accessToken: res.providerInfo.accessToken, - scopes: OAuth2.normalizeScopes( - scopeTransform!, - res.providerInfo.scope, - ), - expiresAt: res.providerInfo.expiresInSeconds - ? new Date(Date.now() + res.providerInfo.expiresInSeconds * 1000) - : undefined, - }, - }; - if (backstageIdentity) { - session.backstageIdentity = { - token: backstageIdentity.token, - identity: backstageIdentity.identity, - expiresAt: backstageIdentity.expiresInSeconds - ? new Date(Date.now() + backstageIdentity.expiresInSeconds * 1000) - : undefined, - }; - } - return session; - }, - popupOptions, - }); - } - static create(options: OAuth2CreateOptions) { const { + configApi, + discoveryApi, environment = 'development', provider = DEFAULT_PROVIDER, + oauthRequestApi, defaultScopes = [], scopeTransform = x => x, - authConnectorFactory = (opts: OAuth2CreateOptions) => - OAuth2.createDefaultAuthConnector(opts), + popupOptions, } = options; - const connector = authConnectorFactory({ - ...options, - scopeTransform, - environment, - provider, - }); + const connector = + options.authConnector ?? + new DefaultAuthConnector({ + configApi, + discoveryApi, + environment, + provider, + oauthRequestApi: oauthRequestApi, + sessionTransform({ + backstageIdentity, + ...res + }: OAuth2Response): OAuth2Session { + const session: OAuth2Session = { + ...res, + providerInfo: { + idToken: res.providerInfo.idToken, + accessToken: res.providerInfo.accessToken, + scopes: OAuth2.normalizeScopes( + scopeTransform, + res.providerInfo.scope, + ), + expiresAt: res.providerInfo.expiresInSeconds + ? new Date( + Date.now() + res.providerInfo.expiresInSeconds * 1000, + ) + : undefined, + }, + }; + if (backstageIdentity) { + session.backstageIdentity = { + token: backstageIdentity.token, + identity: backstageIdentity.identity, + expiresAt: backstageIdentity.expiresInSeconds + ? new Date( + Date.now() + backstageIdentity.expiresInSeconds * 1000, + ) + : undefined, + }; + } + return session; + }, + popupOptions, + }); const sessionManager = new RefreshingAuthSessionManager({ connector, diff --git a/packages/core-app-api/src/index.ts b/packages/core-app-api/src/index.ts index 9033622b9d..1dc8ddec19 100644 --- a/packages/core-app-api/src/index.ts +++ b/packages/core-app-api/src/index.ts @@ -23,7 +23,10 @@ export * from './apis'; export * from './app'; export * from './routing'; -export type { AuthConnector } from './lib'; -export type { CreateSessionOptions } from './lib'; -export { showLoginPopup } from './lib'; -export type { LoginPopupOptions } from './lib'; +export type { + AuthConnector, + AuthConnectorCreateSessionOptions, + AuthConnectorRefreshSessionOptions, + openLoginPopup, + OpenLoginPopupOptions, +} from './lib'; diff --git a/packages/core-app-api/src/lib/AuthConnector/DefaultAuthConnector.test.ts b/packages/core-app-api/src/lib/AuthConnector/DefaultAuthConnector.test.ts index 60c4a3b8d9..192b1b7ac0 100644 --- a/packages/core-app-api/src/lib/AuthConnector/DefaultAuthConnector.test.ts +++ b/packages/core-app-api/src/lib/AuthConnector/DefaultAuthConnector.test.ts @@ -26,7 +26,7 @@ import { ConfigApi } from '@backstage/core-plugin-api'; jest.mock('../loginPopup', () => { return { - showLoginPopup: jest.fn(), + openLoginPopup: jest.fn(), }; }); @@ -74,7 +74,9 @@ describe('DefaultAuthConnector', () => { ); const connector = new DefaultAuthConnector(defaultOptions); - const session = await connector.refreshSession(new Set(['a', 'b', 'c'])); + const session = await connector.refreshSession({ + scopes: new Set(['a', 'b', 'c']), + }); expect(session.idToken).toBe('mock-id-token'); expect(session.accessToken).toBe('mock-access-token'); expect(session.scopes).toEqual(new Set(['a', 'b', 'c'])); @@ -118,7 +120,7 @@ describe('DefaultAuthConnector', () => { it('should create a session', async () => { const mockOauth = new MockOAuthApi(); const popupSpy = jest - .spyOn(loginPopup, 'showLoginPopup') + .spyOn(loginPopup, 'openLoginPopup') .mockResolvedValue({ idToken: 'my-id-token', accessToken: 'my-access-token', @@ -151,7 +153,7 @@ describe('DefaultAuthConnector', () => { it('should instantly show popup if option is set', async () => { const popupSpy = jest - .spyOn(loginPopup, 'showLoginPopup') + .spyOn(loginPopup, 'openLoginPopup') .mockResolvedValue('my-session'); const connector = new DefaultAuthConnector({ ...defaultOptions, @@ -169,7 +171,6 @@ describe('DefaultAuthConnector', () => { expect(popupSpy).toHaveBeenCalledTimes(1); expect(popupSpy).toHaveBeenCalledWith({ name: 'My Provider Login', - origin: 'http://my-host', url: 'http://my-host/api/auth/my-provider/start?scope=&origin=http%3A%2F%2Flocalhost&flow=popup&env=production', width: 450, height: 730, @@ -178,7 +179,7 @@ describe('DefaultAuthConnector', () => { it('should show popup fullscreen', async () => { const popupSpy = jest - .spyOn(loginPopup, 'showLoginPopup') + .spyOn(loginPopup, 'openLoginPopup') .mockResolvedValue('my-session'); jest.spyOn(window.screen, 'width', 'get').mockReturnValue(1000); @@ -205,7 +206,6 @@ describe('DefaultAuthConnector', () => { expect(popupSpy).toHaveBeenCalledWith({ height: 1000, name: 'My Provider Login', - origin: 'http://my-host', url: 'http://my-host/api/auth/my-provider/start?scope=&origin=http%3A%2F%2Flocalhost&flow=popup&env=production', width: 1000, }); @@ -213,7 +213,7 @@ describe('DefaultAuthConnector', () => { it('should show popup with special width and height', async () => { const popupSpy = jest - .spyOn(loginPopup, 'showLoginPopup') + .spyOn(loginPopup, 'openLoginPopup') .mockResolvedValue('my-session'); const connector = new DefaultAuthConnector({ ...defaultOptions, @@ -236,7 +236,6 @@ describe('DefaultAuthConnector', () => { expect(popupSpy).toHaveBeenCalledWith({ name: 'My Provider Login', - origin: 'http://my-host', url: 'http://my-host/api/auth/my-provider/start?scope=&origin=http%3A%2F%2Flocalhost&flow=popup&env=production', width: 500, height: 1000, @@ -246,7 +245,7 @@ describe('DefaultAuthConnector', () => { it('should use join func to join scopes', async () => { const mockOauth = new MockOAuthApi(); const popupSpy = jest - .spyOn(loginPopup, 'showLoginPopup') + .spyOn(loginPopup, 'openLoginPopup') .mockResolvedValue({ scopes: '' }); const connector = new DefaultAuthConnector({ ...defaultOptions, diff --git a/packages/core-app-api/src/lib/AuthConnector/DefaultAuthConnector.ts b/packages/core-app-api/src/lib/AuthConnector/DefaultAuthConnector.ts index ad1b25619d..3671ddc6e6 100644 --- a/packages/core-app-api/src/lib/AuthConnector/DefaultAuthConnector.ts +++ b/packages/core-app-api/src/lib/AuthConnector/DefaultAuthConnector.ts @@ -20,8 +20,13 @@ import { OAuthRequestApi, OAuthRequester, } from '@backstage/core-plugin-api'; -import { showLoginPopup } from '../loginPopup'; -import { AuthConnector, CreateSessionOptions, PopupOptions } from './types'; +import { openLoginPopup } from '../loginPopup'; +import { + AuthConnector, + AuthConnectorCreateSessionOptions, + PopupOptions, + AuthConnectorRefreshSessionOptions, +} from './types'; let warned = false; @@ -123,7 +128,9 @@ export class DefaultAuthConnector this.popupOptions = popupOptions; } - async createSession(options: CreateSessionOptions): Promise { + async createSession( + options: AuthConnectorCreateSessionOptions, + ): Promise { if (options.instantPopup) { if (this.enableExperimentalRedirectFlow) { return this.executeRedirect(options.scopes); @@ -133,11 +140,13 @@ export class DefaultAuthConnector return this.authRequester(options.scopes); } - async refreshSession(scopes?: Set): Promise { + async refreshSession( + options?: AuthConnectorRefreshSessionOptions, + ): Promise { const res = await fetch( await this.buildUrl('/refresh', { optional: true, - ...(scopes && { scope: this.joinScopesFunc(scopes) }), + ...(options && { scope: this.joinScopesFunc(options.scopes) }), }), { headers: { @@ -203,10 +212,9 @@ export class DefaultAuthConnector ? window.screen.height : this.popupOptions?.size?.height || 730; - const payload = await showLoginPopup({ + const payload = await openLoginPopup({ url: popupUrl, name: `${this.provider.title} Login`, - origin: new URL(popupUrl).origin, width, height, }); diff --git a/packages/core-app-api/src/lib/AuthConnector/DirectAuthConnector.ts b/packages/core-app-api/src/lib/AuthConnector/DirectAuthConnector.ts index 200ba755ac..a45bfa0af9 100644 --- a/packages/core-app-api/src/lib/AuthConnector/DirectAuthConnector.ts +++ b/packages/core-app-api/src/lib/AuthConnector/DirectAuthConnector.ts @@ -14,7 +14,7 @@ * limitations under the License. */ import { AuthProviderInfo, DiscoveryApi } from '@backstage/core-plugin-api'; -import { showLoginPopup } from '../loginPopup'; +import { openLoginPopup } from '../loginPopup'; type Options = { discoveryApi: DiscoveryApi; @@ -36,13 +36,12 @@ export class DirectAuthConnector { async createSession(): Promise { const popupUrl = await this.buildUrl('/start'); - const payload = await showLoginPopup({ + const payload = (await openLoginPopup({ url: popupUrl, name: `${this.provider.title} Login`, - origin: new URL(popupUrl).origin, width: 450, height: 730, - }); + })) as any; return { ...payload, diff --git a/packages/core-app-api/src/lib/AuthConnector/types.ts b/packages/core-app-api/src/lib/AuthConnector/types.ts index 1ba96c6cb8..035760d0aa 100644 --- a/packages/core-app-api/src/lib/AuthConnector/types.ts +++ b/packages/core-app-api/src/lib/AuthConnector/types.ts @@ -17,11 +17,18 @@ /** * @public */ -export type CreateSessionOptions = { +export type AuthConnectorCreateSessionOptions = { scopes: Set; instantPopup?: boolean; }; +/** + * @public + */ +export type AuthConnectorRefreshSessionOptions = { + scopes: Set; +}; + /** * An AuthConnector is responsible for realizing auth session actions * by for example communicating with a backend or interacting with the user. @@ -29,8 +36,12 @@ export type CreateSessionOptions = { * @public */ export type AuthConnector = { - createSession(options: CreateSessionOptions): Promise; - refreshSession(scopes?: Set): Promise; + createSession( + options: AuthConnectorCreateSessionOptions, + ): Promise; + refreshSession( + options?: AuthConnectorRefreshSessionOptions, + ): Promise; removeSession(): Promise; }; diff --git a/packages/core-app-api/src/lib/AuthSessionManager/RefreshingAuthSessionManager.test.ts b/packages/core-app-api/src/lib/AuthSessionManager/RefreshingAuthSessionManager.test.ts index 88000638e6..9afc710bdd 100644 --- a/packages/core-app-api/src/lib/AuthSessionManager/RefreshingAuthSessionManager.test.ts +++ b/packages/core-app-api/src/lib/AuthSessionManager/RefreshingAuthSessionManager.test.ts @@ -16,6 +16,7 @@ import { RefreshingAuthSessionManager } from './RefreshingAuthSessionManager'; import { SessionState } from '@backstage/core-plugin-api'; +import { AuthConnectorRefreshSessionOptions } from '../AuthConnector'; const defaultOptions = { sessionScopes: (session: { scopes: Set }) => session.scopes, @@ -47,7 +48,7 @@ describe('RefreshingAuthSessionManager', () => { await manager.getSession({}); expect(createSession).toHaveBeenCalledTimes(1); - expect(refreshSession).toHaveBeenCalledWith(new Set()); + expect(refreshSession).toHaveBeenCalledWith({ scopes: new Set() }); expect(stateSubscriber.mock.calls).toEqual([ [SessionState.SignedOut], [SessionState.SignedIn], @@ -103,7 +104,7 @@ describe('RefreshingAuthSessionManager', () => { await manager.getSession({ scopes: new Set(['a']) }); expect(createSession).toHaveBeenCalledTimes(1); - expect(refreshSession).toHaveBeenCalledWith(new Set(['a'])); + expect(refreshSession).toHaveBeenCalledWith({ scopes: new Set(['a']) }); await manager.getSession({ scopes: new Set(['a']) }); expect(createSession).toHaveBeenCalledTimes(1); @@ -134,7 +135,7 @@ describe('RefreshingAuthSessionManager', () => { expect(await manager.getSession({ optional: true })).toBe(undefined); expect(createSession).toHaveBeenCalledTimes(0); - expect(refreshSession).toHaveBeenCalledWith(new Set()); + expect(refreshSession).toHaveBeenCalledWith({ scopes: new Set() }); }); it('should forward option to instantly show auth popup and not attempt refresh', async () => { @@ -168,10 +169,12 @@ describe('RefreshingAuthSessionManager', () => { it('should handle two simultaneous session refreshes with same scopes', async () => { const createSession = jest.fn(); - const refreshSession = jest.fn(async (scopes?: Set) => ({ - scopes: scopes ?? new Set(), - expired: false, - })); + const refreshSession = jest.fn( + async (options?: AuthConnectorRefreshSessionOptions) => ({ + scopes: options?.scopes ?? new Set(), + expired: false, + }), + ); const manager = new RefreshingAuthSessionManager({ connector: { createSession, refreshSession }, ...defaultOptions, @@ -192,10 +195,12 @@ describe('RefreshingAuthSessionManager', () => { it('should handle two simultaneous session refreshes with different scopes', async () => { const createSession = jest.fn(); - const refreshSession = jest.fn(async (scopes?: Set) => ({ - scopes: scopes ?? new Set(), - expired: false, - })); + const refreshSession = jest.fn( + async (options?: AuthConnectorRefreshSessionOptions) => ({ + scopes: options?.scopes ?? new Set(), + expired: false, + }), + ); const manager = new RefreshingAuthSessionManager({ connector: { createSession, refreshSession }, ...defaultOptions, @@ -216,10 +221,12 @@ describe('RefreshingAuthSessionManager', () => { it('should handle multiple simultaneous session refreshes with different scopes', async () => { const createSession = jest.fn(); - const refreshSession = jest.fn(async (scopes?: Set) => ({ - scopes: scopes ?? new Set(), - expired: false, - })); + const refreshSession = jest.fn( + async (options?: AuthConnectorRefreshSessionOptions) => ({ + scopes: options?.scopes ?? new Set(), + expired: false, + }), + ); const manager = new RefreshingAuthSessionManager({ connector: { createSession, refreshSession }, ...defaultOptions, diff --git a/packages/core-app-api/src/lib/AuthSessionManager/RefreshingAuthSessionManager.ts b/packages/core-app-api/src/lib/AuthSessionManager/RefreshingAuthSessionManager.ts index 414743ba9d..216409a528 100644 --- a/packages/core-app-api/src/lib/AuthSessionManager/RefreshingAuthSessionManager.ts +++ b/packages/core-app-api/src/lib/AuthSessionManager/RefreshingAuthSessionManager.ts @@ -137,9 +137,9 @@ export class RefreshingAuthSessionManager implements SessionManager { return this.refreshPromise; } - this.refreshPromise = this.connector.refreshSession( - this.helper.getExtendedScope(this.currentSession, scopes), - ); + this.refreshPromise = this.connector.refreshSession({ + scopes: this.helper.getExtendedScope(this.currentSession, scopes), + }); try { const session = await this.refreshPromise; diff --git a/packages/core-app-api/src/lib/loginPopup.test.ts b/packages/core-app-api/src/lib/loginPopup.test.ts index 50193cbdd6..448f4f727c 100644 --- a/packages/core-app-api/src/lib/loginPopup.test.ts +++ b/packages/core-app-api/src/lib/loginPopup.test.ts @@ -14,14 +14,14 @@ * limitations under the License. */ -import { showLoginPopup } from './loginPopup'; +import { openLoginPopup } from './loginPopup'; -describe('showLoginPopup', () => { +describe('openLoginPopup', () => { afterEach(() => { jest.resetAllMocks(); }); - it('should show an auth popup', async () => { + it('should open an auth popup', async () => { const popupMock = { closed: false }; const openSpy = jest .spyOn(window, 'open') @@ -29,15 +29,14 @@ describe('showLoginPopup', () => { const addEventListenerSpy = jest.spyOn(window, 'addEventListener'); const removeEventListenerSpy = jest.spyOn(window, 'removeEventListener'); - const payloadPromise = showLoginPopup({ - url: 'my-origin/api/backend/auth/start?scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fa%20https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fb', + const payloadPromise = openLoginPopup({ + url: 'http://my-origin/api/backend/auth/start?scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fa%20https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fb', name: 'test-popup', - origin: 'my-origin', }); expect(openSpy).toHaveBeenCalledTimes(1); expect(openSpy.mock.calls[0][0]).toBe( - 'my-origin/api/backend/auth/start?scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fa%20https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fb', + 'http://my-origin/api/backend/auth/start?scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fa%20https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fb', ); expect(openSpy.mock.calls[0][1]).toBe('test-popup'); expect(addEventListenerSpy).toHaveBeenCalledTimes(1); @@ -57,16 +56,16 @@ describe('showLoginPopup', () => { // None of these should be accepted listener({ source: popupMock } as MessageEvent); - listener({ origin: 'my-origin' } as MessageEvent); + listener({ origin: 'http://my-origin' } as MessageEvent); listener({ data: { type: 'authorization_response' } } as MessageEvent); listener({ source: popupMock, - origin: 'my-origin', + origin: 'http://my-origin', data: {}, } as MessageEvent); listener({ source: popupMock, - origin: 'my-origin', + origin: 'http://my-origin', data: { type: 'not-auth-result', response: {} }, } as MessageEvent); @@ -79,7 +78,7 @@ describe('showLoginPopup', () => { // This should be accepted as a valid sessions response listener({ source: popupMock, - origin: 'my-origin', + origin: 'http://my-origin', data: { type: 'authorization_response', response: myResponse, @@ -101,10 +100,9 @@ describe('showLoginPopup', () => { const addEventListenerSpy = jest.spyOn(window, 'addEventListener'); const removeEventListenerSpy = jest.spyOn(window, 'removeEventListener'); - const payloadPromise = showLoginPopup({ - url: 'url', + const payloadPromise = openLoginPopup({ + url: 'http://my-origin', name: 'name', - origin: 'my-origin', }); expect(openSpy).toHaveBeenCalledTimes(1); @@ -115,7 +113,7 @@ describe('showLoginPopup', () => { listener({ source: popupMock, - origin: 'my-origin', + origin: 'http://my-origin', data: { type: 'authorization_response', error: { @@ -145,10 +143,9 @@ describe('showLoginPopup', () => { openSpy.mockReturnValue(popupMock as Window); - const payloadPromise = showLoginPopup({ - url: 'url', + const payloadPromise = openLoginPopup({ + url: 'http://origin', name: 'name', - origin: 'origin', }); expect(openSpy).toHaveBeenCalledTimes(1); @@ -158,7 +155,7 @@ describe('showLoginPopup', () => { const listener = addEventListenerSpy.mock.calls[0][1] as EventListener; listener({ source: popupMock, - origin: 'origin', + origin: 'http://origin', data: { type: 'config_info', targetOrigin: 'http://localhost', @@ -187,16 +184,15 @@ describe('showLoginPopup', () => { openSpy.mockReturnValue(popupMock as Window); - const payloadPromise = showLoginPopup({ - url: 'url', + const payloadPromise = openLoginPopup({ + url: 'http://origin', name: 'name', - origin: 'origin', }); const listener = addEventListenerSpy.mock.calls[0][1] as EventListener; listener({ source: popupMock, - origin: 'origin', + origin: 'http://origin', data: { type: 'config_info', targetOrigin: 'http://differenthost', diff --git a/packages/core-app-api/src/lib/loginPopup.ts b/packages/core-app-api/src/lib/loginPopup.ts index 9ab75501b0..e7ed84b0cf 100644 --- a/packages/core-app-api/src/lib/loginPopup.ts +++ b/packages/core-app-api/src/lib/loginPopup.ts @@ -19,7 +19,7 @@ * * @public */ -export type LoginPopupOptions = { +export type OpenLoginPopupOptions = { /** * The URL that the auth popup should point to */ @@ -30,11 +30,6 @@ export type LoginPopupOptions = { */ name: string; - /** - * The origin of the final popup page that will post a message to this window. - */ - origin: string; - /** * The width of the popup in pixels, defaults to 500 */ @@ -70,13 +65,17 @@ type AuthResult = * * @public */ -export function showLoginPopup(options: LoginPopupOptions): Promise { +export function openLoginPopup( + options: OpenLoginPopupOptions, +): Promise { return new Promise((resolve, reject) => { const width = options.width || 500; const height = options.height || 700; const left = window.screen.width / 2 - width / 2; const top = window.screen.height / 2 - height / 2; + const origin = new URL(options.url).origin; + const popup = window.open( options.url, options.name, @@ -96,7 +95,7 @@ export function showLoginPopup(options: LoginPopupOptions): Promise { if (event.source !== popup) { return; } - if (event.origin !== options.origin) { + if (event.origin !== origin) { return; } const { data } = event; From f134cea34cdb2a3641fede26c0c30826f67ac02d Mon Sep 17 00:00:00 2001 From: Andy LADJADJ Date: Fri, 17 Jan 2025 16:02:45 +0100 Subject: [PATCH 005/109] feat(integration): add Gerrit option to activate the edit mode Signed-off-by: Andy LADJADJ --- .changeset/ten-spies-explode.md | 11 ++++++++ docs/integrations/gerrit/locations.md | 2 ++ packages/integration/config.d.ts | 5 ++++ packages/integration/report.api.md | 1 + .../src/gerrit/GerritIntegration.test.ts | 26 +++++++++++++++++-- .../src/gerrit/GerritIntegration.ts | 12 +++++++-- .../integration/src/gerrit/config.test.ts | 22 ++++++++++++++++ packages/integration/src/gerrit/config.ts | 14 ++++++---- packages/integration/src/gerrit/core.ts | 23 ++++++++++++++++ 9 files changed, 107 insertions(+), 9 deletions(-) create mode 100644 .changeset/ten-spies-explode.md diff --git a/.changeset/ten-spies-explode.md b/.changeset/ten-spies-explode.md new file mode 100644 index 0000000000..ba7d406398 --- /dev/null +++ b/.changeset/ten-spies-explode.md @@ -0,0 +1,11 @@ +--- +'@backstage/integration': minor +--- + +Add Gerrit option to activate the edit mode for version >= 3.9 + +``` + enableEditUrl: true +``` + +The url pattern is `^\/admin\/repos\/edit\/repo\/(.+)\/branch\/(.+)\/file\/(.+)$` diff --git a/docs/integrations/gerrit/locations.md b/docs/integrations/gerrit/locations.md index 3f237fc9ba..79e7771bc6 100644 --- a/docs/integrations/gerrit/locations.md +++ b/docs/integrations/gerrit/locations.md @@ -22,6 +22,7 @@ integrations: gitilesBaseUrl: https://gerrit.company.com/gitiles baseUrl: https://gerrit.company.com/gerrit cloneUrl: https://gerrit.company.com/clone + enableEditUrl: true username: ${GERRIT_USERNAME} password: ${GERRIT_PASSWORD} ``` @@ -37,6 +38,7 @@ a structure with up to six elements: address here. This is the address that you would open in a browser. - `cloneUrl` (optional): The base URL for HTTP clones. Will default to `baseUrl` if not set. The address used to clone a repo is the `cloneUrl` plus the repo name. +- `enableEditUrl` (optional): Activate the edit mode for Gerrit >= 3.9 - `username` (optional): The Gerrit username to use in API requests. If neither a username nor password are supplied, anonymous access will be used. - `password` (optional): The password or http token for the Gerrit user. diff --git a/packages/integration/config.d.ts b/packages/integration/config.d.ts index 1e4c8d373d..fc4559f73a 100644 --- a/packages/integration/config.d.ts +++ b/packages/integration/config.d.ts @@ -161,6 +161,11 @@ export interface Config { * @visibility frontend */ cloneUrl?: string; + /** + * Activate the edit url feature available since Gerrit 3.9 + * @visibility frontend + */ + enableEditUrl?: boolean; /** * The username to use for authenticated requests. * @visibility secret diff --git a/packages/integration/report.api.md b/packages/integration/report.api.md index 6bb48b8185..ecf46b10f1 100644 --- a/packages/integration/report.api.md +++ b/packages/integration/report.api.md @@ -392,6 +392,7 @@ export type GerritIntegrationConfig = { host: string; baseUrl?: string; cloneUrl?: string; + enableEditUrl?: boolean; gitilesBaseUrl: string; username?: string; password?: string; diff --git a/packages/integration/src/gerrit/GerritIntegration.test.ts b/packages/integration/src/gerrit/GerritIntegration.test.ts index 2cfc748550..b415636072 100644 --- a/packages/integration/src/gerrit/GerritIntegration.test.ts +++ b/packages/integration/src/gerrit/GerritIntegration.test.ts @@ -47,6 +47,7 @@ describe('GerritIntegration', () => { it('returns the basics', () => { const integration = new GerritIntegration({ host: 'gerrit-review.example.com', + gitilesBaseUrl: 'https://gerrit-review.example.com/gitiles', } as any); expect(integration.type).toBe('gerrit'); expect(integration.title).toBe('gerrit-review.example.com'); @@ -70,6 +71,7 @@ describe('GerritIntegration', () => { it('handles line numbers', () => { const integration = new GerritIntegration({ host: 'gerrit-review.example.com', + gitilesBaseUrl: 'https://gerrit-review.example.com/gitiles', } as any); expect( @@ -85,6 +87,7 @@ describe('GerritIntegration', () => { describe('resolves with a relative url', () => { const integration = new GerritIntegration({ host: 'gerrit-review.example.com', + gitilesBaseUrl: 'https://gerrit-review.example.com/gitiles', } as any); it('works for valid urls pointing to a branch', () => { expect( @@ -144,8 +147,27 @@ describe('GerritIntegration', () => { // url as is. expect( integration.resolveEditUrl( - 'https://gerrit-review.example.com/catalog-info.yaml', + 'https://gerrit-review.example.com/gitiles/backstage/backstage/+/refs/heads/master/catalog-info.yaml', ), - ).toBe('https://gerrit-review.example.com/catalog-info.yaml'); + ).toBe( + 'https://gerrit-review.example.com/gitiles/backstage/backstage/+/refs/heads/master/catalog-info.yaml', + ); + }); + + it('resolve edit URL with editUrl', () => { + const integration = new GerritIntegration({ + host: 'gerrit-review.example.com', + baseUrl: 'https://gerrit-review.example.com', + enableEditUrl: true, + gitilesBaseUrl: 'https://gerrit-review.example.com/gitiles', + } as any); + + expect( + integration.resolveEditUrl( + 'https://gerrit-review.example.com/gitiles/backstage/backstage/+/refs/heads/master/catalog-info.yaml', + ), + ).toBe( + 'https://gerrit-review.example.com/admin/repos/edit/repo/backstage/backstage/branch/refs/heads/master/file/catalog-info.yaml', + ); }); }); diff --git a/packages/integration/src/gerrit/GerritIntegration.ts b/packages/integration/src/gerrit/GerritIntegration.ts index 7e2e757c6b..adca205ac7 100644 --- a/packages/integration/src/gerrit/GerritIntegration.ts +++ b/packages/integration/src/gerrit/GerritIntegration.ts @@ -20,7 +20,7 @@ import { GerritIntegrationConfig, readGerritIntegrationConfigs, } from './config'; -import { parseGitilesUrlRef } from './core'; +import { buildGerritEditUrl, parseGitilesUrlRef } from './core'; /** * A Gerrit based integration. @@ -75,7 +75,15 @@ export class GerritIntegration implements ScmIntegration { } resolveEditUrl(url: string): string { - // Not applicable for gerrit. + if (this.config.enableEditUrl) { + const parsed = parseGitilesUrlRef(this.config, url); + return buildGerritEditUrl( + this.config, + parsed.project, + parsed.ref, + parsed.path, + ); + } return url; } } diff --git a/packages/integration/src/gerrit/config.test.ts b/packages/integration/src/gerrit/config.test.ts index be31193f2e..8bf1a3d44c 100644 --- a/packages/integration/src/gerrit/config.test.ts +++ b/packages/integration/src/gerrit/config.test.ts @@ -57,6 +57,7 @@ describe('readGerritIntegrationConfig', () => { host: 'a.com', baseUrl: 'https://a.com/api', cloneUrl: 'https:a.com/clone', + enableEditUrl: false, gitilesBaseUrl: 'https://a.com/git', username: 'u', password: ' p ', @@ -66,12 +67,32 @@ describe('readGerritIntegrationConfig', () => { host: 'a.com', baseUrl: 'https://a.com/api', cloneUrl: 'https:a.com/clone', + enableEditUrl: false, gitilesBaseUrl: 'https://a.com/git', username: 'u', password: 'p', }); }); + it('activate Edit Url', () => { + const output = readGerritIntegrationConfig( + buildConfig({ + host: 'a.com', + enableEditUrl: true, + gitilesBaseUrl: 'https://a.com/gerrit/plugins/gitiles', + }), + ); + expect(output).toEqual({ + host: 'a.com', + baseUrl: 'https://a.com', + cloneUrl: 'https://a.com', + enableEditUrl: true, + gitilesBaseUrl: 'https://a.com/gerrit/plugins/gitiles', + username: undefined, + password: undefined, + }); + }); + it('can create a default value if the API base URL is missing', () => { const output = readGerritIntegrationConfig( buildConfig({ @@ -156,6 +177,7 @@ describe('readGerritIntegrationConfigs', () => { host: 'b.com', baseUrl: 'https://b.com/api', cloneUrl: 'https://b.com/api', + enableEditUrl: undefined, gitilesBaseUrl: 'https://b.com/gerrit/plugins/gitiles', username: undefined, password: undefined, diff --git a/packages/integration/src/gerrit/config.ts b/packages/integration/src/gerrit/config.ts index a5fdc8dabf..710e2847de 100644 --- a/packages/integration/src/gerrit/config.ts +++ b/packages/integration/src/gerrit/config.ts @@ -44,6 +44,11 @@ export type GerritIntegrationConfig = { */ cloneUrl?: string; + /** + * Activate the edit url feature available since Gerrit 3.9 + */ + enableEditUrl?: boolean; + /** * Base url for Gitiles. This is needed for creating a valid * user-friendly url that can be used for browsing the content of the @@ -75,6 +80,7 @@ export function readGerritIntegrationConfig( const host = config.getString('host'); let baseUrl = config.getOptionalString('baseUrl'); let cloneUrl = config.getOptionalString('cloneUrl'); + const enableEditUrl = config.getOptionalBoolean('enableEditUrl'); let gitilesBaseUrl = config.getString('gitilesBaseUrl'); const username = config.getOptionalString('username'); const password = config.getOptionalString('password')?.trim(); @@ -101,21 +107,19 @@ export function readGerritIntegrationConfig( } else { baseUrl = `https://${host}`; } - if (gitilesBaseUrl) { - gitilesBaseUrl = trimEnd(gitilesBaseUrl, '/'); - } else { - gitilesBaseUrl = `https://${host}`; - } if (cloneUrl) { cloneUrl = trimEnd(cloneUrl, '/'); } else { cloneUrl = baseUrl; } + gitilesBaseUrl = trimEnd(gitilesBaseUrl, '/'); + return { host, baseUrl, cloneUrl, + enableEditUrl, gitilesBaseUrl, username, password, diff --git a/packages/integration/src/gerrit/core.ts b/packages/integration/src/gerrit/core.ts index d44865881d..6b90bb28ef 100644 --- a/packages/integration/src/gerrit/core.ts +++ b/packages/integration/src/gerrit/core.ts @@ -208,6 +208,29 @@ export function buildGerritGitilesUrl( }/${project}/+/refs/heads/${branch}/${trimStart(filePath, '/')}`; } +/** + * Build a Gerrit Gitiles url that targets a specific path. + * + * @param config - A Gerrit provider config. + * @param project - The name of the git project + * @param branch - The branch we will target. + * @param filePath - The absolute file path. + * @public + */ +export function buildGerritEditUrl( + config: GerritIntegrationConfig, + project: string, + branch: string, + filePath: string, +): string { + return `${ + config.baseUrl + }/admin/repos/edit/repo/${project}/branch/refs/heads/${branch}/file/${trimStart( + filePath, + '/', + )}`; +} + /** * Build a Gerrit Gitiles archive url that targets a specific branch and path * From 8a61795308eadcdd10f757559fdbad8555edef06 Mon Sep 17 00:00:00 2001 From: Gasan Guseinov Date: Thu, 23 Jan 2025 18:37:54 +0100 Subject: [PATCH 006/109] add a unit test Signed-off-by: Gasan Guseinov --- packages/cli/src/commands/repo/test.ts | 3 + packages/core-app-api/report.api.md | 34 +++- .../auth/oauth2/OAuth2.test.ts | 48 ++++++ .../implementations/auth/oauth2/OAuth2.ts | 146 ++++++++---------- .../oauth2/OAuth2CustomAuthConnector.test.ts | 121 +++++++++++++++ .../apis/implementations/auth/oauth2/types.ts | 43 +++++- packages/core-app-api/src/index.ts | 2 +- 7 files changed, 310 insertions(+), 87 deletions(-) create mode 100644 packages/core-app-api/src/apis/implementations/auth/oauth2/OAuth2CustomAuthConnector.test.ts diff --git a/packages/cli/src/commands/repo/test.ts b/packages/cli/src/commands/repo/test.ts index 6b3478b5d9..b32f3caaa7 100644 --- a/packages/cli/src/commands/repo/test.ts +++ b/packages/cli/src/commands/repo/test.ts @@ -422,5 +422,8 @@ export async function command(opts: OptionValues, cmd: Command): Promise { }; } + console.log('retry'); + console.log(`args: ${JSON.stringify(args)}`); + await runJest(args); } diff --git a/packages/core-app-api/report.api.md b/packages/core-app-api/report.api.md index ccec5ca4ce..6af171ea4d 100644 --- a/packages/core-app-api/report.api.md +++ b/packages/core-app-api/report.api.md @@ -21,6 +21,7 @@ import { AuthRequestOptions } from '@backstage/core-plugin-api'; import { BackstageIdentityApi } from '@backstage/core-plugin-api'; import { BackstageIdentityResponse } from '@backstage/core-plugin-api'; import { BackstagePlugin } from '@backstage/core-plugin-api'; +import { BackstageUserIdentity } from '@backstage/core-plugin-api'; import { bitbucketAuthApiRef } from '@backstage/core-plugin-api'; import { bitbucketServerAuthApiRef } from '@backstage/core-plugin-api'; import { ComponentType } from 'react'; @@ -547,7 +548,9 @@ export class OAuth2 SessionApi { // (undocumented) - static create(options: OAuth2CreateOptions): OAuth2; + static create( + options: OAuth2CreateOptions | OAuth2CreateOptionsWithAuthConnector, + ): OAuth2; // (undocumented) getAccessToken( scope?: string | string[], @@ -562,6 +565,11 @@ export class OAuth2 // (undocumented) getProfile(options?: AuthRequestOptions): Promise; // (undocumented) + static normalizeScopes( + scopeTransform: (scopes: string[]) => string[], + scopes?: string | string[], + ): Set; + // (undocumented) sessionState$(): Observable; // (undocumented) signIn(): Promise; @@ -573,7 +581,29 @@ export class OAuth2 export type OAuth2CreateOptions = OAuthApiCreateOptions & { scopeTransform?: (scopes: string[]) => string[]; popupOptions?: PopupOptions; - authConnector?: AuthConnector; +}; + +// @public +export type OAuth2CreateOptionsWithAuthConnector = { + scopeTransform?: (scopes: string[]) => string[]; + defaultScopes?: string[]; + authConnector: AuthConnector; +}; + +// @public (undocumented) +export type OAuth2Response = { + providerInfo: { + accessToken: string; + idToken: string; + scope: string; + expiresInSeconds?: number; + }; + profile: ProfileInfo; + backstageIdentity: { + token: string; + expiresInSeconds?: number; + identity: BackstageUserIdentity; + }; }; // @public diff --git a/packages/core-app-api/src/apis/implementations/auth/oauth2/OAuth2.test.ts b/packages/core-app-api/src/apis/implementations/auth/oauth2/OAuth2.test.ts index a34589d746..f662c47bda 100644 --- a/packages/core-app-api/src/apis/implementations/auth/oauth2/OAuth2.test.ts +++ b/packages/core-app-api/src/apis/implementations/auth/oauth2/OAuth2.test.ts @@ -18,6 +18,14 @@ import OAuth2 from './OAuth2'; import MockOAuthApi from '../../OAuthRequestApi/MockOAuthApi'; import { UrlPatternDiscovery } from '../../DiscoveryApi'; import { mockApis } from '@backstage/test-utils'; +import { + OAuth2Session, + AuthConnector, + AuthConnectorRefreshSessionOptions, + openLoginPopup, + // OAuth2Response, + OAuth2CreateOptionsWithAuthConnector, +} from '../../../../index'; const theFuture = new Date(Date.now() + 3600000); const thePast = new Date(Date.now() - 10); @@ -37,6 +45,25 @@ jest.mock('../../../../lib/AuthSessionManager', () => ({ const configApi = mockApis.config(); +class CustomAuthConnector implements AuthConnector { + async createSession() { + const s: OAuth2Session = { + providerInfo: { + idToken: '', + accessToken: 'accessToken', + scopes: new Set(['myScope']), + }, + profile: {}, + }; + await openLoginPopup({ url: 'http://localhost', name: 'myPopup' }); + return Promise.resolve(s); + } + + async refreshSession(_?: AuthConnectorRefreshSessionOptions): Promise {} + + async removeSession(): Promise {} +} + describe('OAuth2', () => { it('should get refreshed access token', async () => { getSession = jest.fn().mockResolvedValue({ @@ -215,4 +242,25 @@ describe('OAuth2', () => { await expect(promise3).resolves.toBe('token2'); expect(getSession).toHaveBeenCalledTimes(4); // De-duping of session requests happens in client }); + it('should use provided auth provider', async () => { + getSession = jest.fn().mockResolvedValue({ + providerInfo: { accessToken: 'access-token', expiresAt: theFuture }, + }); + + const customAuthConnector = new CustomAuthConnector(); + + const options: OAuth2CreateOptionsWithAuthConnector = { + scopeTransform, + defaultScopes: ['myScope'], + authConnector: customAuthConnector, + }; + const oauth2 = OAuth2.create(options); + + expect(await oauth2.getAccessToken('my-scope my-scope2')).toBe( + 'access-token', + ); + expect(getSession).toHaveBeenCalledWith( + expect.objectContaining({ scopes: new Set(['my-scope', 'my-scope2']) }), + ); + }); }); diff --git a/packages/core-app-api/src/apis/implementations/auth/oauth2/OAuth2.ts b/packages/core-app-api/src/apis/implementations/auth/oauth2/OAuth2.ts index fb3aead36c..7318d30382 100644 --- a/packages/core-app-api/src/apis/implementations/auth/oauth2/OAuth2.ts +++ b/packages/core-app-api/src/apis/implementations/auth/oauth2/OAuth2.ts @@ -14,53 +14,26 @@ * limitations under the License. */ -import { - AuthConnector, - DefaultAuthConnector, - PopupOptions, -} from '../../../../lib/AuthConnector'; +import { DefaultAuthConnector } from '../../../../lib/AuthConnector'; import { RefreshingAuthSessionManager } from '../../../../lib/AuthSessionManager'; import { SessionManager } from '../../../../lib/AuthSessionManager/types'; import { AuthRequestOptions, + BackstageIdentityApi, BackstageIdentityResponse, OAuthApi, OpenIdConnectApi, - ProfileInfo, ProfileInfoApi, - SessionState, SessionApi, - BackstageIdentityApi, - BackstageUserIdentity, + SessionState, } from '@backstage/core-plugin-api'; import { Observable } from '@backstage/types'; -import { OAuth2Session } from './types'; -import { OAuthApiCreateOptions } from '../types'; - -/** - * OAuth2 create options. - * @public - */ -export type OAuth2CreateOptions = OAuthApiCreateOptions & { - scopeTransform?: (scopes: string[]) => string[]; - popupOptions?: PopupOptions; - authConnector?: AuthConnector; -}; - -export type OAuth2Response = { - providerInfo: { - accessToken: string; - idToken: string; - scope: string; - expiresInSeconds?: number; - }; - profile: ProfileInfo; - backstageIdentity: { - token: string; - expiresInSeconds?: number; - identity: BackstageUserIdentity; - }; -}; +import { + OAuth2CreateOptions, + OAuth2CreateOptionsWithAuthConnector, + OAuth2Response, + OAuth2Session, +} from './types'; const DEFAULT_PROVIDER = { id: 'oauth2', @@ -81,61 +54,67 @@ export default class OAuth2 BackstageIdentityApi, SessionApi { - static create(options: OAuth2CreateOptions) { + private static createAuthConnector( + options: OAuth2CreateOptions | OAuth2CreateOptionsWithAuthConnector, + ) { + if ('authConnector' in options) { + return options.authConnector; + } const { + scopeTransform = x => x, configApi, discoveryApi, environment = 'development', provider = DEFAULT_PROVIDER, oauthRequestApi, - defaultScopes = [], - scopeTransform = x => x, popupOptions, } = options; - const connector = - options.authConnector ?? - new DefaultAuthConnector({ - configApi, - discoveryApi, - environment, - provider, - oauthRequestApi: oauthRequestApi, - sessionTransform({ - backstageIdentity, - ...res - }: OAuth2Response): OAuth2Session { - const session: OAuth2Session = { - ...res, - providerInfo: { - idToken: res.providerInfo.idToken, - accessToken: res.providerInfo.accessToken, - scopes: OAuth2.normalizeScopes( - scopeTransform, - res.providerInfo.scope, - ), - expiresAt: res.providerInfo.expiresInSeconds - ? new Date( - Date.now() + res.providerInfo.expiresInSeconds * 1000, - ) - : undefined, - }, + return new DefaultAuthConnector({ + configApi, + discoveryApi, + environment, + provider, + oauthRequestApi: oauthRequestApi, + sessionTransform({ + backstageIdentity, + ...res + }: OAuth2Response): OAuth2Session { + const session: OAuth2Session = { + ...res, + providerInfo: { + idToken: res.providerInfo.idToken, + accessToken: res.providerInfo.accessToken, + scopes: OAuth2.normalizeScopes( + scopeTransform, + res.providerInfo.scope, + ), + expiresAt: res.providerInfo.expiresInSeconds + ? new Date(Date.now() + res.providerInfo.expiresInSeconds * 1000) + : undefined, + }, + }; + if (backstageIdentity) { + session.backstageIdentity = { + token: backstageIdentity.token, + identity: backstageIdentity.identity, + expiresAt: backstageIdentity.expiresInSeconds + ? new Date(Date.now() + backstageIdentity.expiresInSeconds * 1000) + : undefined, }; - if (backstageIdentity) { - session.backstageIdentity = { - token: backstageIdentity.token, - identity: backstageIdentity.identity, - expiresAt: backstageIdentity.expiresInSeconds - ? new Date( - Date.now() + backstageIdentity.expiresInSeconds * 1000, - ) - : undefined, - }; - } - return session; - }, - popupOptions, - }); + } + return session; + }, + popupOptions, + }); + } + + static create( + options: OAuth2CreateOptions | OAuth2CreateOptionsWithAuthConnector, + ) { + const { defaultScopes = [], scopeTransform = x => x } = options; + + const connector = OAuth2.createAuthConnector(options); const sessionManager = new RefreshingAuthSessionManager({ connector, @@ -218,7 +197,10 @@ export default class OAuth2 return session?.profile; } - private static normalizeScopes( + /** + * @public + */ + public static normalizeScopes( scopeTransform: (scopes: string[]) => string[], scopes?: string | string[], ): Set { diff --git a/packages/core-app-api/src/apis/implementations/auth/oauth2/OAuth2CustomAuthConnector.test.ts b/packages/core-app-api/src/apis/implementations/auth/oauth2/OAuth2CustomAuthConnector.test.ts new file mode 100644 index 0000000000..508d3c7274 --- /dev/null +++ b/packages/core-app-api/src/apis/implementations/auth/oauth2/OAuth2CustomAuthConnector.test.ts @@ -0,0 +1,121 @@ +/* + * 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 OAuth2 from './OAuth2'; +import { + OAuth2Session, + AuthConnector, + AuthConnectorRefreshSessionOptions, + openLoginPopup, + OAuth2CreateOptionsWithAuthConnector, + OAuth2Response, +} from '../../../../index'; + +const scopeTransform = (x: string[]) => x; + +type Options = { + /** + * Function used to transform an auth response into the session type. + */ + sessionTransform?(response: any): OAuth2Session | Promise; +}; + +class CustomAuthConnector implements AuthConnector { + private readonly sessionTransform: (response: any) => Promise; + + constructor(options: Options) { + const { sessionTransform = id => id } = options; + + this.sessionTransform = sessionTransform; + } + + async createSession() { + return await this.sessionTransform( + await openLoginPopup({ url: 'http://my-origin', name: 'myPopup' }), + ); + } + + async refreshSession(_?: AuthConnectorRefreshSessionOptions): Promise {} + + async removeSession(): Promise {} +} + +describe('OAuth2', () => { + it('should use provided auth provider', async () => { + const popupMock = { closed: false }; + + jest.spyOn(window, 'open').mockReturnValue(popupMock as Window); + + const addEventListenerSpy = jest.spyOn(window, 'addEventListener'); + jest.spyOn(window, 'removeEventListener'); + + const customAuthConnector = new CustomAuthConnector({ + sessionTransform(res: OAuth2Response): OAuth2Session { + return { + ...res, + providerInfo: { + idToken: res.providerInfo.idToken, + accessToken: res.providerInfo.accessToken, + scopes: OAuth2.normalizeScopes( + scopeTransform, + res.providerInfo.scope, + ), + expiresAt: res.providerInfo.expiresInSeconds + ? new Date(Date.now() + res.providerInfo.expiresInSeconds * 1000) + : undefined, + }, + }; + }, + }); + + const options: OAuth2CreateOptionsWithAuthConnector = { + scopeTransform, + defaultScopes: ['myScope'], + authConnector: customAuthConnector, + }; + const oauth2 = OAuth2.create(options); + + // so that AuthConnector calls openLoginPopup synchronously (not try to refresh the token) + const accessToken = oauth2.getAccessToken('myScope', { + instantPopup: true, + optional: false, + }); + + const listener = addEventListenerSpy.mock.calls[0][1] as EventListener; + + const accessTokenValue = 'myAccessToken'; + const myResponse = { + providerInfo: { + accessToken: accessTokenValue, + scope: 'myScope', + expiresInSeconds: 900, + }, + profile: { displayName: 'John Doe' }, + }; + + // A valid sessions response + listener({ + source: popupMock, + origin: 'http://my-origin', + data: { + type: 'authorization_response', + response: myResponse, + }, + } as MessageEvent); + + return expect(accessToken).resolves.toBe(accessTokenValue); + }); +}); diff --git a/packages/core-app-api/src/apis/implementations/auth/oauth2/types.ts b/packages/core-app-api/src/apis/implementations/auth/oauth2/types.ts index e51de63dbe..0f320e97e6 100644 --- a/packages/core-app-api/src/apis/implementations/auth/oauth2/types.ts +++ b/packages/core-app-api/src/apis/implementations/auth/oauth2/types.ts @@ -15,11 +15,13 @@ */ import { - ProfileInfo, BackstageIdentityResponse, + BackstageUserIdentity, + ProfileInfo, } from '@backstage/core-plugin-api'; +import { OAuthApiCreateOptions } from '../types.ts'; +import { AuthConnector, PopupOptions } from '../../../../lib'; -export type { OAuth2CreateOptions } from './OAuth2'; export type { PopupOptions } from '../../../../lib/AuthConnector'; /** * Session information for generic OAuth2 auth. @@ -36,3 +38,40 @@ export type OAuth2Session = { profile: ProfileInfo; backstageIdentity?: BackstageIdentityResponse; }; + +/** + * @public + */ +export type OAuth2Response = { + providerInfo: { + accessToken: string; + idToken: string; + scope: string; + expiresInSeconds?: number; + }; + profile: ProfileInfo; + backstageIdentity: { + token: string; + expiresInSeconds?: number; + identity: BackstageUserIdentity; + }; +}; + +/** + * OAuth2 create options. + * @public + */ +export type OAuth2CreateOptions = OAuthApiCreateOptions & { + scopeTransform?: (scopes: string[]) => string[]; + popupOptions?: PopupOptions; +}; + +/** + * OAuth2 create options with custom auth connector. + * @public + */ +export type OAuth2CreateOptionsWithAuthConnector = { + scopeTransform?: (scopes: string[]) => string[]; + defaultScopes?: string[]; + authConnector: AuthConnector; +}; diff --git a/packages/core-app-api/src/index.ts b/packages/core-app-api/src/index.ts index 1dc8ddec19..a963b0cca9 100644 --- a/packages/core-app-api/src/index.ts +++ b/packages/core-app-api/src/index.ts @@ -27,6 +27,6 @@ export type { AuthConnector, AuthConnectorCreateSessionOptions, AuthConnectorRefreshSessionOptions, - openLoginPopup, OpenLoginPopupOptions, } from './lib'; +export { openLoginPopup } from './lib'; From 144f7629891f7d9fd742f96f92d2bcdbdf30792e Mon Sep 17 00:00:00 2001 From: Gasan Guseinov Date: Thu, 23 Jan 2025 18:39:27 +0100 Subject: [PATCH 007/109] remove unwanted changes Signed-off-by: Gasan Guseinov --- packages/cli/src/commands/repo/test.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/cli/src/commands/repo/test.ts b/packages/cli/src/commands/repo/test.ts index b32f3caaa7..6b3478b5d9 100644 --- a/packages/cli/src/commands/repo/test.ts +++ b/packages/cli/src/commands/repo/test.ts @@ -422,8 +422,5 @@ export async function command(opts: OptionValues, cmd: Command): Promise { }; } - console.log('retry'); - console.log(`args: ${JSON.stringify(args)}`); - await runJest(args); } From af4f756a1d9970829018773d19d8322eb7d19337 Mon Sep 17 00:00:00 2001 From: Gasan Guseinov Date: Fri, 24 Jan 2025 14:04:33 +0100 Subject: [PATCH 008/109] update test name Signed-off-by: Gasan Guseinov --- .../auth/oauth2/OAuth2CustomAuthConnector.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core-app-api/src/apis/implementations/auth/oauth2/OAuth2CustomAuthConnector.test.ts b/packages/core-app-api/src/apis/implementations/auth/oauth2/OAuth2CustomAuthConnector.test.ts index 508d3c7274..e5ef3cbc0d 100644 --- a/packages/core-app-api/src/apis/implementations/auth/oauth2/OAuth2CustomAuthConnector.test.ts +++ b/packages/core-app-api/src/apis/implementations/auth/oauth2/OAuth2CustomAuthConnector.test.ts @@ -53,7 +53,7 @@ class CustomAuthConnector implements AuthConnector { async removeSession(): Promise {} } -describe('OAuth2', () => { +describe('OAuth2CustomAuthConnector', () => { it('should use provided auth provider', async () => { const popupMock = { closed: false }; From f6b38b06a2bdc4cb3674796101dd209e5ed35eac Mon Sep 17 00:00:00 2001 From: Gasan Guseinov Date: Fri, 31 Jan 2025 15:40:12 +0100 Subject: [PATCH 009/109] update custom auth connector test Signed-off-by: Gasan Guseinov --- .../auth/oauth2/OAuth2CustomAuthConnector.test.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/core-app-api/src/apis/implementations/auth/oauth2/OAuth2CustomAuthConnector.test.ts b/packages/core-app-api/src/apis/implementations/auth/oauth2/OAuth2CustomAuthConnector.test.ts index e5ef3cbc0d..cfed4d4714 100644 --- a/packages/core-app-api/src/apis/implementations/auth/oauth2/OAuth2CustomAuthConnector.test.ts +++ b/packages/core-app-api/src/apis/implementations/auth/oauth2/OAuth2CustomAuthConnector.test.ts @@ -23,6 +23,7 @@ import { OAuth2CreateOptionsWithAuthConnector, OAuth2Response, } from '../../../../index'; +import { waitFor } from '@testing-library/react'; const scopeTransform = (x: string[]) => x; @@ -54,7 +55,7 @@ class CustomAuthConnector implements AuthConnector { } describe('OAuth2CustomAuthConnector', () => { - it('should use provided auth provider', async () => { + it('should use custom auth connector', async () => { const popupMock = { closed: false }; jest.spyOn(window, 'open').mockReturnValue(popupMock as Window); @@ -88,11 +89,10 @@ describe('OAuth2CustomAuthConnector', () => { }; const oauth2 = OAuth2.create(options); - // so that AuthConnector calls openLoginPopup synchronously (not try to refresh the token) - const accessToken = oauth2.getAccessToken('myScope', { - instantPopup: true, - optional: false, - }); + const accessToken = oauth2.getAccessToken('myScope'); + + // wait until `openLoginPopup` has been called + await waitFor(() => expect(addEventListenerSpy).toHaveBeenCalled()); const listener = addEventListenerSpy.mock.calls[0][1] as EventListener; From 86decb69d1aed022d8e64507cc610e1ee0f1cb8f Mon Sep 17 00:00:00 2001 From: Andy Ladjadj Date: Mon, 3 Feb 2025 19:07:58 +0100 Subject: [PATCH 010/109] docs: add link to the regex pattern Signed-off-by: Andy LADJADJ --- docs/integrations/gerrit/locations.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/integrations/gerrit/locations.md b/docs/integrations/gerrit/locations.md index 79e7771bc6..a0c48692a8 100644 --- a/docs/integrations/gerrit/locations.md +++ b/docs/integrations/gerrit/locations.md @@ -38,7 +38,7 @@ a structure with up to six elements: address here. This is the address that you would open in a browser. - `cloneUrl` (optional): The base URL for HTTP clones. Will default to `baseUrl` if not set. The address used to clone a repo is the `cloneUrl` plus the repo name. -- `enableEditUrl` (optional): Activate the edit mode for Gerrit >= 3.9 +- `enableEditUrl` (optional): Activate the edit mode for Gerrit >= 3.9 following the [official pattern](https://gerrit-review.googlesource.com/Documentation/user-inline-edit.html#create_from_url) - `username` (optional): The Gerrit username to use in API requests. If neither a username nor password are supplied, anonymous access will be used. - `password` (optional): The password or http token for the Gerrit user. From 398f4b32ee267fcaaf3e849afdacfdef3b526103 Mon Sep 17 00:00:00 2001 From: Gasan Guseinov <465806+gusega@users.noreply.github.com> Date: Tue, 11 Feb 2025 16:22:07 +0100 Subject: [PATCH 011/109] Apply suggestions from code review remove commented code Co-authored-by: Patrik Oldsberg Signed-off-by: Gasan Guseinov <465806+gusega@users.noreply.github.com> --- .../src/apis/implementations/auth/oauth2/OAuth2.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/core-app-api/src/apis/implementations/auth/oauth2/OAuth2.test.ts b/packages/core-app-api/src/apis/implementations/auth/oauth2/OAuth2.test.ts index f662c47bda..018f068a62 100644 --- a/packages/core-app-api/src/apis/implementations/auth/oauth2/OAuth2.test.ts +++ b/packages/core-app-api/src/apis/implementations/auth/oauth2/OAuth2.test.ts @@ -23,7 +23,6 @@ import { AuthConnector, AuthConnectorRefreshSessionOptions, openLoginPopup, - // OAuth2Response, OAuth2CreateOptionsWithAuthConnector, } from '../../../../index'; From 75e0c40abbe929b715a557d2195f15aa1ed4dfc5 Mon Sep 17 00:00:00 2001 From: "Gasan.Guseinov" <465806+gusega@users.noreply.github.com> Date: Tue, 11 Feb 2025 18:18:15 +0100 Subject: [PATCH 012/109] update normalizeScopes signature Signed-off-by: Gasan.Guseinov <465806+gusega@users.noreply.github.com> --- packages/core-app-api/report.api.md | 4 +++- .../apis/implementations/auth/oauth2/OAuth2.ts | 17 +++++++++++------ .../oauth2/OAuth2CustomAuthConnector.test.ts | 5 ++--- 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/packages/core-app-api/report.api.md b/packages/core-app-api/report.api.md index 6af171ea4d..09119d8f8d 100644 --- a/packages/core-app-api/report.api.md +++ b/packages/core-app-api/report.api.md @@ -566,8 +566,10 @@ export class OAuth2 getProfile(options?: AuthRequestOptions): Promise; // (undocumented) static normalizeScopes( - scopeTransform: (scopes: string[]) => string[], scopes?: string | string[], + options?: { + scopeTransform: (scopes: string[]) => string[]; + }, ): Set; // (undocumented) sessionState$(): Observable; diff --git a/packages/core-app-api/src/apis/implementations/auth/oauth2/OAuth2.ts b/packages/core-app-api/src/apis/implementations/auth/oauth2/OAuth2.ts index 7318d30382..c03176f8b6 100644 --- a/packages/core-app-api/src/apis/implementations/auth/oauth2/OAuth2.ts +++ b/packages/core-app-api/src/apis/implementations/auth/oauth2/OAuth2.ts @@ -85,10 +85,9 @@ export default class OAuth2 providerInfo: { idToken: res.providerInfo.idToken, accessToken: res.providerInfo.accessToken, - scopes: OAuth2.normalizeScopes( + scopes: OAuth2.normalizeScopes(res.providerInfo.scope, { scopeTransform, - res.providerInfo.scope, - ), + }), expiresAt: res.providerInfo.expiresInSeconds ? new Date(Date.now() + res.providerInfo.expiresInSeconds * 1000) : undefined, @@ -169,7 +168,9 @@ export default class OAuth2 scope?: string | string[], options?: AuthRequestOptions, ) { - const normalizedScopes = OAuth2.normalizeScopes(this.scopeTransform, scope); + const normalizedScopes = OAuth2.normalizeScopes(scope, { + scopeTransform: this.scopeTransform, + }); const session = await this.sessionManager.getSession({ ...options, scopes: normalizedScopes, @@ -201,8 +202,8 @@ export default class OAuth2 * @public */ public static normalizeScopes( - scopeTransform: (scopes: string[]) => string[], scopes?: string | string[], + options?: { scopeTransform: (scopes: string[]) => string[] }, ): Set { if (!scopes) { return new Set(); @@ -212,6 +213,10 @@ export default class OAuth2 ? scopes : scopes.split(/[\s|,]/).filter(Boolean); - return new Set(scopeTransform(scopeList)); + const transformedScopes = options + ? options.scopeTransform(scopeList) + : scopeList; + + return new Set(transformedScopes); } } diff --git a/packages/core-app-api/src/apis/implementations/auth/oauth2/OAuth2CustomAuthConnector.test.ts b/packages/core-app-api/src/apis/implementations/auth/oauth2/OAuth2CustomAuthConnector.test.ts index cfed4d4714..82578f835e 100644 --- a/packages/core-app-api/src/apis/implementations/auth/oauth2/OAuth2CustomAuthConnector.test.ts +++ b/packages/core-app-api/src/apis/implementations/auth/oauth2/OAuth2CustomAuthConnector.test.ts @@ -70,10 +70,9 @@ describe('OAuth2CustomAuthConnector', () => { providerInfo: { idToken: res.providerInfo.idToken, accessToken: res.providerInfo.accessToken, - scopes: OAuth2.normalizeScopes( + scopes: OAuth2.normalizeScopes(res.providerInfo.scope, { scopeTransform, - res.providerInfo.scope, - ), + }), expiresAt: res.providerInfo.expiresInSeconds ? new Date(Date.now() + res.providerInfo.expiresInSeconds * 1000) : undefined, From f56ee499043feeaf147fb7d04b2858eb7658c4fa Mon Sep 17 00:00:00 2001 From: Andy LADJADJ Date: Mon, 24 Mar 2025 15:25:13 +0100 Subject: [PATCH 013/109] chore: activate gerrit editURL by default Signed-off-by: Andy LADJADJ --- .changeset/ten-spies-explode.md | 8 +++----- docs/integrations/gerrit/locations.md | 6 ++++-- packages/integration/report.api.md | 2 +- .../src/gerrit/GerritIntegration.test.ts | 5 +++-- .../src/gerrit/GerritIntegration.ts | 19 ++++++++++--------- .../integration/src/gerrit/config.test.ts | 10 +++++----- packages/integration/src/gerrit/config.ts | 8 ++++---- 7 files changed, 30 insertions(+), 28 deletions(-) diff --git a/.changeset/ten-spies-explode.md b/.changeset/ten-spies-explode.md index ba7d406398..6e6dac205a 100644 --- a/.changeset/ten-spies-explode.md +++ b/.changeset/ten-spies-explode.md @@ -2,10 +2,8 @@ '@backstage/integration': minor --- -Add Gerrit option to activate the edit mode for version >= 3.9 +Implement Edit URL feature for Gerrit 3.9+. Enabled by default due to 3.8 EOL -``` - enableEditUrl: true -``` +Details: - The Edit URL feature allows for direct editing of files in Gerrit through a specific URL pattern. - URL pattern: `^\/admin\/repos\/edit\/repo\/(.+)\/branch\/(.+)\/file\/(.+)$` -The url pattern is `^\/admin\/repos\/edit\/repo\/(.+)\/branch\/(.+)\/file\/(.+)$` +Caution: - To turn off this functionality, you can add the configuration `disableEditUrl: true` in the Gerrit integration section of your settings diff --git a/docs/integrations/gerrit/locations.md b/docs/integrations/gerrit/locations.md index a0c48692a8..16827dbf09 100644 --- a/docs/integrations/gerrit/locations.md +++ b/docs/integrations/gerrit/locations.md @@ -11,6 +11,8 @@ or registered with the [catalog-import](https://github.com/backstage/backstage/tree/master/plugins/catalog-import) plugin. +Gerrit 3.9+ supports inline editing via URL. the integration enables this by default, following Gerrit's [official URL pattern](https://gerrit-review.googlesource.com/Documentation/user-inline-edit.html#create_from_url) for inline edits. + ## Configuration To use this integration, add at least one Gerrit configuration to your root `app-config.yaml`: @@ -22,7 +24,7 @@ integrations: gitilesBaseUrl: https://gerrit.company.com/gitiles baseUrl: https://gerrit.company.com/gerrit cloneUrl: https://gerrit.company.com/clone - enableEditUrl: true + disableEditUrl: false username: ${GERRIT_USERNAME} password: ${GERRIT_PASSWORD} ``` @@ -38,7 +40,7 @@ a structure with up to six elements: address here. This is the address that you would open in a browser. - `cloneUrl` (optional): The base URL for HTTP clones. Will default to `baseUrl` if not set. The address used to clone a repo is the `cloneUrl` plus the repo name. -- `enableEditUrl` (optional): Activate the edit mode for Gerrit >= 3.9 following the [official pattern](https://gerrit-review.googlesource.com/Documentation/user-inline-edit.html#create_from_url) +- `disableEditUrl` (optional): Disable the edit mode for Gerrit < 3.9 - `username` (optional): The Gerrit username to use in API requests. If neither a username nor password are supplied, anonymous access will be used. - `password` (optional): The password or http token for the Gerrit user. diff --git a/packages/integration/report.api.md b/packages/integration/report.api.md index ecf46b10f1..9baabdd6ce 100644 --- a/packages/integration/report.api.md +++ b/packages/integration/report.api.md @@ -392,7 +392,7 @@ export type GerritIntegrationConfig = { host: string; baseUrl?: string; cloneUrl?: string; - enableEditUrl?: boolean; + disableEditUrl?: boolean; gitilesBaseUrl: string; username?: string; password?: string; diff --git a/packages/integration/src/gerrit/GerritIntegration.test.ts b/packages/integration/src/gerrit/GerritIntegration.test.ts index b415636072..0d4058d47e 100644 --- a/packages/integration/src/gerrit/GerritIntegration.test.ts +++ b/packages/integration/src/gerrit/GerritIntegration.test.ts @@ -138,9 +138,10 @@ describe('GerritIntegration', () => { }); }); - it('resolve edit URL', () => { + it('resolve Url when editUrl is disabled', () => { const integration = new GerritIntegration({ host: 'gerrit-review.example.com', + disableEditUrl: true, } as any); // Resolve edit URLs is not applicable for gerrit. Return the input @@ -158,8 +159,8 @@ describe('GerritIntegration', () => { const integration = new GerritIntegration({ host: 'gerrit-review.example.com', baseUrl: 'https://gerrit-review.example.com', - enableEditUrl: true, gitilesBaseUrl: 'https://gerrit-review.example.com/gitiles', + disableEditUrl: false, } as any); expect( diff --git a/packages/integration/src/gerrit/GerritIntegration.ts b/packages/integration/src/gerrit/GerritIntegration.ts index adca205ac7..ba2153c817 100644 --- a/packages/integration/src/gerrit/GerritIntegration.ts +++ b/packages/integration/src/gerrit/GerritIntegration.ts @@ -75,15 +75,16 @@ export class GerritIntegration implements ScmIntegration { } resolveEditUrl(url: string): string { - if (this.config.enableEditUrl) { - const parsed = parseGitilesUrlRef(this.config, url); - return buildGerritEditUrl( - this.config, - parsed.project, - parsed.ref, - parsed.path, - ); + if (this.config.disableEditUrl) { + return url; } - return url; + + const parsed = parseGitilesUrlRef(this.config, url); + return buildGerritEditUrl( + this.config, + parsed.project, + parsed.ref, + parsed.path, + ); } } diff --git a/packages/integration/src/gerrit/config.test.ts b/packages/integration/src/gerrit/config.test.ts index 8bf1a3d44c..7b6dc01e7e 100644 --- a/packages/integration/src/gerrit/config.test.ts +++ b/packages/integration/src/gerrit/config.test.ts @@ -57,7 +57,7 @@ describe('readGerritIntegrationConfig', () => { host: 'a.com', baseUrl: 'https://a.com/api', cloneUrl: 'https:a.com/clone', - enableEditUrl: false, + disableEditUrl: true, gitilesBaseUrl: 'https://a.com/git', username: 'u', password: ' p ', @@ -67,7 +67,7 @@ describe('readGerritIntegrationConfig', () => { host: 'a.com', baseUrl: 'https://a.com/api', cloneUrl: 'https:a.com/clone', - enableEditUrl: false, + disableEditUrl: true, gitilesBaseUrl: 'https://a.com/git', username: 'u', password: 'p', @@ -78,7 +78,7 @@ describe('readGerritIntegrationConfig', () => { const output = readGerritIntegrationConfig( buildConfig({ host: 'a.com', - enableEditUrl: true, + disableEditUrl: false, gitilesBaseUrl: 'https://a.com/gerrit/plugins/gitiles', }), ); @@ -86,7 +86,7 @@ describe('readGerritIntegrationConfig', () => { host: 'a.com', baseUrl: 'https://a.com', cloneUrl: 'https://a.com', - enableEditUrl: true, + disableEditUrl: false, gitilesBaseUrl: 'https://a.com/gerrit/plugins/gitiles', username: undefined, password: undefined, @@ -177,7 +177,7 @@ describe('readGerritIntegrationConfigs', () => { host: 'b.com', baseUrl: 'https://b.com/api', cloneUrl: 'https://b.com/api', - enableEditUrl: undefined, + disableEditUrl: undefined, gitilesBaseUrl: 'https://b.com/gerrit/plugins/gitiles', username: undefined, password: undefined, diff --git a/packages/integration/src/gerrit/config.ts b/packages/integration/src/gerrit/config.ts index 710e2847de..549842da9e 100644 --- a/packages/integration/src/gerrit/config.ts +++ b/packages/integration/src/gerrit/config.ts @@ -45,9 +45,9 @@ export type GerritIntegrationConfig = { cloneUrl?: string; /** - * Activate the edit url feature available since Gerrit 3.9 + * Disable the edit url feature for Gerrit <= 3.8. */ - enableEditUrl?: boolean; + disableEditUrl?: boolean; /** * Base url for Gitiles. This is needed for creating a valid @@ -80,7 +80,7 @@ export function readGerritIntegrationConfig( const host = config.getString('host'); let baseUrl = config.getOptionalString('baseUrl'); let cloneUrl = config.getOptionalString('cloneUrl'); - const enableEditUrl = config.getOptionalBoolean('enableEditUrl'); + const disableEditUrl = config.getOptionalBoolean('disableEditUrl'); let gitilesBaseUrl = config.getString('gitilesBaseUrl'); const username = config.getOptionalString('username'); const password = config.getOptionalString('password')?.trim(); @@ -119,7 +119,7 @@ export function readGerritIntegrationConfig( host, baseUrl, cloneUrl, - enableEditUrl, + disableEditUrl, gitilesBaseUrl, username, password, From aab955ddc790f6610e7f19bec11eb2a43099e677 Mon Sep 17 00:00:00 2001 From: Andy LADJADJ Date: Mon, 24 Mar 2025 15:28:55 +0100 Subject: [PATCH 014/109] style: update changeset format Signed-off-by: Andy LADJADJ --- .changeset/ten-spies-explode.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/.changeset/ten-spies-explode.md b/.changeset/ten-spies-explode.md index 6e6dac205a..efbddef1be 100644 --- a/.changeset/ten-spies-explode.md +++ b/.changeset/ten-spies-explode.md @@ -4,6 +4,11 @@ Implement Edit URL feature for Gerrit 3.9+. Enabled by default due to 3.8 EOL -Details: - The Edit URL feature allows for direct editing of files in Gerrit through a specific URL pattern. - URL pattern: `^\/admin\/repos\/edit\/repo\/(.+)\/branch\/(.+)\/file\/(.+)$` +Details: -Caution: - To turn off this functionality, you can add the configuration `disableEditUrl: true` in the Gerrit integration section of your settings +- The Edit URL feature allows for direct editing of files in Gerrit through a specific URL pattern. +- URL pattern: `^\/admin\/repos\/edit\/repo\/(.+)\/branch\/(.+)\/file\/(.+)$` + +Caution: + +- To turn off this functionality, you can add the configuration `disableEditUrl: true` in the Gerrit integration section of your settings From f687ad4cbdcfb9e0083c3de0a69fbd7328b25ad0 Mon Sep 17 00:00:00 2001 From: Andy LADJADJ Date: Mon, 24 Mar 2025 15:28:55 +0100 Subject: [PATCH 015/109] chore: update config definition Signed-off-by: Andy LADJADJ --- packages/integration/config.d.ts | 4 ++-- packages/integration/src/gerrit/config.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/integration/config.d.ts b/packages/integration/config.d.ts index fc4559f73a..e819fb203f 100644 --- a/packages/integration/config.d.ts +++ b/packages/integration/config.d.ts @@ -162,10 +162,10 @@ export interface Config { */ cloneUrl?: string; /** - * Activate the edit url feature available since Gerrit 3.9 + * Disable the edit url feature for Gerrit version less than 3.9. * @visibility frontend */ - enableEditUrl?: boolean; + disableEditUrl?: boolean; /** * The username to use for authenticated requests. * @visibility secret diff --git a/packages/integration/src/gerrit/config.ts b/packages/integration/src/gerrit/config.ts index 549842da9e..0d1939dda8 100644 --- a/packages/integration/src/gerrit/config.ts +++ b/packages/integration/src/gerrit/config.ts @@ -45,7 +45,7 @@ export type GerritIntegrationConfig = { cloneUrl?: string; /** - * Disable the edit url feature for Gerrit <= 3.8. + * Disable the edit url feature for Gerrit version less than 3.9. */ disableEditUrl?: boolean; From 50e2d6fb6ce7c4ee56b0db17ba46e06f51eb12da Mon Sep 17 00:00:00 2001 From: "Gasan.Guseinov" <465806+gusega@users.noreply.github.com> Date: Tue, 25 Mar 2025 17:11:23 +0100 Subject: [PATCH 016/109] unexport OAuth2Response Signed-off-by: Gasan.Guseinov <465806+gusega@users.noreply.github.com> --- packages/core-app-api/report.api.md | 17 -------------- .../implementations/auth/oauth2/OAuth2.ts | 18 ++++++++++++++- .../oauth2/OAuth2CustomAuthConnector.test.ts | 23 ++++++++++++++++++- .../apis/implementations/auth/oauth2/types.ts | 19 --------------- 4 files changed, 39 insertions(+), 38 deletions(-) diff --git a/packages/core-app-api/report.api.md b/packages/core-app-api/report.api.md index 09119d8f8d..68dd2f05e8 100644 --- a/packages/core-app-api/report.api.md +++ b/packages/core-app-api/report.api.md @@ -21,7 +21,6 @@ import { AuthRequestOptions } from '@backstage/core-plugin-api'; import { BackstageIdentityApi } from '@backstage/core-plugin-api'; import { BackstageIdentityResponse } from '@backstage/core-plugin-api'; import { BackstagePlugin } from '@backstage/core-plugin-api'; -import { BackstageUserIdentity } from '@backstage/core-plugin-api'; import { bitbucketAuthApiRef } from '@backstage/core-plugin-api'; import { bitbucketServerAuthApiRef } from '@backstage/core-plugin-api'; import { ComponentType } from 'react'; @@ -592,22 +591,6 @@ export type OAuth2CreateOptionsWithAuthConnector = { authConnector: AuthConnector; }; -// @public (undocumented) -export type OAuth2Response = { - providerInfo: { - accessToken: string; - idToken: string; - scope: string; - expiresInSeconds?: number; - }; - profile: ProfileInfo; - backstageIdentity: { - token: string; - expiresInSeconds?: number; - identity: BackstageUserIdentity; - }; -}; - // @public export type OAuth2Session = { providerInfo: { diff --git a/packages/core-app-api/src/apis/implementations/auth/oauth2/OAuth2.ts b/packages/core-app-api/src/apis/implementations/auth/oauth2/OAuth2.ts index c03176f8b6..92747a1707 100644 --- a/packages/core-app-api/src/apis/implementations/auth/oauth2/OAuth2.ts +++ b/packages/core-app-api/src/apis/implementations/auth/oauth2/OAuth2.ts @@ -21,8 +21,10 @@ import { AuthRequestOptions, BackstageIdentityApi, BackstageIdentityResponse, + BackstageUserIdentity, OAuthApi, OpenIdConnectApi, + ProfileInfo, ProfileInfoApi, SessionApi, SessionState, @@ -31,7 +33,6 @@ import { Observable } from '@backstage/types'; import { OAuth2CreateOptions, OAuth2CreateOptionsWithAuthConnector, - OAuth2Response, OAuth2Session, } from './types'; @@ -41,6 +42,21 @@ const DEFAULT_PROVIDER = { icon: () => null, }; +export type OAuth2Response = { + providerInfo: { + accessToken: string; + idToken: string; + scope: string; + expiresInSeconds?: number; + }; + profile: ProfileInfo; + backstageIdentity: { + token: string; + expiresInSeconds?: number; + identity: BackstageUserIdentity; + }; +}; + /** * Implements a generic OAuth2 flow for auth. * diff --git a/packages/core-app-api/src/apis/implementations/auth/oauth2/OAuth2CustomAuthConnector.test.ts b/packages/core-app-api/src/apis/implementations/auth/oauth2/OAuth2CustomAuthConnector.test.ts index 82578f835e..a3188c9a80 100644 --- a/packages/core-app-api/src/apis/implementations/auth/oauth2/OAuth2CustomAuthConnector.test.ts +++ b/packages/core-app-api/src/apis/implementations/auth/oauth2/OAuth2CustomAuthConnector.test.ts @@ -21,9 +21,9 @@ import { AuthConnectorRefreshSessionOptions, openLoginPopup, OAuth2CreateOptionsWithAuthConnector, - OAuth2Response, } from '../../../../index'; import { waitFor } from '@testing-library/react'; +import { BackstageUserIdentity, ProfileInfo } from '@backstage/core-plugin-api'; const scopeTransform = (x: string[]) => x; @@ -34,6 +34,27 @@ type Options = { sessionTransform?(response: any): OAuth2Session | Promise; }; +/** + * A replica of private `OAuth2Response` from OAuth2.ts. + * `OAuth2Response` represents raw OAuth2 response from the `auth-backend` plugin. + * If custom auth connector calls `auth-backend` plugin, it will have to transform `OAuth2Response` into + * `OAuth2Session`. + */ +type OAuth2Response = { + providerInfo: { + accessToken: string; + idToken: string; + scope: string; + expiresInSeconds?: number; + }; + profile: ProfileInfo; + backstageIdentity: { + token: string; + expiresInSeconds?: number; + identity: BackstageUserIdentity; + }; +}; + class CustomAuthConnector implements AuthConnector { private readonly sessionTransform: (response: any) => Promise; diff --git a/packages/core-app-api/src/apis/implementations/auth/oauth2/types.ts b/packages/core-app-api/src/apis/implementations/auth/oauth2/types.ts index 0f320e97e6..651113fd4d 100644 --- a/packages/core-app-api/src/apis/implementations/auth/oauth2/types.ts +++ b/packages/core-app-api/src/apis/implementations/auth/oauth2/types.ts @@ -16,7 +16,6 @@ import { BackstageIdentityResponse, - BackstageUserIdentity, ProfileInfo, } from '@backstage/core-plugin-api'; import { OAuthApiCreateOptions } from '../types.ts'; @@ -39,24 +38,6 @@ export type OAuth2Session = { backstageIdentity?: BackstageIdentityResponse; }; -/** - * @public - */ -export type OAuth2Response = { - providerInfo: { - accessToken: string; - idToken: string; - scope: string; - expiresInSeconds?: number; - }; - profile: ProfileInfo; - backstageIdentity: { - token: string; - expiresInSeconds?: number; - identity: BackstageUserIdentity; - }; -}; - /** * OAuth2 create options. * @public From 57420057bce3e1f63003ce715ea96ce3601cbe96 Mon Sep 17 00:00:00 2001 From: Peter Macdonald Date: Tue, 15 Apr 2025 12:42:02 +0200 Subject: [PATCH 017/109] docs: adding some life to the software template docs Signed-off-by: Peter Macdonald --- .../software-templates/configuration.md | 5 +- .../software-templates/input-examples.md | 55 +++++++++++++++---- .../software-templates/writing-templates.md | 3 +- 3 files changed, 48 insertions(+), 15 deletions(-) diff --git a/docs/features/software-templates/configuration.md b/docs/features/software-templates/configuration.md index 571f1150d0..a692335ffe 100644 --- a/docs/features/software-templates/configuration.md +++ b/docs/features/software-templates/configuration.md @@ -78,11 +78,11 @@ Once you have more than a few software templates you may want to customize your accomplish this by creating `groups` and passing them to your `ScaffolderPage` like below -``` +```tsx entity?.metadata?.tags?.includes('recommended') ?? false, }, @@ -96,6 +96,7 @@ top of the page above any other templates not filtered by this group or others. You can also further customize groups by passing in a `titleComponent` instead of a `title` which will be a component to use as the header instead of just the default `ContentHeader` with the `title` set as it's value. + ![Grouped Templates](../../assets/software-templates/grouped-templates.png) There is also an option to hide some templates. diff --git a/docs/features/software-templates/input-examples.md b/docs/features/software-templates/input-examples.md index bb4ecde946..84cfec28ed 100644 --- a/docs/features/software-templates/input-examples.md +++ b/docs/features/software-templates/input-examples.md @@ -12,6 +12,8 @@ It is important to remember that all examples are based on [react-jsonschema-for ### Simple input with basic validations +We can use a `pattern` to validate the input. The `pattern` is a regular expression that the input must match. + ```yaml parameters: - title: Fill in some steps @@ -28,6 +30,8 @@ parameters: #### Custom validation error message +This example shows how to customize the error message shown when the `pattern` validation fails. + ```yaml parameters: - title: Fill in some steps @@ -47,6 +51,8 @@ parameters: ### Multi line text input +If you need to insert a multi-line string, you can use the `ui:widget: textarea` option. This will create a text area input instead of a single line input. + ```yaml parameters: - title: Fill in some steps @@ -74,6 +80,8 @@ parameters: ### Array with custom titles +In the example below the user will see the the `enumNames` instead of the `enum` values, making it easier to read. + ```yaml parameters: - title: Fill in some steps @@ -103,6 +111,8 @@ parameters: ### A multiple choices list +This is a simple multiple choice list. + ```yaml parameters: - title: Fill in some steps @@ -122,6 +132,8 @@ parameters: ### Array with another types +In the example below, it will create an array of custom objects. Once you add one, you will see an object where each one contains 3 fields, "How are you?", "Is it sunny?" and "Anything else?". + ```yaml parameters: - title: Fill in some steps @@ -138,19 +150,19 @@ parameters: type: object properties: array: - title: Array string with default value + title: How are you? type: string - default: value3 + default: good enum: - - value1 - - value2 - - value3 + - good + - okay + - great flag: - title: Boolean flag + title: Is it sunny? type: boolean ui:widget: radio someInput: - title: Simple text input + title: Anything else? type: string ``` @@ -158,6 +170,8 @@ parameters: ### Boolean +This adds a simple checkbox to the form. The value will be `true` or `false`. + ```yaml parameters: - title: Fill in some steps @@ -169,6 +183,8 @@ parameters: ### Boolean Yes or No options +This example shows how to use a radio button instead of a checkbox with `Yes` or `No` options. + ```yaml parameters: - title: Fill in some steps @@ -181,6 +197,8 @@ parameters: ### Boolean multiple options +You can create multiple checkboxes with different options. The example below shows how to create a list of features that can be enabled or disabled for example. + ```yaml parameters: - title: Fill in some steps @@ -200,6 +218,8 @@ parameters: ## Markdown text blocks +Its possible to render markdown text blocks in the form. This is useful to add some help text or instructions for the user. + ```yaml parameters: - title: Fill in some steps @@ -217,7 +237,7 @@ parameters: ## Use parameters as condition in steps -Conditions use Javascript equality operators. +Its possible to conditionally run steps based on the value of a parameter. In the example below, we trigger the steps depending on the value of the `environment` parameter. ```yaml - name: Only development environments @@ -241,6 +261,8 @@ Conditions use Javascript equality operators. ## Use parameters as conditional for fields +Its also possible to conditionally show fields based on the value of a parameter. In the example below, we show the `lastName` field only if the `includeName` parameter is set to `true`. + ```yaml parameters: - title: Fill in some steps @@ -269,6 +291,9 @@ parameters: ### Multiple conditional fields with custom ordering +In this example, we show how to conditionally show multiple fields based on the value of a parameter. The `ui:order` property is used to control the order of the fields in the form. +In this case, we show the `lastName` and `address` fields only if the `includeName` and `includeAddress` parameters are set to `true`. + ```yaml parameters: - title: Fill in some steps @@ -349,12 +374,16 @@ Testing of this functionality is not yet supported using _create/edit_. In addit ::: +Its possible to use placeholders to reference remote files. This is useful when you have some standard parameters or actions that you want to reuse across multiple templates. + ### template.yaml +In our template, we use the `$yaml` placeholder to reference the `parameters.yaml` and `action.yaml` files. The `parameters.yaml` file contains some parameters that we want to use in our template, and the `action.yaml` file contains the action that we want to run. + ```yaml spec: parameters: - - $yaml: https://github.com/example/path/to/example.yaml + - $yaml: https://github.com/example/path/to/parameters.yaml # This would become the parameters as referenced in the parameters.yaml file - title: Fill in some steps properties: path: @@ -362,7 +391,7 @@ spec: type: string steps: - - $yaml: https://github.com/example/path/to/action.yaml + - $yaml: https://github.com/example/path/to/action.yaml # This would become the publish action as referenced in the action.yaml file - id: fetch name: Fetch template @@ -371,7 +400,9 @@ spec: url: ${{ parameters.path if parameters.path else '/root' }} ``` -### example.yaml +### parameters.yaml + +The `url` parameter will be added to the template. ```yaml title: Provide simple information @@ -385,6 +416,8 @@ properties: ### action.yaml +The `publish:github` action will be included in our template. + ```yaml id: publish name: Publish files diff --git a/docs/features/software-templates/writing-templates.md b/docs/features/software-templates/writing-templates.md index bc2847a81b..4f8691e948 100644 --- a/docs/features/software-templates/writing-templates.md +++ b/docs/features/software-templates/writing-templates.md @@ -4,8 +4,7 @@ title: Writing Templates description: Details around creating your own custom Software Templates --- -Templates are stored in the **Software Catalog** under a kind `Template`. You -can create your own templates with a small `yaml` definition which describes the +You can create your own templates with a small `yaml` definition which describes the template and its metadata, along with some input variables that your template will need, and then a list of actions which are then executed by the scaffolding service. From 6b7079be931939eceaec7eed21ab0c68d416737c Mon Sep 17 00:00:00 2001 From: Peter Macdonald Date: Tue, 15 Apr 2025 13:51:24 +0200 Subject: [PATCH 018/109] docs: remove some redundant parts, add urls to the modules Signed-off-by: Peter Macdonald --- .../software-templates/builtin-actions.md | 21 +++++++------------ 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/docs/features/software-templates/builtin-actions.md b/docs/features/software-templates/builtin-actions.md index bf6875764e..75dee9d2a1 100644 --- a/docs/features/software-templates/builtin-actions.md +++ b/docs/features/software-templates/builtin-actions.md @@ -12,13 +12,13 @@ git repository. There are also several modules available for various SCM tools: -- Azure DevOps: `@backstage/plugin-scaffolder-backend-module-azure` -- Bitbucket Cloud: `@backstage/plugin-scaffolder-backend-module-bitbucket-cloud` -- Bitbucket Server: `@backstage/plugin-scaffolder-backend-module-bitbucket-server` -- Gerrit: `@backstage/plugin-scaffolder-backend-module-gerrit` -- Gitea: `@backstage/plugin-scaffolder-backend-module-gitea` -- GitHub: `@backstage/plugin-scaffolder-backend-module-github` -- GitLab: `@backstage/plugin-scaffolder-backend-module-gitlab` +- [Azure DevOps](https://github.com/backstage/backstage/tree/master/plugins/scaffolder-backend-module-azure): `@backstage/plugin-scaffolder-backend-module-azure` +- [Bitbucket Cloud](https://github.com/backstage/backstage/tree/master/plugins/scaffolder-backend-module-bitbucket-cloud): `@backstage/plugin-scaffolder-backend-module-bitbucket-cloud` +- [Bitbucket Server](https://github.com/backstage/backstage/tree/master/plugins/scaffolder-backend-module-bitbucket-server): `@backstage/plugin-scaffolder-backend-module-bitbucket-server` +- [Gerrit](https://github.com/backstage/backstage/tree/master/plugins/scaffolder-backend-module-gerrit): `@backstage/plugin-scaffolder-backend-module-gerrit` +- [Gitea](https://github.com/backstage/backstage/tree/master/plugins/scaffolder-backend-module-gitea): `@backstage/plugin-scaffolder-backend-module-gitea` +- [GitHub](https://github.com/backstage/backstage/tree/master/plugins/scaffolder-backend-module-github): `@backstage/plugin-scaffolder-backend-module-github` +- [GitLab](https://github.com/backstage/backstage/tree/master/plugins/scaffolder-backend-module-gitlab): `@backstage/plugin-scaffolder-backend-module-gitlab` ## Installing Action Modules @@ -37,7 +37,6 @@ const backend = createBackend(); backend.add(import('@backstage/plugin-app-backend')); -// catalog plugin backend.add(import('@backstage/plugin-catalog-backend')); backend.add( import('@backstage/plugin-catalog-backend-module-scaffolder-entity-model'), @@ -51,12 +50,6 @@ backend.add(import('@backstage/plugin-scaffolder-backend-module-github')); backend.start(); ``` -:::note Note - -This is a simplified example of what your backend may look like, you may have more code in here then this. - -::: - ## Listing Actions A list of all registered actions can be found under `/create/actions`. For local From ac82ed9c6364789c657c5352ea6b16fb0f477555 Mon Sep 17 00:00:00 2001 From: Peter Macdonald Date: Tue, 15 Apr 2025 14:00:36 +0200 Subject: [PATCH 019/109] docs: small adjustments to custom actions section Signed-off-by: Peter Macdonald --- .../writing-custom-actions.md | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/docs/features/software-templates/writing-custom-actions.md b/docs/features/software-templates/writing-custom-actions.md index dd7a5e60a1..17a8ff20e4 100644 --- a/docs/features/software-templates/writing-custom-actions.md +++ b/docs/features/software-templates/writing-custom-actions.md @@ -25,7 +25,7 @@ setup process, allowing you to focus on your actions' unique functionality. Start by using the `yarn backstage-cli new` command to generate a scaffolder module. This command sets up the necessary boilerplate code, providing a smooth start: -``` +```sh $ yarn backstage-cli new ? What do you want to create? plugin-common - A new isomorphic common plugin package @@ -34,8 +34,6 @@ $ yarn backstage-cli new > scaffolder-module - An module exporting custom actions for @backstage/plugin-scaffolder-backend ``` -You can find a [list](../../tooling/cli/03-commands.md) of all commands provided by the Backstage CLI. - When prompted, select the option to generate a scaffolder module. This creates a solid foundation for your custom action. Enter the name of the module you wish to create, and the CLI will generate the required files and directory structure. @@ -87,7 +85,7 @@ for reference. The `createTemplateAction` takes an object which specifies the following: -- `id` - A unique ID for your custom action. We encourage you to namespace these +- `id` - A **unique** ID for your custom action. We encourage you to namespace these in some way so that they won't collide with future built-in actions that we may ship with the `scaffolder-backend` plugin. - `description` - An optional field to describe the purpose of the action. This will populate in the `/create/actions` @@ -151,9 +149,6 @@ Also feel free to use your company name to namespace them if you prefer too, for Prefer to use `camelCase` over `snake_case` or `kebab-case` for these actions if possible, which leads to better reading and writing of template entity definitions. -> We're aware that there are some exceptions to this, but try to follow as close as possible. We'll be working on -> migrating these in the repository over time too. - ### Adding a TemplateExample A TemplateExample is a predefined structure that can be used to create custom actions in your software templates. It @@ -187,7 +182,9 @@ export const examples: TemplateExample[] = [ Add the example to the `createTemplateAction` under the object property `examples`: -`return createTemplateAction<{ contents: string; filename: string }>({id: 'acme:file:create', description: 'Create an Acme file', examples, ...};` +```ts +return createTemplateAction<{ contents: string; filename: string }>({id: 'acme:file:create', description: 'Create an Acme file', examples, ...}); +``` ### The context object @@ -275,9 +272,7 @@ env.registerInit({ ### Using Checkpoints in Custom Actions (Experimental) -Idempotent action could be achieved via the usage of checkpoints. - -Example: +Idempotent action could be achieved via the usage of checkpoints, for example: ```ts title="plugins/my-company-scaffolder-actions-plugin/src/vendor/my-custom-action.ts" const res = await ctx.checkpoint?.({ From b5d87f2f501ae0153b75302c6b925d64423b3dd8 Mon Sep 17 00:00:00 2001 From: Peter Macdonald Date: Tue, 15 Apr 2025 14:02:01 +0200 Subject: [PATCH 020/109] docs: remove legacy backend part Signed-off-by: Peter Macdonald --- .../writing-custom-actions.md | 51 ------------------- 1 file changed, 51 deletions(-) diff --git a/docs/features/software-templates/writing-custom-actions.md b/docs/features/software-templates/writing-custom-actions.md index 17a8ff20e4..ca5959e72e 100644 --- a/docs/features/software-templates/writing-custom-actions.md +++ b/docs/features/software-templates/writing-custom-actions.md @@ -300,57 +300,6 @@ If you'll preserve the same key, and you'll try to restart the affected task, it The cached result will not match with the expected updated return type. By changing the key, you'll invalidate the cache of the checkpoint. -### Register Custom Actions with the Legacy Backend System - -Once you have your Custom Action ready for usage with the scaffolder, you'll -need to pass this into the `scaffolder-backend` `createRouter` function. You -should have something similar to the below in -`packages/backend/src/plugins/scaffolder.ts` - -```ts -return await createRouter({ - catalogClient, - logger: env.logger, - config: env.config, - database: env.database, - reader: env.reader, -}); -``` - -There's another property you can pass here, which is an array of `actions` which -will set the available actions that the scaffolder has access to. - -```ts -import { createBuiltinActions } from '@backstage/plugin-scaffolder-backend'; -import { ScmIntegrations } from '@backstage/integration'; -import { createNewFileAction } from './scaffolder/actions/custom'; - -export default async function createPlugin( - env: PluginEnvironment, -): Promise { - const catalogClient = new CatalogClient({ discoveryApi: env.discovery }); - const integrations = ScmIntegrations.fromConfig(env.config); - - const builtInActions = createBuiltinActions({ - integrations, - catalogClient, - config: env.config, - reader: env.reader, - }); - - const actions = [...builtInActions, createNewFileAction()]; - - return createRouter({ - actions, - catalogClient: catalogClient, - logger: env.logger, - config: env.config, - database: env.database, - reader: env.reader, - }); -} -``` - ## List of custom action packages Here is a list of Open Source custom actions that you can add to your Backstage From 141742b184983b6d12fc7c174453a4c122f8945e Mon Sep 17 00:00:00 2001 From: Peter Macdonald Date: Tue, 15 Apr 2025 14:14:39 +0200 Subject: [PATCH 021/109] docs: move around some of the contributed actions and available actions Signed-off-by: Peter Macdonald --- contrib/scaffolder/custom-action-packages.md | 19 ++++++++++++++ .../software-templates/builtin-actions.md | 6 ++++- .../writing-custom-actions.md | 25 ++----------------- 3 files changed, 26 insertions(+), 24 deletions(-) create mode 100644 contrib/scaffolder/custom-action-packages.md diff --git a/contrib/scaffolder/custom-action-packages.md b/contrib/scaffolder/custom-action-packages.md new file mode 100644 index 0000000000..d82221ae1e --- /dev/null +++ b/contrib/scaffolder/custom-action-packages.md @@ -0,0 +1,19 @@ +# List of custom action packages + +Here is a list of Open Source custom actions that you can add to your Backstage +scaffolder backend! + +| Name | Package | Owner | +| ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------ | +| HTTP requests | [scaffolder-backend-module-http-request](https://www.npmjs.com/package/@roadiehq/scaffolder-backend-module-http-request) | [Roadie](https://roadie.io) | +| Utility actions | [scaffolder-backend-module-utils](https://www.npmjs.com/package/@roadiehq/scaffolder-backend-module-utils) | [Roadie](https://roadie.io) | +| AWS cli actions | [scaffolder-backend-module-aws](https://www.npmjs.com/package/@roadiehq/scaffolder-backend-module-aws) | [Roadie](https://roadie.io) | +| Scaffolder .NET Actions | [plugin-scaffolder-dotnet-backend](https://www.npmjs.com/package/@plusultra/plugin-scaffolder-dotnet-backend) | [Alef Carlos](https://github.com/alefcarlos) | +| Scaffolder Git Actions | [plugin-scaffolder-git-actions](https://www.npmjs.com/package/@mdude2314/backstage-plugin-scaffolder-git-actions) | [Drew Hill](https://github.com/arhill05) | +| Azure Pipeline Actions | [scaffolder-backend-module-azure-pipelines](https://www.npmjs.com/package/@parfuemerie-douglas/scaffolder-backend-module-azure-pipelines) | [Parfümerie Douglas](https://github.com/Parfuemerie-Douglas) | +| Azure Repository Actions | [scaffolder-backend-module-azure-repositories](https://www.npmjs.com/package/@parfuemerie-douglas/scaffolder-backend-module-azure-repositories) | [Parfümerie Douglas](https://github.com/Parfuemerie-Douglas) | +| Snyk Import Project | [plugin-scaffolder-backend-module-snyk](https://www.npmjs.com/package/@ma11hewthomas/plugin-scaffolder-backend-module-snyk) | [Matthew Thomas](https://github.com/Ma11hewThomas) | +| JSON Merge Actions | [plugin-scaffolder-json-merge-actions](https://www.npmjs.com/package/@mdude2314/backstage-plugin-scaffolder-json-merge-actions) | [Drew Hill](https://github.com/arhill05) | +| NPM Actions | [plugin-scaffolder-npm-actions](https://www.npmjs.com/package/@mdude2314/backstage-plugin-scaffolder-npm-actions) | [Drew Hill](https://github.com/arhill05) | +| Slack Actions | [plugin-scaffolder-backend-module-slack](https://www.npmjs.com/package/@mdude2314/backstage-plugin-scaffolder-backend-module-slack) | [Drew Hill](https://github.com/arhill05) | +| Microsoft Teams Actions | [plugin-scaffolder-backend-module-ms-teams](https://www.npmjs.com/package/@grvpandey11/backstage-plugin-scaffolder-backend-module-ms-teams) | [Gaurav Pandey](https://github.com/grvpandey11) | diff --git a/docs/features/software-templates/builtin-actions.md b/docs/features/software-templates/builtin-actions.md index 75dee9d2a1..d732bd8a27 100644 --- a/docs/features/software-templates/builtin-actions.md +++ b/docs/features/software-templates/builtin-actions.md @@ -10,7 +10,7 @@ git repository. ## Action Modules -There are also several modules available for various SCM tools: +There are several action modules that are available to be added: - [Azure DevOps](https://github.com/backstage/backstage/tree/master/plugins/scaffolder-backend-module-azure): `@backstage/plugin-scaffolder-backend-module-azure` - [Bitbucket Cloud](https://github.com/backstage/backstage/tree/master/plugins/scaffolder-backend-module-bitbucket-cloud): `@backstage/plugin-scaffolder-backend-module-bitbucket-cloud` @@ -19,6 +19,10 @@ There are also several modules available for various SCM tools: - [Gitea](https://github.com/backstage/backstage/tree/master/plugins/scaffolder-backend-module-gitea): `@backstage/plugin-scaffolder-backend-module-gitea` - [GitHub](https://github.com/backstage/backstage/tree/master/plugins/scaffolder-backend-module-github): `@backstage/plugin-scaffolder-backend-module-github` - [GitLab](https://github.com/backstage/backstage/tree/master/plugins/scaffolder-backend-module-gitlab): `@backstage/plugin-scaffolder-backend-module-gitlab` +- [Rails](https://github.com/backstage/backstage/tree/master/plugins/scaffolder-backend-module-rails): `@backstage/plugin-scaffolder-backend-module-rails` +- [Yeoman](https://github.com/backstage/backstage/tree/master/plugins/scaffolder-backend-module-yeoman): `@backstage/plugin-scaffolder-backend-module-yeoman` +- [Sentry](https://github.com/backstage/backstage/tree/master/plugins/scaffolder-backend-module-sentry): `@backstage/plugin-scaffolder-backend-module-sentry` +- [Cookiecutter](https://github.com/backstage/backstage/tree/master/plugins/scaffolder-backend-module-cookiecutter): `@backstage/plugin-scaffolder-backend-module-cookiecutter` ## Installing Action Modules diff --git a/docs/features/software-templates/writing-custom-actions.md b/docs/features/software-templates/writing-custom-actions.md index ca5959e72e..8987f9af40 100644 --- a/docs/features/software-templates/writing-custom-actions.md +++ b/docs/features/software-templates/writing-custom-actions.md @@ -300,27 +300,6 @@ If you'll preserve the same key, and you'll try to restart the affected task, it The cached result will not match with the expected updated return type. By changing the key, you'll invalidate the cache of the checkpoint. -## List of custom action packages +## Contributed Community Actions -Here is a list of Open Source custom actions that you can add to your Backstage -scaffolder backend: - -| Name | Package | Owner | -| ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------ | -| Yeoman | [plugin-scaffolder-backend-module-yeoman](https://www.npmjs.com/package/@backstage/plugin-scaffolder-backend-module-yeoman) | [Backstage](https://backstage.io) | -| Cookiecutter | [plugin-scaffolder-backend-module-cookiecutter](https://www.npmjs.com/package/@backstage/plugin-scaffolder-backend-module-cookiecutter) | [Backstage](https://backstage.io) | -| Rails | [plugin-scaffolder-backend-module-rails](https://www.npmjs.com/package/@backstage/plugin-scaffolder-backend-module-rails) | [Backstage](https://backstage.io) | -| HTTP requests | [scaffolder-backend-module-http-request](https://www.npmjs.com/package/@roadiehq/scaffolder-backend-module-http-request) | [Roadie](https://roadie.io) | -| Utility actions | [scaffolder-backend-module-utils](https://www.npmjs.com/package/@roadiehq/scaffolder-backend-module-utils) | [Roadie](https://roadie.io) | -| AWS cli actions | [scaffolder-backend-module-aws](https://www.npmjs.com/package/@roadiehq/scaffolder-backend-module-aws) | [Roadie](https://roadie.io) | -| Scaffolder .NET Actions | [plugin-scaffolder-dotnet-backend](https://www.npmjs.com/package/@plusultra/plugin-scaffolder-dotnet-backend) | [Alef Carlos](https://github.com/alefcarlos) | -| Scaffolder Git Actions | [plugin-scaffolder-git-actions](https://www.npmjs.com/package/@mdude2314/backstage-plugin-scaffolder-git-actions) | [Drew Hill](https://github.com/arhill05) | -| Azure Pipeline Actions | [scaffolder-backend-module-azure-pipelines](https://www.npmjs.com/package/@parfuemerie-douglas/scaffolder-backend-module-azure-pipelines) | [Parfümerie Douglas](https://github.com/Parfuemerie-Douglas) | -| Azure Repository Actions | [scaffolder-backend-module-azure-repositories](https://www.npmjs.com/package/@parfuemerie-douglas/scaffolder-backend-module-azure-repositories) | [Parfümerie Douglas](https://github.com/Parfuemerie-Douglas) | -| Snyk Import Project | [plugin-scaffolder-backend-module-snyk](https://www.npmjs.com/package/@ma11hewthomas/plugin-scaffolder-backend-module-snyk) | [Matthew Thomas](https://github.com/Ma11hewThomas) | -| JSON Merge Actions | [plugin-scaffolder-json-merge-actions](https://www.npmjs.com/package/@mdude2314/backstage-plugin-scaffolder-json-merge-actions) | [Drew Hill](https://github.com/arhill05) | -| NPM Actions | [plugin-scaffolder-npm-actions](https://www.npmjs.com/package/@mdude2314/backstage-plugin-scaffolder-npm-actions) | [Drew Hill](https://github.com/arhill05) | -| Slack Actions | [plugin-scaffolder-backend-module-slack](https://www.npmjs.com/package/@mdude2314/backstage-plugin-scaffolder-backend-module-slack) | [Drew Hill](https://github.com/arhill05) | -| Microsoft Teams Actions | [plugin-scaffolder-backend-module-ms-teams](https://www.npmjs.com/package/@grvpandey11/backstage-plugin-scaffolder-backend-module-ms-teams) | [Gaurav Pandey](https://github.com/grvpandey11) | - -Have fun! 🚀 +You can find a list of community-contributed actions here in our [contrib](https://github.com/backstage/backstage/blob/master/contrib/scaffolder/custom-action-packages.md) docs! From aebc6b7119e0b1cd49ec19c9372fe27db013915e Mon Sep 17 00:00:00 2001 From: Peter Macdonald Date: Tue, 15 Apr 2025 14:17:02 +0200 Subject: [PATCH 022/109] docs: mention community plugins repo Signed-off-by: Peter Macdonald --- contrib/scaffolder/custom-action-packages.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/contrib/scaffolder/custom-action-packages.md b/contrib/scaffolder/custom-action-packages.md index d82221ae1e..d3b634bff5 100644 --- a/contrib/scaffolder/custom-action-packages.md +++ b/contrib/scaffolder/custom-action-packages.md @@ -1,7 +1,6 @@ # List of custom action packages -Here is a list of Open Source custom actions that you can add to your Backstage -scaffolder backend! +Here is a list of Open Source custom actions that you can add to your Backstage scaffolder backend! | Name | Package | Owner | | ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------ | @@ -17,3 +16,5 @@ scaffolder backend! | NPM Actions | [plugin-scaffolder-npm-actions](https://www.npmjs.com/package/@mdude2314/backstage-plugin-scaffolder-npm-actions) | [Drew Hill](https://github.com/arhill05) | | Slack Actions | [plugin-scaffolder-backend-module-slack](https://www.npmjs.com/package/@mdude2314/backstage-plugin-scaffolder-backend-module-slack) | [Drew Hill](https://github.com/arhill05) | | Microsoft Teams Actions | [plugin-scaffolder-backend-module-ms-teams](https://www.npmjs.com/package/@grvpandey11/backstage-plugin-scaffolder-backend-module-ms-teams) | [Gaurav Pandey](https://github.com/grvpandey11) | + +You may also want to check out the [Community Plugins Repo](https://github.com/backstage/community-plugins) for more! From 0592917347354e83059a52101d7ae1a83b6821e3 Mon Sep 17 00:00:00 2001 From: Peter Macdonald Date: Tue, 15 Apr 2025 14:42:28 +0200 Subject: [PATCH 023/109] docs: update contributed actions to refer to plugins directory and community repo instead Signed-off-by: Peter Macdonald --- contrib/scaffolder/custom-action-packages.md | 20 ------------------- .../writing-custom-actions.md | 5 ++++- 2 files changed, 4 insertions(+), 21 deletions(-) delete mode 100644 contrib/scaffolder/custom-action-packages.md diff --git a/contrib/scaffolder/custom-action-packages.md b/contrib/scaffolder/custom-action-packages.md deleted file mode 100644 index d3b634bff5..0000000000 --- a/contrib/scaffolder/custom-action-packages.md +++ /dev/null @@ -1,20 +0,0 @@ -# List of custom action packages - -Here is a list of Open Source custom actions that you can add to your Backstage scaffolder backend! - -| Name | Package | Owner | -| ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------ | -| HTTP requests | [scaffolder-backend-module-http-request](https://www.npmjs.com/package/@roadiehq/scaffolder-backend-module-http-request) | [Roadie](https://roadie.io) | -| Utility actions | [scaffolder-backend-module-utils](https://www.npmjs.com/package/@roadiehq/scaffolder-backend-module-utils) | [Roadie](https://roadie.io) | -| AWS cli actions | [scaffolder-backend-module-aws](https://www.npmjs.com/package/@roadiehq/scaffolder-backend-module-aws) | [Roadie](https://roadie.io) | -| Scaffolder .NET Actions | [plugin-scaffolder-dotnet-backend](https://www.npmjs.com/package/@plusultra/plugin-scaffolder-dotnet-backend) | [Alef Carlos](https://github.com/alefcarlos) | -| Scaffolder Git Actions | [plugin-scaffolder-git-actions](https://www.npmjs.com/package/@mdude2314/backstage-plugin-scaffolder-git-actions) | [Drew Hill](https://github.com/arhill05) | -| Azure Pipeline Actions | [scaffolder-backend-module-azure-pipelines](https://www.npmjs.com/package/@parfuemerie-douglas/scaffolder-backend-module-azure-pipelines) | [Parfümerie Douglas](https://github.com/Parfuemerie-Douglas) | -| Azure Repository Actions | [scaffolder-backend-module-azure-repositories](https://www.npmjs.com/package/@parfuemerie-douglas/scaffolder-backend-module-azure-repositories) | [Parfümerie Douglas](https://github.com/Parfuemerie-Douglas) | -| Snyk Import Project | [plugin-scaffolder-backend-module-snyk](https://www.npmjs.com/package/@ma11hewthomas/plugin-scaffolder-backend-module-snyk) | [Matthew Thomas](https://github.com/Ma11hewThomas) | -| JSON Merge Actions | [plugin-scaffolder-json-merge-actions](https://www.npmjs.com/package/@mdude2314/backstage-plugin-scaffolder-json-merge-actions) | [Drew Hill](https://github.com/arhill05) | -| NPM Actions | [plugin-scaffolder-npm-actions](https://www.npmjs.com/package/@mdude2314/backstage-plugin-scaffolder-npm-actions) | [Drew Hill](https://github.com/arhill05) | -| Slack Actions | [plugin-scaffolder-backend-module-slack](https://www.npmjs.com/package/@mdude2314/backstage-plugin-scaffolder-backend-module-slack) | [Drew Hill](https://github.com/arhill05) | -| Microsoft Teams Actions | [plugin-scaffolder-backend-module-ms-teams](https://www.npmjs.com/package/@grvpandey11/backstage-plugin-scaffolder-backend-module-ms-teams) | [Gaurav Pandey](https://github.com/grvpandey11) | - -You may also want to check out the [Community Plugins Repo](https://github.com/backstage/community-plugins) for more! diff --git a/docs/features/software-templates/writing-custom-actions.md b/docs/features/software-templates/writing-custom-actions.md index 8987f9af40..163df094a1 100644 --- a/docs/features/software-templates/writing-custom-actions.md +++ b/docs/features/software-templates/writing-custom-actions.md @@ -302,4 +302,7 @@ By changing the key, you'll invalidate the cache of the checkpoint. ## Contributed Community Actions -You can find a list of community-contributed actions here in our [contrib](https://github.com/backstage/backstage/blob/master/contrib/scaffolder/custom-action-packages.md) docs! +You can find a list of community-contributed and open-source actions by: + +- Going to the [Backstage Plugin Directory](https://backstage.io/plugins/) and filter by `scaffolder`! +- Checking out the [Community Plugins Repo](https://github.com/backstage/community-plugins)! From a182209fadb38b8b8cf3cd874e36d4160dd80d6e Mon Sep 17 00:00:00 2001 From: Peter Macdonald Date: Tue, 15 Apr 2025 14:51:06 +0200 Subject: [PATCH 024/109] docs: not the the, but the Signed-off-by: Peter Macdonald --- docs/features/software-templates/input-examples.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/features/software-templates/input-examples.md b/docs/features/software-templates/input-examples.md index 84cfec28ed..f278351255 100644 --- a/docs/features/software-templates/input-examples.md +++ b/docs/features/software-templates/input-examples.md @@ -80,7 +80,7 @@ parameters: ### Array with custom titles -In the example below the user will see the the `enumNames` instead of the `enum` values, making it easier to read. +In the example below the user will see the `enumNames` instead of the `enum` values, making it easier to read. ```yaml parameters: From 2d0afb02bc82cb150379bdc159630560ca320443 Mon Sep 17 00:00:00 2001 From: Lee Chiang Fong Date: Thu, 9 Jan 2025 15:28:48 +0800 Subject: [PATCH 025/109] Make mock stsClient fail when region is missing Signed-off-by: Lee Chiang Fong --- .../src/DefaultAwsCredentialsManager.test.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/packages/integration-aws-node/src/DefaultAwsCredentialsManager.test.ts b/packages/integration-aws-node/src/DefaultAwsCredentialsManager.test.ts index d81f717a58..a4e13916ff 100644 --- a/packages/integration-aws-node/src/DefaultAwsCredentialsManager.test.ts +++ b/packages/integration-aws-node/src/DefaultAwsCredentialsManager.test.ts @@ -91,6 +91,18 @@ describe('DefaultAwsCredentialsManager', () => { Account: '123456789012', }); + stsMock + .on(GetCallerIdentityCommand) + .callsFake(async (_input, getClient) => { + const client = getClient(); + const region = await client.config.region(); + if (!region) { + throw new Error('Region is missing'); + } + return { + Account: '123456789012', + }; + }); stsMock .on(AssumeRoleCommand, { RoleArn: 'arn:aws:iam::111111111111:role/hello', From 97907dcefe6b3a9599a61e5900b000456b49e57b Mon Sep 17 00:00:00 2001 From: Lee Chiang Fong Date: Thu, 9 Jan 2025 15:34:46 +0800 Subject: [PATCH 026/109] Use a default region for mainAccount's STS region Signed-off-by: Lee Chiang Fong --- .../integration-aws-node/src/DefaultAwsCredentialsManager.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/integration-aws-node/src/DefaultAwsCredentialsManager.ts b/packages/integration-aws-node/src/DefaultAwsCredentialsManager.ts index 3048fdfe40..36c753767c 100644 --- a/packages/integration-aws-node/src/DefaultAwsCredentialsManager.ts +++ b/packages/integration-aws-node/src/DefaultAwsCredentialsManager.ts @@ -37,6 +37,7 @@ import { Config } from '@backstage/config'; /** * Retrieves the account ID for the given credential provider from STS. + * Include the region if present, otherwise use the default region. */ async function fillInAccountId(credProvider: AwsCredentialProvider) { if (credProvider.accountId) { @@ -44,7 +45,7 @@ async function fillInAccountId(credProvider: AwsCredentialProvider) { } const client = new STSClient({ - region: credProvider.stsRegion, + region: credProvider.stsRegion ?? 'us-east-1', customUserAgent: 'backstage-aws-credentials-manager', credentialDefaultProvider: () => credProvider.sdkCredentialProvider, }); From 01dec025fed80bcd1900ab61ad8c5c98533cdf3d Mon Sep 17 00:00:00 2001 From: Lee Chiang Fong Date: Thu, 9 Jan 2025 15:43:57 +0800 Subject: [PATCH 027/109] Use mainAccount's region as stsRegion Signed-off-by: Lee Chiang Fong --- .../src/DefaultAwsCredentialsManager.test.ts | 16 ++++++++++++++++ .../src/DefaultAwsCredentialsManager.ts | 1 + 2 files changed, 17 insertions(+) diff --git a/packages/integration-aws-node/src/DefaultAwsCredentialsManager.test.ts b/packages/integration-aws-node/src/DefaultAwsCredentialsManager.test.ts index a4e13916ff..582b003724 100644 --- a/packages/integration-aws-node/src/DefaultAwsCredentialsManager.test.ts +++ b/packages/integration-aws-node/src/DefaultAwsCredentialsManager.test.ts @@ -500,5 +500,21 @@ describe('DefaultAwsCredentialsManager', () => { }, }); }); + + it('passes mainAccount region to fillInAccountId for account ID lookup during fallback', async () => { + const region = 'us-west-2'; + const configWithRegion = new ConfigReader({ + aws: { + mainAccount: { + region, + }, + }, + }); + const provider = + DefaultAwsCredentialsManager.fromConfig(configWithRegion); + await provider.getCredentialProvider({ accountId: '123456789012' }); + + expect(await stsMock.call(0).thisValue.config.region()).toEqual(region); + }); }); }); diff --git a/packages/integration-aws-node/src/DefaultAwsCredentialsManager.ts b/packages/integration-aws-node/src/DefaultAwsCredentialsManager.ts index 36c753767c..b54a4fda26 100644 --- a/packages/integration-aws-node/src/DefaultAwsCredentialsManager.ts +++ b/packages/integration-aws-node/src/DefaultAwsCredentialsManager.ts @@ -175,6 +175,7 @@ export class DefaultAwsCredentialsManager implements AwsCredentialsManager { awsConfig.mainAccount, ); const mainAccountCredProvider: AwsCredentialProvider = { + stsRegion: awsConfig.mainAccount.region, sdkCredentialProvider: mainAccountSdkCredProvider, }; From db4630ec9fb2189515eae573039d9013d587a52d Mon Sep 17 00:00:00 2001 From: Lee Chiang Fong Date: Thu, 9 Jan 2025 15:51:54 +0800 Subject: [PATCH 028/109] Add changeset Signed-off-by: Lee Chiang Fong --- .changeset/slow-drinks-enjoy.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/slow-drinks-enjoy.md diff --git a/.changeset/slow-drinks-enjoy.md b/.changeset/slow-drinks-enjoy.md new file mode 100644 index 0000000000..5a171b8fd1 --- /dev/null +++ b/.changeset/slow-drinks-enjoy.md @@ -0,0 +1,5 @@ +--- +'@backstage/integration-aws-node': patch +--- + +Fixed bug in DefaultAwsCredentialsManager where aws.mainAccount.region has no effect on the STS region used for account ID lookup during credential provider lookup when falling back to the main account, and it does not default to us-east-1 From 4f107689736fad6ee6e1141baa3a054e400886f2 Mon Sep 17 00:00:00 2001 From: Brian Phillips <28457+brianphillips@users.noreply.github.com> Date: Fri, 25 Apr 2025 13:16:51 -0500 Subject: [PATCH 029/109] fix slack notification processor attachment blocks A `section` block requires that either a `text` or `fields` item be included. Since we were already defaulting the payload.description, this change simply ensures that the text portion of the block is specified (with tests updated accordingly). Signed-off-by: Brian Phillips <28457+brianphillips@users.noreply.github.com> --- .changeset/crazy-chefs-sin.md | 5 +++++ .../src/lib/SlackNotificationProcessor.test.ts | 8 ++++++++ .../notifications-backend-module-slack/src/lib/util.ts | 10 ++++------ 3 files changed, 17 insertions(+), 6 deletions(-) create mode 100644 .changeset/crazy-chefs-sin.md diff --git a/.changeset/crazy-chefs-sin.md b/.changeset/crazy-chefs-sin.md new file mode 100644 index 0000000000..8d33add929 --- /dev/null +++ b/.changeset/crazy-chefs-sin.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-notifications-backend-module-slack': patch +--- + +Fix slack notification processor to handle a notification with an empty description diff --git a/plugins/notifications-backend-module-slack/src/lib/SlackNotificationProcessor.test.ts b/plugins/notifications-backend-module-slack/src/lib/SlackNotificationProcessor.test.ts index f5893ff456..3229c681c8 100644 --- a/plugins/notifications-backend-module-slack/src/lib/SlackNotificationProcessor.test.ts +++ b/plugins/notifications-backend-module-slack/src/lib/SlackNotificationProcessor.test.ts @@ -158,6 +158,10 @@ describe('SlackNotificationProcessor', () => { blocks: [ { type: 'section', + text: { + text: 'No description provided', + type: 'mrkdwn', + }, accessory: { type: 'button', text: { @@ -229,6 +233,10 @@ describe('SlackNotificationProcessor', () => { blocks: [ { type: 'section', + text: { + text: 'No description provided', + type: 'mrkdwn', + }, accessory: { type: 'button', text: { diff --git a/plugins/notifications-backend-module-slack/src/lib/util.ts b/plugins/notifications-backend-module-slack/src/lib/util.ts index 5e2adbeec5..2d3a03bd2c 100644 --- a/plugins/notifications-backend-module-slack/src/lib/util.ts +++ b/plugins/notifications-backend-module-slack/src/lib/util.ts @@ -46,12 +46,10 @@ export function toSlackBlockKit(payload: NotificationPayload): KnownBlock[] { return [ { type: 'section', - ...(description && { - text: { - type: 'mrkdwn', - text: description ?? 'No description provided', - }, - }), + text: { + type: 'mrkdwn', + text: description ?? 'No description provided', + }, accessory: { type: 'button', text: { From 5081531ca4446d023dd2de30265ef645988eea95 Mon Sep 17 00:00:00 2001 From: Andy Ladjadj Date: Mon, 28 Apr 2025 10:52:02 +0200 Subject: [PATCH 030/109] docs: update .changeset/ten-spies-explode.md Co-authored-by: Vincenzo Scamporlino Signed-off-by: Andy Ladjadj --- .changeset/ten-spies-explode.md | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/.changeset/ten-spies-explode.md b/.changeset/ten-spies-explode.md index efbddef1be..53a37fbd74 100644 --- a/.changeset/ten-spies-explode.md +++ b/.changeset/ten-spies-explode.md @@ -2,13 +2,6 @@ '@backstage/integration': minor --- -Implement Edit URL feature for Gerrit 3.9+. Enabled by default due to 3.8 EOL +Implement Edit URL feature for Gerrit 3.9+. -Details: - -- The Edit URL feature allows for direct editing of files in Gerrit through a specific URL pattern. -- URL pattern: `^\/admin\/repos\/edit\/repo\/(.+)\/branch\/(.+)\/file\/(.+)$` - -Caution: - -- To turn off this functionality, you can add the configuration `disableEditUrl: true` in the Gerrit integration section of your settings +It's possible to disable the edit url by adding the `disableEditUrl: true` config in the Gerrit integration. From 5727e7e0a65149d2df89f516c37b430d9ee30a47 Mon Sep 17 00:00:00 2001 From: Andy Ladjadj Date: Mon, 28 Apr 2025 10:52:16 +0200 Subject: [PATCH 031/109] docs: update docs/integrations/gerrit/locations.md Co-authored-by: Vincenzo Scamporlino Signed-off-by: Andy Ladjadj --- docs/integrations/gerrit/locations.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/integrations/gerrit/locations.md b/docs/integrations/gerrit/locations.md index 16827dbf09..ffe24c8201 100644 --- a/docs/integrations/gerrit/locations.md +++ b/docs/integrations/gerrit/locations.md @@ -40,7 +40,7 @@ a structure with up to six elements: address here. This is the address that you would open in a browser. - `cloneUrl` (optional): The base URL for HTTP clones. Will default to `baseUrl` if not set. The address used to clone a repo is the `cloneUrl` plus the repo name. -- `disableEditUrl` (optional): Disable the edit mode for Gerrit < 3.9 +- `disableEditUrl` (optional): Disable the edit mode. - `username` (optional): The Gerrit username to use in API requests. If neither a username nor password are supplied, anonymous access will be used. - `password` (optional): The password or http token for the Gerrit user. From 221474c980797f4f806c8a070759a723aad32688 Mon Sep 17 00:00:00 2001 From: Andy Ladjadj Date: Mon, 28 Apr 2025 10:52:26 +0200 Subject: [PATCH 032/109] docs: update packages/integration/config.d.ts Co-authored-by: Vincenzo Scamporlino Signed-off-by: Andy Ladjadj --- packages/integration/config.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/integration/config.d.ts b/packages/integration/config.d.ts index beca642594..429645d11b 100644 --- a/packages/integration/config.d.ts +++ b/packages/integration/config.d.ts @@ -182,7 +182,7 @@ export interface Config { */ cloneUrl?: string; /** - * Disable the edit url feature for Gerrit version less than 3.9. + * Disable the edit url feature. * @visibility frontend */ disableEditUrl?: boolean; From b7cba28da6ba6d83d84a6fdfdf53e79295e90d66 Mon Sep 17 00:00:00 2001 From: Andy LADJADJ Date: Mon, 28 Apr 2025 15:29:12 +0200 Subject: [PATCH 033/109] docs: update locations.md Signed-off-by: Andy LADJADJ --- docs/integrations/gerrit/locations.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/integrations/gerrit/locations.md b/docs/integrations/gerrit/locations.md index ffe24c8201..b69aee9016 100644 --- a/docs/integrations/gerrit/locations.md +++ b/docs/integrations/gerrit/locations.md @@ -11,7 +11,7 @@ or registered with the [catalog-import](https://github.com/backstage/backstage/tree/master/plugins/catalog-import) plugin. -Gerrit 3.9+ supports inline editing via URL. the integration enables this by default, following Gerrit's [official URL pattern](https://gerrit-review.googlesource.com/Documentation/user-inline-edit.html#create_from_url) for inline edits. +Gerrit 3.9+ supports inline editing via URL. The integration enables this by default, following Gerrit's [official URL pattern](https://gerrit-review.googlesource.com/Documentation/user-inline-edit.html#create_from_url) for inline edits. ## Configuration From 970cb48e07d994b900e618e295fb1bd68d520511 Mon Sep 17 00:00:00 2001 From: Andreas Berger Date: Mon, 13 Jan 2025 09:16:43 +0100 Subject: [PATCH 034/109] Harmonize `CatalogTable` - Show pagination text for `OffsetPagination` - Use same `OffsetPaginatedCatalogTable` also as fallback if no pagination is set - Do not show paging if there is only one page Signed-off-by: Andreas Berger --- .changeset/funny-papayas-tell.md | 9 ++++ .../components/CatalogTable/CatalogTable.tsx | 47 +++++++------------ .../CursorPaginatedCatalogTable.tsx | 1 - .../OffsetPaginatedCatalogTable.tsx | 9 ++-- 4 files changed, 30 insertions(+), 36 deletions(-) create mode 100644 .changeset/funny-papayas-tell.md diff --git a/.changeset/funny-papayas-tell.md b/.changeset/funny-papayas-tell.md new file mode 100644 index 0000000000..d3c58e95c3 --- /dev/null +++ b/.changeset/funny-papayas-tell.md @@ -0,0 +1,9 @@ +--- +'@backstage/plugin-catalog': minor +--- + +Harmonize `CatalogTable` + +- Show pagination text for `OffsetPagination` +- Use same `OffsetPaginatedCatalogTable` also as fallback if no pagination is set +- Do not show paging if there is only one page diff --git a/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx b/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx index 9b3a950ffd..2076750da8 100644 --- a/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx +++ b/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx @@ -23,7 +23,6 @@ import { } from '@backstage/catalog-model'; import { CodeSnippet, - Table, TableColumn, TableProps, WarningPanel, @@ -47,7 +46,7 @@ import { OffsetPaginatedCatalogTable } from './OffsetPaginatedCatalogTable'; import { CursorPaginatedCatalogTable } from './CursorPaginatedCatalogTable'; import { defaultCatalogTableColumnsFunc } from './defaultCatalogTableColumnsFunc'; import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; -import { catalogTranslationRef } from '../../alpha/translation'; +import { catalogTranslationRef } from '../../alpha'; import { FavoriteToggleIcon } from '@backstage/core-components'; /** @@ -194,7 +193,8 @@ export const CatalogTable = (props: CatalogTableProps) => { .join(' '); const actions = props.actions || defaultActions; - const options = { + const options: TableProps['options'] = { + paginationPosition: 'both', actionsColumnIndex: -1, loadingType: 'linear' as const, showEmptyDataSourceMessage: !loading, @@ -202,6 +202,12 @@ export const CatalogTable = (props: CatalogTableProps) => { ...tableOptions, }; + if (paginationMode !== 'cursor' && paginationMode !== 'offset') { + entities.sort(refCompare); + } + + const rows = entities.map(toEntityRow); + if (paginationMode === 'cursor') { return ( { actions={actions} subtitle={subtitle} options={options} - data={entities.map(toEntityRow)} + data={rows} next={pageInfo?.next} prev={pageInfo?.prev} /> ); - } else if (paginationMode === 'offset') { - return ( - - ); } - const rows = entities.sort(refCompare).map(toEntityRow); - const pageSize = 20; - const showPagination = rows.length > pageSize; - + // else use offset paging return ( - - isLoading={loading} + ); }; diff --git a/plugins/catalog/src/components/CatalogTable/CursorPaginatedCatalogTable.tsx b/plugins/catalog/src/components/CatalogTable/CursorPaginatedCatalogTable.tsx index e49ecf3cd9..1fcc2683be 100644 --- a/plugins/catalog/src/components/CatalogTable/CursorPaginatedCatalogTable.tsx +++ b/plugins/catalog/src/components/CatalogTable/CursorPaginatedCatalogTable.tsx @@ -35,7 +35,6 @@ export function CursorPaginatedCatalogTable(props: PaginatedCatalogTableProps) { columns={columns} data={data} options={{ - paginationPosition: 'both', ...options, // These settings are configured to force server side pagination pageSizeOptions: [], diff --git a/plugins/catalog/src/components/CatalogTable/OffsetPaginatedCatalogTable.tsx b/plugins/catalog/src/components/CatalogTable/OffsetPaginatedCatalogTable.tsx index 96c81088ca..7f599cad76 100644 --- a/plugins/catalog/src/components/CatalogTable/OffsetPaginatedCatalogTable.tsx +++ b/plugins/catalog/src/components/CatalogTable/OffsetPaginatedCatalogTable.tsx @@ -36,21 +36,23 @@ export function OffsetPaginatedCatalogTable( useEffect(() => { if (totalItems && page * limit >= totalItems) { - setOffset!(Math.max(0, totalItems - limit)); + setOffset?.(Math.max(0, totalItems - limit)); } else { - setOffset!(Math.max(0, page * limit)); + setOffset?.(Math.max(0, page * limit)); } }, [setOffset, page, limit, totalItems]); + const showPagination = (totalItems ?? data.length) > limit; + return ( ); From 181f2d108d861e50c5d2dde7d48ff49e5aafe686 Mon Sep 17 00:00:00 2001 From: Andreas Berger Date: Fri, 17 Jan 2025 15:53:08 +0100 Subject: [PATCH 035/109] distinguish between client and server side paging Signed-off-by: Andreas Berger --- .../components/CatalogTable/CatalogTable.tsx | 3 ++- .../OffsetPaginatedCatalogTable.tsx | 24 ++++++++++--------- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx b/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx index 2076750da8..b20ac84889 100644 --- a/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx +++ b/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx @@ -23,6 +23,7 @@ import { } from '@backstage/catalog-model'; import { CodeSnippet, + FavoriteToggleIcon, TableColumn, TableProps, WarningPanel, @@ -47,7 +48,6 @@ import { CursorPaginatedCatalogTable } from './CursorPaginatedCatalogTable'; import { defaultCatalogTableColumnsFunc } from './defaultCatalogTableColumnsFunc'; import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; import { catalogTranslationRef } from '../../alpha'; -import { FavoriteToggleIcon } from '@backstage/core-components'; /** * Props for {@link CatalogTable}. @@ -236,6 +236,7 @@ export const CatalogTable = (props: CatalogTableProps) => { subtitle={subtitle} options={options} data={rows} + clientPagination={paginationMode === 'none'} /> ); }; diff --git a/plugins/catalog/src/components/CatalogTable/OffsetPaginatedCatalogTable.tsx b/plugins/catalog/src/components/CatalogTable/OffsetPaginatedCatalogTable.tsx index 7f599cad76..b88558c822 100644 --- a/plugins/catalog/src/components/CatalogTable/OffsetPaginatedCatalogTable.tsx +++ b/plugins/catalog/src/components/CatalogTable/OffsetPaginatedCatalogTable.tsx @@ -25,9 +25,12 @@ import { CatalogTableToolbar } from './CatalogTableToolbar'; * @internal */ export function OffsetPaginatedCatalogTable( - props: TableProps, + props: TableProps & { + // If true, the pagination will be handled client side, the table will use all rows provided in the data prop + clientPagination?: boolean; + }, ) { - const { columns, data, options, ...restProps } = props; + const { columns, data, options, clientPagination, ...restProps } = props; const { setLimit, setOffset, limit, totalItems, offset } = useEntityList(); const [page, setPage] = useState( @@ -42,7 +45,8 @@ export function OffsetPaginatedCatalogTable( } }, [setOffset, page, limit, totalItems]); - const showPagination = (totalItems ?? data.length) > limit; + const showPagination = + (clientPagination ? data.length : totalItems ?? data.length) > limit; return (
{ - setPage(newPage); - }} - onRowsPerPageChange={pageSize => { - setLimit(pageSize); - }} - totalCount={totalItems} + page={clientPagination ? undefined : page} + onPageChange={clientPagination ? undefined : newPage => setPage(newPage)} + onRowsPerPageChange={ + clientPagination ? undefined : pageSize => setLimit(pageSize) + } + totalCount={clientPagination ? undefined : totalItems} {...restProps} /> ); From 07d9f518f5f85c8873132553b2b3130505c9d83a Mon Sep 17 00:00:00 2001 From: Andreas Berger Date: Fri, 17 Jan 2025 16:05:15 +0100 Subject: [PATCH 036/109] use paging mode from context Signed-off-by: Andreas Berger --- .../components/CatalogTable/CatalogTable.tsx | 1 - .../OffsetPaginatedCatalogTable.tsx | 33 ++++++++++--------- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx b/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx index b20ac84889..02f1603114 100644 --- a/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx +++ b/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx @@ -236,7 +236,6 @@ export const CatalogTable = (props: CatalogTableProps) => { subtitle={subtitle} options={options} data={rows} - clientPagination={paginationMode === 'none'} /> ); }; diff --git a/plugins/catalog/src/components/CatalogTable/OffsetPaginatedCatalogTable.tsx b/plugins/catalog/src/components/CatalogTable/OffsetPaginatedCatalogTable.tsx index b88558c822..d27754ee29 100644 --- a/plugins/catalog/src/components/CatalogTable/OffsetPaginatedCatalogTable.tsx +++ b/plugins/catalog/src/components/CatalogTable/OffsetPaginatedCatalogTable.tsx @@ -25,28 +25,29 @@ import { CatalogTableToolbar } from './CatalogTableToolbar'; * @internal */ export function OffsetPaginatedCatalogTable( - props: TableProps & { - // If true, the pagination will be handled client side, the table will use all rows provided in the data prop - clientPagination?: boolean; - }, + props: TableProps, ) { - const { columns, data, options, clientPagination, ...restProps } = props; - const { setLimit, setOffset, limit, totalItems, offset } = useEntityList(); + const { columns, data, options, ...restProps } = props; + const { setLimit, setOffset, limit, totalItems, offset, paginationMode } = + useEntityList(); + const clientPagination = paginationMode === 'none'; const [page, setPage] = useState( offset && limit ? Math.floor(offset / limit) : 0, ); useEffect(() => { + if (clientPagination) { + return; + } if (totalItems && page * limit >= totalItems) { setOffset?.(Math.max(0, totalItems - limit)); } else { setOffset?.(Math.max(0, page * limit)); } - }, [setOffset, page, limit, totalItems]); + }, [setOffset, page, limit, totalItems, clientPagination]); - const showPagination = - (clientPagination ? data.length : totalItems ?? data.length) > limit; + const showPagination = (totalItems ?? data.length) > limit; return (
setPage(newPage)} - onRowsPerPageChange={ - clientPagination ? undefined : pageSize => setLimit(pageSize) - } - totalCount={clientPagination ? undefined : totalItems} + {...(clientPagination + ? {} + : { + page, + onPageChange: newPage => setPage(newPage), + onRowsPerPageChange: pageSize => setLimit(pageSize), + totalCount: totalItems, + })} {...restProps} /> ); From 8c1f0e92e24e9e6b6082302124286d6287d21964 Mon Sep 17 00:00:00 2001 From: Andreas Berger Date: Fri, 17 Jan 2025 16:08:41 +0100 Subject: [PATCH 037/109] clean up code Signed-off-by: Andreas Berger --- .../components/CatalogTable/OffsetPaginatedCatalogTable.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/catalog/src/components/CatalogTable/OffsetPaginatedCatalogTable.tsx b/plugins/catalog/src/components/CatalogTable/OffsetPaginatedCatalogTable.tsx index d27754ee29..0bec07722e 100644 --- a/plugins/catalog/src/components/CatalogTable/OffsetPaginatedCatalogTable.tsx +++ b/plugins/catalog/src/components/CatalogTable/OffsetPaginatedCatalogTable.tsx @@ -67,8 +67,8 @@ export function OffsetPaginatedCatalogTable( ? {} : { page, - onPageChange: newPage => setPage(newPage), - onRowsPerPageChange: pageSize => setLimit(pageSize), + onPageChange: setPage, + onRowsPerPageChange: setLimit, totalCount: totalItems, })} {...restProps} From 572fa299959888259d63f80f77cc3990b04b9960 Mon Sep 17 00:00:00 2001 From: Andreas Berger Date: Fri, 17 Jan 2025 16:11:41 +0100 Subject: [PATCH 038/109] fix tests Signed-off-by: Andreas Berger --- .../CatalogTable/OffsetPaginatedCatalogTable.test.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/plugins/catalog/src/components/CatalogTable/OffsetPaginatedCatalogTable.test.tsx b/plugins/catalog/src/components/CatalogTable/OffsetPaginatedCatalogTable.test.tsx index 54c2550451..600e178c18 100644 --- a/plugins/catalog/src/components/CatalogTable/OffsetPaginatedCatalogTable.test.tsx +++ b/plugins/catalog/src/components/CatalogTable/OffsetPaginatedCatalogTable.test.tsx @@ -108,7 +108,13 @@ describe('OffsetPaginatedCatalogTable', () => { await renderInTestApp( wrapInContext( , - { setOffset: offsetFn, limit: 10, totalItems: data.length, offset: 0 }, + { + setOffset: offsetFn, + limit: 10, + totalItems: data.length, + offset: 0, + paginationMode: 'offset', + }, ), ); From cf02a9ce06fb654c5fdae6f0a50f133885d13494 Mon Sep 17 00:00:00 2001 From: Andreas Berger Date: Fri, 17 Jan 2025 16:21:44 +0100 Subject: [PATCH 039/109] optimize code Signed-off-by: Andreas Berger --- .../CatalogTable/OffsetPaginatedCatalogTable.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/plugins/catalog/src/components/CatalogTable/OffsetPaginatedCatalogTable.tsx b/plugins/catalog/src/components/CatalogTable/OffsetPaginatedCatalogTable.tsx index 0bec07722e..2f7e5a168e 100644 --- a/plugins/catalog/src/components/CatalogTable/OffsetPaginatedCatalogTable.tsx +++ b/plugins/catalog/src/components/CatalogTable/OffsetPaginatedCatalogTable.tsx @@ -37,14 +37,14 @@ export function OffsetPaginatedCatalogTable( ); useEffect(() => { - if (clientPagination) { + if (clientPagination || !setOffset) { return; } - if (totalItems && page * limit >= totalItems) { - setOffset?.(Math.max(0, totalItems - limit)); - } else { - setOffset?.(Math.max(0, page * limit)); + let newOffset = page * limit; + if (totalItems && newOffset >= totalItems) { + newOffset = totalItems - limit; } + setOffset(Math.max(0, newOffset)); }, [setOffset, page, limit, totalItems, clientPagination]); const showPagination = (totalItems ?? data.length) > limit; From 7e07b110488a390ae1a6277236b7b14e324f7c87 Mon Sep 17 00:00:00 2001 From: Andreas Berger Date: Mon, 20 Jan 2025 08:42:54 +0100 Subject: [PATCH 040/109] do always show paging bar, so the user can change the limit even if there is only one page available Signed-off-by: Andreas Berger --- .../components/CatalogTable/OffsetPaginatedCatalogTable.tsx | 3 --- 1 file changed, 3 deletions(-) diff --git a/plugins/catalog/src/components/CatalogTable/OffsetPaginatedCatalogTable.tsx b/plugins/catalog/src/components/CatalogTable/OffsetPaginatedCatalogTable.tsx index 2f7e5a168e..e2954e30cb 100644 --- a/plugins/catalog/src/components/CatalogTable/OffsetPaginatedCatalogTable.tsx +++ b/plugins/catalog/src/components/CatalogTable/OffsetPaginatedCatalogTable.tsx @@ -47,8 +47,6 @@ export function OffsetPaginatedCatalogTable( setOffset(Math.max(0, newOffset)); }, [setOffset, page, limit, totalItems, clientPagination]); - const showPagination = (totalItems ?? data.length) > limit; - return (
Date: Mon, 17 Feb 2025 16:51:21 +0100 Subject: [PATCH 041/109] Revert "fix api-reports (why ever they changed?)" This reverts commit 6a3dc95fe5948abfc893cc57c7250e6a53d686ad. Signed-off-by: Andreas Berger --- plugins/app-visualizer/report.api.md | 42 ++++++------- plugins/catalog-import/report-alpha.api.md | 30 ++++----- plugins/devtools/report-alpha.api.md | 72 +++++++++++----------- plugins/search/report-alpha.api.md | 30 ++++----- 4 files changed, 87 insertions(+), 87 deletions(-) diff --git a/plugins/app-visualizer/report.api.md b/plugins/app-visualizer/report.api.md index 370647afc9..6e31446e63 100644 --- a/plugins/app-visualizer/report.api.md +++ b/plugins/app-visualizer/report.api.md @@ -16,27 +16,6 @@ const visualizerPlugin: FrontendPlugin< {}, {}, { - 'nav-item:app-visualizer': ExtensionDefinition<{ - kind: 'nav-item'; - name: undefined; - config: {}; - configInput: {}; - output: ConfigurableExtensionDataRef< - { - title: string; - icon: IconComponent; - routeRef: RouteRef; - }, - 'core.nav-item.target', - {} - >; - inputs: {}; - params: { - title: string; - icon: IconComponent; - routeRef: RouteRef; - }; - }>; 'page:app-visualizer': ExtensionDefinition<{ kind: 'page'; name: undefined; @@ -63,6 +42,27 @@ const visualizerPlugin: FrontendPlugin< routeRef?: RouteRef; }; }>; + 'nav-item:app-visualizer': ExtensionDefinition<{ + kind: 'nav-item'; + name: undefined; + config: {}; + configInput: {}; + output: ConfigurableExtensionDataRef< + { + title: string; + icon: IconComponent; + routeRef: RouteRef; + }, + 'core.nav-item.target', + {} + >; + inputs: {}; + params: { + title: string; + icon: IconComponent; + routeRef: RouteRef; + }; + }>; } >; export default visualizerPlugin; diff --git a/plugins/catalog-import/report-alpha.api.md b/plugins/catalog-import/report-alpha.api.md index 24789a42e4..0f20fe1480 100644 --- a/plugins/catalog-import/report-alpha.api.md +++ b/plugins/catalog-import/report-alpha.api.md @@ -28,21 +28,6 @@ const _default: FrontendPlugin< }, {}, { - 'api:catalog-import': ExtensionDefinition<{ - kind: 'api'; - name: undefined; - config: {}; - configInput: {}; - output: ConfigurableExtensionDataRef< - AnyApiFactory, - 'core.api.factory', - {} - >; - inputs: {}; - params: { - factory: AnyApiFactory; - }; - }>; 'page:catalog-import': ExtensionDefinition<{ kind: 'page'; name: undefined; @@ -69,6 +54,21 @@ const _default: FrontendPlugin< routeRef?: RouteRef; }; }>; + 'api:catalog-import': ExtensionDefinition<{ + kind: 'api'; + name: undefined; + config: {}; + configInput: {}; + output: ConfigurableExtensionDataRef< + AnyApiFactory, + 'core.api.factory', + {} + >; + inputs: {}; + params: { + factory: AnyApiFactory; + }; + }>; } >; export default _default; diff --git a/plugins/devtools/report-alpha.api.md b/plugins/devtools/report-alpha.api.md index 663c4be34d..f882d851ca 100644 --- a/plugins/devtools/report-alpha.api.md +++ b/plugins/devtools/report-alpha.api.md @@ -19,42 +19,6 @@ const _default: FrontendPlugin< }, {}, { - 'api:devtools': ExtensionDefinition<{ - kind: 'api'; - name: undefined; - config: {}; - configInput: {}; - output: ConfigurableExtensionDataRef< - AnyApiFactory, - 'core.api.factory', - {} - >; - inputs: {}; - params: { - factory: AnyApiFactory; - }; - }>; - 'nav-item:devtools': ExtensionDefinition<{ - kind: 'nav-item'; - name: undefined; - config: {}; - configInput: {}; - output: ConfigurableExtensionDataRef< - { - title: string; - icon: IconComponent; - routeRef: RouteRef; - }, - 'core.nav-item.target', - {} - >; - inputs: {}; - params: { - title: string; - icon: IconComponent; - routeRef: RouteRef; - }; - }>; 'page:devtools': ExtensionDefinition<{ kind: 'page'; name: undefined; @@ -81,6 +45,42 @@ const _default: FrontendPlugin< routeRef?: RouteRef; }; }>; + 'nav-item:devtools': ExtensionDefinition<{ + kind: 'nav-item'; + name: undefined; + config: {}; + configInput: {}; + output: ConfigurableExtensionDataRef< + { + title: string; + icon: IconComponent; + routeRef: RouteRef; + }, + 'core.nav-item.target', + {} + >; + inputs: {}; + params: { + title: string; + icon: IconComponent; + routeRef: RouteRef; + }; + }>; + 'api:devtools': ExtensionDefinition<{ + kind: 'api'; + name: undefined; + config: {}; + configInput: {}; + output: ConfigurableExtensionDataRef< + AnyApiFactory, + 'core.api.factory', + {} + >; + inputs: {}; + params: { + factory: AnyApiFactory; + }; + }>; } >; export default _default; diff --git a/plugins/search/report-alpha.api.md b/plugins/search/report-alpha.api.md index b8df4d3b4c..f1f87d2377 100644 --- a/plugins/search/report-alpha.api.md +++ b/plugins/search/report-alpha.api.md @@ -23,21 +23,6 @@ const _default: FrontendPlugin< }, {}, { - 'api:search': ExtensionDefinition<{ - kind: 'api'; - name: undefined; - config: {}; - configInput: {}; - output: ConfigurableExtensionDataRef< - AnyApiFactory, - 'core.api.factory', - {} - >; - inputs: {}; - params: { - factory: AnyApiFactory; - }; - }>; 'nav-item:search': ExtensionDefinition<{ kind: 'nav-item'; name: undefined; @@ -59,6 +44,21 @@ const _default: FrontendPlugin< routeRef: RouteRef; }; }>; + 'api:search': ExtensionDefinition<{ + kind: 'api'; + name: undefined; + config: {}; + configInput: {}; + output: ConfigurableExtensionDataRef< + AnyApiFactory, + 'core.api.factory', + {} + >; + inputs: {}; + params: { + factory: AnyApiFactory; + }; + }>; 'page:search': ExtensionDefinition<{ config: { noTrack: boolean; From 759487684714d75752742577512a4bbce30aecb7 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 28 Apr 2025 15:00:15 +0000 Subject: [PATCH 042/109] chore(deps): update actions/download-artifact digest to d3f86a1 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/deploy_microsite.yml | 10 +++++----- .github/workflows/verify_microsite.yml | 8 ++++---- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/deploy_microsite.yml b/.github/workflows/deploy_microsite.yml index ff49b94e56..05c8597304 100644 --- a/.github/workflows/deploy_microsite.yml +++ b/.github/workflows/deploy_microsite.yml @@ -211,13 +211,13 @@ jobs: working-directory: microsite - name: download stable reference - uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e # v4 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: stable-reference path: docs/reference - name: download stable OpenAPI API docs - uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e # v4 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: stable-openapi-docs path: docs @@ -245,13 +245,13 @@ jobs: working-directory: microsite - name: download next reference - uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e # v4 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: next-reference path: docs/reference - name: download next OpenAPI API docs - uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e # v4 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: next-openapi-docs path: docs @@ -260,7 +260,7 @@ jobs: run: yarn build working-directory: microsite - - uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e # v4 + - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: storybook path: microsite/build/storybook diff --git a/.github/workflows/verify_microsite.yml b/.github/workflows/verify_microsite.yml index ce3be0cf42..309deaba47 100644 --- a/.github/workflows/verify_microsite.yml +++ b/.github/workflows/verify_microsite.yml @@ -222,13 +222,13 @@ jobs: run: node scripts/verify-lockfile-duplicates.js - name: download stable reference - uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e # v4 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: stable-reference path: docs/reference - name: download stable OpenAPI API docs - uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e # v4 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: stable-openapi-docs path: docs @@ -257,13 +257,13 @@ jobs: run: mkdocs build --strict - name: download next reference - uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e # v4 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: next-reference path: docs/reference - name: download next OpenAPI API docs - uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e # v4 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: next-openapi-docs path: docs From fa67031ff9bd9dd9be789162ebf70039f65f2c01 Mon Sep 17 00:00:00 2001 From: Andreas Berger Date: Mon, 28 Apr 2025 17:00:39 +0200 Subject: [PATCH 043/109] Revert harmonization of CatalogTables see https://github.com/backstage/backstage/pull/28449#pullrequestreview-2559283912 Signed-off-by: Andreas Berger --- .changeset/funny-papayas-tell.md | 1 - plugins/app-visualizer/report.api.md | 42 +++++------ plugins/catalog-import/report-alpha.api.md | 30 ++++---- .../components/CatalogTable/CatalogTable.tsx | 44 ++++++++---- .../OffsetPaginatedCatalogTable.test.tsx | 8 +-- .../OffsetPaginatedCatalogTable.tsx | 29 +++----- plugins/devtools/report-alpha.api.md | 72 +++++++++---------- plugins/search/report-alpha.api.md | 30 ++++---- 8 files changed, 128 insertions(+), 128 deletions(-) diff --git a/.changeset/funny-papayas-tell.md b/.changeset/funny-papayas-tell.md index d3c58e95c3..8931c7a96f 100644 --- a/.changeset/funny-papayas-tell.md +++ b/.changeset/funny-papayas-tell.md @@ -5,5 +5,4 @@ Harmonize `CatalogTable` - Show pagination text for `OffsetPagination` -- Use same `OffsetPaginatedCatalogTable` also as fallback if no pagination is set - Do not show paging if there is only one page diff --git a/plugins/app-visualizer/report.api.md b/plugins/app-visualizer/report.api.md index 6e31446e63..370647afc9 100644 --- a/plugins/app-visualizer/report.api.md +++ b/plugins/app-visualizer/report.api.md @@ -16,6 +16,27 @@ const visualizerPlugin: FrontendPlugin< {}, {}, { + 'nav-item:app-visualizer': ExtensionDefinition<{ + kind: 'nav-item'; + name: undefined; + config: {}; + configInput: {}; + output: ConfigurableExtensionDataRef< + { + title: string; + icon: IconComponent; + routeRef: RouteRef; + }, + 'core.nav-item.target', + {} + >; + inputs: {}; + params: { + title: string; + icon: IconComponent; + routeRef: RouteRef; + }; + }>; 'page:app-visualizer': ExtensionDefinition<{ kind: 'page'; name: undefined; @@ -42,27 +63,6 @@ const visualizerPlugin: FrontendPlugin< routeRef?: RouteRef; }; }>; - 'nav-item:app-visualizer': ExtensionDefinition<{ - kind: 'nav-item'; - name: undefined; - config: {}; - configInput: {}; - output: ConfigurableExtensionDataRef< - { - title: string; - icon: IconComponent; - routeRef: RouteRef; - }, - 'core.nav-item.target', - {} - >; - inputs: {}; - params: { - title: string; - icon: IconComponent; - routeRef: RouteRef; - }; - }>; } >; export default visualizerPlugin; diff --git a/plugins/catalog-import/report-alpha.api.md b/plugins/catalog-import/report-alpha.api.md index 0f20fe1480..24789a42e4 100644 --- a/plugins/catalog-import/report-alpha.api.md +++ b/plugins/catalog-import/report-alpha.api.md @@ -28,6 +28,21 @@ const _default: FrontendPlugin< }, {}, { + 'api:catalog-import': ExtensionDefinition<{ + kind: 'api'; + name: undefined; + config: {}; + configInput: {}; + output: ConfigurableExtensionDataRef< + AnyApiFactory, + 'core.api.factory', + {} + >; + inputs: {}; + params: { + factory: AnyApiFactory; + }; + }>; 'page:catalog-import': ExtensionDefinition<{ kind: 'page'; name: undefined; @@ -54,21 +69,6 @@ const _default: FrontendPlugin< routeRef?: RouteRef; }; }>; - 'api:catalog-import': ExtensionDefinition<{ - kind: 'api'; - name: undefined; - config: {}; - configInput: {}; - output: ConfigurableExtensionDataRef< - AnyApiFactory, - 'core.api.factory', - {} - >; - inputs: {}; - params: { - factory: AnyApiFactory; - }; - }>; } >; export default _default; diff --git a/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx b/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx index 02f1603114..8cce9ff317 100644 --- a/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx +++ b/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx @@ -23,7 +23,7 @@ import { } from '@backstage/catalog-model'; import { CodeSnippet, - FavoriteToggleIcon, + Table, TableColumn, TableProps, WarningPanel, @@ -48,6 +48,7 @@ import { CursorPaginatedCatalogTable } from './CursorPaginatedCatalogTable'; import { defaultCatalogTableColumnsFunc } from './defaultCatalogTableColumnsFunc'; import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; import { catalogTranslationRef } from '../../alpha'; +import { FavoriteToggleIcon } from '@backstage/core-components'; /** * Props for {@link CatalogTable}. @@ -202,12 +203,6 @@ export const CatalogTable = (props: CatalogTableProps) => { ...tableOptions, }; - if (paginationMode !== 'cursor' && paginationMode !== 'offset') { - entities.sort(refCompare); - } - - const rows = entities.map(toEntityRow); - if (paginationMode === 'cursor') { return ( { actions={actions} subtitle={subtitle} options={options} - data={rows} + data={entities.map(toEntityRow)} next={pageInfo?.next} prev={pageInfo?.prev} /> ); + } else if (paginationMode === 'offset') { + return ( + + ); } - // else use offset paging + const rows = entities.sort(refCompare).map(toEntityRow); + const pageSize = 20; + const showPagination = rows.length > pageSize; + return ( - isLoading={loading} + columns={tableColumns} + options={{ + paging: showPagination, + pageSize: pageSize, + pageSizeOptions: [20, 50, 100], + ...options, + }} title={title} + data={rows} actions={actions} subtitle={subtitle} - options={options} - data={rows} + emptyContent={emptyContent} /> ); }; diff --git a/plugins/catalog/src/components/CatalogTable/OffsetPaginatedCatalogTable.test.tsx b/plugins/catalog/src/components/CatalogTable/OffsetPaginatedCatalogTable.test.tsx index 600e178c18..54c2550451 100644 --- a/plugins/catalog/src/components/CatalogTable/OffsetPaginatedCatalogTable.test.tsx +++ b/plugins/catalog/src/components/CatalogTable/OffsetPaginatedCatalogTable.test.tsx @@ -108,13 +108,7 @@ describe('OffsetPaginatedCatalogTable', () => { await renderInTestApp( wrapInContext( , - { - setOffset: offsetFn, - limit: 10, - totalItems: data.length, - offset: 0, - paginationMode: 'offset', - }, + { setOffset: offsetFn, limit: 10, totalItems: data.length, offset: 0 }, ), ); diff --git a/plugins/catalog/src/components/CatalogTable/OffsetPaginatedCatalogTable.tsx b/plugins/catalog/src/components/CatalogTable/OffsetPaginatedCatalogTable.tsx index e2954e30cb..896cf0f8b6 100644 --- a/plugins/catalog/src/components/CatalogTable/OffsetPaginatedCatalogTable.tsx +++ b/plugins/catalog/src/components/CatalogTable/OffsetPaginatedCatalogTable.tsx @@ -28,24 +28,19 @@ export function OffsetPaginatedCatalogTable( props: TableProps, ) { const { columns, data, options, ...restProps } = props; - const { setLimit, setOffset, limit, totalItems, offset, paginationMode } = - useEntityList(); - const clientPagination = paginationMode === 'none'; + const { setLimit, setOffset, limit, totalItems, offset } = useEntityList(); const [page, setPage] = useState( offset && limit ? Math.floor(offset / limit) : 0, ); useEffect(() => { - if (clientPagination || !setOffset) { - return; + if (totalItems && page * limit >= totalItems) { + setOffset!(Math.max(0, totalItems - limit)); + } else { + setOffset!(Math.max(0, page * limit)); } - let newOffset = page * limit; - if (totalItems && newOffset >= totalItems) { - newOffset = totalItems - limit; - } - setOffset(Math.max(0, newOffset)); - }, [setOffset, page, limit, totalItems, clientPagination]); + }, [setOffset, page, limit, totalItems]); return (
); diff --git a/plugins/devtools/report-alpha.api.md b/plugins/devtools/report-alpha.api.md index f882d851ca..663c4be34d 100644 --- a/plugins/devtools/report-alpha.api.md +++ b/plugins/devtools/report-alpha.api.md @@ -19,6 +19,42 @@ const _default: FrontendPlugin< }, {}, { + 'api:devtools': ExtensionDefinition<{ + kind: 'api'; + name: undefined; + config: {}; + configInput: {}; + output: ConfigurableExtensionDataRef< + AnyApiFactory, + 'core.api.factory', + {} + >; + inputs: {}; + params: { + factory: AnyApiFactory; + }; + }>; + 'nav-item:devtools': ExtensionDefinition<{ + kind: 'nav-item'; + name: undefined; + config: {}; + configInput: {}; + output: ConfigurableExtensionDataRef< + { + title: string; + icon: IconComponent; + routeRef: RouteRef; + }, + 'core.nav-item.target', + {} + >; + inputs: {}; + params: { + title: string; + icon: IconComponent; + routeRef: RouteRef; + }; + }>; 'page:devtools': ExtensionDefinition<{ kind: 'page'; name: undefined; @@ -45,42 +81,6 @@ const _default: FrontendPlugin< routeRef?: RouteRef; }; }>; - 'nav-item:devtools': ExtensionDefinition<{ - kind: 'nav-item'; - name: undefined; - config: {}; - configInput: {}; - output: ConfigurableExtensionDataRef< - { - title: string; - icon: IconComponent; - routeRef: RouteRef; - }, - 'core.nav-item.target', - {} - >; - inputs: {}; - params: { - title: string; - icon: IconComponent; - routeRef: RouteRef; - }; - }>; - 'api:devtools': ExtensionDefinition<{ - kind: 'api'; - name: undefined; - config: {}; - configInput: {}; - output: ConfigurableExtensionDataRef< - AnyApiFactory, - 'core.api.factory', - {} - >; - inputs: {}; - params: { - factory: AnyApiFactory; - }; - }>; } >; export default _default; diff --git a/plugins/search/report-alpha.api.md b/plugins/search/report-alpha.api.md index f1f87d2377..b8df4d3b4c 100644 --- a/plugins/search/report-alpha.api.md +++ b/plugins/search/report-alpha.api.md @@ -23,6 +23,21 @@ const _default: FrontendPlugin< }, {}, { + 'api:search': ExtensionDefinition<{ + kind: 'api'; + name: undefined; + config: {}; + configInput: {}; + output: ConfigurableExtensionDataRef< + AnyApiFactory, + 'core.api.factory', + {} + >; + inputs: {}; + params: { + factory: AnyApiFactory; + }; + }>; 'nav-item:search': ExtensionDefinition<{ kind: 'nav-item'; name: undefined; @@ -44,21 +59,6 @@ const _default: FrontendPlugin< routeRef: RouteRef; }; }>; - 'api:search': ExtensionDefinition<{ - kind: 'api'; - name: undefined; - config: {}; - configInput: {}; - output: ConfigurableExtensionDataRef< - AnyApiFactory, - 'core.api.factory', - {} - >; - inputs: {}; - params: { - factory: AnyApiFactory; - }; - }>; 'page:search': ExtensionDefinition<{ config: { noTrack: boolean; From 499fa53939fa5e34aeec94b9776b74b92e354489 Mon Sep 17 00:00:00 2001 From: aramissennyeydd Date: Sun, 13 Apr 2025 17:44:10 -0400 Subject: [PATCH 044/109] chore: remove old backend system guides Signed-off-by: aramissennyeydd --- docs/auth/auth0/provider--old.md | 75 ---- docs/auth/auth0/provider.md | 2 +- docs/auth/bitbucketServer/provider--old.md | 60 --- docs/auth/bitbucketServer/provider.md | 2 +- docs/auth/identity-resolver--old.md | 379 ---------------- docs/auth/identity-resolver.md | 2 +- docs/auth/oidc--old.md | 289 ------------ docs/auth/oidc.md | 2 +- docs/auth/service-to-service-auth--old.md | 177 -------- docs/auth/service-to-service-auth.md | 4 +- docs/integrations/aws-s3/discovery--old.md | 88 ---- docs/integrations/aws-s3/discovery.md | 2 +- docs/integrations/azure/discovery--old.md | 186 -------- docs/integrations/azure/discovery.md | 2 +- docs/integrations/azure/org--old.md | 273 ------------ docs/integrations/azure/org.md | 2 +- .../bitbucketServer/discovery--old.md | 123 ----- .../integrations/bitbucketServer/discovery.md | 2 +- docs/integrations/gerrit/discovery--old.md | 73 --- docs/integrations/gerrit/discovery.md | 2 +- docs/integrations/github/discovery--old.md | 375 ---------------- docs/integrations/github/discovery.md | 2 +- docs/integrations/github/org--old.md | 365 --------------- docs/integrations/github/org.md | 2 +- docs/permissions/custom-rules--old.md | 165 ------- docs/permissions/custom-rules.md | 2 +- docs/permissions/getting-started--old.md | 169 ------- docs/permissions/getting-started.md | 2 +- .../plugin-authors/01-setup--old.md | 134 ------ docs/permissions/plugin-authors/01-setup.md | 2 +- ...02-adding-a-basic-permission-check--old.md | 387 ---------------- .../02-adding-a-basic-permission-check.md | 2 +- ...adding-a-resource-permission-check--old.md | 299 ------------- .../03-adding-a-resource-permission-check.md | 2 +- ...thorizing-access-to-paginated-data--old.md | 193 -------- ...04-authorizing-access-to-paginated-data.md | 2 +- docs/permissions/writing-a-policy--old.md | 148 ------ docs/permissions/writing-a-policy.md | 2 +- .../integrating-search-into-plugins--old.md | 421 ------------------ .../integrating-search-into-plugins.md | 2 +- 40 files changed, 21 insertions(+), 4400 deletions(-) delete mode 100644 docs/auth/auth0/provider--old.md delete mode 100644 docs/auth/bitbucketServer/provider--old.md delete mode 100644 docs/auth/identity-resolver--old.md delete mode 100644 docs/auth/oidc--old.md delete mode 100644 docs/auth/service-to-service-auth--old.md delete mode 100644 docs/integrations/aws-s3/discovery--old.md delete mode 100644 docs/integrations/azure/discovery--old.md delete mode 100644 docs/integrations/azure/org--old.md delete mode 100644 docs/integrations/bitbucketServer/discovery--old.md delete mode 100644 docs/integrations/gerrit/discovery--old.md delete mode 100644 docs/integrations/github/discovery--old.md delete mode 100644 docs/integrations/github/org--old.md delete mode 100644 docs/permissions/custom-rules--old.md delete mode 100644 docs/permissions/getting-started--old.md delete mode 100644 docs/permissions/plugin-authors/01-setup--old.md delete mode 100644 docs/permissions/plugin-authors/02-adding-a-basic-permission-check--old.md delete mode 100644 docs/permissions/plugin-authors/03-adding-a-resource-permission-check--old.md delete mode 100644 docs/permissions/plugin-authors/04-authorizing-access-to-paginated-data--old.md delete mode 100644 docs/permissions/writing-a-policy--old.md delete mode 100644 docs/plugins/integrating-search-into-plugins--old.md diff --git a/docs/auth/auth0/provider--old.md b/docs/auth/auth0/provider--old.md deleted file mode 100644 index a089236aca..0000000000 --- a/docs/auth/auth0/provider--old.md +++ /dev/null @@ -1,75 +0,0 @@ ---- -id: provider--old -title: Auth0 Authentication Provider -sidebar_label: Auth0 -description: Adding Auth0 as an authentication provider in Backstage ---- - -:::info -This documentation is written for the old backend which has been replaced by -[the new backend system](../../backend-system/index.md), being the default since -Backstage [version 1.24](../../releases/v1.24.0.md). If have migrated to the new -backend system, you may want to read [its own article](./provider.md) -instead. Otherwise, [consider migrating](../../backend-system/building-backends/08-migrating.md)! -::: - -The Backstage `core-plugin-api` package comes with an Auth0 authentication -provider that can authenticate users using OAuth. - -## Create an Auth0 Application - -1. Log in to the [Auth0 dashboard](https://manage.auth0.com/dashboard/) -2. Navigate to **Applications** -3. Create an Application - - Name: Backstage (or your custom app name) - - Application type: Single Page Web Application -4. Click on the Settings tab -5. Add under `Application URIs` > `Allowed Callback URLs`: - `http://localhost:7007/api/auth/auth0/handler/frame` -6. Click `Save Changes` - -## Configuration - -The provider configuration can then be added to your `app-config.yaml` under the -root `auth` configuration: - -```yaml -auth: - environment: development - providers: - auth0: - development: - clientId: ${AUTH_AUTH0_CLIENT_ID} - clientSecret: ${AUTH_AUTH0_CLIENT_SECRET} - domain: ${AUTH_AUTH0_DOMAIN_ID} - audience: ${AUTH_AUTH0_AUDIENCE} - connection: ${AUTH_AUTH0_CONNECTION} - connectionScope: ${AUTH_AUTH0_CONNECTION_SCOPE} - session: - secret: ${AUTH_SESSION_SECRET} -``` - -The Auth0 provider is a structure with these configuration keys: - -- `clientId`: The Application client ID, found on the Auth0 Application page -- `clientSecret`: The Application client secret, found on the Auth0 Application - page -- `domain`: The Application domain, found on the Auth0 Application page - -It additionally relies on the following configuration to function: - -- `session.secret`: The session secret is a key used for signing and/or encrypting cookies set by the application to maintain session state. In this case, 'your session secret' should be replaced with a long, complex, and unique string that only your application knows. - -Auth0 requires a session, so you need to give the session a secret key. - -## Optional Configuration - -- `audience`: The intended recipients of the token -- `connection`: Social identity provider name. To check the available social connections, please visit [Auth0 Social Connections](https://marketplace.auth0.com/features/social-connections). -- `connectionScope`: Additional scopes in the interactive token request. It should always be used in combination with the `connection` parameter - -## Adding the provider to the Backstage frontend - -To add the provider to the frontend, add the `auth0AuthApi` reference and -`SignInPage` component as shown in -[Adding the provider to the sign-in page](../index.md#sign-in-configuration). diff --git a/docs/auth/auth0/provider.md b/docs/auth/auth0/provider.md index 44a5bb0e43..90ac36f07d 100644 --- a/docs/auth/auth0/provider.md +++ b/docs/auth/auth0/provider.md @@ -8,7 +8,7 @@ description: Adding Auth0 as an authentication provider in Backstage :::info This documentation is written for [the new backend system](../../backend-system/index.md) which is the default since Backstage [version 1.24](../../releases/v1.24.0.md). If you are still on the old backend -system, you may want to read [its own article](./provider--old.md) +system, you may want to read [its own article](https://github.com/backstage/backstage/blob/v1.37.0/docs/auth/auth0/provider--old.md) instead, and [consider migrating](../../backend-system/building-backends/08-migrating.md)! ::: diff --git a/docs/auth/bitbucketServer/provider--old.md b/docs/auth/bitbucketServer/provider--old.md deleted file mode 100644 index fdd8fa35be..0000000000 --- a/docs/auth/bitbucketServer/provider--old.md +++ /dev/null @@ -1,60 +0,0 @@ ---- -id: provider--od -title: Bitbucket Server Authentication Provider -sidebar_label: Bitbucket Server -description: Adding Bitbucket Server OAuth as an authentication provider in Backstage ---- - -:::info -This documentation is written for the old backend which has been replaced by -[the new backend system](../../backend-system/index.md), being the default since -Backstage [version 1.24](../../releases/v1.24.0.md). If have migrated to the new -backend system, you may want to read [its own article](./provider.md) -instead. Otherwise, [consider migrating](../../backend-system/building-backends/08-migrating.md)! -::: - -The Backstage `core-plugin-api` package comes with a Bitbucket Server authentication provider that can authenticate -users using Bitbucket Server. This does **NOT** work with Bitbucket Cloud. - -## Create an Application Link in Bitbucket Server - -To add Bitbucket Server authentication, you must create an incoming application link. Follow the steps described in -the [Bitbucket Server documentation](https://confluence.atlassian.com/bitbucketserver/configure-an-incoming-link-1108483657.html) -to create one. - -## Configuration - -The provider configuration can then be added to your `app-config.yaml` under the root `auth` configuration: - -```yaml -auth: - environment: development - providers: - bitbucketServer: - development: - host: bitbucket.org - clientId: ${AUTH_BITBUCKET_SERVER_CLIENT_ID} - clientSecret: ${AUTH_BITBUCKET_SERVER_CLIENT_SECRET} -``` - -The Bitbucket Server provider is a structure with two configuration keys: - -- `clientId`: The client ID that was generated by Bitbucket, e.g. `b0f868455c15dcdff5c5fb5d173ae684`. -- `clientSecret`: The client secret tied to the generated client ID. - -## Adding the provider to the Backstage frontend - -To add the provider to the frontend, add the `bitbucketServerAuthApi` reference and `SignInPage` component as shown -in [Adding the provider to the sign-in page](../index.md#sign-in-configuration). - -## Using Bitbucket Server for sign-in - -In order to use the Bitbucket Server provider for sign-in, you must configure it with a `signIn.resolver`. See -the [Sign-In Resolver documentation](../identity-resolver.md) for more details on how this is done. Note that for the -Bitbucket Server provider, you'll want to use `bitbucketServer` as the provider ID, -and `providers.bitbucketServer.create` for the provider factory. - -The `@backstage/plugin-auth-backend` plugin also comes with a built-in resolver that can be used if desired. -The `emailMatchingUserEntityProfileEmail` identifies users by matching their Bitbucket Server email address to the email -address of `User` entities in the catalog. Note that you must populate your catalog with matching entities or users will -not be able to sign in with this resolver. diff --git a/docs/auth/bitbucketServer/provider.md b/docs/auth/bitbucketServer/provider.md index 0da600d3fd..89b8be3ff6 100644 --- a/docs/auth/bitbucketServer/provider.md +++ b/docs/auth/bitbucketServer/provider.md @@ -8,7 +8,7 @@ description: Adding Bitbucket Server OAuth as an authentication provider in Back :::info This documentation is written for [the new backend system](../../backend-system/index.md) which is the default since Backstage [version 1.24](../../releases/v1.24.0.md). If you are still on the old backend -system, you may want to read [its own article](./provider--old.md) +system, you may want to read [its own article](https://github.com/backstage/backstage/blob/v1.37.0/docs/auth/bitbucketServer/provider--old.md) instead, and [consider migrating](../../backend-system/building-backends/08-migrating.md)! ::: diff --git a/docs/auth/identity-resolver--old.md b/docs/auth/identity-resolver--old.md deleted file mode 100644 index ce41fbcf2b..0000000000 --- a/docs/auth/identity-resolver--old.md +++ /dev/null @@ -1,379 +0,0 @@ ---- -id: identity-resolver--old -title: Sign-in Identities and Resolvers (Old Backend System) -description: An introduction to Backstage user identities and sign-in resolvers in the old backend system ---- - -:::info -This documentation is written for the old backend which has been replaced by -[the new backend system](../backend-system/index.md), being the default since -Backstage [version 1.24](../releases/v1.24.0.md). If have migrated to the new -backend system, you may want to read [its own article](./identity-resolver.md) -instead. Otherwise, [consider migrating](../backend-system/building-backends/08-migrating.md)! -::: - -By default, every Backstage auth provider is configured only for the use-case of -access delegation. This enables Backstage to request resources and actions from -external systems on behalf of the user, for example re-triggering a build in CI. - -If you want to use an auth provider to sign in users, you need to explicitly configure -it have sign-in enabled and also tell it how the external identities should -be mapped to user identities within Backstage. - -## Quick Start - -> See [the auth docs](./index.md) -> for a full list of auth providers and their built-in sign-in resolvers. - -Backstage projects created with `npx @backstage/create-app` come configured with a -sign-in resolver for GitHub guest access. This resolver makes all users share -a single "guest" identity and is only intended as a minimum requirement to quickly -get up and running. You can replace `github` for any of the other providers if you need. - -This resolver should not be used in production, as it uses a single shared identity, -and has no restrictions on who is able to sign-in. Be sure to read through the rest -of this page to understand the Backstage identity system once you need to install -a resolver for your production environment. - -The guest resolver can be useful for testing purposes too, and it looks like this: - -```ts -signIn: { - resolver(_, ctx) { - const userRef = 'user:default/guest' - return ctx.issueToken({ - claims: { - sub: userRef, - ent: [userRef], - }, - }), - }, -}, -``` - -## Backstage User Identity - -A user identity within Backstage is built up from two pieces of information, a -user [entity reference](../features/software-catalog/references.md), and a -set of ownership entity references. -When a user signs in, a Backstage token is generated with these two pieces of information, -which is then used to identify the user within the Backstage ecosystem. - -The user entity reference should uniquely identify the logged in user in Backstage. -It is encouraged that a matching user entity also exists within the Software Catalog, -but it is not required. If the user entity exists in the catalog it can be used to -store additional data about the user. There may even be some plugins that require -this for them to be able to function. - -The ownership references are also entity references, and it is likewise -encouraged that these entities exist within the catalog, but it is not a requirement. -The ownership references are used to determine what the user owns, as a set -of references that the user claims ownership though. For example, a user -Jane (`user:default/jane`) might have the ownership references `user:default/jane`, -`group:default/team-a`, and `group:default/admins`. Given these ownership claims, -any entity that is marked as owned by either of `user:jane`, `team-a`, or `admins` would -be considered owned by Jane. - -The ownership claims often contain the user entity reference itself, but it is not -required. It is also worth noting that the ownership claims can also be used to -resolve other relations similar to ownership, such as a claim for a `maintainer` or -`operator` status. - -The Backstage token that encapsulates the user identity is a JWT. The user entity -reference is stored in the `sub` claim of the payload, while the ownership references -are stored in a custom `ent` claim. Both the user and ownership references should -always be full entity references, as opposed to shorthands like just `jane` or `user:jane`. - -## Sign-in Resolvers - -Signing in a user into Backstage requires a mapping of the user identity from the -third-party auth provider to a Backstage user identity. This mapping can vary quite -a lot between different organizations and auth providers, and because of that there's -no default way to resolve user identities. The auth provider that one wants to use -for sign-in must instead be configured with a sign-in resolver, which is a function -that is responsible for creating this user identity mapping. - -The input to the sign-in resolver function is the result of a successful log in with -the given auth provider, as well as a context object that contains various helpers -for looking up users and issuing tokens. There are also a number of built-in sign-in -resolvers that can be used, which are covered a bit further down. - -Note that while it possible to configure multiple auth providers to be used for sign-in, -you should take care when doing so. It is best to make sure that the different auth -providers either do not have any user overlap, or that any users that are able to log -in with multiple providers always end up with the same Backstage identity. - -### Custom Resolver Example - -Let's look at an example of a custom sign-in resolver for the Google auth provider. -This all typically happens within your `packages/backend/src/plugins/auth.ts` file, -which is responsible for setting up and configuring the auth backend plugin. - -You provide the resolver as part of the options you pass when creating a new auth -provider factory. This means you need to replace the default Google provider with -one that you create. Be sure to also include the existing `defaultAuthProviderFactories` -if you want to keep all of the built-in auth providers installed. - -Now let's look at the example, with the rest of the commentary being made with in -the code comments: - -```ts -// File: packages/backend/src/plugins/auth.ts -import { - createRouter, - providers, - defaultAuthProviderFactories, -} from '@backstage/plugin-auth-backend'; -import { Router } from 'express'; -import { PluginEnvironment } from '../types'; - -export default async function createPlugin( - env: PluginEnvironment, -): Promise { - return await createRouter({ - ...env, - providerFactories: { - ...defaultAuthProviderFactories, - google: providers.google.create({ - signIn: { - resolver: async (info, ctx) => { - const { - profile: { email }, - } = info; - // Profiles are not always guaranteed to have an email address. - // You can also find more provider-specific information in `info.result`. - // It typically contains a `fullProfile` object as well as ID and/or access - // tokens that you can use for additional lookups. - if (!email) { - throw new Error('User profile contained no email'); - } - - // You can add your own custom validation logic here. - // Logins can be prevented by throwing an error like the one above. - myEmailValidator(email); - - // This example resolver simply uses the local part of the email as the name. - const [name] = email.split('@'); - - // This helper function handles sign-in by looking up a user in the catalog. - // The lookup can be done either by reference, annotations, or custom filters. - // - // The helper also issues a token for the user, using the standard group - // membership logic to determine the ownership references of the user. - return ctx.signInWithCatalogUser({ - entityRef: { name }, - }); - }, - }, - }), - }, - }); -} -``` - -### Built-in Resolvers - -You don't always have to write your own custom resolver. The auth backend plugin provides -built-in resolvers for many of the common sign-in patterns. You access these via the `resolvers` -property of each of the auth provider integrations. For example, the Google provider has -a built in resolver that works just like the one we defined above: - -```ts -// File: packages/backend/src/plugins/auth.ts -export default async function createPlugin( - // ... - return await createRouter({ - // ... - providerFactories: { - // ... - google: providers.google.create({ - signIn: { - resolver: providers.google.resolvers.emailLocalPartMatchingUserEntityName(), - }, - }); - } - }) -) -``` - -There are also other options, like the this one that looks up a user -by matching the `google.com/email` annotation of user entities in the catalog: - -```ts -providers.google.create({ - signIn: { - resolver: providers.google.resolvers.emailMatchingUserEntityAnnotation(), - }, -}); -``` - -## Custom Ownership Resolution - -If you want to have more control over the membership resolution and token generation -that happens during sign-in you can replace `ctx.signInWithCatalogUser` with a set -of lower-level calls: - -```ts -// File: packages/backend/src/plugins/auth.ts -import { getDefaultOwnershipEntityRefs } from '@backstage/plugin-auth-backend'; - -export default async function createPlugin( - // ... - return await createRouter({ - // ... - providerFactories: { - // ... - google: async ({ profile: { email } }, ctx) => { - if (!email) { - throw new Error('User profile contained no email'); - } - - // This step calls the catalog to look up a user entity. You could for example - // replace it with a call to a different external system. - const { entity } = await ctx.findCatalogUser({ - annotations: { - 'acme.org/email': email, - }, - }); - - // In this step we extract the ownership references from the user entity using - // the standard logic. It uses a reference to the entity itself, as well as the - // target of each `memberOf` relation where the target is of the kind `Group`. - // - // If you replace the catalog lookup with something that does not return - // an entity you will need to replace this step as well. - // - // You might also replace it if you for example want to filter out certain groups. - // - // Note that `getDefaultOwnershipEntityRefs` only includes groups to which the - // user has a direct MEMBER_OF relationship. It's perfectly fine to include - // groups that the user is transitively part of in the claims array, but the - // catalog doesn't currently provide a direct way of accessing this list of - // groups. - const ownershipRefs = getDefaultOwnershipEntityRefs(entity); - - // The last step is to issue the token, where we might provide more options in the future. - return ctx.issueToken({ - claims: { - sub: stringifyEntityRef(entity), - ent: ownershipRefs, - }, - }); - }; - } - }) -) -``` - -## Sign-In without Users in the Catalog - -While populating the catalog with organizational data unlocks more powerful ways -to browse your software ecosystem, it might not always be a viable or prioritized -option. However, even if you do not have user entities populated in your catalog, you -can still sign in users. As there are currently no built-in sign-in resolvers for -this scenario you will need to implement your own. - -Signing in a user that doesn't exist in the catalog is as simple as skipping the -catalog lookup step from the above example. Rather than looking up the user, we -instead immediately issue a token using whatever information is available. One caveat -is that it can be tricky to determine the ownership references, although it can -be achieved for example through a lookup to an external service. You typically -want to at least use the user itself as a lone ownership reference. - -Because we no longer use the catalog as an allow-list of users, it is often important -that you limit what users are allowed to sign in. This could be a simple email domain -check like in the example below, or you might for example look up the GitHub organizations -that the user belongs to using the user access token in the provided result object. - -```ts -// File: packages/backend/src/plugins/auth.ts -import { createRouter, providers } from '@backstage/plugin-auth-backend'; -import { Router } from 'express'; -import { PluginEnvironment } from '../types'; -import { - stringifyEntityRef, - DEFAULT_NAMESPACE, -} from '@backstage/catalog-model'; - -export default async function createPlugin( - env: PluginEnvironment, -): Promise { - return await createRouter({ - ...env, - providerFactories: { - google: providers.google.create({ - signIn: { - resolver: async ({ profile }, ctx) => { - if (!profile.email) { - throw new Error( - 'Login failed, user profile does not contain an email', - ); - } - // Split the email into the local part and the domain. - const [localPart, domain] = profile.email.split('@'); - - // Next we verify the email domain. It is recommended to include this - // kind of check if you don't look up the user in an external service. - if (domain !== 'acme.org') { - throw new Error( - `Login failed, this email ${profile.email} does not belong to the expected domain`, - ); - } - - // By using `stringifyEntityRef` we ensure that the reference is formatted correctly - const userEntity = stringifyEntityRef({ - kind: 'User', - name: localPart, - namespace: DEFAULT_NAMESPACE, - }); - return ctx.issueToken({ - claims: { - sub: userEntity, - ent: [userEntity], - }, - }); - }, - }, - }), - }, - }); -} -``` - -## AuthHandler - -Similar to a custom sign-in resolver, you can also write a custom auth handler -function which is used to verify and convert the auth response into the profile -that will be presented to the user. This is where you can customize things like -display name and profile picture. - -This is also the place where you can do authorization and validation of the user -and throw errors if the user should not be allowed access in Backstage. - -```ts -// File: packages/backend/src/plugins/auth.ts -export default async function createPlugin( - env: PluginEnvironment, -): Promise { - return await createRouter({ - ... - providerFactories: { - google: providers.google.create({ - authHandler: async ({ - fullProfile // Type: passport.Profile, - idToken // Type: (Optional) string, - }) => { - // Custom validation code goes here - return { - profile: { - email, - picture, - displayName, - } - }; - } - }) - } - }) -} -``` diff --git a/docs/auth/identity-resolver.md b/docs/auth/identity-resolver.md index 6a59805b0b..3d15c9541e 100644 --- a/docs/auth/identity-resolver.md +++ b/docs/auth/identity-resolver.md @@ -7,7 +7,7 @@ description: An introduction to Backstage user identities and sign-in resolvers :::info This documentation is written for [the new backend system](../backend-system/index.md) which is the default since Backstage [version 1.24](../releases/v1.24.0.md). If you are still on the old backend -system, you may want to read [its own article](./identity-resolver--old.md) +system, you may want to read [its own article](https://github.com/backstage/backstage/blob/v1.37.0/docs/auth/identity-resolver--old.md) instead, and [consider migrating](../backend-system/building-backends/08-migrating.md)! ::: diff --git a/docs/auth/oidc--old.md b/docs/auth/oidc--old.md deleted file mode 100644 index 904e48c9a3..0000000000 --- a/docs/auth/oidc--old.md +++ /dev/null @@ -1,289 +0,0 @@ ---- -id: oidc--old -title: OIDC provider from scratch -description: This section shows how to use an OIDC provider from scratch, same steps apply for custom providers. ---- - -:::info -This documentation is written for the old backend which has been replaced by -[the new backend system](../backend-system/index.md), being the default since -Backstage [version 1.24](../releases/v1.24.0.md). If have migrated to the new -backend system, you may want to read [its own article](./oidc.md) -instead. Otherwise, [consider migrating](../backend-system/building-backends/08-migrating.md)! -::: - -This section shows how to use an OIDC provider from scratch, same steps apply for custom -providers. Please note these steps are for using a provider, not how to implement one, -and Backstage recommends creating custom providers specific to the IDP, so we'll use a -`azureOIDC` provider throughout this example, feel free to change any of those refs -to your provider name. - -## Summary - -To add providers not enabled by default like OIDC, we need to follow some steps, we -assume you already have a sign in page to which we'll add the provider so users can -sign in through the provider. In simple steps here's how you enable the provider: - -- Create an API reference to identify the provider. -- Create the API factory that will handle the authentication. -- Add or reuse an auth provider so you can authenticate. -- Add or reuse a resolver to handle the result from the authentication. -- Configure the provider to access your 3rd party auth solution. -- Add the provider to sign in page so users can login with it. - -We'll explain each step more in detail next. - -### The API reference - -An API reference exist for the sake of **Dependency Injection**, check [Utility APIs][4] -for extended explanation. - -In this OIDC example, we'll create the API reference directly in the -`packages/app/src/apis.ts` file, it is not a requirement to put the reference in this -file. Any location will do as long as it's available to be imported to where the API -factory is, as well as easily accessible to the rest of the application so any package -and plugin can inject the API instance when necessary. - -An example of such would be when you use an auth provider from a library installed with -NPM, or any other library repository, you would import the API ref from the library. - -```ts -export const azureOIDCAuthApiRef: ApiRef< - OpenIdConnectApi & ProfileInfoApi & BackstageIdentityApi & SessionApi -> = createApiRef({ - id: 'auth.my-custom-provider', -}); -``` - -Please note a few things, the ID can be anything you want as long as it doesn't conflict -with other refs, backstage recommends to use a custom name that references your custom -provider, for example we are using OIDC protocol with Azure, so we could use something -like `auth.azure.oidc` as well. - -Also we're exporting this reference, as well as the `typings`, we need to -be able to import this reference anywhere in the app, and the `typings` will tell typescript -what instance we're getting from DI when injecting the API. In this case we are defining -an API for authentication, so we tell TS that this instance complies with 4 API -interfaces: - -- The OICD API that will handle authentication. -- Profile API for requesting user profile info from the auth provider in question. -- Backstage identity API to handle and associate the user profile with backstage identity. -- Session API, to handle the session the user will have while logged in. - -### The API Factory - -A factory is a function that can take some parameters or dependencies and return an -instance of something, in our case it will be a function that requests some backstage -APIs and use them to create an instance of an OIDC API provider. - -Please note that this function only runs (creates the instance) when somewhere else in -the app you request the DI to give you an instance of the OIDC provider using the API ref -defined above, and the DI will only run this function the first time, from then on any -other DI injection will just receive the same instance created the first time, basically -the instance is cached by the DI library, a singleton. - -Let's add our OIDC API factory to the APIs array in the `packages/app/src/apis.ts` file: - -```ts title="packages/app/src/apis.ts" -/* highlight-add-next-line */ -import { OAuth2 } from '@backstage/core-app-api'; - -export const apis: AnyApiFactory[] = [ - /* highlight-add-start */ - createApiFactory({ - api: azureOIDCAuthApiRef, - deps: { - discoveryApi: discoveryApiRef, - oauthRequestApi: oauthRequestApiRef, - configApi: configApiRef, - }, - factory: ({ discoveryApi, oauthRequestApi, configApi }) => - OAuth2.create({ - configApi, - discoveryApi, - oauthRequestApi, - provider: { - id: 'my-auth-provider', - title: 'My custom auth provider', - icon: () => null, - }, - environment: configApi.getOptionalString('auth.environment'), - defaultScopes: ['openid', 'profile', 'email'], - popupOptions: { - // optional, used to customize login in popup size - size: { - fullscreen: true, - }, - /** - * or specify popup width and height - * size: { - width: 1000, - height: 1000, - } - */ - }, - }), - }), - /* highlight-add-end */ - // .. -]; -``` - -Please note we're importing the `OAuth2` class from `@backstage/core-app-api` effectively -delegating the authentication to it. Also we're using the `my-auth-provider` ID to tell -`OAuth2` to use the auth provider we'll define in the next section, and added the default -scopes to request ID, profile, email and user read permissions. - -## The Auth Provider - -The Auth Provider is responsible for authenticating with the 3rd party service, and give -us back the credentials, here's where you pick which protocol to use, be it Auth0, OAuth2, -OIDC, SAML or any other that your 3rd party IDP provider supports. - -For this example we'll use OIDC, we pass a factory to the `providerFactories` object with -the ID you picked to represent the Auth provider, this ID has to match with the provider's -`id` inside the API factory, the yaml config provider key under `auth.providers`, and the -callback URI provider segment (you'll have to configure your IDP to handle the callback -URI properly). - -```ts -export default async function createPlugin( - env: PluginEnvironment, -): Promise { - return await createRouter({ - logger: env.logger, - config: env.config, - database: env.database, - discovery: env.discovery, - tokenManager: env.tokenManager, - providerFactories: { - ...defaultAuthProviderFactories, - /* highlight-add-next-line */ - 'my-auth-provider': providers.oidc.create({}), - }, - // .. -}) -``` - -### The Resolver - -Resolvers exist to map user identity from the 3rd party (in this case an azure IDP -provider) to the backstage user identity, for a detailed explanation check the -[Identity Resolver][1] page, it explains how to write a custom resolver as well as -linking the built in resolvers of backstage. - -The default OIDC provider does not support SignIn, we need to add such support by -adding a resolver for a SignIn request. - -The OIDC provider doesn't provide any build-in resolvers, so we'll need to define our own: - -```ts -import { - DEFAULT_NAMESPACE, - /* highlight-add-next-line */ - stringifyEntityRef, -} from '@backstage/catalog-model'; - -export default async function createPlugin( - env: PluginEnvironment, -): Promise { - return await createRouter({ - logger: env.logger, - config: env.config, - database: env.database, - discovery: env.discovery, - tokenManager: env.tokenManager, - providerFactories: { - ...defaultAuthProviderFactories, - 'my-auth-provider': providers.oidc.create({ - /* highlight-add-start */ - signIn: { - resolver(info, ctx) { - const userRef = stringifyEntityRef({ - kind: 'User', - name: info.result.userinfo.sub, - namespace: DEFAULT_NAMESPACE, - }); - return ctx.issueToken({ - claims: { - sub: userRef, // The user's own identity - ent: [userRef], // A list of identities that the user claims ownership through - }, - }); - }, - }, - /* highlight-add-end */ - }), - }, - // .. - }) -``` - -### The configuration - -Since we are using our custom OIDC Auth Provider, we need to add a configuration based -on the provider used, in this case based on OIDC protocol (remember the 3rd party has to -support the protocol). - -In this example we'll configure OIDC with `my-auth-provider`, to do so we need to -[Create app registration][2] in the Azure console, the only difference is that the -`http://localhost:7007/api/auth/microsoft/handler/frame` URL needs to change to -`http://localhost:7007/api/auth/my-auth-provider/handler/frame`. - -Then we need to configure the env variables for the provider, based on the provider's code -in `plugins/auth-backend/src/providers/oidc/provider.ts` we need the following variables -in the `app-config.yaml`: - -```yaml title="app-config.yaml" -auth: - environment: development - ### Providing an auth.session.secret will enable session support in the auth-backend - session: - secret: ${SESSION_SECRET} - providers: - my-auth-provider: - development: - metadataUrl: https://example.com/.well-known/openid-configuration - clientId: ${AUTH_MY_CLIENT_ID} - clientSecret: ${AUTH_MY_CLIENT_SECRET} -``` - -Anything enclosed in `${}` can be replaced directly in the yaml, or provided as -environment variables, the way you obtain all these except `scope` and `prompt` is to -check the App Registration you created: - -- `clientId`: Grab from the Overview page. -- `clientSecret`: Can only be seen when creating the secret, if you lose it you'll need a - new secret. -- `metadataUrl`: In Overview > Endpoints tab, grab OpenID Connect metadata document URL. -- `authorizationUrl` and `tokenUrl`: Open the `metadataUrl` in a browser, that json will - hold these 2 urls somewhere in there. -- `tokenEndpointAuthMethod`: Don't define it, use the default unless you know what it does. -- `tokenSignedResponseAlg`: Don't define it, use the default unless you know what it does. -- `scope`: Only used if we didn't specify `defaultScopes` in the provider's factory, - basically the same thing. -- `prompt`: Recommended to use `auto` so the browser will request login to the IDP if the - user has no active session. - -Note that for the time being, any change in this yaml file requires a restart of the app, -also you need to have the `session.secret` part to use OIDC (some other providers might -need this as well) to support user sessions. - -### The Sign In provider - -The last step is to add the provider to the `SignInPage` so users can sign in with your -new provider, please follow the [Sign In Configuration][3] docs, here's where you import -and use the API reference we defined earlier. - -## Note - -These steps apply to most if not all the providers, including custom providers, the main -difference between different providers will be the contents of the API factory, the code -in the Auth Provider Factory, the resolver, and the different variables each provider -needs in the YAML config or env variables. - -[1]: https://backstage.io/docs/auth/identity-resolver -[2]: https://backstage.io/docs/auth/microsoft/provider#create-an-app-registration-on-azure -[3]: https://backstage.io/docs/auth/#sign-in-configuration -[4]: https://backstage.io/docs/api/utility-apis diff --git a/docs/auth/oidc.md b/docs/auth/oidc.md index 838ac51f1f..250736cec7 100644 --- a/docs/auth/oidc.md +++ b/docs/auth/oidc.md @@ -7,7 +7,7 @@ description: This section shows how to use an OIDC provider from scratch, same s :::info This documentation is written for [the new backend system](../backend-system/index.md) which is the default since Backstage [version 1.24](../releases/v1.24.0.md). If you are still on the old backend -system, you may want to read [its own article](./oidc--old.md) +system, you may want to read [its own article](https://github.com/backstage/backstage/blob/v1.37.0/docs/auth/oidc--old.md) instead, and [consider migrating](../backend-system/building-backends/08-migrating.md)! ::: diff --git a/docs/auth/service-to-service-auth--old.md b/docs/auth/service-to-service-auth--old.md deleted file mode 100644 index 8341d69341..0000000000 --- a/docs/auth/service-to-service-auth--old.md +++ /dev/null @@ -1,177 +0,0 @@ ---- -id: service-to-service-auth--old -title: Service to Service Auth -# prettier-ignore -description: This section describes how to use service to service authentication, both internally within Backstage plugins and towards external services. ---- - -:::info -This documentation is written for the old backend which has been replaced by -[the new backend system](../backend-system/index.md), being the default since -Backstage [version 1.24](../releases/v1.24.0.md). If have migrated to the new -backend system, you may want to read [its own article](./identity-resolver.md) -instead. Otherwise, [consider migrating](../backend-system/building-backends/08-migrating.md)! -::: - -This article describes the steps needed to introduce _service-to-service auth_ (formerly _backend-to-backend_ auth). -This allows plugin backends to determine whether a given request originates from -a legitimate Backstage plugin (or other external caller), by requiring a special -type of service-to-service token which is signed with a shared secret. - -When enabling this protection on your Backstage backend plugins, for example the -catalog, other callers in the ecosystem such as the search indexer and -scaffolder would need to present a valid token to the catalog to be able to -request its contents. - -## Setup - -In a newly created Backstage app, the backend is setup up to not require any -auth at all. This means that generated service-to-service tokens are empty, and -that incoming requests are not validated. If you want to enable -service-to-service auth, the first step is to switch out the following line in -your backend setup at `packages/backend/src/index.ts`: - -```ts title="packages/backend/src/index.ts" -/* highlight-remove-next-line */ -const tokenManager = ServerTokenManager.noop(); -/* highlight-add-next-line */ -const tokenManager = ServerTokenManager.fromConfig(config, { logger: root }); -``` - -By switching from the no-op `ServiceTokenManager` to one created from config, -you enable service-to-service auth for any plugin that implements it. The local -development setup will generally not be impacted by this, as temporary keys are -generated under the hood. But for the production setup, this means you must now -provide a shared secret that enables your backend plugins to communicate with -each other. - -Backstage service-to-service tokens are currently always signed with a single -secret key. It needs to be shared across all backend plugins and services that -ones wishes to communicate across. The key can be any base64 encoded secret. -The following command can be used to generate such a key in a terminal: - -```bash -node -p 'require("crypto").randomBytes(24).toString("base64")' -``` - -Then place it in the backend configuration, either as a direct value or -injected as an env variable. - -```yaml -# commonly in your app-config.production.yaml -backend: - auth: - keys: - - secret: - # - secret: ${BACKEND_SECRET} - if you want to use an env variable instead -``` - -**NOTE**: For ease of development, we auto-generate a key for you if you haven't -configured a secret in dev mode. You _must set your own secret_ in order for -service-to-service auth to work in production; the `ServiceTokenManager` will -throw an exception in production if it has no keys to work with, which will lead -to the backend failing to start up. - -## Usage in Backend Plugins - -There are a few steps if you want to make use of the service-to-service auth in -your own backend plugin. First you need to add the `TokenManager` dependency to -the `createRouter` options. Typically as `tokenManager: TokenManager`. Along -with this you'll need to ask users to start providing this new dependency in -their backend setup code. - -Once the `TokenManager` is available, you use the `.getToken()` method to generate -a new token for any outgoing requests towards other Backstage backend plugins. -This method should be called for every request that you make; do not store the -token for later use. The `TokenManager` implementations should already cache -tokens as needed. The returned token should then be added as a `Bearer` token -for the upstream request, for example: - -```ts -const { token } = await this.tokenManager.getToken(); - -const response = await fetch(pluginBackendApiUrl, { - method: 'GET', - headers: { - ...headers, - Authorization: `Bearer ${token}`, - }, -}); -``` - -To authenticate an incoming request you use the `.authenticate(token)` method. -At the time of writing this method doesn't return anything, it will simply -throw if the token is invalid. - -```ts -await tokenManager.authenticate(token); // throws if token is invalid -``` - -## Usage in External Callers - -If you have enabled server-to-server auth, you may be interested in generating -tokens in code that is external to Backstage itself. External callers may even -be written in other languages than Node.js. This section explains how to generate -a valid token yourself. - -The token must be a JWT with a `HS256` signature, using the raw base64 decoded -value of the configured key as the secret. It must also have the following payload: - -- `sub`: "backstage-server" (only this value supported currently) -- `exp`: one hour from the time it was generated, in epoch seconds - -> NOTE: The JWT must encode the `alg` header as a protected header, such as with -> [setProtectedHeader](https://github.com/panva/jose/blob/main/docs/classes/jwt_sign.SignJWT.md#setprotectedheader). - -## Granular Access Control - -We plan to build out the service-to-service auth to be much more powerful in the -future, but before that is done there are a few tricks you can use with the -current system to harden your deployments. This section assumes that you have -already split your backend plugins into more than one backend deployment, in -order to scale or isolate them. - -The backend auth configuration has support for providing multiple keys, for -example: - -```yaml -backend: - auth: - keys: - - secret: my-secret-key-1 - - secret: my-secret-key-2 - - secret: my-secret-key-3 -``` - -The first key will be used for signing requests, while all of the keys will be -used for validation. This means that you can set up an asymmetric configuration -where some backend deployments do not have access to each other. - -For example, consider the case where we have split up the catalog, scaffolder, -and search plugin into three separate backend deployments. We can use the -following configurations to allow both the scaffolder and search plugin to speak -to the -catalog, but not the other way around, and to not allow any communication between -the scaffolder and search plugins. - -```yaml -# catalog config -backend: - auth: - keys: - - secret: my-secret-key-catalog - - secret: my-secret-key-scaffolder - - secret: my-secret-key-search - -# scaffolder config -backend: - auth: - keys: - - secret: my-secret-key-scaffolder - -# search config -backend: - auth: - keys: - - secret: my-secret-key-search -``` diff --git a/docs/auth/service-to-service-auth.md b/docs/auth/service-to-service-auth.md index 126b136d0b..fecb3f5191 100644 --- a/docs/auth/service-to-service-auth.md +++ b/docs/auth/service-to-service-auth.md @@ -8,7 +8,7 @@ description: This section describes service to service authentication works, bot :::info This documentation is written for [the new backend system](../backend-system/index.md) which is the default since Backstage [version 1.24](../releases/v1.24.0.md). If you are still on the old backend -system, you may want to read [its own article](./service-to-service-auth--old.md) +system, you may want to read [its own article](https://github.com/backstage/backstage/blob/v1.37.0/docs/auth/service-to-service-auth--old.md) instead, and [consider migrating](../backend-system/building-backends/08-migrating.md)! ::: @@ -219,7 +219,7 @@ provider's documentation. The subject returned from the token verification will become part of the credentials object that the request recipient plugins get. All subjects will have the prefix -`external:`, but you can also provide a custom subjectPrefix which will get appended before the +`external:`, but you can also provide a custom `subjectPrefix` which will get appended before the subject returned from your JWKS service (ex. `external:custom-prefix:sub`). Callers must pass along tokens with requests in the `Authorization` header when diff --git a/docs/integrations/aws-s3/discovery--old.md b/docs/integrations/aws-s3/discovery--old.md deleted file mode 100644 index 3a9d30a370..0000000000 --- a/docs/integrations/aws-s3/discovery--old.md +++ /dev/null @@ -1,88 +0,0 @@ ---- -id: discovery--old -title: AWS S3 Discovery -sidebar_label: Discovery -# prettier-ignore -description: Automatically discovering catalog entities from an AWS S3 Bucket ---- - -:::info -This documentation is written for the old backend which has been replaced by [the new backend system](../../backend-system/index.md), being the default since Backstage [version 1.24](../../releases/v1.24.0.md). If have migrated to the new backend system, you may want to read [its own article](./discovery.md) instead. Otherwise, [consider migrating](../../backend-system/building-backends/08-migrating.md)! -::: - -The AWS S3 integration has a special entity provider for discovering catalog -entities located in an S3 Bucket. If you have a bucket that contains multiple -catalog files, and you want to automatically discover them, you can use this -provider. The provider will crawl your S3 bucket and register entities -matching the configured path. This can be useful as an alternative to static -locations or manually adding things to the catalog. - -To use the entity provider, you'll need an AWS S3 integration -[set up](locations.md) with `accessKeyId` and `secretAccessKey`, and/or -a `roleArn` or none of these (e.g., profile- or instance-based credentials). - -At production deployments, you likely manage these with the permissions attached -to your instance. - -In your configuration, you add a provider config per bucket: - -```yaml -# app-config.yaml - -catalog: - providers: - awsS3: - yourProviderId: # identifies your dataset / provider independent of config changes - bucketName: sample-bucket - prefix: prefix/ # optional - region: us-east-2 # optional, uses the default region otherwise - schedule: # same options as in SchedulerServiceTaskScheduleDefinition - # supports cron, ISO duration, "human duration" as used in code - frequency: { minutes: 30 } - # supports ISO duration, "human duration" as used in code - timeout: { minutes: 3 } -``` - -For simple setups, you can omit the provider ID at the config -which has the same effect as using `default` for it. - -```yaml -# app-config.yaml - -catalog: - providers: - awsS3: - # uses "default" as provider ID - bucketName: sample-bucket - prefix: prefix/ # optional - region: us-east-2 # optional, uses the default region otherwise - schedule: # same options as in SchedulerServiceTaskScheduleDefinition - # supports cron, ISO duration, "human duration" as used in code - frequency: { minutes: 30 } - # supports ISO duration, "human duration" as used in code - timeout: { minutes: 3 } -``` - -As this provider is not one of the default providers, you will first need to install -the AWS catalog plugin: - -```bash title="From your Backstage root directory" -yarn --cwd packages/backend add @backstage/plugin-catalog-backend-module-aws -``` - -Once you've done that, you'll also need to add the segment below to `packages/backend/src/plugins/catalog.ts`: - -```ts -/* packages/backend/src/plugins/catalog.ts */ - -import { AwsS3EntityProvider } from '@backstage/plugin-catalog-backend-module-aws'; - -const builder = await CatalogBuilder.create(env); -/** ... other processors and/or providers ... */ -builder.addEntityProvider( - AwsS3EntityProvider.fromConfig(env.config, { - logger: env.logger, - scheduler: env.scheduler, - }), -); -``` diff --git a/docs/integrations/aws-s3/discovery.md b/docs/integrations/aws-s3/discovery.md index f4d18015a2..fa1eb017fc 100644 --- a/docs/integrations/aws-s3/discovery.md +++ b/docs/integrations/aws-s3/discovery.md @@ -7,7 +7,7 @@ description: Automatically discovering catalog entities from an AWS S3 Bucket --- :::info -This documentation is written for [the new backend system](../../backend-system/index.md) which is the default since Backstage [version 1.24](../../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](./discovery--old.md) instead, and [consider migrating](../../backend-system/building-backends/08-migrating.md)! +This documentation is written for [the new backend system](../../backend-system/index.md) which is the default since Backstage [version 1.24](../../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](https://github.com/backstage/backstage/blob/v1.37.0/docs/integrations/aws-s3/discovery--old.md) instead, and [consider migrating](../../backend-system/building-backends/08-migrating.md)! ::: The AWS S3 integration has a special entity provider for discovering catalog diff --git a/docs/integrations/azure/discovery--old.md b/docs/integrations/azure/discovery--old.md deleted file mode 100644 index 27532fe6ee..0000000000 --- a/docs/integrations/azure/discovery--old.md +++ /dev/null @@ -1,186 +0,0 @@ ---- -id: discovery--old -title: Azure DevOps Discovery -sidebar_label: Discovery -# prettier-ignore -description: Automatically discovering catalog entities from repositories in an Azure DevOps organization ---- - -:::info -This documentation is written for the old backend which has been replaced by [the new backend system](../../backend-system/index.md), being the default since Backstage [version 1.24](../../releases/v1.24.0.md). If have migrated to the new backend system, you may want to read [its own article](./discovery.md) instead. Otherwise, [consider migrating](../../backend-system/building-backends/08-migrating.md)! -::: - -The Azure DevOps integration has a special entity provider for discovering -catalog entities within an Azure DevOps. The provider will crawl your Azure -DevOps organization and register entities matching the configured path. This can -be useful as an alternative to static locations or manually adding things to the -catalog. - -This guide explains how to install and configure the Azure DevOps Entity Provider (recommended) or the Azure DevOps Processor. - -## Dependencies - -### Code Search Feature - -Azure discovery is driven by the Code Search feature in Azure DevOps, this may not be enabled by default. For Azure -DevOps Services you can confirm this by looking at the installed extensions in your Organization Settings. For Azure -DevOps Server you'll find this information in your Collection Settings. - -If the Code Search extension is not listed then you can install it from the [Visual Studio Marketplace](https://marketplace.visualstudio.com/items?itemName=ms.vss-code-search&targetId=f9352dac-ba6e-434e-9241-a848a510ce3f&utm_source=vstsproduct&utm_medium=SearchExtStatus). - -### Azure Integration - -Setup [Azure integration](locations.md) with `host` and `token`. Host must be `dev.azure.com` for Cloud users, otherwise set this to your on-premise hostname. - -## Installation - -At your configuration, you add one or more provider configs: - -```yaml title="app-config.yaml" -catalog: - providers: - azureDevOps: - yourProviderId: # identifies your dataset / provider independent of config changes - organization: myorg - project: myproject - repository: service-* # this will match all repos starting with service-* - path: /catalog-info.yaml - schedule: # optional; same options as in SchedulerServiceTaskScheduleDefinition - # supports cron, ISO duration, "human duration" as used in code - frequency: { minutes: 30 } - # supports ISO duration, "human duration" as used in code - timeout: { minutes: 3 } - yourSecondProviderId: # identifies your dataset / provider independent of config changes - organization: myorg - project: '*' # this will match all projects - repository: '*' # this will match all repos - path: /catalog-info.yaml - anotherProviderId: # another identifier - organization: myorg - project: myproject - repository: '*' # this will match all repos - path: /src/*/catalog-info.yaml # this will search for files deep inside the /src folder - yetAnotherProviderId: # guess, what? Another one :) - host: selfhostedazure.yourcompany.com - organization: myorg - project: myproject - branch: development -``` - -The parameters available are: - -- **`host:`** _(optional)_ Leave empty for Cloud hosted, otherwise set to your self-hosted instance host. -- **`organization:`** Your Organization slug (or Collection for on-premise users). Required. -- **`project:`** _(required)_ Your project slug. Wildcards are supported as shown on the examples above. Using '\*' will search all projects. For a project name containing spaces, use both single and double quotes as in `project: '"My Project Name"'`. -- **`repository:`** _(optional)_ The repository name. Wildcards are supported as show on the examples above. If not set, all repositories will be searched. -- **`path:`** _(optional)_ Where to find catalog-info.yaml files. Defaults to /catalog-info.yaml. -- **`branch:`** _(optional)_ The branch name to use. -- **`schedule`**: - - **`frequency`**: - How often you want the task to run. The system does its best to avoid overlapping invocations. - - **`timeout`**: - The maximum amount of time that a single task invocation can take. - - **`initialDelay`** _(optional)_: - The amount of time that should pass before the first invocation happens. - - **`scope`** _(optional)_: - `'global'` or `'local'`. Sets the scope of concurrency control. - -_Note:_ - -- The path parameter follows the same rules as the search on Azure DevOps web interface. For more details visit the [official search documentation](https://docs.microsoft.com/en-us/azure/devops/project/search/get-started-search?view=azure-devops). -- To use branch parameters, it is necessary that the desired branch be added to the "Searchable branches" list within Azure DevOps Repositories. To do this, follow the instructions below: - -1. Access your Azure DevOps and open the repository in which you want to add the branch. -2. Click on "Settings" in the lower-left corner of the screen. -3. Select the "Options" option in the left navigation bar. -4. In the "Searchable branches" section, click on the "Add" button to add a new branch. -5. In the window that appears, enter the name of the branch you want to add and click "Add". -6. The added branch will now appear in the "Searchable branches" list. - -It may take some time before the branch is indexed and searchable. - -As this provider is not one of the default providers, you will first need to install -the Azure catalog plugin: - -```bash title="From your Backstage root directory" -yarn --cwd packages/backend add @backstage/plugin-catalog-backend-module-azure -``` - -Once you've done that, you'll also need to add the segment below to `packages/backend/src/plugins/catalog.ts`: - -```ts title="packages/backend/src/plugins/catalog.ts" -/* highlight-add-next-line */ -import { AzureDevOpsEntityProvider } from '@backstage/plugin-catalog-backend-module-azure'; - -const builder = await CatalogBuilder.create(env); -/** ... other processors and/or providers ... */ -/* highlight-add-start */ -builder.addEntityProvider( - AzureDevOpsEntityProvider.fromConfig(env.config, { - logger: env.logger, - // optional: alternatively, use scheduler with schedule defined in app-config.yaml - schedule: env.scheduler.createScheduledTaskRunner({ - frequency: { minutes: 30 }, - timeout: { minutes: 3 }, - }), - // optional: alternatively, use schedule - scheduler: env.scheduler, - }), -); -/* highlight-add-end */ -``` - -## Alternative Processor - -As an alternative to the entity provider `AzureDevOpsEntityProvider`, you can still use the `AzureDevopsDiscoveryProcessor`. - -```ts title="packages/backend/src/plugins/catalog.ts" -/* highlight-add-next-line */ -import { AzureDevOpsDiscoveryProcessor } from '@backstage/plugin-catalog-backend-module-azure'; - -export default async function createPlugin( - env: PluginEnvironment, -): Promise { - const builder = await CatalogBuilder.create(env); - /* highlight-add-next-line */ - builder.addProcessor( - AzureDevOpsDiscoveryProcessor.fromConfig(env.config, { - logger: env.logger, - }), - ); - - // .. -} -``` - -```yaml -catalog: - locations: - # Scan all repositories for a catalog-info.yaml in the root of the default branch - - type: azure-discovery - target: https://dev.azure.com/myorg/myproject - # Or use a custom pattern for a subset of all repositories with default repository - - type: azure-discovery - target: https://dev.azure.com/myorg/myproject/_git/service-* - # Or use a custom file format and location - - type: azure-discovery - target: https://dev.azure.com/myorg/myproject/_git/*?path=/src/*/catalog-info.yaml - # And optionally provide a specific branch name using the version parameter - - type: azure-discovery - target: https://dev.azure.com/myorg/myproject/_git/*?path=/catalog-info.yaml&version=GBtopic/catalog-info -``` - -Note the `azure-discovery` type, as this is not a regular `url` processor. - -When using a custom pattern, the target is composed of these parts: - -- The base instance URL, `https://dev.azure.com` in this case -- The organization name which is required, `myorg` in this case -- The project name which is optional, `myproject` in this case. This defaults to \*, which scans all the projects where the token has access to. -- The repository blob to scan, which accepts \* wildcard tokens and must be - added after `_git/`. This can simply be `*` to scan all repositories in the - project. -- The path within each repository to find the catalog YAML file. This will - usually be `/catalog-info.yaml`, `/src/*/catalog-info.yaml` or a similar - variation for catalog files stored in the root directory of each repository. -- The repository branch to scan which is optional, `topic/catalog-info` in this case. If omitted, the repo's default branch will be scanned. The `GB` prefix is required, as this is how Azure DevOps identifies the version as a branch. diff --git a/docs/integrations/azure/discovery.md b/docs/integrations/azure/discovery.md index 99feb9a806..b749f8e9ed 100644 --- a/docs/integrations/azure/discovery.md +++ b/docs/integrations/azure/discovery.md @@ -7,7 +7,7 @@ description: Automatically discovering catalog entities from repositories in an --- :::info -This documentation is written for [the new backend system](../../backend-system/index.md) which is the default since Backstage [version 1.24](../../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](./discovery--old.md) instead, and [consider migrating](../../backend-system/building-backends/08-migrating.md)! +This documentation is written for [the new backend system](../../backend-system/index.md) which is the default since Backstage [version 1.24](../../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](https://github.com/backstage/backstage/blob/v1.37.0/docs/integrations/azure/discovery--old.md) instead, and [consider migrating](../../backend-system/building-backends/08-migrating.md)! ::: The Azure DevOps integration has a special entity provider for discovering diff --git a/docs/integrations/azure/org--old.md b/docs/integrations/azure/org--old.md deleted file mode 100644 index 899120e88a..0000000000 --- a/docs/integrations/azure/org--old.md +++ /dev/null @@ -1,273 +0,0 @@ ---- -id: org--old -title: Microsoft Entra Tenant Data -sidebar_label: Org Data -# prettier-ignore -description: Importing users and groups from Microsoft Entra ID into Backstage ---- - -:::info -This documentation is written for the old backend which has been replaced by [the new backend system](../../backend-system/index.md), being the default since Backstage [version 1.24](../../releases/v1.24.0.md). If have migrated to the new backend system, you may want to read [its own article](./org.md) instead.Otherwise, [consider migrating](../../backend-system/building-backends/08-migrating.md)! -::: - -The Backstage catalog can be set up to ingest organizational data - users and -teams - directly from a tenant in Microsoft Entra ID via the -Microsoft Graph API. - -## Installation - -The package is not installed by default, therefore you have to add `@backstage/plugin-catalog-backend-module-msgraph` to your backend package. - -```bash title="From your Backstage root directory" -yarn --cwd packages/backend add @backstage/plugin-catalog-backend-module-msgraph -``` - -Next add the basic configuration to `app-config.yaml` - -```yaml title="app-config.yaml" -catalog: - providers: - microsoftGraphOrg: - default: - tenantId: ${AZURE_TENANT_ID} - user: - filter: accountEnabled eq true and userType eq 'member' - group: - filter: > - securityEnabled eq false - and mailEnabled eq true - and groupTypes/any(c:c+eq+'Unified') - schedule: - frequency: PT1H - timeout: PT50M -``` - -Finally, register the plugin in `catalog.ts`. -For large organizations, this plugin can take a long time, so be careful setting low frequency / timeouts and importing a large amount of users / groups for the first try. - -```ts title="packages/backend/src/plugins/catalog.ts" -/* highlight-add-next-line */ -import { MicrosoftGraphOrgEntityProvider } from '@backstage/plugin-catalog-backend-module-msgraph'; - -export default async function createPlugin( - env: PluginEnvironment, -): Promise { - const builder = await CatalogBuilder.create(env); - - /* highlight-add-start */ - builder.addEntityProvider( - MicrosoftGraphOrgEntityProvider.fromConfig(env.config, { - logger: env.logger, - scheduler: env.scheduler, - }), - ); - /* highlight-add-end */ - - // .. -} -``` - -## Authenticating with Microsoft Graph - -### Local Development - -For a local dev environment, it's recommended you have the Azure CLI or Azure PowerShell installed, and are logged in to those. -Alternatively you can use VSCode with the Azure extension if you install `@azure/identity-vscode`. -When these are set up, the plugin will authenticate with the Microsoft Graph API without you needing to configure any credentials, or granting any special permissions. -If you can't do this, you'll have to create an App Registration. - -### App Registration - -If none of the other authentication methods work, you can create an app registration in the azure portal. -By default the graph plugin requires the following Application permissions (not Delegated) for Microsoft Graph: - -- `GroupMember.Read.All` -- `User.Read.All` - -If your organization required Admin Consent for these permissions, that will need to be granted. - -When authenticating with a ClientId/ClientSecret, you can either set the `AZURE_TENANT_ID`, `AZURE_CLIENT_ID` and `AZURE_CLIENT_SECRET` environment variables, or specify the values in configuration - -```yaml -microsoftGraphOrg: - default: - ##... - clientId: 9ef1aac6-b454-4e69-9cf5-7199df049281 - clientSecret: REDACTED -``` - -To authenticate with a certificate rather than a client secret, you can set the `AZURE_TENANT_ID`, `AZURE_CLIENT_ID` and `AZURE_CLIENT_CERTIFICATE_PATH` environments - -### Managed Identity - -If deploying to resources that supports Managed Identity, and has identities configured (e.g. Azure App Services, Azure Container Apps), Managed Identity should be picked up without any additional configuration. -If your app has multiple managed identities, you may need to set the `AZURE_CLIENT_ID` environment variable to tell Azure Identity which identity to use. - -To grant the managed identity the same permissions as mentioned in _App Registration_ above, [please follow this guide](https://docs.microsoft.com/en-us/azure/app-service/tutorial-connect-app-access-microsoft-graph-as-app-javascript?tabs=azure-powershell) - -## Filtering imported Users and Groups - -By default, the plugin will import all users and groups from your directory. -This can be customized through [filters](https://learn.microsoft.com/en-us/graph/filter-query-parameter) and [search](https://learn.microsoft.com/en-us/graph/search-query-parameter) queries. Keep in mind that if you omit filters and search queries for the user or group properties, the plugin will automatically import all available users or groups. - -### Groups - -A smaller set of groups can be obtained by configuring a search query or a filter. -If both `filter` and `search` are provided, then groups must match both to be ingested. - -```yaml -microsoftGraphOrg: - providerId: - group: - filter: securityEnabled eq false and mailEnabled eq true and groupTypes/any(c:c+eq+'Unified') - search: '"description:One" AND ("displayName:Video" OR "displayName:Drive")' -``` - -In addition to these groups, one additional group will be created for your organization. -All imported groups will be a child of this group. - -### Users - -There are two modes for importing users - You can import all user objects matching a `filter`. - -```yaml -microsoftGraphOrg: - providerId: - user: - filter: accountEnabled eq true and userType eq 'member' -``` - -Alternatively you can import users that are members of specific groups. -For each group matching the `search` and `filter` query, each group member will be imported. -Only direct group members will be imported, not transient users. - -```yaml -microsoftGraphOrg: - providerId: - userGroupMember: - filter: "displayName eq 'Backstage Users'" - search: '"description:One" AND ("displayName:Video" OR "displayName:Drive")' -``` - -### User photos - -By default, the photos of users will be fetched and added to each user entity. For huge organizations this may be unfeasible, as it will take a _very_ long time, and can be disabled by setting `loadPhotos` to `false`: - -```yaml -microsoftGraphOrg: - providerId: - user: - filter: ... - loadPhotos: false -``` - -## Customizing Transformation - -Ingested entities can be customized by providing custom transformers. -These can be used to completely replace the built in logic, or used to tweak it by using the default transformers (`defaultGroupTransformer`, `defaultUserTransformer` and `defaultOrganizationTransformer` -Entities can also be excluded from backstage by returning `undefined`. - -These Transformers are be registered when configuring `MicrosoftGraphOrgEntityProvider` - -```ts -builder.addEntityProvider( - MicrosoftGraphOrgEntityProvider.fromConfig(env.config, { - // ... - /* highlight-add-start */ - groupTransformer: myGroupTransformer, - userTransformer: myUserTransformer, - organizationTransformer: myOrganizationTransformer, - /* highlight-add-end */ - }), -); -``` - -When using custom transformers, you may want to customize the data returned. -Several configuration options can be provided to tweak the Microsoft Graph query to get the data you need - -```yaml -microsoftGraphOrg: - providerId: - user: - expand: manager - group: - expand: member - select: ['id', 'displayName', 'description'] -``` - -The following provides an example of each kind of transformer - -```ts -import * as MicrosoftGraph from '@microsoft/microsoft-graph-types'; -import { - defaultGroupTransformer, - defaultUserTransformer, - defaultOrganizationTransformer, -} from '@backstage/plugin-catalog-backend-module-msgraph'; -import { GroupEntity, UserEntity } from '@backstage/catalog-model'; - -// This group transformer completely replaces the built in logic with custom logic. -export async function myGroupTransformer( - group: MicrosoftGraph.Group, - groupPhoto?: string, -): Promise { - return { - apiVersion: 'backstage.io/v1alpha1', - kind: 'Group', - metadata: { - name: group.id!, - annotations: {}, - }, - spec: { - type: 'Microsoft Entra ID', - children: [], - }, - }; -} - -// This user transformer makes use of the built in logic, but also sets the description field -export async function myUserTransformer( - graphUser: MicrosoftGraph.User, - userPhoto?: string, -): Promise { - const backstageUser = await defaultUserTransformer(graphUser, userPhoto); - - if (backstageUser) { - backstageUser.metadata.description = 'Loaded from Microsoft Entra ID'; - } - - return backstageUser; -} - -// Example organization transformer that removes the organization group completely -export async function myOrganizationTransformer( - graphOrganization: MicrosoftGraph.Organization, -): Promise { - return undefined; -} -``` - -## Troubleshooting - -### No data - -First check your logs for the message `Reading msgraph users and groups`. -If you don't see this, check you've registered the provider, and that the schedule is valid - -If you see a log entry `Read 0 msgraph users and 0 msgraph groups`, check your search and filter arguments. - -If you see the start message (`Reading msgraph users and groups`) but no end message (`Read X msgraph users and Y msgraph groups`), then it is likely the job is taking a long time due to a large volume of data. -The default behavior is to import all users and groups, which is often more data than needed. -Try importing a smaller set of data (e.g. `filter: displayName eq 'John Smith'`). - -### Authentication / Token Errors - -See [Troubleshooting Azure Identity Authentication Issues](https://aka.ms/azsdk/js/identity/troubleshoot) - -### Error while reading users from Microsoft Graph: Authorization_RequestDenied - Insufficient privileges to complete the operation - -- Make sure you've granted all the required permissions to your application registration or managed identity -- Make sure the permissions are `Application` permissions rather than `Delegated` -- If your organization has configured "Admin consent" to be required, make sure this has been granted for your application permissions -- If your group queries are returning Microsoft Teams groups, you may need to grant addition permissions (e.g. `Team.ReadBasic.All`, `TeamMember.Read.All`) -- If you've added additional `select` or `expand` fields, those may need additional permissions granted diff --git a/docs/integrations/azure/org.md b/docs/integrations/azure/org.md index 068aa70101..127bc4863b 100644 --- a/docs/integrations/azure/org.md +++ b/docs/integrations/azure/org.md @@ -7,7 +7,7 @@ description: Importing users and groups from Microsoft Entra ID into Backstage --- :::info -This documentation is written for [the new backend system](../../backend-system/index.md) which is the default since Backstage [version 1.24](../../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](./org--old.md) instead, and [consider migrating](../../backend-system/building-backends/08-migrating.md)! +This documentation is written for [the new backend system](../../backend-system/index.md) which is the default since Backstage [version 1.24](../../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](https://github.com/backstage/backstage/blob/v1.37.0/docs/integrations/azure/org--old.md) instead, and [consider migrating](../../backend-system/building-backends/08-migrating.md)! ::: The Backstage catalog can be set up to ingest organizational data - users and diff --git a/docs/integrations/bitbucketServer/discovery--old.md b/docs/integrations/bitbucketServer/discovery--old.md deleted file mode 100644 index 83fe70da0d..0000000000 --- a/docs/integrations/bitbucketServer/discovery--old.md +++ /dev/null @@ -1,123 +0,0 @@ ---- -id: discovery--old -title: Bitbucket Server Discovery -sidebar_label: Discovery -# prettier-ignore -description: Automatically discovering catalog entities from repositories in Bitbucket Server ---- - -:::info -This documentation is written for the old backend which has been replaced by [the new backend system](../../backend-system/index.md), being the default since Backstage [version 1.24](../../releases/v1.24.0.md). If have migrated to the new backend system, you may want to read [its own article](./discovery.md) instead. Otherwise, [consider migrating](../../backend-system/building-backends/08-migrating.md)! -::: - -The Bitbucket Server integration has a special entity provider for discovering -catalog files located in Bitbucket Server. -The provider will search your Bitbucket Server account and register catalog files matching the configured path -as Location entity and via following processing steps add all contained catalog entities. -This can be useful as an alternative to static locations or manually adding things to the catalog. - -## Installation - -You will have to add the entity provider in the catalog initialization code of your -backend. The provider is not installed by default, therefore you have to add a -dependency to `@backstage/plugin-catalog-backend-module-bitbucket-server` to your backend -package. - -```bash -# From your Backstage root directory -yarn --cwd packages/backend add @backstage/plugin-catalog-backend-module-bitbucket-server -``` - -And then add the entity provider to your catalog builder: - -```ts title="packages/backend/src/plugins/catalog.ts" -/* highlight-add-next-line */ -import { BitbucketServerEntityProvider } from '@backstage/plugin-catalog-backend-module-bitbucket-server'; - -export default async function createPlugin( - env: PluginEnvironment, -): Promise { - const builder = await CatalogBuilder.create(env); - /* highlight-add-start */ - builder.addEntityProvider( - BitbucketServerEntityProvider.fromConfig(env.config, { - logger: env.logger, - scheduler: env.scheduler, - }), - ); - /* highlight-add-end */ - - // .. -} -``` - -## Configuration - -To use the entity provider, you'll need a [Bitbucket Server integration set up](locations.md). - -Additionally, you need to configure your entity provider instance(s): - -```yaml title="app-config.yaml" -catalog: - providers: - bitbucketServer: - yourProviderId: # identifies your ingested dataset - host: 'bitbucket.mycompany.com' - catalogPath: /catalog-info.yaml # default value - filters: # optional - projectKey: '^apis-.*$' # optional; RegExp - repoSlug: '^service-.*$' # optional; RegExp - skipArchivedRepos: true # optional; boolean - schedule: # same options as in TaskScheduleDefinition - # supports cron, ISO duration, "human duration" as used in code - frequency: { minutes: 30 } - # supports ISO duration, "human duration" as used in code - timeout: { minutes: 3 } -``` - -- **`host`**: - The host of the Bitbucket Server instance, **note**: the host needs to registered as an integration as well, see [location](locations.md). -- **`catalogPath`** _(optional)_: - Default: `/catalog-info.yaml`. - Path where to look for `catalog-info.yaml` files. - When started with `/`, it is an absolute path from the repo root. -- **`filters`** _(optional)_: - - **`projectKey`** _(optional)_: - Regular expression used to filter results based on the project key. - - **`repoSlug`** _(optional)_: - Regular expression used to filter results based on the repo slug. - - **`skipArchivedRepos`** _(optional)_: - Boolean flag to filter out archived repositories. -- **`schedule`**: - - **`frequency`**: - How often you want the task to run. The system does its best to avoid overlapping invocations. - - **`timeout`**: - The maximum amount of time that a single task invocation can take. - - **`initialDelay`** _(optional)_: - The amount of time that should pass before the first invocation happens. - - **`scope`** _(optional)_: - `'global'` or `'local'`. Sets the scope of concurrency control. - -## Custom location processing - -The Bitbucket Server Entity Provider will by default emit a location for each -matching repository. However, it is possible to override this functionality and take full control of how each -matching repository is processed. - -`BitbucketServerEntityProvider.fromConfig` takes an optional parameter -`options.parser` where you can set your own parser to be used for each matched -repository. - -```typescript -const provider = BitbucketServerEntityProvider.fromConfig(env.config, { - logger: env.logger, - schedule: env.scheduler, - parser: async function* customLocationParser(options: { - location: LocationSpec; - client: BitbucketServerClient; - }) { - // Custom logic for interpreting the matching repository - // See defaultBitbucketServerLocationParser for an example - }, -}); -``` diff --git a/docs/integrations/bitbucketServer/discovery.md b/docs/integrations/bitbucketServer/discovery.md index 7947913182..36282b1f48 100644 --- a/docs/integrations/bitbucketServer/discovery.md +++ b/docs/integrations/bitbucketServer/discovery.md @@ -7,7 +7,7 @@ description: Automatically discovering catalog entities from repositories in Bit --- :::info -This documentation is written for [the new backend system](../../backend-system/index.md) which is the default since Backstage [version 1.24](../../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](./discovery--old.md) instead, and [consider migrating](../../backend-system/building-backends/08-migrating.md)! +This documentation is written for [the new backend system](../../backend-system/index.md) which is the default since Backstage [version 1.24](../../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](https://github.com/backstage/backstage/blob/v1.37.0/docs/integrations/bitbucketServer/discovery--old.md) instead, and [consider migrating](../../backend-system/building-backends/08-migrating.md)! ::: The Bitbucket Server integration has a special entity provider for discovering diff --git a/docs/integrations/gerrit/discovery--old.md b/docs/integrations/gerrit/discovery--old.md deleted file mode 100644 index 250de80bdf..0000000000 --- a/docs/integrations/gerrit/discovery--old.md +++ /dev/null @@ -1,73 +0,0 @@ ---- -id: discovery--old -title: Gerrit Discovery -sidebar_label: Discovery -# prettier-ignore -description: Automatically discovering catalog entities from Gerrit repositories ---- - -:::info -This documentation is written for the old backend which has been replaced by [the new backend system](../../backend-system/index.md), being the default since Backstage [version 1.24](../../releases/v1.24.0.md). If have migrated to the new backend system, you may want to read [its own article](./discovery.md) instead. Otherwise, [consider migrating](../../backend-system/building-backends/08-migrating.md)! -::: - -The Gerrit integration has a special entity provider for discovering catalog entities -from Gerrit repositories. The provider uses the "List Projects" API in Gerrit to get -a list of repositories and will automatically ingest all `catalog-info.yaml` files -stored in the root of the matching projects. - -## Installation - -As this provider is not one of the default providers, you will first need to install -the Gerrit provider plugin: - -```bash title="From your Backstage root directory" -yarn --cwd packages/backend add @backstage/plugin-catalog-backend-module-gerrit -``` - -Then add the plugin to the plugin catalog `packages/backend/src/plugins/catalog.ts`: - -```ts -/* packages/backend/src/plugins/catalog.ts */ -import { GerritEntityProvider } from '@backstage/plugin-catalog-backend-module-gerrit'; -const builder = await CatalogBuilder.create(env); -/** ... other processors and/or providers ... */ -builder.addEntityProvider( - GerritEntityProvider.fromConfig(env.config, { - logger: env.logger, - scheduler: env.scheduler, - }), -); -``` - -## Configuration - -To use the discovery processor, you'll need a Gerrit integration -[set up](locations.md). Then you can add any number of providers. - -```yaml -# app-config.yaml -catalog: - providers: - gerrit: - yourProviderId: # identifies your dataset / provider independent of config changes - host: gerrit-your-company.com - branch: master # Optional - query: 'state=ACTIVE&prefix=webapps' - schedule: - # supports cron, ISO duration, "human duration" as used in code - frequency: { minutes: 30 } - # supports ISO duration, "human duration" as used in code - timeout: { minutes: 3 } - backend: - host: gerrit-your-company.com - branch: master # Optional - query: 'state=ACTIVE&prefix=backend' -``` - -The provider configuration is composed of three parts: - -- **`host`**: the host of the Gerrit integration to use. -- **`branch`** _(optional)_: the branch where we will look for catalog entities (defaults to "master"). -- **`query`**: this string is directly used as the argument to the "List Project" API. - Typically, you will want to have some filter here to exclude projects that will - never contain any catalog files. diff --git a/docs/integrations/gerrit/discovery.md b/docs/integrations/gerrit/discovery.md index 137a716f35..dbbc7ba7a4 100644 --- a/docs/integrations/gerrit/discovery.md +++ b/docs/integrations/gerrit/discovery.md @@ -7,7 +7,7 @@ description: Automatically discovering catalog entities from Gerrit repositories --- :::info -This documentation is written for [the new backend system](../../backend-system/index.md) which is the default since Backstage [version 1.24](../../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](./discovery--old.md) instead, and [consider migrating](../../backend-system/building-backends/08-migrating.md)! +This documentation is written for [the new backend system](../../backend-system/index.md) which is the default since Backstage [version 1.24](../../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](https://github.com/backstage/backstage/blob/v1.37.0/docs/integrations/gerrit/discovery--old.md) instead, and [consider migrating](../../backend-system/building-backends/08-migrating.md)! ::: The Gerrit integration has a special entity provider for discovering catalog entities diff --git a/docs/integrations/github/discovery--old.md b/docs/integrations/github/discovery--old.md deleted file mode 100644 index 901462b1cb..0000000000 --- a/docs/integrations/github/discovery--old.md +++ /dev/null @@ -1,375 +0,0 @@ ---- -id: discovery--old -title: GitHub Discovery -sidebar_label: Discovery -# prettier-ignore -description: Automatically discovering catalog entities from repositories in a GitHub organization ---- - -:::info -This documentation is written for the old backend which has been replaced by [the new backend system](../../backend-system/index.md), being the default since Backstage [version 1.24](../../releases/v1.24.0.md). If have migrated to the new backend system, you may want to read [its own article](./discovery.md) instead. Otherwise, [consider migrating](../../backend-system/building-backends/08-migrating.md)! -::: - -## GitHub Provider - -The GitHub integration has a discovery provider for discovering catalog -entities within a GitHub organization. The provider will crawl the GitHub -organization and register entities matching the configured path. This can be -useful as an alternative to static locations or manually adding things to the -catalog. This is the preferred method for ingesting entities into the catalog. - -## Installation without Events Support - -You will have to add the provider in the catalog initialization code of your -backend. They are not installed by default, therefore you have to add a -dependency on `@backstage/plugin-catalog-backend-module-github` to your backend -package. - -```bash title="From your Backstage root directory" -yarn --cwd packages/backend add @backstage/plugin-catalog-backend-module-github -``` - -And then add the entity provider to your catalog builder: - -```ts title="packages/backend/src/plugins/catalog.ts" -/* highlight-add-next-line */ -import { GithubEntityProvider } from '@backstage/plugin-catalog-backend-module-github'; - -export default async function createPlugin( - env: PluginEnvironment, -): Promise { - const builder = await CatalogBuilder.create(env); - /* highlight-add-start */ - builder.addEntityProvider( - GithubEntityProvider.fromConfig(env.config, { - logger: env.logger, - scheduler: env.scheduler, - }), - ); - /* highlight-add-end */ - - // .. -} -``` - -## Installation with Events Support - -_For the legacy backend system, please read the sub-section below._ - -The catalog module for GitHub comes with events support enabled. -This will make it subscribe to its relevant topics (`github.push`) -and expects these events to be published via the `EventsService`. - -Additionally, you should install the -[event router by `events-backend-module-github`](https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-github/README.md) -which will route received events from the generic topic `github` to more specific ones -based on the event type (e.g., `github.push`). - -In order to receive Webhook events by GitHub, you have to decide how you want them -to be ingested into Backstage and published to its `EventsService`. -You can decide between the following options (extensible): - -- [via HTTP endpoint](https://github.com/backstage/backstage/tree/master/plugins/events-backend/README.md) -- [via an AWS SQS queue](https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-aws-sqs/README.md) - -### Legacy Backend System - -Please follow the installation instructions at - -- -- - -Additionally, you need to decide how you want to receive events from external sources like - -- [via HTTP endpoint](https://github.com/backstage/backstage/tree/master/plugins/events-backend/README.md) -- [via an AWS SQS queue](https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-aws-sqs/README.md) - -Set up your provider - -```ts title="packages/backend/src/plugins/catalog.ts" -import { CatalogBuilder } from '@backstage/plugin-catalog-backend'; -/* highlight-add-next-line */ -import { GithubEntityProvider } from '@backstage/plugin-catalog-backend-module-github'; -import { ScaffolderEntitiesProcessor } from '@backstage/plugin-scaffolder-backend'; -import { Router } from 'express'; -import { PluginEnvironment } from '../types'; - -export default async function createPlugin( - env: PluginEnvironment, -): Promise { - const builder = await CatalogBuilder.create(env); - builder.addProcessor(new ScaffolderEntitiesProcessor()); - /* highlight-add-start */ - const githubProvider = GithubEntityProvider.fromConfig(env.config, { - events: env.events, - logger: env.logger, - scheduler: env.scheduler, - }); - builder.addEntityProvider(githubProvider); - /* highlight-add-end */ - const { processingEngine, router } = await builder.build(); - await processingEngine.start(); - return router; -} -``` - -You can check the official docs to [configure your webhook](https://docs.github.com/en/developers/webhooks-and-events/webhooks/creating-webhooks) and to [secure your request](https://docs.github.com/en/developers/webhooks-and-events/webhooks/securing-your-webhooks). The webhook will need to be configured to forward `push` events. - -## Configuration - -To use the discovery provider, you'll need a GitHub integration -[set up](locations.md) with either a [Personal Access Token](../../getting-started/config/authentication.md) or [GitHub Apps](./github-apps.md). For Personal Access Tokens you should pay attention to the [required scopes](https://backstage.io/docs/integrations/github/locations/#token-scopes), where you will need at least the `repo` scope for reading components. For GitHub Apps you will need to grant it the [required permissions](https://backstage.io/docs/integrations/github/github-apps#app-permissions) instead, where you will need at least the `Contents: Read-only` permissions for reading components. - -Then you can add a `github` config to the catalog providers configuration: - -```yaml -catalog: - providers: - github: - # the provider ID can be any camelCase string - providerId: - organization: 'backstage' # string - catalogPath: '/catalog-info.yaml' # string - filters: - branch: 'main' # string - repository: '.*' # Regex - schedule: # same options as in SchedulerServiceTaskScheduleDefinition - # supports cron, ISO duration, "human duration" as used in code - frequency: { minutes: 30 } - # supports ISO duration, "human duration" as used in code - timeout: { minutes: 3 } - customProviderId: - organization: 'new-org' # string - catalogPath: '/custom/path/catalog-info.yaml' # string - filters: # optional filters - branch: 'develop' # optional string - repository: '.*' # optional Regex - wildcardProviderId: - organization: 'new-org' # string - catalogPath: '/groups/**/*.yaml' # this will search all folders for files that end in .yaml - filters: # optional filters - branch: 'develop' # optional string - repository: '.*' # optional Regex - topicProviderId: - organization: 'backstage' # string - catalogPath: '/catalog-info.yaml' # string - filters: - branch: 'main' # string - repository: '.*' # Regex - topic: 'backstage-exclude' # optional string - topicFilterProviderId: - organization: 'backstage' # string - catalogPath: '/catalog-info.yaml' # string - filters: - branch: 'main' # string - repository: '.*' # Regex - topic: - include: ['backstage-include'] # optional array of strings - exclude: ['experiments'] # optional array of strings - validateLocationsExist: - organization: 'backstage' # string - catalogPath: '/catalog-info.yaml' # string - filters: - branch: 'main' # string - repository: '.*' # Regex - validateLocationsExist: true # optional boolean - visibilityProviderId: - organization: 'backstage' # string - catalogPath: '/catalog-info.yaml' # string - filters: - visibility: - - public - - internal - enterpriseProviderId: - host: ghe.example.net - organization: 'backstage' # string - catalogPath: '/catalog-info.yaml' # string -``` - -This provider supports multiple organizations via unique provider IDs. - -> **Note:** It is possible but certainly not recommended to skip the provider ID level. -> If you do so, `default` will be used as provider ID. - -- **`catalogPath`** _(optional)_: - Default: `/catalog-info.yaml`. - Path where to look for `catalog-info.yaml` files. - You can use wildcards - `*` or `**` - to search the path and/or the filename. - Wildcards cannot be used if the `validateLocationsExist` option is set to `true`. -- **`filters`** _(optional)_: - - **`branch`** _(optional)_: - String used to filter results based on the branch name. - Defaults to the default Branch of the repository. - - **`repository`** _(optional)_: - Regular expression used to filter results based on the repository name. - - **`topic`** _(optional)_: - Both of the filters below may be used at the same time but the exclusion filter has the highest priority. - In the example above, a repository with the `backstage-include` topic would still be excluded - if it were also carrying the `experiments` topic. - - **`include`** _(optional)_: - An array of strings used to filter in results based on their associated GitHub topics. - If configured, only repositories with one (or more) topic(s) present in the inclusion filter will be ingested - - **`exclude`** _(optional)_: - An array of strings used to filter out results based on their associated GitHub topics. - If configured, all repositories _except_ those with one (or more) topics(s) present in the exclusion filter will be ingested. - - **`visibility`** _(optional)_: - An array of strings used to filter results based on their visibility. Available options are `private`, `internal`, `public`. If configured (non empty), only repositories with visibility present in the filter will be ingested -- **`host`** _(optional)_: - The hostname of your GitHub Enterprise instance. It must match a host defined in [integrations.github](locations.md). -- **`organization`**: - Name of your organization account/workspace. - If you want to add multiple organizations, you need to add one provider config each. -- **`validateLocationsExist`** _(optional)_: - Whether to validate locations that exist before emitting them. - This option avoids generating locations for catalog info files that do not exist in the source repository. - Defaults to `false`. - Due to limitations in the GitHub API's ability to query for repository objects, this option cannot be used in - conjunction with wildcards in the `catalogPath`. -- **`schedule`**: - - **`frequency`**: - How often you want the task to run. The system does its best to avoid overlapping invocations. - - **`timeout`**: - The maximum amount of time that a single task invocation can take. - - **`initialDelay`** _(optional)_: - The amount of time that should pass before the first invocation happens. - - **`scope`** _(optional)_: - `'global'` or `'local'`. Sets the scope of concurrency control. - -## GitHub API Rate Limits - -GitHub [rate limits](https://docs.github.com/en/rest/overview/resources-in-the-rest-api#rate-limiting) API requests to 5,000 per hour (or more for Enterprise -accounts). The snippet below refreshes the Backstage catalog data every 35 minutes, which issues an API request for each discovered location. - -If your requests are too frequent then you may get throttled by -rate limiting. You can change the refresh frequency of the catalog in your `app-config.yaml` file by controlling the `schedule`. - -```yaml -schedule: - frequency: { minutes: 35 } - timeout: { minutes: 3 } -``` - -More information about scheduling can be found on the [SchedulerServiceTaskScheduleDefinition](https://backstage.io/docs/reference/backend-plugin-api.schedulerservicetaskscheduledefinition) page. - -Alternatively, or additionally, you can configure [github-apps](github-apps.md) authentication -which carries a much higher rate limit at GitHub. - -This is true for any method of adding GitHub entities to the catalog, but -especially easy to hit with automatic discovery. - -## GitHub Processor (To Be Deprecated) - -The GitHub integration has a special discovery processor for discovering catalog -entities within a GitHub organization. The processor will crawl the GitHub -organization and register entities matching the configured path. This can be -useful as an alternative to static locations or manually adding things to the -catalog. - -## Installation - -You will have to add the processors in the catalog initialization code of your -backend. They are not installed by default, therefore you have to add a -dependency on `@backstage/plugin-catalog-backend-module-github` to your backend -package, plus `@backstage/integration` for the basic credentials management: - -```bash title="From your Backstage root directory" -yarn --cwd packages/backend add @backstage/integration @backstage/plugin-catalog-backend-module-github -``` - -And then add the processors to your catalog builder: - -```ts title="packages/backend/src/plugins/catalog.ts" -/* highlight-add-start */ -import { - GithubDiscoveryProcessor, - GithubOrgReaderProcessor, -} from '@backstage/plugin-catalog-backend-module-github'; -import { - ScmIntegrations, - DefaultGithubCredentialsProvider, -} from '@backstage/integration'; -/* highlight-add-end */ - -export default async function createPlugin( - env: PluginEnvironment, -): Promise { - const builder = await CatalogBuilder.create(env); - /* highlight-add-start */ - const integrations = ScmIntegrations.fromConfig(env.config); - const githubCredentialsProvider = - DefaultGithubCredentialsProvider.fromIntegrations(integrations); - builder.addProcessor( - GithubDiscoveryProcessor.fromConfig(env.config, { - logger: env.logger, - githubCredentialsProvider, - }), - GithubOrgReaderProcessor.fromConfig(env.config, { - logger: env.logger, - githubCredentialsProvider, - }), - ); - /* highlight-add-end */ - - // .. -} -``` - -## Configuration - -To use the discovery processor, you'll need a GitHub integration -[set up](locations.md) with either a [Personal Access Token](../../getting-started/config/authentication.md) or [GitHub Apps](./github-apps.md). - -Then you can add a location target to the catalog configuration: - -```yaml -catalog: - locations: - # (since 0.13.5) Scan all repositories for a catalog-info.yaml in the root of the default branch - - type: github-discovery - target: https://github.com/myorg - # Or use a custom pattern for a subset of all repositories with default repository - - type: github-discovery - target: https://github.com/myorg/service-*/blob/-/catalog-info.yaml - # Or use a custom file format and location - - type: github-discovery - target: https://github.com/*/blob/-/docs/your-own-format.yaml - # Or use a specific branch-name - - type: github-discovery - target: https://github.com/*/blob/backstage-docs/catalog-info.yaml -``` - -Note the `github-discovery` type, as this is not a regular `url` processor. - -When using a custom pattern, the target is composed of three parts: - -- The base organization URL, `https://github.com/myorg` in this case -- The repository blob to scan, which accepts \* wildcard tokens. This can simply - be `*` to scan all repositories in the organization. This example only looks - for repositories prefixed with `service-`. -- The path within each repository to find the catalog YAML file. This will - usually be `/blob/main/catalog-info.yaml`, `/blob/master/catalog-info.yaml` or - a similar variation for catalog files stored in the root directory of each - repository. You could also use a dash (`-`) for referring to the default - branch. - -## GitHub API Rate Limits - -GitHub [rate limits](https://docs.github.com/en/rest/overview/resources-in-the-rest-api#rate-limiting) API requests to 5,000 per hour (or more for Enterprise -accounts). The default Backstage catalog backend refreshes data every 100 -seconds, which issues an API request for each discovered location. - -This means if you have more than ~140 catalog entities, you may get throttled by -rate limiting. You can change the refresh rate of the catalog in your `packages/backend/src/plugins/catalog.ts` file: - -```typescript -const builder = await CatalogBuilder.create(env); - -// For example, to refresh every 5 minutes (300 seconds). -builder.setProcessingIntervalSeconds(300); -``` - -Alternatively, or additionally, you can configure [github-apps](github-apps.md) authentication -which carries a much higher rate limit at GitHub. - -This is true for any method of adding GitHub entities to the catalog, but -especially easy to hit with automatic discovery. diff --git a/docs/integrations/github/discovery.md b/docs/integrations/github/discovery.md index e2513cdf21..354569cdb6 100644 --- a/docs/integrations/github/discovery.md +++ b/docs/integrations/github/discovery.md @@ -7,7 +7,7 @@ description: Automatically discovering catalog entities from repositories in a G --- :::info -This documentation is written for [the new backend system](../../backend-system/index.md) which is the default since Backstage [version 1.24](../../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](./discovery--old.md) instead, and [consider migrating](../../backend-system/building-backends/08-migrating.md)! +This documentation is written for [the new backend system](../../backend-system/index.md) which is the default since Backstage [version 1.24](../../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](https://github.com/backstage/backstage/blob/v1.37.0/docs/integrations/github/discovery--old.md) instead, and [consider migrating](../../backend-system/building-backends/08-migrating.md)! ::: ## GitHub Provider diff --git a/docs/integrations/github/org--old.md b/docs/integrations/github/org--old.md deleted file mode 100644 index bf12cad036..0000000000 --- a/docs/integrations/github/org--old.md +++ /dev/null @@ -1,365 +0,0 @@ ---- -id: org--old -title: GitHub Organizational Data -sidebar_label: Org Data -# prettier-ignore -description: Importing users and groups from a GitHub organization into Backstage ---- - -:::info -This documentation is written for the old backend which has been replaced by [the new backend system](../../backend-system/index.md), being the default since Backstage [version 1.24](../../releases/v1.24.0.md). If have migrated to the new backend system, you may want to read [its own article](./org.md) instead.Otherwise, [consider migrating](../../backend-system/building-backends/08-migrating.md)! -::: - -The Backstage catalog can be set up to ingest organizational data - users and -teams - directly from an organization in GitHub or GitHub Enterprise. The result -is a hierarchy of -[`User`](../../features/software-catalog/descriptor-format.md#kind-user) and -[`Group`](../../features/software-catalog/descriptor-format.md#kind-group) kind -entities that mirror your org setup. - -> Note: This adds `User` and `Group` entities to the catalog, but does not -> provide authentication. See the -> [GitHub auth provider](../../auth/github/provider.md) for that. - -## Installation without Events Support - -This guide will use the Entity Provider method. If you for some reason prefer -the Processor method (not recommended), it is described separately below. - -The provider is not installed by default, therefore you have to add a dependency -to `@backstage/plugin-catalog-backend-module-github` to your backend package. - -```bash title="From your Backstage root directory" -yarn --cwd packages/backend add @backstage/plugin-catalog-backend-module-github -``` - -> Note: When configuring to use a Provider instead of a Processor you do not -> need to add a _location_ pointing to your GitHub server/organization - -Update the catalog plugin initialization in your backend to add the provider and -schedule it: - -```ts title="packages/backend/src/plugins/catalog.ts" -/* highlight-add-next-line */ -import { GithubOrgEntityProvider } from '@backstage/plugin-catalog-backend-module-github'; - -export default async function createPlugin( - env: PluginEnvironment, -): Promise { - const builder = await CatalogBuilder.create(env); - - /* highlight-add-start */ - // The org URL below needs to match a configured integrations.github entry - // specified in your app-config. - builder.addEntityProvider( - GithubOrgEntityProvider.fromConfig(env.config, { - id: 'production', - orgUrl: 'https://github.com/backstage', - logger: env.logger, - schedule: env.scheduler.createScheduledTaskRunner({ - frequency: { minutes: 60 }, - timeout: { minutes: 15 }, - }), - }), - ); - /* highlight-add-end */ - - // .. -} -``` - -Alternatively, if you wish to ingest data from multiple GitHub organizations you can use -the `GithubMultiOrgEntityProvider` instead. Note that by default, this provider will namespace -groups according to the org they originate from to avoid potential name duplicates: - -```ts title="packages/backend/src/plugins/catalog.ts" -/* highlight-add-next-line */ -import { GithubMultiOrgEntityProvider } from '@backstage/plugin-catalog-backend-module-github'; - -export default async function createPlugin( - env: PluginEnvironment, -): Promise { - const builder = await CatalogBuilder.create(env); - - /* highlight-add-start */ - // The GitHub URL below needs to match a configured integrations.github entry - // specified in your app-config. - builder.addEntityProvider( - GithubMultiOrgEntityProvider.fromConfig(env.config, { - id: 'production', - githubUrl: 'https://github.com', - // Set the following to list the GitHub orgs you wish to ingest from. You can - // also omit this option to ingest all orgs accessible by your GitHub integration - orgs: ['org-a', 'org-b'], - logger: env.logger, - schedule: env.scheduler.createScheduledTaskRunner({ - frequency: { minutes: 60 }, - timeout: { minutes: 15 }, - }), - }), - ); - /* highlight-add-end */ - - // .. -} -``` - -## Installation with Events Support - -_For the legacy backend system, please read the subsection below._ - -The catalog module `github-org` comes with events support enabled for the `GithubMultiOrgEntityProvider`. -This will make it subscribe to its relevant topics and expects these events to be published via the `EventsService`. - -Topics: - -- `github.installation` -- `github.membership` -- `github.organization` -- `github.team` - -Additionally, you should install the -[event router by `events-backend-module-github`](https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-github/README.md) -which will route received events from the generic topic `github` to more specific ones -based on the event type (e.g., `github.membership`). - -In order to receive Webhook events by GitHub, you have to decide how you want them -to be ingested into Backstage and published to its `EventsService`. -You can decide between the following options (extensible): - -- [via HTTP endpoint](https://github.com/backstage/backstage/tree/master/plugins/events-backend/README.md) -- [via an AWS SQS queue](https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-aws-sqs/README.md) - -### Legacy Backend System - -Please follow the installation instructions at - -- -- - -Additionally, you need to decide how you want to receive events from external sources like - -- [via HTTP endpoint](https://github.com/backstage/backstage/tree/master/plugins/events-backend/README.md) -- [via an AWS SQS queue](https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-aws-sqs/README.md) - -Set up your provider - -```ts title="packages/backend/src/plugins/catalog.ts" -import { CatalogBuilder } from '@backstage/plugin-catalog-backend'; -/* highlight-add-next-line */ -import { GithubOrgEntityProvider } from '@backstage/plugin-catalog-backend-module-github'; -import { ScaffolderEntitiesProcessor } from '@backstage/plugin-scaffolder-backend'; -import { Router } from 'express'; -import { PluginEnvironment } from '../types'; - -export default async function createPlugin( - env: PluginEnvironment, -): Promise { - const builder = await CatalogBuilder.create(env); - builder.addProcessor(new ScaffolderEntitiesProcessor()); - /* highlight-add-start */ - const githubOrgProvider = GithubOrgEntityProvider.fromConfig(env.config, { - id: 'production', - orgUrl: 'https://github.com/backstage', - logger: env.logger, - events: env.events, - schedule: env.scheduler.createScheduledTaskRunner({ - frequency: { minutes: 60 }, - timeout: { minutes: 15 }, - }), - }); - builder.addEntityProvider(githubOrgProvider); - /* highlight-add-end */ - const { processingEngine, router } = await builder.build(); - await processingEngine.start(); - return router; -} -``` - -Or, alternatively, if using the `GithubMultiOrgEntityProvider`: - -```ts title="packages/backend/src/plugins/catalog.ts" -/* highlight-add-next-line */ -import { GithubMultiOrgEntityProvider } from '@backstage/plugin-catalog-backend-module-github'; - -export default async function createPlugin( - env: PluginEnvironment, -): Promise { - const builder = await CatalogBuilder.create(env); - - /* highlight-add-start */ - // The GitHub URL below needs to match a configured integrations.github entry - // specified in your app-config. - builder.addEntityProvider( - GithubMultiOrgEntityProvider.fromConfig(env.config, { - id: 'production', - githubUrl: 'https://github.com', - // Set the following to list the GitHub orgs you wish to ingest from. You can - // also omit this option to ingest all orgs accessible by your GitHub integration - orgs: ['org-a', 'org-b'], - logger: env.logger, - events: env.events, - schedule: env.scheduler.createScheduledTaskRunner({ - frequency: { minutes: 60 }, - timeout: { minutes: 15 }, - }), - }), - ); - /* highlight-add-end */ - - // .. -} -``` - -You can check the official docs to [configure your webhook](https://docs.github.com/en/developers/webhooks-and-events/webhooks/creating-webhooks) and to [secure your request](https://docs.github.com/en/developers/webhooks-and-events/webhooks/securing-your-webhooks). -The webhook will need to be configured to forward `organization`,`team` and `membership` events. - -## Configuration - -As mentioned above, you also must have some configuration in your app-config -that describes the targets that you want to import. This lets the entity -provider know what authorization to use, and what the API endpoints are. You may -or may not have such an entry already added since before: - -```yaml -integrations: - github: - # example for public github - - host: github.com - token: ${GITHUB_TOKEN} - # example for a private GitHub Enterprise instance - - host: ghe.example.net - apiBaseUrl: https://ghe.example.net/api/v3 - token: ${GHE_TOKEN} -``` - -These examples use `${}` placeholders to reference environment variables. This -is often suitable for production setups, but also means that you will have to -supply those variables to the backend as it starts up. If you want, for local -development in particular, you can experiment first by putting the actual tokens -in a mirrored config directly in your `app-config.local.yaml` as well. - -If Backstage is configured to use GitHub Apps authentication you must grant -`Read-Only` access for `Members` under `Organization` in order to ingest users -correctly. You can modify the app's permissions under the organization settings, -`https://github.com/organizations/{ORG}/settings/apps/{APP_NAME}/permissions`. - -![permissions](../../assets/integrations/github/permissions.png) - -**Please note that when you change permissions, the app owner will get an email -that must be approved first before the changes are applied.** - -![email](../../assets/integrations/github/email.png) - -### Custom Transformers - -You can inject your own transformation logic to help map from GH API responses -into backstage entities. You can do this on the user and team requests to -enable you to do further processing or updates to the entities. - -To enable this you pass a function into the `GitHubOrgEntityProvider`. You can -pass a `UserTransformer`, `TeamTransformer` or both. The function is invoked -for each item (user or team) that is returned from the API. You can either -return an Entity (User or Group) or `undefined` if you do not want to import -that item. - -There is also a `defaultUserTransformer` and `defaultOrganizationTeamTransformer`. -You could use these and simply decorate the response from the default -transformation if you only need to change a few properties. - -### Resolving GitHub users via organization email - -When you authenticate users you should resolve them to an entity within the -catalog. Often the authentication you use could be a corporate SSO system that -provides you with email as a key. To enable you to find and resolve GitHub users -it's useful to also import the private domain verified emails into the User -entity in backstage. - -The integration attempts to return `organizationVerifiedDomainEmails` from the -GitHub API and makes this available as part of the object passed to -`UserTransformer`. The GitHub API will only return emails that use a domain -that's a verified domain for your GitHub Org. It also relies on the user having -configured such an email in their own account. The API will only return these -values when using GitHub App authentication and with the correct app permission -allowing access to emails. - -You can decorate the default `userTransformer` to replace the org email in the -returned identity. - -```ts title="packages/backend/src/plugins/catalog.ts" -const githubOrgProvider = GithubOrgEntityProvider.fromConfig(env.config, { - id: 'production', - orgUrl: 'https://github.com/backstage', - logger: env.logger, - schedule: env.scheduler.createScheduledTaskRunner({ - frequency: { minutes: 60 }, - timeout: { minutes: 15 }, - }), - /* highlight-add-start */ - userTransformer: async (user, ctx) => { - const entity = await defaultUserTransformer(user, ctx); - if (entity && user.organizationVerifiedDomainEmails?.length) { - entity.spec.profile!.email = user.organizationVerifiedDomainEmails[0]; - } - return entity; - }, - /* highlight-add-end */ -}); -``` - -Once you have imported the emails you can resolve users in your [sign-in resolver](../../auth/github/provider.md) using the catalog entity search via email - -```typescript title="packages/backend/src/plugins/auth.ts" -ctx.signInWithCatalogUser({ - filter: { - kind: ['User'], - 'spec.profile.email': email as string, - }, -}); -``` - -## Using a Processor instead of a Provider - -An alternative to using the Provider for ingesting organizational entities is to -use a Processor. This is the old way that's based on registering locations with -the proper type and target, triggering the processor to run. - -The drawback of this method is that it will leave orphaned Group/User entities -whenever they are deleted on your GitHub server, and you cannot control the -frequency with which they are refreshed, separately from other processors. - -### Processor Installation - -The `GithubOrgReaderProcessor` is not registered by default, so you have to -install and register it in the catalog plugin: - -```bash title="From your Backstage root directory" -yarn --cwd packages/backend add @backstage/plugin-catalog-backend-module-github -``` - -```typescript title="packages/backend/src/plugins/catalog.ts" -import { GithubOrgReaderProcessor } from '@backstage/plugin-catalog-backend-module-github'; - -builder.addProcessor( - GithubOrgReaderProcessor.fromConfig(env.config, { logger: env.logger }), -); -``` - -### Processor Configuration - -The integration section of your app-config needs to be set up in the same way as -for the Entity Provider - see above. - -In addition to that, you typically want to add a few static locations to your -app-config, which reference your organizations to import. The following -configuration enables an import of the teams and users under the org -`https://github.com/my-org-name` on public GitHub. - -```yaml -catalog: - locations: - - type: github-org - target: https://github.com/my-org-name - rules: - - allow: [User, Group] -``` diff --git a/docs/integrations/github/org.md b/docs/integrations/github/org.md index 7f0ee1a71f..3d0e0a4221 100644 --- a/docs/integrations/github/org.md +++ b/docs/integrations/github/org.md @@ -7,7 +7,7 @@ description: Importing users and groups from a GitHub organization into Backstag --- :::info -This documentation is written for [the new backend system](../../backend-system/index.md) which is the default since Backstage [version 1.24](../../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](./org--old.md) instead, and [consider migrating](../../backend-system/building-backends/08-migrating.md)! +This documentation is written for [the new backend system](../../backend-system/index.md) which is the default since Backstage [version 1.24](../../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](https://github.com/backstage/backstage/blob/v1.37.0/docs/integrations/github/org--old.md) instead, and [consider migrating](../../backend-system/building-backends/08-migrating.md)! ::: The Backstage catalog can be set up to ingest organizational data - users and diff --git a/docs/permissions/custom-rules--old.md b/docs/permissions/custom-rules--old.md deleted file mode 100644 index 7661e23327..0000000000 --- a/docs/permissions/custom-rules--old.md +++ /dev/null @@ -1,165 +0,0 @@ ---- -id: custom-rules--old -title: Defining custom permission rules -description: How to define custom permission rules for existing resources ---- - -:::info -This documentation is written for the old backend which has been replaced by [the new backend system](../backend-system/index.md), being the default since Backstage [version 1.24](../releases/v1.24.0.md). If have migrated to the new backend system, you may want to read [its own article](./custom-rules.md) instead. Otherwise, [consider migrating](../backend-system/building-backends/08-migrating.md)! -::: - -For some use cases, you may want to define custom [rules](../references/glossary.md#rule-permission-plugin) in addition to the ones provided by a plugin. In the [previous section](./writing-a-policy.md) we used the `isEntityOwner` rule to control access for catalog entities. Let's extend this policy with a custom rule that checks what [system](https://backstage.io/docs/features/software-catalog/system-model#system) an entity is part of. - -## Define a custom rule - -Plugins should export a rule factory that provides type-safety that ensures compatibility with the plugin's backend. The catalog plugin exports `createCatalogPermissionRule` from `@backstage/plugin-catalog-backend/alpha` for this purpose. Note: the `/alpha` path segment is temporary until this API is marked as stable. For this example, we'll define the rule and create a condition in `packages/backend/src/plugins/permission.ts`. - -We use Zod in our example below. To install, run: - -```bash -yarn workspace backend add zod -``` - -```typescript title="packages/backend/src/plugins/permission.ts" -... - -import type { Entity } from '@backstage/catalog-model'; -import { createCatalogPermissionRule } from '@backstage/plugin-catalog-backend/alpha'; -import { createConditionFactory } from '@backstage/plugin-permission-node'; -import { z } from 'zod'; - -export const isInSystemRule = createCatalogPermissionRule({ - name: 'IS_IN_SYSTEM', - description: 'Checks if an entity is part of the system provided', - resourceType: 'catalog-entity', - paramsSchema: z.object({ - systemRef: z - .string() - .describe('SystemRef to check the resource is part of'), - }), - apply: (resource: Entity, { systemRef }) => { - if (!resource.relations) { - return false; - } - - return resource.relations - .filter(relation => relation.type === 'partOf') - .some(relation => relation.targetRef === systemRef); - }, - toQuery: ({ systemRef }) => ({ - key: 'relations.partOf', - values: [systemRef], - }), -}); - -const isInSystem = createConditionFactory(isInSystemRule); - -... -``` - -For a more detailed explanation on defining rules, refer to the [documentation for plugin authors](./plugin-authors/03-adding-a-resource-permission-check.md#adding-support-for-conditional-decisions). - -Still in the `packages/backend/src/plugins/permission.ts` file, let's use the condition we just created in our `TestPermissionPolicy`. - -```ts title="packages/backend/src/plugins/permission.ts" -... -/* highlight-remove-next-line */ -import { createCatalogPermissionRule } from '@backstage/plugin-catalog-backend/alpha'; -/* highlight-add-next-line */ -import { catalogConditions, createCatalogConditionalDecision, createCatalogPermissionRule } from '@backstage/plugin-catalog-backend/alpha'; -/* highlight-remove-next-line */ -import { createConditionFactory } from '@backstage/plugin-permission-node'; -/* highlight-add-next-line */ -import { PermissionPolicy, PolicyQuery, PolicyQueryUser, createConditionFactory } from '@backstage/plugin-permission-node'; -/* highlight-add-start */ -import { AuthorizeResult, PolicyDecision, isResourcePermission } from '@backstage/plugin-permission-common'; -/* highlight-add-end */ -... - -export const isInSystemRule = createCatalogPermissionRule({ - name: 'IS_IN_SYSTEM', - description: 'Checks if an entity is part of the system provided', - resourceType: 'catalog-entity', - paramsSchema: z.object({ - systemRef: z - .string() - .describe('SystemRef to check the resource is part of'), - }), - apply: (resource: Entity, { systemRef }) => { - if (!resource.relations) { - return false; - } - - return resource.relations - .filter(relation => relation.type === 'partOf') - .some(relation => relation.targetRef === systemRef); - }, - toQuery: ({ systemRef }) => ({ - key: 'relations.partOf', - values: [systemRef], - }), -}); - -const isInSystem = createConditionFactory(isInSystemRule); - -class TestPermissionPolicy implements PermissionPolicy { - async handle( - request: PolicyQuery, - user?: PolicyQueryUser, - ): Promise { - if (isResourcePermission(request.permission, 'catalog-entity')) { - return createCatalogConditionalDecision( - request.permission, - /* highlight-remove-start */ - catalogConditions.isEntityOwner({ - claims: user?.info.ownershipEntityRefs ?? [], - }), - /* highlight-remove-end */ - /* highlight-add-start */ - { - anyOf: [ - catalogConditions.isEntityOwner({ - claims: user?.info.ownershipEntityRefs ?? [], - }), - isInSystem({ systemRef: 'interviewing' }), - ], - }, - /* highlight-add-end */ - ); - } - - return { result: AuthorizeResult.ALLOW }; - } -} - -... -``` - -## Provide the rule during plugin setup - -Now that we have a custom rule defined and added to our policy, we need provide it to the catalog plugin. This step is important because the catalog plugin will use the rule's `toQuery` and `apply` methods while evaluating conditional authorize results. There's no guarantee that the catalog and permission backends are running on the same server, so we must explicitly link the rule to ensure that it's available at runtime. - -The api for providing custom rules may differ between plugins, but there should typically be some integration point during the creation of the backend router. For the catalog, this integration point is exposed via `CatalogBuilder.addPermissionRules`. - -```typescript title="packages/backend/src/plugins/catalog.ts" -import { CatalogBuilder } from '@backstage/plugin-catalog-backend'; -/* highlight-add-next-line */ -import { isInSystemRule } from './permission'; - -... - -export default async function createPlugin( - env: PluginEnvironment, -): Promise { - const builder = await CatalogBuilder.create(env); - /* highlight-add-next-line */ - builder.addPermissionRules(isInSystemRule); - ... - return router; -} -``` - -The updated policy will allow catalog entity resource permissions if any of the following are true: - -- User owns the target entity -- Target entity is part of the 'interviewing' system diff --git a/docs/permissions/custom-rules.md b/docs/permissions/custom-rules.md index bd35f5de8e..61b9a5326a 100644 --- a/docs/permissions/custom-rules.md +++ b/docs/permissions/custom-rules.md @@ -5,7 +5,7 @@ description: How to define custom permission rules for existing resources --- :::info -This documentation is written for [the new backend system](../backend-system/index.md) which is the default since Backstage [version 1.24](../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](./custom-rules--old.md) instead, and [consider migrating](../backend-system/building-backends/08-migrating.md)! +This documentation is written for [the new backend system](../backend-system/index.md) which is the default since Backstage [version 1.24](../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](https://github.com/backstage/backstage/blob/v1.37.0/docs/permissions/custom-rules--old.md) instead, and [consider migrating](../backend-system/building-backends/08-migrating.md)! ::: For some use cases, you may want to define custom [rules](../references/glossary.md#rule-permission-plugin) in addition to the ones provided by a plugin. In the [previous section](./writing-a-policy.md) we used the `isEntityOwner` rule to control access for catalog entities. Let's extend this policy with a custom rule that checks what [system](https://backstage.io/docs/features/software-catalog/system-model#system) an entity is part of. diff --git a/docs/permissions/getting-started--old.md b/docs/permissions/getting-started--old.md deleted file mode 100644 index 96dbfec92f..0000000000 --- a/docs/permissions/getting-started--old.md +++ /dev/null @@ -1,169 +0,0 @@ ---- -id: getting-started--old -title: Getting Started -description: How to get started with the permission framework as an integrator ---- - -:::info -This documentation is written for the old backend which has been replaced by [the new backend system](../backend-system/index.md), being the default since Backstage [version 1.24](../releases/v1.24.0.md). If have migrated to the new backend system, you may want to read [its own article](./getting-started.md) instead. Otherwise, [consider migrating](../backend-system/building-backends/08-migrating.md)! -::: - -If you prefer to watch a video instead, you can start with this video introduction: - - - -:::note Note - -This video was recorded in the January 2022 Contributors Session using `@backstage/create-app@0.4.14`. Some aspects of the demo may have changed in later releases. - -::: - -Backstage integrators control permissions by writing a policy. In general terms, a policy is simply an async function which receives a request to authorize a specific action for a user and (optional) resource, and returns a decision on whether to authorize that permission. Integrators can implement their own policies from scratch, or adopt reusable policies written by others. - -## Prerequisites - -The permissions framework depends on a few other Backstage systems, which must be set up before we can dive into writing a policy. - -### Upgrade to the latest version of Backstage - -The permissions framework itself is new to Backstage and still evolving quickly. To ensure your version of Backstage has all the latest permission-related functionality, it’s important to upgrade to the latest version. The [Backstage upgrade helper](https://backstage.github.io/upgrade-helper/) is a great tool to help ensure that you’ve made all the necessary changes during the upgrade! - -### Enable service-to-service authentication - -Service-to-service authentication allows Backstage backend code to verify that a given request originates from elsewhere in the Backstage backend. This is useful for tasks like collation of catalog entities in the search index. This type of request shouldn’t be permissioned, so it’s important to configure this feature before trying to use the permissions framework. - -To set up service-to-service authentication, follow the [service-to-service authentication docs](../auth/service-to-service-auth.md). - -### Supply an identity resolver to populate group membership on sign in - -**Note**: If you are working off of an existing Backstage instance, you likely already have some form of an identity resolver set up. - -Like many other parts of Backstage, the permissions framework relies on information about group membership. This simplifies authoring policies through the use of groups, rather than requiring each user to be listed in the configuration. Group membership is also often useful for conditional permissions, for example allowing permissions to act on an entity to be granted when a user is a member of a group that owns that entity. - -[The IdentityResolver docs](../auth/identity-resolver.md) describe the process for resolving group membership on sign in. - -## Optionally add cookie-based authentication - -Asset requests initiated by the browser will not include a token in the `Authorization` header. If these requests check authorization through the permission framework, as done in plugins like TechDocs, then you'll need to set up cookie-based authentication. Refer to the ["Authenticate API requests"](https://github.com/backstage/backstage/blob/master/contrib/docs/tutorials/authenticate-api-requests.md) tutorial for a demonstration on how to implement this behavior. - -## Integrating the permission framework with your Backstage instance - -### 1. Set up the permission backend - -The permissions framework uses a new `permission-backend` plugin to accept authorization requests from other plugins across your Backstage instance. The Backstage backend does not include this permission backend by default, so you will need to add it: - -1. Add `@backstage/plugin-permission-backend` as a dependency of your Backstage backend: - - ```bash title="From your Backstage root directory" - yarn --cwd packages/backend add @backstage/plugin-permission-backend - ``` - -2. Add the following to a new file, `packages/backend/src/plugins/permission.ts`. This adds the permission-backend router, and configures it with a policy which allows everything. - - ```typescript title="packages/backend/src/plugins/permission.ts" - import { createRouter } from '@backstage/plugin-permission-backend'; - import { - AuthorizeResult, - PolicyDecision, - } from '@backstage/plugin-permission-common'; - import { PermissionPolicy } from '@backstage/plugin-permission-node'; - import { Router } from 'express'; - import { PluginEnvironment } from '../types'; - - class TestPermissionPolicy implements PermissionPolicy { - async handle(): Promise { - return { result: AuthorizeResult.ALLOW }; - } - } - - export default async function createPlugin( - env: PluginEnvironment, - ): Promise { - return await createRouter({ - config: env.config, - logger: env.logger, - discovery: env.discovery, - policy: new TestPermissionPolicy(), - identity: env.identity, - }); - } - ``` - -3. Wire up the permission policy in `packages/backend/src/index.ts`. [The index in the example backend](https://github.com/backstage/backstage/blob/master/packages/backend/src/index.ts) shows how to do this. You’ll need to import the module from the previous step, create a plugin environment, and add the router to the express app: - - ```ts title="packages/backend/src/index.ts" - import proxy from './plugins/proxy'; - import techdocs from './plugins/techdocs'; - import search from './plugins/search'; - /* highlight-add-next-line */ - import permission from './plugins/permission'; - - async function main() { - const techdocsEnv = useHotMemoize(module, () => createEnv('techdocs')); - const searchEnv = useHotMemoize(module, () => createEnv('search')); - const appEnv = useHotMemoize(module, () => createEnv('app')); - /* highlight-add-next-line */ - const permissionEnv = useHotMemoize(module, () => createEnv('permission')); - // .. - - apiRouter.use('/techdocs', await techdocs(techdocsEnv)); - apiRouter.use('/proxy', await proxy(proxyEnv)); - apiRouter.use('/search', await search(searchEnv)); - /* highlight-add-next-line */ - apiRouter.use('/permission', await permission(permissionEnv)); - // .. - } - ``` - -### 2. Enable and test the permissions system - -Now that the permission backend is running, it’s time to enable the permissions framework and make sure it’s working properly. - -1. Set the property `permission.enabled` to `true` in `app-config.yaml`. - - ```yaml title="app-config.yaml" - permission: - enabled: true - ``` - -2. Update the PermissionPolicy in `packages/backend/src/plugins/permission.ts` to disable a permission that’s easy for us to test. This policy rejects any attempt to delete a catalog entity: - - ```ts title="packages/backend/src/plugins/permission.ts" - import { createRouter } from '@backstage/plugin-permission-backend'; - import { - AuthorizeResult, - PolicyDecision, - } from '@backstage/plugin-permission-common'; - /* highlight-remove-next-line */ - import { PermissionPolicy } from '@backstage/plugin-permission-node'; - /* highlight-add-start */ - import { - PermissionPolicy, - PolicyQuery, - } from '@backstage/plugin-permission-node'; - /* highlight-add-end */ - import { Router } from 'express'; - import { PluginEnvironment } from '../types'; - - class TestPermissionPolicy implements PermissionPolicy { - /* highlight-remove-next-line */ - async handle(): Promise { - /* highlight-add-start */ - async handle(request: PolicyQuery): Promise { - if (request.permission.name === 'catalog.entity.delete') { - return { - result: AuthorizeResult.DENY, - }; - } - /* highlight-add-end */ - - return { result: AuthorizeResult.ALLOW }; - } - } - ``` - -3. Now that you’ve made this change, you should find that the unregister entity menu option on the catalog entity page is disabled. - -![Entity detail page showing disabled unregister entity context menu entry](../assets/permissions/disabled-unregister-entity.png) - -Now that the framework is fully configured, you can craft a permission policy that works best for your organization by utilizing a provided authorization method or by [writing your own policy](./writing-a-policy.md)! diff --git a/docs/permissions/getting-started.md b/docs/permissions/getting-started.md index cb0d29ef82..f5b9092ec5 100644 --- a/docs/permissions/getting-started.md +++ b/docs/permissions/getting-started.md @@ -5,7 +5,7 @@ description: How to get started with the permission framework as an integrator --- :::info -This documentation is written for [the new backend system](../backend-system/index.md) which is the default since Backstage [version 1.24](../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](./getting-started--old.md) instead, and [consider migrating](../backend-system/building-backends/08-migrating.md)! +This documentation is written for [the new backend system](../backend-system/index.md) which is the default since Backstage [version 1.24](../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](https://github.com/backstage/backstage/blob/v1.37.0/docs/permissions/getting-started--old.md) instead, and [consider migrating](../backend-system/building-backends/08-migrating.md)! ::: Backstage integrators control permissions by writing a policy. In general terms, a policy is simply an async function which receives a request to authorize a specific action for a user and (optional) resource, and returns a decision on whether to authorize that permission. Integrators can implement their own policies from scratch, or adopt reusable policies written by others. diff --git a/docs/permissions/plugin-authors/01-setup--old.md b/docs/permissions/plugin-authors/01-setup--old.md deleted file mode 100644 index 34802d004f..0000000000 --- a/docs/permissions/plugin-authors/01-setup--old.md +++ /dev/null @@ -1,134 +0,0 @@ ---- -id: 01-setup--old -title: 1. Tutorial setup -description: How to get started with the permission framework as a plugin author ---- - -:::info -This documentation is written for the old backend which has been replaced by [the new backend system](../../backend-system/index.md), being the default since Backstage [version 1.24](../../releases/v1.24.0.md). If have migrated to the new backend system, you may want to read [its own article](./01-setup.md) instead. Otherwise, [consider migrating](../../backend-system/building-backends/08-migrating.md)! -::: - -The following tutorial is designed to help plugin authors add support for permissions to their plugins. We'll add support for permissions to example `todo-list` and `todo-list-backend` plugins, but the process should be similar for other plugins! - -The rest of this page is focused on adding the `todo-list` and `todo-list-backend` plugins to your Backstage instance. If you want to add support for permissions to your own plugin instead, feel free to skip to the [next section](./02-adding-a-basic-permission-check.md). - -## Setup for the Tutorial - -We will use a "Todo list" feature, composed of the `todo-list` and `todo-list-backend` plugins, as well as their dependency, `todo-list-common`. - -The source code is available here: - -- [todo-list](https://github.com/backstage/backstage/blob/master/plugins/example-todo-list) -- [todo-list-backend](https://github.com/backstage/backstage/blob/master/plugins/example-todo-list-backend) -- [todo-list-common](https://github.com/backstage/backstage/blob/master/plugins/example-todo-list-common) - -1. Copy-paste the three folders into the plugins folder of your backstage application repository (removing the `example-` prefix from each folder) or run the following script from the root of your backstage application: - - ```bash - $ cd $(mktemp -d) - git clone --depth 1 --quiet --no-checkout --filter=blob:none https://github.com/backstage/backstage.git . - git checkout master -- plugins/example-todo-list/ - git checkout master -- plugins/example-todo-list-backend/ - git checkout master -- plugins/example-todo-list-common/ - sed -i '' 's/workspace:\^/\*/g' plugins/example-todo-list/package.json - sed -i '' 's/workspace:\^/\*/g' plugins/example-todo-list-backend/package.json - sed -i '' 's/workspace:\^/\*/g' plugins/example-todo-list-common/package.json - for file in plugins/*; do mv "$file" "$OLDPWD/${file/example-todo/todo}"; done - cd - - ``` - - The `plugins` directory of your project should now include `todo-list`, `todo-list-backend`, and `todo-list-common`. - - **Important**: if you are on **Windows**, make sure you have WSL and git installed on your machine before executing the script above. - -2. Add these packages as dependencies for your Backstage app: - - ```sh title="From your Backstage root directory" - yarn --cwd packages/backend add @internal/plugin-todo-list-backend @internal/plugin-todo-list-common - yarn --cwd packages/app add @internal/plugin-todo-list - ``` - -3. Include the backend and frontend plugin in your application: - - Create a new `packages/backend/src/plugins/todolist.ts` with the following content: - - ```typescript title="packages/backend/src/plugins/todolist.ts" - import { DefaultIdentityClient } from '@backstage/plugin-auth-node'; - import { createRouter } from '@internal/plugin-todo-list-backend'; - import { Router } from 'express'; - import { PluginEnvironment } from '../types'; - - export default async function createPlugin({ - logger, - discovery, - }: PluginEnvironment): Promise { - return await createRouter({ - logger, - identity: DefaultIdentityClient.create({ - discovery, - issuer: await discovery.getExternalBaseUrl('auth'), - }), - }); - } - ``` - - Apply the following changes to `packages/backend/src/index.ts`: - - ```ts title="packages/backend/src/index.ts" - import techdocs from './plugins/techdocs'; - /* highlight-add-next-line */ - import todoList from './plugins/todolist'; - import search from './plugins/search'; - - async function main() { - const searchEnv = useHotMemoize(module, () => createEnv('search')); - const appEnv = useHotMemoize(module, () => createEnv('app')); - /* highlight-add-next-line */ - const todoListEnv = useHotMemoize(module, () => createEnv('todolist')); - // .. - - apiRouter.use('/proxy', await proxy(proxyEnv)); - apiRouter.use('/search', await search(searchEnv)); - apiRouter.use('/permission', await permission(permissionEnv)); - /* highlight-add-next-line */ - apiRouter.use('/todolist', await todoList(todoListEnv)); - // Add backends ABOVE this line; this 404 handler is the catch-all fallback - apiRouter.use(notFoundHandler()); - // .. - } - ``` - - Apply the following changes to `packages/app/src/App.tsx`: - - ```tsx title="packages/app/src/App.tsx" - /* highlight-add-next-line */ - import { TodoListPage } from '@internal/plugin-todo-list'; - - const routes = ( - - }> - {searchPage} - - } /> - {/* highlight-add-next-line */} - } /> - {/* ... */} - - ); - ``` - -Now if you start your application you should be able to reach the `/todo-list` page: - -![Todo List plugin page](../../assets/permissions/permission-todo-list-page.png) - ---- - -## Integrate the new plugin - -If you play with the UI, you will notice that it is possible to perform a few actions: - -- create a new todo item (`POST /todos`) -- view todo items (`GET /todos`) -- edit an existing todo item (`PUT /todos`) - -Let's try to bring authorization on top of each one of them. diff --git a/docs/permissions/plugin-authors/01-setup.md b/docs/permissions/plugin-authors/01-setup.md index 6ece0b0b69..743c5c9718 100644 --- a/docs/permissions/plugin-authors/01-setup.md +++ b/docs/permissions/plugin-authors/01-setup.md @@ -5,7 +5,7 @@ description: How to get started with the permission framework as a plugin author --- :::info -This documentation is written for [the new backend system](../../backend-system/index.md) which is the default since Backstage [version 1.24](../../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](./01-setup--old.md) instead, and [consider migrating](../../backend-system/building-backends/08-migrating.md)! +This documentation is written for [the new backend system](../../backend-system/index.md) which is the default since Backstage [version 1.24](../../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](https://github.com/backstage/backstage/blob/v1.37.0/docs/permissions/plugin-authors/01-setup--old.md) instead, and [consider migrating](../../backend-system/building-backends/08-migrating.md)! ::: The following tutorial is designed to help plugin authors add support for permissions to their plugins. We'll add support for permissions to example `todo-list` and `todo-list-backend` plugins, but the process should be similar for other plugins! diff --git a/docs/permissions/plugin-authors/02-adding-a-basic-permission-check--old.md b/docs/permissions/plugin-authors/02-adding-a-basic-permission-check--old.md deleted file mode 100644 index 7c8dad3d87..0000000000 --- a/docs/permissions/plugin-authors/02-adding-a-basic-permission-check--old.md +++ /dev/null @@ -1,387 +0,0 @@ ---- -id: 02-adding-a-basic-permission-check--old -title: 2. Adding a basic permission check -description: Explains how to add a basic permission check to a Backstage plugin ---- - -:::info -This documentation is written for the old backend which has been replaced by [the new backend system](../../backend-system/index.md), being the default since Backstage [version 1.24](../../releases/v1.24.0.md). If have migrated to the new backend system, you may want to read [its own article](./02-adding-a-basic-permission-check.md) instead. Otherwise, [consider migrating](../../backend-system/building-backends/08-migrating.md)! -::: - -If the outcome of a permission check doesn't need to change for different [resources](../../references/glossary.md#resource-permission-plugin), you can use a _basic permission check_. For this kind of check, we simply need to define a permission, and call `authorize` with it. - -For this tutorial, we'll use a basic permission check to authorize the `create` endpoint in our todo-backend. This will allow Backstage integrators to control whether each of their users is authorized to create todos by adjusting their [permission policy](../../references/glossary.md#policy-permission-plugin). - -We'll start by creating a new permission, and then we'll use the permission api to call `authorize` with it during todo creation. - -## Creating a new permission - -Let's navigate to the file `plugins/todo-list-common/src/permissions.ts` and add our first permission: - -```ts title="plugins/todo-list-common/src/permissions.ts" -import { createPermission } from '@backstage/plugin-permission-common'; - -/* highlight-remove-start */ -export const tempExamplePermission = createPermission({ - name: 'temp.example.noop', - attributes: {}, -/* highlight-remove-end */ -/* highlight-add-start */ -export const todoListCreatePermission = createPermission({ - name: 'todo.list.create', - attributes: { action: 'create' }, -/* highlight-add-end */ -}); - -/* highlight-remove-next-line */ -export const todoListPermissions = [tempExamplePermission]; -/* highlight-add-next-line */ -export const todoListPermissions = [todoListCreatePermission]; -``` - -For this tutorial, we've automatically exported all permissions from this file (see `plugins/todo-list-common/src/index.ts`). - -:::note Note - -We use a separate `todo-list-common` package since all permissions authorized by your plugin should be exported from a ["common-library" package](https://backstage.io/docs/tooling/cli/build-system#package-roles). This allows Backstage integrators to reference them in frontend components as well as permission policies. - -::: - -## Authorizing using the new permission - -Install the following module: - -``` -$ yarn workspace @internal/plugin-todo-list-backend \ - add @backstage/plugin-permission-common @backstage/plugin-permission-node @internal/plugin-todo-list-common -``` - -Edit `plugins/todo-list-backend/src/service/router.ts`: - -```ts title="plugins/todo-list-backend/src/service/router.ts" -/* highlight-remove-start */ -import { InputError } from '@backstage/errors'; -import { IdentityApi } from '@backstage/plugin-auth-node'; -/* highlight-remove-end */ -/* highlight-add-start */ -import { InputError, NotAllowedError } from '@backstage/errors'; -import { getBearerTokenFromAuthorizationHeader, IdentityApi } from '@backstage/plugin-auth-node'; -import { PermissionEvaluator, AuthorizeResult } from '@backstage/plugin-permission-common'; -import { createPermissionIntegrationRouter } from '@backstage/plugin-permission-node'; -import { todoListCreatePermission } from '@internal/plugin-todo-list-common'; -/* highlight-add-end */ - -export interface RouterOptions { - logger: Logger; - identity: IdentityApi; - /* highlight-add-next-line */ - permissions: PermissionEvaluator; -} - -export async function createRouter( - options: RouterOptions, -): Promise { - /* highlight-remove-next-line */ - const { logger, identity } = options; - /* highlight-add-next-line */ - const { logger, identity, permissions } = options; - - /* highlight-add-start */ - const permissionIntegrationRouter = createPermissionIntegrationRouter({ - permissions: [todoListCreatePermission], - }); - /* highlight-add-end */ - - const router = Router(); - router.use(express.json()); - - router.get('/health', (_, response) => { - logger.info('PONG!'); - response.json({ status: 'ok' }); - }); - - /* highlight-add-next-line */ - router.use(permissionIntegrationRouter); - - router.get('/todos', async (_req, res) => { - res.json(getAll()); - }); - - router.post('/todos', async (req, res) => { - let author: string | undefined = undefined; - - const user = await identity.getIdentity({ request: req }); - author = user?.identity.userEntityRef; - /* highlight-add-start */ - const token = getBearerTokenFromAuthorizationHeader( - req.header('authorization'), - ); - const decision = ( - await permissions.authorize([{ permission: todoListCreatePermission }], { - token, - }) - )[0]; - - if (decision.result === AuthorizeResult.DENY) { - throw new NotAllowedError('Unauthorized'); - } - /* highlight-add-end */ - - if (!isTodoCreateRequest(req.body)) { - throw new InputError('Invalid payload'); - } - - const todo = add({ title: req.body.title, author }); - res.json(todo); - }); - - // ... -``` - -Pass the `permissions` object to the plugin in `packages/backend/src/plugins/todolist.ts`: - -```ts title="packages/backend/src/plugins/todolist.ts" -import { DefaultIdentityClient } from '@backstage/plugin-auth-node'; -import { createRouter } from '@internal/plugin-todo-list-backend'; -import { Router } from 'express'; -import { PluginEnvironment } from '../types'; - -export default async function createPlugin({ - logger, - discovery, - /* highlight-add-next-line */ - permissions, -}: PluginEnvironment): Promise { - return await createRouter({ - logger, - identity: DefaultIdentityClient.create({ - discovery, - issuer: await discovery.getExternalBaseUrl('auth'), - }), - /* highlight-add-next-line */ - permissions, - }); -} -``` - -That's it! Now your plugin is fully configured. Let's try to test the logic by denying the permission. - -## Test the authorized create endpoint - -Before running this step, please make sure you followed the steps described in [Getting started](../getting-started.md) section. - -In order to test the logic above, the integrators of your backstage instance need to change their permission policy to return `DENY` for our newly-created permission: - -```ts title="packages/backend/src/plugins/permission.ts" -import { - PermissionPolicy, - /* highlight-add-start */ - PolicyQuery, - PolicyQueryUser, - /* highlight-add-end */ -} from '@backstage/plugin-permission-node'; -/* highlight-add-start */ -import { isPermission } from '@backstage/plugin-permission-common'; -import { todoListCreatePermission } from '@internal/plugin-todo-list-common'; -/* highlight-add-end */ - -class TestPermissionPolicy implements PermissionPolicy { - /* highlight-remove-next-line */ - async handle(): Promise { - /* highlight-add-start */ - async handle( - request: PolicyQuery, - _user?: PolicyQueryUser, - ): Promise { - if (isPermission(request.permission, todoListCreatePermission)) { - return { - result: AuthorizeResult.DENY, - }; - } - /* highlight-add-end */ - - return { - result: AuthorizeResult.ALLOW, - }; -} -``` - -Now the frontend should show an error whenever you try to create a new Todo item. - -Let's flip the result back to `ALLOW` before moving on. - -```ts -if (isPermission(request.permission, todoListCreatePermission)) { - return { - /* highlight-remove-next-line */ - result: AuthorizeResult.DENY, - /* highlight-add-next-line */ - result: AuthorizeResult.ALLOW, - }; -} -``` - -At this point everything is working but if you run `yarn tsc` you'll get some errors, let's fix those up. - -First we'll clean up the `plugins/todo-list-backend/src/service/router.test.ts`: - -```ts title="plugins/todo-list-backend/src/service/router.test.ts" -import { getVoidLogger } from '@backstage/backend-common'; -import { DefaultIdentityClient } from '@backstage/plugin-auth-node'; -/* highlight-add-next-line */ -import { PermissionEvaluator } from '@backstage/plugin-permission-common'; -import express from 'express'; -import request from 'supertest'; - -import { createRouter } from './router'; - -/* highlight-add-start */ -const mockedAuthorize: jest.MockedFunction = - jest.fn(); -const mockedPermissionQuery: jest.MockedFunction< - PermissionEvaluator['authorizeConditional'] -> = jest.fn(); - -const permissionEvaluator: PermissionEvaluator = { - authorize: mockedAuthorize, - authorizeConditional: mockedPermissionQuery, -}; -/* highlight-add-end */ - -describe('createRouter', () => { - let app: express.Express; - - beforeAll(async () => { - const router = await createRouter({ - logger: getVoidLogger(), - identity: {} as DefaultIdentityClient, - /* highlight-add-next-line */ - permissions: permissionEvaluator, - }); - app = express().use(router); - }); - - beforeEach(() => { - jest.resetAllMocks(); - }); - - describe('GET /health', () => { - it('returns ok', async () => { - const response = await request(app).get('/health'); - - expect(response.status).toEqual(200); - expect(response.body).toEqual({ status: 'ok' }); - }); - }); -}); -``` - -Then we want to update the `plugins/todo-list-backend/src/service/standaloneServer.ts`: - -```ts title="plugins/todo-list-backend/src/service/standaloneServer.ts" -import { - createServiceBuilder, - loadBackendConfig, - SingleHostDiscovery, - /* highlight-add-next-line */ - ServerTokenManager, -} from '@backstage/backend-common'; -import { DefaultIdentityClient } from '@backstage/plugin-auth-node'; -/* highlight-add-next-line */ -import { ServerPermissionClient } from '@backstage/plugin-permission-node'; -import { Server } from 'http'; -import { Logger } from 'winston'; -import { createRouter } from './router'; - -export interface ServerOptions { - port: number; - enableCors: boolean; - logger: Logger; -} - -export async function startStandaloneServer( - options: ServerOptions, -): Promise { - const logger = options.logger.child({ service: 'todo-list-backend' }); - logger.debug('Starting application server...'); - const config = await loadBackendConfig({ logger, argv: process.argv }); - const discovery = SingleHostDiscovery.fromConfig(config); - /* highlight-add-start */ - const tokenManager = ServerTokenManager.fromConfig(config, { - logger, - }); - const permissions = ServerPermissionClient.fromConfig(config, { - discovery, - tokenManager, - }); - /* highlight-add-end */ - const router = await createRouter({ - logger, - identity: DefaultIdentityClient.create({ - discovery, - issuer: await discovery.getExternalBaseUrl('auth'), - }), - /* highlight-add-next-line */ - permissions, - }); - - let service = createServiceBuilder(module) - .setPort(options.port) - .addRouter('/todo-list', router); - if (options.enableCors) { - service = service.enableCors({ origin: 'http://localhost:3000' }); - } - - return await service.start().catch(err => { - logger.error(err); - process.exit(1); - }); -} - -module.hot?.accept(); -``` - -Finally, we need to update `plugins/todo-list-backend/src/plugin.ts`: - -```ts title="plugins/todo-list-backend/src/plugin.ts" -import { loggerToWinstonLogger } from '@backstage/backend-common'; -import { - coreServices, - createBackendPlugin, -} from '@backstage/backend-plugin-api'; -import { createRouter } from './service/router'; - -/** -* The example TODO list backend plugin. -* -* @public -*/ -export const exampleTodoListPlugin = createBackendPlugin({ - pluginId: 'exampleTodoList', - register(env) { - env.registerInit({ - deps: { - identity: coreServices.identity, - logger: coreServices.logger, - httpRouter: coreServices.httpRouter, - /* highlight-add-next-line */ - permissions: coreServices.permissions, - }, - /* highlight-remove-next-line */ - async init({ identity, logger, httpRouter }) { - /* highlight-add-next-line */ - async init({ identity, logger, httpRouter, permissions }) { - httpRouter.use( - await createRouter({ - identity, - logger: loggerToWinstonLogger(logger), - permissions, - }), - ); - }, - }); - }, -}); -``` - -Now when you run `yarn tsc` you should have no more errors. diff --git a/docs/permissions/plugin-authors/02-adding-a-basic-permission-check.md b/docs/permissions/plugin-authors/02-adding-a-basic-permission-check.md index 4053a81f99..8ba3ac3152 100644 --- a/docs/permissions/plugin-authors/02-adding-a-basic-permission-check.md +++ b/docs/permissions/plugin-authors/02-adding-a-basic-permission-check.md @@ -5,7 +5,7 @@ description: Explains how to add a basic permission check to a Backstage plugin --- :::info -This documentation is written for [the new backend system](../../backend-system/index.md) which is the default since Backstage [version 1.24](../../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](./02-adding-a-basic-permission-check--old.md) instead, and [consider migrating](../../backend-system/building-backends/08-migrating.md)! +This documentation is written for [the new backend system](../../backend-system/index.md) which is the default since Backstage [version 1.24](../../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](https://github.com/backstage/backstage/blob/v1.37.0/docs/permissions/plugin-authors/02-adding-a-basic-permission-check--old.md) instead, and [consider migrating](../../backend-system/building-backends/08-migrating.md)! ::: If the outcome of a permission check doesn't need to change for different [resources](../../references/glossary.md#resource-permission-plugin), you can use a _basic permission check_. For this kind of check, we simply need to define a permission, and call `authorize` with it. diff --git a/docs/permissions/plugin-authors/03-adding-a-resource-permission-check--old.md b/docs/permissions/plugin-authors/03-adding-a-resource-permission-check--old.md deleted file mode 100644 index ee06629e71..0000000000 --- a/docs/permissions/plugin-authors/03-adding-a-resource-permission-check--old.md +++ /dev/null @@ -1,299 +0,0 @@ ---- -id: 03-adding-a-resource-permission-check--old -title: 3. Adding a resource permission check -description: Explains how to add a resource permission check to a Backstage plugin ---- - -:::info -This documentation is written for the old backend which has been replaced by [the new backend system](../../backend-system/index.md), being the default since Backstage [version 1.24](../../releases/v1.24.0.md). If have migrated to the new backend system, you may want to read [its own article](./03-adding-a-resource-permission-check.md) instead. Otherwise, [consider migrating](../../backend-system/building-backends/08-migrating.md)! -::: - -When performing updates (or other operations) on specific [resources](../../references/glossary.md#resource-permission-plugin), the permissions framework allows for the decision to be based on characteristics of the resource itself. This means that it's possible to write policies that (for example) allow the operation for users that own a resource, and deny the operation otherwise. - -## Creating the update permission - -Let's add a new permission to the file `plugins/todo-list-common/src/permissions.ts` from [the previous section](./02-adding-a-basic-permission-check.md). - -```ts title="plugins/todo-list-common/src/permissions.ts" -import { createPermission } from '@backstage/plugin-permission-common'; - -/* highlight-add-next-line */ -export const TODO_LIST_RESOURCE_TYPE = 'todo-item'; - -export const todoListCreatePermission = createPermission({ - name: 'todo.list.create', - attributes: { action: 'create' }, -}); - -/* highlight-add-start */ -export const todoListUpdatePermission = createPermission({ - name: 'todo.list.update', - attributes: { action: 'update' }, - resourceType: TODO_LIST_RESOURCE_TYPE, -}); -/* highlight-add-end */ - -/* highlight-remove-next-line */ -export const todoListPermissions = [todoListCreatePermission]; -/* highlight-add-start */ -export const todoListPermissions = [ - todoListCreatePermission, - todoListUpdatePermission, -]; -/* highlight-add-end */ -``` - -Notice that unlike `todoListCreatePermission`, the `todoListUpdatePermission` permission contains a `resourceType` field. This field indicates to the permission framework that this permission is intended to be authorized in the context of a resource with type `'todo-item'`. You can use whatever string you like as the resource type, as long as you use the same value consistently for each type of resource. - -## Setting up authorization for the update permission - -To start, let's edit `plugins/todo-list-backend/src/service/router.ts` in the same manner as we did in the previous section: - -```ts title="plugins/todo-list-backend/src/service/router.ts" -/* highlight-remove-next-line */ -import { todoListCreatePermission } from '@internal/plugin-todo-list-common'; -/* highlight-add-start */ -import { - todoListCreatePermission, - todoListUpdatePermission, -} from '@internal/plugin-todo-list-common'; -/* highlight-add-end */ - -// ... - -const permissionIntegrationRouter = createPermissionIntegrationRouter({ - /* highlight-remove-next-line */ - permissions: [todoListCreatePermission], - /* highlight-add-next-line */ - permissions: [todoListCreatePermission, todoListUpdatePermission], -}); - -// ... - -router.put('/todos', async (req, res) => { - /* highlight-add-start */ - const token = getBearerTokenFromAuthorizationHeader( - req.header('authorization'), - ); - /* highlight-add-end */ - - if (!isTodoUpdateRequest(req.body)) { - throw new InputError('Invalid payload'); - } - /* highlight-add-start */ - const decision = ( - await permissions.authorize( - [{ permission: todoListUpdatePermission, resourceRef: req.body.id }], - { - token, - }, - ) - )[0]; - - if (decision.result !== AuthorizeResult.ALLOW) { - throw new NotAllowedError('Unauthorized'); - } - /* highlight-add-end */ - - res.json(update(req.body)); -}); -``` - -**Important:** Notice that we are passing an extra `resourceRef` field, with the `id` of the todo item as the value. - -This enables decisions based on characteristics of the resource, but it's important to note that policy authors will not have access to the resource ref inside of their permission policies. Instead, the policies will return conditional decisions, which we need to now support in our plugin. - -## Adding support for conditional decisions - -Install the missing module: - -```bash -$ yarn workspace @internal/plugin-todo-list-backend add zod -``` - -Create a new `plugins/todo-list-backend/src/service/rules.ts` file and append the following code: - -```typescript title="plugins/todo-list-backend/src/service/rules.ts" -import { makeCreatePermissionRule } from '@backstage/plugin-permission-node'; -import { TODO_LIST_RESOURCE_TYPE } from '@internal/plugin-todo-list-common'; -import { z } from 'zod'; -import { Todo, TodoFilter } from './todos'; - -export const createTodoListPermissionRule = makeCreatePermissionRule< - Todo, - TodoFilter, - typeof TODO_LIST_RESOURCE_TYPE ->(); - -export const isOwner = createTodoListPermissionRule({ - name: 'IS_OWNER', - description: 'Should allow only if the todo belongs to the user', - resourceType: TODO_LIST_RESOURCE_TYPE, - paramsSchema: z.object({ - userId: z.string().describe('User ID to match on the resource'), - }), - apply: (resource: Todo, { userId }) => { - return resource.author === userId; - }, - toQuery: ({ userId }) => { - return { - property: 'author', - values: [userId], - }; - }, -}); - -export const rules = { isOwner }; -``` - -`makeCreatePermissionRule` is a helper used to ensure that rules created for this plugin use consistent types for the resource and query. - -:::note Note - -To support custom rules defined by Backstage integrators, you must export `createTodoListPermissionRule` from the backend package and provide some way for custom rules to be passed in before the backend starts, likely via `createRouter`. - -::: - -We have created a new `isOwner` rule, which is going to be automatically used by the permission framework whenever a conditional response is returned in response to an authorized request with an attached `resourceRef`. -Specifically, the `apply` function is used to understand whether the passed resource should be authorized or not. - -Let's skip the `toQuery` function for now, we'll come back to that in the next section. - -Now, let's create the new endpoint by editing `plugins/todo-list-backend/src/service/router.ts`. This uses the `createPermissionIntegrationRouter` helper to add the APIs needed by the permission framework to your plugin. You'll need to supply: - -- `getResources`: a function that accepts an array of `resourceRefs` in the same format you expect to be passed to `authorize`, and returns an array of the corresponding resources. -- `resourceType`: the same value used in the permission rule above. -- `permissions`: the list of permissions that your plugin accepts. -- `rules`: an array of all the permission rules you want to support in conditional decisions. - -```ts title="plugins/todo-list-backend/src/service/router.ts" -// ... -import { - /* highlight-add-next-line */ - TODO_LIST_RESOURCE_TYPE, - todoListCreatePermission, - todoListUpdatePermission, -} from '@internal/plugin-todo-list-common'; -/* highlight-remove-next-line */ -import { add, getAll, update } from './todos'; -/* highlight-add-start */ -import { add, getAll, getTodo, update } from './todos'; -import { rules } from './rules'; -/* highlight-add-end */ - -export async function createRouter( - options: RouterOptions, -): Promise { - const { logger, identity, permissions } = options; - - const permissionIntegrationRouter = createPermissionIntegrationRouter({ - permissions: [todoListCreatePermission, todoListUpdatePermission], - /* highlight-add-start */ - getResources: async resourceRefs => { - return resourceRefs.map(getTodo); - }, - resourceType: TODO_LIST_RESOURCE_TYPE, - rules: Object.values(rules), - /* highlight-add-end */ - }); - - const router = Router(); - router.use(express.json()); - - // ... -} -``` - -## Provide utilities for policy authors - -Now that we have a new resource type and a corresponding rule, we need to export some utilities for policy authors to reference them. - -Create a new `plugins/todo-list-backend/src/conditionExports.ts` file and add the following code: - -```typescript title="plugins/todo-list-backend/src/conditionExports.ts" -import { TODO_LIST_RESOURCE_TYPE } from '@internal/plugin-todo-list-common'; -import { createConditionExports } from '@backstage/plugin-permission-node'; -import { rules } from './service/rules'; - -const { conditions, createConditionalDecision } = createConditionExports({ - pluginId: 'todolist', - resourceType: TODO_LIST_RESOURCE_TYPE, - rules, -}); - -export const todoListConditions = conditions; - -export const createTodoListConditionalDecision = createConditionalDecision; -``` - -Make sure `todoListConditions` and `createTodoListConditionalDecision` are exported from the `todo-list-backend` package by editing `plugins/todo-list-backend/src/index.ts`: - -```ts title="plugins/todo-list-backend/src/index.ts" -export * from './service/router'; -/* highlight-add-next-line */ -export * from './conditionExports'; -export { exampleTodoListPlugin } from './plugin'; -``` - -## Test the authorized update endpoint - -Let's go back to the permission policy's handle function and try to authorize our new permission with an `isOwner` condition. - -```ts title="packages/backend/src/plugins/permission.ts" -import { - IdentityClient -} from '@backstage/plugin-auth-node'; -import { - PermissionPolicy, - PolicyQuery, - PolicyQueryUser, -} from '@backstage/plugin-permission-node'; -import { isPermission } from '@backstage/plugin-permission-common'; -/* highlight-remove-next-line */ -import { todoListCreatePermission } from '@internal/plugin-todo-list-common'; -/* highlight-add-start */ -import { - todoListCreatePermission, - todoListUpdatePermission, -} from '@internal/plugin-todo-list-common'; -import { - todoListConditions, - createTodoListConditionalDecision, -} from '@internal/plugin-todo-list-backend'; -/* highlight-add-end */ - - -async handle( - request: PolicyQuery, - /* highlight-remove-next-line */ - _user?: PolicyQueryUser, - /* highlight-add-next-line */ - user?: PolicyQueryUser, -): Promise { - if (isPermission(request.permission, todoListCreatePermission)) { - return { - result: AuthorizeResult.ALLOW, - }; - } - /* highlight-add-start */ - if (isPermission(request.permission, todoListUpdatePermission)) { - return createTodoListConditionalDecision( - request.permission, - todoListConditions.isOwner({ - userId: user?.info.userEntityRef ?? '', - }), - ); - } - /* highlight-add-end */ - - return { - result: AuthorizeResult.ALLOW, - }; -} -``` - -For any incoming update requests, we now return a _Conditional Decision_. We are saying: - -> Hey permission framework, I can't make a decision alone. Please go to the plugin with id `todolist` and ask it to apply these conditions. - -To check that everything works as expected, you should now see an error in the UI whenever you try to edit an item that wasn’t created by you. Success! diff --git a/docs/permissions/plugin-authors/03-adding-a-resource-permission-check.md b/docs/permissions/plugin-authors/03-adding-a-resource-permission-check.md index dd8e88bb94..6ebbdf77e4 100644 --- a/docs/permissions/plugin-authors/03-adding-a-resource-permission-check.md +++ b/docs/permissions/plugin-authors/03-adding-a-resource-permission-check.md @@ -5,7 +5,7 @@ description: Explains how to add a resource permission check to a Backstage plug --- :::info -This documentation is written for [the new backend system](../../backend-system/index.md) which is the default since Backstage [version 1.24](../../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](./03-adding-a-resource-permission-check--old.md) instead, and [consider migrating](../../backend-system/building-backends/08-migrating.md)! +This documentation is written for [the new backend system](../../backend-system/index.md) which is the default since Backstage [version 1.24](../../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](https://github.com/backstage/backstage/blob/v1.37.0/docs/permissions/plugin-authors/03-adding-a-resource-permission-check--old.md) instead, and [consider migrating](../../backend-system/building-backends/08-migrating.md)! ::: When performing updates (or other operations) on specific [resources](../../references/glossary.md#resource-permission-plugin), the permissions framework allows for the decision to be based on characteristics of the resource itself. This means that it's possible to write policies that (for example) allow the operation for users that own a resource, and deny the operation otherwise. diff --git a/docs/permissions/plugin-authors/04-authorizing-access-to-paginated-data--old.md b/docs/permissions/plugin-authors/04-authorizing-access-to-paginated-data--old.md deleted file mode 100644 index cca6899ef6..0000000000 --- a/docs/permissions/plugin-authors/04-authorizing-access-to-paginated-data--old.md +++ /dev/null @@ -1,193 +0,0 @@ ---- -id: 04-authorizing-access-to-paginated-data--old -title: 4. Authorizing access to paginated data -description: Explains how to authorize access to paginated data in a Backstage plugin ---- - -:::info -This documentation is written for the old backend which has been replaced by [the new backend system](../../backend-system/index.md), being the default since Backstage [version 1.24](../../releases/v1.24.0.md). If have migrated to the new backend system, you may want to read [its own article](./04-authorizing-access-to-paginated-data.md) instead. Otherwise, [consider migrating](../../backend-system/building-backends/08-migrating.md)! -::: - -Authorizing `GET /todos` is similar to the update endpoint, in that it should be possible to authorize access based on the characteristics of each resource. However, we'll need to authorize a list of resources for this endpoint. - -One possible solution may leverage the batching functionality to authorize all of the todos, and then returning only the ones for which the decision was `ALLOW`: - -```ts -router.get('/todos', async (req, res) => { - /* highlight-add-next-line */ - const credentials = await httpAuth.credentials(req, { allow: ['user'] }); - - /* highlight-remove-next-line */ - res.json(getAll()); - /* highlight-add-start */ - const items = getAll(); - const decisions = await permissions.authorize( - items.map(({ id }) => ({ - permission: todoListReadPermission, - resourceRef: id, - })), - { credentials }, - ); - - const filteredItems = decisions.filter( - decision => decision.result === AuthorizeResult.ALLOW, - ); - res.json(filteredItems); - /* highlight-add-end */ -}); -``` - -This approach will work for simple cases, but it has a downside: it forces us to retrieve all the elements upfront and authorize them one by one. This forces the plugin implementation to handle concerns like pagination, which is currently handled by the data source. - -To avoid this situation, the permissions framework has support for filtering items in the data source itself. In this part of the tutorial, we'll describe the steps required to use that behavior. - -:::note Note - -In order to perform authorization filtering in this way, the data source must allow filters to be logically combined with AND, OR, and NOT operators. The conditional decisions returned by the permissions framework use a [nested object](https://backstage.io/docs/reference/plugin-permission-common.permissioncriteria) to combine conditions. If you're implementing a filter API from scratch, we recommend using the same shape for ease of interoperability. If not, you'll need to implement a function which transforms the nested object into your own format. - -::: - -## Creating the read permission - -Let's add another permission to the plugin. - -```ts title="plugins/todo-list-backend/src/service/permissions.ts" -import { createPermission } from '@backstage/plugin-permission-common'; - -export const TODO_LIST_RESOURCE_TYPE = 'todo-item'; - -export const todoListCreatePermission = createPermission({ - name: 'todo.list.create', - attributes: { action: 'create' }, -}); - -export const todoListUpdatePermission = createPermission({ - name: 'todo.list.update', - attributes: { action: 'update' }, - resourceType: TODO_LIST_RESOURCE_TYPE, -}); - -/* highlight-add-start */ -export const todoListReadPermission = createPermission({ - name: 'todos.list.read', - attributes: { action: 'read' }, - resourceType: TODO_LIST_RESOURCE_TYPE, -}); -/* highlight-add-end */ - -export const todoListPermissions = [ - todoListCreatePermission, - todoListUpdatePermission, - /* highlight-add-start */ - todoListReadPermission, - /* highlight-add-end */ -]; -``` - -## Using conditional policy decisions - -So far we've only used the `PermissionsService.authorize` method, which will evaluate conditional decisions before returning a result. In this step, we want to evaluate conditional decisions within our plugin, so we'll use `PermissionsService.authorizeConditional` instead. - -```ts title="plugins/todo-list-backend/src/service/router.ts" -/* highlight-remove-next-line */ -import { createPermissionIntegrationRouter } from '@backstage/plugin-permission-node'; -/* highlight-add-start */ -import { - createPermissionIntegrationRouter, - createConditionTransformer, - ConditionTransformer, -} from '@backstage/plugin-permission-node'; -/* highlight-add-end */ -/* highlight-remove-next-line */ -import { add, getAll, getTodo, update } from './todos'; -/* highlight-add-next-line */ -import { add, getAll, getTodo, TodoFilter, update } from './todos'; -import { - TODO_LIST_RESOURCE_TYPE, - todoListCreatePermission, - todoListUpdatePermission, - /* highlight-add-next-line */ - todoListReadPermission, -} from './permissions'; - -// ... - -const permissionIntegrationRouter = createPermissionIntegrationRouter({ - /* highlight-remove-next-line */ - permissions: [todoListCreatePermission, todoListUpdatePermission], - /* highlight-add-next-line */ - permissions: [todoListCreatePermission, todoListUpdatePermission, todoListReadPermission], - getResources: async resourceRefs => { - return resourceRefs.map(getTodo); - }, - resourceType: TODO_LIST_RESOURCE_TYPE, - rules: Object.values(rules), -}); - -// ... - -/* highlight-add-next-line */ -const transformConditions: ConditionTransformer = createConditionTransformer(Object.values(rules)); - -/* highlight-remove-next-line */ -router.get('/todos', async (_req, res) => { -/* highlight-add-start */ -router.get('/todos', async (req, res) => { - const credentials = await httpAuth.credentials(req, { allow: ['user'] }); - - const decision = ( - await permissions.authorizeConditional([{ permission: todoListReadPermission }], { - credentials, - }) - )[0]; - - if (decision.result === AuthorizeResult.DENY) { - throw new NotAllowedError('Unauthorized'); - } - - if (decision.result === AuthorizeResult.CONDITIONAL) { - const filter = transformConditions(decision.conditions); - res.json(getAll(filter)); - } else { - res.json(getAll()); - } -/* highlight-add-end */ - /* highlight-remove-next-line */ - res.json(getAll()); -}); -``` - -To make the process of handling conditional decisions easier, the permission framework provides a `createConditionTransformer` helper. This function accepts an array of permission rules, and returns a transformer function which converts the conditions to the format needed by the plugin using the `toQuery` method defined on each rule. - -Since `TodoFilter` used in our plugin matches the structure of the conditions object, we can directly pass the output of our condition transformer. If the filters were structured differently, we'd need to transform it further before passing it to the api. - -## Test the authorized read endpoint - -Let's update our permission policy to return a conditional result whenever a `todoListReadPermission` permission is received. In this case, we can reuse the decision returned for the `todosListCreate` permission. - -```ts title="packages/backend/src/plugins/permission.ts" -import { - todoListCreatePermission, - todoListUpdatePermission, - /* highlight-add-next-line */ - todoListReadPermission, -} from '@internal/plugin-todo-list-common'; - -/* highlight-remove-next-line */ -if (isPermission(request.permission, todoListUpdatePermission)) { -/* highlight-add-start */ -if ( - isPermission(request.permission, todoListUpdatePermission) || - isPermission(request.permission, todoListReadPermission) -) { -/* highlight-add-end */ - return createTodoListConditionalDecision( - request.permission, - todoListConditions.isOwner({ - userId: user?.identity.userEntityRef - }), - ); -} -``` - -Once the changes to the permission policy are saved, the UI should show only the todo items you've created. diff --git a/docs/permissions/plugin-authors/04-authorizing-access-to-paginated-data.md b/docs/permissions/plugin-authors/04-authorizing-access-to-paginated-data.md index c03183bbf0..b81382ee16 100644 --- a/docs/permissions/plugin-authors/04-authorizing-access-to-paginated-data.md +++ b/docs/permissions/plugin-authors/04-authorizing-access-to-paginated-data.md @@ -5,7 +5,7 @@ description: Explains how to authorize access to paginated data in a Backstage p --- :::info -This documentation is written for [the new backend system](../../backend-system/index.md) which is the default since Backstage [version 1.24](../../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](./04-authorizing-access-to-paginated-data--old.md) instead, and [consider migrating](../../backend-system/building-backends/08-migrating.md)! +This documentation is written for [the new backend system](../../backend-system/index.md) which is the default since Backstage [version 1.24](../../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](https://github.com/backstage/backstage/blob/v1.37.0/docs/permissions/plugin-authors/04-authorizing-access-to-paginated-data--old.md) instead, and [consider migrating](../../backend-system/building-backends/08-migrating.md)! ::: Authorizing `GET /todos` is similar to the update endpoint, in that it should be possible to authorize access based on the characteristics of each resource. However, we'll need to authorize a list of resources for this endpoint. diff --git a/docs/permissions/writing-a-policy--old.md b/docs/permissions/writing-a-policy--old.md deleted file mode 100644 index d95fa7ffb3..0000000000 --- a/docs/permissions/writing-a-policy--old.md +++ /dev/null @@ -1,148 +0,0 @@ ---- -id: writing-a-policy--old -title: Writing a permission policy -description: How to write your own permission policy as a Backstage integrator ---- - -:::info -This documentation is written for the old backend which has been replaced by [the new backend system](../backend-system/index.md), being the default since Backstage [version 1.24](../releases/v1.24.0.md). If have migrated to the new backend system, you may want to read [its own article](./writing-a-policy.md) instead. Otherwise, [consider migrating](../backend-system/building-backends/08-migrating.md)! -::: - -In the [previous section](./getting-started.md), we were able to set up the permission framework and make a simple change to our `TestPermissionPolicy` to confirm that policy is indeed wired up correctly. - -That policy looked like this: - -```typescript title="packages/backend/src/plugins/permission.ts" -class TestPermissionPolicy implements PermissionPolicy { - async handle( - request: PolicyQuery, - _user?: PolicyQueryUser, - ): Promise { - if (request.permission.name === 'catalog.entity.delete') { - return { - result: AuthorizeResult.DENY, - }; - } - - return { result: AuthorizeResult.ALLOW }; - } -} -``` - -## What's in a policy? - -Let's break this down a bit further. The request object of type [PolicyQuery](https://backstage.io/docs/reference/plugin-permission-node.policyquery) is a simple wrapper around [the Permission object](https://backstage.io/docs/reference/plugin-permission-common.permission). This permission object encapsulates information about the action that the user is attempting to perform (See [the Concepts page](./concepts.md) for more details). - -In the policy above, we are checking to see if the provided action is a catalog entity delete action, which is the permission that the catalog plugin authors have created to represent the action of unregistering a catalog entity. If this is the case, we return a [Definitive Policy Decision](https://backstage.io/docs/reference/plugin-permission-common.definitivepolicydecision) of DENY. In all other cases, we return ALLOW (resulting in an allow-by-default behavior). - -As we confirmed in the previous section, we know that this now prevents us from unregistering catalog components. Hooray! But you may notice that this prevents _anyone_ from unregistering a component, which is not a very realistic policy. Let's improve this policy by disabling the unregister action _unless you are the owner of this component_. - -## Conditional decisions - -Let's change the policy to the following: - -```ts -import { - AuthorizeResult, - PolicyDecision, - /* highlight-add-next-line */ - isPermission, -} from '@backstage/plugin-permission-common'; -/* highlight-add-start */ -import { - catalogConditions, - createCatalogConditionalDecision, -} from '@backstage/plugin-catalog-backend/alpha'; -import { - catalogEntityDeletePermission, -} from '@backstage/plugin-catalog-common/alpha'; -/* highlight-add-end */ - -class TestPermissionPolicy implements PermissionPolicy { - /* highlight-remove-next-line */ - async handle(request: PolicyQuery): Promise { - /* highlight-add-start */ - async handle( - request: PolicyQuery, - user?: PolicyQueryUser, - ): Promise { - /* highlight-add-end */ - /* highlight-remove-next-line */ - if (request.permission.name === 'catalog.entity.delete') { - /* highlight-add-next-line */ - if (isPermission(request.permission, catalogEntityDeletePermission)) { - /* highlight-remove-start */ - return { - result: AuthorizeResult.DENY, - }; - /* highlight-remove-end */ - /* highlight-add-start */ - return createCatalogConditionalDecision( - request.permission, - catalogConditions.isEntityOwner({ - claims: user?.info.ownershipEntityRefs ?? [], - }), - ); - /* highlight-add-end */ - } - return { result: AuthorizeResult.ALLOW }; - } -} -``` - -Let's walk through the new code that we just added. - -Instead of returning an Definitive Policy Decision, we use factory methods to construct a [Conditional Policy Decision](https://backstage.io/docs/reference/plugin-permission-common.conditionalpolicydecision) (See the [Concepts page](./concepts.md) for more details). Since the policy doesn't have enough information to determine if `user` is the entity owner, this criteria is encapsulated within the conditional decision. However, `createCatalogConditionalDecision` will not compile unless `request.permission` is a catalog entity [`ResourcePermission`](https://backstage.io/docs/reference/plugin-permission-common.resourcepermission). This type constraint ensures that policies return conditional decisions that are compatible with the requested permission. To address this, we use [`isPermission`](https://backstage.io/docs/reference/plugin-permission-common.ispermission) to ["narrow"](https://www.typescriptlang.org/docs/handbook/2/narrowing.html) the type of `request.permission` to `ResourcePermission<'catalog-entity'>`. This matches the runtime behavior that was in place before, but you'll notice that the type of `request.permission` has changed within the scope of that `if` statement. - -The `catalogConditions` object contains all of the rules defined by the catalog plugin. These rules can be combined to form a [`PermissionCriteria`](https://backstage.io/docs/reference/plugin-permission-common.permissioncriteria) object, but for this case we only need to use the `isEntityOwner` rule. This rule accepts a list of entity refs that represent User identity and Group membership used to determine ownership. The second argument to `PermissionPolicy#handle` provides us with a `PolicyQueryUser` object, from which we can grab the user's `ownershipEntityRefs`. We provide an empty array as a fallback since the user may be anonymous. - -You should now be able to see in your Backstage app that the unregister entity button is enabled for entities that you own, but disabled for all other entities! - -## Resource types - -Now let's say we want to prevent all actions on catalog entities unless performed by the owner. One way to achieve this may be to simply update the `if` statement and check for each permission. If you choose to write your policy this way, it will certainly work! However, it may be difficult to maintain as the policy grows, and it may not be obvious if certain permissions are left out. We can author this same policy in a more scalable way by checking the resource type of the requested permission. - -```ts -import { - AuthorizeResult, - PolicyDecision, - /* highlight-remove-next-line */ - isPermission, - isResourcePermission, - /* highlight-add-next-line */ -} from '@backstage/plugin-permission-common'; -import { - catalogConditions, - createCatalogConditionalDecision, -} from '@backstage/plugin-catalog-backend/alpha'; -/* highlight-remove-start */ -import { - catalogEntityDeletePermission, -} from '@backstage/plugin-catalog-common/alpha'; -/* highlight-remove-end */ - -class TestPermissionPolicy implements PermissionPolicy { - async handle( - request: PolicyQuery, - user?: PolicyQueryUser, - ): Promise { - /* highlight-remove-next-line */ - if (isPermission(request.permission, catalogEntityDeletePermission)) { - /* highlight-add-next-line */ - if (isResourcePermission(request.permission, 'catalog-entity')) { - return createCatalogConditionalDecision( - request.permission, - catalogConditions.isEntityOwner({ - claims: user?.info.ownershipEntityRefs ?? [], - }), - ); - } - - return { result: AuthorizeResult.ALLOW }; - } -} -``` - -In this example, we use [`isResourcePermission`](https://backstage.io/docs/reference/plugin-permission-common.isresourcepermission) to match all permissions with a resource type of `catalog-entity`. Just like `isPermission`, this helper will "narrow" the type of `request.permission` and enable the use of `createCatalogConditionalDecision`. In addition to the behavior you observed before, you should also see that catalog entities are no longer visible unless you are the owner - success! - -_Note:_ Some catalog permissions do not have the `'catalog-entity'` resource type, such as [`catalogEntityCreatePermission`](https://github.com/backstage/backstage/blob/1e5e9fb9de9856a49e60fc70c38a4e4e94c69570/plugins/catalog-common/src/permissions.ts#L49). In those cases, a definitive decision is required because conditions can't be applied to an entity that does not exist yet. diff --git a/docs/permissions/writing-a-policy.md b/docs/permissions/writing-a-policy.md index ce662b6e1a..a479684287 100644 --- a/docs/permissions/writing-a-policy.md +++ b/docs/permissions/writing-a-policy.md @@ -5,7 +5,7 @@ description: How to write your own permission policy as a Backstage integrator --- :::info -This documentation is written for [the new backend system](../backend-system/index.md) which is the default since Backstage [version 1.24](../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](./writing-a-policy--old.md) instead, and [consider migrating](../backend-system/building-backends/08-migrating.md)! +This documentation is written for [the new backend system](../backend-system/index.md) which is the default since Backstage [version 1.24](../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](https://github.com/backstage/backstage/blob/v1.37.0/docs/permissions/writing-a-policy--old.md) instead, and [consider migrating](../backend-system/building-backends/08-migrating.md)! ::: In the [previous section](./getting-started.md), we were able to set up the permission framework and make a simple change to our `TestPermissionPolicy` to confirm that policy is indeed wired up correctly. diff --git a/docs/plugins/integrating-search-into-plugins--old.md b/docs/plugins/integrating-search-into-plugins--old.md deleted file mode 100644 index 2729dd3d21..0000000000 --- a/docs/plugins/integrating-search-into-plugins--old.md +++ /dev/null @@ -1,421 +0,0 @@ ---- -id: integrating-search-into-plugins--old -title: Integrating Search into a plugin -description: How to integrate Search into a Backstage plugin ---- - -:::info -This documentation is written for the old backend which has been replaced by [the new backend system](../backend-system/index.md), being the default since Backstage [version 1.24](../releases/v1.24.0.md). If have migrated to the new backend system, you may want to read [its own article](./integrating-search-into-plugins.md) instead. Otherwise, [consider migrating](../backend-system/building-backends/08-migrating.md)! -::: - -The Backstage Search Platform was designed to give plugin developers the APIs -and interfaces needed to offer search experiences within their plugins, while -abstracting away (and instead empowering application integrators to choose) the -specific underlying search technologies. - -On this page, you'll find concepts and tutorials for leveraging the Backstage -Search Platform in your plugin. - -## Providing data to the search platform - -### Create a collator - -> Knowing what a [collator](../features/search/concepts.md#collators) is will help you as you build it out. - -Imagine you have a plugin that is responsible for storing FAQ snippets in a database. You want other engineers to be able to easily find your questions and answers. So that means you want them to be indexed by the search platform. Lets say the FAQ snippets can be viewed at a URL like `backstage.example.biz/faq-snippets`. - -The search platform provides an interface (`DocumentCollatorFactory` from package `@backstage/plugin-search-common`) that allows you to do exactly that. It works by registering each of your entries as a "document" that later represents one search result each. - -> You can always look at a working example, e.g. [StackOverflowQuestionsCollatorFactory](https://github.com/backstage/backstage/blob/master/plugins/search-backend-module-stack-overflow-collator/src/collators/StackOverflowQuestionsCollatorFactory.ts), if you are unsure or want to follow best practices. - -#### 1. Install collator interface dependencies - -We will need the interface `DocumentCollatorFactory` from package `@backstage/plugin-search-common`, so let's add it to your plugins dependencies: - -```sh -# navigate to the plugin directory -# (for this tutorial our plugin lives in the backstage repo, if your plugin lives in a separate repo you need to clone that first) -cd plugins/faq-snippets - -# Create a new branch using Git command-line -git checkout -b tutorials/new-faq-snippets-collator - -# Install the package containing the interface -yarn add @backstage/plugin-search-common -``` - -#### 2. Define your document type - -Before we can start generating documents from our FAQ entries, we first have to define a document type containing all necessary information we need to later display our entry as search result. The package `@backstage/plugin-search-common` we installed earlier contains a type `IndexableDocument` that we can extend. - -Create a new file `plugins/faq-snippets/src/search/collators/FaqSnippetDocument.ts` and paste the following below: - -```ts -import { IndexableDocument } from '@backstage/plugin-search-common'; - -export interface FaqSnippetDocument extends IndexableDocument { - answered_by: string; -} -``` - -#### 3. Use Backstage App configuration - -Your new collator could benefit from using configuration directly from the Backstage `app-config.yaml` file which is located on the project's root folder: - -```yaml -faq: - baseUrl: https://backstage.example.biz/faq-snippets -``` - -#### 4. Implement your collator - -Imagine your FAQs can be retrieved at the URL `https://backstage.example.biz/faq-snippets` with following JSON response format: - -```json -{ - "items": [ - { - "id": 42, - "question": "What is The Answer to the Ultimate Question of Life, the Universe, and Everything?", - "answer": "Forty-two", - "user": "Deep Thought" - } - ] -} -``` - -Below we provide an example implementation of how the FAQ collator factory could look like using our new document type, placed in the `plugins/faq-snippets/src/search/collators/FaqCollatorFactory.ts` file: - -```ts -import fetch from 'cross-fetch'; -import { Logger } from 'winston'; -import { Config } from '@backstage/config'; -import { Readable } from 'stream'; -import { DocumentCollatorFactory } from '@backstage/plugin-search-common'; - -import { FaqDocument } from './FaqDocument'; - -export type FaqCollatorFactoryOptions = { - baseUrl?: string; - logger: Logger; -}; - -export class FaqCollatorFactory implements DocumentCollatorFactory { - private readonly baseUrl: string | undefined; - private readonly logger: Logger; - public readonly type: string = 'faq-snippets'; - - private constructor(options: FaqCollatorFactoryOptions) { - this.baseUrl = options.baseUrl; - this.logger = options.logger; - } - - static fromConfig(config: Config, options: FaqCollatorFactoryOptions) { - const baseUrl = - config.getOptionalString('faq.baseUrl') || - 'https://backstage.example.biz/faq-snippets'; - return new FaqCollatorFactory({ ...options, baseUrl }); - } - - async getCollator() { - return Readable.from(this.execute()); - } - - async *execute(): AsyncGenerator { - if (!this.baseUrl) { - this.logger.error(`No faq.baseUrl configured in your app-config.yaml`); - return; - } - - const response = await fetch(this.baseUrl); - const data = await response.json(); - - for (const faq of data.items) { - yield { - title: faq.question, - location: `/faq-snippets/${faq.id}`, - text: faq.answer, - answered_by: faq.user, - }; - } - } -} -``` - -#### 5. Test your collator - -To verify your implementation works as expected make sure to add tests for it. For your convenience, there is the [`TestPipeline`](https://backstage.io/docs/reference/plugin-search-backend-node.testpipeline) utility that emulates a pipeline into which you can integrate your custom collator. - -Look at [DefaultTechDocsCollatorFactory test](https://github.com/backstage/backstage/blob/de294ce5c410c9eb56da6870a1fab795268f60e3/plugins/techdocs-backend/src/search/DefaultTechDocsCollatorFactory.test.ts), for an example. - -#### 6. Make your plugins collator discoverable for others - -If you want to make your collator discoverable for other adopters, add it to the list of [plugins integrated to search](https://backstage.io/docs/features/search/#plugins-integrated-with-backstage-search). - -## Building a search experience into your plugin - -While the core Search plugin offers components and extensions that empower app -integrators to compose a global search experience, you may find that you want a -narrower search experience just within your plugin. This could be as literal as -an autocomplete-style search bar focused on documents provided by your plugin -(for example, the [TechDocsSearch](https://github.com/backstage/backstage/blob/master/plugins/techdocs/src/search/components/TechDocsSearch.tsx) -component), or as abstract as a widget that presents a list of links that -are contextually related to something else on the page. - -### Search Experience Concepts - -Knowing these high-level concepts will help you as you craft your in-plugin -search experience. - -- All search experiences must be wrapped in a ``, which - is provided by `@backstage/plugin-search-react`. This context keeps track - of state necessary to perform search queries and display any results. As - inputs to the query are updated (e.g. a `term` or `filter` values), the - updated query is executed and `results` are refreshed. Check out the - [SearchContextValue](https://backstage.io/docs/reference/plugin-search-react.searchcontextvalue) - for details. -- The aforementioned state can be modified and/or consumed via the - `useSearch()` hook, also exported by `@backstage/plugin-search-react`. -- For more literal search experiences, reusable components are available - to import and compose into a cohesive experience in your plugin (e.g. - `` or ``). You can see all such - components in [Backstage's storybook](https://backstage.io/storybook/?path=/story/plugins-search-searchbar--default). - -### Search Experience Tutorials - -The following tutorials make use of packages and plugins that you may not yet -have as dependencies for your plugin; be sure to add them before you use them! - -- [`@backstage/plugin-search-react`](https://www.npmjs.com/package/@backstage/plugin-search-react) - A - package containing components, hooks, and types that are shared across all - frontend plugins, including plugins like yours! -- [`@backstage/plugin-search`](https://www.npmjs.com/package/@backstage/plugin-search) - The - main search plugin, used by app integrators to compose global search - experiences. -- [`@backstage/core-components`](https://www.npmjs.com/package/@backstage/core-components) - A - package containing generic components useful for a variety of experiences - built in Backstage. - -#### Improved "404" page experience - -Imagine you have a plugin that allows users to manage _widgets_. Perhaps they -can be viewed at a URL like `backstage.example.biz/widgets/{widgetName}`. -At some point, a widget is renamed, and links to that widget's page from -chat systems, wikis, or browser bookmarks become stale, resulting in errors or -404s. - -What if instead of showing a broken page or the generic "looks like someone -dropped the mic" 404 page, you showed a list of possibly related widgets? - -```javascript -import { Link } from '@backstage/core-components'; -import { SearchResult } from '@backstage/plugin-search'; -import { SearchContextProvider } from '@backstage/plugin-search-react'; - -export const Widget404Page = ({ widgetName }) => { - // Supplying this to runs a pre-filtered search with - // the given widgetName as the search term, focused on search result of type - // "widget" with no other filters. - const preFiltered = { - term: widgetName, - types: ['widget'], - filters: {}, - }; - - return ( - - {/* The component allows us to iterate through results and - display them in whatever way fits best! */} - - {({ results }) => ( - {results.map(({ document }) => ( - - {document.title} - - ))} - )} - - - ); -); -``` - -Not all search experiences require user input! As you can see, it's possible to -leverage the Backstage Search Platform's frontend framework without necessarily -giving users input controls. - -#### Simple search page - -Of course, it's also possible to provide a more fully featured search -experience in your plugin. The simplest way is to leverage reusable components -provided by the `@backstage/plugin-search` package, like this: - -```javascript -import { useProfile } from '@internal/api'; -import { - Content, - ContentHeader, - PageWithHeader, -} from '@backstage/core-components'; -import { SearchBar, SearchResult } from '@backstage/plugin-search'; -import { SearchContextProvider } from '@backstage/plugin-search-react'; - -export const ManageMyWidgets = () => { - const { primaryTeam } = useProfile(); - // In this example, note how we are pre-filtering results down to a specific - // owner field value (the currently logged-in user's team), but allowing the - // search term to be controlled by the user via the component. - const preFiltered = { - types: ['widget'], - term: '', - filters: { - owner: primaryTeam, - }, - }; - - return ( - - - - - - - {/* Render results here, just like above */} - - - - - ); -}; -``` - -#### Custom search control surfaces - -If the reusable search components provided by `@backstage/plugin-search` aren't -adequate, no problem! There's an API in place that you can use to author your -own components to control the various parts of the search context. - -```javascript -import { useSearch } from '@backstage/plugin-search-react'; -import ChipInput from 'material-ui-chip-input'; - -export const CustomChipFilter = ({ name }) => { - const { filters, setFilters } = useSearch(); - const chipValues = filters[name] || []; - - // When a chip value is changed, update the filters value by calling the - // setFilters function from the search context. - const handleChipChange = (chip, index) => { - // There may be filters set for other fields. Be sure to maintain them. - setFilters(prevState => { - const { [name]: filter = [], ...others } = prevState; - - if (index === undefined) { - filter.push(chip); - } else { - filter.splice(index, 1); - } - - return { ...others, [name]: filter }; - }); - }; - - return ( - - ); -}; -``` - -Check out the [SearchContextValue type](https://github.com/backstage/backstage/blob/master/plugins/search-react/src/context/SearchContext.tsx) -for more details on what methods and values are available for manipulating and -reading the search context. - -If you produce something generic and reusable, consider contributing your -component upstream so that all users of the Backstage Search Platform can -benefit. Issues and pull requests welcome. - -#### Custom search results - -Search results throughout Backstage are rendered as lists so that list items can easily be customized; although a [default result list item](https://backstage.io/storybook/?path=/story/plugins-search-defaultresultlistitem--default) is available, plugins are in the best position to provide custom result list items that surface relevant information only known to the plugin. - -The example below imagines `YourCustomSearchResult` as a type of search result that contains associated `tags` which could be rendered as chips below the title/text. - -```tsx -import { Link } from '@backstage/core-components'; -import { useAnalytics } from '@backstage/core-plugin-api'; -import { ResultHighlight } from '@backstage/plugin-search-common'; -import { HighlightedSearchResultText } from '@backstage/plugin-search-react'; - -type CustomSearchResultListItemProps = { - result: YourCustomSearchResult; - rank?: number; - highlight?: ResultHighlight; -}; - -export const CustomSearchResultListItem = ( - props: CustomSearchResultListItemProps, -) => { - const { title, text, location, tags } = props.result; - - const analytics = useAnalytics(); - const handleClick = () => { - analytics.captureEvent('discover', title, { - attributes: { to: location }, - value: props.rank, - }); - }; - - return ( - - - - - ) : ( - title - ) - } - secondary={ - highlight?.fields?.text ? ( - - ) : ( - text - ) - } - /> - {tags && - tags.map((tag: string) => ( - - ))} - - - - - ); -}; -``` - -The optional use of the `` component makes it possible to highlight relevant parts of the result based on the user's search query. - -**Note on Analytics**: In order for app integrators to track and improve search experiences across Backstage, it's important for them to understand when and what users search for, as well as what they click on after searching. When providing a custom result component, it's your responsibility as a plugin developer to instrument it according to search analytics conventions. In particular: - -- You must use the `analytics.captureEvent` method, from the `useAnalytics()` hook (detailed [plugin analytics docs are here](./analytics.md)). -- You must ensure that the action of the event, representing a click on a search result item, is `discover`, and the subject is the `title` of the clicked result. In addition, the `to` attribute should be set to the result's `location`, and the `value` of the event must be set to the `rank` (passed in as a prop). -- You must ensure that the aforementioned `captureEvent` method is called when a user clicks the link; you should further ensure that the `noTrack` prop is added to the link (which disables default link click tracking, in favor of this custom instrumentation). - -For other examples and inspiration on custom result list items, check out the [``](https://github.com/backstage/backstage/blob/c981e83/plugins/stack-overflow/src/search/StackOverflowSearchResultListItem/StackOverflowSearchResultListItem.tsx) or [``](https://github.com/backstage/backstage/blob/c981e83/plugins/catalog/src/components/CatalogSearchResultListItem/CatalogSearchResultListItem.tsx) components. diff --git a/docs/plugins/integrating-search-into-plugins.md b/docs/plugins/integrating-search-into-plugins.md index 374a8aeb8a..d6f2037529 100644 --- a/docs/plugins/integrating-search-into-plugins.md +++ b/docs/plugins/integrating-search-into-plugins.md @@ -5,7 +5,7 @@ description: How to integrate Search into a Backstage plugin --- :::info -This documentation is written for [the new backend system](../backend-system/index.md) which is the default since Backstage [version 1.24](../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](./integrating-search-into-plugins--old.md) instead, and [consider migrating](../backend-system/building-backends/08-migrating.md)! +This documentation is written for [the new backend system](../backend-system/index.md) which is the default since Backstage [version 1.24](../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](https://github.com/backstage/backstage/blob/v1.37.0/docs/plugins/integrating-search-into-plugins--old.md) instead, and [consider migrating](../backend-system/building-backends/08-migrating.md)! ::: The Backstage Search Platform was designed to give plugin developers the APIs From 73f6cc3157f5d1fd425bd5ede1fdae2a8071a40f Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 29 Apr 2025 16:56:57 +0200 Subject: [PATCH 045/109] core-{app,plugin}-api: add support for JSX in translation messages Signed-off-by: Patrik Oldsberg --- .changeset/cool-bikes-push.md | 5 ++ .changeset/wicked-dingos-stand.md | 5 ++ docs/plugins/internationalization.md | 37 +++++++++++ ...test.ts => I18nextTranslationApi.test.tsx} | 52 +++++++++++++++ .../TranslationApi/I18nextTranslationApi.ts | 63 +++++++++++++++++-- .../apis/definitions/TranslationApi.test.ts | 27 +++++--- .../src/apis/definitions/TranslationApi.ts | 52 +++++++++++---- 7 files changed, 214 insertions(+), 27 deletions(-) create mode 100644 .changeset/cool-bikes-push.md create mode 100644 .changeset/wicked-dingos-stand.md rename packages/core-app-api/src/apis/implementations/TranslationApi/{I18nextTranslationApi.test.ts => I18nextTranslationApi.test.tsx} (93%) diff --git a/.changeset/cool-bikes-push.md b/.changeset/cool-bikes-push.md new file mode 100644 index 0000000000..f78510f37e --- /dev/null +++ b/.changeset/cool-bikes-push.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-app-api': patch +--- + +Updated `I18nextTranslationApi` to support interpolation with the new `jsx` format. diff --git a/.changeset/wicked-dingos-stand.md b/.changeset/wicked-dingos-stand.md new file mode 100644 index 0000000000..39d59aee1d --- /dev/null +++ b/.changeset/wicked-dingos-stand.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-plugin-api': patch +--- + +Added a new `jsx` interpolation format to `TranslationsApi`. If any of the interpolations in the default translation message uses the `jsx` format, the translation function will always return a `ReactNode`. diff --git a/docs/plugins/internationalization.md b/docs/plugins/internationalization.md index 1fbf54d94e..1dfcca17e7 100644 --- a/docs/plugins/internationalization.md +++ b/docs/plugins/internationalization.md @@ -147,6 +147,43 @@ export const myPluginTranslationRef = createTranslationRef({ }); ``` +#### React Nodes + +In addition to the default formats that `i18next` supports, you can also use the `jsx` format to specify that an interpolated value is a `ReactNode`. + +For example, you might define the following messages: + +```ts title="define the message" +export const myPluginTranslationRef = createTranslationRef({ + id: 'plugin.my-plugin', + messages: { + entityPage: { + redirect: + 'The entity you are looking for has been moved to {{link, jsx}}.', + newLocation: 'new location', + }, + }, +}); +``` + +Which can be used within a component like this: + +```tsx title="use within a component" +const { t } = useTranslationRef(myPluginTranslationRef); + +return ( +
+ {t('entityPage.redirect', { + link: {t('entityPage.newLocation')}, + })} +
+); +``` + +Note that whenever you use the `jsx` format in a message, the return value from the `t` function will be a `ReactNode`. + +When overriding a message you must always keep the `jsx` format for any interpolated values that use it in the original message. + ## For an application developer overwrite plugin messages Step 1: Create translation resources diff --git a/packages/core-app-api/src/apis/implementations/TranslationApi/I18nextTranslationApi.test.ts b/packages/core-app-api/src/apis/implementations/TranslationApi/I18nextTranslationApi.test.tsx similarity index 93% rename from packages/core-app-api/src/apis/implementations/TranslationApi/I18nextTranslationApi.test.ts rename to packages/core-app-api/src/apis/implementations/TranslationApi/I18nextTranslationApi.test.tsx index d5b812c5d7..54d84d2aed 100644 --- a/packages/core-app-api/src/apis/implementations/TranslationApi/I18nextTranslationApi.test.ts +++ b/packages/core-app-api/src/apis/implementations/TranslationApi/I18nextTranslationApi.test.tsx @@ -23,6 +23,7 @@ import { import { Observable } from '@backstage/types'; import { AppLanguageSelector } from '../AppLanguageApi'; import { I18nextTranslationApi } from './I18nextTranslationApi'; +import { render } from '@testing-library/react'; const plainRef = createTranslationRef({ id: 'plain', @@ -538,5 +539,56 @@ describe('I18nextTranslationApi', () => { expect(snapshot.t('derpWithCount', { count: 2 })).toBe('2 derps'); expect(snapshot.t('derpWithCount', { count: 0 })).toBe('0 derps'); }); + + it('should support jsx formatting', () => { + const snapshot = snapshotWithMessages({ + jsx: '{{ hello, jsx }}, {{ world, jsx }}!', + }); + + expect( + render( + snapshot.t('jsx', { + replace: { + hello:

Hello

, + world:
World
, + }, + }), + ).container.textContent, + ).toBe('Hello, World!'); + + expect( + render( + snapshot.t('jsx', { + hello:

world

, + world:
hello
, + }), + ).container.textContent, + ).toBe('world, hello!'); + }); + + it('should support jsx formatting with nested interpolations', () => { + const snapshot = snapshotWithMessages({ + message: '$t(foo), $t(bar)', + foo: 'foo={{ foo, jsx }}', + bar: 'bar={{ bar, jsx }}', + }); + + expect( + render( + snapshot.t('message', { + foo: ( +
+ foo +
+ ), + bar: ( +
+ bar +
+ ), + }), + ).container.textContent, + ).toBe('foo=foo, bar=bar'); + }); }); }); 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 d0ef35568f..6f29a440c2 100644 --- a/packages/core-app-api/src/apis/implementations/TranslationApi/I18nextTranslationApi.ts +++ b/packages/core-app-api/src/apis/implementations/TranslationApi/I18nextTranslationApi.ts @@ -39,6 +39,7 @@ import { } from '../../../../../core-plugin-api/src/translation/TranslationRef'; import { Observable } from '@backstage/types'; import { DEFAULT_LANGUAGE } from '../AppLanguageApi/AppLanguageSelector'; +import { createElement, Fragment, ReactNode } from 'react'; /** @alpha */ export interface I18nextTranslationApiOptions { @@ -161,6 +162,21 @@ export class I18nextTranslationApi implements TranslationApi { throw new Error('i18next was unexpectedly not initialized'); } + if (!i18n.services.formatter) { + throw new Error('i18next was unexpectedly missing formatter'); + } + + const elementMarker = Math.random().toString(36).substring(2, 8); + const elementMarkerPattern = new RegExp(`\\$${elementMarker}\\(([^)]+)\\)`); + i18n.services.formatter.add( + 'jsx', + ( + _value: ReactNode, + _lng: string | undefined, + formatOptions: { interpolationkey: string }, + ) => `$${elementMarker}(${btoa(formatOptions.interpolationkey)})`, + ); + const { language: initialLanguage } = options.languageApi.getLanguage(); if (initialLanguage !== DEFAULT_LANGUAGE) { i18n.changeLanguage(initialLanguage); @@ -198,6 +214,7 @@ export class I18nextTranslationApi implements TranslationApi { i18n, loader, options.languageApi.getLanguage().language, + elementMarkerPattern, ); options.languageApi.language$().subscribe(({ language }) => { @@ -210,16 +227,23 @@ export class I18nextTranslationApi implements TranslationApi { #i18n: I18n; #loader: ResourceLoader; #language: string; + #elementMarkerPattern: RegExp; /** Keep track of which refs we have registered default resources for */ #registeredRefs = new Set(); /** Notify observers when language changes */ #languageChangeListeners = new Set<() => void>(); - private constructor(i18n: I18n, loader: ResourceLoader, language: string) { + private constructor( + i18n: I18n, + loader: ResourceLoader, + language: string, + elementMarkerPattern: RegExp, + ) { this.#i18n = i18n; this.#loader = loader; this.#language = language; + this.#elementMarkerPattern = elementMarkerPattern; } getTranslation( @@ -297,10 +321,39 @@ export class I18nextTranslationApi implements TranslationApi { return { ready: false }; } - const t = this.#i18n.getFixedT( - null, - internalRef.id, - ) as TranslationFunction; + const unwrappedT = this.#i18n.getFixedT(null, internalRef.id); + + const t = ((key: string, options?: any) => { + // Overriding the return options is not allowed via TranslationFunction, + // so this will always be a string + const result = unwrappedT(key, options) as unknown as string; + + const split = result.split(this.#elementMarkerPattern); + if (split.length === 1) { + return split[0]; + } + + return createElement( + Fragment, + null, + ...split + .map((part, index) => { + if (index % 2 === 0) { + return part; + } + + const interpolationKey = atob(part); + const container = options.replace ?? options; + if (interpolationKey in container) { + return container[interpolationKey]; + } + throw new Error( + `Translation options did not provide a JSX node for interpolation key '${interpolationKey}'`, + ); + }) + .filter(Boolean), + ); + }) as TranslationFunction; return { ready: true, diff --git a/packages/core-plugin-api/src/apis/definitions/TranslationApi.test.ts b/packages/core-plugin-api/src/apis/definitions/TranslationApi.test.ts index ba5d43e896..490601a9e8 100644 --- a/packages/core-plugin-api/src/apis/definitions/TranslationApi.test.ts +++ b/packages/core-plugin-api/src/apis/definitions/TranslationApi.test.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import { ReactNode } from 'react'; import { TranslationFunction } from './TranslationApi'; function unused(..._any: any[]) {} @@ -171,24 +172,28 @@ describe('TranslationFunction', () => { datetime: '{{x, dateTime}}'; relativeTimeOptions: '{{x, relativeTime(quarter)}}'; list: '{{x, list}}'; + jsx: '{{x, jsx}}'; + jsxNested: '$t(jsx)'; }>; expect(f).toBeDefined(); - f('none', { replace: { x: 'x' } }); - f('number', { x: 1 }); + f('none', { replace: { x: 'x' } }) satisfies string; + f('number', { x: 1 }) satisfies string; f('number', { replace: { x: 1 }, formatParams: { x: { minimumFractionDigits: 2 } }, - }); - f('numberOptions', { x: 1 }); - f('currency', { replace: { x: 1 } }); - f('datetime', { x: new Date() }); - f('relativeTimeOptions', { replace: { x: 1 } }); + }) satisfies string; + f('numberOptions', { x: 1 }) satisfies string; + f('currency', { replace: { x: 1 } }) satisfies string; + f('datetime', { x: new Date() }) satisfies string; + f('relativeTimeOptions', { replace: { x: 1 } }) satisfies string; f('relativeTimeOptions', { replace: { x: 1 }, formatParams: { x: { style: 'short' } }, - }); - f('list', { replace: { x: ['a', 'b', 'c'] } }); + }) satisfies string; + f('list', { replace: { x: ['a', 'b', 'c'] } }) satisfies string; + f('jsx', { replace: { x: '' } }) satisfies ReactNode; + f('jsxNested', { replace: { x: '' } }) satisfies ReactNode; // @ts-expect-error f('none', { x: 1 }); // @ts-expect-error @@ -208,6 +213,10 @@ describe('TranslationFunction', () => { }); // @ts-expect-error f('list', { x: [1, 2, 3] }); + // @ts-expect-error + f('jsx', { x: Symbol('not-a-node') }); + // @ts-expect-error + f('jsxNested', { x: Symbol('not-a-node') }); }); it('should support nesting', () => { diff --git a/packages/core-plugin-api/src/apis/definitions/TranslationApi.ts b/packages/core-plugin-api/src/apis/definitions/TranslationApi.ts index dba8753828..c7e280f0da 100644 --- a/packages/core-plugin-api/src/apis/definitions/TranslationApi.ts +++ b/packages/core-plugin-api/src/apis/definitions/TranslationApi.ts @@ -17,6 +17,7 @@ import { ApiRef, createApiRef } from '@backstage/core-plugin-api'; import { Expand, ExpandRecursive, Observable } from '@backstage/types'; import { TranslationRef } from '../../translation'; +import { ReactNode } from 'react'; /** * Base translation options. @@ -64,6 +65,10 @@ type I18nextFormatMap = { type: string[]; options: Intl.ListFormatOptions; }; + jsx: { + type: ReactNode; + options: {}; + }; }; /** @@ -279,7 +284,7 @@ type CollectOptions< */ type OptionArgs = keyof TOptions extends never ? [options?: BaseOptions] - : [options: BaseOptions & TOptions]; + : [options: Expand]; /** * @ignore @@ -299,19 +304,40 @@ type TranslationFunctionOptions< > >; -/** @alpha */ -export interface TranslationFunction< +/** + * @ignore + * Evaluates to `true` if any of the replacements for the given key in the + * provided set of messages uses the `jsx` format. + */ +type HasJsxFormat< + TKey extends keyof TMessages, TMessages extends { [key in string]: string }, -> { - >( - key: TKey, - ...[args]: TranslationFunctionOptions< - NestedMessageKeys>, - PluralKeys, - CollapsedMessages - > - ): CollapsedMessages[TKey]; -} +> = UnionToIntersection< + ReplaceFormatsFromMessage]> +> extends infer IFormatMap + ? 'jsx' extends IFormatMap[keyof IFormatMap] + ? true + : false + : never; + +/** @alpha */ +export type TranslationFunction = + CollapsedMessages extends infer IMessages extends { + [key in string]: string; + } + ? { + ( + key: TKey, + ...[args]: TranslationFunctionOptions< + NestedMessageKeys, + PluralKeys, + IMessages + > + ): HasJsxFormat extends true + ? ReactNode + : IMessages[TKey]; + } + : never; /** @alpha */ export type TranslationSnapshot = From b5733414abbfeed5b364b180b50f8ad32faf92e0 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 29 Apr 2025 17:59:59 +0200 Subject: [PATCH 046/109] core-app-api,test-utils: refactor i18n jsx interpolation to re-use in mock implementation Signed-off-by: Patrik Oldsberg --- .changeset/brave-donuts-sink.md | 5 + .../I18nextTranslationApi.test.tsx | 14 ++- .../TranslationApi/I18nextTranslationApi.ts | 119 +++++++++++------- packages/core-plugin-api/report-alpha.api.md | 28 +++-- .../TranslationApi/MockTranslationApi.test.ts | 15 +++ .../apis/TranslationApi/MockTranslationApi.ts | 19 +-- 6 files changed, 132 insertions(+), 68 deletions(-) create mode 100644 .changeset/brave-donuts-sink.md diff --git a/.changeset/brave-donuts-sink.md b/.changeset/brave-donuts-sink.md new file mode 100644 index 0000000000..2d79d657f9 --- /dev/null +++ b/.changeset/brave-donuts-sink.md @@ -0,0 +1,5 @@ +--- +'@backstage/test-utils': patch +--- + +Added support for `jsx` interpolation format for the `MockTranslationApi`. diff --git a/packages/core-app-api/src/apis/implementations/TranslationApi/I18nextTranslationApi.test.tsx b/packages/core-app-api/src/apis/implementations/TranslationApi/I18nextTranslationApi.test.tsx index 54d84d2aed..16a83e5463 100644 --- a/packages/core-app-api/src/apis/implementations/TranslationApi/I18nextTranslationApi.test.tsx +++ b/packages/core-app-api/src/apis/implementations/TranslationApi/I18nextTranslationApi.test.tsx @@ -548,10 +548,8 @@ describe('I18nextTranslationApi', () => { expect( render( snapshot.t('jsx', { - replace: { - hello:

Hello

, - world:
World
, - }, + hello:

Hello

, + world:
World
, }), ).container.textContent, ).toBe('Hello, World!'); @@ -564,6 +562,14 @@ describe('I18nextTranslationApi', () => { }), ).container.textContent, ).toBe('world, hello!'); + + expect(() => + snapshot.t('jsx', { + world:
World
, + } as any), + ).toThrowErrorMatchingInlineSnapshot( + `"Translation options did not provide a JSX node for interpolation key 'hello'"`, + ); }); it('should support jsx formatting with nested interpolations', () => { 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 6f29a440c2..b836991c6e 100644 --- a/packages/core-app-api/src/apis/implementations/TranslationApi/I18nextTranslationApi.ts +++ b/packages/core-app-api/src/apis/implementations/TranslationApi/I18nextTranslationApi.ts @@ -138,6 +138,72 @@ class ResourceLoader { } } +/** + * A helper for implementing the `jsx` format that allows `ReactNode`s to be + * interpolated into translation messages. + */ +export class JsxInterpolator { + readonly #marker: string; + readonly #pattern: RegExp; + + static create(options?: { marker?: string }) { + return new JsxInterpolator( + options?.marker ?? Math.random().toString(36).substring(2, 8), + ); + } + + private constructor(marker: string) { + this.#marker = marker; + this.#pattern = new RegExp(`\\$${marker}\\(([^)]+)\\)`); + } + + format = ( + _value: unknown, + _lng: string | undefined, + formatOptions: { interpolationkey: string }, + ) => `$${this.#marker}(${btoa(formatOptions.interpolationkey)})`; + + wrapT( + originalT: TranslationFunction, + ): TranslationFunction { + return ((...args) => { + // Overriding the return options is not allowed via TranslationFunction, + // so this will always be a string + const result = originalT(...args); + + const options = args[1]; + if (!options) { + return result; + } + + const split = result.split(this.#pattern); + if (split.length === 1) { + return split[0]; + } + + return createElement( + Fragment, + null, + ...split + .map((part, index) => { + if (index % 2 === 0) { + return part; + } + + const interpolationKey = atob(part); + if (interpolationKey in options) { + return (options as any)[interpolationKey] as ReactNode; + } + throw new Error( + `Translation options did not provide a JSX node for interpolation key '${interpolationKey}'`, + ); + }) + .filter(Boolean), + ); + }) as TranslationFunction; + } +} + /** @alpha */ export class I18nextTranslationApi implements TranslationApi { static create(options: I18nextTranslationApiOptions) { @@ -166,16 +232,8 @@ export class I18nextTranslationApi implements TranslationApi { throw new Error('i18next was unexpectedly missing formatter'); } - const elementMarker = Math.random().toString(36).substring(2, 8); - const elementMarkerPattern = new RegExp(`\\$${elementMarker}\\(([^)]+)\\)`); - i18n.services.formatter.add( - 'jsx', - ( - _value: ReactNode, - _lng: string | undefined, - formatOptions: { interpolationkey: string }, - ) => `$${elementMarker}(${btoa(formatOptions.interpolationkey)})`, - ); + const interpolator = JsxInterpolator.create(); + i18n.services.formatter.add('jsx', interpolator.format); const { language: initialLanguage } = options.languageApi.getLanguage(); if (initialLanguage !== DEFAULT_LANGUAGE) { @@ -214,7 +272,7 @@ export class I18nextTranslationApi implements TranslationApi { i18n, loader, options.languageApi.getLanguage().language, - elementMarkerPattern, + interpolator, ); options.languageApi.language$().subscribe(({ language }) => { @@ -227,7 +285,7 @@ export class I18nextTranslationApi implements TranslationApi { #i18n: I18n; #loader: ResourceLoader; #language: string; - #elementMarkerPattern: RegExp; + #jsxInterpolator: JsxInterpolator; /** Keep track of which refs we have registered default resources for */ #registeredRefs = new Set(); @@ -238,12 +296,12 @@ export class I18nextTranslationApi implements TranslationApi { i18n: I18n, loader: ResourceLoader, language: string, - elementMarkerPattern: RegExp, + jsxInterpolator: JsxInterpolator, ) { this.#i18n = i18n; this.#loader = loader; this.#language = language; - this.#elementMarkerPattern = elementMarkerPattern; + this.#jsxInterpolator = jsxInterpolator; } getTranslation( @@ -322,38 +380,7 @@ export class I18nextTranslationApi implements TranslationApi { } const unwrappedT = this.#i18n.getFixedT(null, internalRef.id); - - const t = ((key: string, options?: any) => { - // Overriding the return options is not allowed via TranslationFunction, - // so this will always be a string - const result = unwrappedT(key, options) as unknown as string; - - const split = result.split(this.#elementMarkerPattern); - if (split.length === 1) { - return split[0]; - } - - return createElement( - Fragment, - null, - ...split - .map((part, index) => { - if (index % 2 === 0) { - return part; - } - - const interpolationKey = atob(part); - const container = options.replace ?? options; - if (interpolationKey in container) { - return container[interpolationKey]; - } - throw new Error( - `Translation options did not provide a JSX node for interpolation key '${interpolationKey}'`, - ); - }) - .filter(Boolean), - ); - }) as TranslationFunction; + const t = this.#jsxInterpolator.wrapT(unwrappedT as any); return { ready: true, diff --git a/packages/core-plugin-api/report-alpha.api.md b/packages/core-plugin-api/report-alpha.api.md index 888c0ffea0..eada7dc982 100644 --- a/packages/core-plugin-api/report-alpha.api.md +++ b/packages/core-plugin-api/report-alpha.api.md @@ -7,6 +7,7 @@ import { ApiRef } from '@backstage/core-plugin-api'; import { Expand } from '@backstage/types'; import { ExpandRecursive } from '@backstage/types'; import { Observable } from '@backstage/types'; +import { ReactNode } from 'react'; import { TranslationMessages as TranslationMessages_2 } from '@backstage/core-plugin-api/alpha'; import { TranslationRef as TranslationRef_2 } from '@backstage/core-plugin-api/alpha'; @@ -94,21 +95,26 @@ export type TranslationApi = { export const translationApiRef: ApiRef; // @alpha (undocumented) -export interface TranslationFunction< +export type TranslationFunction< TMessages extends { [key in string]: string; }, -> { - // (undocumented) - >( - key: TKey, - ...[args]: TranslationFunctionOptions< - NestedMessageKeys>, - PluralKeys, - CollapsedMessages - > - ): CollapsedMessages[TKey]; +> = CollapsedMessages extends infer IMessages extends { + [key in string]: string; } + ? { + ( + key: TKey, + ...[args]: TranslationFunctionOptions< + NestedMessageKeys, + PluralKeys, + IMessages + > + ): HasJsxFormat extends true + ? ReactNode + : IMessages[TKey]; + } + : never; // @alpha export interface TranslationMessages< diff --git a/packages/test-utils/src/testUtils/apis/TranslationApi/MockTranslationApi.test.ts b/packages/test-utils/src/testUtils/apis/TranslationApi/MockTranslationApi.test.ts index 0cc755ecb5..a1a1cd8ee2 100644 --- a/packages/test-utils/src/testUtils/apis/TranslationApi/MockTranslationApi.test.ts +++ b/packages/test-utils/src/testUtils/apis/TranslationApi/MockTranslationApi.test.ts @@ -104,6 +104,8 @@ describe('MockTranslationApi', () => { relativeSecondsShort: '= {{ x, relativeTime(range: second; style: short) }}', list: '= {{ x, list }}', + jsx: '={{ x, jsx }}', + nestedJsx: '<$t(jsx)>', }); expect(snapshot.t('plain', { x: '5' })).toBe('= 5'); @@ -146,6 +148,19 @@ describe('MockTranslationApi', () => { expect(snapshot.t('list', { x: ['a'] })).toBe('= a'); expect(snapshot.t('list', { x: ['a', 'b'] })).toBe('= a and b'); expect(snapshot.t('list', { x: ['a', 'b', 'c'] })).toBe('= a, b, and c'); + expect(snapshot.t('jsx', { x: 'hello' })).toMatchInlineSnapshot(` + + = + hello + + `); + expect(snapshot.t('nestedJsx', { x: 'hello' })).toMatchInlineSnapshot(` + + <= + hello + > + + `); }); it('should support plurals', () => { diff --git a/packages/test-utils/src/testUtils/apis/TranslationApi/MockTranslationApi.ts b/packages/test-utils/src/testUtils/apis/TranslationApi/MockTranslationApi.ts index 46c5bfd5d3..d2f53bacf0 100644 --- a/packages/test-utils/src/testUtils/apis/TranslationApi/MockTranslationApi.ts +++ b/packages/test-utils/src/testUtils/apis/TranslationApi/MockTranslationApi.ts @@ -16,7 +16,6 @@ import { TranslationApi, - TranslationFunction, TranslationRef, TranslationSnapshot, } from '@backstage/core-plugin-api/alpha'; @@ -27,6 +26,8 @@ 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'; +// eslint-disable-next-line @backstage/no-relative-monorepo-imports +import { JsxInterpolator } from '../../../../../core-app-api/src/apis/implementations/TranslationApi/I18nextTranslationApi'; const DEFAULT_LANGUAGE = 'en'; @@ -55,14 +56,19 @@ export class MockTranslationApi implements TranslationApi { throw new Error('i18next was unexpectedly not initialized'); } - return new MockTranslationApi(i18n); + const interpolator = JsxInterpolator.create({ marker: '123456' }); + i18n.services.formatter?.add('jsx', interpolator.format); + + return new MockTranslationApi(i18n, interpolator); } #i18n: I18n; + #interpolator: JsxInterpolator; #registeredRefs = new Set(); - private constructor(i18n: I18n) { + private constructor(i18n: I18n, interpolator: JsxInterpolator) { this.#i18n = i18n; + this.#interpolator = interpolator; } getTranslation( @@ -81,10 +87,9 @@ export class MockTranslationApi implements TranslationApi { ); } - const t = this.#i18n.getFixedT( - null, - internalRef.id, - ) as TranslationFunction; + const t = this.#interpolator.wrapT( + this.#i18n.getFixedT(null, internalRef.id) as any, + ); return { ready: true, From 7d445da49a2dc91b83b7da4683617a9e0c05e6d7 Mon Sep 17 00:00:00 2001 From: logonoff Date: Tue, 29 Apr 2025 14:15:16 -0400 Subject: [PATCH 047/109] fix(techdocs): Update keyboard focus on when clicking hash links Signed-off-by: logonoff --- .changeset/open-lands-shop.md | 5 +++++ .../reader/components/TechDocsReaderPageContent/dom.tsx | 7 +++++++ 2 files changed, 12 insertions(+) create mode 100644 .changeset/open-lands-shop.md diff --git a/.changeset/open-lands-shop.md b/.changeset/open-lands-shop.md new file mode 100644 index 0000000000..5205e83cbe --- /dev/null +++ b/.changeset/open-lands-shop.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-techdocs': patch +--- + +Update keyboard focus on when clicking hash links. This fixes the issue where the "skip to content" link rendered by Material MkDocs isn't focused when used. diff --git a/plugins/techdocs/src/reader/components/TechDocsReaderPageContent/dom.tsx b/plugins/techdocs/src/reader/components/TechDocsReaderPageContent/dom.tsx index c17d5fcac4..9dbb9745d3 100644 --- a/plugins/techdocs/src/reader/components/TechDocsReaderPageContent/dom.tsx +++ b/plugins/techdocs/src/reader/components/TechDocsReaderPageContent/dom.tsx @@ -229,6 +229,13 @@ export const useTechDocsReaderDom = ( transformedElement ?.querySelector(`[id="${parsedUrl.hash.slice(1)}"]`) ?.scrollIntoView(); + + // Focus first focusable element in the target section + ( + transformedElement + ?.querySelector(`[id="${parsedUrl.hash.slice(1)}"]`) + ?.querySelector('a, button, [tabindex]') as HTMLElement + )?.focus(); } } else { if (modifierActive) { From bca0248075362bbb192fa6e6d84f36ed39456020 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 29 Apr 2025 11:28:16 +0200 Subject: [PATCH 048/109] cli: move backend runner and IPC into start module Signed-off-by: Patrik Oldsberg --- .../src/modules/start/commands/package/start/startBackend.ts | 2 +- packages/cli/src/{ => modules/start}/lib/ipc/IpcServer.ts | 0 packages/cli/src/{ => modules/start}/lib/ipc/ServerDataStore.ts | 0 packages/cli/src/{ => modules/start}/lib/ipc/index.ts | 0 packages/cli/src/{ => modules/start}/lib/runner/index.ts | 0 packages/cli/src/{ => modules/start}/lib/runner/runBackend.ts | 2 +- 6 files changed, 2 insertions(+), 2 deletions(-) rename packages/cli/src/{ => modules/start}/lib/ipc/IpcServer.ts (100%) rename packages/cli/src/{ => modules/start}/lib/ipc/ServerDataStore.ts (100%) rename packages/cli/src/{ => modules/start}/lib/ipc/index.ts (100%) rename packages/cli/src/{ => modules/start}/lib/runner/index.ts (100%) rename packages/cli/src/{ => modules/start}/lib/runner/runBackend.ts (99%) diff --git a/packages/cli/src/modules/start/commands/package/start/startBackend.ts b/packages/cli/src/modules/start/commands/package/start/startBackend.ts index 417398e9f9..a09a2cfe70 100644 --- a/packages/cli/src/modules/start/commands/package/start/startBackend.ts +++ b/packages/cli/src/modules/start/commands/package/start/startBackend.ts @@ -17,7 +17,7 @@ import fs from 'fs-extra'; import { resolve as resolvePath } from 'path'; import { paths } from '../../../../../lib/paths'; -import { runBackend } from '../../../../../lib/runner'; +import { runBackend } from '../../../lib/runner'; interface StartBackendOptions { targetDir: string; diff --git a/packages/cli/src/lib/ipc/IpcServer.ts b/packages/cli/src/modules/start/lib/ipc/IpcServer.ts similarity index 100% rename from packages/cli/src/lib/ipc/IpcServer.ts rename to packages/cli/src/modules/start/lib/ipc/IpcServer.ts diff --git a/packages/cli/src/lib/ipc/ServerDataStore.ts b/packages/cli/src/modules/start/lib/ipc/ServerDataStore.ts similarity index 100% rename from packages/cli/src/lib/ipc/ServerDataStore.ts rename to packages/cli/src/modules/start/lib/ipc/ServerDataStore.ts diff --git a/packages/cli/src/lib/ipc/index.ts b/packages/cli/src/modules/start/lib/ipc/index.ts similarity index 100% rename from packages/cli/src/lib/ipc/index.ts rename to packages/cli/src/modules/start/lib/ipc/index.ts diff --git a/packages/cli/src/lib/runner/index.ts b/packages/cli/src/modules/start/lib/runner/index.ts similarity index 100% rename from packages/cli/src/lib/runner/index.ts rename to packages/cli/src/modules/start/lib/runner/index.ts diff --git a/packages/cli/src/lib/runner/runBackend.ts b/packages/cli/src/modules/start/lib/runner/runBackend.ts similarity index 99% rename from packages/cli/src/lib/runner/runBackend.ts rename to packages/cli/src/modules/start/lib/runner/runBackend.ts index ebb1d535b3..49dccf804f 100644 --- a/packages/cli/src/lib/runner/runBackend.ts +++ b/packages/cli/src/modules/start/lib/runner/runBackend.ts @@ -21,7 +21,7 @@ import { IpcServer, ServerDataStore } from '../ipc'; import debounce from 'lodash/debounce'; import { fileURLToPath } from 'url'; import { isAbsolute as isAbsolutePath } from 'path'; -import { paths } from '../paths'; +import { paths } from '../../../../lib/paths'; import spawn from 'cross-spawn'; const loaderArgs = [ From 62f444f69ed215aa1d8ba629ffe1359781a02fe3 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 29 Apr 2025 11:30:25 +0200 Subject: [PATCH 049/109] cli: move publishing utils to maintenance module Signed-off-by: Patrik Oldsberg --- packages/cli/src/modules/maintenance/commands/package/pack.ts | 2 +- packages/cli/src/modules/maintenance/commands/repo/fix.ts | 2 +- packages/cli/src/{ => modules/maintenance}/lib/publishing.ts | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename packages/cli/src/{ => modules/maintenance}/lib/publishing.ts (100%) diff --git a/packages/cli/src/modules/maintenance/commands/package/pack.ts b/packages/cli/src/modules/maintenance/commands/package/pack.ts index 510401c6d1..cdd50517ae 100644 --- a/packages/cli/src/modules/maintenance/commands/package/pack.ts +++ b/packages/cli/src/modules/maintenance/commands/package/pack.ts @@ -20,7 +20,7 @@ import { } from '../../../../modules/build/lib/packager/productionPack'; import { paths } from '../../../../lib/paths'; import fs from 'fs-extra'; -import { publishPreflightCheck } from '../../../../lib/publishing'; +import { publishPreflightCheck } from '../../lib/publishing'; import { createTypeDistProject } from '../../../../lib/typeDistProject'; export const pre = async () => { diff --git a/packages/cli/src/modules/maintenance/commands/repo/fix.ts b/packages/cli/src/modules/maintenance/commands/repo/fix.ts index 83f4da6114..231c822483 100644 --- a/packages/cli/src/modules/maintenance/commands/repo/fix.ts +++ b/packages/cli/src/modules/maintenance/commands/repo/fix.ts @@ -25,7 +25,7 @@ import { OptionValues } from 'commander'; import fs from 'fs-extra'; import { resolve as resolvePath, posix, relative as relativePath } from 'path'; import { paths } from '../../../../lib/paths'; -import { publishPreflightCheck } from '../../../../lib/publishing'; +import { publishPreflightCheck } from '../../lib/publishing'; /** * A mutable object representing a package.json file with potential fixes. diff --git a/packages/cli/src/lib/publishing.ts b/packages/cli/src/modules/maintenance/lib/publishing.ts similarity index 100% rename from packages/cli/src/lib/publishing.ts rename to packages/cli/src/modules/maintenance/lib/publishing.ts From 89c7d2e91f5b8cac386edab0a1ab105b1cf474cb Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 29 Apr 2025 11:31:59 +0200 Subject: [PATCH 050/109] cli: move codeowners lib into "new" module Signed-off-by: Patrik Oldsberg --- .../src/{ => modules/new}/lib/codeowners/codeowners.test.ts | 0 .../cli/src/{ => modules/new}/lib/codeowners/codeowners.ts | 2 +- packages/cli/src/{ => modules/new}/lib/codeowners/index.ts | 0 .../src/modules/new/lib/execution/executePortableTemplate.ts | 2 +- .../new/lib/preparation/collectPortableTemplateInput.ts | 5 +---- 5 files changed, 3 insertions(+), 6 deletions(-) rename packages/cli/src/{ => modules/new}/lib/codeowners/codeowners.test.ts (100%) rename packages/cli/src/{ => modules/new}/lib/codeowners/codeowners.ts (98%) rename packages/cli/src/{ => modules/new}/lib/codeowners/index.ts (100%) diff --git a/packages/cli/src/lib/codeowners/codeowners.test.ts b/packages/cli/src/modules/new/lib/codeowners/codeowners.test.ts similarity index 100% rename from packages/cli/src/lib/codeowners/codeowners.test.ts rename to packages/cli/src/modules/new/lib/codeowners/codeowners.test.ts diff --git a/packages/cli/src/lib/codeowners/codeowners.ts b/packages/cli/src/modules/new/lib/codeowners/codeowners.ts similarity index 98% rename from packages/cli/src/lib/codeowners/codeowners.ts rename to packages/cli/src/modules/new/lib/codeowners/codeowners.ts index 5734be95b8..32a38a71c7 100644 --- a/packages/cli/src/lib/codeowners/codeowners.ts +++ b/packages/cli/src/modules/new/lib/codeowners/codeowners.ts @@ -16,7 +16,7 @@ import fs from 'fs-extra'; import path from 'path'; -import { paths } from '../paths'; +import { paths } from '../../../../lib/paths'; const TEAM_ID_RE = /^@[-\w]+\/[-\w]+$/; const USER_ID_RE = /^@[-\w]+$/; diff --git a/packages/cli/src/lib/codeowners/index.ts b/packages/cli/src/modules/new/lib/codeowners/index.ts similarity index 100% rename from packages/cli/src/lib/codeowners/index.ts rename to packages/cli/src/modules/new/lib/codeowners/index.ts diff --git a/packages/cli/src/modules/new/lib/execution/executePortableTemplate.ts b/packages/cli/src/modules/new/lib/execution/executePortableTemplate.ts index cb076f3aa0..039575128e 100644 --- a/packages/cli/src/modules/new/lib/execution/executePortableTemplate.ts +++ b/packages/cli/src/modules/new/lib/execution/executePortableTemplate.ts @@ -15,7 +15,7 @@ */ import { assertError } from '@backstage/errors'; -import { addCodeownersEntry } from '../../../../lib/codeowners'; +import { addCodeownersEntry } from '../codeowners'; import { Task } from '../../../../lib/tasks'; import { PortableTemplate, diff --git a/packages/cli/src/modules/new/lib/preparation/collectPortableTemplateInput.ts b/packages/cli/src/modules/new/lib/preparation/collectPortableTemplateInput.ts index 23a30721b0..38e8eaddba 100644 --- a/packages/cli/src/modules/new/lib/preparation/collectPortableTemplateInput.ts +++ b/packages/cli/src/modules/new/lib/preparation/collectPortableTemplateInput.ts @@ -15,10 +15,7 @@ */ import inquirer, { DistinctQuestion } from 'inquirer'; -import { - getCodeownersFilePath, - parseOwnerIds, -} from '../../../../lib/codeowners'; +import { getCodeownersFilePath, parseOwnerIds } from '../codeowners'; import { paths } from '../../../../lib/paths'; import { PortableTemplateConfig, From e253d1d0afb4af4dd0aa8fe59ed5f127764d9cee Mon Sep 17 00:00:00 2001 From: Jessica He Date: Wed, 9 Apr 2025 16:04:06 -0400 Subject: [PATCH 051/109] improve LDAP missing metadata.name error message Signed-off-by: Jessica He --- .changeset/deep-ties-move.md | 5 ++ .../src/ldap/read.test.ts | 60 +++++++++++++++++++ .../src/ldap/read.ts | 15 +++++ 3 files changed, 80 insertions(+) create mode 100644 .changeset/deep-ties-move.md diff --git a/.changeset/deep-ties-move.md b/.changeset/deep-ties-move.md new file mode 100644 index 0000000000..57f6c7772c --- /dev/null +++ b/.changeset/deep-ties-move.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend-module-ldap': patch +--- + +Improves error reporting for missing metadata.name in LDAP catalog provider. diff --git a/plugins/catalog-backend-module-ldap/src/ldap/read.test.ts b/plugins/catalog-backend-module-ldap/src/ldap/read.test.ts index 007678c188..4e4f4f7cfd 100644 --- a/plugins/catalog-backend-module-ldap/src/ldap/read.test.ts +++ b/plugins/catalog-backend-module-ldap/src/ldap/read.test.ts @@ -988,6 +988,34 @@ describe('defaultUserTransformer', () => { }, }); }); + + it('throws and includes message when uid (metadata.name) is missing', async () => { + const config: UserConfig = { + dn: 'ddd', + options: {}, + map: { + rdn: 'uid', + name: 'uid', + displayName: 'cn', + email: 'mail', + memberOf: 'memberOf', + }, + set: {}, + }; + + const entry = searchEntry({ + description: ['description-value'], + cn: ['cn-value'], + mail: ['mail-value'], + memberOf: ['x', 'y', 'z'], + }); + + await expect( + defaultUserTransformer(DefaultLdapVendor, config, entry), + ).rejects.toThrow( + "User syncing failed: missing 'uid' attribute, consider applying a user filter to skip processing users with incomplete data.", + ); + }); }); describe('defaultGroupTransformer', () => { @@ -1073,6 +1101,38 @@ describe('defaultGroupTransformer', () => { }, }); }); + + it('throws and includes message when cn (metadata.name) is missing', async () => { + const config: GroupConfig = { + dn: 'ddd', + options: {}, + map: { + rdn: 'cn', + name: 'cn', + displayName: 'cn', + email: 'mail', + description: 'description', + type: 'type', + members: 'members', + memberOf: 'memberOf', + }, + }; + + const entry = searchEntry({ + description: ['description-value'], + mail: ['mail-value'], + avatarUrl: ['avatarUrl-value'], + memberOf: ['x', 'y', 'z'], + entryDN: ['dn-value'], + entryUUID: ['uuid-value'], + }); + + await expect( + defaultGroupTransformer(DefaultLdapVendor, config, entry), + ).rejects.toThrow( + "Group syncing failed: missing 'cn' attribute, consider applying a group filter to skip processing groups with incomplete data.", + ); + }); }); /** diff --git a/plugins/catalog-backend-module-ldap/src/ldap/read.ts b/plugins/catalog-backend-module-ldap/src/ldap/read.ts index 176d6fca7a..9b9041913d 100644 --- a/plugins/catalog-backend-module-ldap/src/ldap/read.ts +++ b/plugins/catalog-backend-module-ldap/src/ldap/read.ts @@ -34,6 +34,7 @@ import { LdapVendor } from './vendors'; import { GroupTransformer, UserTransformer } from './types'; import { mapStringAttr } from './util'; import { LoggerService } from '@backstage/backend-plugin-api'; +import { InputError } from '@backstage/errors'; /** * The default implementation of the transformation from an LDAP entry to a @@ -70,6 +71,13 @@ export async function defaultUserTransformer( mapStringAttr(entry, vendor, map.name, v => { entity.metadata.name = v; }); + + if (!entity.metadata.name) { + throw new InputError( + `User syncing failed: missing '${map.name}' attribute, consider applying a user filter to skip processing users with incomplete data.`, + ); + } + mapStringAttr(entry, vendor, map.description, v => { entity.metadata.description = v; }); @@ -180,6 +188,13 @@ export async function defaultGroupTransformer( mapStringAttr(entry, vendor, map.name, v => { entity.metadata.name = v; }); + + if (!entity.metadata.name) { + throw new InputError( + `Group syncing failed: missing '${map.name}' attribute, consider applying a group filter to skip processing groups with incomplete data.`, + ); + } + mapStringAttr(entry, vendor, map.description, v => { entity.metadata.description = v; }); From 49010f03f008199e41821495aa568171fcbcad96 Mon Sep 17 00:00:00 2001 From: Marek Libra Date: Wed, 30 Apr 2025 11:33:15 +0200 Subject: [PATCH 052/109] fix(doc): document reviveConsumedRequestBodies proxy configuration Signed-off-by: Marek Libra --- docs/plugins/proxying.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/plugins/proxying.md b/docs/plugins/proxying.md index 26329f8475..b32ed4fe12 100644 --- a/docs/plugins/proxying.md +++ b/docs/plugins/proxying.md @@ -41,6 +41,7 @@ Example: ```yaml # in app-config.yaml proxy: + reviveConsumedRequestBodies: true endpoints: /simple-example: http://simple.example.com:8080 '/larger-example/v1': @@ -121,6 +122,12 @@ third parties. The same logic applies to headers that are sent from the target back to the frontend. +### Passing POST-request body + +To fix the issue with missing request body passed by proxy to the target, set `proxy.reviveConsumedRequestBodies: true`, so the `fixRequestBody` handler of [http-proxy-middleware](https://github.com/chimurai/http-proxy-middleware?tab=readme-ov-file#intercept-and-manipulate-requests) will be used. + +In that case, mind setting the `Content-Type` header to either `application/json` or `application/x-www-form-urlencoded`. + ### Proxy Extension Endpoint The proxy plugin additionally supports a `proxyExtensionEndpoint` which a proxy From b60253da4d7d28febd858432fcbc88553b43a98e Mon Sep 17 00:00:00 2001 From: Hellgren Heikki Date: Wed, 30 Apr 2025 14:37:50 +0300 Subject: [PATCH 053/109] chore: change notification send action to use zod Signed-off-by: Hellgren Heikki --- .changeset/tame-areas-behave.md | 5 + .../report.api.md | 22 ++--- .../actions/sendNotification.examples.test.ts | 2 +- .../src/actions/sendNotification.test.ts | 2 +- .../src/actions/sendNotification.ts | 91 ++++++------------- 5 files changed, 46 insertions(+), 76 deletions(-) create mode 100644 .changeset/tame-areas-behave.md diff --git a/.changeset/tame-areas-behave.md b/.changeset/tame-areas-behave.md new file mode 100644 index 0000000000..6d5c776d8b --- /dev/null +++ b/.changeset/tame-areas-behave.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend-module-notifications': patch +--- + +Change notification send scaffolder action to use native zod schemas diff --git a/plugins/scaffolder-backend-module-notifications/report.api.md b/plugins/scaffolder-backend-module-notifications/report.api.md index cb796e5033..1d437cebd7 100644 --- a/plugins/scaffolder-backend-module-notifications/report.api.md +++ b/plugins/scaffolder-backend-module-notifications/report.api.md @@ -4,9 +4,7 @@ ```ts import { BackendFeature } from '@backstage/backend-plugin-api'; -import { JsonObject } from '@backstage/types'; import { NotificationService } from '@backstage/plugin-notifications-node'; -import { NotificationSeverity } from '@backstage/plugin-notifications-common'; import { TemplateAction } from '@backstage/plugin-scaffolder-node'; // @public (undocumented) @@ -14,17 +12,19 @@ export function createSendNotificationAction(options: { notifications: NotificationService; }): TemplateAction< { - recipients: string; - entityRefs?: string[]; + recipients: 'entity' | 'broadcast'; title: string; - info?: string; - link?: string; - severity?: NotificationSeverity; - scope?: string; - optional?: boolean; + entityRefs?: string[] | undefined; + info?: string | undefined; + link?: string | undefined; + severity?: 'normal' | 'high' | 'low' | 'critical' | undefined; + scope?: string | undefined; + optional?: boolean | undefined; }, - JsonObject, - 'v1' + { + [x: string]: any; + }, + 'v2' >; // @public diff --git a/plugins/scaffolder-backend-module-notifications/src/actions/sendNotification.examples.test.ts b/plugins/scaffolder-backend-module-notifications/src/actions/sendNotification.examples.test.ts index 84473335e0..cd6fb54c51 100644 --- a/plugins/scaffolder-backend-module-notifications/src/actions/sendNotification.examples.test.ts +++ b/plugins/scaffolder-backend-module-notifications/src/actions/sendNotification.examples.test.ts @@ -25,7 +25,7 @@ describe('notification:send', () => { send: jest.fn(), }; - let action: TemplateAction; + let action: TemplateAction; beforeEach(() => { jest.resetAllMocks(); diff --git a/plugins/scaffolder-backend-module-notifications/src/actions/sendNotification.test.ts b/plugins/scaffolder-backend-module-notifications/src/actions/sendNotification.test.ts index efd1377cae..ee87991c73 100644 --- a/plugins/scaffolder-backend-module-notifications/src/actions/sendNotification.test.ts +++ b/plugins/scaffolder-backend-module-notifications/src/actions/sendNotification.test.ts @@ -23,7 +23,7 @@ describe('notification:send', () => { send: jest.fn(), }; - let action: TemplateAction; + let action: TemplateAction; beforeEach(() => { jest.resetAllMocks(); diff --git a/plugins/scaffolder-backend-module-notifications/src/actions/sendNotification.ts b/plugins/scaffolder-backend-module-notifications/src/actions/sendNotification.ts index d1954dfd8c..a8e86f9b48 100644 --- a/plugins/scaffolder-backend-module-notifications/src/actions/sendNotification.ts +++ b/plugins/scaffolder-backend-module-notifications/src/actions/sendNotification.ts @@ -17,10 +17,7 @@ import { NotificationRecipients, NotificationService, } from '@backstage/plugin-notifications-node'; -import { - NotificationPayload, - NotificationSeverity, -} from '@backstage/plugin-notifications-common'; +import { NotificationPayload } from '@backstage/plugin-notifications-common'; import { createTemplateAction } from '@backstage/plugin-scaffolder-node'; import { examples } from './sendNotification.examples'; @@ -31,73 +28,41 @@ export function createSendNotificationAction(options: { notifications: NotificationService; }) { const { notifications } = options; - return createTemplateAction<{ - recipients: string; - entityRefs?: string[]; - title: string; - info?: string; - link?: string; - severity?: NotificationSeverity; - scope?: string; - optional?: boolean; - }>({ + return createTemplateAction({ id: 'notification:send', description: 'Sends a notification using NotificationService', examples, schema: { input: { - type: 'object', - required: ['recipients', 'title'], - properties: { - recipients: { - title: 'Recipient', - enum: ['broadcast', 'entity'], - description: + recipients: z => + z + .enum(['broadcast', 'entity']) + .describe( 'The recipient of the notification, either broadcast or entity. If using entity, also entityRef must be provided', - type: 'string', - }, - entityRefs: { - title: 'Entity references', - description: + ), + entityRefs: z => + z + .array(z.string()) + .optional() + .describe( 'The entity references to send the notification to, required if using recipient of entity', - type: 'array', - items: { - type: 'string', - }, - }, - title: { - title: 'Title', - description: 'Notification title', - type: 'string', - }, - info: { - title: 'Description', - description: 'Notification description', - type: 'string', - }, - link: { - title: 'Link', - description: 'Notification link', - type: 'string', - }, - severity: { - title: 'Severity', - type: 'string', - description: `Notification severity`, - enum: ['low', 'normal', 'high', 'critical'], - }, - scope: { - title: 'Scope', - description: 'Notification scope', - type: 'string', - }, - optional: { - title: 'Optional', - description: + ), + title: z => z.string().describe('Notification title'), + info: z => z.string().optional().describe('Notification description'), + link: z => z.string().optional().describe('Notification link'), + severity: z => + z + .enum(['low', 'normal', 'high', 'critical']) + .optional() + .describe('Notification severity'), + scope: z => z.string().optional().describe('Notification scope'), + optional: z => + z + .boolean() + .optional() + .describe( 'Do not fail the action if the notification sending fails', - type: 'boolean', - }, - }, + ), }, }, async handler(ctx) { From 3743ac12daf6d9d0bc60b92763d38cae2e10ef38 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 29 Apr 2025 11:36:16 +0200 Subject: [PATCH 054/109] cli: move yarn utils into versioning lib Signed-off-by: Patrik Oldsberg --- packages/cli/src/lib/versioning/packages.test.ts | 4 ++-- packages/cli/src/lib/versioning/packages.ts | 2 +- packages/cli/src/lib/{ => versioning}/yarn.ts | 0 3 files changed, 3 insertions(+), 3 deletions(-) rename packages/cli/src/lib/{ => versioning}/yarn.ts (100%) diff --git a/packages/cli/src/lib/versioning/packages.test.ts b/packages/cli/src/lib/versioning/packages.test.ts index 23ff927c48..78fb1d1f5e 100644 --- a/packages/cli/src/lib/versioning/packages.test.ts +++ b/packages/cli/src/lib/versioning/packages.test.ts @@ -15,7 +15,7 @@ */ import * as runObj from '../run'; -import * as yarn from '../yarn'; +import * as yarn from './yarn'; import { fetchPackageInfo, mapDependencies } from './packages'; import { NotFoundError } from '../errors'; import { createMockDirectory } from '@backstage/backend-test-utils'; @@ -27,7 +27,7 @@ jest.mock('../run', () => { }; }); -jest.mock('../yarn', () => { +jest.mock('./yarn', () => { return { detectYarnVersion: jest.fn(), }; diff --git a/packages/cli/src/lib/versioning/packages.ts b/packages/cli/src/lib/versioning/packages.ts index 0499775bf2..7a75189a5f 100644 --- a/packages/cli/src/lib/versioning/packages.ts +++ b/packages/cli/src/lib/versioning/packages.ts @@ -17,7 +17,7 @@ import { minimatch } from 'minimatch'; import { getPackages } from '@manypkg/get-packages'; import { NotFoundError } from '../errors'; -import { detectYarnVersion } from '../yarn'; +import { detectYarnVersion } from './yarn'; import { execFile } from '../run'; const DEP_TYPES = [ diff --git a/packages/cli/src/lib/yarn.ts b/packages/cli/src/lib/versioning/yarn.ts similarity index 100% rename from packages/cli/src/lib/yarn.ts rename to packages/cli/src/lib/versioning/yarn.ts From 82cb824bdd1a7f4fd1b82a8045638e610288ab99 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 29 Apr 2025 11:43:32 +0200 Subject: [PATCH 055/109] cli: move task logging utils to "new" module Signed-off-by: Patrik Oldsberg --- .../src/modules/new/lib/execution/executePortableTemplate.ts | 2 +- packages/cli/src/modules/new/lib/execution/installNewPackage.ts | 2 +- packages/cli/src/{ => modules/new}/lib/tasks.ts | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename packages/cli/src/{ => modules/new}/lib/tasks.ts (100%) diff --git a/packages/cli/src/modules/new/lib/execution/executePortableTemplate.ts b/packages/cli/src/modules/new/lib/execution/executePortableTemplate.ts index 039575128e..6d34e35e4c 100644 --- a/packages/cli/src/modules/new/lib/execution/executePortableTemplate.ts +++ b/packages/cli/src/modules/new/lib/execution/executePortableTemplate.ts @@ -16,7 +16,7 @@ import { assertError } from '@backstage/errors'; import { addCodeownersEntry } from '../codeowners'; -import { Task } from '../../../../lib/tasks'; +import { Task } from '../tasks'; import { PortableTemplate, PortableTemplateConfig, diff --git a/packages/cli/src/modules/new/lib/execution/installNewPackage.ts b/packages/cli/src/modules/new/lib/execution/installNewPackage.ts index 733a688bfb..0fe0284386 100644 --- a/packages/cli/src/modules/new/lib/execution/installNewPackage.ts +++ b/packages/cli/src/modules/new/lib/execution/installNewPackage.ts @@ -17,7 +17,7 @@ import fs from 'fs-extra'; import upperFirst from 'lodash/upperFirst'; import camelCase from 'lodash/camelCase'; import { paths } from '../../../../lib/paths'; -import { Task } from '../../../../lib/tasks'; +import { Task } from '../tasks'; import { PortableTemplateInput } from '../types'; export async function installNewPackage(input: PortableTemplateInput) { diff --git a/packages/cli/src/lib/tasks.ts b/packages/cli/src/modules/new/lib/tasks.ts similarity index 100% rename from packages/cli/src/lib/tasks.ts rename to packages/cli/src/modules/new/lib/tasks.ts From e27331c490e6f20a7436bb5ad959ba2a0c49fd29 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 29 Apr 2025 11:48:49 +0200 Subject: [PATCH 056/109] cli: move url utils to build module Signed-off-by: Patrik Oldsberg --- .../cli/src/modules/build/commands/package/build/command.ts | 2 +- packages/cli/src/{ => modules/build}/lib/urls.test.ts | 0 packages/cli/src/{ => modules/build}/lib/urls.ts | 0 3 files changed, 1 insertion(+), 1 deletion(-) rename packages/cli/src/{ => modules/build}/lib/urls.test.ts (100%) rename packages/cli/src/{ => modules/build}/lib/urls.ts (100%) diff --git a/packages/cli/src/modules/build/commands/package/build/command.ts b/packages/cli/src/modules/build/commands/package/build/command.ts index 77e48841cf..d6a71b6fc1 100644 --- a/packages/cli/src/modules/build/commands/package/build/command.ts +++ b/packages/cli/src/modules/build/commands/package/build/command.ts @@ -21,7 +21,7 @@ import { PackageGraph, PackageRoles } from '@backstage/cli-node'; import { paths } from '../../../../../lib/paths'; import { buildFrontend } from '../../../lib/buildFrontend'; import { buildBackend } from '../../../lib/buildBackend'; -import { isValidUrl } from '../../../../../lib/urls'; +import { isValidUrl } from '../../../lib/urls'; import chalk from 'chalk'; export async function command(opts: OptionValues): Promise { diff --git a/packages/cli/src/lib/urls.test.ts b/packages/cli/src/modules/build/lib/urls.test.ts similarity index 100% rename from packages/cli/src/lib/urls.test.ts rename to packages/cli/src/modules/build/lib/urls.test.ts diff --git a/packages/cli/src/lib/urls.ts b/packages/cli/src/modules/build/lib/urls.ts similarity index 100% rename from packages/cli/src/lib/urls.ts rename to packages/cli/src/modules/build/lib/urls.ts From 19a4e7cbc461e0420bdd53ccbac82a477626c32a Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 29 Apr 2025 11:54:07 +0200 Subject: [PATCH 057/109] changesets: add changeset for internal CLI refactor Signed-off-by: Patrik Oldsberg --- .changeset/spicy-camels-bet.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/spicy-camels-bet.md diff --git a/.changeset/spicy-camels-bet.md b/.changeset/spicy-camels-bet.md new file mode 100644 index 0000000000..0289980ff9 --- /dev/null +++ b/.changeset/spicy-camels-bet.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +Internal refactor to move things closer to home From df5922da1128b379cb3767f059e70d16a668ea3a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 30 Apr 2025 14:58:44 +0000 Subject: [PATCH 058/109] build(deps): bump formidable from 3.5.1 to 3.5.4 Bumps [formidable](https://github.com/node-formidable/formidable) from 3.5.1 to 3.5.4. - [Release notes](https://github.com/node-formidable/formidable/releases) - [Changelog](https://github.com/node-formidable/formidable/blob/master/CHANGELOG.md) - [Commits](https://github.com/node-formidable/formidable/commits) --- updated-dependencies: - dependency-name: formidable dependency-version: 3.5.4 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- yarn.lock | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/yarn.lock b/yarn.lock index 6d4aa9e768..540359f2bd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12739,6 +12739,13 @@ __metadata: languageName: node linkType: hard +"@noble/hashes@npm:^1.1.5": + version: 1.8.0 + resolution: "@noble/hashes@npm:1.8.0" + checksum: 10/474b7f56bc6fb2d5b3a42132561e221b0ea4f91e590f4655312ca13667840896b34195e2b53b7f097ec080a1fdd3b58d902c2a8d0fbdf51d2e238b53808a177e + languageName: node + linkType: hard + "@nodelib/fs.scandir@npm:2.1.5": version: 2.1.5 resolution: "@nodelib/fs.scandir@npm:2.1.5" @@ -15174,6 +15181,15 @@ __metadata: languageName: node linkType: hard +"@paralleldrive/cuid2@npm:^2.2.2": + version: 2.2.2 + resolution: "@paralleldrive/cuid2@npm:2.2.2" + dependencies: + "@noble/hashes": "npm:^1.1.5" + checksum: 10/40ee269d6e47b4fed7706a2e4da7c27c3c668ebc969110d6d112277b6b16a67cce0503b53b9943f2c55035a72d225f77ea5541e03396d6429eec9252137a53b7 + languageName: node + linkType: hard + "@pkgjs/parseargs@npm:^0.11.0": version: 0.11.0 resolution: "@pkgjs/parseargs@npm:0.11.0" @@ -30801,13 +30817,13 @@ __metadata: linkType: hard "formidable@npm:^3.5.1": - version: 3.5.1 - resolution: "formidable@npm:3.5.1" + version: 3.5.4 + resolution: "formidable@npm:3.5.4" dependencies: + "@paralleldrive/cuid2": "npm:^2.2.2" dezalgo: "npm:^1.0.4" - hexoid: "npm:^1.0.0" once: "npm:^1.4.0" - checksum: 10/c9a7bbbd4ca8142893da88b51cf7797adee022344ea180cf157a108bf999bed5ad8bc07a10a28d8a39fcbfaa02e8cba07f4ba336fbeb330deb23907336ba1fc2 + checksum: 10/4645e6ce3d8bbefd3dd873dcd6211362da3bf8a04c8426d7f454c238be0142975f02e5bdbc792fdbd2be493fdcf5442fe01d9a246bd8c3fd8e779738290cc630 languageName: node linkType: hard @@ -32095,13 +32111,6 @@ __metadata: languageName: node linkType: hard -"hexoid@npm:^1.0.0": - version: 1.0.0 - resolution: "hexoid@npm:1.0.0" - checksum: 10/f2271b8b6b0e13fb5a1eccf740f53ce8bae689c80b9498b854c447f9dc94f75f44e0de064c0e4660ecdbfa8942bb2b69973fdcb080187b45bbb409a3c71f19d4 - languageName: node - linkType: hard - "hey-listen@npm:^1.0.8": version: 1.0.8 resolution: "hey-listen@npm:1.0.8" From 0bc1804ce5166d6adcfd20b9b1a7e27fbe74b1c8 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 29 Apr 2025 22:27:02 +0200 Subject: [PATCH 059/109] core-{app,plugin}-api: switch i18n JSX support to no longer requrie explicit format Signed-off-by: Patrik Oldsberg --- .changeset/brave-donuts-sink.md | 2 +- .changeset/cool-bikes-push.md | 2 +- .changeset/wicked-dingos-stand.md | 2 +- docs/plugins/internationalization.md | 19 ++-- .../I18nextTranslationApi.test.tsx | 41 ++++--- .../TranslationApi/I18nextTranslationApi.ts | 103 +++++++++++------- packages/core-plugin-api/report-alpha.api.md | 18 ++- ...ionApi.test.ts => TranslationApi.test.tsx} | 74 +++++++------ .../src/apis/definitions/TranslationApi.ts | 65 +++++------ ...pi.test.ts => MockTranslationApi.test.tsx} | 61 ++++++++--- .../apis/TranslationApi/MockTranslationApi.ts | 7 +- 11 files changed, 237 insertions(+), 157 deletions(-) rename packages/core-plugin-api/src/apis/definitions/{TranslationApi.test.ts => TranslationApi.test.tsx} (79%) rename packages/test-utils/src/testUtils/apis/TranslationApi/{MockTranslationApi.test.ts => MockTranslationApi.test.tsx} (87%) diff --git a/.changeset/brave-donuts-sink.md b/.changeset/brave-donuts-sink.md index 2d79d657f9..47827345a5 100644 --- a/.changeset/brave-donuts-sink.md +++ b/.changeset/brave-donuts-sink.md @@ -2,4 +2,4 @@ '@backstage/test-utils': patch --- -Added support for `jsx` interpolation format for the `MockTranslationApi`. +Added support for interpolating JSX elements with the `MockTranslationApi`. diff --git a/.changeset/cool-bikes-push.md b/.changeset/cool-bikes-push.md index f78510f37e..00825c1ad0 100644 --- a/.changeset/cool-bikes-push.md +++ b/.changeset/cool-bikes-push.md @@ -2,4 +2,4 @@ '@backstage/core-app-api': patch --- -Updated `I18nextTranslationApi` to support interpolation with the new `jsx` format. +Updated `I18nextTranslationApi` to support interpolation of JSX elements. diff --git a/.changeset/wicked-dingos-stand.md b/.changeset/wicked-dingos-stand.md index 39d59aee1d..92d84825d1 100644 --- a/.changeset/wicked-dingos-stand.md +++ b/.changeset/wicked-dingos-stand.md @@ -2,4 +2,4 @@ '@backstage/core-plugin-api': patch --- -Added a new `jsx` interpolation format to `TranslationsApi`. If any of the interpolations in the default translation message uses the `jsx` format, the translation function will always return a `ReactNode`. +The `TranslationApi` now supports interpolation of JSX elements by passing them directly as values to the translation function. If any of the provided interpolation values are JSX elements, the translation function will return a JSX element instead of a string. diff --git a/docs/plugins/internationalization.md b/docs/plugins/internationalization.md index 1dfcca17e7..4eb5e864d1 100644 --- a/docs/plugins/internationalization.md +++ b/docs/plugins/internationalization.md @@ -147,9 +147,9 @@ export const myPluginTranslationRef = createTranslationRef({ }); ``` -#### React Nodes +#### JSX Elements -In addition to the default formats that `i18next` supports, you can also use the `jsx` format to specify that an interpolated value is a `ReactNode`. +The translation API supports interpolation of JSX elements by passing them directly as values to the translation function. If any of the provided interpolation values are JSX elements, the translation function will return a JSX element instead of a string. For example, you might define the following messages: @@ -158,9 +158,10 @@ export const myPluginTranslationRef = createTranslationRef({ id: 'plugin.my-plugin', messages: { entityPage: { - redirect: - 'The entity you are looking for has been moved to {{link, jsx}}.', - newLocation: 'new location', + redirect: { + message: 'The entity you are looking for has been moved to {{link}}.', + link: 'new location', + }, }, }, }); @@ -173,16 +174,14 @@ const { t } = useTranslationRef(myPluginTranslationRef); return (
- {t('entityPage.redirect', { - link: {t('entityPage.newLocation')}, + {t('entityPage.redirect.message', { + link: {t('entityPage.redirect.link')}, })}
); ``` -Note that whenever you use the `jsx` format in a message, the return value from the `t` function will be a `ReactNode`. - -When overriding a message you must always keep the `jsx` format for any interpolated values that use it in the original message. +The return type of the outer `t` function will be a `JSX.Element`, with the underlying value being a React fragment of the different parts of the message. ## For an application developer overwrite plugin messages diff --git a/packages/core-app-api/src/apis/implementations/TranslationApi/I18nextTranslationApi.test.tsx b/packages/core-app-api/src/apis/implementations/TranslationApi/I18nextTranslationApi.test.tsx index 16a83e5463..b6e79b20ed 100644 --- a/packages/core-app-api/src/apis/implementations/TranslationApi/I18nextTranslationApi.test.tsx +++ b/packages/core-app-api/src/apis/implementations/TranslationApi/I18nextTranslationApi.test.tsx @@ -540,9 +540,13 @@ describe('I18nextTranslationApi', () => { expect(snapshot.t('derpWithCount', { count: 0 })).toBe('0 derps'); }); - it('should support jsx formatting', () => { + it('should support jsx interpolation', () => { const snapshot = snapshotWithMessages({ - jsx: '{{ hello, jsx }}, {{ world, jsx }}!', + jsx: '{{ hello }}, {{ world }}!', + jsxMultiple: '{{ hello }} | {{ hello }}', + jsxNested: '$t(foo), $t(bar)', + foo: 'foo={{ foo }}', + bar: 'bar={{ bar }}', }); expect( @@ -563,25 +567,26 @@ describe('I18nextTranslationApi', () => { ).container.textContent, ).toBe('world, hello!'); - expect(() => - snapshot.t('jsx', { - world:
World
, - } as any), - ).toThrowErrorMatchingInlineSnapshot( - `"Translation options did not provide a JSX node for interpolation key 'hello'"`, - ); - }); - - it('should support jsx formatting with nested interpolations', () => { - const snapshot = snapshotWithMessages({ - message: '$t(foo), $t(bar)', - foo: 'foo={{ foo, jsx }}', - bar: 'bar={{ bar, jsx }}', - }); + // Missing value + expect( + render( + snapshot.t('jsx', { + hello:

hello

, + } as any), + ).container.textContent, + ).toBe('hello, {{ world }}!'); expect( render( - snapshot.t('message', { + snapshot.t('jsxMultiple', { + hello:

hello

, + } as any), + ).container.textContent, + ).toBe('hello | hello'); + + expect( + render( + snapshot.t('jsxNested', { foo: (
foo 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 b836991c6e..de21990cb3 100644 --- a/packages/core-app-api/src/apis/implementations/TranslationApi/I18nextTranslationApi.ts +++ b/packages/core-app-api/src/apis/implementations/TranslationApi/I18nextTranslationApi.ts @@ -23,7 +23,13 @@ import { TranslationResource, TranslationSnapshot, } from '@backstage/core-plugin-api/alpha'; -import { createInstance as createI18n, type i18n as I18n } from 'i18next'; +import { + createInstance as createI18n, + FormatFunction, + Interpolator, + TFunction, + type i18n as I18n, +} from 'i18next'; import ObservableImpl from 'zen-observable'; // Internal import to avoid code duplication, this will lead to duplication in build output @@ -39,7 +45,7 @@ import { } from '../../../../../core-plugin-api/src/translation/TranslationRef'; import { Observable } from '@backstage/types'; import { DEFAULT_LANGUAGE } from '../AppLanguageApi/AppLanguageSelector'; -import { createElement, Fragment, ReactNode } from 'react'; +import { createElement, Fragment, ReactNode, isValidElement } from 'react'; /** @alpha */ export interface I18nextTranslationApiOptions { @@ -139,47 +145,80 @@ class ResourceLoader { } /** - * A helper for implementing the `jsx` format that allows `ReactNode`s to be - * interpolated into translation messages. + * A helper for implementing JSX interpolation */ export class JsxInterpolator { + readonly #setFormatHook: (hook: FormatFunction) => void; readonly #marker: string; readonly #pattern: RegExp; - static create(options?: { marker?: string }) { + static fromI18n(i18n: I18n) { + const interpolator = i18n.services.interpolator as Interpolator & { + format: FormatFunction; + }; + const originalFormat = interpolator.format; + + let formatHook: FormatFunction | undefined; + + // This is the only way to override the format function of the interpolator + // without overriding the default formatters. See the behavior here: + // https://github.com/i18next/i18next/blob/c633121e57e2b6024080142d78027842bf2a6e5e/src/i18next.js#L120-L125 + interpolator.format = (value, format, lng, formatOpts) => { + if (format) { + return originalFormat(value, format, lng, formatOpts); + } + return formatHook?.(value, format, lng, formatOpts) ?? value; + }; + return new JsxInterpolator( - options?.marker ?? Math.random().toString(36).substring(2, 8), + // Using a random marker to ensure it can't be misused + Math.random().toString(36).substring(2, 8), + hook => { + formatHook = hook; + }, ); } - private constructor(marker: string) { + private constructor( + marker: string, + setFormatHook: (hook: FormatFunction) => void, + ) { + this.#setFormatHook = setFormatHook; this.#marker = marker; this.#pattern = new RegExp(`\\$${marker}\\(([^)]+)\\)`); } - format = ( - _value: unknown, - _lng: string | undefined, - formatOptions: { interpolationkey: string }, - ) => `$${this.#marker}(${btoa(formatOptions.interpolationkey)})`; - wrapT( - originalT: TranslationFunction, + originalT: TFunction, ): TranslationFunction { - return ((...args) => { + return ((key, options) => { + let elementsMap: Map | undefined = undefined; + + // There's no way to override the format hook via the translation function + // options, event though types indicate that it might be possible. + // Instead, override the format function hook before every invocation and + // rely on synchronous execution. + this.#setFormatHook(value => { + if (isValidElement(value)) { + if (!elementsMap) { + elementsMap = new Map(); + } + const elementKey = elementsMap.size.toString(); + elementsMap.set(elementKey, value); + + return `$${this.#marker}(${elementKey})`; + } + return value; + }); + // Overriding the return options is not allowed via TranslationFunction, // so this will always be a string - const result = originalT(...args); - - const options = args[1]; - if (!options) { + const result = originalT(key, options as any) as unknown as string; + if (!elementsMap) { return result; } const split = result.split(this.#pattern); - if (split.length === 1) { - return split[0]; - } return createElement( Fragment, @@ -189,14 +228,7 @@ export class JsxInterpolator { if (index % 2 === 0) { return part; } - - const interpolationKey = atob(part); - if (interpolationKey in options) { - return (options as any)[interpolationKey] as ReactNode; - } - throw new Error( - `Translation options did not provide a JSX node for interpolation key '${interpolationKey}'`, - ); + return elementsMap?.get(part); }) .filter(Boolean), ); @@ -214,6 +246,8 @@ export class I18nextTranslationApi implements TranslationApi { supportedLngs: languages, interpolation: { escapeValue: false, + // Used for the JsxInterpolator format hook + alwaysFormat: true, }, ns: [], defaultNS: false, @@ -228,12 +262,7 @@ export class I18nextTranslationApi implements TranslationApi { throw new Error('i18next was unexpectedly not initialized'); } - if (!i18n.services.formatter) { - throw new Error('i18next was unexpectedly missing formatter'); - } - - const interpolator = JsxInterpolator.create(); - i18n.services.formatter.add('jsx', interpolator.format); + const interpolator = JsxInterpolator.fromI18n(i18n); const { language: initialLanguage } = options.languageApi.getLanguage(); if (initialLanguage !== DEFAULT_LANGUAGE) { @@ -380,7 +409,7 @@ export class I18nextTranslationApi implements TranslationApi { } const unwrappedT = this.#i18n.getFixedT(null, internalRef.id); - const t = this.#jsxInterpolator.wrapT(unwrappedT as any); + const t = this.#jsxInterpolator.wrapT(unwrappedT); return { ready: true, diff --git a/packages/core-plugin-api/report-alpha.api.md b/packages/core-plugin-api/report-alpha.api.md index eada7dc982..6243c76cff 100644 --- a/packages/core-plugin-api/report-alpha.api.md +++ b/packages/core-plugin-api/report-alpha.api.md @@ -6,8 +6,8 @@ import { ApiRef } from '@backstage/core-plugin-api'; import { Expand } from '@backstage/types'; import { ExpandRecursive } from '@backstage/types'; +import { JSX as JSX_2 } from 'react'; import { Observable } from '@backstage/types'; -import { ReactNode } from 'react'; import { TranslationMessages as TranslationMessages_2 } from '@backstage/core-plugin-api/alpha'; import { TranslationRef as TranslationRef_2 } from '@backstage/core-plugin-api/alpha'; @@ -108,11 +108,19 @@ export type TranslationFunction< ...[args]: TranslationFunctionOptions< NestedMessageKeys, PluralKeys, - IMessages + IMessages, + string > - ): HasJsxFormat extends true - ? ReactNode - : IMessages[TKey]; + ): IMessages[TKey]; + ( + key: TKey, + ...[args]: TranslationFunctionOptions< + NestedMessageKeys, + PluralKeys, + IMessages, + string | JSX_2.Element + > + ): JSX_2.Element; } : never; diff --git a/packages/core-plugin-api/src/apis/definitions/TranslationApi.test.ts b/packages/core-plugin-api/src/apis/definitions/TranslationApi.test.tsx similarity index 79% rename from packages/core-plugin-api/src/apis/definitions/TranslationApi.test.ts rename to packages/core-plugin-api/src/apis/definitions/TranslationApi.test.tsx index 490601a9e8..f99001ecf6 100644 --- a/packages/core-plugin-api/src/apis/definitions/TranslationApi.test.ts +++ b/packages/core-plugin-api/src/apis/definitions/TranslationApi.test.tsx @@ -14,14 +14,17 @@ * limitations under the License. */ -import { ReactNode } from 'react'; +import { JSX } from 'react'; import { TranslationFunction } from './TranslationApi'; -function unused(..._any: any[]) {} +// This is a weak assertion, don't reuse unless you know the drawbacks +function expectType(value: T) { + return value; +} describe('TranslationFunction', () => { it('should infer plurals', () => { - const f = (() => {}) as TranslationFunction<{ + const f = (() => {}) as unknown as TranslationFunction<{ key_one: 'one'; key_other: 'other'; thingCount_one: '{{count}} thing'; @@ -30,11 +33,11 @@ describe('TranslationFunction', () => { }>; expect(f).toBeDefined(); - f('foo'); + expectType(f('foo')); // @ts-expect-error f('foo', { count: 1 }); - f('key', { count: 1 }); + expectType(f('key', { count: 1 })); // @ts-expect-error f('key'); // @ts-expect-error @@ -48,7 +51,7 @@ describe('TranslationFunction', () => { // @ts-expect-error f('key_other', { count: 6 }); - f('thingCount', { count: 1 }); + expectType(f('thingCount', { count: 1 })); // @ts-expect-error f('thingCount'); // @ts-expect-error @@ -62,14 +65,13 @@ describe('TranslationFunction', () => { // @ts-expect-error f('thingCount_other', { count: 6 }); - const x1: 'one' | 'other' = f('key', { count: 6 }); + expectType<'one' | 'other'>(f('key', { count: 6 })); // @ts-expect-error - const x2: 'one' = f('key', { count: 6 }); - unused(x1, x2); + expectType<'one'>(f('key', { count: 6 })); }); it('should infer interpolation params', () => { - const f = (() => {}) as TranslationFunction<{ + const f = (() => {}) as unknown as TranslationFunction<{ none: '='; simple: '= {{bar}}'; multiple: '= {{bar }} {{ baz}}'; @@ -79,7 +81,11 @@ describe('TranslationFunction', () => { // @ts-expect-error f('none', { replace: { unknown: 1 } }); - f('simple', { bar: '' }); + expectType(f('simple', { bar: '' })); + // @ts-expect-error + expectType(f('simple', { bar:
})); + expectType(f('simple', { bar:
})); + expectType(f('simple', { replace: { bar:
} })); // @ts-expect-error f('simple'); // @ts-expect-error @@ -88,7 +94,10 @@ describe('TranslationFunction', () => { f('simple', { replace: {} }); // @ts-expect-error f('simple', { replace: { wrong: '' } }); - f('multiple', { bar: '', baz: '' }); + expectType(f('multiple', { bar: '', baz: '' })); + expectType(f('multiple', { bar:
, baz: '' })); + expectType(f('multiple', { bar: '', baz:
})); + expectType(f('multiple', { bar:
, baz:
})); // @ts-expect-error f('multiple', { bar: '' }); // @ts-expect-error @@ -99,7 +108,12 @@ describe('TranslationFunction', () => { f('multiple', {}); // @ts-expect-error f('multiple', { replace: {} }); - f('deep', { replace: { x: { y: '', z: '' }, a: { b: { c: '' } } } }); + expectType( + f('deep', { replace: { x: { y: '', z: '' }, a: { b: { c: '' } } } }), + ); + expectType( + f('deep', { replace: { x: { y: '', z: '' }, a: { b: { c:
} } } }), + ); // @ts-expect-error f('deep'); // @ts-expect-error @@ -115,7 +129,7 @@ describe('TranslationFunction', () => { }); it('should infer interpolation params with count', () => { - const f = (() => {}) as TranslationFunction<{ + const f = (() => {}) as unknown as TranslationFunction<{ simple_one: '= {{bar}}'; simple_other: '= {{bar}}'; multiple_one: '= {{ bar}} {{baz }}'; @@ -164,7 +178,7 @@ describe('TranslationFunction', () => { }); it('should support formatting', () => { - const f = (() => {}) as TranslationFunction<{ + const f = (() => {}) as unknown as TranslationFunction<{ none: '{{x}}'; number: '{{x, number}}'; numberOptions: '{{x, number(minimumFractionDigits: 2)}}'; @@ -172,28 +186,24 @@ describe('TranslationFunction', () => { datetime: '{{x, dateTime}}'; relativeTimeOptions: '{{x, relativeTime(quarter)}}'; list: '{{x, list}}'; - jsx: '{{x, jsx}}'; - jsxNested: '$t(jsx)'; }>; expect(f).toBeDefined(); - f('none', { replace: { x: 'x' } }) satisfies string; - f('number', { x: 1 }) satisfies string; + f('none', { replace: { x: 'x' } }); + f('number', { x: 1 }); f('number', { replace: { x: 1 }, formatParams: { x: { minimumFractionDigits: 2 } }, - }) satisfies string; - f('numberOptions', { x: 1 }) satisfies string; - f('currency', { replace: { x: 1 } }) satisfies string; - f('datetime', { x: new Date() }) satisfies string; - f('relativeTimeOptions', { replace: { x: 1 } }) satisfies string; + }); + f('numberOptions', { x: 1 }); + f('currency', { replace: { x: 1 } }); + f('datetime', { x: new Date() }); + f('relativeTimeOptions', { replace: { x: 1 } }); f('relativeTimeOptions', { replace: { x: 1 }, formatParams: { x: { style: 'short' } }, - }) satisfies string; - f('list', { replace: { x: ['a', 'b', 'c'] } }) satisfies string; - f('jsx', { replace: { x: '' } }) satisfies ReactNode; - f('jsxNested', { replace: { x: '' } }) satisfies ReactNode; + }); + f('list', { replace: { x: ['a', 'b', 'c'] } }); // @ts-expect-error f('none', { x: 1 }); // @ts-expect-error @@ -213,14 +223,10 @@ describe('TranslationFunction', () => { }); // @ts-expect-error f('list', { x: [1, 2, 3] }); - // @ts-expect-error - f('jsx', { x: Symbol('not-a-node') }); - // @ts-expect-error - f('jsxNested', { x: Symbol('not-a-node') }); }); it('should support nesting', () => { - const f = (() => {}) as TranslationFunction<{ + const f = (() => {}) as unknown as TranslationFunction<{ simple: '$t(foo)'; nested: '$t(bar)'; nestedCount: '$t(qux)'; @@ -255,7 +261,7 @@ describe('TranslationFunction', () => { }); it('should limit nesting depth', () => { - const f = (() => {}) as TranslationFunction<{ + const f = (() => {}) as unknown as TranslationFunction<{ a: '$t(b) {{a}}'; b: '$t(c) {{b}}'; c: '$t(d) {{c}}'; diff --git a/packages/core-plugin-api/src/apis/definitions/TranslationApi.ts b/packages/core-plugin-api/src/apis/definitions/TranslationApi.ts index c7e280f0da..7c3c3f8e83 100644 --- a/packages/core-plugin-api/src/apis/definitions/TranslationApi.ts +++ b/packages/core-plugin-api/src/apis/definitions/TranslationApi.ts @@ -17,7 +17,7 @@ import { ApiRef, createApiRef } from '@backstage/core-plugin-api'; import { Expand, ExpandRecursive, Observable } from '@backstage/types'; import { TranslationRef } from '../../translation'; -import { ReactNode } from 'react'; +import { JSX } from 'react'; /** * Base translation options. @@ -65,10 +65,6 @@ type I18nextFormatMap = { type: string[]; options: Intl.ListFormatOptions; }; - jsx: { - type: ReactNode; - options: {}; - }; }; /** @@ -173,12 +169,12 @@ type ReplaceFormatsFromMessage = * * @ignore */ -type ReplaceOptionsFromFormats = { +type ReplaceOptionsFromFormats = { [Key in keyof TFormats]: TFormats[Key] extends keyof I18nextFormatMap ? I18nextFormatMap[TFormats[Key]]['type'] : TFormats[Key] extends {} - ? Expand> - : string; + ? Expand> + : TValueType; }; /** @@ -264,14 +260,17 @@ type UnionToIntersection = (U extends any ? (k: U) => void : never) extends ( type CollectOptions< TCount extends { count?: number }, TFormats extends {}, + TValueType, > = TCount & // count is special, omit it from the replacements (keyof Omit extends never ? {} : ( - | Expand, 'count'>> + | Expand, 'count'>> | { - replace: Expand, 'count'>>; + replace: Expand< + Omit, 'count'> + >; } ) & { formatParams?: Expand>; @@ -283,7 +282,7 @@ type CollectOptions< * @ignore */ type OptionArgs = keyof TOptions extends never - ? [options?: BaseOptions] + ? [options?: Expand] : [options: Expand]; /** @@ -293,49 +292,51 @@ type TranslationFunctionOptions< TKeys extends keyof TMessages, // All normalized message keys to be considered, i.e. included nested ones TPluralKeys extends keyof TMessages, // All keys in the message map that are pluralized TMessages extends { [key in string]: string }, // Collapsed message map with normalized keys and union values + TValueType, > = OptionArgs< Expand< CollectOptions< TKeys & TPluralKeys extends never ? {} : { count: number }, ExpandRecursive< UnionToIntersection> - > + >, + TValueType > > >; -/** - * @ignore - * Evaluates to `true` if any of the replacements for the given key in the - * provided set of messages uses the `jsx` format. - */ -type HasJsxFormat< - TKey extends keyof TMessages, - TMessages extends { [key in string]: string }, -> = UnionToIntersection< - ReplaceFormatsFromMessage]> -> extends infer IFormatMap - ? 'jsx' extends IFormatMap[keyof IFormatMap] - ? true - : false - : never; - /** @alpha */ export type TranslationFunction = CollapsedMessages extends infer IMessages extends { [key in string]: string; } ? { + /** + * A translation function that returns a string. + */ ( key: TKey, ...[args]: TranslationFunctionOptions< NestedMessageKeys, PluralKeys, - IMessages + IMessages, + string > - ): HasJsxFormat extends true - ? ReactNode - : IMessages[TKey]; + ): IMessages[TKey]; + /** + * A translation function where at least one JSX.Element has been + * provided as an interpolation value, and will therefore return a + * JSX.Element. + */ + ( + key: TKey, + ...[args]: TranslationFunctionOptions< + NestedMessageKeys, + PluralKeys, + IMessages, + string | JSX.Element + > + ): JSX.Element; } : never; diff --git a/packages/test-utils/src/testUtils/apis/TranslationApi/MockTranslationApi.test.ts b/packages/test-utils/src/testUtils/apis/TranslationApi/MockTranslationApi.test.tsx similarity index 87% rename from packages/test-utils/src/testUtils/apis/TranslationApi/MockTranslationApi.test.ts rename to packages/test-utils/src/testUtils/apis/TranslationApi/MockTranslationApi.test.tsx index a1a1cd8ee2..4bee969a6d 100644 --- a/packages/test-utils/src/testUtils/apis/TranslationApi/MockTranslationApi.test.ts +++ b/packages/test-utils/src/testUtils/apis/TranslationApi/MockTranslationApi.test.tsx @@ -94,6 +94,52 @@ describe('MockTranslationApi', () => { expect(snapshot.t('foo', { qux: 'Deep' })).toBe('Foo Nested Baz Deep'); }); + it('should support jsx interpolation', () => { + const snapshot = snapshotWithMessages({ + empty: 'derp', + jsx: '={{ x }}', + jsxNested: '={{ x.y.z }}', + jsxDeep: '<$t(jsx)>', + }); + + expect(snapshot.t('jsx', { x:

hello

})).toMatchInlineSnapshot(` + + = +

+ hello +

+
+ `); + expect(snapshot.t('jsx', { replace: { x:

hello

} })) + .toMatchInlineSnapshot(` + + = +

+ hello +

+
+ `); + expect( + snapshot.t('jsxNested', { replace: { x: { y: { z:

hello

} } } }), + ).toMatchInlineSnapshot(` + + = +

+ hello +

+
+ `); + expect(snapshot.t('jsxDeep', { x:

hello

})).toMatchInlineSnapshot(` + + <= +

+ hello +

+ > +
+ `); + }); + it('should support formatting', () => { const snapshot = snapshotWithMessages({ plain: '= {{ x }}', @@ -104,8 +150,6 @@ describe('MockTranslationApi', () => { relativeSecondsShort: '= {{ x, relativeTime(range: second; style: short) }}', list: '= {{ x, list }}', - jsx: '={{ x, jsx }}', - nestedJsx: '<$t(jsx)>', }); expect(snapshot.t('plain', { x: '5' })).toBe('= 5'); @@ -148,19 +192,6 @@ describe('MockTranslationApi', () => { expect(snapshot.t('list', { x: ['a'] })).toBe('= a'); expect(snapshot.t('list', { x: ['a', 'b'] })).toBe('= a and b'); expect(snapshot.t('list', { x: ['a', 'b', 'c'] })).toBe('= a, b, and c'); - expect(snapshot.t('jsx', { x: 'hello' })).toMatchInlineSnapshot(` - - = - hello - - `); - expect(snapshot.t('nestedJsx', { x: 'hello' })).toMatchInlineSnapshot(` - - <= - hello - > - - `); }); it('should support plurals', () => { diff --git a/packages/test-utils/src/testUtils/apis/TranslationApi/MockTranslationApi.ts b/packages/test-utils/src/testUtils/apis/TranslationApi/MockTranslationApi.ts index d2f53bacf0..344679355a 100644 --- a/packages/test-utils/src/testUtils/apis/TranslationApi/MockTranslationApi.ts +++ b/packages/test-utils/src/testUtils/apis/TranslationApi/MockTranslationApi.ts @@ -42,6 +42,8 @@ export class MockTranslationApi implements TranslationApi { supportedLngs: [DEFAULT_LANGUAGE], interpolation: { escapeValue: false, + // Used for the JsxInterpolator format hook + alwaysFormat: true, }, ns: [], defaultNS: false, @@ -56,8 +58,7 @@ export class MockTranslationApi implements TranslationApi { throw new Error('i18next was unexpectedly not initialized'); } - const interpolator = JsxInterpolator.create({ marker: '123456' }); - i18n.services.formatter?.add('jsx', interpolator.format); + const interpolator = JsxInterpolator.fromI18n(i18n); return new MockTranslationApi(i18n, interpolator); } @@ -88,7 +89,7 @@ export class MockTranslationApi implements TranslationApi { } const t = this.#interpolator.wrapT( - this.#i18n.getFixedT(null, internalRef.id) as any, + this.#i18n.getFixedT(null, internalRef.id), ); return { From 247bd4fe219eefec321c336e9920c398565748e5 Mon Sep 17 00:00:00 2001 From: Peter Macdonald Date: Wed, 30 Apr 2025 18:16:09 +0200 Subject: [PATCH 060/109] fix: update createTemplateAction example Signed-off-by: Peter Macdonald --- .../software-templates/writing-custom-actions.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/docs/features/software-templates/writing-custom-actions.md b/docs/features/software-templates/writing-custom-actions.md index 163df094a1..51500f1c19 100644 --- a/docs/features/software-templates/writing-custom-actions.md +++ b/docs/features/software-templates/writing-custom-actions.md @@ -183,7 +183,19 @@ export const examples: TemplateExample[] = [ Add the example to the `createTemplateAction` under the object property `examples`: ```ts -return createTemplateAction<{ contents: string; filename: string }>({id: 'acme:file:create', description: 'Create an Acme file', examples, ...}); +return createTemplateAction({ + id: 'acme:file:create', + description: 'Create an Acme file', + schema: { + input: { + contents: d => d.string().describe('The contents of the file'), + filename: d => + d.string().describe('The filename of the file that will be created'), + }, + }, + examples: examples, + // ...rest of the action configuration +}); ``` ### The context object From f9ebd0392e7cd69fc8c408e51858e2d2bc51fcf3 Mon Sep 17 00:00:00 2001 From: Peter Macdonald Date: Wed, 30 Apr 2025 18:19:50 +0200 Subject: [PATCH 061/109] fix: small tweak Signed-off-by: Peter Macdonald --- docs/features/software-templates/writing-custom-actions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/features/software-templates/writing-custom-actions.md b/docs/features/software-templates/writing-custom-actions.md index 51500f1c19..d3a6dffead 100644 --- a/docs/features/software-templates/writing-custom-actions.md +++ b/docs/features/software-templates/writing-custom-actions.md @@ -193,7 +193,7 @@ return createTemplateAction({ d.string().describe('The filename of the file that will be created'), }, }, - examples: examples, + examples, // ...rest of the action configuration }); ``` From 490a376407981eda0a197b77a00dbcee1f498db5 Mon Sep 17 00:00:00 2001 From: Peter Macdonald Date: Wed, 30 Apr 2025 18:22:25 +0200 Subject: [PATCH 062/109] fix: another small tweak Signed-off-by: Peter Macdonald --- docs/features/software-templates/writing-custom-actions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/features/software-templates/writing-custom-actions.md b/docs/features/software-templates/writing-custom-actions.md index d3a6dffead..9a502d7195 100644 --- a/docs/features/software-templates/writing-custom-actions.md +++ b/docs/features/software-templates/writing-custom-actions.md @@ -180,7 +180,7 @@ export const examples: TemplateExample[] = [ ]; ``` -Add the example to the `createTemplateAction` under the object property `examples`: +Add the example to `createTemplateAction` by including the `examples` property: ```ts return createTemplateAction({ From b22947648131a25d96c5597e22c5287db179f2b6 Mon Sep 17 00:00:00 2001 From: Adam Kunicki Date: Wed, 30 Apr 2025 18:06:18 -0700 Subject: [PATCH 063/109] Support passing additional properties to OpenAPI server generator Signed-off-by: Adam Kunicki --- .changeset/cool-colts-float.md | 5 +++ packages/repo-tools/src/commands/index.ts | 4 +++ .../package/schema/openapi/generate/index.ts | 2 +- .../package/schema/openapi/generate/server.ts | 34 +++++++++++++------ 4 files changed, 34 insertions(+), 11 deletions(-) create mode 100644 .changeset/cool-colts-float.md diff --git a/.changeset/cool-colts-float.md b/.changeset/cool-colts-float.md new file mode 100644 index 0000000000..c001f81ade --- /dev/null +++ b/.changeset/cool-colts-float.md @@ -0,0 +1,5 @@ +--- +'@backstage/repo-tools': patch +--- + +Support passing additional properties to OpenAPI server generator diff --git a/packages/repo-tools/src/commands/index.ts b/packages/repo-tools/src/commands/index.ts index b737d918bd..a3db26c9f7 100644 --- a/packages/repo-tools/src/commands/index.ts +++ b/packages/repo-tools/src/commands/index.ts @@ -56,6 +56,10 @@ function registerPackageCommand(program: Command) { .description( 'Additional properties that can be passed to @openapitools/openapi-generator-cli', ) + .option('--server-additional-properties [properties]') + .description( + 'Additional properties that can be passed to @openapitools/openapi-generator-cli', + ) .option('--watch') .description('Watch the OpenAPI spec for changes and regenerate on save.') .action(lazy(() => import('./package/schema/openapi/generate'), 'command')); diff --git a/packages/repo-tools/src/commands/package/schema/openapi/generate/index.ts b/packages/repo-tools/src/commands/package/schema/openapi/generate/index.ts index 1c9fd586fb..bda80d2fb1 100644 --- a/packages/repo-tools/src/commands/package/schema/openapi/generate/index.ts +++ b/packages/repo-tools/src/commands/package/schema/openapi/generate/index.ts @@ -51,7 +51,7 @@ export async function command(opts: OptionValues) { ); } if (opts.server) { - promises.push(generateServer(options)); + promises.push(generateServer(options, opts.serverAdditionalProperties)); } await Promise.all(promises); }; diff --git a/packages/repo-tools/src/commands/package/schema/openapi/generate/server.ts b/packages/repo-tools/src/commands/package/schema/openapi/generate/server.ts index 8c342b0a68..1a8dd36cab 100644 --- a/packages/repo-tools/src/commands/package/schema/openapi/generate/server.ts +++ b/packages/repo-tools/src/commands/package/schema/openapi/generate/server.ts @@ -30,6 +30,7 @@ import { resolvePackagePath } from '@backstage/backend-plugin-api'; import { getPathToCurrentOpenApiSpec, getRelativePathToFile, + toGeneratorAdditionalProperties, } from '../../../../../lib/openapi/helpers'; async function generateSpecFile() { @@ -70,7 +71,7 @@ export const createOpenApiRouter = async ( const indexFile = join(schemaDir, '..', 'index.ts'); await fs.writeFile( indexFile, - `// + `// export * from './generated';`, ); @@ -82,7 +83,10 @@ export const createOpenApiRouter = async ( } } -async function generate(abortSignal?: AbortController) { +async function generate( + serverAdditionalProperties?: string, + abortSignal?: AbortController, +) { const resolvedOpenapiPath = await getPathToCurrentOpenApiSpec(); const resolvedOutputDirectory = await getRelativePathToFile(OUTPUT_PATH); @@ -93,6 +97,10 @@ async function generate(abortSignal?: AbortController) { OPENAPI_IGNORE_FILES.join('\n'), ); + const additionalProperties = toGeneratorAdditionalProperties({ + initialValue: serverAdditionalProperties, + }); + await exec( 'node', [ @@ -111,6 +119,9 @@ async function generate(abortSignal?: AbortController) { ), '--generator-key', 'v3.0', + additionalProperties + ? `--additional-properties=${additionalProperties}` + : '', ], { maxBuffer: Number.MAX_VALUE, @@ -147,15 +158,18 @@ async function generate(abortSignal?: AbortController) { await generateSpecFile(); } -export async function command({ - abortSignal, - isWatch = false, -}: { - abortSignal?: AbortController; - isWatch?: boolean; -}): Promise { +export async function command( + { + abortSignal, + isWatch = false, + }: { + abortSignal?: AbortController; + isWatch?: boolean; + }, + serverAdditionalProperties?: string, +): Promise { try { - await generate(abortSignal); + await generate(serverAdditionalProperties, abortSignal); console.log(chalk.green('Generated server files.')); } catch (err) { if (err.name === 'AbortError') { From 79645dd9ddc7a3c6cc846cdfdaf0c6607785149a Mon Sep 17 00:00:00 2001 From: amchakraborty Date: Thu, 1 May 2025 14:29:10 +0530 Subject: [PATCH 064/109] update mockLogger usage example in docs Signed-off-by: amchakraborty --- docs/backend-system/building-plugins-and-modules/02-testing.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/backend-system/building-plugins-and-modules/02-testing.md b/docs/backend-system/building-plugins-and-modules/02-testing.md index 439fed597a..f72d2de49b 100644 --- a/docs/backend-system/building-plugins-and-modules/02-testing.md +++ b/docs/backend-system/building-plugins-and-modules/02-testing.md @@ -37,7 +37,7 @@ describe('myPlugin', () => { features: [ myPlugin(), mockServices.rootConfig.factory({ data: fakeConfig }), - mockLogger, + mockLogger.factory, ], }); From 644e365fc380a0093b075c220f39bbdda3a0d30e Mon Sep 17 00:00:00 2001 From: benjdlambert Date: Thu, 1 May 2025 11:38:46 +0200 Subject: [PATCH 065/109] fix(nfs): Dialog contents need to be wrapped up in `compatWrapper` Signed-off-by: benjdlambert --- .../catalog/src/alpha/contextMenuItems.tsx | 34 +++++++++++-------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/plugins/catalog/src/alpha/contextMenuItems.tsx b/plugins/catalog/src/alpha/contextMenuItems.tsx index d503bc33a1..4173a4d074 100644 --- a/plugins/catalog/src/alpha/contextMenuItems.tsx +++ b/plugins/catalog/src/alpha/contextMenuItems.tsx @@ -37,6 +37,7 @@ import { import { rootRouteRef, unregisterRedirectRouteRef } from '../routes'; import { catalogEntityDeletePermission } from '@backstage/plugin-catalog-common/alpha'; import { useEffect } from 'react'; +import { compatWrapper } from '@backstage/core-compat-api'; export const copyEntityUrlContextMenuItem = EntityContextMenuItemBlueprint.make( { @@ -99,6 +100,7 @@ export const unregisterEntityContextMenuItem = const dialogApi = useApi(dialogApiRef); const navigate = useNavigate(); const catalogRoute = useRouteRef(rootRouteRef); + const { t } = useTranslationRef(catalogTranslationRef); const unregisterRedirectRoute = useRouteRef(unregisterRedirectRouteRef); const unregisterPermission = useEntityPermission( @@ -109,21 +111,23 @@ export const unregisterEntityContextMenuItem = title: t('entityContextMenu.unregisterMenuTitle'), disabled: !unregisterPermission.allowed, onClick: async () => { - dialogApi.showModal(({ dialog }: { dialog: DialogApiDialog }) => ( - dialog.close()} - onConfirm={() => { - dialog.close(); - navigate( - unregisterRedirectRoute - ? unregisterRedirectRoute() - : catalogRoute(), - ); - }} - /> - )); + dialogApi.showModal(({ dialog }: { dialog: DialogApiDialog }) => + compatWrapper( + dialog.close()} + onConfirm={() => { + dialog.close(); + navigate( + unregisterRedirectRoute + ? unregisterRedirectRoute() + : catalogRoute(), + ); + }} + />, + ), + ); }, }; }, From bf85d37f5008acd5a2bb293aacd3f8894610553a Mon Sep 17 00:00:00 2001 From: benjdlambert Date: Thu, 1 May 2025 11:41:15 +0200 Subject: [PATCH 066/109] chore: add changeset Signed-off-by: benjdlambert --- .changeset/yellow-beans-eat.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/yellow-beans-eat.md diff --git a/.changeset/yellow-beans-eat.md b/.changeset/yellow-beans-eat.md new file mode 100644 index 0000000000..2380b19a6f --- /dev/null +++ b/.changeset/yellow-beans-eat.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog': patch +--- + +Fix for missing `routeRef` when using `core-plugin-api` in a dialog context From da9f79141a28cb2ccff202d569015b8e96d82614 Mon Sep 17 00:00:00 2001 From: James Brooks Date: Fri, 2 May 2025 09:56:00 +0100 Subject: [PATCH 067/109] Canon - Refactor TextField to use Field Signed-off-by: James Brooks --- .../src/components/TextField/TextField.tsx | 41 ++++++------------- 1 file changed, 13 insertions(+), 28 deletions(-) diff --git a/packages/canon/src/components/TextField/TextField.tsx b/packages/canon/src/components/TextField/TextField.tsx index db56463c3a..132d6ab1c6 100644 --- a/packages/canon/src/components/TextField/TextField.tsx +++ b/packages/canon/src/components/TextField/TextField.tsx @@ -14,8 +14,8 @@ * limitations under the License. */ -import { useId, forwardRef } from 'react'; -import { Input } from '@base-ui-components/react/input'; +import { Field } from '@base-ui-components/react/field'; +import { forwardRef } from 'react'; import { useResponsiveValue } from '../../hooks/useResponsiveValue'; import clsx from 'clsx'; @@ -39,56 +39,41 @@ export const TextField = forwardRef( // Get the responsive value for the variant const responsiveSize = useResponsiveValue(size); - // Generate unique IDs for accessibility - const inputId = useId(); - const descriptionId = useId(); - const errorId = useId(); - return ( -
{label && ( - + )} - {description && ( -

+ {description} -

+ )} {error && ( - + )} -
+ ); }, ); From 185d3a8305b98d1bf130f290cc0661a5623e091e Mon Sep 17 00:00:00 2001 From: James Brooks Date: Fri, 2 May 2025 10:15:11 +0100 Subject: [PATCH 068/109] Add changeset Signed-off-by: James Brooks --- .changeset/wise-cobras-sink.md | 5 +++++ packages/canon/css/components.css | 1 + packages/canon/css/styles.css | 1 + packages/canon/css/textfield.css | 1 + packages/canon/src/components/TextField/TextField.styles.css | 1 + 5 files changed, 9 insertions(+) create mode 100644 .changeset/wise-cobras-sink.md diff --git a/.changeset/wise-cobras-sink.md b/.changeset/wise-cobras-sink.md new file mode 100644 index 0000000000..7a6a73c726 --- /dev/null +++ b/.changeset/wise-cobras-sink.md @@ -0,0 +1,5 @@ +--- +'@backstage/canon': patch +--- + +Use the Field component from Base UI within the TextField. diff --git a/packages/canon/css/components.css b/packages/canon/css/components.css index 6496363868..1fc7f275c3 100644 --- a/packages/canon/css/components.css +++ b/packages/canon/css/components.css @@ -544,6 +544,7 @@ color: var(--canon-fg-primary); margin-bottom: var(--canon-space-1_5); cursor: pointer; + margin-right: auto; } .canon-TextFieldLabel[data-disabled] { diff --git a/packages/canon/css/styles.css b/packages/canon/css/styles.css index b2743220c7..3382c15675 100644 --- a/packages/canon/css/styles.css +++ b/packages/canon/css/styles.css @@ -9768,6 +9768,7 @@ color: var(--canon-fg-primary); margin-bottom: var(--canon-space-1_5); cursor: pointer; + margin-right: auto; } .canon-TextFieldLabel[data-disabled] { diff --git a/packages/canon/css/textfield.css b/packages/canon/css/textfield.css index 4caf7cb2e9..8315d48bc6 100644 --- a/packages/canon/css/textfield.css +++ b/packages/canon/css/textfield.css @@ -11,6 +11,7 @@ color: var(--canon-fg-primary); margin-bottom: var(--canon-space-1_5); cursor: pointer; + margin-right: auto; } .canon-TextFieldLabel[data-disabled] { diff --git a/packages/canon/src/components/TextField/TextField.styles.css b/packages/canon/src/components/TextField/TextField.styles.css index 7ee0a937dc..5acc8480f3 100644 --- a/packages/canon/src/components/TextField/TextField.styles.css +++ b/packages/canon/src/components/TextField/TextField.styles.css @@ -26,6 +26,7 @@ font-weight: var(--canon-font-weight-regular); color: var(--canon-fg-primary); margin-bottom: var(--canon-space-1_5); + margin-right: auto; cursor: pointer; } .canon-TextFieldLabel[data-disabled] { From 97b25a190e26e3ac1fe9a7f1f1e41954d93e81ce Mon Sep 17 00:00:00 2001 From: James Brooks <52410024+jabrks@users.noreply.github.com> Date: Fri, 2 May 2025 10:46:20 +0100 Subject: [PATCH 069/109] Canon - Pin Base UI version (#29782) Canon is built on Base UI primitives, but Base UI is still in alpha and does not follow semantic versioning. This means that breaking changes can appear in non-major dependency versions. As Canon does not currently depend on a specific version of Base UI, any project that consumes Canon that also installs a later version of Base UI could end up using a version of Base UI that Canon does not support, resulting in unexpected behaviour or bugs. To try and avoid this scenario, this PR pins the version of Base UI to the one that Canon has been developed against, meaning that Canon will install that version of Base UI rather than inheriting another if it is also in use elsewhere, and ensuring that the components all work as expected --------- Signed-off-by: James Brooks --- .changeset/mean-parents-build.md | 5 +++++ packages/canon/package.json | 2 +- yarn.lock | 4 ++-- 3 files changed, 8 insertions(+), 3 deletions(-) create mode 100644 .changeset/mean-parents-build.md diff --git a/.changeset/mean-parents-build.md b/.changeset/mean-parents-build.md new file mode 100644 index 0000000000..d3bba29019 --- /dev/null +++ b/.changeset/mean-parents-build.md @@ -0,0 +1,5 @@ +--- +'@backstage/canon': patch +--- + +Pin version of @base-ui-components/react. diff --git a/packages/canon/package.json b/packages/canon/package.json index 04f9bbdb8f..610e7132f5 100644 --- a/packages/canon/package.json +++ b/packages/canon/package.json @@ -41,7 +41,7 @@ "test": "backstage-cli package test" }, "dependencies": { - "@base-ui-components/react": "^1.0.0-alpha.7", + "@base-ui-components/react": "1.0.0-alpha.7", "@remixicon/react": "^4.6.0", "@tanstack/react-table": "^8.21.3", "clsx": "^2.1.1" diff --git a/yarn.lock b/yarn.lock index 4d1c179744..c7f8e452f0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3802,7 +3802,7 @@ __metadata: resolution: "@backstage/canon@workspace:packages/canon" dependencies: "@backstage/cli": "workspace:^" - "@base-ui-components/react": "npm:^1.0.0-alpha.7" + "@base-ui-components/react": "npm:1.0.0-alpha.7" "@remixicon/react": "npm:^4.6.0" "@storybook/addon-essentials": "npm:^8.6.12" "@storybook/addon-interactions": "npm:^8.6.12" @@ -9067,7 +9067,7 @@ __metadata: languageName: node linkType: hard -"@base-ui-components/react@npm:^1.0.0-alpha.7": +"@base-ui-components/react@npm:1.0.0-alpha.7": version: 1.0.0-alpha.7 resolution: "@base-ui-components/react@npm:1.0.0-alpha.7" dependencies: From 39cd2c5373be98551bd519de1cee9c42f8430ff5 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 2 May 2025 09:58:43 +0000 Subject: [PATCH 070/109] chore(deps): update github/codeql-action action to v3.28.17 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/scorecard.yml | 2 +- .github/workflows/sync_snyk-monitor.yml | 2 +- .github/workflows/verify_codeql.yml | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index e16a481b4f..45eb5de5c8 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -67,6 +67,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: 'Upload to code-scanning' - uses: github/codeql-action/upload-sarif@28deaeda66b76a05916b6923827895f2b14ab387 # v3.28.16 + uses: github/codeql-action/upload-sarif@60168efe1c415ce0f5521ea06d5c2062adbeed1b # v3.28.17 with: sarif_file: results.sarif diff --git a/.github/workflows/sync_snyk-monitor.yml b/.github/workflows/sync_snyk-monitor.yml index 5f0b44d8c6..741471d558 100644 --- a/.github/workflows/sync_snyk-monitor.yml +++ b/.github/workflows/sync_snyk-monitor.yml @@ -58,6 +58,6 @@ jobs: SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} NODE_OPTIONS: --max-old-space-size=7168 - name: Upload Snyk report - uses: github/codeql-action/upload-sarif@28deaeda66b76a05916b6923827895f2b14ab387 # v3.28.16 + uses: github/codeql-action/upload-sarif@60168efe1c415ce0f5521ea06d5c2062adbeed1b # v3.28.17 with: sarif_file: snyk.sarif diff --git a/.github/workflows/verify_codeql.yml b/.github/workflows/verify_codeql.yml index 5a1fb93d92..c4b21a9e1f 100644 --- a/.github/workflows/verify_codeql.yml +++ b/.github/workflows/verify_codeql.yml @@ -55,7 +55,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@28deaeda66b76a05916b6923827895f2b14ab387 # v3.28.16 + uses: github/codeql-action/init@60168efe1c415ce0f5521ea06d5c2062adbeed1b # v3.28.17 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -66,7 +66,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@28deaeda66b76a05916b6923827895f2b14ab387 # v3.28.16 + uses: github/codeql-action/autobuild@60168efe1c415ce0f5521ea06d5c2062adbeed1b # v3.28.17 # ℹ️ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl @@ -80,4 +80,4 @@ jobs: # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@28deaeda66b76a05916b6923827895f2b14ab387 # v3.28.16 + uses: github/codeql-action/analyze@60168efe1c415ce0f5521ea06d5c2062adbeed1b # v3.28.17 From 4bf7fe41526c194a2cc820925067167ee8a24b0f Mon Sep 17 00:00:00 2001 From: Peter Macdonald Date: Fri, 2 May 2025 15:39:38 +0200 Subject: [PATCH 071/109] docs: adds small addition to docker compose pg environment variables about setting timezones optionally Signed-off-by: Peter Macdonald --- docs/getting-started/config/database.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/getting-started/config/database.md b/docs/getting-started/config/database.md index e068361966..eed1586dc8 100644 --- a/docs/getting-started/config/database.md +++ b/docs/getting-started/config/database.md @@ -153,6 +153,9 @@ services: environment: POSTGRES_USER: postgres POSTGRES_PASSWORD: + # If you want to set a timezone you can use the following environment variables, this is handy when trying to figure out when scheduled tasks will run! + # TZ: Europe/Stockholm + # PGTZ: Europe/Stockholm ports: - 5432:5432 ``` From 0e7a640f98e64e3da8e3c0df572b278394de1c38 Mon Sep 17 00:00:00 2001 From: Adam Letizia Date: Thu, 1 May 2025 10:46:01 -0500 Subject: [PATCH 072/109] fix(GithubUrlReader): use token from options to fetch repoDetails Signed-off-by: Adam Letizia --- .changeset/warm-cases-bathe.md | 5 ++ .../urlReader/lib/GithubUrlReader.test.ts | 64 +++++++++++++------ .../urlReader/lib/GithubUrlReader.ts | 13 ++-- 3 files changed, 58 insertions(+), 24 deletions(-) create mode 100644 .changeset/warm-cases-bathe.md diff --git a/.changeset/warm-cases-bathe.md b/.changeset/warm-cases-bathe.md new file mode 100644 index 0000000000..21e9ceb098 --- /dev/null +++ b/.changeset/warm-cases-bathe.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-defaults': patch +--- + +The `GithubUrlReader` will now use the token from `options` when fetching repo details diff --git a/packages/backend-defaults/src/entrypoints/urlReader/lib/GithubUrlReader.test.ts b/packages/backend-defaults/src/entrypoints/urlReader/lib/GithubUrlReader.test.ts index 70b18df091..4e1c5a2d08 100644 --- a/packages/backend-defaults/src/entrypoints/urlReader/lib/GithubUrlReader.test.ts +++ b/packages/backend-defaults/src/entrypoints/urlReader/lib/GithubUrlReader.test.ts @@ -492,7 +492,7 @@ describe('GithubUrlReader', () => { }); it('should override the token when provided', async () => { - expect.assertions(1); + expect.assertions(2); const mockHeaders = { Authorization: 'bearer blah', @@ -503,6 +503,20 @@ describe('GithubUrlReader', () => { }); worker.use( + rest.get( + 'https://ghe.github.com/api/v3/repos/backstage/mock/commits/main/status', + (req, res, ctx) => { + expect(req.headers.get('authorization')).toBe( + 'Bearer overridentoken', + ); + + return res( + ctx.status(200), + ctx.set('Content-Type', 'application/json'), + ctx.json(commitStatusGheResponse), + ); + }, + ), rest.get( 'https://ghe.github.com/api/v3/repos/backstage/mock/tarball/etag123abc', (req, res, ctx) => { @@ -678,6 +692,21 @@ describe('GithubUrlReader', () => { }, ]; + const gheCommitsResponse = { + sha: 'etag123abc', + repository: { + id: 123, + full_name: 'backstage/mock', + default_branch: 'main', + branches_url: + 'https://ghe.github.com/api/v3/repos/backstage/mock/branches{/branch}', + archive_url: + 'https://ghe.github.com/api/v3/repos/backstage/mock/{archive_format}{/ref}', + trees_url: + 'https://ghe.github.com/api/v3/repos/backstage/mock/git/trees{/sha}', + }, + } as Partial; + // Tarballs beforeEach(() => { worker.use( @@ -773,21 +802,6 @@ describe('GithubUrlReader', () => { }, } as Partial; - const gheResponse = { - sha: 'etag123abc', - repository: { - id: 123, - full_name: 'backstage/mock', - default_branch: 'main', - branches_url: - 'https://ghe.github.com/api/v3/repos/backstage/mock/branches{/branch}', - archive_url: - 'https://ghe.github.com/api/v3/repos/backstage/mock/{archive_format}{/ref}', - trees_url: - 'https://ghe.github.com/api/v3/repos/backstage/mock/git/trees{/sha}', - }, - } as Partial; - worker.use( rest.get( 'https://api.github.com/repos/backstage/mock/commits/main/status', @@ -810,7 +824,7 @@ describe('GithubUrlReader', () => { return res( ctx.status(200), ctx.set('Content-Type', 'application/json'), - ctx.json(gheResponse), + ctx.json(gheCommitsResponse), ); } @@ -969,9 +983,23 @@ describe('GithubUrlReader', () => { }); it('passes through a token for the search request', async () => { - expect.assertions(1); + expect.assertions(2); worker.use( + rest.get( + 'https://ghe.github.com/api/v3/repos/backstage/mock/commits/main/status', + (req, res, ctx) => { + expect(req.headers.get('authorization')).toBe( + 'Bearer overridentoken', + ); + + return res( + ctx.status(200), + ctx.set('Content-Type', 'application/json'), + ctx.json(gheCommitsResponse), + ); + }, + ), rest.get( 'https://ghe.github.com/api/v3/repos/backstage/mock/git/trees/etag123abc', (req, res, ctx) => { diff --git a/packages/backend-defaults/src/entrypoints/urlReader/lib/GithubUrlReader.ts b/packages/backend-defaults/src/entrypoints/urlReader/lib/GithubUrlReader.ts index d78475de0b..1dbade8477 100644 --- a/packages/backend-defaults/src/entrypoints/urlReader/lib/GithubUrlReader.ts +++ b/packages/backend-defaults/src/entrypoints/urlReader/lib/GithubUrlReader.ts @@ -154,7 +154,7 @@ export class GithubUrlReader implements UrlReaderService { url: string, options?: UrlReaderServiceReadTreeOptions, ): Promise { - const repoDetails = await this.getRepoDetails(url); + const repoDetails = await this.getRepoDetails(url, options); const commitSha = repoDetails.commitSha; if (options?.etag && options.etag === commitSha) { @@ -212,7 +212,7 @@ export class GithubUrlReader implements UrlReaderService { } } - const repoDetails = await this.getRepoDetails(url); + const repoDetails = await this.getRepoDetails(url, options); const commitSha = repoDetails.commitSha; if (options?.etag && options.etag === commitSha) { @@ -320,7 +320,10 @@ export class GithubUrlReader implements UrlReaderService { })); } - private async getRepoDetails(url: string): Promise<{ + private async getRepoDetails( + url: string, + options?: { token?: string }, + ): Promise<{ commitSha: string; repo: { archive_url: string; @@ -330,9 +333,7 @@ export class GithubUrlReader implements UrlReaderService { const parsed = parseGitUrl(url); const { ref, full_name } = parsed; - const credentials = await this.deps.credentialsProvider.getCredentials({ - url, - }); + const credentials = await this.getCredentials(url, options); const { headers } = credentials; const commitStatus: GhCombinedCommitStatusResponse = await this.fetchJson( From 5841ea3801f6aa6425e97fb2f8b62d391b5726ab Mon Sep 17 00:00:00 2001 From: Andre Wanlin Date: Fri, 2 May 2025 09:44:18 -0500 Subject: [PATCH 073/109] Add Template Extensions docs to the sidebar Signed-off-by: Andre Wanlin --- microsite/sidebars.ts | 1 + mkdocs.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/microsite/sidebars.ts b/microsite/sidebars.ts index eb61c32daa..dd52f9a801 100644 --- a/microsite/sidebars.ts +++ b/microsite/sidebars.ts @@ -228,6 +228,7 @@ export default { 'features/software-templates/migrating-from-v1beta2-to-v1beta3', 'features/software-templates/dry-run-testing', 'features/software-templates/experimental', + 'features/software-templates/template-extensions', ], }, { diff --git a/mkdocs.yml b/mkdocs.yml index d56ec49b66..939325c82a 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -74,6 +74,7 @@ nav: - Template Extensions: 'features/software-templates/template-extensions.md' - Migrating from v1beta2 to v1beta3 templates: 'features/software-templates/migrating-from-v1beta2-to-v1beta3.md' - Dry Run Testing: 'features/software-templates/dry-run-testing.md' + - Template Extensions: 'features/software-templates/template-extensions.md' - Backstage Search: - Overview: 'features/search/README.md' - Getting Started: 'features/search/getting-started.md' From 4170359e3feec3fd0e0c026714d7a83be109d9c8 Mon Sep 17 00:00:00 2001 From: Andre Wanlin Date: Fri, 2 May 2025 09:58:45 -0500 Subject: [PATCH 074/109] Removed duplicate Signed-off-by: Andre Wanlin --- mkdocs.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/mkdocs.yml b/mkdocs.yml index 939325c82a..d56ec49b66 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -74,7 +74,6 @@ nav: - Template Extensions: 'features/software-templates/template-extensions.md' - Migrating from v1beta2 to v1beta3 templates: 'features/software-templates/migrating-from-v1beta2-to-v1beta3.md' - Dry Run Testing: 'features/software-templates/dry-run-testing.md' - - Template Extensions: 'features/software-templates/template-extensions.md' - Backstage Search: - Overview: 'features/search/README.md' - Getting Started: 'features/search/getting-started.md' From 2c7661423c38be5c44f3f97b5b0e6a54e22273ae Mon Sep 17 00:00:00 2001 From: Moro <46880495+sleepingmoro@users.noreply.github.com> Date: Fri, 2 May 2025 20:50:22 +0000 Subject: [PATCH 075/109] Add useMemo to searchFilter.Autocomplete filterValue Signed-off-by: Moro Signed-off-by: Moro <46880495+sleepingmoro@users.noreply.github.com> --- .changeset/sweet-papayas-slide.md | 5 ++++ .../config/vocabularies/Backstage/accept.txt | 1 + .../SearchFilter.Autocomplete.test.tsx | 29 +++++++++++++++++++ .../SearchFilter.Autocomplete.tsx | 7 +++-- 4 files changed, 40 insertions(+), 2 deletions(-) create mode 100644 .changeset/sweet-papayas-slide.md diff --git a/.changeset/sweet-papayas-slide.md b/.changeset/sweet-papayas-slide.md new file mode 100644 index 0000000000..9db998e948 --- /dev/null +++ b/.changeset/sweet-papayas-slide.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-search-react': patch +--- + +Fix memoization of `filterValue` in `SearchFilter.Autocomplete` to prevent unintended resets \ No newline at end of file diff --git a/.github/vale/config/vocabularies/Backstage/accept.txt b/.github/vale/config/vocabularies/Backstage/accept.txt index c845a6060b..f9a0595489 100644 --- a/.github/vale/config/vocabularies/Backstage/accept.txt +++ b/.github/vale/config/vocabularies/Backstage/accept.txt @@ -252,6 +252,7 @@ makefile Matomo md memcache +memoization memoize memoized microservice diff --git a/plugins/search-react/src/components/SearchFilter/SearchFilter.Autocomplete.test.tsx b/plugins/search-react/src/components/SearchFilter/SearchFilter.Autocomplete.test.tsx index 5bbb8eb27f..3d0dd95859 100644 --- a/plugins/search-react/src/components/SearchFilter/SearchFilter.Autocomplete.test.tsx +++ b/plugins/search-react/src/components/SearchFilter/SearchFilter.Autocomplete.test.tsx @@ -418,5 +418,34 @@ describe('SearchFilter.Autocomplete', () => { expect(screen.getByTestId(`${name}-filter-spy`)).toHaveTextContent(''); }); }); + + it('allows typing a value and shows suggestions', async () => { + render( + + + + + , + ); + + const input = screen.getByRole('textbox'); + await userEvent.type(input, 'value'); + + await waitFor(() => { + expect(input).toHaveValue('value'); + expect(screen.getByRole('listbox')).toBeInTheDocument(); + expect( + screen.getByRole('option', { name: values[0] }), + ).toBeInTheDocument(); + expect( + screen.getByRole('option', { name: values[1] }), + ).toBeInTheDocument(); + }); + }); }); }); diff --git a/plugins/search-react/src/components/SearchFilter/SearchFilter.Autocomplete.tsx b/plugins/search-react/src/components/SearchFilter/SearchFilter.Autocomplete.tsx index f95f0153fe..2002904f3f 100644 --- a/plugins/search-react/src/components/SearchFilter/SearchFilter.Autocomplete.tsx +++ b/plugins/search-react/src/components/SearchFilter/SearchFilter.Autocomplete.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import { ChangeEvent, useState } from 'react'; +import { ChangeEvent, useState, useMemo } from 'react'; import Chip from '@material-ui/core/Chip'; import TextField from '@material-ui/core/TextField'; import Autocomplete, { @@ -69,7 +69,10 @@ export const AutocompleteFilter = (props: SearchAutocompleteFilterProps) => { const filterValueWithLabel = ensureFilterValueWithLabel( filters[name] as string | string[] | undefined, ); - const filterValue = filterValueWithLabel || (multiple ? [] : null); + const filterValue = useMemo( + () => filterValueWithLabel || (multiple ? [] : null), + [filterValueWithLabel, multiple], + ); // Set new filter values on input change. const handleChange = ( From 60100aeb0c2759f3735474a3bd3c9535e61b9bf5 Mon Sep 17 00:00:00 2001 From: Moro Date: Fri, 2 May 2025 23:11:13 +0200 Subject: [PATCH 076/109] prettier fix Signed-off-by: Moro --- .changeset/sweet-papayas-slide.md | 2 +- .../components/SearchFilter/SearchFilter.Autocomplete.test.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.changeset/sweet-papayas-slide.md b/.changeset/sweet-papayas-slide.md index 9db998e948..9fbc067ca7 100644 --- a/.changeset/sweet-papayas-slide.md +++ b/.changeset/sweet-papayas-slide.md @@ -2,4 +2,4 @@ '@backstage/plugin-search-react': patch --- -Fix memoization of `filterValue` in `SearchFilter.Autocomplete` to prevent unintended resets \ No newline at end of file +Fix memoization of `filterValue` in `SearchFilter.Autocomplete` to prevent unintended resets diff --git a/plugins/search-react/src/components/SearchFilter/SearchFilter.Autocomplete.test.tsx b/plugins/search-react/src/components/SearchFilter/SearchFilter.Autocomplete.test.tsx index 3d0dd95859..ca6585a039 100644 --- a/plugins/search-react/src/components/SearchFilter/SearchFilter.Autocomplete.test.tsx +++ b/plugins/search-react/src/components/SearchFilter/SearchFilter.Autocomplete.test.tsx @@ -418,7 +418,7 @@ describe('SearchFilter.Autocomplete', () => { expect(screen.getByTestId(`${name}-filter-spy`)).toHaveTextContent(''); }); }); - + it('allows typing a value and shows suggestions', async () => { render( Date: Fri, 25 Apr 2025 13:39:46 +0200 Subject: [PATCH 077/109] OWNERS.md: add new distinct areas for maintainer team Signed-off-by: Patrik Oldsberg --- .github/ISSUE_TEMPLATE/.common.yaml | 1 + .github/ISSUE_TEMPLATE/01_bug.yaml | 1 + .github/ISSUE_TEMPLATE/02_documentation.yaml | 1 + .github/ISSUE_TEMPLATE/03_suggestion.yaml | 1 + .github/ISSUE_TEMPLATE/04_maintenance.yaml | 1 + .github/advanced-issue-labeler.yml | 8 ++- LABELS.md | 4 +- OWNERS.md | 75 ++++++++++++++++++++ 8 files changed, 89 insertions(+), 3 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/.common.yaml b/.github/ISSUE_TEMPLATE/.common.yaml index c870b21b94..eac60aaa2b 100644 --- a/.github/ISSUE_TEMPLATE/.common.yaml +++ b/.github/ISSUE_TEMPLATE/.common.yaml @@ -42,6 +42,7 @@ body: - Events System - Home - Kubernetes Plugin + - Management of this repository - Microsite - Notifications - OpenAPI Tooling diff --git a/.github/ISSUE_TEMPLATE/01_bug.yaml b/.github/ISSUE_TEMPLATE/01_bug.yaml index ccaebe8bff..303a4fcc61 100644 --- a/.github/ISSUE_TEMPLATE/01_bug.yaml +++ b/.github/ISSUE_TEMPLATE/01_bug.yaml @@ -58,6 +58,7 @@ body: - Events System - Home - Kubernetes Plugin + - Management of this repository - Microsite - Notifications - OpenAPI Tooling diff --git a/.github/ISSUE_TEMPLATE/02_documentation.yaml b/.github/ISSUE_TEMPLATE/02_documentation.yaml index dc9ede2c53..aaf54fc4a0 100644 --- a/.github/ISSUE_TEMPLATE/02_documentation.yaml +++ b/.github/ISSUE_TEMPLATE/02_documentation.yaml @@ -37,6 +37,7 @@ body: - Events System - Home - Kubernetes Plugin + - Management of this repository - Microsite - Notifications - OpenAPI Tooling diff --git a/.github/ISSUE_TEMPLATE/03_suggestion.yaml b/.github/ISSUE_TEMPLATE/03_suggestion.yaml index cafdd7066f..05a98e0956 100644 --- a/.github/ISSUE_TEMPLATE/03_suggestion.yaml +++ b/.github/ISSUE_TEMPLATE/03_suggestion.yaml @@ -50,6 +50,7 @@ body: - Events System - Home - Kubernetes Plugin + - Management of this repository - Microsite - Notifications - OpenAPI Tooling diff --git a/.github/ISSUE_TEMPLATE/04_maintenance.yaml b/.github/ISSUE_TEMPLATE/04_maintenance.yaml index bf81efa5f6..da25225e68 100644 --- a/.github/ISSUE_TEMPLATE/04_maintenance.yaml +++ b/.github/ISSUE_TEMPLATE/04_maintenance.yaml @@ -48,6 +48,7 @@ body: - Events System - Home - Kubernetes Plugin + - Management of this repository - Microsite - Notifications - OpenAPI Tooling diff --git a/.github/advanced-issue-labeler.yml b/.github/advanced-issue-labeler.yml index 4148b4236c..dfd6a036c9 100644 --- a/.github/advanced-issue-labeler.yml +++ b/.github/advanced-issue-labeler.yml @@ -9,8 +9,8 @@ policy: keys: ['Auth'] - name: 'area:catalog' keys: ['Catalog'] - - name: 'area:core' - keys: ['Core Framework', 'CLI Tooling'] + - name: 'area:framework' + keys: ['Core Framework'] - name: 'area:design-system' keys: ['Design System'] - name: 'area:documentation' @@ -25,6 +25,8 @@ policy: keys: ['Notifications'] - name: 'area:openapi-tooling' keys: ['OpenAPI Tooling'] + - name: 'area:operations' + keys: ['Management of this repository'] - name: 'area:permission' keys: ['Permission Framework'] - name: 'area:search' @@ -33,6 +35,8 @@ policy: keys: ['Software Templates'] - name: 'area:techdocs' keys: ['TechDocs'] + - name: 'area:tooling' + keys: ['CLI Tooling'] - id: ['integration'] block-list: [] label: diff --git a/LABELS.md b/LABELS.md index 9ce65d0a71..7f8804f75a 100644 --- a/LABELS.md +++ b/LABELS.md @@ -39,19 +39,21 @@ These labels indicate which part of Backstage an issue or pull request relates t - `area:auditor` - Auditor service and it's use in plugins. - `area:auth` - Authentication and 3rd party authorization. - `area:catalog` - The Catalog plugin and the Software Catalog model and integrations. -- `area:core` - The core Backstage framework. - `area:design-system` - The Canon design system and library. - `area:documentation` - Documentation for adopters, users, and developers. - `area:events` - The Events system and integrations for other plugins. +- `area:framework` - The core Backstage framework. - `area:home` - The Home plugin and the main page of the Backstage site. - `area:kubernetes` - The Kubernetes plugin and integrations for other plugins. - `area:microsite` - The microsite at [backstage.io](https://backstage.io), excluding the documentation. - `area:notifications` - The Notifications plugin and integrations for other plugins. - `area:openapi-tooling` - The OpenAPI tooling it's use in plugins. +- `area:operations` - The management and operations of the main Backstage repository. - `area:permission` - The Permissions system and permission integrations from other plugins. - `area:scaffolder` - The Scaffolder plugin that powers Software Templates. - `area:search` - The Search plugin and search integrations for other plugins. - `area:techdocs` - The TechDocs plugin. +- `area:tooling` - The Backstage CLI and repository tooling. ## Integration Labels - `integration:*` diff --git a/OWNERS.md b/OWNERS.md index c6bbcf71d7..9612f480cd 100644 --- a/OWNERS.md +++ b/OWNERS.md @@ -15,6 +15,21 @@ Team: @backstage/maintainers These are the separate project areas of Backstage, each with their own project area maintainers +### Auth + +Team: @backstage/auth-maintainers + +Scope: The Backstage auth plugin and modules, as well as client-side implementations. + +| Name | Organization | Team | GitHub | Discord | +| -------------------- | ------------ | ------------- | ----------------------------------------------- | --------------- | +| Ben Lambert | Spotify | Cubic Belugas | [benjdlambert](https://github.com/benjdlambert) | `blam#2159` | +| Camila Loiola | Spotify | Cubic Belugas | [camilaibs](http://github.com/camilaibs) | `camilal#0226` | +| Fredrik Adelöw | Spotify | Cubic Belugas | [freben](https://github.com/freben) | `freben#3926` | +| Mihai Tabara | Spotify | Cubic Belugas | [MihaiTabara](http://github.com/MihaiTabara) | `mihait#3107` | +| Patrik Oldsberg | Spotify | Cubic Belugas | [Rugvip](https://github.com/Rugvip) | `Rugvip#0019` | +| Vincenzo Scamporlino | Spotify | Cubic Belugas | [vinzscam](http://github.com/vinzscam) | `vinzscam#6944` | + ### Catalog Team: @backstage/catalog-maintainers @@ -41,6 +56,21 @@ Scope: The Backstage design system, component library, as well as surrounding to | Charles de Dreuille | Spotify | | [cdedreuille](https://github.com/cdedreuille) | `cdedreuille` | | Patrik Oldsberg | Spotify | Cubic Belugas | [Rugvip](https://github.com/Rugvip) | `Rugvip` | +### Framework + +Team: @backstage/framework-maintainers + +Scope: The Backstage core framework, including all revisions of the frontend and backend systems, as well as the App plugin. + +| Name | Organization | Team | GitHub | Discord | +| -------------------- | ------------ | ------------- | ----------------------------------------------- | --------------- | +| Ben Lambert | Spotify | Cubic Belugas | [benjdlambert](https://github.com/benjdlambert) | `blam#2159` | +| Camila Loiola | Spotify | Cubic Belugas | [camilaibs](http://github.com/camilaibs) | `camilal#0226` | +| Fredrik Adelöw | Spotify | Cubic Belugas | [freben](https://github.com/freben) | `freben#3926` | +| Mihai Tabara | Spotify | Cubic Belugas | [MihaiTabara](http://github.com/MihaiTabara) | `mihait#3107` | +| Patrik Oldsberg | Spotify | Cubic Belugas | [Rugvip](https://github.com/Rugvip) | `Rugvip#0019` | +| Vincenzo Scamporlino | Spotify | Cubic Belugas | [vinzscam](http://github.com/vinzscam) | `vinzscam#6944` | + ### Helm Charts Team: @backstage/helm-chart-maintainers @@ -76,6 +106,36 @@ Scope: The Kubernetes plugin and the base it provides for other plugins to build | -------------- | ------------ | ---- | ---------------------------------------- | ------------ | | Matthew Clarke | Spotify | | [mclarke47](http://github.com/mclarke47) | mclarke#0725 | +### Microsite + +Team: @backstage/microsite-maintainers + +Scope: The microsite at [backstage.io](https://backstage.io), excluding the documentation. + +| Name | Organization | Team | GitHub | Discord | +| -------------------- | ------------ | ------------- | ----------------------------------------------- | --------------- | +| Patrik Oldsberg | Spotify | Cubic Belugas | [Rugvip](https://github.com/Rugvip) | `Rugvip#0019` | +| Ben Lambert | Spotify | Cubic Belugas | [benjdlambert](https://github.com/benjdlambert) | `blam#2159` | +| Camila Loiola | Spotify | Cubic Belugas | [camilaibs](http://github.com/camilaibs) | `camilal#0226` | +| Fredrik Adelöw | Spotify | Cubic Belugas | [freben](https://github.com/freben) | `freben#3926` | +| Mihai Tabara | Spotify | Cubic Belugas | [MihaiTabara](http://github.com/MihaiTabara) | `mihait#3107` | +| Vincenzo Scamporlino | Spotify | Cubic Belugas | [vinzscam](http://github.com/vinzscam) | `vinzscam#6944` | + +### Operations + +Team: @backstage/operations-maintainers + +Scope: The management and operation of the main Backstage repository and release process, along with the surrounding tooling. + +| Name | Organization | Team | GitHub | Discord | +| -------------------- | ------------ | ------------- | ----------------------------------------------- | --------------- | +| Ben Lambert | Spotify | Cubic Belugas | [benjdlambert](https://github.com/benjdlambert) | `blam#2159` | +| Camila Loiola | Spotify | Cubic Belugas | [camilaibs](http://github.com/camilaibs) | `camilal#0226` | +| Fredrik Adelöw | Spotify | Cubic Belugas | [freben](https://github.com/freben) | `freben#3926` | +| Mihai Tabara | Spotify | Cubic Belugas | [MihaiTabara](http://github.com/MihaiTabara) | `mihait#3107` | +| Patrik Oldsberg | Spotify | Cubic Belugas | [Rugvip](https://github.com/Rugvip) | `Rugvip#0019` | +| Vincenzo Scamporlino | Spotify | Cubic Belugas | [vinzscam](http://github.com/vinzscam) | `vinzscam#6944` | + ### Permission Framework Team: @backstage/permission-maintainers @@ -117,6 +177,21 @@ Scope: The TechDocs plugin and related tooling | John Philip | Spotify | ProTean | [johnphilip283](https://github.com/johnphilip283) | `john_philip#2399` | | Sydney Achinger | Spotify | ProTean | [squid-ney](https://github.com/squid-ney) | - | +### Tooling + +Team: @backstage/tooling-maintainers + +Scope: All published Backstage CLI tools in the main `backstage` repository that do not belong to other areas, including `@backstage/cli` and `@backstage/repo-tools`. + +| Name | Organization | Team | GitHub | Discord | +| -------------------- | ------------ | ------------- | ----------------------------------------------- | --------------- | +| Patrik Oldsberg | Spotify | Cubic Belugas | [Rugvip](https://github.com/Rugvip) | `Rugvip#0019` | +| Ben Lambert | Spotify | Cubic Belugas | [benjdlambert](https://github.com/benjdlambert) | `blam#2159` | +| Camila Loiola | Spotify | Cubic Belugas | [camilaibs](http://github.com/camilaibs) | `camilal#0226` | +| Fredrik Adelöw | Spotify | Cubic Belugas | [freben](https://github.com/freben) | `freben#3926` | +| Mihai Tabara | Spotify | Cubic Belugas | [MihaiTabara](http://github.com/MihaiTabara) | `mihait#3107` | +| Vincenzo Scamporlino | Spotify | Cubic Belugas | [vinzscam](http://github.com/vinzscam) | `vinzscam#6944` | + ## Incubating Project Areas These incubating project areas have shared ownership with @backstage/maintainers. From 83656f899925ffb26f8e88bf1980e5809b5012be Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 25 Apr 2025 14:26:57 +0200 Subject: [PATCH 078/109] .github/CODEOWNERS: adjust ownership for new project areas Signed-off-by: Patrik Oldsberg --- .github/CODEOWNERS | 30 +++++++++++++++++-- packages/app-defaults/catalog-info.yaml | 2 +- .../app-next-example-plugin/catalog-info.yaml | 2 +- packages/app-next/catalog-info.yaml | 2 +- packages/app/catalog-info.yaml | 2 +- packages/backend-app-api/catalog-info.yaml | 2 +- packages/backend-defaults/catalog-info.yaml | 2 +- packages/backend-dev-utils/catalog-info.yaml | 2 +- .../catalog-info.yaml | 2 +- packages/backend-plugin-api/catalog-info.yaml | 2 +- packages/backend-test-utils/catalog-info.yaml | 2 +- packages/backend/catalog-info.yaml | 2 +- packages/catalog-client/catalog-info.yaml | 2 +- packages/catalog-model/catalog-info.yaml | 2 +- packages/cli-common/catalog-info.yaml | 2 +- packages/cli-node/catalog-info.yaml | 2 +- packages/cli/catalog-info.yaml | 2 +- packages/codemods/catalog-info.yaml | 2 +- packages/config-loader/catalog-info.yaml | 2 +- packages/config/catalog-info.yaml | 2 +- packages/core-app-api/catalog-info.yaml | 2 +- packages/core-compat-api/catalog-info.yaml | 2 +- packages/core-components/catalog-info.yaml | 2 +- packages/core-plugin-api/catalog-info.yaml | 2 +- packages/create-app/catalog-info.yaml | 2 +- packages/dev-utils/catalog-info.yaml | 2 +- packages/e2e-test-utils/catalog-info.yaml | 2 +- packages/e2e-test/catalog-info.yaml | 2 +- packages/errors/catalog-info.yaml | 2 +- packages/frontend-app-api/catalog-info.yaml | 2 +- packages/frontend-defaults/catalog-info.yaml | 2 +- .../catalog-info.yaml | 5 ++-- packages/frontend-internal/catalog-info.yaml | 2 +- .../frontend-plugin-api/catalog-info.yaml | 2 +- .../frontend-test-utils/catalog-info.yaml | 2 +- .../integration-aws-node/catalog-info.yaml | 2 +- packages/integration-react/catalog-info.yaml | 2 +- packages/integration/catalog-info.yaml | 2 +- packages/opaque-internal/catalog-info.yaml | 2 +- packages/release-manifests/catalog-info.yaml | 2 +- packages/repo-tools/catalog-info.yaml | 2 +- .../scaffolder-internal/catalog-info.yaml | 2 +- packages/test-utils/catalog-info.yaml | 2 +- packages/theme/catalog-info.yaml | 2 +- packages/types/catalog-info.yaml | 2 +- packages/version-bridge/catalog-info.yaml | 5 ++-- packages/yarn-plugin/catalog-info.yaml | 2 +- plugins/app-backend/catalog-info.yaml | 2 +- plugins/app-node/catalog-info.yaml | 2 +- plugins/app-visualizer/catalog-info.yaml | 2 +- plugins/app/catalog-info.yaml | 2 +- .../catalog-info.yaml | 2 +- .../catalog-info.yaml | 2 +- .../catalog-info.yaml | 2 +- .../catalog-info.yaml | 2 +- .../catalog-info.yaml | 2 +- .../catalog-info.yaml | 2 +- .../catalog-info.yaml | 2 +- .../catalog-info.yaml | 2 +- .../catalog-info.yaml | 2 +- .../catalog-info.yaml | 2 +- .../catalog-info.yaml | 2 +- .../catalog-info.yaml | 2 +- .../catalog-info.yaml | 2 +- .../catalog-info.yaml | 2 +- .../catalog-info.yaml | 2 +- .../catalog-info.yaml | 2 +- .../catalog-info.yaml | 2 +- .../catalog-info.yaml | 2 +- .../catalog-info.yaml | 2 +- .../catalog-info.yaml | 2 +- plugins/auth-backend/catalog-info.yaml | 2 +- plugins/auth-node/catalog-info.yaml | 2 +- plugins/auth-react/catalog-info.yaml | 2 +- 74 files changed, 103 insertions(+), 79 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 4c51c8f111..41f409a651 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -7,28 +7,50 @@ * @backstage/maintainers @backstage/reviewers yarn.lock @backstage/maintainers @backstage-service */yarn.lock @backstage/maintainers @backstage-service +/.changeset @backstage/operations-maintainers /.changeset/*.md +/.github @backstage/operations-maintainers /beps/0001-notifications-system @backstage/maintainers @backstage/notifications-maintainers /docs @backstage/maintainers @backstage/documentation-maintainers /docs/assets/search @backstage/search-maintainers -/docs/features/search @backstage/search-maintainers +/docs/auth @backstage/auth-maintainers +/docs/backend-system @backstage/framework-maintainers +/docs/deployment @backstage/tooling-maintainers /docs/dls @backstage/design-system-maintainers +/docs/features/search @backstage/search-maintainers /docs/features/techdocs @backstage/techdocs-maintainers +/docs/permissions @backstage/permission-maintainers /docs/plugins/integrating-search-into-plugins.md @backstage/search-maintainers +/docs/releases @backstage/operations-maintainers +/docs/tooling @backstage/tooling-maintainers /microsite @backstage/documentation-maintainers /microsite/data/plugins @backstage/maintainers -/packages/cli/src/commands/onboard @backstage/sharks +/packages @backstage/framework-maintainers /packages/backend-openapi-utils @backstage/maintainers @backstage/reviewers @backstage/openapi-tooling-maintainers /packages/canon @backstage/design-system-maintainers +/packages/catalog-client @backstage/catalog-maintainers +/packages/catalog-model @backstage/catalog-maintainers +/packages/cli @backstage/tooling-maintainers +/packages/cli-* @backstage/tooling-maintainers +/packages/cli/src/commands/onboard @backstage/sharks +/packages/e2e-test @backstage/operations-maintainers +/packages/eslint-plugin @backstage/tooling-maintainers +/packages/release-manifests @backstage/operations-maintainers +/packages/repo-tools @backstage/tooling-maintainers /packages/techdocs-cli @backstage/techdocs-maintainers /packages/techdocs-cli-embedded-app @backstage/techdocs-maintainers +/packages/yarn-plugin @backstage/tooling-maintainers +/plugins/app @backstage/framework-maintainers +/plugins/app-* @backstage/framework-maintainers +/plugins/auth @backstage/auth-maintainers +/plugins/auth-* @backstage/auth-maintainers /plugins/api-docs @backstage/maintainers @backstage/reviewers @backstage/sda-se-reviewers /plugins/bitbucket-cloud-common @backstage/maintainers @backstage/reviewers @pjungermann /plugins/catalog @backstage/maintainers @backstage/reviewers @backstage/catalog-maintainers /plugins/catalog-* @backstage/maintainers @backstage/reviewers @backstage/catalog-maintainers /plugins/catalog-backend-module-aws @backstage/maintainers @backstage/reviewers @backstage/catalog-maintainers @pjungermann /plugins/catalog-backend-module-bitbucket-cloud @backstage/maintainers @backstage/reviewers @backstage/catalog-maintainers @pjungermann -/plugins/catalog-backend-module-backstage-openapi @backstage/maintainers @backstage/reviewers @backstage/openapi-tooling-maintainers +/plugins/catalog-backend-module-backstage-openapi @backstage/maintainers @backstage/reviewers @backstage/openapi-tooling-maintainers /plugins/catalog-backend-module-msgraph @backstage/maintainers @backstage/reviewers @backstage/catalog-maintainers @pjungermann /plugins/catalog-backend-module-puppetdb @backstage/maintainers @backstage/reviewers @backstage/catalog-maintainers /plugins/catalog-graph @backstage/maintainers @backstage/reviewers @backstage/catalog-maintainers @backstage/sda-se-reviewers @@ -56,6 +78,8 @@ yarn.lock @backstage/maintainers @backst /plugins/user-settings-backend @backstage/maintainers @backstage/reviewers @backstage/sda-se-reviewers /plugins/user-settings-common @backstage/maintainers @backstage/reviewers @backstage/sda-se-reviewers +/scripts @backstage/operations-maintainers + /packages/backend-plugin-api/src/services/definitions/AuditorService.ts @backstage/maintainers @backstage/auditor-maintainers /packages/backend-defaults/src/entrypoints/auditor @backstage/maintainers @backstage/auditor-maintainers /packages/backend-defaults/report-auditor.api.md @backstage/maintainers @backstage/auditor-maintainers diff --git a/packages/app-defaults/catalog-info.yaml b/packages/app-defaults/catalog-info.yaml index e8450be1ec..057505a89c 100644 --- a/packages/app-defaults/catalog-info.yaml +++ b/packages/app-defaults/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: production type: backstage-web-library - owner: maintainers + owner: framework-maintainers diff --git a/packages/app-next-example-plugin/catalog-info.yaml b/packages/app-next-example-plugin/catalog-info.yaml index b89b43285a..53869a83a9 100644 --- a/packages/app-next-example-plugin/catalog-info.yaml +++ b/packages/app-next-example-plugin/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-frontend-plugin - owner: maintainers + owner: framework-maintainers diff --git a/packages/app-next/catalog-info.yaml b/packages/app-next/catalog-info.yaml index 85e3f0d70b..14e51ecd04 100644 --- a/packages/app-next/catalog-info.yaml +++ b/packages/app-next/catalog-info.yaml @@ -6,4 +6,4 @@ metadata: spec: lifecycle: experimental type: backstage-frontend - owner: maintainers + owner: framework-maintainers diff --git a/packages/app/catalog-info.yaml b/packages/app/catalog-info.yaml index e0808307cc..5880de9597 100644 --- a/packages/app/catalog-info.yaml +++ b/packages/app/catalog-info.yaml @@ -6,4 +6,4 @@ metadata: spec: lifecycle: experimental type: backstage-frontend - owner: maintainers + owner: framework-maintainers diff --git a/packages/backend-app-api/catalog-info.yaml b/packages/backend-app-api/catalog-info.yaml index 051e7ab5c2..c181c0720a 100644 --- a/packages/backend-app-api/catalog-info.yaml +++ b/packages/backend-app-api/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-node-library - owner: maintainers + owner: framework-maintainers diff --git a/packages/backend-defaults/catalog-info.yaml b/packages/backend-defaults/catalog-info.yaml index d3d00cc3d2..77971441c0 100644 --- a/packages/backend-defaults/catalog-info.yaml +++ b/packages/backend-defaults/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-node-library - owner: maintainers + owner: framework-maintainers diff --git a/packages/backend-dev-utils/catalog-info.yaml b/packages/backend-dev-utils/catalog-info.yaml index ecbcb4e0bb..c53d388a51 100644 --- a/packages/backend-dev-utils/catalog-info.yaml +++ b/packages/backend-dev-utils/catalog-info.yaml @@ -6,4 +6,4 @@ metadata: spec: lifecycle: experimental type: backstage-node-library - owner: maintainers + owner: framework-maintainers diff --git a/packages/backend-dynamic-feature-service/catalog-info.yaml b/packages/backend-dynamic-feature-service/catalog-info.yaml index 1269a5c406..a5ed85a20a 100644 --- a/packages/backend-dynamic-feature-service/catalog-info.yaml +++ b/packages/backend-dynamic-feature-service/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-node-library - owner: maintainers + owner: framework-maintainers diff --git a/packages/backend-plugin-api/catalog-info.yaml b/packages/backend-plugin-api/catalog-info.yaml index 35ca9850fa..28c7ea07b0 100644 --- a/packages/backend-plugin-api/catalog-info.yaml +++ b/packages/backend-plugin-api/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-node-library - owner: maintainers + owner: framework-maintainers diff --git a/packages/backend-test-utils/catalog-info.yaml b/packages/backend-test-utils/catalog-info.yaml index 396f190ef4..94c889b089 100644 --- a/packages/backend-test-utils/catalog-info.yaml +++ b/packages/backend-test-utils/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-node-library - owner: maintainers + owner: framework-maintainers diff --git a/packages/backend/catalog-info.yaml b/packages/backend/catalog-info.yaml index 0e95516a41..3fb9572dc6 100644 --- a/packages/backend/catalog-info.yaml +++ b/packages/backend/catalog-info.yaml @@ -6,4 +6,4 @@ metadata: spec: lifecycle: experimental type: backstage-backend - owner: maintainers + owner: framework-maintainers diff --git a/packages/catalog-client/catalog-info.yaml b/packages/catalog-client/catalog-info.yaml index 91a4cb5504..5dc3df2ec8 100644 --- a/packages/catalog-client/catalog-info.yaml +++ b/packages/catalog-client/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: production type: backstage-common-library - owner: maintainers + owner: catalog-maintainers diff --git a/packages/catalog-model/catalog-info.yaml b/packages/catalog-model/catalog-info.yaml index 7b2bce99b9..e314e43dc5 100644 --- a/packages/catalog-model/catalog-info.yaml +++ b/packages/catalog-model/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: production type: backstage-common-library - owner: maintainers + owner: catalog-maintainers diff --git a/packages/cli-common/catalog-info.yaml b/packages/cli-common/catalog-info.yaml index bc1f589fc1..e60377a809 100644 --- a/packages/cli-common/catalog-info.yaml +++ b/packages/cli-common/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-node-library - owner: maintainers + owner: tooling-maintainers diff --git a/packages/cli-node/catalog-info.yaml b/packages/cli-node/catalog-info.yaml index 3497007bfa..10b59eb3e0 100644 --- a/packages/cli-node/catalog-info.yaml +++ b/packages/cli-node/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-node-library - owner: maintainers + owner: tooling-maintainers diff --git a/packages/cli/catalog-info.yaml b/packages/cli/catalog-info.yaml index 212ba472d4..cec4ea5c65 100644 --- a/packages/cli/catalog-info.yaml +++ b/packages/cli/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-cli - owner: maintainers + owner: tooling-maintainers diff --git a/packages/codemods/catalog-info.yaml b/packages/codemods/catalog-info.yaml index 8e5739bd1a..3d745bb7f6 100644 --- a/packages/codemods/catalog-info.yaml +++ b/packages/codemods/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-cli - owner: maintainers + owner: framework-maintainers diff --git a/packages/config-loader/catalog-info.yaml b/packages/config-loader/catalog-info.yaml index 4dfe9c1e59..6930572b07 100644 --- a/packages/config-loader/catalog-info.yaml +++ b/packages/config-loader/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: production type: backstage-node-library - owner: maintainers + owner: framework-maintainers diff --git a/packages/config/catalog-info.yaml b/packages/config/catalog-info.yaml index c077d9d47b..363390d607 100644 --- a/packages/config/catalog-info.yaml +++ b/packages/config/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: production type: backstage-common-library - owner: maintainers + owner: framework-maintainers diff --git a/packages/core-app-api/catalog-info.yaml b/packages/core-app-api/catalog-info.yaml index 628c5a36d6..a0b76f2dbb 100644 --- a/packages/core-app-api/catalog-info.yaml +++ b/packages/core-app-api/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: production type: backstage-web-library - owner: maintainers + owner: framework-maintainers diff --git a/packages/core-compat-api/catalog-info.yaml b/packages/core-compat-api/catalog-info.yaml index f20f0543a0..c8e198bc53 100644 --- a/packages/core-compat-api/catalog-info.yaml +++ b/packages/core-compat-api/catalog-info.yaml @@ -6,4 +6,4 @@ metadata: spec: lifecycle: experimental type: backstage-web-library - owner: maintainers + owner: framework-maintainers diff --git a/packages/core-components/catalog-info.yaml b/packages/core-components/catalog-info.yaml index ba189b8c39..429253364f 100644 --- a/packages/core-components/catalog-info.yaml +++ b/packages/core-components/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-web-library - owner: maintainers + owner: framework-maintainers diff --git a/packages/core-plugin-api/catalog-info.yaml b/packages/core-plugin-api/catalog-info.yaml index 2f0fa2d4eb..b64c0a80f9 100644 --- a/packages/core-plugin-api/catalog-info.yaml +++ b/packages/core-plugin-api/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: production type: backstage-web-library - owner: maintainers + owner: framework-maintainers diff --git a/packages/create-app/catalog-info.yaml b/packages/create-app/catalog-info.yaml index 7da321f091..5f3b468e5d 100644 --- a/packages/create-app/catalog-info.yaml +++ b/packages/create-app/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-cli - owner: maintainers + owner: framework-maintainers diff --git a/packages/dev-utils/catalog-info.yaml b/packages/dev-utils/catalog-info.yaml index c67eecec83..e1c5408ec0 100644 --- a/packages/dev-utils/catalog-info.yaml +++ b/packages/dev-utils/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: production type: backstage-web-library - owner: maintainers + owner: framework-maintainers diff --git a/packages/e2e-test-utils/catalog-info.yaml b/packages/e2e-test-utils/catalog-info.yaml index d1dea51dc5..b58dc2d88a 100644 --- a/packages/e2e-test-utils/catalog-info.yaml +++ b/packages/e2e-test-utils/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-node-library - owner: maintainers + owner: framework-maintainers diff --git a/packages/e2e-test/catalog-info.yaml b/packages/e2e-test/catalog-info.yaml index 55bce03e8b..3d795a5ef4 100644 --- a/packages/e2e-test/catalog-info.yaml +++ b/packages/e2e-test/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-cli - owner: maintainers + owner: operations-maintainers diff --git a/packages/errors/catalog-info.yaml b/packages/errors/catalog-info.yaml index ee97045b20..d39e32e171 100644 --- a/packages/errors/catalog-info.yaml +++ b/packages/errors/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: production type: backstage-common-library - owner: maintainers + owner: framework-maintainers diff --git a/packages/frontend-app-api/catalog-info.yaml b/packages/frontend-app-api/catalog-info.yaml index 332650f083..4e44782440 100644 --- a/packages/frontend-app-api/catalog-info.yaml +++ b/packages/frontend-app-api/catalog-info.yaml @@ -6,4 +6,4 @@ metadata: spec: lifecycle: experimental type: backstage-web-library - owner: maintainers + owner: framework-maintainers diff --git a/packages/frontend-defaults/catalog-info.yaml b/packages/frontend-defaults/catalog-info.yaml index cd0ac9b79a..0a3f07fd29 100644 --- a/packages/frontend-defaults/catalog-info.yaml +++ b/packages/frontend-defaults/catalog-info.yaml @@ -6,4 +6,4 @@ metadata: spec: lifecycle: experimental type: backstage-web-library - owner: maintainers + owner: framework-maintainers diff --git a/packages/frontend-dynamic-feature-loader/catalog-info.yaml b/packages/frontend-dynamic-feature-loader/catalog-info.yaml index 3f9ef427ed..a44650174a 100644 --- a/packages/frontend-dynamic-feature-loader/catalog-info.yaml +++ b/packages/frontend-dynamic-feature-loader/catalog-info.yaml @@ -3,8 +3,9 @@ kind: Component metadata: name: backstage-frontend-dynamic-feature-loader title: '@backstage/frontend-dynamic-feature-loader' - description: Backstage frontend feature loader to load new frontend system plugins exposed as module federation remotes. + description: Backstage frontend feature loader to load new frontend system + plugins exposed as module federation remotes. spec: lifecycle: experimental type: backstage-web-library - owner: maintainers + owner: framework-maintainers diff --git a/packages/frontend-internal/catalog-info.yaml b/packages/frontend-internal/catalog-info.yaml index 630f53275a..20dcb6c970 100644 --- a/packages/frontend-internal/catalog-info.yaml +++ b/packages/frontend-internal/catalog-info.yaml @@ -6,4 +6,4 @@ metadata: spec: lifecycle: experimental type: backstage-web-library - owner: maintainers + owner: framework-maintainers diff --git a/packages/frontend-plugin-api/catalog-info.yaml b/packages/frontend-plugin-api/catalog-info.yaml index 4531f6eca3..8ce7aacabd 100644 --- a/packages/frontend-plugin-api/catalog-info.yaml +++ b/packages/frontend-plugin-api/catalog-info.yaml @@ -6,4 +6,4 @@ metadata: spec: lifecycle: experimental type: backstage-web-library - owner: maintainers + owner: framework-maintainers diff --git a/packages/frontend-test-utils/catalog-info.yaml b/packages/frontend-test-utils/catalog-info.yaml index e2d2a57897..065a0b64e2 100644 --- a/packages/frontend-test-utils/catalog-info.yaml +++ b/packages/frontend-test-utils/catalog-info.yaml @@ -6,4 +6,4 @@ metadata: spec: lifecycle: experimental type: backstage-web-library - owner: maintainers + owner: framework-maintainers diff --git a/packages/integration-aws-node/catalog-info.yaml b/packages/integration-aws-node/catalog-info.yaml index 4c8264d575..88b86deb84 100644 --- a/packages/integration-aws-node/catalog-info.yaml +++ b/packages/integration-aws-node/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-node-library - owner: maintainers + owner: framework-maintainers diff --git a/packages/integration-react/catalog-info.yaml b/packages/integration-react/catalog-info.yaml index 8df5e52c95..1b6876ed99 100644 --- a/packages/integration-react/catalog-info.yaml +++ b/packages/integration-react/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: production type: backstage-web-library - owner: maintainers + owner: framework-maintainers diff --git a/packages/integration/catalog-info.yaml b/packages/integration/catalog-info.yaml index 7f602c42be..5f9e30681a 100644 --- a/packages/integration/catalog-info.yaml +++ b/packages/integration/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: production type: backstage-common-library - owner: maintainers + owner: framework-maintainers diff --git a/packages/opaque-internal/catalog-info.yaml b/packages/opaque-internal/catalog-info.yaml index b1c5d01937..8fd8136d34 100644 --- a/packages/opaque-internal/catalog-info.yaml +++ b/packages/opaque-internal/catalog-info.yaml @@ -6,4 +6,4 @@ metadata: spec: lifecycle: experimental type: backstage-common-library - owner: maintainers + owner: framework-maintainers diff --git a/packages/release-manifests/catalog-info.yaml b/packages/release-manifests/catalog-info.yaml index 4564145a3d..dfef3b2a5f 100644 --- a/packages/release-manifests/catalog-info.yaml +++ b/packages/release-manifests/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-common-library - owner: maintainers + owner: operations-maintainers diff --git a/packages/repo-tools/catalog-info.yaml b/packages/repo-tools/catalog-info.yaml index 66b5ecc64b..379111cbca 100644 --- a/packages/repo-tools/catalog-info.yaml +++ b/packages/repo-tools/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-cli - owner: maintainers + owner: tooling-maintainers diff --git a/packages/scaffolder-internal/catalog-info.yaml b/packages/scaffolder-internal/catalog-info.yaml index c2c2c013d3..a586883cca 100644 --- a/packages/scaffolder-internal/catalog-info.yaml +++ b/packages/scaffolder-internal/catalog-info.yaml @@ -6,4 +6,4 @@ metadata: spec: lifecycle: experimental type: backstage-web-library - owner: maintainers + owner: framework-maintainers diff --git a/packages/test-utils/catalog-info.yaml b/packages/test-utils/catalog-info.yaml index 4b894f08b5..534241122d 100644 --- a/packages/test-utils/catalog-info.yaml +++ b/packages/test-utils/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: production type: backstage-web-library - owner: maintainers + owner: framework-maintainers diff --git a/packages/theme/catalog-info.yaml b/packages/theme/catalog-info.yaml index 1fee85f58d..f2faf9ed42 100644 --- a/packages/theme/catalog-info.yaml +++ b/packages/theme/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-web-library - owner: maintainers + owner: framework-maintainers diff --git a/packages/types/catalog-info.yaml b/packages/types/catalog-info.yaml index 1bd30379cc..cd20c3758e 100644 --- a/packages/types/catalog-info.yaml +++ b/packages/types/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: production type: backstage-common-library - owner: maintainers + owner: framework-maintainers diff --git a/packages/version-bridge/catalog-info.yaml b/packages/version-bridge/catalog-info.yaml index 6f37a4b276..43899985ab 100644 --- a/packages/version-bridge/catalog-info.yaml +++ b/packages/version-bridge/catalog-info.yaml @@ -4,9 +4,8 @@ metadata: name: backstage-version-bridge title: '@backstage/version-bridge' description: >- - Utilities used by @backstage packages to support multiple concurrent - versions + Utilities used by @backstage packages to support multiple concurrent versions spec: lifecycle: production type: backstage-web-library - owner: maintainers + owner: framework-maintainers diff --git a/packages/yarn-plugin/catalog-info.yaml b/packages/yarn-plugin/catalog-info.yaml index c3a9be8fa5..dbb07a7602 100644 --- a/packages/yarn-plugin/catalog-info.yaml +++ b/packages/yarn-plugin/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-node-library - owner: maintainers + owner: tooling-maintainers diff --git a/plugins/app-backend/catalog-info.yaml b/plugins/app-backend/catalog-info.yaml index 27135f4f8d..d75255357b 100644 --- a/plugins/app-backend/catalog-info.yaml +++ b/plugins/app-backend/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-backend-plugin - owner: maintainers + owner: framework-maintainers diff --git a/plugins/app-node/catalog-info.yaml b/plugins/app-node/catalog-info.yaml index 6624153c6c..3eb9184e2e 100644 --- a/plugins/app-node/catalog-info.yaml +++ b/plugins/app-node/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-node-library - owner: maintainers + owner: framework-maintainers diff --git a/plugins/app-visualizer/catalog-info.yaml b/plugins/app-visualizer/catalog-info.yaml index cb444af524..ed59a5eb45 100644 --- a/plugins/app-visualizer/catalog-info.yaml +++ b/plugins/app-visualizer/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-frontend-plugin - owner: maintainers + owner: framework-maintainers diff --git a/plugins/app/catalog-info.yaml b/plugins/app/catalog-info.yaml index 5bae243ea7..3396a5c98e 100644 --- a/plugins/app/catalog-info.yaml +++ b/plugins/app/catalog-info.yaml @@ -6,4 +6,4 @@ metadata: spec: lifecycle: experimental type: backstage-frontend-plugin - owner: maintainers + owner: framework-maintainers diff --git a/plugins/auth-backend-module-atlassian-provider/catalog-info.yaml b/plugins/auth-backend-module-atlassian-provider/catalog-info.yaml index d3e2c11c17..dc54d48600 100644 --- a/plugins/auth-backend-module-atlassian-provider/catalog-info.yaml +++ b/plugins/auth-backend-module-atlassian-provider/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-backend-plugin-module - owner: maintainers + owner: auth-maintainers diff --git a/plugins/auth-backend-module-auth0-provider/catalog-info.yaml b/plugins/auth-backend-module-auth0-provider/catalog-info.yaml index 26b540ca8a..46503619da 100644 --- a/plugins/auth-backend-module-auth0-provider/catalog-info.yaml +++ b/plugins/auth-backend-module-auth0-provider/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-backend-plugin-module - owner: maintainers + owner: auth-maintainers diff --git a/plugins/auth-backend-module-aws-alb-provider/catalog-info.yaml b/plugins/auth-backend-module-aws-alb-provider/catalog-info.yaml index b3ced6b59d..8282046b05 100644 --- a/plugins/auth-backend-module-aws-alb-provider/catalog-info.yaml +++ b/plugins/auth-backend-module-aws-alb-provider/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-backend-plugin-module - owner: maintainers + owner: auth-maintainers diff --git a/plugins/auth-backend-module-azure-easyauth-provider/catalog-info.yaml b/plugins/auth-backend-module-azure-easyauth-provider/catalog-info.yaml index 174f1a52e4..edbfcbca30 100644 --- a/plugins/auth-backend-module-azure-easyauth-provider/catalog-info.yaml +++ b/plugins/auth-backend-module-azure-easyauth-provider/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-backend-plugin-module - owner: maintainers + owner: auth-maintainers diff --git a/plugins/auth-backend-module-bitbucket-provider/catalog-info.yaml b/plugins/auth-backend-module-bitbucket-provider/catalog-info.yaml index 817eb561a5..2e6f8d4481 100644 --- a/plugins/auth-backend-module-bitbucket-provider/catalog-info.yaml +++ b/plugins/auth-backend-module-bitbucket-provider/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-backend-plugin-module - owner: maintainers + owner: auth-maintainers diff --git a/plugins/auth-backend-module-bitbucket-server-provider/catalog-info.yaml b/plugins/auth-backend-module-bitbucket-server-provider/catalog-info.yaml index bcb659b1c2..893f873c4e 100644 --- a/plugins/auth-backend-module-bitbucket-server-provider/catalog-info.yaml +++ b/plugins/auth-backend-module-bitbucket-server-provider/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-backend-plugin-module - owner: maintainers + owner: auth-maintainers diff --git a/plugins/auth-backend-module-cloudflare-access-provider/catalog-info.yaml b/plugins/auth-backend-module-cloudflare-access-provider/catalog-info.yaml index cdd42a040b..0c8547e048 100644 --- a/plugins/auth-backend-module-cloudflare-access-provider/catalog-info.yaml +++ b/plugins/auth-backend-module-cloudflare-access-provider/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-backend-plugin-module - owner: maintainers + owner: auth-maintainers diff --git a/plugins/auth-backend-module-gcp-iap-provider/catalog-info.yaml b/plugins/auth-backend-module-gcp-iap-provider/catalog-info.yaml index ef604027df..a043e002c3 100644 --- a/plugins/auth-backend-module-gcp-iap-provider/catalog-info.yaml +++ b/plugins/auth-backend-module-gcp-iap-provider/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-backend-plugin-module - owner: maintainers + owner: auth-maintainers diff --git a/plugins/auth-backend-module-github-provider/catalog-info.yaml b/plugins/auth-backend-module-github-provider/catalog-info.yaml index 8eda78fdaa..f8c11a2a17 100644 --- a/plugins/auth-backend-module-github-provider/catalog-info.yaml +++ b/plugins/auth-backend-module-github-provider/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-backend-plugin-module - owner: maintainers + owner: auth-maintainers diff --git a/plugins/auth-backend-module-gitlab-provider/catalog-info.yaml b/plugins/auth-backend-module-gitlab-provider/catalog-info.yaml index 55086778e4..5d355a19ad 100644 --- a/plugins/auth-backend-module-gitlab-provider/catalog-info.yaml +++ b/plugins/auth-backend-module-gitlab-provider/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-backend-plugin-module - owner: maintainers + owner: auth-maintainers diff --git a/plugins/auth-backend-module-google-provider/catalog-info.yaml b/plugins/auth-backend-module-google-provider/catalog-info.yaml index 0897a12168..f32053ea3f 100644 --- a/plugins/auth-backend-module-google-provider/catalog-info.yaml +++ b/plugins/auth-backend-module-google-provider/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-backend-plugin-module - owner: maintainers + owner: auth-maintainers diff --git a/plugins/auth-backend-module-guest-provider/catalog-info.yaml b/plugins/auth-backend-module-guest-provider/catalog-info.yaml index 5d3513296b..45a2bd8701 100644 --- a/plugins/auth-backend-module-guest-provider/catalog-info.yaml +++ b/plugins/auth-backend-module-guest-provider/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-backend-plugin-module - owner: maintainers + owner: auth-maintainers diff --git a/plugins/auth-backend-module-microsoft-provider/catalog-info.yaml b/plugins/auth-backend-module-microsoft-provider/catalog-info.yaml index 1742fc9ed6..4f6609bef3 100644 --- a/plugins/auth-backend-module-microsoft-provider/catalog-info.yaml +++ b/plugins/auth-backend-module-microsoft-provider/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-backend-plugin-module - owner: maintainers + owner: auth-maintainers diff --git a/plugins/auth-backend-module-oauth2-provider/catalog-info.yaml b/plugins/auth-backend-module-oauth2-provider/catalog-info.yaml index 4ee7997b0c..cae10fccd6 100644 --- a/plugins/auth-backend-module-oauth2-provider/catalog-info.yaml +++ b/plugins/auth-backend-module-oauth2-provider/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-backend-plugin-module - owner: maintainers + owner: auth-maintainers diff --git a/plugins/auth-backend-module-oauth2-proxy-provider/catalog-info.yaml b/plugins/auth-backend-module-oauth2-proxy-provider/catalog-info.yaml index c26202c97d..7518c1b534 100644 --- a/plugins/auth-backend-module-oauth2-proxy-provider/catalog-info.yaml +++ b/plugins/auth-backend-module-oauth2-proxy-provider/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-backend-plugin-module - owner: maintainers + owner: auth-maintainers diff --git a/plugins/auth-backend-module-oidc-provider/catalog-info.yaml b/plugins/auth-backend-module-oidc-provider/catalog-info.yaml index 896738898b..207ee0dd8f 100644 --- a/plugins/auth-backend-module-oidc-provider/catalog-info.yaml +++ b/plugins/auth-backend-module-oidc-provider/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-backend-plugin-module - owner: maintainers + owner: auth-maintainers diff --git a/plugins/auth-backend-module-okta-provider/catalog-info.yaml b/plugins/auth-backend-module-okta-provider/catalog-info.yaml index 7a5536edcf..e12e80b4ff 100644 --- a/plugins/auth-backend-module-okta-provider/catalog-info.yaml +++ b/plugins/auth-backend-module-okta-provider/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-backend-plugin-module - owner: maintainers + owner: auth-maintainers diff --git a/plugins/auth-backend-module-onelogin-provider/catalog-info.yaml b/plugins/auth-backend-module-onelogin-provider/catalog-info.yaml index 6988215b59..9995800641 100644 --- a/plugins/auth-backend-module-onelogin-provider/catalog-info.yaml +++ b/plugins/auth-backend-module-onelogin-provider/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-backend-plugin-module - owner: maintainers + owner: auth-maintainers diff --git a/plugins/auth-backend-module-pinniped-provider/catalog-info.yaml b/plugins/auth-backend-module-pinniped-provider/catalog-info.yaml index 9d1ef1c299..5fa819c9d9 100644 --- a/plugins/auth-backend-module-pinniped-provider/catalog-info.yaml +++ b/plugins/auth-backend-module-pinniped-provider/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-backend-plugin-module - owner: maintainers + owner: auth-maintainers diff --git a/plugins/auth-backend-module-vmware-cloud-provider/catalog-info.yaml b/plugins/auth-backend-module-vmware-cloud-provider/catalog-info.yaml index d40b1e4308..e35b58128d 100644 --- a/plugins/auth-backend-module-vmware-cloud-provider/catalog-info.yaml +++ b/plugins/auth-backend-module-vmware-cloud-provider/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-backend-plugin-module - owner: maintainers + owner: auth-maintainers diff --git a/plugins/auth-backend/catalog-info.yaml b/plugins/auth-backend/catalog-info.yaml index 38ac5f0753..1fbb18685d 100644 --- a/plugins/auth-backend/catalog-info.yaml +++ b/plugins/auth-backend/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-backend-plugin - owner: maintainers + owner: auth-maintainers diff --git a/plugins/auth-node/catalog-info.yaml b/plugins/auth-node/catalog-info.yaml index 4b23642810..936aae901e 100644 --- a/plugins/auth-node/catalog-info.yaml +++ b/plugins/auth-node/catalog-info.yaml @@ -6,4 +6,4 @@ metadata: spec: lifecycle: experimental type: backstage-node-library - owner: maintainers + owner: auth-maintainers diff --git a/plugins/auth-react/catalog-info.yaml b/plugins/auth-react/catalog-info.yaml index b75aa32c56..1118130d74 100644 --- a/plugins/auth-react/catalog-info.yaml +++ b/plugins/auth-react/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-web-library - owner: maintainers + owner: auth-maintainers From 729bb0c5f80d56be387d1eeb1a41ff7bb5ea9502 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 25 Apr 2025 16:54:44 +0200 Subject: [PATCH 079/109] OWNERS.md: remove microsite ownership update Signed-off-by: Patrik Oldsberg --- OWNERS.md | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/OWNERS.md b/OWNERS.md index 9612f480cd..c08214cb24 100644 --- a/OWNERS.md +++ b/OWNERS.md @@ -106,21 +106,6 @@ Scope: The Kubernetes plugin and the base it provides for other plugins to build | -------------- | ------------ | ---- | ---------------------------------------- | ------------ | | Matthew Clarke | Spotify | | [mclarke47](http://github.com/mclarke47) | mclarke#0725 | -### Microsite - -Team: @backstage/microsite-maintainers - -Scope: The microsite at [backstage.io](https://backstage.io), excluding the documentation. - -| Name | Organization | Team | GitHub | Discord | -| -------------------- | ------------ | ------------- | ----------------------------------------------- | --------------- | -| Patrik Oldsberg | Spotify | Cubic Belugas | [Rugvip](https://github.com/Rugvip) | `Rugvip#0019` | -| Ben Lambert | Spotify | Cubic Belugas | [benjdlambert](https://github.com/benjdlambert) | `blam#2159` | -| Camila Loiola | Spotify | Cubic Belugas | [camilaibs](http://github.com/camilaibs) | `camilal#0226` | -| Fredrik Adelöw | Spotify | Cubic Belugas | [freben](https://github.com/freben) | `freben#3926` | -| Mihai Tabara | Spotify | Cubic Belugas | [MihaiTabara](http://github.com/MihaiTabara) | `mihait#3107` | -| Vincenzo Scamporlino | Spotify | Cubic Belugas | [vinzscam](http://github.com/vinzscam) | `vinzscam#6944` | - ### Operations Team: @backstage/operations-maintainers From 84489484814ed8a126f244776e9e94b569dd28d5 Mon Sep 17 00:00:00 2001 From: gaelgoth Date: Mon, 5 May 2025 18:33:58 +0200 Subject: [PATCH 080/109] remove lerna-debug.log pattern Signed-off-by: gaelgoth --- .changeset/busy-badgers-hang.md | 5 +++++ packages/create-app/templates/default-app/.gitignore.hbs | 1 - 2 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 .changeset/busy-badgers-hang.md diff --git a/.changeset/busy-badgers-hang.md b/.changeset/busy-badgers-hang.md new file mode 100644 index 0000000000..fc4b0e907e --- /dev/null +++ b/.changeset/busy-badgers-hang.md @@ -0,0 +1,5 @@ +--- +'@backstage/create-app': patch +--- + +Removed `lerna-debug.log*` pattern from `.gitignore` as Lerna was removed from the package in version `@backstage/create-app@0.5.19`. diff --git a/packages/create-app/templates/default-app/.gitignore.hbs b/packages/create-app/templates/default-app/.gitignore.hbs index 77ad56d128..e506e00f92 100644 --- a/packages/create-app/templates/default-app/.gitignore.hbs +++ b/packages/create-app/templates/default-app/.gitignore.hbs @@ -7,7 +7,6 @@ logs npm-debug.log* yarn-debug.log* yarn-error.log* -lerna-debug.log* # Coverage directory generated when running tests with coverage coverage From e099d0a4cedf346c20f58c7e539c0a8a762f1f51 Mon Sep 17 00:00:00 2001 From: Jackson Chen Date: Mon, 5 May 2025 15:19:00 -0400 Subject: [PATCH 081/109] format user entity ref mentions to be Slack compatible Signed-off-by: Jackson Chen --- .changeset/true-trains-tie.md | 7 + .../lib/SlackNotificationProcessor.test.ts | 123 ++++++++++++++++++ .../src/lib/SlackNotificationProcessor.ts | 52 +++++++- 3 files changed, 181 insertions(+), 1 deletion(-) create mode 100644 .changeset/true-trains-tie.md diff --git a/.changeset/true-trains-tie.md b/.changeset/true-trains-tie.md new file mode 100644 index 0000000000..678e6a85e9 --- /dev/null +++ b/.changeset/true-trains-tie.md @@ -0,0 +1,7 @@ +--- +'@backstage/plugin-notifications-backend-module-slack': patch +--- + +Notifications which mention user entity refs are now replaced with Slack compatible mentions. + +Example: `Welcome <@user:default/billy>!` -> `Welcome <@U123456890>!` diff --git a/plugins/notifications-backend-module-slack/src/lib/SlackNotificationProcessor.test.ts b/plugins/notifications-backend-module-slack/src/lib/SlackNotificationProcessor.test.ts index f5893ff456..87abf64717 100644 --- a/plugins/notifications-backend-module-slack/src/lib/SlackNotificationProcessor.test.ts +++ b/plugins/notifications-backend-module-slack/src/lib/SlackNotificationProcessor.test.ts @@ -543,4 +543,127 @@ describe('SlackNotificationProcessor', () => { expect(slack.chat.postMessage).not.toHaveBeenCalled(); }); }); + + describe('when replacing user entity refs with Slack IDs', () => { + const createBaseMessage = (text: string) => ({ + channel: 'U12345678', + text: 'notification', + attachments: [ + { + color: '#00A699', + blocks: [ + { + type: 'section', + text: { + type: 'mrkdwn', + text, + }, + accessory: { + type: 'button', + text: { + type: 'plain_text', + text: 'View More', + }, + action_id: 'button-action', + }, + }, + { + type: 'context', + elements: [ + { + type: 'plain_text', + text: 'Severity: normal', + emoji: true, + }, + { + type: 'plain_text', + text: 'Topic: N/A', + emoji: true, + }, + ], + }, + ], + fallback: 'notification', + }, + ], + }); + + it('should replace user entity refs with Slack compatible mentions', async () => { + const slack = new WebClient(); + const processor = SlackNotificationProcessor.fromConfig(config, { + auth, + discovery, + logger, + catalog: catalogServiceMock({ + entities: DEFAULT_ENTITIES_RESPONSE.items, + }), + slack, + })[0]; + + await processor.postProcess( + { + origin: 'plugin', + id: '1234', + user: 'user:default/mock', + created: new Date(), + payload: { + title: 'notification', + description: + 'Hello <@user:default/mock> and <@user:default/mock-without-slack-annotation>', + }, + }, + { + recipients: { type: 'entity', entityRef: 'user:default/mock' }, + payload: { + title: 'notification', + description: + 'Hello <@user:default/mock> and <@user:default/mock-without-slack-annotation>', + }, + }, + ); + + expect(slack.chat.postMessage).toHaveBeenCalledWith( + createBaseMessage( + 'Hello <@U12345678> and <@user:default/mock-without-slack-annotation>', + ), + ); + }); + + it('should handle text without user entity refs', async () => { + const slack = new WebClient(); + const processor = SlackNotificationProcessor.fromConfig(config, { + auth, + discovery, + logger, + catalog: catalogServiceMock({ + entities: DEFAULT_ENTITIES_RESPONSE.items, + }), + slack, + })[0]; + + await processor.postProcess( + { + origin: 'plugin', + id: '1234', + user: 'user:default/mock', + created: new Date(), + payload: { + title: 'notification', + description: 'Hello world', + }, + }, + { + recipients: { type: 'entity', entityRef: 'user:default/mock' }, + payload: { + title: 'notification', + description: 'Hello world', + }, + }, + ); + + expect(slack.chat.postMessage).toHaveBeenCalledWith( + createBaseMessage('Hello world'), + ); + }); + }); }); diff --git a/plugins/notifications-backend-module-slack/src/lib/SlackNotificationProcessor.ts b/plugins/notifications-backend-module-slack/src/lib/SlackNotificationProcessor.ts index ca51eeb068..774150676c 100644 --- a/plugins/notifications-backend-module-slack/src/lib/SlackNotificationProcessor.ts +++ b/plugins/notifications-backend-module-slack/src/lib/SlackNotificationProcessor.ts @@ -236,8 +236,11 @@ export class SlackNotificationProcessor implements NotificationProcessor { } // Prepare outbound messages + const formattedPayload = await this.formatPayloadDescriptionForSlack( + options.payload, + ); const outbound = destinations.map(channel => - toChatPostMessageArgs({ channel, payload: options.payload }), + toChatPostMessageArgs({ channel, payload: formattedPayload }), ); // Log debug info @@ -249,6 +252,15 @@ export class SlackNotificationProcessor implements NotificationProcessor { await this.sendNotifications(outbound); } + private async formatPayloadDescriptionForSlack( + payload: Notification['payload'], + ) { + return { + ...payload, + description: await this.replaceUserRefsWithSlackIds(payload.description), + }; + } + async getEntities( entityRefs: readonly string[], ): Promise<(Entity | undefined)[]> { @@ -274,6 +286,44 @@ export class SlackNotificationProcessor implements NotificationProcessor { return response.items; } + async replaceUserRefsWithSlackIds( + text?: string, + ): Promise { + if (!text) return undefined; + + // Match user entity refs like "<@user:default/billy>" + const userRefRegex = /<@(user:[^>]+)>/gi; + const matches = [...text.matchAll(userRefRegex)]; + + if (matches.length === 0) return text; + + const uniqueUserRefs = new Set( + matches.map(match => match[1].toLowerCase()), + ); + + const slackIdMap = new Map(); + + await Promise.all( + [...uniqueUserRefs].map(async userRef => { + try { + const slackId = await this.getSlackNotificationTarget(userRef); + if (slackId) { + slackIdMap.set(userRef, `<@${slackId}>`); + } + } catch (error) { + this.logger.warn( + `Failed to resolve Slack ID for user ref "${userRef}": ${error}`, + ); + } + }), + ); + + return text.replace(userRefRegex, (match, userRef) => { + const slackId = slackIdMap.get(userRef.toLowerCase()); + return slackId ?? match; + }); + } + async getSlackNotificationTarget( entityRef: string, ): Promise { From 599ce8b6d8426c65da5cc5432ca184a71d307e8c Mon Sep 17 00:00:00 2001 From: Peter Macdonald Date: Tue, 6 May 2025 08:11:41 +0200 Subject: [PATCH 082/109] feat: add myself to community plugins owners Signed-off-by: Peter Macdonald --- OWNERS.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/OWNERS.md b/OWNERS.md index c6bbcf71d7..c07b803e74 100644 --- a/OWNERS.md +++ b/OWNERS.md @@ -137,12 +137,13 @@ Team: @backstage/community-plugins-maintainers Scope: Tooling and Community Repo Maintainers for the Backstage [Community Plugins repository](https://github.com/backstage/community-plugins) -| Name | Organization | GitHub | Discord | -| -------------------- | ------------ | ------------------------------------------- | ------------ | -| André Wanlin | Spotify | [awanlin](https://github.com/awanlin) | `ahhhndre` | -| Bethany Griggs | Red Hat | [BethGriggs](https://github.com/BethGriggs) | `bethgriggs` | -| Kashish Mittal | Red Hat | [04kash](https://github.com/04kash) | `kashh._.` | -| Vincenzo Scamporlino | Spotify | [vinzscam](https://github.com/vinzscam) | `vinzscam` | +| Name | Organization | GitHub | Discord | +| -------------------- | ------------- | ------------------------------------------- | ------------ | +| André Wanlin | Spotify | [awanlin](https://github.com/awanlin) | `ahhhndre` | +| Bethany Griggs | Red Hat | [BethGriggs](https://github.com/BethGriggs) | `bethgriggs` | +| Kashish Mittal | Red Hat | [04kash](https://github.com/04kash) | `kashh._.` | +| Vincenzo Scamporlino | Spotify | [vinzscam](https://github.com/vinzscam) | `vinzscam` | +| Peter Macdonald | VodafoneZiggo | [Parsifal-M](https://github.com/Parsifal-M) | `parsifal` | ### Documentation From fa485943e45a913e4d795405f0f5bbd536a6b051 Mon Sep 17 00:00:00 2001 From: mario ma Date: Wed, 26 Mar 2025 23:10:41 +0800 Subject: [PATCH 083/109] search plugin support i18n Signed-off-by: mario ma --- .changeset/wicked-clubs-dream.md | 6 +++ plugins/search-react/report-alpha.api.md | 20 +++++++++ plugins/search-react/src/alpha/index.ts | 1 + .../src/components/SearchBar/SearchBar.tsx | 12 ++++-- .../components/SearchFilter/SearchFilter.tsx | 8 +++- .../SearchPagination/SearchPagination.tsx | 9 +++- .../components/SearchResult/SearchResult.tsx | 5 ++- .../SearchResultGroup/SearchResultGroup.tsx | 9 ++-- .../SearchResultList/SearchResultList.tsx | 5 ++- .../SearchResultPager/SearchResultPager.tsx | 7 ++- plugins/search-react/src/translation.ts | 43 +++++++++++++++++++ plugins/search/src/alpha.tsx | 3 ++ .../components/SearchModal/SearchModal.tsx | 5 ++- .../SearchType/SearchType.Accordion.tsx | 18 +++++--- .../components/SearchType/SearchType.Tabs.tsx | 5 ++- .../src/components/SearchType/SearchType.tsx | 5 ++- .../SidebarSearchModal/SidebarSearchModal.tsx | 5 ++- plugins/search/src/translation.ts | 40 +++++++++++++++++ 18 files changed, 182 insertions(+), 24 deletions(-) create mode 100644 .changeset/wicked-clubs-dream.md create mode 100644 plugins/search-react/src/translation.ts create mode 100644 plugins/search/src/translation.ts diff --git a/.changeset/wicked-clubs-dream.md b/.changeset/wicked-clubs-dream.md new file mode 100644 index 0000000000..c866ac6312 --- /dev/null +++ b/.changeset/wicked-clubs-dream.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-search-react': patch +'@backstage/plugin-search': patch +--- + +search plugin support i18n diff --git a/plugins/search-react/report-alpha.api.md b/plugins/search-react/report-alpha.api.md index 98a3c533db..afe08b0f64 100644 --- a/plugins/search-react/report-alpha.api.md +++ b/plugins/search-react/report-alpha.api.md @@ -8,6 +8,7 @@ import { ExtensionBlueprint } from '@backstage/frontend-plugin-api'; import { ListItemProps } from '@material-ui/core/ListItem'; import { SearchDocument } from '@backstage/plugin-search-common'; import { SearchResult } from '@backstage/plugin-search-common'; +import { TranslationRef } from '@backstage/core-plugin-api/alpha'; // @alpha (undocumented) export type BaseSearchResultListItemProps = T & { @@ -94,6 +95,25 @@ export interface SearchFilterResultTypeBlueprintParams { value: string; } +// Warning: (ae-missing-release-tag) "searchReactTranslationRef" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const searchReactTranslationRef: TranslationRef< + 'search-react', + { + readonly 'searchBar.title': 'Search'; + readonly 'searchBar.placeholder': 'Search in {{org}}'; + readonly 'searchFilter.allOptionTitle': 'All'; + readonly 'searchPagination.limitLabel': 'Results per page:'; + readonly 'searchPagination.limitText': 'of {{num}}'; + readonly noResultsDescription: 'Sorry, no results were found'; + readonly 'searchResultGroup.linkTitle': 'See All'; + readonly 'searchResultGroup.addFilterButtonTitle': 'Add filter'; + readonly 'searchResultPager.next': 'Next'; + readonly 'searchResultPager.previous': 'Previous'; + } +>; + // @alpha (undocumented) export type SearchResultItemExtensionComponent = < P extends BaseSearchResultListItemProps, diff --git a/plugins/search-react/src/alpha/index.ts b/plugins/search-react/src/alpha/index.ts index 239ffa89dd..7c44f59d56 100644 --- a/plugins/search-react/src/alpha/index.ts +++ b/plugins/search-react/src/alpha/index.ts @@ -14,3 +14,4 @@ * limitations under the License. */ export * from './blueprints'; +export { searchReactTranslationRef } from '../translation'; diff --git a/plugins/search-react/src/components/SearchBar/SearchBar.tsx b/plugins/search-react/src/components/SearchBar/SearchBar.tsx index 98bfe81b32..d2a1d08283 100644 --- a/plugins/search-react/src/components/SearchBar/SearchBar.tsx +++ b/plugins/search-react/src/components/SearchBar/SearchBar.tsx @@ -38,6 +38,8 @@ import { } from 'react'; import useDebounce from 'react-use/esm/useDebounce'; import { SearchContextProvider, useSearch } from '../../context'; +import { useTranslationRef } from '@backstage/frontend-plugin-api'; +import { searchReactTranslationRef } from '../../translation'; /** * Props for {@link SearchBarBase}. @@ -81,6 +83,7 @@ export const SearchBarBase = forwardRef((props: SearchBarBaseProps, ref) => { const configApi = useApi(configApiRef); const [value, setValue] = useState(''); const forwardedValueRef = useRef(''); + const { t } = useTranslationRef(searchReactTranslationRef); useEffect(() => { setValue(prevValue => { @@ -129,12 +132,15 @@ export const SearchBarBase = forwardRef((props: SearchBarBaseProps, ref) => { } }, [onChange, onClear]); - const ariaLabel: string | undefined = label ? undefined : 'Search'; + const ariaLabel: string | undefined = label + ? undefined + : t('searchBar.title'); const inputPlaceholder = placeholder ?? - `Search in ${configApi.getOptionalString('app.title') || 'Backstage'}`; - + t('searchBar.placeholder', { + org: configApi.getOptionalString('app.title') || 'Backstage', + }); const SearchIcon = useApp().getSystemIcon('search') || DefaultSearchIcon; const startAdornment = ( diff --git a/plugins/search-react/src/components/SearchFilter/SearchFilter.tsx b/plugins/search-react/src/components/SearchFilter/SearchFilter.tsx index da96c711c4..1d0ce79f75 100644 --- a/plugins/search-react/src/components/SearchFilter/SearchFilter.tsx +++ b/plugins/search-react/src/components/SearchFilter/SearchFilter.tsx @@ -31,6 +31,8 @@ import { } from './SearchFilter.Autocomplete'; import { useAsyncFilterValues, useDefaultFilterValue } from './hooks'; import { ensureFilterValueWithLabel, FilterValue } from './types'; +import { useTranslationRef } from '@backstage/frontend-plugin-api'; +import { searchReactTranslationRef } from '../../translation'; const useStyles = makeStyles({ label: { @@ -165,6 +167,7 @@ export const SelectFilter = (props: SearchFilterComponentProps) => { values: givenValues, valuesDebounceMs, } = props; + const { t } = useTranslationRef(searchReactTranslationRef); useDefaultFilterValue(name, defaultValue); const asyncValues = typeof givenValues === 'function' ? givenValues : undefined; @@ -179,7 +182,10 @@ export const SelectFilter = (props: SearchFilterComponentProps) => { valuesDebounceMs, ); const allOptionValue = useRef(uuid()); - const allOption = { value: allOptionValue.current, label: 'All' }; + const allOption = { + value: allOptionValue.current, + label: t('searchFilter.allOptionTitle'), + }; const { filters, setFilters } = useSearch(); const handleChange = (value: SelectedItems) => { diff --git a/plugins/search-react/src/components/SearchPagination/SearchPagination.tsx b/plugins/search-react/src/components/SearchPagination/SearchPagination.tsx index 8906b82e79..571fa3f1a1 100644 --- a/plugins/search-react/src/components/SearchPagination/SearchPagination.tsx +++ b/plugins/search-react/src/components/SearchPagination/SearchPagination.tsx @@ -23,6 +23,8 @@ import { } from 'react'; import TablePagination from '@material-ui/core/TablePagination'; import { useSearch } from '../../context'; +import { useTranslationRef } from '@backstage/frontend-plugin-api'; +import { searchReactTranslationRef } from '../../translation'; const encodePageCursor = (pageCursor: number): string => { return Buffer.from(pageCursor.toString(), 'utf-8').toString('base64'); @@ -119,15 +121,18 @@ export type SearchPaginationBaseProps = { * @public */ export const SearchPaginationBase = (props: SearchPaginationBaseProps) => { + const { t } = useTranslationRef(searchReactTranslationRef); const { total: count = -1, cursor: pageCursor, hasNextPage, onCursorChange: onPageCursorChange, limit: rowsPerPage = 25, - limitLabel: labelRowsPerPage = 'Results per page:', + limitLabel: labelRowsPerPage = t('searchPagination.limitLabel'), limitText: labelDisplayedRows = ({ from, to }) => - count > 0 ? `of ${count}` : `${from}-${to}`, + count > 0 + ? t('searchPagination.limitText', { num: `${count}` }) + : `${from}-${to}`, limitOptions: rowsPerPageOptions, onLimitChange: onPageLimitChange, ...rest diff --git a/plugins/search-react/src/components/SearchResult/SearchResult.tsx b/plugins/search-react/src/components/SearchResult/SearchResult.tsx index b0f4570885..99c7f1fb3a 100644 --- a/plugins/search-react/src/components/SearchResult/SearchResult.tsx +++ b/plugins/search-react/src/components/SearchResult/SearchResult.tsx @@ -32,6 +32,8 @@ import { SearchResultListItemExtensions, SearchResultListItemExtensionsProps, } from '../../extensions'; +import { useTranslationRef } from '@backstage/frontend-plugin-api'; +import { searchReactTranslationRef } from '../../translation'; /** * Props for {@link SearchResultContext} @@ -186,11 +188,12 @@ export type SearchResultProps = Pick & * @public */ export const SearchResultComponent = (props: SearchResultProps) => { + const { t } = useTranslationRef(searchReactTranslationRef); const { query, children, noResultsComponent = ( - + ), ...rest } = props; diff --git a/plugins/search-react/src/components/SearchResultGroup/SearchResultGroup.tsx b/plugins/search-react/src/components/SearchResultGroup/SearchResultGroup.tsx index 4c2d300314..9a9290bcbc 100644 --- a/plugins/search-react/src/components/SearchResultGroup/SearchResultGroup.tsx +++ b/plugins/search-react/src/components/SearchResultGroup/SearchResultGroup.tsx @@ -51,6 +51,8 @@ import { useSearchResultListItemExtensions } from '../../extensions'; import { DefaultResultListItem } from '../DefaultResultListItem'; import { SearchResultState, SearchResultStateProps } from '../SearchResult'; +import { searchReactTranslationRef } from '../../translation'; +import { useTranslationRef } from '@backstage/frontend-plugin-api'; const useStyles = makeStyles((theme: Theme) => ({ listSubheader: { @@ -356,6 +358,7 @@ export function SearchResultGroupLayout( ) { const classes = useStyles(); const [anchorEl, setAnchorEl] = useState(null); + const { t } = useTranslationRef(searchReactTranslationRef); const { error, @@ -365,7 +368,7 @@ export function SearchResultGroupLayout( titleProps = {}, link = ( <> - See all + {t('searchResultGroup.linkTitle')} ), @@ -387,7 +390,7 @@ export function SearchResultGroupLayout( ), disableRenderingWithNoResults, noResultsComponent = disableRenderingWithNoResults ? null : ( - + ), ...rest } = props; @@ -434,7 +437,7 @@ export function SearchResultGroupLayout( component="button" icon={} variant="outlined" - label="Add filter" + label={t('searchResultGroup.addFilterButtonTitle')} aria-controls="filters-menu" aria-haspopup="true" onClick={handleClick} diff --git a/plugins/search-react/src/components/SearchResultList/SearchResultList.tsx b/plugins/search-react/src/components/SearchResultList/SearchResultList.tsx index 48ed8ac92f..a5459d8619 100644 --- a/plugins/search-react/src/components/SearchResultList/SearchResultList.tsx +++ b/plugins/search-react/src/components/SearchResultList/SearchResultList.tsx @@ -30,6 +30,8 @@ import { useSearchResultListItemExtensions } from '../../extensions'; import { DefaultResultListItem } from '../DefaultResultListItem'; import { SearchResultState, SearchResultStateProps } from '../SearchResult'; +import { useTranslationRef } from '@backstage/frontend-plugin-api'; +import { searchReactTranslationRef } from '../../translation'; /** * Props for {@link SearchResultListLayout} @@ -72,6 +74,7 @@ export type SearchResultListLayoutProps = ListProps & { * @public */ export const SearchResultListLayout = (props: SearchResultListLayoutProps) => { + const { t } = useTranslationRef(searchReactTranslationRef); const { error, loading, @@ -84,7 +87,7 @@ export const SearchResultListLayout = (props: SearchResultListLayoutProps) => { ), disableRenderingWithNoResults, noResultsComponent = disableRenderingWithNoResults ? null : ( - + ), ...rest } = props; diff --git a/plugins/search-react/src/components/SearchResultPager/SearchResultPager.tsx b/plugins/search-react/src/components/SearchResultPager/SearchResultPager.tsx index 764bb28129..d1dca8bb95 100644 --- a/plugins/search-react/src/components/SearchResultPager/SearchResultPager.tsx +++ b/plugins/search-react/src/components/SearchResultPager/SearchResultPager.tsx @@ -20,6 +20,8 @@ import ArrowBackIosIcon from '@material-ui/icons/ArrowBackIos'; import ArrowForwardIosIcon from '@material-ui/icons/ArrowForwardIos'; import { useSearch } from '../../context'; +import { useTranslationRef } from '@backstage/frontend-plugin-api'; +import { searchReactTranslationRef } from '../../translation'; const useStyles = makeStyles(theme => ({ root: { @@ -36,6 +38,7 @@ const useStyles = makeStyles(theme => ({ export const SearchResultPager = () => { const { fetchNextPage, fetchPreviousPage } = useSearch(); const classes = useStyles(); + const { t } = useTranslationRef(searchReactTranslationRef); if (!fetchNextPage && !fetchPreviousPage) { return <>; @@ -49,7 +52,7 @@ export const SearchResultPager = () => { onClick={fetchPreviousPage} startIcon={} > - Previous + {t('searchResultPager.previous')} ); diff --git a/plugins/search-react/src/translation.ts b/plugins/search-react/src/translation.ts new file mode 100644 index 0000000000..be73ac981e --- /dev/null +++ b/plugins/search-react/src/translation.ts @@ -0,0 +1,43 @@ +/* + * Copyright 2025 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 { createTranslationRef } from '@backstage/core-plugin-api/alpha'; + +export const searchReactTranslationRef = createTranslationRef({ + id: 'search-react', + messages: { + searchBar: { + title: 'Search', + placeholder: 'Search in {{org}}', + }, + searchFilter: { + allOptionTitle: 'All', + }, + searchPagination: { + limitLabel: 'Results per page:', + limitText: 'of {{num}}', + }, + noResultsDescription: 'Sorry, no results were found', + searchResultGroup: { + linkTitle: 'See All', + addFilterButtonTitle: 'Add filter', + }, + searchResultPager: { + previous: 'Previous', + next: 'Next', + }, + }, +}); diff --git a/plugins/search/src/alpha.tsx b/plugins/search/src/alpha.tsx index fed0124fa7..5f55b46028 100644 --- a/plugins/search/src/alpha.tsx +++ b/plugins/search/src/alpha.tsx @@ -284,3 +284,6 @@ export default createFrontendPlugin({ root: rootRouteRef, }), }); + +/** @alpha */ +export { searchTranslationRef } from './translation'; diff --git a/plugins/search/src/components/SearchModal/SearchModal.tsx b/plugins/search/src/components/SearchModal/SearchModal.tsx index 454d2eae96..af7e1c6e10 100644 --- a/plugins/search/src/components/SearchModal/SearchModal.tsx +++ b/plugins/search/src/components/SearchModal/SearchModal.tsx @@ -39,6 +39,8 @@ import { useNavigate } from 'react-router-dom'; import { rootRouteRef } from '../../plugin'; import { SearchResultSet } from '@backstage/plugin-search-common'; +import { useTranslationRef } from '@backstage/frontend-plugin-api'; +import { searchTranslationRef } from '../../translation'; /** * @public @@ -121,6 +123,7 @@ export const Modal = ({ const navigate = useNavigate(); const { transitions } = useTheme(); const { focusContent } = useContent(); + const { t } = useTranslationRef(searchTranslationRef); const searchRootRoute = useRouteRef(rootRouteRef)(); const searchBarRef = useRef(null); @@ -171,7 +174,7 @@ export const Modal = ({ onClick={handleSearchBarSubmit} disableRipple > - View Full Results + {t('searchModal.viewFullResults')} diff --git a/plugins/search/src/components/SearchType/SearchType.Accordion.tsx b/plugins/search/src/components/SearchType/SearchType.Accordion.tsx index 7726ee4565..b709f3b12c 100644 --- a/plugins/search/src/components/SearchType/SearchType.Accordion.tsx +++ b/plugins/search/src/components/SearchType/SearchType.Accordion.tsx @@ -31,6 +31,8 @@ import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; import Typography from '@material-ui/core/Typography'; import AllIcon from '@material-ui/icons/FontDownload'; import useAsync from 'react-use/esm/useAsync'; +import { useTranslationRef } from '@backstage/frontend-plugin-api'; +import { searchTranslationRef } from '../../translation'; const useStyles = makeStyles(theme => ({ icon: { @@ -83,6 +85,7 @@ export const SearchTypeAccordion = (props: SearchTypeAccordionProps) => { const searchApi = useApi(searchApiRef); const [expanded, setExpanded] = useState(true); const { defaultValue, name, showCounts, types: givenTypes } = props; + const { t } = useTranslationRef(searchTranslationRef); const toggleExpanded = () => setExpanded(prevState => !prevState); const handleClick = (type: string) => { @@ -103,7 +106,7 @@ export const SearchTypeAccordion = (props: SearchTypeAccordionProps) => { const definedTypes = [ { value: '', - name: 'All', + name: t('searchType.accordion.allTitle'), icon: , }, ...givenTypes, @@ -117,7 +120,7 @@ export const SearchTypeAccordion = (props: SearchTypeAccordionProps) => { const counts = await Promise.all( definedTypes - .map(t => t.value) + .map(_t => _t.value) .map(async type => { const { numberOfResults } = await searchApi.query({ term, @@ -130,9 +133,10 @@ export const SearchTypeAccordion = (props: SearchTypeAccordionProps) => { return [ type, numberOfResults !== undefined - ? `${ - numberOfResults >= 10000 ? `>10000` : numberOfResults - } results` + ? t('searchType.accordion.numberOfResults', { + number: + numberOfResults >= 10000 ? `>10000` : `${numberOfResults}`, + }) : ' -- ', ]; }), @@ -160,8 +164,8 @@ export const SearchTypeAccordion = (props: SearchTypeAccordionProps) => { IconButtonProps={{ size: 'small' }} > {expanded - ? 'Collapse' - : definedTypes.filter(t => t.value === selected)[0]!.name} + ? t('searchType.accordion.collapse') + : definedTypes.filter(_t => _t.value === selected)[0]!.name} ({ tabs: { @@ -49,6 +51,7 @@ export const SearchTypeTabs = (props: SearchTypeTabsProps) => { const classes = useStyles(); const { setPageCursor, setTypes, types } = useSearch(); const { defaultValue, types: givenTypes } = props; + const { t } = useTranslationRef(searchTranslationRef); const changeTab = (_: ChangeEvent<{}>, newType: string) => { setTypes(newType !== '' ? [newType] : []); @@ -66,7 +69,7 @@ export const SearchTypeTabs = (props: SearchTypeTabsProps) => { const definedTypes = [ { value: '', - name: 'All', + name: t('searchType.tabs.allTitle'), }, ...givenTypes, ]; diff --git a/plugins/search/src/components/SearchType/SearchType.tsx b/plugins/search/src/components/SearchType/SearchType.tsx index 48b78f00d3..b40aa431a1 100644 --- a/plugins/search/src/components/SearchType/SearchType.tsx +++ b/plugins/search/src/components/SearchType/SearchType.tsx @@ -29,6 +29,8 @@ import { } from './SearchType.Accordion'; import { SearchTypeTabs, SearchTypeTabsProps } from './SearchType.Tabs'; import { useSearch } from '@backstage/plugin-search-react'; +import { useTranslationRef } from '@backstage/frontend-plugin-api'; +import { searchTranslationRef } from '../../translation'; const useStyles = makeStyles(theme => ({ label: { @@ -63,6 +65,7 @@ const SearchType = (props: SearchTypeProps) => { const { className, defaultValue, name, values = [] } = props; const classes = useStyles(); const { types, setTypes } = useSearch(); + const { t } = useTranslationRef(searchTranslationRef); useEffectOnce(() => { if (!types.length) { @@ -94,7 +97,7 @@ const SearchType = (props: SearchTypeProps) => { variant="outlined" value={types} onChange={handleChange} - placeholder="All Results" + placeholder={t('searchType.allResults')} renderValue={selected => (
{(selected as string[]).map(value => ( diff --git a/plugins/search/src/components/SidebarSearchModal/SidebarSearchModal.tsx b/plugins/search/src/components/SidebarSearchModal/SidebarSearchModal.tsx index ad687a35db..5dde4f385b 100644 --- a/plugins/search/src/components/SidebarSearchModal/SidebarSearchModal.tsx +++ b/plugins/search/src/components/SidebarSearchModal/SidebarSearchModal.tsx @@ -22,6 +22,8 @@ import { SearchModalProvider, useSearchModal, } from '../SearchModal'; +import { useTranslationRef } from '@backstage/frontend-plugin-api'; +import { searchTranslationRef } from '../../translation'; /** * Props for {@link SidebarSearchModal}. @@ -38,13 +40,14 @@ export type SidebarSearchModalProps = Pick< const SidebarSearchModalContent = (props: SidebarSearchModalProps) => { const { state, toggleModal } = useSearchModal(); const Icon = props.icon ? props.icon : SearchIcon; + const { t } = useTranslationRef(searchTranslationRef); return ( <> Date: Thu, 27 Mar 2025 14:24:01 +0800 Subject: [PATCH 084/109] fix report error Signed-off-by: mario ma --- plugins/search-react/report-alpha.api.md | 4 +--- plugins/search-react/src/translation.ts | 3 +++ plugins/search/report-alpha.api.md | 15 +++++++++++++++ plugins/search/src/translation.ts | 3 +++ 4 files changed, 22 insertions(+), 3 deletions(-) diff --git a/plugins/search-react/report-alpha.api.md b/plugins/search-react/report-alpha.api.md index afe08b0f64..d3763bfcbf 100644 --- a/plugins/search-react/report-alpha.api.md +++ b/plugins/search-react/report-alpha.api.md @@ -95,9 +95,7 @@ export interface SearchFilterResultTypeBlueprintParams { value: string; } -// Warning: (ae-missing-release-tag) "searchReactTranslationRef" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @alpha (undocumented) export const searchReactTranslationRef: TranslationRef< 'search-react', { diff --git a/plugins/search-react/src/translation.ts b/plugins/search-react/src/translation.ts index be73ac981e..6897911949 100644 --- a/plugins/search-react/src/translation.ts +++ b/plugins/search-react/src/translation.ts @@ -16,6 +16,9 @@ import { createTranslationRef } from '@backstage/core-plugin-api/alpha'; +/** + * @alpha + */ export const searchReactTranslationRef = createTranslationRef({ id: 'search-react', messages: { diff --git a/plugins/search/report-alpha.api.md b/plugins/search/report-alpha.api.md index b8df4d3b4c..3af0384d89 100644 --- a/plugins/search/report-alpha.api.md +++ b/plugins/search/report-alpha.api.md @@ -15,6 +15,7 @@ import { RouteRef } from '@backstage/frontend-plugin-api'; import { SearchFilterExtensionComponent } from '@backstage/plugin-search-react/alpha'; import { SearchResultItemExtensionComponent } from '@backstage/plugin-search-react/alpha'; import { SearchResultItemExtensionPredicate } from '@backstage/plugin-search-react/alpha'; +import { TranslationRef } from '@backstage/core-plugin-api/alpha'; // @alpha (undocumented) const _default: FrontendPlugin< @@ -247,5 +248,19 @@ export const searchPage: ExtensionDefinition<{ }; }>; +// @alpha (undocumented) +export const searchTranslationRef: TranslationRef< + 'search', + { + readonly 'searchModal.viewFullResults': 'View Full Results'; + readonly 'searchType.tabs.allTitle': 'All'; + readonly 'searchType.allResults': 'All Results'; + readonly 'searchType.accordion.collapse': 'Collapse'; + readonly 'searchType.accordion.allTitle': 'All'; + readonly 'searchType.accordion.numberOfResults': '{{number}} results'; + readonly 'sidebarSearchModal.title': 'Search'; + } +>; + // (No @packageDocumentation comment for this package) ``` diff --git a/plugins/search/src/translation.ts b/plugins/search/src/translation.ts index b88482e830..37f86b9218 100644 --- a/plugins/search/src/translation.ts +++ b/plugins/search/src/translation.ts @@ -16,6 +16,9 @@ import { createTranslationRef } from '@backstage/core-plugin-api/alpha'; +/** + * @alpha + */ export const searchTranslationRef = createTranslationRef({ id: 'search', messages: { From d827c84fbae32fd6fbfa3b767a9ebd093ed9cd2c Mon Sep 17 00:00:00 2001 From: mario ma Date: Fri, 28 Mar 2025 15:27:19 +0800 Subject: [PATCH 085/109] fix unit test Signed-off-by: mario ma --- .../SearchFilter/SearchFilter.test.tsx | 34 +++++++++++-------- .../SearchPagination.test.tsx | 20 +++++------ .../SearchType/SearchType.Accordion.test.tsx | 18 ++++++---- .../SearchType/SearchType.Tabs.test.tsx | 16 +++++---- .../components/SearchType/SearchType.test.tsx | 18 ++++++---- 5 files changed, 62 insertions(+), 44 deletions(-) diff --git a/plugins/search-react/src/components/SearchFilter/SearchFilter.test.tsx b/plugins/search-react/src/components/SearchFilter/SearchFilter.test.tsx index 736e3f3f93..d8d3b75aa2 100644 --- a/plugins/search-react/src/components/SearchFilter/SearchFilter.test.tsx +++ b/plugins/search-react/src/components/SearchFilter/SearchFilter.test.tsx @@ -14,14 +14,18 @@ * limitations under the License. */ -import { screen, render, waitFor } from '@testing-library/react'; +import { screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { configApiRef } from '@backstage/core-plugin-api'; import { SearchContextProvider } from '../../context'; import { SearchFilter } from './SearchFilter'; -import { mockApis, TestApiProvider } from '@backstage/test-utils'; +import { + mockApis, + renderInTestApp, + TestApiProvider, +} from '@backstage/test-utils'; import { searchApiRef } from '../../api'; describe('SearchFilter', () => { @@ -55,14 +59,16 @@ describe('SearchFilter', () => { it('Check that element was rendered and received props', async () => { const CustomFilter = (props: { name: string }) =>
{props.name}
; - render(); + await renderInTestApp( + , + ); expect(screen.getByRole('heading', { name })).toBeInTheDocument(); }); describe('Checkbox', () => { it('Renders field name and values when provided as props', async () => { - render( + await renderInTestApp( { }); it('Renders correctly based on filter state', async () => { - render( + await renderInTestApp( { }); it('Renders correctly based on defaultValue', async () => { - render( + await renderInTestApp( { }); it('Checking / unchecking a value sets filter state', async () => { - render( + await renderInTestApp( { }); it('Checking / unchecking a value maintains unrelated filter state', async () => { - render( + await renderInTestApp( { describe('Select', () => { it('Renders field name and values when provided as props', async () => { - render( + await renderInTestApp( { }); it('Renders values when provided asynchronously', async () => { - render( + await renderInTestApp( { }); it('Renders correctly based on filter state', async () => { - render( + await renderInTestApp( { }); it('Renders correctly based on defaultValue', async () => { - render( + await renderInTestApp( { }); it('Selecting a value sets filter state', async () => { - render( + await renderInTestApp( { }); it('Selecting a value maintains unrelated filter state', async () => { - render( + await renderInTestApp( { }); it('Renders without exploding', async () => { - await renderWithEffects( + await renderInTestApp( { }); it('Define default page limit options', async () => { - await renderWithEffects( + await renderInTestApp( { it('Accept custom page limit label', async () => { const label = 'Page limit:'; - await renderWithEffects( + await renderInTestApp( { }); it('Show the total in text', async () => { - await renderWithEffects( + await renderInTestApp( { }); it('Accept custom page limit text', async () => { - await renderWithEffects( + await renderInTestApp( { }); it('Accept custom page limit options', async () => { - await renderWithEffects( + await renderInTestApp( { }); it('Set page limit in the context', async () => { - await renderWithEffects( + await renderInTestApp( { pageCursor: 'MQ==', // page: 1 }; - await renderWithEffects( + await renderInTestApp( { pageCursor: 'Mg==', // page: 2 }; - await renderWithEffects( + await renderInTestApp( { }; it('should render as expected', async () => { - const { getByText } = render( + const { getByText } = await renderInTestApp( , @@ -103,7 +107,7 @@ describe('SearchType.Accordion', () => { }); it('should set entire types array when a type is selected', async () => { - const { getByText } = render( + const { getByText } = await renderInTestApp( , @@ -115,7 +119,7 @@ describe('SearchType.Accordion', () => { }); it('should reset types array when all is selected', async () => { - const { getByText } = render( + const { getByText } = await renderInTestApp( { }); it('should reset page cursor when a new type is selected', async () => { - const { getByText } = render( + const { getByText } = await renderInTestApp( , @@ -143,7 +147,7 @@ describe('SearchType.Accordion', () => { }); it('should show result counts if enabled', async () => { - const { getAllByText } = render( + const { getAllByText } = await renderInTestApp( { }; it('should render as expected', async () => { - const { getByText } = render( + const { getByText } = await renderInTestApp( , @@ -85,7 +89,7 @@ describe('SearchType.Tabs', () => { }); it('should set entire types array when a type is selected', async () => { - const { getByText } = render( + const { getByText } = await renderInTestApp( , @@ -97,7 +101,7 @@ describe('SearchType.Tabs', () => { }); it('should reset types array when all is selected', async () => { - const { getByText } = render( + const { getByText } = await renderInTestApp( { }); it('should reset page cursor when a new type is selected', async () => { - const { getByText } = render( + const { getByText } = await renderInTestApp( , diff --git a/plugins/search/src/components/SearchType/SearchType.test.tsx b/plugins/search/src/components/SearchType/SearchType.test.tsx index fd014369f7..231463c9bf 100644 --- a/plugins/search/src/components/SearchType/SearchType.test.tsx +++ b/plugins/search/src/components/SearchType/SearchType.test.tsx @@ -15,14 +15,18 @@ */ import { configApiRef } from '@backstage/core-plugin-api'; -import { render, screen, waitFor } from '@testing-library/react'; +import { screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { SearchContextProvider, searchApiRef, } from '@backstage/plugin-search-react'; import { SearchType } from './SearchType'; -import { mockApis, TestApiProvider } from '@backstage/test-utils'; +import { + mockApis, + renderInTestApp, + TestApiProvider, +} from '@backstage/test-utils'; describe('SearchType', () => { const initialState = { @@ -53,7 +57,7 @@ describe('SearchType', () => { describe('Type Filter', () => { it('Renders field name and values when provided as props', async () => { - render( + await renderInTestApp( { }); it('Renders correctly based on type filter state', async () => { - render( + await renderInTestApp( { }); it('Renders correctly based on type filter defaultValue', async () => { - render( + await renderInTestApp( { }); it('Selecting a value sets type filter state', async () => { - render( + await renderInTestApp( { }); it('Selecting none defaults to empty state', async () => { - render( + await renderInTestApp( Date: Tue, 6 May 2025 14:58:58 +0800 Subject: [PATCH 086/109] fix: review Signed-off-by: mario ma --- .../search/src/components/SearchType/SearchType.Accordion.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/search/src/components/SearchType/SearchType.Accordion.tsx b/plugins/search/src/components/SearchType/SearchType.Accordion.tsx index b709f3b12c..65c96b1e60 100644 --- a/plugins/search/src/components/SearchType/SearchType.Accordion.tsx +++ b/plugins/search/src/components/SearchType/SearchType.Accordion.tsx @@ -120,7 +120,7 @@ export const SearchTypeAccordion = (props: SearchTypeAccordionProps) => { const counts = await Promise.all( definedTypes - .map(_t => _t.value) + .map(type => type.value) .map(async type => { const { numberOfResults } = await searchApi.query({ term, @@ -165,7 +165,7 @@ export const SearchTypeAccordion = (props: SearchTypeAccordionProps) => { > {expanded ? t('searchType.accordion.collapse') - : definedTypes.filter(_t => _t.value === selected)[0]!.name} + : definedTypes.filter(type => type.value === selected)[0]!.name} Date: Wed, 11 Dec 2024 18:07:38 +0800 Subject: [PATCH 087/109] feat: add i18n support for catalog-import plugin Signed-off-by: JounQin --- .changeset/neat-glasses-occur.md | 5 + plugins/catalog-import/report-alpha.api.md | 63 +++++++- plugins/catalog-import/report.api.md | 3 + plugins/catalog-import/src/alpha.tsx | 2 + .../src/api/AzureRepoApiClient.test.ts | 4 +- .../src/components/Buttons/index.tsx | 6 +- .../DefaultImportPage/DefaultImportPage.tsx | 19 ++- .../ImportInfoCard/ImportInfoCard.tsx | 27 ++-- .../ImportStepper/ImportStepper.tsx | 16 +- .../src/components/ImportStepper/defaults.tsx | 116 ++++++++++---- .../StepFinishImportLocation.tsx | 34 ++-- .../StepInitAnalyzeUrl.test.tsx | 100 +++++++----- .../StepInitAnalyzeUrl/StepInitAnalyzeUrl.tsx | 22 +-- .../PreviewPullRequestComponent.test.tsx | 19 ++- .../PreviewPullRequestComponent.tsx | 11 +- .../StepPrepareCreatePullRequest.test.tsx | 148 +++++++++--------- .../StepPrepareCreatePullRequest.tsx | 14 +- .../StepPrepareSelectLocations.tsx | 20 ++- .../StepReviewLocation/StepReviewLocation.tsx | 26 +-- plugins/catalog-import/src/translation.ts | 125 ++++++++++++++- plugins/scaffolder-react/report-alpha.api.md | 2 +- 21 files changed, 552 insertions(+), 230 deletions(-) create mode 100644 .changeset/neat-glasses-occur.md diff --git a/.changeset/neat-glasses-occur.md b/.changeset/neat-glasses-occur.md new file mode 100644 index 0000000000..4e5e62b7ca --- /dev/null +++ b/.changeset/neat-glasses-occur.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-import': patch +--- + +Add i18n support for catalog-import plugin diff --git a/plugins/catalog-import/report-alpha.api.md b/plugins/catalog-import/report-alpha.api.md index 24789a42e4..d4dab4549a 100644 --- a/plugins/catalog-import/report-alpha.api.md +++ b/plugins/catalog-import/report-alpha.api.md @@ -16,8 +16,69 @@ import { TranslationRef } from '@backstage/core-plugin-api/alpha'; export const catalogImportTranslationRef: TranslationRef< 'catalog-import', { - readonly pageTitle: 'Register an existing component'; + readonly 'buttons.back': 'Back'; + readonly 'defaultImportPage.headerTitle': 'Register an existing component'; + readonly 'defaultImportPage.contentHeaderTitle': 'Start tracking your component in {{appTitle}}'; + readonly 'defaultImportPage.supportTitle': 'Start tracking your component in {{appTitle}} by adding it to the software catalog.'; readonly 'importInfoCard.title': 'Register an existing component'; + readonly 'importInfoCard.deepLinkTitle': 'Learn more about the Software Catalog'; + readonly 'importInfoCard.linkDescription': 'Enter the URL to your source code repository to add it to {{appTitle}}.'; + readonly 'importInfoCard.fileLinkTitle': 'Link to an existing entity file'; + readonly 'importInfoCard.examplePrefix': 'Example: '; + readonly 'importInfoCard.fileLinkDescription': 'The wizard analyzes the file, previews the entities, and adds them to the {{appTitle}} catalog.'; + readonly 'importInfoCard.githubIntegration.label': 'GitHub only'; + readonly 'importInfoCard.githubIntegration.title': 'Link to a repository'; + readonly 'importStepper.finish.title': 'Finish'; + readonly 'importStepper.noLocation.title': 'Create Pull Request'; + readonly 'importStepper.noLocation.createPr.ownerLabel': 'Entity Owner'; + readonly 'importStepper.noLocation.createPr.detailsTitle': 'Pull Request Details'; + readonly 'importStepper.noLocation.createPr.titleLabel': 'Pull Request Title'; + readonly 'importStepper.noLocation.createPr.titlePlaceholder': 'Add Backstage catalog entity descriptor files'; + readonly 'importStepper.noLocation.createPr.bodyLabel': 'Pull Request Body'; + readonly 'importStepper.noLocation.createPr.bodyPlaceholder': 'A describing text with Markdown support'; + readonly 'importStepper.noLocation.createPr.configurationTitle': 'Entity Configuration'; + readonly 'importStepper.noLocation.createPr.componentNameLabel': 'Name of the created component'; + readonly 'importStepper.noLocation.createPr.componentNamePlaceholder': 'my-component'; + readonly 'importStepper.noLocation.createPr.ownerLoadingText': 'Loading groups…'; + readonly 'importStepper.noLocation.createPr.ownerHelperText': 'Select an owner from the list or enter a reference to a Group or a User'; + readonly 'importStepper.noLocation.createPr.ownerErrorHelperText': 'required value'; + readonly 'importStepper.noLocation.createPr.ownerPlaceholder': 'my-group'; + readonly 'importStepper.noLocation.createPr.codeownersHelperText': 'WARNING: This may fail if no CODEOWNERS file is found at the target location.'; + readonly 'importStepper.singleLocation.title': 'Select Locations'; + readonly 'importStepper.singleLocation.description': 'Discovered Locations: 1'; + readonly 'importStepper.multipleLocations.title': 'Select Locations'; + readonly 'importStepper.multipleLocations.description': 'Discovered Locations: {{length, number}}'; + readonly 'importStepper.analyze.title': 'Select URL'; + readonly 'importStepper.prepare.title': 'Import Actions'; + readonly 'importStepper.prepare.description': 'Optional'; + readonly 'importStepper.review.title': 'Review'; + readonly 'stepFinishImportLocation.repository.title': 'The following Pull Request has been opened: '; + readonly 'stepFinishImportLocation.repository.description': 'Your entities will be imported as soon as the Pull Request is merged.'; + readonly 'stepFinishImportLocation.backButtonText': 'Register another'; + readonly 'stepFinishImportLocation.locations.new': 'The following entities have been added to the catalog:'; + readonly 'stepFinishImportLocation.locations.backButtonText': 'Register another'; + readonly 'stepFinishImportLocation.locations.existing': 'A refresh was triggered for the following locations:'; + readonly 'stepFinishImportLocation.locations.viewButtonText': 'View Component'; + readonly 'stepInitAnalyzeUrl.error.default': 'Received unknown analysis result of type {{type}}. Please contact the support team.'; + readonly 'stepInitAnalyzeUrl.error.url': 'Must start with http:// or https://.'; + readonly 'stepInitAnalyzeUrl.error.repository': "Couldn't generate entities for your repository"; + readonly 'stepInitAnalyzeUrl.error.locations': 'There are no entities at this location'; + readonly 'stepInitAnalyzeUrl.urlHelperText': 'Enter the full path to your entity file to start tracking your component'; + readonly 'stepInitAnalyzeUrl.nextButtonText': 'Analyze'; + readonly 'stepPrepareCreatePullRequest.nextButtonText': 'Create PR'; + readonly 'stepPrepareCreatePullRequest.previewPr.title': 'Preview Pull Request'; + readonly 'stepPrepareCreatePullRequest.previewPr.subheader': 'Create a new Pull Request'; + readonly 'stepPrepareCreatePullRequest.previewCatalogInfo.title': 'Preview Entities'; + readonly 'stepPrepareSelectLocations.locations.description': 'Select one or more locations that are present in your git repository:'; + readonly 'stepPrepareSelectLocations.locations.selectAll': 'Select All'; + readonly 'stepPrepareSelectLocations.nextButtonText': 'Review'; + readonly 'stepPrepareSelectLocations.existingLocations.description': 'These locations already exist in the catalog:'; + readonly 'stepReviewLocation.refresh': 'Refresh'; + readonly 'stepReviewLocation.import': 'Import'; + readonly 'stepReviewLocation.catalog.new': 'The following entities will be added to the catalog:'; + readonly 'stepReviewLocation.catalog.exists': 'The following locations already exist in the catalog:'; + readonly 'stepReviewLocation.prepareResult.title': 'The following Pull Request has been opened: '; + readonly 'stepReviewLocation.prepareResult.description': 'You can already import the location and {{appTitle}} will fetch the entities as soon as the Pull Request is merged.'; } >; diff --git a/plugins/catalog-import/report.api.md b/plugins/catalog-import/report.api.md index 9bc71a4296..eed2639168 100644 --- a/plugins/catalog-import/report.api.md +++ b/plugins/catalog-import/report.api.md @@ -6,6 +6,7 @@ import { ApiRef } from '@backstage/core-plugin-api'; import { BackstagePlugin } from '@backstage/core-plugin-api'; import { CatalogApi } from '@backstage/catalog-client'; +import { catalogImportTranslationRef } from '@backstage/plugin-catalog-import/alpha'; import { ComponentProps } from 'react'; import { CompoundEntityRef } from '@backstage/catalog-model'; import { ConfigApi } from '@backstage/core-plugin-api'; @@ -24,6 +25,7 @@ import { ScmAuthApi } from '@backstage/integration-react'; import { ScmIntegrationRegistry } from '@backstage/integration'; import { SubmitHandler } from 'react-hook-form'; import { TextFieldProps } from '@material-ui/core/TextField/TextField'; +import { TranslationFunction } from '@backstage/core-plugin-api/alpha'; import { UseFormProps } from 'react-hook-form'; import { UseFormReturn } from 'react-hook-form'; @@ -145,6 +147,7 @@ export { catalogImportPlugin as plugin }; export function defaultGenerateStepper( flow: ImportFlows, defaults: StepperProvider, + t: TranslationFunction, ): StepperProvider; // @public diff --git a/plugins/catalog-import/src/alpha.tsx b/plugins/catalog-import/src/alpha.tsx index 0d1599ee5c..3fade2d92b 100644 --- a/plugins/catalog-import/src/alpha.tsx +++ b/plugins/catalog-import/src/alpha.tsx @@ -92,3 +92,5 @@ export default createFrontendPlugin({ importPage: convertLegacyRouteRef(rootRouteRef), }, }); + +export { catalogImportTranslationRef } from './translation'; diff --git a/plugins/catalog-import/src/api/AzureRepoApiClient.test.ts b/plugins/catalog-import/src/api/AzureRepoApiClient.test.ts index 9f058dc744..00e77461b6 100644 --- a/plugins/catalog-import/src/api/AzureRepoApiClient.test.ts +++ b/plugins/catalog-import/src/api/AzureRepoApiClient.test.ts @@ -154,7 +154,7 @@ function mockPrEndpoint() { describe('RepoApiClient', () => { const server = setupServer(); registerMswTestHooks(server); - const testToken = new Date().toString(); + const testToken = new Date().toLocaleString('en-US'); const sut = new RepoApiClient({ project: 'project', tenantUrl: 'https://dev.azure.com/acme', @@ -316,7 +316,7 @@ describe('createAzurePullRequest', () => { }); it('should create a new Pull request', async () => { - const testToken = new Date().getTime().toString(); + const testToken = new Date().toLocaleString('en-US'); const options: AzurePrOptions = { tenantUrl: 'https://dev.azure.com/acme', repository: 'test', diff --git a/plugins/catalog-import/src/components/Buttons/index.tsx b/plugins/catalog-import/src/components/Buttons/index.tsx index 6ba5a83fb5..f709f65a75 100644 --- a/plugins/catalog-import/src/components/Buttons/index.tsx +++ b/plugins/catalog-import/src/components/Buttons/index.tsx @@ -15,11 +15,14 @@ */ import { LinkButton } from '@backstage/core-components'; +import { useTranslationRef } from '@backstage/frontend-plugin-api'; import Button from '@material-ui/core/Button'; import CircularProgress from '@material-ui/core/CircularProgress'; import { makeStyles } from '@material-ui/core/styles'; import { ComponentProps } from 'react'; +import { catalogImportTranslationRef } from '../../translation'; + const useStyles = makeStyles(theme => ({ wrapper: { marginTop: theme.spacing(1), @@ -62,11 +65,12 @@ export const NextButton = ( }; export const BackButton = (props: ComponentProps) => { + const { t } = useTranslationRef(catalogImportTranslationRef); const classes = useStyles(); return ( ); }; diff --git a/plugins/catalog-import/src/components/DefaultImportPage/DefaultImportPage.tsx b/plugins/catalog-import/src/components/DefaultImportPage/DefaultImportPage.tsx index 18ffe2bd8d..24a79866b3 100644 --- a/plugins/catalog-import/src/components/DefaultImportPage/DefaultImportPage.tsx +++ b/plugins/catalog-import/src/components/DefaultImportPage/DefaultImportPage.tsx @@ -22,13 +22,14 @@ import { SupportButton, } from '@backstage/core-components'; import { configApiRef, useApi } from '@backstage/core-plugin-api'; +import { useTranslationRef } from '@backstage/frontend-plugin-api'; import Grid from '@material-ui/core/Grid'; -import useMediaQuery from '@material-ui/core/useMediaQuery'; import { useTheme } from '@material-ui/core/styles'; +import useMediaQuery from '@material-ui/core/useMediaQuery'; + +import { catalogImportTranslationRef } from '../../translation'; import { ImportInfoCard } from '../ImportInfoCard'; import { ImportStepper } from '../ImportStepper'; -import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; -import { catalogImportTranslationRef } from '../../translation'; /** * The default catalog import page. @@ -42,8 +43,6 @@ export const DefaultImportPage = () => { const isMobile = useMediaQuery(theme.breakpoints.down('sm')); const appTitle = configApi.getOptionalString('app.title') || 'Backstage'; - const supportTitle = `Start tracking your component in ${appTitle} by adding it to the software catalog.`; - const contentItems = [ @@ -56,10 +55,14 @@ export const DefaultImportPage = () => { return ( -
+
- - {supportTitle} + + + {t('defaultImportPage.supportTitle', { appTitle })} + diff --git a/plugins/catalog-import/src/components/ImportInfoCard/ImportInfoCard.tsx b/plugins/catalog-import/src/components/ImportInfoCard/ImportInfoCard.tsx index fc15d01538..1777c0786b 100644 --- a/plugins/catalog-import/src/components/ImportInfoCard/ImportInfoCard.tsx +++ b/plugins/catalog-import/src/components/ImportInfoCard/ImportInfoCard.tsx @@ -16,11 +16,12 @@ import { InfoCard } from '@backstage/core-components'; import { configApiRef, useApi } from '@backstage/core-plugin-api'; +import { useTranslationRef } from '@backstage/frontend-plugin-api'; import Chip from '@material-ui/core/Chip'; import Typography from '@material-ui/core/Typography'; + import { catalogImportApiRef } from '../../api'; import { useCatalogFilename } from '../../hooks'; -import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; import { catalogImportTranslationRef } from '../../translation'; /** @@ -58,31 +59,37 @@ export const ImportInfoCard = (props: ImportInfoCardProps) => { title={t('importInfoCard.title')} titleTypographyProps={{ component: 'h3' }} deepLink={{ - title: 'Learn more about the Software Catalog', + title: t('importInfoCard.deepLinkTitle'), link: 'https://backstage.io/docs/features/software-catalog/', }} > - Enter the URL to your source code repository to add it to {appTitle}. + {t('importInfoCard.linkDescription', { appTitle })} - Link to an existing entity file + {t('importInfoCard.fileLinkTitle')} - Example: {exampleLocationUrl} + {t('importInfoCard.examplePrefix')} + {exampleLocationUrl} - The wizard analyzes the file, previews the entities, and adds them to - the {appTitle} catalog. + {t('importInfoCard.fileLinkDescription', { appTitle })} {hasGithubIntegration && ( <> - Link to a repository{' '} - + {t('importInfoCard.githubIntegration.title')} + - Example: {exampleRepositoryUrl} + {t('importInfoCard.examplePrefix')} + {exampleRepositoryUrl} The wizard discovers all {catalogFilename} files in the diff --git a/plugins/catalog-import/src/components/ImportStepper/ImportStepper.tsx b/plugins/catalog-import/src/components/ImportStepper/ImportStepper.tsx index 30d02032b6..ed399a8a96 100644 --- a/plugins/catalog-import/src/components/ImportStepper/ImportStepper.tsx +++ b/plugins/catalog-import/src/components/ImportStepper/ImportStepper.tsx @@ -16,12 +16,15 @@ import { InfoCard, InfoCardVariants } from '@backstage/core-components'; import { useApi } from '@backstage/core-plugin-api'; +import { useTranslationRef } from '@backstage/frontend-plugin-api'; import Step from '@material-ui/core/Step'; import StepContent from '@material-ui/core/StepContent'; import Stepper from '@material-ui/core/Stepper'; import { makeStyles } from '@material-ui/core/styles'; import { useMemo } from 'react'; + import { catalogImportApiRef } from '../../api'; +import { catalogImportTranslationRef } from '../../translation'; import { ImportFlows, ImportState, useImportState } from '../useImportState'; import { defaultGenerateStepper, @@ -56,6 +59,7 @@ export interface ImportStepperProps { * @public */ export const ImportStepper = (props: ImportStepperProps) => { + const { t } = useTranslationRef(catalogImportTranslationRef); const { initialUrl, generateStepper = defaultGenerateStepper, @@ -67,8 +71,8 @@ export const ImportStepper = (props: ImportStepperProps) => { const state = useImportState({ initialUrl }); const states = useMemo( - () => generateStepper(state.activeFlow, defaultStepper), - [generateStepper, state.activeFlow], + () => generateStepper(state.activeFlow, defaultStepper, t), + [generateStepper, state.activeFlow, t], ); const render = (step: StepConfiguration) => { @@ -90,25 +94,25 @@ export const ImportStepper = (props: ImportStepperProps) => { {render( states.analyze( state as Extract, - { apis: { catalogImportApi } }, + { apis: { catalogImportApi }, t }, ), )} {render( states.prepare( state as Extract, - { apis: { catalogImportApi } }, + { apis: { catalogImportApi }, t }, ), )} {render( states.review( state as Extract, - { apis: { catalogImportApi } }, + { apis: { catalogImportApi }, t }, ), )} {render( states.finish( state as Extract, - { apis: { catalogImportApi } }, + { apis: { catalogImportApi }, t }, ), )} diff --git a/plugins/catalog-import/src/components/ImportStepper/defaults.tsx b/plugins/catalog-import/src/components/ImportStepper/defaults.tsx index d23ffb4db9..2b8f7e107b 100644 --- a/plugins/catalog-import/src/components/ImportStepper/defaults.tsx +++ b/plugins/catalog-import/src/components/ImportStepper/defaults.tsx @@ -14,6 +14,8 @@ * limitations under the License. */ +import { TranslationFunction } from '@backstage/core-plugin-api/alpha'; +import { catalogImportTranslationRef } from '@backstage/plugin-catalog-import/alpha'; import Box from '@material-ui/core/Box'; import Checkbox from '@material-ui/core/Checkbox'; import FormControlLabel from '@material-ui/core/FormControlLabel'; @@ -48,19 +50,31 @@ export type StepConfiguration = { export interface StepperProvider { analyze: ( s: Extract, - opts: { apis: StepperApis }, + opts: { + apis: StepperApis; + t: TranslationFunction; + }, ) => StepConfiguration; prepare: ( s: Extract, - opts: { apis: StepperApis }, + opts: { + apis: StepperApis; + t: TranslationFunction; + }, ) => StepConfiguration; review: ( s: Extract, - opts: { apis: StepperApis }, + opts: { + apis: StepperApis; + t: TranslationFunction; + }, ) => StepConfiguration; finish: ( s: Extract, - opts: { apis: StepperApis }, + opts: { + apis: StepperApis; + t: TranslationFunction; + }, ) => StepConfiguration; } @@ -77,6 +91,7 @@ export interface StepperProvider { export function defaultGenerateStepper( flow: ImportFlows, defaults: StepperProvider, + t: TranslationFunction, ): StepperProvider { switch (flow) { // the prepare step is skipped but the label of the step is updated @@ -88,11 +103,11 @@ export function defaultGenerateStepper( - Discovered Locations: 1 + {t('importStepper.singleLocation.description')} } > - Select Locations + {t('importStepper.singleLocation.title')} ), content: <>, @@ -113,11 +128,13 @@ export function defaultGenerateStepper( - Discovered Locations: {state.analyzeResult.locations.length} + {t('importStepper.multipleLocations.description', { + length: state.analyzeResult.locations.length, + })} } > - Select Locations + {t('importStepper.multipleLocations.title')} ), content: ( @@ -141,7 +158,9 @@ export function defaultGenerateStepper( } return { - stepLabel: Create Pull Request, + stepLabel: ( + {t('importStepper.noLocation.title')} + ), content: ( ( <> - Pull Request Details + + {t('importStepper.noLocation.createPr.detailsTitle')} + - Entity Configuration + + {t( + 'importStepper.noLocation.createPr.configurationTitle', + )} + - WARNING: This may fail if no CODEOWNERS file is found at - the target location. + {t( + 'importStepper.noLocation.createPr.codeownersHelperText', + )} )} @@ -261,8 +305,8 @@ export function defaultGenerateStepper( } export const defaultStepper: StepperProvider = { - analyze: (state, { apis }) => ({ - stepLabel: Select URL, + analyze: (state, { apis, t }) => ({ + stepLabel: {t('importStepper.analyze.title')}, content: ( ({ + prepare: (state, { t }) => ({ stepLabel: ( - Optional}> - Import Actions + + {t('importStepper.prepare.description')} + + } + > + {t('importStepper.prepare.title')} ), content: , }), - review: state => ({ - stepLabel: Review, + review: (state, { t }) => ({ + stepLabel: {t('importStepper.review.title')}, content: ( ({ - stepLabel: Finish, + finish: (state, { t }) => ({ + stepLabel: {t('importStepper.finish.title')}, content: ( { + const { t } = useTranslationRef(catalogImportTranslationRef); const entityRoute = useRouteRef(entityRouteRef); if (prepareResult.type === 'repository') { return ( <> - The following Pull Request has been opened:{' '} + {t('stepFinishImportLocation.repository.title')} { - Your entities will be imported as soon as the Pull Request is merged. + {t('stepFinishImportLocation.repository.description')} - Register another + + {t('stepFinishImportLocation.backButtonText')} + ); @@ -94,9 +100,7 @@ export const StepFinishImportLocation = ({ prepareResult, onReset }: Props) => { <> {newLocations.length > 0 && ( <> - - The following entities have been added to the catalog: - + {t('stepFinishImportLocation.locations.new')} { {existingLocations.length > 0 && ( <> - A refresh was triggered for the following locations: + {t('stepFinishImportLocation.locations.existing')} { {newComponentEntity && ( - View Component + {t('stepFinishImportLocation.locations.viewButtonText')} )} - Register another + + {t('stepFinishImportLocation.backButtonText')} + ); diff --git a/plugins/catalog-import/src/components/StepInitAnalyzeUrl/StepInitAnalyzeUrl.test.tsx b/plugins/catalog-import/src/components/StepInitAnalyzeUrl/StepInitAnalyzeUrl.test.tsx index b1d5de7527..ed6fb5683f 100644 --- a/plugins/catalog-import/src/components/StepInitAnalyzeUrl/StepInitAnalyzeUrl.test.tsx +++ b/plugins/catalog-import/src/components/StepInitAnalyzeUrl/StepInitAnalyzeUrl.test.tsx @@ -15,8 +15,8 @@ */ import { errorApiRef } from '@backstage/core-plugin-api'; -import { TestApiProvider } from '@backstage/test-utils'; -import { act, render, screen } from '@testing-library/react'; +import { renderInTestApp, TestApiProvider } from '@backstage/test-utils'; +import { act, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { ReactNode } from 'react'; import { AnalyzeResult, catalogImportApiRef } from '../../api/'; @@ -60,23 +60,24 @@ describe('', () => { }); it('renders without exploding', async () => { - render( undefined} />, { - wrapper: Wrapper, - }); + await renderInTestApp( + + undefined} /> + , + ); expect(screen.getByRole('textbox', { name: /URL/i })).toBeInTheDocument(); expect(screen.getByRole('textbox', { name: /URL/i })).toHaveValue(''); }); it('should use default analysis url', async () => { - render( - undefined} - analysisUrl="https://default" - />, - { - wrapper: Wrapper, - }, + await renderInTestApp( + + undefined} + analysisUrl="https://default" + /> + , ); expect(screen.getByRole('textbox', { name: /URL/i })).toBeInTheDocument(); @@ -88,9 +89,11 @@ describe('', () => { it('should not analyze without url', async () => { const onAnalysisFn = jest.fn(); - render(, { - wrapper: Wrapper, - }); + await renderInTestApp( + + + , + ); await act(async () => { try { @@ -108,9 +111,11 @@ describe('', () => { it('should not analyze invalid value', async () => { const onAnalysisFn = jest.fn(); - render(, { - wrapper: Wrapper, - }); + await renderInTestApp( + + + , + ); await act(async () => { await userEvent.type( @@ -136,9 +141,11 @@ describe('', () => { locations: [location], } as AnalyzeResult; - render(, { - wrapper: Wrapper, - }); + await renderInTestApp( + + + , + ); catalogImportApi.analyzeUrl.mockReturnValueOnce( Promise.resolve(analyzeResult), @@ -170,9 +177,11 @@ describe('', () => { locations: [location, location], } as AnalyzeResult; - render(, { - wrapper: Wrapper, - }); + await renderInTestApp( + + + , + ); catalogImportApi.analyzeUrl.mockReturnValueOnce( Promise.resolve(analyzeResult), @@ -203,9 +212,11 @@ describe('', () => { locations: [], } as AnalyzeResult; - render(, { - wrapper: Wrapper, - }); + await renderInTestApp( + + + , + ); catalogImportApi.analyzeUrl.mockReturnValueOnce( Promise.resolve(analyzeResult), @@ -244,9 +255,11 @@ describe('', () => { ], } as AnalyzeResult; - render(, { - wrapper: Wrapper, - }); + await renderInTestApp( + + + , + ); catalogImportApi.analyzeUrl.mockReturnValueOnce( Promise.resolve(analyzeResult), @@ -279,9 +292,11 @@ describe('', () => { generatedEntities: [], } as AnalyzeResult; - render(, { - wrapper: Wrapper, - }); + await renderInTestApp( + + + , + ); catalogImportApi.analyzeUrl.mockReturnValueOnce( Promise.resolve(analyzeResult), @@ -320,11 +335,10 @@ describe('', () => { ], } as AnalyzeResult; - render( - , - { - wrapper: Wrapper, - }, + await renderInTestApp( + + + , ); catalogImportApi.analyzeUrl.mockReturnValueOnce( @@ -349,9 +363,11 @@ describe('', () => { it('should report unknown type to the errorapi', async () => { const onAnalysisFn = jest.fn(); - render(, { - wrapper: Wrapper, - }); + await renderInTestApp( + + + , + ); catalogImportApi.analyzeUrl.mockReturnValueOnce( Promise.resolve({ type: 'unknown' } as any as AnalyzeResult), diff --git a/plugins/catalog-import/src/components/StepInitAnalyzeUrl/StepInitAnalyzeUrl.tsx b/plugins/catalog-import/src/components/StepInitAnalyzeUrl/StepInitAnalyzeUrl.tsx index 2e2994aeb4..5a6cad055c 100644 --- a/plugins/catalog-import/src/components/StepInitAnalyzeUrl/StepInitAnalyzeUrl.tsx +++ b/plugins/catalog-import/src/components/StepInitAnalyzeUrl/StepInitAnalyzeUrl.tsx @@ -15,12 +15,15 @@ */ import { errorApiRef, useApi } from '@backstage/core-plugin-api'; +import { useTranslationRef } from '@backstage/frontend-plugin-api'; import FormHelperText from '@material-ui/core/FormHelperText'; import Grid from '@material-ui/core/Grid'; import TextField from '@material-ui/core/TextField'; import { useCallback, useState } from 'react'; import { useForm } from 'react-hook-form'; + import { AnalyzeResult, catalogImportApiRef } from '../../api'; +import { catalogImportTranslationRef } from '../../translation'; import { NextButton } from '../Buttons'; import { asInputRef } from '../helpers'; import { ImportFlows, PrepareResult } from '../useImportState'; @@ -55,6 +58,7 @@ export interface StepInitAnalyzeUrlProps { * @public */ export const StepInitAnalyzeUrl = (props: StepInitAnalyzeUrlProps) => { + const { t } = useTranslationRef(catalogImportTranslationRef); const { onAnalysis, analysisUrl = '', @@ -95,7 +99,7 @@ export const StepInitAnalyzeUrl = (props: StepInitAnalyzeUrlProps) => { ) { onAnalysis('no-location', url, analysisResult); } else { - setError("Couldn't generate entities for your repository"); + setError(t('stepInitAnalyzeUrl.error.repository')); setSubmitted(false); } break; @@ -108,16 +112,16 @@ export const StepInitAnalyzeUrl = (props: StepInitAnalyzeUrlProps) => { } else if (analysisResult.locations.length > 1) { onAnalysis('multiple-locations', url, analysisResult); } else { - setError('There are no entities at this location'); + setError(t('stepInitAnalyzeUrl.error.locations')); setSubmitted(false); } break; } default: { - const err = `Received unknown analysis result of type ${ - (analysisResult as any).type - }. Please contact the support team.`; + const err = t('stepInitAnalyzeUrl.error.default', { + type: (analysisResult as any).type, + }); setError(err); setSubmitted(false); @@ -130,7 +134,7 @@ export const StepInitAnalyzeUrl = (props: StepInitAnalyzeUrlProps) => { setSubmitted(false); } }, - [catalogImportApi, disablePullRequest, errorApi, onAnalysis], + [catalogImportApi, disablePullRequest, errorApi, onAnalysis, t], ); return ( @@ -143,7 +147,7 @@ export const StepInitAnalyzeUrl = (props: StepInitAnalyzeUrlProps) => { httpsValidator: (value: any) => (typeof value === 'string' && value.match(/^http[s]?:\/\//) !== null) || - 'Must start with http:// or https://.', + t('stepInitAnalyzeUrl.error.url'), }, }), )} @@ -151,7 +155,7 @@ export const StepInitAnalyzeUrl = (props: StepInitAnalyzeUrlProps) => { id="url" label="URL" placeholder={exampleLocationUrl} - helperText="Enter the full path to your entity file to start tracking your component" + helperText={t('stepInitAnalyzeUrl.urlHelperText')} margin="normal" variant="outlined" error={Boolean(errors.url)} @@ -167,7 +171,7 @@ export const StepInitAnalyzeUrl = (props: StepInitAnalyzeUrlProps) => { loading={submitted} type="submit" > - Analyze + {t('stepInitAnalyzeUrl.nextButtonText')} diff --git a/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/PreviewPullRequestComponent.test.tsx b/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/PreviewPullRequestComponent.test.tsx index 45b244d582..d90f950794 100644 --- a/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/PreviewPullRequestComponent.test.tsx +++ b/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/PreviewPullRequestComponent.test.tsx @@ -14,8 +14,9 @@ * limitations under the License. */ +import { renderInTestApp } from '@backstage/test-utils'; import { makeStyles } from '@material-ui/core/styles'; -import { render, screen } from '@testing-library/react'; +import { screen } from '@testing-library/react'; import { renderHook } from '@testing-library/react'; import { PreviewPullRequestComponent } from './PreviewPullRequestComponent'; @@ -27,7 +28,7 @@ const useStyles = makeStyles({ describe('', () => { it('renders without exploding', async () => { - render( + await renderInTestApp( ', () => { it('renders card with custom styles', async () => { const { result } = renderHook(() => useStyles()); - render( + await renderInTestApp( ', () => { const title = screen.getByText('My Title'); const description = screen.getByText('description', { selector: 'strong' }); expect(title).toBeInTheDocument(); - expect(title).not.toBeVisible(); expect(description).toBeInTheDocument(); - expect(description).not.toBeVisible(); + + // FIXME: https://github.com/testing-library/jest-dom/issues/444 + // expect(title).not.toBeVisible(); + // expect(description).not.toBeVisible(); + + const card = title.closest(`.${result.current.displayNone}`); + expect(card).toBeInTheDocument(); + expect(description.closest(`.${result.current.displayNone}`)).toBe(card); }); it('renders with custom styles', async () => { const { result } = renderHook(() => useStyles()); - render( + await renderInTestApp( { const { title, description, classes } = props; + const { t } = useTranslationRef(catalogImportTranslationRef); return ( - + diff --git a/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/StepPrepareCreatePullRequest.test.tsx b/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/StepPrepareCreatePullRequest.test.tsx index edb2e391db..6f69b739bd 100644 --- a/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/StepPrepareCreatePullRequest.test.tsx +++ b/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/StepPrepareCreatePullRequest.test.tsx @@ -16,9 +16,14 @@ import { configApiRef, errorApiRef } from '@backstage/core-plugin-api'; import { catalogApiRef } from '@backstage/plugin-catalog-react'; -import { TestApiProvider, mockApis } from '@backstage/test-utils'; +import { catalogApiMock } from '@backstage/plugin-catalog-react/testUtils'; +import { + mockApis, + renderInTestApp, + TestApiProvider, +} from '@backstage/test-utils'; import TextField from '@material-ui/core/TextField'; -import { render, screen, waitFor } from '@testing-library/react'; +import { screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { ReactNode } from 'react'; import { AnalyzeResult, catalogImportApiRef } from '../../api'; @@ -27,7 +32,6 @@ import { generateEntities, StepPrepareCreatePullRequest, } from './StepPrepareCreatePullRequest'; -import { catalogApiMock } from '@backstage/plugin-catalog-react/testUtils'; describe('', () => { const catalogImportApi: jest.Mocked = { @@ -90,24 +94,23 @@ describe('', () => { it('renders without exploding', async () => { catalogApi.getEntities.mockReturnValue(Promise.resolve({ items: [] })); - render( - { - return ( - <> - - - - - - ); - }} - />, - { - wrapper: Wrapper, - }, + await renderInTestApp( + + { + return ( + <> + + + + + + ); + }} + /> + , ); const title = await screen.findByText('My title'); @@ -129,32 +132,31 @@ describe('', () => { }), ); - render( - { - return ( - <> - - - - - - ); - }} - />, - { - wrapper: Wrapper, - }, + await renderInTestApp( + + { + return ( + <> + + + + + + ); + }} + /> + , ); await userEvent.type(await screen.findByLabelText('name'), '-changed'); @@ -211,24 +213,23 @@ spec: new Error('some error'), ); - render( - { - return ( - <> - - - - - - ); - }} - />, - { - wrapper: Wrapper, - }, + await renderInTestApp( + + { + return ( + <> + + + + + + ); + }} + /> + , ); await userEvent.click( @@ -256,15 +257,14 @@ spec: }), ); - render( - , - { - wrapper: Wrapper, - }, + await renderInTestApp( + + + , ); await waitFor(() => { diff --git a/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/StepPrepareCreatePullRequest.tsx b/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/StepPrepareCreatePullRequest.tsx index ce7448c0db..a40f4868e9 100644 --- a/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/StepPrepareCreatePullRequest.tsx +++ b/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/StepPrepareCreatePullRequest.tsx @@ -17,6 +17,7 @@ import { Entity } from '@backstage/catalog-model'; import { errorApiRef, useApi } from '@backstage/core-plugin-api'; import { assertError } from '@backstage/errors'; +import { useTranslationRef } from '@backstage/frontend-plugin-api'; import { catalogApiRef, humanizeEntityRef, @@ -30,8 +31,10 @@ import { ReactNode, useCallback, useEffect, useState } from 'react'; import { NestedValue, UseFormReturn } from 'react-hook-form'; import useAsync from 'react-use/esm/useAsync'; import YAML from 'yaml'; + import { AnalyzeResult, catalogImportApiRef } from '../../api'; import { useCatalogFilename } from '../../hooks'; +import { catalogImportTranslationRef } from '../../translation'; import { PartialEntity } from '../../types'; import { BackButton, NextButton } from '../Buttons'; import { PrepareResult } from '../useImportState'; @@ -127,6 +130,7 @@ export const StepPrepareCreatePullRequest = ( ) => { const { analyzeResult, onPrepare, onGoBack, renderFormFields } = props; + const { t } = useTranslationRef(catalogImportTranslationRef); const classes = useStyles(); const catalogApi = useApi(catalogApiRef); const catalogImportApi = useApi(catalogImportApiRef); @@ -252,7 +256,9 @@ export const StepPrepareCreatePullRequest = ( })} - Preview Pull Request + + {t('stepPrepareCreatePullRequest.previewPr.title')} + - Preview Entities + + {t('stepPrepareCreatePullRequest.previewCatalogInfo.title')} + - Create PR + {t('stepPrepareCreatePullRequest.nextButtonText')} diff --git a/plugins/catalog-import/src/components/StepPrepareSelectLocations/StepPrepareSelectLocations.tsx b/plugins/catalog-import/src/components/StepPrepareSelectLocations/StepPrepareSelectLocations.tsx index 1d94b22bf4..5aa7760c98 100644 --- a/plugins/catalog-import/src/components/StepPrepareSelectLocations/StepPrepareSelectLocations.tsx +++ b/plugins/catalog-import/src/components/StepPrepareSelectLocations/StepPrepareSelectLocations.tsx @@ -14,6 +14,7 @@ * limitations under the License. */ +import { useTranslationRef } from '@backstage/frontend-plugin-api'; import Checkbox from '@material-ui/core/Checkbox'; import Grid from '@material-ui/core/Grid'; import ListItem from '@material-ui/core/ListItem'; @@ -21,12 +22,14 @@ import ListItemIcon from '@material-ui/core/ListItemIcon'; import ListItemText from '@material-ui/core/ListItemText'; import Typography from '@material-ui/core/Typography'; import LocationOnIcon from '@material-ui/icons/LocationOn'; +import partition from 'lodash/partition'; import { useCallback, useState } from 'react'; + import { AnalyzeResult } from '../../api'; +import { catalogImportTranslationRef } from '../../translation'; import { BackButton, NextButton } from '../Buttons'; import { EntityListComponent } from '../EntityListComponent'; import { PrepareResult } from '../useImportState'; -import partition from 'lodash/partition'; type Props = { analyzeResult: Extract; @@ -49,6 +52,8 @@ export const StepPrepareSelectLocations = ({ onPrepare, onGoBack, }: Props) => { + const { t } = useTranslationRef(catalogImportTranslationRef); + const [selectedUrls, setSelectedUrls] = useState( prepareResult?.locations.map(l => l.target) || [], ); @@ -82,8 +87,7 @@ export const StepPrepareSelectLocations = ({ {locations.length > 0 && ( <> - Select one or more locations that are present in your git - repository: + {t('stepPrepareSelectLocations.locations.description')} - + } onItemClick={onItemClick} @@ -120,7 +126,9 @@ export const StepPrepareSelectLocations = ({ {existingLocations.length > 0 && ( <> - These locations already exist in the catalog: + + {t('stepPrepareSelectLocations.existingLocations.description')} + } @@ -133,7 +141,7 @@ export const StepPrepareSelectLocations = ({ {onGoBack && } - Review + {t('stepPrepareSelectLocations.nextButtonText')} diff --git a/plugins/catalog-import/src/components/StepReviewLocation/StepReviewLocation.tsx b/plugins/catalog-import/src/components/StepReviewLocation/StepReviewLocation.tsx index 122c219a8b..ddf2cd51d5 100644 --- a/plugins/catalog-import/src/components/StepReviewLocation/StepReviewLocation.tsx +++ b/plugins/catalog-import/src/components/StepReviewLocation/StepReviewLocation.tsx @@ -14,20 +14,22 @@ * limitations under the License. */ +import { stringifyEntityRef } from '@backstage/catalog-model'; +import { Link } from '@backstage/core-components'; +import { configApiRef, useAnalytics, useApi } from '@backstage/core-plugin-api'; +import { assertError } from '@backstage/errors'; +import { useTranslationRef } from '@backstage/frontend-plugin-api'; import { catalogApiRef } from '@backstage/plugin-catalog-react'; import FormHelperText from '@material-ui/core/FormHelperText'; import Grid from '@material-ui/core/Grid'; import Typography from '@material-ui/core/Typography'; import LocationOnIcon from '@material-ui/icons/LocationOn'; import { useCallback, useState } from 'react'; + import { BackButton, NextButton } from '../Buttons'; import { EntityListComponent } from '../EntityListComponent'; import { PrepareResult, ReviewResult } from '../useImportState'; - -import { configApiRef, useAnalytics, useApi } from '@backstage/core-plugin-api'; -import { Link } from '@backstage/core-components'; -import { stringifyEntityRef } from '@backstage/catalog-model'; -import { assertError } from '@backstage/errors'; +import { catalogImportTranslationRef } from '../../translation'; type Props = { prepareResult: PrepareResult; @@ -40,6 +42,7 @@ export const StepReviewLocation = ({ onReview, onGoBack, }: Props) => { + const { t } = useTranslationRef(catalogImportTranslationRef); const catalogApi = useApi(catalogApiRef); const configApi = useApi(configApiRef); const analytics = useAnalytics(); @@ -119,7 +122,7 @@ export const StepReviewLocation = ({ {prepareResult.type === 'repository' && ( <> - The following Pull Request has been opened:{' '} + {t('stepReviewLocation.prepareResult.title')} - You can already import the location and {appTitle} will fetch the - entities as soon as the Pull Request is merged. + {t('stepReviewLocation.prepareResult.description', { appTitle })} )} {exists - ? 'The following locations already exist in the catalog:' - : 'The following entities will be added to the catalog:'} + ? t('stepReviewLocation.catalog.exists') + : t('stepReviewLocation.catalog.new')} handleClick()} > - {exists ? 'Refresh' : 'Import'} + {exists + ? t('stepReviewLocation.refresh') + : t('stepReviewLocation.import')} diff --git a/plugins/catalog-import/src/translation.ts b/plugins/catalog-import/src/translation.ts index 353e09056b..857b551d9c 100644 --- a/plugins/catalog-import/src/translation.ts +++ b/plugins/catalog-import/src/translation.ts @@ -20,9 +20,132 @@ import { createTranslationRef } from '@backstage/core-plugin-api/alpha'; export const catalogImportTranslationRef = createTranslationRef({ id: 'catalog-import', messages: { - pageTitle: 'Register an existing component', + buttons: { + back: 'Back', + }, + defaultImportPage: { + headerTitle: 'Register an existing component', + contentHeaderTitle: 'Start tracking your component in {{appTitle}}', + supportTitle: + 'Start tracking your component in {{appTitle}} by adding it to the software catalog.', + }, importInfoCard: { title: 'Register an existing component', + deepLinkTitle: 'Learn more about the Software Catalog', + linkDescription: + 'Enter the URL to your source code repository to add it to {{appTitle}}.', + fileLinkTitle: 'Link to an existing entity file', + examplePrefix: 'Example: ', + fileLinkDescription: + 'The wizard analyzes the file, previews the entities, and adds them to the {{appTitle}} catalog.', + githubIntegration: { + title: 'Link to a repository', + label: 'GitHub only', + }, + }, + importStepper: { + singleLocation: { + title: 'Select Locations', + description: 'Discovered Locations: 1', + }, + multipleLocations: { + title: 'Select Locations', + description: 'Discovered Locations: {{length, number}}', + }, + noLocation: { + title: 'Create Pull Request', + createPr: { + detailsTitle: 'Pull Request Details', + titleLabel: 'Pull Request Title', + titlePlaceholder: 'Add Backstage catalog entity descriptor files', + bodyLabel: 'Pull Request Body', + bodyPlaceholder: 'A describing text with Markdown support', + configurationTitle: 'Entity Configuration', + componentNameLabel: 'Name of the created component', + componentNamePlaceholder: 'my-component', + ownerLoadingText: 'Loading groups…', + ownerHelperText: + 'Select an owner from the list or enter a reference to a Group or a User', + ownerErrorHelperText: 'required value', + ownerLabel: 'Entity Owner', + ownerPlaceholder: 'my-group', + codeownersHelperText: + 'WARNING: This may fail if no CODEOWNERS file is found at the target location.', + }, + }, + analyze: { + title: 'Select URL', + }, + prepare: { + title: 'Import Actions', + description: 'Optional', + }, + review: { + title: 'Review', + }, + finish: { + title: 'Finish', + }, + }, + stepFinishImportLocation: { + backButtonText: 'Register another', + repository: { + title: 'The following Pull Request has been opened: ', + description: + 'Your entities will be imported as soon as the Pull Request is merged.', + }, + locations: { + new: 'The following entities have been added to the catalog:', + existing: 'A refresh was triggered for the following locations:', + viewButtonText: 'View Component', + backButtonText: 'Register another', + }, + }, + stepInitAnalyzeUrl: { + error: { + repository: "Couldn't generate entities for your repository", + locations: 'There are no entities at this location', + default: + 'Received unknown analysis result of type {{type}}. Please contact the support team.', + url: 'Must start with http:// or https://.', + }, + urlHelperText: + 'Enter the full path to your entity file to start tracking your component', + nextButtonText: 'Analyze', + }, + stepPrepareCreatePullRequest: { + previewPr: { + title: 'Preview Pull Request', + subheader: 'Create a new Pull Request', + }, + previewCatalogInfo: { + title: 'Preview Entities', + }, + nextButtonText: 'Create PR', + }, + stepPrepareSelectLocations: { + locations: { + description: + 'Select one or more locations that are present in your git repository:', + selectAll: 'Select All', + }, + existingLocations: { + description: 'These locations already exist in the catalog:', + }, + nextButtonText: 'Review', + }, + stepReviewLocation: { + prepareResult: { + title: 'The following Pull Request has been opened: ', + description: + 'You can already import the location and {{appTitle}} will fetch the entities as soon as the Pull Request is merged.', + }, + catalog: { + exists: 'The following locations already exist in the catalog:', + new: 'The following entities will be added to the catalog:', + }, + refresh: 'Refresh', + import: 'Import', }, }, }); diff --git a/plugins/scaffolder-react/report-alpha.api.md b/plugins/scaffolder-react/report-alpha.api.md index 06769b29e2..2014bff9be 100644 --- a/plugins/scaffolder-react/report-alpha.api.md +++ b/plugins/scaffolder-react/report-alpha.api.md @@ -344,8 +344,8 @@ export const scaffolderReactTranslationRef: TranslationRef< readonly 'stepper.backButtonText': 'Back'; readonly 'stepper.createButtonText': 'Create'; readonly 'stepper.reviewButtonText': 'Review'; - readonly 'stepper.stepIndexLabel': 'Step {{index, number}}'; readonly 'stepper.nextButtonText': 'Next'; + readonly 'stepper.stepIndexLabel': 'Step {{index, number}}'; readonly 'templateCategoryPicker.title': 'Categories'; readonly 'templateCard.noDescription': 'No description'; readonly 'templateCard.chooseButtonText': 'Choose'; From d1ce7a47d97d0c58c52418dd7d8c6e20733691e9 Mon Sep 17 00:00:00 2001 From: JounQin Date: Tue, 11 Mar 2025 21:48:41 +0800 Subject: [PATCH 088/109] docs: add more about prop signature change details Signed-off-by: JounQin --- .changeset/neat-glasses-occur.md | 8 +++++++- plugins/catalog-import/report.api.md | 1 + .../src/components/ImportStepper/ImportStepper.tsx | 4 +++- .../src/components/ImportStepper/defaults.tsx | 1 + 4 files changed, 12 insertions(+), 2 deletions(-) diff --git a/.changeset/neat-glasses-occur.md b/.changeset/neat-glasses-occur.md index 4e5e62b7ca..dedb576dc0 100644 --- a/.changeset/neat-glasses-occur.md +++ b/.changeset/neat-glasses-occur.md @@ -1,5 +1,11 @@ --- -'@backstage/plugin-catalog-import': patch +'@backstage/plugin-catalog-import': minor --- Add i18n support for catalog-import plugin + +`ImportStepper` component now supports i18n, it's `generateStepper` prop now +accepts a third `t: TranslationFunction` +function from `@backstage/core-plugin-api/alpha` and uses it to translate the +stepper steps, checkout `catalogImportTranslationRef` from `@backstage/plugin-catalog-import/alpha` +for more details. diff --git a/plugins/catalog-import/report.api.md b/plugins/catalog-import/report.api.md index eed2639168..ed7d2f3347 100644 --- a/plugins/catalog-import/report.api.md +++ b/plugins/catalog-import/report.api.md @@ -216,6 +216,7 @@ export interface ImportStepperProps { generateStepper?: ( flow: ImportFlows, defaults: StepperProvider, + t: TranslationFunction, ) => StepperProvider; // (undocumented) initialUrl?: string; diff --git a/plugins/catalog-import/src/components/ImportStepper/ImportStepper.tsx b/plugins/catalog-import/src/components/ImportStepper/ImportStepper.tsx index ed399a8a96..614751c292 100644 --- a/plugins/catalog-import/src/components/ImportStepper/ImportStepper.tsx +++ b/plugins/catalog-import/src/components/ImportStepper/ImportStepper.tsx @@ -16,7 +16,9 @@ import { InfoCard, InfoCardVariants } from '@backstage/core-components'; import { useApi } from '@backstage/core-plugin-api'; +import { TranslationFunction } from '@backstage/core-plugin-api/alpha'; import { useTranslationRef } from '@backstage/frontend-plugin-api'; +import { catalogImportTranslationRef } from '@backstage/plugin-catalog-import/alpha'; import Step from '@material-ui/core/Step'; import StepContent from '@material-ui/core/StepContent'; import Stepper from '@material-ui/core/Stepper'; @@ -24,7 +26,6 @@ import { makeStyles } from '@material-ui/core/styles'; import { useMemo } from 'react'; import { catalogImportApiRef } from '../../api'; -import { catalogImportTranslationRef } from '../../translation'; import { ImportFlows, ImportState, useImportState } from '../useImportState'; import { defaultGenerateStepper, @@ -49,6 +50,7 @@ export interface ImportStepperProps { generateStepper?: ( flow: ImportFlows, defaults: StepperProvider, + t: TranslationFunction, ) => StepperProvider; variant?: InfoCardVariants; } diff --git a/plugins/catalog-import/src/components/ImportStepper/defaults.tsx b/plugins/catalog-import/src/components/ImportStepper/defaults.tsx index 2b8f7e107b..7d597c75ad 100644 --- a/plugins/catalog-import/src/components/ImportStepper/defaults.tsx +++ b/plugins/catalog-import/src/components/ImportStepper/defaults.tsx @@ -86,6 +86,7 @@ export interface StepperProvider { * * @param flow - the name of the active flow * @param defaults - the default steps + * @param t - the translation function * @public */ export function defaultGenerateStepper( From e2fd54992dc90a30c3369b901031440c36b326d5 Mon Sep 17 00:00:00 2001 From: benjdlambert Date: Tue, 1 Apr 2025 11:07:07 +0100 Subject: [PATCH 089/109] chore: updating changeset Signed-off-by: benjdlambert --- .changeset/neat-glasses-occur.md | 10 ++-------- .changeset/neat-glasses-occured.md | 5 +++++ 2 files changed, 7 insertions(+), 8 deletions(-) create mode 100644 .changeset/neat-glasses-occured.md diff --git a/.changeset/neat-glasses-occur.md b/.changeset/neat-glasses-occur.md index dedb576dc0..5fea5b1886 100644 --- a/.changeset/neat-glasses-occur.md +++ b/.changeset/neat-glasses-occur.md @@ -1,11 +1,5 @@ --- -'@backstage/plugin-catalog-import': minor +'@backstage/plugin-catalog-import': patch --- -Add i18n support for catalog-import plugin - -`ImportStepper` component now supports i18n, it's `generateStepper` prop now -accepts a third `t: TranslationFunction` -function from `@backstage/core-plugin-api/alpha` and uses it to translate the -stepper steps, checkout `catalogImportTranslationRef` from `@backstage/plugin-catalog-import/alpha` -for more details. +Add i18n support for `catalog-import` plugin. diff --git a/.changeset/neat-glasses-occured.md b/.changeset/neat-glasses-occured.md new file mode 100644 index 0000000000..289e03e243 --- /dev/null +++ b/.changeset/neat-glasses-occured.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-import': minor +--- + +**BREAKING**: `generateStepper` and `defaultGenerateStepper` now require a translation argument to be passed through for supporting translations. From 421b0497fa6673065c6de6c0dac96f6db8eb0922 Mon Sep 17 00:00:00 2001 From: JounQin Date: Tue, 1 Apr 2025 18:40:12 +0800 Subject: [PATCH 090/109] chore: re-run `build:api-reports` Signed-off-by: JounQin --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 15036733a7..826ec4bf97 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "scripts": { "build-storybook": "yarn ./storybook run build-storybook", "build:all": "backstage-cli repo build --all", - "build:api-docs": "LANG=en_EN yarn build:api-reports --docs --exclude 'plugins/@(adr|adr-backend|adr-common|airbrake|airbrake-backend|allure|analytics-module-ga|analytics-module-ga4|analytics-module-newrelic-browser|apache-airflow|api-docs|api-docs-module-protoc-gen-doc|apollo-explorer|app-visualizer|azure-devops|azure-devops-backend|azure-devops-common|azure-sites|azure-sites-backend|azure-sites-common|badges|badges-backend|bazaar|bazaar-backend|bitbucket-cloud-common|bitrise|catalog-graph|catalog-graphql|catalog-import|catalog-unprocessed-entities|cicd-statistics|cicd-statistics-module-gitlab|circleci|cloudbuild|code-climate|code-coverage|code-coverage-backend|codescene|config-schema|cost-insights|cost-insights-common|dynatrace|entity-feedback|entity-feedback-backend|entity-feedback-common|entity-validation|example-todo-list|example-todo-list-backend|example-todo-list-common|firehydrant|fossa|gcalendar|gcp-projects|git-release-manager|github-actions|github-deployments|github-issues|github-pull-requests-board|gitops-profiles|gocd|graphiql|graphql-backend|graphql-voyager|ilert|jenkins|jenkins-backend|jenkins-common|kafka|kafka-backend|lighthouse|lighthouse-backend|lighthouse-common|linguist|linguist-backend|linguist-common|microsoft-calendar|newrelic|newrelic-dashboard|nomad|nomad-backend|octopus-deploy|opencost|pagerduty|periskop|periskop-backend|playlist|playlist-backend|playlist-common|proxy-backend|puppetdb|rollbar|rollbar-backend|sentry|shortcuts|splunk-on-call|stack-overflow|stack-overflow-backend|stackstorm|tech-radar|tech-radar-2|todo|todo-backend|xcmetrics)'", + "build:api-docs": "yarn build:api-reports --docs --exclude 'plugins/@(adr|adr-backend|adr-common|airbrake|airbrake-backend|allure|analytics-module-ga|analytics-module-ga4|analytics-module-newrelic-browser|apache-airflow|api-docs|api-docs-module-protoc-gen-doc|apollo-explorer|app-visualizer|azure-devops|azure-devops-backend|azure-devops-common|azure-sites|azure-sites-backend|azure-sites-common|badges|badges-backend|bazaar|bazaar-backend|bitbucket-cloud-common|bitrise|catalog-graph|catalog-graphql|catalog-import|catalog-unprocessed-entities|cicd-statistics|cicd-statistics-module-gitlab|circleci|cloudbuild|code-climate|code-coverage|code-coverage-backend|codescene|config-schema|cost-insights|cost-insights-common|dynatrace|entity-feedback|entity-feedback-backend|entity-feedback-common|entity-validation|example-todo-list|example-todo-list-backend|example-todo-list-common|firehydrant|fossa|gcalendar|gcp-projects|git-release-manager|github-actions|github-deployments|github-issues|github-pull-requests-board|gitops-profiles|gocd|graphiql|graphql-backend|graphql-voyager|ilert|jenkins|jenkins-backend|jenkins-common|kafka|kafka-backend|lighthouse|lighthouse-backend|lighthouse-common|linguist|linguist-backend|linguist-common|microsoft-calendar|newrelic|newrelic-dashboard|nomad|nomad-backend|octopus-deploy|opencost|pagerduty|periskop|periskop-backend|playlist|playlist-backend|playlist-common|proxy-backend|puppetdb|rollbar|rollbar-backend|sentry|shortcuts|splunk-on-call|stack-overflow|stack-overflow-backend|stackstorm|tech-radar|tech-radar-2|todo|todo-backend|xcmetrics)'", "build:api-reports": "yarn build:api-reports:only --tsc", "build:api-reports:only": "LANG=en_US.UTF-8 NODE_OPTIONS=--max-old-space-size=8192 backstage-repo-tools api-reports --sql-reports --allow-warnings 'packages/backend-app-api,packages/core-components,plugins/+(catalog|catalog-import|git-release-manager|jenkins|kubernetes)' -o ae-undocumented,ae-wrong-input-file-type --validate-release-tags", "build:backend": "yarn workspace example-backend build", From 0bf17832dee1390319aac5ff72fe80bc932275a1 Mon Sep 17 00:00:00 2001 From: benjdlambert Date: Tue, 6 May 2025 11:14:40 +0200 Subject: [PATCH 091/109] chore: revert package.json change Signed-off-by: benjdlambert Signed-off-by: benjdlambert --- package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 826ec4bf97..b9f0f11f5a 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "scripts": { "build-storybook": "yarn ./storybook run build-storybook", "build:all": "backstage-cli repo build --all", - "build:api-docs": "yarn build:api-reports --docs --exclude 'plugins/@(adr|adr-backend|adr-common|airbrake|airbrake-backend|allure|analytics-module-ga|analytics-module-ga4|analytics-module-newrelic-browser|apache-airflow|api-docs|api-docs-module-protoc-gen-doc|apollo-explorer|app-visualizer|azure-devops|azure-devops-backend|azure-devops-common|azure-sites|azure-sites-backend|azure-sites-common|badges|badges-backend|bazaar|bazaar-backend|bitbucket-cloud-common|bitrise|catalog-graph|catalog-graphql|catalog-import|catalog-unprocessed-entities|cicd-statistics|cicd-statistics-module-gitlab|circleci|cloudbuild|code-climate|code-coverage|code-coverage-backend|codescene|config-schema|cost-insights|cost-insights-common|dynatrace|entity-feedback|entity-feedback-backend|entity-feedback-common|entity-validation|example-todo-list|example-todo-list-backend|example-todo-list-common|firehydrant|fossa|gcalendar|gcp-projects|git-release-manager|github-actions|github-deployments|github-issues|github-pull-requests-board|gitops-profiles|gocd|graphiql|graphql-backend|graphql-voyager|ilert|jenkins|jenkins-backend|jenkins-common|kafka|kafka-backend|lighthouse|lighthouse-backend|lighthouse-common|linguist|linguist-backend|linguist-common|microsoft-calendar|newrelic|newrelic-dashboard|nomad|nomad-backend|octopus-deploy|opencost|pagerduty|periskop|periskop-backend|playlist|playlist-backend|playlist-common|proxy-backend|puppetdb|rollbar|rollbar-backend|sentry|shortcuts|splunk-on-call|stack-overflow|stack-overflow-backend|stackstorm|tech-radar|tech-radar-2|todo|todo-backend|xcmetrics)'", + "build:api-docs": "LANG=en_EN yarn build:api-reports --docs --exclude 'plugins/@(adr|adr-backend|adr-common|airbrake|airbrake-backend|allure|analytics-module-ga|analytics-module-ga4|analytics-module-newrelic-browser|apache-airflow|api-docs|api-docs-module-protoc-gen-doc|apollo-explorer|app-visualizer|azure-devops|azure-devops-backend|azure-devops-common|azure-sites|azure-sites-backend|azure-sites-common|badges|badges-backend|bazaar|bazaar-backend|bitbucket-cloud-common|bitrise|catalog-graph|catalog-graphql|catalog-import|catalog-unprocessed-entities|cicd-statistics|cicd-statistics-module-gitlab|circleci|cloudbuild|code-climate|code-coverage|code-coverage-backend|codescene|config-schema|cost-insights|cost-insights-common|dynatrace|entity-feedback|entity-feedback-backend|entity-feedback-common|entity-validation|example-todo-list|example-todo-list-backend|example-todo-list-common|firehydrant|fossa|gcalendar|gcp-projects|git-release-manager|github-actions|github-deployments|github-issues|github-pull-requests-board|gitops-profiles|gocd|graphiql|graphql-backend|graphql-voyager|ilert|jenkins|jenkins-backend|jenkins-common|kafka|kafka-backend|lighthouse|lighthouse-backend|lighthouse-common|linguist|linguist-backend|linguist-common|microsoft-calendar|newrelic|newrelic-dashboard|nomad|nomad-backend|octopus-deploy|opencost|pagerduty|periskop|periskop-backend|playlist|playlist-backend|playlist-common|proxy-backend|puppetdb|rollbar|rollbar-backend|sentry|shortcuts|splunk-on-call|stack-overflow|stack-overflow-backend|stackstorm|tech-radar|tech-radar-2|todo|todo-backend|xcmetrics)'", "build:api-reports": "yarn build:api-reports:only --tsc", "build:api-reports:only": "LANG=en_US.UTF-8 NODE_OPTIONS=--max-old-space-size=8192 backstage-repo-tools api-reports --sql-reports --allow-warnings 'packages/backend-app-api,packages/core-components,plugins/+(catalog|catalog-import|git-release-manager|jenkins|kubernetes)' -o ae-undocumented,ae-wrong-input-file-type --validate-release-tags", "build:backend": "yarn workspace example-backend build", @@ -102,13 +102,13 @@ "@material-ui/pickers@^3.3.10": "patch:@material-ui/pickers@npm%3A3.3.11#./.yarn/patches/@material-ui-pickers-npm-3.3.11-1c8f68ea20.patch", "@types/react": "^18.0.0", "@types/react-dom": "^18.0.0", + "@yarnpkg/plugin-npm@npm:^3.1.0": "patch:@yarnpkg/plugin-npm@npm%3A3.1.0#~/.yarn/patches/@yarnpkg-plugin-npm-npm-3.1.0-6533d0f5a1.patch", "ast-types@0.14.2": "patch:ast-types@npm%3A0.14.2#./.yarn/patches/ast-types-npm-0.14.2-43c4ac4b0d.patch", "ast-types@^0.14.1": "patch:ast-types@npm%3A0.14.2#./.yarn/patches/ast-types-npm-0.14.2-43c4ac4b0d.patch", "csstype@npm:^3.0.2": "3.0.9", "csstype@npm:^3.1.2": "3.0.9", "csstype@npm:^3.1.3": "3.0.9", - "jest-haste-map@^29.7.0": "patch:jest-haste-map@npm%3A29.7.0#./.yarn/patches/jest-haste-map-npm-29.7.0-e3be419eff.patch", - "@yarnpkg/plugin-npm@npm:^3.1.0": "patch:@yarnpkg/plugin-npm@npm%3A3.1.0#~/.yarn/patches/@yarnpkg-plugin-npm-npm-3.1.0-6533d0f5a1.patch" + "jest-haste-map@^29.7.0": "patch:jest-haste-map@npm%3A29.7.0#./.yarn/patches/jest-haste-map-npm-29.7.0-e3be419eff.patch" }, "dependencies": { "@backstage/errors": "workspace:^", From cf7159a5d208c000d62c7f589e92c0028d6d48be Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 6 May 2025 09:34:02 +0000 Subject: [PATCH 092/109] chore(deps): update dependency @changesets/cli to v2.29.3 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/yarn.lock b/yarn.lock index c7f8e452f0..ba8e7ccfee 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9151,9 +9151,9 @@ __metadata: languageName: node linkType: hard -"@changesets/assemble-release-plan@npm:^6.0.6": - version: 6.0.6 - resolution: "@changesets/assemble-release-plan@npm:6.0.6" +"@changesets/assemble-release-plan@npm:^6.0.7": + version: 6.0.7 + resolution: "@changesets/assemble-release-plan@npm:6.0.7" dependencies: "@changesets/errors": "npm:^0.2.0" "@changesets/get-dependents-graph": "npm:^2.1.3" @@ -9161,7 +9161,7 @@ __metadata: "@changesets/types": "npm:^6.1.0" "@manypkg/get-packages": "npm:^1.1.3" semver: "npm:^7.5.3" - checksum: 10/b6c7ce7231e4c1801255d15e99355c700dc6fd62abb5330817231e2f45edd06fa7d31aac0ed3b1908a6cde33ef0c5bf2c1e71f2e03d37435131f2a4d4d48aaf8 + checksum: 10/61e0962c8116b802de11c7eddc34aa0a827166dba84686b69705da7c8e57bf16101f062b4de784a622f511e967554685b050d6f54fffe953da04f6564e65e414 languageName: node linkType: hard @@ -9175,16 +9175,16 @@ __metadata: linkType: hard "@changesets/cli@npm:^2.14.0": - version: 2.29.2 - resolution: "@changesets/cli@npm:2.29.2" + version: 2.29.3 + resolution: "@changesets/cli@npm:2.29.3" dependencies: "@changesets/apply-release-plan": "npm:^7.0.12" - "@changesets/assemble-release-plan": "npm:^6.0.6" + "@changesets/assemble-release-plan": "npm:^6.0.7" "@changesets/changelog-git": "npm:^0.2.1" "@changesets/config": "npm:^3.1.1" "@changesets/errors": "npm:^0.2.0" "@changesets/get-dependents-graph": "npm:^2.1.3" - "@changesets/get-release-plan": "npm:^4.0.10" + "@changesets/get-release-plan": "npm:^4.0.11" "@changesets/git": "npm:^3.0.4" "@changesets/logger": "npm:^0.1.1" "@changesets/pre": "npm:^2.0.2" @@ -9208,7 +9208,7 @@ __metadata: term-size: "npm:^2.1.0" bin: changeset: bin.js - checksum: 10/6c3e02c6449f0fdce675849c9bff04e2ff65d12ae38632cf941b1bfdc2e5e6e6497a949c1087d09ce494e8acf96460a8740c18c15e76123eb2d3d938abfef1bb + checksum: 10/9ab528d026651b8544d071000d7c26a9f6ac157299050ca67279b001ef6215f9647b83906aa5a4c761215696d898e704e5613df16c81c4bab7895ef1fabea9e0 languageName: node linkType: hard @@ -9248,17 +9248,17 @@ __metadata: languageName: node linkType: hard -"@changesets/get-release-plan@npm:^4.0.10": - version: 4.0.10 - resolution: "@changesets/get-release-plan@npm:4.0.10" +"@changesets/get-release-plan@npm:^4.0.11": + version: 4.0.11 + resolution: "@changesets/get-release-plan@npm:4.0.11" dependencies: - "@changesets/assemble-release-plan": "npm:^6.0.6" + "@changesets/assemble-release-plan": "npm:^6.0.7" "@changesets/config": "npm:^3.1.1" "@changesets/pre": "npm:^2.0.2" "@changesets/read": "npm:^0.6.5" "@changesets/types": "npm:^6.1.0" "@manypkg/get-packages": "npm:^1.1.3" - checksum: 10/372087faf29262bc1373721b5793090828fd3dabd9ff64f0fadf2c7dcb6dfcba19d690d9ea86ca3cab9d5c7a45878d64e302cc30f59e159e7074034cf9e806e7 + checksum: 10/4a05b4474847a60604dc6158d0c8204a400a98fcb4593dc46b897abca3a1ce1eea40af208100e119e50ad693e34f061cc9b84912aa5ff3eb1560d4db59a4af59 languageName: node linkType: hard From eeb39a5f4e876ab8da32b83028407dbde592abdf Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 6 May 2025 09:55:33 +0000 Subject: [PATCH 093/109] chore(deps): update dependency @types/inquirer to v8.2.11 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index c7f8e452f0..232601d823 100644 --- a/yarn.lock +++ b/yarn.lock @@ -20415,12 +20415,12 @@ __metadata: linkType: hard "@types/inquirer@npm:^8.1.3": - version: 8.2.10 - resolution: "@types/inquirer@npm:8.2.10" + version: 8.2.11 + resolution: "@types/inquirer@npm:8.2.11" dependencies: "@types/through": "npm:*" rxjs: "npm:^7.2.0" - checksum: 10/d7c0c5ec95af583191942ac33f8af2eb1fe839da6b4560277a8c251fa289f2dd3a5d14850baf910343700646200258ecff89dc9e1d57df29c16a1082d91a5ae3 + checksum: 10/3648c8b76fa9d49e1b4e06a42ab5df00e4fdf9e498056b73d0154dcf46388f8ba034a80d7a45fcae7e7c6499ba24376bd4772ad4dfd3a7a9763413b4ee1ab393 languageName: node linkType: hard From 1c83501fe231cec6e0d0f178a9452939cb072bb0 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 6 May 2025 10:15:42 +0000 Subject: [PATCH 094/109] chore(deps): update dependency msw to v2.7.6 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index c7f8e452f0..1f6c69bd08 100644 --- a/yarn.lock +++ b/yarn.lock @@ -38181,8 +38181,8 @@ __metadata: linkType: hard "msw@npm:^2.0.0, msw@npm:^2.0.8": - version: 2.7.5 - resolution: "msw@npm:2.7.5" + version: 2.7.6 + resolution: "msw@npm:2.7.6" dependencies: "@bundled-es-modules/cookie": "npm:^2.0.1" "@bundled-es-modules/statuses": "npm:^1.0.1" @@ -38209,7 +38209,7 @@ __metadata: optional: true bin: msw: cli/index.js - checksum: 10/9c953276feed70d0c02d80d9a51a15f54082236942f8b7d9b96e0cbf232ecf4fd1a6b26a8df43b8abd6bd11c3766af437d02a4c10f787abaf684e1b00003d241 + checksum: 10/23f3907b487102b395e2405ab8ae69c8cc74413485805a1039e1a3ab0b54b56947aa77b5ef5a15befad02a69aaa21a204ecfafd2e81d4fb1fd284cb99dfe2c0b languageName: node linkType: hard From 6189bfda1dc910a7d725d89c1c47baacb84f3eb7 Mon Sep 17 00:00:00 2001 From: James Brooks Date: Thu, 1 May 2025 11:04:38 +0100 Subject: [PATCH 095/109] Canon - Support left/right elements in TextField Signed-off-by: James Brooks --- .changeset/khaki-grapes-sink.md | 5 ++ packages/canon/css/components.css | 42 ++++++++++++---- packages/canon/css/styles.css | 42 ++++++++++++---- packages/canon/css/textfield.css | 42 ++++++++++++---- packages/canon/src/components/Icon/icons.ts | 4 ++ packages/canon/src/components/Icon/types.ts | 2 + .../TextField/TextField.stories.tsx | 50 +++++++++++++++++++ .../components/TextField/TextField.styles.css | 41 +++++++++++---- .../src/components/TextField/TextField.tsx | 33 +++++++++--- .../canon/src/components/TextField/types.ts | 10 ++++ 10 files changed, 222 insertions(+), 49 deletions(-) create mode 100644 .changeset/khaki-grapes-sink.md diff --git a/.changeset/khaki-grapes-sink.md b/.changeset/khaki-grapes-sink.md new file mode 100644 index 0000000000..774fc5ed63 --- /dev/null +++ b/.changeset/khaki-grapes-sink.md @@ -0,0 +1,5 @@ +--- +'@backstage/canon': patch +--- + +Added new leftElementProps/rightElementProps properties to the TextField to make it easier to accessorize inputs. diff --git a/packages/canon/css/components.css b/packages/canon/css/components.css index 1fc7f275c3..cedd45825c 100644 --- a/packages/canon/css/components.css +++ b/packages/canon/css/components.css @@ -567,16 +567,33 @@ margin: 0; } -.canon-TextFieldInput { +.canon-TextFieldInputWrapper { border-radius: var(--canon-radius-3); border: 1px solid var(--canon-border); - padding: 0 var(--canon-space-4); background-color: var(--canon-bg-surface-1); + align-items: center; + display: flex; +} + +.canon-TextFieldInputLeftElement { + padding-left: var(--canon-space-4); +} + +.canon-TextFieldInputRightElement { + padding-right: var(--canon-space-4); +} + +.canon-TextFieldInput { + padding: 0 var(--canon-space-4); font-size: var(--canon-font-size-3); font-family: var(--canon-font-regular); font-weight: var(--canon-font-weight-regular); color: var(--canon-fg-primary); width: 100%; + height: 100%; + cursor: inherit; + background: none; + border: none; transition: border-color .2s ease-in-out, outline-color .2s ease-in-out; } @@ -584,31 +601,34 @@ color: var(--canon-fg-secondary); } -.canon-TextFieldInput:hover { - border-color: var(--canon-border-hover); -} - .canon-TextFieldInput:focus-visible { outline-color: var(--canon-border-pressed); - border-color: var(--canon-border-pressed); outline-width: 0; } -.canon-TextFieldInput[data-invalid] { +.canon-TextFieldInputWrapper:has( > .canon-TextFieldInput:hover) { + border-color: var(--canon-border-hover); +} + +.canon-TextFieldInputWrapper:has( > .canon-TextFieldInput:focus-visible) { + border-color: var(--canon-border-pressed); +} + +.canon-TextFieldInputWrapper:has(.canon-TextFieldInput[data-invalid]) { border-color: var(--canon-fg-danger); } -.canon-TextFieldInput[data-disabled] { +.canon-TextFieldInputWrapper:has(.canon-TextFieldInput[data-disabled]) { opacity: .5; cursor: not-allowed; border: 1px solid var(--canon-border-disabled); } -.canon-TextFieldInput[data-size="small"] { +.canon-TextFieldInputWrapper[data-size="small"] { height: 2rem; } -.canon-TextFieldInput[data-size="medium"] { +.canon-TextFieldInputWrapper[data-size="medium"] { height: 2.5rem; } diff --git a/packages/canon/css/styles.css b/packages/canon/css/styles.css index 3382c15675..282291c735 100644 --- a/packages/canon/css/styles.css +++ b/packages/canon/css/styles.css @@ -9791,16 +9791,33 @@ margin: 0; } -.canon-TextFieldInput { +.canon-TextFieldInputWrapper { border-radius: var(--canon-radius-3); border: 1px solid var(--canon-border); - padding: 0 var(--canon-space-4); background-color: var(--canon-bg-surface-1); + align-items: center; + display: flex; +} + +.canon-TextFieldInputLeftElement { + padding-left: var(--canon-space-4); +} + +.canon-TextFieldInputRightElement { + padding-right: var(--canon-space-4); +} + +.canon-TextFieldInput { + padding: 0 var(--canon-space-4); font-size: var(--canon-font-size-3); font-family: var(--canon-font-regular); font-weight: var(--canon-font-weight-regular); color: var(--canon-fg-primary); width: 100%; + height: 100%; + cursor: inherit; + background: none; + border: none; transition: border-color .2s ease-in-out, outline-color .2s ease-in-out; } @@ -9808,31 +9825,34 @@ color: var(--canon-fg-secondary); } -.canon-TextFieldInput:hover { - border-color: var(--canon-border-hover); -} - .canon-TextFieldInput:focus-visible { outline-color: var(--canon-border-pressed); - border-color: var(--canon-border-pressed); outline-width: 0; } -.canon-TextFieldInput[data-invalid] { +.canon-TextFieldInputWrapper:has( > .canon-TextFieldInput:hover) { + border-color: var(--canon-border-hover); +} + +.canon-TextFieldInputWrapper:has( > .canon-TextFieldInput:focus-visible) { + border-color: var(--canon-border-pressed); +} + +.canon-TextFieldInputWrapper:has(.canon-TextFieldInput[data-invalid]) { border-color: var(--canon-fg-danger); } -.canon-TextFieldInput[data-disabled] { +.canon-TextFieldInputWrapper:has(.canon-TextFieldInput[data-disabled]) { opacity: .5; cursor: not-allowed; border: 1px solid var(--canon-border-disabled); } -.canon-TextFieldInput[data-size="small"] { +.canon-TextFieldInputWrapper[data-size="small"] { height: 2rem; } -.canon-TextFieldInput[data-size="medium"] { +.canon-TextFieldInputWrapper[data-size="medium"] { height: 2.5rem; } diff --git a/packages/canon/css/textfield.css b/packages/canon/css/textfield.css index 8315d48bc6..511873980d 100644 --- a/packages/canon/css/textfield.css +++ b/packages/canon/css/textfield.css @@ -34,16 +34,33 @@ margin: 0; } -.canon-TextFieldInput { +.canon-TextFieldInputWrapper { border-radius: var(--canon-radius-3); border: 1px solid var(--canon-border); - padding: 0 var(--canon-space-4); background-color: var(--canon-bg-surface-1); + align-items: center; + display: flex; +} + +.canon-TextFieldInputLeftElement { + padding-left: var(--canon-space-4); +} + +.canon-TextFieldInputRightElement { + padding-right: var(--canon-space-4); +} + +.canon-TextFieldInput { + padding: 0 var(--canon-space-4); font-size: var(--canon-font-size-3); font-family: var(--canon-font-regular); font-weight: var(--canon-font-weight-regular); color: var(--canon-fg-primary); width: 100%; + height: 100%; + cursor: inherit; + background: none; + border: none; transition: border-color .2s ease-in-out, outline-color .2s ease-in-out; } @@ -51,31 +68,34 @@ color: var(--canon-fg-secondary); } -.canon-TextFieldInput:hover { - border-color: var(--canon-border-hover); -} - .canon-TextFieldInput:focus-visible { outline-color: var(--canon-border-pressed); - border-color: var(--canon-border-pressed); outline-width: 0; } -.canon-TextFieldInput[data-invalid] { +.canon-TextFieldInputWrapper:has( > .canon-TextFieldInput:hover) { + border-color: var(--canon-border-hover); +} + +.canon-TextFieldInputWrapper:has( > .canon-TextFieldInput:focus-visible) { + border-color: var(--canon-border-pressed); +} + +.canon-TextFieldInputWrapper:has(.canon-TextFieldInput[data-invalid]) { border-color: var(--canon-fg-danger); } -.canon-TextFieldInput[data-disabled] { +.canon-TextFieldInputWrapper:has(.canon-TextFieldInput[data-disabled]) { opacity: .5; cursor: not-allowed; border: 1px solid var(--canon-border-disabled); } -.canon-TextFieldInput[data-size="small"] { +.canon-TextFieldInputWrapper[data-size="small"] { height: 2rem; } -.canon-TextFieldInput[data-size="medium"] { +.canon-TextFieldInputWrapper[data-size="medium"] { height: 2.5rem; } diff --git a/packages/canon/src/components/Icon/icons.ts b/packages/canon/src/components/Icon/icons.ts index 615c918db7..e43cc2e7d6 100644 --- a/packages/canon/src/components/Icon/icons.ts +++ b/packages/canon/src/components/Icon/icons.ts @@ -72,6 +72,8 @@ import { RiGithubLine, RiDiscordLine, RiYoutubeLine, + RiCloseLine, + RiSearchLine, } from '@remixicon/react'; /** @public */ @@ -103,6 +105,7 @@ export const icons: IconMap = { 'chevron-left': RiArrowLeftSLine, 'chevron-right': RiArrowRightSLine, 'chevron-up': RiArrowUpSLine, + close: RiCloseLine, cloud: RiCloudLine, code: RiCodeLine, discord: RiDiscordLine, @@ -118,6 +121,7 @@ export const icons: IconMap = { heart: RiHeartLine, moon: RiMoonLine, plus: RiAddLine, + search: RiSearchLine, 'sidebar-fold': RiSidebarFoldLine, 'sidebar-unfold': RiSidebarUnfoldLine, sparkling: RiSparklingLine, diff --git a/packages/canon/src/components/Icon/types.ts b/packages/canon/src/components/Icon/types.ts index 4870a1afc4..e97ea9c454 100644 --- a/packages/canon/src/components/Icon/types.ts +++ b/packages/canon/src/components/Icon/types.ts @@ -46,6 +46,7 @@ export type IconNames = | 'chevron-left' | 'chevron-right' | 'chevron-up' + | 'close' | 'cloud' | 'code' | 'discord' @@ -61,6 +62,7 @@ export type IconNames = | 'heart' | 'moon' | 'plus' + | 'search' | 'sidebar-fold' | 'sidebar-unfold' | 'sparkling' diff --git a/packages/canon/src/components/TextField/TextField.stories.tsx b/packages/canon/src/components/TextField/TextField.stories.tsx index 8594a8c650..45a8e1436f 100644 --- a/packages/canon/src/components/TextField/TextField.stories.tsx +++ b/packages/canon/src/components/TextField/TextField.stories.tsx @@ -15,8 +15,27 @@ */ import type { Meta, StoryObj } from '@storybook/react'; +import type { ComponentPropsWithoutRef } from 'react'; import { TextField } from './TextField'; import { Flex } from '../Flex'; +import { Icon } from '../Icon'; + +const CloseButton = (props: ComponentPropsWithoutRef<'button'>) => { + return ( + + ); +}; const meta = { title: 'Components/TextField', @@ -109,3 +128,34 @@ export const WithErrorAndDescription: Story = { description: 'Description', }, }; + +export const WithLeftAndRightElements: Story = { + args: { + ...WithLabel.args, + placeholder: 'Search...', + leftElementProps: { + children: , + }, + rightElementProps: { + children: , + }, + }, +}; + +export const WithLeftAndRightElementsAndHelpText: Story = { + args: { + ...WithLeftAndRightElements.args, + error: 'Failed to search', + description: 'Enter some text to search', + }, +}; + +export const DisabledWithLeftAndRightElements: Story = { + args: { + ...WithLeftAndRightElements.args, + disabled: true, + rightElementProps: { + children: , + }, + }, +}; diff --git a/packages/canon/src/components/TextField/TextField.styles.css b/packages/canon/src/components/TextField/TextField.styles.css index 5acc8480f3..f30192f274 100644 --- a/packages/canon/src/components/TextField/TextField.styles.css +++ b/packages/canon/src/components/TextField/TextField.styles.css @@ -29,6 +29,7 @@ margin-right: auto; cursor: pointer; } + .canon-TextFieldLabel[data-disabled] { cursor: default; } @@ -49,48 +50,68 @@ padding-top: var(--canon-space-1_5); } -.canon-TextFieldInput { +.canon-TextFieldInputWrapper { + display: flex; + align-items: center; border-radius: var(--canon-radius-3); border: 1px solid var(--canon-border); - padding: 0 var(--canon-space-4); background-color: var(--canon-bg-surface-1); +} + +.canon-TextFieldInputLeftElement { + padding-left: var(--canon-space-4); +} + +.canon-TextFieldInputRightElement { + padding-right: var(--canon-space-4); +} + +.canon-TextFieldInput { + border: none; + background: none; + padding: 0 var(--canon-space-4); font-size: var(--canon-font-size-3); font-family: var(--canon-font-regular); font-weight: var(--canon-font-weight-regular); color: var(--canon-fg-primary); transition: border-color 0.2s ease-in-out, outline-color 0.2s ease-in-out; width: 100%; + height: 100%; + cursor: inherit; } .canon-TextFieldInput::placeholder { color: var(--canon-fg-secondary); } -.canon-TextFieldInput:hover { - border-color: var(--canon-border-hover); -} - .canon-TextFieldInput:focus-visible { outline-color: var(--canon-border-pressed); outline-width: 0px; +} + +.canon-TextFieldInputWrapper:has(> .canon-TextFieldInput:hover) { + border-color: var(--canon-border-hover); +} + +.canon-TextFieldInputWrapper:has(> .canon-TextFieldInput:focus-visible) { border-color: var(--canon-border-pressed); } -.canon-TextFieldInput[data-invalid] { +.canon-TextFieldInputWrapper:has(.canon-TextFieldInput[data-invalid]) { border-color: var(--canon-fg-danger); } -.canon-TextFieldInput[data-disabled] { +.canon-TextFieldInputWrapper:has(.canon-TextFieldInput[data-disabled]) { opacity: 0.5; cursor: not-allowed; border: 1px solid var(--canon-border-disabled); } -.canon-TextFieldInput[data-size='small'] { +.canon-TextFieldInputWrapper[data-size='small'] { height: 2rem; } -.canon-TextFieldInput[data-size='medium'] { +.canon-TextFieldInputWrapper[data-size='medium'] { height: 2.5rem; } diff --git a/packages/canon/src/components/TextField/TextField.tsx b/packages/canon/src/components/TextField/TextField.tsx index 132d6ab1c6..83640c153a 100644 --- a/packages/canon/src/components/TextField/TextField.tsx +++ b/packages/canon/src/components/TextField/TextField.tsx @@ -33,6 +33,8 @@ export const TextField = forwardRef( required, style, disabled, + leftElementProps, + rightElementProps, ...rest } = props; @@ -57,12 +59,31 @@ export const TextField = forwardRef( )} )} - +
+ {leftElementProps ? ( +
+ ) : null} + + {rightElementProps ? ( +
+ ) : null} +
{description && ( {description} diff --git a/packages/canon/src/components/TextField/types.ts b/packages/canon/src/components/TextField/types.ts index 147d398c99..fbec9fde4b 100644 --- a/packages/canon/src/components/TextField/types.ts +++ b/packages/canon/src/components/TextField/types.ts @@ -49,4 +49,14 @@ export interface TextFieldProps * The error message of the text field */ error?: string | null; + + /** + * Props for an element to render on the left of the input + */ + leftElementProps?: React.ComponentPropsWithoutRef<'div'>; + + /** + * Props for an element to render on the right of the input + */ + rightElementProps?: React.ComponentPropsWithoutRef<'div'>; } From 5605be41f43f3b0069dfad5f0c99462f91428fdb Mon Sep 17 00:00:00 2001 From: James Brooks Date: Thu, 1 May 2025 13:37:32 +0100 Subject: [PATCH 096/109] Update API report Signed-off-by: James Brooks --- packages/canon/report.api.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/canon/report.api.md b/packages/canon/report.api.md index 765a77148f..a1dc9f591a 100644 --- a/packages/canon/report.api.md +++ b/packages/canon/report.api.md @@ -744,6 +744,7 @@ export type IconNames = | 'chevron-left' | 'chevron-right' | 'chevron-up' + | 'close' | 'cloud' | 'code' | 'discord' @@ -759,6 +760,7 @@ export type IconNames = | 'heart' | 'moon' | 'plus' + | 'search' | 'sidebar-fold' | 'sidebar-unfold' | 'sparkling' @@ -1219,7 +1221,9 @@ export interface TextFieldProps description?: string; error?: string | null; label?: string; + leftElementProps?: React.ComponentPropsWithoutRef<'div'>; name: string; + rightElementProps?: React.ComponentPropsWithoutRef<'div'>; size?: 'small' | 'medium' | Partial>; } From 6f150a3bb7c31f35fae1ac0b16436843839f2352 Mon Sep 17 00:00:00 2001 From: James Brooks Date: Thu, 1 May 2025 14:31:32 +0100 Subject: [PATCH 097/109] Spacing tweaks Signed-off-by: James Brooks --- .../canon/src/components/TextField/TextField.stories.tsx | 2 +- .../canon/src/components/TextField/TextField.styles.css | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/canon/src/components/TextField/TextField.stories.tsx b/packages/canon/src/components/TextField/TextField.stories.tsx index 45a8e1436f..8c876eab27 100644 --- a/packages/canon/src/components/TextField/TextField.stories.tsx +++ b/packages/canon/src/components/TextField/TextField.stories.tsx @@ -155,7 +155,7 @@ export const DisabledWithLeftAndRightElements: Story = { ...WithLeftAndRightElements.args, disabled: true, rightElementProps: { - children: , + children: , }, }, }; diff --git a/packages/canon/src/components/TextField/TextField.styles.css b/packages/canon/src/components/TextField/TextField.styles.css index f30192f274..3830f084b2 100644 --- a/packages/canon/src/components/TextField/TextField.styles.css +++ b/packages/canon/src/components/TextField/TextField.styles.css @@ -53,23 +53,23 @@ .canon-TextFieldInputWrapper { display: flex; align-items: center; + padding: 0 var(--canon-space-3); border-radius: var(--canon-radius-3); border: 1px solid var(--canon-border); background-color: var(--canon-bg-surface-1); } .canon-TextFieldInputLeftElement { - padding-left: var(--canon-space-4); + padding-right: var(--canon-space-1); } .canon-TextFieldInputRightElement { - padding-right: var(--canon-space-4); + padding-left: var(--canon-space-1); } .canon-TextFieldInput { border: none; background: none; - padding: 0 var(--canon-space-4); font-size: var(--canon-font-size-3); font-family: var(--canon-font-regular); font-weight: var(--canon-font-weight-regular); From 01f101ca79029fc4792e136a34dcf09e128e8446 Mon Sep 17 00:00:00 2001 From: James Brooks Date: Thu, 1 May 2025 17:12:01 +0100 Subject: [PATCH 098/109] Simpler API Signed-off-by: James Brooks --- .changeset/khaki-grapes-sink.md | 2 +- packages/canon/css/components.css | 46 ++++++++++---- packages/canon/css/styles.css | 46 ++++++++++---- packages/canon/css/textfield.css | 46 ++++++++++---- packages/canon/report.api.md | 4 +- .../TextField/TextField.stories.tsx | 60 +++++++------------ .../components/TextField/TextField.styles.css | 43 ++++++++++--- .../src/components/TextField/TextField.tsx | 33 ++++------ .../canon/src/components/TextField/types.ts | 9 +-- 9 files changed, 181 insertions(+), 108 deletions(-) diff --git a/.changeset/khaki-grapes-sink.md b/.changeset/khaki-grapes-sink.md index 774fc5ed63..d96521e029 100644 --- a/.changeset/khaki-grapes-sink.md +++ b/.changeset/khaki-grapes-sink.md @@ -2,4 +2,4 @@ '@backstage/canon': patch --- -Added new leftElementProps/rightElementProps properties to the TextField to make it easier to accessorize inputs. +Added new icon and onClear props to the TextField to make it easier to accessorize inputs. diff --git a/packages/canon/css/components.css b/packages/canon/css/components.css index cedd45825c..c122251b5b 100644 --- a/packages/canon/css/components.css +++ b/packages/canon/css/components.css @@ -568,6 +568,7 @@ } .canon-TextFieldInputWrapper { + padding: 0 var(--canon-space-3); border-radius: var(--canon-radius-3); border: 1px solid var(--canon-border); background-color: var(--canon-bg-surface-1); @@ -575,16 +576,15 @@ display: flex; } -.canon-TextFieldInputLeftElement { - padding-left: var(--canon-space-4); -} - -.canon-TextFieldInputRightElement { - padding-right: var(--canon-space-4); +.canon-TextFieldInputIcon { + padding-right: var(--canon-space-1); + width: 1.5rem; + height: 1.5rem; + color: var(--canon-fg-primary); + display: block; } .canon-TextFieldInput { - padding: 0 var(--canon-space-4); font-size: var(--canon-font-size-3); font-family: var(--canon-font-regular); font-weight: var(--canon-font-weight-regular); @@ -597,11 +597,31 @@ transition: border-color .2s ease-in-out, outline-color .2s ease-in-out; } +.canon-TextFieldInput[type="search"]::-webkit-search-cancel-button, .canon-TextFieldInput[type="search"]::-webkit-search-decoration { + appearance: none; +} + +.canon-TextFieldClearButton { + padding: 0 0 0 var(--canon-space-1); + vertical-align: middle; + background: none; + border: none; + display: none; +} + +.canon-TextFieldInput[data-filled] + .canon-TextFieldClearButton { + display: inline-block; +} + +.canon-TextFieldClearButtonIcon { + display: block; +} + .canon-TextFieldInput::placeholder { color: var(--canon-fg-secondary); } -.canon-TextFieldInput:focus-visible { +.canon-TextFieldInput[data-focused] { outline-color: var(--canon-border-pressed); outline-width: 0; } @@ -610,20 +630,24 @@ border-color: var(--canon-border-hover); } -.canon-TextFieldInputWrapper:has( > .canon-TextFieldInput:focus-visible) { +.canon-TextField[data-focused] .canon-TextFieldInputWrapper { border-color: var(--canon-border-pressed); } -.canon-TextFieldInputWrapper:has(.canon-TextFieldInput[data-invalid]) { +.canon-TextField[data-invalid] .canon-TextFieldInputWrapper { border-color: var(--canon-fg-danger); } -.canon-TextFieldInputWrapper:has(.canon-TextFieldInput[data-disabled]) { +.canon-TextField[data-disabled] .canon-TextFieldInputWrapper { opacity: .5; cursor: not-allowed; border: 1px solid var(--canon-border-disabled); } +.canon-TextField[data-disabled] .canon-TextFieldClearButton { + cursor: inherit; +} + .canon-TextFieldInputWrapper[data-size="small"] { height: 2rem; } diff --git a/packages/canon/css/styles.css b/packages/canon/css/styles.css index 282291c735..d1f3ba045d 100644 --- a/packages/canon/css/styles.css +++ b/packages/canon/css/styles.css @@ -9792,6 +9792,7 @@ } .canon-TextFieldInputWrapper { + padding: 0 var(--canon-space-3); border-radius: var(--canon-radius-3); border: 1px solid var(--canon-border); background-color: var(--canon-bg-surface-1); @@ -9799,16 +9800,15 @@ display: flex; } -.canon-TextFieldInputLeftElement { - padding-left: var(--canon-space-4); -} - -.canon-TextFieldInputRightElement { - padding-right: var(--canon-space-4); +.canon-TextFieldInputIcon { + padding-right: var(--canon-space-1); + width: 1.5rem; + height: 1.5rem; + color: var(--canon-fg-primary); + display: block; } .canon-TextFieldInput { - padding: 0 var(--canon-space-4); font-size: var(--canon-font-size-3); font-family: var(--canon-font-regular); font-weight: var(--canon-font-weight-regular); @@ -9821,11 +9821,31 @@ transition: border-color .2s ease-in-out, outline-color .2s ease-in-out; } +.canon-TextFieldInput[type="search"]::-webkit-search-cancel-button, .canon-TextFieldInput[type="search"]::-webkit-search-decoration { + appearance: none; +} + +.canon-TextFieldClearButton { + padding: 0 0 0 var(--canon-space-1); + vertical-align: middle; + background: none; + border: none; + display: none; +} + +.canon-TextFieldInput[data-filled] + .canon-TextFieldClearButton { + display: inline-block; +} + +.canon-TextFieldClearButtonIcon { + display: block; +} + .canon-TextFieldInput::placeholder { color: var(--canon-fg-secondary); } -.canon-TextFieldInput:focus-visible { +.canon-TextFieldInput[data-focused] { outline-color: var(--canon-border-pressed); outline-width: 0; } @@ -9834,20 +9854,24 @@ border-color: var(--canon-border-hover); } -.canon-TextFieldInputWrapper:has( > .canon-TextFieldInput:focus-visible) { +.canon-TextField[data-focused] .canon-TextFieldInputWrapper { border-color: var(--canon-border-pressed); } -.canon-TextFieldInputWrapper:has(.canon-TextFieldInput[data-invalid]) { +.canon-TextField[data-invalid] .canon-TextFieldInputWrapper { border-color: var(--canon-fg-danger); } -.canon-TextFieldInputWrapper:has(.canon-TextFieldInput[data-disabled]) { +.canon-TextField[data-disabled] .canon-TextFieldInputWrapper { opacity: .5; cursor: not-allowed; border: 1px solid var(--canon-border-disabled); } +.canon-TextField[data-disabled] .canon-TextFieldClearButton { + cursor: inherit; +} + .canon-TextFieldInputWrapper[data-size="small"] { height: 2rem; } diff --git a/packages/canon/css/textfield.css b/packages/canon/css/textfield.css index 511873980d..d55b5b6f2c 100644 --- a/packages/canon/css/textfield.css +++ b/packages/canon/css/textfield.css @@ -35,6 +35,7 @@ } .canon-TextFieldInputWrapper { + padding: 0 var(--canon-space-3); border-radius: var(--canon-radius-3); border: 1px solid var(--canon-border); background-color: var(--canon-bg-surface-1); @@ -42,16 +43,15 @@ display: flex; } -.canon-TextFieldInputLeftElement { - padding-left: var(--canon-space-4); -} - -.canon-TextFieldInputRightElement { - padding-right: var(--canon-space-4); +.canon-TextFieldInputIcon { + padding-right: var(--canon-space-1); + width: 1.5rem; + height: 1.5rem; + color: var(--canon-fg-primary); + display: block; } .canon-TextFieldInput { - padding: 0 var(--canon-space-4); font-size: var(--canon-font-size-3); font-family: var(--canon-font-regular); font-weight: var(--canon-font-weight-regular); @@ -64,11 +64,31 @@ transition: border-color .2s ease-in-out, outline-color .2s ease-in-out; } +.canon-TextFieldInput[type="search"]::-webkit-search-cancel-button, .canon-TextFieldInput[type="search"]::-webkit-search-decoration { + appearance: none; +} + +.canon-TextFieldClearButton { + padding: 0 0 0 var(--canon-space-1); + vertical-align: middle; + background: none; + border: none; + display: none; +} + +.canon-TextFieldInput[data-filled] + .canon-TextFieldClearButton { + display: inline-block; +} + +.canon-TextFieldClearButtonIcon { + display: block; +} + .canon-TextFieldInput::placeholder { color: var(--canon-fg-secondary); } -.canon-TextFieldInput:focus-visible { +.canon-TextFieldInput[data-focused] { outline-color: var(--canon-border-pressed); outline-width: 0; } @@ -77,20 +97,24 @@ border-color: var(--canon-border-hover); } -.canon-TextFieldInputWrapper:has( > .canon-TextFieldInput:focus-visible) { +.canon-TextField[data-focused] .canon-TextFieldInputWrapper { border-color: var(--canon-border-pressed); } -.canon-TextFieldInputWrapper:has(.canon-TextFieldInput[data-invalid]) { +.canon-TextField[data-invalid] .canon-TextFieldInputWrapper { border-color: var(--canon-fg-danger); } -.canon-TextFieldInputWrapper:has(.canon-TextFieldInput[data-disabled]) { +.canon-TextField[data-disabled] .canon-TextFieldInputWrapper { opacity: .5; cursor: not-allowed; border: 1px solid var(--canon-border-disabled); } +.canon-TextField[data-disabled] .canon-TextFieldClearButton { + cursor: inherit; +} + .canon-TextFieldInputWrapper[data-size="small"] { height: 2rem; } diff --git a/packages/canon/report.api.md b/packages/canon/report.api.md index a1dc9f591a..da1b5015b4 100644 --- a/packages/canon/report.api.md +++ b/packages/canon/report.api.md @@ -1220,10 +1220,10 @@ export interface TextFieldProps className?: string; description?: string; error?: string | null; + icon?: IconNames; label?: string; - leftElementProps?: React.ComponentPropsWithoutRef<'div'>; name: string; - rightElementProps?: React.ComponentPropsWithoutRef<'div'>; + onClear?: React.MouseEventHandler; size?: 'small' | 'medium' | Partial>; } diff --git a/packages/canon/src/components/TextField/TextField.stories.tsx b/packages/canon/src/components/TextField/TextField.stories.tsx index 8c876eab27..df2eb63be3 100644 --- a/packages/canon/src/components/TextField/TextField.stories.tsx +++ b/packages/canon/src/components/TextField/TextField.stories.tsx @@ -15,27 +15,8 @@ */ import type { Meta, StoryObj } from '@storybook/react'; -import type { ComponentPropsWithoutRef } from 'react'; import { TextField } from './TextField'; import { Flex } from '../Flex'; -import { Icon } from '../Icon'; - -const CloseButton = (props: ComponentPropsWithoutRef<'button'>) => { - return ( - - ); -}; const meta = { title: 'Components/TextField', @@ -129,33 +110,34 @@ export const WithErrorAndDescription: Story = { }, }; -export const WithLeftAndRightElements: Story = { +export const WithIcon: Story = { args: { ...WithLabel.args, placeholder: 'Search...', - leftElementProps: { - children: , - }, - rightElementProps: { - children: , - }, + icon: 'search', }, }; -export const WithLeftAndRightElementsAndHelpText: Story = { +export const DisabledWithIcon: Story = { args: { - ...WithLeftAndRightElements.args, - error: 'Failed to search', - description: 'Enter some text to search', - }, -}; - -export const DisabledWithLeftAndRightElements: Story = { - args: { - ...WithLeftAndRightElements.args, + ...WithIcon.args, + disabled: true, + }, +}; + +export const WithOnClear: Story = { + args: { + ...WithLabel.args, + placeholder: 'Search...', + type: 'search', + onClear: () => null, + }, +}; + +export const DisabledWithOnClear: Story = { + args: { + ...WithOnClear.args, + defaultValue: 'Testing', disabled: true, - rightElementProps: { - children: , - }, }, }; diff --git a/packages/canon/src/components/TextField/TextField.styles.css b/packages/canon/src/components/TextField/TextField.styles.css index 3830f084b2..ae901c5cc1 100644 --- a/packages/canon/src/components/TextField/TextField.styles.css +++ b/packages/canon/src/components/TextField/TextField.styles.css @@ -59,12 +59,12 @@ background-color: var(--canon-bg-surface-1); } -.canon-TextFieldInputLeftElement { +.canon-TextFieldInputIcon { + display: block; padding-right: var(--canon-space-1); -} - -.canon-TextFieldInputRightElement { - padding-left: var(--canon-space-1); + width: 1.5rem; + height: 1.5rem; + color: var(--canon-fg-primary); } .canon-TextFieldInput { @@ -80,11 +80,32 @@ cursor: inherit; } +.canon-TextFieldInput[type='search']::-webkit-search-cancel-button, +.canon-TextFieldInput[type='search']::-webkit-search-decoration { + appearance: none; +} + +.canon-TextFieldClearButton { + display: none; + padding: 0 0 0 var(--canon-space-1); + background: none; + border: none; + vertical-align: middle; +} + +.canon-TextFieldInput[data-filled] + .canon-TextFieldClearButton { + display: inline-block; +} + +.canon-TextFieldClearButtonIcon { + display: block; +} + .canon-TextFieldInput::placeholder { color: var(--canon-fg-secondary); } -.canon-TextFieldInput:focus-visible { +.canon-TextFieldInput[data-focused] { outline-color: var(--canon-border-pressed); outline-width: 0px; } @@ -93,20 +114,24 @@ border-color: var(--canon-border-hover); } -.canon-TextFieldInputWrapper:has(> .canon-TextFieldInput:focus-visible) { +.canon-TextField[data-focused] .canon-TextFieldInputWrapper { border-color: var(--canon-border-pressed); } -.canon-TextFieldInputWrapper:has(.canon-TextFieldInput[data-invalid]) { +.canon-TextField[data-invalid] .canon-TextFieldInputWrapper { border-color: var(--canon-fg-danger); } -.canon-TextFieldInputWrapper:has(.canon-TextFieldInput[data-disabled]) { +.canon-TextField[data-disabled] .canon-TextFieldInputWrapper { opacity: 0.5; cursor: not-allowed; border: 1px solid var(--canon-border-disabled); } +.canon-TextField[data-disabled] .canon-TextFieldClearButton { + cursor: inherit; +} + .canon-TextFieldInputWrapper[data-size='small'] { height: 2rem; } diff --git a/packages/canon/src/components/TextField/TextField.tsx b/packages/canon/src/components/TextField/TextField.tsx index 83640c153a..bb32033da4 100644 --- a/packages/canon/src/components/TextField/TextField.tsx +++ b/packages/canon/src/components/TextField/TextField.tsx @@ -20,6 +20,7 @@ import { useResponsiveValue } from '../../hooks/useResponsiveValue'; import clsx from 'clsx'; import type { TextFieldProps } from './types'; +import { Icon } from '../Icon'; /** @public */ export const TextField = forwardRef( @@ -33,8 +34,8 @@ export const TextField = forwardRef( required, style, disabled, - leftElementProps, - rightElementProps, + icon, + onClear, ...rest } = props; @@ -60,29 +61,21 @@ export const TextField = forwardRef( )}
- {leftElementProps ? ( -
- ) : null} + {icon && } - {rightElementProps ? ( -
- ) : null} + {onClear && ( + + )}
{description && ( diff --git a/packages/canon/src/components/TextField/types.ts b/packages/canon/src/components/TextField/types.ts index fbec9fde4b..8111749845 100644 --- a/packages/canon/src/components/TextField/types.ts +++ b/packages/canon/src/components/TextField/types.ts @@ -15,6 +15,7 @@ */ import type { Breakpoint } from '../../types'; +import type { IconNames } from '../Icon'; /** @public */ export interface TextFieldProps @@ -51,12 +52,12 @@ export interface TextFieldProps error?: string | null; /** - * Props for an element to render on the left of the input + * An icon to render before the input */ - leftElementProps?: React.ComponentPropsWithoutRef<'div'>; + icon?: IconNames; /** - * Props for an element to render on the right of the input + * Handler to call when the clear button is pressed */ - rightElementProps?: React.ComponentPropsWithoutRef<'div'>; + onClear?: React.MouseEventHandler; } From 35a0c4b598e95ffa77070cd5cda638dbddfdf16c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 6 May 2025 13:51:57 +0000 Subject: [PATCH 099/109] Version Packages (next) --- .changeset/pre.json | 19 + docs/releases/v1.39.0-next.2-changelog.md | 1505 +++++++++++++++++ package.json | 2 +- packages/app-defaults/CHANGELOG.md | 11 + packages/app-defaults/package.json | 2 +- packages/app-next-example-plugin/CHANGELOG.md | 8 + packages/app-next-example-plugin/package.json | 2 +- packages/app-next/CHANGELOG.md | 46 + packages/app-next/package.json | 2 +- packages/app/CHANGELOG.md | 42 + packages/app/package.json | 2 +- packages/backend-defaults/CHANGELOG.md | 20 + packages/backend-defaults/package.json | 2 +- .../CHANGELOG.md | 25 + .../package.json | 2 +- packages/backend-test-utils/CHANGELOG.md | 14 + packages/backend-test-utils/package.json | 2 +- packages/backend/CHANGELOG.md | 39 + packages/backend/package.json | 2 +- packages/canon/CHANGELOG.md | 8 + packages/canon/package.json | 2 +- packages/cli/CHANGELOG.md | 16 + packages/cli/package.json | 2 +- packages/core-app-api/CHANGELOG.md | 11 + packages/core-app-api/package.json | 2 +- packages/core-compat-api/CHANGELOG.md | 10 + packages/core-compat-api/package.json | 2 +- packages/core-components/CHANGELOG.md | 11 + packages/core-components/package.json | 2 +- packages/core-plugin-api/CHANGELOG.md | 11 + packages/core-plugin-api/package.json | 2 +- packages/create-app/CHANGELOG.md | 8 + packages/create-app/package.json | 2 +- packages/dev-utils/CHANGELOG.md | 14 + packages/dev-utils/package.json | 2 +- packages/e2e-test/CHANGELOG.md | 9 + packages/e2e-test/package.json | 2 +- packages/frontend-app-api/CHANGELOG.md | 14 + packages/frontend-app-api/package.json | 2 +- packages/frontend-defaults/CHANGELOG.md | 11 + packages/frontend-defaults/package.json | 2 +- .../CHANGELOG.md | 8 + .../package.json | 2 +- packages/frontend-internal/CHANGELOG.md | 9 + packages/frontend-internal/package.json | 2 +- packages/frontend-plugin-api/CHANGELOG.md | 10 + packages/frontend-plugin-api/package.json | 2 +- packages/frontend-test-utils/CHANGELOG.md | 13 + packages/frontend-test-utils/package.json | 2 +- packages/integration-aws-node/CHANGELOG.md | 9 + packages/integration-aws-node/package.json | 2 +- packages/integration-react/CHANGELOG.md | 9 + packages/integration-react/package.json | 2 +- packages/integration/CHANGELOG.md | 14 + packages/integration/package.json | 2 +- packages/repo-tools/CHANGELOG.md | 13 + packages/repo-tools/package.json | 2 +- packages/scaffolder-internal/CHANGELOG.md | 8 + packages/scaffolder-internal/package.json | 2 +- .../techdocs-cli-embedded-app/CHANGELOG.md | 19 + .../techdocs-cli-embedded-app/package.json | 2 +- packages/techdocs-cli/CHANGELOG.md | 11 + packages/techdocs-cli/package.json | 2 +- packages/test-utils/CHANGELOG.md | 14 + packages/test-utils/package.json | 2 +- plugins/api-docs/CHANGELOG.md | 15 + plugins/api-docs/package.json | 2 +- plugins/app-visualizer/CHANGELOG.md | 9 + plugins/app-visualizer/package.json | 2 +- plugins/app/CHANGELOG.md | 13 + plugins/app/package.json | 2 +- plugins/auth-react/CHANGELOG.md | 9 + plugins/auth-react/package.json | 2 +- plugins/bitbucket-cloud-common/CHANGELOG.md | 7 + plugins/bitbucket-cloud-common/package.json | 2 +- .../catalog-backend-module-aws/CHANGELOG.md | 16 + .../catalog-backend-module-aws/package.json | 2 +- .../catalog-backend-module-azure/CHANGELOG.md | 11 + .../catalog-backend-module-azure/package.json | 2 +- .../CHANGELOG.md | 15 + .../package.json | 2 +- .../CHANGELOG.md | 15 + .../package.json | 2 +- .../CHANGELOG.md | 12 + .../package.json | 2 +- .../CHANGELOG.md | 11 + .../package.json | 2 +- .../CHANGELOG.md | 15 + .../package.json | 2 +- .../CHANGELOG.md | 10 + .../package.json | 2 +- .../CHANGELOG.md | 14 + .../package.json | 2 +- .../CHANGELOG.md | 16 + .../package.json | 2 +- .../catalog-backend-module-ldap/CHANGELOG.md | 14 + .../catalog-backend-module-ldap/package.json | 2 +- .../catalog-backend-module-logs/CHANGELOG.md | 9 + .../catalog-backend-module-logs/package.json | 2 +- .../CHANGELOG.md | 12 + .../package.json | 2 +- plugins/catalog-backend/CHANGELOG.md | 19 + plugins/catalog-backend/package.json | 2 +- plugins/catalog-graph/CHANGELOG.md | 14 + plugins/catalog-graph/package.json | 2 +- plugins/catalog-import/CHANGELOG.md | 23 + plugins/catalog-import/package.json | 2 +- plugins/catalog-react/CHANGELOG.md | 20 + plugins/catalog-react/package.json | 2 +- .../catalog-unprocessed-entities/CHANGELOG.md | 12 + .../catalog-unprocessed-entities/package.json | 2 +- plugins/catalog/CHANGELOG.md | 23 + plugins/catalog/package.json | 2 +- plugins/config-schema/CHANGELOG.md | 10 + plugins/config-schema/package.json | 2 +- plugins/devtools-backend/CHANGELOG.md | 16 + plugins/devtools-backend/package.json | 2 +- plugins/devtools/CHANGELOG.md | 13 + plugins/devtools/package.json | 2 +- .../events-backend-module-github/CHANGELOG.md | 11 + .../events-backend-module-github/package.json | 2 +- plugins/example-todo-list/CHANGELOG.md | 8 + plugins/example-todo-list/package.json | 2 +- plugins/home-react/CHANGELOG.md | 8 + plugins/home-react/package.json | 2 +- plugins/home/CHANGELOG.md | 17 + plugins/home/package.json | 2 +- plugins/kubernetes-backend/CHANGELOG.md | 19 + plugins/kubernetes-backend/package.json | 2 +- plugins/kubernetes-cluster/CHANGELOG.md | 13 + plugins/kubernetes-cluster/package.json | 2 +- plugins/kubernetes-react/CHANGELOG.md | 12 + plugins/kubernetes-react/package.json | 2 +- plugins/kubernetes/CHANGELOG.md | 15 + plugins/kubernetes/package.json | 2 +- .../CHANGELOG.md | 15 + .../package.json | 2 +- .../CHANGELOG.md | 15 + .../package.json | 2 +- plugins/notifications/CHANGELOG.md | 15 + plugins/notifications/package.json | 2 +- plugins/org-react/CHANGELOG.md | 11 + plugins/org-react/package.json | 2 +- plugins/org/CHANGELOG.md | 13 + plugins/org/package.json | 2 +- plugins/permission-react/CHANGELOG.md | 9 + plugins/permission-react/package.json | 2 +- .../CHANGELOG.md | 11 + .../package.json | 2 +- .../CHANGELOG.md | 12 + .../package.json | 2 +- .../CHANGELOG.md | 11 + .../package.json | 2 +- .../CHANGELOG.md | 13 + .../package.json | 2 +- .../CHANGELOG.md | 11 + .../package.json | 2 +- .../CHANGELOG.md | 13 + .../package.json | 2 +- .../CHANGELOG.md | 11 + .../package.json | 2 +- .../CHANGELOG.md | 11 + .../package.json | 2 +- .../CHANGELOG.md | 11 + .../package.json | 2 +- .../CHANGELOG.md | 14 + .../package.json | 2 +- .../CHANGELOG.md | 11 + .../package.json | 2 +- .../CHANGELOG.md | 11 + .../package.json | 2 +- .../CHANGELOG.md | 12 + .../package.json | 2 +- .../CHANGELOG.md | 10 + .../package.json | 2 +- .../CHANGELOG.md | 10 + .../package.json | 2 +- plugins/scaffolder-backend/CHANGELOG.md | 31 + plugins/scaffolder-backend/package.json | 2 +- .../scaffolder-node-test-utils/CHANGELOG.md | 10 + .../scaffolder-node-test-utils/package.json | 2 +- plugins/scaffolder-node/CHANGELOG.md | 12 + plugins/scaffolder-node/package.json | 2 +- plugins/scaffolder-react/CHANGELOG.md | 17 + plugins/scaffolder-react/package.json | 2 +- plugins/scaffolder/CHANGELOG.md | 21 + plugins/scaffolder/package.json | 2 +- .../CHANGELOG.md | 11 + .../package.json | 2 +- .../CHANGELOG.md | 16 + .../package.json | 2 +- plugins/search-backend/CHANGELOG.md | 16 + plugins/search-backend/package.json | 2 +- plugins/search-react/CHANGELOG.md | 13 + plugins/search-react/package.json | 2 +- plugins/search/CHANGELOG.md | 16 + plugins/search/package.json | 2 +- plugins/signals-react/CHANGELOG.md | 8 + plugins/signals-react/package.json | 2 +- plugins/signals/CHANGELOG.md | 12 + plugins/signals/package.json | 2 +- .../techdocs-addons-test-utils/CHANGELOG.md | 15 + .../techdocs-addons-test-utils/package.json | 2 +- plugins/techdocs-backend/CHANGELOG.md | 19 + plugins/techdocs-backend/package.json | 2 +- .../CHANGELOG.md | 12 + .../package.json | 2 +- plugins/techdocs-node/CHANGELOG.md | 14 + plugins/techdocs-node/package.json | 2 +- plugins/techdocs-react/CHANGELOG.md | 12 + plugins/techdocs-react/package.json | 2 +- plugins/techdocs/CHANGELOG.md | 25 + plugins/techdocs/package.json | 2 +- plugins/user-settings-backend/CHANGELOG.md | 13 + plugins/user-settings-backend/package.json | 2 +- plugins/user-settings/CHANGELOG.md | 18 + plugins/user-settings/package.json | 2 +- yarn.lock | 70 +- 218 files changed, 3201 insertions(+), 111 deletions(-) create mode 100644 docs/releases/v1.39.0-next.2-changelog.md diff --git a/.changeset/pre.json b/.changeset/pre.json index e980d2a115..51d53067bd 100644 --- a/.changeset/pre.json +++ b/.changeset/pre.json @@ -203,11 +203,13 @@ "changesets": [ "angry-sites-fold", "beige-kiwis-flow", + "brave-donuts-sink", "brave-eggs-mate", "brave-toes-switch", "breezy-hotels-deny", "bright-moles-sort", "bumpy-showers-design", + "busy-badgers-hang", "calm-toys-occur", "chatty-showers-cheat", "chilly-trams-cheer", @@ -215,12 +217,16 @@ "chubby-needles-vanish", "cold-humans-check", "common-goats-raise", + "cool-bikes-push", "cool-cities-grab", + "cool-colts-float", "cool-knives-design", + "crazy-chefs-sin", "create-app-1745325336", "create-app-1745936753", "cruel-lights-sip", "cyan-pots-appear", + "deep-ties-move", "dirty-grapes-vanish", "dry-carpets-hope", "dull-doodles-trade", @@ -239,15 +245,20 @@ "heavy-onions-swim", "huge-olives-do", "icy-mugs-glow", + "khaki-grapes-sink", "large-experts-sort", "large-lemons-clap", "lazy-tires-show", "lovely-cats-take", + "mean-parents-build", "mighty-carrots-decide", + "neat-glasses-occur", + "neat-glasses-occured", "new-hands-scream", "nice-vans-vanish", "old-crews-serve", "open-ghosts-fix", + "open-lands-shop", "pretty-corners-speak", "pretty-seas-hug", "public-socks-agree", @@ -267,15 +278,23 @@ "silent-clubs-roll", "slick-brooms-start", "slimy-peas-post", + "slow-drinks-enjoy", "small-eggs-develop", "spicy-steaks-swim", "spotty-doors-design", "stale-symbols-joke", + "tame-areas-behave", + "ten-spies-explode", "ten-tables-build", + "thick-hotels-rhyme", "true-breads-rhyme", "twenty-kiwis-punch", + "warm-cases-bathe", "warm-llamas-return", + "wicked-dingos-stand", + "wise-cobras-sink", "wise-pillows-smile", + "yellow-beans-eat", "yellow-cows-tickle" ] } diff --git a/docs/releases/v1.39.0-next.2-changelog.md b/docs/releases/v1.39.0-next.2-changelog.md new file mode 100644 index 0000000000..7a7b9dc4ed --- /dev/null +++ b/docs/releases/v1.39.0-next.2-changelog.md @@ -0,0 +1,1505 @@ +# Release v1.39.0-next.2 + +Upgrade Helper: [https://backstage.github.io/upgrade-helper/?to=1.39.0-next.2](https://backstage.github.io/upgrade-helper/?to=1.39.0-next.2) + +## @backstage/integration@1.17.0-next.2 + +### Minor Changes + +- f134cea: Implement Edit URL feature for Gerrit 3.9+. + + It's possible to disable the edit url by adding the `disableEditUrl: true` config in the Gerrit integration. + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + +## @backstage/plugin-catalog-import@0.13.0-next.2 + +### Minor Changes + +- e2fd549: **BREAKING**: `generateStepper` and `defaultGenerateStepper` now require a translation argument to be passed through for supporting translations. + +### Patch Changes + +- 66a1140: Add i18n support for `catalog-import` plugin. +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/config@1.3.2 + - @backstage/core-compat-api@0.4.2-next.2 + - @backstage/core-components@0.17.2-next.1 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/integration-react@1.2.7-next.2 + - @backstage/plugin-catalog-react@1.18.0-next.2 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-common@1.1.4-next.0 + +## @backstage/app-defaults@1.6.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.16.2-next.0 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/core-components@0.17.2-next.1 + - @backstage/plugin-permission-react@0.4.34-next.1 + - @backstage/theme@0.6.6-next.0 + +## @backstage/backend-defaults@0.10.0-next.2 + +### Patch Changes + +- 0e7a640: The `GithubUrlReader` will now use the token from `options` when fetching repo details +- Updated dependencies + - @backstage/integration-aws-node@0.1.16-next.0 + - @backstage/integration@1.17.0-next.2 + - @backstage/cli-node@0.2.13 + - @backstage/config@1.3.2 + - @backstage/backend-app-api@1.2.3-next.1 + - @backstage/plugin-auth-node@0.6.3-next.1 + - @backstage/plugin-permission-node@0.10.0-next.1 + - @backstage/backend-dev-utils@0.1.5 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/config-loader@1.10.1-next.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-events-node@0.4.11-next.1 + +## @backstage/backend-dynamic-feature-service@0.7.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.10.0-next.2 + - @backstage/backend-openapi-utils@0.5.3-next.1 + - @backstage/cli-node@0.2.13 + - @backstage/config@1.3.2 + - @backstage/plugin-catalog-backend@2.0.0-next.2 + - @backstage/plugin-events-backend@0.5.2-next.1 + - @backstage/plugin-scaffolder-node@0.8.2-next.2 + - @backstage/plugin-auth-node@0.6.3-next.1 + - @backstage/plugin-permission-node@0.10.0-next.1 + - @backstage/plugin-search-backend-node@1.3.11-next.1 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/cli-common@0.1.15 + - @backstage/config-loader@1.10.1-next.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-app-node@0.1.33-next.1 + - @backstage/plugin-events-node@0.4.11-next.1 + - @backstage/plugin-permission-common@0.9.0-next.0 + - @backstage/plugin-search-common@1.2.18-next.0 + +## @backstage/backend-test-utils@1.5.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.10.0-next.2 + - @backstage/config@1.3.2 + - @backstage/backend-app-api@1.2.3-next.1 + - @backstage/plugin-auth-node@0.6.3-next.1 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-events-node@0.4.11-next.1 + +## @backstage/canon@0.4.0-next.2 + +### Patch Changes + +- 6189bfd: Added new icon and onClear props to the TextField to make it easier to accessorize inputs. +- 97b25a1: Pin version of @base-ui-components/react. +- 185d3a8: Use the Field component from Base UI within the TextField. + +## @backstage/cli@0.32.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/cli-node@0.2.13 + - @backstage/config@1.3.2 + - @backstage/release-manifests@0.0.12 + - @backstage/catalog-model@1.7.3 + - @backstage/cli-common@0.1.15 + - @backstage/config-loader@1.10.1-next.0 + - @backstage/errors@1.2.7 + - @backstage/eslint-plugin@0.1.10 + - @backstage/types@1.2.1 + +## @backstage/core-app-api@1.16.2-next.0 + +### Patch Changes + +- 73f6cc3: Updated `I18nextTranslationApi` to support interpolation of JSX elements. +- Updated dependencies + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/config@1.3.2 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + +## @backstage/core-compat-api@0.4.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/plugin-catalog-react@1.18.0-next.2 + - @backstage/version-bridge@1.0.11 + +## @backstage/core-components@0.17.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/theme@0.6.6-next.0 + - @backstage/version-bridge@1.0.11 + +## @backstage/core-plugin-api@1.10.7-next.0 + +### Patch Changes + +- 73f6cc3: The `TranslationApi` now supports interpolation of JSX elements by passing them directly as values to the translation function. If any of the provided interpolation values are JSX elements, the translation function will return a JSX element instead of a string. +- Updated dependencies + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + +## @backstage/create-app@0.6.2-next.2 + +### Patch Changes + +- 8448948: Removed `lerna-debug.log*` pattern from `.gitignore` as Lerna was removed from the package in version `@backstage/create-app@0.5.19`. +- Updated dependencies + - @backstage/cli-common@0.1.15 + +## @backstage/dev-utils@1.1.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.16.2-next.0 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/app-defaults@1.6.2-next.1 + - @backstage/core-components@0.17.2-next.1 + - @backstage/integration-react@1.2.7-next.2 + - @backstage/plugin-catalog-react@1.18.0-next.2 + - @backstage/catalog-model@1.7.3 + - @backstage/theme@0.6.6-next.0 + +## @backstage/frontend-app-api@0.11.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.16.2-next.0 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/config@1.3.2 + - @backstage/frontend-defaults@0.2.2-next.2 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + +## @backstage/frontend-defaults@0.2.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.2 + - @backstage/frontend-app-api@0.11.2-next.2 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/plugin-app@0.1.9-next.2 + - @backstage/errors@1.2.7 + +## @backstage/frontend-dynamic-feature-loader@0.1.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.2 + - @backstage/frontend-plugin-api@0.10.2-next.1 + +## @backstage/frontend-plugin-api@0.10.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/core-components@0.17.2-next.1 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + +## @backstage/frontend-test-utils@0.3.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/test-utils@1.7.8-next.1 + - @backstage/config@1.3.2 + - @backstage/frontend-app-api@0.11.2-next.2 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/plugin-app@0.1.9-next.2 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + +## @backstage/integration-aws-node@0.1.16-next.0 + +### Patch Changes + +- db4630e: Fixed bug in DefaultAwsCredentialsManager where aws.mainAccount.region has no effect on the STS region used for account ID lookup during credential provider lookup when falling back to the main account, and it does not default to us-east-1 +- Updated dependencies + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + +## @backstage/integration-react@1.2.7-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/config@1.3.2 + +## @backstage/repo-tools@0.13.3-next.2 + +### Patch Changes + +- b229476: Support passing additional properties to OpenAPI server generator +- Updated dependencies + - @backstage/cli-node@0.2.13 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/catalog-model@1.7.3 + - @backstage/cli-common@0.1.15 + - @backstage/config-loader@1.10.1-next.0 + - @backstage/errors@1.2.7 + +## @techdocs/cli@1.9.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.10.0-next.2 + - @backstage/config@1.3.2 + - @backstage/plugin-techdocs-node@1.13.3-next.2 + - @backstage/catalog-model@1.7.3 + - @backstage/cli-common@0.1.15 + +## @backstage/test-utils@1.7.8-next.1 + +### Patch Changes + +- b573341: Added support for interpolating JSX elements with the `MockTranslationApi`. +- Updated dependencies + - @backstage/core-app-api@1.16.2-next.0 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/config@1.3.2 + - @backstage/plugin-permission-react@0.4.34-next.1 + - @backstage/theme@0.6.6-next.0 + - @backstage/types@1.2.1 + - @backstage/plugin-permission-common@0.9.0-next.0 + +## @backstage/plugin-api-docs@0.12.7-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/plugin-catalog@1.29.1-next.2 + - @backstage/core-compat-api@0.4.2-next.2 + - @backstage/core-components@0.17.2-next.1 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/plugin-catalog-react@1.18.0-next.2 + - @backstage/plugin-permission-react@0.4.34-next.1 + - @backstage/catalog-model@1.7.3 + - @backstage/plugin-catalog-common@1.1.4-next.0 + +## @backstage/plugin-app@0.1.9-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/core-components@0.17.2-next.1 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/integration-react@1.2.7-next.2 + - @backstage/plugin-permission-react@0.4.34-next.1 + - @backstage/theme@0.6.6-next.0 + - @backstage/types@1.2.1 + +## @backstage/plugin-app-visualizer@0.1.19-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/core-components@0.17.2-next.1 + - @backstage/frontend-plugin-api@0.10.2-next.1 + +## @backstage/plugin-auth-react@0.1.15-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/core-components@0.17.2-next.1 + - @backstage/errors@1.2.7 + +## @backstage/plugin-bitbucket-cloud-common@0.3.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + +## @backstage/plugin-catalog@1.29.1-next.2 + +### Patch Changes + +- bf85d37: Fix for missing `routeRef` when using `core-plugin-api` in a dialog context +- Updated dependencies + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/core-compat-api@0.4.2-next.2 + - @backstage/core-components@0.17.2-next.1 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/integration-react@1.2.7-next.2 + - @backstage/plugin-catalog-react@1.18.0-next.2 + - @backstage/plugin-permission-react@0.4.34-next.1 + - @backstage/plugin-search-react@1.9.0-next.1 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-catalog-common@1.1.4-next.0 + - @backstage/plugin-scaffolder-common@1.5.11-next.0 + - @backstage/plugin-search-common@1.2.18-next.0 + +## @backstage/plugin-catalog-backend@2.0.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/backend-openapi-utils@0.5.3-next.1 + - @backstage/config@1.3.2 + - @backstage/plugin-permission-node@0.10.0-next.1 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-catalog-common@1.1.4-next.0 + - @backstage/plugin-catalog-node@1.17.0-next.1 + - @backstage/plugin-events-node@0.4.11-next.1 + - @backstage/plugin-permission-common@0.9.0-next.0 + +## @backstage/plugin-catalog-backend-module-aws@0.4.11-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration-aws-node@0.1.16-next.0 + - @backstage/integration@1.17.0-next.2 + - @backstage/backend-defaults@0.10.0-next.2 + - @backstage/config@1.3.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/catalog-model@1.7.3 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-common@1.1.4-next.0 + - @backstage/plugin-catalog-node@1.17.0-next.1 + - @backstage/plugin-kubernetes-common@0.9.5-next.0 + +## @backstage/plugin-catalog-backend-module-azure@0.3.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/config@1.3.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/plugin-catalog-common@1.1.4-next.0 + - @backstage/plugin-catalog-node@1.17.0-next.1 + +## @backstage/plugin-catalog-backend-module-bitbucket-cloud@0.4.8-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/config@1.3.2 + - @backstage/plugin-bitbucket-cloud-common@0.3.0-next.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/plugin-catalog-common@1.1.4-next.0 + - @backstage/plugin-catalog-node@1.17.0-next.1 + - @backstage/plugin-events-node@0.4.11-next.1 + +## @backstage/plugin-catalog-backend-module-bitbucket-server@0.4.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/config@1.3.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-common@1.1.4-next.0 + - @backstage/plugin-catalog-node@1.17.0-next.1 + - @backstage/plugin-events-node@0.4.11-next.1 + +## @backstage/plugin-catalog-backend-module-gerrit@0.3.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/config@1.3.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-common@1.1.4-next.0 + - @backstage/plugin-catalog-node@1.17.0-next.1 + +## @backstage/plugin-catalog-backend-module-github@0.9.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/config@1.3.2 + - @backstage/plugin-catalog-backend@2.0.0-next.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/plugin-catalog-common@1.1.4-next.0 + - @backstage/plugin-catalog-node@1.17.0-next.1 + - @backstage/plugin-events-node@0.4.11-next.1 + +## @backstage/plugin-catalog-backend-module-github-org@0.3.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.2 + - @backstage/plugin-catalog-backend-module-github@0.9.0-next.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/plugin-catalog-node@1.17.0-next.1 + - @backstage/plugin-events-node@0.4.11-next.1 + +## @backstage/plugin-catalog-backend-module-gitlab@0.6.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/backend-defaults@0.10.0-next.2 + - @backstage/config@1.3.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/catalog-model@1.7.3 + - @backstage/plugin-catalog-common@1.1.4-next.0 + - @backstage/plugin-catalog-node@1.17.0-next.1 + - @backstage/plugin-events-node@0.4.11-next.1 + +## @backstage/plugin-catalog-backend-module-gitlab-org@0.2.9-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend-module-gitlab@0.6.6-next.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/plugin-catalog-node@1.17.0-next.1 + - @backstage/plugin-events-node@0.4.11-next.1 + +## @backstage/plugin-catalog-backend-module-incremental-ingestion@0.7.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.10.0-next.2 + - @backstage/config@1.3.2 + - @backstage/plugin-catalog-backend@2.0.0-next.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/catalog-model@1.7.3 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-catalog-node@1.17.0-next.1 + - @backstage/plugin-events-node@0.4.11-next.1 + - @backstage/plugin-permission-common@0.9.0-next.0 + +## @backstage/plugin-catalog-backend-module-ldap@0.11.5-next.2 + +### Patch Changes + +- e253d1d: Improves error reporting for missing metadata.name in LDAP catalog provider. +- Updated dependencies + - @backstage/config@1.3.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/catalog-model@1.7.3 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-catalog-common@1.1.4-next.0 + - @backstage/plugin-catalog-node@1.17.0-next.1 + +## @backstage/plugin-catalog-backend-module-logs@0.1.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@2.0.0-next.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/plugin-events-node@0.4.11-next.1 + +## @backstage/plugin-catalog-backend-module-openapi@0.2.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/catalog-model@1.7.3 + - @backstage/types@1.2.1 + - @backstage/plugin-catalog-common@1.1.4-next.0 + - @backstage/plugin-catalog-node@1.17.0-next.1 + +## @backstage/plugin-catalog-graph@0.4.19-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/core-compat-api@0.4.2-next.2 + - @backstage/core-components@0.17.2-next.1 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/plugin-catalog-react@1.18.0-next.2 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/types@1.2.1 + +## @backstage/plugin-catalog-react@1.18.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/core-compat-api@0.4.2-next.2 + - @backstage/core-components@0.17.2-next.1 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/frontend-test-utils@0.3.2-next.2 + - @backstage/integration-react@1.2.7-next.2 + - @backstage/plugin-permission-react@0.4.34-next.1 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-catalog-common@1.1.4-next.0 + - @backstage/plugin-permission-common@0.9.0-next.0 + +## @backstage/plugin-catalog-unprocessed-entities@0.2.17-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/core-compat-api@0.4.2-next.2 + - @backstage/core-components@0.17.2-next.1 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/catalog-model@1.7.3 + - @backstage/errors@1.2.7 + +## @backstage/plugin-config-schema@0.1.68-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/core-components@0.17.2-next.1 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## @backstage/plugin-devtools@0.1.27-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/core-compat-api@0.4.2-next.2 + - @backstage/core-components@0.17.2-next.1 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/plugin-permission-react@0.4.34-next.1 + - @backstage/errors@1.2.7 + - @backstage/plugin-devtools-common@0.1.16-next.0 + +## @backstage/plugin-devtools-backend@0.5.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.10.0-next.2 + - @backstage/config@1.3.2 + - @backstage/plugin-permission-node@0.10.0-next.1 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/cli-common@0.1.15 + - @backstage/config-loader@1.10.1-next.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-devtools-common@0.1.16-next.0 + - @backstage/plugin-permission-common@0.9.0-next.0 + +## @backstage/plugin-events-backend-module-github@0.4.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/config@1.3.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/types@1.2.1 + - @backstage/plugin-events-node@0.4.11-next.1 + +## @backstage/plugin-home@0.8.8-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.16.2-next.0 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/config@1.3.2 + - @backstage/core-compat-api@0.4.2-next.2 + - @backstage/core-components@0.17.2-next.1 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/plugin-catalog-react@1.18.0-next.2 + - @backstage/plugin-home-react@0.1.26-next.1 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/theme@0.6.6-next.0 + +## @backstage/plugin-home-react@0.1.26-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/core-components@0.17.2-next.1 + +## @backstage/plugin-kubernetes@0.12.7-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/core-compat-api@0.4.2-next.2 + - @backstage/core-components@0.17.2-next.1 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/plugin-catalog-react@1.18.0-next.2 + - @backstage/plugin-kubernetes-react@0.5.7-next.1 + - @backstage/plugin-permission-react@0.4.34-next.1 + - @backstage/catalog-model@1.7.3 + - @backstage/plugin-kubernetes-common@0.9.5-next.0 + +## @backstage/plugin-kubernetes-backend@0.19.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration-aws-node@0.1.16-next.0 + - @backstage/config@1.3.2 + - @backstage/plugin-auth-node@0.6.3-next.1 + - @backstage/plugin-kubernetes-node@0.3.0-next.1 + - @backstage/plugin-permission-node@0.10.0-next.1 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-catalog-node@1.17.0-next.1 + - @backstage/plugin-kubernetes-common@0.9.5-next.0 + - @backstage/plugin-permission-common@0.9.0-next.0 + +## @backstage/plugin-kubernetes-cluster@0.0.25-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/core-components@0.17.2-next.1 + - @backstage/plugin-catalog-react@1.18.0-next.2 + - @backstage/plugin-kubernetes-react@0.5.7-next.1 + - @backstage/plugin-permission-react@0.4.34-next.1 + - @backstage/catalog-model@1.7.3 + - @backstage/plugin-kubernetes-common@0.9.5-next.0 + +## @backstage/plugin-kubernetes-react@0.5.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/core-components@0.17.2-next.1 + - @backstage/catalog-model@1.7.3 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-kubernetes-common@0.9.5-next.0 + +## @backstage/plugin-notifications@0.5.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/core-compat-api@0.4.2-next.2 + - @backstage/core-components@0.17.2-next.1 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/plugin-signals-react@0.0.13-next.0 + - @backstage/errors@1.2.7 + - @backstage/theme@0.6.6-next.0 + - @backstage/types@1.2.1 + - @backstage/plugin-notifications-common@0.0.8 + +## @backstage/plugin-notifications-backend-module-email@0.3.9-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration-aws-node@0.1.16-next.0 + - @backstage/config@1.3.2 + - @backstage/plugin-notifications-node@0.2.15-next.1 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/types@1.2.1 + - @backstage/plugin-catalog-node@1.17.0-next.1 + - @backstage/plugin-notifications-common@0.0.8 + +## @backstage/plugin-notifications-backend-module-slack@0.1.1-next.2 + +### Patch Changes + +- 4f10768: Fix slack notification processor to handle a notification with an empty description +- Updated dependencies + - @backstage/config@1.3.2 + - @backstage/plugin-notifications-node@0.2.15-next.1 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-notifications-common@0.0.8 + +## @backstage/plugin-org@0.6.39-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/core-compat-api@0.4.2-next.2 + - @backstage/core-components@0.17.2-next.1 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/plugin-catalog-react@1.18.0-next.2 + - @backstage/catalog-model@1.7.3 + - @backstage/plugin-catalog-common@1.1.4-next.0 + +## @backstage/plugin-org-react@0.1.38-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/core-components@0.17.2-next.1 + - @backstage/plugin-catalog-react@1.18.0-next.2 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + +## @backstage/plugin-permission-react@0.4.34-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/config@1.3.2 + - @backstage/plugin-permission-common@0.9.0-next.0 + +## @backstage/plugin-scaffolder@1.31.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/core-compat-api@0.4.2-next.2 + - @backstage/core-components@0.17.2-next.1 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/integration-react@1.2.7-next.2 + - @backstage/plugin-catalog-react@1.18.0-next.2 + - @backstage/plugin-permission-react@0.4.34-next.1 + - @backstage/plugin-scaffolder-react@1.16.0-next.2 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-catalog-common@1.1.4-next.0 + - @backstage/plugin-scaffolder-common@1.5.11-next.0 + +## @backstage/plugin-scaffolder-backend@1.33.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/backend-defaults@0.10.0-next.2 + - @backstage/config@1.3.2 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.9.1-next.2 + - @backstage/plugin-bitbucket-cloud-common@0.3.0-next.2 + - @backstage/plugin-scaffolder-backend-module-azure@0.2.9-next.2 + - @backstage/plugin-scaffolder-backend-module-bitbucket@0.3.10-next.2 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.9-next.2 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.9-next.2 + - @backstage/plugin-scaffolder-backend-module-gerrit@0.2.9-next.2 + - @backstage/plugin-scaffolder-backend-module-gitea@0.2.9-next.2 + - @backstage/plugin-scaffolder-backend-module-github@0.7.1-next.2 + - @backstage/plugin-scaffolder-node@0.8.2-next.2 + - @backstage/plugin-auth-node@0.6.3-next.1 + - @backstage/plugin-permission-node@0.10.0-next.1 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.8-next.1 + - @backstage/plugin-catalog-node@1.17.0-next.1 + - @backstage/plugin-events-node@0.4.11-next.1 + - @backstage/plugin-permission-common@0.9.0-next.0 + - @backstage/plugin-scaffolder-common@1.5.11-next.0 + +## @backstage/plugin-scaffolder-backend-module-azure@0.2.9-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/config@1.3.2 + - @backstage/plugin-scaffolder-node@0.8.2-next.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/errors@1.2.7 + +## @backstage/plugin-scaffolder-backend-module-bitbucket@0.3.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/config@1.3.2 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.9-next.2 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.9-next.2 + - @backstage/plugin-scaffolder-node@0.8.2-next.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/errors@1.2.7 + +## @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.9-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/config@1.3.2 + - @backstage/plugin-bitbucket-cloud-common@0.3.0-next.2 + - @backstage/plugin-scaffolder-node@0.8.2-next.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/errors@1.2.7 + +## @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.9-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/config@1.3.2 + - @backstage/plugin-scaffolder-node@0.8.2-next.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/errors@1.2.7 + +## @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.3.9-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/config@1.3.2 + - @backstage/plugin-scaffolder-node@0.8.2-next.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/errors@1.2.7 + +## @backstage/plugin-scaffolder-backend-module-cookiecutter@0.3.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/backend-defaults@0.10.0-next.2 + - @backstage/config@1.3.2 + - @backstage/plugin-scaffolder-node@0.8.2-next.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## @backstage/plugin-scaffolder-backend-module-gcp@0.2.9-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/config@1.3.2 + - @backstage/plugin-scaffolder-node@0.8.2-next.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/errors@1.2.7 + +## @backstage/plugin-scaffolder-backend-module-gerrit@0.2.9-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/config@1.3.2 + - @backstage/plugin-scaffolder-node@0.8.2-next.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/errors@1.2.7 + +## @backstage/plugin-scaffolder-backend-module-gitea@0.2.9-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/config@1.3.2 + - @backstage/plugin-scaffolder-node@0.8.2-next.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/errors@1.2.7 + +## @backstage/plugin-scaffolder-backend-module-github@0.7.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/config@1.3.2 + - @backstage/plugin-scaffolder-node@0.8.2-next.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## @backstage/plugin-scaffolder-backend-module-gitlab@0.9.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/config@1.3.2 + - @backstage/plugin-scaffolder-node@0.8.2-next.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/errors@1.2.7 + +## @backstage/plugin-scaffolder-backend-module-notifications@0.1.10-next.2 + +### Patch Changes + +- b60253d: Change notification send scaffolder action to use native zod schemas +- Updated dependencies + - @backstage/plugin-notifications-node@0.2.15-next.1 + - @backstage/plugin-scaffolder-node@0.8.2-next.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/plugin-notifications-common@0.0.8 + +## @backstage/plugin-scaffolder-backend-module-rails@0.5.9-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/config@1.3.2 + - @backstage/plugin-scaffolder-node@0.8.2-next.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## @backstage/plugin-scaffolder-backend-module-sentry@0.2.9-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.2 + - @backstage/plugin-scaffolder-node@0.8.2-next.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/errors@1.2.7 + +## @backstage/plugin-scaffolder-backend-module-yeoman@0.4.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.8.2-next.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/types@1.2.1 + - @backstage/plugin-scaffolder-node-test-utils@0.2.2-next.2 + +## @backstage/plugin-scaffolder-node@0.8.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/catalog-model@1.7.3 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-scaffolder-common@1.5.11-next.0 + +## @backstage/plugin-scaffolder-node-test-utils@0.2.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.8.2-next.2 + - @backstage/backend-test-utils@1.5.0-next.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/types@1.2.1 + +## @backstage/plugin-scaffolder-react@1.16.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/core-components@0.17.2-next.1 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/plugin-catalog-react@1.18.0-next.2 + - @backstage/plugin-permission-react@0.4.34-next.1 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/theme@0.6.6-next.0 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-scaffolder-common@1.5.11-next.0 + +## @backstage/plugin-search@1.4.26-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/core-compat-api@0.4.2-next.2 + - @backstage/core-components@0.17.2-next.1 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/plugin-catalog-react@1.18.0-next.2 + - @backstage/plugin-search-react@1.9.0-next.1 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-search-common@1.2.18-next.0 + +## @backstage/plugin-search-backend@2.0.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.10.0-next.2 + - @backstage/backend-openapi-utils@0.5.3-next.1 + - @backstage/config@1.3.2 + - @backstage/plugin-permission-node@0.10.0-next.1 + - @backstage/plugin-search-backend-node@1.3.11-next.1 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-permission-common@0.9.0-next.0 + - @backstage/plugin-search-common@1.2.18-next.0 + +## @backstage/plugin-search-backend-module-elasticsearch@1.7.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration-aws-node@0.1.16-next.0 + - @backstage/config@1.3.2 + - @backstage/plugin-search-backend-node@1.3.11-next.1 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/plugin-search-common@1.2.18-next.0 + +## @backstage/plugin-search-backend-module-techdocs@0.4.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.2 + - @backstage/plugin-techdocs-node@1.13.3-next.2 + - @backstage/plugin-search-backend-node@1.3.11-next.1 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/plugin-catalog-common@1.1.4-next.0 + - @backstage/plugin-catalog-node@1.17.0-next.1 + - @backstage/plugin-permission-common@0.9.0-next.0 + - @backstage/plugin-search-common@1.2.18-next.0 + +## @backstage/plugin-search-react@1.9.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/core-components@0.17.2-next.1 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/theme@0.6.6-next.0 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-search-common@1.2.18-next.0 + +## @backstage/plugin-signals@0.0.19-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/core-components@0.17.2-next.1 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/plugin-signals-react@0.0.13-next.0 + - @backstage/theme@0.6.6-next.0 + - @backstage/types@1.2.1 + +## @backstage/plugin-signals-react@0.0.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/types@1.2.1 + +## @backstage/plugin-techdocs@1.12.6-next.2 + +### Patch Changes + +- 7d445da: Update keyboard focus on when clicking hash links. This fixes the issue where the "skip to content" link rendered by Material MkDocs isn't focused when used. +- 2ffd273: Add hover and focus styling to the "copy to clipboard" button within codeblocks in techdocs. Also added an aria-label to the button for accessibility. +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/config@1.3.2 + - @backstage/core-compat-api@0.4.2-next.2 + - @backstage/core-components@0.17.2-next.1 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/integration-react@1.2.7-next.2 + - @backstage/plugin-auth-react@0.1.15-next.1 + - @backstage/plugin-catalog-react@1.18.0-next.2 + - @backstage/plugin-search-react@1.9.0-next.1 + - @backstage/plugin-techdocs-react@1.2.17-next.1 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/errors@1.2.7 + - @backstage/theme@0.6.6-next.0 + - @backstage/plugin-search-common@1.2.18-next.0 + - @backstage/plugin-techdocs-common@0.1.0 + +## @backstage/plugin-techdocs-addons-test-utils@1.0.48-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/test-utils@1.7.8-next.1 + - @backstage/core-app-api@1.16.2-next.0 + - @backstage/plugin-techdocs@1.12.6-next.2 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/plugin-catalog@1.29.1-next.2 + - @backstage/integration-react@1.2.7-next.2 + - @backstage/plugin-catalog-react@1.18.0-next.2 + - @backstage/plugin-search-react@1.9.0-next.1 + - @backstage/plugin-techdocs-react@1.2.17-next.1 + +## @backstage/plugin-techdocs-backend@2.0.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/backend-defaults@0.10.0-next.2 + - @backstage/config@1.3.2 + - @backstage/plugin-techdocs-node@1.13.3-next.2 + - @backstage/plugin-search-backend-module-techdocs@0.4.2-next.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-common@1.1.4-next.0 + - @backstage/plugin-catalog-node@1.17.0-next.1 + - @backstage/plugin-permission-common@0.9.0-next.0 + - @backstage/plugin-techdocs-common@0.1.0 + +## @backstage/plugin-techdocs-module-addons-contrib@1.1.24-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/core-components@0.17.2-next.1 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/integration-react@1.2.7-next.2 + - @backstage/plugin-techdocs-react@1.2.17-next.1 + +## @backstage/plugin-techdocs-node@1.13.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration-aws-node@0.1.16-next.0 + - @backstage/integration@1.17.0-next.2 + - @backstage/config@1.3.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/catalog-model@1.7.3 + - @backstage/errors@1.2.7 + - @backstage/plugin-search-common@1.2.18-next.0 + - @backstage/plugin-techdocs-common@0.1.0 + +## @backstage/plugin-techdocs-react@1.2.17-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/config@1.3.2 + - @backstage/core-components@0.17.2-next.1 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/catalog-model@1.7.3 + - @backstage/version-bridge@1.0.11 + +## @backstage/plugin-user-settings@0.8.22-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.16.2-next.0 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/core-compat-api@0.4.2-next.2 + - @backstage/core-components@0.17.2-next.1 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/plugin-catalog-react@1.18.0-next.2 + - @backstage/plugin-signals-react@0.0.13-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/errors@1.2.7 + - @backstage/theme@0.6.6-next.0 + - @backstage/types@1.2.1 + - @backstage/plugin-user-settings-common@0.0.1 + +## @backstage/plugin-user-settings-backend@0.3.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.10.0-next.2 + - @backstage/plugin-auth-node@0.6.3-next.1 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-signals-node@0.1.20-next.1 + - @backstage/plugin-user-settings-common@0.0.1 + +## example-app@0.2.109-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.16.2-next.0 + - @backstage/canon@0.4.0-next.2 + - @backstage/plugin-catalog-import@0.13.0-next.2 + - @backstage/plugin-techdocs@1.12.6-next.2 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/plugin-catalog@1.29.1-next.2 + - @backstage/app-defaults@1.6.2-next.1 + - @backstage/cli@0.32.1-next.2 + - @backstage/config@1.3.2 + - @backstage/core-components@0.17.2-next.1 + - @backstage/frontend-app-api@0.11.2-next.2 + - @backstage/integration-react@1.2.7-next.2 + - @backstage/plugin-api-docs@0.12.7-next.2 + - @backstage/plugin-auth-react@0.1.15-next.1 + - @backstage/plugin-catalog-graph@0.4.19-next.2 + - @backstage/plugin-catalog-react@1.18.0-next.2 + - @backstage/plugin-home@0.8.8-next.2 + - @backstage/plugin-kubernetes@0.12.7-next.2 + - @backstage/plugin-kubernetes-cluster@0.0.25-next.2 + - @backstage/plugin-notifications@0.5.5-next.2 + - @backstage/plugin-org@0.6.39-next.2 + - @backstage/plugin-permission-react@0.4.34-next.1 + - @backstage/plugin-scaffolder@1.31.0-next.2 + - @backstage/plugin-scaffolder-react@1.16.0-next.2 + - @backstage/plugin-search@1.4.26-next.2 + - @backstage/plugin-search-react@1.9.0-next.1 + - @backstage/plugin-signals@0.0.19-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.24-next.2 + - @backstage/plugin-techdocs-react@1.2.17-next.1 + - @backstage/plugin-user-settings@0.8.22-next.2 + - @backstage/plugin-catalog-unprocessed-entities@0.2.17-next.2 + - @backstage/plugin-devtools@0.1.27-next.2 + - @backstage/catalog-model@1.7.3 + - @backstage/theme@0.6.6-next.0 + - @backstage/plugin-catalog-common@1.1.4-next.0 + - @backstage/plugin-search-common@1.2.18-next.0 + +## example-app-next@0.0.23-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.16.2-next.0 + - @backstage/canon@0.4.0-next.2 + - @backstage/plugin-catalog-import@0.13.0-next.2 + - @backstage/plugin-techdocs@1.12.6-next.2 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/plugin-catalog@1.29.1-next.2 + - @backstage/app-defaults@1.6.2-next.1 + - @backstage/cli@0.32.1-next.2 + - @backstage/config@1.3.2 + - @backstage/core-compat-api@0.4.2-next.2 + - @backstage/core-components@0.17.2-next.1 + - @backstage/frontend-app-api@0.11.2-next.2 + - @backstage/frontend-defaults@0.2.2-next.2 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/integration-react@1.2.7-next.2 + - @backstage/plugin-api-docs@0.12.7-next.2 + - @backstage/plugin-auth-react@0.1.15-next.1 + - @backstage/plugin-catalog-graph@0.4.19-next.2 + - @backstage/plugin-catalog-react@1.18.0-next.2 + - @backstage/plugin-home@0.8.8-next.2 + - @backstage/plugin-kubernetes@0.12.7-next.2 + - @backstage/plugin-kubernetes-cluster@0.0.25-next.2 + - @backstage/plugin-notifications@0.5.5-next.2 + - @backstage/plugin-org@0.6.39-next.2 + - @backstage/plugin-permission-react@0.4.34-next.1 + - @backstage/plugin-scaffolder@1.31.0-next.2 + - @backstage/plugin-scaffolder-react@1.16.0-next.2 + - @backstage/plugin-search@1.4.26-next.2 + - @backstage/plugin-search-react@1.9.0-next.1 + - @backstage/plugin-signals@0.0.19-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.24-next.2 + - @backstage/plugin-techdocs-react@1.2.17-next.1 + - @backstage/plugin-user-settings@0.8.22-next.2 + - @backstage/plugin-app@0.1.9-next.2 + - @backstage/plugin-app-visualizer@0.1.19-next.1 + - @backstage/plugin-catalog-unprocessed-entities@0.2.17-next.2 + - @backstage/catalog-model@1.7.3 + - @backstage/theme@0.6.6-next.0 + - @backstage/plugin-catalog-common@1.1.4-next.0 + - @backstage/plugin-search-common@1.2.18-next.0 + +## app-next-example-plugin@0.0.23-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.2-next.1 + - @backstage/frontend-plugin-api@0.10.2-next.1 + +## example-backend@0.0.38-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend-module-notifications@0.1.10-next.2 + - @backstage/backend-defaults@0.10.0-next.2 + - @backstage/plugin-catalog-backend@2.0.0-next.2 + - @backstage/plugin-events-backend@0.5.2-next.1 + - @backstage/plugin-search-backend@2.0.2-next.2 + - @backstage/plugin-kubernetes-backend@0.19.6-next.2 + - @backstage/plugin-catalog-backend-module-openapi@0.2.10-next.2 + - @backstage/plugin-scaffolder-backend@1.33.0-next.2 + - @backstage/plugin-scaffolder-backend-module-github@0.7.1-next.2 + - @backstage/plugin-techdocs-backend@2.0.2-next.2 + - @backstage/plugin-app-backend@0.5.2-next.1 + - @backstage/plugin-auth-backend@0.25.0-next.1 + - @backstage/plugin-auth-backend-module-github-provider@0.3.3-next.1 + - @backstage/plugin-auth-node@0.6.3-next.1 + - @backstage/plugin-devtools-backend@0.5.5-next.2 + - @backstage/plugin-events-backend-module-google-pubsub@0.1.0-next.1 + - @backstage/plugin-notifications-backend@0.5.6-next.1 + - @backstage/plugin-permission-backend@0.7.0-next.1 + - @backstage/plugin-permission-node@0.10.0-next.1 + - @backstage/plugin-proxy-backend@0.6.2-next.1 + - @backstage/plugin-search-backend-node@1.3.11-next.1 + - @backstage/plugin-signals-backend@0.3.4-next.1 + - @backstage/plugin-search-backend-module-techdocs@0.4.2-next.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/catalog-model@1.7.3 + - @backstage/plugin-auth-backend-module-guest-provider@0.2.8-next.1 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.5.2-next.1 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.8-next.1 + - @backstage/plugin-catalog-backend-module-unprocessed@0.6.0-next.1 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.2.8-next.1 + - @backstage/plugin-permission-common@0.9.0-next.0 + - @backstage/plugin-search-backend-module-catalog@0.3.4-next.1 + - @backstage/plugin-search-backend-module-explore@0.3.2-next.1 + +## e2e-test@0.2.28-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/create-app@0.6.2-next.2 + - @backstage/cli-common@0.1.15 + - @backstage/errors@1.2.7 + +## @internal/frontend@0.0.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + +## @internal/scaffolder@0.0.9-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/plugin-scaffolder-react@1.16.0-next.2 + +## techdocs-cli-embedded-app@0.2.108-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/test-utils@1.7.8-next.1 + - @backstage/core-app-api@1.16.2-next.0 + - @backstage/plugin-techdocs@1.12.6-next.2 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/plugin-catalog@1.29.1-next.2 + - @backstage/app-defaults@1.6.2-next.1 + - @backstage/cli@0.32.1-next.2 + - @backstage/config@1.3.2 + - @backstage/core-components@0.17.2-next.1 + - @backstage/integration-react@1.2.7-next.2 + - @backstage/plugin-techdocs-react@1.2.17-next.1 + - @backstage/catalog-model@1.7.3 + - @backstage/theme@0.6.6-next.0 + +## @internal/plugin-todo-list@1.0.39-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/core-components@0.17.2-next.1 diff --git a/package.json b/package.json index b9f0f11f5a..b5d88d947b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "root", - "version": "1.39.0-next.1", + "version": "1.39.0-next.2", "backstage": { "cli": { "new": { diff --git a/packages/app-defaults/CHANGELOG.md b/packages/app-defaults/CHANGELOG.md index 43487efc73..d130df1b26 100644 --- a/packages/app-defaults/CHANGELOG.md +++ b/packages/app-defaults/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/app-defaults +## 1.6.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.16.2-next.0 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/core-components@0.17.2-next.1 + - @backstage/plugin-permission-react@0.4.34-next.1 + - @backstage/theme@0.6.6-next.0 + ## 1.6.2-next.0 ### Patch Changes diff --git a/packages/app-defaults/package.json b/packages/app-defaults/package.json index a6220cbe18..b2cc639068 100644 --- a/packages/app-defaults/package.json +++ b/packages/app-defaults/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/app-defaults", - "version": "1.6.2-next.0", + "version": "1.6.2-next.1", "description": "Provides the default wiring of a Backstage App", "backstage": { "role": "web-library" diff --git a/packages/app-next-example-plugin/CHANGELOG.md b/packages/app-next-example-plugin/CHANGELOG.md index 1cd3e791f0..f39080a253 100644 --- a/packages/app-next-example-plugin/CHANGELOG.md +++ b/packages/app-next-example-plugin/CHANGELOG.md @@ -1,5 +1,13 @@ # app-next-example-plugin +## 0.0.23-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.2-next.1 + - @backstage/frontend-plugin-api@0.10.2-next.1 + ## 0.0.23-next.0 ### Patch Changes diff --git a/packages/app-next-example-plugin/package.json b/packages/app-next-example-plugin/package.json index f6699ef0fa..e0fd8ffff7 100644 --- a/packages/app-next-example-plugin/package.json +++ b/packages/app-next-example-plugin/package.json @@ -1,6 +1,6 @@ { "name": "app-next-example-plugin", - "version": "0.0.23-next.0", + "version": "0.0.23-next.1", "description": "Backstage internal example plugin", "backstage": { "role": "frontend-plugin", diff --git a/packages/app-next/CHANGELOG.md b/packages/app-next/CHANGELOG.md index 0a34bdb8a8..5cb9e8581e 100644 --- a/packages/app-next/CHANGELOG.md +++ b/packages/app-next/CHANGELOG.md @@ -1,5 +1,51 @@ # example-app-next +## 0.0.23-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.16.2-next.0 + - @backstage/canon@0.4.0-next.2 + - @backstage/plugin-catalog-import@0.13.0-next.2 + - @backstage/plugin-techdocs@1.12.6-next.2 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/plugin-catalog@1.29.1-next.2 + - @backstage/app-defaults@1.6.2-next.1 + - @backstage/cli@0.32.1-next.2 + - @backstage/config@1.3.2 + - @backstage/core-compat-api@0.4.2-next.2 + - @backstage/core-components@0.17.2-next.1 + - @backstage/frontend-app-api@0.11.2-next.2 + - @backstage/frontend-defaults@0.2.2-next.2 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/integration-react@1.2.7-next.2 + - @backstage/plugin-api-docs@0.12.7-next.2 + - @backstage/plugin-auth-react@0.1.15-next.1 + - @backstage/plugin-catalog-graph@0.4.19-next.2 + - @backstage/plugin-catalog-react@1.18.0-next.2 + - @backstage/plugin-home@0.8.8-next.2 + - @backstage/plugin-kubernetes@0.12.7-next.2 + - @backstage/plugin-kubernetes-cluster@0.0.25-next.2 + - @backstage/plugin-notifications@0.5.5-next.2 + - @backstage/plugin-org@0.6.39-next.2 + - @backstage/plugin-permission-react@0.4.34-next.1 + - @backstage/plugin-scaffolder@1.31.0-next.2 + - @backstage/plugin-scaffolder-react@1.16.0-next.2 + - @backstage/plugin-search@1.4.26-next.2 + - @backstage/plugin-search-react@1.9.0-next.1 + - @backstage/plugin-signals@0.0.19-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.24-next.2 + - @backstage/plugin-techdocs-react@1.2.17-next.1 + - @backstage/plugin-user-settings@0.8.22-next.2 + - @backstage/plugin-app@0.1.9-next.2 + - @backstage/plugin-app-visualizer@0.1.19-next.1 + - @backstage/plugin-catalog-unprocessed-entities@0.2.17-next.2 + - @backstage/catalog-model@1.7.3 + - @backstage/theme@0.6.6-next.0 + - @backstage/plugin-catalog-common@1.1.4-next.0 + - @backstage/plugin-search-common@1.2.18-next.0 + ## 0.0.23-next.1 ### Patch Changes diff --git a/packages/app-next/package.json b/packages/app-next/package.json index 690a9d4f28..0f56c433dd 100644 --- a/packages/app-next/package.json +++ b/packages/app-next/package.json @@ -1,6 +1,6 @@ { "name": "example-app-next", - "version": "0.0.23-next.1", + "version": "0.0.23-next.2", "backstage": { "role": "frontend" }, diff --git a/packages/app/CHANGELOG.md b/packages/app/CHANGELOG.md index 8ebf850a7d..ff42b42cc8 100644 --- a/packages/app/CHANGELOG.md +++ b/packages/app/CHANGELOG.md @@ -1,5 +1,47 @@ # example-app +## 0.2.109-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.16.2-next.0 + - @backstage/canon@0.4.0-next.2 + - @backstage/plugin-catalog-import@0.13.0-next.2 + - @backstage/plugin-techdocs@1.12.6-next.2 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/plugin-catalog@1.29.1-next.2 + - @backstage/app-defaults@1.6.2-next.1 + - @backstage/cli@0.32.1-next.2 + - @backstage/config@1.3.2 + - @backstage/core-components@0.17.2-next.1 + - @backstage/frontend-app-api@0.11.2-next.2 + - @backstage/integration-react@1.2.7-next.2 + - @backstage/plugin-api-docs@0.12.7-next.2 + - @backstage/plugin-auth-react@0.1.15-next.1 + - @backstage/plugin-catalog-graph@0.4.19-next.2 + - @backstage/plugin-catalog-react@1.18.0-next.2 + - @backstage/plugin-home@0.8.8-next.2 + - @backstage/plugin-kubernetes@0.12.7-next.2 + - @backstage/plugin-kubernetes-cluster@0.0.25-next.2 + - @backstage/plugin-notifications@0.5.5-next.2 + - @backstage/plugin-org@0.6.39-next.2 + - @backstage/plugin-permission-react@0.4.34-next.1 + - @backstage/plugin-scaffolder@1.31.0-next.2 + - @backstage/plugin-scaffolder-react@1.16.0-next.2 + - @backstage/plugin-search@1.4.26-next.2 + - @backstage/plugin-search-react@1.9.0-next.1 + - @backstage/plugin-signals@0.0.19-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.24-next.2 + - @backstage/plugin-techdocs-react@1.2.17-next.1 + - @backstage/plugin-user-settings@0.8.22-next.2 + - @backstage/plugin-catalog-unprocessed-entities@0.2.17-next.2 + - @backstage/plugin-devtools@0.1.27-next.2 + - @backstage/catalog-model@1.7.3 + - @backstage/theme@0.6.6-next.0 + - @backstage/plugin-catalog-common@1.1.4-next.0 + - @backstage/plugin-search-common@1.2.18-next.0 + ## 0.2.109-next.1 ### Patch Changes diff --git a/packages/app/package.json b/packages/app/package.json index 2393a89feb..b988bf460d 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,6 +1,6 @@ { "name": "example-app", - "version": "0.2.109-next.1", + "version": "0.2.109-next.2", "backstage": { "role": "frontend" }, diff --git a/packages/backend-defaults/CHANGELOG.md b/packages/backend-defaults/CHANGELOG.md index 49a911c398..b66cdb5ac7 100644 --- a/packages/backend-defaults/CHANGELOG.md +++ b/packages/backend-defaults/CHANGELOG.md @@ -1,5 +1,25 @@ # @backstage/backend-defaults +## 0.10.0-next.2 + +### Patch Changes + +- 0e7a640: The `GithubUrlReader` will now use the token from `options` when fetching repo details +- Updated dependencies + - @backstage/integration-aws-node@0.1.16-next.0 + - @backstage/integration@1.17.0-next.2 + - @backstage/cli-node@0.2.13 + - @backstage/config@1.3.2 + - @backstage/backend-app-api@1.2.3-next.1 + - @backstage/plugin-auth-node@0.6.3-next.1 + - @backstage/plugin-permission-node@0.10.0-next.1 + - @backstage/backend-dev-utils@0.1.5 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/config-loader@1.10.1-next.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-events-node@0.4.11-next.1 + ## 0.10.0-next.1 ### Minor Changes diff --git a/packages/backend-defaults/package.json b/packages/backend-defaults/package.json index ec6486d2b3..093ebdc422 100644 --- a/packages/backend-defaults/package.json +++ b/packages/backend-defaults/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/backend-defaults", - "version": "0.10.0-next.1", + "version": "0.10.0-next.2", "description": "Backend defaults used by Backstage backend apps", "backstage": { "role": "node-library" diff --git a/packages/backend-dynamic-feature-service/CHANGELOG.md b/packages/backend-dynamic-feature-service/CHANGELOG.md index e81fb162c6..8ad2f1e0cf 100644 --- a/packages/backend-dynamic-feature-service/CHANGELOG.md +++ b/packages/backend-dynamic-feature-service/CHANGELOG.md @@ -1,5 +1,30 @@ # @backstage/backend-dynamic-feature-service +## 0.7.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.10.0-next.2 + - @backstage/backend-openapi-utils@0.5.3-next.1 + - @backstage/cli-node@0.2.13 + - @backstage/config@1.3.2 + - @backstage/plugin-catalog-backend@2.0.0-next.2 + - @backstage/plugin-events-backend@0.5.2-next.1 + - @backstage/plugin-scaffolder-node@0.8.2-next.2 + - @backstage/plugin-auth-node@0.6.3-next.1 + - @backstage/plugin-permission-node@0.10.0-next.1 + - @backstage/plugin-search-backend-node@1.3.11-next.1 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/cli-common@0.1.15 + - @backstage/config-loader@1.10.1-next.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-app-node@0.1.33-next.1 + - @backstage/plugin-events-node@0.4.11-next.1 + - @backstage/plugin-permission-common@0.9.0-next.0 + - @backstage/plugin-search-common@1.2.18-next.0 + ## 0.7.0-next.1 ### Patch Changes diff --git a/packages/backend-dynamic-feature-service/package.json b/packages/backend-dynamic-feature-service/package.json index e8c2a38398..54ea6c8986 100644 --- a/packages/backend-dynamic-feature-service/package.json +++ b/packages/backend-dynamic-feature-service/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/backend-dynamic-feature-service", - "version": "0.7.0-next.1", + "version": "0.7.0-next.2", "description": "Backstage dynamic feature service", "backstage": { "role": "node-library" diff --git a/packages/backend-test-utils/CHANGELOG.md b/packages/backend-test-utils/CHANGELOG.md index 7ea0d20986..5cf584fc9f 100644 --- a/packages/backend-test-utils/CHANGELOG.md +++ b/packages/backend-test-utils/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/backend-test-utils +## 1.5.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.10.0-next.2 + - @backstage/config@1.3.2 + - @backstage/backend-app-api@1.2.3-next.1 + - @backstage/plugin-auth-node@0.6.3-next.1 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-events-node@0.4.11-next.1 + ## 1.5.0-next.1 ### Minor Changes diff --git a/packages/backend-test-utils/package.json b/packages/backend-test-utils/package.json index 799db1e589..bcd3ef014c 100644 --- a/packages/backend-test-utils/package.json +++ b/packages/backend-test-utils/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/backend-test-utils", - "version": "1.5.0-next.1", + "version": "1.5.0-next.2", "description": "Test helpers library for Backstage backends", "backstage": { "role": "node-library" diff --git a/packages/backend/CHANGELOG.md b/packages/backend/CHANGELOG.md index 18f850b5da..b7d8283cd1 100644 --- a/packages/backend/CHANGELOG.md +++ b/packages/backend/CHANGELOG.md @@ -1,5 +1,44 @@ # example-backend +## 0.0.38-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend-module-notifications@0.1.10-next.2 + - @backstage/backend-defaults@0.10.0-next.2 + - @backstage/plugin-catalog-backend@2.0.0-next.2 + - @backstage/plugin-events-backend@0.5.2-next.1 + - @backstage/plugin-search-backend@2.0.2-next.2 + - @backstage/plugin-kubernetes-backend@0.19.6-next.2 + - @backstage/plugin-catalog-backend-module-openapi@0.2.10-next.2 + - @backstage/plugin-scaffolder-backend@1.33.0-next.2 + - @backstage/plugin-scaffolder-backend-module-github@0.7.1-next.2 + - @backstage/plugin-techdocs-backend@2.0.2-next.2 + - @backstage/plugin-app-backend@0.5.2-next.1 + - @backstage/plugin-auth-backend@0.25.0-next.1 + - @backstage/plugin-auth-backend-module-github-provider@0.3.3-next.1 + - @backstage/plugin-auth-node@0.6.3-next.1 + - @backstage/plugin-devtools-backend@0.5.5-next.2 + - @backstage/plugin-events-backend-module-google-pubsub@0.1.0-next.1 + - @backstage/plugin-notifications-backend@0.5.6-next.1 + - @backstage/plugin-permission-backend@0.7.0-next.1 + - @backstage/plugin-permission-node@0.10.0-next.1 + - @backstage/plugin-proxy-backend@0.6.2-next.1 + - @backstage/plugin-search-backend-node@1.3.11-next.1 + - @backstage/plugin-signals-backend@0.3.4-next.1 + - @backstage/plugin-search-backend-module-techdocs@0.4.2-next.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/catalog-model@1.7.3 + - @backstage/plugin-auth-backend-module-guest-provider@0.2.8-next.1 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.5.2-next.1 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.8-next.1 + - @backstage/plugin-catalog-backend-module-unprocessed@0.6.0-next.1 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.2.8-next.1 + - @backstage/plugin-permission-common@0.9.0-next.0 + - @backstage/plugin-search-backend-module-catalog@0.3.4-next.1 + - @backstage/plugin-search-backend-module-explore@0.3.2-next.1 + ## 0.0.38-next.1 ### Patch Changes diff --git a/packages/backend/package.json b/packages/backend/package.json index fa51d98d4a..70a47f794c 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -1,6 +1,6 @@ { "name": "example-backend", - "version": "0.0.38-next.1", + "version": "0.0.38-next.2", "backstage": { "role": "backend" }, diff --git a/packages/canon/CHANGELOG.md b/packages/canon/CHANGELOG.md index 7cb4492505..cca1758025 100644 --- a/packages/canon/CHANGELOG.md +++ b/packages/canon/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/canon +## 0.4.0-next.2 + +### Patch Changes + +- 6189bfd: Added new icon and onClear props to the TextField to make it easier to accessorize inputs. +- 97b25a1: Pin version of @base-ui-components/react. +- 185d3a8: Use the Field component from Base UI within the TextField. + ## 0.4.0-next.1 ### Minor Changes diff --git a/packages/canon/package.json b/packages/canon/package.json index 610e7132f5..c63e307453 100644 --- a/packages/canon/package.json +++ b/packages/canon/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/canon", - "version": "0.4.0-next.1", + "version": "0.4.0-next.2", "backstage": { "role": "web-library" }, diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index 8c5fb14d96..faddf38926 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/cli +## 0.32.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/cli-node@0.2.13 + - @backstage/config@1.3.2 + - @backstage/release-manifests@0.0.12 + - @backstage/catalog-model@1.7.3 + - @backstage/cli-common@0.1.15 + - @backstage/config-loader@1.10.1-next.0 + - @backstage/errors@1.2.7 + - @backstage/eslint-plugin@0.1.10 + - @backstage/types@1.2.1 + ## 0.32.1-next.1 ### Patch Changes diff --git a/packages/cli/package.json b/packages/cli/package.json index 1be77acd70..616556b5b6 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/cli", - "version": "0.32.1-next.1", + "version": "0.32.1-next.2", "description": "CLI for developing Backstage plugins and apps", "backstage": { "role": "cli" diff --git a/packages/core-app-api/CHANGELOG.md b/packages/core-app-api/CHANGELOG.md index 34fb45655d..39a4f5b63b 100644 --- a/packages/core-app-api/CHANGELOG.md +++ b/packages/core-app-api/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/core-app-api +## 1.16.2-next.0 + +### Patch Changes + +- 73f6cc3: Updated `I18nextTranslationApi` to support interpolation of JSX elements. +- Updated dependencies + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/config@1.3.2 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + ## 1.16.1 ### Patch Changes diff --git a/packages/core-app-api/package.json b/packages/core-app-api/package.json index d226dbccb0..be8f0592eb 100644 --- a/packages/core-app-api/package.json +++ b/packages/core-app-api/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/core-app-api", - "version": "1.16.1", + "version": "1.16.2-next.0", "description": "Core app API used by Backstage apps", "backstage": { "role": "web-library" diff --git a/packages/core-compat-api/CHANGELOG.md b/packages/core-compat-api/CHANGELOG.md index 58d8e5df47..e06676f2e3 100644 --- a/packages/core-compat-api/CHANGELOG.md +++ b/packages/core-compat-api/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/core-compat-api +## 0.4.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/plugin-catalog-react@1.18.0-next.2 + - @backstage/version-bridge@1.0.11 + ## 0.4.2-next.1 ### Patch Changes diff --git a/packages/core-compat-api/package.json b/packages/core-compat-api/package.json index 51a48c130f..6f20d04c5f 100644 --- a/packages/core-compat-api/package.json +++ b/packages/core-compat-api/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/core-compat-api", - "version": "0.4.2-next.1", + "version": "0.4.2-next.2", "backstage": { "role": "web-library" }, diff --git a/packages/core-components/CHANGELOG.md b/packages/core-components/CHANGELOG.md index ed75738a7a..55608d11bf 100644 --- a/packages/core-components/CHANGELOG.md +++ b/packages/core-components/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/core-components +## 0.17.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/theme@0.6.6-next.0 + - @backstage/version-bridge@1.0.11 + ## 0.17.2-next.0 ### Patch Changes diff --git a/packages/core-components/package.json b/packages/core-components/package.json index d55c977c87..2fe5f03f28 100644 --- a/packages/core-components/package.json +++ b/packages/core-components/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/core-components", - "version": "0.17.2-next.0", + "version": "0.17.2-next.1", "description": "Core components used by Backstage plugins and apps", "backstage": { "role": "web-library" diff --git a/packages/core-plugin-api/CHANGELOG.md b/packages/core-plugin-api/CHANGELOG.md index 15575b2eb8..90dee1e81b 100644 --- a/packages/core-plugin-api/CHANGELOG.md +++ b/packages/core-plugin-api/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/core-plugin-api +## 1.10.7-next.0 + +### Patch Changes + +- 73f6cc3: The `TranslationApi` now supports interpolation of JSX elements by passing them directly as values to the translation function. If any of the provided interpolation values are JSX elements, the translation function will return a JSX element instead of a string. +- Updated dependencies + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + ## 1.10.6 ### Patch Changes diff --git a/packages/core-plugin-api/package.json b/packages/core-plugin-api/package.json index 3c106675d0..7c9eaa3943 100644 --- a/packages/core-plugin-api/package.json +++ b/packages/core-plugin-api/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/core-plugin-api", - "version": "1.10.6", + "version": "1.10.7-next.0", "description": "Core API used by Backstage plugins", "backstage": { "role": "web-library" diff --git a/packages/create-app/CHANGELOG.md b/packages/create-app/CHANGELOG.md index 2587a880a8..004799767d 100644 --- a/packages/create-app/CHANGELOG.md +++ b/packages/create-app/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/create-app +## 0.6.2-next.2 + +### Patch Changes + +- 8448948: Removed `lerna-debug.log*` pattern from `.gitignore` as Lerna was removed from the package in version `@backstage/create-app@0.5.19`. +- Updated dependencies + - @backstage/cli-common@0.1.15 + ## 0.6.2-next.1 ### Patch Changes diff --git a/packages/create-app/package.json b/packages/create-app/package.json index 015a84409f..55432ddc6b 100644 --- a/packages/create-app/package.json +++ b/packages/create-app/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/create-app", - "version": "0.6.2-next.1", + "version": "0.6.2-next.2", "description": "A CLI that helps you create your own Backstage app", "backstage": { "role": "cli" diff --git a/packages/dev-utils/CHANGELOG.md b/packages/dev-utils/CHANGELOG.md index 5d3eeaf067..1481726448 100644 --- a/packages/dev-utils/CHANGELOG.md +++ b/packages/dev-utils/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/dev-utils +## 1.1.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.16.2-next.0 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/app-defaults@1.6.2-next.1 + - @backstage/core-components@0.17.2-next.1 + - @backstage/integration-react@1.2.7-next.2 + - @backstage/plugin-catalog-react@1.18.0-next.2 + - @backstage/catalog-model@1.7.3 + - @backstage/theme@0.6.6-next.0 + ## 1.1.10-next.1 ### Patch Changes diff --git a/packages/dev-utils/package.json b/packages/dev-utils/package.json index 9bdc8a25ac..8e68e50aed 100644 --- a/packages/dev-utils/package.json +++ b/packages/dev-utils/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/dev-utils", - "version": "1.1.10-next.1", + "version": "1.1.10-next.2", "description": "Utilities for developing Backstage plugins.", "backstage": { "role": "web-library" diff --git a/packages/e2e-test/CHANGELOG.md b/packages/e2e-test/CHANGELOG.md index 5575b8f05b..487eb771f1 100644 --- a/packages/e2e-test/CHANGELOG.md +++ b/packages/e2e-test/CHANGELOG.md @@ -1,5 +1,14 @@ # e2e-test +## 0.2.28-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/create-app@0.6.2-next.2 + - @backstage/cli-common@0.1.15 + - @backstage/errors@1.2.7 + ## 0.2.28-next.1 ### Patch Changes diff --git a/packages/e2e-test/package.json b/packages/e2e-test/package.json index a27bdcc4b3..7287232a01 100644 --- a/packages/e2e-test/package.json +++ b/packages/e2e-test/package.json @@ -1,6 +1,6 @@ { "name": "e2e-test", - "version": "0.2.28-next.1", + "version": "0.2.28-next.2", "description": "E2E test for verifying Backstage packages", "backstage": { "role": "cli" diff --git a/packages/frontend-app-api/CHANGELOG.md b/packages/frontend-app-api/CHANGELOG.md index 95c44da351..3e119d7b2f 100644 --- a/packages/frontend-app-api/CHANGELOG.md +++ b/packages/frontend-app-api/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/frontend-app-api +## 0.11.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.16.2-next.0 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/config@1.3.2 + - @backstage/frontend-defaults@0.2.2-next.2 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + ## 0.11.2-next.1 ### Patch Changes diff --git a/packages/frontend-app-api/package.json b/packages/frontend-app-api/package.json index e64b6b8b2c..ea086e29ee 100644 --- a/packages/frontend-app-api/package.json +++ b/packages/frontend-app-api/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/frontend-app-api", - "version": "0.11.2-next.1", + "version": "0.11.2-next.2", "backstage": { "role": "web-library" }, diff --git a/packages/frontend-defaults/CHANGELOG.md b/packages/frontend-defaults/CHANGELOG.md index 9dc4b7be1c..df3f24050b 100644 --- a/packages/frontend-defaults/CHANGELOG.md +++ b/packages/frontend-defaults/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/frontend-defaults +## 0.2.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.2 + - @backstage/frontend-app-api@0.11.2-next.2 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/plugin-app@0.1.9-next.2 + - @backstage/errors@1.2.7 + ## 0.2.2-next.1 ### Patch Changes diff --git a/packages/frontend-defaults/package.json b/packages/frontend-defaults/package.json index 15285842b6..b1b85f3eb1 100644 --- a/packages/frontend-defaults/package.json +++ b/packages/frontend-defaults/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/frontend-defaults", - "version": "0.2.2-next.1", + "version": "0.2.2-next.2", "backstage": { "role": "web-library" }, diff --git a/packages/frontend-dynamic-feature-loader/CHANGELOG.md b/packages/frontend-dynamic-feature-loader/CHANGELOG.md index dbbda03911..1b377ed777 100644 --- a/packages/frontend-dynamic-feature-loader/CHANGELOG.md +++ b/packages/frontend-dynamic-feature-loader/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/frontend-dynamic-feature-loader +## 0.1.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.2 + - @backstage/frontend-plugin-api@0.10.2-next.1 + ## 0.1.1-next.0 ### Patch Changes diff --git a/packages/frontend-dynamic-feature-loader/package.json b/packages/frontend-dynamic-feature-loader/package.json index 3513ac9cc5..d3b05fdc12 100644 --- a/packages/frontend-dynamic-feature-loader/package.json +++ b/packages/frontend-dynamic-feature-loader/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/frontend-dynamic-feature-loader", - "version": "0.1.1-next.0", + "version": "0.1.1-next.1", "backstage": { "role": "web-library" }, diff --git a/packages/frontend-internal/CHANGELOG.md b/packages/frontend-internal/CHANGELOG.md index 42e102807a..65ffbd826f 100644 --- a/packages/frontend-internal/CHANGELOG.md +++ b/packages/frontend-internal/CHANGELOG.md @@ -1,5 +1,14 @@ # @internal/frontend +## 0.0.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + ## 0.0.9-next.0 ### Patch Changes diff --git a/packages/frontend-internal/package.json b/packages/frontend-internal/package.json index 70276bf9c6..7f1615c5f5 100644 --- a/packages/frontend-internal/package.json +++ b/packages/frontend-internal/package.json @@ -1,6 +1,6 @@ { "name": "@internal/frontend", - "version": "0.0.9-next.0", + "version": "0.0.9-next.1", "backstage": { "role": "web-library", "inline": true diff --git a/packages/frontend-plugin-api/CHANGELOG.md b/packages/frontend-plugin-api/CHANGELOG.md index a43fcec1d8..ce9e73745f 100644 --- a/packages/frontend-plugin-api/CHANGELOG.md +++ b/packages/frontend-plugin-api/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/frontend-plugin-api +## 0.10.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/core-components@0.17.2-next.1 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + ## 0.10.2-next.0 ### Patch Changes diff --git a/packages/frontend-plugin-api/package.json b/packages/frontend-plugin-api/package.json index 5877a3800c..1e3161489c 100644 --- a/packages/frontend-plugin-api/package.json +++ b/packages/frontend-plugin-api/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/frontend-plugin-api", - "version": "0.10.2-next.0", + "version": "0.10.2-next.1", "backstage": { "role": "web-library" }, diff --git a/packages/frontend-test-utils/CHANGELOG.md b/packages/frontend-test-utils/CHANGELOG.md index 38048e3fd3..3534a15653 100644 --- a/packages/frontend-test-utils/CHANGELOG.md +++ b/packages/frontend-test-utils/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/frontend-test-utils +## 0.3.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/test-utils@1.7.8-next.1 + - @backstage/config@1.3.2 + - @backstage/frontend-app-api@0.11.2-next.2 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/plugin-app@0.1.9-next.2 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + ## 0.3.2-next.1 ### Patch Changes diff --git a/packages/frontend-test-utils/package.json b/packages/frontend-test-utils/package.json index 5bdefbbb49..b065490006 100644 --- a/packages/frontend-test-utils/package.json +++ b/packages/frontend-test-utils/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/frontend-test-utils", - "version": "0.3.2-next.1", + "version": "0.3.2-next.2", "backstage": { "role": "web-library" }, diff --git a/packages/integration-aws-node/CHANGELOG.md b/packages/integration-aws-node/CHANGELOG.md index 67060c78c2..41d4eecb08 100644 --- a/packages/integration-aws-node/CHANGELOG.md +++ b/packages/integration-aws-node/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/integration-aws-node +## 0.1.16-next.0 + +### Patch Changes + +- db4630e: Fixed bug in DefaultAwsCredentialsManager where aws.mainAccount.region has no effect on the STS region used for account ID lookup during credential provider lookup when falling back to the main account, and it does not default to us-east-1 +- Updated dependencies + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + ## 0.1.15 ### Patch Changes diff --git a/packages/integration-aws-node/package.json b/packages/integration-aws-node/package.json index 28cf435fd7..65403727b6 100644 --- a/packages/integration-aws-node/package.json +++ b/packages/integration-aws-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/integration-aws-node", - "version": "0.1.15", + "version": "0.1.16-next.0", "description": "Helpers for fetching AWS account credentials", "backstage": { "role": "node-library" diff --git a/packages/integration-react/CHANGELOG.md b/packages/integration-react/CHANGELOG.md index a3440c8d2c..58fe14bfb1 100644 --- a/packages/integration-react/CHANGELOG.md +++ b/packages/integration-react/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/integration-react +## 1.2.7-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/config@1.3.2 + ## 1.2.7-next.1 ### Patch Changes diff --git a/packages/integration-react/package.json b/packages/integration-react/package.json index f49fc4dc69..bf99892817 100644 --- a/packages/integration-react/package.json +++ b/packages/integration-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/integration-react", - "version": "1.2.7-next.1", + "version": "1.2.7-next.2", "description": "Frontend package for managing integrations towards external systems", "backstage": { "role": "web-library" diff --git a/packages/integration/CHANGELOG.md b/packages/integration/CHANGELOG.md index 535d154f6e..6a67d797c8 100644 --- a/packages/integration/CHANGELOG.md +++ b/packages/integration/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/integration +## 1.17.0-next.2 + +### Minor Changes + +- f134cea: Implement Edit URL feature for Gerrit 3.9+. + + It's possible to disable the edit url by adding the `disableEditUrl: true` config in the Gerrit integration. + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + ## 1.16.4-next.1 ### Patch Changes diff --git a/packages/integration/package.json b/packages/integration/package.json index 53a380b1c9..053a3e793a 100644 --- a/packages/integration/package.json +++ b/packages/integration/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/integration", - "version": "1.16.4-next.1", + "version": "1.17.0-next.2", "description": "Helpers for managing integrations towards external systems", "backstage": { "role": "common-library" diff --git a/packages/repo-tools/CHANGELOG.md b/packages/repo-tools/CHANGELOG.md index 8dcd1341f7..917b364d2e 100644 --- a/packages/repo-tools/CHANGELOG.md +++ b/packages/repo-tools/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/repo-tools +## 0.13.3-next.2 + +### Patch Changes + +- b229476: Support passing additional properties to OpenAPI server generator +- Updated dependencies + - @backstage/cli-node@0.2.13 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/catalog-model@1.7.3 + - @backstage/cli-common@0.1.15 + - @backstage/config-loader@1.10.1-next.0 + - @backstage/errors@1.2.7 + ## 0.13.3-next.1 ### Patch Changes diff --git a/packages/repo-tools/package.json b/packages/repo-tools/package.json index a8481d81c9..1ec1d1a48f 100644 --- a/packages/repo-tools/package.json +++ b/packages/repo-tools/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/repo-tools", - "version": "0.13.3-next.1", + "version": "0.13.3-next.2", "description": "CLI for Backstage repo tooling ", "backstage": { "role": "cli" diff --git a/packages/scaffolder-internal/CHANGELOG.md b/packages/scaffolder-internal/CHANGELOG.md index 78d4f59df4..9ba8836fff 100644 --- a/packages/scaffolder-internal/CHANGELOG.md +++ b/packages/scaffolder-internal/CHANGELOG.md @@ -1,5 +1,13 @@ # @internal/scaffolder +## 0.0.9-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/plugin-scaffolder-react@1.16.0-next.2 + ## 0.0.9-next.1 ### Patch Changes diff --git a/packages/scaffolder-internal/package.json b/packages/scaffolder-internal/package.json index 44e409cb48..6dda7d0b41 100644 --- a/packages/scaffolder-internal/package.json +++ b/packages/scaffolder-internal/package.json @@ -1,6 +1,6 @@ { "name": "@internal/scaffolder", - "version": "0.0.9-next.1", + "version": "0.0.9-next.2", "backstage": { "role": "web-library", "inline": true diff --git a/packages/techdocs-cli-embedded-app/CHANGELOG.md b/packages/techdocs-cli-embedded-app/CHANGELOG.md index 570bfbd446..8afd99d6fb 100644 --- a/packages/techdocs-cli-embedded-app/CHANGELOG.md +++ b/packages/techdocs-cli-embedded-app/CHANGELOG.md @@ -1,5 +1,24 @@ # techdocs-cli-embedded-app +## 0.2.108-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/test-utils@1.7.8-next.1 + - @backstage/core-app-api@1.16.2-next.0 + - @backstage/plugin-techdocs@1.12.6-next.2 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/plugin-catalog@1.29.1-next.2 + - @backstage/app-defaults@1.6.2-next.1 + - @backstage/cli@0.32.1-next.2 + - @backstage/config@1.3.2 + - @backstage/core-components@0.17.2-next.1 + - @backstage/integration-react@1.2.7-next.2 + - @backstage/plugin-techdocs-react@1.2.17-next.1 + - @backstage/catalog-model@1.7.3 + - @backstage/theme@0.6.6-next.0 + ## 0.2.108-next.1 ### Patch Changes diff --git a/packages/techdocs-cli-embedded-app/package.json b/packages/techdocs-cli-embedded-app/package.json index 875c80a2b1..cd95f9e37e 100644 --- a/packages/techdocs-cli-embedded-app/package.json +++ b/packages/techdocs-cli-embedded-app/package.json @@ -1,6 +1,6 @@ { "name": "techdocs-cli-embedded-app", - "version": "0.2.108-next.1", + "version": "0.2.108-next.2", "backstage": { "role": "frontend" }, diff --git a/packages/techdocs-cli/CHANGELOG.md b/packages/techdocs-cli/CHANGELOG.md index a116a77b24..e9a719087e 100644 --- a/packages/techdocs-cli/CHANGELOG.md +++ b/packages/techdocs-cli/CHANGELOG.md @@ -1,5 +1,16 @@ # @techdocs/cli +## 1.9.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.10.0-next.2 + - @backstage/config@1.3.2 + - @backstage/plugin-techdocs-node@1.13.3-next.2 + - @backstage/catalog-model@1.7.3 + - @backstage/cli-common@0.1.15 + ## 1.9.3-next.1 ### Patch Changes diff --git a/packages/techdocs-cli/package.json b/packages/techdocs-cli/package.json index 3ff6938e9c..e44b6fb107 100644 --- a/packages/techdocs-cli/package.json +++ b/packages/techdocs-cli/package.json @@ -1,6 +1,6 @@ { "name": "@techdocs/cli", - "version": "1.9.3-next.1", + "version": "1.9.3-next.2", "description": "Utility CLI for managing TechDocs sites in Backstage.", "backstage": { "role": "cli" diff --git a/packages/test-utils/CHANGELOG.md b/packages/test-utils/CHANGELOG.md index 42bfd5315e..2561b80f57 100644 --- a/packages/test-utils/CHANGELOG.md +++ b/packages/test-utils/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/test-utils +## 1.7.8-next.1 + +### Patch Changes + +- b573341: Added support for interpolating JSX elements with the `MockTranslationApi`. +- Updated dependencies + - @backstage/core-app-api@1.16.2-next.0 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/config@1.3.2 + - @backstage/plugin-permission-react@0.4.34-next.1 + - @backstage/theme@0.6.6-next.0 + - @backstage/types@1.2.1 + - @backstage/plugin-permission-common@0.9.0-next.0 + ## 1.7.8-next.0 ### Patch Changes diff --git a/packages/test-utils/package.json b/packages/test-utils/package.json index fae2069bbc..0839e44b69 100644 --- a/packages/test-utils/package.json +++ b/packages/test-utils/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/test-utils", - "version": "1.7.8-next.0", + "version": "1.7.8-next.1", "description": "Utilities to test Backstage plugins and apps.", "backstage": { "role": "web-library" diff --git a/plugins/api-docs/CHANGELOG.md b/plugins/api-docs/CHANGELOG.md index 78358a8970..ea622deaba 100644 --- a/plugins/api-docs/CHANGELOG.md +++ b/plugins/api-docs/CHANGELOG.md @@ -1,5 +1,20 @@ # @backstage/plugin-api-docs +## 0.12.7-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/plugin-catalog@1.29.1-next.2 + - @backstage/core-compat-api@0.4.2-next.2 + - @backstage/core-components@0.17.2-next.1 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/plugin-catalog-react@1.18.0-next.2 + - @backstage/plugin-permission-react@0.4.34-next.1 + - @backstage/catalog-model@1.7.3 + - @backstage/plugin-catalog-common@1.1.4-next.0 + ## 0.12.7-next.1 ### Patch Changes diff --git a/plugins/api-docs/package.json b/plugins/api-docs/package.json index 389f8265b6..4854695e23 100644 --- a/plugins/api-docs/package.json +++ b/plugins/api-docs/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-api-docs", - "version": "0.12.7-next.1", + "version": "0.12.7-next.2", "description": "A Backstage plugin that helps represent API entities in the frontend", "backstage": { "role": "frontend-plugin", diff --git a/plugins/app-visualizer/CHANGELOG.md b/plugins/app-visualizer/CHANGELOG.md index 4f3b6f2178..9d0513b41a 100644 --- a/plugins/app-visualizer/CHANGELOG.md +++ b/plugins/app-visualizer/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-app-visualizer +## 0.1.19-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/core-components@0.17.2-next.1 + - @backstage/frontend-plugin-api@0.10.2-next.1 + ## 0.1.19-next.0 ### Patch Changes diff --git a/plugins/app-visualizer/package.json b/plugins/app-visualizer/package.json index 057a383b20..d481e89225 100644 --- a/plugins/app-visualizer/package.json +++ b/plugins/app-visualizer/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-app-visualizer", - "version": "0.1.19-next.0", + "version": "0.1.19-next.1", "description": "Visualizes the Backstage app structure", "backstage": { "role": "frontend-plugin", diff --git a/plugins/app/CHANGELOG.md b/plugins/app/CHANGELOG.md index a45b254f79..790b735bb1 100644 --- a/plugins/app/CHANGELOG.md +++ b/plugins/app/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-app +## 0.1.9-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/core-components@0.17.2-next.1 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/integration-react@1.2.7-next.2 + - @backstage/plugin-permission-react@0.4.34-next.1 + - @backstage/theme@0.6.6-next.0 + - @backstage/types@1.2.1 + ## 0.1.9-next.1 ### Patch Changes diff --git a/plugins/app/package.json b/plugins/app/package.json index 5cada40ebc..fed1f47f4c 100644 --- a/plugins/app/package.json +++ b/plugins/app/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-app", - "version": "0.1.9-next.1", + "version": "0.1.9-next.2", "backstage": { "role": "frontend-plugin", "pluginId": "app", diff --git a/plugins/auth-react/CHANGELOG.md b/plugins/auth-react/CHANGELOG.md index 238b119291..0a1043daa1 100644 --- a/plugins/auth-react/CHANGELOG.md +++ b/plugins/auth-react/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-auth-react +## 0.1.15-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/core-components@0.17.2-next.1 + - @backstage/errors@1.2.7 + ## 0.1.15-next.0 ### Patch Changes diff --git a/plugins/auth-react/package.json b/plugins/auth-react/package.json index 214276dbc6..3a04afbf51 100644 --- a/plugins/auth-react/package.json +++ b/plugins/auth-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-react", - "version": "0.1.15-next.0", + "version": "0.1.15-next.1", "description": "Web library for the auth plugin", "backstage": { "role": "web-library", diff --git a/plugins/bitbucket-cloud-common/CHANGELOG.md b/plugins/bitbucket-cloud-common/CHANGELOG.md index 68006127be..5cff146b61 100644 --- a/plugins/bitbucket-cloud-common/CHANGELOG.md +++ b/plugins/bitbucket-cloud-common/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/plugin-bitbucket-cloud-common +## 0.3.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + ## 0.3.0-next.1 ### Patch Changes diff --git a/plugins/bitbucket-cloud-common/package.json b/plugins/bitbucket-cloud-common/package.json index ecacd6966c..7b336e3455 100644 --- a/plugins/bitbucket-cloud-common/package.json +++ b/plugins/bitbucket-cloud-common/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-bitbucket-cloud-common", - "version": "0.3.0-next.1", + "version": "0.3.0-next.2", "description": "Common functionalities for bitbucket-cloud plugins", "backstage": { "role": "common-library", diff --git a/plugins/catalog-backend-module-aws/CHANGELOG.md b/plugins/catalog-backend-module-aws/CHANGELOG.md index 929fac5677..600a4f7e93 100644 --- a/plugins/catalog-backend-module-aws/CHANGELOG.md +++ b/plugins/catalog-backend-module-aws/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-catalog-backend-module-aws +## 0.4.11-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration-aws-node@0.1.16-next.0 + - @backstage/integration@1.17.0-next.2 + - @backstage/backend-defaults@0.10.0-next.2 + - @backstage/config@1.3.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/catalog-model@1.7.3 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-common@1.1.4-next.0 + - @backstage/plugin-catalog-node@1.17.0-next.1 + - @backstage/plugin-kubernetes-common@0.9.5-next.0 + ## 0.4.11-next.1 ### Patch Changes diff --git a/plugins/catalog-backend-module-aws/package.json b/plugins/catalog-backend-module-aws/package.json index 0308b1f6e1..157fe665b0 100644 --- a/plugins/catalog-backend-module-aws/package.json +++ b/plugins/catalog-backend-module-aws/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-aws", - "version": "0.4.11-next.1", + "version": "0.4.11-next.2", "description": "A Backstage catalog backend module that helps integrate towards AWS", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend-module-azure/CHANGELOG.md b/plugins/catalog-backend-module-azure/CHANGELOG.md index 600cb2080c..9ce4ec3f4e 100644 --- a/plugins/catalog-backend-module-azure/CHANGELOG.md +++ b/plugins/catalog-backend-module-azure/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-catalog-backend-module-azure +## 0.3.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/config@1.3.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/plugin-catalog-common@1.1.4-next.0 + - @backstage/plugin-catalog-node@1.17.0-next.1 + ## 0.3.5-next.1 ### Patch Changes diff --git a/plugins/catalog-backend-module-azure/package.json b/plugins/catalog-backend-module-azure/package.json index e8dcea480b..5fc27e581d 100644 --- a/plugins/catalog-backend-module-azure/package.json +++ b/plugins/catalog-backend-module-azure/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-azure", - "version": "0.3.5-next.1", + "version": "0.3.5-next.2", "description": "A Backstage catalog backend module that helps integrate towards Azure", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend-module-bitbucket-cloud/CHANGELOG.md b/plugins/catalog-backend-module-bitbucket-cloud/CHANGELOG.md index c203b575c8..f562d20651 100644 --- a/plugins/catalog-backend-module-bitbucket-cloud/CHANGELOG.md +++ b/plugins/catalog-backend-module-bitbucket-cloud/CHANGELOG.md @@ -1,5 +1,20 @@ # @backstage/plugin-catalog-backend-module-bitbucket-cloud +## 0.4.8-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/config@1.3.2 + - @backstage/plugin-bitbucket-cloud-common@0.3.0-next.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/plugin-catalog-common@1.1.4-next.0 + - @backstage/plugin-catalog-node@1.17.0-next.1 + - @backstage/plugin-events-node@0.4.11-next.1 + ## 0.4.8-next.1 ### Patch Changes diff --git a/plugins/catalog-backend-module-bitbucket-cloud/package.json b/plugins/catalog-backend-module-bitbucket-cloud/package.json index eeae1afeb7..8d73b5fd74 100644 --- a/plugins/catalog-backend-module-bitbucket-cloud/package.json +++ b/plugins/catalog-backend-module-bitbucket-cloud/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-bitbucket-cloud", - "version": "0.4.8-next.1", + "version": "0.4.8-next.2", "description": "A Backstage catalog backend module that helps integrate towards Bitbucket Cloud", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend-module-bitbucket-server/CHANGELOG.md b/plugins/catalog-backend-module-bitbucket-server/CHANGELOG.md index 18ea70c09b..ecd451bd5a 100644 --- a/plugins/catalog-backend-module-bitbucket-server/CHANGELOG.md +++ b/plugins/catalog-backend-module-bitbucket-server/CHANGELOG.md @@ -1,5 +1,20 @@ # @backstage/plugin-catalog-backend-module-bitbucket-server +## 0.4.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/config@1.3.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-common@1.1.4-next.0 + - @backstage/plugin-catalog-node@1.17.0-next.1 + - @backstage/plugin-events-node@0.4.11-next.1 + ## 0.4.1-next.1 ### Patch Changes diff --git a/plugins/catalog-backend-module-bitbucket-server/package.json b/plugins/catalog-backend-module-bitbucket-server/package.json index 0c3b601cb1..9dd5e16e55 100644 --- a/plugins/catalog-backend-module-bitbucket-server/package.json +++ b/plugins/catalog-backend-module-bitbucket-server/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-bitbucket-server", - "version": "0.4.1-next.1", + "version": "0.4.1-next.2", "backstage": { "role": "backend-plugin-module", "pluginId": "catalog", diff --git a/plugins/catalog-backend-module-gerrit/CHANGELOG.md b/plugins/catalog-backend-module-gerrit/CHANGELOG.md index 66de538a1a..e207e24dcf 100644 --- a/plugins/catalog-backend-module-gerrit/CHANGELOG.md +++ b/plugins/catalog-backend-module-gerrit/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-catalog-backend-module-gerrit +## 0.3.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/config@1.3.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-common@1.1.4-next.0 + - @backstage/plugin-catalog-node@1.17.0-next.1 + ## 0.3.2-next.1 ### Patch Changes diff --git a/plugins/catalog-backend-module-gerrit/package.json b/plugins/catalog-backend-module-gerrit/package.json index c907cb6bfd..9d176a1a9d 100644 --- a/plugins/catalog-backend-module-gerrit/package.json +++ b/plugins/catalog-backend-module-gerrit/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-gerrit", - "version": "0.3.2-next.1", + "version": "0.3.2-next.2", "backstage": { "role": "backend-plugin-module", "pluginId": "catalog", diff --git a/plugins/catalog-backend-module-github-org/CHANGELOG.md b/plugins/catalog-backend-module-github-org/CHANGELOG.md index 7a8de4a045..14fb414c48 100644 --- a/plugins/catalog-backend-module-github-org/CHANGELOG.md +++ b/plugins/catalog-backend-module-github-org/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-catalog-backend-module-github-org +## 0.3.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.2 + - @backstage/plugin-catalog-backend-module-github@0.9.0-next.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/plugin-catalog-node@1.17.0-next.1 + - @backstage/plugin-events-node@0.4.11-next.1 + ## 0.3.10-next.1 ### Patch Changes diff --git a/plugins/catalog-backend-module-github-org/package.json b/plugins/catalog-backend-module-github-org/package.json index c445e926c6..ce47411b0a 100644 --- a/plugins/catalog-backend-module-github-org/package.json +++ b/plugins/catalog-backend-module-github-org/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-github-org", - "version": "0.3.10-next.1", + "version": "0.3.10-next.2", "description": "The github-org backend module for the catalog plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend-module-github/CHANGELOG.md b/plugins/catalog-backend-module-github/CHANGELOG.md index 9e28f853f9..51e1498de5 100644 --- a/plugins/catalog-backend-module-github/CHANGELOG.md +++ b/plugins/catalog-backend-module-github/CHANGELOG.md @@ -1,5 +1,20 @@ # @backstage/plugin-catalog-backend-module-github +## 0.9.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/config@1.3.2 + - @backstage/plugin-catalog-backend@2.0.0-next.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/plugin-catalog-common@1.1.4-next.0 + - @backstage/plugin-catalog-node@1.17.0-next.1 + - @backstage/plugin-events-node@0.4.11-next.1 + ## 0.9.0-next.1 ### Patch Changes diff --git a/plugins/catalog-backend-module-github/package.json b/plugins/catalog-backend-module-github/package.json index 84fd003bcb..bb2e436804 100644 --- a/plugins/catalog-backend-module-github/package.json +++ b/plugins/catalog-backend-module-github/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-github", - "version": "0.9.0-next.1", + "version": "0.9.0-next.2", "description": "A Backstage catalog backend module that helps integrate towards GitHub", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend-module-gitlab-org/CHANGELOG.md b/plugins/catalog-backend-module-gitlab-org/CHANGELOG.md index 64f599f3a0..7d5701a31d 100644 --- a/plugins/catalog-backend-module-gitlab-org/CHANGELOG.md +++ b/plugins/catalog-backend-module-gitlab-org/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-catalog-backend-module-gitlab-org +## 0.2.9-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend-module-gitlab@0.6.6-next.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/plugin-catalog-node@1.17.0-next.1 + - @backstage/plugin-events-node@0.4.11-next.1 + ## 0.2.9-next.1 ### Patch Changes diff --git a/plugins/catalog-backend-module-gitlab-org/package.json b/plugins/catalog-backend-module-gitlab-org/package.json index 39f1864944..44015c6c16 100644 --- a/plugins/catalog-backend-module-gitlab-org/package.json +++ b/plugins/catalog-backend-module-gitlab-org/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-gitlab-org", - "version": "0.2.9-next.1", + "version": "0.2.9-next.2", "description": "The gitlab-org backend module for the catalog plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend-module-gitlab/CHANGELOG.md b/plugins/catalog-backend-module-gitlab/CHANGELOG.md index 44623a8405..76e67018cf 100644 --- a/plugins/catalog-backend-module-gitlab/CHANGELOG.md +++ b/plugins/catalog-backend-module-gitlab/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-catalog-backend-module-gitlab +## 0.6.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/backend-defaults@0.10.0-next.2 + - @backstage/config@1.3.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/catalog-model@1.7.3 + - @backstage/plugin-catalog-common@1.1.4-next.0 + - @backstage/plugin-catalog-node@1.17.0-next.1 + - @backstage/plugin-events-node@0.4.11-next.1 + ## 0.6.6-next.1 ### Patch Changes diff --git a/plugins/catalog-backend-module-gitlab/package.json b/plugins/catalog-backend-module-gitlab/package.json index 10f5c92b8b..e129b83cd9 100644 --- a/plugins/catalog-backend-module-gitlab/package.json +++ b/plugins/catalog-backend-module-gitlab/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-gitlab", - "version": "0.6.6-next.1", + "version": "0.6.6-next.2", "description": "A Backstage catalog backend module that helps integrate towards GitLab", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend-module-incremental-ingestion/CHANGELOG.md b/plugins/catalog-backend-module-incremental-ingestion/CHANGELOG.md index 988e281d76..98efe68781 100644 --- a/plugins/catalog-backend-module-incremental-ingestion/CHANGELOG.md +++ b/plugins/catalog-backend-module-incremental-ingestion/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-catalog-backend-module-incremental-ingestion +## 0.7.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.10.0-next.2 + - @backstage/config@1.3.2 + - @backstage/plugin-catalog-backend@2.0.0-next.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/catalog-model@1.7.3 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-catalog-node@1.17.0-next.1 + - @backstage/plugin-events-node@0.4.11-next.1 + - @backstage/plugin-permission-common@0.9.0-next.0 + ## 0.7.0-next.1 ### Patch Changes diff --git a/plugins/catalog-backend-module-incremental-ingestion/package.json b/plugins/catalog-backend-module-incremental-ingestion/package.json index fdf6641505..60b3873e0b 100644 --- a/plugins/catalog-backend-module-incremental-ingestion/package.json +++ b/plugins/catalog-backend-module-incremental-ingestion/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-incremental-ingestion", - "version": "0.7.0-next.1", + "version": "0.7.0-next.2", "description": "An entity provider for streaming large asset sources into the catalog", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend-module-ldap/CHANGELOG.md b/plugins/catalog-backend-module-ldap/CHANGELOG.md index cc7072e891..431a672828 100644 --- a/plugins/catalog-backend-module-ldap/CHANGELOG.md +++ b/plugins/catalog-backend-module-ldap/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-catalog-backend-module-ldap +## 0.11.5-next.2 + +### Patch Changes + +- e253d1d: Improves error reporting for missing metadata.name in LDAP catalog provider. +- Updated dependencies + - @backstage/config@1.3.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/catalog-model@1.7.3 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-catalog-common@1.1.4-next.0 + - @backstage/plugin-catalog-node@1.17.0-next.1 + ## 0.11.5-next.1 ### Patch Changes diff --git a/plugins/catalog-backend-module-ldap/package.json b/plugins/catalog-backend-module-ldap/package.json index bd04eb70c3..4d9888ac6d 100644 --- a/plugins/catalog-backend-module-ldap/package.json +++ b/plugins/catalog-backend-module-ldap/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-ldap", - "version": "0.11.5-next.1", + "version": "0.11.5-next.2", "description": "A Backstage catalog backend module that helps integrate towards LDAP", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend-module-logs/CHANGELOG.md b/plugins/catalog-backend-module-logs/CHANGELOG.md index 73ba915c4a..a7e0d37921 100644 --- a/plugins/catalog-backend-module-logs/CHANGELOG.md +++ b/plugins/catalog-backend-module-logs/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-catalog-backend-module-logs +## 0.1.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@2.0.0-next.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/plugin-events-node@0.4.11-next.1 + ## 0.1.10-next.1 ### Patch Changes diff --git a/plugins/catalog-backend-module-logs/package.json b/plugins/catalog-backend-module-logs/package.json index 27a5a04766..a6f748e4ca 100644 --- a/plugins/catalog-backend-module-logs/package.json +++ b/plugins/catalog-backend-module-logs/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-logs", - "version": "0.1.10-next.1", + "version": "0.1.10-next.2", "description": "A module that subscribes to catalog related events and logs them.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend-module-openapi/CHANGELOG.md b/plugins/catalog-backend-module-openapi/CHANGELOG.md index b403e55dd1..c6b53d288d 100644 --- a/plugins/catalog-backend-module-openapi/CHANGELOG.md +++ b/plugins/catalog-backend-module-openapi/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-catalog-backend-module-openapi +## 0.2.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/catalog-model@1.7.3 + - @backstage/types@1.2.1 + - @backstage/plugin-catalog-common@1.1.4-next.0 + - @backstage/plugin-catalog-node@1.17.0-next.1 + ## 0.2.10-next.1 ### Patch Changes diff --git a/plugins/catalog-backend-module-openapi/package.json b/plugins/catalog-backend-module-openapi/package.json index 75ea03d23d..5f0c106b0d 100644 --- a/plugins/catalog-backend-module-openapi/package.json +++ b/plugins/catalog-backend-module-openapi/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-openapi", - "version": "0.2.10-next.1", + "version": "0.2.10-next.2", "description": "A Backstage catalog backend module that helps with OpenAPI specifications", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend/CHANGELOG.md b/plugins/catalog-backend/CHANGELOG.md index f5fd6342f9..1f9802a3e8 100644 --- a/plugins/catalog-backend/CHANGELOG.md +++ b/plugins/catalog-backend/CHANGELOG.md @@ -1,5 +1,24 @@ # @backstage/plugin-catalog-backend +## 2.0.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/backend-openapi-utils@0.5.3-next.1 + - @backstage/config@1.3.2 + - @backstage/plugin-permission-node@0.10.0-next.1 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-catalog-common@1.1.4-next.0 + - @backstage/plugin-catalog-node@1.17.0-next.1 + - @backstage/plugin-events-node@0.4.11-next.1 + - @backstage/plugin-permission-common@0.9.0-next.0 + ## 2.0.0-next.1 ### Major Changes diff --git a/plugins/catalog-backend/package.json b/plugins/catalog-backend/package.json index 8f2f8afe94..3b548a9f67 100644 --- a/plugins/catalog-backend/package.json +++ b/plugins/catalog-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend", - "version": "2.0.0-next.1", + "version": "2.0.0-next.2", "description": "The Backstage backend plugin that provides the Backstage catalog", "backstage": { "role": "backend-plugin", diff --git a/plugins/catalog-graph/CHANGELOG.md b/plugins/catalog-graph/CHANGELOG.md index 269dcf2a4f..7f669774f8 100644 --- a/plugins/catalog-graph/CHANGELOG.md +++ b/plugins/catalog-graph/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-catalog-graph +## 0.4.19-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/core-compat-api@0.4.2-next.2 + - @backstage/core-components@0.17.2-next.1 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/plugin-catalog-react@1.18.0-next.2 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/types@1.2.1 + ## 0.4.19-next.1 ### Patch Changes diff --git a/plugins/catalog-graph/package.json b/plugins/catalog-graph/package.json index 08d88ceca7..d674ca6f95 100644 --- a/plugins/catalog-graph/package.json +++ b/plugins/catalog-graph/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-graph", - "version": "0.4.19-next.1", + "version": "0.4.19-next.2", "backstage": { "role": "frontend-plugin", "pluginId": "catalog-graph", diff --git a/plugins/catalog-import/CHANGELOG.md b/plugins/catalog-import/CHANGELOG.md index 8ede217379..3722f55f86 100644 --- a/plugins/catalog-import/CHANGELOG.md +++ b/plugins/catalog-import/CHANGELOG.md @@ -1,5 +1,28 @@ # @backstage/plugin-catalog-import +## 0.13.0-next.2 + +### Minor Changes + +- e2fd549: **BREAKING**: `generateStepper` and `defaultGenerateStepper` now require a translation argument to be passed through for supporting translations. + +### Patch Changes + +- 66a1140: Add i18n support for `catalog-import` plugin. +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/config@1.3.2 + - @backstage/core-compat-api@0.4.2-next.2 + - @backstage/core-components@0.17.2-next.1 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/integration-react@1.2.7-next.2 + - @backstage/plugin-catalog-react@1.18.0-next.2 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-common@1.1.4-next.0 + ## 0.12.14-next.1 ### Patch Changes diff --git a/plugins/catalog-import/package.json b/plugins/catalog-import/package.json index b6730e7fd6..cb046ecfb4 100644 --- a/plugins/catalog-import/package.json +++ b/plugins/catalog-import/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-import", - "version": "0.12.14-next.1", + "version": "0.13.0-next.2", "description": "A Backstage plugin the helps you import entities into your catalog", "backstage": { "role": "frontend-plugin", diff --git a/plugins/catalog-react/CHANGELOG.md b/plugins/catalog-react/CHANGELOG.md index 4a261ad2aa..7ca221a40d 100644 --- a/plugins/catalog-react/CHANGELOG.md +++ b/plugins/catalog-react/CHANGELOG.md @@ -1,5 +1,25 @@ # @backstage/plugin-catalog-react +## 1.18.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/core-compat-api@0.4.2-next.2 + - @backstage/core-components@0.17.2-next.1 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/frontend-test-utils@0.3.2-next.2 + - @backstage/integration-react@1.2.7-next.2 + - @backstage/plugin-permission-react@0.4.34-next.1 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-catalog-common@1.1.4-next.0 + - @backstage/plugin-permission-common@0.9.0-next.0 + ## 1.18.0-next.1 ### Patch Changes diff --git a/plugins/catalog-react/package.json b/plugins/catalog-react/package.json index aeeb398cad..bb9d1bde29 100644 --- a/plugins/catalog-react/package.json +++ b/plugins/catalog-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-react", - "version": "1.18.0-next.1", + "version": "1.18.0-next.2", "description": "A frontend library that helps other Backstage plugins interact with the catalog", "backstage": { "role": "web-library", diff --git a/plugins/catalog-unprocessed-entities/CHANGELOG.md b/plugins/catalog-unprocessed-entities/CHANGELOG.md index d85e0919cd..e7b747928f 100644 --- a/plugins/catalog-unprocessed-entities/CHANGELOG.md +++ b/plugins/catalog-unprocessed-entities/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-catalog-unprocessed-entities +## 0.2.17-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/core-compat-api@0.4.2-next.2 + - @backstage/core-components@0.17.2-next.1 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/catalog-model@1.7.3 + - @backstage/errors@1.2.7 + ## 0.2.17-next.1 ### Patch Changes diff --git a/plugins/catalog-unprocessed-entities/package.json b/plugins/catalog-unprocessed-entities/package.json index e985ba2d9c..c6663150c0 100644 --- a/plugins/catalog-unprocessed-entities/package.json +++ b/plugins/catalog-unprocessed-entities/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-unprocessed-entities", - "version": "0.2.17-next.1", + "version": "0.2.17-next.2", "backstage": { "role": "frontend-plugin", "pluginId": "catalog-unprocessed-entities", diff --git a/plugins/catalog/CHANGELOG.md b/plugins/catalog/CHANGELOG.md index 9cfa2876ba..b410114940 100644 --- a/plugins/catalog/CHANGELOG.md +++ b/plugins/catalog/CHANGELOG.md @@ -1,5 +1,28 @@ # @backstage/plugin-catalog +## 1.29.1-next.2 + +### Patch Changes + +- bf85d37: Fix for missing `routeRef` when using `core-plugin-api` in a dialog context +- Updated dependencies + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/core-compat-api@0.4.2-next.2 + - @backstage/core-components@0.17.2-next.1 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/integration-react@1.2.7-next.2 + - @backstage/plugin-catalog-react@1.18.0-next.2 + - @backstage/plugin-permission-react@0.4.34-next.1 + - @backstage/plugin-search-react@1.9.0-next.1 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-catalog-common@1.1.4-next.0 + - @backstage/plugin-scaffolder-common@1.5.11-next.0 + - @backstage/plugin-search-common@1.2.18-next.0 + ## 1.29.1-next.1 ### Patch Changes diff --git a/plugins/catalog/package.json b/plugins/catalog/package.json index 87f0ce2052..8eda14596b 100644 --- a/plugins/catalog/package.json +++ b/plugins/catalog/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog", - "version": "1.29.1-next.1", + "version": "1.29.1-next.2", "description": "The Backstage plugin for browsing the Backstage catalog", "backstage": { "role": "frontend-plugin", diff --git a/plugins/config-schema/CHANGELOG.md b/plugins/config-schema/CHANGELOG.md index 34168e5c87..ff5cd9d89f 100644 --- a/plugins/config-schema/CHANGELOG.md +++ b/plugins/config-schema/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-config-schema +## 0.1.68-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/core-components@0.17.2-next.1 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + ## 0.1.68-next.0 ### Patch Changes diff --git a/plugins/config-schema/package.json b/plugins/config-schema/package.json index f2ede3c05e..f21d03be67 100644 --- a/plugins/config-schema/package.json +++ b/plugins/config-schema/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-config-schema", - "version": "0.1.68-next.0", + "version": "0.1.68-next.1", "description": "A Backstage plugin that lets you browse the configuration schema of your app", "backstage": { "role": "frontend-plugin", diff --git a/plugins/devtools-backend/CHANGELOG.md b/plugins/devtools-backend/CHANGELOG.md index b1875099e6..b51d75a457 100644 --- a/plugins/devtools-backend/CHANGELOG.md +++ b/plugins/devtools-backend/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-devtools-backend +## 0.5.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.10.0-next.2 + - @backstage/config@1.3.2 + - @backstage/plugin-permission-node@0.10.0-next.1 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/cli-common@0.1.15 + - @backstage/config-loader@1.10.1-next.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-devtools-common@0.1.16-next.0 + - @backstage/plugin-permission-common@0.9.0-next.0 + ## 0.5.5-next.1 ### Patch Changes diff --git a/plugins/devtools-backend/package.json b/plugins/devtools-backend/package.json index abf589f322..f3831118b7 100644 --- a/plugins/devtools-backend/package.json +++ b/plugins/devtools-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-devtools-backend", - "version": "0.5.5-next.1", + "version": "0.5.5-next.2", "backstage": { "role": "backend-plugin", "pluginId": "devtools", diff --git a/plugins/devtools/CHANGELOG.md b/plugins/devtools/CHANGELOG.md index 7d3bfebced..267df2e952 100644 --- a/plugins/devtools/CHANGELOG.md +++ b/plugins/devtools/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-devtools +## 0.1.27-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/core-compat-api@0.4.2-next.2 + - @backstage/core-components@0.17.2-next.1 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/plugin-permission-react@0.4.34-next.1 + - @backstage/errors@1.2.7 + - @backstage/plugin-devtools-common@0.1.16-next.0 + ## 0.1.27-next.1 ### Patch Changes diff --git a/plugins/devtools/package.json b/plugins/devtools/package.json index 2c6cac0cf4..2a1c4890a1 100644 --- a/plugins/devtools/package.json +++ b/plugins/devtools/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-devtools", - "version": "0.1.27-next.1", + "version": "0.1.27-next.2", "backstage": { "role": "frontend-plugin", "pluginId": "devtools", diff --git a/plugins/events-backend-module-github/CHANGELOG.md b/plugins/events-backend-module-github/CHANGELOG.md index 72262b82c9..db61a7eba0 100644 --- a/plugins/events-backend-module-github/CHANGELOG.md +++ b/plugins/events-backend-module-github/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-events-backend-module-github +## 0.4.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/config@1.3.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/types@1.2.1 + - @backstage/plugin-events-node@0.4.11-next.1 + ## 0.4.0-next.1 ### Minor Changes diff --git a/plugins/events-backend-module-github/package.json b/plugins/events-backend-module-github/package.json index b83a52e308..8b38788c91 100644 --- a/plugins/events-backend-module-github/package.json +++ b/plugins/events-backend-module-github/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-events-backend-module-github", - "version": "0.4.0-next.1", + "version": "0.4.0-next.2", "backstage": { "role": "backend-plugin-module", "pluginId": "events", diff --git a/plugins/example-todo-list/CHANGELOG.md b/plugins/example-todo-list/CHANGELOG.md index de493d3af3..9af56aa38d 100644 --- a/plugins/example-todo-list/CHANGELOG.md +++ b/plugins/example-todo-list/CHANGELOG.md @@ -1,5 +1,13 @@ # @internal/plugin-todo-list +## 1.0.39-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/core-components@0.17.2-next.1 + ## 1.0.39-next.0 ### Patch Changes diff --git a/plugins/example-todo-list/package.json b/plugins/example-todo-list/package.json index 2e33a7825c..620f8ad05b 100644 --- a/plugins/example-todo-list/package.json +++ b/plugins/example-todo-list/package.json @@ -1,6 +1,6 @@ { "name": "@internal/plugin-todo-list", - "version": "1.0.39-next.0", + "version": "1.0.39-next.1", "backstage": { "role": "frontend-plugin", "pluginId": "todo-list", diff --git a/plugins/home-react/CHANGELOG.md b/plugins/home-react/CHANGELOG.md index 009ef27cc2..06a3cdff85 100644 --- a/plugins/home-react/CHANGELOG.md +++ b/plugins/home-react/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-home-react +## 0.1.26-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/core-components@0.17.2-next.1 + ## 0.1.26-next.0 ### Patch Changes diff --git a/plugins/home-react/package.json b/plugins/home-react/package.json index 7d105e22b4..43f4e79aed 100644 --- a/plugins/home-react/package.json +++ b/plugins/home-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-home-react", - "version": "0.1.26-next.0", + "version": "0.1.26-next.1", "description": "A Backstage plugin that contains react components helps you build a home page", "backstage": { "role": "web-library", diff --git a/plugins/home/CHANGELOG.md b/plugins/home/CHANGELOG.md index 4496735e3f..73eb07ffd7 100644 --- a/plugins/home/CHANGELOG.md +++ b/plugins/home/CHANGELOG.md @@ -1,5 +1,22 @@ # @backstage/plugin-home +## 0.8.8-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.16.2-next.0 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/config@1.3.2 + - @backstage/core-compat-api@0.4.2-next.2 + - @backstage/core-components@0.17.2-next.1 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/plugin-catalog-react@1.18.0-next.2 + - @backstage/plugin-home-react@0.1.26-next.1 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/theme@0.6.6-next.0 + ## 0.8.8-next.1 ### Patch Changes diff --git a/plugins/home/package.json b/plugins/home/package.json index c89a9db849..c832e8f888 100644 --- a/plugins/home/package.json +++ b/plugins/home/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-home", - "version": "0.8.8-next.1", + "version": "0.8.8-next.2", "description": "A Backstage plugin that helps you build a home page", "backstage": { "role": "frontend-plugin", diff --git a/plugins/kubernetes-backend/CHANGELOG.md b/plugins/kubernetes-backend/CHANGELOG.md index 3c580c2fb8..4147c855f8 100644 --- a/plugins/kubernetes-backend/CHANGELOG.md +++ b/plugins/kubernetes-backend/CHANGELOG.md @@ -1,5 +1,24 @@ # @backstage/plugin-kubernetes-backend +## 0.19.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration-aws-node@0.1.16-next.0 + - @backstage/config@1.3.2 + - @backstage/plugin-auth-node@0.6.3-next.1 + - @backstage/plugin-kubernetes-node@0.3.0-next.1 + - @backstage/plugin-permission-node@0.10.0-next.1 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-catalog-node@1.17.0-next.1 + - @backstage/plugin-kubernetes-common@0.9.5-next.0 + - @backstage/plugin-permission-common@0.9.0-next.0 + ## 0.19.6-next.1 ### Patch Changes diff --git a/plugins/kubernetes-backend/package.json b/plugins/kubernetes-backend/package.json index 730a63db91..95854d8e88 100644 --- a/plugins/kubernetes-backend/package.json +++ b/plugins/kubernetes-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-kubernetes-backend", - "version": "0.19.6-next.1", + "version": "0.19.6-next.2", "description": "A Backstage backend plugin that integrates towards Kubernetes", "backstage": { "role": "backend-plugin", diff --git a/plugins/kubernetes-cluster/CHANGELOG.md b/plugins/kubernetes-cluster/CHANGELOG.md index b7987e7327..aeaf6868ed 100644 --- a/plugins/kubernetes-cluster/CHANGELOG.md +++ b/plugins/kubernetes-cluster/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-kubernetes-cluster +## 0.0.25-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/core-components@0.17.2-next.1 + - @backstage/plugin-catalog-react@1.18.0-next.2 + - @backstage/plugin-kubernetes-react@0.5.7-next.1 + - @backstage/plugin-permission-react@0.4.34-next.1 + - @backstage/catalog-model@1.7.3 + - @backstage/plugin-kubernetes-common@0.9.5-next.0 + ## 0.0.25-next.1 ### Patch Changes diff --git a/plugins/kubernetes-cluster/package.json b/plugins/kubernetes-cluster/package.json index 8e06903b3a..c7d0013ad2 100644 --- a/plugins/kubernetes-cluster/package.json +++ b/plugins/kubernetes-cluster/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-kubernetes-cluster", - "version": "0.0.25-next.1", + "version": "0.0.25-next.2", "description": "A Backstage plugin that shows details of Kubernetes clusters", "backstage": { "role": "frontend-plugin", diff --git a/plugins/kubernetes-react/CHANGELOG.md b/plugins/kubernetes-react/CHANGELOG.md index de556688b3..51e96bbea9 100644 --- a/plugins/kubernetes-react/CHANGELOG.md +++ b/plugins/kubernetes-react/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-kubernetes-react +## 0.5.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/core-components@0.17.2-next.1 + - @backstage/catalog-model@1.7.3 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-kubernetes-common@0.9.5-next.0 + ## 0.5.7-next.0 ### Patch Changes diff --git a/plugins/kubernetes-react/package.json b/plugins/kubernetes-react/package.json index 7beaa4f166..0dd828d88d 100644 --- a/plugins/kubernetes-react/package.json +++ b/plugins/kubernetes-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-kubernetes-react", - "version": "0.5.7-next.0", + "version": "0.5.7-next.1", "description": "Web library for the kubernetes-react plugin", "backstage": { "role": "web-library", diff --git a/plugins/kubernetes/CHANGELOG.md b/plugins/kubernetes/CHANGELOG.md index f63120448e..b2c602a222 100644 --- a/plugins/kubernetes/CHANGELOG.md +++ b/plugins/kubernetes/CHANGELOG.md @@ -1,5 +1,20 @@ # @backstage/plugin-kubernetes +## 0.12.7-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/core-compat-api@0.4.2-next.2 + - @backstage/core-components@0.17.2-next.1 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/plugin-catalog-react@1.18.0-next.2 + - @backstage/plugin-kubernetes-react@0.5.7-next.1 + - @backstage/plugin-permission-react@0.4.34-next.1 + - @backstage/catalog-model@1.7.3 + - @backstage/plugin-kubernetes-common@0.9.5-next.0 + ## 0.12.7-next.1 ### Patch Changes diff --git a/plugins/kubernetes/package.json b/plugins/kubernetes/package.json index c032c3a006..60c232bc51 100644 --- a/plugins/kubernetes/package.json +++ b/plugins/kubernetes/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-kubernetes", - "version": "0.12.7-next.1", + "version": "0.12.7-next.2", "description": "A Backstage plugin that integrates towards Kubernetes", "backstage": { "role": "frontend-plugin", diff --git a/plugins/notifications-backend-module-email/CHANGELOG.md b/plugins/notifications-backend-module-email/CHANGELOG.md index e00c05cd1f..f0baa3195d 100644 --- a/plugins/notifications-backend-module-email/CHANGELOG.md +++ b/plugins/notifications-backend-module-email/CHANGELOG.md @@ -1,5 +1,20 @@ # @backstage/plugin-notifications-backend-module-email +## 0.3.9-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration-aws-node@0.1.16-next.0 + - @backstage/config@1.3.2 + - @backstage/plugin-notifications-node@0.2.15-next.1 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/types@1.2.1 + - @backstage/plugin-catalog-node@1.17.0-next.1 + - @backstage/plugin-notifications-common@0.0.8 + ## 0.3.9-next.1 ### Patch Changes diff --git a/plugins/notifications-backend-module-email/package.json b/plugins/notifications-backend-module-email/package.json index 0b73322476..566a446aa3 100644 --- a/plugins/notifications-backend-module-email/package.json +++ b/plugins/notifications-backend-module-email/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-notifications-backend-module-email", - "version": "0.3.9-next.1", + "version": "0.3.9-next.2", "description": "The email backend module for the notifications plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/notifications-backend-module-slack/CHANGELOG.md b/plugins/notifications-backend-module-slack/CHANGELOG.md index 7842ae0428..2cdd8848a4 100644 --- a/plugins/notifications-backend-module-slack/CHANGELOG.md +++ b/plugins/notifications-backend-module-slack/CHANGELOG.md @@ -1,5 +1,20 @@ # @backstage/plugin-notifications-backend-module-slack +## 0.1.1-next.2 + +### Patch Changes + +- 4f10768: Fix slack notification processor to handle a notification with an empty description +- Updated dependencies + - @backstage/config@1.3.2 + - @backstage/plugin-notifications-node@0.2.15-next.1 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-notifications-common@0.0.8 + ## 0.1.1-next.1 ### Patch Changes diff --git a/plugins/notifications-backend-module-slack/package.json b/plugins/notifications-backend-module-slack/package.json index b6cb16d81a..ce08a5f002 100644 --- a/plugins/notifications-backend-module-slack/package.json +++ b/plugins/notifications-backend-module-slack/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-notifications-backend-module-slack", - "version": "0.1.1-next.1", + "version": "0.1.1-next.2", "description": "The slack backend module for the notifications plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/notifications/CHANGELOG.md b/plugins/notifications/CHANGELOG.md index 0e8034ba48..f171b8a148 100644 --- a/plugins/notifications/CHANGELOG.md +++ b/plugins/notifications/CHANGELOG.md @@ -1,5 +1,20 @@ # @backstage/plugin-notifications +## 0.5.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/core-compat-api@0.4.2-next.2 + - @backstage/core-components@0.17.2-next.1 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/plugin-signals-react@0.0.13-next.0 + - @backstage/errors@1.2.7 + - @backstage/theme@0.6.6-next.0 + - @backstage/types@1.2.1 + - @backstage/plugin-notifications-common@0.0.8 + ## 0.5.5-next.1 ### Patch Changes diff --git a/plugins/notifications/package.json b/plugins/notifications/package.json index d766758857..0a9d383056 100644 --- a/plugins/notifications/package.json +++ b/plugins/notifications/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-notifications", - "version": "0.5.5-next.1", + "version": "0.5.5-next.2", "backstage": { "role": "frontend-plugin", "pluginId": "notifications", diff --git a/plugins/org-react/CHANGELOG.md b/plugins/org-react/CHANGELOG.md index 2af1cb5abd..766eb1b7ca 100644 --- a/plugins/org-react/CHANGELOG.md +++ b/plugins/org-react/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-org-react +## 0.1.38-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/core-components@0.17.2-next.1 + - @backstage/plugin-catalog-react@1.18.0-next.2 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + ## 0.1.38-next.1 ### Patch Changes diff --git a/plugins/org-react/package.json b/plugins/org-react/package.json index f129987c01..8659c22098 100644 --- a/plugins/org-react/package.json +++ b/plugins/org-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-org-react", - "version": "0.1.38-next.1", + "version": "0.1.38-next.2", "backstage": { "role": "web-library", "pluginId": "org", diff --git a/plugins/org/CHANGELOG.md b/plugins/org/CHANGELOG.md index 0fccadefa0..5dd3fe3463 100644 --- a/plugins/org/CHANGELOG.md +++ b/plugins/org/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-org +## 0.6.39-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/core-compat-api@0.4.2-next.2 + - @backstage/core-components@0.17.2-next.1 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/plugin-catalog-react@1.18.0-next.2 + - @backstage/catalog-model@1.7.3 + - @backstage/plugin-catalog-common@1.1.4-next.0 + ## 0.6.39-next.1 ### Patch Changes diff --git a/plugins/org/package.json b/plugins/org/package.json index 893e5060fc..5337c5ec0f 100644 --- a/plugins/org/package.json +++ b/plugins/org/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-org", - "version": "0.6.39-next.1", + "version": "0.6.39-next.2", "description": "A Backstage plugin that helps you create entity pages for your organization", "backstage": { "role": "frontend-plugin", diff --git a/plugins/permission-react/CHANGELOG.md b/plugins/permission-react/CHANGELOG.md index 2d076700ec..d68a3a1566 100644 --- a/plugins/permission-react/CHANGELOG.md +++ b/plugins/permission-react/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-permission-react +## 0.4.34-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/config@1.3.2 + - @backstage/plugin-permission-common@0.9.0-next.0 + ## 0.4.34-next.0 ### Patch Changes diff --git a/plugins/permission-react/package.json b/plugins/permission-react/package.json index 0deb739819..f52f0eb58b 100644 --- a/plugins/permission-react/package.json +++ b/plugins/permission-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-permission-react", - "version": "0.4.34-next.0", + "version": "0.4.34-next.1", "backstage": { "role": "web-library", "pluginId": "permission", diff --git a/plugins/scaffolder-backend-module-azure/CHANGELOG.md b/plugins/scaffolder-backend-module-azure/CHANGELOG.md index 05f71427c7..4575bae2c7 100644 --- a/plugins/scaffolder-backend-module-azure/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-azure/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-scaffolder-backend-module-azure +## 0.2.9-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/config@1.3.2 + - @backstage/plugin-scaffolder-node@0.8.2-next.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/errors@1.2.7 + ## 0.2.9-next.1 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-azure/package.json b/plugins/scaffolder-backend-module-azure/package.json index d1c26d78eb..9507c527d7 100644 --- a/plugins/scaffolder-backend-module-azure/package.json +++ b/plugins/scaffolder-backend-module-azure/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-azure", - "version": "0.2.9-next.1", + "version": "0.2.9-next.2", "description": "The azure module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/scaffolder-backend-module-bitbucket-cloud/CHANGELOG.md b/plugins/scaffolder-backend-module-bitbucket-cloud/CHANGELOG.md index 2e6e218f58..cd1207a738 100644 --- a/plugins/scaffolder-backend-module-bitbucket-cloud/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-bitbucket-cloud/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-scaffolder-backend-module-bitbucket-cloud +## 0.2.9-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/config@1.3.2 + - @backstage/plugin-bitbucket-cloud-common@0.3.0-next.2 + - @backstage/plugin-scaffolder-node@0.8.2-next.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/errors@1.2.7 + ## 0.2.9-next.1 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-bitbucket-cloud/package.json b/plugins/scaffolder-backend-module-bitbucket-cloud/package.json index cbf2c5daae..79ea0f1131 100644 --- a/plugins/scaffolder-backend-module-bitbucket-cloud/package.json +++ b/plugins/scaffolder-backend-module-bitbucket-cloud/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-bitbucket-cloud", - "version": "0.2.9-next.1", + "version": "0.2.9-next.2", "description": "The Bitbucket Cloud module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/scaffolder-backend-module-bitbucket-server/CHANGELOG.md b/plugins/scaffolder-backend-module-bitbucket-server/CHANGELOG.md index b2bf58fc04..ad54b35865 100644 --- a/plugins/scaffolder-backend-module-bitbucket-server/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-bitbucket-server/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-scaffolder-backend-module-bitbucket-server +## 0.2.9-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/config@1.3.2 + - @backstage/plugin-scaffolder-node@0.8.2-next.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/errors@1.2.7 + ## 0.2.9-next.1 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-bitbucket-server/package.json b/plugins/scaffolder-backend-module-bitbucket-server/package.json index 8315efaedd..c092f10207 100644 --- a/plugins/scaffolder-backend-module-bitbucket-server/package.json +++ b/plugins/scaffolder-backend-module-bitbucket-server/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-bitbucket-server", - "version": "0.2.9-next.1", + "version": "0.2.9-next.2", "description": "The Bitbucket Server module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/scaffolder-backend-module-bitbucket/CHANGELOG.md b/plugins/scaffolder-backend-module-bitbucket/CHANGELOG.md index 99e0d6a3ed..c8bfd2fc7b 100644 --- a/plugins/scaffolder-backend-module-bitbucket/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-bitbucket/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-scaffolder-backend-module-bitbucket +## 0.3.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/config@1.3.2 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.9-next.2 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.9-next.2 + - @backstage/plugin-scaffolder-node@0.8.2-next.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/errors@1.2.7 + ## 0.3.10-next.1 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-bitbucket/package.json b/plugins/scaffolder-backend-module-bitbucket/package.json index 3f69f1b5bf..699858702c 100644 --- a/plugins/scaffolder-backend-module-bitbucket/package.json +++ b/plugins/scaffolder-backend-module-bitbucket/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-bitbucket", - "version": "0.3.10-next.1", + "version": "0.3.10-next.2", "description": "The bitbucket module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/scaffolder-backend-module-confluence-to-markdown/CHANGELOG.md b/plugins/scaffolder-backend-module-confluence-to-markdown/CHANGELOG.md index 6f3fb91881..06756b19fd 100644 --- a/plugins/scaffolder-backend-module-confluence-to-markdown/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-confluence-to-markdown/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-scaffolder-backend-module-confluence-to-markdown +## 0.3.9-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/config@1.3.2 + - @backstage/plugin-scaffolder-node@0.8.2-next.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/errors@1.2.7 + ## 0.3.9-next.1 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-confluence-to-markdown/package.json b/plugins/scaffolder-backend-module-confluence-to-markdown/package.json index fb91896783..de6045689f 100644 --- a/plugins/scaffolder-backend-module-confluence-to-markdown/package.json +++ b/plugins/scaffolder-backend-module-confluence-to-markdown/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-confluence-to-markdown", - "version": "0.3.9-next.1", + "version": "0.3.9-next.2", "description": "The confluence-to-markdown module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md b/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md index ccb9289646..c1a9ce091f 100644 --- a/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-scaffolder-backend-module-cookiecutter +## 0.3.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/backend-defaults@0.10.0-next.2 + - @backstage/config@1.3.2 + - @backstage/plugin-scaffolder-node@0.8.2-next.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + ## 0.3.10-next.1 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-cookiecutter/package.json b/plugins/scaffolder-backend-module-cookiecutter/package.json index 0341a564b6..404bad2b94 100644 --- a/plugins/scaffolder-backend-module-cookiecutter/package.json +++ b/plugins/scaffolder-backend-module-cookiecutter/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-cookiecutter", - "version": "0.3.10-next.1", + "version": "0.3.10-next.2", "description": "A module for the scaffolder backend that lets you template projects using cookiecutter", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/scaffolder-backend-module-gcp/CHANGELOG.md b/plugins/scaffolder-backend-module-gcp/CHANGELOG.md index c1b1041c0e..9f7f9b1631 100644 --- a/plugins/scaffolder-backend-module-gcp/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-gcp/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-scaffolder-backend-module-gcp +## 0.2.9-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/config@1.3.2 + - @backstage/plugin-scaffolder-node@0.8.2-next.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/errors@1.2.7 + ## 0.2.9-next.1 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-gcp/package.json b/plugins/scaffolder-backend-module-gcp/package.json index 688d2bb0d6..7c2fc0a624 100644 --- a/plugins/scaffolder-backend-module-gcp/package.json +++ b/plugins/scaffolder-backend-module-gcp/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-gcp", - "version": "0.2.9-next.1", + "version": "0.2.9-next.2", "description": "The GCP Bucket module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/scaffolder-backend-module-gerrit/CHANGELOG.md b/plugins/scaffolder-backend-module-gerrit/CHANGELOG.md index f77361fa0e..7c0367d1b0 100644 --- a/plugins/scaffolder-backend-module-gerrit/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-gerrit/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-scaffolder-backend-module-gerrit +## 0.2.9-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/config@1.3.2 + - @backstage/plugin-scaffolder-node@0.8.2-next.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/errors@1.2.7 + ## 0.2.9-next.1 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-gerrit/package.json b/plugins/scaffolder-backend-module-gerrit/package.json index 2959df75a0..aa6e7478ef 100644 --- a/plugins/scaffolder-backend-module-gerrit/package.json +++ b/plugins/scaffolder-backend-module-gerrit/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-gerrit", - "version": "0.2.9-next.1", + "version": "0.2.9-next.2", "description": "The gerrit module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/scaffolder-backend-module-gitea/CHANGELOG.md b/plugins/scaffolder-backend-module-gitea/CHANGELOG.md index 0ad531c6c2..c7e65d176e 100644 --- a/plugins/scaffolder-backend-module-gitea/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-gitea/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-scaffolder-backend-module-gitea +## 0.2.9-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/config@1.3.2 + - @backstage/plugin-scaffolder-node@0.8.2-next.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/errors@1.2.7 + ## 0.2.9-next.1 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-gitea/package.json b/plugins/scaffolder-backend-module-gitea/package.json index cd69424100..1be15b9164 100644 --- a/plugins/scaffolder-backend-module-gitea/package.json +++ b/plugins/scaffolder-backend-module-gitea/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-gitea", - "version": "0.2.9-next.1", + "version": "0.2.9-next.2", "description": "The gitea module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/scaffolder-backend-module-github/CHANGELOG.md b/plugins/scaffolder-backend-module-github/CHANGELOG.md index 8ba0029bed..6170587864 100644 --- a/plugins/scaffolder-backend-module-github/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-github/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-scaffolder-backend-module-github +## 0.7.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/config@1.3.2 + - @backstage/plugin-scaffolder-node@0.8.2-next.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + ## 0.7.1-next.1 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-github/package.json b/plugins/scaffolder-backend-module-github/package.json index af935262ae..0217d0b48d 100644 --- a/plugins/scaffolder-backend-module-github/package.json +++ b/plugins/scaffolder-backend-module-github/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-github", - "version": "0.7.1-next.1", + "version": "0.7.1-next.2", "description": "The github module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/scaffolder-backend-module-gitlab/CHANGELOG.md b/plugins/scaffolder-backend-module-gitlab/CHANGELOG.md index 9115edc834..aa4a43c22c 100644 --- a/plugins/scaffolder-backend-module-gitlab/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-gitlab/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-scaffolder-backend-module-gitlab +## 0.9.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/config@1.3.2 + - @backstage/plugin-scaffolder-node@0.8.2-next.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/errors@1.2.7 + ## 0.9.1-next.1 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-gitlab/package.json b/plugins/scaffolder-backend-module-gitlab/package.json index 6f4ad7b882..d2f699b219 100644 --- a/plugins/scaffolder-backend-module-gitlab/package.json +++ b/plugins/scaffolder-backend-module-gitlab/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-gitlab", - "version": "0.9.1-next.1", + "version": "0.9.1-next.2", "backstage": { "role": "backend-plugin-module", "pluginId": "scaffolder", diff --git a/plugins/scaffolder-backend-module-notifications/CHANGELOG.md b/plugins/scaffolder-backend-module-notifications/CHANGELOG.md index 00dfebcffd..4db8585828 100644 --- a/plugins/scaffolder-backend-module-notifications/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-notifications/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-scaffolder-backend-module-notifications +## 0.1.10-next.2 + +### Patch Changes + +- b60253d: Change notification send scaffolder action to use native zod schemas +- Updated dependencies + - @backstage/plugin-notifications-node@0.2.15-next.1 + - @backstage/plugin-scaffolder-node@0.8.2-next.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/plugin-notifications-common@0.0.8 + ## 0.1.10-next.1 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-notifications/package.json b/plugins/scaffolder-backend-module-notifications/package.json index 4512b2c1a7..ea6c416a9e 100644 --- a/plugins/scaffolder-backend-module-notifications/package.json +++ b/plugins/scaffolder-backend-module-notifications/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-notifications", - "version": "0.1.10-next.1", + "version": "0.1.10-next.2", "description": "The notifications backend module for the scaffolder plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/scaffolder-backend-module-rails/CHANGELOG.md b/plugins/scaffolder-backend-module-rails/CHANGELOG.md index b8ff9dd9cf..9c9117bfb3 100644 --- a/plugins/scaffolder-backend-module-rails/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-rails/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-scaffolder-backend-module-rails +## 0.5.9-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/config@1.3.2 + - @backstage/plugin-scaffolder-node@0.8.2-next.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + ## 0.5.9-next.1 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-rails/package.json b/plugins/scaffolder-backend-module-rails/package.json index b888aead7e..c4f58e24f0 100644 --- a/plugins/scaffolder-backend-module-rails/package.json +++ b/plugins/scaffolder-backend-module-rails/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-rails", - "version": "0.5.9-next.1", + "version": "0.5.9-next.2", "description": "A module for the scaffolder backend that lets you template projects using Rails", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/scaffolder-backend-module-sentry/CHANGELOG.md b/plugins/scaffolder-backend-module-sentry/CHANGELOG.md index 4672939b4c..886803e1f0 100644 --- a/plugins/scaffolder-backend-module-sentry/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-sentry/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-scaffolder-backend-module-sentry +## 0.2.9-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.2 + - @backstage/plugin-scaffolder-node@0.8.2-next.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/errors@1.2.7 + ## 0.2.9-next.1 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-sentry/package.json b/plugins/scaffolder-backend-module-sentry/package.json index 9e74b5758e..c59a288861 100644 --- a/plugins/scaffolder-backend-module-sentry/package.json +++ b/plugins/scaffolder-backend-module-sentry/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-sentry", - "version": "0.2.9-next.1", + "version": "0.2.9-next.2", "backstage": { "role": "backend-plugin-module", "pluginId": "scaffolder", diff --git a/plugins/scaffolder-backend-module-yeoman/CHANGELOG.md b/plugins/scaffolder-backend-module-yeoman/CHANGELOG.md index cba5d9a47c..a319388ed3 100644 --- a/plugins/scaffolder-backend-module-yeoman/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-yeoman/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-scaffolder-backend-module-yeoman +## 0.4.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.8.2-next.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/types@1.2.1 + - @backstage/plugin-scaffolder-node-test-utils@0.2.2-next.2 + ## 0.4.10-next.1 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-yeoman/package.json b/plugins/scaffolder-backend-module-yeoman/package.json index a943e3f4b9..577e6735a0 100644 --- a/plugins/scaffolder-backend-module-yeoman/package.json +++ b/plugins/scaffolder-backend-module-yeoman/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-yeoman", - "version": "0.4.10-next.1", + "version": "0.4.10-next.2", "backstage": { "role": "backend-plugin-module", "pluginId": "scaffolder", diff --git a/plugins/scaffolder-backend/CHANGELOG.md b/plugins/scaffolder-backend/CHANGELOG.md index c0797151b9..bfcea59479 100644 --- a/plugins/scaffolder-backend/CHANGELOG.md +++ b/plugins/scaffolder-backend/CHANGELOG.md @@ -1,5 +1,36 @@ # @backstage/plugin-scaffolder-backend +## 1.33.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/backend-defaults@0.10.0-next.2 + - @backstage/config@1.3.2 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.9.1-next.2 + - @backstage/plugin-bitbucket-cloud-common@0.3.0-next.2 + - @backstage/plugin-scaffolder-backend-module-azure@0.2.9-next.2 + - @backstage/plugin-scaffolder-backend-module-bitbucket@0.3.10-next.2 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.9-next.2 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.9-next.2 + - @backstage/plugin-scaffolder-backend-module-gerrit@0.2.9-next.2 + - @backstage/plugin-scaffolder-backend-module-gitea@0.2.9-next.2 + - @backstage/plugin-scaffolder-backend-module-github@0.7.1-next.2 + - @backstage/plugin-scaffolder-node@0.8.2-next.2 + - @backstage/plugin-auth-node@0.6.3-next.1 + - @backstage/plugin-permission-node@0.10.0-next.1 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.8-next.1 + - @backstage/plugin-catalog-node@1.17.0-next.1 + - @backstage/plugin-events-node@0.4.11-next.1 + - @backstage/plugin-permission-common@0.9.0-next.0 + - @backstage/plugin-scaffolder-common@1.5.11-next.0 + ## 1.33.0-next.1 ### Minor Changes diff --git a/plugins/scaffolder-backend/package.json b/plugins/scaffolder-backend/package.json index 6b2eb79555..8a36adfd7a 100644 --- a/plugins/scaffolder-backend/package.json +++ b/plugins/scaffolder-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend", - "version": "1.33.0-next.1", + "version": "1.33.0-next.2", "description": "The Backstage backend plugin that helps you create new things", "backstage": { "role": "backend-plugin", diff --git a/plugins/scaffolder-node-test-utils/CHANGELOG.md b/plugins/scaffolder-node-test-utils/CHANGELOG.md index f3501eed74..02f9c91a97 100644 --- a/plugins/scaffolder-node-test-utils/CHANGELOG.md +++ b/plugins/scaffolder-node-test-utils/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-scaffolder-node-test-utils +## 0.2.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.8.2-next.2 + - @backstage/backend-test-utils@1.5.0-next.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/types@1.2.1 + ## 0.2.2-next.1 ### Patch Changes diff --git a/plugins/scaffolder-node-test-utils/package.json b/plugins/scaffolder-node-test-utils/package.json index 9354a2ae68..9473450114 100644 --- a/plugins/scaffolder-node-test-utils/package.json +++ b/plugins/scaffolder-node-test-utils/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-node-test-utils", - "version": "0.2.2-next.1", + "version": "0.2.2-next.2", "backstage": { "role": "node-library", "pluginId": "scaffolder", diff --git a/plugins/scaffolder-node/CHANGELOG.md b/plugins/scaffolder-node/CHANGELOG.md index a93c305958..9654b575de 100644 --- a/plugins/scaffolder-node/CHANGELOG.md +++ b/plugins/scaffolder-node/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-scaffolder-node +## 0.8.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/catalog-model@1.7.3 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-scaffolder-common@1.5.11-next.0 + ## 0.8.2-next.1 ### Patch Changes diff --git a/plugins/scaffolder-node/package.json b/plugins/scaffolder-node/package.json index 0c8dc3401e..011b09c91f 100644 --- a/plugins/scaffolder-node/package.json +++ b/plugins/scaffolder-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-node", - "version": "0.8.2-next.1", + "version": "0.8.2-next.2", "description": "The plugin-scaffolder-node module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "node-library", diff --git a/plugins/scaffolder-react/CHANGELOG.md b/plugins/scaffolder-react/CHANGELOG.md index dc405aae51..97925ee2a1 100644 --- a/plugins/scaffolder-react/CHANGELOG.md +++ b/plugins/scaffolder-react/CHANGELOG.md @@ -1,5 +1,22 @@ # @backstage/plugin-scaffolder-react +## 1.16.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/core-components@0.17.2-next.1 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/plugin-catalog-react@1.18.0-next.2 + - @backstage/plugin-permission-react@0.4.34-next.1 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/theme@0.6.6-next.0 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-scaffolder-common@1.5.11-next.0 + ## 1.16.0-next.1 ### Patch Changes diff --git a/plugins/scaffolder-react/package.json b/plugins/scaffolder-react/package.json index 612390b179..514da4fe93 100644 --- a/plugins/scaffolder-react/package.json +++ b/plugins/scaffolder-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-react", - "version": "1.16.0-next.1", + "version": "1.16.0-next.2", "description": "A frontend library that helps other Backstage plugins interact with the Scaffolder", "backstage": { "role": "web-library", diff --git a/plugins/scaffolder/CHANGELOG.md b/plugins/scaffolder/CHANGELOG.md index f887e0cad7..ba8d5db71b 100644 --- a/plugins/scaffolder/CHANGELOG.md +++ b/plugins/scaffolder/CHANGELOG.md @@ -1,5 +1,26 @@ # @backstage/plugin-scaffolder +## 1.31.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/core-compat-api@0.4.2-next.2 + - @backstage/core-components@0.17.2-next.1 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/integration-react@1.2.7-next.2 + - @backstage/plugin-catalog-react@1.18.0-next.2 + - @backstage/plugin-permission-react@0.4.34-next.1 + - @backstage/plugin-scaffolder-react@1.16.0-next.2 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-catalog-common@1.1.4-next.0 + - @backstage/plugin-scaffolder-common@1.5.11-next.0 + ## 1.31.0-next.1 ### Patch Changes diff --git a/plugins/scaffolder/package.json b/plugins/scaffolder/package.json index 338044b0af..9c71f584ed 100644 --- a/plugins/scaffolder/package.json +++ b/plugins/scaffolder/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder", - "version": "1.31.0-next.1", + "version": "1.31.0-next.2", "description": "The Backstage plugin that helps you create new things", "backstage": { "role": "frontend-plugin", diff --git a/plugins/search-backend-module-elasticsearch/CHANGELOG.md b/plugins/search-backend-module-elasticsearch/CHANGELOG.md index 80be642c02..08897eb4c3 100644 --- a/plugins/search-backend-module-elasticsearch/CHANGELOG.md +++ b/plugins/search-backend-module-elasticsearch/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-search-backend-module-elasticsearch +## 1.7.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration-aws-node@0.1.16-next.0 + - @backstage/config@1.3.2 + - @backstage/plugin-search-backend-node@1.3.11-next.1 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/plugin-search-common@1.2.18-next.0 + ## 1.7.2-next.1 ### Patch Changes diff --git a/plugins/search-backend-module-elasticsearch/package.json b/plugins/search-backend-module-elasticsearch/package.json index 6b7de58534..fb9399cf2d 100644 --- a/plugins/search-backend-module-elasticsearch/package.json +++ b/plugins/search-backend-module-elasticsearch/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search-backend-module-elasticsearch", - "version": "1.7.2-next.1", + "version": "1.7.2-next.2", "description": "A module for the search backend that implements search using ElasticSearch", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/search-backend-module-techdocs/CHANGELOG.md b/plugins/search-backend-module-techdocs/CHANGELOG.md index 23ee89b58c..4af1906d57 100644 --- a/plugins/search-backend-module-techdocs/CHANGELOG.md +++ b/plugins/search-backend-module-techdocs/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-search-backend-module-techdocs +## 0.4.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.2 + - @backstage/plugin-techdocs-node@1.13.3-next.2 + - @backstage/plugin-search-backend-node@1.3.11-next.1 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/plugin-catalog-common@1.1.4-next.0 + - @backstage/plugin-catalog-node@1.17.0-next.1 + - @backstage/plugin-permission-common@0.9.0-next.0 + - @backstage/plugin-search-common@1.2.18-next.0 + ## 0.4.2-next.1 ### Patch Changes diff --git a/plugins/search-backend-module-techdocs/package.json b/plugins/search-backend-module-techdocs/package.json index f44b27bc78..8d088fe1b7 100644 --- a/plugins/search-backend-module-techdocs/package.json +++ b/plugins/search-backend-module-techdocs/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search-backend-module-techdocs", - "version": "0.4.2-next.1", + "version": "0.4.2-next.2", "description": "A module for the search backend that exports techdocs modules", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/search-backend/CHANGELOG.md b/plugins/search-backend/CHANGELOG.md index 94cb067c97..9f51eb47ea 100644 --- a/plugins/search-backend/CHANGELOG.md +++ b/plugins/search-backend/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-search-backend +## 2.0.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.10.0-next.2 + - @backstage/backend-openapi-utils@0.5.3-next.1 + - @backstage/config@1.3.2 + - @backstage/plugin-permission-node@0.10.0-next.1 + - @backstage/plugin-search-backend-node@1.3.11-next.1 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-permission-common@0.9.0-next.0 + - @backstage/plugin-search-common@1.2.18-next.0 + ## 2.0.2-next.1 ### Patch Changes diff --git a/plugins/search-backend/package.json b/plugins/search-backend/package.json index 04619a3c2b..57b212d906 100644 --- a/plugins/search-backend/package.json +++ b/plugins/search-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search-backend", - "version": "2.0.2-next.1", + "version": "2.0.2-next.2", "description": "The Backstage backend plugin that provides your backstage app with search", "backstage": { "role": "backend-plugin", diff --git a/plugins/search-react/CHANGELOG.md b/plugins/search-react/CHANGELOG.md index be4b5107ca..a6c9421949 100644 --- a/plugins/search-react/CHANGELOG.md +++ b/plugins/search-react/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-search-react +## 1.9.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/core-components@0.17.2-next.1 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/theme@0.6.6-next.0 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-search-common@1.2.18-next.0 + ## 1.9.0-next.0 ### Minor Changes diff --git a/plugins/search-react/package.json b/plugins/search-react/package.json index 7d3b90c2b4..59bc492e10 100644 --- a/plugins/search-react/package.json +++ b/plugins/search-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search-react", - "version": "1.9.0-next.0", + "version": "1.9.0-next.1", "backstage": { "role": "web-library", "pluginId": "search", diff --git a/plugins/search/CHANGELOG.md b/plugins/search/CHANGELOG.md index 62b1e53a2f..8a4d42569e 100644 --- a/plugins/search/CHANGELOG.md +++ b/plugins/search/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-search +## 1.4.26-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/core-compat-api@0.4.2-next.2 + - @backstage/core-components@0.17.2-next.1 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/plugin-catalog-react@1.18.0-next.2 + - @backstage/plugin-search-react@1.9.0-next.1 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-search-common@1.2.18-next.0 + ## 1.4.26-next.1 ### Patch Changes diff --git a/plugins/search/package.json b/plugins/search/package.json index 64e66dada5..8a872978e2 100644 --- a/plugins/search/package.json +++ b/plugins/search/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search", - "version": "1.4.26-next.1", + "version": "1.4.26-next.2", "description": "The Backstage plugin that provides your backstage app with search", "backstage": { "role": "frontend-plugin", diff --git a/plugins/signals-react/CHANGELOG.md b/plugins/signals-react/CHANGELOG.md index f02987b872..7983ebd1c2 100644 --- a/plugins/signals-react/CHANGELOG.md +++ b/plugins/signals-react/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-signals-react +## 0.0.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/types@1.2.1 + ## 0.0.12 ### Patch Changes diff --git a/plugins/signals-react/package.json b/plugins/signals-react/package.json index 6f4df673ec..8367209df4 100644 --- a/plugins/signals-react/package.json +++ b/plugins/signals-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-signals-react", - "version": "0.0.12", + "version": "0.0.13-next.0", "description": "Web library for the signals plugin", "backstage": { "role": "web-library", diff --git a/plugins/signals/CHANGELOG.md b/plugins/signals/CHANGELOG.md index d483d44850..54dad6dbcd 100644 --- a/plugins/signals/CHANGELOG.md +++ b/plugins/signals/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-signals +## 0.0.19-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/core-components@0.17.2-next.1 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/plugin-signals-react@0.0.13-next.0 + - @backstage/theme@0.6.6-next.0 + - @backstage/types@1.2.1 + ## 0.0.19-next.0 ### Patch Changes diff --git a/plugins/signals/package.json b/plugins/signals/package.json index 47cb2c4613..0af3bc534b 100644 --- a/plugins/signals/package.json +++ b/plugins/signals/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-signals", - "version": "0.0.19-next.0", + "version": "0.0.19-next.1", "backstage": { "role": "frontend-plugin", "pluginId": "signals", diff --git a/plugins/techdocs-addons-test-utils/CHANGELOG.md b/plugins/techdocs-addons-test-utils/CHANGELOG.md index 3a3219345a..b59f735816 100644 --- a/plugins/techdocs-addons-test-utils/CHANGELOG.md +++ b/plugins/techdocs-addons-test-utils/CHANGELOG.md @@ -1,5 +1,20 @@ # @backstage/plugin-techdocs-addons-test-utils +## 1.0.48-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/test-utils@1.7.8-next.1 + - @backstage/core-app-api@1.16.2-next.0 + - @backstage/plugin-techdocs@1.12.6-next.2 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/plugin-catalog@1.29.1-next.2 + - @backstage/integration-react@1.2.7-next.2 + - @backstage/plugin-catalog-react@1.18.0-next.2 + - @backstage/plugin-search-react@1.9.0-next.1 + - @backstage/plugin-techdocs-react@1.2.17-next.1 + ## 1.0.48-next.1 ### Patch Changes diff --git a/plugins/techdocs-addons-test-utils/package.json b/plugins/techdocs-addons-test-utils/package.json index 24e668f56b..0b6ee4f297 100644 --- a/plugins/techdocs-addons-test-utils/package.json +++ b/plugins/techdocs-addons-test-utils/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-techdocs-addons-test-utils", - "version": "1.0.48-next.1", + "version": "1.0.48-next.2", "backstage": { "role": "web-library", "pluginId": "techdocs-addons", diff --git a/plugins/techdocs-backend/CHANGELOG.md b/plugins/techdocs-backend/CHANGELOG.md index f9613fbfc5..8ba2abcdb2 100644 --- a/plugins/techdocs-backend/CHANGELOG.md +++ b/plugins/techdocs-backend/CHANGELOG.md @@ -1,5 +1,24 @@ # @backstage/plugin-techdocs-backend +## 2.0.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/backend-defaults@0.10.0-next.2 + - @backstage/config@1.3.2 + - @backstage/plugin-techdocs-node@1.13.3-next.2 + - @backstage/plugin-search-backend-module-techdocs@0.4.2-next.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-common@1.1.4-next.0 + - @backstage/plugin-catalog-node@1.17.0-next.1 + - @backstage/plugin-permission-common@0.9.0-next.0 + - @backstage/plugin-techdocs-common@0.1.0 + ## 2.0.2-next.1 ### Patch Changes diff --git a/plugins/techdocs-backend/package.json b/plugins/techdocs-backend/package.json index 82183f5d8b..380d37f79a 100644 --- a/plugins/techdocs-backend/package.json +++ b/plugins/techdocs-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-techdocs-backend", - "version": "2.0.2-next.1", + "version": "2.0.2-next.2", "description": "The Backstage backend plugin that renders technical documentation for your components", "backstage": { "role": "backend-plugin", diff --git a/plugins/techdocs-module-addons-contrib/CHANGELOG.md b/plugins/techdocs-module-addons-contrib/CHANGELOG.md index 69be121eed..df7bb86451 100644 --- a/plugins/techdocs-module-addons-contrib/CHANGELOG.md +++ b/plugins/techdocs-module-addons-contrib/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-techdocs-module-addons-contrib +## 1.1.24-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/core-components@0.17.2-next.1 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/integration-react@1.2.7-next.2 + - @backstage/plugin-techdocs-react@1.2.17-next.1 + ## 1.1.24-next.1 ### Patch Changes diff --git a/plugins/techdocs-module-addons-contrib/package.json b/plugins/techdocs-module-addons-contrib/package.json index 8c0a2a3795..8896409a09 100644 --- a/plugins/techdocs-module-addons-contrib/package.json +++ b/plugins/techdocs-module-addons-contrib/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-techdocs-module-addons-contrib", - "version": "1.1.24-next.1", + "version": "1.1.24-next.2", "description": "Plugin module for contributed TechDocs Addons", "backstage": { "role": "frontend-plugin-module", diff --git a/plugins/techdocs-node/CHANGELOG.md b/plugins/techdocs-node/CHANGELOG.md index ebafd1d472..b7c474a1e9 100644 --- a/plugins/techdocs-node/CHANGELOG.md +++ b/plugins/techdocs-node/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-techdocs-node +## 1.13.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration-aws-node@0.1.16-next.0 + - @backstage/integration@1.17.0-next.2 + - @backstage/config@1.3.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/catalog-model@1.7.3 + - @backstage/errors@1.2.7 + - @backstage/plugin-search-common@1.2.18-next.0 + - @backstage/plugin-techdocs-common@0.1.0 + ## 1.13.3-next.1 ### Patch Changes diff --git a/plugins/techdocs-node/package.json b/plugins/techdocs-node/package.json index 9f48155fd0..2cabc2b3c2 100644 --- a/plugins/techdocs-node/package.json +++ b/plugins/techdocs-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-techdocs-node", - "version": "1.13.3-next.1", + "version": "1.13.3-next.2", "description": "Common node.js functionalities for TechDocs, to be shared between techdocs-backend plugin and techdocs-cli", "backstage": { "role": "node-library", diff --git a/plugins/techdocs-react/CHANGELOG.md b/plugins/techdocs-react/CHANGELOG.md index 4d36f1c08c..1713b0f271 100644 --- a/plugins/techdocs-react/CHANGELOG.md +++ b/plugins/techdocs-react/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-techdocs-react +## 1.2.17-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/config@1.3.2 + - @backstage/core-components@0.17.2-next.1 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/catalog-model@1.7.3 + - @backstage/version-bridge@1.0.11 + ## 1.2.17-next.0 ### Patch Changes diff --git a/plugins/techdocs-react/package.json b/plugins/techdocs-react/package.json index c289c44441..420775b251 100644 --- a/plugins/techdocs-react/package.json +++ b/plugins/techdocs-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-techdocs-react", - "version": "1.2.17-next.0", + "version": "1.2.17-next.1", "description": "Shared frontend utilities for TechDocs and Addons", "backstage": { "role": "web-library", diff --git a/plugins/techdocs/CHANGELOG.md b/plugins/techdocs/CHANGELOG.md index ab61337daa..bcc2f88efa 100644 --- a/plugins/techdocs/CHANGELOG.md +++ b/plugins/techdocs/CHANGELOG.md @@ -1,5 +1,30 @@ # @backstage/plugin-techdocs +## 1.12.6-next.2 + +### Patch Changes + +- 7d445da: Update keyboard focus on when clicking hash links. This fixes the issue where the "skip to content" link rendered by Material MkDocs isn't focused when used. +- 2ffd273: Add hover and focus styling to the "copy to clipboard" button within codeblocks in techdocs. Also added an aria-label to the button for accessibility. +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/config@1.3.2 + - @backstage/core-compat-api@0.4.2-next.2 + - @backstage/core-components@0.17.2-next.1 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/integration-react@1.2.7-next.2 + - @backstage/plugin-auth-react@0.1.15-next.1 + - @backstage/plugin-catalog-react@1.18.0-next.2 + - @backstage/plugin-search-react@1.9.0-next.1 + - @backstage/plugin-techdocs-react@1.2.17-next.1 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/errors@1.2.7 + - @backstage/theme@0.6.6-next.0 + - @backstage/plugin-search-common@1.2.18-next.0 + - @backstage/plugin-techdocs-common@0.1.0 + ## 1.12.6-next.1 ### Patch Changes diff --git a/plugins/techdocs/package.json b/plugins/techdocs/package.json index 994661c03b..8ddeaa597f 100644 --- a/plugins/techdocs/package.json +++ b/plugins/techdocs/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-techdocs", - "version": "1.12.6-next.1", + "version": "1.12.6-next.2", "description": "The Backstage plugin that renders technical documentation for your components", "backstage": { "role": "frontend-plugin", diff --git a/plugins/user-settings-backend/CHANGELOG.md b/plugins/user-settings-backend/CHANGELOG.md index 1d87bcbcd5..5c3a22f044 100644 --- a/plugins/user-settings-backend/CHANGELOG.md +++ b/plugins/user-settings-backend/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-user-settings-backend +## 0.3.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.10.0-next.2 + - @backstage/plugin-auth-node@0.6.3-next.1 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-signals-node@0.1.20-next.1 + - @backstage/plugin-user-settings-common@0.0.1 + ## 0.3.2-next.1 ### Patch Changes diff --git a/plugins/user-settings-backend/package.json b/plugins/user-settings-backend/package.json index 60f599f37a..b216e566a5 100644 --- a/plugins/user-settings-backend/package.json +++ b/plugins/user-settings-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-user-settings-backend", - "version": "0.3.2-next.1", + "version": "0.3.2-next.2", "description": "The Backstage backend plugin to manage user settings", "backstage": { "role": "backend-plugin", diff --git a/plugins/user-settings/CHANGELOG.md b/plugins/user-settings/CHANGELOG.md index 410305140b..f5170c340f 100644 --- a/plugins/user-settings/CHANGELOG.md +++ b/plugins/user-settings/CHANGELOG.md @@ -1,5 +1,23 @@ # @backstage/plugin-user-settings +## 0.8.22-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.16.2-next.0 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/core-compat-api@0.4.2-next.2 + - @backstage/core-components@0.17.2-next.1 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/plugin-catalog-react@1.18.0-next.2 + - @backstage/plugin-signals-react@0.0.13-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/errors@1.2.7 + - @backstage/theme@0.6.6-next.0 + - @backstage/types@1.2.1 + - @backstage/plugin-user-settings-common@0.0.1 + ## 0.8.22-next.1 ### Patch Changes diff --git a/plugins/user-settings/package.json b/plugins/user-settings/package.json index c235f79e28..54dfcc0d26 100644 --- a/plugins/user-settings/package.json +++ b/plugins/user-settings/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-user-settings", - "version": "0.8.22-next.1", + "version": "0.8.22-next.2", "description": "A Backstage plugin that provides a settings page", "backstage": { "role": "frontend-plugin", diff --git a/yarn.lock b/yarn.lock index 234d1028ac..e2d48e3440 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4156,7 +4156,35 @@ __metadata: languageName: unknown linkType: soft -"@backstage/core-app-api@npm:^1.16.1, @backstage/core-app-api@workspace:^, @backstage/core-app-api@workspace:packages/core-app-api": +"@backstage/core-app-api@npm:^1.16.1": + version: 1.16.1 + resolution: "@backstage/core-app-api@npm:1.16.1" + dependencies: + "@backstage/config": "npm:^1.3.2" + "@backstage/core-plugin-api": "npm:^1.10.6" + "@backstage/types": "npm:^1.2.1" + "@backstage/version-bridge": "npm:^1.0.11" + "@types/prop-types": "npm:^15.7.3" + history: "npm:^5.0.0" + i18next: "npm:^22.4.15" + lodash: "npm:^4.17.21" + prop-types: "npm:^15.7.2" + react-use: "npm:^17.2.4" + zen-observable: "npm:^0.10.0" + zod: "npm:^3.22.4" + peerDependencies: + "@types/react": ^17.0.0 || ^18.0.0 + react: ^17.0.0 || ^18.0.0 + react-dom: ^17.0.0 || ^18.0.0 + react-router-dom: ^6.3.0 + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10/fae37fa5c4598dc1c2a4f82356d4039633cc7323f44f369ad1ca4151e1e27f668b2929f759e7a4843e273979a5aae1b473809e8add23cbd788fc48329bdf51fd + languageName: node + linkType: hard + +"@backstage/core-app-api@workspace:^, @backstage/core-app-api@workspace:packages/core-app-api": version: 0.0.0-use.local resolution: "@backstage/core-app-api@workspace:packages/core-app-api" dependencies: @@ -4493,7 +4521,28 @@ __metadata: languageName: unknown linkType: soft -"@backstage/core-plugin-api@npm:^1.10.0, @backstage/core-plugin-api@npm:^1.10.6, @backstage/core-plugin-api@npm:^1.8.2, @backstage/core-plugin-api@workspace:^, @backstage/core-plugin-api@workspace:packages/core-plugin-api": +"@backstage/core-plugin-api@npm:^1.10.0, @backstage/core-plugin-api@npm:^1.10.6, @backstage/core-plugin-api@npm:^1.8.2": + version: 1.10.6 + resolution: "@backstage/core-plugin-api@npm:1.10.6" + dependencies: + "@backstage/config": "npm:^1.3.2" + "@backstage/errors": "npm:^1.2.7" + "@backstage/types": "npm:^1.2.1" + "@backstage/version-bridge": "npm:^1.0.11" + history: "npm:^5.0.0" + peerDependencies: + "@types/react": ^17.0.0 || ^18.0.0 + react: ^17.0.0 || ^18.0.0 + react-dom: ^17.0.0 || ^18.0.0 + react-router-dom: ^6.3.0 + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10/1359bb2dc294c6eb0b8de6aa4a0788561c5d43ef2a4464d4f7600c382126398a42724e8ba894925ab02f1d332ef9b87b0eeee31e56a9e9223f8d6504a283d896 + languageName: node + linkType: hard + +"@backstage/core-plugin-api@workspace:^, @backstage/core-plugin-api@workspace:packages/core-plugin-api": version: 0.0.0-use.local resolution: "@backstage/core-plugin-api@workspace:packages/core-plugin-api" dependencies: @@ -4882,7 +4931,22 @@ __metadata: languageName: unknown linkType: soft -"@backstage/integration-aws-node@npm:^0.1.12, @backstage/integration-aws-node@workspace:^, @backstage/integration-aws-node@workspace:packages/integration-aws-node": +"@backstage/integration-aws-node@npm:^0.1.12": + version: 0.1.15 + resolution: "@backstage/integration-aws-node@npm:0.1.15" + dependencies: + "@aws-sdk/client-sts": "npm:^3.350.0" + "@aws-sdk/credential-provider-node": "npm:^3.350.0" + "@aws-sdk/credential-providers": "npm:^3.350.0" + "@aws-sdk/types": "npm:^3.347.0" + "@aws-sdk/util-arn-parser": "npm:^3.310.0" + "@backstage/config": "npm:^1.3.2" + "@backstage/errors": "npm:^1.2.7" + checksum: 10/92c52ad6f33ff270840b44bb060837f0f0b9f133c76c171f8aad29192580dd04c5d4db716ad6c07c8a4ebfc709458781bfb39def7ddadce64918d8b4352730ab + languageName: node + linkType: hard + +"@backstage/integration-aws-node@workspace:^, @backstage/integration-aws-node@workspace:packages/integration-aws-node": version: 0.0.0-use.local resolution: "@backstage/integration-aws-node@workspace:packages/integration-aws-node" dependencies: From f6480c7fea127096c7c35887ccf8d32729eb45b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Tue, 6 May 2025 14:47:50 +0200 Subject: [PATCH 100/109] Fix dataloader caching, and use the proper catalog service ref MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .changeset/soft-ravens-build.md | 5 ++ .../config/vocabularies/Backstage/accept.txt | 1 + .../package.json | 2 +- .../lib/SlackNotificationProcessor.test.ts | 12 --- .../src/lib/SlackNotificationProcessor.ts | 77 ++++++++----------- .../src/lib/util.ts | 39 ++++++++++ .../src/module.ts | 13 +--- yarn.lock | 1 - 8 files changed, 83 insertions(+), 67 deletions(-) create mode 100644 .changeset/soft-ravens-build.md diff --git a/.changeset/soft-ravens-build.md b/.changeset/soft-ravens-build.md new file mode 100644 index 0000000000..dfa14f9b0c --- /dev/null +++ b/.changeset/soft-ravens-build.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-notifications-backend-module-slack': patch +--- + +Fix dataloader caching, and use the proper catalog service ref diff --git a/.github/vale/config/vocabularies/Backstage/accept.txt b/.github/vale/config/vocabularies/Backstage/accept.txt index f9a0595489..0ca8986140 100644 --- a/.github/vale/config/vocabularies/Backstage/accept.txt +++ b/.github/vale/config/vocabularies/Backstage/accept.txt @@ -89,6 +89,7 @@ CVEs daemonsets Datadog dataflow +dataloader dayjs debounce debounces diff --git a/plugins/notifications-backend-module-slack/package.json b/plugins/notifications-backend-module-slack/package.json index ce08a5f002..33770bdeac 100644 --- a/plugins/notifications-backend-module-slack/package.json +++ b/plugins/notifications-backend-module-slack/package.json @@ -35,10 +35,10 @@ }, "dependencies": { "@backstage/backend-plugin-api": "workspace:^", - "@backstage/catalog-client": "workspace:^", "@backstage/catalog-model": "workspace:^", "@backstage/config": "workspace:^", "@backstage/errors": "workspace:^", + "@backstage/plugin-catalog-node": "workspace:^", "@backstage/plugin-notifications-common": "workspace:^", "@backstage/plugin-notifications-node": "workspace:^", "@backstage/types": "workspace:^", diff --git a/plugins/notifications-backend-module-slack/src/lib/SlackNotificationProcessor.test.ts b/plugins/notifications-backend-module-slack/src/lib/SlackNotificationProcessor.test.ts index 2ee328f832..45a74cee70 100644 --- a/plugins/notifications-backend-module-slack/src/lib/SlackNotificationProcessor.test.ts +++ b/plugins/notifications-backend-module-slack/src/lib/SlackNotificationProcessor.test.ts @@ -109,7 +109,6 @@ const DEFAULT_ENTITIES_RESPONSE = { describe('SlackNotificationProcessor', () => { const logger = mockServices.logger.mock(); const auth = mockServices.auth(); - const discovery = mockServices.discovery(); const config = mockServices.rootConfig({ data: { app: { @@ -136,7 +135,6 @@ describe('SlackNotificationProcessor', () => { const processor = SlackNotificationProcessor.fromConfig(config, { auth, - discovery, logger, catalog: catalogServiceMock({ entities: DEFAULT_ENTITIES_RESPONSE.items, @@ -199,7 +197,6 @@ describe('SlackNotificationProcessor', () => { const processor = SlackNotificationProcessor.fromConfig(config, { auth, - discovery, logger, catalog: catalogServiceMock({ entities: DEFAULT_ENTITIES_RESPONSE.items, @@ -275,7 +272,6 @@ describe('SlackNotificationProcessor', () => { const processor = SlackNotificationProcessor.fromConfig(config, { auth, - discovery, logger, catalog: catalogServiceMock({ entities: DEFAULT_ENTITIES_RESPONSE.items, @@ -310,7 +306,6 @@ describe('SlackNotificationProcessor', () => { const processor = SlackNotificationProcessor.fromConfig(config, { auth, - discovery, logger, catalog: catalogServiceMock({ entities: DEFAULT_ENTITIES_RESPONSE.items, @@ -366,7 +361,6 @@ describe('SlackNotificationProcessor', () => { const processor = SlackNotificationProcessor.fromConfig(broadcastConfig, { auth, - discovery, logger, catalog: catalogServiceMock({ entities: DEFAULT_ENTITIES_RESPONSE.items, @@ -405,7 +399,6 @@ describe('SlackNotificationProcessor', () => { const processor = SlackNotificationProcessor.fromConfig(config, { auth, - discovery, logger, catalog: catalogServiceMock({ entities: DEFAULT_ENTITIES_RESPONSE.items, @@ -429,7 +422,6 @@ describe('SlackNotificationProcessor', () => { const processor = SlackNotificationProcessor.fromConfig(config, { auth, - discovery, logger, catalog: catalogServiceMock({ entities: [DEFAULT_ENTITIES_RESPONSE.items[2]], @@ -469,7 +461,6 @@ describe('SlackNotificationProcessor', () => { const processor = SlackNotificationProcessor.fromConfig(config, { auth, - discovery, logger, catalog: catalogServiceMock({ entities: DEFAULT_ENTITIES_RESPONSE.items, @@ -515,7 +506,6 @@ describe('SlackNotificationProcessor', () => { const processor = SlackNotificationProcessor.fromConfig(config, { auth, - discovery, logger, catalog: catalogServiceMock({ entities: DEFAULT_ENTITIES_RESPONSE.items, @@ -600,7 +590,6 @@ describe('SlackNotificationProcessor', () => { const slack = new WebClient(); const processor = SlackNotificationProcessor.fromConfig(config, { auth, - discovery, logger, catalog: catalogServiceMock({ entities: DEFAULT_ENTITIES_RESPONSE.items, @@ -641,7 +630,6 @@ describe('SlackNotificationProcessor', () => { const slack = new WebClient(); const processor = SlackNotificationProcessor.fromConfig(config, { auth, - discovery, logger, catalog: catalogServiceMock({ entities: DEFAULT_ENTITIES_RESPONSE.items, diff --git a/plugins/notifications-backend-module-slack/src/lib/SlackNotificationProcessor.ts b/plugins/notifications-backend-module-slack/src/lib/SlackNotificationProcessor.ts index 774150676c..c587580657 100644 --- a/plugins/notifications-backend-module-slack/src/lib/SlackNotificationProcessor.ts +++ b/plugins/notifications-backend-module-slack/src/lib/SlackNotificationProcessor.ts @@ -14,12 +14,7 @@ * limitations under the License. */ -import { - AuthService, - DiscoveryService, - LoggerService, -} from '@backstage/backend-plugin-api'; -import { CatalogApi } from '@backstage/catalog-client'; +import { AuthService, LoggerService } from '@backstage/backend-plugin-api'; import { Entity, isUserEntity, @@ -39,25 +34,26 @@ import { ChatPostMessageArguments, WebClient } from '@slack/web-api'; import DataLoader from 'dataloader'; import pThrottle from 'p-throttle'; import { ANNOTATION_SLACK_BOT_NOTIFY } from './constants'; -import { toChatPostMessageArgs } from './util'; +import { ExpiryMap, toChatPostMessageArgs } from './util'; +import { CatalogService } from '@backstage/plugin-catalog-node'; export class SlackNotificationProcessor implements NotificationProcessor { private readonly logger: LoggerService; - private readonly catalog: CatalogApi; + private readonly catalog: CatalogService; private readonly auth: AuthService; private readonly slack: WebClient; private readonly sendNotifications; private readonly messagesSent: Counter; private readonly messagesFailed: Counter; private readonly broadcastChannels?: string[]; + private readonly entityLoader: DataLoader; static fromConfig( config: Config, options: { auth: AuthService; - discovery: DiscoveryService; logger: LoggerService; - catalog: CatalogApi; + catalog: CatalogService; slack?: WebClient; broadcastChannels?: string[]; }, @@ -79,9 +75,8 @@ export class SlackNotificationProcessor implements NotificationProcessor { private constructor(options: { slack: WebClient; auth: AuthService; - discovery: DiscoveryService; logger: LoggerService; - catalog: CatalogApi; + catalog: CatalogService; broadcastChannels?: string[]; }) { const { auth, catalog, logger, slack, broadcastChannels } = options; @@ -91,6 +86,31 @@ export class SlackNotificationProcessor implements NotificationProcessor { this.slack = slack; this.broadcastChannels = broadcastChannels; + this.entityLoader = new DataLoader( + async entityRefs => { + return await this.catalog + .getEntitiesByRefs( + { + entityRefs: entityRefs.slice(), + fields: [ + `kind`, + `spec.profile.email`, + `metadata.annotations.${ANNOTATION_SLACK_BOT_NOTIFY}`, + ], + }, + { credentials: await this.auth.getOwnServiceCredentials() }, + ) + .then(r => r.items); + }, + { + name: 'SlackNotificationProcessor.entityLoader', + cacheMap: new ExpiryMap(durationToMilliseconds({ minutes: 10 })), + maxBatchSize: 100, + batchScheduleFn: cb => + setTimeout(cb, durationToMilliseconds({ milliseconds: 10 })), + }, + ); + const meter = metrics.getMeter('default'); this.messagesSent = meter.createCounter( 'notifications.processors.slack.sent.count', @@ -193,7 +213,6 @@ export class SlackNotificationProcessor implements NotificationProcessor { }), ); - console.log('dispatching message'); await this.sendNotifications(outbound); return options; @@ -261,31 +280,6 @@ export class SlackNotificationProcessor implements NotificationProcessor { }; } - async getEntities( - entityRefs: readonly string[], - ): Promise<(Entity | undefined)[]> { - const { token } = await this.auth.getPluginRequestToken({ - onBehalfOf: await this.auth.getOwnServiceCredentials(), - targetPluginId: 'catalog', - }); - - const response = await this.catalog.getEntitiesByRefs( - { - entityRefs: entityRefs.slice(), - fields: [ - `kind`, - `spec.profile.email`, - `metadata.annotations.${ANNOTATION_SLACK_BOT_NOTIFY}`, - ], - }, - { - token, - }, - ); - - return response.items; - } - async replaceUserRefsWithSlackIds( text?: string, ): Promise { @@ -327,13 +321,8 @@ export class SlackNotificationProcessor implements NotificationProcessor { async getSlackNotificationTarget( entityRef: string, ): Promise { - const entityLoader = new DataLoader( - entityRefs => this.getEntities(entityRefs), - ); - const entity = await entityLoader.load(entityRef); - + const entity = await this.entityLoader.load(entityRef); if (!entity) { - console.log(`Entity not found: ${entityRef}`); throw new NotFoundError(`Entity not found: ${entityRef}`); } diff --git a/plugins/notifications-backend-module-slack/src/lib/util.ts b/plugins/notifications-backend-module-slack/src/lib/util.ts index 2d3a03bd2c..24f9f6a964 100644 --- a/plugins/notifications-backend-module-slack/src/lib/util.ts +++ b/plugins/notifications-backend-module-slack/src/lib/util.ts @@ -90,3 +90,42 @@ function getColor(severity: NotificationSeverity | undefined) { return '#00A699'; // Neutral color } } + +// Simple expiry map for the data loader, which only expects a map that implements set, get, and delete and clear +export class ExpiryMap extends Map { + #ttlMs: number; + #timestamps: Map = new Map(); + + constructor(ttlMs: number) { + super(); + this.#ttlMs = ttlMs; + } + + set(key: K, value: V) { + const result = super.set(key, value); + this.#timestamps.set(key, Date.now()); + return result; + } + + get(key: K) { + if (!this.has(key)) { + return undefined; + } + const timestamp = this.#timestamps.get(key)!; + if (Date.now() - timestamp > this.#ttlMs) { + this.delete(key); + return undefined; + } + return super.get(key); + } + + delete(key: K) { + this.#timestamps.delete(key); + return super.delete(key); + } + + clear() { + this.#timestamps.clear(); + return super.clear(); + } +} diff --git a/plugins/notifications-backend-module-slack/src/module.ts b/plugins/notifications-backend-module-slack/src/module.ts index cb096dabba..ff783ef104 100644 --- a/plugins/notifications-backend-module-slack/src/module.ts +++ b/plugins/notifications-backend-module-slack/src/module.ts @@ -17,9 +17,9 @@ import { coreServices, createBackendModule, } from '@backstage/backend-plugin-api'; -import { CatalogClient } from '@backstage/catalog-client'; import { notificationsProcessingExtensionPoint } from '@backstage/plugin-notifications-node'; import { SlackNotificationProcessor } from './lib/SlackNotificationProcessor'; +import { catalogServiceRef } from '@backstage/plugin-catalog-node'; /** * The Slack notification processor for use with the notifications plugin. @@ -35,21 +35,16 @@ export const notificationsModuleSlack = createBackendModule({ deps: { auth: coreServices.auth, config: coreServices.rootConfig, - discovery: coreServices.discovery, logger: coreServices.logger, + catalog: catalogServiceRef, notifications: notificationsProcessingExtensionPoint, }, - async init({ auth, config, discovery, logger, notifications }) { - const catalogClient = new CatalogClient({ - discoveryApi: discovery, - }); - + async init({ auth, config, logger, catalog, notifications }) { notifications.addProcessor( SlackNotificationProcessor.fromConfig(config, { auth, - discovery, logger, - catalog: catalogClient, + catalog, }), ); }, diff --git a/yarn.lock b/yarn.lock index e2d48e3440..fbef448abd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7205,7 +7205,6 @@ __metadata: dependencies: "@backstage/backend-plugin-api": "workspace:^" "@backstage/backend-test-utils": "workspace:^" - "@backstage/catalog-client": "workspace:^" "@backstage/catalog-model": "workspace:^" "@backstage/cli": "workspace:^" "@backstage/config": "workspace:^" From 90c7daae2c0748ea9f5324d4a32853ad68357fd2 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 7 May 2025 00:46:01 +0000 Subject: [PATCH 101/109] chore(deps): update dependency lint-staged to v15.5.2 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 96b0056283..7557a5aac2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -35943,8 +35943,8 @@ __metadata: linkType: hard "lint-staged@npm:^15.0.0": - version: 15.5.1 - resolution: "lint-staged@npm:15.5.1" + version: 15.5.2 + resolution: "lint-staged@npm:15.5.2" dependencies: chalk: "npm:^5.4.1" commander: "npm:^13.1.0" @@ -35958,7 +35958,7 @@ __metadata: yaml: "npm:^2.7.0" bin: lint-staged: bin/lint-staged.js - checksum: 10/58662ea6e40c9292a3499ffd01cf6c1e8415f79bee7526fc8d9abbb173ba020d0099d7996a407f67be8e0f23cc6a6f898d86b7e27f41f37ba924ec25597b7914 + checksum: 10/523c332d6cb6e34972a6530a7a2487307555e784df9466c82f2b8d17c8090a3db561a6362065ae6b63048c25fcb85c9e32057cd0bfb756bf7ab185bea1dbb89c languageName: node linkType: hard From 9b8db99f78d1834472dcaa5a838ec05d41a9d103 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 7 May 2025 00:46:41 +0000 Subject: [PATCH 102/109] fix(deps): update dependency @codemirror/view to v6.36.7 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 96b0056283..0559775040 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9524,13 +9524,13 @@ __metadata: linkType: hard "@codemirror/view@npm:^6.0.0, @codemirror/view@npm:^6.23.0": - version: 6.36.6 - resolution: "@codemirror/view@npm:6.36.6" + version: 6.36.7 + resolution: "@codemirror/view@npm:6.36.7" dependencies: "@codemirror/state": "npm:^6.5.0" style-mod: "npm:^4.1.0" w3c-keyname: "npm:^2.2.4" - checksum: 10/a98d19fe8a76557ac442cd173bac83e0a471a00db98c4a8da35c7e80e78a23f4460e57eb46a66cc60f58ae854e5e35f1f0b9c8a3919cd8ef765f37a912c2b093 + checksum: 10/0ba49a3025fbb381a8a35022135ea40363a3a3c298d41e3083bb5e263dfa4f11ff9419a46f2b78ef8e853376384ad6c7e48d2a7887e10d366e878d10b53e3b54 languageName: node linkType: hard From b4715a2d8fbe286b41c36430407db3c792cd9aef Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 7 May 2025 01:51:07 +0000 Subject: [PATCH 103/109] fix(deps): update dependency @uiw/codemirror-themes to v4.23.12 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- canon-docs/yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/canon-docs/yarn.lock b/canon-docs/yarn.lock index d4049730ff..cbcdc2a55c 100644 --- a/canon-docs/yarn.lock +++ b/canon-docs/yarn.lock @@ -1100,8 +1100,8 @@ __metadata: linkType: hard "@uiw/codemirror-themes@npm:^4.23.7": - version: 4.23.11 - resolution: "@uiw/codemirror-themes@npm:4.23.11" + version: 4.23.12 + resolution: "@uiw/codemirror-themes@npm:4.23.12" dependencies: "@codemirror/language": "npm:^6.0.0" "@codemirror/state": "npm:^6.0.0" @@ -1110,7 +1110,7 @@ __metadata: "@codemirror/language": ">=6.0.0" "@codemirror/state": ">=6.0.0" "@codemirror/view": ">=6.0.0" - checksum: 10/cbdb7865410e4dead2a96857d14e7dd50fcaf3af9f013ce932578b7fdd824648aa8f21f7964e3b1d657f79961f14db605f28178fc81ec53ee77e27c1a066ff32 + checksum: 10/472121efadf2b9eb110030f06d657cb9693861e1ca360c81c7b0d7aa4533e6e6791737dfd14bcbe496bcb818d33f4cf6a18caf504ec628807e273441f969ef83 languageName: node linkType: hard From b3780309d6e59be094ab26780149392bcf328b26 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 7 May 2025 01:51:54 +0000 Subject: [PATCH 104/109] fix(deps): update dependency esbuild to v0.25.4 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 206 +++++++++++++++++++++++++++--------------------------- 1 file changed, 103 insertions(+), 103 deletions(-) diff --git a/yarn.lock b/yarn.lock index a7bb149ca2..37ec0122ea 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9827,9 +9827,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/aix-ppc64@npm:0.25.3": - version: 0.25.3 - resolution: "@esbuild/aix-ppc64@npm:0.25.3" +"@esbuild/aix-ppc64@npm:0.25.4": + version: 0.25.4 + resolution: "@esbuild/aix-ppc64@npm:0.25.4" conditions: os=aix & cpu=ppc64 languageName: node linkType: hard @@ -9841,9 +9841,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/android-arm64@npm:0.25.3": - version: 0.25.3 - resolution: "@esbuild/android-arm64@npm:0.25.3" +"@esbuild/android-arm64@npm:0.25.4": + version: 0.25.4 + resolution: "@esbuild/android-arm64@npm:0.25.4" conditions: os=android & cpu=arm64 languageName: node linkType: hard @@ -9855,9 +9855,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/android-arm@npm:0.25.3": - version: 0.25.3 - resolution: "@esbuild/android-arm@npm:0.25.3" +"@esbuild/android-arm@npm:0.25.4": + version: 0.25.4 + resolution: "@esbuild/android-arm@npm:0.25.4" conditions: os=android & cpu=arm languageName: node linkType: hard @@ -9869,9 +9869,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/android-x64@npm:0.25.3": - version: 0.25.3 - resolution: "@esbuild/android-x64@npm:0.25.3" +"@esbuild/android-x64@npm:0.25.4": + version: 0.25.4 + resolution: "@esbuild/android-x64@npm:0.25.4" conditions: os=android & cpu=x64 languageName: node linkType: hard @@ -9883,9 +9883,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/darwin-arm64@npm:0.25.3": - version: 0.25.3 - resolution: "@esbuild/darwin-arm64@npm:0.25.3" +"@esbuild/darwin-arm64@npm:0.25.4": + version: 0.25.4 + resolution: "@esbuild/darwin-arm64@npm:0.25.4" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard @@ -9897,9 +9897,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/darwin-x64@npm:0.25.3": - version: 0.25.3 - resolution: "@esbuild/darwin-x64@npm:0.25.3" +"@esbuild/darwin-x64@npm:0.25.4": + version: 0.25.4 + resolution: "@esbuild/darwin-x64@npm:0.25.4" conditions: os=darwin & cpu=x64 languageName: node linkType: hard @@ -9911,9 +9911,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/freebsd-arm64@npm:0.25.3": - version: 0.25.3 - resolution: "@esbuild/freebsd-arm64@npm:0.25.3" +"@esbuild/freebsd-arm64@npm:0.25.4": + version: 0.25.4 + resolution: "@esbuild/freebsd-arm64@npm:0.25.4" conditions: os=freebsd & cpu=arm64 languageName: node linkType: hard @@ -9925,9 +9925,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/freebsd-x64@npm:0.25.3": - version: 0.25.3 - resolution: "@esbuild/freebsd-x64@npm:0.25.3" +"@esbuild/freebsd-x64@npm:0.25.4": + version: 0.25.4 + resolution: "@esbuild/freebsd-x64@npm:0.25.4" conditions: os=freebsd & cpu=x64 languageName: node linkType: hard @@ -9939,9 +9939,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-arm64@npm:0.25.3": - version: 0.25.3 - resolution: "@esbuild/linux-arm64@npm:0.25.3" +"@esbuild/linux-arm64@npm:0.25.4": + version: 0.25.4 + resolution: "@esbuild/linux-arm64@npm:0.25.4" conditions: os=linux & cpu=arm64 languageName: node linkType: hard @@ -9953,9 +9953,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-arm@npm:0.25.3": - version: 0.25.3 - resolution: "@esbuild/linux-arm@npm:0.25.3" +"@esbuild/linux-arm@npm:0.25.4": + version: 0.25.4 + resolution: "@esbuild/linux-arm@npm:0.25.4" conditions: os=linux & cpu=arm languageName: node linkType: hard @@ -9967,9 +9967,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-ia32@npm:0.25.3": - version: 0.25.3 - resolution: "@esbuild/linux-ia32@npm:0.25.3" +"@esbuild/linux-ia32@npm:0.25.4": + version: 0.25.4 + resolution: "@esbuild/linux-ia32@npm:0.25.4" conditions: os=linux & cpu=ia32 languageName: node linkType: hard @@ -9981,9 +9981,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-loong64@npm:0.25.3": - version: 0.25.3 - resolution: "@esbuild/linux-loong64@npm:0.25.3" +"@esbuild/linux-loong64@npm:0.25.4": + version: 0.25.4 + resolution: "@esbuild/linux-loong64@npm:0.25.4" conditions: os=linux & cpu=loong64 languageName: node linkType: hard @@ -9995,9 +9995,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-mips64el@npm:0.25.3": - version: 0.25.3 - resolution: "@esbuild/linux-mips64el@npm:0.25.3" +"@esbuild/linux-mips64el@npm:0.25.4": + version: 0.25.4 + resolution: "@esbuild/linux-mips64el@npm:0.25.4" conditions: os=linux & cpu=mips64el languageName: node linkType: hard @@ -10009,9 +10009,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-ppc64@npm:0.25.3": - version: 0.25.3 - resolution: "@esbuild/linux-ppc64@npm:0.25.3" +"@esbuild/linux-ppc64@npm:0.25.4": + version: 0.25.4 + resolution: "@esbuild/linux-ppc64@npm:0.25.4" conditions: os=linux & cpu=ppc64 languageName: node linkType: hard @@ -10023,9 +10023,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-riscv64@npm:0.25.3": - version: 0.25.3 - resolution: "@esbuild/linux-riscv64@npm:0.25.3" +"@esbuild/linux-riscv64@npm:0.25.4": + version: 0.25.4 + resolution: "@esbuild/linux-riscv64@npm:0.25.4" conditions: os=linux & cpu=riscv64 languageName: node linkType: hard @@ -10037,9 +10037,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-s390x@npm:0.25.3": - version: 0.25.3 - resolution: "@esbuild/linux-s390x@npm:0.25.3" +"@esbuild/linux-s390x@npm:0.25.4": + version: 0.25.4 + resolution: "@esbuild/linux-s390x@npm:0.25.4" conditions: os=linux & cpu=s390x languageName: node linkType: hard @@ -10051,16 +10051,16 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-x64@npm:0.25.3": - version: 0.25.3 - resolution: "@esbuild/linux-x64@npm:0.25.3" +"@esbuild/linux-x64@npm:0.25.4": + version: 0.25.4 + resolution: "@esbuild/linux-x64@npm:0.25.4" conditions: os=linux & cpu=x64 languageName: node linkType: hard -"@esbuild/netbsd-arm64@npm:0.25.3": - version: 0.25.3 - resolution: "@esbuild/netbsd-arm64@npm:0.25.3" +"@esbuild/netbsd-arm64@npm:0.25.4": + version: 0.25.4 + resolution: "@esbuild/netbsd-arm64@npm:0.25.4" conditions: os=netbsd & cpu=arm64 languageName: node linkType: hard @@ -10072,16 +10072,16 @@ __metadata: languageName: node linkType: hard -"@esbuild/netbsd-x64@npm:0.25.3": - version: 0.25.3 - resolution: "@esbuild/netbsd-x64@npm:0.25.3" +"@esbuild/netbsd-x64@npm:0.25.4": + version: 0.25.4 + resolution: "@esbuild/netbsd-x64@npm:0.25.4" conditions: os=netbsd & cpu=x64 languageName: node linkType: hard -"@esbuild/openbsd-arm64@npm:0.25.3": - version: 0.25.3 - resolution: "@esbuild/openbsd-arm64@npm:0.25.3" +"@esbuild/openbsd-arm64@npm:0.25.4": + version: 0.25.4 + resolution: "@esbuild/openbsd-arm64@npm:0.25.4" conditions: os=openbsd & cpu=arm64 languageName: node linkType: hard @@ -10093,9 +10093,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/openbsd-x64@npm:0.25.3": - version: 0.25.3 - resolution: "@esbuild/openbsd-x64@npm:0.25.3" +"@esbuild/openbsd-x64@npm:0.25.4": + version: 0.25.4 + resolution: "@esbuild/openbsd-x64@npm:0.25.4" conditions: os=openbsd & cpu=x64 languageName: node linkType: hard @@ -10107,9 +10107,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/sunos-x64@npm:0.25.3": - version: 0.25.3 - resolution: "@esbuild/sunos-x64@npm:0.25.3" +"@esbuild/sunos-x64@npm:0.25.4": + version: 0.25.4 + resolution: "@esbuild/sunos-x64@npm:0.25.4" conditions: os=sunos & cpu=x64 languageName: node linkType: hard @@ -10121,9 +10121,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/win32-arm64@npm:0.25.3": - version: 0.25.3 - resolution: "@esbuild/win32-arm64@npm:0.25.3" +"@esbuild/win32-arm64@npm:0.25.4": + version: 0.25.4 + resolution: "@esbuild/win32-arm64@npm:0.25.4" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard @@ -10135,9 +10135,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/win32-ia32@npm:0.25.3": - version: 0.25.3 - resolution: "@esbuild/win32-ia32@npm:0.25.3" +"@esbuild/win32-ia32@npm:0.25.4": + version: 0.25.4 + resolution: "@esbuild/win32-ia32@npm:0.25.4" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard @@ -10149,9 +10149,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/win32-x64@npm:0.25.3": - version: 0.25.3 - resolution: "@esbuild/win32-x64@npm:0.25.3" +"@esbuild/win32-x64@npm:0.25.4": + version: 0.25.4 + resolution: "@esbuild/win32-x64@npm:0.25.4" conditions: os=win32 & cpu=x64 languageName: node linkType: hard @@ -28944,34 +28944,34 @@ __metadata: linkType: hard "esbuild@npm:^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0, esbuild@npm:^0.25.0": - version: 0.25.3 - resolution: "esbuild@npm:0.25.3" + version: 0.25.4 + resolution: "esbuild@npm:0.25.4" dependencies: - "@esbuild/aix-ppc64": "npm:0.25.3" - "@esbuild/android-arm": "npm:0.25.3" - "@esbuild/android-arm64": "npm:0.25.3" - "@esbuild/android-x64": "npm:0.25.3" - "@esbuild/darwin-arm64": "npm:0.25.3" - "@esbuild/darwin-x64": "npm:0.25.3" - "@esbuild/freebsd-arm64": "npm:0.25.3" - "@esbuild/freebsd-x64": "npm:0.25.3" - "@esbuild/linux-arm": "npm:0.25.3" - "@esbuild/linux-arm64": "npm:0.25.3" - "@esbuild/linux-ia32": "npm:0.25.3" - "@esbuild/linux-loong64": "npm:0.25.3" - "@esbuild/linux-mips64el": "npm:0.25.3" - "@esbuild/linux-ppc64": "npm:0.25.3" - "@esbuild/linux-riscv64": "npm:0.25.3" - "@esbuild/linux-s390x": "npm:0.25.3" - "@esbuild/linux-x64": "npm:0.25.3" - "@esbuild/netbsd-arm64": "npm:0.25.3" - "@esbuild/netbsd-x64": "npm:0.25.3" - "@esbuild/openbsd-arm64": "npm:0.25.3" - "@esbuild/openbsd-x64": "npm:0.25.3" - "@esbuild/sunos-x64": "npm:0.25.3" - "@esbuild/win32-arm64": "npm:0.25.3" - "@esbuild/win32-ia32": "npm:0.25.3" - "@esbuild/win32-x64": "npm:0.25.3" + "@esbuild/aix-ppc64": "npm:0.25.4" + "@esbuild/android-arm": "npm:0.25.4" + "@esbuild/android-arm64": "npm:0.25.4" + "@esbuild/android-x64": "npm:0.25.4" + "@esbuild/darwin-arm64": "npm:0.25.4" + "@esbuild/darwin-x64": "npm:0.25.4" + "@esbuild/freebsd-arm64": "npm:0.25.4" + "@esbuild/freebsd-x64": "npm:0.25.4" + "@esbuild/linux-arm": "npm:0.25.4" + "@esbuild/linux-arm64": "npm:0.25.4" + "@esbuild/linux-ia32": "npm:0.25.4" + "@esbuild/linux-loong64": "npm:0.25.4" + "@esbuild/linux-mips64el": "npm:0.25.4" + "@esbuild/linux-ppc64": "npm:0.25.4" + "@esbuild/linux-riscv64": "npm:0.25.4" + "@esbuild/linux-s390x": "npm:0.25.4" + "@esbuild/linux-x64": "npm:0.25.4" + "@esbuild/netbsd-arm64": "npm:0.25.4" + "@esbuild/netbsd-x64": "npm:0.25.4" + "@esbuild/openbsd-arm64": "npm:0.25.4" + "@esbuild/openbsd-x64": "npm:0.25.4" + "@esbuild/sunos-x64": "npm:0.25.4" + "@esbuild/win32-arm64": "npm:0.25.4" + "@esbuild/win32-ia32": "npm:0.25.4" + "@esbuild/win32-x64": "npm:0.25.4" dependenciesMeta: "@esbuild/aix-ppc64": optional: true @@ -29025,7 +29025,7 @@ __metadata: optional: true bin: esbuild: bin/esbuild - checksum: 10/f1ff72289938330312926421f90eea442025cbbac295a7a2e8cfc2abbd9e3a8bc1502883468b0487e4020f1369e4726c851a2fa4b65a7c71331940072c3a1808 + checksum: 10/227ffe9b31f0b184a0b0a0210bb9d32b2b115b8c5c9b09f08db2c3928cb470fc55a22dbba3c2894365d3abcc62c2089b85638be96a20691d1234d31990ea01b2 languageName: node linkType: hard From accaf1f8f5a246c86949b6c903e8c8baa236bd0f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 7 May 2025 02:31:41 +0000 Subject: [PATCH 105/109] fix(deps): update dependency zod-validation-error to v3.4.1 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/yarn.lock b/yarn.lock index a7bb149ca2..e2357ed9e8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -49322,11 +49322,11 @@ __metadata: linkType: hard "zod-validation-error@npm:^3.0.3, zod-validation-error@npm:^3.4.0": - version: 3.4.0 - resolution: "zod-validation-error@npm:3.4.0" + version: 3.4.1 + resolution: "zod-validation-error@npm:3.4.1" peerDependencies: - zod: ^3.18.0 - checksum: 10/b98b1bbba14a3bb31649a1566c8c5a5213ec70dcaa2cbb1e89db00d56648a446225b35a8f6768471730d7013f4f141cd70c2b9740d69e6433ebfa148aecdac2f + zod: ^3.24.4 + checksum: 10/4975aacc1a931acdbaa3eeaf92fc89a210c6fd14a260d17688ec6302ce478c268c008c61bcdeab51c76191ac627e580f319775a7862be7b9c54c94f2f7cb6ed2 languageName: node linkType: hard From eef2c5c3fdc2817c1957517751bbf5cf0d6ac72a Mon Sep 17 00:00:00 2001 From: Andreas Berger Date: Wed, 7 May 2025 11:13:53 +0200 Subject: [PATCH 106/109] Apply suggestions from code review Co-authored-by: Vincenzo Scamporlino Signed-off-by: Andreas Berger --- .changeset/funny-papayas-tell.md | 5 +---- plugins/catalog/src/components/CatalogTable/CatalogTable.tsx | 1 - 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/.changeset/funny-papayas-tell.md b/.changeset/funny-papayas-tell.md index 8931c7a96f..98beaf89e0 100644 --- a/.changeset/funny-papayas-tell.md +++ b/.changeset/funny-papayas-tell.md @@ -2,7 +2,4 @@ '@backstage/plugin-catalog': minor --- -Harmonize `CatalogTable` - -- Show pagination text for `OffsetPagination` -- Do not show paging if there is only one page +Show the pagination text for the offset-paginated catalog table, and remove the pagination bar from the top of the `CatalogTable` when pagination is enabled. diff --git a/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx b/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx index 8cce9ff317..464688c590 100644 --- a/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx +++ b/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx @@ -195,7 +195,6 @@ export const CatalogTable = (props: CatalogTableProps) => { const actions = props.actions || defaultActions; const options: TableProps['options'] = { - paginationPosition: 'both', actionsColumnIndex: -1, loadingType: 'linear' as const, showEmptyDataSourceMessage: !loading, From c8f32dbefea93f3151dbf96e0862261b3a30ffa4 Mon Sep 17 00:00:00 2001 From: James Brooks <52410024+jabrks@users.noreply.github.com> Date: Wed, 7 May 2025 18:35:37 +0100 Subject: [PATCH 107/109] Canon - Styling fixes for field clear button (#29878) I spotted a couple of problems with the new clear button added to the TextField in #29820. Firstly, it did not use the correct colour token and therefore was not visible in dark mode. Secondly, for fields without a fixed width, the field would grow/shrink in size as the button was shown or hidden. Both of these issues have been rectified here --- .changeset/chatty-months-grow.md | 5 +++++ packages/canon/css/components.css | 10 +++++++++- packages/canon/css/styles.css | 10 +++++++++- packages/canon/css/textfield.css | 10 +++++++++- .../src/components/TextField/TextField.stories.tsx | 2 +- .../src/components/TextField/TextField.styles.css | 10 +++++++++- 6 files changed, 42 insertions(+), 5 deletions(-) create mode 100644 .changeset/chatty-months-grow.md diff --git a/.changeset/chatty-months-grow.md b/.changeset/chatty-months-grow.md new file mode 100644 index 0000000000..1495e7732e --- /dev/null +++ b/.changeset/chatty-months-grow.md @@ -0,0 +1,5 @@ +--- +'@backstage/canon': patch +--- + +Use correct colour token for TextField clear button icon, prevent layout shift whenever it is hidden or shown and properly size focus area around it. Also stop leading icon shrinking when used together with clear button. diff --git a/packages/canon/css/components.css b/packages/canon/css/components.css index c122251b5b..6aae715633 100644 --- a/packages/canon/css/components.css +++ b/packages/canon/css/components.css @@ -581,6 +581,7 @@ width: 1.5rem; height: 1.5rem; color: var(--canon-fg-primary); + flex-shrink: 0; display: block; } @@ -594,18 +595,25 @@ cursor: inherit; background: none; border: none; + padding: 0; transition: border-color .2s ease-in-out, outline-color .2s ease-in-out; } +.canon-TextFieldInput:not([data-filled]):has( + .canon-TextFieldClearButton) { + padding-right: 1.25rem; +} + .canon-TextFieldInput[type="search"]::-webkit-search-cancel-button, .canon-TextFieldInput[type="search"]::-webkit-search-decoration { appearance: none; } .canon-TextFieldClearButton { - padding: 0 0 0 var(--canon-space-1); + margin-left: var(--canon-space-1); vertical-align: middle; + color: var(--canon-fg-primary); background: none; border: none; + padding: 0; display: none; } diff --git a/packages/canon/css/styles.css b/packages/canon/css/styles.css index d1f3ba045d..5fb6a934cb 100644 --- a/packages/canon/css/styles.css +++ b/packages/canon/css/styles.css @@ -9805,6 +9805,7 @@ width: 1.5rem; height: 1.5rem; color: var(--canon-fg-primary); + flex-shrink: 0; display: block; } @@ -9818,18 +9819,25 @@ cursor: inherit; background: none; border: none; + padding: 0; transition: border-color .2s ease-in-out, outline-color .2s ease-in-out; } +.canon-TextFieldInput:not([data-filled]):has( + .canon-TextFieldClearButton) { + padding-right: 1.25rem; +} + .canon-TextFieldInput[type="search"]::-webkit-search-cancel-button, .canon-TextFieldInput[type="search"]::-webkit-search-decoration { appearance: none; } .canon-TextFieldClearButton { - padding: 0 0 0 var(--canon-space-1); + margin-left: var(--canon-space-1); vertical-align: middle; + color: var(--canon-fg-primary); background: none; border: none; + padding: 0; display: none; } diff --git a/packages/canon/css/textfield.css b/packages/canon/css/textfield.css index d55b5b6f2c..a2980d6beb 100644 --- a/packages/canon/css/textfield.css +++ b/packages/canon/css/textfield.css @@ -48,6 +48,7 @@ width: 1.5rem; height: 1.5rem; color: var(--canon-fg-primary); + flex-shrink: 0; display: block; } @@ -61,18 +62,25 @@ cursor: inherit; background: none; border: none; + padding: 0; transition: border-color .2s ease-in-out, outline-color .2s ease-in-out; } +.canon-TextFieldInput:not([data-filled]):has( + .canon-TextFieldClearButton) { + padding-right: 1.25rem; +} + .canon-TextFieldInput[type="search"]::-webkit-search-cancel-button, .canon-TextFieldInput[type="search"]::-webkit-search-decoration { appearance: none; } .canon-TextFieldClearButton { - padding: 0 0 0 var(--canon-space-1); + margin-left: var(--canon-space-1); vertical-align: middle; + color: var(--canon-fg-primary); background: none; border: none; + padding: 0; display: none; } diff --git a/packages/canon/src/components/TextField/TextField.stories.tsx b/packages/canon/src/components/TextField/TextField.stories.tsx index df2eb63be3..0615a157a1 100644 --- a/packages/canon/src/components/TextField/TextField.stories.tsx +++ b/packages/canon/src/components/TextField/TextField.stories.tsx @@ -130,7 +130,7 @@ export const WithOnClear: Story = { ...WithLabel.args, placeholder: 'Search...', type: 'search', - onClear: () => null, + onClear: () => console.log('Cleared!'), }, }; diff --git a/packages/canon/src/components/TextField/TextField.styles.css b/packages/canon/src/components/TextField/TextField.styles.css index ae901c5cc1..ad3ccc931b 100644 --- a/packages/canon/src/components/TextField/TextField.styles.css +++ b/packages/canon/src/components/TextField/TextField.styles.css @@ -65,6 +65,7 @@ width: 1.5rem; height: 1.5rem; color: var(--canon-fg-primary); + flex-shrink: 0; } .canon-TextFieldInput { @@ -78,6 +79,11 @@ width: 100%; height: 100%; cursor: inherit; + padding: 0; +} + +.canon-TextFieldInput:not([data-filled]):has(+ .canon-TextFieldClearButton) { + padding-right: 1.25rem; } .canon-TextFieldInput[type='search']::-webkit-search-cancel-button, @@ -87,10 +93,12 @@ .canon-TextFieldClearButton { display: none; - padding: 0 0 0 var(--canon-space-1); + padding: 0; + margin-left: var(--canon-space-1); background: none; border: none; vertical-align: middle; + color: var(--canon-fg-primary); } .canon-TextFieldInput[data-filled] + .canon-TextFieldClearButton { From 95a70eb07d6679909e472829e46616416aa318a5 Mon Sep 17 00:00:00 2001 From: Danzel Artamadja Date: Thu, 8 May 2025 11:59:22 +0700 Subject: [PATCH 108/109] update createPermissionResourceRef in docs Signed-off-by: Danzel Artamadja --- .../plugin-authors/03-adding-a-resource-permission-check.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/permissions/plugin-authors/03-adding-a-resource-permission-check.md b/docs/permissions/plugin-authors/03-adding-a-resource-permission-check.md index 6ebbdf77e4..e3f23ba17f 100644 --- a/docs/permissions/plugin-authors/03-adding-a-resource-permission-check.md +++ b/docs/permissions/plugin-authors/03-adding-a-resource-permission-check.md @@ -125,7 +125,7 @@ $ yarn workspace @internal/plugin-todo-list-backend add zod Create a new `plugins/todo-list-backend/src/service/rules.ts` file and append the following code: ```typescript title="plugins/todo-list-backend/src/service/rules.ts" -import { makeCreatePermissionRule } from '@backstage/plugin-permission-node'; +import { createPermissionResourceRef } from '@backstage/plugin-permission-node'; import { TODO_LIST_RESOURCE_TYPE } from '@internal/plugin-todo-list-common'; import { z } from 'zod'; import { Todo, TodoFilter } from './todos'; @@ -135,7 +135,7 @@ export const todoListPermissionResourceRef = createPermissionResourceRef< TodoFilter >().with({ pluginId: 'todolist', - type: TODO_LIST_RESOURCE_TYPE, + resourceType: TODO_LIST_RESOURCE_TYPE, }); export const isOwner = createPermissionRule({ From a3da8ca01fd0577742042382447f791231502ee4 Mon Sep 17 00:00:00 2001 From: Danzel Artamadja Date: Thu, 8 May 2025 12:16:40 +0700 Subject: [PATCH 109/109] update use non deprecated createPermissionRule Signed-off-by: Danzel Artamadja --- .../03-adding-a-resource-permission-check.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/permissions/plugin-authors/03-adding-a-resource-permission-check.md b/docs/permissions/plugin-authors/03-adding-a-resource-permission-check.md index e3f23ba17f..afa56307ce 100644 --- a/docs/permissions/plugin-authors/03-adding-a-resource-permission-check.md +++ b/docs/permissions/plugin-authors/03-adding-a-resource-permission-check.md @@ -125,7 +125,10 @@ $ yarn workspace @internal/plugin-todo-list-backend add zod Create a new `plugins/todo-list-backend/src/service/rules.ts` file and append the following code: ```typescript title="plugins/todo-list-backend/src/service/rules.ts" -import { createPermissionResourceRef } from '@backstage/plugin-permission-node'; +import { + createPermissionResourceRef, + createPermissionRule, +} from '@backstage/plugin-permission-node'; import { TODO_LIST_RESOURCE_TYPE } from '@internal/plugin-todo-list-common'; import { z } from 'zod'; import { Todo, TodoFilter } from './todos'; @@ -141,7 +144,7 @@ export const todoListPermissionResourceRef = createPermissionResourceRef< export const isOwner = createPermissionRule({ name: 'IS_OWNER', description: 'Should allow only if the todo belongs to the user', - resourceType: todoListPermissionResourceRef, + resourceRef: todoListPermissionResourceRef, paramsSchema: z.object({ userId: z.string().describe('User ID to match on the resource'), }),