diff --git a/packages/core/src/awsService/cloudformation/commands/cfnCommands.ts b/packages/core/src/awsService/cloudformation/commands/cfnCommands.ts index cbc067cb85d..ee7c829cf1c 100644 --- a/packages/core/src/awsService/cloudformation/commands/cfnCommands.ts +++ b/packages/core/src/awsService/cloudformation/commands/cfnCommands.ts @@ -31,7 +31,6 @@ import { shouldImportResources, getResourcesToImport, getEnvironmentName, - getChangeSetName, chooseOptionalFlagSuggestion as chooseOptionalFlagMode, getTags, getOnStackFailure, @@ -111,14 +110,8 @@ export function executeChangeSetCommand(client: LanguageClient, coordinator: Sta } export function deleteChangeSetCommand(client: LanguageClient) { - return commands.registerCommand(commandKey('stacks.deleteChangeSet'), async (params?: ChangeSetReference) => { + return commands.registerCommand(commandKey('stacks.deleteChangeSet'), async (params: ChangeSetReference) => { try { - params = params ?? (await promptForChangeSetReference()) - - if (!params) { - return - } - const changeSetDeletion = new ChangeSetDeletion(params.stackName, params.changeSetName, client) await changeSetDeletion.delete() @@ -129,14 +122,8 @@ export function deleteChangeSetCommand(client: LanguageClient) { } export function viewChangeSetCommand(client: LanguageClient, diffProvider: DiffWebviewProvider) { - return commands.registerCommand(commandKey('stacks.viewChangeSet'), async (params?: ChangeSetReference) => { + return commands.registerCommand(commandKey('stacks.viewChangeSet'), async (params: ChangeSetReference) => { try { - params = params ?? (await promptForChangeSetReference()) - - if (!params) { - return - } - const describeChangeSetResult = await describeChangeSet(client, { changeSetName: params.changeSetName, stackName: params.stackName, @@ -157,16 +144,6 @@ export function viewChangeSetCommand(client: LanguageClient, diffProvider: DiffW }) } -async function promptForChangeSetReference(): Promise { - const stackName = await getStackName() - const changeSetName = await getChangeSetName() - if (!stackName || !changeSetName) { - return undefined - } - - return { stackName: stackName, changeSetName: changeSetName } -} - export function deployTemplateCommand( client: LanguageClient, diffProvider: DiffWebviewProvider, diff --git a/packages/core/src/awsService/cloudformation/stacks/actions/changeSetDeletionWorkflow.ts b/packages/core/src/awsService/cloudformation/stacks/actions/changeSetDeletionWorkflow.ts index 43e23a64eea..a1fa6bdd65c 100644 --- a/packages/core/src/awsService/cloudformation/stacks/actions/changeSetDeletionWorkflow.ts +++ b/packages/core/src/awsService/cloudformation/stacks/actions/changeSetDeletionWorkflow.ts @@ -15,20 +15,20 @@ import { import { deleteChangeSet, describeChangeSetDeletionStatus, getChangeSetDeletionStatus } from './stackActionApi' import { createChangeSetDeletionParams } from './stackActionUtil' import { getLogger } from '../../../../shared/logger/logger' -import { extractErrorMessage } from '../../utils' +import { commandKey, extractErrorMessage } from '../../utils' +import { commands } from 'vscode' +import globals from '../../../../shared/extensionGlobals' export class ChangeSetDeletion { private readonly id: string - private readonly stackName: string - private readonly changeSetName: string - private readonly client: LanguageClient private status: StackActionPhase | undefined - constructor(stackName: string, changeSetName: string, client: LanguageClient) { + constructor( + private readonly stackName: string, + private readonly changeSetName: string, + private readonly client: LanguageClient + ) { this.id = uuidv4() - this.stackName = stackName - this.changeSetName = changeSetName - this.client = client } async delete() { @@ -38,7 +38,7 @@ export class ChangeSetDeletion { } private pollForProgress() { - const interval = setInterval(() => { + const interval = globals.clock.setInterval(() => { getChangeSetDeletionStatus(this.client, { id: this.id }) .then(async (deletionResult) => { if (deletionResult.phase === this.status) { @@ -66,7 +66,8 @@ export class ChangeSetDeletion { describeDeplomentStatusResult.FailureReason ?? 'No failure reason provided' ) } - clearInterval(interval) + void commands.executeCommand(commandKey('stacks.refresh')) + globals.clock.clearInterval(interval) break case StackActionPhase.DELETION_FAILED: { const describeDeplomentStatusResult = await describeChangeSetDeletionStatus(this.client, { @@ -77,7 +78,8 @@ export class ChangeSetDeletion { this.stackName, describeDeplomentStatusResult.FailureReason ?? 'No failure reason provided' ) - clearInterval(interval) + void commands.executeCommand(commandKey('stacks.refresh')) + globals.clock.clearInterval(interval) break } } @@ -85,7 +87,8 @@ export class ChangeSetDeletion { .catch(async (error) => { getLogger().error(`Error polling for deletion status: ${error}`) showErrorMessage(`Error polling for deletion status: ${extractErrorMessage(error)}`) - clearInterval(interval) + void commands.executeCommand(commandKey('stacks.refresh')) + globals.clock.clearInterval(interval) }) }, 1000) } diff --git a/packages/core/src/test/awsService/cloudformation/stacks/actions/changeSetDeletionWorkflow.test.ts b/packages/core/src/test/awsService/cloudformation/stacks/actions/changeSetDeletionWorkflow.test.ts new file mode 100644 index 00000000000..9dec3c1e3d3 --- /dev/null +++ b/packages/core/src/test/awsService/cloudformation/stacks/actions/changeSetDeletionWorkflow.test.ts @@ -0,0 +1,84 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import assert from 'assert' +import { SinonSandbox, SinonStub, createSandbox } from 'sinon' +import { commands } from 'vscode' +import { ChangeSetDeletion } from '../../../../../awsService/cloudformation/stacks/actions/changeSetDeletionWorkflow' +import { + StackActionPhase, + StackActionState, +} from '../../../../../awsService/cloudformation/stacks/actions/stackActionRequestType' +import { commandKey } from '../../../../../awsService/cloudformation/utils' +import { globals } from '../../../../../shared' + +describe('ChangeSetDeletion', function () { + let sandbox: SinonSandbox + + beforeEach(function () { + sandbox = createSandbox() + }) + + afterEach(function () { + sandbox.restore() + }) + + describe('delete', function () { + let mockClient: any + let executeCommandStub: SinonStub + let getChangeSetDeletionStatusStub: SinonStub + let describeChangeSetDeletionStatusStub: SinonStub + + beforeEach(function () { + mockClient = { sendRequest: sandbox.stub().resolves({}) } + executeCommandStub = sandbox.stub(commands, 'executeCommand').resolves() + + const stackActionApi = require('../../../../../awsService/cloudformation/stacks/actions/stackActionApi') + getChangeSetDeletionStatusStub = sandbox.stub(stackActionApi, 'getChangeSetDeletionStatus') + describeChangeSetDeletionStatusStub = sandbox.stub(stackActionApi, 'describeChangeSetDeletionStatus') + sandbox.stub(stackActionApi, 'deleteChangeSet').resolves() + + sandbox.stub(globals.clock, 'setInterval').callsFake((callback: () => void) => { + setImmediate(() => callback()) + return 1 as any + }) + sandbox.stub(globals.clock, 'clearInterval') + }) + + it('should call refresh command after successful deletion', async function () { + getChangeSetDeletionStatusStub.resolves({ + phase: StackActionPhase.DELETION_COMPLETE, + state: StackActionState.SUCCESSFUL, + }) + + const deletion = new ChangeSetDeletion('test-stack', 'test-changeset', mockClient) + await deletion.delete() + await new Promise((resolve) => setImmediate(resolve)) + + assert.ok(executeCommandStub.calledWith(commandKey('stacks.refresh'))) + }) + + it('should call refresh command after failed deletion', async function () { + getChangeSetDeletionStatusStub.resolves({ phase: StackActionPhase.DELETION_FAILED }) + describeChangeSetDeletionStatusStub.resolves({ FailureReason: 'Test failure' }) + + const deletion = new ChangeSetDeletion('test-stack', 'test-changeset', mockClient) + await deletion.delete() + await new Promise((resolve) => setImmediate(resolve)) + + assert.ok(executeCommandStub.calledWith(commandKey('stacks.refresh'))) + }) + + it('should not call refresh command when polling encounters error', async function () { + getChangeSetDeletionStatusStub.rejects(new Error('Polling error')) + + const deletion = new ChangeSetDeletion('test-stack', 'test-changeset', mockClient) + await deletion.delete() + await new Promise((resolve) => setImmediate(resolve)) + + assert.ok(executeCommandStub.calledWith(commandKey('stacks.refresh'))) + }) + }) +}) diff --git a/packages/toolkit/.changes/next-release/Bug Fix-20f1c834-8e8b-47a2-ab09-4a24df3e959e.json b/packages/toolkit/.changes/next-release/Bug Fix-20f1c834-8e8b-47a2-ab09-4a24df3e959e.json new file mode 100644 index 00000000000..c04dbc69061 --- /dev/null +++ b/packages/toolkit/.changes/next-release/Bug Fix-20f1c834-8e8b-47a2-ab09-4a24df3e959e.json @@ -0,0 +1,4 @@ +{ + "type": "Bug Fix", + "description": "CloudFormation: refresh stacks after change set deletion" +}