From 787e54fb91bb26f55e9097ddc44fd5a389bd1134 Mon Sep 17 00:00:00 2001 From: Tuan Dang Date: Wed, 14 Dec 2022 18:18:21 -0500 Subject: [PATCH] Finish Netlify integration v1 full-loop --- .../controllers/integrationAuthController.ts | 2 +- .../src/controllers/integrationController.ts | 15 +- backend/src/helpers/bot.ts | 1 + backend/src/helpers/integration.ts | 23 +- backend/src/integrations/apps.ts | 69 +++++- backend/src/integrations/exchange.ts | 57 +++-- backend/src/integrations/sync.ts | 228 +++++++++++++++--- backend/src/models/integration.ts | 12 +- backend/src/models/integrationAuth.ts | 8 +- backend/src/routes/integration.ts | 3 + .../components/integrations/Integration.tsx | 102 ++++++-- .../api/integrations/updateIntegration.js | 12 +- frontend/pages/netlify.js | 2 - frontend/public/data/frequentConstants.ts | 15 +- 14 files changed, 441 insertions(+), 108 deletions(-) diff --git a/backend/src/controllers/integrationAuthController.ts b/backend/src/controllers/integrationAuthController.ts index f38516e30a..b5c9167aac 100644 --- a/backend/src/controllers/integrationAuthController.ts +++ b/backend/src/controllers/integrationAuthController.ts @@ -51,7 +51,7 @@ export const getIntegrationAuthApps = async (req: Request, res: Response) => { let apps; try { apps = await getApps({ - integration: req.integrationAuth.integration, + integrationAuth: req.integrationAuth, accessToken: req.accessToken }); } catch (err) { diff --git a/backend/src/controllers/integrationController.ts b/backend/src/controllers/integrationController.ts index 87ee77dbe6..910c7e825a 100644 --- a/backend/src/controllers/integrationController.ts +++ b/backend/src/controllers/integrationController.ts @@ -35,8 +35,15 @@ export const updateIntegration = async (req: Request, res: Response) => { // integration has the correct fields populated in [Integration] try { - const { app, environment, isActive, target } = req.body; - + const { + app, + environment, + isActive, + target, // vercel-specific integration param + context, // netlify-specific integration param + siteId // netlify-specific integration param + } = req.body; + integration = await Integration.findOneAndUpdate( { _id: req.integration._id @@ -45,7 +52,9 @@ export const updateIntegration = async (req: Request, res: Response) => { environment, isActive, app, - target + target, + context, + siteId }, { new: true diff --git a/backend/src/helpers/bot.ts b/backend/src/helpers/bot.ts index 3235a488d5..3285ccd6f1 100644 --- a/backend/src/helpers/bot.ts +++ b/backend/src/helpers/bot.ts @@ -74,6 +74,7 @@ const getSecretsHelper = async ({ const key = await getKey({ workspaceId }); const secrets = await Secret.find({ workspaceId, + environment, type: SECRET_SHARED }); diff --git a/backend/src/helpers/integration.ts b/backend/src/helpers/integration.ts index aae9d4b00d..9aaff9741a 100644 --- a/backend/src/helpers/integration.ts +++ b/backend/src/helpers/integration.ts @@ -11,13 +11,15 @@ import { BotService, IntegrationService } from '../services'; import { ENV_DEV, EVENT_PUSH_SECRETS, - INTEGRATION_VERCEL + INTEGRATION_VERCEL, + INTEGRATION_NETLIFY } from '../variables'; interface Update { workspace: string; integration: string; teamId?: string; + accountId?: string; } /** @@ -55,8 +57,6 @@ const handleOAuthExchangeHelper = async ({ integration, code }); - - return; let update: Update = { workspace: workspaceId, @@ -67,6 +67,9 @@ const handleOAuthExchangeHelper = async ({ case INTEGRATION_VERCEL: update.teamId = res.teamId; break; + case INTEGRATION_NETLIFY: + update.accountId = res.accountId; + break; } integrationAuth = await IntegrationAuth.findOneAndUpdate({ @@ -124,11 +127,12 @@ const syncIntegrationsHelper = async ({ }) => { let integrations; try { + integrations = await Integration.find({ workspace: workspaceId, isActive: true, app: { $ne: null } - }).populate<{integrationAuth: IIntegrationAuth}>('integrationAuth', 'accessToken'); + }); // for each workspace integration, sync/push secrets // to that integration @@ -139,22 +143,23 @@ const syncIntegrationsHelper = async ({ environment: integration.environment }); + const integrationAuth = await IntegrationAuth.findById(integration.integrationAuth); + if (!integrationAuth) throw new Error('Failed to find integration auth'); + // get integration auth access token const accessToken = await getIntegrationAuthAccessHelper({ - integrationAuthId: integration.integrationAuth._id.toString() + integrationAuthId: integration.integrationAuth.toString() }); // sync secrets to integration await syncSecrets({ - integration: integration.integration, - app: integration.app, - target: integration.target, + integration, + integrationAuth, secrets, accessToken }); } } catch (err) { - console.log('syncIntegrationsHelper error', err); Sentry.setUser(null); Sentry.captureException(err); throw new Error('Failed to sync secrets to integrations'); diff --git a/backend/src/integrations/apps.ts b/backend/src/integrations/apps.ts index f255214817..70680ef7d6 100644 --- a/backend/src/integrations/apps.ts +++ b/backend/src/integrations/apps.ts @@ -1,10 +1,15 @@ import axios from 'axios'; import * as Sentry from '@sentry/node'; +import { + IIntegrationAuth +} from '../models'; import { INTEGRATION_HEROKU, INTEGRATION_VERCEL, + INTEGRATION_NETLIFY, INTEGRATION_HEROKU_API_URL, - INTEGRATION_VERCEL_API_URL + INTEGRATION_VERCEL_API_URL, + INTEGRATION_NETLIFY_API_URL } from '../variables'; /** @@ -16,15 +21,21 @@ import { * @returns {String} apps.name - name of integration app */ const getApps = async ({ - integration, + integrationAuth, accessToken }: { - integration: string; + integrationAuth: IIntegrationAuth; accessToken: string; }) => { - let apps; + + interface App { + name: string; + siteId?: string; + } + + let apps: App[]; // TODO: add type and define payloads for apps try { - switch (integration) { + switch (integrationAuth.integration) { case INTEGRATION_HEROKU: apps = await getAppsHeroku({ accessToken @@ -35,6 +46,12 @@ const getApps = async ({ accessToken }); break; + case INTEGRATION_NETLIFY: + apps = await getAppsNetlify({ + integrationAuth, + accessToken + }); + break; } } catch (err) { @@ -82,9 +99,9 @@ const getAppsHeroku = async ({ /** * Return list of names of apps for Vercel integration * @param {Object} obj - * @param {String} obj.accessToken - access token for Heroku API - * @returns {Object[]} apps - names of Heroku apps - * @returns {String} apps.name - name of Heroku app + * @param {String} obj.accessToken - access token for Vercel API + * @returns {Object[]} apps - names of Vercel apps + * @returns {String} apps.name - name of Vercel app */ const getAppsVercel = async ({ accessToken @@ -111,6 +128,42 @@ const getAppsVercel = async ({ return apps; } +/** + * Return list of names of sites for Netlify integration + * @param {Object} obj + * @param {String} obj.accessToken - access token for Netlify API + * @returns {Object[]} apps - names of Netlify sites + * @returns {String} apps.name - name of Netlify site + */ +const getAppsNetlify = async ({ + integrationAuth, + accessToken +}: { + integrationAuth: IIntegrationAuth; + accessToken: string; +}) => { + let apps; + try { + const res = (await axios.get(`${INTEGRATION_NETLIFY_API_URL}/api/v1/sites`, { + headers: { + Authorization: `Bearer ${accessToken}` + } + })).data; + + apps = res.map((a: any) => ({ + name: a.name, + siteId: a.site_id + })); + + } catch (err) { + Sentry.setUser(null); + Sentry.captureException(err); + throw new Error('Failed to get Netlify integration apps'); + } + + return apps; +} + export { getApps } \ No newline at end of file diff --git a/backend/src/integrations/exchange.ts b/backend/src/integrations/exchange.ts index 1c764c1da1..5b7bc71ae3 100644 --- a/backend/src/integrations/exchange.ts +++ b/backend/src/integrations/exchange.ts @@ -35,6 +35,14 @@ interface ExchangeCodeVercelResponse { team_id?: string; } +interface ExchangeCodeNetlifyResponse { + access_token: string; + token_type: string; + refresh_token: string; + scope: string; + created_at: number; +} + /** * Return [accessToken], [accessExpiresAt], and [refreshToken] for OAuth2 * code-token exchange for integration named [integration] @@ -152,7 +160,6 @@ const exchangeCodeVercel = async ({ redirect_uri: `${SITE_URL}/vercel` } as any) )).data; - } catch (err) { Sentry.setUser(null); Sentry.captureException(err); @@ -182,42 +189,50 @@ const exchangeCodeNetlify = async ({ }: { code: string; }) => { - console.log('exchangeCodeNetlify'); - let res: ExchangeCodeVercelResponse; + let res: ExchangeCodeNetlifyResponse; + let accountId; try { res = (await axios.post( - INTEGRATION_VERCEL_TOKEN_URL, + INTEGRATION_NETLIFY_TOKEN_URL, new URLSearchParams({ + grant_type: 'authorization_code', code: code, - client_id: CLIENT_ID_VERCEL, - client_secret: CLIENT_SECRET_VERCEL, - redirect_uri: `${SITE_URL}/vercel` + client_id: CLIENT_ID_NETLIFY, + client_secret: CLIENT_SECRET_NETLIFY, + redirect_uri: `${SITE_URL}/netlify` } as any) )).data; - res = (await axios.post( - INTEGRATION_NETLIFY_TOKEN_URL, - `${"https://api.netlify.com/oauth/token"}?code=${code}&client_id=${CLIENT_ID_NETLIFY}&client_secret=${CLIENT_SECRET_NETLIFY}&grant_type=authorization_code&redirect_uri=${SITE_URL}/netlify` - // INTEGRATION_NETLIFY_TOKEN_URL, - // new URLSearchParams({ - // code: code, - // client_id: CLIENT_ID_NETLIFY, - // client_secret: CLIENT_SECRET_NETLIFY, - // redirect_uri: `${SITE_URL}/netlify` - // } as any) - )); + const res2 = await axios.get( + 'https://api.netlify.com/api/v1/sites', + { + headers: { + Authorization: `Bearer ${res.access_token}` + } + } + ); - console.log('resss', res); + const res3 = (await axios.get( + 'https://api.netlify.com/api/v1/accounts', + { + headers: { + Authorization: `Bearer ${res.access_token}` + } + } + )).data; + accountId = res3[0].id; + } catch (err) { - console.error('netlify err', err); Sentry.setUser(null); Sentry.captureException(err); throw new Error('Failed OAuth2 code-token exchange with Netlify'); } return ({ - + accessToken: res.access_token, + refreshToken: res.refresh_token, + accountId }); } diff --git a/backend/src/integrations/sync.ts b/backend/src/integrations/sync.ts index 930721d72c..3cef519cc2 100644 --- a/backend/src/integrations/sync.ts +++ b/backend/src/integrations/sync.ts @@ -1,10 +1,15 @@ import axios from 'axios'; import * as Sentry from '@sentry/node'; +import { + IIntegration, IIntegrationAuth +} from '../models'; import { INTEGRATION_HEROKU, INTEGRATION_VERCEL, + INTEGRATION_NETLIFY, INTEGRATION_HEROKU_API_URL, - INTEGRATION_VERCEL_API_URL + INTEGRATION_VERCEL_API_URL, + INTEGRATION_NETLIFY_API_URL } from '../variables'; // TODO: need a helper function in the future to handle integration @@ -13,7 +18,8 @@ import { /** * Sync/push [secrets] to [app] in integration named [integration] * @param {Object} obj - * @param {Object} obj.integration - name of integration + * @param {IIntegration} obj.integration - integration details + * @param {IIntegrationAuth} obj.integrationAuth - integration auth details * @param {Object} obj.app - app in integration * @param {Object} obj.target - (optional) target (environment) in integration * @param {Object} obj.secrets - secrets to push to integration (object where keys are secret keys and values are secret values) @@ -21,33 +27,39 @@ import { */ const syncSecrets = async ({ integration, - app, - target, + integrationAuth, secrets, accessToken, }: { - integration: string; - app: string; - target: string; + integration: IIntegration; + integrationAuth: IIntegrationAuth; secrets: any; accessToken: string; }) => { try { - switch (integration) { + switch (integration.integration) { case INTEGRATION_HEROKU: await syncSecretsHeroku({ - app, + integration, secrets, accessToken }); break; case INTEGRATION_VERCEL: await syncSecretsVercel({ - app, - target, + integration, secrets, accessToken }); + break; + case INTEGRATION_NETLIFY: + await syncSecretsNetlify({ + integration, + integrationAuth, + secrets, + accessToken + }); + break; } } catch (err) { Sentry.setUser(null); @@ -59,21 +71,21 @@ const syncSecrets = async ({ /** * Sync/push [secrets] to Heroku [app] * @param {Object} obj - * @param {String} obj.app - app in integration + * @param {IIntegration} obj.integration - integration details * @param {Object} obj.secrets - secrets to push to integration (object where keys are secret keys and values are secret values) */ const syncSecretsHeroku = async ({ - app, + integration, secrets, accessToken }: { - app: string; + integration: IIntegration, secrets: any; accessToken: string; }) => { try { const herokuSecrets = (await axios.get( - `${INTEGRATION_HEROKU_API_URL}/apps/${app}/config-vars`, + `${INTEGRATION_HEROKU_API_URL}/apps/${integration.app}/config-vars`, { headers: { Accept: 'application/vnd.heroku+json; version=3', @@ -89,7 +101,7 @@ const syncSecretsHeroku = async ({ }); await axios.patch( - `${INTEGRATION_HEROKU_API_URL}/apps/${app}/config-vars`, + `${INTEGRATION_HEROKU_API_URL}/apps/${integration.app}/config-vars`, secrets, { headers: { @@ -108,18 +120,15 @@ const syncSecretsHeroku = async ({ /** * Sync/push [secrets] to Heroku [app] * @param {Object} obj - * @param {String} obj.app - app in integration - * @param {String} obj.target - (optional) target (environment) in integration + * @param {IIntegration} obj.integration - integration details * @param {Object} obj.secrets - secrets to push to integration (object where keys are secret keys and values are secret values) */ const syncSecretsVercel = async ({ - app, - target, + integration, secrets, accessToken }: { - app: string; - target: string; + integration: IIntegration, secrets: any; accessToken: string; }) => { @@ -140,7 +149,7 @@ const syncSecretsVercel = async ({ }); const res = (await Promise.all((await axios.get( - `${INTEGRATION_VERCEL_API_URL}/v9/projects/${app}/env`, + `${INTEGRATION_VERCEL_API_URL}/v9/projects/${integration.app}/env`, { params, headers: { @@ -150,9 +159,9 @@ const syncSecretsVercel = async ({ )) .data .envs - .filter((secret: VercelSecret) => secret.target.includes(target)) + .filter((secret: VercelSecret) => secret.target.includes(integration.target)) .map(async (secret: VercelSecret) => (await axios.get( - `${INTEGRATION_VERCEL_API_URL}/v9/projects/${app}/env/${secret.id}`, + `${INTEGRATION_VERCEL_API_URL}/v9/projects/${integration.app}/env/${secret.id}`, { headers: { Authorization: `Bearer ${accessToken}` @@ -172,11 +181,12 @@ const syncSecretsVercel = async ({ // Identify secrets to create Object.keys(secrets).map((key) => { if (!(key in res)) { + // case: secret has been created newSecrets.push({ key: key, value: secrets[key], type: 'encrypted', - target: [target] + target: [integration.target] }); } }); @@ -191,7 +201,7 @@ const syncSecretsVercel = async ({ key: key, value: secrets[key], type: 'encrypted', - target: [target] + target: [integration.target] }); } } else { @@ -201,7 +211,7 @@ const syncSecretsVercel = async ({ key: key, value: res[key].value, type: 'encrypted', - target: [target], + target: [integration.target], }); } }); @@ -209,7 +219,7 @@ const syncSecretsVercel = async ({ // Sync/push new secrets if (newSecrets.length > 0) { await axios.post( - `${INTEGRATION_VERCEL_API_URL}/v10/projects/${app}/env`, + `${INTEGRATION_VERCEL_API_URL}/v10/projects/${integration.app}/env`, newSecrets, { headers: { @@ -227,7 +237,7 @@ const syncSecretsVercel = async ({ ...updatedSecret } = secret; await axios.patch( - `${INTEGRATION_VERCEL_API_URL}/v9/projects/${app}/env/${secret.id}`, + `${INTEGRATION_VERCEL_API_URL}/v9/projects/${integration.app}/env/${secret.id}`, updatedSecret, { headers: { @@ -242,7 +252,7 @@ const syncSecretsVercel = async ({ if (deleteSecrets.length > 0) { deleteSecrets.forEach(async (secret: VercelSecret) => { await axios.delete( - `${INTEGRATION_VERCEL_API_URL}/v9/projects/${app}/env/${secret.id}`, + `${INTEGRATION_VERCEL_API_URL}/v9/projects/${integration.app}/env/${secret.id}`, { headers: { Authorization: `Bearer ${accessToken}` @@ -258,6 +268,162 @@ const syncSecretsVercel = async ({ } } +/** + * Sync/push [secrets] to Netlify site [app] + * @param {Object} obj + * @param {IIntegration} obj.integration - integration details + * @param {IIntegrationAuth} obj.integrationAuth - integration auth details + * @param {Object} obj.secrets - secrets to push to integration (object where keys are secret keys and values are secret values) + */ +const syncSecretsNetlify = async ({ + integration, + integrationAuth, + secrets, + accessToken +}: { + integration: IIntegration; + integrationAuth: IIntegrationAuth; + secrets: any; + accessToken: string; +}) => { + try { + const getParams = new URLSearchParams({ + context_name: integration.context, + site_id: integration.siteId + }); + + const res = (await axios.get( + `${INTEGRATION_NETLIFY_API_URL}/api/v1/accounts/${integrationAuth.accountId}/env`, + { + params: getParams, + headers: { + Authorization: `Bearer ${accessToken}` + } + } + )) + .data + .reduce((obj: any, secret: any) => ({ + ...obj, + [secret.key]: secret.values[0].value + }), {}); + + interface UpdateNetlifySecret { + key: string; + context: string; + value: string; + } + + interface DeleteNetlifySecret { + key: string; + } + + interface NewNetlifySecretValue { + value: string; + context: string; + } + + interface NewNetlifySecret { + key: string; + values: NewNetlifySecretValue[]; + } + + let updateSecrets: UpdateNetlifySecret[] = []; + let deleteSecrets: DeleteNetlifySecret[] = []; + let newSecrets: NewNetlifySecret[] = []; + + // Identify secrets to create + Object.keys(secrets).map((key) => { + if (!(key in res)) { + // case: secret has been created + newSecrets.push({ + key: key, + values: [{ + value: secrets[key], // include id? + context: integration.context + }] + }); + } + }); + + // Identify secrets to update and delete + Object.keys(res).map((key) => { + if (key in secrets) { + if (res[key] !== secrets[key]) { + // case: secret value has changed + updateSecrets.push({ + key: key, + context: integration.context, + value: secrets[key] + }); + } + } else { + // case: secret has been deleted + deleteSecrets.push({ + key + }); + } + }); + + const syncParams = new URLSearchParams({ + site_id: integration.siteId + }); + + // Sync/push new secrets + if (newSecrets.length > 0) { + await axios.post( + `${INTEGRATION_NETLIFY_API_URL}/api/v1/accounts/${integrationAuth.accountId}/env`, + newSecrets, + { + params: syncParams, + headers: { + Authorization: `Bearer ${accessToken}` + } + } + ); + } + + // Sync/push updated secrets + if (updateSecrets.length > 0) { + + updateSecrets.forEach(async (secret: UpdateNetlifySecret) => { + await axios.patch( + `${INTEGRATION_NETLIFY_API_URL}/api/v1/accounts/${integrationAuth.accountId}/env/${secret.key}`, + { + context: secret.context, + value: secret.value + }, + { + params: syncParams, + headers: { + Authorization: `Bearer ${accessToken}` + } + } + ); + }); + } + + // Delete secrets + if (deleteSecrets.length > 0) { + deleteSecrets.forEach(async (secret: DeleteNetlifySecret) => { + await axios.delete( + `${INTEGRATION_NETLIFY_API_URL}/api/v1/accounts/${integrationAuth.accountId}/env/${secret.key}`, + { + params: syncParams, + headers: { + Authorization: `Bearer ${accessToken}` + } + } + ); + }); + } + + } catch (err) { + Sentry.setUser(null); + Sentry.captureException(err); + throw new Error('Failed to sync secrets to Heroku'); + } +} + export { syncSecrets } \ No newline at end of file diff --git a/backend/src/models/integration.ts b/backend/src/models/integration.ts index eb82b95caf..edbe0234ef 100644 --- a/backend/src/models/integration.ts +++ b/backend/src/models/integration.ts @@ -16,7 +16,9 @@ export interface IIntegration { isActive: boolean; app: string; target: string; - integration: 'heroku' | 'netlify'; + context: string; + siteId: string; + integration: 'heroku' | 'vercel' | 'netlify'; integrationAuth: Types.ObjectId; } @@ -44,6 +46,14 @@ const integrationSchema = new Schema( type: String, default: null }, + context: { // netlify-specific context (deploy) + type: String, + default: null + }, + siteId: { // netlify-specific site (app) id + type: String, + default: null + }, integration: { type: String, enum: [ diff --git a/backend/src/models/integrationAuth.ts b/backend/src/models/integrationAuth.ts index b03a73e52d..0da3eb0d8c 100644 --- a/backend/src/models/integrationAuth.ts +++ b/backend/src/models/integrationAuth.ts @@ -8,8 +8,9 @@ import { export interface IIntegrationAuth { _id: Types.ObjectId; workspace: Types.ObjectId; - integration: 'heroku' | 'netlify'; + integration: 'heroku' | 'vercel' | 'netlify'; teamId: string; + accountId: string; refreshCiphertext?: string; refreshIV?: string; refreshTag?: string; @@ -34,7 +35,10 @@ const integrationAuthSchema = new Schema( ], required: true }, - teamId: { // vercel-specific integration param set at OAuth2 code-token exchange + teamId: { // vercel-specific integration param + type: String + }, + accountId: { // netlify-specific integration param type: String }, refreshCiphertext: { diff --git a/backend/src/routes/integration.ts b/backend/src/routes/integration.ts index 88eebaa5f2..e6738a803b 100644 --- a/backend/src/routes/integration.ts +++ b/backend/src/routes/integration.ts @@ -20,6 +20,9 @@ router.patch( body('app').exists().trim(), body('environment').exists().trim(), body('isActive').exists().isBoolean(), + body('target').exists(), + body('context').exists(), + body('siteId').exists(), validateRequest, integrationController.updateIntegration ); diff --git a/frontend/components/integrations/Integration.tsx b/frontend/components/integrations/Integration.tsx index 8dabf9246b..8a323336eb 100644 --- a/frontend/components/integrations/Integration.tsx +++ b/frontend/components/integrations/Integration.tsx @@ -8,7 +8,8 @@ import { import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { envMapping, - reverseEnvMapping + reverseEnvMapping, + reverseContextNetlifyMapping } from "../../public/data/frequentConstants"; import updateIntegration from "../../pages/api/integrations/updateIntegration" import deleteIntegration from "../../pages/api/integrations/DeleteIntegration" @@ -36,23 +37,85 @@ const Integration = ({ ); const [fileState, setFileState] = useState([]); const router = useRouter(); - const [apps, setApps] = useState([]); - const [integrationApp, setIntegrationApp] = useState(null); - const [integrationTarget, setIntegrationTarget] = useState(null); + const [apps, setApps] = useState([]); // integration app objects + const [integrationApp, setIntegrationApp] = useState(null); // integration app name + const [integrationTarget, setIntegrationTarget] = useState(null); // vercel-specific integration param + const [integrationContext, setIntegrationContext] = useState(null); // netlify-specific integration param useEffect(async () => { + interface App { + name: string; + siteId?: string; + } + const tempApps = await getIntegrationApps({ integrationAuthId: integration.integrationAuth, }); - const tempAppNames = tempApps.map((app) => app.name); - setApps(tempAppNames); + setApps(tempApps); setIntegrationApp( - integration.app ? integration.app : tempAppNames[0] + integration.app ? integration.app : tempApps[0].name ); - setIntegrationTarget("Development"); + + switch (integration.integration) { + case "vercel": + setIntegrationTarget("Development"); + break; + case "netlify": + setIntegrationContext("All"); + break; + default: + break; + } }, []); + const renderIntegrationSpecificParams = (integration) => { + try { + switch (integration.integration) { + case "vercel": + return ( +
+
+ ENVIRONMENT +
+ +
+ ); + case "netlify": + return ( +
+
+ CONTEXT +
+ +
+ ); + default: + return
; + } + } catch (err) { + console.error(err); + } + } + if (!integrationApp || apps.length === 0) return
return ( @@ -91,27 +154,12 @@ const Integration = ({ APP app.name)} selected={integrationApp} onChange={setIntegrationApp} /> - {integration.integration === "vercel" && ( -
-
- ENVIRONMENT -
- -
- )} + {renderIntegrationSpecificParams(integration)}
{integration.isActive ? ( @@ -131,7 +179,9 @@ const Integration = ({ environment: envMapping[integrationEnvironment], app: integrationApp, isActive: true, - target: integrationTarget.toLowerCase() + target: integrationTarget ? integrationTarget.toLowerCase() : null, + context: integrationContext ? reverseContextNetlifyMapping[integrationContext] : null, + siteId: apps.find((app) => app.name === integrationApp).siteId }); router.reload(); }} diff --git a/frontend/pages/api/integrations/updateIntegration.js b/frontend/pages/api/integrations/updateIntegration.js index cb5288f5ca..c77297e3a2 100644 --- a/frontend/pages/api/integrations/updateIntegration.js +++ b/frontend/pages/api/integrations/updateIntegration.js @@ -9,7 +9,9 @@ import SecurityClient from "~/utilities/SecurityClient"; * @param {String} obj.app - name of app * @param {String} obj.environment - project environment to push secrets from * @param {Boolean} obj.isActive - active state - * @param {String} obj.target - (optional) target (environment) + * @param {String} obj.target - (optional) target (environment) for Vercel integration + * @param {String} obj.context - (optional) context (environment) for Netlify integration + * @param {String} obj.siteId - (optional) app (site_id) for Netlify integration * @returns */ const updateIntegration = ({ @@ -17,7 +19,9 @@ const updateIntegration = ({ app, environment, isActive, - target + target, + context, + siteId }) => { return SecurityClient.fetchCall( "/api/v1/integration/" + integrationId, @@ -30,7 +34,9 @@ const updateIntegration = ({ app, environment, isActive, - target + target, + context, + siteId }), } ).then(async (res) => { diff --git a/frontend/pages/netlify.js b/frontend/pages/netlify.js index 4217ba54d6..4fbcf2061b 100644 --- a/frontend/pages/netlify.js +++ b/frontend/pages/netlify.js @@ -16,11 +16,9 @@ export default function Netlify() { */ // eslint-disable-next-line react-hooks/exhaustive-deps useEffect(async () => { - console.log('AA'); if (state === localStorage.getItem('latestCSRFToken')) { localStorage.removeItem('latestCSRFToken'); - console.log('Netlify', parsedUrl); // http://localhost:8080/netlify?code=qnG0g_krhklWDpdUqfhU-t1sLeZzYI3gF2d6QVnL-Gc&state=3a78cd3154a9a99ddd4eb5d99dbb3289 // http://localhost:8080/netlify#access_token=d-78qUAnnSzvlgfG9Y_oUV6_4TQBxLbofImiBbKAjzE&token_type=Bearer&state=5da34fa49e301e9fa1a6e40925694b77 diff --git a/frontend/public/data/frequentConstants.ts b/frontend/public/data/frequentConstants.ts index d44706660b..bbaa42adef 100644 --- a/frontend/public/data/frequentConstants.ts +++ b/frontend/public/data/frequentConstants.ts @@ -12,7 +12,20 @@ const reverseEnvMapping = { test: "Testing", }; +const vercelMapping = { + +} + +const reverseContextNetlifyMapping = { + "All": "all", + "Local development": "dev", + "Branch deploys": "branch-deploy", + "Deploy Previews": "deploy-preview", + "Production": "production" +} + export { envMapping, - reverseEnvMapping + reverseEnvMapping, + reverseContextNetlifyMapping };