Skip to content
Open
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
5 changes: 4 additions & 1 deletion .env
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@ POSTGRES_PORT=5433
# must be exactly 32 bytes (64 hex characters)
AEAD_SECRET_KEY=0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef


# Telemetry Configuration
# Set to 'true' to enable anonymous usage analytics and error reporting
# To opt out of telemetry, set this to 'false' or remove this line entirely
NEXT_PUBLIC_ENABLE_TELEMETRY=true
# Uncomment and set these to override the default exposed ports.
# APP_SERVER_HOST_PORT=8080
# APP_SERVER_GRPC_HOST_PORT=8081
Expand Down
1 change: 1 addition & 0 deletions docker-compose-full.yml
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ services:
CLICKHOUSE_PASSWORD: ${CLICKHOUSE_PASSWORD}
OPENAI_API_KEY: ${OPENAI_API_KEY}
AEAD_SECRET_KEY: ${AEAD_SECRET_KEY}
NEXT_PUBLIC_ENABLE_TELEMETRY: ${NEXT_PUBLIC_ENABLE_TELEMETRY}

volumes:
clickhouse-data:
Expand Down
1 change: 1 addition & 0 deletions docker-compose-local-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ services:
CLICKHOUSE_PASSWORD: ${CLICKHOUSE_PASSWORD}
OPENAI_API_KEY: ${OPENAI_API_KEY}
AEAD_SECRET_KEY: ${AEAD_SECRET_KEY}
NEXT_PUBLIC_ENABLE_TELEMETRY: ${NEXT_PUBLIC_ENABLE_TELEMETRY}

volumes:
clickhouse-data:
Expand Down
1 change: 1 addition & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ services:
CLICKHOUSE_PASSWORD: ${CLICKHOUSE_PASSWORD}
OPENAI_API_KEY: ${OPENAI_API_KEY}
AEAD_SECRET_KEY: ${AEAD_SECRET_KEY}
NEXT_PUBLIC_ENABLE_TELEMETRY: ${NEXT_PUBLIC_ENABLE_TELEMETRY}

app-server:
image: ghcr.io/lmnr-ai/app-server
Expand Down
7 changes: 5 additions & 2 deletions frontend/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import { Metadata } from 'next';
import { Suspense } from 'react';

import { Toaster } from '@/components/ui/toaster';
import { manrope,sans } from '@/lib/fonts';
import { Feature, isFeatureEnabled } from "@/lib/features/features";
import { manrope, sans } from '@/lib/fonts';
import { cn } from '@/lib/utils';

import PostHogPageView from './posthog-pageview';
Expand Down Expand Up @@ -40,14 +41,16 @@ export default async function RootLayout({
}: {
children: React.ReactNode;
}) {
const isPostHogEnabled = isFeatureEnabled(Feature.POSTHOG);

return (
<html lang="en" className={cn('h-full antialiased', sans.variable, manrope.variable)}>
<PHProvider>
<body
className="flex flex-col h-full"
>
<Suspense fallback={null}>
<PostHogPageView />
<PostHogPageView isEnabled={isPostHogEnabled} />
</Suspense>
<div className="flex">
<div className="flex flex-col flex-grow max-w-full min-h-screen">
Expand Down
16 changes: 9 additions & 7 deletions frontend/app/posthog-identifier.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,25 @@
'use client';
"use client";

import { usePostHog } from 'posthog-js/react';
import { useEffect } from 'react';
import { usePostHog } from "posthog-js/react";
import { useEffect } from "react";

interface PostHogIdentifierProps {
email: string
email: string;
isEnabled?: boolean;
}

export default function PostHogIdentifier({ email }: PostHogIdentifierProps) {
export default function PostHogIdentifier({ email, isEnabled = false }: PostHogIdentifierProps) {
const posthog = usePostHog();

useEffect(() => {
// This runs in the browser and connects the current session with the user
if (email && posthog) {
// Only identify users if the feature is enabled (passed from server)
if (email && posthog && isEnabled) {
posthog.identify(email, {
email: email,
});
}
}, [email, posthog]);
}, [email, posthog, isEnabled]);

return null; // This component doesn't render anything
}
23 changes: 14 additions & 9 deletions frontend/app/posthog-pageview.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,31 @@
// app/PostHogPageView.tsx
'use client';
"use client";

import { usePathname, useSearchParams } from 'next/navigation';
import { usePostHog } from 'posthog-js/react';
import { useEffect } from 'react';
import { usePathname, useSearchParams } from "next/navigation";
import { usePostHog } from "posthog-js/react";
import { useEffect } from "react";

export default function PostHogPageView(): null {
interface PostHogPageViewProps {
isEnabled?: boolean;
}

export default function PostHogPageView({ isEnabled = false }: PostHogPageViewProps): null {
const pathname = usePathname();
const searchParams = useSearchParams();
const posthog = usePostHog();

useEffect(() => {
// Track pageviews
if (pathname && posthog) {
if (pathname && posthog && isEnabled) {
let url = window.origin + pathname;
if (searchParams.toString()) {
url = url + `?${searchParams.toString()}`;
}
posthog.capture('$pageview', {
$current_url: url
posthog.capture("$pageview", {
$current_url: url,
});
}
}, [pathname, searchParams, posthog]);
}, [pathname, searchParams, posthog, isEnabled]);

return null;
}
10 changes: 6 additions & 4 deletions frontend/app/project/[projectId]/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -109,9 +109,11 @@ export default async function ProjectIdLayout(props: { children: ReactNode; para
project.gbUsedThisMonth >= 0.8 * project.gbLimit;

const posthog = PostHogClient();
posthog.identify({
distinctId: user.email ?? "",
});
if (isFeatureEnabled(Feature.POSTHOG_IDENTIFY)) {
posthog.identify({
distinctId: user.email ?? "",
});
}

const cookieStore = await cookies();
const defaultOpen = cookieStore.get("sidebar:state") ? cookieStore.get("sidebar:state")?.value === "true" : true;
Expand All @@ -124,7 +126,7 @@ export default async function ProjectIdLayout(props: { children: ReactNode; para
imageUrl={user.image!}
supabaseAccessToken={session.supabaseAccessToken}
>
<PostHogIdentifier email={user.email!} />
<PostHogIdentifier email={user.email!} isEnabled={isFeatureEnabled(Feature.POSTHOG_IDENTIFY)} />
<ProjectContextProvider workspace={workspace} projects={projects} project={project}>
<div className="flex flex-row flex-1 overflow-hidden max-h-screen">
<SidebarProvider defaultOpen={defaultOpen}>
Expand Down
12 changes: 7 additions & 5 deletions frontend/app/projects/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { Feature, isFeatureEnabled } from "@/lib/features/features";
import PostHogClient from "../posthog";
import PostHogIdentifier from "../posthog-identifier";

export const dynamic = 'force-dynamic';
export const dynamic = "force-dynamic";

export const metadata: Metadata = {
title: "Projects",
Expand Down Expand Up @@ -46,9 +46,11 @@ export default async function ProjectsPage() {
}

const posthog = PostHogClient();
posthog.identify({
distinctId: user.email ?? "",
});
if (isFeatureEnabled(Feature.POSTHOG_IDENTIFY)) {
posthog.identify({
distinctId: user.email ?? "",
});
}

return (
<UserContextProvider
Expand All @@ -58,7 +60,7 @@ export default async function ProjectsPage() {
username={user.name!}
imageUrl={user.image!}
>
<PostHogIdentifier email={user.email!} />
<PostHogIdentifier email={user.email!} isEnabled={isFeatureEnabled(Feature.POSTHOG_IDENTIFY)} />
<WorkspacesNavbar />
<div className="flex flex-col flex-grow min-h-screen ml-64 overflow-auto">
<Header path="Projects" showSidebarTrigger={false} />
Expand Down
33 changes: 23 additions & 10 deletions frontend/app/providers.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,28 @@
// app/providers.tsx
'use client';
import posthog from 'posthog-js';
import { PostHogProvider } from 'posthog-js/react';

if (typeof window !== 'undefined') {
posthog.init('phc_dUMdjfNKf11jcHgtn7juSnT4P1pO0tafsPUWt4PuwG7', {
api_host: 'https://p.laminar.sh',
person_profiles: 'identified_only'
});
"use client";
import posthog, { PostHogConfig } from "posthog-js";
import { PostHogProvider } from "posthog-js/react";
import { PropsWithChildren } from "react";

if (typeof window !== "undefined") {
// Only initialize PostHog if telemetry is enabled
if (
process.env.NEXT_PUBLIC_ENABLE_TELEMETRY === "true" ||
process.env.NEXT_PUBLIC_ENABLE_PRODUCTION_TELEMETRY === "true"
) {
const config: Partial<PostHogConfig> = {
api_host: "https://p.laminar.sh",
person_profiles: "identified_only",
};

if (process.env.NEXT_PUBLIC_ENABLE_PRODUCTION_TELEMETRY !== "true") {
config.disable_session_recording = true;
}

posthog.init("phc_dUMdjfNKf11jcHgtn7juSnT4P1pO0tafsPUWt4PuwG7", config);
}
}

export function PHProvider({ children }: { children: React.ReactNode }) {
export function PHProvider({ children }: PropsWithChildren) {
return <PostHogProvider client={posthog}>{children}</PostHogProvider>;
}
2 changes: 1 addition & 1 deletion frontend/components/evaluations/evaluations.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ export default function Evaluations() {
}
};

if (isFeatureEnabled(Feature.POSTHOG)) {
if (isFeatureEnabled(Feature.POSTHOG_IDENTIFY)) {
posthog.identify(email);
}

Expand Down
2 changes: 1 addition & 1 deletion frontend/components/traces/trace-view/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -451,7 +451,7 @@ export default function TraceView({
if (typeof window !== "undefined") {
localStorage.setItem("trace-view:tree-view-width", treeViewWidth.toString());
}
} catch (e) {}
} catch (e) { }
}, [treeViewWidth]);

const isLoading = !trace || (isSpansLoading && isTraceLoading);
Expand Down
8 changes: 4 additions & 4 deletions frontend/components/traces/traces.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@ enum TracesTab {
type NavigationItem =
| string
| {
traceId: string;
spanId: string;
};
traceId: string;
spanId: string;
};

function TracesContent({ initialTraceViewWidth }: { initialTraceViewWidth?: number }) {
const searchParams = useSearchParams();
Expand All @@ -52,7 +52,7 @@ function TracesContent({ initialTraceViewWidth }: { initialTraceViewWidth?: numb
}
}, []);

if (isFeatureEnabled(Feature.POSTHOG)) {
if (isFeatureEnabled(Feature.POSTHOG_IDENTIFY)) {
posthog.identify(email);
}

Expand Down
12 changes: 8 additions & 4 deletions frontend/components/user/avatar-menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,14 @@ export default function AvatarMenu({ showDetails }: AvatarMenuProps) {
</div>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem onClick={() => {
posthog.reset();
signOut({ callbackUrl: "/" });
}}>Sign out</DropdownMenuItem>
<DropdownMenuItem
onClick={() => {
posthog.reset();
signOut({ callbackUrl: "/" });
}}
>
Sign out
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
);
Expand Down
6 changes: 6 additions & 0 deletions frontend/instrumentation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ export async function register() {
if (process.env.ENVIRONMENT === "PRODUCTION") {
registerOTel({ serviceName: "lmnr-web" });
}

if (process.env.NEXT_PUBLIC_ENABLE_TELEMETRY === "true") {
console.log(
"Telemetry is enabled. To opt out, set NEXT_PUBLIC_ENABLE_TELEMETRY=false in your .env file or remove the variable entirely."
);
}
// prevent this from running in the edge runtime for the second time
if (process.env.NEXT_RUNTIME === "nodejs") {
const { Feature, isFeatureEnabled } = await import("@/lib/features/features.ts");
Expand Down
30 changes: 24 additions & 6 deletions frontend/lib/features/features.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@ export const enum Feature {
WORKSPACE = "WORKSPACE",
SUPABASE = "SUPABASE",
POSTHOG = "POSTHOG",
POSTHOG_IDENTIFY = "POSTHOG_IDENTIFY",
POSTHOG_SESSION_RECORDING = "POSTHOG_SESSION_RECORDING",
LOCAL_DB = "LOCAL_DB",
FULL_BUILD = "FULL_BUILD",
SUBSCRIPTION = "SUBSCRIPTION",
LANDING = "LANDING"
LANDING = "LANDING",
}

// right now all managed-version features are disabled in local environment
Expand All @@ -32,7 +34,11 @@ export const isFeatureEnabled = (feature: Feature) => {
}

if (feature === Feature.AZURE_AUTH) {
return !!process.env.AUTH_AZURE_AD_CLIENT_ID && !!process.env.AUTH_AZURE_AD_CLIENT_SECRET && !!process.env.AUTH_AZURE_AD_TENANT_ID;
return (
!!process.env.AUTH_AZURE_AD_CLIENT_ID &&
!!process.env.AUTH_AZURE_AD_CLIENT_SECRET &&
!!process.env.AUTH_AZURE_AD_TENANT_ID
);
}

if (feature === Feature.FULL_BUILD) {
Expand All @@ -44,15 +50,27 @@ export const isFeatureEnabled = (feature: Feature) => {
}

if (feature === Feature.SUBSCRIPTION) {
return (
process.env.ENVIRONMENT === "PRODUCTION" &&
!!process.env.STRIPE_SECRET_KEY
);
return process.env.ENVIRONMENT === "PRODUCTION" && !!process.env.STRIPE_SECRET_KEY;
}

if (feature === Feature.SEND_EMAIL) {
return !!process.env.RESEND_API_KEY;
}

if (feature === Feature.POSTHOG) {
return (
process.env.NEXT_PUBLIC_ENABLE_TELEMETRY === "true" ||
process.env.NEXT_PUBLIC_ENABLE_PRODUCTION_TELEMETRY === "true"
);
}

if (feature === Feature.POSTHOG_IDENTIFY) {
return process.env.NEXT_PUBLIC_ENABLE_PRODUCTION_TELEMETRY === "true";
}

if (feature === Feature.POSTHOG_SESSION_RECORDING) {
return process.env.NEXT_PUBLIC_ENABLE_PRODUCTION_TELEMETRY === "true";
}

return process.env.ENVIRONMENT === "PRODUCTION";
};