mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-05 21:26:40 +00:00
Checkly group level sync support
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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,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,
|
||||||
}
|
}
|
||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user