Skip to content

Commit 4317ad5

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 58d703f commit 4317ad5

File tree

10 files changed

+980
-154
lines changed

10 files changed

+980
-154
lines changed

AGENTS.md

Lines changed: 38 additions & 45 deletions
Large diffs are not rendered by default.

src/commands/issue/list.ts

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ type ListFlags = {
8080
readonly period: string;
8181
readonly json: boolean;
8282
readonly cursor?: string;
83+
readonly compact: boolean;
8384
};
8485

8586
/** @internal */ export type SortValue = "date" | "new" | "freq" | "user";
@@ -132,7 +133,7 @@ function writeListFooter(
132133
break;
133134
case "multi":
134135
stdout.write(
135-
"\nTip: Use 'sentry issue view <ALIAS>' to view details (see ALIAS column).\n"
136+
"\nTip: Use 'sentry issue view <ALIAS>' to view details (alias shown under SHORT ID).\n"
136137
);
137138
break;
138139
default:
@@ -421,6 +422,8 @@ async function fetchIssuesForTarget(
421422
limit: number;
422423
sort: SortValue;
423424
statsPeriod?: string;
425+
/** Controls the time resolution of inline stats data. */
426+
groupStatsPeriod?: "" | "14d" | "24h" | "auto";
424427
/** Resume from this cursor (Phase 2 redistribution or next-page resume). */
425428
startCursor?: string;
426429
onPage?: (fetched: number, limit: number) => void;
@@ -502,6 +505,8 @@ type BudgetFetchOptions = {
502505
limit: number;
503506
sort: SortValue;
504507
statsPeriod?: string;
508+
/** Controls the time resolution of inline stats data. */
509+
groupStatsPeriod?: "" | "14d" | "24h" | "auto";
505510
/** Per-target cursors from a previous page (compound cursor resume). */
506511
startCursors?: Map<string, string>;
507512
};
@@ -731,6 +736,7 @@ async function fetchOrgAllIssues(
731736
perPage,
732737
sort: flags.sort,
733738
statsPeriod: flags.period,
739+
groupStatsPeriod: "auto",
734740
});
735741
return { issues: response.data, nextCursor: response.nextCursor };
736742
}
@@ -741,6 +747,7 @@ async function fetchOrgAllIssues(
741747
limit: flags.limit,
742748
sort: flags.sort,
743749
statsPeriod: flags.period,
750+
groupStatsPeriod: "auto",
744751
onPage,
745752
});
746753
return { issues, nextCursor };
@@ -822,7 +829,7 @@ async function handleOrgAllIssues(options: OrgAllIssuesOptions): Promise<void> {
822829
isMultiProject: true,
823830
},
824831
}));
825-
writeIssueTable(stdout, issuesWithOpts, true);
832+
writeIssueTable(stdout, issuesWithOpts, { compact: flags.compact });
826833

827834
if (hasMore) {
828835
stdout.write(`\nShowing ${issues.length} issues (more available)\n`);
@@ -928,6 +935,7 @@ async function handleResolvedTargets(
928935
limit: flags.limit,
929936
sort: flags.sort,
930937
statsPeriod: flags.period,
938+
groupStatsPeriod: "auto",
931939
startCursors,
932940
},
933941
(fetched) => {
@@ -1078,13 +1086,18 @@ async function handleResolvedTargets(
10781086
return;
10791087
}
10801088

1081-
const title =
1082-
isSingleProject && firstTarget
1083-
? `Issues in ${firstTarget.orgDisplay}/${firstTarget.projectDisplay}`
1084-
: `Issues from ${validResults.length} projects`;
1089+
// Header: use detailed project info when available, otherwise simple title
1090+
if (footer) {
1091+
// Multi-project: "Found N Sentry projects:" + bullet list as pre-header
1092+
stdout.write(`${footer}\n\n`);
1093+
} else if (isSingleProject && firstTarget) {
1094+
writeListHeader(
1095+
stdout,
1096+
`Issues in ${firstTarget.orgDisplay}/${firstTarget.projectDisplay}`
1097+
);
1098+
}
10851099

1086-
writeListHeader(stdout, title);
1087-
writeIssueTable(stdout, issuesWithOptions, isMultiProject);
1100+
writeIssueTable(stdout, issuesWithOptions, { compact: flags.compact });
10881101

10891102
let footerMode: "single" | "multi" | "none" = "none";
10901103
if (isMultiProject) {
@@ -1113,10 +1126,6 @@ async function handleResolvedTargets(
11131126
);
11141127
}
11151128
}
1116-
1117-
if (footer) {
1118-
stdout.write(`\n${footer}\n`);
1119-
}
11201129
}
11211130

11221131
/** Metadata for the shared dispatch infrastructure. */
@@ -1193,6 +1202,11 @@ export const listCommand = buildListCommand("issue", {
11931202
'Pagination cursor for <org>/ or multi-target modes (use "last" to continue)',
11941203
optional: true,
11951204
},
1205+
compact: {
1206+
kind: "boolean",
1207+
brief: "Single-line rows for compact output",
1208+
default: false,
1209+
},
11961210
},
11971211
aliases: { ...LIST_BASE_ALIASES, q: "query", s: "sort", t: "period" },
11981212
},

src/lib/api-client.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1136,6 +1136,8 @@ export async function listIssuesPaginated(
11361136
* instead of `project:<slug>` search syntax, avoiding "not actively
11371137
* selected" errors. */
11381138
projectId?: number;
1139+
/** Controls the time resolution of inline stats data. "auto" adapts to statsPeriod. */
1140+
groupStatsPeriod?: "" | "14d" | "24h" | "auto";
11391141
} = {}
11401142
): Promise<PaginatedResponse<SentryIssue[]>> {
11411143
// When we have a numeric project ID, use the `project` query param (Array<number>)
@@ -1162,6 +1164,7 @@ export async function listIssuesPaginated(
11621164
limit: options.perPage ?? 25,
11631165
sort: options.sort,
11641166
statsPeriod: options.statsPeriod,
1167+
groupStatsPeriod: options.groupStatsPeriod,
11651168
},
11661169
});
11671170

@@ -1208,6 +1211,8 @@ export async function listIssuesAllPages(
12081211
statsPeriod?: string;
12091212
/** Numeric project ID for direct project selection via query param. */
12101213
projectId?: number;
1214+
/** Controls the time resolution of inline stats data. "auto" adapts to statsPeriod. */
1215+
groupStatsPeriod?: "" | "14d" | "24h" | "auto";
12111216
/** Resume pagination from this cursor instead of starting from the beginning. */
12121217
startCursor?: string;
12131218
/** Called after each page is fetched. Useful for progress indicators. */
@@ -1234,6 +1239,7 @@ export async function listIssuesAllPages(
12341239
sort: options.sort,
12351240
statsPeriod: options.statsPeriod,
12361241
projectId: options.projectId,
1242+
groupStatsPeriod: options.groupStatsPeriod,
12371243
});
12381244

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

0 commit comments

Comments
 (0)