mirror of
https://github.com/cpvalente/ontime.git
synced 2026-09-10 00:29:41 +00:00
fix: excel parsing
This commit is contained in:
committed by
Carlos Valente
parent
ad46fac2d7
commit
dae6f84af0
@@ -19,7 +19,7 @@ import { dbModel } from '../../models/dataModel.js';
|
|||||||
import { createEvent, getCustomFieldData, parseExcel, parseJson } from '../parser.js';
|
import { createEvent, getCustomFieldData, parseExcel, parseJson } from '../parser.js';
|
||||||
import { makeString } from '../parserUtils.js';
|
import { makeString } from '../parserUtils.js';
|
||||||
import { parseRundown, parseUrlPresets, parseViewSettings } from '../parserFunctions.js';
|
import { parseRundown, parseUrlPresets, parseViewSettings } from '../parserFunctions.js';
|
||||||
import { ImportMap } from 'ontime-utils';
|
import { ImportMap, MILLIS_PER_MINUTE } from 'ontime-utils';
|
||||||
import * as cache from '../../services/rundown-service/rundownCache.js';
|
import * as cache from '../../services/rundown-service/rundownCache.js';
|
||||||
|
|
||||||
describe('test json parser with valid def', () => {
|
describe('test json parser with valid def', () => {
|
||||||
@@ -1515,4 +1515,131 @@ describe('parseExcel()', () => {
|
|||||||
expect((rundown[sixthId] as OntimeEvent).timeStart).toEqual((rundown[fourthId] as OntimeEvent).timeEnd);
|
expect((rundown[sixthId] as OntimeEvent).timeStart).toEqual((rundown[fourthId] as OntimeEvent).timeEnd);
|
||||||
expect((rundown[sixthId] as OntimeEvent).linkStart).toEqual((rundown[fourthId] as OntimeEvent).id);
|
expect((rundown[sixthId] as OntimeEvent).linkStart).toEqual((rundown[fourthId] as OntimeEvent).id);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('#971 BUG: parses time fields and booleans', () => {
|
||||||
|
const testData = [
|
||||||
|
[
|
||||||
|
'Cue',
|
||||||
|
'Colour',
|
||||||
|
'Time Start',
|
||||||
|
'Time End',
|
||||||
|
'Duration',
|
||||||
|
'Link Start',
|
||||||
|
'Title',
|
||||||
|
'Note',
|
||||||
|
'Timer Type',
|
||||||
|
'End Action',
|
||||||
|
'Warning time',
|
||||||
|
'Danger time',
|
||||||
|
'Public',
|
||||||
|
'Skip',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'SETUP',
|
||||||
|
'',
|
||||||
|
'1899-12-30T07:15:00.000Z',
|
||||||
|
'1899-12-30T08:30:00.000Z',
|
||||||
|
'',
|
||||||
|
'false',
|
||||||
|
'Setup',
|
||||||
|
'',
|
||||||
|
'time-to-end',
|
||||||
|
'none',
|
||||||
|
'15',
|
||||||
|
'00:05:00',
|
||||||
|
'FALSE',
|
||||||
|
'TRUE',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'MEET1',
|
||||||
|
'#779BE7',
|
||||||
|
'1899-12-30T08:30:00.000Z',
|
||||||
|
'1899-12-30T10:00:00.000Z',
|
||||||
|
'',
|
||||||
|
'false',
|
||||||
|
'Meeting 1',
|
||||||
|
'',
|
||||||
|
'time-to-end',
|
||||||
|
'none',
|
||||||
|
15,
|
||||||
|
'00:05:00',
|
||||||
|
'TRUE',
|
||||||
|
'FALSE',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'MEET2',
|
||||||
|
'#779BE7',
|
||||||
|
'1899-12-30T10:00:00.000Z',
|
||||||
|
'',
|
||||||
|
'60',
|
||||||
|
'false',
|
||||||
|
'Meeting 2',
|
||||||
|
'',
|
||||||
|
'time-to-end',
|
||||||
|
'none',
|
||||||
|
'13',
|
||||||
|
'5',
|
||||||
|
'TRUE',
|
||||||
|
'FALSE',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'lunch',
|
||||||
|
'#77C785',
|
||||||
|
'',
|
||||||
|
'1899-12-30T11:30:00.000Z',
|
||||||
|
'',
|
||||||
|
'true',
|
||||||
|
'Lunch',
|
||||||
|
'',
|
||||||
|
'time-to-end',
|
||||||
|
'none',
|
||||||
|
13,
|
||||||
|
5,
|
||||||
|
'FALSE',
|
||||||
|
'FALSE',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'MEET3',
|
||||||
|
'#779BE7',
|
||||||
|
'1899-12-30T11:30:00.000Z',
|
||||||
|
'',
|
||||||
|
90,
|
||||||
|
false,
|
||||||
|
'Meeting 3',
|
||||||
|
'',
|
||||||
|
'count-up',
|
||||||
|
'none',
|
||||||
|
'11',
|
||||||
|
5,
|
||||||
|
'TRUE',
|
||||||
|
'FALSE',
|
||||||
|
],
|
||||||
|
['MEET4', '#779BE7', '', '', 30, true, 'Meeting 4', '', 'count-up', 'none', 11, '00:05:00', 'TRUE', 'FALSE'],
|
||||||
|
];
|
||||||
|
|
||||||
|
const parsedData = parseExcel(testData);
|
||||||
|
const { rundown } = parsedData;
|
||||||
|
|
||||||
|
// elements in bug report
|
||||||
|
// 15 is a number, in which case we parse it as a minutes value
|
||||||
|
expect((rundown.at(1) as OntimeEvent).timeWarning).toBe(15 * MILLIS_PER_MINUTE);
|
||||||
|
|
||||||
|
// in the case where a string is passed, we need to check whether it is an ISO 8601 date
|
||||||
|
expect((rundown.at(2) as OntimeEvent).duration).toBe(60 * MILLIS_PER_MINUTE);
|
||||||
|
expect((rundown.at(2) as OntimeEvent).timeDanger).toBe(5 * MILLIS_PER_MINUTE);
|
||||||
|
|
||||||
|
expect((rundown.at(3) as OntimeEvent).timeWarning).toBe(13 * MILLIS_PER_MINUTE);
|
||||||
|
expect((rundown.at(3) as OntimeEvent).timeDanger).toBe(5 * MILLIS_PER_MINUTE);
|
||||||
|
|
||||||
|
expect((rundown.at(4) as OntimeEvent).duration).toBe(90 * MILLIS_PER_MINUTE);
|
||||||
|
expect((rundown.at(4) as OntimeEvent).linkStart).toBe(false);
|
||||||
|
expect((rundown.at(4) as OntimeEvent).timeWarning).toBe(11 * MILLIS_PER_MINUTE);
|
||||||
|
expect((rundown.at(4) as OntimeEvent).timeDanger).toBe(5 * MILLIS_PER_MINUTE);
|
||||||
|
|
||||||
|
expect((rundown.at(5) as OntimeEvent).duration).toBe(30 * MILLIS_PER_MINUTE);
|
||||||
|
|
||||||
|
// if we get a boolean, we should just use that
|
||||||
|
expect((rundown.at(5) as OntimeEvent).linkStart).toBe(true);
|
||||||
|
expect((rundown.at(5) as OntimeEvent).timeWarning).toBe(11 * MILLIS_PER_MINUTE);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { MILLIS_PER_MINUTE } from 'ontime-utils';
|
||||||
import { parseExcelDate } from '../time.js';
|
import { parseExcelDate } from '../time.js';
|
||||||
|
|
||||||
describe('parseExcelDate', () => {
|
describe('parseExcelDate', () => {
|
||||||
@@ -46,8 +47,18 @@ describe('parseExcelDate', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('uses numeric fields as minutes', () => {
|
||||||
|
const invalidFields = [1, 10, 100];
|
||||||
|
invalidFields.forEach((field) => {
|
||||||
|
it(`handles ${field}`, () => {
|
||||||
|
const millis = parseExcelDate(field);
|
||||||
|
expect(millis).toBe(field * MILLIS_PER_MINUTE);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('returns 0 on other strings', () => {
|
describe('returns 0 on other strings', () => {
|
||||||
const invalidFields = ['10', 'test', ''];
|
const invalidFields = ['test', ''];
|
||||||
invalidFields.forEach((field) => {
|
invalidFields.forEach((field) => {
|
||||||
it(`handles ${field}`, () => {
|
it(`handles ${field}`, () => {
|
||||||
const millis = parseExcelDate(field);
|
const millis = parseExcelDate(field);
|
||||||
|
|||||||
@@ -43,6 +43,10 @@ type ExcelData = Pick<DatabaseModel, 'rundown' | 'customFields'> & {
|
|||||||
};
|
};
|
||||||
|
|
||||||
function parseBooleanString(value: unknown): boolean {
|
function parseBooleanString(value: unknown): boolean {
|
||||||
|
if (typeof value === 'boolean') {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
// falsy values would be nullish or empty string
|
// falsy values would be nullish or empty string
|
||||||
if (!value || typeof value !== 'string') {
|
if (!value || typeof value !== 'string') {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { isTimeString } from 'ontime-utils';
|
import { MILLIS_PER_MINUTE } from 'ontime-utils';
|
||||||
|
import { isISO8601 } from '../../../../packages/utils/src/date-utils/isTimeString.js';
|
||||||
|
|
||||||
const mts = 1000; // millis to seconds
|
const mts = 1000; // millis to seconds
|
||||||
const mtm = 1000 * 60; // millis to minutes
|
const mtm = 1000 * 60; // millis to minutes
|
||||||
@@ -49,10 +50,9 @@ const stripAMPM = (value: string) => {
|
|||||||
/**
|
/**
|
||||||
* @description Parses a time string to millis, copied from client code
|
* @description Parses a time string to millis, copied from client code
|
||||||
* @param {string} value - time string
|
* @param {string} value - time string
|
||||||
* @param {boolean} fillLeft - autofill left = hours / right = seconds
|
|
||||||
* @returns {number} - time string in millis
|
* @returns {number} - time string in millis
|
||||||
*/
|
*/
|
||||||
export const forgivingStringToMillis = (value: string, fillLeft: boolean = true): number => {
|
export const forgivingStringToMillis = (value: string): number => {
|
||||||
let millis = 0;
|
let millis = 0;
|
||||||
|
|
||||||
// check for AM/PM indicators
|
// check for AM/PM indicators
|
||||||
@@ -87,16 +87,9 @@ export const forgivingStringToMillis = (value: string, fillLeft: boolean = true)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (first != null && second != null && third == null) {
|
if (first != null && second != null && third == null) {
|
||||||
// if string has two sections
|
// if string has two sections treat as [hours] [minutes]
|
||||||
if (fillLeft) {
|
millis += parse(first) * mth;
|
||||||
// treat as [hours] [minutes]
|
millis += parse(second) * mtm;
|
||||||
millis += parse(first) * mth;
|
|
||||||
millis += parse(second) * mtm;
|
|
||||||
} else {
|
|
||||||
// treat as [minutes] [seconds]
|
|
||||||
millis += parse(first) * mtm;
|
|
||||||
millis += parse(second) * mts;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return millis;
|
return millis;
|
||||||
};
|
};
|
||||||
@@ -109,13 +102,21 @@ export const forgivingStringToMillis = (value: string, fillLeft: boolean = true)
|
|||||||
export const parseExcelDate = (excelDate: unknown): number => {
|
export const parseExcelDate = (excelDate: unknown): number => {
|
||||||
if (excelDate instanceof Date) {
|
if (excelDate instanceof Date) {
|
||||||
return dateToMillis(excelDate);
|
return dateToMillis(excelDate);
|
||||||
} else if (typeof excelDate === 'string') {
|
}
|
||||||
const date = new Date(excelDate);
|
|
||||||
if (date instanceof Date && !isNaN(date.getTime())) {
|
if (typeof excelDate === 'string') {
|
||||||
return dateToMillis(date);
|
if (isISO8601(excelDate)) {
|
||||||
} else if (isTimeString(excelDate)) {
|
const date = new Date(excelDate);
|
||||||
return forgivingStringToMillis(excelDate);
|
if (date instanceof Date && !isNaN(date.getTime())) {
|
||||||
|
return dateToMillis(date);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return forgivingStringToMillis(excelDate);
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the user uses a number value eg. 15, excel could format the cell as number
|
||||||
|
if (typeof excelDate === 'number') {
|
||||||
|
return excelDate * MILLIS_PER_MINUTE;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { isTimeString } from './isTimeString';
|
import { isISO8601, isTimeString } from './isTimeString';
|
||||||
|
|
||||||
describe('test isTimeString() function', () => {
|
describe('test isTimeString() function', () => {
|
||||||
it('it validates time strings', () => {
|
it('it validates time strings', () => {
|
||||||
@@ -33,3 +33,16 @@ describe('test isTimeString() function handle AM/PM', () => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('isISO8601()', () => {
|
||||||
|
it('returns true for valid ISO 8601 date-time strings', () => {
|
||||||
|
expect(isISO8601('1899-12-30T08:30:00.000Z')).toBe(true);
|
||||||
|
expect(isISO8601('2022-01-01T00:00:00.000Z')).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns false for invalid ISO 8601 date-time strings', () => {
|
||||||
|
expect(isISO8601('not a date')).toBe(false);
|
||||||
|
expect(isISO8601('1899-12-30T08:30:00Z')).toBe(false); // missing milliseconds
|
||||||
|
expect(isISO8601('1899-12-30 08:30:00.000Z')).toBe(false); // space instead of 'T'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -1,9 +1,15 @@
|
|||||||
/**
|
/**
|
||||||
* @description Validates a time string
|
* @description Validates a time string
|
||||||
* @param {string} text - time string "23:00:12"
|
|
||||||
* @returns {boolean} string represents time
|
|
||||||
*/
|
*/
|
||||||
export const isTimeString = (text: string): boolean => {
|
export function isTimeString(text: string): boolean {
|
||||||
const regex = /^(?:(?:([01]?\d|2[0-3])[:,.])?([0-5]?\d)[:,.])?([0-5]?\d)?(\s)?([APap][Mm])?$/;
|
const regex = /^(?:(?:([01]?\d|2[0-3])[:,.])?([0-5]?\d)[:,.])?([0-5]?\d)?(\s)?([APap][Mm])?$/;
|
||||||
return regex.test(text);
|
return regex.test(text);
|
||||||
};
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Validates a ISO8601 date-time string
|
||||||
|
*/
|
||||||
|
export function isISO8601(text: string): boolean {
|
||||||
|
const regex = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/;
|
||||||
|
return regex.test(text);
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user