mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-21 23:19:09 +00:00
fix: customFields was not persisted on import and force Custom Field keys to be lower case (#849)
* refactor: force Custom Field keys to be lower case * fix: customFields was not persisted on import
This commit is contained in:
committed by
GitHub
parent
df8b76305e
commit
a86496ed27
+4
-5
@@ -11,6 +11,7 @@ import CustomFieldForm from './CustomFieldForm';
|
|||||||
import style from './ProjectSettingsPanel.module.scss';
|
import style from './ProjectSettingsPanel.module.scss';
|
||||||
|
|
||||||
interface CustomFieldEntryProps {
|
interface CustomFieldEntryProps {
|
||||||
|
field: string;
|
||||||
colour: string;
|
colour: string;
|
||||||
label: string;
|
label: string;
|
||||||
onEdit: (label: CustomFieldLabel, patch: CustomField) => Promise<void>;
|
onEdit: (label: CustomFieldLabel, patch: CustomField) => Promise<void>;
|
||||||
@@ -18,13 +19,11 @@ interface CustomFieldEntryProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function CustomFieldEntry(props: CustomFieldEntryProps) {
|
export default function CustomFieldEntry(props: CustomFieldEntryProps) {
|
||||||
const { colour, label, onEdit, onDelete } = props;
|
const { colour, label, onEdit, onDelete, field } = props;
|
||||||
|
|
||||||
const [isEditing, setIsEditing] = useState(false);
|
const [isEditing, setIsEditing] = useState(false);
|
||||||
|
|
||||||
const handleEdit = async (patch: CustomField) => {
|
const handleEdit = async (patch: CustomField) => {
|
||||||
const oldLabel = label;
|
await onEdit(field, patch);
|
||||||
await onEdit(oldLabel, patch);
|
|
||||||
setIsEditing(false);
|
setIsEditing(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -64,7 +63,7 @@ export default function CustomFieldEntry(props: CustomFieldEntryProps) {
|
|||||||
color='#FA5656' // $red-500
|
color='#FA5656' // $red-500
|
||||||
icon={<IoTrash />}
|
icon={<IoTrash />}
|
||||||
aria-label='Delete entry'
|
aria-label='Delete entry'
|
||||||
onClick={() => onDelete(label)}
|
onClick={() => onDelete(field)}
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
+1
@@ -84,6 +84,7 @@ export default function ProjectSettingsPanel() {
|
|||||||
return (
|
return (
|
||||||
<CustomFieldEntry
|
<CustomFieldEntry
|
||||||
key={key}
|
key={key}
|
||||||
|
field={key}
|
||||||
colour={colour}
|
colour={colour}
|
||||||
label={label}
|
label={label}
|
||||||
onEdit={handleEditField}
|
onEdit={handleEditField}
|
||||||
|
|||||||
@@ -841,35 +841,35 @@ describe('custom fields', () => {
|
|||||||
it('creates a field from given parameters', async () => {
|
it('creates a field from given parameters', async () => {
|
||||||
const expected = {
|
const expected = {
|
||||||
lighting: {
|
lighting: {
|
||||||
label: 'lighting',
|
label: 'Lighting',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
colour: 'blue',
|
colour: 'blue',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const customField = await createCustomField({ label: 'lighting', type: 'string', colour: 'blue' });
|
const customField = await createCustomField({ label: 'Lighting', type: 'string', colour: 'blue' });
|
||||||
expect(customField).toStrictEqual(expected);
|
expect(customField).toStrictEqual(expected);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('editCustomField()', () => {
|
describe('editCustomField()', () => {
|
||||||
it('edits a field with a given label', async () => {
|
it('edits a field with a given label', async () => {
|
||||||
await createCustomField({ label: 'sound', type: 'string', colour: 'blue' });
|
await createCustomField({ label: 'Sound', type: 'string', colour: 'blue' });
|
||||||
|
|
||||||
const expected = {
|
const expected = {
|
||||||
lighting: {
|
lighting: {
|
||||||
label: 'lighting',
|
label: 'Lighting',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
colour: 'blue',
|
colour: 'blue',
|
||||||
},
|
},
|
||||||
sound: {
|
sound: {
|
||||||
label: 'sound',
|
label: 'Sound',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
colour: 'blue',
|
colour: 'green',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const customField = await editCustomField('sound', { label: 'sound', type: 'string', colour: 'blue' });
|
const customField = await editCustomField('sound', { label: 'Sound', type: 'string', colour: 'green' });
|
||||||
|
|
||||||
expect(customField).toStrictEqual(expected);
|
expect(customField).toStrictEqual(expected);
|
||||||
});
|
});
|
||||||
@@ -879,7 +879,7 @@ describe('custom fields', () => {
|
|||||||
it('deletes a field with a given label', async () => {
|
it('deletes a field with a given label', async () => {
|
||||||
const expected = {
|
const expected = {
|
||||||
lighting: {
|
lighting: {
|
||||||
label: 'lighting',
|
label: 'Lighting',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
colour: 'blue',
|
colour: 'blue',
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ export async function init(initialRundown: Readonly<OntimeRundown>, customFields
|
|||||||
persistedCustomFields = structuredClone(customFields);
|
persistedCustomFields = structuredClone(customFields);
|
||||||
generate();
|
generate();
|
||||||
await DataProvider.setRundown(persistedRundown);
|
await DataProvider.setRundown(persistedRundown);
|
||||||
|
await DataProvider.setCustomFields(customFields);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -428,16 +429,16 @@ function scheduleCustomFieldPersist(persistedCustomFields: CustomFields) {
|
|||||||
*/
|
*/
|
||||||
export const createCustomField = async (field: CustomField) => {
|
export const createCustomField = async (field: CustomField) => {
|
||||||
const { label, type, colour } = field;
|
const { label, type, colour } = field;
|
||||||
|
const key = label.toLowerCase();
|
||||||
// check if label already exists
|
// check if label already exists
|
||||||
const alreadyExists = Object.hasOwn(persistedCustomFields, label);
|
const alreadyExists = Object.hasOwn(persistedCustomFields, key);
|
||||||
|
|
||||||
if (alreadyExists) {
|
if (alreadyExists) {
|
||||||
throw new Error('Label already exists');
|
throw new Error('Label already exists');
|
||||||
}
|
}
|
||||||
|
|
||||||
// update object and persist
|
// update object and persist
|
||||||
persistedCustomFields[label] = { label, type, colour };
|
persistedCustomFields[key] = { label, type, colour };
|
||||||
|
|
||||||
scheduleCustomFieldPersist(persistedCustomFields);
|
scheduleCustomFieldPersist(persistedCustomFields);
|
||||||
|
|
||||||
@@ -446,29 +447,30 @@ export const createCustomField = async (field: CustomField) => {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Edits an existing custom field in the database
|
* Edits an existing custom field in the database
|
||||||
* @param label
|
* @param key
|
||||||
* @param newField
|
* @param newField
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const editCustomField = async (label: string, newField: Partial<CustomField>) => {
|
export const editCustomField = async (key: string, newField: Partial<CustomField>) => {
|
||||||
if (!(label in persistedCustomFields)) {
|
if (!(key in persistedCustomFields)) {
|
||||||
throw new Error('Could not find label');
|
throw new Error('Could not find label');
|
||||||
}
|
}
|
||||||
|
|
||||||
const existingField = persistedCustomFields[label];
|
const existingField = persistedCustomFields[key];
|
||||||
if (existingField.type !== newField.type) {
|
if (existingField.type !== newField.type) {
|
||||||
throw new Error('Change of field type is not allowed');
|
throw new Error('Change of field type is not allowed');
|
||||||
}
|
}
|
||||||
|
|
||||||
persistedCustomFields[newField.label] = { ...existingField, ...newField };
|
const newKey = newField.label.toLowerCase();
|
||||||
|
persistedCustomFields[newKey] = { ...existingField, ...newField };
|
||||||
|
|
||||||
if (existingField.label !== newField.label) {
|
if (key !== newKey) {
|
||||||
delete persistedCustomFields[existingField.label];
|
delete persistedCustomFields[key];
|
||||||
customFieldChangelog[label] = newField.label;
|
customFieldChangelog[key] = newKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
scheduleCustomFieldPersist(persistedCustomFields);
|
scheduleCustomFieldPersist(persistedCustomFields);
|
||||||
invalidateIfUsed(label);
|
invalidateIfUsed(key);
|
||||||
|
|
||||||
return persistedCustomFields;
|
return persistedCustomFields;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -796,7 +796,7 @@ describe('parseExcel()', () => {
|
|||||||
'Skip',
|
'Skip',
|
||||||
'Notes',
|
'Notes',
|
||||||
't0',
|
't0',
|
||||||
'UpperCaseFromSheet',
|
'Test1',
|
||||||
'test2',
|
'test2',
|
||||||
'test3',
|
'test3',
|
||||||
'test4',
|
'test4',
|
||||||
@@ -859,8 +859,8 @@ describe('parseExcel()', () => {
|
|||||||
const importMap = {
|
const importMap = {
|
||||||
custom: {
|
custom: {
|
||||||
user0: 't0',
|
user0: 't0',
|
||||||
user1: 'UpperCaseFromSheet',
|
User1: 'Test1',
|
||||||
UpperCaseFromOntime: 'test2',
|
user2: 'test2',
|
||||||
user3: 'test3',
|
user3: 'test3',
|
||||||
user4: 'test4',
|
user4: 'test4',
|
||||||
user5: 'test5',
|
user5: 'test5',
|
||||||
@@ -885,7 +885,7 @@ describe('parseExcel()', () => {
|
|||||||
custom: {
|
custom: {
|
||||||
user0: { value: 'a0' },
|
user0: { value: 'a0' },
|
||||||
user1: { value: 'a1' },
|
user1: { value: 'a1' },
|
||||||
UpperCaseFromOntime: { value: 'a2' },
|
user2: { value: 'a2' },
|
||||||
user3: { value: 'a3' },
|
user3: { value: 'a3' },
|
||||||
user4: { value: 'a4' },
|
user4: { value: 'a4' },
|
||||||
user5: { value: 'a5' },
|
user5: { value: 'a5' },
|
||||||
@@ -927,12 +927,12 @@ describe('parseExcel()', () => {
|
|||||||
user1: {
|
user1: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
colour: '',
|
colour: '',
|
||||||
label: 'user1',
|
label: 'User1',
|
||||||
},
|
},
|
||||||
UpperCaseFromOntime: {
|
user2: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
colour: '',
|
colour: '',
|
||||||
label: 'UpperCaseFromOntime',
|
label: 'user2',
|
||||||
},
|
},
|
||||||
user3: {
|
user3: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
|
|||||||
@@ -49,15 +49,15 @@ export function getCustomFieldData(importMap: ImportMap): {
|
|||||||
} {
|
} {
|
||||||
const customFields = {};
|
const customFields = {};
|
||||||
const customFieldImportKeys = {};
|
const customFieldImportKeys = {};
|
||||||
for (const key in importMap.custom) {
|
for (const ontimeLabel in importMap.custom) {
|
||||||
const ontimeName = key;
|
const ontimeKey = ontimeLabel.toLowerCase();
|
||||||
const importName = importMap.custom[key];
|
const importLabel = importMap.custom[ontimeLabel].toLowerCase();
|
||||||
customFields[ontimeName] = {
|
customFields[ontimeKey] = {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
colour: '',
|
colour: '',
|
||||||
label: ontimeName,
|
label: ontimeLabel,
|
||||||
};
|
};
|
||||||
customFieldImportKeys[importName] = ontimeName;
|
customFieldImportKeys[importLabel] = ontimeKey;
|
||||||
}
|
}
|
||||||
return { customFields, customFieldImportKeys };
|
return { customFields, customFieldImportKeys };
|
||||||
}
|
}
|
||||||
@@ -230,8 +230,8 @@ export const parseExcel = (excelData: unknown[][], options?: Partial<ImportMap>)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// check if it is a custom field
|
// check if it is a custom field
|
||||||
if (column in customFieldImportKeys) {
|
if (columnText in customFieldImportKeys) {
|
||||||
handlers.custom(rowIndex, j, column);
|
handlers.custom(rowIndex, j, columnText);
|
||||||
}
|
}
|
||||||
|
|
||||||
// else. we don't know how to handle this column
|
// else. we don't know how to handle this column
|
||||||
|
|||||||
Reference in New Issue
Block a user