sheet settings with feedback

This commit is contained in:
arc-alex
2023-11-23 17:17:13 +01:00
parent b15b4b48a7
commit 4f1fa2053f
8 changed files with 199 additions and 38 deletions
+30 -1
View File
@@ -3,6 +3,8 @@ import {
Alias, Alias,
DatabaseModel, DatabaseModel,
GetInfo, GetInfo,
GoogleSheet,
GoogleSheetState,
OntimeRundown, OntimeRundown,
OSCSettings, OSCSettings,
OscSubscription, OscSubscription,
@@ -239,7 +241,7 @@ export const uploadSheetClientFile = async (file: File) => {
.post(`${ontimeURL}/sheet-clientsecrect`, formData, { .post(`${ontimeURL}/sheet-clientsecrect`, formData, {
headers: { headers: {
'Content-Type': 'multipart/form-data', 'Content-Type': 'multipart/form-data',
} },
}) })
.then((response) => response.data.id); .then((response) => response.data.id);
}; };
@@ -273,3 +275,30 @@ export const postPushSheet = async (sheetId: string, worksheet: string, options?
}; };
/**
* @description HTTP request to retrieve google sheets settings
* @return {Promise}
*/
export async function getSheetSettings(): Promise<GoogleSheet> {
const res = await axios.get(`${ontimeURL}/sheet-settings`);
return res.data;
}
/**
* @description HTTP request to mutate google sheets settings
* @return {Promise}
*/
export async function postSheetSettings(data: GoogleSheet): Promise<GoogleSheet> {
const res = await axios.post(`${ontimeURL}/sheet-settings`, data);
return res.data;
}
/**
* @description HTTP request to retrieve google sheets state
* @return {Promise}
*/
export async function getSheetstate(): Promise<GoogleSheetState> {
const res = await axios.get(`${ontimeURL}/sheet-state`);
console.log(res.data)
return res.data;
}
@@ -9,9 +9,13 @@ import {
ModalFooter, ModalFooter,
ModalHeader, ModalHeader,
ModalOverlay, ModalOverlay,
useDisclosure,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import { IoCheckmarkCircleOutline } from '@react-icons/all-files/io5/IoCheckmarkCircleOutline';
import { IoCloseCircleOutline } from '@react-icons/all-files/io5/IoCloseCircleOutline';
import { useQueryClient } from '@tanstack/react-query'; import { useQueryClient } from '@tanstack/react-query';
import { OntimeRundown, ProjectData, UserFields } from 'ontime-types'; import { OntimeRundown, ProjectData, UserFields, GoogleSheetState } from 'ontime-types';
import { PROJECT_DATA, RUNDOWN, USERFIELDS } from '../../../common/api/apiConstants'; import { PROJECT_DATA, RUNDOWN, USERFIELDS } from '../../../common/api/apiConstants';
import { maybeAxiosError } from '../../../common/api/apiUtils'; import { maybeAxiosError } from '../../../common/api/apiUtils';
@@ -22,6 +26,9 @@ import {
postPreviewSheet, postPreviewSheet,
postPushSheet, postPushSheet,
uploadSheetClientFile, uploadSheetClientFile,
getSheetSettings,
postSheetSettings,
getSheetstate,
} from '../../../common/api/ontimeApi'; } from '../../../common/api/ontimeApi';
import { projectDataPlaceholder } from '../../../common/models/ProjectData'; import { projectDataPlaceholder } from '../../../common/models/ProjectData';
import { userFieldsPlaceholder } from '../../../common/models/UserFields'; import { userFieldsPlaceholder } from '../../../common/models/UserFields';
@@ -34,6 +41,7 @@ interface SheetsModalProps {
export default function SheetsModal(props: SheetsModalProps) { export default function SheetsModal(props: SheetsModalProps) {
const { isOpen, onClose } = props; const { isOpen, onClose } = props;
const fileInputRef = useRef<HTMLInputElement>(null); const fileInputRef = useRef<HTMLInputElement>(null);
const [authState, setAuthState] = useState<boolean>(true); const [authState, setAuthState] = useState<boolean>(true);
@@ -41,14 +49,18 @@ export default function SheetsModal(props: SheetsModalProps) {
const [userFields, setUserFields] = useState<UserFields | null>(null); const [userFields, setUserFields] = useState<UserFields | null>(null);
const [project, setProject] = useState<ProjectData | null>(null); const [project, setProject] = useState<ProjectData | null>(null);
const [sheetState, setSheetState] = useState<GoogleSheetState>({ auth: false, id: false, worksheet: false });
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const sheetid = useRef<HTMLInputElement>(null); const sheetid = useRef<HTMLInputElement>(null);
const worksheet = useRef<HTMLInputElement>(null); const worksheet = useRef<HTMLInputElement>(null);
const handleClose = () => { const handleClose = () => {
setRundown(null); setRundown(null);
setProject(null); setProject(null);
setUserFields(null); setUserFields(null);
// setSheetState({ auth: false, id: false, worksheet: false });
onClose(); onClose();
}; };
const handleClick = () => { const handleClick = () => {
@@ -64,19 +76,37 @@ export default function SheetsModal(props: SheetsModalProps) {
} }
}; };
//TODO: do smoething better here const _onChange = async () => {
getSheetsAuthStatus().then((data) => { setSheetState(await getSheetstate());
setAuthState(data); };
});
const handleAuthenticate = () => { if (isOpen) {
getSheetsAuthUrl().then((data) => { //TODO: how to get this on modal open
console.log(data); getSheetSettings().then((data) => {
if (data != 'bad') { if (sheetid.current?.value != data.id || worksheet.current?.value != data.worksheet) {
window.open(data, '_blank', 'noreferrer'); _onChange();
}
if (sheetid.current) {
sheetid.current.value = data.id;
}
if (worksheet.current) {
worksheet.current.value = data.worksheet;
}
});
}
const handelSave = () => {
postSheetSettings({ id: sheetid.current?.value ?? '', worksheet: worksheet.current?.value ?? '' }).then((data) => {
_onChange();
if (sheetid.current) {
sheetid.current.value = data.id;
}
if (worksheet.current) {
worksheet.current.value = data.worksheet;
} }
}); });
}; };
const handlePullData = () => { const handlePullData = () => {
postPreviewSheet(sheetid.current?.value ?? '', worksheet.current?.value ?? '').then((data) => { postPreviewSheet(sheetid.current?.value ?? '', worksheet.current?.value ?? '').then((data) => {
setProject(data.project); setProject(data.project);
@@ -163,11 +193,12 @@ export default function SheetsModal(props: SheetsModalProps) {
type='text' type='text'
ref={sheetid} ref={sheetid}
id='sheetid' id='sheetid'
width='440px' width='240px'
size='sm' size='sm'
textAlign='right' textAlign='right'
variant='ontime-filled-on-light' variant='ontime-filled-on-light'
/> />
{sheetState.id ? <IoCheckmarkCircleOutline /> : <IoCloseCircleOutline />}
<br /> <br />
<label htmlFor='worksheet'>Worksheet </label> <label htmlFor='worksheet'>Worksheet </label>
<Input <Input
@@ -179,6 +210,8 @@ export default function SheetsModal(props: SheetsModalProps) {
textAlign='right' textAlign='right'
variant='ontime-filled-on-light' variant='ontime-filled-on-light'
/> />
{sheetState.worksheet ? <IoCheckmarkCircleOutline /> : <IoCloseCircleOutline />}
</div> </div>
<div> <div>
<Button variant='ontime-filled' padding='0 2em' onClick={handlePullData}> <Button variant='ontime-filled' padding='0 2em' onClick={handlePullData}>
@@ -194,8 +227,8 @@ export default function SheetsModal(props: SheetsModalProps) {
<ModalFooter> <ModalFooter>
<Button variant='ontime-ghost-on-light'>Reset</Button> <Button variant='ontime-ghost-on-light'>Reset</Button>
{!rundown && ( {!rundown && (
<Button variant='ontime-filled' padding='0 2em' onClick={handleAuthenticate}> <Button variant='ontime-filled' padding='0 2em' onClick={handelSave}>
Authenticate Save
</Button> </Button>
)} )}
{rundown && ( {rundown && (
+55 -17
View File
@@ -394,6 +394,29 @@ export async function previewExcel(req, res) {
} }
} }
/**
* Meant to create a new project file, it will clear only fields which are specific to a project
* @param req
* @param res
*/
export const postNew: RequestHandler = async (req, res) => {
try {
const newProjectData: ProjectData = {
title: req.body?.title ?? '',
description: req.body?.description ?? '',
publicUrl: req.body?.publicUrl ?? '',
publicInfo: req.body?.publicInfo ?? '',
backstageUrl: req.body?.backstageUrl ?? '',
backstageInfo: req.body?.backstageInfo ?? '',
};
const newData = await DataProvider.setProjectData(newProjectData);
await deleteAllEvents();
res.status(201).send(newData);
} catch (error) {
res.status(400).send({ message: error.toString() });
}
};
/** /**
* downloads and parses an sheet * downloads and parses an sheet
* @returns parsed result * @returns parsed result
@@ -424,7 +447,7 @@ export async function pushSheet(req, res) {
* uploads Client secrets file * uploads Client secrets file
* @returns parsed result * @returns parsed result
*/ */
export async function sheetClientFile(req, res) { export async function uploadGoogleSheetClientFile(req, res) {
if (!req.file.path) { if (!req.file.path) {
res.status(400).send({ message: 'File not found' }); res.status(400).send({ message: 'File not found' });
return; return;
@@ -432,8 +455,8 @@ export async function sheetClientFile(req, res) {
try { try {
const client = JSON.parse(fs.readFileSync(req.file.path as string, 'utf-8')); const client = JSON.parse(fs.readFileSync(req.file.path as string, 'utf-8'));
await Sheet.saveClientSecrets(client); const auth = await Sheet.saveClientSecrets(client);
res.status(200).send('OK'); res.status(200).send(auth);
} catch (error) { } catch (error) {
res.status(500).send({ message: error.toString() }); res.status(500).send({ message: error.toString() });
} }
@@ -463,24 +486,39 @@ export async function sheetAuthUrl(req, res) {
} }
/** /**
* Meant to create a new project file, it will clear only fields which are specific to a project * @description Get google sheet Settings
* @param req * @method GET
* @param res
*/ */
export const postNew: RequestHandler = async (req, res) => { export const getGoogleSheetSettings = async (req, res) => {
const sheet = DataProvider.getGoogleSheet();
res.status(200).send(sheet);
};
/**
* @description Change view Settings
* @method POST
*/
export const postGoogleSheetSettings = async (req, res) => {
if (failEmptyObjects(req.body, res)) {
return;
}
try { try {
const newProjectData: ProjectData = { const newData = {
title: req.body?.title ?? '', id: req.body.id,
description: req.body?.description ?? '', worksheet: req.body.worksheet,
publicUrl: req.body?.publicUrl ?? '',
publicInfo: req.body?.publicInfo ?? '',
backstageUrl: req.body?.backstageUrl ?? '',
backstageInfo: req.body?.backstageInfo ?? '',
}; };
const newData = await DataProvider.setProjectData(newProjectData); await DataProvider.setGoogleSheet(newData);
await deleteAllEvents(); res.status(200).send(newData);
res.status(201).send(newData);
} catch (error) { } catch (error) {
res.status(400).send({ message: error.toString() }); res.status(400).send({ message: error.toString() });
} }
}; };
/**
* @description Get google sheet state
* @method GET
*/
export const getGoogleSheetState = async (req, res) => {
res.status(200).send(await Sheet.getSheetState());
};
@@ -145,3 +145,13 @@ export const validateSheetPreview = [
next(); next();
}, },
]; ];
export const validateGoogleSheetSettings = [
body('id').isString().optional({ nullable: false }),
body('worksheet').isString().optional({ nullable: false }),
(req, res, next) => {
const errors = validationResult(req);
if (!errors.isEmpty()) return res.status(422).json({ errors: errors.array() });
next();
},
];
+16 -3
View File
@@ -21,13 +21,17 @@ import {
previewExcel, previewExcel,
sheetAuthUrl, sheetAuthUrl,
sheetAuthState, sheetAuthState,
sheetClientFile, uploadGoogleSheetClientFile,
previewSheet, previewSheet,
pushSheet, pushSheet,
getGoogleSheetSettings,
postGoogleSheetSettings,
getGoogleSheetState,
} from '../controllers/ontimeController.js'; } from '../controllers/ontimeController.js';
import { import {
validateAliases, validateAliases,
validateGoogleSheetSettings,
validateOSC, validateOSC,
validateOscSubscription, validateOscSubscription,
validatePatchProjectFile, validatePatchProjectFile,
@@ -95,7 +99,7 @@ router.post('/osc-subscriptions', validateOscSubscription, postOscSubscriptions)
router.post('/new', projectSanitiser, postNew); router.post('/new', projectSanitiser, postNew);
// create route between controller and '/ontime/sheet-client' endpoint // create route between controller and '/ontime/sheet-client' endpoint
router.post('/sheet-clientsecrect', uploadFile, sheetClientFile); router.post('/sheet-clientsecrect', uploadFile, uploadGoogleSheetClientFile);
// create route between controller and '/ontime/sheet-authstatus' endpoint // create route between controller and '/ontime/sheet-authstatus' endpoint
router.get('/sheet-authstatus', sheetAuthState); router.get('/sheet-authstatus', sheetAuthState);
@@ -107,4 +111,13 @@ router.get('/sheet-authurl', sheetAuthUrl);
router.post('/sheet-preview', validateSheetPreview, previewSheet); router.post('/sheet-preview', validateSheetPreview, previewSheet);
// create route between controller and '/ontime/preview-sheet' endpoint // create route between controller and '/ontime/preview-sheet' endpoint
router.post('/sheet-push', pushSheet); router.post('/sheet-push', pushSheet);
// create route between controller and '/ontime/sheet-settings' endpoint
router.get('/sheet-settings', getGoogleSheetSettings);
// create route between controller and '/ontime/sheet-settings' endpoint
router.post('/sheet-settings', validateGoogleSheetSettings, postGoogleSheetSettings);
// create route between controller and '/ontime/sheet-state' endpoint
router.get('/sheet-state', getGoogleSheetState);
+35 -3
View File
@@ -11,6 +11,7 @@ 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'; import { DataProvider } from '../classes/data-provider/DataProvider.js';
import { GoogleSheetState } from 'ontime-types';
type ResponseOK = { type ResponseOK = {
data: Partial<DatabaseModel>; data: Partial<DatabaseModel>;
@@ -24,6 +25,27 @@ class sheet {
private readonly token = this.sheetsFolder + '/token.json'; private readonly token = this.sheetsFolder + '/token.json';
private static authUrl: null | string = null; private static authUrl: null | string = null;
public async getSheetState(): Promise<GoogleSheetState> {
const ret: GoogleSheetState = {
auth: false,
id: false,
worksheet: false,
};
ret.auth = await this.authorized();
if (ret.auth) {
const settings = DataProvider.getGoogleSheet();
const x = await this.exist(settings.id, settings.worksheet);
if (x === true) {
ret.id = true;
} else if (x !== false) {
ret.id = true;
ret.worksheet = true;
}
}
logger.info(LogOrigin.Server, `Sheet State: ${ret}`);
return ret;
}
/** /**
* checks the authorized state * checks the authorized state
* @returns {Promise<boolean>} * @returns {Promise<boolean>}
@@ -42,10 +64,13 @@ class sheet {
* test existance of sheet and workssheet and get is index * test existance of sheet and workssheet and get is index
* @param {string} sheetId - https://docs.google.com/spreadsheets/d/[[spreadsheetId]]/edit#gid=0 * @param {string} sheetId - https://docs.google.com/spreadsheets/d/[[spreadsheetId]]/edit#gid=0
* @param {string} worksheet - the name of the worksheet containg ontime data * @param {string} worksheet - the name of the worksheet containg ontime data
* @returns {Promise<false | {worksheetId: number, range: string}>} - false if not found | id of worksheet * @returns {Promise<false | {worksheetId: number, range: string}>} - false if not found | true if sheetId existes | id of worksheet and rage of worksheet
* @throws * @throws
*/ */
public async exist(sheetId: string, worksheet: string): Promise<false | { worksheetId: number; range: string }> { public async exist(
sheetId: string,
worksheet: string,
): Promise<false | true | { worksheetId: number; range: string }> {
const spreadsheets = await sheets({ version: 'v4', auth: sheet.client }).spreadsheets.get({ const spreadsheets = await sheets({ version: 'v4', auth: sheet.client }).spreadsheets.get({
spreadsheetId: sheetId, spreadsheetId: sheetId,
}); });
@@ -58,6 +83,8 @@ class sheet {
w.properties.gridProperties.columnCount, w.properties.gridProperties.columnCount,
); );
return { worksheetId: w.properties.sheetId, range: worksheet + '!A1:' + endCell }; return { worksheetId: w.properties.sheetId, range: worksheet + '!A1:' + endCell };
} else {
return true;
} }
} }
return false; return false;
@@ -73,6 +100,8 @@ class sheet {
const sheetInfo = await this.exist(sheetId, worksheet); const sheetInfo = await this.exist(sheetId, worksheet);
if (!sheetInfo) { if (!sheetInfo) {
throw new Error(`Sheet not dose not exits`); throw new Error(`Sheet not dose not exits`);
} else if (sheetInfo === true) {
throw new Error(`Worksheet not dose not exits`);
} }
if (!isExcelImportMap(options)) { if (!isExcelImportMap(options)) {
@@ -176,6 +205,8 @@ class sheet {
const sheetInfo = await this.exist(sheetId, worksheet); const sheetInfo = await this.exist(sheetId, worksheet);
if (!sheetInfo) { if (!sheetInfo) {
throw new Error(`Sheet not dose not exits`); throw new Error(`Sheet not dose not exits`);
} else if (sheetInfo === true) {
throw new Error(`Worksheet not dose not exits`);
} }
const rq = await sheets({ version: 'v4', auth: sheet.client }).spreadsheets.values.get({ const rq = await sheets({ version: 'v4', auth: sheet.client }).spreadsheets.values.get({
@@ -203,7 +234,7 @@ class sheet {
* saves Object to appdata path as client_secret.json * saves Object to appdata path as client_secret.json
* @param {} secrets * @param {} secrets
*/ */
public async saveClientSecrets(secrets) { public async saveClientSecrets(secrets): Promise<GoogleSheetState> {
ensureDirectory(this.sheetsFolder); ensureDirectory(this.sheetsFolder);
logger.info(LogOrigin.Server, 'Sheets: got new client_secret'); logger.info(LogOrigin.Server, 'Sheets: got new client_secret');
//TODO: test that this is actualy a client file? //TODO: test that this is actualy a client file?
@@ -211,6 +242,7 @@ class sheet {
sheet.client = null; sheet.client = null;
sheet.authUrl = null; sheet.authUrl = null;
await writeFile(this.client_secret, JSON.stringify(secrets), 'utf-8'); await writeFile(this.client_secret, JSON.stringify(secrets), 'utf-8');
return await this.getSheetState();
} }
/** /**
@@ -2,3 +2,9 @@ export type GoogleSheet = {
worksheet: string; worksheet: string;
id: string; id: string;
}; };
export type GoogleSheetState = {
auth: boolean;
id: boolean;
worksheet: boolean;
};
+1 -1
View File
@@ -34,7 +34,7 @@ export type { OSCSettings, OscSubscription, OscSubscriptionOptions } from './def
// ---> HTTP // ---> HTTP
// ---> Google Sheet // ---> Google Sheet
export type { GoogleSheet } from './definitions/core/GoogleSheet.type.js'; export type { GoogleSheet, GoogleSheetState } from './definitions/core/GoogleSheet.type.js';
// SERVER RESPONSES // SERVER RESPONSES
export type { NetworkInterface, GetInfo } from './api/ontime-controller/BackendResponse.type.js'; export type { NetworkInterface, GetInfo } from './api/ontime-controller/BackendResponse.type.js';