Feature/Add teams, gmail, outlook tools (#4577)

* add teams, gmail, outlook tools

* update docs link

* update credentials for oauth2

* add jira tool

* add google drive, google calendar, google sheets tools, powerpoint, excel, word doc loader

* update jira logo

* Refactor Gmail and Outlook tools to remove maxOutputLength parameter and enhance request handling. Update response formatting to include parameters in the output. Adjust Google Drive tools to simplify success messages by removing unnecessary parameter details.
This commit is contained in:
Henry Heng
2025-06-06 19:52:04 +01:00
committed by GitHub
parent 6dcb65cedb
commit 30c4180d97
62 changed files with 16832 additions and 144 deletions
+13
View File
@@ -0,0 +1,13 @@
import client from './client'
const authorize = (credentialId) => client.post(`/oauth2-credential/authorize/${credentialId}`)
const refresh = (credentialId) => client.post(`/oauth2-credential/refresh/${credentialId}`)
const getCallback = (queryParams) => client.get(`/oauth2-credential/callback?${queryParams}`)
export default {
authorize,
refresh,
getCallback
}
+79 -34
View File
@@ -1118,42 +1118,87 @@ const _showHideOperation = (nodeData, inputParam, displayType, index) => {
if (path.includes('$index')) {
path = path.replace('$index', index)
}
const groundValue = get(nodeData.inputs, path, '')
let groundValue = get(nodeData.inputs, path, '')
if (groundValue && typeof groundValue === 'string' && groundValue.startsWith('[') && groundValue.endsWith(']')) {
groundValue = JSON.parse(groundValue)
}
if (Array.isArray(comparisonValue)) {
if (displayType === 'show' && !comparisonValue.includes(groundValue)) {
inputParam.display = false
// Handle case where groundValue is an array
if (Array.isArray(groundValue)) {
if (Array.isArray(comparisonValue)) {
// Both are arrays - check if there's any intersection
const hasIntersection = comparisonValue.some((val) => groundValue.includes(val))
if (displayType === 'show' && !hasIntersection) {
inputParam.display = false
}
if (displayType === 'hide' && hasIntersection) {
inputParam.display = false
}
} else if (typeof comparisonValue === 'string') {
// comparisonValue is string, groundValue is array - check if array contains the string
const matchFound = groundValue.some((val) => comparisonValue === val || new RegExp(comparisonValue).test(val))
if (displayType === 'show' && !matchFound) {
inputParam.display = false
}
if (displayType === 'hide' && matchFound) {
inputParam.display = false
}
} else if (typeof comparisonValue === 'boolean' || typeof comparisonValue === 'number') {
// For boolean/number comparison with array, check if array contains the value
const matchFound = groundValue.includes(comparisonValue)
if (displayType === 'show' && !matchFound) {
inputParam.display = false
}
if (displayType === 'hide' && matchFound) {
inputParam.display = false
}
} else if (typeof comparisonValue === 'object') {
// For object comparison with array, use deep equality check
const matchFound = groundValue.some((val) => isEqual(comparisonValue, val))
if (displayType === 'show' && !matchFound) {
inputParam.display = false
}
if (displayType === 'hide' && matchFound) {
inputParam.display = false
}
}
if (displayType === 'hide' && comparisonValue.includes(groundValue)) {
inputParam.display = false
}
} else if (typeof comparisonValue === 'string') {
if (displayType === 'show' && !(comparisonValue === groundValue || new RegExp(comparisonValue).test(groundValue))) {
inputParam.display = false
}
if (displayType === 'hide' && (comparisonValue === groundValue || new RegExp(comparisonValue).test(groundValue))) {
inputParam.display = false
}
} else if (typeof comparisonValue === 'boolean') {
if (displayType === 'show' && comparisonValue !== groundValue) {
inputParam.display = false
}
if (displayType === 'hide' && comparisonValue === groundValue) {
inputParam.display = false
}
} else if (typeof comparisonValue === 'object') {
if (displayType === 'show' && !isEqual(comparisonValue, groundValue)) {
inputParam.display = false
}
if (displayType === 'hide' && isEqual(comparisonValue, groundValue)) {
inputParam.display = false
}
} else if (typeof comparisonValue === 'number') {
if (displayType === 'show' && comparisonValue !== groundValue) {
inputParam.display = false
}
if (displayType === 'hide' && comparisonValue === groundValue) {
inputParam.display = false
} else {
// Original logic for non-array groundValue
if (Array.isArray(comparisonValue)) {
if (displayType === 'show' && !comparisonValue.includes(groundValue)) {
inputParam.display = false
}
if (displayType === 'hide' && comparisonValue.includes(groundValue)) {
inputParam.display = false
}
} else if (typeof comparisonValue === 'string') {
if (displayType === 'show' && !(comparisonValue === groundValue || new RegExp(comparisonValue).test(groundValue))) {
inputParam.display = false
}
if (displayType === 'hide' && (comparisonValue === groundValue || new RegExp(comparisonValue).test(groundValue))) {
inputParam.display = false
}
} else if (typeof comparisonValue === 'boolean') {
if (displayType === 'show' && comparisonValue !== groundValue) {
inputParam.display = false
}
if (displayType === 'hide' && comparisonValue === groundValue) {
inputParam.display = false
}
} else if (typeof comparisonValue === 'object') {
if (displayType === 'show' && !isEqual(comparisonValue, groundValue)) {
inputParam.display = false
}
if (displayType === 'hide' && isEqual(comparisonValue, groundValue)) {
inputParam.display = false
}
} else if (typeof comparisonValue === 'number') {
if (displayType === 'show' && comparisonValue !== groundValue) {
inputParam.display = false
}
if (displayType === 'hide' && comparisonValue === groundValue) {
inputParam.display = false
}
}
}
})
@@ -18,6 +18,7 @@ import { IconHandStop, IconX } from '@tabler/icons-react'
// API
import credentialsApi from '@/api/credentials'
import oauth2Api from '@/api/oauth2'
// Hooks
import useApi from '@/hooks/useApi'
@@ -212,6 +213,149 @@ const AddEditCredentialDialog = ({ show, dialogProps, onCancel, onConfirm, setEr
}
}
const setOAuth2 = async () => {
try {
let credentialId = null
// First save or add the credential
if (dialogProps.type === 'ADD') {
// Add new credential first
const obj = {
name,
credentialName: componentCredential.name,
plainDataObj: credentialData
}
const createResp = await credentialsApi.createCredential(obj)
if (createResp.data) {
credentialId = createResp.data.id
}
} else {
// Save existing credential first
const saveObj = {
name,
credentialName: componentCredential.name
}
let plainDataObj = {}
for (const key in credentialData) {
if (credentialData[key] !== REDACTED_CREDENTIAL_VALUE) {
plainDataObj[key] = credentialData[key]
}
}
if (Object.keys(plainDataObj).length) saveObj.plainDataObj = plainDataObj
const saveResp = await credentialsApi.updateCredential(credential.id, saveObj)
if (saveResp.data) {
credentialId = credential.id
}
}
if (!credentialId) {
throw new Error('Failed to save credential')
}
const authResponse = await oauth2Api.authorize(credentialId)
if (authResponse.data && authResponse.data.success && authResponse.data.authorizationUrl) {
// Open the authorization URL in a new window/tab
const authWindow = window.open(
authResponse.data.authorizationUrl,
'_blank',
'width=600,height=700,scrollbars=yes,resizable=yes'
)
if (!authWindow) {
throw new Error('Failed to open authorization window. Please check if popups are blocked.')
}
// Listen for messages from the popup window
const handleMessage = (event) => {
// Verify origin if needed (you may want to add origin checking)
if (event.data && (event.data.type === 'OAUTH2_SUCCESS' || event.data.type === 'OAUTH2_ERROR')) {
window.removeEventListener('message', handleMessage)
if (event.data.type === 'OAUTH2_SUCCESS') {
enqueueSnackbar({
message: 'OAuth2 authorization completed successfully',
options: {
key: new Date().getTime() + Math.random(),
variant: 'success',
action: (key) => (
<Button style={{ color: 'white' }} onClick={() => closeSnackbar(key)}>
<IconX />
</Button>
)
}
})
onConfirm(credentialId)
} else if (event.data.type === 'OAUTH2_ERROR') {
enqueueSnackbar({
message: event.data.message || 'OAuth2 authorization failed',
options: {
key: new Date().getTime() + Math.random(),
variant: 'error',
persist: true,
action: (key) => (
<Button style={{ color: 'white' }} onClick={() => closeSnackbar(key)}>
<IconX />
</Button>
)
}
})
}
// Close the auth window if it's still open
if (authWindow && !authWindow.closed) {
authWindow.close()
}
}
}
// Add message listener
window.addEventListener('message', handleMessage)
// Fallback: Monitor the auth window and handle if it closes manually
const checkClosed = setInterval(() => {
if (authWindow.closed) {
clearInterval(checkClosed)
window.removeEventListener('message', handleMessage)
// If no message was received, assume user closed window manually
// Don't show error in this case, just close dialog
onConfirm(credentialId)
}
}, 1000)
// Cleanup after a reasonable timeout (5 minutes)
setTimeout(() => {
clearInterval(checkClosed)
window.removeEventListener('message', handleMessage)
if (authWindow && !authWindow.closed) {
authWindow.close()
}
}, 300000) // 5 minutes
} else {
throw new Error('Invalid response from authorization endpoint')
}
} catch (error) {
console.error('OAuth2 authorization error:', error)
if (setError) setError(error)
enqueueSnackbar({
message: `OAuth2 authorization failed: ${error.response?.data?.message || error.message || 'Unknown error'}`,
options: {
key: new Date().getTime() + Math.random(),
variant: 'error',
persist: true,
action: (key) => (
<Button style={{ color: 'white' }} onClick={() => closeSnackbar(key)}>
<IconX />
</Button>
)
}
})
}
}
const component = show ? (
<Dialog
fullWidth
@@ -315,12 +459,34 @@ const AddEditCredentialDialog = ({ show, dialogProps, onCancel, onConfirm, setEr
/>
</Box>
)}
{!shared && componentCredential && componentCredential.name && componentCredential.name.includes('OAuth2') && (
<Box sx={{ p: 2 }}>
<Stack sx={{ position: 'relative' }} direction='row'>
<Typography variant='overline'>OAuth Redirect URL</Typography>
</Stack>
<OutlinedInput
id='oauthRedirectUrl'
type='string'
disabled
fullWidth
value={`${baseURL}/api/v1/oauth2-credential/callback`}
/>
</Box>
)}
{!shared &&
componentCredential &&
componentCredential.inputs &&
componentCredential.inputs.map((inputParam, index) => (
<CredentialInputHandler key={index} inputParam={inputParam} data={credentialData} />
))}
componentCredential.inputs
.filter((inputParam) => inputParam.hidden !== true)
.map((inputParam, index) => <CredentialInputHandler key={index} inputParam={inputParam} data={credentialData} />)}
{!shared && componentCredential && componentCredential.name && componentCredential.name.includes('OAuth2') && (
<Box sx={{ p: 2 }}>
<Button variant='contained' color='secondary' onClick={() => setOAuth2()}>
Authenticate
</Button>
</Box>
)}
</DialogContent>
<DialogActions>
{!shared && (