From 1af42b766d16f1fb99d768cc7096c325dc1f9817 Mon Sep 17 00:00:00 2001 From: arc-alex Date: Sat, 6 Sep 2025 12:52:20 +0200 Subject: [PATCH] fix: insert group-end where needed --- .../services/sheet-service/SheetService.ts | 38 +++++++++++++++++-- .../src/services/sheet-service/sheetUtils.ts | 8 +++- 2 files changed, 40 insertions(+), 6 deletions(-) diff --git a/apps/server/src/services/sheet-service/SheetService.ts b/apps/server/src/services/sheet-service/SheetService.ts index 5e6b4379b..2c0fa66d0 100644 --- a/apps/server/src/services/sheet-service/SheetService.ts +++ b/apps/server/src/services/sheet-service/SheetService.ts @@ -4,7 +4,18 @@ * @link https://developers.google.com/identity/protocols/oauth2/limited-input-device */ -import { AuthenticationStatus, CustomFields, DatabaseModel, LogOrigin, MaybeString, Rundown } from 'ontime-types'; +import { + AuthenticationStatus, + CustomFields, + DatabaseModel, + isOntimeEvent, + isOntimeMilestone, + LogOrigin, + MaybeString, + OntimeGroup, + Rundown, + SupportedEntry, +} from 'ontime-types'; import { ImportMap, getErrorMessage } from 'ontime-utils'; import { sheets, type sheets_v4 } from '@googleapis/sheets'; @@ -348,6 +359,21 @@ export async function upload(sheetId: string, options: ImportMap) { const { sheetMetadata } = parseExcel(readResponse.data.values, getProjectCustomFields(), 'not-used', options); const rundown = getCurrentRundown(); + const sheetOrder: string[] = []; + let prevGroup: string | null = null; + for (const id of rundown.flatOrder) { + const entry = rundown.entries[id]; + + if (isOntimeEvent(entry) || isOntimeMilestone(entry)) { + if (prevGroup && entry.parent === null) { + // if we were in a group and are now not insert a group end + sheetOrder.push(`group-end-${prevGroup}`); + } + prevGroup = entry.parent; + } + sheetOrder.push(entry.id); + } + const titleMetadata = Object.values(sheetMetadata)[0]; if (titleMetadata === undefined) { throw new Error(`Sheet read failed: failed to find title row`); @@ -380,15 +406,19 @@ export async function upload(sheetId: string, options: ImportMap) { range: { dimension: 'ROWS', startIndex: titleRow + 1, - endIndex: titleRow + rundown.order.length, + endIndex: titleRow + sheetOrder.length, sheetId: worksheetId, }, }, }); // update the corresponding row with event data - rundown.order.forEach((entryId, index) => { - const entry = rundown.entries[entryId]; + sheetOrder.forEach((entryId, index) => { + const isGroupEnd = entryId.startsWith('group-end-'); + const id = isGroupEnd ? entryId.split('group-end-')[1] : entryId; + const entry = isGroupEnd + ? ({ id: entryId, type: SupportedEntry.Group } as OntimeGroup) + : structuredClone(rundown.entries[id]); updateRundown.push(cellRequestFromEvent(entry, index, worksheetId, sheetMetadata)); }); diff --git a/apps/server/src/services/sheet-service/sheetUtils.ts b/apps/server/src/services/sheet-service/sheetUtils.ts index 9d5de2a4d..45df0b462 100644 --- a/apps/server/src/services/sheet-service/sheetUtils.ts +++ b/apps/server/src/services/sheet-service/sheetUtils.ts @@ -91,7 +91,10 @@ export function cellRequestFromEvent( ): sheets_v4.Schema$Request { const rowData = Object.entries(metadata) // check what headings are available in the sheet .filter(([_, value]) => value !== undefined) // drop anything that is undefined - .sort(([_a, a], [_b, b]) => a['col'] - b['col']) as [keyof OntimeEntry | 'blank', { col: number; row: number }][]; // sort the array by the column index + .sort(([_a, a], [_b, b]) => a['col'] - b['col']) as [ + OntimeEntryCommonKeys | 'blank', + { col: number; row: number }, + ][]; // sort the array by the column index // inset blank data is there is spacing between relevant ontime columns for (const [index, e] of rowData.entries()) { @@ -159,7 +162,8 @@ function getCellData(key: OntimeEntryCommonKeys | 'blank', entry: OntimeEntry) { // we need to remap the event type to timer type in the case of groups and milestones if (key === 'timerType') { - if (isOntimeGroup(entry)) return { userEnteredValue: { stringValue: 'group' } }; + if (isOntimeGroup(entry)) + return { userEnteredValue: { stringValue: entry.id.startsWith('group-end') ? 'group-end' : 'group' } }; if (isOntimeMilestone(entry)) return { userEnteredValue: { stringValue: 'milestone' } }; return { userEnteredValue: { stringValue: entry.timerType } }; }