Add Delete Database dialog to enable additional deletion options (#24109)

This commit is contained in:
Cory Rivera
2023-08-14 11:20:08 -07:00
committed by GitHub
parent 6acc9f4a75
commit 22e2b0df9f
10 changed files with 165 additions and 5 deletions

View File

@@ -1,6 +1,6 @@
{
"downloadUrl": "https://github.com/Microsoft/sqltoolsservice/releases/download/{#version#}/microsoft.sqltools.servicelayer-{#fileName#}",
"version": "4.9.0.10",
"version": "4.9.0.13",
"downloadFileNames": {
"Windows_86": "win-x86-net7.0.zip",
"Windows_64": "win-x64-net7.0.zip",

View File

@@ -95,6 +95,12 @@
"title": "%title.objectProperties%",
"icon": "$(edit)"
},
{
"command": "mssql.deleteDatabase",
"category": "MSSQL",
"title": "%title.deleteObject%",
"icon": "$(trash)"
},
{
"command": "mssql.deleteObject",
"category": "MSSQL",
@@ -504,6 +510,10 @@
"command": "mssql.objectProperties",
"when": "false"
},
{
"command": "mssql.deleteDatabase",
"when": "false"
},
{
"command": "mssql.deleteObject",
"when": "false"
@@ -569,9 +579,14 @@
"when": "connectionProvider == MSSQL && nodeType == Database && !isCloud && !(nodePath =~ /^.*\\/System Databases\\/.*$/) && config.workbench.enablePreviewFeatures && (productQualityType =~ /^(insider|dev)$/ || isDevelopment)",
"group": "1_objectManagement@2"
},
{
"command": "mssql.deleteDatabase",
"when": "connectionProvider == MSSQL && nodeType == Database && !(nodePath =~ /^.*\\/System Databases\\/.*$/) && config.workbench.enablePreviewFeatures",
"group": "1_objectManagement@3"
},
{
"command": "mssql.deleteObject",
"when": "connectionProvider == MSSQL && nodeType =~ /^(ServerLevelLogin|User|ServerLevelServerRole|ApplicationRole|DatabaseRole|Database)$/ && !(nodePath =~ /^.*\\/System Databases\\/.*$/) && config.workbench.enablePreviewFeatures",
"when": "connectionProvider == MSSQL && nodeType =~ /^(ServerLevelLogin|User|ServerLevelServerRole|ApplicationRole|DatabaseRole)$/ && !(nodePath =~ /^.*\\/System Databases\\/.*$/) && config.workbench.enablePreviewFeatures",
"group": "1_objectManagement@3"
},
{
@@ -614,9 +629,14 @@
"when": "connectionProvider == MSSQL && nodeType =~ /^(ServerLevelLogin|User|ServerLevelServerRole|ApplicationRole|DatabaseRole)$/ && config.workbench.enablePreviewFeatures",
"group": "inline@2"
},
{
"command": "mssql.deleteDatabase",
"when": "connectionProvider == MSSQL && nodeType == Database && !(nodePath =~ /^.*\\/System Databases\\/.*$/) && config.workbench.enablePreviewFeatures",
"group": "inline@3"
},
{
"command": "mssql.deleteObject",
"when": "connectionProvider == MSSQL && nodeType =~ /^(ServerLevelLogin|User|ServerLevelServerRole|ApplicationRole|DatabaseRole|Database)$/ && !(nodePath =~ /^.*\\/System Databases\\/.*$/) && config.workbench.enablePreviewFeatures",
"when": "connectionProvider == MSSQL && nodeType =~ /^(ServerLevelLogin|User|ServerLevelServerRole|ApplicationRole|DatabaseRole)$/ && !(nodePath =~ /^.*\\/System Databases\\/.*$/) && config.workbench.enablePreviewFeatures",
"group": "inline@3"
}
],
@@ -661,9 +681,14 @@
"when": "connectionProvider == MSSQL && nodeType == Column && config.workbench.enablePreviewFeatures && nodePath =~ /^.*\\/Tables\\/.*\\/Columns\\/.*$/",
"group": "1_objectManagement@1"
},
{
"command": "mssql.deleteDatabase",
"when": "connectionProvider == MSSQL && nodeType == Database && !(nodePath =~ /^.*\\/System Databases\\/.*$/) && config.workbench.enablePreviewFeatures",
"group": "1_objectManagement@3"
},
{
"command": "mssql.deleteObject",
"when": "connectionProvider == MSSQL && nodeType =~ /^(ServerLevelLogin|User|ServerLevelServerRole|ApplicationRole|DatabaseRole|Database)$/ && !(nodePath =~ /^.*\\/System Databases\\/.*$/) && config.workbench.enablePreviewFeatures",
"when": "connectionProvider == MSSQL && nodeType =~ /^(ServerLevelLogin|User|ServerLevelServerRole|ApplicationRole|DatabaseRole)$/ && !(nodePath =~ /^.*\\/System Databases\\/.*$/) && config.workbench.enablePreviewFeatures",
"group": "1_objectManagement@3"
},
{

View File

@@ -1645,6 +1645,18 @@ export namespace DetachDatabaseRequest {
export const type = new RequestType<DetachDatabaseRequestParams, string, void, void>('objectManagement/detachDatabase');
}
export interface DropDatabaseRequestParams {
connectionUri: string;
objectUrn: string;
dropConnections: boolean;
deleteBackupHistory: boolean;
generateScript: boolean;
}
export namespace DropDatabaseRequest {
export const type = new RequestType<DropDatabaseRequestParams, string, void, void>('objectManagement/dropDatabase');
}
// ------------------------------- < Object Management > ------------------------------------
// ------------------------------- < Encryption IV/KEY updation Event > ------------------------------------

View File

@@ -984,6 +984,8 @@ declare module 'mssql' {
* @returns A string value representing the generated TSQL query if generateScript was set to true, and an empty string otherwise.
*/
detachDatabase(connectionUri: string, objectUrn: string, dropConnections: boolean, updateStatistics: boolean, generateScript: boolean): Thenable<string>;
deleteDatabase(connectionUri: string, objectUrn: string, dropConnections: boolean, deleteBackupHistory: boolean, generateScript: boolean): Thenable<string>;
}
// Object Management - End.
}

View File

@@ -24,6 +24,7 @@ import { ApplicationRoleDialog } from './ui/applicationRoleDialog';
import { DatabaseDialog } from './ui/databaseDialog';
import { ServerPropertiesDialog } from './ui/serverPropertiesDialog';
import { DetachDatabaseDialog } from './ui/detachDatabaseDialog';
import { DeleteDatabaseDialog } from './ui/deleteDatabaseDialog';
export function registerObjectManagementCommands(appContext: AppContext) {
// Notes: Change the second parameter to false to use the actual object management service.
@@ -46,6 +47,9 @@ export function registerObjectManagementCommands(appContext: AppContext) {
appContext.extensionContext.subscriptions.push(vscode.commands.registerCommand('mssql.detachDatabase', async (context: azdata.ObjectExplorerContext) => {
await handleDetachDatabase(context, service);
}));
appContext.extensionContext.subscriptions.push(vscode.commands.registerCommand('mssql.deleteDatabase', async (context: azdata.ObjectExplorerContext) => {
await handleDeleteDatabase(context, service);
}));
}
function getObjectManagementService(appContext: AppContext, useTestService: boolean): IObjectManagementService {
@@ -286,6 +290,35 @@ async function handleDetachDatabase(context: azdata.ObjectExplorerContext, servi
}
}
async function handleDeleteDatabase(context: azdata.ObjectExplorerContext, service: IObjectManagementService): Promise<void> {
const connectionUri = await getConnectionUri(context);
if (!connectionUri) {
return;
}
try {
const parentUrn = await getParentUrn(context);
const options: ObjectManagementDialogOptions = {
connectionUri: connectionUri,
isNewObject: false,
database: context.connectionProfile!.databaseName!,
objectType: context.nodeInfo.nodeType as ObjectManagement.NodeType,
objectName: context.nodeInfo.label,
parentUrn: parentUrn,
objectUrn: context.nodeInfo!.metadata!.urn,
objectExplorerContext: context
};
const dialog = new DeleteDatabaseDialog(service, options);
await dialog.open();
}
catch (err) {
TelemetryReporter.createErrorEvent2(ObjectManagementViewName, TelemetryActions.OpenDeleteDatabaseDialog, err).withAdditionalProperties({
objectType: context.nodeInfo!.nodeType
}).send();
console.error(err);
await vscode.window.showErrorMessage(objectManagementLoc.OpenDeleteDatabaseDialogError(getErrorMessage(err)));
}
}
function getDialog(service: IObjectManagementService, dialogOptions: ObjectManagementDialogOptions): ObjectManagementDialogBase<ObjectManagement.SqlObject, ObjectManagement.ObjectViewInfo<ObjectManagement.SqlObject>> {
switch (dialogOptions.objectType) {
case ObjectManagement.NodeType.ApplicationRole:

View File

@@ -33,6 +33,7 @@ export const ViewMemoryServerPropertiesDocUrl = 'https://learn.microsoft.com/sql
export const DetachDatabaseDocUrl = 'https://go.microsoft.com/fwlink/?linkid=2240322';
export const DatabaseGeneralPropertiesDocUrl = 'https://learn.microsoft.com/sql/relational-databases/databases/database-properties-general-page';
export const DatabaseOptionsPropertiesDocUrl = 'https://learn.microsoft.com/sql/relational-databases/databases/database-properties-options-page'
export const DeleteDatabaseDocUrl = 'https://learn.microsoft.com/sql/t-sql/statements/drop-database-transact-sql';
export const enum TelemetryActions {
CreateObject = 'CreateObject',
@@ -41,7 +42,8 @@ export const enum TelemetryActions {
OpenPropertiesDialog = 'OpenPropertiesDialog',
RenameObject = 'RenameObject',
UpdateObject = 'UpdateObject',
OpenDetachDatabaseDialog = 'OpenDetachDatabaseDialog'
OpenDetachDatabaseDialog = 'OpenDetachDatabaseDialog',
OpenDeleteDatabaseDialog = 'OpenDeleteDatabaseDialog'
}
export const ObjectManagementViewName = 'ObjectManagement';

View File

@@ -458,6 +458,8 @@ export interface Database extends ObjectManagement.SqlObject {
export interface DatabaseViewInfo extends ObjectManagement.ObjectViewInfo<Database> {
isAzureDB: boolean;
isManagedInstance: boolean;
isSqlOnDemand: boolean;
loginNames?: OptionsCollection;
collationNames?: OptionsCollection;
compatibilityLevels?: OptionsCollection;

View File

@@ -102,6 +102,13 @@ export function OpenDetachDatabaseDialogError(error: string): string {
}, "An error occurred while opening the detach database dialog. {0}", error);
}
export function OpenDeleteDatabaseDialogError(error: string): string {
return localize({
key: 'objectManagement.openDeleteDatabaseDialogError',
comment: ['{0}: error message.']
}, "An error occurred while opening the delete database dialog. {0}", error);
}
export function OpenObjectPropertiesDialogError(objectType: string, objectName: string, error: string): string {
return localize({
key: 'objectManagement.openObjectPropertiesDialogError',
@@ -182,6 +189,12 @@ export const DatabaseFilePathLabel = localize('objectManagement.databaseFilePath
export const DatabaseFileGroupLabel = localize('objectManagement.databaseFileGroup', "File Group");
export const DetachDatabaseOptions = localize('objectManagement.detachDatabaseOptions', "Detach Database Options");
export const DetachButtonLabel = localize('objectManagement.detachButtonLabel', "Detach");
export const DeleteDatabaseDialogTitle = (dbName: string) => localize('objectManagement.deleteDatabaseDialogTitle', "Delete Database - {0} (Preview)", dbName);
export const DeleteButtonLabel = localize('objectManagement.deleteButtonLabel', "Delete");
export const DeleteDatabaseOptions = localize('objectManagement.deleteDatabaseOptions', "Delete Database Options");
export const DeleteDropConnections = localize('objectManagement.deleteDropConnections', "Close existing connections");
export const DeleteDropBackupHistory = localize('objectManagement.deleteDropBackupHistory', "Delete backup and restore history information for database");
export const DatabaseDetailsLabel = localize('objectManagement.databaseDetails', "Database Details");
// Login
export const BlankPasswordConfirmationText: string = localize('objectManagement.blankPasswordConfirmation', "Creating a login with a blank password is a security risk. Are you sure you want to continue?");

View File

@@ -70,6 +70,11 @@ export class ObjectManagementService extends BaseService implements IObjectManag
const params: contracts.DetachDatabaseRequestParams = { connectionUri, objectUrn, dropConnections, updateStatistics, generateScript };
return this.runWithErrorHandling(contracts.DetachDatabaseRequest.type, params);
}
async deleteDatabase(connectionUri: string, objectUrn: string, dropConnections: boolean, deleteBackupHistory: boolean, generateScript: boolean): Promise<string> {
const params: contracts.DropDatabaseRequestParams = { connectionUri, objectUrn, dropConnections, deleteBackupHistory, generateScript };
return this.runWithErrorHandling(contracts.DropDatabaseRequest.type, params);
}
}
const ServerLevelSecurableTypes: SecurableTypeMetadata[] = [
@@ -241,6 +246,10 @@ export class TestObjectManagementService implements IObjectManagementService {
return this.delayAndResolve('');
}
deleteDatabase(connectionUri: string, objectUrn: string, dropConnections: boolean, deleteBackupHistory: boolean, generateScript: boolean): Thenable<string> {
return this.delayAndResolve('');
}
private generateSearchResult(objectType: ObjectManagement.NodeType, schema: string | undefined, count: number): ObjectManagement.SearchResultItem[] {
let items: ObjectManagement.SearchResultItem[] = [];
for (let i = 0; i < count; i++) {

View File

@@ -0,0 +1,62 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ObjectManagementDialogBase, ObjectManagementDialogOptions } from './objectManagementDialogBase';
import { IObjectManagementService, ObjectManagement } from 'mssql';
import { Database, DatabaseViewInfo } from '../interfaces';
import { DeleteDatabaseDocUrl } from '../constants';
import { DeleteButtonLabel, DeleteDatabaseDialogTitle, DeleteDropBackupHistory, DeleteDropConnections, DeleteDatabaseOptions, NameText, OwnerText, StatusText, DatabaseDetailsLabel } from '../localizedConstants';
export class DeleteDatabaseDialog extends ObjectManagementDialogBase<Database, DatabaseViewInfo> {
private _dropConnections = false;
private _deleteBackupHistory = false;
constructor(objectManagementService: IObjectManagementService, options: ObjectManagementDialogOptions) {
super(objectManagementService, options, DeleteDatabaseDialogTitle(options.database), 'DeleteDatabase');
this.dialogObject.okButton.label = DeleteButtonLabel;
}
protected override get isDirty(): boolean {
return true;
}
protected async initializeUI(): Promise<void> {
let components = [];
let tableData = [[this.objectInfo.name, this.objectInfo.owner ?? '', this.objectInfo.status ?? '']];
let columnNames = [NameText, OwnerText, StatusText];
let fileTable = this.createTable(DatabaseDetailsLabel, columnNames, tableData);
let tableGroup = this.createGroup(DatabaseDetailsLabel, [fileTable], false);
components.push(tableGroup);
if (!this.viewInfo.isAzureDB && !this.viewInfo.isManagedInstance && !this.viewInfo.isSqlOnDemand) {
let connCheckbox = this.createCheckbox(DeleteDropConnections, async checked => {
this._dropConnections = checked;
});
let updateCheckbox = this.createCheckbox(DeleteDropBackupHistory, async checked => {
this._deleteBackupHistory = checked;
});
let checkboxGroup = this.createGroup(DeleteDatabaseOptions, [connCheckbox, updateCheckbox], false);
components.push(checkboxGroup);
}
this.formContainer.addItems(components);
}
protected override get helpUrl(): string {
return DeleteDatabaseDocUrl;
}
protected override async saveChanges(contextId: string, object: ObjectManagement.SqlObject): Promise<void> {
await this.objectManagementService.deleteDatabase(this.options.connectionUri, this.options.objectUrn, this._dropConnections, this._deleteBackupHistory, false);
}
protected override async generateScript(): Promise<string> {
return await this.objectManagementService.deleteDatabase(this.options.connectionUri, this.options.objectUrn, this._dropConnections, this._deleteBackupHistory, true);
}
protected override async validateInput(): Promise<string[]> {
return [];
}
}