diff --git a/packages/cli/src/lib/api/releases.ts b/packages/cli/src/lib/api/releases.ts index 73db501b8..dc3eb7633 100644 --- a/packages/cli/src/lib/api/releases.ts +++ b/packages/cli/src/lib/api/releases.ts @@ -61,6 +61,17 @@ export type ListReleasesOptions = { status?: string; }; +/** + * Sorts the releases endpoint only accepts with `flatten=1`; otherwise it + * responds 400 "sorting by crash statistics requires flattening". + */ +const SESSION_SORTS = new Set([ + "sessions", + "users", + "crash_free_sessions", + "crash_free_users", +]); + export async function listReleasesPaginated( orgSlug: string, options: ListReleasesOptions = {}, @@ -76,6 +87,7 @@ export async function listReleasesPaginated( per_page: options.perPage ?? 25, query: options.query, sort: options.sort, + flatten: options.sort && SESSION_SORTS.has(options.sort) ? 1 : undefined, health: options.health ? 1 : undefined, project: options.project, environment: options.environment, @@ -86,6 +98,7 @@ export async function listReleasesPaginated( per_page?: number; query?: string; sort?: string; + flatten?: number; health?: number; project?: number[]; environment?: string[]; diff --git a/packages/cli/test/lib/api/releases.test.ts b/packages/cli/test/lib/api/releases.test.ts index 4d7218442..2eb2ba8c5 100644 --- a/packages/cli/test/lib/api/releases.test.ts +++ b/packages/cli/test/lib/api/releases.test.ts @@ -614,4 +614,50 @@ describe("listReleasesPaginated", () => { expect(result.data).toHaveLength(0); }); + + test.each(["sessions", "users", "crash_free_sessions", "crash_free_users"])( + "requests flattened results when sorting by %s", + async (sort) => { + let flatten: string | null = null; + globalThis.fetch = mockFetch(async (input, init) => { + const url = new URL(new Request(input!, init).url); + flatten = url.searchParams.get("flatten"); + // Sentry rejects session sorts unless the results are flattened. + if (flatten !== "1") { + return new Response( + JSON.stringify({ + detail: + "sorting by crash statistics requires flattening (flatten=1)", + }), + { status: 400, headers: { "Content-Type": "application/json" } }, + ); + } + return new Response(JSON.stringify([SAMPLE_RELEASE]), { + status: 200, + headers: { "Content-Type": "application/json" }, + }); + }); + + const result = await listReleasesPaginated("test-org", { sort }); + + expect(flatten).toBe("1"); + expect(result.data).toHaveLength(1); + }, + ); + + test("does not flatten date-sorted results", async () => { + let flatten: string | null = "unset"; + globalThis.fetch = mockFetch(async (input, init) => { + const url = new URL(new Request(input!, init).url); + flatten = url.searchParams.get("flatten"); + return new Response(JSON.stringify([]), { + status: 200, + headers: { "Content-Type": "application/json" }, + }); + }); + + await listReleasesPaginated("test-org", { sort: "date" }); + + expect(flatten).toBeNull(); + }); });