From aaabae16957b3097fb684b0d14d308f4a7197773 Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Mon, 11 Nov 2024 15:42:23 +0000 Subject: [PATCH 01/14] wip: boilerplate --- meteor/__mocks__/helpers/database.ts | 1 + .../serviceMessagesApi.test.ts | 1 + meteor/server/coreSystem/index.ts | 1 + meteor/server/migration/databaseMigration.ts | 4 + meteor/server/migration/upgrades/lib.ts | 76 ++++++++++++++++++ .../migration/upgrades/showStyleBase.ts | 80 ++----------------- meteor/server/migration/upgrades/system.ts | 63 +++++++++++++++ .../blueprints-integration/src/api/system.ts | 35 +++++++- .../meteor-lib/src/collections/CoreSystem.ts | 7 ++ 9 files changed, 194 insertions(+), 74 deletions(-) create mode 100644 meteor/server/migration/upgrades/lib.ts create mode 100644 meteor/server/migration/upgrades/system.ts diff --git a/meteor/__mocks__/helpers/database.ts b/meteor/__mocks__/helpers/database.ts index 6abd5a60bf..a1e72469d4 100644 --- a/meteor/__mocks__/helpers/database.ts +++ b/meteor/__mocks__/helpers/database.ts @@ -171,6 +171,7 @@ export async function setupMockCore(doc?: Partial): Promise { diff --git a/meteor/server/coreSystem/index.ts b/meteor/server/coreSystem/index.ts index 95f4b74080..407682989d 100644 --- a/meteor/server/coreSystem/index.ts +++ b/meteor/server/coreSystem/index.ts @@ -64,6 +64,7 @@ async function initializeCoreSystem() { enabled: true, }, }, + lastBlueprintConfig: undefined, }) if (!isRunningInJest()) { diff --git a/meteor/server/migration/databaseMigration.ts b/meteor/server/migration/databaseMigration.ts index 42b0d76b1e..b342be20fd 100644 --- a/meteor/server/migration/databaseMigration.ts +++ b/meteor/server/migration/databaseMigration.ts @@ -254,6 +254,10 @@ export async function prepareMigration(returnAllChunks?: boolean): Promise { + const oldTriggeredActionsArray = await TriggeredActions.findFetchAsync({ + showStyleBaseId: showStyleBaseId, + blueprintUniqueId: { $ne: null }, + }) + const oldTriggeredActions = normalizeArrayToMap(oldTriggeredActionsArray, 'blueprintUniqueId') + + const newDocIds: TriggeredActionId[] = [] + const bulkOps: AnyBulkWriteOperation[] = [] + + for (const newTriggeredAction of triggeredActions) { + const oldValue = oldTriggeredActions.get(newTriggeredAction._id) + if (oldValue) { + // Update an existing TriggeredAction + newDocIds.push(oldValue._id) + bulkOps.push({ + updateOne: { + filter: { + _id: oldValue._id, + }, + update: { + $set: { + _rank: newTriggeredAction._rank, + name: newTriggeredAction.name, + 'triggersWithOverrides.defaults': newTriggeredAction.triggers, + 'actionsWithOverrides.defaults': newTriggeredAction.actions, + }, + }, + }, + }) + } else { + // Insert a new TriggeredAction + const newDocId = getRandomId() + newDocIds.push(newDocId) + bulkOps.push({ + insertOne: { + document: literal>({ + _id: newDocId, + _rank: newTriggeredAction._rank, + name: newTriggeredAction.name, + showStyleBaseId: showStyleBaseId, + blueprintUniqueId: newTriggeredAction._id, + triggersWithOverrides: wrapDefaultObject(newTriggeredAction.triggers), + actionsWithOverrides: wrapDefaultObject(newTriggeredAction.actions), + styleClassNames: newTriggeredAction.styleClassNames, + }), + }, + }) + } + } + + // Remove any removed TriggeredAction + // Future: should this orphan them or something? Will that cause issues if they get re-added? + bulkOps.push({ + deleteMany: { + filter: { + showStyleBaseId: showStyleBaseId, + blueprintUniqueId: { $ne: null }, + _id: { $nin: newDocIds }, + }, + }, + }) + + await TriggeredActions.bulkWriteAsync(bulkOps) +} diff --git a/meteor/server/migration/upgrades/showStyleBase.ts b/meteor/server/migration/upgrades/showStyleBase.ts index d2281043ae..bcbe015617 100644 --- a/meteor/server/migration/upgrades/showStyleBase.ts +++ b/meteor/server/migration/upgrades/showStyleBase.ts @@ -3,25 +3,21 @@ import { JSONBlobParse, ShowStyleBlueprintManifest, } from '@sofie-automation/blueprints-integration' -import { ShowStyleBaseId, TriggeredActionId } from '@sofie-automation/corelib/dist/dataModel/Ids' -import { normalizeArray, normalizeArrayToMap, getRandomId, literal, Complete } from '@sofie-automation/corelib/dist/lib' -import { - applyAndValidateOverrides, - wrapDefaultObject, -} from '@sofie-automation/corelib/dist/settings/objectWithOverrides' +import { ShowStyleBaseId } from '@sofie-automation/corelib/dist/dataModel/Ids' +import { normalizeArray } from '@sofie-automation/corelib/dist/lib' +import { applyAndValidateOverrides } from '@sofie-automation/corelib/dist/settings/objectWithOverrides' import { wrapTranslatableMessageFromBlueprints } from '@sofie-automation/corelib/dist/TranslatableMessage' import { BlueprintValidateConfigForStudioResult } from '@sofie-automation/corelib/dist/worker/studio' import { Meteor } from 'meteor/meteor' -import { Blueprints, ShowStyleBases, TriggeredActions } from '../../collections' +import { Blueprints, ShowStyleBases } from '../../collections' import { DBShowStyleBase } from '@sofie-automation/corelib/dist/dataModel/ShowStyleBase' -import { DBTriggeredActions } from '@sofie-automation/meteor-lib/dist/collections/TriggeredActions' import { evalBlueprint } from '../../api/blueprints/cache' import { logger } from '../../logging' import { CommonContext } from './context' -import type { AnyBulkWriteOperation } from 'mongodb' import { FixUpBlueprintConfigContext } from '@sofie-automation/corelib/dist/fixUpBlueprintConfig/context' import { Blueprint } from '@sofie-automation/corelib/dist/dataModel/Blueprint' import { BlueprintFixUpConfigMessage } from '@sofie-automation/meteor-lib/dist/api/migration' +import { updateTriggeredActionsForShowStyleBaseId } from './lib' export async function fixupConfigForShowStyleBase( showStyleBaseId: ShowStyleBaseId @@ -100,7 +96,7 @@ export async function validateConfigForShowStyleBase( throwIfNeedsFixupConfigRunning(showStyleBase, blueprint, blueprintManifest) const blueprintContext = new CommonContext( - 'applyConfig', + 'validateConfig', `showStyleBase:${showStyleBaseId},blueprint:${blueprint._id}` ) const rawBlueprintConfig = applyAndValidateOverrides(showStyleBase.blueprintConfigWithOverrides).obj @@ -146,69 +142,7 @@ export async function runUpgradeForShowStyleBase(showStyleBaseId: ShowStyleBaseI }, }) - const oldTriggeredActionsArray = await TriggeredActions.findFetchAsync({ - showStyleBaseId: showStyleBaseId, - blueprintUniqueId: { $ne: null }, - }) - const oldTriggeredActions = normalizeArrayToMap(oldTriggeredActionsArray, 'blueprintUniqueId') - - const newDocIds: TriggeredActionId[] = [] - const bulkOps: AnyBulkWriteOperation[] = [] - - for (const newTriggeredAction of result.triggeredActions) { - const oldValue = oldTriggeredActions.get(newTriggeredAction._id) - if (oldValue) { - // Update an existing TriggeredAction - newDocIds.push(oldValue._id) - bulkOps.push({ - updateOne: { - filter: { - _id: oldValue._id, - }, - update: { - $set: { - _rank: newTriggeredAction._rank, - name: newTriggeredAction.name, - 'triggersWithOverrides.defaults': newTriggeredAction.triggers, - 'actionsWithOverrides.defaults': newTriggeredAction.actions, - }, - }, - }, - }) - } else { - // Insert a new TriggeredAction - const newDocId = getRandomId() - newDocIds.push(newDocId) - bulkOps.push({ - insertOne: { - document: literal>({ - _id: newDocId, - _rank: newTriggeredAction._rank, - name: newTriggeredAction.name, - showStyleBaseId: showStyleBaseId, - blueprintUniqueId: newTriggeredAction._id, - triggersWithOverrides: wrapDefaultObject(newTriggeredAction.triggers), - actionsWithOverrides: wrapDefaultObject(newTriggeredAction.actions), - styleClassNames: newTriggeredAction.styleClassNames, - }), - }, - }) - } - } - - // Remove any removed TriggeredAction - // Future: should this orphan them or something? Will that cause issues if they get re-added? - bulkOps.push({ - deleteMany: { - filter: { - showStyleBaseId: showStyleBaseId, - blueprintUniqueId: { $ne: null }, - _id: { $nin: newDocIds }, - }, - }, - }) - - await TriggeredActions.bulkWriteAsync(bulkOps) + await updateTriggeredActionsForShowStyleBaseId(showStyleBaseId, result.triggeredActions) } async function loadShowStyleAndBlueprint(showStyleBaseId: ShowStyleBaseId) { diff --git a/meteor/server/migration/upgrades/system.ts b/meteor/server/migration/upgrades/system.ts new file mode 100644 index 0000000000..da0669ec94 --- /dev/null +++ b/meteor/server/migration/upgrades/system.ts @@ -0,0 +1,63 @@ +import { Meteor } from 'meteor/meteor' +import { getCoreSystemAsync } from '../../coreSystem/collection' +import { logger } from '../../logging' +import { Blueprints, CoreSystem } from '../../collections' +import { BlueprintManifestType, SystemBlueprintManifest } from '@sofie-automation/blueprints-integration' +import { evalBlueprint } from '../../api/blueprints/cache' +import { CommonContext } from './context' +import { updateTriggeredActionsForShowStyleBaseId } from './lib' +import { SYSTEM_ID } from '@sofie-automation/meteor-lib/dist/collections/CoreSystem' + +export async function runUpgradeForCoreSystem(): Promise { + logger.info(`Running upgrade for CoreSystem`) + + const { coreSystem, blueprint, blueprintManifest } = await loadCoreSystemAndBlueprint() + + if (typeof blueprintManifest.applyConfig !== 'function') + throw new Meteor.Error(500, 'Blueprint does not support this config flow') + + const blueprintContext = new CommonContext( + 'applyConfig', + `coreSystem:${coreSystem._id},blueprint:${blueprint.blueprintId}` + ) + + const result = blueprintManifest.applyConfig(blueprintContext) + + await CoreSystem.updateAsync(SYSTEM_ID, { + $set: { + // 'sourceLayersWithOverrides.defaults': normalizeArray(result.sourceLayers, '_id'), + // 'outputLayersWithOverrides.defaults': normalizeArray(result.outputLayers, '_id'), + lastBlueprintConfig: { + blueprintHash: blueprint.blueprintHash, + blueprintId: blueprint._id, + }, + }, + }) + + await updateTriggeredActionsForShowStyleBaseId(null, result.triggeredActions) +} + +async function loadCoreSystemAndBlueprint() { + const coreSystem = await getCoreSystemAsync() + if (!coreSystem) throw new Meteor.Error(404, `CoreSystem not found!`) + + // if (!showStyleBase.blueprintConfigPresetId) throw new Meteor.Error(500, 'ShowStyleBase is missing config preset') + + const blueprint = coreSystem.blueprintId + ? await Blueprints.findOneAsync({ + _id: coreSystem.blueprintId, + blueprintType: BlueprintManifestType.SYSTEM, + }) + : undefined + if (!blueprint) throw new Meteor.Error(404, `Blueprint "${coreSystem.blueprintId}" not found!`) + + if (!blueprint.blueprintHash) throw new Meteor.Error(500, 'Blueprint is not valid') + + const blueprintManifest = evalBlueprint(blueprint) as SystemBlueprintManifest + + return { + coreSystem, + blueprint, + blueprintManifest, + } +} diff --git a/packages/blueprints-integration/src/api/system.ts b/packages/blueprints-integration/src/api/system.ts index 078ab6ffed..8a90ff889b 100644 --- a/packages/blueprints-integration/src/api/system.ts +++ b/packages/blueprints-integration/src/api/system.ts @@ -1,12 +1,45 @@ +import type { IBlueprintTriggeredActions } from '../triggers' +import type { ICommonContext } from '../context' import type { MigrationStepSystem } from '../migrations' import type { BlueprintManifestBase, BlueprintManifestType } from './base' export interface SystemBlueprintManifest extends BlueprintManifestBase { blueprintType: BlueprintManifestType.SYSTEM - /** A list of Migration steps related to the Core system */ + /** A list of Migration steps related to the Core system + * @deprecated This has been replaced with `validateConfig` and `applyConfig` + */ coreMigrations: MigrationStepSystem[] /** Translations connected to the studio (as stringified JSON) */ translations?: string + + // /** + // * Apply automatic upgrades to the structure of user specified config overrides + // * This lets you apply various changes to the user's values in an abstract way + // */ + // fixUpConfig?: (context: IFixUpConfigContext) => void + + // /** + // * Validate the config passed to this blueprint + // * In this you should do various sanity checks of the config and return a list of messages to display to the user. + // * These messages do not stop `applyConfig` from being called. + // */ + // validateConfig?: (context: ICommonContext, config: TRawConfig) => Array + + /** + * Apply the config by generating the data to be saved into the db. + * This should be written to give a predictable and stable result, it can be called with the same config multiple times + */ + applyConfig?: ( + context: ICommonContext + // config: TRawConfig, + ) => BlueprintResultApplySystemConfig +} + +export interface BlueprintResultApplySystemConfig { + // sourceLayers: ISourceLayer[] + // outputLayers: IOutputLayer[] + + triggeredActions: IBlueprintTriggeredActions[] } diff --git a/packages/meteor-lib/src/collections/CoreSystem.ts b/packages/meteor-lib/src/collections/CoreSystem.ts index 5a8f58641b..c2d9dc7210 100644 --- a/packages/meteor-lib/src/collections/CoreSystem.ts +++ b/packages/meteor-lib/src/collections/CoreSystem.ts @@ -1,3 +1,4 @@ +import { LastBlueprintConfig } from '@sofie-automation/corelib/dist/dataModel/Blueprint' import { LogLevel } from '../lib' import { CoreSystemId, BlueprintId } from '@sofie-automation/corelib/dist/dataModel/Ids' import { protectString } from '@sofie-automation/corelib/dist/protectedString' @@ -107,6 +108,12 @@ export interface ICoreSystem { } logo?: SofieLogo + + /** Details on the last blueprint used to generate the defaults values for this + * Note: This doesn't currently have any 'config' which it relates to. + * The name is to be consistent with studio/showstyle, and in preparation for their being config/configpresets used here + */ + lastBlueprintConfig: Omit | undefined } /** In the beginning, there was the database, and the database was with Sofie, and the database was Sofie. From 055e580f08b0a5e6edde8dc14ce50ba88565074d Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Mon, 11 Nov 2024 16:03:41 +0000 Subject: [PATCH 02/14] wip --- meteor/server/migration/api.ts | 7 +++ meteor/server/migration/upgrades/system.ts | 17 +++--- .../blueprintUpgradeStatus/checkStatus.ts | 7 ++- .../blueprintUpgradeStatus/publication.ts | 56 ++++++++++++++++++- .../reactiveContentCache.ts | 21 +++++++ .../upgradesContentObserver.ts | 6 +- packages/meteor-lib/src/api/migration.ts | 18 +++++- packages/meteor-lib/src/api/upgradeStatus.ts | 16 ++++-- .../meteor-lib/src/collections/CoreSystem.ts | 7 ++- 9 files changed, 133 insertions(+), 22 deletions(-) diff --git a/meteor/server/migration/api.ts b/meteor/server/migration/api.ts index fd4fac48e1..da257512ad 100644 --- a/meteor/server/migration/api.ts +++ b/meteor/server/migration/api.ts @@ -22,6 +22,7 @@ import { } from './upgrades' import { ShowStyleBaseId, StudioId } from '@sofie-automation/corelib/dist/dataModel/Ids' import { BlueprintValidateConfigForStudioResult } from '@sofie-automation/corelib/dist/worker/studio' +import { runUpgradeForCoreSystem } from './upgrades/system' class ServerMigrationAPI extends MethodContextAPI implements NewMigrationAPI { async getMigrationStatus() { @@ -123,5 +124,11 @@ class ServerMigrationAPI extends MethodContextAPI implements NewMigrationAPI { return runUpgradeForShowStyleBase(showStyleBaseId) } + + async runUpgradeForCoreSystem(): Promise { + await SystemWriteAccess.migrations(this) + + return runUpgradeForCoreSystem() + } } registerClassToMeteorMethods(MigrationAPIMethods, ServerMigrationAPI, false) diff --git a/meteor/server/migration/upgrades/system.ts b/meteor/server/migration/upgrades/system.ts index da0669ec94..51bbb6e631 100644 --- a/meteor/server/migration/upgrades/system.ts +++ b/meteor/server/migration/upgrades/system.ts @@ -1,17 +1,16 @@ import { Meteor } from 'meteor/meteor' -import { getCoreSystemAsync } from '../../coreSystem/collection' import { logger } from '../../logging' import { Blueprints, CoreSystem } from '../../collections' import { BlueprintManifestType, SystemBlueprintManifest } from '@sofie-automation/blueprints-integration' import { evalBlueprint } from '../../api/blueprints/cache' import { CommonContext } from './context' import { updateTriggeredActionsForShowStyleBaseId } from './lib' -import { SYSTEM_ID } from '@sofie-automation/meteor-lib/dist/collections/CoreSystem' +import { CoreSystemId } from '@sofie-automation/corelib/dist/dataModel/Ids' -export async function runUpgradeForCoreSystem(): Promise { +export async function runUpgradeForCoreSystem(coreSystemId: CoreSystemId): Promise { logger.info(`Running upgrade for CoreSystem`) - const { coreSystem, blueprint, blueprintManifest } = await loadCoreSystemAndBlueprint() + const { coreSystem, blueprint, blueprintManifest } = await loadCoreSystemAndBlueprint(coreSystemId) if (typeof blueprintManifest.applyConfig !== 'function') throw new Meteor.Error(500, 'Blueprint does not support this config flow') @@ -23,13 +22,15 @@ export async function runUpgradeForCoreSystem(): Promise { const result = blueprintManifest.applyConfig(blueprintContext) - await CoreSystem.updateAsync(SYSTEM_ID, { + await CoreSystem.updateAsync(coreSystemId, { $set: { // 'sourceLayersWithOverrides.defaults': normalizeArray(result.sourceLayers, '_id'), // 'outputLayersWithOverrides.defaults': normalizeArray(result.outputLayers, '_id'), lastBlueprintConfig: { blueprintHash: blueprint.blueprintHash, blueprintId: blueprint._id, + blueprintConfigPresetId: '', + config: {}, }, }, }) @@ -37,9 +38,9 @@ export async function runUpgradeForCoreSystem(): Promise { await updateTriggeredActionsForShowStyleBaseId(null, result.triggeredActions) } -async function loadCoreSystemAndBlueprint() { - const coreSystem = await getCoreSystemAsync() - if (!coreSystem) throw new Meteor.Error(404, `CoreSystem not found!`) +async function loadCoreSystemAndBlueprint(coreSystemId: CoreSystemId) { + const coreSystem = await CoreSystem.findOneAsync(coreSystemId) + if (!coreSystem) throw new Meteor.Error(404, `CoreSystem "${coreSystemId}" not found!`) // if (!showStyleBase.blueprintConfigPresetId) throw new Meteor.Error(500, 'ShowStyleBase is missing config preset') diff --git a/meteor/server/publications/blueprintUpgradeStatus/checkStatus.ts b/meteor/server/publications/blueprintUpgradeStatus/checkStatus.ts index 5567ab0fb3..24e239ab13 100644 --- a/meteor/server/publications/blueprintUpgradeStatus/checkStatus.ts +++ b/meteor/server/publications/blueprintUpgradeStatus/checkStatus.ts @@ -16,10 +16,11 @@ import { joinObjectPathFragments, objectPathGet } from '@sofie-automation/coreli import { applyAndValidateOverrides } from '@sofie-automation/corelib/dist/settings/objectWithOverrides' import { generateTranslation } from '../../lib/tempLib' import { logger } from '../../logging' -import { ShowStyleBaseFields, StudioFields } from './reactiveContentCache' +import { CoreSystemFields, ShowStyleBaseFields, StudioFields } from './reactiveContentCache' import _ from 'underscore' import { UIBlueprintUpgradeStatusBase } from '@sofie-automation/meteor-lib/dist/api/upgradeStatus' import { stringifyError } from '@sofie-automation/shared-lib/dist/lib/stringifyError' +import { ICoreSystem } from '@sofie-automation/meteor-lib/dist/collections/CoreSystem' export interface BlueprintMapEntry { _id: BlueprintId @@ -31,7 +32,7 @@ export interface BlueprintMapEntry { export function checkDocUpgradeStatus( blueprintMap: Map, - doc: Pick | Pick + doc: Pick | Pick | Pick ): Pick { // Check the blueprintId is valid const blueprint = doc.blueprintId ? blueprintMap.get(doc.blueprintId) : null @@ -101,7 +102,7 @@ export function checkDocUpgradeStatus( changes.push(generateTranslation('Blueprint has a new version')) } - if (doc.lastBlueprintConfig) { + if (doc.lastBlueprintConfig && doc.blueprintConfigWithOverrides) { // Check if the config blob has changed since last run const newConfig = applyAndValidateOverrides(doc.blueprintConfigWithOverrides).obj const oldConfig = doc.lastBlueprintConfig.config diff --git a/meteor/server/publications/blueprintUpgradeStatus/publication.ts b/meteor/server/publications/blueprintUpgradeStatus/publication.ts index 568f9b0756..c81b4e6520 100644 --- a/meteor/server/publications/blueprintUpgradeStatus/publication.ts +++ b/meteor/server/publications/blueprintUpgradeStatus/publication.ts @@ -13,7 +13,13 @@ import { import { logger } from '../../logging' import { resolveCredentials } from '../../security/lib/credentials' import { NoSecurityReadAccess } from '../../security/noSecurity' -import { ContentCache, createReactiveContentCache, ShowStyleBaseFields, StudioFields } from './reactiveContentCache' +import { + ContentCache, + CoreSystemFields, + createReactiveContentCache, + ShowStyleBaseFields, + StudioFields, +} from './reactiveContentCache' import { UpgradesContentObserver } from './upgradesContentObserver' import { BlueprintMapEntry, checkDocUpgradeStatus } from './checkStatus' import { BlueprintManifestType } from '@sofie-automation/blueprints-integration' @@ -23,6 +29,7 @@ import { UIBlueprintUpgradeStatus, UIBlueprintUpgradeStatusId, } from '@sofie-automation/meteor-lib/dist/api/upgradeStatus' +import { ICoreSystem } from '@sofie-automation/meteor-lib/dist/collections/CoreSystem' type BlueprintUpgradeStatusArgs = Record @@ -33,6 +40,7 @@ export interface BlueprintUpgradeStatusState { interface BlueprintUpgradeStatusUpdateProps { newCache: ContentCache + invalidateSystem: boolean invalidateStudioIds: StudioId[] invalidateShowStyleBaseIds: ShowStyleBaseId[] invalidateBlueprintIds: BlueprintId[] @@ -54,6 +62,11 @@ async function setupBlueprintUpgradeStatusPublicationObservers( return [ mongoObserver, + cache.CoreSystem.find({}).observeChanges({ + added: () => triggerUpdate({ invalidateSystem: true }), + changed: () => triggerUpdate({ invalidateSystem: true }), + removed: () => triggerUpdate({ invalidateSystem: true }), + }), cache.Studios.find({}).observeChanges({ added: (id) => triggerUpdate({ invalidateStudioIds: [protectString(id)] }), changed: (id) => triggerUpdate({ invalidateStudioIds: [protectString(id)] }), @@ -72,7 +85,10 @@ async function setupBlueprintUpgradeStatusPublicationObservers( ] } -function getDocumentId(type: 'studio' | 'showStyle', id: ProtectedString): UIBlueprintUpgradeStatusId { +function getDocumentId( + type: 'coreSystem' | 'studio' | 'showStyle', + id: ProtectedString +): UIBlueprintUpgradeStatusId { return protectString(`${type}:${id}`) } @@ -100,6 +116,7 @@ export async function manipulateBlueprintUpgradeStatusPublicationData( const studioBlueprintsMap = new Map() const showStyleBlueprintsMap = new Map() + const systemBlueprintsMap = new Map() state.contentCache.Blueprints.find({}).forEach((blueprint) => { switch (blueprint.blueprintType) { case BlueprintManifestType.SHOWSTYLE: @@ -120,6 +137,15 @@ export async function manipulateBlueprintUpgradeStatusPublicationData( hasFixUpFunction: blueprint.hasFixUpFunction, }) break + case BlueprintManifestType.SYSTEM: + systemBlueprintsMap.set(blueprint._id, { + _id: blueprint._id, + configPresets: {}, + configSchema: undefined, // TODO + blueprintHash: blueprint.blueprintHash, + hasFixUpFunction: false, + }) + break // TODO - default? } }) @@ -136,6 +162,10 @@ export async function manipulateBlueprintUpgradeStatusPublicationData( state.contentCache.ShowStyleBases.find({}).forEach((showStyleBase) => { updateShowStyleUpgradeStatus(collection, showStyleBlueprintsMap, showStyleBase) }) + + state.contentCache.CoreSystem.find({}).forEach((coreSystem) => { + updateCoreSystemUpgradeStatus(collection, systemBlueprintsMap, coreSystem) + }) } else { const regenerateForStudioIds = new Set(updateProps.invalidateStudioIds) const regenerateForShowStyleBaseIds = new Set(updateProps.invalidateShowStyleBaseIds) @@ -181,9 +211,31 @@ export async function manipulateBlueprintUpgradeStatusPublicationData( collection.remove(getDocumentId('showStyle', showStyleBaseId)) } } + + if (updateProps.invalidateSystem) { + state.contentCache.CoreSystem.find({}).forEach((coreSystem) => { + updateCoreSystemUpgradeStatus(collection, systemBlueprintsMap, coreSystem) + }) + } } } +function updateCoreSystemUpgradeStatus( + collection: CustomPublishCollection, + blueprintsMap: Map, + coreSystem: Pick +) { + const status = checkDocUpgradeStatus(blueprintsMap, coreSystem) + + collection.replace({ + ...status, + _id: getDocumentId('coreSystem', coreSystem._id), + documentType: 'coreSystem', + documentId: coreSystem._id, + name: coreSystem.name ?? 'System', + }) +} + function updateStudioUpgradeStatus( collection: CustomPublishCollection, blueprintsMap: Map, diff --git a/meteor/server/publications/blueprintUpgradeStatus/reactiveContentCache.ts b/meteor/server/publications/blueprintUpgradeStatus/reactiveContentCache.ts index 501e678062..1aa3474720 100644 --- a/meteor/server/publications/blueprintUpgradeStatus/reactiveContentCache.ts +++ b/meteor/server/publications/blueprintUpgradeStatus/reactiveContentCache.ts @@ -4,6 +4,25 @@ import { MongoFieldSpecifierOnesStrict } from '@sofie-automation/corelib/dist/mo import { DBStudio } from '@sofie-automation/corelib/dist/dataModel/Studio' import { DBShowStyleBase } from '@sofie-automation/corelib/dist/dataModel/ShowStyleBase' import { Blueprint } from '@sofie-automation/corelib/dist/dataModel/Blueprint' +import { ICoreSystem } from '@sofie-automation/meteor-lib/dist/collections/CoreSystem' + +export type CoreSystemFields = + | '_id' + | 'blueprintId' + | 'blueprintConfigPresetId' + | 'lastBlueprintConfig' + | 'blueprintConfigWithOverrides' + | 'lastBlueprintFixUpHash' + | 'name' +export const coreSystemFieldsSpecifier = literal>>({ + _id: 1, + blueprintId: 1, + blueprintConfigPresetId: 1, + lastBlueprintConfig: 1, + lastBlueprintFixUpHash: 1, + blueprintConfigWithOverrides: 1, + name: 1, +}) export type StudioFields = | '_id' @@ -64,6 +83,7 @@ export const blueprintFieldSpecifier = literal> Studios: ReactiveCacheCollection> ShowStyleBases: ReactiveCacheCollection> Blueprints: ReactiveCacheCollection> @@ -71,6 +91,7 @@ export interface ContentCache { export function createReactiveContentCache(): ContentCache { const cache: ContentCache = { + CoreSystem: new ReactiveCacheCollection>('coreSystem'), Studios: new ReactiveCacheCollection>('studios'), ShowStyleBases: new ReactiveCacheCollection>('showStyleBases'), Blueprints: new ReactiveCacheCollection>('blueprints'), diff --git a/meteor/server/publications/blueprintUpgradeStatus/upgradesContentObserver.ts b/meteor/server/publications/blueprintUpgradeStatus/upgradesContentObserver.ts index a88ba8575b..e8f8d6281a 100644 --- a/meteor/server/publications/blueprintUpgradeStatus/upgradesContentObserver.ts +++ b/meteor/server/publications/blueprintUpgradeStatus/upgradesContentObserver.ts @@ -3,10 +3,11 @@ import { logger } from '../../logging' import { blueprintFieldSpecifier, ContentCache, + coreSystemFieldsSpecifier, showStyleFieldSpecifier, studioFieldSpecifier, } from './reactiveContentCache' -import { Blueprints, ShowStyleBases, Studios } from '../../collections' +import { Blueprints, CoreSystem, ShowStyleBases, Studios } from '../../collections' import { waitForAllObserversReady } from '../lib/lib' export class UpgradesContentObserver { @@ -22,6 +23,9 @@ export class UpgradesContentObserver { logger.silly(`Creating UpgradesContentObserver`) const observers = await waitForAllObserversReady([ + CoreSystem.observeChanges({}, cache.CoreSystem.link(), { + projection: coreSystemFieldsSpecifier, + }), Studios.observeChanges({}, cache.Studios.link(), { projection: studioFieldSpecifier, }), diff --git a/packages/meteor-lib/src/api/migration.ts b/packages/meteor-lib/src/api/migration.ts index 40df1b77a0..80f6485667 100644 --- a/packages/meteor-lib/src/api/migration.ts +++ b/packages/meteor-lib/src/api/migration.ts @@ -1,5 +1,11 @@ import { MigrationStepInput, MigrationStepInputResult } from '@sofie-automation/blueprints-integration' -import { BlueprintId, ShowStyleBaseId, SnapshotId, StudioId } from '@sofie-automation/corelib/dist/dataModel/Ids' +import { + BlueprintId, + CoreSystemId, + ShowStyleBaseId, + SnapshotId, + StudioId, +} from '@sofie-automation/corelib/dist/dataModel/Ids' import { ITranslatableMessage } from '@sofie-automation/corelib/dist/TranslatableMessage' import { BlueprintValidateConfigForStudioResult } from '@sofie-automation/corelib/dist/worker/studio' @@ -64,10 +70,15 @@ export interface NewMigrationAPI { validateConfigForShowStyleBase(showStyleBaseId: ShowStyleBaseId): Promise /** - * Run `applyConfig` on the blueprint for a Studio, and store the results into the db - * @param studioId Id of the Studio + * Run `applyConfig` on the blueprint for a ShowStyleBase, and store the results into the db + * @param showStyleBaseId Id of the ShowStyleBase */ runUpgradeForShowStyleBase(showStyleBaseId: ShowStyleBaseId): Promise + + /** + * Run `applyConfig` on the blueprint for the CoreSystem, and store the results into the db + */ + runUpgradeForCoreSystem(coreSystemId: CoreSystemId): Promise } export enum MigrationAPIMethods { @@ -85,6 +96,7 @@ export enum MigrationAPIMethods { 'ignoreFixupConfigForShowStyleBase' = 'migration.ignoreFixupConfigForShowStyleBase', 'validateConfigForShowStyleBase' = 'migration.validateConfigForShowStyleBase', 'runUpgradeForShowStyleBase' = 'migration.runUpgradeForShowStyleBase', + 'runUpgradeForCoreSystem' = 'migration.runUpgradeForCoreSystem', } export interface GetMigrationStatusResult { diff --git a/packages/meteor-lib/src/api/upgradeStatus.ts b/packages/meteor-lib/src/api/upgradeStatus.ts index d50fdd81e1..2f6d025d3f 100644 --- a/packages/meteor-lib/src/api/upgradeStatus.ts +++ b/packages/meteor-lib/src/api/upgradeStatus.ts @@ -1,16 +1,19 @@ import { ITranslatableMessage } from '@sofie-automation/blueprints-integration' -import { StudioId, ShowStyleBaseId } from '@sofie-automation/corelib/dist/dataModel/Ids' +import { StudioId, ShowStyleBaseId, CoreSystemId } from '@sofie-automation/corelib/dist/dataModel/Ids' import { ProtectedString } from '@sofie-automation/corelib/dist/protectedString' export type UIBlueprintUpgradeStatusId = ProtectedString<'UIBlueprintUpgradeStatus'> -export type UIBlueprintUpgradeStatus = UIBlueprintUpgradeStatusStudio | UIBlueprintUpgradeStatusShowStyle +export type UIBlueprintUpgradeStatus = + | UIBlueprintUpgradeStatusCoreSystem + | UIBlueprintUpgradeStatusStudio + | UIBlueprintUpgradeStatusShowStyle export interface UIBlueprintUpgradeStatusBase { _id: UIBlueprintUpgradeStatusId - documentType: 'studio' | 'showStyle' - documentId: StudioId | ShowStyleBaseId + documentType: 'coreSystem' | 'studio' | 'showStyle' + documentId: CoreSystemId | StudioId | ShowStyleBaseId name: string @@ -30,6 +33,11 @@ export interface UIBlueprintUpgradeStatusBase { changes: ITranslatableMessage[] } +export interface UIBlueprintUpgradeStatusCoreSystem extends UIBlueprintUpgradeStatusBase { + documentType: 'coreSystem' + documentId: CoreSystemId +} + export interface UIBlueprintUpgradeStatusStudio extends UIBlueprintUpgradeStatusBase { documentType: 'studio' documentId: StudioId diff --git a/packages/meteor-lib/src/collections/CoreSystem.ts b/packages/meteor-lib/src/collections/CoreSystem.ts index c2d9dc7210..f379677340 100644 --- a/packages/meteor-lib/src/collections/CoreSystem.ts +++ b/packages/meteor-lib/src/collections/CoreSystem.ts @@ -113,7 +113,12 @@ export interface ICoreSystem { * Note: This doesn't currently have any 'config' which it relates to. * The name is to be consistent with studio/showstyle, and in preparation for their being config/configpresets used here */ - lastBlueprintConfig: Omit | undefined + lastBlueprintConfig: LastBlueprintConfig | undefined + + /** These fields are to have type consistency with the full config driven upgrades flow, but we don't use them yet */ + blueprintConfigPresetId?: undefined + lastBlueprintFixUpHash?: undefined + blueprintConfigWithOverrides?: undefined } /** In the beginning, there was the database, and the database was with Sofie, and the database was Sofie. From 6d8670ab5feaee008cde73c86ff3d2709f5afabc Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Mon, 11 Nov 2024 16:36:06 +0000 Subject: [PATCH 03/14] wip: --- meteor/server/collections/index.ts | 1 + meteor/server/migration/api.ts | 6 +- .../blueprintUpgradeStatus/checkStatus.ts | 38 ++++--- .../blueprintUpgradeStatus/publication.ts | 6 +- .../webui/src/__mocks__/helpers/database.ts | 1 + .../BlueprintConfiguration/index.tsx | 3 +- .../Studio/BlueprintConfiguration/index.tsx | 3 +- .../client/ui/Settings/SystemManagement.tsx | 3 + .../Settings/SystemManagement/Blueprint.tsx | 99 +++++++++++++++++++ .../ui/Settings/Upgrades/Components.tsx | 74 ++++++++++++++ .../src/client/ui/Settings/Upgrades/View.tsx | 26 +++-- 11 files changed, 230 insertions(+), 30 deletions(-) create mode 100644 packages/webui/src/client/ui/Settings/SystemManagement/Blueprint.tsx diff --git a/meteor/server/collections/index.ts b/meteor/server/collections/index.ts index 303096db7b..1906034805 100644 --- a/meteor/server/collections/index.ts +++ b/meteor/server/collections/index.ts @@ -75,6 +75,7 @@ export const CoreSystem = createAsyncOnlyMongoCollection(Collection 'cron', 'logo', 'evaluations', + 'blueprintId', ]) }, }) diff --git a/meteor/server/migration/api.ts b/meteor/server/migration/api.ts index da257512ad..23a1169759 100644 --- a/meteor/server/migration/api.ts +++ b/meteor/server/migration/api.ts @@ -20,7 +20,7 @@ import { validateConfigForShowStyleBase, validateConfigForStudio, } from './upgrades' -import { ShowStyleBaseId, StudioId } from '@sofie-automation/corelib/dist/dataModel/Ids' +import { CoreSystemId, ShowStyleBaseId, StudioId } from '@sofie-automation/corelib/dist/dataModel/Ids' import { BlueprintValidateConfigForStudioResult } from '@sofie-automation/corelib/dist/worker/studio' import { runUpgradeForCoreSystem } from './upgrades/system' @@ -125,10 +125,10 @@ class ServerMigrationAPI extends MethodContextAPI implements NewMigrationAPI { return runUpgradeForShowStyleBase(showStyleBaseId) } - async runUpgradeForCoreSystem(): Promise { + async runUpgradeForCoreSystem(coreSystemId: CoreSystemId): Promise { await SystemWriteAccess.migrations(this) - return runUpgradeForCoreSystem() + return runUpgradeForCoreSystem(coreSystemId) } } registerClassToMeteorMethods(MigrationAPIMethods, ServerMigrationAPI, false) diff --git a/meteor/server/publications/blueprintUpgradeStatus/checkStatus.ts b/meteor/server/publications/blueprintUpgradeStatus/checkStatus.ts index 24e239ab13..bdefa4a25f 100644 --- a/meteor/server/publications/blueprintUpgradeStatus/checkStatus.ts +++ b/meteor/server/publications/blueprintUpgradeStatus/checkStatus.ts @@ -32,7 +32,11 @@ export interface BlueprintMapEntry { export function checkDocUpgradeStatus( blueprintMap: Map, - doc: Pick | Pick | Pick + doc: + | Pick + | Pick + | Pick, + blueprintUsesConfig: boolean ): Pick { // Check the blueprintId is valid const blueprint = doc.blueprintId ? blueprintMap.get(doc.blueprintId) : null @@ -40,26 +44,30 @@ export function checkDocUpgradeStatus( // Studio blueprint is missing/invalid return { invalidReason: generateTranslation('Invalid blueprint: "{{blueprintId}}"', { - blueprintId: doc.blueprintId, + blueprintId: doc.blueprintId ?? 'undefined', }), pendingRunOfFixupFunction: false, changes: [], } } - // Check the blueprintConfigPresetId is valid - const configPreset = doc.blueprintConfigPresetId ? blueprint.configPresets[doc.blueprintConfigPresetId] : undefined - if (!configPreset) { - return { - invalidReason: generateTranslation( - 'Invalid config preset for blueprint: "{{configPresetId}}" ({{blueprintId}})', - { - configPresetId: doc.blueprintConfigPresetId ?? '', - blueprintId: doc.blueprintId, - } - ), - pendingRunOfFixupFunction: false, // TODO - verify - changes: [], + if (blueprintUsesConfig) { + // Check the blueprintConfigPresetId is valid + const configPreset = doc.blueprintConfigPresetId + ? blueprint.configPresets[doc.blueprintConfigPresetId] + : undefined + if (!configPreset) { + return { + invalidReason: generateTranslation( + 'Invalid config preset for blueprint: "{{configPresetId}}" ({{blueprintId}})', + { + configPresetId: doc.blueprintConfigPresetId ?? '', + blueprintId: doc.blueprintId, + } + ), + pendingRunOfFixupFunction: false, // TODO - verify + changes: [], + } } } diff --git a/meteor/server/publications/blueprintUpgradeStatus/publication.ts b/meteor/server/publications/blueprintUpgradeStatus/publication.ts index c81b4e6520..7479e0f9f5 100644 --- a/meteor/server/publications/blueprintUpgradeStatus/publication.ts +++ b/meteor/server/publications/blueprintUpgradeStatus/publication.ts @@ -225,7 +225,7 @@ function updateCoreSystemUpgradeStatus( blueprintsMap: Map, coreSystem: Pick ) { - const status = checkDocUpgradeStatus(blueprintsMap, coreSystem) + const status = checkDocUpgradeStatus(blueprintsMap, coreSystem, false) collection.replace({ ...status, @@ -241,7 +241,7 @@ function updateStudioUpgradeStatus( blueprintsMap: Map, studio: Pick ) { - const status = checkDocUpgradeStatus(blueprintsMap, studio) + const status = checkDocUpgradeStatus(blueprintsMap, studio, true) collection.replace({ ...status, @@ -257,7 +257,7 @@ function updateShowStyleUpgradeStatus( blueprintsMap: Map, showStyleBase: Pick ) { - const status = checkDocUpgradeStatus(blueprintsMap, showStyleBase) + const status = checkDocUpgradeStatus(blueprintsMap, showStyleBase, true) collection.replace({ ...status, diff --git a/packages/webui/src/__mocks__/helpers/database.ts b/packages/webui/src/__mocks__/helpers/database.ts index fbc094fa42..a8273a81bf 100644 --- a/packages/webui/src/__mocks__/helpers/database.ts +++ b/packages/webui/src/__mocks__/helpers/database.ts @@ -72,6 +72,7 @@ export async function setupMockCore(doc?: Partial): Promise + + diff --git a/packages/webui/src/client/ui/Settings/SystemManagement/Blueprint.tsx b/packages/webui/src/client/ui/Settings/SystemManagement/Blueprint.tsx new file mode 100644 index 0000000000..ce87ee092e --- /dev/null +++ b/packages/webui/src/client/ui/Settings/SystemManagement/Blueprint.tsx @@ -0,0 +1,99 @@ +import { MeteorPubSub } from '@sofie-automation/meteor-lib/dist/api/pubsub' +import { UIBlueprintUpgradeStatusCoreSystem } from '@sofie-automation/meteor-lib/dist/api/upgradeStatus' +import { useTranslation } from 'react-i18next' +import { useSubscription, useTracker } from '../../../lib/ReactMeteorData/ReactMeteorData' +import { UIBlueprintUpgradeStatuses } from '../../Collections' +import { getUpgradeStatusMessage, SystemUpgradeStatusButtons } from '../Upgrades/Components' +import { ICoreSystem } from '@sofie-automation/meteor-lib/dist/collections/CoreSystem' +import { Blueprints, CoreSystem } from '../../../collections' +import { BlueprintManifestType } from '@sofie-automation/blueprints-integration' +import { faExclamationTriangle } from '@fortawesome/free-solid-svg-icons' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import { BlueprintId } from '@sofie-automation/corelib/dist/dataModel/Ids' +import { unprotectString } from '@sofie-automation/corelib/dist/protectedString' +import { useMemo } from 'react' +import { LabelActual } from '../../../lib/Components/LabelAndOverrides' +import { EditAttribute } from '../../../lib/EditAttribute' +import { RedirectToBlueprintButton } from '../../../lib/SettingsNavigation' + +interface SystemManagementBlueprintProps { + coreSystem: ICoreSystem | undefined +} +export function SystemManagementBlueprint({ coreSystem }: Readonly): JSX.Element { + const { t } = useTranslation() + + const isStatusReady = useSubscription(MeteorPubSub.uiBlueprintUpgradeStatuses) + const status = useTracker( + () => + coreSystem && + (UIBlueprintUpgradeStatuses.findOne({ + documentId: coreSystem._id, + documentType: 'coreSystem', + }) as UIBlueprintUpgradeStatusCoreSystem | undefined), + [coreSystem?._id] + ) + const statusMessage = isStatusReady && status ? getUpgradeStatusMessage(t, status) ?? t('OK') : t('Loading...') + + return ( +
+
+ + +

+ {t('Upgrade Status')}: {statusMessage} + {status && } +

+
+
+ ) +} + +interface SelectBlueprintProps { + coreSystem: ICoreSystem | undefined +} + +function SelectBlueprint({ coreSystem }: Readonly): JSX.Element { + const { t } = useTranslation() + + const allSystemBlueprints = useTracker(() => { + return Blueprints.find({ + blueprintType: BlueprintManifestType.SYSTEM, + }).fetch() + }, []) + const blueprintOptions: { name: string; value: BlueprintId | null }[] = useMemo(() => { + if (allSystemBlueprints) { + return allSystemBlueprints.map((blueprint) => { + return { + name: blueprint.name ? `${blueprint.name} (${blueprint._id})` : unprotectString(blueprint._id), + value: blueprint._id, + } + }) + } else { + return [] + } + }, [allSystemBlueprints]) + + return ( +
+ +
+ ) +} diff --git a/packages/webui/src/client/ui/Settings/Upgrades/Components.tsx b/packages/webui/src/client/ui/Settings/Upgrades/Components.tsx index 19e7fc1778..1034a6a91a 100644 --- a/packages/webui/src/client/ui/Settings/Upgrades/Components.tsx +++ b/packages/webui/src/client/ui/Settings/Upgrades/Components.tsx @@ -10,6 +10,7 @@ import { NoteSeverity } from '@sofie-automation/blueprints-integration' import { NotificationCenter, NoticeLevel, Notification } from '../../../lib/notifications/notifications' import { UIBlueprintUpgradeStatusBase, + UIBlueprintUpgradeStatusCoreSystem, UIBlueprintUpgradeStatusShowStyle, UIBlueprintUpgradeStatusStudio, } from '@sofie-automation/meteor-lib/dist/api/upgradeStatus' @@ -288,3 +289,76 @@ export function UpgradeStatusButtons({ upgradeResult }: Readonly ) } + +interface SystemUpgradeStatusButtonsProps { + upgradeResult: UIBlueprintUpgradeStatusCoreSystem +} +export function SystemUpgradeStatusButtons({ upgradeResult }: Readonly): JSX.Element { + const { t } = useTranslation() + + const applyConfig = useCallback( + async () => MeteorCall.migration.runUpgradeForCoreSystem(upgradeResult.documentId), + [upgradeResult.documentId, upgradeResult.documentType] + ) + + const clickApply = useCallback(() => { + applyConfig() + .then(() => { + NotificationCenter.push( + new Notification( + undefined, + NoticeLevel.NOTIFICATION, + t('Config for {{name}} upgraded successfully', { name: upgradeResult.name }), + 'UpgradesView' + ) + ) + }) + .catch((e) => { + catchError('Upgrade applyConfig')(e) + NotificationCenter.push( + new Notification( + undefined, + NoticeLevel.WARNING, + t('Config for {{name}} upgraded failed', { name: upgradeResult.name }), + 'UpgradesView' + ) + ) + }) + }, [upgradeResult, applyConfig]) + + const clickShowChanges = useCallback(() => { + doModalDialog({ + title: t('Upgrade config for {{name}}', { name: upgradeResult.name }), + message: ( +
+ {upgradeResult.changes.length === 0 &&

{t('No changes')}

} + {upgradeResult.changes.map((msg, i) => ( +

{translateMessage(msg, i18nTranslator)}

+ ))} +
+ ), + acceptOnly: true, + yes: t('Dismiss'), + onAccept: () => { + // Do nothing + }, + }) + }, [upgradeResult]) + + return ( +
+ + +
+ ) +} diff --git a/packages/webui/src/client/ui/Settings/Upgrades/View.tsx b/packages/webui/src/client/ui/Settings/Upgrades/View.tsx index 2e6f2bbfd7..7302d2acd0 100644 --- a/packages/webui/src/client/ui/Settings/Upgrades/View.tsx +++ b/packages/webui/src/client/ui/Settings/Upgrades/View.tsx @@ -4,11 +4,8 @@ import { unprotectString } from '@sofie-automation/corelib/dist/protectedString' import { useSubscription, useTracker } from '../../../lib/ReactMeteorData/ReactMeteorData' import { MeteorPubSub } from '@sofie-automation/meteor-lib/dist/api/pubsub' import { UIBlueprintUpgradeStatuses } from '../../Collections' -import { - UIBlueprintUpgradeStatusShowStyle, - UIBlueprintUpgradeStatusStudio, -} from '@sofie-automation/meteor-lib/dist/api/upgradeStatus' -import { getUpgradeStatusMessage, UpgradeStatusButtons } from './Components' +import { UIBlueprintUpgradeStatus } from '@sofie-automation/meteor-lib/dist/api/upgradeStatus' +import { getUpgradeStatusMessage, SystemUpgradeStatusButtons, UpgradeStatusButtons } from './Components' export function UpgradesView(): JSX.Element { const { t } = useTranslation() @@ -39,6 +36,17 @@ export function UpgradesView(): JSX.Element { )} + {statuses?.map( + (document) => + document.documentType === 'coreSystem' && ( + + ) + )} + {statuses?.map( (document) => document.documentType === 'studio' && ( @@ -69,7 +77,7 @@ export function UpgradesView(): JSX.Element { interface ShowUpgradesRowProps { resourceName: string - upgradeResult: UIBlueprintUpgradeStatusStudio | UIBlueprintUpgradeStatusShowStyle + upgradeResult: UIBlueprintUpgradeStatus } function ShowUpgradesRow({ resourceName, upgradeResult }: Readonly) { const { t } = useTranslation() @@ -83,7 +91,11 @@ function ShowUpgradesRow({ resourceName, upgradeResult }: Readonly{getUpgradeStatusMessage(t, upgradeResult)} - + {upgradeResult.documentType === 'coreSystem' ? ( + + ) : ( + + )} ) From 23c43b702484b93c802d2fa567568bd7905a73b7 Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Mon, 11 Nov 2024 16:45:23 +0000 Subject: [PATCH 04/14] fix --- meteor/server/migration/upgrades/system.ts | 2 +- packages/corelib/src/dataModel/Blueprint.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/meteor/server/migration/upgrades/system.ts b/meteor/server/migration/upgrades/system.ts index 51bbb6e631..24d2fa30a4 100644 --- a/meteor/server/migration/upgrades/system.ts +++ b/meteor/server/migration/upgrades/system.ts @@ -29,7 +29,7 @@ export async function runUpgradeForCoreSystem(coreSystemId: CoreSystemId): Promi lastBlueprintConfig: { blueprintHash: blueprint.blueprintHash, blueprintId: blueprint._id, - blueprintConfigPresetId: '', + blueprintConfigPresetId: undefined, config: {}, }, }, diff --git a/packages/corelib/src/dataModel/Blueprint.ts b/packages/corelib/src/dataModel/Blueprint.ts index 99e025bfdf..32ba8af5e5 100644 --- a/packages/corelib/src/dataModel/Blueprint.ts +++ b/packages/corelib/src/dataModel/Blueprint.ts @@ -64,7 +64,7 @@ export interface Blueprint { export interface LastBlueprintConfig { blueprintId: BlueprintId blueprintHash: BlueprintHash - blueprintConfigPresetId: string + blueprintConfigPresetId: string | undefined config: IBlueprintConfig } From 6b5a1f44367c19cc86656d4fb5d2f5e426fc79bd Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Tue, 12 Nov 2024 10:26:40 +0000 Subject: [PATCH 05/14] wip: handle no blueprint --- meteor/server/migration/0_1_0.ts | 411 +---------------- meteor/server/migration/X_X_X.ts | 27 +- .../upgrades/defaultSystemActionTriggers.ts | 413 ++++++++++++++++++ meteor/server/migration/upgrades/system.ts | 55 ++- .../blueprintUpgradeStatus/checkStatus.ts | 97 +++- .../blueprintUpgradeStatus/publication.ts | 8 +- 6 files changed, 558 insertions(+), 453 deletions(-) create mode 100644 meteor/server/migration/upgrades/defaultSystemActionTriggers.ts diff --git a/meteor/server/migration/0_1_0.ts b/meteor/server/migration/0_1_0.ts index 80247e212b..9d8bef61f0 100644 --- a/meteor/server/migration/0_1_0.ts +++ b/meteor/server/migration/0_1_0.ts @@ -1,424 +1,17 @@ import { addMigrationSteps } from './databaseMigration' import { logger } from '../logging' -import { getRandomId, protectString, generateTranslation as t, getHash } from '../lib/tempLib' +import { getRandomId, protectString, getHash } from '../lib/tempLib' import { wrapDefaultObject } from '@sofie-automation/corelib/dist/settings/objectWithOverrides' import { ShowStyleVariantId } from '@sofie-automation/corelib/dist/dataModel/Ids' import { ShowStyleBases, ShowStyleVariants, Studios, TriggeredActions } from '../collections' -import { - IBlueprintTriggeredActions, - ClientActions, - TriggerType, - PlayoutActions, -} from '@sofie-automation/blueprints-integration' import { DEFAULT_MINIMUM_TAKE_SPAN } from '@sofie-automation/shared-lib/dist/core/constants' +import { DEFAULT_CORE_TRIGGERS } from './upgrades/defaultSystemActionTriggers' /** * This file contains system specific migration steps. * These files are combined with / overridden by migration steps defined in the blueprints. */ -let j = 0 - -const DEFAULT_CORE_TRIGGERS: IBlueprintTriggeredActions[] = [ - { - _id: 'core_toggleShelf', - actions: { - '0': { - action: ClientActions.shelf, - filterChain: [ - { - object: 'view', - }, - ], - state: 'toggle', - }, - }, - triggers: { - '0': { - type: TriggerType.hotkey, - keys: 'Tab', - up: true, - }, - }, - _rank: ++j * 1000, - name: t('Toggle Shelf'), - }, - { - _id: 'core_activateRundownPlaylist', - actions: { - '0': { - action: PlayoutActions.activateRundownPlaylist, - rehearsal: false, - filterChain: [ - { - object: 'view', - }, - ], - }, - }, - triggers: { - '0': { - type: TriggerType.hotkey, - keys: 'Backquote', - up: true, - }, - }, - _rank: ++j * 1000, - name: t('Activate (On-Air)'), - }, - { - _id: 'core_activateRundownPlaylist_rehearsal', - actions: { - '0': { - action: PlayoutActions.activateRundownPlaylist, - rehearsal: true, - filterChain: [ - { - object: 'view', - }, - ], - }, - }, - triggers: { - '0': { - type: TriggerType.hotkey, - keys: 'Control+Backquote', - up: true, - }, - }, - _rank: ++j * 1000, - name: t('Activate (Rehearsal)'), - }, - { - _id: 'core_deactivateRundownPlaylist', - actions: { - '0': { - action: PlayoutActions.deactivateRundownPlaylist, - filterChain: [ - { - object: 'view', - }, - ], - }, - }, - triggers: { - '0': { - type: TriggerType.hotkey, - keys: 'Control+Shift+Backquote', - up: true, - }, - }, - _rank: ++j * 1000, - name: t('Deactivate'), - }, - { - _id: 'core_take', - actions: { - '0': { - action: PlayoutActions.take, - filterChain: [ - { - object: 'view', - }, - ], - }, - }, - triggers: { - '0': { - type: TriggerType.hotkey, - keys: 'NumpadEnter', - up: true, - }, - '1': { - type: TriggerType.hotkey, - keys: 'F12', - up: true, - }, - }, - _rank: ++j * 1000, - name: t('Take'), - }, - { - _id: 'core_hold', - actions: { - '0': { - action: PlayoutActions.hold, - filterChain: [ - { - object: 'view', - }, - ], - }, - }, - triggers: { - '0': { - type: TriggerType.hotkey, - keys: 'KeyH', - up: true, - }, - }, - _rank: ++j * 1000, - name: t('Hold'), - }, - { - _id: 'core_hold_undo', - actions: { - '0': { - action: PlayoutActions.hold, - undo: true, - filterChain: [ - { - object: 'view', - }, - ], - }, - }, - triggers: { - '0': { - type: TriggerType.hotkey, - keys: 'Shift+KeyH', - up: true, - }, - }, - _rank: ++j * 1000, - name: t('Undo Hold'), - }, - { - _id: 'core_reset_rundown_playlist', - actions: { - '0': { - action: PlayoutActions.resetRundownPlaylist, - filterChain: [ - { - object: 'view', - }, - ], - }, - }, - triggers: { - '0': { - type: TriggerType.hotkey, - keys: 'Control+Shift+F12', - up: true, - }, - '1': { - type: TriggerType.hotkey, - keys: 'Control+Shift+AnyEnter', - up: true, - }, - }, - _rank: ++j * 1000, - name: t('Reset Rundown'), - }, - { - _id: 'core_disable_next_piece', - actions: { - '0': { - action: PlayoutActions.disableNextPiece, - filterChain: [ - { - object: 'view', - }, - ], - }, - }, - triggers: { - '0': { - type: TriggerType.hotkey, - keys: 'KeyG', - up: true, - }, - }, - _rank: ++j * 1000, - name: t('Disable the next element'), - }, - { - _id: 'core_disable_next_piece_undo', - actions: { - '0': { - action: PlayoutActions.disableNextPiece, - filterChain: [ - { - object: 'view', - }, - ], - undo: true, - }, - }, - triggers: { - '0': { - type: TriggerType.hotkey, - keys: 'Shift+KeyG', - up: true, - }, - }, - _rank: ++j * 1000, - name: t('Undo Disable the next element'), - }, - { - _id: 'core_create_snapshot_for_debug', - actions: { - '0': { - action: PlayoutActions.createSnapshotForDebug, - filterChain: [ - { - object: 'view', - }, - ], - }, - }, - triggers: { - '0': { - type: TriggerType.hotkey, - keys: 'Backspace', - up: true, - }, - }, - _rank: ++j * 1000, - name: t('Store Snapshot'), - }, - { - _id: 'core_move_next_part', - actions: { - '0': { - action: PlayoutActions.moveNext, - filterChain: [ - { - object: 'view', - }, - ], - parts: 1, - segments: 0, - }, - }, - triggers: { - '0': { - type: TriggerType.hotkey, - keys: 'F9', - up: true, - }, - }, - _rank: ++j * 1000, - name: t('Move Next forwards'), - }, - { - _id: 'core_move_next_segment', - actions: { - '0': { - action: PlayoutActions.moveNext, - filterChain: [ - { - object: 'view', - }, - ], - parts: 0, - segments: 1, - }, - }, - triggers: { - '0': { - type: TriggerType.hotkey, - keys: 'F10', - up: true, - }, - }, - _rank: ++j * 1000, - name: t('Move Next to the following segment'), - }, - { - _id: 'core_move_previous_part', - actions: { - '0': { - action: PlayoutActions.moveNext, - filterChain: [ - { - object: 'view', - }, - ], - parts: -1, - segments: 0, - }, - }, - triggers: { - '0': { - type: TriggerType.hotkey, - keys: 'Shift+F9', - up: true, - }, - }, - _rank: ++j * 1000, - name: t('Move Next backwards'), - }, - { - _id: 'core_move_previous_segment', - actions: { - '0': { - action: PlayoutActions.moveNext, - filterChain: [ - { - object: 'view', - }, - ], - parts: 0, - segments: -1, - }, - }, - triggers: { - '0': { - type: TriggerType.hotkey, - keys: 'Shift+F10', - up: true, - }, - }, - _rank: ++j * 1000, - name: t('Move Next to the previous segment'), - }, - { - _id: 'core_go_to_onAir_line', - actions: { - '0': { - action: ClientActions.goToOnAirLine, - filterChain: [ - { - object: 'view', - }, - ], - }, - }, - triggers: { - '0': { - type: TriggerType.hotkey, - keys: 'Control+Home', - up: true, - }, - }, - _rank: ++j * 1000, - name: t('Go to On Air line'), - }, - { - _id: 'core_rewind_segments', - actions: { - '0': { - action: ClientActions.rewindSegments, - filterChain: [ - { - object: 'view', - }, - ], - }, - }, - triggers: { - '0': { - type: TriggerType.hotkey, - keys: 'Shift+Home', - up: true, - }, - }, - _rank: ++j * 1000, - name: t('Rewind segments to start'), - }, -] - // 0.1.0: These are the "base" migration steps, setting up a default system export const addSteps = addMigrationSteps('0.1.0', [ { diff --git a/meteor/server/migration/X_X_X.ts b/meteor/server/migration/X_X_X.ts index 37862b391d..590e5a1c80 100644 --- a/meteor/server/migration/X_X_X.ts +++ b/meteor/server/migration/X_X_X.ts @@ -1,12 +1,13 @@ import { addMigrationSteps } from './databaseMigration' import { CURRENT_SYSTEM_VERSION } from './currentSystemVersion' -import { Studios } from '../collections' +import { Studios, TriggeredActions } from '../collections' import { convertObjectIntoOverrides } from '@sofie-automation/corelib/dist/settings/objectWithOverrides' import { StudioRouteSet, StudioRouteSetExclusivityGroup, StudioPackageContainer, } from '@sofie-automation/corelib/dist/dataModel/Studio' +import { DEFAULT_CORE_TRIGGER_IDS } from './upgrades/defaultSystemActionTriggers' /* * ************************************************************************************** @@ -187,4 +188,28 @@ export const addSteps = addMigrationSteps(CURRENT_SYSTEM_VERSION, [ } }, }, + { + id: 'TriggeredActions.remove old systemwide', + canBeRunAutomatically: true, + validate: async () => { + const coreTriggeredActionsCount = await TriggeredActions.countDocuments({ + showStyleBaseId: null, + blueprintUniqueId: null, + _id: { $in: DEFAULT_CORE_TRIGGER_IDS }, + }) + + if (coreTriggeredActionsCount > 0) { + return `System-wide triggered actions needing removal.` + } + + return false + }, + migrate: async () => { + await TriggeredActions.removeAsync({ + showStyleBaseId: null, + blueprintUniqueId: null, + _id: { $in: DEFAULT_CORE_TRIGGER_IDS }, + }) + }, + }, ]) diff --git a/meteor/server/migration/upgrades/defaultSystemActionTriggers.ts b/meteor/server/migration/upgrades/defaultSystemActionTriggers.ts new file mode 100644 index 0000000000..22c74bfc61 --- /dev/null +++ b/meteor/server/migration/upgrades/defaultSystemActionTriggers.ts @@ -0,0 +1,413 @@ +import { + IBlueprintTriggeredActions, + ClientActions, + TriggerType, + PlayoutActions, +} from '@sofie-automation/blueprints-integration' +import { getHash, protectString, generateTranslation as t } from '../../lib/tempLib' + +let j = 0 + +export const DEFAULT_CORE_TRIGGERS: IBlueprintTriggeredActions[] = [ + { + _id: 'core_toggleShelf', + actions: { + '0': { + action: ClientActions.shelf, + filterChain: [ + { + object: 'view', + }, + ], + state: 'toggle', + }, + }, + triggers: { + '0': { + type: TriggerType.hotkey, + keys: 'Tab', + up: true, + }, + }, + _rank: ++j * 1000, + name: t('Toggle Shelf'), + }, + { + _id: 'core_activateRundownPlaylist', + actions: { + '0': { + action: PlayoutActions.activateRundownPlaylist, + rehearsal: false, + filterChain: [ + { + object: 'view', + }, + ], + }, + }, + triggers: { + '0': { + type: TriggerType.hotkey, + keys: 'Backquote', + up: true, + }, + }, + _rank: ++j * 1000, + name: t('Activate (On-Air)'), + }, + { + _id: 'core_activateRundownPlaylist_rehearsal', + actions: { + '0': { + action: PlayoutActions.activateRundownPlaylist, + rehearsal: true, + filterChain: [ + { + object: 'view', + }, + ], + }, + }, + triggers: { + '0': { + type: TriggerType.hotkey, + keys: 'Control+Backquote', + up: true, + }, + }, + _rank: ++j * 1000, + name: t('Activate (Rehearsal)'), + }, + { + _id: 'core_deactivateRundownPlaylist', + actions: { + '0': { + action: PlayoutActions.deactivateRundownPlaylist, + filterChain: [ + { + object: 'view', + }, + ], + }, + }, + triggers: { + '0': { + type: TriggerType.hotkey, + keys: 'Control+Shift+Backquote', + up: true, + }, + }, + _rank: ++j * 1000, + name: t('Deactivate'), + }, + { + _id: 'core_take', + actions: { + '0': { + action: PlayoutActions.take, + filterChain: [ + { + object: 'view', + }, + ], + }, + }, + triggers: { + '0': { + type: TriggerType.hotkey, + keys: 'NumpadEnter', + up: true, + }, + '1': { + type: TriggerType.hotkey, + keys: 'F12', + up: true, + }, + }, + _rank: ++j * 1000, + name: t('Take'), + }, + { + _id: 'core_hold', + actions: { + '0': { + action: PlayoutActions.hold, + filterChain: [ + { + object: 'view', + }, + ], + }, + }, + triggers: { + '0': { + type: TriggerType.hotkey, + keys: 'KeyH', + up: true, + }, + }, + _rank: ++j * 1000, + name: t('Hold'), + }, + { + _id: 'core_hold_undo', + actions: { + '0': { + action: PlayoutActions.hold, + undo: true, + filterChain: [ + { + object: 'view', + }, + ], + }, + }, + triggers: { + '0': { + type: TriggerType.hotkey, + keys: 'Shift+KeyH', + up: true, + }, + }, + _rank: ++j * 1000, + name: t('Undo Hold'), + }, + { + _id: 'core_reset_rundown_playlist', + actions: { + '0': { + action: PlayoutActions.resetRundownPlaylist, + filterChain: [ + { + object: 'view', + }, + ], + }, + }, + triggers: { + '0': { + type: TriggerType.hotkey, + keys: 'Control+Shift+F12', + up: true, + }, + '1': { + type: TriggerType.hotkey, + keys: 'Control+Shift+AnyEnter', + up: true, + }, + }, + _rank: ++j * 1000, + name: t('Reset Rundown'), + }, + { + _id: 'core_disable_next_piece', + actions: { + '0': { + action: PlayoutActions.disableNextPiece, + filterChain: [ + { + object: 'view', + }, + ], + }, + }, + triggers: { + '0': { + type: TriggerType.hotkey, + keys: 'KeyG', + up: true, + }, + }, + _rank: ++j * 1000, + name: t('Disable the next element'), + }, + { + _id: 'core_disable_next_piece_undo', + actions: { + '0': { + action: PlayoutActions.disableNextPiece, + filterChain: [ + { + object: 'view', + }, + ], + undo: true, + }, + }, + triggers: { + '0': { + type: TriggerType.hotkey, + keys: 'Shift+KeyG', + up: true, + }, + }, + _rank: ++j * 1000, + name: t('Undo Disable the next element'), + }, + { + _id: 'core_create_snapshot_for_debug', + actions: { + '0': { + action: PlayoutActions.createSnapshotForDebug, + filterChain: [ + { + object: 'view', + }, + ], + }, + }, + triggers: { + '0': { + type: TriggerType.hotkey, + keys: 'Backspace', + up: true, + }, + }, + _rank: ++j * 1000, + name: t('Store Snapshot'), + }, + { + _id: 'core_move_next_part', + actions: { + '0': { + action: PlayoutActions.moveNext, + filterChain: [ + { + object: 'view', + }, + ], + parts: 1, + segments: 0, + }, + }, + triggers: { + '0': { + type: TriggerType.hotkey, + keys: 'F9', + up: true, + }, + }, + _rank: ++j * 1000, + name: t('Move Next forwards'), + }, + { + _id: 'core_move_next_segment', + actions: { + '0': { + action: PlayoutActions.moveNext, + filterChain: [ + { + object: 'view', + }, + ], + parts: 0, + segments: 1, + }, + }, + triggers: { + '0': { + type: TriggerType.hotkey, + keys: 'F10', + up: true, + }, + }, + _rank: ++j * 1000, + name: t('Move Next to the following segment'), + }, + { + _id: 'core_move_previous_part', + actions: { + '0': { + action: PlayoutActions.moveNext, + filterChain: [ + { + object: 'view', + }, + ], + parts: -1, + segments: 0, + }, + }, + triggers: { + '0': { + type: TriggerType.hotkey, + keys: 'Shift+F9', + up: true, + }, + }, + _rank: ++j * 1000, + name: t('Move Next backwards'), + }, + { + _id: 'core_move_previous_segment', + actions: { + '0': { + action: PlayoutActions.moveNext, + filterChain: [ + { + object: 'view', + }, + ], + parts: 0, + segments: -1, + }, + }, + triggers: { + '0': { + type: TriggerType.hotkey, + keys: 'Shift+F10', + up: true, + }, + }, + _rank: ++j * 1000, + name: t('Move Next to the previous segment'), + }, + { + _id: 'core_go_to_onAir_line', + actions: { + '0': { + action: ClientActions.goToOnAirLine, + filterChain: [ + { + object: 'view', + }, + ], + }, + }, + triggers: { + '0': { + type: TriggerType.hotkey, + keys: 'Control+Home', + up: true, + }, + }, + _rank: ++j * 1000, + name: t('Go to On Air line'), + }, + { + _id: 'core_rewind_segments', + actions: { + '0': { + action: ClientActions.rewindSegments, + filterChain: [ + { + object: 'view', + }, + ], + }, + }, + triggers: { + '0': { + type: TriggerType.hotkey, + keys: 'Shift+Home', + up: true, + }, + }, + _rank: ++j * 1000, + name: t('Rewind segments to start'), + }, +] + +export const DEFAULT_CORE_TRIGGER_IDS = DEFAULT_CORE_TRIGGERS.map((triggeredAction) => + protectString(getHash(triggeredAction._id)) +) diff --git a/meteor/server/migration/upgrades/system.ts b/meteor/server/migration/upgrades/system.ts index 24d2fa30a4..e991df968b 100644 --- a/meteor/server/migration/upgrades/system.ts +++ b/meteor/server/migration/upgrades/system.ts @@ -1,34 +1,44 @@ import { Meteor } from 'meteor/meteor' import { logger } from '../../logging' import { Blueprints, CoreSystem } from '../../collections' -import { BlueprintManifestType, SystemBlueprintManifest } from '@sofie-automation/blueprints-integration' +import { + BlueprintManifestType, + BlueprintResultApplySystemConfig, + SystemBlueprintManifest, +} from '@sofie-automation/blueprints-integration' import { evalBlueprint } from '../../api/blueprints/cache' import { CommonContext } from './context' import { updateTriggeredActionsForShowStyleBaseId } from './lib' import { CoreSystemId } from '@sofie-automation/corelib/dist/dataModel/Ids' +import { DEFAULT_CORE_TRIGGERS } from './defaultSystemActionTriggers' +import { protectString } from '@sofie-automation/corelib/dist/protectedString' export async function runUpgradeForCoreSystem(coreSystemId: CoreSystemId): Promise { logger.info(`Running upgrade for CoreSystem`) const { coreSystem, blueprint, blueprintManifest } = await loadCoreSystemAndBlueprint(coreSystemId) - if (typeof blueprintManifest.applyConfig !== 'function') - throw new Meteor.Error(500, 'Blueprint does not support this config flow') + let result: BlueprintResultApplySystemConfig - const blueprintContext = new CommonContext( - 'applyConfig', - `coreSystem:${coreSystem._id},blueprint:${blueprint.blueprintId}` - ) + if (blueprintManifest && typeof blueprintManifest.applyConfig === 'function') { + const blueprintContext = new CommonContext( + 'applyConfig', + `coreSystem:${coreSystem._id},blueprint:${blueprint.blueprintId}` + ) - const result = blueprintManifest.applyConfig(blueprintContext) + result = blueprintManifest.applyConfig(blueprintContext) + } else { + // Ensure some defaults are populated when no blueprint method is present + result = generateDefaultSystemConfig() + } await CoreSystem.updateAsync(coreSystemId, { $set: { // 'sourceLayersWithOverrides.defaults': normalizeArray(result.sourceLayers, '_id'), // 'outputLayersWithOverrides.defaults': normalizeArray(result.outputLayers, '_id'), lastBlueprintConfig: { - blueprintHash: blueprint.blueprintHash, - blueprintId: blueprint._id, + blueprintHash: blueprint?.blueprintHash ?? protectString('default'), + blueprintId: blueprint?._id ?? protectString('default'), blueprintConfigPresetId: undefined, config: {}, }, @@ -42,14 +52,21 @@ async function loadCoreSystemAndBlueprint(coreSystemId: CoreSystemId) { const coreSystem = await CoreSystem.findOneAsync(coreSystemId) if (!coreSystem) throw new Meteor.Error(404, `CoreSystem "${coreSystemId}" not found!`) + if (!coreSystem.blueprintId) { + // No blueprint is valid + return { + coreSystem, + blueprint: undefined, + blueprintHash: undefined, + } + } + // if (!showStyleBase.blueprintConfigPresetId) throw new Meteor.Error(500, 'ShowStyleBase is missing config preset') - const blueprint = coreSystem.blueprintId - ? await Blueprints.findOneAsync({ - _id: coreSystem.blueprintId, - blueprintType: BlueprintManifestType.SYSTEM, - }) - : undefined + const blueprint = await Blueprints.findOneAsync({ + _id: coreSystem.blueprintId, + blueprintType: BlueprintManifestType.SYSTEM, + }) if (!blueprint) throw new Meteor.Error(404, `Blueprint "${coreSystem.blueprintId}" not found!`) if (!blueprint.blueprintHash) throw new Meteor.Error(500, 'Blueprint is not valid') @@ -62,3 +79,9 @@ async function loadCoreSystemAndBlueprint(coreSystemId: CoreSystemId) { blueprintManifest, } } + +function generateDefaultSystemConfig(): BlueprintResultApplySystemConfig { + return { + triggeredActions: DEFAULT_CORE_TRIGGERS, + } +} diff --git a/meteor/server/publications/blueprintUpgradeStatus/checkStatus.ts b/meteor/server/publications/blueprintUpgradeStatus/checkStatus.ts index bdefa4a25f..5fde9762ab 100644 --- a/meteor/server/publications/blueprintUpgradeStatus/checkStatus.ts +++ b/meteor/server/publications/blueprintUpgradeStatus/checkStatus.ts @@ -14,7 +14,7 @@ import { DBShowStyleBase } from '@sofie-automation/corelib/dist/dataModel/ShowSt import { DBStudio } from '@sofie-automation/corelib/dist/dataModel/Studio' import { joinObjectPathFragments, objectPathGet } from '@sofie-automation/corelib/dist/lib' import { applyAndValidateOverrides } from '@sofie-automation/corelib/dist/settings/objectWithOverrides' -import { generateTranslation } from '../../lib/tempLib' +import { generateTranslation, protectString } from '../../lib/tempLib' import { logger } from '../../logging' import { CoreSystemFields, ShowStyleBaseFields, StudioFields } from './reactiveContentCache' import _ from 'underscore' @@ -32,11 +32,7 @@ export interface BlueprintMapEntry { export function checkDocUpgradeStatus( blueprintMap: Map, - doc: - | Pick - | Pick - | Pick, - blueprintUsesConfig: boolean + doc: Pick | Pick ): Pick { // Check the blueprintId is valid const blueprint = doc.blueprintId ? blueprintMap.get(doc.blueprintId) : null @@ -51,23 +47,19 @@ export function checkDocUpgradeStatus( } } - if (blueprintUsesConfig) { - // Check the blueprintConfigPresetId is valid - const configPreset = doc.blueprintConfigPresetId - ? blueprint.configPresets[doc.blueprintConfigPresetId] - : undefined - if (!configPreset) { - return { - invalidReason: generateTranslation( - 'Invalid config preset for blueprint: "{{configPresetId}}" ({{blueprintId}})', - { - configPresetId: doc.blueprintConfigPresetId ?? '', - blueprintId: doc.blueprintId, - } - ), - pendingRunOfFixupFunction: false, // TODO - verify - changes: [], - } + // Check the blueprintConfigPresetId is valid + const configPreset = doc.blueprintConfigPresetId ? blueprint.configPresets[doc.blueprintConfigPresetId] : undefined + if (!configPreset) { + return { + invalidReason: generateTranslation( + 'Invalid config preset for blueprint: "{{configPresetId}}" ({{blueprintId}})', + { + configPresetId: doc.blueprintConfigPresetId ?? '', + blueprintId: doc.blueprintId, + } + ), + pendingRunOfFixupFunction: false, // TODO - verify + changes: [], } } @@ -144,6 +136,65 @@ export function checkDocUpgradeStatus( } } +export function checkSystemUpgradeStatus( + blueprintMap: Map, + doc: Pick +): Pick { + const changes: ITranslatableMessage[] = [] + + // Check the blueprintId is valid + if (doc.blueprintId) { + const blueprint = blueprintMap.get(doc.blueprintId) + if (!blueprint || !blueprint.configPresets) { + // Studio blueprint is missing/invalid + return { + invalidReason: generateTranslation('Invalid blueprint: "{{blueprintId}}"', { + blueprintId: doc.blueprintId ?? 'undefined', + }), + pendingRunOfFixupFunction: false, + changes: [], + } + } + + // Some basic property checks + if (!doc.lastBlueprintConfig) { + changes.push(generateTranslation('Config has not been applied before')) + } else if (doc.lastBlueprintConfig.blueprintId !== doc.blueprintId) { + changes.push( + generateTranslation('Blueprint has been changed. From "{{ oldValue }}", to "{{ newValue }}"', { + oldValue: doc.lastBlueprintConfig.blueprintId || '', + newValue: doc.blueprintId || '', + }) + ) + } else if (doc.lastBlueprintConfig.blueprintHash !== blueprint.blueprintHash) { + changes.push(generateTranslation('Blueprint has a new version')) + } + } else { + // No blueprint assigned + + const defaultId = protectString('default') + + // Some basic property checks + if (!doc.lastBlueprintConfig) { + changes.push(generateTranslation('Config has not been applied before')) + } else if (doc.lastBlueprintConfig.blueprintId !== defaultId) { + changes.push( + generateTranslation('Blueprint has been changed. From "{{ oldValue }}", to "{{ newValue }}"', { + oldValue: doc.lastBlueprintConfig.blueprintId || '', + newValue: defaultId, + }) + ) + } else if (doc.lastBlueprintConfig.blueprintHash !== defaultId) { + changes.push(generateTranslation('Blueprint has a new version')) + } + } + + return { + changes, + pendingRunOfFixupFunction: false, + } +} + /** * This is a slightly crude diffing of objects based on a jsonschema. Only keys in the schema will be compared. * For now this has some limitations such as not looking inside of arrays, but this could be expanded later on diff --git a/meteor/server/publications/blueprintUpgradeStatus/publication.ts b/meteor/server/publications/blueprintUpgradeStatus/publication.ts index 7479e0f9f5..9ea8d72fe5 100644 --- a/meteor/server/publications/blueprintUpgradeStatus/publication.ts +++ b/meteor/server/publications/blueprintUpgradeStatus/publication.ts @@ -21,7 +21,7 @@ import { StudioFields, } from './reactiveContentCache' import { UpgradesContentObserver } from './upgradesContentObserver' -import { BlueprintMapEntry, checkDocUpgradeStatus } from './checkStatus' +import { BlueprintMapEntry, checkDocUpgradeStatus, checkSystemUpgradeStatus } from './checkStatus' import { BlueprintManifestType } from '@sofie-automation/blueprints-integration' import { DBShowStyleBase } from '@sofie-automation/corelib/dist/dataModel/ShowStyleBase' import { DBStudio } from '@sofie-automation/corelib/dist/dataModel/Studio' @@ -225,7 +225,7 @@ function updateCoreSystemUpgradeStatus( blueprintsMap: Map, coreSystem: Pick ) { - const status = checkDocUpgradeStatus(blueprintsMap, coreSystem, false) + const status = checkSystemUpgradeStatus(blueprintsMap, coreSystem) collection.replace({ ...status, @@ -241,7 +241,7 @@ function updateStudioUpgradeStatus( blueprintsMap: Map, studio: Pick ) { - const status = checkDocUpgradeStatus(blueprintsMap, studio, true) + const status = checkDocUpgradeStatus(blueprintsMap, studio) collection.replace({ ...status, @@ -257,7 +257,7 @@ function updateShowStyleUpgradeStatus( blueprintsMap: Map, showStyleBase: Pick ) { - const status = checkDocUpgradeStatus(blueprintsMap, showStyleBase, true) + const status = checkDocUpgradeStatus(blueprintsMap, showStyleBase) collection.replace({ ...status, From 17be8beb6cf9197f9186c15d00034365443e23ba Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Tue, 12 Nov 2024 12:45:32 +0000 Subject: [PATCH 06/14] wip: flow --- meteor/server/migration/0_1_0.ts | 33 +--------- meteor/server/migration/upgrades/context.ts | 16 ++++- .../upgrades/defaultSystemActionTriggers.ts | 65 +++++++++++++------ meteor/server/migration/upgrades/system.ts | 7 +- .../blueprints-integration/src/api/system.ts | 4 +- .../src/context/systemApplyConfigContext.ts | 6 ++ .../blueprints-integration/src/triggers.ts | 24 +++++++ .../TriggeredActionsEditor.tsx | 14 ++-- 8 files changed, 106 insertions(+), 63 deletions(-) create mode 100644 packages/blueprints-integration/src/context/systemApplyConfigContext.ts diff --git a/meteor/server/migration/0_1_0.ts b/meteor/server/migration/0_1_0.ts index 9d8bef61f0..b79a03d3be 100644 --- a/meteor/server/migration/0_1_0.ts +++ b/meteor/server/migration/0_1_0.ts @@ -1,11 +1,10 @@ import { addMigrationSteps } from './databaseMigration' import { logger } from '../logging' -import { getRandomId, protectString, getHash } from '../lib/tempLib' +import { getRandomId, protectString } from '../lib/tempLib' import { wrapDefaultObject } from '@sofie-automation/corelib/dist/settings/objectWithOverrides' import { ShowStyleVariantId } from '@sofie-automation/corelib/dist/dataModel/Ids' -import { ShowStyleBases, ShowStyleVariants, Studios, TriggeredActions } from '../collections' +import { ShowStyleBases, ShowStyleVariants, Studios } from '../collections' import { DEFAULT_MINIMUM_TAKE_SPAN } from '@sofie-automation/shared-lib/dist/core/constants' -import { DEFAULT_CORE_TRIGGERS } from './upgrades/defaultSystemActionTriggers' /** * This file contains system specific migration steps. @@ -131,32 +130,4 @@ export const addSteps = addMigrationSteps('0.1.0', [ } }, }, - { - id: 'TriggeredActions.core', - canBeRunAutomatically: true, - validate: async () => { - const coreTriggeredActionsCount = await TriggeredActions.countDocuments({ - showStyleBaseId: null, - }) - - if (coreTriggeredActionsCount === 0) { - return `No system-wide triggered actions set up.` - } - - return false - }, - migrate: async () => { - for (const triggeredAction of DEFAULT_CORE_TRIGGERS) { - await TriggeredActions.insertAsync({ - _id: protectString(getHash(triggeredAction._id)), - _rank: triggeredAction._rank, - name: triggeredAction.name, - blueprintUniqueId: null, - showStyleBaseId: null, - actionsWithOverrides: wrapDefaultObject(triggeredAction.actions), - triggersWithOverrides: wrapDefaultObject(triggeredAction.triggers), - }) - } - }, - }, ]) diff --git a/meteor/server/migration/upgrades/context.ts b/meteor/server/migration/upgrades/context.ts index 5e0f7b6af1..921032dd8b 100644 --- a/meteor/server/migration/upgrades/context.ts +++ b/meteor/server/migration/upgrades/context.ts @@ -1,6 +1,12 @@ -import { ICommonContext, NoteSeverity } from '@sofie-automation/blueprints-integration' -import { assertNever, getHash } from '@sofie-automation/corelib/dist/lib' +import { + IBlueprintDefaultCoreSystemTriggers, + ICommonContext, + NoteSeverity, +} from '@sofie-automation/blueprints-integration' +import { assertNever, clone, getHash } from '@sofie-automation/corelib/dist/lib' import { logger } from '../../logging' +import { ICoreSystemApplyConfigContext } from '@sofie-automation/blueprints-integration/dist/context/systemApplyConfigContext' +import { DEFAULT_CORE_TRIGGERS } from './defaultSystemActionTriggers' /** * This is almost identical to the one in the job-worker, but it is hard to share the implementation due to differing loggers @@ -56,3 +62,9 @@ export class CommonContext implements ICommonContext { } } } + +export class CoreSystemApplyConfigContext extends CommonContext implements ICoreSystemApplyConfigContext { + getDefaultSystemActionTriggers(): IBlueprintDefaultCoreSystemTriggers { + return clone(DEFAULT_CORE_TRIGGERS) + } +} diff --git a/meteor/server/migration/upgrades/defaultSystemActionTriggers.ts b/meteor/server/migration/upgrades/defaultSystemActionTriggers.ts index 22c74bfc61..83cf7abfaa 100644 --- a/meteor/server/migration/upgrades/defaultSystemActionTriggers.ts +++ b/meteor/server/migration/upgrades/defaultSystemActionTriggers.ts @@ -5,11 +5,34 @@ import { PlayoutActions, } from '@sofie-automation/blueprints-integration' import { getHash, protectString, generateTranslation as t } from '../../lib/tempLib' +import { TriggeredActionId } from '@sofie-automation/corelib/dist/dataModel/Ids' let j = 0 -export const DEFAULT_CORE_TRIGGERS: IBlueprintTriggeredActions[] = [ - { +export enum IBlueprintDefaultCoreTriggersType { + toggleShelf = 'toggleShelf', + activateRundownPlaylist = 'activateRundownPlaylist', + activateRundownPlaylistRehearsal = 'activateRundownPlaylistRehearsal', + deactivateRundownPlaylist = 'deactivateRundownPlaylist', + take = 'take', + hold = 'hold', + holdUndo = 'holdUndo', + resetRundownPlaylist = 'resetRundownPlaylist', + disableNextPiece = 'disableNextPiece', + disableNextPieceUndo = 'disableNextPieceUndo', + createSnapshotForDebug = 'createSnapshotForDebug', + moveNextPart = 'moveNextPart', + moveNextSegment = 'moveNextSegment', + movePreviousPart = 'movePreviousPart', + movePreviousSegment = 'movePreviousSegment', + goToOnAirLine = 'goToOnAirLine', + rewindSegments = 'rewindSegments', +} + +export type IBlueprintDefaultCoreTriggers = { [key in IBlueprintDefaultCoreTriggersType]: IBlueprintTriggeredActions } + +export const DEFAULT_CORE_TRIGGERS: IBlueprintDefaultCoreTriggers = { + [IBlueprintDefaultCoreTriggersType.toggleShelf]: { _id: 'core_toggleShelf', actions: { '0': { @@ -32,7 +55,7 @@ export const DEFAULT_CORE_TRIGGERS: IBlueprintTriggeredActions[] = [ _rank: ++j * 1000, name: t('Toggle Shelf'), }, - { + [IBlueprintDefaultCoreTriggersType.activateRundownPlaylist]: { _id: 'core_activateRundownPlaylist', actions: { '0': { @@ -55,7 +78,7 @@ export const DEFAULT_CORE_TRIGGERS: IBlueprintTriggeredActions[] = [ _rank: ++j * 1000, name: t('Activate (On-Air)'), }, - { + [IBlueprintDefaultCoreTriggersType.activateRundownPlaylistRehearsal]: { _id: 'core_activateRundownPlaylist_rehearsal', actions: { '0': { @@ -78,7 +101,7 @@ export const DEFAULT_CORE_TRIGGERS: IBlueprintTriggeredActions[] = [ _rank: ++j * 1000, name: t('Activate (Rehearsal)'), }, - { + [IBlueprintDefaultCoreTriggersType.deactivateRundownPlaylist]: { _id: 'core_deactivateRundownPlaylist', actions: { '0': { @@ -100,7 +123,7 @@ export const DEFAULT_CORE_TRIGGERS: IBlueprintTriggeredActions[] = [ _rank: ++j * 1000, name: t('Deactivate'), }, - { + [IBlueprintDefaultCoreTriggersType.take]: { _id: 'core_take', actions: { '0': { @@ -127,7 +150,7 @@ export const DEFAULT_CORE_TRIGGERS: IBlueprintTriggeredActions[] = [ _rank: ++j * 1000, name: t('Take'), }, - { + [IBlueprintDefaultCoreTriggersType.hold]: { _id: 'core_hold', actions: { '0': { @@ -149,7 +172,7 @@ export const DEFAULT_CORE_TRIGGERS: IBlueprintTriggeredActions[] = [ _rank: ++j * 1000, name: t('Hold'), }, - { + [IBlueprintDefaultCoreTriggersType.holdUndo]: { _id: 'core_hold_undo', actions: { '0': { @@ -172,7 +195,7 @@ export const DEFAULT_CORE_TRIGGERS: IBlueprintTriggeredActions[] = [ _rank: ++j * 1000, name: t('Undo Hold'), }, - { + [IBlueprintDefaultCoreTriggersType.resetRundownPlaylist]: { _id: 'core_reset_rundown_playlist', actions: { '0': { @@ -199,7 +222,7 @@ export const DEFAULT_CORE_TRIGGERS: IBlueprintTriggeredActions[] = [ _rank: ++j * 1000, name: t('Reset Rundown'), }, - { + [IBlueprintDefaultCoreTriggersType.disableNextPiece]: { _id: 'core_disable_next_piece', actions: { '0': { @@ -221,7 +244,7 @@ export const DEFAULT_CORE_TRIGGERS: IBlueprintTriggeredActions[] = [ _rank: ++j * 1000, name: t('Disable the next element'), }, - { + [IBlueprintDefaultCoreTriggersType.disableNextPieceUndo]: { _id: 'core_disable_next_piece_undo', actions: { '0': { @@ -244,7 +267,7 @@ export const DEFAULT_CORE_TRIGGERS: IBlueprintTriggeredActions[] = [ _rank: ++j * 1000, name: t('Undo Disable the next element'), }, - { + [IBlueprintDefaultCoreTriggersType.createSnapshotForDebug]: { _id: 'core_create_snapshot_for_debug', actions: { '0': { @@ -266,7 +289,7 @@ export const DEFAULT_CORE_TRIGGERS: IBlueprintTriggeredActions[] = [ _rank: ++j * 1000, name: t('Store Snapshot'), }, - { + [IBlueprintDefaultCoreTriggersType.moveNextPart]: { _id: 'core_move_next_part', actions: { '0': { @@ -290,7 +313,7 @@ export const DEFAULT_CORE_TRIGGERS: IBlueprintTriggeredActions[] = [ _rank: ++j * 1000, name: t('Move Next forwards'), }, - { + [IBlueprintDefaultCoreTriggersType.moveNextSegment]: { _id: 'core_move_next_segment', actions: { '0': { @@ -314,7 +337,7 @@ export const DEFAULT_CORE_TRIGGERS: IBlueprintTriggeredActions[] = [ _rank: ++j * 1000, name: t('Move Next to the following segment'), }, - { + [IBlueprintDefaultCoreTriggersType.movePreviousPart]: { _id: 'core_move_previous_part', actions: { '0': { @@ -338,7 +361,7 @@ export const DEFAULT_CORE_TRIGGERS: IBlueprintTriggeredActions[] = [ _rank: ++j * 1000, name: t('Move Next backwards'), }, - { + [IBlueprintDefaultCoreTriggersType.movePreviousSegment]: { _id: 'core_move_previous_segment', actions: { '0': { @@ -362,7 +385,7 @@ export const DEFAULT_CORE_TRIGGERS: IBlueprintTriggeredActions[] = [ _rank: ++j * 1000, name: t('Move Next to the previous segment'), }, - { + [IBlueprintDefaultCoreTriggersType.goToOnAirLine]: { _id: 'core_go_to_onAir_line', actions: { '0': { @@ -384,7 +407,7 @@ export const DEFAULT_CORE_TRIGGERS: IBlueprintTriggeredActions[] = [ _rank: ++j * 1000, name: t('Go to On Air line'), }, - { + [IBlueprintDefaultCoreTriggersType.rewindSegments]: { _id: 'core_rewind_segments', actions: { '0': { @@ -406,8 +429,8 @@ export const DEFAULT_CORE_TRIGGERS: IBlueprintTriggeredActions[] = [ _rank: ++j * 1000, name: t('Rewind segments to start'), }, -] +} -export const DEFAULT_CORE_TRIGGER_IDS = DEFAULT_CORE_TRIGGERS.map((triggeredAction) => - protectString(getHash(triggeredAction._id)) +export const DEFAULT_CORE_TRIGGER_IDS = Object.values(DEFAULT_CORE_TRIGGERS).map( + (triggeredAction) => protectString(getHash(triggeredAction._id)) ) diff --git a/meteor/server/migration/upgrades/system.ts b/meteor/server/migration/upgrades/system.ts index e991df968b..3621fd78e4 100644 --- a/meteor/server/migration/upgrades/system.ts +++ b/meteor/server/migration/upgrades/system.ts @@ -4,10 +4,11 @@ import { Blueprints, CoreSystem } from '../../collections' import { BlueprintManifestType, BlueprintResultApplySystemConfig, + IBlueprintTriggeredActions, SystemBlueprintManifest, } from '@sofie-automation/blueprints-integration' import { evalBlueprint } from '../../api/blueprints/cache' -import { CommonContext } from './context' +import { CoreSystemApplyConfigContext } from './context' import { updateTriggeredActionsForShowStyleBaseId } from './lib' import { CoreSystemId } from '@sofie-automation/corelib/dist/dataModel/Ids' import { DEFAULT_CORE_TRIGGERS } from './defaultSystemActionTriggers' @@ -21,7 +22,7 @@ export async function runUpgradeForCoreSystem(coreSystemId: CoreSystemId): Promi let result: BlueprintResultApplySystemConfig if (blueprintManifest && typeof blueprintManifest.applyConfig === 'function') { - const blueprintContext = new CommonContext( + const blueprintContext = new CoreSystemApplyConfigContext( 'applyConfig', `coreSystem:${coreSystem._id},blueprint:${blueprint.blueprintId}` ) @@ -82,6 +83,6 @@ async function loadCoreSystemAndBlueprint(coreSystemId: CoreSystemId) { function generateDefaultSystemConfig(): BlueprintResultApplySystemConfig { return { - triggeredActions: DEFAULT_CORE_TRIGGERS, + triggeredActions: Object.values(DEFAULT_CORE_TRIGGERS), } } diff --git a/packages/blueprints-integration/src/api/system.ts b/packages/blueprints-integration/src/api/system.ts index 8a90ff889b..045d3a0ee1 100644 --- a/packages/blueprints-integration/src/api/system.ts +++ b/packages/blueprints-integration/src/api/system.ts @@ -1,7 +1,7 @@ import type { IBlueprintTriggeredActions } from '../triggers' -import type { ICommonContext } from '../context' import type { MigrationStepSystem } from '../migrations' import type { BlueprintManifestBase, BlueprintManifestType } from './base' +import type { ICoreSystemApplyConfigContext } from '../context/systemApplyConfigContext' export interface SystemBlueprintManifest extends BlueprintManifestBase { blueprintType: BlueprintManifestType.SYSTEM @@ -32,7 +32,7 @@ export interface SystemBlueprintManifest extends BlueprintManifestBase { * This should be written to give a predictable and stable result, it can be called with the same config multiple times */ applyConfig?: ( - context: ICommonContext + context: ICoreSystemApplyConfigContext // config: TRawConfig, ) => BlueprintResultApplySystemConfig } diff --git a/packages/blueprints-integration/src/context/systemApplyConfigContext.ts b/packages/blueprints-integration/src/context/systemApplyConfigContext.ts new file mode 100644 index 0000000000..c1878ed75c --- /dev/null +++ b/packages/blueprints-integration/src/context/systemApplyConfigContext.ts @@ -0,0 +1,6 @@ +import type { IBlueprintDefaultCoreSystemTriggers } from '../triggers' +import type { ICommonContext } from './baseContext' + +export interface ICoreSystemApplyConfigContext extends ICommonContext { + getDefaultSystemActionTriggers(): IBlueprintDefaultCoreSystemTriggers +} diff --git a/packages/blueprints-integration/src/triggers.ts b/packages/blueprints-integration/src/triggers.ts index 3b7a54db85..c360fa6567 100644 --- a/packages/blueprints-integration/src/triggers.ts +++ b/packages/blueprints-integration/src/triggers.ts @@ -340,3 +340,27 @@ export interface IBlueprintTriggeredActions { } export { SomeActionIdentifier, ClientActions, PlayoutActions } + +export enum IBlueprintDefaultCoreSystemTriggersType { + toggleShelf = 'toggleShelf', + activateRundownPlaylist = 'activateRundownPlaylist', + activateRundownPlaylistRehearsal = 'activateRundownPlaylistRehearsal', + deactivateRundownPlaylist = 'deactivateRundownPlaylist', + take = 'take', + hold = 'hold', + holdUndo = 'holdUndo', + resetRundownPlaylist = 'resetRundownPlaylist', + disableNextPiece = 'disableNextPiece', + disableNextPieceUndo = 'disableNextPieceUndo', + createSnapshotForDebug = 'createSnapshotForDebug', + moveNextPart = 'moveNextPart', + moveNextSegment = 'moveNextSegment', + movePreviousPart = 'movePreviousPart', + movePreviousSegment = 'movePreviousSegment', + goToOnAirLine = 'goToOnAirLine', + rewindSegments = 'rewindSegments', +} + +export type IBlueprintDefaultCoreSystemTriggers = { + [key in IBlueprintDefaultCoreSystemTriggersType]: IBlueprintTriggeredActions +} diff --git a/packages/webui/src/client/ui/Settings/components/triggeredActions/TriggeredActionsEditor.tsx b/packages/webui/src/client/ui/Settings/components/triggeredActions/TriggeredActionsEditor.tsx index 8a623bc773..d313b89a5c 100644 --- a/packages/webui/src/client/ui/Settings/components/triggeredActions/TriggeredActionsEditor.tsx +++ b/packages/webui/src/client/ui/Settings/components/triggeredActions/TriggeredActionsEditor.tsx @@ -437,7 +437,7 @@ export const TriggeredActionsEditor: React.FC = function TriggeredAction {showStyleBaseId !== null ? ( <>
- {(systemTriggeredActionIds?.length ?? 0) > 0 && !parsedTriggerFilter ? ( + {!parsedTriggerFilter ? (

setSystemWideCollapsed(!systemWideCollapsed)} @@ -470,13 +470,19 @@ export const TriggeredActionsEditor: React.FC = function TriggeredAction /> )) : null} + + {!systemWideCollapsed && !parsedTriggerFilter && systemTriggeredActionIds?.length === 0 && ( +

{t('No Action Triggers set up.')}

+ )}

) : null}
- + + + Date: Tue, 12 Nov 2024 14:18:10 +0000 Subject: [PATCH 07/14] wip: move `Studio.settings` to `Studio.settingsWithOverrides` --- meteor/__mocks__/defaultCollectionObjects.ts | 4 +- meteor/server/api/evaluations.ts | 4 +- meteor/server/api/rest/v1/typeConversion.ts | 8 +- meteor/server/api/studio/api.ts | 4 +- meteor/server/migration/0_1_0.ts | 4 +- meteor/server/migration/X_X_X.ts | 48 +- .../migration/__tests__/migrations.test.ts | 12 +- meteor/server/publications/lib/quickLoop.ts | 10 +- .../partInstancesUI/publication.ts | 8 +- .../partInstancesUI/reactiveContentCache.ts | 16 +- .../partInstancesUI/rundownContentObserver.ts | 27 +- .../publications/partsUI/publication.ts | 8 +- .../partsUI/reactiveContentCache.ts | 16 +- .../partsUI/rundownContentObserver.ts | 27 +- .../checkPieceContentStatus.ts | 4 +- .../pieceContentStatusUI/common.ts | 6 +- meteor/server/publications/studioUI.ts | 6 +- packages/corelib/src/dataModel/Studio.ts | 3 +- packages/corelib/src/studio/baseline.ts | 4 +- packages/job-worker/src/blueprints/config.ts | 9 +- .../context/OnTimelineGenerateContext.ts | 5 +- .../blueprints/context/PartEventContext.ts | 5 +- .../src/blueprints/context/RundownContext.ts | 5 +- .../blueprints/context/RundownEventContext.ts | 5 +- .../blueprints/context/ShowStyleContext.ts | 5 +- .../src/blueprints/context/StudioContext.ts | 13 +- .../blueprints/context/StudioUserContext.ts | 4 +- .../SyncIngestUpdateToPartInstanceContext.ts | 5 +- .../src/blueprints/context/adlibActions.ts | 4 +- .../job-worker/src/ingest/expectedPackages.ts | 17 +- packages/job-worker/src/jobs/index.ts | 9 +- packages/job-worker/src/jobs/studio.ts | 58 ++ .../src/playout/abPlayback/index.ts | 3 +- .../playout/abPlayback/routeSetDisabling.ts | 5 +- .../job-worker/src/playout/lookahead/index.ts | 5 +- .../src/playout/timeline/generate.ts | 5 +- packages/job-worker/src/playout/upgrade.ts | 6 +- packages/job-worker/src/rundownPlaylists.ts | 5 +- packages/job-worker/src/workers/caches.ts | 40 +- .../src/workers/context/JobContextImpl.ts | 10 +- .../workers/context/StudioCacheContextImpl.ts | 24 +- .../workers/context/StudioRouteSetUpdater.ts | 59 +- .../job-worker/src/workers/events/child.ts | 4 +- .../job-worker/src/workers/ingest/child.ts | 4 +- .../job-worker/src/workers/studio/child.ts | 4 +- .../src/__mocks__/defaultCollectionObjects.ts | 4 +- .../src/client/ui/Settings/Studio/Generic.tsx | 650 ++++++++++-------- 47 files changed, 746 insertions(+), 445 deletions(-) create mode 100644 packages/job-worker/src/jobs/studio.ts diff --git a/meteor/__mocks__/defaultCollectionObjects.ts b/meteor/__mocks__/defaultCollectionObjects.ts index d2349c80ec..62420caaac 100644 --- a/meteor/__mocks__/defaultCollectionObjects.ts +++ b/meteor/__mocks__/defaultCollectionObjects.ts @@ -105,12 +105,12 @@ export function defaultStudio(_id: StudioId): DBStudio { mappingsWithOverrides: wrapDefaultObject({}), supportedShowStyleBase: [], blueprintConfigWithOverrides: wrapDefaultObject({}), - settings: { + settingsWithOverrides: wrapDefaultObject({ frameRate: 25, mediaPreviewsUrl: '', minimumTakeSpan: DEFAULT_MINIMUM_TAKE_SPAN, fallbackPartDuration: DEFAULT_FALLBACK_PART_DURATION, - }, + }), _rundownVersionHash: '', routeSetsWithOverrides: wrapDefaultObject({}), routeSetExclusivityGroupsWithOverrides: wrapDefaultObject({}), diff --git a/meteor/server/api/evaluations.ts b/meteor/server/api/evaluations.ts index 3034110a65..3be9d57818 100644 --- a/meteor/server/api/evaluations.ts +++ b/meteor/server/api/evaluations.ts @@ -10,6 +10,7 @@ import { sendSlackMessageToWebhook } from './integration/slack' import { OrganizationId, UserId } from '@sofie-automation/corelib/dist/dataModel/Ids' import { DBRundownPlaylist } from '@sofie-automation/corelib/dist/dataModel/RundownPlaylist' import { Evaluations, RundownPlaylists } from '../collections' +import { applyAndValidateOverrides } from '@sofie-automation/corelib/dist/settings/objectWithOverrides' export async function saveEvaluation( credentials: { @@ -33,8 +34,9 @@ export async function saveEvaluation( deferAsync(async () => { const studio = await fetchStudioLight(evaluation.studioId) if (!studio) throw new Meteor.Error(500, `Studio ${evaluation.studioId} not found!`) + const studioSettings = applyAndValidateOverrides(studio.settingsWithOverrides).obj - const webhookUrls = _.compact((studio.settings.slackEvaluationUrls || '').split(',')) + const webhookUrls = _.compact((studioSettings.slackEvaluationUrls || '').split(',')) if (webhookUrls.length) { // Only send notes if not everything is OK diff --git a/meteor/server/api/rest/v1/typeConversion.ts b/meteor/server/api/rest/v1/typeConversion.ts index a9c43b9bf0..8bb122c617 100644 --- a/meteor/server/api/rest/v1/typeConversion.ts +++ b/meteor/server/api/rest/v1/typeConversion.ts @@ -266,13 +266,17 @@ export async function studioFrom(apiStudio: APIStudio, existingId?: StudioId): P ? updateOverrides(studio.blueprintConfigWithOverrides, apiStudio.config as IBlueprintConfig) : wrapDefaultObject({}) + const studioSettings = studioSettingsFrom(apiStudio.settings) + return { _id: existingId ?? getRandomId(), name: apiStudio.name, blueprintId: blueprint?._id, blueprintConfigPresetId: apiStudio.blueprintConfigPresetId, blueprintConfigWithOverrides: blueprintConfig, - settings: studioSettingsFrom(apiStudio.settings), + settingsWithOverrides: studio + ? updateOverrides(studio.settingsWithOverrides, studioSettings) + : wrapDefaultObject(studioSettings), supportedShowStyleBase: apiStudio.supportedShowStyleBase?.map((id) => protectString(id)) ?? [], organizationId: null, mappingsWithOverrides: wrapDefaultObject({}), @@ -293,7 +297,7 @@ export async function studioFrom(apiStudio: APIStudio, existingId?: StudioId): P } export function APIStudioFrom(studio: DBStudio): APIStudio { - const studioSettings = APIStudioSettingsFrom(studio.settings) + const studioSettings = APIStudioSettingsFrom(applyAndValidateOverrides(studio.settingsWithOverrides).obj) return { name: studio.name, diff --git a/meteor/server/api/studio/api.ts b/meteor/server/api/studio/api.ts index 94ac811d40..fa5d7c1c37 100644 --- a/meteor/server/api/studio/api.ts +++ b/meteor/server/api/studio/api.ts @@ -45,11 +45,11 @@ export async function insertStudioInner(organizationId: OrganizationId | null, n supportedShowStyleBase: [], blueprintConfigWithOverrides: wrapDefaultObject({}), // testToolsConfig?: ITestToolsConfig - settings: { + settingsWithOverrides: wrapDefaultObject({ frameRate: 25, mediaPreviewsUrl: '', minimumTakeSpan: DEFAULT_MINIMUM_TAKE_SPAN, - }, + }), _rundownVersionHash: '', routeSetsWithOverrides: wrapDefaultObject({}), routeSetExclusivityGroupsWithOverrides: wrapDefaultObject({}), diff --git a/meteor/server/migration/0_1_0.ts b/meteor/server/migration/0_1_0.ts index b79a03d3be..f4a6abf7ad 100644 --- a/meteor/server/migration/0_1_0.ts +++ b/meteor/server/migration/0_1_0.ts @@ -29,11 +29,11 @@ export const addSteps = addMigrationSteps('0.1.0', [ name: 'Default studio', organizationId: null, supportedShowStyleBase: [], - settings: { + settingsWithOverrides: wrapDefaultObject({ frameRate: 25, mediaPreviewsUrl: '', minimumTakeSpan: DEFAULT_MINIMUM_TAKE_SPAN, - }, + }), mappingsWithOverrides: wrapDefaultObject({}), blueprintConfigWithOverrides: wrapDefaultObject({}), _rundownVersionHash: '', diff --git a/meteor/server/migration/X_X_X.ts b/meteor/server/migration/X_X_X.ts index 590e5a1c80..d4162596f8 100644 --- a/meteor/server/migration/X_X_X.ts +++ b/meteor/server/migration/X_X_X.ts @@ -1,11 +1,15 @@ import { addMigrationSteps } from './databaseMigration' import { CURRENT_SYSTEM_VERSION } from './currentSystemVersion' import { Studios, TriggeredActions } from '../collections' -import { convertObjectIntoOverrides } from '@sofie-automation/corelib/dist/settings/objectWithOverrides' +import { + convertObjectIntoOverrides, + wrapDefaultObject, +} from '@sofie-automation/corelib/dist/settings/objectWithOverrides' import { StudioRouteSet, StudioRouteSetExclusivityGroup, StudioPackageContainer, + IStudioSettings, } from '@sofie-automation/corelib/dist/dataModel/Studio' import { DEFAULT_CORE_TRIGGER_IDS } from './upgrades/defaultSystemActionTriggers' @@ -212,4 +216,46 @@ export const addSteps = addMigrationSteps(CURRENT_SYSTEM_VERSION, [ }) }, }, + + { + id: `convert studio.settings to ObjectWithOverrides`, + canBeRunAutomatically: true, + validate: async () => { + const studios = await Studios.findFetchAsync({ + settings: { $exists: true }, + settingsWithOverrides: { $exists: false }, + }) + + for (const studio of studios) { + //@ts-expect-error settings is not typed as ObjectWithOverrides + if (studio.settings) { + return 'settings must be converted to an ObjectWithOverrides' + } + } + + return false + }, + migrate: async () => { + const studios = await Studios.findFetchAsync({ + settings: { $exists: true }, + settingsWithOverrides: { $exists: false }, + }) + + for (const studio of studios) { + //@ts-expect-error settings is typed as Record + const oldSettings = studio.settings + + const newSettings = wrapDefaultObject(oldSettings || {}) + + await Studios.updateAsync(studio._id, { + $set: { + settingsWithOverrides: newSettings, + }, + $unset: { + // settings: 1, + }, + }) + } + }, + }, ]) diff --git a/meteor/server/migration/__tests__/migrations.test.ts b/meteor/server/migration/__tests__/migrations.test.ts index 276eed4054..62967260fe 100644 --- a/meteor/server/migration/__tests__/migrations.test.ts +++ b/meteor/server/migration/__tests__/migrations.test.ts @@ -121,11 +121,11 @@ describe('Migrations', () => { name: 'Default studio', organizationId: null, supportedShowStyleBase: [], - settings: { + settingsWithOverrides: wrapDefaultObject({ mediaPreviewsUrl: '', frameRate: 25, minimumTakeSpan: DEFAULT_MINIMUM_TAKE_SPAN, - }, + }), mappingsWithOverrides: wrapDefaultObject({}), blueprintConfigWithOverrides: wrapDefaultObject({}), _rundownVersionHash: '', @@ -159,11 +159,11 @@ describe('Migrations', () => { name: 'Default studio', organizationId: null, supportedShowStyleBase: [], - settings: { + settingsWithOverrides: wrapDefaultObject({ mediaPreviewsUrl: '', frameRate: 25, minimumTakeSpan: DEFAULT_MINIMUM_TAKE_SPAN, - }, + }), mappingsWithOverrides: wrapDefaultObject({}), blueprintConfigWithOverrides: wrapDefaultObject({}), _rundownVersionHash: '', @@ -197,11 +197,11 @@ describe('Migrations', () => { name: 'Default studio', organizationId: null, supportedShowStyleBase: [], - settings: { + settingsWithOverrides: wrapDefaultObject({ mediaPreviewsUrl: '', frameRate: 25, minimumTakeSpan: DEFAULT_MINIMUM_TAKE_SPAN, - }, + }), mappingsWithOverrides: wrapDefaultObject({}), blueprintConfigWithOverrides: wrapDefaultObject({}), _rundownVersionHash: '', diff --git a/meteor/server/publications/lib/quickLoop.ts b/meteor/server/publications/lib/quickLoop.ts index 967d4ac745..f8c356a7ce 100644 --- a/meteor/server/publications/lib/quickLoop.ts +++ b/meteor/server/publications/lib/quickLoop.ts @@ -10,7 +10,7 @@ import { ProtectedString, unprotectString } from '@sofie-automation/corelib/dist import { DEFAULT_FALLBACK_PART_DURATION } from '@sofie-automation/shared-lib/dist/core/constants' import { getCurrentTime } from '../../lib/lib' import { generateTranslation } from '@sofie-automation/corelib/dist/lib' -import { DBStudio } from '@sofie-automation/corelib/dist/dataModel/Studio' +import { IStudioSettings } from '@sofie-automation/corelib/dist/dataModel/Studio' import { DBPartInstance } from '@sofie-automation/corelib/dist/dataModel/PartInstance' import { DBSegment } from '@sofie-automation/corelib/dist/dataModel/Segment' import { ReadonlyObjectDeep } from 'type-fest/source/readonly-deep' @@ -47,7 +47,7 @@ export function modifyPartForQuickLoop( segmentRanks: Record, rundownRanks: Record, playlist: Pick, - studio: Pick, + studioSettings: IStudioSettings, quickLoopStartPosition: MarkerPosition | undefined, quickLoopEndPosition: MarkerPosition | undefined, canSetAutoNext = () => true @@ -60,7 +60,7 @@ export function modifyPartForQuickLoop( compareMarkerPositions(quickLoopStartPosition, partPosition) >= 0 && compareMarkerPositions(partPosition, quickLoopEndPosition) >= 0 - const fallbackPartDuration = studio.settings.fallbackPartDuration ?? DEFAULT_FALLBACK_PART_DURATION + const fallbackPartDuration = studioSettings.fallbackPartDuration ?? DEFAULT_FALLBACK_PART_DURATION if (isLoopingOverriden && (part.expectedDuration ?? 0) < fallbackPartDuration) { if (playlist.quickLoop?.forceAutoNext === ForceQuickLoopAutoNext.ENABLED_FORCING_MIN_DURATION) { @@ -82,7 +82,7 @@ export function modifyPartInstanceForQuickLoop( segmentRanks: Record, rundownRanks: Record, playlist: Pick, - studio: Pick, + studioSettings: IStudioSettings, quickLoopStartPosition: MarkerPosition | undefined, quickLoopEndPosition: MarkerPosition | undefined ): void { @@ -107,7 +107,7 @@ export function modifyPartInstanceForQuickLoop( segmentRanks, rundownRanks, playlist, - studio, + studioSettings, quickLoopStartPosition, quickLoopEndPosition, canAutoNext // do not adjust the part instance if we have passed the time where we can still enable auto next diff --git a/meteor/server/publications/partInstancesUI/publication.ts b/meteor/server/publications/partInstancesUI/publication.ts index 01a21711f2..11e017ae16 100644 --- a/meteor/server/publications/partInstancesUI/publication.ts +++ b/meteor/server/publications/partInstancesUI/publication.ts @@ -106,7 +106,7 @@ async function setupUIPartInstancesPublicationObservers( changed: () => triggerUpdate({ invalidateQuickLoop: true }), removed: () => triggerUpdate({ invalidateQuickLoop: true }), }), - cache.Studios.find({}).observeChanges({ + cache.StudioSettings.find({}).observeChanges({ added: () => triggerUpdate({ invalidateQuickLoop: true }), changed: () => triggerUpdate({ invalidateQuickLoop: true }), removed: () => triggerUpdate({ invalidateQuickLoop: true }), @@ -148,8 +148,8 @@ export async function manipulateUIPartInstancesPublicationData( const playlist = state.contentCache.RundownPlaylists.findOne({}) if (!playlist) return - const studio = state.contentCache.Studios.findOne({}) - if (!studio) return + const studioSettings = state.contentCache.StudioSettings.findOne({}) + if (!studioSettings) return const rundownRanks = stringsToIndexLookup(playlist.rundownIdsInOrder as unknown as string[]) const segmentRanks = extractRanks(state.contentCache.Segments.find({}).fetch()) @@ -191,7 +191,7 @@ export async function manipulateUIPartInstancesPublicationData( segmentRanks, rundownRanks, playlist, - studio, + studioSettings.settings, quickLoopStartPosition, quickLoopEndPosition ) diff --git a/meteor/server/publications/partInstancesUI/reactiveContentCache.ts b/meteor/server/publications/partInstancesUI/reactiveContentCache.ts index b7c01b627a..aac741cbb4 100644 --- a/meteor/server/publications/partInstancesUI/reactiveContentCache.ts +++ b/meteor/server/publications/partInstancesUI/reactiveContentCache.ts @@ -3,9 +3,10 @@ import { ReactiveCacheCollection } from '../lib/ReactiveCacheCollection' import { literal } from '@sofie-automation/corelib/dist/lib' import { MongoFieldSpecifierOnesStrict, MongoFieldSpecifierZeroes } from '@sofie-automation/corelib/dist/mongo' import { DBRundownPlaylist } from '@sofie-automation/corelib/dist/dataModel/RundownPlaylist' -import { DBStudio } from '@sofie-automation/corelib/dist/dataModel/Studio' +import { DBStudio, IStudioSettings } from '@sofie-automation/corelib/dist/dataModel/Studio' import { DBPartInstance } from '@sofie-automation/corelib/dist/dataModel/PartInstance' import { DBPart } from '@sofie-automation/corelib/dist/dataModel/Part' +import { StudioId } from '@sofie-automation/corelib/dist/dataModel/Ids' export type RundownPlaylistCompact = Pick export const rundownPlaylistFieldSpecifier = literal>({ @@ -36,14 +37,19 @@ export const partInstanceFieldSpecifier = literal>>({ _id: 1, - settings: 1, + settingsWithOverrides: 1, }) +export interface StudioSettingsDoc { + _id: StudioId + settings: IStudioSettings +} + export interface ContentCache { - Studios: ReactiveCacheCollection> + StudioSettings: ReactiveCacheCollection Segments: ReactiveCacheCollection> Parts: ReactiveCacheCollection> PartInstances: ReactiveCacheCollection> @@ -52,7 +58,7 @@ export interface ContentCache { export function createReactiveContentCache(): ContentCache { const cache: ContentCache = { - Studios: new ReactiveCacheCollection>('studios'), + StudioSettings: new ReactiveCacheCollection('studioSettings'), Segments: new ReactiveCacheCollection>('segments'), Parts: new ReactiveCacheCollection>('parts'), PartInstances: new ReactiveCacheCollection>('partInstances'), diff --git a/meteor/server/publications/partInstancesUI/rundownContentObserver.ts b/meteor/server/publications/partInstancesUI/rundownContentObserver.ts index a2f14e6c44..2acff0301c 100644 --- a/meteor/server/publications/partInstancesUI/rundownContentObserver.ts +++ b/meteor/server/publications/partInstancesUI/rundownContentObserver.ts @@ -7,10 +7,21 @@ import { partInstanceFieldSpecifier, rundownPlaylistFieldSpecifier, segmentFieldSpecifier, + StudioFields, studioFieldSpecifier, + StudioSettingsDoc, } from './reactiveContentCache' import { PartInstances, Parts, RundownPlaylists, Segments, Studios } from '../../collections' import { waitForAllObserversReady } from '../lib/lib' +import { DBStudio } from '@sofie-automation/corelib/dist/dataModel/Studio' +import { applyAndValidateOverrides } from '@sofie-automation/corelib/dist/settings/objectWithOverrides' + +function convertStudioSettingsDoc(doc: Pick): StudioSettingsDoc { + return { + _id: doc._id, + settings: applyAndValidateOverrides(doc.settingsWithOverrides).obj, + } +} export class RundownContentObserver { readonly #cache: ContentCache @@ -31,11 +42,23 @@ export class RundownContentObserver { logger.silly(`Creating RundownContentObserver for rundowns "${rundownIds.join(',')}"`) const observers = await waitForAllObserversReady([ - Studios.observeChanges( + Studios.observe( { _id: studioId, }, - cache.Studios.link(), + { + added: (doc) => { + const newDoc = convertStudioSettingsDoc(doc) + cache.StudioSettings.upsert(doc._id, { $set: newDoc as Partial }) + }, + changed: (doc) => { + const newDoc = convertStudioSettingsDoc(doc) + cache.StudioSettings.upsert(doc._id, { $set: newDoc as Partial }) + }, + removed: (doc) => { + cache.StudioSettings.remove(doc._id) + }, + }, { fields: studioFieldSpecifier, } diff --git a/meteor/server/publications/partsUI/publication.ts b/meteor/server/publications/partsUI/publication.ts index 31af1ed031..6e5b051553 100644 --- a/meteor/server/publications/partsUI/publication.ts +++ b/meteor/server/publications/partsUI/publication.ts @@ -93,7 +93,7 @@ async function setupUIPartsPublicationObservers( changed: () => triggerUpdate({ invalidateQuickLoop: true }), removed: () => triggerUpdate({ invalidateQuickLoop: true }), }), - cache.Studios.find({}).observeChanges({ + cache.StudioSettings.find({}).observeChanges({ added: () => triggerUpdate({ invalidateQuickLoop: true }), changed: () => triggerUpdate({ invalidateQuickLoop: true }), removed: () => triggerUpdate({ invalidateQuickLoop: true }), @@ -135,8 +135,8 @@ export async function manipulateUIPartsPublicationData( const playlist = state.contentCache.RundownPlaylists.findOne({}) if (!playlist) return - const studio = state.contentCache.Studios.findOne({}) - if (!studio) return + const studioSettings = state.contentCache.StudioSettings.findOne({}) + if (!studioSettings) return const rundownRanks = stringsToIndexLookup(playlist.rundownIdsInOrder as unknown as string[]) const segmentRanks = extractRanks(state.contentCache.Segments.find({}).fetch()) @@ -178,7 +178,7 @@ export async function manipulateUIPartsPublicationData( segmentRanks, rundownRanks, playlist, - studio, + studioSettings.settings, quickLoopStartPosition, quickLoopEndPosition ) diff --git a/meteor/server/publications/partsUI/reactiveContentCache.ts b/meteor/server/publications/partsUI/reactiveContentCache.ts index 13361fd51c..12d9423e8e 100644 --- a/meteor/server/publications/partsUI/reactiveContentCache.ts +++ b/meteor/server/publications/partsUI/reactiveContentCache.ts @@ -4,7 +4,8 @@ import { ReactiveCacheCollection } from '../lib/ReactiveCacheCollection' import { literal } from '@sofie-automation/corelib/dist/lib' import { MongoFieldSpecifierOnesStrict, MongoFieldSpecifierZeroes } from '@sofie-automation/corelib/dist/mongo' import { DBRundownPlaylist } from '@sofie-automation/corelib/dist/dataModel/RundownPlaylist' -import { DBStudio } from '@sofie-automation/corelib/dist/dataModel/Studio' +import { DBStudio, IStudioSettings } from '@sofie-automation/corelib/dist/dataModel/Studio' +import { StudioId } from '@sofie-automation/corelib/dist/dataModel/Ids' export type RundownPlaylistCompact = Pick export const rundownPlaylistFieldSpecifier = literal>({ @@ -26,14 +27,19 @@ export const partFieldSpecifier = literal>>({ _id: 1, - settings: 1, + settingsWithOverrides: 1, }) +export interface StudioSettingsDoc { + _id: StudioId + settings: IStudioSettings +} + export interface ContentCache { - Studios: ReactiveCacheCollection> + StudioSettings: ReactiveCacheCollection Segments: ReactiveCacheCollection> Parts: ReactiveCacheCollection> RundownPlaylists: ReactiveCacheCollection @@ -41,7 +47,7 @@ export interface ContentCache { export function createReactiveContentCache(): ContentCache { const cache: ContentCache = { - Studios: new ReactiveCacheCollection>('studios'), + StudioSettings: new ReactiveCacheCollection('studioSettings'), Segments: new ReactiveCacheCollection>('segments'), Parts: new ReactiveCacheCollection>('parts'), RundownPlaylists: new ReactiveCacheCollection('rundownPlaylists'), diff --git a/meteor/server/publications/partsUI/rundownContentObserver.ts b/meteor/server/publications/partsUI/rundownContentObserver.ts index ee7e92c7d6..8a8032ecf5 100644 --- a/meteor/server/publications/partsUI/rundownContentObserver.ts +++ b/meteor/server/publications/partsUI/rundownContentObserver.ts @@ -6,10 +6,21 @@ import { partFieldSpecifier, rundownPlaylistFieldSpecifier, segmentFieldSpecifier, + StudioFields, studioFieldSpecifier, + StudioSettingsDoc, } from './reactiveContentCache' import { Parts, RundownPlaylists, Segments, Studios } from '../../collections' import { waitForAllObserversReady } from '../lib/lib' +import { DBStudio } from '@sofie-automation/corelib/dist/dataModel/Studio' +import { applyAndValidateOverrides } from '@sofie-automation/corelib/dist/settings/objectWithOverrides' + +function convertStudioSettingsDoc(doc: Pick): StudioSettingsDoc { + return { + _id: doc._id, + settings: applyAndValidateOverrides(doc.settingsWithOverrides).obj, + } +} export class RundownContentObserver { readonly #cache: ContentCache @@ -29,11 +40,23 @@ export class RundownContentObserver { logger.silly(`Creating RundownContentObserver for rundowns "${rundownIds.join(',')}"`) const observers = await waitForAllObserversReady([ - Studios.observeChanges( + Studios.observe( { _id: studioId, }, - cache.Studios.link(), + { + added: (doc) => { + const newDoc = convertStudioSettingsDoc(doc) + cache.StudioSettings.upsert(doc._id, { $set: newDoc as Partial }) + }, + changed: (doc) => { + const newDoc = convertStudioSettingsDoc(doc) + cache.StudioSettings.upsert(doc._id, { $set: newDoc as Partial }) + }, + removed: (doc) => { + cache.StudioSettings.remove(doc._id) + }, + }, { fields: studioFieldSpecifier, } diff --git a/meteor/server/publications/pieceContentStatusUI/checkPieceContentStatus.ts b/meteor/server/publications/pieceContentStatusUI/checkPieceContentStatus.ts index 536c41fbed..159830fbc5 100644 --- a/meteor/server/publications/pieceContentStatusUI/checkPieceContentStatus.ts +++ b/meteor/server/publications/pieceContentStatusUI/checkPieceContentStatus.ts @@ -185,7 +185,7 @@ export type PieceContentStatusPiece = Pick { + extends Pick { /** Mappings between the physical devices / outputs and logical ones */ mappings: MappingsExt /** Route sets with overrides */ @@ -194,6 +194,8 @@ export interface PieceContentStatusStudio * (These are used by the Package Manager and the Expected Packages) */ packageContainers: Record + + settings: IStudioSettings } export async function checkPieceContentStatusAndDependencies( diff --git a/meteor/server/publications/pieceContentStatusUI/common.ts b/meteor/server/publications/pieceContentStatusUI/common.ts index 591f1eb16e..32cf9328d1 100644 --- a/meteor/server/publications/pieceContentStatusUI/common.ts +++ b/meteor/server/publications/pieceContentStatusUI/common.ts @@ -13,7 +13,7 @@ import { PieceContentStatusStudio } from './checkPieceContentStatus' export type StudioFields = | '_id' - | 'settings' + | 'settingsWithOverrides' | 'packageContainersWithOverrides' | 'previewContainerIds' | 'thumbnailContainerIds' @@ -21,7 +21,7 @@ export type StudioFields = | 'routeSetsWithOverrides' export const studioFieldSpecifier = literal>>({ _id: 1, - settings: 1, + settingsWithOverrides: 1, packageContainersWithOverrides: 1, previewContainerIds: 1, thumbnailContainerIds: 1, @@ -112,7 +112,7 @@ export async function fetchStudio(studioId: StudioId): Promise): UIStudio { name: studio.name, mappings: applyAndValidateOverrides(studio.mappingsWithOverrides).obj, - settings: studio.settings, + settings: applyAndValidateOverrides(studio.settingsWithOverrides).obj, routeSets: applyAndValidateOverrides(studio.routeSetsWithOverrides).obj, routeSetExclusivityGroups: applyAndValidateOverrides(studio.routeSetExclusivityGroupsWithOverrides).obj, @@ -47,14 +47,14 @@ type StudioFields = | '_id' | 'name' | 'mappingsWithOverrides' - | 'settings' + | 'settingsWithOverrides' | 'routeSetsWithOverrides' | 'routeSetExclusivityGroupsWithOverrides' const fieldSpecifier = literal>>({ _id: 1, name: 1, mappingsWithOverrides: 1, - settings: 1, + settingsWithOverrides: 1, routeSetsWithOverrides: 1, routeSetExclusivityGroupsWithOverrides: 1, }) diff --git a/packages/corelib/src/dataModel/Studio.ts b/packages/corelib/src/dataModel/Studio.ts index 78ee077c19..44abb1a068 100644 --- a/packages/corelib/src/dataModel/Studio.ts +++ b/packages/corelib/src/dataModel/Studio.ts @@ -122,7 +122,8 @@ export interface DBStudio { /** Config values are used by the Blueprints */ blueprintConfigWithOverrides: ObjectWithOverrides - settings: IStudioSettings + settingsWithOverrides: ObjectWithOverrides + // settings: IStudioSettings _rundownVersionHash: string diff --git a/packages/corelib/src/studio/baseline.ts b/packages/corelib/src/studio/baseline.ts index d1766e1245..86492cf75c 100644 --- a/packages/corelib/src/studio/baseline.ts +++ b/packages/corelib/src/studio/baseline.ts @@ -1,4 +1,4 @@ -import { StudioLight } from '../dataModel/Studio' +import { DBStudio } from '../dataModel/Studio' import { TimelineComplete } from '../dataModel/Timeline' import { ReadonlyDeep } from 'type-fest' import { unprotectString } from '../protectedString' @@ -6,7 +6,7 @@ import { Blueprint } from '../dataModel/Blueprint' export function shouldUpdateStudioBaselineInner( coreVersion: string, - studio: ReadonlyDeep, + studio: Pick, studioTimeline: ReadonlyDeep | null, studioBlueprint: Pick | null ): string | false { diff --git a/packages/job-worker/src/blueprints/config.ts b/packages/job-worker/src/blueprints/config.ts index 77ae34389d..78b755419f 100644 --- a/packages/job-worker/src/blueprints/config.ts +++ b/packages/job-worker/src/blueprints/config.ts @@ -11,10 +11,9 @@ import { stringifyError } from '@sofie-automation/shared-lib/dist/lib/stringifyE import _ = require('underscore') import { logger } from '../logging' import { CommonContext } from './context' -import { DBStudio, IStudioSettings } from '@sofie-automation/corelib/dist/dataModel/Studio' +import { IStudioSettings } from '@sofie-automation/corelib/dist/dataModel/Studio' import { protectString } from '@sofie-automation/corelib/dist/protectedString' -import { ProcessedShowStyleCompound, StudioCacheContext } from '../jobs' -import { applyAndValidateOverrides } from '@sofie-automation/corelib/dist/settings/objectWithOverrides' +import { JobStudio, ProcessedShowStyleCompound, StudioCacheContext } from '../jobs' /** * Parse a string containing BlueprintConfigRefs (`${studio.studio0.myConfigField}`) to replace the refs with the current values @@ -100,10 +99,10 @@ export function compileCoreConfigValues(studioSettings: ReadonlyDeep, + studio: ReadonlyDeep, blueprint: ReadonlyDeep ): ProcessedStudioConfig { - let res: any = applyAndValidateOverrides(studio.blueprintConfigWithOverrides).obj + let res: any = studio.blueprintConfig try { if (blueprint.preprocessConfig) { diff --git a/packages/job-worker/src/blueprints/context/OnTimelineGenerateContext.ts b/packages/job-worker/src/blueprints/context/OnTimelineGenerateContext.ts index 6c3f8cd30d..c71a4d33fe 100644 --- a/packages/job-worker/src/blueprints/context/OnTimelineGenerateContext.ts +++ b/packages/job-worker/src/blueprints/context/OnTimelineGenerateContext.ts @@ -4,7 +4,6 @@ import { ITimelineEventContext, } from '@sofie-automation/blueprints-integration' import { ReadonlyDeep } from 'type-fest' -import { DBStudio } from '@sofie-automation/corelib/dist/dataModel/Studio' import { OnGenerateTimelineObjExt } from '@sofie-automation/corelib/dist/dataModel/Timeline' import { DBPartInstance } from '@sofie-automation/corelib/dist/dataModel/PartInstance' import { clone } from '@sofie-automation/corelib/dist/lib' @@ -14,7 +13,7 @@ import { getCurrentTime } from '../../lib' import { PieceInstance, ResolvedPieceInstance } from '@sofie-automation/corelib/dist/dataModel/PieceInstance' import { ProcessedStudioConfig, ProcessedShowStyleConfig } from '../config' import _ = require('underscore') -import { ProcessedShowStyleCompound } from '../../jobs' +import { JobStudio, ProcessedShowStyleCompound } from '../../jobs' import { convertPartInstanceToBlueprints, createBlueprintQuickLoopInfo } from './lib' import { RundownContext } from './RundownContext' import { AbSessionHelper } from '../../playout/abPlayback/abSessionHelper' @@ -33,7 +32,7 @@ export class OnTimelineGenerateContext extends RundownContext implements ITimeli readonly #pieceInstanceCache = new Map>() constructor( - studio: ReadonlyDeep, + studio: ReadonlyDeep, studioBlueprintConfig: ProcessedStudioConfig, showStyleCompound: ReadonlyDeep, showStyleBlueprintConfig: ProcessedShowStyleConfig, diff --git a/packages/job-worker/src/blueprints/context/PartEventContext.ts b/packages/job-worker/src/blueprints/context/PartEventContext.ts index 880aa4b923..34722ec3ac 100644 --- a/packages/job-worker/src/blueprints/context/PartEventContext.ts +++ b/packages/job-worker/src/blueprints/context/PartEventContext.ts @@ -1,11 +1,10 @@ import { IBlueprintPartInstance, IPartEventContext } from '@sofie-automation/blueprints-integration' import { ReadonlyDeep } from 'type-fest' -import { DBStudio } from '@sofie-automation/corelib/dist/dataModel/Studio' import { DBPartInstance } from '@sofie-automation/corelib/dist/dataModel/PartInstance' import { DBRundown } from '@sofie-automation/corelib/dist/dataModel/Rundown' import { getCurrentTime } from '../../lib' import { ProcessedStudioConfig, ProcessedShowStyleConfig } from '../config' -import { ProcessedShowStyleCompound } from '../../jobs' +import { JobStudio, ProcessedShowStyleCompound } from '../../jobs' import { convertPartInstanceToBlueprints } from './lib' import { RundownContext } from './RundownContext' @@ -14,7 +13,7 @@ export class PartEventContext extends RundownContext implements IPartEventContex constructor( eventName: string, - studio: ReadonlyDeep, + studio: ReadonlyDeep, studioBlueprintConfig: ProcessedStudioConfig, showStyleCompound: ReadonlyDeep, showStyleBlueprintConfig: ProcessedShowStyleConfig, diff --git a/packages/job-worker/src/blueprints/context/RundownContext.ts b/packages/job-worker/src/blueprints/context/RundownContext.ts index 8faaefeba1..c84a27ac70 100644 --- a/packages/job-worker/src/blueprints/context/RundownContext.ts +++ b/packages/job-worker/src/blueprints/context/RundownContext.ts @@ -1,10 +1,9 @@ import { IRundownContext, IBlueprintSegmentRundown } from '@sofie-automation/blueprints-integration' import { ReadonlyDeep } from 'type-fest' -import { DBStudio } from '@sofie-automation/corelib/dist/dataModel/Studio' import { unprotectString } from '@sofie-automation/corelib/dist/protectedString' import { DBRundown } from '@sofie-automation/corelib/dist/dataModel/Rundown' import { ProcessedStudioConfig, ProcessedShowStyleConfig } from '../config' -import { ProcessedShowStyleCompound } from '../../jobs' +import { JobStudio, ProcessedShowStyleCompound } from '../../jobs' import { convertRundownToBlueprintSegmentRundown } from './lib' import { ContextInfo } from './CommonContext' import { ShowStyleContext } from './ShowStyleContext' @@ -19,7 +18,7 @@ export class RundownContext extends ShowStyleContext implements IRundownContext constructor( contextInfo: ContextInfo, - studio: ReadonlyDeep, + studio: ReadonlyDeep, studioBlueprintConfig: ProcessedStudioConfig, showStyleCompound: ReadonlyDeep, showStyleBlueprintConfig: ProcessedShowStyleConfig, diff --git a/packages/job-worker/src/blueprints/context/RundownEventContext.ts b/packages/job-worker/src/blueprints/context/RundownEventContext.ts index b28f533063..9852e0dd72 100644 --- a/packages/job-worker/src/blueprints/context/RundownEventContext.ts +++ b/packages/job-worker/src/blueprints/context/RundownEventContext.ts @@ -1,15 +1,14 @@ import { IEventContext } from '@sofie-automation/blueprints-integration' import { ReadonlyDeep } from 'type-fest' -import { DBStudio } from '@sofie-automation/corelib/dist/dataModel/Studio' import { DBRundown } from '@sofie-automation/corelib/dist/dataModel/Rundown' import { getCurrentTime } from '../../lib' import { ProcessedStudioConfig, ProcessedShowStyleConfig } from '../config' -import { ProcessedShowStyleCompound } from '../../jobs' +import { JobStudio, ProcessedShowStyleCompound } from '../../jobs' import { RundownContext } from './RundownContext' export class RundownEventContext extends RundownContext implements IEventContext { constructor( - studio: ReadonlyDeep, + studio: ReadonlyDeep, studioBlueprintConfig: ProcessedStudioConfig, showStyleCompound: ReadonlyDeep, showStyleBlueprintConfig: ProcessedShowStyleConfig, diff --git a/packages/job-worker/src/blueprints/context/ShowStyleContext.ts b/packages/job-worker/src/blueprints/context/ShowStyleContext.ts index 7a243320b5..1310c72f72 100644 --- a/packages/job-worker/src/blueprints/context/ShowStyleContext.ts +++ b/packages/job-worker/src/blueprints/context/ShowStyleContext.ts @@ -1,9 +1,8 @@ import { IOutputLayer, IShowStyleContext, ISourceLayer } from '@sofie-automation/blueprints-integration' import { ReadonlyDeep } from 'type-fest' -import { DBStudio } from '@sofie-automation/corelib/dist/dataModel/Studio' import { ProcessedStudioConfig, ProcessedShowStyleConfig } from '../config' import { getShowStyleConfigRef } from '../configRefs' -import { ProcessedShowStyleCompound } from '../../jobs' +import { JobStudio, ProcessedShowStyleCompound } from '../../jobs' import { ContextInfo } from './CommonContext' import { StudioContext } from './StudioContext' @@ -12,7 +11,7 @@ import { StudioContext } from './StudioContext' export class ShowStyleContext extends StudioContext implements IShowStyleContext { constructor( contextInfo: ContextInfo, - studio: ReadonlyDeep, + studio: ReadonlyDeep, studioBlueprintConfig: ProcessedStudioConfig, public readonly showStyleCompound: ReadonlyDeep, public readonly showStyleBlueprintConfig: ProcessedShowStyleConfig diff --git a/packages/job-worker/src/blueprints/context/StudioContext.ts b/packages/job-worker/src/blueprints/context/StudioContext.ts index f1627c483b..8d5915d338 100644 --- a/packages/job-worker/src/blueprints/context/StudioContext.ts +++ b/packages/job-worker/src/blueprints/context/StudioContext.ts @@ -1,21 +1,18 @@ import { IStudioContext, BlueprintMappings } from '@sofie-automation/blueprints-integration' import { ReadonlyDeep } from 'type-fest' -import { DBStudio, MappingsExt } from '@sofie-automation/corelib/dist/dataModel/Studio' import { unprotectString } from '@sofie-automation/corelib/dist/protectedString' import { StudioId } from '@sofie-automation/corelib/dist/dataModel/Ids' import { ProcessedStudioConfig } from '../config' import { getStudioConfigRef } from '../configRefs' -import { applyAndValidateOverrides } from '@sofie-automation/corelib/dist/settings/objectWithOverrides' import { CommonContext, ContextInfo } from './CommonContext' +import { JobStudio } from '../../jobs' /** Studio */ export class StudioContext extends CommonContext implements IStudioContext { - #processedMappings: ReadonlyDeep | undefined - constructor( contextInfo: ContextInfo, - public readonly studio: ReadonlyDeep, + public readonly studio: ReadonlyDeep, public readonly studioBlueprintConfig: ProcessedStudioConfig ) { super(contextInfo) @@ -36,10 +33,8 @@ export class StudioContext extends CommonContext implements IStudioContext { return getStudioConfigRef(this.studio._id, configKey) } getStudioMappings(): Readonly { - if (!this.#processedMappings) { - this.#processedMappings = applyAndValidateOverrides(this.studio.mappingsWithOverrides).obj - } + const mappings = this.studio.mappings // @ts-expect-error ProtectedString deviceId not compatible with string - return this.#processedMappings + return mappings } } diff --git a/packages/job-worker/src/blueprints/context/StudioUserContext.ts b/packages/job-worker/src/blueprints/context/StudioUserContext.ts index be2c471dc4..fff5232cd1 100644 --- a/packages/job-worker/src/blueprints/context/StudioUserContext.ts +++ b/packages/job-worker/src/blueprints/context/StudioUserContext.ts @@ -1,17 +1,17 @@ import { IStudioUserContext, NoteSeverity } from '@sofie-automation/blueprints-integration' import { ReadonlyDeep } from 'type-fest' -import { DBStudio } from '@sofie-automation/corelib/dist/dataModel/Studio' import { ProcessedStudioConfig } from '../config' import { INoteBase } from '@sofie-automation/corelib/dist/dataModel/Notes' import { ContextInfo } from './CommonContext' import { StudioContext } from './StudioContext' +import { JobStudio } from '../../jobs' export class StudioUserContext extends StudioContext implements IStudioUserContext { public readonly notes: INoteBase[] = [] constructor( contextInfo: ContextInfo, - studio: ReadonlyDeep, + studio: ReadonlyDeep, studioBlueprintConfig: ProcessedStudioConfig ) { super(contextInfo, studio, studioBlueprintConfig) diff --git a/packages/job-worker/src/blueprints/context/SyncIngestUpdateToPartInstanceContext.ts b/packages/job-worker/src/blueprints/context/SyncIngestUpdateToPartInstanceContext.ts index 1515ae71ec..efef608cc8 100644 --- a/packages/job-worker/src/blueprints/context/SyncIngestUpdateToPartInstanceContext.ts +++ b/packages/job-worker/src/blueprints/context/SyncIngestUpdateToPartInstanceContext.ts @@ -25,8 +25,7 @@ import { convertPartialBlueprintMutablePartToCore, } from './lib' import { DBRundown } from '@sofie-automation/corelib/dist/dataModel/Rundown' -import { DBStudio } from '@sofie-automation/corelib/dist/dataModel/Studio' -import { JobContext, ProcessedShowStyleCompound } from '../../jobs' +import { JobContext, JobStudio, ProcessedShowStyleCompound } from '../../jobs' import { PieceTimelineObjectsBlob, serializePieceTimelineObjectsBlob, @@ -44,7 +43,7 @@ export class SyncIngestUpdateToPartInstanceContext constructor( private readonly _context: JobContext, contextInfo: ContextInfo, - studio: ReadonlyDeep, + studio: ReadonlyDeep, showStyleCompound: ReadonlyDeep, rundown: ReadonlyDeep, partInstance: PlayoutPartInstanceModel, diff --git a/packages/job-worker/src/blueprints/context/adlibActions.ts b/packages/job-worker/src/blueprints/context/adlibActions.ts index 2ff5e49979..8ce4882fd8 100644 --- a/packages/job-worker/src/blueprints/context/adlibActions.ts +++ b/packages/job-worker/src/blueprints/context/adlibActions.ts @@ -31,7 +31,6 @@ import { executePeripheralDeviceAction, listPlayoutDevices } from '../../periphe import { ActionPartChange, PartAndPieceInstanceActionService } from './services/PartAndPieceInstanceActionService' import { BlueprintQuickLookInfo } from '@sofie-automation/blueprints-integration/dist/context/quickLoopInfo' import { setNextPartFromPart } from '../../playout/setNext' -import { applyAndValidateOverrides } from '@sofie-automation/corelib/dist/settings/objectWithOverrides' export class DatastoreActionExecutionContext extends ShowStyleUserContext @@ -201,7 +200,8 @@ export class ActionExecutionContext extends ShowStyleUserContext implements IAct } async listRouteSets(): Promise> { - return applyAndValidateOverrides(this._context.studio.routeSetsWithOverrides).obj + // Discard ReadonlyDeep wrapper + return this._context.studio.routeSets as Record } async switchRouteSet(routeSetId: string, state: boolean | 'toggle'): Promise { diff --git a/packages/job-worker/src/ingest/expectedPackages.ts b/packages/job-worker/src/ingest/expectedPackages.ts index c1d6099a9e..b94c6498b9 100644 --- a/packages/job-worker/src/ingest/expectedPackages.ts +++ b/packages/job-worker/src/ingest/expectedPackages.ts @@ -41,9 +41,8 @@ import { updateExpectedPlayoutItemsForPartModel, updateExpectedPlayoutItemsForRundownBaseline, } from './expectedPlayoutItems' -import { JobContext } from '../jobs' +import { JobContext, JobStudio } from '../jobs' import { ExpectedPackageForIngestModelBaseline, IngestModel } from './model/IngestModel' -import { DBStudio } from '@sofie-automation/corelib/dist/dataModel/Studio' import { IngestPartModel } from './model/IngestPartModel' import { clone } from '@sofie-automation/corelib/dist/lib' @@ -160,7 +159,7 @@ export async function updateExpectedPackagesForRundownBaseline( } function generateExpectedPackagesForPiece( - studio: ReadonlyDeep, + studio: ReadonlyDeep, rundownId: RundownId, segmentId: SegmentId, pieces: ReadonlyDeep[], @@ -186,7 +185,7 @@ function generateExpectedPackagesForPiece( return packages } function generateExpectedPackagesForBaselineAdlibPiece( - studio: ReadonlyDeep, + studio: ReadonlyDeep, rundownId: RundownId, pieces: ReadonlyDeep ) { @@ -207,7 +206,7 @@ function generateExpectedPackagesForBaselineAdlibPiece( return packages } function generateExpectedPackagesForAdlibAction( - studio: ReadonlyDeep, + studio: ReadonlyDeep, rundownId: RundownId, segmentId: SegmentId, actions: ReadonlyDeep @@ -231,7 +230,7 @@ function generateExpectedPackagesForAdlibAction( return packages } function generateExpectedPackagesForBaselineAdlibAction( - studio: ReadonlyDeep, + studio: ReadonlyDeep, rundownId: RundownId, actions: ReadonlyDeep ) { @@ -251,7 +250,7 @@ function generateExpectedPackagesForBaselineAdlibAction( } return packages } -function generateExpectedPackagesForBucketAdlib(studio: ReadonlyDeep, adlibs: BucketAdLib[]) { +function generateExpectedPackagesForBucketAdlib(studio: ReadonlyDeep, adlibs: BucketAdLib[]) { const packages: ExpectedPackageDBFromBucketAdLib[] = [] for (const adlib of adlibs) { if (adlib.expectedPackages) { @@ -270,7 +269,7 @@ function generateExpectedPackagesForBucketAdlib(studio: ReadonlyDeep, return packages } function generateExpectedPackagesForBucketAdlibAction( - studio: ReadonlyDeep, + studio: ReadonlyDeep, adlibActions: BucketAdLibAction[] ) { const packages: ExpectedPackageDBFromBucketAdLibAction[] = [] @@ -291,7 +290,7 @@ function generateExpectedPackagesForBucketAdlibAction( return packages } function generateExpectedPackageBases( - studio: ReadonlyDeep, + studio: ReadonlyDeep, ownerId: | PieceId | AdLibActionId diff --git a/packages/job-worker/src/jobs/index.ts b/packages/job-worker/src/jobs/index.ts index f8ab90e9f4..41103a4c23 100644 --- a/packages/job-worker/src/jobs/index.ts +++ b/packages/job-worker/src/jobs/index.ts @@ -18,9 +18,11 @@ import { PlaylistLock, RundownLock } from './lock' import { BaseModel } from '../modelBase' import { TimelineComplete } from '@sofie-automation/corelib/dist/dataModel/Timeline' import { ProcessedShowStyleBase, ProcessedShowStyleVariant, ProcessedShowStyleCompound } from './showStyle' +import { JobStudio } from './studio' export { ApmSpan } export { ProcessedShowStyleVariant, ProcessedShowStyleBase, ProcessedShowStyleCompound } +export { JobStudio } /** * Context for any job run in the job-worker @@ -104,7 +106,12 @@ export interface StudioCacheContext { /** * The Studio the job belongs to */ - readonly studio: ReadonlyDeep + readonly studio: ReadonlyDeep + + /** + * The Studio the job belongs to + */ + readonly rawStudio: ReadonlyDeep /** * Blueprint for the studio the job belongs to diff --git a/packages/job-worker/src/jobs/studio.ts b/packages/job-worker/src/jobs/studio.ts new file mode 100644 index 0000000000..00a2f87893 --- /dev/null +++ b/packages/job-worker/src/jobs/studio.ts @@ -0,0 +1,58 @@ +import type { + IBlueprintConfig, + StudioRouteSet, + StudioRouteSetExclusivityGroup, +} from '@sofie-automation/blueprints-integration' +import type { DBStudio, IStudioSettings, MappingsExt } from '@sofie-automation/corelib/dist/dataModel/Studio' +import { omit } from '@sofie-automation/corelib/dist/lib' +import { applyAndValidateOverrides } from '@sofie-automation/corelib/dist/settings/objectWithOverrides' + +/** + * A lightly processed version of DBStudio, with any ObjectWithOverrides pre-flattened + */ +export interface JobStudio + extends Omit< + DBStudio, + | 'mappingsWithOverrides' + | 'blueprintConfigWithOverrides' + | 'settingsWithOverrides' + | 'routeSetsWithOverrides' + | 'routeSetExclusivityGroupsWithOverrides' + | 'packageContainersWithOverrides' + > { + /** Mappings between the physical devices / outputs and logical ones */ + mappings: MappingsExt + + /** Config values are used by the Blueprints */ + blueprintConfig: IBlueprintConfig + + settings: IStudioSettings + + routeSets: Record + routeSetExclusivityGroups: Record + + // /** Contains settings for which Package Containers are present in the studio. + // * (These are used by the Package Manager and the Expected Packages) + // */ + // packageContainers: Record +} + +export function convertStudioToJobStudio(studio: DBStudio): JobStudio { + return { + ...omit( + studio, + 'mappingsWithOverrides', + 'blueprintConfigWithOverrides', + 'settingsWithOverrides', + 'routeSetsWithOverrides', + 'routeSetExclusivityGroupsWithOverrides', + 'packageContainersWithOverrides' + ), + mappings: applyAndValidateOverrides(studio.mappingsWithOverrides).obj, + blueprintConfig: applyAndValidateOverrides(studio.blueprintConfigWithOverrides).obj, + settings: applyAndValidateOverrides(studio.settingsWithOverrides).obj, + routeSets: applyAndValidateOverrides(studio.routeSetsWithOverrides).obj, + routeSetExclusivityGroups: applyAndValidateOverrides(studio.routeSetExclusivityGroupsWithOverrides).obj, + // packageContainers: applyAndValidateOverrides(studio.packageContainersWithOverrides).obj, + } +} diff --git a/packages/job-worker/src/playout/abPlayback/index.ts b/packages/job-worker/src/playout/abPlayback/index.ts index 9ab7534d06..f3b0c80c8e 100644 --- a/packages/job-worker/src/playout/abPlayback/index.ts +++ b/packages/job-worker/src/playout/abPlayback/index.ts @@ -17,7 +17,6 @@ import { AbSessionHelper } from './abSessionHelper' import { ShowStyleContext } from '../../blueprints/context' import { logger } from '../../logging' import { ABPlayerDefinition, NoteSeverity } from '@sofie-automation/blueprints-integration' -import { applyAndValidateOverrides } from '@sofie-automation/corelib/dist/settings/objectWithOverrides' import { abPoolFilterDisabled, findPlayersInRouteSets } from './routeSetDisabling' import type { INotification } from '../../notifications/NotificationsModel' import { generateTranslation } from '@sofie-automation/corelib/dist/lib' @@ -85,7 +84,7 @@ export function applyAbPlaybackForTimeline( const notifications: INotification[] = [] const abConfiguration = blueprint.blueprint.getAbResolverConfiguration(blueprintContext) - const routeSetMembers = findPlayersInRouteSets(applyAndValidateOverrides(context.studio.routeSetsWithOverrides).obj) + const routeSetMembers = findPlayersInRouteSets(context.studio.routeSets) for (const [poolName, players] of Object.entries(abConfiguration.pools)) { // Filter out offline devices diff --git a/packages/job-worker/src/playout/abPlayback/routeSetDisabling.ts b/packages/job-worker/src/playout/abPlayback/routeSetDisabling.ts index d6be0a4ab6..9f71f1d0ac 100644 --- a/packages/job-worker/src/playout/abPlayback/routeSetDisabling.ts +++ b/packages/job-worker/src/playout/abPlayback/routeSetDisabling.ts @@ -1,6 +1,7 @@ import type { ABPlayerDefinition } from '@sofie-automation/blueprints-integration' import type { StudioRouteSet } from '@sofie-automation/corelib/dist/dataModel/Studio' import { logger } from '../../logging' +import { ReadonlyDeep } from 'type-fest' /** * Map> @@ -8,9 +9,9 @@ import { logger } from '../../logging' */ type MembersOfRouteSets = Map> -export function findPlayersInRouteSets(routeSets: Record): MembersOfRouteSets { +export function findPlayersInRouteSets(routeSets: ReadonlyDeep>): MembersOfRouteSets { const routeSetEnabledPlayers: MembersOfRouteSets = new Map() - for (const [_key, routeSet] of Object.entries(routeSets)) { + for (const [_key, routeSet] of Object.entries>(routeSets)) { for (const abPlayer of routeSet.abPlayers) { let poolEntry = routeSetEnabledPlayers.get(abPlayer.poolName) if (!poolEntry) { diff --git a/packages/job-worker/src/playout/lookahead/index.ts b/packages/job-worker/src/playout/lookahead/index.ts index 893d6eb174..12ac4935ec 100644 --- a/packages/job-worker/src/playout/lookahead/index.ts +++ b/packages/job-worker/src/playout/lookahead/index.ts @@ -22,7 +22,6 @@ import { LOOKAHEAD_DEFAULT_SEARCH_DISTANCE } from '@sofie-automation/shared-lib/ import { prefixSingleObjectId } from '../lib' import { LookaheadTimelineObject } from './findObjects' import { hasPieceInstanceDefinitelyEnded } from '../timeline/lib' -import { applyAndValidateOverrides } from '@sofie-automation/corelib/dist/settings/objectWithOverrides' import { DBPart } from '@sofie-automation/corelib/dist/dataModel/Part' import { ReadonlyDeep } from 'type-fest' @@ -65,8 +64,8 @@ export async function getLookeaheadObjects( partInstancesInfo0: SelectedPartInstancesTimelineInfo ): Promise> { const span = context.startSpan('getLookeaheadObjects') - const allMappings = applyAndValidateOverrides(context.studio.mappingsWithOverrides) - const mappingsToConsider = Object.entries(allMappings.obj).filter( + const allMappings = context.studio.mappings + const mappingsToConsider = Object.entries(allMappings).filter( ([_id, map]) => map.lookahead !== LookaheadMode.NONE && map.lookahead !== undefined ) if (mappingsToConsider.length === 0) { diff --git a/packages/job-worker/src/playout/timeline/generate.ts b/packages/job-worker/src/playout/timeline/generate.ts index 8c71749fd6..535fac659e 100644 --- a/packages/job-worker/src/playout/timeline/generate.ts +++ b/packages/job-worker/src/playout/timeline/generate.ts @@ -1,5 +1,5 @@ import { BlueprintId } from '@sofie-automation/corelib/dist/dataModel/Ids' -import { JobContext } from '../../jobs' +import { JobContext, JobStudio } from '../../jobs' import { ReadonlyDeep } from 'type-fest' import { BlueprintResultBaseline, @@ -36,7 +36,6 @@ import { WatchedPackagesHelper } from '../../blueprints/context/watchedPackages' import { postProcessStudioBaselineObjects } from '../../blueprints/postProcess' import { updateBaselineExpectedPackagesOnStudio } from '../../ingest/expectedPackages' import { endTrace, sendTrace, startTrace } from '@sofie-automation/corelib/dist/influxdb' -import { StudioLight } from '@sofie-automation/corelib/dist/dataModel/Studio' import { deserializePieceTimelineObjectsBlob } from '@sofie-automation/corelib/dist/dataModel/Piece' import { convertResolvedPieceInstanceToBlueprints } from '../../blueprints/context/lib' import { buildTimelineObjsForRundown, RundownTimelineTimingContext } from './rundown' @@ -54,7 +53,7 @@ function isModelForStudio(model: StudioPlayoutModelBase): model is StudioPlayout } function generateTimelineVersions( - studio: ReadonlyDeep, + studio: ReadonlyDeep, blueprintId: BlueprintId | undefined, blueprintVersion: string ): TimelineCompleteGenerationVersions { diff --git a/packages/job-worker/src/playout/upgrade.ts b/packages/job-worker/src/playout/upgrade.ts index 66fba34cfa..f69da25d85 100644 --- a/packages/job-worker/src/playout/upgrade.ts +++ b/packages/job-worker/src/playout/upgrade.ts @@ -41,7 +41,7 @@ export async function handleBlueprintUpgradeForStudio(context: JobContext, _data name: 'applyConfig', identifier: `studio:${context.studioId},blueprint:${blueprint.blueprintId}`, }) - const rawBlueprintConfig = applyAndValidateOverrides(context.studio.blueprintConfigWithOverrides).obj + const rawBlueprintConfig = context.studio.blueprintConfig const result = blueprint.blueprint.applyConfig( blueprintContext, @@ -158,7 +158,7 @@ export async function handleBlueprintValidateConfigForStudio( name: 'validateConfig', identifier: `studio:${context.studioId},blueprint:${blueprint.blueprintId}`, }) - const rawBlueprintConfig = applyAndValidateOverrides(context.studio.blueprintConfigWithOverrides).obj + const rawBlueprintConfig = applyAndValidateOverrides(context.rawStudio.blueprintConfigWithOverrides).obj // This clone seems excessive, but without it a DataCloneError is generated when posting the result to the parent const messages = clone(blueprint.blueprint.validateConfig(blueprintContext, rawBlueprintConfig)) @@ -200,7 +200,7 @@ export async function handleBlueprintFixUpConfigForStudio( const blueprintContext = new FixUpBlueprintConfigContext( commonContext, JSONBlobParse(blueprint.blueprint.studioConfigSchema), - context.studio.blueprintConfigWithOverrides + context.rawStudio.blueprintConfigWithOverrides ) blueprint.blueprint.fixUpConfig(blueprintContext) diff --git a/packages/job-worker/src/rundownPlaylists.ts b/packages/job-worker/src/rundownPlaylists.ts index 9306cc458a..1d535c284e 100644 --- a/packages/job-worker/src/rundownPlaylists.ts +++ b/packages/job-worker/src/rundownPlaylists.ts @@ -27,12 +27,11 @@ import { IBlueprintRundown, NoteSeverity, } from '@sofie-automation/blueprints-integration' -import { JobContext } from './jobs' +import { JobContext, JobStudio } from './jobs' import { logger } from './logging' import { resetRundownPlaylist } from './playout/lib' import { runJobWithPlaylistLock, runWithPlayoutModel } from './playout/lock' import { updateTimeline } from './playout/timeline/generate' -import { DBStudio } from '@sofie-automation/corelib/dist/dataModel/Studio' import { WrappedStudioBlueprint } from './blueprints/cache' import { StudioUserContext } from './blueprints/context' import { getCurrentTime } from './lib' @@ -329,7 +328,7 @@ export function produceRundownPlaylistInfoFromRundown( function defaultPlaylistForRundown( rundown: ReadonlyDeep, - studio: ReadonlyDeep, + studio: ReadonlyDeep, existingPlaylist?: ReadonlyDeep ): Omit { return { diff --git a/packages/job-worker/src/workers/caches.ts b/packages/job-worker/src/workers/caches.ts index f252a9a0de..6fee8f1aed 100644 --- a/packages/job-worker/src/workers/caches.ts +++ b/packages/job-worker/src/workers/caches.ts @@ -15,8 +15,9 @@ import { protectString } from '@sofie-automation/corelib/dist/protectedString' import { clone, deepFreeze } from '@sofie-automation/corelib/dist/lib' import { logger } from '../logging' import deepmerge = require('deepmerge') -import { ProcessedShowStyleBase, ProcessedShowStyleVariant, StudioCacheContext } from '../jobs' +import { JobStudio, ProcessedShowStyleBase, ProcessedShowStyleVariant, StudioCacheContext } from '../jobs' import { StudioCacheContextImpl } from './context/StudioCacheContextImpl' +import { convertStudioToJobStudio } from '../jobs/studio' /** * A Wrapper to maintain a cache and provide a context using the cache when appropriate @@ -43,7 +44,7 @@ export class WorkerDataCacheWrapperImpl implements WorkerDataCacheWrapper { * The StudioId the cache is maintained for */ get studioId(): StudioId { - return this.#dataCache.studio._id + return this.#dataCache.rawStudio._id } constructor(collections: IDirectCollections, dataCache: WorkerDataCache) { @@ -99,7 +100,8 @@ export class WorkerDataCacheWrapperImpl implements WorkerDataCacheWrapper { * This is a reusable cache of these properties */ export interface WorkerDataCache { - studio: ReadonlyDeep + rawStudio: ReadonlyDeep + jobStudio: ReadonlyDeep studioBlueprint: ReadonlyDeep studioBlueprintConfig: ProcessedStudioConfig | undefined @@ -133,12 +135,16 @@ export async function loadWorkerDataCache( studioId: StudioId ): Promise { // Load some 'static' data from the db - const studio = deepFreeze(await collections.Studios.findOne(studioId)) - if (!studio) throw new Error('Missing studio') + const dbStudio = await collections.Studios.findOne(studioId) + if (!dbStudio) throw new Error('Missing studio') + const studio = deepFreeze(dbStudio) const studioBlueprint = await loadStudioBlueprintOrPlaceholder(collections, studio) + const jobStudio = deepFreeze(convertStudioToJobStudio(dbStudio)) + return { - studio, + rawStudio: studio, + jobStudio: jobStudio, studioBlueprint, studioBlueprintConfig: undefined, @@ -157,11 +163,12 @@ export async function invalidateWorkerDataCache( if (data.forceAll) { // Clear everything! - const newStudio = await collections.Studios.findOne(cache.studio._id) + const newStudio = await collections.Studios.findOne(cache.rawStudio._id) if (!newStudio) throw new Error(`Studio is missing during cache invalidation!`) - cache.studio = deepFreeze(newStudio) + cache.rawStudio = deepFreeze(newStudio) + cache.jobStudio = deepFreeze(convertStudioToJobStudio(newStudio)) - cache.studioBlueprint = await loadStudioBlueprintOrPlaceholder(collections, cache.studio) + cache.studioBlueprint = await loadStudioBlueprintOrPlaceholder(collections, cache.rawStudio) cache.studioBlueprintConfig = undefined cache.showStyleBases.clear() @@ -176,25 +183,26 @@ export async function invalidateWorkerDataCache( if (data.studio) { logger.debug('WorkerDataCache: Reloading studio') - const newStudio = await collections.Studios.findOne(cache.studio._id) + const newStudio = await collections.Studios.findOne(cache.rawStudio._id) if (!newStudio) throw new Error(`Studio is missing during cache invalidation!`) // If studio blueprintId changed, then force it to be reloaded - if (newStudio.blueprintId !== cache.studio.blueprintId) updateStudioBlueprint = true + if (newStudio.blueprintId !== cache.rawStudio.blueprintId) updateStudioBlueprint = true - cache.studio = deepFreeze(newStudio) + cache.rawStudio = deepFreeze(newStudio) + cache.jobStudio = deepFreeze(convertStudioToJobStudio(newStudio)) cache.studioBlueprintConfig = undefined } // Check if studio blueprint was in the changed list - if (!updateStudioBlueprint && cache.studio.blueprintId) { - updateStudioBlueprint = data.blueprints.includes(cache.studio.blueprintId) + if (!updateStudioBlueprint && cache.rawStudio.blueprintId) { + updateStudioBlueprint = data.blueprints.includes(cache.rawStudio.blueprintId) } // Reload studioBlueprint if (updateStudioBlueprint) { logger.debug('WorkerDataCache: Reloading studioBlueprint') - cache.studioBlueprint = await loadStudioBlueprintOrPlaceholder(collections, cache.studio) + cache.studioBlueprint = await loadStudioBlueprintOrPlaceholder(collections, cache.rawStudio) cache.studioBlueprintConfig = undefined } @@ -210,7 +218,7 @@ export async function invalidateWorkerDataCache( if (data.studio) { // Ensure showStyleBases & showStyleVariants are all still valid for the studio - const allowedBases = new Set(cache.studio.supportedShowStyleBase) + const allowedBases = new Set(cache.rawStudio.supportedShowStyleBase) for (const id of Array.from(cache.showStyleBases.keys())) { if (!allowedBases.has(id)) { diff --git a/packages/job-worker/src/workers/context/JobContextImpl.ts b/packages/job-worker/src/workers/context/JobContextImpl.ts index 7be35b55f2..8cd15572dc 100644 --- a/packages/job-worker/src/workers/context/JobContextImpl.ts +++ b/packages/job-worker/src/workers/context/JobContextImpl.ts @@ -1,5 +1,5 @@ import { IDirectCollections } from '../../db' -import { JobContext } from '../../jobs' +import { JobContext, JobStudio } from '../../jobs' import { WorkerDataCache } from '../caches' import { RundownId, RundownPlaylistId } from '@sofie-automation/corelib/dist/dataModel/Ids' import { getIngestQueueName, IngestJobFunc } from '@sofie-automation/corelib/dist/worker/ingest' @@ -41,8 +41,12 @@ export class JobContextImpl extends StudioCacheContextImpl implements JobContext this.studioRouteSetUpdater = new StudioRouteSetUpdater(directCollections, cacheData) } - get studio(): ReadonlyDeep { - return this.studioRouteSetUpdater.studioWithChanges ?? super.studio + get studio(): ReadonlyDeep { + return this.studioRouteSetUpdater.jobStudioWithChanges ?? super.studio + } + + get rawStudio(): ReadonlyDeep { + return this.studioRouteSetUpdater.rawStudioWithChanges ?? super.rawStudio } trackCache(cache: BaseModel): void { diff --git a/packages/job-worker/src/workers/context/StudioCacheContextImpl.ts b/packages/job-worker/src/workers/context/StudioCacheContextImpl.ts index dff38b6e88..183a89d01b 100644 --- a/packages/job-worker/src/workers/context/StudioCacheContextImpl.ts +++ b/packages/job-worker/src/workers/context/StudioCacheContextImpl.ts @@ -4,6 +4,7 @@ import { ProcessedShowStyleVariant, ProcessedShowStyleCompound, StudioCacheContext, + JobStudio, } from '../../jobs' import { ReadonlyDeep } from 'type-fest' import { WorkerDataCache } from '../caches' @@ -30,9 +31,14 @@ export class StudioCacheContextImpl implements StudioCacheContext { protected readonly cacheData: WorkerDataCache ) {} - get studio(): ReadonlyDeep { + get studio(): ReadonlyDeep { // This is frozen at the point of populating the cache - return this.cacheData.studio + return this.cacheData.jobStudio + } + + get rawStudio(): ReadonlyDeep { + // This is frozen at the point of populating the cache + return this.cacheData.rawStudio } get studioId(): StudioId { @@ -47,7 +53,9 @@ export class StudioCacheContextImpl implements StudioCacheContext { getStudioBlueprintConfig(): ProcessedStudioConfig { if (!this.cacheData.studioBlueprintConfig) { this.cacheData.studioBlueprintConfig = deepFreeze( - clone(preprocessStudioConfig(this.cacheData.studio, this.cacheData.studioBlueprint.blueprint) ?? null) + clone( + preprocessStudioConfig(this.cacheData.jobStudio, this.cacheData.studioBlueprint.blueprint) ?? null + ) ) } @@ -59,7 +67,7 @@ export class StudioCacheContextImpl implements StudioCacheContext { const loadedDocs: Array> = [] // Figure out what is already cached, and what needs loading - for (const id of this.cacheData.studio.supportedShowStyleBase) { + for (const id of this.cacheData.jobStudio.supportedShowStyleBase) { const doc = this.cacheData.showStyleBases.get(id) if (doc === undefined) { docsToLoad.push(id) @@ -95,7 +103,7 @@ export class StudioCacheContextImpl implements StudioCacheContext { async getShowStyleBase(id: ShowStyleBaseId): Promise> { // Check if allowed - if (!this.cacheData.studio.supportedShowStyleBase.includes(id)) { + if (!this.cacheData.jobStudio.supportedShowStyleBase.includes(id)) { throw new Error(`ShowStyleBase "${id}" is not allowed in studio`) } @@ -123,7 +131,7 @@ export class StudioCacheContextImpl implements StudioCacheContext { async getShowStyleVariants(id: ShowStyleBaseId): Promise>> { // Check if allowed - if (!this.cacheData.studio.supportedShowStyleBase.includes(id)) { + if (!this.cacheData.jobStudio.supportedShowStyleBase.includes(id)) { throw new Error(`ShowStyleBase "${id}" is not allowed in studio`) } @@ -172,7 +180,7 @@ export class StudioCacheContextImpl implements StudioCacheContext { const doc0 = await this.directCollections.ShowStyleVariants.findOne(id) // Check allowed - if (doc0 && !this.cacheData.studio.supportedShowStyleBase.includes(doc0.showStyleBaseId)) { + if (doc0 && !this.cacheData.jobStudio.supportedShowStyleBase.includes(doc0.showStyleBaseId)) { throw new Error(`ShowStyleVariant "${id}" is not allowed in studio`) } @@ -187,7 +195,7 @@ export class StudioCacheContextImpl implements StudioCacheContext { if (doc) { // Check allowed - if (!this.cacheData.studio.supportedShowStyleBase.includes(doc.showStyleBaseId)) { + if (!this.cacheData.jobStudio.supportedShowStyleBase.includes(doc.showStyleBaseId)) { throw new Error(`ShowStyleVariant "${id}" is not allowed in studio`) } diff --git a/packages/job-worker/src/workers/context/StudioRouteSetUpdater.ts b/packages/job-worker/src/workers/context/StudioRouteSetUpdater.ts index cea5c9e53b..9de2f486f4 100644 --- a/packages/job-worker/src/workers/context/StudioRouteSetUpdater.ts +++ b/packages/job-worker/src/workers/context/StudioRouteSetUpdater.ts @@ -10,26 +10,39 @@ import { logger } from '../../logging' import type { ReadonlyDeep } from 'type-fest' import type { WorkerDataCache } from '../caches' import type { IDirectCollections } from '../../db' +import { JobStudio } from '../../jobs' +import { applyAndValidateOverrides } from '@sofie-automation/corelib/dist/settings/objectWithOverrides' export class StudioRouteSetUpdater { readonly #directCollections: Readonly - readonly #cacheData: Pick + readonly #cacheData: Pick - constructor(directCollections: Readonly, cacheData: Pick) { + constructor( + directCollections: Readonly, + cacheData: Pick + ) { this.#directCollections = directCollections this.#cacheData = cacheData } // Future: this could store a Map, if the context exposed a simplified view of DBStudio - #studioWithRouteSetChanges: ReadonlyDeep | undefined = undefined - - get studioWithChanges(): ReadonlyDeep | undefined { - return this.#studioWithRouteSetChanges + #studioWithRouteSetChanges: + | { + rawStudio: ReadonlyDeep + jobStudio: ReadonlyDeep + } + | undefined = undefined + + get rawStudioWithChanges(): ReadonlyDeep | undefined { + return this.#studioWithRouteSetChanges?.rawStudio + } + get jobStudioWithChanges(): ReadonlyDeep | undefined { + return this.#studioWithRouteSetChanges?.jobStudio } setRouteSetActive(routeSetId: string, isActive: boolean | 'toggle'): boolean { - const currentStudio = this.#studioWithRouteSetChanges ?? this.#cacheData.studio - const currentRouteSets = getAllCurrentItemsFromOverrides(currentStudio.routeSetsWithOverrides, null) + const currentStudios = this.#studioWithRouteSetChanges ?? this.#cacheData + const currentRouteSets = getAllCurrentItemsFromOverrides(currentStudios.rawStudio.routeSetsWithOverrides, null) const routeSet = currentRouteSets.find((routeSet) => routeSet.id === routeSetId) if (!routeSet) throw new Error(`RouteSet "${routeSetId}" not found!`) @@ -41,10 +54,10 @@ export class StudioRouteSetUpdater { if (routeSet.computed.behavior === StudioRouteBehavior.ACTIVATE_ONLY && !isActive) throw new Error(`RouteSet "${routeSet.id}" is ACTIVATE_ONLY`) - const overrideHelper = new OverrideOpHelperImpl(null, currentStudio.routeSetsWithOverrides) + const overrideHelper = new OverrideOpHelperImpl(null, currentStudios.rawStudio.routeSetsWithOverrides) // Update the pending changes - logger.debug(`switchRouteSet "${this.#cacheData.studio._id}" "${routeSet.id}"=${isActive}`) + logger.debug(`switchRouteSet "${this.#cacheData.rawStudio._id}" "${routeSet.id}"=${isActive}`) overrideHelper.setItemValue(routeSetId, 'active', isActive) let mayAffectTimeline = couldRoutesetAffectTimelineGeneration(routeSet) @@ -54,7 +67,9 @@ export class StudioRouteSetUpdater { for (const otherRouteSet of Object.values>(currentRouteSets)) { if (otherRouteSet.id === routeSet.id) continue if (otherRouteSet.computed?.exclusivityGroup === routeSet.computed.exclusivityGroup) { - logger.debug(`switchRouteSet Other ID "${this.#cacheData.studio._id}" "${otherRouteSet.id}"=false`) + logger.debug( + `switchRouteSet Other ID "${this.#cacheData.rawStudio._id}" "${otherRouteSet.id}"=false` + ) overrideHelper.setItemValue(otherRouteSet.id, 'active', false) mayAffectTimeline = mayAffectTimeline || couldRoutesetAffectTimelineGeneration(otherRouteSet) @@ -65,13 +80,22 @@ export class StudioRouteSetUpdater { const updatedOverrideOps = overrideHelper.getPendingOps() // Update the cached studio - this.#studioWithRouteSetChanges = Object.freeze({ - ...currentStudio, + const updatedRawStudio: ReadonlyDeep = Object.freeze({ + ...currentStudios.rawStudio, routeSetsWithOverrides: Object.freeze({ - ...currentStudio.routeSetsWithOverrides, + ...currentStudios.rawStudio.routeSetsWithOverrides, overrides: deepFreeze(updatedOverrideOps), }), }) + const updatedJobStudio: ReadonlyDeep = Object.freeze({ + ...currentStudios.jobStudio, + routeSets: deepFreeze(applyAndValidateOverrides(updatedRawStudio.routeSetsWithOverrides).obj), + }) + + this.#studioWithRouteSetChanges = { + rawStudio: updatedRawStudio, + jobStudio: updatedJobStudio, + } return mayAffectTimeline } @@ -83,18 +107,19 @@ export class StudioRouteSetUpdater { // This is technically a little bit of a race condition, if someone uses the config pages but no more so than the rest of the system await this.#directCollections.Studios.update( { - _id: this.#cacheData.studio._id, + _id: this.#cacheData.rawStudio._id, }, { $set: { 'routeSetsWithOverrides.overrides': - this.#studioWithRouteSetChanges.routeSetsWithOverrides.overrides, + this.#studioWithRouteSetChanges.rawStudio.routeSetsWithOverrides.overrides, }, } ) // Pretend that the studio as reported by the database has changed, this will be fixed after this job by the ChangeStream firing - this.#cacheData.studio = this.#studioWithRouteSetChanges + this.#cacheData.rawStudio = this.#studioWithRouteSetChanges.rawStudio + this.#cacheData.jobStudio = this.#studioWithRouteSetChanges.jobStudio this.#studioWithRouteSetChanges = undefined } diff --git a/packages/job-worker/src/workers/events/child.ts b/packages/job-worker/src/workers/events/child.ts index 76d95c4c31..d6c0a59da1 100644 --- a/packages/job-worker/src/workers/events/child.ts +++ b/packages/job-worker/src/workers/events/child.ts @@ -98,7 +98,7 @@ export class EventsWorkerChild { const transaction = startTransaction('invalidateCaches', 'worker-studio') if (transaction) { - transaction.setLabel('studioId', unprotectString(this.#staticData.dataCache.studio._id)) + transaction.setLabel('studioId', unprotectString(this.#staticData.dataCache.jobStudio._id)) } try { @@ -118,7 +118,7 @@ export class EventsWorkerChild { const trace = startTrace('studioWorker' + jobName) const transaction = startTransaction(jobName, 'worker-studio') if (transaction) { - transaction.setLabel('studioId', unprotectString(this.#staticData.dataCache.studio._id)) + transaction.setLabel('studioId', unprotectString(this.#staticData.dataCache.jobStudio._id)) } const context = new JobContextImpl( diff --git a/packages/job-worker/src/workers/ingest/child.ts b/packages/job-worker/src/workers/ingest/child.ts index 86af4b8634..41ebc90eaf 100644 --- a/packages/job-worker/src/workers/ingest/child.ts +++ b/packages/job-worker/src/workers/ingest/child.ts @@ -81,7 +81,7 @@ export class IngestWorkerChild { const transaction = startTransaction('invalidateCaches', 'worker-studio') if (transaction) { - transaction.setLabel('studioId', unprotectString(this.#staticData.dataCache.studio._id)) + transaction.setLabel('studioId', unprotectString(this.#staticData.dataCache.jobStudio._id)) } try { @@ -99,7 +99,7 @@ export class IngestWorkerChild { const trace = startTrace('ingestWorker:' + jobName) const transaction = startTransaction(jobName, 'worker-ingest') if (transaction) { - transaction.setLabel('studioId', unprotectString(this.#staticData.dataCache.studio._id)) + transaction.setLabel('studioId', unprotectString(this.#staticData.dataCache.jobStudio._id)) // transaction.setLabel('rundownId', unprotectString(staticData.rundownId)) } diff --git a/packages/job-worker/src/workers/studio/child.ts b/packages/job-worker/src/workers/studio/child.ts index 40903527c6..cd468780ee 100644 --- a/packages/job-worker/src/workers/studio/child.ts +++ b/packages/job-worker/src/workers/studio/child.ts @@ -82,7 +82,7 @@ export class StudioWorkerChild { const transaction = startTransaction('invalidateCaches', 'worker-studio') if (transaction) { - transaction.setLabel('studioId', unprotectString(this.#staticData.dataCache.studio._id)) + transaction.setLabel('studioId', unprotectString(this.#staticData.dataCache.jobStudio._id)) } try { @@ -100,7 +100,7 @@ export class StudioWorkerChild { const trace = startTrace('studioWorker:' + jobName) const transaction = startTransaction(jobName, 'worker-studio') if (transaction) { - transaction.setLabel('studioId', unprotectString(this.#staticData.dataCache.studio._id)) + transaction.setLabel('studioId', unprotectString(this.#staticData.dataCache.jobStudio._id)) } const context = new JobContextImpl( diff --git a/packages/webui/src/__mocks__/defaultCollectionObjects.ts b/packages/webui/src/__mocks__/defaultCollectionObjects.ts index af06dffe31..609f774ae7 100644 --- a/packages/webui/src/__mocks__/defaultCollectionObjects.ts +++ b/packages/webui/src/__mocks__/defaultCollectionObjects.ts @@ -101,11 +101,11 @@ export function defaultStudio(_id: StudioId): DBStudio { mappingsWithOverrides: wrapDefaultObject({}), supportedShowStyleBase: [], blueprintConfigWithOverrides: wrapDefaultObject({}), - settings: { + settingsWithOverrides: wrapDefaultObject({ frameRate: 25, mediaPreviewsUrl: '', minimumTakeSpan: DEFAULT_MINIMUM_TAKE_SPAN, - }, + }), _rundownVersionHash: '', routeSetsWithOverrides: wrapDefaultObject({}), routeSetExclusivityGroupsWithOverrides: wrapDefaultObject({}), diff --git a/packages/webui/src/client/ui/Settings/Studio/Generic.tsx b/packages/webui/src/client/ui/Settings/Studio/Generic.tsx index 011672ff34..21f1659932 100644 --- a/packages/webui/src/client/ui/Settings/Studio/Generic.tsx +++ b/packages/webui/src/client/ui/Settings/Studio/Generic.tsx @@ -1,9 +1,8 @@ import * as React from 'react' -import { DBStudio } from '@sofie-automation/corelib/dist/dataModel/Studio' -import { Translated } from '../../../lib/ReactMeteorData/react-meteor-data' +import { DBStudio, IStudioSettings } from '@sofie-automation/corelib/dist/dataModel/Studio' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { faExclamationTriangle } from '@fortawesome/free-solid-svg-icons' -import { withTranslation } from 'react-i18next' +import { useTranslation } from 'react-i18next' import { EditAttribute } from '../../../lib/EditAttribute' import { StudioBaselineStatus } from './Baseline' import { ShowStyleBaseId } from '@sofie-automation/corelib/dist/dataModel/Ids' @@ -11,9 +10,27 @@ import { DBShowStyleBase } from '@sofie-automation/corelib/dist/dataModel/ShowSt import { Studios } from '../../../collections' import { useHistory } from 'react-router-dom' import { MeteorCall } from '../../../lib/meteorApi' -import { LabelActual } from '../../../lib/Components/LabelAndOverrides' +import { + LabelActual, + LabelAndOverrides, + LabelAndOverridesForCheckbox, + LabelAndOverridesForDropdown, + LabelAndOverridesForInt, +} from '../../../lib/Components/LabelAndOverrides' import { catchError } from '../../../lib/lib' import { ForceQuickLoopAutoNext } from '@sofie-automation/corelib/src/dataModel/RundownPlaylist' +import { + applyAndValidateOverrides, + ObjectWithOverrides, + SomeObjectOverrideOp, +} from '@sofie-automation/corelib/dist/settings/objectWithOverrides' +import { useOverrideOpHelper, WrappedOverridableItemNormal } from '../util/OverrideOpHelper' +import { IntInputControl } from '../../../lib/Components/IntInput' +import { literal } from '@sofie-automation/corelib/dist/lib' +import { useMemo } from 'react' +import { CheckboxControl } from '../../../lib/Components/Checkbox' +import { TextInputControl } from '../../../lib/Components/TextInput' +import { DropdownInputControl, DropdownInputOption } from '../../../lib/Components/DropdownInput' interface IStudioGenericPropertiesProps { studio: DBStudio @@ -23,285 +40,76 @@ interface IStudioGenericPropertiesProps { showStyleBase: DBShowStyleBase }> } -interface IStudioGenericPropertiesState {} -export const StudioGenericProperties = withTranslation()( - class StudioGenericProperties extends React.Component< - Translated, - IStudioGenericPropertiesState - > { - constructor(props: Translated) { - super(props) - } - renderShowStyleEditButtons() { - const buttons: JSX.Element[] = [] - if (this.props.studio) { - for (const showStyleBaseId of this.props.studio.supportedShowStyleBase) { - const showStyleBase = this.props.availableShowStyleBases.find( - (base) => base.showStyleBase._id === showStyleBaseId - ) - if (showStyleBase) { - buttons.push( - - ) - } - } - } - return buttons - } +export function StudioGenericProperties({ + studio, + availableShowStyleBases, +}: IStudioGenericPropertiesProps): JSX.Element { + const { t } = useTranslation() - render(): JSX.Element { - const { t } = this.props - return ( -
-

{t('Generic Properties')}

- -
- {t('Select Compatible Show Styles')} -
- - {this.renderShowStyleEditButtons()} - -
- {!this.props.studio.supportedShowStyleBase.length ? ( -
- {t('Show style not set')} -
- ) : null} -
- - - - - - - - - - - - - - - - - - - - - -
+ const showStyleEditButtons: JSX.Element[] = [] + for (const showStyleBaseId of studio.supportedShowStyleBase) { + const showStyleBase = availableShowStyleBases.find((base) => base.showStyleBase._id === showStyleBaseId) + if (showStyleBase) { + showStyleEditButtons.push( + ) } } -) + + return ( +
+

{t('Generic Properties')}

+ +
+ {t('Select Compatible Show Styles')} +
+ + {showStyleEditButtons} + +
+ {!studio.supportedShowStyleBase.length ? ( +
+ {t('Show style not set')} +
+ ) : null} +
+ + + + +
+ ) +} const NewShowStyleButton = React.memo(function NewShowStyleButton() { const history = useHistory() @@ -336,3 +144,289 @@ const RedirectToShowStyleButton = React.memo(function RedirectToShowStyleButton( ) }) + +function StudioSettings({ studio }: { studio: DBStudio }): JSX.Element { + const { t } = useTranslation() + + const saveOverrides = React.useCallback( + (newOps: SomeObjectOverrideOp[]) => { + console.log('save', newOps) + Studios.update(studio._id, { + $set: { + 'settingsWithOverrides.overrides': newOps.map((op) => ({ + ...op, + path: op.path.startsWith('0.') ? op.path.slice(2) : op.path, + })), + }, + }) + }, + [studio._id] + ) + + const [wrappedItem, wrappedConfigObject] = useMemo(() => { + const prefixedOps = studio.settingsWithOverrides.overrides.map((op) => ({ + ...op, + // TODO: can we avoid doing this hack? + path: `0.${op.path}`, + })) + + const computedValue = applyAndValidateOverrides(studio.settingsWithOverrides).obj + + const wrappedItem = literal>({ + type: 'normal', + id: '0', + computed: computedValue, + defaults: studio.settingsWithOverrides.defaults, + overrideOps: prefixedOps, + }) + + const wrappedConfigObject: ObjectWithOverrides = { + defaults: studio.settingsWithOverrides.defaults, + overrides: prefixedOps, + } + + return [wrappedItem, wrappedConfigObject] + }, [studio.settingsWithOverrides]) + + const overrideHelper = useOverrideOpHelper(saveOverrides, wrappedConfigObject) + + const autoNextOptions: DropdownInputOption[] = useMemo( + () => [ + { + name: t('Disabled'), + value: ForceQuickLoopAutoNext.DISABLED, + i: 0, + }, + { + name: t('Enabled, but skipping parts with undefined or 0 duration'), + value: ForceQuickLoopAutoNext.ENABLED_WHEN_VALID_DURATION, + i: 1, + }, + { + name: t('Enabled on all Parts, applying QuickLoop Fallback Part Duration if needed'), + value: ForceQuickLoopAutoNext.ENABLED_FORCING_MIN_DURATION, + i: 2, + }, + ], + [t] + ) + + return ( + <> + + {(value, handleUpdate) => ( + + )} + + + + {(value, handleUpdate) => ( + + )} + + + + {(value, handleUpdate) => } + + + + {(value, handleUpdate) => ( + + )} + + + + {(value, handleUpdate) => ( + + )} + + + + {(value, handleUpdate) => ( + + )} + + + + {(value, handleUpdate) => ( + + )} + + + + {(value, handleUpdate) => } + + + + {(value, handleUpdate) => ( + + )} + + + + {(value, handleUpdate) => } + + + + {(value, handleUpdate) => } + + + + {(value, handleUpdate) => } + + + + {(value, handleUpdate) => } + + + + {(value, handleUpdate, options) => ( + + )} + + + + {(value, handleUpdate) => ( + + )} + + + ) +} From 5caa5664ca5a5ad3523ea6eab041167cd131a2db Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Tue, 12 Nov 2024 15:18:45 +0000 Subject: [PATCH 08/14] chore: remove opPrefix prop --- .../lib/Components/LabelAndOverrides.tsx | 12 ++++------ .../lib/forms/SchemaFormWithOverrides.tsx | 2 -- .../ui/Settings/ShowStyle/OutputLayer.tsx | 12 +--------- .../ui/Settings/ShowStyle/SourceLayer.tsx | 23 +------------------ .../Studio/Devices/GenericSubDevices.tsx | 2 -- .../src/client/ui/Settings/Studio/Generic.tsx | 15 ------------ .../client/ui/Settings/Studio/Mappings.tsx | 7 ------ .../PackageManager/AccessorTableRow.tsx | 23 ------------------- .../PackageManager/PackageContainers.tsx | 2 -- .../Studio/Routings/ExclusivityGroups.tsx | 1 - .../Studio/Routings/RouteSetAbPlayers.tsx | 2 -- .../ui/Settings/Studio/Routings/RouteSets.tsx | 11 --------- 12 files changed, 7 insertions(+), 105 deletions(-) diff --git a/packages/webui/src/client/lib/Components/LabelAndOverrides.tsx b/packages/webui/src/client/lib/Components/LabelAndOverrides.tsx index be6c4d39f2..fb7c4b14a2 100644 --- a/packages/webui/src/client/lib/Components/LabelAndOverrides.tsx +++ b/packages/webui/src/client/lib/Components/LabelAndOverrides.tsx @@ -13,7 +13,6 @@ export interface LabelAndOverridesProps { hint?: string item: WrappedOverridableItemNormal itemKey: keyof ReadonlyDeep - opPrefix: string overrideHelper: OverrideOpHelperForItemContents showClearButton?: boolean @@ -32,7 +31,6 @@ export function LabelAndOverrides({ hint, item, itemKey, - opPrefix, overrideHelper, showClearButton, formatDefaultValue, @@ -40,16 +38,16 @@ export function LabelAndOverrides({ const { t } = useTranslation() const clearOverride = useCallback(() => { - overrideHelper().clearItemOverrides(opPrefix, String(itemKey)).commit() - }, [overrideHelper, opPrefix, itemKey]) + overrideHelper().clearItemOverrides(item.id, String(itemKey)).commit() + }, [overrideHelper, item.id, itemKey]) const setValue = useCallback( (newValue: any) => { - overrideHelper().setItemValue(opPrefix, String(itemKey), newValue).commit() + overrideHelper().setItemValue(item.id, String(itemKey), newValue).commit() }, - [overrideHelper, opPrefix, itemKey] + [overrideHelper, item.id, itemKey] ) - const isOverridden = hasOpWithPath(item.overrideOps, opPrefix, String(itemKey)) + const isOverridden = hasOpWithPath(item.overrideOps, item.id, String(itemKey)) let displayValue: JSX.Element | string | null = '""' if (item.defaults) { diff --git a/packages/webui/src/client/lib/forms/SchemaFormWithOverrides.tsx b/packages/webui/src/client/lib/forms/SchemaFormWithOverrides.tsx index bcd00e9afb..2e9779a076 100644 --- a/packages/webui/src/client/lib/forms/SchemaFormWithOverrides.tsx +++ b/packages/webui/src/client/lib/forms/SchemaFormWithOverrides.tsx @@ -45,7 +45,6 @@ interface FormComponentProps { item: WrappedOverridableItemNormal overrideHelper: OverrideOpHelperForItemContents itemKey: string - opPrefix: string /** Whether a clear button should be showed for fields not marked as "required" */ showClearButton: boolean @@ -68,7 +67,6 @@ function useChildPropsForFormComponent(props: Readonly
- + {(value, handleUpdate) => ( {(value, handleUpdate) => } @@ -309,7 +302,6 @@ function OutputLayerEntry({ item, isExpanded, toggleExpanded, overrideHelper }: label={t('Display Rank')} item={item} itemKey={'_rank'} - opPrefix={item.id} overrideHelper={overrideHelper} > {(value, handleUpdate) => ( @@ -325,7 +317,6 @@ function OutputLayerEntry({ item, isExpanded, toggleExpanded, overrideHelper }: label={t('Is collapsed by default')} item={item} itemKey={'isDefaultCollapsed'} - opPrefix={item.id} overrideHelper={overrideHelper} > {(value, handleUpdate) => } @@ -334,7 +325,6 @@ function OutputLayerEntry({ item, isExpanded, toggleExpanded, overrideHelper }: label={t('Is flattened')} item={item} itemKey={'isFlattened'} - opPrefix={item.id} overrideHelper={overrideHelper} > {(value, handleUpdate) => } diff --git a/packages/webui/src/client/ui/Settings/ShowStyle/SourceLayer.tsx b/packages/webui/src/client/ui/Settings/ShowStyle/SourceLayer.tsx index a7405ffe7a..9b31c7bac9 100644 --- a/packages/webui/src/client/ui/Settings/ShowStyle/SourceLayer.tsx +++ b/packages/webui/src/client/ui/Settings/ShowStyle/SourceLayer.tsx @@ -295,13 +295,7 @@ function SourceLayerEntry({ item, isExpanded, toggleExpanded, overrideHelper }:
- + {(value, handleUpdate) => ( {(value, handleUpdate) => ( @@ -341,7 +334,6 @@ function SourceLayerEntry({ item, isExpanded, toggleExpanded, overrideHelper }: label={t('Source Type')} item={item} itemKey={'type'} - opPrefix={item.id} overrideHelper={overrideHelper} options={getDropdownInputOptions(SourceLayerType)} > @@ -358,7 +350,6 @@ function SourceLayerEntry({ item, isExpanded, toggleExpanded, overrideHelper }: label={t('Is a Live Remote Input')} item={item} itemKey={'isRemoteInput'} - opPrefix={item.id} overrideHelper={overrideHelper} > {(value, handleUpdate) => } @@ -367,7 +358,6 @@ function SourceLayerEntry({ item, isExpanded, toggleExpanded, overrideHelper }: label={t('Is a Guest Input')} item={item} itemKey={'isGuestInput'} - opPrefix={item.id} overrideHelper={overrideHelper} > {(value, handleUpdate) => } @@ -376,7 +366,6 @@ function SourceLayerEntry({ item, isExpanded, toggleExpanded, overrideHelper }: label={t('Is hidden')} item={item} itemKey={'isHidden'} - opPrefix={item.id} overrideHelper={overrideHelper} > {(value, handleUpdate) => } @@ -385,7 +374,6 @@ function SourceLayerEntry({ item, isExpanded, toggleExpanded, overrideHelper }: label={t('Display Rank')} item={item} itemKey={'_rank'} - opPrefix={item.id} overrideHelper={overrideHelper} > {(value, handleUpdate) => ( @@ -401,7 +389,6 @@ function SourceLayerEntry({ item, isExpanded, toggleExpanded, overrideHelper }: label={t('Treat as Main content')} item={item} itemKey={'onPresenterScreen'} - opPrefix={item.id} overrideHelper={overrideHelper} hint="When set, Pieces on this Source Layer will be used to display summaries, thumbnails etc for the Part in GUIs. " > @@ -411,7 +398,6 @@ function SourceLayerEntry({ item, isExpanded, toggleExpanded, overrideHelper }: label={t('Display in a column in List View')} item={item} itemKey={'onListViewColumn'} - opPrefix={item.id} overrideHelper={overrideHelper} > {(value, handleUpdate) => } @@ -420,7 +406,6 @@ function SourceLayerEntry({ item, isExpanded, toggleExpanded, overrideHelper }: label={t('Display AdLibs in a column in List View')} item={item} itemKey={'onListViewAdLibColumn'} - opPrefix={item.id} overrideHelper={overrideHelper} > {(value, handleUpdate) => } @@ -429,7 +414,6 @@ function SourceLayerEntry({ item, isExpanded, toggleExpanded, overrideHelper }: label={t('Pieces on this layer can be cleared')} item={item} itemKey={'isClearable'} - opPrefix={item.id} overrideHelper={overrideHelper} > {(value, handleUpdate) => } @@ -438,7 +422,6 @@ function SourceLayerEntry({ item, isExpanded, toggleExpanded, overrideHelper }: label={t('Pieces on this layer are sticky')} item={item} itemKey={'isSticky'} - opPrefix={item.id} overrideHelper={overrideHelper} > {(value, handleUpdate) => } @@ -447,7 +430,6 @@ function SourceLayerEntry({ item, isExpanded, toggleExpanded, overrideHelper }: label={t('Only Pieces present in rundown are sticky')} item={item} itemKey={'stickyOriginalOnly'} - opPrefix={item.id} overrideHelper={overrideHelper} > {(value, handleUpdate) => } @@ -456,7 +438,6 @@ function SourceLayerEntry({ item, isExpanded, toggleExpanded, overrideHelper }: label={t('Allow disabling of Pieces')} item={item} itemKey={'allowDisable'} - opPrefix={item.id} overrideHelper={overrideHelper} > {(value, handleUpdate) => } @@ -465,7 +446,6 @@ function SourceLayerEntry({ item, isExpanded, toggleExpanded, overrideHelper }: label={t('AdLibs on this layer can be queued')} item={item} itemKey={'isQueueable'} - opPrefix={item.id} overrideHelper={overrideHelper} > {(value, handleUpdate) => } @@ -474,7 +454,6 @@ function SourceLayerEntry({ item, isExpanded, toggleExpanded, overrideHelper }: label={t('Exclusivity group')} item={item} itemKey={'exclusiveGroup'} - opPrefix={item.id} overrideHelper={overrideHelper} > {(value, handleUpdate) => ( diff --git a/packages/webui/src/client/ui/Settings/Studio/Devices/GenericSubDevices.tsx b/packages/webui/src/client/ui/Settings/Studio/Devices/GenericSubDevices.tsx index b3967bf83e..ae9b79ae30 100644 --- a/packages/webui/src/client/ui/Settings/Studio/Devices/GenericSubDevices.tsx +++ b/packages/webui/src/client/ui/Settings/Studio/Devices/GenericSubDevices.tsx @@ -285,7 +285,6 @@ function SubDeviceEditRow({ label={t('Peripheral Device ID')} item={item} overrideHelper={overrideHelper} - opPrefix={item.id} itemKey={'peripheralDeviceId'} options={peripheralDeviceOptions} > @@ -379,7 +378,6 @@ function SubDeviceEditForm({ peripheralDevice, item, overrideHelper }: Readonly< label={t('Device Type')} item={item} overrideHelper={overrideHelper} - opPrefix={item.id} itemKey={'options.type'} options={subdeviceTypeOptions} > diff --git a/packages/webui/src/client/ui/Settings/Studio/Generic.tsx b/packages/webui/src/client/ui/Settings/Studio/Generic.tsx index 21f1659932..06b13f1010 100644 --- a/packages/webui/src/client/ui/Settings/Studio/Generic.tsx +++ b/packages/webui/src/client/ui/Settings/Studio/Generic.tsx @@ -217,7 +217,6 @@ function StudioSettings({ studio }: { studio: DBStudio }): JSX.Element { label={t('Frame Rate')} item={wrappedItem} itemKey={'frameRate'} - opPrefix={wrappedItem.id} overrideHelper={overrideHelper} > {(value, handleUpdate) => ( @@ -234,7 +233,6 @@ function StudioSettings({ studio }: { studio: DBStudio }): JSX.Element { label={t('Minimum Take Span')} item={wrappedItem} itemKey={'minimumTakeSpan'} - opPrefix={wrappedItem.id} overrideHelper={overrideHelper} > {(value, handleUpdate) => ( @@ -251,7 +249,6 @@ function StudioSettings({ studio }: { studio: DBStudio }): JSX.Element { label={t('Enable "Play from Anywhere"')} item={wrappedItem} itemKey={'enablePlayFromAnywhere'} - opPrefix={wrappedItem.id} overrideHelper={overrideHelper} > {(value, handleUpdate) => } @@ -261,7 +258,6 @@ function StudioSettings({ studio }: { studio: DBStudio }): JSX.Element { label={t('Media Preview URL')} item={wrappedItem} itemKey={'mediaPreviewsUrl'} - opPrefix={wrappedItem.id} overrideHelper={overrideHelper} > {(value, handleUpdate) => ( @@ -278,7 +274,6 @@ function StudioSettings({ studio }: { studio: DBStudio }): JSX.Element { label={t('Slack Webhook URLs')} item={wrappedItem} itemKey={'slackEvaluationUrls'} - opPrefix={wrappedItem.id} overrideHelper={overrideHelper} > {(value, handleUpdate) => ( @@ -295,7 +290,6 @@ function StudioSettings({ studio }: { studio: DBStudio }): JSX.Element { label={t('Supported Media Formats')} item={wrappedItem} itemKey={'supportedMediaFormats'} - opPrefix={wrappedItem.id} overrideHelper={overrideHelper} > {(value, handleUpdate) => ( @@ -312,7 +306,6 @@ function StudioSettings({ studio }: { studio: DBStudio }): JSX.Element { label={t('Supported Audio Formats')} item={wrappedItem} itemKey={'supportedAudioStreams'} - opPrefix={wrappedItem.id} overrideHelper={overrideHelper} > {(value, handleUpdate) => ( @@ -329,7 +322,6 @@ function StudioSettings({ studio }: { studio: DBStudio }): JSX.Element { label={t('Force the Multi-gateway-mode')} item={wrappedItem} itemKey={'forceMultiGatewayMode'} - opPrefix={wrappedItem.id} overrideHelper={overrideHelper} > {(value, handleUpdate) => } @@ -339,7 +331,6 @@ function StudioSettings({ studio }: { studio: DBStudio }): JSX.Element { label={t('Multi-gateway-mode delay time')} item={wrappedItem} itemKey={'multiGatewayNowSafeLatency'} - opPrefix={wrappedItem.id} overrideHelper={overrideHelper} > {(value, handleUpdate) => ( @@ -356,7 +347,6 @@ function StudioSettings({ studio }: { studio: DBStudio }): JSX.Element { label={t('Allow Rundowns to be reset while on-air')} item={wrappedItem} itemKey={'allowRundownResetOnAir'} - opPrefix={wrappedItem.id} overrideHelper={overrideHelper} > {(value, handleUpdate) => } @@ -366,7 +356,6 @@ function StudioSettings({ studio }: { studio: DBStudio }): JSX.Element { label={t('Preserve position of segments when unsynced relative to other segments')} item={wrappedItem} itemKey={'preserveOrphanedSegmentPositionInRundown'} - opPrefix={wrappedItem.id} overrideHelper={overrideHelper} hint={t('This has only been tested for the iNews gateway')} > @@ -377,7 +366,6 @@ function StudioSettings({ studio }: { studio: DBStudio }): JSX.Element { label={t('Allow AdlibTesting (rehearsal) mode, for testing adlibs before taking the first Part')} item={wrappedItem} itemKey={'allowAdlibTestingSegment'} - opPrefix={wrappedItem.id} overrideHelper={overrideHelper} > {(value, handleUpdate) => } @@ -387,7 +375,6 @@ function StudioSettings({ studio }: { studio: DBStudio }): JSX.Element { label={t('Enable QuickLoop')} item={wrappedItem} itemKey={'enableQuickLoop'} - opPrefix={wrappedItem.id} overrideHelper={overrideHelper} > {(value, handleUpdate) => } @@ -397,7 +384,6 @@ function StudioSettings({ studio }: { studio: DBStudio }): JSX.Element { label={t('Source Type')} item={wrappedItem} itemKey={'forceQuickLoopAutoNext'} - opPrefix={wrappedItem.id} overrideHelper={overrideHelper} options={autoNextOptions} > @@ -415,7 +401,6 @@ function StudioSettings({ studio }: { studio: DBStudio }): JSX.Element { label={t('QuickLoop Fallback Part Duration')} item={wrappedItem} itemKey={'fallbackPartDuration'} - opPrefix={wrappedItem.id} overrideHelper={overrideHelper} > {(value, handleUpdate) => ( diff --git a/packages/webui/src/client/ui/Settings/Studio/Mappings.tsx b/packages/webui/src/client/ui/Settings/Studio/Mappings.tsx index f47a0bc4fc..5807d916e3 100644 --- a/packages/webui/src/client/ui/Settings/Studio/Mappings.tsx +++ b/packages/webui/src/client/ui/Settings/Studio/Mappings.tsx @@ -432,7 +432,6 @@ function StudioMappingsEntry({ hint={t('Human-readable name of the layer')} item={item} itemKey={'layerName'} - opPrefix={item.id} overrideHelper={overrideHelper} > {(value, handleUpdate) => ( @@ -450,7 +449,6 @@ function StudioMappingsEntry({ hint={t('The type of device to use for the output')} item={item} itemKey={'device'} - opPrefix={item.id} overrideHelper={overrideHelper} options={deviceTypeOptions} > @@ -469,7 +467,6 @@ function StudioMappingsEntry({ hint={t('ID of the device (corresponds to the device ID in the peripheralDevice settings)')} item={item} itemKey={'deviceId'} - opPrefix={item.id} overrideHelper={overrideHelper} > {(value, handleUpdate) => ( @@ -486,7 +483,6 @@ function StudioMappingsEntry({ label={t('Lookahead Mode')} item={item} itemKey={'lookahead'} - opPrefix={item.id} overrideHelper={overrideHelper} options={getDropdownInputOptions(LookaheadMode)} > @@ -504,7 +500,6 @@ function StudioMappingsEntry({ label={t('Lookahead Target Objects (Undefined = 1)')} item={item} itemKey={'lookaheadDepth'} - opPrefix={item.id} overrideHelper={overrideHelper} > {(value, handleUpdate) => ( @@ -523,7 +518,6 @@ function StudioMappingsEntry({ })} item={item} itemKey={'lookaheadMaxSearchDistance'} - opPrefix={item.id} overrideHelper={overrideHelper} > {(value, handleUpdate) => ( @@ -543,7 +537,6 @@ function StudioMappingsEntry({ hint={t('The type of mapping to use')} item={item} itemKey={'options.mappingType'} - opPrefix={item.id} overrideHelper={overrideHelper} options={mappingTypeOptions} > diff --git a/packages/webui/src/client/ui/Settings/Studio/PackageManager/AccessorTableRow.tsx b/packages/webui/src/client/ui/Settings/Studio/PackageManager/AccessorTableRow.tsx index eee92146cd..780a8afb12 100644 --- a/packages/webui/src/client/ui/Settings/Studio/PackageManager/AccessorTableRow.tsx +++ b/packages/webui/src/client/ui/Settings/Studio/PackageManager/AccessorTableRow.tsx @@ -133,7 +133,6 @@ export function AccessorTableRow({ item={packageContainer} //@ts-expect-error can't be 4 levels deep itemKey={`container.accessors.${accessorId}.label`} - opPrefix={packageContainer.id} overrideHelper={overrideHelper} > {(value, handleUpdate) => ( @@ -150,7 +149,6 @@ export function AccessorTableRow({ item={packageContainer} //@ts-expect-error can't be 4 levels deep itemKey={`container.accessors.${accessorId}.type`} - opPrefix={packageContainer.id} overrideHelper={overrideHelper} options={getDropdownInputOptions(Accessor.AccessType)} > @@ -173,7 +171,6 @@ export function AccessorTableRow({ item={packageContainer} //@ts-expect-error can't be 4 levels deep itemKey={`container.accessors.${accessorId}.folderPath`} - opPrefix={packageContainer.id} overrideHelper={overrideHelper} > {(value, handleUpdate) => ( @@ -191,7 +188,6 @@ export function AccessorTableRow({ item={packageContainer} //@ts-expect-error can't be 4 levels deep itemKey={`container.accessors.${accessorId}.resourceId`} - opPrefix={packageContainer.id} overrideHelper={overrideHelper} > {(value, handleUpdate) => ( @@ -212,7 +208,6 @@ export function AccessorTableRow({ item={packageContainer} //@ts-expect-error can't be 4 levels deep itemKey={`container.accessors.${accessorId}.baseUrl`} - opPrefix={packageContainer.id} overrideHelper={overrideHelper} > {(value, handleUpdate) => ( @@ -230,7 +225,6 @@ export function AccessorTableRow({ item={packageContainer} //@ts-expect-error can't be 4 levels deep itemKey={`container.accessors.${accessorId}.isImmutable`} - opPrefix={packageContainer.id} overrideHelper={overrideHelper} > {(value, handleUpdate) => ( @@ -249,7 +243,6 @@ export function AccessorTableRow({ item={packageContainer} //@ts-expect-error can't be 4 levels deep itemKey={`container.accessors.${accessorId}.useGETinsteadOfHEAD`} - opPrefix={packageContainer.id} overrideHelper={overrideHelper} > {(value, handleUpdate) => ( @@ -269,7 +262,6 @@ export function AccessorTableRow({ item={packageContainer} //@ts-expect-error can't be 4 levels deep itemKey={`container.accessors.${accessorId}.networkId`} - opPrefix={packageContainer.id} overrideHelper={overrideHelper} > {(value, handleUpdate) => ( @@ -290,7 +282,6 @@ export function AccessorTableRow({ item={packageContainer} //@ts-expect-error can't be 4 levels deep itemKey={`container.accessors.${accessorId}.baseUrl`} - opPrefix={packageContainer.id} overrideHelper={overrideHelper} > {(value, handleUpdate) => ( @@ -310,7 +301,6 @@ export function AccessorTableRow({ item={packageContainer} //@ts-expect-error can't be 4 levels deep itemKey={`container.accessors.${accessorId}.networkId`} - opPrefix={packageContainer.id} overrideHelper={overrideHelper} > {(value, handleUpdate) => ( @@ -331,7 +321,6 @@ export function AccessorTableRow({ item={packageContainer} //@ts-expect-error can't be 4 levels deep itemKey={`container.accessors.${accessorId}.folderPath`} - opPrefix={packageContainer.id} overrideHelper={overrideHelper} > {(value, handleUpdate) => ( @@ -349,7 +338,6 @@ export function AccessorTableRow({ item={packageContainer} //@ts-expect-error can't be 4 levels deep itemKey={`container.accessors.${accessorId}.userName`} - opPrefix={packageContainer.id} overrideHelper={overrideHelper} > {(value, handleUpdate) => ( @@ -367,7 +355,6 @@ export function AccessorTableRow({ item={packageContainer} //@ts-expect-error can't be 4 levels deep itemKey={`container.accessors.${accessorId}.password`} - opPrefix={packageContainer.id} overrideHelper={overrideHelper} > {(value, handleUpdate) => ( @@ -385,7 +372,6 @@ export function AccessorTableRow({ item={packageContainer} //@ts-expect-error can't be 4 levels deep itemKey={`container.accessors.${accessorId}.networkId`} - opPrefix={packageContainer.id} overrideHelper={overrideHelper} > {(value, handleUpdate) => ( @@ -406,7 +392,6 @@ export function AccessorTableRow({ item={packageContainer} //@ts-expect-error can't be 4 levels deep itemKey={`container.accessors.${accessorId}.quantelGatewayUrl`} - opPrefix={packageContainer.id} overrideHelper={overrideHelper} > {(value, handleUpdate) => ( @@ -424,7 +409,6 @@ export function AccessorTableRow({ item={packageContainer} //@ts-expect-error can't be 4 levels deep itemKey={`container.accessors.${accessorId}.ISAUrls`} - opPrefix={packageContainer.id} overrideHelper={overrideHelper} > {(value, handleUpdate) => ( @@ -442,7 +426,6 @@ export function AccessorTableRow({ item={packageContainer} //@ts-expect-error can't be 4 levels deep itemKey={`container.accessors.${accessorId}.zoneId`} - opPrefix={packageContainer.id} overrideHelper={overrideHelper} > {(value, handleUpdate) => ( @@ -462,7 +445,6 @@ export function AccessorTableRow({ item={packageContainer} //@ts-expect-error can't be 4 levels deep itemKey={`container.accessors.${accessorId}.serverId`} - opPrefix={packageContainer.id} overrideHelper={overrideHelper} > {(value, handleUpdate) => ( @@ -480,7 +462,6 @@ export function AccessorTableRow({ item={packageContainer} //@ts-expect-error can't be 4 levels deep itemKey={`container.accessors.${accessorId}.transformerURL`} - opPrefix={packageContainer.id} overrideHelper={overrideHelper} > {(value, handleUpdate) => ( @@ -498,7 +479,6 @@ export function AccessorTableRow({ item={packageContainer} //@ts-expect-error can't be 4 levels deep itemKey={`container.accessors.${accessorId}.fileflowURL`} - opPrefix={packageContainer.id} overrideHelper={overrideHelper} > {(value, handleUpdate) => ( @@ -516,7 +496,6 @@ export function AccessorTableRow({ item={packageContainer} //@ts-expect-error can't be 4 levels deep itemKey={`container.accessors.${accessorId}.fileflowProfile`} - opPrefix={packageContainer.id} overrideHelper={overrideHelper} > {(value, handleUpdate) => ( @@ -536,7 +515,6 @@ export function AccessorTableRow({ item={packageContainer} //@ts-expect-error can't be 4 levels deep itemKey={`container.accessors.${accessorId}.allowRead`} - opPrefix={packageContainer.id} overrideHelper={overrideHelper} > {(value, handleUpdate) => } @@ -546,7 +524,6 @@ export function AccessorTableRow({ item={packageContainer} //@ts-expect-error can't be 4 levels deep itemKey={`container.accessors.${accessorId}.allowWrite`} - opPrefix={packageContainer.id} overrideHelper={overrideHelper} > {(value, handleUpdate) => } diff --git a/packages/webui/src/client/ui/Settings/Studio/PackageManager/PackageContainers.tsx b/packages/webui/src/client/ui/Settings/Studio/PackageManager/PackageContainers.tsx index d92e3e31c8..ac915bfbab 100644 --- a/packages/webui/src/client/ui/Settings/Studio/PackageManager/PackageContainers.tsx +++ b/packages/webui/src/client/ui/Settings/Studio/PackageManager/PackageContainers.tsx @@ -285,7 +285,6 @@ function PackageContainerRow({ item={packageContainer} //@ts-expect-error can't be 2 levels deep itemKey={'container.label'} - opPrefix={packageContainer.id} overrideHelper={overrideHelper} > {(value, handleUpdate) => ( @@ -302,7 +301,6 @@ function PackageContainerRow({ hint={t('Select which playout devices are using this package container')} item={packageContainer} itemKey={'deviceIds'} - opPrefix={packageContainer.id} overrideHelper={overrideHelper} options={availablePlayoutDevicesOptions} > diff --git a/packages/webui/src/client/ui/Settings/Studio/Routings/ExclusivityGroups.tsx b/packages/webui/src/client/ui/Settings/Studio/Routings/ExclusivityGroups.tsx index 7b3a167749..ec442fd0cc 100644 --- a/packages/webui/src/client/ui/Settings/Studio/Routings/ExclusivityGroups.tsx +++ b/packages/webui/src/client/ui/Settings/Studio/Routings/ExclusivityGroups.tsx @@ -223,7 +223,6 @@ function ExclusivityGroupRow({ label={t('Exclusivity Group Name')} item={exclusivityGroup} itemKey={'name'} - opPrefix={exclusivityGroup.id} overrideHelper={exclusivityOverrideHelper} > {(value, handleUpdate) => ( diff --git a/packages/webui/src/client/ui/Settings/Studio/Routings/RouteSetAbPlayers.tsx b/packages/webui/src/client/ui/Settings/Studio/Routings/RouteSetAbPlayers.tsx index f6dc204d50..83ec047d7a 100644 --- a/packages/webui/src/client/ui/Settings/Studio/Routings/RouteSetAbPlayers.tsx +++ b/packages/webui/src/client/ui/Settings/Studio/Routings/RouteSetAbPlayers.tsx @@ -118,7 +118,6 @@ function AbPlayerRow({ label={t('Pool name')} item={player} itemKey={'poolName'} - opPrefix={player.id} overrideHelper={tableOverrideHelper} > {(value, handleUpdate) => ( @@ -134,7 +133,6 @@ function AbPlayerRow({ label={t('Pool PlayerId')} item={player} itemKey={'playerId'} - opPrefix={player.id} overrideHelper={tableOverrideHelper} > {(value, handleUpdate) => ( diff --git a/packages/webui/src/client/ui/Settings/Studio/Routings/RouteSets.tsx b/packages/webui/src/client/ui/Settings/Studio/Routings/RouteSets.tsx index cf555b4611..fe82a55647 100644 --- a/packages/webui/src/client/ui/Settings/Studio/Routings/RouteSets.tsx +++ b/packages/webui/src/client/ui/Settings/Studio/Routings/RouteSets.tsx @@ -306,7 +306,6 @@ function RouteSetRow({ hint={t('he default state of this Route Set')} item={routeSet} itemKey={'defaultActive'} - opPrefix={routeSet.id} overrideHelper={overrideHelper} options={getDropdownInputOptions(DEFAULT_ACTIVE_OPTIONS)} > @@ -323,7 +322,6 @@ function RouteSetRow({ label={t('Active')} item={routeSet} itemKey={'active'} - opPrefix={routeSet.id} overrideHelper={overrideHelper} > {(value, handleUpdate) => } @@ -332,7 +330,6 @@ function RouteSetRow({ label={t('Route Set Name')} item={routeSet} itemKey={'name'} - opPrefix={routeSet.id} overrideHelper={overrideHelper} > {(value, handleUpdate) => ( @@ -350,7 +347,6 @@ function RouteSetRow({ hint={t('If set, only one Route Set will be active per exclusivity group')} item={routeSet} itemKey={'exclusivityGroup'} - opPrefix={routeSet.id} overrideHelper={overrideHelper} options={exclusivityGroupOptions} > @@ -369,7 +365,6 @@ function RouteSetRow({ hint={t('The way this Route Set should behave towards the user')} item={routeSet} itemKey={'behavior'} - opPrefix={routeSet.id} overrideHelper={overrideHelper} options={getDropdownInputOptions(StudioRouteBehavior)} > @@ -601,7 +596,6 @@ function RenderRoutesRow({ label={t('Original Layer')} item={route} itemKey={'mappedLayer'} - opPrefix={route.id} overrideHelper={tableOverrideHelper} options={getDropdownInputOptions(Object.keys(studioMappings))} > @@ -619,7 +613,6 @@ function RenderRoutesRow({ label={t('New Layer')} item={route} itemKey={'outputMappedLayer'} - opPrefix={route.id} overrideHelper={tableOverrideHelper} > {(value, handleUpdate) => ( @@ -636,7 +629,6 @@ function RenderRoutesRow({ label={t('Route Type')} item={route} itemKey={'routeType'} - opPrefix={route.id} overrideHelper={tableOverrideHelper} options={getDropdownInputOptions(StudioRouteType)} > @@ -660,7 +652,6 @@ function RenderRoutesRow({ label={t('Device Type')} item={route} itemKey={'deviceType'} - opPrefix={route.id} overrideHelper={tableOverrideHelper} options={getDropdownInputOptions(TSR.DeviceType)} > @@ -689,7 +680,6 @@ function RenderRoutesRow({ label={t('Mapping Type')} item={route} itemKey={'remapping.options.mappingType'} - opPrefix={route.id} overrideHelper={tableOverrideHelper} options={mappingTypeOptions} > @@ -710,7 +700,6 @@ function RenderRoutesRow({ label={t('Device ID')} item={route} itemKey={'remapping.deviceId'} - opPrefix={route.id} overrideHelper={tableOverrideHelper} showClearButton={true} > From 731a0922bb9bf3fc2fa03299cfd74f151b9e8bdd Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Wed, 13 Nov 2024 09:51:44 +0000 Subject: [PATCH 09/14] wip: tests --- packages/job-worker/src/__mocks__/context.ts | 12 +++- .../src/__mocks__/defaultCollectionObjects.ts | 4 +- .../src/blueprints/__tests__/config.test.ts | 16 ++--- .../src/blueprints/__tests__/context.test.ts | 3 +- .../src/ingest/__tests__/ingest.test.ts | 7 +- .../__tests__/selectShowStyleVariant.test.ts | 18 +++--- .../mosDevice/__tests__/mosIngest.test.ts | 2 +- .../src/playout/__tests__/playout.test.ts | 7 +- .../lookahead/__tests__/lookahead.test.ts | 8 +-- .../playout/lookahead/__tests__/util.test.ts | 2 +- .../__tests__/StudioRouteSetUpdater.spec.ts | 64 +++++++++++++------ 11 files changed, 87 insertions(+), 56 deletions(-) diff --git a/packages/job-worker/src/__mocks__/context.ts b/packages/job-worker/src/__mocks__/context.ts index 57a861bb9f..d06fac5154 100644 --- a/packages/job-worker/src/__mocks__/context.ts +++ b/packages/job-worker/src/__mocks__/context.ts @@ -42,6 +42,7 @@ import { IDirectCollections } from '../db' import { ApmSpan, JobContext, + JobStudio, ProcessedShowStyleBase, ProcessedShowStyleCompound, ProcessedShowStyleVariant, @@ -56,6 +57,7 @@ import { JSONBlobStringify } from '@sofie-automation/shared-lib/dist/lib/JSONBlo import { removeRundownPlaylistFromDb } from '../ingest/__tests__/lib' import { processShowStyleBase, processShowStyleVariant } from '../jobs/showStyle' import { defaultStudio } from './defaultCollectionObjects' +import { convertStudioToJobStudio } from '../jobs/studio' export function setupDefaultJobEnvironment(studioId?: StudioId): MockJobContext { const { mockCollections, jobCollections } = getMockCollections() @@ -75,6 +77,7 @@ export class MockJobContext implements JobContext { #jobCollections: Readonly #mockCollections: Readonly #studio: ReadonlyDeep + #jobStudio: ReadonlyDeep #studioBlueprint: ReadonlyDeep #showStyleBlueprint: ReadonlyDeep @@ -87,6 +90,7 @@ export class MockJobContext implements JobContext { this.#jobCollections = jobCollections this.#mockCollections = mockCollections this.#studio = studio + this.#jobStudio = convertStudioToJobStudio(clone(studio)) this.#studioBlueprint = MockStudioBlueprint() this.#showStyleBlueprint = MockShowStyleBlueprint() @@ -103,7 +107,10 @@ export class MockJobContext implements JobContext { get studioId(): StudioId { return this.#studio._id } - get studio(): ReadonlyDeep { + get studio(): ReadonlyDeep { + return this.#jobStudio + } + get rawStudio(): ReadonlyDeep { return this.#studio } @@ -219,7 +226,7 @@ export class MockJobContext implements JobContext { } } getShowStyleBlueprintConfig(showStyle: ReadonlyDeep): ProcessedShowStyleConfig { - return preprocessShowStyleConfig(showStyle, this.#showStyleBlueprint, this.#studio.settings) + return preprocessShowStyleConfig(showStyle, this.#showStyleBlueprint, this.studio.settings) } hackPublishTimelineToFastTrack(_newTimeline: TimelineComplete): void { @@ -244,6 +251,7 @@ export class MockJobContext implements JobContext { setStudio(studio: ReadonlyDeep): void { this.#studio = clone(studio) + this.#jobStudio = convertStudioToJobStudio(clone(studio)) } setShowStyleBlueprint(blueprint: ReadonlyDeep): void { this.#showStyleBlueprint = blueprint diff --git a/packages/job-worker/src/__mocks__/defaultCollectionObjects.ts b/packages/job-worker/src/__mocks__/defaultCollectionObjects.ts index 5f13ba6285..752b8f9eb6 100644 --- a/packages/job-worker/src/__mocks__/defaultCollectionObjects.ts +++ b/packages/job-worker/src/__mocks__/defaultCollectionObjects.ts @@ -102,12 +102,12 @@ export function defaultStudio(_id: StudioId): DBStudio { mappingsWithOverrides: wrapDefaultObject({}), supportedShowStyleBase: [], blueprintConfigWithOverrides: wrapDefaultObject({}), - settings: { + settingsWithOverrides: wrapDefaultObject({ frameRate: 25, mediaPreviewsUrl: '', minimumTakeSpan: DEFAULT_MINIMUM_TAKE_SPAN, allowAdlibTestingSegment: true, - }, + }), routeSetsWithOverrides: wrapDefaultObject({}), routeSetExclusivityGroupsWithOverrides: wrapDefaultObject({}), packageContainersWithOverrides: wrapDefaultObject({}), diff --git a/packages/job-worker/src/blueprints/__tests__/config.test.ts b/packages/job-worker/src/blueprints/__tests__/config.test.ts index 2e77bd5dd8..5e142a6bec 100644 --- a/packages/job-worker/src/blueprints/__tests__/config.test.ts +++ b/packages/job-worker/src/blueprints/__tests__/config.test.ts @@ -10,12 +10,12 @@ describe('Test blueprint config', () => { test('compileStudioConfig', () => { const jobContext = setupDefaultJobEnvironment() jobContext.setStudio({ - ...jobContext.studio, - settings: { + ...jobContext.rawStudio, + settingsWithOverrides: wrapDefaultObject({ mediaPreviewsUrl: '', frameRate: 25, minimumTakeSpan: DEFAULT_MINIMUM_TAKE_SPAN, - }, + }), blueprintConfigWithOverrides: wrapDefaultObject({ sdfsdf: 'one', another: 5 }), }) jobContext.updateStudioBlueprint({ @@ -33,12 +33,12 @@ describe('Test blueprint config', () => { test('compileStudioConfig with function', () => { const jobContext = setupDefaultJobEnvironment() jobContext.setStudio({ - ...jobContext.studio, - settings: { + ...jobContext.rawStudio, + settingsWithOverrides: wrapDefaultObject({ mediaPreviewsUrl: '', frameRate: 25, minimumTakeSpan: DEFAULT_MINIMUM_TAKE_SPAN, - }, + }), blueprintConfigWithOverrides: wrapDefaultObject({ sdfsdf: 'one', another: 5 }), }) jobContext.updateStudioBlueprint({ @@ -136,7 +136,7 @@ describe('Test blueprint config', () => { const studioId = jobContext.studioId jobContext.setStudio({ - ...jobContext.studio, + ...jobContext.rawStudio, blueprintConfigWithOverrides: wrapDefaultObject({ two: 'abc', number: 99, @@ -183,7 +183,7 @@ describe('Test blueprint config', () => { }, }) jobContext.setStudio({ - ...jobContext.studio, + ...jobContext.rawStudio, supportedShowStyleBase: [showStyle._id], }) jobContext.updateShowStyleBlueprint({ diff --git a/packages/job-worker/src/blueprints/__tests__/context.test.ts b/packages/job-worker/src/blueprints/__tests__/context.test.ts index 307289c2df..5388b21303 100644 --- a/packages/job-worker/src/blueprints/__tests__/context.test.ts +++ b/packages/job-worker/src/blueprints/__tests__/context.test.ts @@ -1,6 +1,5 @@ import { getHash } from '@sofie-automation/corelib/dist/lib' import { unprotectString } from '@sofie-automation/corelib/dist/protectedString' -import { applyAndValidateOverrides } from '@sofie-automation/corelib/dist/settings/objectWithOverrides' import { MockJobContext, setupDefaultJobEnvironment } from '../../__mocks__/context' import { getShowStyleConfigRef, getStudioConfigRef } from '../configRefs' import { CommonContext } from '../context/CommonContext' @@ -81,7 +80,7 @@ describe('Test blueprint api context', () => { expect(context.studio).toBe(studio) expect(context.getStudioConfig()).toBe(studioConfig) - expect(context.getStudioMappings()).toEqual(applyAndValidateOverrides(studio.mappingsWithOverrides).obj) + expect(context.getStudioMappings()).toEqual(studio.mappings) }) test('getStudioConfigRef', () => { const context = new StudioContext( diff --git a/packages/job-worker/src/ingest/__tests__/ingest.test.ts b/packages/job-worker/src/ingest/__tests__/ingest.test.ts index e46a0f827b..a9d96c0c97 100644 --- a/packages/job-worker/src/ingest/__tests__/ingest.test.ts +++ b/packages/job-worker/src/ingest/__tests__/ingest.test.ts @@ -47,6 +47,7 @@ import { UserErrorMessage } from '@sofie-automation/corelib/dist/error' import { PlayoutPartInstanceModel } from '../../playout/model/PlayoutPartInstanceModel' import { NrcsIngestCacheType } from '@sofie-automation/corelib/dist/dataModel/NrcsIngestDataCache' import { wrapGenericIngestJob, wrapGenericIngestJobWithPrecheck } from '../jobWrappers' +import { wrapDefaultObject } from '@sofie-automation/corelib/dist/settings/objectWithOverrides' const handleRemovedRundownWrapped = wrapGenericIngestJob(handleRemovedRundown) const handleUpdatedRundownWrapped = wrapGenericIngestJob(handleUpdatedRundown) @@ -121,11 +122,11 @@ describe('Test ingest actions for rundowns and segments', () => { const showStyleCompound = await setupMockShowStyleCompound(context) context.setStudio({ - ...context.studio, - settings: { + ...context.rawStudio, + settingsWithOverrides: wrapDefaultObject({ ...context.studio.settings, minimumTakeSpan: 0, - }, + }), supportedShowStyleBase: [showStyleCompound._id], }) diff --git a/packages/job-worker/src/ingest/__tests__/selectShowStyleVariant.test.ts b/packages/job-worker/src/ingest/__tests__/selectShowStyleVariant.test.ts index 2c6e7d8ce4..cb63251f09 100644 --- a/packages/job-worker/src/ingest/__tests__/selectShowStyleVariant.test.ts +++ b/packages/job-worker/src/ingest/__tests__/selectShowStyleVariant.test.ts @@ -35,7 +35,7 @@ describe('selectShowStyleVariant', () => { const context = setupDefaultJobEnvironment() const showStyleCompound = await setupMockShowStyleCompound(context) context.setStudio({ - ...context.studio, + ...context.rawStudio, supportedShowStyleBase: [showStyleCompound._id], }) @@ -57,7 +57,7 @@ describe('selectShowStyleVariant', () => { const context = setupDefaultJobEnvironment() const showStyleCompound = await setupMockShowStyleCompound(context) context.setStudio({ - ...context.studio, + ...context.rawStudio, supportedShowStyleBase: [], }) @@ -76,7 +76,7 @@ describe('selectShowStyleVariant', () => { const context = setupDefaultJobEnvironment() const showStyleCompound = await setupMockShowStyleCompound(context) context.setStudio({ - ...context.studio, + ...context.rawStudio, supportedShowStyleBase: [showStyleCompound._id], }) @@ -118,7 +118,7 @@ describe('selectShowStyleVariant', () => { const showStyleCompoundVariant2 = await setupMockShowStyleVariant(context, showStyleCompound._id) const showStyleCompound2 = await setupMockShowStyleCompound(context) context.setStudio({ - ...context.studio, + ...context.rawStudio, supportedShowStyleBase: [showStyleCompound._id, showStyleCompound2._id], }) @@ -153,7 +153,7 @@ describe('selectShowStyleVariant', () => { test('no show style bases', async () => { const context = setupDefaultJobEnvironment() context.setStudio({ - ...context.studio, + ...context.rawStudio, supportedShowStyleBase: [protectString('fakeId')], }) @@ -176,7 +176,7 @@ describe('selectShowStyleVariant', () => { const context = setupDefaultJobEnvironment() const showStyleCompound = await setupMockShowStyleCompound(context) context.setStudio({ - ...context.studio, + ...context.rawStudio, supportedShowStyleBase: [showStyleCompound._id], }) @@ -201,7 +201,7 @@ describe('selectShowStyleVariant', () => { const context = setupDefaultJobEnvironment() const showStyleCompound = await setupMockShowStyleCompound(context) context.setStudio({ - ...context.studio, + ...context.rawStudio, supportedShowStyleBase: [showStyleCompound._id], }) @@ -226,7 +226,7 @@ describe('selectShowStyleVariant', () => { const context = setupDefaultJobEnvironment() const showStyleCompound = await setupMockShowStyleCompound(context) context.setStudio({ - ...context.studio, + ...context.rawStudio, supportedShowStyleBase: [showStyleCompound._id], }) @@ -251,7 +251,7 @@ describe('selectShowStyleVariant', () => { const context = setupDefaultJobEnvironment() const showStyleCompound = await setupMockShowStyleCompound(context) context.setStudio({ - ...context.studio, + ...context.rawStudio, supportedShowStyleBase: [showStyleCompound._id], }) diff --git a/packages/job-worker/src/ingest/mosDevice/__tests__/mosIngest.test.ts b/packages/job-worker/src/ingest/mosDevice/__tests__/mosIngest.test.ts index c96a18890d..d6e675ba1f 100644 --- a/packages/job-worker/src/ingest/mosDevice/__tests__/mosIngest.test.ts +++ b/packages/job-worker/src/ingest/mosDevice/__tests__/mosIngest.test.ts @@ -87,7 +87,7 @@ describe('Test recieved mos ingest payloads', () => { const showStyleCompound = await setupMockShowStyleCompound(context) context.setStudio({ - ...context.studio, + ...context.rawStudio, supportedShowStyleBase: [showStyleCompound._id], }) diff --git a/packages/job-worker/src/playout/__tests__/playout.test.ts b/packages/job-worker/src/playout/__tests__/playout.test.ts index 01c63b2d17..93ab3ef753 100644 --- a/packages/job-worker/src/playout/__tests__/playout.test.ts +++ b/packages/job-worker/src/playout/__tests__/playout.test.ts @@ -47,6 +47,7 @@ import { PlayoutChangedType } from '@sofie-automation/shared-lib/dist/peripheral import { ProcessedShowStyleCompound } from '../../jobs' import { handleOnPlayoutPlaybackChanged } from '../timings' import { sleep } from '@sofie-automation/shared-lib/dist/lib/lib' +import { wrapDefaultObject } from '@sofie-automation/corelib/dist/settings/objectWithOverrides' // const mockGetCurrentTime = jest.spyOn(lib, 'getCurrentTime') const mockExecutePeripheralDeviceFunction = jest @@ -98,11 +99,11 @@ describe('Playout API', () => { context = setupDefaultJobEnvironment() context.setStudio({ - ...context.studio, - settings: { + ...context.rawStudio, + settingsWithOverrides: wrapDefaultObject({ ...context.studio.settings, minimumTakeSpan: 0, - }, + }), }) // Ignore event jobs diff --git a/packages/job-worker/src/playout/lookahead/__tests__/lookahead.test.ts b/packages/job-worker/src/playout/lookahead/__tests__/lookahead.test.ts index 0256447f51..1a925310bb 100644 --- a/packages/job-worker/src/playout/lookahead/__tests__/lookahead.test.ts +++ b/packages/job-worker/src/playout/lookahead/__tests__/lookahead.test.ts @@ -51,7 +51,7 @@ describe('Lookahead', () => { } } context.setStudio({ - ...context.studio, + ...context.rawStudio, mappingsWithOverrides: wrapDefaultObject(mappings), }) @@ -222,7 +222,7 @@ describe('Lookahead', () => { // Set really low { - const studio = clone(context.studio) + const studio = clone(context.rawStudio) studio.mappingsWithOverrides.defaults['WHEN_CLEAR'].lookaheadMaxSearchDistance = 0 studio.mappingsWithOverrides.defaults['PRELOAD'].lookaheadMaxSearchDistance = 0 context.setStudio(studio) @@ -236,7 +236,7 @@ describe('Lookahead', () => { // really high getOrderedPartsAfterPlayheadMock.mockClear() { - const studio = clone(context.studio) + const studio = clone(context.rawStudio) studio.mappingsWithOverrides.defaults['WHEN_CLEAR'].lookaheadMaxSearchDistance = -1 studio.mappingsWithOverrides.defaults['PRELOAD'].lookaheadMaxSearchDistance = 2000 context.setStudio(studio) @@ -250,7 +250,7 @@ describe('Lookahead', () => { // unset getOrderedPartsAfterPlayheadMock.mockClear() { - const studio = clone(context.studio) + const studio = clone(context.rawStudio) studio.mappingsWithOverrides.defaults['WHEN_CLEAR'].lookaheadMaxSearchDistance = undefined studio.mappingsWithOverrides.defaults['PRELOAD'].lookaheadMaxSearchDistance = -1 context.setStudio(studio) diff --git a/packages/job-worker/src/playout/lookahead/__tests__/util.test.ts b/packages/job-worker/src/playout/lookahead/__tests__/util.test.ts index 4c3cc76bd8..5807e8b6c1 100644 --- a/packages/job-worker/src/playout/lookahead/__tests__/util.test.ts +++ b/packages/job-worker/src/playout/lookahead/__tests__/util.test.ts @@ -37,7 +37,7 @@ describe('getOrderedPartsAfterPlayhead', () => { } } context.setStudio({ - ...context.studio, + ...context.rawStudio, mappingsWithOverrides: wrapDefaultObject(mappings), }) diff --git a/packages/job-worker/src/workers/context/__tests__/StudioRouteSetUpdater.spec.ts b/packages/job-worker/src/workers/context/__tests__/StudioRouteSetUpdater.spec.ts index 77692f4072..e6ef0fe40d 100644 --- a/packages/job-worker/src/workers/context/__tests__/StudioRouteSetUpdater.spec.ts +++ b/packages/job-worker/src/workers/context/__tests__/StudioRouteSetUpdater.spec.ts @@ -6,11 +6,15 @@ import { wrapDefaultObject } from '@sofie-automation/corelib/dist/settings/objec function setupTest(routeSets: Record) { const context = setupDefaultJobEnvironment() - const mockCache: Pick = { - studio: { - ...context.studio, + const mockCache: Pick = { + rawStudio: { + ...context.rawStudio, routeSetsWithOverrides: wrapDefaultObject(routeSets), }, + jobStudio: { + ...context.studio, + routeSets: routeSets, + }, } const mockCollection = context.mockCollections.Studios const routeSetHelper = new StudioRouteSetUpdater(context.directCollections, mockCache) @@ -197,11 +201,13 @@ describe('StudioRouteSetUpdater', () => { routeSetHelper.setRouteSetActive('one', true) - expect(routeSetHelper.studioWithChanges).toBeTruthy() + expect(routeSetHelper.rawStudioWithChanges).toBeTruthy() + expect(routeSetHelper.jobStudioWithChanges).toBeTruthy() routeSetHelper.discardRouteSetChanges() - expect(routeSetHelper.studioWithChanges).toBeFalsy() + expect(routeSetHelper.rawStudioWithChanges).toBeFalsy() + expect(routeSetHelper.jobStudioWithChanges).toBeFalsy() expect(mockCollection.operations).toHaveLength(0) await routeSetHelper.saveRouteSetChanges() @@ -211,54 +217,70 @@ describe('StudioRouteSetUpdater', () => { it('save should update mockCache', async () => { const { mockCache, mockCollection, routeSetHelper } = setupTest(SINGLE_ROUTESET) - const studioBefore = mockCache.studio - expect(routeSetHelper.studioWithChanges).toBeFalsy() + const rawStudioBefore = mockCache.rawStudio + const jobStudioBefore = mockCache.jobStudio + expect(routeSetHelper.rawStudioWithChanges).toBeFalsy() + expect(routeSetHelper.jobStudioWithChanges).toBeFalsy() routeSetHelper.setRouteSetActive('one', true) - expect(routeSetHelper.studioWithChanges).toBeTruthy() + expect(routeSetHelper.rawStudioWithChanges).toBeTruthy() + expect(routeSetHelper.jobStudioWithChanges).toBeTruthy() expect(mockCollection.operations).toHaveLength(0) await routeSetHelper.saveRouteSetChanges() expect(mockCollection.operations).toHaveLength(1) // Object should have changed - expect(mockCache.studio).not.toBe(studioBefore) + expect(mockCache.rawStudio).not.toBe(rawStudioBefore) + expect(mockCache.jobStudio).not.toBe(jobStudioBefore) // Object should not be equal - expect(mockCache.studio).not.toEqual(studioBefore) - expect(routeSetHelper.studioWithChanges).toBeFalsy() + expect(mockCache.rawStudio).not.toEqual(rawStudioBefore) + expect(mockCache.jobStudio).not.toEqual(jobStudioBefore) + expect(routeSetHelper.rawStudioWithChanges).toBeFalsy() + expect(routeSetHelper.jobStudioWithChanges).toBeFalsy() }) it('no changes should not update mockCache', async () => { const { mockCache, mockCollection, routeSetHelper } = setupTest(SINGLE_ROUTESET) - const studioBefore = mockCache.studio - expect(routeSetHelper.studioWithChanges).toBeFalsy() + const rawStudioBefore = mockCache.rawStudio + const jobStudioBefore = mockCache.jobStudio + expect(routeSetHelper.rawStudioWithChanges).toBeFalsy() + expect(routeSetHelper.jobStudioWithChanges).toBeFalsy() expect(mockCollection.operations).toHaveLength(0) await routeSetHelper.saveRouteSetChanges() expect(mockCollection.operations).toHaveLength(0) - expect(mockCache.studio).toBe(studioBefore) - expect(routeSetHelper.studioWithChanges).toBeFalsy() + expect(mockCache.rawStudio).toBe(rawStudioBefore) + expect(mockCache.jobStudio).toBe(jobStudioBefore) + expect(routeSetHelper.rawStudioWithChanges).toBeFalsy() + expect(routeSetHelper.jobStudioWithChanges).toBeFalsy() }) it('discard changes should not update mockCache', async () => { const { mockCache, mockCollection, routeSetHelper } = setupTest(SINGLE_ROUTESET) - const studioBefore = mockCache.studio - expect(routeSetHelper.studioWithChanges).toBeFalsy() + const rawStudioBefore = mockCache.rawStudio + const jobStudioBefore = mockCache.jobStudio + expect(routeSetHelper.rawStudioWithChanges).toBeFalsy() + expect(routeSetHelper.jobStudioWithChanges).toBeFalsy() routeSetHelper.setRouteSetActive('one', true) - expect(routeSetHelper.studioWithChanges).toBeTruthy() + expect(routeSetHelper.rawStudioWithChanges).toBeTruthy() + expect(routeSetHelper.jobStudioWithChanges).toBeTruthy() routeSetHelper.discardRouteSetChanges() - expect(routeSetHelper.studioWithChanges).toBeFalsy() + expect(routeSetHelper.rawStudioWithChanges).toBeFalsy() + expect(routeSetHelper.jobStudioWithChanges).toBeFalsy() expect(mockCollection.operations).toHaveLength(0) await routeSetHelper.saveRouteSetChanges() expect(mockCollection.operations).toHaveLength(0) - expect(mockCache.studio).toBe(studioBefore) - expect(routeSetHelper.studioWithChanges).toBeFalsy() + expect(mockCache.rawStudio).toBe(rawStudioBefore) + expect(mockCache.jobStudio).toBe(jobStudioBefore) + expect(routeSetHelper.rawStudioWithChanges).toBeFalsy() + expect(routeSetHelper.jobStudioWithChanges).toBeFalsy() }) it('ACTIVATE_ONLY routeset can be activated', async () => { From 712f7ce1486b56dd05a35c1baf3090713d04b5c1 Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Wed, 13 Nov 2024 14:56:01 +0000 Subject: [PATCH 10/14] wip: move studio settings types to blueprint accessible, and generate in applyConfig --- .../blueprints-integration/src/api/studio.ts | 5 +- packages/blueprints-integration/src/index.ts | 1 + .../corelib/src/dataModel/RundownPlaylist.ts | 12 +--- packages/corelib/src/dataModel/Studio.ts | 63 +----------------- packages/job-worker/src/playout/upgrade.ts | 9 +++ .../src/core/model/StudioSettings.ts | 66 +++++++++++++++++++ 6 files changed, 85 insertions(+), 71 deletions(-) create mode 100644 packages/shared-lib/src/core/model/StudioSettings.ts diff --git a/packages/blueprints-integration/src/api/studio.ts b/packages/blueprints-integration/src/api/studio.ts index f4a3b6525b..198e7c554e 100644 --- a/packages/blueprints-integration/src/api/studio.ts +++ b/packages/blueprints-integration/src/api/studio.ts @@ -27,7 +27,8 @@ import type { StudioRouteSet, StudioRouteSetExclusivityGroup, } from '@sofie-automation/shared-lib/dist/core/model/StudioRouteSet' -import { StudioPackageContainer } from '@sofie-automation/shared-lib/dist/core/model/PackageContainer' +import type { StudioPackageContainer } from '@sofie-automation/shared-lib/dist/core/model/PackageContainer' +import type { IStudioSettings } from '@sofie-automation/shared-lib/dist/core/model/StudioSettings' export interface StudioBlueprintManifest extends BlueprintManifestBase { @@ -142,6 +143,8 @@ export interface BlueprintResultApplyStudioConfig { routeSetExclusivityGroups?: Record /** Package Containers */ packageContainers?: Record + + studioSettings?: IStudioSettings } export interface IStudioConfigPreset { diff --git a/packages/blueprints-integration/src/index.ts b/packages/blueprints-integration/src/index.ts index d5196e59f7..30089e21c9 100644 --- a/packages/blueprints-integration/src/index.ts +++ b/packages/blueprints-integration/src/index.ts @@ -28,3 +28,4 @@ export { JSONSchema } from '@sofie-automation/shared-lib/dist/lib/JSONSchemaType export * from '@sofie-automation/shared-lib/dist/lib/JSONBlob' export * from '@sofie-automation/shared-lib/dist/lib/JSONSchemaUtil' export * from '@sofie-automation/shared-lib/dist/core/model/StudioRouteSet' +export * from '@sofie-automation/shared-lib/dist/core/model/StudioSettings' diff --git a/packages/corelib/src/dataModel/RundownPlaylist.ts b/packages/corelib/src/dataModel/RundownPlaylist.ts index 241e0c3895..840184a0bb 100644 --- a/packages/corelib/src/dataModel/RundownPlaylist.ts +++ b/packages/corelib/src/dataModel/RundownPlaylist.ts @@ -11,6 +11,9 @@ import { RundownId, } from './Ids' import { RundownPlaylistNote } from './Notes' +import { ForceQuickLoopAutoNext } from '@sofie-automation/shared-lib/dist/core/model/StudioSettings' + +export { ForceQuickLoopAutoNext } /** Details of an ab-session requested by the blueprints in onTimelineGenerate */ export interface ABSessionInfo { @@ -80,15 +83,6 @@ export type QuickLoopMarker = | QuickLoopRundownMarker | QuickLoopPlaylistMarker -export enum ForceQuickLoopAutoNext { - /** Parts will auto-next only when explicitly set by the NRCS/blueprints */ - DISABLED = 'disabled', - /** Parts will auto-next when the expected duration is set and within range */ - ENABLED_WHEN_VALID_DURATION = 'enabled_when_valid_duration', - /** All parts will auto-next. If expected duration is undefined or low, the default display duration will be used */ - ENABLED_FORCING_MIN_DURATION = 'enabled_forcing_min_duration', -} - export interface QuickLoopProps { /** The Start marker */ start?: QuickLoopMarker diff --git a/packages/corelib/src/dataModel/Studio.ts b/packages/corelib/src/dataModel/Studio.ts index 44abb1a068..5a440f9d18 100644 --- a/packages/corelib/src/dataModel/Studio.ts +++ b/packages/corelib/src/dataModel/Studio.ts @@ -3,7 +3,6 @@ import { ObjectWithOverrides } from '../settings/objectWithOverrides' import { StudioId, OrganizationId, BlueprintId, ShowStyleBaseId, MappingsHash, PeripheralDeviceId } from './Ids' import { BlueprintHash, LastBlueprintConfig } from './Blueprint' import { MappingsExt, MappingExt } from '@sofie-automation/shared-lib/dist/core/model/Timeline' -import { ForceQuickLoopAutoNext } from './RundownPlaylist' import { ResultingMappingRoute, RouteMapping, @@ -15,8 +14,9 @@ import { StudioAbPlayerDisabling, } from '@sofie-automation/shared-lib/dist/core/model/StudioRouteSet' import { StudioPackageContainer } from '@sofie-automation/shared-lib/dist/core/model/PackageContainer' +import { IStudioSettings } from '@sofie-automation/shared-lib/dist/core/model/StudioSettings' -export { MappingsExt, MappingExt, MappingsHash } +export { MappingsExt, MappingExt, MappingsHash, IStudioSettings } // RouteSet functions has been moved to shared-lib: // So we need to re-export them here: @@ -32,64 +32,6 @@ export { StudioPackageContainer, } -export interface IStudioSettings { - /** The framerate (frames per second) used to convert internal timing information (in milliseconds) - * into timecodes and timecode-like strings and interpret timecode user input - * Default: 25 - */ - frameRate: number - - /** URL to endpoint where media preview are exposed */ - mediaPreviewsUrl: string // (former media_previews_url in config) - /** URLs for slack webhook to send evaluations */ - slackEvaluationUrls?: string // (former slack_evaluation in config) - - /** Media Resolutions supported by the studio for media playback */ - supportedMediaFormats?: string // (former mediaResolutions in config) - /** Audio Stream Formats supported by the studio for media playback */ - supportedAudioStreams?: string // (former audioStreams in config) - - /** Should the play from anywhere feature be enabled in this studio */ - enablePlayFromAnywhere?: boolean - - /** - * If set, forces the multi-playout-gateway mode (aka set "now"-time right away) - * for single playout-gateways setups - */ - forceMultiGatewayMode?: boolean - - /** How much extra delay to add to the Now-time (used for the "multi-playout-gateway" feature) . - * A higher value adds delays in playout, but reduces the risk of missed frames. */ - multiGatewayNowSafeLatency?: number - - /** Allow resets while a rundown is on-air */ - allowRundownResetOnAir?: boolean - - /** Preserve unsynced segments psoition in the rundown, relative to the other segments */ - preserveOrphanedSegmentPositionInRundown?: boolean - - /** - * The minimum amount of time, in milliseconds, that must pass after a take before another take may be performed. - * Default: 1000 - */ - minimumTakeSpan: number - - /** Whether to allow adlib testing mode, before a Part is playing in a Playlist */ - allowAdlibTestingSegment?: boolean - - /** Should QuickLoop context menu options be available to the users. It does not affect Playlist loop enabled by the NRCS. */ - enableQuickLoop?: boolean - - /** If and how to force auto-nexting in a looping Playlist */ - forceQuickLoopAutoNext?: ForceQuickLoopAutoNext - - /** - * The duration to apply on too short Parts Within QuickLoop when ForceQuickLoopAutoNext.ENABLED_FORCING_MIN_DURATION is selected - * Default: 3000 - */ - fallbackPartDuration?: number -} - export type StudioLight = Omit /** A set of available layer groups in a given installation */ @@ -123,7 +65,6 @@ export interface DBStudio { blueprintConfigWithOverrides: ObjectWithOverrides settingsWithOverrides: ObjectWithOverrides - // settings: IStudioSettings _rundownVersionHash: string diff --git a/packages/job-worker/src/playout/upgrade.ts b/packages/job-worker/src/playout/upgrade.ts index f69da25d85..4e2cf3dece 100644 --- a/packages/job-worker/src/playout/upgrade.ts +++ b/packages/job-worker/src/playout/upgrade.ts @@ -1,6 +1,7 @@ import { BlueprintMapping, BlueprintMappings, + IStudioSettings, JSONBlobParse, StudioRouteBehavior, TSR, @@ -26,6 +27,7 @@ import { compileCoreConfigValues } from '../blueprints/config' import { CommonContext } from '../blueprints/context' import { JobContext } from '../jobs' import { FixUpBlueprintConfigContext } from '@sofie-automation/corelib/dist/fixUpBlueprintConfig/context' +import { DEFAULT_MINIMUM_TAKE_SPAN } from '@sofie-automation/shared-lib/dist/core/constants' /** * Run the Blueprint applyConfig for the studio @@ -109,8 +111,15 @@ export async function handleBlueprintUpgradeForStudio(context: JobContext, _data ]) ) + const studioSettings: IStudioSettings = result.studioSettings ?? { + frameRate: 25, + mediaPreviewsUrl: '', + minimumTakeSpan: DEFAULT_MINIMUM_TAKE_SPAN, + } + await context.directCollections.Studios.update(context.studioId, { $set: { + 'settingsWithOverrides.defaults': studioSettings, 'mappingsWithOverrides.defaults': translateMappings(result.mappings), 'peripheralDeviceSettings.playoutDevices.defaults': playoutDevices, 'peripheralDeviceSettings.ingestDevices.defaults': ingestDevices, diff --git a/packages/shared-lib/src/core/model/StudioSettings.ts b/packages/shared-lib/src/core/model/StudioSettings.ts new file mode 100644 index 0000000000..45f88be0a4 --- /dev/null +++ b/packages/shared-lib/src/core/model/StudioSettings.ts @@ -0,0 +1,66 @@ +export enum ForceQuickLoopAutoNext { + /** Parts will auto-next only when explicitly set by the NRCS/blueprints */ + DISABLED = 'disabled', + /** Parts will auto-next when the expected duration is set and within range */ + ENABLED_WHEN_VALID_DURATION = 'enabled_when_valid_duration', + /** All parts will auto-next. If expected duration is undefined or low, the default display duration will be used */ + ENABLED_FORCING_MIN_DURATION = 'enabled_forcing_min_duration', +} + +export interface IStudioSettings { + /** The framerate (frames per second) used to convert internal timing information (in milliseconds) + * into timecodes and timecode-like strings and interpret timecode user input + * Default: 25 + */ + frameRate: number + + /** URL to endpoint where media preview are exposed */ + mediaPreviewsUrl: string // (former media_previews_url in config) + /** URLs for slack webhook to send evaluations */ + slackEvaluationUrls?: string // (former slack_evaluation in config) + + /** Media Resolutions supported by the studio for media playback */ + supportedMediaFormats?: string // (former mediaResolutions in config) + /** Audio Stream Formats supported by the studio for media playback */ + supportedAudioStreams?: string // (former audioStreams in config) + + /** Should the play from anywhere feature be enabled in this studio */ + enablePlayFromAnywhere?: boolean + + /** + * If set, forces the multi-playout-gateway mode (aka set "now"-time right away) + * for single playout-gateways setups + */ + forceMultiGatewayMode?: boolean + + /** How much extra delay to add to the Now-time (used for the "multi-playout-gateway" feature). + * A higher value adds delays in playout, but reduces the risk of missed frames. */ + multiGatewayNowSafeLatency?: number + + /** Allow resets while a rundown is on-air */ + allowRundownResetOnAir?: boolean + + /** Preserve unsynced segments position in the rundown, relative to the other segments */ + preserveOrphanedSegmentPositionInRundown?: boolean + + /** + * The minimum amount of time, in milliseconds, that must pass after a take before another take may be performed. + * Default: 1000 + */ + minimumTakeSpan: number + + /** Whether to allow adlib testing mode, before a Part is playing in a Playlist */ + allowAdlibTestingSegment?: boolean + + /** Should QuickLoop context menu options be available to the users. It does not affect Playlist loop enabled by the NRCS. */ + enableQuickLoop?: boolean + + /** If and how to force auto-nexting in a looping Playlist */ + forceQuickLoopAutoNext?: ForceQuickLoopAutoNext + + /** + * The duration to apply on too short Parts Within QuickLoop when ForceQuickLoopAutoNext.ENABLED_FORCING_MIN_DURATION is selected + * Default: 3000 + */ + fallbackPartDuration?: number +} From 73c8edec2521d28eec46b040c59aaead17081692 Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Wed, 13 Nov 2024 15:39:17 +0000 Subject: [PATCH 11/14] wip: coresystem settings --- meteor/__mocks__/helpers/database.ts | 18 ++ .../serviceMessagesApi.test.ts | 2 + meteor/server/collections/index.ts | 4 +- meteor/server/coreSystem/index.ts | 24 +- meteor/server/cronjobs.ts | 21 +- meteor/server/logo.ts | 2 +- meteor/server/migration/X_X_X.ts | 78 +++++- meteor/server/migration/upgrades/system.ts | 24 +- meteor/server/publications/system.ts | 4 +- .../blueprints-integration/src/api/system.ts | 4 +- packages/blueprints-integration/src/index.ts | 1 + .../meteor-lib/src/collections/CoreSystem.ts | 24 +- .../src/core/model/CoreSystemSettings.ts | 23 ++ ...{sofie-logo.svg => sofie-logo-default.svg} | 0 .../webui/src/__mocks__/helpers/database.ts | 18 ++ .../lib/Components/MultiLineTextInput.tsx | 18 +- .../src/client/ui/AfterBroadcastForm.tsx | 9 +- .../client/ui/Settings/SystemManagement.tsx | 258 +++++++++++------- packages/webui/src/client/ui/SupportPopUp.tsx | 7 +- 19 files changed, 392 insertions(+), 147 deletions(-) create mode 100644 packages/shared-lib/src/core/model/CoreSystemSettings.ts rename packages/webui/public/images/{sofie-logo.svg => sofie-logo-default.svg} (100%) diff --git a/meteor/__mocks__/helpers/database.ts b/meteor/__mocks__/helpers/database.ts index a1e72469d4..7c4bd82c41 100644 --- a/meteor/__mocks__/helpers/database.ts +++ b/meteor/__mocks__/helpers/database.ts @@ -171,6 +171,24 @@ export async function setupMockCore(doc?: Partial): Promise(Collection if (!access.update) return logNotAllowed('CoreSystem', access.reason) return allowOnlyFields(doc, fields, [ - 'support', 'systemInfo', 'name', 'logLevel', 'apm', - 'cron', 'logo', - 'evaluations', 'blueprintId', + 'settingsWithOverrides', ]) }, }) diff --git a/meteor/server/coreSystem/index.ts b/meteor/server/coreSystem/index.ts index 407682989d..85a2586745 100644 --- a/meteor/server/coreSystem/index.ts +++ b/meteor/server/coreSystem/index.ts @@ -10,13 +10,14 @@ import { getEnvLogLevel, logger, LogLevel, setLogLevel } from '../logging' const PackageInfo = require('../../package.json') import { startAgent } from '../api/profiler/apm' import { profiler } from '../api/profiler' -import { TMP_TSR_VERSION } from '@sofie-automation/blueprints-integration' +import { ICoreSystemSettings, TMP_TSR_VERSION } from '@sofie-automation/blueprints-integration' import { getAbsolutePath } from '../lib' import * as fs from 'fs/promises' import path from 'path' import { checkDatabaseVersions } from './checkDatabaseVersions' import PLazy from 'p-lazy' import { getCoreSystemAsync } from './collection' +import { wrapDefaultObject } from '@sofie-automation/corelib/dist/settings/objectWithOverrides' export { PackageInfo } @@ -59,11 +60,24 @@ async function initializeCoreSystem() { enabled: false, transactionSampleRate: -1, }, - cron: { - casparCGRestart: { - enabled: true, + settingsWithOverrides: wrapDefaultObject({ + cron: { + casparCGRestart: { + enabled: true, + }, + storeRundownSnapshots: { + enabled: false, + }, }, - }, + support: { + message: '', + }, + evaluationsMessage: { + enabled: false, + heading: '', + message: '', + }, + }), lastBlueprintConfig: undefined, }) diff --git a/meteor/server/cronjobs.ts b/meteor/server/cronjobs.ts index 88bb16c851..7b9d0d1ffb 100644 --- a/meteor/server/cronjobs.ts +++ b/meteor/server/cronjobs.ts @@ -18,13 +18,14 @@ import { deferAsync, normalizeArrayToMap } from '@sofie-automation/corelib/dist/ import { getCoreSystemAsync } from './coreSystem/collection' import { cleanupOldDataInner } from './api/cleanup' import { CollectionCleanupResult } from '@sofie-automation/meteor-lib/dist/api/system' -import { ICoreSystem } from '@sofie-automation/meteor-lib/dist/collections/CoreSystem' +import { ICoreSystemSettings } from '@sofie-automation/shared-lib/dist/core/model/CoreSystemSettings' import { executePeripheralDeviceFunctionWithCustomTimeout } from './api/peripheralDevice/executeFunction' import { interpollateTranslation, isTranslatableMessage, translateMessage, } from '@sofie-automation/corelib/dist/TranslatableMessage' +import { applyAndValidateOverrides } from '@sofie-automation/corelib/dist/settings/objectWithOverrides' const lowPrioFcn = (fcn: () => any) => { // Do it at a random time in the future: @@ -49,15 +50,17 @@ export async function nightlyCronjobInner(): Promise { logger.info('Nightly cronjob: starting...') const system = await getCoreSystemAsync() + const systemSettings = system && applyAndValidateOverrides(system.settingsWithOverrides).obj + await Promise.allSettled([ cleanupOldDataCronjob().catch((error) => { logger.error(`Cronjob: Error when cleaning up old data: ${stringifyError(error)}`) logger.error(error) }), - restartCasparCG(system, previousLastNightlyCronjob).catch((e) => { + restartCasparCG(systemSettings, previousLastNightlyCronjob).catch((e) => { logger.error(`Cron: Restart CasparCG error: ${stringifyError(e)}`) }), - storeSnapshots(system).catch((e) => { + storeSnapshots(systemSettings).catch((e) => { logger.error(`Cron: Rundown Snapshots error: ${stringifyError(e)}`) }), ]) @@ -81,8 +84,8 @@ async function cleanupOldDataCronjob() { const CASPARCG_LAST_SEEN_PERIOD_MS = 3 * 60 * 1000 // Note: this must be higher than the ping interval used by playout-gateway -async function restartCasparCG(system: ICoreSystem | undefined, previousLastNightlyCronjob: number) { - if (!system?.cron?.casparCGRestart?.enabled) return +async function restartCasparCG(systemSettings: ICoreSystemSettings | undefined, previousLastNightlyCronjob: number) { + if (!systemSettings?.cron?.casparCGRestart?.enabled) return let shouldRetryAttempt = false const ps: Array> = [] @@ -176,10 +179,10 @@ async function restartCasparCG(system: ICoreSystem | undefined, previousLastNigh } } -async function storeSnapshots(system: ICoreSystem | undefined) { - if (system?.cron?.storeRundownSnapshots?.enabled) { - const filter = system.cron.storeRundownSnapshots.rundownNames?.length - ? { name: { $in: system.cron.storeRundownSnapshots.rundownNames } } +async function storeSnapshots(systemSettings: ICoreSystemSettings | undefined) { + if (systemSettings?.cron?.storeRundownSnapshots?.enabled) { + const filter = systemSettings.cron.storeRundownSnapshots.rundownNames?.length + ? { name: { $in: systemSettings.cron.storeRundownSnapshots.rundownNames } } : {} const playlists = await RundownPlaylists.findFetchAsync(filter) diff --git a/meteor/server/logo.ts b/meteor/server/logo.ts index 26536e65b8..2e7910bae6 100644 --- a/meteor/server/logo.ts +++ b/meteor/server/logo.ts @@ -13,7 +13,7 @@ logoRouter.get('/', async (ctx) => { const logo = core?.logo ?? SofieLogo.Default const paths: Record = { - [SofieLogo.Default]: '/images/sofie-logo.svg', + [SofieLogo.Default]: '/images/sofie-logo-default.svg', [SofieLogo.Pride]: '/images/sofie-logo-pride.svg', [SofieLogo.Norway]: '/images/sofie-logo-norway.svg', [SofieLogo.Christmas]: '/images/sofie-logo-christmas.svg', diff --git a/meteor/server/migration/X_X_X.ts b/meteor/server/migration/X_X_X.ts index d4162596f8..62abd9a8f8 100644 --- a/meteor/server/migration/X_X_X.ts +++ b/meteor/server/migration/X_X_X.ts @@ -1,6 +1,6 @@ import { addMigrationSteps } from './databaseMigration' import { CURRENT_SYSTEM_VERSION } from './currentSystemVersion' -import { Studios, TriggeredActions } from '../collections' +import { CoreSystem, Studios, TriggeredActions } from '../collections' import { convertObjectIntoOverrides, wrapDefaultObject, @@ -12,6 +12,8 @@ import { IStudioSettings, } from '@sofie-automation/corelib/dist/dataModel/Studio' import { DEFAULT_CORE_TRIGGER_IDS } from './upgrades/defaultSystemActionTriggers' +import { ICoreSystem } from '@sofie-automation/meteor-lib/dist/collections/CoreSystem' +import { ICoreSystemSettings } from '@sofie-automation/shared-lib/dist/core/model/CoreSystemSettings' /* * ************************************************************************************** @@ -258,4 +260,78 @@ export const addSteps = addMigrationSteps(CURRENT_SYSTEM_VERSION, [ } }, }, + + { + id: `convert CoreSystem.settingsWithOverrides`, + canBeRunAutomatically: true, + validate: async () => { + const systems = await CoreSystem.findFetchAsync({ + settingsWithOverrides: { $exists: false }, + }) + + if (systems.length > 0) { + return 'settings must be converted to an ObjectWithOverrides' + } + + return false + }, + migrate: async () => { + const systems = await CoreSystem.findFetchAsync({ + settingsWithOverrides: { $exists: false }, + }) + + for (const system of systems) { + const oldSystem = system as ICoreSystem as PartialOldICoreSystem + + const newSettings = wrapDefaultObject({ + cron: { + casparCGRestart: { + enabled: false, + }, + storeRundownSnapshots: { + enabled: false, + }, + ...oldSystem.cron, + }, + support: oldSystem.support ?? { message: '' }, + evaluationsMessage: oldSystem.evaluations ?? { enabled: false, heading: '', message: '' }, + }) + + await CoreSystem.updateAsync(system._id, { + $set: { + settingsWithOverrides: newSettings, + }, + $unset: { + cron: 1, + support: 1, + evaluations: 1, + }, + }) + } + }, + }, ]) + +interface PartialOldICoreSystem { + /** Support info */ + support?: { + message: string + } + + evaluations?: { + enabled: boolean + heading: string + message: string + } + + /** Cron jobs running nightly */ + cron?: { + casparCGRestart?: { + enabled: boolean + } + storeRundownSnapshots?: { + enabled: boolean + rundownNames?: string[] + } + } +} diff --git a/meteor/server/migration/upgrades/system.ts b/meteor/server/migration/upgrades/system.ts index 3621fd78e4..15ae90bea8 100644 --- a/meteor/server/migration/upgrades/system.ts +++ b/meteor/server/migration/upgrades/system.ts @@ -13,6 +13,7 @@ import { updateTriggeredActionsForShowStyleBaseId } from './lib' import { CoreSystemId } from '@sofie-automation/corelib/dist/dataModel/Ids' import { DEFAULT_CORE_TRIGGERS } from './defaultSystemActionTriggers' import { protectString } from '@sofie-automation/corelib/dist/protectedString' +import { ICoreSystemSettings } from '@sofie-automation/shared-lib/dist/core/model/CoreSystemSettings' export async function runUpgradeForCoreSystem(coreSystemId: CoreSystemId): Promise { logger.info(`Running upgrade for CoreSystem`) @@ -33,10 +34,11 @@ export async function runUpgradeForCoreSystem(coreSystemId: CoreSystemId): Promi result = generateDefaultSystemConfig() } + const coreSystemSettings: ICoreSystemSettings = result.settings + await CoreSystem.updateAsync(coreSystemId, { $set: { - // 'sourceLayersWithOverrides.defaults': normalizeArray(result.sourceLayers, '_id'), - // 'outputLayersWithOverrides.defaults': normalizeArray(result.outputLayers, '_id'), + 'settingsWithOverrides.defaults': coreSystemSettings, lastBlueprintConfig: { blueprintHash: blueprint?.blueprintHash ?? protectString('default'), blueprintId: blueprint?._id ?? protectString('default'), @@ -83,6 +85,24 @@ async function loadCoreSystemAndBlueprint(coreSystemId: CoreSystemId) { function generateDefaultSystemConfig(): BlueprintResultApplySystemConfig { return { + settings: { + cron: { + casparCGRestart: { + enabled: true, + }, + storeRundownSnapshots: { + enabled: false, + }, + }, + support: { + message: '', + }, + evaluationsMessage: { + enabled: false, + heading: '', + message: '', + }, + }, triggeredActions: Object.values(DEFAULT_CORE_TRIGGERS), } } diff --git a/meteor/server/publications/system.ts b/meteor/server/publications/system.ts index e494807e85..d1f615a9df 100644 --- a/meteor/server/publications/system.ts +++ b/meteor/server/publications/system.ts @@ -15,16 +15,14 @@ meteorPublish(MeteorPubSub.coreSystem, async function (token: string | undefined fields: { // Include only specific fields in the result documents: _id: 1, - support: 1, systemInfo: 1, apm: 1, name: 1, logLevel: 1, serviceMessages: 1, blueprintId: 1, - cron: 1, logo: 1, - evaluations: 1, + settingsWithOverrides: 1, }, }) } diff --git a/packages/blueprints-integration/src/api/system.ts b/packages/blueprints-integration/src/api/system.ts index 045d3a0ee1..2df0b01c9b 100644 --- a/packages/blueprints-integration/src/api/system.ts +++ b/packages/blueprints-integration/src/api/system.ts @@ -2,6 +2,7 @@ import type { IBlueprintTriggeredActions } from '../triggers' import type { MigrationStepSystem } from '../migrations' import type { BlueprintManifestBase, BlueprintManifestType } from './base' import type { ICoreSystemApplyConfigContext } from '../context/systemApplyConfigContext' +import type { ICoreSystemSettings } from '@sofie-automation/shared-lib/dist/core/model/CoreSystemSettings' export interface SystemBlueprintManifest extends BlueprintManifestBase { blueprintType: BlueprintManifestType.SYSTEM @@ -38,8 +39,7 @@ export interface SystemBlueprintManifest extends BlueprintManifestBase { } export interface BlueprintResultApplySystemConfig { - // sourceLayers: ISourceLayer[] - // outputLayers: IOutputLayer[] + settings: ICoreSystemSettings triggeredActions: IBlueprintTriggeredActions[] } diff --git a/packages/blueprints-integration/src/index.ts b/packages/blueprints-integration/src/index.ts index 30089e21c9..4eb1fa41a5 100644 --- a/packages/blueprints-integration/src/index.ts +++ b/packages/blueprints-integration/src/index.ts @@ -29,3 +29,4 @@ export * from '@sofie-automation/shared-lib/dist/lib/JSONBlob' export * from '@sofie-automation/shared-lib/dist/lib/JSONSchemaUtil' export * from '@sofie-automation/shared-lib/dist/core/model/StudioRouteSet' export * from '@sofie-automation/shared-lib/dist/core/model/StudioSettings' +export * from '@sofie-automation/shared-lib/dist/core/model/CoreSystemSettings' diff --git a/packages/meteor-lib/src/collections/CoreSystem.ts b/packages/meteor-lib/src/collections/CoreSystem.ts index f379677340..e710091a16 100644 --- a/packages/meteor-lib/src/collections/CoreSystem.ts +++ b/packages/meteor-lib/src/collections/CoreSystem.ts @@ -2,6 +2,8 @@ import { LastBlueprintConfig } from '@sofie-automation/corelib/dist/dataModel/Bl import { LogLevel } from '../lib' import { CoreSystemId, BlueprintId } from '@sofie-automation/corelib/dist/dataModel/Ids' import { protectString } from '@sofie-automation/corelib/dist/protectedString' +import { ObjectWithOverrides } from '@sofie-automation/corelib/dist/settings/objectWithOverrides' +import { ICoreSystemSettings } from '@sofie-automation/shared-lib/dist/core/model/CoreSystemSettings' export const SYSTEM_ID: CoreSystemId = protectString('core') @@ -55,22 +57,11 @@ export interface ICoreSystem { /** Id of the blueprint used by this system */ blueprintId?: BlueprintId - /** Support info */ - support?: { - message: string - } - systemInfo?: { message: string enabled: boolean } - evaluations?: { - enabled: boolean - heading: string - message: string - } - /** A user-defined name for the installation */ name?: string @@ -96,16 +87,7 @@ export interface ICoreSystem { } enableMonitorBlockedThread?: boolean - /** Cron jobs running nightly */ - cron?: { - casparCGRestart?: { - enabled: boolean - } - storeRundownSnapshots?: { - enabled: boolean - rundownNames?: string[] - } - } + settingsWithOverrides: ObjectWithOverrides logo?: SofieLogo diff --git a/packages/shared-lib/src/core/model/CoreSystemSettings.ts b/packages/shared-lib/src/core/model/CoreSystemSettings.ts new file mode 100644 index 0000000000..e5392915a5 --- /dev/null +++ b/packages/shared-lib/src/core/model/CoreSystemSettings.ts @@ -0,0 +1,23 @@ +export interface ICoreSystemSettings { + /** Cron jobs running nightly */ + cron: { + casparCGRestart: { + enabled: boolean + } + storeRundownSnapshots?: { + enabled: boolean + rundownNames?: string[] + } + } + + /** Support info */ + support: { + message: string + } + + evaluationsMessage: { + enabled: boolean + heading: string + message: string + } +} diff --git a/packages/webui/public/images/sofie-logo.svg b/packages/webui/public/images/sofie-logo-default.svg similarity index 100% rename from packages/webui/public/images/sofie-logo.svg rename to packages/webui/public/images/sofie-logo-default.svg diff --git a/packages/webui/src/__mocks__/helpers/database.ts b/packages/webui/src/__mocks__/helpers/database.ts index a8273a81bf..e5d1b62274 100644 --- a/packages/webui/src/__mocks__/helpers/database.ts +++ b/packages/webui/src/__mocks__/helpers/database.ts @@ -72,6 +72,24 @@ export async function setupMockCore(doc?: Partial): Promise ) } + +interface ICombinedMultiLineTextInputControlProps + extends Omit { + value: string + handleUpdate: (value: string) => void +} +export function CombinedMultiLineTextInputControl({ + value, + handleUpdate, + ...props +}: Readonly): JSX.Element { + const valueArray = useMemo(() => splitValueIntoLines(value), [value]) + const handleUpdateArray = useCallback((value: string[]) => handleUpdate(joinLines(value)), [handleUpdate]) + + return +} diff --git a/packages/webui/src/client/ui/AfterBroadcastForm.tsx b/packages/webui/src/client/ui/AfterBroadcastForm.tsx index 974bbcc00d..2dd28795cc 100644 --- a/packages/webui/src/client/ui/AfterBroadcastForm.tsx +++ b/packages/webui/src/client/ui/AfterBroadcastForm.tsx @@ -16,6 +16,8 @@ import { NotificationCenter, Notification, NoticeLevel } from '../lib/notificati import { isLoopRunning } from '../lib/RundownResolver' import { useTracker } from '../lib/ReactMeteorData/ReactMeteorData' import { CoreSystem } from '../collections' +import { applyAndValidateOverrides } from '@sofie-automation/corelib/dist/settings/objectWithOverrides' +import { SYSTEM_ID } from '@sofie-automation/meteor-lib/dist/collections/CoreSystem' type ProblemType = 'nothing' | 'minor' | 'major' @@ -169,9 +171,12 @@ export function AfterBroadcastForm({ playlist }: Readonly<{ playlist: DBRundownP } const EvaluationInfoBubble = React.memo(function EvaluationInfoBubble() { - const coreSystem = useTracker(() => CoreSystem.findOne(), []) + const coreSystemSettings = useTracker(() => { + const core = CoreSystem.findOne(SYSTEM_ID, { projection: { settingsWithOverrides: 1 } }) + return core && applyAndValidateOverrides(core.settingsWithOverrides).obj + }, []) - const message = coreSystem?.evaluations?.enabled ? coreSystem.evaluations : undefined + const message = coreSystemSettings?.evaluationsMessage?.enabled ? coreSystemSettings.evaluationsMessage : undefined if (!message) return null return ( diff --git a/packages/webui/src/client/ui/Settings/SystemManagement.tsx b/packages/webui/src/client/ui/Settings/SystemManagement.tsx index 93b9f41d25..2b41a9fafa 100644 --- a/packages/webui/src/client/ui/Settings/SystemManagement.tsx +++ b/packages/webui/src/client/ui/Settings/SystemManagement.tsx @@ -9,15 +9,30 @@ import { languageAnd } from '../../lib/language' import { TriggeredActionsEditor } from './components/triggeredActions/TriggeredActionsEditor' import { TFunction, useTranslation } from 'react-i18next' import { Meteor } from 'meteor/meteor' -import { LogLevel } from '../../lib/tempLib' +import { literal, LogLevel } from '../../lib/tempLib' import { CoreSystem } from '../../collections' import { CollectionCleanupResult } from '@sofie-automation/meteor-lib/dist/api/system' -import { LabelActual } from '../../lib/Components/LabelAndOverrides' +import { + LabelActual, + LabelAndOverrides, + LabelAndOverridesForCheckbox, + LabelAndOverridesForMultiLineText, +} from '../../lib/Components/LabelAndOverrides' import { catchError } from '../../lib/lib' import { SystemManagementBlueprint } from './SystemManagement/Blueprint' +import { + applyAndValidateOverrides, + ObjectWithOverrides, + SomeObjectOverrideOp, +} from '@sofie-automation/corelib/dist/settings/objectWithOverrides' +import { ICoreSystemSettings } from '@sofie-automation/blueprints-integration' +import { WrappedOverridableItemNormal, useOverrideOpHelper } from './util/OverrideOpHelper' +import { CheckboxControl } from '../../lib/Components/Checkbox' +import { CombinedMultiLineTextInputControl, MultiLineTextInputControl } from '../../lib/Components/MultiLineTextInput' +import { TextInputControl } from '../../lib/Components/TextInput' interface WithCoreSystemProps { - coreSystem: ICoreSystem | undefined + coreSystem: ICoreSystem } export default function SystemManagement(): JSX.Element | null { @@ -159,25 +174,29 @@ function SystemManagementNotificationMessage({ coreSystem }: Readonly) { const { t } = useTranslation() + const { wrappedItem, overrideHelper } = useCoreSystemSettingsWithOverrides(coreSystem) + return ( <>

{t('Support Panel')}

- + )} +
) @@ -186,50 +205,56 @@ function SystemManagementSupportPanel({ coreSystem }: Readonly) { const { t } = useTranslation() + const { wrappedItem, overrideHelper } = useCoreSystemSettingsWithOverrides(coreSystem) + return ( <>

{t('Evaluations')}

- - - + )} +
) @@ -307,55 +332,49 @@ function SystemManagementMonitoring({ coreSystem }: Readonly) { const { t } = useTranslation() + const { wrappedItem, overrideHelper } = useCoreSystemSettingsWithOverrides(coreSystem) + return ( <>

{t('Cron jobs')}

- - - + )} +
) @@ -574,3 +593,52 @@ function SystemManagementHeapSnapshot() { ) } + +function useCoreSystemSettingsWithOverrides(coreSystem: ICoreSystem) { + const saveOverrides = useCallback( + (newOps: SomeObjectOverrideOp[]) => { + console.log('save', newOps) + CoreSystem.update(coreSystem._id, { + $set: { + 'settingsWithOverrides.overrides': newOps.map((op) => ({ + ...op, + path: op.path.startsWith('0.') ? op.path.slice(2) : op.path, + })), + }, + }) + }, + [coreSystem._id] + ) + + const [wrappedItem, wrappedConfigObject] = useMemo(() => { + const prefixedOps = coreSystem.settingsWithOverrides.overrides.map((op) => ({ + ...op, + // TODO: can we avoid doing this hack? + path: `0.${op.path}`, + })) + + const computedValue = applyAndValidateOverrides(coreSystem.settingsWithOverrides).obj + + const wrappedItem = literal>({ + type: 'normal', + id: '0', + computed: computedValue, + defaults: coreSystem.settingsWithOverrides.defaults, + overrideOps: prefixedOps, + }) + + const wrappedConfigObject: ObjectWithOverrides = { + defaults: coreSystem.settingsWithOverrides.defaults, + overrides: prefixedOps, + } + + return [wrappedItem, wrappedConfigObject] + }, [coreSystem.settingsWithOverrides]) + + const overrideHelper = useOverrideOpHelper(saveOverrides, wrappedConfigObject) + + return { + wrappedItem, + overrideHelper, + } +} diff --git a/packages/webui/src/client/ui/SupportPopUp.tsx b/packages/webui/src/client/ui/SupportPopUp.tsx index 9c6a2b8c7c..827fe6145e 100644 --- a/packages/webui/src/client/ui/SupportPopUp.tsx +++ b/packages/webui/src/client/ui/SupportPopUp.tsx @@ -5,6 +5,8 @@ import { SupportIcon } from '../lib/ui/icons/supportIcon' import { useTranslation } from 'react-i18next' import { getHelpMode } from '../lib/localStorage' import { CoreSystem } from '../collections' +import { SYSTEM_ID } from '@sofie-automation/meteor-lib/dist/collections/CoreSystem' +import { applyAndValidateOverrides } from '@sofie-automation/corelib/dist/settings/objectWithOverrides' interface IProps {} @@ -13,9 +15,10 @@ export function SupportPopUp({ children }: Readonly { - const core = CoreSystem.findOne() + const core = CoreSystem.findOne(SYSTEM_ID, { projection: { settingsWithOverrides: 1 } }) + const coreSettings = core && applyAndValidateOverrides(core.settingsWithOverrides).obj return { - supportMessage: core?.support?.message ?? '', + supportMessage: coreSettings?.support?.message ?? '', } }, [], From 97316573313555caec7dcc5754b1fcd4d48aed95 Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Mon, 18 Nov 2024 13:16:37 +0000 Subject: [PATCH 12/14] chore: remove duplicate types --- .../upgrades/defaultSystemActionTriggers.ts | 60 +++++++------------ 1 file changed, 20 insertions(+), 40 deletions(-) diff --git a/meteor/server/migration/upgrades/defaultSystemActionTriggers.ts b/meteor/server/migration/upgrades/defaultSystemActionTriggers.ts index 83cf7abfaa..29242cbecd 100644 --- a/meteor/server/migration/upgrades/defaultSystemActionTriggers.ts +++ b/meteor/server/migration/upgrades/defaultSystemActionTriggers.ts @@ -3,36 +3,16 @@ import { ClientActions, TriggerType, PlayoutActions, + IBlueprintDefaultCoreSystemTriggersType, + IBlueprintDefaultCoreSystemTriggers, } from '@sofie-automation/blueprints-integration' import { getHash, protectString, generateTranslation as t } from '../../lib/tempLib' import { TriggeredActionId } from '@sofie-automation/corelib/dist/dataModel/Ids' let j = 0 -export enum IBlueprintDefaultCoreTriggersType { - toggleShelf = 'toggleShelf', - activateRundownPlaylist = 'activateRundownPlaylist', - activateRundownPlaylistRehearsal = 'activateRundownPlaylistRehearsal', - deactivateRundownPlaylist = 'deactivateRundownPlaylist', - take = 'take', - hold = 'hold', - holdUndo = 'holdUndo', - resetRundownPlaylist = 'resetRundownPlaylist', - disableNextPiece = 'disableNextPiece', - disableNextPieceUndo = 'disableNextPieceUndo', - createSnapshotForDebug = 'createSnapshotForDebug', - moveNextPart = 'moveNextPart', - moveNextSegment = 'moveNextSegment', - movePreviousPart = 'movePreviousPart', - movePreviousSegment = 'movePreviousSegment', - goToOnAirLine = 'goToOnAirLine', - rewindSegments = 'rewindSegments', -} - -export type IBlueprintDefaultCoreTriggers = { [key in IBlueprintDefaultCoreTriggersType]: IBlueprintTriggeredActions } - -export const DEFAULT_CORE_TRIGGERS: IBlueprintDefaultCoreTriggers = { - [IBlueprintDefaultCoreTriggersType.toggleShelf]: { +export const DEFAULT_CORE_TRIGGERS: IBlueprintDefaultCoreSystemTriggers = { + [IBlueprintDefaultCoreSystemTriggersType.toggleShelf]: { _id: 'core_toggleShelf', actions: { '0': { @@ -55,7 +35,7 @@ export const DEFAULT_CORE_TRIGGERS: IBlueprintDefaultCoreTriggers = { _rank: ++j * 1000, name: t('Toggle Shelf'), }, - [IBlueprintDefaultCoreTriggersType.activateRundownPlaylist]: { + [IBlueprintDefaultCoreSystemTriggersType.activateRundownPlaylist]: { _id: 'core_activateRundownPlaylist', actions: { '0': { @@ -78,7 +58,7 @@ export const DEFAULT_CORE_TRIGGERS: IBlueprintDefaultCoreTriggers = { _rank: ++j * 1000, name: t('Activate (On-Air)'), }, - [IBlueprintDefaultCoreTriggersType.activateRundownPlaylistRehearsal]: { + [IBlueprintDefaultCoreSystemTriggersType.activateRundownPlaylistRehearsal]: { _id: 'core_activateRundownPlaylist_rehearsal', actions: { '0': { @@ -101,7 +81,7 @@ export const DEFAULT_CORE_TRIGGERS: IBlueprintDefaultCoreTriggers = { _rank: ++j * 1000, name: t('Activate (Rehearsal)'), }, - [IBlueprintDefaultCoreTriggersType.deactivateRundownPlaylist]: { + [IBlueprintDefaultCoreSystemTriggersType.deactivateRundownPlaylist]: { _id: 'core_deactivateRundownPlaylist', actions: { '0': { @@ -123,7 +103,7 @@ export const DEFAULT_CORE_TRIGGERS: IBlueprintDefaultCoreTriggers = { _rank: ++j * 1000, name: t('Deactivate'), }, - [IBlueprintDefaultCoreTriggersType.take]: { + [IBlueprintDefaultCoreSystemTriggersType.take]: { _id: 'core_take', actions: { '0': { @@ -150,7 +130,7 @@ export const DEFAULT_CORE_TRIGGERS: IBlueprintDefaultCoreTriggers = { _rank: ++j * 1000, name: t('Take'), }, - [IBlueprintDefaultCoreTriggersType.hold]: { + [IBlueprintDefaultCoreSystemTriggersType.hold]: { _id: 'core_hold', actions: { '0': { @@ -172,7 +152,7 @@ export const DEFAULT_CORE_TRIGGERS: IBlueprintDefaultCoreTriggers = { _rank: ++j * 1000, name: t('Hold'), }, - [IBlueprintDefaultCoreTriggersType.holdUndo]: { + [IBlueprintDefaultCoreSystemTriggersType.holdUndo]: { _id: 'core_hold_undo', actions: { '0': { @@ -195,7 +175,7 @@ export const DEFAULT_CORE_TRIGGERS: IBlueprintDefaultCoreTriggers = { _rank: ++j * 1000, name: t('Undo Hold'), }, - [IBlueprintDefaultCoreTriggersType.resetRundownPlaylist]: { + [IBlueprintDefaultCoreSystemTriggersType.resetRundownPlaylist]: { _id: 'core_reset_rundown_playlist', actions: { '0': { @@ -222,7 +202,7 @@ export const DEFAULT_CORE_TRIGGERS: IBlueprintDefaultCoreTriggers = { _rank: ++j * 1000, name: t('Reset Rundown'), }, - [IBlueprintDefaultCoreTriggersType.disableNextPiece]: { + [IBlueprintDefaultCoreSystemTriggersType.disableNextPiece]: { _id: 'core_disable_next_piece', actions: { '0': { @@ -244,7 +224,7 @@ export const DEFAULT_CORE_TRIGGERS: IBlueprintDefaultCoreTriggers = { _rank: ++j * 1000, name: t('Disable the next element'), }, - [IBlueprintDefaultCoreTriggersType.disableNextPieceUndo]: { + [IBlueprintDefaultCoreSystemTriggersType.disableNextPieceUndo]: { _id: 'core_disable_next_piece_undo', actions: { '0': { @@ -267,7 +247,7 @@ export const DEFAULT_CORE_TRIGGERS: IBlueprintDefaultCoreTriggers = { _rank: ++j * 1000, name: t('Undo Disable the next element'), }, - [IBlueprintDefaultCoreTriggersType.createSnapshotForDebug]: { + [IBlueprintDefaultCoreSystemTriggersType.createSnapshotForDebug]: { _id: 'core_create_snapshot_for_debug', actions: { '0': { @@ -289,7 +269,7 @@ export const DEFAULT_CORE_TRIGGERS: IBlueprintDefaultCoreTriggers = { _rank: ++j * 1000, name: t('Store Snapshot'), }, - [IBlueprintDefaultCoreTriggersType.moveNextPart]: { + [IBlueprintDefaultCoreSystemTriggersType.moveNextPart]: { _id: 'core_move_next_part', actions: { '0': { @@ -313,7 +293,7 @@ export const DEFAULT_CORE_TRIGGERS: IBlueprintDefaultCoreTriggers = { _rank: ++j * 1000, name: t('Move Next forwards'), }, - [IBlueprintDefaultCoreTriggersType.moveNextSegment]: { + [IBlueprintDefaultCoreSystemTriggersType.moveNextSegment]: { _id: 'core_move_next_segment', actions: { '0': { @@ -337,7 +317,7 @@ export const DEFAULT_CORE_TRIGGERS: IBlueprintDefaultCoreTriggers = { _rank: ++j * 1000, name: t('Move Next to the following segment'), }, - [IBlueprintDefaultCoreTriggersType.movePreviousPart]: { + [IBlueprintDefaultCoreSystemTriggersType.movePreviousPart]: { _id: 'core_move_previous_part', actions: { '0': { @@ -361,7 +341,7 @@ export const DEFAULT_CORE_TRIGGERS: IBlueprintDefaultCoreTriggers = { _rank: ++j * 1000, name: t('Move Next backwards'), }, - [IBlueprintDefaultCoreTriggersType.movePreviousSegment]: { + [IBlueprintDefaultCoreSystemTriggersType.movePreviousSegment]: { _id: 'core_move_previous_segment', actions: { '0': { @@ -385,7 +365,7 @@ export const DEFAULT_CORE_TRIGGERS: IBlueprintDefaultCoreTriggers = { _rank: ++j * 1000, name: t('Move Next to the previous segment'), }, - [IBlueprintDefaultCoreTriggersType.goToOnAirLine]: { + [IBlueprintDefaultCoreSystemTriggersType.goToOnAirLine]: { _id: 'core_go_to_onAir_line', actions: { '0': { @@ -407,7 +387,7 @@ export const DEFAULT_CORE_TRIGGERS: IBlueprintDefaultCoreTriggers = { _rank: ++j * 1000, name: t('Go to On Air line'), }, - [IBlueprintDefaultCoreTriggersType.rewindSegments]: { + [IBlueprintDefaultCoreSystemTriggersType.rewindSegments]: { _id: 'core_rewind_segments', actions: { '0': { From 60a73ac581afe0a1ccffce8c7870b604263d4642 Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Wed, 20 Nov 2024 15:25:50 +0000 Subject: [PATCH 13/14] chore: avoid reexport --- meteor/server/api/rest/v1/typeConversion.ts | 2 +- meteor/server/publications/lib/quickLoop.ts | 2 +- packages/corelib/src/dataModel/RundownPlaylist.ts | 2 -- .../src/playout/__tests__/selectNextPart.test.ts | 3 ++- .../src/playout/lookahead/__tests__/util.test.ts | 3 ++- .../src/playout/model/services/QuickLoopService.ts | 2 +- packages/job-worker/src/playout/selectNextPart.ts | 7 ++----- packages/job-worker/src/rundownPlaylists.ts | 7 ++----- .../webui/src/client/lib/__tests__/rundownTiming.test.ts | 7 ++----- packages/webui/src/client/ui/Settings/Studio/Generic.tsx | 2 +- 10 files changed, 14 insertions(+), 23 deletions(-) diff --git a/meteor/server/api/rest/v1/typeConversion.ts b/meteor/server/api/rest/v1/typeConversion.ts index 8bb122c617..7b44254687 100644 --- a/meteor/server/api/rest/v1/typeConversion.ts +++ b/meteor/server/api/rest/v1/typeConversion.ts @@ -38,7 +38,7 @@ import { DEFAULT_FALLBACK_PART_DURATION, } from '@sofie-automation/shared-lib/dist/core/constants' import { Bucket } from '@sofie-automation/meteor-lib/dist/collections/Buckets' -import { ForceQuickLoopAutoNext } from '@sofie-automation/corelib/dist/dataModel/RundownPlaylist' +import { ForceQuickLoopAutoNext } from '@sofie-automation/shared-lib/dist/core/model/StudioSettings' /* This file contains functions that convert between the internal Sofie-Core types and types exposed to the external API. diff --git a/meteor/server/publications/lib/quickLoop.ts b/meteor/server/publications/lib/quickLoop.ts index f8c356a7ce..6178ddad6c 100644 --- a/meteor/server/publications/lib/quickLoop.ts +++ b/meteor/server/publications/lib/quickLoop.ts @@ -1,10 +1,10 @@ import { DBPart } from '@sofie-automation/corelib/dist/dataModel/Part' import { DBRundownPlaylist, - ForceQuickLoopAutoNext, QuickLoopMarker, QuickLoopMarkerType, } from '@sofie-automation/corelib/dist/dataModel/RundownPlaylist' +import { ForceQuickLoopAutoNext } from '@sofie-automation/shared-lib/dist/core/model/StudioSettings' import { MarkerPosition, compareMarkerPositions } from '@sofie-automation/corelib/dist/playout/playlist' import { ProtectedString, unprotectString } from '@sofie-automation/corelib/dist/protectedString' import { DEFAULT_FALLBACK_PART_DURATION } from '@sofie-automation/shared-lib/dist/core/constants' diff --git a/packages/corelib/src/dataModel/RundownPlaylist.ts b/packages/corelib/src/dataModel/RundownPlaylist.ts index 840184a0bb..f9ff938c02 100644 --- a/packages/corelib/src/dataModel/RundownPlaylist.ts +++ b/packages/corelib/src/dataModel/RundownPlaylist.ts @@ -13,8 +13,6 @@ import { import { RundownPlaylistNote } from './Notes' import { ForceQuickLoopAutoNext } from '@sofie-automation/shared-lib/dist/core/model/StudioSettings' -export { ForceQuickLoopAutoNext } - /** Details of an ab-session requested by the blueprints in onTimelineGenerate */ export interface ABSessionInfo { /** The unique id of the session. */ diff --git a/packages/job-worker/src/playout/__tests__/selectNextPart.test.ts b/packages/job-worker/src/playout/__tests__/selectNextPart.test.ts index 2d5f84d4be..387f21b6e8 100644 --- a/packages/job-worker/src/playout/__tests__/selectNextPart.test.ts +++ b/packages/job-worker/src/playout/__tests__/selectNextPart.test.ts @@ -8,7 +8,8 @@ import { MockJobContext, setupDefaultJobEnvironment } from '../../__mocks__/cont import { PlayoutSegmentModelImpl } from '../model/implementation/PlayoutSegmentModelImpl' import { PlayoutSegmentModel } from '../model/PlayoutSegmentModel' import { selectNextPart } from '../selectNextPart' -import { ForceQuickLoopAutoNext, QuickLoopMarkerType } from '@sofie-automation/corelib/dist/dataModel/RundownPlaylist' +import { QuickLoopMarkerType } from '@sofie-automation/corelib/dist/dataModel/RundownPlaylist' +import { ForceQuickLoopAutoNext } from '@sofie-automation/shared-lib/dist/core/model/StudioSettings' class MockPart { constructor( diff --git a/packages/job-worker/src/playout/lookahead/__tests__/util.test.ts b/packages/job-worker/src/playout/lookahead/__tests__/util.test.ts index 5807e8b6c1..969506ce1a 100644 --- a/packages/job-worker/src/playout/lookahead/__tests__/util.test.ts +++ b/packages/job-worker/src/playout/lookahead/__tests__/util.test.ts @@ -11,7 +11,8 @@ import { defaultRundownPlaylist } from '../../../__mocks__/defaultCollectionObje import _ = require('underscore') import { wrapPartToTemporaryInstance } from '../../../__mocks__/partinstance' import { wrapDefaultObject } from '@sofie-automation/corelib/dist/settings/objectWithOverrides' -import { ForceQuickLoopAutoNext, QuickLoopMarkerType } from '@sofie-automation/corelib/dist/dataModel/RundownPlaylist' +import { QuickLoopMarkerType } from '@sofie-automation/corelib/dist/dataModel/RundownPlaylist' +import { ForceQuickLoopAutoNext } from '@sofie-automation/shared-lib/dist/core/model/StudioSettings' describe('getOrderedPartsAfterPlayhead', () => { let context!: MockJobContext diff --git a/packages/job-worker/src/playout/model/services/QuickLoopService.ts b/packages/job-worker/src/playout/model/services/QuickLoopService.ts index eb7bb0c6e1..b9d252ca7a 100644 --- a/packages/job-worker/src/playout/model/services/QuickLoopService.ts +++ b/packages/job-worker/src/playout/model/services/QuickLoopService.ts @@ -1,11 +1,11 @@ import { MarkerPosition, compareMarkerPositions } from '@sofie-automation/corelib/dist/playout/playlist' import { PlayoutModelReadonly } from '../PlayoutModel' import { - ForceQuickLoopAutoNext, QuickLoopMarker, QuickLoopMarkerType, QuickLoopProps, } from '@sofie-automation/corelib/dist/dataModel/RundownPlaylist' +import { ForceQuickLoopAutoNext } from '@sofie-automation/shared-lib/dist/core/model/StudioSettings' import { ReadonlyObjectDeep } from 'type-fest/source/readonly-deep' import { DBPart } from '@sofie-automation/corelib/dist/dataModel/Part' import { RundownId, SegmentId } from '@sofie-automation/corelib/dist/dataModel/Ids' diff --git a/packages/job-worker/src/playout/selectNextPart.ts b/packages/job-worker/src/playout/selectNextPart.ts index d07abbf446..48d495d85c 100644 --- a/packages/job-worker/src/playout/selectNextPart.ts +++ b/packages/job-worker/src/playout/selectNextPart.ts @@ -1,11 +1,8 @@ import { DBPart, isPartPlayable } from '@sofie-automation/corelib/dist/dataModel/Part' import { JobContext } from '../jobs' import { DBPartInstance } from '@sofie-automation/corelib/dist/dataModel/PartInstance' -import { - DBRundownPlaylist, - ForceQuickLoopAutoNext, - QuickLoopMarkerType, -} from '@sofie-automation/corelib/dist/dataModel/RundownPlaylist' +import { DBRundownPlaylist, QuickLoopMarkerType } from '@sofie-automation/corelib/dist/dataModel/RundownPlaylist' +import { ForceQuickLoopAutoNext } from '@sofie-automation/shared-lib/dist/core/model/StudioSettings' import { SegmentId } from '@sofie-automation/corelib/dist/dataModel/Ids' import { ReadonlyDeep } from 'type-fest' import { PlayoutSegmentModel } from './model/PlayoutSegmentModel' diff --git a/packages/job-worker/src/rundownPlaylists.ts b/packages/job-worker/src/rundownPlaylists.ts index 1d535c284e..31e8e90ffd 100644 --- a/packages/job-worker/src/rundownPlaylists.ts +++ b/packages/job-worker/src/rundownPlaylists.ts @@ -1,10 +1,7 @@ import { RundownPlaylistId, StudioId } from '@sofie-automation/corelib/dist/dataModel/Ids' import { DBRundown, Rundown } from '@sofie-automation/corelib/dist/dataModel/Rundown' -import { - DBRundownPlaylist, - ForceQuickLoopAutoNext, - QuickLoopMarkerType, -} from '@sofie-automation/corelib/dist/dataModel/RundownPlaylist' +import { DBRundownPlaylist, QuickLoopMarkerType } from '@sofie-automation/corelib/dist/dataModel/RundownPlaylist' +import { ForceQuickLoopAutoNext } from '@sofie-automation/shared-lib/dist/core/model/StudioSettings' import { clone, getHash, diff --git a/packages/webui/src/client/lib/__tests__/rundownTiming.test.ts b/packages/webui/src/client/lib/__tests__/rundownTiming.test.ts index 262fe8922c..89c4b34f05 100644 --- a/packages/webui/src/client/lib/__tests__/rundownTiming.test.ts +++ b/packages/webui/src/client/lib/__tests__/rundownTiming.test.ts @@ -1,8 +1,5 @@ -import { - DBRundownPlaylist, - ForceQuickLoopAutoNext, - QuickLoopMarkerType, -} from '@sofie-automation/corelib/dist/dataModel/RundownPlaylist' +import { DBRundownPlaylist, QuickLoopMarkerType } from '@sofie-automation/corelib/dist/dataModel/RundownPlaylist' +import { ForceQuickLoopAutoNext } from '@sofie-automation/shared-lib/dist/core/model/StudioSettings' import { PartInstance, wrapPartToTemporaryInstance } from '@sofie-automation/meteor-lib/dist/collections/PartInstances' import { DBPart } from '@sofie-automation/corelib/dist/dataModel/Part' import { DBSegment } from '@sofie-automation/corelib/dist/dataModel/Segment' diff --git a/packages/webui/src/client/ui/Settings/Studio/Generic.tsx b/packages/webui/src/client/ui/Settings/Studio/Generic.tsx index 06b13f1010..f3d4332047 100644 --- a/packages/webui/src/client/ui/Settings/Studio/Generic.tsx +++ b/packages/webui/src/client/ui/Settings/Studio/Generic.tsx @@ -18,7 +18,7 @@ import { LabelAndOverridesForInt, } from '../../../lib/Components/LabelAndOverrides' import { catchError } from '../../../lib/lib' -import { ForceQuickLoopAutoNext } from '@sofie-automation/corelib/src/dataModel/RundownPlaylist' +import { ForceQuickLoopAutoNext } from '@sofie-automation/shared-lib/dist/core/model/StudioSettings' import { applyAndValidateOverrides, ObjectWithOverrides, From bdce74757490ffb1dc5f8933a27ee83fcbe5c515 Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Wed, 20 Nov 2024 15:30:58 +0000 Subject: [PATCH 14/14] chore: review comments --- packages/blueprints-integration/src/api/system.ts | 15 +-------------- .../src/client/ui/Settings/Studio/Generic.tsx | 1 - .../src/client/ui/Settings/SystemManagement.tsx | 1 - 3 files changed, 1 insertion(+), 16 deletions(-) diff --git a/packages/blueprints-integration/src/api/system.ts b/packages/blueprints-integration/src/api/system.ts index 2df0b01c9b..a4c544d571 100644 --- a/packages/blueprints-integration/src/api/system.ts +++ b/packages/blueprints-integration/src/api/system.ts @@ -8,26 +8,13 @@ export interface SystemBlueprintManifest extends BlueprintManifestBase { blueprintType: BlueprintManifestType.SYSTEM /** A list of Migration steps related to the Core system - * @deprecated This has been replaced with `validateConfig` and `applyConfig` + * @deprecated This has been replaced with `applyConfig` */ coreMigrations: MigrationStepSystem[] /** Translations connected to the studio (as stringified JSON) */ translations?: string - // /** - // * Apply automatic upgrades to the structure of user specified config overrides - // * This lets you apply various changes to the user's values in an abstract way - // */ - // fixUpConfig?: (context: IFixUpConfigContext) => void - - // /** - // * Validate the config passed to this blueprint - // * In this you should do various sanity checks of the config and return a list of messages to display to the user. - // * These messages do not stop `applyConfig` from being called. - // */ - // validateConfig?: (context: ICommonContext, config: TRawConfig) => Array - /** * Apply the config by generating the data to be saved into the db. * This should be written to give a predictable and stable result, it can be called with the same config multiple times diff --git a/packages/webui/src/client/ui/Settings/Studio/Generic.tsx b/packages/webui/src/client/ui/Settings/Studio/Generic.tsx index f3d4332047..485c197da9 100644 --- a/packages/webui/src/client/ui/Settings/Studio/Generic.tsx +++ b/packages/webui/src/client/ui/Settings/Studio/Generic.tsx @@ -150,7 +150,6 @@ function StudioSettings({ studio }: { studio: DBStudio }): JSX.Element { const saveOverrides = React.useCallback( (newOps: SomeObjectOverrideOp[]) => { - console.log('save', newOps) Studios.update(studio._id, { $set: { 'settingsWithOverrides.overrides': newOps.map((op) => ({ diff --git a/packages/webui/src/client/ui/Settings/SystemManagement.tsx b/packages/webui/src/client/ui/Settings/SystemManagement.tsx index 2b41a9fafa..b15eef1622 100644 --- a/packages/webui/src/client/ui/Settings/SystemManagement.tsx +++ b/packages/webui/src/client/ui/Settings/SystemManagement.tsx @@ -597,7 +597,6 @@ function SystemManagementHeapSnapshot() { function useCoreSystemSettingsWithOverrides(coreSystem: ICoreSystem) { const saveOverrides = useCallback( (newOps: SomeObjectOverrideOp[]) => { - console.log('save', newOps) CoreSystem.update(coreSystem._id, { $set: { 'settingsWithOverrides.overrides': newOps.map((op) => ({