Skip to content

Commit

Permalink
Merge pull request #1303 from bbc/upstream/server-side-notifications
Browse files Browse the repository at this point in the history
feat: Server-side notifications #1193
  • Loading branch information
jstarpl authored Dec 11, 2024
2 parents 34d3f91 + b628877 commit 31d72ff
Show file tree
Hide file tree
Showing 47 changed files with 1,915 additions and 172 deletions.
17 changes: 17 additions & 0 deletions meteor/server/api/__tests__/cleanup.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,13 @@ import {
TranslationsBundles,
PackageContainerStatuses,
TimelineDatastore,
Notifications,
SofieIngestDataCache,
} from '../../collections'
import { Collections } from '../../collections/lib'
import { generateTranslationBundleOriginId } from '../translationsBundles'
import { CollectionCleanupResult } from '@sofie-automation/meteor-lib/dist/api/system'
import { DBNotificationTargetType } from '@sofie-automation/corelib/dist/dataModel/Notifications'

describe('Cleanup', () => {
let env: DefaultEnvironment
Expand Down Expand Up @@ -445,6 +447,21 @@ async function setDefaultDatatoDB(env: DefaultEnvironment, now: number) {
type: '' as any,
})

await Notifications.insertAsync({
_id: getRandomId(),
category: '',
created: now,
localId: '',
message: {} as any,
severity: 0 as any,
modified: now,
relatedTo: {
type: DBNotificationTargetType.RUNDOWN,
studioId,
rundownId,
},
})

// Ensure that we have added one of everything:
for (const [collectionName, collection] of Collections.entries()) {
if (
Expand Down
30 changes: 30 additions & 0 deletions meteor/server/api/cleanup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,11 +69,13 @@ import {
UserActionsLog,
Workers,
WorkerThreadStatuses,
Notifications,
SofieIngestDataCache,
} from '../collections'
import { AsyncOnlyMongoCollection, AsyncOnlyReadOnlyMongoCollection } from '../collections/collection'
import { getCollectionKey } from '../collections/lib'
import { generateTranslationBundleOriginId } from './translationsBundles'
import { DBNotificationTargetType } from '@sofie-automation/corelib/dist/dataModel/Notifications'

/**
* If actuallyCleanup=true, cleans up old data. Otherwise just checks what old data there is
Expand Down Expand Up @@ -445,6 +447,34 @@ export async function cleanupOldDataInner(actuallyCleanup = false): Promise<Coll
addToResult(getCollectionKey(WorkerThreadStatuses), 0)
}

// Notifications
{
const rundownIds = await getAllIdsInCollection(Rundowns)
const playlistIds = await getAllIdsInCollection(RundownPlaylists)
await removeByQuery(Notifications, {
studioId: { $nin: studioIds },
$or: [
// {
// 'relatedTo.type': DBNotificationTargetType.EVERYWHERE,
// },
{
'relatedTo.type': DBNotificationTargetType.PLAYLIST,
'relatedTo.playlistId': { $nin: playlistIds },
},
{
'relatedTo.type': {
$in: [
DBNotificationTargetType.RUNDOWN,
DBNotificationTargetType.PARTINSTANCE,
DBNotificationTargetType.PIECEINSTANCE,
],
},
'relatedTo.rundownId': { $nin: rundownIds },
},
],
})
}

return result
}
async function isAllowedToRunCleanup(): Promise<string | void> {
Expand Down
2 changes: 2 additions & 0 deletions meteor/server/api/studio/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
ExpectedPackageWorkStatuses,
ExternalMessageQueue,
MediaObjects,
Notifications,
PackageContainerPackageStatuses,
PackageInfos,
PeripheralDevices,
Expand Down Expand Up @@ -113,6 +114,7 @@ async function removeStudio(context: MethodContext, studioId: StudioId): Promise
ExpectedPackageWorkStatuses.removeAsync({ studioId: studio._id }),
PackageInfos.removeAsync({ studioId: studio._id }),
PackageContainerPackageStatuses.removeAsync({ studioId: studio._id }),
Notifications.removeAsync({ 'relatedTo.studioId': studio._id }),
])
}

Expand Down
21 changes: 21 additions & 0 deletions meteor/server/collections/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import { ObserveChangesForHash } from './lib'
import { logger } from '../logging'
import { allowOnlyFields, rejectFields } from '../security/allowDeny'
import { checkUserIdHasOneOfPermissions } from '../security/auth'
import { DBNotificationObj } from '@sofie-automation/corelib/dist/dataModel/Notifications'

export * from './bucket'
export * from './packages-media'
Expand Down Expand Up @@ -88,6 +89,26 @@ registerIndex(ExternalMessageQueue, {
rundownId: 1,
})

export const Notifications = createAsyncOnlyMongoCollection<DBNotificationObj>(CollectionName.Notifications, false)
// For NotificationsModelHelper.getAllNotifications
registerIndex(Notifications, {
// @ts-expect-error nested property
'relatedTo.studioId': 1,
catgory: 1,
})
// For MeteorPubSub.notificationsForRundownPlaylist
registerIndex(Notifications, {
// @ts-expect-error nested property
'relatedTo.studioId': 1,
'relatedTo.playlistId': 1,
})
// For MeteorPubSub.notificationsForRundown
registerIndex(Notifications, {
// @ts-expect-error nested property
'relatedTo.studioId': 1,
'relatedTo.rundownId': 1,
})

export const Organizations = createAsyncOnlyMongoCollection<DBOrganization>(CollectionName.Organizations, {
async update(userId, doc, fields, _modifier) {
if (!checkUserIdHasOneOfPermissions(userId, CollectionName.Organizations, 'configure')) return false
Expand Down
35 changes: 34 additions & 1 deletion meteor/server/publications/system.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { meteorPublish } from './lib/lib'
import { MeteorPubSub } from '@sofie-automation/meteor-lib/dist/api/pubsub'
import { CoreSystem } from '../collections'
import { CoreSystem, Notifications } from '../collections'
import { RundownId, RundownPlaylistId, StudioId } from '@sofie-automation/corelib/dist/dataModel/Ids'
import { check } from 'meteor/check'
import { SYSTEM_ID } from '@sofie-automation/meteor-lib/dist/collections/CoreSystem'
import { triggerWriteAccessBecauseNoCheckNecessary } from '../security/securityVerify'

Expand All @@ -22,3 +24,34 @@ meteorPublish(MeteorPubSub.coreSystem, async function (_token: string | undefine
},
})
})

meteorPublish(MeteorPubSub.notificationsForRundown, async function (studioId: StudioId, rundownId: RundownId) {
// HACK: This should do real auth
triggerWriteAccessBecauseNoCheckNecessary()

check(studioId, String)
check(rundownId, String)

return Notifications.findWithCursor({
// Loosely match any notifications related to this rundown
'relatedTo.studioId': studioId,
'relatedTo.rundownId': rundownId,
})
})

meteorPublish(
MeteorPubSub.notificationsForRundownPlaylist,
async function (studioId: StudioId, playlistId: RundownPlaylistId) {
// HACK: This should do real auth
triggerWriteAccessBecauseNoCheckNecessary()

check(studioId, String)
check(playlistId, String)

return Notifications.findWithCursor({
// Loosely match any notifications related to this playlist
'relatedTo.studioId': studioId,
'relatedTo.playlistId': playlistId,
})
}
)
1 change: 1 addition & 0 deletions packages/corelib/src/dataModel/Collections.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export enum CollectionName {
MediaObjects = 'mediaObjects',
MediaWorkFlows = 'mediaWorkFlows',
MediaWorkFlowSteps = 'mediaWorkFlowSteps',
Notifications = 'notifications',
Organizations = 'organizations',
PartInstances = 'partInstances',
PackageInfos = 'packageInfos',
Expand Down
3 changes: 3 additions & 0 deletions packages/corelib/src/dataModel/Ids.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ export type NrcsIngestDataCacheObjId = ProtectedString<'NrcsIngestDataCacheObjId
/** A string, identifying a SofieIngestDataCacheObj */
export type SofieIngestDataCacheObjId = ProtectedString<'SofieIngestDataCacheObjId'>

/** A string, identifying a DBNotificationObj */
export type NotificationId = ProtectedString<'NotificationId'>

/** A string, identifying a Organization */
export type OrganizationId = ProtectedString<'OrganizationId'>

Expand Down
124 changes: 124 additions & 0 deletions packages/corelib/src/dataModel/Notifications.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import type { NoteSeverity } from '@sofie-automation/blueprints-integration'
import type { NotificationId, PartInstanceId, PieceInstanceId, RundownId, RundownPlaylistId, StudioId } from './Ids'
import type { ITranslatableMessage } from '../TranslatableMessage'

/**
* This describes a notification that should be shown to a user
* These can come from various sources, and are added and removed dynamically during system usage
*/
export interface DBNotificationObj {
_id: NotificationId

/**
* Used to group a certain group of notifications
* Each source of these notifications should use its own value, so that it can find and cleanup after itself when appropriate
* Typically, a method will clear all previous notifications for a category when it is called, and then possibly add new ones
* This is a technical value, not intended to be conusmed outside of the generation/update logic
*/
category: string

/**
* Unique id for this notification within the category
*/
localId: string

severity: NoteSeverity
message: ITranslatableMessage
// type: 'event' | 'persistent'

/** Description of what the notification is related to */
relatedTo: DBNotificationTarget

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

// /**
// * 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
}

export type DBNotificationTarget =
// | DBNotificationTargetEverywhere
// | DBNotificationTargetStudio
| DBNotificationTargetRundown
// | DBNotificationTargetSegment
// | DBNotificationTargetPart
// | DBNotificationTargetPiece
| DBNotificationTargetRundownPlaylist
| DBNotificationTargetPartInstance
| DBNotificationTargetPieceInstance

export enum DBNotificationTargetType {
// EVERYWHERE = 'everywhere',
// STUDIO = 'studio',
RUNDOWN = 'rundown',
// SEGMENT = 'segment',
// PART = 'part',
// PIECE = 'piece',
PLAYLIST = 'playlist',
PARTINSTANCE = 'partInstance',
PIECEINSTANCE = 'pieceInstance',
}

// export interface DBNotificationTargetEverywhere {
// type: DBNotificationTargetType.EVERYWHERE
// }

// export interface DBNotificationTargetStudio {
// type: DBNotificationTargetType.STUDIO
// studioId: StudioId
// }

export interface DBNotificationTargetRundown {
type: DBNotificationTargetType.RUNDOWN
studioId: StudioId
rundownId: RundownId
}

// export interface DBNotificationTargetSegment {
// type: DBNotificationTargetType.SEGMENT
// studioId: StudioId
// rundownId: RundownId
// segmentId: SegmentId
// }

// export interface DBNotificationTargetPart {
// type: DBNotificationTargetType.PART
// studioId: StudioId
// rundownId: RundownId
// // segmentId: SegmentId
// partId: PartId
// }

// export interface DBNotificationTargetPiece {
// type: DBNotificationTargetType.PIECE
// studioId: StudioId
// rundownId: RundownId
// // segmentId: SegmentId
// partId: PartId
// pieceId: PieceId
// }

export interface DBNotificationTargetRundownPlaylist {
type: DBNotificationTargetType.PLAYLIST
studioId: StudioId
playlistId: RundownPlaylistId
}

export interface DBNotificationTargetPartInstance {
type: DBNotificationTargetType.PARTINSTANCE
studioId: StudioId
rundownId: RundownId
partInstanceId: PartInstanceId
}

export interface DBNotificationTargetPieceInstance {
type: DBNotificationTargetType.PIECEINSTANCE
studioId: StudioId
rundownId: RundownId
partInstanceId: PartInstanceId
pieceInstanceId: PieceInstanceId
}
2 changes: 1 addition & 1 deletion packages/corelib/src/dataModel/Rundown.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ export interface Rundown {
/** Last sent storyStatus to ingestDevice (MOS) */
notifiedCurrentPlayingPartExternalId?: string

/** Holds notes (warnings / errors) thrown by the blueprints during creation, or appended after */
/** Holds notes (warnings / errors) thrown by the blueprints during creation */
notes?: Array<RundownNote>

externalId: string
Expand Down
3 changes: 3 additions & 0 deletions packages/job-worker/src/__mocks__/collection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import { RundownBaselineAdLibItem } from '@sofie-automation/corelib/dist/dataMod
import { ExternalMessageQueueObj } from '@sofie-automation/corelib/dist/dataModel/ExternalMessageQueue'
import { MediaObjects } from '@sofie-automation/corelib/dist/dataModel/MediaObjects'
import { PackageInfoDB } from '@sofie-automation/corelib/dist/dataModel/PackageInfos'
import type { DBNotificationObj } from '@sofie-automation/corelib/dist/dataModel/Notifications'

export interface CollectionOperation {
type: string
Expand Down Expand Up @@ -284,6 +285,7 @@ export function getMockCollections(): {
BucketAdLibPieces: new MockMongoCollection<BucketAdLib>(CollectionName.BucketAdLibPieces),
ExpectedMediaItems: new MockMongoCollection(CollectionName.ExpectedMediaItems),
ExpectedPlayoutItems: new MockMongoCollection<ExpectedPlayoutItem>(CollectionName.ExpectedPlayoutItems),
Notifications: new MockMongoCollection<DBNotificationObj>(CollectionName.Notifications),
SofieIngestDataCache: new MockMongoCollection<SofieIngestDataCacheObj>(CollectionName.SofieIngestDataCache),
NrcsIngestDataCache: new MockMongoCollection<NrcsIngestDataCacheObj>(CollectionName.NrcsIngestDataCache),
Parts: new MockMongoCollection<DBPart>(CollectionName.Parts),
Expand Down Expand Up @@ -341,6 +343,7 @@ export interface IMockCollections {
BucketAdLibPieces: MockMongoCollection<BucketAdLib>
ExpectedMediaItems: MockMongoCollection<ExpectedMediaItem>
ExpectedPlayoutItems: MockMongoCollection<ExpectedPlayoutItem>
Notifications: MockMongoCollection<DBNotificationObj>
SofieIngestDataCache: MockMongoCollection<SofieIngestDataCacheObj>
NrcsIngestDataCache: MockMongoCollection<NrcsIngestDataCacheObj>
Parts: MockMongoCollection<DBPart>
Expand Down
3 changes: 0 additions & 3 deletions packages/job-worker/src/blueprints/context/CommonContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,6 @@ export interface ContextInfo {
/** Full identifier info for the context. Should be able to identify the rundown/studio/blueprint etc being executed */
identifier: string
}
export interface UserContextInfo extends ContextInfo {
tempSendUserNotesIntoBlackHole?: boolean // TODO-CONTEXT remove this
}

/** Common */

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@ import { DBRundownPlaylist } from '@sofie-automation/corelib/dist/dataModel/Rund
import { WatchedPackagesHelper } from './watchedPackages'
import { JobContext, ProcessedShowStyleCompound } from '../../jobs'
import { ReadonlyObjectDeep } from 'type-fest/source/readonly-deep'
import { UserContextInfo } from './CommonContext'
import { ContextInfo } from './CommonContext'
import { ShowStyleUserContext } from './ShowStyleUserContext'
import { convertRundownPlaylistToBlueprints } from './lib'

export class GetRundownContext extends ShowStyleUserContext implements IGetRundownContext {
private cachedPlaylistsInStudio: Promise<Readonly<IBlueprintRundownPlaylist>[]> | undefined

constructor(
contextInfo: UserContextInfo,
contextInfo: ContextInfo,
context: JobContext,
showStyleCompound: ReadonlyDeep<ProcessedShowStyleCompound>,
watchedPackages: WatchedPackagesHelper,
Expand Down
Loading

0 comments on commit 31d72ff

Please sign in to comment.