diff --git a/src/components/CANFilter/Add/__snapshots__/index.test.jsx.snap b/src/components/CANFilter/Add/__snapshots__/index.test.jsx.snap index 2d2d5b1..d72040a 100644 --- a/src/components/CANFilter/Add/__snapshots__/index.test.jsx.snap +++ b/src/components/CANFilter/Add/__snapshots__/index.test.jsx.snap @@ -22,53 +22,6 @@ exports[`CANFiltersAdd Render 1`] = ` class="makeStyles-form-0" novalidate="" > -
- -
- - -
-
@@ -152,6 +105,43 @@ exports[`CANFiltersAdd Render 1`] = `
+
+ +
+ + +
+
); diff --git a/src/components/CANFilter/Table/index.jsx b/src/components/CANFilter/Table/index.jsx index 5156f0b..270965e 100644 --- a/src/components/CANFilter/Table/index.jsx +++ b/src/components/CANFilter/Table/index.jsx @@ -137,9 +137,16 @@ const MainForm = ({ vin }) => { let actions = []; if (hasRole(groups, Permissions.FiskerCreate, providers)) { + let link = `/filter-update?vin=${vin}&can_id=${row.can_id}`; + if (row.interval || row.interval===0) { + link = link + `&interval=${row.interval}` + } + if (row.edge_mask) { + link = link + `&edge_mask=${row.edge_mask}` + } actions.push({ tip: `Update "${row.can_id}"`, - link: `/filter-update?vin=${vin}&can_id=${row.can_id}&interval=${row.interval}`, + link, icon: , }); } @@ -195,7 +202,7 @@ const MainForm = ({ vin }) => { /> {filters.map((row) => ( - + {row.can_id} {row.interval} diff --git a/src/components/CANFilter/Update/__snapshots__/index.test.jsx.snap b/src/components/CANFilter/Update/__snapshots__/index.test.jsx.snap index 4f66541..5a7f019 100644 --- a/src/components/CANFilter/Update/__snapshots__/index.test.jsx.snap +++ b/src/components/CANFilter/Update/__snapshots__/index.test.jsx.snap @@ -26,54 +26,7 @@ exports[`CANFiltersUpdate Render 1`] = ` class="MuiFormControl-root MuiTextField-root MuiFormControl-marginNormal MuiFormControl-fullWidth" > -
- - -
- -
-
@@ -156,7 +101,43 @@ exports[`CANFiltersUpdate Render 1`] = ` > Interval (ms) -  * + + + +
+
+
+ +
+ + diff --git a/src/components/CANFilter/Update/index.jsx b/src/components/CANFilter/Update/index.jsx index 5e3226b..7681424 100644 --- a/src/components/CANFilter/Update/index.jsx +++ b/src/components/CANFilter/Update/index.jsx @@ -1,7 +1,6 @@ -import React, { useEffect, useRef, useState } from "react"; +import React, { useEffect, useState } from "react"; import { Redirect } from "react-router"; import { useLocation } from "react-router-dom"; -import { Button, TextField } from "@material-ui/core"; import { CANFiltersProvider, @@ -11,6 +10,7 @@ import { useUserContext } from "../../Contexts/UserContext"; import { useStatusContext } from "../../Contexts/StatusContext"; import useStyles from "../../useStyles"; import { logger } from "../../../services/monitoring"; +import {CANFilterFragment} from "../../CANFilterEditFragment"; const MainForm = () => { @@ -19,12 +19,12 @@ const MainForm = () => { const { setMessage, setTitle, setSitePath } = useStatusContext(); const [redirect, setRedirect] = useState(null); const classes = useStyles(); - const intervalEl = useRef(null); const queries = new URLSearchParams(useLocation().search); const vin = queries.get("vin") ?? "" const canID = queries.get("can_id") ?? "" - const interval = queries.get("interval") ?? "" - // const edge_mask = queries.get("edge_mask") ?? "" + + const [interval, setInterval] = useState(queries.get("interval") ?? ""); + const [edgeMask, setEdgeMask] = useState(queries.get("edge_mask") ?? ""); useEffect(() => { setTitle("Update CAN Filter"); @@ -45,7 +45,8 @@ const MainForm = () => { event.preventDefault(); const formData = { can_id: canID, - interval: parseInt(intervalEl.current.value) + interval: interval, + edge_mask: edgeMask, }; const result = await updateFilter(vin, canID, formData, token); if (!result || result.error) return; @@ -65,59 +66,16 @@ const MainForm = () => { return (
- - - -
); diff --git a/src/components/CANFilterEditFragment/index.jsx b/src/components/CANFilterEditFragment/index.jsx new file mode 100644 index 0000000..288de79 --- /dev/null +++ b/src/components/CANFilterEditFragment/index.jsx @@ -0,0 +1,122 @@ +import React from 'react'; +import {Button, TextField} from "@material-ui/core"; +import useStyles from "../useStyles"; + +export const CANFilterFragment = ( + { + vin, + fleet, + canId, + setCanId, + edgeMask, + setEdgeMask, + interval, + setInterval, + busy, + onSubmit, + }) => { + + const classes = useStyles(); + + const FirstField = () => { + if (vin) { + return + } + if (fleet) { + return + } + } + + const canProps = setCanId ? { + value: canId, + onChange: (event) => setCanId(event.target.value) + } : { + value: canId, + disabled: true, + } + + return ( + + {FirstField()} + + setInterval(event.target.value)} + /> + setEdgeMask(event.target.value)} + /> + + + ) +} + diff --git a/src/components/Contexts/CANFiltersContext.jsx b/src/components/Contexts/CANFiltersContext.jsx index fc64d6b..b751827 100644 --- a/src/components/Contexts/CANFiltersContext.jsx +++ b/src/components/Contexts/CANFiltersContext.jsx @@ -1,5 +1,6 @@ import React, { useContext, useState } from "react"; import api from "../../services/CANFiltersAPI"; +import { validateCANID, validateFilter } from "../../utils/validationSupplier"; const CANFiltersContext = React.createContext(); @@ -50,7 +51,6 @@ export const CANFiltersProvider = ({ children }) => { setBusy(true); validateVIN(vin); - validateID(canID); validateFilter(filter); const result = await api.updateFilter(vin, canID, filter, token); @@ -68,7 +68,7 @@ export const CANFiltersProvider = ({ children }) => { setBusy(true); validateVIN(vin); - validateID(canID); + validateCANID(canID); const result = await api.deleteFilter(vin, canID, token); if (result.error) { @@ -104,24 +104,6 @@ const validateVIN = (vin) => { if (vin == null || vin.length !== 17) { throw new Error("Invalid VIN"); } -} - -const validateID = (can_id) => { - if (can_id == null || can_id === "") { - throw new Error("CAN ID required"); - } -} - -const validateFilter = (filter) => { - if (filter == null) { - throw new Error("No filter data"); - } - - validateID(filter.can_id) - - if (filter.interval == null) { - throw new Error("Interval required"); - } }; export const useCANFiltersContext = () => useContext(CANFiltersContext); diff --git a/src/components/Contexts/CANFiltersContext.test.jsx b/src/components/Contexts/CANFiltersContext.test.jsx index dcb3e57..77ab9fd 100644 --- a/src/components/Contexts/CANFiltersContext.test.jsx +++ b/src/components/Contexts/CANFiltersContext.test.jsx @@ -118,7 +118,7 @@ describe("CANFiltersContext", () => { await waitFor(() => expect(screen.getByTestId("busy").innerHTML).toEqual("false") ); - checkBaseResults("CAN ID required", "false"); + checkBaseResults("Invalid CAN ID", "false"); }); it("addFilter", async () => { @@ -195,7 +195,7 @@ describe("CANFiltersContext", () => { await waitFor(() => expect(screen.getByTestId("busy").innerHTML).toEqual("false") ); - checkBaseResults("CAN ID required", "false"); + checkBaseResults("Invalid CAN ID", "false"); }); it("updateFilter", async () => { @@ -228,10 +228,6 @@ describe("CANFiltersContext", () => { data-testid="deleteFilterNull" onClick={() => deleteF(null)} /> -
+
+ +
+ + +
+
); diff --git a/src/components/Fleets/Status/CANFilters/Table/index.jsx b/src/components/Fleets/Status/CANFilters/Table/index.jsx index 69740b4..ec06573 100644 --- a/src/components/Fleets/Status/CANFilters/Table/index.jsx +++ b/src/components/Fleets/Status/CANFilters/Table/index.jsx @@ -131,10 +131,18 @@ const MainForm = ({ name }) => { const Actions = (row) => { let actions = []; if (hasRole(groups, Permissions.FiskerCreate, providers)) { + let link = `/fleet/${name}/filter-update?name=${name}&can_id=${row.can_id}`; + if (row.interval || row.interval===0) { + link = link + `&interval=${row.interval}` + } + if (row.edge_mask) { + link = link + `&edge_mask=${row.edge_mask}` + } + actions.push({ tip: `Update "${row.can_id}"`, - link: `/fleet/${name}/filter-update?name=${name}&can_id=${row.can_id}&interval=${row.interval}`, - icon: , + link, + icon: }); } if (hasRole(groups, Permissions.FiskerDelete, providers)) { diff --git a/src/components/Fleets/Status/CANFilters/Update/__snapshots__/index.test.jsx.snap b/src/components/Fleets/Status/CANFilters/Update/__snapshots__/index.test.jsx.snap index 82e34d2..2d9cd1f 100644 --- a/src/components/Fleets/Status/CANFilters/Update/__snapshots__/index.test.jsx.snap +++ b/src/components/Fleets/Status/CANFilters/Update/__snapshots__/index.test.jsx.snap @@ -26,54 +26,7 @@ exports[`FleetCANFilterUpdate Render 1`] = ` class="MuiFormControl-root MuiTextField-root MuiFormControl-marginNormal MuiFormControl-fullWidth" > -
- - -
- -
-
@@ -156,7 +101,43 @@ exports[`FleetCANFilterUpdate Render 1`] = ` > Interval (ms) -  * + + + +
+
+
+ +
+ + diff --git a/src/components/Fleets/Status/CANFilters/Update/index.jsx b/src/components/Fleets/Status/CANFilters/Update/index.jsx index 7e5d2d1..c280364 100644 --- a/src/components/Fleets/Status/CANFilters/Update/index.jsx +++ b/src/components/Fleets/Status/CANFilters/Update/index.jsx @@ -1,13 +1,13 @@ -import React, { useEffect, useRef, useState } from "react"; +import React, { useEffect, useState } from "react"; import { Redirect, useParams } from "react-router"; import { useLocation } from "react-router-dom"; -import { Button, TextField } from "@material-ui/core"; import { useUserContext } from "../../../../Contexts/UserContext"; import { useStatusContext } from "../../../../Contexts/StatusContext"; import { useFleetContext, FleetProvider } from "../../../../Contexts/FleetContext"; import useStyles from "../../../../useStyles"; import { logger } from "../../../../../services/monitoring"; +import {CANFilterFragment} from "../../../../CANFilterEditFragment"; const MainForm = () => { @@ -16,11 +16,12 @@ const MainForm = () => { const { updateFleetCANFilter, busy } = useFleetContext(); const { token: { idToken: { jwtToken: token } } } = useUserContext(); const classes = useStyles(); - const intervalEl = useRef(null); - const [redirect, setRedirect] = useState(null); const queries = new URLSearchParams(useLocation().search); const canID = queries.get("can_id") ?? ""; - const interval = queries.get("interval") ?? ""; + const [interval, setInterval] = useState(queries.get("interval") ?? ""); + const [edgeMask, setEdgeMask] = useState(queries.get("edge_mask") ?? "") + const [redirect, setRedirect] = useState(null); + useEffect(() => { const title = "Update CAN Filter" @@ -46,7 +47,8 @@ const MainForm = () => { event.preventDefault(); const formData = { can_id: canID, - interval: parseInt(intervalEl.current.value) + interval: (interval == null || interval === "") ? null : interval, + edge_mask: edgeMask === "" ? null : edgeMask, }; const result = await updateFleetCANFilter(name, canID, formData, token); if (!result || result.error) return; @@ -66,59 +68,16 @@ const MainForm = () => { return (
- - - -
); diff --git a/src/utils/validationSupplier.js b/src/utils/validationSupplier.js index 8c8eec7..58446d8 100644 --- a/src/utils/validationSupplier.js +++ b/src/utils/validationSupplier.js @@ -35,3 +35,56 @@ export const validateSupplier = (supplier) => { export const validateEmail = (email) => { if (!validator.validate(email)) throw new Error("invalid email"); }; + +export const validateInterval = (interval) => { + if (!/^\d+$/.test(interval)) { + throw new Error("Interval must be number") + } +} + +export const validateEdgeMask = (edgeMask) => { + if (!/^[a-fA-F0-9]+$/.test(edgeMask)) { + throw new Error("Edge mask must be hex"); + } +} + +export const validateVIN = (vin) => { + if (vin == null || vin.length !== 17) { + throw new Error("Invalid VIN"); + } +} + +export const validateCANID = (can_id) => { + if (can_id == null || can_id === "" || !/^\d+(-\d+)?$/.test(can_id)) { + throw new Error("Invalid CAN ID"); + } +} + +export const validateFilter = (filter) => { + if (filter == null) { + throw new Error("No filter data"); + } + + validateCANID(filter.can_id) + + const oneOf = (filter.interval || filter.edge_mask) && !(filter.interval && filter.edge_mask) + if (!oneOf) { + throw new Error("Only interval or edge mask must be defined") + } + + if (filter.interval) { + validateInterval(filter.interval) + filter.interval = parseInt(filter.interval) + delete filter.edge_mask; + } + + if (filter.edge_mask && filter.edge_mask.length > 0) { + validateEdgeMask(filter.edge_mask) + delete filter.interval; + + // if the length is odd, add a leading zero + if (filter.edge_mask.length % 2 === 1) filter.edge_mask = "0"+filter.edge_mask; + } else { + filter.edge_mask = undefined; + } +};