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 <srnara@microsoft.com>
This commit is contained in:
fnbalaji 2021-05-17 22:15:26 -07:00 committed by GitHub
parent 62e205be6a
commit 48eeb8419d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 147 additions and 80 deletions

View File

@ -37,16 +37,19 @@
"CannotSave": "Cannot save the changes to the Dedicated gateway resource at the moment.", "CannotSave": "Cannot save the changes to the Dedicated gateway resource at the moment.",
"DedicatedGatewayEndpoint": "Dedicated gatewayEndpoint", "DedicatedGatewayEndpoint": "Dedicated gatewayEndpoint",
"NoValue": "", "NoValue": "",
"SKUDetails": "SKU Details:",
"CosmosD4Details": "General Purpose Cosmos Compute with 4 vCPUs, 16 GB Memory", "CosmosD4Details": "General Purpose Cosmos Compute with 4 vCPUs, 16 GB Memory",
"CosmosD8Details": "General Purpose Cosmos Compute with 8 vCPUs, 32 GB Memory", "CosmosD8Details": "General Purpose Cosmos Compute with 8 vCPUs, 32 GB Memory",
"CosmosD16Details": "General Purpose Cosmos Compute with 16 vCPUs, 64 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", "Cost": "Cost",
"CostText": "Hourly cost of the dedicated gateway resource depends on the SKU selection, number of instances per region, and number of regions.", "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", "ConnectionString": "Connection String",
"ConnectionStringText": "To use the dedicated gateway, use the connection string shown in ", "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.", "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." "WarningBannerOnDelete": "After deprovisioning the dedicated gateway, you must update any applications using the old dedicated gateway connection string."
} }

View File

@ -1,10 +1,12 @@
import { RefreshResult } from "../SelfServeTypes"; import { configContext } from "../../ConfigContext";
import { userContext } from "../../UserContext"; import { userContext } from "../../UserContext";
import { armRequestWithoutPolling } from "../../Utils/arm/request"; 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"; import { SqlxServiceResource, UpdateDedicatedGatewayRequestParameters } from "./SqlxTypes";
const apiVersion = "2020-06-01-preview"; const apiVersion = "2021-04-01-preview";
export enum ResourceStatus { export enum ResourceStatus {
Running = "Running", Running = "Running",
@ -21,7 +23,7 @@ export interface DedicatedGatewayResponse {
} }
export const getPath = (subscriptionId: string, resourceGroup: string, name: string): string => { 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<string> => { export const updateDedicatedGatewayResource = async (sku: string, instances: number): Promise<string> => {
@ -30,39 +32,66 @@ export const updateDedicatedGatewayResource = async (sku: string, instances: num
properties: { properties: {
instanceSize: sku, instanceSize: sku,
instanceCount: instances, instanceCount: instances,
serviceType: "Sqlx", serviceType: "SqlDedicatedGateway",
}, },
}; };
const armRequestResult = await armRequestWithoutPolling({ const telemetryData = { ...body, httpMethod: "PUT", selfServeClassName: SqlX.name };
host: configContext.ARM_ENDPOINT, const updateTimeStamp = selfServeTraceStart(telemetryData);
path, let armRequestResult;
method: "PUT", try {
apiVersion, armRequestResult = await armRequestWithoutPolling({
body, host: configContext.ARM_ENDPOINT,
}); path,
return armRequestResult.operationStatusUrl; 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<string> => { export const deleteDedicatedGatewayResource = async (): Promise<string> => {
const path = getPath(userContext.subscriptionId, userContext.resourceGroup, userContext.databaseAccount.name); const path = getPath(userContext.subscriptionId, userContext.resourceGroup, userContext.databaseAccount.name);
const armRequestResult = await armRequestWithoutPolling({ const telemetryData = { httpMethod: "DELETE", selfServeClassName: SqlX.name };
host: configContext.ARM_ENDPOINT, const deleteTimeStamp = selfServeTraceStart(telemetryData);
path, let armRequestResult;
method: "DELETE", try {
apiVersion, armRequestResult = await armRequestWithoutPolling({
}); host: configContext.ARM_ENDPOINT,
return armRequestResult.operationStatusUrl; 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<SqlxServiceResource> => { export const getDedicatedGatewayResource = async (): Promise<SqlxServiceResource> => {
const path = getPath(userContext.subscriptionId, userContext.resourceGroup, userContext.databaseAccount.name); const path = getPath(userContext.subscriptionId, userContext.resourceGroup, userContext.databaseAccount.name);
const armRequestResult = await armRequestWithoutPolling<SqlxServiceResource>({ const telemetryData = { httpMethod: "GET", selfServeClassName: SqlX.name };
host: configContext.ARM_ENDPOINT, const getResourceTimeStamp = selfServeTraceStart(telemetryData);
path, let armRequestResult;
method: "GET", try {
apiVersion, armRequestResult = await armRequestWithoutPolling<SqlxServiceResource>({
}); host: configContext.ARM_ENDPOINT,
return armRequestResult.result; 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<DedicatedGatewayResponse> => { export const getCurrentProvisioningState = async (): Promise<DedicatedGatewayResponse> => {

View File

@ -23,7 +23,7 @@ const costPerHourValue: Description = {
textTKey: "CostText", textTKey: "CostText",
type: DescriptionType.Text, type: DescriptionType.Text,
link: { link: {
href: "https://azure.microsoft.com/en-us/pricing/details/cosmos-db/", href: "https://aka.ms/cosmos-db-dedicated-gateway-pricing",
textTKey: "DedicatedGatewayPricing", 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 CosmosD4s = "Cosmos.D4s";
const CosmosD8s = "Cosmos.D8s"; const CosmosD8s = "Cosmos.D8s";
const CosmosD16s = "Cosmos.D16s"; 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<string, SmartUiInput>): Map<string, SmartUiInput> => { const onSKUChange = (newValue: InputType, currentValues: Map<string, SmartUiInput>): Map<string, SmartUiInput> => {
currentValues.set("sku", { value: newValue }); currentValues.set("sku", { value: newValue });
currentValues.set("skuDetails", {
value: { textTKey: getSKUDetails(`${newValue.toString()}`), type: DescriptionType.Text } as Description,
});
currentValues.set("costPerHour", { value: costPerHourValue }); currentValues.set("costPerHour", { value: costPerHourValue });
return currentValues; return currentValues;
}; };
const onNumberOfInstancesChange = ( const onNumberOfInstancesChange = (
newValue: InputType, newValue: InputType,
currentValues: Map<string, SmartUiInput> currentValues: Map<string, SmartUiInput>,
baselineValues: Map<string, SmartUiInput>
): Map<string, SmartUiInput> => { ): Map<string, SmartUiInput> => {
currentValues.set("instances", { value: newValue }); currentValues.set("instances", { value: newValue });
currentValues.set("warningBanner", { const dedicatedGatewayOriginallyEnabled = baselineValues.get("enableDedicatedGateway")?.value as boolean;
value: { textTKey: "WarningBannerOnUpdate" } as Description, const baselineInstances = baselineValues.get("instances")?.value as number;
hidden: false, 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; return currentValues;
}; };
@ -87,10 +100,11 @@ const onEnableDedicatedGatewayChange = (
if (dedicatedGatewayOriginallyEnabled === newValue) { if (dedicatedGatewayOriginallyEnabled === newValue) {
currentValues.set("sku", baselineValues.get("sku")); currentValues.set("sku", baselineValues.get("sku"));
currentValues.set("instances", baselineValues.get("instances")); currentValues.set("instances", baselineValues.get("instances"));
currentValues.set("skuDetails", baselineValues.get("skuDetails"));
currentValues.set("costPerHour", baselineValues.get("costPerHour")); currentValues.set("costPerHour", baselineValues.get("costPerHour"));
currentValues.set("warningBanner", baselineValues.get("warningBanner")); currentValues.set("warningBanner", baselineValues.get("warningBanner"));
currentValues.set("connectionString", baselineValues.get("connectionString")); currentValues.set("connectionString", baselineValues.get("connectionString"));
currentValues.set("metricsString", baselineValues.get("metricsString"));
currentValues.set("resizingDecisionString", baselineValues.get("resizingDecisionString"));
return currentValues; return currentValues;
} }
@ -100,7 +114,7 @@ const onEnableDedicatedGatewayChange = (
value: { value: {
textTKey: "WarningBannerOnUpdate", textTKey: "WarningBannerOnUpdate",
link: { link: {
href: "https://docs.microsoft.com/en-us/azure/cosmos-db/introduction", href: "https://aka.ms/cosmos-db-dedicated-gateway-pricing",
textTKey: "DedicatedGatewayPricing", textTKey: "DedicatedGatewayPricing",
}, },
} as Description, } as Description,
@ -111,7 +125,7 @@ const onEnableDedicatedGatewayChange = (
value: { value: {
textTKey: "WarningBannerOnDelete", textTKey: "WarningBannerOnDelete",
link: { link: {
href: "https://docs.microsoft.com/en-us/azure/cosmos-db/introduction", href: "https://aka.ms/cosmos-db-dedicated-gateway-overview",
textTKey: "DeprovisioningDetailsText", textTKey: "DeprovisioningDetailsText",
}, },
} as Description, } as Description,
@ -132,18 +146,22 @@ const onEnableDedicatedGatewayChange = (
disabled: dedicatedGatewayOriginallyEnabled, 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("costPerHour", { value: costPerHourValue, hidden: hideAttributes });
currentValues.set("connectionString", { currentValues.set("connectionString", {
value: connectionStringValue, value: connectionStringValue,
hidden: !newValue || !dedicatedGatewayOriginallyEnabled, hidden: !newValue || !dedicatedGatewayOriginallyEnabled,
}); });
currentValues.set("metricsString", {
value: metricsStringValue,
hidden: !newValue || !dedicatedGatewayOriginallyEnabled,
});
currentValues.set("resizingDecisionString", {
value: resizingDecisionValue,
hidden: !newValue || !dedicatedGatewayOriginallyEnabled,
});
return currentValues; return currentValues;
}; };
@ -151,7 +169,6 @@ const skuDropDownItems: ChoiceItem[] = [
{ labelTKey: "CosmosD4s", key: CosmosD4s }, { labelTKey: "CosmosD4s", key: CosmosD4s },
{ labelTKey: "CosmosD8s", key: CosmosD8s }, { labelTKey: "CosmosD8s", key: CosmosD8s },
{ labelTKey: "CosmosD16s", key: CosmosD16s }, { labelTKey: "CosmosD16s", key: CosmosD16s },
{ labelTKey: "CosmosD32s", key: CosmosD32s },
]; ];
const getSkus = async (): Promise<ChoiceItem[]> => { const getSkus = async (): Promise<ChoiceItem[]> => {
@ -184,7 +201,6 @@ export default class SqlX extends SelfServeBaseClass {
currentValues.set("warningBanner", undefined); currentValues.set("warningBanner", undefined);
//TODO : Add try catch for each RP call and return relevant notifications
if (dedicatedGatewayOriginallyEnabled) { if (dedicatedGatewayOriginallyEnabled) {
if (!dedicatedGatewayCurrentlyEnabled) { if (!dedicatedGatewayCurrentlyEnabled) {
const operationStatusUrl = await deleteDedicatedGatewayResource(); const operationStatusUrl = await deleteDedicatedGatewayResource();
@ -206,9 +222,11 @@ export default class SqlX extends SelfServeBaseClass {
}, },
}; };
} else { } 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 { return {
operationStatusUrl: undefined, operationStatusUrl: operationStatusUrl,
portalNotification: { portalNotification: {
initialize: { initialize: {
titleTKey: "UpdateInitializeTitle", titleTKey: "UpdateInitializeTitle",
@ -255,24 +273,37 @@ export default class SqlX extends SelfServeBaseClass {
defaults.set("enableDedicatedGateway", { value: false }); defaults.set("enableDedicatedGateway", { value: false });
defaults.set("sku", { value: CosmosD4s, hidden: true }); defaults.set("sku", { value: CosmosD4s, hidden: true });
defaults.set("instances", { value: await getInstancesMin(), hidden: true }); defaults.set("instances", { value: await getInstancesMin(), hidden: true });
defaults.set("skuDetails", undefined);
defaults.set("costPerHour", undefined); defaults.set("costPerHour", undefined);
defaults.set("connectionString", undefined); defaults.set("connectionString", undefined);
defaults.set("metricsString", {
value: undefined,
hidden: true,
});
defaults.set("resizingDecisionString", {
value: undefined,
hidden: true,
});
const response = await getCurrentProvisioningState(); const response = await getCurrentProvisioningState();
if (response.status && response.status !== "Deleting") { if (response.status && response.status !== "Deleting") {
defaults.set("enableDedicatedGateway", { value: true }); defaults.set("enableDedicatedGateway", { value: true });
defaults.set("sku", { value: response.sku, disabled: 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("costPerHour", { value: costPerHourValue });
defaults.set("skuDetails", {
value: { textTKey: getSKUDetails(`${defaults.get("sku").value}`), type: DescriptionType.Text } as Description,
hidden: false,
});
defaults.set("connectionString", { defaults.set("connectionString", {
value: connectionStringValue, value: connectionStringValue,
hidden: false, hidden: false,
}); });
defaults.set("metricsString", {
value: metricsStringValue,
hidden: false,
});
defaults.set("resizingDecisionString", {
value: resizingDecisionValue,
hidden: false,
});
} }
defaults.set("warningBanner", undefined); defaults.set("warningBanner", undefined);
@ -289,7 +320,7 @@ export default class SqlX extends SelfServeBaseClass {
textTKey: "DedicatedGatewayDescription", textTKey: "DedicatedGatewayDescription",
type: DescriptionType.Text, type: DescriptionType.Text,
link: { link: {
href: "https://docs.microsoft.com/en-us/azure/cosmos-db/introduction", href: "https://aka.ms/cosmos-db-dedicated-gateway-overview",
textTKey: "LearnAboutDedicatedGateway", textTKey: "LearnAboutDedicatedGateway",
}, },
}, },
@ -312,12 +343,6 @@ export default class SqlX extends SelfServeBaseClass {
}) })
sku: ChoiceItem; sku: ChoiceItem;
@Values({
labelTKey: "SKUDetails",
isDynamicDescription: true,
})
skuDetails: string;
@OnChange(onNumberOfInstancesChange) @OnChange(onNumberOfInstancesChange)
@Values({ @Values({
labelTKey: "NumberOfInstances", labelTKey: "NumberOfInstances",
@ -328,6 +353,16 @@ export default class SqlX extends SelfServeBaseClass {
}) })
instances: number; instances: number;
@Values({
description: metricsStringValue,
})
metricsString: string;
@Values({
description: resizingDecisionValue,
})
resizingDecisionString: string;
@Values({ @Values({
labelTKey: "Cost", labelTKey: "Cost",
isDynamicDescription: true, isDynamicDescription: true,