CEC-5436: add release_notes, update_duration, and max_attempts to manifest update form (#489)
This commit is contained in:
44
src/components/Controls/NumberField/NumberField.jsx
Normal file
44
src/components/Controls/NumberField/NumberField.jsx
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
import {
|
||||||
|
Box,
|
||||||
|
FormControl,
|
||||||
|
FormHelperText,
|
||||||
|
Input,
|
||||||
|
InputLabel,
|
||||||
|
} from "@material-ui/core";
|
||||||
|
|
||||||
|
const PREFIX = "number-field";
|
||||||
|
|
||||||
|
function kebab(str) {
|
||||||
|
return str.replaceAll(" ", "-").toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function NumberField({
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
value = 0,
|
||||||
|
setValue = () => { },
|
||||||
|
...rest
|
||||||
|
}) {
|
||||||
|
const inputId = `${PREFIX}-${kebab(name)}`;
|
||||||
|
const describeId = `${PREFIX}-${kebab(name)}-describe`;
|
||||||
|
|
||||||
|
const handleChange = (event) => {
|
||||||
|
setValue(event.target.value);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Box sx={{ my: 2 }}>
|
||||||
|
<FormControl fullWidth>
|
||||||
|
<InputLabel htmlFor={inputId}>{name}</InputLabel>
|
||||||
|
<Input
|
||||||
|
id={inputId}
|
||||||
|
aria-describedby={describeId}
|
||||||
|
type="number"
|
||||||
|
value={value}
|
||||||
|
onChange={handleChange}
|
||||||
|
{...rest}
|
||||||
|
/>
|
||||||
|
<FormHelperText id={describeId}>{description}</FormHelperText>
|
||||||
|
</FormControl>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
55
src/components/Controls/NumberField/NumberField.test.jsx
Normal file
55
src/components/Controls/NumberField/NumberField.test.jsx
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { fireEvent, render } from "@testing-library/react";
|
||||||
|
|
||||||
|
import { NumberField } from "./index";
|
||||||
|
|
||||||
|
describe("NumberField", () => {
|
||||||
|
it("renders with form label and aria describe", () => {
|
||||||
|
const { getByText } = render(
|
||||||
|
<NumberField
|
||||||
|
name="My First Field"
|
||||||
|
description="Some helper text"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
const labelEl = getByText("My First Field");
|
||||||
|
const describeEl = getByText("Some helper text");
|
||||||
|
|
||||||
|
expect(labelEl.htmlFor).toEqual("number-field-my-first-field");
|
||||||
|
expect(describeEl.id).toEqual("number-field-my-first-field-describe");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("input is of type number", () => {
|
||||||
|
const { getByLabelText } = render(
|
||||||
|
<NumberField
|
||||||
|
name="My First Field"
|
||||||
|
description="Some helper text"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
const inputEl = getByLabelText("My First Field", { selector: "input" });
|
||||||
|
|
||||||
|
expect(inputEl.type).toEqual("number");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("updates parent state", () => {
|
||||||
|
let mockState = 0;
|
||||||
|
const mockSetState = jest.fn((value) => mockState = value);
|
||||||
|
|
||||||
|
const { getByLabelText } = render(
|
||||||
|
<NumberField
|
||||||
|
name="My First Field"
|
||||||
|
description="Enter a number"
|
||||||
|
value={mockState}
|
||||||
|
setValue={mockSetState}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
const inputEl = getByLabelText("My First Field", { selector: "input" });
|
||||||
|
|
||||||
|
fireEvent.change(inputEl, { target: { value: "1" } });
|
||||||
|
|
||||||
|
expect(mockState).toEqual("1");
|
||||||
|
expect(mockSetState.mock.calls.length).toEqual(1);
|
||||||
|
});
|
||||||
|
})
|
||||||
1
src/components/Controls/NumberField/index.jsx
Normal file
1
src/components/Controls/NumberField/index.jsx
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export * from "./NumberField";
|
||||||
@@ -112,6 +112,43 @@ exports[`Manifest Details Component Render 1`] = `
|
|||||||
</fieldset>
|
</fieldset>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
class="MuiFormControl-root MuiTextField-root MuiFormControl-marginNormal MuiFormControl-fullWidth"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
class="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-shrink MuiInputLabel-outlined MuiFormLabel-filled"
|
||||||
|
data-shrink="true"
|
||||||
|
for="release_notes"
|
||||||
|
id="release_notes-label"
|
||||||
|
>
|
||||||
|
Release Notes
|
||||||
|
</label>
|
||||||
|
<div
|
||||||
|
class="MuiInputBase-root MuiOutlinedInput-root MuiInputBase-fullWidth MuiInputBase-formControl"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
aria-invalid="false"
|
||||||
|
class="MuiInputBase-input MuiOutlinedInput-input"
|
||||||
|
id="release_notes"
|
||||||
|
maxlength="255"
|
||||||
|
name="release_notes"
|
||||||
|
type="text"
|
||||||
|
value="https://releasenotes.com"
|
||||||
|
/>
|
||||||
|
<fieldset
|
||||||
|
aria-hidden="true"
|
||||||
|
class="PrivateNotchedOutline-root-0 MuiOutlinedInput-notchedOutline"
|
||||||
|
>
|
||||||
|
<legend
|
||||||
|
class="PrivateNotchedOutline-legendLabelled-0 PrivateNotchedOutline-legendNotched-0"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
Release Notes
|
||||||
|
</span>
|
||||||
|
</legend>
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
class="MuiFormControl-root MuiFormControl-marginNormal"
|
class="MuiFormControl-root MuiFormControl-marginNormal"
|
||||||
>
|
>
|
||||||
@@ -334,6 +371,68 @@ exports[`Manifest Details Component Render 1`] = `
|
|||||||
</fieldset>
|
</fieldset>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
class="MuiBox-root MuiBox-root-0"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiFormControl-root MuiFormControl-fullWidth"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
class="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-shrink MuiFormLabel-filled"
|
||||||
|
data-shrink="true"
|
||||||
|
for="number-field-update-duration"
|
||||||
|
>
|
||||||
|
Update Duration
|
||||||
|
</label>
|
||||||
|
<div
|
||||||
|
class="MuiInputBase-root MuiInput-root MuiInput-underline MuiInputBase-formControl MuiInput-formControl"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
aria-describedby="number-field-update-duration-describe"
|
||||||
|
aria-invalid="false"
|
||||||
|
class="MuiInputBase-input MuiInput-input"
|
||||||
|
id="number-field-update-duration"
|
||||||
|
type="number"
|
||||||
|
value="0"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
class="MuiFormHelperText-root MuiFormHelperText-filled"
|
||||||
|
id="number-field-update-duration-describe"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="MuiBox-root MuiBox-root-0"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiFormControl-root MuiFormControl-fullWidth"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
class="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-shrink MuiFormLabel-filled"
|
||||||
|
data-shrink="true"
|
||||||
|
for="number-field-max-attempts"
|
||||||
|
>
|
||||||
|
Max Attempts
|
||||||
|
</label>
|
||||||
|
<div
|
||||||
|
class="MuiInputBase-root MuiInput-root MuiInput-underline MuiInputBase-formControl MuiInput-formControl"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
aria-describedby="number-field-max-attempts-describe"
|
||||||
|
aria-invalid="false"
|
||||||
|
class="MuiInputBase-input MuiInput-input"
|
||||||
|
id="number-field-max-attempts"
|
||||||
|
type="number"
|
||||||
|
value="0"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
class="MuiFormHelperText-root MuiFormHelperText-filled"
|
||||||
|
id="number-field-max-attempts-describe"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<button
|
<button
|
||||||
aria-label="send command"
|
aria-label="send command"
|
||||||
class="MuiButtonBase-root MuiButton-root MuiButton-contained makeStyles-submit-0 MuiButton-containedPrimary MuiButton-fullWidth"
|
class="MuiButtonBase-root MuiButton-root MuiButton-contained makeStyles-submit-0 MuiButton-containedPrimary MuiButton-fullWidth"
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { DropDownList } from "../../Controls/DropDownList";
|
|||||||
import { RoleWrap } from "../../Controls/RoleWrap";
|
import { RoleWrap } from "../../Controls/RoleWrap";
|
||||||
import { Permissions } from "../../../utils/roles";
|
import { Permissions } from "../../../utils/roles";
|
||||||
import useStyles from "../../useStyles";
|
import useStyles from "../../useStyles";
|
||||||
|
import { NumberField } from "../../Controls/NumberField";
|
||||||
|
|
||||||
const manifestTypes = [
|
const manifestTypes = [
|
||||||
{ value: "standard", label: "Standard" },
|
{ value: "standard", label: "Standard" },
|
||||||
@@ -51,15 +52,22 @@ const MainForm = () => {
|
|||||||
const { setMessage, setTitle, setSitePath } = useStatusContext();
|
const { setMessage, setTitle, setSitePath } = useStatusContext();
|
||||||
|
|
||||||
const [name, setName] = useState("");
|
const [name, setName] = useState("");
|
||||||
|
const [releaseNotes, setReleaseNotes] = useState("");
|
||||||
const [type, setType] = useState("");
|
const [type, setType] = useState("");
|
||||||
const [active, setActive] = useState(true); // So !active = archived
|
const [active, setActive] = useState(true); // So !active = archived
|
||||||
const [rollback, setRollback] = useState(true);
|
const [rollback, setRollback] = useState(true);
|
||||||
const [env, setEnv] = useState("current");
|
const [env, setEnv] = useState("current");
|
||||||
|
const [updateDuration, setUpdateDuration] = useState(0);
|
||||||
|
const [maxAttempts, setMaxAttempts] = useState(0);
|
||||||
|
|
||||||
const changeName = (e) => {
|
const changeName = (e) => {
|
||||||
setName(e.target.value);
|
setName(e.target.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const changeReleaseNotes = (e) => {
|
||||||
|
setReleaseNotes(e.target.value);
|
||||||
|
}
|
||||||
|
|
||||||
const changeType = (e) => {
|
const changeType = (e) => {
|
||||||
setType(e.target.value);
|
setType(e.target.value);
|
||||||
};
|
};
|
||||||
@@ -76,10 +84,33 @@ const MainForm = () => {
|
|||||||
setEnv(e.target.value)
|
setEnv(e.target.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const changeUpdateDuration = (value) => {
|
||||||
|
if (value < 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setUpdateDuration(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
const changeMaxAttempts = (value) => {
|
||||||
|
if (value < 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setMaxAttempts(value);
|
||||||
|
}
|
||||||
|
|
||||||
const onSubmit = async (e) => {
|
const onSubmit = async (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
try {
|
try {
|
||||||
const result = await updateManifest(manifest_id, { name, type, active, rollback, env }, token);
|
const result = await updateManifest(manifest_id, {
|
||||||
|
name,
|
||||||
|
release_notes: releaseNotes,
|
||||||
|
type,
|
||||||
|
active,
|
||||||
|
rollback,
|
||||||
|
env,
|
||||||
|
update_duration: parseInt(updateDuration),
|
||||||
|
max_attempts: parseInt(maxAttempts),
|
||||||
|
}, token);
|
||||||
if (!result || result.error) return;
|
if (!result || result.error) return;
|
||||||
|
|
||||||
setMessage(`Updated manifest ${manifest_id}`);
|
setMessage(`Updated manifest ${manifest_id}`);
|
||||||
@@ -110,10 +141,13 @@ const MainForm = () => {
|
|||||||
} else {
|
} else {
|
||||||
setManifest(result);
|
setManifest(result);
|
||||||
setName(result.name);
|
setName(result.name);
|
||||||
|
setReleaseNotes(result.release_notes);
|
||||||
setType(result.type);
|
setType(result.type);
|
||||||
setActive(result.active);
|
setActive(result.active);
|
||||||
setRollback(result.rollback);
|
setRollback(result.rollback);
|
||||||
setEnv(result.env ?? "current");
|
setEnv(result.env ?? "current");
|
||||||
|
setUpdateDuration(result.update_duration);
|
||||||
|
setMaxAttempts(result.max_attempts);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setMessage(e.message);
|
setMessage(e.message);
|
||||||
@@ -176,12 +210,35 @@ const MainForm = () => {
|
|||||||
fullWidth
|
fullWidth
|
||||||
onChange={changeName}
|
onChange={changeName}
|
||||||
/>
|
/>
|
||||||
|
<TextField
|
||||||
|
id="release_notes"
|
||||||
|
name="release_notes"
|
||||||
|
label="Release Notes"
|
||||||
|
variant="outlined"
|
||||||
|
margin="normal"
|
||||||
|
inputProps={{
|
||||||
|
maxLength: "255",
|
||||||
|
}}
|
||||||
|
value={releaseNotes}
|
||||||
|
fullWidth
|
||||||
|
onChange={changeReleaseNotes}
|
||||||
|
/>
|
||||||
<DropDownList label="Type" data={manifestTypes} classes={classes} onChange={changeType} value={type} />
|
<DropDownList label="Type" data={manifestTypes} classes={classes} onChange={changeType} value={type} />
|
||||||
<DropDownList label="Active" data={activeStates} classes={classes} onChange={changeActive} value={active} />
|
<DropDownList label="Active" data={activeStates} classes={classes} onChange={changeActive} value={active} />
|
||||||
<DropDownList label="Rollback" data={booleanStates} classes={classes} onChange={changeRollback} value={rollback} />
|
<DropDownList label="Rollback" data={booleanStates} classes={classes} onChange={changeRollback} value={rollback} />
|
||||||
{
|
{
|
||||||
ENVS.length > 1 && <DropDownList label="ECC Keys" data={ENVS} classes={classes} onChange={changeEnv} value={env} />
|
ENVS.length > 1 && <DropDownList label="ECC Keys" data={ENVS} classes={classes} onChange={changeEnv} value={env} />
|
||||||
}
|
}
|
||||||
|
<NumberField
|
||||||
|
name="Update Duration"
|
||||||
|
value={updateDuration}
|
||||||
|
setValue={changeUpdateDuration}
|
||||||
|
/>
|
||||||
|
<NumberField
|
||||||
|
name="Max Attempts"
|
||||||
|
value={maxAttempts}
|
||||||
|
setValue={changeMaxAttempts}
|
||||||
|
/>
|
||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
aria-label="send command"
|
aria-label="send command"
|
||||||
|
|||||||
Reference in New Issue
Block a user