Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding database properties general tab with read-only fields #23318

Merged
merged 21 commits into from
Jun 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
ca43ade
Initial commit for adding a basic general tab for the database proper…
ssreerama Jun 5, 2023
ca0ad70
Refactoring for dialog inputs
ssreerama Jun 5, 2023
1a9094a
removed properties nodeType and using database node and additional cl…
ssreerama Jun 6, 2023
84d24c7
Changes according to STS data fetch
ssreerama Jun 8, 2023
54ebf98
Merge branch 'main' into 'sai/databasePropertiesInitial'
ssreerama Jun 8, 2023
8733af8
Reuse database Dialog
ssreerama Jun 8, 2023
10beedc
Undo contract file change
ssreerama Jun 8, 2023
8caf792
more refactoring
ssreerama Jun 8, 2023
09475f1
fetched scrollbar fix into this PR
ssreerama Jun 9, 2023
cfbabdf
Tabbed panel is being used for horizontal tabs
ssreerama Jun 13, 2023
cd2feb5
stying fix for general tab button
ssreerama Jun 13, 2023
8be6593
Merge branch 'main' into 'sai/databasePropertiesInitial'
ssreerama Jun 13, 2023
1cf0f50
Merge branch 'main' into 'sai/databasePropertiesInitial'
ssreerama Jun 13, 2023
86585b1
final commit for today :)
ssreerama Jun 14, 2023
31e4532
Merge branch 'main' into 'sai/databasePropertiesInitial'
ssreerama Jun 14, 2023
552a35e
Updates according to STS changes
ssreerama Jun 14, 2023
1bbc647
missed updates
ssreerama Jun 14, 2023
7a112c7
Refactored updates
ssreerama Jun 19, 2023
38844ce
Merge branch 'main' into 'sai/databasePropertiesInitial'
ssreerama Jun 20, 2023
9465829
moved options as discussed and added collapsible sections... need to …
ssreerama Jun 20, 2023
03217e6
Fixing the horizontal scroll bar of tabbedpanel
ssreerama Jun 21, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about updating getParentUrn to possibly return undefined instead? That makes sense if there isn't a parent already

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So if the ParentUrn is empty, then can we cconsider the objectUrn as a parent and update the value there?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's do this as a follow-up - I already asked @barbaravaldez to do a separate PR for moving this into a helper function anyways and we can discuss it there.

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>{
ssreerama marked this conversation as resolved.
Show resolved Hide resolved
objectInfo: {
name: 'New Database Name',
owner: '',
collationName: '',
compatibilityLevel: '',
containmentType: '',
recoveryModel: '',
azureEdition: '',
azureMaxSize: '',
azureBackupRedundancyLevel: '',
azureServiceLevelObjective: ''
}
} : <DatabaseViewInfo>{
objectInfo: {
name: 'Database Properties1',
ssreerama marked this conversation as resolved.
Show resolved Hide resolved
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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use actual JS doc syntax for doc comments please

export function convertNumToTwoDecimalStringinMB(value: number): string {
return localizedConstants.StringValueInMB(value?.toFixed(2));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If value is actually possible to be undefined then the parameter should reflect that. And in that case it'd probably be better to just return an empty string instead

}