Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions apps/local/.oxlintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"rules": {
"react/rules-of-hooks": "error",
"typescript/return-await": ["error", "always"],
"typescript/no-unnecessary-condition": "error",
"react/only-export-components": ["warn", { "allowConstantExport": true }]
}
}
2 changes: 1 addition & 1 deletion apps/local/scripts/replay.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ async function main() {
repeat: Number(values.repeat),
onSent: (file, index) =>
console.log(
`${index}. ${file.slice(dirname(resolve(values.fixtures ?? positionals[0] ?? defaultFixtures)).length + 1)}`,
`${index}. ${file.slice(dirname(resolve(values.fixtures ?? positionals.at(0) ?? defaultFixtures)).length + 1)}`,
),
});
console.log(`Sent ${count} envelope${count === 1 ? "" : "s"}.`);
Expand Down
81 changes: 41 additions & 40 deletions apps/local/src/App.integration.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -771,8 +771,9 @@ describe("local receiver to viewer integration", () => {

test("replays bundled envelopes through the receiver into every investigation view", async () => {
const { server, port } = await startReceiver();
const originalCreate = URL.createObjectURL?.bind(URL);
const originalRevoke = URL.revokeObjectURL?.bind(URL);
const urlStatics: Partial<typeof URL> = URL;
const originalCreate = urlStatics.createObjectURL?.bind(URL);
const originalRevoke = urlStatics.revokeObjectURL?.bind(URL);
URL.createObjectURL = () => "blob:fixture";
URL.revokeObjectURL = () => {};
try {
Expand Down Expand Up @@ -842,8 +843,8 @@ describe("local receiver to viewer integration", () => {
expect(await screen.findByText("sentry.javascript.node")).toBeDefined();
} finally {
cleanup();
URL.createObjectURL = originalCreate;
URL.revokeObjectURL = originalRevoke;
urlStatics.createObjectURL = originalCreate;
urlStatics.revokeObjectURL = originalRevoke;
await stopReceiver(server);
}
}, 15_000);
Expand Down Expand Up @@ -1366,7 +1367,7 @@ describe("local receiver to viewer integration", () => {

const source = ControllableEventSource.instances[0];
expect(source).toBeDefined();
await act(async () => source?.onerror?.(new Event("error")));
await act(async () => source.onerror?.(new Event("error")));
await screen.findByRole("alert", { name: "Receiver connection error" });

fireEvent.change(screen.getByLabelText("Receiver endpoint"), {
Expand All @@ -1377,7 +1378,7 @@ describe("local receiver to viewer integration", () => {
"Enter a loopback stream or an HTTPS remote stream",
);

await act(async () => source?.onerror?.(new Event("error")));
await act(async () => source.onerror?.(new Event("error")));
expect(screen.getByRole("alert").textContent).toContain(
"Enter a loopback stream or an HTTPS remote stream",
);
Expand All @@ -1393,7 +1394,7 @@ describe("local receiver to viewer integration", () => {

const initialSource = ControllableEventSource.instances[0];
expect(initialSource).toBeDefined();
await act(async () => initialSource?.onerror?.(new Event("error")));
await act(async () => initialSource.onerror?.(new Event("error")));
fireEvent.change(screen.getByLabelText("Receiver endpoint"), {
target: { value: "http://127.0.0.1:8970/stream" },
});
Expand All @@ -1411,7 +1412,7 @@ describe("local receiver to viewer integration", () => {
vi.advanceTimersByTime(10_000);
});

expect(source?.closed).toBe(true);
expect(source.closed).toBe(true);
expect(
screen.getByRole("alert", { name: "Receiver connection error" })
.textContent,
Expand All @@ -1433,12 +1434,12 @@ describe("local receiver to viewer integration", () => {

const source = ControllableEventSource.instances[0];
expect(source).toBeDefined();
await act(async () => source?.onopen?.(new Event("open")));
await act(async () => source.onopen?.(new Event("open")));
await screen.findByText("Connected to local receiver");
fireEvent.click(screen.getByRole("button", { name: "Switch receiver" }));

await act(async () => {
source?.dispatchEvent(
source.dispatchEvent(
new MessageEvent(SENTRY_CONTENT_TYPE, {
data: JSON.stringify([
{},
Expand All @@ -1463,7 +1464,7 @@ describe("local receiver to viewer integration", () => {

const source = ControllableEventSource.instances[0];
expect(source).toBeDefined();
await act(async () => source?.onopen?.(new Event("open")));
await act(async () => source.onopen?.(new Event("open")));
await screen.findByText("Connected to local receiver");

fireEvent.click(screen.getByRole("button", { name: "Switch receiver" }));
Expand All @@ -1485,15 +1486,15 @@ describe("local receiver to viewer integration", () => {

const source = ControllableEventSource.instances[0];
expect(source).toBeDefined();
await act(async () => source?.onopen?.(new Event("open")));
await act(async () => source.onopen?.(new Event("open")));
await screen.findByText("Connected to local receiver");
fireEvent.click(screen.getByRole("button", { name: "Switch receiver" }));
fireEvent.change(screen.getByLabelText("Receiver endpoint"), {
target: { value: "http://receiver.example/stream" },
});
fireEvent.click(screen.getByRole("button", { name: "Connect" }));

expect(source?.closed).toBe(false);
expect(source.closed).toBe(false);
expect(
screen.getAllByText("Connected to local receiver", { exact: true })[0],
).not.toBeNull();
Expand Down Expand Up @@ -1698,11 +1699,11 @@ describe("local receiver to viewer integration", () => {

const source = ControllableEventSource.instances[0];
expect(source).toBeDefined();
await act(async () => source?.onopen?.(new Event("open")));
await act(async () => source.onopen?.(new Event("open")));
await screen.findByText("Connected to local receiver");

await act(async () => {
source?.dispatchEvent(
source.dispatchEvent(
new MessageEvent(SENTRY_CONTENT_TYPE, {
data: JSON.stringify([{}, []]),
lastEventId: "raw-only-envelope",
Expand All @@ -1726,8 +1727,8 @@ describe("local receiver to viewer integration", () => {
renderBareViewer();
const source = ControllableEventSource.instances[0];
await act(async () => {
source?.onopen?.(new Event("open"));
source?.dispatchEvent(
source.onopen?.(new Event("open"));
source.dispatchEvent(
new MessageEvent(SENTRY_CONTENT_TYPE, {
data: "undecodable",
lastEventId: "raw-broken",
Expand Down Expand Up @@ -1846,15 +1847,15 @@ describe("local receiver to viewer integration", () => {
vi.stubGlobal("fetch", fetchMock);
renderBareViewer();
const source = ControllableEventSource.instances[0];
await act(async () => source?.onopen?.(new Event("open")));
await act(async () => source.onopen?.(new Event("open")));
await act(async () => {
source?.dispatchEvent(
source.dispatchEvent(
new MessageEvent("sentry.local.retention", {
data: JSON.stringify({ maxEnvelopes: 500 }),
}),
);
for (const id of ["one", "two"])
source?.dispatchEvent(
source.dispatchEvent(
new MessageEvent(SENTRY_CONTENT_TYPE, {
data: JSON.stringify([
{},
Expand Down Expand Up @@ -1885,7 +1886,7 @@ describe("local receiver to viewer integration", () => {
);
await waitFor(() => expect(fetchMock).toHaveBeenCalledTimes(1));
await act(async () =>
source?.dispatchEvent(
source.dispatchEvent(
new MessageEvent("sentry.local.retention", {
data: JSON.stringify({ maxEnvelopes: 1000 }),
}),
Expand All @@ -1909,7 +1910,7 @@ describe("local receiver to viewer integration", () => {
expect(
screen.getAllByRole("button", { name: "View event event" }),
).toHaveLength(2);
expect(source?.closed).toBe(false);
expect(source.closed).toBe(false);
expect(ControllableEventSource.instances).toHaveLength(1);
});

Expand All @@ -1919,13 +1920,13 @@ describe("local receiver to viewer integration", () => {
renderBareViewer();
const source = ControllableEventSource.instances[0];
await act(async () => {
source?.onopen?.(new Event("open"));
source?.dispatchEvent(
source.onopen?.(new Event("open"));
source.dispatchEvent(
new MessageEvent("sentry.local.retention", {
data: '{"maxEnvelopes":1000}',
}),
);
source?.dispatchEvent(
source.dispatchEvent(
new MessageEvent(SENTRY_CONTENT_TYPE, {
data: JSON.stringify([
{},
Expand All @@ -1934,7 +1935,7 @@ describe("local receiver to viewer integration", () => {
lastEventId: "kept",
}),
);
source?.onerror?.(new Event("error"));
source.onerror?.(new Event("error"));
});
fireEvent.click(
await screen.findByRole("button", { name: "Settings", exact: true }),
Expand Down Expand Up @@ -2013,8 +2014,8 @@ describe("local receiver to viewer integration", () => {
renderBareViewer();
let source = ControllableEventSource.instances[0];
await act(async () => {
source?.onopen?.(new Event("open"));
source?.dispatchEvent(
source.onopen?.(new Event("open"));
source.dispatchEvent(
new MessageEvent("sentry.local.retention", {
data: '{"maxEnvelopes":500}',
}),
Expand Down Expand Up @@ -2044,8 +2045,8 @@ describe("local receiver to viewer integration", () => {
source = await openLatestReceiver();
}
await act(async () => {
source?.onopen?.(new Event("open"));
source?.dispatchEvent(
source.onopen?.(new Event("open"));
source.dispatchEvent(
new MessageEvent("sentry.local.retention", {
data: '{"maxEnvelopes":1000}',
}),
Expand All @@ -2067,7 +2068,7 @@ describe("local receiver to viewer integration", () => {
);
await act(async () => {
for (const id of ["one", "two"])
source?.dispatchEvent(
source.dispatchEvent(
new MessageEvent(SENTRY_CONTENT_TYPE, {
data: JSON.stringify([
{},
Expand Down Expand Up @@ -2222,7 +2223,7 @@ describe("local receiver to viewer integration", () => {

const initialSource = ControllableEventSource.instances[0];
expect(initialSource).toBeDefined();
await act(async () => initialSource?.onopen?.(new Event("open")));
await act(async () => initialSource.onopen?.(new Event("open")));
fireEvent.click(screen.getByRole("button", { name: "Switch receiver" }));

const streamUrl =
Expand All @@ -2234,7 +2235,7 @@ describe("local receiver to viewer integration", () => {

const scopedSource = await openLatestReceiver();
await act(async () => {
scopedSource?.dispatchEvent(
scopedSource.dispatchEvent(
new MessageEvent(SENTRY_CONTENT_TYPE, {
data: JSON.stringify([{}, []]),
lastEventId: "scoped-envelope",
Expand Down Expand Up @@ -2280,7 +2281,7 @@ describe("local receiver to viewer integration", () => {
signal: expect.any(AbortSignal),
}),
);
await waitFor(() => expect(scopedSource?.closed).toBe(true));
await waitFor(() => expect(scopedSource.closed).toBe(true));
await screen.findByText("Saved capture", { exact: true });
expect(
screen.queryByText(
Expand Down Expand Up @@ -2313,11 +2314,11 @@ describe("local receiver to viewer integration", () => {

const source = ControllableEventSource.instances[0];
expect(source).toBeDefined();
await act(async () => source?.onopen?.(new Event("open")));
await act(async () => source.onopen?.(new Event("open")));
await screen.findByText("Connected to local receiver");

await act(async () => {
source?.dispatchEvent(
source.dispatchEvent(
new MessageEvent(SENTRY_CONTENT_TYPE, {
data: JSON.stringify([{}, []]),
lastEventId: "raw-envelope-before-failure",
Expand All @@ -2327,7 +2328,7 @@ describe("local receiver to viewer integration", () => {
fireEvent.click(viewButton("Open envelopes view"));
fireEvent.click(await screen.findByLabelText("View envelope event"));

await act(async () => source?.onerror?.(new Event("error")));
await act(async () => source.onerror?.(new Event("error")));

expect(screen.getByLabelText("Workspace navigation")).not.toBeNull();
expect(
Expand Down Expand Up @@ -2374,7 +2375,7 @@ describe("local receiver to viewer integration", () => {
const source = await openLatestReceiver();
await act(async () => {
for (const transaction of ["GET /alpha", "GET /beta"]) {
source?.dispatchEvent(
source.dispatchEvent(
new MessageEvent(SENTRY_CONTENT_TYPE, {
data: JSON.stringify([
{},
Expand Down Expand Up @@ -2403,7 +2404,7 @@ describe("local receiver to viewer integration", () => {
);
if (state === "closed") {
fireEvent.click(captureAction("Stop capture"));
await waitFor(() => expect(source?.closed).toBe(true));
await waitFor(() => expect(source.closed).toBe(true));
await screen.findByText("Saved capture", { exact: true });
expect(screen.getAllByLabelText("View transaction event")).toHaveLength(
1,
Expand All @@ -2426,7 +2427,7 @@ describe("local receiver to viewer integration", () => {
1,
);
} else {
await act(async () => source?.onerror?.(new Event("error")));
await act(async () => source.onerror?.(new Event("error")));
}
fireEvent.change(
screen.getByRole("searchbox", { name: "Filter Events" }),
Expand Down
4 changes: 2 additions & 2 deletions apps/local/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -454,7 +454,7 @@ export default function App() {
});
const selectedItem =
searchedItems.find((item) => item.id === workspaceQuery.eventId) ??
searchedItems[0];
searchedItems.at(0);
const selectedEventTrace = selectedItem?.metadata?.traceId
? traces.find((trace) => trace.id === selectedItem.metadata?.traceId)
: undefined;
Expand Down Expand Up @@ -1300,7 +1300,7 @@ export default function App() {
new URL(currentEndpoint.url).pathname === "/stream",
)}
sessionPicker={
activeDirectoryUrl && !isDisconnected ? (
activeDirectoryUrl ? (
<SessionSwitcher
key={activeDirectoryUrl}
refreshKey={closedSessionUrl}
Expand Down
4 changes: 3 additions & 1 deletion apps/local/src/components/inspector-data.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { stringifyJson } from "@/lib/spotlight.ts";

export type DetailField = { label: string; value: string; code?: boolean };

export function detailRecord(value: unknown): Record<string, unknown> {
Expand All @@ -15,7 +17,7 @@ export function attributeFields(value: unknown): DetailField[] {
value:
typeof content === "string"
? content
: (JSON.stringify(content) ?? String(content)),
: (stringifyJson(content) ?? String(content)),
code: true,
};
});
Expand Down
6 changes: 4 additions & 2 deletions apps/local/src/components/performance-workspace.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@ export function SpanAnalysis({
),
[traces, kind, query, sort, ascending],
);
const selected = groups.find((group) => group.id === selectedId) ?? groups[0];
const selected =
groups.find((group) => group.id === selectedId) ?? groups.at(0);
const label = kind === "queries" ? "Database queries" : "Resources";
if (!selected)
return (
Expand Down Expand Up @@ -203,7 +204,8 @@ export function WebVitalsWorkspace({
);
const [selectedName, setSelectedName] = useState<string>();
const [chosenMetric, setChosenMetric] = useState<VitalName>("lcp");
const selected = pages.find((page) => page.name === selectedName) ?? pages[0];
const selected =
pages.find((page) => page.name === selectedName) ?? pages.at(0);
if (!selected)
return (
<Empty>
Expand Down
2 changes: 1 addition & 1 deletion apps/local/src/components/profile-workspace.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -468,7 +468,7 @@ export function ProfileWorkspace({
item.text.toLowerCase().includes(query.toLowerCase()),
);
const selected =
profiles.find((item) => item.id === selectedId) ?? profiles[0];
profiles.find((item) => item.id === selectedId) ?? profiles.at(0);
if (!selected)
return (
<p role="status" className="p-6 text-sm text-muted-foreground">
Expand Down
2 changes: 1 addition & 1 deletion apps/local/src/components/retention-settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ export function RetentionSettings({
});
const error =
form.formState.errors.maxEnvelopes?.message ??
form.formState.errors.root?.server?.message;
form.formState.errors.root?.server.message;
return (
<div className="space-y-4">
<p className="text-xs leading-relaxed text-muted-foreground">
Expand Down
Loading
Loading