|
4 | 4 | * Remove a widget from an existing dashboard. |
5 | 5 | * |
6 | 6 | * Uses `buildDeleteCommand` — auto-injects `--yes`/`--force`/`--dry-run` |
7 | | - * flags and enforces the non-interactive guard before `func()` runs. |
8 | | - * `--yes`/`--force` are no-ops for now (no confirmation prompt) but |
9 | | - * available for scripting and forward-compatibility. |
| 7 | + * flags. Non-interactive guard is disabled (`noNonInteractiveGuard`) because |
| 8 | + * widget deletion is reversible (re-add the widget). `--yes`/`--force` are |
| 9 | + * accepted but have no effect today (no confirmation prompt); `--dry-run` |
| 10 | + * shows which widget would be removed without modifying the dashboard. |
10 | 11 | */ |
11 | 12 |
|
12 | 13 | import type { SentryContext } from "../../../context.js"; |
@@ -47,128 +48,131 @@ type DeleteResult = { |
47 | 48 | dryRun?: boolean; |
48 | 49 | }; |
49 | 50 |
|
50 | | -export const deleteCommand = buildDeleteCommand({ |
51 | | - docs: { |
52 | | - brief: "Delete a widget from a dashboard", |
53 | | - fullDescription: |
54 | | - "Remove a widget from an existing Sentry dashboard.\n\n" + |
55 | | - "The dashboard can be specified by numeric ID or title.\n" + |
56 | | - "Identify the widget by --index (0-based) or --title.\n\n" + |
57 | | - "Examples:\n" + |
58 | | - " sentry dashboard widget delete 12345 --index 0\n" + |
59 | | - " sentry dashboard widget delete 'My Dashboard' --title 'Error Rate'\n" + |
60 | | - " sentry dashboard widget delete 12345 --index 0 --dry-run", |
61 | | - }, |
62 | | - output: { |
63 | | - human: formatWidgetDeleted, |
64 | | - jsonTransform: (result: DeleteResult) => { |
65 | | - if (result.dryRun) { |
| 51 | +export const deleteCommand = buildDeleteCommand( |
| 52 | + { |
| 53 | + docs: { |
| 54 | + brief: "Delete a widget from a dashboard", |
| 55 | + fullDescription: |
| 56 | + "Remove a widget from an existing Sentry dashboard.\n\n" + |
| 57 | + "The dashboard can be specified by numeric ID or title.\n" + |
| 58 | + "Identify the widget by --index (0-based) or --title.\n\n" + |
| 59 | + "Examples:\n" + |
| 60 | + " sentry dashboard widget delete 12345 --index 0\n" + |
| 61 | + " sentry dashboard widget delete 'My Dashboard' --title 'Error Rate'\n" + |
| 62 | + " sentry dashboard widget delete 12345 --index 0 --dry-run", |
| 63 | + }, |
| 64 | + output: { |
| 65 | + human: formatWidgetDeleted, |
| 66 | + jsonTransform: (result: DeleteResult) => { |
| 67 | + if (result.dryRun) { |
| 68 | + return { |
| 69 | + dryRun: true, |
| 70 | + widgetTitle: result.widgetTitle, |
| 71 | + widgetCount: result.dashboard.widgets?.length ?? 0, |
| 72 | + url: result.url, |
| 73 | + }; |
| 74 | + } |
66 | 75 | return { |
67 | | - dryRun: true, |
| 76 | + deleted: true, |
68 | 77 | widgetTitle: result.widgetTitle, |
69 | 78 | widgetCount: result.dashboard.widgets?.length ?? 0, |
70 | 79 | url: result.url, |
71 | 80 | }; |
72 | | - } |
73 | | - return { |
74 | | - deleted: true, |
75 | | - widgetTitle: result.widgetTitle, |
76 | | - widgetCount: result.dashboard.widgets?.length ?? 0, |
77 | | - url: result.url, |
78 | | - }; |
79 | | - }, |
80 | | - }, |
81 | | - parameters: { |
82 | | - positional: { |
83 | | - kind: "array", |
84 | | - parameter: { |
85 | | - placeholder: "org/project/dashboard", |
86 | | - brief: "[<org/project>] <dashboard-id-or-title>", |
87 | | - parse: String, |
88 | 81 | }, |
89 | 82 | }, |
90 | | - flags: { |
91 | | - index: { |
92 | | - kind: "parsed", |
93 | | - parse: numberParser, |
94 | | - brief: "Widget index (0-based)", |
95 | | - optional: true, |
| 83 | + parameters: { |
| 84 | + positional: { |
| 85 | + kind: "array", |
| 86 | + parameter: { |
| 87 | + placeholder: "org/project/dashboard", |
| 88 | + brief: "[<org/project>] <dashboard-id-or-title>", |
| 89 | + parse: String, |
| 90 | + }, |
96 | 91 | }, |
97 | | - title: { |
98 | | - kind: "parsed", |
99 | | - parse: String, |
100 | | - brief: "Widget title to match", |
101 | | - optional: true, |
| 92 | + flags: { |
| 93 | + index: { |
| 94 | + kind: "parsed", |
| 95 | + parse: numberParser, |
| 96 | + brief: "Widget index (0-based)", |
| 97 | + optional: true, |
| 98 | + }, |
| 99 | + title: { |
| 100 | + kind: "parsed", |
| 101 | + parse: String, |
| 102 | + brief: "Widget title to match", |
| 103 | + optional: true, |
| 104 | + }, |
102 | 105 | }, |
| 106 | + aliases: { i: "index", t: "title" }, |
103 | 107 | }, |
104 | | - aliases: { i: "index", t: "title" }, |
105 | | - }, |
106 | | - async *func(this: SentryContext, flags: DeleteFlags, ...args: string[]) { |
107 | | - const { cwd } = this; |
| 108 | + async *func(this: SentryContext, flags: DeleteFlags, ...args: string[]) { |
| 109 | + const { cwd } = this; |
108 | 110 |
|
109 | | - if (flags.index === undefined && !flags.title) { |
110 | | - throw new ValidationError( |
111 | | - "Specify --index or --title to identify the widget to delete.", |
112 | | - "index" |
| 111 | + if (flags.index === undefined && !flags.title) { |
| 112 | + throw new ValidationError( |
| 113 | + "Specify --index or --title to identify the widget to delete.", |
| 114 | + "index" |
| 115 | + ); |
| 116 | + } |
| 117 | + |
| 118 | + const { dashboardRef, targetArg } = parseDashboardPositionalArgs(args); |
| 119 | + const parsed = parseOrgProjectArg(targetArg); |
| 120 | + const orgSlug = await resolveOrgFromTarget( |
| 121 | + parsed, |
| 122 | + cwd, |
| 123 | + "sentry dashboard widget delete <org>/ <id> (--index <n> | --title <name>)" |
| 124 | + ); |
| 125 | + const dashboardId = await resolveDashboardId(orgSlug, dashboardRef); |
| 126 | + |
| 127 | + // GET current dashboard → find widget |
| 128 | + const current = await getDashboard(orgSlug, dashboardId).catch( |
| 129 | + (error: unknown) => |
| 130 | + enrichDashboardError(error, { |
| 131 | + orgSlug, |
| 132 | + dashboardId, |
| 133 | + operation: "view", |
| 134 | + }) |
113 | 135 | ); |
114 | | - } |
| 136 | + const widgets = current.widgets ?? []; |
115 | 137 |
|
116 | | - const { dashboardRef, targetArg } = parseDashboardPositionalArgs(args); |
117 | | - const parsed = parseOrgProjectArg(targetArg); |
118 | | - const orgSlug = await resolveOrgFromTarget( |
119 | | - parsed, |
120 | | - cwd, |
121 | | - "sentry dashboard widget delete <org>/ <id> (--index <n> | --title <name>)" |
122 | | - ); |
123 | | - const dashboardId = await resolveDashboardId(orgSlug, dashboardRef); |
| 138 | + const widgetIndex = resolveWidgetIndex(widgets, flags.index, flags.title); |
| 139 | + const widgetTitle = widgets[widgetIndex]?.title; |
| 140 | + const url = buildDashboardUrl(orgSlug, dashboardId); |
124 | 141 |
|
125 | | - // GET current dashboard → find widget |
126 | | - const current = await getDashboard(orgSlug, dashboardId).catch( |
127 | | - (error: unknown) => |
| 142 | + // Dry-run mode: show what would be removed without removing it |
| 143 | + if (flags["dry-run"]) { |
| 144 | + yield new CommandOutput({ |
| 145 | + dashboard: current, |
| 146 | + widgetTitle, |
| 147 | + url, |
| 148 | + dryRun: true, |
| 149 | + } as DeleteResult); |
| 150 | + return { hint: `Dashboard: ${url}` }; |
| 151 | + } |
| 152 | + |
| 153 | + // Splice the widget and PUT the updated dashboard |
| 154 | + const updateBody = prepareDashboardForUpdate(current); |
| 155 | + updateBody.widgets.splice(widgetIndex, 1); |
| 156 | + |
| 157 | + const updated = await updateDashboard( |
| 158 | + orgSlug, |
| 159 | + dashboardId, |
| 160 | + updateBody |
| 161 | + ).catch((error: unknown) => |
128 | 162 | enrichDashboardError(error, { |
129 | 163 | orgSlug, |
130 | 164 | dashboardId, |
131 | | - operation: "view", |
| 165 | + operation: "update", |
132 | 166 | }) |
133 | | - ); |
134 | | - const widgets = current.widgets ?? []; |
135 | | - |
136 | | - const widgetIndex = resolveWidgetIndex(widgets, flags.index, flags.title); |
137 | | - const widgetTitle = widgets[widgetIndex]?.title; |
138 | | - const url = buildDashboardUrl(orgSlug, dashboardId); |
| 167 | + ); |
139 | 168 |
|
140 | | - // Dry-run mode: show what would be removed without removing it |
141 | | - if (flags["dry-run"]) { |
142 | 169 | yield new CommandOutput({ |
143 | | - dashboard: current, |
| 170 | + dashboard: updated, |
144 | 171 | widgetTitle, |
145 | 172 | url, |
146 | | - dryRun: true, |
147 | 173 | } as DeleteResult); |
148 | 174 | return { hint: `Dashboard: ${url}` }; |
149 | | - } |
150 | | - |
151 | | - // Splice the widget and PUT the updated dashboard |
152 | | - const updateBody = prepareDashboardForUpdate(current); |
153 | | - updateBody.widgets.splice(widgetIndex, 1); |
154 | | - |
155 | | - const updated = await updateDashboard( |
156 | | - orgSlug, |
157 | | - dashboardId, |
158 | | - updateBody |
159 | | - ).catch((error: unknown) => |
160 | | - enrichDashboardError(error, { |
161 | | - orgSlug, |
162 | | - dashboardId, |
163 | | - operation: "update", |
164 | | - }) |
165 | | - ); |
166 | | - |
167 | | - yield new CommandOutput({ |
168 | | - dashboard: updated, |
169 | | - widgetTitle, |
170 | | - url, |
171 | | - } as DeleteResult); |
172 | | - return { hint: `Dashboard: ${url}` }; |
| 175 | + }, |
173 | 176 | }, |
174 | | -}); |
| 177 | + { noNonInteractiveGuard: true } |
| 178 | +); |
0 commit comments