mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-07-26 23:51:14 +02:00
feat(backend): integrate PostHog analytics for enhanced observability
- Added PostHog configuration options to .env.example files for both Docker and Surfsense backend. - Introduced PostHog dependency in pyproject.toml. - Implemented analytics middleware to capture various events across the application, including user authentication, automation runs, and API requests. - Enhanced existing routes and services to emit analytics events, providing insights into user interactions and system performance. - Ensured graceful shutdown of analytics clients in worker processes and application lifecycles.
This commit is contained in:
parent
ca4f231577
commit
dbedf0cfa5
47 changed files with 1618 additions and 513 deletions
|
|
@ -13,7 +13,7 @@ import { Spinner } from "@/components/ui/spinner";
|
|||
import { getAuthErrorDetails, isNetworkError } from "@/lib/auth-errors";
|
||||
import { getPostLoginRedirectPath } from "@/lib/auth-utils";
|
||||
import { ValidationError } from "@/lib/error";
|
||||
import { trackLoginAttempt, trackLoginFailure, trackLoginSuccess } from "@/lib/posthog/events";
|
||||
import { trackLoginAttempt, trackLoginFailure } from "@/lib/posthog/events";
|
||||
|
||||
export function LocalLoginForm() {
|
||||
const t = useTranslations("auth");
|
||||
|
|
@ -45,8 +45,8 @@ export function LocalLoginForm() {
|
|||
grant_type: "password",
|
||||
});
|
||||
|
||||
// Track successful login
|
||||
trackLoginSuccess("local");
|
||||
// auth_login_success is now emitted server-side
|
||||
// (UserManager.on_after_login) — authoritative vs. optimistic.
|
||||
|
||||
// Small delay to show success message
|
||||
setTimeout(() => {
|
||||
|
|
|
|||
|
|
@ -15,11 +15,7 @@ import { Spinner } from "@/components/ui/spinner";
|
|||
import { useSession } from "@/hooks/use-session";
|
||||
import { getAuthErrorDetails, isNetworkError, shouldRetry } from "@/lib/auth-errors";
|
||||
import { AppError, ValidationError } from "@/lib/error";
|
||||
import {
|
||||
trackRegistrationAttempt,
|
||||
trackRegistrationFailure,
|
||||
trackRegistrationSuccess,
|
||||
} from "@/lib/posthog/events";
|
||||
import { trackRegistrationAttempt, trackRegistrationFailure } from "@/lib/posthog/events";
|
||||
import { AmbientBackground } from "../login/AmbientBackground";
|
||||
|
||||
export default function RegisterPage() {
|
||||
|
|
@ -81,8 +77,8 @@ export default function RegisterPage() {
|
|||
is_verified: false,
|
||||
});
|
||||
|
||||
// Track successful registration
|
||||
trackRegistrationSuccess();
|
||||
// auth_registration_success is now emitted server-side
|
||||
// (UserManager.on_after_register) — authoritative vs. optimistic.
|
||||
|
||||
// Success toast
|
||||
toast.success(t("register_success"), {
|
||||
|
|
|
|||
|
|
@ -96,7 +96,6 @@ import type { Role } from "@/contracts/types/roles.types";
|
|||
import { invitesApiService } from "@/lib/apis/invites-api.service";
|
||||
import { rolesApiService } from "@/lib/apis/roles-api.service";
|
||||
import { formatRelativeDate } from "@/lib/format-date";
|
||||
import { trackWorkspaceInviteSent, trackWorkspaceUsersViewed } from "@/lib/posthog/events";
|
||||
import { cacheKeys } from "@/lib/query-client/cache-keys";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
|
|
@ -226,12 +225,7 @@ export function TeamContent({ workspaceId }: TeamContentProps) {
|
|||
const canPrev = pageIndex > 0;
|
||||
const canNext = displayEnd < totalItems;
|
||||
|
||||
useEffect(() => {
|
||||
if (members.length > 0 && !membersLoading) {
|
||||
const ownerCount = members.filter((m) => m.is_owner).length;
|
||||
trackWorkspaceUsersViewed(workspaceId, members.length, ownerCount);
|
||||
}
|
||||
}, [members, membersLoading, workspaceId]);
|
||||
// workspace_users_viewed removed — redundant with $pageview.
|
||||
|
||||
if (accessLoading || membersLoading) {
|
||||
return (
|
||||
|
|
@ -342,11 +336,7 @@ export function TeamContent({ workspaceId }: TeamContentProps) {
|
|||
Invite members
|
||||
</Button>
|
||||
) : (
|
||||
<CreateInviteDialog
|
||||
roles={roles}
|
||||
onCreateInvite={handleCreateInvite}
|
||||
workspaceId={workspaceId}
|
||||
/>
|
||||
<CreateInviteDialog roles={roles} onCreateInvite={handleCreateInvite} />
|
||||
)}
|
||||
{invitesLoading ? (
|
||||
<Button
|
||||
|
|
@ -617,11 +607,9 @@ function MemberRow({
|
|||
function CreateInviteDialog({
|
||||
roles,
|
||||
onCreateInvite,
|
||||
workspaceId,
|
||||
}: {
|
||||
roles: Role[];
|
||||
onCreateInvite: (data: CreateInviteRequest["data"]) => Promise<Invite>;
|
||||
workspaceId: number;
|
||||
}) {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [creating, setCreating] = useState(false);
|
||||
|
|
@ -653,12 +641,7 @@ function CreateInviteDialog({
|
|||
const invite = await onCreateInvite(data);
|
||||
setCreatedInvite(invite);
|
||||
|
||||
const roleName = roleId ? roles.find((r) => r.id.toString() === roleId)?.name : undefined;
|
||||
trackWorkspaceInviteSent(workspaceId, {
|
||||
roleName,
|
||||
hasExpiry: !!expiresAt,
|
||||
hasMaxUses: !!maxUses,
|
||||
});
|
||||
// workspace_invite_sent is now emitted server-side (rbac_routes.create_invite).
|
||||
} catch (error) {
|
||||
console.error("Failed to create invite:", error);
|
||||
toast.error("Failed to create invite. Please try again.");
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ import { motion } from "motion/react";
|
|||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { useParams, useRouter } from "next/navigation";
|
||||
import { use, useCallback, useEffect, useState } from "react";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
import { acceptInviteMutationAtom } from "@/atoms/invites/invites-mutation.atoms";
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
|
@ -33,11 +33,7 @@ import type { AcceptInviteResponse } from "@/contracts/types/invites.types";
|
|||
import { useSession } from "@/hooks/use-session";
|
||||
import { invitesApiService } from "@/lib/apis/invites-api.service";
|
||||
import { setRedirectPath } from "@/lib/auth-utils";
|
||||
import {
|
||||
trackWorkspaceInviteAccepted,
|
||||
trackWorkspaceInviteDeclined,
|
||||
trackWorkspaceUserAdded,
|
||||
} from "@/lib/posthog/events";
|
||||
import { trackWorkspaceInviteDeclined } from "@/lib/posthog/events";
|
||||
import { cacheKeys } from "@/lib/query-client/cache-keys";
|
||||
|
||||
export default function InviteAcceptPage() {
|
||||
|
|
@ -96,9 +92,9 @@ export default function InviteAcceptPage() {
|
|||
setAccepted(true);
|
||||
setAcceptedData(result);
|
||||
|
||||
// Track invite accepted and user added events
|
||||
trackWorkspaceInviteAccepted(result.workspace_id, result.workspace_name, result.role_name);
|
||||
trackWorkspaceUserAdded(result.workspace_id, result.workspace_name, result.role_name);
|
||||
// workspace_invite_accepted + workspace_user_added are now emitted
|
||||
// server-side (rbac_routes.accept_invite) — the server redirect is
|
||||
// the authoritative join point.
|
||||
}
|
||||
} catch (err: any) {
|
||||
setError(err.message || "Failed to accept invite");
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue