From 9152ba8f364f97105aa1de12a155f48f21c9fb27 Mon Sep 17 00:00:00 2001 From: Frank Ye Date: Wed, 21 May 2025 11:28:08 -0400 Subject: [PATCH 1/9] add generic attribute field to NotificationPayload Signed-off-by: Frank Ye --- .changeset/wise-mails-agree.md | 6 ++++ .../migrations/20250521_addAttributes.js | 33 +++++++++++++++++++ plugins/notifications-backend/report.sql.md | 2 ++ .../DatabaseNotificationsStore.test.ts | 6 ++++ .../database/DatabaseNotificationsStore.ts | 10 +++++- .../src/service/router.test.ts | 12 +++++++ plugins/notifications-common/report.api.md | 11 +++++-- plugins/notifications-common/src/types.ts | 14 ++++++-- 8 files changed, 87 insertions(+), 7 deletions(-) create mode 100644 .changeset/wise-mails-agree.md create mode 100644 plugins/notifications-backend/migrations/20250521_addAttributes.js diff --git a/.changeset/wise-mails-agree.md b/.changeset/wise-mails-agree.md new file mode 100644 index 0000000000..d6e96a3f20 --- /dev/null +++ b/.changeset/wise-mails-agree.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-notifications-backend': minor +'@backstage/plugin-notifications-common': minor +--- + +Add an optional generic object `attributes` field to `NotificationPayload`. Attributes can be used to store additional unstructured data for the notification, and are stored in the database. diff --git a/plugins/notifications-backend/migrations/20250521_addAttributes.js b/plugins/notifications-backend/migrations/20250521_addAttributes.js new file mode 100644 index 0000000000..e7519d2e29 --- /dev/null +++ b/plugins/notifications-backend/migrations/20250521_addAttributes.js @@ -0,0 +1,33 @@ +/* + * 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. + */ + +exports.up = async function up(knex) { + await knex.schema.alterTable('notification', table => { + table.text('attributes').nullable(); + }); + await knex.schema.alterTable('broadcast', table => { + table.text('attributes').nullable(); + }); +}; + +exports.down = async function down(knex) { + await knex.schema.alterTable('notification', table => { + table.dropColumn('attributes'); + }); + await knex.schema.alterTable('broadcast', table => { + table.dropColumn('attributes'); + }); +}; diff --git a/plugins/notifications-backend/report.sql.md b/plugins/notifications-backend/report.sql.md index aacb787a61..bb9612b357 100644 --- a/plugins/notifications-backend/report.sql.md +++ b/plugins/notifications-backend/report.sql.md @@ -6,6 +6,7 @@ | Column | Type | Nullable | Max Length | Default | | ------------- | -------------------------- | -------- | ---------- | ------------------- | +| `attributes` | `text` | true | - | - | | `created` | `timestamp with time zone` | false | - | `CURRENT_TIMESTAMP` | | `description` | `text` | true | - | - | | `icon` | `character varying` | true | 255 | - | @@ -40,6 +41,7 @@ | Column | Type | Nullable | Max Length | Default | | ------------- | -------------------------- | -------- | ---------- | ------------------- | +| `attributes` | `text` | true | - | - | | `created` | `timestamp with time zone` | false | - | `CURRENT_TIMESTAMP` | | `description` | `text` | true | - | - | | `icon` | `character varying` | true | 255 | - | diff --git a/plugins/notifications-backend/src/database/DatabaseNotificationsStore.test.ts b/plugins/notifications-backend/src/database/DatabaseNotificationsStore.test.ts index d8ae3faf57..03c4428289 100644 --- a/plugins/notifications-backend/src/database/DatabaseNotificationsStore.test.ts +++ b/plugins/notifications-backend/src/database/DatabaseNotificationsStore.test.ts @@ -69,6 +69,9 @@ const testNotification1: Notification = { link: '/catalog', severity: 'critical', icon: 'docs', + attributes: { + attribute1: 'attributeValue', + }, }, }; const testNotification2: Notification = { @@ -209,6 +212,9 @@ describe.each(databases.eachSupportedId())( expect(notification?.payload?.link).toBe('/catalog'); expect(notification?.payload?.severity).toBe('critical'); expect(notification?.payload?.icon).toBe('docs'); + expect(notification?.payload?.attributes).toEqual({ + attribute1: 'attributeValue', + }); }); }); diff --git a/plugins/notifications-backend/src/database/DatabaseNotificationsStore.ts b/plugins/notifications-backend/src/database/DatabaseNotificationsStore.ts index 94131ca257..5f68aa0e39 100644 --- a/plugins/notifications-backend/src/database/DatabaseNotificationsStore.ts +++ b/plugins/notifications-backend/src/database/DatabaseNotificationsStore.ts @@ -53,6 +53,7 @@ const NOTIFICATION_COLUMNS = [ 'user', 'read', 'saved', + 'attributes', ]; type NotificationRowType = { @@ -164,6 +165,7 @@ export class DatabaseNotificationsStore implements NotificationsStore { severity: row.severity, scope: row.scope, icon: row.icon, + attributes: row.attributes ? JSON.parse(row.attributes) : undefined, }, })); }; @@ -224,9 +226,12 @@ export class DatabaseNotificationsStore implements NotificationsStore { description: notification.payload?.description, severity: normalizeSeverity(notification.payload?.severity), scope: notification.payload?.scope, - icon: notification.payload.icon, + icon: notification.payload?.icon, saved: notification.saved, read: notification.read, + attributes: notification.payload?.attributes + ? JSON.stringify(notification.payload.attributes) + : undefined, }; }; @@ -242,6 +247,9 @@ export class DatabaseNotificationsStore implements NotificationsStore { severity: normalizeSeverity(notification.payload?.severity), icon: notification.payload.icon, scope: notification.payload?.scope, + attributes: notification.payload?.attributes + ? JSON.stringify(notification.payload.attributes) + : undefined, }; }; diff --git a/plugins/notifications-backend/src/service/router.test.ts b/plugins/notifications-backend/src/service/router.test.ts index 6c74fed41c..b745754ba6 100644 --- a/plugins/notifications-backend/src/service/router.test.ts +++ b/plugins/notifications-backend/src/service/router.test.ts @@ -529,6 +529,9 @@ describe.each(databases.eachSupportedId())('createRouter (%s)', databaseId => { title: 'Test broadcast notification', created: new Date(), severity: 'high', + attributes: { + attr1: 'attrValue', + }, }); await client('notification').insert({ id: uuid(), @@ -537,6 +540,9 @@ describe.each(databases.eachSupportedId())('createRouter (%s)', databaseId => { title: 'Test notification', created: new Date(), severity: 'normal', + attributes: { + attr1: 'attrValue', + }, }); const response = await request(app).get('/'); @@ -555,6 +561,9 @@ describe.each(databases.eachSupportedId())('createRouter (%s)', databaseId => { severity: 'normal', title: 'Test notification', topic: null, + attributes: { + attr1: 'attrValue', + }, }, read: null, saved: null, @@ -573,6 +582,9 @@ describe.each(databases.eachSupportedId())('createRouter (%s)', databaseId => { severity: 'high', title: 'Test broadcast notification', topic: null, + attributes: { + attr1: 'attrValue', + }, }, read: null, saved: null, diff --git a/plugins/notifications-common/report.api.md b/plugins/notifications-common/report.api.md index 93889b206b..f619ab677d 100644 --- a/plugins/notifications-common/report.api.md +++ b/plugins/notifications-common/report.api.md @@ -31,7 +31,9 @@ export type NewNotificationSignal = { }; // @public (undocumented) -type Notification_2 = { +type Notification_2< + T extends Record = Record, +> = { id: string; user: string | null; created: Date; @@ -39,12 +41,14 @@ type Notification_2 = { read?: Date; updated?: Date; origin: string; - payload: NotificationPayload; + payload: NotificationPayload; }; export { Notification_2 as Notification }; // @public (undocumented) -export type NotificationPayload = { +export type NotificationPayload< + T extends Record = Record, +> = { title: string; description?: string; link?: string; @@ -52,6 +56,7 @@ export type NotificationPayload = { topic?: string; scope?: string; icon?: string; + attributes?: T; }; // @public (undocumented) diff --git a/plugins/notifications-common/src/types.ts b/plugins/notifications-common/src/types.ts index 48f89cbbb4..2583d44dc1 100644 --- a/plugins/notifications-common/src/types.ts +++ b/plugins/notifications-common/src/types.ts @@ -18,7 +18,9 @@ export type NotificationSeverity = 'critical' | 'high' | 'normal' | 'low'; /** @public */ -export type NotificationPayload = { +export type NotificationPayload< + T extends Record = Record, +> = { /** * Notification title */ @@ -50,10 +52,16 @@ export type NotificationPayload = { * Optional notification icon */ icon?: string; + /** + * Optional additional customizable attributes. + */ + attributes?: T; }; /** @public */ -export type Notification = { +export type Notification< + T extends Record = Record, +> = { /** * Unique identifier for the notification */ @@ -87,7 +95,7 @@ export type Notification = { /** * Actual notification payload */ - payload: NotificationPayload; + payload: NotificationPayload; }; /** @public */ From b345e53dd30d644a573a00614a3a630e473a2dc6 Mon Sep 17 00:00:00 2001 From: Frank Ye Date: Fri, 30 May 2025 14:05:19 -0400 Subject: [PATCH 2/9] rename to metadata Signed-off-by: Frank Ye --- .changeset/wise-mails-agree.md | 2 +- ..._addAttributes.js => 20250521_addMetadata.js} | 8 ++++---- plugins/notifications-backend/report.sql.md | 4 ++-- .../database/DatabaseNotificationsStore.test.ts | 8 ++++---- .../src/database/DatabaseNotificationsStore.ts | 12 ++++++------ .../src/service/router.test.ts | 16 ++++++++-------- plugins/notifications-common/package.json | 1 + plugins/notifications-common/report.api.md | 15 +++++++-------- plugins/notifications-common/src/types.ts | 16 +++++++--------- yarn.lock | 1 + 10 files changed, 41 insertions(+), 42 deletions(-) rename plugins/notifications-backend/migrations/{20250521_addAttributes.js => 20250521_addMetadata.js} (85%) diff --git a/.changeset/wise-mails-agree.md b/.changeset/wise-mails-agree.md index d6e96a3f20..301c941339 100644 --- a/.changeset/wise-mails-agree.md +++ b/.changeset/wise-mails-agree.md @@ -3,4 +3,4 @@ '@backstage/plugin-notifications-common': minor --- -Add an optional generic object `attributes` field to `NotificationPayload`. Attributes can be used to store additional unstructured data for the notification, and are stored in the database. +Add an optional generic object `metadata` field to `NotificationPayload`. Metadata can be used to store additional unstructured data for the notification, and are stored in the database. diff --git a/plugins/notifications-backend/migrations/20250521_addAttributes.js b/plugins/notifications-backend/migrations/20250521_addMetadata.js similarity index 85% rename from plugins/notifications-backend/migrations/20250521_addAttributes.js rename to plugins/notifications-backend/migrations/20250521_addMetadata.js index e7519d2e29..c298ed49e1 100644 --- a/plugins/notifications-backend/migrations/20250521_addAttributes.js +++ b/plugins/notifications-backend/migrations/20250521_addMetadata.js @@ -16,18 +16,18 @@ exports.up = async function up(knex) { await knex.schema.alterTable('notification', table => { - table.text('attributes').nullable(); + table.text('metadata').nullable(); }); await knex.schema.alterTable('broadcast', table => { - table.text('attributes').nullable(); + table.text('metadata').nullable(); }); }; exports.down = async function down(knex) { await knex.schema.alterTable('notification', table => { - table.dropColumn('attributes'); + table.dropColumn('metadata'); }); await knex.schema.alterTable('broadcast', table => { - table.dropColumn('attributes'); + table.dropColumn('metadata'); }); }; diff --git a/plugins/notifications-backend/report.sql.md b/plugins/notifications-backend/report.sql.md index bb9612b357..ab491dc791 100644 --- a/plugins/notifications-backend/report.sql.md +++ b/plugins/notifications-backend/report.sql.md @@ -6,12 +6,12 @@ | Column | Type | Nullable | Max Length | Default | | ------------- | -------------------------- | -------- | ---------- | ------------------- | -| `attributes` | `text` | true | - | - | | `created` | `timestamp with time zone` | false | - | `CURRENT_TIMESTAMP` | | `description` | `text` | true | - | - | | `icon` | `character varying` | true | 255 | - | | `id` | `uuid` | false | - | - | | `link` | `text` | true | - | - | +| `metadata` | `text` | true | - | - | | `origin` | `character varying` | false | 255 | - | | `scope` | `character varying` | true | 255 | - | | `severity` | `character varying` | false | 8 | - | @@ -41,12 +41,12 @@ | Column | Type | Nullable | Max Length | Default | | ------------- | -------------------------- | -------- | ---------- | ------------------- | -| `attributes` | `text` | true | - | - | | `created` | `timestamp with time zone` | false | - | `CURRENT_TIMESTAMP` | | `description` | `text` | true | - | - | | `icon` | `character varying` | true | 255 | - | | `id` | `uuid` | false | - | - | | `link` | `text` | true | - | - | +| `metadata` | `text` | true | - | - | | `origin` | `character varying` | false | 255 | - | | `read` | `timestamp with time zone` | true | - | - | | `saved` | `timestamp with time zone` | true | - | - | diff --git a/plugins/notifications-backend/src/database/DatabaseNotificationsStore.test.ts b/plugins/notifications-backend/src/database/DatabaseNotificationsStore.test.ts index 03c4428289..9d8898f0e8 100644 --- a/plugins/notifications-backend/src/database/DatabaseNotificationsStore.test.ts +++ b/plugins/notifications-backend/src/database/DatabaseNotificationsStore.test.ts @@ -69,8 +69,8 @@ const testNotification1: Notification = { link: '/catalog', severity: 'critical', icon: 'docs', - attributes: { - attribute1: 'attributeValue', + metadata: { + attribute1: 'value1', }, }, }; @@ -212,8 +212,8 @@ describe.each(databases.eachSupportedId())( expect(notification?.payload?.link).toBe('/catalog'); expect(notification?.payload?.severity).toBe('critical'); expect(notification?.payload?.icon).toBe('docs'); - expect(notification?.payload?.attributes).toEqual({ - attribute1: 'attributeValue', + expect(notification?.payload?.metadata).toEqual({ + attribute1: 'value1', }); }); }); diff --git a/plugins/notifications-backend/src/database/DatabaseNotificationsStore.ts b/plugins/notifications-backend/src/database/DatabaseNotificationsStore.ts index 5f68aa0e39..6a2f3f80b8 100644 --- a/plugins/notifications-backend/src/database/DatabaseNotificationsStore.ts +++ b/plugins/notifications-backend/src/database/DatabaseNotificationsStore.ts @@ -53,7 +53,7 @@ const NOTIFICATION_COLUMNS = [ 'user', 'read', 'saved', - 'attributes', + 'metadata', ]; type NotificationRowType = { @@ -165,7 +165,7 @@ export class DatabaseNotificationsStore implements NotificationsStore { severity: row.severity, scope: row.scope, icon: row.icon, - attributes: row.attributes ? JSON.parse(row.attributes) : undefined, + metadata: row.metadata ? JSON.parse(row.metadata) : undefined, }, })); }; @@ -229,8 +229,8 @@ export class DatabaseNotificationsStore implements NotificationsStore { icon: notification.payload?.icon, saved: notification.saved, read: notification.read, - attributes: notification.payload?.attributes - ? JSON.stringify(notification.payload.attributes) + metadata: notification.payload?.metadata + ? JSON.stringify(notification.payload.metadata) : undefined, }; }; @@ -247,8 +247,8 @@ export class DatabaseNotificationsStore implements NotificationsStore { severity: normalizeSeverity(notification.payload?.severity), icon: notification.payload.icon, scope: notification.payload?.scope, - attributes: notification.payload?.attributes - ? JSON.stringify(notification.payload.attributes) + metadata: notification.payload?.metadata + ? JSON.stringify(notification.payload.metadata) : undefined, }; }; diff --git a/plugins/notifications-backend/src/service/router.test.ts b/plugins/notifications-backend/src/service/router.test.ts index b745754ba6..b19a78c3d9 100644 --- a/plugins/notifications-backend/src/service/router.test.ts +++ b/plugins/notifications-backend/src/service/router.test.ts @@ -529,8 +529,8 @@ describe.each(databases.eachSupportedId())('createRouter (%s)', databaseId => { title: 'Test broadcast notification', created: new Date(), severity: 'high', - attributes: { - attr1: 'attrValue', + metadata: { + attr1: 'value1', }, }); await client('notification').insert({ @@ -540,8 +540,8 @@ describe.each(databases.eachSupportedId())('createRouter (%s)', databaseId => { title: 'Test notification', created: new Date(), severity: 'normal', - attributes: { - attr1: 'attrValue', + metadata: { + attr1: 'value1', }, }); @@ -561,8 +561,8 @@ describe.each(databases.eachSupportedId())('createRouter (%s)', databaseId => { severity: 'normal', title: 'Test notification', topic: null, - attributes: { - attr1: 'attrValue', + metadata: { + attr1: 'value1', }, }, read: null, @@ -582,8 +582,8 @@ describe.each(databases.eachSupportedId())('createRouter (%s)', databaseId => { severity: 'high', title: 'Test broadcast notification', topic: null, - attributes: { - attr1: 'attrValue', + metadata: { + attr1: 'value1', }, }, read: null, diff --git a/plugins/notifications-common/package.json b/plugins/notifications-common/package.json index 66473d0237..9e1c544ba1 100644 --- a/plugins/notifications-common/package.json +++ b/plugins/notifications-common/package.json @@ -40,6 +40,7 @@ }, "dependencies": { "@backstage/config": "workspace:^", + "@backstage/types": "workspace:^", "@material-ui/icons": "^4.9.1" }, "devDependencies": { diff --git a/plugins/notifications-common/report.api.md b/plugins/notifications-common/report.api.md index f619ab677d..337c41b7b5 100644 --- a/plugins/notifications-common/report.api.md +++ b/plugins/notifications-common/report.api.md @@ -4,6 +4,7 @@ ```ts import { Config } from '@backstage/config'; +import { JsonValue } from '@backstage/types'; // @public (undocumented) export type ChannelSetting = { @@ -31,9 +32,7 @@ export type NewNotificationSignal = { }; // @public (undocumented) -type Notification_2< - T extends Record = Record, -> = { +type Notification_2 = { id: string; user: string | null; created: Date; @@ -41,14 +40,12 @@ type Notification_2< read?: Date; updated?: Date; origin: string; - payload: NotificationPayload; + payload: NotificationPayload; }; export { Notification_2 as Notification }; // @public (undocumented) -export type NotificationPayload< - T extends Record = Record, -> = { +export type NotificationPayload = { title: string; description?: string; link?: string; @@ -56,7 +53,9 @@ export type NotificationPayload< topic?: string; scope?: string; icon?: string; - attributes?: T; + metadata?: { + [KMetadataKey in string]?: JsonValue; + }; }; // @public (undocumented) diff --git a/plugins/notifications-common/src/types.ts b/plugins/notifications-common/src/types.ts index 2583d44dc1..4e49de79de 100644 --- a/plugins/notifications-common/src/types.ts +++ b/plugins/notifications-common/src/types.ts @@ -14,13 +14,13 @@ * limitations under the License. */ +import { JsonValue } from '@backstage/types'; + /** @public */ export type NotificationSeverity = 'critical' | 'high' | 'normal' | 'low'; /** @public */ -export type NotificationPayload< - T extends Record = Record, -> = { +export type NotificationPayload = { /** * Notification title */ @@ -53,15 +53,13 @@ export type NotificationPayload< */ icon?: string; /** - * Optional additional customizable attributes. + * Optional additional customizable metadata. */ - attributes?: T; + metadata?: { [KMetadataKey in string]?: JsonValue }; }; /** @public */ -export type Notification< - T extends Record = Record, -> = { +export type Notification = { /** * Unique identifier for the notification */ @@ -95,7 +93,7 @@ export type Notification< /** * Actual notification payload */ - payload: NotificationPayload; + payload: NotificationPayload; }; /** @public */ diff --git a/yarn.lock b/yarn.lock index a3334417ab..c3cdc943dd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5751,6 +5751,7 @@ __metadata: dependencies: "@backstage/cli": "workspace:^" "@backstage/config": "workspace:^" + "@backstage/types": "workspace:^" "@material-ui/icons": "npm:^4.9.1" languageName: unknown linkType: soft From 1506df32f1247f1460e8072b827a2308cad7659d Mon Sep 17 00:00:00 2001 From: Frank Ye Date: Wed, 16 Jul 2025 17:21:21 -0400 Subject: [PATCH 3/9] add usage docs Signed-off-by: Frank Ye --- docs/notifications/usage.md | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/docs/notifications/usage.md b/docs/notifications/usage.md index 358426dc21..614a5f8bfe 100644 --- a/docs/notifications/usage.md +++ b/docs/notifications/usage.md @@ -210,3 +210,39 @@ notificationsApi.getNotification(yourId); // or with connection to signals: notificationsApi.getNotification(lastSignal.notification_id); ``` + +## Metadata Field + +The metadata field is a freeform object that is designed to be used by processors. + +For example, for a custom processor that supports markdown format the metadata field can be used to pass an optional `markdown` field. + +When sending a notification: + +```ts +notificationService.send({ + recipients: { type: 'entity', entityRef: 'group/default:team-a' }, + payload: { + title: 'Notification', + description: 'Description' + metadata: { + markdown: ` + ### Notification + Description + `, + }, + }, +}); +``` + +In your processor, you can then use the metadata field accordingly: + +```ts +async postProcess(notification: Notification): Promise { + customNotificationSender.send({ + to: getUsers(notification.recipients), + subject: notification.payload.title, + markdownText: notification.payload.metadata?.markdown ?? notification.payload.description, + }); +} +``` From 50319c04bb417458a03d307cf2bd99733611c544 Mon Sep 17 00:00:00 2001 From: Frank Ye Date: Thu, 7 Aug 2025 17:33:21 -0400 Subject: [PATCH 4/9] change metadata field to be processor only and update docs Signed-off-by: Frank Ye --- .changeset/wise-mails-agree.md | 4 +- docs/notifications/usage.md | 37 +++++++++++++++++-- .../migrations/20250521_addMetadata.js | 33 ----------------- plugins/notifications-backend/report.sql.md | 2 - .../DatabaseNotificationsStore.test.ts | 6 --- .../database/DatabaseNotificationsStore.ts | 10 +---- .../src/service/router.test.ts | 18 +++------ 7 files changed, 42 insertions(+), 68 deletions(-) delete mode 100644 plugins/notifications-backend/migrations/20250521_addMetadata.js diff --git a/.changeset/wise-mails-agree.md b/.changeset/wise-mails-agree.md index 301c941339..1ad08c2ef6 100644 --- a/.changeset/wise-mails-agree.md +++ b/.changeset/wise-mails-agree.md @@ -1,6 +1,6 @@ --- -'@backstage/plugin-notifications-backend': minor +'@backstage/plugin-notifications-backend': patch '@backstage/plugin-notifications-common': minor --- -Add an optional generic object `metadata` field to `NotificationPayload`. Metadata can be used to store additional unstructured data for the notification, and are stored in the database. +Add an optional generic object `metadata` field to `NotificationPayload`. Metadata can be used to store additional unstructured data for the notification and are available to use by processors. diff --git a/docs/notifications/usage.md b/docs/notifications/usage.md index 614a5f8bfe..94de585d21 100644 --- a/docs/notifications/usage.md +++ b/docs/notifications/usage.md @@ -215,7 +215,32 @@ notificationsApi.getNotification(lastSignal.notification_id); The metadata field is a freeform object that is designed to be used by processors. -For example, for a custom processor that supports markdown format the metadata field can be used to pass an optional `markdown` field. +### Well-known Notification Metadata Fields + +Below are metadata fields that will be commonly used between processors and have defined schematics. + +#### backstage.io/body.markdown + +```ts +# Example: +const payload = { + title: 'Entities Require Attention', + description: 'Entities: Service A, Service B' + metadata: { + 'backstage.io/body.markdown': ` + # Entities + - Service A + - System B + ` + } +} +``` + +This value of this metadata field should be the notification message in markdown format. This allows additional formatting options for processors that support markdown. + +### Usage + +Below is an example of using the `backstage.io/body.markdown` metadata field in a custom processor. When sending a notification: @@ -226,7 +251,7 @@ notificationService.send({ title: 'Notification', description: 'Description' metadata: { - markdown: ` + 'backstage.io/body.markdown': ` ### Notification Description `, @@ -235,14 +260,18 @@ notificationService.send({ }); ``` -In your processor, you can then use the metadata field accordingly: +In the processor, you can then use the metadata field accordingly: ```ts async postProcess(notification: Notification): Promise { + // We suggest you parse the metadata field with a schema, i.e. Zod + const parseResult = CustomProcessorMetadataSchema.safeParse(notification.payload.metadata ?? {}); + const metadata = parseResult.success ? parseResult.data : {}; + customNotificationSender.send({ to: getUsers(notification.recipients), subject: notification.payload.title, - markdownText: notification.payload.metadata?.markdown ?? notification.payload.description, + markdownText: metadata['backstage.io/body.markdown'] ?? notification.payload.description, }); } ``` diff --git a/plugins/notifications-backend/migrations/20250521_addMetadata.js b/plugins/notifications-backend/migrations/20250521_addMetadata.js deleted file mode 100644 index c298ed49e1..0000000000 --- a/plugins/notifications-backend/migrations/20250521_addMetadata.js +++ /dev/null @@ -1,33 +0,0 @@ -/* - * 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. - */ - -exports.up = async function up(knex) { - await knex.schema.alterTable('notification', table => { - table.text('metadata').nullable(); - }); - await knex.schema.alterTable('broadcast', table => { - table.text('metadata').nullable(); - }); -}; - -exports.down = async function down(knex) { - await knex.schema.alterTable('notification', table => { - table.dropColumn('metadata'); - }); - await knex.schema.alterTable('broadcast', table => { - table.dropColumn('metadata'); - }); -}; diff --git a/plugins/notifications-backend/report.sql.md b/plugins/notifications-backend/report.sql.md index ab491dc791..aacb787a61 100644 --- a/plugins/notifications-backend/report.sql.md +++ b/plugins/notifications-backend/report.sql.md @@ -11,7 +11,6 @@ | `icon` | `character varying` | true | 255 | - | | `id` | `uuid` | false | - | - | | `link` | `text` | true | - | - | -| `metadata` | `text` | true | - | - | | `origin` | `character varying` | false | 255 | - | | `scope` | `character varying` | true | 255 | - | | `severity` | `character varying` | false | 8 | - | @@ -46,7 +45,6 @@ | `icon` | `character varying` | true | 255 | - | | `id` | `uuid` | false | - | - | | `link` | `text` | true | - | - | -| `metadata` | `text` | true | - | - | | `origin` | `character varying` | false | 255 | - | | `read` | `timestamp with time zone` | true | - | - | | `saved` | `timestamp with time zone` | true | - | - | diff --git a/plugins/notifications-backend/src/database/DatabaseNotificationsStore.test.ts b/plugins/notifications-backend/src/database/DatabaseNotificationsStore.test.ts index 9d8898f0e8..d8ae3faf57 100644 --- a/plugins/notifications-backend/src/database/DatabaseNotificationsStore.test.ts +++ b/plugins/notifications-backend/src/database/DatabaseNotificationsStore.test.ts @@ -69,9 +69,6 @@ const testNotification1: Notification = { link: '/catalog', severity: 'critical', icon: 'docs', - metadata: { - attribute1: 'value1', - }, }, }; const testNotification2: Notification = { @@ -212,9 +209,6 @@ describe.each(databases.eachSupportedId())( expect(notification?.payload?.link).toBe('/catalog'); expect(notification?.payload?.severity).toBe('critical'); expect(notification?.payload?.icon).toBe('docs'); - expect(notification?.payload?.metadata).toEqual({ - attribute1: 'value1', - }); }); }); diff --git a/plugins/notifications-backend/src/database/DatabaseNotificationsStore.ts b/plugins/notifications-backend/src/database/DatabaseNotificationsStore.ts index 6a2f3f80b8..94131ca257 100644 --- a/plugins/notifications-backend/src/database/DatabaseNotificationsStore.ts +++ b/plugins/notifications-backend/src/database/DatabaseNotificationsStore.ts @@ -53,7 +53,6 @@ const NOTIFICATION_COLUMNS = [ 'user', 'read', 'saved', - 'metadata', ]; type NotificationRowType = { @@ -165,7 +164,6 @@ export class DatabaseNotificationsStore implements NotificationsStore { severity: row.severity, scope: row.scope, icon: row.icon, - metadata: row.metadata ? JSON.parse(row.metadata) : undefined, }, })); }; @@ -226,12 +224,9 @@ export class DatabaseNotificationsStore implements NotificationsStore { description: notification.payload?.description, severity: normalizeSeverity(notification.payload?.severity), scope: notification.payload?.scope, - icon: notification.payload?.icon, + icon: notification.payload.icon, saved: notification.saved, read: notification.read, - metadata: notification.payload?.metadata - ? JSON.stringify(notification.payload.metadata) - : undefined, }; }; @@ -247,9 +242,6 @@ export class DatabaseNotificationsStore implements NotificationsStore { severity: normalizeSeverity(notification.payload?.severity), icon: notification.payload.icon, scope: notification.payload?.scope, - metadata: notification.payload?.metadata - ? JSON.stringify(notification.payload.metadata) - : undefined, }; }; diff --git a/plugins/notifications-backend/src/service/router.test.ts b/plugins/notifications-backend/src/service/router.test.ts index b19a78c3d9..980762edbc 100644 --- a/plugins/notifications-backend/src/service/router.test.ts +++ b/plugins/notifications-backend/src/service/router.test.ts @@ -241,6 +241,9 @@ describe.each(databases.eachSupportedId())('createRouter (%s)', databaseId => { }, payload: { title: 'test notification', + metadata: { + attr: 1, + }, }, }); @@ -253,6 +256,9 @@ describe.each(databases.eachSupportedId())('createRouter (%s)', databaseId => { payload: { severity: 'normal', title: 'test notification', + metadata: { + attr: 1, + }, }, user: 'user:default/mock', }, @@ -529,9 +535,6 @@ describe.each(databases.eachSupportedId())('createRouter (%s)', databaseId => { title: 'Test broadcast notification', created: new Date(), severity: 'high', - metadata: { - attr1: 'value1', - }, }); await client('notification').insert({ id: uuid(), @@ -540,9 +543,6 @@ describe.each(databases.eachSupportedId())('createRouter (%s)', databaseId => { title: 'Test notification', created: new Date(), severity: 'normal', - metadata: { - attr1: 'value1', - }, }); const response = await request(app).get('/'); @@ -561,9 +561,6 @@ describe.each(databases.eachSupportedId())('createRouter (%s)', databaseId => { severity: 'normal', title: 'Test notification', topic: null, - metadata: { - attr1: 'value1', - }, }, read: null, saved: null, @@ -582,9 +579,6 @@ describe.each(databases.eachSupportedId())('createRouter (%s)', databaseId => { severity: 'high', title: 'Test broadcast notification', topic: null, - metadata: { - attr1: 'value1', - }, }, read: null, saved: null, From 17b34e585f454334f0943d919f07f2c6c94035e7 Mon Sep 17 00:00:00 2001 From: Frank Ye Date: Thu, 14 Aug 2025 16:26:29 -0400 Subject: [PATCH 5/9] update BEP typing for metadata Signed-off-by: Frank Ye --- beps/0001-notifications-system/README.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/beps/0001-notifications-system/README.md b/beps/0001-notifications-system/README.md index 03fb224e1c..bfea3115f9 100644 --- a/beps/0001-notifications-system/README.md +++ b/beps/0001-notifications-system/README.md @@ -211,10 +211,7 @@ export type NotificationPayload = { topic?: string; scope?: string; icon?: string; - metadata?: Array<{ - type: string; - value: JsonValue; - }>; + metadata?: { [KMetadataKey in string]?: JsonValue }; }; export type Notification = { From e33fce15df360d5795d7e413d01c47a6219e2aef Mon Sep 17 00:00:00 2001 From: Andre Wanlin Date: Fri, 15 Aug 2025 07:42:16 -0500 Subject: [PATCH 6/9] create-app - Added Notifications and Signals to be installed by default Signed-off-by: Andre Wanlin --- .changeset/loose-bees-see.md | 5 ++ docs/notifications/index.md | 52 +++++++++++++++++++ packages/create-app/src/lib/tasks.test.ts | 5 ++ packages/create-app/src/lib/versions.ts | 11 ++++ .../examples/template/template.yaml | 12 +++++ .../default-app/packages/app/package.json.hbs | 2 + .../default-app/packages/app/src/App.tsx | 4 ++ .../packages/app/src/components/Root/Root.tsx | 3 ++ .../packages/backend/package.json.hbs | 3 ++ .../default-app/packages/backend/src/index.ts | 11 +++- .../next-app/examples/template/template.yaml | 12 +++++ .../next-app/packages/app/package.json.hbs | 2 + .../packages/backend/package.json.hbs | 3 ++ .../next-app/packages/backend/src/index.ts | 11 +++- 14 files changed, 134 insertions(+), 2 deletions(-) create mode 100644 .changeset/loose-bees-see.md diff --git a/.changeset/loose-bees-see.md b/.changeset/loose-bees-see.md new file mode 100644 index 0000000000..35fd134816 --- /dev/null +++ b/.changeset/loose-bees-see.md @@ -0,0 +1,5 @@ +--- +'@backstage/create-app': patch +--- + +Added Notifications and Signals to be installed by default diff --git a/docs/notifications/index.md b/docs/notifications/index.md index c31d2716e4..59ff2962ed 100644 --- a/docs/notifications/index.md +++ b/docs/notifications/index.md @@ -33,6 +33,12 @@ Example of use-cases: ## Installation +:::note + +As of the `1.42.0` release of Backstage, Notifications and Signals are installed as part of the default `@backstage/create-app` instance which means you won't need to follow the installations steps outlined here. The only exception to this is adding the [Notifications tab to User Settings](#user-specific-notification-settings) to allow managing these settings. + +::: + The following sections will walk you through the installation of the various parts of the Backstage Notification System. ### Add Notifications Backend @@ -173,6 +179,52 @@ notifications: If the retention is set to false, notifications will not be automatically deleted. +## Scaffolder Action + +:::note + +As of the `1.42.0` release of Backstage, the Notifications Scaffolder action is installed as part of the default `@backstage/create-app` instance which means you won't need to follow the installations steps outlined here. Feel free to skip to the [Basic Example](#basic-example). + +::: + +There is also a Scaffolder action that you can use to send a notification as part of your Software Template. + +First we need to add the backend package: + +```bash title="From your Backstage root directory" +yarn --cwd packages/backend add @backstage/plugin-scaffolder-backend-module-notifications +``` + +Then we need to add it to our backend: + +```ts title="packages/backend/src/index.ts" +const backend = createBackend(); +// ... +backend.add( + import('@backstage/plugin-scaffolder-backend-module-notifications'), +); +``` + +### Basic Example + +Here's an example of how you can use it in your Software Template, more details and examples can be found in the "Installed actions" screen in your Backstage instances: + +```yaml title="template.yaml" +steps: + - id: notify + name: Notify + action: notification:send + input: + recipients: entity + entityRefs: + - user:default/guest + title: 'Template executed' + info: 'Your template has been executed' + severity: 'normal' +``` + +The example above would send a notification to the Guest user (`user:default/guest`) + ## Additional info An example of a backend plugin sending notifications can be found in the [`@backstage/plugin-scaffolder-backend-module-notifications` package](https://github.com/backstage/backstage/tree/master/plugins/scaffolder-backend-module-notifications). diff --git a/packages/create-app/src/lib/tasks.test.ts b/packages/create-app/src/lib/tasks.test.ts index c5d5092410..86a7008b4d 100644 --- a/packages/create-app/src/lib/tasks.test.ts +++ b/packages/create-app/src/lib/tasks.test.ts @@ -89,12 +89,17 @@ jest.mock('./versions', () => ({ '@backstage/plugin-catalog-react': '1.0.0', '@backstage/plugin-kubernetes': '1.0.0', '@backstage/plugin-kubernetes-backend': '1.0.0', + '@backstage/plugin-notifications': '1.0.0', + '@backstage/plugin-notifications-backend': '1.0.0', '@backstage/plugin-org': '1.0.0', '@backstage/plugin-scaffolder': '1.0.0', '@backstage/plugin-scaffolder-backend-module-github': '1.0.0', + '@backstage/plugin-scaffolder-backend-module-notifications': '1.0.0', '@backstage/plugin-permission-react': '1.0.0', '@backstage/plugin-search': '1.0.0', '@backstage/plugin-search-react': '1.0.0', + '@backstage/plugin-signals': '1.0.0', + '@backstage/plugin-signals-backend': '1.0.0', '@backstage/plugin-techdocs': '1.0.0', '@backstage/plugin-techdocs-react': '1.0.0', '@backstage/plugin-techdocs-module-addons-contrib': '1.0.0', diff --git a/packages/create-app/src/lib/versions.ts b/packages/create-app/src/lib/versions.ts index bd0ea7eb41..6785fb9b52 100644 --- a/packages/create-app/src/lib/versions.ts +++ b/packages/create-app/src/lib/versions.ts @@ -68,6 +68,8 @@ import { version as pluginCatalogGraph } from '../../../../plugins/catalog-graph import { version as pluginCatalogImport } from '../../../../plugins/catalog-import/package.json'; import { version as pluginKubernetes } from '../../../../plugins/kubernetes/package.json'; import { version as pluginKubernetesBackend } from '../../../../plugins/kubernetes-backend/package.json'; +import { version as pluginNotifications } from '../../../../plugins/notifications/package.json'; +import { version as pluginNotificationsBackend } from '../../../../plugins/notifications-backend/package.json'; import { version as pluginOrg } from '../../../../plugins/org/package.json'; import { version as pluginPermissionBackend } from '../../../../plugins/permission-backend/package.json'; import { version as pluginPermissionBackendModulePolicyAllowAll } from '../../../../plugins/permission-backend-module-policy-allow-all/package.json'; @@ -78,6 +80,7 @@ import { version as pluginProxyBackend } from '../../../../plugins/proxy-backend import { version as pluginScaffolder } from '../../../../plugins/scaffolder/package.json'; import { version as pluginScaffolderBackend } from '../../../../plugins/scaffolder-backend/package.json'; import { version as pluginScaffolderBackendModuleGithub } from '../../../../plugins/scaffolder-backend-module-github/package.json'; +import { version as pluginScaffolderBackendModuleNotifications } from '../../../../plugins/scaffolder-backend-module-notifications/package.json'; import { version as pluginSearch } from '../../../../plugins/search/package.json'; import { version as pluginSearchReact } from '../../../../plugins/search-react/package.json'; import { version as pluginSearchBackend } from '../../../../plugins/search-backend/package.json'; @@ -85,6 +88,8 @@ import { version as pluginSearchBackendModuleCatalog } from '../../../../plugins import { version as pluginSearchBackendModulePg } from '../../../../plugins/search-backend-module-pg/package.json'; import { version as pluginSearchBackendModuleTechdocs } from '../../../../plugins/search-backend-module-techdocs/package.json'; import { version as pluginSearchBackendNode } from '../../../../plugins/search-backend-node/package.json'; +import { version as pluginSignals } from '../../../../plugins/signals/package.json'; +import { version as pluginSignalsBackend } from '../../../../plugins/signals-backend/package.json'; import { version as pluginTechdocs } from '../../../../plugins/techdocs/package.json'; import { version as pluginTechdocsReact } from '../../../../plugins/techdocs-react/package.json'; import { version as pluginTechdocsModuleAddonsContrib } from '../../../../plugins/techdocs-module-addons-contrib/package.json'; @@ -130,6 +135,8 @@ export const packageVersions = { '@backstage/plugin-catalog-import': pluginCatalogImport, '@backstage/plugin-kubernetes': pluginKubernetes, '@backstage/plugin-kubernetes-backend': pluginKubernetesBackend, + '@backstage/plugin-notifications': pluginNotifications, + '@backstage/plugin-notifications-backend': pluginNotificationsBackend, '@backstage/plugin-org': pluginOrg, '@backstage/plugin-permission-backend': pluginPermissionBackend, '@backstage/plugin-permission-backend-module-allow-all-policy': @@ -142,6 +149,8 @@ export const packageVersions = { '@backstage/plugin-scaffolder-backend': pluginScaffolderBackend, '@backstage/plugin-scaffolder-backend-module-github': pluginScaffolderBackendModuleGithub, + '@backstage/plugin-scaffolder-backend-module-notifications': + pluginScaffolderBackendModuleNotifications, '@backstage/plugin-search': pluginSearch, '@backstage/plugin-search-react': pluginSearchReact, '@backstage/plugin-search-backend': pluginSearchBackend, @@ -151,6 +160,8 @@ export const packageVersions = { '@backstage/plugin-search-backend-module-techdocs': pluginSearchBackendModuleTechdocs, '@backstage/plugin-search-backend-node': pluginSearchBackendNode, + '@backstage/plugin-signals': pluginSignals, + '@backstage/plugin-signals-backend': pluginSignalsBackend, '@backstage/plugin-techdocs': pluginTechdocs, '@backstage/plugin-techdocs-react': pluginTechdocsReact, '@backstage/plugin-techdocs-module-addons-contrib': diff --git a/packages/create-app/templates/default-app/examples/template/template.yaml b/packages/create-app/templates/default-app/examples/template/template.yaml index f04c3241c5..efb33be866 100644 --- a/packages/create-app/templates/default-app/examples/template/template.yaml +++ b/packages/create-app/templates/default-app/examples/template/template.yaml @@ -66,6 +66,18 @@ spec: repoContentsUrl: ${{ steps['publish'].output.repoContentsUrl }} catalogInfoPath: '/catalog-info.yaml' + # Let's notify the user that the template has completed using the Notification action + - id: notify + name: Notify + action: notification:send + input: + recipients: entity + entityRefs: + - user:default/guest + title: 'Template executed' + info: 'Your template has been executed' + severity: 'normal' + # Outputs are displayed to the user after a successful execution of the template. output: links: diff --git a/packages/create-app/templates/default-app/packages/app/package.json.hbs b/packages/create-app/templates/default-app/packages/app/package.json.hbs index b7d16f8a84..5152113098 100644 --- a/packages/create-app/templates/default-app/packages/app/package.json.hbs +++ b/packages/create-app/templates/default-app/packages/app/package.json.hbs @@ -28,11 +28,13 @@ "@backstage/plugin-catalog-import": "^{{version '@backstage/plugin-catalog-import'}}", "@backstage/plugin-catalog-react": "^{{version '@backstage/plugin-catalog-react'}}", "@backstage/plugin-kubernetes": "^{{version '@backstage/plugin-kubernetes'}}", + "@backstage/plugin-notifications": "^{{ version '@backstage/plugin-notifications'}}", "@backstage/plugin-org": "^{{version '@backstage/plugin-org'}}", "@backstage/plugin-permission-react": "^{{version '@backstage/plugin-permission-react'}}", "@backstage/plugin-scaffolder": "^{{version '@backstage/plugin-scaffolder'}}", "@backstage/plugin-search": "^{{version '@backstage/plugin-search'}}", "@backstage/plugin-search-react": "^{{version '@backstage/plugin-search-react'}}", + "@backstage/plugin-signals": "^{{ version '@backstage/plugin-signals'}}", "@backstage/plugin-techdocs": "^{{version '@backstage/plugin-techdocs'}}", "@backstage/plugin-techdocs-module-addons-contrib": "^{{version '@backstage/plugin-techdocs-module-addons-contrib'}}", "@backstage/plugin-techdocs-react": "^{{version '@backstage/plugin-techdocs-react'}}", diff --git a/packages/create-app/templates/default-app/packages/app/src/App.tsx b/packages/create-app/templates/default-app/packages/app/src/App.tsx index 89b199c8d3..bb9c98eff8 100644 --- a/packages/create-app/templates/default-app/packages/app/src/App.tsx +++ b/packages/create-app/templates/default-app/packages/app/src/App.tsx @@ -35,6 +35,8 @@ import { AppRouter, FlatRoutes } from '@backstage/core-app-api'; import { CatalogGraphPage } from '@backstage/plugin-catalog-graph'; import { RequirePermission } from '@backstage/plugin-permission-react'; import { catalogEntityCreatePermission } from '@backstage/plugin-catalog-common/alpha'; +import { NotificationsPage } from '@backstage/plugin-notifications'; +import { SignalsDisplay } from '@backstage/plugin-signals'; const app = createApp({ apis, @@ -94,6 +96,7 @@ const routes = ( } /> } /> + } /> ); @@ -101,6 +104,7 @@ export default app.createRoot( <> + {routes} diff --git a/packages/create-app/templates/default-app/packages/app/src/components/Root/Root.tsx b/packages/create-app/templates/default-app/packages/app/src/components/Root/Root.tsx index 31fc08db7f..4163f34ede 100644 --- a/packages/create-app/templates/default-app/packages/app/src/components/Root/Root.tsx +++ b/packages/create-app/templates/default-app/packages/app/src/components/Root/Root.tsx @@ -27,6 +27,7 @@ import MenuIcon from '@material-ui/icons/Menu'; import SearchIcon from '@material-ui/icons/Search'; import { MyGroupsSidebarItem } from '@backstage/plugin-org'; import GroupIcon from '@material-ui/icons/People'; +import { NotificationsSidebarItem } from '@backstage/plugin-notifications'; const useSidebarLogoStyles = makeStyles({ root: { @@ -83,6 +84,8 @@ export const Root = ({ children }: PropsWithChildren<{}>) => ( + + } diff --git a/packages/create-app/templates/default-app/packages/backend/package.json.hbs b/packages/create-app/templates/default-app/packages/backend/package.json.hbs index 8949a2ed0d..518683ffd7 100644 --- a/packages/create-app/templates/default-app/packages/backend/package.json.hbs +++ b/packages/create-app/templates/default-app/packages/backend/package.json.hbs @@ -27,6 +27,7 @@ "@backstage/plugin-catalog-backend-module-logs": "^{{version '@backstage/plugin-catalog-backend-module-logs'}}", "@backstage/plugin-catalog-backend-module-scaffolder-entity-model": "^{{version '@backstage/plugin-catalog-backend-module-scaffolder-entity-model'}}", "@backstage/plugin-kubernetes-backend": "^{{version '@backstage/plugin-kubernetes-backend'}}", + "@backstage/plugin-notifications-backend": "^{{version '@backstage/plugin-notifications-backend'}}", "@backstage/plugin-permission-backend": "^{{version '@backstage/plugin-permission-backend'}}", "@backstage/plugin-permission-backend-module-allow-all-policy": "^{{version '@backstage/plugin-permission-backend-module-allow-all-policy'}}", "@backstage/plugin-permission-common": "^{{version '@backstage/plugin-permission-common'}}", @@ -34,11 +35,13 @@ "@backstage/plugin-proxy-backend": "^{{version '@backstage/plugin-proxy-backend'}}", "@backstage/plugin-scaffolder-backend": "^{{version '@backstage/plugin-scaffolder-backend'}}", "@backstage/plugin-scaffolder-backend-module-github": "^{{version '@backstage/plugin-scaffolder-backend-module-github'}}", + "@backstage/plugin-scaffolder-backend-module-notifications": "^{{version '@backstage/plugin-scaffolder-backend-module-notifications'}}", "@backstage/plugin-search-backend": "^{{version '@backstage/plugin-search-backend'}}", "@backstage/plugin-search-backend-module-catalog": "^{{version '@backstage/plugin-search-backend-module-catalog'}}", "@backstage/plugin-search-backend-module-pg": "^{{version '@backstage/plugin-search-backend-module-pg'}}", "@backstage/plugin-search-backend-module-techdocs": "^{{version '@backstage/plugin-search-backend-module-techdocs'}}", "@backstage/plugin-search-backend-node": "^{{version '@backstage/plugin-search-backend-node'}}", + "@backstage/plugin-signals-backend": "^{{version '@backstage/plugin-signals-backend'}}", "@backstage/plugin-techdocs-backend": "^{{version '@backstage/plugin-techdocs-backend'}}", "app": "link:../app", "better-sqlite3": "^12.0.0", diff --git a/packages/create-app/templates/default-app/packages/backend/src/index.ts b/packages/create-app/templates/default-app/packages/backend/src/index.ts index 6d0a6f4ca5..8cb615b1fd 100644 --- a/packages/create-app/templates/default-app/packages/backend/src/index.ts +++ b/packages/create-app/templates/default-app/packages/backend/src/index.ts @@ -12,8 +12,13 @@ const backend = createBackend(); backend.add(import('@backstage/plugin-app-backend')); backend.add(import('@backstage/plugin-proxy-backend')); + +// scaffolder plugin backend.add(import('@backstage/plugin-scaffolder-backend')); backend.add(import('@backstage/plugin-scaffolder-backend-module-github')); +backend.add(import('@backstage/plugin-scaffolder-backend-module-notifications')); + +// techdocs plugin backend.add(import('@backstage/plugin-techdocs-backend')); // auth plugin @@ -49,7 +54,11 @@ backend.add(import('@backstage/plugin-search-backend-module-pg')); backend.add(import('@backstage/plugin-search-backend-module-catalog')); backend.add(import('@backstage/plugin-search-backend-module-techdocs')); -// kubernetes +// kubernetes plugin backend.add(import('@backstage/plugin-kubernetes-backend')); +// notifications and signals plugins +backend.add(import('@backstage/plugin-notifications-backend')); +backend.add(import('@backstage/plugin-signals-backend')); + backend.start(); diff --git a/packages/create-app/templates/next-app/examples/template/template.yaml b/packages/create-app/templates/next-app/examples/template/template.yaml index f04c3241c5..2a20bd45ed 100644 --- a/packages/create-app/templates/next-app/examples/template/template.yaml +++ b/packages/create-app/templates/next-app/examples/template/template.yaml @@ -65,6 +65,18 @@ spec: input: repoContentsUrl: ${{ steps['publish'].output.repoContentsUrl }} catalogInfoPath: '/catalog-info.yaml' + + # Let's notify the user that the template has completed using the Notification action + - id: notify + name: Notify + action: notification:send + input: + recipients: entity + entityRefs: + - user:default/guest + title: 'Template executed' + info: 'Your template has been executed' + severity: 'normal' # Outputs are displayed to the user after a successful execution of the template. output: diff --git a/packages/create-app/templates/next-app/packages/app/package.json.hbs b/packages/create-app/templates/next-app/packages/app/package.json.hbs index 51a8956263..eb214e4ad1 100644 --- a/packages/create-app/templates/next-app/packages/app/package.json.hbs +++ b/packages/create-app/templates/next-app/packages/app/package.json.hbs @@ -19,9 +19,11 @@ "@backstage/frontend-defaults": "^{{ version '@backstage/frontend-defaults'}}", "@backstage/frontend-plugin-api": "^{{ version '@backstage/frontend-plugin-api'}}", "@backstage/plugin-catalog": "^{{ version '@backstage/plugin-catalog'}}", + "@backstage/plugin-notifications": "^{{ version '@backstage/plugin-notifications'}}", "@backstage/plugin-org": "^{{ version '@backstage/plugin-org'}}", "@backstage/plugin-scaffolder": "^{{ version '@backstage/plugin-scaffolder'}}", "@backstage/plugin-search": "^{{ version '@backstage/plugin-search'}}", + "@backstage/plugin-signals": "^{{ version '@backstage/plugin-signals'}}", "@backstage/plugin-user-settings": "^{{ version '@backstage/plugin-user-settings'}}", "@backstage/ui": "^{{ version '@backstage/ui'}}", "@material-ui/core": "^4.12.2", diff --git a/packages/create-app/templates/next-app/packages/backend/package.json.hbs b/packages/create-app/templates/next-app/packages/backend/package.json.hbs index 42afefd3b8..483c0b7a4e 100644 --- a/packages/create-app/templates/next-app/packages/backend/package.json.hbs +++ b/packages/create-app/templates/next-app/packages/backend/package.json.hbs @@ -27,6 +27,7 @@ "@backstage/plugin-catalog-backend-module-logs": "^{{version '@backstage/plugin-catalog-backend-module-logs'}}", "@backstage/plugin-catalog-backend-module-scaffolder-entity-model": "^{{version '@backstage/plugin-catalog-backend-module-scaffolder-entity-model'}}", "@backstage/plugin-kubernetes-backend": "^{{version '@backstage/plugin-kubernetes-backend'}}", + "@backstage/plugin-notifications-backend": "^{{version '@backstage/plugin-notifications-backend'}}", "@backstage/plugin-permission-backend": "^{{version '@backstage/plugin-permission-backend'}}", "@backstage/plugin-permission-backend-module-allow-all-policy": "^{{version '@backstage/plugin-permission-backend-module-allow-all-policy'}}", "@backstage/plugin-permission-common": "^{{version '@backstage/plugin-permission-common'}}", @@ -34,11 +35,13 @@ "@backstage/plugin-proxy-backend": "^{{version '@backstage/plugin-proxy-backend'}}", "@backstage/plugin-scaffolder-backend": "^{{version '@backstage/plugin-scaffolder-backend'}}", "@backstage/plugin-scaffolder-backend-module-github": "^{{version '@backstage/plugin-scaffolder-backend-module-github'}}", + "@backstage/plugin-scaffolder-backend-module-notifications": "^{{version '@backstage/plugin-scaffolder-backend-module-notifications'}}", "@backstage/plugin-search-backend": "^{{version '@backstage/plugin-search-backend'}}", "@backstage/plugin-search-backend-module-catalog": "^{{version '@backstage/plugin-search-backend-module-catalog'}}", "@backstage/plugin-search-backend-module-pg": "^{{version '@backstage/plugin-search-backend-module-pg'}}", "@backstage/plugin-search-backend-module-techdocs": "^{{version '@backstage/plugin-search-backend-module-techdocs'}}", "@backstage/plugin-search-backend-node": "^{{version '@backstage/plugin-search-backend-node'}}", + "@backstage/plugin-signals-backend": "^{{version '@backstage/plugin-signals-backend'}}", "@backstage/plugin-techdocs-backend": "^{{version '@backstage/plugin-techdocs-backend'}}", "app": "link:../app", "better-sqlite3": "^9.0.0", diff --git a/packages/create-app/templates/next-app/packages/backend/src/index.ts b/packages/create-app/templates/next-app/packages/backend/src/index.ts index 6d0a6f4ca5..8cb615b1fd 100644 --- a/packages/create-app/templates/next-app/packages/backend/src/index.ts +++ b/packages/create-app/templates/next-app/packages/backend/src/index.ts @@ -12,8 +12,13 @@ const backend = createBackend(); backend.add(import('@backstage/plugin-app-backend')); backend.add(import('@backstage/plugin-proxy-backend')); + +// scaffolder plugin backend.add(import('@backstage/plugin-scaffolder-backend')); backend.add(import('@backstage/plugin-scaffolder-backend-module-github')); +backend.add(import('@backstage/plugin-scaffolder-backend-module-notifications')); + +// techdocs plugin backend.add(import('@backstage/plugin-techdocs-backend')); // auth plugin @@ -49,7 +54,11 @@ backend.add(import('@backstage/plugin-search-backend-module-pg')); backend.add(import('@backstage/plugin-search-backend-module-catalog')); backend.add(import('@backstage/plugin-search-backend-module-techdocs')); -// kubernetes +// kubernetes plugin backend.add(import('@backstage/plugin-kubernetes-backend')); +// notifications and signals plugins +backend.add(import('@backstage/plugin-notifications-backend')); +backend.add(import('@backstage/plugin-signals-backend')); + backend.start(); From db58953a4fdfe2c3731d5da0d998a60d55f6cd3c Mon Sep 17 00:00:00 2001 From: Andre Wanlin Date: Fri, 15 Aug 2025 08:09:00 -0500 Subject: [PATCH 7/9] Prettier changes Signed-off-by: Andre Wanlin --- .../templates/default-app/packages/backend/src/index.ts | 4 +++- .../templates/next-app/packages/backend/src/index.ts | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/create-app/templates/default-app/packages/backend/src/index.ts b/packages/create-app/templates/default-app/packages/backend/src/index.ts index 8cb615b1fd..cc2db7c3d4 100644 --- a/packages/create-app/templates/default-app/packages/backend/src/index.ts +++ b/packages/create-app/templates/default-app/packages/backend/src/index.ts @@ -16,7 +16,9 @@ backend.add(import('@backstage/plugin-proxy-backend')); // scaffolder plugin backend.add(import('@backstage/plugin-scaffolder-backend')); backend.add(import('@backstage/plugin-scaffolder-backend-module-github')); -backend.add(import('@backstage/plugin-scaffolder-backend-module-notifications')); +backend.add( + import('@backstage/plugin-scaffolder-backend-module-notifications'), +); // techdocs plugin backend.add(import('@backstage/plugin-techdocs-backend')); diff --git a/packages/create-app/templates/next-app/packages/backend/src/index.ts b/packages/create-app/templates/next-app/packages/backend/src/index.ts index 8cb615b1fd..cc2db7c3d4 100644 --- a/packages/create-app/templates/next-app/packages/backend/src/index.ts +++ b/packages/create-app/templates/next-app/packages/backend/src/index.ts @@ -16,7 +16,9 @@ backend.add(import('@backstage/plugin-proxy-backend')); // scaffolder plugin backend.add(import('@backstage/plugin-scaffolder-backend')); backend.add(import('@backstage/plugin-scaffolder-backend-module-github')); -backend.add(import('@backstage/plugin-scaffolder-backend-module-notifications')); +backend.add( + import('@backstage/plugin-scaffolder-backend-module-notifications'), +); // techdocs plugin backend.add(import('@backstage/plugin-techdocs-backend')); From c371f6fe12371de31dca537510e6653e287cdc2e Mon Sep 17 00:00:00 2001 From: benjdlambert Date: Fri, 15 Aug 2025 15:33:07 +0200 Subject: [PATCH 8/9] chore: dont log splat values in logger Signed-off-by: benjdlambert --- .../scaffolder-backend/src/scaffolder/tasks/logger.ts | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/logger.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/logger.ts index a8e844c827..da309b37ff 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/logger.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/logger.ts @@ -61,23 +61,22 @@ export class BackstageLoggerTransport extends Transport { const message = info[MESSAGE]; const level = info[LEVEL]; - const splat = info[SPLAT]; switch (level) { case 'error': - this.backstageLogger.error(String(message), ...splat); + this.backstageLogger.error(String(message)); break; case 'warn': - this.backstageLogger.warn(String(message), ...splat); + this.backstageLogger.warn(String(message)); break; case 'info': - this.backstageLogger.info(String(message), ...splat); + this.backstageLogger.info(String(message)); break; case 'debug': - this.backstageLogger.debug(String(message), ...splat); + this.backstageLogger.debug(String(message)); break; default: - this.backstageLogger.info(String(message), ...splat); + this.backstageLogger.info(String(message)); } this.taskContext.emitLog(message, { stepId: this.stepId }); From b817c1d3720610b28f2b673c691632c16916de64 Mon Sep 17 00:00:00 2001 From: benjdlambert Date: Fri, 15 Aug 2025 16:01:56 +0200 Subject: [PATCH 9/9] chore: fix logging twice Signed-off-by: benjdlambert --- .changeset/twelve-ants-knock.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/twelve-ants-knock.md diff --git a/.changeset/twelve-ants-knock.md b/.changeset/twelve-ants-knock.md new file mode 100644 index 0000000000..434a2b4059 --- /dev/null +++ b/.changeset/twelve-ants-knock.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend': patch +--- + +Stop logging of `SPLAT` twice in logs