mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
feat(telemetry): Sentry + PostHog quality pass (#546)
Comprehensive telemetry quality improvements across Sentry and PostHog, grounded in an audit of the live data plus current best-practice research. Sentry: job_id/instance_id tags, operational fingerprinting, PII-safe settings context on bug events, web tag population + extension-noise filtering, an early-crash buffer, http status/method kept on breadcrumbs, and a gated-off-by-default performance-tracing re-enable (tracesSampler that zeroes db/redis/queue-poll root spans + drops the Redis integration) with worker job spans and canonical-host cron monitors. PostHog: history_change SPA pageviews, instance_id super property for fleet rollups, enriched tool_used (formats, byte sizes, is_batch, execution_hint, real error_kind taxonomy), the previously-dead result_saved/batch_processed/ai_bundle_prompted events fired, search click-through, editor + Automate authoring + auth instrumentation, a before_send PII boundary, and minimal opt-in landing-site pageviews.
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { modalityForExtension, type PipelineTemplate } from "@snapotter/shared";
|
||||
import { ANALYTICS_EVENTS, modalityForExtension, type PipelineTemplate } from "@snapotter/shared";
|
||||
import {
|
||||
CheckCircle2,
|
||||
ChevronDown,
|
||||
@@ -69,6 +69,10 @@ function previewIcon(kind: string) {
|
||||
export function AutomatePage() {
|
||||
const { t } = useTranslation();
|
||||
usePageTitle(t.sidebar.automate);
|
||||
|
||||
useEffect(() => {
|
||||
import("@/lib/analytics").then(({ track }) => track(ANALYTICS_EVENTS.PIPELINE_OPENED, {}));
|
||||
}, []);
|
||||
const {
|
||||
files,
|
||||
entries,
|
||||
@@ -232,6 +236,9 @@ export function AutomatePage() {
|
||||
}),
|
||||
});
|
||||
if (res.ok) {
|
||||
import("@/lib/analytics").then(({ track }) =>
|
||||
track(ANALYTICS_EVENTS.PIPELINE_SAVED, { step_count: steps.length }),
|
||||
);
|
||||
const listRes = await fetch("/api/v1/pipeline/list", {
|
||||
headers: formatHeaders(),
|
||||
});
|
||||
@@ -281,6 +288,9 @@ export function AutomatePage() {
|
||||
const handleUseTemplate = useCallback(
|
||||
(template: PipelineTemplate) => {
|
||||
loadSteps(template.steps);
|
||||
import("@/lib/analytics").then(({ track }) =>
|
||||
track(ANALYTICS_EVENTS.PIPELINE_TEMPLATE_SELECTED, { template_id: template.id }),
|
||||
);
|
||||
},
|
||||
[loadSteps],
|
||||
);
|
||||
@@ -412,6 +422,9 @@ export function AutomatePage() {
|
||||
const handleAddStep = useCallback(
|
||||
(toolId: string) => {
|
||||
addStep(toolId);
|
||||
import("@/lib/analytics").then(({ track }) =>
|
||||
track(ANALYTICS_EVENTS.PIPELINE_STEP_ADDED, { tool_id: toolId }),
|
||||
);
|
||||
if (isMobile) setMobileToolPaletteOpen(false);
|
||||
},
|
||||
[addStep, isMobile],
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// apps/web/src/pages/editor-page.tsx
|
||||
import { apiToolPath } from "@snapotter/shared";
|
||||
import { ANALYTICS_EVENTS, apiToolPath } from "@snapotter/shared";
|
||||
import { Monitor } from "lucide-react";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { CanvasResizeDialog } from "@/components/editor/common/canvas-resize-dialog";
|
||||
@@ -52,6 +52,10 @@ export function EditorPage() {
|
||||
onFillDialog: () => setFillDialogOpen(true),
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
import("@/lib/analytics").then(({ track }) => track(ANALYTICS_EVENTS.EDITOR_OPENED, {}));
|
||||
}, []);
|
||||
|
||||
// Listen for fill-dialog custom event (dispatched from Shift+Backspace shortcut)
|
||||
useEffect(() => {
|
||||
const handler = () => setFillDialogOpen(true);
|
||||
|
||||
@@ -403,7 +403,22 @@ function SearchResults({
|
||||
<div className="space-y-4">
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-2">
|
||||
{results.map((tool) => (
|
||||
<ToolCard key={tool.id} tool={tool} variant="descriptive" showModalityBadge showPin />
|
||||
<ToolCard
|
||||
key={tool.id}
|
||||
tool={tool}
|
||||
variant="descriptive"
|
||||
showModalityBadge
|
||||
showPin
|
||||
onNavigate={() => {
|
||||
// search -> click attribution: which result the user opened.
|
||||
import("@/lib/analytics").then(({ track }) =>
|
||||
track(ANALYTICS_EVENTS.SEARCH, {
|
||||
results_count: results.length,
|
||||
clicked_tool_id: tool.id,
|
||||
}),
|
||||
);
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<div className="pt-1 text-center">
|
||||
|
||||
@@ -268,7 +268,11 @@ export function ToolPage() {
|
||||
useEffect(() => {
|
||||
if (tool) {
|
||||
recordRecentTool(tool.id);
|
||||
import("@/lib/analytics").then(({ track }) => {
|
||||
import("@/lib/analytics").then(({ track, setSentryTag }) => {
|
||||
// Tag Sentry so a frontend crash is filterable by which tool/section the
|
||||
// user was on (the web TAG_ALLOWLIST reserves these but nothing set them).
|
||||
setSentryTag("tool_id", tool.id);
|
||||
setSentryTag("route", toolSection(tool));
|
||||
track(ANALYTICS_EVENTS.TOOL_OPENED, {
|
||||
tool_id: tool.id,
|
||||
modality: tool.modality,
|
||||
|
||||
Reference in New Issue
Block a user