Checkly group level sync support

This commit is contained in:
Andrew Atimapre
2023-10-30 18:23:23 +01:00
parent c0592ad904
commit f3c76c79ee
9 changed files with 216 additions and 7 deletions
@@ -1,7 +1,7 @@
import { Request, Response } from "express"; import { Request, Response } from "express";
import { Types } from "mongoose"; import { Types } from "mongoose";
import { standardRequest } from "../../config/request"; import { standardRequest } from "../../config/request";
import { getApps, getTeams, revokeAccess } from "../../integrations"; import { getApps, getTeams, getGroups, revokeAccess } from "../../integrations";
import { Bot, IntegrationAuth, Workspace } from "../../models"; import { Bot, IntegrationAuth, Workspace } from "../../models";
import { EventType } from "../../ee/models"; import { EventType } from "../../ee/models";
import { IntegrationService } from "../../services"; import { IntegrationService } from "../../services";
@@ -208,6 +208,40 @@ export const saveIntegrationToken = async (req: Request, res: Response) => {
}); });
}; };
/**
* Return list of groups allowed for integration with integration authorization id [integrationAuthId]
* @param req
* @param res
* @returns
*/
export const getIntegrationAuthGroups = async (req: Request, res: Response) => {
const {
params: { integrationAuthId }
} = await validateRequest(reqValidator.GetIntegrationAuthGroupsV1, req);
const { integrationAuth, accessToken } = await getIntegrationAuthAccessHelper({
integrationAuthId: new ObjectId(integrationAuthId)
});
const { permission } = await getUserProjectPermissions(
req.user._id,
integrationAuth.workspace.toString()
);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.Integrations
);
const groups = await getGroups({
integrationAuth: integrationAuth,
accessToken: accessToken
});
return res.status(200).send({
groups
});
};
/** /**
* Return list of applications allowed for integration with integration authorization id [integrationAuthId] * Return list of applications allowed for integration with integration authorization id [integrationAuthId]
* @param req * @param req
+96
View File
@@ -0,0 +1,96 @@
import {
IIntegrationAuth,
} from "../models";
import {
INTEGRATION_CHECKLY,
INTEGRATION_CHECKLY_API_URL,
} from "../variables";
import { standardRequest } from "../config/request";
interface Group {
name: string;
groupId: string;
}
/**
* Return list of groups for checkly integration authorization [integrationAuth]
* @param {Object} obj
* @param {String} obj.integrationAuth - integration authorization to get groups
* @param {String} obj.accessToken - access token for integration authorization
* @returns {Object[]} groups - groups for integration authorization
* @returns {String} groups.name - name of group
* @returns {String} groups.groupId - id of group
*/
const getGroups = async ({
integrationAuth,
accessToken,
}: {
integrationAuth: IIntegrationAuth;
accessToken: string;
}) => {
let groups: Group[] = [];
switch (integrationAuth.integration) {
case INTEGRATION_CHECKLY:
groups = await getGroupsCheckly({
accessToken,
});
break;
}
return groups;
}
/**
* Return list of groups for Checkly integration
* @param {Object} obj
* @param {String} obj.accessToken - access token for Checkly API
* @returns {Object[]} groups - list of groups in Checkly
* @returns {String} groups.name - name of group
* @returns {String} groups.groupId - id of group
*/
const getGroupsCheckly = async ({
accessToken,
}: {
accessToken: string;
}) => {
let groups: Group[] = [];
// case: fetch account id
const { data } = await standardRequest.get(`${INTEGRATION_CHECKLY_API_URL}/v1/accounts`, {
headers: {
Authorization: `Bearer ${accessToken}`,
Accept: "application/json"
}
});
const accountId = data.map((a: any) => {
return {
id: a.id,
};
});
// case: fetch list of groups in Checkly
const res = accountId.length > 0 && (
await standardRequest.get(`${INTEGRATION_CHECKLY_API_URL}/v1/check-groups`, {
headers: {
Authorization: `Bearer ${accessToken}`,
Accept: "application/json",
"X-Checkly-Account": accountId[0].id,
}
})
).data;
groups = res.map((g: any) => ({
name: g.name,
groupId: g.id,
}));
return groups;
}
export {
getGroups,
}
+2
View File
@@ -2,6 +2,7 @@ import { exchangeCode } from "./exchange";
import { exchangeRefresh } from "./refresh"; import { exchangeRefresh } from "./refresh";
import { getApps } from "./apps"; import { getApps } from "./apps";
import { getTeams } from "./teams"; import { getTeams } from "./teams";
import { getGroups } from "./groups";
import { revokeAccess } from "./revoke"; import { revokeAccess } from "./revoke";
export { export {
@@ -9,5 +10,6 @@ export {
exchangeRefresh, exchangeRefresh,
getApps, getApps,
getTeams, getTeams,
getGroups,
revokeAccess, revokeAccess,
} }
+8
View File
@@ -52,6 +52,14 @@ router.get(
integrationAuthController.getIntegrationAuthTeams integrationAuthController.getIntegrationAuthTeams
); );
router.get(
"/:integrationAuthId/groups",
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
}),
integrationAuthController.getIntegrationAuthGroups
);
router.get( router.get(
"/:integrationAuthId/vercel/branches", "/:integrationAuthId/vercel/branches",
requireAuth({ requireAuth({
@@ -117,6 +117,12 @@ export const GetIntegrationAuthVercelBranchesV1 = z.object({
}) })
}); });
export const GetIntegrationAuthGroupsV1 = z.object({
params: z.object({
integrationAuthId: z.string().trim()
})
});
export const GetIntegrationAuthQoveryOrgsV1 = z.object({ export const GetIntegrationAuthQoveryOrgsV1 = z.object({
params: z.object({ params: z.object({
integrationAuthId: z.string().trim() integrationAuthId: z.string().trim()
@@ -10,5 +10,6 @@ export {
useGetIntegrationAuthTeamCityBuildConfigs, useGetIntegrationAuthTeamCityBuildConfigs,
useGetIntegrationAuthTeams, useGetIntegrationAuthTeams,
useGetIntegrationAuthVercelBranches, useGetIntegrationAuthVercelBranches,
useSaveIntegrationAccessToken useSaveIntegrationAccessToken,
useGetIntegrationAuthGroups
} from "./queries"; } from "./queries";
@@ -12,7 +12,8 @@ import {
Org, Org,
Project, Project,
Service, Service,
Team, Team,
Group,
TeamCityBuildConfig} from "./types"; TeamCityBuildConfig} from "./types";
const integrationAuthKeys = { const integrationAuthKeys = {
@@ -22,6 +23,8 @@ const integrationAuthKeys = {
[{ integrationAuthId, teamId, workspaceSlug }, "integrationAuthApps"] as const, [{ integrationAuthId, teamId, workspaceSlug }, "integrationAuthApps"] as const,
getIntegrationAuthTeams: (integrationAuthId: string) => getIntegrationAuthTeams: (integrationAuthId: string) =>
[{ integrationAuthId }, "integrationAuthTeams"] as const, [{ integrationAuthId }, "integrationAuthTeams"] as const,
getIntegrationAuthGroups: (integrationAuthId: string) =>
[{ integrationAuthId }, "integrationAuthGroups"] as const,
getIntegrationAuthVercelBranches: ({ getIntegrationAuthVercelBranches: ({
integrationAuthId, integrationAuthId,
appId appId
@@ -125,6 +128,12 @@ const fetchIntegrationAuthTeams = async (integrationAuthId: string) => {
return data.teams; return data.teams;
}; };
const fetchIntegrationAuthGroups = async (integrationAuthId: string) => {
const { data } = await apiRequest.get<{ groups: Group[] }>(
`/api/v1/integration-auth/${integrationAuthId}/groups`
);
return data.groups;
};
const fetchIntegrationAuthVercelBranches = async ({ const fetchIntegrationAuthVercelBranches = async ({
integrationAuthId, integrationAuthId,
@@ -413,6 +422,14 @@ export const useGetIntegrationAuthVercelBranches = ({
}); });
}; };
export const useGetIntegrationAuthGroups = (integrationAuthId: string) => {
return useQuery({
queryKey: integrationAuthKeys.getIntegrationAuthGroups(integrationAuthId),
queryFn: () => fetchIntegrationAuthGroups(integrationAuthId),
enabled: true
});
};
export const useGetIntegrationAuthQoveryOrgs = (integrationAuthId: string) => { export const useGetIntegrationAuthQoveryOrgs = (integrationAuthId: string) => {
return useQuery({ return useQuery({
queryKey: integrationAuthKeys.getIntegrationAuthQoveryOrgs(integrationAuthId), queryKey: integrationAuthKeys.getIntegrationAuthQoveryOrgs(integrationAuthId),
@@ -26,6 +26,11 @@ export type Environment = {
environmentId: string; environmentId: string;
}; };
export type Group = {
name: string;
groupId: string;
};
export type Container = { export type Container = {
name: string; name: string;
containerId: string; containerId: string;
@@ -9,6 +9,8 @@ import { motion } from "framer-motion";
import queryString from "query-string"; import queryString from "query-string";
import { import {
Alert,
AlertDescription,
Button, Button,
Card, Card,
CardTitle, CardTitle,
@@ -27,7 +29,8 @@ import {
import { import {
useGetIntegrationAuthApps, useGetIntegrationAuthApps,
useGetIntegrationAuthById useGetIntegrationAuthById,
useGetIntegrationAuthGroups
} from "../../../hooks/api/integrationAuth"; } from "../../../hooks/api/integrationAuth";
import { useGetWorkspaceById } from "../../../hooks/api/workspace"; import { useGetWorkspaceById } from "../../../hooks/api/workspace";
@@ -47,6 +50,9 @@ export default function ChecklyCreateIntegrationPage() {
const { data: integrationAuthApps, isLoading: isIntegrationAuthAppsLoading } = useGetIntegrationAuthApps({ const { data: integrationAuthApps, isLoading: isIntegrationAuthAppsLoading } = useGetIntegrationAuthApps({
integrationAuthId: (integrationAuthId as string) ?? "" integrationAuthId: (integrationAuthId as string) ?? ""
}); });
const { data: integrationAuthGroups, isLoading: isintegrationAuthGroupsLoading } = useGetIntegrationAuthGroups(
(integrationAuthId as string) ?? ""
);
const [selectedSourceEnvironment, setSelectedSourceEnvironment] = useState(""); const [selectedSourceEnvironment, setSelectedSourceEnvironment] = useState("");
const [secretPath, setSecretPath] = useState("/"); const [secretPath, setSecretPath] = useState("/");
@@ -55,6 +61,9 @@ export default function ChecklyCreateIntegrationPage() {
const [targetApp, setTargetApp] = useState(""); const [targetApp, setTargetApp] = useState("");
const [targetAppId, setTargetAppId] = useState(""); const [targetAppId, setTargetAppId] = useState("");
const [targetGroup, setTargetGroup] = useState("");
const [targetGroupId, setTargetGroupId] = useState("");
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
useEffect(() => { useEffect(() => {
@@ -104,9 +113,10 @@ export default function ChecklyCreateIntegrationPage() {
return integrationAuth && return integrationAuth &&
workspace && workspace &&
selectedSourceEnvironment && selectedSourceEnvironment &&
integrationAuthApps && integrationAuthApps &&
integrationAuthGroups &&
targetApp ? ( targetApp ? (
<div className="flex flex-col h-full w-full items-center justify-center bg-gradient-to-tr from-mineshaft-900 to-bunker-900"> <div className="flex flex-col w-full py-6 items-center justify-center bg-gradient-to-tr from-mineshaft-900 to-bunker-900">
<Head> <Head>
<title>Set Up Checkly Integration</title> <title>Set Up Checkly Integration</title>
<link rel='icon' href='/infisical.ico' /> <link rel='icon' href='/infisical.ico' />
@@ -175,6 +185,36 @@ export default function ChecklyCreateIntegrationPage() {
placeholder="Provide a path, default is /" placeholder="Provide a path, default is /"
/> />
</FormControl> </FormControl>
<FormControl label="Checkly Group">
<Select
value={targetGroup}
onValueChange={(val) => setTargetGroup(val)}
className="w-full border border-mineshaft-500"
>
<SelectItem value="">
Select an option
</SelectItem>
{integrationAuthGroups.length > 0 ? (
integrationAuthGroups.map((integrationAuthGroup) => (
<SelectItem
value={integrationAuthGroup.name}
key={`target-group-${integrationAuthGroup.name}`}
>
{integrationAuthGroup.name}
</SelectItem>
))
) : (
<SelectItem value="none" key="target-group-none" disabled>
No groups found
</SelectItem>
)}
</Select>
</FormControl>
<Alert className="mb-5" hideTitle="true">
<AlertDescription>
By default environment variables are synced to the global level, select a group above to sync at the Group level.
</AlertDescription>
</Alert>
<FormControl label="Checkly Account"> <FormControl label="Checkly Account">
<Select <Select
value={targetApp} value={targetApp}
@@ -242,7 +282,7 @@ export default function ChecklyCreateIntegrationPage() {
<title>Set Up Checkly Integration</title> <title>Set Up Checkly Integration</title>
<link rel='icon' href='/infisical.ico' /> <link rel='icon' href='/infisical.ico' />
</Head> </Head>
{isIntegrationAuthAppsLoading ? <img src="/images/loading/loading.gif" height={70} width={120} alt="infisical loading indicator" /> : <div className="max-w-md h-max p-6 border border-mineshaft-600 rounded-md bg-mineshaft-800 text-mineshaft-200 flex flex-col text-center"> {isIntegrationAuthAppsLoading || isintegrationAuthGroupsLoading ? <img src="/images/loading/loading.gif" height={70} width={120} alt="infisical loading indicator" /> : <div className="max-w-md h-max p-6 border border-mineshaft-600 rounded-md bg-mineshaft-800 text-mineshaft-200 flex flex-col text-center">
<FontAwesomeIcon icon={faBugs} className="text-6xl my-2 inlineli"/> <FontAwesomeIcon icon={faBugs} className="text-6xl my-2 inlineli"/>
<p> <p>
Something went wrong. Please contact <a Something went wrong. Please contact <a