-
[OPIK-5890] [BE][FE] feat: add optimization flow analytics events (#6356)
发布于
2026-04-20 13:41:28 +00:00 - [OPIK-5890] [BE][FE] feat: add optimization flow analytics events
Add PostHog instrumentation for the Optimization flow to track launch
metrics defined in epic OPIK-5245.Backend (2 events via AnalyticsService in OptimizationService):
- opik_optimization_created: fires on upsert, captures UI + SDK paths
- opik_optimization_completed: fires on terminal status transition,
includes num_trials, baseline/best scores for improvement delta
Frontend (1 event via trackEvent):
- opik_optimization_wizard_started: fires on wizard page mount (v2 only)
Implements OPIK-5890: Optimization Flow Analytics Events
- fix(analytics): guard created event for new optimizations only, add opik_ prefix
- Guard trackOptimizationCreated with existingOpt.isEmpty() to prevent
double-counting on re-saves/upserts of existing optimizations - Add explicit opik_ prefix to event names matching convention from
sibling PRs (OPIK-5691, OPIK-5692)
- fix(analytics): match new trackEvent signature, guard completed event on transition, thread user identity
Addresses review on PR #6356:
- Reorder trackOptimizationCreated args to (eventType, properties, identity) after #6362
- Only emit opik_optimization_completed on the transition into a terminal state
- Thread userName through trackOptimizationCompleted to preserve attribution on scheduler threads; fall back to resolveIdentity() on the internal cancelOptimization() path where USER_NAME is absent
- fix(analytics): re-fetch optimization post-update so completed event carries fresh metrics
Addresses review on PR #6356 — numTrials, baselineObjectiveScore, and bestObjectiveScore
are computed by query-time aggregation over trials/scores; the pre-update snapshot can be
stale versus the state observed right after the terminal status write. Re-read the row
after the update and emit the event from the refreshed entity.- fix(analytics): offload optimization track calls to boundedElastic
Per PR #6356 review — trackOptimizationCreated/Completed fire from reactive
doOnSuccess/doOnNext callbacks. AnalyticsService.resolveIdentity() falls back to
usageReportService.getAnonymousId(), a synchronous JDBC read, when identity is null
(reachable via the internal cancelOptimization() path). Wrap with
Schedulers.boundedElastic().schedule(...) so a blocking fallback can't stall the
reactive thread.- refactor(analytics): inline track calls and drop post-update re-fetch
Addresses andrescrz on PR #6356:
- Drop the optimizationDAO.getById re-fetch after update — ClickHouse replication means the read can land on a replica that hasn't seen the write yet, producing stale data or a spurious NotFound. Use the pre-update snapshot; analytics metric drift here is acceptable and doesn't justify the extra latency or error surface on the request path.
- Inline trackOptimizationCreated/trackOptimizationCompleted at the two call sites and remove the helpers — they no longer add value over a direct analyticsService.trackEvent call.
- docs(skill): codify backend analytics patterns surfaced in PR #6356 review
- Document both trackEvent overloads and when to pick the 3-arg identity variant
- Add canonical reactive-chain recipe: Schedulers.boundedElastic() wrap + Mono.deferContextual identity capture, with the "why" for each
- Call out the getOrDefault(USER_NAME, null) fallback for internal, system-triggered paths
- Add a Don'ts list: no try/catch, no trivial helper delegates, no ClickHouse read-after-write for analytics, no emission unit tests, don't trust the non-blocking contract
下载附件