mirror of
https://github.com/cpvalente/ontime.git
synced 2026-09-01 20:39:18 +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:
@@ -11,6 +11,8 @@
|
||||
*/
|
||||
|
||||
import {
|
||||
CustomField,
|
||||
CustomFieldKey,
|
||||
CustomFields,
|
||||
EntryId,
|
||||
isOntimeBlock,
|
||||
@@ -23,13 +25,11 @@ import {
|
||||
PatchWithId,
|
||||
Rundown,
|
||||
} from 'ontime-types';
|
||||
import { insertAtIndex } from 'ontime-utils';
|
||||
import { customFieldLabelToKey, insertAtIndex } from 'ontime-utils';
|
||||
|
||||
import { makeRundownMetadata, ProcessedRundownMetadata } from '../../services/rundown-service/rundownCache.utils.js';
|
||||
import { customFieldChangelog } from '../../services/rundown-service/rundownCache.js';
|
||||
import { getDataProvider } from '../../classes/data-provider/DataProvider.js';
|
||||
|
||||
import type { RundownMetadata } from './rundown.types.js';
|
||||
import type { AssignedMap, CustomFieldsMetadata, RundownMetadata } from './rundown.types.js';
|
||||
import {
|
||||
applyPatchToEntry,
|
||||
cloneBlock,
|
||||
@@ -39,6 +39,7 @@ import {
|
||||
doesInvalidateMetadata,
|
||||
getUniqueId,
|
||||
} from './rundown.utils.js';
|
||||
import { makeRundownMetadata, ProcessedRundownMetadata } from './rundown.parser.js';
|
||||
|
||||
/**
|
||||
* The currently loaded rundown in cache
|
||||
@@ -62,8 +63,15 @@ let rundownMetadata: RundownMetadata = {
|
||||
playableEventOrder: [],
|
||||
timedEventOrder: [],
|
||||
flatEntryOrder: [],
|
||||
};
|
||||
|
||||
assignedCustomFields: {},
|
||||
const customFieldsMetadata: CustomFieldsMetadata = {
|
||||
/**
|
||||
* Keep track of which custom fields are used.
|
||||
* This will be handy for when we delete custom fields
|
||||
* since we can clear the custom fields from every event where they are used
|
||||
*/
|
||||
assigned: {},
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -73,47 +81,103 @@ let rundownMetadata: RundownMetadata = {
|
||||
let projectCustomFields: CustomFields = {};
|
||||
|
||||
export const getCurrentRundown = (): Readonly<Rundown> => cachedRundown;
|
||||
export const getRundownMetadata = (): Readonly<RundownMetadata> => rundownMetadata;
|
||||
export const getProjectCustomFields = (): Readonly<CustomFields> => projectCustomFields;
|
||||
export const getEntryWithId = (entryId: EntryId): OntimeEntry | undefined => cachedRundown.entries[entryId];
|
||||
|
||||
export function createTransaction() {
|
||||
const rundown = structuredClone(cachedRundown);
|
||||
const customFields = projectCustomFields;
|
||||
type Transaction = {
|
||||
customFields: CustomFields;
|
||||
customFieldsMetadata: Readonly<CustomFieldsMetadata>;
|
||||
rundown: Rundown;
|
||||
rundownMetadata: Readonly<RundownMetadata>;
|
||||
|
||||
commit: (shouldProcess?: boolean) => {
|
||||
rundown: Readonly<Rundown>;
|
||||
rundownMetadata: Readonly<RundownMetadata>;
|
||||
customFields: Readonly<CustomFields>;
|
||||
revision: Readonly<number>;
|
||||
};
|
||||
};
|
||||
|
||||
type TransactionOptions = {
|
||||
mutableRundown?: boolean;
|
||||
mutableCustomFields?: boolean;
|
||||
};
|
||||
|
||||
export function createTransaction(options: TransactionOptions): Transaction {
|
||||
const rundown = options.mutableRundown ? structuredClone(cachedRundown) : cachedRundown;
|
||||
const customFields = options.mutableCustomFields ? structuredClone(projectCustomFields) : projectCustomFields;
|
||||
|
||||
/**
|
||||
* Applies a mutated rundown to the cache
|
||||
* @param shouldProcess - whether the rundown should be processed after the commit
|
||||
* Some edit mutations, and custom field changes do not require processing
|
||||
*/
|
||||
function commit(shouldProcess: boolean = true) {
|
||||
// schedule a database update
|
||||
setImmediate(async () => {
|
||||
await getDataProvider().setRundown(cachedRundown.id, cachedRundown);
|
||||
});
|
||||
// if the rundown is mutable we persist the changes
|
||||
if (options.mutableRundown) {
|
||||
// schedule a database update
|
||||
setImmediate(async () => {
|
||||
await getDataProvider().setRundown(cachedRundown.id, cachedRundown);
|
||||
});
|
||||
|
||||
const revision = rundown.revision + 1;
|
||||
cachedRundown.revision = revision;
|
||||
// increment the revision number
|
||||
cachedRundown.revision = cachedRundown.revision + 1;
|
||||
|
||||
/**
|
||||
* Some mutations do not require processing the rundown
|
||||
* We simply increment the revision and return the rundown
|
||||
*/
|
||||
if (!shouldProcess) {
|
||||
cachedRundown.entries = rundown.entries;
|
||||
cachedRundown.order = rundown.order;
|
||||
cachedRundown.flatOrder = rundown.flatOrder;
|
||||
return { rundown, rundownMetadata, customFields: projectCustomFields, revision: cachedRundown.revision };
|
||||
/**
|
||||
* Some mutations do not require processing the rundown
|
||||
* We simply increment the revision and return the rundown
|
||||
*/
|
||||
if (!shouldProcess) {
|
||||
cachedRundown.title = rundown.title;
|
||||
cachedRundown.entries = rundown.entries;
|
||||
cachedRundown.order = rundown.order;
|
||||
cachedRundown.flatOrder = rundown.flatOrder;
|
||||
return {
|
||||
rundown: cachedRundown,
|
||||
rundownMetadata, // metadata doesnt change as long as we dont process the rundown
|
||||
customFields: projectCustomFields,
|
||||
revision: cachedRundown.revision,
|
||||
};
|
||||
}
|
||||
|
||||
const processedData = processRundown(rundown, projectCustomFields);
|
||||
// update the cache values
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars -- we are not interested in the iteration data
|
||||
const { previousEvent, latestEvent, previousEntry, entries, order, assignedCustomFields, ...metadata } =
|
||||
processedData;
|
||||
|
||||
cachedRundown.title = rundown.title;
|
||||
cachedRundown.entries = entries;
|
||||
cachedRundown.order = order;
|
||||
cachedRundown.flatOrder = metadata.flatEntryOrder; // TODO: remove in favour of the metadata flatEntryOrder
|
||||
customFieldsMetadata.assigned = assignedCustomFields;
|
||||
rundownMetadata = metadata;
|
||||
}
|
||||
|
||||
const processedData = processRundown(rundown, projectCustomFields);
|
||||
// update the cache values
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars -- we are not interested in the iteration data
|
||||
const { previousEvent, latestEvent, previousEntry, entries, order, ...metadata } = processedData;
|
||||
cachedRundown.entries = entries;
|
||||
cachedRundown.order = order;
|
||||
cachedRundown.flatOrder = metadata.flatEntryOrder; // TODO: remove in favour of the metadata flatEntryOrder
|
||||
rundownMetadata = metadata;
|
||||
// if the customFields are mutable we persist the changes
|
||||
if (options.mutableCustomFields) {
|
||||
// schedule a database update
|
||||
setImmediate(async () => {
|
||||
await getDataProvider().setCustomFields(projectCustomFields);
|
||||
});
|
||||
|
||||
return { rundown, rundownMetadata, customFields: projectCustomFields, revision: cachedRundown.revision };
|
||||
projectCustomFields = customFields;
|
||||
}
|
||||
|
||||
return {
|
||||
rundown: cachedRundown,
|
||||
rundownMetadata,
|
||||
customFields: projectCustomFields,
|
||||
revision: cachedRundown.revision,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
customFields,
|
||||
customFieldsMetadata,
|
||||
rundown,
|
||||
rundownMetadata,
|
||||
commit,
|
||||
};
|
||||
}
|
||||
@@ -329,7 +393,7 @@ function applyDelay(rundown: Rundown, delay: OntimeDelay) {
|
||||
/**
|
||||
* Swaps the data between two events
|
||||
* The schedule and metadata are preserved
|
||||
* TODO: this logic is for now duplcate of Ontime-Utils.swapEventData
|
||||
* TODO: this logic is for now duplicate of Ontime-Utils.swapEventData
|
||||
*/
|
||||
function swap(rundown: Rundown, eventFrom: OntimeEvent, eventTo: OntimeEvent) {
|
||||
rundown.entries[eventFrom.id] = {
|
||||
@@ -484,6 +548,100 @@ export const rundownMutation = {
|
||||
ungroup,
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds a new custom field to the object and returns it
|
||||
*/
|
||||
function customFieldAdd(customFields: CustomFields, key: CustomFieldKey, newCustomField: CustomField): CustomFields {
|
||||
customFields[key] = {
|
||||
label: newCustomField.label,
|
||||
type: newCustomField.type,
|
||||
colour: newCustomField.colour,
|
||||
};
|
||||
|
||||
return { [key]: newCustomField };
|
||||
}
|
||||
|
||||
/**
|
||||
* Edits an existing custom field
|
||||
*/
|
||||
function customFieldEdit(
|
||||
customFields: CustomFields,
|
||||
key: CustomFieldKey,
|
||||
existingField: CustomField,
|
||||
newField: Partial<CustomField>,
|
||||
): { oldKey: CustomFieldKey; newKey: CustomFieldKey } {
|
||||
// calculate the key in case it has changed
|
||||
const newKey = newField?.label ? customFieldLabelToKey(newField.label ?? key) : key;
|
||||
|
||||
// patch the new field and replace the reference in the object
|
||||
customFields[newKey] = { ...existingField, ...newField };
|
||||
|
||||
return { oldKey: key, newKey };
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a custom field from the object
|
||||
*/
|
||||
function customFieldRemove(customFields: CustomFields, key: CustomFieldKey) {
|
||||
delete customFields[key];
|
||||
}
|
||||
|
||||
/**
|
||||
* Renames a custom field key in all the rundown entries that use it
|
||||
*/
|
||||
function customFieldRenameUsages(
|
||||
rundown: Rundown,
|
||||
assigned: AssignedMap,
|
||||
oldKey: CustomFieldKey,
|
||||
newKey: CustomFieldKey,
|
||||
) {
|
||||
const usages = assigned[oldKey];
|
||||
|
||||
// iterate through all the entries that use the custom field
|
||||
for (let i = 0; i < usages.length; i++) {
|
||||
const entryId = usages[i];
|
||||
const entry = rundown.entries[entryId] as OntimeEvent;
|
||||
|
||||
// copy the data a new key and delete the old key
|
||||
entry.custom[newKey] = entry.custom[oldKey];
|
||||
delete entry.custom[oldKey];
|
||||
}
|
||||
|
||||
// update assignment
|
||||
assigned[newKey] = [...assigned[oldKey]];
|
||||
delete assigned[oldKey];
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes data for a custom field from all the entries that use it
|
||||
*/
|
||||
function customFieldRemoveUsages(rundown: Rundown, assigned: AssignedMap, key: CustomFieldKey) {
|
||||
const usages = assigned[key];
|
||||
if (!usages) {
|
||||
return;
|
||||
}
|
||||
|
||||
// iterate through all the entries that use the custom field
|
||||
for (let i = 0; i < usages.length; i++) {
|
||||
const entryId = usages[i];
|
||||
const entry = rundown.entries[entryId] as OntimeEvent;
|
||||
|
||||
// delete the custom field entry
|
||||
delete entry.custom[key];
|
||||
}
|
||||
|
||||
// update assignment
|
||||
delete assigned[key];
|
||||
}
|
||||
|
||||
export const customFieldMutation = {
|
||||
add: customFieldAdd,
|
||||
edit: customFieldEdit,
|
||||
remove: customFieldRemove,
|
||||
renameUsages: customFieldRenameUsages,
|
||||
removeUsages: customFieldRemoveUsages,
|
||||
};
|
||||
|
||||
/**
|
||||
* Expose function to add an initial rundown to the system
|
||||
*/
|
||||
@@ -498,11 +656,13 @@ export function init(initialRundown: Readonly<Rundown>, initialCustomFields: Rea
|
||||
projectCustomFields = customFields;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars -- we are not interested in the iteration data
|
||||
const { previousEvent, latestEvent, previousEntry, entries, order, ...metadata } = processedData;
|
||||
const { previousEvent, latestEvent, previousEntry, entries, order, assignedCustomFields, ...metadata } =
|
||||
processedData;
|
||||
cachedRundown.entries = entries;
|
||||
cachedRundown.order = order;
|
||||
cachedRundown.flatOrder = metadata.flatEntryOrder; // TODO: remove in favour of the metadata flatEntryOrder
|
||||
cachedRundown.revision = rundown.revision;
|
||||
customFieldsMetadata.assigned = assignedCustomFields;
|
||||
rundownMetadata = metadata;
|
||||
|
||||
// defer writing to the database
|
||||
@@ -533,7 +693,7 @@ export function processRundown(
|
||||
initialRundown: Readonly<Rundown>,
|
||||
customFields: Readonly<CustomFields>,
|
||||
): ProcessedRundownMetadata {
|
||||
const { process, getMetadata } = makeRundownMetadata(customFields, customFieldChangelog);
|
||||
const { process, getMetadata } = makeRundownMetadata(customFields);
|
||||
|
||||
for (let i = 0; i < initialRundown.order.length; i++) {
|
||||
// we assign a reference to the current entry, this will be mutated in place
|
||||
|
||||
Reference in New Issue
Block a user