diff --git a/packages/cli/lib/index.ts b/packages/cli/lib/index.ts index 8e116f7803..f3de67d384 100644 --- a/packages/cli/lib/index.ts +++ b/packages/cli/lib/index.ts @@ -118,7 +118,7 @@ program ) .option( '--integration-id [integrationId]', - 'Optional: The integration id to use for the dryrun. If not provided, the integration id will be retrieved from the nango.yaml file. This is useful using nested directories and script name are repeated' + 'Optional: The integration id to use for the dryrun. If not provided, the integration id will be retrieved from the nango.yaml file. This is useful using nested directories and script names are repeated' ) .action(async function (this: Command, sync: string, connectionId: string) { const { autoConfirm, debug, e: environment, integrationId } = this.opts(); diff --git a/packages/server/lib/controllers/environment.controller.ts b/packages/server/lib/controllers/environment.controller.ts index 666cb2404c..95178551eb 100644 --- a/packages/server/lib/controllers/environment.controller.ts +++ b/packages/server/lib/controllers/environment.controller.ts @@ -80,7 +80,7 @@ class EnvironmentController { const environmentVariables = await environmentService.getEnvironmentVariables(environment.id); res.status(200).send({ - account: { ...environment, env_variables: environmentVariables, host: baseUrl, uuid: account.uuid, email: user.email } + environment: { ...environment, env_variables: environmentVariables, host: baseUrl, uuid: account.uuid, email: user.email } }); } catch (err) { next(err); @@ -171,6 +171,22 @@ class EnvironmentController { } } + async updateSecondaryWebhookURL(req: Request, res: Response>, next: NextFunction) { + try { + if (!req.body) { + errorManager.errRes(res, 'missing_body'); + return; + } + + const { environment } = res.locals; + + await environmentService.editSecondaryWebhookUrl(req.body['webhook_secondary_url'], environment.id); + res.status(200).send(); + } catch (err) { + next(err); + } + } + async updateAlwaysSendWebhook(req: Request, res: Response>, next: NextFunction) { try { if (!req.body) { diff --git a/packages/server/lib/routes.ts b/packages/server/lib/routes.ts index 712a5dd7b9..f2c104f3d9 100644 --- a/packages/server/lib/routes.ts +++ b/packages/server/lib/routes.ts @@ -144,6 +144,7 @@ web.route('/api/v1/account/admin/switch').post(webAuth, accountController.switch web.route('/api/v1/environment').get(webAuth, environmentController.getEnvironment.bind(environmentController)); web.route('/api/v1/environment/callback').post(webAuth, environmentController.updateCallback.bind(environmentController)); web.route('/api/v1/environment/webhook').post(webAuth, environmentController.updateWebhookURL.bind(environmentController)); +web.route('/api/v1/environment/webhook-secondary').post(webAuth, environmentController.updateSecondaryWebhookURL.bind(environmentController)); web.route('/api/v1/environment/hmac').get(webAuth, environmentController.getHmacDigest.bind(environmentController)); web.route('/api/v1/environment/hmac-enabled').post(webAuth, environmentController.updateHmacEnabled.bind(environmentController)); web.route('/api/v1/environment/webhook-send').post(webAuth, environmentController.updateAlwaysSendWebhook.bind(environmentController)); diff --git a/packages/shared/lib/db/migrations/20240509171724_add_webhook_secondary.cjs b/packages/shared/lib/db/migrations/20240509171724_add_webhook_secondary.cjs new file mode 100644 index 0000000000..24c8553495 --- /dev/null +++ b/packages/shared/lib/db/migrations/20240509171724_add_webhook_secondary.cjs @@ -0,0 +1,11 @@ +exports.up = async function (knex, _) { + return knex.schema.alterTable('_nango_environments', function (table) { + table.text('webhook_url_secondary'); + }); +}; + +exports.down = function (knex, _) { + return knex.schema.alterTable('_nango_environments', function (table) { + table.dropColumn('webhook_url_secondary'); + }); +}; diff --git a/packages/shared/lib/models/Environment.ts b/packages/shared/lib/models/Environment.ts index bed49e708e..07c6d13ddc 100644 --- a/packages/shared/lib/models/Environment.ts +++ b/packages/shared/lib/models/Environment.ts @@ -12,6 +12,7 @@ export interface Environment extends Timestamps { secret_key_hashed?: string | null; callback_url: string | null; webhook_url: string | null; + webhook_url_secondary: string | null; websockets_path?: string | null; hmac_enabled: boolean; always_send_webhook: boolean; diff --git a/packages/shared/lib/services/environment.service.integration.test.ts b/packages/shared/lib/services/environment.service.integration.test.ts index 802b860a86..2f3796bf7c 100644 --- a/packages/shared/lib/services/environment.service.integration.test.ts +++ b/packages/shared/lib/services/environment.service.integration.test.ts @@ -39,7 +39,8 @@ describe('Environment service', () => { slack_notifications: false, updated_at: expect.toBeIsoDate(), uuid: expect.any(String), - webhook_url: null + webhook_url: null, + webhook_url_secondary: null }); expect(env.secret_key).not.toEqual(env.secret_key_hashed); diff --git a/packages/shared/lib/services/environment.service.ts b/packages/shared/lib/services/environment.service.ts index 23d746921f..4e73c1583c 100644 --- a/packages/shared/lib/services/environment.service.ts +++ b/packages/shared/lib/services/environment.service.ts @@ -289,6 +289,10 @@ class EnvironmentService { return db.knex.from(TABLE).where({ id }).update({ webhook_url: webhookUrl }, ['id']); } + async editSecondaryWebhookUrl(webhookUrl: string, id: number): Promise { + return db.knex.from(TABLE).where({ id }).update({ webhook_url_secondary: webhookUrl }, ['id']); + } + async editHmacEnabled(hmacEnabled: boolean, id: number): Promise { return db.knex.from(TABLE).where({ id }).update({ hmac_enabled: hmacEnabled }, ['id']); } diff --git a/packages/shared/lib/services/notification/webhook.service.ts b/packages/shared/lib/services/notification/webhook.service.ts index 1addb1fee4..97e4fb8efd 100644 --- a/packages/shared/lib/services/notification/webhook.service.ts +++ b/packages/shared/lib/services/notification/webhook.service.ts @@ -100,8 +100,9 @@ class WebhookService { ): Promise<{ send: boolean; environmentInfo: Environment | null }> { const environmentInfo = await environmentService.getById(environment_id); const hasWebhookUrl = environmentInfo?.webhook_url; + const hasSecondaryWebhookUrl = environmentInfo?.webhook_url_secondary; - if (options?.forward && hasWebhookUrl) { + if ((options?.forward && hasWebhookUrl) || hasSecondaryWebhookUrl) { return { send: true, environmentInfo }; } @@ -128,11 +129,11 @@ class WebhookService { ) { const { environmentInfo } = await this.shouldSendWebhook(nangoConnection.environment_id); - if (!environmentInfo || !environmentInfo.webhook_url) { + if (!environmentInfo || (!environmentInfo.webhook_url && !environmentInfo.webhook_url_secondary)) { return; } - const { webhook_url: webhookUrl, always_send_webhook: alwaysSendWebhook } = environmentInfo; + const { webhook_url: webhookUrl, webhook_url_secondary: webhookUrlSecondary, always_send_webhook: alwaysSendWebhook } = environmentInfo; const noChanges = responseResults.added === 0 && responseResults.updated === 0 && (responseResults.deleted === 0 || responseResults.deleted === undefined); @@ -179,48 +180,60 @@ class WebhookService { ? 'with no data changes as per your environment settings.' : `with the following data: ${JSON.stringify(body, null, 2)}`; - try { - const headers = this.getSignatureHeader(environmentInfo.secret_key, body); + const webhookUrls: { url: string; type: string }[] = [ + { url: webhookUrl, type: 'webhookUrl' }, + { url: webhookUrlSecondary, type: 'webhookUrlSecondary' } + ].filter((webhook) => webhook.url) as { url: string; type: string }[]; - const response = await backOff( - () => { - return axios.post(webhookUrl, body, { headers }); - }, - { numOfAttempts: RETRY_ATTEMPTS, retry: this.retry.bind(this, activityLogId, environment_id, logCtx) } - ); + for (const webhookUrl of webhookUrls) { + const { url, type } = webhookUrl; + try { + const headers = this.getSignatureHeader(environmentInfo.secret_key, body); + + const response = await backOff( + () => { + return axios.post(url, body, { headers }); + }, + { numOfAttempts: RETRY_ATTEMPTS, retry: this.retry.bind(this, activityLogId, environment_id, logCtx) } + ); + + if (response.status >= 200 && response.status < 300) { + await createActivityLogMessage({ + level: 'info', + environment_id, + activity_log_id: activityLogId, + content: `Sync webhook sent successfully ${type === 'webhookUrlSecondary' ? 'to the secondary webhook URL' : ''} and received with a ${response.status} response code to ${url} ${endingMessage}`, + timestamp: Date.now() + }); + await logCtx.info( + `Sync webhook sent successfully ${type === 'webhookUrlSecondary' ? 'to the secondary webhook URL' : ''} and received with a ${response.status} response code to ${url} ${endingMessage}` + ); + } else { + await createActivityLogMessage({ + level: 'error', + environment_id, + activity_log_id: activityLogId, + content: `Sync webhook sent successfully ${type === 'webhookUrlSecondary' ? 'to the secondary webhook URL' : ''} to ${url} ${endingMessage} but received a ${response.status} response code. Please send a 2xx on successful receipt.`, + timestamp: Date.now() + }); + await logCtx.error( + `Sync webhook sent successfully ${type === 'webhookUrlSecondary' ? 'to the secondary webhook URL' : ''} to ${url} ${endingMessage} but received a ${response.status} response code. Please send a 2xx on successful receipt.` + ); + } + } catch (e) { + const errorMessage = stringifyError(e, { pretty: true }); - if (response.status >= 200 && response.status < 300) { - await createActivityLogMessage({ - level: 'info', - environment_id, - activity_log_id: activityLogId, - content: `Sync webhook sent successfully and received with a ${response.status} response code to ${webhookUrl} ${endingMessage}`, - timestamp: Date.now() - }); - await logCtx.info(`Sync webhook sent successfully and received with a ${response.status} response code to ${webhookUrl} ${endingMessage}`); - } else { await createActivityLogMessage({ level: 'error', environment_id, activity_log_id: activityLogId, - content: `Sync webhook sent successfully to ${webhookUrl} ${endingMessage} but received a ${response.status} response code. Please send a 2xx on successful receipt.`, + content: `Sync webhook failed to send ${type === 'webhookUrlSecondary' ? 'to the secondary webhook URL' : ''} to ${url}. The error was: ${errorMessage}`, timestamp: Date.now() }); - await logCtx.error( - `Sync webhook sent successfully to ${webhookUrl} ${endingMessage} but received a ${response.status} response code. Please send a 2xx on successful receipt.` - ); + await logCtx.error(`Sync webhook failed to send ${type === 'webhookUrlSecondary' ? 'to the secondary webhook URL' : ''} to ${url}`, { + error: e + }); } - } catch (e) { - const errorMessage = stringifyError(e, { pretty: true }); - - await createActivityLogMessage({ - level: 'error', - environment_id, - activity_log_id: activityLogId, - content: `Sync webhook failed to send to ${webhookUrl}. The error was: ${errorMessage}`, - timestamp: Date.now() - }); - await logCtx.error(`Sync webhook failed to send to ${webhookUrl}`, { error: e }); } } diff --git a/packages/webapp/src/hooks/useEnvironment.tsx b/packages/webapp/src/hooks/useEnvironment.tsx index 0c02ee8878..b27ad12bd1 100644 --- a/packages/webapp/src/hooks/useEnvironment.tsx +++ b/packages/webapp/src/hooks/useEnvironment.tsx @@ -1,16 +1,16 @@ import useSWR from 'swr'; -import type { Account } from '../types'; +import type { Environment } from '../types'; import { swrFetcher } from '../utils/api'; export function useEnvironment(env: string) { - const { data, error, mutate } = useSWR<{ account: Account }>(`/api/v1/environment?env=${env}`, swrFetcher, {}); + const { data, error, mutate } = useSWR<{ environment: Environment }>(`/api/v1/environment?env=${env}`, swrFetcher, {}); const loading = !data && !error; return { loading, error, - environment: data?.account, + environment: data?.environment, mutate }; } diff --git a/packages/webapp/src/pages/Connection/Syncs.tsx b/packages/webapp/src/pages/Connection/Syncs.tsx index 90c67ee28f..4dfe7804e5 100644 --- a/packages/webapp/src/pages/Connection/Syncs.tsx +++ b/packages/webapp/src/pages/Connection/Syncs.tsx @@ -215,7 +215,11 @@ export default function Syncs({ syncs, connection, reload, loaded, syncLoaded, e className={`flex items-center px-2 py-3 text-[13px] ${syncCommandButtonsDisabled ? '' : 'cursor-pointer'} justify-between border-b border-border-gray`} >
-
{Array.isArray(sync.models) ? sync.models.join(', ') : sync.models}
+ +
+ {Array.isArray(sync.models) ? sync.models.join(', ') : sync.models} +
+
diff --git a/packages/webapp/src/pages/EnvironmentSettings.tsx b/packages/webapp/src/pages/EnvironmentSettings.tsx index 5174c4c29c..093fce30a8 100644 --- a/packages/webapp/src/pages/EnvironmentSettings.tsx +++ b/packages/webapp/src/pages/EnvironmentSettings.tsx @@ -9,6 +9,7 @@ import Nango from '@nangohq/frontend'; import { useEditCallbackUrlAPI, useEditWebhookUrlAPI, + useEditWebhookSecondaryUrlAPI, useEditHmacEnabledAPI, useEditHmacKeyAPI, useEditEnvVariablesAPI, @@ -42,6 +43,9 @@ export const EnvironmentSettings: React.FC = () => { const [callbackEditMode, setCallbackEditMode] = useState(false); const [webhookEditMode, setWebhookEditMode] = useState(false); + const [webhookUrlSecondary, setWebhookUrlSecondary] = useState(''); + const [webhookSecondaryEditMode, setWebhookSecondaryEditMode] = useState(false); + const [slackIsConnected, setSlackIsConnected] = useState(false); const [hmacKey, setHmacKey] = useState(''); @@ -53,6 +57,7 @@ export const EnvironmentSettings: React.FC = () => { const [envVariables, setEnvVariables] = useState<{ id?: number; name: string; value: string }[]>([]); const editCallbackUrlAPI = useEditCallbackUrlAPI(env); const editWebhookUrlAPI = useEditWebhookUrlAPI(env); + const editWebhookSecondaryUrlAPI = useEditWebhookSecondaryUrlAPI(env); const editHmacEnabled = useEditHmacEnabledAPI(env); const editAlwaysSendWebhook = useEditAlwaysSendWebhookAPI(env); const editSendAuthWebhook = useEditSendAuthWebhookAPI(env); @@ -85,6 +90,7 @@ export const EnvironmentSettings: React.FC = () => { setCallbackUrl(environment.callback_url || defaultCallback()); setWebhookUrl(environment.webhook_url || ''); + setWebhookUrlSecondary(environment.webhook_url_secondary || ''); setSendAuthWebhook(environment.send_auth_webhook); setHostUrl(environment.host); setAccountUUID(environment.uuid); @@ -115,7 +121,7 @@ export const EnvironmentSettings: React.FC = () => { } }; - const handleWebhookbackSave = async (e: React.SyntheticEvent) => { + const handleWebhookEditSave = async (e: React.SyntheticEvent) => { e.preventDefault(); const target = e.target as typeof e.target & { @@ -132,6 +138,23 @@ export const EnvironmentSettings: React.FC = () => { } }; + const handleWebhookSecondaryEditSave = async (e: React.SyntheticEvent) => { + e.preventDefault(); + + const target = e.target as typeof e.target & { + webhook_url_secondary: { value: string }; + }; + + const res = await editWebhookSecondaryUrlAPI(target.webhook_url_secondary.value); + + if (res?.status === 200) { + toast.success('Secondary Wehook URL updated!', { position: toast.POSITION.BOTTOM_CENTER }); + setWebhookSecondaryEditMode(false); + setWebhookUrlSecondary(target.webhook_url_secondary.value); + void mutate(); + } + }; + const handleCallbackEdit = () => { setCallbackEditMode(true); }; @@ -707,7 +730,7 @@ export const EnvironmentSettings: React.FC = () => {
{webhookEditMode && ( -
+
{ )}
+
+ {!environment?.webhook_url_secondary && !webhookSecondaryEditMode ? ( + + ) : ( + <> +
+
+ + +
+ {`Be notified when new data is available from Nango (cf. `} + + webhook docs + + {`).`} +
+ + } + > + +
+
+ {webhookSecondaryEditMode && ( + +
+ + + +
+ + )} + {!webhookSecondaryEditMode && ( +
+ + {webhookUrlSecondary || '\u0000'} + + +
+ )} +
+ + )} +
diff --git a/packages/webapp/src/pages/Integration/APIReference.tsx b/packages/webapp/src/pages/Integration/APIReference.tsx index 409f274f6f..832257a96a 100644 --- a/packages/webapp/src/pages/Integration/APIReference.tsx +++ b/packages/webapp/src/pages/Integration/APIReference.tsx @@ -2,13 +2,13 @@ import { Fragment } from 'react'; import type { Tabs, SubTabs, EndpointResponse } from './Show'; import EndpointRow from './components/EndpointRow'; import HelpFooter from './components/HelpFooter'; -import type { IntegrationConfig, Account, Flow } from '../../types'; +import type { IntegrationConfig, Environment, Flow } from '../../types'; interface APIReferenceProps { integration: IntegrationConfig | null; setActiveTab: (tab: Tabs) => void; endpoints: EndpointResponse; - account: Account; + environment: Environment; setSubTab: (tab: SubTabs) => void; setFlow: (flow: Flow) => void; } diff --git a/packages/webapp/src/pages/Integration/AuthSettings.tsx b/packages/webapp/src/pages/Integration/AuthSettings.tsx index 5a2100efef..ac193349d6 100644 --- a/packages/webapp/src/pages/Integration/AuthSettings.tsx +++ b/packages/webapp/src/pages/Integration/AuthSettings.tsx @@ -4,7 +4,7 @@ import { toast } from 'react-toastify'; import { HelpCircle } from '@geist-ui/icons'; import { PencilSquareIcon, XCircleIcon } from '@heroicons/react/24/outline'; import { Tooltip, useModal } from '@geist-ui/core'; -import type { IntegrationConfig, Account } from '../../types'; +import type { IntegrationConfig, Environment } from '../../types'; import { AuthModes } from '../../types'; import { useDeleteIntegrationAPI, useCreateIntegrationAPI, useEditIntegrationAPI, useEditIntegrationNameAPI } from '../../utils/api'; import Info from '../../components/ui/Info'; @@ -20,12 +20,12 @@ import { useSWRConfig } from 'swr'; interface AuthSettingsProps { integration: IntegrationConfig | null; - account: Account; + environment: Environment; } export default function AuthSettings(props: AuthSettingsProps) { const { mutate } = useSWRConfig(); - const { integration, account } = props; + const { integration, environment } = props; const [serverErrorMessage, setServerErrorMessage] = useState(''); const [isTyping, setIsTyping] = useState(false); @@ -279,8 +279,8 @@ export default function AuthSettings(props: AuthSettingsProps) { Callback Url
- {account.callback_url || defaultCallback()} - + {environment.callback_url || defaultCallback()} +
@@ -304,8 +304,8 @@ export default function AuthSettings(props: AuthSettingsProps) { - {account.callback_url.replace('oauth/callback', 'app-auth/connect')} - + {environment.callback_url.replace('oauth/callback', 'app-auth/connect')} + @@ -329,8 +329,8 @@ export default function AuthSettings(props: AuthSettingsProps) {
- {`${account.webhook_receive_url}/${integrationId}`} - + {`${environment.webhook_receive_url}/${integrationId}`} +
{(integration?.auth_mode === AuthModes.App || integration?.auth_mode === AuthModes.Custom) && integration?.webhook_secret && ( diff --git a/packages/webapp/src/pages/Integration/EndpointReference.tsx b/packages/webapp/src/pages/Integration/EndpointReference.tsx index 1f38b8d2af..26bc1e2b3d 100644 --- a/packages/webapp/src/pages/Integration/EndpointReference.tsx +++ b/packages/webapp/src/pages/Integration/EndpointReference.tsx @@ -5,7 +5,7 @@ import Button from '../../components/ui/button/Button'; import CopyButton from '../../components/ui/button/CopyButton'; import Info from '../../components/ui/Info'; import EndpointLabel from './components/EndpointLabel'; -import type { NangoSyncEndpoint, IntegrationConfig, FlowEndpoint, Flow, Account } from '../../types'; +import type { NangoSyncEndpoint, IntegrationConfig, FlowEndpoint, Flow, Environment } from '../../types'; import { nodeSnippet, nodeActionSnippet, curlSnippet } from '../../utils/language-snippets'; import { parseInput, generateResponseModel } from '../../utils/utils'; import { Tabs, SubTabs } from './Show'; @@ -21,7 +21,7 @@ enum Language { } interface EndpointReferenceProps { - account: Account; + environment: Environment; integration: IntegrationConfig; activeFlow: Flow | null; setSubTab: (tab: SubTabs) => void; @@ -29,7 +29,7 @@ interface EndpointReferenceProps { } export default function EndpointReference(props: EndpointReferenceProps) { - const { account, integration, activeFlow, setSubTab, setActiveTab } = props; + const { environment, integration, activeFlow, setSubTab, setActiveTab } = props; const [showParametersOpen, setShowParametersOpen] = useState(false); const [language, setLanguage] = useState(Language.Node); @@ -44,8 +44,8 @@ export default function EndpointReference(props: EndpointReferenceProps) { if (activeFlow) { setSyncSnippet( activeFlow.type === 'sync' - ? nodeSnippet(activeFlow.models, account.secret_key, connectionId, integration.unique_key) - : nodeActionSnippet(activeFlow.name, account.secret_key, connectionId, integration.unique_key, parseInput(activeFlow)) + ? nodeSnippet(activeFlow.models, environment.secret_key, connectionId, integration.unique_key) + : nodeActionSnippet(activeFlow.name, environment.secret_key, connectionId, integration.unique_key, parseInput(activeFlow)) ); const jsonModel = generateResponseModel( @@ -61,7 +61,7 @@ export default function EndpointReference(props: EndpointReferenceProps) { setJsonResponseSnippet(JSON.stringify(jsonModel, null, 2)); } } - }, [activeFlow, account, integration.unique_key]); + }, [activeFlow, environment, integration.unique_key]); const routeToFlow = () => { setActiveTab(Tabs.Scripts); @@ -100,10 +100,10 @@ export default function EndpointReference(props: EndpointReferenceProps) { if (language !== Language.Node) { setSyncSnippet( activeFlow?.type === 'sync' - ? nodeSnippet(activeFlow.models, account.secret_key, connectionId, integration.unique_key) + ? nodeSnippet(activeFlow.models, environment.secret_key, connectionId, integration.unique_key) : nodeActionSnippet( activeFlow?.name as string, - account.secret_key, + environment.secret_key, connectionId, integration.unique_key, parseInput(activeFlow as Flow) @@ -125,7 +125,7 @@ export default function EndpointReference(props: EndpointReferenceProps) { curlSnippet( baseUrl, activeFlow?.endpoints[0] as NangoSyncEndpoint, - account.secret_key, + environment.secret_key, connectionId, integration.unique_key, parseInput(activeFlow as Flow) @@ -221,10 +221,10 @@ export default function EndpointReference(props: EndpointReferenceProps) { if (language !== Language.Node) { setSyncSnippet( activeFlow?.type === 'sync' - ? nodeSnippet(activeFlow.models, account.secret_key, connectionId, integration.unique_key) + ? nodeSnippet(activeFlow.models, environment.secret_key, connectionId, integration.unique_key) : nodeActionSnippet( activeFlow?.name as string, - account.secret_key, + environment.secret_key, connectionId, integration.unique_key, parseInput(activeFlow as Flow) diff --git a/packages/webapp/src/pages/Integration/FlowPage.tsx b/packages/webapp/src/pages/Integration/FlowPage.tsx index f7f9a844cc..1fa48a68f5 100644 --- a/packages/webapp/src/pages/Integration/FlowPage.tsx +++ b/packages/webapp/src/pages/Integration/FlowPage.tsx @@ -11,7 +11,7 @@ import CopyButton from '../../components/ui/button/CopyButton'; import Spinner from '../../components/ui/Spinner'; import type { FlowConfiguration, EndpointResponse } from './Show'; import { Tabs, SubTabs } from './Show'; -import type { IntegrationConfig, Account, Flow, Connection } from '../../types'; +import type { IntegrationConfig, Environment, Flow, Connection } from '../../types'; import EndpointLabel from './components/EndpointLabel'; import ActionModal from '../../components/ui/ActionModal'; import Info from '../../components/ui/Info'; @@ -21,7 +21,7 @@ import { autoStartSnippet, setMetadaSnippet } from '../../utils/language-snippet import { useStore } from '../../store'; interface FlowPageProps { - account: Account; + environment: Environment; integration: IntegrationConfig; flow: Flow | null; flowConfig: FlowConfiguration | null; @@ -33,7 +33,7 @@ interface FlowPageProps { } export default function FlowPage(props: FlowPageProps) { - const { account, integration, flow, flowConfig, reload, endpoints, setFlow, setActiveTab, setSubTab } = props; + const { environment, integration, flow, flowConfig, reload, endpoints, setFlow, setActiveTab, setSubTab } = props; const env = useStore((state) => state.env); const { data: connections, error } = useSWR(`/api/v1/integration/${integration.unique_key}/connections?env=${env}`); @@ -417,7 +417,7 @@ export default function FlowPage(props: FlowPageProps) { )} @@ -425,7 +425,7 @@ export default function FlowPage(props: FlowPageProps) { {setMetadaSnippet( - account.secret_key, + environment.secret_key, integration.unique_key, parseInput(flow) as Record )} @@ -479,11 +479,11 @@ export default function FlowPage(props: FlowPageProps) { - {autoStartSnippet(account.secret_key, integration.unique_key, flow?.name)} + {autoStartSnippet(environment.secret_key, integration.unique_key, flow?.name)} )} diff --git a/packages/webapp/src/pages/Integration/Show.tsx b/packages/webapp/src/pages/Integration/Show.tsx index bba03a7c70..c6e9b4c029 100644 --- a/packages/webapp/src/pages/Integration/Show.tsx +++ b/packages/webapp/src/pages/Integration/Show.tsx @@ -51,7 +51,7 @@ export default function ShowIntegration() { `/api/v1/integration/${providerConfigKey}?include_creds=true&include_flows=true&env=${env}` ); - const { environment, error: accountError } = useEnvironment(env); + const { environment, error: environmentError } = useEnvironment(env); const [activeTab, setActiveTab] = useState(Tabs.API); const [subTab, setSubTab] = useState(null); @@ -76,7 +76,7 @@ export default function ShowIntegration() { return ; } - if (error || accountError) { + if (error || environmentError) { requestErrorToast(); return ( @@ -161,7 +161,7 @@ export default function ShowIntegration() { <> {subTab === SubTabs.Reference ? ( @@ -184,7 +184,7 @@ export default function ShowIntegration() { {subTab === SubTabs.Flow ? ( mutate()} @@ -205,7 +205,7 @@ export default function ShowIntegration() { )} )} - {activeTab === Tabs.Auth && integration && } + {activeTab === Tabs.Auth && integration && } ); diff --git a/packages/webapp/src/types.ts b/packages/webapp/src/types.ts index 589ddc01f8..bf892328fa 100644 --- a/packages/webapp/src/types.ts +++ b/packages/webapp/src/types.ts @@ -242,7 +242,7 @@ export interface Flow { webhookSubscriptions: string[]; } -export interface Account { +export interface Environment { id: number; name: string; account_id: number; @@ -252,6 +252,7 @@ export interface Account { secret_key_tag: string | null; callback_url: string; webhook_url: string; + webhook_url_secondary: string | null; webhook_receive_url: string; hmac_enabled: boolean; hmac_key: string; diff --git a/packages/webapp/src/utils/api.tsx b/packages/webapp/src/utils/api.tsx index dae9f5707d..7f51e1f9fe 100644 --- a/packages/webapp/src/utils/api.tsx +++ b/packages/webapp/src/utils/api.tsx @@ -293,6 +293,34 @@ export function useEditWebhookUrlAPI(env: string) { }; } +export function useEditWebhookSecondaryUrlAPI(env: string) { + const signout = useSignout(); + + return async (webhookSecondaryUrl: string) => { + try { + const options = { + method: 'POST', + headers: getHeaders(), + body: JSON.stringify({ webhook_secondary_url: webhookSecondaryUrl }) + }; + + const res = await fetch(`/api/v1/environment/webhook-secondary?env=${env}`, options); + + if (res.status === 401) { + return signout(); + } + + if (res.status !== 200) { + return serverErrorToast(); + } + + return res; + } catch { + requestErrorToast(); + } + }; +} + export function useGetIntegrationListAPI(env: string) { const signout = useSignout();