mirror of
https://github.com/Infisical/infisical.git
synced 2026-01-08 23:18:05 -05:00
Merge pull request #423 from Infisical/check-integrations
Patch create integration page on no integration projects and add support for groups in GitLab integration
This commit is contained in:
@@ -2,13 +2,16 @@ import { Request, Response } from 'express';
|
||||
import { Types } from 'mongoose';
|
||||
import * as Sentry from '@sentry/node';
|
||||
import {
|
||||
Integration,
|
||||
IntegrationAuth,
|
||||
Bot
|
||||
} from '../../models';
|
||||
import { INTEGRATION_SET, INTEGRATION_OPTIONS } from '../../variables';
|
||||
import { IntegrationService } from '../../services';
|
||||
import { getApps, revokeAccess } from '../../integrations';
|
||||
import {
|
||||
getApps,
|
||||
getTeams,
|
||||
revokeAccess
|
||||
} from '../../integrations';
|
||||
|
||||
/***
|
||||
* Return integration authorization with id [integrationAuthId]
|
||||
@@ -154,25 +157,54 @@ export const saveIntegrationAccessToken = async (
|
||||
* @returns
|
||||
*/
|
||||
export const getIntegrationAuthApps = async (req: Request, res: Response) => {
|
||||
let apps;
|
||||
try {
|
||||
apps = await getApps({
|
||||
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 applications",
|
||||
});
|
||||
}
|
||||
let apps;
|
||||
try {
|
||||
const teamId = req.query.teamId as string;
|
||||
|
||||
apps = await getApps({
|
||||
integrationAuth: req.integrationAuth,
|
||||
accessToken: req.accessToken,
|
||||
...teamId && { teamId }
|
||||
});
|
||||
} catch (err) {
|
||||
Sentry.setUser({ email: req.user.email });
|
||||
Sentry.captureException(err);
|
||||
return res.status(400).send({
|
||||
message: "Failed to get integration authorization applications",
|
||||
});
|
||||
}
|
||||
|
||||
return res.status(200).send({
|
||||
apps,
|
||||
});
|
||||
return res.status(200).send({
|
||||
apps
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Return list of teams allowed for integration with integration authorization id [integrationAuthId]
|
||||
* @param req
|
||||
* @param res
|
||||
* @returns
|
||||
*/
|
||||
export const getIntegrationAuthTeams = async (req: Request, res: Response) => {
|
||||
let teams;
|
||||
try {
|
||||
teams = await getTeams({
|
||||
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({
|
||||
teams
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete integration authorization with id [integrationAuthId]
|
||||
* @param req
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
});
|
||||
}
|
||||
|
||||
@@ -25,26 +25,30 @@ import {
|
||||
INTEGRATION_TRAVISCI_API_URL,
|
||||
} from "../variables";
|
||||
|
||||
interface App {
|
||||
name: string;
|
||||
appId?: string;
|
||||
owner?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return list of names of apps for integration named [integration]
|
||||
* @param {Object} obj
|
||||
* @param {String} obj.integration - name of integration
|
||||
* @param {String} obj.accessToken - access token for integration
|
||||
* @param {String} obj.teamId - (optional) id of team for getting integration apps (used for integrations like GitLab)
|
||||
* @returns {Object[]} apps - names of integration apps
|
||||
* @returns {String} apps.name - name of integration app
|
||||
*/
|
||||
const getApps = async ({
|
||||
integrationAuth,
|
||||
accessToken,
|
||||
teamId
|
||||
}: {
|
||||
integrationAuth: IIntegrationAuth;
|
||||
accessToken: string;
|
||||
teamId?: string;
|
||||
}) => {
|
||||
interface App {
|
||||
name: string;
|
||||
appId?: string;
|
||||
owner?: string;
|
||||
}
|
||||
|
||||
let apps: App[] = [];
|
||||
try {
|
||||
@@ -82,6 +86,7 @@ const getApps = async ({
|
||||
case INTEGRATION_GITLAB:
|
||||
apps = await getAppsGitlab({
|
||||
accessToken,
|
||||
teamId
|
||||
});
|
||||
break;
|
||||
case INTEGRATION_RENDER:
|
||||
@@ -434,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;
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
INTEGRATION_VERCEL_TOKEN_URL,
|
||||
INTEGRATION_NETLIFY_TOKEN_URL,
|
||||
INTEGRATION_GITHUB_TOKEN_URL,
|
||||
INTEGRATION_GITLAB_TOKEN_URL,
|
||||
INTEGRATION_GITLAB_TOKEN_URL
|
||||
} from '../variables';
|
||||
import {
|
||||
SITE_URL,
|
||||
@@ -73,7 +73,7 @@ interface ExchangeCodeGithubResponse {
|
||||
interface ExchangeCodeGitlabResponse {
|
||||
access_token: string;
|
||||
token_type: string;
|
||||
expires_in: string;
|
||||
expires_in: number;
|
||||
refresh_token: string;
|
||||
scope: string;
|
||||
created_at: number;
|
||||
@@ -168,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');
|
||||
@@ -370,6 +370,7 @@ const exchangeCodeGithub = async ({ code }: { code: string }) => {
|
||||
*/
|
||||
const exchangeCodeGitlab = async ({ code }: { code: string }) => {
|
||||
let res: ExchangeCodeGitlabResponse;
|
||||
const accessExpiresAt = new Date();
|
||||
|
||||
try {
|
||||
res = (
|
||||
@@ -389,7 +390,11 @@ const exchangeCodeGitlab = async ({ code }: { code: string }) => {
|
||||
}
|
||||
)
|
||||
).data;
|
||||
}catch (err) {
|
||||
|
||||
accessExpiresAt.setSeconds(
|
||||
accessExpiresAt.getSeconds() + res.expires_in
|
||||
);
|
||||
} catch (err) {
|
||||
Sentry.setUser(null);
|
||||
Sentry.captureException(err);
|
||||
throw new Error('Failed OAuth2 code-token exchange with Gitlab');
|
||||
@@ -397,8 +402,8 @@ const exchangeCodeGitlab = async ({ code }: { code: string }) => {
|
||||
|
||||
return {
|
||||
accessToken: res.access_token,
|
||||
refreshToken: null,
|
||||
accessExpiresAt: null
|
||||
refreshToken: res.refresh_token,
|
||||
accessExpiresAt
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { exchangeCode } from './exchange';
|
||||
import { exchangeRefresh } from './refresh';
|
||||
import { getApps } from './apps';
|
||||
import { getTeams } from './teams';
|
||||
import { syncSecrets } from './sync';
|
||||
import { revokeAccess } from './revoke';
|
||||
|
||||
@@ -8,6 +9,7 @@ export {
|
||||
exchangeCode,
|
||||
exchangeRefresh,
|
||||
getApps,
|
||||
getTeams,
|
||||
syncSecrets,
|
||||
revokeAccess
|
||||
}
|
||||
@@ -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 };
|
||||
|
||||
@@ -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}`,
|
||||
|
||||
92
backend/src/integrations/teams.ts
Normal file
92
backend/src/integrations/teams.ts
Normal file
@@ -0,0 +1,92 @@
|
||||
import * as Sentry from "@sentry/node";
|
||||
import {
|
||||
IIntegrationAuth
|
||||
} from '../models';
|
||||
import {
|
||||
INTEGRATION_GITLAB,
|
||||
INTEGRATION_GITLAB_API_URL
|
||||
} 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
|
||||
}: {
|
||||
integrationAuth: IIntegrationAuth;
|
||||
accessToken: string;
|
||||
}) => {
|
||||
|
||||
let teams: Team[] = [];
|
||||
try {
|
||||
switch (integrationAuth.integration) {
|
||||
case INTEGRATION_GITLAB:
|
||||
teams = await getTeamsGitLab({
|
||||
accessToken
|
||||
});
|
||||
break;
|
||||
}
|
||||
} catch (err) {
|
||||
Sentry.setUser(null);
|
||||
Sentry.captureException(err);
|
||||
throw new Error('Failed to get integration teams');
|
||||
}
|
||||
|
||||
|
||||
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: Team[] = [];
|
||||
try {
|
||||
const res = (await request.get(
|
||||
`${INTEGRATION_GITLAB_API_URL}/v4/groups`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
"Accept-Encoding": "application/json"
|
||||
}
|
||||
}
|
||||
)).data;
|
||||
|
||||
teams = res.map((t: any) => ({
|
||||
name: t.name,
|
||||
teamId: t.id
|
||||
}));
|
||||
} catch (err) {
|
||||
Sentry.setUser(null);
|
||||
Sentry.captureException(err);
|
||||
throw new Error("Failed to get GitLab integration teams");
|
||||
}
|
||||
|
||||
return teams;
|
||||
}
|
||||
|
||||
export {
|
||||
getTeams
|
||||
}
|
||||
@@ -62,13 +62,11 @@ const integrationSchema = new Schema<IIntegration>(
|
||||
default: null,
|
||||
},
|
||||
appId: {
|
||||
// (new)
|
||||
// id of app in provider
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
targetEnvironment: {
|
||||
// (new)
|
||||
// target environment
|
||||
type: String,
|
||||
default: null,
|
||||
|
||||
@@ -23,9 +23,9 @@ export interface IIntegrationAuth {
|
||||
refreshCiphertext?: string;
|
||||
refreshIV?: string;
|
||||
refreshTag?: string;
|
||||
accessIdCiphertext?: string; // new
|
||||
accessIdIV?: string; // new
|
||||
accessIdTag?: string; // new
|
||||
accessIdCiphertext?: string;
|
||||
accessIdIV?: string;
|
||||
accessIdTag?: string;
|
||||
accessCiphertext?: string;
|
||||
accessIV?: string;
|
||||
accessTag?: string;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import express from 'express';
|
||||
const router = express.Router();
|
||||
import { body, param } from 'express-validator';
|
||||
import { body, param, query } from 'express-validator';
|
||||
import {
|
||||
requireAuth,
|
||||
requireWorkspaceAuth,
|
||||
@@ -73,10 +73,24 @@ router.get(
|
||||
acceptedRoles: [ADMIN, MEMBER]
|
||||
}),
|
||||
param('integrationAuthId'),
|
||||
query('teamId'),
|
||||
validateRequest,
|
||||
integrationAuthController.getIntegrationAuthApps
|
||||
);
|
||||
|
||||
router.get(
|
||||
'/:integrationAuthId/teams',
|
||||
requireAuth({
|
||||
acceptedAuthModes: ['jwt']
|
||||
}),
|
||||
requireIntegrationAuthorizationAuth({
|
||||
acceptedRoles: [ADMIN, MEMBER]
|
||||
}),
|
||||
param('integrationAuthId'),
|
||||
validateRequest,
|
||||
integrationAuthController.getIntegrationAuthTeams
|
||||
);
|
||||
|
||||
router.delete(
|
||||
'/:integrationAuthId',
|
||||
requireAuth({
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -59,6 +59,7 @@ const INTEGRATION_FLYIO_API_URL = "https://api.fly.io/graphql";
|
||||
const INTEGRATION_CIRCLECI_API_URL = "https://circleci.com/api";
|
||||
const INTEGRATION_TRAVISCI_API_URL = "https://api.travis-ci.com";
|
||||
|
||||
// TODO: deprecate types?
|
||||
const INTEGRATION_OPTIONS = [
|
||||
{
|
||||
name: 'Heroku',
|
||||
@@ -156,7 +157,7 @@ const INTEGRATION_OPTIONS = [
|
||||
slug: 'gitlab',
|
||||
image: 'GitLab.png',
|
||||
isAvailable: true,
|
||||
type: 'oauth',
|
||||
type: 'custom',
|
||||
clientId: CLIENT_ID_GITLAB,
|
||||
docsLink: ''
|
||||
},
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export {
|
||||
useGetIntegrationAuthApps,
|
||||
useGetIntegrationAuthById} from './queries';
|
||||
useGetIntegrationAuthById,
|
||||
useGetIntegrationAuthTeams} from './queries';
|
||||
@@ -4,11 +4,13 @@ import { apiRequest } from "@app/config/request";
|
||||
|
||||
import {
|
||||
App,
|
||||
IntegrationAuth} from './types';
|
||||
IntegrationAuth,
|
||||
Team} from './types';
|
||||
|
||||
const integrationAuthKeys = {
|
||||
getIntegrationAuthById: (integrationAuthId: string) => [{ integrationAuthId }, 'integrationAuth'] as const,
|
||||
getIntegrationAuthApps: (integrationAuthId: string) => [{ integrationAuthId }, 'integrationAuthApps'] as const,
|
||||
getIntegrationAuthApps: (integrationAuthId: string, teamId?: string) => [{ integrationAuthId, teamId }, 'integrationAuthApps'] as const,
|
||||
getIntegrationAuthTeams: (integrationAuthId: string) => [{ integrationAuthId }, 'integrationAuthTeams'] as const
|
||||
}
|
||||
|
||||
const fetchIntegrationAuthById = async (integrationAuthId: string) => {
|
||||
@@ -16,11 +18,26 @@ const fetchIntegrationAuthById = async (integrationAuthId: string) => {
|
||||
return data.integrationAuth;
|
||||
}
|
||||
|
||||
const fetchIntegrationAuthApps = async (integrationAuthId: string) => {
|
||||
const { data } = await apiRequest.get<{ apps: App[] }>(`/api/v1/integration-auth/${integrationAuthId}/apps`);
|
||||
const fetchIntegrationAuthApps = async ({
|
||||
integrationAuthId,
|
||||
teamId
|
||||
}: {
|
||||
integrationAuthId: string;
|
||||
teamId?: string;
|
||||
}) => {
|
||||
const searchParams = new URLSearchParams(teamId ? { teamId } : undefined);
|
||||
const { data } = await apiRequest.get<{ apps: App[] }>(
|
||||
`/api/v1/integration-auth/${integrationAuthId}/apps`,
|
||||
{ params: searchParams }
|
||||
);
|
||||
return data.apps;
|
||||
}
|
||||
|
||||
const fetchIntegrationAuthTeams = async (integrationAuthId: string) => {
|
||||
const { data } = await apiRequest.get<{ teams: Team[] }>(`/api/v1/integration-auth/${integrationAuthId}/teams`);
|
||||
return data.teams;
|
||||
}
|
||||
|
||||
export const useGetIntegrationAuthById = (integrationAuthId: string) => {
|
||||
return useQuery({
|
||||
queryKey: integrationAuthKeys.getIntegrationAuthById(integrationAuthId),
|
||||
@@ -29,10 +46,28 @@ export const useGetIntegrationAuthById = (integrationAuthId: string) => {
|
||||
});
|
||||
}
|
||||
|
||||
export const useGetIntegrationAuthApps = (integrationAuthId: string) => {
|
||||
// TODO: fix to teamId
|
||||
export const useGetIntegrationAuthApps = ({
|
||||
integrationAuthId,
|
||||
teamId
|
||||
}: {
|
||||
integrationAuthId: string;
|
||||
teamId?: string;
|
||||
}) => {
|
||||
return useQuery({
|
||||
queryKey: integrationAuthKeys.getIntegrationAuthApps(integrationAuthId),
|
||||
queryFn: () => fetchIntegrationAuthApps(integrationAuthId),
|
||||
queryKey: integrationAuthKeys.getIntegrationAuthApps(integrationAuthId, teamId),
|
||||
queryFn: () => fetchIntegrationAuthApps({
|
||||
integrationAuthId,
|
||||
teamId
|
||||
}),
|
||||
enabled: true
|
||||
});
|
||||
}
|
||||
|
||||
export const useGetIntegrationAuthTeams = (integrationAuthId: string) => {
|
||||
return useQuery({
|
||||
queryKey: integrationAuthKeys.getIntegrationAuthTeams(integrationAuthId),
|
||||
queryFn: () => fetchIntegrationAuthTeams(integrationAuthId),
|
||||
enabled: true
|
||||
});
|
||||
}
|
||||
@@ -10,4 +10,9 @@ export type App = {
|
||||
name: string;
|
||||
appId?: string;
|
||||
owner?: string;
|
||||
}
|
||||
|
||||
export type Team = {
|
||||
name: string;
|
||||
teamId: string;
|
||||
}
|
||||
@@ -196,16 +196,16 @@ export default function Integrations() {
|
||||
link = `https://gitlab.com/oauth/authorize?client_id=${integrationOption.clientId}&redirect_uri=${window.location.origin}/integrations/gitlab/oauth2/callback&response_type=code&state=${state}`;
|
||||
break;
|
||||
case 'render':
|
||||
link = `${window.location.origin}/integrations/render/authorize`
|
||||
link = `${window.location.origin}/integrations/render/authorize`;
|
||||
break;
|
||||
case 'flyio':
|
||||
link = `${window.location.origin}/integrations/flyio/authorize`
|
||||
link = `${window.location.origin}/integrations/flyio/authorize`;
|
||||
break;
|
||||
case 'circleci':
|
||||
link = `${window.location.origin}/integrations/circleci/authorize`
|
||||
link = `${window.location.origin}/integrations/circleci/authorize`;
|
||||
break;
|
||||
case 'travisci':
|
||||
link = `${window.location.origin}/integrations/travisci/authorize`
|
||||
link = `${window.location.origin}/integrations/travisci/authorize`;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
||||
@@ -22,7 +22,9 @@ export default function CircleCICreateIntegrationPage() {
|
||||
|
||||
const { data: workspace } = useGetWorkspaceById(localStorage.getItem('projectData.id') ?? '');
|
||||
const { data: integrationAuth } = useGetIntegrationAuthById(integrationAuthId as string ?? '');
|
||||
const { data: integrationAuthApps } = useGetIntegrationAuthApps(integrationAuthId as string ?? '');
|
||||
const { data: integrationAuthApps } = useGetIntegrationAuthApps({
|
||||
integrationAuthId: integrationAuthId as string ?? ''
|
||||
});
|
||||
|
||||
const [selectedSourceEnvironment, setSelectedSourceEnvironment] = useState('');
|
||||
const [targetApp, setTargetApp] = useState('');
|
||||
@@ -36,9 +38,12 @@ export default function CircleCICreateIntegrationPage() {
|
||||
}, [workspace]);
|
||||
|
||||
useEffect(() => {
|
||||
// TODO: handle case where apps can be empty
|
||||
if (integrationAuthApps) {
|
||||
setTargetApp(integrationAuthApps[0]?.name);
|
||||
if (integrationAuthApps.length > 0 ) {
|
||||
setTargetApp(integrationAuthApps[0]?.name);
|
||||
} else {
|
||||
setTargetApp('none');
|
||||
}
|
||||
}
|
||||
}, [integrationAuthApps]);
|
||||
|
||||
@@ -84,7 +89,7 @@ export default function CircleCICreateIntegrationPage() {
|
||||
className='w-full border border-mineshaft-500'
|
||||
>
|
||||
{workspace?.environments.map((sourceEnvironment) => (
|
||||
<SelectItem value={sourceEnvironment.slug} key={`azure-key-vault-environment-${sourceEnvironment.slug}`}>
|
||||
<SelectItem value={sourceEnvironment.slug} key={`source-environment-${sourceEnvironment.slug}`}>
|
||||
{sourceEnvironment.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
@@ -98,19 +103,27 @@ export default function CircleCICreateIntegrationPage() {
|
||||
value={targetApp}
|
||||
onValueChange={(val) => setTargetApp(val)}
|
||||
className='w-full border border-mineshaft-500'
|
||||
isDisabled={integrationAuthApps.length === 0}
|
||||
>
|
||||
{integrationAuthApps.map((integrationAuthApp) => (
|
||||
<SelectItem value={integrationAuthApp.name} key={`render-environment-${integrationAuthApp.name}`}>
|
||||
{integrationAuthApp.name}
|
||||
{integrationAuthApps.length > 0 ? (
|
||||
integrationAuthApps.map((integrationAuthApp) => (
|
||||
<SelectItem value={integrationAuthApp.name} key={`target-app-${integrationAuthApp.name}`}>
|
||||
{integrationAuthApp.name}
|
||||
</SelectItem>
|
||||
))
|
||||
) : (
|
||||
<SelectItem value="none" key="target-app-none">
|
||||
No projects found
|
||||
</SelectItem>
|
||||
))}
|
||||
)}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<Button
|
||||
onClick={handleButtonClick}
|
||||
color="mineshaft"
|
||||
className='mt-4'
|
||||
isLoading={isLoading}
|
||||
onClick={handleButtonClick}
|
||||
color="mineshaft"
|
||||
className='mt-4'
|
||||
isLoading={isLoading}
|
||||
isDisabled={integrationAuthApps.length === 0}
|
||||
>
|
||||
Create Integration
|
||||
</Button>
|
||||
|
||||
@@ -22,7 +22,9 @@ export default function FlyioCreateIntegrationPage() {
|
||||
|
||||
const { data: workspace } = useGetWorkspaceById(localStorage.getItem('projectData.id') ?? '');
|
||||
const { data: integrationAuth } = useGetIntegrationAuthById(integrationAuthId as string ?? '');
|
||||
const { data: integrationAuthApps } = useGetIntegrationAuthApps(integrationAuthId as string ?? '');
|
||||
const { data: integrationAuthApps } = useGetIntegrationAuthApps({
|
||||
integrationAuthId: integrationAuthId as string ?? ''
|
||||
});
|
||||
|
||||
const [selectedSourceEnvironment, setSelectedSourceEnvironment] = useState('');
|
||||
const [targetApp, setTargetApp] = useState('');
|
||||
@@ -31,14 +33,18 @@ export default function FlyioCreateIntegrationPage() {
|
||||
|
||||
useEffect(() => {
|
||||
if (workspace) {
|
||||
setSelectedSourceEnvironment(workspace.environments[0].slug);
|
||||
setSelectedSourceEnvironment(workspace.environments[0].slug);
|
||||
}
|
||||
}, [workspace]);
|
||||
|
||||
useEffect(() => {
|
||||
// TODO: handle case where apps can be empty
|
||||
if (integrationAuthApps) {
|
||||
if (integrationAuthApps.length > 0) {
|
||||
setTargetApp(integrationAuthApps[0].name);
|
||||
} else {
|
||||
setTargetApp('none');
|
||||
}
|
||||
}
|
||||
}, [integrationAuthApps]);
|
||||
|
||||
@@ -84,7 +90,7 @@ export default function FlyioCreateIntegrationPage() {
|
||||
className='w-full border border-mineshaft-500'
|
||||
>
|
||||
{workspace?.environments.map((sourceEnvironment) => (
|
||||
<SelectItem value={sourceEnvironment.slug} key={`flyio-environment-${sourceEnvironment.slug}`}>
|
||||
<SelectItem value={sourceEnvironment.slug} key={`source-environment-${sourceEnvironment.slug}`}>
|
||||
{sourceEnvironment.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
@@ -98,12 +104,19 @@ export default function FlyioCreateIntegrationPage() {
|
||||
value={targetApp}
|
||||
onValueChange={(val) => setTargetApp(val)}
|
||||
className='w-full border border-mineshaft-500'
|
||||
isDisabled={integrationAuthApps.length === 0}
|
||||
>
|
||||
{integrationAuthApps.map((integrationAuthApp) => (
|
||||
<SelectItem value={integrationAuthApp.name} key={`render-environment-${integrationAuthApp.name}`}>
|
||||
{integrationAuthApp.name}
|
||||
{integrationAuthApps.length > 0 ? (
|
||||
integrationAuthApps.map((integrationAuthApp) => (
|
||||
<SelectItem value={integrationAuthApp.name} key={`target-app-${integrationAuthApp.name}`}>
|
||||
{integrationAuthApp.name}
|
||||
</SelectItem>
|
||||
))
|
||||
) : (
|
||||
<SelectItem value="none" key="target-app-none">
|
||||
No apps found
|
||||
</SelectItem>
|
||||
))}
|
||||
)}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<Button
|
||||
@@ -111,6 +124,7 @@ export default function FlyioCreateIntegrationPage() {
|
||||
color="mineshaft"
|
||||
className='mt-4'
|
||||
isLoading={isLoading}
|
||||
isDisabled={integrationAuthApps.length === 0}
|
||||
>
|
||||
Create Integration
|
||||
</Button>
|
||||
|
||||
@@ -22,7 +22,9 @@ export default function GitHubCreateIntegrationPage() {
|
||||
|
||||
const { data: workspace } = useGetWorkspaceById(localStorage.getItem('projectData.id') ?? '');
|
||||
const { data: integrationAuth } = useGetIntegrationAuthById(integrationAuthId as string ?? '');
|
||||
const { data: integrationAuthApps } = useGetIntegrationAuthApps(integrationAuthId as string ?? '');
|
||||
const { data: integrationAuthApps } = useGetIntegrationAuthApps({
|
||||
integrationAuthId: integrationAuthId as string ?? ''
|
||||
});
|
||||
|
||||
const [selectedSourceEnvironment, setSelectedSourceEnvironment] = useState('');
|
||||
const [owner, setOwner] = useState<string | null>(null);
|
||||
@@ -37,10 +39,13 @@ export default function GitHubCreateIntegrationPage() {
|
||||
}, [workspace]);
|
||||
|
||||
useEffect(() => {
|
||||
// TODO: handle case where apps can be empty
|
||||
if (integrationAuthApps) {
|
||||
if (integrationAuthApps.length > 0) {
|
||||
setTargetApp(integrationAuthApps[0].name);
|
||||
setOwner(integrationAuthApps[0]?.owner ?? null);
|
||||
} else {
|
||||
setTargetApp('none');
|
||||
}
|
||||
}
|
||||
}, [integrationAuthApps]);
|
||||
|
||||
@@ -99,21 +104,29 @@ export default function GitHubCreateIntegrationPage() {
|
||||
value={targetApp}
|
||||
onValueChange={(val) => setTargetApp(val)}
|
||||
className='w-full border border-mineshaft-500'
|
||||
isDisabled={integrationAuthApps.length === 0}
|
||||
>
|
||||
{integrationAuthApps.map((integrationAuthApp) => (
|
||||
<SelectItem value={integrationAuthApp.name} key={`github-environment-${integrationAuthApp.name}`}>
|
||||
{integrationAuthApp.name}
|
||||
{integrationAuthApps.length > 0 ? (
|
||||
integrationAuthApps.map((integrationAuthApp) => (
|
||||
<SelectItem value={integrationAuthApp.name} key={`github-environment-${integrationAuthApp.name}`}>
|
||||
{integrationAuthApp.name}
|
||||
</SelectItem>
|
||||
))
|
||||
) : (
|
||||
<SelectItem value="none" key="target-app-none">
|
||||
No repositories found
|
||||
</SelectItem>
|
||||
))}
|
||||
)}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<Button
|
||||
onClick={handleButtonClick}
|
||||
color="mineshaft"
|
||||
className='mt-4'
|
||||
isLoading={isLoading}
|
||||
onClick={handleButtonClick}
|
||||
color="mineshaft"
|
||||
className='mt-4'
|
||||
isLoading={isLoading}
|
||||
isDisabled={integrationAuthApps.length === 0}
|
||||
>
|
||||
Create Integration
|
||||
Create Integration
|
||||
</Button>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
@@ -11,10 +11,18 @@ import {
|
||||
Select,
|
||||
SelectItem
|
||||
} from '../../../components/v2';
|
||||
import { useGetIntegrationAuthApps,useGetIntegrationAuthById } from '../../../hooks/api/integrationAuth';
|
||||
import {
|
||||
useGetIntegrationAuthApps,
|
||||
useGetIntegrationAuthById,
|
||||
useGetIntegrationAuthTeams} from '../../../hooks/api/integrationAuth';
|
||||
import { useGetWorkspaceById } from '../../../hooks/api/workspace';
|
||||
import createIntegration from "../../api/integrations/createIntegration";
|
||||
|
||||
const gitLabEntities = [
|
||||
{ name: 'Individual', value: 'individual' },
|
||||
{ name: 'Group', value: 'group' }
|
||||
]
|
||||
|
||||
export default function GitLabCreateIntegrationPage() {
|
||||
const router = useRouter();
|
||||
|
||||
@@ -22,28 +30,52 @@ export default function GitLabCreateIntegrationPage() {
|
||||
|
||||
const { data: workspace } = useGetWorkspaceById(localStorage.getItem('projectData.id') ?? '');
|
||||
const { data: integrationAuth } = useGetIntegrationAuthById(integrationAuthId as string ?? '');
|
||||
const { data: integrationAuthApps } = useGetIntegrationAuthApps(integrationAuthId as string ?? '');
|
||||
|
||||
const [targetTeamId, setTargetTeamId] = useState<string | null>(null);
|
||||
|
||||
const { data: integrationAuthApps } = useGetIntegrationAuthApps({
|
||||
integrationAuthId: integrationAuthId as string ?? '',
|
||||
...(targetTeamId ? { teamId: targetTeamId } : {})
|
||||
});
|
||||
const { data: integrationAuthTeams } = useGetIntegrationAuthTeams(integrationAuthId as string ?? '');
|
||||
|
||||
const [targetEntity, setTargetEntity] = useState(gitLabEntities[0].value);
|
||||
const [selectedSourceEnvironment, setSelectedSourceEnvironment] = useState('');
|
||||
const [owner, setOwner] = useState<string | null>(null);
|
||||
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(() => {
|
||||
// TODO: handle case where apps can be empty
|
||||
if (integrationAuthApps) {
|
||||
setTargetApp(integrationAuthApps[0].name);
|
||||
setOwner(integrationAuthApps[0]?.owner ?? null);
|
||||
if (integrationAuthApps) {
|
||||
if (integrationAuthApps.length > 0) {
|
||||
setTargetAppId(integrationAuthApps[0].appId as string);
|
||||
} else {
|
||||
setTargetAppId('none');
|
||||
}
|
||||
}
|
||||
}, [integrationAuthApps]);
|
||||
|
||||
useEffect(() => {
|
||||
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 {
|
||||
@@ -53,11 +85,11 @@ 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,
|
||||
owner: null,
|
||||
path: null,
|
||||
region: null
|
||||
});
|
||||
@@ -71,7 +103,7 @@ export default function GitLabCreateIntegrationPage() {
|
||||
}
|
||||
}
|
||||
|
||||
return (integrationAuth && workspace && selectedSourceEnvironment && integrationAuthApps && targetApp) ? (
|
||||
return (integrationAuth && workspace && selectedSourceEnvironment && integrationAuthApps && integrationAuthTeams && targetAppId) ? (
|
||||
<div className="h-full w-full flex justify-center items-center">
|
||||
<Card className="max-w-md p-8 rounded-md">
|
||||
<CardTitle className='text-center'>GitLab Integration</CardTitle>
|
||||
@@ -85,33 +117,83 @@ export default function GitLabCreateIntegrationPage() {
|
||||
className='w-full border border-mineshaft-500'
|
||||
>
|
||||
{workspace?.environments.map((sourceEnvironment) => (
|
||||
<SelectItem value={sourceEnvironment.slug} key={`azure-key-vault-environment-${sourceEnvironment.slug}`}>
|
||||
<SelectItem value={sourceEnvironment.slug} key={`source-environment-${sourceEnvironment.slug}`}>
|
||||
{sourceEnvironment.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<FormControl
|
||||
label="GitLab Repo"
|
||||
label="GitLab Integration Type"
|
||||
className='mt-4'
|
||||
>
|
||||
<Select
|
||||
value={targetApp}
|
||||
onValueChange={(val) => setTargetApp(val)}
|
||||
value={targetEntity}
|
||||
onValueChange={(val) => setTargetEntity(val)}
|
||||
className='w-full border border-mineshaft-500'
|
||||
>
|
||||
{integrationAuthApps.map((integrationAuthApp) => (
|
||||
<SelectItem value={integrationAuthApp.name} key={`gitlab-environment-${integrationAuthApp.name}`}>
|
||||
{integrationAuthApp.name}
|
||||
{gitLabEntities.map((entity) => {
|
||||
return (
|
||||
<SelectItem value={entity.value} key={`target-entity-${entity.value}`}>
|
||||
{entity.name}
|
||||
</SelectItem>
|
||||
);
|
||||
})}
|
||||
</Select>
|
||||
</FormControl>
|
||||
{targetEntity === 'group' && targetTeamId && (
|
||||
<FormControl
|
||||
label="GitLab Group"
|
||||
className='mt-4'
|
||||
>
|
||||
<Select
|
||||
value={targetTeamId}
|
||||
onValueChange={(val) => setTargetTeamId(val)}
|
||||
className='w-full border border-mineshaft-500'
|
||||
>
|
||||
{integrationAuthTeams.length > 0 ? (
|
||||
integrationAuthTeams.map((integrationAuthTeam) => (
|
||||
<SelectItem value={integrationAuthTeam.teamId} key={`target-team-${integrationAuthTeam.teamId}`}>
|
||||
{integrationAuthTeam.name}
|
||||
</SelectItem>
|
||||
))
|
||||
) : (
|
||||
<SelectItem value="none" key="target-team-none">
|
||||
No groups found
|
||||
</SelectItem>
|
||||
)}
|
||||
</Select>
|
||||
</FormControl>
|
||||
)}
|
||||
<FormControl
|
||||
label="GitLab Project"
|
||||
className='mt-4'
|
||||
>
|
||||
<Select
|
||||
value={targetAppId}
|
||||
onValueChange={(val) => setTargetAppId(val)}
|
||||
className='w-full border border-mineshaft-500'
|
||||
isDisabled={integrationAuthApps.length === 0}
|
||||
>
|
||||
{integrationAuthApps.length > 0 ? (
|
||||
integrationAuthApps.map((integrationAuthApp) => (
|
||||
<SelectItem value={integrationAuthApp.appId as string} key={`target-app-${integrationAuthApp.appId}`}>
|
||||
{integrationAuthApp.name}
|
||||
</SelectItem>
|
||||
))
|
||||
) : (
|
||||
<SelectItem value="none" key="target-app-none">
|
||||
No projects found
|
||||
</SelectItem>
|
||||
))}
|
||||
)}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<Button
|
||||
onClick={handleButtonClick}
|
||||
color="mineshaft"
|
||||
className='mt-4'
|
||||
isLoading={isLoading}
|
||||
onClick={handleButtonClick}
|
||||
color="mineshaft"
|
||||
className='mt-4'
|
||||
isLoading={isLoading}
|
||||
isDisabled={integrationAuthApps.length === 0}
|
||||
>
|
||||
Create Integration
|
||||
</Button>
|
||||
|
||||
@@ -22,7 +22,9 @@ export default function HerokuCreateIntegrationPage() {
|
||||
|
||||
const { data: workspace } = useGetWorkspaceById(localStorage.getItem('projectData.id') ?? '');
|
||||
const { data: integrationAuth } = useGetIntegrationAuthById(integrationAuthId as string ?? '');
|
||||
const { data: integrationAuthApps } = useGetIntegrationAuthApps(integrationAuthId as string ?? '');
|
||||
const { data: integrationAuthApps } = useGetIntegrationAuthApps({
|
||||
integrationAuthId: integrationAuthId as string ?? ''
|
||||
});
|
||||
|
||||
const [selectedSourceEnvironment, setSelectedSourceEnvironment] = useState('');
|
||||
const [targetApp, setTargetApp] = useState('');
|
||||
@@ -36,9 +38,12 @@ export default function HerokuCreateIntegrationPage() {
|
||||
}, [workspace]);
|
||||
|
||||
useEffect(() => {
|
||||
// TODO: handle case where apps can be empty
|
||||
if (integrationAuthApps) {
|
||||
if (integrationAuthApps.length > 0) {
|
||||
setTargetApp(integrationAuthApps[0].name);
|
||||
} else {
|
||||
setTargetApp('none');
|
||||
}
|
||||
}
|
||||
}, [integrationAuthApps]);
|
||||
|
||||
@@ -83,7 +88,7 @@ export default function HerokuCreateIntegrationPage() {
|
||||
className='w-full border border-mineshaft-500'
|
||||
>
|
||||
{workspace?.environments.map((sourceEnvironment) => (
|
||||
<SelectItem value={sourceEnvironment.slug} key={`azure-key-vault-environment-${sourceEnvironment.slug}`}>
|
||||
<SelectItem value={sourceEnvironment.slug} key={`source-environment-${sourceEnvironment.slug}`}>
|
||||
{sourceEnvironment.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
@@ -97,19 +102,27 @@ export default function HerokuCreateIntegrationPage() {
|
||||
value={targetApp}
|
||||
onValueChange={(val) => setTargetApp(val)}
|
||||
className='w-full border border-mineshaft-500'
|
||||
isDisabled={integrationAuthApps.length === 0}
|
||||
>
|
||||
{integrationAuthApps.map((integrationAuthApp) => (
|
||||
<SelectItem value={integrationAuthApp.name} key={`heroku-environment-${integrationAuthApp.name}`}>
|
||||
{integrationAuthApp.name}
|
||||
{integrationAuthApps.length > 0 ? (
|
||||
integrationAuthApps.map((integrationAuthApp) => (
|
||||
<SelectItem value={integrationAuthApp.name} key={`target-app-${integrationAuthApp.name}`}>
|
||||
{integrationAuthApp.name}
|
||||
</SelectItem>
|
||||
))
|
||||
) : (
|
||||
<SelectItem value="none" key="target-app-none">
|
||||
No apps found
|
||||
</SelectItem>
|
||||
))}
|
||||
)}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<Button
|
||||
onClick={handleButtonClick}
|
||||
color="mineshaft"
|
||||
className='mt-4'
|
||||
isLoading={isLoading}
|
||||
onClick={handleButtonClick}
|
||||
color="mineshaft"
|
||||
className='mt-4'
|
||||
isLoading={isLoading}
|
||||
isDisabled={integrationAuthApps.length === 0}
|
||||
>
|
||||
Create Integration
|
||||
</Button>
|
||||
|
||||
@@ -29,7 +29,9 @@ export default function NetlifyCreateIntegrationPage() {
|
||||
|
||||
const { data: workspace } = useGetWorkspaceById(localStorage.getItem('projectData.id') ?? '');
|
||||
const { data: integrationAuth } = useGetIntegrationAuthById(integrationAuthId as string ?? '');
|
||||
const { data: integrationAuthApps } = useGetIntegrationAuthApps(integrationAuthId as string ?? '');
|
||||
const { data: integrationAuthApps } = useGetIntegrationAuthApps({
|
||||
integrationAuthId: integrationAuthId as string ?? ''
|
||||
});
|
||||
|
||||
const [selectedSourceEnvironment, setSelectedSourceEnvironment] = useState('');
|
||||
const [targetApp, setTargetApp] = useState('');
|
||||
@@ -45,10 +47,13 @@ export default function NetlifyCreateIntegrationPage() {
|
||||
}, [workspace]);
|
||||
|
||||
useEffect(() => {
|
||||
// TODO: handle case where apps can be empty
|
||||
if (integrationAuthApps) {
|
||||
setTargetApp(integrationAuthApps[0].name);
|
||||
if (integrationAuthApps) {
|
||||
if (integrationAuthApps.length > 0) {
|
||||
setTargetApp(integrationAuthApps[0].name);
|
||||
} else {
|
||||
setTargetApp('none');
|
||||
}
|
||||
}
|
||||
}, [integrationAuthApps]);
|
||||
|
||||
const handleButtonClick = async () => {
|
||||
@@ -91,7 +96,7 @@ export default function NetlifyCreateIntegrationPage() {
|
||||
className='w-full border border-mineshaft-500'
|
||||
>
|
||||
{workspace?.environments.map((sourceEnvironment) => (
|
||||
<SelectItem value={sourceEnvironment.slug} key={`azure-key-vault-environment-${sourceEnvironment.slug}`}>
|
||||
<SelectItem value={sourceEnvironment.slug} key={`source-environment-${sourceEnvironment.slug}`}>
|
||||
{sourceEnvironment.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
@@ -104,12 +109,19 @@ export default function NetlifyCreateIntegrationPage() {
|
||||
value={targetApp}
|
||||
onValueChange={(val) => setTargetApp(val)}
|
||||
className='w-full border border-mineshaft-500'
|
||||
isDisabled={integrationAuthApps.length === 0}
|
||||
>
|
||||
{integrationAuthApps.map((integrationAuthApp) => (
|
||||
<SelectItem value={integrationAuthApp.name} key={`heroku-environment-${integrationAuthApp.name}`}>
|
||||
{integrationAuthApp.name}
|
||||
{integrationAuthApps.length > 0 ? (
|
||||
integrationAuthApps.map((integrationAuthApp) => (
|
||||
<SelectItem value={integrationAuthApp.name} key={`target-app-${integrationAuthApp.name}`}>
|
||||
{integrationAuthApp.name}
|
||||
</SelectItem>
|
||||
))
|
||||
) : (
|
||||
<SelectItem value="none" key="target-app-none">
|
||||
No sites found
|
||||
</SelectItem>
|
||||
))}
|
||||
)}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<FormControl
|
||||
@@ -121,17 +133,18 @@ export default function NetlifyCreateIntegrationPage() {
|
||||
className='w-full border border-mineshaft-500'
|
||||
>
|
||||
{netlifyEnvironments.map((netlifyEnvironment) => (
|
||||
<SelectItem value={netlifyEnvironment.slug} key={`netlify-environment-${netlifyEnvironment.slug}`}>
|
||||
<SelectItem value={netlifyEnvironment.slug} key={`target-environment-${netlifyEnvironment.slug}`}>
|
||||
{netlifyEnvironment.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<Button
|
||||
onClick={handleButtonClick}
|
||||
color="mineshaft"
|
||||
className='mt-4'
|
||||
isLoading={isLoading}
|
||||
onClick={handleButtonClick}
|
||||
color="mineshaft"
|
||||
className='mt-4'
|
||||
isLoading={isLoading}
|
||||
isDisabled={integrationAuthApps.length === 0}
|
||||
>
|
||||
Create Integration
|
||||
</Button>
|
||||
|
||||
@@ -22,7 +22,9 @@ export default function RenderCreateIntegrationPage() {
|
||||
|
||||
const { data: workspace } = useGetWorkspaceById(localStorage.getItem('projectData.id') ?? '');
|
||||
const { data: integrationAuth } = useGetIntegrationAuthById(integrationAuthId as string ?? '');
|
||||
const { data: integrationAuthApps } = useGetIntegrationAuthApps(integrationAuthId as string ?? '');
|
||||
const { data: integrationAuthApps } = useGetIntegrationAuthApps({
|
||||
integrationAuthId: integrationAuthId as string ?? ''
|
||||
});
|
||||
|
||||
const [selectedSourceEnvironment, setSelectedSourceEnvironment] = useState('');
|
||||
const [targetApp, setTargetApp] = useState('');
|
||||
@@ -38,7 +40,11 @@ export default function RenderCreateIntegrationPage() {
|
||||
useEffect(() => {
|
||||
// TODO: handle case where apps can be empty
|
||||
if (integrationAuthApps) {
|
||||
setTargetApp(integrationAuthApps[0].name);
|
||||
if (integrationAuthApps.length > 0) {
|
||||
setTargetApp(integrationAuthApps[0].name);
|
||||
} else {
|
||||
setTargetApp('none');
|
||||
}
|
||||
}
|
||||
}, [integrationAuthApps]);
|
||||
|
||||
@@ -84,7 +90,7 @@ export default function RenderCreateIntegrationPage() {
|
||||
className='w-full border border-mineshaft-500'
|
||||
>
|
||||
{workspace?.environments.map((sourceEnvironment) => (
|
||||
<SelectItem value={sourceEnvironment.slug} key={`azure-key-vault-environment-${sourceEnvironment.slug}`}>
|
||||
<SelectItem value={sourceEnvironment.slug} key={`source-environment-${sourceEnvironment.slug}`}>
|
||||
{sourceEnvironment.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
@@ -98,19 +104,27 @@ export default function RenderCreateIntegrationPage() {
|
||||
value={targetApp}
|
||||
onValueChange={(val) => setTargetApp(val)}
|
||||
className='w-full border border-mineshaft-500'
|
||||
isDisabled={integrationAuthApps.length === 0}
|
||||
>
|
||||
{integrationAuthApps.map((integrationAuthApp) => (
|
||||
<SelectItem value={integrationAuthApp.name} key={`render-environment-${integrationAuthApp.name}`}>
|
||||
{integrationAuthApp.name}
|
||||
{integrationAuthApps.length > 0 ? (
|
||||
integrationAuthApps.map((integrationAuthApp) => (
|
||||
<SelectItem value={integrationAuthApp.name} key={`target-app-${integrationAuthApp.name}`}>
|
||||
{integrationAuthApp.name}
|
||||
</SelectItem>
|
||||
))
|
||||
) : (
|
||||
<SelectItem value="none" key="target-app-none">
|
||||
No services found
|
||||
</SelectItem>
|
||||
))}
|
||||
)}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<Button
|
||||
onClick={handleButtonClick}
|
||||
color="mineshaft"
|
||||
className='mt-4'
|
||||
isLoading={isLoading}
|
||||
onClick={handleButtonClick}
|
||||
color="mineshaft"
|
||||
className='mt-4'
|
||||
isLoading={isLoading}
|
||||
isDisabled={integrationAuthApps.length === 0}
|
||||
>
|
||||
Create Integration
|
||||
</Button>
|
||||
|
||||
@@ -22,7 +22,9 @@ export default function TravisCICreateIntegrationPage() {
|
||||
|
||||
const { data: workspace } = useGetWorkspaceById(localStorage.getItem('projectData.id') ?? '');
|
||||
const { data: integrationAuth } = useGetIntegrationAuthById(integrationAuthId as string ?? '');
|
||||
const { data: integrationAuthApps } = useGetIntegrationAuthApps(integrationAuthId as string ?? '');
|
||||
const { data: integrationAuthApps } = useGetIntegrationAuthApps({
|
||||
integrationAuthId: integrationAuthId as string ?? ''
|
||||
});
|
||||
|
||||
const [selectedSourceEnvironment, setSelectedSourceEnvironment] = useState('');
|
||||
const [targetApp, setTargetApp] = useState('');
|
||||
@@ -36,10 +38,13 @@ export default function TravisCICreateIntegrationPage() {
|
||||
}, [workspace]);
|
||||
|
||||
useEffect(() => {
|
||||
// TODO: handle case where apps can be empty
|
||||
if (integrationAuthApps) {
|
||||
setTargetApp(integrationAuthApps[0]?.name);
|
||||
if (integrationAuthApps) {
|
||||
if (integrationAuthApps.length > 0) {
|
||||
setTargetApp(integrationAuthApps[0].name);
|
||||
} else {
|
||||
setTargetApp('none');
|
||||
}
|
||||
}
|
||||
}, [integrationAuthApps]);
|
||||
|
||||
const handleButtonClick = async () => {
|
||||
@@ -84,7 +89,7 @@ export default function TravisCICreateIntegrationPage() {
|
||||
className='w-full border border-mineshaft-500'
|
||||
>
|
||||
{workspace?.environments.map((sourceEnvironment) => (
|
||||
<SelectItem value={sourceEnvironment.slug} key={`azure-key-vault-environment-${sourceEnvironment.slug}`}>
|
||||
<SelectItem value={sourceEnvironment.slug} key={`source-environment-${sourceEnvironment.slug}`}>
|
||||
{sourceEnvironment.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
@@ -98,19 +103,27 @@ export default function TravisCICreateIntegrationPage() {
|
||||
value={targetApp}
|
||||
onValueChange={(val) => setTargetApp(val)}
|
||||
className='w-full border border-mineshaft-500'
|
||||
isDisabled={integrationAuthApps.length === 0}
|
||||
>
|
||||
{integrationAuthApps.map((integrationAuthApp) => (
|
||||
<SelectItem value={integrationAuthApp.name} key={`render-environment-${integrationAuthApp.name}`}>
|
||||
{integrationAuthApp.name}
|
||||
{integrationAuthApps.length > 0 ? (
|
||||
integrationAuthApps.map((integrationAuthApp) => (
|
||||
<SelectItem value={integrationAuthApp.name} key={`target-environment-${integrationAuthApp.name}`}>
|
||||
{integrationAuthApp.name}
|
||||
</SelectItem>
|
||||
))
|
||||
) : (
|
||||
<SelectItem value="none" key="target-app-none">
|
||||
No projects found
|
||||
</SelectItem>
|
||||
))}
|
||||
)}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<Button
|
||||
onClick={handleButtonClick}
|
||||
color="mineshaft"
|
||||
className='mt-4'
|
||||
isLoading={isLoading}
|
||||
onClick={handleButtonClick}
|
||||
color="mineshaft"
|
||||
className='mt-4'
|
||||
isLoading={isLoading}
|
||||
isDisabled={integrationAuthApps.length === 0}
|
||||
>
|
||||
Create Integration
|
||||
</Button>
|
||||
|
||||
@@ -28,11 +28,13 @@ export default function VercelCreateIntegrationPage() {
|
||||
|
||||
const { data: workspace } = useGetWorkspaceById(localStorage.getItem('projectData.id') ?? '');
|
||||
const { data: integrationAuth } = useGetIntegrationAuthById(integrationAuthId as string ?? '');
|
||||
const { data: integrationAuthApps } = useGetIntegrationAuthApps(integrationAuthId as string ?? '');
|
||||
const { data: integrationAuthApps } = useGetIntegrationAuthApps({
|
||||
integrationAuthId: integrationAuthId as string ?? ''
|
||||
});
|
||||
|
||||
const [selectedSourceEnvironment, setSelectedSourceEnvironment] = useState('');
|
||||
const [targetApp, setTargetApp] = useState('');
|
||||
const [targetEnvironment, setTargetEnvironemnt] = useState('');
|
||||
const [targetEnvironment, setTargetEnvironment] = useState('');
|
||||
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
@@ -43,10 +45,14 @@ export default function VercelCreateIntegrationPage() {
|
||||
}, [workspace]);
|
||||
|
||||
useEffect(() => {
|
||||
// TODO: handle case where apps can be empty
|
||||
if (integrationAuthApps) {
|
||||
if (integrationAuthApps.length > 0) {
|
||||
setTargetApp(integrationAuthApps[0].name);
|
||||
setTargetEnvironemnt(vercelEnvironments[0].slug);
|
||||
setTargetEnvironment(vercelEnvironments[0].slug);
|
||||
} else {
|
||||
setTargetApp('none');
|
||||
setTargetEnvironment(vercelEnvironments[0].slug);
|
||||
}
|
||||
}
|
||||
}, [integrationAuthApps]);
|
||||
|
||||
@@ -103,12 +109,19 @@ export default function VercelCreateIntegrationPage() {
|
||||
value={targetApp}
|
||||
onValueChange={(val) => setTargetApp(val)}
|
||||
className='w-full border border-mineshaft-500'
|
||||
isDisabled={integrationAuthApps.length === 0}
|
||||
>
|
||||
{integrationAuthApps.map((integrationAuthApp) => (
|
||||
<SelectItem value={integrationAuthApp.name} key={`heroku-environment-${integrationAuthApp.name}`}>
|
||||
{integrationAuthApp.name}
|
||||
{integrationAuthApps.length > 0 ? (
|
||||
integrationAuthApps.map((integrationAuthApp) => (
|
||||
<SelectItem value={integrationAuthApp.name} key={`target-app-${integrationAuthApp.name}`}>
|
||||
{integrationAuthApp.name}
|
||||
</SelectItem>
|
||||
))
|
||||
) : (
|
||||
<SelectItem value="none" key="target-app-none">
|
||||
No projects found
|
||||
</SelectItem>
|
||||
))}
|
||||
)}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<FormControl
|
||||
@@ -116,21 +129,22 @@ export default function VercelCreateIntegrationPage() {
|
||||
>
|
||||
<Select
|
||||
value={targetEnvironment}
|
||||
onValueChange={(val) => setTargetEnvironemnt(val)}
|
||||
onValueChange={(val) => setTargetEnvironment(val)}
|
||||
className='w-full border border-mineshaft-500'
|
||||
>
|
||||
{vercelEnvironments.map((vercelEnvironment) => (
|
||||
<SelectItem value={vercelEnvironment.slug} key={`vercel-environment-${vercelEnvironment.slug}`}>
|
||||
<SelectItem value={vercelEnvironment.slug} key={`target-environment-${vercelEnvironment.slug}`}>
|
||||
{vercelEnvironment.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<Button
|
||||
onClick={handleButtonClick}
|
||||
color="mineshaft"
|
||||
className='mt-4'
|
||||
isLoading={isLoading}
|
||||
onClick={handleButtonClick}
|
||||
color="mineshaft"
|
||||
className='mt-4'
|
||||
isLoading={isLoading}
|
||||
isDisabled={integrationAuthApps.length === 0}
|
||||
>
|
||||
Create Integration
|
||||
</Button>
|
||||
|
||||
Reference in New Issue
Block a user