From 9a41a7bfa8030340f9eeaa2c3e0c89e3924ca021 Mon Sep 17 00:00:00 2001 From: Heikki Hellgren Date: Fri, 1 Mar 2024 10:27:11 +0200 Subject: [PATCH 1/3] chore: migrate to new backend in local development additionally allow defining custom sidebar item for dev app page. Signed-off-by: Heikki Hellgren --- .changeset/light-eggs-switch.md | 5 + .changeset/rude-hats-bow.md | 8 + packages/dev-utils/api-report.md | 3 + packages/dev-utils/src/devApp/render.tsx | 40 +++-- plugins/notifications-backend/dev/index.ts | 64 ++++++++ plugins/notifications-backend/package.json | 5 + plugins/notifications-backend/src/run.ts | 32 ---- .../src/service/standaloneServer.ts | 150 ------------------ plugins/notifications/dev/index.tsx | 9 +- plugins/signals-backend/dev/index.ts | 59 +++++++ plugins/signals-backend/package.json | 2 + plugins/signals-backend/src/run.ts | 32 ---- .../src/service/standaloneServer.ts | 112 ------------- plugins/signals/dev/index.tsx | 31 ++-- yarn.lock | 7 + 15 files changed, 212 insertions(+), 347 deletions(-) create mode 100644 .changeset/light-eggs-switch.md create mode 100644 .changeset/rude-hats-bow.md create mode 100644 plugins/notifications-backend/dev/index.ts delete mode 100644 plugins/notifications-backend/src/run.ts delete mode 100644 plugins/notifications-backend/src/service/standaloneServer.ts create mode 100644 plugins/signals-backend/dev/index.ts delete mode 100644 plugins/signals-backend/src/run.ts delete mode 100644 plugins/signals-backend/src/service/standaloneServer.ts diff --git a/.changeset/light-eggs-switch.md b/.changeset/light-eggs-switch.md new file mode 100644 index 0000000000..3fbb77b0d8 --- /dev/null +++ b/.changeset/light-eggs-switch.md @@ -0,0 +1,5 @@ +--- +'@backstage/dev-utils': patch +--- + +Allow defining custom side bar item for page diff --git a/.changeset/rude-hats-bow.md b/.changeset/rude-hats-bow.md new file mode 100644 index 0000000000..0bd84617c0 --- /dev/null +++ b/.changeset/rude-hats-bow.md @@ -0,0 +1,8 @@ +--- +'@backstage/plugin-notifications-backend': patch +'@backstage/plugin-signals-backend': patch +'@backstage/plugin-notifications': patch +'@backstage/plugin-signals': patch +--- + +Migrate signals and notifications to the new backend in local development diff --git a/packages/dev-utils/api-report.md b/packages/dev-utils/api-report.md index 3f21ba5502..c44f7328b9 100644 --- a/packages/dev-utils/api-report.md +++ b/packages/dev-utils/api-report.md @@ -14,6 +14,7 @@ import { GridProps } from '@material-ui/core/Grid'; import { IconComponent } from '@backstage/core-plugin-api'; import { PropsWithChildren } from 'react'; import { ReactNode } from 'react'; +import { RouteRef } from '@backstage/core-plugin-api'; // @public export function createDevApp(): DevAppBuilder; @@ -42,6 +43,8 @@ export type DevAppPageOptions = { children?: JSX.Element; title?: string; icon?: IconComponent; + sideBarItem?: JSX.Element; + routeRef?: RouteRef; }; // @public (undocumented) diff --git a/packages/dev-utils/src/devApp/render.tsx b/packages/dev-utils/src/devApp/render.tsx index 79834cddc8..1b57a4b9b7 100644 --- a/packages/dev-utils/src/devApp/render.tsx +++ b/packages/dev-utils/src/devApp/render.tsx @@ -44,7 +44,7 @@ import { } from '@backstage/integration-react'; import Box from '@material-ui/core/Box'; import BookmarkIcon from '@material-ui/icons/Bookmark'; -import React, { ComponentType, ReactNode, PropsWithChildren } from 'react'; +import React, { ComponentType, PropsWithChildren, ReactNode } from 'react'; import { createRoutesFromChildren, Route } from 'react-router-dom'; import { SidebarThemeSwitcher } from './SidebarThemeSwitcher'; import 'react-dom'; @@ -80,6 +80,9 @@ export type DevAppPageOptions = { children?: JSX.Element; title?: string; icon?: IconComponent; + sideBarItem?: JSX.Element; + + routeRef?: RouteRef; }; /** @@ -141,7 +144,9 @@ export class DevAppBuilder { this.defaultPage = path; } - if (opts.title) { + if (opts.sideBarItem) { + this.sidebarItems.push(opts.sideBarItem); + } else if (opts.title) { this.sidebarItems.push( , ); } - this.routes.push( - , - ); + + if (opts.routeRef) { + const Elem = () => <>{opts.element}; + attachComponentData(Elem, 'core.mountPoint', opts.routeRef); + + this.routes.push( + } + children={opts.children} + />, + ); + } else { + this.routes.push( + , + ); + } return this; } diff --git a/plugins/notifications-backend/dev/index.ts b/plugins/notifications-backend/dev/index.ts new file mode 100644 index 0000000000..987f984dcb --- /dev/null +++ b/plugins/notifications-backend/dev/index.ts @@ -0,0 +1,64 @@ +/* + * 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. + */ + +import { createBackend } from '@backstage/backend-defaults'; +import { + coreServices, + createBackendPlugin, +} from '@backstage/backend-plugin-api'; +import { notificationService } from '@backstage/plugin-notifications-node'; + +const notificationsDebug = createBackendPlugin({ + pluginId: 'notifications-debug', + register(env) { + env.registerInit({ + deps: { + notifications: notificationService, + lifecycle: coreServices.lifecycle, + }, + async init({ notifications, lifecycle }) { + let interval: NodeJS.Timeout | undefined; + lifecycle.addStartupHook(async () => { + interval = setInterval(async () => { + await notifications.send({ + recipients: { + type: 'entity', + entityRef: 'user:development/guest', + }, + payload: { title: 'Test notification' }, + }); + }, 5000); + }); + + lifecycle.addShutdownHook(async () => { + if (interval) { + clearInterval(interval); + } + }); + }, + }); + }, +}); + +const backend = createBackend(); +backend.add(import('@backstage/plugin-events-backend/alpha')); +backend.add(import('@backstage/plugin-signals-backend')); +backend.add(import('@backstage/plugin-auth-backend')); +backend.add(import('@backstage/plugin-auth-backend-module-guest-provider')); +backend.add(import('../src')); +backend.add(notificationsDebug); + +backend.start(); diff --git a/plugins/notifications-backend/package.json b/plugins/notifications-backend/package.json index 15297856fb..cdb7642301 100644 --- a/plugins/notifications-backend/package.json +++ b/plugins/notifications-backend/package.json @@ -51,8 +51,13 @@ "yn": "^4.0.0" }, "devDependencies": { + "@backstage/backend-defaults": "workspace:^", "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", + "@backstage/plugin-auth-backend": "workspace:^", + "@backstage/plugin-auth-backend-module-guest-provider": "workspace:^", + "@backstage/plugin-events-backend": "workspace:^", + "@backstage/plugin-signals-backend": "workspace:^", "@types/express": "^4.17.6", "@types/supertest": "^2.0.8", "msw": "^1.0.0", diff --git a/plugins/notifications-backend/src/run.ts b/plugins/notifications-backend/src/run.ts deleted file mode 100644 index d299ed23e9..0000000000 --- a/plugins/notifications-backend/src/run.ts +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 2023 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { getRootLogger } from '@backstage/backend-common'; -import yn from 'yn'; -import { startStandaloneServer } from './service/standaloneServer'; - -const port = process.env.PLUGIN_PORT ? Number(process.env.PLUGIN_PORT) : 7007; -const enableCors = yn(process.env.PLUGIN_CORS, { default: false }); -const logger = getRootLogger(); - -startStandaloneServer({ port, enableCors, logger }).catch(err => { - logger.error(err); - process.exit(1); -}); - -process.on('SIGINT', () => { - logger.info('CTRL+C pressed; exiting.'); - process.exit(0); -}); diff --git a/plugins/notifications-backend/src/service/standaloneServer.ts b/plugins/notifications-backend/src/service/standaloneServer.ts deleted file mode 100644 index 5445d9b6b4..0000000000 --- a/plugins/notifications-backend/src/service/standaloneServer.ts +++ /dev/null @@ -1,150 +0,0 @@ -/* - * Copyright 2023 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { - createLegacyAuthAdapters, - createServiceBuilder, - HostDiscovery, - loadBackendConfig, - PluginDatabaseManager, - ServerTokenManager, -} from '@backstage/backend-common'; -import { Server } from 'http'; -import { Logger } from 'winston'; -import { createRouter } from './router'; -import Knex from 'knex'; -import { IdentityApi } from '@backstage/plugin-auth-node'; -import { Request } from 'express'; -import { - CatalogApi, - CatalogRequestOptions, - GetEntitiesByRefsRequest, -} from '@backstage/catalog-client'; -import { DefaultSignalsService } from '@backstage/plugin-signals-node'; -import { - EventParams, - EventsService, - EventsServiceSubscribeOptions, -} from '@backstage/plugin-events-node'; -import { - AuthService, - HttpAuthService, - UserInfoService, -} from '@backstage/backend-plugin-api'; - -export interface ServerOptions { - port: number; - enableCors: boolean; - logger: Logger; -} - -export async function startStandaloneServer( - options: ServerOptions, -): Promise { - const logger = options.logger.child({ service: 'notifications-backend' }); - logger.debug('Starting application server...'); - - const config = await loadBackendConfig({ logger, argv: process.argv }); - const db = Knex(config.get('backend.database')); - - const tokenManager = ServerTokenManager.fromConfig(config, { - logger, - }); - const discovery = HostDiscovery.fromConfig(config); - - const dbMock: PluginDatabaseManager = { - async getClient() { - return db; - }, - }; - - const catalogApi = { - async getEntitiesByRefs( - _request: GetEntitiesByRefsRequest, - __options?: CatalogRequestOptions, - ) { - return { - items: [ - { - apiVersion: 'backstage.io/v1alpha1', - kind: 'User', - metadata: { name: 'user', namespace: 'default' }, - spec: {}, - }, - ], - }; - }, - } as Partial as CatalogApi; - - const identityMock: IdentityApi = { - async getIdentity({ request }: { request: Request }) { - const token = request.headers.authorization?.split(' ')[1]; - return { - identity: { - type: 'user', - ownershipEntityRefs: [], - userEntityRef: 'user:default/guest', - }, - token: token || 'no-token', - }; - }, - }; - - const mockSubscribers: EventsServiceSubscribeOptions[] = []; - const events: EventsService = { - async publish(params: EventParams): Promise { - mockSubscribers.forEach(sub => sub.onEvent(params)); - }, - async subscribe(subscription: EventsServiceSubscribeOptions) { - mockSubscribers.push(subscription); - }, - }; - - const signalService = DefaultSignalsService.create({ events }); - // TODO: Move to use services instead this hack - const { auth, httpAuth, userInfo } = createLegacyAuthAdapters< - any, - { auth: AuthService; httpAuth: HttpAuthService; userInfo: UserInfoService } - >({ - identity: identityMock, - tokenManager, - discovery, - }); - - const router = await createRouter({ - logger, - database: dbMock, - catalog: catalogApi, - discovery, - signals: signalService, - auth, - httpAuth, - userInfo, - }); - - let service = createServiceBuilder(module) - .setPort(options.port) - .addRouter('/notifications', router); - if (options.enableCors) { - service = service.enableCors({ origin: 'http://localhost:3000' }); - } - - return await service.start().catch(err => { - logger.error(err); - process.exit(1); - }); -} - -module.hot?.accept(); diff --git a/plugins/notifications/dev/index.tsx b/plugins/notifications/dev/index.tsx index f4ea31f4ab..27bfe23842 100644 --- a/plugins/notifications/dev/index.tsx +++ b/plugins/notifications/dev/index.tsx @@ -16,12 +16,17 @@ import React from 'react'; import { createDevApp } from '@backstage/dev-utils'; import { notificationsPlugin } from '../src/plugin'; +import { NotificationsPage } from '../src/components/NotificationsPage'; +import { NotificationsSidebarItem } from '../src'; +import { rootRouteRef } from '../src/routes'; +// TODO: How to sign in here as guest user? createDevApp() .registerPlugin(notificationsPlugin) .addPage({ - element:
, - title: 'Root Page', + element: , path: '/notifications', + sideBarItem: , + routeRef: rootRouteRef, }) .render(); diff --git a/plugins/signals-backend/dev/index.ts b/plugins/signals-backend/dev/index.ts new file mode 100644 index 0000000000..ae98e54eed --- /dev/null +++ b/plugins/signals-backend/dev/index.ts @@ -0,0 +1,59 @@ +/* + * 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. + */ + +import { createBackend } from '@backstage/backend-defaults'; +import { + coreServices, + createBackendPlugin, +} from '@backstage/backend-plugin-api'; +import { signalService } from '@backstage/plugin-signals-node'; + +const signalDebug = createBackendPlugin({ + pluginId: 'signals-debug', + register(env) { + env.registerInit({ + deps: { + signals: signalService, + lifecycle: coreServices.lifecycle, + }, + async init({ signals, lifecycle }) { + let interval: NodeJS.Timeout | undefined; + lifecycle.addStartupHook(async () => { + interval = setInterval(async () => { + await signals.publish<{ date: string }>({ + channel: 'debug', + message: { date: new Date().toISOString() }, + recipients: { type: 'broadcast' }, + }); + }, 1000); + }); + + lifecycle.addShutdownHook(async () => { + if (interval) { + clearInterval(interval); + } + }); + }, + }); + }, +}); + +const backend = createBackend(); +backend.add(import('@backstage/plugin-events-backend/alpha')); +backend.add(import('../src')); +backend.add(signalDebug); + +backend.start(); diff --git a/plugins/signals-backend/package.json b/plugins/signals-backend/package.json index 24682829e8..923b4bb150 100644 --- a/plugins/signals-backend/package.json +++ b/plugins/signals-backend/package.json @@ -45,7 +45,9 @@ "yn": "^4.0.0" }, "devDependencies": { + "@backstage/backend-defaults": "workspace:^", "@backstage/cli": "workspace:^", + "@backstage/plugin-events-backend": "workspace:^", "@types/supertest": "^2.0.8", "msw": "^1.0.0", "supertest": "^6.2.4" diff --git a/plugins/signals-backend/src/run.ts b/plugins/signals-backend/src/run.ts deleted file mode 100644 index d299ed23e9..0000000000 --- a/plugins/signals-backend/src/run.ts +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 2023 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { getRootLogger } from '@backstage/backend-common'; -import yn from 'yn'; -import { startStandaloneServer } from './service/standaloneServer'; - -const port = process.env.PLUGIN_PORT ? Number(process.env.PLUGIN_PORT) : 7007; -const enableCors = yn(process.env.PLUGIN_CORS, { default: false }); -const logger = getRootLogger(); - -startStandaloneServer({ port, enableCors, logger }).catch(err => { - logger.error(err); - process.exit(1); -}); - -process.on('SIGINT', () => { - logger.info('CTRL+C pressed; exiting.'); - process.exit(0); -}); diff --git a/plugins/signals-backend/src/service/standaloneServer.ts b/plugins/signals-backend/src/service/standaloneServer.ts deleted file mode 100644 index efb7ecedee..0000000000 --- a/plugins/signals-backend/src/service/standaloneServer.ts +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright 2023 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { - createServiceBuilder, - HostDiscovery, - loadBackendConfig, -} from '@backstage/backend-common'; -import { Server } from 'http'; -import { Logger } from 'winston'; -import { createRouter } from './router'; -import { DefaultSignalsService } from '@backstage/plugin-signals-node'; -import { DefaultIdentityClient } from '@backstage/plugin-auth-node'; -import { - EventParams, - EventsService, - EventsServiceSubscribeOptions, -} from '@backstage/plugin-events-node'; -import { - BackstageCredentials, - BackstageUserInfo, - UserInfoService, -} from '@backstage/backend-plugin-api'; - -export interface ServerOptions { - port: number; - enableCors: boolean; - logger: Logger; -} - -export async function startStandaloneServer( - options: ServerOptions, -): Promise { - const logger = options.logger.child({ service: 'signals-backend' }); - logger.debug('Starting application server...'); - const config = await loadBackendConfig({ logger, argv: process.argv }); - const discovery = HostDiscovery.fromConfig(config); - - const identity = DefaultIdentityClient.create({ - discovery, - issuer: await discovery.getExternalBaseUrl('auth'), - }); - - const mockSubscribers: EventsServiceSubscribeOptions[] = []; - const events: EventsService = { - async publish(params: EventParams): Promise { - mockSubscribers.forEach(sub => sub.onEvent(params)); - }, - async subscribe(subscription: EventsServiceSubscribeOptions) { - mockSubscribers.push(subscription); - }, - }; - - const signals = DefaultSignalsService.create({ - events, - }); - - const userInfo: UserInfoService = { - async getUserInfo(_: BackstageCredentials): Promise { - return { - userEntityRef: 'user:default/guest', - ownershipEntityRefs: ['user:default/guest'], - }; - }, - }; - - const router = await createRouter({ - logger, - identity, - events, - discovery, - userInfo, - }); - - let service = createServiceBuilder(module) - .setPort(options.port) - .addRouter('/signals', router); - if (options.enableCors) { - service = service.enableCors({ origin: 'http://localhost:3000' }); - } - - let server: Promise; - try { - server = service.start(); - - setInterval(() => { - signals.publish({ - recipients: { type: 'broadcast' }, - channel: 'test', - message: { hello: 'world' }, - }); - }, 5000); - } catch (err) { - logger.error(err); - process.exit(1); - } - return server; -} - -module.hot?.accept(); diff --git a/plugins/signals/dev/index.tsx b/plugins/signals/dev/index.tsx index 2b22424eee..629c820a8e 100644 --- a/plugins/signals/dev/index.tsx +++ b/plugins/signals/dev/index.tsx @@ -16,20 +16,33 @@ import React from 'react'; import { createDevApp } from '@backstage/dev-utils'; import { signalsPlugin } from '../src/plugin'; -import { Content, Header, Page } from '@backstage/core-components'; +import { CodeSnippet, Content, Header, Page } from '@backstage/core-components'; import Typography from '@material-ui/core/Typography'; +import { useSignal } from '@backstage/plugin-signals-react'; + +const SignalsDebugPage = () => { + const { lastSignal } = useSignal('debug'); + return ( + +
+ + Last signal: + + {lastSignal ? ( + + ) : ( + 'Not received' + )} + + + + ); +}; createDevApp() .registerPlugin(signalsPlugin) .addPage({ title: 'Debug', - element: ( - -
- - TODO - - - ), + element: , }) .render(); diff --git a/yarn.lock b/yarn.lock index bff13e8dd1..e392282e15 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7899,6 +7899,7 @@ __metadata: resolution: "@backstage/plugin-notifications-backend@workspace:plugins/notifications-backend" dependencies: "@backstage/backend-common": "workspace:^" + "@backstage/backend-defaults": "workspace:^" "@backstage/backend-plugin-api": "workspace:^" "@backstage/backend-test-utils": "workspace:^" "@backstage/catalog-client": "workspace:^" @@ -7906,10 +7907,14 @@ __metadata: "@backstage/cli": "workspace:^" "@backstage/config": "workspace:^" "@backstage/errors": "workspace:^" + "@backstage/plugin-auth-backend": "workspace:^" + "@backstage/plugin-auth-backend-module-guest-provider": "workspace:^" "@backstage/plugin-auth-node": "workspace:^" + "@backstage/plugin-events-backend": "workspace:^" "@backstage/plugin-events-node": "workspace:^" "@backstage/plugin-notifications-common": "workspace:^" "@backstage/plugin-notifications-node": "workspace:^" + "@backstage/plugin-signals-backend": "workspace:^" "@backstage/plugin-signals-node": "workspace:^" "@types/express": ^4.17.6 "@types/supertest": ^2.0.8 @@ -9276,10 +9281,12 @@ __metadata: resolution: "@backstage/plugin-signals-backend@workspace:plugins/signals-backend" dependencies: "@backstage/backend-common": "workspace:^" + "@backstage/backend-defaults": "workspace:^" "@backstage/backend-plugin-api": "workspace:^" "@backstage/cli": "workspace:^" "@backstage/config": "workspace:^" "@backstage/plugin-auth-node": "workspace:^" + "@backstage/plugin-events-backend": "workspace:^" "@backstage/plugin-events-node": "workspace:^" "@backstage/plugin-signals-node": "workspace:^" "@backstage/types": "workspace:^" From 282f62f49001da10760771b64cde0cc80b200be3 Mon Sep 17 00:00:00 2001 From: Heikki Hellgren Date: Fri, 8 Mar 2024 08:11:56 +0200 Subject: [PATCH 2/3] chore: use code snippet for signal debugging Signed-off-by: Heikki Hellgren --- packages/dev-utils/api-report.md | 4 +- packages/dev-utils/src/devApp/render.tsx | 38 ++++++------------- plugins/notifications-backend/dev/index.ts | 3 +- .../src/service/router.ts | 6 ++- plugins/notifications/dev/index.tsx | 7 +--- 5 files changed, 19 insertions(+), 39 deletions(-) diff --git a/packages/dev-utils/api-report.md b/packages/dev-utils/api-report.md index c44f7328b9..d30df29200 100644 --- a/packages/dev-utils/api-report.md +++ b/packages/dev-utils/api-report.md @@ -14,7 +14,6 @@ import { GridProps } from '@material-ui/core/Grid'; import { IconComponent } from '@backstage/core-plugin-api'; import { PropsWithChildren } from 'react'; import { ReactNode } from 'react'; -import { RouteRef } from '@backstage/core-plugin-api'; // @public export function createDevApp(): DevAppBuilder; @@ -43,8 +42,7 @@ export type DevAppPageOptions = { children?: JSX.Element; title?: string; icon?: IconComponent; - sideBarItem?: JSX.Element; - routeRef?: RouteRef; + sidebarItem?: JSX.Element; }; // @public (undocumented) diff --git a/packages/dev-utils/src/devApp/render.tsx b/packages/dev-utils/src/devApp/render.tsx index 1b57a4b9b7..26fd96052b 100644 --- a/packages/dev-utils/src/devApp/render.tsx +++ b/packages/dev-utils/src/devApp/render.tsx @@ -80,9 +80,7 @@ export type DevAppPageOptions = { children?: JSX.Element; title?: string; icon?: IconComponent; - sideBarItem?: JSX.Element; - - routeRef?: RouteRef; + sidebarItem?: JSX.Element; }; /** @@ -144,8 +142,8 @@ export class DevAppBuilder { this.defaultPage = path; } - if (opts.sideBarItem) { - this.sidebarItems.push(opts.sideBarItem); + if (opts.sidebarItem) { + this.sidebarItems.push(opts.sidebarItem); } else if (opts.title) { this.sidebarItems.push( <>{opts.element}; - attachComponentData(Elem, 'core.mountPoint', opts.routeRef); - - this.routes.push( - } - children={opts.children} - />, - ); - } else { - this.routes.push( - , - ); - } + this.routes.push( + , + ); return this; } diff --git a/plugins/notifications-backend/dev/index.ts b/plugins/notifications-backend/dev/index.ts index 987f984dcb..fe35606dd2 100644 --- a/plugins/notifications-backend/dev/index.ts +++ b/plugins/notifications-backend/dev/index.ts @@ -35,8 +35,7 @@ const notificationsDebug = createBackendPlugin({ interval = setInterval(async () => { await notifications.send({ recipients: { - type: 'entity', - entityRef: 'user:development/guest', + type: 'broadcast', }, payload: { title: 'Test notification' }, }); diff --git a/plugins/notifications-backend/src/service/router.ts b/plugins/notifications-backend/src/service/router.ts index 0bce3d4bb3..4c38d2ba2a 100644 --- a/plugins/notifications-backend/src/service/router.ts +++ b/plugins/notifications-backend/src/service/router.ts @@ -470,7 +470,7 @@ export async function createRouter( if (!recipients || !title) { logger.error(`Invalid notification request received`); - throw new InputError(); + throw new InputError(`Invalid notification request received`); } const origin = credentials.principal.subject; @@ -496,8 +496,10 @@ export async function createRouter( try { users = await getUsersForEntityRef(entityRef); } catch (e) { - throw new InputError(); + logger.error(`Failed to resolve notification receivers: ${e}`); + throw new InputError('Failed to resolve notification receivers', e); } + const userNotifications = await sendUserNotifications( baseNotification, users, diff --git a/plugins/notifications/dev/index.tsx b/plugins/notifications/dev/index.tsx index 27bfe23842..1406f55b59 100644 --- a/plugins/notifications/dev/index.tsx +++ b/plugins/notifications/dev/index.tsx @@ -15,10 +15,8 @@ */ import React from 'react'; import { createDevApp } from '@backstage/dev-utils'; -import { notificationsPlugin } from '../src/plugin'; -import { NotificationsPage } from '../src/components/NotificationsPage'; +import { NotificationsPage, notificationsPlugin } from '../src/plugin'; import { NotificationsSidebarItem } from '../src'; -import { rootRouteRef } from '../src/routes'; // TODO: How to sign in here as guest user? createDevApp() @@ -26,7 +24,6 @@ createDevApp() .addPage({ element: , path: '/notifications', - sideBarItem: , - routeRef: rootRouteRef, + sidebarItem: , }) .render(); From f28242025e29066478364b511ade883fedc31d5d Mon Sep 17 00:00:00 2001 From: Heikki Hellgren Date: Thu, 21 Mar 2024 07:41:02 +0100 Subject: [PATCH 3/3] feat: support login for the development app this fixes the local development issue with new backend system where the user is not logged in thus resulting 401 errors from all api endpoints. adds possibility to also add other login providers for testing plugins independently. Signed-off-by: Heikki Hellgren --- .changeset/light-eggs-switch.md | 2 +- packages/dev-utils/api-report.md | 11 ++++- .../SidebarSignOutButton.tsx | 44 +++++++++++++++++++ .../components/SidebarSignOutButton/index.ts | 16 +++++++ packages/dev-utils/src/components/index.ts | 1 + packages/dev-utils/src/devApp/render.test.tsx | 3 +- packages/dev-utils/src/devApp/render.tsx | 40 +++++++++++++++-- plugins/notifications/dev/index.tsx | 3 +- plugins/signals-backend/dev/index.ts | 2 + plugins/signals-backend/package.json | 2 + yarn.lock | 2 + 11 files changed, 117 insertions(+), 9 deletions(-) create mode 100644 packages/dev-utils/src/components/SidebarSignOutButton/SidebarSignOutButton.tsx create mode 100644 packages/dev-utils/src/components/SidebarSignOutButton/index.ts diff --git a/.changeset/light-eggs-switch.md b/.changeset/light-eggs-switch.md index 3fbb77b0d8..26934f155e 100644 --- a/.changeset/light-eggs-switch.md +++ b/.changeset/light-eggs-switch.md @@ -2,4 +2,4 @@ '@backstage/dev-utils': patch --- -Allow defining custom side bar item for page +Allow defining custom sidebar item for page and login for the development app diff --git a/packages/dev-utils/api-report.md b/packages/dev-utils/api-report.md index d30df29200..0a5b3d10e8 100644 --- a/packages/dev-utils/api-report.md +++ b/packages/dev-utils/api-report.md @@ -13,7 +13,9 @@ import { Entity } from '@backstage/catalog-model'; import { GridProps } from '@material-ui/core/Grid'; import { IconComponent } from '@backstage/core-plugin-api'; import { PropsWithChildren } from 'react'; +import { default as React_2 } from 'react'; import { ReactNode } from 'react'; +import { SignInProviderConfig } from '@backstage/core-components'; // @public export function createDevApp(): DevAppBuilder; @@ -22,6 +24,8 @@ export function createDevApp(): DevAppBuilder; export class DevAppBuilder { addPage(opts: DevAppPageOptions): DevAppBuilder; addRootChild(node: ReactNode): DevAppBuilder; + addSidebarItem(sidebarItem: JSX.Element): DevAppBuilder; + addSignInProvider(provider: SignInProviderConfig): this; addThemes(themes: AppTheme[]): this; build(): ComponentType>; registerApi< @@ -42,7 +46,6 @@ export type DevAppPageOptions = { children?: JSX.Element; title?: string; icon?: IconComponent; - sidebarItem?: JSX.Element; }; // @public (undocumented) @@ -51,4 +54,10 @@ export const EntityGridItem: ( entity: Entity; }, ) => JSX.Element; + +// @public +export const SidebarSignOutButton: (props: { + icon?: IconComponent; + text?: string; +}) => React_2.JSX.Element; ``` diff --git a/packages/dev-utils/src/components/SidebarSignOutButton/SidebarSignOutButton.tsx b/packages/dev-utils/src/components/SidebarSignOutButton/SidebarSignOutButton.tsx new file mode 100644 index 0000000000..9cfe888d4d --- /dev/null +++ b/packages/dev-utils/src/components/SidebarSignOutButton/SidebarSignOutButton.tsx @@ -0,0 +1,44 @@ +/* + * 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. + */ + +import React from 'react'; +import { SidebarItem } from '@backstage/core-components'; +import LockIcon from '@material-ui/icons/Lock'; +import { + IconComponent, + identityApiRef, + useApi, +} from '@backstage/core-plugin-api'; + +/** + * Button for sidebar that signs out user + * + * @public + */ +export const SidebarSignOutButton = (props: { + icon?: IconComponent; + text?: string; +}) => { + const identityApi = useApi(identityApiRef); + + return ( + identityApi.signOut()} + icon={props.icon ?? LockIcon} + text={props.text ?? 'Sign Out'} + /> + ); +}; diff --git a/packages/dev-utils/src/components/SidebarSignOutButton/index.ts b/packages/dev-utils/src/components/SidebarSignOutButton/index.ts new file mode 100644 index 0000000000..6182e1fa12 --- /dev/null +++ b/packages/dev-utils/src/components/SidebarSignOutButton/index.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 * from './SidebarSignOutButton'; diff --git a/packages/dev-utils/src/components/index.ts b/packages/dev-utils/src/components/index.ts index d71b424a34..8959b6e7b1 100644 --- a/packages/dev-utils/src/components/index.ts +++ b/packages/dev-utils/src/components/index.ts @@ -15,3 +15,4 @@ */ export * from './EntityGridItem'; +export * from './SidebarSignOutButton'; diff --git a/packages/dev-utils/src/devApp/render.test.tsx b/packages/dev-utils/src/devApp/render.test.tsx index 2da6ef370a..7fc0024cc3 100644 --- a/packages/dev-utils/src/devApp/render.test.tsx +++ b/packages/dev-utils/src/devApp/render.test.tsx @@ -17,7 +17,7 @@ import React from 'react'; import { render } from '@testing-library/react'; import { createDevApp } from './render'; -import { useApi, configApiRef } from '@backstage/core-plugin-api'; +import { configApiRef, useApi } from '@backstage/core-plugin-api'; const anyEnv = (process.env = { ...process.env }) as any; @@ -28,6 +28,7 @@ describe('DevAppBuilder', () => { context: 'test', data: { app: { title: 'Test App' }, + backend: { baseUrl: 'http://localhost' }, }, }, ]; diff --git a/packages/dev-utils/src/devApp/render.tsx b/packages/dev-utils/src/devApp/render.tsx index 26fd96052b..3b3c993a6b 100644 --- a/packages/dev-utils/src/devApp/render.tsx +++ b/packages/dev-utils/src/devApp/render.tsx @@ -25,6 +25,8 @@ import { SidebarPage, SidebarSpace, SidebarSpacer, + SignInPage, + SignInProviderConfig, } from '@backstage/core-components'; import { AnyApiFactory, @@ -48,6 +50,7 @@ import React, { ComponentType, PropsWithChildren, ReactNode } from 'react'; import { createRoutesFromChildren, Route } from 'react-router-dom'; import { SidebarThemeSwitcher } from './SidebarThemeSwitcher'; import 'react-dom'; +import { SidebarSignOutButton } from '../components'; let ReactDOMPromise: Promise< typeof import('react-dom') | typeof import('react-dom/client') @@ -80,7 +83,6 @@ export type DevAppPageOptions = { children?: JSX.Element; title?: string; icon?: IconComponent; - sidebarItem?: JSX.Element; }; /** @@ -95,6 +97,7 @@ export class DevAppBuilder { private readonly rootChildren = new Array(); private readonly routes = new Array(); private readonly sidebarItems = new Array(); + private readonly signInProviders = new Array(); private defaultPage?: string; private themes?: Array; @@ -129,6 +132,16 @@ export class DevAppBuilder { return this; } + /** + * Adds a new sidebar item to the dev app. + * + * Useful for adding only sidebar items without a corresponding page. + */ + addSidebarItem(sidebarItem: JSX.Element): DevAppBuilder { + this.sidebarItems.push(sidebarItem); + return this; + } + /** * Adds a page component along with accompanying sidebar item. * @@ -142,9 +155,7 @@ export class DevAppBuilder { this.defaultPage = path; } - if (opts.sidebarItem) { - this.sidebarItems.push(opts.sidebarItem); - } else if (opts.title) { + if (opts.title) { this.sidebarItems.push( { + return ( + + ); + }, + }, bindRoutes: ({ bind }) => { for (const plugin of this.plugins ?? []) { const targets: Record> = {}; @@ -221,6 +252,7 @@ export class DevAppBuilder { + {this.routes} diff --git a/plugins/notifications/dev/index.tsx b/plugins/notifications/dev/index.tsx index 1406f55b59..f4d9176a70 100644 --- a/plugins/notifications/dev/index.tsx +++ b/plugins/notifications/dev/index.tsx @@ -18,12 +18,11 @@ import { createDevApp } from '@backstage/dev-utils'; import { NotificationsPage, notificationsPlugin } from '../src/plugin'; import { NotificationsSidebarItem } from '../src'; -// TODO: How to sign in here as guest user? createDevApp() .registerPlugin(notificationsPlugin) .addPage({ element: , path: '/notifications', - sidebarItem: , }) + .addSidebarItem() .render(); diff --git a/plugins/signals-backend/dev/index.ts b/plugins/signals-backend/dev/index.ts index ae98e54eed..39021a9fcd 100644 --- a/plugins/signals-backend/dev/index.ts +++ b/plugins/signals-backend/dev/index.ts @@ -53,6 +53,8 @@ const signalDebug = createBackendPlugin({ const backend = createBackend(); backend.add(import('@backstage/plugin-events-backend/alpha')); +backend.add(import('@backstage/plugin-auth-backend')); +backend.add(import('@backstage/plugin-auth-backend-module-guest-provider')); backend.add(import('../src')); backend.add(signalDebug); diff --git a/plugins/signals-backend/package.json b/plugins/signals-backend/package.json index 923b4bb150..a6ebb228e4 100644 --- a/plugins/signals-backend/package.json +++ b/plugins/signals-backend/package.json @@ -47,6 +47,8 @@ "devDependencies": { "@backstage/backend-defaults": "workspace:^", "@backstage/cli": "workspace:^", + "@backstage/plugin-auth-backend": "workspace:^", + "@backstage/plugin-auth-backend-module-guest-provider": "workspace:^", "@backstage/plugin-events-backend": "workspace:^", "@types/supertest": "^2.0.8", "msw": "^1.0.0", diff --git a/yarn.lock b/yarn.lock index e392282e15..893930d505 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9285,6 +9285,8 @@ __metadata: "@backstage/backend-plugin-api": "workspace:^" "@backstage/cli": "workspace:^" "@backstage/config": "workspace:^" + "@backstage/plugin-auth-backend": "workspace:^" + "@backstage/plugin-auth-backend-module-guest-provider": "workspace:^" "@backstage/plugin-auth-node": "workspace:^" "@backstage/plugin-events-backend": "workspace:^" "@backstage/plugin-events-node": "workspace:^"