Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
295 changes: 180 additions & 115 deletions web-app/pontoon/src/app/destinations/new/page.js
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -51,133 +53,159 @@ 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);
}
}
};

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 <Typography>Error with API</Typography>;
}
Expand Down Expand Up @@ -389,7 +417,8 @@ const AddDestination = () => {
variant="contained"
disabled={
isValidating ||
testConnectionStatus == Status.LOADING ||
isTestConnectionMutating ||
isEnableDestinationMutating ||
!isValid ||
!dirty
}
Expand All @@ -399,31 +428,50 @@ const AddDestination = () => {
>
Test Connection
</Button>
{testConnectionStatus == Status.LOADING ? (
<CircularProgress size={28} />
) : null}
{testConnectionStatus == Status.SUCCESS ? (
<Typography fontSize={26}>✅</Typography>
) : null}
{testConnectionStatus == Status.FAILED ? (
<Typography fontSize={26}>❌</Typography>
) : null}
{renderTestConnectionStatus(
testConnectionResult,
testConnectionError,
isTestConnectionMutating
)}
</Stack>
<FormHelperText>
Testing the connection may take a few minutes.
</FormHelperText>
{testConnectionError ? (
<Alert severity="error">
<Typography>{testConnectionError.message}</Typography>
</Alert>
) : null}
{enableDestinationError ? (
<Alert severity="error">
<Typography>
{enableDestinationError.message}
</Typography>
</Alert>
) : null}
</Stack>

<Button
type="submit"
variant="contained"
disabled={
isSubmitting || !(testConnectionStatus == Status.SUCCESS)
}
sx={{ width: "fit-content" }}
>
Create
</Button>
<Stack direction="row" alignItems="center" spacing={1.5}>
<Button
type="submit"
variant="contained"
disabled={
isValidating ||
isTestConnectionMutating ||
isEnableDestinationMutating ||
testConnectionError ||
testConnectionResult?.success === false ||
!isValid ||
!dirty
}
sx={{ width: "fit-content" }}
>
Create
</Button>
{isEnableDestinationMutating ? (
<CircularProgress size={28} />
) : null}
</Stack>
</Stack>
</Form>
);
Expand All @@ -449,6 +497,23 @@ const renderConnectionDetails = (vendor_type, setFieldValue, values) => {
}
};

const renderTestConnectionStatus = (
testConnectionResult,
testConnectionError,
isTestConnectionMutating
) => {
if (isTestConnectionMutating) {
return <CircularProgress size={28} />;
}
if (testConnectionError) {
return <Typography fontSize={26}>❌</Typography>;
}
if (testConnectionResult?.success === true) {
return <Typography fontSize={26}>✅</Typography>;
}
return null;
};

const renderScheduleDetails = (schedule_frequency) => {
return (
<>
Expand Down
Loading