diff --git a/packages/components/nodes/documentloaders/GoogleDrive/GoogleDrive.ts b/packages/components/nodes/documentloaders/GoogleDrive/GoogleDrive.ts index 603c2be09cf..222d7d8f928 100644 --- a/packages/components/nodes/documentloaders/GoogleDrive/GoogleDrive.ts +++ b/packages/components/nodes/documentloaders/GoogleDrive/GoogleDrive.ts @@ -310,14 +310,7 @@ class GoogleDrive_DocumentLoaders implements INode { if (selectedFiles) { // Load selected files (selectedFiles can be a single ID or comma-separated IDs) - let ids: string[] = [] - if (typeof selectedFiles === 'string' && selectedFiles.startsWith('[') && selectedFiles.endsWith(']')) { - ids = convertMultiOptionsToStringArray(selectedFiles) - } else if (typeof selectedFiles === 'string') { - ids = [selectedFiles] - } else if (Array.isArray(selectedFiles)) { - ids = selectedFiles - } + const ids = this.normalizeSelectedFileIds(selectedFiles) for (const id of ids) { const fileInfo = await this.getFileInfo(id, accessToken, includeSharedDrives) if (fileInfo && this.shouldProcessFile(fileInfo, fileTypes)) { @@ -400,6 +393,44 @@ class GoogleDrive_DocumentLoaders implements INode { } } + private normalizeSelectedFileIds(selectedFiles: unknown): string[] { + if (!selectedFiles) return [] + + let rawItems: unknown[] = [] + + if (typeof selectedFiles === 'string') { + const trimmed = selectedFiles.trim() + if (trimmed.startsWith('[') && trimmed.endsWith(']')) { + try { + rawItems = JSON.parse(trimmed) + } catch (error) { + rawItems = [] + } + } else if (trimmed.includes(',')) { + rawItems = trimmed.split(',').map((item) => item.trim()) + } else { + rawItems = [trimmed] + } + } else if (Array.isArray(selectedFiles)) { + rawItems = selectedFiles + } else { + rawItems = [selectedFiles] + } + + const normalized = rawItems + .map((item) => { + if (typeof item === 'string') return item + if (item && typeof item === 'object') { + const maybeItem = item as { fileId?: string; id?: string } + return maybeItem.fileId || maybeItem.id + } + return undefined + }) + .filter((value): value is string => Boolean(value)) + + return Array.from(new Set(normalized)) + } + private async getFileInfo(fileId: string, accessToken: string, includeSharedDrives: boolean): Promise { const url = new URL(`https://www.googleapis.com/drive/v3/files/${encodeURIComponent(fileId)}`) url.searchParams.append('fields', 'id, name, mimeType, size, createdTime, modifiedTime, parents, webViewLink, driveId') diff --git a/packages/ui/src/ui-component/drive/GoogleDrivePicker.jsx b/packages/ui/src/ui-component/drive/GoogleDrivePicker.jsx index e58b08b5c1e..d1dec1fd5ff 100644 --- a/packages/ui/src/ui-component/drive/GoogleDrivePicker.jsx +++ b/packages/ui/src/ui-component/drive/GoogleDrivePicker.jsx @@ -168,6 +168,7 @@ const useGooglePicker = (accessToken, onFilesSelected) => { export const GoogleDrivePicker = ({ onChange, value, disabled, credentialId, credentialData, handleCredentialDataChange }) => { const dispatch = useDispatch() + const previousCredentialIdRef = useRef(null) const [selectedFiles, setSelectedFiles] = useState(() => { try { return value ? JSON.parse(value) : [] @@ -213,22 +214,40 @@ export const GoogleDrivePicker = ({ onChange, value, disabled, credentialId, cre const { createPicker, closePicker, pickerInstance } = useGooglePicker(accessToken, handleFilesSelected) - // Load credential data on mount and clear files when credential changes + // Load credential data on mount and clear files only when credential changes useEffect(() => { if (credentialId) { getCredentialDataApi.request(credentialId) - // Clear selected files when credential changes - setSelectedFiles([]) - onChange(JSON.stringify([])) + const previousCredentialId = previousCredentialIdRef.current + if (previousCredentialId && previousCredentialId !== credentialId) { + // Clear selected files when credential changes + setSelectedFiles([]) + onChange(JSON.stringify([])) - // Reset token state - setAccessToken(null) - setIsTokenExpired(false) - setIsReauthRequired(false) + // Reset token state + setAccessToken(null) + setIsTokenExpired(false) + setIsReauthRequired(false) + } + + previousCredentialIdRef.current = credentialId } }, [credentialId]) + // Sync local selections when value prop changes + useEffect(() => { + if (value === undefined) return + try { + const parsedValue = typeof value === 'string' ? JSON.parse(value) : value + if (Array.isArray(parsedValue)) { + setSelectedFiles(parsedValue) + } + } catch (error) { + console.error('Error parsing selected files value:', error) + } + }, [value]) + // Handle credential data from prop useEffect(() => { if (credentialData?.plainDataObj) { @@ -311,7 +330,28 @@ export const GoogleDrivePicker = ({ onChange, value, disabled, credentialId, cre try { setIsRefreshing(true) - await oauth2Api.refresh(credentialId) + try { + const response = await oauth2Api.refresh(credentialId) + const oauth2Message = response?.data?.message || '' + const oauth2Failed = response?.data?.success === false + const isMissingOauthConfig = oauth2Message.includes('Missing required OAuth configuration') + + if (oauth2Failed && isMissingOauthConfig) { + await credentialsApi.refreshAccessToken({ credentialId }) + } else if (oauth2Failed) { + throw new Error(oauth2Message || 'Error refreshing access token') + } + } catch (error) { + const errorMessage = error.response?.data?.message || error.message || '' + const isMissingOauthConfig = errorMessage.includes('Missing required OAuth configuration') + + if (!isMissingOauthConfig) { + throw error + } + + await credentialsApi.refreshAccessToken({ credentialId }) + } + getCredentialDataApi.request(credentialId) setIsTokenExpired(false) setIsReauthRequired(false) diff --git a/packages/ui/src/views/docstore/DocStoreInputHandler.jsx b/packages/ui/src/views/docstore/DocStoreInputHandler.jsx index e3d999221e5..92c0aacb014 100644 --- a/packages/ui/src/views/docstore/DocStoreInputHandler.jsx +++ b/packages/ui/src/views/docstore/DocStoreInputHandler.jsx @@ -176,7 +176,7 @@ const DocStoreInputHandler = ({ inputParam, data, disabled = false, onNodeDataCh {inputParam && data.name === 'gmail' && inputParam.name === 'selectedLabels' && ( (data.inputs[inputParam.name] = newValue)} + onChange={(newValue) => handleDataChange({ inputParam, newValue })} value={data.inputs[inputParam.name] ?? '[]'} credentialId={selectedCredential} credentialData={selectedCredentialData} @@ -186,7 +186,7 @@ const DocStoreInputHandler = ({ inputParam, data, disabled = false, onNodeDataCh {inputParam && data.name === 'googleDrive' && inputParam.name === 'selectedFiles' && ( (data.inputs[inputParam.name] = newValue)} + onChange={(newValue) => handleDataChange({ inputParam, newValue })} value={data.inputs[inputParam.name] ?? '[]'} credentialId={selectedCredential} credentialData={selectedCredentialData}