diff --git a/.changeset/light-eggs-switch.md b/.changeset/light-eggs-switch.md new file mode 100644 index 0000000000..26934f155e --- /dev/null +++ b/.changeset/light-eggs-switch.md @@ -0,0 +1,5 @@ +--- +'@backstage/dev-utils': patch +--- + +Allow defining custom sidebar item for page and login for the development app 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..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< @@ -50,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 79834cddc8..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, @@ -44,10 +46,11 @@ 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'; +import { SidebarSignOutButton } from '../components'; let ReactDOMPromise: Promise< typeof import('react-dom') | typeof import('react-dom/client') @@ -94,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; @@ -128,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. * @@ -151,6 +165,7 @@ export class DevAppBuilder { />, ); } + this.routes.push( { + return ( + + ); + }, + }, bindRoutes: ({ bind }) => { for (const plugin of this.plugins ?? []) { const targets: Record> = {}; @@ -217,6 +252,7 @@ export class DevAppBuilder { + {this.routes} diff --git a/plugins/notifications-backend/dev/index.ts b/plugins/notifications-backend/dev/index.ts new file mode 100644 index 0000000000..fe35606dd2 --- /dev/null +++ b/plugins/notifications-backend/dev/index.ts @@ -0,0 +1,63 @@ +/* + * 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: 'broadcast', + }, + 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/router.ts b/plugins/notifications-backend/src/service/router.ts index dba32addfe..68fbe0bb27 100644 --- a/plugins/notifications-backend/src/service/router.ts +++ b/plugins/notifications-backend/src/service/router.ts @@ -458,7 +458,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; @@ -484,8 +484,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-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..f4d9176a70 100644 --- a/plugins/notifications/dev/index.tsx +++ b/plugins/notifications/dev/index.tsx @@ -15,13 +15,14 @@ */ import React from 'react'; import { createDevApp } from '@backstage/dev-utils'; -import { notificationsPlugin } from '../src/plugin'; +import { NotificationsPage, notificationsPlugin } from '../src/plugin'; +import { NotificationsSidebarItem } from '../src'; createDevApp() .registerPlugin(notificationsPlugin) .addPage({ - element:
, - title: 'Root Page', + element: , path: '/notifications', }) + .addSidebarItem() .render(); diff --git a/plugins/signals-backend/dev/index.ts b/plugins/signals-backend/dev/index.ts new file mode 100644 index 0000000000..39021a9fcd --- /dev/null +++ b/plugins/signals-backend/dev/index.ts @@ -0,0 +1,61 @@ +/* + * 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('@backstage/plugin-auth-backend')); +backend.add(import('@backstage/plugin-auth-backend-module-guest-provider')); +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..a6ebb228e4 100644 --- a/plugins/signals-backend/package.json +++ b/plugins/signals-backend/package.json @@ -45,7 +45,11 @@ "yn": "^4.0.0" }, "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", "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 4d6d2a36c8..e298638f16 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7900,6 +7900,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:^" @@ -7907,10 +7908,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 @@ -9277,10 +9282,14 @@ __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-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-signals-node": "workspace:^" "@backstage/types": "workspace:^"