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 @@
+
\ 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();
+ }
+}