mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 18:46:40 -05:00
Added a dynamic Cost Summary section to SQL MIAA Deployment Wizard (#17420)
* Added valueprovider for pricing. Pushing this for troubleshooting help. * Committing changes for troubleshooting help. Moved InputValueType to typings file. * Add readonly inputs to list * Fixed ordering of package.json merge items * Estimated cost moved to input page, ValueProvider only takes in a triggerfields[] and not a single string, fixed pricing logic. * Removed pricingModel.ts * Reverted some comments and code changes that were used in debugging. * Changed some values from localizedConstants to single-quote constants' * Changed some values from localizedConstants to single-quote constants' * Added copyright header to pricingUtils.ts * Removed try catch in extension.ts valueproviders, made some values in PricingUtils.ts top-level instead of exporting. * Minor changes, added some comments and localized USD. * Changes pricingutils classes to be constants, and added disposable to Hookupvalueprovider Co-authored-by: Candice Ye <canye@microsoft.com> Co-authored-by: chgagnon <chgagnon@microsoft.com>
This commit is contained in:
@@ -1079,6 +1079,7 @@
|
|||||||
"variableName": "AZDATA_NB_VAR_SQL_CORES_REQUEST",
|
"variableName": "AZDATA_NB_VAR_SQL_CORES_REQUEST",
|
||||||
"type": "number",
|
"type": "number",
|
||||||
"min": 1,
|
"min": 1,
|
||||||
|
"defaultValue": 2,
|
||||||
"required": false,
|
"required": false,
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
@@ -1095,6 +1096,7 @@
|
|||||||
"type": "number",
|
"type": "number",
|
||||||
"min": 1,
|
"min": 1,
|
||||||
"required": false,
|
"required": false,
|
||||||
|
"defaultValue": 4,
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": ">=",
|
"type": ">=",
|
||||||
@@ -1109,6 +1111,7 @@
|
|||||||
"variableName": "AZDATA_NB_VAR_SQL_MEMORY_REQUEST",
|
"variableName": "AZDATA_NB_VAR_SQL_MEMORY_REQUEST",
|
||||||
"type": "number",
|
"type": "number",
|
||||||
"min": 2,
|
"min": 2,
|
||||||
|
"defaultValue": 4,
|
||||||
"required": false,
|
"required": false,
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
@@ -1124,6 +1127,7 @@
|
|||||||
"variableName": "AZDATA_NB_VAR_SQL_MEMORY_LIMIT",
|
"variableName": "AZDATA_NB_VAR_SQL_MEMORY_LIMIT",
|
||||||
"type": "number",
|
"type": "number",
|
||||||
"min": 2,
|
"min": 2,
|
||||||
|
"defaultValue": 8,
|
||||||
"required": false,
|
"required": false,
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
@@ -1136,9 +1140,9 @@
|
|||||||
{
|
{
|
||||||
"type": "options",
|
"type": "options",
|
||||||
"label": "%arc.sql.service.tier.label%",
|
"label": "%arc.sql.service.tier.label%",
|
||||||
|
"variableName": "AZDATA_NB_VAR_SQL_SERVICE_TIER",
|
||||||
"description": "%arc.sql.service.tier.description%",
|
"description": "%arc.sql.service.tier.description%",
|
||||||
"required": true,
|
"required": true,
|
||||||
"variableName": "AZDATA_NB_VAR_SQL_SERVICE_TIER",
|
|
||||||
"options": {
|
"options": {
|
||||||
"values": [
|
"values": [
|
||||||
"%arc.sql.service.tier.business.critical%",
|
"%arc.sql.service.tier.business.critical%",
|
||||||
@@ -1151,10 +1155,16 @@
|
|||||||
{
|
{
|
||||||
"type": "checkbox",
|
"type": "checkbox",
|
||||||
"label": "%arc.sql.dev.use.label%",
|
"label": "%arc.sql.dev.use.label%",
|
||||||
"description": "%arc.sql.dev.use.description%",
|
|
||||||
"defaultValue": "false",
|
|
||||||
"variableName": "AZDATA_NB_VAR_SQL_DEV_USE",
|
"variableName": "AZDATA_NB_VAR_SQL_DEV_USE",
|
||||||
"required": true
|
"description": "%arc.sql.dev.use.description%",
|
||||||
|
"defaultValue": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "checkbox",
|
||||||
|
"label": "%arc.sql.license.type.label%",
|
||||||
|
"variableName": "AZDATA_NB_VAR_SQL_LICENSE_TYPE",
|
||||||
|
"description": "%arc.sql.license.type.description%",
|
||||||
|
"defaultValue": false
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -1184,8 +1194,81 @@
|
|||||||
"required": false
|
"required": false
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "%arc.sql.cost.summary%",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"label": "%arc.sql.cost.summary.additional.charge%",
|
||||||
|
"type": "readonly_text",
|
||||||
|
"enabled": true,
|
||||||
|
"labelWidth": "750px",
|
||||||
|
"links": [
|
||||||
|
{
|
||||||
|
"text": "%arc.sql.cost.summary.pricing.details%",
|
||||||
|
"url": "https://aka.ms/ArcSQLBilling"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "%arc.sql.cost.summary.cost.vcore%",
|
||||||
|
"type": "readonly_text",
|
||||||
|
"isEvaluated": true,
|
||||||
|
"defaultValue": "0.00 USD",
|
||||||
|
"valueProvider": {
|
||||||
|
"providerId": "params-to-cost-per-vcore",
|
||||||
|
"triggerFields": [
|
||||||
|
"AZDATA_NB_VAR_SQL_DEV_USE",
|
||||||
|
"AZDATA_NB_VAR_SQL_SERVICE_TIER"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "%arc.sql.cost.summary.vcore.limit%",
|
||||||
|
"type": "readonly_text",
|
||||||
|
"isEvaluated": true,
|
||||||
|
"defaultValue": "x 4",
|
||||||
|
"valueProvider": {
|
||||||
|
"providerId": "params-to-vcore-limit",
|
||||||
|
"triggerFields": [
|
||||||
|
"AZDATA_NB_VAR_SQL_CORES_LIMIT"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "%arc.sql.cost.summary.azure.hybrid.benefit.discount%",
|
||||||
|
"type": "readonly_text",
|
||||||
|
"isEvaluated": true,
|
||||||
|
"defaultValue": "- 0",
|
||||||
|
"valueProvider": {
|
||||||
|
"providerId": "params-to-hybrid-benefit-discount",
|
||||||
|
"triggerFields": [
|
||||||
|
"AZDATA_NB_VAR_SQL_CORES_LIMIT",
|
||||||
|
"AZDATA_NB_VAR_SQL_DEV_USE",
|
||||||
|
"AZDATA_NB_VAR_SQL_SERVICE_TIER",
|
||||||
|
"AZDATA_NB_VAR_SQL_LICENSE_TYPE"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "%arc.sql.cost.summary.estimated.cost.per.month%",
|
||||||
|
"type": "readonly_text",
|
||||||
|
"variableName": "AZDATA_NB_VAR_SQL_ESTIMATED_COST",
|
||||||
|
"defaultValue": "0.00 USD",
|
||||||
|
"valueProvider": {
|
||||||
|
"providerId": "params-to-estimated-cost",
|
||||||
|
"triggerFields": [
|
||||||
|
"AZDATA_NB_VAR_SQL_REPLICAS",
|
||||||
|
"AZDATA_NB_VAR_SQL_CORES_LIMIT",
|
||||||
|
"AZDATA_NB_VAR_SQL_DEV_USE",
|
||||||
|
"AZDATA_NB_VAR_SQL_SERVICE_TIER",
|
||||||
|
"AZDATA_NB_VAR_SQL_LICENSE_TYPE"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
"command.removeController.title": "Remove Controller",
|
"command.removeController.title": "Remove Controller",
|
||||||
"command.refresh.title": "Refresh",
|
"command.refresh.title": "Refresh",
|
||||||
"command.editConnection.title": "Edit Connection",
|
"command.editConnection.title": "Edit Connection",
|
||||||
|
"command.estimateCostSqlMiaa.title": "Estimate Cost of SQL Managed Instance - Azure Arc",
|
||||||
"arc.openDashboard": "Manage",
|
"arc.openDashboard": "Manage",
|
||||||
|
|
||||||
"resource.type.azure.arc.display.name": "Azure Arc data controller (preview)",
|
"resource.type.azure.arc.display.name": "Azure Arc data controller (preview)",
|
||||||
@@ -96,6 +97,30 @@
|
|||||||
"arc.sql.three.replicas": "3 replicas",
|
"arc.sql.three.replicas": "3 replicas",
|
||||||
"arc.storage-class.data.label": "Storage Class (Data)",
|
"arc.storage-class.data.label": "Storage Class (Data)",
|
||||||
"arc.sql.storage-class.data.description": "The storage class to be used for data (.mdf). If no value is specified, the default storage class will be used.",
|
"arc.sql.storage-class.data.description": "The storage class to be used for data (.mdf). If no value is specified, the default storage class will be used.",
|
||||||
|
"arc.sql.cost.summary.sql.miaa.cost.summary": "SQL Managed Instance - Azure Arc Cost Summary",
|
||||||
|
"arc.sql.cost.summary.sql.miaa": "SQL managed instance - Azure Arc",
|
||||||
|
"arc.sql.cost.summary.estimated.cost.per.month": "Estimated cost per month",
|
||||||
|
"arc.sql.summary.arc.by.microsoft" : "by Microsoft",
|
||||||
|
"arc.sql.cost.summary": "Cost Summary",
|
||||||
|
"arc.sql.cost.summary.service.tier": "Service Tier",
|
||||||
|
"arc.sql.cost.summary.general.purpose": "General Purpose",
|
||||||
|
"arc.sql.cost.summary.business.critical": "Business Critical",
|
||||||
|
"arc.sql.cost.summary.cost.vcore": "Cost per vCore (in USD)",
|
||||||
|
"arc.sql.cost.summary.vcore.limit": "CPU vCores Limit",
|
||||||
|
"arc.sql.cost.summary.azure.hybrid.benefit.discount": "Azure Hybrid Benefit discount (in USD)",
|
||||||
|
"arc.sql.cost.summary.sql.connection.info": "SQL Connection Information",
|
||||||
|
"arc.sql.cost.summary.sql.instance.settings": "SQL Instance Settings",
|
||||||
|
"arc.sql.cost.summary.service.tier.learn.more.description": "Select from the latest vCore service tiers available for SQL Managed Instance - Azure Arc including General Purpose and Business Critical. {0}",
|
||||||
|
"arc.sql.cost.summary.service.tier.learn.more.text": "Learn more",
|
||||||
|
"arc.sql.cost.summary.basics": "Basics",
|
||||||
|
"arc.sql.cost.summary.subscription": "Subscription",
|
||||||
|
"arc.sql.cost.summary.resource.group": "Resource group",
|
||||||
|
"arc.sql.cost.summary.instance.name": "Instance name",
|
||||||
|
"arc.sql.cost.summary.custom.location": "Custom location",
|
||||||
|
"arc.sql.cost.summary.admin.account": "Administrator account",
|
||||||
|
"arc.sql.cost.summary.managed.instance.admin.login": "Managed Instance admin login",
|
||||||
|
"arc.sql.cost.summary.additional.charge": "Additional charge per usage. See {0} for more detail.",
|
||||||
|
"arc.sql.cost.summary.pricing.details": "pricing details",
|
||||||
"arc.postgres.storage-class.data.description": "The storage class to be used for data persistent volumes",
|
"arc.postgres.storage-class.data.description": "The storage class to be used for data persistent volumes",
|
||||||
"arc.storage-class.datalogs.label": "Storage Class (Database logs)",
|
"arc.storage-class.datalogs.label": "Storage Class (Database logs)",
|
||||||
"arc.sql.storage-class.datalogs.description": "The storage class to be used for database logs (.ldf). If no value is specified, the default storage class will be used.",
|
"arc.sql.storage-class.datalogs.description": "The storage class to be used for database logs (.ldf). If no value is specified, the default storage class will be used.",
|
||||||
@@ -123,6 +148,8 @@
|
|||||||
"arc.sql.service.tier.label": "Service Tier",
|
"arc.sql.service.tier.label": "Service Tier",
|
||||||
"arc.sql.service.tier.description": "Select from the latest vCore service tiers available for SQL Managed Instance - Azure Arc including General Purpose and Business Critical. {0}",
|
"arc.sql.service.tier.description": "Select from the latest vCore service tiers available for SQL Managed Instance - Azure Arc including General Purpose and Business Critical. {0}",
|
||||||
"arc.sql.dev.use.label": "For development use only",
|
"arc.sql.dev.use.label": "For development use only",
|
||||||
|
"arc.sql.license.type.label": "I already have a SQL Server License",
|
||||||
|
"arc.sql.license.type.description": "Apply the Azure Hybrid Benefit if you already own a SQL Server License",
|
||||||
"arc.sql.pitr.description": "Point in time restore",
|
"arc.sql.pitr.description": "Point in time restore",
|
||||||
"arc.sql.retention.days.label": "PITR retention (days)",
|
"arc.sql.retention.days.label": "PITR retention (days)",
|
||||||
"arc.sql.retention.days.description": "Specify how long you want to keep your point-in-time backups.",
|
"arc.sql.retention.days.description": "Specify how long you want to keep your point-in-time backups.",
|
||||||
|
|||||||
117
extensions/arc/src/common/pricingUtils.ts
Normal file
117
extensions/arc/src/common/pricingUtils.ts
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
import { InputValueType } from 'resource-deployment';
|
||||||
|
import * as loc from '../localizedConstants';
|
||||||
|
|
||||||
|
export const SqlManagedInstanceGeneralPurpose = {
|
||||||
|
tierName: loc.generalPurposeLabel,
|
||||||
|
basePricePerCore: 80,
|
||||||
|
licenseIncludedPricePerCore: 153,
|
||||||
|
maxMemorySize: 128,
|
||||||
|
maxVCores: 24,
|
||||||
|
|
||||||
|
replicaOptions: [
|
||||||
|
{
|
||||||
|
text: loc.replicaOne,
|
||||||
|
value: 1,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
defaultReplicaValue: 1
|
||||||
|
};
|
||||||
|
|
||||||
|
const SqlManagedInstanceBusinessCritical = {
|
||||||
|
tierName: loc.businessCriticalLabel,
|
||||||
|
|
||||||
|
// Set to real values when BC is ready
|
||||||
|
basePricePerCore: 0,
|
||||||
|
licenseIncludedPricePerCore: 0,
|
||||||
|
|
||||||
|
replicaOptions: [
|
||||||
|
{
|
||||||
|
text: loc.replicaTwo,
|
||||||
|
value: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: loc.replicaThree,
|
||||||
|
value: 3,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
defaultReplicaValue: 3
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SqlManagedInstancePricingLink: string = 'https://aka.ms/ArcSQLBilling';
|
||||||
|
|
||||||
|
export const serviceTierVarName = 'AZDATA_NB_VAR_SQL_SERVICE_TIER';
|
||||||
|
export const devUseVarName = 'AZDATA_NB_VAR_SQL_DEV_USE';
|
||||||
|
export const vcoresLimitVarName = 'AZDATA_NB_VAR_SQL_CORES_LIMIT';
|
||||||
|
export const licenseTypeVarName = 'AZDATA_NB_VAR_SQL_LICENSE_TYPE';
|
||||||
|
|
||||||
|
// Estimated base price for one vCore.
|
||||||
|
export function estimatedBasePriceForOneVCore(mapping: { [key: string]: InputValueType }): number {
|
||||||
|
let price = 0;
|
||||||
|
if (mapping[devUseVarName] === 'true') {
|
||||||
|
price = 0;
|
||||||
|
} else if (mapping[devUseVarName] === 'false') {
|
||||||
|
if (mapping[serviceTierVarName] === SqlManagedInstanceGeneralPurpose.tierName) {
|
||||||
|
price = SqlManagedInstanceGeneralPurpose.basePricePerCore;
|
||||||
|
} else if (mapping[serviceTierVarName] === SqlManagedInstanceBusinessCritical.tierName) {
|
||||||
|
price = SqlManagedInstanceBusinessCritical.basePricePerCore;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return price;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Estimated SQL server license price for one vCore.
|
||||||
|
export function estimatedSqlServerLicensePriceForOneVCore(mapping: { [key: string]: InputValueType }): number {
|
||||||
|
let price = 0;
|
||||||
|
if (mapping[devUseVarName] === 'true') {
|
||||||
|
price = 0;
|
||||||
|
} else if (mapping[devUseVarName] === 'false') {
|
||||||
|
if (mapping[serviceTierVarName] === SqlManagedInstanceGeneralPurpose.tierName) {
|
||||||
|
price = SqlManagedInstanceGeneralPurpose.licenseIncludedPricePerCore - SqlManagedInstanceGeneralPurpose.basePricePerCore;
|
||||||
|
} else if (mapping[serviceTierVarName] === SqlManagedInstanceBusinessCritical.tierName) {
|
||||||
|
price = SqlManagedInstanceBusinessCritical.licenseIncludedPricePerCore - SqlManagedInstanceBusinessCritical.basePricePerCore;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return price;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Full price for one vCore. This is shown on the cost summary card.
|
||||||
|
export function fullPriceForOneVCore(mapping: { [key: string]: InputValueType }): number {
|
||||||
|
return estimatedBasePriceForOneVCore(mapping) + estimatedSqlServerLicensePriceForOneVCore(mapping);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gets number of vCores limit specified
|
||||||
|
export function numCores(mapping: { [key: string]: InputValueType }): number {
|
||||||
|
return mapping[vcoresLimitVarName] ? <number>mapping[vcoresLimitVarName] : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Full price for all selected vCores.
|
||||||
|
export function vCoreFullPriceForAllCores(mapping: { [key: string]: InputValueType }): number {
|
||||||
|
return fullPriceForOneVCore(mapping) * numCores(mapping);
|
||||||
|
}
|
||||||
|
|
||||||
|
// SQL Server License price for all vCores. This is shown on the cost summary card if customer has SQL server license.
|
||||||
|
export function vCoreSqlServerLicensePriceForAllCores(mapping: { [key: string]: InputValueType }): number {
|
||||||
|
return estimatedSqlServerLicensePriceForOneVCore(mapping) * numCores(mapping);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the customer doesn't already have SQL Server License, AHB discount is set to zero because the price will be included
|
||||||
|
// in the total cost. If they already have it (they checked the box), then a discount will be applied.
|
||||||
|
export function azureHybridBenefitDiscount(mapping: { [key: string]: InputValueType }): number {
|
||||||
|
if (mapping[licenseTypeVarName] === 'true') {
|
||||||
|
return vCoreSqlServerLicensePriceForAllCores(mapping);
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Total price that will be charged to a customer. Is shown on the cost summary card.
|
||||||
|
export function total(mapping: { [key: string]: InputValueType }): number {
|
||||||
|
return vCoreFullPriceForAllCores(mapping) - azureHybridBenefitDiscount(mapping);
|
||||||
|
}
|
||||||
@@ -14,6 +14,7 @@ import { ConnectToControllerDialog } from './ui/dialogs/connectControllerDialog'
|
|||||||
import { AzureArcTreeDataProvider } from './ui/tree/azureArcTreeDataProvider';
|
import { AzureArcTreeDataProvider } from './ui/tree/azureArcTreeDataProvider';
|
||||||
import { ControllerTreeNode } from './ui/tree/controllerTreeNode';
|
import { ControllerTreeNode } from './ui/tree/controllerTreeNode';
|
||||||
import { TreeNode } from './ui/tree/treeNode';
|
import { TreeNode } from './ui/tree/treeNode';
|
||||||
|
import * as pricing from './common/pricingUtils';
|
||||||
|
|
||||||
export async function activate(context: vscode.ExtensionContext): Promise<arc.IExtension> {
|
export async function activate(context: vscode.ExtensionContext): Promise<arc.IExtension> {
|
||||||
IconPathHelper.setExtensionContext(context);
|
IconPathHelper.setExtensionContext(context);
|
||||||
@@ -61,6 +62,38 @@ export async function activate(context: vscode.ExtensionContext): Promise<arc.IE
|
|||||||
const rdApi = <rd.IExtension>vscode.extensions.getExtension(rd.extension.name)?.exports;
|
const rdApi = <rd.IExtension>vscode.extensions.getExtension(rd.extension.name)?.exports;
|
||||||
context.subscriptions.push(rdApi.registerOptionsSourceProvider(new ArcControllersOptionsSourceProvider(treeDataProvider)));
|
context.subscriptions.push(rdApi.registerOptionsSourceProvider(new ArcControllersOptionsSourceProvider(treeDataProvider)));
|
||||||
|
|
||||||
|
// Register valueprovider for getting the calculated cost per VCore.
|
||||||
|
context.subscriptions.push(rdApi.registerValueProvider({
|
||||||
|
id: 'params-to-cost-per-vcore',
|
||||||
|
getValue: async (mapping: { [key: string]: rd.InputValueType }) => {
|
||||||
|
return pricing.fullPriceForOneVCore(mapping);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Register valueprovider for getting the number of CPU VCores Limit input by the user.
|
||||||
|
context.subscriptions.push(rdApi.registerValueProvider({
|
||||||
|
id: 'params-to-vcore-limit',
|
||||||
|
getValue: async (mapping: { [key: string]: rd.InputValueType }) => {
|
||||||
|
return 'x ' + pricing.numCores(mapping).toString();
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Register valueprovider for getting the amount of hybrid benefit discount to be applied.
|
||||||
|
context.subscriptions.push(rdApi.registerValueProvider({
|
||||||
|
id: 'params-to-hybrid-benefit-discount',
|
||||||
|
getValue: async (mapping: { [key: string]: rd.InputValueType }) => {
|
||||||
|
return '- ' + pricing.azureHybridBenefitDiscount(mapping).toString();
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Register valueprovider for getting the total estimated cost.
|
||||||
|
context.subscriptions.push(rdApi.registerValueProvider({
|
||||||
|
id: 'params-to-estimated-cost',
|
||||||
|
getValue: async (mapping: { [key: string]: rd.InputValueType }) => {
|
||||||
|
return pricing.total(mapping).toString() + ' ' + loc.USD;
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
return arcApi(treeDataProvider);
|
return arcApi(treeDataProvider);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -274,6 +274,14 @@ export function connectionString(type: string): string { return localize({ key:
|
|||||||
export function copyConnectionStringToClipboard(type: string): string { return localize({ key: 'arc.copyConnectionStringToClipboard', comment: ['{0} is the name of the type of connection string (e.g. Java)'] }, "Copy {0} Connection String to clipboard", type); }
|
export function copyConnectionStringToClipboard(type: string): string { return localize({ key: 'arc.copyConnectionStringToClipboard', comment: ['{0} is the name of the type of connection string (e.g. Java)'] }, "Copy {0} Connection String to clipboard", type); }
|
||||||
export function copyValueToClipboard(valueName: string): string { return localize({ key: 'arc.copyValueToClipboard', comment: ['{0} is the name of the type of value being copied (e.g. Coordinator endpoint)'] }, "Copy {0} to clipboard", valueName); }
|
export function copyValueToClipboard(valueName: string): string { return localize({ key: 'arc.copyValueToClipboard', comment: ['{0} is the name of the type of value being copied (e.g. Coordinator endpoint)'] }, "Copy {0} to clipboard", valueName); }
|
||||||
|
|
||||||
|
// Pricing Constants
|
||||||
|
export const replicaOne = localize('arc.replicaOne', "1 replica");
|
||||||
|
export const replicaTwo = localize('arc.replicaTwo', "2 replicas");
|
||||||
|
export const replicaThree = localize('arc.replicaThree', "3 replicas");
|
||||||
|
export const generalPurposeLabel = localize('arc.generalPurposeLabel', "General Purpose (Up to 24 vCores and 128 Gi of RAM, standard high availability)");
|
||||||
|
export const businessCriticalLabel = localize('arc.businessCriticalLabel', "[PREVIEW] Business Critical (Unlimited vCores and RAM, advanced high availability)");
|
||||||
|
export const USD = localize('arc.USD', "USD");
|
||||||
|
|
||||||
// Errors
|
// Errors
|
||||||
export const pgConnectionRequired = localize('arc.pgConnectionRequired', "A connection is required to show and set database engine settings.");
|
export const pgConnectionRequired = localize('arc.pgConnectionRequired', "A connection is required to show and set database engine settings.");
|
||||||
export const miaaConnectionRequired = localize('arc.miaaConnectionRequired', "A connection is required to list the databases on this instance.");
|
export const miaaConnectionRequired = localize('arc.miaaConnectionRequired', "A connection is required to list the databases on this instance.");
|
||||||
|
|||||||
@@ -307,7 +307,7 @@ export interface DynamicOptionsAlternates {
|
|||||||
|
|
||||||
export interface ValueProviderInfo {
|
export interface ValueProviderInfo {
|
||||||
providerId: string,
|
providerId: string,
|
||||||
triggerField: string
|
triggerFields: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface FieldInfoBase {
|
export interface FieldInfoBase {
|
||||||
|
|||||||
@@ -5,10 +5,10 @@
|
|||||||
|
|
||||||
import * as azdata from 'azdata';
|
import * as azdata from 'azdata';
|
||||||
import 'mocha';
|
import 'mocha';
|
||||||
|
import { InputValueType } from 'resource-deployment';
|
||||||
import * as should from 'should';
|
import * as should from 'should';
|
||||||
import * as sinon from 'sinon';
|
import * as sinon from 'sinon';
|
||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
import { InputValueType } from '../../../ui/modelViewUtils';
|
|
||||||
import { createValidation, GreaterThanOrEqualsValidation, IntegerValidation, LessThanOrEqualsValidation, RegexValidation, validateInputBoxComponent, Validation, ValidationType } from '../../../ui/validation/validations';
|
import { createValidation, GreaterThanOrEqualsValidation, IntegerValidation, LessThanOrEqualsValidation, RegexValidation, validateInputBoxComponent, Validation, ValidationType } from '../../../ui/validation/validations';
|
||||||
|
|
||||||
const inputBox = <azdata.InputBoxComponent>{
|
const inputBox = <azdata.InputBoxComponent>{
|
||||||
|
|||||||
@@ -24,9 +24,15 @@ declare module 'resource-deployment' {
|
|||||||
getIsPassword?: (variableName: string) => boolean | Promise<boolean>;
|
getIsPassword?: (variableName: string) => boolean | Promise<boolean>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type InputValueType = string | number | boolean | undefined;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a calculated value based on the given input values.
|
||||||
|
* @param triggerValues A map of the trigger field names and their current values specified in the valueProvider field info
|
||||||
|
*/
|
||||||
export interface IValueProvider {
|
export interface IValueProvider {
|
||||||
readonly id: string,
|
readonly id: string,
|
||||||
getValue(triggerValue: string): Promise<string>;
|
getValue(triggerValues: string | {[key: string]: InputValueType}): Promise<InputValueType>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { azureResource } from 'azureResource';
|
|||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import { EOL } from 'os';
|
import { EOL } from 'os';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import { IOptionsSourceProvider } from 'resource-deployment';
|
import { InputValueType, IOptionsSourceProvider } from 'resource-deployment';
|
||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
import * as nls from 'vscode-nls';
|
import * as nls from 'vscode-nls';
|
||||||
import { getDateTimeString, getErrorMessage, isUserCancelledError, throwUnless } from '../common/utils';
|
import { getDateTimeString, getErrorMessage, isUserCancelledError, throwUnless } from '../common/utils';
|
||||||
@@ -33,7 +33,6 @@ const localize = nls.loadMessageBundle();
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
export type Validator = () => { valid: boolean, message: string };
|
export type Validator = () => { valid: boolean, message: string };
|
||||||
export type InputValueType = string | number | undefined;
|
|
||||||
export type InputComponent = azdata.TextComponent | azdata.InputBoxComponent | azdata.DropDownComponent | azdata.CheckBoxComponent | RadioGroupLoadingComponentBuilder;
|
export type InputComponent = azdata.TextComponent | azdata.InputBoxComponent | azdata.DropDownComponent | azdata.CheckBoxComponent | RadioGroupLoadingComponentBuilder;
|
||||||
export type InputComponentInfo<T extends InputComponent> = {
|
export type InputComponentInfo<T extends InputComponent> = {
|
||||||
component: T;
|
component: T;
|
||||||
@@ -471,20 +470,35 @@ async function hookUpValueProviders(context: WizardPageContext): Promise<void> {
|
|||||||
if (field.valueProvider) {
|
if (field.valueProvider) {
|
||||||
const fieldKey = field.variableName || field.label;
|
const fieldKey = field.variableName || field.label;
|
||||||
const fieldComponent = context.inputComponents[fieldKey];
|
const fieldComponent = context.inputComponents[fieldKey];
|
||||||
const targetComponent = context.inputComponents[field.valueProvider.triggerField];
|
|
||||||
if (!targetComponent) {
|
|
||||||
console.error(`Could not find target component ${field.valueProvider.triggerField} when hooking up value providers for ${field.label}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const provider = await valueProviderService.getValueProvider(field.valueProvider.providerId);
|
const provider = await valueProviderService.getValueProvider(field.valueProvider.providerId);
|
||||||
|
|
||||||
|
let targetComponentLabelToComponent: { [label: string]: InputComponentInfo<InputComponent>; } = {};
|
||||||
|
|
||||||
|
field.valueProvider.triggerFields.forEach((triggerField) => {
|
||||||
|
const targetComponent = context.inputComponents[triggerField];
|
||||||
|
if (!targetComponent) {
|
||||||
|
console.error(`Could not find target component ${triggerField} when hooking up value providers for ${field.label}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
targetComponentLabelToComponent[triggerField] = targetComponent;
|
||||||
|
});
|
||||||
|
|
||||||
|
// If one triggerfield changes value, update the new field value.
|
||||||
const updateFields = async () => {
|
const updateFields = async () => {
|
||||||
const targetComponentValue = await targetComponent.getValue();
|
let targetComponentLabelToValue: { [label: string]: InputValueType; } = {};
|
||||||
const newFieldValue = await provider.getValue(targetComponentValue?.toString() ?? '');
|
for (let label in targetComponentLabelToComponent) {
|
||||||
|
targetComponentLabelToValue[label] = await targetComponentLabelToComponent[label].getValue();
|
||||||
|
}
|
||||||
|
let newFieldValue = await provider.getValue(targetComponentLabelToValue);
|
||||||
fieldComponent.setValue(newFieldValue);
|
fieldComponent.setValue(newFieldValue);
|
||||||
};
|
};
|
||||||
targetComponent.onValueChanged(() => {
|
|
||||||
updateFields();
|
// Set the onValueChanged behavior for each component
|
||||||
});
|
for (let label in targetComponentLabelToComponent) {
|
||||||
|
context.onNewDisposableCreated(targetComponentLabelToComponent[label].onValueChanged(() => {
|
||||||
|
updateFields();
|
||||||
|
}));
|
||||||
|
}
|
||||||
await updateFields();
|
await updateFields();
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
@@ -863,6 +877,18 @@ function processReadonlyTextField(context: FieldContext, allowEvaluation: boolea
|
|||||||
const text = context.fieldInfo.defaultValue !== undefined
|
const text = context.fieldInfo.defaultValue !== undefined
|
||||||
? createLabel(context.view, { text: context.fieldInfo.defaultValue, description: '', required: false, width: context.fieldInfo.inputWidth })
|
? createLabel(context.view, { text: context.fieldInfo.defaultValue, description: '', required: false, width: context.fieldInfo.inputWidth })
|
||||||
: undefined;
|
: undefined;
|
||||||
|
if (text) {
|
||||||
|
// If we created the text component then add it to our list of inputs so other fields can utilize it
|
||||||
|
const onChangedEmitter = new vscode.EventEmitter<void>(); // Stub event since we don't currently support updating this when the dependent fields change
|
||||||
|
context.onNewDisposableCreated(onChangedEmitter);
|
||||||
|
context.onNewInputComponentCreated(context.fieldInfo.variableName || context.fieldInfo.label, {
|
||||||
|
component: text,
|
||||||
|
getValue: async (): Promise<InputValueType> => typeof text.value === 'string' ? text.value : text.value?.join(EOL),
|
||||||
|
setValue: (value: InputValueType) => text.value = value?.toString(),
|
||||||
|
onValueChanged: onChangedEmitter.event,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
addLabelInputPairToContainer(context.view, context.components, label, text, context.fieldInfo);
|
addLabelInputPairToContainer(context.view, context.components, label, text, context.fieldInfo);
|
||||||
return { label: label, text: text };
|
return { label: label, text: text };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,9 +4,9 @@
|
|||||||
*--------------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
import * as azdata from 'azdata';
|
import * as azdata from 'azdata';
|
||||||
|
import { InputValueType } from 'resource-deployment';
|
||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
import { isUndefinedOrEmpty, throwUnless } from '../../common/utils';
|
import { isUndefinedOrEmpty, throwUnless } from '../../common/utils';
|
||||||
import { InputValueType } from '../modelViewUtils';
|
|
||||||
|
|
||||||
export interface ValidationResult {
|
export interface ValidationResult {
|
||||||
valid: boolean;
|
valid: boolean;
|
||||||
|
|||||||
Reference in New Issue
Block a user