diff --git a/src/modules/integration-picker/components/IntegrationFields.tsx b/src/modules/integration-picker/components/IntegrationFields.tsx index c625f73..bb66e4c 100644 --- a/src/modules/integration-picker/components/IntegrationFields.tsx +++ b/src/modules/integration-picker/components/IntegrationFields.tsx @@ -55,12 +55,25 @@ export const IntegrationForm: React.FC = ({ fields, onCh })); }; + const errorJson = () => { + if (!error) { + return null; + } + try { + return ; + } catch (_e) { + if (error?.provider_response && error?.provider_response.length > 0) { + return ; + } + return null; + } + }; return ( {error && ( - + {errorJson()} )} diff --git a/src/modules/integration-picker/components/IntegrationList.tsx b/src/modules/integration-picker/components/IntegrationList.tsx index 0860a0b..c63638d 100644 --- a/src/modules/integration-picker/components/IntegrationList.tsx +++ b/src/modules/integration-picker/components/IntegrationList.tsx @@ -14,6 +14,7 @@ import { } from '@stackone/malachite'; import { useCallback, useMemo, useState } from 'react'; import { CATEGORIES_WITH_LABELS } from '../../../shared/categories'; +import { isFalconVersion } from '../../../shared/utils/utils'; import { Integration } from '../types'; interface IntegrationRowProps { @@ -43,6 +44,9 @@ const IntegrationRow: React.FC = ({ integration }) => { /> {integration.name ?? 'N/A'} + {isFalconVersion(integration.version) && ( + {integration.version} + )} { CATEGORIES_WITH_LABELS.find((category) => category.value === integration.type) @@ -123,7 +127,7 @@ export const IntegrationList: React.FC<{ ({ - key: integration.provider, + key: `${integration.provider}@${integration.version}`, children: , onClick: () => onSelect(integration), }))} diff --git a/src/modules/integration-picker/hooks/useIntegrationPicker.ts b/src/modules/integration-picker/hooks/useIntegrationPicker.ts index 99a593c..1b4c4aa 100644 --- a/src/modules/integration-picker/hooks/useIntegrationPicker.ts +++ b/src/modules/integration-picker/hooks/useIntegrationPicker.ts @@ -1,14 +1,21 @@ import { evaluate } from '@stackone/expressions'; import { useQuery } from '@tanstack/react-query'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { isFalconVersion } from '../../../shared/utils/utils'; import { connectAccount, getAccountData, - getConnectorConfig, + getFalconConnectorConfig, getHubData, + getLegacyConnectorConfig, updateAccount, } from '../queries'; -import { ConnectorConfigField, Integration } from '../types'; +import { + ConnectorConfigField, + Integration, + isFalconConnectorConfig, + isLegacyConnectorConfig, +} from '../types'; const DUMMY_VALUE = 'totally-fake-value'; @@ -137,10 +144,26 @@ export const useIntegrationPicker = ({ queryKey: ['connectorData', selectedIntegration?.provider, accountData?.provider], queryFn: async () => { if (selectedIntegration) { - return getConnectorConfig(baseUrl, token, selectedIntegration.provider); + if (isFalconVersion(selectedIntegration.version)) { + return getFalconConnectorConfig( + baseUrl, + token, + `${selectedIntegration.provider}@${selectedIntegration.version}`, + ); + } else { + return getLegacyConnectorConfig(baseUrl, token, selectedIntegration.provider); + } } if (accountData) { - return getConnectorConfig(baseUrl, token, accountData.provider); + if (isFalconVersion(accountData.version)) { + return getFalconConnectorConfig( + baseUrl, + token, + `${accountData.provider}@${accountData.version}`, + ); + } else { + return getLegacyConnectorConfig(baseUrl, token, accountData.provider); + } } return null; }, @@ -153,6 +176,79 @@ export const useIntegrationPicker = ({ return { fields }; } + if (isFalconConnectorConfig(connectorData.config)) { + const fieldsWithPrefilledValues: ConnectorConfigField[] = + connectorData.config.configFields + .map((field) => { + const setupValue = accountData?.setupInformation?.[field.key]; + + if (accountData && (field.secret || field.type === 'password')) { + return { + ...field, + key: field.key, + value: DUMMY_VALUE, + }; + } + + if (field.key === 'external-trigger-token') { + return { + ...field, + key: field.key, + value: hubData?.external_trigger_token, + }; + } + + const evaluationContext = { + ...formData, + ...accountData?.setupInformation, + external_trigger_token: hubData?.external_trigger_token, + hub_settings: connectorData.hub_settings, + }; + + if (field.condition) { + const evaluated = evaluate(field.condition, evaluationContext); + + const shouldShow = evaluated != null && evaluated !== 'false'; + + if (!shouldShow) { + return; + } + } + + if (!field.value) { + return { + ...field, + key: field.key, + }; + } + + const valueToEvaluate = setupValue !== undefined ? setupValue : field.value; + let evaluatedValue = evaluate( + valueToEvaluate?.toString(), + evaluationContext, + ); + + if (typeof evaluatedValue === 'object' && evaluatedValue !== null) { + evaluatedValue = JSON.stringify(evaluatedValue); + } + + return { + ...field, + key: field.key, + value: evaluatedValue as string | number | undefined, + }; + }) + .filter((value) => value != null); + + return { + fields: fieldsWithPrefilledValues, + guide: { + supportLink: connectorData.config.support.link, + description: connectorData.config.support.description, + }, + }; + } + const authConfig = connectorData.config.authentication?.[selectedIntegration.authentication_config_key]; const authConfigForEnvironment = authConfig?.[selectedIntegration.environment]; @@ -228,9 +324,12 @@ export const useIntegrationPicker = ({ if (!connectorData || !selectedIntegration) { return null; } - return connectorData.config.authentication?.[ - selectedIntegration.authentication_config_key - ]?.[selectedIntegration.environment]; + if (isLegacyConnectorConfig(connectorData.config)) { + return connectorData.config.authentication?.[ + selectedIntegration.authentication_config_key + ]?.[selectedIntegration.environment]; + } + return connectorData.config; }, [connectorData, selectedIntegration]); const handleConnect = useCallback(async () => { @@ -318,7 +417,13 @@ export const useIntegrationPicker = ({ cleanedFormData, ); } else { - await connectAccount(baseUrl, token, selectedIntegration.provider, cleanedFormData); + await connectAccount( + baseUrl, + token, + selectedIntegration.provider, + selectedIntegration.version, + cleanedFormData, + ); } setConnectionState({ loading: false, success: true }); diff --git a/src/modules/integration-picker/queries.ts b/src/modules/integration-picker/queries.ts index b42df8f..504e46c 100644 --- a/src/modules/integration-picker/queries.ts +++ b/src/modules/integration-picker/queries.ts @@ -18,9 +18,27 @@ export const getHubData = async (token: string, baseUrl: string, provider?: stri }); }; -export const getConnectorConfig = async (baseUrl: string, token: string, connectorKey: string) => { +export const getLegacyConnectorConfig = async ( + baseUrl: string, + token: string, + connectorKey: string, +) => { return await getRequest({ - url: `${baseUrl}/hub/connectors/${connectorKey}`, + url: `${baseUrl}/hub/connectors/legacy/${connectorKey}`, + headers: { + 'Content-Type': 'application/json', + 'x-hub-session-token': token, + }, + }); +}; + +export const getFalconConnectorConfig = async ( + baseUrl: string, + token: string, + connectorKey: string, +) => { + return await getRequest({ + url: `${baseUrl}/hub/connectors/falcon/${encodeURIComponent(connectorKey)}`, headers: { 'Content-Type': 'application/json', 'x-hub-session-token': token, @@ -32,6 +50,7 @@ export const connectAccount = async ( baseUrl: string, token: string, provider: string, + version: string, credentials: Record, ) => { return await postRequest({ @@ -41,7 +60,7 @@ export const connectAccount = async ( 'x-hub-session-token': token, }, body: { - provider, + provider: `${provider}@${version}`, credentials, }, }); diff --git a/src/modules/integration-picker/types.ts b/src/modules/integration-picker/types.ts index 9f5cf98..322a5be 100644 --- a/src/modules/integration-picker/types.ts +++ b/src/modules/integration-picker/types.ts @@ -38,7 +38,7 @@ export interface ConnectorConfigField { }; } -export interface ConnectorConfig { +export interface LegacyConnectorConfig { key: string; name: string; authentication: { @@ -55,6 +55,19 @@ export interface ConnectorConfig { }; } +export interface FalconConnectorConfig { + key: string; + name: string; + type: 'oauth2' | 'custom'; + configFields: Array; + support: { + link: string; + description: string; + }; +} + +export type ConnectorConfig = LegacyConnectorConfig | FalconConnectorConfig; + export interface HubConnectorConfig { config: ConnectorConfig; hub_settings: { @@ -63,8 +76,18 @@ export interface HubConnectorConfig { }; } +// Type guards for safe type checking - using structural properties instead of explicit type field +export function isLegacyConnectorConfig(config: ConnectorConfig): config is LegacyConnectorConfig { + return 'authentication' in config && !('configFields' in config); +} + +export function isFalconConnectorConfig(config: ConnectorConfig): config is FalconConnectorConfig { + return 'configFields' in config && !('authentication' in config); +} + export interface AccountData { account_id: string; provider: string; setupInformation: Record; + version: string; } diff --git a/src/shared/utils/utils.ts b/src/shared/utils/utils.ts new file mode 100644 index 0000000..33f87d5 --- /dev/null +++ b/src/shared/utils/utils.ts @@ -0,0 +1,3 @@ +export const isFalconVersion = (version: string) => { + return version != null && version != '1' && version != '2'; +};