CEC-5432: add additional options (#486)

* CEC-5432: add additional options

* fix missing dep
This commit is contained in:
Tristan Timblin
2023-11-29 16:36:18 -08:00
committed by GitHub
parent f4652b5de7
commit 5ad467b116
6 changed files with 256 additions and 40 deletions

View File

@@ -1,30 +1,72 @@
import { useState, forwardRef, useImperativeHandle } from "react"; import { useState, useEffect, forwardRef, useImperativeHandle } from "react";
import { import {
Checkbox,
FormControl, FormControl,
FormControlLabel,
FormGroup,
FormLabel,
Radio,
RadioGroup,
TextField,
} from '@material-ui/core'; } from '@material-ui/core';
import SearchSelect from "../../SearchSelect/SearchSelect"; import SearchSelect from "../../SearchSelect/SearchSelect";
import { useStatusContext } from "../../Contexts/StatusContext"; import { useStatusContext } from "../../Contexts/StatusContext";
import { useUserContext } from "../../Contexts/UserContext"; import { useUserContext } from "../../Contexts/UserContext";
import fleetsAPI from "../../../services/fleetsAPI"; import fleetsAPI from "../../../services/fleetsAPI";
import useStyles from "../../useStyles";
const DEFAULT_LOG_LEVEL = "info";
const DEFAULT_CANBUS_ENABLED = false;
const DEFAULT_DATA_LOGGER_ENABLED = false;
const DEFAULT_MAX_MEM_BUFFER_SIZE = 0;
const DEFAULT_MAX_DISK_BUFFER_SIZE = 0;
export default forwardRef(({ export default forwardRef(({
ids, ids = [],
idCSV, idCSV = "",
fleet, fleet,
}, ref) => { }, ref) => {
const [fromFleet, setFromFleet] = useState(fleet);
const [toFleet, setToFleet] = useState();
const classnames = useStyles();
const { setMessage } = useStatusContext(); const { setMessage } = useStatusContext();
const { token: { idToken: { jwtToken: token } } } = useUserContext(); const { token: { idToken: { jwtToken: token } } } = useUserContext();
const [fromFleet, setFromFleet] = useState(fleet);
const [toFleet, setToFleet] = useState();
const [selectedLogLevel, setSelectedLogLevel] = useState(DEFAULT_LOG_LEVEL);
const [canbusEnabled, setCANBusEnabled] = useState(DEFAULT_CANBUS_ENABLED);
const [dataLoggerEnabled, setDataLoggerEnabled] = useState(DEFAULT_DATA_LOGGER_ENABLED);
const [maxMemBufferSize, setMaxMemBufferSize] = useState(DEFAULT_MAX_MEM_BUFFER_SIZE);
const [maxDiskBufferSize, setMaxDiskBufferSize] = useState(DEFAULT_MAX_DISK_BUFFER_SIZE);
const logLevel = [
["trace", "Trace"],
["debug", "Debug"],
["info", "Info"],
["warning", "Warning"],
["error", "Error"],
["critical", "Critical"],
];
useImperativeHandle(ref, () => ({ useImperativeHandle(ref, () => ({
async submit() { async submit() {
const errorTracking = [false, false]; const errorTracking = [false, false];
if (toFleet) { if (toFleet) {
const payload = {
vins: ids,
log_level: selectedLogLevel,
canbus: {
enabled: canbusEnabled,
data_logger_enabled: canbusEnabled ? dataLoggerEnabled : false,
max_mem_buffer_size: parseInt(maxMemBufferSize),
max_disk_buffer_size: parseInt(maxDiskBufferSize),
}
};
await fleetsAPI await fleetsAPI
.addFleetVehicles(toFleet, ids, token) .addFleetVehicles(toFleet, payload, token)
.then((response) => { .then((response) => {
if (response.error) { if (response.error) {
errorTracking[0] = true; errorTracking[0] = true;
@@ -85,6 +127,58 @@ export default forwardRef(({
.catch(() => []); .catch(() => []);
} }
const onLogLevelChange = (event) => {
setSelectedLogLevel(event.target.value);
}
const onCANBusChange = (event) => {
setCANBusEnabled(event.target.checked);
}
const onDataLoggerChange = (event) => {
setDataLoggerEnabled(event.target.checked);
}
const onMaxMemBufferSizeChange = (event) => {
setMaxMemBufferSize(event.target.value);
}
const onMaxDiskBufferSizeChange = (event) => {
setMaxDiskBufferSize(event.target.value);
}
useEffect(() => {
if (!toFleet) {
setSelectedLogLevel(DEFAULT_LOG_LEVEL);
setCANBusEnabled(DEFAULT_CANBUS_ENABLED);
setDataLoggerEnabled(DEFAULT_DATA_LOGGER_ENABLED);
setMaxMemBufferSize(DEFAULT_MAX_MEM_BUFFER_SIZE);
setMaxDiskBufferSize(DEFAULT_MAX_DISK_BUFFER_SIZE);
}
}, [
toFleet, setSelectedLogLevel, setCANBusEnabled,
setDataLoggerEnabled, setMaxMemBufferSize, setMaxDiskBufferSize
]);
useEffect(() => {
if (!canbusEnabled) {
setDataLoggerEnabled(DEFAULT_DATA_LOGGER_ENABLED);
setMaxMemBufferSize(DEFAULT_MAX_MEM_BUFFER_SIZE);
setMaxDiskBufferSize(DEFAULT_MAX_DISK_BUFFER_SIZE);
}
}, [
canbusEnabled, setDataLoggerEnabled, setMaxMemBufferSize,
setMaxDiskBufferSize,
]);
useEffect(() => {
if (!dataLoggerEnabled) {
setMaxDiskBufferSize(DEFAULT_MAX_DISK_BUFFER_SIZE);
}
}, [
dataLoggerEnabled, setMaxDiskBufferSize,
]);
return ( return (
<div> <div>
<p> <p>
@@ -94,24 +188,87 @@ export default forwardRef(({
VINs will be removed from the "From Fleet" and added to the "To Fleet". If you would VINs will be removed from the "From Fleet" and added to the "To Fleet". If you would
like to only add or only remove the other field can be left blank. like to only add or only remove the other field can be left blank.
</p> </p>
<FormControl variant="filled" fullWidth={true}> <FormGroup row className={classnames.formGrid}>
<SearchSelect <FormControl variant="filled" className={classnames.formGridItem}>
label="Remove From Fleet" <SearchSelect
value={fromFleet} label="Remove From Fleet"
setValue={setFromFleet} value={fromFleet}
getData={searchFleets} setValue={setFromFleet}
research={true} getData={searchFleets}
research={true}
/>
</FormControl>
<FormControl variant="filled" className={classnames.formGridItem}>
<SearchSelect
label="Add To Fleet"
value={toFleet}
setValue={setToFleet}
getData={searchFleets}
research={true}
/>
</FormControl>
</FormGroup>
<FormGroup className={classnames.marginX}>
<FormControl>
<FormLabel id="demo-row-radio-buttons-group-label">Log Level</FormLabel>
<RadioGroup
aria-labelledby="demo-row-radio-buttons-group-label"
value={selectedLogLevel}
onChange={onLogLevelChange}
row
>
{logLevel.map(([value, label]) => (
<FormControlLabel
key={value}
value={value}
control={<Radio size="small" />}
label={label}
disabled={!toFleet}
/>
))}
</RadioGroup>
</FormControl>
</FormGroup>
<FormGroup row className={classnames.marginX}>
<FormControlLabel control={
<Checkbox
checked={canbusEnabled}
onChange={onCANBusChange}
disabled={!toFleet}
/>
} label="CAN Bus Enabled" />
<FormControlLabel control={
<Checkbox
checked={dataLoggerEnabled}
onChange={onDataLoggerChange}
disabled={!canbusEnabled}
/>
} label="Data Logger Enabled" />
</FormGroup>
<FormGroup row className={classnames.formGrid}>
<TextField
className={classnames.formGridItem}
id="max_mem_buffer_size"
label='Max Memory Buffer Size'
value={maxMemBufferSize}
onChange={onMaxMemBufferSizeChange}
variant="outlined"
inputProps={{ maxLength: "12" }}
type="number"
disabled={!canbusEnabled || !toFleet}
/> />
</FormControl> <TextField
<FormControl variant="filled" fullWidth={true}> className={classnames.formGridItem}
<SearchSelect id="max_disk_buffer_size"
label="Add To Fleet" label='Max Disk Buffer Size'
value={toFleet} value={maxDiskBufferSize}
setValue={setToFleet} onChange={onMaxDiskBufferSizeChange}
getData={searchFleets} variant="outlined"
research={true} inputProps={{ maxLength: "12" }}
type="number"
disabled={!dataLoggerEnabled || !toFleet}
/> />
</FormControl> </FormGroup>
</div> </div>
); );
}); });

View File

@@ -2,7 +2,7 @@ jest.mock("../../Contexts/UserContext");
jest.mock("../../Contexts/StatusContext"); jest.mock("../../Contexts/StatusContext");
jest.mock("../../../services/fleetsAPI"); jest.mock("../../../services/fleetsAPI");
import React, { useState } from "react"; import React from "react";
import { import {
render, render,
act, act,
@@ -13,28 +13,53 @@ import { TEST_AUTH_OBJECT_FISKER } from "../../../utils/testing";
import UpdateFleetVehicles from "./UpdateFleetVehicles"; import UpdateFleetVehicles from "./UpdateFleetVehicles";
import fleetsAPI from "../../../services/fleetsAPI"; import fleetsAPI from "../../../services/fleetsAPI";
jest.mock('react', () => ({
...jest.requireActual('react'),
useState: jest.fn(),
}));
jest.mock('@material-ui/core/FormControl', () => { jest.mock('@material-ui/core/FormControl', () => {
const React = require('react'); const React = require('react');
return () => <div data-testid="mock-form-control" />; return () => <div data-testid="mock-form-control" />;
}); });
jest.mock('@material-ui/core/FormControlLabel', () => {
const React = require('react');
return () => <div data-testid="mock-form-control-label" />;
});
jest.mock('@material-ui/core/FormGroup', () => {
const React = require('react');
return () => <div data-testid="mock-form-group" />;
});
jest.mock('@material-ui/core/FormLabel', () => {
const React = require('react');
return () => <div data-testid="mock-form-label" />;
});
jest.mock('@material-ui/core/Checkbox', () => {
const React = require('react');
return () => <div data-testid="mock-checkbox" />;
});
jest.mock('@material-ui/core/Radio', () => {
const React = require('react');
return () => <div data-testid="mock-radio" />;
});
jest.mock('@material-ui/core/RadioGroup', () => {
const React = require('react');
return () => <div data-testid="mock-radio-group" />;
});
jest.mock('@material-ui/core/TextField', () => {
const React = require('react');
return () => <div data-testid="mock-text-field" />;
});
describe("BulkActions/UpdateFleetVehicles", () => { describe("BulkActions/UpdateFleetVehicles", () => {
beforeAll(() => { beforeAll(() => {
setToken(TEST_AUTH_OBJECT_FISKER); setToken(TEST_AUTH_OBJECT_FISKER);
}); });
it("makes request to update the config of multiple vehicles", async () => { it("makes request add vehicle to one fleet and remove from another", async () => {
useState // const add = jest.spyOn(fleetsAPI, "addFleetVehicles");
.mockReturnValueOnce(["Default-Test", jest.fn()])
.mockReturnValueOnce([["Default-Test"], jest.fn()])
.mockReturnValueOnce(["Default-Test", jest.fn()])
.mockReturnValueOnce([["Default-Test"], jest.fn()]);
const add = jest.spyOn(fleetsAPI, "addFleetVehicles");
const remove = jest.spyOn(fleetsAPI, "deleteFleetVehicles"); const remove = jest.spyOn(fleetsAPI, "deleteFleetVehicles");
const ref = React.createRef(); const ref = React.createRef();
@@ -43,15 +68,16 @@ describe("BulkActions/UpdateFleetVehicles", () => {
<UserProvider> <UserProvider>
<UpdateFleetVehicles <UpdateFleetVehicles
ref={ref} ref={ref}
ids={["TESTVIN1234567890"]} ids={["TESTVIN123456789a", "TESTVIN123456789b", "TESTVIN123456789c"]}
idCSV="" idCSV=""
fleet="US-WEST"
/> />
</UserProvider> </UserProvider>
</StatusProvider> </StatusProvider>
); );
await act(async () => ref.current.submit()); await act(async () => ref.current.submit());
expect(add).toHaveBeenCalled(); // expect(add).toHaveBeenCalled();
expect(remove).toHaveBeenCalled(); expect(remove).toHaveBeenCalled();
}); });
}); });

View File

@@ -162,6 +162,10 @@ export const FleetProvider = ({ children }) => {
}; };
const watchFleetVehicles = new Polling(async ({ token }) => { const watchFleetVehicles = new Polling(async ({ token }) => {
if (carUpdateIdsRef.current.length === 0) {
return;
}
const result = await updatesApi.getCarUpdateProgress( const result = await updatesApi.getCarUpdateProgress(
carUpdateIdsRef.current.join(","), carUpdateIdsRef.current.join(","),
token token
@@ -217,7 +221,7 @@ export const FleetProvider = ({ children }) => {
return vehicle; return vehicle;
})); }));
return Promise.resolve(); return Promise.resolve();
}, 5000); }, 2500);
const addFleetVehicles = async (name, vehicles, token) => { const addFleetVehicles = async (name, vehicles, token) => {
try { try {
@@ -233,6 +237,7 @@ export const FleetProvider = ({ children }) => {
if (result.error) { if (result.error) {
throw new Error(`Add fleet vehicle error. ${result.message}`); throw new Error(`Add fleet vehicle error. ${result.message}`);
} }
console.log(result)
return result; return result;
} finally { } finally {
setBusy(false); setBusy(false);

View File

@@ -418,7 +418,7 @@ describe("FleetContext", () => {
/> />
<button <button
data-testid="addFleetVehicles" data-testid="addFleetVehicles"
onClick={() => add("US-TEST", { vins: ["TESTVIN1234567890"] })} onClick={() => add("US-CENTRAL", { vins: ["TESTVIN1234567890"] })}
/> />
</> </>
); );

View File

@@ -340,6 +340,18 @@ const useStyles = makeStyles((theme) => ({
display: "flex", display: "flex",
alignItems: "center", alignItems: "center",
}, },
formGrid: {
display: "flex",
gap: "16px",
width: "100%",
},
formGridItem: {
flexGrow: 1,
},
marginX: {
marginTop: theme.spacing(2),
marginBottom: theme.spacing(2),
},
})); }));
export default useStyles; export default useStyles;

View File

@@ -82,20 +82,36 @@ const fleetsAPI = {
return { data: vehicles }; return { data: vehicles };
}, },
addFleetVehicles: async (_name, payload) => { addFleetVehicles: async (_name, payload) => {
payload.vins && vehicles.push(...payload.vins.map((vin => ({ const index = fleets.findIndex((fleet) => fleet.name === _name);
if (!payload.vins || index === -1) {
return;
}
vehicles.push(...payload.vins.map((vin => ({
vin, vin,
connected: false, connected: false,
connectedHMI: false, connectedHMI: false,
trex_version: "", trex_version: "",
})))); }))));
fleets[index].vehicles.push(...payload.vins);
return payload; return payload;
}, },
deleteFleetVehicles: async (_name, vins) => { deleteFleetVehicles: async (_name, vins) => {
const index = fleets.findIndex((fleet) => fleet.name === _name);
if (!vins || index === -1) {
return;
}
for (let i = 0; i < vins; i++) { for (let i = 0; i < vins; i++) {
const vin = vins[i]; const vin = vins[i];
const index = vehicles.findIndex(element => element.vin === vin); const index = vehicles.findIndex(element => element.vin === vin);
if (index >= 0) vehicles.splice(index, 1); if (index >= 0) vehicles.splice(index, 1);
fleets[index].vehicles = fleets[index].vehicles.filter((vin) => !vins.includes(vin))
} }
return { message: "Deleted" }; return { message: "Deleted" };
}, },