From 48eeb8419d5a842ee43fe1ec739633f6ce732d02 Mon Sep 17 00:00:00 2001 From: fnbalaji <75445927+fnbalaji@users.noreply.github.com> Date: Mon, 17 May 2021 22:15:26 -0700 Subject: [PATCH] Portal changes for DedicatedGateway (#742) * src/SelfServe/Example/SelfServeExample.rp.ts. Portal changes for DedicatedGateway 1. Change Sqlx endpoints to SqlDedicatedGateway endpoint 2. Remove D32s from the SKU list 3. Add telemetry 4. Remove SKU details field per discussion 5. Support dynamic instance scaling. * format files to ensure format check and lint tests pass * Lint fixes * Lint fixes * Added metrics blade link * updated conditions for warning banner * fixed lint error * Incorporate metrics link and CR feedback * Lint fixes * CR feedback and fix links * CR feedback and fix links * Link fix Co-authored-by: Srinath Narayanan --- src/Localization/en/SqlX.json | 9 ++- src/SelfServe/SqlX/SqlX.rp.ts | 83 ++++++++++++++------- src/SelfServe/SqlX/SqlX.tsx | 135 +++++++++++++++++++++------------- 3 files changed, 147 insertions(+), 80 deletions(-) diff --git a/src/Localization/en/SqlX.json b/src/Localization/en/SqlX.json index 3c1d81dac..e2128a40e 100644 --- a/src/Localization/en/SqlX.json +++ b/src/Localization/en/SqlX.json @@ -37,16 +37,19 @@ "CannotSave": "Cannot save the changes to the Dedicated gateway resource at the moment.", "DedicatedGatewayEndpoint": "Dedicated gatewayEndpoint", "NoValue": "", - "SKUDetails": "SKU Details:", "CosmosD4Details": "General Purpose Cosmos Compute with 4 vCPUs, 16 GB Memory", "CosmosD8Details": "General Purpose Cosmos Compute with 8 vCPUs, 32 GB Memory", "CosmosD16Details": "General Purpose Cosmos Compute with 16 vCPUs, 64 GB Memory", - "CosmosD32Details": "General Purpose Cosmos Compute with 32 vCPUs, 128 GB Memory", "Cost": "Cost", "CostText": "Hourly cost of the dedicated gateway resource depends on the SKU selection, number of instances per region, and number of regions.", "ConnectionString": "Connection String", "ConnectionStringText": "To use the dedicated gateway, use the connection string shown in ", - "KeysBlade": "the keys blade", + "KeysBlade": "the keys blade.", + "MetricsString": "Metrics", + "MetricsText": "Monitor the \"DedicatedGatewayMaximumCpuUsage\" and \"DedicatedGatewayAverageMemoryUsage\" in ", + "MetricsBlade": "the metrics blade.", + "ResizingDecisionText": "To understand if the dedicated gateway is the right size, ", + "ResizingDecisionLink": "learn more about dedicated gateway sizing.", "WarningBannerOnUpdate": "Adding or modifying dedicated gateway instances may affect your bill.", "WarningBannerOnDelete": "After deprovisioning the dedicated gateway, you must update any applications using the old dedicated gateway connection string." } \ No newline at end of file diff --git a/src/SelfServe/SqlX/SqlX.rp.ts b/src/SelfServe/SqlX/SqlX.rp.ts index b5a097fca..2bc7a70fa 100644 --- a/src/SelfServe/SqlX/SqlX.rp.ts +++ b/src/SelfServe/SqlX/SqlX.rp.ts @@ -1,10 +1,12 @@ -import { RefreshResult } from "../SelfServeTypes"; +import { configContext } from "../../ConfigContext"; import { userContext } from "../../UserContext"; import { armRequestWithoutPolling } from "../../Utils/arm/request"; -import { configContext } from "../../ConfigContext"; +import { selfServeTraceFailure, selfServeTraceStart, selfServeTraceSuccess } from "../SelfServeTelemetryProcessor"; +import { RefreshResult } from "../SelfServeTypes"; +import SqlX from "./SqlX"; import { SqlxServiceResource, UpdateDedicatedGatewayRequestParameters } from "./SqlxTypes"; -const apiVersion = "2020-06-01-preview"; +const apiVersion = "2021-04-01-preview"; export enum ResourceStatus { Running = "Running", @@ -21,7 +23,7 @@ export interface DedicatedGatewayResponse { } export const getPath = (subscriptionId: string, resourceGroup: string, name: string): string => { - return `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroup}/providers/Microsoft.DocumentDB/databaseAccounts/${name}/services/sqlx`; + return `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroup}/providers/Microsoft.DocumentDB/databaseAccounts/${name}/services/SqlDedicatedGateway`; }; export const updateDedicatedGatewayResource = async (sku: string, instances: number): Promise => { @@ -30,39 +32,66 @@ export const updateDedicatedGatewayResource = async (sku: string, instances: num properties: { instanceSize: sku, instanceCount: instances, - serviceType: "Sqlx", + serviceType: "SqlDedicatedGateway", }, }; - const armRequestResult = await armRequestWithoutPolling({ - host: configContext.ARM_ENDPOINT, - path, - method: "PUT", - apiVersion, - body, - }); - return armRequestResult.operationStatusUrl; + const telemetryData = { ...body, httpMethod: "PUT", selfServeClassName: SqlX.name }; + const updateTimeStamp = selfServeTraceStart(telemetryData); + let armRequestResult; + try { + armRequestResult = await armRequestWithoutPolling({ + host: configContext.ARM_ENDPOINT, + path, + method: "PUT", + apiVersion, + body, + }); + selfServeTraceSuccess(telemetryData, updateTimeStamp); + } catch (e) { + const failureTelemetry = { ...body, e, selfServeClassName: SqlX.name }; + selfServeTraceFailure(failureTelemetry, updateTimeStamp); + } + return armRequestResult?.operationStatusUrl; }; export const deleteDedicatedGatewayResource = async (): Promise => { const path = getPath(userContext.subscriptionId, userContext.resourceGroup, userContext.databaseAccount.name); - const armRequestResult = await armRequestWithoutPolling({ - host: configContext.ARM_ENDPOINT, - path, - method: "DELETE", - apiVersion, - }); - return armRequestResult.operationStatusUrl; + const telemetryData = { httpMethod: "DELETE", selfServeClassName: SqlX.name }; + const deleteTimeStamp = selfServeTraceStart(telemetryData); + let armRequestResult; + try { + armRequestResult = await armRequestWithoutPolling({ + host: configContext.ARM_ENDPOINT, + path, + method: "DELETE", + apiVersion, + }); + selfServeTraceSuccess(telemetryData, deleteTimeStamp); + } catch (e) { + const failureTelemetry = { e, selfServeClassName: SqlX.name }; + selfServeTraceFailure(failureTelemetry, deleteTimeStamp); + } + return armRequestResult?.operationStatusUrl; }; export const getDedicatedGatewayResource = async (): Promise => { const path = getPath(userContext.subscriptionId, userContext.resourceGroup, userContext.databaseAccount.name); - const armRequestResult = await armRequestWithoutPolling({ - host: configContext.ARM_ENDPOINT, - path, - method: "GET", - apiVersion, - }); - return armRequestResult.result; + const telemetryData = { httpMethod: "GET", selfServeClassName: SqlX.name }; + const getResourceTimeStamp = selfServeTraceStart(telemetryData); + let armRequestResult; + try { + armRequestResult = await armRequestWithoutPolling({ + host: configContext.ARM_ENDPOINT, + path, + method: "GET", + apiVersion, + }); + selfServeTraceSuccess(telemetryData, getResourceTimeStamp); + } catch (e) { + const failureTelemetry = { e, selfServeClassName: SqlX.name }; + selfServeTraceFailure(failureTelemetry, getResourceTimeStamp); + } + return armRequestResult?.result; }; export const getCurrentProvisioningState = async (): Promise => { diff --git a/src/SelfServe/SqlX/SqlX.tsx b/src/SelfServe/SqlX/SqlX.tsx index a532c5244..44356969a 100644 --- a/src/SelfServe/SqlX/SqlX.tsx +++ b/src/SelfServe/SqlX/SqlX.tsx @@ -23,7 +23,7 @@ const costPerHourValue: Description = { textTKey: "CostText", type: DescriptionType.Text, link: { - href: "https://azure.microsoft.com/en-us/pricing/details/cosmos-db/", + href: "https://aka.ms/cosmos-db-dedicated-gateway-pricing", textTKey: "DedicatedGatewayPricing", }, }; @@ -37,43 +37,56 @@ const connectionStringValue: Description = { }, }; +const metricsStringValue: Description = { + textTKey: "MetricsText", + type: DescriptionType.Text, + link: { + href: generateBladeLink(BladeType.Metrics), + textTKey: "MetricsBlade", + }, +}; + +const resizingDecisionValue: Description = { + textTKey: "ResizingDecisionText", + type: DescriptionType.Text, + link: { + href: "https://aka.ms/cosmos-db-dedicated-gateway-size", + textTKey: "ResizingDecisionLink", + }, +}; + const CosmosD4s = "Cosmos.D4s"; const CosmosD8s = "Cosmos.D8s"; const CosmosD16s = "Cosmos.D16s"; -const CosmosD32s = "Cosmos.D32s"; - -const getSKUDetails = (sku: string): string => { - if (sku === CosmosD4s) { - return "CosmosD4Details"; - } else if (sku === CosmosD8s) { - return "CosmosD8Details"; - } else if (sku === CosmosD16s) { - return "CosmosD16Details"; - } else if (sku === CosmosD32s) { - return "CosmosD32Details"; - } - return "Not Supported Yet"; -}; const onSKUChange = (newValue: InputType, currentValues: Map): Map => { currentValues.set("sku", { value: newValue }); - currentValues.set("skuDetails", { - value: { textTKey: getSKUDetails(`${newValue.toString()}`), type: DescriptionType.Text } as Description, - }); currentValues.set("costPerHour", { value: costPerHourValue }); return currentValues; }; const onNumberOfInstancesChange = ( newValue: InputType, - currentValues: Map + currentValues: Map, + baselineValues: Map ): Map => { currentValues.set("instances", { value: newValue }); - currentValues.set("warningBanner", { - value: { textTKey: "WarningBannerOnUpdate" } as Description, - hidden: false, - }); - + const dedicatedGatewayOriginallyEnabled = baselineValues.get("enableDedicatedGateway")?.value as boolean; + const baselineInstances = baselineValues.get("instances")?.value as number; + if (!dedicatedGatewayOriginallyEnabled || baselineInstances !== newValue) { + currentValues.set("warningBanner", { + value: { + textTKey: "WarningBannerOnUpdate", + link: { + href: "https://aka.ms/cosmos-db-dedicated-gateway-overview", + textTKey: "DedicatedGatewayPricing", + }, + } as Description, + hidden: false, + }); + } else { + currentValues.set("warningBanner", undefined); + } return currentValues; }; @@ -87,10 +100,11 @@ const onEnableDedicatedGatewayChange = ( if (dedicatedGatewayOriginallyEnabled === newValue) { currentValues.set("sku", baselineValues.get("sku")); currentValues.set("instances", baselineValues.get("instances")); - currentValues.set("skuDetails", baselineValues.get("skuDetails")); currentValues.set("costPerHour", baselineValues.get("costPerHour")); currentValues.set("warningBanner", baselineValues.get("warningBanner")); currentValues.set("connectionString", baselineValues.get("connectionString")); + currentValues.set("metricsString", baselineValues.get("metricsString")); + currentValues.set("resizingDecisionString", baselineValues.get("resizingDecisionString")); return currentValues; } @@ -100,7 +114,7 @@ const onEnableDedicatedGatewayChange = ( value: { textTKey: "WarningBannerOnUpdate", link: { - href: "https://docs.microsoft.com/en-us/azure/cosmos-db/introduction", + href: "https://aka.ms/cosmos-db-dedicated-gateway-pricing", textTKey: "DedicatedGatewayPricing", }, } as Description, @@ -111,7 +125,7 @@ const onEnableDedicatedGatewayChange = ( value: { textTKey: "WarningBannerOnDelete", link: { - href: "https://docs.microsoft.com/en-us/azure/cosmos-db/introduction", + href: "https://aka.ms/cosmos-db-dedicated-gateway-overview", textTKey: "DeprovisioningDetailsText", }, } as Description, @@ -132,18 +146,22 @@ const onEnableDedicatedGatewayChange = ( disabled: dedicatedGatewayOriginallyEnabled, }); - currentValues.set("skuDetails", { - value: { textTKey: getSKUDetails(`${currentValues.get("sku").value}`), type: DescriptionType.Text } as Description, - hidden: hideAttributes, - disabled: dedicatedGatewayOriginallyEnabled, - }); - currentValues.set("costPerHour", { value: costPerHourValue, hidden: hideAttributes }); currentValues.set("connectionString", { value: connectionStringValue, hidden: !newValue || !dedicatedGatewayOriginallyEnabled, }); + currentValues.set("metricsString", { + value: metricsStringValue, + hidden: !newValue || !dedicatedGatewayOriginallyEnabled, + }); + + currentValues.set("resizingDecisionString", { + value: resizingDecisionValue, + hidden: !newValue || !dedicatedGatewayOriginallyEnabled, + }); + return currentValues; }; @@ -151,7 +169,6 @@ const skuDropDownItems: ChoiceItem[] = [ { labelTKey: "CosmosD4s", key: CosmosD4s }, { labelTKey: "CosmosD8s", key: CosmosD8s }, { labelTKey: "CosmosD16s", key: CosmosD16s }, - { labelTKey: "CosmosD32s", key: CosmosD32s }, ]; const getSkus = async (): Promise => { @@ -184,7 +201,6 @@ export default class SqlX extends SelfServeBaseClass { currentValues.set("warningBanner", undefined); - //TODO : Add try catch for each RP call and return relevant notifications if (dedicatedGatewayOriginallyEnabled) { if (!dedicatedGatewayCurrentlyEnabled) { const operationStatusUrl = await deleteDedicatedGatewayResource(); @@ -206,9 +222,11 @@ export default class SqlX extends SelfServeBaseClass { }, }; } else { - // Check for scaling up/down/in/out + const sku = currentValues.get("sku")?.value as string; + const instances = currentValues.get("instances").value as number; + const operationStatusUrl = await updateDedicatedGatewayResource(sku, instances); return { - operationStatusUrl: undefined, + operationStatusUrl: operationStatusUrl, portalNotification: { initialize: { titleTKey: "UpdateInitializeTitle", @@ -255,24 +273,37 @@ export default class SqlX extends SelfServeBaseClass { defaults.set("enableDedicatedGateway", { value: false }); defaults.set("sku", { value: CosmosD4s, hidden: true }); defaults.set("instances", { value: await getInstancesMin(), hidden: true }); - defaults.set("skuDetails", undefined); defaults.set("costPerHour", undefined); defaults.set("connectionString", undefined); + defaults.set("metricsString", { + value: undefined, + hidden: true, + }); + defaults.set("resizingDecisionString", { + value: undefined, + hidden: true, + }); const response = await getCurrentProvisioningState(); if (response.status && response.status !== "Deleting") { defaults.set("enableDedicatedGateway", { value: true }); defaults.set("sku", { value: response.sku, disabled: true }); - defaults.set("instances", { value: response.instances, disabled: true }); + defaults.set("instances", { value: response.instances, disabled: false }); defaults.set("costPerHour", { value: costPerHourValue }); - defaults.set("skuDetails", { - value: { textTKey: getSKUDetails(`${defaults.get("sku").value}`), type: DescriptionType.Text } as Description, - hidden: false, - }); defaults.set("connectionString", { value: connectionStringValue, hidden: false, }); + + defaults.set("metricsString", { + value: metricsStringValue, + hidden: false, + }); + + defaults.set("resizingDecisionString", { + value: resizingDecisionValue, + hidden: false, + }); } defaults.set("warningBanner", undefined); @@ -289,7 +320,7 @@ export default class SqlX extends SelfServeBaseClass { textTKey: "DedicatedGatewayDescription", type: DescriptionType.Text, link: { - href: "https://docs.microsoft.com/en-us/azure/cosmos-db/introduction", + href: "https://aka.ms/cosmos-db-dedicated-gateway-overview", textTKey: "LearnAboutDedicatedGateway", }, }, @@ -312,12 +343,6 @@ export default class SqlX extends SelfServeBaseClass { }) sku: ChoiceItem; - @Values({ - labelTKey: "SKUDetails", - isDynamicDescription: true, - }) - skuDetails: string; - @OnChange(onNumberOfInstancesChange) @Values({ labelTKey: "NumberOfInstances", @@ -328,6 +353,16 @@ export default class SqlX extends SelfServeBaseClass { }) instances: number; + @Values({ + description: metricsStringValue, + }) + metricsString: string; + + @Values({ + description: resizingDecisionValue, + }) + resizingDecisionString: string; + @Values({ labelTKey: "Cost", isDynamicDescription: true,