mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-16 12:53:32 +00:00
refactor: migrate custom fields to transactions
refactor: extract functions to api domain refactor: strict custom field parsing refactor: remove rundown cache utilities refactor: directory restructure
This commit is contained in:
@@ -1,4 +1,6 @@
|
||||
import { TriggerDTO, TimerLifeCycle, AutomationDTO, Automation } from 'ontime-types';
|
||||
import { TriggerDTO, TimerLifeCycle, AutomationDTO, Automation, EntryId } from 'ontime-types';
|
||||
|
||||
import { makeRundown } from '../../rundown/__mocks__/rundown.mocks.js';
|
||||
|
||||
import {
|
||||
addTrigger,
|
||||
@@ -12,6 +14,7 @@ import {
|
||||
getAutomationTriggers,
|
||||
getAutomations,
|
||||
} from '../automation.dao.js';
|
||||
|
||||
import { makeOSCAction, makeHTTPAction } from './testUtils.js';
|
||||
|
||||
beforeAll(() => {
|
||||
@@ -186,11 +189,9 @@ describe('editAutomation()', async () => {
|
||||
});
|
||||
|
||||
describe('deleteAutomation()', () => {
|
||||
// saving the ID of the added automation
|
||||
let firstAutomation: Automation;
|
||||
beforeEach(async () => {
|
||||
await deleteAll();
|
||||
firstAutomation = await addAutomation({
|
||||
await addAutomation({
|
||||
title: 'test-osc',
|
||||
filterRule: 'all',
|
||||
filters: [],
|
||||
@@ -198,35 +199,15 @@ describe('deleteAutomation()', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should remove m automation from the list', async () => {
|
||||
it('should remove an automation from the list', async () => {
|
||||
const automations = getAutomations();
|
||||
expect(Object.keys(automations).length).toEqual(1);
|
||||
|
||||
await deleteAutomation(Object.keys(automations)[0]);
|
||||
const rundown = makeRundown({});
|
||||
const timedEventOrder: EntryId[] = [];
|
||||
|
||||
await deleteAutomation(rundown, timedEventOrder, Object.keys(automations)[0]);
|
||||
const removed = getAutomations();
|
||||
expect(Object.keys(removed).length).toEqual(0);
|
||||
});
|
||||
|
||||
it('should not remove an automation which is in use', async () => {
|
||||
const automations = getAutomations();
|
||||
await addTrigger({
|
||||
title: 'test-automation',
|
||||
trigger: TimerLifeCycle.onLoad,
|
||||
automationId: firstAutomation.id,
|
||||
});
|
||||
|
||||
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),
|
||||
});
|
||||
|
||||
await expect(deleteAutomation(automationId)).rejects.toThrowError();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { parseTemplateNested, stringToOSCArgs } from '../automation.utils.js';
|
||||
import { TimerLifeCycle } from 'ontime-types';
|
||||
import { makeOntimeEvent, makeRundown } from '../../rundown/__mocks__/rundown.mocks.js';
|
||||
import { isAutomationUsed, parseTemplateNested, stringToOSCArgs } from '../automation.utils.js';
|
||||
|
||||
describe('parseTemplateNested()', () => {
|
||||
it('parses string with a single-level variable name', () => {
|
||||
@@ -245,3 +247,53 @@ describe('test stringToOSCArgs()', () => {
|
||||
expect(stringToOSCArgs(test)).toStrictEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isAutomationUsed()', () => {
|
||||
it('returns the first event which uses an automation', () => {
|
||||
const rundown = makeRundown({
|
||||
entries: {
|
||||
'1': makeOntimeEvent({
|
||||
id: '1',
|
||||
triggers: [
|
||||
{
|
||||
id: 'trigger-1',
|
||||
title: 'Trigger 1',
|
||||
trigger: TimerLifeCycle.onClock,
|
||||
automationId: 'test-automation',
|
||||
},
|
||||
],
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
const timedEventOrder = ['1'];
|
||||
const automationId = 'test-automation';
|
||||
|
||||
const result = isAutomationUsed(rundown, timedEventOrder, automationId);
|
||||
expect(result).toBe('1');
|
||||
});
|
||||
|
||||
it('returns returns undefined if there are no matches', () => {
|
||||
const rundown = makeRundown({
|
||||
entries: {
|
||||
'1': makeOntimeEvent({
|
||||
id: '1',
|
||||
triggers: [
|
||||
{
|
||||
id: 'trigger-1',
|
||||
title: 'Trigger 1',
|
||||
trigger: TimerLifeCycle.onClock,
|
||||
automationId: 'test-automation',
|
||||
},
|
||||
],
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
const timedEventOrder = ['1'];
|
||||
const automationId = 'does-not-exist';
|
||||
|
||||
const result = isAutomationUsed(rundown, timedEventOrder, automationId);
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -5,6 +5,8 @@ import type { Request, Response } from 'express';
|
||||
|
||||
import { oscServer } from '../../adapters/OscAdapter.js';
|
||||
|
||||
import { getCurrentRundown, getRundownMetadata } from '../rundown/rundown.dao.js';
|
||||
|
||||
import * as automationDao from './automation.dao.js';
|
||||
import * as automationService from './automation.service.js';
|
||||
import { parseOutput } from './automation.validation.js';
|
||||
@@ -106,7 +108,10 @@ export async function editAutomation(req: Request, res: Response<Automation | Er
|
||||
|
||||
export async function deleteAutomation(req: Request, res: Response<void | ErrorResponse>) {
|
||||
try {
|
||||
await automationDao.deleteAutomation(req.params.id);
|
||||
const rundown = getCurrentRundown();
|
||||
const { timedEventOrder } = getRundownMetadata();
|
||||
|
||||
await automationDao.deleteAutomation(rundown, timedEventOrder, req.params.id);
|
||||
res.status(204).send();
|
||||
} catch (error) {
|
||||
const message = getErrorMessage(error);
|
||||
|
||||
@@ -2,14 +2,17 @@ import type {
|
||||
Automation,
|
||||
AutomationDTO,
|
||||
AutomationSettings,
|
||||
EntryId,
|
||||
NormalisedAutomation,
|
||||
Rundown,
|
||||
Trigger,
|
||||
TriggerDTO,
|
||||
} from 'ontime-types';
|
||||
import { deleteAtIndex, generateId } from 'ontime-utils';
|
||||
|
||||
import { getDataProvider } from '../../classes/data-provider/DataProvider.js';
|
||||
import { getTimedEvents } from '../../services/rundown-service/rundownUtils.js';
|
||||
|
||||
import { isAutomationUsed } from './automation.utils.js';
|
||||
|
||||
/**
|
||||
* Gets a copy of the stored automation settings
|
||||
@@ -133,7 +136,7 @@ export async function editAutomation(id: string, newAutomation: AutomationDTO):
|
||||
/**
|
||||
* Deletes a automation given its ID
|
||||
*/
|
||||
export async function deleteAutomation(id: string): Promise<void> {
|
||||
export async function deleteAutomation(rundown: Rundown, timedEventOrder: EntryId[], id: string): Promise<void> {
|
||||
const automations = getAutomations();
|
||||
// ignore request if automation does not exist
|
||||
if (!Object.hasOwn(automations, id)) {
|
||||
@@ -149,13 +152,9 @@ export async function deleteAutomation(id: string): Promise<void> {
|
||||
}
|
||||
|
||||
// prevent deleting a automation that is in use in events
|
||||
const events = getTimedEvents().filter(
|
||||
(event) => event.triggers && event.triggers.some((trigger) => trigger.automationId === id),
|
||||
);
|
||||
if (events.length) {
|
||||
throw new Error(
|
||||
`Unable to delete automation used in event: ${events[0].id}${events.length > 1 ? ` and ${events.length - 1} more` : ''}`,
|
||||
);
|
||||
const isInUse = isAutomationUsed(rundown, timedEventOrder, id);
|
||||
if (isInUse) {
|
||||
throw new Error(`Unable to delete automation used in event with ID ${isInUse}`);
|
||||
}
|
||||
|
||||
delete automations[id];
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { DatabaseModel, AutomationSettings, NormalisedAutomation, Trigger } from 'ontime-types';
|
||||
|
||||
import { dbModel } from '../../models/dataModel.js';
|
||||
import type { ErrorEmitter } from '../../utils/parser.js';
|
||||
import type { ErrorEmitter } from '../../utils/parserUtils.js';
|
||||
|
||||
interface LegacyData extends Partial<DatabaseModel> {
|
||||
http?: unknown;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { FilterRule, MaybeNumber, OntimeAction } from 'ontime-types';
|
||||
import { EntryId, FilterRule, isOntimeEvent, MaybeNumber, OntimeAction, Rundown } from 'ontime-types';
|
||||
import { millisToString, removeLeadingZero, splitWhitespace, getPropertyFromPath } from 'ontime-utils';
|
||||
import type { OscArgOrArrayInput, OscArgInput } from 'osc-min';
|
||||
|
||||
@@ -195,3 +195,25 @@ export function isBooleanEquals(a: boolean, b: string): boolean {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks is an automation is used in a rundown
|
||||
* TODO(v4): this currently only checks the current rundown, we will need to check all rundowns in the future
|
||||
*/
|
||||
export function isAutomationUsed(
|
||||
rundown: Rundown,
|
||||
timedEventOrder: EntryId[],
|
||||
automationId: string,
|
||||
): EntryId | undefined {
|
||||
for (let i = 0; i < timedEventOrder.length; i++) {
|
||||
const eventId = timedEventOrder[i];
|
||||
const event = rundown.entries[eventId];
|
||||
if (isOntimeEvent(event) && event.triggers) {
|
||||
for (const trigger of event.triggers) {
|
||||
if (trigger.automationId === automationId) {
|
||||
return eventId;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user