chore: rename feature elements

rename automations > triggers
rename blueprints > automations

chore: add link to documentation
This commit is contained in:
Carlos Valente
2025-01-20 19:22:00 +01:00
committed by Carlos Valente
parent f4f266dbd4
commit efe5ac16f2
30 changed files with 1125 additions and 1155 deletions
@@ -1,16 +1,16 @@
import { AutomationBlueprint, AutomationBlueprintDTO, AutomationDTO, TimerLifeCycle } from 'ontime-types';
import { TriggerDTO, TimerLifeCycle, AutomationDTO, Automation } from 'ontime-types';
import {
addTrigger,
addAutomation,
addBlueprint,
deleteAll,
deleteAllAutomations,
deleteAllTriggers,
deleteTrigger,
deleteAutomation,
deleteBlueprint,
editTrigger,
editAutomation,
editBlueprint,
getAutomationTriggers,
getAutomations,
getBlueprints,
} from '../automation.dao.js';
import { makeOSCAction, makeHTTPAction } from './testUtils.js';
@@ -21,8 +21,8 @@ beforeAll(() => {
enabledAutomations: true,
enabledOscIn: true,
oscPortIn: 8888,
automations: [],
blueprints: {},
triggers: [],
automations: {},
};
return {
getDataProvider: vi.fn().mockImplementation(() => {
@@ -39,117 +39,117 @@ afterAll(() => {
vi.clearAllMocks();
});
describe('addAutomations()', () => {
describe('addTrigger()', () => {
beforeEach(() => {
deleteAllAutomations();
deleteAllTriggers();
});
it('should accept a valid automation', () => {
const testData: AutomationDTO = {
const testData: TriggerDTO = {
title: 'test',
trigger: TimerLifeCycle.onLoad,
blueprintId: 'test-blueprint-id',
automationId: 'test-automation-id',
};
const automation = addAutomation(testData);
expect(automation).toMatchObject(testData);
const trigger = addTrigger(testData);
expect(trigger).toMatchObject(testData);
});
});
describe('editAutomation()', () => {
describe('editTrigger()', () => {
beforeEach(() => {
deleteAllAutomations();
addAutomation({
deleteAllTriggers();
addTrigger({
title: 'test-osc',
trigger: TimerLifeCycle.onLoad,
blueprintId: 'test-osc-blueprint',
automationId: 'test-osc-automation',
});
addAutomation({
addTrigger({
title: 'test-http',
trigger: TimerLifeCycle.onFinish,
blueprintId: 'test-http-blueprint',
automationId: 'test-http-automation',
});
});
it('should edit the contents of an automation', () => {
const automations = getAutomations();
const firstAutomation = automations[0];
expect(firstAutomation).toMatchObject({ id: expect.any(String), title: 'test-osc' });
const triggers = getAutomationTriggers();
const fistTrigger = triggers[0];
expect(fistTrigger).toMatchObject({ id: expect.any(String), title: 'test-osc' });
const editedOSC = editAutomation(firstAutomation.id, {
const editedOSC = editTrigger(fistTrigger.id, {
title: 'edited-title',
trigger: TimerLifeCycle.onDanger,
blueprintId: 'test-osc-blueprint',
automationId: 'test-osc-automation',
});
expect(editedOSC).toMatchObject({
id: expect.any(String),
title: 'edited-title',
trigger: TimerLifeCycle.onDanger,
blueprintId: 'test-osc-blueprint',
automationId: 'test-osc-automation',
});
});
});
describe('deleteAutomation()', () => {
describe('deleteTrigger()', () => {
beforeEach(() => {
deleteAllAutomations();
addAutomation({
deleteAllTriggers();
addTrigger({
title: 'test-osc',
trigger: TimerLifeCycle.onLoad,
blueprintId: 'test-osc-blueprint',
automationId: 'test-osc-automation',
});
addAutomation({
addTrigger({
title: 'test-http',
trigger: TimerLifeCycle.onFinish,
blueprintId: 'test-http-blueprint',
automationId: 'test-http-automation',
});
});
it('should remove an automation from the list', () => {
const automations = getAutomations();
expect(automations.length).toEqual(2);
const firstAutomation = automations[0];
expect(firstAutomation).toMatchObject({ id: expect.any(String), title: 'test-osc' });
const triggers = getAutomationTriggers();
expect(triggers.length).toEqual(2);
const fistTrigger = triggers[0];
expect(fistTrigger).toMatchObject({ id: expect.any(String), title: 'test-osc' });
deleteAutomation(firstAutomation.id);
const removed = getAutomations();
deleteTrigger(fistTrigger.id);
const removed = getAutomationTriggers();
expect(removed.length).toEqual(1);
expect(removed[0].title).not.toEqual('test-osc');
});
});
describe('addBlueprint()', () => {
describe('addAutomation()', () => {
beforeEach(() => {
deleteAll();
});
it('should accept a valid blueprint', () => {
const testData: AutomationBlueprintDTO = {
it('should accept a valid automation', () => {
const testData: AutomationDTO = {
title: 'test',
filterRule: 'all',
filters: [],
outputs: [makeOSCAction(), makeHTTPAction()],
};
const blueprint = addBlueprint(testData);
const blueprints = getBlueprints();
expect(blueprints[blueprint.id]).toMatchObject(testData);
const automation = addAutomation(testData);
const automations = getAutomations();
expect(automations[automation.id]).toMatchObject(testData);
});
});
describe('editBlueprint()', () => {
// saving the ID of the added blueprint
let firstBlueprint: AutomationBlueprint;
describe('editAutomation()', () => {
// saving the ID of the added automation
let firstAutomation: Automation;
beforeEach(() => {
deleteAll();
firstBlueprint = addBlueprint({
firstAutomation = addAutomation({
title: 'test-osc',
filterRule: 'all',
filters: [],
outputs: [],
});
addBlueprint({
addAutomation({
title: 'test-http',
filterRule: 'all',
filters: [],
@@ -157,18 +157,18 @@ describe('editBlueprint()', () => {
});
});
it('should edit the contents of a blueprint', () => {
const blueprints = getBlueprints();
expect(Object.keys(blueprints).length).toEqual(2);
expect(blueprints[firstBlueprint.id]).toMatchObject({
id: firstBlueprint.id,
it('should edit the contents of an automation', () => {
const automations = getAutomations();
expect(Object.keys(automations).length).toEqual(2);
expect(automations[firstAutomation.id]).toMatchObject({
id: firstAutomation.id,
title: 'test-osc',
filterRule: 'all',
filters: expect.any(Array),
outputs: expect.any(Array),
});
const editedOSC = editBlueprint(firstBlueprint.id, {
const editedOSC = editAutomation(firstAutomation.id, {
title: 'edited-title',
filterRule: 'any',
filters: [],
@@ -176,7 +176,7 @@ describe('editBlueprint()', () => {
});
expect(editedOSC).toMatchObject({
id: firstBlueprint.id,
id: firstAutomation.id,
title: 'edited-title',
filterRule: 'any',
filters: expect.any(Array),
@@ -185,12 +185,12 @@ describe('editBlueprint()', () => {
});
});
describe('deleteBlueprint()', () => {
// saving the ID of the added blueprint
let firstBlueprint: AutomationBlueprint;
describe('deleteAutomation()', () => {
// saving the ID of the added automation
let firstAutomation: Automation;
beforeEach(() => {
deleteAll();
firstBlueprint = addBlueprint({
firstAutomation = addAutomation({
title: 'test-osc',
filterRule: 'all',
filters: [],
@@ -198,35 +198,35 @@ describe('deleteBlueprint()', () => {
});
});
it('should remove a blueprint from the list', () => {
const blueprints = getBlueprints();
expect(Object.keys(blueprints).length).toEqual(1);
it('should remove m automation from the list', () => {
const automations = getAutomations();
expect(Object.keys(automations).length).toEqual(1);
deleteBlueprint(Object.keys(blueprints)[0]);
const removed = getBlueprints();
deleteAutomation(Object.keys(automations)[0]);
const removed = getAutomations();
expect(Object.keys(removed).length).toEqual(0);
});
it('should not remove a blueprint which is in use', () => {
const blueprints = getBlueprints();
addAutomation({
it('should not remove an automation which is in use', () => {
const automations = getAutomations();
addTrigger({
title: 'test-automation',
trigger: TimerLifeCycle.onLoad,
blueprintId: firstBlueprint.id,
automationId: firstAutomation.id,
});
const blueprintKeys = Object.keys(blueprints);
const blueprintId = blueprintKeys[0];
expect(blueprintId).toEqual(firstBlueprint.id);
expect(blueprintKeys.length).toEqual(1);
expect(blueprints[blueprintId]).toMatchObject({
id: blueprintId,
const automationKeys = Object.keys(automations);
const automationId = automationKeys[0];
expect(automationId).toEqual(firstAutomation.id);
expect(automationKeys.length).toEqual(1);
expect(automations[automationId]).toMatchObject({
id: automationId,
title: 'test-osc',
filterRule: 'all',
filters: expect.any(Array),
outputs: expect.any(Array),
});
expect(() => deleteBlueprint(blueprintId)).toThrowError();
expect(() => deleteAutomation(automationId)).toThrowError();
});
});
@@ -3,7 +3,7 @@ import { PlayableEvent, TimerLifeCycle } from 'ontime-types';
import { makeRuntimeStateData } from '../../../stores/__mocks__/runtimeState.mocks.js';
import { makeOntimeEvent } from '../../../services/rundown-service/__mocks__/rundown.mocks.js';
import { deleteAllAutomations, addAutomation, addBlueprint } from '../automation.dao.js';
import { deleteAllTriggers, addTrigger, addAutomation } from '../automation.dao.js';
import { testConditions, triggerAutomations } from '../automation.service.js';
import * as oscClient from '../clients/osc.client.js';
import * as httpClient from '../clients/http.client.js';
@@ -17,8 +17,8 @@ beforeAll(() => {
enabledAutomations: true,
enabledOscIn: true,
oscPortIn: 8888,
automations: [],
blueprints: {},
triggers: [],
automations: {},
};
return {
getDataProvider: vi.fn().mockImplementation(() => {
@@ -43,28 +43,28 @@ describe('triggerAction()', () => {
oscSpy = vi.spyOn(oscClient, 'emitOSC').mockImplementation(() => {});
httpSpy = vi.spyOn(httpClient, 'emitHTTP').mockImplementation(() => {});
deleteAllAutomations();
const oscBlueprint = addBlueprint({
deleteAllTriggers();
const oscAutomation = addAutomation({
title: 'test-osc',
filterRule: 'all',
filters: [],
outputs: [makeOSCAction()],
});
const httpBlueprint = addBlueprint({
const httpAutomation = addAutomation({
title: 'test-http',
filterRule: 'any',
filters: [],
outputs: [makeHTTPAction()],
});
addAutomation({
addTrigger({
title: 'test-osc',
trigger: TimerLifeCycle.onLoad,
blueprintId: oscBlueprint.id,
automationId: oscAutomation.id,
});
addAutomation({
addTrigger({
title: 'test-http',
trigger: TimerLifeCycle.onFinish,
blueprintId: httpBlueprint.id,
automationId: httpAutomation.id,
});
});
@@ -1,5 +1,5 @@
import { getErrorMessage } from 'ontime-utils';
import { Automation, AutomationBlueprint, AutomationOutput, AutomationSettings, ErrorResponse } from 'ontime-types';
import { Automation, AutomationOutput, AutomationSettings, ErrorResponse, Trigger } from 'ontime-types';
import type { Request, Response } from 'express';
@@ -18,8 +18,8 @@ export function postAutomationSettings(req: Request, res: Response<AutomationSet
enabledAutomations: req.body.enabledAutomations,
enabledOscIn: req.body.enabledOscIn,
oscPortIn: req.body.oscPortIn,
triggers: req.body.triggers ?? undefined,
automations: req.body.automations ?? undefined,
blueprints: req.body.blueprints ?? undefined,
});
if (automationSettings.enabledOscIn) {
oscServer.init(automationSettings.oscPortIn);
@@ -33,12 +33,12 @@ export function postAutomationSettings(req: Request, res: Response<AutomationSet
}
}
export function postAutomation(req: Request, res: Response<Automation | ErrorResponse>) {
export function postTrigger(req: Request, res: Response<Trigger | ErrorResponse>) {
try {
const automation = automationDao.addAutomation({
const automation = automationDao.addTrigger({
title: req.body.title,
trigger: req.body.trigger,
blueprintId: req.body.blueprintId,
automationId: req.body.automationId,
});
res.status(201).send(automation);
} catch (error) {
@@ -47,13 +47,13 @@ export function postAutomation(req: Request, res: Response<Automation | ErrorRes
}
}
export function putAutomation(req: Request, res: Response<Automation | ErrorResponse>) {
export function putTrigger(req: Request, res: Response<Trigger | ErrorResponse>) {
try {
// body payload is a patch object
const automation = automationDao.editAutomation(req.params.id, {
const automation = automationDao.editTrigger(req.params.id, {
title: req.body.title ?? undefined,
trigger: req.body.trigger ?? undefined,
blueprintId: req.body.blueprintId ?? undefined,
automationId: req.body.automationId ?? undefined,
});
res.status(200).send(automation);
} catch (error) {
@@ -62,6 +62,46 @@ export function putAutomation(req: Request, res: Response<Automation | ErrorResp
}
}
export function deleteTrigger(req: Request, res: Response<void | ErrorResponse>) {
try {
automationDao.deleteTrigger(req.params.id);
res.status(204).send();
} catch (error) {
const message = getErrorMessage(error);
res.status(400).send({ message });
}
}
export function postAutomation(req: Request, res: Response<Automation | ErrorResponse>) {
try {
const newAutomation = automationDao.addAutomation({
title: req.body.title,
filterRule: req.body.filterRule,
filters: req.body.filters,
outputs: req.body.outputs,
});
res.status(201).send(newAutomation);
} catch (error) {
const message = getErrorMessage(error);
res.status(400).send({ message });
}
}
export function editAutomation(req: Request, res: Response<Automation | ErrorResponse>) {
try {
const newAutomation = automationDao.editAutomation(req.params.id, {
title: req.body.title,
filterRule: req.body.filterRule,
filters: req.body.filters,
outputs: req.body.outputs,
});
res.status(200).send(newAutomation);
} catch (error) {
const message = getErrorMessage(error);
res.status(400).send({ message });
}
}
export function deleteAutomation(req: Request, res: Response<void | ErrorResponse>) {
try {
automationDao.deleteAutomation(req.params.id);
@@ -72,46 +112,6 @@ export function deleteAutomation(req: Request, res: Response<void | ErrorRespons
}
}
export function postBlueprint(req: Request, res: Response<AutomationBlueprint | ErrorResponse>) {
try {
const newBlueprint = automationDao.addBlueprint({
title: req.body.title,
filterRule: req.body.filterRule,
filters: req.body.filters,
outputs: req.body.outputs,
});
res.status(201).send(newBlueprint);
} catch (error) {
const message = getErrorMessage(error);
res.status(400).send({ message });
}
}
export function editBlueprint(req: Request, res: Response<AutomationBlueprint | ErrorResponse>) {
try {
const newBlueprint = automationDao.editBlueprint(req.params.id, {
title: req.body.title,
filterRule: req.body.filterRule,
filters: req.body.filters,
outputs: req.body.outputs,
});
res.status(200).send(newBlueprint);
} catch (error) {
const message = getErrorMessage(error);
res.status(400).send({ message });
}
}
export function deleteBlueprint(req: Request, res: Response<void | ErrorResponse>) {
try {
automationDao.deleteBlueprint(req.params.id);
res.status(204).send();
} catch (error) {
const message = getErrorMessage(error);
res.status(400).send({ message });
}
}
export function testOutput(req: Request, res: Response<void | ErrorResponse>) {
try {
const payload = req.body as AutomationOutput;
@@ -1,11 +1,4 @@
import type {
Automation,
AutomationBlueprint,
AutomationBlueprintDTO,
AutomationDTO,
AutomationSettings,
NormalisedAutomationBlueprint,
} from 'ontime-types';
import type { Automation, AutomationDTO, AutomationSettings, NormalisedAutomation, Trigger, TriggerDTO } from 'ontime-types';
import { deleteAtIndex, generateId } from 'ontime-utils';
import { getDataProvider } from '../../classes/data-provider/DataProvider.js';
@@ -24,20 +17,23 @@ export function getAutomationsEnabled(): boolean {
return getAutomationSettings().enabledAutomations;
}
/**
* Gets a copy of the stored automation triggers
*/
export function getAutomationTriggers(): Trigger[] {
return getAutomationSettings().triggers;
}
/**
* Gets a copy of the stored automations
*/
export function getAutomations(): Automation[] {
export function getAutomations(): NormalisedAutomation {
return getAutomationSettings().automations;
}
/**
* Gets a copy of the stored blueprints
* Patches the automation settings object
*/
export function getBlueprints(): NormalisedAutomationBlueprint {
return getAutomationSettings().blueprints;
}
export function editAutomationSettings(settings: Partial<AutomationSettings>): AutomationSettings {
saveChanges(settings);
return getAutomationSettings();
@@ -46,105 +42,105 @@ export function editAutomationSettings(settings: Partial<AutomationSettings>): A
/**
* Adds a validated automation to the store
*/
export function addAutomation(newAutomation: AutomationDTO): Automation {
const automations = getAutomations();
const id = getUniqueAutomationId(automations);
const automation = { ...newAutomation, id };
automations.push(automation);
saveChanges({ automations });
return automation;
export function addTrigger(newTrigger: TriggerDTO): Trigger {
const triggers = getAutomationTriggers();
const id = getUniqueTriggerId(triggers);
const trigger = { ...newTrigger, id };
triggers.push(trigger);
saveChanges({ triggers });
return trigger;
}
/**
* Patches an existing automation
* Patches an existing automation trigger
*/
export function editAutomation(id: string, newAutomation: AutomationDTO): Automation {
const automations = getAutomations();
const index = automations.findIndex((automation) => automation.id === id);
export function editTrigger(id: string, newTrigger: TriggerDTO): Trigger {
const triggers = getAutomationTriggers();
const index = triggers.findIndex((trigger) => trigger.id === id);
if (index === -1) {
throw new Error(`Automation with id ${id} not found`);
}
automations[index] = { ...automations[index], ...newAutomation };
saveChanges({ automations });
return automations[index];
triggers[index] = { ...triggers[index], ...newTrigger };
saveChanges({ triggers });
return triggers[index];
}
/**
* Deletes an automation given its ID
* Deletes an automation trigger given its ID
*/
export function deleteAutomation(id: string): void {
let automations = getAutomations();
const index = automations.findIndex((automation) => automation.id === id);
export function deleteTrigger(id: string): void {
let triggers = getAutomationTriggers();
const index = triggers.findIndex((trigger) => trigger.id === id);
if (index === -1) {
throw new Error(`Automation with id ${id} not found`);
}
automations = deleteAtIndex(index, automations);
saveChanges({ automations });
triggers = deleteAtIndex(index, triggers);
saveChanges({ triggers });
}
/**
* Deletes all project automations
* Deletes all project automation triggers
*/
export function deleteAllAutomations(): void {
saveChanges({ automations: [] });
export function deleteAllTriggers(): void {
saveChanges({ triggers: [] });
}
/**
* Deletes all project automations and blueprints
* Deletes all project automation triggers and automations
* We do this together to avoid issues with missing references
*/
export function deleteAll(): void {
saveChanges({ automations: [], blueprints: {} });
saveChanges({ triggers: [], automations: {} });
}
/**
* Adds a validated blueprint to the store
* Adds a validated automation to the store
*/
export function addBlueprint(newBlueprint: Omit<AutomationBlueprint, 'id'>): AutomationBlueprint {
const blueprints = getBlueprints();
const id = getUniqueBlueprintId(blueprints);
blueprints[id] = { ...newBlueprint, id };
saveChanges({ blueprints });
return blueprints[id];
export function addAutomation(newAutomation: AutomationDTO): Automation {
const automations = getAutomations();
const id = getUniqueAutomationId(automations);
automations[id] = { ...newAutomation, id };
saveChanges({ automations });
return automations[id];
}
/**
* Updates an existing blueprint with a new entry
* Updates an existing automation with a new entry
*/
export function editBlueprint(id: string, newBlueprint: AutomationBlueprintDTO): AutomationBlueprint {
const blueprints = getBlueprints();
if (!Object.hasOwn(blueprints, id)) {
throw new Error(`Blueprint with id ${id} not found`);
export function editAutomation(id: string, newAutomation: AutomationDTO): Automation {
const automations = getAutomations();
if (!Object.hasOwn(automations, id)) {
throw new Error(`Automation with id ${id} not found`);
}
blueprints[id] = { ...newBlueprint, id };
saveChanges({ blueprints });
return blueprints[id];
automations[id] = { ...newAutomation, id };
saveChanges({ automations });
return automations[id];
}
/**
* Deletes a blueprint given its ID
* Deletes a automation given its ID
*/
export function deleteBlueprint(id: string): void {
const blueprints = getBlueprints();
// ignore request if blueprint does not exist
if (!Object.hasOwn(blueprints, id)) {
export function deleteAutomation(id: string): void {
const automations = getAutomations();
// ignore request if automation does not exist
if (!Object.hasOwn(automations, id)) {
return;
}
// prevent deleting a blueprint that is in use
const automations = getAutomations();
for (let i = 0; i < automations.length; i++) {
const automation = automations[i];
if (automation.blueprintId === id) {
throw new Error(`Unable to delete blueprint used in automation ${automation.title}`);
// prevent deleting a automation that is in use
const triggers = getAutomationTriggers();
for (let i = 0; i < triggers.length; i++) {
const trigger = triggers[i];
if (trigger.automationId === id) {
throw new Error(`Unable to delete automation used in trigger ${trigger.title}`);
}
}
delete blueprints[id];
saveChanges({ blueprints });
delete automations[id];
saveChanges({ automations });
}
/**
@@ -161,14 +157,14 @@ async function saveChanges(patch: Partial<AutomationSettings>) {
/**
* Returns an ID guaranteed to be unique in an array
*/
function getUniqueAutomationId(automations: Automation[]): string {
function getUniqueTriggerId(triggers: Trigger[]): string {
let id = '';
do {
id = generateId();
} while (isInArray(id));
function isInArray(id: string): boolean {
return automations.some((automation) => automation.id === id);
return triggers.some((trigger) => trigger.id === id);
}
return id;
}
@@ -176,10 +172,10 @@ function getUniqueAutomationId(automations: Automation[]): string {
/**
* Returns an ID guaranteed to be unique in an objects keys
*/
function getUniqueBlueprintId(blueprints: NormalisedAutomationBlueprint): string {
function getUniqueAutomationId(automations: NormalisedAutomation): string {
let id = '';
do {
id = generateId();
} while (Object.hasOwn(blueprints, id));
} while (Object.hasOwn(automations, id));
return id;
}
@@ -1,4 +1,4 @@
import { DatabaseModel, AutomationSettings, Automation, NormalisedAutomationBlueprint } from 'ontime-types';
import { DatabaseModel, AutomationSettings, NormalisedAutomation, Trigger } from 'ontime-types';
import { dbModel } from '../../models/dataModel.js';
import type { ErrorEmitter } from '../../utils/parser.js';
@@ -24,8 +24,8 @@ export function parseAutomationSettings(data: LegacyData, emitError?: ErrorEmitt
enabledAutomations: dbModel.automation.enabledAutomations,
enabledOscIn: data.osc?.enabledIn ?? dbModel.automation.enabledOscIn,
oscPortIn: data.osc?.portIn ?? dbModel.automation.oscPortIn,
automations: [],
blueprints: {},
triggers: [],
automations: {},
};
} else {
return { ...dbModel.automation };
@@ -42,17 +42,17 @@ export function parseAutomationSettings(data: LegacyData, emitError?: ErrorEmitt
enabledAutomations: data.automation.enabledAutomations ?? dbModel.automation.enabledAutomations,
enabledOscIn: data.automation.enabledOscIn ?? dbModel.automation.enabledOscIn,
oscPortIn: data.automation.oscPortIn ?? dbModel.automation.oscPortIn,
triggers: parseTriggers(data.automation.triggers),
automations: parseAutomations(data.automation.automations),
blueprints: parseBlueprints(data.automation.blueprints),
};
}
function parseAutomations(maybeAutomations: unknown): Automation[] {
function parseTriggers(maybeAutomations: unknown): Trigger[] {
if (!Array.isArray(maybeAutomations)) return [];
return maybeAutomations as Automation[];
return maybeAutomations as Trigger[];
}
function parseBlueprints(maybeBlueprint: unknown): NormalisedAutomationBlueprint {
if (typeof maybeBlueprint !== 'object' || maybeBlueprint === null) return {};
return maybeBlueprint as NormalisedAutomationBlueprint;
function parseAutomations(maybeAutomation: unknown): NormalisedAutomation {
if (typeof maybeAutomation !== 'object' || maybeAutomation === null) return {};
return maybeAutomation as NormalisedAutomation;
}
@@ -1,24 +1,24 @@
import express from 'express';
import {
deleteTrigger,
deleteAutomation,
deleteBlueprint,
editBlueprint,
editAutomation,
getAutomationSettings,
postTrigger,
postAutomation,
postBlueprint,
putAutomation,
putTrigger,
postAutomationSettings,
testOutput,
} from './automation.controller.js';
import {
paramContainsId,
validateAutomationSettings,
validateAutomation,
validateAutomationPatch,
validateAutomationSettings,
validateBlueprint,
validateBlueprintPatch,
validateTestPayload,
validateTrigger,
validateTriggerPatch,
} from './automation.validation.js';
export const router = express.Router();
@@ -26,12 +26,12 @@ export const router = express.Router();
router.get('/', getAutomationSettings);
router.post('/', validateAutomationSettings, postAutomationSettings);
router.post('/trigger', validateTrigger, postTrigger);
router.put('/trigger/:id', validateTriggerPatch, putTrigger);
router.delete('/trigger/:id', paramContainsId, deleteTrigger);
router.post('/automation', validateAutomation, postAutomation);
router.put('/automation/:id', validateAutomationPatch, putAutomation);
router.put('/automation/:id', validateAutomationPatch, editAutomation);
router.delete('/automation/:id', paramContainsId, deleteAutomation);
router.post('/blueprint', validateBlueprint, postBlueprint);
router.put('/blueprint/:id', validateBlueprintPatch, editBlueprint);
router.delete('/blueprint/:id', paramContainsId, deleteBlueprint);
router.post('/test', validateTestPayload, testOutput);
@@ -13,7 +13,7 @@ import { isOntimeCloud } from '../../externals.js';
import { emitOSC } from './clients/osc.client.js';
import { emitHTTP } from './clients/http.client.js';
import { getAutomations, getAutomationsEnabled, getBlueprints } from './automation.dao.js';
import { getAutomationsEnabled, getAutomations, getAutomationTriggers } from './automation.dao.js';
/**
* Exposes a method for triggering actions based on a TimerLifeCycle event
@@ -23,25 +23,25 @@ export function triggerAutomations(event: TimerLifeCycle, state: RuntimeState) {
return;
}
const automations = getAutomations();
const triggerAutomations = automations.filter((automation) => automation.trigger === event);
const triggers = getAutomationTriggers();
const triggerAutomations = triggers.filter((trigger) => trigger.trigger === event);
if (triggerAutomations.length === 0) {
return;
}
const blueprints = getBlueprints();
if (Object.keys(blueprints).length === 0) {
const automations = getAutomations();
if (Object.keys(automations).length === 0) {
return;
}
triggerAutomations.forEach((automation) => {
const blueprint = blueprints[automation.blueprintId];
if (!blueprint) {
triggerAutomations.forEach((trigger) => {
const automation = automations[trigger.automationId];
if (!automation) {
return;
}
const shouldSend = testConditions(blueprint.filters, blueprint.filterRule, state);
const shouldSend = testConditions(automation.filters, automation.filterRule, state);
if (shouldSend) {
send(blueprint.outputs, state);
send(automation.outputs, state);
}
});
}
@@ -1,5 +1,5 @@
import {
AutomationBlueprint,
Automation,
AutomationFilter,
AutomationOutput,
HTTPOutput,
@@ -28,11 +28,36 @@ export const validateAutomationSettings = [
body('enabledAutomations').exists().isBoolean(),
body('enabledOscIn').exists().isBoolean(),
body('oscPortIn').exists().isPort(),
body('automations').optional().isArray(),
body('automations.*.title').optional().isString().trim(),
body('automations.*.trigger').optional().isIn(timerLifecycleValues),
body('automations.*.blueprintId').optional().isString().trim(),
body('blueprints').optional().custom(parseBluePrint),
body('triggers').optional().isArray(),
body('triggers.*.title').optional().isString().trim(),
body('triggers.*.trigger').optional().isIn(timerLifecycleValues),
body('triggers.*.automationId').optional().isString().trim(),
body('automations').optional().custom(parseAutomation),
(req: Request, res: Response, next: NextFunction) => {
const errors = validationResult(req);
if (!errors.isEmpty()) return res.status(422).json({ errors: errors.array() });
next();
},
];
export const validateTrigger = [
body('title').exists().isString().trim(),
body('trigger').exists().isIn(timerLifecycleValues),
body('automationId').exists().isString().trim(),
(req: Request, res: Response, next: NextFunction) => {
const errors = validationResult(req);
if (!errors.isEmpty()) return res.status(422).json({ errors: errors.array() });
next();
},
];
export const validateTriggerPatch = [
param('id').exists(),
body('title').optional().isString().trim(),
body('trigger').optional().isIn(timerLifecycleValues),
body('automationId').optional().isString().trim(),
(req: Request, res: Response, next: NextFunction) => {
const errors = validationResult(req);
@@ -42,9 +67,7 @@ export const validateAutomationSettings = [
];
export const validateAutomation = [
body('title').exists().isString().trim(),
body('trigger').exists().isIn(timerLifecycleValues),
body('blueprintId').exists().isString().trim(),
body().custom(parseAutomation),
(req: Request, res: Response, next: NextFunction) => {
const errors = validationResult(req);
@@ -55,30 +78,7 @@ export const validateAutomation = [
export const validateAutomationPatch = [
param('id').exists(),
body('title').optional().isString().trim(),
body('trigger').optional().isIn(timerLifecycleValues),
body('blueprintId').optional().isString().trim(),
(req: Request, res: Response, next: NextFunction) => {
const errors = validationResult(req);
if (!errors.isEmpty()) return res.status(422).json({ errors: errors.array() });
next();
},
];
export const validateBlueprint = [
body().custom(parseBluePrint),
(req: Request, res: Response, next: NextFunction) => {
const errors = validationResult(req);
if (!errors.isEmpty()) return res.status(422).json({ errors: errors.array() });
next();
},
];
export const validateBlueprintPatch = [
param('id').exists(),
body().custom(parseBluePrint),
body().custom(parseAutomation),
(req: Request, res: Response, next: NextFunction) => {
const errors = validationResult(req);
@@ -88,17 +88,17 @@ export const validateBlueprintPatch = [
];
/**
* Parses and validates a use given blueprint
* Parses and validates a use given automation
*/
export function parseBluePrint(maybeBlueprint: unknown): AutomationBlueprint {
assert.isObject(maybeBlueprint);
assert.hasKeys(maybeBlueprint, ['title', 'filterRule', 'filters', 'outputs']);
export function parseAutomation(maybeAutomation: unknown): Automation {
assert.isObject(maybeAutomation);
assert.hasKeys(maybeAutomation, ['title', 'filterRule', 'filters', 'outputs']);
const { title, filterRule, filters, outputs } = maybeBlueprint;
const { title, filterRule, filters, outputs } = maybeAutomation;
assert.isString(title);
assert.isString(filterRule);
if (!isFilterRule(filterRule)) {
throw new Error(`Invalid blueprint: unknown filter rule ${filterRule}`);
throw new Error(`Invalid automation: unknown filter rule ${filterRule}`);
}
assert.isArray(filters);
validateFilters(filters);
@@ -106,7 +106,7 @@ export function parseBluePrint(maybeBlueprint: unknown): AutomationBlueprint {
assert.isArray(outputs);
validateOutput(outputs);
return maybeBlueprint as AutomationBlueprint;
return maybeAutomation as Automation;
}
function validateFilters(filters: Array<unknown>): filters is AutomationFilter[] {
@@ -119,7 +119,7 @@ function validateFilters(filters: Array<unknown>): filters is AutomationFilter[]
assert.isString(operator);
assert.isString(value);
if (!isFilterOperator(operator)) {
throw new Error(`Invalid blueprint: unknown filter operator ${operator}`);
throw new Error(`Invalid automation: unknown filter operator ${operator}`);
}
if (typeof value !== 'string' && typeof value !== 'number' && typeof value !== 'boolean') {