From c86f301e0eab498701d80f0556af793b4756496c Mon Sep 17 00:00:00 2001 From: Charles Gagnon Date: Wed, 17 Jun 2020 07:50:10 -0700 Subject: [PATCH] More arc controller dashboard updates (#10949) --- extensions/arc/images/miaa.svg | 21 ++++ extensions/arc/src/common/utils.ts | 27 ++++- extensions/arc/src/constants.ts | 25 ++++ extensions/arc/src/localizedConstants.ts | 3 + extensions/arc/src/models/controllerModel.ts | 3 +- extensions/arc/src/models/postgresModel.ts | 2 +- extensions/arc/src/test/common/utils.test.ts | 3 +- .../controllerDashboardOverviewPage.ts | 109 ++++++++++++++---- .../miaa/miaaConnectionStringsPage.ts | 3 +- .../miaa/miaaDashboardOverviewPage.ts | 4 +- .../postgres/postgresOverviewPage.ts | 3 +- .../postgres/postgresPropertiesPage.ts | 3 +- .../postgres/postgresSupportRequestPage.ts | 3 +- 13 files changed, 169 insertions(+), 40 deletions(-) create mode 100644 extensions/arc/images/miaa.svg diff --git a/extensions/arc/images/miaa.svg b/extensions/arc/images/miaa.svg new file mode 100644 index 0000000000..789ed2f565 --- /dev/null +++ b/extensions/arc/images/miaa.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/extensions/arc/src/common/utils.ts b/extensions/arc/src/common/utils.ts index 4230c2f17b..eb09c1ec05 100644 --- a/extensions/arc/src/common/utils.ts +++ b/extensions/arc/src/common/utils.ts @@ -6,12 +6,8 @@ import * as vscode from 'vscode'; import * as azurecore from '../../../azurecore/src/azurecore'; import * as loc from '../localizedConstants'; +import { IconPathHelper, IconPath, ResourceType, Connectionmode } from '../constants'; -export enum ResourceType { - dataControllers = 'dataControllers', - postgresInstances = 'postgresInstances', - sqlManagedInstances = 'sqlManagedInstances' -} /** * Converts the resource type name into the localized Display Name for that type. * @param resourceType The resource type name to convert @@ -49,3 +45,24 @@ export async function getAzurecoreApi(): Promise { } return azurecoreApi; } + +export function getResourceTypeIcon(resourceType: string): IconPath | undefined { + switch (resourceType) { + case ResourceType.sqlManagedInstances: + return IconPathHelper.miaa; + case ResourceType.postgresInstances: + return IconPathHelper.postgres; + } + return undefined; +} + +export function getConnectionModeDisplayText(connectionMode: string | undefined): string { + connectionMode = connectionMode ?? ''; + switch (connectionMode) { + case Connectionmode.connected: + return loc.connected; + case Connectionmode.disconnected: + return loc.disconnected; + } + return connectionMode; +} diff --git a/extensions/arc/src/constants.ts b/extensions/arc/src/constants.ts index 6969f55af1..f20c9d4b1d 100644 --- a/extensions/arc/src/constants.ts +++ b/extensions/arc/src/constants.ts @@ -30,6 +30,7 @@ export class IconPathHelper { public static refresh: IconPath; public static support: IconPath; public static wrench: IconPath; + public static miaa: IconPath; public static setExtensionContext(context: vscode.ExtensionContext) { IconPathHelper.context = context; @@ -101,12 +102,36 @@ export class IconPathHelper { light: context.asAbsolutePath('images/wrench.svg'), dark: context.asAbsolutePath('images/wrench.svg') }; + IconPathHelper.miaa = { + light: context.asAbsolutePath('images/miaa.svg'), + dark: context.asAbsolutePath('images/miaa.svg'), + }; } } +export const enum ResourceType { + dataControllers = 'dataControllers', + postgresInstances = 'postgresInstances', + sqlManagedInstances = 'sqlManagedInstances' +} + +export const enum Endpoints { + mgmtproxy = 'mgmtproxy', + logsui = 'logsui', + metricsui = 'metricsui', + controller = 'controller' +} + +export const enum Connectionmode { + connected = 'connected', + disconnected = 'disconnected' +} + export namespace cssStyles { export const text = { 'user-select': 'text', 'cursor': 'text' }; export const title = { ...text, 'font-weight': 'bold', 'font-size': '14px' }; export const tableHeader = { ...text, 'text-align': 'left', 'border': 'none' }; export const tableRow = { ...text, 'border-top': 'solid 1px #ccc', 'border-bottom': 'solid 1px #ccc', 'border-left': 'none', 'border-right': 'none' }; } + +export const iconSize = '20px'; diff --git a/extensions/arc/src/localizedConstants.ts b/extensions/arc/src/localizedConstants.ts index 9d976ee140..98d59b1dad 100644 --- a/extensions/arc/src/localizedConstants.ts +++ b/extensions/arc/src/localizedConstants.ts @@ -41,6 +41,7 @@ export const name = localize('arc.name', "Name"); 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 dataController = localize('arc.dataController', "Data controller"); export const kibanaDashboard = localize('arc.kibanaDashboard', "Kibana Dashboard"); export const grafanaDashboard = localize('arc.grafanaDashboard', "Grafana Dashboard"); @@ -60,6 +61,8 @@ export const refresh = localize('arc.refresh', "Refresh"); export const troubleshoot = localize('arc.troubleshoot', "Troubleshoot"); export const clickTheNewSupportRequestButton = localize('arc.clickTheNewSupportRequestButton', "Click the new support request button to file a support request in the Azure Portal."); export const running = localize('arc.running', "Running"); +export const connected = localize('arc.connected', "Connected"); +export const disconnected = localize('arc.disconnected', "Disconnected"); // Postgres constants export const coordinatorEndpoint = localize('arc.coordinatorEndpoint', "Coordinator endpoint"); diff --git a/extensions/arc/src/models/controllerModel.ts b/extensions/arc/src/models/controllerModel.ts index a5854489cf..8c851d7207 100644 --- a/extensions/arc/src/models/controllerModel.ts +++ b/extensions/arc/src/models/controllerModel.ts @@ -6,7 +6,8 @@ import * as vscode from 'vscode'; import { Authentication } from '../controller/auth'; import { EndpointsRouterApi, EndpointModel, RegistrationRouterApi, RegistrationResponse, TokenRouterApi, SqlInstanceRouterApi } from '../controller/generated/v1/api'; -import { ResourceType, parseEndpoint } from '../common/utils'; +import { parseEndpoint } from '../common/utils'; +import { ResourceType } from '../constants'; export interface Registration extends RegistrationResponse { externalIp?: string; diff --git a/extensions/arc/src/models/postgresModel.ts b/extensions/arc/src/models/postgresModel.ts index b18de9dd88..539470a789 100644 --- a/extensions/arc/src/models/postgresModel.ts +++ b/extensions/arc/src/models/postgresModel.ts @@ -108,7 +108,7 @@ export class PostgresModel { /** Creates a SQL database in the service */ public async createDatabase(db: DuskyObjectModelsDatabase): Promise { - return await (await this._databaseRouter.createDuskyDatabase(this.namespace, this.name, db)).body; + return (await this._databaseRouter.createDuskyDatabase(this.namespace, this.name, db)).body; } /** diff --git a/extensions/arc/src/test/common/utils.test.ts b/extensions/arc/src/test/common/utils.test.ts index e9889730cb..272af9aadc 100644 --- a/extensions/arc/src/test/common/utils.test.ts +++ b/extensions/arc/src/test/common/utils.test.ts @@ -5,9 +5,10 @@ import * as should from 'should'; import 'mocha'; -import { resourceTypeToDisplayName, ResourceType, parseEndpoint } from '../../common/utils'; +import { resourceTypeToDisplayName, parseEndpoint } from '../../common/utils'; import * as loc from '../../localizedConstants'; +import { ResourceType } from '../../constants'; describe('resourceTypeToDisplayName Method Tests', () => { it('Display Name should be correct for valid ResourceType', function (): void { diff --git a/extensions/arc/src/ui/dashboards/controller/controllerDashboardOverviewPage.ts b/extensions/arc/src/ui/dashboards/controller/controllerDashboardOverviewPage.ts index 22588c5113..f558676490 100644 --- a/extensions/arc/src/ui/dashboards/controller/controllerDashboardOverviewPage.ts +++ b/extensions/arc/src/ui/dashboards/controller/controllerDashboardOverviewPage.ts @@ -7,16 +7,33 @@ import * as azdata from 'azdata'; import * as vscode from 'vscode'; import * as loc from '../../../localizedConstants'; import { DashboardPage } from '../../components/dashboardPage'; -import { IconPathHelper, cssStyles } from '../../../constants'; +import { IconPathHelper, cssStyles, iconSize, ResourceType, Endpoints } from '../../../constants'; import { ControllerModel } from '../../../models/controllerModel'; -import { resourceTypeToDisplayName, ResourceType, getAzurecoreApi } from '../../../common/utils'; +import { resourceTypeToDisplayName, getAzurecoreApi, getResourceTypeIcon, getConnectionModeDisplayText } from '../../../common/utils'; import { RegistrationResponse } from '../../../controller/generated/v1/model/registrationResponse'; +import { EndpointModel } from '../../../controller/generated/v1/api'; export class ControllerDashboardOverviewPage extends DashboardPage { + private _propertiesLoadingComponent!: azdata.LoadingComponent; + private _arcResourcesLoadingComponent!: azdata.LoadingComponent; + private _arcResourcesTable!: azdata.DeclarativeTableComponent; private _propertiesContainer!: azdata.PropertiesContainerComponent; + private controllerProperties = { + instanceName: '-', + resourceGroupName: '-', + location: '-', + subscriptionId: '-', + controllerEndpoint: '-', + connectionMode: '-', + instanceNamespace: '-', + }; + + private _endpointsRetrieved = false; + private _registrationsRetrieved = false; + constructor(modelView: azdata.ModelView, private _controllerModel: ControllerModel) { super(modelView); this._controllerModel.onRegistrationsUpdated((_: RegistrationResponse[]) => { @@ -24,6 +41,11 @@ export class ControllerDashboardOverviewPage extends DashboardPage { this.handleRegistrationsUpdated().catch(e => console.log(e)); }); }); + this._controllerModel.onEndpointsUpdated(endpoints => { + this.eventuallyRunOnInitialized(() => { + this.handleEndpointsUpdated(endpoints); + }); + }); this.refresh().catch(e => { console.log(e); }); @@ -55,8 +77,9 @@ export class ControllerDashboardOverviewPage extends DashboardPage { rootContainer.addItem(contentContainer, { CSSStyles: { 'margin': '10px 20px 0px 20px' } }); this._propertiesContainer = this.modelView.modelBuilder.propertiesContainer().component(); + this._propertiesLoadingComponent = this.modelView.modelBuilder.loadingComponent().withItem(this._propertiesContainer).component(); - contentContainer.addItem(this._propertiesContainer); + contentContainer.addItem(this._propertiesLoadingComponent); const arcResourcesTitle = this.modelView.modelBuilder.text() .withProperties({ value: loc.arcResources }) @@ -69,6 +92,14 @@ export class ControllerDashboardOverviewPage extends DashboardPage { this._arcResourcesTable = this.modelView.modelBuilder.declarativeTable().withProperties({ data: [], columns: [ + { + displayName: '', + valueType: azdata.DeclarativeDataType.component, + width: iconSize, + isReadOnly: true, + headerCssStyles: cssStyles.tableHeader, + rowCssStyles: cssStyles.tableRow + }, { displayName: loc.name, valueType: azdata.DeclarativeDataType.string, @@ -100,7 +131,9 @@ export class ControllerDashboardOverviewPage extends DashboardPage { .withItems([this._arcResourcesTable]) .component(); - contentContainer.addItem(arcResourcesTableContainer); + this._arcResourcesLoadingComponent = this.modelView.modelBuilder.loadingComponent().withItem(arcResourcesTableContainer).component(); + + contentContainer.addItem(this._arcResourcesLoadingComponent); this.initialized = true; return rootContainer; } @@ -112,10 +145,9 @@ export class ControllerDashboardOverviewPage extends DashboardPage { iconPath: IconPathHelper.add }).component(); - const deleteButton = this.modelView.modelBuilder.button().withProperties({ - label: loc.deleteText, - iconPath: IconPathHelper.delete - }).component(); + createNewButton.onDidClick(async () => { + await vscode.commands.executeCommand('azdata.resource.deploy'); + }); const openInAzurePortalButton = this.modelView.modelBuilder.button().withProperties({ label: loc.openInAzurePortal, @@ -134,8 +166,7 @@ export class ControllerDashboardOverviewPage extends DashboardPage { return this.modelView.modelBuilder.toolbarContainer().withToolbarItems( [ - { component: createNewButton }, - { component: deleteButton, toolbarSeparatorAfter: true }, + { component: createNewButton, toolbarSeparatorAfter: true }, { component: openInAzurePortalButton } ] ).component(); @@ -143,45 +174,79 @@ export class ControllerDashboardOverviewPage extends DashboardPage { private async handleRegistrationsUpdated(): Promise { const reg = this._controllerModel.controllerRegistration; - if (reg) { + this.controllerProperties.instanceName = reg?.instanceName || this.controllerProperties.instanceName; + this.controllerProperties.resourceGroupName = reg?.resourceGroupName || this.controllerProperties.resourceGroupName; + this.controllerProperties.location = (await getAzurecoreApi()).getRegionDisplayName(reg?.location) || this.controllerProperties.location; + this.controllerProperties.subscriptionId = reg?.subscriptionId || this.controllerProperties.subscriptionId; + this.controllerProperties.connectionMode = getConnectionModeDisplayText(reg?.connectionMode) || this.controllerProperties.connectionMode; + this.controllerProperties.instanceNamespace = reg?.instanceNamespace || this.controllerProperties.instanceNamespace; + this._registrationsRetrieved = true; + this.refreshDisplayedProperties(); + + this._arcResourcesTable.data = this._controllerModel.registrations + .filter(r => r.instanceType !== ResourceType.dataControllers) + .map(r => { + const iconPath = getResourceTypeIcon(r.instanceType ?? ''); + const imageComponent = this.modelView.modelBuilder.image() + .withProperties({ + width: iconSize, + height: iconSize, + iconPath: iconPath, + iconHeight: iconSize, + iconWidth: iconSize + }) + .component(); + return [imageComponent, r.instanceName, resourceTypeToDisplayName(r.instanceType), r.vCores]; + }); + this._arcResourcesLoadingComponent.loading = false; + } + + private handleEndpointsUpdated(endpoints: EndpointModel[]): void { + const controllerEndpoint = endpoints.find(endpoint => endpoint.name === Endpoints.controller); + this.controllerProperties.controllerEndpoint = controllerEndpoint?.endpoint || this.controllerProperties.controllerEndpoint; + this._endpointsRetrieved = true; + this.refreshDisplayedProperties(); + } + + private refreshDisplayedProperties(): void { + // Only update once we've retrieved all the necessary properties + if (this._endpointsRetrieved && this._registrationsRetrieved) { this._propertiesContainer.propertyItems = [ { displayName: loc.name, - value: reg.instanceName || '-' + value: this.controllerProperties.instanceName }, { displayName: loc.resourceGroup, - value: reg.resourceGroupName || '-' + value: this.controllerProperties.resourceGroupName }, { displayName: loc.region, - value: (await getAzurecoreApi()).getRegionDisplayName(reg.location) || '-' + value: this.controllerProperties.location }, { displayName: loc.subscriptionId, - value: reg.subscriptionId || '-' + value: this.controllerProperties.subscriptionId }, { displayName: loc.type, value: loc.dataControllersType }, { - displayName: loc.coordinatorEndpoint, - value: '-' + displayName: loc.controllerEndpoint, + value: this.controllerProperties.controllerEndpoint }, { displayName: loc.connectionMode, - value: reg.connectionMode || '-' + value: this.controllerProperties.connectionMode }, { displayName: loc.namespace, - value: reg.instanceNamespace || '-' + value: this.controllerProperties.instanceNamespace } ]; + this._propertiesLoadingComponent.loading = false; } - this._arcResourcesTable.data = this._controllerModel.registrations - .filter(r => r.instanceType !== ResourceType.dataControllers) - .map(r => [r.instanceName, resourceTypeToDisplayName(r.instanceType), r.vCores]); } } diff --git a/extensions/arc/src/ui/dashboards/miaa/miaaConnectionStringsPage.ts b/extensions/arc/src/ui/dashboards/miaa/miaaConnectionStringsPage.ts index e7de54d42c..38b0db9e3b 100644 --- a/extensions/arc/src/ui/dashboards/miaa/miaaConnectionStringsPage.ts +++ b/extensions/arc/src/ui/dashboards/miaa/miaaConnectionStringsPage.ts @@ -5,11 +5,10 @@ import * as azdata from 'azdata'; import * as loc from '../../../localizedConstants'; -import { IconPathHelper, cssStyles } from '../../../constants'; +import { IconPathHelper, cssStyles, ResourceType } from '../../../constants'; import { KeyValueContainer, KeyValue, InputKeyValue, MultilineInputKeyValue } from '../../components/keyValueContainer'; import { DashboardPage } from '../../components/dashboardPage'; import { ControllerModel, Registration } from '../../../models/controllerModel'; -import { ResourceType } from '../../../common/utils'; import { MiaaModel } from '../../../models/miaaModel'; export class MiaaConnectionStringsPage extends DashboardPage { diff --git a/extensions/arc/src/ui/dashboards/miaa/miaaDashboardOverviewPage.ts b/extensions/arc/src/ui/dashboards/miaa/miaaDashboardOverviewPage.ts index b6995af9d3..e14984da9c 100644 --- a/extensions/arc/src/ui/dashboards/miaa/miaaDashboardOverviewPage.ts +++ b/extensions/arc/src/ui/dashboards/miaa/miaaDashboardOverviewPage.ts @@ -6,9 +6,9 @@ import * as azdata from 'azdata'; import * as loc from '../../../localizedConstants'; import { DashboardPage } from '../../components/dashboardPage'; -import { IconPathHelper, cssStyles } from '../../../constants'; +import { IconPathHelper, cssStyles, ResourceType } from '../../../constants'; import { ControllerModel, Registration } from '../../../models/controllerModel'; -import { ResourceType, getAzurecoreApi } from '../../../common/utils'; +import { getAzurecoreApi } from '../../../common/utils'; import { MiaaModel, DatabaseModel } from '../../../models/miaaModel'; import { HybridSqlNsNameGetResponse } from '../../../controller/generated/v1/model/hybridSqlNsNameGetResponse'; import { EndpointModel } from '../../../controller/generated/v1/api'; diff --git a/extensions/arc/src/ui/dashboards/postgres/postgresOverviewPage.ts b/extensions/arc/src/ui/dashboards/postgres/postgresOverviewPage.ts index be587e1fc5..dc583df589 100644 --- a/extensions/arc/src/ui/dashboards/postgres/postgresOverviewPage.ts +++ b/extensions/arc/src/ui/dashboards/postgres/postgresOverviewPage.ts @@ -6,12 +6,11 @@ import * as vscode from 'vscode'; import * as azdata from 'azdata'; import * as loc from '../../../localizedConstants'; -import { IconPathHelper, cssStyles } from '../../../constants'; +import { IconPathHelper, cssStyles, ResourceType } from '../../../constants'; import { DuskyObjectModelsDatabase, DuskyObjectModelsDatabaseServiceArcPayload, V1Pod } from '../../../controller/generated/dusky/api'; import { DashboardPage } from '../../components/dashboardPage'; import { ControllerModel } from '../../../models/controllerModel'; import { PostgresModel, PodRole } from '../../../models/postgresModel'; -import { ResourceType } from '../../../common/utils'; export class PostgresOverviewPage extends DashboardPage { private propertiesLoading?: azdata.LoadingComponent; diff --git a/extensions/arc/src/ui/dashboards/postgres/postgresPropertiesPage.ts b/extensions/arc/src/ui/dashboards/postgres/postgresPropertiesPage.ts index 60a79c041b..5271209bc8 100644 --- a/extensions/arc/src/ui/dashboards/postgres/postgresPropertiesPage.ts +++ b/extensions/arc/src/ui/dashboards/postgres/postgresPropertiesPage.ts @@ -6,12 +6,11 @@ import * as vscode from 'vscode'; import * as azdata from 'azdata'; import * as loc from '../../../localizedConstants'; -import { IconPathHelper, cssStyles } from '../../../constants'; +import { IconPathHelper, cssStyles, ResourceType } from '../../../constants'; import { KeyValueContainer, InputKeyValue, LinkKeyValue, TextKeyValue } from '../../components/keyValueContainer'; import { DashboardPage } from '../../components/dashboardPage'; import { ControllerModel } from '../../../models/controllerModel'; import { PostgresModel } from '../../../models/postgresModel'; -import { ResourceType } from '../../../common/utils'; export class PostgresPropertiesPage extends DashboardPage { private keyValueContainer?: KeyValueContainer; diff --git a/extensions/arc/src/ui/dashboards/postgres/postgresSupportRequestPage.ts b/extensions/arc/src/ui/dashboards/postgres/postgresSupportRequestPage.ts index 7a32b56070..e7b8e76270 100644 --- a/extensions/arc/src/ui/dashboards/postgres/postgresSupportRequestPage.ts +++ b/extensions/arc/src/ui/dashboards/postgres/postgresSupportRequestPage.ts @@ -6,11 +6,10 @@ import * as vscode from 'vscode'; import * as azdata from 'azdata'; import * as loc from '../../../localizedConstants'; -import { IconPathHelper, cssStyles } from '../../../constants'; +import { IconPathHelper, cssStyles, ResourceType } from '../../../constants'; import { DashboardPage } from '../../components/dashboardPage'; import { ControllerModel } from '../../../models/controllerModel'; import { PostgresModel } from '../../../models/postgresModel'; -import { ResourceType } from '../../../common/utils'; export class PostgresSupportRequestPage extends DashboardPage { constructor(protected modelView: azdata.ModelView, private _controllerModel: ControllerModel, private _postgresModel: PostgresModel) {