diff --git a/.changeset/wise-mails-agree.md b/.changeset/wise-mails-agree.md new file mode 100644 index 0000000000..1ad08c2ef6 --- /dev/null +++ b/.changeset/wise-mails-agree.md @@ -0,0 +1,6 @@ +--- +'@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 available to use by processors. 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 = { diff --git a/docs/notifications/usage.md b/docs/notifications/usage.md index 358426dc21..94de585d21 100644 --- a/docs/notifications/usage.md +++ b/docs/notifications/usage.md @@ -210,3 +210,68 @@ 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. + +### 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: + +```ts +notificationService.send({ + recipients: { type: 'entity', entityRef: 'group/default:team-a' }, + payload: { + title: 'Notification', + description: 'Description' + metadata: { + 'backstage.io/body.markdown': ` + ### Notification + Description + `, + }, + }, +}); +``` + +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: metadata['backstage.io/body.markdown'] ?? notification.payload.description, + }); +} +``` diff --git a/plugins/notifications-backend/src/service/router.test.ts b/plugins/notifications-backend/src/service/router.test.ts index 6c74fed41c..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', }, 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 93889b206b..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 = { @@ -52,6 +53,9 @@ export type NotificationPayload = { topic?: string; scope?: string; icon?: string; + metadata?: { + [KMetadataKey in string]?: JsonValue; + }; }; // @public (undocumented) diff --git a/plugins/notifications-common/src/types.ts b/plugins/notifications-common/src/types.ts index 48f89cbbb4..4e49de79de 100644 --- a/plugins/notifications-common/src/types.ts +++ b/plugins/notifications-common/src/types.ts @@ -14,6 +14,8 @@ * limitations under the License. */ +import { JsonValue } from '@backstage/types'; + /** @public */ export type NotificationSeverity = 'critical' | 'high' | 'normal' | 'low'; @@ -50,6 +52,10 @@ export type NotificationPayload = { * Optional notification icon */ icon?: string; + /** + * Optional additional customizable metadata. + */ + metadata?: { [KMetadataKey in string]?: JsonValue }; }; /** @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