support rename for login, user and a few other types (#22331)

* rename object

* add comment

* use URN property

* vbump STS

* revert loc string change

* fix name check

* pr comments
This commit is contained in:
Alan Ren
2023-03-16 15:00:07 -07:00
committed by GitHub
parent f5628ed8e3
commit 20cf2489a2
12 changed files with 164 additions and 10 deletions

View File

@@ -32,6 +32,9 @@ export function registerObjectManagementCommands(appContext: AppContext) {
appContext.extensionContext.subscriptions.push(vscode.commands.registerCommand('mssql.deleteObject', async (context: azdata.ObjectExplorerContext) => {
await handleDeleteObjectCommand(context, service);
}));
appContext.extensionContext.subscriptions.push(vscode.commands.registerCommand('mssql.renameObject', async (context: azdata.ObjectExplorerContext) => {
await handleRenameObjectCommand(context, service);
}));
}
function getObjectManagementService(appContext: AppContext, useTestService: boolean): IObjectManagementService {
@@ -159,8 +162,60 @@ async function handleDeleteObjectCommand(context: azdata.ObjectExplorerContext,
}).send();
return;
}
await refreshParentNode(context);
operation.updateStatus(azdata.TaskStatus.Succeeded);
await refreshParentNode(context);
}
});
}
async function handleRenameObjectCommand(context: azdata.ObjectExplorerContext, service: IObjectManagementService): Promise<void> {
const connectionUri = await getConnectionUri(context);
if (!connectionUri) {
return;
}
const nodeTypeDisplayName = getNodeTypeDisplayName(context.nodeInfo.nodeType);
const originalName = context.nodeInfo.metadata.name;
const newName = await vscode.window.showInputBox({
title: localizedConstants.RenameObjectDialogTitle,
value: originalName,
validateInput: (value: string): string | undefined => {
if (!value) {
return localizedConstants.NameCannotBeEmptyError;
} else {
// valid
return undefined;
}
}
});
// return if no change was made or the dialog was canceled.
if (newName === originalName || !newName) {
return;
}
azdata.tasks.startBackgroundOperation({
displayName: localizedConstants.RenameObjectOperationDisplayName(nodeTypeDisplayName, originalName, newName),
description: '',
isCancelable: false,
operation: async (operation) => {
try {
const startTime = Date.now();
await service.rename(connectionUri, context.nodeInfo.metadata.urn, newName);
TelemetryReporter.sendTelemetryEvent(TelemetryActions.RenameObject, {
objectType: context.nodeInfo.nodeType
}, {
elapsedTimeMs: Date.now() - startTime
});
}
catch (err) {
operation.updateStatus(azdata.TaskStatus.Failed, localizedConstants.RenameObjectError(nodeTypeDisplayName, originalName, newName, getErrorMessage(err)));
TelemetryReporter.createErrorEvent2(TelemetryViews.ObjectManagement, TelemetryActions.RenameObject, err).withAdditionalProperties({
objectType: context.nodeInfo.nodeType
}).send();
return;
}
operation.updateStatus(azdata.TaskStatus.Succeeded);
await refreshParentNode(context);
}
});
}

View File

@@ -7,8 +7,12 @@
* The object types in object explorer's node context.
*/
export enum NodeType {
Column = 'Column',
Database = 'Database',
Login = 'ServerLevelLogin',
User = 'User'
Table = 'Table',
User = 'User',
View = 'View'
}
export const PublicServerRoleName = 'public';
@@ -51,10 +55,11 @@ export const AlterLoginDocUrl = 'https://learn.microsoft.com/en-us/sql/t-sql/sta
export enum TelemetryActions {
CreateObject = 'CreateObject',
UpdateObject = 'UpdateObject',
DeleteObject = 'DeleteObject',
OpenNewObjectDialog = 'OpenNewObjectDialog',
OpenPropertiesDialog = 'OpenPropertiesDialog'
OpenPropertiesDialog = 'OpenPropertiesDialog',
RenameObject = 'RenameObject',
UpdateObject = 'UpdateObject'
}
export enum TelemetryViews {

View File

@@ -11,6 +11,10 @@ export const LoginTypeDisplayName: string = localize('objectManagement.LoginType
export const UserTypeDisplayName: string = localize('objectManagement.UserDisplayName', "user");
export const LoginTypeDisplayNameInTitle: string = localize('objectManagement.LoginTypeDisplayNameInTitle', "Login");
export const UserTypeDisplayNameInTitle: string = localize('objectManagement.UserTypeDisplayNameInTitle', "User");
export const TableTypeDisplayName: string = localize('objectManagement.TableDisplayName', "table");
export const ViewTypeDisplayName: string = localize('objectManagement.ViewDisplayName', "view");
export const ColumnTypeDisplayName: string = localize('objectManagement.ColumnDisplayName', "column");
export const DatabaseTypeDisplayName: string = localize('objectManagement.DatabaseDisplayName', "database");
// Shared Strings
export const HelpText: string = localize('objectManagement.helpText', "Help");
@@ -18,6 +22,7 @@ export const YesText: string = localize('objectManagement.yesText', "Yes");
export const OkText: string = localize('objectManagement.OkText', "OK");
export const LoadingDialogText: string = localize('objectManagement.loadingDialog', "Loading dialog...");
export const FailedToRetrieveConnectionInfoErrorMessage: string = localize('objectManagement.noConnectionUriError', "Failed to retrieve the connection information, please reconnect and try again.")
export const RenameObjectDialogTitle: string = localize('objectManagement.renameObjectDialogTitle', "Enter new name");
export function RefreshObjectExplorerError(error: string): string {
return localize({
@@ -89,6 +94,20 @@ export function ObjectPropertiesDialogTitle(objectType: string, objectName: stri
}, '{0} - {1} (Preview)', objectType, objectName);
}
export function RenameObjectOperationDisplayName(objectType: string, originalName: string, newName: string): string {
return localize({
key: 'objectManagement.renameObjectOperationName',
comment: ['{0} object type, {1}: original name, {2}: new name']
}, "Rename {0} '{1}' to '{2}'", objectType, originalName, newName);
}
export function RenameObjectError(objectType: string, originalName: string, newName: string, error: string): string {
return localize({
key: 'objectManagement.renameObjectError',
comment: ['{0} object type, {1}: original name, {2}: new name, {3}: error message.']
}, "An error occurred while renaming {0} '{1}' to '{2}'. {3}", objectType, originalName, newName, error);
}
export const NameText = localize('objectManagement.nameLabel', "Name");
export const SelectedText = localize('objectManagement.selectedLabel', "Selected");
export const GeneralSectionHeader = localize('objectManagement.generalSectionHeader', "General");

View File

@@ -136,6 +136,16 @@ export class ObjectManagementService implements IObjectManagementService {
}
);
}
rename(connectionUri: string, objectUrn: string, newName: string): Thenable<void> {
const params: contracts.RenameObjectRequestParams = { connectionUri, objectUrn, newName };
return this.client.sendRequest(contracts.RenameObjectRequest.type, params).then(
r => { },
e => {
this.client.logFailedRequest(contracts.RenameObjectRequest.type, e);
return Promise.reject(new Error(e.message));
}
);
}
}
export class TestObjectManagementService implements IObjectManagementService {
@@ -292,6 +302,9 @@ export class TestObjectManagementService implements IObjectManagementService {
}
async disposeUserView(contextId: string): Promise<void> {
}
async rename(connectionUri: string, objectUrn: string, newName: string): Promise<void> {
return this.delayAndResolve();
}
private delayAndResolve(): Promise<void> {
return new Promise((resolve, reject) => {

View File

@@ -234,9 +234,9 @@ export abstract class ObjectManagementDialogBase<ObjectInfoType extends ObjectMa
protected createGroup(header: string, items: azdata.Component[], collapsible: boolean = true, collapsed: boolean = false): azdata.GroupContainer {
return this.modelView.modelBuilder.groupContainer().withLayout({
header: header,
collapsed: false,
collapsible: collapsible
}).withProps({ collapsed: collapsed }).withItems(items).component();
collapsible: collapsible,
collapsed: collapsed
}).withItems(items).component();
}
protected createFormContainer(items: azdata.Component[]): azdata.DivContainer {

View File

@@ -7,7 +7,7 @@ import * as azdata from 'azdata';
import * as vscode from 'vscode';
import { getErrorMessage } from '../utils';
import { AuthenticationType, NodeType, UserType } from './constants';
import { AADAuthenticationTypeDisplayText, ContainedUserText, LoginTypeDisplayName, LoginTypeDisplayNameInTitle, RefreshObjectExplorerError, SQLAuthenticationTypeDisplayText, UserTypeDisplayName, UserTypeDisplayNameInTitle, UserWithLoginText, UserWithNoConnectAccess, UserWithWindowsGroupLoginText, WindowsAuthenticationTypeDisplayText } from './localizedConstants';
import { AADAuthenticationTypeDisplayText, ColumnTypeDisplayName, ContainedUserText, DatabaseTypeDisplayName, LoginTypeDisplayName, LoginTypeDisplayNameInTitle, RefreshObjectExplorerError, SQLAuthenticationTypeDisplayText, TableTypeDisplayName, UserTypeDisplayName, UserTypeDisplayNameInTitle, UserWithLoginText, UserWithNoConnectAccess, UserWithWindowsGroupLoginText, ViewTypeDisplayName, WindowsAuthenticationTypeDisplayText } from './localizedConstants';
export function deepClone<T>(obj: T): T {
if (!obj || typeof obj !== 'object') {
@@ -59,6 +59,14 @@ export function getNodeTypeDisplayName(type: string, inTitle: boolean = false):
return inTitle ? LoginTypeDisplayNameInTitle : LoginTypeDisplayName;
case NodeType.User:
return inTitle ? UserTypeDisplayNameInTitle : UserTypeDisplayName;
case NodeType.Table:
return TableTypeDisplayName;
case NodeType.View:
return ViewTypeDisplayName;
case NodeType.Column:
return ColumnTypeDisplayName;
case NodeType.Database:
return DatabaseTypeDisplayName;
default:
throw new Error(`Unkown node type: ${type}`);
}
@@ -85,6 +93,7 @@ export function getAuthenticationTypeByDisplayName(displayValue: string): Authen
return AuthenticationType.Sql;
}
}
export function getUserTypeDisplayName(userType: UserType): string {
switch (userType) {
case UserType.WithLogin: