Skip to content

Commit ab020fb

Browse files
ianshadeJulusian
authored andcommitted
fix(EAV-513): prevent queued Part from hijacking infinite Pieces from the following Part(s)
problem was in the rank initially set to a placeholder 99999 recalculated too late, it was confusing the code that finds infinites from pieces in parts preceding the inserted one
1 parent 1ac8ca4 commit ab020fb

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
@@ -394,10 +394,9 @@ export class PartAndPieceInstanceActionService {
394394
throw new Error('New part must contain at least one piece')
395395
}
396396

397-
const newPart: Omit<DBPart, 'segmentId' | 'rundownId'> = {
397+
const newPart: Omit<DBPart, 'segmentId' | 'rundownId' | '_rank'> = {
398398
...rawPart,
399399
_id: getRandomId(),
400-
_rank: 99999, // Corrected in innerStartQueuedAdLib
401400
notes: [],
402401
invalid: false,
403402
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'
@@ -177,7 +178,9 @@ describe('Test blueprint api context', () => {
177178
return runJobWithPlayoutModel(context, { playlistId }, null, fcn as any)
178179
}
179180

180-
async function setupMyDefaultRundown(): Promise<{
181+
async function setupMyDefaultRundown(
182+
insertExtraContents?: (jobContext: MockJobContext, rundownId: RundownId) => Promise<void>
183+
): Promise<{
181184
jobContext: MockJobContext
182185
playlistId: RundownPlaylistId
183186
rundownId: RundownId
@@ -201,6 +204,8 @@ describe('Test blueprint api context', () => {
201204

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

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

@@ -1190,16 +1195,84 @@ describe('Test blueprint api context', () => {
11901195
expect(newPartInstance.partInstance.part._rank).toEqual(0.5)
11911196
expect(newPartInstance.partInstance.orphaned).toEqual('adlib-part')
11921197

1193-
const newNextPartInstances = await service.getPieceInstances('next')
1194-
expect(newNextPartInstances).toHaveLength(1)
1195-
expect(newNextPartInstances[0].partInstanceId).toEqual(
1198+
const newNextPieceInstances = await service.getPieceInstances('next')
1199+
expect(newNextPieceInstances).toHaveLength(1)
1200+
expect(newNextPieceInstances[0].partInstanceId).toEqual(
11961201
unprotectString(newPartInstance.partInstance._id)
11971202
)
11981203

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

12051278
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,
@@ -188,41 +187,29 @@ export async function innerFindLastScriptedPieceOnLayer(
188187
return fullPiece
189188
}
190189

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

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

228215
// Find any rundown defined infinites that we should inherit
@@ -238,13 +225,13 @@ export async function insertQueuedPartWithPieces(
238225
)
239226

240227
const newPartInstance = playoutModel.createAdlibbedPartInstance(
241-
newPart,
228+
newPartFull,
242229
initialPieces,
243230
fromAdlibId,
244231
infinitePieceInstances
245232
)
246233

247-
updateRankForAdlibbedPartInstance(context, playoutModel, newPartInstance)
234+
updatePartInstanceRanksAfterAdlib(playoutModel, currentPartInstance, newPartInstance)
248235

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

0 commit comments

Comments
 (0)