CEC-2385 Only show software updates (#193)

* CEC-2385 Only show software updates

* Update browser list

* update threshold

* Clean up
This commit is contained in:
John Wu
2022-09-02 09:56:52 -07:00
committed by GitHub
parent 8d0dbf8030
commit 153c6bdcf7
18 changed files with 31 additions and 2266 deletions

20
package-lock.json generated
View File

@@ -6505,13 +6505,19 @@
}
},
"node_modules/caniuse-lite": {
"version": "1.0.30001314",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001314.tgz",
"integrity": "sha512-0zaSO+TnCHtHJIbpLroX7nsD+vYuOVjl3uzFbJO1wMVbuveJA0RK2WcQA9ZUIOiO0/ArMiMgHJLxfEZhQiC0kw==",
"funding": {
"version": "1.0.30001387",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001387.tgz",
"integrity": "sha512-fKDH0F1KOJvR+mWSOvhj8lVRr/Q/mc5u5nabU2vi1/sgvlSqEsE8dOq0Hy/BqVbDkCYQPRRHB1WRjW6PGB/7PA==",
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/browserslist"
},
{
"type": "tidelift",
"url": "https://tidelift.com/funding/github/npm/caniuse-lite"
}
]
},
"node_modules/case-sensitive-paths-webpack-plugin": {
"version": "2.4.0",
@@ -21996,9 +22002,9 @@
}
},
"caniuse-lite": {
"version": "1.0.30001314",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001314.tgz",
"integrity": "sha512-0zaSO+TnCHtHJIbpLroX7nsD+vYuOVjl3uzFbJO1wMVbuveJA0RK2WcQA9ZUIOiO0/ArMiMgHJLxfEZhQiC0kw=="
"version": "1.0.30001387",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001387.tgz",
"integrity": "sha512-fKDH0F1KOJvR+mWSOvhj8lVRr/Q/mc5u5nabU2vi1/sgvlSqEsE8dOq0Hy/BqVbDkCYQPRRHB1WRjW6PGB/7PA=="
},
"case-sensitive-paths-webpack-plugin": {
"version": "2.4.0",

View File

@@ -62,7 +62,7 @@
"coverageThreshold": {
"global": {
"branches": 39,
"functions": 47,
"functions": 45.5,
"lines": 50,
"statements": 49
}

View File

@@ -1,7 +1,6 @@
jest.mock("../Contexts/CarUpdatesContext");
jest.mock("../Contexts/FileUploadContext");
jest.mock("../Contexts/VehicleContext");
jest.mock("../Contexts/ManifestCreateContext");
jest.mock("../Contexts/ManifestsContext");
jest.mock("../Contexts/UserContext");
jest.mock("../../services/monitoring");
@@ -73,10 +72,6 @@ describe("App", () => {
await check("/package-deploy/1", "span.MuiButton-label", "Sign In");
});
it("Route /package-create unauthenticated", async () => {
await check("/package-create", "span.MuiButton-label", "Sign In");
});
it("Route /vehicle-add unauthenticated", async () => {
await check("/vehicle-add", "span.MuiButton-label", "Sign In");
});
@@ -146,11 +141,6 @@ describe("App", () => {
await check("/package-deploy/1", "h6", "Deploy Test Manifest 1.0");
});
it("Route /package-create authenticated", async () => {
setToken(TEST_AUTH_OBJECT);
await check("/package-create", "h6", "Create Deployments");
});
it("Route /vehicle-add authenticated", async () => {
setToken(TEST_AUTH_OBJECT);
await check("/vehicle-add", "h6", "Add Vehicle");

File diff suppressed because it is too large Load Diff

View File

@@ -15,7 +15,7 @@ exports[`CarStatus Render 1`] = `
class="makeStyles-paper-0 makeStyles-tableSize-0"
>
<div
class="MuiBox-root MuiBox-root-64 makeStyles-tableToolbar-0"
class="MuiBox-root MuiBox-root-0 makeStyles-tableToolbar-0"
>
<div
class="MuiTabs-root"
@@ -133,7 +133,7 @@ exports[`CarStatus Render 1`] = `
role="tabpanel"
>
<div
class="MuiBox-root MuiBox-root-69"
class="MuiBox-root MuiBox-root-0"
>
<div
class="makeStyles-paper-0 makeStyles-tableSize-0"

View File

@@ -1,247 +0,0 @@
import React, { useContext, useState } from "react";
import api from "../../services/manifestsAPI";
import { uploadFile, getCancelToken } from "../../services/uploadFile";
import { useStatusContext } from "./StatusContext";
import {
validateManifest,
validateManifestECUs,
} from "../../utils/manifestValidation";
const ManifestCreateContext = React.createContext();
const checkExistingManifest = async (data, token) => {
const check = {
name: data.name,
version: data.version,
};
const { data: result } = await api.getManifests(check, token);
if (result.length > 0)
throw new Error(`Update ${data.name} ${data.version} already exists`);
};
const ECUTemplate = {
name: "AGS",
version: "",
hw_version: "",
configuration_mask: "",
configuration: "",
files: [],
manifest_id: 0,
};
const FileTemplate = {
offset: "0",
checksum: "",
type: "none",
};
export const ManifestCreateProvider = ({ children }) => {
const { setMessage } = useStatusContext();
const [busy, setBusy] = useState(false);
const [uploadProgress, setUploadProgress] = useState(0);
const [uploadStatus, setUploadStatus] = useState(null);
const [cancelUploadToken, setCancelUploadToken] = useState(null);
const [uploadFileIndex, setUploadFileIndex] = useState(0);
const [uploadedFiles, setUploadedFiles] = useState([]);
const [ecus, setECUs] = useState([]);
const [ecuIndex, setECUIndex] = useState(0);
const addECU = () => {
try {
const result = ecus.concat(
Object.assign({ data_id: ecuIndex }, ECUTemplate)
);
setECUIndex(ecuIndex + 1);
setECUs(result);
} catch (err) {
setMessage(err.message);
}
};
const deleteECU = (data_id) => {
try {
const result = ecus.filter((item) => item.data_id !== data_id);
setECUs(result);
} catch (err) {
setMessage(err.message);
}
};
const getECU = (ecu_data_id) =>
ecus.find((item) => item.data_id === ecu_data_id);
const getECUFile = (ecu, filename) =>
ecu.files.find((file) => file.filename === filename);
const validateECUFiles = (ecu, files) => {
files.forEach((file) => {
if (file.size === 0) throw new Error(`${file.name} is 0 size`);
const result = getECUFile(ecu, file.name);
if (result) throw new Error(`${file.name} already exists`);
});
};
const getAllECUFiles = () => {
let result = [];
for (let i = 0, len = ecus.length; i < len; i++) {
const ecu = ecus[i];
result = result.concat(ecu.files);
}
return result;
};
const addECUFile = (ecu_data_id, items) => {
try {
const ecu = getECU(ecu_data_id);
if (ecu === undefined) return;
const files = Array.from(items);
validateECUFiles(ecu, files);
const total = ecu.files.length;
const result = files.map((file, index) =>
Object.assign(
{ order: total + index, file: file, filename: file.name },
FileTemplate
)
);
ecu.files = ecu.files.concat(result);
updateECUs();
} catch (err) {
setMessage(err.message);
}
};
const sortECUFiles = (ecu) => {
ecu.files.sort((x, y) => {
if (x.order === y.order) return 0;
return x.order < y.order ? -1 : 1;
});
};
const deleteECUFile = (ecu_data_id, filename) => {
try {
const ecu = getECU(ecu_data_id);
if (ecu === undefined) return;
ecu.files = ecu.files.filter((file) => file.filename !== filename);
sortECUFiles(ecu);
updateECUs();
} catch (err) {
setMessage(err.message);
}
};
const updateECUs = () => {
setECUs(ecus.concat([]));
};
const createManifest = async (data, token) => {
let result;
let currentFileIndex = 0;
const incremFileIndex = () => {
setUploadFileIndex(currentFileIndex);
currentFileIndex++;
};
try {
setBusy(true);
validateManifest(data, token);
validateManifestECUs(ecus);
await checkExistingManifest(data, token);
setUploadedFiles(getAllECUFiles());
if (result !== null) result = await api.createManifest(data, token);
if (result.error)
throw new Error(`Create manifest error. ${result.message}`);
for (let i = 0, len = ecus.length; i < len; i++) {
const ecu = ecus[i];
ecu.manifest_id = result.id;
await createManifestECU(ecu, token, incremFileIndex);
}
} finally {
setBusy(false);
setUploadFileIndex(0);
setUploadedFiles([]);
}
return result;
};
const createManifestECU = async (ecu, token, incremFileIndexFn) => {
const { files, ...data } = ecu;
const result = await api.createManifestECU(data, token);
if (result.error)
throw new Error(`Create manifest error. ${result.message}`);
for (let i = 0, len = ecu.files.length; i < len; i++) {
const file = ecu.files[i];
file.manifest_ecu_id = result.id;
incremFileIndexFn();
const resp = await uploadECUFile(file, token);
if (resp.error)
throw new Error(`Upload manifest file error. ${resp.error}`);
}
};
const cancelUpload = () => {
if (cancelUploadToken) cancelUploadToken.cancel();
setBusy(false);
setUploadStatus("Upload cancelled");
setCancelUploadToken(null);
setUploadProgress(0);
};
const uploadECUFile = async (file, accessToken) => {
const cancel = getCancelToken();
setUploadProgress(0);
setUploadStatus(`Uploading ${file.filename}`);
setCancelUploadToken(cancel);
const result = await uploadFile(
file,
accessToken,
setUploadProgress,
cancel.token
);
if (result.message) {
throw new Error(`${result.error}. ${result.message}`);
}
setUploadStatus(`Uploaded ${file.filename}`);
setCancelUploadToken(null);
setUploadProgress(100);
return result;
};
return (
<ManifestCreateContext.Provider
value={{
busy,
ecus,
uploadedFiles,
uploadFileIndex,
uploadProgress,
uploadStatus,
addECU,
addECUFile,
cancelUpload,
createManifest,
createManifestECU,
deleteECU,
deleteECUFile,
updateECUs,
}}
>
{children}
</ManifestCreateContext.Provider>
);
};
export const useManifestCreateContext = () => useContext(ManifestCreateContext);

View File

@@ -1,50 +0,0 @@
import React from "react";
const ManifestsContext = React.createContext();
let busy = false;
let ecus = [
{
data_id: 0,
name: "AGS",
version: "",
hw_version: "",
manifest_id: 0,
files: [
{
filename: "test.bin",
order: 0,
offset: "0",
checksum: "",
self_download: false,
mode: "D",
type: 1,
},
],
},
];
let uploadedFiles = [];
let uploadFileIndex = 0;
let uploadProgress = 0;
let uploadStatus = null;
export const ManifestCreateProvider = ({ children }) => {
return <div data-testid="mocked-manifestcreateprovider">{children}</div>;
};
export const useManifestCreateContext = () => ({
busy,
ecus,
uploadedFiles,
uploadFileIndex,
uploadProgress,
uploadStatus,
addECU: jest.fn(),
addECUFile: jest.fn(),
cancelUpload: jest.fn(),
createManifest: jest.fn(),
createManifestECU: jest.fn(),
deleteECU: jest.fn(),
deleteECUFile: jest.fn(),
updateECUs: jest.fn(),
});

View File

@@ -1,79 +0,0 @@
import React from "react";
import {
Table,
TableBody,
TableCell,
TableFooter,
TableRow,
} from "@material-ui/core";
import FileDragArea from "../FileDragArea";
import ListHead from "../ListHead";
import SubListItem from "../SubListItem";
import { useManifestCreateContext } from "../../Contexts/ManifestCreateContext";
import ManifestECUFileTypes from "../ManifestECUFileTypes";
const options = [
{
label: "Filename",
field: "filename",
readonly: true,
},
{
label: "Offset",
field: "offset",
},
{
label: "Checksum",
field: "checksum",
},
{
label: "Type",
field: "type",
control: ManifestECUFileTypes,
},
{
label: "",
field: "filename",
delete: true,
},
];
const ManifestECUFileList = ({ data }) => {
const { addECUFile, deleteECUFile } = useManifestCreateContext();
const onAddFile = (files) => {
addECUFile(data.data_id, files);
};
const onDeletFile = (filename) => {
deleteECUFile(data.data_id, filename);
};
return (
<Table>
{data && data.files && data.files.length > 0 && (
<ListHead options={options} />
)}
<TableBody>
{data.files.map((file) => (
<SubListItem
key={file.filename}
data={file}
options={options}
onDelete={onDeletFile}
/>
))}
</TableBody>
<TableFooter>
<TableRow>
<TableCell colSpan={options.length}>
<FileDragArea onFileSelect={onAddFile}>ADD FILES</FileDragArea>
</TableCell>
</TableRow>
</TableFooter>
</Table>
);
};
export default ManifestECUFileList;

View File

@@ -1,75 +0,0 @@
import React from "react";
import {
Button,
Table,
TableBody,
TableCell,
TableFooter,
TableRow,
} from "@material-ui/core";
import ECUDropDrop from "../ECUDropDown";
import ListHead from "../ListHead";
import ManifestECURow from "../ManifestECURow";
import { useManifestCreateContext } from "../../Contexts/ManifestCreateContext";
import PageDragPreventDefault from "../PageDragPreventDefault";
const options = [
{
label: "ID",
field: "data_id",
readonly: true,
},
{
label: "ECU",
field: "name",
control: ECUDropDrop,
required: true,
},
{
label: "Version",
field: "version",
required: true,
},
{
label: "Hardware",
field: "hw_version",
},
{
label: "",
delete: true,
},
];
const ManifestECUList = () => {
const { ecus, addECU } = useManifestCreateContext();
return (
<>
<PageDragPreventDefault />
<Table>
<ListHead options={options} />
<TableBody>
{ecus.map((item) => {
return (
<ManifestECURow
key={item.data_id}
data={item}
options={options}
/>
);
})}
</TableBody>
<TableFooter>
<TableRow>
<TableCell colSpan={5} align="center">
<Button onClick={addECU}>Add ECU</Button>
</TableCell>
</TableRow>
</TableFooter>
</Table>
</>
);
};
export default ManifestECUList;

View File

@@ -1,33 +0,0 @@
import React from "react";
import { TableCell, TableRow } from "@material-ui/core";
import ManifestECUFileList from "../ManifestECUFileList";
import SubListItem from "../SubListItem";
import { useManifestCreateContext } from "../../Contexts/ManifestCreateContext";
const ManifestECURow = ({ data, options }) => {
const { deleteECU } = useManifestCreateContext();
const onDeleteECU = () => {
deleteECU(data.data_id);
};
return (
<>
<SubListItem
key={data.data_id}
data={data}
options={options}
onDelete={onDeleteECU}
/>
<TableRow>
<TableCell colSpan={options.length}>
<ManifestECUFileList data={data} />
</TableCell>
</TableRow>
</>
);
};
export default ManifestECURow;

View File

@@ -1,68 +0,0 @@
import React, { useEffect, useState } from "react";
import { Button, Grid, LinearProgress, Typography } from "@material-ui/core";
import { useManifestCreateContext } from "../../Contexts/ManifestCreateContext";
const ManifestUploadProgress = (props) => {
const { uploadProgress, uploadStatus, uploadFileIndex, uploadedFiles } =
useManifestCreateContext();
const [progress, setProgress] = useState(0);
const [completed, setCompleted] = useState(0);
const [total, setTotal] = useState(0);
useEffect(() => {
const x = uploadedFiles.reduce(
(current, { file }) => current + file.size,
0
);
setTotal(x);
}, [uploadedFiles]);
useEffect(() => {
if (uploadFileIndex === 0 || uploadFileIndex >= uploadedFiles.length)
return;
let uploaded = 0;
uploadedFiles.forEach(({ file }, i) => {
if (i < uploadFileIndex) uploaded += file.size;
});
setCompleted(uploaded);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [uploadFileIndex]);
useEffect(() => {
if (total === 0 || uploadFileIndex >= uploadedFiles.length) return;
const { file } = uploadedFiles[uploadFileIndex];
const uploaded = completed + file.size * uploadProgress;
const x = Math.min(99, Math.floor((uploaded / total) * 100));
setProgress(x);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [uploadProgress, completed]);
return (
<>
<Grid container>
<Grid xs={12}>
<Typography align="center">
{`File ${uploadFileIndex + 1} of ${
uploadedFiles.length
}. ${uploadStatus}`}
</Typography>
</Grid>
</Grid>
<Grid container alignContent="center" spacing={0}>
<Grid xs={11}>
<LinearProgress
variant="determinate"
value={progress}
style={{ marginTop: 16 }}
/>
</Grid>
<Grid xs={1} alignContent="flex-end" align="right">
<Button onClick={props.onCancel}>Cancel</Button>
</Grid>
</Grid>
</>
);
};
export default ManifestUploadProgress;

View File

@@ -8,7 +8,7 @@ exports[`TabPanel Render 1`] = `
role="tabpanel"
>
<div
class="MuiBox-root MuiBox-root-1"
class="MuiBox-root MuiBox-root-0"
>
<div>
Test

View File

@@ -15,7 +15,7 @@ exports[`FleetStatus Render 1`] = `
class="makeStyles-paper-0 makeStyles-tableSize-0"
>
<div
class="MuiBox-root MuiBox-root-64 makeStyles-tableToolbar-0"
class="MuiBox-root MuiBox-root-0 makeStyles-tableToolbar-0"
>
<div
class="MuiTabs-root"
@@ -97,7 +97,7 @@ exports[`FleetStatus Render 1`] = `
role="tabpanel"
>
<div
class="MuiBox-root MuiBox-root-69"
class="MuiBox-root MuiBox-root-0"
>
<div
class="makeStyles-paper-0 makeStyles-tableSize-0"

View File

@@ -1,157 +0,0 @@
import React, { useEffect, useRef, useState } from "react";
import { Redirect } from "react-router";
import { Button, TextField, Typography } from "@material-ui/core";
import { useUserContext } from "../../Contexts/UserContext";
import { useStatusContext } from "../../Contexts/StatusContext";
import {
ManifestCreateProvider,
useManifestCreateContext,
} from "../../Contexts/ManifestCreateContext";
import useStyles from "../../useStyles";
import { logger } from "../../../services/monitoring";
import ManifestECUList from "../../Controls/ManifestECUList";
import ManifestUploadProgress from "../../Controls/ManifestUploadProgress";
const MainForm = () => {
const { createManifest, cancelUpload, busy } = useManifestCreateContext();
const { token } = useUserContext();
const { setMessage, setTitle, setSitePath } = useStatusContext();
const [redirect, setRedirect] = useState(null);
const classes = useStyles();
const packagenameEl = useRef(null);
const versionEl = useRef(null);
const descEl = useRef(null);
const releasenotesEl = useRef(null);
useEffect(() => {
setTitle("Create Deployments");
setSitePath([
{
label: "Deployments",
link: "/packages",
},
{
label: "Create Deployments",
},
]);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const onSubmit = async (event) => {
try {
event.preventDefault();
const {
idToken: { jwtToken: authToken },
} = token;
const formData = {
name: packagenameEl.current.value,
version: versionEl.current.value,
description: descEl.current.value,
release_notes: releasenotesEl.current.value,
};
const manifest = await createManifest(formData, authToken);
if (!manifest || manifest.error) return;
cancelUpload();
setMessage(`Package uploaded`);
setRedirect(`/package-deploy/${manifest.id}`);
} catch (e) {
setMessage(e.message);
logger.warn(e.stack);
}
};
if (redirect && redirect.length > 0) {
return <Redirect to={redirect} />;
}
return (
<div className={classes.paper}>
<form className={classes.form} noValidate action="{onSubmit}">
<TextField
id="packagename"
name="packagename"
label="Package name"
variant="outlined"
margin="normal"
inputProps={{
maxLength: "255",
}}
required
fullWidth
inputRef={packagenameEl}
/>
<TextField
id="version"
name="version"
label="Version"
variant="outlined"
margin="normal"
inputProps={{
maxLength: "255",
}}
required
fullWidth
inputRef={versionEl}
/>
<TextField
id="description"
name="description"
label="Description"
variant="outlined"
margin="normal"
inputProps={{
maxLength: "5120",
}}
required
fullWidth
multiline
minRows={4}
placeholder="Package description"
inputRef={descEl}
/>
<TextField
id="release_notes"
name="release_notes"
label="Release Notes URL"
variant="outlined"
margin="normal"
inputProps={{
maxLength: "1024",
}}
required
fullWidth
placeholder="Release Notes URL"
inputRef={releasenotesEl}
/>
<Typography variant="h6">ECU Files</Typography>
<ManifestECUList />
{busy ? (
<ManifestUploadProgress onCancel={cancelUpload} />
) : (
<Button
type="submit"
disabled={busy}
fullWidth
variant="contained"
color="primary"
className={classes.submit}
onClick={onSubmit}
>
Submit
</Button>
)}
</form>
</div>
);
};
export default function FileUploadForm() {
return (
<ManifestCreateProvider>
<MainForm />
</ManifestCreateProvider>
);
}

View File

@@ -29,6 +29,7 @@ import { logger } from "../../../services/monitoring";
import ECUList from "../../Controls/ECUList";
import { Roles, hasRole } from "../../../utils/roles";
import { useLocalStorage } from "../../useLocalStorage";
import { TYPE_MANIFEST_SOFTWARE } from "../../../utils/manifest_types";
const tableColumns = [
{
@@ -103,6 +104,7 @@ const MainForm = () => {
limit: pageSize,
offset: pageSize * pageIndex,
order: `${orderBy} ${order}`,
manifest_type: TYPE_MANIFEST_SOFTWARE,
search,
},
token

View File

@@ -28,7 +28,6 @@ const Home = React.lazy(() => import("../Home"));
const Manifests = React.lazy(() => import("../Manifest/List"));
const ManifestDeploy = React.lazy(() => import("../Manifest/Deploy"));
const ManifestStatus = React.lazy(() => import("../Manifest/Status"));
const ManifestCreate = React.lazy(() => import("../Manifest/Create"));
const PageNotFound = React.lazy(() => import("../404"));
const SSOForm = React.lazy(() => import("../SSOForm"));
const VehicleAddForm = React.lazy(() => import("../Cars/Add"));
@@ -137,14 +136,6 @@ const SiteRoutes = () => {
groups={groups}
roles={[Roles.READ, Roles.CREATE]}
/>
<AuthRoute
path="/package-create"
render={() => <ManifestCreate />}
type={TYPES.PROTECTED}
token={token}
groups={groups}
roles={[Roles.CREATE]}
/>
<AuthRoute
path="/package-deploy/:manifest_id"
render={() => <ManifestDeploy />}

View File

@@ -0,0 +1,2 @@
export const TYPE_MANIFEST_SOFTWARE = 1;
export const TYPE_MANIFEST_CONFIG = 2;

View File

@@ -1,4 +1,4 @@
const rxStyles = /(mui|makeStyles|Private)([^\s]*)-\d+/g;
const rxStyles = /(mui|MuiBox-root|makeStyles|Private)([^\s]*)-\d+/g;
const snapshotSerializers = [
{