Skip to content

Commit 2ac7cb1

Browse files
committed
feat: customizable package status messages
1 parent cb59dd9 commit 2ac7cb1

File tree

19 files changed

+782
-191
lines changed

19 files changed

+782
-191
lines changed

meteor/server/api/blueprints/api.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,7 @@ async function innerUploadBlueprint(
215215
newBlueprint.showStyleConfigSchema = blueprintManifest.showStyleConfigSchema
216216
newBlueprint.showStyleConfigPresets = blueprintManifest.configPresets
217217
newBlueprint.hasFixUpFunction = !!blueprintManifest.fixUpConfig
218+
newBlueprint.packageStatusMessages = blueprintManifest.packageStatusMessages
218219
} else if (blueprintManifest.blueprintType === BlueprintManifestType.STUDIO) {
219220
newBlueprint.studioConfigSchema = blueprintManifest.studioConfigSchema
220221
newBlueprint.studioConfigPresets = blueprintManifest.configPresets

meteor/server/publications/pieceContentStatusUI/__tests__/checkPieceContentStatus.test.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import { defaultStudio } from '../../../../__mocks__/defaultCollectionObjects'
3636
import { MediaObjects } from '../../../collections'
3737
import { PieceDependencies } from '../common'
3838
import { DEFAULT_MINIMUM_TAKE_SPAN } from '@sofie-automation/shared-lib/dist/core/constants'
39+
import { PieceContentStatusMessageFactory } from '../messageFactory'
3940

4041
const mockMediaObjectsCollection = MongoMock.getInnerMockCollection<MediaObject>(MediaObjects)
4142

@@ -449,7 +450,9 @@ describe('lib/mediaObjects', () => {
449450
timelineObjectsString: EmptyPieceTimelineObjectsBlob,
450451
})
451452

452-
const status1 = await checkPieceContentStatusAndDependencies(mockStudio, piece1, sourcelayer1)
453+
const messageFactory = new PieceContentStatusMessageFactory(undefined)
454+
455+
const status1 = await checkPieceContentStatusAndDependencies(mockStudio, messageFactory, piece1, sourcelayer1)
453456
expect(status1[0].status).toEqual(PieceStatusCode.OK)
454457
expect(status1[0].messages).toHaveLength(0)
455458
expect(status1[1]).toMatchObject(
@@ -460,7 +463,7 @@ describe('lib/mediaObjects', () => {
460463
})
461464
)
462465

463-
const status2 = await checkPieceContentStatusAndDependencies(mockStudio, piece2, sourcelayer1)
466+
const status2 = await checkPieceContentStatusAndDependencies(mockStudio, messageFactory, piece2, sourcelayer1)
464467
expect(status2[0].status).toEqual(PieceStatusCode.SOURCE_BROKEN)
465468
expect(status2[0].messages).toHaveLength(1)
466469
expect(status2[0].messages[0]).toMatchObject({
@@ -474,7 +477,7 @@ describe('lib/mediaObjects', () => {
474477
})
475478
)
476479

477-
const status3 = await checkPieceContentStatusAndDependencies(mockStudio, piece3, sourcelayer1)
480+
const status3 = await checkPieceContentStatusAndDependencies(mockStudio, messageFactory, piece3, sourcelayer1)
478481
expect(status3[0].status).toEqual(PieceStatusCode.SOURCE_MISSING)
479482
expect(status3[0].messages).toHaveLength(1)
480483
expect(status3[0].messages[0]).toMatchObject({

meteor/server/publications/pieceContentStatusUI/bucket/bucketContentCache.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { MongoFieldSpecifierOnesStrict } from '@sofie-automation/corelib/dist/mo
55
import { BucketAdLibAction } from '@sofie-automation/corelib/dist/dataModel/BucketAdLibAction'
66
import { BucketAdLib } from '@sofie-automation/corelib/dist/dataModel/BucketAdLibPiece'
77
import { BlueprintId, ShowStyleBaseId } from '@sofie-automation/corelib/dist/dataModel/Ids'
8+
import { Blueprint } from '@sofie-automation/corelib/dist/dataModel/Blueprint'
89

910
export interface SourceLayersDoc {
1011
_id: ShowStyleBaseId
@@ -53,10 +54,17 @@ export const showStyleBaseFieldSpecifier = literal<
5354
sourceLayersWithOverrides: 1,
5455
})
5556

57+
export type BlueprintFields = '_id' | 'packageStatusMessages'
58+
export const blueprintFieldSpecifier = literal<MongoFieldSpecifierOnesStrict<Pick<Blueprint, BlueprintFields>>>({
59+
_id: 1,
60+
packageStatusMessages: 1,
61+
})
62+
5663
export interface BucketContentCache {
5764
BucketAdLibs: ReactiveCacheCollection<Pick<BucketAdLib, BucketAdLibFields>>
5865
BucketAdLibActions: ReactiveCacheCollection<Pick<BucketAdLibAction, BucketActionFields>>
5966
ShowStyleSourceLayers: ReactiveCacheCollection<SourceLayersDoc>
67+
Blueprints: ReactiveCacheCollection<Pick<Blueprint, BlueprintFields>>
6068
}
6169

6270
export function createReactiveContentCache(): BucketContentCache {
@@ -66,6 +74,7 @@ export function createReactiveContentCache(): BucketContentCache {
6674
'bucketAdlibActions'
6775
),
6876
ShowStyleSourceLayers: new ReactiveCacheCollection<SourceLayersDoc>('sourceLayers'),
77+
Blueprints: new ReactiveCacheCollection<Pick<Blueprint, BlueprintFields>>('blueprints'),
6978
}
7079

7180
return cache

meteor/server/publications/pieceContentStatusUI/bucket/bucketContentObserver.ts

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
import { Meteor } from 'meteor/meteor'
2-
import { BucketId, ShowStyleBaseId } from '@sofie-automation/corelib/dist/dataModel/Ids'
2+
import { BlueprintId, BucketId, ShowStyleBaseId } from '@sofie-automation/corelib/dist/dataModel/Ids'
33
import { logger } from '../../../logging'
44
import {
5+
blueprintFieldSpecifier,
56
bucketActionFieldSpecifier,
67
bucketAdlibFieldSpecifier,
78
BucketContentCache,
89
ShowStyleBaseFields,
910
showStyleBaseFieldSpecifier,
1011
SourceLayersDoc,
1112
} from './bucketContentCache'
12-
import { BucketAdLibActions, BucketAdLibs, ShowStyleBases } from '../../../collections'
13+
import { Blueprints, BucketAdLibActions, BucketAdLibs, ShowStyleBases } from '../../../collections'
1314
import { DBShowStyleBase } from '@sofie-automation/corelib/dist/dataModel/ShowStyleBase'
1415
import { equivalentArrays } from '@sofie-automation/shared-lib/dist/lib/lib'
1516
import { applyAndValidateOverrides } from '@sofie-automation/corelib/dist/settings/objectWithOverrides'
@@ -33,6 +34,9 @@ export class BucketContentObserver implements Meteor.LiveQueryHandle {
3334
#showStyleBaseIds: ShowStyleBaseId[] = []
3435
#showStyleBaseIdObserver!: ReactiveMongoObserverGroupHandle
3536

37+
#blueprintIds: BlueprintId[] = []
38+
#blueprintIdObserver!: ReactiveMongoObserverGroupHandle
39+
3640
#disposed = false
3741

3842
private constructor(cache: BucketContentCache) {
@@ -59,13 +63,16 @@ export class BucketContentObserver implements Meteor.LiveQueryHandle {
5963
added: (doc) => {
6064
const newDoc = convertShowStyleBase(doc)
6165
cache.ShowStyleSourceLayers.upsert(doc._id, { $set: newDoc as Partial<Document> })
66+
observer.updateBlueprintIds()
6267
},
6368
changed: (doc) => {
6469
const newDoc = convertShowStyleBase(doc)
6570
cache.ShowStyleSourceLayers.upsert(doc._id, { $set: newDoc as Partial<Document> })
71+
observer.updateBlueprintIds()
6672
},
6773
removed: (doc) => {
6874
cache.ShowStyleSourceLayers.remove(doc._id)
75+
observer.updateBlueprintIds()
6976
},
7077
},
7178
{
@@ -75,6 +82,27 @@ export class BucketContentObserver implements Meteor.LiveQueryHandle {
7582
]
7683
})
7784

85+
// Run the Blueprint query in a ReactiveMongoObserverGroup, so that it can be restarted whenever
86+
observer.#blueprintIdObserver = await ReactiveMongoObserverGroup(async () => {
87+
// Clear already cached data
88+
cache.Blueprints.remove({})
89+
90+
logger.silly(`optimized observer restarting ${observer.#blueprintIds}`)
91+
92+
return [
93+
Blueprints.observeChanges(
94+
{
95+
// We can use the `this.#blueprintIds` here, as this is restarted every time that property changes
96+
_id: { $in: observer.#blueprintIds },
97+
},
98+
cache.Blueprints.link(),
99+
{
100+
projection: blueprintFieldSpecifier,
101+
}
102+
),
103+
]
104+
})
105+
78106
// Subscribe to the database, and pipe any updates into the ReactiveCacheCollections
79107
// This takes ownership of the #showStyleBaseIdObserver, and will stop it if this throws
80108
observer.#observers = await waitForAllObserversReady([
@@ -106,6 +134,7 @@ export class BucketContentObserver implements Meteor.LiveQueryHandle {
106134
),
107135

108136
observer.#showStyleBaseIdObserver,
137+
observer.#blueprintIdObserver,
109138
])
110139

111140
return observer
@@ -132,6 +161,22 @@ export class BucketContentObserver implements Meteor.LiveQueryHandle {
132161
REACTIVITY_DEBOUNCE
133162
)
134163

164+
private updateBlueprintIds = _.debounce(
165+
Meteor.bindEnvironment(() => {
166+
if (this.#disposed) return
167+
168+
const newBlueprintIds = _.uniq(this.#cache.ShowStyleSourceLayers.find({}).map((rd) => rd.blueprintId))
169+
170+
if (!equivalentArrays(newBlueprintIds, this.#blueprintIds)) {
171+
logger.silly(`optimized observer changed ids ${JSON.stringify(newBlueprintIds)} ${this.#blueprintIds}`)
172+
this.#blueprintIds = newBlueprintIds
173+
// trigger the rundown group to restart
174+
this.#blueprintIdObserver.restart()
175+
}
176+
}),
177+
REACTIVITY_DEBOUNCE
178+
)
179+
135180
public get cache(): BucketContentCache {
136181
return this.#cache
137182
}

meteor/server/publications/pieceContentStatusUI/bucket/publication.ts

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
BucketId,
66
ExpectedPackageId,
77
PackageContainerPackageId,
8+
ShowStyleBaseId,
89
StudioId,
910
} from '@sofie-automation/corelib/dist/dataModel/Ids'
1011
import { MongoFieldSpecifierOnesStrict } from '@sofie-automation/corelib/dist/mongo'
@@ -37,6 +38,7 @@ import { regenerateForBucketActionIds, regenerateForBucketAdLibIds } from './reg
3738
import { PieceContentStatusStudio } from '../checkPieceContentStatus'
3839
import { check } from 'meteor/check'
3940
import { triggerWriteAccessBecauseNoCheckNecessary } from '../../../security/securityVerify'
41+
import { PieceContentStatusMessageFactory } from '../messageFactory'
4042

4143
interface UIBucketContentStatusesArgs {
4244
readonly studioId: StudioId
@@ -48,6 +50,8 @@ interface UIBucketContentStatusesState {
4850

4951
studio: PieceContentStatusStudio
5052

53+
showStyleMessageFactories: Map<ShowStyleBaseId, PieceContentStatusMessageFactory>
54+
5155
adlibDependencies: Map<BucketAdLibId, PieceDependencies>
5256
actionDependencies: Map<BucketAdLibActionId, PieceDependencies>
5357
}
@@ -111,6 +115,16 @@ async function setupUIBucketContentStatusesPublicationObservers(
111115
changed: (id) => triggerUpdate(trackActionChange(protectString(id))),
112116
removed: (id) => triggerUpdate(trackActionChange(protectString(id))),
113117
}),
118+
contentCache.Blueprints.find({}).observeChanges({
119+
added: () => triggerUpdate({ invalidateAll: true }),
120+
changed: () => triggerUpdate({ invalidateAll: true }),
121+
removed: () => triggerUpdate({ invalidateAll: true }),
122+
}),
123+
contentCache.ShowStyleSourceLayers.find({}).observeChanges({
124+
added: () => triggerUpdate({ invalidateAll: true }),
125+
changed: () => triggerUpdate({ invalidateAll: true }),
126+
removed: () => triggerUpdate({ invalidateAll: true }),
127+
}),
114128

115129
Studios.observeChanges(
116130
{ _id: bucket.studioId },
@@ -198,14 +212,26 @@ async function manipulateUIBucketContentStatusesPublicationData(
198212

199213
let regenerateActionIds: Set<BucketAdLibActionId>
200214
let regenerateAdlibIds: Set<BucketAdLibId>
201-
if (!state.adlibDependencies || !state.actionDependencies || invalidateAllItems) {
215+
if (
216+
!state.adlibDependencies ||
217+
!state.actionDependencies ||
218+
!state.showStyleMessageFactories ||
219+
invalidateAllItems
220+
) {
202221
state.adlibDependencies = new Map()
203222
state.actionDependencies = new Map()
223+
state.showStyleMessageFactories = new Map()
204224

205225
// force every piece to be regenerated
206226
collection.remove(null)
207227
regenerateAdlibIds = new Set(state.contentCache.BucketAdLibs.find({}).map((p) => p._id))
208228
regenerateActionIds = new Set(state.contentCache.BucketAdLibActions.find({}).map((p) => p._id))
229+
230+
// prepare the message factories
231+
for (const showStyle of state.contentCache.ShowStyleSourceLayers.find({})) {
232+
const blueprint = state.contentCache.Blueprints.findOne(showStyle.blueprintId)
233+
state.showStyleMessageFactories.set(showStyle._id, new PieceContentStatusMessageFactory(blueprint))
234+
}
209235
} else {
210236
regenerateAdlibIds = new Set(updateProps.invalidateBucketAdlibIds)
211237
regenerateActionIds = new Set(updateProps.invalidateBucketActionIds)
@@ -227,13 +253,15 @@ async function manipulateUIBucketContentStatusesPublicationData(
227253
state.contentCache,
228254
state.studio,
229255
state.adlibDependencies,
256+
state.showStyleMessageFactories,
230257
collection,
231258
regenerateAdlibIds
232259
)
233260
await regenerateForBucketActionIds(
234261
state.contentCache,
235262
state.studio,
236263
state.actionDependencies,
264+
state.showStyleMessageFactories,
237265
collection,
238266
regenerateActionIds
239267
)

meteor/server/publications/pieceContentStatusUI/bucket/regenerateForItem.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { BucketAdLibActionId, BucketAdLibId } from '@sofie-automation/corelib/dist/dataModel/Ids'
1+
import { BucketAdLibActionId, BucketAdLibId, ShowStyleBaseId } from '@sofie-automation/corelib/dist/dataModel/Ids'
22
import { ReadonlyDeep } from 'type-fest'
33
import { UIBucketContentStatus } from '@sofie-automation/meteor-lib/dist/api/rundownNotifications'
44
import { literal, protectString } from '../../../lib/tempLib'
@@ -11,6 +11,7 @@ import {
1111
PieceContentStatusPiece,
1212
PieceContentStatusStudio,
1313
} from '../checkPieceContentStatus'
14+
import type { PieceContentStatusMessageFactory } from '../messageFactory'
1415

1516
/**
1617
* Regenerating the status for the provided AdLibActionId
@@ -20,6 +21,7 @@ export async function regenerateForBucketAdLibIds(
2021
contentCache: ReadonlyDeep<BucketContentCache>,
2122
uiStudio: PieceContentStatusStudio,
2223
dependenciesState: Map<BucketAdLibId, PieceDependencies>,
24+
messageFactories: Map<ShowStyleBaseId, PieceContentStatusMessageFactory>,
2325
collection: CustomPublishCollection<UIBucketContentStatus>,
2426
regenerateIds: Set<BucketAdLibId>
2527
): Promise<void> {
@@ -45,6 +47,7 @@ export async function regenerateForBucketAdLibIds(
4547
if (sourceLayer) {
4648
const [status, itemDependencies] = await checkPieceContentStatusAndDependencies(
4749
uiStudio,
50+
messageFactories.get(actionDoc.showStyleBaseId),
4851
actionDoc,
4952
sourceLayer
5053
)
@@ -79,6 +82,7 @@ export async function regenerateForBucketActionIds(
7982
contentCache: ReadonlyDeep<BucketContentCache>,
8083
uiStudio: PieceContentStatusStudio,
8184
dependenciesState: Map<BucketAdLibActionId, PieceDependencies>,
85+
messageFactories: Map<ShowStyleBaseId, PieceContentStatusMessageFactory>,
8286
collection: CustomPublishCollection<UIBucketContentStatus>,
8387
regenerateIds: Set<BucketAdLibActionId>
8488
): Promise<void> {
@@ -106,11 +110,16 @@ export async function regenerateForBucketActionIds(
106110
const fakedPiece = literal<PieceContentStatusPiece>({
107111
_id: protectString(`${actionDoc._id}`),
108112
content: 'content' in actionDoc.display ? actionDoc.display.content : {},
113+
name:
114+
typeof actionDoc.display.label === 'string'
115+
? actionDoc.display.label
116+
: actionDoc.display.label.key,
109117
expectedPackages: actionDoc.expectedPackages,
110118
})
111119

112120
const [status, itemDependencies] = await checkPieceContentStatusAndDependencies(
113121
uiStudio,
122+
messageFactories.get(actionDoc.showStyleBaseId),
114123
fakedPiece,
115124
sourceLayer
116125
)

0 commit comments

Comments
 (0)