Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions meteor/server/api/rest/v1/playlists.ts
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ class PlaylistsServerAPI implements PlaylistsRestAPI {
)
)

return ServerClientAPI.runUserActionInLogForPlaylistOnWorker(
const result = await ServerClientAPI.runUserActionInLogForPlaylistOnWorker(
this.context.getMethodContext(connection),
event,
getCurrentTime(),
Expand All @@ -220,6 +220,8 @@ class PlaylistsServerAPI implements PlaylistsRestAPI {
actionOptions: adLibOptions,
}
)
if (ClientAPI.isClientResponseError(result)) return result
return ClientAPI.responseSuccess(result.result ?? {})
} else {
return ClientAPI.responseError(
UserError.from(new Error(`No adLib with Id ${adLibId}`), UserErrorMessage.AdlibNotFound, undefined, 412)
Expand Down Expand Up @@ -268,7 +270,7 @@ class PlaylistsServerAPI implements PlaylistsRestAPI {
)
}

return ServerClientAPI.runUserActionInLogForPlaylistOnWorker(
const result = await ServerClientAPI.runUserActionInLogForPlaylistOnWorker(
this.context.getMethodContext(connection),
event,
getCurrentTime(),
Expand All @@ -286,6 +288,8 @@ class PlaylistsServerAPI implements PlaylistsRestAPI {
triggerMode: triggerMode ?? undefined,
}
)
if (ClientAPI.isClientResponseError(result)) return result
return ClientAPI.responseSuccess(result.result ?? {})
}
async moveNextPart(
connection: Meteor.Connection,
Expand Down Expand Up @@ -602,6 +606,7 @@ export function registerRoutes(registerRoute: APIRegisterHook<PlaylistsRestAPI>)
'post',
'/playlists/:playlistId/execute-adlib',
new Map([
[400, []],
[404, [UserErrorMessage.RundownPlaylistNotFound]],
[412, [UserErrorMessage.InactiveRundown, UserErrorMessage.NoCurrentPart, UserErrorMessage.AdlibNotFound]],
]),
Expand Down Expand Up @@ -638,6 +643,7 @@ export function registerRoutes(registerRoute: APIRegisterHook<PlaylistsRestAPI>)
'post',
'/playlists/:playlistId/execute-bucket-adlib',
new Map([
[400, []],
[404, [UserErrorMessage.RundownPlaylistNotFound]],
[
412,
Expand Down
2 changes: 1 addition & 1 deletion packages/blueprints-integration/src/api/showStyle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ export interface ShowStyleBlueprintManifest<TRawConfig = IBlueprintConfig, TProc
privateData: unknown | undefined,
publicData: unknown | undefined,
actionOptions: { [key: string]: any } | undefined
) => Promise<void>
) => Promise<{ validationErrors: any } | void>

/** Generate adlib piece from ingest data */
getAdlibItem?: (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,13 @@ export interface IActionExecutionContext
/** Insert a queued part to follow the taken part */
queuePartAfterTake(part: IBlueprintPart, pieces: IBlueprintPiece[]): void

/**
* Reject the action request with an error message.
* This will cause the API to return a 400 error response.
* @param message Error message to return to the client
*/
rejectRequest(message: string): void

/** Misc actions */
// updateAction(newManifest: Pick<IBlueprintAdLibActionManifest, 'description' | 'payload'>): void // only updates itself. to allow for the next one to do something different
// executePeripheralDeviceAction(deviceId: string, functionName: string, args: any[]): Promise<any>
Expand Down
2 changes: 1 addition & 1 deletion packages/corelib/src/error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ const UserErrorMessagesTranslations: { [key in UserErrorMessage]: string } = {
[UserErrorMessage.DeviceAlreadyAttachedToStudio]: t(`Device is already attached to another studio.`),
[UserErrorMessage.ShowStyleBaseNotFound]: t(`ShowStyleBase not found!`),
[UserErrorMessage.NoMigrationsToApply]: t(`No migrations to apply`),
[UserErrorMessage.ValidationFailed]: t('Validation failed!'),
[UserErrorMessage.ValidationFailed]: t('Validation failed! {{message}}'),
[UserErrorMessage.AdlibTestingNotAllowed]: t(`Rehearsal mode is not allowed`),
[UserErrorMessage.AdlibTestingAlreadyActive]: t(`Rehearsal mode is already active`),
[UserErrorMessage.BucketNotFound]: t(`Bucket not found!`),
Expand Down
6 changes: 1 addition & 5 deletions packages/corelib/src/worker/studio.ts
Original file line number Diff line number Diff line change
Expand Up @@ -277,14 +277,10 @@ export interface ExecuteBucketAdLibOrActionProps extends RundownPlayoutPropsBase
externalId: string
triggerMode?: string
}
export interface ExecuteBucketAdLibOrActionProps extends RundownPlayoutPropsBase {
bucketId: BucketId
externalId: string
triggerMode?: string
}
export interface ExecuteActionResult {
queuedPartInstanceId?: PartInstanceId
taken?: boolean
validationErrors?: any
}
export interface TakeNextPartProps extends RundownPlayoutPropsBase {
fromPartInstanceId: PartInstanceId | null
Expand Down
9 changes: 9 additions & 0 deletions packages/job-worker/src/blueprints/context/adlibActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,11 @@ export class ActionExecutionContext extends ShowStyleUserContext implements IAct

public partToQueueAfterTake: QueueablePartAndPieces | undefined

/**
* If set, the blueprint has rejected the request with an error message
*/
public requestError: string | undefined

public get quickLoopInfo(): BlueprintQuickLookInfo | null {
return this.partAndPieceInstanceService.quickLoopInfo
}
Expand Down Expand Up @@ -280,4 +285,8 @@ export class ActionExecutionContext extends ShowStyleUserContext implements IAct
getCurrentTime(): number {
return getCurrentTime()
}

rejectRequest(message: string): void {
this.requestError = message
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -200,5 +200,28 @@ describe('Playout API', () => {

expect(takeNextPartMock).toHaveBeenCalledTimes(0)
})

test('rejectRequest returns error message', async () => {
const errorMessage = 'This action is not allowed right now'

context.updateShowStyleBlueprint({
executeAction: async (context) => {
context.rejectRequest(errorMessage)
},
})

const actionDocId: AdLibActionId = protectString('action-id')
const actionId = 'some-action'
const userData = { blobby: true }
const result = await handleExecuteAdlibAction(context, {
playlistId,
actionDocId,
actionId,
userData,
})

expect(result.errorMessage).toBe(errorMessage)
expect(result.taken).toBeFalsy()
})
})
})
15 changes: 14 additions & 1 deletion packages/job-worker/src/playout/adlibAction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -240,10 +240,12 @@ export async function executeActionInner(
)} (${actionParameters.triggerMode})`
)

let result: ExecuteActionResult | void

try {
const blueprintPersistentState = new PersistentPlayoutStateStore(playoutModel.playlist.previousPersistentState)

await blueprint.blueprint.executeAction(
result = await blueprint.blueprint.executeAction(
actionContext,
blueprintPersistentState,
actionParameters.actionId,
Expand All @@ -262,6 +264,17 @@ export async function executeActionInner(
throw UserError.fromUnknown(err)
}

const validationErrors = result?.validationErrors ?? actionContext.requestError
if (validationErrors) {
const message = typeof validationErrors === 'string' ? validationErrors : JSON.stringify(validationErrors)
throw UserError.from(
new Error(`AdLib Action "${actionParameters.actionId}" validation failed: ${message}`),
UserErrorMessage.ValidationFailed,
{ message },
409
)
}

// Store any notes generated by the action
storeNotificationsForCategory(
playoutModel,
Expand Down
Loading