Skip to content

Commit

Permalink
feat(providers): refactor workflow providers v2 (#6001)
Browse files Browse the repository at this point in the history
  • Loading branch information
paabloLC authored Dec 3, 2024
1 parent 4a46365 commit 52723ed
Show file tree
Hide file tree
Showing 17 changed files with 754 additions and 101 deletions.
98 changes: 97 additions & 1 deletion ui/actions/providers/providers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,102 @@ export const addCredentialsProvider = async (formData: FormData) => {
}
};

export const updateCredentialsProvider = async (
credentialsId: string,
formData: FormData,
) => {
const session = await auth();
const keyServer = process.env.API_BASE_URL;
const url = new URL(`${keyServer}/providers/secrets/${credentialsId}`);

const secretName = formData.get("secretName");
const providerType = formData.get("providerType");

const isRole = formData.get("role_arn") !== null;

let secret = {};

if (providerType === "aws") {
if (isRole) {
// Role-based configuration for AWS
secret = {
role_arn: formData.get("role_arn"),
aws_access_key_id: formData.get("aws_access_key_id") || undefined,
aws_secret_access_key:
formData.get("aws_secret_access_key") || undefined,
aws_session_token: formData.get("aws_session_token") || undefined,
session_duration:
parseInt(formData.get("session_duration") as string, 10) || 3600,
external_id: formData.get("external_id") || undefined,
role_session_name: formData.get("role_session_name") || undefined,
};
} else {
// Static credentials configuration for AWS
secret = {
aws_access_key_id: formData.get("aws_access_key_id"),
aws_secret_access_key: formData.get("aws_secret_access_key"),
aws_session_token: formData.get("aws_session_token") || undefined,
};
}
} else if (providerType === "azure") {
// Static credentials configuration for Azure
secret = {
client_id: formData.get("client_id"),
client_secret: formData.get("client_secret"),
tenant_id: formData.get("tenant_id"),
};
} else if (providerType === "gcp") {
// Static credentials configuration for GCP
secret = {
client_id: formData.get("client_id"),
client_secret: formData.get("client_secret"),
refresh_token: formData.get("refresh_token"),
};
} else if (providerType === "kubernetes") {
// Static credentials configuration for Kubernetes
secret = {
kubeconfig_content: formData.get("kubeconfig_content"),
};
}

const bodyData = {
data: {
type: "provider-secrets",
id: credentialsId,
attributes: {
name: secretName,
secret,
},
},
};

try {
const response = await fetch(url.toString(), {
method: "PATCH",
headers: {
"Content-Type": "application/vnd.api+json",
Accept: "application/vnd.api+json",
Authorization: `Bearer ${session?.accessToken}`,
},
body: JSON.stringify(bodyData),
});

if (!response.ok) {
throw new Error(`Failed to update credentials: ${response.statusText}`);
}

const data = await response.json();
revalidatePath("/providers");
return parseStringify(data);
} catch (error) {
// eslint-disable-next-line no-console
console.error(error);
return {
error: getErrorMessage(error),
};
}
};

export const checkConnectionProvider = async (formData: FormData) => {
const session = await auth();
const keyServer = process.env.API_BASE_URL;
Expand All @@ -270,7 +366,7 @@ export const checkConnectionProvider = async (formData: FormData) => {
},
});
const data = await response.json();
await wait(1000);
await wait(2000);
revalidatePath("/providers");
return parseStringify(data);
} catch (error) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,35 +1,30 @@
import { redirect } from "next/navigation";
import React from "react";

import {
ViaCredentialsForm,
ViaRoleForm,
} from "@/components/providers/workflow/forms";
import { SelectViaAWS } from "@/components/providers/workflow/forms/select-via-aws/select-via-aws";

interface Props {
searchParams: { type: string; id: string; via?: string };
}

export default function AddCredentialsPage({ searchParams }: Props) {
if (
!searchParams.type ||
!searchParams.id ||
(searchParams.type === "aws" && !searchParams.via)
) {
redirect("/providers/connect-account");
}

const useCredentialsForm =
(searchParams.type === "aws" && searchParams.via === "credentials") ||
(searchParams.type !== "aws" && !searchParams.via);

const useRoleForm =
searchParams.type === "aws" && searchParams.via === "role";

return (
<>
{useCredentialsForm && <ViaCredentialsForm searchParams={searchParams} />}
{useRoleForm && <ViaRoleForm searchParams={searchParams} />}
{searchParams.type === "aws" && !searchParams.via && (
<SelectViaAWS initialVia={searchParams.via} />
)}

{((searchParams.type === "aws" && searchParams.via === "credentials") ||
searchParams.type !== "aws") && (
<ViaCredentialsForm searchParams={searchParams} />
)}

{searchParams.type === "aws" && searchParams.via === "role" && (
<ViaRoleForm searchParams={searchParams} />
)}
</>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { getProvider } from "@/actions/providers";
import { TestConnectionForm } from "@/components/providers/workflow/forms";

interface Props {
searchParams: { type: string; id: string };
searchParams: { type: string; id: string; updated: string };
}

export default async function TestConnectionPage({ searchParams }: Props) {
Expand All @@ -25,7 +25,7 @@ export default async function TestConnectionPage({ searchParams }: Props) {
async function SSRTestConnection({
searchParams,
}: {
searchParams: { type: string; id: string };
searchParams: { type: string; id: string; updated: string };
}) {
const formData = new FormData();
formData.append("id", searchParams.id);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import React from "react";

import {
UpdateViaCredentialsForm,
UpdateViaRoleForm,
} from "@/components/providers/workflow/forms";
import { SelectViaAWS } from "@/components/providers/workflow/forms/select-via-aws/select-via-aws";

interface Props {
searchParams: { type: string; id: string; via?: string };
}

export default function UpdateCredentialsPage({ searchParams }: Props) {
return (
<>
{searchParams.type === "aws" && !searchParams.via && (
<SelectViaAWS initialVia={searchParams.via} />
)}

{((searchParams.type === "aws" && searchParams.via === "credentials") ||
searchParams.type !== "aws") && (
<UpdateViaCredentialsForm searchParams={searchParams} />
)}

{searchParams.type === "aws" && searchParams.via === "role" && (
<UpdateViaRoleForm searchParams={searchParams} />
)}
</>
);
}
21 changes: 20 additions & 1 deletion ui/components/providers/table/data-table-row-actions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { Row } from "@tanstack/react-table";
import clsx from "clsx";
import { useState } from "react";

import { checkConnectionProvider } from "@/actions/providers/providers";
import { VerticalDotsIcon } from "@/components/icons";
import { CustomAlertModal } from "@/components/ui/custom";

Expand All @@ -37,6 +38,15 @@ export function DataTableRowActions<ProviderProps>({
const providerId = (row.original as { id: string }).id;
const providerType = (row.original as any).attributes?.provider;
const providerAlias = (row.original as any).attributes?.alias;
const providerSecretId =
(row.original as any).relationships?.secret?.data?.id || null;

const handleTestConnection = async () => {
const formData = new FormData();
formData.append("providerId", providerId);
await checkConnectionProvider(formData);
};

return (
<>
<CustomAlertModal
Expand Down Expand Up @@ -77,11 +87,20 @@ export function DataTableRowActions<ProviderProps>({
>
<DropdownSection title="Actions">
<DropdownItem
href={`/providers/test-connection?type=${providerType}&id=${providerId}`}
href={`/providers/update-credentials?type=${providerType}&id=${providerId}${providerSecretId ? `&secretId=${providerSecretId}` : ""}`}
key="update"
description="Update the provider credentials"
textValue="Update Credentials"
startContent={<EditDocumentBulkIcon className={iconClasses} />}
>
Update Credentials
</DropdownItem>
<DropdownItem
key="new"
description="Check the connection to the provider"
textValue="Check Connection"
startContent={<AddNoteBulkIcon className={iconClasses} />}
onClick={handleTestConnection}
>
Test Connection
</DropdownItem>
Expand Down
110 changes: 53 additions & 57 deletions ui/components/providers/workflow/forms/connect-account-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import { Form } from "@/components/ui/form";
import { addProvider } from "../../../../actions/providers/providers";
import { addProviderFormSchema, ApiError } from "../../../../types";
import { RadioGroupProvider } from "../../radio-group-provider";
import { RadioGroupAWSViaCredentialsForm } from "./radio-group-aws-via-credentials-form";

export type FormValues = z.infer<typeof addProviderFormSchema>;

Expand All @@ -36,7 +35,6 @@ export const ConnectAccountForm = () => {
providerType: undefined,
providerUid: "",
providerAlias: "",
awsCredentialsType: "",
},
});

Expand All @@ -51,55 +49,60 @@ export const ConnectAccountForm = () => {
([key, value]) => value !== undefined && formData.append(key, value),
);

const data = await addProvider(formData);

if (data?.errors && data.errors.length > 0) {
// Handle server-side validation errors
data.errors.forEach((error: ApiError) => {
const errorMessage = error.detail;
const pointer = error.source?.pointer;

switch (pointer) {
case "/data/attributes/provider":
form.setError("providerType", {
type: "server",
message: errorMessage,
});
break;
case "/data/attributes/uid":
case "/data/attributes/__all__":
form.setError("providerUid", {
type: "server",
message: errorMessage,
});
break;
case "/data/attributes/alias":
form.setError("providerAlias", {
type: "server",
message: errorMessage,
});
break;
default:
toast({
variant: "destructive",
title: "Oops! Something went wrong",
description: errorMessage,
});
}
try {
const data = await addProvider(formData);

if (data?.errors && data.errors.length > 0) {
// Handle server-side validation errors
data.errors.forEach((error: ApiError) => {
const errorMessage = error.detail;
const pointer = error.source?.pointer;

switch (pointer) {
case "/data/attributes/provider":
form.setError("providerType", {
type: "server",
message: errorMessage,
});
break;
case "/data/attributes/uid":
case "/data/attributes/__all__":
form.setError("providerUid", {
type: "server",
message: errorMessage,
});
break;
case "/data/attributes/alias":
form.setError("providerAlias", {
type: "server",
message: errorMessage,
});
break;
default:
toast({
variant: "destructive",
title: "Oops! Something went wrong",
description: errorMessage,
});
}
});
return;
} else {
// Go to the next step after successful submission
const {
id,
attributes: { provider: providerType },
} = data.data;

router.push(`/providers/add-credentials?type=${providerType}&id=${id}`);
}
} catch (error: any) {
console.error("Error during submission:", error);
toast({
variant: "destructive",
title: "Submission Error",
description: error.message || "Something went wrong. Please try again.",
});
return;
} else {
// Navigate to the next step after successful submission
const {
id,
attributes: { provider: providerType },
} = data.data;
const credentialsParam = values.awsCredentialsType
? `&via=${values.awsCredentialsType}`
: "";
router.push(
`/providers/add-credentials?type=${providerType}&id=${id}${credentialsParam}`,
);
}
};

Expand Down Expand Up @@ -158,13 +161,6 @@ export const ConnectAccountForm = () => {
isRequired={false}
isInvalid={!!form.formState.errors.providerAlias}
/>
{providerType === "aws" && (
<RadioGroupAWSViaCredentialsForm
control={form.control}
isInvalid={!!form.formState.errors.awsCredentialsType}
errorMessage={form.formState.errors.awsCredentialsType?.message}
/>
)}
</>
)}
{/* Navigation buttons */}
Expand Down
Loading

0 comments on commit 52723ed

Please sign in to comment.