Skip to content

Commit 1c6e4d8

Browse files
authored
Merge pull request Sofie-Automation#1396 from tv2norge-collab/contribute/EAV-513
fix: prevent queued Part from hijacking infinite Pieces from the following Part(s)
2 parents 3df9ad6 + f222545 commit 1c6e4d8

File tree

4 files changed

+90
-32
lines changed

4 files changed

+90
-32
lines changed

packages/job-worker/src/blueprints/context/services/PartAndPieceInstanceActionService.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -381,10 +381,9 @@ export class PartAndPieceInstanceActionService {
381381
throw new Error('New part must contain at least one piece')
382382
}
383383

384-
const newPart: Omit<DBPart, 'segmentId' | 'rundownId'> = {
384+
const newPart: Omit<DBPart, 'segmentId' | 'rundownId' | '_rank'> = {
385385
...rawPart,
386386
_id: getRandomId(),
387-
_rank: 99999, // Corrected in innerStartQueuedAdLib
388387
notes: [],
389388
invalid: false,
390389
invalidReason: undefined,

packages/job-worker/src/blueprints/context/services/__tests__/PartAndPieceInstanceActionService.test.ts

Lines changed: 77 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import { DBPartInstance } from '@sofie-automation/corelib/dist/dataModel/PartIns
2626
import { getCurrentTime } from '../../../../lib'
2727
import {
2828
EmptyPieceTimelineObjectsBlob,
29+
Piece,
2930
serializePieceTimelineObjectsBlob,
3031
} from '@sofie-automation/corelib/dist/dataModel/Piece'
3132
import { PlayoutPartInstanceModel } from '../../../../playout/model/PlayoutPartInstanceModel'
@@ -176,7 +177,9 @@ describe('Test blueprint api context', () => {
176177
return runJobWithPlayoutModel(context, { playlistId }, null, fcn as any)
177178
}
178179

179-
async function setupMyDefaultRundown(): Promise<{
180+
async function setupMyDefaultRundown(
181+
insertExtraContents?: (jobContext: MockJobContext, rundownId: RundownId) => Promise<void>
182+
): Promise<{
180183
jobContext: MockJobContext
181184
playlistId: RundownPlaylistId
182185
rundownId: RundownId
@@ -200,6 +203,8 @@ describe('Test blueprint api context', () => {
200203

201204
await setupDefaultRundown(context, showStyleCompound, playlistId, rundownId)
202205

206+
if (insertExtraContents) await insertExtraContents(context, rundownId)
207+
203208
const allPartInstances = await generateSparsePieceInstances(context, activationId, rundownId)
204209
expect(allPartInstances).toHaveLength(5)
205210

@@ -1187,16 +1192,84 @@ describe('Test blueprint api context', () => {
11871192
expect(newPartInstance.partInstance.part._rank).toEqual(0.5)
11881193
expect(newPartInstance.partInstance.orphaned).toEqual('adlib-part')
11891194

1190-
const newNextPartInstances = await service.getPieceInstances('next')
1191-
expect(newNextPartInstances).toHaveLength(1)
1192-
expect(newNextPartInstances[0].partInstanceId).toEqual(
1195+
const newNextPieceInstances = await service.getPieceInstances('next')
1196+
expect(newNextPieceInstances).toHaveLength(1)
1197+
expect(newNextPieceInstances[0].partInstanceId).toEqual(
11931198
unprotectString(newPartInstance.partInstance._id)
11941199
)
11951200

11961201
expect(service.nextPartState).toEqual(ActionPartChange.SAFE_CHANGE)
11971202
expect(service.currentPartState).toEqual(ActionPartChange.NONE)
11981203
})
11991204
})
1205+
1206+
test('queued part does not hijack infinites from following parts', async () => {
1207+
// makes sure that infinites which would normally start in the part AFTER the part that is being queued,
1208+
// are not starting in the queued part itself
1209+
1210+
const { jobContext, playlistId, rundownId } = await setupMyDefaultRundown(
1211+
async (context, rundownId) => {
1212+
const secondPart = await context.mockCollections.Parts.findOne({ externalId: 'MOCK_PART_0_1' })
1213+
if (!secondPart) throw Error('could not find mock part')
1214+
const piece001: Piece = {
1215+
_id: protectString(rundownId + '_piece012'),
1216+
externalId: 'MOCK_PIECE_012',
1217+
startRundownId: rundownId,
1218+
startSegmentId: secondPart.segmentId,
1219+
startPartId: secondPart._id,
1220+
name: 'Piece 012',
1221+
enable: {
1222+
start: 0,
1223+
},
1224+
sourceLayerId: '',
1225+
outputLayerId: '',
1226+
pieceType: IBlueprintPieceType.Normal,
1227+
lifespan: PieceLifespan.OutOnSegmentEnd,
1228+
invalid: false,
1229+
content: {},
1230+
timelineObjectsString: EmptyPieceTimelineObjectsBlob,
1231+
}
1232+
await context.mockCollections.Pieces.insertOne(piece001)
1233+
}
1234+
)
1235+
1236+
const partInstance = (await jobContext.mockCollections.PartInstances.findOne({
1237+
rundownId,
1238+
})) as DBPartInstance
1239+
expect(partInstance).toBeTruthy()
1240+
await setPartInstances(jobContext, playlistId, partInstance, undefined)
1241+
1242+
await wrapWithPlayoutModel(jobContext, playlistId, async (playoutModel) => {
1243+
const { service } = await getTestee(jobContext, playoutModel)
1244+
1245+
const newPiece: IBlueprintPiece = {
1246+
name: 'test piece',
1247+
sourceLayerId: 'sl1',
1248+
outputLayerId: 'o1',
1249+
externalId: '-',
1250+
enable: { start: 0 },
1251+
lifespan: PieceLifespan.OutOnRundownEnd,
1252+
content: {
1253+
timelineObjects: [],
1254+
},
1255+
}
1256+
const newPart: IBlueprintPart = {
1257+
externalId: 'nope',
1258+
title: 'something',
1259+
}
1260+
1261+
// Create it with most of the real flow
1262+
postProcessPiecesMock.mockImplementationOnce(postProcessPiecesOrig)
1263+
insertQueuedPartWithPiecesMock.mockImplementationOnce(insertQueuedPartWithPiecesOrig)
1264+
expect((await service.queuePart(newPart, [newPiece]))._id).toEqual(
1265+
playoutModel.playlist.nextPartInfo?.partInstanceId
1266+
)
1267+
1268+
const newNextPartInstances = await service.getPieceInstances('next')
1269+
expect(newNextPartInstances).toHaveLength(1)
1270+
expect(newNextPartInstances[0].piece.name).toBe('test piece')
1271+
})
1272+
})
12001273
})
12011274

12021275
describe('insertPiece', () => {

packages/job-worker/src/ingest/__tests__/ingest.test.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1733,7 +1733,6 @@ describe('Test ingest actions for rundowns and segments', () => {
17331733
currentPartInstance,
17341734
{
17351735
_id: protectString(`after_${currentPartInstance.partInstance._id}_part`),
1736-
_rank: 0,
17371736
externalId: `after_${currentPartInstance.partInstance._id}_externalId`,
17381737
title: 'New part',
17391738
expectedDurationWithTransition: undefined,

packages/job-worker/src/playout/adlibUtils.ts

Lines changed: 12 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,8 @@ export async function innerStartOrQueueAdLibPiece(
4040
const span = context.startSpan('innerStartOrQueueAdLibPiece')
4141
let queuedPartInstanceId: PartInstanceId | undefined
4242
if (queue || adLibPiece.toBeQueued) {
43-
const adlibbedPart: Omit<DBPart, 'segmentId' | 'rundownId'> = {
43+
const adlibbedPart: Omit<DBPart, 'segmentId' | 'rundownId' | '_rank'> = {
4444
_id: getRandomId(),
45-
_rank: 99999, // Corrected in innerStartQueuedAdLib
4645
externalId: '',
4746
title: adLibPiece.name,
4847
expectedDuration: adLibPiece.expectedDuration,
@@ -187,41 +186,29 @@ export async function innerFindLastScriptedPieceOnLayer(
187186
return fullPiece
188187
}
189188

190-
function updateRankForAdlibbedPartInstance(
191-
_context: JobContext,
192-
playoutModel: PlayoutModel,
193-
newPartInstance: PlayoutPartInstanceModel
194-
) {
195-
const currentPartInstance = playoutModel.currentPartInstance
196-
if (!currentPartInstance) throw new Error('CurrentPartInstance not found')
197-
198-
// Parts are always integers spaced by one, and orphaned PartInstances will be decimals spaced between two Part
199-
// so we can predict a 'safe' rank to get the desired position with some simple maths
200-
newPartInstance.setRank(
201-
getRank(
202-
currentPartInstance.partInstance.part._rank,
203-
Math.floor(currentPartInstance.partInstance.part._rank + 1)
204-
)
205-
)
206-
207-
updatePartInstanceRanksAfterAdlib(playoutModel, currentPartInstance, newPartInstance)
208-
}
209-
210189
export async function insertQueuedPartWithPieces(
211190
context: JobContext,
212191
playoutModel: PlayoutModel,
213192
rundown: PlayoutRundownModel,
214193
currentPartInstance: PlayoutPartInstanceModel,
215-
newPart: Omit<DBPart, 'segmentId' | 'rundownId'>,
194+
newPart: Omit<DBPart, 'segmentId' | 'rundownId' | '_rank'>,
216195
initialPieces: Omit<PieceInstancePiece, 'startPartId'>[],
217196
fromAdlibId: PieceId | undefined
218197
): Promise<PlayoutPartInstanceModel> {
219198
const span = context.startSpan('insertQueuedPartWithPieces')
220199

200+
// Parts are always integers spaced by one, and orphaned PartInstances will be decimals spaced between two Part
201+
// so we can predict a 'safe' rank to get the desired position with some simple maths
202+
const newRank = getRank(
203+
currentPartInstance.partInstance.part._rank,
204+
Math.floor(currentPartInstance.partInstance.part._rank + 1)
205+
)
206+
221207
const newPartFull: DBPart = {
222208
...newPart,
223209
segmentId: currentPartInstance.partInstance.segmentId,
224210
rundownId: currentPartInstance.partInstance.rundownId,
211+
_rank: newRank,
225212
}
226213

227214
// Find any rundown defined infinites that we should inherit
@@ -237,13 +224,13 @@ export async function insertQueuedPartWithPieces(
237224
)
238225

239226
const newPartInstance = playoutModel.createAdlibbedPartInstance(
240-
newPart,
227+
newPartFull,
241228
initialPieces,
242229
fromAdlibId,
243230
infinitePieceInstances
244231
)
245232

246-
updateRankForAdlibbedPartInstance(context, playoutModel, newPartInstance)
233+
updatePartInstanceRanksAfterAdlib(playoutModel, currentPartInstance, newPartInstance)
247234

248235
await setNextPart(context, playoutModel, newPartInstance, false)
249236

0 commit comments

Comments
 (0)