Skip to content

Commit a4135f4

Browse files
Merge master into feature/smus
2 parents a5bc36c + 71d7bd2 commit a4135f4

File tree

3 files changed

+91
-3
lines changed

3 files changed

+91
-3
lines changed

packages/core/src/awsService/cloudformation/ui/stackEventsWebviewProvider.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -255,7 +255,7 @@ export class StackEventsWebviewProvider implements WebviewViewProvider, Disposab
255255
}
256256

257257
private renderError(message: string): void {
258-
if (!this.view || this.view.visible === false) {
258+
if (!this.view || !this.view.visible) {
259259
return
260260
}
261261
this.view.webview.html = `<!DOCTYPE html>
@@ -283,6 +283,7 @@ export class StackEventsWebviewProvider implements WebviewViewProvider, Disposab
283283
}
284284

285285
const groupedEvents = this.groupEvents(this.allEvents)
286+
const hasHooks = this.allEvents.some((e) => e.HookType)
286287
const start = this.currentPage * EventsPerPage
287288
const end = start + EventsPerPage
288289
const pageEvents = groupedEvents.slice(start, end)
@@ -295,6 +296,7 @@ export class StackEventsWebviewProvider implements WebviewViewProvider, Disposab
295296
totalPages,
296297
hasMore,
297298
this.allEvents.length,
299+
hasHooks,
298300
notification
299301
)
300302
}
@@ -305,6 +307,7 @@ export class StackEventsWebviewProvider implements WebviewViewProvider, Disposab
305307
totalPages: number,
306308
hasMore: boolean,
307309
totalEvents: number,
310+
hasHooks: boolean,
308311
notification?: string
309312
): string {
310313
const emptyMessage =
@@ -465,9 +468,10 @@ export class StackEventsWebviewProvider implements WebviewViewProvider, Disposab
465468
<th>Logical ID</th>
466469
<th>Status</th>
467470
<th>Status Reason</th>
471+
${hasHooks ? '<th>Hook Invocation</th>' : ''}
468472
</tr></thead>
469473
<tbody>
470-
${events.map((e) => this.renderEventRow(e)).join('')}
474+
${events.map((e) => this.renderEventRow(e, hasHooks)).join('')}
471475
</tbody>
472476
</table>`}
473477
</div>
@@ -487,7 +491,11 @@ ${events.map((e) => this.renderEventRow(e)).join('')}
487491
</html>`
488492
}
489493

490-
private renderEventRow(event: GroupedEvent): string {
494+
private renderEventRow(event: GroupedEvent, hasHooks: boolean): string {
495+
const hookCell = hasHooks
496+
? `<td>${event.HookType ? `${event.HookType} (${event.HookStatus ?? '-'})` : '-'}</td>`
497+
: ''
498+
491499
if (event.isParent) {
492500
const expanded = this.expandedGroups.has(event.groupId)
493501
const chevron = event.OperationId ? `<span class="chevron ${expanded ? 'expanded' : ''}">▶</span>` : ''
@@ -503,6 +511,7 @@ ${events.map((e) => this.renderEventRow(e)).join('')}
503511
<td>${event.LogicalResourceId ?? '-'}</td>
504512
<td class="${getStackStatusClass(event.ResourceStatus)}">${event.ResourceStatus ?? '-'}</td>
505513
<td>${event.ResourceStatusReason ?? '-'}</td>
514+
${hookCell}
506515
</tr>`
507516
}
508517

@@ -516,6 +525,7 @@ ${events.map((e) => this.renderEventRow(e)).join('')}
516525
<td>${event.LogicalResourceId ?? '-'}</td>
517526
<td class="${getStackStatusClass(event.ResourceStatus)}">${event.ResourceStatus ?? '-'}</td>
518527
<td>${event.ResourceStatusReason ?? '-'}</td>
528+
${hookCell}
519529
</tr>`
520530
}
521531
}

packages/core/src/test/awsService/cloudformation/ui/stackEventsWebviewProvider.test.ts

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,80 @@ describe('StackEventsWebviewProvider', () => {
234234
assert.ok(!html.includes('(50 events loaded)'))
235235
})
236236

237+
it('should show Hook Invocation column when events have hooks', async () => {
238+
mockClient.sendRequest.resolves({
239+
events: [
240+
{
241+
EventId: 'event-1',
242+
StackName: 'test-stack',
243+
Timestamp: new Date(),
244+
ResourceStatus: 'CREATE_COMPLETE',
245+
HookType: 'AWS::CloudFormation::Hook',
246+
HookStatus: 'HOOK_COMPLETE_SUCCEEDED',
247+
},
248+
],
249+
nextToken: undefined,
250+
})
251+
252+
const view = createMockView()
253+
provider.resolveWebviewView(view as any)
254+
await provider.showStackEvents('test-stack')
255+
256+
const html = view.webview.html
257+
assert.ok(html.includes('Hook Invocation'))
258+
assert.ok(html.includes('AWS::CloudFormation::Hook'))
259+
assert.ok(html.includes('HOOK_COMPLETE_SUCCEEDED'))
260+
})
261+
262+
it('should not show Hook Invocation column when no hooks present', async () => {
263+
mockClient.sendRequest.resolves({
264+
events: [
265+
{
266+
EventId: 'event-1',
267+
StackName: 'test-stack',
268+
Timestamp: new Date(),
269+
ResourceStatus: 'CREATE_COMPLETE',
270+
},
271+
],
272+
nextToken: undefined,
273+
})
274+
275+
const view = createMockView()
276+
provider.resolveWebviewView(view as any)
277+
await provider.showStackEvents('test-stack')
278+
279+
const html = view.webview.html
280+
assert.ok(!html.includes('Hook Invocation'))
281+
})
282+
283+
it('should include operation ID link in events', async () => {
284+
mockClient.sendRequest.resolves({
285+
events: [
286+
{
287+
EventId: 'event-1',
288+
StackName: 'test-stack',
289+
Timestamp: new Date(),
290+
ResourceStatus: 'CREATE_COMPLETE',
291+
OperationId: 'op-123',
292+
},
293+
],
294+
nextToken: undefined,
295+
})
296+
297+
const view = createMockView()
298+
provider.resolveWebviewView(view as any)
299+
300+
await coordinatorCallback({
301+
stackName: 'test-stack',
302+
stackArn: 'arn:aws:cloudformation:us-west-2:123456789012:stack/test-stack/xyz-456',
303+
isChangeSetMode: false,
304+
})
305+
306+
const html = view.webview.html
307+
assert.ok(html.includes('/stacks/operations/info'))
308+
assert.ok(html.includes('operationId=op-123'))
309+
})
310+
237311
it('should not hyperlink operation ID when stackArn is malformed', async () => {
238312
mockSingleEventWithOperationId()
239313

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"type": "Feature",
3+
"description": "CloudFormation: Show hook invocations in stack events on failure"
4+
}

0 commit comments

Comments
 (0)