From c9be7f53aa5e2cfa8c88fb027e7d80b6c0bd19c3 Mon Sep 17 00:00:00 2001 From: Michael Matloka Date: Wed, 6 May 2026 21:02:04 +0200 Subject: [PATCH 1/3] feat(code): set posthog-js project + organization groups on identify Mirrors how the main PostHog frontend (frontend/src/scenes/userLogic.ts) attaches the user to project and organization groups so feature flags targeting those group types evaluate correctly. Without this, flags like inbox-gated-due-to-scale rolled out to specific organizations never match in PostHog Code. Generated-By: PostHog Code Task-Id: fe3aec7b-02ed-418f-8476-0298557dba9a --- .../features/auth/hooks/useAuthSession.ts | 18 +++++++++++++++++- .../renderer/features/auth/stores/authStore.ts | 18 +++++++++++++++++- apps/code/src/renderer/utils/analytics.ts | 14 ++++++++++++++ 3 files changed, 48 insertions(+), 2 deletions(-) diff --git a/apps/code/src/renderer/features/auth/hooks/useAuthSession.ts b/apps/code/src/renderer/features/auth/hooks/useAuthSession.ts index a8ed01f4c..a0e96f2e5 100644 --- a/apps/code/src/renderer/features/auth/hooks/useAuthSession.ts +++ b/apps/code/src/renderer/features/auth/hooks/useAuthSession.ts @@ -12,7 +12,7 @@ import { useSeatStore } from "@features/billing/stores/seatStore"; import { useFeatureFlag } from "@hooks/useFeatureFlag"; import { trpcClient } from "@renderer/trpc/client"; import { BILLING_FLAG } from "@shared/constants"; -import { identifyUser, resetUser } from "@utils/analytics"; +import { identifyUser, resetUser, setUserGroup } from "@utils/analytics"; import { logger } from "@utils/logger"; import { queryClient } from "@utils/queryClient"; import { useEffect } from "react"; @@ -72,6 +72,22 @@ function useAuthAnalyticsIdentity( region: authState.cloudRegion ?? "", }); + if (currentUser.team) { + setUserGroup("project", currentUser.team.uuid, { + id: currentUser.team.id, + uuid: currentUser.team.uuid, + name: currentUser.team.name, + }); + } + + if (currentUser.organization) { + setUserGroup("organization", currentUser.organization.id, { + id: currentUser.organization.id, + name: currentUser.organization.name, + slug: currentUser.organization.slug, + }); + } + void trpcClient.analytics.setUserId.mutate({ userId: distinctId, properties: { diff --git a/apps/code/src/renderer/features/auth/stores/authStore.ts b/apps/code/src/renderer/features/auth/stores/authStore.ts index 2373c9778..04f70fb2b 100644 --- a/apps/code/src/renderer/features/auth/stores/authStore.ts +++ b/apps/code/src/renderer/features/auth/stores/authStore.ts @@ -6,7 +6,7 @@ import { ANALYTICS_EVENTS } from "@shared/types/analytics"; import type { CloudRegion } from "@shared/types/regions"; import { getCloudUrlFromRegion } from "@shared/utils/urls"; import { useNavigationStore } from "@stores/navigationStore"; -import { identifyUser, resetUser, track } from "@utils/analytics"; +import { identifyUser, resetUser, setUserGroup, track } from "@utils/analytics"; import { logger } from "@utils/logger"; import { queryClient } from "@utils/queryClient"; import { create } from "zustand"; @@ -165,6 +165,22 @@ async function syncAuthState(): Promise { region: authState.cloudRegion ?? "", }); + if (user.team) { + setUserGroup("project", user.team.uuid, { + id: user.team.id, + uuid: user.team.uuid, + name: user.team.name, + }); + } + + if (user.organization) { + setUserGroup("organization", user.organization.id, { + id: user.organization.id, + name: user.organization.name, + slug: user.organization.slug, + }); + } + trpcClient.analytics.setUserId.mutate({ userId: distinctId, properties: { diff --git a/apps/code/src/renderer/utils/analytics.ts b/apps/code/src/renderer/utils/analytics.ts index 23e9a971b..5c9f461e1 100644 --- a/apps/code/src/renderer/utils/analytics.ts +++ b/apps/code/src/renderer/utils/analytics.ts @@ -100,6 +100,20 @@ export function identifyUser( posthog.identify(userId, properties); } +type GroupProperties = Record; + +export function setUserGroup( + groupType: "project" | "organization", + groupKey: string, + properties?: GroupProperties, +) { + if (!isInitialized) { + return; + } + + posthog.group(groupType, groupKey, properties); +} + export function resetUser() { if (!isInitialized) { return; From f2e8c3bd339947fd914370e4a9fc8145201fa928 Mon Sep 17 00:00:00 2001 From: Michael Matloka Date: Wed, 6 May 2026 21:08:27 +0200 Subject: [PATCH 2/3] refactor(code): extract setUserGroups helper to remove duplication Generated-By: PostHog Code Task-Id: fe3aec7b-02ed-418f-8476-0298557dba9a --- .../features/auth/hooks/useAuthSession.ts | 18 ++----------- .../features/auth/stores/authStore.ts | 23 +++++----------- apps/code/src/renderer/utils/analytics.ts | 27 ++++++++++++++----- 3 files changed, 29 insertions(+), 39 deletions(-) diff --git a/apps/code/src/renderer/features/auth/hooks/useAuthSession.ts b/apps/code/src/renderer/features/auth/hooks/useAuthSession.ts index a0e96f2e5..f3b946ce9 100644 --- a/apps/code/src/renderer/features/auth/hooks/useAuthSession.ts +++ b/apps/code/src/renderer/features/auth/hooks/useAuthSession.ts @@ -12,7 +12,7 @@ import { useSeatStore } from "@features/billing/stores/seatStore"; import { useFeatureFlag } from "@hooks/useFeatureFlag"; import { trpcClient } from "@renderer/trpc/client"; import { BILLING_FLAG } from "@shared/constants"; -import { identifyUser, resetUser, setUserGroup } from "@utils/analytics"; +import { identifyUser, resetUser, setUserGroups } from "@utils/analytics"; import { logger } from "@utils/logger"; import { queryClient } from "@utils/queryClient"; import { useEffect } from "react"; @@ -72,21 +72,7 @@ function useAuthAnalyticsIdentity( region: authState.cloudRegion ?? "", }); - if (currentUser.team) { - setUserGroup("project", currentUser.team.uuid, { - id: currentUser.team.id, - uuid: currentUser.team.uuid, - name: currentUser.team.name, - }); - } - - if (currentUser.organization) { - setUserGroup("organization", currentUser.organization.id, { - id: currentUser.organization.id, - name: currentUser.organization.name, - slug: currentUser.organization.slug, - }); - } + setUserGroups(currentUser); void trpcClient.analytics.setUserId.mutate({ userId: distinctId, diff --git a/apps/code/src/renderer/features/auth/stores/authStore.ts b/apps/code/src/renderer/features/auth/stores/authStore.ts index 04f70fb2b..8de660445 100644 --- a/apps/code/src/renderer/features/auth/stores/authStore.ts +++ b/apps/code/src/renderer/features/auth/stores/authStore.ts @@ -6,7 +6,12 @@ import { ANALYTICS_EVENTS } from "@shared/types/analytics"; import type { CloudRegion } from "@shared/types/regions"; import { getCloudUrlFromRegion } from "@shared/utils/urls"; import { useNavigationStore } from "@stores/navigationStore"; -import { identifyUser, resetUser, setUserGroup, track } from "@utils/analytics"; +import { + identifyUser, + resetUser, + setUserGroups, + track, +} from "@utils/analytics"; import { logger } from "@utils/logger"; import { queryClient } from "@utils/queryClient"; import { create } from "zustand"; @@ -165,21 +170,7 @@ async function syncAuthState(): Promise { region: authState.cloudRegion ?? "", }); - if (user.team) { - setUserGroup("project", user.team.uuid, { - id: user.team.id, - uuid: user.team.uuid, - name: user.team.name, - }); - } - - if (user.organization) { - setUserGroup("organization", user.organization.id, { - id: user.organization.id, - name: user.organization.name, - slug: user.organization.slug, - }); - } + setUserGroups(user); trpcClient.analytics.setUserId.mutate({ userId: distinctId, diff --git a/apps/code/src/renderer/utils/analytics.ts b/apps/code/src/renderer/utils/analytics.ts index 5c9f461e1..3e106d6a5 100644 --- a/apps/code/src/renderer/utils/analytics.ts +++ b/apps/code/src/renderer/utils/analytics.ts @@ -100,18 +100,31 @@ export function identifyUser( posthog.identify(userId, properties); } -type GroupProperties = Record; +type UserWithGroups = { + team?: { id: number; uuid: string; name: string } | null; + organization?: { id: string; name: string; slug: string } | null; +}; -export function setUserGroup( - groupType: "project" | "organization", - groupKey: string, - properties?: GroupProperties, -) { +export function setUserGroups(user: UserWithGroups) { if (!isInitialized) { return; } - posthog.group(groupType, groupKey, properties); + if (user.team) { + posthog.group("project", user.team.uuid, { + id: user.team.id, + uuid: user.team.uuid, + name: user.team.name, + }); + } + + if (user.organization) { + posthog.group("organization", user.organization.id, { + id: user.organization.id, + name: user.organization.name, + slug: user.organization.slug, + }); + } } export function resetUser() { From 9dbecb58230a79586cc3213045e2618f3e90db2d Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 6 May 2026 23:06:23 +0000 Subject: [PATCH 3/3] test(code): mock analytics groups in auth store test Co-authored-by: Michael Matloka --- apps/code/src/renderer/features/auth/stores/authStore.test.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/code/src/renderer/features/auth/stores/authStore.test.ts b/apps/code/src/renderer/features/auth/stores/authStore.test.ts index a160efb19..f5d0ec951 100644 --- a/apps/code/src/renderer/features/auth/stores/authStore.test.ts +++ b/apps/code/src/renderer/features/auth/stores/authStore.test.ts @@ -55,6 +55,7 @@ vi.mock("@renderer/api/posthogClient", () => ({ vi.mock("@utils/analytics", () => ({ identifyUser: vi.fn(), resetUser: vi.fn(), + setUserGroups: vi.fn(), track: vi.fn(), })); @@ -83,7 +84,7 @@ vi.mock("@stores/navigationStore", () => ({ }, })); -import { resetUser } from "@utils/analytics"; +import { resetUser, setUserGroups } from "@utils/analytics"; import { queryClient } from "@utils/queryClient"; import { resetAuthStoreModuleStateForTest, useAuthStore } from "./authStore"; @@ -166,6 +167,7 @@ describe("authStore", () => { await useAuthStore.getState().checkCodeAccess(); expect(mockGetCurrentUser).toHaveBeenCalledTimes(1); + expect(setUserGroups).toHaveBeenCalledTimes(1); }); it("clears user identity and cached current user on implicit auth loss", async () => {