From dc751dbde6e5bb6e469941e145553f0d9329f959 Mon Sep 17 00:00:00 2001 From: Charles Gagnon Date: Thu, 18 Jun 2020 07:12:32 -0700 Subject: [PATCH] Hook up deletion for MIAA (#10991) * Hook up deletion for MIAA * fix button disabling * cannot --- extensions/arc/src/common/utils.ts | 49 +++++++++++++++++++ extensions/arc/src/localizedConstants.ts | 10 ++-- extensions/arc/src/models/controllerModel.ts | 4 +- .../miaa/miaaDashboardOverviewPage.ts | 16 +++++- .../postgres/postgresOverviewPage.ts | 13 +++-- 5 files changed, 77 insertions(+), 15 deletions(-) diff --git a/extensions/arc/src/common/utils.ts b/extensions/arc/src/common/utils.ts index eb09c1ec05..4d52d5b219 100644 --- a/extensions/arc/src/common/utils.ts +++ b/extensions/arc/src/common/utils.ts @@ -66,3 +66,52 @@ export function getConnectionModeDisplayText(connectionMode: string | undefined) } return connectionMode; } + +/** + * Opens an input box prompting the user to enter in the name of a resource to delete + * @param namespace The namespace of the resource to delete + * @param name The name of the resource to delete + * @returns Promise resolving to true if the user confirmed the name, false if the input box was closed for any other reason + */ +export async function promptForResourceDeletion(namespace: string, name: string): Promise { + const inputBox = vscode.window.createInputBox(); + inputBox.title = loc.resourceDeletionWarning(namespace, name); + inputBox.placeholder = name; + return new Promise(resolve => { + let valueAccepted = false; + inputBox.show(); + inputBox.onDidAccept(() => { + if (inputBox.value === name) { + valueAccepted = true; + inputBox.hide(); + inputBox.dispose(); + resolve(true); + } else { + inputBox.validationMessage = loc.invalidResourceDeletionName(inputBox.value); + } + }); + inputBox.onDidHide(() => { + if (!valueAccepted) { + resolve(false); + } + }); + inputBox.onDidChangeValue(() => { + inputBox.validationMessage = ''; + }); + }); +} + +/** + * Gets the message to display for a given error object that may be a variety of types. + * @param error The error object + */ +export function getErrorText(error: any): string { + if (error?.body?.reason) { + // For HTTP Errors pull out the reason message since that's usually the most helpful + return error.body.reason; + } else if (error instanceof Error) { + return error.message; + } else { + return error; + } +} diff --git a/extensions/arc/src/localizedConstants.ts b/extensions/arc/src/localizedConstants.ts index f1f41b9303..752939bec6 100644 --- a/extensions/arc/src/localizedConstants.ts +++ b/extensions/arc/src/localizedConstants.ts @@ -84,14 +84,14 @@ export const learnAboutPostgresClients = localize('arc.learnAboutPostgresClients export const node = localize('arc.node', "node"); export const nodes = localize('arc.nodes', "nodes"); export const storagePerNode = localize('arc.storagePerNode', "storage per node"); +export const arcResources = localize('arc.arcResources', "Azure Arc Resources"); export function databaseCreated(name: string): string { return localize('arc.databaseCreated', "Database {0} created", name); } export function databaseCreationFailed(name: string, error: any): string { return localize('arc.databaseCreationFailed', "Failed to create database {0}. {1}", name, (error instanceof Error ? error.message : error)); } export function passwordReset(name: string): string { return localize('arc.passwordReset', "Password reset for service {0}", name); } export function passwordResetFailed(name: string, error: any): string { return localize('arc.passwordResetFailed', "Failed to reset password for service {0}. {1}", name, (error instanceof Error ? error.message : error)); } -export function deleteServicePrompt(name: string): string { return localize('arc.deleteServicePrompt', "Delete service {0}?", name); } -export function serviceDeleted(name: string): string { return localize('arc.serviceDeleted', "Service {0} deleted", name); } -export function serviceDeletionFailed(name: string, error: any): string { return localize('arc.serviceDeletionFailed', "Failed to delete service {0}. {1}", name, (error instanceof Error ? error.message : error)); } +export function resourceDeleted(name: string): string { return localize('arc.resourceDeleted', "Resource '{0}' deleted", name); } +export function resourceDeletionFailed(name: string, error: any): string { return localize('arc.resourceDeletionFailed', "Failed to delete resource {0}. {1}", name, (error instanceof Error ? error.message : error)); } export function couldNotFindAzureResource(name: string): string { return localize('arc.couldNotFindAzureResource', "Could not find Azure resource for {0}", name); } export function copiedToClipboard(name: string): string { return localize('arc.copiedToClipboard', "{0} copied to clipboard", name); } export function refreshFailed(error: any): string { return localize('arc.refreshFailed', "Refresh failed. {0}", (error instanceof Error ? error.message : error)); } @@ -106,5 +106,5 @@ export function numVCores(vCores: string): string { } } export function couldNotFindRegistration(namespace: string, name: string) { return localize('arc.couldNotFindRegistration', "Could not find controller registration for {0} ({1})", name, namespace); } - -export const arcResources = localize('arc.arcResources', "Azure Arc Resources"); +export function resourceDeletionWarning(namespace: string, name: string): string { return localize('arc.resourceDeletionWarning', "Warning! Deleting a resource is permanent and cannot be undone. To delete the resource '{0}.{1}' type the name '{1}' below to proceed.", namespace, name); } +export function invalidResourceDeletionName(name: string): string { return localize('arc.invalidResourceDeletionName', "The value '{0}' does not match the instance name. Try again or press escape to exit", name); } diff --git a/extensions/arc/src/models/controllerModel.ts b/extensions/arc/src/models/controllerModel.ts index 8c851d7207..2edf5bd088 100644 --- a/extensions/arc/src/models/controllerModel.ts +++ b/extensions/arc/src/models/controllerModel.ts @@ -97,8 +97,8 @@ export class ControllerModel { }); } - public miaaDelete(name: string): void { - this._sqlInstanceRouter.apiV1HybridSqlNsNameDelete(this._namespace, name); + public async miaaDelete(namespace: string, name: string): Promise { + await this._sqlInstanceRouter.apiV1HybridSqlNsNameDelete(namespace, name); } } diff --git a/extensions/arc/src/ui/dashboards/miaa/miaaDashboardOverviewPage.ts b/extensions/arc/src/ui/dashboards/miaa/miaaDashboardOverviewPage.ts index 45cd92ac60..1ca1c473e7 100644 --- a/extensions/arc/src/ui/dashboards/miaa/miaaDashboardOverviewPage.ts +++ b/extensions/arc/src/ui/dashboards/miaa/miaaDashboardOverviewPage.ts @@ -9,7 +9,7 @@ import * as loc from '../../../localizedConstants'; import { DashboardPage } from '../../components/dashboardPage'; import { IconPathHelper, cssStyles, ResourceType } from '../../../constants'; import { ControllerModel, Registration } from '../../../models/controllerModel'; -import { getAzurecoreApi } from '../../../common/utils'; +import { getAzurecoreApi, promptForResourceDeletion, getErrorText } from '../../../common/utils'; import { MiaaModel, DatabaseModel } from '../../../models/miaaModel'; import { HybridSqlNsNameGetResponse } from '../../../controller/generated/v1/model/hybridSqlNsNameGetResponse'; import { EndpointModel } from '../../../controller/generated/v1/api'; @@ -176,6 +176,20 @@ export class MiaaDashboardOverviewPage extends DashboardPage { iconPath: IconPathHelper.delete }).component(); + deleteButton.onDidClick(async () => { + deleteButton.enabled = false; + try { + if (await promptForResourceDeletion(this._miaaModel.namespace, this._miaaModel.name)) { + await this._controllerModel.miaaDelete(this._miaaModel.namespace, this._miaaModel.name); + vscode.window.showInformationMessage(loc.resourceDeleted(this._miaaModel.name)); + } + } catch (error) { + vscode.window.showErrorMessage(loc.resourceDeletionFailed(this._miaaModel.name, getErrorText(error))); + } finally { + deleteButton.enabled = true; + } + }); + const resetPasswordButton = this.modelView.modelBuilder.button().withProperties({ label: loc.resetPassword, iconPath: IconPathHelper.edit diff --git a/extensions/arc/src/ui/dashboards/postgres/postgresOverviewPage.ts b/extensions/arc/src/ui/dashboards/postgres/postgresOverviewPage.ts index dc583df589..c15ae91889 100644 --- a/extensions/arc/src/ui/dashboards/postgres/postgresOverviewPage.ts +++ b/extensions/arc/src/ui/dashboards/postgres/postgresOverviewPage.ts @@ -11,6 +11,7 @@ import { DuskyObjectModelsDatabase, DuskyObjectModelsDatabaseServiceArcPayload, import { DashboardPage } from '../../components/dashboardPage'; import { ControllerModel } from '../../../models/controllerModel'; import { PostgresModel, PodRole } from '../../../models/postgresModel'; +import { promptForResourceDeletion, getErrorText } from '../../../common/utils'; export class PostgresOverviewPage extends DashboardPage { private propertiesLoading?: azdata.LoadingComponent; @@ -220,14 +221,12 @@ export class PostgresOverviewPage extends DashboardPage { deleteButton.onDidClick(async () => { deleteButton.enabled = false; try { - const response = await vscode.window.showQuickPick([loc.yes, loc.no], { - placeHolder: loc.deleteServicePrompt(this._postgresModel.fullName) - }); - if (response !== loc.yes) { return; } - await this._postgresModel.delete(); - vscode.window.showInformationMessage(loc.serviceDeleted(this._postgresModel.fullName)); + if (await promptForResourceDeletion(this._postgresModel.namespace, this._postgresModel.name)) { + await this._postgresModel.delete(); + vscode.window.showInformationMessage(loc.resourceDeleted(this._postgresModel.fullName)); + } } catch (error) { - vscode.window.showErrorMessage(loc.serviceDeletionFailed(this._postgresModel.fullName, error)); + vscode.window.showErrorMessage(loc.resourceDeletionFailed(this._postgresModel.fullName, getErrorText(error))); } finally { deleteButton.enabled = true; }