From 86b2132cdcfd8ac2e3c1b64f47af88184ae5da01 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 8 Jul 2026 15:39:48 +0000 Subject: [PATCH] feat(automation): expose group title and allow setting secondary text Surface {{groupNow.*}} template variables (title, note, colour, times, custom fields) in the automation template autocomplete so events inside a group can reference their group. The runtime store already carries groupNow, so substitution and filters worked already; this makes it discoverable. Extend the message-secondary action with an optional text field so an automation can set the secondary message content, not just its source. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01LZYPZVdZLWowU7DyzGkWyy --- .../automations-panel/OntimeActionForm.tsx | 57 +++++++++++-------- .../template-input/templateInput.utils.ts | 12 ++++ .../__tests__/automation.validation.test.ts | 46 +++++++++++++++ .../automation/automation.validation.ts | 10 ++++ .../automation/clients/ontime.client.ts | 1 + .../src/definitions/core/Automation.type.ts | 2 +- 6 files changed, 102 insertions(+), 26 deletions(-) diff --git a/apps/client/src/features/app-settings/panel/automations-panel/OntimeActionForm.tsx b/apps/client/src/features/app-settings/panel/automations-panel/OntimeActionForm.tsx index ab874a965..6eac2eb7c 100644 --- a/apps/client/src/features/app-settings/panel/automations-panel/OntimeActionForm.tsx +++ b/apps/client/src/features/app-settings/panel/automations-panel/OntimeActionForm.tsx @@ -120,31 +120,38 @@ export default function OntimeActionForm({ )} {selectedAction === 'message-secondary' && ( - + <> + + + )}
{children}
diff --git a/apps/client/src/features/app-settings/panel/automations-panel/template-input/templateInput.utils.ts b/apps/client/src/features/app-settings/panel/automations-panel/template-input/templateInput.utils.ts index 9404f15fb..51fa7a7b4 100644 --- a/apps/client/src/features/app-settings/panel/automations-panel/template-input/templateInput.utils.ts +++ b/apps/client/src/features/app-settings/panel/automations-panel/template-input/templateInput.utils.ts @@ -54,6 +54,16 @@ const eventStaticPropertiesNext = [ '{{eventNext.delay}}', ]; +const groupStaticPropertiesNow = [ + '{{groupNow.id}}', + '{{groupNow.title}}', + '{{groupNow.note}}', + '{{groupNow.colour}}', + '{{groupNow.timeStart}}', + '{{groupNow.timeEnd}}', + '{{groupNow.duration}}', +]; + const staticAuxProperties = (index: 1 | 2 | 3) => [ `{{auxtimer${index}.current}}`, `{{auxtimer${index}.duration}}`, @@ -75,6 +85,8 @@ export function makeAutoCompleteList(customFields: CustomFields): string[] { ...Object.entries(customFields).map(([key]) => `{{eventNow.custom.${key}}}`), ...eventStaticPropertiesNext, ...Object.entries(customFields).map(([key]) => `{{eventNext.custom.${key}}}`), + ...groupStaticPropertiesNow, + ...Object.entries(customFields).map(([key]) => `{{groupNow.custom.${key}}}`), ...staticAuxProperties(1), ...staticAuxProperties(2), ...staticAuxProperties(3), diff --git a/apps/server/src/api-data/automation/__tests__/automation.validation.test.ts b/apps/server/src/api-data/automation/__tests__/automation.validation.test.ts index 0ec82b633..6519cf548 100644 --- a/apps/server/src/api-data/automation/__tests__/automation.validation.test.ts +++ b/apps/server/src/api-data/automation/__tests__/automation.validation.test.ts @@ -163,5 +163,51 @@ describe('parseOutput', () => { secondarySource: 'secondary', }); }); + + it('parses message-secondary with a text value', () => { + expect( + parseOutput({ + type: 'ontime', + action: 'message-secondary', + secondarySource: 'secondary', + text: 'hello', + }), + ).toMatchObject({ + secondarySource: 'secondary', + text: 'hello', + }); + // an empty text is treated as no change + expect( + parseOutput({ + type: 'ontime', + action: 'message-secondary', + secondarySource: 'secondary', + text: '', + }), + ).toMatchObject({ + secondarySource: 'secondary', + text: undefined, + }); + // text can be set while clearing the secondary source + expect( + parseOutput({ + type: 'ontime', + action: 'message-secondary', + secondarySource: null, + text: 'hello', + }), + ).toMatchObject({ + secondarySource: null, + text: 'hello', + }); + expect(() => + parseOutput({ + type: 'ontime', + action: 'message-secondary', + secondarySource: 'secondary', + text: 123, + }), + ).toThrow('Unexpected payload type:'); + }); }); }); diff --git a/apps/server/src/api-data/automation/automation.validation.ts b/apps/server/src/api-data/automation/automation.validation.ts index 98d2af828..26f03252a 100644 --- a/apps/server/src/api-data/automation/automation.validation.ts +++ b/apps/server/src/api-data/automation/automation.validation.ts @@ -226,12 +226,21 @@ 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) { + assert.isString(maybeOntimeAction.text); + text = indeterminateText(maybeOntimeAction.text); + } + // null is used to clear the secondary source if (maybeOntimeAction.secondarySource === null) { return { type: 'ontime', action: 'message-secondary', secondarySource: null, + text, }; } @@ -240,6 +249,7 @@ function parseOntimeAction(maybeOntimeAction: object): OntimeAction { type: 'ontime', action: 'message-secondary', secondarySource: chooseSecondarySource(maybeOntimeAction.secondarySource), + text, }; } diff --git a/apps/server/src/api-data/automation/clients/ontime.client.ts b/apps/server/src/api-data/automation/clients/ontime.client.ts index 537b31931..4c706cb46 100644 --- a/apps/server/src/api-data/automation/clients/ontime.client.ts +++ b/apps/server/src/api-data/automation/clients/ontime.client.ts @@ -66,6 +66,7 @@ export function toOntimeAction(action: OntimeAction) { timer: { secondarySource: action.secondarySource, }, + secondary: action.text, }); break; } diff --git a/packages/types/src/definitions/core/Automation.type.ts b/packages/types/src/definitions/core/Automation.type.ts index 93f25e538..7e3382697 100644 --- a/packages/types/src/definitions/core/Automation.type.ts +++ b/packages/types/src/definitions/core/Automation.type.ts @@ -111,9 +111,9 @@ export type OntimeAction = text?: string; visible?: boolean; } - // TODO: when setting a secondary source of type secondary we could specify a value to it | { type: 'ontime'; action: OntimeMessageSecondary; secondarySource: SecondarySource; + text?: string; };