Skip to content

Add new migration for signup tracking #3569

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Apr 11, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 73 additions & 0 deletions supabase/migrations/20250408183827_user_signup_tracking.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
CREATE EXTENSION IF NOT EXISTS http;
CREATE TABLE IF NOT EXISTS public.system_config (
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this table will come in handy a lot, nice

key TEXT PRIMARY KEY,
value TEXT NOT NULL,
description TEXT,
created_at TIMESTAMPTZ DEFAULT NOW()
);
INSERT INTO public.system_config (key, value, description)
VALUES (
'enable_tracking',
'false',
'Enable tracking of user signups'
) ON CONFLICT (key) DO NOTHING;
ALTER TABLE public.system_config ENABLE ROW LEVEL SECURITY;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
ALTER TABLE public.system_config ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.system_config ENABLE ROW LEVEL SECURITY;
REVOKE ALL PRIVILEGES ON TABLE public.system_config FROM anon;
REVOKE ALL PRIVILEGES ON TABLE public.system_config FROM authenticated;

REVOKE ALL PRIVILEGES ON TABLE public.system_config
FROM anon;
REVOKE ALL PRIVILEGES ON TABLE public.system_config
FROM authenticated;
CREATE POLICY "Allow postgres access to system_config" ON public.system_config FOR ALL TO postgres USING (true);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fuck RLS

Suggested change
CREATE POLICY "Allow postgres access to system_config" ON public.system_config FOR ALL TO postgres USING (true);

CREATE OR REPLACE FUNCTION public.track_user_signup_to_posthog() RETURNS TRIGGER LANGUAGE plpgsql SECURITY DEFINER
SET search_path = public AS $$
DECLARE posthog_key TEXT;
tracking_enabled TEXT;
response http_response;
BEGIN BEGIN
SELECT value INTO tracking_enabled
FROM public.system_config
WHERE key = 'enable_tracking';
IF tracking_enabled = 'true' THEN BEGIN
SELECT value INTO posthog_key
FROM public.system_config
WHERE key = 'posthog_public_key';
IF posthog_key IS NOT NULL
AND posthog_key != '' THEN
SELECT * INTO response
FROM http_post(
'https://app.posthog.com/capture/',
jsonb_build_object(
'api_key',
posthog_key,
'event',
'user_signed_up',
'properties',
jsonb_build_object(
'distinct_id',
NEW.id,
'email',
NEW.email,
'source',
'database_trigger',
'timestamp',
extract(
epoch
from now()
) * 1000
)
)::text,
'application/json'
);
END IF;
EXCEPTION
WHEN OTHERS THEN NULL;
END;
END IF;
EXCEPTION
WHEN OTHERS THEN NULL;
END;
RETURN NEW;
END;
$$;
CREATE TRIGGER track_user_signup_after_creation
AFTER
INSERT ON auth.users FOR EACH ROW EXECUTE FUNCTION public.track_user_signup_to_posthog();
71 changes: 71 additions & 0 deletions supabase/migrations/20250408201536_onboard-tracking.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
CREATE OR REPLACE FUNCTION public.track_organization_onboarding() RETURNS TRIGGER LANGUAGE plpgsql SECURITY DEFINER AS $$
DECLARE posthog_key TEXT;
tracking_enabled TEXT;
org_owner_email TEXT;
org_owner_name TEXT;
response http_response;
BEGIN IF (
OLD.has_onboarded = false
AND NEW.has_onboarded = true
AND NEW.tier != 'demo'
) THEN
SELECT value INTO tracking_enabled
FROM public.system_config
WHERE key = 'enable_tracking';
IF tracking_enabled = 'true' THEN BEGIN
SELECT email,
COALESCE(
raw_user_meta_data->>'full_name',
raw_user_meta_data->>'name',
''
) INTO org_owner_email,
org_owner_name
FROM auth.users
WHERE id = NEW.owner;
SELECT value INTO posthog_key
FROM public.system_config
WHERE key = 'posthog_public_key';
IF posthog_key IS NOT NULL
AND posthog_key != '' THEN
SELECT * INTO response
FROM http_post(
'https://app.posthog.com/capture/',
jsonb_build_object(
'api_key',
posthog_key,
'event',
'organization_onboarded',
'distinct_id',
NEW.owner,
'properties',
jsonb_build_object(
'email',
org_owner_email,
'name',
org_owner_name,
'timestamp',
extract(
epoch
from now()
) * 1000,
'$groups',
jsonb_build_object(
'organization',
NEW.id
)
)
)::text,
'application/json'
);
END IF;
EXCEPTION
WHEN OTHERS THEN NULL;
END;
END IF;
END IF;
RETURN NEW;
END;
$$;
CREATE TRIGGER track_organization_onboarding_trigger
AFTER
UPDATE ON public.organization FOR EACH ROW EXECUTE FUNCTION public.track_organization_onboarding();
63 changes: 63 additions & 0 deletions web/lib/clients/posthogClient.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
export class PosthogClient {
private static instance: PosthogClient;
private isEnabled: boolean;
private apiKey: string | null;

private constructor() {
this.apiKey = process.env.NEXT_PUBLIC_POSTHOG_API_KEY || null;
this.isEnabled =
!!this.apiKey &&
process.env.NEXT_PUBLIC_DISABLE_POSTHOG !== "true" &&
process.env.NODE_ENV !== "development";
}

static getInstance(): PosthogClient {
if (!PosthogClient.instance) {
PosthogClient.instance = new PosthogClient();
}
return PosthogClient.instance;
}

public async captureEvent(
eventName: string,
properties: Record<string, any> = {},
userId?: string,
organizationId?: string
): Promise<boolean> {
if (!this.isEnabled || !this.apiKey) {
console.log(`[PostHog Disabled] Would have sent: ${eventName}`);
return false;
}

try {
const payload = {
api_key: this.apiKey,
event: eventName,
distinct_id: userId || "server",
properties: {
...properties,
...(organizationId
? { $groups: { organization: organizationId } }
: {}),
},
};

const response = await fetch("https://app.posthog.com/capture/", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
});

const success = response.status === 200;
if (!success) {
console.error(
`PostHog Error (${response.status}): ${await response.text()}`
);
}
return success;
} catch (error) {
console.error("PostHog Capture Error:", error);
return false;
}
}
}
48 changes: 4 additions & 44 deletions web/pages/api/stripe/event_webhook/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { buildDynamicUpdateQuery, dbExecute } from "@/lib/api/db/dbExecute";
import {
getEvaluatorUsage,
getExperimentUsage,
Expand All @@ -7,6 +6,8 @@ import { getHeliconeAuthClient } from "@/packages/common/auth/server/AuthClientF
import { costOf } from "@/packages/cost";
import { OnboardingState } from "@/services/hooks/useOrgOnboarding";
import { WebClient } from "@slack/web-api";
import { buildDynamicUpdateQuery, dbExecute } from "@/lib/api/db/dbExecute";
import { PosthogClient } from "@/lib/clients/posthogClient";
import generateApiKey from "generate-api-key";
import { buffer } from "micro";
import { NextApiRequest, NextApiResponse } from "next";
Expand Down Expand Up @@ -37,47 +38,6 @@ async function getUserIdFromEmail(email: string): Promise<string | null> {
}
}

async function sendPosthogEvent(
event: string,
properties: Record<string, any>,
userId: string,
orgId?: string
) {
try {
if (!userId) {
console.error(
`Cannot send PostHog event: missing userId for event ${event}`
);
return;
}

const posthogPayload = {
api_key: process.env.NEXT_PUBLIC_POSTHOG_API_KEY,
event: event,
distinct_id: userId,
properties: {
...properties,
...(orgId ? { $groups: { organization: orgId } } : {}),
},
};

const response = await fetch(POSTHOG_EVENT_API, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(posthogPayload),
});

console.log(`PostHog: Event response for ${event}: ${response.status}`);

if (!response.ok) {
const responseText = await response.text();
console.error(`PostHog error: ${responseText}`);
}
} catch (error) {
console.error(`Error sending event to PostHog:`, error);
}
}

const ADDON_PRICES: Record<string, keyof Addons> = {
[process.env.PRICE_PROD_ALERTS_ID!]: "alerts",
[process.env.PRICE_PROD_PROMPTS_ID!]: "prompts",
Expand Down Expand Up @@ -165,8 +125,8 @@ async function sendSubscriptionEvent(
: "immediate";
}

// Send the event
await sendPosthogEvent(
const analytics = PosthogClient.getInstance();
await analytics.captureEvent(
eventType,
{
...baseProperties,
Expand Down
11 changes: 0 additions & 11 deletions web/pages/signup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import AuthForm from "../components/templates/auth/authForm";
import { DEMO_EMAIL } from "../lib/constants";
import PublicMetaData from "../components/layout/public/publicMetaData";
import { GetServerSidePropsContext } from "next";
import posthog from "posthog-js";
import { InfoBanner } from "../components/shared/themed/themedDemoBanner";
import { env } from "next-runtime-env";
const SignUp = () => {
Expand Down Expand Up @@ -42,10 +41,6 @@ const SignUp = () => {
<AuthForm
handleEmailSubmit={async (email: string, password: string) => {
const origin = window.location.origin;
posthog.capture("user_signed_up", {
method: "email",
email: email,
});

const { data, error } = await supabase.auth.signUp({
email: email,
Expand All @@ -68,9 +63,6 @@ const SignUp = () => {
setShowEmailConfirmation(true);
}}
handleGoogleSubmit={async () => {
posthog.capture("user_signed_up", {
method: "google",
});
const { error } = await supabase.auth.signInWithOAuth({
provider: "google",
options: {
Expand All @@ -87,9 +79,6 @@ const SignUp = () => {
}
}}
handleGithubSubmit={async () => {
posthog.capture("user_signed_up", {
method: "github",
});
const { error } = await supabase.auth.signInWithOAuth({
provider: "github",
options: {
Expand Down
Loading
Loading