From 478a2bf64b591f4aef4227480e66dccaea763949 Mon Sep 17 00:00:00 2001 From: nasc17 <69922333+nasc17@users.noreply.github.com> Date: Thu, 24 Jun 2021 09:20:22 -0700 Subject: [PATCH] View extensions of Postgres server group (#15887) * Init * Fix view * Change strings * Change strings edit/view * Only view extensions * Added loading text * Add table label, remove try catch block, prfixes * String add * Take out podstatus model, add correct link --- extensions/arc/images/extensions.svg | 18 ++ extensions/arc/src/constants.ts | 5 + extensions/arc/src/localizedConstants.ts | 9 + .../dashboards/postgres/postgresDashboard.ts | 3 + .../postgres/postgresExtensionsPage.ts | 158 ++++++++++++++++++ 5 files changed, 193 insertions(+) create mode 100644 extensions/arc/images/extensions.svg create mode 100644 extensions/arc/src/ui/dashboards/postgres/postgresExtensionsPage.ts diff --git a/extensions/arc/images/extensions.svg b/extensions/arc/images/extensions.svg new file mode 100644 index 0000000000..97022571d2 --- /dev/null +++ b/extensions/arc/images/extensions.svg @@ -0,0 +1,18 @@ + + + + + + + + + + MsPortalFx.base.images-21 + + + + + + + + \ No newline at end of file diff --git a/extensions/arc/src/constants.ts b/extensions/arc/src/constants.ts index 516ceab943..38622a4961 100644 --- a/extensions/arc/src/constants.ts +++ b/extensions/arc/src/constants.ts @@ -28,6 +28,7 @@ export class IconPathHelper { public static collapseUp: IconPath; public static collapseDown: IconPath; public static postgres: IconPath; + public static extensions: IconPath; public static computeStorage: IconPath; public static connection: IconPath; public static backup: IconPath; @@ -74,6 +75,10 @@ export class IconPathHelper { light: IconPathHelper.context.asAbsolutePath('images/postgres.svg'), dark: IconPathHelper.context.asAbsolutePath('images/postgres.svg') }; + IconPathHelper.extensions = { + light: IconPathHelper.context.asAbsolutePath('images/extensions.svg'), + dark: IconPathHelper.context.asAbsolutePath('images/extensions.svg') + }; IconPathHelper.computeStorage = { light: context.asAbsolutePath('images/billing.svg'), dark: context.asAbsolutePath('images/billing.svg') diff --git a/extensions/arc/src/localizedConstants.ts b/extensions/arc/src/localizedConstants.ts index 195a8ffc33..fa349294a4 100644 --- a/extensions/arc/src/localizedConstants.ts +++ b/extensions/arc/src/localizedConstants.ts @@ -18,6 +18,7 @@ export const miaaType = localize('arc.miaaType', "SQL managed instance - Azure A export const overview = localize('arc.overview', "Overview"); export const connectionStrings = localize('arc.connectionStrings', "Connection Strings"); +export const preLoadedExtensions = localize('arc.preloaded Extensions', "Preloaded Extensions"); export const networking = localize('arc.networking', "Networking"); export const properties = localize('arc.properties', "Properties"); export const settings = localize('arc.settings', "Settings"); @@ -39,6 +40,7 @@ export const deleteText = localize('arc.delete', "Delete"); export const saveText = localize('arc.save', "Save"); export const discardText = localize('arc.discard', "Discard"); export const resetPassword = localize('arc.resetPassword', "Reset Password"); +export const addExtensions = localize('arc.addExtensions', "Add extensions"); export const openInAzurePortal = localize('arc.openInAzurePortal', "Open in Azure Portal"); export const resourceGroup = localize('arc.resourceGroup', "Resource Group"); export const region = localize('arc.region', "Region"); @@ -52,6 +54,13 @@ export const type = localize('arc.type', "Type"); export const status = localize('arc.status', "Status"); export const miaaAdmin = localize('arc.miaaAdmin', "Managed instance admin"); export const controllerEndpoint = localize('arc.controllerEndpoint', "Controller endpoint"); +export const extensionName = localize('arc.extensionName', "Extension name"); +export const extensionsDescription = localize('arc.extensionsDescription', "PostgreSQL provides the ability to extend the functionality of your database by using extensions. Extensions allow for bundling multiple related SQL objects together in a single package that can be loaded or removed from your database with a single command. After being loaded in the database, extensions can function like built-in features."); +export const extensionsFunction = localize('arc.extensionsFunction', "Some extensions must be loaded into PostgreSQL at startup time before they can be used. These preloaded extensions can be viewed below."); +export const extensionsLearnMore = localize('arc.extensionsLearnMore', "Learn more about PostgreSQL extensions."); +export const extensionsTableLoading = localize('arc.extensionsTableLoading', "Table of preloaded extensions are loading."); +export const extensionsTableLabel = localize('arc.extensionsTableLabel', "Table of preloaded extensions."); +export const extensionsTableLoadingComplete = localize('arc.extensionsTableLoadingComplete', "Preloaded extensions can now be viewed."); export const dataController = localize('arc.dataController', "Data controller"); export const kibanaDashboard = localize('arc.kibanaDashboard', "Kibana Dashboard"); export const grafanaDashboard = localize('arc.grafanaDashboard', "Grafana Dashboard"); diff --git a/extensions/arc/src/ui/dashboards/postgres/postgresDashboard.ts b/extensions/arc/src/ui/dashboards/postgres/postgresDashboard.ts index 9d40c3da61..722e44e7c0 100644 --- a/extensions/arc/src/ui/dashboards/postgres/postgresDashboard.ts +++ b/extensions/arc/src/ui/dashboards/postgres/postgresDashboard.ts @@ -18,6 +18,7 @@ import { PostgresWorkerNodeParametersPage } from './postgresWorkerNodeParameters import { PostgresPropertiesPage } from './postgresPropertiesPage'; import { PostgresResourceHealthPage } from './postgresResourceHealthPage'; import { PostgresCoordinatorNodeParametersPage } from './postgresCoordinatorNodeParametersPage'; +import { PostgresExtensionsPage } from './postgresExtensionsPage'; export class PostgresDashboard extends Dashboard { constructor(private _context: vscode.ExtensionContext, private _controllerModel: ControllerModel, private _postgresModel: PostgresModel) { @@ -34,6 +35,7 @@ export class PostgresDashboard extends Dashboard { protected async registerTabs(modelView: azdata.ModelView): Promise<(azdata.DashboardTab | azdata.DashboardTabGroup)[]> { const overviewPage = new PostgresOverviewPage(modelView, this.dashboard, this._controllerModel, this._postgresModel); + const extensionsPage = new PostgresExtensionsPage(modelView, this.dashboard, this._postgresModel); const connectionStringsPage = new PostgresConnectionStringsPage(modelView, this.dashboard, this._postgresModel); const computeAndStoragePage = new PostgresComputeAndStoragePage(modelView, this.dashboard, this._postgresModel); const propertiesPage = new PostgresPropertiesPage(modelView, this.dashboard, this._controllerModel, this._postgresModel); @@ -49,6 +51,7 @@ export class PostgresDashboard extends Dashboard { title: loc.settings, tabs: [ propertiesPage.tab, + extensionsPage.tab, connectionStringsPage.tab, computeAndStoragePage.tab, coordinatorNodeParametersPage.tab, diff --git a/extensions/arc/src/ui/dashboards/postgres/postgresExtensionsPage.ts b/extensions/arc/src/ui/dashboards/postgres/postgresExtensionsPage.ts new file mode 100644 index 0000000000..082aeba2cd --- /dev/null +++ b/extensions/arc/src/ui/dashboards/postgres/postgresExtensionsPage.ts @@ -0,0 +1,158 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as vscode from 'vscode'; +import * as azdata from 'azdata'; +import * as azdataExt from 'azdata-ext'; +import * as loc from '../../../localizedConstants'; +import { IconPathHelper, cssStyles } from '../../../constants'; +import { DashboardPage } from '../../components/dashboardPage'; +import { PostgresModel } from '../../../models/postgresModel'; + +export class PostgresExtensionsPage extends DashboardPage { + + private extensions: { name: string; }[] = []; + private extensionsTable!: azdata.DeclarativeTableComponent; + private extensionsLoading!: azdata.LoadingComponent; + + private readonly _azdataApi: azdataExt.IExtension; + + constructor(modelView: azdata.ModelView, dashboard: azdata.window.ModelViewDashboard, private _postgresModel: PostgresModel) { + super(modelView, dashboard); + this._azdataApi = vscode.extensions.getExtension(azdataExt.extension.name)?.exports; + + this.disposables.push( + this._postgresModel.onConfigUpdated(() => this.eventuallyRunOnInitialized(() => this.handleConfigUpdated()))); + } + + protected get title(): string { + return loc.preLoadedExtensions; + } + + protected get id(): string { + return 'postgres-extensions'; + } + + protected get icon(): { dark: string; light: string; } { + return IconPathHelper.extensions; + } + + protected get container(): azdata.Component { + const root = this.modelView.modelBuilder.divContainer().component(); + const content = this.modelView.modelBuilder.divContainer().component(); + root.addItem(content, { CSSStyles: { 'margin': '10px 20px 0px 20px' } }); + + content.addItem(this.modelView.modelBuilder.text().withProps({ + value: this.title, + CSSStyles: { ...cssStyles.title } + }).component()); + + content.addItem(this.modelView.modelBuilder.text().withProps({ + value: loc.extensionsDescription, + CSSStyles: { ...cssStyles.text, 'margin-block-start': '0px', 'margin-block-end': '0px' } + }).component()); + + const info = this.modelView.modelBuilder.text().withProperties({ + value: loc.extensionsFunction, + CSSStyles: { ...cssStyles.text, 'margin-block-start': '0px', 'margin-block-end': '0px' } + }).component(); + + const link = this.modelView.modelBuilder.hyperlink().withProperties({ + label: loc.extensionsLearnMore, + url: 'https://docs.microsoft.com/azure/azure-arc/data/using-extensions-in-postgresql-hyperscale-server-group', + }).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': '15px', 'margin-top': '25px' } }); + + this.extensionsTable = this.modelView.modelBuilder.declarativeTable().withProperties({ + ariaLabel: loc.extensionsTableLabel, + width: '100%', + columns: [ + { + displayName: loc.extensionName, + valueType: azdata.DeclarativeDataType.string, + isReadOnly: true, + width: '100%', + headerCssStyles: cssStyles.tableHeader, + rowCssStyles: cssStyles.tableRow + } + ], + data: [] + }).component(); + + this.extensionsLoading = this.modelView.modelBuilder.loadingComponent() + .withItem(this.extensionsTable) + .withProperties({ + loading: !this._postgresModel.configLastUpdated, + loadingText: loc.extensionsTableLoading, + loadingCompletedText: loc.extensionsTableLoadingComplete + }).component(); + + content.addItem(this.extensionsLoading, { CSSStyles: cssStyles.text }); + + this.initialized = true; + return root; + } + + protected get toolbarContainer(): azdata.ToolbarContainer { + // Add extensions + const addExtensionsButton = this.modelView.modelBuilder.button().withProperties({ + label: loc.addExtensions, + iconPath: IconPathHelper.add + }).component(); + + this.disposables.push( + addExtensionsButton.onDidClick(async () => { + addExtensionsButton.enabled = false; + try { + await vscode.window.withProgress( + { + location: vscode.ProgressLocation.Notification, + title: loc.updatingInstance(this._postgresModel.info.name), + cancellable: false + }, + async (_progress, _token): Promise => { + await this._azdataApi.azdata.arc.postgres.server.edit( + this._postgresModel.info.name, + { + extensions: '' + }, + this._postgresModel.controllerModel.azdataAdditionalEnvVars); + + try { + await this._postgresModel.refresh(); + } catch (error) { + vscode.window.showErrorMessage(loc.refreshFailed(error)); + } + } + ); + + vscode.window.showInformationMessage(loc.instanceUpdated(this._postgresModel.info.name)); + + } catch (error) { + vscode.window.showErrorMessage(loc.instanceUpdateFailed(this._postgresModel.info.name, error)); + } finally { + addExtensionsButton.enabled = true; + } + })); + + return this.modelView.modelBuilder.toolbarContainer().component(); + } + + private refreshExtensionsTable(): void { + if (this._postgresModel.config) { + this.extensions = this._postgresModel.config?.spec.engine.extensions; + this.extensionsTable.data = this.extensions.map(e => [e.name]); + } + } + + private handleConfigUpdated(): void { + this.extensionsLoading.loading = false; + this.refreshExtensionsTable(); + } +}