CEC-3672 Update manifest version on deploy (#277)
* CEC-3672 Add versions to CarUpdatesContext Stub out getSoftwareVersions and updateManifestVersion * CEC-3672 update version on deploy * Validate version before updating
This commit is contained in:
@@ -3229,6 +3229,22 @@ exports[`App Route /package-deploy authenticated 1`] = `
|
|||||||
<main
|
<main
|
||||||
class="MuiContainer-root MuiContainer-maxWidthLg"
|
class="MuiContainer-root MuiContainer-maxWidthLg"
|
||||||
>
|
>
|
||||||
|
<div
|
||||||
|
class="MuiSnackbar-root MuiSnackbar-anchorOriginTopCenter"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiPaper-root MuiSnackbarContent-root MuiPaper-elevation6"
|
||||||
|
direction="down"
|
||||||
|
role="alert"
|
||||||
|
style="opacity: 1; transform: scale(1, 1); transition: opacity 225ms cubic-bezier(0.4, 0, 0.2, 1) 0ms,transform 150ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiSnackbarContent-message"
|
||||||
|
>
|
||||||
|
getSoftwareVersions is not a function
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
data-testid="mocked-manifestsprovider"
|
data-testid="mocked-manifestsprovider"
|
||||||
>
|
>
|
||||||
@@ -3359,10 +3375,52 @@ exports[`App Route /package-deploy authenticated 1`] = `
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="MuiGrid-root makeStyles-textRightAlign-0 MuiGrid-item MuiGrid-grid-md-4"
|
class="MuiGrid-root MuiGrid-container MuiGrid-item MuiGrid-justify-content-xs-flex-end MuiGrid-grid-md-4"
|
||||||
>
|
>
|
||||||
|
<div
|
||||||
|
class="MuiFormControl-root MuiFormControl-marginNormal"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
class="MuiFormLabel-root MuiInputLabel-root makeStyles-whiteBackground-0 MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-outlined"
|
||||||
|
data-shrink="false"
|
||||||
|
>
|
||||||
|
Software Version
|
||||||
|
</label>
|
||||||
|
<div
|
||||||
|
class="MuiInputBase-root MuiOutlinedInput-root MuiInputBase-formControl"
|
||||||
|
>
|
||||||
|
<select
|
||||||
|
aria-invalid="false"
|
||||||
|
class="MuiSelect-root MuiSelect-select MuiSelect-outlined MuiInputBase-input MuiOutlinedInput-input"
|
||||||
|
/>
|
||||||
|
<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
|
<button
|
||||||
class="MuiButtonBase-root MuiButton-root MuiButton-contained makeStyles-formControl-0 makeStyles-textField-0 MuiButton-containedPrimary Mui-disabled Mui-disabled"
|
class="MuiButtonBase-root MuiButton-root MuiButton-text MuiButton-textPrimary Mui-disabled Mui-disabled"
|
||||||
disabled=""
|
disabled=""
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
type="submit"
|
type="submit"
|
||||||
@@ -3370,7 +3428,16 @@ exports[`App Route /package-deploy authenticated 1`] = `
|
|||||||
<span
|
<span
|
||||||
class="MuiButton-label"
|
class="MuiButton-label"
|
||||||
>
|
>
|
||||||
Deploy
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
import React, { useContext, useState } from "react";
|
import React, { useContext, useState } from "react";
|
||||||
|
|
||||||
import api from "../../services/updatesAPI";
|
import api from "../../services/updatesAPI";
|
||||||
|
import { validateSoftwareVersion } from "../../utils/softwareVersions";
|
||||||
import { validateStatusMessage } from "../../utils/statusMessage";
|
import { validateStatusMessage } from "../../utils/statusMessage";
|
||||||
|
|
||||||
|
export const SELECT_VERSION = "Select Version";
|
||||||
const FINAL_UPDATE_STATES = ["package_install_complete"];
|
const FINAL_UPDATE_STATES = ["package_install_complete"];
|
||||||
const CarUpdatesContext = React.createContext();
|
const CarUpdatesContext = React.createContext();
|
||||||
|
const SELECT_VERSION_OBJ = { version: SELECT_VERSION }
|
||||||
|
|
||||||
const validateDeployClosure = (data, propertyName, errPfx) => {
|
const validateDeployClosure = (data, propertyName, errPfx) => {
|
||||||
if (data === null) {
|
if (data === null) {
|
||||||
@@ -32,6 +35,7 @@ const validateDeployFleetUpdates = (data) => {
|
|||||||
export const CarUpdatesProvider = ({ children }) => {
|
export const CarUpdatesProvider = ({ children }) => {
|
||||||
const [busy, setBusy] = useState(false);
|
const [busy, setBusy] = useState(false);
|
||||||
const [carUpdates, setCarUpdates] = useState([]);
|
const [carUpdates, setCarUpdates] = useState([]);
|
||||||
|
const [versions, setVersions] = useState([SELECT_VERSION_OBJ]);
|
||||||
const [totalCarUpdates, setTotalCarUpdates] = useState(0);
|
const [totalCarUpdates, setTotalCarUpdates] = useState(0);
|
||||||
const [delayCount, setDelayCount] = useState(0);
|
const [delayCount, setDelayCount] = useState(0);
|
||||||
let progressTimer = 0;
|
let progressTimer = 0;
|
||||||
@@ -238,20 +242,60 @@ export const CarUpdatesProvider = ({ children }) => {
|
|||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getSoftwareVersions = async (token) => {
|
||||||
|
let result;
|
||||||
|
|
||||||
|
try {
|
||||||
|
setBusy(true);
|
||||||
|
|
||||||
|
result = await api.getSoftwareVersions(token);
|
||||||
|
if (result.error)
|
||||||
|
throw new Error(`Get software versions error. ${result.message}`);
|
||||||
|
|
||||||
|
result.data.unshift(SELECT_VERSION_OBJ)
|
||||||
|
setVersions(result.data);
|
||||||
|
} finally {
|
||||||
|
setBusy(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateManifestVersion = async (id, version, token) => {
|
||||||
|
let result;
|
||||||
|
|
||||||
|
try {
|
||||||
|
setBusy(true);
|
||||||
|
|
||||||
|
if (!validateSoftwareVersion(version)) throw new Error(`invalid version ${version}`);
|
||||||
|
|
||||||
|
result = await api.updateManifestVersion(id, version, token);
|
||||||
|
if (result.error)
|
||||||
|
throw new Error(`Update manifest version error. ${result.message}`);
|
||||||
|
} finally {
|
||||||
|
setBusy(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CarUpdatesContext.Provider
|
<CarUpdatesContext.Provider
|
||||||
value={{
|
value={{
|
||||||
busy,
|
busy,
|
||||||
carUpdates,
|
carUpdates,
|
||||||
totalCarUpdates,
|
totalCarUpdates,
|
||||||
|
versions,
|
||||||
cancelUpdate,
|
cancelUpdate,
|
||||||
deployCarUpdates,
|
deployCarUpdates,
|
||||||
deployFleetUpdates,
|
deployFleetUpdates,
|
||||||
getCarUpdates,
|
getCarUpdates,
|
||||||
getLog,
|
getLog,
|
||||||
|
getSoftwareVersions,
|
||||||
getVINUpdates,
|
getVINUpdates,
|
||||||
startMonitor,
|
startMonitor,
|
||||||
stopMonitor,
|
stopMonitor,
|
||||||
|
updateManifestVersion,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
25
src/components/Controls/DropDownList/index.jsx
Normal file
25
src/components/Controls/DropDownList/index.jsx
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import { FormControl, InputLabel, Select } from "@material-ui/core";
|
||||||
|
|
||||||
|
export const DropDownList = ({data, label, value, labelField, valueField, onChange, classes, ...others}) => {
|
||||||
|
return (
|
||||||
|
<FormControl
|
||||||
|
variant="outlined"
|
||||||
|
margin="normal"
|
||||||
|
>
|
||||||
|
<InputLabel className={classes.whiteBackground}>
|
||||||
|
{label}
|
||||||
|
</InputLabel>
|
||||||
|
<Select
|
||||||
|
native
|
||||||
|
value={value}
|
||||||
|
variant="outlined"
|
||||||
|
onChange={onChange}
|
||||||
|
{...others}
|
||||||
|
>
|
||||||
|
{data && data.map((item, index) => (
|
||||||
|
<option key={index} value={item[valueField || "value"]}>{item[labelField || "label"]}</option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,18 +1,19 @@
|
|||||||
import { Button, FormControlLabel, Grid, Switch, Typography } from "@material-ui/core";
|
import { Button, FormControlLabel, Grid, Switch, Typography } from "@material-ui/core";
|
||||||
import clsx from "clsx";
|
import SendIcon from "@material-ui/icons/Send";
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { Redirect, useParams } from "react-router";
|
import { Redirect, useParams } from "react-router";
|
||||||
|
|
||||||
import { logger } from "../../../services/monitoring";
|
import { logger } from "../../../services/monitoring";
|
||||||
import { LocalDateTimeString } from "../../../utils/dates";
|
import { LocalDateTimeString } from "../../../utils/dates";
|
||||||
import { Permissions } from "../../../utils/roles";
|
import { Permissions } from "../../../utils/roles";
|
||||||
import { CarUpdatesProvider, useCarUpdatesContext } from "../../Contexts/CarUpdatesContext";
|
import { CarUpdatesProvider, SELECT_VERSION, useCarUpdatesContext } from "../../Contexts/CarUpdatesContext";
|
||||||
import { FleetProvider } from "../../Contexts/FleetContext";
|
import { FleetProvider } from "../../Contexts/FleetContext";
|
||||||
import { ManifestsProvider, useManifestsContext } from "../../Contexts/ManifestsContext";
|
import { ManifestsProvider, useManifestsContext } from "../../Contexts/ManifestsContext";
|
||||||
import { useStatusContext } from "../../Contexts/StatusContext";
|
import { useStatusContext } from "../../Contexts/StatusContext";
|
||||||
import { useUserContext } from "../../Contexts/UserContext";
|
import { useUserContext } from "../../Contexts/UserContext";
|
||||||
import { VehicleProvider } from "../../Contexts/VehicleContext";
|
import { VehicleProvider } from "../../Contexts/VehicleContext";
|
||||||
import CarSelectionTable from "../../Controls/CarSelectionTable";
|
import CarSelectionTable from "../../Controls/CarSelectionTable";
|
||||||
|
import { DropDownList } from "../../Controls/DropDownList";
|
||||||
import FleetSelectionTable from "../../Controls/FleetSelectionTable";
|
import FleetSelectionTable from "../../Controls/FleetSelectionTable";
|
||||||
import { RoleWrap } from "../../Controls/RoleWrap";
|
import { RoleWrap } from "../../Controls/RoleWrap";
|
||||||
import SearchField from "../../Controls/SearchField";
|
import SearchField from "../../Controls/SearchField";
|
||||||
@@ -25,7 +26,7 @@ const MainForm = () => {
|
|||||||
const [updateType, setUpdateType] = useState(CAR_UPDATE);
|
const [updateType, setUpdateType] = useState(CAR_UPDATE);
|
||||||
const {manifest_id} = useParams();
|
const {manifest_id} = useParams();
|
||||||
const {getManifests, manifests, busy} = useManifestsContext();
|
const {getManifests, manifests, busy} = useManifestsContext();
|
||||||
const {deployCarUpdates, deployFleetUpdates} = useCarUpdatesContext();
|
const {deployCarUpdates, deployFleetUpdates, getSoftwareVersions, versions, updateManifestVersion} = useCarUpdatesContext();
|
||||||
const {
|
const {
|
||||||
groups,
|
groups,
|
||||||
providers,
|
providers,
|
||||||
@@ -39,6 +40,7 @@ const MainForm = () => {
|
|||||||
const [createDate, setCreateDate] = useState("");
|
const [createDate, setCreateDate] = useState("");
|
||||||
const [selected, setSelected] = useState([]);
|
const [selected, setSelected] = useState([]);
|
||||||
const [search, setSearch] = useState("");
|
const [search, setSearch] = useState("");
|
||||||
|
const [softwareVersion, setSoftwareVersion] = useState(SELECT_VERSION);
|
||||||
const [redirect, setRedirect] = useState("");
|
const [redirect, setRedirect] = useState("");
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
|
|
||||||
@@ -77,6 +79,8 @@ const MainForm = () => {
|
|||||||
const data = {
|
const data = {
|
||||||
manifest_id: parseInt(manifest_id),
|
manifest_id: parseInt(manifest_id),
|
||||||
}
|
}
|
||||||
|
await updateManifestVersion(manifest_id, softwareVersion, token);
|
||||||
|
|
||||||
if (updateType === CAR_UPDATE) {
|
if (updateType === CAR_UPDATE) {
|
||||||
data.vins = selected;
|
data.vins = selected;
|
||||||
await deployCarUpdates(data, token);
|
await deployCarUpdates(data, token);
|
||||||
@@ -96,13 +100,18 @@ const MainForm = () => {
|
|||||||
|
|
||||||
const getData = async () => {
|
const getData = async () => {
|
||||||
try {
|
try {
|
||||||
getManifests({id: parseInt(manifest_id)}, token);
|
await getManifests({id: parseInt(manifest_id)}, token);
|
||||||
|
await getSoftwareVersions(token);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setMessage(e.message);
|
setMessage(e.message);
|
||||||
logger.warn(e.stack);
|
logger.warn(e.stack);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const changeVersion = (e) => {
|
||||||
|
setSoftwareVersion(e.target.value);
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getData();
|
getData();
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
@@ -162,16 +171,22 @@ const MainForm = () => {
|
|||||||
<Grid item md={4} className={classes.textCenterAlign}>
|
<Grid item md={4} className={classes.textCenterAlign}>
|
||||||
<SearchField classes={classes} onSearch={handleSearch}/>
|
<SearchField classes={classes} onSearch={handleSearch}/>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item md={4} className={classes.textRightAlign}>
|
<Grid item md={4} container justifyContent="flex-end">
|
||||||
|
<DropDownList
|
||||||
|
label="Software Version"
|
||||||
|
labelField="version"
|
||||||
|
valueField="version"
|
||||||
|
value={softwareVersion}
|
||||||
|
data={versions}
|
||||||
|
classes={classes}
|
||||||
|
onChange={changeVersion} />
|
||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={busy || selected.length === 0}
|
disabled={busy || selected.length === 0 || softwareVersion === SELECT_VERSION}
|
||||||
variant="contained"
|
|
||||||
color="primary"
|
color="primary"
|
||||||
className={clsx(classes.formControl, classes.textField)}
|
|
||||||
onClick={onSubmit}
|
onClick={onSubmit}
|
||||||
>
|
>
|
||||||
{busy ? "Deploying..." : "Deploy"}
|
<SendIcon />
|
||||||
</Button>
|
</Button>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|||||||
@@ -113,12 +113,11 @@ exports[`Manifest Details Component Render 1`] = `
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="MuiFormControl-root makeStyles-form-0 MuiFormControl-marginNormal MuiFormControl-fullWidth"
|
class="MuiFormControl-root MuiFormControl-marginNormal"
|
||||||
>
|
>
|
||||||
<label
|
<label
|
||||||
class="MuiFormLabel-root MuiInputLabel-root makeStyles-whiteBackground-0 MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-outlined"
|
class="MuiFormLabel-root MuiInputLabel-root makeStyles-whiteBackground-0 MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-outlined"
|
||||||
data-shrink="false"
|
data-shrink="false"
|
||||||
for="manifest-type"
|
|
||||||
>
|
>
|
||||||
Type
|
Type
|
||||||
</label>
|
</label>
|
||||||
@@ -128,8 +127,6 @@ exports[`Manifest Details Component Render 1`] = `
|
|||||||
<select
|
<select
|
||||||
aria-invalid="false"
|
aria-invalid="false"
|
||||||
class="MuiSelect-root MuiSelect-select MuiSelect-outlined MuiInputBase-input MuiOutlinedInput-input"
|
class="MuiSelect-root MuiSelect-select MuiSelect-outlined MuiInputBase-input MuiOutlinedInput-input"
|
||||||
id="send-manifest-type"
|
|
||||||
name="manifest-type"
|
|
||||||
>
|
>
|
||||||
<option
|
<option
|
||||||
value="standard"
|
value="standard"
|
||||||
@@ -169,14 +166,13 @@ exports[`Manifest Details Component Render 1`] = `
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="MuiFormControl-root makeStyles-form-0 MuiFormControl-marginNormal MuiFormControl-fullWidth"
|
class="MuiFormControl-root MuiFormControl-marginNormal"
|
||||||
>
|
>
|
||||||
<label
|
<label
|
||||||
class="MuiFormLabel-root MuiInputLabel-root makeStyles-whiteBackground-0 MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-shrink MuiInputLabel-outlined MuiFormLabel-filled"
|
class="MuiFormLabel-root MuiInputLabel-root makeStyles-whiteBackground-0 MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-shrink MuiInputLabel-outlined MuiFormLabel-filled"
|
||||||
data-shrink="true"
|
data-shrink="true"
|
||||||
for="manifest-active"
|
|
||||||
>
|
>
|
||||||
Type
|
Active
|
||||||
</label>
|
</label>
|
||||||
<div
|
<div
|
||||||
class="MuiInputBase-root MuiOutlinedInput-root MuiInputBase-formControl"
|
class="MuiInputBase-root MuiOutlinedInput-root MuiInputBase-formControl"
|
||||||
@@ -184,18 +180,16 @@ exports[`Manifest Details Component Render 1`] = `
|
|||||||
<select
|
<select
|
||||||
aria-invalid="false"
|
aria-invalid="false"
|
||||||
class="MuiSelect-root MuiSelect-select MuiSelect-outlined MuiInputBase-input MuiOutlinedInput-input"
|
class="MuiSelect-root MuiSelect-select MuiSelect-outlined MuiInputBase-input MuiOutlinedInput-input"
|
||||||
id="send-manifest-active"
|
|
||||||
name="manifest-active"
|
|
||||||
>
|
>
|
||||||
<option
|
<option
|
||||||
value="true"
|
value="true"
|
||||||
>
|
>
|
||||||
active
|
Active
|
||||||
</option>
|
</option>
|
||||||
<option
|
<option
|
||||||
value="false"
|
value="false"
|
||||||
>
|
>
|
||||||
archived
|
Archived
|
||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
<svg
|
<svg
|
||||||
|
|||||||
@@ -1,17 +1,23 @@
|
|||||||
|
import { Button, FormControl, TextField } from "@material-ui/core";
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { useParams } from "react-router-dom";
|
|
||||||
import { Redirect } from "react-router";
|
import { Redirect } from "react-router";
|
||||||
import useStyles from "../../useStyles";
|
import { useParams } from "react-router-dom";
|
||||||
import { ManifestsProvider, useManifestsContext } from "../../Contexts/ManifestsContext";
|
import { ManifestsProvider, useManifestsContext } from "../../Contexts/ManifestsContext";
|
||||||
import { useUserContext } from "../../Contexts/UserContext";
|
|
||||||
import { useStatusContext } from "../../Contexts/StatusContext";
|
import { useStatusContext } from "../../Contexts/StatusContext";
|
||||||
import { Button, FormControl, InputLabel, Select, TextField } from "@material-ui/core";
|
import { useUserContext } from "../../Contexts/UserContext";
|
||||||
|
import { DropDownList } from "../../Controls/DropDownList";
|
||||||
|
import useStyles from "../../useStyles";
|
||||||
|
|
||||||
const manifestTypes = [
|
const manifestTypes = [
|
||||||
{ value: "standard", label: "Standard" },
|
{ value: "standard", label: "Standard" },
|
||||||
{ value: "forced", label: "Forced" },
|
{ value: "forced", label: "Forced" },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const activeStates = [
|
||||||
|
{value: true, label: "Active" },
|
||||||
|
{value: false, label: "Archived" },
|
||||||
|
];
|
||||||
|
|
||||||
const emptyManifest = {
|
const emptyManifest = {
|
||||||
name: "",
|
name: "",
|
||||||
version: "",
|
version: "",
|
||||||
@@ -138,53 +144,8 @@ const MainForm = () => {
|
|||||||
fullWidth
|
fullWidth
|
||||||
onChange={changeName}
|
onChange={changeName}
|
||||||
/>
|
/>
|
||||||
<FormControl
|
<DropDownList label="Type" data={manifestTypes} classes={classes} onChange={changeType} value={type} />
|
||||||
className={classes.form}
|
<DropDownList label="Active" data={activeStates} classes={classes} onChange={changeActive} value={active}/>
|
||||||
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>
|
|
||||||
<FormControl
|
|
||||||
className={classes.form}
|
|
||||||
variant="outlined"
|
|
||||||
fullWidth
|
|
||||||
margin="normal"
|
|
||||||
>
|
|
||||||
<InputLabel htmlFor="manifest-active" className={classes.whiteBackground}>
|
|
||||||
Type
|
|
||||||
</InputLabel>
|
|
||||||
<Select
|
|
||||||
native
|
|
||||||
value={active}
|
|
||||||
variant="outlined"
|
|
||||||
inputProps={{
|
|
||||||
name: "manifest-active",
|
|
||||||
id: "send-manifest-active",
|
|
||||||
}}
|
|
||||||
onChange={changeActive}
|
|
||||||
>
|
|
||||||
<option key={0} value={true}>active</option>
|
|
||||||
<option key={1} value={false}>archived</option>
|
|
||||||
</Select>
|
|
||||||
</FormControl>
|
|
||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
aria-label="send command"
|
aria-label="send command"
|
||||||
|
|||||||
@@ -26,6 +26,16 @@ const updatesAPI = {
|
|||||||
cancelCarUpdate: async (id, token) => {
|
cancelCarUpdate: async (id, token) => {
|
||||||
return { message: "OK" };
|
return { message: "OK" };
|
||||||
},
|
},
|
||||||
|
|
||||||
|
getSoftwareVersions: async (token) => {
|
||||||
|
return {
|
||||||
|
"data": ["2023.02.01.0.0.A", "2023.02.01.0.0.B"]
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
updateManifestVersion: async (_id, version) => {
|
||||||
|
return { version };
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default updatesAPI;
|
export default updatesAPI;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
addQueryParams, errorHandler, fetchRespHandler, getAuthHeaderOptions
|
addQueryParams, errorHandler, fetchRespHandler, getAuthHeaderOptions
|
||||||
} from "../utils/http";
|
} from "../utils/http";
|
||||||
|
|
||||||
const API_ENDPOINT = process.env.REACT_APP_OTA_SERVICE_URL;
|
const API_ENDPOINT = process.env.REACT_APP_OTA_SERVICE_URL;
|
||||||
@@ -86,6 +86,31 @@ const updatesAPI = {
|
|||||||
.then(fetchRespHandler)
|
.then(fetchRespHandler)
|
||||||
.catch(errorHandler);
|
.catch(errorHandler);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
getSoftwareVersions: async (token) => {
|
||||||
|
return fetch(`${API_ENDPOINT}/manifest/versions`, {
|
||||||
|
method: "GET",
|
||||||
|
headers: Object.assign(
|
||||||
|
{ "Content-Type": "application/json" },
|
||||||
|
getAuthHeaderOptions(token)
|
||||||
|
),
|
||||||
|
})
|
||||||
|
.then(fetchRespHandler)
|
||||||
|
.catch(errorHandler);
|
||||||
|
},
|
||||||
|
|
||||||
|
updateManifestVersion: async (id, version, token) => {
|
||||||
|
return fetch(`${API_ENDPOINT}/manifests/${id}/version`, {
|
||||||
|
method: "PUT",
|
||||||
|
headers: Object.assign(
|
||||||
|
{ "Content-Type": "application/json" },
|
||||||
|
getAuthHeaderOptions(token),
|
||||||
|
),
|
||||||
|
body: JSON.stringify({ version }),
|
||||||
|
})
|
||||||
|
.then(fetchRespHandler)
|
||||||
|
.catch(errorHandler);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export default updatesAPI;
|
export default updatesAPI;
|
||||||
|
|||||||
5
src/utils/softwareVersions.js
Normal file
5
src/utils/softwareVersions.js
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
const rxSoftwareVersion = /^\d{4}\.(0[1-9]|1[0-2])\.\d{2}\.\d{2}(\.[\d\w]{1})?$/i;
|
||||||
|
|
||||||
|
export const validateSoftwareVersion = (version) => {
|
||||||
|
return rxSoftwareVersion.test(version);
|
||||||
|
}
|
||||||
19
src/utils/softwareVersions.test.js
Normal file
19
src/utils/softwareVersions.test.js
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import { validateSoftwareVersion } from "./softwareVersions";
|
||||||
|
|
||||||
|
describe("Software versions", () => {
|
||||||
|
it("validation", () =>{
|
||||||
|
expect(validateSoftwareVersion("2023.12.01.01.A")).toEqual(true);
|
||||||
|
expect(validateSoftwareVersion("2023.10.01.01.A")).toEqual(true);
|
||||||
|
expect(validateSoftwareVersion("2023.09.01.01.A")).toEqual(true);
|
||||||
|
expect(validateSoftwareVersion("2023.13.01.01.A")).toEqual(false);
|
||||||
|
expect(validateSoftwareVersion("2023.12.01.01")).toEqual(true);
|
||||||
|
expect(validateSoftwareVersion("2023.10.01.01")).toEqual(true);
|
||||||
|
expect(validateSoftwareVersion("2023.09.01.01")).toEqual(true);
|
||||||
|
expect(validateSoftwareVersion("2023.13.01.01")).toEqual(false);
|
||||||
|
expect(validateSoftwareVersion("2023.12.01")).toEqual(false);
|
||||||
|
expect(validateSoftwareVersion("2023.10.AA.01")).toEqual(false);
|
||||||
|
expect(validateSoftwareVersion("2023.09.01.AA")).toEqual(false);
|
||||||
|
expect(validateSoftwareVersion("202A.09.01.01")).toEqual(false);
|
||||||
|
expect(validateSoftwareVersion("2023.1A.01.01")).toEqual(false);
|
||||||
|
})
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user