mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-13 19:33:46 +00:00
refactor: remove userFields (#791)
* refactor: process custom fields on cache generate * refactor: remove userFields
This commit is contained in:
@@ -153,11 +153,12 @@ export const startServer = async () => {
|
||||
checkStart(OntimeStartOrder.InitServer);
|
||||
|
||||
const { serverPort } = DataProvider.getSettings();
|
||||
|
||||
const returnMessage = `Ontime is listening on port ${serverPort}`;
|
||||
|
||||
expressServer = http.createServer(app);
|
||||
|
||||
socket.init(expressServer);
|
||||
logger.info(LogOrigin.Server, returnMessage);
|
||||
|
||||
/**
|
||||
* Module initialises the services and provides initial payload for the store
|
||||
|
||||
@@ -8,7 +8,6 @@ import {
|
||||
ViewSettings,
|
||||
DatabaseModel,
|
||||
OSCSettings,
|
||||
UserFields,
|
||||
Alias,
|
||||
Settings,
|
||||
CustomFields,
|
||||
@@ -75,10 +74,6 @@ export class DataProvider {
|
||||
await this.persist();
|
||||
}
|
||||
|
||||
static getUserFields() {
|
||||
return { ...data.userFields };
|
||||
}
|
||||
|
||||
static getViewSettings() {
|
||||
return { ...data.viewSettings };
|
||||
}
|
||||
@@ -88,11 +83,6 @@ export class DataProvider {
|
||||
await this.persist();
|
||||
}
|
||||
|
||||
static async setUserFields(newData: UserFields) {
|
||||
data.userFields = { ...newData };
|
||||
await this.persist();
|
||||
}
|
||||
|
||||
static async setOsc(newData: OSCSettings): Promise<OSCSettings> {
|
||||
data.osc = { ...newData };
|
||||
await this.persist();
|
||||
@@ -124,7 +114,6 @@ export class DataProvider {
|
||||
data.osc = mergedData.osc;
|
||||
data.http = mergedData.http;
|
||||
data.aliases = mergedData.aliases;
|
||||
data.userFields = mergedData.userFields;
|
||||
data.customFields = mergedData.customFields;
|
||||
data.rundown = mergedData.rundown;
|
||||
await this.persist();
|
||||
|
||||
@@ -6,7 +6,7 @@ import { DatabaseModel } from 'ontime-types';
|
||||
* @param {object} newData
|
||||
*/
|
||||
export function safeMerge(existing: DatabaseModel, newData: Partial<DatabaseModel>) {
|
||||
const { rundown, project, settings, viewSettings, aliases, customFields, userFields, osc, http } = newData || {};
|
||||
const { rundown, project, settings, viewSettings, aliases, customFields, osc, http } = newData || {};
|
||||
|
||||
return {
|
||||
...existing,
|
||||
@@ -16,10 +16,6 @@ export function safeMerge(existing: DatabaseModel, newData: Partial<DatabaseMode
|
||||
viewSettings: { ...existing.viewSettings, ...viewSettings },
|
||||
aliases: aliases ?? existing.aliases,
|
||||
customFields: customFields ?? existing.customFields,
|
||||
userFields: {
|
||||
...existing.userFields,
|
||||
...(userFields && Object.fromEntries(Object.entries(userFields).filter(([_, value]) => value !== null))),
|
||||
},
|
||||
osc: { ...existing.osc, ...osc },
|
||||
http: { ...existing.http, ...http },
|
||||
};
|
||||
|
||||
@@ -29,18 +29,6 @@ describe('safeMerge', () => {
|
||||
dangerColor: '#ED3333',
|
||||
},
|
||||
aliases: [],
|
||||
userFields: {
|
||||
user0: 'existing user0',
|
||||
user1: 'existing user1',
|
||||
user2: 'existing user2',
|
||||
user3: 'existing user3',
|
||||
user4: 'existing user4',
|
||||
user5: 'existing user5',
|
||||
user6: 'existing user6',
|
||||
user7: 'existing user7',
|
||||
user8: 'existing user8',
|
||||
user9: 'existing user9',
|
||||
},
|
||||
customFields: {
|
||||
lighting: { type: 'string', label: 'lighting', colour: 'red' },
|
||||
vfx: { type: 'string', label: 'vfx', colour: 'blue' },
|
||||
@@ -166,18 +154,6 @@ describe('safeMerge', () => {
|
||||
endMessage: '',
|
||||
},
|
||||
aliases: [],
|
||||
userFields: {
|
||||
user0: 'user0',
|
||||
user1: 'user1',
|
||||
user2: 'user2',
|
||||
user3: 'user3',
|
||||
user4: 'user4',
|
||||
user5: 'user5',
|
||||
user6: 'user6',
|
||||
user7: 'user7',
|
||||
user8: 'user8',
|
||||
user9: 'user9',
|
||||
},
|
||||
osc: {
|
||||
portIn: 8888,
|
||||
portOut: 9999,
|
||||
@@ -200,34 +176,6 @@ describe('safeMerge', () => {
|
||||
expect(mergedData.aliases).toEqual(newData.aliases);
|
||||
});
|
||||
|
||||
it('merges userFields into existing object', () => {
|
||||
const existing = {
|
||||
userFields: {
|
||||
user0: 'Alice',
|
||||
user1: 'Bob',
|
||||
},
|
||||
};
|
||||
|
||||
const newData = {
|
||||
userFields: {
|
||||
user2: 'Charlie',
|
||||
user3: 'David',
|
||||
user4: null,
|
||||
},
|
||||
};
|
||||
|
||||
const expected = {
|
||||
user0: 'Alice',
|
||||
user1: 'Bob',
|
||||
user2: 'Charlie',
|
||||
user3: 'David',
|
||||
};
|
||||
|
||||
//@ts-expect-error -- testing partial merge
|
||||
const result = safeMerge(existing, newData);
|
||||
expect(result.userFields).toEqual(expected);
|
||||
});
|
||||
|
||||
it('merges customFields into existing object', () => {
|
||||
const existing = {
|
||||
customFields: {
|
||||
|
||||
@@ -3,6 +3,7 @@ import { editEvent } from '../services/rundown-service/RundownService.js';
|
||||
import { getEventWithId } from '../services/rundown-service/rundownUtils.js';
|
||||
import { coerceString, coerceNumber, coerceBoolean, coerceColour } from '../utils/coerceType.js';
|
||||
|
||||
// TODO: handle custom fields
|
||||
const whitelistedPayload = {
|
||||
title: coerceString,
|
||||
subtitle: coerceString,
|
||||
@@ -16,17 +17,6 @@ const whitelistedPayload = {
|
||||
skip: coerceBoolean,
|
||||
|
||||
colour: coerceColour,
|
||||
|
||||
user0: coerceString,
|
||||
user1: coerceString,
|
||||
user2: coerceString,
|
||||
user3: coerceString,
|
||||
user4: coerceString,
|
||||
user5: coerceString,
|
||||
user6: coerceString,
|
||||
user7: coerceString,
|
||||
user8: coerceString,
|
||||
user9: coerceString,
|
||||
};
|
||||
|
||||
export function parse(property: string, value: unknown) {
|
||||
|
||||
@@ -8,7 +8,7 @@ import type {
|
||||
ProjectFileListResponse,
|
||||
OSCSettings,
|
||||
} from 'ontime-types';
|
||||
import { ExcelImportOptions, deepmerge } from 'ontime-utils';
|
||||
import { ImportOptions } from 'ontime-utils';
|
||||
|
||||
import { RequestHandler, Request, Response } from 'express';
|
||||
import fs from 'fs';
|
||||
@@ -72,16 +72,16 @@ export const dbDownload = async (_req: Request, res: Response) => {
|
||||
|
||||
/**
|
||||
* Parses a file and returns the result objects
|
||||
* @param file
|
||||
* @param filePath
|
||||
* @param _req
|
||||
* @param _res
|
||||
* @param options
|
||||
*/
|
||||
async function parseFile(file, _req: Request, _res: Response, options: ExcelImportOptions) {
|
||||
if (!fs.existsSync(file)) {
|
||||
async function parseFile(filePath: string, _req: Request, _res: Response, options: ImportOptions) {
|
||||
if (!fs.existsSync(filePath)) {
|
||||
throw new Error('Upload failed');
|
||||
}
|
||||
const result = await fileHandler(file, options);
|
||||
const result = await fileHandler(filePath, options);
|
||||
return result.data;
|
||||
}
|
||||
|
||||
@@ -186,29 +186,6 @@ export const postAliases = async (req: Request, res: Response) => {
|
||||
}
|
||||
};
|
||||
|
||||
// Create controller for GET request to '/ontime/userfields'
|
||||
// Returns -
|
||||
export const getUserFields = async (_req: Request, res: Response) => {
|
||||
const userFields = DataProvider.getUserFields();
|
||||
res.status(200).send(userFields);
|
||||
};
|
||||
|
||||
// Create controller for POST request to '/ontime/userfields'
|
||||
// Returns ACK message
|
||||
export const postUserFields = async (req: Request, res: Response) => {
|
||||
if (failEmptyObjects(req.body, res)) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const persistedData = DataProvider.getUserFields();
|
||||
const newData = deepmerge(persistedData, req.body);
|
||||
await DataProvider.setUserFields(newData);
|
||||
res.status(200).send(newData);
|
||||
} catch (error) {
|
||||
res.status(400).send({ message: String(error) });
|
||||
}
|
||||
};
|
||||
|
||||
// Create controller for POST request to '/ontime/settings'
|
||||
// Returns -
|
||||
export const getSettings = async (_req: Request, res: Response) => {
|
||||
@@ -356,7 +333,9 @@ export const postHTTP = async (req: Request, res: Response<HttpSettings | Ontime
|
||||
};
|
||||
|
||||
export async function patchPartialProjectFile(req: Request, res: Response) {
|
||||
// all fields are optional in validation
|
||||
if (failEmptyObjects(req.body, res)) {
|
||||
res.status(400).send({ message: 'No field found to patch' });
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -367,7 +346,7 @@ export async function patchPartialProjectFile(req: Request, res: Response) {
|
||||
viewSettings: req.body?.viewSettings,
|
||||
osc: req.body?.osc,
|
||||
aliases: req.body?.aliases,
|
||||
userFields: req.body?.userFields,
|
||||
customFields: req.body?.customFields,
|
||||
};
|
||||
|
||||
const maybeRundown = req.body?.rundown;
|
||||
@@ -402,10 +381,10 @@ export const dbUpload = async (req: Request, res: Response) => {
|
||||
};
|
||||
|
||||
/**
|
||||
* uploads and parses an excel file
|
||||
* uploads and parses an excel spreadsheet
|
||||
* @returns parsed result
|
||||
*/
|
||||
export async function previewExcel(req: Request, res: Response) {
|
||||
export async function previewSpreadsheet(req: Request, res: Response) {
|
||||
if (!req.file) {
|
||||
res.status(400).send({ message: 'File not found' });
|
||||
return;
|
||||
@@ -413,8 +392,8 @@ export async function previewExcel(req: Request, res: Response) {
|
||||
|
||||
try {
|
||||
const options = JSON.parse(req.body.options);
|
||||
const file = req.file.path;
|
||||
const data = await parseFile(file, req, res, options);
|
||||
const filePath = req.file.path;
|
||||
const data = await parseFile(filePath, req, res, options);
|
||||
res.status(200).send(data);
|
||||
} catch (error) {
|
||||
res.status(500).send({ message: String(error) });
|
||||
|
||||
@@ -39,28 +39,6 @@ export const validateAliases = [
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* @description Validates object for POST /ontime/userfields
|
||||
*/
|
||||
export const validateUserFields = [
|
||||
body('user0').exists().isString().trim(),
|
||||
body('user1').exists().isString().trim(),
|
||||
body('user2').exists().isString().trim(),
|
||||
body('user3').exists().isString().trim(),
|
||||
body('user4').exists().isString().trim(),
|
||||
body('user5').exists().isString().trim(),
|
||||
body('user6').exists().isString().trim(),
|
||||
body('user7').exists().isString().trim(),
|
||||
body('user8').exists().isString().trim(),
|
||||
body('user9').exists().isString().trim(),
|
||||
|
||||
(req: Request, res: Response, next: NextFunction) => {
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty()) return res.status(422).json({ errors: errors.array() });
|
||||
next();
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* @description Validates object for POST /ontime/settings
|
||||
*/
|
||||
@@ -122,7 +100,7 @@ export const validatePatchProjectFile = [
|
||||
body('settings').isObject().optional({ nullable: false }),
|
||||
body('viewSettings').isObject().optional({ nullable: false }),
|
||||
body('aliases').isArray().optional({ nullable: false }),
|
||||
body('userFields').isObject().optional({ nullable: false }),
|
||||
body('customFields').isObject().optional({ nullable: false }),
|
||||
body('osc').isObject().optional({ nullable: false }),
|
||||
|
||||
(req: Request, res: Response, next: NextFunction) => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { defaultExcelImportMap } from 'ontime-utils';
|
||||
import { isImportMap } from 'ontime-utils';
|
||||
|
||||
import { body, param, validationResult } from 'express-validator';
|
||||
import { NextFunction, Request, Response } from 'express';
|
||||
@@ -26,17 +26,8 @@ export const validateSheetOptions = [
|
||||
.exists()
|
||||
.isObject()
|
||||
.custom((content) => {
|
||||
// Check if the fileContent has the same keys as defaultExcelImportMap
|
||||
const hasValidKeys = Object.keys(defaultExcelImportMap).every((key) => key in content);
|
||||
|
||||
// Check if all values in fileContent are strings
|
||||
const hasValidValues = Object.values(content).every((value) => typeof value === 'string');
|
||||
|
||||
if (!hasValidKeys || !hasValidValues) {
|
||||
throw new Error('Invalid file format');
|
||||
}
|
||||
|
||||
return true;
|
||||
const isValid = isImportMap(content);
|
||||
return isValid;
|
||||
}),
|
||||
|
||||
(req: Request, res: Response, next: NextFunction) => {
|
||||
|
||||
@@ -2,18 +2,16 @@ import { initAssets, startIntegrations, startOSCServer, startServer } from './ap
|
||||
|
||||
async function startOntime() {
|
||||
try {
|
||||
console.log('Starting Ontime');
|
||||
console.log('Loading Assets');
|
||||
console.log('Request: Initialise assets...');
|
||||
await initAssets();
|
||||
console.log('Starting Server');
|
||||
console.log('Request: Start server...');
|
||||
await startServer();
|
||||
console.log('Starting OSC Server');
|
||||
console.log('Request: Start OSC server...');
|
||||
await startOSCServer();
|
||||
console.log('Starting Integrations');
|
||||
console.log('Request: Start OSC integrations...');
|
||||
await startIntegrations();
|
||||
} catch (error) {
|
||||
console.log('Error starting Ontime');
|
||||
console.log(error);
|
||||
console.log(`Request failed: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -29,18 +29,6 @@ export const dbModel: DatabaseModel = {
|
||||
},
|
||||
aliases: [],
|
||||
customFields: {},
|
||||
userFields: {
|
||||
user0: 'user0',
|
||||
user1: 'user1',
|
||||
user2: 'user2',
|
||||
user3: 'user3',
|
||||
user4: 'user4',
|
||||
user5: 'user5',
|
||||
user6: 'user6',
|
||||
user7: 'user7',
|
||||
user8: 'user8',
|
||||
user9: 'user9',
|
||||
},
|
||||
osc: {
|
||||
portIn: 8888,
|
||||
portOut: 9999,
|
||||
|
||||
@@ -23,16 +23,6 @@ export const event: Omit<OntimeEvent, 'id' | 'delay' | 'cue'> = {
|
||||
isPublic: false,
|
||||
skip: false,
|
||||
colour: '',
|
||||
user0: '',
|
||||
user1: '',
|
||||
user2: '',
|
||||
user3: '',
|
||||
user4: '',
|
||||
user5: '',
|
||||
user6: '',
|
||||
user7: '',
|
||||
user8: '',
|
||||
user9: '',
|
||||
type: SupportedEvent.Event,
|
||||
revision: 0,
|
||||
timeWarning: 120000,
|
||||
|
||||
@@ -8,16 +8,14 @@ import {
|
||||
getOSC,
|
||||
getHTTP,
|
||||
getSettings,
|
||||
getUserFields,
|
||||
getViewSettings,
|
||||
patchPartialProjectFile,
|
||||
poll,
|
||||
postAliases,
|
||||
postOSC,
|
||||
postSettings,
|
||||
postUserFields,
|
||||
postViewSettings,
|
||||
previewExcel,
|
||||
previewSpreadsheet,
|
||||
postHTTP,
|
||||
duplicateProjectFile,
|
||||
listProjects,
|
||||
@@ -32,7 +30,6 @@ import {
|
||||
validateOSC,
|
||||
validatePatchProjectFile,
|
||||
validateSettings,
|
||||
validateUserFields,
|
||||
viewValidator,
|
||||
validateHTTP,
|
||||
validateProjectDuplicate,
|
||||
@@ -61,11 +58,12 @@ router.get('/db', dbDownload);
|
||||
// create route between controller and '/ontime/db' endpoint
|
||||
router.post('/db', uploadFile, dbUpload);
|
||||
|
||||
// create route between controller and '/ontime/excel' endpoint
|
||||
// create route between controller and '/ontime/db' endpoint
|
||||
router.patch('/db', validatePatchProjectFile, patchPartialProjectFile);
|
||||
|
||||
// create route between controller and '/ontime/preview-spreadsheet' endpoint
|
||||
router.post('/preview-spreadsheet', uploadFile, previewExcel);
|
||||
// create route between controller and '/spreadsheet/preview' endpoint
|
||||
// TODO: validate import map
|
||||
router.post('/spreadsheet/preview', uploadFile, previewSpreadsheet);
|
||||
|
||||
// create route between controller and '/ontime/settings' endpoint
|
||||
router.get('/settings', getSettings);
|
||||
@@ -85,12 +83,6 @@ router.get('/aliases', getAliases);
|
||||
// create route between controller and '/ontime/aliases' endpoint
|
||||
router.post('/aliases', validateAliases, postAliases);
|
||||
|
||||
// create route between controller and '/ontime/aliases' endpoint
|
||||
router.get('/userfields', getUserFields);
|
||||
|
||||
// create route between controller and '/ontime/aliases' endpoint
|
||||
router.post('/userfields', validateUserFields, postUserFields);
|
||||
|
||||
// create route between controller and '/ontime/info' endpoint
|
||||
router.get('/info', getInfo);
|
||||
|
||||
|
||||
@@ -408,16 +408,6 @@ describe('calculateRuntimeDelays', () => {
|
||||
isPublic: true,
|
||||
skip: false,
|
||||
colour: '',
|
||||
user0: '',
|
||||
user1: '',
|
||||
user2: '',
|
||||
user3: '',
|
||||
user4: '',
|
||||
user5: '',
|
||||
user6: '',
|
||||
user7: '',
|
||||
user8: '',
|
||||
user9: '',
|
||||
type: SupportedEvent.Event,
|
||||
revision: 0,
|
||||
timeWarning: 120000,
|
||||
@@ -446,16 +436,6 @@ describe('calculateRuntimeDelays', () => {
|
||||
isPublic: true,
|
||||
skip: false,
|
||||
colour: '',
|
||||
user0: '',
|
||||
user1: '',
|
||||
user2: '',
|
||||
user3: '',
|
||||
user4: '',
|
||||
user5: '',
|
||||
user6: '',
|
||||
user7: '',
|
||||
user8: '',
|
||||
user9: '',
|
||||
type: SupportedEvent.Event,
|
||||
revision: 0,
|
||||
timeWarning: 120000,
|
||||
@@ -484,16 +464,6 @@ describe('calculateRuntimeDelays', () => {
|
||||
isPublic: true,
|
||||
skip: false,
|
||||
colour: '',
|
||||
user0: '',
|
||||
user1: '',
|
||||
user2: '',
|
||||
user3: '',
|
||||
user4: '',
|
||||
user5: '',
|
||||
user6: '',
|
||||
user7: '',
|
||||
user8: '',
|
||||
user9: '',
|
||||
type: SupportedEvent.Event,
|
||||
revision: 0,
|
||||
timeWarning: 120000,
|
||||
@@ -522,16 +492,6 @@ describe('calculateRuntimeDelays', () => {
|
||||
isPublic: true,
|
||||
skip: false,
|
||||
colour: '',
|
||||
user0: '',
|
||||
user1: '',
|
||||
user2: '',
|
||||
user3: '',
|
||||
user4: '',
|
||||
user5: '',
|
||||
user6: '',
|
||||
user7: '',
|
||||
user8: '',
|
||||
user9: '',
|
||||
type: SupportedEvent.Event,
|
||||
revision: 0,
|
||||
timeWarning: 120000,
|
||||
@@ -569,16 +529,6 @@ describe('getDelayAt()', () => {
|
||||
isPublic: true,
|
||||
skip: false,
|
||||
colour: '',
|
||||
user0: '',
|
||||
user1: '',
|
||||
user2: '',
|
||||
user3: '',
|
||||
user4: '',
|
||||
user5: '',
|
||||
user6: '',
|
||||
user7: '',
|
||||
user8: '',
|
||||
user9: '',
|
||||
type: SupportedEvent.Event,
|
||||
revision: 0,
|
||||
timeWarning: 120000,
|
||||
@@ -608,16 +558,6 @@ describe('getDelayAt()', () => {
|
||||
isPublic: true,
|
||||
skip: false,
|
||||
colour: '',
|
||||
user0: '',
|
||||
user1: '',
|
||||
user2: '',
|
||||
user3: '',
|
||||
user4: '',
|
||||
user5: '',
|
||||
user6: '',
|
||||
user7: '',
|
||||
user8: '',
|
||||
user9: '',
|
||||
type: SupportedEvent.Event,
|
||||
revision: 0,
|
||||
timeWarning: 120000,
|
||||
@@ -647,16 +587,6 @@ describe('getDelayAt()', () => {
|
||||
isPublic: true,
|
||||
skip: false,
|
||||
colour: '',
|
||||
user0: '',
|
||||
user1: '',
|
||||
user2: '',
|
||||
user3: '',
|
||||
user4: '',
|
||||
user5: '',
|
||||
user6: '',
|
||||
user7: '',
|
||||
user8: '',
|
||||
user9: '',
|
||||
type: SupportedEvent.Event,
|
||||
revision: 0,
|
||||
timeWarning: 120000,
|
||||
@@ -686,16 +616,6 @@ describe('getDelayAt()', () => {
|
||||
isPublic: true,
|
||||
skip: false,
|
||||
colour: '',
|
||||
user0: '',
|
||||
user1: '',
|
||||
user2: '',
|
||||
user3: '',
|
||||
user4: '',
|
||||
user5: '',
|
||||
user6: '',
|
||||
user7: '',
|
||||
user8: '',
|
||||
user9: '',
|
||||
type: SupportedEvent.Event,
|
||||
revision: 0,
|
||||
timeWarning: 120000,
|
||||
@@ -751,16 +671,6 @@ describe('calculateRuntimeDelaysFrom()', () => {
|
||||
isPublic: true,
|
||||
skip: false,
|
||||
colour: '',
|
||||
user0: '',
|
||||
user1: '',
|
||||
user2: '',
|
||||
user3: '',
|
||||
user4: '',
|
||||
user5: '',
|
||||
user6: '',
|
||||
user7: '',
|
||||
user8: '',
|
||||
user9: '',
|
||||
type: SupportedEvent.Event,
|
||||
revision: 0,
|
||||
timeWarning: 120000,
|
||||
@@ -790,16 +700,6 @@ describe('calculateRuntimeDelaysFrom()', () => {
|
||||
isPublic: true,
|
||||
skip: false,
|
||||
colour: '',
|
||||
user0: '',
|
||||
user1: '',
|
||||
user2: '',
|
||||
user3: '',
|
||||
user4: '',
|
||||
user5: '',
|
||||
user6: '',
|
||||
user7: '',
|
||||
user8: '',
|
||||
user9: '',
|
||||
type: SupportedEvent.Event,
|
||||
revision: 0,
|
||||
timeWarning: 120000,
|
||||
@@ -829,16 +729,6 @@ describe('calculateRuntimeDelaysFrom()', () => {
|
||||
isPublic: true,
|
||||
skip: false,
|
||||
colour: '',
|
||||
user0: '',
|
||||
user1: '',
|
||||
user2: '',
|
||||
user3: '',
|
||||
user4: '',
|
||||
user5: '',
|
||||
user6: '',
|
||||
user7: '',
|
||||
user8: '',
|
||||
user9: '',
|
||||
type: SupportedEvent.Event,
|
||||
revision: 0,
|
||||
timeWarning: 120000,
|
||||
@@ -868,16 +758,6 @@ describe('calculateRuntimeDelaysFrom()', () => {
|
||||
isPublic: true,
|
||||
skip: false,
|
||||
colour: '',
|
||||
user0: '',
|
||||
user1: '',
|
||||
user2: '',
|
||||
user3: '',
|
||||
user4: '',
|
||||
user5: '',
|
||||
user6: '',
|
||||
user7: '',
|
||||
user8: '',
|
||||
user9: '',
|
||||
type: SupportedEvent.Event,
|
||||
revision: 0,
|
||||
timeWarning: 120000,
|
||||
|
||||
@@ -62,6 +62,7 @@ export async function setRundown(initialRundown: OntimeRundown) {
|
||||
generate();
|
||||
await DataProvider.setRundown(persistedRundown);
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility initialises cache
|
||||
* @param rundown
|
||||
@@ -89,10 +90,6 @@ export function generate(
|
||||
// 1. handle links
|
||||
handleLink(i, initialRundown, updatedEvent, links);
|
||||
|
||||
// TODO: wait until the next thing?
|
||||
// update the persisted event
|
||||
initialRundown[i] = updatedEvent;
|
||||
|
||||
// 2. handle custom fields
|
||||
handleCustomField(customFields, customFieldChangelog, updatedEvent, assignedCustomFields);
|
||||
|
||||
@@ -196,14 +193,12 @@ export function mutateCache<T extends object>(mutation: MutatingFn<T>) {
|
||||
console.timeEnd('rundownCache__init');
|
||||
});
|
||||
|
||||
// TODO: should we trottle this?
|
||||
// TODO: should we throttle this?
|
||||
// defer writing to the database
|
||||
setImmediate(() => {
|
||||
console.log('writing to database', persistedRundown.length);
|
||||
DataProvider.setRundown(persistedRundown);
|
||||
});
|
||||
|
||||
// TODO: could we return a patch object?
|
||||
return { newEvent };
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* @link https://developers.google.com/identity/protocols/oauth2/limited-input-device
|
||||
*/
|
||||
|
||||
import { AuthenticationStatus, LogOrigin, MaybeString, OntimeRundown, UserFields } from 'ontime-types';
|
||||
import { AuthenticationStatus, CustomFields, LogOrigin, MaybeString, OntimeRundown } from 'ontime-types';
|
||||
|
||||
import { sheets, sheets_v4 } from '@googleapis/sheets';
|
||||
import { Credentials, OAuth2Client } from 'google-auth-library';
|
||||
@@ -13,10 +13,10 @@ import got from 'got';
|
||||
import { resolveSheetsDirectory } from '../../setup.js';
|
||||
import { ensureDirectory } from '../../utils/fileManagement.js';
|
||||
import { type ClientSecret, cellRequestFromEvent, getA1Notation, validateClientSecret } from './sheetUtils.js';
|
||||
import { ExcelImportMap } from 'ontime-utils';
|
||||
import { ImportMap } from 'ontime-utils';
|
||||
import { parseExcel } from '../../utils/parser.js';
|
||||
import { logger } from '../../classes/Logger.js';
|
||||
import { parseRundown, parseUserFields } from '../../utils/parserFunctions.js';
|
||||
import { parseCustomFields, parseRundown } from '../../utils/parserFunctions.js';
|
||||
import { getRundown } from '../rundown-service/rundownUtils.js';
|
||||
|
||||
const sheetScope = 'https://www.googleapis.com/auth/spreadsheets';
|
||||
@@ -258,7 +258,9 @@ async function verifyWorksheet(sheetId: string, worksheet: string): Promise<{ wo
|
||||
throw new Error(`Request failed: ${spreadsheets.status} ${spreadsheets.statusText}`);
|
||||
}
|
||||
|
||||
const selectedWorksheet = spreadsheets.data.sheets.find((n) => n.properties.title == worksheet);
|
||||
const selectedWorksheet = spreadsheets.data.sheets.find(
|
||||
(n) => n.properties.title.toLowerCase() === worksheet.toLowerCase(),
|
||||
);
|
||||
|
||||
if (!selectedWorksheet) {
|
||||
throw new Error('Could not find worksheet');
|
||||
@@ -271,7 +273,7 @@ async function verifyWorksheet(sheetId: string, worksheet: string): Promise<{ wo
|
||||
return { worksheetId: selectedWorksheet.properties.sheetId, range: `${worksheet}!A1:${endCell}` };
|
||||
}
|
||||
|
||||
export async function upload(sheetId: string, options: ExcelImportMap) {
|
||||
export async function upload(sheetId: string, options: ImportMap) {
|
||||
const { worksheetId, range } = await verifyWorksheet(sheetId, options.worksheet);
|
||||
|
||||
const readResponse = await sheets({ version: 'v4', auth: currentAuthClient }).spreadsheets.values.get({
|
||||
@@ -344,10 +346,10 @@ export async function upload(sheetId: string, options: ExcelImportMap) {
|
||||
|
||||
export async function download(
|
||||
sheetId: string,
|
||||
options: ExcelImportMap,
|
||||
options: ImportMap,
|
||||
): Promise<{
|
||||
rundown: OntimeRundown;
|
||||
userFields: UserFields;
|
||||
customFields: CustomFields;
|
||||
}> {
|
||||
const { range } = await verifyWorksheet(sheetId, options.worksheet);
|
||||
|
||||
@@ -367,6 +369,6 @@ export async function download(
|
||||
if (rundown.length < 1) {
|
||||
throw new Error('Sheet: Could not find data to import in the worksheet');
|
||||
}
|
||||
const userFields = parseUserFields(dataFromSheet);
|
||||
return { rundown, userFields };
|
||||
const customFields = parseCustomFields(dataFromSheet);
|
||||
return { rundown, customFields };
|
||||
}
|
||||
|
||||
@@ -37,16 +37,6 @@ describe('cellRequestFromEvent()', () => {
|
||||
isPublic: false,
|
||||
skip: false,
|
||||
colour: 'red',
|
||||
user0: '',
|
||||
user1: '',
|
||||
user2: '',
|
||||
user3: '',
|
||||
user4: '',
|
||||
user5: '',
|
||||
user6: '',
|
||||
user7: '',
|
||||
user8: '',
|
||||
user9: '',
|
||||
revision: 0,
|
||||
id: '1358',
|
||||
timeWarning: 0,
|
||||
@@ -68,16 +58,6 @@ describe('cellRequestFromEvent()', () => {
|
||||
isPublic: { row: 1, col: 25 },
|
||||
skip: { row: 1, col: 26 },
|
||||
colour: { row: 1, col: 27 },
|
||||
user0: { row: 1, col: 28 },
|
||||
user1: { row: 1, col: 29 },
|
||||
user2: { row: 1, col: 30 },
|
||||
user3: { row: 1, col: 31 },
|
||||
user4: { row: 1, col: 32 },
|
||||
user5: { row: 1, col: 33 },
|
||||
user6: { row: 1, col: 34 },
|
||||
user7: { row: 1, col: 35 },
|
||||
user8: { row: 1, col: 36 },
|
||||
user9: { row: 1, col: 37 },
|
||||
revision: { row: 1, col: 38 },
|
||||
id: { row: 1, col: 39 },
|
||||
timeWarning: { row: 1, col: 40 },
|
||||
@@ -87,7 +67,7 @@ describe('cellRequestFromEvent()', () => {
|
||||
expect(result.updateCells.rows[0].values[5].userEnteredValue.stringValue).toStrictEqual(event.note);
|
||||
});
|
||||
|
||||
test('numer to timer', () => {
|
||||
test('number to timer', () => {
|
||||
const event: OntimeEvent = {
|
||||
type: SupportedEvent.Event,
|
||||
cue: '1',
|
||||
@@ -105,16 +85,6 @@ describe('cellRequestFromEvent()', () => {
|
||||
isPublic: false,
|
||||
skip: false,
|
||||
colour: 'red',
|
||||
user0: '',
|
||||
user1: '',
|
||||
user2: '',
|
||||
user3: '',
|
||||
user4: '',
|
||||
user5: '',
|
||||
user6: '',
|
||||
user7: '',
|
||||
user8: '',
|
||||
user9: '',
|
||||
revision: 0,
|
||||
id: '1358',
|
||||
timeWarning: 0,
|
||||
@@ -136,16 +106,6 @@ describe('cellRequestFromEvent()', () => {
|
||||
isPublic: { row: 1, col: 25 },
|
||||
skip: { row: 1, col: 26 },
|
||||
colour: { row: 1, col: 27 },
|
||||
user0: { row: 1, col: 28 },
|
||||
user1: { row: 1, col: 29 },
|
||||
user2: { row: 1, col: 30 },
|
||||
user3: { row: 1, col: 31 },
|
||||
user4: { row: 1, col: 32 },
|
||||
user5: { row: 1, col: 33 },
|
||||
user6: { row: 1, col: 34 },
|
||||
user7: { row: 1, col: 35 },
|
||||
user8: { row: 1, col: 36 },
|
||||
user9: { row: 1, col: 37 },
|
||||
revision: { row: 1, col: 38 },
|
||||
id: { row: 1, col: 39 },
|
||||
timeWarning: { row: 1, col: 40 },
|
||||
@@ -174,16 +134,6 @@ describe('cellRequestFromEvent()', () => {
|
||||
isPublic: true,
|
||||
skip: false,
|
||||
colour: 'red',
|
||||
user0: 'u',
|
||||
user1: 'u',
|
||||
user2: 'u',
|
||||
user3: 'u',
|
||||
user4: 'u',
|
||||
user5: 'u',
|
||||
user6: 'u',
|
||||
user7: 'u',
|
||||
user8: 'u',
|
||||
user9: 'u',
|
||||
revision: 0,
|
||||
id: '1358',
|
||||
timeWarning: 0,
|
||||
@@ -205,16 +155,6 @@ describe('cellRequestFromEvent()', () => {
|
||||
isPublic: { row: 1, col: 25 },
|
||||
skip: { row: 1, col: 26 },
|
||||
colour: { row: 1, col: 27 },
|
||||
user0: { row: 1, col: 28 },
|
||||
user1: { row: 1, col: 29 },
|
||||
user2: { row: 1, col: 30 },
|
||||
user3: { row: 1, col: 31 },
|
||||
user4: { row: 1, col: 32 },
|
||||
user5: { row: 1, col: 33 },
|
||||
user6: { row: 1, col: 34 },
|
||||
user7: { row: 1, col: 35 },
|
||||
user8: { row: 1, col: 36 },
|
||||
user9: { row: 1, col: 37 },
|
||||
revision: { row: 1, col: 38 },
|
||||
id: { row: 1, col: 39 },
|
||||
timeWarning: { row: 1, col: 40 },
|
||||
@@ -243,16 +183,6 @@ describe('cellRequestFromEvent()', () => {
|
||||
isPublic: true,
|
||||
skip: false,
|
||||
colour: 'red',
|
||||
user0: 'u',
|
||||
user1: 'u',
|
||||
user2: 'u',
|
||||
user3: 'u',
|
||||
user4: 'u',
|
||||
user5: 'u',
|
||||
user6: 'u',
|
||||
user7: 'u',
|
||||
user8: 'u',
|
||||
user9: 'u',
|
||||
revision: 0,
|
||||
id: '1358',
|
||||
timeWarning: 0,
|
||||
@@ -263,7 +193,6 @@ describe('cellRequestFromEvent()', () => {
|
||||
cue: { row: 1, col: 0 },
|
||||
title: { row: 1, col: 6 },
|
||||
subtitle: { row: 1, col: 10 },
|
||||
user0: { row: 1, col: 16 },
|
||||
};
|
||||
const result = cellRequestFromEvent(event, 1, 1234, metadata);
|
||||
expect(result.updateCells.rows[0].values[0].userEnteredValue.stringValue).toStrictEqual(event.cue);
|
||||
@@ -289,16 +218,6 @@ describe('cellRequestFromEvent()', () => {
|
||||
isPublic: true,
|
||||
skip: false,
|
||||
colour: 'red',
|
||||
user0: 'u',
|
||||
user1: 'u',
|
||||
user2: 'u',
|
||||
user3: 'u',
|
||||
user4: 'u',
|
||||
user5: 'u',
|
||||
user6: 'u',
|
||||
user7: 'u',
|
||||
user8: 'u',
|
||||
user9: 'u',
|
||||
revision: 0,
|
||||
id: '1358',
|
||||
timeWarning: 0,
|
||||
@@ -335,16 +254,6 @@ describe('cellRequestFromEvent()', () => {
|
||||
isPublic: true,
|
||||
skip: false,
|
||||
colour: 'red',
|
||||
user0: 'u',
|
||||
user1: 'u',
|
||||
user2: 'u',
|
||||
user3: 'u',
|
||||
user4: 'u',
|
||||
user5: 'u',
|
||||
user6: 'u',
|
||||
user7: 'u',
|
||||
user8: 'u',
|
||||
user9: 'u',
|
||||
revision: 0,
|
||||
id: '1358',
|
||||
timeWarning: 0,
|
||||
@@ -355,7 +264,6 @@ describe('cellRequestFromEvent()', () => {
|
||||
cue: { row: 10, col: 5 },
|
||||
title: { row: 10, col: 6 },
|
||||
subtitle: { row: 1, col: 10 },
|
||||
user0: { row: 10, col: 16 },
|
||||
};
|
||||
const result1 = cellRequestFromEvent(event, 1, 1234, metadata);
|
||||
expect(result1.updateCells.start.sheetId).toStrictEqual(1234);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/* eslint-disable no-console -- we are mocking the console */
|
||||
import { vi } from 'vitest';
|
||||
import { assertType, vi } from 'vitest';
|
||||
|
||||
import {
|
||||
DatabaseModel,
|
||||
@@ -11,12 +11,14 @@ import {
|
||||
TimeStrategy,
|
||||
TimerType,
|
||||
ViewSettings,
|
||||
OntimeRundown,
|
||||
} from 'ontime-types';
|
||||
|
||||
import { dbModel } from '../../models/dataModel.js';
|
||||
import { parseExcel, parseJson, createEvent } from '../parser.js';
|
||||
|
||||
import { parseExcel, parseJson, createEvent, getCustomFieldData } from '../parser.js';
|
||||
import { makeString } from '../parserUtils.js';
|
||||
import { parseAliases, parseUserFields, parseViewSettings } from '../parserFunctions.js';
|
||||
import { parseAliases, parseViewSettings } from '../parserFunctions.js';
|
||||
|
||||
describe('test json parser with valid def', () => {
|
||||
const testData: Partial<DatabaseModel> = {
|
||||
@@ -39,16 +41,6 @@ describe('test json parser with valid def', () => {
|
||||
isPublic: false,
|
||||
skip: false,
|
||||
colour: '',
|
||||
user0: '',
|
||||
user1: '',
|
||||
user2: '',
|
||||
user3: '',
|
||||
user4: '',
|
||||
user5: '',
|
||||
user6: '',
|
||||
user7: '',
|
||||
user8: '',
|
||||
user9: '',
|
||||
revision: 0,
|
||||
timeWarning: 0,
|
||||
timeDanger: 0,
|
||||
@@ -72,16 +64,6 @@ describe('test json parser with valid def', () => {
|
||||
isPublic: true,
|
||||
skip: true,
|
||||
colour: 'red',
|
||||
user0: '',
|
||||
user1: '',
|
||||
user2: '',
|
||||
user3: '',
|
||||
user4: '',
|
||||
user5: '',
|
||||
user6: '',
|
||||
user7: '',
|
||||
user8: '',
|
||||
user9: '',
|
||||
revision: 0,
|
||||
timeWarning: 0,
|
||||
timeDanger: 0,
|
||||
@@ -105,16 +87,6 @@ describe('test json parser with valid def', () => {
|
||||
isPublic: false,
|
||||
skip: false,
|
||||
colour: '',
|
||||
user0: '',
|
||||
user1: '',
|
||||
user2: '',
|
||||
user3: '',
|
||||
user4: '',
|
||||
user5: '',
|
||||
user6: '',
|
||||
user7: '',
|
||||
user8: '',
|
||||
user9: '',
|
||||
revision: 0,
|
||||
timeWarning: 0,
|
||||
timeDanger: 0,
|
||||
@@ -159,16 +131,6 @@ describe('test json parser with valid def', () => {
|
||||
isPublic: false,
|
||||
skip: false,
|
||||
colour: '',
|
||||
user0: '',
|
||||
user1: '',
|
||||
user2: '',
|
||||
user3: '',
|
||||
user4: '',
|
||||
user5: '',
|
||||
user6: '',
|
||||
user7: '',
|
||||
user8: '',
|
||||
user9: '',
|
||||
revision: 0,
|
||||
timeWarning: 0,
|
||||
timeDanger: 0,
|
||||
@@ -192,16 +154,6 @@ describe('test json parser with valid def', () => {
|
||||
isPublic: true,
|
||||
skip: true,
|
||||
colour: '',
|
||||
user0: '',
|
||||
user1: '',
|
||||
user2: '',
|
||||
user3: '',
|
||||
user4: '',
|
||||
user5: '',
|
||||
user6: '',
|
||||
user7: '',
|
||||
user8: '',
|
||||
user9: '',
|
||||
revision: 0,
|
||||
timeWarning: 0,
|
||||
timeDanger: 0,
|
||||
@@ -223,21 +175,11 @@ describe('test json parser with valid def', () => {
|
||||
duration: 37200000 - 32400000,
|
||||
isPublic: true,
|
||||
colour: '',
|
||||
user0: '',
|
||||
user1: '',
|
||||
user2: '',
|
||||
user3: '',
|
||||
user4: '',
|
||||
user5: '',
|
||||
user6: '',
|
||||
user7: '',
|
||||
user8: '',
|
||||
user9: '',
|
||||
revision: 0,
|
||||
timeWarning: 0,
|
||||
timeDanger: 0,
|
||||
} as OntimeEvent,
|
||||
],
|
||||
] as OntimeRundown,
|
||||
project: {
|
||||
title: 'This is a test definition',
|
||||
backstageUrl: 'www.carlosvalente.com',
|
||||
@@ -540,16 +482,7 @@ describe('test event validator', () => {
|
||||
id: expect.any(String),
|
||||
cue: 'test',
|
||||
colour: expect.any(String),
|
||||
user0: expect.any(String),
|
||||
user1: expect.any(String),
|
||||
user2: expect.any(String),
|
||||
user3: expect.any(String),
|
||||
user4: expect.any(String),
|
||||
user5: expect.any(String),
|
||||
user6: expect.any(String),
|
||||
user7: expect.any(String),
|
||||
user8: expect.any(String),
|
||||
user9: expect.any(String),
|
||||
custom: expect.any(Object),
|
||||
}),
|
||||
);
|
||||
});
|
||||
@@ -600,7 +533,197 @@ describe('test event validator', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('test makeString function', () => {
|
||||
describe('test aliases import', () => {
|
||||
it('imports a well defined alias', () => {
|
||||
const testData = {
|
||||
rundown: [],
|
||||
settings: {
|
||||
app: 'ontime',
|
||||
version: '2.0.0',
|
||||
},
|
||||
aliases: [
|
||||
{
|
||||
enabled: false,
|
||||
alias: 'testalias',
|
||||
pathAndParams: 'testpathAndParams',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const parsed = parseAliases(testData);
|
||||
expect(parsed.length).toBe(1);
|
||||
|
||||
// generates missing id
|
||||
expect(parsed[0].alias).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('test views import', () => {
|
||||
it('imports data from file', () => {
|
||||
const testData = {
|
||||
rundown: [],
|
||||
settings: {
|
||||
app: 'ontime',
|
||||
version: '2.0.0',
|
||||
},
|
||||
viewSettings: {
|
||||
normalColor: '#ffffffcc',
|
||||
warningColor: '#FFAB33',
|
||||
dangerColor: '#ED3333',
|
||||
endMessage: '',
|
||||
overrideStyles: false,
|
||||
notAthing: true,
|
||||
},
|
||||
views: {
|
||||
overrideStyles: true,
|
||||
},
|
||||
};
|
||||
const expectedParsedViewSettings = {
|
||||
normalColor: '#ffffffcc',
|
||||
warningColor: '#FFAB33',
|
||||
dangerColor: '#ED3333',
|
||||
endMessage: '',
|
||||
overrideStyles: false,
|
||||
};
|
||||
const parsed = parseViewSettings(testData);
|
||||
expect(parsed).toStrictEqual(expectedParsedViewSettings);
|
||||
});
|
||||
|
||||
it('imports defaults to model', () => {
|
||||
const testData = {
|
||||
rundown: [],
|
||||
settings: {
|
||||
app: 'ontime',
|
||||
version: '2.0.0',
|
||||
},
|
||||
};
|
||||
const parsed = parseViewSettings(testData);
|
||||
expect(parsed).toStrictEqual({});
|
||||
});
|
||||
});
|
||||
|
||||
describe('test import of v2 datamodel', () => {
|
||||
it('ignores deprecated fields and generates new ones', async () => {
|
||||
const v2ProjectFile = {
|
||||
rundown: [
|
||||
{ type: SupportedEvent.Block, title: 'block-title', id: 'block-id' },
|
||||
{ type: SupportedEvent.Delay, duration: 0 },
|
||||
{ type: SupportedEvent.Event, title: 'block-title', id: 'block-id' },
|
||||
],
|
||||
project: {
|
||||
title: '',
|
||||
description: '',
|
||||
publicUrl: '',
|
||||
publicInfo: '',
|
||||
backstageUrl: '',
|
||||
backstageInfo: '',
|
||||
},
|
||||
settings: {
|
||||
app: 'ontime',
|
||||
version: '2.0.0',
|
||||
serverPort: 4001,
|
||||
editorKey: null,
|
||||
operatorKey: null,
|
||||
timeFormat: '24',
|
||||
language: 'en',
|
||||
},
|
||||
viewSettings: {
|
||||
overrideStyles: false,
|
||||
normalColor: '#ffffffcc',
|
||||
warningColor: '#FFAB33',
|
||||
dangerColor: '#ED3333',
|
||||
endMessage: '',
|
||||
},
|
||||
aliases: [],
|
||||
userFields: {
|
||||
user0: 'user0',
|
||||
user1: 'user1',
|
||||
user2: 'user2',
|
||||
user3: 'user3',
|
||||
user4: 'user4',
|
||||
user5: 'user5',
|
||||
user6: 'user6',
|
||||
user7: 'user7',
|
||||
user8: 'user8',
|
||||
user9: 'user9',
|
||||
},
|
||||
osc: {
|
||||
portIn: 8888,
|
||||
portOut: 9999,
|
||||
targetIP: '127.0.0.1',
|
||||
enabledIn: false,
|
||||
enabledOut: false,
|
||||
subscriptions: {
|
||||
onLoad: [],
|
||||
onStart: [],
|
||||
onPause: [],
|
||||
onStop: [],
|
||||
onUpdate: [],
|
||||
onFinish: [],
|
||||
},
|
||||
},
|
||||
http: {
|
||||
enabledOut: false,
|
||||
subscriptions: {
|
||||
onLoad: [],
|
||||
onStart: [],
|
||||
onPause: [],
|
||||
onStop: [],
|
||||
onUpdate: [],
|
||||
onFinish: [],
|
||||
},
|
||||
},
|
||||
};
|
||||
// @ts-expect-error -- we know this is wrong, testing imports outside domain
|
||||
const parsed = await parseJson(v2ProjectFile);
|
||||
expect(parsed.rundown.length).toBe(3);
|
||||
expect(parsed.rundown[0]).toMatchObject({ type: SupportedEvent.Block });
|
||||
expect(parsed.rundown[0]).toEqual(
|
||||
expect.objectContaining({
|
||||
id: expect.any(String),
|
||||
title: expect.any(String),
|
||||
}),
|
||||
);
|
||||
expect(parsed.rundown[1]).toMatchObject({ type: SupportedEvent.Delay });
|
||||
expect(parsed.rundown[1]).toEqual(
|
||||
expect.objectContaining({
|
||||
id: expect.any(String),
|
||||
duration: expect.any(Number),
|
||||
}),
|
||||
);
|
||||
expect(parsed.rundown[2]).toMatchObject({ type: SupportedEvent.Event });
|
||||
expect(parsed.rundown[2]).toEqual(
|
||||
expect.objectContaining({
|
||||
id: expect.any(String),
|
||||
cue: expect.any(String),
|
||||
title: expect.any(String),
|
||||
subtitle: expect.any(String),
|
||||
presenter: expect.any(String),
|
||||
note: expect.any(String),
|
||||
endAction: expect.any(String),
|
||||
timerType: expect.any(String),
|
||||
linkStart: null,
|
||||
timeStrategy: expect.any(String),
|
||||
timeStart: expect.any(Number),
|
||||
timeEnd: expect.any(Number),
|
||||
duration: expect.any(Number),
|
||||
isPublic: expect.any(Boolean),
|
||||
skip: expect.any(Boolean),
|
||||
colour: expect.any(String),
|
||||
revision: expect.any(Number),
|
||||
timeWarning: expect.any(Number),
|
||||
timeDanger: expect.any(Number),
|
||||
custom: expect.any(Object),
|
||||
}),
|
||||
);
|
||||
// @ts-expect-error -- checking if the field is removed
|
||||
expect(parsed?.userFields).toBeUndefined();
|
||||
expect(parsed.osc).toMatchObject({ subscriptions: [] });
|
||||
expect(parsed.http).toMatchObject({ enabledOut: false, subscriptions: [] });
|
||||
});
|
||||
});
|
||||
|
||||
describe('makeString()', () => {
|
||||
it('converts variables to string', () => {
|
||||
let val = 2;
|
||||
let expected = '2';
|
||||
@@ -625,7 +748,61 @@ describe('test makeString function', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('test parseExcel function', () => {
|
||||
describe('getCustomFieldData()', () => {
|
||||
it('generates a list of keys from the given import map', () => {
|
||||
const importMap = {
|
||||
worksheet: 'event schedule',
|
||||
timeStart: 'time start',
|
||||
timeEnd: 'time end',
|
||||
duration: 'duration',
|
||||
cue: 'cue',
|
||||
title: 'title',
|
||||
presenter: 'presenter',
|
||||
subtitle: 'subtitle',
|
||||
isPublic: 'public',
|
||||
skip: 'skip',
|
||||
note: 'notes',
|
||||
colour: 'colour',
|
||||
endAction: 'end action',
|
||||
timerType: 'timer type',
|
||||
timeWarning: 'warning time',
|
||||
timeDanger: 'danger time',
|
||||
custom: {
|
||||
lighting: 'lx',
|
||||
sound: 'sound',
|
||||
video: 'av',
|
||||
},
|
||||
};
|
||||
|
||||
const result = getCustomFieldData(importMap);
|
||||
expect(result.customFields).toStrictEqual({
|
||||
lighting: {
|
||||
type: 'string',
|
||||
colour: '',
|
||||
label: 'lighting',
|
||||
},
|
||||
sound: {
|
||||
type: 'string',
|
||||
colour: '',
|
||||
label: 'sound',
|
||||
},
|
||||
video: {
|
||||
type: 'string',
|
||||
colour: '',
|
||||
label: 'video',
|
||||
},
|
||||
});
|
||||
|
||||
// it is an inverted record of <importKey, ontimeKey>
|
||||
expect(result.customFieldImportKeys).toStrictEqual({
|
||||
lx: 'lighting',
|
||||
sound: 'sound',
|
||||
av: 'video',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('parseExcel()', () => {
|
||||
it('parses the example file', async () => {
|
||||
const testdata = [
|
||||
['Ontime ┬À Schedule Template'],
|
||||
@@ -705,17 +882,20 @@ describe('test parseExcel function', () => {
|
||||
[],
|
||||
];
|
||||
|
||||
const partialOptions = {
|
||||
user0: 'test0',
|
||||
user1: 'test1',
|
||||
user2: 'test2',
|
||||
user3: 'test3',
|
||||
user4: 'test4',
|
||||
user5: 'test5',
|
||||
user6: 'test6',
|
||||
user7: 'test7',
|
||||
user8: 'test8',
|
||||
user9: 'test9',
|
||||
// partial import map with only custom fields
|
||||
const importMap = {
|
||||
custom: {
|
||||
user0: 'test0',
|
||||
user1: 'test1',
|
||||
user2: 'test2',
|
||||
user3: 'test3',
|
||||
user4: 'test4',
|
||||
user5: 'test5',
|
||||
user6: 'test6',
|
||||
user7: 'test7',
|
||||
user8: 'test8',
|
||||
user9: 'test9',
|
||||
},
|
||||
};
|
||||
|
||||
// TODO: update tests once import is resolved
|
||||
@@ -731,16 +911,18 @@ describe('test parseExcel function', () => {
|
||||
isPublic: true,
|
||||
skip: false,
|
||||
note: 'Ballyhoo',
|
||||
user0: 'a0',
|
||||
user1: 'a1',
|
||||
user2: 'a2',
|
||||
user3: 'a3',
|
||||
user4: 'a4',
|
||||
user5: 'a5',
|
||||
user6: 'a6',
|
||||
user7: 'a7',
|
||||
user8: 'a8',
|
||||
user9: 'a9',
|
||||
custom: {
|
||||
user0: { value: 'a0' },
|
||||
user1: { value: 'a1' },
|
||||
user2: { value: 'a2' },
|
||||
user3: { value: 'a3' },
|
||||
user4: { value: 'a4' },
|
||||
user5: { value: 'a5' },
|
||||
user6: { value: 'a6' },
|
||||
user7: { value: 'a7' },
|
||||
user8: { value: 'a8' },
|
||||
user9: { value: 'a9' },
|
||||
},
|
||||
colour: 'red',
|
||||
type: 'event',
|
||||
cue: '101',
|
||||
@@ -756,174 +938,214 @@ describe('test parseExcel function', () => {
|
||||
isPublic: false,
|
||||
skip: true,
|
||||
note: 'Rainbow chase',
|
||||
user0: 'b0',
|
||||
user5: 'b5',
|
||||
custom: {
|
||||
user0: { value: 'b0' },
|
||||
user5: { value: 'b5' },
|
||||
},
|
||||
colour: '#F00',
|
||||
type: 'event',
|
||||
cue: '102',
|
||||
},
|
||||
];
|
||||
|
||||
const parsedData = parseExcel(testdata, partialOptions);
|
||||
expect(parsedData.rundown).toBeDefined();
|
||||
const parsedData = parseExcel(testdata, importMap);
|
||||
expect(parsedData.customFields).toStrictEqual({
|
||||
user0: {
|
||||
type: 'string',
|
||||
colour: '',
|
||||
label: 'user0',
|
||||
},
|
||||
user1: {
|
||||
type: 'string',
|
||||
colour: '',
|
||||
label: 'user1',
|
||||
},
|
||||
user2: {
|
||||
type: 'string',
|
||||
colour: '',
|
||||
label: 'user2',
|
||||
},
|
||||
user3: {
|
||||
type: 'string',
|
||||
colour: '',
|
||||
label: 'user3',
|
||||
},
|
||||
user4: {
|
||||
type: 'string',
|
||||
colour: '',
|
||||
label: 'user4',
|
||||
},
|
||||
user5: {
|
||||
type: 'string',
|
||||
colour: '',
|
||||
label: 'user5',
|
||||
},
|
||||
user6: {
|
||||
type: 'string',
|
||||
colour: '',
|
||||
label: 'user6',
|
||||
},
|
||||
user7: {
|
||||
type: 'string',
|
||||
colour: '',
|
||||
label: 'user7',
|
||||
},
|
||||
user8: {
|
||||
type: 'string',
|
||||
colour: '',
|
||||
label: 'user8',
|
||||
},
|
||||
user9: {
|
||||
type: 'string',
|
||||
colour: '',
|
||||
label: 'user9',
|
||||
},
|
||||
});
|
||||
expect(parsedData.rundown.length).toBe(2);
|
||||
expect(parsedData.rundown[0]).toMatchObject(expectedParsedRundown[0]);
|
||||
expect(parsedData.rundown[1]).toMatchObject(expectedParsedRundown[1]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('test aliases import', () => {
|
||||
it('imports a well defined alias', () => {
|
||||
const testData = {
|
||||
rundown: [],
|
||||
settings: {
|
||||
app: 'ontime',
|
||||
version: '2.0.0',
|
||||
},
|
||||
aliases: [
|
||||
{
|
||||
enabled: false,
|
||||
alias: 'testalias',
|
||||
pathAndParams: 'testpathAndParams',
|
||||
},
|
||||
it('parses a file without custom fields', async () => {
|
||||
const testdata = [
|
||||
['Ontime ┬À Schedule Template'],
|
||||
[],
|
||||
[
|
||||
'Time Start',
|
||||
'Time End',
|
||||
'Title',
|
||||
'Presenter',
|
||||
'Subtitle',
|
||||
'End Action',
|
||||
'Timer type',
|
||||
'Public',
|
||||
'Skip',
|
||||
'Notes',
|
||||
'test0',
|
||||
'test1',
|
||||
'test2',
|
||||
'test3',
|
||||
'test4',
|
||||
'test5',
|
||||
'test6',
|
||||
'test7',
|
||||
'test8',
|
||||
'test9',
|
||||
'Colour',
|
||||
'cue',
|
||||
],
|
||||
[
|
||||
'1899-12-30T07:00:00.000Z',
|
||||
'1899-12-30T08:00:10.000Z',
|
||||
'Guest Welcome',
|
||||
'Carlos',
|
||||
'Getting things started',
|
||||
'',
|
||||
'',
|
||||
'x',
|
||||
'',
|
||||
'Ballyhoo',
|
||||
'a0',
|
||||
'a1',
|
||||
'a2',
|
||||
'a3',
|
||||
'a4',
|
||||
'a5',
|
||||
'a6',
|
||||
'a7',
|
||||
'a8',
|
||||
'a9',
|
||||
'red',
|
||||
101,
|
||||
],
|
||||
[
|
||||
'1899-12-30T08:00:00.000Z',
|
||||
'1899-12-30T08:30:00.000Z',
|
||||
'A song from the hearth',
|
||||
'Still Carlos',
|
||||
'Derailing early',
|
||||
'load-next',
|
||||
'clock',
|
||||
'',
|
||||
'x',
|
||||
'Rainbow chase',
|
||||
'b0',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'b5',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'#F00',
|
||||
102,
|
||||
],
|
||||
[],
|
||||
];
|
||||
|
||||
// partial import map with only custom fields
|
||||
const importMap = {
|
||||
custom: {
|
||||
niu1: 'niu1',
|
||||
niu2: 'niu2',
|
||||
},
|
||||
};
|
||||
|
||||
const parsed = parseAliases(testData);
|
||||
expect(parsed.length).toBe(1);
|
||||
// TODO: update tests once import is resolved
|
||||
const expectedParsedRundown = [
|
||||
{
|
||||
//timeStart: 28800000,
|
||||
//timeEnd: 32410000,
|
||||
title: 'Guest Welcome',
|
||||
presenter: 'Carlos',
|
||||
subtitle: 'Getting things started',
|
||||
timerType: 'count-down',
|
||||
endAction: 'none',
|
||||
isPublic: true,
|
||||
skip: false,
|
||||
note: 'Ballyhoo',
|
||||
custom: {},
|
||||
colour: 'red',
|
||||
type: 'event',
|
||||
cue: '101',
|
||||
},
|
||||
{
|
||||
//timeStart: 32400000,
|
||||
//timeEnd: 34200000,
|
||||
title: 'A song from the hearth',
|
||||
presenter: 'Still Carlos',
|
||||
subtitle: 'Derailing early',
|
||||
timerType: 'clock',
|
||||
endAction: 'load-next',
|
||||
isPublic: false,
|
||||
skip: true,
|
||||
note: 'Rainbow chase',
|
||||
custom: {},
|
||||
colour: '#F00',
|
||||
type: 'event',
|
||||
cue: '102',
|
||||
},
|
||||
];
|
||||
|
||||
// generates missing id
|
||||
expect(parsed[0].alias).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('test userFields import', () => {
|
||||
const model = dbModel.userFields;
|
||||
it('imports a fully defined user fields', () => {
|
||||
const testUserFields = {
|
||||
user0: 'test0',
|
||||
user1: 'test1',
|
||||
user2: 'test2',
|
||||
user3: 'test3',
|
||||
user4: 'test4',
|
||||
user5: 'test5',
|
||||
user6: 'test6',
|
||||
user7: 'test7',
|
||||
user8: 'test8',
|
||||
user9: 'test9',
|
||||
};
|
||||
|
||||
const testData = {
|
||||
rundown: [],
|
||||
settings: {
|
||||
app: 'ontime',
|
||||
version: '2.0.0',
|
||||
},
|
||||
userFields: testUserFields,
|
||||
};
|
||||
|
||||
const parsed = parseUserFields(testData);
|
||||
expect(parsed).toStrictEqual(testUserFields);
|
||||
});
|
||||
|
||||
it('imports a partially defined user fields', () => {
|
||||
const testUserFields = {
|
||||
user0: 'test0',
|
||||
user1: 'test1',
|
||||
user7: 'test7',
|
||||
user8: 'test8',
|
||||
user9: 'test9',
|
||||
};
|
||||
|
||||
const expected = {
|
||||
...model,
|
||||
...testUserFields,
|
||||
};
|
||||
|
||||
const testData = {
|
||||
rundown: [],
|
||||
settings: {
|
||||
app: 'ontime',
|
||||
version: '2.0.0',
|
||||
},
|
||||
userFields: testUserFields,
|
||||
};
|
||||
|
||||
const parsed = parseUserFields(testData);
|
||||
expect(parsed).toStrictEqual(expected);
|
||||
});
|
||||
|
||||
it('handles missing user fields', () => {
|
||||
const testData = {
|
||||
rundown: [],
|
||||
settings: {
|
||||
app: 'ontime',
|
||||
version: '2.0.0',
|
||||
},
|
||||
};
|
||||
|
||||
const parsed = parseUserFields(testData);
|
||||
expect(parsed).toStrictEqual(model);
|
||||
expect(parsed).toStrictEqual(model);
|
||||
});
|
||||
|
||||
it('ignores badly defined fields', () => {
|
||||
const testData = {
|
||||
rundown: [],
|
||||
settings: {
|
||||
app: 'ontime',
|
||||
version: '2.0.0',
|
||||
},
|
||||
userFields: {
|
||||
notThis: 'this shouldng be accepted',
|
||||
orThis: 'this neither',
|
||||
},
|
||||
};
|
||||
|
||||
const parsed = parseUserFields(testData);
|
||||
expect(parsed).toStrictEqual(model);
|
||||
});
|
||||
});
|
||||
|
||||
describe('test views import', () => {
|
||||
it('imports data from file', () => {
|
||||
const testData = {
|
||||
rundown: [],
|
||||
settings: {
|
||||
app: 'ontime',
|
||||
version: '2.0.0',
|
||||
},
|
||||
viewSettings: {
|
||||
normalColor: '#ffffffcc',
|
||||
warningColor: '#FFAB33',
|
||||
dangerColor: '#ED3333',
|
||||
endMessage: '',
|
||||
overrideStyles: false,
|
||||
notAthing: true,
|
||||
},
|
||||
views: {
|
||||
overrideStyles: true,
|
||||
},
|
||||
};
|
||||
const expectedParsedViewSettings = {
|
||||
normalColor: '#ffffffcc',
|
||||
warningColor: '#FFAB33',
|
||||
dangerColor: '#ED3333',
|
||||
endMessage: '',
|
||||
overrideStyles: false,
|
||||
};
|
||||
const parsed = parseViewSettings(testData);
|
||||
expect(parsed).toStrictEqual(expectedParsedViewSettings);
|
||||
});
|
||||
|
||||
it('imports defaults to model', () => {
|
||||
const testData = {
|
||||
rundown: [],
|
||||
settings: {
|
||||
app: 'ontime',
|
||||
version: '2.0.0',
|
||||
},
|
||||
};
|
||||
const parsed = parseViewSettings(testData);
|
||||
expect(parsed).toStrictEqual({});
|
||||
const parsedData = parseExcel(testdata, importMap);
|
||||
expect(parsedData.customFields).toStrictEqual({
|
||||
niu1: {
|
||||
type: 'string',
|
||||
colour: '',
|
||||
label: 'niu1',
|
||||
},
|
||||
niu2: {
|
||||
type: 'string',
|
||||
colour: '',
|
||||
label: 'niu2',
|
||||
},
|
||||
});
|
||||
expect(parsedData.rundown.length).toBe(2);
|
||||
expect(parsedData.rundown[0]).toMatchObject(expectedParsedRundown[0]);
|
||||
expect(parsedData.rundown[1]).toMatchObject(expectedParsedRundown[1]);
|
||||
});
|
||||
|
||||
it.todo('imports events and blocks, ignores otherwise', () => {});
|
||||
});
|
||||
|
||||
+72
-119
@@ -1,11 +1,11 @@
|
||||
import {
|
||||
generateId,
|
||||
isExcelImportMap,
|
||||
type ExcelImportMap,
|
||||
defaultExcelImportMap,
|
||||
isImportMap,
|
||||
type ImportMap,
|
||||
defaultImportMap,
|
||||
validateEndAction,
|
||||
validateTimerType,
|
||||
type ExcelImportOptions,
|
||||
type ImportOptions,
|
||||
validateTimes,
|
||||
isKnownTimerType,
|
||||
validateLinkStart,
|
||||
@@ -15,10 +15,11 @@ import {
|
||||
OntimeEvent,
|
||||
OntimeRundown,
|
||||
SupportedEvent,
|
||||
UserFields,
|
||||
EndAction,
|
||||
TimerType,
|
||||
TimeStrategy,
|
||||
CustomFields,
|
||||
EventCustomFields,
|
||||
} from 'ontime-types';
|
||||
|
||||
import fs from 'fs';
|
||||
@@ -35,7 +36,6 @@ import {
|
||||
parseHttp,
|
||||
parseRundown,
|
||||
parseSettings,
|
||||
parseUserFields,
|
||||
parseViewSettings,
|
||||
parseCustomFields,
|
||||
} from './parserFunctions.js';
|
||||
@@ -46,34 +46,46 @@ import { coerceBoolean } from './coerceType.js';
|
||||
export const EXCEL_MIME = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
|
||||
export const JSON_MIME = 'application/json';
|
||||
|
||||
type ExcelData = Pick<DatabaseModel, 'rundown' | 'userFields'> & {
|
||||
type ExcelData = Pick<DatabaseModel, 'rundown' | 'customFields'> & {
|
||||
rundownMetadata: Record<string, { row: number; col: number }>;
|
||||
};
|
||||
|
||||
export function getCustomFieldData(importMap: ImportMap): {
|
||||
customFields: CustomFields;
|
||||
customFieldImportKeys: Record<keyof CustomFields, string>;
|
||||
} {
|
||||
const customFields = {};
|
||||
const customFieldImportKeys = {};
|
||||
for (const key in importMap.custom) {
|
||||
const ontimeName = key;
|
||||
const importName = importMap.custom[key];
|
||||
customFields[ontimeName] = {
|
||||
type: 'string',
|
||||
colour: '',
|
||||
label: ontimeName,
|
||||
};
|
||||
customFieldImportKeys[importName] = ontimeName;
|
||||
}
|
||||
return { customFields, customFieldImportKeys };
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Excel array parser
|
||||
* @param {array} excelData - array with excel sheet
|
||||
* @param {ExcelImportOptions} options - an object that contains the import map
|
||||
* @param {ImportOptions} options - an object that contains the import map
|
||||
* @returns {object} - parsed object
|
||||
*/
|
||||
export const parseExcel = (excelData: unknown[][], options?: Partial<ExcelImportMap>): ExcelData => {
|
||||
export const parseExcel = (excelData: unknown[][], options?: Partial<ImportMap>): ExcelData => {
|
||||
const rundownMetadata = {};
|
||||
const importMap: ExcelImportMap = { ...defaultExcelImportMap, ...options };
|
||||
const importMap: ImportMap = { ...defaultImportMap, ...options };
|
||||
|
||||
for (const [key, value] of Object.entries(importMap)) {
|
||||
importMap[key] = value.toLocaleLowerCase();
|
||||
if (typeof value === 'string') {
|
||||
importMap[key] = value.toLocaleLowerCase();
|
||||
}
|
||||
}
|
||||
const customUserFields: UserFields = {
|
||||
user0: importMap.user0,
|
||||
user1: importMap.user1,
|
||||
user2: importMap.user2,
|
||||
user3: importMap.user3,
|
||||
user4: importMap.user4,
|
||||
user5: importMap.user5,
|
||||
user6: importMap.user6,
|
||||
user7: importMap.user7,
|
||||
user8: importMap.user8,
|
||||
user9: importMap.user9,
|
||||
};
|
||||
|
||||
const { customFields, customFieldImportKeys } = getCustomFieldData(importMap);
|
||||
const rundown: OntimeRundown = [];
|
||||
|
||||
// title stuff: strings
|
||||
@@ -99,23 +111,15 @@ export const parseExcel = (excelData: unknown[][], options?: Partial<ExcelImport
|
||||
let endActionIndex: number | null = null;
|
||||
let timerTypeIndex: number | null = null;
|
||||
|
||||
// user fields: strings
|
||||
let user0Index: number | null = null;
|
||||
let user1Index: number | null = null;
|
||||
let user2Index: number | null = null;
|
||||
let user3Index: number | null = null;
|
||||
let user4Index: number | null = null;
|
||||
let user5Index: number | null = null;
|
||||
let user6Index: number | null = null;
|
||||
let user7Index: number | null = null;
|
||||
let user8Index: number | null = null;
|
||||
let user9Index: number | null = null;
|
||||
// record of column index and the name of the field
|
||||
const customFieldIndexes: Record<number, string> = {};
|
||||
|
||||
excelData.forEach((row, rowIndex) => {
|
||||
if (row.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: extract generating handlers from importMap
|
||||
const handlers = {
|
||||
[importMap.timeStart]: (row: number, col: number) => {
|
||||
timeStartIndex = col;
|
||||
@@ -162,7 +166,6 @@ export const parseExcel = (excelData: unknown[][], options?: Partial<ExcelImport
|
||||
colourIndex = col;
|
||||
rundownMetadata['colour'] = { row, col };
|
||||
},
|
||||
|
||||
[importMap.endAction]: (row: number, col: number) => {
|
||||
endActionIndex = col;
|
||||
rundownMetadata['endAction'] = { row, col };
|
||||
@@ -179,50 +182,15 @@ export const parseExcel = (excelData: unknown[][], options?: Partial<ExcelImport
|
||||
timeDangerIndex = col;
|
||||
rundownMetadata['timeDangerIndex'] = { row, col };
|
||||
},
|
||||
|
||||
[importMap.user0]: (row: number, col: number) => {
|
||||
user0Index = col;
|
||||
rundownMetadata['user0'] = { row, col };
|
||||
},
|
||||
[importMap.user1]: (row: number, col: number) => {
|
||||
user1Index = col;
|
||||
rundownMetadata['user1'] = { row, col };
|
||||
},
|
||||
[importMap.user2]: (row: number, col: number) => {
|
||||
user2Index = col;
|
||||
rundownMetadata['user2'] = { row, col };
|
||||
},
|
||||
[importMap.user3]: (row: number, col: number) => {
|
||||
user3Index = col;
|
||||
rundownMetadata['user3'] = { row, col };
|
||||
},
|
||||
[importMap.user4]: (row: number, col: number) => {
|
||||
user4Index = col;
|
||||
rundownMetadata['user4'] = { row, col };
|
||||
},
|
||||
[importMap.user5]: (row: number, col: number) => {
|
||||
user5Index = col;
|
||||
rundownMetadata['user5'] = { row, col };
|
||||
},
|
||||
[importMap.user6]: (row: number, col: number) => {
|
||||
user6Index = col;
|
||||
rundownMetadata['user6'] = { row, col };
|
||||
},
|
||||
[importMap.user7]: (row: number, col: number) => {
|
||||
user7Index = col;
|
||||
rundownMetadata['user7'] = { row, col };
|
||||
},
|
||||
[importMap.user8]: (row: number, col: number) => {
|
||||
user8Index = col;
|
||||
rundownMetadata['user8'] = { row, col };
|
||||
},
|
||||
[importMap.user9]: (row: number, col: number) => {
|
||||
user9Index = col;
|
||||
rundownMetadata['user9'] = { row, col };
|
||||
custom: (row: number, col: number, columnText: string) => {
|
||||
customFieldIndexes[col] = columnText;
|
||||
rundownMetadata[`custom-${columnText}`] = { row, col };
|
||||
},
|
||||
} as const;
|
||||
|
||||
const event: any = {};
|
||||
const eventCustomFields: EventCustomFields = {};
|
||||
|
||||
row.forEach((column, j) => {
|
||||
// 1. we check if we have set a flag for a known field
|
||||
if (j === timerTypeIndex) {
|
||||
@@ -267,49 +235,45 @@ export const parseExcel = (excelData: unknown[][], options?: Partial<ExcelImport
|
||||
event.timeDanger = parseExcelDate(column);
|
||||
} else if (j === colourIndex) {
|
||||
event.colour = makeString(column, '');
|
||||
} else if (j === user0Index) {
|
||||
event.user0 = makeString(column, '');
|
||||
} else if (j === user1Index) {
|
||||
event.user1 = makeString(column, '');
|
||||
} else if (j === user2Index) {
|
||||
event.user2 = makeString(column, '');
|
||||
} else if (j === user3Index) {
|
||||
event.user3 = makeString(column, '');
|
||||
} else if (j === user4Index) {
|
||||
event.user4 = makeString(column, '');
|
||||
} else if (j === user5Index) {
|
||||
event.user5 = makeString(column, '');
|
||||
} else if (j === user6Index) {
|
||||
event.user6 = makeString(column, '');
|
||||
} else if (j === user7Index) {
|
||||
event.user7 = makeString(column, '');
|
||||
} else if (j === user8Index) {
|
||||
event.user8 = makeString(column, '');
|
||||
} else if (j === user9Index) {
|
||||
event.user9 = makeString(column, '');
|
||||
} else if (j in customFieldIndexes) {
|
||||
const importKey = customFieldIndexes[j];
|
||||
const ontimeKey = customFieldImportKeys[importKey];
|
||||
eventCustomFields[ontimeKey] = { value: makeString(column, '') };
|
||||
} else {
|
||||
// 2. if there is no flag, lets see if we know the field type
|
||||
if (typeof column === 'string') {
|
||||
const col = column.toLowerCase();
|
||||
|
||||
if (handlers[col]) {
|
||||
handlers[col](rowIndex, j);
|
||||
// we cant deal with empty content
|
||||
if (column.length === 0) {
|
||||
return;
|
||||
}
|
||||
const columnText = column.toLowerCase();
|
||||
|
||||
// check if it is an ontime column
|
||||
if (handlers[columnText]) {
|
||||
handlers[columnText](rowIndex, j, undefined);
|
||||
}
|
||||
|
||||
// check if it is a custom field
|
||||
if (columnText in customFieldImportKeys) {
|
||||
handlers.custom(rowIndex, j, columnText);
|
||||
}
|
||||
|
||||
// else. we don't know how to handle this column
|
||||
// just ignore it
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (Object.keys(event).length > 0) {
|
||||
// if any data was found, push to array
|
||||
rundown.push({ ...event });
|
||||
// if any data was found in row, push to array
|
||||
const keysFound = Object.keys(event).length + Object.keys(eventCustomFields).length;
|
||||
if (keysFound > 0) {
|
||||
rundown.push({ ...event, custom: { ...eventCustomFields } });
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
rundown,
|
||||
userFields: customUserFields,
|
||||
customFields,
|
||||
rundownMetadata,
|
||||
};
|
||||
};
|
||||
@@ -330,7 +294,6 @@ export const parseJson = async (jsonData: Partial<DatabaseModel>): Promise<Datab
|
||||
settings: parseSettings(jsonData) ?? dbModel.settings,
|
||||
viewSettings: parseViewSettings(jsonData) ?? dbModel.viewSettings,
|
||||
aliases: parseAliases(jsonData),
|
||||
userFields: parseUserFields(jsonData),
|
||||
customFields: parseCustomFields(jsonData),
|
||||
osc: parseOsc(jsonData) ?? dbModel.osc,
|
||||
http: parseHttp(jsonData) ?? dbModel.http,
|
||||
@@ -386,16 +349,6 @@ export function createPatch(originalEvent: OntimeEvent, patchEvent: Partial<Onti
|
||||
isPublic: typeof patchEvent.isPublic === 'boolean' ? patchEvent.isPublic : originalEvent.isPublic,
|
||||
skip: typeof patchEvent.skip === 'boolean' ? patchEvent.skip : originalEvent.skip,
|
||||
note: makeString(patchEvent.note, originalEvent.note),
|
||||
user0: makeString(patchEvent.user0, originalEvent.user0),
|
||||
user1: makeString(patchEvent.user1, originalEvent.user1),
|
||||
user2: makeString(patchEvent.user2, originalEvent.user2),
|
||||
user3: makeString(patchEvent.user3, originalEvent.user3),
|
||||
user4: makeString(patchEvent.user4, originalEvent.user4),
|
||||
user5: makeString(patchEvent.user5, originalEvent.user5),
|
||||
user6: makeString(patchEvent.user6, originalEvent.user6),
|
||||
user7: makeString(patchEvent.user7, originalEvent.user7),
|
||||
user8: makeString(patchEvent.user8, originalEvent.user8),
|
||||
user9: makeString(patchEvent.user9, originalEvent.user9),
|
||||
colour: makeString(patchEvent.colour, originalEvent.colour),
|
||||
// short circuit empty string
|
||||
cue: makeString(patchEvent.cue ?? null, originalEvent.cue),
|
||||
@@ -436,7 +389,7 @@ type ResponseOK = {
|
||||
* @param options - import options
|
||||
* @return {object} - parse result message
|
||||
*/
|
||||
export const fileHandler = async (file: string, options: ExcelImportOptions): Promise<Partial<ResponseOK>> => {
|
||||
export const fileHandler = async (file: string, options: ImportOptions): Promise<Partial<ResponseOK>> => {
|
||||
const res: Partial<ResponseOK> = {};
|
||||
|
||||
const fileName = path.basename(file);
|
||||
@@ -444,8 +397,8 @@ export const fileHandler = async (file: string, options: ExcelImportOptions): Pr
|
||||
// check which file type are we dealing with
|
||||
if (file.endsWith('.xlsx')) {
|
||||
// we need to check that the options are applicable
|
||||
if (!isExcelImportMap(options)) {
|
||||
throw new Error('Got incorrect options to excel import');
|
||||
if (!isImportMap(options)) {
|
||||
throw new Error('Got incorrect options for spreadsheet import');
|
||||
}
|
||||
|
||||
const excelData = xlsx
|
||||
@@ -461,9 +414,9 @@ export const fileHandler = async (file: string, options: ExcelImportOptions): Pr
|
||||
res.data = {};
|
||||
res.data.rundown = parseRundown(dataFromExcel);
|
||||
if (res.data.rundown.length < 1) {
|
||||
throw new Error(`Could not find data to import in the worksheet ${options.worksheet}`);
|
||||
throw new Error(`Could not find data to import in the worksheet: ${options.worksheet}`);
|
||||
}
|
||||
res.data.userFields = parseUserFields(dataFromExcel);
|
||||
res.data.customFields = parseCustomFields(dataFromExcel);
|
||||
|
||||
deleteFile(file);
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ import {
|
||||
OSCSettings,
|
||||
ProjectData,
|
||||
Settings,
|
||||
UserFields,
|
||||
ViewSettings,
|
||||
OscSubscription,
|
||||
DatabaseModel,
|
||||
@@ -251,33 +250,6 @@ export const parseAliases = (data): Alias[] => {
|
||||
return newAliases;
|
||||
};
|
||||
|
||||
/**
|
||||
* Parse userFields entry
|
||||
* @param {object} data - data object
|
||||
* @returns {object} - event object data
|
||||
*/
|
||||
export const parseUserFields = (data): UserFields => {
|
||||
const newUserFields: UserFields = { ...dbModel.userFields };
|
||||
|
||||
if ('userFields' in data) {
|
||||
console.log('Found User Fields definition, importing...');
|
||||
// we will only be importing the fields we know, so look for that
|
||||
try {
|
||||
let fieldsFound = 0;
|
||||
for (const n in newUserFields) {
|
||||
if (n in data.userFields) {
|
||||
fieldsFound++;
|
||||
newUserFields[n] = data.userFields[n];
|
||||
}
|
||||
}
|
||||
console.log(`Uploaded ${fieldsFound} user fields`);
|
||||
} catch (error) {
|
||||
console.log(`Error: ${error}`);
|
||||
}
|
||||
}
|
||||
return { ...newUserFields };
|
||||
};
|
||||
|
||||
/**
|
||||
* Parse customFields entry
|
||||
* @param {object} data - data object
|
||||
|
||||
Reference in New Issue
Block a user