Skip to content

Commit 695e21b

Browse files
authored
fix(event): detect org/ISSUE-SHORT-ID in event view single-arg path (CLI-9K) (#529)
## Summary Extends the issue short ID detection in `event view` to handle the `org/ISSUE-SHORT-ID` pattern (e.g., `figma/FULLSCREEN-2RN`). ### Bug (CLI-9K — 13 events, 11 users) When passing `figma/FULLSCREEN-2RN` to `sentry event view`, `parseSlashSeparatedArg` sees exactly one slash and interprets it as `org=figma` / `project=FULLSCREEN-2RN` with no event ID → throws `ContextError: Event ID is required.` PR #524 added auto-redirect for **bare** issue short IDs like `BRUNCHIE-APP-29`, but the `org/SHORT-ID` pattern wasn't covered because `parseSlashSeparatedArg` throws before the detection code runs. ### Fix Extract single-arg handling into `parseSingleArg()` which checks for the `org/ISSUE-SHORT-ID` pattern **before** calling `parseSlashSeparatedArg`: 1. If the arg has exactly one slash and the part after it matches `looksLikeIssueShortId()`, return it as an issue short ID auto-redirect (same as the bare case) 2. Otherwise, proceed with normal `parseSlashSeparatedArg` flow This refactor also reduces `parsePositionalArgs` complexity from 16 to within the biome limit of 15. ### Tests added - `figma/FULLSCREEN-2RN` → `{ eventId: "latest", targetArg: "figma", issueShortId: "FULLSCREEN-2RN" }` - `sentry/CLI-G` → `{ eventId: "latest", targetArg: "sentry", issueShortId: "CLI-G" }` - `my-org/my-project` → still throws ContextError (not a short ID)
1 parent 7db4f7c commit 695e21b

2 files changed

Lines changed: 75 additions & 21 deletions

File tree

src/commands/event/view.ts

Lines changed: 52 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,50 @@ function jsonTransformEventView(
104104
/** Usage hint for ContextError messages */
105105
const USAGE_HINT = "sentry event view <org>/<project> <event-id>";
106106

107+
/**
108+
* Parse a single positional arg for event view, handling issue short ID
109+
* detection both in bare form ("BRUNCHIE-APP-29") and org-prefixed form
110+
* ("figma/FULLSCREEN-2RN").
111+
*
112+
* Must run before `parseSlashSeparatedArg` because that function throws
113+
* ContextError for single-slash args like "org/SHORT-ID", which looks like
114+
* "org/project" with a missing event ID.
115+
*/
116+
function parseSingleArg(arg: string): ParsedPositionalArgs {
117+
// Detect "org/SHORT-ID" pattern before parseSlashSeparatedArg.
118+
// e.g., "figma/FULLSCREEN-2RN" → auto-redirect to that issue's latest event.
119+
const slashIdx = arg.indexOf("/");
120+
if (slashIdx !== -1 && arg.indexOf("/", slashIdx + 1) === -1) {
121+
const afterSlash = arg.slice(slashIdx + 1);
122+
if (afterSlash && looksLikeIssueShortId(afterSlash)) {
123+
// Use "org/" (trailing slash) to signal OrgAll mode so downstream
124+
// parseOrgProjectArg interprets this as an org, not a project search.
125+
return {
126+
eventId: "latest",
127+
targetArg: `${arg.slice(0, slashIdx)}/`,
128+
issueShortId: afterSlash,
129+
};
130+
}
131+
}
132+
133+
const { id: eventId, targetArg } = parseSlashSeparatedArg(
134+
arg,
135+
"Event ID",
136+
USAGE_HINT
137+
);
138+
139+
// Detect bare issue short ID passed as event ID (e.g., "BRUNCHIE-APP-29").
140+
if (!targetArg && looksLikeIssueShortId(eventId)) {
141+
return {
142+
eventId: "latest",
143+
targetArg: undefined,
144+
issueShortId: eventId,
145+
};
146+
}
147+
148+
return { eventId, targetArg };
149+
}
150+
107151
/** Return type for parsePositionalArgs */
108152
type ParsedPositionalArgs = {
109153
eventId: string;
@@ -175,24 +219,7 @@ export function parsePositionalArgs(args: string[]): ParsedPositionalArgs {
175219
}
176220

177221
if (args.length === 1) {
178-
const { id: eventId, targetArg } = parseSlashSeparatedArg(
179-
first,
180-
"Event ID",
181-
USAGE_HINT
182-
);
183-
184-
// Detect issue short ID passed as event ID (e.g., "BRUNCHIE-APP-29").
185-
// When a single arg matches the issue short ID pattern, the user likely
186-
// wanted `sentry issue view`. Auto-redirect to show the latest event.
187-
if (!targetArg && looksLikeIssueShortId(eventId)) {
188-
return {
189-
eventId: "latest",
190-
targetArg: undefined,
191-
issueShortId: eventId,
192-
};
193-
}
194-
195-
return { eventId, targetArg };
222+
return parseSingleArg(first);
196223
}
197224

198225
const second = args[1];
@@ -486,14 +513,18 @@ async function resolveIssueShortcut(
486513
}
487514

488515
// Issue short ID auto-redirect: user passed an issue short ID
489-
// (e.g., "BRUNCHIE-APP-29") instead of a hex event ID. Resolve
490-
// the issue and show its latest event.
516+
// (e.g., "BRUNCHIE-APP-29" or "figma/FULLSCREEN-2RN") instead of a hex
517+
// event ID. Resolve the issue and show its latest event.
491518
if (issueShortId) {
492519
log.warn(
493520
`'${issueShortId}' is an issue short ID, not an event ID. Showing the latest event.`
494521
);
495522

496-
const resolved = await resolveOrg({ cwd });
523+
// Use the explicit org from the parsed target if available (e.g.,
524+
// "figma/" → org-all with org "figma"), otherwise fall back to
525+
// auto-detection via DSN/env/config.
526+
const explicitOrg = parsed.type === "org-all" ? parsed.org : undefined;
527+
const resolved = await resolveOrg({ org: explicitOrg, cwd });
497528
if (!resolved) {
498529
throw new ContextError(
499530
"Organization",

test/commands/event/view.test.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,29 @@ describe("parsePositionalArgs", () => {
8181
expect(result.eventId).toBe("my-project");
8282
expect(result.issueShortId).toBeUndefined();
8383
});
84+
85+
test("detects org/ISSUE-SHORT-ID pattern (CLI-9K)", () => {
86+
const result = parsePositionalArgs(["figma/FULLSCREEN-2RN"]);
87+
expect(result.eventId).toBe("latest");
88+
// Trailing slash signals OrgAll mode so downstream resolves org correctly
89+
expect(result.targetArg).toBe("figma/");
90+
expect(result.issueShortId).toBe("FULLSCREEN-2RN");
91+
});
92+
93+
test("detects org/CLI-G pattern", () => {
94+
const result = parsePositionalArgs(["sentry/CLI-G"]);
95+
expect(result.eventId).toBe("latest");
96+
expect(result.targetArg).toBe("sentry/");
97+
expect(result.issueShortId).toBe("CLI-G");
98+
});
99+
100+
test("does not detect org/lowercase-slug as issue short ID", () => {
101+
// "my-org/my-project" is a normal org/project target, not an issue short ID.
102+
// parseSlashSeparatedArg will throw ContextError as expected.
103+
expect(() => parsePositionalArgs(["my-org/my-project"])).toThrow(
104+
"Event ID"
105+
);
106+
});
84107
});
85108

86109
describe("two arguments (target + event ID)", () => {

0 commit comments

Comments
 (0)