finos / FDC3

An open standard for the financial desktop.
https://fdc3.finos.org
Other
202 stars 132 forks source link

Actionable Notifications via Intent & Context #828

Open kriswest opened 2 years ago

kriswest commented 2 years ago

This issue is a proposal to resolve:

via Intents and contexts, rather than proliferating new API calls within the Desktop Agent scope. This approach has the advantage that it might be implemented by a desktop agent (with optional rules in its resolver to router the intents to its internal handling) or by another app in the desktop (e.g. a custom Notification center implementation) at the discretion of the DesktopAgent provider / assembler.

The proposal draws inspiration from the Notifications Web API (see MDN and the Notification API Spec), but deviates from it in a few key areas. The notifications Web API allows for actionable notifications but they are performed by the application that generated the notification, which will use the data element and the action name to determine what action to take.

Whereas in FDC3 we are focused on application interoperability and our primary use case is to allow actions to be performed by other apps, through intents and contexts. Hence, the actions array is specified via the fdc3.action type (currently proposed in PR #779) which encapsulates an FDC3 context and an optional intent - allowing a range of actions with associated data to be specified, which can then be performed by other applications when sent to them via raiseIntent or raiseIntentForContext. This obviates the need for a data element in the notification.

Enhancement Request

Allow notifications to be raised via a standardized intent and context.

Use Case:

Actionable notifications are a common part of a suite of desktop applications. They are tied to application interop by the fact that they may be generated by one application or system but could or should be actioned via one or more other applications.

For example: For example, An order management system (or a service monitoring one) might raise notifications for new orders, and offer you actions to ViewChart, ViewRisk or ViewHoldings via other applications.

Intents

There are several possible Intents that could be associated with notifications. The primary need for one is to create a notification, but you might also want to dismiss it (if actioned independently in the source app), subscribe to updates as it changes state or subscribe to a (filtered) stream of notifications.

CreateNotification

Create a new notification using the supplied notification context.

UpdateNotification

Update or dismiss a particular notification, by passing an updated notification context with the id set.

GetNotifications

Retrieve a stream of notifications or updates about notifications matching a filter. Can be used to retrieve a historical notification and receive details of any updates to it, or to subscribe to a filtered stream of notifications. Would require a context type describing a filter for notifications.

Contexts

fdc3.notification

To define the content of a notification and any options relating to how it should be handled or displayed. May include actions that would be performed via FDC3 APIs (primarily raiseIntent, but could also be used to broadcast context on a channel.

Draws inspiration from the Notification web API:

Details
Property Type Required Example Value
type string Yes 'fdc3.notification'
id.notificationId string No 'unique-value-123'
title string Yes 'Notification title displayed at top'
options object No see below
options.body string No "Text content of the notification displayed in its body"
options.icon string No https://www.example.com/example.png
options.image string No https://www.example.com/example.png
options.notificationType string No warning
options.actions array No see below
options.notificationAlertSound string No https://www.example.com/example.mp3
metadata object No see below
metadata.issuedAt datetime No "2022-03-30T15:44:44Z"
metadata.receivedAt datetime No "2022-03-30T15:44:44Z"
metadata.source AppIdentifier No { appId: "ABC", instanceId: "ABC123" }
metadata.timeout number No 600000
metadata.isRead boolean No false
metadata.isMuted boolean No false
metadata.isSnoozed boolean No false
metadata.isDeleted boolean No false

Examples

const notification = {
    type: 'fdc3.notification',

    /**
     * @optional
     * Identifiers for a notification. Might not be set when submitting a notification and applied by the
     * receiving system. Multiple identifiers might be used to link the notification to other systems that
     * generate or interact with it.
     */
    id: {
        notificationId: "uniqueIdentifier"
    },

    /**
     * @required
     * Defines a title for the notification, which is typically shown at the top of the notification window.
     */
    title: "string",

    /**
     * An options object containing any custom settings that you want to apply to the notification.
     * @see https://developer.mozilla.org/en-US/docs/Web/API/Notification/Notification
     */
    options: {
        /**
         * A string representing the body text of the notification, which is displayed below the title.
         */
        body: "Body content",

        /**
         * A string containing the URL of an icon to be displayed in the notification to attribute it.
         * If not specified a desktop agent might harvest this from the submitting applications
         * appD config.
         */
        icon: "https://...",

        /**
         * A string containing the URL of an image to be displayed in the body of the notification.
         * If not specified, this might be drawn from a templated notificationType (see below).
         */
        image: "https://...",

        /**
         * @optional
         * A classification for the notification that might be used to apply different templates and/or
         * filters to notifications.
         * Could use enumerated types: info | success | warning | error | debug
         * or free text...
         */
        notificationType: "warning",

        /**
         * A list of Actions that might be performed by a user interacting with the notification.
         * This is a key part of the relevance of Notifications to FDC3 as the actions can be loosely
         * coupled to applications that resolve them by defining them via intents and contexts. Hence,
         * though inspired by the Notifications web API we *should* depart from its spec here.
         * Encoded as an array of fdc3.action contexts as defined in PR #779
         * https://github.com/finos/FDC3/pull/779/files
         */
        actions: [
            {
                type:"fdc3.action",
                title: "View Contact",
                intent: "ViewContact",
                context: {
                    type: 'fdc3.contact',
                    name: 'Jane Doe',
                    id: {
                        email: 'jane@mail.com'
                    }
                }
            },
            {
                type:"fdc3.action",
                name: "Other actions", //No intent is specified, use findIntentByContext or raiseIntentForContext to handle
                context: {
                    type: 'fdc3.contact',
                    name: 'Jane Doe',
                    id: {
                        email: 'jane@mail.com'
                    }
                }
            }
        ],

        /**
         * A string containing the URL of an alert sound to play when the notification is received.
         */
        notificationAlertSound: "*.wav",
    },

    /**
     * Additional details about the notification - intended to be populated or used by the Desktop Agent
     * or the system receiving the notification, rather than the app raising the notification.
     * Not necessary for submitting a notification - but may be useful for other use cases
     * such as subscribing to a stream of filtered notifications or updates on a specific notification.
     */
    metadata: {
        /**
         * ISO8601 date formatted string.
         * When the notification was generated.
         */
        issuedAt: "2022-03-30T15:44:44Z",

        /**
         * ISO8601 date formatted string.
         * When the notification was received.
         */
        receivedAt: "2022-03-30T15:44:44Z", 

        /**
         * AppIdentifier representing the source application that generated the notification.
         */
        source: { appId: "ABC", instanceId: "ABC123" }, 

        /**
         * How long should the notification appear in a toast UI in millisecs?
         */
        timeout: 60000,

        /* Properties relating to the state of the notification. */
        isRead: false,
        isMuted: false,
        isSnoozed: false,
        isDeleted: false,
    }
}

//perform feature detection to determine if notifications are supported
const appIntent = await fdc3.findIntent("CreateNotification");
if (appIntent.apps.length > 0) {

    //Submit a notification
    const resolution = await fdc3.raiseIntent("CreateNotification", notification);

    //receive it back updated with ids and metadata
    const submittedNotification = await resolution.getResult();

    //update the notification after an action or status change
    submittedNotification.metadata.isSnoozed = true;
    await fdc3.raiseIntent("UpdateNotification", submittedNotification);

    //retrieve a past notification and/or a stream of updates about it
    const filter = { type: "fdc3.notification.filter", id: { notificationId: "ABC123" } };
    const resolution2 = await fdc3.raiseIntent("GetNotifications", filter );
    const channel = await resolution2.getResult();
    channel.addContextListener("fdc3.notification", handlerFn);

    //retrieve a stream of notifications (and any updates) according to a filter
    const filter2 = { type: "fdc3.notification.filter", titleRegex: "(AAPL)/g", options: { type: "alert"} };
    const resolution3 = await fdc3.raiseIntent("GetNotifications", filter2 );
    const channel2 = await resolution3.getResult();
    channel2.addContextListener("fdc3.notification", handlerFn);

}

robmoffat commented 2 years ago

I really like this approach of using CD&I to augment the functionality of FDC3

lspiro-Tick42 commented 2 years ago

I really like this approach of using CD&I to augment the functionality of FDC3

And IMO it is always a mistake:) You start with an IOCTL based API with agreed message formats and eventually you move to an API with named parameters that can be checked at compile type (.Net, Java and Typescript) since this helps reduce run time errors.

However, an API can always be created to wrap the CD&I to provide the same results as only defining an API, and since the inter-agent stuff is already being specificed for CD&I defining this in terms of CD&I provides cross agent capabilities from day 1.

So I still think defining a functional helper API at this stage, with an implementation expressed in terms of Context Data and Intents would be a useful service, and would also make writing the conformance tests for Notifications easier.

gerard-ciq commented 2 years ago

What is the expected behaviour when raising a CreateNotification intent multiple times with the same id.notificationId value? Update the notification if one with that id exists?

And likewise what happens when raising an UpdateNotifcation intent without a id.notificationId value set? Create a notification if one with that id does not exist?

gerard-ciq commented 2 years ago

Is "GetNotifications" the correct naming? It's not quite returning notifications, it's returning a channel that you can use to get notifications. "GetNotificationsChannel" or "GetNotificationsStream"?

kriswest commented 2 years ago

What is the expected behaviour when raising a CreateNotification intent multiple times with the same id.notificationId value? Update the notification if one with that id exists?

And likewise what happens when raising an UpdateNotifcation intent without a id.notificationId value set? Create a notification if one with that id does not exist?

As discussed at #845 we should combine CreateNotification and UpdateNotification and have a single CreateOrUpdateNotification, then document that, to update an existing notification, you must set an id value in the notification context. We should also add that implementors SHOULD return the notification object as a result with its id value set to enable updates.

Is "GetNotifications" the correct naming? It's not quite returning notifications, it's returning a channel that you can use to get notifications. "GetNotificationsChannel" or "GetNotificationsStream"?

The original use case for channel results from intents was referred to as a 'feed'. How about GetNotificationFeed?

lspiro-Tick42 commented 2 years ago

As I have said multiple times, I think this approach is a mistake, and will eventually be replaced by an API defined by function calls.

I think having the platform provide the ID in response to a create call is less error prone than requiring the client app to allocated unique id’s. I would then specify a Create call and (if appropriate) an Update call. NB The use of API signatures, in which the Update call would take an alert id, and the comment could make it clear that this is the id provided by the create call, would make this easier to use.

Having said that, using an exchange of message blocks with well commented fields can obviously also work, since it is functionally equivalent. It’s just it is easier for developers to get wrong.

I would expect GetNotificationFeed to send me an event every time a Notification is created/changes state. This is useful functionality, but not something that most apps that just want to raise a Notification will typically run.

From: Kris West @.> Sent: 03 November 2022 13:55 To: finos/FDC3 @.> Cc: Leslie Spiro @.>; Comment @.> Subject: Re: [finos/FDC3] Actionable Notifications via Intent & Context (Issue #828)

What is the expected behaviour when raising a CreateNotification intent multiple times with the same id.notificationId value? Update the notification if one with that id exists?

And likewise what happens when raising an UpdateNotifcation intent without a id.notificationId value set? Create a notification if one with that id does not exist?

As discussed at #845https://github.com/finos/FDC3/issues/845 we should combine CreateNotification and UpdateNotification and have a single CreateOrUpdateNotification, then document that, to update an existing notification, you must set an id value in the notification context. We should also add that implementors SHOULD return the notification object as a result with its id value set to enable updates.

Is "GetNotifications" the correct naming? It's not quite returning notifications, it's returning a channel that you can use to get notifications. "GetNotificationsChannel" or "GetNotificationsStream"?

The original use case for channel results from intents was referred to as a 'feed'. How about GetNotificationFeed?

— Reply to this email directly, view it on GitHubhttps://github.com/finos/FDC3/issues/828#issuecomment-1302150783, or unsubscribehttps://github.com/notifications/unsubscribe-auth/AD4YWNKZTLOL4QNEJRBTGRLWGO72PANCNFSM6AAAAAAQ5OK2EA. You are receiving this because you commented.Message ID: @.**@.>>

kriswest commented 2 years ago

As I have said multiple times, I think this approach is a mistake, and will eventually be replaced by an API defined by function calls.

Nevertheless, it has served as an example of being able to extend FDC3 with new, optional capabilities, without requiring an update to the API - which a number of other participants seem keen to demonstrate. The difference between this proposal and a full API boils down to a function signature vs. a raised intent - the other 95% of the proposal stands I assume.

If you'd really rather this particular feature go via an API a proposal needs to be fully worked up and voted on by the Standards Working Group. It can be based on the content of this proposal, but should stand alone (i.e. import detail rather than just reference) and will have to result in a PR to add it to the docs and sources. Ultimately, the SWG will have to vote on a preferred approach. Most API calls are required (MUST) rather than recommend (SHOULD) or optional (MAY), which might color responses.

I think having the platform provide the ID in response to a create call is less error-prone than requiring the client app to allocated unique id’s. I would then specify a Create call and (if appropriate) an Update call.

That is what is intended. By providing a notification context without an ID, a new notification should be created and an ID generated by the platform. The notification is returned back with the ID added. I believe @gerard-ciq's point is that the interface for a create and an update call then end up having the same signature (it is only the data passed that is different - i.e. for update the notification will already have an ID.

NB The use of API signatures, in which the Update call would take an alert id, and the comment could make it clear that this is the id provided by the create call, would make this easier to use.

The proposal as written is for separate Create and Update calls - although I'm leaning towards a CreateOrUpdate personally... but would be happy either way. With separate APIs I guess we'd be rejecting create calls for notifications that already have ids (that have been used), and rejecting update calls for notifications without ids. I think you are suggesting breaking the ID out of the notification object to make things clearer - however, when returning the notification (with added metadata) it would see to make sense to keep the API encapsulated (making a broken-out ID redundant).

I would expect GetNotificationFeed to send me an event every time a Notification is created/changes state. This is useful functionality, but not something that most apps that just want to raise a Notification will typically run.

Agreed - and as intended, with the filter just serving to filter the feed received. For example to just a particular application (as the source of the notification) or notification type etc.. I think this has utility for apps following up on particular notifications, types of notification or for the build-out of a custom notification centre (should a firm wish to develop its own rather than use a platform's built-in one). However, it will definitely be used far less often than an API to just create a notification.

kriswest commented 2 years ago

@gerard-ciq demonstrating the proposed FDC3 intent & context based interface to notifications

https://user-images.githubusercontent.com/1701764/201643126-f8766d05-d080-42ab-86d1-e2afdd23d317.mp4

mistryvinay commented 1 year ago

Based on feedback for this particular issue during #868, next steps would be to put this forward at the next Standards Working Group meeting for validation.