Skip to content

Commit a241e02

Browse files
committed
Merge remote-tracking branch 'nrk/release52' into bbc-release52
2 parents 20bec45 + cc866ca commit a241e02

File tree

51 files changed

+1307
-213
lines changed

Some content is hidden

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

51 files changed

+1307
-213
lines changed

meteor/CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,14 @@
22

33
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
44

5+
### [1.51.2](https://github.com/nrkno/tv-automation-server-core/compare/v1.51.1...v1.51.2) (2024-11-21)
6+
7+
8+
### Bug Fixes
9+
10+
* Include previousPartInstance in check to orphan segments rather than remove them. ([2c113b5](https://github.com/nrkno/tv-automation-server-core/commit/2c113b58b205198d13f0fc7e2114704311eb915b))
11+
* updatePartInstancesSegmentIds: take into account when multiple segments have been merged into one. ([bdab8c4](https://github.com/nrkno/tv-automation-server-core/commit/bdab8c4e4ee1e67a3568cccc98106bb7f1258673))
12+
513
## [1.51.0-in-testing.3](https://github.com/nrkno/sofie-core/compare/v1.51.0-in-testing.2...v1.51.0-in-testing.3) (2024-09-25)
614

715
## [1.51.0-in-testing.2](https://github.com/nrkno/sofie-core/compare/v1.51.0-in-testing.1...v1.51.0-in-testing.2) (2024-09-24)

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

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import KoaRouter from '@koa/router'
22
import { interpollateTranslation, translateMessage } from '@sofie-automation/corelib/dist/TranslatableMessage'
33
import { UserError, UserErrorMessage } from '@sofie-automation/corelib/dist/error'
4+
import { IConfigMessage, NoteSeverity } from '@sofie-automation/blueprints-integration'
45
import Koa from 'koa'
56
import bodyParser from 'koa-bodyparser'
67
import { Meteor } from 'meteor/meteor'
@@ -92,6 +93,30 @@ function extractErrorDetails(e: unknown): string[] | undefined {
9293
}
9394
}
9495

96+
export const checkValidation = (method: string, configValidationMsgs: IConfigMessage[]): void => {
97+
/**
98+
* Throws if any of the configValidationMsgs indicates that the config has errors.
99+
* Will log any messages with severity WARNING or INFO
100+
*/
101+
const configValidationOK = configValidationMsgs.reduce((acc, msg) => acc && msg.level !== NoteSeverity.ERROR, true)
102+
if (!configValidationOK) {
103+
const details = JSON.stringify(
104+
configValidationMsgs.filter((msg) => msg.level === NoteSeverity.ERROR).map((msg) => msg.message.key),
105+
null,
106+
2
107+
)
108+
logger.error(`${method} failed blueprint config validation with errors: ${details}`)
109+
throw new Meteor.Error(409, `${method} has failed blueprint config validation`, details)
110+
} else {
111+
const details = JSON.stringify(
112+
configValidationMsgs.map((msg) => msg.message.key),
113+
null,
114+
2
115+
)
116+
logger.info(`${method} received messages from bluepring config validation: ${details}`)
117+
}
118+
}
119+
95120
interface APIRequestError {
96121
status: number
97122
message: string

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

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,8 @@ class PlaylistsServerAPI implements PlaylistsRestAPI {
9696
event: string,
9797
rundownPlaylistId: RundownPlaylistId,
9898
adLibId: AdLibActionId | RundownBaselineAdLibActionId | PieceId | BucketAdLibId,
99-
triggerMode?: string | null
99+
triggerMode?: string | null,
100+
adLibOptions?: { [key: string]: any }
100101
): Promise<ClientAPI.ClientResponse<object>> {
101102
const baselineAdLibPiece = RundownBaselineAdLibPieces.findOneAsync(adLibId as PieceId, {
102103
projection: { _id: 1 },
@@ -204,6 +205,7 @@ class PlaylistsServerAPI implements PlaylistsRestAPI {
204205
actionId: adLibActionDoc.actionId,
205206
userData: adLibActionDoc.userData,
206207
triggerMode: triggerMode ?? undefined,
208+
actionOptions: adLibOptions,
207209
}
208210
)
209211
} else {
@@ -576,7 +578,7 @@ export function registerRoutes(registerRoute: APIRegisterHook<PlaylistsRestAPI>)
576578
}
577579
)
578580

579-
registerRoute<{ playlistId: string }, { adLibId: string; actionType?: string }, object>(
581+
registerRoute<{ playlistId: string }, { adLibId: string; actionType?: string; adLibOptions?: any }, object>(
580582
'post',
581583
'/playlists/:playlistId/execute-adlib',
582584
new Map([
@@ -591,12 +593,24 @@ export function registerRoutes(registerRoute: APIRegisterHook<PlaylistsRestAPI>)
591593
)
592594
const actionTypeObj = body
593595
const triggerMode = actionTypeObj ? (actionTypeObj as { actionType: string }).actionType : undefined
594-
logger.info(`API POST: execute-adlib ${rundownPlaylistId} ${adLibId} - triggerMode: ${triggerMode}`)
596+
const adLibOptions = actionTypeObj ? actionTypeObj.adLibOptions : undefined
597+
logger.info(
598+
`API POST: execute-adlib ${rundownPlaylistId} ${adLibId} - actionType: ${triggerMode} - options: ${
599+
adLibOptions ? JSON.stringify(adLibOptions) : 'undefined'
600+
}`
601+
)
595602

596603
check(adLibId, String)
597604
check(rundownPlaylistId, String)
598605

599-
return await serverAPI.executeAdLib(connection, event, rundownPlaylistId, adLibId, triggerMode)
606+
return await serverAPI.executeAdLib(
607+
connection,
608+
event,
609+
rundownPlaylistId,
610+
adLibId,
611+
triggerMode,
612+
adLibOptions
613+
)
600614
}
601615
)
602616

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

Lines changed: 125 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,14 @@ import {
2020
APIShowStyleVariantFrom,
2121
showStyleBaseFrom,
2222
showStyleVariantFrom,
23+
validateAPIBlueprintConfigForShowStyle,
2324
} from './typeConversion'
2425
import { Rundown } from '@sofie-automation/corelib/dist/dataModel/Rundown'
2526
import { DBRundownPlaylist } from '@sofie-automation/corelib/dist/dataModel/RundownPlaylist'
2627
import { runUpgradeForShowStyleBase, validateConfigForShowStyleBase } from '../../../migration/upgrades'
27-
import { NoteSeverity } from '@sofie-automation/blueprints-integration'
2828
import { DBShowStyleVariant } from '@sofie-automation/corelib/dist/dataModel/ShowStyleVariant'
2929
import { assertNever } from '@sofie-automation/corelib/dist/lib'
30+
import { checkValidation } from '.'
3031

3132
class ShowStylesServerAPI implements ShowStylesRestAPI {
3233
async getShowStyleBases(
@@ -42,9 +43,15 @@ class ShowStylesServerAPI implements ShowStylesRestAPI {
4243
async addShowStyleBase(
4344
_connection: Meteor.Connection,
4445
_event: string,
45-
showStyleBase: APIShowStyleBase
46+
apiShowStyleBase: APIShowStyleBase
4647
): Promise<ClientAPI.ClientResponse<string>> {
47-
const showStyle = await showStyleBaseFrom(showStyleBase)
48+
const blueprintConfigValidation = await validateAPIBlueprintConfigForShowStyle(
49+
apiShowStyleBase,
50+
protectString(apiShowStyleBase.blueprintId)
51+
)
52+
checkValidation(`addShowStyleBase`, blueprintConfigValidation)
53+
54+
const showStyle = await showStyleBaseFrom(apiShowStyleBase)
4855
if (!showStyle) throw new Meteor.Error(400, `Invalid ShowStyleBase`)
4956
const showStyleId = showStyle._id
5057
await ShowStyleBases.insertAsync(showStyle)
@@ -60,16 +67,22 @@ class ShowStylesServerAPI implements ShowStylesRestAPI {
6067
const showStyleBase = await ShowStyleBases.findOneAsync(showStyleBaseId)
6168
if (!showStyleBase) throw new Meteor.Error(404, `ShowStyleBase ${showStyleBaseId} does not exist`)
6269

63-
return ClientAPI.responseSuccess(APIShowStyleBaseFrom(showStyleBase))
70+
return ClientAPI.responseSuccess(await APIShowStyleBaseFrom(showStyleBase))
6471
}
6572

6673
async addOrUpdateShowStyleBase(
6774
_connection: Meteor.Connection,
6875
_event: string,
6976
showStyleBaseId: ShowStyleBaseId,
70-
showStyleBase: APIShowStyleBase
77+
apiShowStyleBase: APIShowStyleBase
7178
): Promise<ClientAPI.ClientResponse<void>> {
72-
const showStyle = await showStyleBaseFrom(showStyleBase, showStyleBaseId)
79+
const blueprintConfigValidation = await validateAPIBlueprintConfigForShowStyle(
80+
apiShowStyleBase,
81+
protectString(apiShowStyleBase.blueprintId)
82+
)
83+
checkValidation(`addOrUpdateShowStyleBase ${showStyleBaseId}`, blueprintConfigValidation)
84+
85+
const showStyle = await showStyleBaseFrom(apiShowStyleBase, showStyleBaseId)
7386
if (!showStyle) throw new Meteor.Error(400, `Invalid ShowStyleBase`)
7487

7588
const existingShowStyle = await ShowStyleBases.findOneAsync(showStyleBaseId)
@@ -96,17 +109,73 @@ class ShowStylesServerAPI implements ShowStylesRestAPI {
96109

97110
await ShowStyleBases.upsertAsync(showStyleBaseId, showStyle)
98111

112+
// wait for the upsert to complete before validation and upgrade read from the showStyleBases collection
113+
await new Promise<void>((resolve) => setTimeout(() => resolve(), 200))
114+
99115
const validation = await validateConfigForShowStyleBase(showStyleBaseId)
100-
const validateOK = validation.messages.reduce((acc, msg) => acc && msg.level === NoteSeverity.INFO, true)
101-
if (!validateOK) {
102-
const details = JSON.stringify(
103-
validation.messages.filter((msg) => msg.level < NoteSeverity.INFO).map((msg) => msg.message.key),
104-
null,
105-
2
106-
)
107-
logger.error(`addOrUpdateShowStyleBase failed validation with errors: ${details}`)
108-
throw new Meteor.Error(409, `ShowStyleBase ${showStyleBaseId} has failed validation`, details)
109-
}
116+
checkValidation(`addOrUpdateShowStyleBase ${showStyleBaseId}`, validation.messages)
117+
118+
return ClientAPI.responseSuccess(await runUpgradeForShowStyleBase(showStyleBaseId))
119+
}
120+
121+
async getShowStyleConfig(
122+
_connection: Meteor.Connection,
123+
_event: string,
124+
showStyleBaseId: ShowStyleBaseId
125+
): Promise<ClientAPI.ClientResponse<object>> {
126+
const showStyleBase = await ShowStyleBases.findOneAsync(showStyleBaseId)
127+
if (!showStyleBase) throw new Meteor.Error(404, `ShowStyleBase ${showStyleBaseId} does not exist`)
128+
129+
return ClientAPI.responseSuccess((await APIShowStyleBaseFrom(showStyleBase)).config)
130+
}
131+
132+
async updateShowStyleConfig(
133+
_connection: Meteor.Connection,
134+
_event: string,
135+
showStyleBaseId: ShowStyleBaseId,
136+
config: object
137+
): Promise<ClientAPI.ClientResponse<void>> {
138+
const existingShowStyleBase = await ShowStyleBases.findOneAsync(showStyleBaseId)
139+
if (existingShowStyleBase) {
140+
const rundowns = (await Rundowns.findFetchAsync(
141+
{ showStyleBaseId },
142+
{ projection: { playlistId: 1 } }
143+
)) as Array<Pick<Rundown, 'playlistId'>>
144+
const playlists = (await RundownPlaylists.findFetchAsync(
145+
{ _id: { $in: rundowns.map((r) => r.playlistId) } },
146+
{
147+
projection: {
148+
activationId: 1,
149+
},
150+
}
151+
)) as Array<Pick<DBRundownPlaylist, 'activationId'>>
152+
if (playlists.some((playlist) => playlist.activationId !== undefined)) {
153+
throw new Meteor.Error(
154+
412,
155+
`Cannot update ShowStyleBase ${showStyleBaseId} as it is in use by an active Playlist`
156+
)
157+
}
158+
} else throw new Meteor.Error(404, `ShowStyleBase ${showStyleBaseId} not found`)
159+
160+
const apiShowStyleBase = await APIShowStyleBaseFrom(existingShowStyleBase)
161+
apiShowStyleBase.config = config
162+
163+
const blueprintConfigValidation = await validateAPIBlueprintConfigForShowStyle(
164+
apiShowStyleBase,
165+
protectString(apiShowStyleBase.blueprintId)
166+
)
167+
checkValidation(`updateShowStyleConfig ${showStyleBaseId}`, blueprintConfigValidation)
168+
169+
const showStyle = await showStyleBaseFrom(apiShowStyleBase, showStyleBaseId)
170+
if (!showStyle) throw new Meteor.Error(400, `Invalid ShowStyleBase`)
171+
172+
await ShowStyleBases.upsertAsync(showStyleBaseId, showStyle)
173+
174+
// wait for the upsert to complete before validation and upgrade read from the showStyleBases collection
175+
await new Promise<void>((resolve) => setTimeout(() => resolve(), 200))
176+
177+
const validation = await validateConfigForShowStyleBase(showStyleBaseId)
178+
checkValidation(`updateShowStyleConfig ${showStyleBaseId}`, validation.messages)
110179

111180
return ClientAPI.responseSuccess(await runUpgradeForShowStyleBase(showStyleBaseId))
112181
}
@@ -185,20 +254,26 @@ class ShowStylesServerAPI implements ShowStylesRestAPI {
185254
const variant = await ShowStyleVariants.findOneAsync(showStyleVariantId)
186255
if (!variant) throw new Meteor.Error(404, `ShowStyleVariant ${showStyleVariantId} not found`)
187256

188-
return ClientAPI.responseSuccess(APIShowStyleVariantFrom(variant))
257+
return ClientAPI.responseSuccess(await APIShowStyleVariantFrom(showStyleBase, variant))
189258
}
190259

191260
async addOrUpdateShowStyleVariant(
192261
_connection: Meteor.Connection,
193262
_event: string,
194263
showStyleBaseId: ShowStyleBaseId,
195264
showStyleVariantId: ShowStyleVariantId,
196-
showStyleVariant: APIShowStyleVariant
265+
apiShowStyleVariant: APIShowStyleVariant
197266
): Promise<ClientAPI.ClientResponse<void>> {
198267
const showStyleBase = await ShowStyleBases.findOneAsync(showStyleBaseId)
199268
if (!showStyleBase) throw new Meteor.Error(404, `ShowStyleBase ${showStyleBaseId} does not exist`)
200269

201-
const showStyle = showStyleVariantFrom(showStyleVariant, showStyleVariantId)
270+
const blueprintConfigValidation = await validateAPIBlueprintConfigForShowStyle(
271+
apiShowStyleVariant,
272+
showStyleBase.blueprintId
273+
)
274+
checkValidation(`addOrUpdateShowStyleVariant ${showStyleVariantId}`, blueprintConfigValidation)
275+
276+
const showStyle = showStyleVariantFrom(apiShowStyleVariant, showStyleVariantId)
202277
if (!showStyle) throw new Meteor.Error(400, `Invalid ShowStyleVariant`)
203278

204279
const existingShowStyle = await ShowStyleVariants.findOneAsync(showStyleVariantId)
@@ -335,6 +410,37 @@ export function registerRoutes(registerRoute: APIRegisterHook<ShowStylesRestAPI>
335410
}
336411
)
337412

413+
registerRoute<{ showStyleBaseId: string }, never, object>(
414+
'get',
415+
'/showstyles/:showStyleBaseId/config',
416+
new Map([[404, [UserErrorMessage.ShowStyleBaseNotFound]]]),
417+
showStylesAPIFactory,
418+
async (serverAPI, connection, event, params, _) => {
419+
const showStyleBaseId = protectString<ShowStyleBaseId>(params.showStyleBaseId)
420+
logger.info(`API GET: ShowStyleBase config ${showStyleBaseId}`)
421+
422+
check(showStyleBaseId, String)
423+
return await serverAPI.getShowStyleConfig(connection, event, showStyleBaseId)
424+
}
425+
)
426+
427+
registerRoute<{ showStyleBaseId: string }, object, void>(
428+
'put',
429+
'/showstyles/:showStyleBaseId/config',
430+
new Map([
431+
[404, [UserErrorMessage.ShowStyleBaseNotFound]],
432+
[409, [UserErrorMessage.ValidationFailed]],
433+
]),
434+
showStylesAPIFactory,
435+
async (serverAPI, connection, event, params, body) => {
436+
const showStyleBaseId = protectString<ShowStyleBaseId>(params.showStyleBaseId)
437+
logger.info(`API PUT: Update ShowStyleBase config ${showStyleBaseId}`)
438+
439+
check(showStyleBaseId, String)
440+
return await serverAPI.updateShowStyleConfig(connection, event, showStyleBaseId, body)
441+
}
442+
)
443+
338444
registerRoute<{ showStyleBaseId: string }, never, void>(
339445
'delete',
340446
'/showstyles/:showStyleBaseId',

0 commit comments

Comments
 (0)