From 5af0304ebffb26a9431204af599cd87dc6d5b3a8 Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 31 Jul 2025 17:10:36 -0700 Subject: [PATCH 1/3] Added test connection errors to add source form --- .../src/app/sources/new/AddSourceForm.js | 235 ++++++++++-------- 1 file changed, 132 insertions(+), 103 deletions(-) diff --git a/web-app/pontoon/src/app/sources/new/AddSourceForm.js b/web-app/pontoon/src/app/sources/new/AddSourceForm.js index 85d6b0b..5a3fbae 100644 --- a/web-app/pontoon/src/app/sources/new/AddSourceForm.js +++ b/web-app/pontoon/src/app/sources/new/AddSourceForm.js @@ -7,11 +7,11 @@ import { CircularProgress, Divider, FormHelperText, + Alert, } from "@mui/material"; -import { useState } from "react"; import { Form, Formik } from "formik"; import * as Yup from "yup"; -import useSWR from "swr"; +import { mutate } from "swr"; import useSWRMutation from "swr/mutation"; import Link from "next/link"; import { ChevronLeft } from "@mui/icons-material"; @@ -40,116 +40,128 @@ import { getPostgresValidation, getPostgresInitialValues, } from "@/app/components/forms/connection-details/PostgresConnectionDetails"; -import { - getRequest, - postRequest, - putRequest, - pollTaskStatus, -} from "@/app/api/requests"; +import { postRequest, putRequest, pollTaskStatus } from "@/app/api/requests"; -const AddSourceForm = () => { - const Status = Object.freeze({ - NOT_STARTED: 0, - LOADING: 1, - SUCCESS: 2, - FAILED: 3, - }); +const testConnection = async (key, { arg: values, sourceId }) => { + const params = { + source_name: values.source_name, + vendor_type: values.vendor_type, + connection_info: { + vendor_type: values.vendor_type, + ...values[values.vendor_type], + }, + }; - // After refreshing the page, making the first API call resets the form. - // In order to avoid the refresh happening during form validation, this api call is made here, - // even though it is not used for anything - const { - data: sources, - error: isError, - isLoading, - } = useSWR("/sources", getRequest); + try { + if (!sourceId) { + console.log("Creating source"); + const result = await postRequest("/sources", { arg: params }); + const check = await postRequest(`/sources/${result.source_id}/check`, { + arg: {}, + }); + const status = await pollTaskStatus( + `/sources/${result.source_id}/check/${check.task_id}` + ); + console.log("source_id", result.source_id); + console.log("task_id", check.task_id); - // state of the connection test - const [testConnectionStatus, setTestConnectionStatus] = useState( - Status.NOT_STARTED - ); + if ( + status.success === undefined || + status.success === null || + status.success === false + ) { + throw new Error(status.message); + } - // state for the source being created - const [sourceCreated, setSourceCreated] = useState(false); - const [sourceId, setSourceId] = useState(""); + return { + source_id: result.source_id, + success: status.success, + }; + } else { + console.log("Updating source"); + const result = await putRequest(`/sources/${sourceId}`, { arg: params }); + const check = await postRequest(`/sources/${sourceId}/check`, { + arg: {}, + }); + const status = await pollTaskStatus( + `/sources/${sourceId}/check/${check.task_id}` + ); + console.log("source_id", sourceId); + console.log("task_id", check.task_id); - const router = useRouter(); + if ( + status.success === undefined || + status.success === null || + status.success === false + ) { + throw new Error(status.message); + } - const createSource = (params) => { - return postRequest("/sources", { arg: params }); - }; + return { + source_id: result.source_id, + success: status.success, + }; + } + } catch (e) { + console.warn("Error testing connection: ", e); + throw e; + } +}; - const updateSource = (sourceId, params) => { - return putRequest(`/sources/${sourceId}`, { arg: params }); - }; +const enableSource = async (key, { arg: sourceId }) => { + try { + const result = await putRequest(`/sources/${sourceId}`, { + arg: { is_enabled: true, state: "CREATED" }, + }); + // Invalidate the sources cache since there's a new source + mutate("/sources"); + return result; + } catch (e) { + console.warn("Error enabling source: ", e); + throw e; + } +}; - const startSourceCheck = (sourceId) => { - return postRequest(`/sources/${sourceId}/check`, { arg: {} }); - }; +const AddSourceForm = () => { + const { + trigger: testConnectionTrigger, + data: testConnectionResult, + error: testConnectionError, + isMutating: isTestConnectionMutating, + } = useSWRMutation("/sources/test_connection", testConnection); + const sourceId = testConnectionResult?.source_id; - const waitForSourceCheck = (sourceId, taskId) => { - return pollTaskStatus(`/sources/${sourceId}/check/${taskId}`); - }; + const { + trigger: enableSourceTrigger, + data: enableSourceResult, + error: enableSourceError, + isMutating: isEnableSourceMutating, + } = useSWRMutation((sourceId) => `/sources/${sourceId}`, enableSource); - const updateCheckState = (result) => { - if (!result.success || result.success === false) { - setTestConnectionStatus(Status.FAILED); - } else { - setTestConnectionStatus(Status.SUCCESS); - } - }; + const router = useRouter(); // form submission handler const handleCreateAndCheckSource = async (values, validateForm) => { - setTestConnectionStatus(Status.LOADING); const errors = await validateForm(values); if (Object.keys(errors).length > 0) { console.log(errors); - setTestConnectionStatus(Status.FAILED); return; } - - const params = { - source_name: values.source_name, - vendor_type: values.vendor_type, - connection_info: { - vendor_type: values.vendor_type, - ...values[values.vendor_type], - }, - }; try { - if (!sourceCreated) { - console.log("Creating source"); - const result = await createSource(params); - const check = await startSourceCheck(result.source_id); - const status = await waitForSourceCheck( - result.source_id, - check.task_id - ); - - setSourceCreated(true); - setSourceId(result.source_id); - updateCheckState(status); - } else { - console.log("Updating source"); - await updateSource(sourceId, params); - const check = await startSourceCheck(sourceId); - const status = await waitForSourceCheck(sourceId, check.task_id); - updateCheckState(status); - } + const result = await testConnectionTrigger(values, sourceId); } catch (e) { - setTestConnectionStatus(Status.FAILED); - } finally { + console.warn("Error testing connection: ", e); + return; } }; // when the create button is clicked const handleEnableSource = async (values) => { console.log("Submitting form"); - if (sourceCreated) { + if (sourceId) { try { - await updateSource(sourceId, { is_enabled: true, state: "CREATED" }); + await enableSourceTrigger(sourceId); router.push("/sources"); } catch (e) { console.log("Enabling source failed: ", e); @@ -243,7 +255,8 @@ const AddSourceForm = () => { variant="contained" disabled={ isValidating || - testConnectionStatus == Status.LOADING || + isTestConnectionMutating || + isEnableSourceMutating || !isValid || !dirty } @@ -253,35 +266,51 @@ const AddSourceForm = () => { > Test Connection - {testConnectionStatus == Status.LOADING ? ( + {isTestConnectionMutating ? ( ) : null} - {testConnectionStatus == Status.SUCCESS ? ( + {testConnectionResult?.success === true ? ( ) : null} - {testConnectionStatus == Status.FAILED ? ( + {testConnectionError && !isTestConnectionMutating ? ( ) : null} Testing the connection may take up to a minute. + {testConnectionError ? ( + + {testConnectionError.message} + + ) : null} + {enableSourceError ? ( + + {enableSourceError.message} + + ) : null} + + + + {isEnableSourceMutating ? : null} - )} From f8d9ecc4d9f041a5a5446adb89ef02a1919cc74c Mon Sep 17 00:00:00 2001 From: unknown Date: Sat, 2 Aug 2025 01:37:51 -0700 Subject: [PATCH 2/3] Added destination error messages --- .../pontoon/src/app/destinations/new/page.js | 299 +++++++++++------- .../src/app/sources/new/AddSourceForm.js | 35 +- 2 files changed, 206 insertions(+), 128 deletions(-) diff --git a/web-app/pontoon/src/app/destinations/new/page.js b/web-app/pontoon/src/app/destinations/new/page.js index bcfef50..7c4cd73 100644 --- a/web-app/pontoon/src/app/destinations/new/page.js +++ b/web-app/pontoon/src/app/destinations/new/page.js @@ -9,11 +9,13 @@ import { Checkbox, CircularProgress, FormHelperText, + Alert, } from "@mui/material"; import { useState } from "react"; import { Form, Formik } from "formik"; import * as Yup from "yup"; import useSWRMutation from "swr/mutation"; +import { mutate } from "swr"; import Link from "next/link"; import { ChevronLeft } from "@mui/icons-material"; import { useRouter } from "next/navigation"; @@ -51,126 +53,143 @@ import { getPostgresInitialValues, } from "@/app/components/forms/connection-details/PostgresConnectionDetails"; -const AddDestination = () => { - const Status = Object.freeze({ - NOT_STARTED: 0, - LOADING: 1, - SUCCESS: 2, - FAILED: 3, - }); - - // state of the connection test - const [testConnectionStatus, setTestConnectionStatus] = useState( - Status.NOT_STARTED - ); - - // state for the source being created - const [destinationCreated, setDestinationCreated] = useState(false); - const [destinationId, setDestinationId] = useState(""); - - const router = useRouter(); - - const createDestination = (params) => { - return postRequest("/destinations", { arg: params }); - }; - - const updateDestination = (destinationId, params) => { - return putRequest(`/destinations/${destinationId}`, { arg: params }); - }; - - const startDestinationCheck = (destinationId) => { - return postRequest(`/destinations/${destinationId}/check`, { arg: {} }); +const testConnection = async (key, { arg: values, destinationId }) => { + const params = { + destination_name: values.destination_name, + vendor_type: values.vendor_type, + recipient_id: values.recipient_id, + schedule: { + type: "INCREMENTAL", + frequency: values.schedule_frequency, + day: values.schedule_day, + hour: values.schedule_hour, + minute: 0, + }, + models: values.selectedModels, + connection_info: { + vendor_type: values.vendor_type, + ...values[values.vendor_type], + }, }; - const waitForDestinationCheck = (destinationId, taskId) => { - return pollTaskStatus(`/destinations/${destinationId}/check/${taskId}`); - }; + try { + if (!destinationId) { + console.log("Creating destination"); + const result = await postRequest("/destinations", { arg: params }); + const check = await postRequest( + `/destinations/${result.destination_id}/check`, + { + arg: {}, + } + ); + const status = await pollTaskStatus( + `/destinations/${result.destination_id}/check/${check.task_id}` + ); + console.log("destination_id", result.destination_id); + console.log("task_id", check.task_id); + + if ( + status.success === undefined || + status.success === null || + status.success === false + ) { + throw new Error(status.cause); + } - const updateCheckState = (result) => { - if (!result.success || result.success === false) { - setTestConnectionStatus(Status.FAILED); + return { + destination_id: result.destination_id, + success: status.success, + }; } else { - setTestConnectionStatus(Status.SUCCESS); + console.log("Updating destination"); + const result = await putRequest(`/destinations/${destinationId}`, { + arg: params, + }); + const check = await postRequest(`/destinations/${destinationId}/check`, { + arg: {}, + }); + const status = await pollTaskStatus( + `/destinations/${destinationId}/check/${check.task_id}` + ); + console.log("destination_id", destinationId); + console.log("task_id", check.task_id); + + if ( + status.success === undefined || + status.success === null || + status.success === false + ) { + throw new Error(status.cause); + } + + return { + destination_id: result.destination_id, + success: status.success, + }; } - }; + } catch (e) { + console.warn("Error testing connection: ", e); + throw e; + } +}; + +const enableDestination = async (key, { arg: destinationId }) => { + try { + const result = await putRequest(`/destinations/${destinationId}`, { + arg: { is_enabled: true, state: "CREATED" }, + }); + // Invalidate the destinations cache since there's a new destination + mutate("/destinations"); + return result; + } catch (e) { + console.warn("Error enabling destination: ", e); + throw e; + } +}; +const AddDestination = () => { const { - data: recipients, - error: recipientsError, - isLoading: recipientsLoading, - } = useSWR("/recipients", getRequest); + trigger: testConnectionTrigger, + data: testConnectionResult, + error: testConnectionError, + isMutating: isTestConnectionMutating, + } = useSWRMutation("/destinations/test_connection", testConnection); + const destinationId = testConnectionResult?.destination_id; const { - data: models, - error: modelsError, - isLoading: modelsLoading, - } = useSWR("/models", getRequest); - const modelIds = models?.map((m) => m.model_id); + trigger: enableDestinationTrigger, + data: enableDestinationResult, + error: enableDestinationError, + isMutating: isEnableDestinationMutating, + } = useSWRMutation( + (destinationId) => `/destinations/${destinationId}`, + enableDestination + ); + + const router = useRouter(); // form submission handler const handleCreateAndCheckDestination = async (values, validateForm) => { - setTestConnectionStatus(Status.LOADING); const errors = await validateForm(values); if (Object.keys(errors).length > 0) { console.log(errors); - setTestConnectionStatus(Status.FAILED); return; } - const params = { - destination_name: values.destination_name, - vendor_type: values.vendor_type, - recipient_id: values.recipient_id, - schedule: { - type: "INCREMENTAL", - frequency: values.schedule_frequency, - day: values.schedule_day, - hour: values.schedule_hour, - minute: 0, - }, - models: values.selectedModels, - connection_info: { - vendor_type: values.vendor_type, - ...values[values.vendor_type], - }, - }; try { - if (!destinationCreated) { - console.log("Creating destination"); - const result = await createDestination(params); - const check = await startDestinationCheck(result.destination_id); - const status = await waitForDestinationCheck( - result.destination_id, - check.task_id - ); - - setDestinationCreated(true); - setDestinationId(result.destination_id); - updateCheckState(status); - } else { - console.log("Updating destination"); - await updateDestination(destinationId, params); - const check = await startDestinationCheck(destinationId); - const status = await waitForDestinationCheck( - destinationId, - check.task_id - ); - updateCheckState(status); - } + const result = await testConnectionTrigger(values, destinationId); } catch (e) { - setTestConnectionStatus(Status.FAILED); - } finally { + console.warn("Error testing connection: ", e); + return; } }; // when the create button is clicked - const handleEnableDestination = async () => { - if (destinationCreated) { + const handleEnableDestination = async (values) => { + console.log("Submitting form"); + if (destinationId) { try { - await updateDestination(destinationId, { - is_enabled: true, - state: "CREATED", - }); + await enableDestinationTrigger(destinationId); router.push("/destinations"); } catch (e) { console.log("Enabling destination failed: ", e); @@ -178,6 +197,19 @@ const AddDestination = () => { } }; + const { + data: recipients, + error: recipientsError, + isLoading: recipientsLoading, + } = useSWR("/recipients", getRequest); + + const { + data: models, + error: modelsError, + isLoading: modelsLoading, + } = useSWR("/models", getRequest); + const modelIds = models?.map((m) => m.model_id); + if (recipientsError || modelsError) { return Error with API; } @@ -389,7 +421,8 @@ const AddDestination = () => { variant="contained" disabled={ isValidating || - testConnectionStatus == Status.LOADING || + isTestConnectionMutating || + isEnableDestinationMutating || !isValid || !dirty } @@ -399,31 +432,50 @@ const AddDestination = () => { > Test Connection - {testConnectionStatus == Status.LOADING ? ( - - ) : null} - {testConnectionStatus == Status.SUCCESS ? ( - - ) : null} - {testConnectionStatus == Status.FAILED ? ( - - ) : null} + {renderTestConnectionStatus( + testConnectionResult, + testConnectionError, + isTestConnectionMutating + )} Testing the connection may take a few minutes. + {testConnectionError ? ( + + {testConnectionError.message} + + ) : null} + {enableDestinationError ? ( + + + {enableDestinationError.message} + + + ) : null} - + + + {isEnableDestinationMutating ? ( + + ) : null} + ); @@ -449,6 +501,23 @@ const renderConnectionDetails = (vendor_type, setFieldValue, values) => { } }; +const renderTestConnectionStatus = ( + testConnectionResult, + testConnectionError, + isTestConnectionMutating +) => { + if (isTestConnectionMutating) { + return ; + } + if (testConnectionError) { + return ; + } + if (testConnectionResult?.success === true) { + return ; + } + return null; +}; + const renderScheduleDetails = (schedule_frequency) => { return ( <> diff --git a/web-app/pontoon/src/app/sources/new/AddSourceForm.js b/web-app/pontoon/src/app/sources/new/AddSourceForm.js index 5a3fbae..a40bbc3 100644 --- a/web-app/pontoon/src/app/sources/new/AddSourceForm.js +++ b/web-app/pontoon/src/app/sources/new/AddSourceForm.js @@ -62,8 +62,6 @@ const testConnection = async (key, { arg: values, sourceId }) => { const status = await pollTaskStatus( `/sources/${result.source_id}/check/${check.task_id}` ); - console.log("source_id", result.source_id); - console.log("task_id", check.task_id); if ( status.success === undefined || @@ -86,8 +84,6 @@ const testConnection = async (key, { arg: values, sourceId }) => { const status = await pollTaskStatus( `/sources/${sourceId}/check/${check.task_id}` ); - console.log("source_id", sourceId); - console.log("task_id", check.task_id); if ( status.success === undefined || @@ -266,15 +262,11 @@ const AddSourceForm = () => { > Test Connection - {isTestConnectionMutating ? ( - - ) : null} - {testConnectionResult?.success === true ? ( - - ) : null} - {testConnectionError && !isTestConnectionMutating ? ( - - ) : null} + {renderTestConnectionStatus( + testConnectionResult, + testConnectionError, + isTestConnectionMutating + )} Testing the connection may take up to a minute. @@ -319,6 +311,23 @@ const AddSourceForm = () => { ); }; +const renderTestConnectionStatus = ( + testConnectionResult, + testConnectionError, + isTestConnectionMutating +) => { + if (isTestConnectionMutating) { + return ; + } + if (testConnectionError) { + return ; + } + if (testConnectionResult?.success === true) { + return ; + } + return null; +}; + const renderConnectionDetails = (vendor_type) => { switch (vendor_type) { case "snowflake": From 409aab4b89dc4d3b5cadd7d5e42866ac8b6afea8 Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 5 Aug 2025 13:53:19 -0700 Subject: [PATCH 3/3] Removed debug logs --- web-app/pontoon/src/app/destinations/new/page.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/web-app/pontoon/src/app/destinations/new/page.js b/web-app/pontoon/src/app/destinations/new/page.js index 7c4cd73..2225b13 100644 --- a/web-app/pontoon/src/app/destinations/new/page.js +++ b/web-app/pontoon/src/app/destinations/new/page.js @@ -85,8 +85,6 @@ const testConnection = async (key, { arg: values, destinationId }) => { const status = await pollTaskStatus( `/destinations/${result.destination_id}/check/${check.task_id}` ); - console.log("destination_id", result.destination_id); - console.log("task_id", check.task_id); if ( status.success === undefined || @@ -111,8 +109,6 @@ const testConnection = async (key, { arg: values, destinationId }) => { const status = await pollTaskStatus( `/destinations/${destinationId}/check/${check.task_id}` ); - console.log("destination_id", destinationId); - console.log("task_id", check.task_id); if ( status.success === undefined ||