mirror of https://github.com/locustio/locust.git
Add option for extra options to be required
This commit is contained in:
parent
665f786cd0
commit
83fcdda19d
|
@ -10,6 +10,10 @@ def _(parser):
|
|||
parser.add_argument("--my-ui-invisible-argument", include_in_web_ui=False, default="I am invisible")
|
||||
# Set `is_secret` to True if you want the text input to be password masked in the web UI
|
||||
parser.add_argument("--my-ui-password-argument", is_secret=True, default="I am a secret")
|
||||
# Use a boolean default value if you want the input to be a checkmark
|
||||
parser.add_argument("--my-ui-boolean-argument", default=True)
|
||||
# Set `is_required` to mark a form field as required
|
||||
parser.add_argument("--my-ui-required-argument", is_required=True, default="I am required")
|
||||
|
||||
|
||||
@events.test_start.add_listener
|
||||
|
|
|
@ -53,6 +53,8 @@ def locust_init(environment, **_kwargs):
|
|||
{
|
||||
"label": "Username",
|
||||
"name": "username",
|
||||
# make field required
|
||||
"is_required": True,
|
||||
},
|
||||
# boolean checkmark field
|
||||
{"label": "Admin", "name": "is_admin", "default_value": False},
|
||||
|
|
|
@ -59,15 +59,18 @@ class LocustArgumentParser(configargparse.ArgumentParser):
|
|||
Arguments:
|
||||
include_in_web_ui: If True (default), the argument will show in the UI.
|
||||
is_secret: If True (default is False) and include_in_web_ui is True, the argument will show in the UI with a password masked text input.
|
||||
is_required: If True (default is False) and include_in_web_ui is True, the argument will show in the UI as a required form field.
|
||||
|
||||
Returns:
|
||||
argparse.Action: the new argparse action
|
||||
"""
|
||||
include_in_web_ui = kwargs.pop("include_in_web_ui", True)
|
||||
is_secret = kwargs.pop("is_secret", False)
|
||||
is_required = kwargs.pop("is_required", False)
|
||||
action = super().add_argument(*args, **kwargs)
|
||||
action.include_in_web_ui = include_in_web_ui
|
||||
action.is_secret = is_secret
|
||||
action.is_required = is_required
|
||||
return action
|
||||
|
||||
@property
|
||||
|
@ -82,6 +85,14 @@ class LocustArgumentParser(configargparse.ArgumentParser):
|
|||
if a.dest in self.args_included_in_web_ui and hasattr(a, "is_secret") and a.is_secret
|
||||
}
|
||||
|
||||
@property
|
||||
def required_args_included_in_web_ui(self) -> dict[str, configargparse.Action]:
|
||||
return {
|
||||
a.dest: a
|
||||
for a in self._actions
|
||||
if a.dest in self.args_included_in_web_ui and hasattr(a, "is_required") and a.is_required
|
||||
}
|
||||
|
||||
|
||||
class LocustTomlConfigParser(configargparse.TomlConfigParser):
|
||||
def parse(self, stream):
|
||||
|
@ -798,6 +809,7 @@ def default_args_dict() -> dict:
|
|||
class UIExtraArgOptions(NamedTuple):
|
||||
default_value: str
|
||||
is_secret: bool
|
||||
is_required: bool
|
||||
help_text: str
|
||||
choices: list[str] | None = None
|
||||
|
||||
|
@ -813,6 +825,7 @@ def ui_extra_args_dict(args=None) -> dict[str, dict[str, Any]]:
|
|||
k: UIExtraArgOptions(
|
||||
default_value=v,
|
||||
is_secret=k in parser.secret_args_included_in_web_ui,
|
||||
is_required=k in parser.required_args_included_in_web_ui,
|
||||
help_text=parser.args_included_in_web_ui[k].help,
|
||||
choices=parser.args_included_in_web_ui[k].choices,
|
||||
)._asdict()
|
||||
|
|
|
@ -373,6 +373,7 @@ class TestArgumentParser(LocustTestCase):
|
|||
parser.add_argument("--a1", help="a1 help")
|
||||
parser.add_argument("--a2", help="a2 help", include_in_web_ui=False)
|
||||
parser.add_argument("--a3", help="a3 help", is_secret=True)
|
||||
parser.add_argument("--a4", help="a3 help", is_required=True)
|
||||
|
||||
args = ["-u", "666", "--a1", "v1", "--a2", "v2", "--a3", "v3"]
|
||||
options = parse_options(args=args)
|
||||
|
@ -384,6 +385,7 @@ class TestArgumentParser(LocustTestCase):
|
|||
self.assertIn("a1", extra_args)
|
||||
self.assertNotIn("a2", extra_args)
|
||||
self.assertIn("a3", extra_args)
|
||||
self.assertIn("a4", extra_args)
|
||||
self.assertEqual("v1", extra_args["a1"]["default_value"])
|
||||
|
||||
|
||||
|
|
|
@ -60,6 +60,7 @@ class InputField(TypedDict, total=False):
|
|||
default_value: bool | None
|
||||
choices: list[str] | None
|
||||
is_secret: bool | None
|
||||
is_required: bool | None
|
||||
|
||||
|
||||
class CustomForm(TypedDict, total=False):
|
||||
|
|
|
@ -11,6 +11,7 @@ export default function CustomInput({
|
|||
defaultValue,
|
||||
choices,
|
||||
isSecret,
|
||||
isRequired,
|
||||
}: ICustomInput) {
|
||||
if (choices) {
|
||||
return (
|
||||
|
@ -19,6 +20,7 @@ export default function CustomInput({
|
|||
label={label}
|
||||
name={name}
|
||||
options={choices}
|
||||
required={isRequired}
|
||||
sx={{ width: '100%' }}
|
||||
/>
|
||||
);
|
||||
|
@ -27,7 +29,7 @@ export default function CustomInput({
|
|||
if (typeof defaultValue === 'boolean') {
|
||||
return (
|
||||
<FormControlLabel
|
||||
control={<Checkbox defaultChecked={defaultValue} />}
|
||||
control={<Checkbox defaultChecked={defaultValue} required={isRequired} />}
|
||||
label={<Markdown content={label} />}
|
||||
name={name}
|
||||
/>
|
||||
|
@ -35,7 +37,14 @@ export default function CustomInput({
|
|||
}
|
||||
|
||||
if (isSecret) {
|
||||
return <PasswordField defaultValue={defaultValue} label={label} name={name} />;
|
||||
return (
|
||||
<PasswordField
|
||||
defaultValue={defaultValue}
|
||||
isRequired={isRequired}
|
||||
label={label}
|
||||
name={name}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -43,6 +52,7 @@ export default function CustomInput({
|
|||
defaultValue={defaultValue}
|
||||
label={label}
|
||||
name={name}
|
||||
required={isRequired}
|
||||
sx={{ width: '100%' }}
|
||||
type='text'
|
||||
/>
|
||||
|
|
|
@ -8,7 +8,8 @@ export default function PasswordField({
|
|||
name = 'password',
|
||||
label = 'Password',
|
||||
defaultValue,
|
||||
}: Partial<Pick<ICustomInput, 'name' | 'label' | 'defaultValue'>>) {
|
||||
isRequired,
|
||||
}: Partial<Pick<ICustomInput, 'name' | 'label' | 'defaultValue' | 'isRequired'>>) {
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
|
||||
const handleClickShowPassword = () => setShowPassword(!showPassword);
|
||||
|
@ -28,6 +29,7 @@ export default function PasswordField({
|
|||
id={`${label}-${name}-field`}
|
||||
label={label}
|
||||
name={name}
|
||||
required={isRequired}
|
||||
type={showPassword ? 'text' : 'password'}
|
||||
/>
|
||||
</FormControl>
|
||||
|
|
|
@ -4,4 +4,5 @@ export interface ICustomInput {
|
|||
choices?: string[] | null;
|
||||
defaultValue?: string | number | boolean | null;
|
||||
isSecret?: boolean;
|
||||
isRequired?: boolean;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue