From 846eaf569d93f14870cf46c2199dc4bbc5546090 Mon Sep 17 00:00:00 2001 From: Carlos Valente Date: Sun, 5 Oct 2025 07:31:43 +0200 Subject: [PATCH] refactor: remove project CSV export option --- apps/client/src/common/api/db.ts | 24 ------ .../src/common/utils/__tests__/csv.test.ts | 35 +------- apps/client/src/common/utils/csv.ts | 23 ------ .../panel/project-panel/ProjectListItem.tsx | 6 -- .../cuesheet/__tests__/cuesheet.utils.test.ts | 70 +--------------- .../src/views/cuesheet/cuesheet.utils.ts | 79 +------------------ 6 files changed, 3 insertions(+), 234 deletions(-) diff --git a/apps/client/src/common/api/db.ts b/apps/client/src/common/api/db.ts index 0a3092d0d..28f3b9b33 100644 --- a/apps/client/src/common/api/db.ts +++ b/apps/client/src/common/api/db.ts @@ -1,9 +1,6 @@ import axios, { AxiosResponse } from 'axios'; 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 { 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 */ diff --git a/apps/client/src/common/utils/__tests__/csv.test.ts b/apps/client/src/common/utils/__tests__/csv.test.ts index 2b5fbc562..13bd6b1b5 100644 --- a/apps/client/src/common/utils/__tests__/csv.test.ts +++ b/apps/client/src/common/utils/__tests__/csv.test.ts @@ -1,6 +1,4 @@ -import { OntimeEntry, ProjectRundowns, Rundown } from 'ontime-types'; - -import { aggregateRundowns, makeCSVFromArrayOfArrays } from '../csv'; +import { makeCSVFromArrayOfArrays } from '../csv'; describe('makeCSVFromArrayOfArrays()', () => { 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' }]); - }); -}); diff --git a/apps/client/src/common/utils/csv.ts b/apps/client/src/common/utils/csv.ts index 055c0c9d6..e24a7f0a3 100644 --- a/apps/client/src/common/utils/csv.ts +++ b/apps/client/src/common/utils/csv.ts @@ -1,5 +1,4 @@ import { stringify } from 'csv-stringify/browser/esm/sync'; -import { OntimeEntry, ProjectRundowns } from 'ontime-types'; /** * 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 { 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; -} diff --git a/apps/client/src/features/app-settings/panel/project-panel/ProjectListItem.tsx b/apps/client/src/features/app-settings/panel/project-panel/ProjectListItem.tsx index 4ebb35745..acc53133f 100644 --- a/apps/client/src/features/app-settings/panel/project-panel/ProjectListItem.tsx +++ b/apps/client/src/features/app-settings/panel/project-panel/ProjectListItem.tsx @@ -10,7 +10,6 @@ import { import { deleteProject, - downloadCSV, downloadProject, duplicateProject, loadProject, @@ -187,10 +186,6 @@ function ActionMenu(props: ActionMenuProps) { await downloadProject(filename); }; - const handleExportCSV = async () => { - await downloadCSV(filename); - }; - return ( } @@ -213,7 +208,6 @@ function ActionMenu(props: ActionMenuProps) { { type: 'item', icon: IoPencilOutline, label: 'Rename', onClick: handleRename }, { type: 'item', icon: IoCopyOutline, label: 'Duplicate', onClick: handleDuplicate }, { type: 'item', icon: IoDocumentOutline, label: 'Download', onClick: handleDownload }, - { type: 'item', icon: IoDocumentOutline, label: 'Export CSV Rundown', onClick: handleExportCSV }, { type: 'divider' }, { type: 'item', icon: IoTrash, label: 'Delete', onClick: () => onDelete(filename), disabled: current }, ]} diff --git a/apps/client/src/views/cuesheet/__tests__/cuesheet.utils.test.ts b/apps/client/src/views/cuesheet/__tests__/cuesheet.utils.test.ts index 83e0690a6..cd54fe9f5 100644 --- a/apps/client/src/views/cuesheet/__tests__/cuesheet.utils.test.ts +++ b/apps/client/src/views/cuesheet/__tests__/cuesheet.utils.test.ts @@ -1,6 +1,4 @@ -import { ProjectData } from 'ontime-types'; - -import { makeTable, parseField } from '../cuesheet.utils'; +import { parseField } from '../cuesheet.utils'; describe('parseField()', () => { 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", - "", - "", - ], - ] - `); - }); -}); diff --git a/apps/client/src/views/cuesheet/cuesheet.utils.ts b/apps/client/src/views/cuesheet/cuesheet.utils.ts index 993774c9f..b2b200021 100644 --- a/apps/client/src/views/cuesheet/cuesheet.utils.ts +++ b/apps/client/src/views/cuesheet/cuesheet.utils.ts @@ -1,12 +1,4 @@ -import { - CustomFields, - isOntimeDelay, - isOntimeEvent, - MaybeNumber, - OntimeEntry, - OntimeEntryCommonKeys, - ProjectData, -} from 'ontime-types'; +import { CustomFields, MaybeNumber, OntimeEntryCommonKeys } from 'ontime-types'; import { millisToString } from 'ontime-utils'; type CsvHeaderKey = OntimeEntryCommonKeys | keyof CustomFields; @@ -29,72 +21,3 @@ export const parseField = (field: CsvHeaderKey, data: unknown): string => { 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; -};