mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 10:58:30 -05:00
Added dynamic options for SQL MIAA Deployment Wizard and updated checkbox field (#17119)
* Dynamic enablement * Added new package.json field for dynamic options and corresponding functions and classes. * Enabled dynamic options non-generalized and changed formatting of checkbox to have label on the left. * Added setOptions under InputComponentInfo for generalization, comments under checkbox component, and changed Replicas to High Availability to reflect parity in portal. * fix unit test Co-authored-by: Candice Ye <canye@microsoft.com> Co-authored-by: Alan Ren <alanren@microsoft.com>
This commit is contained in:
@@ -989,11 +989,24 @@
|
||||
"variableName": "AZDATA_NB_VAR_SQL_REPLICAS",
|
||||
"options": {
|
||||
"values": [
|
||||
"1",
|
||||
"3"
|
||||
"%arc.sql.two.replicas%",
|
||||
"%arc.sql.three.replicas%"
|
||||
],
|
||||
"defaultValue": "1",
|
||||
"defaultValue": "%arc.sql.two.replicas%",
|
||||
"optionsType": "radio"
|
||||
},
|
||||
"dynamicOptions":
|
||||
{
|
||||
"target": "AZDATA_NB_VAR_SQL_SERVICE_TIER",
|
||||
"alternates": [
|
||||
{
|
||||
"selection": "%arc.sql.service.tier.general.purpose%",
|
||||
"alternateValues": [
|
||||
"%arc.sql.one.replica%"
|
||||
],
|
||||
"defaultValue": "%arc.sql.one.replica%"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -1069,6 +1082,29 @@
|
||||
"description": "%memory.limit.greater.than.or.equal.to.requested.memory%"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "options",
|
||||
"label": "%arc.sql.service.tier.label%",
|
||||
"description": "%arc.sql.service.tier.description%",
|
||||
"required": true,
|
||||
"variableName": "AZDATA_NB_VAR_SQL_SERVICE_TIER",
|
||||
"options": {
|
||||
"values": [
|
||||
"%arc.sql.service.tier.business.critical%",
|
||||
"%arc.sql.service.tier.general.purpose%"
|
||||
],
|
||||
"defaultValue": "%arc.sql.service.tier.business.critical%",
|
||||
"optionsType": "radio"
|
||||
}
|
||||
},
|
||||
{
|
||||
"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
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -86,8 +86,13 @@
|
||||
"arc.sql.invalid.instance.name": "Instance name must consist of lower case alphanumeric characters or '-', start with a letter, end with an alphanumeric character, and be 13 characters or fewer in length.",
|
||||
"arc.storage-class.dc.label": "Storage Class",
|
||||
"arc.sql.storage-class.dc.description": "The storage class to be used for all data and logs persistent volumes for all data controller pods that require them.",
|
||||
"arc.sql.replicas.label": "Replicas",
|
||||
"arc.sql.replicas.description": "The number of SQL Managed Instance replicas that will be deployed in your Kubernetes cluster for high availability purposes",
|
||||
"arc.sql.high.availability.label": "High Availability",
|
||||
"arc.sql.high.availability.description": "Enable additional replicas for high availabilty. The compute and storage configuration selected below will be applied to all replicas.",
|
||||
"arc.sql.service.tier.general.purpose": "General Purpose (Up to 24 vCores and 128 Gi of RAM, standard high availability)",
|
||||
"arc.sql.service.tier.business.critical": "[PREVIEW] Business Critical (Unlimited vCores and RAM, advanced high availability)",
|
||||
"arc.sql.one.replica": "1 replica",
|
||||
"arc.sql.two.replicas": "2 replicas",
|
||||
"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.postgres.storage-class.data.description": "The storage class to be used for data persistent volumes",
|
||||
@@ -103,6 +108,10 @@
|
||||
"arc.sql.memory-limit.description": "The limit of the capacity of the managed instance as an integer.",
|
||||
"arc.memory-request.label": "Memory Request",
|
||||
"arc.sql.memory-request.description": "The request for the capacity of the managed instance as an integer amount of memory in GBs.",
|
||||
"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.dev.use.description": "Check the box to indicate this instance will be used for development or testing purposes only. This instance will not be billed.",
|
||||
"arc.postgres.storage-class.backups.description": "The storage class to be used for backup persistent volumes",
|
||||
"arc.password": "Password",
|
||||
"arc.confirm.password": "Confirm password",
|
||||
|
||||
@@ -242,6 +242,10 @@ export function instanceOfDynamicEnablementInfo(obj: any): obj is DynamicEnablem
|
||||
return (<DynamicEnablementInfo>obj)?.target !== undefined && (<DynamicEnablementInfo>obj)?.value !== undefined;
|
||||
}
|
||||
|
||||
export function instanceOfDynamicOptionsInfo(obj: any): obj is DynamicOptionsInfo {
|
||||
return (<DynamicOptionsInfo>obj)?.target !== undefined && (<DynamicOptionsInfo>obj)?.alternates !== undefined;
|
||||
}
|
||||
|
||||
export interface DialogInfoBase {
|
||||
title: string;
|
||||
name: string;
|
||||
@@ -290,6 +294,17 @@ export interface DynamicEnablementInfo {
|
||||
value: string
|
||||
}
|
||||
|
||||
export interface DynamicOptionsInfo {
|
||||
target: string,
|
||||
alternates: DynamicOptionsAlternates[]
|
||||
}
|
||||
|
||||
export interface DynamicOptionsAlternates {
|
||||
selection: string,
|
||||
alternateValues: string[],
|
||||
defaultValue: string
|
||||
}
|
||||
|
||||
export interface ValueProviderInfo {
|
||||
providerId: string,
|
||||
triggerField: string
|
||||
@@ -340,6 +355,7 @@ export interface FieldInfo extends SubFieldInfo, FieldInfoBase {
|
||||
links?: azdata.LinkArea[];
|
||||
editable?: boolean; // for editable drop-down,
|
||||
enabled?: boolean | DynamicEnablementInfo;
|
||||
dynamicOptions?: DynamicOptionsInfo;
|
||||
isEvaluated?: boolean;
|
||||
validations?: ValidationInfo[];
|
||||
valueProvider?: ValueProviderInfo;
|
||||
|
||||
@@ -11,7 +11,7 @@ import { IOptionsSourceProvider } from 'resource-deployment';
|
||||
import * as vscode from 'vscode';
|
||||
import * as nls from 'vscode-nls';
|
||||
import { getDateTimeString, getErrorMessage, isUserCancelledError, throwUnless } from '../common/utils';
|
||||
import { AzureAccountFieldInfo, AzureLocationsFieldInfo, ComponentCSSStyles, DialogInfoBase, FieldInfo, FieldType, FilePickerFieldInfo, InitialVariableValues, instanceOfDynamicEnablementInfo, IOptionsSource, KubeClusterContextFieldInfo, LabelPosition, NoteBookEnvironmentVariablePrefix, OptionsInfo, OptionsType, PageInfoBase, RowInfo, SectionInfo, TextCSSStyles } from '../interfaces';
|
||||
import { AzureAccountFieldInfo, AzureLocationsFieldInfo, ComponentCSSStyles, DialogInfoBase, FieldInfo, FieldType, FilePickerFieldInfo, InitialVariableValues, instanceOfDynamicEnablementInfo, instanceOfDynamicOptionsInfo, IOptionsSource, KubeClusterContextFieldInfo, LabelPosition, NoteBookEnvironmentVariablePrefix, OptionsInfo, OptionsType, PageInfoBase, RowInfo, SectionInfo, TextCSSStyles } from '../interfaces';
|
||||
import * as loc from '../localizedConstants';
|
||||
import { apiService } from '../services/apiService';
|
||||
import { valueProviderService } from '../services/valueProviderService';
|
||||
@@ -41,6 +41,7 @@ export type InputComponentInfo<T extends InputComponent> = {
|
||||
getValue: () => Promise<InputValueType>;
|
||||
setValue: (value: InputValueType) => void;
|
||||
getDisplayValue?: () => Promise<string>;
|
||||
setOptions?: (options: OptionsInfo) => void;
|
||||
onValueChanged: vscode.Event<void>;
|
||||
isPassword?: boolean
|
||||
};
|
||||
@@ -341,6 +342,7 @@ export function initializeWizardPage(context: WizardPageContext): void {
|
||||
});
|
||||
}));
|
||||
await hookUpDynamicEnablement(context);
|
||||
await hookUpDynamicOptions(context);
|
||||
await hookUpValueProviders(context);
|
||||
const formBuilder = view.modelBuilder.formContainer().withFormItems(
|
||||
sections.map(section => { return { title: '', component: section }; }),
|
||||
@@ -408,6 +410,58 @@ async function hookUpDynamicEnablement(context: WizardPageContext): Promise<void
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Hooks up the dynamic options for fields which use that. This will attach a listener to the target component
|
||||
* for when the value changes and update the options of the source component based on the current value
|
||||
* of the target component.
|
||||
*
|
||||
* Note that currently this is only supported for Notebook Wizard Pages and only supports direct equals comparison
|
||||
* for the value currently selected.
|
||||
*
|
||||
* Additionally this only supports hooking up components that are on the same page.
|
||||
* @param context The page context
|
||||
*/
|
||||
async function hookUpDynamicOptions(context: WizardPageContext): Promise<void> {
|
||||
await Promise.all(context.pageInfo.sections.map(async section => {
|
||||
if (!section.fields) {
|
||||
return;
|
||||
}
|
||||
await Promise.all(section.fields.map(async field => {
|
||||
if (instanceOfDynamicOptionsInfo(field.dynamicOptions)) {
|
||||
const fieldKey = field.variableName || field.label;
|
||||
const fieldComponent = context.inputComponents[fieldKey];
|
||||
const targetComponent = context.inputComponents[field.dynamicOptions.target];
|
||||
if (!targetComponent) {
|
||||
console.error(`Could not find target component ${field.dynamicOptions.target} when hooking up dynamic options for ${field.label}`);
|
||||
return;
|
||||
}
|
||||
const updateOptions = async () => {
|
||||
const currentValue = await targetComponent.getValue();
|
||||
if (field.dynamicOptions && field.options && fieldComponent && fieldComponent.setOptions) {
|
||||
const targetValueFound = field.dynamicOptions.alternates.find(item => item.selection === currentValue);
|
||||
if (targetValueFound) {
|
||||
fieldComponent.setOptions(<OptionsInfo>{
|
||||
values: targetValueFound.alternateValues,
|
||||
defaultValue: targetValueFound.defaultValue
|
||||
});
|
||||
} else {
|
||||
fieldComponent.setOptions(<OptionsInfo>{
|
||||
values: field.options.values,
|
||||
defaultValue: (<OptionsInfo>field.options).defaultValue
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
targetComponent.onValueChanged(() => {
|
||||
updateOptions();
|
||||
});
|
||||
await updateOptions();
|
||||
}
|
||||
}));
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
async function hookUpValueProviders(context: WizardPageContext): Promise<void> {
|
||||
await Promise.all(context.pageInfo.sections.map(async section => {
|
||||
if (!section.fields) {
|
||||
@@ -861,11 +915,16 @@ async function substituteVariableValues(inputComponents: InputComponents, inputV
|
||||
);
|
||||
return inputValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a label on the left and a checkbox with an empty string label on the right, for use under page sections.
|
||||
* @param context The context to use to create the field
|
||||
*/
|
||||
function processCheckboxField(context: FieldContext): void {
|
||||
const checkbox = createCheckboxInputInfo(context.view, { initialValue: context.fieldInfo.defaultValue! === 'true', label: context.fieldInfo.label, required: context.fieldInfo.required });
|
||||
context.components.push(checkbox.component);
|
||||
const label = createLabel(context.view, { text: context.fieldInfo.label, description: context.fieldInfo.description, required: context.fieldInfo.required, width: context.fieldInfo.labelWidth, cssStyles: context.fieldInfo.labelCSSStyles });
|
||||
const checkbox = createCheckboxInputInfo(context.view, { initialValue: context.fieldInfo.defaultValue! === 'true', label: '', required: context.fieldInfo.required });
|
||||
checkbox.labelComponent = label;
|
||||
context.onNewInputComponentCreated(context.fieldInfo.variableName || context.fieldInfo.label, checkbox);
|
||||
addLabelInputPairToContainer(context.view, context.components, label, checkbox.component, context.fieldInfo);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1024,7 +1083,8 @@ async function createRadioOptions(context: FieldContext, getRadioButtonInfo?: ((
|
||||
component: radioGroupLoadingComponentBuilder,
|
||||
labelComponent: label,
|
||||
getValue: async (): Promise<InputValueType> => radioGroupLoadingComponentBuilder.value,
|
||||
setValue: (value: InputValueType) => { throw new Error('Setting value of radio group isn\'t currently supported'); },
|
||||
setValue: (_value: InputValueType) => { throw new Error('Setting value of radio group isn\'t currently supported'); },
|
||||
setOptions: (optionsInfo: OptionsInfo) => { radioGroupLoadingComponentBuilder.loadOptions(optionsInfo); },
|
||||
getDisplayValue: async (): Promise<string> => radioGroupLoadingComponentBuilder.displayValue,
|
||||
onValueChanged: radioGroupLoadingComponentBuilder.onValueChanged,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user