mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-08 08:53:51 +00:00
refactor: improve sheet import UX
This commit is contained in:
committed by
Carlos Valente
parent
42dcf35f45
commit
f53cb687bf
@@ -27,6 +27,9 @@ import {
|
||||
upload,
|
||||
} from './sheets.service.js';
|
||||
|
||||
/**
|
||||
* Starts the Google device authorization flow for the provided sheet.
|
||||
*/
|
||||
export async function requestConnection(
|
||||
req: Request,
|
||||
res: Response<{ verification_url: string; user_code: string } | ErrorResponse>,
|
||||
@@ -50,6 +53,9 @@ export async function requestConnection(
|
||||
await deleteFile(filePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current Google Sheets authentication status for this server session.
|
||||
*/
|
||||
export async function verifyAuthentication(
|
||||
_req: Request,
|
||||
res: Response<{ authenticated: AuthenticationStatus } | ErrorResponse>,
|
||||
@@ -63,6 +69,9 @@ export async function verifyAuthentication(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the current Google Sheets authentication session.
|
||||
*/
|
||||
export async function revokeAuthentication(
|
||||
_req: Request,
|
||||
res: Response<{ authenticated: AuthenticationStatus } | ErrorResponse>,
|
||||
@@ -76,7 +85,10 @@ export async function revokeAuthentication(
|
||||
}
|
||||
}
|
||||
|
||||
export async function getWorksheetNamesFromSheet(
|
||||
/**
|
||||
* Lists worksheet titles. Metadata is loaded lazily for the selected worksheet.
|
||||
*/
|
||||
export async function getWorksheetOptionsFromSheet(
|
||||
req: Request,
|
||||
res: Response<SpreadsheetWorksheetOptions | ErrorResponse>,
|
||||
) {
|
||||
@@ -90,7 +102,13 @@ export async function getWorksheetNamesFromSheet(
|
||||
}
|
||||
}
|
||||
|
||||
export async function getWorksheetMetadataFromSheet(req: Request, res: Response<SpreadsheetWorksheetMetadata | ErrorResponse>) {
|
||||
/**
|
||||
* Returns derived metadata for a single worksheet by inspecting its row data.
|
||||
*/
|
||||
export async function getWorksheetMetadataFromSheet(
|
||||
req: Request,
|
||||
res: Response<SpreadsheetWorksheetMetadata | ErrorResponse>,
|
||||
) {
|
||||
try {
|
||||
const { sheetId } = req.params;
|
||||
const { worksheet } = req.body;
|
||||
@@ -102,10 +120,10 @@ export async function getWorksheetMetadataFromSheet(req: Request, res: Response<
|
||||
}
|
||||
}
|
||||
|
||||
export async function readFromSheet(
|
||||
req: Request,
|
||||
res: Response<SpreadsheetPreviewResponse | ErrorResponse>,
|
||||
) {
|
||||
/**
|
||||
* Reads a Google Sheet worksheet and converts it into a rundown preview.
|
||||
*/
|
||||
export async function readFromSheet(req: Request, res: Response<SpreadsheetPreviewResponse | ErrorResponse>) {
|
||||
try {
|
||||
const { sheetId } = req.params;
|
||||
const { options } = req.body;
|
||||
@@ -117,6 +135,9 @@ export async function readFromSheet(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the current rundown back to the selected Google Sheet worksheet.
|
||||
*/
|
||||
export async function writeToSheet(req: Request, res: Response<void | ErrorResponse>) {
|
||||
try {
|
||||
const { sheetId } = req.params;
|
||||
|
||||
@@ -6,7 +6,7 @@ import express from 'express';
|
||||
|
||||
import {
|
||||
getWorksheetMetadataFromSheet,
|
||||
getWorksheetNamesFromSheet,
|
||||
getWorksheetOptionsFromSheet,
|
||||
readFromSheet,
|
||||
requestConnection,
|
||||
revokeAuthentication,
|
||||
@@ -28,7 +28,7 @@ router.post('/:sheetId/connect', uploadClientSecret, validateRequestConnection,
|
||||
|
||||
router.post('/revoke', revokeAuthentication);
|
||||
|
||||
router.post('/:sheetId/worksheets', validateSheetId, getWorksheetNamesFromSheet);
|
||||
router.post('/:sheetId/worksheet-options', validateSheetId, getWorksheetOptionsFromSheet);
|
||||
router.post('/:sheetId/metadata', validateWorksheetMetadata, getWorksheetMetadataFromSheet);
|
||||
|
||||
router.post('/:sheetId/read', validateSheetOptions, readFromSheet);
|
||||
|
||||
@@ -20,14 +20,13 @@ import {
|
||||
isOntimeEvent,
|
||||
isOntimeMilestone,
|
||||
} from 'ontime-types';
|
||||
import type { SpreadsheetWorksheetMetadata } from 'ontime-types';
|
||||
import { ImportMap, getErrorMessage } from 'ontime-utils';
|
||||
|
||||
import { logger } from '../../classes/Logger.js';
|
||||
import { consoleSubdued } from '../../utils/console.js';
|
||||
import { parseCustomFields } from '../custom-fields/customFields.parser.js';
|
||||
import { parseExcel } from '../excel/excel.parser.js';
|
||||
import type { SpreadsheetWorksheetMetadata } from 'ontime-types';
|
||||
|
||||
import { getWorksheetMetadataFromRows } from '../excel/spreadsheetMetadata.utils.js';
|
||||
import { getCurrentRundown, getProjectCustomFields, processRundown } from '../rundown/rundown.dao.js';
|
||||
import { parseRundowns } from '../rundown/rundown.parser.js';
|
||||
@@ -224,10 +223,10 @@ export function hasAuth(): { authenticated: AuthenticationStatus; sheetId: strin
|
||||
return { authenticated: currentAuthClient ? 'authenticated' : 'not_authenticated', sheetId: currentSheetId };
|
||||
}
|
||||
|
||||
async function verifySheet(
|
||||
sheetId = currentSheetId,
|
||||
authClient = currentAuthClient,
|
||||
): Promise<string[]> {
|
||||
/**
|
||||
* Validates that a spreadsheet exists and returns its worksheet titles without reading cell data.
|
||||
*/
|
||||
async function verifySheet(sheetId = currentSheetId, authClient = currentAuthClient): Promise<string[]> {
|
||||
if (!sheetId || !authClient) {
|
||||
throw new Error('Missing sheet ID or authentication');
|
||||
}
|
||||
@@ -284,8 +283,7 @@ export async function handleInitialConnection(
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow calling verification for sheetId
|
||||
* @returns
|
||||
* Returns the available worksheet titles. Metadata is loaded lazily per worksheet.
|
||||
*/
|
||||
export async function getWorksheetOptions(
|
||||
sheetId: string,
|
||||
@@ -296,29 +294,16 @@ export async function getWorksheetOptions(
|
||||
currentSheetId = sheetId;
|
||||
|
||||
const worksheets = await verifySheet(sheetId);
|
||||
const metadata = await getInitialWorksheetMetadata(sheetId, worksheets);
|
||||
|
||||
return {
|
||||
worksheets,
|
||||
metadata,
|
||||
metadata: null,
|
||||
};
|
||||
}
|
||||
|
||||
async function getInitialWorksheetMetadata(
|
||||
sheetId: string,
|
||||
worksheets: string[],
|
||||
): Promise<SpreadsheetWorksheetMetadata | null> {
|
||||
for (const worksheet of worksheets) {
|
||||
try {
|
||||
return await getWorksheetMetadata(sheetId, worksheet);
|
||||
} catch {
|
||||
// Continue looking for the first worksheet with usable headers.
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads worksheet rows from Google Sheets and derives import metadata from the detected header row.
|
||||
*/
|
||||
export async function getWorksheetMetadata(sheetId: string, worksheet: string) {
|
||||
if (!currentAuthClient) {
|
||||
throw new Error('Not authenticated');
|
||||
@@ -344,6 +329,9 @@ export async function getWorksheetMetadata(sheetId: string, worksheet: string) {
|
||||
return getWorksheetMetadataFromRows(worksheet, googleResponse.data.values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates that a worksheet exists and computes the A1 range needed to read its current grid.
|
||||
*/
|
||||
async function verifyWorksheet(sheetId: string, worksheet: string): Promise<{ worksheetId: number; range: string }> {
|
||||
if (!currentAuthClient) {
|
||||
throw new Error('Not authenticated');
|
||||
|
||||
Reference in New Issue
Block a user