feat: integrate telemetry service for secret synchronization events and update project role permissions to reflect workspace integrations

This commit is contained in:
Victor Santos
2025-11-26 16:52:12 -03:00
parent f98b803e76
commit 9ffb440639
7 changed files with 84 additions and 81 deletions

View File

@@ -1329,7 +1329,8 @@ export const registerRoutes = async (
eventBusService,
licenseService,
membershipRoleDAL,
membershipUserDAL
membershipUserDAL,
telemetryService
});
const projectService = projectServiceFactory({

View File

@@ -64,6 +64,8 @@ import { expandSecretReferencesFactory, getAllSecretReferences } from "../secret
import { TSecretVersionV2DALFactory } from "../secret-v2-bridge/secret-version-dal";
import { TSecretVersionV2TagDALFactory } from "../secret-v2-bridge/secret-version-tag-dal";
import { SmtpTemplates, TSmtpService } from "../smtp/smtp-service";
import { TTelemetryServiceFactory } from "../telemetry/telemetry-service";
import { PostHogEventTypes } from "../telemetry/telemetry-types";
import { TUserDALFactory } from "../user/user-dal";
import { TWebhookDALFactory } from "../webhook/webhook-dal";
import { fnTriggerWebhook } from "../webhook/webhook-fns";
@@ -120,6 +122,7 @@ type TSecretQueueFactoryDep = {
reminderService: Pick<TReminderServiceFactory, "createReminderInternal" | "deleteReminderBySecretId">;
eventBusService: TEventBusService;
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
telemetryService: Pick<TTelemetryServiceFactory, "sendPostHogEvents">;
};
export type TGetSecrets = {
@@ -184,7 +187,8 @@ export const secretQueueFactory = ({
eventBusService,
licenseService,
membershipUserDAL,
membershipRoleDAL
membershipRoleDAL,
telemetryService
}: TSecretQueueFactoryDep) => {
const integrationMeter = opentelemetry.metrics.getMeter("Integrations");
const errorHistogram = integrationMeter.createHistogram("integration_secret_sync_errors", {
@@ -1029,6 +1033,29 @@ export const secretQueueFactory = ({
isSynced: response?.isSynced ?? true
});
await telemetryService.sendPostHogEvents({
event: PostHogEventTypes.IntegrationSynced,
distinctId: `project/${projectId}`,
organizationId: project.orgId,
properties: {
integrationId: integration.id,
integration: integration.integration,
environment,
secretPath,
projectId,
url: integration.url ?? undefined,
app: integration.app ?? undefined,
appId: integration.appId ?? undefined,
targetEnvironment: integration.targetEnvironment ?? undefined,
targetEnvironmentId: integration.targetEnvironmentId ?? undefined,
targetService: integration.targetService ?? undefined,
targetServiceId: integration.targetServiceId ?? undefined,
path: integration.path ?? undefined,
region: integration.region ?? undefined,
isManualSync: isManual ?? false
}
});
// May be undefined, if it's undefined we assume the sync was successful, hence the strict equality type check.
if (response?.isSynced === false) {
integrationsFailedToSync.push({

View File

@@ -574,7 +574,6 @@
"pages": [
"integrations/cicd/aws-amplify",
"integrations/cicd/bitbucket",
"integrations/cicd/checkly",
"integrations/cicd/githubactions",
"integrations/cicd/gitlab",
"integrations/cicd/jenkins"

View File

@@ -1,45 +0,0 @@
---
title: "Checkly"
description: "How to sync secrets from Infisical to Checkly"
---
Prerequisites:
- Set up and add envars to [Infisical Cloud](https://app.infisical.com)
<Steps>
<Step title="Authorize Infisical for Checkly">
Obtain a Checkly API Key in User Settings > API Keys.
![integrations checkly dashboard](../../images/integrations/checkly/integrations-checkly-dashboard.png)
![integrations checkly token](../../images/integrations/checkly/integrations-checkly-token.png)
Navigate to your project's integrations tab in Infisical.
![integrations](../../images/integrations.png)
Press on the Checkly tile and input your Checkly API Key to grant Infisical access to your Checkly account.
![integrations checkly authorization](../../images/integrations/checkly/integrations-checkly-auth.png)
</Step>
<Step title="Start integration">
Select which Infisical environment secrets you want to sync to Checkly and press create integration to start syncing secrets.
![integrations checkly](../../images/integrations/checkly/integrations-checkly-create.png)
<Note>
Infisical integrates with Checkly's environment variables at the **global** and **group** levels.
To sync secrets to a specific group, you can select a group from the Checkly Group dropdown; otherwise, leaving it empty will sync secrets globally.
</Note>
![integrations checkly](../../images/integrations/checkly/integrations-checkly.png)
<Info>
In the new version of the Checkly integration, you are able to specify suffixes that depend on the secrets' environment and path.
If you choose to do so, you should utilize such suffixes for ALL Checkly integrations  otherwise the integration system
might run into issues with deleting secrets from the wrong environments.
</Info>
</Step>
</Steps>

View File

@@ -18,7 +18,8 @@ import {
Tooltip,
Tr
} from "@app/components/v2";
import { ProjectPermissionSub } from "@app/context";
import { ProjectPermissionSub, useProject } from "@app/context";
import { useGetWorkspaceIntegrations } from "@app/hooks/api";
import { ProjectType } from "@app/hooks/api/projects/types";
import {
@@ -46,6 +47,9 @@ type TForm = { permissions: Record<ProjectPermissionSub, boolean> };
const Content = ({ onClose, type: projectType }: ContentProps) => {
const rootForm = useFormContext<TFormSchema>();
const [search, setSearch] = useState("");
const { currentProject } = useProject();
const { data: integrations = [] } = useGetWorkspaceIntegrations(currentProject?.id ?? "");
const {
control,
handleSubmit,
@@ -60,6 +64,8 @@ const Content = ({ onClose, type: projectType }: ContentProps) => {
}
});
const hasNativeIntegrations = integrations.length > 0;
const filteredPolicies = Object.entries(PROJECT_PERMISSION_OBJECT)
.filter(
([subject, { title }]) =>
@@ -68,6 +74,11 @@ const Content = ({ onClose, type: projectType }: ContentProps) => {
] && (search ? title.toLowerCase().includes(search.toLowerCase()) : true)
)
.filter(([subject]) => !EXCLUDED_PERMISSION_SUBS.includes(subject as ProjectPermissionSub))
.filter(
([subject]) =>
// Hide Native Integrations policy if project has no integrations
subject !== ProjectPermissionSub.Integrations || hasNativeIntegrations
)
.sort((a, b) => a[1].title.localeCompare(b[1].title))
.map(([subject]) => subject);

View File

@@ -11,7 +11,11 @@ import { Button } from "@app/components/v2";
import { ProjectPermissionSub, useProject } from "@app/context";
import { ProjectPermissionSet } from "@app/context/ProjectPermissionContext";
import { evaluatePermissionsAbility } from "@app/helpers/permissions";
import { useGetProjectRoleBySlug, useUpdateProjectRole } from "@app/hooks/api";
import {
useGetProjectRoleBySlug,
useGetWorkspaceIntegrations,
useUpdateProjectRole
} from "@app/hooks/api";
import { ProjectType } from "@app/hooks/api/projects/types";
import { ProjectMembershipRole } from "@app/hooks/api/roles/types";
@@ -105,6 +109,8 @@ export const RolePermissionsSection = ({ roleSlug, isDisabled }: Props) => {
currentProject?.id ?? "",
roleSlug as string
);
const { data: integrations = [] } = useGetWorkspaceIntegrations(projectId);
const hasNativeIntegrations = integrations.length > 0;
const [showAccessTree, setShowAccessTree] = useState<ProjectPermissionSub | null>(null);
@@ -198,6 +204,11 @@ export const RolePermissionsSection = ({ roleSlug, isDisabled }: Props) => {
{!isPending && <PermissionEmptyState />}
{(Object.keys(PROJECT_PERMISSION_OBJECT) as ProjectPermissionSub[])
.filter((subject) => !EXCLUDED_PERMISSION_SUBS.includes(subject))
.filter(
(subject) =>
// Hide Native Integrations policy if project has no integrations
subject !== ProjectPermissionSub.Integrations || hasNativeIntegrations
)
.map((subject) => (
<GeneralPermissionPolicies
subject={subject}

View File

@@ -1,17 +1,11 @@
import { Helmet } from "react-helmet";
import { useTranslation } from "react-i18next";
import { faWarning } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useNavigate, useSearch } from "@tanstack/react-router";
import { ProjectPermissionCan } from "@app/components/permissions";
import {
Alert,
AlertDescription,
PageHeader,
Tab,
TabList,
TabPanel,
Tabs
} from "@app/components/v2";
import { PageHeader, Tab, TabList, TabPanel, Tabs } from "@app/components/v2";
import { ROUTE_PATHS } from "@app/const/routes";
import {
ProjectPermissionActions,
@@ -106,28 +100,33 @@ export const IntegrationsListPage = () => {
</TabPanel>
{hasNativeIntegrations && (
<TabPanel value={IntegrationsListPageTabs.NativeIntegrations}>
<Alert variant="warning" className="mb-4" hideTitle>
<AlertDescription>
We&apos;re moving Native Integrations to{" "}
<a
href="https://infisical.com/docs/integrations/secret-syncs/overview"
target="_blank"
rel="noopener noreferrer"
className="underline underline-offset-2 hover:text-mineshaft-100"
>
Secret Syncs
</a>
. If the integration you need isn&apos;t available in the Secret Syncs menu,
please get in touch with us at{" "}
<a
href="mailto:team@infisical.com"
className="underline underline-offset-2 hover:text-mineshaft-100"
>
team@infisical.com
</a>
.
</AlertDescription>
</Alert>
<div className="mb-4 flex items-start rounded-md border border-yellow-600 bg-yellow-900/20 px-3 py-2">
<div className="flex text-sm text-yellow-100">
<FontAwesomeIcon icon={faWarning} className="mt-1 mr-2 text-yellow-600" />
<div>
<p>
We&apos;re moving Native Integrations to{" "}
<a
href="https://infisical.com/docs/integrations/secret-syncs/overview"
target="_blank"
rel="noopener noreferrer"
className="underline underline-offset-2 hover:text-mineshaft-100"
>
Secret Syncs
</a>
. If the integration you need isn&apos;t available in the Secret Syncs menu,
please get in touch with us at{" "}
<a
href="mailto:team@infisical.com"
className="underline underline-offset-2 hover:text-mineshaft-100"
>
team@infisical.com
</a>
.
</p>
</div>
</div>
</div>
<ProjectPermissionCan
renderGuardBanner
I={ProjectPermissionActions.Read}