feat(frontend): Enhance Sentry integration and TallyPopup telemetry (#10862)

Added Sentry captureConsoleIntegration and extraErrorDataIntegration to
client, edge, and server configs. Improved replay integration with
unmasking support. Updated TallyPopup to collect and expose Sentry
replay data, user agent, and page URL for enhanced telemetry and
debugging. Improved event handling and error logging for Tally events.
Marked CustomNode title for Sentry unmasking.<!-- Clearly explain the
need for these changes: -->

### Changes 🏗️
Reconfigure sentry
Pass the id with sentry replay to tally alongside prefilling email, and
passing non user identifying attributes like platform url, full url, and
is authenticated.
<!-- Concisely describe all of the changes made in this pull request:
-->

### Checklist 📋

#### For code changes:
- [x] I have clearly listed my changes in the PR description
- [x] I have made a test plan
- [x] I have tested my changes according to the test plan:
  <!-- Put your test plan here: -->
  - [x] Test the results show up in sentry
  - [x] Test the url works in tally
This commit is contained in:
Nicholas Tindle
2025-09-30 22:00:20 -05:00
committed by GitHub
parent f33ec1f2ec
commit aad0434cb2
6 changed files with 89 additions and 5 deletions

View File

@@ -4,6 +4,7 @@ from enum import Enum
import sentry_sdk
from pydantic import SecretStr
from sentry_sdk.integrations.anthropic import AnthropicIntegration
from sentry_sdk.integrations.asyncio import AsyncioIntegration
from sentry_sdk.integrations.logging import LoggingIntegration
from backend.util.settings import Settings
@@ -25,6 +26,7 @@ def sentry_init():
environment=f"app:{settings.config.app_env.value}-behave:{settings.config.behave_as.value}",
_experiments={"enable_logs": True},
integrations=[
AsyncioIntegration(),
LoggingIntegration(sentry_logs_level=logging.INFO),
AnthropicIntegration(
include_prompts=False,

View File

@@ -27,11 +27,16 @@ Sentry.init({
// Add optional integrations for additional features
integrations: [
Sentry.replayIntegration(),
Sentry.captureConsoleIntegration(),
Sentry.extraErrorDataIntegration(),
Sentry.browserProfilingIntegration(),
Sentry.httpClientIntegration(),
// Sentry.launchDarklyIntegration(),
Sentry.replayIntegration({
unmask: [".sentry-unmask, [data-sentry-unmask]"],
}),
Sentry.replayCanvasIntegration(),
Sentry.reportingObserverIntegration(),
Sentry.browserProfilingIntegration(),
// Sentry.feedbackIntegration({
// // Additional SDK configuration goes in here, for example:
// colorScheme: "system",

View File

@@ -39,4 +39,8 @@ Sentry.init({
debug: false,
enableLogs: true,
integrations: [
Sentry.captureConsoleIntegration(),
Sentry.extraErrorDataIntegration(),
],
});

View File

@@ -42,6 +42,7 @@ Sentry.init({
integrations: [
Sentry.anrIntegration(),
// NodeProfilingIntegration,
Sentry.extraErrorDataIntegration(),
// Sentry.fsIntegration(),
],

View File

@@ -828,7 +828,7 @@ export const CustomNode = React.memo(
) : (
<Tooltip>
<TooltipTrigger asChild>
<h3 className="font-roboto cursor-default text-lg">
<h3 className="font-roboto sentry-unmask cursor-default text-lg">
<TextRenderer
value={displayTitle}
truncateLengthLimit={80}

View File

@@ -4,9 +4,18 @@ import React, { useEffect, useState } from "react";
import { Button } from "../../__legacy__/ui/button";
import { QuestionMarkCircledIcon } from "@radix-ui/react-icons";
import { useRouter, usePathname } from "next/navigation";
import * as Sentry from "@sentry/nextjs";
import { getCurrentUser } from "@/lib/supabase/actions";
const TallyPopupSimple = () => {
const [isFormVisible, setIsFormVisible] = useState(false);
const [sentryReplayId, setSentryReplayId] = useState("");
const [replayUrl, setReplayUrl] = useState("");
const [pageUrl, setPageUrl] = useState("");
const [userAgent, setUserAgent] = useState("");
const [isAuthenticated, setIsAuthenticated] = useState<boolean | null>(null);
// const [userId, setUserId] = useState<string>("");
const [userEmail, setUserEmail] = useState<string>("");
const router = useRouter();
const pathname = usePathname();
@@ -16,6 +25,35 @@ const TallyPopupSimple = () => {
setShowTutorial(pathname.includes("build"));
}, [pathname]);
useEffect(() => {
// Set client-side values
if (typeof window !== "undefined") {
setPageUrl(window.location.href);
setUserAgent(window.navigator.userAgent);
const replay = Sentry.getReplay();
if (replay) {
const replayId = replay.getReplayId();
if (replayId) {
setSentryReplayId(replayId);
const orgSlug = "significant-gravitas";
setReplayUrl(`https://${orgSlug}.sentry.io/replays/${replayId}/`);
}
}
}
}, [pathname]);
useEffect(() => {
// Check authentication status using server action (works with httpOnly cookies)
getCurrentUser().then(({ user }) => {
setIsAuthenticated(user != null);
// setUserId(user?.id || "");
setUserEmail(user?.email || "");
});
}, [pathname]);
useEffect(() => {
// Load Tally script
const script = document.createElement("script");
@@ -26,15 +64,40 @@ const TallyPopupSimple = () => {
// Setup event listeners for Tally events
const handleTallyMessage = (event: MessageEvent) => {
if (typeof event.data === "string") {
// Ignore iframe-resizer messages
if (
event.data.startsWith("[iFrameSize") ||
event.data.startsWith("[iFrameResizer")
) {
return;
}
try {
const data = JSON.parse(event.data);
// Only process Tally events
if (!data.event?.startsWith("Tally.")) {
return;
}
if (data.event === "Tally.FormLoaded") {
setIsFormVisible(true);
// Flush Sentry replay when form opens
if (typeof window !== "undefined") {
const replay = Sentry.getReplay();
if (replay) {
replay.flush();
}
}
} else if (data.event === "Tally.PopupClosed") {
setIsFormVisible(false);
}
} catch (error) {
console.error("Error parsing Tally message:", error);
// Only log errors for messages we care about
if (event.data.includes("Tally")) {
console.error("Error parsing Tally message:", error);
}
}
}
};
@@ -48,7 +111,7 @@ const TallyPopupSimple = () => {
}, []);
if (isFormVisible) {
return null; // Hide the button when the form is visible
return null;
}
const resetTutorial = () => {
@@ -72,6 +135,15 @@ const TallyPopupSimple = () => {
data-tally-open="3yx2L0"
data-tally-emoji-text="👋"
data-tally-emoji-animation="wave"
data-sentry-replay-id={sentryReplayId || "not-initialized"}
data-sentry-replay-url={replayUrl || "not-initialized"}
data-user-agent={userAgent}
data-page-url={pageUrl}
data-is-authenticated={
isAuthenticated === null ? "unknown" : String(isAuthenticated)
}
data-email={userEmail || "not-authenticated"}
// data-user-id={userId || "not-authenticated"}
>
<QuestionMarkCircledIcon className="h-14 w-14" />
<span className="sr-only">Reach Out</span>