nrkno / sofie-core

Sofie Core: A Part of the Sofie TV Studio Automation System
https://github.com/nrkno/Sofie-TV-automation/
MIT License
125 stars 38 forks source link

RFC: Server-side notifications #1193

Open olzzon opened 4 months ago

olzzon commented 4 months ago

About me

This RFC is opened on behalf of BBC

Use case

Using the AB playback feature with 3 playout channels in the pool. In the AB pool, the channel A is currently playing out a video. Channel B has the next video queued. Channel C is playing out another video (due to an adlib).

Now, the user disables the channel B, because of a hardware fault.

The AB pool is now not able to fulfill its purpose, and needs a way to notify the user of this right away (ie not have to wait for the next Take and fail then) at the moment only messaging to logging is possible.

Proposal

I propose to have a Mongo collection that can be used to store notifications.

Then Clients can Subscribe to their relevant notifications from all over the system and display them in their GUIs.

Also an “audience” concept is introduced, this way it’s possible to target messages. So it’s possible to control what client’s receives what messages. This can be handy e.g. when using the rest API.

I’d propose we have a workshop to outline the details of this, so that we have a common understanding for my eventual implementation of this.

Sketch implementation:

// Johan thinks that there are 2 importants things to consider
// 1. A Client should be able to narrow what to listen for
//    audience, which rundown/playlist/studio/system
// 2. Be able to pipe/convert in the exisitng notification center

enum NotificationAudience
  'everyone' 
  'logging'
  'producer' 
  'audio' 
  'prompter' 
  .....  
}
// Stored into the DB
export interface NotificationObj extends TrackedNote {
    _id: string
    /** Defines who the notification is intended for */
    audience?: NotificationAudience[] // A nice-to-have idea

    type: NoteSeverity // eg WARNING , ERRROR, INFO 
    message: ITranslatableMessage

    origin?: {
        name: string
    }
    timestamp?: Date // TBD
    autoTimeout?: Date // TBD   
}

export interface TrackedNote { // This already exists in Sofie Core
    rank: number
    origin: {
        name: string
        segmentName?: string
        rundownId?: RundownId
        segmentId?: SegmentId
        partId?: PartId
        pieceId?: PieceId
    }
}

Process

The Sofie Team will evaluate this RFC and open up a discussion about it, usually within a week.

nytamin commented 4 months ago

Thanks for this PR! Agreed, let's do a workshop to iron out a plan fpr the implementation. I've sent invites for Thusday, May 30th at 13:00 CEST, if anyone else would like to join the discussion, please email me at johan.nyman@nrk.no.

nytamin commented 4 months ago

Here are the notes from today's workshop


// Notes from the workshop
// (in pseudo-code)

/*
    There are various places in Sofie that would benefit from this, one example is the userLogContext in Blueprints.
*/

// Johan thinks that there are 2 importants things to consider
// 1. A Client should be able to narrow what to listen for
//    audience, which rundown/playlist/studio/system
// 2. Be able to pipe/convert in the exisitng notification center

// The object stored into the DB
export interface NotificationObj {
    _id: string

    severity: NoteSeverity // eg WARNING , ERROR, INFO
    message: ITranslatableMessage
    type: 'event' | 'persistent'

    /** Description of what the notification is related to */
    relatedTo: {
        studio: StudioId
        rundown: RundownId
        // segment: SegmentId
        part: PartId
        piece: PieceId
    } |
    {
        studio: StudioId,
        rundown: RundownId
        // segment: SegmentId
        part: PartId
    } |
    {
        studio: StudioId,
        rundown: RundownId
        segment: SegmentId
    } |
    {
        studio: StudioId,
        rundown: RundownId
    } |
    {
        studio: StudioId,
        playlist: RundownPlaylistId,
    } |
    {
        studio: StudioId,
        playlist: RundownPlaylistId,
        partInstance: PartInstanceId
    } |
    {
        studio: StudioId,
        playlist: RundownPlaylistId,
        partInstance: PartInstanceId
        pieceInstance: PieceInstanceId
    } |
    {
        studio: StudioId,
    } | {
        // Will be sent to everyone
        everywhere: true
    }

    created: number // unix timestamp
    modified: number // unix timestamp

    /**
     * Used to group a certain group of notifications
     */
    className?: string

    /**
     * When set, the notification will be automatically dismissed after this time
     * For events, this is typically set to less than a minute
     * For persistent notifications, this is never set
     */
    autoTimeout?: number // unix timestamp
}

function getUniqueNotificationId (notification: Pick<NotificationObj, 'relatedTo'|'className'>, thingName: string): string {

    return hash(`${notification.className}_${thingName}_${notification.relatedTo.studio}_${notification.relatedTo.rundown}_${notification.relatedTo.segment}_${notification.relatedTo.part}_${notification.relatedTo.piece}`)
}

function evaluateABStuff() {
    const className = 'abPicker'
    const notifications = getNotificationsWithClassName(className)

    for (... of ...){
        const notificationId = getUniqueNotificationId({}, 'abPickerError')
        try {
            // Do stuff here
            Try to find file:
            No file error

            File is being copied:
            setNotification({
                _id: notificationId,
                className,
                message: "File is flying in now"
            })

            clearNotification(notificationId)
        } catch (e) {
            // No file:
            setNotification({
                _id: notificationId,
                message: msgFromError(e)
            })
        }
    }

    clearUnusedNotifications(unusedNotifications)
}

// -----------------------------------------------------------------------------------
// ------------------ Copies from Core -----------------------------------------------
export enum NoteSeverity { // from blueprint-integration
    WARNING = 1,
    ERROR = 2,
    INFO = 3,
}

export interface TrackedNote { // This already exists in Sofie Core
    rank: number
    origin: {
        name: string
        segmentName?: string
        rundownId?: RundownId
        segmentId?: SegmentId
        partId?: PartId
        pieceId?: PieceId
    }
}