mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-20 06:29:07 +00:00
push to sheet
This commit is contained in:
@@ -265,3 +265,14 @@ export const postPreviewSheet = async (sheetId: string, worksheet: string, optio
|
|||||||
});
|
});
|
||||||
return response.data.data;
|
return response.data.data;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const postPushSheet = async (sheetId: string, worksheet: string, options?: ExcelImportMap) => {
|
||||||
|
const response = await axios.post(`${ontimeURL}/sheet-push`, {
|
||||||
|
sheetid: sheetId,
|
||||||
|
worksheet: worksheet,
|
||||||
|
options: options,
|
||||||
|
});
|
||||||
|
return response.data.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import {
|
|||||||
postPreviewSheet,
|
postPreviewSheet,
|
||||||
uploadSheetClientFile,
|
uploadSheetClientFile,
|
||||||
patchData,
|
patchData,
|
||||||
|
postPushSheet,
|
||||||
} from '../../../common/api/ontimeApi';
|
} from '../../../common/api/ontimeApi';
|
||||||
import { OntimeRundown, ProjectData, UserFields } from 'ontime-types';
|
import { OntimeRundown, ProjectData, UserFields } from 'ontime-types';
|
||||||
|
|
||||||
@@ -88,6 +89,12 @@ export default function SheetsModal(props: SheetsModalProps) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handlePushData = () => {
|
||||||
|
postPushSheet(sheetid.current?.value ?? '', worksheet.current?.value ?? '').then((data) => {
|
||||||
|
console.log(data);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const handleFinalise = async () => {
|
const handleFinalise = async () => {
|
||||||
// this step is currently only used for excel files, after preview
|
// this step is currently only used for excel files, after preview
|
||||||
if (rundown && userFields && project) {
|
if (rundown && userFields && project) {
|
||||||
@@ -181,6 +188,9 @@ export default function SheetsModal(props: SheetsModalProps) {
|
|||||||
<Button variant='ontime-filled' padding='0 2em' onClick={handlePullData}>
|
<Button variant='ontime-filled' padding='0 2em' onClick={handlePullData}>
|
||||||
Pull data
|
Pull data
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button variant='ontime-filled' padding='0 2em' onClick={handlePushData}>
|
||||||
|
Push data
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -405,6 +405,19 @@ export async function previewSheet(req, res) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* downloads and parses an sheet
|
||||||
|
* @returns parsed result
|
||||||
|
*/
|
||||||
|
export async function pushSheet(req, res) {
|
||||||
|
try {
|
||||||
|
const data = await Sheet.push(req.body.sheetid, req.body.worksheet);
|
||||||
|
res.status(200).send('ok');
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).send({ message: error.toString() });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* uploads Client secrets file
|
* uploads Client secrets file
|
||||||
* @returns parsed result
|
* @returns parsed result
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import {
|
|||||||
sheetAuthState,
|
sheetAuthState,
|
||||||
sheetClientFile,
|
sheetClientFile,
|
||||||
previewSheet,
|
previewSheet,
|
||||||
|
pushSheet,
|
||||||
} from '../controllers/ontimeController.js';
|
} from '../controllers/ontimeController.js';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -104,3 +105,6 @@ router.get('/sheet-authurl', sheetAuthUrl);
|
|||||||
|
|
||||||
// create route between controller and '/ontime/preview-sheet' endpoint
|
// create route between controller and '/ontime/preview-sheet' endpoint
|
||||||
router.post('/sheet-preview', validateSheetPreview, previewSheet);
|
router.post('/sheet-preview', validateSheetPreview, previewSheet);
|
||||||
|
|
||||||
|
// create route between controller and '/ontime/preview-sheet' endpoint
|
||||||
|
router.post('/sheet-push', pushSheet);
|
||||||
@@ -6,10 +6,11 @@ import { URL } from 'url';
|
|||||||
import { logger } from '../classes/Logger.js';
|
import { logger } from '../classes/Logger.js';
|
||||||
import { getAppDataPath } from '../setup.js';
|
import { getAppDataPath } from '../setup.js';
|
||||||
import { DatabaseModel, LogOrigin, OntimeRundownEntry, isOntimeEvent } from 'ontime-types';
|
import { DatabaseModel, LogOrigin, OntimeRundownEntry, isOntimeEvent } from 'ontime-types';
|
||||||
import { ExcelImportOptions, isExcelImportMap, defaultExcelImportMap } from 'ontime-utils';
|
import { ExcelImportOptions, isExcelImportMap, defaultExcelImportMap, millisToString } from 'ontime-utils';
|
||||||
import { parseExcel } from './parser.js';
|
import { parseExcel } from './parser.js';
|
||||||
import { parseProject, parseRundown, parseUserFields } from './parserFunctions.js';
|
import { parseProject, parseRundown, parseUserFields } from './parserFunctions.js';
|
||||||
import { ensureDirectory } from './fileManagement.js';
|
import { ensureDirectory } from './fileManagement.js';
|
||||||
|
import { DataProvider } from '../classes/data-provider/DataProvider.js';
|
||||||
|
|
||||||
type ResponseOK = {
|
type ResponseOK = {
|
||||||
data: Partial<DatabaseModel>;
|
data: Partial<DatabaseModel>;
|
||||||
@@ -47,6 +48,71 @@ class sheet {
|
|||||||
if (!isExcelImportMap(options)) {
|
if (!isExcelImportMap(options)) {
|
||||||
throw new Error('Got incorrect options to excel import', JSON.parse(options));
|
throw new Error('Got incorrect options to excel import', JSON.parse(options));
|
||||||
}
|
}
|
||||||
|
const rq = await sheets({ version: 'v4', auth: sheet.client }).spreadsheets.values.get({
|
||||||
|
spreadsheetId: sheetId,
|
||||||
|
valueRenderOption: 'FORMATTED_VALUE',
|
||||||
|
majorDimension: 'ROWS',
|
||||||
|
range: worksheet + '!A:Z', //FIXME: this is an abitrary range
|
||||||
|
});
|
||||||
|
if (rq.status === 200) {
|
||||||
|
const { projectMetadata, rundownMetadata } = parseExcel(rq.data.values, options);
|
||||||
|
const rundown = DataProvider.getRundown();
|
||||||
|
const titleRow = Object.values(rundownMetadata)[0]['row'];
|
||||||
|
|
||||||
|
const updateRundown = Array<sheets_v4.Schema$Request>();
|
||||||
|
|
||||||
|
// we can't delete the last unflozzen row so we create an empty one
|
||||||
|
updateRundown.push({
|
||||||
|
insertDimension: {
|
||||||
|
inheritFromBefore: false,
|
||||||
|
range: { dimension: 'ROWS', startIndex: titleRow + 1, endIndex: titleRow + 2, sheetId: 0 },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
//and delete the rest
|
||||||
|
updateRundown.push({
|
||||||
|
deleteDimension: { range: { dimension: 'ROWS', startIndex: titleRow + 2, sheetId: 0 } },
|
||||||
|
});
|
||||||
|
// insert the lenght of the rundown
|
||||||
|
updateRundown.push({
|
||||||
|
insertDimension: {
|
||||||
|
inheritFromBefore: false,
|
||||||
|
range: {
|
||||||
|
dimension: 'ROWS',
|
||||||
|
startIndex: titleRow + 1,
|
||||||
|
endIndex: titleRow + rundown.length,
|
||||||
|
sheetId: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
//TODO: this is a mess
|
||||||
|
const titleCol = Object.values(rundownMetadata)
|
||||||
|
// .filter(([key, value]) => value !== undefined)
|
||||||
|
.reduce((accumulator: number, val) => Math.min(accumulator, val['col']), Number.MAX_VALUE);
|
||||||
|
if (titleCol == Number.MAX_VALUE) {
|
||||||
|
throw new Error(`could not finde starting title column`);
|
||||||
|
}
|
||||||
|
//update the corespunding row with event data
|
||||||
|
rundown.forEach((entry, index) =>
|
||||||
|
updateRundown.push(this.cellRequenstFromEvent(entry, index, rundownMetadata, titleCol as number)),
|
||||||
|
);
|
||||||
|
const writeResponds = await sheets({ version: 'v4', auth: sheet.client }).spreadsheets.batchUpdate({
|
||||||
|
spreadsheetId: sheetId,
|
||||||
|
requestBody: {
|
||||||
|
includeSpreadsheetInResponse: false,
|
||||||
|
responseRanges: [worksheet + '!A:Z'], //FIXME:
|
||||||
|
requests: updateRundown,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (writeResponds.status == 200) {
|
||||||
|
logger.info(LogOrigin.Server, `Sheet write: ${writeResponds.statusText}`);
|
||||||
|
} else {
|
||||||
|
throw new Error(`Sheet write faild: ${writeResponds.statusText}`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new Error(`Sheet read faild: ${rq.statusText}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -149,51 +215,68 @@ class sheet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param index - the index of the event in Sheet dimentions
|
* @param index - the index of the event in rundown dimentions
|
||||||
*/
|
*/
|
||||||
private cellRequenstFromEvent(event: OntimeRundownEntry, index: number): sheets_v4.Schema$Request['updateCells'] {
|
private cellRequenstFromEvent(
|
||||||
const titelCol = 1
|
event: OntimeRundownEntry,
|
||||||
// index += titelRow;
|
index: number,
|
||||||
|
metadata,
|
||||||
|
titleCol: number,
|
||||||
|
): sheets_v4.Schema$Request {
|
||||||
let r: sheets_v4.Schema$CellData[] = [];
|
let r: sheets_v4.Schema$CellData[] = [];
|
||||||
Object.entries(this.metadata['rundown'])
|
const tmp = Object.entries(metadata)
|
||||||
.filter(([key, value]) => value !== undefined)
|
.filter(([key, value]) => value !== undefined)
|
||||||
.sort(([aKey, a], [bKey, b]) => a['col'] - b['col'])
|
.sort(([aKey, a], [bKey, b]) => a['col'] - b['col']);
|
||||||
.forEach(([key, value]) => {
|
|
||||||
if (isOntimeEvent(event)) {
|
tmp.forEach(([key, value], index, arr) => {
|
||||||
if (key === 'colour') {
|
if (index != 0) {
|
||||||
r.push({
|
if (arr[index - 1][1]['col'] + 1 < value['col']) {
|
||||||
userEnteredValue: { stringValue: event.colour },
|
console.log('missing', key, value['col'], arr[index - 1][1]['col']);
|
||||||
userEnteredFormat: { backgroundColor: hexToRgb(event?.colour ?? null) },
|
arr.splice(index, 0, ['blank', { col: arr[index - 1][1]['col'] + 1 }]);
|
||||||
});
|
|
||||||
} else if (typeof event[key] === 'number') {
|
|
||||||
r.push({
|
|
||||||
userEnteredValue: { stringValue: millisToString(event[key], true) },
|
|
||||||
});
|
|
||||||
} else if (typeof event[key] === 'string') {
|
|
||||||
r.push({
|
|
||||||
userEnteredValue: { stringValue: event[key] },
|
|
||||||
});
|
|
||||||
} else if (typeof event[key] === 'boolean') {
|
|
||||||
r.push({
|
|
||||||
userEnteredValue: { stringValue: event[key] ? 'x' : '' },
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
r.push({});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
});
|
||||||
|
console.log(tmp);
|
||||||
|
|
||||||
|
tmp.forEach(([key, value]) => {
|
||||||
|
if (isOntimeEvent(event)) {
|
||||||
|
if (key === 'blank') {
|
||||||
|
r.push({});
|
||||||
|
} else if (key === 'colour') {
|
||||||
|
r.push({
|
||||||
|
userEnteredValue: { stringValue: event.colour },
|
||||||
|
});
|
||||||
|
} else if (typeof event[key] === 'number') {
|
||||||
|
r.push({
|
||||||
|
userEnteredValue: { stringValue: millisToString(event[key], true) },
|
||||||
|
});
|
||||||
|
} else if (typeof event[key] === 'string') {
|
||||||
|
r.push({
|
||||||
|
userEnteredValue: { stringValue: event[key] },
|
||||||
|
});
|
||||||
|
} else if (typeof event[key] === 'boolean') {
|
||||||
|
r.push({
|
||||||
|
userEnteredValue: { stringValue: event[key] ? 'x' : '' },
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
r.push({});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
return {
|
return {
|
||||||
start: {
|
updateCells: {
|
||||||
sheetId: 0, //FIXME: get from smalest medata col
|
start: {
|
||||||
rowIndex: index,
|
sheetId: 0, //FIXME:
|
||||||
columnIndex: titelCol,
|
rowIndex: index + Object.values(metadata)[0]['row'] + 1,
|
||||||
},
|
columnIndex: titleCol,
|
||||||
fields: 'userEnteredValue,userEnteredFormat.backgroundColor',
|
|
||||||
rows: [
|
|
||||||
{
|
|
||||||
values: r,
|
|
||||||
},
|
},
|
||||||
],
|
fields: 'userEnteredValue',
|
||||||
|
rows: [
|
||||||
|
{
|
||||||
|
values: r,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user