function ArgumentDialog()

in src/web/src/views/workspace/WSEditorCommandArgumentsContent.tsx [748:1385]


function ArgumentDialog(props: {
  commandUrl: string;
  arg: CMDArg;
  clsArgDefineMap: ClsArgDefinitionMap;
  open: boolean;
  onClose: (updated: boolean) => Promise<void>;
}) {
  const [updating, setUpdating] = useState<boolean>(false);
  const [stage, setStage] = useState<string>("");
  const [invalidText, setInvalidText] = useState<string | undefined>(undefined);
  const [options, setOptions] = useState<string>("");
  const [singularOptions, setSingularOptions] = useState<string | undefined>(undefined);
  const [group, setGroup] = useState<string>("");
  const [hide, setHide] = useState<boolean>(false);
  const [supportEnumExtension, setSupportEnumExtension] = useState<boolean>(false);
  const [shortHelp, setShortHelp] = useState<string>("");
  const [longHelp, setLongHelp] = useState<string>("");
  const [argSimilarTree, setArgSimilarTree] = useState<ArgSimilarTree | undefined>(undefined);
  const [argSimilarTreeExpandedIds, setArgSimilarTreeExpandedIds] = useState<string[]>([]);
  const [argSimilarTreeArgIdsUpdated, setArgSimilarTreeArgIdsUpdated] = useState<string[]>([]);
  const [hasDefault, setHasDefault] = useState<boolean | undefined>(false);
  const [defaultValue, setDefaultValue] = useState<any | undefined>(undefined);
  const [defaultValueInJson, setDefaultValueInJson] = useState<boolean>(false);
  const [hasPrompt, setHasPrompt] = useState<boolean | undefined>(false);
  const [promptMsg, setPromptMsg] = useState<string | undefined>(undefined);
  const [promptConfirm, setPromptConfirm] = useState<boolean | undefined>(undefined);
  const [configurationKey, setConfigurationKey] = useState<string>("");
  const [isClientArg, setIsClientArg] = useState<boolean>(false);

  const handleClose = () => {
    setInvalidText(undefined);
    props.onClose(false);
  };

  const verifyModification = () => {
    setInvalidText(undefined);
    const name = options.trim();
    const sName = singularOptions?.trim() ?? undefined;
    const sHelp = shortHelp.trim();
    const lHelp = longHelp.trim();
    const gName = group.trim();
    const cfgKey = configurationKey.trim();

    const names = name.split(" ").filter((n) => n.length > 0);
    const sNames = sName?.split(" ").filter((n) => n.length > 0) ?? undefined;

    if (names.length < 1) {
      setInvalidText(`Argument 'Option names' is required.`);
      return undefined;
    }

    for (const idx in names) {
      const piece = names[idx];
      if (!/^[a-z0-9]+(-[a-z0-9]+)*$/.test(piece)) {
        setInvalidText(`Invalid 'Option name': '${piece}'. Supported regular expression is: [a-z0-9]+(-[a-z0-9]+)* `);
        return undefined;
      }
    }

    if (sNames) {
      for (const idx in sNames) {
        const piece = sNames[idx];
        if (!/^[a-z0-9]+(-[a-z0-9]+)*$/.test(piece)) {
          setInvalidText(
            `Invalid 'Singular option name': '${piece}'. Supported regular expression is: [a-z0-9]+(-[a-z0-9]+)* `,
          );
          return undefined;
        }
      }
    }

    if (
      sHelp.length < 1 &&
      names.find((n) => {
        return n === "subscription" || n === "resource-group";
      }) === undefined
    ) {
      setInvalidText(`Field 'Short Summary' is required.`);
      return undefined;
    }

    let lines: string[] | null = null;
    if (lHelp.length > 1) {
      lines = lHelp.split("\n").filter((l) => l.length > 0);
    }

    let argCfgKey: string | null = null;
    if (cfgKey.length > 0) {
      argCfgKey = cfgKey;
    }

    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 {
          let argType = props.arg.type;
          if (argType.startsWith("@")) {
            argType = props.clsArgDefineMap[(props.arg as CMDClsArg).clsName].type;
          }
          argDefault = {
            value: convertArgDefaultText(defaultValue!, argType),
          };
        } 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;
        }
      }
    }

    let argPrompt = undefined;
    if (hasPrompt === false) {
      if (props.arg.prompt !== undefined) {
        argPrompt = null;
      }
    } else if (hasPrompt === true) {
      if (promptMsg === undefined) {
        setInvalidText(`Field 'Prompt Message' is undefined.`);
        return undefined;
      } else {
        const msg = promptMsg.trim();
        if (msg.length < 1) {
          setInvalidText(`Field 'Prompt Message' is empty.`);
          return undefined;
        }
        if (!msg.endsWith(":")) {
          setInvalidText(`Field 'Prompt Message' must end with a colon.`);
          return undefined;
        }
        argPrompt = {
          msg: msg,
          confirm: promptConfirm,
        };
      }
    }

    return {
      options: names,
      singularOptions: sNames,
      stage: stage,
      group: gName,
      hide: hide,
      help: {
        short: sHelp,
        lines: lines,
      },
      default: argDefault,
      prompt: argPrompt,
      configurationKey: argCfgKey,
      supportEnumExtension: supportEnumExtension,
    };
  };

  const handleModify = async () => {
    const data = verifyModification();
    if (data === undefined) {
      return;
    }

    setUpdating(true);

    const argumentUrl = `${props.commandUrl}/Arguments/${props.arg.var}`;

    try {
      await axios.patch(argumentUrl, {
        ...data,
      });
      setUpdating(false);
      await props.onClose(true);
    } catch (err: any) {
      console.error(err);
      if (err.response?.data?.message) {
        const data = err.response!.data!;
        setInvalidText(`ResponseError: ${data.message!}: ${JSON.stringify(data.details)}`);
      }
      setUpdating(false);
    }
  };

  const handleDisplaySimilar = () => {
    if (verifyModification() === undefined) {
      return;
    }

    setUpdating(true);

    const similarUrl = `${props.commandUrl}/Arguments/${props.arg.var}/FindSimilar`;
    axios
      .post(similarUrl)
      .then((res) => {
        setUpdating(false);
        const { tree, expandedIds } = BuildArgSimilarTree(res);
        setArgSimilarTree(tree);
        setArgSimilarTreeExpandedIds(expandedIds);
        setArgSimilarTreeArgIdsUpdated([]);
      })
      .catch((err) => {
        console.error(err);
        if (err.response?.data?.message) {
          const data = err.response!.data!;
          setInvalidText(`ResponseError: ${data.message!}: ${JSON.stringify(data.details)}`);
        }
        setUpdating(false);
      });
  };

  const handleDisableSimilar = () => {
    setArgSimilarTree(undefined);
    setArgSimilarTreeExpandedIds([]);
  };

  const onSimilarTreeUpdated = (newTree: ArgSimilarTree) => {
    setArgSimilarTree(newTree);
  };

  const onSimilarTreeExpandedIdsUpdated = (expandedIds: string[]) => {
    setArgSimilarTreeExpandedIds(expandedIds);
  };

  const handleModifySimilar = async () => {
    const data = verifyModification();
    if (data === undefined) {
      return;
    }

    setUpdating(true);
    let invalidText = "";
    const updatedIds: string[] = [...argSimilarTreeArgIdsUpdated];
    for (const idx in argSimilarTree!.selectedArgIds) {
      const argId = argSimilarTree!.selectedArgIds[idx];
      if (updatedIds.indexOf(argId) === -1) {
        try {
          await axios.patch(argId, {
            ...data,
          });
          updatedIds.push(argId);
          setArgSimilarTreeArgIdsUpdated([...updatedIds]);
        } catch (err: any) {
          console.error(err);
          if (err.response?.data?.message) {
            const data = err.response!.data!;
            invalidText += `ResponseError: ${data.message!}: ${JSON.stringify(data.details)}`;
          }
        }
      }
    }

    if (invalidText.length > 0) {
      setInvalidText(invalidText);
      setUpdating(false);
    } else {
      setUpdating(false);
      await props.onClose(true);
    }
  };

  useEffect(() => {
    const { arg, clsArgDefineMap } = props;
    setIsClientArg(arg.var.startsWith("$Client."));

    setOptions(arg.options.join(" "));
    if (arg.type.startsWith("array")) {
      setSingularOptions((arg as CMDArrayArg).singularOptions?.join(" ") ?? "");
    } else if (arg.type.startsWith("@") && clsArgDefineMap[(arg as CMDClsArg).clsName].type.startsWith("array")) {
      setSingularOptions((arg as CMDClsArg).singularOptions?.join(" ") ?? "");
    } else {
      setSingularOptions(undefined);
    }

    if (
      arg.type === "object" ||
      arg.type.startsWith("dict<") ||
      arg.type.startsWith("array<") ||
      arg.type.startsWith("@")
    ) {
      setHasPrompt(undefined);
    } else {
      setHasPrompt(arg.prompt !== undefined);
      if (arg.prompt !== undefined) {
        setPromptMsg(arg.prompt.msg);
        setPromptConfirm(undefined);
      }
    }
    if (arg.type === "password") {
      setPromptConfirm((arg as CMDPasswordArg).prompt?.confirm ?? false);
    }
    setStage(props.arg.stage);
    setGroup(props.arg.group);
    setHide(props.arg.hide);
    setSupportEnumExtension(props.arg.supportEnumExtension || false);
    setShortHelp(props.arg.help?.short ?? "");
    setLongHelp(props.arg.help?.lines?.join("\n") ?? "");
    setConfigurationKey(props.arg.configurationKey ?? "");
    setUpdating(false);
    setArgSimilarTree(undefined);
    setArgSimilarTreeExpandedIds([]);

    if (
      arg.type === "object" ||
      arg.type.startsWith("dict<") ||
      arg.type.startsWith("array<") ||
      arg.type.startsWith("@")
    ) {
      setDefaultValueInJson(true);
      if (props.arg.default !== undefined && props.arg.default !== null) {
        setHasDefault(true);
        setDefaultValue(JSON.stringify(props.arg.default.value));
      } else {
        setHasDefault(false);
        setDefaultValue(undefined);
      }
    } else {
      setDefaultValueInJson(false);
      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 (
    <Dialog disableEscapeKeyDown open={props.open} sx={{ "& .MuiDialog-paper": { width: "80%" } }}>
      {!argSimilarTree && (
        <>
          <DialogTitle>{isClientArg ? "Modify Client Argument" : "Modify Argument"}</DialogTitle>
          <DialogContent dividers={true}>
            {invalidText && (
              <Alert variant="filled" severity="error">
                {" "}
                {invalidText}{" "}
              </Alert>
            )}
            <TextField
              id="options"
              label="Option names"
              helperText="You can input multiple names separated by a space character"
              type="text"
              fullWidth
              variant="standard"
              value={options}
              onChange={(event: any) => {
                setOptions(event.target.value);
              }}
              margin="normal"
              required
            />
            {singularOptions !== undefined && (
              <TextField
                id="singularOptions"
                label="Singular option names"
                type="text"
                fullWidth
                variant="standard"
                value={singularOptions}
                onChange={(event: any) => {
                  setSingularOptions(event.target.value);
                }}
                margin="normal"
              />
            )}
            {!isClientArg && (
              <>
                <TextField
                  id="group"
                  label="Argument Group"
                  type="text"
                  fullWidth
                  variant="standard"
                  value={group}
                  onChange={(event: any) => {
                    setGroup(event.target.value);
                  }}
                  margin="normal"
                />
                <InputLabel required shrink sx={{ font: "inherit" }}>
                  Stage
                </InputLabel>
                <RadioGroup
                  row
                  value={stage}
                  name="stage"
                  onChange={(event: any) => {
                    setStage(event.target.value);
                  }}
                >
                  <FormControlLabel value="Stable" control={<Radio />} label="Stable" sx={{ ml: 4 }} />
                  <FormControlLabel value="Preview" control={<Radio />} label="Preview" sx={{ ml: 4 }} />
                  <FormControlLabel value="Experimental" control={<Radio />} label="Experimental" sx={{ ml: 4 }} />
                </RadioGroup>

                {!props.arg.required && (
                  <>
                    <InputLabel shrink sx={{ font: "inherit" }}>
                      Hide Argument
                    </InputLabel>
                    <Switch
                      sx={{ ml: 4 }}
                      checked={hide}
                      onChange={() => {
                        setHide(!hide);
                      }}
                    />
                  </>
                )}

                {props.arg.hasEnum && (
                  <>
                    <InputLabel shrink sx={{ font: "inherit" }}>
                      Support Enum Extension
                    </InputLabel>
                    <Switch
                      sx={{ ml: 4 }}
                      checked={supportEnumExtension}
                      onChange={() => {
                        setSupportEnumExtension(!supportEnumExtension);
                      }}
                    />
                  </>
                )}
              </>
            )}
            {hasDefault !== undefined && (
              <>
                <InputLabel shrink sx={{ font: "inherit" }}>
                  Default Value
                </InputLabel>
                <Box
                  sx={{
                    display: "flex",
                    flexDirection: "row",
                    alignItems: "center",
                    justifyContent: "flex-start",
                    ml: 4,
                  }}
                >
                  <Switch
                    checked={hasDefault}
                    onChange={() => {
                      setHasDefault(!hasDefault);
                      setDefaultValue(undefined);
                    }}
                  />
                  <TextField
                    id="defaultValue"
                    label="default Value"
                    hiddenLabel
                    type="text"
                    hidden={!hasDefault}
                    fullWidth
                    size="small"
                    value={defaultValue !== undefined ? defaultValue : ""}
                    onChange={(event: any) => {
                      setDefaultValue(event.target.value);
                    }}
                    placeholder={defaultValueInJson ? "Default Value in json format" : "Default Value"}
                    margin="normal"
                    aria-controls=""
                    required
                  />
                </Box>
              </>
            )}

            {hasPrompt !== undefined && (
              <>
                <InputLabel shrink sx={{ font: "inherit" }}>
                  Prompt Input
                </InputLabel>
                <Box
                  sx={{
                    display: "flex",
                    flexDirection: "row",
                    alignItems: "center",
                    justifyContent: "flex-start",
                    ml: 4,
                  }}
                >
                  <Switch
                    checked={hasPrompt}
                    onChange={() => {
                      setHasPrompt(!hasPrompt);
                      setPromptMsg(undefined);
                    }}
                  />
                  <Box
                    sx={{
                      display: "flex",
                      flexDirection: "column",
                      alignItems: "stretch",
                      justifyContent: "flex-start",
                      flexGrow: 1,
                    }}
                  >
                    <TextField
                      id="SetPromptMsg"
                      label="Prompt Message"
                      hiddenLabel
                      type="text"
                      hidden={!hasPrompt}
                      fullWidth
                      size="small"
                      value={promptMsg !== undefined ? promptMsg : ""}
                      onChange={(event: any) => {
                        setPromptMsg(event.target.value);
                      }}
                      placeholder="Please input the prompt hint end with a colon."
                      margin="normal"
                      aria-controls=""
                      required
                    />
                    {promptConfirm !== undefined && (
                      <>
                        <FormControlLabel
                          control={
                            <Checkbox
                              size="small"
                              checked={promptConfirm}
                              onChange={() => {
                                setPromptConfirm(!promptConfirm);
                              }}
                            />
                          }
                          label="Confirm input"
                        />
                      </>
                    )}
                  </Box>
                </Box>
              </>
            )}
            {!isClientArg && (
              <TextField
                id="configurationKey"
                label="Configuration Key"
                helperText="The key to retrieve the default value from cli Configuration"
                type="text"
                fullWidth
                variant="standard"
                value={configurationKey}
                onChange={(event: any) => {
                  setConfigurationKey(event.target.value);
                }}
                margin="normal"
              />
            )}

            <TextField
              id="shortSummary"
              label="Short Summary"
              type="text"
              fullWidth
              variant="standard"
              value={shortHelp}
              onChange={(event: any) => {
                setShortHelp(event.target.value);
              }}
              margin="normal"
              required
            />
            <TextField
              id="longSummary"
              label="Long Summary"
              helperText="Please add long summer in lines."
              type="text"
              fullWidth
              multiline
              variant="standard"
              value={longHelp}
              onChange={(event: any) => {
                setLongHelp(event.target.value);
              }}
              margin="normal"
            />
          </DialogContent>
        </>
      )}

      {argSimilarTree && (
        <>
          <DialogTitle>Modify Similar Arguments</DialogTitle>
          <DialogContent dividers={true}>
            {invalidText && (
              <Alert variant="filled" severity="error">
                {" "}
                {invalidText}{" "}
              </Alert>
            )}
            <WSECArgumentSimilarPicker
              tree={argSimilarTree}
              expandedIds={argSimilarTreeExpandedIds}
              updatedIds={argSimilarTreeArgIdsUpdated}
              onTreeUpdated={onSimilarTreeUpdated}
              onToggle={onSimilarTreeExpandedIdsUpdated}
            />
          </DialogContent>
        </>
      )}
      <DialogActions>
        {updating && (
          <Box sx={{ width: "100%" }}>
            <LinearProgress color="secondary" />
          </Box>
        )}
        {!updating && !argSimilarTree && (
          <>
            <Button onClick={handleClose}>Cancel</Button>
            {/* cls argument should flatten similar. Customer should unwrap cls argument before to modify it*/}
            {!props.arg.var.startsWith("@") && (
              <Button onClick={handleModify}>{isClientArg ? "Update Global" : "Update"}</Button>
            )}
            {/* TODO: support unwrap and update */}
            {!isClientArg && <Button onClick={handleDisplaySimilar}>Update Similar</Button>}
          </>
        )}
        {!updating && argSimilarTree && (
          <>
            <Button onClick={handleDisableSimilar}>Back</Button>
            <Button onClick={handleModifySimilar} disabled={argSimilarTree.selectedArgIds.length === 0}>
              Update
            </Button>
          </>
        )}
      </DialogActions>
    </Dialog>
  );
}