From d87635386467133fd392a04bcf0ea4444e4931ef Mon Sep 17 00:00:00 2001 From: Arslan Saleem Date: Sat, 26 Oct 2024 10:33:00 +0200 Subject: [PATCH] feat[mixpanel]: integration of mix panel for analytics (#43) * feat[MIX_PANEL]: integrate mix panel for analytics * fix: make debug optional based on the environment Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * fix: remove api key tracking from event tracking Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * fix: correct mixpanel variable name * lint: fix prettier --------- Co-authored-by: Gabriele Venturi Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- backend/app/api/v1/user.py | 6 +- frontend/.env.example | 3 +- frontend/package.json | 2 + .../processes/[processId]/csv/page.tsx | 4 + frontend/src/lib/mixpanelLib.ts | 55 +++++++++++ frontend/src/services/processes.tsx | 2 + frontend/src/services/projects.tsx | 4 +- frontend/src/services/user.tsx | 6 ++ frontend/yarn.lock | 94 ++++++++++++++++++- 9 files changed, 168 insertions(+), 8 deletions(-) create mode 100644 frontend/src/lib/mixpanelLib.ts diff --git a/backend/app/api/v1/user.py b/backend/app/api/v1/user.py index 8bb9b01..a53bbc2 100644 --- a/backend/app/api/v1/user.py +++ b/backend/app/api/v1/user.py @@ -70,13 +70,13 @@ def get_user_api_key(db: Session = Depends(get_db)): @user_router.get("/getme", status_code=200) def get_me(db: Session = Depends(get_db)): - user_email = "john.doe@example.com" - user = user_repository.get_user(db, user_email) + users = user_repository.get_users(db, n=1) + return { "status": "success", "message": "User details returned successfully!", - "data": user, + "data": users[0] if len(users) > 0 else None, } diff --git a/frontend/.env.example b/frontend/.env.example index 3443f69..49593d3 100644 --- a/frontend/.env.example +++ b/frontend/.env.example @@ -1,2 +1,3 @@ NEXT_PUBLIC_API_URL=http://localhost:3000/api/v1 -NEXT_PUBLIC_STORAGE_URL=http://localhost:3000/api/assets \ No newline at end of file +NEXT_PUBLIC_STORAGE_URL=http://localhost:3000/api/assets +NEXT_PUBLIC_MIXPANEL_TOKEN=f2e8a71ab2bde33ebf346c5abf6ba9fa \ No newline at end of file diff --git a/frontend/package.json b/frontend/package.json index 19e36fc..4faf62c 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -18,11 +18,13 @@ "@react-pdf/renderer": "^4.0.0", "@tanstack/react-query": "^5.51.1", "@tippyjs/react": "^4.2.6", + "@types/mixpanel-browser": "^2.50.1", "axios": "^1.7.2", "date-fns": "^3.6.0", "framer-motion": "^11.3.31", "lucide-react": "^0.408.0", "marked": "^13.0.2", + "mixpanel-browser": "^2.55.1", "next": "14.2.4", "openai": "^4.52.5", "papaparse": "^5.4.1", diff --git a/frontend/src/app/(editor)/projects/[projectId]/processes/[processId]/csv/page.tsx b/frontend/src/app/(editor)/projects/[projectId]/processes/[processId]/csv/page.tsx index a7fb055..4b44488 100644 --- a/frontend/src/app/(editor)/projects/[projectId]/processes/[processId]/csv/page.tsx +++ b/frontend/src/app/(editor)/projects/[projectId]/processes/[processId]/csv/page.tsx @@ -9,6 +9,7 @@ import DataTable from "@/components/DataTable"; import Papa from "papaparse"; import { BASE_API_URL } from "@/constants"; import { toast } from "react-hot-toast"; +import { trackEvent } from "@/lib/mixpanelLib"; const ProcessPage = () => { const router = useRouter(); @@ -55,6 +56,8 @@ const ProcessPage = () => { }, header: true, }); + + trackEvent("Table opened", { url: `process_${processId}.csv` }); } catch (error) { console.error("Error loading CSV data:", error); setIsLoading(false); @@ -77,6 +80,7 @@ const ProcessPage = () => { document.body.removeChild(a); // Show success toast + trackEvent("Download CSV", { url: `process_${processId}.csv` }); toast.success("CSV downloaded successfully"); } catch (error) { console.error("Error downloading CSV:", error); diff --git a/frontend/src/lib/mixpanelLib.ts b/frontend/src/lib/mixpanelLib.ts new file mode 100644 index 0000000..ff52edd --- /dev/null +++ b/frontend/src/lib/mixpanelLib.ts @@ -0,0 +1,55 @@ +import { GetUserData } from "@/services/user"; +import mixpanel, { Mixpanel } from "mixpanel-browser"; + +const MIXPANEL_TOKEN = process.env.NEXT_PUBLIC_MIXPANEL_TOKEN; + +let mixpanelInstance: Mixpanel | null = null; + +let isInitialized = false; + +const initMixpanel = async () => { + if (typeof window !== "undefined" && MIXPANEL_TOKEN && !isInitialized) { + mixpanel.init(MIXPANEL_TOKEN, { + debug: process.env.NODE_ENV === "development", + }); + + const user = await GetUserData(); + + if (user.data.data) { + mixpanel.identify(user.data.data.email); + + mixpanel.people.set({ + $name: `${user.data.data.first_name} ${user.data.data.last_name}`, + $email: user.data.data.email, + }); + } + + mixpanelInstance = mixpanel; + isInitialized = true; + } else if (!MIXPANEL_TOKEN) { + console.warn("Mixpanel token is missing; tracking will be disabled."); + } +}; + +const trackEvent = async ( + eventName: string, + eventProps = {} +): Promise => { + if (!isInitialized) { + initMixpanel(); + } + if (mixpanelInstance) { + mixpanelInstance.track(eventName, eventProps); + } else { + console.warn( + `Event "${eventName}" not tracked because Mixpanel is not initialized.` + ); + } +}; + +// Initialize Mixpanel on the client at import +if (typeof window !== "undefined") { + initMixpanel(); +} + +export { trackEvent }; diff --git a/frontend/src/services/processes.tsx b/frontend/src/services/processes.tsx index e1a5e09..1eba61e 100644 --- a/frontend/src/services/processes.tsx +++ b/frontend/src/services/processes.tsx @@ -6,6 +6,7 @@ import { ProcessResumeData, ProcessSuggestionRequest, } from "@/interfaces/processes"; +import { trackEvent } from "@/lib/mixpanelLib"; export const processApiUrl = "/processes"; @@ -35,6 +36,7 @@ export const StartProcess = async (data: ProcessRequest) => { `${processApiUrl}/start`, data ); + trackEvent("Start Process", data); return response; } catch (error) { throw error; diff --git a/frontend/src/services/projects.tsx b/frontend/src/services/projects.tsx index 6b0ed62..66319ba 100644 --- a/frontend/src/services/projects.tsx +++ b/frontend/src/services/projects.tsx @@ -9,6 +9,7 @@ import { AssetData } from "@/interfaces/assets"; import { ProcessData } from "@/interfaces/processes"; import { BASE_API_URL } from "@/constants"; import { AxiosResponse } from "axios"; +import { trackEvent } from "@/lib/mixpanelLib"; const projectsApiUrl = "/projects"; @@ -49,6 +50,8 @@ export const CreateProject = async (data: { projectsApiUrl, data ); + + trackEvent("Project created", { data }); return response; } catch (error) { throw error; @@ -158,7 +161,6 @@ export const DeleteAssets = async (projectId: string, assetId: string) => { } }; -// Add this new function to update a project export const UpdateProject = async ( projectId: string, data: { name: string; description: string } diff --git a/frontend/src/services/user.tsx b/frontend/src/services/user.tsx index 70996cb..c694c1d 100644 --- a/frontend/src/services/user.tsx +++ b/frontend/src/services/user.tsx @@ -2,6 +2,7 @@ import { GetRequest, PostRequest, PutRequest } from "@/lib/requests"; import { APIKeyData } from "@/interfaces/user"; import { UserData } from "@/interfaces/user"; import localStorage from "@/lib/localStorage"; +import { trackEvent } from "@/lib/mixpanelLib"; const userApiUrl = "/user"; @@ -11,6 +12,7 @@ export const APIKeyRequest = async (data: { email: string }) => { `${userApiUrl}/request-api-key`, data ); + trackEvent("API Key Request", data); return response; } catch (error) { throw error; @@ -24,6 +26,10 @@ export const SaveAPIKey = async (data: { api_key: string }) => { data ); localStorage.setItem("api_key", data.api_key); + trackEvent("API Key Activation", { + timestamp: new Date().toISOString(), + success: true, + }); return response; } catch (error) { throw error; diff --git a/frontend/yarn.lock b/frontend/yarn.lock index d4b567c..d938d4b 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -685,6 +685,13 @@ resolved "https://registry.npmjs.org/@react-types/shared/-/shared-3.24.1.tgz" integrity sha512-AUQeGYEm/zDTN6zLzdXolDxz3Jk5dDL7f506F07U8tBwxNNI3WRdhU84G0/AaFikOZzDXhOZDr3MhQMzyE7Ydw== +"@rrweb/types@^2.0.0-alpha.13": + version "2.0.0-alpha.17" + resolved "https://registry.yarnpkg.com/@rrweb/types/-/types-2.0.0-alpha.17.tgz#bf4af026e767022674892919692d59e766e9368e" + integrity sha512-AfDTVUuCyCaIG0lTSqYtrZqJX39ZEYzs4fYKnexhQ+id+kbZIpIJtaut5cto6dWZbB3SEe4fW0o90Po3LvTmfg== + dependencies: + rrweb-snapshot "^2.0.0-alpha.17" + "@rtsao/scc@^1.1.0": version "1.1.0" resolved "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz" @@ -746,6 +753,11 @@ dependencies: tippy.js "^6.3.1" +"@types/css-font-loading-module@0.0.7": + version "0.0.7" + resolved "https://registry.yarnpkg.com/@types/css-font-loading-module/-/css-font-loading-module-0.0.7.tgz#2f98ede46acc0975de85c0b7b0ebe06041d24601" + integrity sha512-nl09VhutdjINdWyXxHWN/w9zlNCfr60JUqJbd24YXUuCwgeL0TpFSdElCwb6cxfB6ybE19Gjj4g0jsgkXxKv1Q== + "@types/debug@^4.0.0": version "4.1.12" resolved "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz" @@ -784,6 +796,11 @@ dependencies: "@types/unist" "*" +"@types/mixpanel-browser@^2.50.1": + version "2.50.1" + resolved "https://registry.yarnpkg.com/@types/mixpanel-browser/-/mixpanel-browser-2.50.1.tgz#e93b8754893369bd0a9eae61ce2a45f392ca4e7f" + integrity sha512-Z9QnzNIZtsyhc0tvGFeaYCo2NJTGwWi8pqLMB3z/BnGUdEpr3x2qK28KKTjVH31DMbQ0IwqjNQElwJP+XpmeMQ== + "@types/ms@*": version "0.7.34" resolved "https://registry.npmjs.org/@types/ms/-/ms-0.7.34.tgz" @@ -918,6 +935,11 @@ resolved "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz" integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ== +"@xstate/fsm@^1.4.0": + version "1.6.5" + resolved "https://registry.yarnpkg.com/@xstate/fsm/-/fsm-1.6.5.tgz#f599e301997ad7e3c572a0b1ff0696898081bea5" + integrity sha512-b5o1I6aLNeYlU/3CPlj/Z91ybk1gUsKT+5NAJI+2W4UjvS5KLG28K9v5UvNoFVjHV8PajVZ00RH3vnjyQO7ZAw== + abbrev@1: version "1.1.1" resolved "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz" @@ -1188,6 +1210,11 @@ balanced-match@^1.0.0: resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== +base64-arraybuffer@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz#1c37589a7c4b0746e34bd1feb951da2df01c1bdc" + integrity sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ== + base64-js@^1.1.2, base64-js@^1.3.0: version "1.5.1" resolved "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz" @@ -2123,6 +2150,11 @@ fastq@^1.6.0: dependencies: reusify "^1.0.4" +fflate@^0.4.4: + version "0.4.8" + resolved "https://registry.yarnpkg.com/fflate/-/fflate-0.4.8.tgz#f90b82aefbd8ac174213abb338bd7ef848f0f5ae" + integrity sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA== + file-entry-cache@^6.0.1: version "6.0.1" resolved "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz" @@ -3628,6 +3660,18 @@ minizlib@^2.1.1: minipass "^3.0.0" yallist "^4.0.0" +mitt@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/mitt/-/mitt-3.0.1.tgz#ea36cf0cc30403601ae074c8f77b7092cdab36d1" + integrity sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw== + +mixpanel-browser@^2.55.1: + version "2.55.1" + resolved "https://registry.yarnpkg.com/mixpanel-browser/-/mixpanel-browser-2.55.1.tgz#d37a8d3777abd4c58d537ea87959c9d9b9b507c8" + integrity sha512-NSEPdFSJxoR1OCKWKHbtqd3BeH1c9NjXbEt0tN5TgBEO1nSDji6niU9n4MopAXOP0POET9spjpQKxZtLZKTJwA== + dependencies: + rrweb "2.0.0-alpha.13" + mkdirp@^1.0.3: version "1.0.4" resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz" @@ -4050,7 +4094,7 @@ postcss@8.4.31: picocolors "^1.0.0" source-map-js "^1.0.2" -postcss@^8, postcss@^8.4.23: +postcss@^8, postcss@^8.4.23, postcss@^8.4.38: version "8.4.47" resolved "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz" integrity sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ== @@ -4408,6 +4452,34 @@ rimraf@^3.0.2: dependencies: glob "^7.1.3" +rrdom@^2.0.0-alpha.13: + version "2.0.0-alpha.17" + resolved "https://registry.yarnpkg.com/rrdom/-/rrdom-2.0.0-alpha.17.tgz#c200f21a63bab341caea7f3f2f88d760aa045c3a" + integrity sha512-b6caDiNcFO96Opp7TGdcVd4OLGSXu5dJe+A0IDiAu8mk7OmhqZCSDlgQdTKmdO5wMf4zPsUTgb8H/aNvR3kDHA== + dependencies: + rrweb-snapshot "^2.0.0-alpha.17" + +rrweb-snapshot@^2.0.0-alpha.13, rrweb-snapshot@^2.0.0-alpha.17: + version "2.0.0-alpha.17" + resolved "https://registry.yarnpkg.com/rrweb-snapshot/-/rrweb-snapshot-2.0.0-alpha.17.tgz#c4667cbca62530bcb98508e6c85c20f2b320f5ca" + integrity sha512-GBg5pV8LHOTbeVmH2VHLEFR0mc2QpQMzAvcoxEGfPNWgWHc8UvKCyq7pqN1vA+fDZ+yXXbixeO0kB2pzVvFCBw== + dependencies: + postcss "^8.4.38" + +rrweb@2.0.0-alpha.13: + version "2.0.0-alpha.13" + resolved "https://registry.yarnpkg.com/rrweb/-/rrweb-2.0.0-alpha.13.tgz#37798404acd985212f72544c8823af275fdad514" + integrity sha512-a8GXOCnzWHNaVZPa7hsrLZtNZ3CGjiL+YrkpLo0TfmxGLhjNZbWY2r7pE06p+FcjFNlgUVTmFrSJbK3kO7yxvw== + dependencies: + "@rrweb/types" "^2.0.0-alpha.13" + "@types/css-font-loading-module" "0.0.7" + "@xstate/fsm" "^1.4.0" + base64-arraybuffer "^1.0.1" + fflate "^0.4.4" + mitt "^3.0.0" + rrdom "^2.0.0-alpha.13" + rrweb-snapshot "^2.0.0-alpha.13" + run-parallel@^1.1.9: version "1.2.0" resolved "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz" @@ -4592,7 +4664,16 @@ string-argv@~0.3.2: resolved "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz" integrity sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q== -"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0": + version "4.2.3" + resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -4696,7 +4777,14 @@ stringify-entities@^4.0.0: character-entities-html4 "^2.0.0" character-entities-legacy "^3.0.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1": + version "6.0.1" + resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==