mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-26 01:19:11 +00:00
fix: set not initialised custom field from api (#1926)
This commit is contained in:
committed by
GitHub
parent
cbba19a9b1
commit
deb1f043c7
@@ -1,35 +1,52 @@
|
|||||||
import { EntryCustomFields, OntimeEvent } from 'ontime-types';
|
import { CustomFields, EntryCustomFields, OntimeDelay, OntimeEvent } from 'ontime-types';
|
||||||
import { isValidChangeProperty } from '../integration.utils.js';
|
import { isValidChangeProperty } from '../integration.utils.js';
|
||||||
|
|
||||||
describe('isValidChangeProperty()', () => {
|
describe('isValidChangeProperty()', () => {
|
||||||
test('correct value and property', () => {
|
test('allows changing a valid event property with valid value', () => {
|
||||||
const testEvent = {
|
const testEvent = {
|
||||||
id: 'test',
|
id: 'test',
|
||||||
duration: 111,
|
duration: 111,
|
||||||
} as OntimeEvent;
|
} as OntimeEvent;
|
||||||
|
|
||||||
expect(isValidChangeProperty(testEvent, 'duration', 123)).toBeTruthy();
|
expect(isValidChangeProperty(testEvent, 'duration', 123, {})).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('correct property and undefined value', () => {
|
test('forbids changing a valid event property with undefined value', () => {
|
||||||
const testEvent = {
|
const testEvent = {
|
||||||
id: 'test',
|
id: 'test',
|
||||||
duration: 111,
|
duration: 111,
|
||||||
} as OntimeEvent;
|
} as OntimeEvent;
|
||||||
|
|
||||||
expect(isValidChangeProperty(testEvent, 'duration', undefined)).toBeFalsy();
|
expect(isValidChangeProperty(testEvent, 'duration', undefined, {})).toBeFalsy();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('missing property and undefined value', () => {
|
test('forbids changing a non-existing property', () => {
|
||||||
const testEvent = {
|
const testEvent = {
|
||||||
id: 'test',
|
id: 'test',
|
||||||
duration: 111,
|
duration: 111,
|
||||||
} as OntimeEvent;
|
} as OntimeEvent;
|
||||||
|
|
||||||
expect(isValidChangeProperty(testEvent, 'missing', 123)).toBeFalsy();
|
expect(isValidChangeProperty(testEvent, 'missing', 123, {})).toBeFalsy();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('non existing custom value', () => {
|
test('forbids changing customValues in event without custom fields', () => {
|
||||||
|
const testDelay = {
|
||||||
|
id: 'test',
|
||||||
|
duration: 111,
|
||||||
|
} as OntimeDelay;
|
||||||
|
|
||||||
|
const testCustomFields = {
|
||||||
|
test: {
|
||||||
|
type: 'text',
|
||||||
|
colour: '#ffffff',
|
||||||
|
label: 'Test Field',
|
||||||
|
},
|
||||||
|
} satisfies CustomFields;
|
||||||
|
|
||||||
|
expect(isValidChangeProperty(testDelay, 'custom:test', 123, testCustomFields)).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('forbids changing a non-existing custom value', () => {
|
||||||
const testEvent = {
|
const testEvent = {
|
||||||
id: 'test',
|
id: 'test',
|
||||||
duration: 111,
|
duration: 111,
|
||||||
@@ -38,10 +55,10 @@ describe('isValidChangeProperty()', () => {
|
|||||||
} as EntryCustomFields,
|
} as EntryCustomFields,
|
||||||
} as OntimeEvent;
|
} as OntimeEvent;
|
||||||
|
|
||||||
expect(isValidChangeProperty(testEvent, 'custom:test', 123)).toBeFalsy();
|
expect(isValidChangeProperty(testEvent, 'custom:test', 123, {})).toBeFalsy();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('existing custom value', () => {
|
test('allows changing an existing custom value', () => {
|
||||||
const testEvent = {
|
const testEvent = {
|
||||||
id: 'test',
|
id: 'test',
|
||||||
duration: 111,
|
duration: 111,
|
||||||
@@ -50,10 +67,18 @@ describe('isValidChangeProperty()', () => {
|
|||||||
} as EntryCustomFields,
|
} as EntryCustomFields,
|
||||||
} as OntimeEvent;
|
} as OntimeEvent;
|
||||||
|
|
||||||
expect(isValidChangeProperty(testEvent, 'custom:test', 123)).toBeTruthy();
|
const testCustomFields = {
|
||||||
|
test: {
|
||||||
|
type: 'text',
|
||||||
|
colour: '#ffffff',
|
||||||
|
label: 'Test Field',
|
||||||
|
},
|
||||||
|
} satisfies CustomFields;
|
||||||
|
|
||||||
|
expect(isValidChangeProperty(testEvent, 'custom:test', 123, testCustomFields)).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('empty custom definition', () => {
|
test('forbids changing a custom field with a missing value', () => {
|
||||||
const testEvent = {
|
const testEvent = {
|
||||||
id: 'test',
|
id: 'test',
|
||||||
duration: 111,
|
duration: 111,
|
||||||
@@ -62,10 +87,18 @@ describe('isValidChangeProperty()', () => {
|
|||||||
} as EntryCustomFields,
|
} as EntryCustomFields,
|
||||||
} as OntimeEvent;
|
} as OntimeEvent;
|
||||||
|
|
||||||
expect(isValidChangeProperty(testEvent, 'custom:', 123)).toBeFalsy();
|
const testCustomFields = {
|
||||||
|
test: {
|
||||||
|
type: 'text',
|
||||||
|
colour: '#ffffff',
|
||||||
|
label: 'Test Field',
|
||||||
|
},
|
||||||
|
} satisfies CustomFields;
|
||||||
|
|
||||||
|
expect(isValidChangeProperty(testEvent, 'custom:', 123, testCustomFields)).toBeFalsy();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('build-in in custom', () => {
|
test('forbids references to prototype properties in custom object', () => {
|
||||||
const testEvent = {
|
const testEvent = {
|
||||||
id: 'test',
|
id: 'test',
|
||||||
duration: 111,
|
duration: 111,
|
||||||
@@ -74,10 +107,18 @@ describe('isValidChangeProperty()', () => {
|
|||||||
} as EntryCustomFields,
|
} as EntryCustomFields,
|
||||||
} as OntimeEvent;
|
} as OntimeEvent;
|
||||||
|
|
||||||
expect(isValidChangeProperty(testEvent, 'custom:toString', 123)).toBeFalsy();
|
const testCustomFields = {
|
||||||
|
test: {
|
||||||
|
type: 'text',
|
||||||
|
colour: '#ffffff',
|
||||||
|
label: 'Test Field',
|
||||||
|
},
|
||||||
|
} satisfies CustomFields;
|
||||||
|
|
||||||
|
expect(isValidChangeProperty(testEvent, 'custom:toString', 123, testCustomFields)).toBeFalsy();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('build-in in top object', () => {
|
test('forbids references to prototype properties in object', () => {
|
||||||
const testEvent = {
|
const testEvent = {
|
||||||
id: 'test',
|
id: 'test',
|
||||||
duration: 111,
|
duration: 111,
|
||||||
@@ -86,6 +127,14 @@ describe('isValidChangeProperty()', () => {
|
|||||||
} as EntryCustomFields,
|
} as EntryCustomFields,
|
||||||
} as OntimeEvent;
|
} as OntimeEvent;
|
||||||
|
|
||||||
expect(isValidChangeProperty(testEvent, 'toString', 123)).toBeFalsy();
|
const testCustomFields = {
|
||||||
|
test: {
|
||||||
|
type: 'text',
|
||||||
|
colour: '#ffffff',
|
||||||
|
label: 'Test Field',
|
||||||
|
},
|
||||||
|
} satisfies CustomFields;
|
||||||
|
|
||||||
|
expect(isValidChangeProperty(testEvent, 'toString', 123, testCustomFields)).toBeFalsy();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import { socket } from '../adapters/WebsocketAdapter.js';
|
|||||||
import { coerceEnum } from '../utils/coerceType.js';
|
import { coerceEnum } from '../utils/coerceType.js';
|
||||||
import { editEntry } from '../api-data/rundown/rundown.service.js';
|
import { editEntry } from '../api-data/rundown/rundown.service.js';
|
||||||
import { willCauseRegeneration } from '../api-data/rundown/rundown.utils.js';
|
import { willCauseRegeneration } from '../api-data/rundown/rundown.utils.js';
|
||||||
import { getCurrentRundown } from '../api-data/rundown/rundown.dao.js';
|
import { getCurrentRundown, getProjectCustomFields } from '../api-data/rundown/rundown.dao.js';
|
||||||
|
|
||||||
let lastRequest: Date | null = null;
|
let lastRequest: Date | null = null;
|
||||||
|
|
||||||
@@ -65,6 +65,7 @@ const actionHandlers: Record<ApiActionTag, ActionHandler> = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { entries } = getCurrentRundown();
|
const { entries } = getCurrentRundown();
|
||||||
|
const customFields = getProjectCustomFields();
|
||||||
|
|
||||||
const targetEntry = entries[id];
|
const targetEntry = entries[id];
|
||||||
if (!targetEntry) {
|
if (!targetEntry) {
|
||||||
@@ -77,7 +78,7 @@ const actionHandlers: Record<ApiActionTag, ActionHandler> = {
|
|||||||
let shouldThrottle = false;
|
let shouldThrottle = false;
|
||||||
|
|
||||||
Object.entries(data).forEach(([property, value]) => {
|
Object.entries(data).forEach(([property, value]) => {
|
||||||
if (!isValidChangeProperty(targetEntry, property, value)) {
|
if (!isValidChangeProperty(targetEntry, property, value, customFields)) {
|
||||||
throw new Error('Invalid property or value');
|
throw new Error('Invalid property or value');
|
||||||
}
|
}
|
||||||
const newObjectProperty = parseProperty(property, value);
|
const newObjectProperty = parseProperty(property, value);
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import { EndAction, OntimeEntry, TimeStrategy, TimerType, isKeyOfType } from 'ontime-types';
|
import { CustomFields, EndAction, OntimeEntry, TimeStrategy, TimerType, isKeyOfType } from 'ontime-types';
|
||||||
import { maxDuration } from 'ontime-utils';
|
import { maxDuration } from 'ontime-utils';
|
||||||
|
|
||||||
import { coerceBoolean, coerceColour, coerceEnum, coerceNumber, coerceString } from '../utils/coerceType.js';
|
import { coerceBoolean, coerceColour, coerceEnum, coerceNumber, coerceString } from '../utils/coerceType.js';
|
||||||
import { getDataProvider } from '../classes/data-provider/DataProvider.js';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@@ -44,13 +43,14 @@ const propertyConversion: Record<string, (value: unknown) => unknown> = {
|
|||||||
timeEnd: (value: unknown) => clampDuration(coerceNumber(value)),
|
timeEnd: (value: unknown) => clampDuration(coerceNumber(value)),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* coerces a property of an Ontime Entry
|
||||||
|
* @throws if the value does not conform to the expected type
|
||||||
|
*/
|
||||||
export function parseProperty(property: string, value: unknown) {
|
export function parseProperty(property: string, value: unknown) {
|
||||||
if (property.startsWith('custom:')) {
|
if (property.startsWith('custom:')) {
|
||||||
|
// custom fields have been validated when used here
|
||||||
const customKey = property.split(':')[1];
|
const customKey = property.split(':')[1];
|
||||||
const customFields = getDataProvider().getCustomFields();
|
|
||||||
if (!(customKey in customFields)) {
|
|
||||||
throw new Error(`Custom field ${customKey} not found`);
|
|
||||||
}
|
|
||||||
const parserFn = propertyConversion.custom;
|
const parserFn = propertyConversion.custom;
|
||||||
return { custom: { [customKey]: parserFn(value) } };
|
return { custom: { [customKey]: parserFn(value) } };
|
||||||
}
|
}
|
||||||
@@ -61,13 +61,21 @@ export function parseProperty(property: string, value: unknown) {
|
|||||||
return { [property]: parserFn(value) };
|
return { [property]: parserFn(value) };
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isValidChangeProperty(target: OntimeEntry, property: string, value: unknown): boolean {
|
/**
|
||||||
|
* Checks whether a valid property - value pair are applicable to an entry
|
||||||
|
*/
|
||||||
|
export function isValidChangeProperty(
|
||||||
|
target: OntimeEntry,
|
||||||
|
property: string,
|
||||||
|
value: unknown,
|
||||||
|
customFields: CustomFields,
|
||||||
|
): boolean {
|
||||||
if (typeof property !== 'string') return false;
|
if (typeof property !== 'string') return false;
|
||||||
if (value === undefined) return false;
|
if (value === undefined) return false;
|
||||||
if (property.startsWith('custom:') && 'custom' in target) {
|
if (property.startsWith('custom:') && Object.hasOwn(target, 'custom')) {
|
||||||
const customProperty = property.slice('custom:'.length);
|
const customProperty = property.slice('custom:'.length);
|
||||||
if (!customProperty) return false;
|
if (!customProperty) return false;
|
||||||
return Object.hasOwn(target.custom, customProperty);
|
return Object.hasOwn(customFields, customProperty);
|
||||||
}
|
}
|
||||||
return Object.hasOwn(target, property);
|
return Object.hasOwn(target, property);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user