diff --git a/web-app/pontoon/src/app/destinations/new/page.js b/web-app/pontoon/src/app/destinations/new/page.js
index bcfef50..2225b13 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,139 @@ 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}`
+ );
+
+ 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}`
+ );
+
+ 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 +193,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 +417,8 @@ const AddDestination = () => {
variant="contained"
disabled={
isValidating ||
- testConnectionStatus == Status.LOADING ||
+ isTestConnectionMutating ||
+ isEnableDestinationMutating ||
!isValid ||
!dirty
}
@@ -399,31 +428,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 +497,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 85d6b0b..a40bbc3 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,124 @@ 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}`
+ );
- // 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}`
+ );
- 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 +251,8 @@ const AddSourceForm = () => {
variant="contained"
disabled={
isValidating ||
- testConnectionStatus == Status.LOADING ||
+ isTestConnectionMutating ||
+ isEnableSourceMutating ||
!isValid ||
!dirty
}
@@ -253,35 +262,47 @@ const AddSourceForm = () => {
>
Test Connection
- {testConnectionStatus == Status.LOADING ? (
-
- ) : null}
- {testConnectionStatus == Status.SUCCESS ? (
- ✅
- ) : null}
- {testConnectionStatus == Status.FAILED ? (
- ❌
- ) : null}
+ {renderTestConnectionStatus(
+ testConnectionResult,
+ testConnectionError,
+ isTestConnectionMutating
+ )}
Testing the connection may take up to a minute.
+ {testConnectionError ? (
+
+ {testConnectionError.message}
+
+ ) : null}
+ {enableSourceError ? (
+
+ {enableSourceError.message}
+
+ ) : null}
+
+
+
+ {isEnableSourceMutating ? : null}
-
)}
@@ -290,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":