Development (#94)
* CEC-371 Car ECU display (#79) * Merge Development (#53) * Use responsive iframe control for charts (#49) * Use responsive iframe control to charts * Move external Grafana link to Dashboard page * Remove unused embedded style class * Add button label * added delete button to deploy packages * Fix unit test warning Remove unused route from test * Fix styling of button * minor fixes per pr review Co-authored-by: jcw-fisker <jwatson@fiskerinc.com> Co-authored-by: John Cotten Watson <83605808+jcw-fisker@users.noreply.github.com> * Development Merge (#57) * CEC-287 Car connection status (#59) (#60) * Car connection status * Formatting * Merge Development (#64) * Add connection status to vehicles page * ConnectedIcon control * Handle Style * Development (#67) * preliminary map for vehicles * weird zoom bug * passing react tests * fixing warnings and updating snapshots * update node environment to 14 * addressing comments by changing variable types and adding styles to home page title * adding CODEOWNERS file * fixing token error * CEC-371 Update car ECUs display (#78) * Clean up className styles Update car status page to show update and ECUs * Add update ecu version button Show all ECUs on car status page Only show car ecus for search Co-authored-by: jcw-fisker <jwatson@fiskerinc.com> Co-authored-by: John Cotten Watson <83605808+jcw-fisker@users.noreply.github.com> Co-authored-by: Drew Taylor <69828061+drew-fisker@users.noreply.github.com> * CEC-394 Car update log (#81) * CEC-394 Car update status control * Remove Datadog RUM Remove package update components Move control components into Controls folder Add Car update status page * Display update status log Clean up unused update package code * Remove console.logs * no vars * adding timestamp to vehicle popup * modifying vehicle data query * removing extraneous code * removing console log * Clean up SonarCloud warnings (#83) * Clean up SonarCloud warnings * Bogus security warning * Fix another warning * Fix unauthorized locations request * Fix update progress control * CEC-563 New manifest format (#88) * Add ManifestCreateContext Update create manifest page * Finish UI changes and API integration * Fixes * Fix test * Remove manifest ECU file version and type * Fixes * Add manifest ecu file type control * Fix Sonar warnings * Fix test * Update codeowners * Formatting * CEC-553 Change file type to string (#90) * CEC-553 File type uses string enum * Fix test timeout * Fix * Merge development * Increase timeout * Clean up (#95) * Clean up Mock missing methods * Smell Co-authored-by: jcw-fisker <jwatson@fiskerinc.com> Co-authored-by: John Cotten Watson <83605808+jcw-fisker@users.noreply.github.com> Co-authored-by: Drew Taylor <69828061+drew-fisker@users.noreply.github.com> Co-authored-by: Drew Taylor <dtaylor@fiskerinc.com>
This commit is contained in:
250
src/components/Contexts/ManifestCreateContext.jsx
Normal file
250
src/components/Contexts/ManifestCreateContext.jsx
Normal file
@@ -0,0 +1,250 @@
|
||||
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",
|
||||
part_number: "",
|
||||
version: "",
|
||||
serial_number: "",
|
||||
hw_version: "",
|
||||
vendor: "",
|
||||
configuration: "",
|
||||
fingerprint: "",
|
||||
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);
|
||||
@@ -1,17 +1,11 @@
|
||||
import React, { useContext, useState } from "react";
|
||||
|
||||
import api from "../../services/manifestsAPI";
|
||||
import { uploadFile, getCancelToken } from "../../services/uploadFile";
|
||||
|
||||
const ManifestsContext = React.createContext();
|
||||
|
||||
export const ManifestsProvider = ({ children }) => {
|
||||
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 [manifests, setManifests] = useState([]);
|
||||
const [totalManifests, setTotalManifests] = useState(0);
|
||||
|
||||
@@ -54,164 +48,14 @@ export const ManifestsProvider = ({ children }) => {
|
||||
return result;
|
||||
};
|
||||
|
||||
const validateManifest = (data, accessToken) => {
|
||||
const fileKeys = {};
|
||||
|
||||
if (!accessToken || accessToken.length === 0) {
|
||||
throw new Error("Access token required");
|
||||
}
|
||||
|
||||
if (!data) {
|
||||
throw new Error("Missing manifest data");
|
||||
}
|
||||
|
||||
if (!data.name || data.name.length === 0) {
|
||||
throw new Error("Package name required");
|
||||
}
|
||||
|
||||
if (!data.version || data.version.length === 0) {
|
||||
throw new Error("Package version required");
|
||||
}
|
||||
|
||||
if (!data.description || data.description.length === 0) {
|
||||
throw new Error("Package description required");
|
||||
}
|
||||
|
||||
if (!data.releasenotes || data.releasenotes.length === 0) {
|
||||
throw new Error("Package release notes link required");
|
||||
}
|
||||
|
||||
if (!data.files || data.files.length === 0) {
|
||||
throw new Error("Package files required");
|
||||
}
|
||||
|
||||
data.files.forEach((file) => validateFile(file, fileKeys));
|
||||
};
|
||||
|
||||
const validateFile = (file, keys) => {
|
||||
if (!file) {
|
||||
throw new Error("File data required");
|
||||
}
|
||||
|
||||
if (!file.filename || file.filename.length === 0) {
|
||||
throw new Error("Filename required");
|
||||
}
|
||||
|
||||
if (!file.name || file.name.length === 0) {
|
||||
throw new Error(`${file.filename} ECU name required`);
|
||||
}
|
||||
|
||||
if (!file.update_version || file.update_version.length === 0) {
|
||||
throw new Error(`${file.filename} version required`);
|
||||
}
|
||||
|
||||
if (!file.part_number || file.part_number.length === 0) {
|
||||
throw new Error(`${file.filename} part number required`);
|
||||
}
|
||||
|
||||
const key = `${file.name}, ${file.update_version}, ${file.filename}`;
|
||||
if (!keys[key]) {
|
||||
keys[key] = true;
|
||||
} else {
|
||||
throw new Error(`${key} already exists`);
|
||||
}
|
||||
};
|
||||
|
||||
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 createManifest = async (data, token) => {
|
||||
let result;
|
||||
|
||||
try {
|
||||
setBusy(true);
|
||||
validateManifest(data, token);
|
||||
setUploadedFiles(data.files);
|
||||
|
||||
await checkExistingManifest(data, token);
|
||||
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 = data.files.length; i < len; i++) {
|
||||
setUploadFileIndex(i);
|
||||
const resp = await uploadECUFile(result.id, data.files[i], token);
|
||||
if (resp.error)
|
||||
throw new Error(`Upload manifest file error. ${resp.error}`);
|
||||
}
|
||||
} finally {
|
||||
setBusy(false);
|
||||
setUploadFileIndex(0);
|
||||
setUploadedFiles([]);
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
const cancelUpload = () => {
|
||||
if (cancelUploadToken) cancelUploadToken.cancel();
|
||||
setBusy(false);
|
||||
setUploadStatus("Upload cancelled");
|
||||
setCancelUploadToken(null);
|
||||
setUploadProgress(0);
|
||||
};
|
||||
|
||||
const uploadECUFile = async (manifest_id, data, accessToken) => {
|
||||
try {
|
||||
Object.assign(data, { manifest_id });
|
||||
|
||||
const filename = data.file.name;
|
||||
const cancel = getCancelToken();
|
||||
|
||||
setBusy(true);
|
||||
setUploadProgress(0);
|
||||
setUploadStatus(`Uploading ${filename}`);
|
||||
setCancelUploadToken(cancel);
|
||||
|
||||
const result = await uploadFile(
|
||||
data,
|
||||
accessToken,
|
||||
setUploadProgress,
|
||||
cancel.token
|
||||
);
|
||||
if (result.message) {
|
||||
throw new Error(`${result.error}. ${result.message}`);
|
||||
}
|
||||
setUploadStatus(`Uploaded ${filename}`);
|
||||
setCancelUploadToken(null);
|
||||
setUploadProgress(100);
|
||||
|
||||
return result;
|
||||
} catch (e) {
|
||||
setBusy(false);
|
||||
setUploadStatus(`Error occured: ${e.message}`);
|
||||
setUploadProgress(-1);
|
||||
|
||||
return { error: e.message };
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<ManifestsContext.Provider
|
||||
value={{
|
||||
busy,
|
||||
uploadProgress,
|
||||
uploadStatus,
|
||||
uploadFileIndex,
|
||||
uploadedFiles,
|
||||
manifests,
|
||||
totalManifests,
|
||||
getManifests,
|
||||
deleteManifest,
|
||||
createManifest,
|
||||
cancelUpload,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
||||
@@ -150,16 +150,16 @@ export const UserProvider = ({ children }) => {
|
||||
return (
|
||||
<UserContext.Provider
|
||||
value={{
|
||||
fetching,
|
||||
token,
|
||||
groups,
|
||||
error,
|
||||
fetching,
|
||||
groups,
|
||||
token,
|
||||
getAuthorizeURL,
|
||||
getLogoutURL,
|
||||
setError,
|
||||
signIn,
|
||||
signOut,
|
||||
refresh,
|
||||
getAuthorizeURL,
|
||||
getLogoutURL,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useContext, useState } from "react";
|
||||
import { logger } from "../../services/monitoring";
|
||||
import api from "../../services/vehicles";
|
||||
import api from "../../services/vehiclesAPI";
|
||||
|
||||
const VehicleContext = React.createContext();
|
||||
|
||||
@@ -163,10 +163,10 @@ export const VehicleProvider = ({ children }) => {
|
||||
<VehicleContext.Provider
|
||||
value={{
|
||||
busy,
|
||||
vehicles,
|
||||
models,
|
||||
years,
|
||||
totalVehicles,
|
||||
vehicles,
|
||||
years,
|
||||
addVehicle,
|
||||
getConnections,
|
||||
getECUs,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
jest.mock("../../services/vehicles");
|
||||
jest.mock("../../services/vehiclesAPI");
|
||||
|
||||
import {
|
||||
render,
|
||||
|
||||
53
src/components/Contexts/__mocks__/ManifestCreateContext.jsx
Normal file
53
src/components/Contexts/__mocks__/ManifestCreateContext.jsx
Normal file
@@ -0,0 +1,53 @@
|
||||
import React from "react";
|
||||
|
||||
const ManifestsContext = React.createContext();
|
||||
|
||||
let busy = false;
|
||||
let ecus = [
|
||||
{
|
||||
data_id: 0,
|
||||
name: "AGS",
|
||||
part_number: "",
|
||||
version: "",
|
||||
serial_number: "",
|
||||
hw_version: "",
|
||||
vendor: "",
|
||||
configuration: "",
|
||||
fingerprint: "",
|
||||
manifest_id: 0,
|
||||
files: [
|
||||
{
|
||||
filename: "test.bin",
|
||||
order: 0,
|
||||
offset: "0",
|
||||
checksum: "",
|
||||
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(),
|
||||
});
|
||||
@@ -17,17 +17,18 @@ export const UserProvider = ({ children }) => {
|
||||
};
|
||||
|
||||
export const useUserContext = () => ({
|
||||
token,
|
||||
fetching,
|
||||
error,
|
||||
fetching,
|
||||
groups,
|
||||
signIn: jest.fn(() => signInResp),
|
||||
signOut: jest.fn(),
|
||||
token,
|
||||
getAuthorizeURL: jest.fn(() => authorizeURL),
|
||||
getLogoutURL: jest.fn(() => logoutURL),
|
||||
setError: jest.fn((value) => {
|
||||
error = value;
|
||||
}),
|
||||
signIn: jest.fn(() => signInResp),
|
||||
signOut: jest.fn(),
|
||||
refresh: jest.fn(),
|
||||
});
|
||||
|
||||
export const setToken = (val) => {
|
||||
|
||||
@@ -13,9 +13,9 @@ export const VehicleProvider = ({ children }) => {
|
||||
|
||||
export const useVehicleContext = () => ({
|
||||
busy,
|
||||
vehicles,
|
||||
totalVehicles,
|
||||
models,
|
||||
totalVehicles,
|
||||
vehicles,
|
||||
years,
|
||||
addVehicle: jest.fn(),
|
||||
getConnections: jest.fn((vins, token) => {
|
||||
@@ -58,18 +58,19 @@ export const useVehicleContext = () => ({
|
||||
total: 2,
|
||||
};
|
||||
}),
|
||||
getModels: jest.fn(() => {
|
||||
models = ["Ocean", "PEAR"];
|
||||
}),
|
||||
getLocations: jest
|
||||
.fn()
|
||||
.mockResolvedValue([
|
||||
{ altitude: 5, longitude: 10, latitude: 15, vin: "TESTVIN123" },
|
||||
]),
|
||||
getVehicles: jest.fn(() => vehicles),
|
||||
getModels: jest.fn(() => {
|
||||
models = ["Ocean", "PEAR"];
|
||||
}),
|
||||
getState: jest.fn(),
|
||||
getYears: jest.fn(() => {
|
||||
years = [2023, 2024];
|
||||
}),
|
||||
getVehicles: jest.fn(() => vehicles),
|
||||
sendCommand: jest.fn((vins, command, parameters, token) => ({
|
||||
vins,
|
||||
command,
|
||||
|
||||
Reference in New Issue
Block a user