Postgres Parameters Page (#13855)

* Addition: properties page with link to dashboard

* Include new page

* Initial Parameter page start

* Include new changes from merged PRs

* Including new constants

* Git errors

* Add parameter commands and help

* Reset command

* Added chart

* git fix

* Fixed string issues

* connectSqlDialog is an abstract class. Separated out Miaa and Postgress connection

* Initial start to adding connect to sql for postgres instance

* Simplified classes extending ConnectToSqlDialog, added get providerName, and function to create error message

* Miaa models provides dialog title

* Updated failed message parameters

* completionPromise.reject

* Fixed connect to MSSql

* Messy dialog showing from button

* removed this._completionPromise.reject

* Cleaning up code

* Set connectSqlDialog to be an abstract class. Separated out Miaa and Postgres.  (#13532)

* connectSqlDialog is an abstract class. Separated out Miaa and Postgress connection

* Simplified classes extending ConnectToSqlDialog, added get providerName, and function to create error message

* Miaa models provides dialog title

* Updated failed message parameters

* completionPromise.reject

* Fixed connect to MSSql

* removed this._completionPromise.reject

* Connect button clean up

* Format

* Format doc

* Fixed compile errors

* Cleaning up page

* Clean up

* clean up refresh

* Format doc

* Removed ellipse

* Cleaning up problems

* Updating localized constants

* Missing username update

* Get connection profile added to Resource model, abstract method created for calling connection dialog

* Added createConnectionProfile

* took out import

* Pulled in new changes, fixed usercancellederror

* Getting engine settings

* Git errors

* Git errors

* Git errors fix

* Fixing Css

* Freezes, Search function working, 20 parameters

* Fixed re

* Git errors

* Save and reset commands working

* Discard works, updated how engine settings refresh with model

* Updated search, add back loading for when trying to connect

* Cleaning up comments left in code

* Git error

* Corrected names of icons and constants, Fixed Miaa dialog title

* Removed using any on page, added void return types, took out commented code

* Changed gear svg, made postgres extension a loc constant, fixed formatting

* Fixed controller model name

* Put connection profile and id in resource model, changed back controller model in base class

* Fixed a comment

* Added loading component for waiting for postgres extension to be installed

* Fix parameters page to show parameters if engine settings are already loaded (#13996)

* Added progress message for installing postgres extension

* Minor styling updates

* Making sure search box and rest buttons are enabled when opening page with loaded data. Update refresh

* Git errors

* change name

* Code review updates: Combined create parameters and refresh table.

* Update sql-assessment to use latest ads-extension-telemetry npm package (#14003)

* Change configure Jupyter server steps from async to sync (#13937)

* change config steps to sync

* fix tests

* use pathexistsSync

* remove pathExistsSync call

* address PR comments

* Use a minimum cell height to prevent whitespace markdown cells from becoming invisible. (#14008)

* Fix validation errors (#14009)

* Fix validation errors

* fix compile

* update return type

* Cleanup model component wrapper event handlers (#14012)

* Clean up button component disposables (#14011)

* Clean up button component disposables

* consolidate logic

* Adding Dacpac extension telemetry and core wizard/page telemetry updates(#13859)

* Dacpac telmetry code changes

* Removed added spaces

* Generate deployScript accessibility changed back to public

* code review suggessions updates

* dacpac extension tests fixes

* Updated time and filesize methods allowing general return values

* Telemetry code updates

* Dacpac Telemetry potential data loss capture and PII error excluded

* Dacpac telemetry code updates for comments

* Wizard pages navigation telemetry event capture moved to the core

* DacpacTelemetry code updates

* Extension wizard cancel telemetry for data loss

* Dacpac telemetry pagename and small code updates

* final Dacpac telemetry code updates...

* migrated loc files (#14015)

* Took out some info bubbles and addingitems

* Update search function

* Handle special value occasions

* Undo change

* Undo change

Co-authored-by: chgagnon <chgagnon@microsoft.com>
Co-authored-by: Lucy Zhang <luczhan@microsoft.com>
Co-authored-by: Cory Rivera <corivera@microsoft.com>
Co-authored-by: Sai Avishkar Sreerama <74571829+ssreerama@users.noreply.github.com>
Co-authored-by: khoiph1 <khoiph@microsoft.com>
This commit is contained in:
nasc17
2021-01-29 13:48:35 -08:00
committed by GitHub
parent 927e435aea
commit d98ac86bd9
6 changed files with 360 additions and 285 deletions

View File

@@ -30,6 +30,8 @@ export const newSupportRequest = localize('arc.newSupportRequest', "New support
export const diagnoseAndSolveProblems = localize('arc.diagnoseAndSolveProblems', "Diagnose and solve problems");
export const supportAndTroubleshooting = localize('arc.supportAndTroubleshooting', "Support + troubleshooting");
export const resourceHealth = localize('arc.resourceHealth', "Resource health");
export const parameterName = localize('arc.parameterName', "Parameter Name");
export const value = localize('arc.value', "Value");
export const newInstance = localize('arc.createNew', "New Instance");
export const deleteText = localize('arc.delete', "Delete");
@@ -173,6 +175,8 @@ export function rangeSetting(min: string, max: string): string { return localize
export function allowedValue(value: string): string { return localize('arc.allowedValue', "Value is expected to be {0}", value); }
export function databaseCreated(name: string): string { return localize('arc.databaseCreated', "Database {0} created", name); }
export function deletingInstance(name: string): string { return localize('arc.deletingInstance', "Deleting instance '{0}'...", name); }
export function installingExtension(name: string): string { return localize('arc.installingExtension', "Installing extension '{0}'...", name); }
export function extensionInstalled(name: string): string { return localize('arc.extensionInstalled', "Extension '{0}' has been installed.", name); }
export function updatingInstance(name: string): string { return localize('arc.updatingInstance', "Updating instance '{0}'...", name); }
export function instanceDeleted(name: string): string { return localize('arc.instanceDeleted', "Instance '{0}' deleted", name); }
export function instanceUpdated(name: string): string { return localize('arc.instanceUpdated', "Instance '{0}' updated", name); }
@@ -198,6 +202,7 @@ export const pgConnectionRequired = localize('arc.pgConnectionRequired', "A conn
export const couldNotFindControllerRegistration = localize('arc.couldNotFindControllerRegistration', "Could not find controller registration.");
export function outOfRange(min: string, max: string): string { return localize('arc.outOfRange', "The number must be in range {0} - {1}", min, max); }
export function refreshFailed(error: any): string { return localize('arc.refreshFailed', "Refresh failed. {0}", getErrorMessage(error)); }
export function resetFailed(error: any): string { return localize('arc.resetFailed', "Reset failed. {0}", getErrorMessage(error)); }
export function openDashboardFailed(error: any): string { return localize('arc.openDashboardFailed', "Error opening dashboard. {0}", getErrorMessage(error)); }
export function instanceDeletionFailed(name: string, error: any): string { return localize('arc.instanceDeletionFailed', "Failed to delete instance {0}. {1}", name, getErrorMessage(error)); }
export function instanceUpdateFailed(name: string, error: any): string { return localize('arc.instanceUpdateFailed', "Failed to update instance {0}. {1}", name, getErrorMessage(error)); }
@@ -207,6 +212,7 @@ export function connectToControllerFailed(url: string, error: any): string { ret
export function connectToMSSqlFailed(serverName: string, error: any): string { return localize('arc.connectToMSSqlFailed', "Could not connect to SQL managed instance - Azure Arc Instance {0}. {1}", serverName, getErrorMessage(error)); }
export function connectToPGSqlFailed(serverName: string, error: any): string { return localize('arc.connectToPGSqlFailed', "Could not connect to PostgreSQL Hyperscale - Azure Arc Instance {0}. {1}", serverName, getErrorMessage(error)); }
export function missingExtension(extensionName: string): string { return localize('arc.missingExtension', "The {0} extension is required to view engine settings. Do you wish to install it now?", extensionName); }
export function extensionInstallationFailed(extensionName: string): string { return localize('arc.extensionInstallationFailed', "Failed to install extension {0}.", extensionName); }
export function fetchConfigFailed(name: string, error: any): string { return localize('arc.fetchConfigFailed', "An unexpected error occurred retrieving the config for '{0}'. {1}", name, getErrorMessage(error)); }
export function fetchEndpointsFailed(name: string, error: any): string { return localize('arc.fetchEndpointsFailed', "An unexpected error occurred retrieving the endpoints for '{0}'. {1}", name, getErrorMessage(error)); }
export function fetchRegistrationsFailed(name: string, error: any): string { return localize('arc.fetchRegistrationsFailed', "An unexpected error occurred retrieving the registrations for '{0}'. {1}", name, getErrorMessage(error)); }

View File

@@ -144,22 +144,34 @@ export class PostgresModel extends ResourceModel {
if (!engineSettings) {
throw new Error('Could not fetch engine settings');
}
const skippedEngineSettings: String[] = [
'archive_command', 'archive_timeout', 'log_directory', 'log_file_mode', 'log_filename', 'restore_command',
'shared_preload_libraries', 'synchronous_commit', 'ssl', 'unix_socket_permissions', 'wal_level'
];
this._engineSettings = [];
engineSettings.rows.forEach(row => {
let rowValues = row.map(c => c.displayValue);
let result: EngineSettingsModel = {
parameterName: rowValues.shift(),
value: rowValues.shift(),
description: rowValues.shift(),
min: rowValues.shift(),
max: rowValues.shift(),
options: rowValues.shift(),
type: rowValues.shift()
};
let name = rowValues.shift();
if (!skippedEngineSettings.includes(name!)) {
let result: EngineSettingsModel = {
parameterName: name,
value: rowValues.shift(),
description: rowValues.shift(),
min: rowValues.shift(),
max: rowValues.shift(),
options: rowValues.shift(),
type: rowValues.shift()
};
this._engineSettings.push(result);
this._engineSettings.push(result);
}
});
this.engineSettingsLastUpdated = new Date();
this._onEngineSettingsUpdated.fire(this._engineSettings);
}
protected createConnectionProfile(): azdata.IConnectionProfile {

View File

@@ -341,8 +341,8 @@ export class PostgresComputeAndStoragePage extends DashboardPage {
const information = this.modelView.modelBuilder.button().withProperties<azdata.ButtonProperties>({
iconPath: IconPathHelper.information,
title: loc.workerNodesInformation,
width: '12px',
height: '12px',
width: '15px',
height: '15px',
enabled: false
}).component();
@@ -434,8 +434,8 @@ export class PostgresComputeAndStoragePage extends DashboardPage {
const information = this.modelView.modelBuilder.button().withProperties<azdata.ButtonProperties>({
iconPath: IconPathHelper.information,
title: loc.postgresConfigurationInformation,
width: '12px',
height: '12px',
width: '15px',
height: '15px',
enabled: false
}).component();

View File

@@ -34,6 +34,8 @@ export class PostgresDashboard extends Dashboard {
const computeAndStoragePage = new PostgresComputeAndStoragePage(modelView, this._postgresModel);
// TODO: Removed properties page while investigating bug where refreshed values don't appear in UI
// const propertiesPage = new PostgresPropertiesPage(modelView, this._controllerModel, this._postgresModel);
// TODO: Removed parameters page while investigating bug where UI freezes dealing with large declarative table of components
// const parametersPage = new PostgresParametersPage(modelView, this._postgresModel);
const diagnoseAndSolveProblemsPage = new PostgresDiagnoseAndSolveProblemsPage(modelView, this._context, this._postgresModel);
const supportRequestPage = new PostgresSupportRequestPage(modelView, this._controllerModel, this._postgresModel);

View File

@@ -10,21 +10,28 @@ import * as loc from '../../../localizedConstants';
import { UserCancelledError } from '../../../common/api';
import { IconPathHelper, cssStyles } from '../../../constants';
import { DashboardPage } from '../../components/dashboardPage';
import { PostgresModel } from '../../../models/postgresModel';
import { EngineSettingsModel, PostgresModel } from '../../../models/postgresModel';
export type ParametersModel = {
parameterName: string,
valueContainer: azdata.FlexContainer,
description: string,
resetButton: azdata.ButtonComponent
};
export class PostgresParametersPage extends DashboardPage {
private searchBox?: azdata.InputBoxComponent;
private searchBox!: azdata.InputBoxComponent;
private parametersTable!: azdata.DeclarativeTableComponent;
private parameterContainer?: azdata.DivContainer;
private _parametersTableLoading!: azdata.LoadingComponent;
private discardButton?: azdata.ButtonComponent;
private saveButton?: azdata.ButtonComponent;
private resetButton?: azdata.ButtonComponent;
private discardButton!: azdata.ButtonComponent;
private saveButton!: azdata.ButtonComponent;
private resetAllButton!: azdata.ButtonComponent;
private connectToServerButton?: azdata.ButtonComponent;
private engineSettings = `'`;
private engineSettingUpdates?: Map<string, string>;
private _parameters: ParametersModel[] = [];
private parameterUpdates: Map<string, string> = new Map();
private readonly _azdataApi: azdataExt.IExtension;
@@ -35,11 +42,10 @@ export class PostgresParametersPage extends DashboardPage {
this.initializeConnectButton();
this.initializeSearchBox();
this.engineSettingUpdates = new Map();
this.disposables.push(
this._postgresModel.onConfigUpdated(() => this.eventuallyRunOnInitialized(() => this.handleServiceUpdated())),
this._postgresModel.onEngineSettingsUpdated(() => this.eventuallyRunOnInitialized(() => this.handleEngineSettingsUpdated())));
this._postgresModel.onEngineSettingsUpdated(() => this.eventuallyRunOnInitialized(() => this.refreshParametersTable()))
);
}
protected get title(): string {
@@ -64,20 +70,15 @@ export class PostgresParametersPage extends DashboardPage {
CSSStyles: { ...cssStyles.title }
}).component());
const info = this.modelView.modelBuilder.text().withProps({
content.addItem(this.modelView.modelBuilder.text().withProps({
value: loc.nodeParametersDescription,
CSSStyles: { ...cssStyles.text, 'margin-block-start': '0px', 'margin-block-end': '0px' }
}).component();
}).component());
const link = this.modelView.modelBuilder.hyperlink().withProps({
content.addItem(this.modelView.modelBuilder.hyperlink().withProps({
label: loc.learnAboutNodeParameters,
url: 'https://docs.microsoft.com/azure/azure-arc/data/configure-server-parameters-postgresql-hyperscale',
}).component();
const infoAndLink = this.modelView.modelBuilder.flexContainer().withLayout({ flexWrap: 'wrap' }).component();
infoAndLink.addItem(info, { CSSStyles: { 'margin-right': '5px' } });
infoAndLink.addItem(link);
content.addItem(infoAndLink, { CSSStyles: { 'margin-bottom': '20px' } });
url: 'https://docs.microsoft.com/azure/azure-arc/data/configure-server-parameters-postgresql-hyperscale'
}).component(), { CSSStyles: { 'margin-bottom': '20px' } });
content.addItem(this.searchBox!, { CSSStyles: { ...cssStyles.text, 'margin-block-start': '0px', 'margin-block-end': '0px', 'margin-bottom': '20px' } });
@@ -85,43 +86,33 @@ export class PostgresParametersPage extends DashboardPage {
width: '100%',
columns: [
{
displayName: 'Parameter Name',
valueType: azdata.DeclarativeDataType.component,
displayName: loc.parameterName,
valueType: azdata.DeclarativeDataType.string,
isReadOnly: true,
width: '20%',
headerCssStyles: cssStyles.tableHeader,
rowCssStyles: cssStyles.tableRow
},
{
displayName: 'Value',
displayName: loc.value,
valueType: azdata.DeclarativeDataType.component,
isReadOnly: false,
width: '20%',
headerCssStyles: cssStyles.tableHeader,
rowCssStyles: {
...cssStyles.tableRow,
'overflow': 'hidden',
'text-overflow': 'ellipsis',
'white-space': 'nowrap',
'max-width': '0'
}
rowCssStyles: cssStyles.tableRow
},
{
displayName: 'Description',
valueType: azdata.DeclarativeDataType.component,
displayName: loc.description,
valueType: azdata.DeclarativeDataType.string,
isReadOnly: true,
width: '50%',
headerCssStyles: cssStyles.tableHeader,
rowCssStyles: {
...cssStyles.tableRow,
'overflow': 'hidden',
'text-overflow': 'ellipsis',
'white-space': 'nowrap',
'max-width': '0'
...cssStyles.tableRow
}
},
{
displayName: 'Reset To Default',
displayName: loc.resetToDefault,
valueType: azdata.DeclarativeDataType.component,
isReadOnly: false,
width: '10%',
@@ -129,11 +120,7 @@ export class PostgresParametersPage extends DashboardPage {
rowCssStyles: cssStyles.tableRow
}
],
data: [
this.parameterComponents('TEST NAME', 'string'),
this.parameterComponents('TEST NAME 2', 'real'),
this.createParametersTable()]
data: []
}).component();
this._parametersTableLoading = this.modelView.modelBuilder.loadingComponent().component();
@@ -156,8 +143,10 @@ export class PostgresParametersPage extends DashboardPage {
enabled: false
}).component();
let engineSettings: string[] = [];
this.disposables.push(
this.saveButton.onDidClick(async () => {
this.saveButton!.enabled = false;
try {
await vscode.window.withProgress(
{
@@ -166,20 +155,19 @@ export class PostgresParametersPage extends DashboardPage {
cancellable: false
},
async (_progress, _token): Promise<void> => {
//Edit multiple
// azdata arc postgres server edit -n <server group name> -e '<parameter name>=<parameter value>, <parameter name>=<parameter value>,...'
try {
this.engineSettingUpdates!.forEach((value: string) => {
this.engineSettings += value + ', ';
this.parameterUpdates!.forEach((value, key) => {
engineSettings.push(`${key}="${value}"`);
});
const session = await this._postgresModel.controllerModel.acquireAzdataSession();
try {
await this._azdataApi.azdata.arc.postgres.server.edit(
this._postgresModel.info.name, { engineSettings: this.engineSettings + `'` });
this._postgresModel.info.name,
{ engineSettings: engineSettings.toString() },
this._postgresModel.engineVersion);
} finally {
session.dispose();
}
} catch (err) {
// If an error occurs while editing the instance then re-enable the save button since
// the edit wasn't successfully applied
@@ -192,14 +180,16 @@ export class PostgresParametersPage extends DashboardPage {
vscode.window.showInformationMessage(loc.instanceUpdated(this._postgresModel.info.name));
this.engineSettings = `'`;
this.engineSettingUpdates!.clear();
engineSettings = [];
this.parameterUpdates!.clear();
this.discardButton!.enabled = false;
this.resetAllButton!.enabled = true;
} catch (error) {
vscode.window.showErrorMessage(loc.instanceUpdateFailed(this._postgresModel.info.name, error));
}
}));
})
);
// Discard
this.discardButton = this.modelView.modelBuilder.button().withProps({
@@ -212,27 +202,27 @@ export class PostgresParametersPage extends DashboardPage {
this.discardButton.onDidClick(async () => {
this.discardButton!.enabled = false;
try {
// TODO
// this.parametersTable.data = [];
this.engineSettingUpdates!.clear();
this.refreshParametersTable();
} catch (error) {
vscode.window.showErrorMessage(loc.pageDiscardFailed(error));
} finally {
this.saveButton!.enabled = false;
}
}));
})
);
// Reset
this.resetButton = this.modelView.modelBuilder.button().withProps({
// Reset all
this.resetAllButton = this.modelView.modelBuilder.button().withProps({
label: loc.resetAllToDefault,
iconPath: IconPathHelper.reset,
enabled: true
enabled: false
}).component();
this.disposables.push(
this.resetButton.onDidClick(async () => {
this.resetButton!.enabled = false;
this.resetAllButton.onDidClick(async () => {
this.resetAllButton!.enabled = false;
this.discardButton!.enabled = false;
this.saveButton!.enabled = false;
try {
await vscode.window.withProgress(
{
@@ -247,11 +237,17 @@ export class PostgresParametersPage extends DashboardPage {
try {
session = await this._postgresModel.controllerModel.acquireAzdataSession();
await this._azdataApi.azdata.arc.postgres.server.edit(
this._postgresModel.info.name, { engineSettings: `'' -re` });
this._postgresModel.info.name,
{ engineSettings: `''`, replaceEngineSettings: true },
this._postgresModel.engineVersion);
} catch (err) {
// If an error occurs while resetting the instance then re-enable the reset button since
// the edit wasn't successfully applied
this.resetButton!.enabled = true;
if (this.parameterUpdates.size > 0) {
this.discardButton!.enabled = true;
this.saveButton!.enabled = true;
}
this.resetAllButton!.enabled = true;
throw err;
} finally {
session?.dispose();
@@ -260,20 +256,24 @@ export class PostgresParametersPage extends DashboardPage {
}
);
vscode.window.showInformationMessage(loc.instanceUpdated(this._postgresModel.info.name));
this.parameterUpdates!.clear();
} catch (error) {
vscode.window.showErrorMessage(loc.refreshFailed(error));
vscode.window.showErrorMessage(loc.resetFailed(error));
}
}));
})
);
return this.modelView.modelBuilder.toolbarContainer().withToolbarItems([
{ component: this.saveButton },
{ component: this.discardButton },
{ component: this.resetButton }
{ component: this.resetAllButton }
]).component();
}
private initializeConnectButton() {
private initializeConnectButton(): void {
this.connectToServerButton = this.modelView.modelBuilder.button().withProps({
label: loc.connectToServer,
enabled: false,
@@ -290,211 +290,36 @@ export class PostgresParametersPage extends DashboardPage {
return;
}
await vscode.commands.executeCommand('workbench.extensions.installExtension', loc.postgresExtension);
await vscode.window.withProgress(
{
location: vscode.ProgressLocation.Notification,
title: loc.installingExtension(loc.postgresExtension),
cancellable: false
},
async (_progress, _token): Promise<void> => {
try {
await vscode.commands.executeCommand('workbench.extensions.installExtension', loc.postgresExtension);
} catch (err) {
vscode.window.showErrorMessage(loc.extensionInstallationFailed(loc.postgresExtension));
this.connectToServerButton!.enabled = true;
throw err;
}
}
);
vscode.window.showInformationMessage(loc.extensionInstalled(loc.postgresExtension));
}
await this._postgresModel.getEngineSettings().catch(err => {
// If an error occurs show a message so the user knows something failed but still
// fire the event so callers can know to update (e.g. so dashboards don't show the
// loading icon forever)
if (err instanceof UserCancelledError) {
vscode.window.showWarningMessage(loc.pgConnectionRequired);
} else {
vscode.window.showErrorMessage(loc.fetchEngineSettingsFailed(this._postgresModel.info.name, err));
}
this._postgresModel.engineSettingsLastUpdated = new Date();
this._postgresModel._onEngineSettingsUpdated.fire(this._postgresModel._engineSettings);
this.connectToServerButton!.enabled = true;
throw err;
});
this._parametersTableLoading!.loading = true;
await this.callGetEngineSettings().finally(() => this._parametersTableLoading!.loading = false);
this.searchBox!.enabled = true;
this.resetAllButton!.enabled = true;
this.parameterContainer!.clearItems();
this.parameterContainer!.addItem(this.parametersTable);
}));
}
private initializeSearchBox() {
this.searchBox = this.modelView.modelBuilder.inputBox().withProps({
readOnly: false,
placeHolder: loc.searchToFilter
}).component();
this.disposables.push(
this.searchBox.onTextChanged(() => {
this.filterParameters();
})
);
}
private filterParameters() {
//TODO
}
private createParametersTable(): any[] {
/*Define server settings that shouldn't be modified. we block archive_*, restore_*, and synchronous_commit to prevent the user
from messing up our backups. (we rely on synchronous_commit to ensure WAL changes are written immediately.)
we block log_* to protect our logging. we block wal_level because Citus needs a particular wal_Level to rebalance shards
TODO: Review list of blacklisted parameters. wal_level should only be blacklisted if sharding is enabled
To not be modified
"archive_command", "archive_timeout", "log_directory", "log_file_mode", "log_filename", "restore_command",
"shared_preload_libraries", "synchronous_commit", "ssl", "unix_socket_permissions", "wal_level" */
// For ev in this._postgresModel._engineSettings
// create row
// return rows
this.parameterComponents('engineSetting', '');
return [];
}
private parameterComponents(name: string, type: string): any[] {
let data = [];
// Set parameter name
const parameterName = this.modelView.modelBuilder.text().withProps({
value: name,
CSSStyles: { ...cssStyles.text, 'margin-block-start': '0px', 'margin-block-end': '0px' }
}).component();
data.push(parameterName);
// Container to hold input component and information bubble
const valueContainer = this.modelView.modelBuilder.flexContainer().withLayout({ alignItems: 'center' }).component();
// Information bubble title to be set depening on type of input
let information = this.modelView.modelBuilder.button().withProps({
iconPath: IconPathHelper.information,
width: '12px',
height: '12px',
enabled: false
}).component();
if (type === 'enum') {
// If type is enum, component should be drop down menu
let valueBox = this.modelView.modelBuilder.dropDown().withProps({
values: [], //TODO,
value: '', //TODO
CSSStyles: { ...cssStyles.text, 'margin-block-start': '0px', 'margin-block-end': '0px' }
}).component();
valueContainer.addItem(valueBox, { CSSStyles: { 'margin-right': '0px', 'margin-bottom': '15px' } });
this.disposables.push(
valueBox.onValueChanged(() => {
this.engineSettingUpdates!.set(name, String(valueBox.value));
})
);
information.updateProperty('title', loc.allowedValue('enums')); //TODO
} else if (type === 'bool') {
// If type is bool, component should be checkbox to turn on or off
let valueBox = this.modelView.modelBuilder.checkBox().withProps({
label: loc.on,
checked: true, //TODO
CSSStyles: { ...cssStyles.text, 'margin-block-start': '0px', 'margin-block-end': '0px' }
}).component();
valueContainer.addItem(valueBox, { CSSStyles: { 'margin-right': '0px', 'margin-bottom': '15px' } });
this.disposables.push(
valueBox.onChanged(() => {
if (valueBox.checked) {
this.engineSettingUpdates!.set(name, loc.on);
} else {
this.engineSettingUpdates!.set(name, loc.off);
}
})
);
information.updateProperty('title', loc.allowedValue('on,off')); //TODO
} else if (type === 'string') {
// If type is string, component should be text inputbox
// How to add validation: .withValidation(component => component.value?.search('[0-9]') == -1)
let valueBox = this.modelView.modelBuilder.inputBox().withProps({
readOnly: false,
value: '', //TODO
CSSStyles: { 'margin-bottom': '15px', 'min-width': '50px', 'max-width': '200px' }
}).component();
valueContainer.addItem(valueBox, { CSSStyles: { 'margin-right': '0px', 'margin-bottom': '15px' } });
this.disposables.push(
valueBox.onTextChanged(() => {
this.engineSettingUpdates!.set(name, valueBox.value!);
})
);
information.updateProperty('title', loc.allowedValue(loc.allowedValue('[A-Za-z._]+'))); //TODO
} else {
// If type is real or interger, component should be inputbox set to inputType of number. Max and min values also set.
let valueBox = this.modelView.modelBuilder.inputBox().withProps({
readOnly: false,
min: 0, //TODO
max: 10000,
validationErrorMessage: loc.outOfRange('min', 'max'), //TODO
inputType: 'number',
value: '0', //TODO
CSSStyles: { 'margin-bottom': '15px', 'min-width': '50px', 'max-width': '200px' }
}).component();
valueContainer.addItem(valueBox, { CSSStyles: { 'margin-right': '0px', 'margin-bottom': '15px' } });
this.disposables.push(
valueBox.onTextChanged(() => {
this.engineSettingUpdates!.set(name, valueBox.value!);
})
);
information.updateProperty('title', loc.allowedValue(loc.rangeSetting('min', 'max'))); //TODO
}
valueContainer.addItem(information, { CSSStyles: { 'margin-left': '5px', 'margin-bottom': '15px' } });
data.push(valueContainer);
const parameterDescription = this.modelView.modelBuilder.text().withProps({
value: 'TEST DESCRIPTION HERE ...............................ytgbyugvtyvctyrcvytjv ycrtctyv tyfty ftyuvuyvuy', // TODO
CSSStyles: { ...cssStyles.text, 'margin-block-start': '0px', 'margin-block-end': '0px' }
}).component();
data.push(parameterDescription);
// Can reset individual component
const resetParameter = this.modelView.modelBuilder.button().withProps({
iconPath: IconPathHelper.reset,
title: loc.resetToDefault,
width: '20px',
height: '20px',
enabled: true
}).component();
data.push(resetParameter);
// azdata arc postgres server edit -n postgres01 -e shared_buffers=
this.disposables.push(
resetParameter.onDidClick(async () => {
try {
await vscode.window.withProgress(
{
location: vscode.ProgressLocation.Notification,
title: loc.updatingInstance(this._postgresModel.info.name),
cancellable: false
},
async (_progress, _token) => {
const session = await this._postgresModel.controllerModel.acquireAzdataSession();
try {
this._azdataApi.azdata.arc.postgres.server.edit(
this._postgresModel.info.name, { engineSettings: name + '=' });
} finally {
session.dispose();
}
}
);
vscode.window.showInformationMessage(loc.instanceUpdated(this._postgresModel.info.name));
} catch (error) {
vscode.window.showErrorMessage(loc.instanceUpdateFailed(this._postgresModel.info.name, error));
}
}));
return data;
}
private selectComponent() {
private selectComponent(): void {
if (!this._postgresModel.engineSettingsLastUpdated) {
this.parameterContainer!.addItem(this.modelView.modelBuilder.text().withProps({
value: loc.connectToPostgresDescription,
@@ -503,19 +328,249 @@ export class PostgresParametersPage extends DashboardPage {
this.parameterContainer!.addItem(this.connectToServerButton!, { CSSStyles: { 'max-width': '125px' } });
this.parameterContainer!.addItem(this._parametersTableLoading!);
} else {
this.searchBox!.enabled = true;
this.resetAllButton!.enabled = true;
this.parameterContainer!.addItem(this.parametersTable!);
this.refreshParametersTable();
}
}
private handleEngineSettingsUpdated(): void {
//TODO
private async callGetEngineSettings(): Promise<void> {
try {
await this._postgresModel.getEngineSettings();
} catch (error) {
if (error instanceof UserCancelledError) {
vscode.window.showWarningMessage(loc.pgConnectionRequired);
} else {
vscode.window.showErrorMessage(loc.fetchEngineSettingsFailed(this._postgresModel.info.name, error));
}
this.connectToServerButton!.enabled = true;
throw error;
}
}
private handleServiceUpdated() {
// TODO
if (this._postgresModel.configLastUpdated) {
private initializeSearchBox(): void {
this.searchBox = this.modelView.modelBuilder.inputBox().withProps({
readOnly: false,
enabled: false,
placeHolder: loc.searchToFilter
}).component();
this.disposables.push(
this.searchBox.onTextChanged(() => {
if (!this.searchBox!.value) {
this.parametersTable.data = this._parameters.map(p => [p.parameterName, p.valueContainer, p.description, p.resetButton]);
} else {
this.filterParameters(this.searchBox!.value);
}
})
);
}
private filterParameters(search: string): void {
this.parametersTable.data = this._parameters
.filter(p => p.parameterName?.search(search) !== -1 || p.description?.search(search) !== -1)
.map(p => [p.parameterName, p.valueContainer, p.description, p.resetButton]);
}
private handleOnTextChanged(component: azdata.InputBoxComponent, currentValue: string | undefined): boolean {
if (!component.valid) {
// If invalid value retun false and enable discard button
this.discardButton!.enabled = true;
return false;
} else if (component.value === currentValue) {
return false;
} else {
/* If a valid value has been entered into the input box, enable save and discard buttons
so that user could choose to either edit instance or clear all inputs
return true */
this.saveButton!.enabled = true;
this.discardButton!.enabled = true;
return true;
}
}
private createParameterComponents(engineSetting: EngineSettingsModel): ParametersModel {
// Container to hold input component and information bubble
const valueContainer = this.modelView.modelBuilder.flexContainer().withLayout({ alignItems: 'center' }).component();
if (engineSetting.type === 'enum') {
// If type is enum, component should be drop down menu
let options = engineSetting.options?.slice(1, -1).split(',');
let values: string[] = [];
options!.forEach(option => {
values.push(option.slice(option.indexOf('"') + 1, -1));
});
let valueBox = this.modelView.modelBuilder.dropDown().withProps({
values: values,
value: engineSetting.value,
width: '150px',
CSSStyles: { 'height': '40px' }
}).component();
valueContainer.addItem(valueBox);
this.disposables.push(
valueBox.onValueChanged(() => {
if (engineSetting.value !== String(valueBox.value)) {
this.parameterUpdates!.set(engineSetting.parameterName!, String(valueBox.value));
this.saveButton!.enabled = true;
this.discardButton!.enabled = true;
} else if (this.parameterUpdates!.has(engineSetting.parameterName!)) {
this.parameterUpdates!.delete(engineSetting.parameterName!);
}
})
);
} else if (engineSetting.type === 'bool') {
// If type is bool, component should be checkbox to turn on or off
let valueBox = this.modelView.modelBuilder.checkBox().withProps({
label: loc.on,
CSSStyles: { ...cssStyles.text, 'margin-block-start': '0px', 'margin-block-end': '0px' }
}).component();
valueContainer.addItem(valueBox);
if (engineSetting.value === 'on') {
valueBox.checked = true;
} else {
valueBox.checked = false;
}
this.disposables.push(
valueBox.onChanged(() => {
if (valueBox.checked && engineSetting.value === 'off') {
this.parameterUpdates!.set(engineSetting.parameterName!, loc.on);
this.saveButton!.enabled = true;
this.discardButton!.enabled = true;
} else if (!valueBox.checked && engineSetting.value === 'on') {
this.parameterUpdates!.set(engineSetting.parameterName!, loc.off);
this.saveButton!.enabled = true;
this.discardButton!.enabled = true;
} else if (this.parameterUpdates!.has(engineSetting.parameterName!)) {
this.parameterUpdates!.delete(engineSetting.parameterName!);
}
})
);
} else if (engineSetting.type === 'string') {
// If type is string, component should be text inputbox
let valueBox = this.modelView.modelBuilder.inputBox().withProps({
required: true,
readOnly: false,
value: engineSetting.value,
width: '150px'
}).component();
valueContainer.addItem(valueBox);
this.disposables.push(
valueBox.onTextChanged(() => {
if ((this.handleOnTextChanged(valueBox, engineSetting.value))) {
this.parameterUpdates!.set(engineSetting.parameterName!, `"${valueBox.value!}"`);
} else if (this.parameterUpdates!.has(engineSetting.parameterName!)) {
this.parameterUpdates!.delete(engineSetting.parameterName!);
}
})
);
} else {
// Child components to be added to container
let components: Array<azdata.Component> = [];
// If type is real or interger, component should be inputbox set to inputType of number. Max and min values also set.
let valueBox = this.modelView.modelBuilder.inputBox().withProps({
required: true,
readOnly: false,
min: parseInt(engineSetting.min!),
max: parseInt(engineSetting.max!),
validationErrorMessage: loc.outOfRange(engineSetting.min!, engineSetting.max!),
inputType: 'number',
value: engineSetting.value,
width: '150px'
}).component();
components.push(valueBox);
this.disposables.push(
valueBox.onTextChanged(() => {
if ((this.handleOnTextChanged(valueBox, engineSetting.value))) {
this.parameterUpdates!.set(engineSetting.parameterName!, valueBox.value!);
} else if (this.parameterUpdates!.has(engineSetting.parameterName!)) {
this.parameterUpdates!.delete(engineSetting.parameterName!);
}
})
);
// Information bubble title to show allowed values
let information = this.modelView.modelBuilder.button().withProps({
iconPath: IconPathHelper.information,
width: '15px',
height: '15px',
enabled: false
}).component();
information.updateProperty('title', loc.allowedValue(loc.rangeSetting(engineSetting.min!, engineSetting.max!)));
components.push(information);
valueContainer.addItems(components);
}
// Can reset individual parameter
const resetParameterButton = this.modelView.modelBuilder.button().withProps({
iconPath: IconPathHelper.reset,
title: loc.resetToDefault,
width: '20px',
height: '20px',
enabled: true
}).component();
this.disposables.push(
resetParameterButton.onDidClick(async () => {
try {
await vscode.window.withProgress(
{
location: vscode.ProgressLocation.Notification,
title: loc.updatingInstance(this._postgresModel.info.name),
cancellable: false
},
async (_progress, _token): Promise<void> => {
const session = await this._postgresModel.controllerModel.acquireAzdataSession();
try {
await this._azdataApi.azdata.arc.postgres.server.edit(
this._postgresModel.info.name,
{ engineSettings: engineSetting.parameterName + '=' },
this._postgresModel.engineVersion);
} finally {
session.dispose();
}
await this._postgresModel.refresh();
}
);
vscode.window.showInformationMessage(loc.instanceUpdated(this._postgresModel.info.name));
} catch (error) {
vscode.window.showErrorMessage(loc.instanceUpdateFailed(this._postgresModel.info.name, error));
}
})
);
let parameter: ParametersModel = {
parameterName: engineSetting.parameterName!,
valueContainer: valueContainer,
description: engineSetting.description!,
resetButton: resetParameterButton
};
return parameter;
}
private refreshParametersTable(): void {
this._parameters = this._postgresModel._engineSettings.map(engineSetting => this.createParameterComponents(engineSetting));
this.parametersTable.data = this._parameters.map(p => [p.parameterName, p.valueContainer, p.description, p.resetButton]);
}
private async handleServiceUpdated(): Promise<void> {
if (this._postgresModel.configLastUpdated && !this._postgresModel.engineSettingsLastUpdated) {
this.connectToServerButton!.enabled = true;
this._parametersTableLoading!.loading = false;
} else if (this._postgresModel.engineSettingsLastUpdated) {
await this.callGetEngineSettings();
}
}
}

View File

@@ -1097,4 +1097,4 @@
</Item>
</Item>
</Item>
</LCX>
</LCX>