import { PostHog } from "posthog-node"; import { env } from "~/env.server"; import type { MatchedOrganization } from "~/hooks/useOrganizations"; import type { Organization } from "~/models/organization.server"; import type { Project } from "~/models/project.server"; import type { User } from "~/models/user.server"; import { singleton } from "~/utils/singleton"; import { enqueueAttioUserSync } from "./attio.server"; import { loopsClient } from "./loops.server"; type Options = { postHogApiKey?: string; }; class Telemetry { #posthogClient: PostHog | undefined = undefined; constructor({ postHogApiKey }: Options) { if (env.TRIGGER_TELEMETRY_DISABLED !== undefined) { console.log("📉 Telemetry disabled"); return; } if (postHogApiKey) { this.#posthogClient = new PostHog(postHogApiKey, { host: env.POSTHOG_HOST }); } else { console.log("No PostHog API key, so analytics won't track"); } } user = { identify: ({ user, isNewUser, referralSource, }: { user: User; isNewUser: boolean; referralSource?: string; }) => { if (this.#posthogClient) { const properties: Record = { email: user.email, name: user.name, authenticationMethod: user.authenticationMethod, admin: user.admin, createdAt: user.createdAt, isNewUser, }; if (referralSource) { properties.referralSource = referralSource; } this.#posthogClient.identify({ distinctId: user.id, properties, }); } if (isNewUser) { this.#capture({ userId: user.id, event: "user created", eventProperties: { email: user.email, name: user.name, authenticationMethod: user.authenticationMethod, admin: user.admin, createdAt: user.createdAt, }, }); loopsClient?.userCreated({ userId: user.id, email: user.email, name: user.name, }); enqueueAttioUserSync({ userId: user.id, email: user.email, referralSource: referralSource ?? user.referralSource, marketingEmails: user.marketingEmails, createdAt: user.createdAt, }); } }, }; organization = { identify: ({ organization }: { organization: MatchedOrganization }) => { if (this.#posthogClient === undefined) return; this.#posthogClient.groupIdentify({ groupType: "organization", groupKey: organization.id, properties: { name: organization.title, slug: organization.slug, }, }); }, new: ({ userId, organization, organizationCount, }: { userId: string; organization: Organization; organizationCount: number; }) => { if (this.#posthogClient === undefined) return; this.#capture({ userId, event: "organization created", organizationId: organization.id, eventProperties: { id: organization.id, slug: organization.slug, title: organization.title, createdAt: organization.createdAt, updatedAt: organization.updatedAt, }, userProperties: { organizationCount: organizationCount, }, }); }, }; project = { identify: ({ project, }: { project: Pick; }) => { if (this.#posthogClient === undefined) return; this.#posthogClient.groupIdentify({ groupType: "project", groupKey: project.id, properties: { name: project.name, createdAt: project.createdAt, updatedAt: project.updatedAt, }, }); }, new: ({ userId, organizationId, project, }: { userId: string; organizationId: string; project: Project; }) => { if (this.#posthogClient === undefined) return; this.#capture({ userId, event: "project created", organizationId, eventProperties: { id: project.id, title: project.name, createdAt: project.createdAt, updatedAt: project.updatedAt, }, }); }, }; #capture(event: CaptureEvent) { if (this.#posthogClient === undefined) return; let groups: Record = {}; if (event.organizationId) { groups = { ...groups, organization: event.organizationId, }; } if (event.projectId) { groups = { ...groups, project: event.projectId, }; } if (event.jobId) { groups = { ...groups, workflow: event.jobId, }; } if (event.environmentId) { groups = { ...groups, environment: event.environmentId, }; } let properties: Record = {}; if (event.eventProperties) { properties = { ...properties, ...event.eventProperties, }; } if (event.userProperties) { properties = { ...properties, $set: event.userProperties, }; } if (event.userOnceProperties) { properties = { ...properties, $set_once: event.userOnceProperties, }; } const eventData = { distinctId: event.userId, event: event.event, properties, groups, }; this.#posthogClient.capture(eventData); } } type CaptureEvent = { userId: string; event: string; organizationId?: string; projectId?: string; jobId?: string; environmentId?: string; eventProperties?: Record; userProperties?: Record; userOnceProperties?: Record; }; export const telemetry = singleton( "telemetry", () => new Telemetry({ postHogApiKey: env.POSTHOG_PROJECT_KEY, }) );