From dab4cf2814ea23aaf1d408a6c69c85d2bec1ec28 Mon Sep 17 00:00:00 2001 From: Matteo Silvestri Date: Thu, 18 Apr 2024 11:22:14 +0100 Subject: [PATCH 01/69] drafting onelogin auth backend module Signed-off-by: Matteo Silvestri --- .../.eslintrc.js | 1 + .../README.md | 8 ++ .../catalog-info.yaml | 10 +++ .../config.d.ts | 34 ++++++++ .../dev/index.ts | 24 ++++++ .../knip-report.md | 19 +++++ .../package.json | 49 ++++++++++++ .../src/authenticator.ts | 74 ++++++++++++++++++ .../src/index.ts | 25 ++++++ .../src/module.test.ts | 78 +++++++++++++++++++ .../src/module.ts | 48 ++++++++++++ .../src/resolvers.ts | 50 ++++++++++++ .../src}/types.d.ts | 5 ++ yarn.lock | 17 ++++ 14 files changed, 442 insertions(+) create mode 100644 plugins/auth-backend-module-onelogin-provider/.eslintrc.js create mode 100644 plugins/auth-backend-module-onelogin-provider/README.md create mode 100644 plugins/auth-backend-module-onelogin-provider/catalog-info.yaml create mode 100644 plugins/auth-backend-module-onelogin-provider/config.d.ts create mode 100644 plugins/auth-backend-module-onelogin-provider/dev/index.ts create mode 100644 plugins/auth-backend-module-onelogin-provider/knip-report.md create mode 100644 plugins/auth-backend-module-onelogin-provider/package.json create mode 100644 plugins/auth-backend-module-onelogin-provider/src/authenticator.ts create mode 100644 plugins/auth-backend-module-onelogin-provider/src/index.ts create mode 100644 plugins/auth-backend-module-onelogin-provider/src/module.test.ts create mode 100644 plugins/auth-backend-module-onelogin-provider/src/module.ts create mode 100644 plugins/auth-backend-module-onelogin-provider/src/resolvers.ts rename plugins/{auth-backend/src/providers/onelogin => auth-backend-module-onelogin-provider/src}/types.d.ts (81%) diff --git a/plugins/auth-backend-module-onelogin-provider/.eslintrc.js b/plugins/auth-backend-module-onelogin-provider/.eslintrc.js new file mode 100644 index 0000000000..e2a53a6ad2 --- /dev/null +++ b/plugins/auth-backend-module-onelogin-provider/.eslintrc.js @@ -0,0 +1 @@ +module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); diff --git a/plugins/auth-backend-module-onelogin-provider/README.md b/plugins/auth-backend-module-onelogin-provider/README.md new file mode 100644 index 0000000000..2af119d25a --- /dev/null +++ b/plugins/auth-backend-module-onelogin-provider/README.md @@ -0,0 +1,8 @@ +# Auth Module: OneLogin Provider + +This module provides an OneLogin auth provider implementation for `@backstage/plugin-auth-backend`. + +## Links + +- [Repository](https://github.com/backstage/backstage/tree/master/plugins/auth-backend-module-onelogin-provider) +- [Backstage Project Homepage](https://backstage.io) diff --git a/plugins/auth-backend-module-onelogin-provider/catalog-info.yaml b/plugins/auth-backend-module-onelogin-provider/catalog-info.yaml new file mode 100644 index 0000000000..6988215b59 --- /dev/null +++ b/plugins/auth-backend-module-onelogin-provider/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-auth-backend-module-onelogin-provider + title: '@backstage/plugin-auth-backend-module-onelogin-provider' + description: The onelogin-provider backend module for the auth plugin. +spec: + lifecycle: experimental + type: backstage-backend-plugin-module + owner: maintainers diff --git a/plugins/auth-backend-module-onelogin-provider/config.d.ts b/plugins/auth-backend-module-onelogin-provider/config.d.ts new file mode 100644 index 0000000000..d4f1457461 --- /dev/null +++ b/plugins/auth-backend-module-onelogin-provider/config.d.ts @@ -0,0 +1,34 @@ +/* + * 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. + */ + +export interface Config { + auth?: { + providers?: { + /** @visibility frontend */ + onelogin?: { + [authEnv: string]: { + clientId: string; + /** + * @visibility secret + */ + clientSecret: string; + issuer: string; + callbackUrl?: string; + }; + }; + }; + }; +} diff --git a/plugins/auth-backend-module-onelogin-provider/dev/index.ts b/plugins/auth-backend-module-onelogin-provider/dev/index.ts new file mode 100644 index 0000000000..d3c18c1d48 --- /dev/null +++ b/plugins/auth-backend-module-onelogin-provider/dev/index.ts @@ -0,0 +1,24 @@ +/* + * Copyright 2023 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { createBackend } from '@backstage/backend-defaults'; + +const backend = createBackend(); + +backend.add(import('@backstage/plugin-auth-backend')); +backend.add(import('../src')); + +backend.start(); diff --git a/plugins/auth-backend-module-onelogin-provider/knip-report.md b/plugins/auth-backend-module-onelogin-provider/knip-report.md new file mode 100644 index 0000000000..a9ad7e156a --- /dev/null +++ b/plugins/auth-backend-module-onelogin-provider/knip-report.md @@ -0,0 +1,19 @@ +# Knip report + +## Unused dependencies (4) + +| Name | Location | Severity | +| :---------------------------- | :----------- | :------- | +| @backstage/backend-plugin-api | package.json | error | +| @backstage/plugin-auth-node | package.json | error | +| passport | package.json | error | +| express | package.json | error | + +## Unused devDependencies (3) + +| Name | Location | Severity | +| :----------------------------- | :----------- | :------- | +| @backstage/plugin-auth-backend | package.json | error | +| @backstage/backend-test-utils | package.json | error | +| @backstage/backend-defaults | package.json | error | + diff --git a/plugins/auth-backend-module-onelogin-provider/package.json b/plugins/auth-backend-module-onelogin-provider/package.json new file mode 100644 index 0000000000..d9185be128 --- /dev/null +++ b/plugins/auth-backend-module-onelogin-provider/package.json @@ -0,0 +1,49 @@ +{ + "name": "@backstage/plugin-auth-backend-module-onelogin-provider", + "version": "0.0.1", + "description": "The onelogin-provider backend module for the auth plugin.", + "backstage": { + "role": "backend-plugin-module" + }, + "publishConfig": { + "access": "public", + "main": "dist/index.cjs.js", + "types": "dist/index.d.ts" + }, + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/auth-backend-module-onelogin-provider" + }, + "license": "Apache-2.0", + "main": "src/index.ts", + "types": "src/index.ts", + "files": [ + "dist", + "config.d.ts" + ], + "scripts": { + "build": "backstage-cli package build", + "clean": "backstage-cli package clean", + "lint": "backstage-cli package lint", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "start": "backstage-cli package start", + "test": "backstage-cli package test" + }, + "dependencies": { + "@backstage/backend-plugin-api": "workspace:^", + "@backstage/plugin-auth-node": "workspace:^", + "express": "^4.18.2", + "passport": "^0.7.0", + "passport-onelogin-oauth": "^0.0.1" + }, + "devDependencies": { + "@backstage/backend-defaults": "workspace:^", + "@backstage/backend-test-utils": "workspace:^", + "@backstage/cli": "workspace:^", + "@backstage/plugin-auth-backend": "workspace:^", + "supertest": "^6.3.3" + }, + "configSchema": "config.d.ts" +} diff --git a/plugins/auth-backend-module-onelogin-provider/src/authenticator.ts b/plugins/auth-backend-module-onelogin-provider/src/authenticator.ts new file mode 100644 index 0000000000..ab1d4c3238 --- /dev/null +++ b/plugins/auth-backend-module-onelogin-provider/src/authenticator.ts @@ -0,0 +1,74 @@ +/* + * Copyright 2023 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Strategy as OneLoginStrategy } from 'passport-onelogin-oauth'; +import { + createOAuthAuthenticator, + PassportOAuthAuthenticatorHelper, + PassportOAuthDoneCallback, + PassportProfile, +} from '@backstage/plugin-auth-node'; + +/** @public */ +export const oneLoginAuthenticator = createOAuthAuthenticator({ + defaultProfileTransform: + PassportOAuthAuthenticatorHelper.defaultProfileTransform, + initialize({ callbackUrl, config }) { + const clientId = config.getString('clientId'); + const clientSecret = config.getString('clientSecret'); + const issuer = config.getString('issuer'); + + return PassportOAuthAuthenticatorHelper.from( + new OneLoginStrategy( + { + clientID: clientId, + clientSecret: clientSecret, + callbackURL: callbackUrl, + issuer, + passReqToCallback: false, + }, + ( + accessToken: string, + refreshToken: string, + params: any, + fullProfile: PassportProfile, + done: PassportOAuthDoneCallback, + ) => { + done( + undefined, + { fullProfile, params, accessToken }, + { refreshToken }, + ); + }, + ), + ); + }, + + async start(input, helper) { + return helper.start(input, { + accessType: 'offline', + prompt: 'consent', + }); + }, + + async authenticate(input, helper) { + return helper.authenticate(input); + }, + + async refresh(input, helper) { + return helper.refresh(input); + }, +}); diff --git a/plugins/auth-backend-module-onelogin-provider/src/index.ts b/plugins/auth-backend-module-onelogin-provider/src/index.ts new file mode 100644 index 0000000000..82855d11a0 --- /dev/null +++ b/plugins/auth-backend-module-onelogin-provider/src/index.ts @@ -0,0 +1,25 @@ +/* + * Copyright 2023 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * The onelogin-provider backend module for the auth plugin. + * + * @packageDocumentation + */ + +export { oneLoginAuthenticator } from './authenticator'; +export { authModuleOneLoginProvider as default } from './module'; +export { oneLoginSignInResolvers } from './resolvers'; diff --git a/plugins/auth-backend-module-onelogin-provider/src/module.test.ts b/plugins/auth-backend-module-onelogin-provider/src/module.test.ts new file mode 100644 index 0000000000..56465d33f4 --- /dev/null +++ b/plugins/auth-backend-module-onelogin-provider/src/module.test.ts @@ -0,0 +1,78 @@ +/* + * Copyright 2023 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { mockServices, startTestBackend } from '@backstage/backend-test-utils'; +import { authModuleOneLoginProvider } from './module'; +import request from 'supertest'; +import { decodeOAuthState } from '@backstage/plugin-auth-node'; + +describe('authModuleOneLoginProvider', () => { + it('should start', async () => { + const { server } = await startTestBackend({ + features: [ + import('@backstage/plugin-auth-backend'), + authModuleOneLoginProvider, + mockServices.rootConfig.factory({ + data: { + app: { + baseUrl: 'http://localhost:3000', + }, + auth: { + providers: { + onelogin: { + development: { + clientId: 'my-client-id', + clientSecret: 'my-client-secret', + issuer: 'https://my-company.onelogin.com/oidc/2', + }, + }, + }, + }, + }, + }), + ], + }); + + const agent = request.agent(server); + + const res = await agent.get('/api/auth/onelogin/start?env=development'); + + expect(res.status).toEqual(302); + + const nonceCookie = agent.jar.getCookie('onelogin-nonce', { + domain: 'localhost', + path: '/api/auth/onelogin/handler', + script: false, + secure: false, + }); + expect(nonceCookie).toBeDefined(); + + const startUrl = new URL(res.get('location')); + expect(startUrl.origin).toBe('https://my-company.onelogin.com'); + expect(startUrl.pathname).toBe('/oidc/2/auth'); + expect(Object.fromEntries(startUrl.searchParams)).toEqual({ + response_type: 'code', + client_id: 'my-client-id', + redirect_uri: `http://localhost:${server.port()}/api/auth/onelogin/handler/frame`, + state: expect.any(String), + }); + + expect(decodeOAuthState(startUrl.searchParams.get('state')!)).toEqual({ + env: 'development', + nonce: decodeURIComponent(nonceCookie.value), + }); + }); +}); diff --git a/plugins/auth-backend-module-onelogin-provider/src/module.ts b/plugins/auth-backend-module-onelogin-provider/src/module.ts new file mode 100644 index 0000000000..7d67424152 --- /dev/null +++ b/plugins/auth-backend-module-onelogin-provider/src/module.ts @@ -0,0 +1,48 @@ +/* + * Copyright 2023 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { createBackendModule } from '@backstage/backend-plugin-api'; +import { + authProvidersExtensionPoint, + commonSignInResolvers, + createOAuthProviderFactory, +} from '@backstage/plugin-auth-node'; +import { oneLoginAuthenticator } from './authenticator'; +import { oneLoginSignInResolvers } from './resolvers'; + +/** @public */ +export const authModuleOneLoginProvider = createBackendModule({ + pluginId: 'auth', + moduleId: 'onelogin-provider', + register(reg) { + reg.registerInit({ + deps: { + providers: authProvidersExtensionPoint, + }, + async init({ providers }) { + providers.registerProvider({ + providerId: 'onelogin', + factory: createOAuthProviderFactory({ + authenticator: oneLoginAuthenticator, + signInResolverFactories: { + ...oneLoginSignInResolvers, + ...commonSignInResolvers, + }, + }), + }); + }, + }); + }, +}); diff --git a/plugins/auth-backend-module-onelogin-provider/src/resolvers.ts b/plugins/auth-backend-module-onelogin-provider/src/resolvers.ts new file mode 100644 index 0000000000..93bfd758cb --- /dev/null +++ b/plugins/auth-backend-module-onelogin-provider/src/resolvers.ts @@ -0,0 +1,50 @@ +/* + * Copyright 2023 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + createSignInResolverFactory, + OAuthAuthenticatorResult, + PassportProfile, + SignInInfo, +} from '@backstage/plugin-auth-node'; + +/** + * Available sign-in resolvers for the OneLogin auth provider. + * + * @public + */ +export namespace oneLoginSignInResolvers { + /** + * Looks up the user by matching their OneLogin username to the entity name. + */ + export const usernameMatchingUserEntityName = createSignInResolverFactory({ + create() { + return async ( + info: SignInInfo>, + ctx, + ) => { + const { result } = info; + + const id = result.fullProfile.username; + if (!id) { + throw new Error(`OneLogin user profile does not contain a username`); + } + + return ctx.signInWithCatalogUser({ entityRef: { name: id } }); + }; + }, + }); +} diff --git a/plugins/auth-backend/src/providers/onelogin/types.d.ts b/plugins/auth-backend-module-onelogin-provider/src/types.d.ts similarity index 81% rename from plugins/auth-backend/src/providers/onelogin/types.d.ts rename to plugins/auth-backend-module-onelogin-provider/src/types.d.ts index 6df10388af..9a05c4f2ec 100644 --- a/plugins/auth-backend/src/providers/onelogin/types.d.ts +++ b/plugins/auth-backend-module-onelogin-provider/src/types.d.ts @@ -13,8 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + declare module 'passport-onelogin-oauth' { + import { Request } from 'express'; + import { StrategyCreated } from 'passport'; + export class Strategy { constructor(options: any, verify: any); + authenticate(this: StrategyCreated, req: Request, options?: any): any; } } diff --git a/yarn.lock b/yarn.lock index 7610418fa2..452796c641 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4688,6 +4688,23 @@ __metadata: languageName: unknown linkType: soft +"@backstage/plugin-auth-backend-module-onelogin-provider@workspace:plugins/auth-backend-module-onelogin-provider": + version: 0.0.0-use.local + resolution: "@backstage/plugin-auth-backend-module-onelogin-provider@workspace:plugins/auth-backend-module-onelogin-provider" + dependencies: + "@backstage/backend-defaults": "workspace:^" + "@backstage/backend-plugin-api": "workspace:^" + "@backstage/backend-test-utils": "workspace:^" + "@backstage/cli": "workspace:^" + "@backstage/plugin-auth-backend": "workspace:^" + "@backstage/plugin-auth-node": "workspace:^" + express: ^4.18.2 + passport: ^0.7.0 + passport-onelogin-oauth: ^0.0.1 + supertest: ^6.3.3 + languageName: unknown + linkType: soft + "@backstage/plugin-auth-backend-module-pinniped-provider@workspace:plugins/auth-backend-module-pinniped-provider": version: 0.0.0-use.local resolution: "@backstage/plugin-auth-backend-module-pinniped-provider@workspace:plugins/auth-backend-module-pinniped-provider" From b78caea0c6aa4747c3a1cfad0871508ba51d446b Mon Sep 17 00:00:00 2001 From: Matteo Silvestri Date: Fri, 19 Apr 2024 09:51:18 +0100 Subject: [PATCH 02/69] add api-report.md Signed-off-by: Matteo Silvestri --- .../api-report.md | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 plugins/auth-backend-module-onelogin-provider/api-report.md diff --git a/plugins/auth-backend-module-onelogin-provider/api-report.md b/plugins/auth-backend-module-onelogin-provider/api-report.md new file mode 100644 index 0000000000..8f94b27cab --- /dev/null +++ b/plugins/auth-backend-module-onelogin-provider/api-report.md @@ -0,0 +1,30 @@ +## API Report File for "@backstage/plugin-auth-backend-module-onelogin-provider" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts +import { BackendFeature } from '@backstage/backend-plugin-api'; +import { OAuthAuthenticator } from '@backstage/plugin-auth-node'; +import { OAuthAuthenticatorResult } from '@backstage/plugin-auth-node'; +import { PassportOAuthAuthenticatorHelper } from '@backstage/plugin-auth-node'; +import { PassportProfile } from '@backstage/plugin-auth-node'; +import { SignInResolverFactory } from '@backstage/plugin-auth-node'; + +// @public (undocumented) +const authModuleOneLoginProvider: () => BackendFeature; +export default authModuleOneLoginProvider; + +// @public (undocumented) +export const oneLoginAuthenticator: OAuthAuthenticator< + PassportOAuthAuthenticatorHelper, + PassportProfile +>; + +// @public +export namespace oneLoginSignInResolvers { + const usernameMatchingUserEntityName: SignInResolverFactory< + OAuthAuthenticatorResult, + unknown + >; +} +``` From 844f5a21bb6abb8e38296856128d067e9a6a4f0b Mon Sep 17 00:00:00 2001 From: Matteo Silvestri Date: Fri, 19 Apr 2024 13:05:14 +0100 Subject: [PATCH 03/69] fix onelogin auth Signed-off-by: Matteo Silvestri --- .../src/apis/implementations/auth/onelogin/OneLoginAuth.ts | 2 +- .../auth-backend-module-onelogin-provider/src/authenticator.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/core-app-api/src/apis/implementations/auth/onelogin/OneLoginAuth.ts b/packages/core-app-api/src/apis/implementations/auth/onelogin/OneLoginAuth.ts index d8f54e7e50..582b5a8282 100644 --- a/packages/core-app-api/src/apis/implementations/auth/onelogin/OneLoginAuth.ts +++ b/packages/core-app-api/src/apis/implementations/auth/onelogin/OneLoginAuth.ts @@ -76,7 +76,7 @@ export default class OneLoginAuth { oauthRequestApi, provider, environment, - defaultScopes: ['openid', 'email', 'profile', 'offline_access'], + defaultScopes: ['openid', 'email', 'profile'], scopeTransform(scopes) { return scopes.map(scope => { if (OIDC_SCOPES.has(scope)) { diff --git a/plugins/auth-backend-module-onelogin-provider/src/authenticator.ts b/plugins/auth-backend-module-onelogin-provider/src/authenticator.ts index ab1d4c3238..f6ac3f55d5 100644 --- a/plugins/auth-backend-module-onelogin-provider/src/authenticator.ts +++ b/plugins/auth-backend-module-onelogin-provider/src/authenticator.ts @@ -58,6 +58,7 @@ export const oneLoginAuthenticator = createOAuthAuthenticator({ }, async start(input, helper) { + input.scope = 'openid email profile'; return helper.start(input, { accessType: 'offline', prompt: 'consent', From 0631fe2d283e807fad2d77d5e974ea3025caa2a5 Mon Sep 17 00:00:00 2001 From: Matteo Silvestri Date: Fri, 19 Apr 2024 13:28:33 +0100 Subject: [PATCH 04/69] fix tests Signed-off-by: Matteo Silvestri --- plugins/auth-backend-module-onelogin-provider/package.json | 2 +- .../auth-backend-module-onelogin-provider/src/module.test.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/auth-backend-module-onelogin-provider/package.json b/plugins/auth-backend-module-onelogin-provider/package.json index d9185be128..7012742eb2 100644 --- a/plugins/auth-backend-module-onelogin-provider/package.json +++ b/plugins/auth-backend-module-onelogin-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-onelogin-provider", - "version": "0.0.1", + "version": "0.0.0", "description": "The onelogin-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/auth-backend-module-onelogin-provider/src/module.test.ts b/plugins/auth-backend-module-onelogin-provider/src/module.test.ts index 56465d33f4..ed3fd4c477 100644 --- a/plugins/auth-backend-module-onelogin-provider/src/module.test.ts +++ b/plugins/auth-backend-module-onelogin-provider/src/module.test.ts @@ -65,6 +65,7 @@ describe('authModuleOneLoginProvider', () => { expect(startUrl.pathname).toBe('/oidc/2/auth'); expect(Object.fromEntries(startUrl.searchParams)).toEqual({ response_type: 'code', + scope: 'openid email profile', client_id: 'my-client-id', redirect_uri: `http://localhost:${server.port()}/api/auth/onelogin/handler/frame`, state: expect.any(String), From 566d7cb3cdf4accc2647bf53d229ef2035caf822 Mon Sep 17 00:00:00 2001 From: Matteo Silvestri Date: Mon, 13 May 2024 17:12:49 +0200 Subject: [PATCH 05/69] add changeset for onelogin-provider Signed-off-by: Matteo Silvestri --- .changeset/breezy-planets-sparkle.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/breezy-planets-sparkle.md diff --git a/.changeset/breezy-planets-sparkle.md b/.changeset/breezy-planets-sparkle.md new file mode 100644 index 0000000000..025becb2d6 --- /dev/null +++ b/.changeset/breezy-planets-sparkle.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-auth-backend-module-onelogin-provider': patch +--- + +Initial changeset to introduce onelogin provider From 3e1bb1567437fbcc11ed20ebdc1b514cc7c7dc8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Tue, 21 May 2024 10:09:30 +0200 Subject: [PATCH 06/69] fixup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .changeset/breezy-planets-sparkle.md | 4 +- .changeset/wild-coats-doubt.md | 5 + .../config/vocabularies/Backstage/accept.txt | 1 + .../auth/onelogin/OneLoginAuth.ts | 2 +- .../src/authenticator.ts | 1 - plugins/auth-backend/package.json | 1 + .../src/providers/onelogin/provider.ts | 192 ++---------------- yarn.lock | 3 +- 8 files changed, 26 insertions(+), 183 deletions(-) create mode 100644 .changeset/wild-coats-doubt.md diff --git a/.changeset/breezy-planets-sparkle.md b/.changeset/breezy-planets-sparkle.md index 025becb2d6..eea9e5c6bf 100644 --- a/.changeset/breezy-planets-sparkle.md +++ b/.changeset/breezy-planets-sparkle.md @@ -1,5 +1,5 @@ --- -'@backstage/plugin-auth-backend-module-onelogin-provider': patch +'@backstage/plugin-auth-backend-module-onelogin-provider': minor --- -Initial changeset to introduce onelogin provider +Separate out the OneLogin provider into its own module diff --git a/.changeset/wild-coats-doubt.md b/.changeset/wild-coats-doubt.md new file mode 100644 index 0000000000..4e86911127 --- /dev/null +++ b/.changeset/wild-coats-doubt.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-auth-backend': patch +--- + +Updated to use the new `@backstage/plugin-auth-backend-module-onelogin-provider` implementation diff --git a/.github/vale/config/vocabularies/Backstage/accept.txt b/.github/vale/config/vocabularies/Backstage/accept.txt index 77dd930a73..7322385e9b 100644 --- a/.github/vale/config/vocabularies/Backstage/accept.txt +++ b/.github/vale/config/vocabularies/Backstage/accept.txt @@ -268,6 +268,7 @@ Olausson Oldsberg onboarding Onboarding +onelogin OpenSearch OpenShift openssl diff --git a/packages/core-app-api/src/apis/implementations/auth/onelogin/OneLoginAuth.ts b/packages/core-app-api/src/apis/implementations/auth/onelogin/OneLoginAuth.ts index 582b5a8282..d8f54e7e50 100644 --- a/packages/core-app-api/src/apis/implementations/auth/onelogin/OneLoginAuth.ts +++ b/packages/core-app-api/src/apis/implementations/auth/onelogin/OneLoginAuth.ts @@ -76,7 +76,7 @@ export default class OneLoginAuth { oauthRequestApi, provider, environment, - defaultScopes: ['openid', 'email', 'profile'], + defaultScopes: ['openid', 'email', 'profile', 'offline_access'], scopeTransform(scopes) { return scopes.map(scope => { if (OIDC_SCOPES.has(scope)) { diff --git a/plugins/auth-backend-module-onelogin-provider/src/authenticator.ts b/plugins/auth-backend-module-onelogin-provider/src/authenticator.ts index f6ac3f55d5..ab1d4c3238 100644 --- a/plugins/auth-backend-module-onelogin-provider/src/authenticator.ts +++ b/plugins/auth-backend-module-onelogin-provider/src/authenticator.ts @@ -58,7 +58,6 @@ export const oneLoginAuthenticator = createOAuthAuthenticator({ }, async start(input, helper) { - input.scope = 'openid email profile'; return helper.start(input, { accessType: 'offline', prompt: 'consent', diff --git a/plugins/auth-backend/package.json b/plugins/auth-backend/package.json index 0797145855..ffdaf53fb3 100644 --- a/plugins/auth-backend/package.json +++ b/plugins/auth-backend/package.json @@ -57,6 +57,7 @@ "@backstage/plugin-auth-backend-module-oauth2-proxy-provider": "workspace:^", "@backstage/plugin-auth-backend-module-oidc-provider": "workspace:^", "@backstage/plugin-auth-backend-module-okta-provider": "workspace:^", + "@backstage/plugin-auth-backend-module-onelogin-provider": "workspace:^", "@backstage/plugin-auth-node": "workspace:^", "@backstage/plugin-catalog-node": "workspace:^", "@backstage/types": "workspace:^", diff --git a/plugins/auth-backend/src/providers/onelogin/provider.ts b/plugins/auth-backend/src/providers/onelogin/provider.ts index 0360a91a1c..ab5995da4c 100644 --- a/plugins/auth-backend/src/providers/onelogin/provider.ts +++ b/plugins/auth-backend/src/providers/onelogin/provider.ts @@ -14,156 +14,18 @@ * limitations under the License. */ -import { Strategy as OneLoginStrategy } from 'passport-onelogin-oauth'; -import express from 'express'; +import { oneLoginAuthenticator } from '@backstage/plugin-auth-backend-module-onelogin-provider'; import { - OAuthAdapter, - OAuthProviderOptions, - OAuthHandlers, - OAuthResponse, - OAuthEnvironmentHandler, - OAuthStartRequest, - encodeState, - OAuthRefreshRequest, - OAuthResult, -} from '../../lib/oauth'; -import passport from 'passport'; -import { - executeFrameHandlerStrategy, - executeRedirectStrategy, - executeRefreshTokenStrategy, - makeProfileInfo, - executeFetchUserProfileStrategy, - PassportDoneCallback, -} from '../../lib/passport'; -import { OAuthStartResponse, AuthHandler } from '../types'; -import { createAuthProviderIntegration } from '../createAuthProviderIntegration'; -import { - AuthResolverContext, SignInResolver, + createOAuthProviderFactory, } from '@backstage/plugin-auth-node'; - -type PrivateInfo = { - refreshToken: string; -}; - -export type Options = OAuthProviderOptions & { - issuer: string; - signInResolver?: SignInResolver; - authHandler: AuthHandler; - resolverContext: AuthResolverContext; -}; - -export class OneLoginProvider implements OAuthHandlers { - private readonly _strategy: any; - private readonly signInResolver?: SignInResolver; - private readonly authHandler: AuthHandler; - private readonly resolverContext: AuthResolverContext; - - constructor(options: Options) { - this.signInResolver = options.signInResolver; - this.authHandler = options.authHandler; - this.resolverContext = options.resolverContext; - this._strategy = new OneLoginStrategy( - { - issuer: options.issuer, - clientID: options.clientId, - clientSecret: options.clientSecret, - callbackURL: options.callbackUrl, - passReqToCallback: false, - }, - ( - accessToken: any, - refreshToken: any, - params: any, - fullProfile: passport.Profile, - done: PassportDoneCallback, - ) => { - done( - undefined, - { - accessToken, - refreshToken, - params, - fullProfile, - }, - { - refreshToken, - }, - ); - }, - ); - } - async start(req: OAuthStartRequest): Promise { - return await executeRedirectStrategy(req, this._strategy, { - accessType: 'offline', - prompt: 'consent', - scope: 'openid', - state: encodeState(req.state), - }); - } - - async handler(req: express.Request) { - const { result, privateInfo } = await executeFrameHandlerStrategy< - OAuthResult, - PrivateInfo - >(req, this._strategy); - - return { - response: await this.handleResult(result), - refreshToken: privateInfo.refreshToken, - }; - } - - async refresh(req: OAuthRefreshRequest) { - const { accessToken, refreshToken, params } = - await executeRefreshTokenStrategy( - this._strategy, - req.refreshToken, - 'openid', - ); - - const fullProfile = await executeFetchUserProfileStrategy( - this._strategy, - accessToken, - ); - - return { - response: await this.handleResult({ - fullProfile, - params, - accessToken, - }), - refreshToken, - }; - } - - private async handleResult(result: OAuthResult) { - const { profile } = await this.authHandler(result, this.resolverContext); - - const response: OAuthResponse = { - providerInfo: { - idToken: result.params.id_token, - accessToken: result.accessToken, - scope: result.params.scope, - expiresInSeconds: result.params.expires_in, - }, - profile, - }; - - if (this.signInResolver) { - response.backstageIdentity = await this.signInResolver( - { - result, - profile, - }, - this.resolverContext, - ); - } - - return response; - } -} +import { + adaptLegacyOAuthHandler, + adaptLegacyOAuthSignInResolver, +} from '../../lib/legacy'; +import { OAuthResult } from '../../lib/oauth'; +import { createAuthProviderIntegration } from '../createAuthProviderIntegration'; +import { AuthHandler } from '../types'; /** * Auth provider integration for OneLogin auth @@ -188,36 +50,10 @@ export const onelogin = createAuthProviderIntegration({ resolver: SignInResolver; }; }) { - return ({ providerId, globalConfig, config, resolverContext }) => - OAuthEnvironmentHandler.mapConfig(config, envConfig => { - const clientId = envConfig.getString('clientId'); - const clientSecret = envConfig.getString('clientSecret'); - const issuer = envConfig.getString('issuer'); - const customCallbackUrl = envConfig.getOptionalString('callbackUrl'); - const callbackUrl = - customCallbackUrl || - `${globalConfig.baseUrl}/${providerId}/handler/frame`; - - const authHandler: AuthHandler = options?.authHandler - ? options.authHandler - : async ({ fullProfile, params }) => ({ - profile: makeProfileInfo(fullProfile, params.id_token), - }); - - const provider = new OneLoginProvider({ - clientId, - clientSecret, - callbackUrl, - issuer, - authHandler, - signInResolver: options?.signIn?.resolver, - resolverContext, - }); - - return OAuthAdapter.fromConfig(globalConfig, provider, { - providerId, - callbackUrl, - }); - }); + return createOAuthProviderFactory({ + authenticator: oneLoginAuthenticator, + profileTransform: adaptLegacyOAuthHandler(options?.authHandler), + signInResolver: adaptLegacyOAuthSignInResolver(options?.signIn?.resolver), + }); }, }); diff --git a/yarn.lock b/yarn.lock index 452796c641..33dbd33246 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4688,7 +4688,7 @@ __metadata: languageName: unknown linkType: soft -"@backstage/plugin-auth-backend-module-onelogin-provider@workspace:plugins/auth-backend-module-onelogin-provider": +"@backstage/plugin-auth-backend-module-onelogin-provider@workspace:^, @backstage/plugin-auth-backend-module-onelogin-provider@workspace:plugins/auth-backend-module-onelogin-provider": version: 0.0.0-use.local resolution: "@backstage/plugin-auth-backend-module-onelogin-provider@workspace:plugins/auth-backend-module-onelogin-provider" dependencies: @@ -4776,6 +4776,7 @@ __metadata: "@backstage/plugin-auth-backend-module-oauth2-proxy-provider": "workspace:^" "@backstage/plugin-auth-backend-module-oidc-provider": "workspace:^" "@backstage/plugin-auth-backend-module-okta-provider": "workspace:^" + "@backstage/plugin-auth-backend-module-onelogin-provider": "workspace:^" "@backstage/plugin-auth-node": "workspace:^" "@backstage/plugin-catalog-node": "workspace:^" "@backstage/types": "workspace:^" From 59cee8160b60635be927b099e5a8cba8df3ffdd8 Mon Sep 17 00:00:00 2001 From: Thomas Cardonne Date: Fri, 24 May 2024 18:24:45 +0200 Subject: [PATCH 07/69] fix(core-components): set variant to inherit on OverflowTooltip Typography Signed-off-by: Thomas Cardonne --- .changeset/gentle-llamas-cross.md | 5 +++++ .../src/components/OverflowTooltip/OverflowTooltip.tsx | 4 +++- 2 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 .changeset/gentle-llamas-cross.md diff --git a/.changeset/gentle-llamas-cross.md b/.changeset/gentle-llamas-cross.md new file mode 100644 index 0000000000..42a6d70cd6 --- /dev/null +++ b/.changeset/gentle-llamas-cross.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-components': patch +--- + +Use `inherit` variant on OverflowTooltip underlying Typography component. diff --git a/packages/core-components/src/components/OverflowTooltip/OverflowTooltip.tsx b/packages/core-components/src/components/OverflowTooltip/OverflowTooltip.tsx index 4b5b0f4bde..dcc381da5f 100644 --- a/packages/core-components/src/components/OverflowTooltip/OverflowTooltip.tsx +++ b/packages/core-components/src/components/OverflowTooltip/OverflowTooltip.tsx @@ -52,7 +52,9 @@ export function OverflowTooltip(props: Props) { title={props.title ?? (props.text || '')} placement={props.placement} > - {props.text} + + {props.text} + ); } From 914736b2bb2c9a5ef3265c32287f234fded83422 Mon Sep 17 00:00:00 2001 From: JeevaRamanathan Date: Sat, 25 May 2024 15:53:43 +0530 Subject: [PATCH 08/69] fixed autocomplete poppercomponent overlapping sidebar Signed-off-by: JeevaRamanathan --- .../EntityAutocompletePicker/EntityAutocompletePicker.tsx | 3 +++ .../src/components/EntityOwnerPicker/EntityOwnerPicker.tsx | 3 +++ .../EntityProcessingStatusPicker.tsx | 3 +++ .../TemplateCategoryPicker/TemplateCategoryPicker.tsx | 3 +++ 4 files changed, 12 insertions(+) diff --git a/plugins/catalog-react/src/components/EntityAutocompletePicker/EntityAutocompletePicker.tsx b/plugins/catalog-react/src/components/EntityAutocompletePicker/EntityAutocompletePicker.tsx index d3c76a183d..8df0c367f6 100644 --- a/plugins/catalog-react/src/components/EntityAutocompletePicker/EntityAutocompletePicker.tsx +++ b/plugins/catalog-react/src/components/EntityAutocompletePicker/EntityAutocompletePicker.tsx @@ -152,6 +152,9 @@ export function EntityAutocompletePicker< {label} + PopperComponent={popperProps => ( +
{popperProps.children}
+ )} multiple disableCloseOnSelect options={availableOptions} diff --git a/plugins/catalog-react/src/components/EntityOwnerPicker/EntityOwnerPicker.tsx b/plugins/catalog-react/src/components/EntityOwnerPicker/EntityOwnerPicker.tsx index 88ff3945d1..f6f1746744 100644 --- a/plugins/catalog-react/src/components/EntityOwnerPicker/EntityOwnerPicker.tsx +++ b/plugins/catalog-react/src/components/EntityOwnerPicker/EntityOwnerPicker.tsx @@ -178,6 +178,9 @@ export const EntityOwnerPicker = (props?: EntityOwnerPickerProps) => { Owner ( +
{popperProps.children}
+ )} multiple disableCloseOnSelect loading={loading} diff --git a/plugins/catalog-react/src/components/EntityProcessingStatusPicker/EntityProcessingStatusPicker.tsx b/plugins/catalog-react/src/components/EntityProcessingStatusPicker/EntityProcessingStatusPicker.tsx index f7de5d72b1..692cf213ab 100644 --- a/plugins/catalog-react/src/components/EntityProcessingStatusPicker/EntityProcessingStatusPicker.tsx +++ b/plugins/catalog-react/src/components/EntityProcessingStatusPicker/EntityProcessingStatusPicker.tsx @@ -73,6 +73,9 @@ export const EntityProcessingStatusPicker = () => { Processing Status + PopperComponent={popperProps => ( +
{popperProps.children}
+ )} multiple disableCloseOnSelect options={availableAdvancedItems} diff --git a/plugins/scaffolder-react/src/next/components/TemplateCategoryPicker/TemplateCategoryPicker.tsx b/plugins/scaffolder-react/src/next/components/TemplateCategoryPicker/TemplateCategoryPicker.tsx index 24f8a21109..5247e86c53 100644 --- a/plugins/scaffolder-react/src/next/components/TemplateCategoryPicker/TemplateCategoryPicker.tsx +++ b/plugins/scaffolder-react/src/next/components/TemplateCategoryPicker/TemplateCategoryPicker.tsx @@ -78,6 +78,9 @@ export const TemplateCategoryPicker = () => { Categories
+ PopperComponent={popperProps => ( +
{popperProps.children}
+ )} multiple id="categories-picker" options={availableTypes} From fa8560edd52516cc357c452aa27957598d60daf6 Mon Sep 17 00:00:00 2001 From: JeevaRamanathan Date: Sat, 25 May 2024 16:01:05 +0530 Subject: [PATCH 09/69] added changeset Signed-off-by: JeevaRamanathan --- .changeset/pretty-spoons-knock.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/pretty-spoons-knock.md diff --git a/.changeset/pretty-spoons-knock.md b/.changeset/pretty-spoons-knock.md new file mode 100644 index 0000000000..f4f55c0c1b --- /dev/null +++ b/.changeset/pretty-spoons-knock.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-scaffolder-react': minor +'@backstage/plugin-catalog-react': minor +--- + +Prevents Autocomplete dropdown from overlapping sidebar on hovering it From 62c80557b638946f4c01569b5ede4338cb517543 Mon Sep 17 00:00:00 2001 From: JeevaRamanathan Date: Sat, 25 May 2024 18:42:43 +0530 Subject: [PATCH 10/69] added ReactNode to fix pipeline Signed-off-by: JeevaRamanathan --- .../EntityAutocompletePicker/EntityAutocompletePicker.tsx | 4 ++-- .../src/components/EntityOwnerPicker/EntityOwnerPicker.tsx | 4 ++-- .../EntityProcessingStatusPicker.tsx | 4 ++-- .../TemplateCategoryPicker/TemplateCategoryPicker.tsx | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/plugins/catalog-react/src/components/EntityAutocompletePicker/EntityAutocompletePicker.tsx b/plugins/catalog-react/src/components/EntityAutocompletePicker/EntityAutocompletePicker.tsx index 8df0c367f6..4bd9f100f9 100644 --- a/plugins/catalog-react/src/components/EntityAutocompletePicker/EntityAutocompletePicker.tsx +++ b/plugins/catalog-react/src/components/EntityAutocompletePicker/EntityAutocompletePicker.tsx @@ -20,7 +20,7 @@ import Typography from '@material-ui/core/Typography'; import { makeStyles } from '@material-ui/core/styles'; import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; import Autocomplete from '@material-ui/lab/Autocomplete'; -import React, { useEffect, useMemo, useState } from 'react'; +import React, { useEffect, useMemo, useState, ReactNode } from 'react'; import { useApi } from '@backstage/core-plugin-api'; import useAsync from 'react-use/esm/useAsync'; import { catalogApiRef } from '../../api'; @@ -153,7 +153,7 @@ export function EntityAutocompletePicker< {label} PopperComponent={popperProps => ( -
{popperProps.children}
+
{popperProps.children as ReactNode}
)} multiple disableCloseOnSelect diff --git a/plugins/catalog-react/src/components/EntityOwnerPicker/EntityOwnerPicker.tsx b/plugins/catalog-react/src/components/EntityOwnerPicker/EntityOwnerPicker.tsx index f6f1746744..c6b738d18b 100644 --- a/plugins/catalog-react/src/components/EntityOwnerPicker/EntityOwnerPicker.tsx +++ b/plugins/catalog-react/src/components/EntityOwnerPicker/EntityOwnerPicker.tsx @@ -30,7 +30,7 @@ import CheckBoxIcon from '@material-ui/icons/CheckBox'; import CheckBoxOutlineBlankIcon from '@material-ui/icons/CheckBoxOutlineBlank'; import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; import Autocomplete from '@material-ui/lab/Autocomplete'; -import React, { useEffect, useMemo, useState } from 'react'; +import React, { useEffect, useMemo, useState, ReactNode } from 'react'; import { useEntityList } from '../../hooks/useEntityListProvider'; import { EntityOwnerFilter } from '../../filters'; import { useDebouncedEffect } from '@react-hookz/web'; @@ -179,7 +179,7 @@ export const EntityOwnerPicker = (props?: EntityOwnerPickerProps) => { Owner ( -
{popperProps.children}
+
{popperProps.children as ReactNode}
)} multiple disableCloseOnSelect diff --git a/plugins/catalog-react/src/components/EntityProcessingStatusPicker/EntityProcessingStatusPicker.tsx b/plugins/catalog-react/src/components/EntityProcessingStatusPicker/EntityProcessingStatusPicker.tsx index 692cf213ab..9995aba936 100644 --- a/plugins/catalog-react/src/components/EntityProcessingStatusPicker/EntityProcessingStatusPicker.tsx +++ b/plugins/catalog-react/src/components/EntityProcessingStatusPicker/EntityProcessingStatusPicker.tsx @@ -24,7 +24,7 @@ import { makeStyles } from '@material-ui/core/styles'; import CheckBoxIcon from '@material-ui/icons/CheckBox'; import CheckBoxOutlineBlankIcon from '@material-ui/icons/CheckBoxOutlineBlank'; import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; -import React, { useState } from 'react'; +import React, { useState, ReactNode } from 'react'; import { useEntityList } from '../../hooks'; import Autocomplete from '@material-ui/lab/Autocomplete'; @@ -74,7 +74,7 @@ export const EntityProcessingStatusPicker = () => { Processing Status PopperComponent={popperProps => ( -
{popperProps.children}
+
{popperProps.children as ReactNode}
)} multiple disableCloseOnSelect diff --git a/plugins/scaffolder-react/src/next/components/TemplateCategoryPicker/TemplateCategoryPicker.tsx b/plugins/scaffolder-react/src/next/components/TemplateCategoryPicker/TemplateCategoryPicker.tsx index 5247e86c53..f2505ec148 100644 --- a/plugins/scaffolder-react/src/next/components/TemplateCategoryPicker/TemplateCategoryPicker.tsx +++ b/plugins/scaffolder-react/src/next/components/TemplateCategoryPicker/TemplateCategoryPicker.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import React from 'react'; +import React, { ReactNode } from 'react'; import capitalize from 'lodash/capitalize'; import { Progress } from '@backstage/core-components'; import Box from '@material-ui/core/Box'; @@ -79,7 +79,7 @@ export const TemplateCategoryPicker = () => {
PopperComponent={popperProps => ( -
{popperProps.children}
+
{popperProps.children as ReactNode}
)} multiple id="categories-picker" From bccc70acd1f0810e328a08c4355e2d65c7819ebb Mon Sep 17 00:00:00 2001 From: JeevaRamanathan Date: Sat, 25 May 2024 19:49:02 +0530 Subject: [PATCH 11/69] Retrigger Build Signed-off-by: JeevaRamanathan From 1f71c1cd3835eb56f77af64ed8140f4d2990457d Mon Sep 17 00:00:00 2001 From: Valber Junior <84424883+ValberJunior@users.noreply.github.com> Date: Mon, 27 May 2024 12:19:22 -0300 Subject: [PATCH 12/69] Create kong-service-manager.yaml The Kong Service Manager plugin provides information about the kong service, a list of the routes it has and also offers the possibility of manipulating plugins without leaving the backstage. Signed-off-by: Valber Junior <84424883+ValberJunior@users.noreply.github.com> --- microsite/data/plugins/kong-service-manager.yaml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 microsite/data/plugins/kong-service-manager.yaml diff --git a/microsite/data/plugins/kong-service-manager.yaml b/microsite/data/plugins/kong-service-manager.yaml new file mode 100644 index 0000000000..78ff37e36e --- /dev/null +++ b/microsite/data/plugins/kong-service-manager.yaml @@ -0,0 +1,16 @@ +--- +title: VeeCode Kong Service Manager +author: VeeCode Platform +authorUrl: https://platform.vee.codes/ +category: api-manager +description: The Kong Service Manager plugin provides information about the kong service, a list of the routes it has and also offers the possibility of manipulating plugins without leaving the backstage. +documentation: https://platform.vee.codes/plugin/Kong%20Service%20Manager/ +iconUrl: https://veecode-platform.github.io/support/imgs/logo_3.svg +npmPackageName: '@veecode-platform/plugin-kong-service-manager' +tags: + - kong + - api-manager + - services + - routes + - plugins +addedDate: '2024-05-27' From 0a08d9027c07655b4e26dd5e8d6d6998b1d88d28 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 20 May 2024 12:53:04 +0200 Subject: [PATCH 13/69] beps: plugin meta Signed-off-by: Patrik Oldsberg --- beps/0008-plugin-metadata/README.md | 119 ++++++++++++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 beps/0008-plugin-metadata/README.md diff --git a/beps/0008-plugin-metadata/README.md b/beps/0008-plugin-metadata/README.md new file mode 100644 index 0000000000..1d7db98d92 --- /dev/null +++ b/beps/0008-plugin-metadata/README.md @@ -0,0 +1,119 @@ +--- +title: Plugin Metadata +status: provisional +authors: + - '@Rugvip' +owners: + - '@backstage/maintainers' +project-areas: + - core +creation-date: yyyy-mm-dd +--- + + + +# BEP: Plugin Metadata + + + +[**Discussion Issue**](https://github.com/backstage/backstage/issues/NNNNN) + +- [Summary](#summary) +- [Motivation](#motivation) + - [Goals](#goals) + - [Non-Goals](#non-goals) +- [Proposal](#proposal) +- [Design Details](#design-details) +- [Release Plan](#release-plan) +- [Dependencies](#dependencies) +- [Alternatives](#alternatives) + +## Summary + + + +## Motivation + +All Backstage adopters benefit from it being easy to find, install, configure, and validate the setup of plugins and modules. That's regardless of whether one is only using a minimal set of core plugins, or installing a large number of 3rd party features from the Backstage ecosystem. This proposal aims to build upon our [existing package metadata](https://backstage.io/docs/tooling/cli/build-system#package-roles) to introduce a new set of standardized fields that plugins can use to provide additional information about themselves. + +This metadata will be used to improve tooling in an around Backstage, such as providing better validation and more powerful utilities in the Backstage CLI, and the more information to be surfaced in association with plugins for better discoverability. + +### Goals + +We want to provide a new set of standardized fields that makes it possible to build tooling that improves the following interactions with the Backstage ecosystem: + +- Browsing packages related to a plugin, i.e. listing all available library packages and frontend/backend variants of a specific plugin. +- Discoverability of plugin modules. Given a plugin you should be able to identify all of its modules. + +Furthermore this proposal should also lay the foundation for how we define additional metadata fields in the future. How they are validate, documented, and surfaced in the Backstage ecosystem. + +In relation to the above, this proposal will aim to define what existing `package.json` fields are known in the Backstage ecosystem, and what their purpose is. This will help avoid duplication of fields, and aim to reduce metadata fragmentation. + +Where appropriate, this new metadata will be made required for all packages that are published using Backstage's tooling. This will ensure that packages across the ecosystem are consistent in how they provide metadata. This requirement should not be unnecessarily strict, ideally only requiring fields that either have sensible defaults or that can be inferred from workspace content. + +These goals must all be reached in a way that does not risk intentional or unintentional disruption of the metadata. For example it should not be possible for an unrelated package to claim to be part of a group of plugin packages that it is not part of. + +### Non-Goals + +The surfacing of extensive implementation metadata is not in scope for this proposal. This includes for example the extension point and extensions exported by a plugin, what route references a frontend plugin has, or what API endpoints a backend plugin exposes. + +This proposal will not aim to standardize any other fields that are not included in the above [goals](#goals). It will provide the foundation for how to define such fields, but not the fields themselves. This includes any informational fields such as package lifecycle or usage, but also fields that provide functional requirements, such as plugin dependencies or environment restrictions. + +This proposal does not aim to provide any direct solution for plugin support metadata as proposed in [#10256](https://github.com/backstage/backstage/pull/10256). Many of the fields defined as followups to this proposal may cover the support metadata use-case, but what is also important for such a solution is to be able to provide local overrides for the metadata, which this proposal does not cover. We do however want to make sure that this use-case is taken into consideration when defining new metadata fields. + +## Proposal + +The proposal is split into separate sections, each contributing to the overall solution in different ways. + +### Validation & Automation + +The purpose of this section is to define how we make it as simple as possible to populate the metadata fields, and how we ensure that the metadata is present and correct. + +Validation and automation is split into two separate phases. The first is phase is manual tooling that is run by the developer and typically validated in CI, for example the `backstage-cli fix` command. The second phase is validation at the time of publishing the package, which is done by the `yarn prepack` command. By splitting the tooling into these two phases we can ensure that a minimal set of metadata is present for all packages published using the Backstage tooling, but at the same time avoid adding unnecessary friction to the development process. + +An important aspect to take into consideration is that most Backstage packages are never published to NPM, as they're part of an internal monorepo and get built directly into an internal Backstage application. Defining the full set of metadata for these packages is likely to be wasteful, which means we + +### Package Relationships + +### Package Metadata Documentation + +## Design Details + +Light validation done in `yarn prepack` to ensure that metadata is present and correct. + +### References & Prior Art + +The following resources have been used to inform this proposal: + +- [package.json documentation](https://docs.npmjs.com/cli/v10/configuring-npm/package-json) +- [VSCode Extensions](https://code.visualstudio.com/api/references/extension-manifest) + +## Release Plan + +Assuming that the required fields can be populated with minimal effort + + + +## Dependencies + + + +## Alternatives + + From dc2bb1c39eba2c2f731e637901efd51941796701 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 30 May 2024 16:00:36 +0200 Subject: [PATCH 14/69] beps/0008: fill out the rest of the proposal Signed-off-by: Patrik Oldsberg --- beps/0008-plugin-metadata/README.md | 58 +++++++++++++++++++++++++++-- 1 file changed, 54 insertions(+), 4 deletions(-) diff --git a/beps/0008-plugin-metadata/README.md b/beps/0008-plugin-metadata/README.md index 1d7db98d92..790a97c69b 100644 --- a/beps/0008-plugin-metadata/README.md +++ b/beps/0008-plugin-metadata/README.md @@ -77,15 +77,61 @@ The purpose of this section is to define how we make it as simple as possible to Validation and automation is split into two separate phases. The first is phase is manual tooling that is run by the developer and typically validated in CI, for example the `backstage-cli fix` command. The second phase is validation at the time of publishing the package, which is done by the `yarn prepack` command. By splitting the tooling into these two phases we can ensure that a minimal set of metadata is present for all packages published using the Backstage tooling, but at the same time avoid adding unnecessary friction to the development process. -An important aspect to take into consideration is that most Backstage packages are never published to NPM, as they're part of an internal monorepo and get built directly into an internal Backstage application. Defining the full set of metadata for these packages is likely to be wasteful, which means we +An important aspect to take into consideration is that most Backstage packages are never published to NPM, as they're part of an internal monorepo and get built directly into an internal Backstage application. Defining the full set of metadata for these packages is likely to be wasteful, which means we should avoid building too many requirements for that path. + +### Package Metadata and Documentation + +This section defines our strategy for defining new metadata fields, and how they are documented. + +To the furthest extent possible we will rely on established standards for `package.json` fields, but we will do so within the bounds of the intended usage of these fields. This means that we will only use existing fields for their intended purpose, and instead define new fields if no existing field is suitable. An index of existing fields is found in the [NPM documentation](https://docs.npmjs.com/cli/v10/configuring-npm/package-json), but we are not limited to this list. Any fields that we can find within the NPM ecosystem may be used, as long as they are well established, for example if there could be a use for the `prettier` field [defined by prettier](https://prettier.io/docs/en/configuration.html#sharing-configurations). + +In the case of new fields we always define them to be part of the top-level `"backstage"` field. The assumption is that any field that we define will always be tied to the Backstage ecosystem, even in the case of general fields such as "lifecycle". The only exception to this is if we believe that a field is generic enough that it could genuinely become part of the broader NPM ecosystem. + +All fields must be documented in a new section of the "Tooling" documentation on backstage.io. This documentation should include a description of the field, what it is used for, and what the expected values are. The documentation must also mention whether the field is expected to be filled in manually, or if it will be generated by tooling. Changes to this documentation must be approved by the Backstage core maintainers, it is not owned by individual project area maintainers. ### Package Relationships -### Package Metadata Documentation +This section defines how we associate packages from the same plugin with each other. + +Package the are part of the same plugin should always be managed within the same monorepo and workspace. This does not apply to modules for a plugin, which may be hosted separately, but may still need to refer to the plugin that it is a module for. + +Each plugin package must define a `backstage.pluginId` field, which is the same identifier as is used in the implementation of the plugin. This field is inferred from the package name by the `backstage-cli repo fix` command if it is not present. It should only be defined for plugin package, for example `@backstage/errors` should not define a plugin ID. The `backstage.pluginId` field is required when publishing a package with a plugin or module role, or a library role with "plugin" in its name. + +The package relationships are defined in the `backstage.pluginPackages` field. The value of the field is an object where each key is the role of the package as defined by the `backstage.role` field, and the value is the package name. For example: + +```json +{ + "backstage": { + "pluginId": "catalog", + "pluginPackages": { + "frontend-plugin": "@backstage/plugin-catalog", + "backend-plugin": "@backstage/plugin-catalog-backend", + "web-library": "@backstage/plugin-catalog-react", + "node-library": "@backstage/plugin-catalog-node", + "common-library": "@backstage/plugin-catalog-common" + } + } +} +``` + +The `backstage.pluginPackages` field is generated and updated by the `backstage-cli repo fix` command based on the packages that are present in the workspace and their `backstage.pluginId` and `backstage.role` fields. There can only be a single package of each role with a given plugin ID. The `backstage.pluginPackages` field is required when publishing a package with a `backstage.pluginId` field that is not using a module role. + +Module packages define their target plugin both via the `backstage.pluginId` field, as well as via `backstage.pluginPackage`. For example: + +```json +{ + "backstage": { + "pluginId": "catalog", + "pluginPackage": "@backstage/plugin-catalog-backend" + } +} +``` + +The `backstage.pluginPackage` field is required when publishing a package with a module role. ## Design Details -Light validation done in `yarn prepack` to ensure that metadata is present and correct. +_-_ ### References & Prior Art @@ -96,7 +142,7 @@ The following resources have been used to inform this proposal: ## Release Plan -Assuming that the required fields can be populated with minimal effort +TBD ## Alternatives +TBD + +This proposal suggests that we keep metadata in the `package.json` file. An alternative would be to define a separate metadata file, for example `backstage.json` or `backstage.yaml`. + +A benefit of this approach is that we do not pollute the `package.json` file with additional metadata, which can help keep overall metadata easier to browse and understand. It also allows us to use our own format, such as allowing comments in JSON, or using YAML for easier readability. + +A downside of this approach is that it introduces a bit more complexity and friction in the tooling and package publishing, because we need to make sure that the metadata file is always included in the published package. We also end up with additional logic for finding and parsing this file, whereas resolving and parsing `package.json` is already a solved problem. + +One of the larger benefits of keeping package metadata `package.json` is that it makes the data immediately available in registry APIs. It avoids the need to download and parse additional files to get the metadata for a package. For this reason we believe that we should stick to using `package.json` for metadata for the time being. If we for some reason in the future find that we need to use a separate file, for example because the metadata becomes too large, we can always evolve the `backstage` field in a couple of different ways: + +```json +{ + // Moving all data to a separate file + "backstage": "backstage.json" +} +{ + // Moving some data to a separate file + "backstage": { + "role": "frontend-plugin", + "metadata": "backstage.json" + } +} +``` From 830780d6cf7a3be710f87b90bb458fcb9d11b019 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 5 Jun 2024 15:48:02 +0200 Subject: [PATCH 55/69] beps/0009: add conflict resolution Signed-off-by: Patrik Oldsberg --- beps/0009-plugin-metadata/README.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/beps/0009-plugin-metadata/README.md b/beps/0009-plugin-metadata/README.md index 366a042e46..ce66e64416 100644 --- a/beps/0009-plugin-metadata/README.md +++ b/beps/0009-plugin-metadata/README.md @@ -135,7 +135,16 @@ The `backstage.pluginPackage` field is required when publishing a package with a ## Design Details -_-_ +### Package Relationships Conflict Resolution + +Conflicts in the defined metadata are handled as follows: + +- The union of all `pluginPackages` is always used. If there are multiple packages defined for the same role, conflicts are resolved using the `backstage.moved` field if possible, but otherwise all packages are rejected. +- All plugin packages with references to each other must have the same `pluginId`, or they are all rejected. +- A module must have the same `pluginId` as the plugin it is a module for, or it is rejected. +- If the role of a package does not match the definition in the `pluginPackages` field, the package is rejected. + +When metadata is "rejected" the consumer should either throw an error, or treat the conflicted fields as if they do not exist. ### References & Prior Art From 4532e8aab98f269beabe94787eb8bc6e75057194 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 5 Jun 2024 15:52:11 +0200 Subject: [PATCH 56/69] beps/0009: add lightweight rollout plan Signed-off-by: Patrik Oldsberg --- beps/0009-plugin-metadata/README.md | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/beps/0009-plugin-metadata/README.md b/beps/0009-plugin-metadata/README.md index ce66e64416..c8820d411e 100644 --- a/beps/0009-plugin-metadata/README.md +++ b/beps/0009-plugin-metadata/README.md @@ -155,22 +155,12 @@ The following resources have been used to inform this proposal: ## Release Plan -TBD - - +The new metadata fields and any generation and validation of fields will initially be rolled out as part of the `repo fix` command in the Backstage CLI. In addition we will also add validation to the `yarn prepack` command in such a way that it does not interfere with backend package bundling, but does apply when packing packages for publishing. These changes will all be rolled out in a single release of the Backstage CLI, meaning that anyone publishing Backstage packages using the Backstage CLI will be required to provide the new metadata. ## Dependencies None - - ## Alternatives ### Separate Metadata File From 7aa11037e959298e5872db0cf9c9acf3b34e01ff Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 5 Jun 2024 16:09:32 +0200 Subject: [PATCH 57/69] beps/0009: add alternative library format Signed-off-by: Patrik Oldsberg --- beps/0009-plugin-metadata/README.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/beps/0009-plugin-metadata/README.md b/beps/0009-plugin-metadata/README.md index c8820d411e..22a2468d35 100644 --- a/beps/0009-plugin-metadata/README.md +++ b/beps/0009-plugin-metadata/README.md @@ -163,6 +163,35 @@ None ## Alternatives +### Simplified Library Relationships + +Rather than listing libraries for each role, we could simply have an array of libraries that are available for the plugin: + +```json +{ + "name": "@backstage/plugin-catalog", + "backstage": { + "role": "frontend-plugin", + "pluginId": "catalog", + "pluginPackages": { + "frontend-plugin": "@backstage/plugin-catalog", + "backend-plugin": "@backstage/plugin-catalog-backend", + "libraries": [ + "@backstage/plugin-catalog-react", + "@backstage/plugin-catalog-node", + "@backstage/plugin-catalog-common" + ] + } + } +} +``` + +A benefit of this approach is that it keeps the metadata simpler and reduces the need for conflict resolution logic since we can use the union of all listed library packages. + +It also allows for the use-case of having multiple libraries of the same role, in case that would provide a benefit. Examples of this are the `@backstage/catalog-client` and `@backstage/catalog-model` packages. + +A downside of this approach could be that it may encourage a larger number of library packages, which is not necessarily what we want. Another downside is that the role of each package is not immediately available. + ### Separate Metadata File This proposal suggests that we keep metadata in the `package.json` file. An alternative would be to define a separate metadata file, for example `backstage.json` or `backstage.yaml`. From 1f60035ab38ede3672cf9cca5d588b80f21d90a6 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 5 Jun 2024 16:11:07 +0200 Subject: [PATCH 58/69] beps/0009: explicit goal to be able to discovery plugin from module Signed-off-by: Patrik Oldsberg --- beps/0009-plugin-metadata/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beps/0009-plugin-metadata/README.md b/beps/0009-plugin-metadata/README.md index 22a2468d35..1336178ce9 100644 --- a/beps/0009-plugin-metadata/README.md +++ b/beps/0009-plugin-metadata/README.md @@ -49,7 +49,7 @@ This metadata will be used to improve tooling in and around Backstage, such as p We want to provide a new set of standardized fields that makes it possible to build tooling that improves the following interactions with the Backstage ecosystem: - Browsing packages related to a plugin, i.e. listing all available library packages and frontend/backend variants of a specific plugin. -- Discoverability of plugin modules. Given a plugin you should be able to identify all of its modules. +- Discoverability of plugin modules. Given a plugin you should be able to identify all of its modules, and given a module you should be able to discovery its plugin. Furthermore this proposal should also lay the foundation for how we define additional metadata fields in the future. How they are validated, documented, and surfaced in the Backstage ecosystem. From 1576daa31c6c96c65e830aa9cccf7b71f81228e2 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 5 Jun 2024 16:26:15 +0200 Subject: [PATCH 59/69] beps/0009: add summary Signed-off-by: Patrik Oldsberg --- beps/0009-plugin-metadata/README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/beps/0009-plugin-metadata/README.md b/beps/0009-plugin-metadata/README.md index 1336178ce9..8a2165e10f 100644 --- a/beps/0009-plugin-metadata/README.md +++ b/beps/0009-plugin-metadata/README.md @@ -34,9 +34,7 @@ When editing BEPs, aim for tightly-scoped, single-topic PRs to keep discussions ## Summary - +The goal of this BEP is to define a strategy for how we define metadata for Backstage packages, as well as define a new set of metadata for how to associate packages with each other. The proposed solution is to store all plugin metadata in `package.json` under the `backstage` field, unless there is an existing field that can be used instead. Each piece of metadata is documented in the Backstage documentation, and as much of the metadata as possible is generated and validated by the Backstage CLI. The validation is structured in such a way that it does not interfere with internal Backstage setups, and only applies when publishing packages to NPM. ## Motivation From 4fbdb21e83bb62fad57087d7f6c4820bf3aca24e Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 5 Jun 2024 16:26:43 +0200 Subject: [PATCH 60/69] beps/0009: add issue link Signed-off-by: Patrik Oldsberg --- beps/0009-plugin-metadata/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beps/0009-plugin-metadata/README.md b/beps/0009-plugin-metadata/README.md index 8a2165e10f..a23a2d7ce4 100644 --- a/beps/0009-plugin-metadata/README.md +++ b/beps/0009-plugin-metadata/README.md @@ -20,7 +20,7 @@ When editing BEPs, aim for tightly-scoped, single-topic PRs to keep discussions -[**Discussion Issue**](https://github.com/backstage/backstage/issues/NNNNN) +[**Discussion Issue**](https://github.com/backstage/backstage/issues/25076) - [Summary](#summary) - [Motivation](#motivation) From 5a9d2ba3a9f53028b763c59a1fdfc4408f10085c Mon Sep 17 00:00:00 2001 From: Andre Wanlin Date: Thu, 6 Jun 2024 08:03:09 -0500 Subject: [PATCH 61/69] Fixed link to Tech Radar in Blog Post Signed-off-by: Andre Wanlin --- microsite/blog/2020-05-14-tech-radar-plugin.mdx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/microsite/blog/2020-05-14-tech-radar-plugin.mdx b/microsite/blog/2020-05-14-tech-radar-plugin.mdx index 457a03d488..511e764ce3 100644 --- a/microsite/blog/2020-05-14-tech-radar-plugin.mdx +++ b/microsite/blog/2020-05-14-tech-radar-plugin.mdx @@ -22,7 +22,7 @@ We also assign clear definitions for each lifecycle: - **Use:** This technology is recommended for use by the majority of teams with a specific use case. - **Trial:** This technology has been evaluated for specific use cases and has showed clear benefits. Some teams adopt it in production, although it should be limited to low-impact projects as it might incur a higher risk. - **Assess:** This technology has the potential to be beneficial for the company. Some teams are evaluating it and using it in experimental projects. Using it in production comes with a high cost and risk due to lack of in-house knowledge, maintenance, and support. -- **Hold:** We don’t want to further invest in this technology or we evaluated it and we don’t see it as beneficial for the company. Teams should not use it in new projects and should plan on migrating to a supported alternative if they use it for historical reasons. For broadly adopted technologies, the Radar should refer to a migration path to a supported alternative. +- **Hold:** We don't want to further invest in this technology or we evaluated it and we don't see it as beneficial for the company. Teams should not use it in new projects and should plan on migrating to a supported alternative if they use it for historical reasons. For broadly adopted technologies, the Radar should refer to a migration path to a supported alternative. Since rolling out the Tech Radar, it has become the source of truth when creating, maintaining, or evolving our software ecosystem. Spotify has dozens of entries in our Radar and it can scale quite well whilst being easy for our engineers and engineering managers to consume. @@ -32,13 +32,13 @@ To learn about how you can bring the Tech Radar to your Backstage installation, ## A personal note -I want to thank both the Backstage team and Spotify. Firstly, I’ve been working with our internal version of Backstage for over a year, and the developer experience since open sourcing has been even more of a joy to work with. Secondly, the 10% hack time that Spotify generously provides to all engineers enabled me to open source the Tech Radar plugin. +I want to thank both the Backstage team and Spotify. Firstly, I've been working with our internal version of Backstage for over a year, and the developer experience since open sourcing has been even more of a joy to work with. Secondly, the 10% hack time that Spotify generously provides to all engineers enabled me to open source the Tech Radar plugin. Since open sourcing it, the community has shown great interest in yet another powerful use case of Backstage. There was also an enthusiastic open source contributor who volunteered to migrate the plugin to TypeScript and React Hooks [in just 29 minutes](https://github.com/backstage/backstage/issues/661) of opening the issue! -I can’t wait to see how others benefit from the Tech Radar in their organizations! +I can't wait to see how others benefit from the Tech Radar in their organizations! [lighthouse website audits]: https://backstage.io/blog/2020/04/06/lighthouse-plugin -[tech radar plugin]: https://github.com/backstage/backstage/tree/master/plugins/tech-radar +[tech radar plugin]: https://github.com/backstage/community-plugins/tree/main/workspaces/tech-radar/plugins/tech-radar [thoughtworks]: https://www.thoughtworks.com/radar [zalando]: https://opensource.zalando.com/tech-radar/ From e6ec17948dce032a9d556eb877024d46d4a3c805 Mon Sep 17 00:00:00 2001 From: Heikki Hellgren Date: Thu, 30 May 2024 13:17:59 +0300 Subject: [PATCH 62/69] feat: use signals to update user settings over sessions closes #24981 Signed-off-by: Heikki Hellgren --- .changeset/rude-snakes-clap.md | 7 ++++ plugins/user-settings-backend/README.md | 21 +++++++++-- plugins/user-settings-backend/api-report.md | 3 ++ plugins/user-settings-backend/package.json | 2 + plugins/user-settings-backend/src/alpha.ts | 8 ++-- .../src/service/router.test.ts | 15 ++++++++ .../src/service/router.ts | 20 ++++++++++ plugins/user-settings-common/.eslintrc.js | 1 + plugins/user-settings-common/README.md | 5 +++ plugins/user-settings-common/api-report.md | 13 +++++++ .../user-settings-common/catalog-info.yaml | 10 +++++ plugins/user-settings-common/package.json | 37 +++++++++++++++++++ plugins/user-settings-common/src/index.ts | 17 +++++++++ .../user-settings-common/src/setupTests.ts | 16 ++++++++ plugins/user-settings-common/src/types.ts | 21 +++++++++++ plugins/user-settings/api-report.md | 2 + plugins/user-settings/package.json | 2 + .../apis/StorageApi/UserSettingsStorage.ts | 33 ++++++++++++++--- yarn.lock | 12 ++++++ 19 files changed, 234 insertions(+), 11 deletions(-) create mode 100644 .changeset/rude-snakes-clap.md create mode 100644 plugins/user-settings-common/.eslintrc.js create mode 100644 plugins/user-settings-common/README.md create mode 100644 plugins/user-settings-common/api-report.md create mode 100644 plugins/user-settings-common/catalog-info.yaml create mode 100644 plugins/user-settings-common/package.json create mode 100644 plugins/user-settings-common/src/index.ts create mode 100644 plugins/user-settings-common/src/setupTests.ts create mode 100644 plugins/user-settings-common/src/types.ts diff --git a/.changeset/rude-snakes-clap.md b/.changeset/rude-snakes-clap.md new file mode 100644 index 0000000000..6292730c4c --- /dev/null +++ b/.changeset/rude-snakes-clap.md @@ -0,0 +1,7 @@ +--- +'@backstage/plugin-user-settings-backend': patch +'@backstage/plugin-user-settings-common': patch +'@backstage/plugin-user-settings': patch +--- + +Use signals to update user settings across sessions diff --git a/plugins/user-settings-backend/README.md b/plugins/user-settings-backend/README.md index bbd69cb18e..a1fb504415 100644 --- a/plugins/user-settings-backend/README.md +++ b/plugins/user-settings-backend/README.md @@ -6,13 +6,26 @@ authorization token. ## Setup backend -1. Install the backend plugin: +Install the backend plugin ```bash # From your Backstage root directory yarn --cwd packages/backend add @backstage/plugin-user-settings-backend ``` +### New backend + +Add the plugin to your backend in `packages/backend/src/index.ts`: + +```ts +backend.add(import('@backstage/plugin-user-settings-backend/alpha')); +``` + +To get real-time updates of the user settings across different user sessions, you must also install +the `@backstage/plugin-signals-backend` plugin. + +### Old backend + 1. Configure the routes by adding a new `userSettings.ts` file in `packages/backend/src/plugins/`: @@ -29,7 +42,7 @@ export default async function createPlugin(env: PluginEnvironment) { } ``` -3. Add the new routes to your backend by modifying `packages/backend/src/index.ts`: +2. Add the new routes to your backend by modifying `packages/backend/src/index.ts`: ```diff // packages/backend/src/index.ts @@ -58,6 +71,7 @@ To make use of the user settings backend, replace the `WebStorage` with the + storageApiRef, } from '@backstage/core-plugin-api'; +import { UserSettingsStorage } from '@backstage/plugin-user-settings'; ++import { signalApiRef } from '@backstage/plugin-signals-react'; export const apis: AnyApiFactory[] = [ + createApiFactory({ @@ -66,7 +80,8 @@ To make use of the user settings backend, replace the `WebStorage` with the + discoveryApi: discoveryApiRef, + errorApi: errorApiRef, + fetchApi: fetchApiRef, -+ identityApi: identityApiRef ++ identityApi: identityApiRef, ++ signalApi: signalApiRef, // Optional + }, + factory: deps => UserSettingsStorage.create(deps), + }), diff --git a/plugins/user-settings-backend/api-report.md b/plugins/user-settings-backend/api-report.md index 1bc37bbec7..f8bf571fa7 100644 --- a/plugins/user-settings-backend/api-report.md +++ b/plugins/user-settings-backend/api-report.md @@ -6,6 +6,7 @@ import express from 'express'; import { IdentityApi } from '@backstage/plugin-auth-node'; import { PluginDatabaseManager } from '@backstage/backend-common'; +import { SignalsService } from '@backstage/plugin-signals-node'; // @public export function createRouter(options: RouterOptions): Promise; @@ -16,6 +17,8 @@ export interface RouterOptions { database: PluginDatabaseManager; // (undocumented) identity: IdentityApi; + // (undocumented) + signals?: SignalsService; } // (No @packageDocumentation comment for this package) diff --git a/plugins/user-settings-backend/package.json b/plugins/user-settings-backend/package.json index 38d6d0e04d..1d20d5fa23 100644 --- a/plugins/user-settings-backend/package.json +++ b/plugins/user-settings-backend/package.json @@ -51,6 +51,8 @@ "@backstage/config": "workspace:^", "@backstage/errors": "workspace:^", "@backstage/plugin-auth-node": "workspace:^", + "@backstage/plugin-signals-node": "workspace:^", + "@backstage/plugin-user-settings-common": "workspace:^", "@backstage/types": "workspace:^", "@types/express": "^4.17.6", "express": "^4.17.1", diff --git a/plugins/user-settings-backend/src/alpha.ts b/plugins/user-settings-backend/src/alpha.ts index a070396ca0..86180c8699 100644 --- a/plugins/user-settings-backend/src/alpha.ts +++ b/plugins/user-settings-backend/src/alpha.ts @@ -15,10 +15,11 @@ */ import { - createBackendPlugin, coreServices, + createBackendPlugin, } from '@backstage/backend-plugin-api'; import { createRouter } from './service/router'; +import { signalsServiceRef } from '@backstage/plugin-signals-node'; /** * The user settings backend plugin. @@ -33,9 +34,10 @@ export default createBackendPlugin({ database: coreServices.database, identity: coreServices.identity, httpRouter: coreServices.httpRouter, + signals: signalsServiceRef, }, - async init({ database, identity, httpRouter }) { - httpRouter.use(await createRouter({ database, identity })); + async init({ database, identity, httpRouter, signals }) { + httpRouter.use(await createRouter({ database, identity, signals })); }, }); }, diff --git a/plugins/user-settings-backend/src/service/router.test.ts b/plugins/user-settings-backend/src/service/router.test.ts index 64cdfcc465..27375c6333 100644 --- a/plugins/user-settings-backend/src/service/router.test.ts +++ b/plugins/user-settings-backend/src/service/router.test.ts @@ -22,6 +22,7 @@ import express from 'express'; import request from 'supertest'; import { UserSettingsStore } from '../database/UserSettingsStore'; import { createRouterInternal } from './router'; +import { SignalsService } from '@backstage/plugin-signals-node'; describe('createRouter', () => { const userSettingsStore: jest.Mocked = { @@ -36,6 +37,9 @@ describe('createRouter', () => { const identityApi: jest.Mocked> = { getIdentity: getIdentityMock, }; + const signalService: jest.Mocked = { + publish: jest.fn(), + }; let app: express.Express; @@ -43,6 +47,7 @@ describe('createRouter', () => { const router = await createRouterInternal({ userSettingsStore, identity: identityApi as IdentityApi, + signals: signalService as SignalsService, }); app = express().use(router); @@ -118,6 +123,11 @@ describe('createRouter', () => { bucket: 'my-bucket', key: 'my-key', }); + expect(signalService.publish).toHaveBeenCalledWith({ + recipients: { type: 'user', entityRef: 'user-1' }, + channel: `user-settings`, + message: { type: 'key-deleted', key: 'my-key' }, + }); }); it('returns an error if the Authorization header is missing', async () => { @@ -167,6 +177,11 @@ describe('createRouter', () => { bucket: 'my-bucket', key: 'my-key', }); + expect(signalService.publish).toHaveBeenCalledWith({ + recipients: { type: 'user', entityRef: 'user-1' }, + channel: `user-settings`, + message: { type: 'key-changed', key: 'my-key' }, + }); }); it('returns an error if the value is not given', async () => { diff --git a/plugins/user-settings-backend/src/service/router.ts b/plugins/user-settings-backend/src/service/router.ts index ebbb6716a3..b38e536354 100644 --- a/plugins/user-settings-backend/src/service/router.ts +++ b/plugins/user-settings-backend/src/service/router.ts @@ -21,6 +21,8 @@ import express, { Request } from 'express'; import Router from 'express-promise-router'; import { DatabaseUserSettingsStore } from '../database/DatabaseUserSettingsStore'; import { UserSettingsStore } from '../database/UserSettingsStore'; +import { SignalsService } from '@backstage/plugin-signals-node'; +import { UserSettingsSignal } from '@backstage/plugin-user-settings-common'; /** * @public @@ -28,6 +30,7 @@ import { UserSettingsStore } from '../database/UserSettingsStore'; export interface RouterOptions { database: PluginDatabaseManager; identity: IdentityApi; + signals?: SignalsService; } /** @@ -45,12 +48,14 @@ export async function createRouter( return await createRouterInternal({ userSettingsStore, identity: options.identity, + signals: options.signals, }); } export async function createRouterInternal(options: { identity: IdentityApi; userSettingsStore: UserSettingsStore; + signals?: SignalsService; }): Promise { const router = Router(); router.use(express.json()); @@ -104,6 +109,14 @@ export async function createRouterInternal(options: { key, }); + if (options.signals) { + await options.signals.publish({ + recipients: { type: 'user', entityRef: userEntityRef }, + channel: `user-settings`, + message: { type: 'key-changed', key }, + }); + } + res.json(setting); }); @@ -113,6 +126,13 @@ export async function createRouterInternal(options: { const { bucket, key } = req.params; await options.userSettingsStore.delete({ userEntityRef, bucket, key }); + if (options.signals) { + await options.signals.publish({ + recipients: { type: 'user', entityRef: userEntityRef }, + channel: 'user-settings', + message: { type: 'key-deleted', key }, + }); + } res.status(204).end(); }); diff --git a/plugins/user-settings-common/.eslintrc.js b/plugins/user-settings-common/.eslintrc.js new file mode 100644 index 0000000000..e2a53a6ad2 --- /dev/null +++ b/plugins/user-settings-common/.eslintrc.js @@ -0,0 +1 @@ +module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); diff --git a/plugins/user-settings-common/README.md b/plugins/user-settings-common/README.md new file mode 100644 index 0000000000..0b8039a5cd --- /dev/null +++ b/plugins/user-settings-common/README.md @@ -0,0 +1,5 @@ +# @backstage/plugin-user-settings-common + +Welcome to the common package for the user-settings plugin! + +_This plugin was created through the Backstage CLI_ diff --git a/plugins/user-settings-common/api-report.md b/plugins/user-settings-common/api-report.md new file mode 100644 index 0000000000..b69f4a4190 --- /dev/null +++ b/plugins/user-settings-common/api-report.md @@ -0,0 +1,13 @@ +## API Report File for "@backstage/plugin-user-settings-common" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts +// @public (undocumented) +export type UserSettingsSignal = { + type: 'key-changed' | 'key-deleted'; + key: string; +}; + +// (No @packageDocumentation comment for this package) +``` diff --git a/plugins/user-settings-common/catalog-info.yaml b/plugins/user-settings-common/catalog-info.yaml new file mode 100644 index 0000000000..ebbd0eec1e --- /dev/null +++ b/plugins/user-settings-common/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-user-settings-common + title: '@backstage/plugin-user-settings-common' + description: The Backstage common plugin to manage user settings +spec: + lifecycle: experimental + type: backstage-common-library + owner: maintainers diff --git a/plugins/user-settings-common/package.json b/plugins/user-settings-common/package.json new file mode 100644 index 0000000000..1575712a05 --- /dev/null +++ b/plugins/user-settings-common/package.json @@ -0,0 +1,37 @@ +{ + "name": "@backstage/plugin-user-settings-common", + "version": "0.0.0", + "description": "Common functionalities for the user-settings plugin", + "backstage": { + "role": "common-library" + }, + "publishConfig": { + "access": "public", + "main": "dist/index.cjs.js", + "module": "dist/index.esm.js", + "types": "dist/index.d.ts" + }, + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/user-settings-common" + }, + "license": "Apache-2.0", + "sideEffects": false, + "main": "src/index.ts", + "types": "src/index.ts", + "files": [ + "dist" + ], + "scripts": { + "build": "backstage-cli package build", + "clean": "backstage-cli package clean", + "lint": "backstage-cli package lint", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "test": "backstage-cli package test" + }, + "devDependencies": { + "@backstage/cli": "workspace:^" + } +} diff --git a/plugins/user-settings-common/src/index.ts b/plugins/user-settings-common/src/index.ts new file mode 100644 index 0000000000..5d542de408 --- /dev/null +++ b/plugins/user-settings-common/src/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export * from './types'; diff --git a/plugins/user-settings-common/src/setupTests.ts b/plugins/user-settings-common/src/setupTests.ts new file mode 100644 index 0000000000..c7ce5c0988 --- /dev/null +++ b/plugins/user-settings-common/src/setupTests.ts @@ -0,0 +1,16 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export {}; diff --git a/plugins/user-settings-common/src/types.ts b/plugins/user-settings-common/src/types.ts new file mode 100644 index 0000000000..6a49a72c93 --- /dev/null +++ b/plugins/user-settings-common/src/types.ts @@ -0,0 +1,21 @@ +/* + * Copyright 2024 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. + */ + +/** @public */ +export type UserSettingsSignal = { + type: 'key-changed' | 'key-deleted'; + key: string; +}; diff --git a/plugins/user-settings/api-report.md b/plugins/user-settings/api-report.md index d485320fe8..cee35724bb 100644 --- a/plugins/user-settings/api-report.md +++ b/plugins/user-settings/api-report.md @@ -22,6 +22,7 @@ import { PropsWithChildren } from 'react'; import { default as React_2 } from 'react'; import { RouteRef } from '@backstage/core-plugin-api'; import { SessionApi } from '@backstage/core-plugin-api'; +import { SignalApi } from '@backstage/plugin-signals-react'; import { StorageApi } from '@backstage/core-plugin-api'; import { StorageValueSnapshot } from '@backstage/core-plugin-api'; import { TabProps } from '@material-ui/core/Tab'; @@ -133,6 +134,7 @@ export class UserSettingsStorage implements StorageApi { discoveryApi: DiscoveryApi; errorApi: ErrorApi; identityApi: IdentityApi; + signalApi?: SignalApi; namespace?: string; }): UserSettingsStorage; // (undocumented) diff --git a/plugins/user-settings/package.json b/plugins/user-settings/package.json index 03617c88b1..3b44bf15b6 100644 --- a/plugins/user-settings/package.json +++ b/plugins/user-settings/package.json @@ -56,6 +56,8 @@ "@backstage/errors": "workspace:^", "@backstage/frontend-plugin-api": "workspace:^", "@backstage/plugin-catalog-react": "workspace:^", + "@backstage/plugin-signals-react": "workspace:^", + "@backstage/plugin-user-settings-common": "workspace:^", "@backstage/theme": "workspace:^", "@backstage/types": "workspace:^", "@material-ui/core": "^4.12.2", diff --git a/plugins/user-settings/src/apis/StorageApi/UserSettingsStorage.ts b/plugins/user-settings/src/apis/StorageApi/UserSettingsStorage.ts index ef4d3ead5d..6b109f5ecd 100644 --- a/plugins/user-settings/src/apis/StorageApi/UserSettingsStorage.ts +++ b/plugins/user-settings/src/apis/StorageApi/UserSettingsStorage.ts @@ -25,7 +25,9 @@ import { } from '@backstage/core-plugin-api'; import { ResponseError } from '@backstage/errors'; import { JsonValue, Observable } from '@backstage/types'; +import { SignalApi, SignalSubscriber } from '@backstage/plugin-signals-react'; import ObservableImpl from 'zen-observable'; +import { UserSettingsSignal } from '@backstage/plugin-user-settings-common'; const JSON_HEADERS = { 'Content-Type': 'application/json; charset=utf-8', @@ -57,6 +59,7 @@ export class UserSettingsStorage implements StorageApi { private readonly errorApi: ErrorApi, private readonly identityApi: IdentityApi, private readonly fallback: WebStorage, + private readonly signalApi?: SignalApi, ) {} static create(options: { @@ -64,6 +67,7 @@ export class UserSettingsStorage implements StorageApi { discoveryApi: DiscoveryApi; errorApi: ErrorApi; identityApi: IdentityApi; + signalApi?: SignalApi; namespace?: string; }): UserSettingsStorage { return new UserSettingsStorage( @@ -76,6 +80,7 @@ export class UserSettingsStorage implements StorageApi { namespace: options.namespace, errorApi: options.errorApi, }), + options.signalApi, ); } @@ -145,15 +150,33 @@ export class UserSettingsStorage implements StorageApi { this.observables.set( key, new ObservableImpl>(subscriber => { + let signalSubscription: SignalSubscriber | undefined; this.subscribers.add(subscriber); - // TODO(freben): Introduce server polling or similar, to ensure that different devices update when values change - Promise.resolve() - .then(() => this.get(key)) - .then(snapshot => subscriber.next(snapshot)) - .catch(error => this.errorApi.post(error)); + const updateSnapshot = () => { + Promise.resolve() + .then(() => this.get(key)) + .then(snapshot => subscriber.next(snapshot)) + .catch(error => this.errorApi.post(error)); + }; + + if (this.signalApi) { + signalSubscription = this.signalApi.subscribe( + `user-settings`, + (msg: UserSettingsSignal) => { + if (msg.key === key) { + updateSnapshot(); + } + }, + ); + } + + updateSnapshot(); return () => { + if (signalSubscription) { + signalSubscription.unsubscribe(); + } this.subscribers.delete(subscriber); }; }).filter(({ key: messageKey }) => messageKey === key), diff --git a/yarn.lock b/yarn.lock index 2ad2995d52..8c94dc8907 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7590,6 +7590,8 @@ __metadata: "@backstage/config": "workspace:^" "@backstage/errors": "workspace:^" "@backstage/plugin-auth-node": "workspace:^" + "@backstage/plugin-signals-node": "workspace:^" + "@backstage/plugin-user-settings-common": "workspace:^" "@backstage/types": "workspace:^" "@types/express": ^4.17.6 "@types/supertest": ^2.0.8 @@ -7602,6 +7604,14 @@ __metadata: languageName: unknown linkType: soft +"@backstage/plugin-user-settings-common@workspace:^, @backstage/plugin-user-settings-common@workspace:plugins/user-settings-common": + version: 0.0.0-use.local + resolution: "@backstage/plugin-user-settings-common@workspace:plugins/user-settings-common" + dependencies: + "@backstage/cli": "workspace:^" + languageName: unknown + linkType: soft + "@backstage/plugin-user-settings@workspace:^, @backstage/plugin-user-settings@workspace:plugins/user-settings": version: 0.0.0-use.local resolution: "@backstage/plugin-user-settings@workspace:plugins/user-settings" @@ -7616,6 +7626,8 @@ __metadata: "@backstage/frontend-plugin-api": "workspace:^" "@backstage/plugin-catalog": "workspace:^" "@backstage/plugin-catalog-react": "workspace:^" + "@backstage/plugin-signals-react": "workspace:^" + "@backstage/plugin-user-settings-common": "workspace:^" "@backstage/test-utils": "workspace:^" "@backstage/theme": "workspace:^" "@backstage/types": "workspace:^" From d57ebbca65fdfbd66b91bd3c8357d75ae1ecbee7 Mon Sep 17 00:00:00 2001 From: Julien Hery Date: Fri, 7 Jun 2024 10:55:34 +0200 Subject: [PATCH 63/69] feat(EntityPicker): use EntityPresentationApi to display entities in the picker (#24081) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit @Julien-Hery Thank you for your contribution! 🎉 --- .changeset/slow-dolls-type.md | 5 ++ .../fields/EntityPicker/EntityPicker.test.tsx | 26 +++++-- .../fields/EntityPicker/EntityPicker.tsx | 70 ++++++++++++++----- .../fields/OwnerPicker/OwnerPicker.test.tsx | 31 ++++++-- 4 files changed, 106 insertions(+), 26 deletions(-) create mode 100644 .changeset/slow-dolls-type.md diff --git a/.changeset/slow-dolls-type.md b/.changeset/slow-dolls-type.md new file mode 100644 index 0000000000..d2626f3ab3 --- /dev/null +++ b/.changeset/slow-dolls-type.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder': minor +--- + +Changed the way to display entities in EntityPicker to use entityPresentationApi instead of humanizeEntityRef diff --git a/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.test.tsx b/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.test.tsx index 8016a6ae83..dcb52a3949 100644 --- a/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.test.tsx +++ b/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.test.tsx @@ -16,14 +16,18 @@ import { CATALOG_FILTER_EXISTS } from '@backstage/catalog-client'; import { Entity } from '@backstage/catalog-model'; -import { CatalogApi, catalogApiRef } from '@backstage/plugin-catalog-react'; +import { + CatalogApi, + catalogApiRef, + entityPresentationApiRef, +} from '@backstage/plugin-catalog-react'; import { renderInTestApp, TestApiProvider } from '@backstage/test-utils'; - import { fireEvent, screen } from '@testing-library/react'; import React from 'react'; import { EntityPicker } from './EntityPicker'; import { EntityPickerProps } from './schema'; import { ScaffolderRJSFFieldProps as FieldProps } from '@backstage/plugin-scaffolder-react'; +import { DefaultEntityPresentationApi } from '@backstage/plugin-catalog'; const makeEntity = (kind: string, namespace: string, name: string): Entity => ({ apiVersion: 'scaffolder.backstage.io/v1beta3', @@ -50,6 +54,7 @@ describe('', () => { getLocationByRef: jest.fn(), removeEntityByUid: jest.fn(), } as any; + let Wrapper: React.ComponentType>; beforeEach(() => { @@ -59,7 +64,15 @@ describe('', () => { ]; Wrapper = ({ children }: { children?: React.ReactNode }) => ( - + {children} ); @@ -90,7 +103,12 @@ describe('', () => { ); expect(catalogApi.getEntities).toHaveBeenCalledWith({ - fields: ['metadata.name', 'metadata.namespace', 'kind'], + fields: [ + 'metadata.name', + 'metadata.namespace', + 'metadata.title', + 'kind', + ], filter: undefined, }); }); diff --git a/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.tsx b/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.tsx index 69e1b5efad..176f6e9ce0 100644 --- a/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.tsx +++ b/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.tsx @@ -24,13 +24,16 @@ import { } from '@backstage/catalog-model'; import { useApi } from '@backstage/core-plugin-api'; import { + EntityDisplayName, + EntityRefPresentationSnapshot, catalogApiRef, - humanizeEntityRef, + entityPresentationApiRef, } from '@backstage/plugin-catalog-react'; import TextField from '@material-ui/core/TextField'; import FormControl from '@material-ui/core/FormControl'; import Autocomplete, { AutocompleteChangeReason, + createFilterOptions, } from '@material-ui/lab/Autocomplete'; import React, { useCallback, useEffect } from 'react'; import useAsync from 'react-use/esm/useAsync'; @@ -65,31 +68,55 @@ export const EntityPicker = (props: EntityPickerProps) => { uiSchema['ui:options']?.defaultNamespace || undefined; const catalogApi = useApi(catalogApiRef); + const entityPresentationApi = useApi(entityPresentationApiRef); const { value: entities, loading } = useAsync(async () => { - const fields = ['metadata.name', 'metadata.namespace', 'kind']; + const fields = [ + 'metadata.name', + 'metadata.namespace', + 'metadata.title', + 'kind', + ]; const { items } = await catalogApi.getEntities( catalogFilter ? { filter: catalogFilter, fields } : { filter: undefined, fields }, ); - return items; + + const entityRefToPresentation = new Map< + string, + EntityRefPresentationSnapshot + >( + await Promise.all( + items.map(async item => { + const presentation = await entityPresentationApi.forEntity(item) + .promise; + return [stringifyEntityRef(item), presentation] as [ + string, + EntityRefPresentationSnapshot, + ]; + }), + ), + ); + + return { catalogEntities: items, entityRefToPresentation }; }); + const allowArbitraryValues = uiSchema['ui:options']?.allowArbitraryValues ?? true; const getLabel = useCallback( - (ref: string) => { + (freeSoloValue: string) => { try { - return humanizeEntityRef( - parseEntityRef(ref, { defaultKind, defaultNamespace }), - { - defaultKind, - defaultNamespace, - }, - ); + // Will throw if defaultKind or defaultNamespace are not set + const parsedRef = parseEntityRef(freeSoloValue, { + defaultKind, + defaultNamespace, + }); + + return stringifyEntityRef(parsedRef); } catch (err) { - return ref; + return freeSoloValue; } }, [defaultKind, defaultNamespace], @@ -129,12 +156,12 @@ export const EntityPicker = (props: EntityPickerProps) => { // Since free solo can be enabled, attempt to parse as a full entity ref first, then fall // back to the given value. const selectedEntity = - entities?.find(e => stringifyEntityRef(e) === formData) ?? + entities?.catalogEntities.find(e => stringifyEntityRef(e) === formData) ?? (allowArbitraryValues && formData ? getLabel(formData) : ''); useEffect(() => { - if (entities?.length === 1 && selectedEntity === '') { - onChange(stringifyEntityRef(entities[0])); + if (entities?.catalogEntities.length === 1 && selectedEntity === '') { + onChange(stringifyEntityRef(entities.catalogEntities[0])); } }, [entities, onChange, selectedEntity]); @@ -145,17 +172,18 @@ export const EntityPicker = (props: EntityPickerProps) => { error={rawErrors?.length > 0 && !formData} > // option can be a string due to freeSolo. typeof option === 'string' ? option - : humanizeEntityRef(option, { defaultKind, defaultNamespace })! + : entities?.entityRefToPresentation.get(stringifyEntityRef(option)) + ?.entityRef! } autoSelect freeSolo={allowArbitraryValues} @@ -171,6 +199,12 @@ export const EntityPicker = (props: EntityPickerProps) => { InputProps={params.InputProps} /> )} + renderOption={option => } + filterOptions={createFilterOptions({ + stringify: option => + entities?.entityRefToPresentation.get(stringifyEntityRef(option)) + ?.primaryTitle!, + })} /> ); diff --git a/plugins/scaffolder/src/components/fields/OwnerPicker/OwnerPicker.test.tsx b/plugins/scaffolder/src/components/fields/OwnerPicker/OwnerPicker.test.tsx index 57ca8e53ab..79105e05c3 100644 --- a/plugins/scaffolder/src/components/fields/OwnerPicker/OwnerPicker.test.tsx +++ b/plugins/scaffolder/src/components/fields/OwnerPicker/OwnerPicker.test.tsx @@ -16,11 +16,16 @@ import { type EntityFilterQuery } from '@backstage/catalog-client'; import { Entity } from '@backstage/catalog-model'; -import { CatalogApi, catalogApiRef } from '@backstage/plugin-catalog-react'; +import { + CatalogApi, + catalogApiRef, + entityPresentationApiRef, +} from '@backstage/plugin-catalog-react'; import { renderInTestApp, TestApiProvider } from '@backstage/test-utils'; import { ScaffolderRJSFFieldProps as FieldProps } from '@backstage/plugin-scaffolder-react'; import React from 'react'; import { OwnerPicker } from './OwnerPicker'; +import { DefaultEntityPresentationApi } from '@backstage/plugin-catalog'; const makeEntity = (kind: string, namespace: string, name: string): Entity => ({ apiVersion: 'backstage.io/v1beta1', @@ -64,7 +69,15 @@ describe('', () => { ]; Wrapper = ({ children }: { children?: React.ReactNode }) => ( - + {children} ); @@ -99,7 +112,12 @@ describe('', () => { filter: { kind: ['Group', 'User'], }, - fields: ['metadata.name', 'metadata.namespace', 'kind'], + fields: [ + 'metadata.name', + 'metadata.namespace', + 'metadata.title', + 'kind', + ], }), ); }); @@ -132,7 +150,12 @@ describe('', () => { filter: { kind: ['User'], }, - fields: ['metadata.name', 'metadata.namespace', 'kind'], + fields: [ + 'metadata.name', + 'metadata.namespace', + 'metadata.title', + 'kind', + ], }), ); }); From f4c848604f00887fb06bd6398dc65dd5dec09a84 Mon Sep 17 00:00:00 2001 From: Bogdan Nechyporenko Date: Mon, 10 Jun 2024 09:58:52 +0200 Subject: [PATCH 64/69] Increase max wait time in debug:wait action to 10 minutes (#25109) --- .changeset/twelve-pumpkins-fold.md | 5 +++++ .../src/scaffolder/actions/builtin/debug/wait.test.ts | 2 +- .../src/scaffolder/actions/builtin/debug/wait.ts | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) create mode 100644 .changeset/twelve-pumpkins-fold.md diff --git a/.changeset/twelve-pumpkins-fold.md b/.changeset/twelve-pumpkins-fold.md new file mode 100644 index 0000000000..ae63a8fce2 --- /dev/null +++ b/.changeset/twelve-pumpkins-fold.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend': patch +--- + +Increase max wait time in debug:wait action to 10 minutes diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/debug/wait.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/debug/wait.test.ts index 0dcbd10f0f..fc688eee44 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/debug/wait.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/debug/wait.test.ts @@ -57,7 +57,7 @@ describe('debug:wait', () => { await expect(async () => { await action.handler(context); }).rejects.toThrow( - 'Waiting duration is longer than the maximum threshold of 0 hours, 0 minutes, 30 seconds', + 'Waiting duration is longer than the maximum threshold of 0 hours, 10 minutes, 0 seconds', ); }); }); diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/debug/wait.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/debug/wait.ts index ae0af4db5a..fd4a77754b 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/debug/wait.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/debug/wait.ts @@ -21,7 +21,7 @@ import { examples } from './wait.examples'; const id = 'debug:wait'; -const MAX_WAIT_TIME_IN_ISO = 'T00:00:30'; +const MAX_WAIT_TIME_IN_ISO = 'T00:10:00'; /** * Waits for a certain period of time. From 8e96872989f665d16006f3c31e97049f5b3e2d24 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 10 Jun 2024 11:05:46 +0200 Subject: [PATCH 65/69] microsite: add redirects for cli docs Signed-off-by: Patrik Oldsberg --- microsite/docusaurus.config.ts | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/microsite/docusaurus.config.ts b/microsite/docusaurus.config.ts index 75a67406b6..9e2a70e74e 100644 --- a/microsite/docusaurus.config.ts +++ b/microsite/docusaurus.config.ts @@ -175,6 +175,26 @@ const config: Config = { from: '/docs/features/software-templates/authorizing-parameters-steps-and-actions', to: '/docs/features/software-templates/authorizing-scaffolder-template-details', }, + { + from: '/docs/local-dev/cli-commands/', + to: '/docs/tooling/cli/commands/', + }, + { + from: '/docs/local-dev/cli-build-system/', + to: '/docs/tooling/cli/build-system/', + }, + { + from: '/docs/local-dev/cli-overview/', + to: '/docs/tooling/cli/overview/', + }, + { + from: '/docs/local-dev/linking-local-packages/', + to: '/docs/tooling/local-dev/linking-local-packages', + }, + { + from: '/docs/local-dev/debugging/', + to: '/docs/tooling/local-dev/debugging', + }, ], }, ], From 8aab451602ba25306d66f236bf14b03a0556b7ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Mon, 10 Jun 2024 13:57:09 +0200 Subject: [PATCH 66/69] remove intermediate connector MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .changeset/popular-boxes-press.md | 5 +++ .../entrypoints/database/connectors/index.ts | 6 ++-- .../entrypoints/database/connectors/mysql.ts | 35 +++++------------- .../database/connectors/postgres.ts | 36 ++++++------------- .../database/connectors/sqlite3.ts | 26 +++----------- 5 files changed, 31 insertions(+), 77 deletions(-) create mode 100644 .changeset/popular-boxes-press.md diff --git a/.changeset/popular-boxes-press.md b/.changeset/popular-boxes-press.md new file mode 100644 index 0000000000..44b50d711a --- /dev/null +++ b/.changeset/popular-boxes-press.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-defaults': patch +--- + +Internal minor refactors of the database connectors diff --git a/packages/backend-defaults/src/entrypoints/database/connectors/index.ts b/packages/backend-defaults/src/entrypoints/database/connectors/index.ts index 9d18e7460a..117f727825 100644 --- a/packages/backend-defaults/src/entrypoints/database/connectors/index.ts +++ b/packages/backend-defaults/src/entrypoints/database/connectors/index.ts @@ -14,6 +14,6 @@ * limitations under the License. */ -export * from './mysql'; -export * from './postgres'; -export * from './sqlite3'; +export { MysqlConnector } from './mysql'; +export { PgConnector } from './postgres'; +export { Sqlite3Connector } from './sqlite3'; diff --git a/packages/backend-defaults/src/entrypoints/database/connectors/mysql.ts b/packages/backend-defaults/src/entrypoints/database/connectors/mysql.ts index 2fb3545047..45368627d9 100644 --- a/packages/backend-defaults/src/entrypoints/database/connectors/mysql.ts +++ b/packages/backend-defaults/src/entrypoints/database/connectors/mysql.ts @@ -25,7 +25,7 @@ import knexFactory, { Knex } from 'knex'; import { merge, omit } from 'lodash'; import limiterFactory from 'p-limit'; import yn from 'yn'; -import { Connector, DatabaseConnector } from '../types'; +import { Connector } from '../types'; import defaultNameOverride from './defaultNameOverride'; import { mergeDatabaseConfig } from './mergeDatabaseConfig'; @@ -226,19 +226,6 @@ export async function dropMysqlDatabase( } } -/** - * MySQL database connector. - * - * Exposes database connector functionality via an immutable object. - */ -export const mysqlConnector: DatabaseConnector = Object.freeze({ - createClient: createMysqlDatabaseClient, - ensureDatabaseExists: ensureMysqlDatabaseExists, - createNameOverride: defaultNameOverride, - parseConnectionString: parseMysqlConnectionString, - dropDatabase: dropMysqlDatabase, -}); - /** * Provides a config lookup path for a plugin's config block. */ @@ -248,14 +235,13 @@ function pluginPath(pluginId: string): string { function normalizeConnection( connection: Knex.StaticConnectionConfig | JsonObject | string | undefined, - client: string, ): Partial { if (typeof connection === 'undefined' || connection === null) { return {}; } return typeof connection === 'string' || connection instanceof String - ? mysqlConnector.parseConnectionString(connection as string, client) + ? parseMysqlConnectionString(connection as string) : connection; } @@ -264,7 +250,7 @@ function createNameOverride( name: string, ): Partial { try { - return mysqlConnector.createNameOverride(name); + return defaultNameOverride(name); } catch (e) { throw new InputError( `Unable to create database name override for '${client}' connector`, @@ -281,7 +267,7 @@ export class MysqlConnector implements Connector { async getClient( pluginId: string, - deps?: { + _deps?: { lifecycle: LifecycleService; pluginMetadata: PluginMetadataService; }, @@ -293,7 +279,7 @@ export class MysqlConnector implements Connector { const databaseName = this.getDatabaseName(pluginId); if (databaseName && this.getEnsureExistsConfig(pluginId)) { try { - await mysqlConnector.ensureDatabaseExists!(pluginConfig, databaseName); + await ensureMysqlDatabaseExists(pluginConfig, databaseName); } catch (error) { throw new Error( `Failed to connect to the database to make sure that '${databaseName}' exists, ${error}`, @@ -313,10 +299,9 @@ export class MysqlConnector implements Connector { this.getDatabaseOverrides(pluginId), ); - const client = mysqlConnector.createClient( + const client = createMysqlDatabaseClient( pluginConfig, databaseClientOverrides, - deps, ); return client; @@ -418,12 +403,9 @@ export class MysqlConnector implements Connector { * unless `pluginDivisionMode` is set to `schema`. */ private getConnectionConfig(pluginId: string): Knex.StaticConnectionConfig { - const { client, overridden } = this.getClientType(pluginId); + const { overridden } = this.getClientType(pluginId); - let baseConnection = normalizeConnection( - this.config.get('connection'), - this.config.getString('client'), - ); + let baseConnection = normalizeConnection(this.config.get('connection')); // Databases cannot be shared unless the `pluginDivisionMode` is set to `schema`. The // `database` property from the base connection is omitted unless `pluginDivisionMode` @@ -435,7 +417,6 @@ export class MysqlConnector implements Connector { // get and normalize optional plugin specific database connection const connection = normalizeConnection( this.config.getOptional(`${pluginPath(pluginId)}.connection`), - client, ); return { diff --git a/packages/backend-defaults/src/entrypoints/database/connectors/postgres.ts b/packages/backend-defaults/src/entrypoints/database/connectors/postgres.ts index b81bfd5b50..4464349b16 100644 --- a/packages/backend-defaults/src/entrypoints/database/connectors/postgres.ts +++ b/packages/backend-defaults/src/entrypoints/database/connectors/postgres.ts @@ -25,7 +25,7 @@ import knexFactory, { Knex } from 'knex'; import { merge, omit } from 'lodash'; import limiterFactory from 'p-limit'; import { Client } from 'pg'; -import { Connector, DatabaseConnector } from '../types'; +import { Connector } from '../types'; import defaultNameOverride from './defaultNameOverride'; import defaultSchemaOverride from './defaultSchemaOverride'; import { mergeDatabaseConfig } from './mergeDatabaseConfig'; @@ -232,16 +232,6 @@ export async function dropPgDatabase( } } -export const pgConnector: DatabaseConnector = Object.freeze({ - createClient: createPgDatabaseClient, - ensureDatabaseExists: ensurePgDatabaseExists, - ensureSchemaExists: ensurePgSchemaExists, - createNameOverride: defaultNameOverride, - createSchemaOverride: defaultSchemaOverride, - parseConnectionString: parsePgConnectionString, - dropDatabase: dropPgDatabase, -}); - /** * Provides a config lookup path for a plugin's config block. */ @@ -251,14 +241,13 @@ function pluginPath(pluginId: string): string { function normalizeConnection( connection: Knex.StaticConnectionConfig | JsonObject | string | undefined, - client: string, ): Partial { if (typeof connection === 'undefined' || connection === null) { return {}; } return typeof connection === 'string' || connection instanceof String - ? pgConnector.parseConnectionString(connection as string, client) + ? parsePgConnectionString(connection as string) : connection; } @@ -267,7 +256,7 @@ function createSchemaOverride( name: string, ): Partial { try { - return pgConnector.createSchemaOverride?.(name); + return defaultSchemaOverride(name); } catch (e) { throw new InputError( `Unable to create database schema override for '${client}' connector`, @@ -281,7 +270,7 @@ function createNameOverride( name: string, ): Partial { try { - return pgConnector.createNameOverride(name); + return defaultNameOverride(name); } catch (e) { throw new InputError( `Unable to create database name override for '${client}' connector`, @@ -298,7 +287,7 @@ export class PgConnector implements Connector { async getClient( pluginId: string, - deps?: { + _deps?: { lifecycle: LifecycleService; pluginMetadata: PluginMetadataService; }, @@ -310,7 +299,7 @@ export class PgConnector implements Connector { const databaseName = this.getDatabaseName(pluginId); if (databaseName && this.getEnsureExistsConfig(pluginId)) { try { - await pgConnector.ensureDatabaseExists!(pluginConfig, databaseName); + await ensurePgDatabaseExists(pluginConfig, databaseName); } catch (error) { throw new Error( `Failed to connect to the database to make sure that '${databaseName}' exists, ${error}`, @@ -326,7 +315,7 @@ export class PgConnector implements Connector { this.getEnsureExistsConfig(pluginId) ) { try { - await pgConnector.ensureSchemaExists!(pluginConfig, pluginId); + await ensurePgSchemaExists(pluginConfig, pluginId); } catch (error) { throw new Error( `Failed to connect to the database to make sure that schema for plugin '${pluginId}' exists, ${error}`, @@ -341,10 +330,9 @@ export class PgConnector implements Connector { schemaOverrides, ); - const client = pgConnector.createClient( + const client = createPgDatabaseClient( pluginConfig, databaseClientOverrides, - deps, ); return client; @@ -465,12 +453,9 @@ export class PgConnector implements Connector { * unless `pluginDivisionMode` is set to `schema`. */ private getConnectionConfig(pluginId: string): Knex.StaticConnectionConfig { - const { client, overridden } = this.getClientType(pluginId); + const { overridden } = this.getClientType(pluginId); - let baseConnection = normalizeConnection( - this.config.get('connection'), - this.config.getString('client'), - ); + let baseConnection = normalizeConnection(this.config.get('connection')); // Databases cannot be shared unless the `pluginDivisionMode` is set to `schema`. The // `database` property from the base connection is omitted unless `pluginDivisionMode` @@ -482,7 +467,6 @@ export class PgConnector implements Connector { // get and normalize optional plugin specific database connection const connection = normalizeConnection( this.config.getOptional(`${pluginPath(pluginId)}.connection`), - client, ); ( diff --git a/packages/backend-defaults/src/entrypoints/database/connectors/sqlite3.ts b/packages/backend-defaults/src/entrypoints/database/connectors/sqlite3.ts index 46ff756854..a66a8981d4 100644 --- a/packages/backend-defaults/src/entrypoints/database/connectors/sqlite3.ts +++ b/packages/backend-defaults/src/entrypoints/database/connectors/sqlite3.ts @@ -26,7 +26,7 @@ import { ensureDirSync } from 'fs-extra'; import knexFactory, { Knex } from 'knex'; import { merge, omit } from 'lodash'; import path from 'path'; -import { Connector, DatabaseConnector } from '../types'; +import { Connector } from '../types'; import { mergeDatabaseConfig } from './mergeDatabaseConfig'; /** @@ -157,17 +157,6 @@ export function parseSqliteConnectionString( }; } -/** - * SQLite3 database connector. - * - * Exposes database connector functionality via an immutable object. - */ -export const sqliteConnector: DatabaseConnector = Object.freeze({ - createClient: createSqliteDatabaseClient, - createNameOverride: createSqliteNameOverride, - parseConnectionString: parseSqliteConnectionString, -}); - /** * Provides a config lookup path for a plugin's config block. */ @@ -177,14 +166,13 @@ function pluginPath(pluginId: string): string { function normalizeConnection( connection: Knex.StaticConnectionConfig | JsonObject | string | undefined, - client: string, ): Partial { if (typeof connection === 'undefined' || connection === null) { return {}; } return typeof connection === 'string' || connection instanceof String - ? sqliteConnector.parseConnectionString(connection as string, client) + ? parseSqliteConnectionString(connection as string) : connection; } @@ -193,7 +181,7 @@ function createNameOverride( name: string, ): Partial { try { - return sqliteConnector.createNameOverride(name); + return createSqliteNameOverride(name); } catch (e) { throw new InputError( `Unable to create database name override for '${client}' connector`, @@ -228,7 +216,7 @@ export class Sqlite3Connector implements Connector { this.getDatabaseOverrides(pluginId), ); - const client = sqliteConnector.createClient( + const client = createSqliteDatabaseClient( pluginConfig, databaseClientOverrides, deps, @@ -340,10 +328,7 @@ export class Sqlite3Connector implements Connector { private getConnectionConfig(pluginId: string): Knex.StaticConnectionConfig { const { client, overridden } = this.getClientType(pluginId); - let baseConnection = normalizeConnection( - this.config.get('connection'), - this.config.getString('client'), - ); + let baseConnection = normalizeConnection(this.config.get('connection')); if ( client.includes('sqlite3') && @@ -366,7 +351,6 @@ export class Sqlite3Connector implements Connector { // get and normalize optional plugin specific database connection const connection = normalizeConnection( this.config.getOptional(`${pluginPath(pluginId)}.connection`), - client, ); return { From 128f89624e772edda88f98bc2e4b6fd857215587 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Mon, 10 Jun 2024 13:59:32 +0200 Subject: [PATCH 67/69] step one of flattening overrides MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .../entrypoints/database/connectors/mysql.ts | 18 +------ .../database/connectors/postgres.ts | 47 ++----------------- .../database/connectors/sqlite3.ts | 19 +------- 3 files changed, 5 insertions(+), 79 deletions(-) diff --git a/packages/backend-defaults/src/entrypoints/database/connectors/mysql.ts b/packages/backend-defaults/src/entrypoints/database/connectors/mysql.ts index 45368627d9..b11b659a6d 100644 --- a/packages/backend-defaults/src/entrypoints/database/connectors/mysql.ts +++ b/packages/backend-defaults/src/entrypoints/database/connectors/mysql.ts @@ -245,20 +245,6 @@ function normalizeConnection( : connection; } -function createNameOverride( - client: string, - name: string, -): Partial { - try { - return defaultNameOverride(name); - } catch (e) { - throw new InputError( - `Unable to create database name override for '${client}' connector`, - e, - ); - } -} - export class MysqlConnector implements Connector { constructor( private readonly config: Config, @@ -454,8 +440,6 @@ export class MysqlConnector implements Connector { */ private getDatabaseOverrides(pluginId: string): Knex.Config { const databaseName = this.getDatabaseName(pluginId); - return databaseName - ? createNameOverride(this.getClientType(pluginId).client, databaseName) - : {}; + return databaseName ? defaultNameOverride(databaseName) : {}; } } diff --git a/packages/backend-defaults/src/entrypoints/database/connectors/postgres.ts b/packages/backend-defaults/src/entrypoints/database/connectors/postgres.ts index 4464349b16..444bbc0d18 100644 --- a/packages/backend-defaults/src/entrypoints/database/connectors/postgres.ts +++ b/packages/backend-defaults/src/entrypoints/database/connectors/postgres.ts @@ -19,7 +19,7 @@ import { PluginMetadataService, } from '@backstage/backend-plugin-api'; import { Config, ConfigReader } from '@backstage/config'; -import { ForwardedError, InputError } from '@backstage/errors'; +import { ForwardedError } from '@backstage/errors'; import { JsonObject } from '@backstage/types'; import knexFactory, { Knex } from 'knex'; import { merge, omit } from 'lodash'; @@ -251,34 +251,6 @@ function normalizeConnection( : connection; } -function createSchemaOverride( - client: string, - name: string, -): Partial { - try { - return defaultSchemaOverride(name); - } catch (e) { - throw new InputError( - `Unable to create database schema override for '${client}' connector`, - e, - ); - } -} - -function createNameOverride( - client: string, - name: string, -): Partial { - try { - return defaultNameOverride(name); - } catch (e) { - throw new InputError( - `Unable to create database name override for '${client}' connector`, - e, - ); - } -} - export class PgConnector implements Connector { constructor( private readonly config: Config, @@ -309,7 +281,7 @@ export class PgConnector implements Connector { let schemaOverrides; if (this.getPluginDivisionModeConfig() === 'schema') { - schemaOverrides = this.getSchemaOverrides(pluginId); + schemaOverrides = defaultSchemaOverride(pluginId); if ( this.getEnsureSchemaExistsConfig(pluginId) || this.getEnsureExistsConfig(pluginId) @@ -500,17 +472,6 @@ export class PgConnector implements Connector { }; } - /** - * Provides a partial `Knex.Config` database schema override for a given - * plugin. - * - * @param pluginId - Target plugin to get database schema override - * @returns Partial `Knex.Config` with database schema override - */ - private getSchemaOverrides(pluginId: string): Knex.Config | undefined { - return createSchemaOverride(this.getClientType(pluginId).client, pluginId); - } - /** * Provides a partial `Knex.Config`• database name override for a given plugin. * @@ -519,8 +480,6 @@ export class PgConnector implements Connector { */ private getDatabaseOverrides(pluginId: string): Knex.Config { const databaseName = this.getDatabaseName(pluginId); - return databaseName - ? createNameOverride(this.getClientType(pluginId).client, databaseName) - : {}; + return databaseName ? defaultNameOverride(databaseName) : {}; } } diff --git a/packages/backend-defaults/src/entrypoints/database/connectors/sqlite3.ts b/packages/backend-defaults/src/entrypoints/database/connectors/sqlite3.ts index a66a8981d4..f7d55b34da 100644 --- a/packages/backend-defaults/src/entrypoints/database/connectors/sqlite3.ts +++ b/packages/backend-defaults/src/entrypoints/database/connectors/sqlite3.ts @@ -20,7 +20,6 @@ import { PluginMetadataService, } from '@backstage/backend-plugin-api'; import { Config, ConfigReader } from '@backstage/config'; -import { InputError } from '@backstage/errors'; import { JsonObject } from '@backstage/types'; import { ensureDirSync } from 'fs-extra'; import knexFactory, { Knex } from 'knex'; @@ -176,20 +175,6 @@ function normalizeConnection( : connection; } -function createNameOverride( - client: string, - name: string, -): Partial { - try { - return createSqliteNameOverride(name); - } catch (e) { - throw new InputError( - `Unable to create database name override for '${client}' connector`, - e, - ); - } -} - export class Sqlite3Connector implements Connector { constructor(private readonly config: Config) {} @@ -388,8 +373,6 @@ export class Sqlite3Connector implements Connector { */ private getDatabaseOverrides(pluginId: string): Knex.Config { const databaseName = this.getDatabaseName(pluginId); - return databaseName - ? createNameOverride(this.getClientType(pluginId).client, databaseName) - : {}; + return databaseName ? createSqliteNameOverride(databaseName) : {}; } } From 96091d32d0ae337bc6317f61764c44ee856f2465 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Pi=C4=85tkiewicz?= Date: Mon, 10 Jun 2024 15:04:54 +0200 Subject: [PATCH 68/69] fix missing actions after adding pagination to catalog table #25097 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Piotr Piątkiewicz --- .../src/components/CatalogTable/PaginatedCatalogTable.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plugins/catalog/src/components/CatalogTable/PaginatedCatalogTable.tsx b/plugins/catalog/src/components/CatalogTable/PaginatedCatalogTable.tsx index 38691b49d2..6692d5fbfe 100644 --- a/plugins/catalog/src/components/CatalogTable/PaginatedCatalogTable.tsx +++ b/plugins/catalog/src/components/CatalogTable/PaginatedCatalogTable.tsx @@ -29,7 +29,8 @@ type PaginatedCatalogTableProps = { * @internal */ export function PaginatedCatalogTable(props: PaginatedCatalogTableProps) { - const { columns, data, next, prev, title, isLoading, options } = props; + const { columns, data, next, prev, title, isLoading, options, ...restProps } = + props; return ( ); } From e04e57d1c2e5f9e9bf5638d004860daea7a246b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Pi=C4=85tkiewicz?= Date: Mon, 10 Jun 2024 15:09:10 +0200 Subject: [PATCH 69/69] added changeset after bugfix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Piotr Piątkiewicz --- .changeset/nice-kangaroos-occur.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/nice-kangaroos-occur.md diff --git a/.changeset/nice-kangaroos-occur.md b/.changeset/nice-kangaroos-occur.md new file mode 100644 index 0000000000..9798885d1a --- /dev/null +++ b/.changeset/nice-kangaroos-occur.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog': patch +--- + +Fix bug with missing Actions column after adding "pagination" prop to catalog table