feature: octopus deploy integration

This commit is contained in:
Scott Wilson
2024-11-22 14:47:15 -08:00
parent d7b494c6f8
commit cf275979ba
38 changed files with 1229 additions and 25 deletions
+2 -1
View File
@@ -36,7 +36,8 @@ const integrationSlugNameMapping: Mapping = {
"hasura-cloud": "Hasura Cloud",
rundeck: "Rundeck",
"azure-devops": "Azure DevOps",
"azure-app-configuration": "Azure App Configuration"
"azure-app-configuration": "Azure App Configuration",
"octopus-deploy": "Octopus Deploy"
};
const envMapping: Mapping = {
Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

@@ -79,7 +79,8 @@ export const FilterableSelect = <T,>({ isMulti, closeMenuOnSelect, ...props }: P
),
placeholder: () => "text-mineshaft-400 text-sm pl-1 py-0.5",
input: () => "pl-1 py-0.5",
valueContainer: () => `p-1 max-h-[14rem] ${isMulti ? "!overflow-y-scroll" : ""} gap-1`,
valueContainer: () =>
`p-1 max-h-[14rem] ${isMulti ? "!overflow-y-auto thin-scrollbar" : ""} gap-1`,
singleValue: () => "leading-7 ml-1",
multiValue: () => "bg-mineshaft-600 rounded items-center py-0.5 px-2 gap-1.5",
multiValueLabel: () => "leading-6 text-sm",
@@ -17,7 +17,9 @@ import {
Project,
Service,
Team,
TeamCityBuildConfig
TeamCityBuildConfig,
TGetIntegrationAuthOctopusDeployScopeValuesDTO,
TOctopusDeployScopeValues
} from "./types";
const integrationAuthKeys = {
@@ -119,7 +121,14 @@ const integrationAuthKeys = {
}: {
integrationAuthId: string;
appId: string;
}) => [{ integrationAuthId, appId }, "integrationAuthTeamCityBranchConfigs"] as const
}) => [{ integrationAuthId, appId }, "integrationAuthTeamCityBranchConfigs"] as const,
getIntegrationAuthOctopusDeploySpaces: (integrationAuthId: string) =>
[{ integrationAuthId }, "getIntegrationAuthOctopusDeploySpaces"] as const,
getIntegrationAuthOctopusDeployScopeValues: ({
integrationAuthId,
...params
}: TGetIntegrationAuthOctopusDeployScopeValuesDTO) =>
[{ integrationAuthId }, "getIntegrationAuthOctopusDeployScopeValues", params] as const
};
const fetchIntegrationAuthById = async (integrationAuthId: string) => {
@@ -479,6 +488,28 @@ const fetchIntegrationAuthTeamCityBuildConfigs = async ({
return buildConfigs;
};
const fetchIntegrationAuthOctopusDeploySpaces = async (integrationAuthId: string) => {
const {
data: { spaces }
} = await apiRequest.get<{
spaces: { Name: string; Slug: string; Id: string; IsDefault: boolean }[];
}>(`/api/v1/integration-auth/${integrationAuthId}/octopus-deploy/spaces`);
return spaces;
};
const fetchIntegrationAuthOctopusDeployScopeValues = async ({
integrationAuthId,
scope,
spaceId,
resourceId
}: TGetIntegrationAuthOctopusDeployScopeValuesDTO) => {
const { data } = await apiRequest.get<TOctopusDeployScopeValues>(
`/api/v1/integration-auth/${integrationAuthId}/octopus-deploy/scope-values`,
{ params: { scope, spaceId, resourceId } }
);
return data;
};
export const useGetIntegrationAuthById = (integrationAuthId: string) => {
return useQuery({
queryKey: integrationAuthKeys.getIntegrationAuthById(integrationAuthId),
@@ -487,17 +518,24 @@ export const useGetIntegrationAuthById = (integrationAuthId: string) => {
});
};
export const useGetIntegrationAuthApps = ({
integrationAuthId,
teamId,
azureDevOpsOrgName,
workspaceSlug
}: {
integrationAuthId: string;
teamId?: string;
azureDevOpsOrgName?: string;
workspaceSlug?: string;
}) => {
export const useGetIntegrationAuthApps = (
{
integrationAuthId,
teamId,
azureDevOpsOrgName,
workspaceSlug
}: {
integrationAuthId: string;
teamId?: string;
azureDevOpsOrgName?: string;
workspaceSlug?: string;
},
options?: UseQueryOptions<
Awaited<ReturnType<typeof fetchIntegrationAuthApps>>,
unknown,
Awaited<ReturnType<typeof fetchIntegrationAuthApps>>
>
) => {
return useQuery({
queryKey: integrationAuthKeys.getIntegrationAuthApps(integrationAuthId, teamId, workspaceSlug),
queryFn: () =>
@@ -507,7 +545,7 @@ export const useGetIntegrationAuthApps = ({
azureDevOpsOrgName,
workspaceSlug
}),
enabled: true
...options
});
};
@@ -759,6 +797,23 @@ export const useGetIntegrationAuthBitBucketWorkspaces = (integrationAuthId: stri
});
};
export const useGetIntegrationAuthOctopusDeploySpaces = (integrationAuthId: string) => {
return useQuery({
queryKey: integrationAuthKeys.getIntegrationAuthBitBucketWorkspaces(integrationAuthId),
queryFn: () => fetchIntegrationAuthOctopusDeploySpaces(integrationAuthId)
});
};
export const useGetIntegrationAuthOctopusDeployScopeValues = (
params: TGetIntegrationAuthOctopusDeployScopeValuesDTO,
options?: UseQueryOptions<TOctopusDeployScopeValues, unknown, TOctopusDeployScopeValues>
) =>
useQuery({
queryKey: integrationAuthKeys.getIntegrationAuthOctopusDeployScopeValues(params),
queryFn: () => fetchIntegrationAuthOctopusDeployScopeValues(params),
...options
});
export const useGetIntegrationAuthBitBucketEnvironments = (
{
integrationAuthId,
@@ -99,3 +99,29 @@ export type TDuplicateIntegrationAuthDTO = {
integrationAuthId: string;
projectId: string;
};
export enum OctopusDeployScope {
Project = "project"
// tenant, variable set
}
export type TGetIntegrationAuthOctopusDeployScopeValuesDTO = {
integrationAuthId: string;
spaceId: string;
resourceId: string;
scope: OctopusDeployScope;
};
export type TOctopusDeployScopeValues = {
Environments: { Id: string; Name: string }[];
Machines: { Id: string; Name: string }[];
Actions: { Id: string; Name: string }[];
Roles: { Id: string; Name: string }[];
Channels: { Id: string; Name: string }[];
TenantTags: { Id: string; Name: string }[];
Processes: {
ProcessType: string;
Id: string;
Name: string;
}[];
};
@@ -87,6 +87,14 @@ export const useCreateIntegration = () => {
shouldMaskSecrets?: boolean;
shouldProtectSecrets?: boolean;
shouldEnableDelete?: boolean;
octopusDeployScopeValues?: {
Environment?: string[];
Action?: string[];
Channel?: string[];
Machine?: string[];
ProcessOwner?: string[];
Role?: string[];
};
};
}) => {
const {
@@ -181,7 +181,7 @@ export default function BitBucketCreateIntegrationPage() {
onChange={onChange}
options={currentWorkspace?.environments}
placeholder="Select a project environment"
isDisabled={!bitbucketWorkspaces?.length}
isDisabled={!currentWorkspace?.environments.length}
/>
</FormControl>
)}
@@ -0,0 +1,138 @@
import { Controller, useForm } from "react-hook-form";
import Head from "next/head";
import Image from "next/image";
import Link from "next/link";
import { useRouter } from "next/router";
import { faArrowUpRightFromSquare, faBookOpen } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import { createNotification } from "@app/components/notifications";
import { Button, Card, CardTitle, FormControl, Input } from "@app/components/v2";
import { useWorkspace } from "@app/context";
import { removeTrailingSlash } from "@app/helpers/string";
import { useSaveIntegrationAccessToken } from "@app/hooks/api";
const formSchema = z.object({
instanceUrl: z.string().min(1, { message: "Instance URL required" }),
apiKey: z.string().min(1, { message: "API Key required" })
});
type TForm = z.infer<typeof formSchema>;
export default function OctopusDeployIntegrationPage() {
const router = useRouter();
const { mutateAsync, isLoading } = useSaveIntegrationAccessToken();
const { currentWorkspace } = useWorkspace();
const { control, handleSubmit } = useForm<TForm>({
resolver: zodResolver(formSchema)
});
const onSubmit = async ({ instanceUrl, apiKey }: TForm) => {
try {
const integrationAuth = await mutateAsync({
workspaceId: currentWorkspace!.id,
integration: "octopus-deploy",
url: removeTrailingSlash(instanceUrl),
accessToken: apiKey
});
router.push(`/integrations/octopus-deploy/create?integrationAuthId=${integrationAuth.id}`);
} catch (err: any) {
createNotification({
type: "error",
text: err.message ?? "Error authorizing integration"
});
console.error(err);
}
};
return (
<form
onSubmit={handleSubmit(onSubmit)}
className="flex h-full w-full items-center justify-center"
>
<Head>
<title>Authorize Octopus Deploy Integration</title>
<link rel="icon" href="/infisical.ico" />
</Head>
<Card className="mb-12 max-w-lg rounded-md border border-mineshaft-600">
<CardTitle
className="px-6 text-left text-xl"
subTitle="After adding your credentials, you will be prompted to set up an integration for a particular environment and secret path."
>
<div className="flex flex-row items-center">
<div className="inline-flex items-center pb-0.5">
<Image
src="/images/integrations/Octopus Deploy.png"
height={30}
width={30}
alt="Databricks logo"
/>
</div>
<span className="ml-1.5">Octopus Deploy Integration</span>
<Link href="https://infisical.com/docs/integrations/cloud/octopus-deploy" passHref>
<a target="_blank" rel="noopener noreferrer">
<div className="ml-2 mb-1 inline-block cursor-default rounded-md bg-yellow/20 px-1.5 pb-[0.03rem] pt-[0.04rem] text-sm text-yellow opacity-80 hover:opacity-100">
<FontAwesomeIcon icon={faBookOpen} className="mr-1.5" />
Docs
<FontAwesomeIcon
icon={faArrowUpRightFromSquare}
className="ml-1.5 mb-[0.07rem] text-xxs"
/>
</div>
</a>
</Link>
</div>
</CardTitle>
<Controller
render={({ field: { value, onChange }, fieldState: { error } }) => (
<FormControl
label="Octopus Deploy Instance URL"
errorText={error?.message}
isError={Boolean(error)}
className="px-6"
>
<Input value={value} onChange={onChange} placeholder="https://xxxx.octopus.app" />
</FormControl>
)}
name="instanceUrl"
control={control}
/>
<Controller
render={({ field: { value, onChange }, fieldState: { error } }) => (
<FormControl
label="Octopus Deploy API Key"
errorText={error?.message}
isError={Boolean(error)}
className="px-6"
>
<Input
value={value}
onChange={onChange}
placeholder="API-XXXXXXXXXXXXXXXXXXXXXXXX"
type="password"
/>
</FormControl>
)}
name="apiKey"
control={control}
/>
<Button
type="submit"
colorSchema="primary"
variant="outline_bg"
className="mb-6 mt-2 ml-auto mr-6 w-min"
isLoading={isLoading}
isDisabled={isLoading}
>
Connect to Octopus Deploy
</Button>
</Card>
</form>
);
}
OctopusDeployIntegrationPage.requireAuth = true;
@@ -0,0 +1,443 @@
import { useEffect } from "react";
import { Controller, useForm } from "react-hook-form";
import { SiOctopusdeploy } from "react-icons/si";
import { useRouter } from "next/router";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import { createNotification } from "@app/components/notifications";
import {
Button,
Card,
CardTitle,
FilterableSelect,
FormControl,
Input,
Spinner
} from "@app/components/v2";
import { useWorkspace } from "@app/context";
import { useCreateIntegration, useGetIntegrationAuthApps } from "@app/hooks/api";
import {
useGetIntegrationAuthOctopusDeployScopeValues,
useGetIntegrationAuthOctopusDeploySpaces
} from "@app/hooks/api/integrationAuth/queries";
import { OctopusDeployScope } from "@app/hooks/api/integrationAuth/types";
const formSchema = z.object({
scope: z.nativeEnum(OctopusDeployScope),
secretPath: z.string().default("/"),
sourceEnvironment: z.object({ name: z.string(), slug: z.string() }),
targetSpace: z.object({ Name: z.string(), Id: z.string() }),
targetResource: z.object({ appId: z.string().optional(), name: z.string() }),
targetEnvironments: z.object({ Name: z.string(), Id: z.string() }).array().optional(),
targetRoles: z.object({ Name: z.string(), Id: z.string() }).array().optional(),
targetMachines: z.object({ Name: z.string(), Id: z.string() }).array().optional(),
targetProcesses: z
.object({ Name: z.string(), Id: z.string(), ProcessType: z.string() })
.array()
.optional(),
targetActions: z.object({ Name: z.string(), Id: z.string() }).array().optional(),
targetChannels: z.object({ Name: z.string(), Id: z.string() }).array().optional()
});
type TFormData = z.infer<typeof formSchema>;
export default function OctopusDeployCreateIntegrationPage() {
const router = useRouter();
const createIntegration = useCreateIntegration();
const { watch, control, reset, handleSubmit } = useForm<TFormData>({
resolver: zodResolver(formSchema),
defaultValues: {
secretPath: "/",
scope: OctopusDeployScope.Project
}
});
const integrationAuthId = router.query.integrationAuthId as string;
const { currentWorkspace, isLoading: isProjectLoading } = useWorkspace();
const { data: octopusDeploySpaces, isLoading: isLoadingOctopusDeploySpaces } =
useGetIntegrationAuthOctopusDeploySpaces((integrationAuthId as string) ?? "");
const currentSpace = watch("targetSpace", octopusDeploySpaces?.[0]);
const currentScope = watch("scope");
const { data: octopusDeployResources, isLoading: isOctopusDeployResourcesLoading } =
useGetIntegrationAuthApps(
{
integrationAuthId,
workspaceSlug: currentSpace?.Name
// scope once we support other resources than project
},
{
enabled: Boolean(currentSpace ?? octopusDeploySpaces?.find((space) => space.IsDefault))
}
);
const currentResource = watch("targetResource", octopusDeployResources?.[0]);
const { data: octopusDeployScopeValues, isLoading: isOctopusDeployScopeValuesLoading } =
useGetIntegrationAuthOctopusDeployScopeValues(
{
integrationAuthId,
spaceId: currentSpace?.Id,
resourceId: currentResource?.appId!,
scope: currentScope
},
{ enabled: Boolean(currentSpace && currentResource) }
);
const onSubmit = async ({
sourceEnvironment,
secretPath,
targetEnvironments,
targetResource,
targetSpace,
targetChannels,
targetActions,
targetMachines,
targetProcesses,
targetRoles,
scope
}: TFormData) => {
try {
await createIntegration.mutateAsync({
integrationAuthId,
isActive: true,
scope,
app: targetResource.name,
appId: targetResource.appId,
targetEnvironment: targetSpace.Name,
targetEnvironmentId: targetSpace.Id,
metadata: {
octopusDeployScopeValues: {
Environment: targetEnvironments?.map(({ Id }) => Id),
Action: targetActions?.map(({ Id }) => Id),
Channel: targetChannels?.map(({ Id }) => Id),
ProcessOwner: targetProcesses?.map(({ Id }) => Id),
Role: targetRoles?.map(({ Id }) => Id),
Machine: targetMachines?.map(({ Id }) => Id)
}
},
sourceEnvironment: sourceEnvironment.slug,
secretPath
});
createNotification({
type: "success",
text: "Successfully created integration"
});
router.push(`/integrations/${currentWorkspace?.id}`);
} catch (err) {
createNotification({
type: "error",
text: "Failed to create integration"
});
console.error(err);
}
};
useEffect(() => {
if (!octopusDeployResources || !octopusDeploySpaces || !currentWorkspace) return;
reset({
targetResource: octopusDeployResources[0],
targetSpace: octopusDeploySpaces.find((space) => space.IsDefault),
sourceEnvironment: currentWorkspace.environments[0],
secretPath: "/",
scope: OctopusDeployScope.Project
});
}, [octopusDeploySpaces, octopusDeployResources, currentWorkspace]);
if (isProjectLoading || isLoadingOctopusDeploySpaces || isOctopusDeployResourcesLoading)
return (
<div className="flex h-full w-full items-center justify-center p-24">
<Spinner />
</div>
);
return (
<form
onSubmit={handleSubmit(onSubmit)}
className="flex h-full w-full items-center justify-center"
>
<Card className="max-w-4xl rounded-md p-8 pt-4">
<CardTitle className=" text-center">
<SiOctopusdeploy size="1.2rem" className="mr-2 mb-1 inline-block" />
Octopus Deploy Integration
</CardTitle>
<div className="grid grid-cols-2 gap-4">
<Controller
control={control}
name="sourceEnvironment"
render={({ field: { value, onChange }, fieldState: { error } }) => (
<FormControl
errorText={error?.message}
isError={Boolean(error)}
label="Project Environment"
>
<FilterableSelect
getOptionValue={(option) => option.slug}
value={value}
getOptionLabel={(option) => option.name}
onChange={onChange}
options={currentWorkspace?.environments}
placeholder="Select a project environment"
isDisabled={!currentWorkspace?.environments.length}
/>
</FormControl>
)}
/>
<Controller
control={control}
name="secretPath"
render={({ field: { value, onChange }, fieldState: { error } }) => (
<FormControl isError={Boolean(error)} label="Secrets Path">
<Input
className="mt-[1px] h-[2.46rem]"
value={value}
onChange={onChange}
placeholder={'Provide a path (defaults to "/")'}
/>
</FormControl>
)}
/>
<div className="col-span-2 flex w-full flex-row items-center pb-2">
<div className="w-full border-t border-mineshaft-500" />
<span className="mx-2 whitespace-nowrap text-xs text-mineshaft-400">Sync To</span>
<div className="w-full border-t border-mineshaft-500" />
</div>
<Controller
control={control}
name="targetSpace"
render={({ field: { value, onChange }, fieldState: { error } }) => (
<FormControl
errorText={error?.message}
isError={Boolean(error)}
label="Octopus Deploy Space"
>
<FilterableSelect
getOptionValue={(option) => option.Id}
value={value}
getOptionLabel={(option) => option.Name}
onChange={onChange}
options={octopusDeploySpaces}
placeholder={
octopusDeploySpaces?.length ? "Select a space..." : "No spaces found..."
}
isDisabled={!octopusDeploySpaces?.length}
/>
</FormControl>
)}
/>
<Controller
control={control}
name="targetResource"
render={({ field: { value, onChange }, fieldState: { error } }) => (
<FormControl
errorText={error?.message}
isError={Boolean(error)}
className="capitalize"
label={`Octopus Deploy ${currentScope}`}
>
<FilterableSelect
getOptionValue={(option) => option.appId!}
value={value}
getOptionLabel={(option) => option.name}
onChange={onChange}
options={octopusDeployResources}
placeholder={
octopusDeployResources?.length ? "Select a project..." : "No projects found..."
}
isDisabled={!octopusDeployResources?.length}
/>
</FormControl>
)}
/>
<Controller
control={control}
name="targetEnvironments"
render={({ field: { value, onChange }, fieldState: { error } }) => (
<FormControl
errorText={error?.message}
isError={Boolean(error)}
label="Octopus Deploy Environments"
isOptional
>
<FilterableSelect
isMulti
getOptionValue={(option) => option.Name}
value={value}
getOptionLabel={(option) => option.Name}
onChange={onChange}
isLoading={isOctopusDeployScopeValuesLoading}
options={octopusDeployScopeValues?.Environments}
placeholder={
octopusDeployScopeValues?.Environments?.length
? "Select environments..."
: "No environments found..."
}
isDisabled={!octopusDeployScopeValues?.Environments?.length}
/>
</FormControl>
)}
/>
<Controller
control={control}
name="targetRoles"
render={({ field: { value, onChange }, fieldState: { error } }) => (
<FormControl
errorText={error?.message}
isError={Boolean(error)}
label="Octopus Deploy Target Tags"
isOptional
>
<FilterableSelect
isMulti
getOptionValue={(option) => option.Name}
value={value}
getOptionLabel={(option) => option.Name}
onChange={onChange}
isLoading={isOctopusDeployScopeValuesLoading}
options={octopusDeployScopeValues?.Roles}
placeholder={
octopusDeployScopeValues?.Roles?.length
? "Select target tags..."
: "No target tags found..."
}
isDisabled={!octopusDeployScopeValues?.Roles?.length}
/>
</FormControl>
)}
/>
<Controller
control={control}
name="targetMachines"
render={({ field: { value, onChange }, fieldState: { error } }) => (
<FormControl
errorText={error?.message}
isError={Boolean(error)}
label="Octopus Deploy Targets"
isOptional
>
<FilterableSelect
isMulti
getOptionValue={(option) => option.Name}
value={value}
getOptionLabel={(option) => option.Name}
onChange={onChange}
isLoading={isOctopusDeployScopeValuesLoading}
options={octopusDeployScopeValues?.Machines}
placeholder={
octopusDeployScopeValues?.Machines?.length
? "Select targets..."
: "No targets found..."
}
isDisabled={!octopusDeployScopeValues?.Machines?.length}
/>
</FormControl>
)}
/>
<Controller
control={control}
name="targetProcesses"
render={({ field: { value, onChange }, fieldState: { error } }) => (
<FormControl
errorText={error?.message}
isError={Boolean(error)}
label="Octopus Deploy Processes"
isOptional
>
<FilterableSelect
isMulti
getOptionValue={(option) => option.Name}
value={value}
getOptionLabel={(option) => option.Name}
onChange={onChange}
isLoading={isOctopusDeployScopeValuesLoading}
options={octopusDeployScopeValues?.Processes}
placeholder={
octopusDeployScopeValues?.Processes?.length
? "Select processes..."
: "No processes found..."
}
isDisabled={!octopusDeployScopeValues?.Processes?.length}
/>
</FormControl>
)}
/>
<Controller
control={control}
name="targetActions"
render={({ field: { value, onChange }, fieldState: { error } }) => (
<FormControl
errorText={error?.message}
isError={Boolean(error)}
label="Octopus Deploy Deployment Steps"
isOptional
>
<FilterableSelect
isMulti
getOptionValue={(option) => option.Name}
value={value}
getOptionLabel={(option) => option.Name}
onChange={onChange}
isLoading={isOctopusDeployScopeValuesLoading}
options={octopusDeployScopeValues?.Actions}
placeholder={
octopusDeployScopeValues?.Actions?.length
? "Select deployment steps..."
: "No deployment steps found..."
}
isDisabled={!octopusDeployScopeValues?.Actions?.length}
/>
</FormControl>
)}
/>
<Controller
control={control}
name="targetChannels"
render={({ field: { value, onChange }, fieldState: { error } }) => (
<FormControl
errorText={error?.message}
isError={Boolean(error)}
label="Octopus Deploy Channels"
isOptional
>
<FilterableSelect
isMulti
getOptionValue={(option) => option.Name}
value={value}
getOptionLabel={(option) => option.Name}
onChange={onChange}
isLoading={isOctopusDeployScopeValuesLoading}
options={octopusDeployScopeValues?.Channels}
placeholder={
octopusDeployScopeValues?.Channels?.length
? "Select channels..."
: "No channels found..."
}
isDisabled={!octopusDeployScopeValues?.Channels?.length}
/>
</FormControl>
)}
/>
</div>
<Button
type="submit"
colorSchema="primary"
className="mt-4"
isLoading={createIntegration.isLoading}
isDisabled={createIntegration.isLoading || !octopusDeployResources?.length}
>
Create Integration
</Button>
</Card>
</form>
);
}
OctopusDeployCreateIntegrationPage.requireAuth = true;
@@ -140,6 +140,9 @@ export const redirectForProviderAuth = (integrationOption: TCloudIntegration) =>
case "azure-devops":
link = `${window.location.origin}/integrations/azure-devops/authorize`;
break;
case "octopus-deploy":
link = `${window.location.origin}/integrations/octopus-deploy/authorize`;
break;
default:
break;
}
@@ -76,6 +76,14 @@ export const ConfiguredIntegrationItem = ({
{integrationSlugNameMapping[integration.integration]}
</div>
</div>
{integration.integration === "octopus-deploy" && (
<div className="ml-2 flex flex-col">
<FormLabel label="Space" />
<div className="overflow-clip text-ellipsis whitespace-nowrap rounded-md border border-mineshaft-700 bg-mineshaft-900 px-3 py-2 font-inter text-sm text-bunker-200">
{integration.targetEnvironment || integration.targetEnvironmentId}
</div>
</div>
)}
{integration.integration === "qovery" && (
<div className="flex flex-row">
<div className="ml-2 flex flex-col">
@@ -108,6 +116,7 @@ export const ConfiguredIntegrationItem = ({
(integration.integration === "qovery" && integration?.scope) ||
(integration.integration === "circleci" && "Project") ||
(integration.integration === "bitbucket" && "Repository") ||
(integration.integration === "octopus-deploy" && "Project") ||
(integration.integration === "aws-secret-manager" && "Secret") ||
(["aws-parameter-store", "rundeck"].includes(integration.integration) && "Path") ||
(integration?.integration === "terraform-cloud" && "Project") ||