Skip to content

Commit b6b10ea

Browse files
feat!: Support clicks and custom event tracing (#432)
## Summary Support tracing for the following product analytics events - Clicks - PageViews - LD.track Make sure to add the LD context to each of these traces <img width="736" height="575" alt="Screenshot 2026-03-30 at 12 26 56 PM" src="https://github.com/user-attachments/assets/71a916cd-e948-4ab4-a781-e258cd426cd0" /> Note: - Removes support for otel eventNames - Passes in full url for clicks to better support search ## How did you test this change? 1. Run the react router e2e app 2. Make sure the client is running - [ ] Clicks are recorded with correct info - [ ] PageViews are recorded with correct info - [ ] Custom events are recorded with correct info https://www.loom.com/share/e112659086f94a228279a97cc2b690b7 ## Are there any deployment considerations? Breaking change: removal of event_names <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Introduces new auto-instrumented spans for user interactions and navigation, and removes the prior `otel.eventNames` configuration (breaking API change). Risk is moderate because it changes tracing behavior and emitted telemetry volume/shape across consumers. > > **Overview** > **Adds product-analytics tracing controls and spans.** Introduces `productAnalytics` configuration (boolean or per-event toggles) to drive which product analytics events are traced (clicks, page views, and `LD.track`). > > **Reworks OTel instrumentation wiring.** Removes support for `otel.eventNames`, updates `UserInteractionInstrumentation` to emit click spans with full URL and attach LaunchDarkly context-key attributes, and adds new `LocationChangeInstrumentation` to emit `page_view` spans on URL changes (also enriched with LD context). > > **LaunchDarkly observe integration now correlates context + events.** Persists LD context key attributes after `identify` and uses them when generating `launchdarkly.track` spans; e2e React Router app adds new `LDClient` / `LDClient Lazy` test pages and updates staging endpoints to exercise these behaviors. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 9a1c887. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent de17cdf commit b6b10ea

16 files changed

Lines changed: 759 additions & 28 deletions

File tree

e2e/react-router/src/ldclient.tsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,21 +11,22 @@ const observabilitySettings: ConstructorParameters<typeof Observability>[0] = {
1111
recordHeadersAndBody: true,
1212
},
1313
serviceName: 'ryan-test',
14-
backendUrl: 'http://localhost:8082/public',
14+
backendUrl: 'https://pub.observability.ld-stg.launchdarkly.com/',
1515
otel: {
16-
otlpEndpoint: 'http://localhost:4318',
16+
otlpEndpoint: 'https://otel.observability.ld-stg.launchdarkly.com:4318',
1717
},
18+
productAnalytics: true,
1819
}
1920
const sessionReplaySettings: ConstructorParameters<typeof SessionReplay>[0] = {
2021
debug: { clientInteractions: true, domRecording: true },
2122
privacySetting: 'none',
2223
serviceName: 'ryan-test',
23-
backendUrl: 'http://localhost:8082/public',
24+
backendUrl: 'https://pub.observability.ld-stg.launchdarkly.com/',
2425
}
2526

2627
export const client = init(
2728
'548f6741c1efad40031b18ae',
28-
{ key: 'unknown' },
29+
{ kind: 'user', anonymous: true },
2930
{
3031
// Not including plugins at all would be equivalent to the current LaunchDarkly SDK.
3132
plugins: [

e2e/react-router/src/main.tsx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@ import Root from './routes/root'
1212
import Welcome from './routes/welcome'
1313
import PrivacyDemo from './routes/privacy-demo'
1414
import HttpTest from './routes/http-test'
15+
import LDClientPage, {
16+
LDClientPageA,
17+
LDClientPageB,
18+
LDClientPageC,
19+
} from './routes/ldclient'
20+
import LDClientLazyPage from './routes/ldclient-lazy'
1521

1622
function rootAction() {
1723
const contact = { name: 'hello' }
@@ -59,6 +65,12 @@ const router = createBrowserRouter(
5965
<Route path={'/welcome'} element={<Welcome />} />
6066
<Route path={'/privacy'} element={<PrivacyDemo />} />
6167
<Route path={'/http-test'} element={<HttpTest />} />
68+
<Route path={'/ldclient'} element={<LDClientPage />}>
69+
<Route path="page-a" element={<LDClientPageA />} />
70+
<Route path="page-b" element={<LDClientPageB />} />
71+
<Route path="page-c" element={<LDClientPageC />} />
72+
</Route>
73+
<Route path={'/ldclient-lazy'} element={<LDClientLazyPage />} />
6274
</>,
6375
),
6476
)
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
import { LDObserve } from '@launchdarkly/observability'
2+
import { LDRecord } from '@launchdarkly/session-replay'
3+
import { useState } from 'react'
4+
import { client, recordObservability, recordSession } from '../ldclientLazy'
5+
6+
export default function LDClientLazyPage() {
7+
const [flags, setFlags] = useState<string>()
8+
const [session, setSession] = useState<string>()
9+
const [started, setStarted] = useState(false)
10+
11+
return (
12+
<div id="ldclient-lazy-page">
13+
<h1>LDClient Lazy (Manual Start)</h1>
14+
<p>
15+
Uses <code>ldclientLazy.tsx</code> — plugins have{' '}
16+
<code>manualStart: true</code> and must be started explicitly.
17+
</p>
18+
<nav style={{ marginBottom: 16 }}>
19+
<a href="/">← Home</a>
20+
</nav>
21+
22+
<div
23+
style={{
24+
display: 'flex',
25+
flexDirection: 'column',
26+
gap: 8,
27+
maxWidth: 400,
28+
}}
29+
>
30+
<button
31+
onClick={async () => {
32+
await recordObservability()
33+
await recordSession()
34+
setStarted(true)
35+
}}
36+
>
37+
Start plugins (recordObservability + recordSession)
38+
</button>
39+
{started && <p style={{ color: 'green' }}>Plugins started</p>}
40+
41+
<button
42+
onClick={() => {
43+
const url = LDRecord.getSession()?.url
44+
setSession(url)
45+
}}
46+
>
47+
Get session URL
48+
</button>
49+
{session && (
50+
<a href={session} target="_blank" rel="noreferrer">
51+
{session}
52+
</a>
53+
)}
54+
55+
<button
56+
onClick={() => {
57+
LDObserve.recordLog(
58+
'hello from ldclient-lazy page',
59+
'info',
60+
)
61+
}}
62+
>
63+
LDObserve.recordLog
64+
</button>
65+
66+
<button
67+
onClick={() => {
68+
LDObserve.recordError(
69+
new Error('test error from ldclient-lazy page'),
70+
)
71+
}}
72+
>
73+
LDObserve.recordError
74+
</button>
75+
76+
<button
77+
onClick={async () => {
78+
await client.identify({
79+
kind: 'user',
80+
key: 'ldclient-lazy-test-user',
81+
})
82+
setFlags(JSON.stringify(client.allFlags(), null, 2))
83+
}}
84+
>
85+
client.identify
86+
</button>
87+
88+
<button
89+
onClick={() => {
90+
setFlags(
91+
JSON.stringify(
92+
client.variation('enable-session-card-style'),
93+
null,
94+
2,
95+
),
96+
)
97+
}}
98+
>
99+
client.variation
100+
</button>
101+
102+
<button
103+
onClick={() => {
104+
client.track('ldclient-lazy-page-custom-event', {
105+
random: Math.random(),
106+
})
107+
}}
108+
>
109+
client.track
110+
</button>
111+
112+
{flags && (
113+
<pre
114+
style={{
115+
background: '#f5f5f5',
116+
padding: 8,
117+
borderRadius: 4,
118+
}}
119+
>
120+
{flags}
121+
</pre>
122+
)}
123+
</div>
124+
</div>
125+
)
126+
}

0 commit comments

Comments
 (0)