From a5e7af491a5f48225f905b7a9fb1b6cdc93bb6d7 Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Fri, 23 Feb 2024 14:47:12 +0000 Subject: [PATCH 1/6] fix: content status for PieceInstances can be under the id of the PieceInstance or the wrapped Piece SOFIE-2993 --- .../SegmentTimeline/withMediaObjectStatus.tsx | 42 +++++++++++++------ 1 file changed, 30 insertions(+), 12 deletions(-) diff --git a/meteor/client/ui/SegmentTimeline/withMediaObjectStatus.tsx b/meteor/client/ui/SegmentTimeline/withMediaObjectStatus.tsx index e93277fac5..e63789323a 100644 --- a/meteor/client/ui/SegmentTimeline/withMediaObjectStatus.tsx +++ b/meteor/client/ui/SegmentTimeline/withMediaObjectStatus.tsx @@ -70,6 +70,35 @@ export function withMediaObjectStatus(): ( } } + private getStatusDocForPiece( + piece: BucketAdLibUi | IAdLibListItem | AdLibPieceUi | PieceUi | BucketAdLibActionUi + ) { + const pieceUnwrapped = WithMediaObjectStatusHOCComponent.unwrapPieceInstance(piece) + + // Bucket items use a different collection + if (RundownUtils.isBucketAdLibItem(piece)) { + return UIBucketContentStatuses.findOne({ + bucketId: piece.bucketId, + docId: pieceUnwrapped._id, + }) + } + + // PieceInstance's might have a dedicated status + if (RundownUtils.isPieceInstance(piece)) { + const status = UIPieceContentStatuses.findOne({ + // Future: It would be good for this to be stricter. + pieceId: piece.instance._id, + }) + if (status) return status + } + + // Fallback to using the one from the source piece + return UIPieceContentStatuses.findOne({ + // Future: It would be good for this to be stricter. + pieceId: pieceUnwrapped._id, + }) + } + updateDataTracker() { if (this.destroyed) return @@ -80,19 +109,8 @@ export function withMediaObjectStatus(): ( // Check item status if (piece && (piece.sourceLayer || layer) && studio) { - const pieceUnwrapped = WithMediaObjectStatusHOCComponent.unwrapPieceInstance(piece) - const statusDoc = RundownUtils.isBucketAdLibItem(piece) - ? UIBucketContentStatuses.findOne({ - bucketId: piece.bucketId, - docId: pieceUnwrapped._id, - }) - : UIPieceContentStatuses.findOne({ - // Future: It would be good for this to be stricter. - pieceId: pieceUnwrapped._id, - }) - // Extract the status or populate some default values - const statusObj = statusDoc?.status ?? DEFAULT_STATUS + const statusObj = this.getStatusDocForPiece(piece)?.status ?? DEFAULT_STATUS if (RundownUtils.isAdLibPieceOrAdLibListItem(piece)) { if (!overrides.piece || !_.isEqual(statusObj, (overrides.piece as AdLibPieceUi).contentStatus)) { From 5d3a6106b360fa0e17cdb74c58ec39c9b0e2cb9a Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Fri, 23 Feb 2024 14:47:55 +0000 Subject: [PATCH 2/6] fix: use the PartInstance instead of Part for PieceInstance content status SOFIE-2993 Adlibbed/orphaned parts don't have a Part, so never got any status --- meteor/lib/api/rundownNotifications.ts | 1 + .../rundown/publication.ts | 36 +++++++++-- .../rundown/reactiveContentCache.ts | 16 ++++- .../rundown/regenerateItems.ts | 59 ++++++++++++++----- .../rundown/rundownContentObserver.ts | 14 +++++ 5 files changed, 106 insertions(+), 20 deletions(-) diff --git a/meteor/lib/api/rundownNotifications.ts b/meteor/lib/api/rundownNotifications.ts index 46554b9b8f..a863b2b958 100644 --- a/meteor/lib/api/rundownNotifications.ts +++ b/meteor/lib/api/rundownNotifications.ts @@ -38,6 +38,7 @@ export interface UIPieceContentStatus { segmentId: SegmentId | undefined pieceId: PieceId | AdLibActionId | RundownBaselineAdLibActionId | PieceInstanceId + isPieceInstance: boolean name: string | ITranslatableMessage segmentName: string | undefined diff --git a/meteor/server/publications/pieceContentStatusUI/rundown/publication.ts b/meteor/server/publications/pieceContentStatusUI/rundown/publication.ts index 09e22e909b..9fde25bfb4 100644 --- a/meteor/server/publications/pieceContentStatusUI/rundown/publication.ts +++ b/meteor/server/publications/pieceContentStatusUI/rundown/publication.ts @@ -4,6 +4,7 @@ import { ExpectedPackageId, PackageContainerPackageId, PartId, + PartInstanceId, PieceId, PieceInstanceId, RundownBaselineAdLibActionId, @@ -33,7 +34,7 @@ import { logger } from '../../../logging' import { resolveCredentials } from '../../../security/lib/credentials' import { NoSecurityReadAccess } from '../../../security/noSecurity' import { RundownPlaylistReadAccess } from '../../../security/rundownPlaylist' -import { ContentCache, createReactiveContentCache } from './reactiveContentCache' +import { ContentCache, PartInstanceFields, createReactiveContentCache } from './reactiveContentCache' import { RundownContentObserver } from './rundownContentObserver' import { RundownsObserver } from '../../lib/rundownsObserver' import { LiveQueryHandle } from '../../../lib/lib' @@ -57,6 +58,7 @@ import { } from './regenerateItems' import { PieceContentStatusStudio } from '../checkPieceContentStatus' import { check, Match } from 'meteor/check' +import { DBPartInstance } from '@sofie-automation/corelib/dist/dataModel/PartInstance' interface UIPieceContentStatusesArgs { readonly rundownPlaylistId: RundownPlaylistId @@ -80,6 +82,7 @@ interface UIPieceContentStatusesUpdateProps extends IContentStatusesUpdatePropsB updatedSegmentIds: SegmentId[] updatedPartIds: PartId[] + updatedPartInstanceIds: PartInstanceId[] updatedPieceIds: PieceId[] updatedPieceInstanceIds: PieceInstanceId[] @@ -137,13 +140,18 @@ async function setupUIPieceContentStatusesPublicationObservers( contentCache.Parts.find({}).observeChanges({ added: (id) => triggerUpdate({ updatedPartIds: [protectString(id)] }), changed: (id) => triggerUpdate({ updatedPartIds: [protectString(id)] }), - removed: (id) => triggerUpdate({ updatedSegmentIds: [protectString(id)] }), + removed: (id) => triggerUpdate({ updatedPartIds: [protectString(id)] }), }), contentCache.Pieces.find({}).observeChanges({ added: (id) => triggerUpdate({ updatedPieceIds: [protectString(id)] }), changed: (id) => triggerUpdate({ updatedPieceIds: [protectString(id)] }), removed: (id) => triggerUpdate({ updatedPieceIds: [protectString(id)] }), }), + contentCache.PartInstances.find({}).observeChanges({ + added: (id) => triggerUpdate({ updatedPartInstanceIds: [protectString(id)] }), + changed: (id) => triggerUpdate({ updatedPartInstanceIds: [protectString(id)] }), + removed: (id) => triggerUpdate({ updatedPartInstanceIds: [protectString(id)] }), + }), contentCache.PieceInstances.find({}).observeChanges({ added: (id) => triggerUpdate({ updatedPieceInstanceIds: [protectString(id)] }), changed: (id) => triggerUpdate({ updatedPieceInstanceIds: [protectString(id)] }), @@ -292,7 +300,8 @@ async function manipulateUIPieceContentStatusesPublicationData( state.contentCache, collection, new Set(updateProps?.updatedSegmentIds), - new Set(updateProps?.updatedPartIds) + new Set(updateProps?.updatedPartIds), + new Set(updateProps?.updatedPartInstanceIds) ) let regeneratePieceIds: Set @@ -414,20 +423,37 @@ function updatePartAndSegmentInfoForExistingDocs( contentCache: ReadonlyDeep, collection: CustomPublishCollection, updatedSegmentIds: Set, - updatedPartIds: Set + updatedPartIds: Set, + updatedPartInstanceIds: Set ) { + const updatedPartInstancesByPartId = new Map>() + for (const partInstanceId of updatedPartInstanceIds) { + const partInstance = contentCache.PartInstances.findOne(partInstanceId) + if (partInstance) updatedPartInstancesByPartId.set(partInstance.part._id, partInstance) + } + collection.updateAll((doc) => { let changed = false // If the part for this doc changed, update its part. // Note: if the segment of the doc's part changes that will only be noticed here - if (doc.partId && updatedPartIds.has(doc.partId)) { + if (doc.partId && !doc.isPieceInstance && updatedPartIds.has(doc.partId)) { const part = contentCache.Parts.findOne(doc.partId) if (part && (part.segmentId !== doc.segmentId || part._rank !== doc.partRank)) { doc.segmentId = part.segmentId doc.partRank = part._rank changed = true } + } else if (doc.partId && doc.isPieceInstance) { + const newPartProps = updatedPartInstancesByPartId.get(doc.partId) + if ( + newPartProps && + (newPartProps.segmentId !== doc.segmentId || newPartProps.part._rank !== doc.partRank) + ) { + doc.segmentId = newPartProps.segmentId + doc.partRank = newPartProps.part._rank + changed = true + } } // If the segment for this doc changed, update its rank diff --git a/meteor/server/publications/pieceContentStatusUI/rundown/reactiveContentCache.ts b/meteor/server/publications/pieceContentStatusUI/rundown/reactiveContentCache.ts index ba20fd77ef..d4503e8775 100644 --- a/meteor/server/publications/pieceContentStatusUI/rundown/reactiveContentCache.ts +++ b/meteor/server/publications/pieceContentStatusUI/rundown/reactiveContentCache.ts @@ -13,6 +13,7 @@ import { AdLibAction } from '@sofie-automation/corelib/dist/dataModel/AdlibActio import { RundownBaselineAdLibAction } from '@sofie-automation/corelib/dist/dataModel/RundownBaselineAdLibAction' import { BlueprintId } from '@sofie-automation/corelib/dist/dataModel/Ids' import { PieceInstance } from '@sofie-automation/corelib/dist/dataModel/PieceInstance' +import { DBPartInstance } from '@sofie-automation/corelib/dist/dataModel/PartInstance' export type SourceLayersDocId = ProtectedString<'SourceLayersDocId'> export interface SourceLayersDoc { @@ -54,12 +55,23 @@ export const pieceFieldSpecifier = literal> +>({ + _id: 1, + segmentId: 1, + rundownId: 1, + part: 1, // This could be stricter, but this is unlikely to be changed once the PartInstance is created +}) + +export type PieceInstanceFields = '_id' | 'rundownId' | 'partInstanceId' | 'piece' export const pieceInstanceFieldSpecifier = literal< MongoFieldSpecifierOnesStrict> >({ _id: 1, rundownId: 1, + partInstanceId: 1, piece: 1, // This could be stricter, but this is unlikely to be changed once the PieceInstance is created }) @@ -110,6 +122,7 @@ export interface ContentCache { Segments: ReactiveCacheCollection> Parts: ReactiveCacheCollection> Pieces: ReactiveCacheCollection> + PartInstances: ReactiveCacheCollection> PieceInstances: ReactiveCacheCollection> AdLibPieces: ReactiveCacheCollection> AdLibActions: ReactiveCacheCollection> @@ -124,6 +137,7 @@ export function createReactiveContentCache(): ContentCache { Segments: new ReactiveCacheCollection>('segments'), Parts: new ReactiveCacheCollection>('parts'), Pieces: new ReactiveCacheCollection>('pieces'), + PartInstances: new ReactiveCacheCollection>('partInstances'), PieceInstances: new ReactiveCacheCollection>('pieceInstances'), AdLibPieces: new ReactiveCacheCollection>('adlibPieces'), AdLibActions: new ReactiveCacheCollection>('adlibActions'), diff --git a/meteor/server/publications/pieceContentStatusUI/rundown/regenerateItems.ts b/meteor/server/publications/pieceContentStatusUI/rundown/regenerateItems.ts index 2cbcd4b75e..29ff5deb52 100644 --- a/meteor/server/publications/pieceContentStatusUI/rundown/regenerateItems.ts +++ b/meteor/server/publications/pieceContentStatusUI/rundown/regenerateItems.ts @@ -56,6 +56,8 @@ async function regenerateGenericPiece( segmentName: segment.name, name: wrapTranslatableMessageFromBlueprintsIfNotString(doc.name, [sourceLayersForRundown.blueprintId]), + isPieceInstance: false, + status, }, } @@ -134,29 +136,54 @@ export async function regenerateForPieceInstanceIds( // Piece has been deleted, queue it for batching deletedPieceIds.add(pieceId) } else { - const res = await regenerateGenericPiece( - contentCache, - uiStudio, - { - ...pieceDoc.piece, - _id: protectString(unprotectString(pieceDoc._id)), - }, - pieceDoc.piece.sourceLayerId, - { + // Regenerate piece + const rundown = contentCache.Rundowns.findOne(pieceDoc.rundownId) + const sourceLayersForRundown = rundown + ? contentCache.ShowStyleSourceLayers.findOne(rundown.showStyleBaseId) + : undefined + + const partInstance = pieceDoc.partInstanceId + ? contentCache.PartInstances.findOne(pieceDoc.partInstanceId) + : undefined + const segment = partInstance ? contentCache.Segments.findOne(partInstance.segmentId) : undefined + const sourceLayer = + pieceDoc.piece.sourceLayerId && sourceLayersForRundown?.sourceLayers?.[pieceDoc.piece.sourceLayerId] + + if (partInstance && segment && sourceLayer) { + const [status, dependencies] = await checkPieceContentStatusAndDependencies( + uiStudio, + { + ...pieceDoc.piece, + _id: protectString(unprotectString(pieceDoc._id)), + }, + sourceLayer + ) + + const res: UIPieceContentStatus = { _id: protectString(`piece_${pieceId}`), partId: pieceDoc.piece.startPartId, rundownId: pieceDoc.rundownId, pieceId: pieceId, - name: pieceDoc.piece.name, + segmentId: segment._id, + + segmentRank: segment._rank, + partRank: partInstance.part._rank, + + segmentName: segment.name, + name: wrapTranslatableMessageFromBlueprintsIfNotString(pieceDoc.piece.name, [ + sourceLayersForRundown.blueprintId, + ]), + + isPieceInstance: true, + + status, } - ) - if (res) { - dependenciesState.set(pieceId, res.dependencies) + dependenciesState.set(pieceId, dependencies) - collection.replace(res.doc) + collection.replace(res) } else { deletedPieceIds.add(pieceId) } @@ -323,6 +350,8 @@ export async function regenerateForBaselineAdLibPieceIds( segmentRank: -1, partRank: -1, + isPieceInstance: false, + status, }) } else { @@ -399,6 +428,8 @@ export async function regenerateForBaselineAdLibActionIds( segmentRank: -1, partRank: -1, + isPieceInstance: false, + status, }) } else { diff --git a/meteor/server/publications/pieceContentStatusUI/rundown/rundownContentObserver.ts b/meteor/server/publications/pieceContentStatusUI/rundown/rundownContentObserver.ts index 877fdc35e9..356f4ee8eb 100644 --- a/meteor/server/publications/pieceContentStatusUI/rundown/rundownContentObserver.ts +++ b/meteor/server/publications/pieceContentStatusUI/rundown/rundownContentObserver.ts @@ -6,6 +6,7 @@ import { adLibPieceFieldSpecifier, ContentCache, partFieldSpecifier, + partInstanceFieldSpecifier, pieceFieldSpecifier, pieceInstanceFieldSpecifier, rundownFieldSpecifier, @@ -17,6 +18,7 @@ import { import { AdLibActions, AdLibPieces, + PartInstances, Parts, PieceInstances, Pieces, @@ -138,6 +140,18 @@ export class RundownContentObserver { projection: pieceFieldSpecifier, } ), + PartInstances.observe( + { + rundownId: { + $in: rundownIds, + }, + reset: { $ne: true }, + }, + cache.PartInstances.link(), + { + projection: partInstanceFieldSpecifier, + } + ), PieceInstances.observe( { rundownId: { From f361c89de60adfb62813e2e1409d08a5ec8b4611 Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Fri, 23 Feb 2024 15:00:32 +0000 Subject: [PATCH 3/6] fix: bucket content status publication SOFIE-2993 --- .../publications/pieceContentStatusUI/bucket/publication.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/meteor/server/publications/pieceContentStatusUI/bucket/publication.ts b/meteor/server/publications/pieceContentStatusUI/bucket/publication.ts index 96aba90d4b..bb37c4bc43 100644 --- a/meteor/server/publications/pieceContentStatusUI/bucket/publication.ts +++ b/meteor/server/publications/pieceContentStatusUI/bucket/publication.ts @@ -99,6 +99,7 @@ async function setupUIBucketContentStatusesPublicationObservers( if (!bucket || bucket.studioId !== args.studioId) throw new Error(`Bucket "${args.bucketId}" not found!`) const contentCache = createReactiveContentCache() + triggerUpdate({ newCache: contentCache }) // Set up observers: return [ From 89880fe57bdabc60d8f15b28d14824ede34b7c73 Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Tue, 27 Feb 2024 10:59:19 +0100 Subject: [PATCH 4/6] fix: PieceInstance ExpectedPackages generated with incorrect id --- .../packageManager/expectedPackages/generate.ts | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/meteor/server/publications/packageManager/expectedPackages/generate.ts b/meteor/server/publications/packageManager/expectedPackages/generate.ts index cc9a80ed78..781c584109 100644 --- a/meteor/server/publications/packageManager/expectedPackages/generate.ts +++ b/meteor/server/publications/packageManager/expectedPackages/generate.ts @@ -1,6 +1,11 @@ import { PackageContainerOnPackage, Accessor, AccessorOnPackage } from '@sofie-automation/blueprints-integration' -import { getContentVersionHash } from '@sofie-automation/corelib/dist/dataModel/ExpectedPackages' -import { PeripheralDeviceId, ExpectedPackageId, PieceInstanceId } from '@sofie-automation/corelib/dist/dataModel/Ids' +import { getContentVersionHash, getExpectedPackageId } from '@sofie-automation/corelib/dist/dataModel/ExpectedPackages' +import { + PeripheralDeviceId, + ExpectedPackageId, + PieceInstanceId, + PieceId, +} from '@sofie-automation/corelib/dist/dataModel/Ids' import { protectString, unprotectString } from '@sofie-automation/corelib/dist/protectedString' import { PackageManagerExpectedPackage, @@ -115,7 +120,10 @@ export async function updateCollectionForPieceInstanceIds( if (!pieceInstanceDoc.piece?.expectedPackages) continue pieceInstanceDoc.piece.expectedPackages.forEach((expectedPackage, i) => { - const sanitisedPackageId = expectedPackage._id || '__unnamed' + i + const sanitisedPackageId = getExpectedPackageId( + protectString(unprotectString(pieceInstanceId)), + expectedPackage._id || '__unnamed' + i + ) // Map the expectedPackages onto their specified layer: const allDeviceIds = new Set() @@ -134,7 +142,7 @@ export async function updateCollectionForPieceInstanceIds( studio, { ...expectedPackage, - _id: `${pieceInstanceId}_${sanitisedPackageId}`, + _id: unprotectString(sanitisedPackageId), rundownId: pieceInstanceDoc.rundownId, contentVersionHash: getContentVersionHash(expectedPackage), }, From 3158b09697be14ddcb87868ab4bb2e7cf3532003 Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Tue, 27 Feb 2024 14:34:34 +0100 Subject: [PATCH 5/6] fix: statuses for PieceInstances should copy from the Piece until package-manager catches up --- .../expectedPackages/generate.ts | 12 +- .../checkPieceContentStatus.ts | 135 ++++++++++-------- .../rundown/regenerateItems.ts | 4 +- .../corelib/src/dataModel/ExpectedPackages.ts | 2 + 4 files changed, 85 insertions(+), 68 deletions(-) diff --git a/meteor/server/publications/packageManager/expectedPackages/generate.ts b/meteor/server/publications/packageManager/expectedPackages/generate.ts index 781c584109..495b9a8c29 100644 --- a/meteor/server/publications/packageManager/expectedPackages/generate.ts +++ b/meteor/server/publications/packageManager/expectedPackages/generate.ts @@ -1,11 +1,6 @@ import { PackageContainerOnPackage, Accessor, AccessorOnPackage } from '@sofie-automation/blueprints-integration' import { getContentVersionHash, getExpectedPackageId } from '@sofie-automation/corelib/dist/dataModel/ExpectedPackages' -import { - PeripheralDeviceId, - ExpectedPackageId, - PieceInstanceId, - PieceId, -} from '@sofie-automation/corelib/dist/dataModel/Ids' +import { PeripheralDeviceId, ExpectedPackageId, PieceInstanceId } from '@sofie-automation/corelib/dist/dataModel/Ids' import { protectString, unprotectString } from '@sofie-automation/corelib/dist/protectedString' import { PackageManagerExpectedPackage, @@ -120,10 +115,7 @@ export async function updateCollectionForPieceInstanceIds( if (!pieceInstanceDoc.piece?.expectedPackages) continue pieceInstanceDoc.piece.expectedPackages.forEach((expectedPackage, i) => { - const sanitisedPackageId = getExpectedPackageId( - protectString(unprotectString(pieceInstanceId)), - expectedPackage._id || '__unnamed' + i - ) + const sanitisedPackageId = getExpectedPackageId(pieceInstanceId, expectedPackage._id || '__unnamed' + i) // Map the expectedPackages onto their specified layer: const allDeviceIds = new Set() diff --git a/meteor/server/publications/pieceContentStatusUI/checkPieceContentStatus.ts b/meteor/server/publications/pieceContentStatusUI/checkPieceContentStatus.ts index 72a4a69ec6..b83250f4e3 100644 --- a/meteor/server/publications/pieceContentStatusUI/checkPieceContentStatus.ts +++ b/meteor/server/publications/pieceContentStatusUI/checkPieceContentStatus.ts @@ -11,7 +11,7 @@ import { VTContent, } from '@sofie-automation/blueprints-integration' import { getExpectedPackageId } from '@sofie-automation/corelib/dist/dataModel/ExpectedPackages' -import { ExpectedPackageId, PeripheralDeviceId } from '@sofie-automation/corelib/dist/dataModel/Ids' +import { ExpectedPackageId, PeripheralDeviceId, PieceInstanceId } from '@sofie-automation/corelib/dist/dataModel/Ids' import { getPackageContainerPackageId, PackageContainerPackageStatusDB, @@ -156,7 +156,9 @@ export function getMediaObjectMediaId( return undefined } -export type PieceContentStatusPiece = Pick +export type PieceContentStatusPiece = Pick & { + pieceInstanceId?: PieceInstanceId +} export interface PieceContentStatusStudio extends Pick< Studio, @@ -506,60 +508,78 @@ async function checkPieceContentExpectedPackageStatus( checkedPackageContainers.add(packageContainerId) - const expectedPackageId = getExpectedPackageId(piece._id, expectedPackage._id) - const packageOnPackageContainer = await getPackageContainerPackageStatus( - packageContainerId, - expectedPackageId - ) - const packageName = - // @ts-expect-error hack - expectedPackage.content.filePath || - // @ts-expect-error hack - expectedPackage.content.guid || - expectedPackage._id - - if (!thumbnailUrl && packageOnPackageContainer) { - const sideEffect = getSideEffect(expectedPackage, studio) - - const packageThumbnailPath = sideEffect.thumbnailPackageSettings?.path - const thumbnailContainerId = sideEffect.thumbnailContainerId - if (packageThumbnailPath && thumbnailContainerId) { - thumbnailUrl = getAssetUrlFromExpectedPackages( - packageThumbnailPath, - thumbnailContainerId, - studio, - packageOnPackageContainer - ) - } + const expectedPackageIds = [getExpectedPackageId(piece._id, expectedPackage._id)] + if (piece.pieceInstanceId) { + // If this is a PieceInstance, try looking up the PieceInstance first + expectedPackageIds.unshift(getExpectedPackageId(piece.pieceInstanceId, expectedPackage._id)) } - if (!previewUrl && packageOnPackageContainer) { - const sideEffect = getSideEffect(expectedPackage, studio) - - const packagePreviewPath = sideEffect.previewPackageSettings?.path - const previewContainerId = sideEffect.previewContainerId - if (packagePreviewPath && previewContainerId) { - previewUrl = getAssetUrlFromExpectedPackages( - packagePreviewPath, - previewContainerId, - studio, - packageOnPackageContainer - ) + let warningMessage: ContentMessage | null = null + let matchedExpectedPackageId: ExpectedPackageId | null = null + for (const expectedPackageId of expectedPackageIds) { + const packageOnPackageContainer = await getPackageContainerPackageStatus( + packageContainerId, + expectedPackageId + ) + if (!packageOnPackageContainer) continue + + matchedExpectedPackageId = expectedPackageId + + if (!thumbnailUrl) { + const sideEffect = getSideEffect(expectedPackage, studio) + + const packageThumbnailPath = sideEffect.thumbnailPackageSettings?.path + const thumbnailContainerId = sideEffect.thumbnailContainerId + if (packageThumbnailPath && thumbnailContainerId) { + thumbnailUrl = getAssetUrlFromExpectedPackages( + packageThumbnailPath, + thumbnailContainerId, + studio, + packageOnPackageContainer + ) + } + } + + if (!previewUrl) { + const sideEffect = getSideEffect(expectedPackage, studio) + + const packagePreviewPath = sideEffect.previewPackageSettings?.path + const previewContainerId = sideEffect.previewContainerId + if (packagePreviewPath && previewContainerId) { + previewUrl = getAssetUrlFromExpectedPackages( + packagePreviewPath, + previewContainerId, + studio, + packageOnPackageContainer + ) + } } + + warningMessage = getPackageWarningMessage(packageOnPackageContainer, sourceLayer) + + // Found a packageOnPackageContainer + break } - const warningMessage = getPackageWarningMessage(packageOnPackageContainer, sourceLayer) - if (warningMessage) { - messages.push(warningMessage) + if (!matchedExpectedPackageId || warningMessage) { + // If no package matched, we must have a warning + messages.push(warningMessage ?? getPackageSoruceMissingWarning(sourceLayer)) } else { // No warning, must be OK + const packageName = + // @ts-expect-error hack + expectedPackage.content.filePath || + // @ts-expect-error hack + expectedPackage.content.guid || + expectedPackage._id + readyCount++ packageInfos[expectedPackage._id] = { packageName, } // Fetch scan-info about the package: - const dbPackageInfos = await getPackageInfos(expectedPackageId) + const dbPackageInfos = await getPackageInfos(matchedExpectedPackageId) for (const packageInfo of dbPackageInfos) { if (packageInfo.type === PackageInfo.Type.SCAN) { packageInfos[expectedPackage._id].scan = packageInfo.payload @@ -689,25 +709,28 @@ function getAssetUrlFromExpectedPackages( } } +function getPackageSoruceMissingWarning(sourceLayer: ISourceLayer): ContentMessage { + // Examples of contents in packageOnPackageContainer?.status.statusReason.user: + // * Target package: Quantel clip "XXX" not found + // * Can't read the Package from PackageContainer "Quantel source 0" (on accessor "${accessorLabel}"), due to: Quantel clip "XXX" not found + + return { + status: PieceStatusCode.SOURCE_MISSING, + message: generateTranslation(`{{sourceLayer}} can't be found on the playout system`, { + sourceLayer: sourceLayer.name, + }), + } +} + function getPackageWarningMessage( - packageOnPackageContainer: Pick | undefined, + packageOnPackageContainer: Pick, sourceLayer: ISourceLayer ): ContentMessage | null { if ( - !packageOnPackageContainer || packageOnPackageContainer.status.status === - ExpectedPackageStatusAPI.PackageContainerPackageStatusStatus.NOT_FOUND + ExpectedPackageStatusAPI.PackageContainerPackageStatusStatus.NOT_FOUND ) { - // Examples of contents in packageOnPackageContainer?.status.statusReason.user: - // * Target package: Quantel clip "XXX" not found - // * Can't read the Package from PackageContainer "Quantel source 0" (on accessor "${accessorLabel}"), due to: Quantel clip "XXX" not found - - return { - status: PieceStatusCode.SOURCE_MISSING, - message: generateTranslation(`{{sourceLayer}} can't be found on the playout system`, { - sourceLayer: sourceLayer.name, - }), - } + return getPackageSoruceMissingWarning(sourceLayer) } else if ( packageOnPackageContainer.status.status === ExpectedPackageStatusAPI.PackageContainerPackageStatusStatus.NOT_READY diff --git a/meteor/server/publications/pieceContentStatusUI/rundown/regenerateItems.ts b/meteor/server/publications/pieceContentStatusUI/rundown/regenerateItems.ts index 29ff5deb52..8ea1221efc 100644 --- a/meteor/server/publications/pieceContentStatusUI/rundown/regenerateItems.ts +++ b/meteor/server/publications/pieceContentStatusUI/rundown/regenerateItems.ts @@ -7,7 +7,7 @@ import { } from '@sofie-automation/corelib/dist/dataModel/Ids' import { ReadonlyDeep } from 'type-fest' import { UIPieceContentStatus } from '../../../../lib/api/rundownNotifications' -import { literal, protectString, unprotectString } from '../../../../lib/lib' +import { literal, protectString } from '../../../../lib/lib' import { CustomPublishCollection } from '../../../lib/customPublication' import { ContentCache } from './reactiveContentCache' import { wrapTranslatableMessageFromBlueprintsIfNotString } from '@sofie-automation/corelib/dist/TranslatableMessage' @@ -154,7 +154,7 @@ export async function regenerateForPieceInstanceIds( uiStudio, { ...pieceDoc.piece, - _id: protectString(unprotectString(pieceDoc._id)), + pieceInstanceId: pieceDoc._id, }, sourceLayer ) diff --git a/packages/corelib/src/dataModel/ExpectedPackages.ts b/packages/corelib/src/dataModel/ExpectedPackages.ts index afe2d905e9..dc28197ec0 100644 --- a/packages/corelib/src/dataModel/ExpectedPackages.ts +++ b/packages/corelib/src/dataModel/ExpectedPackages.ts @@ -8,6 +8,7 @@ import { BucketId, ExpectedPackageId, PieceId, + PieceInstanceId, RundownBaselineAdLibActionId, RundownId, SegmentId, @@ -134,6 +135,7 @@ export function getExpectedPackageId( /** _id of the owner (the piece, adlib etc..) */ ownerId: | PieceId + | PieceInstanceId | AdLibActionId | RundownBaselineAdLibActionId | BucketAdLibId From 391bfc47e25a47b07a845570e8dbe8d256c18ec1 Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Wed, 28 Feb 2024 15:55:38 +0100 Subject: [PATCH 6/6] fix: thumbnail and scrub previews being reported as available too early --- .../checkPieceContentStatus.ts | 61 +++++++++++-------- 1 file changed, 36 insertions(+), 25 deletions(-) diff --git a/meteor/server/publications/pieceContentStatusUI/checkPieceContentStatus.ts b/meteor/server/publications/pieceContentStatusUI/checkPieceContentStatus.ts index b83250f4e3..7b24ff79b3 100644 --- a/meteor/server/publications/pieceContentStatusUI/checkPieceContentStatus.ts +++ b/meteor/server/publications/pieceContentStatusUI/checkPieceContentStatus.ts @@ -528,31 +528,25 @@ async function checkPieceContentExpectedPackageStatus( if (!thumbnailUrl) { const sideEffect = getSideEffect(expectedPackage, studio) - const packageThumbnailPath = sideEffect.thumbnailPackageSettings?.path - const thumbnailContainerId = sideEffect.thumbnailContainerId - if (packageThumbnailPath && thumbnailContainerId) { - thumbnailUrl = getAssetUrlFromExpectedPackages( - packageThumbnailPath, - thumbnailContainerId, - studio, - packageOnPackageContainer - ) - } + thumbnailUrl = await getAssetUrlFromPackageContainerStatus( + studio, + getPackageContainerPackageStatus, + expectedPackageId, + sideEffect.thumbnailContainerId, + sideEffect.thumbnailPackageSettings?.path + ) } if (!previewUrl) { const sideEffect = getSideEffect(expectedPackage, studio) - const packagePreviewPath = sideEffect.previewPackageSettings?.path - const previewContainerId = sideEffect.previewContainerId - if (packagePreviewPath && previewContainerId) { - previewUrl = getAssetUrlFromExpectedPackages( - packagePreviewPath, - previewContainerId, - studio, - packageOnPackageContainer - ) - } + previewUrl = await getAssetUrlFromPackageContainerStatus( + studio, + getPackageContainerPackageStatus, + expectedPackageId, + sideEffect.previewContainerId, + sideEffect.previewPackageSettings?.path + ) } warningMessage = getPackageWarningMessage(packageOnPackageContainer, sourceLayer) @@ -678,15 +672,32 @@ async function checkPieceContentExpectedPackageStatus( } } +async function getAssetUrlFromPackageContainerStatus( + studio: PieceContentStatusStudio, + getPackageContainerPackageStatus: ( + packageContainerId: string, + expectedPackageId: ExpectedPackageId + ) => Promise, + expectedPackageId: ExpectedPackageId, + assetContainerId: string | null | undefined, + packageAssetPath: string | undefined +): Promise { + if (!assetContainerId || !packageAssetPath) return + + const assetPackageContainer = studio.packageContainers[assetContainerId] + if (!assetPackageContainer) return + + const previewPackageOnPackageContainer = await getPackageContainerPackageStatus(assetContainerId, expectedPackageId) + if (!previewPackageOnPackageContainer) return + + return getAssetUrlFromExpectedPackages(packageAssetPath, assetPackageContainer, previewPackageOnPackageContainer) +} + function getAssetUrlFromExpectedPackages( assetPath: string, - assetContainerId: string, - studio: PieceContentStatusStudio, + packageContainer: StudioPackageContainer, packageOnPackageContainer: Pick ): string | undefined { - const packageContainer = studio.packageContainers[assetContainerId] - if (!packageContainer) return - if (packageOnPackageContainer.status.status !== ExpectedPackageStatusAPI.PackageContainerPackageStatusStatus.READY) return