diff --git a/backend/src/controllers/v1/integrationAuthController.ts b/backend/src/controllers/v1/integrationAuthController.ts index 4d6f6b37d..d26bab1cc 100644 --- a/backend/src/controllers/v1/integrationAuthController.ts +++ b/backend/src/controllers/v1/integrationAuthController.ts @@ -2,7 +2,6 @@ import { Request, Response } from 'express'; import { Types } from 'mongoose'; import * as Sentry from '@sentry/node'; import { - Integration, IntegrationAuth, Bot } from '../../models'; @@ -161,7 +160,7 @@ export const getIntegrationAuthApps = async (req: Request, res: Response) => { let apps; try { const teamId = req.query.teamId as string; - + apps = await getApps({ integrationAuth: req.integrationAuth, accessToken: req.accessToken, diff --git a/backend/src/controllers/v1/integrationController.ts b/backend/src/controllers/v1/integrationController.ts index a17e4d7a6..827ce21b9 100644 --- a/backend/src/controllers/v1/integrationController.ts +++ b/backend/src/controllers/v1/integrationController.ts @@ -2,10 +2,7 @@ import { Request, Response } from 'express'; import { Types } from 'mongoose'; import * as Sentry from '@sentry/node'; import { - Integration, - Workspace, - Bot, - BotKey + Integration } from '../../models'; import { EventService } from '../../services'; import { eventPushSecrets } from '../../events'; @@ -18,6 +15,7 @@ import { eventPushSecrets } from '../../events'; */ export const createIntegration = async (req: Request, res: Response) => { let integration; + try { const { integrationAuthId, @@ -34,19 +32,19 @@ export const createIntegration = async (req: Request, res: Response) => { // TODO: validate [sourceEnvironment] and [targetEnvironment] // initialize new integration after saving integration access token - integration = await new Integration({ - workspace: req.integrationAuth.workspace._id, - environment: sourceEnvironment, - isActive, - app, + integration = await new Integration({ + workspace: req.integrationAuth.workspace._id, + environment: sourceEnvironment, + isActive, + app, appId, targetEnvironment, owner, path, region, - integration: req.integrationAuth.integration, - integrationAuth: new Types.ObjectId(integrationAuthId) - }).save(); + integration: req.integrationAuth.integration, + integrationAuth: new Types.ObjectId(integrationAuthId) + }).save(); if (integration) { // trigger event - push secrets diff --git a/backend/src/helpers/integration.ts b/backend/src/helpers/integration.ts index f4a87c44d..27afbb4b1 100644 --- a/backend/src/helpers/integration.ts +++ b/backend/src/helpers/integration.ts @@ -229,7 +229,7 @@ const getIntegrationAuthAccessHelper = async ({ integrationAuthId }: { integrati // access token is expired const refreshToken = await getIntegrationAuthRefreshHelper({ integrationAuthId }); accessToken = await exchangeRefresh({ - integration: integrationAuth.integration, + integrationAuth, refreshToken }); } diff --git a/backend/src/integrations/apps.ts b/backend/src/integrations/apps.ts index 521c51a2f..f7eef6263 100644 --- a/backend/src/integrations/apps.ts +++ b/backend/src/integrations/apps.ts @@ -24,7 +24,12 @@ import { INTEGRATION_CIRCLECI_API_URL, INTEGRATION_TRAVISCI_API_URL, } from "../variables"; -import { requireIntegrationAuthorizationAuth } from "../middleware"; + +interface App { + name: string; + appId?: string; + owner?: string; +} /** * Return list of names of apps for integration named [integration] @@ -45,12 +50,6 @@ const getApps = async ({ teamId?: string; }) => { - interface App { - name: string; - appId?: string; - owner?: string; - } - let apps: App[] = []; try { switch (integrationAuth.integration) { @@ -87,6 +86,7 @@ const getApps = async ({ case INTEGRATION_GITLAB: apps = await getAppsGitlab({ accessToken, + teamId }); break; case INTEGRATION_RENDER: @@ -439,44 +439,107 @@ const getAppsTravisCI = async ({ accessToken }: { accessToken: string }) => { * @returns {Object[]} apps - names of GitLab sites * @returns {String} apps.name - name of GitLab site */ -const getAppsGitlab = async ({ accessToken }: {accessToken: string}) => { - let apps; +const getAppsGitlab = async ({ + accessToken, + teamId +}: { + accessToken: string; + teamId?: string; +}) => { + const apps: App[] = []; + let page = 1; + const perPage = 10; + let hasMorePages = true; try { - const { id } = ( - await request.get( - `${INTEGRATION_GITLAB_API_URL}/v4/user`, - { - headers: { - "Authorization": `Bearer ${accessToken}`, - "Accept-Encoding": "application/json", - }, - } - ) - ).data; - const res = ( - await request.get( - `${INTEGRATION_GITLAB_API_URL}/v4/users/${id}/projects`, - { - headers: { - "Authorization": `Bearer ${accessToken}`, - "Accept-Encoding": "application/json", - }, - } - ) - ).data; + if (teamId) { + // case: fetch projects for group with id [teamId] in GitLab + + while (hasMorePages) { + const params = new URLSearchParams({ + page: String(page), + per_page: String(perPage) + }); - apps = res?.map((a: any) => { - return { - name: a?.name, - appId: `${a?.id}`, + const { data } = ( + await request.get( + `${INTEGRATION_GITLAB_API_URL}/v4/groups/${teamId}/projects`, + { + params, + headers: { + "Authorization": `Bearer ${accessToken}`, + "Accept-Encoding": "application/json", + }, + } + ) + ); + + data.map((a: any) => { + apps.push({ + name: a.name, + appId: a.id + }); + }); + + if (data.length < perPage) { + hasMorePages = false; + } + + page++; } - }); - }catch (err) { + } else { + // case: fetch projects for individual in GitLab + + const { id } = ( + await request.get( + `${INTEGRATION_GITLAB_API_URL}/v4/user`, + { + headers: { + "Authorization": `Bearer ${accessToken}`, + "Accept-Encoding": "application/json", + }, + } + ) + ).data; + + while (hasMorePages) { + const params = new URLSearchParams({ + page: String(page), + per_page: String(perPage) + }); + + const { data } = ( + await request.get( + `${INTEGRATION_GITLAB_API_URL}/v4/users/${id}/projects`, + { + params, + headers: { + "Authorization": `Bearer ${accessToken}`, + "Accept-Encoding": "application/json", + }, + } + ) + ); + + data.map((a: any) => { + apps.push({ + name: a.name, + appId: a.id + }); + }); + + if (data.length < perPage) { + hasMorePages = false; + } + + page++; + } + } + } catch (err) { Sentry.setUser(null); Sentry.captureException(err); - throw new Error("Failed to get GitLab repos"); + throw new Error("Failed to get GitLab projects"); } return apps; diff --git a/backend/src/integrations/exchange.ts b/backend/src/integrations/exchange.ts index 45ad74370..224979c88 100644 --- a/backend/src/integrations/exchange.ts +++ b/backend/src/integrations/exchange.ts @@ -12,8 +12,7 @@ import { INTEGRATION_VERCEL_TOKEN_URL, INTEGRATION_NETLIFY_TOKEN_URL, INTEGRATION_GITHUB_TOKEN_URL, - INTEGRATION_GITLAB_TOKEN_URL, - INTEGRATION_GITLAB_API_URL + INTEGRATION_GITLAB_TOKEN_URL } from '../variables'; import { SITE_URL, @@ -169,7 +168,7 @@ const exchangeCodeAzure = async ({ accessExpiresAt.setSeconds( accessExpiresAt.getSeconds() + res.expires_in ); - } catch (err: any) { + } catch (err) { Sentry.setUser(null); Sentry.captureException(err); throw new Error('Failed OAuth2 code-token exchange with Azure'); @@ -392,46 +391,10 @@ const exchangeCodeGitlab = async ({ code }: { code: string }) => { ) ).data; - // 1. try getting groups - // https://gitlab.com/api/v4/groups - - // const res2 = (await request.get( - // `${INTEGRATION_GITLAB_API_URL}/v4/groups`, - // { - // headers: { - // Authorization: `Bearer ${res.access_token}`, - // "Accept-Encoding": "application/json" - // } - // } - // )).data; - - // 2. try getting projects of that group - // const res3 = (await request.get( - // `${INTEGRATION_GITLAB_API_URL}/v4/groups/${res2[0].id}`, - // { - // headers: { - // Authorization: `Bearer ${res.access_token}`, - // "Accept-Encoding": "application/json" - // } - // } - // )).data; - - // const res = ( - // await request.get( - // `${INTEGRATION_GITLAB_API_URL}/v4/users/${id}/projects`, - // { - // headers: { - // "Authorization": `Bearer ${accessToken}`, - // "Accept-Encoding": "application/json", - // }, - // } - // ) - // ).data; - accessExpiresAt.setSeconds( accessExpiresAt.getSeconds() + res.expires_in ); - }catch (err) { + } catch (err) { Sentry.setUser(null); Sentry.captureException(err); throw new Error('Failed OAuth2 code-token exchange with Gitlab'); diff --git a/backend/src/integrations/refresh.ts b/backend/src/integrations/refresh.ts index 3e5bc712c..a3aec1ebd 100644 --- a/backend/src/integrations/refresh.ts +++ b/backend/src/integrations/refresh.ts @@ -1,16 +1,29 @@ import request from '../config/request'; import * as Sentry from '@sentry/node'; -import { INTEGRATION_AZURE_KEY_VAULT, INTEGRATION_HEROKU } from '../variables'; +import { + IIntegrationAuth +} from '../models'; +import { + INTEGRATION_AZURE_KEY_VAULT, + INTEGRATION_HEROKU, + INTEGRATION_GITLAB, +} from '../variables'; import { SITE_URL, CLIENT_ID_AZURE, + CLIENT_ID_GITLAB, CLIENT_SECRET_AZURE, - CLIENT_SECRET_HEROKU + CLIENT_SECRET_HEROKU, + CLIENT_SECRET_GITLAB } from '../config'; import { INTEGRATION_AZURE_TOKEN_URL, - INTEGRATION_HEROKU_TOKEN_URL + INTEGRATION_HEROKU_TOKEN_URL, + INTEGRATION_GITLAB_TOKEN_URL } from '../variables'; +import { + IntegrationService +} from '../services'; interface RefreshTokenAzureResponse { token_type: string; @@ -21,6 +34,23 @@ interface RefreshTokenAzureResponse { refresh_token: string; } +interface RefreshTokenHerokuResponse { + access_token: string; + expires_in: number; + refresh_token: string; + token_type: string; + user_id: string; +} + +interface RefreshTokenGitLabResponse { + token_type: string; + scope: string; + expires_in: number; + access_token: string; + refresh_token: string; + created_at: number; +} + /** * Return new access token by exchanging refresh token [refreshToken] for integration * named [integration] @@ -29,33 +59,61 @@ interface RefreshTokenAzureResponse { * @param {String} obj.refreshToken - refresh token to use to get new access token for Heroku */ const exchangeRefresh = async ({ - integration, + integrationAuth, refreshToken }: { - integration: string; + integrationAuth: IIntegrationAuth; refreshToken: string; }) => { - let accessToken; + + interface TokenDetails { + accessToken: string; + refreshToken: string; + accessExpiresAt: Date; + } + + let tokenDetails: TokenDetails; try { - switch (integration) { + switch (integrationAuth.integration) { case INTEGRATION_AZURE_KEY_VAULT: - accessToken = await exchangeRefreshAzure({ + tokenDetails = await exchangeRefreshAzure({ refreshToken }); break; case INTEGRATION_HEROKU: - accessToken = await exchangeRefreshHeroku({ + tokenDetails = await exchangeRefreshHeroku({ refreshToken }); break; + case INTEGRATION_GITLAB: + tokenDetails = await exchangeRefreshGitLab({ + refreshToken + }); + break; + default: + throw new Error('Failed to exchange token for incompatible integration'); } + + if (tokenDetails?.accessToken && tokenDetails?.refreshToken && tokenDetails?.accessExpiresAt) { + await IntegrationService.setIntegrationAuthAccess({ + integrationAuthId: integrationAuth._id.toString(), + accessId: null, + accessToken: tokenDetails.accessToken, + accessExpiresAt: tokenDetails.accessExpiresAt + }); + + await IntegrationService.setIntegrationAuthRefresh({ + integrationAuthId: integrationAuth._id.toString(), + refreshToken: tokenDetails.refreshToken + }); + } + + return tokenDetails.accessToken; } catch (err) { Sentry.setUser(null); Sentry.captureException(err); throw new Error('Failed to get new OAuth2 access token'); } - - return accessToken; }; /** @@ -71,7 +129,8 @@ const exchangeRefreshAzure = async ({ refreshToken: string; }) => { try { - const res: RefreshTokenAzureResponse = (await request.post( + const accessExpiresAt = new Date(); + const { data }: { data: RefreshTokenAzureResponse } = await request.post( INTEGRATION_AZURE_TOKEN_URL, new URLSearchParams({ client_id: CLIENT_ID_AZURE, @@ -80,9 +139,17 @@ const exchangeRefreshAzure = async ({ grant_type: 'refresh_token', client_secret: CLIENT_SECRET_AZURE } as any) - )).data; + ); - return res.access_token; + accessExpiresAt.setSeconds( + accessExpiresAt.getSeconds() + data.expires_in + ); + + return ({ + accessToken: data.access_token, + refreshToken: data.refresh_token, + accessExpiresAt + }); } catch (err) { Sentry.setUser(null); Sentry.captureException(err); @@ -102,10 +169,13 @@ const exchangeRefreshHeroku = async ({ }: { refreshToken: string; }) => { - - let accessToken; try { - const res = await request.post( + const accessExpiresAt = new Date(); + const { + data + }: { + data: RefreshTokenHerokuResponse + } = await request.post( INTEGRATION_HEROKU_TOKEN_URL, new URLSearchParams({ grant_type: 'refresh_token', @@ -114,14 +184,69 @@ const exchangeRefreshHeroku = async ({ } as any) ); - accessToken = res.data.access_token; + accessExpiresAt.setSeconds( + accessExpiresAt.getSeconds() + data.expires_in + ); + + return ({ + accessToken: data.access_token, + refreshToken: data.refresh_token, + accessExpiresAt + }); } catch (err) { Sentry.setUser(null); Sentry.captureException(err); throw new Error('Failed to refresh OAuth2 access token for Heroku'); } +}; - return accessToken; +/** + * Return new access token by exchanging refresh token [refreshToken] for the + * GitLab integration + * @param {Object} obj + * @param {String} obj.refreshToken - refresh token to use to get new access token for GitLab + * @returns + */ +const exchangeRefreshGitLab = async ({ + refreshToken +}: { + refreshToken: string; +}) => { + try { + const accessExpiresAt = new Date(); + const { + data + }: { + data: RefreshTokenGitLabResponse + } = await request.post( + INTEGRATION_GITLAB_TOKEN_URL, + new URLSearchParams({ + grant_type: 'refresh_token', + refresh_token: refreshToken, + client_id: CLIENT_ID_GITLAB, + client_secret: CLIENT_SECRET_GITLAB, + redirect_uri: `${SITE_URL}/integrations/gitlab/oauth2/callback` + } as any), + { + headers: { + "Accept-Encoding": "application/json", + } + }); + + accessExpiresAt.setSeconds( + accessExpiresAt.getSeconds() + data.expires_in + ); + + return ({ + accessToken: data.access_token, + refreshToken: data.refresh_token, + accessExpiresAt + }); + } catch (err) { + Sentry.setUser(null); + Sentry.captureException(err); + throw new Error('Failed to refresh OAuth2 access token for GitLab'); + } }; export { exchangeRefresh }; diff --git a/backend/src/integrations/sync.ts b/backend/src/integrations/sync.ts index 21081294d..829b50531 100644 --- a/backend/src/integrations/sync.ts +++ b/backend/src/integrations/sync.ts @@ -172,7 +172,6 @@ const syncSecretsAzureKeyVault = async ({ accessToken: string; }) => { try { - interface GetAzureKeyVaultSecret { id: string; // secret URI attributes: { @@ -1512,7 +1511,7 @@ const syncSecretsGitLab = async ({ ) ).data; - for (const key of Object.keys(secrets)) { + for await (const key of Object.keys(secrets)) { const existingSecret = getSecretsRes.find((s: any) => s.key == key); if (!existingSecret) { await request.post( @@ -1533,7 +1532,7 @@ const syncSecretsGitLab = async ({ }, } ) - }else { + } else { // udpate secret await request.put( `${INTEGRATION_GITLAB_API_URL}/v4/projects/${integration?.appId}/variables/${existingSecret.key}`, @@ -1553,7 +1552,7 @@ const syncSecretsGitLab = async ({ } // delete secrets - for (const sec of getSecretsRes) { + for await (const sec of getSecretsRes) { if (!(sec.key in secrets)) { await request.delete( `${INTEGRATION_GITLAB_API_URL}/v4/projects/${integration?.appId}/variables/${sec.key}`, diff --git a/backend/src/integrations/teams.ts b/backend/src/integrations/teams.ts index 41040aee5..9943c531e 100644 --- a/backend/src/integrations/teams.ts +++ b/backend/src/integrations/teams.ts @@ -8,6 +8,20 @@ import { } from '../variables'; import request from '../config/request'; +interface Team { + name: string; + teamId: string; +} + +/** + * Return list of teams for integration authorization [integrationAuth] + * @param {Object} obj + * @param {String} obj.integrationAuth - integration authorization to get teams for + * @param {String} obj.accessToken - access token for integration authorization + * @returns {Object[]} teams - teams of integration authorization + * @returns {String} teams.name - name of team + * @returns {String} teams.teamId - id of team +*/ const getTeams = async ({ integrationAuth, accessToken @@ -15,10 +29,6 @@ const getTeams = async ({ integrationAuth: IIntegrationAuth; accessToken: string; }) => { - interface Team { - name: string; - teamId: string; - } let teams: Team[] = []; try { @@ -39,12 +49,20 @@ const getTeams = async ({ return teams; } +/** + * Return list of teams for GitLab integration + * @param {Object} obj + * @param {String} obj.accessToken - access token for GitLab API + * @returns {Object[]} teams - teams that user is part of in GitLab + * @returns {String} teams.name - name of team + * @returns {String} teams.teamId - id of team +*/ const getTeamsGitLab = async ({ accessToken }: { accessToken: string; }) => { - let teams = []; + let teams: Team[] = []; try { const res = (await request.get( `${INTEGRATION_GITLAB_API_URL}/v4/groups`, diff --git a/backend/src/models/integration.ts b/backend/src/models/integration.ts index d5d9b80ce..698846ea7 100644 --- a/backend/src/models/integration.ts +++ b/backend/src/models/integration.ts @@ -62,13 +62,11 @@ const integrationSchema = new Schema( default: null, }, appId: { - // (new) // id of app in provider type: String, default: null, }, targetEnvironment: { - // (new) // target environment type: String, default: null, diff --git a/backend/src/routes/v1/integrationAuth.ts b/backend/src/routes/v1/integrationAuth.ts index 409422e93..2c1f5f045 100644 --- a/backend/src/routes/v1/integrationAuth.ts +++ b/backend/src/routes/v1/integrationAuth.ts @@ -64,9 +64,6 @@ router.post( integrationAuthController.saveIntegrationAccessToken ); -// this can optionally accept a teamId? -// IF teamId is passed in then it probably means that we want to get -// the apps for that team router.get( '/:integrationAuthId/apps', requireAuth({ @@ -76,7 +73,7 @@ router.get( acceptedRoles: [ADMIN, MEMBER] }), param('integrationAuthId'), - query('entity'), + query('teamId'), validateRequest, integrationAuthController.getIntegrationAuthApps ); diff --git a/backend/src/services/IntegrationService.ts b/backend/src/services/IntegrationService.ts index d245c5663..7b3a20a6b 100644 --- a/backend/src/services/IntegrationService.ts +++ b/backend/src/services/IntegrationService.ts @@ -7,9 +7,6 @@ import { setIntegrationAuthAccessHelper, } from '../helpers/integration'; -// should sync stuff be here too? Probably. -// TODO: move bot functions to IntegrationService. - /** * Class to handle integrations */ diff --git a/frontend/src/hooks/api/integrationAuth/queries.tsx b/frontend/src/hooks/api/integrationAuth/queries.tsx index e60436f99..56a6ee45e 100644 --- a/frontend/src/hooks/api/integrationAuth/queries.tsx +++ b/frontend/src/hooks/api/integrationAuth/queries.tsx @@ -25,7 +25,6 @@ const fetchIntegrationAuthApps = async ({ integrationAuthId: string; teamId?: string; }) => { - console.log('fetchIntegrationAuthApps: ', integrationAuthId); const searchParams = new URLSearchParams(teamId ? { teamId } : undefined); const { data } = await apiRequest.get<{ apps: App[] }>( `/api/v1/integration-auth/${integrationAuthId}/apps`, diff --git a/frontend/src/pages/integrations/gitlab/create.tsx b/frontend/src/pages/integrations/gitlab/create.tsx index 9bd7d4ac1..488e6c53b 100644 --- a/frontend/src/pages/integrations/gitlab/create.tsx +++ b/frontend/src/pages/integrations/gitlab/create.tsx @@ -23,8 +23,6 @@ const gitLabEntities = [ { name: 'Group', value: 'group' } ] -// TODO: flesh out the data flow... - export default function GitLabCreateIntegrationPage() { const router = useRouter(); @@ -33,59 +31,51 @@ export default function GitLabCreateIntegrationPage() { const { data: workspace } = useGetWorkspaceById(localStorage.getItem('projectData.id') ?? ''); const { data: integrationAuth } = useGetIntegrationAuthById(integrationAuthId as string ?? ''); - const [targetEntity, setTargetEntity] = useState(gitLabEntities[0].value); - const [targetTeam, setTargetTeam] = useState('aa'); // ? - const [targetTeamId] = useState(undefined); + const [targetTeamId, setTargetTeamId] = useState(null); + const { data: integrationAuthApps } = useGetIntegrationAuthApps({ integrationAuthId: integrationAuthId as string ?? '', ...(targetTeamId ? { teamId: targetTeamId } : {}) }); const { data: integrationAuthTeams } = useGetIntegrationAuthTeams(integrationAuthId as string ?? ''); - console.log('integrationAuthTeams: ', integrationAuthTeams); + const [targetEntity, setTargetEntity] = useState(gitLabEntities[0].value); const [selectedSourceEnvironment, setSelectedSourceEnvironment] = useState(''); - const [targetApp, setTargetApp] = useState(''); + const [targetAppId, setTargetAppId] = useState(''); const [isLoading, setIsLoading] = useState(false); useEffect(() => { - if (workspace) { - setSelectedSourceEnvironment(workspace.environments[0].slug); - } - + if (workspace) { + setSelectedSourceEnvironment(workspace.environments[0].slug); + } }, [workspace]); useEffect(() => { if (integrationAuthApps) { if (integrationAuthApps.length > 0) { - console.log('AA'); - setTargetApp(integrationAuthApps[0].name); + setTargetAppId(integrationAuthApps[0].appId as string); } else { - console.log('BB'); - setTargetApp('none'); + setTargetAppId('none'); } } }, [integrationAuthApps]); useEffect(() => { - // if (targetEntity === 'group' && integrationAuthTeams) { - // if (integrationAuthTeams.length > 0) { - // setTargetTeam(integrationAuthTeams[0].name); - // } else { - // setTargetTeam('none'); - // } - // } - - // if (targetEntity === 'group') { - // if (integrationAuthTeams && integrationAuthTeams.length > 0) { - // setTargetTeamId(integrationAuthTeams[0].teamId); - // } else { - // setTargetTeamId(''); - // } - // } else { - - // } - }, [integrationAuthTeams, integrationAuthApps, targetEntity]); + if (targetEntity === 'group' && integrationAuthTeams && integrationAuthTeams.length > 0) { + if (integrationAuthTeams) { + if (integrationAuthTeams.length > 0) { + // case: user is part of at least 1 group in GitLab + setTargetTeamId(integrationAuthTeams[0].teamId); + } else { + // case: user is not part of any groups in GitLab + setTargetTeamId('none'); + } + } + } else if (targetEntity === 'individual') { + setTargetTeamId(null); + } + }, [targetEntity, integrationAuthTeams]); const handleButtonClick = async () => { try { @@ -95,8 +85,8 @@ export default function GitLabCreateIntegrationPage() { await createIntegration({ integrationAuthId: integrationAuth?._id, isActive: true, - app: targetApp, - appId: (integrationAuthApps?.find((integrationAuthApp) => integrationAuthApp.name === targetApp))?.appId ?? null, + app: (integrationAuthApps?.find((integrationAuthApp) => integrationAuthApp.appId === targetAppId))?.name ?? null, + appId: targetAppId, sourceEnvironment: selectedSourceEnvironment, targetEnvironment: null, owner: null, @@ -113,15 +103,7 @@ export default function GitLabCreateIntegrationPage() { } } - console.log('A', (integrationAuth && workspace && selectedSourceEnvironment && integrationAuthApps && integrationAuthTeams && targetApp && targetTeam)); - console.log('B', integrationAuth); - console.log('C', workspace); - console.log('D', selectedSourceEnvironment); - console.log('E', integrationAuthApps); - console.log('F', integrationAuthTeams); - console.log('G', targetApp); - console.log('H', targetTeam); - return (integrationAuth && workspace && selectedSourceEnvironment && integrationAuthApps && integrationAuthTeams && targetApp && targetTeam) ? ( + return (integrationAuth && workspace && selectedSourceEnvironment && integrationAuthApps && integrationAuthTeams && targetAppId) ? (
GitLab Integration @@ -165,13 +147,13 @@ export default function GitLabCreateIntegrationPage() { className='mt-4' > setTargetApp(val)} + value={targetAppId} + onValueChange={(val) => setTargetAppId(val)} className='w-full border border-mineshaft-500' isDisabled={integrationAuthApps.length === 0} > {integrationAuthApps.length > 0 ? ( integrationAuthApps.map((integrationAuthApp) => ( - + {integrationAuthApp.name} ))