From 162c0f5db5b005e62e487224857b2938b3dc6cfe Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Thu, 9 Feb 2023 09:01:56 +0000 Subject: [PATCH] =?UTF-8?q?Identify=20user=E2=80=99s=20on=20the=20backend?= =?UTF-8?q?=20when=20they=20login/sign=20up?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/webapp/app/services/analytics.server.ts | 32 ++++++++++++ apps/webapp/app/services/postAuth.server.ts | 5 +- apps/webapp/package.json | 1 + pnpm-lock.yaml | 52 +++++++++++++------- 4 files changed, 72 insertions(+), 18 deletions(-) create mode 100644 apps/webapp/app/services/analytics.server.ts diff --git a/apps/webapp/app/services/analytics.server.ts b/apps/webapp/app/services/analytics.server.ts new file mode 100644 index 000000000..4920ca443 --- /dev/null +++ b/apps/webapp/app/services/analytics.server.ts @@ -0,0 +1,32 @@ +import { PostHog } from "posthog-node"; +import { env } from "~/env.server"; +import type { User } from "~/models/user.server"; + +class BehaviouralAnalytics { + client: PostHog | undefined = undefined; + + constructor(apiKey?: string) { + if (!apiKey) { + console.log("No PostHog API key, so analytics won't track"); + return; + } + this.client = new PostHog(apiKey, { host: "https://app.posthog.com" }); + } + + identify(user: User, isNewUser: boolean) { + if (this.client === undefined) return; + this.client.identify({ + distinctId: user.id, + properties: { + email: user.email, + name: user.name, + authenticationMethod: user.authenticationMethod, + admin: user.admin, + createdAt: user.createdAt, + isNewUser, + }, + }); + } +} + +export const analytics = new BehaviouralAnalytics(env.POSTHOG_PROJECT_KEY); diff --git a/apps/webapp/app/services/postAuth.server.ts b/apps/webapp/app/services/postAuth.server.ts index 428c6b691..9b989cb9f 100644 --- a/apps/webapp/app/services/postAuth.server.ts +++ b/apps/webapp/app/services/postAuth.server.ts @@ -1,6 +1,7 @@ import { createFirstOrganization } from "~/models/organization.server"; import type { User } from "~/models/user.server"; import * as emailProvider from "~/services/email.server"; +import { analytics } from "./analytics.server"; import { taskQueue } from "./messageBroker.server"; export async function postAuthentication({ @@ -9,7 +10,7 @@ export async function postAuthentication({ isNewUser, }: { user: User; - loginMethod: "MAGIC_LINK" | "GITHUB"; + loginMethod: User["authenticationMethod"]; isNewUser: boolean; }) { if (isNewUser) { @@ -26,4 +27,6 @@ export async function postAuthentication({ }, }); } + + analytics.identify(user); } diff --git a/apps/webapp/package.json b/apps/webapp/package.json index 0df303eef..6a3ee0d28 100644 --- a/apps/webapp/package.json +++ b/apps/webapp/package.json @@ -106,6 +106,7 @@ "openapi-types": "^12.0.0", "postcss-import": "^14.1.0", "posthog-js": "^1.31.0", + "posthog-node": "^2.4.0", "pretty-bytes": "^6.0.0", "prism-react-renderer": "^1.3.5", "prismjs": "^1.29.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 550784821..6d6f262ed 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -146,6 +146,7 @@ importers: postcss: ^8.4.14 postcss-import: ^14.1.0 posthog-js: ^1.31.0 + posthog-node: ^2.4.0 prettier: ^2.6.2 prettier-plugin-tailwindcss: ^0.1.13 pretty-bytes: ^6.0.0 @@ -187,7 +188,7 @@ importers: '@aws-sdk/client-s3': 3.245.0 '@aws-sdk/s3-request-presigner': 3.245.0 '@cfworker/json-schema': 1.12.5 - '@codemirror/autocomplete': 6.4.0_czcfkg2f66rxeiodoti7r2gulu + '@codemirror/autocomplete': 6.4.0_eo6pz6bvsllvatnnwfprpuflde '@codemirror/commands': 6.1.3 '@codemirror/lang-javascript': 6.1.2 '@codemirror/lang-json': 6.0.1 @@ -217,7 +218,7 @@ importers: '@trigger.dev/slack': link:../../integrations/slack '@trigger.dev/whatsapp': link:../../integrations/whatsapp '@typeform/embed-react': 2.14.1_react@18.2.0 - '@uiw/react-codemirror': 4.19.5_k4ec5g7vuuzzonc3d6xbjnmmle + '@uiw/react-codemirror': 4.19.5_aguurb4bmecpxzejz52amioxne bcryptjs: 2.4.3 classnames: 2.3.2 clsx: 1.2.1 @@ -250,6 +251,7 @@ importers: openapi-types: 12.1.0 postcss-import: 14.1.0_postcss@8.4.21 posthog-js: 1.39.4 + posthog-node: 2.4.0 pretty-bytes: 6.0.0 prism-react-renderer: 1.3.5_react@18.2.0 prismjs: 1.29.0 @@ -3538,12 +3540,13 @@ packages: prettier: 2.8.2 dev: false - /@codemirror/autocomplete/6.4.0_czcfkg2f66rxeiodoti7r2gulu: + /@codemirror/autocomplete/6.4.0_eo6pz6bvsllvatnnwfprpuflde: resolution: {integrity: sha512-HLF2PnZAm1s4kGs30EiqKMgD7XsYaQ0XJnMR0rofEWQ5t5D60SfqpDIkIh1ze5tiEbyUWm8+VJ6W1/erVvBMIA==} peerDependencies: '@codemirror/language': ^6.0.0 '@codemirror/state': ^6.0.0 '@codemirror/view': ^6.0.0 + '@lezer/common': ^1.0.0 dependencies: '@codemirror/language': 6.3.2 '@codemirror/state': 6.2.0 @@ -3563,7 +3566,7 @@ packages: /@codemirror/lang-javascript/6.1.2: resolution: {integrity: sha512-OcwLfZXdQ1OHrLiIcKCn7MqZ7nx205CMKlhe+vL88pe2ymhT9+2P+QhwkYGxMICj8TDHyp8HFKVwpiisUT7iEQ==} dependencies: - '@codemirror/autocomplete': 6.4.0_czcfkg2f66rxeiodoti7r2gulu + '@codemirror/autocomplete': 6.4.0_eo6pz6bvsllvatnnwfprpuflde '@codemirror/language': 6.3.2 '@codemirror/lint': 6.1.0 '@codemirror/state': 6.2.0 @@ -4780,7 +4783,7 @@ packages: eslint: 8.31.0 eslint-import-resolver-node: 0.3.6 eslint-import-resolver-typescript: 3.5.3_hnftvkj7qg3s6bbigj4pr6djxy - eslint-plugin-import: 2.27.4_2ac3tknkazjoq5fxmuugu665ny + eslint-plugin-import: 2.27.4_qdjeohovcytra7xto5vgmxssaq eslint-plugin-jest: 26.9.0_ohsifnwenhmxgcp7mend4dnv74 eslint-plugin-jest-dom: 4.0.3_eslint@8.31.0 eslint-plugin-jsx-a11y: 6.7.1_eslint@8.31.0 @@ -5901,17 +5904,18 @@ packages: eslint-visitor-keys: 3.3.0 dev: true - /@uiw/codemirror-extensions-basic-setup/4.19.5_wd2tsis3in55bkaiwnc2c46tom: + /@uiw/codemirror-extensions-basic-setup/4.19.5_tbeldtdcrf45b35pezgkzq2u4e: resolution: {integrity: sha512-1zt7ZPJ01xKkSW/KDy0FZNga0bngN1fC594wCVG7FBi60ehfcAucpooQ+JSPScKXopxcb+ugPKZvVLzr9/OfzA==} peerDependencies: '@codemirror/autocomplete': '>=6.0.0' '@codemirror/commands': '>=6.0.0' '@codemirror/language': '>=6.0.0' + '@codemirror/lint': '>=6.0.0' '@codemirror/search': '>=6.0.0' '@codemirror/state': '>=6.0.0' '@codemirror/view': '>=6.0.0' dependencies: - '@codemirror/autocomplete': 6.4.0_czcfkg2f66rxeiodoti7r2gulu + '@codemirror/autocomplete': 6.4.0_eo6pz6bvsllvatnnwfprpuflde '@codemirror/commands': 6.1.3 '@codemirror/language': 6.3.2 '@codemirror/lint': 6.1.0 @@ -5920,11 +5924,14 @@ packages: '@codemirror/view': 6.7.2 dev: false - /@uiw/react-codemirror/4.19.5_k4ec5g7vuuzzonc3d6xbjnmmle: + /@uiw/react-codemirror/4.19.5_aguurb4bmecpxzejz52amioxne: resolution: {integrity: sha512-ZCHh8d7beXbF8/t7F1+yHht6A9Y6CdKeOkZq4A09lxJEnyTQrj1FMf2zvfaqc7K23KNjkTCtSlbqKKbVDgrWaw==} peerDependencies: + '@babel/runtime': '>=7.11.0' '@codemirror/state': '>=6.0.0' + '@codemirror/theme-one-dark': '>=6.0.0' '@codemirror/view': '>=6.0.0' + codemirror: '>=6.0.0' react: '>=16.8.0' react-dom: '>=16.8.0' dependencies: @@ -5933,13 +5940,14 @@ packages: '@codemirror/state': 6.2.0 '@codemirror/theme-one-dark': 6.1.0 '@codemirror/view': 6.7.2 - '@uiw/codemirror-extensions-basic-setup': 4.19.5_wd2tsis3in55bkaiwnc2c46tom - codemirror: 6.0.1 + '@uiw/codemirror-extensions-basic-setup': 4.19.5_tbeldtdcrf45b35pezgkzq2u4e + codemirror: 6.0.1_@lezer+common@1.0.2 react: 18.2.0 react-dom: 18.2.0_react@18.2.0 transitivePeerDependencies: - '@codemirror/autocomplete' - '@codemirror/language' + - '@codemirror/lint' - '@codemirror/search' dev: false @@ -7174,16 +7182,18 @@ packages: engines: {node: '>=0.10.0'} dev: false - /codemirror/6.0.1: + /codemirror/6.0.1_@lezer+common@1.0.2: resolution: {integrity: sha512-J8j+nZ+CdWmIeFIGXEFbFPtpiYacFMDR8GlHK3IyHQJMCaVRfGx9NT+Hxivv1ckLWPvNdZqndbr/7lVhrf/Svg==} dependencies: - '@codemirror/autocomplete': 6.4.0_czcfkg2f66rxeiodoti7r2gulu + '@codemirror/autocomplete': 6.4.0_eo6pz6bvsllvatnnwfprpuflde '@codemirror/commands': 6.1.3 '@codemirror/language': 6.3.2 '@codemirror/lint': 6.1.0 '@codemirror/search': 6.2.3 '@codemirror/state': 6.2.0 '@codemirror/view': 6.7.2 + transitivePeerDependencies: + - '@lezer/common' dev: false /collection-visit/1.0.0: @@ -8411,7 +8421,7 @@ packages: debug: 4.3.4 enhanced-resolve: 5.12.0 eslint: 8.31.0 - eslint-plugin-import: 2.27.4_2ac3tknkazjoq5fxmuugu665ny + eslint-plugin-import: 2.27.4_qdjeohovcytra7xto5vgmxssaq get-tsconfig: 4.3.0 globby: 13.1.3 is-core-module: 2.11.0 @@ -8421,7 +8431,7 @@ packages: - supports-color dev: true - /eslint-module-utils/2.7.4_v73lhamtbyinynmwa5fn7kpmfq: + /eslint-module-utils/2.7.4_sqt5xxn4ciiurbqrzlaarm6ama: resolution: {integrity: sha512-j4GT+rqzCoRKHwURX7pddtIPGySnX9Si/cgMI5ztrcqOPtk5dDEeZ34CQVPphnqkJytlc97Vuk05Um2mJ3gEQA==} engines: {node: '>=4'} peerDependencies: @@ -8446,7 +8456,6 @@ packages: debug: 3.2.7 eslint: 8.31.0 eslint-import-resolver-node: 0.3.7 - eslint-import-resolver-typescript: 3.5.3_hnftvkj7qg3s6bbigj4pr6djxy transitivePeerDependencies: - supports-color dev: true @@ -8471,7 +8480,7 @@ packages: regexpp: 3.2.0 dev: true - /eslint-plugin-import/2.27.4_2ac3tknkazjoq5fxmuugu665ny: + /eslint-plugin-import/2.27.4_qdjeohovcytra7xto5vgmxssaq: resolution: {integrity: sha512-Z1jVt1EGKia1X9CnBCkpAOhWy8FgQ7OmJ/IblEkT82yrFU/xJaxwujaTzLWqigewwynRQ9mmHfX9MtAfhxm0sA==} engines: {node: '>=4'} peerDependencies: @@ -8489,7 +8498,7 @@ packages: doctrine: 2.1.0 eslint: 8.31.0 eslint-import-resolver-node: 0.3.7 - eslint-module-utils: 2.7.4_v73lhamtbyinynmwa5fn7kpmfq + eslint-module-utils: 2.7.4_sqt5xxn4ciiurbqrzlaarm6ama has: 1.0.3 is-core-module: 2.11.0 is-glob: 4.0.3 @@ -12885,6 +12894,15 @@ packages: rrweb-snapshot: 1.1.14 dev: false + /posthog-node/2.4.0: + resolution: {integrity: sha512-ijenljLS49AzMskyrDsmEbuPUI641I/qUEUfsVTFZYzNcmmiwWCyJu4v51DjzcH/vAda4p44CIhzL2LkROCl2Q==} + engines: {node: '>=14.17.0'} + dependencies: + axios: 0.27.2 + transitivePeerDependencies: + - debug + dev: false + /preferred-pm/3.0.3: resolution: {integrity: sha512-+wZgbxNES/KlJs9q40F/1sfOd/j7f1O9JaHcW5Dsn3aUUOZg3L2bjpVUcKV2jvtElYfoTuQiNeMfQJ4kwUAhCQ==} engines: {node: '>=10'}