Feat/table (#105)

* feat/table add react-table
* feat/table add protected table route
* feat/table upgrade dependencies
* feat/table support parsing of new properties

Co-authored-by: DeepSource Bot <bot@deepsource.io>
This commit is contained in:
Carlos Valente
2022-04-09 22:05:56 +02:00
committed by GitHub
parent 8841a02754
commit 67ddcd8c68
146 changed files with 7423 additions and 5780 deletions
+125 -258
View File
@@ -1,18 +1,23 @@
import fs from 'fs';
import xlsx from 'node-xlsx';
import {
block as blockDef,
delay as delayDef,
event as eventDef,
} from '../models/eventsDefinition.js';
import { event as eventDef } from '../models/eventsDefinition.js';
import { dbModelv1 } from '../models/dataModel.js';
import { generateId } from 'ontime-utils/generate_id.js';
import { excelDateStringToMillis } from 'ontime-utils/time.js';
import { deleteFile, makeString, validateDuration } from './parserUtils.js';
import {
parseAliases_v1,
parseEvent_v1,
parseEvents_v1,
parseHttp_v1,
parseOsc_v1,
parseSettings_v1,
parseUserFields_v1,
} from './parserUtils_v1.js';
export const EXCEL_MIME =
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
export const EXCEL_MIME = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
export const JSON_MIME = 'application/json';
export const ALLOWED_TYPES = ['JSON', 'EXCEL'];
export const MAX_EVENTS = 99;
/**
* @description Middleware function that checks file type and calls relevant parser
@@ -29,9 +34,7 @@ export const fileHandler = async (file) => {
const excelData = xlsx
.parse(file, { cellDates: true })
.find(
({ name }) =>
name.toLowerCase() === 'ontime' ||
name.toLowerCase() === 'event schedule'
({ name }) => name.toLowerCase() === 'ontime' || name.toLowerCase() === 'event schedule'
);
// we only look at worksheets called ontime or event schedule
@@ -76,7 +79,6 @@ export const fileHandler = async (file) => {
// delete file
await deleteFile(file);
return res;
};
@@ -90,7 +92,7 @@ export const parseExcel_v1 = async (excelData) => {
title: '',
url: '',
};
let customUserFields = {};
let events = [];
let timeStartIndex = null;
let timeEndIndex = null;
@@ -99,6 +101,17 @@ export const parseExcel_v1 = async (excelData) => {
let subtitleIndex = null;
let isPublicIndex = null;
let notesIndex = null;
let colourIndex = null;
let user0Index = null;
let user1Index = null;
let user2Index = null;
let user3Index = null;
let user4Index = null;
let user5Index = null;
let user6Index = null;
let user7Index = null;
let user8Index = null;
let user9Index = null;
excelData
.filter((e) => e.length > 0)
@@ -127,14 +140,41 @@ export const parseExcel_v1 = async (excelData) => {
event.subtitle = column;
} else if (j === isPublicIndex) {
// whether column is not empty
event.isPublic = column !== '';
let c = column;
if (typeof column === 'string') {
c = column.replace(/\s+/g, '');
}
event.isPublic = c !== '';
} else if (j === notesIndex) {
event.note = column;
} else if (j === colourIndex) {
event.colour = column;
} else if (j === user0Index) {
event.user0 = column;
} else if (j === user1Index) {
event.user1 = column;
} else if (j === user2Index) {
event.user2 = column;
} else if (j === user3Index) {
event.user3 = column;
} else if (j === user4Index) {
event.user4 = column;
} else if (j === user5Index) {
event.user5 = column;
} else if (j === user6Index) {
event.user6 = column;
} else if (j === user7Index) {
event.user7 = column;
} else if (j === user8Index) {
event.user8 = column;
} else if (j === user9Index) {
event.user9 = column;
} else {
if (typeof column === 'string') {
const col = column.toLowerCase();
// look for keywords
// need to make sure it is a string first
switch (column.toLowerCase()) {
switch (col) {
case 'event name':
eventTitleNext = true;
break;
@@ -171,12 +211,56 @@ export const parseExcel_v1 = async (excelData) => {
case 'notes':
notesIndex = j;
break;
case 'colour':
case 'color':
colourIndex = j;
break;
default:
// look for user defined
if (col.startsWith('user')) {
const index = column.charAt(4);
// name is the bit after the :
const [, name] = column.split(':');
if (typeof name !== 'undefined') {
if (index === '0') {
customUserFields.user0 = name;
user0Index = j;
} else if (index === '1') {
customUserFields.user1 = name;
user1Index = j;
} else if (index === '2') {
customUserFields.user2 = name;
user2Index = j;
} else if (index === '3') {
customUserFields.user3 = name;
user3Index = j;
} else if (index === '4') {
customUserFields.user4 = name;
user4Index = j;
} else if (index === '5') {
customUserFields.user5 = name;
user5Index = j;
} else if (index === '6') {
customUserFields.user6 = name;
user6Index = j;
} else if (index === '7') {
customUserFields.user7 = name;
user7Index = j;
} else if (index === '8') {
customUserFields.user8 = name;
user8Index = j;
} else if (index === '9') {
customUserFields.user9 = name;
user9Index = j;
}
}
}
break;
}
}
}
});
if (Object.keys(event).length > 0) {
// if any data was found, push to array
// take care of it in the next step
@@ -190,6 +274,7 @@ export const parseExcel_v1 = async (excelData) => {
app: 'ontime',
version: 1,
},
userFields: { ...dbModelv1.userFields, ...customUserFields },
};
};
@@ -220,22 +305,12 @@ export const parseJson_v1 = async (jsonData, enforce = false) => {
returnData.http = parseHttp_v1(jsonData, enforce);
// Import Aliases if any
returnData.aliases = parseAliases_v1(jsonData);
// Import user fields if any
returnData.userFields = parseUserFields_v1(jsonData);
return returnData;
};
/**
* @description Ensures variable is string, it skips object types
* @param {any} val - variable to convert
* @param {string} [fallback=''] - fallback value
* @returns {string} - value as string or fallback if not possible
*/
export const makeString = (val, fallback = '') => {
if (typeof val === 'string') return val;
else if (val == null || val.constructor === Object) return fallback;
else return val.toString();
};
/**
* @description Enforces formatting for events
* @param {object} eventArgs - attributes of event
@@ -254,26 +329,36 @@ export const validateEvent_v1 = (eventArgs) => {
const e = eventArgs;
const d = eventDef;
const start =
e.timeStart != null && typeof e.timeStart === 'number' ? e.timeStart : d.timeStart;
const end = e.timeEnd != null && typeof e.timeEnd === 'number' ? e.timeEnd : d.timeEnd;
event = {
...d,
title: makeString(e.title, d.title),
subtitle: makeString(e.subtitle, d.subtitle),
presenter: makeString(e.presenter, d.presenter),
timeStart: start,
timeEnd: end,
timeType: 'start-end',
duration: validateDuration(start, end),
isPublic: e.isPublic != null && typeof e.isPublic === 'boolean' ? e.isPublic : d.isPublic,
note: makeString(e.note, d.note),
timeStart:
e.timeStart != null && typeof e.timeStart === 'number'
? e.timeStart
: d.timeStart,
timeEnd:
e.timeEnd != null && typeof e.timeEnd === 'number'
? e.timeEnd
: d.timeEnd,
isPublic:
e.isPublic != null && typeof e.isPublic === 'boolean'
? e.isPublic
: d.isPublic,
user0: makeString(e.user0, d.user0),
user1: makeString(e.user1, d.user1),
user2: makeString(e.user2, d.user2),
user3: makeString(e.user3, d.user3),
user4: makeString(e.user4, d.user4),
user5: makeString(e.user5, d.user5),
user6: makeString(e.user6, d.user6),
user7: makeString(e.user7, d.user7),
user8: makeString(e.user8, d.user8),
user9: makeString(e.user9, d.user9),
// deciding not to validate colour
// this adds flexibility to the user to write hex codes, rgb,
// but also colour names like blue and red
// CSS.supports is only available in frontend
colour: makeString(e.colour, d.colour),
id,
type: 'event',
};
@@ -281,221 +366,3 @@ export const validateEvent_v1 = (eventArgs) => {
return event;
};
/**
* @description Delete file from system
* @param {string} file - reference to file
*/
const deleteFile = async (file) => {
// delete a file
fs.unlink(file, (err) => {
if (err) {
console.log(err);
}
});
};
/**
* @description Delete file from system
* @param {string} file - reference to file
* @returns {boolean} - whether file is valid JSON
*/
export const validateFile = (file) => {
try {
JSON.parse(fs.readFileSync(file));
return true;
} catch (err) {
return false;
}
};
/**
* Parse events array of an entry
* @param {object} data - data object
* @returns {object} - event object data
*/
export const parseEvents_v1 = (data) => {
let newEvents = [];
if ('events' in data) {
console.log('Found events definition, importing...');
let events = [];
let ids = [];
for (const e of data.events) {
// double check unique ids
if (ids.indexOf(e?.id) !== -1) {
console.log('ERROR: ID collision on import, skipping');
continue;
}
if (e.type === 'event') {
let event = validateEvent_v1(e);
if (event != null) {
events.push(event);
ids.push(event.id);
}
} else if (e.type === 'delay') {
events.push({
...delayDef,
duration: e.duration,
id: e.id || generateId(),
});
} else if (e.type === 'block') {
events.push({ ...blockDef, id: e.id || generateId() });
} else {
console.log('ERROR: undefined event type, skipping');
}
}
// write to db
newEvents = events;
console.log(`Uploaded file with ${events.length} entries`);
}
return newEvents;
};
/**
* Parse event portion of an entry
* @param {object} data - data object
* @param {boolean} enforce - whether to create a definition if one is missing
* @returns {object} - event object data
*/
export const parseEvent_v1 = (data, enforce) => {
let newEvent = {};
if ('event' in data) {
console.log('Found event data, importing...');
const e = data.event;
// filter known properties and write to db
newEvent = {
...dbModelv1.event,
title: e.title || dbModelv1.event.title,
url: e.url || dbModelv1.event.url,
publicInfo: e.publicInfo || dbModelv1.event.publicInfo,
backstageInfo: e.backstageInfo || dbModelv1.event.backstageInfo,
endMessage: e.endMessage || dbModelv1.event.endMessage,
};
} else if (enforce) {
newEvent = dbModelv1.event;
console.log(`Created event object in db`);
}
return newEvent;
};
/**
* Parse settings portion of an entry
* @param {object} data - data object
* @param {boolean} enforce - whether to create a definition if one is missing
* @returns {object} - event object data
*/
export const parseSettings_v1 = (data, enforce) => {
let newSettings = {};
if ('settings' in data) {
console.log('Found settings definition, importing...');
const s = data.settings;
// skip if file definition is missing
if (s.app == null || s.version == null) {
console.log('ERROR: unknown app version, skipping');
} else {
let settings = {
lock: s.lock || null,
pinCode: s.pinCode || null,
};
// write to db
newSettings = {
...dbModelv1.settings,
...settings,
};
}
} else if (enforce) {
newSettings = dbModelv1.settings;
console.log(`Created settings object in db`);
}
return newSettings;
};
/**
* Parse osc portion of an entry
* @param {object} data - data object
* @param {boolean} enforce - whether to create a definition if one is missing
* @returns {object} - event object data
*/
export const parseOsc_v1 = (data, enforce) => {
let newOsc = {};
if ('osc' in data) {
console.log('Found OSC definition, importing...');
const s = data.osc;
let osc = {};
if (s.port) osc.port = s.port;
if (s.portOut) osc.portOut = s.portOut;
if (s.targetIP) osc.targetIP = s.targetIP;
if (s.enabled !== undefined) osc.enabled = s.enabled;
// write to db
newOsc = {
...dbModelv1.osc,
...osc,
};
} else if (enforce) {
newOsc = dbModelv1.osc;
console.log(`Created OSC object in db`);
}
return newOsc;
};
/**
* Parse Http portion of an entry
* @param {object} data - data object
* @param {boolean} enforce - whether to create a definition if one is missing
* @returns {object} - event object data
*/
export const parseHttp_v1 = (data, enforce) => {
let newHttp = {};
if ('http' in data) {
console.log('Found HTTP definition, importing...');
const h = data.osc;
let http = {};
if (h.user) http.user = h.user;
if (h.pwd) http.pwd = h.pwd;
// write to db
newHttp.http = {
...dbModelv1.http,
...http,
};
} else if (enforce) {
newHttp.http = dbModelv1.http;
console.log(`Created http object in db`);
}
return newHttp;
};
/**
* Parse aliases portion of an entry
* @param {object} data - data object
* @returns {object} - event object data
*/
export const parseAliases_v1 = (data) => {
let newAliases = [];
if ('aliases' in data) {
console.log('Found Aliases definition, importing...');
const ids = [];
for (const a of data.aliases) {
// double check unique ids
if (ids.indexOf(a?.id) !== -1) {
console.log('ERROR: ID collision on import, skipping');
continue;
}
const newAlias = {
id: a.id || generateId(),
enabled: a.enabled || false,
alias: a.alias || '',
pathAndParams: a.pathAndParams || '',
};
ids.push(newAlias.id);
newAliases.push(newAlias);
}
console.log(`Uploaded ${newAliases?.length || 0} alias(es)`);
}
return newAliases;
};