mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-07-18 10:01:56 +00:00
feat: centralize PostHog analytics with proper error handling
- Create lib/analytics.ts with centralized tracking functions - Use arrow functions and camelCase naming per code standards - Add error handling to prevent analytics failures from crashing app - Update auth forms and provider workflows to use new analytics lib - Export TypeScript interfaces for type-safe analytics payloads Addresses PR review comments: - Convert to arrow functions (requested by @paabloLC) - Use camelCase for event and property names - Centralize analytics logic for better maintainability
This commit is contained in:
@@ -4,7 +4,6 @@ import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { Icon } from "@iconify/react";
|
||||
import { Button, Checkbox, Divider, Link, Tooltip } from "@nextui-org/react";
|
||||
import { useRouter, useSearchParams } from "next/navigation";
|
||||
import posthog from "posthog-js";
|
||||
import { useEffect } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { z } from "zod";
|
||||
@@ -22,6 +21,7 @@ import {
|
||||
FormField,
|
||||
FormMessage,
|
||||
} from "@/components/ui/form";
|
||||
import { initializeSession, trackUserLogin, trackUserRegistration } from "@/lib/analytics";
|
||||
import { ApiError, authFormSchema } from "@/types";
|
||||
|
||||
export const AuthForm = ({
|
||||
@@ -82,7 +82,7 @@ export const AuthForm = ({
|
||||
|
||||
const onSubmit = async (data: z.infer<typeof formSchema>) => {
|
||||
//getting an new posthog init
|
||||
posthog.reset(true);
|
||||
initializeSession();
|
||||
if (type === "sign-in") {
|
||||
if (data.isSamlMode) {
|
||||
const email = data.email.toLowerCase();
|
||||
@@ -110,11 +110,7 @@ export const AuthForm = ({
|
||||
password: data.password,
|
||||
});
|
||||
if (result?.message === "Success") {
|
||||
posthog.identify(data.email.toLowerCase());
|
||||
posthog.capture("user_login", {
|
||||
email: data.email.toLocaleLowerCase(),
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
trackUserLogin({ email: data.email });
|
||||
router.push("/");
|
||||
} else if (result?.errors && "credentials" in result.errors) {
|
||||
form.setError("email", {
|
||||
@@ -136,19 +132,10 @@ export const AuthForm = ({
|
||||
const newUser = await createNewUser(data);
|
||||
|
||||
if (!newUser.errors) {
|
||||
let firstName = "";
|
||||
let lastName = "";
|
||||
if (data.name) {
|
||||
const nameParts = data.name.trim().split(" ");
|
||||
firstName = nameParts[0] || "";
|
||||
lastName = nameParts.slice(1).join(" ") || "";
|
||||
}
|
||||
posthog.capture("user_registered", {
|
||||
email: data.email.toLocaleLowerCase(),
|
||||
firstname: firstName,
|
||||
lastname: lastName,
|
||||
company: data.company || "",
|
||||
timestamp: Date.now(),
|
||||
trackUserRegistration({
|
||||
email: data.email,
|
||||
fullName: data.name,
|
||||
company: data.company,
|
||||
});
|
||||
toast({
|
||||
title: "Success!",
|
||||
|
||||
@@ -5,7 +5,6 @@ import { Icon } from "@iconify/react";
|
||||
import { Checkbox } from "@nextui-org/react";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/navigation";
|
||||
import posthog from "posthog-js";
|
||||
import { useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { z } from "zod";
|
||||
@@ -18,6 +17,7 @@ import { scanOnDemand, scheduleDaily } from "@/actions/scans";
|
||||
import { getTask } from "@/actions/task/tasks";
|
||||
import { CheckIcon, RocketIcon } from "@/components/icons";
|
||||
import { useToast } from "@/components/ui";
|
||||
import { trackCloudConnectionSuccess } from "@/lib/analytics";
|
||||
import { CustomButton } from "@/components/ui/custom";
|
||||
import { Form } from "@/components/ui/form";
|
||||
import { checkTaskStatus } from "@/lib/helper";
|
||||
@@ -146,12 +146,11 @@ export const TestConnectionForm = ({
|
||||
});
|
||||
} else {
|
||||
setIsRedirecting(true);
|
||||
// Capture PostHog event for successful cloud connection
|
||||
posthog.capture("cloud_connection_success", {
|
||||
provider_type: providerType,
|
||||
provider_alias: providerData.data.attributes.alias,
|
||||
scan_type: form.watch("runOnce") ? "single" : "scheduled",
|
||||
timestamp: Date.now(),
|
||||
// Track cloud connection success event
|
||||
trackCloudConnectionSuccess({
|
||||
providerType: providerType,
|
||||
providerAlias: providerData.data.attributes.alias,
|
||||
scanType: form.watch("runOnce") ? "single" : "scheduled",
|
||||
});
|
||||
router.push("/scans");
|
||||
}
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
import posthog from "posthog-js";
|
||||
|
||||
// Type definitions for tracking payloads
|
||||
export interface UserLoginPayload {
|
||||
email: string;
|
||||
}
|
||||
|
||||
export interface UserRegistrationPayload {
|
||||
email: string;
|
||||
firstName?: string;
|
||||
lastName?: string;
|
||||
company?: string;
|
||||
fullName?: string;
|
||||
}
|
||||
|
||||
export interface CloudConnectionPayload {
|
||||
providerType: string;
|
||||
providerAlias: string;
|
||||
scanType: "single" | "scheduled";
|
||||
}
|
||||
|
||||
// Initialize a new PostHog session
|
||||
export const initializeSession = (): void => {
|
||||
try {
|
||||
posthog.reset(true);
|
||||
} catch (error) {
|
||||
console.error("Failed to initialize PostHog session:", error);
|
||||
}
|
||||
};
|
||||
|
||||
// Identify a user in PostHog
|
||||
export const identifyUser = (email: string): void => {
|
||||
try {
|
||||
posthog.identify(email.toLowerCase());
|
||||
} catch (error) {
|
||||
console.error("Failed to identify user in PostHog:", error);
|
||||
}
|
||||
};
|
||||
|
||||
// Track user login event
|
||||
export const trackUserLogin = ({ email }: UserLoginPayload): void => {
|
||||
try {
|
||||
const normalizedEmail = email.toLowerCase();
|
||||
identifyUser(normalizedEmail);
|
||||
posthog.capture("userLogin", {
|
||||
email: normalizedEmail,
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Failed to track user login:", error);
|
||||
}
|
||||
};
|
||||
|
||||
// Track user registration event
|
||||
export const trackUserRegistration = ({
|
||||
email,
|
||||
firstName = "",
|
||||
lastName = "",
|
||||
company = "",
|
||||
fullName = "",
|
||||
}: UserRegistrationPayload): void => {
|
||||
try {
|
||||
// Parse name if fullName is provided
|
||||
let first = firstName;
|
||||
let last = lastName;
|
||||
|
||||
if (fullName && (!firstName || !lastName)) {
|
||||
const nameParts = fullName.trim().split(" ");
|
||||
first = nameParts[0] || "";
|
||||
last = nameParts.slice(1).join(" ") || "";
|
||||
}
|
||||
|
||||
posthog.capture("userRegistered", {
|
||||
email: email.toLowerCase(),
|
||||
firstName: first,
|
||||
lastName: last,
|
||||
company: company,
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Failed to track user registration:", error);
|
||||
}
|
||||
};
|
||||
|
||||
// Track cloud connection success
|
||||
export const trackCloudConnectionSuccess = ({
|
||||
providerType,
|
||||
providerAlias,
|
||||
scanType,
|
||||
}: CloudConnectionPayload): void => {
|
||||
try {
|
||||
posthog.capture("cloudConnectionSuccess", {
|
||||
providerType: providerType,
|
||||
providerAlias: providerAlias,
|
||||
scanType: scanType,
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Failed to track cloud connection success:", error);
|
||||
}
|
||||
};
|
||||
|
||||
// Generic event tracking function for custom events
|
||||
export const trackEvent = (eventName: string, properties?: Record<string, any>): void => {
|
||||
try {
|
||||
posthog.capture(eventName, {
|
||||
...properties,
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(`Failed to track event "${eventName}":`, error);
|
||||
}
|
||||
};
|
||||
|
||||
// Track page view
|
||||
export const trackPageView = (pageName: string, properties?: Record<string, any>): void => {
|
||||
try {
|
||||
posthog.capture("$pageview", {
|
||||
$current_url: window.location.href,
|
||||
pageName,
|
||||
...properties,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Failed to track page view:", error);
|
||||
}
|
||||
};
|
||||
|
||||
// Set user properties
|
||||
export const setUserProperties = (properties: Record<string, any>): void => {
|
||||
try {
|
||||
posthog.people.set(properties);
|
||||
} catch (error) {
|
||||
console.error("Failed to set user properties:", error);
|
||||
}
|
||||
};
|
||||
|
||||
// Check if PostHog is initialized and ready
|
||||
export const isAnalyticsReady = (): boolean => {
|
||||
try {
|
||||
return typeof posthog !== "undefined" && posthog._isIdentified !== undefined;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
+4
-2
@@ -1,11 +1,13 @@
|
||||
import { PostHog } from "posthog-node";
|
||||
|
||||
// NOTE: This is a Node.js client, so you can use it for sending events from the server side to PostHog.
|
||||
export default function PostHogClient() {
|
||||
const PostHogClient = () => {
|
||||
const posthogClient = new PostHog(process.env.NEXT_PUBLIC_POSTHOG_KEY!, {
|
||||
host: process.env.NEXT_PUBLIC_POSTHOG_HOST,
|
||||
flushAt: 1,
|
||||
flushInterval: 0,
|
||||
});
|
||||
return posthogClient;
|
||||
}
|
||||
};
|
||||
|
||||
export default PostHogClient;
|
||||
|
||||
Reference in New Issue
Block a user