mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-15 10:58:31 -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",
|
||||
"type": "number",
|
||||
"min": 1,
|
||||
"defaultValue": 2,
|
||||
"required": false,
|
||||
"validations": [
|
||||
{
|
||||
@@ -1095,6 +1096,7 @@
|
||||
"type": "number",
|
||||
"min": 1,
|
||||
"required": false,
|
||||
"defaultValue": 4,
|
||||
"validations": [
|
||||
{
|
||||
"type": ">=",
|
||||
@@ -1109,6 +1111,7 @@
|
||||
"variableName": "AZDATA_NB_VAR_SQL_MEMORY_REQUEST",
|
||||
"type": "number",
|
||||
"min": 2,
|
||||
"defaultValue": 4,
|
||||
"required": false,
|
||||
"validations": [
|
||||
{
|
||||
@@ -1124,6 +1127,7 @@
|
||||
"variableName": "AZDATA_NB_VAR_SQL_MEMORY_LIMIT",
|
||||
"type": "number",
|
||||
"min": 2,
|
||||
"defaultValue": 8,
|
||||
"required": false,
|
||||
"validations": [
|
||||
{
|
||||
@@ -1136,9 +1140,9 @@
|
||||
{
|
||||
"type": "options",
|
||||
"label": "%arc.sql.service.tier.label%",
|
||||
"variableName": "AZDATA_NB_VAR_SQL_SERVICE_TIER",
|
||||
"description": "%arc.sql.service.tier.description%",
|
||||
"required": true,
|
||||
"variableName": "AZDATA_NB_VAR_SQL_SERVICE_TIER",
|
||||
"options": {
|
||||
"values": [
|
||||
"%arc.sql.service.tier.business.critical%",
|
||||
@@ -1151,10 +1155,16 @@
|
||||
{
|
||||
"type": "checkbox",
|
||||
"label": "%arc.sql.dev.use.label%",
|
||||
"description": "%arc.sql.dev.use.description%",
|
||||
"defaultValue": "false",
|
||||
"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
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"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.refresh.title": "Refresh",
|
||||
"command.editConnection.title": "Edit Connection",
|
||||
"command.estimateCostSqlMiaa.title": "Estimate Cost of SQL Managed Instance - Azure Arc",
|
||||
"arc.openDashboard": "Manage",
|
||||
|
||||
"resource.type.azure.arc.display.name": "Azure Arc data controller (preview)",
|
||||
@@ -96,6 +97,30 @@
|
||||
"arc.sql.three.replicas": "3 replicas",
|
||||
"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.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.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.",
|
||||
@@ -123,6 +148,8 @@
|
||||
"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.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.retention.days.label": "PITR retention (days)",
|
||||
"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 { ControllerTreeNode } from './ui/tree/controllerTreeNode';
|
||||
import { TreeNode } from './ui/tree/treeNode';
|
||||
import * as pricing from './common/pricingUtils';
|
||||
|
||||
export async function activate(context: vscode.ExtensionContext): Promise<arc.IExtension> {
|
||||
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;
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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 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
|
||||
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.");
|
||||
|
||||
@@ -307,7 +307,7 @@ export interface DynamicOptionsAlternates {
|
||||
|
||||
export interface ValueProviderInfo {
|
||||
providerId: string,
|
||||
triggerField: string
|
||||
triggerFields: string[]
|
||||
}
|
||||
|
||||
export interface FieldInfoBase {
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
|
||||
import * as azdata from 'azdata';
|
||||
import 'mocha';
|
||||
import { InputValueType } from 'resource-deployment';
|
||||
import * as should from 'should';
|
||||
import * as sinon from 'sinon';
|
||||
import * as vscode from 'vscode';
|
||||
import { InputValueType } from '../../../ui/modelViewUtils';
|
||||
import { createValidation, GreaterThanOrEqualsValidation, IntegerValidation, LessThanOrEqualsValidation, RegexValidation, validateInputBoxComponent, Validation, ValidationType } from '../../../ui/validation/validations';
|
||||
|
||||
const inputBox = <azdata.InputBoxComponent>{
|
||||
|
||||
@@ -24,9 +24,15 @@ declare module 'resource-deployment' {
|
||||
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 {
|
||||
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 { EOL } from 'os';
|
||||
import * as path from 'path';
|
||||
import { IOptionsSourceProvider } from 'resource-deployment';
|
||||
import { InputValueType, IOptionsSourceProvider } from 'resource-deployment';
|
||||
import * as vscode from 'vscode';
|
||||
import * as nls from 'vscode-nls';
|
||||
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 InputValueType = string | number | undefined;
|
||||
export type InputComponent = azdata.TextComponent | azdata.InputBoxComponent | azdata.DropDownComponent | azdata.CheckBoxComponent | RadioGroupLoadingComponentBuilder;
|
||||
export type InputComponentInfo<T extends InputComponent> = {
|
||||
component: T;
|
||||
@@ -471,20 +470,35 @@ async function hookUpValueProviders(context: WizardPageContext): Promise<void> {
|
||||
if (field.valueProvider) {
|
||||
const fieldKey = field.variableName || field.label;
|
||||
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);
|
||||
|
||||
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 targetComponentValue = await targetComponent.getValue();
|
||||
const newFieldValue = await provider.getValue(targetComponentValue?.toString() ?? '');
|
||||
let targetComponentLabelToValue: { [label: string]: InputValueType; } = {};
|
||||
for (let label in targetComponentLabelToComponent) {
|
||||
targetComponentLabelToValue[label] = await targetComponentLabelToComponent[label].getValue();
|
||||
}
|
||||
let newFieldValue = await provider.getValue(targetComponentLabelToValue);
|
||||
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();
|
||||
}
|
||||
}));
|
||||
@@ -863,6 +877,18 @@ function processReadonlyTextField(context: FieldContext, allowEvaluation: boolea
|
||||
const text = context.fieldInfo.defaultValue !== undefined
|
||||
? createLabel(context.view, { text: context.fieldInfo.defaultValue, description: '', required: false, width: context.fieldInfo.inputWidth })
|
||||
: 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);
|
||||
return { label: label, text: text };
|
||||
}
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as azdata from 'azdata';
|
||||
import { InputValueType } from 'resource-deployment';
|
||||
import * as vscode from 'vscode';
|
||||
import { isUndefinedOrEmpty, throwUnless } from '../../common/utils';
|
||||
import { InputValueType } from '../modelViewUtils';
|
||||
|
||||
export interface ValidationResult {
|
||||
valid: boolean;
|
||||
|
||||
Reference in New Issue
Block a user