Skip to content

Commit 714deb9

Browse files
committed
feat(issue-list): redesign table to match Sentry web UI
Redesign the `sentry issue list` table to match the Sentry web UI issue stream layout with better information density and visual clarity. Column layout: SHORT ID | ISSUE | SEEN | AGE | TREND | EVENTS | USERS | TRIAGE Key changes: - Sparkline trend graphs using Unicode block characters (▁▂▃▄▅▆▇█) with bucket-averaging downsample, rendered via new sparkline module - TREND column with sparkline + substatus label (New, Ongoing, Regressed, Escalating), auto-hidden on narrow terminals (<100 cols) - TRIAGE column combining priority and Seer fixability into a composite score: impact×0.6 + fixability×0.4, colored by tier - 2-line default rows (title+subtitle, sparkline+substatus, id+alias) with --compact flag for single-line condensed output - Row separators between data rows for visual clarity - Request groupStatsPeriod=auto from the API for sparkline data Text table improvements: - Row separator support (dimmed horizontal dividers between data rows) - Multi-line cell width calculation fix: split on newlines and take max line width instead of summing across the entire string Removed columns: LEVEL, ALIAS (merged into SHORT ID), ASSIGNEE, PRIORITY (replaced by TRIAGE). Renamed COUNT → EVENTS.
1 parent 8ce5128 commit 714deb9

File tree

10 files changed

+1092
-206
lines changed

10 files changed

+1092
-206
lines changed

AGENTS.md

Lines changed: 42 additions & 15 deletions
Large diffs are not rendered by default.

src/commands/issue/list.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ type ListFlags = {
8484
readonly json: boolean;
8585
readonly cursor?: string;
8686
readonly fresh: boolean;
87+
readonly compact: boolean;
8788
};
8889

8990
/** @internal */ export type SortValue = "date" | "new" | "freq" | "user";
@@ -438,7 +439,7 @@ async function fetchIssuesForTarget(
438439
const { issues, nextCursor } = await listIssuesAllPages(
439440
target.org,
440441
target.project,
441-
{ ...options, projectId: target.projectId }
442+
{ ...options, projectId: target.projectId, groupStatsPeriod: "auto" }
442443
);
443444
return { target, issues, hasMore: !!nextCursor, nextCursor };
444445
});
@@ -735,6 +736,7 @@ async function fetchOrgAllIssues(
735736
perPage,
736737
sort: flags.sort,
737738
statsPeriod: flags.period,
739+
groupStatsPeriod: "auto",
738740
});
739741
return { issues: response.data, nextCursor: response.nextCursor };
740742
}
@@ -745,6 +747,7 @@ async function fetchOrgAllIssues(
745747
limit: flags.limit,
746748
sort: flags.sort,
747749
statsPeriod: flags.period,
750+
groupStatsPeriod: "auto",
748751
onPage,
749752
});
750753
return { issues, nextCursor };
@@ -826,7 +829,7 @@ async function handleOrgAllIssues(options: OrgAllIssuesOptions): Promise<void> {
826829
isMultiProject: true,
827830
},
828831
}));
829-
writeIssueTable(stdout, issuesWithOpts, true);
832+
writeIssueTable(stdout, issuesWithOpts, { compact: flags.compact });
830833

831834
if (hasMore) {
832835
stdout.write(`\nShowing ${issues.length} issues (more available)\n`);
@@ -1088,7 +1091,7 @@ async function handleResolvedTargets(
10881091
: `Issues from ${validResults.length} projects`;
10891092

10901093
writeListHeader(stdout, title);
1091-
writeIssueTable(stdout, issuesWithOptions, isMultiProject);
1094+
writeIssueTable(stdout, issuesWithOptions, { compact: flags.compact });
10921095

10931096
let footerMode: "single" | "multi" | "none" = "none";
10941097
if (isMultiProject) {
@@ -1198,6 +1201,11 @@ export const listCommand = buildListCommand("issue", {
11981201
optional: true,
11991202
},
12001203
fresh: FRESH_FLAG,
1204+
compact: {
1205+
kind: "boolean",
1206+
brief: "Single-line rows for compact output",
1207+
default: false,
1208+
},
12011209
},
12021210
aliases: {
12031211
...LIST_BASE_ALIASES,

src/lib/api-client.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1149,6 +1149,8 @@ export async function listIssuesPaginated(
11491149
* instead of `project:<slug>` search syntax, avoiding "not actively
11501150
* selected" errors. */
11511151
projectId?: number;
1152+
/** Controls the time resolution of inline stats data. "auto" adapts to statsPeriod. */
1153+
groupStatsPeriod?: "" | "14d" | "24h" | "auto";
11521154
} = {}
11531155
): Promise<PaginatedResponse<SentryIssue[]>> {
11541156
// When we have a numeric project ID, use the `project` query param (Array<number>)
@@ -1175,6 +1177,7 @@ export async function listIssuesPaginated(
11751177
limit: options.perPage ?? 25,
11761178
sort: options.sort,
11771179
statsPeriod: options.statsPeriod,
1180+
groupStatsPeriod: options.groupStatsPeriod,
11781181
},
11791182
});
11801183

@@ -1221,6 +1224,8 @@ export async function listIssuesAllPages(
12211224
statsPeriod?: string;
12221225
/** Numeric project ID for direct project selection via query param. */
12231226
projectId?: number;
1227+
/** Controls the time resolution of inline stats data. "auto" adapts to statsPeriod. */
1228+
groupStatsPeriod?: "" | "14d" | "24h" | "auto";
12241229
/** Resume pagination from this cursor instead of starting from the beginning. */
12251230
startCursor?: string;
12261231
/** Called after each page is fetched. Useful for progress indicators. */
@@ -1247,6 +1252,7 @@ export async function listIssuesAllPages(
12471252
sort: options.sort,
12481253
statsPeriod: options.statsPeriod,
12491254
projectId: options.projectId,
1255+
groupStatsPeriod: options.groupStatsPeriod,
12501256
});
12511257

12521258
allResults.push(...response.data);

0 commit comments

Comments
 (0)