diff --git a/apps/client/src/features/app-settings/panel/automations-panel/AutomationsList.tsx b/apps/client/src/features/app-settings/panel/automations-panel/AutomationsList.tsx
index 05999234d..630957c22 100644
--- a/apps/client/src/features/app-settings/panel/automations-panel/AutomationsList.tsx
+++ b/apps/client/src/features/app-settings/panel/automations-panel/AutomationsList.tsx
@@ -107,16 +107,16 @@ export default function AutomationsList(props: AutomationsListProps) {
- {deleteError && (
-
- |
- {deleteError}
- |
-
- )}
);
})}
+ {deleteError && (
+
+ |
+ {deleteError}
+ |
+
+ )}
diff --git a/apps/server/src/api-data/automation/__tests__/automation.dao.test.ts b/apps/server/src/api-data/automation/__tests__/automation.dao.test.ts
index d5e497be5..bacdf5db2 100644
--- a/apps/server/src/api-data/automation/__tests__/automation.dao.test.ts
+++ b/apps/server/src/api-data/automation/__tests__/automation.dao.test.ts
@@ -1,6 +1,6 @@
-import { TriggerDTO, TimerLifeCycle, AutomationDTO, Automation, EntryId } from 'ontime-types';
+import { TriggerDTO, TimerLifeCycle, AutomationDTO, Automation, ProjectRundowns } from 'ontime-types';
-import { makeRundown } from '../../rundown/__mocks__/rundown.mocks.js';
+import { makeOntimeEvent } from '../../rundown/__mocks__/rundown.mocks.js';
import {
addTrigger,
@@ -203,10 +203,29 @@ describe('deleteAutomation()', () => {
const automations = getAutomations();
expect(Object.keys(automations).length).toEqual(1);
- const rundown = makeRundown({});
- const timedEventOrder: EntryId[] = [];
-
- await deleteAutomation(rundown, timedEventOrder, Object.keys(automations)[0]);
+ const projectRundowns: ProjectRundowns = {
+ 'rundown-1': {
+ id: 'rundown-1',
+ title: 'Rundown 1',
+ order: ['1'],
+ flatOrder: ['1'],
+ entries: {
+ '1': makeOntimeEvent({
+ id: '1',
+ triggers: [
+ {
+ id: 'trigger-1',
+ title: 'Trigger 1',
+ trigger: TimerLifeCycle.onClock,
+ automationId: 'test-automation',
+ },
+ ],
+ }),
+ },
+ revision: 1,
+ },
+ };
+ await deleteAutomation(projectRundowns, Object.keys(automations)[0]);
const removed = getAutomations();
expect(Object.keys(removed).length).toEqual(0);
});
diff --git a/apps/server/src/api-data/automation/__tests__/automation.utils.test.ts b/apps/server/src/api-data/automation/__tests__/automation.utils.test.ts
index 7dc03a253..e6e9f6c14 100644
--- a/apps/server/src/api-data/automation/__tests__/automation.utils.test.ts
+++ b/apps/server/src/api-data/automation/__tests__/automation.utils.test.ts
@@ -1,5 +1,7 @@
-import { TimerLifeCycle } from 'ontime-types';
-import { makeOntimeEvent, makeRundown } from '../../rundown/__mocks__/rundown.mocks.js';
+import { ProjectRundowns, TimerLifeCycle } from 'ontime-types';
+
+import { makeOntimeEvent } from '../../rundown/__mocks__/rundown.mocks.js';
+
import { isAutomationUsed, parseTemplateNested, stringToOSCArgs } from '../automation.utils.js';
describe('parseTemplateNested()', () => {
@@ -250,50 +252,109 @@ describe('test stringToOSCArgs()', () => {
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 projectRundowns: ProjectRundowns = {
+ 'rundown-1': {
+ id: 'rundown-1',
+ title: 'Rundown 1',
+ order: ['1'],
+ flatOrder: ['1'],
+ entries: {
+ '1': makeOntimeEvent({
+ id: '1',
+ triggers: [
+ {
+ id: 'trigger-1',
+ title: 'Trigger 1',
+ trigger: TimerLifeCycle.onClock,
+ automationId: 'test-automation',
+ },
+ ],
+ }),
+ },
+ revision: 1,
},
- });
-
- const timedEventOrder = ['1'];
+ };
const automationId = 'test-automation';
- const result = isAutomationUsed(rundown, timedEventOrder, automationId);
- expect(result).toBe('1');
+ const result = isAutomationUsed(projectRundowns, automationId);
+ expect(result).toStrictEqual(['Rundown 1', '1']);
+ });
+
+ it('finds usages in any rundown', () => {
+ const projectRundowns: ProjectRundowns = {
+ 'rundown-1': {
+ id: 'rundown-1',
+ title: 'Rundown 1',
+ order: ['1'],
+ flatOrder: ['1'],
+ entries: {
+ '1': makeOntimeEvent({
+ id: '1',
+ triggers: [
+ {
+ id: 'trigger-1',
+ title: 'Trigger 1',
+ trigger: TimerLifeCycle.onClock,
+ automationId: 'test-automation',
+ },
+ ],
+ }),
+ },
+ revision: 1,
+ },
+ 'rundown-2': {
+ id: 'rundown-2',
+ title: 'Rundown 2',
+ order: ['1'],
+ flatOrder: ['1'],
+ entries: {
+ '1': makeOntimeEvent({
+ id: '1',
+ triggers: [
+ {
+ id: 'trigger-1',
+ title: 'Trigger 1',
+ trigger: TimerLifeCycle.onClock,
+ automationId: 'in-the-second-rundown',
+ },
+ ],
+ }),
+ },
+ revision: 1,
+ },
+ };
+ const automationId = 'in-the-second-rundown';
+
+ const result = isAutomationUsed(projectRundowns, automationId);
+ expect(result).toStrictEqual(['Rundown 2', '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 projectRundowns: ProjectRundowns = {
+ 'rundown-1': {
+ id: 'rundown-1',
+ title: 'Rundown 1',
+ order: ['1'],
+ flatOrder: ['1'],
+ entries: {
+ '1': makeOntimeEvent({
+ id: '1',
+ triggers: [
+ {
+ id: 'trigger-1',
+ title: 'Trigger 1',
+ trigger: TimerLifeCycle.onClock,
+ automationId: 'test-automation',
+ },
+ ],
+ }),
+ },
+ revision: 1,
},
- });
-
- const timedEventOrder = ['1'];
+ };
const automationId = 'does-not-exist';
- const result = isAutomationUsed(rundown, timedEventOrder, automationId);
+ const result = isAutomationUsed(projectRundowns, automationId);
expect(result).toBeUndefined();
});
});
diff --git a/apps/server/src/api-data/automation/automation.controller.ts b/apps/server/src/api-data/automation/automation.controller.ts
index cc797dee7..9e4a84691 100644
--- a/apps/server/src/api-data/automation/automation.controller.ts
+++ b/apps/server/src/api-data/automation/automation.controller.ts
@@ -4,8 +4,7 @@ import { Automation, AutomationSettings, ErrorResponse, Trigger } from 'ontime-t
import type { Request, Response } from 'express';
import { oscServer } from '../../adapters/OscAdapter.js';
-
-import { getCurrentRundown, getRundownMetadata } from '../rundown/rundown.dao.js';
+import { getDataProvider } from '../../classes/data-provider/DataProvider.js';
import * as automationDao from './automation.dao.js';
import * as automationService from './automation.service.js';
@@ -108,10 +107,8 @@ export async function editAutomation(req: Request, res: Response) {
try {
- const rundown = getCurrentRundown();
- const { timedEventOrder } = getRundownMetadata();
-
- await automationDao.deleteAutomation(rundown, timedEventOrder, req.params.id);
+ const projectRundowns = getDataProvider().getProjectRundowns();
+ await automationDao.deleteAutomation(projectRundowns, req.params.id);
res.status(204).send();
} catch (error) {
const message = getErrorMessage(error);
diff --git a/apps/server/src/api-data/automation/automation.dao.ts b/apps/server/src/api-data/automation/automation.dao.ts
index e882675cc..63bbcd61a 100644
--- a/apps/server/src/api-data/automation/automation.dao.ts
+++ b/apps/server/src/api-data/automation/automation.dao.ts
@@ -2,9 +2,8 @@ import type {
Automation,
AutomationDTO,
AutomationSettings,
- EntryId,
NormalisedAutomation,
- Rundown,
+ ProjectRundowns,
Trigger,
TriggerDTO,
} from 'ontime-types';
@@ -136,15 +135,15 @@ export async function editAutomation(id: string, newAutomation: AutomationDTO):
/**
* Deletes a automation given its ID
*/
-export async function deleteAutomation(rundown: Rundown, timedEventOrder: EntryId[], id: string): Promise {
+export async function deleteAutomation(projectRundowns: ProjectRundowns, automationId: string): Promise {
const automations = getAutomations();
// ignore request if automation does not exist
- if (!Object.hasOwn(automations, id)) {
+ if (!Object.hasOwn(automations, automationId)) {
return;
}
// prevent deleting a automation that is in use in triggers
- const triggers = getAutomationTriggers().filter((trigger) => trigger.automationId === id);
+ const triggers = getAutomationTriggers().filter((trigger) => trigger.automationId === automationId);
if (triggers.length) {
throw new Error(
`Unable to delete automation used in trigger ${triggers[0].title}${triggers.length > 1 ? ` and ${triggers.length - 1} more` : ''}`,
@@ -152,12 +151,12 @@ export async function deleteAutomation(rundown: Rundown, timedEventOrder: EntryI
}
// prevent deleting a automation that is in use in events
- const isInUse = isAutomationUsed(rundown, timedEventOrder, id);
+ const isInUse = isAutomationUsed(projectRundowns, automationId);
if (isInUse) {
- throw new Error(`Unable to delete automation used in event with ID ${isInUse}`);
+ throw new Error(`Unable to delete automation used in rundown: ${isInUse[0]}, in event with ID: ${isInUse[1]}`);
}
- delete automations[id];
+ delete automations[automationId];
await saveChanges({ automations });
}
diff --git a/apps/server/src/api-data/automation/automation.utils.ts b/apps/server/src/api-data/automation/automation.utils.ts
index 41856bc8c..357b2138d 100644
--- a/apps/server/src/api-data/automation/automation.utils.ts
+++ b/apps/server/src/api-data/automation/automation.utils.ts
@@ -1,4 +1,13 @@
-import { EntryId, FilterRule, isOntimeEvent, MaybeNumber, OntimeAction, ontimeActionKeyValues, Rundown } from 'ontime-types';
+import {
+ EntryId,
+ FilterRule,
+ isOntimeEvent,
+ MaybeNumber,
+ OntimeAction,
+ ontimeActionKeyValues,
+ ProjectRundowns,
+ RundownEntries,
+} from 'ontime-types';
import { millisToString, removeLeadingZero, splitWhitespace, getPropertyFromPath } from 'ontime-utils';
import type { OscArgOrArrayInput, OscArgInput } from 'osc-min';
@@ -198,22 +207,40 @@ export function isBooleanEquals(a: boolean, b: string): boolean {
/**
* 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[],
+function isAutomationUsedInRundown(
+ entries: RundownEntries,
+ flatOrder: 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) {
+ for (let i = 0; i < flatOrder.length; i++) {
+ const eventId = flatOrder[i];
+ const entry = entries[eventId];
+
+ // only ontime events can contain triggers
+ if (isOntimeEvent(entry) && entry.triggers) {
+ for (const trigger of entry.triggers) {
if (trigger.automationId === automationId) {
- return eventId;
+ return entry.id;
}
}
}
}
}
+
+/**
+ * Checks if an automation is used in any of the project rundowns
+ */
+export function isAutomationUsed(
+ projectRundowns: ProjectRundowns,
+ automationId: string,
+): [string, EntryId] | undefined {
+ for (const rundownId in projectRundowns) {
+ const rundown = projectRundowns[rundownId];
+ const usedInEvent = isAutomationUsedInRundown(rundown.entries, rundown.flatOrder, automationId);
+
+ if (usedInEvent) {
+ return [rundown.title, usedInEvent];
+ }
+ }
+}