Merge pull request #488 from Fisker-Inc/CEC-5443

CEC-5443 - Ability to add Flashpack/ECU mappings
This commit is contained in:
Paul Adamsen
2024-01-24 09:26:20 -05:00
committed by GitHub
17 changed files with 1889 additions and 0 deletions

View File

@@ -456,6 +456,28 @@ exports[`App Route / authenticated 1`] = `
/>
</a>
</li>
<li>
<a
aria-disabled="false"
class="MuiButtonBase-root MuiListItem-root MuiListItem-gutters MuiListItem-button"
href="/tools/flashpacks"
role="button"
tabindex="0"
>
<div
class="MuiListItemText-root"
>
<span
class="MuiTypography-root MuiListItemText-primary MuiTypography-body1 MuiTypography-displayBlock"
>
Flashpack
</span>
</div>
<span
class="MuiTouchRipple-root"
/>
</a>
</li>
</ul>
</ul>
</div>
@@ -1853,6 +1875,28 @@ exports[`App Route /home authenticated 1`] = `
/>
</a>
</li>
<li>
<a
aria-disabled="false"
class="MuiButtonBase-root MuiListItem-root MuiListItem-gutters MuiListItem-button"
href="/tools/flashpacks"
role="button"
tabindex="0"
>
<div
class="MuiListItemText-root"
>
<span
class="MuiTypography-root MuiListItemText-primary MuiTypography-body1 MuiTypography-displayBlock"
>
Flashpack
</span>
</div>
<span
class="MuiTouchRipple-root"
/>
</a>
</li>
</ul>
</ul>
</div>
@@ -2627,6 +2671,28 @@ exports[`App Route /issue-info authenticated 1`] = `
/>
</a>
</li>
<li>
<a
aria-disabled="false"
class="MuiButtonBase-root MuiListItem-root MuiListItem-gutters MuiListItem-button"
href="/tools/flashpacks"
role="button"
tabindex="0"
>
<div
class="MuiListItemText-root"
>
<span
class="MuiTypography-root MuiListItemText-primary MuiTypography-body1 MuiTypography-displayBlock"
>
Flashpack
</span>
</div>
<span
class="MuiTouchRipple-root"
/>
</a>
</li>
</ul>
</ul>
</div>
@@ -3239,6 +3305,28 @@ exports[`App Route /issues authenticated 1`] = `
/>
</a>
</li>
<li>
<a
aria-disabled="false"
class="MuiButtonBase-root MuiListItem-root MuiListItem-gutters MuiListItem-button"
href="/tools/flashpacks"
role="button"
tabindex="0"
>
<div
class="MuiListItemText-root"
>
<span
class="MuiTypography-root MuiListItemText-primary MuiTypography-body1 MuiTypography-displayBlock"
>
Flashpack
</span>
</div>
<span
class="MuiTouchRipple-root"
/>
</a>
</li>
</ul>
</ul>
</div>
@@ -4220,6 +4308,28 @@ exports[`App Route /package-deploy authenticated 1`] = `
/>
</a>
</li>
<li>
<a
aria-disabled="false"
class="MuiButtonBase-root MuiListItem-root MuiListItem-gutters MuiListItem-button"
href="/tools/flashpacks"
role="button"
tabindex="0"
>
<div
class="MuiListItemText-root"
>
<span
class="MuiTypography-root MuiListItemText-primary MuiTypography-body1 MuiTypography-displayBlock"
>
Flashpack
</span>
</div>
<span
class="MuiTouchRipple-root"
/>
</a>
</li>
</ul>
</ul>
</div>
@@ -5324,6 +5434,28 @@ exports[`App Route /package-status authenticated 1`] = `
/>
</a>
</li>
<li>
<a
aria-disabled="false"
class="MuiButtonBase-root MuiListItem-root MuiListItem-gutters MuiListItem-button"
href="/tools/flashpacks"
role="button"
tabindex="0"
>
<div
class="MuiListItemText-root"
>
<span
class="MuiTypography-root MuiListItemText-primary MuiTypography-body1 MuiTypography-displayBlock"
>
Flashpack
</span>
</div>
<span
class="MuiTouchRipple-root"
/>
</a>
</li>
</ul>
</ul>
</div>
@@ -6383,6 +6515,28 @@ exports[`App Route /packages authenticated 1`] = `
/>
</a>
</li>
<li>
<a
aria-disabled="false"
class="MuiButtonBase-root MuiListItem-root MuiListItem-gutters MuiListItem-button"
href="/tools/flashpacks"
role="button"
tabindex="0"
>
<div
class="MuiListItemText-root"
>
<span
class="MuiTypography-root MuiListItemText-primary MuiTypography-body1 MuiTypography-displayBlock"
>
Flashpack
</span>
</div>
<span
class="MuiTouchRipple-root"
/>
</a>
</li>
</ul>
</ul>
</div>
@@ -7561,6 +7715,28 @@ exports[`App Route /page-not-found authenticated 1`] = `
/>
</a>
</li>
<li>
<a
aria-disabled="false"
class="MuiButtonBase-root MuiListItem-root MuiListItem-gutters MuiListItem-button"
href="/tools/flashpacks"
role="button"
tabindex="0"
>
<div
class="MuiListItemText-root"
>
<span
class="MuiTypography-root MuiListItemText-primary MuiTypography-body1 MuiTypography-displayBlock"
>
Flashpack
</span>
</div>
<span
class="MuiTouchRipple-root"
/>
</a>
</li>
</ul>
</ul>
</div>
@@ -8189,6 +8365,28 @@ exports[`App Route /tools/certificates/add authenticated 1`] = `
/>
</a>
</li>
<li>
<a
aria-disabled="false"
class="MuiButtonBase-root MuiListItem-root MuiListItem-gutters MuiListItem-button"
href="/tools/flashpacks"
role="button"
tabindex="0"
>
<div
class="MuiListItemText-root"
>
<span
class="MuiTypography-root MuiListItemText-primary MuiTypography-body1 MuiTypography-displayBlock"
>
Flashpack
</span>
</div>
<span
class="MuiTouchRipple-root"
/>
</a>
</li>
</ul>
</ul>
</div>
@@ -9483,6 +9681,28 @@ exports[`App Route /tools/sms/send authenticated 1`] = `
/>
</a>
</li>
<li>
<a
aria-disabled="false"
class="MuiButtonBase-root MuiListItem-root MuiListItem-gutters MuiListItem-button"
href="/tools/flashpacks"
role="button"
tabindex="0"
>
<div
class="MuiListItemText-root"
>
<span
class="MuiTypography-root MuiListItemText-primary MuiTypography-body1 MuiTypography-displayBlock"
>
Flashpack
</span>
</div>
<span
class="MuiTouchRipple-root"
/>
</a>
</li>
</ul>
</ul>
</div>
@@ -10210,6 +10430,28 @@ exports[`App Route /vehicle-add authenticated 1`] = `
/>
</a>
</li>
<li>
<a
aria-disabled="false"
class="MuiButtonBase-root MuiListItem-root MuiListItem-gutters MuiListItem-button"
href="/tools/flashpacks"
role="button"
tabindex="0"
>
<div
class="MuiListItemText-root"
>
<span
class="MuiTypography-root MuiListItemText-primary MuiTypography-body1 MuiTypography-displayBlock"
>
Flashpack
</span>
</div>
<span
class="MuiTouchRipple-root"
/>
</a>
</li>
</ul>
</ul>
</div>
@@ -11392,6 +11634,28 @@ exports[`App Route /vehicle-status authenticated 1`] = `
/>
</a>
</li>
<li>
<a
aria-disabled="false"
class="MuiButtonBase-root MuiListItem-root MuiListItem-gutters MuiListItem-button"
href="/tools/flashpacks"
role="button"
tabindex="0"
>
<div
class="MuiListItemText-root"
>
<span
class="MuiTypography-root MuiListItemText-primary MuiTypography-body1 MuiTypography-displayBlock"
>
Flashpack
</span>
</div>
<span
class="MuiTouchRipple-root"
/>
</a>
</li>
</ul>
</ul>
</div>
@@ -12503,6 +12767,28 @@ exports[`App Route /vehicles authenticated 1`] = `
/>
</a>
</li>
<li>
<a
aria-disabled="false"
class="MuiButtonBase-root MuiListItem-root MuiListItem-gutters MuiListItem-button"
href="/tools/flashpacks"
role="button"
tabindex="0"
>
<div
class="MuiListItemText-root"
>
<span
class="MuiTypography-root MuiListItemText-primary MuiTypography-body1 MuiTypography-displayBlock"
>
Flashpack
</span>
</div>
<span
class="MuiTouchRipple-root"
/>
</a>
</li>
</ul>
</ul>
</div>

View File

@@ -30,6 +30,10 @@ export const VehicleProvider = ({ children }) => {
const [totalFleets, setTotalFleets] = useState(0);
const [models, setModels] = useState([]);
const [years, setYears] = useState([]);
const [flashpacks, setFlashpacks] = useState([]);
const [totalFlashpacks, setTotalFlashpacks] = useState(0);
const [flashpackECUMappings, setFlashpackECUMappings] = useState([])
const [totalFlashpackECUMappings, setTotalFlashpackECUMappings] = useState(0)
const addConnections = async (cars, token) => {
try {
@@ -287,6 +291,90 @@ export const VehicleProvider = ({ children }) => {
}
};
const getAllFlashpacks = async (options, token) => {
try {
setBusy(true);
const result = await api.getAllFlashpacks(options, token);
if (result.error) {
throw new Error(`Get all flashpacks error. ${result.message}`);
}
setFlashpacks(result.data);
if (options && options.offset === 0 && result.total) {
setTotalFlashpacks(result.total);
}
return result;
} finally {
setBusy(false);
}
};
const getFlashpackECUMappings = async (model, year, flashpack, options, token) => {
try {
setBusy(true);
const result = await api.getFlashpackECUMappings(model, year, flashpack, options, token);
if (result.error) {
throw new Error(`Get flashpack ecu mappings error. ${result.message}`);
}
setFlashpackECUMappings(result.data);
if (options && options.offset === 0 && result.total) {
setTotalFlashpackECUMappings(result.total);
}
return result;
} finally {
setBusy(false);
}
};
const addFlashpackVersion = async (model, year, flashpack, carFlashpackVersions, token) => {
try {
setBusy(true);
const data = {
"car_model": model,
"car_year": year,
"flashpack": flashpack,
"car_flashpack_versions": carFlashpackVersions,
}
const result = await api.addFlashpackVersion(data, token)
if (result.error) {
throw new Error(`Add flashpack version error. ${result.message}`);
}
return result;
} finally {
setBusy(false)
}
}
const deleteFlashpackVersion = async (model, year, flashpack, token) => {
try {
setBusy(true);
const data = {
"car_model": model,
"car_year": year,
"flashpack": flashpack,
}
const result = await api.deleteFlashpackVersion(data, token);
if (result.error) {
throw new Error(`Delete flashpack ecu mappings error. ${result.message}`);
}
return result;
} finally {
setBusy(false);
}
};
return (
<VehicleContext.Provider
value={{
@@ -316,6 +404,14 @@ export const VehicleProvider = ({ children }) => {
getVersionLog,
uploadConfig,
addTags,
flashpacks,
totalFlashpacks,
getAllFlashpacks,
totalFlashpackECUMappings,
flashpackECUMappings,
getFlashpackECUMappings,
addFlashpackVersion,
deleteFlashpackVersion,
}}
>
{children}

View File

@@ -187,6 +187,44 @@ export const useVehicleContext = () => ({
total: 2,
}
}),
getAllFlashpacks: jest.fn((options, token) => {
return {
data: [
{
flashpack: "44.4",
car_model: "Ocean",
car_year: "2023",
},
{
flashpack: "38.4",
car_model: "Ocean",
car_year: "2023",
},
],
total: 2,
}
}),
getFlashpackECUMappings: jest.fn((model, year, flashpack, options, token) => {
return {
data: [
{
flashpack: "44.4",
car_model: "Ocean",
car_year: "2023",
car_ecu_name: "ADAS",
car_ecu_version: "99",
},
{
flashpack: "44.4",
car_model: "Ocean",
car_year: "2023",
car_ecu_name: "PDI",
car_ecu_version: "11",
},
],
total: 2,
}
}),
});
export const setBusy = (val) => {

View File

@@ -0,0 +1,293 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`FlashpackAdd Render 1`] = `
<div>
<div
data-testid="mocked-vehicleprovider"
>
<div
data-testid="mocked-statusprovider"
>
<div
data-testid="mocked-userprovider"
>
<div
data-testid="mocked-vehicleprovider"
>
<div
class="makeStyles-paper-0"
>
<form
action="{onSubmit}"
class="makeStyles-form-0"
novalidate=""
>
<div>
<div
class="MuiFormControl-root MuiTextField-root MuiFormControl-marginNormal MuiFormControl-fullWidth"
>
<label
class="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-outlined Mui-required Mui-required"
data-shrink="false"
for="carModel"
id="carModel-label"
>
Model
<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="carModel"
maxlength="255"
name="carModel"
required=""
type="text"
value=""
/>
<fieldset
aria-hidden="true"
class="PrivateNotchedOutline-root-0 MuiOutlinedInput-notchedOutline"
>
<legend
class="PrivateNotchedOutline-legendLabelled-0"
>
<span>
Model
 *
</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-outlined Mui-required Mui-required"
data-shrink="false"
for="carYear"
id="carYear-label"
>
Year
<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="carYear"
maxlength="255"
name="carYear"
required=""
type="number"
value=""
/>
<fieldset
aria-hidden="true"
class="PrivateNotchedOutline-root-0 MuiOutlinedInput-notchedOutline"
>
<legend
class="PrivateNotchedOutline-legendLabelled-0"
>
<span>
Year
 *
</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-outlined Mui-required Mui-required"
data-shrink="false"
for="flashpack"
id="flashpack-label"
>
Flashpack Number
<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="flashpack"
maxlength="255"
name="flashpack"
required=""
type="number"
value=""
/>
<fieldset
aria-hidden="true"
class="PrivateNotchedOutline-root-0 MuiOutlinedInput-notchedOutline"
>
<legend
class="PrivateNotchedOutline-legendLabelled-0"
>
<span>
Flashpack Number
 *
</span>
</legend>
</fieldset>
</div>
</div>
<div
class="container"
>
<div
class="input_container"
>
<div
class="MuiFormControl-root MuiTextField-root MuiFormControl-marginNormal"
>
<label
class="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-outlined"
data-shrink="false"
for="ecuName"
id="ecuName-label"
>
ECU Name
</label>
<div
class="MuiInputBase-root MuiOutlinedInput-root MuiInputBase-formControl"
>
<input
aria-invalid="false"
class="MuiInputBase-input MuiOutlinedInput-input"
id="ecuName"
maxlength="255"
name="ecuName"
type="text"
value=""
/>
<fieldset
aria-hidden="true"
class="PrivateNotchedOutline-root-0 MuiOutlinedInput-notchedOutline"
>
<legend
class="PrivateNotchedOutline-legendLabelled-0"
>
<span>
ECU Name
</span>
</legend>
</fieldset>
</div>
</div>
<div
class="MuiFormControl-root MuiTextField-root MuiFormControl-marginNormal"
>
<label
class="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-outlined"
data-shrink="false"
for="ecuVersion"
id="ecuVersion-label"
>
ECU Version
</label>
<div
class="MuiInputBase-root MuiOutlinedInput-root MuiInputBase-formControl"
>
<input
aria-invalid="false"
class="MuiInputBase-input MuiOutlinedInput-input"
id="ecuVersion"
maxlength="255"
name="ecuVersion"
type="text"
value=""
/>
<fieldset
aria-hidden="true"
class="PrivateNotchedOutline-root-0 MuiOutlinedInput-notchedOutline"
>
<legend
class="PrivateNotchedOutline-legendLabelled-0"
>
<span>
ECU Version
</span>
</legend>
</fieldset>
</div>
</div>
<button
aria-label="Add"
class="MuiButtonBase-root MuiIconButton-root MuiIconButton-colorPrimary MuiIconButton-sizeSmall"
tabindex="0"
type="button"
>
<span
class="MuiIconButton-label"
>
<svg
aria-hidden="true"
aria-label="Add"
class="MuiSvgIcon-root"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm5 11h-4v4h-2v-4H7v-2h4V7h2v4h4v2z"
/>
</svg>
</span>
<span
class="MuiTouchRipple-root"
/>
</button>
</div>
</div>
</div>
<button
class="MuiButtonBase-root MuiButton-root MuiButton-contained makeStyles-submit-0 MuiButton-containedPrimary MuiButton-fullWidth"
tabindex="0"
type="submit"
>
<span
class="MuiButton-label"
>
Submit
</span>
<span
class="MuiTouchRipple-root"
/>
</button>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
`;

View File

@@ -0,0 +1,239 @@
import {
Button,
IconButton,
TextField
} from "@material-ui/core";
import React, { useEffect, useState } from "react";
import AddCircleIcon from "@material-ui/icons/AddCircle";
import DeleteIcon from "@material-ui/icons/Delete";
import { Redirect } from "react-router";
import { logger } from "../../../services/monitoring";
import { useStatusContext } from "../../Contexts/StatusContext";
import { useVehicleContext, VehicleProvider } from "../../Contexts/VehicleContext";
import { useUserContext } from "../../Contexts/UserContext";
import useStyles from "../../useStyles";
const MainForm = () => {
const {
token: {
idToken: { jwtToken: token },
},
} = useUserContext();
const classes = useStyles();
const [redirect, setRedirect] = useState(null);
const { setMessage, setTitle, setSitePath } = useStatusContext();
const [carModel, setCarModel] = useState("");
const [carYear, setCarYear] = useState();
const [flashpack, setFlashpack] = useState();
const [mappingInputs, setMappingInputs] = useState([{ ecuName: "", ecuVersion: "" }]);
const {
addFlashpackVersion,
busy,
} = useVehicleContext();
useEffect(() => {
setTitle(`Add Flashpack Version`);
setSitePath([
{
label: "Tools",
link: "/tools/flashpacks",
},
{
label: "Flashpack Versions",
link: "/tools/flashpacks",
},
{
label: `Add Flashpack Version`,
},
]);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const onCarModelChange = (event) => {
setCarModel(event.target.value);
}
const onCarYearChange = (event) => {
setCarYear(event.target.value);
}
const onFlashpackChange = (event) => {
setFlashpack(event.target.value);
}
const onSubmit = async (event) => {
try {
event.preventDefault();
const carFlashpackVersions = []
for (let i = 0; i < mappingInputs.length; i++) {
mappingInputs[i] && mappingInputs[i].ecuName && mappingInputs[i].ecuVersion &&
carFlashpackVersions.push({
"car_model": carModel,
"car_year": parseInt(carYear),
"flashpack": flashpack,
"car_ecu_name": mappingInputs[i].ecuName,
"car_ecu_version": mappingInputs[i].ecuVersion,
})
}
const result = await addFlashpackVersion(carModel, parseInt(carYear), flashpack, carFlashpackVersions, token);
if (!result || result.error) return;
setMessage(`Added ${carYear} ${carModel} ${flashpack}`);
setRedirect(`/tools/flashpack/${carModel}/${carYear}/${flashpack}`);
} catch (e) {
setMessage(e.message);
logger.warn(e.stack);
}
};
if (redirect && redirect.length > 0) {
return <Redirect to={redirect} />;
}
// code for handling the dynamic input fields adapted from
// https://blog.stackademic.com/how-to-dynamically-add-input-fields-on-button-click-in-reactjs-ddf8d8fe495b
const handleAddMappingInput = () => {
setMappingInputs([...mappingInputs, { ecuName: "", ecuVersion: "", }]);
};
const handleChange = (event, index) => {
let { name, value } = event.target;
let onChangeValue = [...mappingInputs];
onChangeValue[index][name] = value;
setMappingInputs(onChangeValue);
};
const handleDeleteMappingInput = (index) => {
const newArray = [...mappingInputs];
newArray.splice(index, 1);
setMappingInputs(newArray);
};
return (
<div className={classes.paper}>
<form className={classes.form} noValidate action="{onSubmit}">
<div>
<TextField
id="carModel"
name="carModel"
label="Model"
variant="outlined"
margin="normal"
inputProps={{
maxLength: "255",
}}
required
fullWidth
value={carModel}
onChange={onCarModelChange}
type="text"
/>
<TextField
id="carYear"
name="carYear"
label="Year"
variant="outlined"
margin="normal"
inputProps={{
maxLength: "255",
}}
required
fullWidth
value={carYear}
onChange={onCarYearChange}
type="number"
/>
<TextField
id="flashpack"
name="flashpack"
label="Flashpack Number"
variant="outlined"
margin="normal"
inputProps={{
maxLength: "255",
}}
required
fullWidth
value={flashpack}
onChange={onFlashpackChange}
type="number"
/>
<div className="container">
{mappingInputs.map((item, index) => (
<div className="input_container" key={index}>
<TextField
id="ecuName"
name="ecuName"
label="ECU Name"
variant="outlined"
margin="normal"
inputProps={{
maxLength: "255",
}}
value={item.ecuName}
onChange={(event) => handleChange(event, index)}
type="text"
/>
<TextField
id="ecuVersion"
name="ecuVersion"
label="ECU Version"
variant="outlined"
margin="normal"
inputProps={{
maxLength: "255",
}}
value={item.ecuVersion}
onChange={(event) => handleChange(event, index)}
type="text"
/>
{mappingInputs.length > 1 && (
<IconButton
onClick={() => handleDeleteMappingInput(index)}
aria-label={`Delete`}
size="small"
color="primary"
>
<DeleteIcon aria-label={`Delete`} />
</IconButton>
)}
{index === mappingInputs.length - 1 && (
<IconButton
onClick={() => handleAddMappingInput()}
aria-label={`Add`}
size="small"
color="primary"
>
<AddCircleIcon aria-label={`Add`} />
</IconButton>
)}
</div>
))}
</div>
</div>
<Button
type="submit"
disabled={busy}
fullWidth
variant="contained"
color="primary"
className={classes.submit}
onClick={onSubmit}
>
{busy ? "Submitting..." : "Submit"}
</Button>
</form>
</div>
);
};
const FlashpackAdd = () => (
<VehicleProvider>
<MainForm />
</VehicleProvider>
);
export default FlashpackAdd;

View File

@@ -0,0 +1,46 @@
jest.mock("../../Contexts/VehicleContext");
jest.mock("../../Contexts/StatusContext");
jest.mock("../../Contexts/UserContext");
jest.mock("@material-ui/core/utils/unstable_useId", () =>
jest.fn().mockReturnValue("mui-test-id")
);
import { render, waitFor } from "@testing-library/react";
import { MemoryRouter, Route } from "react-router-dom";
import { VehicleProvider } from "../../Contexts/VehicleContext";
import { StatusProvider } from "../../Contexts/StatusContext";
import { UserProvider, setToken } from "../../Contexts/UserContext";
import { TEST_AUTH_OBJECT_FISKER } from "../../../utils/testing";
import MainForm from "./index";
import addSnapshotSerializer from "../../../utils/snapshot";
const renderFlashpackAdd = async () => {
const { container } = render(
<VehicleProvider>
<StatusProvider>
<UserProvider>
<MemoryRouter initialEntries={["/tools/flashpack/add"]}>
<Route path="/tools/flashpack/add">
<MainForm />
</Route>
</MemoryRouter>
</UserProvider>
</StatusProvider>
</VehicleProvider>
);
await waitFor(() => {
/* render */
});
return container;
};
describe("FlashpackAdd", () => {
beforeAll(() => {
addSnapshotSerializer(expect);
});
it("Render", async () => {
setToken(TEST_AUTH_OBJECT_FISKER);
const container = await renderFlashpackAdd();
expect(container).toMatchSnapshot();
});
});

View File

@@ -0,0 +1,99 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`FlashpackDetails Render 1`] = `
<div>
<div
data-testid="mocked-vehicleprovider"
>
<div
data-testid="mocked-statusprovider"
>
<div
data-testid="mocked-userprovider"
>
<div
data-testid="mocked-vehicleprovider"
>
<div>
<table
class="MuiTable-root"
>
<thead
class="MuiTableHead-root"
>
<tr
class="MuiTableRow-root MuiTableRow-head"
>
<th
class="MuiTableCell-root MuiTableCell-head MuiTableCell-alignCenter"
scope="col"
>
<span
aria-disabled="false"
class="MuiButtonBase-root MuiTableSortLabel-root"
role="button"
tabindex="0"
>
ECU Name
<svg
aria-hidden="true"
class="MuiSvgIcon-root MuiTableSortLabel-icon MuiTableSortLabel-iconDirectionAsc"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M20 12l-1.41-1.41L13 16.17V4h-2v12.17l-5.58-5.59L4 12l8 8 8-8z"
/>
</svg>
</span>
</th>
<th
class="MuiTableCell-root MuiTableCell-head MuiTableCell-alignCenter"
scope="col"
>
<span
aria-disabled="false"
class="MuiButtonBase-root MuiTableSortLabel-root"
role="button"
tabindex="0"
>
ECU Version
<svg
aria-hidden="true"
class="MuiSvgIcon-root MuiTableSortLabel-icon MuiTableSortLabel-iconDirectionAsc"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M20 12l-1.41-1.41L13 16.17V4h-2v12.17l-5.58-5.59L4 12l8 8 8-8z"
/>
</svg>
</span>
</th>
</tr>
</thead>
<tbody
class="MuiTableBody-root"
/>
<tfoot
class="MuiTableFooter-root"
>
<tr
class="MuiTableRow-root MuiTableRow-footer"
>
<td
class="MuiTableCell-root MuiTableCell-footer MuiTableCell-alignCenter"
colspan="8"
>
No Flashpack ECU Mappings
</td>
</tr>
</tfoot>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
`;

View File

@@ -0,0 +1,173 @@
import React, { useEffect, useState } from "react";
import { useParams } from "react-router-dom";
import {
Table,
TableBody,
TableCell,
TableFooter,
TablePagination,
TableRow,
} from "@material-ui/core";
import { logger } from "../../../services/monitoring";
import { useVehicleContext, VehicleProvider } from "../../Contexts/VehicleContext";
import { useStatusContext } from "../../Contexts/StatusContext";
import { useUserContext } from "../../Contexts/UserContext";
import TableHeaderSortable from "../../Table/HeaderSortable";
import { useLocalStorage } from "../../useLocalStorage";
import useStyles from "../../useStyles";
const tableColumns = [
{
id: "car_ecu_name",
label: "ECU Name",
},
{
id: "car_ecu_version",
label: "ECU Version",
},
];
const PAGE_SIZE = "FLASHPACK_MAPPINGS_TABLE_PAGE_SIZE";
const MainForm = () => {
const { model, year, flashpack } = useParams();
const classes = useStyles();
const { setMessage, setTitle, setSitePath } = useStatusContext();
const [pageSize, setPageSize] = useLocalStorage(PAGE_SIZE, 10);
const [pageIndex, setPageIndex] = useState(0);
const [orderBy, setOrderBy] = useState("flashpack");
const [order, setOrder] = useState("desc");
const {
getFlashpackECUMappings,
flashpackECUMappings,
totalFlashpackECUMappings,
} = useVehicleContext();
const {
token: {
idToken: { jwtToken: token },
},
} = useUserContext();
useEffect(() => {
setTitle(`${year} ${model} Flashpack Version ${flashpack}`);
setSitePath([
{
label: "Tools",
link: "/tools/flashpacks",
},
{
label: "Flashpack Versions",
link: "/tools/flashpacks",
},
{
label: `Flashpack ${flashpack}`,
},
]);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
useEffect(() => {
loadFlashpackECUMappings();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [token, token, pageIndex, pageSize, orderBy, order]);
const loadFlashpackECUMappings = async () => {
try {
if (!token) return;
await getFlashpackECUMappings(
model,
year,
flashpack,
{
limit: pageSize,
offset: pageSize * pageIndex,
order: `${orderBy} ${order}`,
},
token
);
} catch (e) {
setMessage(e.message);
logger.warn(e.stack);
}
};
const handleChangePageIndex = (event, newIndex) => {
setPageIndex(newIndex);
};
const handleChangePageSize = (event) => {
setPageSize(parseInt(event.target.value, 10));
setPageIndex(0);
};
const handleSort = (event, property) => {
try {
if (property === orderBy) {
if (order === "asc") {
setOrder("desc");
} else {
setOrder("asc");
}
} else {
setOrderBy(property);
setOrder("asc");
}
} catch (e) {
logger.warn(e.stack);
}
};
return (
<div>
<Table>
<TableHeaderSortable
classes={classes}
orderBy={orderBy}
order={order}
columnData={tableColumns}
onSortRequest={handleSort}
/>
<TableBody>
{flashpackECUMappings && flashpackECUMappings.map((row, index) => (
<TableRow key={index}>
<TableCell align="center">
{row.car_ecu_name}
</TableCell>
<TableCell align="center">
{row.car_ecu_version}
</TableCell>
</TableRow>
))}
</TableBody>
<TableFooter>
<TableRow>
{!flashpackECUMappings || flashpackECUMappings.length === 0 ? (
<TableCell colSpan={8} align="center">No Flashpack ECU Mappings</TableCell>
) : (
<TablePagination
rowsPerPageOptions={[5, 10, 25, 100]}
colSpan={2}
count={totalFlashpackECUMappings}
rowsPerPage={pageSize}
page={pageIndex}
SelectProps={{
inputProps: { "aria-label": "rows per page" },
native: true,
}}
onPageChange={handleChangePageIndex}
onRowsPerPageChange={handleChangePageSize}
/>)}
</TableRow>
</TableFooter>
</Table>
</div>
);
};
const FlashpackDetails = () => (
<VehicleProvider>
<MainForm />
</VehicleProvider>
);
export default FlashpackDetails;

View File

@@ -0,0 +1,46 @@
jest.mock("../../Contexts/VehicleContext");
jest.mock("../../Contexts/StatusContext");
jest.mock("../../Contexts/UserContext");
jest.mock("@material-ui/core/utils/unstable_useId", () =>
jest.fn().mockReturnValue("mui-test-id")
);
import { render, waitFor } from "@testing-library/react";
import { MemoryRouter, Route } from "react-router-dom";
import { VehicleProvider } from "../../Contexts/VehicleContext";
import { StatusProvider } from "../../Contexts/StatusContext";
import { UserProvider, setToken } from "../../Contexts/UserContext";
import { TEST_AUTH_OBJECT_FISKER } from "../../../utils/testing";
import MainForm from "./index";
import addSnapshotSerializer from "../../../utils/snapshot";
const renderFlashpackDetails = async () => {
const { container } = render(
<VehicleProvider>
<StatusProvider>
<UserProvider>
<MemoryRouter initialEntries={["/tools/flashpack/Ocean/2023/44.4"]}>
<Route path="/tools/flashpack/:carModel/:carYear/:flashpack">
<MainForm carModel="Ocean" carYear="2023" flashpack="44.4" />
</Route>
</MemoryRouter>
</UserProvider>
</StatusProvider>
</VehicleProvider>
);
await waitFor(() => {
/* render */
});
return container;
};
describe("FlashpackDetails", () => {
beforeAll(() => {
addSnapshotSerializer(expect);
});
it("Render", async () => {
setToken(TEST_AUTH_OBJECT_FISKER);
const container = await renderFlashpackDetails();
expect(container).toMatchSnapshot();
});
});

View File

@@ -0,0 +1,175 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Flashpack Render 1`] = `
<div>
<div
data-testid="mocked-vehicleprovider"
>
<div
data-testid="mocked-statusprovider"
>
<div
data-testid="mocked-userprovider"
>
<div
data-testid="mocked-vehicleprovider"
>
<div>
<div
class="MuiGrid-root makeStyles-root-0 MuiGrid-container MuiGrid-spacing-xs-2"
>
<div
class="MuiGrid-root makeStyles-textJustifyAlign-0 makeStyles-actionsBar-0 MuiGrid-item MuiGrid-grid-md-4"
>
<a
class="makeStyles-labelInline-0"
href="/tools/flashpack/add"
>
<svg
aria-hidden="true"
class="MuiSvgIcon-root MuiSvgIcon-fontSizeLarge"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm5 11h-4v4h-2v-4H7v-2h4V7h2v4h4v2z"
/>
</svg>
</a>
</div>
</div>
<table
class="MuiTable-root"
>
<thead
class="MuiTableHead-root"
>
<tr
class="MuiTableRow-root MuiTableRow-head"
>
<th
aria-sort="descending"
class="MuiTableCell-root MuiTableCell-head MuiTableCell-alignCenter"
scope="col"
>
<span
aria-disabled="false"
class="MuiButtonBase-root MuiTableSortLabel-root MuiTableSortLabel-active"
role="button"
tabindex="0"
>
Flashpack Number
<span
class="makeStyles-hiddenSortSpan-0"
>
sorted descending
</span>
<svg
aria-hidden="true"
class="MuiSvgIcon-root MuiTableSortLabel-icon MuiTableSortLabel-iconDirectionDesc"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M20 12l-1.41-1.41L13 16.17V4h-2v12.17l-5.58-5.59L4 12l8 8 8-8z"
/>
</svg>
</span>
</th>
<th
class="MuiTableCell-root MuiTableCell-head MuiTableCell-alignCenter"
scope="col"
>
<span
aria-disabled="false"
class="MuiButtonBase-root MuiTableSortLabel-root"
role="button"
tabindex="0"
>
Model
<svg
aria-hidden="true"
class="MuiSvgIcon-root MuiTableSortLabel-icon MuiTableSortLabel-iconDirectionAsc"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M20 12l-1.41-1.41L13 16.17V4h-2v12.17l-5.58-5.59L4 12l8 8 8-8z"
/>
</svg>
</span>
</th>
<th
class="MuiTableCell-root MuiTableCell-head MuiTableCell-alignCenter"
scope="col"
>
<span
aria-disabled="false"
class="MuiButtonBase-root MuiTableSortLabel-root"
role="button"
tabindex="0"
>
Year
<svg
aria-hidden="true"
class="MuiSvgIcon-root MuiTableSortLabel-icon MuiTableSortLabel-iconDirectionAsc"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M20 12l-1.41-1.41L13 16.17V4h-2v12.17l-5.58-5.59L4 12l8 8 8-8z"
/>
</svg>
</span>
</th>
<th
class="MuiTableCell-root MuiTableCell-head MuiTableCell-alignCenter"
scope="col"
>
<span
aria-disabled="false"
class="MuiButtonBase-root MuiTableSortLabel-root"
role="button"
tabindex="0"
>
Delete
<svg
aria-hidden="true"
class="MuiSvgIcon-root MuiTableSortLabel-icon MuiTableSortLabel-iconDirectionAsc"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M20 12l-1.41-1.41L13 16.17V4h-2v12.17l-5.58-5.59L4 12l8 8 8-8z"
/>
</svg>
</span>
</th>
</tr>
</thead>
<tbody
class="MuiTableBody-root"
/>
<tfoot
class="MuiTableFooter-root"
>
<tr
class="MuiTableRow-root MuiTableRow-footer"
>
<td
class="MuiTableCell-root MuiTableCell-footer MuiTableCell-alignCenter"
colspan="8"
>
No Flashpack Numbers
</td>
</tr>
</tfoot>
</table>
<div />
</div>
</div>
</div>
</div>
</div>
</div>
`;

View File

@@ -0,0 +1,219 @@
import {
Grid,
IconButton,
Table,
TableBody,
TableCell,
TableFooter,
TablePagination,
TableRow,
} from "@material-ui/core";
import AddCircleIcon from "@material-ui/icons/AddCircle";
import DeleteIcon from "@material-ui/icons/Delete";
import clsx from "clsx";
import { Link } from "react-router-dom";
import { logger } from "../../services/monitoring";
import React, { useEffect, useState } from "react";
import { useVehicleContext, VehicleProvider } from "../Contexts/VehicleContext";
import { useStatusContext } from "../Contexts/StatusContext";
import { useUserContext } from "../Contexts/UserContext";
import TableHeaderSortable from "../Table/HeaderSortable";
import { useLocalStorage } from "../useLocalStorage";
import DeleteConfirmation from "../DeleteConfirmation";
import { RoleWrap } from "../Controls/RoleWrap";
import useStyles from "../useStyles";
const tableColumns = [
{
id: "flashpack",
label: "Flashpack Number",
},
{
id: "car_model",
label: "Model",
},
{
id: "car_year",
label: "Year",
},
{
id: "delete",
label: "Delete",
},
];
const PAGE_SIZE = "FLASHPACKS_TABLE_PAGE_SIZE";
const MainForm = () => {
const classes = useStyles();
const { setMessage, setTitle, setSitePath } = useStatusContext();
const [pageSize, setPageSize] = useLocalStorage(PAGE_SIZE, 10);
const [pageIndex, setPageIndex] = useState(0);
const [showDeleteModal, setShowDeleteModal] = useState(false);
const [rowToDelete, setRowToDelete] = useState({});
const {
getAllFlashpacks,
flashpacks,
totalFlashpacks,
deleteFlashpackVersion,
} = useVehicleContext();
const {
token: {
idToken: { jwtToken: token },
},
groups,
providers,
} = useUserContext();
useEffect(() => {
setTitle("Flashpack Versions");
setSitePath([
{
label: "Tools",
link: "/tools/flashpacks",
},
{
label: "Flashpack Versions",
},
]);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
useEffect(() => {
loadFlashpacks();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [token, token, pageIndex, pageSize]);
const loadFlashpacks = async () => {
try {
if (!token) return;
await getAllFlashpacks(
{
limit: pageSize,
offset: pageSize * pageIndex,
},
token
);
} catch (e) {
setMessage(e.message);
logger.warn(e.stack);
}
};
const handleChangePageIndex = (event, newIndex) => {
setPageIndex(newIndex);
};
const handleChangePageSize = (event) => {
setPageSize(parseInt(event.target.value, 10));
setPageIndex(0);
};
const onDeleteClick = (row) => {
setRowToDelete(row);
setShowDeleteModal(true);
}
const sendDelete = async () => {
if (rowToDelete) {
try {
const result = await deleteFlashpackVersion(rowToDelete.car_model, rowToDelete.car_year, rowToDelete.flashpack, token);
if (!result || result.error) return;
setMessage(`Deleted ${rowToDelete.car_year} ${rowToDelete.car_model} ${rowToDelete.flashpack}`);
loadFlashpacks();
} catch (e) {
setMessage(e.message);
logger.warn(e.stack);
}
}
};
return (
<div>
<Grid container className={classes.root} spacing={2}>
<Grid item md={4} className={clsx(classes.textJustifyAlign, classes.actionsBar)}>
<Link to={`/tools/flashpack/add`} className={classes.labelInline}>
<AddCircleIcon fontSize="large" />
</Link>
</Grid>
</Grid>
<Table>
<TableHeaderSortable
classes={classes}
orderBy={"flashpack"}
order={"desc"}
columnData={tableColumns}
onSortRequest={() => { return null }}
/>
<TableBody>
{flashpacks && flashpacks.map((row, index) => (
<TableRow key={row.flashpack + row.car_model}>
<TableCell align="center">
<Link to={`/tools/flashpack/${row.car_model}/${row.car_year}/${row.flashpack}`}>
{row.flashpack}
</Link>
</TableCell>
<TableCell align="center">
{row.car_model}
</TableCell>
<TableCell align="center">
{row.car_year}
</TableCell>
<TableCell align="center">
<RoleWrap
groups={groups}
providers={providers}
rolesPerProvider={Permissions.FiskerDelete}
>
<IconButton
onClick={() => onDeleteClick(row)}
aria-label={`Send delete for ${row.car_year} ${row.car_model} ${row.flashpack}`}
size="small"
color="primary"
>
<DeleteIcon aria-label={`Delete ${row.car_year} ${row.car_model} ${row.flashpack}`} />
</IconButton>
</RoleWrap>
</TableCell>
</TableRow>
))}
</TableBody>
<TableFooter>
<TableRow>
{!flashpacks || flashpacks.length === 0 ? (
<TableCell colSpan={8} align="center">No Flashpack Numbers</TableCell>
) : (
<TablePagination
rowsPerPageOptions={[5, 10, 25, 100]}
colSpan={4}
count={totalFlashpacks}
rowsPerPage={pageSize}
page={pageIndex}
SelectProps={{
inputProps: { "aria-label": "rows per page" },
native: true,
}}
onPageChange={handleChangePageIndex}
onRowsPerPageChange={handleChangePageSize}
/>)}
</TableRow>
</TableFooter>
</Table>
<DeleteConfirmation
message={rowToDelete && rowToDelete.car_year + " " + rowToDelete.car_model + " " + rowToDelete.flashpack}
open={showDeleteModal}
close={() => setShowDeleteModal(false)}
deleteFunction={sendDelete}
/>
</div>
);
};
const Flashpacks = () => (
<VehicleProvider>
<MainForm />
</VehicleProvider>
);
export default Flashpacks;

View File

@@ -0,0 +1,46 @@
jest.mock("../Contexts/VehicleContext");
jest.mock("../Contexts/StatusContext");
jest.mock("../Contexts/UserContext");
jest.mock("@material-ui/core/utils/unstable_useId", () =>
jest.fn().mockReturnValue("mui-test-id")
);
import { render, waitFor } from "@testing-library/react";
import { MemoryRouter, Route } from "react-router-dom";
import { VehicleProvider } from "../Contexts/VehicleContext";
import { StatusProvider } from "../Contexts/StatusContext";
import { UserProvider, setToken } from "../Contexts/UserContext";
import { TEST_AUTH_OBJECT_FISKER } from "../../utils/testing";
import MainForm from "./index";
import addSnapshotSerializer from "../../utils/snapshot";
const renderFlashpack = async () => {
const { container } = render(
<VehicleProvider>
<StatusProvider>
<UserProvider>
<MemoryRouter initialEntries={["/tools/flashpacks"]} >
<Route path="/tools/flashpacks">
<MainForm />
</Route>
</MemoryRouter>
</UserProvider>
</StatusProvider>
</VehicleProvider >
);
await waitFor(() => {
/* render */
});
return container;
};
describe("Flashpack", () => {
beforeAll(() => {
addSnapshotSerializer(expect);
});
it("Render", async () => {
setToken(TEST_AUTH_OBJECT_FISKER);
const container = await renderFlashpack();
expect(container).toMatchSnapshot();
});
});

View File

@@ -79,6 +79,11 @@ const menuData = [
to: "/tools/sms/send",
rolesPerProvider: Permissions.FiskerCreate,
},
{
label: "Flashpack",
to: "/tools/flashpacks",
rolesPerProvider: Permissions.FiskerRead,
},
],
},
];

View File

@@ -375,6 +375,28 @@ exports[`SideMenu Authenticated 1`] = `
/>
</a>
</li>
<li>
<a
aria-disabled="false"
class="MuiButtonBase-root MuiListItem-root MuiListItem-gutters MuiListItem-button"
href="/tools/flashpacks"
role="button"
tabindex="0"
>
<div
class="MuiListItemText-root"
>
<span
class="MuiTypography-root MuiListItemText-primary MuiTypography-body1 MuiTypography-displayBlock"
>
Flashpack
</span>
</div>
<span
class="MuiTouchRipple-root"
/>
</a>
</li>
</ul>
</ul>
</div>

View File

@@ -40,6 +40,9 @@ const SecurityDLL = React.lazy(() => import("../Magna/SecurityDLL"));
const SMSSend = React.lazy(() => import("../SMS/Send"));
const SuppliersList = React.lazy(() => import("../Suppliers/List"));
const SupplierDetails = React.lazy(() => import("../Suppliers/Details"));
const Flashpacks = React.lazy(() => import("../Flashpack"));
const FlashpackDetails = React.lazy(() => import("../Flashpack/Details"));
const FlashpackAdd = React.lazy(() => import("../Flashpack/Add"))
const Datascope = React.lazy(() => import("../Dashboard"));
const SumsRxSwin = React.lazy(() => import("../SUMS"));
const SumsRxSwinAdd = React.lazy(() => import("../SUMS/Add"));
@@ -280,6 +283,33 @@ const SiteRoutes = () => {
rolesPerGroup={Permissions.FiskerCreate}
providers={providers}
/>
<AuthRoute
path="/tools/flashpacks"
render={() => <Flashpacks />}
type={TYPES.PROTECTED}
token={token}
groups={groups}
rolesPerGroup={Permissions.FiskerRead}
providers={providers}
/>
<AuthRoute
path="/tools/flashpack/:model/:year/:flashpack"
render={() => <FlashpackDetails />}
type={TYPES.PROTECTED}
token={token}
groups={groups}
rolesPerGroup={Permissions.FiskerRead}
providers={providers}
/>
<AuthRoute
path="/tools/flashpack/add"
render={() => <FlashpackAdd />}
type={TYPES.PROTECTED}
token={token}
groups={groups}
rolesPerGroup={Permissions.FiskerCreate}
providers={providers}
/>
<AuthRoute
path="/suppliers"
render={() => <SuppliersList />}

View File

@@ -199,6 +199,36 @@ const vehiclesAPI = {
sendDiagnosticCommand: async (search) => ({
Message: `remote diagnostic command sent to ${search.vins.length} vehicles`
}),
getAllFlashpacks: async (token) => {
return {
data: ["41.14", "43.19"],
};
},
getFlashpackECUMappings: async (model, year, flashpack, token) => {
return {
"data": [
{
"flashpack": "41.14",
"car_model": "Ocean",
"car_year": 2023,
"car_ecu_name": "ADAS",
"car_ecu_version": "ADASVersion"
},
{
"flashpack": "41.14",
"car_model": "Ocean",
"car_year": 2023, "car_ecu_name": "ACUN",
"car_ecu_version": "ACUNVersion"
}
],
};
},
addFlashpackECUMapping: async (data, token) => {
return { message: "Created" };
},
deleteFlashpackECUMapping: async (data, token) => {
return { message: "Deleted" };
},
};
export default vehiclesAPI;

View File

@@ -260,6 +260,52 @@ const vehiclesAPI = {
}).then(fetchRespHandler)
.catch(errorHandler)
},
getAllFlashpacks: async (options, token) => {
return fetch(addQueryParams(`${API_ENDPOINT}/flashpack_versions`, options), {
method: "GET",
headers: Object.assign(
{ "Content-Type": "application/json" },
getAuthHeaderOptions(token)
),
}).then(fetchRespHandler)
.catch(errorHandler)
},
getFlashpackECUMappings: async (model, year, flashpack, options, token) => {
return fetch(addQueryParams(`${API_ENDPOINT}/flashpack_version_ecu_mappings/${model}/${year}/${flashpack}`, options), {
method: "GET",
headers: Object.assign(
{ "Content-Type": "application/json" },
getAuthHeaderOptions(token)
),
}).then(fetchRespHandler)
.catch(errorHandler)
},
addFlashpackVersion: async (data, token) => {
return fetch(`${API_ENDPOINT}/flashpack_version`, {
method: "POST",
headers: Object.assign(
{ "Content-Type": "application/json" },
getAuthHeaderOptions(token),
),
body: JSON.stringify(data),
}).then(fetchRespHandler)
.catch(errorHandler)
},
deleteFlashpackVersion: async (data, token) => {
return fetch(`${API_ENDPOINT}/flashpack_version`, {
method: "DELETE",
headers: Object.assign(
{ "Content-Type": "application/json" },
getAuthHeaderOptions(token)
),
body: JSON.stringify(data),
}).then(fetchRespHandler)
.catch(errorHandler)
},
};
export default vehiclesAPI;