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:
BlackMagiq
2023-03-10 21:50:10 +07:00
committed by GitHub
27 changed files with 800 additions and 239 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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
});
}

View File

@@ -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;

View File

@@ -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
};
}

View File

@@ -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
}

View File

@@ -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 };

View File

@@ -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}`,

View 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
}

View File

@@ -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,

View File

@@ -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;

View File

@@ -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({

View File

@@ -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
*/

View File

@@ -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: ''
},

View File

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

View File

@@ -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
});
}

View File

@@ -10,4 +10,9 @@ export type App = {
name: string;
appId?: string;
owner?: string;
}
export type Team = {
name: string;
teamId: string;
}

View File

@@ -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;

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>