Skip to content

Commit 17ccacf

Browse files
anteeekIvan MaretićJulusian
authored
feat: allow returning error from adlib actions in blueprints (Sofie-Automation#1638)
Co-authored-by: Ivan Maretić <i.maretic@evs.com> Co-authored-by: Julian Waller <git@julusian.co.uk>
1 parent 59aff41 commit 17ccacf

6 files changed

Lines changed: 41 additions & 22 deletions

File tree

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -989,7 +989,9 @@ export function registerRoutes(registerRoute: APIRegisterHook<PlaylistsRestAPI>)
989989
'post',
990990
'/playlists/:playlistId/execute-adlib',
991991
new Map([
992+
[400, []],
992993
[404, [UserErrorMessage.RundownPlaylistNotFound]],
994+
[409, [UserErrorMessage.ValidationFailed]],
993995
[412, [UserErrorMessage.InactiveRundown, UserErrorMessage.NoCurrentPart, UserErrorMessage.AdlibNotFound]],
994996
]),
995997
playlistsAPIFactory,
@@ -1025,7 +1027,9 @@ export function registerRoutes(registerRoute: APIRegisterHook<PlaylistsRestAPI>)
10251027
'post',
10261028
'/playlists/:playlistId/execute-bucket-adlib',
10271029
new Map([
1030+
[400, []],
10281031
[404, [UserErrorMessage.RundownPlaylistNotFound]],
1032+
[409, [UserErrorMessage.ValidationFailed]],
10291033
[
10301034
412,
10311035
[

packages/blueprints-integration/src/api/showStyle.ts

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ import type { IBlueprintShowStyleVariant, IOutputLayer, ISourceLayer } from '../
4141
import type { SourceLayerType } from '../content.js'
4242
import type { TSR, OnGenerateTimelineObj, TimelineObjectCoreExt } from '../timeline.js'
4343
import type { IBlueprintConfig } from '../common.js'
44-
import type { ReadonlyDeep } from 'type-fest'
44+
import type { JsonValue, ReadonlyDeep } from 'type-fest'
4545
import type { JSONSchema } from '@sofie-automation/shared-lib/dist/lib/JSONSchemaTypes'
4646
import type { JSONBlob } from '@sofie-automation/shared-lib/dist/lib/JSONBlob'
4747
import type { BlueprintConfigCoreConfig, BlueprintManifestBase, BlueprintManifestType, IConfigMessage } from './base.js'
@@ -51,6 +51,7 @@ import type { ABResolverConfiguration } from '../abPlayback.js'
5151
import type { SofieIngestSegment } from '../ingest-types.js'
5252
import { PackageStatusMessage } from '@sofie-automation/shared-lib/dist/packageStatusMessages'
5353
import { BlueprintPlayoutPersistentStore } from '../context/playoutStore.js'
54+
import type { ITranslatableMessage } from '../translations.js'
5455

5556
export { PackageStatusMessage }
5657

@@ -141,12 +142,7 @@ export interface ShowStyleBlueprintManifest<
141142
privateData: unknown | undefined,
142143
publicData: unknown | undefined,
143144
actionOptions: { [key: string]: any } | undefined
144-
) => Promise<{
145-
/**
146-
* To be set if the payload sent by the user is invalid. When set, a 409 `ValidationFailed` message will be displayed to the User.
147-
*/
148-
validationErrors: any
149-
} | void>
145+
) => Promise<BlueprintExecuteActionResult | void>
150146

151147
/** Generate adlib piece from ingest data */
152148
getAdlibItem?: (
@@ -381,3 +377,20 @@ export interface IShowStyleVariantConfigPreset<TConfig = IBlueprintConfig> {
381377

382378
config: Partial<TConfig>
383379
}
380+
381+
export interface BlueprintExecuteActionResult {
382+
/**
383+
* User friendly error message to return to the caller if the action was rejected.
384+
*/
385+
message: ITranslatableMessage
386+
387+
/**
388+
* HTTP error code for the action. If set, must be in the range 400-499
389+
*/
390+
errorCode?: number
391+
392+
/**
393+
* Additional details payload to provide to the caller
394+
*/
395+
details?: JsonValue
396+
}

packages/corelib/src/__tests__/lib.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ describe('Lib', () => {
146146
errorCode: 42,
147147
key: UserErrorMessage.ValidationFailed,
148148
userMessage: {
149-
key: 'Validation failed!',
149+
key: 'Validation failed! {{message}}',
150150
args: {},
151151
},
152152
rawError: {

packages/corelib/src/error.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ const UserErrorMessagesTranslations: { [key in UserErrorMessage]: string } = {
116116
[UserErrorMessage.DeviceAlreadyAttachedToStudio]: t(`Device is already attached to another studio.`),
117117
[UserErrorMessage.ShowStyleBaseNotFound]: t(`ShowStyleBase not found!`),
118118
[UserErrorMessage.NoMigrationsToApply]: t(`No migrations to apply`),
119-
[UserErrorMessage.ValidationFailed]: t('Validation failed!'),
119+
[UserErrorMessage.ValidationFailed]: t('Validation failed! {{message}}'),
120120
[UserErrorMessage.AdlibTestingNotAllowed]: t(`Rehearsal mode is not allowed`),
121121
[UserErrorMessage.AdlibTestingAlreadyActive]: t(`Rehearsal mode is already active`),
122122
[UserErrorMessage.BucketNotFound]: t(`Bucket not found!`),

packages/corelib/src/worker/studio.ts

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -322,11 +322,6 @@ export interface ExecuteBucketAdLibOrActionProps extends RundownPlayoutPropsBase
322322
externalId: string
323323
triggerMode?: string
324324
}
325-
export interface ExecuteBucketAdLibOrActionProps extends RundownPlayoutPropsBase {
326-
bucketId: BucketId
327-
externalId: string
328-
triggerMode?: string
329-
}
330325
export interface ExecuteActionResult {
331326
queuedPartInstanceId?: PartInstanceId
332327
taken?: boolean

packages/job-worker/src/playout/adlibAction.ts

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import { PlayoutModel, PlayoutModelPreInit } from './model/PlayoutModel.js'
1414
import { runJobWithPlaylistLock } from './lock.js'
1515
import { updateTimeline } from './timeline/generate.js'
1616
import { performTakeToNextedPart } from './take.js'
17-
import { ActionUserData } from '@sofie-automation/blueprints-integration'
17+
import { ActionUserData, BlueprintExecuteActionResult } from '@sofie-automation/blueprints-integration'
1818
import {
1919
DBRundownPlaylist,
2020
SelectedPartInstance,
@@ -38,6 +38,7 @@ import type { INoteBase } from '@sofie-automation/corelib/dist/dataModel/Notes'
3838
import { NotificationsModelHelper } from '../notifications/NotificationsModelHelper.js'
3939
import type { INotificationsModel } from '../notifications/NotificationsModel.js'
4040
import { PersistentPlayoutStateStore } from '../blueprints/context/services/PersistantStateStore.js'
41+
import { interpollateTranslation } from '@sofie-automation/corelib/dist/TranslatableMessage'
4142

4243
/**
4344
* Execute an AdLib Action
@@ -249,7 +250,7 @@ export async function executeActionInner(
249250
)} (${actionParameters.triggerMode})`
250251
)
251252

252-
let result: ExecuteActionResult | void
253+
let result: BlueprintExecuteActionResult | void
253254

254255
try {
255256
const blueprintPersistentState = new PersistentPlayoutStateStore(
@@ -274,15 +275,21 @@ export async function executeActionInner(
274275
throw UserError.fromUnknown(err)
275276
}
276277

277-
if (result && result.validationErrors) {
278+
// If the blueprint returned an error, abort the action and throw the error
279+
if (result && typeof result === 'object' && result.message) {
280+
const messageStr = interpollateTranslation(result.message.key, result.message.args)
281+
const statusCode = Number.isFinite(result.errorCode)
282+
? Math.max(Math.min(Math.round(result.errorCode as number), 499), 400)
283+
: 409
278284
throw UserError.from(
279-
new Error(
280-
`AdLib Action "${actionParameters.actionId}" validation failed: ${JSON.stringify(result.validationErrors)}`
281-
),
285+
new Error(messageStr),
282286
UserErrorMessage.ValidationFailed,
283-
undefined,
284-
409
287+
{ message: messageStr, rawMessage: result.message, details: result.details },
288+
statusCode
285289
)
290+
} else if (result !== undefined) {
291+
// Unexpected return value — does not match the BlueprintExecuteActionResult shape; treat as success but warn so it can be investigated
292+
logger.warn(`executeAction returned an unexpected value: ${JSON.stringify(result)}`)
286293
}
287294

288295
// Store any notes generated by the action

0 commit comments

Comments
 (0)