From d87635386467133fd392a04bcf0ea4444e4931ef Mon Sep 17 00:00:00 2001
From: Arslan Saleem <khan.arslan38@gmail.com>
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 <lele.venturi@gmail.com>
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<void> => {
+  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==