Skip to content

Commit 19f40c3

Browse files
author
Ivan Maretić
committed
fix: keep orphaned next part if syncIngestUpdateToPartInstance exists
1 parent d94a5d8 commit 19f40c3

2 files changed

Lines changed: 83 additions & 5 deletions

File tree

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

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { protectString } from '@sofie-automation/corelib/dist/protectedString'
77
import { saveIntoDb } from '../../db/changes.js'
88
import { ensureNextPartIsValid as ensureNextPartIsValidRaw } from '../updateNext.js'
99
import { MockJobContext, setupDefaultJobEnvironment } from '../../__mocks__/context.js'
10+
import { setupMockShowStyleCompound } from '../../__mocks__/presetCollections.js'
1011
import { runJobWithPlayoutModel } from '../../playout/lock.js'
1112

1213
jest.mock('../../playout/setNext')
@@ -538,6 +539,47 @@ describe('ensureNextPartIsValid', () => {
538539
false
539540
)
540541
})
542+
test('Next part instance is orphaned: "deleted" and `syncIngestUpdateToPartInstance` exists', async () => {
543+
const showStyleCompound = await setupMockShowStyleCompound(context)
544+
await context.mockCollections.Rundowns.update(rundownId, {
545+
$set: {
546+
showStyleBaseId: showStyleCompound._id,
547+
showStyleVariantId: showStyleCompound.showStyleVariantId,
548+
},
549+
})
550+
context.updateShowStyleBlueprint({
551+
syncIngestUpdateToPartInstance: jest.fn(),
552+
})
553+
554+
const instanceId: PartInstanceId = protectString('orphaned_first_part_with_callback')
555+
await context.mockCollections.PartInstances.insertOne(
556+
literal<DBPartInstance>({
557+
_id: instanceId,
558+
rundownId: rundownId,
559+
segmentId: protectString('mock_segment1'),
560+
playlistActivationId: protectString('active'),
561+
segmentPlayoutId: protectString(''),
562+
takeCount: 0,
563+
rehearsal: false,
564+
part: literal<DBPart>({
565+
_id: protectString('orphan_with_callback_1'),
566+
_rank: 1.5,
567+
rundownId: rundownId,
568+
segmentId: protectString('mock_segment1'),
569+
externalId: 'o1-callback',
570+
title: 'Orphan 1 Callback',
571+
expectedDurationWithTransition: undefined,
572+
}),
573+
orphaned: 'deleted',
574+
})
575+
)
576+
577+
await resetPartIds(null, instanceId, false)
578+
579+
await expect(ensureNextPartIsValid()).resolves.toBeFalsy()
580+
581+
expect(setNextPartMock).not.toHaveBeenCalled()
582+
})
541583
test('Next part is invalid, but instance is not', async () => {
542584
// Insert a temporary instance
543585
const instanceId: PartInstanceId = protectString('orphaned_first_part')

packages/job-worker/src/ingest/updateNext.ts

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,13 @@ import { setNextPart } from '../playout/setNext.js'
55
import { isPartPlayable } from '@sofie-automation/corelib/dist/dataModel/Part'
66

77
/**
8-
* Make sure that the nextPartInstance for the current Playlist is still correct
9-
* This will often change the nextPartInstance
8+
* Make sure that the nextPartInstance for the current Playlist is still correct.
9+
* This will often change the nextPartInstance.
10+
*
11+
* If the selected next part has been deleted by ingest, the default behavior is to
12+
* drop it and autoselect a replacement. If the show-style blueprint defines
13+
* `syncIngestUpdateToPartInstance`, the deleted next part is kept selected here so
14+
* the blueprint can decide whether to remove it or preserve it.
1015
* @param context Context of the job being run
1116
* @param playoutModel Playout Model to operate on
1217
* @returns Whether the timeline should be updated following this operation
@@ -43,14 +48,22 @@ export async function ensureNextPartIsValid(context: JobContext, playoutModel: P
4348

4449
const orderedSegments = playoutModel.getAllOrderedSegments()
4550
const orderedParts = playoutModel.getAllOrderedParts()
51+
const nextPartIsDeleted = nextPartInstance?.partInstance.orphaned === 'deleted'
4652

47-
if (!nextPartInstance || nextPartInstance.partInstance.orphaned === 'deleted') {
48-
// Don't have a nextPart or it has been deleted, so autoselect something
53+
if (nextPartIsDeleted && (await hasSyncIngestUpdateToPartInstance(context, playoutModel, nextPartInstance))) {
54+
// Source has deleted this part, but it is selected as next.
55+
// Keep it selected, and let blueprint function `syncIngestUpdateToPartInstance` decide what to do with it.
56+
span?.end()
57+
return false
58+
}
59+
60+
if (!nextPartInstance || nextPartIsDeleted) {
61+
// Don't have a nextPart, so autoselect something
4962
const newNextPart = selectNextPart(
5063
context,
5164
playlist,
5265
currentPartInstance?.partInstance ?? null,
53-
nextPartInstance?.partInstance ?? null,
66+
null,
5467
orderedSegments,
5568
orderedParts,
5669
{ ignoreUnplayable: true, ignoreQuickLoop: false }
@@ -97,3 +110,26 @@ export async function ensureNextPartIsValid(context: JobContext, playoutModel: P
97110
span?.end()
98111
return false
99112
}
113+
114+
async function hasSyncIngestUpdateToPartInstance(
115+
context: JobContext,
116+
playoutModel: PlayoutModel,
117+
nextPartInstance: PlayoutModel['nextPartInstance']
118+
): Promise<boolean> {
119+
if (!nextPartInstance) return false
120+
const rundown = playoutModel.getRundown(nextPartInstance.partInstance.part.rundownId)
121+
if (!rundown) return false
122+
if (!rundown.rundown.showStyleVariantId) return false
123+
124+
try {
125+
const showStyle = await context.getShowStyleCompound(
126+
rundown.rundown.showStyleVariantId,
127+
rundown.rundown.showStyleBaseId
128+
)
129+
const blueprint = await context.getShowStyleBlueprint(showStyle._id)
130+
131+
return !!blueprint.blueprint.syncIngestUpdateToPartInstance
132+
} catch {
133+
return false
134+
}
135+
}

0 commit comments

Comments
 (0)