mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-21 06:59:09 +00:00
refactor: remove project CSV export option
This commit is contained in:
committed by
Alex Christoffer Rasmussen
parent
ccf1ddf2fd
commit
846eaf569d
@@ -1,9 +1,6 @@
|
|||||||
import axios, { AxiosResponse } from 'axios';
|
import axios, { AxiosResponse } from 'axios';
|
||||||
import { DatabaseModel, MessageResponse, ProjectData, ProjectFileListResponse, QuickStartData } from 'ontime-types';
|
import { DatabaseModel, MessageResponse, ProjectData, ProjectFileListResponse, QuickStartData } from 'ontime-types';
|
||||||
|
|
||||||
import { makeTable } from '../../views/cuesheet/cuesheet.utils';
|
|
||||||
import { aggregateRundowns, makeCSVFromArrayOfArrays } from '../utils/csv';
|
|
||||||
|
|
||||||
import { apiEntryUrl } from './constants';
|
import { apiEntryUrl } from './constants';
|
||||||
import { createBlob, downloadBlob } from './utils';
|
import { createBlob, downloadBlob } from './utils';
|
||||||
|
|
||||||
@@ -33,27 +30,6 @@ export async function downloadProject(fileName: string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Request download of the current rundown as a CSV file
|
|
||||||
* @param fileName
|
|
||||||
*/
|
|
||||||
export async function downloadCSV(fileName: string = 'rundown') {
|
|
||||||
try {
|
|
||||||
const { data, name } = await fileDownload(fileName);
|
|
||||||
const { project, rundowns, customFields } = data;
|
|
||||||
|
|
||||||
const flatRundowns = aggregateRundowns(rundowns);
|
|
||||||
const sheetData = makeTable(project, flatRundowns, customFields);
|
|
||||||
|
|
||||||
const fileContent = makeCSVFromArrayOfArrays(sheetData);
|
|
||||||
|
|
||||||
const blob = createBlob(fileContent, 'text/csv;charset=utf-8;');
|
|
||||||
downloadBlob(blob, `${name}.csv`);
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* HTTP request to upload project file
|
* HTTP request to upload project file
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
import { OntimeEntry, ProjectRundowns, Rundown } from 'ontime-types';
|
import { makeCSVFromArrayOfArrays } from '../csv';
|
||||||
|
|
||||||
import { aggregateRundowns, makeCSVFromArrayOfArrays } from '../csv';
|
|
||||||
|
|
||||||
describe('makeCSVFromArrayOfArrays()', () => {
|
describe('makeCSVFromArrayOfArrays()', () => {
|
||||||
it('joins an array of arrays with commas and newlines', () => {
|
it('joins an array of arrays with commas and newlines', () => {
|
||||||
@@ -13,34 +11,3 @@ after newline,after comma
|
|||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('aggregateRundowns()', () => {
|
|
||||||
it('flattens an object of rundowns into a single array', () => {
|
|
||||||
const rundowns = {
|
|
||||||
first: {
|
|
||||||
id: '',
|
|
||||||
title: '',
|
|
||||||
revision: 0,
|
|
||||||
order: ['1', '2'],
|
|
||||||
flatOrder: ['1', '2'],
|
|
||||||
entries: {
|
|
||||||
'1': { id: '1' } as OntimeEntry,
|
|
||||||
'2': { id: '2' } as OntimeEntry,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
second: {
|
|
||||||
id: '',
|
|
||||||
title: '',
|
|
||||||
revision: 0,
|
|
||||||
order: ['3', '4'],
|
|
||||||
flatOrder: ['3', '4'],
|
|
||||||
entries: {
|
|
||||||
'3': { id: '3' } as OntimeEntry,
|
|
||||||
'4': { id: '4' } as OntimeEntry,
|
|
||||||
},
|
|
||||||
} as Rundown,
|
|
||||||
} as ProjectRundowns;
|
|
||||||
|
|
||||||
expect(aggregateRundowns(rundowns)).toStrictEqual([{ id: '1' }, { id: '2' }, { id: '3' }, { id: '4' }]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { stringify } from 'csv-stringify/browser/esm/sync';
|
import { stringify } from 'csv-stringify/browser/esm/sync';
|
||||||
import { OntimeEntry, ProjectRundowns } from 'ontime-types';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts an array of arrays to a CSV file
|
* Converts an array of arrays to a CSV file
|
||||||
@@ -7,25 +6,3 @@ import { OntimeEntry, ProjectRundowns } from 'ontime-types';
|
|||||||
export function makeCSVFromArrayOfArrays(arrayOfArrays: string[][]): string {
|
export function makeCSVFromArrayOfArrays(arrayOfArrays: string[][]): string {
|
||||||
return stringify(arrayOfArrays);
|
return stringify(arrayOfArrays);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Receives an object of rundowns, and flattens them into a single, linear rundown
|
|
||||||
* Used for CSV export
|
|
||||||
*/
|
|
||||||
export function aggregateRundowns(rundowns: ProjectRundowns): OntimeEntry[] {
|
|
||||||
const rundownKeys = Object.keys(rundowns);
|
|
||||||
if (rundownKeys.length === 0) return [];
|
|
||||||
const flatRundown: OntimeEntry[] = [];
|
|
||||||
|
|
||||||
for (const key of rundownKeys) {
|
|
||||||
const { order, entries } = rundowns[key];
|
|
||||||
|
|
||||||
for (let i = 0; i < order.length; i++) {
|
|
||||||
const entryId = order[i];
|
|
||||||
const entry = entries[entryId];
|
|
||||||
|
|
||||||
flatRundown.push(entry);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return flatRundown;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import {
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
deleteProject,
|
deleteProject,
|
||||||
downloadCSV,
|
|
||||||
downloadProject,
|
downloadProject,
|
||||||
duplicateProject,
|
duplicateProject,
|
||||||
loadProject,
|
loadProject,
|
||||||
@@ -187,10 +186,6 @@ function ActionMenu(props: ActionMenuProps) {
|
|||||||
await downloadProject(filename);
|
await downloadProject(filename);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleExportCSV = async () => {
|
|
||||||
await downloadCSV(filename);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DropdownMenu
|
<DropdownMenu
|
||||||
render={<IconButton variant='ghosted-white' />}
|
render={<IconButton variant='ghosted-white' />}
|
||||||
@@ -213,7 +208,6 @@ function ActionMenu(props: ActionMenuProps) {
|
|||||||
{ type: 'item', icon: IoPencilOutline, label: 'Rename', onClick: handleRename },
|
{ type: 'item', icon: IoPencilOutline, label: 'Rename', onClick: handleRename },
|
||||||
{ type: 'item', icon: IoCopyOutline, label: 'Duplicate', onClick: handleDuplicate },
|
{ type: 'item', icon: IoCopyOutline, label: 'Duplicate', onClick: handleDuplicate },
|
||||||
{ type: 'item', icon: IoDocumentOutline, label: 'Download', onClick: handleDownload },
|
{ type: 'item', icon: IoDocumentOutline, label: 'Download', onClick: handleDownload },
|
||||||
{ type: 'item', icon: IoDocumentOutline, label: 'Export CSV Rundown', onClick: handleExportCSV },
|
|
||||||
{ type: 'divider' },
|
{ type: 'divider' },
|
||||||
{ type: 'item', icon: IoTrash, label: 'Delete', onClick: () => onDelete(filename), disabled: current },
|
{ type: 'item', icon: IoTrash, label: 'Delete', onClick: () => onDelete(filename), disabled: current },
|
||||||
]}
|
]}
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
import { ProjectData } from 'ontime-types';
|
import { parseField } from '../cuesheet.utils';
|
||||||
|
|
||||||
import { makeTable, parseField } from '../cuesheet.utils';
|
|
||||||
|
|
||||||
describe('parseField()', () => {
|
describe('parseField()', () => {
|
||||||
it('returns a string from given millis on timeStart, TimeEnd and duration', () => {
|
it('returns a string from given millis on timeStart, TimeEnd and duration', () => {
|
||||||
@@ -32,69 +30,3 @@ describe('parseField()', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('makeTable()', () => {
|
|
||||||
it('returns array of arrays with given fields', () => {
|
|
||||||
const headerData = {
|
|
||||||
title: 'test title',
|
|
||||||
description: 'test description',
|
|
||||||
logo: 'test logo',
|
|
||||||
};
|
|
||||||
const tableData = [
|
|
||||||
{
|
|
||||||
title: 'test title 1',
|
|
||||||
timeStart: 0,
|
|
||||||
timeEnd: 0,
|
|
||||||
skip: true,
|
|
||||||
lighting: { value: 'test lighting' },
|
|
||||||
sound: { value: 'test sound' },
|
|
||||||
},
|
|
||||||
];
|
|
||||||
const customFields = {
|
|
||||||
lighting: { label: 'test' },
|
|
||||||
};
|
|
||||||
|
|
||||||
// @ts-expect-error -- testing user data with missing fields
|
|
||||||
const table = makeTable(headerData as ProjectData, tableData, customFields);
|
|
||||||
expect(table).not.toContain('test logo');
|
|
||||||
expect(table).toMatchInlineSnapshot(`
|
|
||||||
[
|
|
||||||
[
|
|
||||||
"Ontime · Rundown export",
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"Project title: test title",
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"Project description: test description",
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"Time Start",
|
|
||||||
"Time End",
|
|
||||||
"Duration",
|
|
||||||
"ID",
|
|
||||||
"Colour",
|
|
||||||
"Cue",
|
|
||||||
"Title",
|
|
||||||
"Note",
|
|
||||||
"Skip?",
|
|
||||||
"lighting",
|
|
||||||
"Type",
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"00:00:00",
|
|
||||||
"00:00:00",
|
|
||||||
"",
|
|
||||||
"",
|
|
||||||
"",
|
|
||||||
"",
|
|
||||||
"test title 1",
|
|
||||||
"",
|
|
||||||
"x",
|
|
||||||
"",
|
|
||||||
"",
|
|
||||||
],
|
|
||||||
]
|
|
||||||
`);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -1,12 +1,4 @@
|
|||||||
import {
|
import { CustomFields, MaybeNumber, OntimeEntryCommonKeys } from 'ontime-types';
|
||||||
CustomFields,
|
|
||||||
isOntimeDelay,
|
|
||||||
isOntimeEvent,
|
|
||||||
MaybeNumber,
|
|
||||||
OntimeEntry,
|
|
||||||
OntimeEntryCommonKeys,
|
|
||||||
ProjectData,
|
|
||||||
} from 'ontime-types';
|
|
||||||
import { millisToString } from 'ontime-utils';
|
import { millisToString } from 'ontime-utils';
|
||||||
|
|
||||||
type CsvHeaderKey = OntimeEntryCommonKeys | keyof CustomFields;
|
type CsvHeaderKey = OntimeEntryCommonKeys | keyof CustomFields;
|
||||||
@@ -29,72 +21,3 @@ export const parseField = (field: CsvHeaderKey, data: unknown): string => {
|
|||||||
|
|
||||||
return String(data ?? '');
|
return String(data ?? '');
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* @description Creates an array of arrays usable by xlsx for export
|
|
||||||
*/
|
|
||||||
export const makeTable = (headerData: ProjectData, rundown: OntimeEntry[], customFields: CustomFields): string[][] => {
|
|
||||||
// create metadata header row
|
|
||||||
const data = [['Ontime · Rundown export']];
|
|
||||||
if (headerData.title) data.push([`Project title: ${headerData.title}`]);
|
|
||||||
if (headerData.description) data.push([`Project description: ${headerData.description}`]);
|
|
||||||
|
|
||||||
const customFieldKeys = Object.keys(customFields).map((key) => `custom-${key}`);
|
|
||||||
const customFieldLabels = Object.keys(customFields);
|
|
||||||
|
|
||||||
// we chose not to expose internals of the application
|
|
||||||
const fieldOrder: CsvHeaderKey[] = [
|
|
||||||
'timeStart',
|
|
||||||
'timeEnd',
|
|
||||||
'duration',
|
|
||||||
'id',
|
|
||||||
'colour',
|
|
||||||
'cue',
|
|
||||||
'title',
|
|
||||||
'note',
|
|
||||||
'skip',
|
|
||||||
...customFieldKeys,
|
|
||||||
'type',
|
|
||||||
];
|
|
||||||
|
|
||||||
const fieldTitles = [
|
|
||||||
'Time Start',
|
|
||||||
'Time End',
|
|
||||||
'Duration',
|
|
||||||
'ID',
|
|
||||||
'Colour',
|
|
||||||
'Cue',
|
|
||||||
'Title',
|
|
||||||
'Note',
|
|
||||||
'Skip?',
|
|
||||||
...customFieldLabels,
|
|
||||||
'Type',
|
|
||||||
];
|
|
||||||
|
|
||||||
// add header row to data
|
|
||||||
data.push(fieldTitles);
|
|
||||||
rundown.forEach((entry) => {
|
|
||||||
if (isOntimeDelay(entry)) return;
|
|
||||||
|
|
||||||
const row: string[] = [];
|
|
||||||
fieldOrder.forEach((field) => {
|
|
||||||
if (isOntimeEvent(entry)) {
|
|
||||||
// for custom fields, we need to extract the value from the custom object
|
|
||||||
if (field.startsWith('custom-')) {
|
|
||||||
const fieldLabel = field.split('custom-')[1];
|
|
||||||
const value = entry.custom[fieldLabel];
|
|
||||||
row.push(parseField(fieldLabel, value));
|
|
||||||
} else {
|
|
||||||
// @ts-expect-error -- it is ok, we will just not have the data for other fields
|
|
||||||
row.push(parseField(field, entry[field]));
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// @ts-expect-error -- it is ok, we will just not have the data for other fields
|
|
||||||
row.push(parseField(field, entry[field]));
|
|
||||||
});
|
|
||||||
data.push(row);
|
|
||||||
});
|
|
||||||
|
|
||||||
return data;
|
|
||||||
};
|
|
||||||
|
|||||||
Reference in New Issue
Block a user