From 158eb584d271af422ef1b3c70c9a6a13b7cea75c Mon Sep 17 00:00:00 2001 From: Vladyslav Matsiiako Date: Wed, 7 Jun 2023 13:11:39 -0700 Subject: [PATCH 1/3] integration with checkly done --- backend/src/integrations/apps.ts | 35 +++++ backend/src/integrations/sync.ts | 111 +++++++++++++- backend/src/models/integration.ts | 9 +- backend/src/models/integrationAuth.ts | 2 +- backend/src/variables/integration.ts | 14 +- frontend/public/data/frequentConstants.ts | 3 +- .../public/images/integrations/Checkly.png | Bin 0 -> 8684 bytes .../src/components/basic/buttons/Button.tsx | 2 +- .../integrations/CloudIntegration.tsx | 12 +- .../integrations/FrameworkIntegration.tsx | 4 +- .../components/integrations/Integration.tsx | 22 +-- .../integrations/IntegrationSection.tsx | 2 +- frontend/src/components/v2/Select/Select.tsx | 4 +- frontend/src/pages/integrations/[id].tsx | 10 +- .../pages/integrations/checkly/authorize.tsx | 69 +++++++++ .../src/pages/integrations/checkly/create.tsx | 141 ++++++++++++++++++ 16 files changed, 408 insertions(+), 32 deletions(-) create mode 100644 frontend/public/images/integrations/Checkly.png create mode 100644 frontend/src/pages/integrations/checkly/authorize.tsx create mode 100644 frontend/src/pages/integrations/checkly/create.tsx diff --git a/backend/src/integrations/apps.ts b/backend/src/integrations/apps.ts index fa0020d616..334ffdb97e 100644 --- a/backend/src/integrations/apps.ts +++ b/backend/src/integrations/apps.ts @@ -16,6 +16,7 @@ import { INTEGRATION_CIRCLECI, INTEGRATION_TRAVISCI, INTEGRATION_SUPABASE, + INTEGRATION_CHECKLY, INTEGRATION_HEROKU_API_URL, INTEGRATION_GITLAB_API_URL, INTEGRATION_VERCEL_API_URL, @@ -26,6 +27,7 @@ import { INTEGRATION_CIRCLECI_API_URL, INTEGRATION_TRAVISCI_API_URL, INTEGRATION_SUPABASE_API_URL, + INTEGRATION_CHECKLY_API_URL } from "../variables"; interface App { @@ -120,6 +122,11 @@ const getApps = async ({ accessToken, }); break; + case INTEGRATION_CHECKLY: + apps = await getAppsCheckly({ + accessToken, + }); + break; } return apps; @@ -601,4 +608,32 @@ const getAppsSupabase = async ({ accessToken }: { accessToken: string }) => { return apps; }; +/** + * Return list of projects for the Checkly integration + * @param {Object} obj + * @param {String} obj.accessToken - access token for Supabase API + * @returns {Object[]} apps - names of Supabase apps + * @returns {String} apps.name - name of Supabase app + */ +const getAppsCheckly = async ({ accessToken }: { accessToken: string }) => { + const { data } = await standardRequest.get( + `${INTEGRATION_CHECKLY_API_URL}/v1/accounts`, + { + headers: { + Authorization: `Bearer ${accessToken}`, + "Accept": "application/json", + }, + } + ); + + const apps = data.map((a: any) => { + return { + name: a.name, + appId: a.id, + }; + }); + + return apps; +}; + export { getApps }; diff --git a/backend/src/integrations/sync.ts b/backend/src/integrations/sync.ts index e6082d7c5e..a35e4567fe 100644 --- a/backend/src/integrations/sync.ts +++ b/backend/src/integrations/sync.ts @@ -35,7 +35,9 @@ import { INTEGRATION_FLYIO_API_URL, INTEGRATION_CIRCLECI_API_URL, INTEGRATION_TRAVISCI_API_URL, - INTEGRATION_SUPABASE_API_URL + INTEGRATION_SUPABASE_API_URL, + INTEGRATION_CHECKLY, + INTEGRATION_CHECKLY_API_URL } from "../variables"; import { standardRequest} from '../config/request'; @@ -165,6 +167,13 @@ const syncSecrets = async ({ accessToken }); break; + case INTEGRATION_CHECKLY: + await syncSecretsCheckly({ + integration, + secrets, + accessToken, + }); + break; } } catch (err) { Sentry.setUser(null); @@ -1729,4 +1738,104 @@ const syncSecretsSupabase = async ({ }; +/** + * Sync/push [secrets] to Checkly app + * @param {Object} obj + * @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) + * @param {String} obj.accessToken - access token for Checkly integration + */ +const syncSecretsCheckly = async ({ + integration, + secrets, + accessToken, +}: { + integration: IIntegration; + secrets: any; + accessToken: string; +}) => { + try { + // get secrets from travis-ci + const getSecretsRes = ( + await standardRequest.get( + `${INTEGRATION_CHECKLY_API_URL}/v1/variables`, + { + headers: { + "Authorization": `Bearer ${accessToken}`, + "Accept-Encoding": "application/json", + "X-Checkly-Account": integration.appId + }, + } + ) + ) + .data + .reduce((obj: any, secret: any) => ({ + ...obj, + [secret.key]: secret.value + }), {}); + + // add secrets + for await (const key of Object.keys(secrets)) { + if (!(key in getSecretsRes)) { + // case: secret does not exist in checkly + // -> add secret + await standardRequest.post( + `${INTEGRATION_CHECKLY_API_URL}/v1/variables`, + { + key, + value: secrets[key] ? secrets[key] : 'EMPTY' + }, + { + headers: { + "Authorization": `Bearer ${accessToken}`, + "Accept": "application/json", + "Content-Type": "application/json", + "X-Checkly-Account": integration.appId + }, + } + ); + } else { + // case: secret exists in checkly + // -> update/set secret + await standardRequest.put( + `${INTEGRATION_CHECKLY_API_URL}/v1/variables/${key}`, + { + value: secrets[key] ? secrets[key] : 'EMPTY' + }, + { + headers: { + "Authorization": `Bearer ${accessToken}`, + "Content-Type": "application/json", + "Accept": "application/json", + "X-Checkly-Account": integration.appId + }, + } + ); + } + } + + for await (const key of Object.keys(getSecretsRes)) { + if (!(key in secrets)){ + // delete secret + await standardRequest.delete( + `${INTEGRATION_CHECKLY_API_URL}/v1/variables/${key}`, + { + headers: { + "Authorization": `Bearer ${accessToken}`, + "Accept": "application/json", + "X-Checkly-Account": integration.appId + }, + } + ); + } + } + } catch (err) { + console.log(err) + Sentry.setUser(null); + Sentry.captureException(err); + throw new Error("Failed to sync secrets to Checkly"); + } +}; + + export { syncSecrets }; diff --git a/backend/src/models/integration.ts b/backend/src/models/integration.ts index 555f481f8f..504d4a2be5 100644 --- a/backend/src/models/integration.ts +++ b/backend/src/models/integration.ts @@ -13,7 +13,8 @@ import { INTEGRATION_FLYIO, INTEGRATION_CIRCLECI, INTEGRATION_TRAVISCI, - INTEGRATION_SUPABASE + INTEGRATION_SUPABASE, + INTEGRATION_CHECKLY } from "../variables"; export interface IIntegration { @@ -45,7 +46,8 @@ export interface IIntegration { | 'flyio' | 'circleci' | 'travisci' - | 'supabase'; + | 'supabase' + | 'checkly'; integrationAuth: Types.ObjectId; } @@ -130,7 +132,8 @@ const integrationSchema = new Schema( INTEGRATION_FLYIO, INTEGRATION_CIRCLECI, INTEGRATION_TRAVISCI, - INTEGRATION_SUPABASE + INTEGRATION_SUPABASE, + INTEGRATION_CHECKLY ], required: true, }, diff --git a/backend/src/models/integrationAuth.ts b/backend/src/models/integrationAuth.ts index affe811df9..4f2209f2d0 100644 --- a/backend/src/models/integrationAuth.ts +++ b/backend/src/models/integrationAuth.ts @@ -22,7 +22,7 @@ import { export interface IIntegrationAuth extends Document { _id: Types.ObjectId; workspace: Types.ObjectId; - integration: 'heroku' | 'vercel' | 'netlify' | 'github' | 'gitlab' | 'render' | 'railway' | 'flyio' | 'azure-key-vault' | 'circleci' | 'travisci' | 'supabase' | 'aws-parameter-store' | 'aws-secret-manager'; + integration: 'heroku' | 'vercel' | 'netlify' | 'github' | 'gitlab' | 'render' | 'railway' | 'flyio' | 'azure-key-vault' | 'circleci' | 'travisci' | 'supabase' | 'aws-parameter-store' | 'aws-secret-manager' | 'checkly'; teamId: string; accountId: string; refreshCiphertext?: string; diff --git a/backend/src/variables/integration.ts b/backend/src/variables/integration.ts index 6efd968179..7fa8f6a23a 100644 --- a/backend/src/variables/integration.ts +++ b/backend/src/variables/integration.ts @@ -22,6 +22,7 @@ export const INTEGRATION_FLYIO = "flyio"; export const INTEGRATION_CIRCLECI = "circleci"; export const INTEGRATION_TRAVISCI = "travisci"; export const INTEGRATION_SUPABASE = 'supabase'; +export const INTEGRATION_CHECKLY = 'checkly'; export const INTEGRATION_SET = new Set([ INTEGRATION_AZURE_KEY_VAULT, INTEGRATION_HEROKU, @@ -33,7 +34,8 @@ export const INTEGRATION_SET = new Set([ INTEGRATION_FLYIO, INTEGRATION_CIRCLECI, INTEGRATION_TRAVISCI, - INTEGRATION_SUPABASE + INTEGRATION_SUPABASE, + INTEGRATION_CHECKLY ]); // integration types @@ -60,6 +62,7 @@ export const INTEGRATION_FLYIO_API_URL = "https://api.fly.io/graphql"; export const INTEGRATION_CIRCLECI_API_URL = "https://circleci.com/api"; export const INTEGRATION_TRAVISCI_API_URL = "https://api.travis-ci.com"; export const INTEGRATION_SUPABASE_API_URL = 'https://api.supabase.com'; +export const INTEGRATION_CHECKLY_API_URL = 'https://api.checklyhq.com'; export const getIntegrationOptions = async () => { const INTEGRATION_OPTIONS = [ @@ -190,6 +193,15 @@ export const getIntegrationOptions = async () => { clientId: '', docsLink: '' }, + { + name: 'Checkly', + slug: 'checkly', + image: 'Checkly.png', + isAvailable: true, + type: 'pat', + clientId: '', + docsLink: '' + }, { name: 'Google Cloud Platform', slug: 'gcp', diff --git a/frontend/public/data/frequentConstants.ts b/frontend/public/data/frequentConstants.ts index 40c88f65b5..5efc33e273 100644 --- a/frontend/public/data/frequentConstants.ts +++ b/frontend/public/data/frequentConstants.ts @@ -16,7 +16,8 @@ const integrationSlugNameMapping: Mapping = { 'flyio': 'Fly.io', 'circleci': 'CircleCI', 'travisci': 'TravisCI', - 'supabase': 'Supabase' + 'supabase': 'Supabase', + 'checkly': 'Checkly' } const envMapping: Mapping = { diff --git a/frontend/public/images/integrations/Checkly.png b/frontend/public/images/integrations/Checkly.png new file mode 100644 index 0000000000000000000000000000000000000000..563513d4b4c3da965666396fad63c473e422d090 GIT binary patch literal 8684 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 Bp#A^= literal 0 HcmV?d00001 diff --git a/frontend/src/components/basic/buttons/Button.tsx b/frontend/src/components/basic/buttons/Button.tsx index 890650d965..461d160bbe 100644 --- a/frontend/src/components/basic/buttons/Button.tsx +++ b/frontend/src/components/basic/buttons/Button.tsx @@ -57,7 +57,7 @@ const Button = ({ color === 'mineshaft' && !activityStatus && 'bg-mineshaft', (color === 'primary' || !color) && activityStatus && 'bg-primary border border-primary-400 opacity-80 hover:opacity-100', (color === 'primary' || !color) && !activityStatus && 'bg-primary', - color === 'red' && 'bg-red', + color === 'red' && 'bg-red-800 border border-red', // Changing the opacity when active vs when not activityStatus ? 'opacity-100 cursor-pointer' : 'opacity-40', diff --git a/frontend/src/components/integrations/CloudIntegration.tsx b/frontend/src/components/integrations/CloudIntegration.tsx index 9536957ce9..cbd5853227 100644 --- a/frontend/src/components/integrations/CloudIntegration.tsx +++ b/frontend/src/components/integrations/CloudIntegration.tsx @@ -1,5 +1,5 @@ import Image from 'next/image'; -import { faCheck, faX } from '@fortawesome/free-solid-svg-icons'; +import { faCheck, faXmark } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import deleteIntegrationAuth from '../../pages/api/integrations/DeleteIntegrationAuth'; @@ -44,9 +44,9 @@ const CloudIntegration = ({ tabIndex={0} className={`relative ${ cloudIntegrationOption.isAvailable - ? 'cursor-pointer duration-200 hover:bg-white/10' + ? 'cursor-pointer duration-200 hover:bg-mineshaft-700' : 'opacity-50' - } flex h-32 flex-row items-center rounded-md bg-white/5 p-4`} + } flex h-32 flex-row items-center rounded-md bg-mineshaft-800 border border-mineshaft-600 p-4`} onClick={() => { if (!cloudIntegrationOption.isAvailable) return; setSelectedIntegrationOption(cloudIntegrationOption); @@ -95,12 +95,12 @@ const CloudIntegration = ({ integrationAuth: deletedIntegrationAuth }); }} - className="flex w-max cursor-pointer flex-row items-center rounded-b-md bg-red py-0.5 px-2 text-xs opacity-0 duration-200 group-hover:opacity-100" + className="flex w-max cursor-pointer flex-row items-center rounded-bl-md bg-red py-0.5 px-2 text-xs opacity-30 duration-200 group-hover:opacity-100" > - + Revoke -
+
Authorized
diff --git a/frontend/src/components/integrations/FrameworkIntegration.tsx b/frontend/src/components/integrations/FrameworkIntegration.tsx index 8a494059a1..56441c3d28 100644 --- a/frontend/src/components/integrations/FrameworkIntegration.tsx +++ b/frontend/src/components/integrations/FrameworkIntegration.tsx @@ -12,10 +12,10 @@ const FrameworkIntegration = ({ framework }: { framework: Framework }) => ( href={framework.docsLink} rel="noopener noreferrer" target="_blank" - className="relative flex flex-row justify-center bg-bunker-500 hover:bg-gradient-to-tr duration-200 h-32 rounded-md p-0.5 items-center cursor-pointer" + className="relative flex flex-row justify-center duration-200 h-32 rounded-md p-0.5 items-center cursor-pointer" >
1 ? 'text-sm px-1' : 'text-xl px-2' } text-center w-full max-w-xs`} > diff --git a/frontend/src/components/integrations/Integration.tsx b/frontend/src/components/integrations/Integration.tsx index 1597df84b4..d081e1a9a9 100644 --- a/frontend/src/components/integrations/Integration.tsx +++ b/frontend/src/components/integrations/Integration.tsx @@ -1,7 +1,7 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ import { useEffect, useState } from 'react'; import { useRouter } from 'next/router'; -import { faArrowRight, faRotate, faX } from '@fortawesome/free-solid-svg-icons'; +import { faArrowRight, faCheck, faXmark } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; // TODO: This needs to be moved from public folder import { contextNetlifyMapping, integrationSlugNameMapping, reverseContextNetlifyMapping } from 'public/data/frequentConstants'; @@ -212,10 +212,10 @@ const IntegrationTile = ({ return
; }; - if (!integrationApp) return
; + if (!integrationApp && integration.integration !== "checkly") return
; return ( -
+

ENVIRONMENT

@@ -238,27 +238,27 @@ const IntegrationTile = ({

INTEGRATION

-
+
{/* {integration.integration.charAt(0).toUpperCase() + integration.integration.slice(1)} */} {integrationSlugNameMapping[integration.integration]}
APP
- app.name) : null} isSelected={integrationApp} onChange={(app) => { setIntegrationApp(app); }} - /> + />
:
-
}
{renderIntegrationSpecificParams(integration)}
-
+
{integration.isActive ? ( -
- +
+
In Sync
) : ( @@ -269,7 +269,7 @@ const IntegrationTile = ({ size="md" /> )} -
+
diff --git a/frontend/src/components/integrations/IntegrationSection.tsx b/frontend/src/components/integrations/IntegrationSection.tsx index 5d3c88d2bd..9b8799b6a5 100644 --- a/frontend/src/components/integrations/IntegrationSection.tsx +++ b/frontend/src/components/integrations/IntegrationSection.tsx @@ -37,7 +37,7 @@ const ProjectIntegrationSection = ({

Current Integrations

-

Manage integrations with third-party services.

+

Manage integrations with third-party services.

{integrations.map((integration: Integration) => { return ( diff --git a/frontend/src/components/v2/Select/Select.tsx b/frontend/src/components/v2/Select/Select.tsx index 17e6060503..9d2ab02e35 100644 --- a/frontend/src/components/v2/Select/Select.tsx +++ b/frontend/src/components/v2/Select/Select.tsx @@ -40,7 +40,7 @@ export const Select = forwardRef( ref={ref} className={twMerge( `inline-flex items-center justify-between rounded-md - bg-bunker-800 px-3 py-2 font-inter text-sm font-normal text-bunker-200 outline-none data-[placeholder]:text-gray-500`, + bg-mineshaft-900 px-3 py-2 font-inter text-sm font-normal text-bunker-200 outline-none data-[placeholder]:text-gray-500`, className )} > @@ -56,7 +56,7 @@ export const Select = forwardRef( +
{t('common.head-title', { title: t('integrations.title') })} @@ -392,7 +398,7 @@ export default function Integrations() { -
+
{ + try { + setAccessTokenErrorText(''); + if (accessToken.length === 0) { + setAccessTokenErrorText('Access token cannot be blank'); + return; + } + + setIsLoading(true); + + const integrationAuth = await saveIntegrationAccessToken({ + workspaceId: localStorage.getItem('projectData.id'), + integration: 'checkly', + accessId: null, + accessToken + }); + + setIsLoading(false); + + router.push(`/integrations/checkly/create?integrationAuthId=${integrationAuth._id}`); + } catch (err) { + console.error(err); + } + }; + + return ( +
+ + Checkly Integration + + setAccessToken(e.target.value)} + /> + + + +
+ ); +} + +ChecklyCreateIntegrationPage.requireAuth = true; diff --git a/frontend/src/pages/integrations/checkly/create.tsx b/frontend/src/pages/integrations/checkly/create.tsx new file mode 100644 index 0000000000..c76bb87b03 --- /dev/null +++ b/frontend/src/pages/integrations/checkly/create.tsx @@ -0,0 +1,141 @@ +import { useEffect, useState } from 'react'; +import { useRouter } from 'next/router'; +import queryString from 'query-string'; + +import { Button, Card, CardTitle, FormControl, Select, SelectItem } from '../../../components/v2'; +import { + useGetIntegrationAuthApps, + useGetIntegrationAuthById +} from '../../../hooks/api/integrationAuth'; +import { useGetWorkspaceById } from '../../../hooks/api/workspace'; +import createIntegration from '../../api/integrations/createIntegration'; + +export default function ChecklyCreateIntegrationPage() { + const router = useRouter(); + + const { integrationAuthId } = queryString.parse(router.asPath.split('?')[1]); + + const { data: workspace } = useGetWorkspaceById(localStorage.getItem('projectData.id') ?? ''); + const { data: integrationAuth } = useGetIntegrationAuthById((integrationAuthId as string) ?? ''); + const { data: integrationAuthApps } = useGetIntegrationAuthApps({ + integrationAuthId: (integrationAuthId as string) ?? '' + }); + + const [selectedSourceEnvironment, setSelectedSourceEnvironment] = useState(''); + const [targetApp, setTargetApp] = useState(''); + const [targetAppId, setTargetAppId] = useState(''); + + const [isLoading, setIsLoading] = useState(false); + + useEffect(() => { + if (workspace) { + setSelectedSourceEnvironment(workspace.environments[0].slug); + } + }, [workspace]); + + useEffect(() => { + // TODO: handle case where apps can be empty + if (integrationAuthApps) { + if (integrationAuthApps.length > 0) { + setTargetApp(integrationAuthApps[0].name); + setTargetAppId(String(integrationAuthApps[0].appId)); + } else { + setTargetApp('none'); + } + } + }, [integrationAuthApps]); + + const handleButtonClick = async () => { + try { + if (!integrationAuth?._id) return; + + setIsLoading(true); + + await createIntegration({ + integrationAuthId: integrationAuth?._id, + isActive: true, + app: targetApp, + appId: targetAppId, + sourceEnvironment: selectedSourceEnvironment, + targetEnvironment: null, + targetEnvironmentId: null, + targetService: null, + targetServiceId: null, + owner: null, + path: null, + region: null + }); + + setIsLoading(false); + + router.push(`/integrations/${localStorage.getItem('projectData.id')}`); + } catch (err) { + console.error(err); + } + }; + + return integrationAuth && + workspace && + selectedSourceEnvironment && + integrationAuthApps && + targetApp ? ( +
+ + Checkly Integration + + + + + + + + +
+ ) : ( +
+ ); +} + +ChecklyCreateIntegrationPage.requireAuth = true; From a3fca200fca1f3637c7099fc0a73da3d351419b3 Mon Sep 17 00:00:00 2001 From: Vladyslav Matsiiako Date: Wed, 7 Jun 2023 13:12:21 -0700 Subject: [PATCH 2/3] comment fixes --- backend/src/integrations/apps.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/src/integrations/apps.ts b/backend/src/integrations/apps.ts index 334ffdb97e..f8ed772775 100644 --- a/backend/src/integrations/apps.ts +++ b/backend/src/integrations/apps.ts @@ -611,9 +611,9 @@ const getAppsSupabase = async ({ accessToken }: { accessToken: string }) => { /** * Return list of projects for the Checkly integration * @param {Object} obj - * @param {String} obj.accessToken - access token for Supabase API - * @returns {Object[]} apps - names of Supabase apps - * @returns {String} apps.name - name of Supabase app + * @param {String} obj.accessToken - api key for the Checkly API + * @returns {Object[]} apps - Сheckly accounts + * @returns {String} apps.name - name of Checkly account */ const getAppsCheckly = async ({ accessToken }: { accessToken: string }) => { const { data } = await standardRequest.get( From 49b3e8b538b6b8fe7bba6e050c21f5da59fde5b4 Mon Sep 17 00:00:00 2001 From: Vladyslav Matsiiako Date: Wed, 7 Jun 2023 13:12:58 -0700 Subject: [PATCH 3/3] comment fixes --- backend/src/integrations/sync.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/backend/src/integrations/sync.ts b/backend/src/integrations/sync.ts index a35e4567fe..025f6400f0 100644 --- a/backend/src/integrations/sync.ts +++ b/backend/src/integrations/sync.ts @@ -1830,7 +1830,6 @@ const syncSecretsCheckly = async ({ } } } catch (err) { - console.log(err) Sentry.setUser(null); Sentry.captureException(err); throw new Error("Failed to sync secrets to Checkly");