From 9ffb4406391db7df0324b7cca047a5a4b4506ae4 Mon Sep 17 00:00:00 2001 From: Victor Santos Date: Wed, 26 Nov 2025 16:52:12 -0300 Subject: [PATCH] feat: integrate telemetry service for secret synchronization events and update project role permissions to reflect workspace integrations --- backend/src/server/routes/index.ts | 3 +- backend/src/services/secret/secret-queue.ts | 29 ++++++++- docs/docs.json | 1 - docs/integrations/cicd/checkly.mdx | 45 -------------- .../components/PolicySelectionModal.tsx | 13 +++- .../components/RolePermissionsSection.tsx | 13 +++- .../IntegrationsListPage.tsx | 61 +++++++++---------- 7 files changed, 84 insertions(+), 81 deletions(-) delete mode 100644 docs/integrations/cicd/checkly.mdx diff --git a/backend/src/server/routes/index.ts b/backend/src/server/routes/index.ts index 00771168cf..0d79999578 100644 --- a/backend/src/server/routes/index.ts +++ b/backend/src/server/routes/index.ts @@ -1329,7 +1329,8 @@ export const registerRoutes = async ( eventBusService, licenseService, membershipRoleDAL, - membershipUserDAL + membershipUserDAL, + telemetryService }); const projectService = projectServiceFactory({ diff --git a/backend/src/services/secret/secret-queue.ts b/backend/src/services/secret/secret-queue.ts index eefa0764f3..5246aa8d1f 100644 --- a/backend/src/services/secret/secret-queue.ts +++ b/backend/src/services/secret/secret-queue.ts @@ -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; eventBusService: TEventBusService; licenseService: Pick; + telemetryService: Pick; }; 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({ diff --git a/docs/docs.json b/docs/docs.json index 27d93b2c86..68ac594fbd 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -574,7 +574,6 @@ "pages": [ "integrations/cicd/aws-amplify", "integrations/cicd/bitbucket", - "integrations/cicd/checkly", "integrations/cicd/githubactions", "integrations/cicd/gitlab", "integrations/cicd/jenkins" diff --git a/docs/integrations/cicd/checkly.mdx b/docs/integrations/cicd/checkly.mdx deleted file mode 100644 index 00ec38d2f1..0000000000 --- a/docs/integrations/cicd/checkly.mdx +++ /dev/null @@ -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) - - - - 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) - - - - 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) - - - 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. - - - ![integrations checkly](../../images/integrations/checkly/integrations-checkly.png) - - - 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. - - - \ No newline at end of file diff --git a/frontend/src/pages/project/RoleDetailsBySlugPage/components/PolicySelectionModal.tsx b/frontend/src/pages/project/RoleDetailsBySlugPage/components/PolicySelectionModal.tsx index 63853ad511..3505722bc1 100644 --- a/frontend/src/pages/project/RoleDetailsBySlugPage/components/PolicySelectionModal.tsx +++ b/frontend/src/pages/project/RoleDetailsBySlugPage/components/PolicySelectionModal.tsx @@ -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 }; const Content = ({ onClose, type: projectType }: ContentProps) => { const rootForm = useFormContext(); 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); diff --git a/frontend/src/pages/project/RoleDetailsBySlugPage/components/RolePermissionsSection.tsx b/frontend/src/pages/project/RoleDetailsBySlugPage/components/RolePermissionsSection.tsx index c93cea5346..f50b39a920 100644 --- a/frontend/src/pages/project/RoleDetailsBySlugPage/components/RolePermissionsSection.tsx +++ b/frontend/src/pages/project/RoleDetailsBySlugPage/components/RolePermissionsSection.tsx @@ -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(null); @@ -198,6 +204,11 @@ export const RolePermissionsSection = ({ roleSlug, isDisabled }: Props) => { {!isPending && } {(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) => ( { {hasNativeIntegrations && ( - - - We're moving Native Integrations to{" "} - - Secret Syncs - - . If the integration you need isn't available in the Secret Syncs menu, - please get in touch with us at{" "} - - team@infisical.com - - . - - +
+
+ +
+

+ We're moving Native Integrations to{" "} + + Secret Syncs + + . If the integration you need isn't available in the Secret Syncs menu, + please get in touch with us at{" "} + + team@infisical.com + + . +

+
+
+