Skip to content

Commit ec6395b

Browse files
feat(telemetry): EmptyState component (#1264)
Saw that we created an empty state in Tracelist, so just made that empty state generic and applied on every page. 2 variants - Simple - one-liner simple empty state - full - with title, description --------- Co-authored-by: mathuraditya724 <mathuraditya724@gmail.com>
1 parent d0b389c commit ec6395b

File tree

18 files changed

+302
-189
lines changed

18 files changed

+302
-189
lines changed

packages/spotlight/src/ui/telemetry/components/events/EventDetails.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import type { SentryErrorEvent, SentryEvent } from "../../types";
44
import { isErrorEvent } from "../../utils/sentry";
55
import { createTab } from "../../utils/tabs";
66
import TelemetryTabs from "../TelemetryTabs";
7+
import EmptyState from "../shared/EmptyState";
78
import PlatformIcon from "../shared/PlatformIcon";
89
import AICopyButton from "./AICopyButton";
910
import Event, { EventTitle } from "./Event";
@@ -19,13 +20,13 @@ export default function EventDetails() {
1920
const getEventById = useSentryStore(state => state.getEventById);
2021

2122
if (!eventId) {
22-
return <p className="text-primary-300 p-6">Unknown event id</p>;
23+
return <EmptyState description="Unknown event id." />;
2324
}
2425

2526
const event = getEventById(eventId);
2627

2728
if (!event) {
28-
return <p className="text-primary-300 p-6">Event not found.</p>;
29+
return <EmptyState description="Event not found." />;
2930
}
3031

3132
const tabs = [

packages/spotlight/src/ui/telemetry/components/events/EventList.tsx

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { Link } from "react-router-dom";
55
import { useSentryEvents } from "../../data/useSentryEvents";
66
import { isErrorEvent } from "../../utils/sentry";
77
import { truncateId } from "../../utils/text";
8+
import EmptyState from "../shared/EmptyState";
89
import PlatformIcon from "../shared/PlatformIcon";
910
import { EventSummary } from "./Event";
1011

@@ -39,6 +40,16 @@ export default function EventList({ traceId }: { traceId?: string }) {
3940
})}
4041
</CardList>
4142
) : (
42-
<div className="text-primary-300 p-6">Looks like there's no events recorded matching this query. 🤔</div>
43+
<EmptyState
44+
variant={!traceId ? "full" : "simple"}
45+
className={!traceId ? "h-full" : undefined}
46+
title={!traceId ? "No Errors" : undefined}
47+
description={
48+
!traceId
49+
? "No errors captured yet. That's either very good news or your SDK isn't set up."
50+
: "No errors in this trace."
51+
}
52+
showDocsLink={!traceId}
53+
/>
4354
);
4455
}

packages/spotlight/src/ui/telemetry/components/insights/Profiles.tsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import useSort from "../../hooks/useSort";
88
import useSentryStore from "../../store";
99
import type { AggregateCallData } from "../../types";
1010
import { getFormattedDuration, getSpanDurationClassName } from "../../utils/duration";
11+
import EmptyState from "../shared/EmptyState";
1112
import { TimeBar } from "../shared/TimeBar";
1213

1314
type AggregateCallProfileComparator = (a: AggregateCallData, b: AggregateCallData) => number;
@@ -38,7 +39,15 @@ function Profiles() {
3839
}, [sort]);
3940

4041
if (!aggregateCallData.length) {
41-
return <p className="text-primary-300 px-6 py-4">No profiles found.</p>;
42+
return (
43+
<EmptyState
44+
variant="full"
45+
className="h-full"
46+
title="No Profiles"
47+
description="No profiles recorded. Enable profiling in your Sentry SDK to see function-level performance."
48+
showDocsLink
49+
/>
50+
);
4251
}
4352

4453
// Calculate max time for bar visualization (100%, scaling form here)

packages/spotlight/src/ui/telemetry/components/insights/Queries.tsx

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { getFormattedDuration, getSpanDurationClassName } from "@spotlight/ui/te
1010
import Table from "@spotlight/ui/ui/table";
1111
import { useMemo } from "react";
1212
import { useNavigate } from "react-router-dom";
13+
import EmptyState from "../shared/EmptyState";
1314

1415
type QueryInfo = {
1516
avgDuration: number;
@@ -67,9 +68,13 @@ const Queries = () => {
6768

6869
if (!queriesData?.length) {
6970
return (
70-
<p className="text-primary-300 px-6 py-4">
71-
No Database queries found. Add integration in Sentry initialization to track Database queries.
72-
</p>
71+
<EmptyState
72+
variant="full"
73+
className="h-full"
74+
title="No Database Queries"
75+
description="No queries captured. Add a database integration to your Sentry setup to track them."
76+
showDocsLink
77+
/>
7378
);
7479
}
7580

packages/spotlight/src/ui/telemetry/components/insights/QuerySummary.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import useSort from "../../hooks/useSort";
1212
import type { Span } from "../../types";
1313
import { getFormattedDuration, getSpanDurationClassName } from "../../utils/duration";
1414
import { truncateId } from "../../utils/text";
15+
import EmptyState from "../shared/EmptyState";
1516
import { TimeBar } from "../shared/TimeBar";
1617

1718
type SpanInfoComparator = (a: Span, b: Span) => number;
@@ -53,7 +54,7 @@ const QuerySummary = () => {
5354
}, [allSpans, sort, decodedType]);
5455

5556
if (!filteredDBSpans || !filteredDBSpans.length) {
56-
return <p className="text-primary-300 px-6 py-4">Query not found.</p>;
57+
return <EmptyState description="Query not found." />;
5758
}
5859

5960
const maxTime = Math.max(...filteredDBSpans.map(dbSpan => dbSpan.timestamp - dbSpan.start_timestamp));

packages/spotlight/src/ui/telemetry/components/insights/Resources.tsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import useSort from "../../hooks/useSort";
99
import type { Span } from "../../types";
1010
import { formatBytes } from "../../utils/bytes";
1111
import { getFormattedDuration, getSpanDurationClassName } from "../../utils/duration";
12+
import EmptyState from "../shared/EmptyState";
1213

1314
type ResourceInfo = {
1415
avgDuration: number;
@@ -87,7 +88,15 @@ const Resources = () => {
8788
}, [sort, allSpans]);
8889

8990
if (!resources?.length) {
90-
return <p className="text-primary-300 px-6 py-4">No Resource found.</p>;
91+
return (
92+
<EmptyState
93+
variant="full"
94+
className="h-full"
95+
title="No Resources"
96+
description="No resources tracked. Enable browser tracing to see asset loading performance."
97+
showDocsLink
98+
/>
99+
);
91100
}
92101
return (
93102
<Table variant="detail">

packages/spotlight/src/ui/telemetry/components/insights/aiTraces/AITraceList.tsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { SearchProvider } from "../../../context/SearchContext";
1010
import { useSpotlightAITraces } from "../../../data/useSpotlightAITraces";
1111
import useSort from "../../../hooks/useSort";
1212
import useSentryStore from "../../../store";
13+
import EmptyState from "../../shared/EmptyState";
1314
import AITraceDetail from "./AITraceDetails";
1415
import AITraceItem from "./AITraceItem";
1516

@@ -51,7 +52,15 @@ export default function AITraceList() {
5152
const selectedRawSpan = spanId ? traceContext?.spans.get(spanId) : null;
5253

5354
if (spotlightAITraces.length === 0) {
54-
return <div className="text-primary-300 p-6">No AI traces have been recorded yet. 🤔</div>;
55+
return (
56+
<EmptyState
57+
variant="full"
58+
className="h-full"
59+
title="No AI Traces"
60+
description="No AI traces recorded. Instrument your LLM calls with Sentry to see them here."
61+
showDocsLink
62+
/>
63+
);
5564
}
5665

5766
const handleTraceClick = (trace: SpotlightAITrace) => {

packages/spotlight/src/ui/telemetry/components/insights/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import WebVitalsDetail from "./webVitals/WebVitalsDetail";
1212
export default function InsightsTabDetails() {
1313
return (
1414
<>
15-
<div className="flex min-h-0 flex-1 flex-col overflow-x-hidden overflow-y-auto">
15+
<div className="flex min-h-0 flex-1 flex-col overflow-x-hidden overflow-y-auto h-full">
1616
<Routes>
1717
<Route path="queries" element={<Queries />} />
1818
<Route path="queries/:type" element={<QuerySummary />} />

packages/spotlight/src/ui/telemetry/components/insights/sdks/SdkList.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import CardList from "@spotlight/ui/telemetry/components/shared/CardList";
22
import TimeSince from "@spotlight/ui/telemetry/components/shared/TimeSince";
33
import { useSentrySdks } from "@spotlight/ui/telemetry/data/useSentrySdks";
44
import { sdkToPlatform } from "@spotlight/ui/telemetry/utils/sdkToPlatform";
5+
import EmptyState from "../../shared/EmptyState";
56
import PlatformIcon from "../../shared/PlatformIcon";
67

78
export default function SdkList() {
@@ -24,7 +25,13 @@ export default function SdkList() {
2425
))}
2526
</CardList>
2627
) : (
27-
<div className="text-primary-300 px-6 py-4">Looks like there's no SDKs that have reported yet. 🤔</div>
28+
<EmptyState
29+
variant="full"
30+
className="h-full"
31+
title="No SDKs"
32+
description="No SDKs detected yet. Once Sentry sends data, we'll show what's running."
33+
showDocsLink
34+
/>
2835
)}
2936
</>
3037
);

packages/spotlight/src/ui/telemetry/components/insights/webVitals/WebVitalsDetail.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,13 @@ import { getFormattedDuration } from "@spotlight/ui/telemetry/utils/duration";
99
import Breadcrumbs from "@spotlight/ui/ui/breadcrumbs";
1010
import { useParams } from "react-router-dom";
1111
import { normalizePerformanceScore } from "../../../utils/webVitals";
12+
import EmptyState from "../../shared/EmptyState";
1213
import PerformanceChart from "./PerformanceChart";
1314

1415
const WebVitalsDetail = () => {
1516
const events = useSentryEvents();
1617
const { page } = useParams();
17-
18+
1819
let measurementEvent: SentryEventWithPerformanceData | undefined;
1920
for (const event of events) {
2021
if (event.event_id === page && event.measurements && event?.contexts?.trace?.op === "pageload") {
@@ -118,7 +119,7 @@ const WebVitalsDetail = () => {
118119
</>
119120
);
120121
}
121-
return <p className="text-primary-300 px-6 py-4">No measurement found.</p>;
122+
return <EmptyState description="No measurement found." />;
122123
};
123124

124125
export default WebVitalsDetail;

0 commit comments

Comments
 (0)