Link start times from excel import (#903)

* add test for linking start time in excel import

* link ids

* add link to UI

* ensure times with cachedRundown

---------

Co-authored-by: Carlos Valente <carlosvalente@pm.me>
This commit is contained in:
Alex Christoffer Rasmussen
2024-04-26 17:09:59 +01:00
committed by GitHub
parent d8a8f0ad92
commit 74418ce17e
9 changed files with 157 additions and 22 deletions
+69 -1
View File
@@ -19,6 +19,8 @@ import { dbModel } from '../../models/dataModel.js';
import { createEvent, getCustomFieldData, parseExcel, parseJson } from '../parser.js';
import { makeString } from '../parserUtils.js';
import { parseRundown, parseUrlPresets, parseViewSettings } from '../parserFunctions.js';
import { ImportMap } from 'ontime-utils';
import * as cache from '../../services/rundown-service/rundownCache.js';
describe('test json parser with valid def', () => {
const testData: Partial<DatabaseModel> = {
@@ -734,6 +736,7 @@ describe('getCustomFieldData()', () => {
const importMap = {
worksheet: 'event schedule',
timeStart: 'time start',
linkStart: 'link start',
timeEnd: 'time end',
duration: 'duration',
cue: 'cue',
@@ -751,7 +754,7 @@ describe('getCustomFieldData()', () => {
sound: 'sound',
video: 'av',
},
};
} as ImportMap;
const result = getCustomFieldData(importMap);
expect(result.customFields).toStrictEqual({
@@ -1440,4 +1443,69 @@ describe('parseExcel()', () => {
expect(events.at(0).title).toEqual('A song from the hearth'); //<--leading white space in Excel data
expect(events.at(0).colour).toEqual('#F00'); //<--trailing white space in Excel data
});
it('link start', () => {
const testData = [
[
'Time Start',
'Time End',
'Title',
'End Action',
'Public',
'Skip',
'Notes',
'Colour',
'cue',
'Link Start',
'Timer type',
],
['4:30:00', '9:45:00', 'A', 'load-next', '', '', 'Rainbow chase', '#F00', 102, '', 'count-down'],
['9:45:00', '10:56:00', 'C', 'load-next', 'x', '', 'Rainbow chase', '#0F0', 103, 'x', 'count-down'],
['10:00:00', '16:36:00', 'D', 'load-next', 'x', '', 'Rainbow chase', '#F00', 102, 'x', 'count-down'], //<-- incorrect start times are overridden
['21:45:00', '22:56:00', 'E', 'load-next', 'x', '', 'Rainbow chase', '#0F0', 103, '', 'count-down'],
['', '', 'BLOCK', '', '', '', '', '', '', '', 'block'],
['00:0:00', '23:56:00', 'G', 'load-next', 'x', '', 'Rainbow chase', '#0F0', 103, 'x', 'count-down'], //<-- link past blocks
[],
];
const importMap = {
worksheet: 'event schedule',
timeStart: 'time start',
linkStart: 'link start',
timeEnd: 'time end',
duration: 'duration',
cue: 'cue',
title: 'title',
isPublic: 'public',
skip: 'skip',
note: 'notes',
colour: 'colour',
endAction: 'end action',
timerType: 'timer type',
timeWarning: 'warning time',
timeDanger: 'danger time',
custom: {},
};
const result = parseExcel(testData, importMap);
const rundown = parseRundown(result);
cache.init(rundown, {});
const cachedRundown = cache.get().rundown;
const events = Object.values(cachedRundown).filter((e) => e.type === SupportedEvent.Event) as OntimeEvent[];
expect(events.at(0).timeStart).toEqual(16200000);
expect(events.at(1).timeStart).toEqual(events.at(0).timeEnd);
expect(events.at(1).linkStart).toEqual(events.at(0).id);
expect(events.at(2).timeStart).toEqual(events.at(1).timeEnd);
expect(events.at(2).linkStart).toEqual(events.at(1).id);
expect(events.at(3).timeStart).toEqual(78300000);
expect(events.at(4).timeStart).toEqual(events.at(3).timeEnd);
expect(events.at(4).linkStart).toEqual(events.at(3).id);
});
});
+18 -3
View File
@@ -34,7 +34,6 @@ import {
parseViewSettings,
} from './parserFunctions.js';
import { parseExcelDate } from './time.js';
import { coerceBoolean } from './coerceType.js';
export const EXCEL_MIME = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
export const JSON_MIME = 'application/json';
@@ -43,6 +42,14 @@ type ExcelData = Pick<DatabaseModel, 'rundown' | 'customFields'> & {
rundownMetadata: Record<string, { row: number; col: number }>;
};
function parseBooleanString(value: unknown): boolean {
// falsy values would be nullish or empty string
if (!value || typeof value !== 'string') {
return false;
}
return value.toLowerCase() !== 'false';
}
export function getCustomFieldData(importMap: ImportMap): {
customFields: CustomFields;
customFieldImportKeys: Record<keyof CustomFields, string>;
@@ -91,6 +98,8 @@ export const parseExcel = (excelData: unknown[][], options?: Partial<ImportMap>)
let isPublicIndex: number | null = null;
let skipIndex: number | null = null;
let linkStartIndex: number | null = null;
// times: numbers
let timeStartIndex: number | null = null;
let timeEndIndex: number | null = null;
@@ -116,6 +125,10 @@ export const parseExcel = (excelData: unknown[][], options?: Partial<ImportMap>)
timeStartIndex = col;
rundownMetadata['timeStart'] = { row, col };
},
[importMap.linkStart]: (row: number, col: number) => {
linkStartIndex = col;
rundownMetadata['linkStart'] = { row, col };
},
[importMap.timeEnd]: (row: number, col: number) => {
timeEndIndex = col;
rundownMetadata['timeEnd'] = { row, col };
@@ -191,6 +204,8 @@ export const parseExcel = (excelData: unknown[][], options?: Partial<ImportMap>)
event.title = makeString(column, '');
} else if (j === timeStartIndex) {
event.timeStart = parseExcelDate(column);
} else if (j === linkStartIndex) {
event.linkStart = parseBooleanString(column);
} else if (j === timeEndIndex) {
event.timeEnd = parseExcelDate(column);
} else if (j === durationIndex) {
@@ -198,9 +213,9 @@ export const parseExcel = (excelData: unknown[][], options?: Partial<ImportMap>)
} else if (j === cueIndex) {
event.cue = makeString(column, '');
} else if (j === isPublicIndex) {
event.isPublic = column == 'x' ? true : coerceBoolean(column);
event.isPublic = parseBooleanString(column);
} else if (j === skipIndex) {
event.skip = column == 'x' ? true : coerceBoolean(column);
event.skip = parseBooleanString(column);
} else if (j === notesIndex) {
event.note = makeString(column, '');
} else if (j === endActionIndex) {
+6 -1
View File
@@ -18,7 +18,7 @@ import {
isOntimeDelay,
isOntimeEvent,
} from 'ontime-types';
import { generateId } from 'ontime-utils';
import { generateId, getLastEvent } from 'ontime-utils';
import { dbModel } from '../models/dataModel.js';
import { block as blockDef, delay as delayDef } from '../models/eventsDefinition.js';
@@ -48,11 +48,16 @@ export const parseRundown = (data: Partial<DatabaseModel>): OntimeRundown => {
let newEvent: OntimeEvent | OntimeDelay | OntimeBlock | null;
if (isOntimeEvent(event)) {
if (event.linkStart) {
const prevEvent = getLastEvent(rundown).lastEvent;
event.linkStart = prevEvent.id;
}
newEvent = createEvent(event, eventIndex.toString());
// skip if event is invalid
if (newEvent == null) {
continue;
}
eventIndex += 1;
} else if (isOntimeDelay(event)) {
newEvent = { ...delayDef, duration: event.duration, id };