Skip to content

feat: Resolve device action errors using blueprint deviceActionMessages#1743

Open
rjmunro wants to merge 2 commits into
Sofie-Automation:mainfrom
bbc:rjmunro/improve-device-error-notifications
Open

feat: Resolve device action errors using blueprint deviceActionMessages#1743
rjmunro wants to merge 2 commits into
Sofie-Automation:mainfrom
bbc:rjmunro/improve-device-error-notifications

Conversation

@rjmunro
Copy link
Copy Markdown
Contributor

@rjmunro rjmunro commented May 11, 2026

About the Contributor

This pull request is posted on behalf of the BBC.

Type of Contribution

Feature

Current Behavior

Device action errors (e.g. HTTP Send failures, CasparCG restart failures) are returned to the UI as raw strings from the Playout Gateway. Blueprint authors have no way to customise these messages.

New Behavior

  • deviceActionMessages added to StudioBlueprintManifest — blueprint authors can provide custom message templates (with {{variable}} interpolation) keyed by action error code string
  • resolveActionResult() resolves structured action errors server-side using the studio blueprint, reusing the StatusMessageResolver infrastructure from PR feat: Status message customisation #1604
  • Wired into executePeripheralDeviceAction so customised messages reach the UI automatically

Example blueprint configuration:

import { HttpSendActionErrorCode } from 'timeline-state-resolver-types'

deviceActionMessages: {
  [HttpSendActionErrorCode.REQUEST_FAILED]: 'HTTP action failed - could not reach {{url}}: {{errorMessage}}',
  [HttpSendActionErrorCode.MISSING_URL]: 'HTTP action not configured - no URL set in the timeline object',
}

This is the companion to PR #1604 which covered device status errors — this PR covers device action execution failures.

Testing Instructions

Requires the TSR stacked PR (on #418) for structured error codes from devices.

  1. Configure deviceActionMessages in a studio blueprint with a custom message for HttpSendActionErrorCode.REQUEST_FAILED
  2. Trigger an HTTP Send action with an unreachable URL
  3. Verify the custom message (with interpolated {{url}} and {{errorMessage}}) appears in the UI notification

Other Information

Stacked on PR #1604 (feat: Status message customisation). The base branch for this PR is rjmunro/error-message-customisation.

Status

  • PR is ready to be reviewed.
  • The functionality has been tested by the author.
  • Relevant unit tests has been added / updated.
  • Relevant documentation (code comments, system documentation) has been added / updated.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 11, 2026

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c50f5b5a-f200-4fdf-9132-b0903836e224

📥 Commits

Reviewing files that changed from the base of the PR and between d4a6368 and 38283b2.

⛔ Files ignored due to path filters (2)
  • meteor/yarn.lock is excluded by !**/yarn.lock, !**/*.lock
  • packages/yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (1)
  • packages/shared-lib/package.json
✅ Files skipped from review due to trivial changes (1)
  • packages/shared-lib/package.json

Walkthrough

Blueprints can now define per-action error message templates in deviceActionMessages. Action execution results from peripheral devices are passed through resolveActionResult, which applies blueprint-defined templates (or suppresses messages) before returning the final ActionExecutionResult.

Changes

Device action error message resolution

Layer / File(s) Summary
Blueprint schema for device action messages
packages/blueprints-integration/src/api/studio.ts
StudioBlueprintManifest adds optional deviceActionMessages field supporting per-action-error-code keys mapped to string templates, DeviceStatusMessageFunction, or undefined to suppress messages.
Action result resolution with message templating
meteor/server/api/peripheralDevice.ts
Adds resolveActionResult(deviceId, result) which, for non-Ok TSR results, looks up the device's studio blueprint, resolves a matching deviceActionMessages entry (interpolating templates or invoking message functions), and returns an updated ActionExecutionResult or the original result on no-match or error.
Client API action invocation with result resolution
meteor/server/api/client.ts
ServerClientAPIClass.callPeripheralDeviceAction now awaits the underlying call, captures its ActionExecutionResult, and returns resolveActionResult(deviceId, result) so blueprint message resolution is applied at the API boundary.
Dependency bump (unrelated)
packages/shared-lib/package.json
Bumps timeline-state-resolver-types nightly version. (No code changes tied to device action messages.)

Sequence Diagram

sequenceDiagram
  participant Client
  participant CallPeripheralDeviceAction
  participant CallPeripheralDeviceFunctionOrAction
  participant ResolveActionResult
  participant PeripheralDeviceRepo
  participant StudioRepo
  participant ResultClient

  Client->>CallPeripheralDeviceAction: callPeripheralDeviceAction(deviceId, action)
  CallPeripheralDeviceAction->>CallPeripheralDeviceFunctionOrAction: await callPeripheralDeviceFunctionOrAction(deviceId, action)
  CallPeripheralDeviceFunctionOrAction-->>CallPeripheralDeviceAction: ActionExecutionResult
  CallPeripheralDeviceAction->>ResolveActionResult: resolveActionResult(deviceId, result)
  ResolveActionResult->>PeripheralDeviceRepo: fetch PeripheralDevice by deviceId
  PeripheralDeviceRepo-->>ResolveActionResult: PeripheralDevice (contains studioId)
  ResolveActionResult->>StudioRepo: fetch StudioBlueprintManifest by studioId
  StudioRepo-->>ResolveActionResult: StudioBlueprintManifest (deviceActionMessages)
  ResolveActionResult->>ResolveActionResult: interpolate/invoke template/function -> resolved response
  ResolveActionResult-->>CallPeripheralDeviceAction: resolved ActionExecutionResult
  CallPeripheralDeviceAction-->>ResultClient: resolved result
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • imaretic
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically summarizes the main change: adding device action error message resolution using blueprint configuration.
Description check ✅ Passed The description comprehensively explains the feature, providing context, implementation details, example usage, testing instructions, and relationship to related work.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov
Copy link
Copy Markdown

codecov Bot commented May 11, 2026

Codecov Report

❌ Patch coverage is 16.25000% with 67 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
meteor/server/api/peripheralDevice.ts 15.58% 65 Missing ⚠️
meteor/server/api/client.ts 33.33% 2 Missing ⚠️

📢 Thoughts on this report? Let us know!

@rjmunro rjmunro changed the title feat: resolve device action errors using blueprint deviceActionMessages feat: Resolve device action errors using blueprint deviceActionMessages May 12, 2026
@rjmunro rjmunro force-pushed the rjmunro/error-message-customisation branch 3 times, most recently from 692ce19 to f3e9c26 Compare May 18, 2026 00:31
@rjmunro rjmunro force-pushed the rjmunro/improve-device-error-notifications branch 2 times, most recently from 4825591 to ea7c4ac Compare May 18, 2026 11:45
@rjmunro rjmunro marked this pull request as ready for review May 22, 2026 09:11
@Saftret Saftret added the Contribution from BBC Contributions sponsored by BBC (bbc.co.uk) label May 28, 2026
@rjmunro rjmunro changed the base branch from rjmunro/error-message-customisation to main June 2, 2026 08:44
@rjmunro rjmunro force-pushed the rjmunro/improve-device-error-notifications branch from ea7c4ac to d4a6368 Compare June 2, 2026 08:44
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
packages/blueprints-integration/src/api/studio.ts (1)

138-138: 💤 Low value

Type inconsistency with deviceStatusMessages.

deviceActionMessages includes | undefined in the value union, whereas deviceStatusMessages (line 113) does not. The doc block only describes string templates, functions, and empty-string suppression — it doesn't mention undefined. Adding | undefined to a Record value is redundant for optionality and just diverges from the sibling field. Consider dropping it for consistency (or documenting it intentionally).

♻️ Proposed change
-	deviceActionMessages?: Record<string, string | DeviceStatusMessageFunction | undefined>
+	deviceActionMessages?: Record<string, string | DeviceStatusMessageFunction>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/blueprints-integration/src/api/studio.ts` at line 138, The type for
deviceActionMessages is inconsistent with deviceStatusMessages: remove the
redundant "| undefined" from the Record value union so the property stays
optional via the "?" and the values match deviceStatusMessages; update the
deviceActionMessages declaration to use Record<string, string |
DeviceStatusMessageFunction> and, if the inclusion of undefined was intentional,
instead document that behavior in the docblock for deviceActionMessages to match
the expected semantics.
meteor/server/api/peripheralDevice.ts (1)

209-227: ⚖️ Poor tradeoff

Duplicated device → studio → blueprint → manifest lookup.

This sequence (find device, find studio, find blueprint, evalBlueprint) duplicates the logic in resolveDeviceStatusDetails (Lines 104-124). Consider extracting a small shared helper that returns the resolved StudioBlueprintManifest for a studio/device, so both message-resolution paths stay in sync.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@meteor/server/api/peripheralDevice.ts` around lines 209 - 227, Duplicate
device→studio→blueprint→manifest lookup: extract a small shared helper (e.g.,
resolveStudioBlueprintManifestForDevice or getStudioBlueprintManifest) that
encapsulates the sequence of PeripheralDevices.findOneAsync,
Studios.findOneAsync, Blueprints.findOneAsync and evalBlueprint and returns the
StudioBlueprintManifest (or undefined on failure); replace the inline sequence
in the current code and in resolveDeviceStatusDetails with calls to this helper
so both paths use the same lookup logic and return shape.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@meteor/server/api/peripheralDevice.ts`:
- Around line 246-255: The branch that returns early when resolved === null
should instead remove/suppress the TSR message so it doesn't reach the UI: in
the function resolveActionResult (the code path that calls
StatusMessageResolver.getDeviceStatusMessage and assigns
resolved/defaultMessage), replace the early return with logic that clears
result.response and any translatable response fields (e.g.
result.responseTranslated and result.translatableResponse or similarly named
properties on result) and leaves other result metadata intact; keep the existing
defaultMessage check unchanged. Ensure you only mutate the response fields (set
to empty string or undefined per local patterns) when resolved === null so the
blueprint suppression takes effect.

---

Nitpick comments:
In `@meteor/server/api/peripheralDevice.ts`:
- Around line 209-227: Duplicate device→studio→blueprint→manifest lookup:
extract a small shared helper (e.g., resolveStudioBlueprintManifestForDevice or
getStudioBlueprintManifest) that encapsulates the sequence of
PeripheralDevices.findOneAsync, Studios.findOneAsync, Blueprints.findOneAsync
and evalBlueprint and returns the StudioBlueprintManifest (or undefined on
failure); replace the inline sequence in the current code and in
resolveDeviceStatusDetails with calls to this helper so both paths use the same
lookup logic and return shape.

In `@packages/blueprints-integration/src/api/studio.ts`:
- Line 138: The type for deviceActionMessages is inconsistent with
deviceStatusMessages: remove the redundant "| undefined" from the Record value
union so the property stays optional via the "?" and the values match
deviceStatusMessages; update the deviceActionMessages declaration to use
Record<string, string | DeviceStatusMessageFunction> and, if the inclusion of
undefined was intentional, instead document that behavior in the docblock for
deviceActionMessages to match the expected semantics.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f6cb12a4-12e7-4a21-8be6-ac050517d0fe

📥 Commits

Reviewing files that changed from the base of the PR and between c580d76 and d4a6368.

📒 Files selected for processing (3)
  • meteor/server/api/client.ts
  • meteor/server/api/peripheralDevice.ts
  • packages/blueprints-integration/src/api/studio.ts

Comment on lines +246 to +255
if (resolved === null) {
// Message suppressed by blueprint
return result
}

// resolved.key is either the custom blueprint message or the defaultMessage
if (resolved.key === defaultMessage) {
// No custom message found - keep original response unchanged
return result
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Inspect StatusMessageResolver.getDeviceStatusMessage return semantics (null vs undefined on suppression)
fd -i statusmessageresolver -e ts | head
ast-grep --pattern 'getDeviceStatusMessage($$$) {
  $$$
}'
rg -nP -C4 '\bgetDeviceStatusMessage\b' --type=ts

Repository: Sofie-Automation/sofie-core

Length of output: 12279


Fix suppression handling for peripheral action status messages

  • resolveActionResult returns result unchanged when resolved === null, so the original TSR result.response is still surfaced even when the blueprint intends to “suppress” via an empty-string template (generic error behavior).
  • StatusMessageResolver.getDeviceStatusMessage is typed/documented to return ITranslatableMessage | null (null indicates suppression), so the === null check is the correct guard (no undefined/null mismatch).
  • Change the resolved === null branch to clear/replace result.response (and related translatable response fields) so the suppressed TSR message can’t reach the UI.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@meteor/server/api/peripheralDevice.ts` around lines 246 - 255, The branch
that returns early when resolved === null should instead remove/suppress the TSR
message so it doesn't reach the UI: in the function resolveActionResult (the
code path that calls StatusMessageResolver.getDeviceStatusMessage and assigns
resolved/defaultMessage), replace the early return with logic that clears
result.response and any translatable response fields (e.g.
result.responseTranslated and result.translatableResponse or similarly named
properties on result) and leaves other result metadata intact; keep the existing
defaultMessage check unchanged. Ensure you only mutate the response fields (set
to empty string or undefined per local patterns) when resolved === null so the
blueprint suppression takes effect.

Updates to 10.0.0-nightly-main-20260602-133931-c0882da4d.0 which includes
the new code and context fields on ActionExecutionResult (merged via
Sofie-Automation/sofie-timeline-state-resolver#459).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Contribution from BBC Contributions sponsored by BBC (bbc.co.uk)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants