Skip to content

Commit e515403

Browse files
betegonclaude
andcommitted
refactor(alert): align alert list commands with sentry issue list structure
- Use jsonTransformListResult (shared) instead of custom JSON transforms - Separate raw rules into items (for JSON) and displayRows (for human output) - Add --query/-q flag for client-side name filtering on both alert commands - Restore api-client.ts alerts export to original shape Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent ed1f484 commit e515403

File tree

3 files changed

+166
-143
lines changed

3 files changed

+166
-143
lines changed

src/commands/alert/issues/list.ts

Lines changed: 72 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ import {
2020
import { parseOrgProjectArg } from "../../../lib/arg-parsing.js";
2121
import { openInBrowser } from "../../../lib/browser.js";
2222
import { ContextError } from "../../../lib/errors.js";
23-
import { filterFields } from "../../../lib/formatters/json.js";
2423
import {
2524
colorTag,
2625
escapeMarkdownCell,
@@ -30,10 +29,13 @@ import { type Column, writeTable } from "../../../lib/formatters/table.js";
3029
import {
3130
buildListCommand,
3231
buildListLimitFlag,
32+
LIST_BASE_ALIASES,
33+
LIST_TARGET_POSITIONAL,
3334
targetPatternExplanation,
3435
} from "../../../lib/list-command.js";
3536
import {
3637
dispatchOrgScopedList,
38+
jsonTransformListResult,
3739
type ListCommandMeta,
3840
type ListResult,
3941
} from "../../../lib/org-list.js";
@@ -57,11 +59,18 @@ type ListFlags = {
5759
readonly cursor?: string;
5860
readonly json: boolean;
5961
readonly fields?: string[];
62+
readonly query?: string;
6063
};
6164

62-
type AlertRuleWithTarget = {
63-
rule: IssueAlertRule;
64-
target: ResolvedTarget;
65+
/** Display row carrying per-rule project context for the human formatter. */
66+
type AlertRuleRow = { rule: IssueAlertRule; target: ResolvedTarget };
67+
68+
/**
69+
* Extended result type: raw rules in `items` (for JSON), display rows in
70+
* `displayRows` (for human output). Mirrors the IssueListResult pattern.
71+
*/
72+
type IssueAlertListResult = ListResult<IssueAlertRule> & {
73+
displayRows?: AlertRuleRow[];
6574
};
6675

6776
const issueAlertListMeta: ListCommandMeta = {
@@ -119,7 +128,7 @@ async function fetchFromTargets(
119128
targets: ResolvedTarget[],
120129
limit: number,
121130
json: boolean
122-
): Promise<AlertRuleWithTarget[]> {
131+
): Promise<AlertRuleRow[]> {
123132
const perTargetLimit = Math.max(limit, Math.ceil(limit / targets.length) * 2);
124133
const results = await withProgress(
125134
{
@@ -140,14 +149,26 @@ async function fetchFromTargets(
140149
return results.flat().slice(0, limit);
141150
}
142151

152+
/** Client-side name filter applied after fetch (API has no query param). */
153+
function applyQueryFilter(
154+
rows: AlertRuleRow[],
155+
query: string | undefined
156+
): AlertRuleRow[] {
157+
if (!query) {
158+
return rows;
159+
}
160+
const q = query.toLowerCase();
161+
return rows.filter((r) => r.rule.name.toLowerCase().includes(q));
162+
}
163+
143164
// ---------------------------------------------------------------------------
144165
// Mode handlers
145166
// ---------------------------------------------------------------------------
146167

147168
async function handleAutoDetectIssueAlerts(
148169
cwd: string,
149170
flags: ListFlags
150-
): Promise<ListResult<AlertRuleWithTarget>> {
171+
): Promise<IssueAlertListResult> {
151172
const { targets, footer } = await withProgress(
152173
{ message: "Resolving targets...", json: flags.json },
153174
() =>
@@ -159,15 +180,23 @@ async function handleAutoDetectIssueAlerts(
159180
if (targets.length === 0) {
160181
throw new ContextError("Organization and project", USAGE_HINT);
161182
}
162-
const items = await fetchFromTargets(targets, flags.limit, flags.json);
163-
return { items, hasMore: false, hint: footer };
183+
const displayRows = applyQueryFilter(
184+
await fetchFromTargets(targets, flags.limit, flags.json),
185+
flags.query
186+
);
187+
return {
188+
items: displayRows.map((r) => r.rule),
189+
displayRows,
190+
hasMore: false,
191+
hint: footer,
192+
};
164193
}
165194

166195
async function handleExplicitIssueAlerts(
167196
org: string,
168197
project: string,
169198
flags: ListFlags
170-
): Promise<ListResult<AlertRuleWithTarget>> {
199+
): Promise<IssueAlertListResult> {
171200
const target: ResolvedTarget = {
172201
org,
173202
project,
@@ -181,8 +210,13 @@ async function handleExplicitIssueAlerts(
181210
},
182211
() => fetchIssueRulesForTarget(target, flags.limit)
183212
);
213+
const displayRows = applyQueryFilter(
214+
rules.map((rule) => ({ rule, target })),
215+
flags.query
216+
);
184217
return {
185-
items: rules.map((rule) => ({ rule, target })),
218+
items: displayRows.map((r) => r.rule),
219+
displayRows,
186220
hasMore: false,
187221
hint: `Alert rules: ${buildIssueAlertsUrl(org, project)}`,
188222
};
@@ -191,7 +225,7 @@ async function handleExplicitIssueAlerts(
191225
async function handleOrgAllIssueAlerts(
192226
org: string,
193227
flags: ListFlags
194-
): Promise<ListResult<AlertRuleWithTarget>> {
228+
): Promise<IssueAlertListResult> {
195229
// org-all: list all projects in the org, then fetch alerts for each
196230
const { targets } = await withProgress(
197231
{ message: `Listing projects in ${org}...`, json: flags.json },
@@ -201,9 +235,13 @@ async function handleOrgAllIssueAlerts(
201235
{ cwd: "", usageHint: USAGE_HINT }
202236
)
203237
);
204-
const items = await fetchFromTargets(targets, flags.limit, flags.json);
238+
const displayRows = applyQueryFilter(
239+
await fetchFromTargets(targets, flags.limit, flags.json),
240+
flags.query
241+
);
205242
return {
206-
items,
243+
items: displayRows.map((r) => r.rule),
244+
displayRows,
207245
hasMore: false,
208246
hint:
209247
targets.length > 1
@@ -216,7 +254,7 @@ async function handleProjectSearchIssueAlerts(
216254
projectSlug: string,
217255
cwd: string,
218256
flags: ListFlags
219-
): Promise<ListResult<AlertRuleWithTarget>> {
257+
): Promise<IssueAlertListResult> {
220258
const { targets } = await withProgress(
221259
{ message: `Searching for project '${projectSlug}'...`, json: flags.json },
222260
() =>
@@ -225,23 +263,25 @@ async function handleProjectSearchIssueAlerts(
225263
{ cwd, usageHint: USAGE_HINT }
226264
)
227265
);
228-
const items = await fetchFromTargets(targets, flags.limit, flags.json);
229-
return { items, hasMore: false };
266+
const displayRows = applyQueryFilter(
267+
await fetchFromTargets(targets, flags.limit, flags.json),
268+
flags.query
269+
);
270+
return { items: displayRows.map((r) => r.rule), displayRows, hasMore: false };
230271
}
231272

232273
// ---------------------------------------------------------------------------
233274
// Human output
234275
// ---------------------------------------------------------------------------
235276

236-
function formatIssueAlertListHuman(
237-
result: ListResult<AlertRuleWithTarget>
238-
): string {
277+
function formatIssueAlertListHuman(result: IssueAlertListResult): string {
239278
if (result.items.length === 0) {
240279
return result.hint ?? "No issue alert rules found.";
241280
}
242281

282+
const rows = result.displayRows ?? [];
243283
const uniqueProjects = new Set(
244-
result.items.map((r) => `${r.target.org}/${r.target.project}`)
284+
rows.map((r) => `${r.target.org}/${r.target.project}`)
245285
);
246286
const isMultiProject = uniqueProjects.size > 1;
247287

@@ -255,7 +295,7 @@ function formatIssueAlertListHuman(
255295
status: string;
256296
};
257297

258-
const rows: Row[] = result.items.map(({ rule: r, target }) => ({
298+
const tableRows: Row[] = rows.map(({ rule: r, target }) => ({
259299
id: r.id,
260300
name: escapeMarkdownCell(r.name),
261301
...(isMultiProject && {
@@ -284,36 +324,11 @@ function formatIssueAlertListHuman(
284324

285325
const parts: string[] = [];
286326
const buffer: Writer = { write: (s) => parts.push(s) };
287-
writeTable(buffer, rows, columns);
327+
writeTable(buffer, tableRows, columns);
288328

289329
return parts.join("").trimEnd();
290330
}
291331

292-
// ---------------------------------------------------------------------------
293-
// JSON transform
294-
// ---------------------------------------------------------------------------
295-
296-
function jsonTransformIssueAlertList(
297-
result: ListResult<AlertRuleWithTarget>,
298-
fields?: string[]
299-
): unknown {
300-
const rules = result.items.map(({ rule }) => rule);
301-
const items =
302-
fields && fields.length > 0
303-
? rules.map((r) => filterFields(r, fields))
304-
: rules;
305-
306-
const envelope: Record<string, unknown> = {
307-
data: items,
308-
hasMore: !!result.hasMore,
309-
hasPrev: !!result.hasPrev,
310-
};
311-
if (result.nextCursor) {
312-
envelope.nextCursor = result.nextCursor;
313-
}
314-
return envelope;
315-
}
316-
317332
// ---------------------------------------------------------------------------
318333
// Command
319334
// ---------------------------------------------------------------------------
@@ -334,30 +349,25 @@ export const listCommand = buildListCommand("alert", {
334349
},
335350
output: {
336351
human: formatIssueAlertListHuman,
337-
jsonTransform: jsonTransformIssueAlertList,
352+
jsonTransform: jsonTransformListResult,
338353
},
339354
parameters: {
340-
positional: {
341-
kind: "tuple",
342-
parameters: [
343-
{
344-
placeholder: "org/project",
345-
brief:
346-
"<org>/<project>, <org>/ (all), <project> (search), or omit to auto-detect",
347-
parse: String,
348-
optional: true,
349-
},
350-
],
351-
},
355+
positional: LIST_TARGET_POSITIONAL,
352356
flags: {
353357
web: {
354358
kind: "boolean",
355359
brief: "Open in browser",
356360
default: false,
357361
},
358362
limit: buildListLimitFlag("issue alert rules"),
363+
query: {
364+
kind: "parsed",
365+
parse: String,
366+
brief: "Filter rules by name",
367+
optional: true,
368+
},
359369
},
360-
aliases: { w: "web", n: "limit" },
370+
aliases: { ...LIST_BASE_ALIASES, w: "web", q: "query" },
361371
},
362372
async *func(this: SentryContext, flags: ListFlags, target?: string) {
363373
const { cwd } = this;
@@ -390,7 +400,7 @@ export const listCommand = buildListCommand("alert", {
390400
flags
391401
),
392402
},
393-
})) as ListResult<AlertRuleWithTarget>;
403+
})) as IssueAlertListResult;
394404

395405
yield new CommandOutput(result);
396406
return { hint: result.hint };

0 commit comments

Comments
 (0)