Skip to content

Commit

Permalink
Adding database properties general tab with read-only fields (#23318)
Browse files Browse the repository at this point in the history
* Initial commit for adding a basic general tab for the database properties

* Refactoring for dialog inputs

* removed properties nodeType and using database node and additional cleanup, diabling the functionality.

* Changes according to STS data fetch

* Reuse database Dialog

* Undo contract file change

* more refactoring

* fetched scrollbar fix into this PR

* Tabbed panel is being used for horizontal tabs

* stying fix for general tab button

* final commit for today :)

* Updates according to STS changes

* missed updates

* Refactored updates

* moved options as discussed and added collapsible sections... need to fix scroll bar

* Fixing the horizontal scroll bar of tabbedpanel
  • Loading branch information
ssreerama committed Jun 22, 2023
1 parent b15217b commit c8ed14f
Show file tree
Hide file tree
Showing 8 changed files with 192 additions and 15 deletions.
5 changes: 5 additions & 0 deletions extensions/mssql/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -522,6 +522,11 @@
"group": "0_query@1",
"isDefault": true
},
{
"command": "mssql.objectProperties",
"when": "connectionProvider == MSSQL && serverInfo && !isCloud && nodeType && nodeType =~ /^(Database|Server)$/ && mssql:engineedition != 11 && isDevelopment",
"group": "0_query@1"
},
{
"command": "mssql.deleteObject",
"when": "connectionProvider == MSSQL && nodeType =~ /^(ServerLevelLogin|User|ServerLevelServerRole|ApplicationRole|DatabaseRole|Database)$/ && config.workbench.enablePreviewFeatures",
Expand Down
2 changes: 1 addition & 1 deletion extensions/mssql/src/objectManagement/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ async function handleObjectPropertiesDialogCommand(context: azdata.ObjectExplore
return;
}
try {
const parentUrn = await getParentUrn(context);
const parentUrn = context.nodeInfo ? await getParentUrn(context) : undefined;
const options: ObjectManagementDialogOptions = {
connectionUri: connectionUri,
isNewObject: false,
Expand Down
1 change: 1 addition & 0 deletions extensions/mssql/src/objectManagement/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export const AlterApplicationRoleDocUrl = 'https://learn.microsoft.com/sql/t-sql
export const CreateDatabaseRoleDocUrl = 'https://learn.microsoft.com/sql/t-sql/statements/create-role-transact-sql';
export const AlterDatabaseRoleDocUrl = 'https://learn.microsoft.com/sql/t-sql/statements/alter-role-transact-sql';
export const CreateDatabaseDocUrl = 'https://learn.microsoft.com/sql/t-sql/statements/create-database-transact-sql';
export const DatabasePropertiesDocUrl = 'https://learn.microsoft.com/sql/relational-databases/databases/database-properties-general-page';

export const enum TelemetryActions {
CreateObject = 'CreateObject',
Expand Down
10 changes: 9 additions & 1 deletion extensions/mssql/src/objectManagement/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -430,7 +430,15 @@ export interface Database extends ObjectManagement.SqlObject {
recoveryModel?: string;
compatibilityLevel?: string;
containmentType?: string;

dateCreated?: string;
lastDatabaseBackup?: string;
lastDatabaseLogBackup?: string;
memoryAllocatedToMemoryOptimizedObjectsInMb?: number;
memoryUsedByMemoryOptimizedObjectsInMb?: number;
numberOfUsers?: number;
sizeInMb?: number;
spaceAvailableInMb?: number;
status?: string;
azureBackupRedundancyLevel?: string;
azureServiceLevelObjective?: string;
azureEdition?: string;
Expand Down
16 changes: 16 additions & 0 deletions extensions/mssql/src/objectManagement/localizedConstants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,22 @@ export const ObjectSelectionMethodDialog_AllObjectsOfTypes = localize('objectMan
export const ObjectSelectionMethodDialog_AllObjectsOfSchema = localize('objectManagement.ObjectSelectionMethodDialog_AllObjectsOfSchema', "All objects belonging to a schema");
export const ObjectSelectionMethodDialog_SelectSchemaDropdownLabel = localize('objectManagement.ObjectSelectionMethodDialog_SelectSchemaDropdownLabel', "Schema");

//Database properties Dialog
export const LastDatabaseBackupText = localize('objectManagement.lastDatabaseBackup', "Last Database Backup");
export const LastDatabaseLogBackupText = localize('objectManagement.lastDatabaseLogBackup', "Last Database Log Backup");
export const BackupSectionHeader = localize('objectManagement.databaseProperties.backupSectionHeader', "Backup");
export const DatabaseSectionHeader = localize('objectManagement.databaseProperties.databaseSectionHeader', "Database");
export const NamePropertyText = localize('objectManagement.databaseProperties.name', "Name");
export const StatusText = localize('objectManagement.databaseProperties.status', "Status");
export const OwnerPropertyText = localize('objectManagement.databaseProperties.owner', "Owner");
export const DateCreatedText = localize('objectManagement.databaseProperties.dateCreated', "Date Created");
export const SizeText = localize('objectManagement.databaseProperties.size', "Size");
export const SpaceAvailableText = localize('objectManagement.databaseProperties.spaceAvailable', "Space Available");
export const NumberOfUsersText = localize('objectManagement.databaseProperties.numberOfUsers', "Number of Users");
export const MemoryAllocatedText = localize('objectManagement.databaseProperties.memoryAllocated', "Memory Allocated To Memory Optimized Objects");
export const MemoryUsedText = localize('objectManagement.databaseProperties.memoryUsed', "Memory Used By Memory Optimized Objects");
export const StringValueInMB = (value: string) => localize('objectManagement.databaseProperties.mbUnitText', "{0} MB", value);

// Util functions
export function getNodeTypeDisplayName(type: string, inTitle: boolean = false): string {
switch (type) {
Expand Down
36 changes: 35 additions & 1 deletion extensions/mssql/src/objectManagement/objectManagementService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ApplicationRoleViewInfo, AuthenticationType, DatabaseRoleViewInfo, LoginViewInfo, SecurablePermissions, SecurableTypeMetadata, ServerRoleViewInfo, User, UserType, UserViewInfo } from './interfaces';
import { ApplicationRoleViewInfo, AuthenticationType, DatabaseRoleViewInfo, DatabaseViewInfo, LoginViewInfo, SecurablePermissions, SecurableTypeMetadata, ServerRoleViewInfo, User, UserType, UserViewInfo } from './interfaces';
import * as Utils from '../utils';
import * as constants from '../constants';
import * as contracts from '../contracts';
Expand Down Expand Up @@ -194,6 +194,8 @@ export class TestObjectManagementService implements IObjectManagementService {
obj = this.getApplicationRoleView(isNewObject, objectUrn);
} else if (objectType === ObjectManagement.NodeType.DatabaseRole) {
obj = this.getDatabaseRoleView(isNewObject, objectUrn);
} else if (objectType === ObjectManagement.NodeType.Database) {
obj = this.getDatabaseView(isNewObject, objectUrn);
} else if (objectType === ObjectManagement.NodeType.ServerLevelLogin) {
obj = this.getLoginView(isNewObject, objectUrn);
} else if (objectType === ObjectManagement.NodeType.ServerLevelServerRole) {
Expand Down Expand Up @@ -432,6 +434,38 @@ export class TestObjectManagementService implements IObjectManagementService {
};
}

private getDatabaseView(isNewObject: boolean, name: string): DatabaseViewInfo {
return isNewObject ? <DatabaseViewInfo>{
objectInfo: {
name: 'New Database Name',
owner: '',
collationName: '',
compatibilityLevel: '',
containmentType: '',
recoveryModel: '',
azureEdition: '',
azureMaxSize: '',
azureBackupRedundancyLevel: '',
azureServiceLevelObjective: ''
}
} : <DatabaseViewInfo>{
objectInfo: {
name: 'Database Properties1',
collationName: 'Latin1_General_100_CI_AS_KS_WS',
dateCreated: '5/31/2023 8:05:55 AM',
lastDatabaseBackup: 'None',
lastDatabaseLogBackup: 'None',
memoryAllocatedToMemoryOptimizedObjectsInMb: 0,
memoryUsedByMemoryOptimizedObjectsInMb: 0,
numberOfUsers: 5,
owner: 'databaseProperties 1',
sizeInMb: 16.00,
spaceAvailableInMb: 1.15,
status: 'Normal'
}
};
}

private delayAndResolve(obj?: any): Promise<any> {
return new Promise((resolve, reject) => {
setTimeout(() => {
Expand Down
132 changes: 120 additions & 12 deletions extensions/mssql/src/objectManagement/ui/databaseDialog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,37 +7,84 @@ import * as azdata from 'azdata';
import { ObjectManagementDialogBase, ObjectManagementDialogOptions } from './objectManagementDialogBase';
import { IObjectManagementService } from 'mssql';
import * as localizedConstants from '../localizedConstants';
import { CreateDatabaseDocUrl } from '../constants';
import { CreateDatabaseDocUrl, DatabasePropertiesDocUrl } from '../constants';
import { Database, DatabaseViewInfo } from '../interfaces';
import { convertNumToTwoDecimalStringinMB } from '../utils';

export class DatabaseDialog extends ObjectManagementDialogBase<Database, DatabaseViewInfo> {
private _nameInput: azdata.InputBoxComponent;
// Database Properties tabs
private generalTab: azdata.Tab;

//Database properties options
private nameInput: azdata.InputBoxComponent;
private backupSection: azdata.GroupContainer;
private lastDatabaseBackupInput: azdata.InputBoxComponent;
private lastDatabaseLogBackupInput: azdata.InputBoxComponent;
private databaseSection: azdata.GroupContainer;
private statusInput: azdata.InputBoxComponent;
private ownerInput: azdata.InputBoxComponent;
private dateCreatedInput: azdata.InputBoxComponent;
private sizeInput: azdata.InputBoxComponent;
private spaceAvailabeInput: azdata.InputBoxComponent;
private numberOfUsersInput: azdata.InputBoxComponent;
private memoryAllocatedInput: azdata.InputBoxComponent;
private memoryUsedInput: azdata.InputBoxComponent;
private collationInput: azdata.InputBoxComponent;

constructor(objectManagementService: IObjectManagementService, options: ObjectManagementDialogOptions) {
super(objectManagementService, options);
}

protected override get helpUrl(): string {
return CreateDatabaseDocUrl;
return this.options.isNewObject ? CreateDatabaseDocUrl : DatabasePropertiesDocUrl;
}

protected async initializeUI(): Promise<void> {
let components = [];
components.push(this.initializeGeneralSection());
components.push(this.initializeOptionsSection());
if (this.viewInfo.isAzureDB) {
components.push(this.initializeConfigureSLOSection());
if (this.options.isNewObject) {
let components = [];
components.push(this.initializeGeneralSection());
components.push(this.initializeOptionsSection());
if (this.viewInfo.isAzureDB) {
components.push(this.initializeConfigureSLOSection());
}
this.formContainer.addItems(components);
} else {
// Initilaize general Tab sections
this.initializeBackupSection();
this.initializeDatabaseSection();

// Initilaize general Tab
this.generalTab = {
title: localizedConstants.GeneralSectionHeader,
id: 'generalId',
content: this.createGroup('', [
this.databaseSection,
this.backupSection
], false)
};

// Initilaize tab group with tabbed panel
const propertiesTabGroup = { title: '', tabs: [this.generalTab] };
const propertiesTabbedPannel = this.modelView.modelBuilder.tabbedPanel()
.withTabs([propertiesTabGroup])
.withProps({
CSSStyles: {
'margin': '-10px 0px 0px -10px'
}
})
.component();
this.formContainer.addItem(propertiesTabbedPannel);
}
this.formContainer.addItems(components);
}

//#region Create Database
private initializeGeneralSection(): azdata.GroupContainer {
let containers: azdata.Component[] = [];
this._nameInput = this.createInputBox(localizedConstants.NameText, async () => {
this.objectInfo.name = this._nameInput.value;
this.nameInput = this.createInputBox(localizedConstants.NameText, async () => {
this.objectInfo.name = this.nameInput.value;
await this.runValidation(false);
});
containers.push(this.createLabelInputContainer(localizedConstants.NameText, this._nameInput));
containers.push(this.createLabelInputContainer(localizedConstants.NameText, this.nameInput));

if (this.viewInfo.loginNames?.length > 0) {
this.objectInfo.owner = this.viewInfo.loginNames[0];
Expand Down Expand Up @@ -86,6 +133,67 @@ export class DatabaseDialog extends ObjectManagementDialogBase<Database, Databas

return this.createGroup(localizedConstants.OptionsSectionHeader, containers, true, true);
}
//#endregion

//#region Database Properties - General Tab
private initializeBackupSection(): void {
this.lastDatabaseBackupInput = this.createInputBox(localizedConstants.LastDatabaseBackupText, async () => { }, this.objectInfo.lastDatabaseBackup, this.options.isNewObject);
const lastDatabaseBackupContainer = this.createLabelInputContainer(localizedConstants.LastDatabaseBackupText, this.lastDatabaseBackupInput);

this.lastDatabaseLogBackupInput = this.createInputBox(localizedConstants.LastDatabaseLogBackupText, async () => { }, this.objectInfo.lastDatabaseLogBackup, this.options.isNewObject);
const lastDatabaseLogBackupContainer = this.createLabelInputContainer(localizedConstants.LastDatabaseLogBackupText, this.lastDatabaseLogBackupInput);

this.backupSection = this.createGroup(localizedConstants.BackupSectionHeader, [
lastDatabaseBackupContainer,
lastDatabaseLogBackupContainer
], true);
}

private initializeDatabaseSection(): void {
this.nameInput = this.createInputBox(localizedConstants.NamePropertyText, async () => { }, this.objectInfo.name, this.options.isNewObject);
const nameContainer = this.createLabelInputContainer(localizedConstants.NamePropertyText, this.nameInput);

this.statusInput = this.createInputBox(localizedConstants.StatusText, async () => { }, this.objectInfo.status, this.options.isNewObject);
const statusContainer = this.createLabelInputContainer(localizedConstants.StatusText, this.statusInput);

this.ownerInput = this.createInputBox(localizedConstants.OwnerPropertyText, async () => { }, this.objectInfo.owner, this.options.isNewObject);
const ownerContainer = this.createLabelInputContainer(localizedConstants.OwnerPropertyText, this.ownerInput);

this.dateCreatedInput = this.createInputBox(localizedConstants.DateCreatedText, async () => { }, this.objectInfo.dateCreated, this.options.isNewObject);
const dateCreatedContainer = this.createLabelInputContainer(localizedConstants.DateCreatedText, this.dateCreatedInput);

this.sizeInput = this.createInputBox(localizedConstants.SizeText, async () => { }, convertNumToTwoDecimalStringinMB(this.objectInfo.sizeInMb), this.options.isNewObject);
const sizeContainer = this.createLabelInputContainer(localizedConstants.SizeText, this.sizeInput);

this.spaceAvailabeInput = this.createInputBox(localizedConstants.SpaceAvailableText, async () => { }, convertNumToTwoDecimalStringinMB(this.objectInfo.spaceAvailableInMb), this.options.isNewObject);
const spaceAvailabeContainer = this.createLabelInputContainer(localizedConstants.SpaceAvailableText, this.spaceAvailabeInput);

this.numberOfUsersInput = this.createInputBox(localizedConstants.NumberOfUsersText, async () => { }, this.objectInfo.numberOfUsers.toString(), this.options.isNewObject);
const numberOfUsersContainer = this.createLabelInputContainer(localizedConstants.NumberOfUsersText, this.numberOfUsersInput);

this.memoryAllocatedInput = this.createInputBox(localizedConstants.MemoryAllocatedText, async () => { }, convertNumToTwoDecimalStringinMB(this.objectInfo.memoryAllocatedToMemoryOptimizedObjectsInMb), this.options.isNewObject);
const memoryAllocatedContainer = this.createLabelInputContainer(localizedConstants.MemoryAllocatedText, this.memoryAllocatedInput);

this.memoryUsedInput = this.createInputBox(localizedConstants.MemoryUsedText, async () => { }, convertNumToTwoDecimalStringinMB(this.objectInfo.memoryUsedByMemoryOptimizedObjectsInMb), this.options.isNewObject);
const memoryUsedContainer = this.createLabelInputContainer(localizedConstants.MemoryUsedText, this.memoryUsedInput);

this.collationInput = this.createInputBox(localizedConstants.CollationText, async () => { }, this.objectInfo.collationName, this.options.isNewObject);
const collationContainer = this.createLabelInputContainer(localizedConstants.CollationText, this.collationInput);

this.databaseSection = this.createGroup(localizedConstants.DatabaseSectionHeader, [
nameContainer,
statusContainer,
ownerContainer,
collationContainer,
dateCreatedContainer,
sizeContainer,
spaceAvailabeContainer,
numberOfUsersContainer,
memoryAllocatedContainer,
memoryUsedContainer
], true);
}
//#endregion

private initializeConfigureSLOSection(): azdata.GroupContainer {
let containers: azdata.Component[] = [];
Expand Down
5 changes: 5 additions & 0 deletions extensions/mssql/src/objectManagement/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,8 @@ export function isValidSQLPassword(password: string, userName: string = 'sa'): b
const hasNonAlphas = /\W/.test(password) ? 1 : 0;
return !containsUserName && password.length >= 8 && password.length <= 128 && (hasUpperCase + hasLowerCase + hasNumbers + hasNonAlphas >= 3);
}

// Converts number to two decimal placed string
export function convertNumToTwoDecimalStringinMB(value: number): string {
return localizedConstants.StringValueInMB(value?.toFixed(2));
}

0 comments on commit c8ed14f

Please sign in to comment.