Enabling QueryStore options to the database properties (#24255)

* Initial commit

* updated query capture policy props

* all done except, querydiskSpaceSection

* disabling capture policy options on off mode but with custom mode enabled

* adding query disk usage section with purge button

* fg table replace

* STS vbump to 4.9.0.34

* typo
This commit is contained in:
Sai Avishkar Sreerama
2023-09-07 21:37:24 -05:00
committed by GitHub
parent 594aabb835
commit aa006b9da7
9 changed files with 535 additions and 63 deletions

View File

@@ -1,6 +1,6 @@
{
"downloadUrl": "https://github.com/Microsoft/sqltoolsservice/releases/download/{#version#}/microsoft.sqltools.servicelayer-{#fileName#}",
"version": "4.9.0.32",
"version": "4.9.0.34",
"downloadFileNames": {
"Windows_86": "win-x86-net7.0.zip",
"Windows_64": "win-x64-net7.0.zip",

View File

@@ -1699,6 +1699,16 @@ export namespace GetAssociatedFilesRequest {
export const type = new RequestType<GetAssociatedFilesRequestParams, string[], void, void>('admin/getassociatedfiles');
}
export namespace PurgeQueryStoreDataRequest {
export const type = new RequestType<purgeQueryStoreDataRequestParams, void, void, void>('objectManagement/purgeQueryStoreData');
}
export interface purgeQueryStoreDataRequestParams {
connectionUri: string;
database: string;
objectUrn: string;
}
// ------------------------------- < Object Management > ------------------------------------
// ------------------------------- < Encryption IV/KEY updation Event > ------------------------------------

View File

@@ -1020,6 +1020,13 @@ declare module 'mssql' {
* @returns An array of file path strings for each of the associated files.
*/
getAssociatedFiles(connectionUri: string, primaryFilePath: string): Thenable<string[]>;
/**
* Clears all query store data from the database
* @param connectionUri The URI of the server connection.
* @param database The target database.
* @param objectUrn SMO Urn of the database to be detached. More information: https://learn.microsoft.com/sql/relational-databases/server-management-objects-smo/overview-smo
*/
purgeQueryStoreData(connectionUri: string, database: string, objectUrn: string): Thenable<void>;
}
export interface DatabaseFileData {

View File

@@ -40,6 +40,7 @@ export const DatabaseGeneralPropertiesDocUrl = 'https://learn.microsoft.com/sql/
export const DatabaseOptionsPropertiesDocUrl = 'https://learn.microsoft.com/sql/relational-databases/databases/database-properties-options-page'
export const DropDatabaseDocUrl = 'https://learn.microsoft.com/sql/t-sql/statements/drop-database-transact-sql';
export const DatabaseScopedConfigurationPropertiesDocUrl = 'https://learn.microsoft.com/sql/t-sql/statements/alter-database-scoped-configuration-transact-sql'
export const QueryStorePropertiesDocUrl = 'https://learn.microsoft.com/sql/relational-databases/databases/database-properties-query-store-page'
export const DatabaseFilesPropertiesDocUrl = 'https://learn.microsoft.com/sql/relational-databases/databases/database-properties-files-page'
export const DatabaseFileGroupsPropertiesDocUrl = 'https://learn.microsoft.com/sql/relational-databases/databases/database-properties-filegroups-page'

View File

@@ -458,6 +458,7 @@ export interface Database extends ObjectManagement.SqlObject {
isFilesTabSupported?: boolean;
files?: DatabaseFile[];
filegroups?: FileGroup[];
queryStoreOptions?: QueryStoreOptions;
}
export interface DatabaseViewInfo extends ObjectManagement.ObjectViewInfo<Database> {
@@ -481,6 +482,32 @@ export interface DatabaseViewInfo extends ObjectManagement.ObjectViewInfo<Databa
rowDataFileGroupsOptions?: string[];
fileStreamFileGroupsOptions?: string[];
fileTypesOptions?: string[];
operationModeOptions?: string[];
statisticsCollectionIntervalOptions?: string[];
queryStoreCaptureModeOptions?: string[];
sizeBasedCleanupModeOptions?: string[];
staleThresholdOptions?: string[];
}
export interface QueryStoreOptions {
actualMode: string;
dataFlushIntervalInMinutes: number;
statisticsCollectionInterval: string;
maxPlansPerQuery: number;
maxSizeInMB: number;
queryStoreCaptureMode: string;
sizeBasedCleanupMode: string;
staleQueryThresholdInDays: number;
waitStatisticsCaptureMode?: boolean;
capturePolicyOptions?: QueryStoreCapturePolicyOptions;
currentStorageSizeInMB: number;
}
export interface QueryStoreCapturePolicyOptions {
executionCount: number;
staleThreshold: string;
totalCompileCPUTimeInMS: number;
totalExecutionCPUTimeInMS: number;
}
export interface DatabaseScopedConfigurationsInfo {

View File

@@ -385,6 +385,7 @@ export const DatabaseStateText = localize('objectManagement.databaseProperties.d
export const EncryptionEnabledText = localize('objectManagement.databaseProperties.encryptionEnabledText', "Encryption Enabled");
export const RestrictAccessText = localize('objectManagement.databaseProperties.restrictAccessText', "Restrict Access");
export const DatabaseScopedConfigurationTabHeader = localize('objectManagement.databaseProperties.databaseProperties.databaseScopedConfigurationTabHeader', "Database Scoped Configuration");
export const QueryStoreTabHeader = localize('objectManagement.databaseProperties.databaseProperties.queryStoreTabHeader', "Query Store");
export const DatabaseScopedOptionsColumnHeader = localize('objectManagement.databaseProperties.databaseScopedOptionsColumnHeader', "Database Scoped Options");
export const ValueForPrimaryColumnHeader = localize('objectManagement.databaseProperties.valueForPrimaryColumnHeader', "Value for Primary");
export const ValueForSecondaryColumnHeader = localize('objectManagement.databaseProperties.valueForSecondaryColumnHeader', "Value for Secondary");
@@ -440,6 +441,30 @@ export const FilestreamFilesText = localize('objectManagement.databaseProperties
export const AddFilegroupText = localize('objectManagement.databaseProperties.addFilegroupButtonText', "Add Filegroup");
export const FilegroupExistsError = (name: string) => localize('objectManagement.databaseProperties.FilegroupExistsError', "File group '{0}' could not be added to the collection, because it already exists.", name);
export const EmptyFilegroupNameError = localize('objectManagement.databaseProperties.emptyFilegroupNameError', "Cannot use empty object names for filegroups.");
export const ActualOperationModeText = localize('objectManagement.databaseProperties.actualOperationModeText', "Operation Mode (Actual)");
export const RequestedOperationModeText = localize('objectManagement.databaseProperties.requestedOperationModeText', "Operation Mode (Requested)");
export const DataFlushIntervalInMinutesText = localize('objectManagement.databaseProperties.dataFlushIntervalInMinutesText', "Data Flush Interval (Minutes)");
export const StatisticsCollectionInterval = localize('objectManagement.databaseProperties.statisticsCollectionInterval', "Statistics Collection Interval");
export const MaxPlansPerQueryText = localize('objectManagement.databaseProperties.maxPlansPerQueryText', "Max Plans Per Query");
export const MaxSizeInMbText = localize('objectManagement.databaseProperties.maxSizeInMbText', "Max Size (MB)");
export const QueryStoreCaptureModeText = localize('objectManagement.databaseProperties.queryStoreCaptureModeText', "Query Store Capture Mode");
export const SizeBasedCleanupModeText = localize('objectManagement.databaseProperties.sizeBasedCleanupModeText', "Size Based Cleanup Mode");
export const StateQueryThresholdInDaysText = localize('objectManagement.databaseProperties.stateQueryThresholdInDaysText', "State Query Threshold (Days)");
export const WaitStatisticsCaptureModeText = localize('objectManagement.databaseProperties.waitStatisticsCaptureModeText', "Wait Statistics Capture Mode");
export const MonitoringSectionText = localize('objectManagement.databaseProperties.monitoringSectionText', "Monitoring");
export const QueryStoreRetentionSectionText = localize('objectManagement.databaseProperties.queryStoreRetentionSectionText', "Query Store Retention");
export const QueryStoreCapturePolicySectionText = localize('objectManagement.databaseProperties.queryStoreCapturePolicySectionText', "Query Store Capture Policy");
export const QueryStoreCurrentDiskUsageSectionText = localize('objectManagement.databaseProperties.queryStoreCurrentDiskUsageSectionText', "Current Disk Usage");
export const ExecutionCountText = localize('objectManagement.databaseProperties.executionCountText', "Execution Count");
export const StaleThresholdText = localize('objectManagement.databaseProperties.staleThresholdText', "Stale Threshold");
export const TotalCompileCPUTimeInMsText = localize('objectManagement.databaseProperties.totalCompileCPUTimeInMs', "Total Compile CPU Time (ms)");
export const TotalExecutionCPUTimeInMsText = localize('objectManagement.databaseProperties.totalExecutionCPUTimeInMsText', "Total Execution CPU Time (ms)");
export const QueryStoreCapturemodeCustomText = localize('objectManagement.databaseProperties.queryStoreCapturemodeCustomText', "Custom");
export const QueryStoreUsedText = localize('objectManagement.databaseProperties.queryStoreUsedText', "Query Store Used");
export const QueryStoreAvailableText = localize('objectManagement.databaseProperties.queryStoreAvailableText', "Query Store Available");
export const PurgeQueryDataButtonText = localize('objectManagement.databaseProperties.purgeQueryDataButtonText', "Purge Query Store Data");
export const YesText = localize('objectManagement.databaseProperties.yesText', "Yes");
export const PurgeQueryStoreDataMessage = (databaseName: string) => localize('objectManagement.databaseProperties.purgeQueryStoreDataMessage', "Are you sure you want to purge the Query Store data from '{0}'?", databaseName);
// Util functions
export function getNodeTypeDisplayName(type: string, inTitle: boolean = false): string {

View File

@@ -90,6 +90,11 @@ export class ObjectManagementService extends BaseService implements IObjectManag
const params: contracts.GetAssociatedFilesRequestParams = { connectionUri, primaryFilePath };
return this.runWithErrorHandling(contracts.GetAssociatedFilesRequest.type, params);
}
async purgeQueryStoreData(connectionUri: string, database: string, objectUrn: string): Promise<void> {
const params: contracts.purgeQueryStoreDataRequestParams = { connectionUri, database, objectUrn };
return this.runWithErrorHandling(contracts.PurgeQueryStoreDataRequest.type, params);
}
}
const ServerLevelSecurableTypes: SecurableTypeMetadata[] = [
@@ -277,6 +282,10 @@ export class TestObjectManagementService implements IObjectManagementService {
return this.delayAndResolve([]);
}
async purgeQueryStoreData(connectionUri: string, database: string, objectUrn: string): Promise<void> {
return this.delayAndResolve([]);
}
private generateSearchResult(objectType: ObjectManagement.NodeType, schema: string | undefined, count: number): ObjectManagement.SearchResultItem[] {
let items: ObjectManagement.SearchResultItem[] = [];
for (let i = 0; i < count; i++) {

View File

@@ -8,17 +8,23 @@ import { ObjectManagementDialogBase, ObjectManagementDialogOptions } from './obj
import { DefaultInputWidth, DefaultTableWidth, DefaultMinTableRowCount, DefaultMaxTableRowCount, getTableHeight, DialogButton } from '../../ui/dialogBase';
import { IObjectManagementService } from 'mssql';
import * as localizedConstants from '../localizedConstants';
import { CreateDatabaseDocUrl, DatabaseGeneralPropertiesDocUrl, DatabaseFilesPropertiesDocUrl, DatabaseOptionsPropertiesDocUrl, DatabaseScopedConfigurationPropertiesDocUrl, DatabaseFileGroupsPropertiesDocUrl } from '../constants';
import { CreateDatabaseDocUrl, DatabaseGeneralPropertiesDocUrl, DatabaseFilesPropertiesDocUrl, DatabaseOptionsPropertiesDocUrl, DatabaseScopedConfigurationPropertiesDocUrl, DatabaseFileGroupsPropertiesDocUrl, QueryStorePropertiesDocUrl } from '../constants';
import { Database, DatabaseFile, DatabaseScopedConfigurationsInfo, DatabaseViewInfo, FileGrowthType, FileGroup, FileGroupType } from '../interfaces';
import { convertNumToTwoDecimalStringInMB } from '../utils';
import { isUndefinedOrNull } from '../../types';
import { deepClone } from '../../util/objects';
import { DatabaseFileDialog } from './databaseFileDialog';
import * as vscode from 'vscode';
const MAXDOP_Max_Limit = 32767;
const PAUSED_RESUMABLE_INDEX_Max_Limit = 71582;
const DscTableRowLength = 15;
export const tableHeaderCssStylings = { 'border': 'solid 1px #C8C8C8' };
export const tableRowCssStylings = { 'border': 'solid 1px #C8C8C8' }
// export const tableHeaderCssStylings = { 'border-top': 'solid 1px #C8C8C8', 'border-bottom': 'none', 'border-left': 'none', 'border-right': 'none' };
// export const tableRowCssStylings = { 'border-top': 'solid 1px #ccc', 'border-bottom': 'none', 'border-left': 'none', 'border-right': 'none' }
export class DatabaseDialog extends ObjectManagementDialogBase<Database, DatabaseViewInfo> {
// Database Properties tabs
private generalTab: azdata.Tab;
@@ -26,6 +32,7 @@ export class DatabaseDialog extends ObjectManagementDialogBase<Database, Databas
private optionsTab: azdata.Tab;
private fileGroupsTab: azdata.Tab;
private dscTab: azdata.Tab;
private queryStoreTab: azdata.Tab;
private optionsTabSectionsContainer: azdata.Component[] = [];
private activeTabId: string;
@@ -53,7 +60,7 @@ export class DatabaseDialog extends ObjectManagementDialogBase<Database, Databas
private filestreamDatafileGroupsOptions: string[];
// fileGroups Tab
private readonly fileGroupsTabId: string = 'fileGroupsDatabaseId';
private rowsFilegroupsTable: azdata.TableComponent;
private rowsFilegroupsTable: azdata.DeclarativeTableComponent;
private filestreamFilegroupsTable: azdata.TableComponent;
private memoryOptimizedFilegroupsTable: azdata.TableComponent;
private rowsFilegroupNameInput: azdata.InputBoxComponent;
@@ -96,6 +103,26 @@ export class DatabaseDialog extends ObjectManagementDialogBase<Database, Databas
private dscSecondaryCheckboxForInputGroup: azdata.GroupContainer;
private setFocusToInput: azdata.InputBoxComponent = undefined;
private currentRowObjectInfo: DatabaseScopedConfigurationsInfo;
// Query store Tab
private readonly queryStoreTabId: string = 'queryStoreTabId';
private queryStoreTabSectionsContainer: azdata.Component[] = [];
private areQueryStoreOptionsEnabled: boolean;
private requestedOperationMode: azdata.DropDownComponent;
private dataFlushIntervalInMinutes: azdata.InputBoxComponent;
private statisticsCollectionInterval: azdata.DropDownComponent;
private maxPlansPerQuery: azdata.InputBoxComponent;
private maxSizeinMB: azdata.InputBoxComponent;
private queryStoreCaptureMode: azdata.DropDownComponent;
private sizeBasedCleanupMode: azdata.DropDownComponent;
private stateQueryThresholdInDays: azdata.InputBoxComponent;
private waitStatisticsCaptureMode: azdata.CheckBoxComponent;
private executionCount: azdata.InputBoxComponent;
private staleThreshold: azdata.DropDownComponent;
private totalCompileCPUTimeInMS: azdata.InputBoxComponent;
private totalExecutionCPUTimeInMS: azdata.InputBoxComponent;
private operationModeOffOption: string;
private purgeQueryDataButton: azdata.ButtonComponent;
constructor(objectManagementService: IObjectManagementService, options: ObjectManagementDialogOptions) {
super(objectManagementService, options);
@@ -123,6 +150,9 @@ export class DatabaseDialog extends ObjectManagementDialogBase<Database, Databas
case this.dscTabId:
helpUrl = DatabaseScopedConfigurationPropertiesDocUrl;
break;
case this.queryStoreTabId:
helpUrl = QueryStorePropertiesDocUrl;
break;
default:
break;
}
@@ -219,6 +249,23 @@ export class DatabaseDialog extends ObjectManagementDialogBase<Database, Databas
tabs.push(this.dscTab);
}
// Intialize Query Store Tab
if (!isUndefinedOrNull(this.objectInfo.queryStoreOptions)) {
this.initializeQueryStoreGeneralSection();
this.initializeQueryStoreMonitoringSection();
this.initializeQueryStoreRetentionSection();
if (!isUndefinedOrNull(this.objectInfo.queryStoreOptions.capturePolicyOptions)) {
this.initializeQueryStoreCapturePolicySection();
}
await this.initializeQueryStoreCurrentDiskStorageSection();
this.queryStoreTab = {
title: localizedConstants.QueryStoreTabHeader,
id: this.queryStoreTabId,
content: this.createGroup('', this.queryStoreTabSectionsContainer, false)
}
tabs.push(this.queryStoreTab);
}
// Initialize tab group with tabbed panel
const propertiesTabGroup = { title: '', tabs: tabs };
const propertiesTabbedPannel = this.modelView.modelBuilder.tabbedPanel()
@@ -616,8 +663,8 @@ export class DatabaseDialog extends ObjectManagementDialogBase<Database, Databas
*/
private async updateFileGroupsTablesfileCount(fileType: string): Promise<void> {
if (fileType === localizedConstants.RowsDataFileType) {
let data = this.getTableData(FileGroupType.RowsFileGroup);
await this.setTableData(this.rowsFilegroupsTable, data);
// let data = this.getTableData(FileGroupType.RowsFileGroup);
// await this.setTableData(this.rowsFilegroupsTable, data);
}
else if (fileType === localizedConstants.FilestreamFileType) {
let data = this.getTableData(FileGroupType.FileStreamDataFileGroup);
@@ -649,13 +696,13 @@ export class DatabaseDialog extends ObjectManagementDialogBase<Database, Databas
});
}
}
else if (table === this.rowsFilegroupsTable && this.rowsFilegroupsTable.selectedRows !== undefined && this.rowsFilegroupsTable.selectedRows.length === 1) {
const selectedRow = this.rowDataFileGroupsTableRows[this.rowsFilegroupsTable.selectedRows[0]];
// Cannot delete a row file if the fileGroup is Primary.
if (selectedRow.name === 'PRIMARY' && selectedRow.id > 0) {
isEnabled = false;
}
}
// else if (table === this.rowsFilegroupsTable && this.rowsFilegroupsTable.selectedRows !== undefined && this.rowsFilegroupsTable.selectedRows.length === 1) {
// const selectedRow = this.rowDataFileGroupsTableRows[this.rowsFilegroupsTable.selectedRows[0]];
// // Cannot delete a row file if the fileGroup is Primary.
// if (selectedRow.name === 'PRIMARY' && selectedRow.id > 0) {
// isEnabled = false;
// }
// }
return isEnabled;
}
@@ -713,49 +760,62 @@ export class DatabaseDialog extends ObjectManagementDialogBase<Database, Databas
*/
private async initializeRowsFileGroupSection(): Promise<azdata.GroupContainer> {
const data = this.getTableData(FileGroupType.RowsFileGroup);
this.rowsFilegroupsTable = this.modelView.modelBuilder.table().withProps({
this.rowsFilegroupsTable = this.modelView.modelBuilder.declarativeTable().withProps({
columns: [{
type: azdata.ColumnType.text,
value: localizedConstants.NameText,
width: 120
valueType: azdata.DeclarativeDataType.string,
width: 120,
isReadOnly: false,
displayName: localizedConstants.NameText,
headerCssStyles: tableHeaderCssStylings,
rowCssStyles: tableRowCssStylings
}, {
type: azdata.ColumnType.text,
value: localizedConstants.FilesText,
width: 60
valueType: azdata.DeclarativeDataType.string,
width: 60,
isReadOnly: true,
displayName: localizedConstants.FilesText,
headerCssStyles: tableHeaderCssStylings,
rowCssStyles: tableRowCssStylings
}, {
type: azdata.ColumnType.checkBox,
value: localizedConstants.ReadOnlyText,
width: 80
valueType: azdata.DeclarativeDataType.boolean,
width: 80,
isReadOnly: false,
displayName: localizedConstants.ReadOnlyText,
headerCssStyles: tableHeaderCssStylings,
rowCssStyles: tableRowCssStylings
}, {
type: azdata.ColumnType.checkBox,
value: localizedConstants.DefaultText,
width: 80
valueType: azdata.DeclarativeDataType.boolean,
width: 80,
isReadOnly: false,
displayName: localizedConstants.DefaultText,
headerCssStyles: tableHeaderCssStylings,
rowCssStyles: tableRowCssStylings
}, {
type: azdata.ColumnType.checkBox,
value: localizedConstants.AutogrowAllFilesText,
width: 110
valueType: azdata.DeclarativeDataType.boolean,
isReadOnly: false,
displayName: localizedConstants.AutogrowAllFilesText,
width: 110,
headerCssStyles: tableHeaderCssStylings,
rowCssStyles: tableRowCssStylings
}],
data: data,
height: getTableHeight(data.length, DefaultMinTableRowCount, DefaultMaxTableRowCount),
width: DefaultTableWidth,
forceFitColumns: azdata.ColumnSizingMode.DataFit,
CSSStyles: {
'margin-left': '10px'
}
}).component();
this.rowsFilegroupNameInput = this.getFilegroupNameInput(this.rowsFilegroupsTable, FileGroupType.RowsFileGroup);
const addButtonComponent: DialogButton = {
buttonAriaLabel: localizedConstants.AddFilegroupText,
buttonHandler: () => this.onAddDatabaseFileGroupsButtonClicked(this.rowsFilegroupsTable)
buttonHandler: () => this.onAddDatabaseFileGroupsButtonClicked1(this.rowsFilegroupsTable)
};
const removeButtonComponent: DialogButton = {
buttonAriaLabel: localizedConstants.RemoveButton,
buttonHandler: () => this.onRemoveDatabaseFileGroupsButtonClicked(this.rowsFilegroupsTable)
buttonHandler: () => this.onRemoveDatabaseFileGroupsButtonClicked1(this.rowsFilegroupsTable)
};
const rowsFileGroupButtonContainer = this.addButtonsForTable(this.rowsFilegroupsTable, addButtonComponent, removeButtonComponent);
const rowsFileGroupButtonContainer = this.addButtonsForTable1(this.rowsFilegroupsTable, addButtonComponent, removeButtonComponent);
this.disposables.push(
this.rowsFilegroupsTable.onCellAction(async (arg: azdata.ICheckboxCellActionEventArgs) => {
this.rowsFilegroupsTable.onDataChanged(async (arg: azdata.ICheckboxCellActionEventArgs) => {
let filegroup = this.rowDataFileGroupsTableRows[arg.row];
// Read-Only column
if (arg.column === 2) {
@@ -771,14 +831,14 @@ export class DatabaseDialog extends ObjectManagementDialogBase<Database, Databas
}
// Refresh the table with updated data
let data = this.getTableData(FileGroupType.RowsFileGroup);
await this.setTableData(this.rowsFilegroupsTable, data);
// let data = this.getTableData(FileGroupType.RowsFileGroup);
//await this.setTableData(this.rowsFilegroupsTable, data);
this.onFormFieldChange();
}),
this.rowsFilegroupsTable.onRowSelected(
async () => {
if (this.rowsFilegroupsTable.selectedRows.length === 1) {
const fileGroup = this.rowDataFileGroupsTableRows[this.rowsFilegroupsTable.selectedRows[0]];
if (this.rowsFilegroupsTable.selectedRow === 1) {
const fileGroup = this.rowDataFileGroupsTableRows[this.rowsFilegroupsTable.selectedRow];
await this.rowsFilegroupNameInput.updateCssStyles({ 'visibility': fileGroup.id < 0 ? 'visible' : 'hidden' });
this.rowsFilegroupNameInput.value = fileGroup.name;
this.onFormFieldChange();
@@ -786,10 +846,7 @@ export class DatabaseDialog extends ObjectManagementDialogBase<Database, Databas
}
)
);
const rowContainer = this.modelView.modelBuilder.flexContainer().withItems([this.rowsFilegroupNameInput]).component();
rowContainer.addItems([rowsFileGroupButtonContainer], { flex: '0 0 auto' });
return this.createGroup(localizedConstants.RowsFileGroupsSectionText, [this.rowsFilegroupsTable, rowContainer], true);
return this.createGroup(localizedConstants.RowsFileGroupsSectionText, [this.rowsFilegroupsTable, rowsFileGroupButtonContainer], true);
}
/**
@@ -948,15 +1005,16 @@ export class DatabaseDialog extends ObjectManagementDialogBase<Database, Databas
isDefault: false,
autogrowAllFiles: false
};
if (table === this.rowsFilegroupsTable) {
newRow.type = FileGroupType.RowsFileGroup;
newRow.isReadOnly = false;
newRow.isDefault = false;
newRow.autogrowAllFiles = false
this.objectInfo.filegroups?.push(newRow);
newData = this.getTableData(FileGroupType.RowsFileGroup);
}
else if (table === this.filestreamFilegroupsTable) {
// if (table === this.rowsFilegroupsTable) {
// newRow.type = FileGroupType.RowsFileGroup;
// newRow.isReadOnly = false;
// newRow.isDefault = false;
// newRow.autogrowAllFiles = false
// this.objectInfo.filegroups?.push(newRow);
// newData = this.getTableData(FileGroupType.RowsFileGroup);
// }
// else
if (table === this.filestreamFilegroupsTable) {
newRow.type = FileGroupType.FileStreamDataFileGroup;
newRow.isReadOnly = false;
newRow.isDefault = false;
@@ -976,6 +1034,55 @@ export class DatabaseDialog extends ObjectManagementDialogBase<Database, Databas
}
}
private async onAddDatabaseFileGroupsButtonClicked1(table: azdata.DeclarativeTableComponent): Promise<void> {
let newData: any[] | undefined;
let newRow: FileGroup = {
id: --this.newFileGroupTemporaryId,
name: '',
type: undefined,
isReadOnly: false,
isDefault: false,
autogrowAllFiles: false
};
if (table === this.rowsFilegroupsTable) {
newRow.type = FileGroupType.RowsFileGroup;
newRow.isReadOnly = false;
newRow.isDefault = false;
newRow.autogrowAllFiles = false
this.objectInfo.filegroups?.push(newRow);
newData = this.getTableData(FileGroupType.RowsFileGroup);
}
if (newData !== undefined) {
// Refresh the table with new row data
this.updateFileGroupsOptionsAndTableRows();
// await this.setTableData(table, newData, DefaultMaxTableRowCount);
await table.updateProperties({
data: newData,
height: getTableHeight(newData.length, DefaultMinTableRowCount)
});
}
}
private async onRemoveDatabaseFileGroupsButtonClicked1(table: azdata.DeclarativeTableComponent): Promise<void> {
if (table === this.rowsFilegroupsTable) {
if (this.rowsFilegroupsTable.selectedRow === 1) {
const removeFilegroupIndex = this.objectInfo.filegroups.indexOf(this.rowDataFileGroupsTableRows[this.rowsFilegroupsTable.selectedRow]);
this.objectInfo.filegroups?.splice(removeFilegroupIndex, 1);
var newData = this.getTableData(FileGroupType.RowsFileGroup);
await this.rowsFilegroupNameInput.updateCssStyles({ 'visibility': 'hidden' });
}
}
// Refresh the individual table rows object and table with updated data
this.updateFileGroupsOptionsAndTableRows();
await table.updateProperties({
data: newData,
height: getTableHeight(newData.length, DefaultMinTableRowCount)
});
}
/**
* Prepares the individual table rows for each filegroup type and list of filegroups options
* This will be useful to get the selected row data from the table to get the filegroup property details, helps when have duplicate rows added
@@ -1000,15 +1107,16 @@ export class DatabaseDialog extends ObjectManagementDialogBase<Database, Databas
* @param table table component
*/
private async onRemoveDatabaseFileGroupsButtonClicked(table: azdata.TableComponent): Promise<void> {
if (table === this.rowsFilegroupsTable) {
if (this.rowsFilegroupsTable.selectedRows.length === 1) {
const removeFilegroupIndex = this.objectInfo.filegroups.indexOf(this.rowDataFileGroupsTableRows[this.rowsFilegroupsTable.selectedRows[0]]);
this.objectInfo.filegroups?.splice(removeFilegroupIndex, 1);
var newData = this.getTableData(FileGroupType.RowsFileGroup);
await this.rowsFilegroupNameInput.updateCssStyles({ 'visibility': 'hidden' });
}
}
else if (table === this.filestreamFilegroupsTable) {
// if (table === this.rowsFilegroupsTable) {
// if (this.rowsFilegroupsTable.selectedRows.length === 1) {
// const removeFilegroupIndex = this.objectInfo.filegroups.indexOf(this.rowDataFileGroupsTableRows[this.rowsFilegroupsTable.selectedRows[0]]);
// this.objectInfo.filegroups?.splice(removeFilegroupIndex, 1);
// var newData = this.getTableData(FileGroupType.RowsFileGroup);
// await this.rowsFilegroupNameInput.updateCssStyles({ 'visibility': 'hidden' });
// }
// }
// else
if (table === this.filestreamFilegroupsTable) {
if (this.filestreamFilegroupsTable.selectedRows.length === 1) {
const removeFilegroupIndex = this.objectInfo.filegroups.indexOf(this.filestreamDataFileGroupsTableRows[this.filestreamFilegroupsTable.selectedRows[0]]);
this.objectInfo.filegroups?.splice(removeFilegroupIndex, 1);
@@ -1040,9 +1148,10 @@ export class DatabaseDialog extends ObjectManagementDialogBase<Database, Databas
return this.createInputBox(async (value) => {
if (table.selectedRows.length === 1) {
let fg = null;
if (table === this.rowsFilegroupsTable) {
fg = this.rowDataFileGroupsTableRows[table.selectedRows[0]];
} else if (table === this.filestreamFilegroupsTable) {
// if (table === this.rowsFilegroupsTable) {
// fg = this.rowDataFileGroupsTableRows[table.selectedRows[0]];
// } else
if (table === this.filestreamFilegroupsTable) {
fg = this.filestreamDataFileGroupsTableRows[table.selectedRows[0]];
} else if (table === this.memoryOptimizedFilegroupsTable) {
fg = this.memoryoptimizedFileGroupsTableRows[table.selectedRows[0]];
@@ -1581,6 +1690,245 @@ export class DatabaseDialog extends ObjectManagementDialogBase<Database, Databas
}
// #endregion
//#region Database Properties - Query Store Tab
private initializeQueryStoreGeneralSection(): void {
let containers: azdata.Component[] = [];
const actualOperationMode = this.objectInfo.queryStoreOptions.actualMode;
this.operationModeOffOption = 'Off'
this.areQueryStoreOptionsEnabled = this.objectInfo.queryStoreOptions.actualMode !== this.operationModeOffOption;
// Operation Mode (Actual)
const operationModeActual = this.createInputBox(async () => { }, {
ariaLabel: localizedConstants.ActualOperationModeText,
inputType: 'text',
enabled: false,
value: actualOperationMode
});
containers.push(this.createLabelInputContainer(localizedConstants.ActualOperationModeText, operationModeActual));
// Operation Mode (Requested)
this.requestedOperationMode = this.createDropdown(localizedConstants.RequestedOperationModeText, async (newValue) => {
this.objectInfo.queryStoreOptions.actualMode = newValue as string;
this.areQueryStoreOptionsEnabled = newValue !== this.operationModeOffOption;
await this.toggleQueryStoreOptions();
}, this.viewInfo.operationModeOptions, String(this.objectInfo.queryStoreOptions.actualMode), true, DefaultInputWidth);
containers.push(this.createLabelInputContainer(localizedConstants.RequestedOperationModeText, this.requestedOperationMode));
const generalSection = this.createGroup(localizedConstants.GeneralSectionHeader, containers, true);
this.queryStoreTabSectionsContainer.push(generalSection);
}
private initializeQueryStoreMonitoringSection(): void {
let containers: azdata.Component[] = [];
// Data Flush Interval (Minutes)
this.dataFlushIntervalInMinutes = this.createInputBox(async (newValue) => {
this.objectInfo.queryStoreOptions.dataFlushIntervalInMinutes = Number(newValue);
}, {
ariaLabel: localizedConstants.DataFlushIntervalInMinutesText,
inputType: 'number',
enabled: this.areQueryStoreOptionsEnabled,
value: String(this.objectInfo.queryStoreOptions.dataFlushIntervalInMinutes),
min: 0
});
containers.push(this.createLabelInputContainer(localizedConstants.DataFlushIntervalInMinutesText, this.dataFlushIntervalInMinutes));
// Statistics Collection Interval
this.statisticsCollectionInterval = this.createDropdown(localizedConstants.StatisticsCollectionInterval, async (newValue) => {
this.objectInfo.queryStoreOptions.statisticsCollectionInterval = String(newValue);
}, this.viewInfo.statisticsCollectionIntervalOptions, this.objectInfo.queryStoreOptions.statisticsCollectionInterval, this.areQueryStoreOptionsEnabled, DefaultInputWidth);
containers.push(this.createLabelInputContainer(localizedConstants.StatisticsCollectionInterval, this.statisticsCollectionInterval));
const monitoringSection = this.createGroup(localizedConstants.MonitoringSectionText, containers, true);
this.queryStoreTabSectionsContainer.push(monitoringSection);
}
private initializeQueryStoreRetentionSection(): void {
let containers: azdata.Component[] = [];
// Max Plans Per Query
this.maxPlansPerQuery = this.createInputBox(async (newValue) => {
this.objectInfo.queryStoreOptions.maxPlansPerQuery = Number(newValue);
}, {
ariaLabel: localizedConstants.MaxPlansPerQueryText,
inputType: 'number',
enabled: this.areQueryStoreOptionsEnabled,
value: String(this.objectInfo.queryStoreOptions.maxPlansPerQuery),
min: 0
});
containers.push(this.createLabelInputContainer(localizedConstants.MaxPlansPerQueryText, this.maxPlansPerQuery));
// Max size (MB)
this.maxSizeinMB = this.createInputBox(async (newValue) => {
this.objectInfo.queryStoreOptions.maxSizeInMB = Number(newValue);
}, {
ariaLabel: localizedConstants.MaxSizeInMbText,
inputType: 'number',
enabled: this.areQueryStoreOptionsEnabled,
value: String(this.objectInfo.queryStoreOptions.maxSizeInMB),
min: 0
});
containers.push(this.createLabelInputContainer(localizedConstants.MaxSizeInMbText, this.maxSizeinMB));
// Query Store Capture Mode
this.queryStoreCaptureMode = this.createDropdown(localizedConstants.QueryStoreCaptureModeText, async (newValue) => {
this.objectInfo.queryStoreOptions.queryStoreCaptureMode = newValue as string;
await this.toggleQueryCapturePolicySection(newValue === localizedConstants.QueryStoreCapturemodeCustomText
&& this.requestedOperationMode.value !== this.operationModeOffOption);
}, this.viewInfo.queryStoreCaptureModeOptions, this.objectInfo.queryStoreOptions.queryStoreCaptureMode, this.areQueryStoreOptionsEnabled, DefaultInputWidth);
containers.push(this.createLabelInputContainer(localizedConstants.QueryStoreCaptureModeText, this.queryStoreCaptureMode));
// Size Based Cleanup Mode
this.sizeBasedCleanupMode = this.createDropdown(localizedConstants.SizeBasedCleanupModeText, async (newValue) => {
this.objectInfo.queryStoreOptions.sizeBasedCleanupMode = newValue as string;
}, this.viewInfo.sizeBasedCleanupModeOptions, this.objectInfo.queryStoreOptions.sizeBasedCleanupMode, this.areQueryStoreOptionsEnabled, DefaultInputWidth);
containers.push(this.createLabelInputContainer(localizedConstants.SizeBasedCleanupModeText, this.sizeBasedCleanupMode));
// State Query Threshold (Days)
this.stateQueryThresholdInDays = this.createInputBox(async (newValue) => {
this.objectInfo.queryStoreOptions.staleQueryThresholdInDays = Number(newValue);
}, {
ariaLabel: localizedConstants.StateQueryThresholdInDaysText,
inputType: 'number',
enabled: this.areQueryStoreOptionsEnabled,
value: String(this.objectInfo.queryStoreOptions.staleQueryThresholdInDays),
min: 0
});
containers.push(this.createLabelInputContainer(localizedConstants.StateQueryThresholdInDaysText, this.stateQueryThresholdInDays));
// Wait Statistics Capture Mode - supported from 2017 or higher
if (!isUndefinedOrNull(this.objectInfo.queryStoreOptions.waitStatisticsCaptureMode)) {
this.waitStatisticsCaptureMode = this.createCheckbox(localizedConstants.WaitStatisticsCaptureModeText, async (checked) => {
this.objectInfo.queryStoreOptions.waitStatisticsCaptureMode = checked;
}, this.objectInfo.queryStoreOptions.waitStatisticsCaptureMode, this.areQueryStoreOptionsEnabled);
containers.push(this.waitStatisticsCaptureMode);
}
const retentionSection = this.createGroup(localizedConstants.WaitStatisticsCaptureModeText, containers, true);
this.queryStoreTabSectionsContainer.push(retentionSection);
}
private initializeQueryStoreCapturePolicySection(): void {
let containers: azdata.Component[] = [];
// Execution Count
this.executionCount = this.createInputBox(async (newValue) => {
this.objectInfo.queryStoreOptions.capturePolicyOptions.executionCount = Number(newValue);
}, {
ariaLabel: localizedConstants.ExecutionCountText,
inputType: 'number',
enabled: this.areQueryStoreOptionsEnabled,
value: String(this.objectInfo.queryStoreOptions.capturePolicyOptions.executionCount),
min: 0
});
containers.push(this.createLabelInputContainer(localizedConstants.ExecutionCountText, this.executionCount));
// Stale Threshold
this.staleThreshold = this.createDropdown(localizedConstants.StaleThresholdText, async (newValue) => {
this.objectInfo.queryStoreOptions.capturePolicyOptions.staleThreshold = newValue as string;
}, this.viewInfo.staleThresholdOptions, this.objectInfo.queryStoreOptions.capturePolicyOptions.staleThreshold, this.areQueryStoreOptionsEnabled, DefaultInputWidth);
containers.push(this.createLabelInputContainer(localizedConstants.StaleThresholdText, this.staleThreshold));
// Total Compile CPU Time (ms)
this.totalCompileCPUTimeInMS = this.createInputBox(async (newValue) => {
this.objectInfo.queryStoreOptions.capturePolicyOptions.totalCompileCPUTimeInMS = Number(newValue);
}, {
ariaLabel: localizedConstants.TotalCompileCPUTimeInMsText,
inputType: 'number',
enabled: this.areQueryStoreOptionsEnabled,
value: String(this.objectInfo.queryStoreOptions.capturePolicyOptions.totalCompileCPUTimeInMS),
min: 0
});
containers.push(this.createLabelInputContainer(localizedConstants.TotalCompileCPUTimeInMsText, this.totalCompileCPUTimeInMS));
// Total Execution CPU Time (ms)
this.totalExecutionCPUTimeInMS = this.createInputBox(async (newValue) => {
this.objectInfo.queryStoreOptions.capturePolicyOptions.totalExecutionCPUTimeInMS = Number(newValue);
}, {
ariaLabel: localizedConstants.TotalExecutionCPUTimeInMsText,
inputType: 'number',
enabled: this.areQueryStoreOptionsEnabled,
value: String(this.objectInfo.queryStoreOptions.capturePolicyOptions.totalExecutionCPUTimeInMS),
min: 0
});
containers.push(this.createLabelInputContainer(localizedConstants.TotalExecutionCPUTimeInMsText, this.totalExecutionCPUTimeInMS));
const policySection = this.createGroup(localizedConstants.QueryStoreCapturePolicySectionText, containers, true);
this.queryStoreTabSectionsContainer.push(policySection);
}
private async initializeQueryStoreCurrentDiskStorageSection(): Promise<void> {
let containers: azdata.Component[] = [];
// Database Max size
const databaseName = this.createInputBox(async () => { }, {
ariaLabel: this.objectInfo.name,
inputType: 'text',
enabled: false,
value: localizedConstants.StringValueInMB(String(this.objectInfo.sizeInMb))
});
containers.push(this.createLabelInputContainer(this.objectInfo.name, databaseName));
// Query Store Used
const queryStoreUsed = this.createInputBox(async () => { }, {
ariaLabel: localizedConstants.QueryStoreUsedText,
inputType: 'text',
enabled: false,
value: localizedConstants.StringValueInMB(String(this.objectInfo.queryStoreOptions.currentStorageSizeInMB))
});
containers.push(this.createLabelInputContainer(localizedConstants.QueryStoreUsedText, queryStoreUsed));
// Query Store Available
const queryStoreAvailable = this.createInputBox(async () => { }, {
ariaLabel: localizedConstants.QueryStoreAvailableText,
inputType: 'text',
enabled: false,
value: localizedConstants.StringValueInMB(String(this.objectInfo.queryStoreOptions.maxSizeInMB - this.objectInfo.queryStoreOptions.currentStorageSizeInMB))
});
containers.push(this.createLabelInputContainer(localizedConstants.QueryStoreAvailableText, queryStoreAvailable));
// Prge query data button
this.purgeQueryDataButton = this.createButton(localizedConstants.PurgeQueryDataButtonText, localizedConstants.PurgeQueryDataButtonText, async () => {
await this.purgeQueryStoreDataButtonClick();
});
this.purgeQueryDataButton.width = DefaultInputWidth;
await this.purgeQueryDataButton.updateCssStyles({ 'margin': '10px 0px, 0px, 0px' });
containers.push(this.createLabelInputContainer('', this.purgeQueryDataButton));
const diskUsageSection = this.createGroup(localizedConstants.QueryStoreCurrentDiskUsageSectionText, containers, true);
this.queryStoreTabSectionsContainer.push(diskUsageSection);
}
/**
* Opens confirmation warning for clearing the query store data for the database
*/
private async purgeQueryStoreDataButtonClick(): Promise<void> {
const response = await vscode.window.showWarningMessage(localizedConstants.PurgeQueryStoreDataMessage(this.objectInfo.name), localizedConstants.YesText);
if (response !== localizedConstants.YesText) {
return;
}
await this.objectManagementService.purgeQueryStoreData(this.options.connectionUri, this.options.database, this.options.objectUrn);
}
private async toggleQueryStoreOptions(): Promise<void> {
this.dataFlushIntervalInMinutes.enabled
= this.statisticsCollectionInterval.enabled
= this.maxPlansPerQuery.enabled
= this.maxSizeinMB.enabled
= this.queryStoreCaptureMode.enabled
= this.sizeBasedCleanupMode.enabled
= this.stateQueryThresholdInDays.enabled = this.areQueryStoreOptionsEnabled;
if (!isUndefinedOrNull(this.objectInfo.queryStoreOptions.waitStatisticsCaptureMode)) {
this.waitStatisticsCaptureMode.enabled = this.areQueryStoreOptionsEnabled
}
await this.toggleQueryCapturePolicySection(this.areQueryStoreOptionsEnabled);
}
private async toggleQueryCapturePolicySection(enable: boolean): Promise<void> {
if (!isUndefinedOrNull(this.objectInfo.queryStoreOptions.capturePolicyOptions)) {
this.executionCount.enabled
= this.staleThreshold.enabled
= this.totalCompileCPUTimeInMS.enabled
= this.totalExecutionCPUTimeInMS.enabled = enable;
}
}
//#endregion
private initializeConfigureSLOSection(): azdata.GroupContainer {
let containers: azdata.Component[] = [];
if (this.viewInfo.azureEditions?.length > 0) {

View File

@@ -328,6 +328,51 @@ export abstract class DialogBase<DialogResult> {
return this.createButtonContainer(buttonComponents)
}
protected addButtonsForTable1(table: azdata.DeclarativeTableComponent, addbutton: DialogButton, removeButton: DialogButton, editButton: DialogButton = undefined): azdata.FlexContainer {
let addButtonComponent: azdata.ButtonComponent;
let editButtonComponent: azdata.ButtonComponent;
let removeButtonComponent: azdata.ButtonComponent;
let buttonComponents: azdata.ButtonComponent[] = [];
const updateButtons = (isRemoveEnabled: boolean = undefined) => {
this.onFormFieldChange();
const tableSelectedRowsLengthCheck = table.selectedRow === 1 && table.selectedRow < table.data.length;
if (editButton !== undefined) {
editButtonComponent.enabled = tableSelectedRowsLengthCheck;
}
removeButtonComponent.enabled = !!isRemoveEnabled && tableSelectedRowsLengthCheck;
}
addButtonComponent = this.createButton(uiLoc.AddText, addbutton.buttonAriaLabel, async () => {
await addbutton.buttonHandler(addButtonComponent);
updateButtons();
});
buttonComponents.push(addButtonComponent);
if (editButton !== undefined) {
editButtonComponent = this.createButton(uiLoc.EditText, editButton.buttonAriaLabel, async () => {
await editButton.buttonHandler(editButtonComponent);
updateButtons();
}, false);
buttonComponents.push(editButtonComponent);
}
removeButtonComponent = this.createButton(uiLoc.RemoveText, removeButton.buttonAriaLabel, async () => {
await removeButton.buttonHandler(removeButtonComponent);
if (table.selectedRow === 1) {
// table.selectedRow = [table.data.length - 1];
}
updateButtons();
}, false);
buttonComponents.push(removeButtonComponent);
this.disposables.push(table.onRowSelected(() => {
const isRemoveButtonEnabled = true;
updateButtons(isRemoveButtonEnabled);
}));
return this.createButtonContainer(buttonComponents)
}
protected createDropdown(ariaLabel: string, handler: (newValue: string) => Promise<void>, values: string[], value: string | undefined, enabled: boolean = true, width: number = DefaultInputWidth, editable?: boolean, strictSelection?: boolean): azdata.DropDownComponent {
// Automatically add an empty item to the beginning of the list if the current value is not specified.
// This is needed when no meaningful default value can be provided.