Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions apps/web/i18n.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2906,6 +2906,11 @@ checksums:
workspace/surveys/edit/checkbox_label: 12a07d6bdf38e283a2e95892ec49b7f8
workspace/surveys/edit/choose_the_actions_which_trigger_the_survey: 773b311a148a112243f3b139506b9987
workspace/surveys/edit/choose_the_first_question_on_your_block: bdece06ca04f89d0c445ba1554dd5b80
workspace/surveys/edit/element_category_input: c281c28cbb062bc3538cbd4a42d79cf6
workspace/surveys/edit/element_category_choice: 307101bb385166b538e9370428963461
workspace/surveys/edit/element_category_scoring: 29dbcaf797b1e9f985bbd7b5258749e4
workspace/surveys/edit/element_category_contact: 9afa39bc47019ee6dec6c74b6273967c
workspace/surveys/edit/element_category_content: 669b337031ef165af577f04f2b7fd54f
workspace/surveys/edit/choose_where_to_run_the_survey: ad87bcae97c445f1fd9ac110ea24f117
workspace/surveys/edit/city: 1831f32e1babbb29af27fac3053504a2
workspace/surveys/edit/clear_close_on_date: 673ed6940f36b02cf871ffacf034e114
Expand Down
54 changes: 50 additions & 4 deletions apps/web/lib/posthog/capture.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { beforeEach, describe, expect, test, vi } from "vitest";
import { capturePostHogEvent, groupIdentifyPostHog } from "./capture";
import { capturePostHogEvent, groupIdentifyPostHog, identifyPostHogPerson } from "./capture";

const mocks = vi.hoisted(() => ({
capture: vi.fn(),
groupIdentify: vi.fn(),
identify: vi.fn(),
loggerWarn: vi.fn(),
}));

Expand All @@ -14,7 +15,11 @@ vi.mock("@formbricks/logger", () => ({
}));

vi.mock("./server", () => ({
posthogServerClient: { capture: mocks.capture, groupIdentify: mocks.groupIdentify },
posthogServerClient: {
capture: mocks.capture,
groupIdentify: mocks.groupIdentify,
identify: mocks.identify,
},
}));

describe("capturePostHogEvent", () => {
Expand Down Expand Up @@ -136,6 +141,42 @@ describe("groupIdentifyPostHog", () => {
});
});

describe("identifyPostHogPerson", () => {
beforeEach(() => {
vi.clearAllMocks();
});

test("calls posthog identify with distinctId and properties", () => {
identifyPostHogPerson("user123", { email: "user@example.com", name: "Ada" });

expect(mocks.identify).toHaveBeenCalledWith({
distinctId: "user123",
properties: { email: "user@example.com", name: "Ada" },
});
});

test("passes undefined properties when none provided", () => {
identifyPostHogPerson("user123");

expect(mocks.identify).toHaveBeenCalledWith({
distinctId: "user123",
properties: undefined,
});
});

test("does not throw when identify throws", () => {
mocks.identify.mockImplementation(() => {
throw new Error("Network error");
});

expect(() => identifyPostHogPerson("user123", { email: "user@example.com" })).not.toThrow();
expect(mocks.loggerWarn).toHaveBeenCalledWith(
{ error: expect.any(Error) },
"Failed to identify PostHog person"
);
});
});

describe("capturePostHogEvent with null client", () => {
test("no-ops when posthogServerClient is null", async () => {
vi.clearAllMocks();
Expand All @@ -149,14 +190,19 @@ describe("capturePostHogEvent with null client", () => {
posthogServerClient: null,
}));

const { capturePostHogEvent: captureWithNullClient, groupIdentifyPostHog: identifyWithNullClient } =
await import("./capture");
const {
capturePostHogEvent: captureWithNullClient,
groupIdentifyPostHog: identifyWithNullClient,
identifyPostHogPerson: identifyPersonWithNullClient,
} = await import("./capture");

captureWithNullClient("user123", "test_event", { key: "value" });
identifyWithNullClient("organization", "org_1");
identifyPersonWithNullClient("user123", { email: "user@example.com" });

expect(mocks.capture).not.toHaveBeenCalled();
expect(mocks.groupIdentify).not.toHaveBeenCalled();
expect(mocks.identify).not.toHaveBeenCalled();
expect(mocks.loggerWarn).not.toHaveBeenCalled();
});
});
10 changes: 10 additions & 0 deletions apps/web/lib/posthog/capture.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,16 @@ export function capturePostHogEvent(
}
}

export function identifyPostHogPerson(distinctId: string, properties?: PostHogEventProperties): void {
if (!posthogServerClient) return;

try {
posthogServerClient.identify({ distinctId, properties });
} catch (error) {
logger.warn({ error }, "Failed to identify PostHog person");
}
}

type PostHogGroupType = "organization" | "workspace";

export function groupIdentifyPostHog(
Expand Down
2 changes: 1 addition & 1 deletion apps/web/lib/posthog/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import "server-only";

export { capturePostHogEvent, groupIdentifyPostHog } from "./capture";
export { capturePostHogEvent, groupIdentifyPostHog, identifyPostHogPerson } from "./capture";
export type { PostHogGroupContext } from "./capture";
export { getPostHogFeatureFlag } from "./get-feature-flag";
export type { TPostHogFeatureFlagContext, TPostHogFeatureFlagValue } from "./types";
5 changes: 5 additions & 0 deletions apps/web/locales/de-DE.json
Original file line number Diff line number Diff line change
Expand Up @@ -3074,6 +3074,11 @@
"duplicate_question": "Frage duplizieren",
"edit_link": "Bearbeitungslink",
"edit_recall": "Erinnerung bearbeiten",
"element_category_choice": "Choice",
"element_category_contact": "Contact",
"element_category_content": "Content",
"element_category_input": "Input",
"element_category_scoring": "Rating & Scoring",
"element_not_found": "Frage nicht gefunden",
"enable_participants_to_switch_the_survey_language_at_any_point_during_the_survey": "Erlaube Teilnehmenden, die Sprache jederzeit zu wechseln. Mindestens 2 aktive Sprachen erforderlich.",
"enable_recaptcha_to_protect_your_survey_from_spam": "Der Spam-Schutz nutzt reCAPTCHA v3, um Spam-Antworten herauszufiltern.",
Expand Down
5 changes: 5 additions & 0 deletions apps/web/locales/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -3074,6 +3074,11 @@
"duplicate_question": "Duplicate question",
"edit_link": "Edit link",
"edit_recall": "Edit Recall",
"element_category_choice": "Choice",
"element_category_contact": "Contact",
"element_category_content": "Content",
"element_category_input": "Input",
"element_category_scoring": "Rating & Scoring",
"element_not_found": "Question not found",
"enable_participants_to_switch_the_survey_language_at_any_point_during_the_survey": "Allow respondents to switch language at any time. Needs min. 2 active languages.",
"enable_recaptcha_to_protect_your_survey_from_spam": "Spam protection uses reCAPTCHA v3 to filter out the spam responses.",
Expand Down
5 changes: 5 additions & 0 deletions apps/web/locales/es-ES.json
Original file line number Diff line number Diff line change
Expand Up @@ -3074,6 +3074,11 @@
"duplicate_question": "Duplicar pregunta",
"edit_link": "Editar enlace",
"edit_recall": "Editar recuperación",
"element_category_choice": "Choice",
"element_category_contact": "Contact",
"element_category_content": "Content",
"element_category_input": "Input",
"element_category_scoring": "Rating & Scoring",
"element_not_found": "Pregunta no encontrada",
"enable_participants_to_switch_the_survey_language_at_any_point_during_the_survey": "Permitir a los participantes cambiar el idioma de la encuesta en cualquier momento durante la encuesta.",
"enable_recaptcha_to_protect_your_survey_from_spam": "La protección contra spam utiliza reCAPTCHA v3 para filtrar las respuestas spam.",
Expand Down
5 changes: 5 additions & 0 deletions apps/web/locales/fr-FR.json
Original file line number Diff line number Diff line change
Expand Up @@ -3074,6 +3074,11 @@
"duplicate_question": "Dupliquer la question",
"edit_link": "Modifier le lien",
"edit_recall": "Modifier le rappel",
"element_category_choice": "Choice",
"element_category_contact": "Contact",
"element_category_content": "Content",
"element_category_input": "Input",
"element_category_scoring": "Rating & Scoring",
"element_not_found": "Question non trouvée",
"enable_participants_to_switch_the_survey_language_at_any_point_during_the_survey": "Permettre aux répondants de changer de langue à tout moment. Nécessite au moins 2 langues actives.",
"enable_recaptcha_to_protect_your_survey_from_spam": "La protection contre le spam utilise reCAPTCHA v3 pour filtrer les réponses indésirables.",
Expand Down
5 changes: 5 additions & 0 deletions apps/web/locales/hu-HU.json
Original file line number Diff line number Diff line change
Expand Up @@ -3074,6 +3074,11 @@
"duplicate_question": "Kérdés kettőzése",
"edit_link": "Hivatkozás szerkesztése",
"edit_recall": "Visszahívás szerkesztése",
"element_category_choice": "Choice",
"element_category_contact": "Contact",
"element_category_content": "Content",
"element_category_input": "Input",
"element_category_scoring": "Rating & Scoring",
"element_not_found": "A kérdés nem található",
"enable_participants_to_switch_the_survey_language_at_any_point_during_the_survey": "Lehetővé tétel a válaszadóknak, hogy bármikor nyelvet váltsanak. Legalább 2 aktív nyelvet igényel.",
"enable_recaptcha_to_protect_your_survey_from_spam": "A szemét elleni védekezés a reCAPTCHA v3-at használja a kéretlen válaszok kiszűréséhez.",
Expand Down
5 changes: 5 additions & 0 deletions apps/web/locales/ja-JP.json
Original file line number Diff line number Diff line change
Expand Up @@ -3074,6 +3074,11 @@
"duplicate_question": "質問を複製",
"edit_link": "編集 リンク",
"edit_recall": "リコールを編集",
"element_category_choice": "Choice",
"element_category_contact": "Contact",
"element_category_content": "Content",
"element_category_input": "Input",
"element_category_scoring": "Rating & Scoring",
"element_not_found": "質問が見つかりません",
"enable_participants_to_switch_the_survey_language_at_any_point_during_the_survey": "回答者がいつでも言語を切り替えられるようにします。最低2つのアクティブな言語が必要です。",
"enable_recaptcha_to_protect_your_survey_from_spam": "スパム対策はreCAPTCHA v3を使用してスパム回答をフィルタリングします。",
Expand Down
5 changes: 5 additions & 0 deletions apps/web/locales/nl-NL.json
Original file line number Diff line number Diff line change
Expand Up @@ -3074,6 +3074,11 @@
"duplicate_question": "Vraag dupliceren",
"edit_link": "Link bewerken",
"edit_recall": "Bewerken Terugroepen",
"element_category_choice": "Choice",
"element_category_contact": "Contact",
"element_category_content": "Content",
"element_category_input": "Input",
"element_category_scoring": "Rating & Scoring",
"element_not_found": "Vraag niet gevonden",
"enable_participants_to_switch_the_survey_language_at_any_point_during_the_survey": "Sta respondenten toe om op elk moment van taal te wisselen. Vereist min. 2 actieve talen.",
"enable_recaptcha_to_protect_your_survey_from_spam": "Spambeveiliging maakt gebruik van reCAPTCHA v3 om de spamreacties eruit te filteren.",
Expand Down
5 changes: 5 additions & 0 deletions apps/web/locales/pt-BR.json
Original file line number Diff line number Diff line change
Expand Up @@ -3074,6 +3074,11 @@
"duplicate_question": "Duplicar pergunta",
"edit_link": "Editar link",
"edit_recall": "Editar Lembrete",
"element_category_choice": "Choice",
"element_category_contact": "Contact",
"element_category_content": "Content",
"element_category_input": "Input",
"element_category_scoring": "Rating & Scoring",
"element_not_found": "Pergunta não encontrada",
"enable_participants_to_switch_the_survey_language_at_any_point_during_the_survey": "Permitir que os respondentes alterem o idioma a qualquer momento. Necessita de no mínimo 2 idiomas ativos.",
"enable_recaptcha_to_protect_your_survey_from_spam": "A proteção contra spam usa o reCAPTCHA v3 para filtrar as respostas de spam.",
Expand Down
5 changes: 5 additions & 0 deletions apps/web/locales/pt-PT.json
Original file line number Diff line number Diff line change
Expand Up @@ -3074,6 +3074,11 @@
"duplicate_question": "Duplicar pergunta",
"edit_link": "Editar link",
"edit_recall": "Editar Lembrete",
"element_category_choice": "Choice",
"element_category_contact": "Contact",
"element_category_content": "Content",
"element_category_input": "Input",
"element_category_scoring": "Rating & Scoring",
"element_not_found": "Pergunta não encontrada",
"enable_participants_to_switch_the_survey_language_at_any_point_during_the_survey": "Permitir que os inquiridos mudem de idioma a qualquer momento. Necessita de pelo menos 2 idiomas ativos.",
"enable_recaptcha_to_protect_your_survey_from_spam": "A proteção contra spam usa o reCAPTCHA v3 para filtrar as respostas de spam.",
Expand Down
5 changes: 5 additions & 0 deletions apps/web/locales/ro-RO.json
Original file line number Diff line number Diff line change
Expand Up @@ -3074,6 +3074,11 @@
"duplicate_question": "Duplică întrebarea",
"edit_link": "Editare legătură",
"edit_recall": "Editează Referințele",
"element_category_choice": "Choice",
"element_category_contact": "Contact",
"element_category_content": "Content",
"element_category_input": "Input",
"element_category_scoring": "Rating & Scoring",
"element_not_found": "Întrebarea nu a fost găsită",
"enable_participants_to_switch_the_survey_language_at_any_point_during_the_survey": "Permite respondenților să schimbe limba în orice moment. Necesită minimum 2 limbi active.",
"enable_recaptcha_to_protect_your_survey_from_spam": "Protecția împotriva spamului folosește reCAPTCHA v3 pentru a filtra răspunsurile de spam.",
Expand Down
5 changes: 5 additions & 0 deletions apps/web/locales/ru-RU.json
Original file line number Diff line number Diff line change
Expand Up @@ -3074,6 +3074,11 @@
"duplicate_question": "Дублировать вопрос",
"edit_link": "Редактировать ссылку",
"edit_recall": "Редактировать напоминание",
"element_category_choice": "Choice",
"element_category_contact": "Contact",
"element_category_content": "Content",
"element_category_input": "Input",
"element_category_scoring": "Rating & Scoring",
"element_not_found": "Вопрос не найден",
"enable_participants_to_switch_the_survey_language_at_any_point_during_the_survey": "Разрешить респондентам менять язык опроса в любое время. Требуется минимум 2 активных языка.",
"enable_recaptcha_to_protect_your_survey_from_spam": "Для защиты от спама используется reCAPTCHA v3, чтобы отфильтровывать спам-ответы.",
Expand Down
5 changes: 5 additions & 0 deletions apps/web/locales/sv-SE.json
Original file line number Diff line number Diff line change
Expand Up @@ -3074,6 +3074,11 @@
"duplicate_question": "Duplicera fråga",
"edit_link": "Redigera länk",
"edit_recall": "Redigera återkallning",
"element_category_choice": "Choice",
"element_category_contact": "Contact",
"element_category_content": "Content",
"element_category_input": "Input",
"element_category_scoring": "Rating & Scoring",
"element_not_found": "Fråga hittades inte",
"enable_participants_to_switch_the_survey_language_at_any_point_during_the_survey": "Tillåt respondenter att byta språk när som helst. Kräver minst 2 aktiva språk.",
"enable_recaptcha_to_protect_your_survey_from_spam": "Spamskydd använder reCAPTCHA v3 för att filtrera bort spam-svar.",
Expand Down
5 changes: 5 additions & 0 deletions apps/web/locales/tr-TR.json
Original file line number Diff line number Diff line change
Expand Up @@ -3074,6 +3074,11 @@
"duplicate_question": "Soruyu çoğalt",
"edit_link": "Bağlantıyı düzenle",
"edit_recall": "Hatırlatmayı düzenle",
"element_category_choice": "Choice",
"element_category_contact": "Contact",
"element_category_content": "Content",
"element_category_input": "Input",
"element_category_scoring": "Rating & Scoring",
"element_not_found": "Soru bulunamadı",
"enable_participants_to_switch_the_survey_language_at_any_point_during_the_survey": "Katılımcıların istediği zaman anket dilini değiştirmesine izin ver. En az 2 aktif dil gerektirir.",
"enable_recaptcha_to_protect_your_survey_from_spam": "Spam koruması, spam yanıtları filtrelemek için reCAPTCHA v3 kullanır.",
Expand Down
5 changes: 5 additions & 0 deletions apps/web/locales/zh-Hans-CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -3074,6 +3074,11 @@
"duplicate_question": "复制问题",
"edit_link": "编辑 链接",
"edit_recall": "编辑 调用",
"element_category_choice": "Choice",
"element_category_contact": "Contact",
"element_category_content": "Content",
"element_category_input": "Input",
"element_category_scoring": "Rating & Scoring",
"element_not_found": "未找到问题",
"enable_participants_to_switch_the_survey_language_at_any_point_during_the_survey": "允许受访者在调查过程中随时切换语言。需要至少启用两种语言。",
"enable_recaptcha_to_protect_your_survey_from_spam": "垃圾 邮件 保护 使用 reCAPTCHA v3 来 过滤 掉 垃圾 响应 。",
Expand Down
5 changes: 5 additions & 0 deletions apps/web/locales/zh-Hant-TW.json
Original file line number Diff line number Diff line change
Expand Up @@ -3074,6 +3074,11 @@
"duplicate_question": "複製問題",
"edit_link": "編輯連結",
"edit_recall": "編輯回憶",
"element_category_choice": "Choice",
"element_category_contact": "Contact",
"element_category_content": "Content",
"element_category_input": "Input",
"element_category_scoring": "Rating & Scoring",
"element_not_found": "找不到問題",
"enable_participants_to_switch_the_survey_language_at_any_point_during_the_survey": "允許受訪者隨時切換語言。需要至少啟用兩種語言。",
"enable_recaptcha_to_protect_your_survey_from_spam": "垃圾郵件保護使用 reCAPTCHA v3 過濾垃圾回應。",
Expand Down
6 changes: 5 additions & 1 deletion apps/web/modules/auth/signup/actions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,11 @@ vi.mock("@/modules/auth/signup/lib/team", () => ({ createTeamMembership: vi.fn()
vi.mock("@/modules/auth/signup/lib/utils", () => ({ verifyTurnstileToken: vi.fn() }));
vi.mock("@/lib/membership/service", () => ({ createMembership: vi.fn() }));
vi.mock("@/lib/organization/service", () => ({ createOrganization: vi.fn(), getOrganization: vi.fn() }));
vi.mock("@/lib/posthog", () => ({ capturePostHogEvent: vi.fn(), groupIdentifyPostHog: vi.fn() }));
vi.mock("@/lib/posthog", () => ({
capturePostHogEvent: vi.fn(),
groupIdentifyPostHog: vi.fn(),
identifyPostHogPerson: vi.fn(),
}));
vi.mock("@/modules/ee/billing/lib/organization-billing", () => ({
ensureCloudStripeSetupForOrganization: vi.fn(),
}));
Expand Down
3 changes: 2 additions & 1 deletion apps/web/modules/auth/signup/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { IS_FORMBRICKS_CLOUD, IS_TURNSTILE_CONFIGURED, TURNSTILE_SECRET_KEY } fr
import { verifyInviteToken } from "@/lib/jwt";
import { createMembership } from "@/lib/membership/service";
import { createOrganization, getOrganization } from "@/lib/organization/service";
import { capturePostHogEvent, groupIdentifyPostHog } from "@/lib/posthog";
import { capturePostHogEvent, groupIdentifyPostHog, identifyPostHogPerson } from "@/lib/posthog";
import { getUserByEmail } from "@/lib/user/service";
import { actionClient } from "@/lib/utils/action-client";
import { ActionClientCtx } from "@/lib/utils/action-client/types/context";
Expand Down Expand Up @@ -294,6 +294,7 @@ export const createUserAction = actionClient.inputSchema(ZCreateUserAction).acti
const hasAttributionCookie = cookieStore.get(ATTRIBUTION_COOKIE_NAME) !== undefined;
const attributionProperties = getAttributionPropertiesFromCookies(cookieStore);

identifyPostHogPerson(user.id, { email: user.email, name: user.name });
capturePostHogEvent(
user.id,
"user_signed_up",
Expand Down
3 changes: 3 additions & 0 deletions apps/web/modules/ee/sso/lib/better-auth-hooks.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { getOAuthState } from "better-auth/api";
import { beforeEach, describe, expect, test, vi } from "vitest";
import { prisma } from "@formbricks/database";
import { SIGNUP_EMAIL_DOMAIN_BLOCKED_ERROR_CODE } from "@formbricks/types/errors";
import { identifyPostHogPerson } from "@/lib/posthog";
import { findMatchingLocale } from "@/lib/utils/locale";
import { isSignupEmailDomainBlocked } from "@/modules/auth/lib/signup-email-domain";
import { isSignupDomainAllowed } from "@/modules/auth/lib/signup-request-context";
Expand Down Expand Up @@ -37,6 +38,7 @@ vi.mock("better-auth/api", () => ({
},
}));
vi.mock("@formbricks/database", () => ({ prisma: { user: { findUnique: vi.fn() } } }));
vi.mock("@/lib/posthog", () => ({ identifyPostHogPerson: vi.fn() }));
vi.mock("@/lib/utils/locale", () => ({ findMatchingLocale: vi.fn() }));
vi.mock("@/modules/ee/license-check/lib/utils", () => ({
getIsSsoEnabled: vi.fn(),
Expand Down Expand Up @@ -217,6 +219,7 @@ describe("ssoDatabaseHooks.user.create.after", () => {
signupSource: "direct",
attributionProperties: {},
});
expect(identifyPostHogPerson).toHaveBeenCalledWith("u1", { email: "a@b.com", name: undefined });
});

test("does nothing when no decision is stashed (e.g. non-SSO sign-up)", async () => {
Expand Down
Loading
Loading