refactor(automation): improve automation UX

This commit is contained in:
Carlos Valente
2026-07-13 15:33:05 +02:00
committed by Carlos Valente
parent 86b2132cdc
commit 6911e37c18
14 changed files with 664 additions and 99 deletions
@@ -165,6 +165,25 @@ describe('parseOutput', () => {
});
it('parses message-secondary with a text value', () => {
expect(
parseOutput({
type: 'ontime',
action: 'message-secondary',
text: 'hello',
}),
).toMatchObject({
text: 'hello',
});
expect(
parseOutput({
type: 'ontime',
action: 'message-secondary',
secondarySource: undefined,
text: 'hello',
}),
).toMatchObject({
text: 'hello',
});
expect(
parseOutput({
type: 'ontime',
@@ -0,0 +1,113 @@
import { runtimeStorePlaceholder } from 'ontime-types';
import * as messageService from '../../../services/message-service/message.service.js';
import { toOntimeAction } from '../clients/ontime.client.js';
vi.mock('../../../services/message-service/message.service.js', () => ({
patch: vi.fn(),
}));
describe('toOntimeAction()', () => {
beforeEach(() => {
vi.clearAllMocks();
});
it('parses templates in primary message text', () => {
toOntimeAction(
{
type: 'ontime',
action: 'message-set',
text: 'Current: {{timer.current}}',
visible: true,
},
{
...runtimeStorePlaceholder,
timer: {
...runtimeStorePlaceholder.timer,
current: 42,
},
},
);
expect(messageService.patch).toHaveBeenCalledWith({
timer: {
text: 'Current: 42',
visible: true,
},
});
});
it('parses templates in secondary message text', () => {
toOntimeAction(
{
type: 'ontime',
action: 'message-secondary',
secondarySource: 'secondary',
text: 'Next: {{eventNext.title}}',
},
{
...runtimeStorePlaceholder,
eventNext: {
id: 'next-event',
type: 'event',
cue: '2',
title: 'Keynote',
note: '',
timeStart: 0,
timeEnd: 0,
duration: 0,
timerType: 'count-down',
colour: '',
delay: 0,
isPublic: true,
skip: false,
endAction: 'none',
revision: 0,
custom: {},
},
},
);
expect(messageService.patch).toHaveBeenCalledWith({
timer: {
secondarySource: 'secondary',
},
secondary: 'Next: Keynote',
});
});
it('can set secondary message text without changing the secondary source', () => {
toOntimeAction(
{
type: 'ontime',
action: 'message-secondary',
text: 'Next: {{eventNext.title}}',
},
{
...runtimeStorePlaceholder,
eventNext: {
id: 'next-event',
type: 'event',
cue: '2',
title: 'Keynote',
note: '',
timeStart: 0,
timeEnd: 0,
duration: 0,
timerType: 'count-down',
colour: '',
delay: 0,
isPublic: true,
skip: false,
endAction: 'none',
revision: 0,
custom: {},
},
},
);
expect(messageService.patch).toHaveBeenCalledWith({
secondary: 'Next: Keynote',
});
});
});
@@ -132,7 +132,7 @@ function send(output: AutomationOutput[], store: RuntimeStore) {
} else if (isHTTPOutput(payload)) {
emitHTTP(payload, store);
} else if (isOntimeAction(payload)) {
toOntimeAction(payload);
toOntimeAction(payload, store);
} else {
logger.warning(LogOrigin.Tx, `Unknown output type: ${payload}`);
}
@@ -225,8 +225,6 @@ function parseOntimeAction(maybeOntimeAction: object): OntimeAction {
}
if (maybeOntimeAction.action === 'message-secondary') {
assert.hasKeys(maybeOntimeAction, ['secondarySource']);
// the secondary text is optional, an empty string is treated as no change
let text: string | undefined = undefined;
if ('text' in maybeOntimeAction) {
@@ -234,6 +232,14 @@ function parseOntimeAction(maybeOntimeAction: object): OntimeAction {
text = indeterminateText(maybeOntimeAction.text);
}
if (!('secondarySource' in maybeOntimeAction) || maybeOntimeAction.secondarySource === undefined) {
return {
type: 'ontime',
action: 'message-secondary',
text,
};
}
// null is used to clear the secondary source
if (maybeOntimeAction.secondarySource === null) {
return {
@@ -1,12 +1,14 @@
import { LogOrigin, OntimeAction } from 'ontime-types';
import { LogOrigin, OntimeAction, RuntimeStore } from 'ontime-types';
import { parseUserTime } from 'ontime-utils';
import { DeepReadonly } from 'ts-essentials';
import { logger } from '../../../classes/Logger.js';
import { auxTimerService } from '../../../services/aux-timer-service/AuxTimerService.js';
import * as messageService from '../../../services/message-service/message.service.js';
import { runtimeService } from '../../../services/runtime-service/runtime.service.js';
import { parseTemplateNested } from '../automation.utils.js';
export function toOntimeAction(action: OntimeAction) {
export function toOntimeAction(action: OntimeAction, store: DeepReadonly<RuntimeStore>) {
const actionType = action.action;
switch (actionType) {
// Aux timer actions
@@ -55,19 +57,25 @@ export function toOntimeAction(action: OntimeAction) {
case 'message-set': {
messageService.patch({
timer: {
text: action.text,
text: action.text ? parseTemplateNested(action.text, store) : action.text,
visible: action.visible,
},
});
break;
}
case 'message-secondary': {
messageService.patch({
timer: {
secondarySource: action.secondarySource,
},
secondary: action.text,
});
const secondary = action.text ? parseTemplateNested(action.text, store) : action.text;
const patch =
action.secondarySource === undefined
? { secondary }
: {
timer: {
secondarySource: action.secondarySource,
},
secondary,
};
messageService.patch(patch);
break;
}