diff --git a/extensions/mssql/src/objectManagement/interfaces.ts b/extensions/mssql/src/objectManagement/interfaces.ts index 96acf72bc8..719dea0d31 100644 --- a/extensions/mssql/src/objectManagement/interfaces.ts +++ b/extensions/mssql/src/objectManagement/interfaces.ts @@ -443,6 +443,17 @@ export interface Database extends ObjectManagement.SqlObject { azureServiceLevelObjective?: string; azureEdition?: string; azureMaxSize?: string; + autoCreateIncrementalStatistics: boolean; + autoCreateStatistics: boolean; + autoShrink: boolean; + autoUpdateStatistics: boolean; + autoUpdateStatisticsAsynchronously: boolean; + isLedgerDatabase?: boolean; + pageVerify?: string; + targetRecoveryTimeInSec?: number; + databaseReadOnly?: boolean; + encryptionEnabled: boolean; + restrictAccess?: string; } export interface DatabaseViewInfo extends ObjectManagement.ObjectViewInfo { @@ -452,12 +463,13 @@ export interface DatabaseViewInfo extends ObjectManagement.ObjectViewInfo localize('objectManagement.databaseProperties.mbUnitText', "{0} MB", value); +export const AutoCreateIncrementalStatisticsText = localize('objectManagement.databaseProperties.autoCreateIncrementalStatisticsText', "Auto Create Incremental Statistics"); +export const AutoCreateStatisticsText = localize('objectManagement.databaseProperties.AutoCreateStatisticsText', "Auto Create Statistics"); +export const AutoShrinkText = localize('objectManagement.databaseProperties.autoShrinkText', "Auto Shrink"); +export const AutoUpdateStatisticsText = localize('objectManagement.databaseProperties.autoUpdateStatisticsText', "Auto Update Statistics"); +export const AutoUpdateStatisticsAsynchronouslyText = localize('objectManagement.databaseProperties.autoUpdateStatisticsAsynchronouslyText', "Auto Update Statistics Asynchronously"); +export const IsLedgerDatabaseText = localize('objectManagement.databaseProperties.isLedgerDatabaseText', "Is Ledger Database"); +export const PageVerifyText = localize('objectManagement.databaseProperties.pageVerifyText', "Page Verify"); +export const TargetRecoveryTimeInSecondsText = localize('objectManagement.databaseProperties.targetRecoveryTimeInSecondsText', "Target Recovery Time (Seconds)"); +export const DatabaseReadOnlyText = localize('objectManagement.databaseProperties.databaseReadOnlyText', "Database Read-Only"); +export const DatabaseStateText = localize('objectManagement.databaseProperties.databaseStateText', "Database State"); +export const EncryptionEnabledText = localize('objectManagement.databaseProperties.encryptionEnabledText', "Encryption Enabled"); +export const RestrictAccessText = localize('objectManagement.databaseProperties.restrictAccessText', "Restrict Access"); + // Util functions export function getNodeTypeDisplayName(type: string, inTitle: boolean = false): string { diff --git a/extensions/mssql/src/objectManagement/objectManagementService.ts b/extensions/mssql/src/objectManagement/objectManagementService.ts index 751f6aa0bb..07d53dc0ca 100644 --- a/extensions/mssql/src/objectManagement/objectManagementService.ts +++ b/extensions/mssql/src/objectManagement/objectManagementService.ts @@ -470,7 +470,18 @@ export class TestObjectManagementService implements IObjectManagementService { owner: 'databaseProperties 1', sizeInMb: 16.00, spaceAvailableInMb: 1.15, - status: 'Normal' + status: 'Normal', + autoCreateIncrementalStatistics: false, + autoCreateStatistics: true, + autoShrink: false, + autoUpdateStatistics: true, + autoUpdateStatisticsAsynchronously: false, + isLedgerDatabase: false, + pageVerify: 'CHECKSUM', + targetRecoveryTimeInSec: 60, + databaseReadOnly: true, + encryptionEnabled: false, + restrictAccess: 'SINGLE_USER', } }; } diff --git a/extensions/mssql/src/objectManagement/ui/databaseDialog.ts b/extensions/mssql/src/objectManagement/ui/databaseDialog.ts index 86a7b6cab7..820163854e 100644 --- a/extensions/mssql/src/objectManagement/ui/databaseDialog.ts +++ b/extensions/mssql/src/objectManagement/ui/databaseDialog.ts @@ -9,13 +9,17 @@ import { IObjectManagementService } from 'mssql'; import * as localizedConstants from '../localizedConstants'; import { CreateDatabaseDocUrl, DatabasePropertiesDocUrl } from '../constants'; import { Database, DatabaseViewInfo } from '../interfaces'; -import { convertNumToTwoDecimalStringinMB } from '../utils'; +import { convertNumToTwoDecimalStringInMB } from '../utils'; +import { isUndefinedOrNull } from '../../types'; export class DatabaseDialog extends ObjectManagementDialogBase { // Database Properties tabs private generalTab: azdata.Tab; + private optionsTab: azdata.Tab; + private optionsTabSectionsContainer: azdata.Component[] = []; - //Database properties options + // Database properties options + // General Tab private nameInput: azdata.InputBoxComponent; private backupSection: azdata.GroupContainer; private lastDatabaseBackupInput: azdata.InputBoxComponent; @@ -30,6 +34,18 @@ export class DatabaseDialog extends ObjectManagementDialogBase { }, this.objectInfo.lastDatabaseBackup, this.options.isNewObject); const lastDatabaseBackupContainer = this.createLabelInputContainer(localizedConstants.LastDatabaseBackupText, this.lastDatabaseBackupInput); + // Last Database Log Backup this.lastDatabaseLogBackupInput = this.createInputBox(localizedConstants.LastDatabaseLogBackupText, async () => { }, this.objectInfo.lastDatabaseLogBackup, this.options.isNewObject); const lastDatabaseLogBackupContainer = this.createLabelInputContainer(localizedConstants.LastDatabaseLogBackupText, this.lastDatabaseLogBackupInput); @@ -150,33 +186,43 @@ export class DatabaseDialog extends ObjectManagementDialogBase { }, this.objectInfo.name, this.options.isNewObject); const nameContainer = this.createLabelInputContainer(localizedConstants.NamePropertyText, this.nameInput); + // Database Status this.statusInput = this.createInputBox(localizedConstants.StatusText, async () => { }, this.objectInfo.status, this.options.isNewObject); const statusContainer = this.createLabelInputContainer(localizedConstants.StatusText, this.statusInput); + // Database Owner this.ownerInput = this.createInputBox(localizedConstants.OwnerPropertyText, async () => { }, this.objectInfo.owner, this.options.isNewObject); const ownerContainer = this.createLabelInputContainer(localizedConstants.OwnerPropertyText, this.ownerInput); + // Created Date 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); + // Size + 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); + // Space Available + this.spaceAvailabeInput = this.createInputBox(localizedConstants.SpaceAvailableText, async () => { }, convertNumToTwoDecimalStringInMB(this.objectInfo.spaceAvailableInMb), this.options.isNewObject); const spaceAvailabeContainer = this.createLabelInputContainer(localizedConstants.SpaceAvailableText, this.spaceAvailabeInput); + // Number of Users 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); + // Memory Allocated To Memory Optimized Objects + 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); + // Memory Used By Memory Optimized Objects + this.memoryUsedInput = this.createInputBox(localizedConstants.MemoryUsedText, async () => { }, convertNumToTwoDecimalStringInMB(this.objectInfo.memoryUsedByMemoryOptimizedObjectsInMb), this.options.isNewObject); const memoryUsedContainer = this.createLabelInputContainer(localizedConstants.MemoryUsedText, this.memoryUsedInput); + // Collation this.collationInput = this.createInputBox(localizedConstants.CollationText, async () => { }, this.objectInfo.collationName, this.options.isNewObject); const collationContainer = this.createLabelInputContainer(localizedConstants.CollationText, this.collationInput); @@ -195,6 +241,144 @@ export class DatabaseDialog extends ObjectManagementDialogBase { + this.objectInfo.collationName = newValue as string; + }, this.viewInfo.collationNames, this.objectInfo.collationName); + containers.push(this.createLabelInputContainer(localizedConstants.CollationText, collationDropbox)); + + // Recovery Model + let displayOptionsArray = this.viewInfo.recoveryModels.length === 0 ? [this.objectInfo.recoveryModel] : this.viewInfo.recoveryModels; + let isEnabled = this.viewInfo.recoveryModels.length === 0 ? false : true; + let recoveryDropbox = this.createDropdown(localizedConstants.RecoveryModelText, async (newValue) => { + this.objectInfo.recoveryModel = newValue as string; + }, displayOptionsArray, this.objectInfo.recoveryModel, isEnabled); + containers.push(this.createLabelInputContainer(localizedConstants.RecoveryModelText, recoveryDropbox)); + + // Compatibility Level + let compatibilityDropbox = this.createDropdown(localizedConstants.CompatibilityLevelText, async (newValue) => { + this.objectInfo.compatibilityLevel = newValue as string; + }, this.viewInfo.compatibilityLevels, this.objectInfo.compatibilityLevel); + containers.push(this.createLabelInputContainer(localizedConstants.CompatibilityLevelText, compatibilityDropbox)); + + // Containment Type + displayOptionsArray = this.viewInfo.containmentTypes.length === 0 ? [this.objectInfo.containmentType] : this.viewInfo.containmentTypes; + isEnabled = this.viewInfo.containmentTypes.length === 0 ? false : true; + let containmentDropbox = this.createDropdown(localizedConstants.ContainmentTypeText, async (newValue) => { + this.objectInfo.containmentType = newValue as string; + }, displayOptionsArray, this.objectInfo.containmentType, isEnabled); + containers.push(this.createLabelInputContainer(localizedConstants.ContainmentTypeText, containmentDropbox)); + + const optionsGeneralSection = this.createGroup('', containers, true, true); + this.optionsTabSectionsContainer.push(optionsGeneralSection); + } + + private initializeAutomaticSection(): void { + // Auto Create Incremental Statistics + this.autoCreateIncrementalStatisticsInput = this.createCheckbox(localizedConstants.AutoCreateIncrementalStatisticsText, async (checked) => { + this.objectInfo.autoCreateIncrementalStatistics = checked; + }, this.objectInfo.autoCreateIncrementalStatistics); + + // Auto Create Statistics + this.autoCreateStatisticsInput = this.createCheckbox(localizedConstants.AutoCreateStatisticsText, async (checked) => { + this.objectInfo.autoCreateStatistics = checked; + }, this.objectInfo.autoCreateStatistics); + + // Auto Shrink + this.autoShrinkInput = this.createCheckbox(localizedConstants.AutoShrinkText, async (checked) => { + this.objectInfo.autoShrink = checked; + }, this.objectInfo.autoShrink); + + // Auto Update Statistics + this.autoUpdateStatisticsInput = this.createCheckbox(localizedConstants.AutoUpdateStatisticsText, async (checked) => { + this.objectInfo.autoUpdateStatistics = checked; + }, this.objectInfo.autoUpdateStatistics); + + //Auto Update Statistics Asynchronously + this.autoUpdateStatisticsAsynchronouslyInput = this.createCheckbox(localizedConstants.AutoUpdateStatisticsAsynchronouslyText, async (checked) => { + this.objectInfo.autoUpdateStatisticsAsynchronously = checked; + }, this.objectInfo.autoUpdateStatisticsAsynchronously); + const automaticSection = this.createGroup(localizedConstants.AutomaticSectionHeader, [ + this.autoCreateIncrementalStatisticsInput, + this.autoCreateStatisticsInput, + this.autoShrinkInput, + this.autoUpdateStatisticsInput, + this.autoUpdateStatisticsAsynchronouslyInput + ], true); + + this.optionsTabSectionsContainer.push(automaticSection); + } + + private initializeLedgerSection(): void { + // Ledger Database + this.isLedgerDatabaseInput = this.createCheckbox(localizedConstants.IsLedgerDatabaseText, async (checked) => { + this.objectInfo.isLedgerDatabase = checked; + }, this.objectInfo.isLedgerDatabase); + + const ledgerSection = this.createGroup(localizedConstants.LedgerSectionHeader, [ + this.isLedgerDatabaseInput + ], true); + + this.optionsTabSectionsContainer.push(ledgerSection); + } + + private initializeRecoverySection(): void { + // Page Verify + this.pageVerifyInput = this.createDropdown(localizedConstants.PageVerifyText, async (newValue) => { + this.objectInfo.pageVerify = newValue; + }, this.viewInfo.pageVerifyOptions, this.objectInfo.pageVerify, true); + const pageVerifyContainer = this.createLabelInputContainer(localizedConstants.PageVerifyText, this.pageVerifyInput); + + // Recovery Time In Seconds + this.targetRecoveryTimeInSecInput = this.createInputBox(localizedConstants.TargetRecoveryTimeInSecondsText, async (newValue) => { + this.objectInfo.targetRecoveryTimeInSec = Number(newValue); + }, this.objectInfo.targetRecoveryTimeInSec.toString(), true, 'number'); + const targetRecoveryTimeContainer = this.createLabelInputContainer(localizedConstants.TargetRecoveryTimeInSecondsText, this.targetRecoveryTimeInSecInput); + + const recoverySection = this.createGroup(localizedConstants.RecoverySectionHeader, [ + pageVerifyContainer, + targetRecoveryTimeContainer + ], true); + + this.optionsTabSectionsContainer.push(recoverySection); + } + + private initializeStateSection(): void { + let containers: azdata.Component[] = []; + // Database Read-Only + if (!isUndefinedOrNull(this.objectInfo.databaseReadOnly)) { + this.databaseReadOnlyInput = this.createCheckbox(localizedConstants.DatabaseReadOnlyText, async (checked) => { + this.objectInfo.databaseReadOnly = checked; + }, this.objectInfo.databaseReadOnly); + containers.push(this.databaseReadOnlyInput); + } + + // Database Status + this.statusInput = this.createInputBox(localizedConstants.StatusText, async () => { }, this.objectInfo.status, this.options.isNewObject); + containers.push(this.createLabelInputContainer(localizedConstants.DatabaseStateText, this.statusInput)); + + // Encryption Enabled + this.encryptionEnabledInput = this.createCheckbox(localizedConstants.EncryptionEnabledText, async (checked) => { + this.objectInfo.encryptionEnabled = checked; + }, this.objectInfo.encryptionEnabled); + containers.push(this.encryptionEnabledInput); + + // Restrict Access + if (!isUndefinedOrNull(this.objectInfo.restrictAccess)) { + this.restrictAccessInput = this.createDropdown(localizedConstants.RestrictAccessText, async (newValue) => { + this.objectInfo.restrictAccess = newValue; + }, this.viewInfo.restrictAccessOptions, this.objectInfo.restrictAccess, true); + containers.push(this.createLabelInputContainer(localizedConstants.RestrictAccessText, this.restrictAccessInput)); + } + + const stateSection = this.createGroup(localizedConstants.StateSectionHeader, containers, true); + this.optionsTabSectionsContainer.push(stateSection); + } + //#endregion + private initializeConfigureSLOSection(): azdata.GroupContainer { let containers: azdata.Component[] = []; if (this.viewInfo.azureEditions?.length > 0) { diff --git a/extensions/mssql/src/objectManagement/utils.ts b/extensions/mssql/src/objectManagement/utils.ts index 482c23b6d0..ef68617d54 100644 --- a/extensions/mssql/src/objectManagement/utils.ts +++ b/extensions/mssql/src/objectManagement/utils.ts @@ -43,8 +43,10 @@ export function isValidSQLPassword(password: string, userName: string = 'sa'): b 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 { +/** + * Converts number to two decimal placed string + */ +export function convertNumToTwoDecimalStringInMB(value: number): string { return localizedConstants.StringValueInMB(value?.toFixed(2)); } diff --git a/extensions/mssql/src/test/util/utils.test.ts b/extensions/mssql/src/test/util/utils.test.ts index d5b34ef63b..8022c328f6 100644 --- a/extensions/mssql/src/test/util/utils.test.ts +++ b/extensions/mssql/src/test/util/utils.test.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { escapeSingleQuotes } from '../../objectManagement/utils'; +import { convertNumToTwoDecimalStringInMB, escapeSingleQuotes } from '../../objectManagement/utils'; import 'mocha'; import * as should from 'should'; @@ -29,4 +29,10 @@ describe('escapeSingleQuotes Method Tests', () => { const ret = `Server/Database[@Name='${escapeSingleQuotes(dbName)}']`; should(ret).equal(testString); }); + + it('convertNumToTwoDecimalStringInMB function should convert and return the passed integer value to string with two decimals and in MB units', () => { + should(convertNumToTwoDecimalStringInMB(0)).equals('0.00 MB', 'should return string value In MB with two decimals'); + should(convertNumToTwoDecimalStringInMB(10)).equals('10.00 MB', 'should return string value In MB with two decimals'); + should(convertNumToTwoDecimalStringInMB(10.23)).equals('10.23 MB', 'should return string value In MB with two decimals'); + }); });