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
Original file line number Diff line number Diff line change
Expand Up @@ -486,4 +486,121 @@ describe('generateNotesForSegment', () => {
])
)
})

test('partInstance with runtime invalidReason', async () => {
const playlistId = protectString<RundownPlaylistId>('playlist0')
const nrcsName = 'some nrcs'

const segment: Pick<DBSegment, SegmentFields> = {
_id: protectString('segment0'),
_rank: 1,
rundownId: protectString('rundown0'),
name: 'A segment',
notes: [],
orphaned: undefined,
}

const partInstance0: Pick<DBPartInstance, PartInstanceFields> = {
_id: protectString('instance0'),
segmentId: segment._id,
rundownId: segment.rundownId,
orphaned: undefined,
reset: false,
invalidReason: {
message: generateTranslation('Runtime error occurred'),
severity: NoteSeverity.ERROR,
},
part: {
_id: protectString('part0'),
title: 'Test Part',
} as any,
}

const notes = generateNotesForSegment(playlistId, segment, nrcsName, [], [partInstance0])
expect(notes).toEqual(
literal<UISegmentPartNote[]>([
{
_id: protectString('segment0_partinstance_instance0_invalid_runtime'),
note: {
type: NoteSeverity.ERROR,
message: partInstance0.invalidReason!.message,
rank: segment._rank,
origin: {
segmentId: segment._id,
rundownId: segment.rundownId,
name: partInstance0.part.title,
partId: partInstance0.part._id,
segmentName: segment.name,
},
},
playlistId: playlistId,
rundownId: segment.rundownId,
segmentId: segment._id,
},
])
)
})

test('partInstance with runtime invalidReason but reset - no note', async () => {
const playlistId = protectString<RundownPlaylistId>('playlist0')
const nrcsName = 'some nrcs'

const segment: Pick<DBSegment, SegmentFields> = {
_id: protectString('segment0'),
_rank: 1,
rundownId: protectString('rundown0'),
name: 'A segment',
notes: [],
orphaned: undefined,
}

const partInstance0: Pick<DBPartInstance, PartInstanceFields> = {
_id: protectString('instance0'),
segmentId: segment._id,
rundownId: segment.rundownId,
orphaned: undefined,
reset: true,
invalidReason: {
message: generateTranslation('Runtime error occurred'),
severity: NoteSeverity.ERROR,
},
part: {
_id: protectString('part0'),
title: 'Test Part',
} as any,
}

const notes = generateNotesForSegment(playlistId, segment, nrcsName, [], [partInstance0])
expect(notes).toHaveLength(0)
})

test('partInstance without invalidReason - no note', async () => {
const playlistId = protectString<RundownPlaylistId>('playlist0')
const nrcsName = 'some nrcs'

const segment: Pick<DBSegment, SegmentFields> = {
_id: protectString('segment0'),
_rank: 1,
rundownId: protectString('rundown0'),
name: 'A segment',
notes: [],
orphaned: undefined,
}

const partInstance0: Pick<DBPartInstance, PartInstanceFields> = {
_id: protectString('instance0'),
segmentId: segment._id,
rundownId: segment.rundownId,
orphaned: undefined,
reset: false,
invalidReason: undefined,
part: {
_id: protectString('part0'),
title: 'Test Part',
} as any,
}

const notes = generateNotesForSegment(playlistId, segment, nrcsName, [], [partInstance0])
expect(notes).toHaveLength(0)
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ describe('manipulateUISegmentPartNotesPublicationData', () => {
Rundowns: new ReactiveCacheCollection('Rundowns'),
Segments: new ReactiveCacheCollection('Segments'),
Parts: new ReactiveCacheCollection('Parts'),
DeletedPartInstances: new ReactiveCacheCollection('DeletedPartInstances'),
PartInstances: new ReactiveCacheCollection('PartInstances'),
}

newCache.Rundowns.insert({
Expand Down Expand Up @@ -356,11 +356,11 @@ describe('manipulateUISegmentPartNotesPublicationData', () => {
invalid: false,
invalidReason: undefined,
})
newCache.DeletedPartInstances.insert({
newCache.PartInstances.insert({
_id: 'instance0',
segmentId: segmentId0,
rundownId: rundownId,
orphaned: undefined,
orphaned: 'deleted',
reset: false,
part: 'part' as any,
})
Expand Down Expand Up @@ -421,6 +421,7 @@ describe('manipulateUISegmentPartNotesPublicationData', () => {
[
{
_id: 'instance0',
orphaned: 'deleted',
part: 'part',
reset: false,
rundownId: 'rundown0',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,5 +155,31 @@ export function generateNotesForSegment(
}
}

// Generate notes for runtime invalidReason on PartInstances
// This is distinct from planned invalidReason on Parts - these are runtime validation issues
for (const partInstance of partInstances) {
// Skip if the PartInstance has been reset (no longer relevant) or has no runtime invalidReason
if (partInstance.reset || !partInstance.invalidReason) continue

notes.push({
_id: protectString(`${segment._id}_partinstance_${partInstance._id}_invalid_runtime`),
playlistId,
rundownId: partInstance.rundownId,
segmentId: segment._id,
note: {
type: partInstance.invalidReason.severity ?? NoteSeverity.ERROR,
message: partInstance.invalidReason.message,
rank: segment._rank,
origin: {
segmentId: partInstance.segmentId,
partId: partInstance.part._id,
rundownId: partInstance.rundownId,
segmentName: segment.name,
name: partInstance.part.title,
},
},
})
}

return notes
}
8 changes: 4 additions & 4 deletions meteor/server/publications/segmentPartNotesUI/publication.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ async function setupUISegmentPartNotesPublicationObservers(
triggerUpdate({ invalidateSegmentIds: [doc.segmentId, oldDoc.segmentId] }),
removed: (doc) => triggerUpdate({ invalidateSegmentIds: [doc.segmentId] }),
}),
cache.DeletedPartInstances.find({}).observe({
cache.PartInstances.find({}).observe({
added: (doc) => triggerUpdate({ invalidateSegmentIds: [doc.segmentId] }),
changed: (doc, oldDoc) =>
triggerUpdate({ invalidateSegmentIds: [doc.segmentId, oldDoc.segmentId] }),
Expand Down Expand Up @@ -184,13 +184,13 @@ export async function manipulateUISegmentPartNotesPublicationData(
interface UpdateNotesData {
rundownsCache: Map<RundownId, Pick<Rundown, RundownFields>>
parts: Map<SegmentId, Pick<DBPart, PartFields>[]>
deletedPartInstances: Map<SegmentId, Pick<DBPartInstance, PartInstanceFields>[]>
partInstances: Map<SegmentId, Pick<DBPartInstance, PartInstanceFields>[]>
}
function compileUpdateNotesData(cache: ReadonlyDeep<ContentCache>): UpdateNotesData {
return {
rundownsCache: normalizeArrayToMap(cache.Rundowns.find({}).fetch(), '_id'),
parts: groupByToMap(cache.Parts.find({}).fetch(), 'segmentId'),
deletedPartInstances: groupByToMap(cache.DeletedPartInstances.find({}).fetch(), 'segmentId'),
partInstances: groupByToMap(cache.PartInstances.find({}).fetch(), 'segmentId'),
}
}

Expand All @@ -205,7 +205,7 @@ function updateNotesForSegment(
segment,
getRundownNrcsName(state.rundownsCache.get(segment.rundownId)),
state.parts.get(segment._id) ?? [],
state.deletedPartInstances.get(segment._id) ?? []
state.partInstances.get(segment._id) ?? []
)

// Insert generated notes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export const partFieldSpecifier = literal<MongoFieldSpecifierOnesStrict<Pick<DBP
invalidReason: 1,
})

export type PartInstanceFields = '_id' | 'segmentId' | 'rundownId' | 'orphaned' | 'reset' | 'part'
export type PartInstanceFields = '_id' | 'segmentId' | 'rundownId' | 'orphaned' | 'reset' | 'part' | 'invalidReason'
export const partInstanceFieldSpecifier = literal<
MongoFieldSpecifierOnesStrict<Pick<PartInstance, PartInstanceFields>>
>({
Expand All @@ -44,25 +44,25 @@ export const partInstanceFieldSpecifier = literal<
rundownId: 1,
orphaned: 1,
reset: 1,
invalidReason: 1,
// @ts-expect-error Deep not supported
'part._id': 1,
'part.title': 1,
})

export interface ContentCache {
Rundowns: ReactiveCacheCollection<Pick<Rundown, RundownFields>>
Segments: ReactiveCacheCollection<Pick<DBSegment, SegmentFields>>
Parts: ReactiveCacheCollection<Pick<DBPart, PartFields>>
DeletedPartInstances: ReactiveCacheCollection<Pick<PartInstance, PartInstanceFields>>
PartInstances: ReactiveCacheCollection<Pick<PartInstance, PartInstanceFields>>
}

export function createReactiveContentCache(): ContentCache {
const cache: ContentCache = {
Rundowns: new ReactiveCacheCollection<Pick<Rundown, RundownFields>>('rundowns'),
Segments: new ReactiveCacheCollection<Pick<DBSegment, SegmentFields>>('segments'),
Parts: new ReactiveCacheCollection<Pick<DBPart, PartFields>>('parts'),
DeletedPartInstances: new ReactiveCacheCollection<Pick<PartInstance, PartInstanceFields>>(
'deletedPartInstances'
),
PartInstances: new ReactiveCacheCollection<Pick<PartInstance, PartInstanceFields>>('partInstances'),
}

return cache
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,12 @@ export class RundownContentObserver {
}
),
PartInstances.observeChanges(
{ rundownId: { $in: rundownIds }, reset: { $ne: true }, orphaned: 'deleted' },
cache.DeletedPartInstances.link(),
{
rundownId: { $in: rundownIds },
reset: { $ne: true },
$or: [{ invalidReason: { $exists: true } }, { orphaned: 'deleted' }],
},
cache.PartInstances.link(),
{ projection: partInstanceFieldSpecifier }
),
])
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {
IBlueprintMutatablePart,
IBlueprintMutatablePartInstance,
IBlueprintPart,
IBlueprintPartInstance,
IBlueprintPiece,
Expand Down Expand Up @@ -73,10 +74,16 @@ export interface IOnSetAsNextContext extends IShowStyleUserContext, IEventContex
/** Update a piecesInstance */
updatePieceInstance(pieceInstanceId: string, piece: Partial<IBlueprintPiece>): Promise<IBlueprintPieceInstance>

/** Update a partInstance */
/**
* Update a partInstance
* @param part Which part to update
* @param props Properties of the Part itself
* @param instanceProps Properties of the PartInstance (runtime state)
*/
updatePartInstance(
part: 'current' | 'next',
props: Partial<IBlueprintMutatablePart>
props: Partial<IBlueprintMutatablePart>,
instanceProps?: Partial<IBlueprintMutatablePartInstance>
): Promise<IBlueprintPartInstance>

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { ReadonlyDeep } from 'type-fest'
import {
IBlueprintMutatablePart,
IBlueprintMutatablePartInstance,
IBlueprintPart,
IBlueprintPartInstance,
IBlueprintPiece,
Expand Down Expand Up @@ -67,10 +68,16 @@ export interface IPartAndPieceActionContext {
/** Update a piecesInstance */
updatePieceInstance(pieceInstanceId: string, piece: Partial<IBlueprintPiece>): Promise<IBlueprintPieceInstance>

/** Update a partInstance */
/**
* Update a partInstance
* @param part Which part to update
* @param props Properties of the Part itself
* @param instanceProps Properties of the PartInstance (runtime state)
*/
updatePartInstance(
part: 'current' | 'next',
props: Partial<IBlueprintMutatablePart>
props: Partial<IBlueprintMutatablePart>,
instanceProps?: Partial<IBlueprintMutatablePartInstance>
): Promise<IBlueprintPartInstance>
/** Inform core that a take out of the partinstance should be blocked until the specified time */
blockTakeUntil(time: Time | null): Promise<void>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { IRundownUserContext } from './rundownContext.js'
import type {
IBlueprintMutatablePart,
IBlueprintMutatablePartInstance,
IBlueprintPartInstance,
IBlueprintPiece,
IBlueprintPieceInstance,
Expand Down Expand Up @@ -37,8 +38,15 @@ export interface ISyncIngestUpdateToPartInstanceContext extends IRundownUserCont
// /** Remove a ActionInstance */
// removeActionInstances(...actionInstanceIds: string[]): string[]

/** Update a partInstance */
updatePartInstance(props: Partial<IBlueprintMutatablePart>): IBlueprintPartInstance
/**
* Update a partInstance
* @param props Properties of the Part itself
* @param instanceProps Properties of the PartInstance (runtime state)
*/
updatePartInstance(
props: Partial<IBlueprintMutatablePart>,
instanceProps?: Partial<IBlueprintMutatablePartInstance>
): IBlueprintPartInstance

/** Remove the partInstance. This is only valid when `playstatus: 'next'` */
removePartInstance(): void
Expand Down
19 changes: 18 additions & 1 deletion packages/blueprints-integration/src/documents/partInstance.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,27 @@
import type { Time } from '../common.js'
import type { IBlueprintPartDB } from './part.js'
import type { ITranslatableMessage } from '../translations.js'

export type PartEndState = unknown

/**
* Properties of a PartInstance that can be modified at runtime by blueprints.
* These are runtime state properties, distinct from the planned Part properties.
*/
export interface IBlueprintMutatablePartInstance {
/**
* If set, this PartInstance exists and is valid as being next, but it cannot be taken in its current state.
* This can be used to block taking a PartInstance that requires user action to resolve.
* This is a runtime validation issue, distinct from the planned `invalidReason` on the Part itself.
*/
invalidReason?: ITranslatableMessage
}

/** The Part instance sent from Core */
export interface IBlueprintPartInstance<TPrivateData = unknown, TPublicData = unknown> {
export interface IBlueprintPartInstance<
TPrivateData = unknown,
TPublicData = unknown,
> extends IBlueprintMutatablePartInstance {
_id: string
/** The segment ("Title") this line belongs to */
segmentId: string
Expand Down
Loading
Loading