Skip to content

Commit

Permalink
feat: configure Core system/studio settings via blueprints
Browse files Browse the repository at this point in the history
  • Loading branch information
Julusian committed Dec 10, 2024
1 parent c4365ad commit ef14c8f
Show file tree
Hide file tree
Showing 116 changed files with 2,469 additions and 1,447 deletions.
4 changes: 2 additions & 2 deletions meteor/__mocks__/defaultCollectionObjects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,15 +105,15 @@ 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,
allowHold: false,
allowPieceDirectPlay: false,
enableBuckets: false,
},
}),
_rundownVersionHash: '',
routeSetsWithOverrides: wrapDefaultObject({}),
routeSetExclusivityGroupsWithOverrides: wrapDefaultObject({}),
Expand Down
19 changes: 19 additions & 0 deletions meteor/__mocks__/helpers/database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,25 @@ export async function setupMockCore(doc?: Partial<ICoreSystem>): Promise<ICoreSy
version: '0.0.0',
previousVersion: '0.0.0',
serviceMessages: {},
settingsWithOverrides: wrapDefaultObject({
cron: {
casparCGRestart: {
enabled: true,
},
storeRundownSnapshots: {
enabled: false,
},
},
support: {
message: '',
},
evaluationsMessage: {
enabled: false,
heading: '',
message: '',
},
}),
lastBlueprintConfig: undefined,
}
const coreSystem = _.extend(defaultCore, doc)
await CoreSystem.removeAsync(SYSTEM_ID)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
} from '@sofie-automation/meteor-lib/dist/collections/CoreSystem'
import { CoreSystem } from '../../../collections'
import { SupressLogMessages } from '../../../../__mocks__/suppressLogging'
import { wrapDefaultObject } from '@sofie-automation/corelib/dist/settings/objectWithOverrides'

function convertExternalToServiceMessage(message: ExternalServiceMessage): ServiceMessage {
return {
Expand Down Expand Up @@ -42,6 +43,8 @@ const fakeCoreSystem: ICoreSystem = {
version: '3',
previousVersion: null,
serviceMessages: {},
settingsWithOverrides: wrapDefaultObject({} as any),
lastBlueprintConfig: undefined,
}

describe('Service messages internal API', () => {
Expand Down
38 changes: 20 additions & 18 deletions meteor/server/__tests__/cronjobs.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import '../../__mocks__/_extendJest'
import { testInFiber, runAllTimers, beforeAllInFiber, waitUntil } from '../../__mocks__/helpers/jest'
import { MeteorMock } from '../../__mocks__/meteor'
import { logger } from '../logging'
import { getRandomId, getRandomString, protectString } from '../lib/tempLib'
import { getRandomId, getRandomString, literal, protectString } from '../lib/tempLib'
import { SnapshotType } from '@sofie-automation/meteor-lib/dist/collections/Snapshots'
import { IBlueprintPieceType, PieceLifespan, StatusCode, TSR } from '@sofie-automation/blueprints-integration'
import {
Expand Down Expand Up @@ -64,26 +64,36 @@ import {
import { DBSegment } from '@sofie-automation/corelib/dist/dataModel/Segment'
import { Settings } from '../Settings'
import { SofieIngestCacheType } from '@sofie-automation/corelib/dist/dataModel/SofieIngestDataCache'
import { ObjectOverrideSetOp } from '@sofie-automation/corelib/dist/settings/objectWithOverrides'

describe('cronjobs', () => {
let env: DefaultEnvironment
let rundownId: RundownId

beforeAllInFiber(async () => {
env = await setupDefaultStudioEnvironment()

const o = await setupDefaultRundownPlaylist(env)
rundownId = o.rundownId

async function setCasparCGCronEnabled(enabled: boolean) {
await CoreSystem.updateAsync(
{},
{
$set: {
'cron.casparCGRestart.enabled': true,
// This is a little bit of a hack, as it will result in duplicate ops, but it's fine for unit tests
$push: {
'settingsWithOverrides.overrides': literal<ObjectOverrideSetOp>({
op: 'set',
path: 'cron.casparCGRestart.enabled',
value: enabled,
}),
},
},
{ multi: true }
)
}

beforeAllInFiber(async () => {
env = await setupDefaultStudioEnvironment()

const o = await setupDefaultRundownPlaylist(env)
rundownId = o.rundownId

await setCasparCGCronEnabled(true)

jest.useFakeTimers()
// set time to 2020/07/19 00:00 Local Time
Expand Down Expand Up @@ -589,15 +599,7 @@ describe('cronjobs', () => {
})
testInFiber('Does not attempt to restart CasparCG when job is disabled', async () => {
await createMockPlayoutGatewayAndDevices(Date.now()) // Some time after the threshold
await CoreSystem.updateAsync(
{},
{
$set: {
'cron.casparCGRestart.enabled': false,
},
},
{ multi: true }
)
await setCasparCGCronEnabled(false)
;(logger.info as jest.Mock).mockClear()
// set time to 2020/07/{date} 04:05 Local Time, should be more than 24 hours after 2020/07/19 00:00 UTC
mockCurrentTime = new Date(2020, 6, date++, 4, 5, 0).getTime()
Expand Down
4 changes: 3 additions & 1 deletion meteor/server/api/evaluations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand All @@ -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

Check warning on line 37 in meteor/server/api/evaluations.ts

View check run for this annotation

Codecov / codecov/patch

meteor/server/api/evaluations.ts#L37

Added line #L37 was not covered by tests

const webhookUrls = _.compact((studio.settings.slackEvaluationUrls || '').split(','))
const webhookUrls = _.compact((studioSettings.slackEvaluationUrls || '').split(','))

Check warning on line 39 in meteor/server/api/evaluations.ts

View check run for this annotation

Codecov / codecov/patch

meteor/server/api/evaluations.ts#L39

Added line #L39 was not covered by tests

if (webhookUrls.length) {
// Only send notes if not everything is OK
Expand Down
10 changes: 7 additions & 3 deletions meteor/server/api/rest/v1/typeConversion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,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'

Check warning on line 56 in meteor/server/api/rest/v1/typeConversion.ts

View check run for this annotation

Codecov / codecov/patch

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

Added line #L56 was not covered by tests

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

const studioSettings = studioSettingsFrom(apiStudio.settings)

Check warning on line 311 in meteor/server/api/rest/v1/typeConversion.ts

View check run for this annotation

Codecov / codecov/patch

meteor/server/api/rest/v1/typeConversion.ts#L310-L311

Added lines #L310 - L311 were not covered by tests
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),

Check warning on line 320 in meteor/server/api/rest/v1/typeConversion.ts

View check run for this annotation

Codecov / codecov/patch

meteor/server/api/rest/v1/typeConversion.ts#L318-L320

Added lines #L318 - L320 were not covered by tests
supportedShowStyleBase: apiStudio.supportedShowStyleBase?.map((id) => protectString<ShowStyleBaseId>(id)) ?? [],
organizationId: null,
mappingsWithOverrides: wrapDefaultObject({}),
Expand All @@ -334,7 +338,7 @@ export async function studioFrom(apiStudio: APIStudio, existingId?: StudioId): P
}

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

Check warning on line 341 in meteor/server/api/rest/v1/typeConversion.ts

View check run for this annotation

Codecov / codecov/patch

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

Added line #L341 was not covered by tests

return {
name: studio.name,
Expand Down
4 changes: 2 additions & 2 deletions meteor/server/api/studio/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,14 +44,14 @@ export async function insertStudioInner(organizationId: OrganizationId | null, n
supportedShowStyleBase: [],
blueprintConfigWithOverrides: wrapDefaultObject({}),
// testToolsConfig?: ITestToolsConfig
settings: {
settingsWithOverrides: wrapDefaultObject({

Check warning on line 47 in meteor/server/api/studio/api.ts

View check run for this annotation

Codecov / codecov/patch

meteor/server/api/studio/api.ts#L47

Added line #L47 was not covered by tests
frameRate: 25,
mediaPreviewsUrl: '',
minimumTakeSpan: DEFAULT_MINIMUM_TAKE_SPAN,
allowHold: false,
allowPieceDirectPlay: false,
enableBuckets: true,
},
}),

Check warning on line 54 in meteor/server/api/studio/api.ts

View check run for this annotation

Codecov / codecov/patch

meteor/server/api/studio/api.ts#L54

Added line #L54 was not covered by tests
_rundownVersionHash: '',
routeSetsWithOverrides: wrapDefaultObject({}),
routeSetExclusivityGroupsWithOverrides: wrapDefaultObject({}),
Expand Down
5 changes: 2 additions & 3 deletions meteor/server/collections/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,14 +67,13 @@ export const CoreSystem = createAsyncOnlyMongoCollection<ICoreSystem>(Collection
if (!access.update) return logNotAllowed('CoreSystem', access.reason)

return allowOnlyFields(doc, fields, [
'support',
'systemInfo',
'name',
'logLevel',
'apm',
'cron',
'logo',
'evaluations',
'blueprintId',
'settingsWithOverrides',

Check warning on line 76 in meteor/server/collections/index.ts

View check run for this annotation

Codecov / codecov/patch

meteor/server/collections/index.ts#L75-L76

Added lines #L75 - L76 were not covered by tests
])
},
})
Expand Down
25 changes: 20 additions & 5 deletions meteor/server/coreSystem/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,14 @@ import { getEnvLogLevel, logger, LogLevel, setLogLevel } from '../logging'
const PackageInfo = require('../../package.json')
import Agent from 'meteor/julusian:meteor-elastic-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 }

Expand Down Expand Up @@ -60,11 +61,25 @@ async function initializeCoreSystem() {
enabled: false,
transactionSampleRate: -1,
},
cron: {
casparCGRestart: {
enabled: true,
settingsWithOverrides: wrapDefaultObject<ICoreSystemSettings>({
cron: {
casparCGRestart: {
enabled: true,
},
storeRundownSnapshots: {
enabled: false,
},
},
},
support: {
message: '',
},
evaluationsMessage: {
enabled: false,
heading: '',
message: '',
},
}),
lastBlueprintConfig: undefined,
})

if (!isRunningInJest()) {
Expand Down
21 changes: 12 additions & 9 deletions meteor/server/cronjobs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -49,15 +50,17 @@ export async function nightlyCronjobInner(): Promise<void> {
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)}`)
}),
])
Expand All @@ -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<Promise<any>> = []
Expand Down Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion meteor/server/logo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ logoRouter.get('/', async (ctx) => {
const logo = core?.logo ?? SofieLogo.Default

const paths: Record<SofieLogo, string> = {
[SofieLogo.Default]: '/images/sofie-logo.svg',
[SofieLogo.Default]: '/images/sofie-logo-default.svg',

Check warning on line 16 in meteor/server/logo.ts

View check run for this annotation

Codecov / codecov/patch

meteor/server/logo.ts#L16

Added line #L16 was not covered by tests
[SofieLogo.Pride]: '/images/sofie-logo-pride.svg',
[SofieLogo.Norway]: '/images/sofie-logo-norway.svg',
[SofieLogo.Christmas]: '/images/sofie-logo-christmas.svg',
Expand Down
Loading

0 comments on commit ef14c8f

Please sign in to comment.