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")
|
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
|
# 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")
|
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
|
@events.test_start.add_listener
|
||||||
|
|
|
@ -53,6 +53,8 @@ def locust_init(environment, **_kwargs):
|
||||||
{
|
{
|
||||||
"label": "Username",
|
"label": "Username",
|
||||||
"name": "username",
|
"name": "username",
|
||||||
|
# make field required
|
||||||
|
"is_required": True,
|
||||||
},
|
},
|
||||||
# boolean checkmark field
|
# boolean checkmark field
|
||||||
{"label": "Admin", "name": "is_admin", "default_value": False},
|
{"label": "Admin", "name": "is_admin", "default_value": False},
|
||||||
|
|
|
@ -59,15 +59,18 @@ class LocustArgumentParser(configargparse.ArgumentParser):
|
||||||
Arguments:
|
Arguments:
|
||||||
include_in_web_ui: If True (default), the argument will show in the UI.
|
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_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:
|
Returns:
|
||||||
argparse.Action: the new argparse action
|
argparse.Action: the new argparse action
|
||||||
"""
|
"""
|
||||||
include_in_web_ui = kwargs.pop("include_in_web_ui", True)
|
include_in_web_ui = kwargs.pop("include_in_web_ui", True)
|
||||||
is_secret = kwargs.pop("is_secret", False)
|
is_secret = kwargs.pop("is_secret", False)
|
||||||
|
is_required = kwargs.pop("is_required", False)
|
||||||
action = super().add_argument(*args, **kwargs)
|
action = super().add_argument(*args, **kwargs)
|
||||||
action.include_in_web_ui = include_in_web_ui
|
action.include_in_web_ui = include_in_web_ui
|
||||||
action.is_secret = is_secret
|
action.is_secret = is_secret
|
||||||
|
action.is_required = is_required
|
||||||
return action
|
return action
|
||||||
|
|
||||||
@property
|
@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
|
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):
|
class LocustTomlConfigParser(configargparse.TomlConfigParser):
|
||||||
def parse(self, stream):
|
def parse(self, stream):
|
||||||
|
@ -798,6 +809,7 @@ def default_args_dict() -> dict:
|
||||||
class UIExtraArgOptions(NamedTuple):
|
class UIExtraArgOptions(NamedTuple):
|
||||||
default_value: str
|
default_value: str
|
||||||
is_secret: bool
|
is_secret: bool
|
||||||
|
is_required: bool
|
||||||
help_text: str
|
help_text: str
|
||||||
choices: list[str] | None = None
|
choices: list[str] | None = None
|
||||||
|
|
||||||
|
@ -813,6 +825,7 @@ def ui_extra_args_dict(args=None) -> dict[str, dict[str, Any]]:
|
||||||
k: UIExtraArgOptions(
|
k: UIExtraArgOptions(
|
||||||
default_value=v,
|
default_value=v,
|
||||||
is_secret=k in parser.secret_args_included_in_web_ui,
|
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,
|
help_text=parser.args_included_in_web_ui[k].help,
|
||||||
choices=parser.args_included_in_web_ui[k].choices,
|
choices=parser.args_included_in_web_ui[k].choices,
|
||||||
)._asdict()
|
)._asdict()
|
||||||
|
|
|
@ -373,6 +373,7 @@ class TestArgumentParser(LocustTestCase):
|
||||||
parser.add_argument("--a1", help="a1 help")
|
parser.add_argument("--a1", help="a1 help")
|
||||||
parser.add_argument("--a2", help="a2 help", include_in_web_ui=False)
|
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("--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"]
|
args = ["-u", "666", "--a1", "v1", "--a2", "v2", "--a3", "v3"]
|
||||||
options = parse_options(args=args)
|
options = parse_options(args=args)
|
||||||
|
@ -384,6 +385,7 @@ class TestArgumentParser(LocustTestCase):
|
||||||
self.assertIn("a1", extra_args)
|
self.assertIn("a1", extra_args)
|
||||||
self.assertNotIn("a2", extra_args)
|
self.assertNotIn("a2", extra_args)
|
||||||
self.assertIn("a3", extra_args)
|
self.assertIn("a3", extra_args)
|
||||||
|
self.assertIn("a4", extra_args)
|
||||||
self.assertEqual("v1", extra_args["a1"]["default_value"])
|
self.assertEqual("v1", extra_args["a1"]["default_value"])
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -60,6 +60,7 @@ class InputField(TypedDict, total=False):
|
||||||
default_value: bool | None
|
default_value: bool | None
|
||||||
choices: list[str] | None
|
choices: list[str] | None
|
||||||
is_secret: bool | None
|
is_secret: bool | None
|
||||||
|
is_required: bool | None
|
||||||
|
|
||||||
|
|
||||||
class CustomForm(TypedDict, total=False):
|
class CustomForm(TypedDict, total=False):
|
||||||
|
|
|
@ -11,6 +11,7 @@ export default function CustomInput({
|
||||||
defaultValue,
|
defaultValue,
|
||||||
choices,
|
choices,
|
||||||
isSecret,
|
isSecret,
|
||||||
|
isRequired,
|
||||||
}: ICustomInput) {
|
}: ICustomInput) {
|
||||||
if (choices) {
|
if (choices) {
|
||||||
return (
|
return (
|
||||||
|
@ -19,6 +20,7 @@ export default function CustomInput({
|
||||||
label={label}
|
label={label}
|
||||||
name={name}
|
name={name}
|
||||||
options={choices}
|
options={choices}
|
||||||
|
required={isRequired}
|
||||||
sx={{ width: '100%' }}
|
sx={{ width: '100%' }}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
@ -27,7 +29,7 @@ export default function CustomInput({
|
||||||
if (typeof defaultValue === 'boolean') {
|
if (typeof defaultValue === 'boolean') {
|
||||||
return (
|
return (
|
||||||
<FormControlLabel
|
<FormControlLabel
|
||||||
control={<Checkbox defaultChecked={defaultValue} />}
|
control={<Checkbox defaultChecked={defaultValue} required={isRequired} />}
|
||||||
label={<Markdown content={label} />}
|
label={<Markdown content={label} />}
|
||||||
name={name}
|
name={name}
|
||||||
/>
|
/>
|
||||||
|
@ -35,7 +37,14 @@ export default function CustomInput({
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isSecret) {
|
if (isSecret) {
|
||||||
return <PasswordField defaultValue={defaultValue} label={label} name={name} />;
|
return (
|
||||||
|
<PasswordField
|
||||||
|
defaultValue={defaultValue}
|
||||||
|
isRequired={isRequired}
|
||||||
|
label={label}
|
||||||
|
name={name}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -43,6 +52,7 @@ export default function CustomInput({
|
||||||
defaultValue={defaultValue}
|
defaultValue={defaultValue}
|
||||||
label={label}
|
label={label}
|
||||||
name={name}
|
name={name}
|
||||||
|
required={isRequired}
|
||||||
sx={{ width: '100%' }}
|
sx={{ width: '100%' }}
|
||||||
type='text'
|
type='text'
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -8,7 +8,8 @@ export default function PasswordField({
|
||||||
name = 'password',
|
name = 'password',
|
||||||
label = 'Password',
|
label = 'Password',
|
||||||
defaultValue,
|
defaultValue,
|
||||||
}: Partial<Pick<ICustomInput, 'name' | 'label' | 'defaultValue'>>) {
|
isRequired,
|
||||||
|
}: Partial<Pick<ICustomInput, 'name' | 'label' | 'defaultValue' | 'isRequired'>>) {
|
||||||
const [showPassword, setShowPassword] = useState(false);
|
const [showPassword, setShowPassword] = useState(false);
|
||||||
|
|
||||||
const handleClickShowPassword = () => setShowPassword(!showPassword);
|
const handleClickShowPassword = () => setShowPassword(!showPassword);
|
||||||
|
@ -28,6 +29,7 @@ export default function PasswordField({
|
||||||
id={`${label}-${name}-field`}
|
id={`${label}-${name}-field`}
|
||||||
label={label}
|
label={label}
|
||||||
name={name}
|
name={name}
|
||||||
|
required={isRequired}
|
||||||
type={showPassword ? 'text' : 'password'}
|
type={showPassword ? 'text' : 'password'}
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
|
@ -4,4 +4,5 @@ export interface ICustomInput {
|
||||||
choices?: string[] | null;
|
choices?: string[] | null;
|
||||||
defaultValue?: string | number | boolean | null;
|
defaultValue?: string | number | boolean | null;
|
||||||
isSecret?: boolean;
|
isSecret?: boolean;
|
||||||
|
isRequired?: boolean;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue