feat(secret-sync): add Gitlab PR comments suggestions

This commit is contained in:
carlosmonastyrski
2025-06-24 10:05:46 -03:00
parent c305ddd463
commit 43e0d400f9
17 changed files with 243 additions and 153 deletions
@@ -1,6 +1,6 @@
import { Controller, useFormContext, useWatch } from "react-hook-form";
import { SingleValue } from "react-select";
import { faCircleInfo } from "@fortawesome/free-solid-svg-icons";
import { faCircleInfo, faQuestionCircle } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { SecretSyncConnectionField } from "@app/components/secret-syncs/forms/SecretSyncConnectionField";
@@ -40,26 +40,24 @@ const SecretProtectionOption = ({
tooltip?: string;
}) => {
return (
<div className="flex items-start justify-between rounded-lg border border-mineshaft-600 bg-mineshaft-800/50 p-4 transition-all duration-200 hover:border-mineshaft-500">
<div className="flex flex-1 items-start space-x-3">
<div className="min-w-0 flex-1">
<div className="mb-1 flex items-center gap-2">
<h4 className="text-sm font-medium text-bunker-100">{title}</h4>
{tooltip && (
<Tooltip className="max-w-sm" content={tooltip}>
<FontAwesomeIcon
icon={faCircleInfo}
className="cursor-help text-xs text-mineshaft-400 hover:text-mineshaft-300"
/>
</Tooltip>
)}
</div>
</div>
</div>
<div className="ml-4 flex-shrink-0">
<Switch id={id} onCheckedChange={onChange} isChecked={isEnabled} isDisabled={isDisabled} />
</div>
</div>
<Switch
className="bg-mineshaft-400/80 shadow-inner data-[state=checked]:bg-green/80"
id={id}
thumbClassName="bg-mineshaft-800"
onCheckedChange={onChange}
isChecked={isEnabled}
isDisabled={isDisabled}
containerClassName="w-full"
>
<p>
{title}{" "}
{tooltip && (
<Tooltip className="max-w-md" content={tooltip}>
<FontAwesomeIcon icon={faQuestionCircle} size="sm" className="ml-1" />
</Tooltip>
)}
</p>
</Switch>
);
};
@@ -86,7 +84,7 @@ export const GitLabSyncFields = () => {
);
return (
<div className="h-[calc(100vh-20rem)] overflow-auto">
<div className="h-[calc(100vh-28rem)] overflow-auto">
<SecretSyncConnectionField
onChange={() => {
setValue("destinationConfig.projectId", "");
@@ -175,7 +173,7 @@ export const GitLabSyncFields = () => {
helperText={
<Tooltip
className="max-w-md"
content="Ensure the project exists in the connection's GitLab instance URL."
content="Ensure the project exists in the connection's GitLab instance URL and the connection has access to it."
>
<div>
<span>Don&#39;t see the project you&#39;re looking for?</span>{" "}
@@ -229,7 +227,7 @@ export const GitLabSyncFields = () => {
render={({ field: { onChange, value } }) => (
<SecretProtectionOption
id="should-protect-secrets"
title="Mark Infisical secrets in GitLab as 'Protected' secrets"
title="Mark secrets as Protected"
isEnabled={value || false}
onChange={onChange}
/>
@@ -242,7 +240,7 @@ export const GitLabSyncFields = () => {
render={({ field: { onChange, value } }) => (
<SecretProtectionOption
id="should-mask-secrets"
title="Mark Infisical secrets in GitLab as 'Masked' secrets"
title="Mark secrets as Masked"
tooltip="GitLab has limitations for masked variables: secrets must be at least 8 characters long and not match existing CI/CD variable names. Secrets not meeting these criteria won't be masked."
isEnabled={value || false}
onChange={(checked) => {
@@ -262,7 +260,7 @@ export const GitLabSyncFields = () => {
<div className="max-h-32 opacity-100 transition-all duration-300">
<SecretProtectionOption
id="should-hide-secrets"
title="Mark Infisical secrets in GitLab as 'Hidden' secrets"
title="Mark secrets as Hidden"
tooltip="Secrets can only be marked as hidden if they are also masked."
isEnabled={value || false}
onChange={onChange}
@@ -6,7 +6,7 @@ import { SecretSync } from "@app/hooks/api/secretSyncs";
export const GitLabSyncReviewFields = () => {
const { watch } = useFormContext<TSecretSyncForm & { destination: SecretSync.GitLab }>();
const projectId = watch("destinationConfig.projectId");
const projectName = watch("destinationConfig.projectName");
const targetEnvironment = watch("destinationConfig.targetEnvironment");
const groupId = watch("destinationConfig.groupId");
const scope = watch("destinationConfig.scope");
@@ -17,7 +17,7 @@ export const GitLabSyncReviewFields = () => {
return (
<>
<GenericFieldLabel label="Scope">{scope}</GenericFieldLabel>
<GenericFieldLabel label="Project ID">{projectId}</GenericFieldLabel>
<GenericFieldLabel label="Project Name">{projectName}</GenericFieldLabel>
{groupId && <GenericFieldLabel label="Group ID">{groupId}</GenericFieldLabel>}
{targetEnvironment && (
<GenericFieldLabel label="Environment">{targetEnvironment}</GenericFieldLabel>
+1 -1
View File
@@ -75,7 +75,7 @@ export const SECRET_SYNC_MAP: Record<SecretSync, { name: string; image: string }
image: "Flyio.svg"
},
[SecretSync.GitLab]: {
name: "Gitlab",
name: "GitLab",
image: "GitLab.png"
}
};
@@ -7,3 +7,8 @@ export type TGitLabGroup = {
id: string;
name: string;
};
export enum GitLabAccessTokenType {
Personal = "personal",
Project = "project"
}
@@ -1,6 +1,8 @@
import { AppConnection } from "@app/hooks/api/appConnections/enums";
import { TRootAppConnection } from "@app/hooks/api/appConnections/types/root-connection";
import { GitLabAccessTokenType } from "../gitlab";
export enum GitlabConnectionMethod {
AccessToken = "access-token",
OAuth = "oauth"
@@ -12,6 +14,7 @@ export type TGitlabConnection = TRootAppConnection & { app: AppConnection.Gitlab
credentials: {
instanceUrl?: string;
accessToken: string;
accessTokenType: GitLabAccessTokenType;
};
}
| {
@@ -20,6 +20,7 @@ import { APP_CONNECTION_MAP, getAppConnectionMethodDetails } from "@app/helpers/
import { isInfisicalCloud } from "@app/helpers/platform";
import { useGetAppConnectionOption } from "@app/hooks/api/appConnections";
import { AppConnection } from "@app/hooks/api/appConnections/enums";
import { GitLabAccessTokenType } from "@app/hooks/api/appConnections/gitlab";
import {
GitlabConnectionMethod,
TGitlabConnection
@@ -41,6 +42,7 @@ const formSchema = z.discriminatedUnion("method", [
method: z.literal(GitlabConnectionMethod.AccessToken),
credentials: z.object({
accessToken: z.string().min(1, "Access token is required"),
accessTokenType: z.nativeEnum(GitLabAccessTokenType),
instanceUrl: z
.string()
.trim()
@@ -90,6 +92,7 @@ export const GitLabConnectionForm = ({ appConnection, onSubmit: formSubmit }: Pr
method: GitlabConnectionMethod.AccessToken,
credentials: {
accessToken: "",
accessTokenType: GitLabAccessTokenType.Personal,
instanceUrl: ""
}
} as FormData))
@@ -151,7 +154,7 @@ export const GitLabConnectionForm = ({ appConnection, onSubmit: formSubmit }: Pr
break;
default:
throw new Error("Unhandled Gitlab Connection method");
throw new Error("Unhandled GitLab Connection method");
}
} catch (error) {
console.error("Error handling form submission:", error);
@@ -169,7 +172,7 @@ export const GitLabConnectionForm = ({ appConnection, onSubmit: formSubmit }: Pr
isMissingConfig = false;
break;
default:
throw new Error(`Unhandled Gitlab Connection method: ${selectedMethod}`);
throw new Error(`Unhandled GitLab Connection method: ${selectedMethod}`);
}
const methodDetails = getAppConnectionMethodDetails(selectedMethod);
@@ -211,7 +214,7 @@ export const GitLabConnectionForm = ({ appConnection, onSubmit: formSubmit }: Pr
? `Environment variables have not been configured. ${
isInfisicalCloud()
? "Please contact Infisical."
: `See Docs to configure Gitlab ${methodDetails.name} Connections.`
: `See Docs to configure GitLab ${methodDetails.name} Connections.`
}`
: error?.message
}
@@ -245,24 +248,59 @@ export const GitLabConnectionForm = ({ appConnection, onSubmit: formSubmit }: Pr
/>
{selectedMethod === GitlabConnectionMethod.AccessToken && (
<Controller
name="credentials.accessToken"
control={control}
render={({ field: { value, onChange }, fieldState: { error } }) => (
<FormControl
label="Access Token"
errorText={error?.message}
isError={Boolean(error?.message)}
tooltipText="Your Gitlab Access Token"
>
<SecretInput
containerClassName="text-gray-400 group-focus-within:!border-primary-400/50 border border-mineshaft-500 bg-mineshaft-900 px-2.5 py-1.5"
value={value}
onChange={(e) => onChange(e.target.value)}
/>
</FormControl>
)}
/>
<>
<Controller
name="credentials.accessTokenType"
control={control}
render={({ field: { value, onChange }, fieldState: { error } }) => (
<FormControl
errorText={error?.message}
isError={Boolean(error?.message)}
label="Access Token Type"
>
<Select
isDisabled={isUpdate}
value={value}
onValueChange={(val) => {
onChange(val);
if (val === GitlabConnectionMethod.OAuth) {
setValue("credentials.code", "custom");
}
}}
className="w-full border border-mineshaft-500"
position="popper"
dropdownContainerClassName="max-w-none"
>
{Object.values(GitLabAccessTokenType).map((method) => {
return (
<SelectItem value={method} key={method}>
{method.charAt(0).toUpperCase() + method.slice(1)} Access Token
</SelectItem>
);
})}
</Select>
</FormControl>
)}
/>
<Controller
name="credentials.accessToken"
control={control}
render={({ field: { value, onChange }, fieldState: { error } }) => (
<FormControl
label="Access Token"
errorText={error?.message}
isError={Boolean(error?.message)}
tooltipText="Your GitLab Access Token"
>
<SecretInput
containerClassName="text-gray-400 group-focus-within:!border-primary-400/50 border border-mineshaft-500 bg-mineshaft-900 px-2.5 py-1.5"
value={value}
onChange={(e) => onChange(e.target.value)}
/>
</FormControl>
)}
/>
</>
)}
<div className="mt-8 flex items-center">
@@ -280,10 +318,10 @@ export const GitLabConnectionForm = ({ appConnection, onSubmit: formSubmit }: Pr
}
>
{isRedirecting && selectedMethod === GitlabConnectionMethod.OAuth
? "Redirecting to Gitlab..."
? "Redirecting to GitLab..."
: isUpdate
? "Reconnect to Gitlab"
: "Connect to Gitlab"}
? "Reconnect to GitLab"
: "Connect to GitLab"}
</Button>
<ModalClose asChild>
<Button colorSchema="secondary" variant="plain">