From 3b909bec0a0c1d87e3b2317b572b85fb3a962191 Mon Sep 17 00:00:00 2001 From: Darin Kelkhoff Date: Tue, 27 Sep 2022 10:42:55 -0500 Subject: [PATCH] Try to use google login separately from picker, to get an access token with more scopes --- package.json | 1 + .../components/QGoogleDriveFolderPicker.tsx | 108 ++++++++++++++---- .../QGoogleDriveFolderPickerWrapper.tsx | 46 ++++++++ src/qqq/pages/process-run/index.tsx | 23 +--- src/qqq/utils/QValueUtils.tsx | 39 +++++-- 5 files changed, 168 insertions(+), 49 deletions(-) create mode 100644 src/qqq/pages/process-run/components/QGoogleDriveFolderPickerWrapper.tsx diff --git a/package.json b/package.json index 13e43a8..9048327 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "@react-jvectormap/core": "1.0.1", "@react-jvectormap/unitedstates": "1.0.1", "@react-jvectormap/world": "1.0.0", + "@react-oauth/google": "0.2.8", "@testing-library/jest-dom": "5.16.2", "@testing-library/react": "12.1.2", "@testing-library/user-event": "13.5.0", diff --git a/src/qqq/pages/process-run/components/QGoogleDriveFolderPicker.tsx b/src/qqq/pages/process-run/components/QGoogleDriveFolderPicker.tsx index 1aeae8f..5484a5f 100644 --- a/src/qqq/pages/process-run/components/QGoogleDriveFolderPicker.tsx +++ b/src/qqq/pages/process-run/components/QGoogleDriveFolderPicker.tsx @@ -23,12 +23,19 @@ import {colors} from "@mui/material" import Box from "@mui/material/Box"; import Button from "@mui/material/Button"; import Grid from "@mui/material/Grid"; +import {useGoogleLogin} from "@react-oauth/google"; import {useFormikContext} from "formik"; import React, {useEffect, useState} from "react"; import useDrivePicker from "react-google-drive-picker"; -import MDBox from "qqq/components/Temporary/MDBox"; +import MDTypography from "qqq/components/Temporary/MDTypography"; -export function QGoogleDriveFolderPicker(): JSX.Element +interface Props +{ + showDefaultFoldersView: boolean; + showSharedDrivesView: boolean; +} + +export function QGoogleDriveFolderPicker({showDefaultFoldersView, showSharedDrivesView}: Props): JSX.Element { const clientId = process.env.REACT_APP_GOOGLE_APP_CLIENT_ID; const appApiKey = process.env.REACT_APP_GOOGLE_APP_API_KEY; @@ -36,24 +43,71 @@ export function QGoogleDriveFolderPicker(): JSX.Element const [ selectedGoogleFolderName, setSelectedGoogleFolderName ] = useState(null as string); const [ selectedGoogleFolderId, setSelectedGoogleFolderId ] = useState(null as string); const [ googleToken, setGoogleToken ] = useState(null as string); // maybe get from cookie/local-storage + const [ errorMessage, setErrorMessage ] = useState(null as string); const [ openPicker, authResponse ] = useDrivePicker(); const formikProps = useFormikContext(); + const loginOrOpenPicker = (token: string) => + { + if(token) + { + handleOpenPicker(token); + } + else + { + login(); + } + }; + + const login = useGoogleLogin({ + scope: "https://www.googleapis.com/auth/drive", + onSuccess: tokenResponse => + { + console.log("Token response"); + console.log(tokenResponse); + setGoogleToken(tokenResponse.access_token) + handleOpenPicker(tokenResponse.access_token); + } + }); + const handleOpenPicker = (token: string) => { + // @ts-ignore + const google = window.google + const customViewsArray: any[] = []; + + if(showDefaultFoldersView) + { + customViewsArray.push(new google.picker.DocsView(google.picker.ViewId.FOLDERS) + .setIncludeFolders(true) + .setMode(google.picker.DocsViewMode.LIST) + .setSelectFolderEnabled(true)); + } + + if(showSharedDrivesView) + { + customViewsArray.push(new google.picker.DocsView(google.picker.ViewId.FOLDERS) + .setEnableDrives(true) + .setIncludeFolders(true) + .setMode(google.picker.DocsViewMode.LIST) + .setSelectFolderEnabled(true)); + } + openPicker({ clientId: clientId, developerKey: appApiKey, viewId: "FOLDERS", token: token, // pass oauth token in case you already have one + disableDefaultView: (customViewsArray.length > 0), // if we have any custom views, then disable the default. showUploadView: false, showUploadFolders: false, supportDrives: true, multiselect: false, setSelectFolderEnabled: true, setIncludeFolders: true, + customViews: customViewsArray, callbackFunction: (data) => { if (data.action === "cancel") @@ -62,49 +116,61 @@ export function QGoogleDriveFolderPicker(): JSX.Element setSelectedGoogleFolderId(null); setSelectedGoogleFolderName(null); } - else + else if (data.action === "picked") { console.log(data); - setSelectedGoogleFolderId(data.docs[0].id); - setSelectedGoogleFolderName(data.docs[0].name); + const mimeType = data.docs[0].mimeType; + if(mimeType === "application/vnd.google-apps.folder") + { + setSelectedGoogleFolderId(data.docs[0].id); + setSelectedGoogleFolderName(data.docs[0].name); + setErrorMessage(null) + } + else + { + setSelectedGoogleFolderId(null); + setSelectedGoogleFolderName(null); + setErrorMessage("You selected a file, but a folder is required.") + } + } + else + { + console.log("Called with un-recognized action:"); + console.log(data); } }, }); }; - if(authResponse && authResponse.access_token && authResponse.access_token !== googleToken) - { - setGoogleToken(authResponse.access_token); - } - useEffect(() => { - formikProps.setFieldValue("googleDriveAccessToken", authResponse?.access_token); + formikProps.setFieldValue("googleDriveAccessToken", googleToken); formikProps.setFieldValue("googleDriveFolderId", selectedGoogleFolderId); formikProps.setFieldValue("googleDriveFolderName", selectedGoogleFolderName); }, [selectedGoogleFolderId, selectedGoogleFolderName]) return ( - - + - {selectedGoogleFolderName} - - {/* - + - {errors[fieldName] && You must select a file to proceed} +
{errorMessage}
-
- */} -
+ +
); } + +QGoogleDriveFolderPicker.defaultProps = { + showDefaultFoldersView: true, + showSharedDrivesView: true +}; diff --git a/src/qqq/pages/process-run/components/QGoogleDriveFolderPickerWrapper.tsx b/src/qqq/pages/process-run/components/QGoogleDriveFolderPickerWrapper.tsx new file mode 100644 index 0000000..7f793be --- /dev/null +++ b/src/qqq/pages/process-run/components/QGoogleDriveFolderPickerWrapper.tsx @@ -0,0 +1,46 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2022. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import {GoogleOAuthProvider} from "@react-oauth/google"; +import React from "react"; +import {QGoogleDriveFolderPicker} from "qqq/pages/process-run/components/QGoogleDriveFolderPicker"; + +interface Props +{ + showDefaultFoldersView: boolean; + showSharedDrivesView: boolean; +} + +export function QGoogleDriveFolderPickerWrapper({showDefaultFoldersView, showSharedDrivesView}: Props): JSX.Element +{ + const clientId = process.env.REACT_APP_GOOGLE_APP_CLIENT_ID; + + return ( + + + + ); +} + +QGoogleDriveFolderPickerWrapper.defaultProps = { + showDefaultFoldersView: true, + showSharedDrivesView: true +}; diff --git a/src/qqq/pages/process-run/index.tsx b/src/qqq/pages/process-run/index.tsx index 16ed041..2843ca0 100644 --- a/src/qqq/pages/process-run/index.tsx +++ b/src/qqq/pages/process-run/index.tsx @@ -41,6 +41,7 @@ import StepLabel from "@mui/material/StepLabel"; import Stepper from "@mui/material/Stepper"; import Typography from "@mui/material/Typography"; import {DataGridPro, GridColDef} from "@mui/x-data-grid-pro"; +import {GoogleOAuthProvider} from "@react-oauth/google"; import FormData from "form-data"; import {Form, Formik} from "formik"; import React, {useEffect, useState} from "react"; @@ -54,7 +55,7 @@ import MDBox from "qqq/components/Temporary/MDBox"; import MDButton from "qqq/components/Temporary/MDButton"; import MDProgress from "qqq/components/Temporary/MDProgress"; import MDTypography from "qqq/components/Temporary/MDTypography"; -import {QGoogleDriveFolderPicker} from "qqq/pages/process-run/components/QGoogleDriveFolderPicker"; +import {QGoogleDriveFolderPickerWrapper} from "qqq/pages/process-run/components/QGoogleDriveFolderPickerWrapper"; import QValidationReview from "qqq/pages/process-run/components/QValidationReview"; import QClient from "qqq/utils/QClient"; import QValueUtils from "qqq/utils/QValueUtils"; @@ -181,21 +182,6 @@ function ProcessRun({process, defaultProcessValues}: Props): JSX.Element setDisabledBulkEditFields(newDisabledBulkEditFields); }; - const formatViewValue = (value: any): JSX.Element => - { - if (value === null || value === undefined) - { - return  ; - } - - if (typeof value === "string") - { - return QValueUtils.breakTextIntoLines(value); - } - - return ({value}); - }; - const toggleShowErrorDetail = () => { setShowErrorDetail(!showErrorDetail); @@ -335,7 +321,7 @@ function ProcessRun({process, defaultProcessValues}: Props): JSX.Element :   - {formatViewValue(processValues[field.name])} + {QValueUtils.getValueForDisplay(field, processValues[field.name])} ))} @@ -396,7 +382,8 @@ function ProcessRun({process, defaultProcessValues}: Props): JSX.Element } { component.type === QComponentType.GOOGLE_DRIVE_SELECT_FOLDER && ( - + // todo - make these booleans configurable (values on the component) + ) } { diff --git a/src/qqq/utils/QValueUtils.tsx b/src/qqq/utils/QValueUtils.tsx index f69ad53..e6a5432 100644 --- a/src/qqq/utils/QValueUtils.tsx +++ b/src/qqq/utils/QValueUtils.tsx @@ -26,7 +26,6 @@ import {QRecord} from "@kingsrook/qqq-frontend-core/lib/model/QRecord"; import "datejs"; import {Chip, Icon} from "@mui/material"; import React, {Fragment} from "react"; -import {Link} from "react-router-dom"; /******************************************************************************* ** Utility class for working with QQQ Values @@ -34,15 +33,29 @@ import {Link} from "react-router-dom"; *******************************************************************************/ class QValueUtils { + + /******************************************************************************* + ** When you have a field, and a record - call this method to get a string or + ** element back to display the field's value. + *******************************************************************************/ public static getDisplayValue(field: QFieldMetaData, record: QRecord): string | JSX.Element { const displayValue = record.displayValues ? record.displayValues.get(field.name) : undefined; const rawValue = record.values ? record.values.get(field.name) : undefined; + return QValueUtils.getValueForDisplay(field, rawValue, displayValue); + } + + /******************************************************************************* + ** When you have a field and a value (either just a raw value, or a raw and + ** display value), call this method to get a string Element to display. + *******************************************************************************/ + public static getValueForDisplay(field: QFieldMetaData, rawValue: any, displayValue: any = rawValue): string | JSX.Element + { if (field.hasAdornment(AdornmentType.LINK)) { const adornment = field.getAdornment(AdornmentType.LINK); - return ( + return ( { e.stopPropagation(); }}>{rawValue}) @@ -62,14 +75,15 @@ class QValueUtils return (); } - return (QValueUtils._getDisplayValueString(field, record)); + return (QValueUtils.getUnadornedValueForDisplay(field, rawValue, displayValue)); } - private static _getDisplayValueString(field: QFieldMetaData, record: QRecord): string + /******************************************************************************* + ** After we know there's no element to be returned (e.g., because no adornment), + ** this method does the string formatting. + *******************************************************************************/ + private static getUnadornedValueForDisplay(field: QFieldMetaData, rawValue: any, displayValue: any): string | JSX.Element { - const displayValue = record.displayValues ? record.displayValues.get(field.name) : undefined; - const rawValue = record.values ? record.values.get(field.name) : undefined; - if (field.type === QFieldType.DATE_TIME) { if (!rawValue) @@ -90,12 +104,18 @@ class QValueUtils return (displayValue); } + let returnValue = displayValue; if (displayValue === undefined && rawValue !== undefined) { - return (rawValue); + returnValue = rawValue; } - return (displayValue); + if (typeof returnValue === "string" && returnValue.indexOf("\n") > -1) + { + return QValueUtils.breakTextIntoLines(returnValue); + } + + return (returnValue); } public static getFormattedNumber(n: number): string @@ -117,7 +137,6 @@ class QValueUtils } } - public static breakTextIntoLines(value: string): JSX.Element { return (