Skip to content

Commit ef14c8f

Browse files
committed
feat: configure Core system/studio settings via blueprints
1 parent c4365ad commit ef14c8f

File tree

116 files changed

+2469
-1447
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

116 files changed

+2469
-1447
lines changed

meteor/__mocks__/defaultCollectionObjects.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,15 +105,15 @@ export function defaultStudio(_id: StudioId): DBStudio {
105105
mappingsWithOverrides: wrapDefaultObject({}),
106106
supportedShowStyleBase: [],
107107
blueprintConfigWithOverrides: wrapDefaultObject({}),
108-
settings: {
108+
settingsWithOverrides: wrapDefaultObject({
109109
frameRate: 25,
110110
mediaPreviewsUrl: '',
111111
minimumTakeSpan: DEFAULT_MINIMUM_TAKE_SPAN,
112112
fallbackPartDuration: DEFAULT_FALLBACK_PART_DURATION,
113113
allowHold: false,
114114
allowPieceDirectPlay: false,
115115
enableBuckets: false,
116-
},
116+
}),
117117
_rundownVersionHash: '',
118118
routeSetsWithOverrides: wrapDefaultObject({}),
119119
routeSetExclusivityGroupsWithOverrides: wrapDefaultObject({}),

meteor/__mocks__/helpers/database.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,25 @@ export async function setupMockCore(doc?: Partial<ICoreSystem>): Promise<ICoreSy
171171
version: '0.0.0',
172172
previousVersion: '0.0.0',
173173
serviceMessages: {},
174+
settingsWithOverrides: wrapDefaultObject({
175+
cron: {
176+
casparCGRestart: {
177+
enabled: true,
178+
},
179+
storeRundownSnapshots: {
180+
enabled: false,
181+
},
182+
},
183+
support: {
184+
message: '',
185+
},
186+
evaluationsMessage: {
187+
enabled: false,
188+
heading: '',
189+
message: '',
190+
},
191+
}),
192+
lastBlueprintConfig: undefined,
174193
}
175194
const coreSystem = _.extend(defaultCore, doc)
176195
await CoreSystem.removeAsync(SYSTEM_ID)

meteor/server/__tests__/api/serviceMessages/serviceMessagesApi.test.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
} from '@sofie-automation/meteor-lib/dist/collections/CoreSystem'
1010
import { CoreSystem } from '../../../collections'
1111
import { SupressLogMessages } from '../../../../__mocks__/suppressLogging'
12+
import { wrapDefaultObject } from '@sofie-automation/corelib/dist/settings/objectWithOverrides'
1213

1314
function convertExternalToServiceMessage(message: ExternalServiceMessage): ServiceMessage {
1415
return {
@@ -42,6 +43,8 @@ const fakeCoreSystem: ICoreSystem = {
4243
version: '3',
4344
previousVersion: null,
4445
serviceMessages: {},
46+
settingsWithOverrides: wrapDefaultObject({} as any),
47+
lastBlueprintConfig: undefined,
4548
}
4649

4750
describe('Service messages internal API', () => {

meteor/server/__tests__/cronjobs.test.ts

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import '../../__mocks__/_extendJest'
22
import { testInFiber, runAllTimers, beforeAllInFiber, waitUntil } from '../../__mocks__/helpers/jest'
33
import { MeteorMock } from '../../__mocks__/meteor'
44
import { logger } from '../logging'
5-
import { getRandomId, getRandomString, protectString } from '../lib/tempLib'
5+
import { getRandomId, getRandomString, literal, protectString } from '../lib/tempLib'
66
import { SnapshotType } from '@sofie-automation/meteor-lib/dist/collections/Snapshots'
77
import { IBlueprintPieceType, PieceLifespan, StatusCode, TSR } from '@sofie-automation/blueprints-integration'
88
import {
@@ -64,26 +64,36 @@ import {
6464
import { DBSegment } from '@sofie-automation/corelib/dist/dataModel/Segment'
6565
import { Settings } from '../Settings'
6666
import { SofieIngestCacheType } from '@sofie-automation/corelib/dist/dataModel/SofieIngestDataCache'
67+
import { ObjectOverrideSetOp } from '@sofie-automation/corelib/dist/settings/objectWithOverrides'
6768

6869
describe('cronjobs', () => {
6970
let env: DefaultEnvironment
7071
let rundownId: RundownId
7172

72-
beforeAllInFiber(async () => {
73-
env = await setupDefaultStudioEnvironment()
74-
75-
const o = await setupDefaultRundownPlaylist(env)
76-
rundownId = o.rundownId
77-
73+
async function setCasparCGCronEnabled(enabled: boolean) {
7874
await CoreSystem.updateAsync(
7975
{},
8076
{
81-
$set: {
82-
'cron.casparCGRestart.enabled': true,
77+
// This is a little bit of a hack, as it will result in duplicate ops, but it's fine for unit tests
78+
$push: {
79+
'settingsWithOverrides.overrides': literal<ObjectOverrideSetOp>({
80+
op: 'set',
81+
path: 'cron.casparCGRestart.enabled',
82+
value: enabled,
83+
}),
8384
},
8485
},
8586
{ multi: true }
8687
)
88+
}
89+
90+
beforeAllInFiber(async () => {
91+
env = await setupDefaultStudioEnvironment()
92+
93+
const o = await setupDefaultRundownPlaylist(env)
94+
rundownId = o.rundownId
95+
96+
await setCasparCGCronEnabled(true)
8797

8898
jest.useFakeTimers()
8999
// set time to 2020/07/19 00:00 Local Time
@@ -589,15 +599,7 @@ describe('cronjobs', () => {
589599
})
590600
testInFiber('Does not attempt to restart CasparCG when job is disabled', async () => {
591601
await createMockPlayoutGatewayAndDevices(Date.now()) // Some time after the threshold
592-
await CoreSystem.updateAsync(
593-
{},
594-
{
595-
$set: {
596-
'cron.casparCGRestart.enabled': false,
597-
},
598-
},
599-
{ multi: true }
600-
)
602+
await setCasparCGCronEnabled(false)
601603
;(logger.info as jest.Mock).mockClear()
602604
// set time to 2020/07/{date} 04:05 Local Time, should be more than 24 hours after 2020/07/19 00:00 UTC
603605
mockCurrentTime = new Date(2020, 6, date++, 4, 5, 0).getTime()

meteor/server/api/evaluations.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { sendSlackMessageToWebhook } from './integration/slack'
1010
import { OrganizationId, UserId } from '@sofie-automation/corelib/dist/dataModel/Ids'
1111
import { DBRundownPlaylist } from '@sofie-automation/corelib/dist/dataModel/RundownPlaylist'
1212
import { Evaluations, RundownPlaylists } from '../collections'
13+
import { applyAndValidateOverrides } from '@sofie-automation/corelib/dist/settings/objectWithOverrides'
1314

1415
export async function saveEvaluation(
1516
credentials: {
@@ -33,8 +34,9 @@ export async function saveEvaluation(
3334
deferAsync(async () => {
3435
const studio = await fetchStudioLight(evaluation.studioId)
3536
if (!studio) throw new Meteor.Error(500, `Studio ${evaluation.studioId} not found!`)
37+
const studioSettings = applyAndValidateOverrides(studio.settingsWithOverrides).obj
3638

37-
const webhookUrls = _.compact((studio.settings.slackEvaluationUrls || '').split(','))
39+
const webhookUrls = _.compact((studioSettings.slackEvaluationUrls || '').split(','))
3840

3941
if (webhookUrls.length) {
4042
// Only send notes if not everything is OK

meteor/server/api/rest/v1/typeConversion.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ import {
5353
DEFAULT_FALLBACK_PART_DURATION,
5454
} from '@sofie-automation/shared-lib/dist/core/constants'
5555
import { Bucket } from '@sofie-automation/meteor-lib/dist/collections/Buckets'
56-
import { ForceQuickLoopAutoNext } from '@sofie-automation/corelib/dist/dataModel/RundownPlaylist'
56+
import { ForceQuickLoopAutoNext } from '@sofie-automation/shared-lib/dist/core/model/StudioSettings'
5757

5858
/*
5959
This file contains functions that convert between the internal Sofie-Core types and types exposed to the external API.
@@ -307,13 +307,17 @@ export async function studioFrom(apiStudio: APIStudio, existingId?: StudioId): P
307307
: convertObjectIntoOverrides(await StudioBlueprintConfigFromAPI(apiStudio, blueprintManifest))
308308
}
309309

310+
const studioSettings = studioSettingsFrom(apiStudio.settings)
311+
310312
return {
311313
_id: existingId ?? getRandomId(),
312314
name: apiStudio.name,
313315
blueprintId: blueprint?._id,
314316
blueprintConfigPresetId: apiStudio.blueprintConfigPresetId,
315317
blueprintConfigWithOverrides: blueprintConfig,
316-
settings: studioSettingsFrom(apiStudio.settings),
318+
settingsWithOverrides: studio
319+
? updateOverrides(studio.settingsWithOverrides, studioSettings)
320+
: wrapDefaultObject(studioSettings),
317321
supportedShowStyleBase: apiStudio.supportedShowStyleBase?.map((id) => protectString<ShowStyleBaseId>(id)) ?? [],
318322
organizationId: null,
319323
mappingsWithOverrides: wrapDefaultObject({}),
@@ -334,7 +338,7 @@ export async function studioFrom(apiStudio: APIStudio, existingId?: StudioId): P
334338
}
335339

336340
export async function APIStudioFrom(studio: DBStudio): Promise<Complete<APIStudio>> {
337-
const studioSettings = APIStudioSettingsFrom(studio.settings)
341+
const studioSettings = APIStudioSettingsFrom(applyAndValidateOverrides(studio.settingsWithOverrides).obj)
338342

339343
return {
340344
name: studio.name,

meteor/server/api/studio/api.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,14 +44,14 @@ export async function insertStudioInner(organizationId: OrganizationId | null, n
4444
supportedShowStyleBase: [],
4545
blueprintConfigWithOverrides: wrapDefaultObject({}),
4646
// testToolsConfig?: ITestToolsConfig
47-
settings: {
47+
settingsWithOverrides: wrapDefaultObject({
4848
frameRate: 25,
4949
mediaPreviewsUrl: '',
5050
minimumTakeSpan: DEFAULT_MINIMUM_TAKE_SPAN,
5151
allowHold: false,
5252
allowPieceDirectPlay: false,
5353
enableBuckets: true,
54-
},
54+
}),
5555
_rundownVersionHash: '',
5656
routeSetsWithOverrides: wrapDefaultObject({}),
5757
routeSetExclusivityGroupsWithOverrides: wrapDefaultObject({}),

meteor/server/collections/index.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,14 +67,13 @@ export const CoreSystem = createAsyncOnlyMongoCollection<ICoreSystem>(Collection
6767
if (!access.update) return logNotAllowed('CoreSystem', access.reason)
6868

6969
return allowOnlyFields(doc, fields, [
70-
'support',
7170
'systemInfo',
7271
'name',
7372
'logLevel',
7473
'apm',
75-
'cron',
7674
'logo',
77-
'evaluations',
75+
'blueprintId',
76+
'settingsWithOverrides',
7877
])
7978
},
8079
})

meteor/server/coreSystem/index.ts

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,14 @@ import { getEnvLogLevel, logger, LogLevel, setLogLevel } from '../logging'
1111
const PackageInfo = require('../../package.json')
1212
import Agent from 'meteor/julusian:meteor-elastic-apm'
1313
import { profiler } from '../api/profiler'
14-
import { TMP_TSR_VERSION } from '@sofie-automation/blueprints-integration'
14+
import { ICoreSystemSettings, TMP_TSR_VERSION } from '@sofie-automation/blueprints-integration'
1515
import { getAbsolutePath } from '../lib'
1616
import * as fs from 'fs/promises'
1717
import path from 'path'
1818
import { checkDatabaseVersions } from './checkDatabaseVersions'
1919
import PLazy from 'p-lazy'
2020
import { getCoreSystemAsync } from './collection'
21+
import { wrapDefaultObject } from '@sofie-automation/corelib/dist/settings/objectWithOverrides'
2122

2223
export { PackageInfo }
2324

@@ -60,11 +61,25 @@ async function initializeCoreSystem() {
6061
enabled: false,
6162
transactionSampleRate: -1,
6263
},
63-
cron: {
64-
casparCGRestart: {
65-
enabled: true,
64+
settingsWithOverrides: wrapDefaultObject<ICoreSystemSettings>({
65+
cron: {
66+
casparCGRestart: {
67+
enabled: true,
68+
},
69+
storeRundownSnapshots: {
70+
enabled: false,
71+
},
6672
},
67-
},
73+
support: {
74+
message: '',
75+
},
76+
evaluationsMessage: {
77+
enabled: false,
78+
heading: '',
79+
message: '',
80+
},
81+
}),
82+
lastBlueprintConfig: undefined,
6883
})
6984

7085
if (!isRunningInJest()) {

meteor/server/cronjobs.ts

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,14 @@ import { deferAsync, normalizeArrayToMap } from '@sofie-automation/corelib/dist/
1818
import { getCoreSystemAsync } from './coreSystem/collection'
1919
import { cleanupOldDataInner } from './api/cleanup'
2020
import { CollectionCleanupResult } from '@sofie-automation/meteor-lib/dist/api/system'
21-
import { ICoreSystem } from '@sofie-automation/meteor-lib/dist/collections/CoreSystem'
21+
import { ICoreSystemSettings } from '@sofie-automation/shared-lib/dist/core/model/CoreSystemSettings'
2222
import { executePeripheralDeviceFunctionWithCustomTimeout } from './api/peripheralDevice/executeFunction'
2323
import {
2424
interpollateTranslation,
2525
isTranslatableMessage,
2626
translateMessage,
2727
} from '@sofie-automation/corelib/dist/TranslatableMessage'
28+
import { applyAndValidateOverrides } from '@sofie-automation/corelib/dist/settings/objectWithOverrides'
2829

2930
const lowPrioFcn = (fcn: () => any) => {
3031
// Do it at a random time in the future:
@@ -49,15 +50,17 @@ export async function nightlyCronjobInner(): Promise<void> {
4950
logger.info('Nightly cronjob: starting...')
5051
const system = await getCoreSystemAsync()
5152

53+
const systemSettings = system && applyAndValidateOverrides(system.settingsWithOverrides).obj
54+
5255
await Promise.allSettled([
5356
cleanupOldDataCronjob().catch((error) => {
5457
logger.error(`Cronjob: Error when cleaning up old data: ${stringifyError(error)}`)
5558
logger.error(error)
5659
}),
57-
restartCasparCG(system, previousLastNightlyCronjob).catch((e) => {
60+
restartCasparCG(systemSettings, previousLastNightlyCronjob).catch((e) => {
5861
logger.error(`Cron: Restart CasparCG error: ${stringifyError(e)}`)
5962
}),
60-
storeSnapshots(system).catch((e) => {
63+
storeSnapshots(systemSettings).catch((e) => {
6164
logger.error(`Cron: Rundown Snapshots error: ${stringifyError(e)}`)
6265
}),
6366
])
@@ -81,8 +84,8 @@ async function cleanupOldDataCronjob() {
8184

8285
const CASPARCG_LAST_SEEN_PERIOD_MS = 3 * 60 * 1000 // Note: this must be higher than the ping interval used by playout-gateway
8386

84-
async function restartCasparCG(system: ICoreSystem | undefined, previousLastNightlyCronjob: number) {
85-
if (!system?.cron?.casparCGRestart?.enabled) return
87+
async function restartCasparCG(systemSettings: ICoreSystemSettings | undefined, previousLastNightlyCronjob: number) {
88+
if (!systemSettings?.cron?.casparCGRestart?.enabled) return
8689

8790
let shouldRetryAttempt = false
8891
const ps: Array<Promise<any>> = []
@@ -176,10 +179,10 @@ async function restartCasparCG(system: ICoreSystem | undefined, previousLastNigh
176179
}
177180
}
178181

179-
async function storeSnapshots(system: ICoreSystem | undefined) {
180-
if (system?.cron?.storeRundownSnapshots?.enabled) {
181-
const filter = system.cron.storeRundownSnapshots.rundownNames?.length
182-
? { name: { $in: system.cron.storeRundownSnapshots.rundownNames } }
182+
async function storeSnapshots(systemSettings: ICoreSystemSettings | undefined) {
183+
if (systemSettings?.cron?.storeRundownSnapshots?.enabled) {
184+
const filter = systemSettings.cron.storeRundownSnapshots.rundownNames?.length
185+
? { name: { $in: systemSettings.cron.storeRundownSnapshots.rundownNames } }
183186
: {}
184187

185188
const playlists = await RundownPlaylists.findFetchAsync(filter)

0 commit comments

Comments
 (0)