refactor: normalise data (#756)

* chore: remove legal from bundle

* refactor: create normalised dataset

* refactor: cuesheet uses flat rundown

* refactor: multi-selection

* refactor: prevent stale data on server restart

* refactor: increase ID size

* chore: instrument operation

* chore: update csv tests

* fix: resolve directory to test-db (#758)
This commit is contained in:
Carlos Valente
2024-02-03 21:43:17 +01:00
committed by GitHub
parent 47a519bac1
commit 1890fc49d7
62 changed files with 1838 additions and 3341 deletions
@@ -1,119 +1,87 @@
import {
LogOrigin,
OntimeBaseEvent,
OntimeBlock,
OntimeDelay,
OntimeEvent,
SupportedEvent,
OntimeRundown,
OntimeRundownEntry,
isOntimeBlock,
isOntimeDelay,
isOntimeEvent,
} from 'ontime-types';
import { generateId, getCueCandidate } from 'ontime-utils';
import { getCueCandidate } from 'ontime-utils';
import { DataProvider } from '../../classes/data-provider/DataProvider.js';
import { block as blockDef, delay as delayDef } from '../../models/eventsDefinition.js';
import { sendRefetch } from '../../adapters/websocketAux.js';
import { runtimeCacheStore } from '../../stores/cachingStore.js';
import {
cachedAdd,
cachedApplyDelay,
cachedClear,
cachedDelete,
cachedEdit,
cachedBatchEdit,
cachedReorder,
cachedSwap,
delayedRundownCacheKey,
} from './rundownCache.js';
import { logger } from '../../classes/Logger.js';
import { createEvent } from '../../utils/parser.js';
import { updateNumEvents } from '../../stores/runtimeState.js';
import { runtimeService } from '../runtime-service/RuntimeService.js';
/**
* Forces rundown to be recalculated
* To be used when we know the rundown has changed completely
*/
export function forceReset() {
runtimeService.reset();
runtimeCacheStore.invalidate(delayedRundownCacheKey);
import * as cache from './rundownCache.js';
function generateEvent(eventData: Partial<OntimeEvent> | Partial<OntimeDelay> | Partial<OntimeBlock>) {
// we discard any UI provided events and add our own
const id = cache.getUniqueId();
if (isOntimeEvent(eventData)) {
return createEvent(eventData, getCueCandidate(DataProvider.getRundown(), eventData?.after)) as OntimeEvent;
}
if (isOntimeDelay(eventData)) {
return { ...delayDef, duration: eventData.duration ?? 0, id } as OntimeDelay;
}
if (isOntimeBlock(eventData)) {
return { ...blockDef, title: eventData?.title ?? '', id } as OntimeBlock;
}
throw new Error('Invalid event type');
}
/**
* @description creates a new event with given data
* @param {object} eventData
* @return {unknown[]}
* @return {OntimeRundownEntry}
*/
export async function addEvent(eventData: Partial<OntimeEvent> | Partial<OntimeDelay> | Partial<OntimeBlock>) {
let newEvent: Partial<OntimeBaseEvent> = {};
const id = generateId();
let insertIndex = 0;
// if the user didnt provide an index, we add the event to start
let atIndex = 0;
if (eventData?.after !== undefined) {
const index = DataProvider.getIndexOf(eventData.after);
if (index < 0) {
const previousIndex = cache.getIndexOf(eventData.after);
if (previousIndex < 0) {
logger.warning(LogOrigin.Server, `Could not find event with id ${eventData.after}`);
} else {
insertIndex = index + 1;
atIndex = previousIndex + 1;
}
}
switch (eventData.type) {
case SupportedEvent.Event: {
newEvent = createEvent(eventData, getCueCandidate(DataProvider.getRundown(), eventData?.after)) as OntimeEvent;
break;
}
case SupportedEvent.Delay:
newEvent = { ...delayDef, duration: eventData.duration, id } as OntimeDelay;
break;
case SupportedEvent.Block:
newEvent = { ...blockDef, title: eventData.title, id } as OntimeBlock;
break;
}
delete eventData.after;
// generate a fully formed event from the patch
const eventToAdd = generateEvent(eventData);
// modify rundown
await cachedAdd(insertIndex, newEvent as OntimeEvent | OntimeDelay | OntimeBlock);
notifyChanges({ timer: [id], external: true });
// notify event loader that rundown size has changed
updateChangeNumEvents();
return newEvent;
}
export async function editEvent(eventData: Partial<OntimeEvent> | Partial<OntimeBlock> | Partial<OntimeDelay>) {
if (!eventData?.id) {
throw new Error('Event misses ID');
}
if (isOntimeEvent(eventData) && eventData?.cue === '') {
throw new Error('Cue value invalid');
}
const newEvent = await cachedEdit(eventData.id, eventData);
const scopedMutation = cache.mutateCache(cache.add);
const { newEvent } = await scopedMutation({ atIndex, event: eventToAdd as OntimeRundownEntry });
notifyChanges({ timer: [newEvent.id], external: true });
// notify runtime that rundown size has changed
updateChangeNumEvents();
return newEvent;
}
export async function batchEditEvents(ids: string[], data: Partial<OntimeEvent>) {
await cachedBatchEdit(ids, data);
// notify runtime service of changed events
runtimeService.update(ids);
// advice socket subscribers of change
sendRefetch();
}
/**
* deletes event by its ID
* @param eventId
* @returns {Promise<void>}
*/
export async function deleteEvent(eventId: string) {
await cachedDelete(eventId);
const scopedMutation = cache.mutateCache(cache.remove);
await scopedMutation({ eventId });
notifyChanges({ timer: [eventId], external: true });
// notify event loader that rundown size has changed
updateChangeNumEvents();
}
@@ -123,10 +91,31 @@ export async function deleteEvent(eventId: string) {
* @returns {Promise<void>}
*/
export async function deleteAllEvents() {
await cachedClear();
const scopedMutation = cache.mutateCache(cache.removeAll);
await scopedMutation({});
// no need to modify timer since we will reset
notifyChanges({ external: true, reset: true });
notifyChanges({ external: true });
}
export async function editEvent(patch: Partial<OntimeEvent> | Partial<OntimeBlock> | Partial<OntimeDelay>) {
if (isOntimeEvent(patch) && patch?.cue === '') {
throw new Error('Cue value invalid');
}
const scopedMutation = cache.mutateCache(cache.edit);
const { newEvent } = await scopedMutation({ patch, eventId: patch.id });
notifyChanges({ timer: [patch.id], external: true });
return newEvent;
}
export async function batchEditEvents(ids: string[], data: Partial<OntimeEvent>) {
const scopedMutation = cache.mutateCache(cache.batchEdit);
await scopedMutation({ patch: data, eventIds: ids });
notifyChanges({ timer: ids, external: true });
}
/**
@@ -137,7 +126,8 @@ export async function deleteAllEvents() {
* @returns {Promise<void>}
*/
export async function reorderEvent(eventId: string, from: number, to: number) {
const reorderedItem = await cachedReorder(eventId, from, to);
const scopedMutation = cache.mutateCache(cache.reorder);
const reorderedItem = await scopedMutation({ eventId, from, to });
notifyChanges({ timer: true, external: true });
@@ -145,7 +135,8 @@ export async function reorderEvent(eventId: string, from: number, to: number) {
}
export async function applyDelay(eventId: string) {
await cachedApplyDelay(eventId);
const scopedMutation = cache.mutateCache(cache.applyDelay);
await scopedMutation({ eventId });
notifyChanges({ timer: true, external: true });
}
@@ -157,7 +148,8 @@ export async function applyDelay(eventId: string) {
* @returns {Promise<void>}
*/
export async function swapEvents(from: string, to: string) {
await cachedSwap(from, to);
const scopedMutation = cache.mutateCache(cache.swap);
await scopedMutation({ fromId: from, toId: to });
notifyChanges({ timer: true, external: true });
}
@@ -174,7 +166,7 @@ function updateChangeNumEvents() {
/**
* Notify services of changes in the rundown
*/
export function notifyChanges(options: { timer?: boolean | string[]; external?: boolean; reset?: boolean }) {
export function notifyChanges(options: { timer?: boolean | string[]; external?: boolean }) {
if (options.timer) {
// notify timer service of changed events
// timer can be true or an array of changed IDs
@@ -184,17 +176,20 @@ export function notifyChanges(options: { timer?: boolean | string[]; external?:
runtimeService.update();
}
if (options.reset) {
// force rundown to be recalculated
forceReset();
}
if (options.external) {
// advice socket subscribers of change
sendRefetch();
}
}
/**
* returns entire unfiltered rundown
* @return {array}
*/
export function getRundown(): OntimeRundown {
return DataProvider.getRundown();
}
/**
* returns all events of type OntimeEvent
* @return {array}
@@ -290,3 +285,9 @@ export function findNext(currentEventId?: string): OntimeEvent | null {
const nextEvent = timedEvents.at(newIndex);
return nextEvent ?? null;
}
export async function setRundown(rundown: OntimeRundown) {
await DataProvider.setRundown(rundown);
cache.init(rundown);
notifyChanges({ timer: true });
}