From cb0a87e1a1b683e5d59cd0dca0311d05a9a8d959 Mon Sep 17 00:00:00 2001 From: Devanshu Gupta Date: Mon, 11 Aug 2025 18:59:49 +0530 Subject: [PATCH] Feat/ms graph pre release (#13581) * ee commit * merge commit * feat: updated openapi operation component * updated query operation sepctype * fix: updated query dropdown style * init plugin * init plugin * feat: config dropdown update * feat: added ms plugin * fix: plugin connection name * submodule reference updated * plugin label updated * added back margin top class --------- Co-authored-by: Ganesh Kumar --- frontend/src/_components/ApiEndpointInput.jsx | 65 +++- frontend/src/_components/OAuthWrapper.jsx | 6 +- frontend/src/_ui/OAuth/GrantTypes.jsx | 26 +- frontend/src/_ui/OAuth/index.js | 21 +- .../DataSourceManager/DataSourceManager.jsx | 12 +- .../plugins/microsoft_graph/.gitignore | 5 + marketplace/plugins/microsoft_graph/README.md | 4 + .../microsoft_graph/__tests__/index.js | 7 + .../plugins/microsoft_graph/lib/icon.svg | 13 + .../plugins/microsoft_graph/lib/index.ts | 302 ++++++++++++++++++ .../plugins/microsoft_graph/lib/manifest.json | 142 ++++++++ .../microsoft_graph/lib/operations.json | 23 ++ .../plugins/microsoft_graph/lib/types.ts | 30 ++ .../plugins/microsoft_graph/package.json | 27 ++ .../plugins/microsoft_graph/tsconfig.json | 11 + .../plugins/salesforce/lib/manifest.json | 3 + plugins/packages/common/lib/oauth.ts | 1 + server/src/assets/marketplace/plugins.json | 8 + .../src/modules/data-sources/util.service.ts | 5 +- 19 files changed, 670 insertions(+), 41 deletions(-) create mode 100644 marketplace/plugins/microsoft_graph/.gitignore create mode 100644 marketplace/plugins/microsoft_graph/README.md create mode 100644 marketplace/plugins/microsoft_graph/__tests__/index.js create mode 100644 marketplace/plugins/microsoft_graph/lib/icon.svg create mode 100644 marketplace/plugins/microsoft_graph/lib/index.ts create mode 100644 marketplace/plugins/microsoft_graph/lib/manifest.json create mode 100644 marketplace/plugins/microsoft_graph/lib/operations.json create mode 100644 marketplace/plugins/microsoft_graph/lib/types.ts create mode 100644 marketplace/plugins/microsoft_graph/package.json create mode 100644 marketplace/plugins/microsoft_graph/tsconfig.json diff --git a/frontend/src/_components/ApiEndpointInput.jsx b/frontend/src/_components/ApiEndpointInput.jsx index b58a2c9fe8..fdd75be699 100644 --- a/frontend/src/_components/ApiEndpointInput.jsx +++ b/frontend/src/_components/ApiEndpointInput.jsx @@ -63,7 +63,10 @@ const ApiEndpointInput = (props) => { // Check if specUrl is an object (multiple specs) or string (single spec) const isMultiSpec = typeof props.specUrl === 'object' && !Array.isArray(props.specUrl); - const [selectedSpecType, setSelectedSpecType] = useState(isMultiSpec ? Object.keys(props.specUrl)[0] || '' : null); + // Initialize selectedSpecType from props.options.specType if available + const [selectedSpecType, setSelectedSpecType] = useState( + isMultiSpec ? props.options?.specType || Object.keys(props.specUrl)[0] || '' : null + ); const fetchOpenApiSpec = (specUrlOrType) => { setLoadingSpec(true); @@ -177,6 +180,7 @@ const ApiEndpointInput = (props) => { [paramName]: parsedValue, }, }, + ...(isMultiSpec && { specType: selectedSpecType }), // Include specType if multiSpec }; setOptions(newOptions); props.optionsChanged(newOptions); @@ -204,25 +208,24 @@ const ApiEndpointInput = (props) => { if (path && operation) { return ( -
-
- {operation.toUpperCase()} +
+
+ {operation.toUpperCase()} + {path}
-
-
{path}
- {summary && !isSelected && ( - + {summary && !isSelected && ( +
+ {summary} - )} -
+
+ )}
); } else { return 'Select an operation'; } }; - const categorizeOperations = (operation, path, acc, category) => { const operationData = specJson.paths[path][operation]; const summary = operationData?.summary || ''; @@ -286,7 +289,14 @@ const ApiEndpointInput = (props) => { request: props.options?.params?.request ?? {}, }; setLoadingSpec(true); - setOptions({ ...props.options, params: queryParams }); + + // Initialize options with specType if multiSpec + const initialOptions = { + ...props.options, + params: queryParams, + ...(isMultiSpec && { specType: selectedSpecType }), + }; + setOptions(initialOptions); if (!isMultiSpec) { fetchOpenApiSpec(); @@ -299,10 +309,30 @@ const ApiEndpointInput = (props) => { } }, [selectedSpecType]); + const handleSpecTypeChange = (val) => { + setSelectedSpecType(val); + // When spec type changes, immediately update options with new specType + const newOptions = { + ...options, + specType: val, + // Clear operation-specific data when changing spec type + operation: null, + path: null, + selectedOperation: null, + params: { + path: {}, + query: {}, + request: {}, + }, + }; + setOptions(newOptions); + props.optionsChanged(newOptions); + }; + const specTypeOptions = isMultiSpec ? Object.keys(props.specUrl).map((key) => ({ value: key, - label: key, + label: key.charAt(0).toUpperCase() + key.slice(1), })) : []; @@ -312,13 +342,16 @@ const ApiEndpointInput = (props) => { {isMultiSpec && (
- +
- - optionchanged('grant_type', value)} + width={'100%'} + useMenuPortal={false} + /> +
+ )} - optionchanged('auth_type', value)} + width={'100%'} + useMenuPortal={false} + isDisabled={isDisabled} + /> +
+ )} diff --git a/marketplace/plugins/microsoft_graph/.gitignore b/marketplace/plugins/microsoft_graph/.gitignore new file mode 100644 index 0000000000..23e6609462 --- /dev/null +++ b/marketplace/plugins/microsoft_graph/.gitignore @@ -0,0 +1,5 @@ +node_modules +lib/*.d.* +lib/*.js +lib/*.js.map +dist/* \ No newline at end of file diff --git a/marketplace/plugins/microsoft_graph/README.md b/marketplace/plugins/microsoft_graph/README.md new file mode 100644 index 0000000000..93a855e2e8 --- /dev/null +++ b/marketplace/plugins/microsoft_graph/README.md @@ -0,0 +1,4 @@ + +# Microsoft_graph + +Documentation on: https://docs.tooljet.com/docs/data-sources/microsoft_graph \ No newline at end of file diff --git a/marketplace/plugins/microsoft_graph/__tests__/index.js b/marketplace/plugins/microsoft_graph/__tests__/index.js new file mode 100644 index 0000000000..2431cfddbe --- /dev/null +++ b/marketplace/plugins/microsoft_graph/__tests__/index.js @@ -0,0 +1,7 @@ +'use strict'; + +const microsoft_graph = require('../lib'); + +describe('microsoft_graph', () => { + it.todo('needs tests'); +}); diff --git a/marketplace/plugins/microsoft_graph/lib/icon.svg b/marketplace/plugins/microsoft_graph/lib/icon.svg new file mode 100644 index 0000000000..aabfd2d7c0 --- /dev/null +++ b/marketplace/plugins/microsoft_graph/lib/icon.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/marketplace/plugins/microsoft_graph/lib/index.ts b/marketplace/plugins/microsoft_graph/lib/index.ts new file mode 100644 index 0000000000..d8668a6e5d --- /dev/null +++ b/marketplace/plugins/microsoft_graph/lib/index.ts @@ -0,0 +1,302 @@ +import { + QueryError, + QueryResult, + QueryService, + User, + App, + validateAndSetRequestOptionsBasedOnAuthType, +} from '@tooljet-marketplace/common'; +import { SourceOptions, ConvertedFormat } from './types'; +import got, { OptionsOfTextResponseBody, Headers } from 'got'; + +export default class Microsoft_graph implements QueryService { + authUrl(source_options: SourceOptions) { + const { clientId, clientSecret, scopes, tenantId, redirectUri } = this.getOAuthCredentials(source_options); + + if (!clientId) { + throw new Error('Client Id is required'); + } + if (!clientSecret) { + throw new Error('Client Secret is required'); + } + if (!scopes) { + throw new Error('Scope is required'); + } + if (!tenantId) { + throw new Error('Tenant is required'); + } + + const authState = Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15); + + const params = new URLSearchParams({ + client_id: clientId, + response_type: 'code', + redirect_uri: redirectUri, + response_mode: 'query', + scope: scopes, + state: authState, + }); + + const baseUrl = `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/authorize`; + + return `${baseUrl}?${params.toString()}`; + } + + async accessDetailsFrom(authCode: string, source_options): Promise { + const { clientId, clientSecret, tenantId, scopes, redirectUri } = this.getOAuthCredentials(source_options); + const tokenEndpoint = `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/token`; + + const tokenRequestBody = { + grant_type: 'authorization_code', + code: authCode, + redirect_uri: redirectUri, + client_id: clientId, + client_secret: clientSecret, + scope: scopes, + }; + + const headers = { + 'Content-Type': 'application/x-www-form-urlencoded', + }; + + try { + const response = await got(tokenEndpoint, { + method: 'post', + headers, + form: tokenRequestBody, + }); + + const tokenData = JSON.parse(response.body); + const authDetails = []; + + if (tokenData.access_token) { + authDetails.push(['access_token', tokenData.access_token]); + } + if (tokenData.refresh_token) { + authDetails.push(['refresh_token', tokenData.refresh_token]); + } + if (tokenData.expires_in) { + authDetails.push(['expires_in', tokenData.expires_in.toString()]); + } + if (tokenData.token_type) { + authDetails.push(['token_type', tokenData.token_type]); + } + if (tokenData.scope) { + authDetails.push(['scope', tokenData.scope]); + } + + return authDetails; + } catch (error) { + throw new QueryError('Authorization Error', error.message, { error: error }); + } + } + + async run( + sourceOptions: any, + queryOptions: any, + dataSourceId: string, + dataSourceUpdatedAt: string, + context?: { user?: User; app?: App } + ): Promise { + let result = {}; + if (sourceOptions['oauth_type'] === 'tooljet_app') { + sourceOptions['client_id'] = process.env.MICROSOFT_CLIENT_ID; + sourceOptions['client_secret'] = process.env.MICROSOFT_CLIENT_SECRET; + } + const operation = queryOptions.operation; + const accessToken = sourceOptions['access_token']; + const baseUrl = 'https://graph.microsoft.com/v1.0'; + const path = queryOptions['path']; + let url = `${baseUrl}${path}`; + const pathParams = queryOptions['params']['path']; + const queryParams = queryOptions['params']['query']; + const bodyParams = queryOptions['params']['request']; + + for (const param of Object.keys(pathParams)) { + url = url.replace(`{${param}}`, pathParams[param]); + } + + let requestOptions; + if (sourceOptions['multiple_auth_enabled']) { + const customHeaders = { 'tj-x-forwarded-for': '::1' }; + const newSourcOptions = this.constructSourceOptions(sourceOptions); + const authValidatedRequestOptions = this.convertQueryOptions(queryOptions, customHeaders); + + const _requestOptions = await validateAndSetRequestOptionsBasedOnAuthType( + newSourcOptions, + context, + authValidatedRequestOptions as any, + { kind: 'microsoft_graph' } + ); + if (_requestOptions.status === 'needs_oauth') return _requestOptions; + requestOptions = _requestOptions.data as OptionsOfTextResponseBody; + } else { + requestOptions = + operation === 'get' || operation === 'delete' + ? { + method: operation, + headers: this.authHeader(accessToken), + searchParams: queryParams, + } + : { + method: operation, + headers: this.authHeader(accessToken), + json: bodyParams, + searchParams: queryParams, + }; + } + try { + const response = await got(url, requestOptions); + if (response && response.body) { + try { + result = JSON.parse(response.body); + } catch (parseError) { + result = response.body; + } + } else { + result = 'Query Success'; + } + } catch (error) { + if (error.response && error.response.body) { + try { + result = JSON.parse(error.response.body); + } catch (parseError) { + result = error.response.body; + } + const message = result?.['error']?.['message']; + throw new QueryError('Query could not be completed', message, result || {}); + } else { + const errorMessage = error?.message === 'Query could not be completed' ? error?.description : error?.message; + throw new QueryError('Query could not be completed', errorMessage, error || {}); + } + } + return { + status: 'ok', + data: result, + }; + } + + authHeader(token: string): Headers { + return { + Authorization: `Bearer ${token}`, + 'Content-Type': 'application/json', + }; + } + + private constructSourceOptions(sourceOptions) { + const baseUrl = 'https://graph.microsoft.com/v1.0'; + const tenantId = sourceOptions['tenant_id'] || 'common'; + const accessTokenUrl = sourceOptions['access_token_url']; + const authUrl = `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/authorize`; + const scope = 'https://graph.microsoft.com/.default'; + + const addSourceOptions = { + url: baseUrl, + auth_url: authUrl, + add_token_to: 'header', + header_prefix: 'Bearer ', + access_token_url: accessTokenUrl, + audience: '', + username: '', + password: '', + bearer_token: '', + client_auth: 'header', + headers: [ + ['', ''], + ['tj-x-forwarded-for', '::1'], + ], + custom_query_params: [['', '']], + custom_auth_params: [['', '']], + access_token_custom_headers: [['', '']], + ssl_certificate: 'none', + retry_network_errors: true, + scopes: this.encodeOAuthScope(scope), + }; + + const newSourceOptions = { + ...sourceOptions, + ...addSourceOptions, + }; + + return newSourceOptions; + } + + private encodeOAuthScope(scope: string): string { + return encodeURIComponent(scope); + } + + private convertQueryOptions(queryOptions: any, customHeaders?: Record): any { + // Extract operation and params + const { operation, params } = queryOptions; + + // Start building the result + const result: ConvertedFormat = { + method: operation.toLowerCase(), + headers: customHeaders || {}, + }; + + // Convert query params to URLSearchParams if they exist + if (params.query && Object.keys(params.query).length > 0) { + const urlParams = new URLSearchParams(); + + Object.entries(params.query).forEach(([key, value]) => { + if (value !== undefined && value !== null) { + if (Array.isArray(value)) { + value.forEach((v) => urlParams.append(key, String(v))); + } else { + urlParams.append(key, String(value)); + } + } + }); + + result.searchParams = urlParams; + } + + if (!['get', 'delete'].includes(result.method) && params.request) { + result.json = params.request; + } + + return result; + } + + private normalizeSourceOptions(source_options: any): Record { + if (!Array.isArray(source_options)) { + return source_options; + } + + const normalized = {}; + source_options.forEach((item) => { + normalized[item.key] = item.value; + }); + return normalized; + } + + private getOptionValue(option: any): any { + if (option?.value !== undefined) { + return option.value; + } + return option; + } + + getOAuthCredentials(source_options: any) { + const options = this.normalizeSourceOptions(source_options); + const oauthType = this.getOptionValue(options.oauth_type); + let clientId = this.getOptionValue(options.client_id); + let clientSecret = this.getOptionValue(options.client_secret); + const tenantId = this.getOptionValue(options.tenant_id); + const accessTokenUrl = this.getOptionValue(options.access_token_url); + const scopes = this.getOptionValue(options.scopes); + + if (oauthType === 'tooljet_app') { + clientId = process.env.MICROSOFT_CLIENT_ID; + clientSecret = process.env.MICROSOFT_CLIENT_SECRET; + } + + const host = process.env.TOOLJET_HOST; + const subpath = process.env.SUB_PATH; + const fullUrl = `${host}${subpath ? subpath : '/'}`; + const redirectUri = `${fullUrl}oauth2/authorize`; + + return { clientId, clientSecret, tenantId, accessTokenUrl, scopes, redirectUri }; + } +} diff --git a/marketplace/plugins/microsoft_graph/lib/manifest.json b/marketplace/plugins/microsoft_graph/lib/manifest.json new file mode 100644 index 0000000000..2d5052895c --- /dev/null +++ b/marketplace/plugins/microsoft_graph/lib/manifest.json @@ -0,0 +1,142 @@ + +{ + "$schema": "https://raw.githubusercontent.com/ToolJet/ToolJet/develop/plugins/schemas/manifest.schema.json", + "title": "Microsoft Graph", + "description": "Plugin to use Microsoft 365 services (Outlook, Calendars, OneDrive etc)", + "type": "api", + "source": { + "name": "Microsoft Graph", + "kind": "microsoft_graph", + "exposedVariables": { + "isLoading": false, + "data": {}, + "rawData": {} + }, + "options": { + "tenant_id": { + "type": "string" + }, + "access_token_url": { + "type": "string" + }, + "client_secret": { + "type": "string", + "encrypted": true + } + }, + "customTesting": true + }, + "defaults": { + "username": { + "value": "" + }, + "account": { + "value": "" + }, + "password": { + "value": "" + }, + "database": { + "value": "" + }, + "schema": { + "value": "" + }, + "warehouse": { + "value": "" + }, + "role": { + "value": "" + }, + "client_id": { + "value": "" + }, + "client_secret": { + "value": "" + }, + "tenant_id": { + "value": "" + }, + "access_token_url": { + "value": "https://login.microsoftonline.com/{{tenant_id}}/oauth2/v2.0/token" + }, + "redirect_url": { + "value": "" + }, + "auth_url": { + "value": "" + }, + "allowed_auth_types": { + "value": "oauth2" + }, + "auth_type": { + "value": "oauth2" + }, + "grant_type": { + "value": "authorization_code" + }, + "scopes": { + "value": "https://graph.microsoft.com/.default" + }, + "access_token_custom_headers": { + "value": [ + ["Content-Type", "application/x-www-form-urlencoded"] + ] + }, + "oauth_type": { + "value": "custom_app" + } + }, + "properties": { + "tenant_id": { + "label": "Tenant", + "key": "tenant_id", + "type": "text", + "description": "Enter tenant" + }, + "access_token_url": { + "label": "Access token URL", + "key": "access_token_url", + "type": "text", + "description": "Enter access token url", + "helpText": "Replace < {{tenant_id}} > with the actual tenent value" + }, + "oauth": { + "key": "oauth", + "type": "react-component-oauth", + "description": "A component for Microsoft Graph", + "oauth_configs": { + "allowed_field_groups": { + "authorization_code": [ + "client_id", + "client_secret" + ] + }, + "allowed_auth_types": [ + "oauth2" + ], + "oauthTypes": { + "required": true, + "default_value": "custom_app", + "editions": { + "ce": [ + "custom_app" + ], + "ee": [ + "custom_app" + ], + "cloud": [ + "custom_app", + "tooljet_app" + ] + } + }, + "allowed_grant_types": [ + "authorization_code" + ], + "allowed_scope_field": false + } + } + }, + "required": [] +} \ No newline at end of file diff --git a/marketplace/plugins/microsoft_graph/lib/operations.json b/marketplace/plugins/microsoft_graph/lib/operations.json new file mode 100644 index 0000000000..48f2975ea8 --- /dev/null +++ b/marketplace/plugins/microsoft_graph/lib/operations.json @@ -0,0 +1,23 @@ + +{ + "$schema": "https://raw.githubusercontent.com/ToolJet/ToolJet/develop/plugins/schemas/operations.schema.json", + "title": "Microsoft Graph datasource", + "description": "A schema defining Microsoft_graph datasource", + "type": "api", + "defaults": {}, + "properties": { + "operation": { + "label": "", + "key": "ms_graph_operation", + "type": "react-component-api-endpoint", + "description": "Single select dropdown for operation", + "specUrl": { + "Outlook": "https://raw.githubusercontent.com/adishM98/base-repo-testing/refs/heads/main/outlook.json", + "Calendar": "https://raw.githubusercontent.com/adishM98/base-repo-testing/refs/heads/main/calendar.json", + "Users": "https://raw.githubusercontent.com/adishM98/base-repo-testing/refs/heads/main/users.json", + "Teams": "https://raw.githubusercontent.com/adishM98/base-repo-testing/refs/heads/main/teams.yaml", + "OneDrive": "https://raw.githubusercontent.com/adishM98/base-repo-testing/refs/heads/main/onedrive.json" + } + } + } +} \ No newline at end of file diff --git a/marketplace/plugins/microsoft_graph/lib/types.ts b/marketplace/plugins/microsoft_graph/lib/types.ts new file mode 100644 index 0000000000..e9758be004 --- /dev/null +++ b/marketplace/plugins/microsoft_graph/lib/types.ts @@ -0,0 +1,30 @@ +import { OptionsOfTextResponseBody } from 'got'; + +export type SourceOptions = { + client_id: OptionData; + client_secret: OptionData; + scopes: OptionData; + tenant_id: OptionData; +}; +export type QueryOptions = { + operation: string; +}; + +type OptionData = { + value: string; + encypted: boolean; +}; + +export type ConvertedFormat = { + method: string; + headers: Record; + searchParams?: URLSearchParams; + json?: Record; +}; + +export type QueryResult = { + status: 'ok' | 'failed' | 'needs_oauth'; + errorMessage?: string; + data: Array | object | OptionsOfTextResponseBody; + metadata?: Array | object; +}; diff --git a/marketplace/plugins/microsoft_graph/package.json b/marketplace/plugins/microsoft_graph/package.json new file mode 100644 index 0000000000..c50334d125 --- /dev/null +++ b/marketplace/plugins/microsoft_graph/package.json @@ -0,0 +1,27 @@ +{ + "name": "@tooljet-marketplace/microsoft_graph", + "version": "1.0.0", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "directories": { + "lib": "lib", + "test": "__tests__" + }, + "files": [ + "lib" + ], + "scripts": { + "test": "echo \"Error: run tests from root\" && exit 1", + "build": "ncc build lib/index.ts -o dist", + "watch": "ncc build lib/index.ts -o dist --watch" + }, + "homepage": "https://github.com/tooljet/tooljet#readme", + "dependencies": { + "@tooljet-marketplace/common": "^1.0.0", + "got": "^14.4.7" + }, + "devDependencies": { + "@vercel/ncc": "^0.34.0", + "typescript": "^4.7.4" + } +} diff --git a/marketplace/plugins/microsoft_graph/tsconfig.json b/marketplace/plugins/microsoft_graph/tsconfig.json new file mode 100644 index 0000000000..a18a801b14 --- /dev/null +++ b/marketplace/plugins/microsoft_graph/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "dist", + "rootDir": "lib" + }, + "exclude": [ + "node_modules", + "dist" + ] +} \ No newline at end of file diff --git a/marketplace/plugins/salesforce/lib/manifest.json b/marketplace/plugins/salesforce/lib/manifest.json index 4f8c2441f2..60074bcace 100644 --- a/marketplace/plugins/salesforce/lib/manifest.json +++ b/marketplace/plugins/salesforce/lib/manifest.json @@ -53,6 +53,9 @@ }, "scopes": { "value": "full" + }, + "oauth_type": { + "value": "custom_app" } }, "properties": { diff --git a/plugins/packages/common/lib/oauth.ts b/plugins/packages/common/lib/oauth.ts index 8fa06cce96..04ebace359 100644 --- a/plugins/packages/common/lib/oauth.ts +++ b/plugins/packages/common/lib/oauth.ts @@ -207,6 +207,7 @@ function fetchEnvVariables(pluginKind, keyAppend) { const dataSourcePrefix = { googlecalendar: 'GOOGLE', snowflake: 'SNOWFLAKE', + microsoft_graph: 'MICROSOFT', }; const key = dataSourcePrefix[pluginKind] + '_' + keyAppend; return key; diff --git a/server/src/assets/marketplace/plugins.json b/server/src/assets/marketplace/plugins.json index 3407b77ca5..50082bb95f 100644 --- a/server/src/assets/marketplace/plugins.json +++ b/server/src/assets/marketplace/plugins.json @@ -245,5 +245,13 @@ "id": "googlecalendar", "author": "Tooljet", "timestamp": "Sat, 12 Jul 2025 14:33:45 GMT" + }, + { + "name": "Microsoft Graph", + "description": "Plugin to use Microsoft 365 services (Outlook, Calendars, OneDrive etc)", + "version": "1.0.0", + "id": "microsoft_graph", + "author": "Tooljet", + "timestamp": "Thu, 31 Jul 2025 08:56:30 GMT" } ] \ No newline at end of file diff --git a/server/src/modules/data-sources/util.service.ts b/server/src/modules/data-sources/util.service.ts index c814696966..0e5960aac7 100644 --- a/server/src/modules/data-sources/util.service.ts +++ b/server/src/modules/data-sources/util.service.ts @@ -530,7 +530,9 @@ export class DataSourcesUtilService implements IDataSourcesUtilService { // Auth flow starts from datasource config page if ( !isMultiAuthEnabled && - ['googlesheets', 'slack', 'zendesk', 'salesforce', 'googlecalendar', 'snowflake'].includes(dataSource.kind) + ['googlesheets', 'slack', 'zendesk', 'salesforce', 'googlecalendar', 'snowflake', 'microsoft_graph'].includes( + dataSource.kind + ) ) { tokenOptions = await this.fetchAPITokenFromPlugins(dataSource, code, sourceOptions); } @@ -642,6 +644,7 @@ export class DataSourcesUtilService implements IDataSourcesUtilService { const dataSourcePrefix = { googlecalendar: 'GOOGLE', snowflake: 'SNOWFLAKE', + microsoft_graph: 'MICROSFT', }; const key = dataSourcePrefix[pluginKind] + '_' + keyAppend; return key;