CEC-2579 Add ability to edit manifest (#226)

This commit is contained in:
arpanetus
2022-10-26 03:54:20 +06:00
committed by GitHub
parent aaf47f4cc7
commit 9a9766df12
11 changed files with 656 additions and 0 deletions

View File

@@ -31,6 +31,7 @@ import { Roles, hasRole } from "../../../utils/roles";
import { useLocalStorage } from "../../useLocalStorage";
import { TYPE_MANIFEST_SOFTWARE } from "../../../utils/manifest_types";
import DeleteConfirmation from "../../DeleteConfirmation";
import EditIcon from "@material-ui/icons/Edit";
const tableColumns = [
{
@@ -45,6 +46,10 @@ const tableColumns = [
id: "version",
label: "Version",
},
{
id: "type",
label: "Type",
},
{
id: "created_at",
label: "Created",
@@ -59,6 +64,15 @@ const tableColumns = [
},
];
const formatManifestType = (type) => {
switch (type) {
case "forced":
return "Forced";
default:
return "Standard";
}
}
const PAGE_SIZE = "MANIFEST_LIST_PAGE_SIZE";
const MainForm = () => {
@@ -161,6 +175,12 @@ const MainForm = () => {
icon: (
<VisibilityIcon aria-label={`Status ${row.name} ${row.version}`} />
),
}, {
tip: `Update "${row.name} ${row.version}"`,
link: `/package-update/${row.id}`,
icon: (
<EditIcon aria-label={`Update ${row.name} ${row.version}`} />
),
});
}
if (hasRole([Roles.CREATE], groups)) {
@@ -237,6 +257,7 @@ const MainForm = () => {
)}
</TableCell>
<TableCell align="center">{row.version}</TableCell>
<TableCell align="center">{formatManifestType(row.type)}</TableCell>
<TableCell align="center">
{LocalDateTimeString(row.created)}
</TableCell>

View File

@@ -0,0 +1,192 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Manifest Details Component Render 1`] = `
<div>
<div
data-testid="mocked-userprovider"
>
<div
data-testid="mocked-manifestsprovider"
>
<div
data-testid="mocked-manifestsprovider"
>
<div
class="makeStyles-paper-0"
>
<div
class="MuiFormControl-root makeStyles-form-0 MuiFormControl-fullWidth"
novalidate=""
>
<div
class="MuiFormControl-root MuiTextField-root MuiFormControl-marginNormal MuiFormControl-fullWidth"
>
<label
class="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-shrink MuiInputLabel-outlined Mui-disabled Mui-disabled MuiFormLabel-filled Mui-required Mui-required"
data-shrink="true"
for="id"
id="id-label"
>
ID
<span
aria-hidden="true"
class="MuiFormLabel-asterisk MuiInputLabel-asterisk"
>
*
</span>
</label>
<div
class="MuiInputBase-root MuiOutlinedInput-root Mui-disabled Mui-disabled MuiInputBase-fullWidth MuiInputBase-formControl"
>
<input
aria-invalid="false"
class="MuiInputBase-input MuiOutlinedInput-input Mui-disabled Mui-disabled"
disabled=""
id="id"
maxlength="20"
name="id"
readonly=""
required=""
type="text"
value="1"
/>
<fieldset
aria-hidden="true"
class="PrivateNotchedOutline-root-0 MuiOutlinedInput-notchedOutline"
>
<legend
class="PrivateNotchedOutline-legendLabelled-0 PrivateNotchedOutline-legendNotched-0"
>
<span>
ID
 *
</span>
</legend>
</fieldset>
</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 Mui-required Mui-required"
data-shrink="true"
for="name"
id="name-label"
>
Name
<span
aria-hidden="true"
class="MuiFormLabel-asterisk MuiInputLabel-asterisk"
>
*
</span>
</label>
<div
class="MuiInputBase-root MuiOutlinedInput-root MuiInputBase-fullWidth MuiInputBase-formControl"
>
<input
aria-invalid="false"
class="MuiInputBase-input MuiOutlinedInput-input"
id="name"
maxlength="255"
name="name"
required=""
type="text"
value="Test Deployment"
/>
<fieldset
aria-hidden="true"
class="PrivateNotchedOutline-root-0 MuiOutlinedInput-notchedOutline"
>
<legend
class="PrivateNotchedOutline-legendLabelled-0 PrivateNotchedOutline-legendNotched-0"
>
<span>
Name
 *
</span>
</legend>
</fieldset>
</div>
</div>
<div
class="MuiFormControl-root makeStyles-form-0 MuiFormControl-marginNormal MuiFormControl-fullWidth"
>
<label
class="MuiFormLabel-root MuiInputLabel-root makeStyles-whiteBackground-0 MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-outlined"
data-shrink="false"
for="manifest-type"
>
Type
</label>
<div
class="MuiInputBase-root MuiOutlinedInput-root MuiInputBase-formControl"
>
<select
aria-invalid="false"
class="MuiSelect-root MuiSelect-select MuiSelect-outlined MuiInputBase-input MuiOutlinedInput-input"
id="send-manifest-type"
name="manifest-type"
>
<option
value="standard"
>
Standard
</option>
<option
value="forced"
>
Forced
</option>
</select>
<svg
aria-hidden="true"
class="MuiSvgIcon-root MuiSelect-icon MuiSelect-iconOutlined"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M7 10l5 5 5-5z"
/>
</svg>
<fieldset
aria-hidden="true"
class="PrivateNotchedOutline-root-0 MuiOutlinedInput-notchedOutline"
style="padding-left: 8px;"
>
<legend
class="PrivateNotchedOutline-legend-0"
style="width: 0.01px;"
>
<span>
</span>
</legend>
</fieldset>
</div>
</div>
<button
aria-label="send command"
class="MuiButtonBase-root MuiButton-root MuiButton-contained makeStyles-submit-0 MuiButton-containedPrimary MuiButton-fullWidth"
tabindex="0"
type="submit"
>
<span
class="MuiButton-label"
>
Update
</span>
<span
class="MuiTouchRipple-root"
/>
</button>
</div>
</div>
</div>
</div>
</div>
</div>
`;

View File

@@ -0,0 +1,185 @@
import React, {useEffect, useState} from "react";
import {useParams} from "react-router-dom";
import { Redirect } from "react-router";
import useStyles from "../../useStyles";
import {ManifestsProvider, useManifestsContext} from "../../Contexts/ManifestsContext";
import {useUserContext} from "../../Contexts/UserContext";
import {useStatusContext} from "../../Contexts/StatusContext";
import {Button, FormControl, InputLabel, Select, TextField} from "@material-ui/core";
const manifestTypes = [
{value: "standard", label: "Standard"},
{value: "forced", label: "Forced"},
];
const emptyManifest = {
name: "",
version: "",
};
const MainForm = () => {
const {manifest_id} = useParams();
const classes = useStyles();
const [manifest, setManifest] = useState(null);
const [redirect, setRedirect] = useState(null);
const {getManifest, busy, updateManifest} = useManifestsContext();
const {
token: {
idToken: {jwtToken: token},
},
} = useUserContext();
const {setMessage, setTitle, setSitePath} = useStatusContext();
const [name, setName] = useState("");
const [type, setType] = useState("");
const changeName = (e) => {
setName(e.target.value);
}
const changeType = (e) => {
setType(e.target.value);
};
const onSubmit = async (e) => {
e.preventDefault();
try {
const data = {name, type};
console.log(data);
const result = await updateManifest(manifest_id, data, token);
if (!result || result.error) return;
setMessage(`Updated manifest ${manifest_id}`);
setRedirect(`/package-status/${manifest_id}`);
} catch (e) {
setMessage(`Failed to update manifest ${manifest_id}`);
}
}
useEffect(() => {
(async () => {
try {
const result = await getManifest(manifest_id, token);
if (result.error) {
throw new Error(`Get manifest error. ${result.message}`);
} else {
setManifest(result);
setName(result.name);
setType(result.type);
}
} catch (e) {
setMessage(e.message);
}
})();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [token]);
useEffect(() => {
const curManifest = manifest ? manifest : emptyManifest;
setTitle("Update Package");
setSitePath([
{
label: "Deployments",
link: "/packages",
},
{
label: `Update Package ${curManifest.name} ${curManifest.version}`,
},
]);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [manifest]);
if (redirect && redirect.length > 0) {
return <Redirect to={redirect} />;
}
if (manifest === null) return <div>Loading Manifest...</div>;
return (
<div className={classes.paper}>
<FormControl className={classes.form} fullWidth noValidate>
<TextField
id="id"
name="id"
label="ID"
variant="outlined"
margin="normal"
inputProps={{
maxLength: "20",
readOnly: true,
}}
disabled
value={manifest.id}
required
fullWidth
/>
<TextField
id="name"
name="name"
label="Name"
variant="outlined"
margin="normal"
inputProps={{
maxLength: "255",
}}
value={name}
required
fullWidth
onChange={changeName}
/>
<FormControl
className={classes.form}
variant="outlined"
fullWidth
margin="normal"
>
<InputLabel htmlFor="manifest-type" className={classes.whiteBackground}>
Type
</InputLabel>
<Select
native
value={type}
variant="outlined"
inputProps={{
name: "manifest-type",
id: "send-manifest-type",
}}
onChange={changeType}
>
{manifestTypes.map((item, index) => (
<option key={index} value={item.value}>{item.label}</option>
))}
</Select>
</FormControl>
<Button
type="submit"
aria-label="send command"
disabled={busy || manifest == null}
fullWidth
variant="contained"
color="primary"
className={classes.submit}
onClick={onSubmit}
>
{busy ? "Updating..." : "Update"}
</Button>
</FormControl>
</div>
);
}
const ManifestUpdate = () => (
<ManifestsProvider>
<MainForm/>
</ManifestsProvider>
);
export default ManifestUpdate;

View File

@@ -0,0 +1,45 @@
jest.mock("../../Contexts/ManifestsContext");
jest.mock("../../Contexts/UserContext");
import React from "react";
import { render, waitFor } from "@testing-library/react";
import { BrowserRouter } from "react-router-dom";
import { ManifestsProvider } from "../../Contexts/ManifestsContext";
import { UserProvider, setToken } from "../../Contexts/UserContext";
import { StatusProvider } from "../../Contexts/StatusContext";
import ManifestUpdate from ".";
import { TEST_AUTH_OBJECT } from "../../../utils/testing";
import addSnapshotSerializer from "../../../utils/snapshot";
const renderManifestUpdate = async () => {
const { container } = render(
<UserProvider>
<BrowserRouter>
<StatusProvider>
<ManifestsProvider>
<ManifestUpdate />
</ManifestsProvider>
</StatusProvider>
</BrowserRouter>
</UserProvider>);
await waitFor(() => {
/* render */
});
return container;
}
describe("Manifest Details Component", () => {
beforeAll(() => {
setToken(TEST_AUTH_OBJECT);
addSnapshotSerializer(expect);
});
it("Render", async () => {
setToken(TEST_AUTH_OBJECT);
const view = await renderManifestUpdate();
expect(view).toMatchSnapshot();
});
});