diff --git a/src/aaz_dev/command/api/editor.py b/src/aaz_dev/command/api/editor.py index 04d889a1..5b3f54ba 100644 --- a/src/aaz_dev/command/api/editor.py +++ b/src/aaz_dev/command/api/editor.py @@ -253,6 +253,13 @@ def editor_workspace_command_argument(name, node_names, leaf_name, arg_var): return jsonify(result) +# TODO: support to modify element of array or dict arguments +# @bp.route("/Workspaces//CommandTree/Nodes//Leaves//Arguments//Element", +# methods=("GET", "PATCH")) +# def editor_workspace_command_arg_element_of_array_or_dict(name, node_names, leaf_name, arg_var): +# pass + + @bp.route("/Workspaces//CommandTree/Nodes//Leaves//Arguments//Flatten", methods=("POST", )) def editor_workspace_command_argument_flatten(name, node_names, leaf_name, arg_var): diff --git a/src/aaz_dev/command/controller/workspace_cfg_editor.py b/src/aaz_dev/command/controller/workspace_cfg_editor.py index 0f67ee85..cd3b1032 100644 --- a/src/aaz_dev/command/controller/workspace_cfg_editor.py +++ b/src/aaz_dev/command/controller/workspace_cfg_editor.py @@ -3,9 +3,9 @@ import os from command.model.configuration import CMDConfiguration, CMDHttpOperation, CMDDiffLevelEnum, \ - CMDHttpRequest, CMDArgGroup, CMDObjectArg, CMDArrayArg, CMDArg, CMDClsArg, \ + CMDHttpRequest, CMDArgGroup, CMDObjectArg, CMDArrayArg, CMDArg, CMDBooleanArg, CMDClsArg, \ CMDObjectArgBase, CMDArrayArgBase, CMDCondition, CMDConditionNotOperator, CMDConditionHasValueOperator, \ - CMDConditionAndOperator, CMDCommandGroup, CMDArgumentHelp + CMDConditionAndOperator, CMDCommandGroup, CMDArgumentHelp, CMDArgDefault from utils import exceptions from utils.base64 import b64encode_str from utils.case import to_camel_case @@ -232,11 +232,14 @@ def update_arg_by_var(self, *cmd_names, arg_var, **kwargs): return None if isinstance(arg, CMDArg): self._update_cmd_arg(arg, **kwargs) + if isinstance(arg, CMDBooleanArg): + self._update_boolean_arg(arg, **kwargs) if isinstance(arg, CMDClsArg): self._update_cls_arg(arg, **kwargs) if isinstance(arg, CMDArrayArg): self._update_array_arg(arg, **kwargs) - return arg + + self.reformat() def _update_cmd_arg(self, arg, **kwargs): if 'options' in kwargs: @@ -251,6 +254,15 @@ def _update_cmd_arg(self, arg, **kwargs): arg.group = kwargs['group'] or None if 'help' in kwargs: arg.help = CMDArgumentHelp(kwargs['help']) + if 'default' in kwargs: + if kwargs['default'] is None: + arg.default = None + else: + arg.default = CMDArgDefault(kwargs['default']) + + def _update_boolean_arg(self, arg, **kwargs): + if 'reverse' in kwargs: + arg.reverse = kwargs['reverse'] or False def _update_cls_arg(self, arg, **kwargs): if 'singularOptions' in kwargs: diff --git a/src/aaz_dev/command/model/configuration/_arg.py b/src/aaz_dev/command/model/configuration/_arg.py index 126a8e7b..25fa942e 100644 --- a/src/aaz_dev/command/model/configuration/_arg.py +++ b/src/aaz_dev/command/model/configuration/_arg.py @@ -307,10 +307,20 @@ def _reformat_base(self, **kwargs): super()._reformat_base(**kwargs) if self.enum: self.enum.reformat(**kwargs) + if self.blank: + if not isinstance(self.blank.value, str) and not (self.nullable and self.blank.value is None): + raise exceptions.VerificationError( + f"Invalid blank value", details=f"'{self.blank.value}' is not a valid string arg value") class CMDStringArg(CMDStringArgBase, CMDArg): - pass + + def _reformat(self, **kwargs): + super()._reformat(**kwargs) + if self.default: + if not isinstance(self.default.value, str) and not (self.nullable and self.default.value is None): + raise exceptions.VerificationError( + f"Invalid default value", details=f"'{self.default.value}' is not a valid string arg value") # byte: base64 encoded characters @@ -421,7 +431,6 @@ class CMDResourceLocationArgBase(CMDStringArgBase): TYPE_VALUE = "ResourceLocation" no_rg_default = CMDBooleanField() # the default value will not be the location of resource group - # TODO: support global as default @classmethod def build_arg_base(cls, builder): @@ -465,10 +474,20 @@ def _reformat_base(self, **kwargs): super()._reformat_base(**kwargs) if self.enum: self.enum.reformat(**kwargs) + if self.blank: + if not isinstance(self.blank.value, int) and not (self.nullable and self.blank.value is None): + raise exceptions.VerificationError( + f"Invalid blank value", details=f"'{self.blank.value}' is not a valid int arg value") class CMDIntegerArg(CMDIntegerArgBase, CMDArg): - pass + + def _reformat(self, **kwargs): + super()._reformat(**kwargs) + if self.default: + if not isinstance(self.default.value, int) and not (self.nullable and self.default.value is None): + raise exceptions.VerificationError( + f"Invalid default value", details=f"'{self.default.value}' is not a valid int arg value") # integer32 @@ -504,7 +523,13 @@ def build_arg_base(cls, builder): class CMDBooleanArg(CMDBooleanArgBase, CMDArg): - pass + + def _reformat(self, **kwargs): + super()._reformat(**kwargs) + if self.default: + if not isinstance(self.default.value, bool) and not (self.nullable and self.default.value is None): + raise exceptions.VerificationError( + f"Invalid default value", details=f"'{self.default.value}' is not a valid boolean arg value") # float @@ -530,10 +555,20 @@ def _reformat_base(self, **kwargs): super()._reformat_base(**kwargs) if self.enum: self.enum.reformat(**kwargs) + if self.blank: + if not isinstance(self.blank.value, float) and not (self.nullable and self.blank.value is None): + raise exceptions.VerificationError( + f"Invalid blank value", details=f"'{self.blank.value}' is not a valid float arg value") class CMDFloatArg(CMDFloatArgBase, CMDArg): - pass + + def _reformat(self, **kwargs): + super()._reformat(**kwargs) + if self.default: + if not isinstance(self.default.value, float) and not (self.nullable and self.default.value is None): + raise exceptions.VerificationError( + f"Invalid default value", details=f"'{self.default.value}' is not a valid float arg value") # float32 @@ -645,6 +680,11 @@ def _reformat_base(self, **kwargs): if self.additional_props: self.additional_props.reformat(**kwargs) + if self.blank: + if not isinstance(self.blank.value, dict) and not (self.nullable and self.blank.value is None): + raise exceptions.VerificationError( + f"Invalid blank value", details=f"'{self.blank.value}' is not a valid object arg value") + class CMDObjectArg(CMDObjectArgBase, CMDArg): @@ -654,6 +694,13 @@ def build_arg(cls, builder): assert isinstance(arg, CMDObjectArg) return arg + def _reformat(self, **kwargs): + super()._reformat(**kwargs) + if self.default: + if not isinstance(self.default.value, dict) and not (self.nullable and self.default.value is None): + raise exceptions.VerificationError( + f"Invalid default value", details=f"'{self.default.value}' is not a valid object arg value") + # array class CMDArrayArgBase(CMDArgBase): @@ -693,6 +740,11 @@ def _reformat_base(self, **kwargs): } raise err + if self.blank: + if not isinstance(self.blank.value, list) and not (self.nullable and self.blank.value is None): + raise exceptions.VerificationError( + f"Invalid blank value", details=f"'{self.blank.value}' is not a valid array arg value") + class CMDArrayArg(CMDArrayArgBase, CMDArg): @@ -713,3 +765,7 @@ def _reformat(self, **kwargs): super()._reformat(**kwargs) if self.singular_options: self.singular_options = sorted(self.singular_options, key=lambda op: (len(op), op)) + if self.default: + if not isinstance(self.default.value, list) and not (self.nullable and self.default.value is None): + raise exceptions.VerificationError( + f"Invalid default value", details=f"'{self.default.value}' is not a valid array arg value") diff --git a/src/web/src/views/workspace/WSEditorCommandArgumentsContent.tsx b/src/web/src/views/workspace/WSEditorCommandArgumentsContent.tsx index b6a6f8bd..283f9745 100644 --- a/src/web/src/views/workspace/WSEditorCommandArgumentsContent.tsx +++ b/src/web/src/views/workspace/WSEditorCommandArgumentsContent.tsx @@ -499,9 +499,22 @@ function ArgumentReviewer(props: { {props.arg.required && [Required]} - {choices.length > 0 && - {`Choices: ` + choices.join(', ')} - } + {(props.arg.default !== undefined || choices.length > 0) && + {choices.length > 0 && + {`Choices: ` + choices.join(', ')} + } + {props.arg.default !== undefined && + {`Default: ${props.arg.default.value}`} + + } + } {props.arg.help?.short && {props.arg.help?.short} } {!(props.arg.help?.short) && Please add argument short summary!} {props.arg.help?.lines && @@ -530,6 +543,8 @@ function ArgumentDialog(props: { const [argSimilarTree, setArgSimilarTree] = useState(undefined); const [argSimilarTreeExpandedIds, setArgSimilarTreeExpandedIds] = useState([]); const [argSimilarTreeArgIdsUpdated, setArgSimilarTreeArgIdsUpdated] = useState([]); + const [hasDefault, setHasDefault] = useState(false); + const [defaultValue, setDefaultValue] = useState(undefined); const handleClose = () => { setInvalidText(undefined); @@ -580,6 +595,30 @@ function ArgumentDialog(props: { lines = lHelp.split('\n').filter(l => l.length > 0); } + let argDefault = undefined; + if (hasDefault === false) { + if (props.arg.default !== undefined) { + argDefault = null; + } + } else if (hasDefault === true) { + if (defaultValue === undefined) { + setInvalidText(`Field 'Default Value' is undefined.`) + return undefined; + } else { + try { + argDefault = { + value: convertArgDefaultText(defaultValue!, props.arg), + } + } catch (err: any) { + setInvalidText(`Field 'Default Value' is invalid: ${err.message}.`) + return undefined; + } + if (props.arg.default !== undefined && props.arg.default.value === argDefault.value) { + argDefault = undefined; + } + } + } + return { options: names, singularOptions: sNames, @@ -589,7 +628,8 @@ function ArgumentDialog(props: { help: { short: sHelp, lines: lines - } + }, + default: argDefault, } } @@ -717,6 +757,19 @@ function ArgumentDialog(props: { setUpdating(false); setArgSimilarTree(undefined); setArgSimilarTreeExpandedIds([]); + + if (arg.type === "object" || arg.type.startsWith("dict<") || arg.type.startsWith("array<") || arg.type.startsWith("@")) { + // disable the default value modification + setHasDefault(undefined); + setDefaultValue(undefined); + } else if (props.arg.default !== undefined && props.arg.default !== null) { + setHasDefault(true); + setDefaultValue(props.arg.default.value.toString()); + } else { + setHasDefault(false); + setDefaultValue(undefined); + } + }, [props.arg]); return ( @@ -789,6 +842,40 @@ function ArgumentDialog(props: { }} /> } + {hasDefault !== undefined && <> + Default Value + + { + setHasDefault(!hasDefault); + setDefaultValue(undefined); + }} + /> + + } + { props.onSelectSubArg(arg.var) }}> - {!arg.hide && {argOptionsString} } + {!arg.hide && {argOptionsString}} {arg.hide && {argOptionsString}} @@ -1329,6 +1416,8 @@ type CMDArgEnum = { interface CMDArgBase { type: string + nullable: boolean + blank?: CMDArgBlank } interface CMDArg extends CMDArgBase { @@ -1342,10 +1431,13 @@ interface CMDArg extends CMDArgBase { help?: CMDArgHelp default?: CMDArgDefault - blank?: CMDArgBlank idPart?: string } +interface CMDArgBaseT extends CMDArgBase { + blank?: CMDArgBlank +} + interface CMDArgT extends CMDArg { default?: CMDArgDefault blank?: CMDArgBlank @@ -1361,7 +1453,7 @@ interface CMDClsArg extends CMDClsArgBase, CMDArg { } // type: string -interface CMDStringArgBase extends CMDArgBase { +interface CMDStringArgBase extends CMDArgBaseT { enum?: CMDArgEnum // fmt?: CMDStringFormat } @@ -1413,7 +1505,7 @@ interface CMDResourceLocationNameArgBase extends CMDStringArgBase { } interface CMDResourceLocationNameArg extends CMDResourceLocationNameArgBase, CMDStringArg { } -interface CMDNumberArgBase extends CMDArgBase { +interface CMDNumberArgBase extends CMDArgBaseT { enum?: CMDArgEnum // fmt?: CMDIntegerFormat } @@ -1444,7 +1536,7 @@ interface CMDFloat64ArgBase extends CMDNumberArgBase { } interface CMDFloat64Arg extends CMDFloat64ArgBase, CMDNumberArg { } // type: boolean -interface CMDBooleanArgBase extends CMDArgBase { } +interface CMDBooleanArgBase extends CMDArgBaseT { } interface CMDBooleanArg extends CMDBooleanArgBase, CMDArgT { } // type: object @@ -1490,9 +1582,30 @@ function decodeArgEnum(response: any): CMDArgEnum { return argEnum; } +function decodeArgBlank(response: any | undefined): CMDArgBlank | undefined { + if (response === undefined || response === null) { + return undefined; + } + + return { + value: response.value as (T | null), + } +} + +function decodeArgDefault(response: any | undefined): CMDArgDefault | undefined { + if (response === undefined || response === null) { + return undefined; + } + + return { + value: response.value as (T | null), + } +} + function decodeArgBase(response: any): { argBase: CMDArgBase, clsDefineMap: ClsArgDefinitionMap } { let argBase: any = { - type: response.type + type: response.type, + nullable: (response.nullable ?? false) as boolean, } let clsDefineMap: ClsArgDefinitionMap = {}; @@ -1516,6 +1629,12 @@ function decodeArgBase(response: any): { argBase: CMDArgBase, clsDefineMap: ClsA enum: decodeArgEnum(response.enum), } } + if (response.blank) { + argBase = { + ...argBase, + blank: decodeArgBlank(response.blank), + } + } break case "integer32": case "integer64": @@ -1526,6 +1645,12 @@ function decodeArgBase(response: any): { argBase: CMDArgBase, clsDefineMap: ClsA enum: decodeArgEnum(response.enum), } } + if (response.blank) { + argBase = { + ...argBase, + blank: decodeArgBlank(response.blank), + } + } break case "float32": case "float64": @@ -1536,8 +1661,20 @@ function decodeArgBase(response: any): { argBase: CMDArgBase, clsDefineMap: ClsA enum: decodeArgEnum(response.enum), } } + if (response.blank) { + argBase = { + ...argBase, + blank: decodeArgBlank(response.blank), + } + } break case "boolean": + if (response.blank) { + argBase = { + ...argBase, + blank: decodeArgBlank(response.blank), + } + } break case "object": if (response.args && Array.isArray(response.args) && response.args.length > 0) { @@ -1637,8 +1774,6 @@ function decodeArg(response: any): { arg: CMDArg, clsDefineMap: ClsArgDefinition hide: (response.hide ?? false) as boolean, group: (response.group ?? "") as string, help: help, - default: response.default, - blank: response.blank, idPart: response.idPart, } @@ -1655,16 +1790,40 @@ function decodeArg(response: any): { arg: CMDArg, clsDefineMap: ClsArgDefinition case "ResourceId": case "ResourceLocation": case "string": + if (response.default) { + arg = { + ...arg, + default: decodeArgDefault(response.default), + } + } break case "integer32": case "integer64": case "integer": + if (response.default) { + arg = { + ...arg, + default: decodeArgDefault(response.default), + } + } break case "float32": case "float64": case "float": + if (response.default) { + arg = { + ...arg, + default: decodeArgDefault(response.default), + } + } break case "boolean": + if (response.default) { + arg = { + ...arg, + default: decodeArgDefault(response.default), + } + } break case "object": break @@ -1698,6 +1857,54 @@ function decodeArg(response: any): { arg: CMDArg, clsDefineMap: ClsArgDefinition } } +function convertArgDefaultText(defaultText: string, argBase: CMDArgBase): any { + switch (argBase.type) { + case "byte": + case "binary": + case "duration": + case "date": + case "dateTime": + case "uuid": + case "password": + case "SubscriptionId": + case "ResourceGroupName": + case "ResourceId": + case "ResourceLocation": + case "string": + if (defaultText.trim().length === 0) { + throw Error(`Not supported empty value: '${defaultText}'`); + } + return defaultText.trim(); + case "integer32": + case "integer64": + case "integer": + if (Number.isNaN(parseInt(defaultText.trim()))) { + throw Error(`Not supported default value for integer type: '${defaultText}'`) + } + return parseInt(defaultText.trim()); + case "float32": + case "float64": + case "float": + if (Number.isNaN(parseFloat(defaultText.trim()))) { + throw Error(`Not supported default value for float type: '${defaultText}'`) + } + return parseFloat(defaultText.trim()); + case "boolean": + switch (defaultText.trim().toLowerCase()) { + case 'true': + case 'yes': + return true; + case 'false': + case 'no': + return false; + default: + throw Error(`Not supported default value for boolean type: '${defaultText}'`) + } + default: + throw Error(`Not supported type: ${argBase.type}`) + } +} + function decodeResponse(responseCommand: any): { args: CMDArg[], clsDefineMap: ClsArgDefinitionMap } { let clsDefineMap: ClsArgDefinitionMap = {}; const args: CMDArg[] = [];