Merge remote-tracking branch 'origin' into railway

This commit is contained in:
Tuan Dang
2023-04-08 22:17:58 +03:00
13 changed files with 1070 additions and 344 deletions

1037
backend/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{ {
"dependencies": { "dependencies": {
"@aws-sdk/client-secrets-manager": "^3.281.0", "@aws-sdk/client-secrets-manager": "^3.287.0",
"@godaddy/terminus": "^4.11.2", "@godaddy/terminus": "^4.11.2",
"@octokit/rest": "^19.0.5", "@octokit/rest": "^19.0.5",
"@sentry/tracing": "^7.39.0", "@sentry/tracing": "^7.39.0",
@@ -8,7 +8,7 @@
"@types/crypto-js": "^4.1.1", "@types/crypto-js": "^4.1.1",
"@types/libsodium-wrappers": "^0.7.10", "@types/libsodium-wrappers": "^0.7.10",
"await-to-js": "^3.0.0", "await-to-js": "^3.0.0",
"aws-sdk": "^2.1324.0", "aws-sdk": "^2.1331.0",
"axios": "^1.1.3", "axios": "^1.1.3",
"axios-retry": "^3.4.0", "axios-retry": "^3.4.0",
"bcrypt": "^5.1.0", "bcrypt": "^5.1.0",
@@ -29,7 +29,7 @@
"jsrp": "^0.2.4", "jsrp": "^0.2.4",
"libsodium-wrappers": "^0.7.10", "libsodium-wrappers": "^0.7.10",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"mongoose": "^6.10.2", "mongoose": "^6.10.3",
"nodemailer": "^6.8.0", "nodemailer": "^6.8.0",
"posthog-node": "^2.5.4", "posthog-node": "^2.5.4",
"query-string": "^7.1.3", "query-string": "^7.1.3",

View File

@@ -12,6 +12,10 @@ import {
getTeams, getTeams,
revokeAccess revokeAccess
} from '../../integrations'; } from '../../integrations';
import {
INTEGRATION_VERCEL_API_URL
} from '../../variables';
import request from '../../config/request';
/*** /***
* Return integration authorization with id [integrationAuthId] * Return integration authorization with id [integrationAuthId]
@@ -188,25 +192,60 @@ export const getIntegrationAuthApps = async (req: Request, res: Response) => {
* @returns * @returns
*/ */
export const getIntegrationAuthTeams = async (req: Request, res: Response) => { export const getIntegrationAuthTeams = async (req: Request, res: Response) => {
let teams; const teams = await getTeams({
try { integrationAuth: req.integrationAuth,
teams = await getTeams({ accessToken: req.accessToken
integrationAuth: req.integrationAuth, });
accessToken: req.accessToken
});
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: "Failed to get integration authorization teams"
});
}
return res.status(200).send({ return res.status(200).send({
teams teams
}); });
} }
/**
* Return list of available Vercel (preview) branches
* @param req
* @param res
*/
export const getIntegrationAuthVercelBranches = async (req: Request, res: Response) => {
const { integrationAuthId } = req.params;
const appId = req.query.appId as string;
interface VercelBranch {
ref: string;
lastCommit: string;
isProtected: boolean;
}
const params = new URLSearchParams({
projectId: appId,
...(req.integrationAuth.teamId ? {
teamId: req.integrationAuth.teamId
} : {})
});
let branches: string[] = [];
if (appId && appId !== '') {
const { data }: { data: VercelBranch[] } = await request.get(
`${INTEGRATION_VERCEL_API_URL}/v1/integrations/git-branches`,
{
params,
headers: {
Authorization: `Bearer ${req.accessToken}`,
'Accept-Encoding': 'application/json'
}
}
);
branches = data.map((b) => b.ref);
}
return res.status(200).send({
branches
});
}
/** /**
* Delete integration authorization with id [integrationAuthId] * Delete integration authorization with id [integrationAuthId]
* @param req * @param req

View File

@@ -191,6 +191,7 @@ const getAppsVercel = async ({
apps = res.projects.map((a: any) => ({ apps = res.projects.map((a: any) => ({
name: a.name, name: a.name,
appId: a.id
})); }));
} catch (err) { } catch (err) {
Sentry.setUser(null); Sentry.setUser(null);
@@ -277,10 +278,13 @@ const getAppsGithub = async ({ accessToken }: { accessToken: string }) => {
apps = repos apps = repos
.filter((a: any) => a.permissions.admin === true) .filter((a: any) => a.permissions.admin === true)
.map((a: any) => ({ .map((a: any) => {
name: a.name, return ({
owner: a.owner.login, appId: a.id,
})); name: a.name,
owner: a.owner.login,
});
});
} catch (err) { } catch (err) {
Sentry.setUser(null); Sentry.setUser(null);
Sentry.captureException(err); Sentry.captureException(err);

View File

@@ -608,6 +608,7 @@ const syncSecretsVercel = async ({
key: string; key: string;
value: string; value: string;
target: string[]; target: string[];
gitBranch?: string;
} }
try { try {
@@ -621,46 +622,7 @@ const syncSecretsVercel = async ({
} }
: {}), : {}),
}; };
// const res = (
// await Promise.all(
// (
// await request.get(
// `${INTEGRATION_VERCEL_API_URL}/v9/projects/${integration.app}/env`,
// {
// params,
// headers: {
// Authorization: `Bearer ${accessToken}`,
// 'Accept-Encoding': 'application/json'
// }
// }
// ))
// .data
// .envs
// .filter((secret: VercelSecret) => secret.target.includes(integration.targetEnvironment))
// .map(async (secret: VercelSecret) => {
// if (secret.type === 'encrypted') {
// // case: secret is encrypted -> need to decrypt
// const decryptedSecret = (await request.get(
// `${INTEGRATION_VERCEL_API_URL}/v9/projects/${integration.app}/env/${secret.id}`,
// {
// params,
// headers: {
// Authorization: `Bearer ${accessToken}`,
// 'Accept-Encoding': 'application/json'
// }
// }
// )).data;
// return decryptedSecret;
// }
// return secret;
// }))).reduce((obj: any, secret: any) => ({
// ...obj,
// [secret.key]: secret
// }), {});
const vercelSecrets: VercelSecret[] = (await request.get( const vercelSecrets: VercelSecret[] = (await request.get(
`${INTEGRATION_VERCEL_API_URL}/v9/projects/${integration.app}/env`, `${INTEGRATION_VERCEL_API_URL}/v9/projects/${integration.app}/env`,
{ {
@@ -673,7 +635,21 @@ const syncSecretsVercel = async ({
)) ))
.data .data
.envs .envs
.filter((secret: VercelSecret) => secret.target.includes(integration.targetEnvironment)); .filter((secret: VercelSecret) => {
if (!secret.target.includes(integration.targetEnvironment)) {
// case: secret does not have the same target environment
return false;
}
if (integration.targetEnvironment === 'preview' && integration.path && integration.path !== secret.gitBranch) {
// case: secret on preview environment does not have same target git branch
return false;
}
return true;
});
// return secret.target.includes(integration.targetEnvironment);
const res: { [key: string]: VercelSecret } = {}; const res: { [key: string]: VercelSecret } = {};
@@ -696,7 +672,7 @@ const syncSecretsVercel = async ({
res[vercelSecret.key] = vercelSecret; res[vercelSecret.key] = vercelSecret;
} }
} }
const updateSecrets: VercelSecret[] = []; const updateSecrets: VercelSecret[] = [];
const deleteSecrets: VercelSecret[] = []; const deleteSecrets: VercelSecret[] = [];
const newSecrets: VercelSecret[] = []; const newSecrets: VercelSecret[] = [];
@@ -710,6 +686,9 @@ const syncSecretsVercel = async ({
value: secrets[key], value: secrets[key],
type: "encrypted", type: "encrypted",
target: [integration.targetEnvironment], target: [integration.targetEnvironment],
...(integration.path ? {
gitBranch: integration.path
} : {})
}); });
} }
}); });
@@ -726,7 +705,10 @@ const syncSecretsVercel = async ({
type: res[key].type, type: res[key].type,
target: res[key].target.includes(integration.targetEnvironment) target: res[key].target.includes(integration.targetEnvironment)
? [...res[key].target] ? [...res[key].target]
: [...res[key].target, integration.targetEnvironment] : [...res[key].target, integration.targetEnvironment],
...(integration.path ? {
gitBranch: integration.path
} : {})
}); });
} }
} else { } else {
@@ -737,6 +719,9 @@ const syncSecretsVercel = async ({
value: res[key].value, value: res[key].value,
type: "encrypted", // value doesn't matter type: "encrypted", // value doesn't matter
target: [integration.targetEnvironment], target: [integration.targetEnvironment],
...(integration.path ? {
gitBranch: integration.path
} : {})
}); });
} }
}); });
@@ -1060,7 +1045,7 @@ const syncSecretsGitHub = async ({
"GET /repos/{owner}/{repo}/actions/secrets/public-key", "GET /repos/{owner}/{repo}/actions/secrets/public-key",
{ {
owner: integration.owner, owner: integration.owner,
repo: integration.app, repo: integration.app
} }
) )
).data; ).data;

View File

@@ -80,6 +80,7 @@ const integrationSchema = new Schema<IIntegration>(
}, },
path: { path: {
// aws-parameter-store-specific path // aws-parameter-store-specific path
// (also) vercel preview-branch
type: String, type: String,
default: null default: null
}, },

View File

@@ -96,6 +96,21 @@ router.get(
integrationAuthController.getIntegrationAuthTeams integrationAuthController.getIntegrationAuthTeams
); );
router.get(
'/:integrationAuthId/vercel/branches',
requireAuth({
acceptedAuthModes: ['jwt']
}),
requireIntegrationAuthorizationAuth({
acceptedRoles: [ADMIN, MEMBER]
}),
param('integrationAuthId').exists().isString(),
query('appId').exists().isString(),
query('teamId').optional().isString(),
validateRequest,
integrationAuthController.getIntegrationAuthVercelBranches
);
router.delete( router.delete(
'/:integrationAuthId', '/:integrationAuthId',
requireAuth({ requireAuth({

View File

@@ -109,15 +109,33 @@ infisical login
<Accordion title="Optional: point CLI to self-hosted"> <Accordion title="Optional: point CLI to self-hosted">
The CLI is set to connect to Infisical Cloud by default, but if you're running your own instance of Infisical, you can direct the CLI to it using one of the methods provided below. The CLI is set to connect to Infisical Cloud by default, but if you're running your own instance of Infisical, you can direct the CLI to it using one of the methods provided below.
#### Export environment variable #### Method 1: Export environment variable
You can point the CLI to the self hosted Infisical instance by exporting the environment variable `INFISICAL_API_URL` in your terminal. You can point the CLI to the self hosted Infisical instance by exporting the environment variable `INFISICAL_API_URL` in your terminal.
```bash <Tabs>
# Example <Tab title="Linux/MacOs">
export INFISICAL_API_URL="https://your-self-hosted-infisical.com/api" ```bash
``` # Set backend host
export INFISICAL_API_URL="https://your-self-hosted-infisical.com/api"
#### Set manually on every command # Remove backend host
unset INFISICAL_API_URL
```
</Tab>
<Tab title="Windows Powershell">
```bash
# Set backend host
setx INFISICAL_API_URL "https://your-self-hosted-infisical.com/api"
# Remove backend host
setx INFISICAL_API_URL ""
# NOTE: Once set or removed, please restart powershell for the change to take effect
```
</Tab>
</Tabs>
#### Method 2: Set manually on every command
Another option to point the CLI to your self hosted Infisical instance is to set it via a flag on every command you run. Another option to point the CLI to your self hosted Infisical instance is to set it via a flag on every command you run.
```bash ```bash

View File

@@ -1,4 +1,6 @@
export { export {
useGetIntegrationAuthApps, useGetIntegrationAuthApps,
useGetIntegrationAuthById, useGetIntegrationAuthById,
useGetIntegrationAuthTeams} from './queries'; useGetIntegrationAuthTeams,
useGetIntegrationAuthVercelBranches
} from './queries';

View File

@@ -5,12 +5,20 @@ import { apiRequest } from "@app/config/request";
import { import {
App, App,
IntegrationAuth, IntegrationAuth,
Team} from './types'; Team
} from './types';
const integrationAuthKeys = { const integrationAuthKeys = {
getIntegrationAuthById: (integrationAuthId: string) => [{ integrationAuthId }, 'integrationAuth'] as const, getIntegrationAuthById: (integrationAuthId: string) => [{ integrationAuthId }, 'integrationAuth'] as const,
getIntegrationAuthApps: (integrationAuthId: string, teamId?: string) => [{ integrationAuthId, teamId }, 'integrationAuthApps'] as const, getIntegrationAuthApps: (integrationAuthId: string, teamId?: string) => [{ integrationAuthId, teamId }, 'integrationAuthApps'] as const,
getIntegrationAuthTeams: (integrationAuthId: string) => [{ integrationAuthId }, 'integrationAuthTeams'] as const getIntegrationAuthTeams: (integrationAuthId: string) => [{ integrationAuthId }, 'integrationAuthTeams'] as const,
getIntegrationAuthVercelBranches: ({
integrationAuthId,
appId,
}: {
integrationAuthId: string;
appId: string;
}) => [{ integrationAuthId, appId }, 'integrationAuthVercelBranches']
} }
const fetchIntegrationAuthById = async (integrationAuthId: string) => { const fetchIntegrationAuthById = async (integrationAuthId: string) => {
@@ -38,6 +46,22 @@ const fetchIntegrationAuthTeams = async (integrationAuthId: string) => {
return data.teams; return data.teams;
} }
const fetchIntegrationAuthVercelBranches = async ({
integrationAuthId,
appId
}: {
integrationAuthId: string;
appId: string;
}) => {
const { data: { branches } } = await apiRequest.get<{ branches: string[] }>(`/api/v1/integration-auth/${integrationAuthId}/vercel/branches`, {
params: {
appId
}
});
return branches;
};
export const useGetIntegrationAuthById = (integrationAuthId: string) => { export const useGetIntegrationAuthById = (integrationAuthId: string) => {
return useQuery({ return useQuery({
queryKey: integrationAuthKeys.getIntegrationAuthById(integrationAuthId), queryKey: integrationAuthKeys.getIntegrationAuthById(integrationAuthId),
@@ -46,7 +70,6 @@ export const useGetIntegrationAuthById = (integrationAuthId: string) => {
}); });
} }
// TODO: fix to teamId
export const useGetIntegrationAuthApps = ({ export const useGetIntegrationAuthApps = ({
integrationAuthId, integrationAuthId,
teamId teamId
@@ -70,4 +93,24 @@ export const useGetIntegrationAuthTeams = (integrationAuthId: string) => {
queryFn: () => fetchIntegrationAuthTeams(integrationAuthId), queryFn: () => fetchIntegrationAuthTeams(integrationAuthId),
enabled: true enabled: true
}); });
} }
export const useGetIntegrationAuthVercelBranches = ({
integrationAuthId,
appId,
}: {
integrationAuthId: string;
appId: string;
}) => {
return useQuery({
queryKey: integrationAuthKeys.getIntegrationAuthVercelBranches({
integrationAuthId,
appId,
}),
queryFn: () => fetchIntegrationAuthVercelBranches({
integrationAuthId,
appId,
}),
enabled: true
});
}

View File

@@ -27,8 +27,7 @@ export default function GitHubCreateIntegrationPage() {
}); });
const [selectedSourceEnvironment, setSelectedSourceEnvironment] = useState(''); const [selectedSourceEnvironment, setSelectedSourceEnvironment] = useState('');
const [owner, setOwner] = useState<string | null>(null); const [targetAppId, setTargetAppId] = useState('');
const [targetApp, setTargetApp] = useState('');
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
@@ -41,10 +40,9 @@ export default function GitHubCreateIntegrationPage() {
useEffect(() => { useEffect(() => {
if (integrationAuthApps) { if (integrationAuthApps) {
if (integrationAuthApps.length > 0) { if (integrationAuthApps.length > 0) {
setTargetApp(integrationAuthApps[0].name); setTargetAppId(integrationAuthApps[0].appId as string);
setOwner(integrationAuthApps[0]?.owner ?? null);
} else { } else {
setTargetApp('none'); setTargetAppId('none');
} }
} }
}, [integrationAuthApps]); }, [integrationAuthApps]);
@@ -55,14 +53,18 @@ export default function GitHubCreateIntegrationPage() {
if (!integrationAuth?._id) return; if (!integrationAuth?._id) return;
const targetApp = integrationAuthApps?.find((integrationAuthApp) => integrationAuthApp.appId === targetAppId);
if (!targetApp || !targetApp.owner) return;
await createIntegration({ await createIntegration({
integrationAuthId: integrationAuth?._id, integrationAuthId: integrationAuth?._id,
isActive: true, isActive: true,
app: targetApp, app: targetApp.name,
appId: null, appId: null,
sourceEnvironment: selectedSourceEnvironment, sourceEnvironment: selectedSourceEnvironment,
targetEnvironment: null, targetEnvironment: null,
owner, owner: targetApp.owner,
path: null, path: null,
region: null region: null
}); });
@@ -76,7 +78,7 @@ export default function GitHubCreateIntegrationPage() {
} }
} }
return (integrationAuth && workspace && selectedSourceEnvironment && integrationAuthApps && targetApp) ? ( return (integrationAuth && workspace && selectedSourceEnvironment && integrationAuthApps && targetAppId) ? (
<div className="h-full w-full flex justify-center items-center"> <div className="h-full w-full flex justify-center items-center">
<Card className="max-w-md p-8 rounded-md"> <Card className="max-w-md p-8 rounded-md">
<CardTitle className='text-center'>GitHub Integration</CardTitle> <CardTitle className='text-center'>GitHub Integration</CardTitle>
@@ -101,14 +103,14 @@ export default function GitHubCreateIntegrationPage() {
className='mt-4' className='mt-4'
> >
<Select <Select
value={targetApp} value={targetAppId}
onValueChange={(val) => setTargetApp(val)} onValueChange={(val) => setTargetAppId(val)}
className='w-full border border-mineshaft-500' className='w-full border border-mineshaft-500'
isDisabled={integrationAuthApps.length === 0} isDisabled={integrationAuthApps.length === 0}
> >
{integrationAuthApps.length > 0 ? ( {integrationAuthApps.length > 0 ? (
integrationAuthApps.map((integrationAuthApp) => ( integrationAuthApps.map((integrationAuthApp) => (
<SelectItem value={integrationAuthApp.name} key={`github-environment-${integrationAuthApp.name}`}> <SelectItem value={integrationAuthApp.appId as string} key={`github-repo-${integrationAuthApp.appId}`}>
{integrationAuthApp.name} {integrationAuthApp.name}
</SelectItem> </SelectItem>
)) ))

View File

@@ -109,7 +109,6 @@ export default function GitLabCreateIntegrationPage() {
<CardTitle className='text-center'>GitLab Integration</CardTitle> <CardTitle className='text-center'>GitLab Integration</CardTitle>
<FormControl <FormControl
label="Project Environment" label="Project Environment"
className='mt-4'
> >
<Select <Select
value={selectedSourceEnvironment} value={selectedSourceEnvironment}
@@ -125,7 +124,6 @@ export default function GitLabCreateIntegrationPage() {
</FormControl> </FormControl>
<FormControl <FormControl
label="GitLab Integration Type" label="GitLab Integration Type"
className='mt-4'
> >
<Select <Select
value={targetEntity} value={targetEntity}
@@ -144,7 +142,6 @@ export default function GitLabCreateIntegrationPage() {
{targetEntity === 'group' && targetTeamId && ( {targetEntity === 'group' && targetTeamId && (
<FormControl <FormControl
label="GitLab Group" label="GitLab Group"
className='mt-4'
> >
<Select <Select
value={targetTeamId} value={targetTeamId}
@@ -167,7 +164,6 @@ export default function GitLabCreateIntegrationPage() {
)} )}
<FormControl <FormControl
label="GitLab Project" label="GitLab Project"
className='mt-4'
> >
<Select <Select
value={targetAppId} value={targetAppId}

View File

@@ -4,14 +4,18 @@ import queryString from 'query-string';
import { getTranslatedServerSideProps } from '../../../components/utilities/withTranslateProps'; import { getTranslatedServerSideProps } from '../../../components/utilities/withTranslateProps';
import { import {
Button, Button,
Card, Card,
CardTitle, CardTitle,
FormControl, FormControl,
Select, Select,
SelectItem SelectItem
} from '../../../components/v2'; } from '../../../components/v2';
import { useGetIntegrationAuthApps,useGetIntegrationAuthById } from '../../../hooks/api/integrationAuth'; import {
useGetIntegrationAuthApps,
useGetIntegrationAuthById,
useGetIntegrationAuthVercelBranches
} from '../../../hooks/api/integrationAuth';
import { useGetWorkspaceById } from '../../../hooks/api/workspace'; import { useGetWorkspaceById } from '../../../hooks/api/workspace';
import createIntegration from "../../api/integrations/createIntegration"; import createIntegration from "../../api/integrations/createIntegration";
@@ -24,20 +28,28 @@ const vercelEnvironments = [
export default function VercelCreateIntegrationPage() { export default function VercelCreateIntegrationPage() {
const router = useRouter(); const router = useRouter();
const { integrationAuthId } = queryString.parse(router.asPath.split('?')[1]); const [selectedSourceEnvironment, setSelectedSourceEnvironment] = useState('');
const [targetAppId, setTargetAppId] = useState('');
const [targetEnvironment, setTargetEnvironment] = useState('');
const [targetBranch, setTargetBranch] = useState('');
const [isLoading, setIsLoading] = useState(false);
const { integrationAuthId } = queryString.parse(router.asPath.split('?')[1]);
const { data: workspace } = useGetWorkspaceById(localStorage.getItem('projectData.id') ?? ''); const { data: workspace } = useGetWorkspaceById(localStorage.getItem('projectData.id') ?? '');
const { data: integrationAuth } = useGetIntegrationAuthById(integrationAuthId as string ?? ''); const { data: integrationAuth } = useGetIntegrationAuthById(integrationAuthId as string ?? '');
const { data: integrationAuthApps } = useGetIntegrationAuthApps({ const { data: integrationAuthApps } = useGetIntegrationAuthApps({
integrationAuthId: integrationAuthId as string ?? '' integrationAuthId: integrationAuthId as string ?? ''
}); });
const [selectedSourceEnvironment, setSelectedSourceEnvironment] = useState(''); const { data: branches } = useGetIntegrationAuthVercelBranches({
const [targetApp, setTargetApp] = useState(''); integrationAuthId: integrationAuthId as string,
const [targetEnvironment, setTargetEnvironment] = useState(''); appId: targetAppId,
});
const [isLoading, setIsLoading] = useState(false);
const filteredBranches = branches
?.filter((branchName) => branchName !== 'main')
.concat('');
useEffect(() => { useEffect(() => {
if (workspace) { if (workspace) {
setSelectedSourceEnvironment(workspace.environments[0].slug); setSelectedSourceEnvironment(workspace.environments[0].slug);
@@ -45,15 +57,15 @@ export default function VercelCreateIntegrationPage() {
}, [workspace]); }, [workspace]);
useEffect(() => { useEffect(() => {
if (integrationAuthApps) { if (integrationAuthApps) {
if (integrationAuthApps.length > 0) { if (integrationAuthApps.length > 0) {
setTargetApp(integrationAuthApps[0].name); setTargetAppId(integrationAuthApps[0].appId as string);
setTargetEnvironment(vercelEnvironments[0].slug); setTargetEnvironment(vercelEnvironments[0].slug);
} else { } else {
setTargetApp('none'); setTargetAppId('none');
setTargetEnvironment(vercelEnvironments[0].slug); setTargetEnvironment(vercelEnvironments[0].slug);
}
} }
}
}, [integrationAuthApps]); }, [integrationAuthApps]);
const handleButtonClick = async () => { const handleButtonClick = async () => {
@@ -61,15 +73,22 @@ export default function VercelCreateIntegrationPage() {
if (!integrationAuth?._id) return; if (!integrationAuth?._id) return;
setIsLoading(true); setIsLoading(true);
const targetApp = integrationAuthApps?.find((integrationAuthApp) => integrationAuthApp.appId === targetAppId);
if (!targetApp || !targetApp.appId) return;
const path = (targetEnvironment === 'preview' && targetBranch !== '') ? targetBranch : null;
await createIntegration({ await createIntegration({
integrationAuthId: integrationAuth?._id, integrationAuthId: integrationAuth?._id,
isActive: true, isActive: true,
app: targetApp, app: targetApp.name,
appId: null, appId: targetApp.appId,
sourceEnvironment: selectedSourceEnvironment, sourceEnvironment: selectedSourceEnvironment,
targetEnvironment, targetEnvironment,
owner: null, owner: null,
path: null, path,
region: null region: null
}); });
@@ -82,7 +101,7 @@ export default function VercelCreateIntegrationPage() {
} }
} }
return (integrationAuth && workspace && selectedSourceEnvironment && integrationAuthApps && targetApp && targetEnvironment) ? ( return (integrationAuth && workspace && selectedSourceEnvironment && integrationAuthApps && targetAppId && targetEnvironment) ? (
<div className="h-full w-full flex justify-center items-center"> <div className="h-full w-full flex justify-center items-center">
<Card className="max-w-md p-8 rounded-md"> <Card className="max-w-md p-8 rounded-md">
<CardTitle className='text-center'>Vercel Integration</CardTitle> <CardTitle className='text-center'>Vercel Integration</CardTitle>
@@ -106,14 +125,14 @@ export default function VercelCreateIntegrationPage() {
label="Vercel App" label="Vercel App"
> >
<Select <Select
value={targetApp} value={targetAppId}
onValueChange={(val) => setTargetApp(val)} onValueChange={(val) => setTargetAppId(val)}
className='w-full border border-mineshaft-500' className='w-full border border-mineshaft-500'
isDisabled={integrationAuthApps.length === 0} isDisabled={integrationAuthApps.length === 0}
> >
{integrationAuthApps.length > 0 ? ( {integrationAuthApps.length > 0 ? (
integrationAuthApps.map((integrationAuthApp) => ( integrationAuthApps.map((integrationAuthApp) => (
<SelectItem value={integrationAuthApp.name} key={`target-app-${integrationAuthApp.name}`}> <SelectItem value={integrationAuthApp.appId as string} key={`target-app-${integrationAuthApp.appId as string}`}>
{integrationAuthApp.name} {integrationAuthApp.name}
</SelectItem> </SelectItem>
)) ))
@@ -139,6 +158,23 @@ export default function VercelCreateIntegrationPage() {
))} ))}
</Select> </Select>
</FormControl> </FormControl>
{targetEnvironment === 'preview' && filteredBranches && (
<FormControl
label="Vercel Preview Branch (Optional)"
>
<Select
value={targetBranch}
onValueChange={(val) => setTargetBranch(val)}
className='w-full border border-mineshaft-500'
>
{filteredBranches.map((branchName) => (
<SelectItem value={branchName} key={`target-branch-${branchName}`}>
{branchName}
</SelectItem>
))}
</Select>
</FormControl>
)}
<Button <Button
onClick={handleButtonClick} onClick={handleButtonClick}
color="mineshaft" color="mineshaft"