Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,16 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
- Return the full HTTP response from the REST notification handler.
Note: With outbox enabled (default), the application's `await notify()` resolves when
the message is queued; the return value is only available when `outbox: false`.
- Support for defining notification types via CDS `@notification` annotations as an alternative to `srv/notification-types.json`.
- Support for email delivery channels via `@notification.deliveryChannels` in CDS annotations and `DeliveryChannels` in the JSON format.
- Support for email templates via `@notification.template.email.subject` and `@notification.template.email.html` in CDS annotations, and `EmailSubject` / `EmailHtml` in JSON templates.
- i18n support for CDS annotation string values using `{i18n>key}` syntax.
- Notification types are automatically registered and kept in sync with the notification service on application startup when running in hybrid or production mode.

### Fixed

- Fixed `#EnumValue` enum references in `@notification.deliveryChannels` — the `{ "#": "..." }` CSN form produced by the CDS compiler was not handled by `resolveEnum`, causing a `TypeError` at runtime.
- Improved error messages when notification type registration fails, now surfacing the ANS error detail instead of a raw HTTP error dump.
- New default `auto` for `cds.env.requires.notifications.authenticationIdentifier`. Each recipient is inspected: UUID values are published with `GlobalUserId`, everything else with `RecipientId`, with a warning when a value is neither a UUID nor an email. The previous values `UserUUID` and `RecipientId` are still supported for an explicit choice.
In practice this means the plugin "just works" without configuration: applications can pass emails, UUIDs, or a mix of both, and the correct recipient key is chosen per value — no upfront configuration about Work Zone's authentication identifier required.

Expand Down
196 changes: 152 additions & 44 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

# Notifications Plugin

The `@cap-js/notifications` package is a [CDS plugin](https://cap.cloud.sap/docs/node.js/cds-plugins#cds-plugin-packages) that provides support for publishing business notifications in SAP Build WorkZone.
The `@cap-js/notifications` package is a [CDS plugin](https://cap.cloud.sap/docs/node.js/cds-plugins#cds-plugin-packages) that provides support for publishing business notifications in SAP Build Work Zone and by email.

### Table of Contents

Expand All @@ -17,6 +17,7 @@ The `@cap-js/notifications` package is a [CDS plugin](https://cap.cloud.sap/docs
- [Code of Conduct](#code-of-conduct)
- [Licensing](#licensing)


## Setup

To enable notifications, simply add this self-configuring plugin package to your project:
Expand All @@ -25,68 +26,170 @@ To enable notifications, simply add this self-configuring plugin package to your
npm add @cap-js/notifications
```

In this guide, we use the [Incidents Management reference sample app](https://github.com/cap-js/incidents-app) as the base, to publish notifications.
In this guide, we use the [Bookshop reference sample app](https://github.com/capire/bookshop) as the basis for publishing notifications.


## Send Notifications

With that you can use the NotificationService as any other CAP Service like so in you event handlers:

```js
const alert = await cds.connect.to('notifications');
const alert = await cds.connect.to('notifications')
```

You can use the following signature to send the simple notification with title and description

```js
alert.notify({
recipients: [ ...supporters() ],
recipients: [ ...readers() ],
priority: "HIGH",
title: "New high priority incident is assigned to you!",
description: "Incident titled 'Engine overheating' created by 'customer X' with priority high is assigned to you!"
});
title: "New book arrived!",
description: "Book 'Wuthering Heights' has been added to the catalogue."
})
```

> **Note:** The simple API supports only: `recipients`, `priority`, `title`, and `description`. For advanced properties like `ActorId`, `NavigationTargetObject`, `TargetParameters`, etc., use a [named notification type](#use-notification-types) or the [low-level API](#low-level-notifications-api).
* **priority** - Priority of the notification, this argument is optional, it defaults to NEUTRAL
* **description** - Subtitle for the notification, this argument is optional


## Use Notification Types

The plugin supports two ways to define notification types. These can be combined with types from both sources are merged at startup.

### 1. Add notification types

If you want to send custom notifications in your application, you can add the notification types in the `srv/notification-types.json` file.
#### Option A: CDS Annotations

The recommended approach is to annotate CDS events directly in your service model. The plugin discovers these at startup and registers them as notification types automatically. No separate file is needed.

Define events in your `srv/` model and annotate them with `@notification`:

```cds
@description: 'Sent when a book is ordered'
@notification: {
template: {
title : 'Book {{title}} Ordered',
publicTitle : 'Book Ordered',
subtitle : '{{buyer}} ordered {{title}}',
groupedTitle : 'Bookshop Updates'
}
}
@Common.SemanticObject: 'Books'
@Common.SemanticObjectAction: 'display'
event BookOrdered {
title : String;
buyer : String;
}
```

Any event with at least one `@notification` annotation (the bare `@notification` flag or any `@notification.*` property) is picked up as a notification type. The notification type key is derived from the event name. Namespace prefixes are stripped, so `my.bookshop.BookOrdered` becomes `BookOrdered`.

> **Note:** The plugin automatically injects a `recipients` element into every notification event at model-load time, no need to declare it yourself.
The following annotations are supported:

| Annotation | Notification field |
|---|---|
| `@description` | `Description` |
| `@notification.template.title` | `TemplateSensitive` |
| `@notification.template.publicTitle` | `TemplatePublic` |
| `@notification.template.subtitle` | `Subtitle` |
| `@notification.template.groupedTitle` | `TemplateGrouped` |
| `@notification.template.email.subject` | `EmailSubject` |
| `@notification.template.email.html` | `EmailHtml` |
| `@Common.SemanticObject` | `NavigationTargetObject` |
| `@Common.SemanticObjectAction` | `NavigationTargetAction` |

Sample: If you want to send the notification when the incident is resolved, you can modify the `srv/notification-types.json` as below:
Annotation values support `{i18n>key}` syntax. Keys are resolved against your project's `_i18n/i18n.properties` English labels at startup:

```cds
@notification.template.title: '{i18n>BOOK_ORDERED_TITLE}'
@notification.template.subtitle: '{i18n>BOOK_ORDERED_SUBTITLE}'
event BookOrdered { ... }
```

**Build integration:** Running `cds build` also processes `@notification`-annotated events and writes a merged `notification-types.json` to the build output. This file combines types derived from your CDS annotations with any types defined in the JSON file.

#### Option B: JSON file

As an alternative (or in addition) to CDS annotations, you can define types statically in `srv/notification-types.json`:

```json
[
[
{
"NotificationTypeKey": "BookOrdered",
"NotificationTypeVersion": "1",
"Templates": [
{
"Language": "en",
"TemplatePublic": "Book Ordered",
"TemplateSensitive": "Book '{{title}}' Ordered",
"TemplateGrouped": "Bookshop Updates",
"TemplateLanguage": "mustache",
"Subtitle": "{{buyer}} ordered {{title}}."
}
]
}
]
```

#### Email Delivery

To enable email delivery for a notification type, add `deliveryChannels` and email template fields. Both definition approaches support this.

**Via CDS annotations:**

```cds
@notification: {
template: {
title : 'Book {{title}} Ordered',
publicTitle : 'Book Ordered',
subtitle : '{{buyer}} ordered {{title}}',
groupedTitle : 'Bookshop Updates',
email: {
subject: 'Your order: {{title}}',
html : '<p>Thanks for ordering <b>{{title}}</b>!</p>'
}
},
deliveryChannels: [{ channel: #Mail, enabled: true, defaultPreference: true, editablePreference: true }]
}
event BookOrdered { ... }
```

**Via JSON:**

```json
{
"NotificationTypeKey": "BookOrdered",
"Templates": [
{
"NotificationTypeKey": "IncidentResolved",
"NotificationTypeVersion": "1",
"Templates": [
{
"Language": "en",
"TemplatePublic": "Incident Resolved",
"TemplateSensitive": "Incident '{{title}}' Resolved",
"TemplateGrouped": "Incident Status Update",
"TemplateLanguage": "mustache",
"Subtitle": "Incident from '{{customer}}' resolved by {{user}}."
}
]
"Language": "en",
"TemplatePublic": "Book Ordered",
"TemplateSensitive": "Book '{{title}}' Ordered",
"TemplateGrouped": "Bookshop Updates",
"TemplateLanguage": "mustache",
"EmailSubject": "Your order: {{title}}",
"EmailHtml": "<p>Thanks for ordering <b>{{title}}</b>!</p>"
}
],
"DeliveryChannels": [
{ "Type": "MAIL", "Enabled": true, "DefaultPreference": true, "EditablePreference": true }
]
}
```

> **Note:** Email delivery requires the SAP Alert Notification service with the `business-notifications` plan and a corresponding BTP destination. The `business-notifications` plan enforces that `TemplatePublic` and `TemplateGrouped` are set on all notification types (including those without email).
### 2. Use pre-defined types in your code like that:

```js
await alert.notify ('IncidentResolved', {
recipients: [ customer.id ],
await alert.notify ('BookOrdered', {
recipients: [ buyer.id ],
data: {
customer: customer.info,
title: incident.title,
user: cds.context.user.id,
title: book.title,
buyer: buyer.name,
}
})
```
Expand All @@ -98,24 +201,28 @@ Sample: If you want to send the notification when the incident is resolved, you
* **priority** - Priority of the notification, this argument is optional, it defaults to NEUTRAL
* **data** - A key-value pair that is used to fill a placeholder of the notification type template, this argument is optional


## Test-drive Locally
In local environment, when you publish notification, it is mocked to publish the nofication to the console.

<img width="700" alt="Notify to console" style="border-radius:0.5rem" src="_assets/notifyToConsole.png">


## Run in Production

#### Notification Destination

As a pre-requisite to publish the notification, you need to have a [destination](https://help.sap.com/docs/build-work-zone-standard-edition/sap-build-work-zone-standard-edition/enabling-notifications-for-custom-apps-on-sap-btp-cloud-foundry#configure-the-destination-to-the-notifications-service) configured to publish the notification. The plugin is pre-configured to use destination name `SAP_Notifications` by default for hybrid and production environments. You can override this in your
application's CDS configuration if needed (see Advanced Usage section below).
As a pre-requisite to publish the notification, you need to have a [destination](https://help.sap.com/docs/build-work-zone-standard-edition/sap-build-work-zone-standard-edition/enabling-notifications-for-custom-apps-on-sap-btp-cloud-foundry#configure-the-destination-to-the-notifications-service) configured to publish the notification. The plugin is pre-configured to use destination name `SAP_Notifications` by default for hybrid and production environments. You can override this in your application's CDS configuration if needed (see [Advanced Usage](#advanced-usage) section below).

#### Integrate with SAP Build Work Zone

Once application is deployed and integrated with SAP Build Work Zone, you can see the notification under fiori notifications icon!
Once application is deployed and integrated with SAP Build Work Zone, you can see the notification under Fiori notifications icon!

<img width="1300" alt="Sample Application Demo" style="border-radius:0.5rem;" src="_assets/incidentsNotificationDemo.gif">

#### Notification Type Registration

Notification types are automatically registered and synced with the notification service each time the application starts in hybrid or production mode. No manual `cds build` or content deployment step is required as any additions, changes, or removals to your notification types (whether defined via CDS annotations or JSON) are applied on the next startup.


## Advanced Usage
Expand All @@ -134,7 +241,7 @@ To make notification types unique to the application, prefix is added to the typ

- `auto` (default): the recipient key is chosen per recipient. Values matching the UUID format are published with `GlobalUserId`, everything else with `RecipientId`. If a value is neither a UUID nor an email a warning is logged. This allows mixing UUIDs and emails in the same `recipients` array without additional configuration.
- `UserUUID`: always publish with `GlobalUserId`. Use this when the authentication identifier in Work Zone is set to `User ID`.
- `RecipientId`: always publish with `RecipientId`. Use this when you want to enforce email recipients.
- `RecipientId`: always publish with `RecipientId`. Use this when recipients are identified by email or login name.

Note, that in order for E-Mail Notifications to be sent for notifications published with a User ID, a destination to the IDS needs to be configured for the lookup of the corresponding email address.

Expand All @@ -150,13 +257,12 @@ By using this approach you can send notifications with the predefined parameters

```js
alert.notify({
recipients: [...supporters()],
type: "IncidentResolved",
recipients: [...readers()],
type: "BookOrdered",
priority: 'NEUTRAL',
data: {
customer: customer.info,
title: incident.title,
user: cds.context.user.id,
title: book.title,
buyer: buyer.name,
},
OriginId: "Example Origin Id",
NotificationTypeVersion: "1",
Expand All @@ -171,7 +277,7 @@ alert.notify({
"Value": "string"
}
]
});
})
```

#### Passing the whole notification object
Expand All @@ -180,37 +286,39 @@ By using this approach you need to pass the whole notification object as describ

```js
alert.notify({
NotificationTypeKey: 'IncidentCreated',
NotificationTypeKey: 'BookOrdered',
NotificationTypeVersion: '1',
Priority: 'NEUTRAL',
Properties: [
{
Key: 'name',
Key: 'title',
IsSensitive: false,
Language: 'en',
Value: 'Engine overheating',
Value: 'Wuthering Heights',
Type: 'String'
},
{
Key: 'customer',
Key: 'buyer',
IsSensitive: false,
Language: 'en',
Value: 'Dave',
Value: 'reader@bookshop.com',
Type: 'String'
}
],
Recipients: [{ RecipientId: "supportuser1@mycompany.com" },{ RecipientId: "supportuser2@mycompany.com" }]
});
Recipients: [{ RecipientId: "reader1@bookshop.com" },{ RecipientId: "reader2@bookshop.com" }]
})
```

## Contributing

This project is open to feature requests/suggestions, bug reports etc. via [GitHub issues](https://github.com/cap-js/change-tracking/issues). Contribution and feedback are encouraged and always welcome. For more information about how to contribute, the project structure, as well as additional contribution information, see our [Contribution Guidelines](CONTRIBUTING.md).


## Code of Conduct

We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone. By participating in this project, you agree to abide by its [Code of Conduct](CODE_OF_CONDUCT.md) at all times.


## Licensing

Copyright 2023 SAP SE or an SAP affiliate company and contributors. Please see our [LICENSE](LICENSE) for copyright and license information. Detailed information including third-party components and their licensing/copyright information is available [via the REUSE tool](https://api.reuse.software/info/github.com/cap-js/change-tracking).
Loading
Loading