add objects dialog (#23243)

This commit is contained in:
Alan Ren
2023-05-30 09:53:44 -07:00
committed by GitHub
parent 82f5ef7ea3
commit f4d5ab616c
14 changed files with 250 additions and 60 deletions

View File

@@ -196,8 +196,10 @@ export const SelectServerRoleMemberDialogTitle = localize('objectManagement.serv
export const SelectServerRoleOwnerDialogTitle = localize('objectManagement.serverRole.SelectOwnerDialogTitle', "Select Server Role Owner");
// Find Object Dialog
export const ObjectTypesText = localize('objectManagement.objectTypesLabel', "Object Types");
export const FilterSectionTitle = localize('objectManagement.filterSectionTitle', "Filters");
export const ObjectTypeText = localize('objectManagement.objectTypeLabel', "Object Type");
export const FilterText = localize('objectManagement.filterText', "Filter");
export const SearchTextLabel = localize('objectManagement.SearchTextLabel', "Search Text");
export const FindText = localize('objectManagement.findText', "Find");
export const SelectText = localize('objectManagement.selectText', "Select");
export const ObjectsText = localize('objectManagement.objectsLabel', "Objects");
@@ -206,8 +208,15 @@ export function LoadingObjectsCompletedText(count: number): string {
return localize('objectManagement.loadingObjectsCompletedLabel', "Loading objects completed, {0} objects found", count);
}
// Util functions
// ObjectSelectionMethodDialog
export const ObjectSelectionMethodDialogTitle = localize('objectManagement.objectSelectionMethodDialogTitle', "Add Objects");
export const ObjectSelectionMethodDialog_TypeLabel = localize('objectManagement.ObjectSelectionMethodDialog_TypeLabel', "How do you want to add objects?");
export const ObjectSelectionMethodDialog_SpecificObjects = localize('objectManagement.ObjectSelectionMethodDialog_SpecificObjects', "Specific objects…");
export const ObjectSelectionMethodDialog_AllObjectsOfTypes = localize('objectManagement.ObjectSelectionMethodDialog_AllObjectsOfTypes', "All objects of certain types");
export const ObjectSelectionMethodDialog_AllObjectsOfSchema = localize('objectManagement.ObjectSelectionMethodDialog_AllObjectsOfSchema', "All objects belonging to a schema");
export const ObjectSelectionMethodDialog_SelectSchemaDropdownLabel = localize('objectManagement.ObjectSelectionMethodDialog_SelectSchemaDropdownLabel', "Schema");
// Util functions
export function getNodeTypeDisplayName(type: string, inTitle: boolean = false): string {
switch (type) {
case ObjectManagement.NodeType.ApplicationRole:

View File

@@ -26,7 +26,7 @@ export class ApplicationRoleDialog extends PrincipalDialogBase<ObjectManagement.
private ownedSchemaTable: azdata.TableComponent;
constructor(objectManagementService: IObjectManagementService, options: ObjectManagementDialogOptions) {
super(objectManagementService, options, true, false);
super(objectManagementService, { ...options, isDatabaseLevelPrincipal: true, supportEffectivePermissions: false });
}
protected override postInitializeData(): void {

View File

@@ -28,7 +28,7 @@ export class DatabaseRoleDialog extends PrincipalDialogBase<ObjectManagement.Dat
private memberTable: azdata.TableComponent;
constructor(objectManagementService: IObjectManagementService, options: ObjectManagementDialogOptions) {
super(objectManagementService, options, true, false);
super(objectManagementService, { ...options, isDatabaseLevelPrincipal: true, supportEffectivePermissions: false });
}
protected override get helpUrl(): string {

View File

@@ -36,6 +36,7 @@ const ObjectsTableMaxRowCount = 20;
export class FindObjectDialog extends DialogBase<FindObjectDialogResult> {
private objectTypesTable: azdata.TableComponent;
private searchTextInputBox: azdata.InputBoxComponent;
private findButton: azdata.ButtonComponent;
private objectsTable: azdata.TableComponent;
private objectsLoadingComponent: azdata.LoadingComponent;
@@ -54,7 +55,7 @@ export class FindObjectDialog extends DialogBase<FindObjectDialogResult> {
protected override async initialize(): Promise<void> {
this.dialogObject.okButton.enabled = false;
this.objectTypesTable = this.createTableList<ObjectTypeInfo>(localizedConstants.ObjectTypeText,
this.objectTypesTable = this.createTableList<ObjectTypeInfo>(localizedConstants.ObjectTypesText,
[localizedConstants.ObjectTypeText],
this.options.objectTypes,
this.selectedObjectTypes,
@@ -64,11 +65,17 @@ export class FindObjectDialog extends DialogBase<FindObjectDialogResult> {
}, (item1, item2) => {
return item1.name === item2.name;
});
this.searchTextInputBox = this.createInputBox(localizedConstants.SearchTextLabel, async () => { });
const searchTextRow = this.createLabelInputContainer(localizedConstants.SearchTextLabel, this.searchTextInputBox);
this.findButton = this.createButton(localizedConstants.FindText, localizedConstants.FindText, async () => {
await this.onFindObjectButtonClick();
}, this.options.selectAllObjectTypes);
const buttonContainer = this.createButtonContainer([this.findButton]);
const objectTypeSection = this.createGroup(localizedConstants.ObjectTypeText, [this.objectTypesTable, buttonContainer]);
const filterSection = this.createGroup(localizedConstants.FilterSectionTitle, [
searchTextRow,
this.objectTypesTable,
buttonContainer
]);
const columns = [localizedConstants.NameText, localizedConstants.ObjectTypeText];
if (this.options.showSchemaColumn) {
columns.splice(1, 0, localizedConstants.SchemaText);
@@ -101,7 +108,7 @@ export class FindObjectDialog extends DialogBase<FindObjectDialogResult> {
}).component();
const objectsSection = this.createGroup(localizedConstants.ObjectsText, [this.objectsLoadingComponent]);
this.formContainer.addItems([objectTypeSection, objectsSection], this.getSectionItemLayout());
this.formContainer.addItems([filterSection, objectsSection], this.getSectionItemLayout());
}
protected override get dialogResult(): FindObjectDialogResult | undefined {
@@ -113,7 +120,7 @@ export class FindObjectDialog extends DialogBase<FindObjectDialogResult> {
this.objectsLoadingComponent.loading = true;
this.findButton.enabled = false;
try {
const results = await this.objectManagementService.search(this.options.contextId, this.selectedObjectTypes.map(item => item.name));
const results = await this.objectManagementService.search(this.options.contextId, this.selectedObjectTypes.map(item => item.name), this.searchTextInputBox.value);
this.allObjects.splice(0, this.allObjects.length, ...results);
let data;
if (this.options.multiSelect) {

View File

@@ -35,7 +35,7 @@ export class LoginDialog extends PrincipalDialogBase<ObjectManagement.Login, Obj
private lockedOutCheckbox: azdata.CheckBoxComponent;
constructor(objectManagementService: IObjectManagementService, options: ObjectManagementDialogOptions) {
super(objectManagementService, options, false);
super(objectManagementService, { ...options, isDatabaseLevelPrincipal: false, supportEffectivePermissions: true });
}
protected override get helpUrl(): string {

View File

@@ -0,0 +1,101 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as azdata from 'azdata';
import { DefaultMaxTableRowCount, DefaultTableListItemEnabledStateGetter, DialogBase } from '../../ui/dialogBase';
import * as localizedConstants from '../localizedConstants';
import { ObjectTypeInfo } from './findObjectDialog';
export enum ObjectSelectionMethod {
SpecificObjects,
AllObjectsOfTypes,
AllObjectsOfSchema
}
export interface ObjectSelectionMethodDialogOptions {
objectTypes: ObjectTypeInfo[];
schemas: string[];
}
export interface ObjectSelectionMethodDialogResult {
method: ObjectSelectionMethod;
schema: string | undefined;
objectTypes: ObjectTypeInfo[];
}
export class ObjectSelectionMethodDialog extends DialogBase<ObjectSelectionMethodDialogResult> {
private specificObjectsRadioButton: azdata.RadioButtonComponent;
private allObjectsOfTypesRadioButton: azdata.RadioButtonComponent;
private allObjectsOfSchemaRadioButton: azdata.RadioButtonComponent;
private objectTypesTable: azdata.TableComponent;
private schemaRow: azdata.FlexContainer;
private result: ObjectSelectionMethodDialogResult;
constructor(private readonly options: ObjectSelectionMethodDialogOptions) {
super(localizedConstants.ObjectSelectionMethodDialogTitle, 'ObjectSelectionMethodDialog');
this.result = {
method: ObjectSelectionMethod.SpecificObjects,
schema: undefined,
objectTypes: []
};
}
protected override async initialize(): Promise<void> {
const radioGroupName = 'objectSelectionMethodRadioGroup';
this.specificObjectsRadioButton = this.createRadioButton(localizedConstants.ObjectSelectionMethodDialog_SpecificObjects, radioGroupName, true, async (checked) => { await this.handleTypeChange(checked); });
this.allObjectsOfTypesRadioButton = this.createRadioButton(localizedConstants.ObjectSelectionMethodDialog_AllObjectsOfTypes, radioGroupName, false, async (checked) => { await this.handleTypeChange(checked); });
this.allObjectsOfSchemaRadioButton = this.createRadioButton(localizedConstants.ObjectSelectionMethodDialog_AllObjectsOfSchema, radioGroupName, false, async (checked) => { await this.handleTypeChange(checked); });
const typeGroup = this.createGroup(localizedConstants.ObjectSelectionMethodDialog_TypeLabel, [this.specificObjectsRadioButton, this.allObjectsOfTypesRadioButton, this.allObjectsOfSchemaRadioButton], false);
this.objectTypesTable = this.createTableList<ObjectTypeInfo>(localizedConstants.ObjectTypeText,
[localizedConstants.ObjectTypesText],
this.options.objectTypes,
this.result.objectTypes,
DefaultMaxTableRowCount,
DefaultTableListItemEnabledStateGetter, (item) => {
return [item.displayName];
}, (item1, item2) => {
return item1.name === item2.name;
});
const schemaDropdown = this.createDropdown(localizedConstants.ObjectSelectionMethodDialog_SelectSchemaDropdownLabel, async (newValue) => {
this.result.schema = newValue;
}, this.options.schemas, this.options.schemas[0]);
this.schemaRow = this.createLabelInputContainer(localizedConstants.ObjectSelectionMethodDialog_SelectSchemaDropdownLabel, schemaDropdown);
await this.setComponentsVisibility(false, false);
this.formContainer.addItems([typeGroup, this.schemaRow, this.objectTypesTable], this.getSectionItemLayout());
}
private async handleTypeChange(checked: boolean): Promise<void> {
let method: ObjectSelectionMethod = ObjectSelectionMethod.SpecificObjects;
let showSchema = false;
let showObjectTypes = false;
await this.setComponentsVisibility(showObjectTypes, showSchema);
if (this.allObjectsOfTypesRadioButton.checked) {
method = ObjectSelectionMethod.AllObjectsOfTypes;
showSchema = false;
showObjectTypes = true;
} else if (this.allObjectsOfSchemaRadioButton.checked) {
method = ObjectSelectionMethod.AllObjectsOfSchema;
showSchema = true;
showObjectTypes = false;
}
this.result.method = method;
await this.setComponentsVisibility(showObjectTypes, showSchema);
}
private async setComponentsVisibility(showObjectTypes: boolean, showSchema: boolean): Promise<void> {
await this.schemaRow.updateCssStyles({ display: showSchema ? 'flex' : 'none' });
await this.objectTypesTable.updateCssStyles({ display: showObjectTypes ? 'block' : 'none' });
}
protected override get dialogResult(): ObjectSelectionMethodDialogResult | undefined {
return this.result;
}
protected override async onFormFieldChange(): Promise<void> {
this.dialogObject.okButton.enabled = this.result.method !== ObjectSelectionMethod.AllObjectsOfTypes || this.result.objectTypes.length > 0;
}
}

View File

@@ -8,14 +8,20 @@ import * as mssql from 'mssql';
import * as localizedConstants from '../localizedConstants';
import { ObjectManagementDialogBase, ObjectManagementDialogOptions } from './objectManagementDialogBase';
import { FindObjectDialog } from './findObjectDialog';
import { FindObjectDialog, FindObjectDialogResult } from './findObjectDialog';
import { deepClone } from '../../util/objects';
import { DefaultTableWidth, getTableHeight } from '../../ui/dialogBase';
import { ObjectSelectionMethod, ObjectSelectionMethodDialog } from './objectSelectionMethodDialog';
const GrantColumnIndex = 2;
const WithGrantColumnIndex = 3;
const DenyColumnIndex = 4;
export interface PrincipalDialogOptions extends ObjectManagementDialogOptions {
isDatabaseLevelPrincipal: boolean;
supportEffectivePermissions: boolean;
}
/**
* Base class for security principal dialogs such as user, role, etc.
*/
@@ -28,8 +34,8 @@ export abstract class PrincipalDialogBase<ObjectInfoType extends mssql.ObjectMan
protected effectivePermissionTableLabel: azdata.TextComponent;
private securablePermissions: mssql.ObjectManagement.SecurablePermissions[] = [];
constructor(objectManagementService: mssql.IObjectManagementService, options: ObjectManagementDialogOptions, private readonly showSchemaColumn: boolean, private readonly supportEffectivePermissions: boolean = true) {
super(objectManagementService, options);
constructor(objectManagementService: mssql.IObjectManagementService, private readonly dialogOptions: PrincipalDialogOptions) {
super(objectManagementService, dialogOptions);
}
protected override async initializeUI(): Promise<void> {
@@ -40,12 +46,12 @@ export abstract class PrincipalDialogBase<ObjectInfoType extends mssql.ObjectMan
private initializeSecurableSection(): void {
const items: azdata.Component[] = [];
const securableTableColumns = [localizedConstants.NameText, localizedConstants.ObjectTypeText];
if (this.showSchemaColumn) {
if (this.dialogOptions.isDatabaseLevelPrincipal) {
securableTableColumns.splice(1, 0, localizedConstants.SchemaText);
}
this.securableTable = this.createTable(localizedConstants.SecurablesText, securableTableColumns, this.getSecurableTableData());
const buttonContainer = this.addButtonsForTable(this.securableTable, localizedConstants.AddSecurableAriaLabel, localizedConstants.RemoveSecurableAriaLabel,
() => this.onAddSecurableButtonClicked(), () => this.onRemoveSecurableButtonClicked());
(button) => this.onAddSecurableButtonClicked(button), () => this.onRemoveSecurableButtonClicked());
this.disposables.push(this.securableTable.onRowSelected(async () => {
await this.updatePermissionsTable();
}));
@@ -115,19 +121,40 @@ export abstract class PrincipalDialogBase<ObjectInfoType extends mssql.ObjectMan
this.securableSection = this.createGroup(localizedConstants.SecurablesText, items, true, true);
}
private async onAddSecurableButtonClicked(): Promise<void> {
const dialog = new FindObjectDialog(this.objectManagementService, {
objectTypes: this.viewInfo.supportedSecurableTypes,
selectAllObjectTypes: false,
multiSelect: true,
contextId: this.contextId,
title: localizedConstants.SelectSecurablesDialogTitle,
showSchemaColumn: this.showSchemaColumn
});
await dialog.open();
const result = await dialog.waitForClose();
if (result && result.selectedObjects.length > 0) {
result.selectedObjects.forEach(obj => {
private async onAddSecurableButtonClicked(button: azdata.ButtonComponent): Promise<void> {
const selectedObjects: mssql.ObjectManagement.SearchResultItem[] = [];
if (this.dialogOptions.isDatabaseLevelPrincipal) {
const methodDialog = new ObjectSelectionMethodDialog({
objectTypes: this.viewInfo.supportedSecurableTypes,
schemas: (<mssql.ObjectManagement.DatabaseLevelPrincipalViewInfo<mssql.ObjectManagement.SecurityPrincipalObject>><unknown>this.viewInfo).schemas,
});
await methodDialog.open();
const methodResult = await methodDialog.waitForClose();
if (methodResult) {
switch (methodResult.method) {
case ObjectSelectionMethod.AllObjectsOfTypes:
selectedObjects.push(... await this.searchForObjects(methodResult.objectTypes.map(item => item.name)));
break;
case ObjectSelectionMethod.AllObjectsOfSchema:
selectedObjects.push(... await this.searchForObjects(this.viewInfo.supportedSecurableTypes.map(item => item.name), methodResult.schema));
break;
default:
const objectsResult = await this.openFindObjectsDialog();
if (objectsResult) {
selectedObjects.push(...objectsResult.selectedObjects);
}
break;
}
}
} else {
const result = await this.openFindObjectsDialog();
if (result) {
selectedObjects.push(...result.selectedObjects);
}
}
if (selectedObjects.length > 0) {
selectedObjects.forEach(obj => {
if (this.securablePermissions.find(securable => securable.type === obj.type && securable.name === obj.name && securable.schema === obj.schema)) {
return;
}
@@ -150,6 +177,27 @@ export abstract class PrincipalDialogBase<ObjectInfoType extends mssql.ObjectMan
const data = this.getSecurableTableData();
await this.setTableData(this.securableTable, data);
}
await button.focus();
}
private async searchForObjects(objectTypes: string[], schema: string = undefined): Promise<mssql.ObjectManagement.SearchResultItem[]> {
this.updateLoadingStatus(true);
const result = await this.objectManagementService.search(this.contextId, objectTypes, undefined, schema);
this.updateLoadingStatus(false);
return result;
}
private async openFindObjectsDialog(): Promise<FindObjectDialogResult> {
const dialog = new FindObjectDialog(this.objectManagementService, {
objectTypes: this.viewInfo.supportedSecurableTypes,
selectAllObjectTypes: false,
multiSelect: true,
contextId: this.contextId,
title: localizedConstants.SelectSecurablesDialogTitle,
showSchemaColumn: this.dialogOptions.isDatabaseLevelPrincipal
});
await dialog.open();
return await dialog.waitForClose();
}
private async onRemoveSecurableButtonClicked(): Promise<void> {
@@ -164,7 +212,7 @@ export abstract class PrincipalDialogBase<ObjectInfoType extends mssql.ObjectMan
private getSecurableTableData(): string[][] {
return this.securablePermissions.map(securable => {
const row = [securable.name, this.getSecurableTypeDisplayName(securable.type)];
if (this.showSchemaColumn) {
if (this.dialogOptions.isDatabaseLevelPrincipal) {
row.splice(1, 0, securable.schema);
}
return row;
@@ -223,6 +271,6 @@ export abstract class PrincipalDialogBase<ObjectInfoType extends mssql.ObjectMan
}
private get showEffectivePermissions(): boolean {
return !this.options.isNewObject && this.supportEffectivePermissions;
return !this.dialogOptions.isNewObject && this.dialogOptions.supportEffectivePermissions;
}
}

View File

@@ -28,7 +28,7 @@ export class ServerRoleDialog extends PrincipalDialogBase<ObjectManagement.Serve
constructor(objectManagementService: IObjectManagementService, options: ObjectManagementDialogOptions) {
super(objectManagementService, options, false, false);
super(objectManagementService, { ...options, isDatabaseLevelPrincipal: false, supportEffectivePermissions: false });
}
protected override get helpUrl(): string {

View File

@@ -32,7 +32,7 @@ export class UserDialog extends PrincipalDialogBase<ObjectManagement.User, Objec
private membershipTable: azdata.TableComponent;
constructor(objectManagementService: IObjectManagementService, options: ObjectManagementDialogOptions) {
super(objectManagementService, options, true);
super(objectManagementService, { ...options, isDatabaseLevelPrincipal: true, supportEffectivePermissions: true });
}
protected override get helpUrl(): string {