diff --git a/src/components/CANFilter/Table/index.jsx b/src/components/CANFilter/Table/index.jsx index 587ba26..7001606 100644 --- a/src/components/CANFilter/Table/index.jsx +++ b/src/components/CANFilter/Table/index.jsx @@ -11,8 +11,8 @@ import { Tooltip, } from "@material-ui/core"; import AddCircleIcon from "@material-ui/icons/AddCircle"; -import EditIcon from '@material-ui/icons/Edit'; import DeleteIcon from "@material-ui/icons/Delete"; +import EditIcon from '@material-ui/icons/Edit'; import clsx from "clsx"; import { @@ -104,6 +104,7 @@ const MainForm = ({ vin }) => { const onDelete = async (can_id) => { try { await deleteFilter(vin, can_id, token); + setMessage(`Deleted ${can_id}`) } catch (e) { setMessage(e.message); logger.warn(e.stack); diff --git a/src/components/Contexts/FleetContext.jsx b/src/components/Contexts/FleetContext.jsx index e2cea30..d69afdb 100644 --- a/src/components/Contexts/FleetContext.jsx +++ b/src/components/Contexts/FleetContext.jsx @@ -12,6 +12,9 @@ export const FleetProvider = ({ children }) => { const [fleetVehicles, setFleetVehicles] = useState([]); const [totalFleetVehicles, setTotalFleetVehicles] = useState(0); + const [fleetCANFilters, setFleetCANFilters] = useState([]); + const [totalFleetCANFilters, setTotalFleetCANFilters] = useState(0); + const addFleet = async (fleet, token) => { try { setBusy(true); @@ -60,7 +63,7 @@ export const FleetProvider = ({ children }) => { } finally { setBusy(false); } - } + }; const deleteFleet = async (name, token) => { try { @@ -79,7 +82,7 @@ export const FleetProvider = ({ children }) => { } finally { setBusy(false); } - } + }; const getFleetVehicles = async (name, search, token) => { try { @@ -99,7 +102,7 @@ export const FleetProvider = ({ children }) => { } finally { setBusy(false); } - } + }; const addFleetVehicle = async (name, vehicle, token) => { try { @@ -116,7 +119,7 @@ export const FleetProvider = ({ children }) => { } finally { setBusy(false); } - } + }; const deleteFleetVehicle = async (name, vehicle, token) => { try { @@ -136,8 +139,83 @@ export const FleetProvider = ({ children }) => { } finally { setBusy(false); } + }; + + const getFleetCANFilters = async (name, search, token) => { + try { + setBusy(true); + + const result = await api.getFleetCANFilters(name, search, token); + if (result.error) { + setFleetCANFilters([]) + throw new Error(`Get fleet filters error. ${result.message}`); + } + + setFleetCANFilters(result.data) + if (result.total) { + setTotalFleetCANFilters(result.total); + } + return result; + } finally { + setBusy(false); + } + }; + + const addFleetCANFilter = async (name, filter, token) => { + try { + setBusy(true); + + validateFleetName(name); + validateFilter(filter); + + const result = await api.addFleetCANFilter(name, filter, token); + if (result.error) { + throw new Error(`Add fleet CAN filter error. ${result.message}`); + } + return result; + } finally { + setBusy(false); + } } + const updateFleetCANFilter = async (name, can_id, filter, token) => { + try { + setBusy(true); + + validateFleetName(name); + validateCANID(can_id); + validateFilter(filter); + + const result = await api.updateFleetCANFilter(name, can_id, filter, token); + if (result.error) { + throw new Error(`Update fleet CAN filter error. ${result.message}`); + } + return result; + } finally { + setBusy(false); + } + } + + const deleteFleetCANFilter = async (name, can_id, token) => { + try { + setBusy(true); + + validateFleetName(name); + validateCANID(can_id); + + const result = await api.deleteFleetCANFilter(name, can_id, token); + if (result.error) { + throw new Error(`Delete fleet vehicle error. ${result.message}`); + } + + const index = fleetCANFilters.findIndex(element => element.can_id === can_id); + if (index >= 0) fleetCANFilters.splice(index, 1); + return result; + } finally { + setBusy(false); + } + }; + return ( { totalFleetVehicles, getFleetVehicles, addFleetVehicle, - deleteFleetVehicle + deleteFleetVehicle, + + fleetCANFilters, + totalFleetCANFilters, + getFleetCANFilters, + addFleetCANFilter, + updateFleetCANFilter, + deleteFleetCANFilter }} > {children} @@ -171,7 +256,7 @@ const validateFleet = (fleet) => { } const validateFleetName = (name) => { - if (name == null || !/^[0-9A-Za-z-]+$/.test(name)) { + if (name == null || !/^[\w-]+$/.test(name)) { throw new Error("Invalid name"); } } @@ -182,4 +267,22 @@ const validateVIN = (vin) => { } } +const validateFilter = (filter) => { + if (filter == null) { + throw new Error("No CAN filter data"); + } + + validateCANID(filter.can_id) + + if (filter.interval == null || !/^\d+$/.test(filter.interval)) { + throw new Error("Invalid interval"); + } +} + +const validateCANID = (can_id) => { + if (can_id == null || !/^\d+(-\d+)?$/.test(can_id)) { + throw new Error("Invalid CAN ID"); + } +} + export const useFleetContext = () => useContext(FleetContext); diff --git a/src/components/Contexts/FleetContext.test.jsx b/src/components/Contexts/FleetContext.test.jsx index 6276545..bfb0c26 100644 --- a/src/components/Contexts/FleetContext.test.jsx +++ b/src/components/Contexts/FleetContext.test.jsx @@ -20,6 +20,11 @@ const checkFleetVehicleResults = (error, busy, vehicles) => { expect(screen.getByTestId("fleet-vehicles").innerHTML).toEqual(vehicles); } +const checkFleetCANFilterResults = (error, busy, filters) => { + checkBaseResults(error, busy); + expect(screen.getByTestId("fleet-filters").innerHTML).toEqual(filters); +} + const checkBaseResults = (error, busy) => { expect(screen.getByTestId("error").innerHTML).toEqual(error); expect(screen.getByTestId("busy").innerHTML).toEqual(busy); @@ -457,6 +462,187 @@ describe("FleetContext", () => { checkBaseResults("", "false"); }); }); + + describe("getFleetCANFilters", () => { + beforeEach(() => { + const TestComp = () => { + const { busy, error, fleetCANFilters, getFleetCANFilters } = useFleetContext(); + + return ( + <> +
{error}
+
{busy.toString()}
+
{JSON.stringify(fleetCANFilters)}
+ + + + + + + + +`; diff --git a/src/components/Fleets/Status/CANFilters/Add/index.jsx b/src/components/Fleets/Status/CANFilters/Add/index.jsx new file mode 100644 index 0000000..f9172f6 --- /dev/null +++ b/src/components/Fleets/Status/CANFilters/Add/index.jsx @@ -0,0 +1,127 @@ +import React, { useEffect, useRef, useState } from "react"; +import { Redirect, useParams } from "react-router"; +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"; + + +const MainForm = () => { + const { name } = useParams(); + const { setMessage, setTitle, setSitePath } = useStatusContext(); + const { addFleetCANFilter, busy } = useFleetContext(); + const { token: { idToken: { jwtToken: token } } } = useUserContext(); + const classes = useStyles(); + const canIdEl = useRef(null); + const intervalEl = useRef(null); + const [redirect, setRedirect] = useState(null); + + useEffect(() => { + const title = "Add CAN Filter" + setTitle(title); + setSitePath([ + { + label: `Fleets`, + link: "/fleets", + }, + { + label: `${name}`, + link: `/fleet/${name}` + }, + { + label: title + }, + ]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + const onSubmit = async (event) => { + try { + event.preventDefault(); + const formData = { + can_id: canIdEl.current.value, + interval: parseInt(intervalEl.current.value) + }; + const result = await addFleetCANFilter(name, formData, token); + if (!result || result.error) return; + + setMessage(`Added CAN filter ${result.can_id}`); + setRedirect(`/fleet/${name}#filters`); + } catch (e) { + setMessage(e.message); + logger.warn(e.stack); + } + }; + + if (redirect && redirect.length > 0) { + return ; + } + + return ( +
+
+ + + + + +
+ ); +}; + +const FleetAddCANFilterForm = (props) => ( + + + +); + +export default FleetAddCANFilterForm; \ No newline at end of file diff --git a/src/components/Fleets/Status/CANFilters/Add/index.test.jsx b/src/components/Fleets/Status/CANFilters/Add/index.test.jsx new file mode 100644 index 0000000..f4bfc25 --- /dev/null +++ b/src/components/Fleets/Status/CANFilters/Add/index.test.jsx @@ -0,0 +1,36 @@ +jest.mock("../../../../Contexts/FleetContext"); +jest.mock("../../../../Contexts/StatusContext"); +jest.mock("../../../../Contexts/UserContext"); + +import { render, waitFor } from "@testing-library/react"; +import { BrowserRouter } from "react-router-dom"; + +import { FleetProvider } from "../../../../Contexts/FleetContext"; +import { StatusProvider } from "../../../../Contexts/StatusContext"; +import { UserProvider, setToken } from "../../../../Contexts/UserContext"; +import { TEST_AUTH_OBJECT } from "../../../../../utils/testing"; +import MainForm from "./index" + +const renderFleetCANFilterAdd = async () => { + const { container } = render( + + + + + + + + + + ); + await waitFor(() => { }); + return container; +}; + +describe("FleetCANFilterAdd", () => { + it("Render", async () => { + setToken(TEST_AUTH_OBJECT); + const container = await renderFleetCANFilterAdd(); + expect(container).toMatchSnapshot(); + }); +}); diff --git a/src/components/Fleets/Status/CANFilters/Table/__snapshots__/index.test.jsx.snap b/src/components/Fleets/Status/CANFilters/Table/__snapshots__/index.test.jsx.snap new file mode 100644 index 0000000..00875ba --- /dev/null +++ b/src/components/Fleets/Status/CANFilters/Table/__snapshots__/index.test.jsx.snap @@ -0,0 +1,403 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`FleetCANFiltersTable Render 1`] = ` +
+
+
+
+
+
+
+ +
+
+ +
+ +
+ +
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + CAN ID + + + + + Interval (ms) + + + + Actions +
+ 123-456 + + 789 + + + + +
+ 1 + + 1000 + + + + +
+ 1000 + + 1 + + + + +
+
+
+
+
+
+
+`; diff --git a/src/components/Fleets/Status/CANFilters/Table/index.jsx b/src/components/Fleets/Status/CANFilters/Table/index.jsx new file mode 100644 index 0000000..1572045 --- /dev/null +++ b/src/components/Fleets/Status/CANFilters/Table/index.jsx @@ -0,0 +1,205 @@ +import React, { useEffect, useState } from "react"; +import { Link } from 'react-router-dom'; +import { + Grid, + Table, + TableBody, + TableCell, + TableFooter, + TablePagination, + TableRow, + Tooltip, +} from "@material-ui/core"; +import AddCircleIcon from "@material-ui/icons/AddCircle"; +import DeleteIcon from "@material-ui/icons/Delete"; +import EditIcon from '@material-ui/icons/Edit'; +import clsx from "clsx"; + +import TableHeaderSortable from "../../../../Table/HeaderSortable"; +import { useUserContext } from "../../../../Contexts/UserContext" +import { useStatusContext } from "../../../../Contexts/StatusContext"; +import { FleetProvider, useFleetContext } from "../../../../Contexts/FleetContext" +import useStyles from "../../../../useStyles"; +import SearchField from "../../../../Controls/SearchField"; +import { logger } from "../../../../../services/monitoring"; +import { Roles, hasRole } from "../../../../../utils/roles"; + +const tableColumns = [ + { + id: "can_id", + label: "CAN ID" + }, + { + id: "interval", + label: "Interval (ms)" + }, + { + id: "", + label: "Actions" + } +]; + +const MainForm = ({ name }) => { + const [pageSize, setPageSize] = useState(10); + const [pageIndex, setPageIndex] = useState(0); + const [orderBy, setOrderBy] = useState("id"); + const [order, setOrder] = useState("desc"); + const classes = useStyles(); + const { setMessage } = useStatusContext(); + const { fleetCANFilters, totalFleetCANFilters, getFleetCANFilters, deleteFleetCANFilter } = useFleetContext(); + const { token: { idToken: { jwtToken: token } }, groups } = useUserContext(); + + useEffect(() => { + (async () => { + try { + if (!name || !token) return; + await getFleetCANFilters( + name, + { + limit: pageSize, + offset: pageSize * pageIndex, + order: `${orderBy} ${order}`, + }, + token + ); + } catch (e) { + setMessage(e.message); + logger.warn(e.stack); + } + })(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [token, pageIndex, pageSize, orderBy, order]); + + 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); + } + }; + + const onDelete = async (can_id) => { + try { + await deleteFleetCANFilter(name, can_id, token); + setMessage(`Deleted ${can_id}`) + } catch (e) { + setMessage(e.message); + logger.warn(e.stack); + } + }; + + const Actions = (row) => { + let actions = []; + if (hasRole([Roles.CREATE], groups)) { + actions.push({ + tip: `Update "${row.can_id}"`, + link: `/fleet/${name}/filter-update?name=${name}&can_id=${row.can_id}&interval=${row.interval}`, + icon: + }); + } + if (hasRole([Roles.DELETE], groups)) { + actions.push({ + tip: `Delete "${row.can_id}"`, + id: row.can_id, + icon: + }) + } + if (actions.length === 0) return ["No actions"]; + + return actions.map((action) => { + if (action.link != null) { + return ( + + + {action.icon} + + + ); + } else { + return ( + + onDelete(action.id)}> + {action.icon} + + + ); + } + }); + }; + + return ( +
+ + + + + + + + + + + + + + {fleetCANFilters.map(row => ( + + {row.can_id} + {row.interval} + {Actions(row)} + + ))} + + + + + + +
+
+ ); +}; + +const FleetCANFiltersTable = (props) => ( + + + +); + +export default FleetCANFiltersTable; diff --git a/src/components/Fleets/Status/CANFilters/Table/index.test.jsx b/src/components/Fleets/Status/CANFilters/Table/index.test.jsx new file mode 100644 index 0000000..aa313c9 --- /dev/null +++ b/src/components/Fleets/Status/CANFilters/Table/index.test.jsx @@ -0,0 +1,39 @@ +jest.mock("../../../../Contexts/FleetContext"); +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 { BrowserRouter } from "react-router-dom"; + +import { FleetProvider } from "../../../../Contexts/FleetContext"; +import { StatusProvider } from "../../../../Contexts/StatusContext"; +import { UserProvider, setToken } from "../../../../Contexts/UserContext"; +import { TEST_AUTH_OBJECT } from "../../../../../utils/testing"; +import MainForm from "./index" + +const renderFleetCANFiltersTable = async () => { + const { container } = render( + + + + + + + + + + ); + await waitFor(() => { }); + return container; +}; + +describe("FleetCANFiltersTable", () => { + it("Render", async () => { + setToken(TEST_AUTH_OBJECT); + const container = await renderFleetCANFiltersTable(); + expect(container).toMatchSnapshot(); + }); +}); 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 new file mode 100644 index 0000000..def73c8 --- /dev/null +++ b/src/components/Fleets/Status/CANFilters/Update/__snapshots__/index.test.jsx.snap @@ -0,0 +1,186 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`FleetCANFilterUpdate Render 1`] = ` +
+
+
+
+
+
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+ +
+
+
+
+
+
+
+`; diff --git a/src/components/Fleets/Status/CANFilters/Update/index.jsx b/src/components/Fleets/Status/CANFilters/Update/index.jsx new file mode 100644 index 0000000..8722304 --- /dev/null +++ b/src/components/Fleets/Status/CANFilters/Update/index.jsx @@ -0,0 +1,133 @@ +import React, { useEffect, useRef, 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"; + + +const MainForm = () => { + const { name } = useParams(); + const { setMessage, setTitle, setSitePath } = useStatusContext(); + 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") ?? ""; + + useEffect(() => { + const title = "Update CAN Filter" + setTitle(title); + setSitePath([ + { + label: `Fleets`, + link: "/fleets", + }, + { + label: `${name}`, + link: `/fleet/${name}`, + }, + { + label: title + }, + ]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + const onSubmit = async (event) => { + try { + event.preventDefault(); + const formData = { + can_id: canID, + interval: parseInt(intervalEl.current.value) + }; + const result = await updateFleetCANFilter(name, canID, formData, token); + if (!result || result.error) return; + + setMessage(`Updated CAN filter ${result.can_id}`); + setRedirect(`/fleet/${name}#filters`); + } catch (e) { + setMessage(e.message); + logger.warn(e.stack); + } + }; + + if (redirect && redirect.length > 0) { + return ; + } + + return ( +
+
+ + + + + +
+ ); +}; + +const FleetCANFilterUpdateForm = (props) => ( + + + +); + +export default FleetCANFilterUpdateForm; \ No newline at end of file diff --git a/src/components/Fleets/Status/CANFilters/Update/index.test.jsx b/src/components/Fleets/Status/CANFilters/Update/index.test.jsx new file mode 100644 index 0000000..68e08fe --- /dev/null +++ b/src/components/Fleets/Status/CANFilters/Update/index.test.jsx @@ -0,0 +1,36 @@ +jest.mock("../../../../Contexts/FleetContext"); +jest.mock("../../../../Contexts/StatusContext"); +jest.mock("../../../../Contexts/UserContext"); + +import { render, waitFor } from "@testing-library/react"; +import { BrowserRouter } from "react-router-dom"; + +import { FleetProvider } from "../../../../Contexts/FleetContext"; +import { StatusProvider } from "../../../../Contexts/StatusContext"; +import { UserProvider, setToken } from "../../../../Contexts/UserContext"; +import { TEST_AUTH_OBJECT } from "../../../../../utils/testing"; +import MainForm from "./index" + +const renderFleetCANFilterUpdate = async () => { + const { container } = render( + + + + + + + + + + ); + await waitFor(() => { }); + return container; +}; + +describe("FleetCANFilterUpdate", () => { + it("Render", async () => { + setToken(TEST_AUTH_OBJECT); + const container = await renderFleetCANFilterUpdate(); + expect(container).toMatchSnapshot(); + }); +}); diff --git a/src/components/Fleets/Status/CANFiltersTab.jsx b/src/components/Fleets/Status/CANFiltersTab.jsx new file mode 100644 index 0000000..78361a0 --- /dev/null +++ b/src/components/Fleets/Status/CANFiltersTab.jsx @@ -0,0 +1,21 @@ +import React from "react"; +import { useParams } from "react-router"; +import clsx from "clsx"; +import { Typography } from "@material-ui/core"; + +import FleetCANFiltersTable from "./CANFilters/Table"; +import useStyles from "../../useStyles"; + +const FleetCANFiltersTab = () => { + const { name } = useParams(); + const classes = useStyles(); + + return ( +
+ CAN Filters + +
+ ); +}; + +export default FleetCANFiltersTab; diff --git a/src/components/Fleets/Status/CANFiltersTab.test.jsx b/src/components/Fleets/Status/CANFiltersTab.test.jsx new file mode 100644 index 0000000..43c2776 --- /dev/null +++ b/src/components/Fleets/Status/CANFiltersTab.test.jsx @@ -0,0 +1,31 @@ +jest.mock("../../Contexts/FleetContext"); +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 { BrowserRouter } from "react-router-dom"; + +import { setToken } from "../../Contexts/UserContext"; +import { TEST_AUTH_OBJECT } from "../../../utils/testing"; +import CANFiltersTab from "./CANFiltersTab" + +const renderCANFitlersTab = async () => { + const { container } = render( + + + + ); + await waitFor(() => { }); + return container; +}; + +describe("CANFiltersTab", () => { + it("Render", async () => { + setToken(TEST_AUTH_OBJECT); + const container = await renderCANFitlersTab(); + expect(container).toMatchSnapshot(); + }); +}); diff --git a/src/components/Fleets/Status/Vehicles/Add/__snapshots__/index.test.jsx.snap b/src/components/Fleets/Status/Vehicles/Add/__snapshots__/index.test.jsx.snap index fd995d4..b919270 100644 --- a/src/components/Fleets/Status/Vehicles/Add/__snapshots__/index.test.jsx.snap +++ b/src/components/Fleets/Status/Vehicles/Add/__snapshots__/index.test.jsx.snap @@ -3,7 +3,7 @@ exports[`FleetVehicleAdd Render 1`] = `
-
-
-
- - +   + * + + +
+ + +
-
- -
+ + Submit + + + + +
- f `; diff --git a/src/components/Fleets/Status/Vehicles/Add/index.test.jsx b/src/components/Fleets/Status/Vehicles/Add/index.test.jsx index 31afb5b..54acf8f 100644 --- a/src/components/Fleets/Status/Vehicles/Add/index.test.jsx +++ b/src/components/Fleets/Status/Vehicles/Add/index.test.jsx @@ -1,27 +1,27 @@ -jest.mock("../../../../Contexts/CANFiltersContext"); +jest.mock("../../../../Contexts/FleetContext"); jest.mock("../../../../Contexts/StatusContext"); jest.mock("../../../../Contexts/UserContext"); import { render, waitFor } from "@testing-library/react"; import { BrowserRouter } from "react-router-dom"; -import { CANFiltersProvider } from "../../../../Contexts/CANFiltersContext"; +import { FleetProvider } from "../../../../Contexts/FleetContext"; import { StatusProvider } from "../../../../Contexts/StatusContext"; import { UserProvider, setToken } from "../../../../Contexts/UserContext"; import { TEST_AUTH_OBJECT } from "../../../../../utils/testing"; import MainForm from "./index" -const renderCANFiltersAdd = async () => { +const renderFleetVehicleAdd = async () => { const { container } = render( - + - f - + + ); await waitFor(() => { }); return container; @@ -30,7 +30,7 @@ const renderCANFiltersAdd = async () => { describe("FleetVehicleAdd", () => { it("Render", async () => { setToken(TEST_AUTH_OBJECT); - const container = await renderCANFiltersAdd(); + const container = await renderFleetVehicleAdd(); expect(container).toMatchSnapshot(); }); }); diff --git a/src/components/Fleets/Status/Vehicles/Table/index.jsx b/src/components/Fleets/Status/Vehicles/Table/index.jsx index 0fdfc23..697a3e8 100644 --- a/src/components/Fleets/Status/Vehicles/Table/index.jsx +++ b/src/components/Fleets/Status/Vehicles/Table/index.jsx @@ -47,7 +47,7 @@ const MainForm = ({ name }) => { useEffect(() => { (async () => { try { - if (!token) return; + if (!name || !token) return; await getFleetVehicles( name, { diff --git a/src/components/Fleets/Status/Vehicles/Table/index.test.jsx b/src/components/Fleets/Status/Vehicles/Table/index.test.jsx index 6559abd..4f1e3ab 100644 --- a/src/components/Fleets/Status/Vehicles/Table/index.test.jsx +++ b/src/components/Fleets/Status/Vehicles/Table/index.test.jsx @@ -14,7 +14,7 @@ import { UserProvider, setToken } from "../../../../Contexts/UserContext"; import { TEST_AUTH_OBJECT } from "../../../../../utils/testing"; import MainForm from "./index" -const renderFleetTable = async () => { +const renderFleetVehiclesTable = async () => { const { container } = render( @@ -33,7 +33,7 @@ const renderFleetTable = async () => { describe("FleetVehiclesTable", () => { it("Render", async () => { setToken(TEST_AUTH_OBJECT); - const container = await renderFleetTable(); + const container = await renderFleetVehiclesTable(); expect(container).toMatchSnapshot(); }); }); diff --git a/src/components/Fleets/Status/__snapshots__/CANFiltersTab.test.jsx.snap b/src/components/Fleets/Status/__snapshots__/CANFiltersTab.test.jsx.snap new file mode 100644 index 0000000..09a8600 --- /dev/null +++ b/src/components/Fleets/Status/__snapshots__/CANFiltersTab.test.jsx.snap @@ -0,0 +1,400 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`CANFiltersTab Render 1`] = ` +
+
+
+ CAN Filters +
+
+
+
+ +
+
+ +
+ +
+ +
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + CAN ID + + + + + Interval (ms) + + + + Actions +
+ 123-456 + + 789 + + + + +
+ 1 + + 1000 + + + + +
+ 1000 + + 1 + + + + +
+
+
+
+
+`; diff --git a/src/components/Fleets/Status/index.jsx b/src/components/Fleets/Status/index.jsx index 7d70f3c..f350836 100644 --- a/src/components/Fleets/Status/index.jsx +++ b/src/components/Fleets/Status/index.jsx @@ -5,12 +5,13 @@ import clsx from "clsx"; import { Box, Tab, Tabs } from "@material-ui/core"; import FleetVehiclesTab from "./VehiclesTab"; +import FleetCANFiltersTab from "./CANFiltersTab"; import TabPanel from "../../Controls/TabPanel"; import { useStatusContext } from "../../Contexts/StatusContext"; import useStyles from "../../useStyles"; const tabHashes = [ - "updates", + "vehicles", "filters" ] @@ -60,7 +61,7 @@ const FleetStatus = () => { - {/* */} + ); diff --git a/src/components/Routes/SiteRoutes.jsx b/src/components/Routes/SiteRoutes.jsx index 4aabca3..e67602d 100644 --- a/src/components/Routes/SiteRoutes.jsx +++ b/src/components/Routes/SiteRoutes.jsx @@ -16,8 +16,10 @@ const Datascope = React.lazy(() => import("../Datascope/Home")); const FleetsList = React.lazy(() => import("../Fleets/Table")); const FleetStatus = React.lazy(() => import("../Fleets/Status")); const FleetAddForm = React.lazy(() => import("../Fleets/Add")); -const FleetUpdateForm = React.lazy(() => import("../Fleets/Update")) -const FleetAddVehicleForm = React.lazy(() => import("../Fleets/Status/Vehicles/Add")) +const FleetUpdateForm = React.lazy(() => import("../Fleets/Update")); +const FleetAddVehicleForm = React.lazy(() => import("../Fleets/Status/Vehicles/Add")); +const FleetAddCANFilterForm = React.lazy(() => import("../Fleets/Status/CANFilters/Add")); +const FleetUpdateCANFilterForm = React.lazy(() => import("../Fleets/Status/CANFilters/Update")); const Home = React.lazy(() => import("../Home")); const Manifests = React.lazy(() => import("../Manifest/List")); const ManifestDeploy = React.lazy(() => import("../Manifest/Deploy")); @@ -88,6 +90,22 @@ const SiteRoutes = () => { groups={groups} roles={[Roles.READ, Roles.CREATE]} /> + } + type={TYPES.PROTECTED} + token={token} + groups={groups} + roles={[Roles.READ, Roles.CREATE]} + /> + } + type={TYPES.PROTECTED} + token={token} + groups={groups} + roles={[Roles.READ, Roles.CREATE]} + /> } diff --git a/src/services/__mocks__/fleetsAPI.js b/src/services/__mocks__/fleetsAPI.js index 2ca7cf0..3766027 100644 --- a/src/services/__mocks__/fleetsAPI.js +++ b/src/services/__mocks__/fleetsAPI.js @@ -21,13 +21,28 @@ const fleets = [ const vehicles = ["USWESTVIN12345678", "USWESTVIN12345679", "USWESTVIN12345670"]; +const filters = [ + { + can_id: "123-456", + interval: 789 + }, + { + can_id: "1", + interval: 1000 + }, + { + can_id: "1000", + interval: 1 + } +] + const fleetsAPI = { addFleet: async (fleet, token) => { fleets.push(fleet); return fleet; }, getFleets: async (search, token) => { - return {data: fleets}; + return { data: fleets }; }, updateFleet: async (name, fleet, token) => { const index = fleets.findIndex(element => element.name === name); @@ -41,7 +56,7 @@ const fleetsAPI = { }, getFleetVehicles: async (name, search, token) => { - return {data: vehicles}; + return { data: vehicles }; }, addFleetVehicle: async (name, vehicle, token) => { vehicles.push(vehicle.vin); @@ -50,7 +65,25 @@ const fleetsAPI = { deleteFleetVehicle: async (name, vehicle, token) => { const index = vehicles.findIndex(element => element === vehicle.vin); if (index >= 0) vehicles.splice(index, 1); - return vehicle; + return vehicle.vin; + }, + + getFleetCANFilters: async (name, search, token) => { + return { data: filters }; + }, + addFleetCANFilter: async (name, filter, token) => { + filters.push(filter); + return filter; + }, + updateFleetCANFilter: async (name, can_id, filter, token) => { + const index = filters.findIndex(element => element.can_id === can_id); + if (index >= 0) filters[index] = filter; + return filter; + }, + deleteFleetCANFilter: async (name, can_id, token) => { + const index = filters.findIndex(element => element.can_id === can_id); + if (index >= 0) vehicles.splice(index, 1); + return can_id; } }; diff --git a/src/services/fleetsAPI.js b/src/services/fleetsAPI.js index 60100fa..af8a9bc 100644 --- a/src/services/fleetsAPI.js +++ b/src/services/fleetsAPI.js @@ -72,6 +72,44 @@ const fleetsAPI = { getAuthHeaderOptions(token) ) }).then(fetchRespHandler), + + getFleetCANFilters: async (name, search, token) => + fetch(addQueryParams(`${API_ENDPOINT}/fleet/${name}/filters`, search), { + method: "GET", + headers: Object.assign( + { "Content-Type": "application/json" }, + getAuthHeaderOptions(token) + ) + }).then(fetchRespHandler), + + addFleetCANFilter: async (name, filter, token) => + fetch(`${API_ENDPOINT}/fleet/${name}/filter`, { + method: "POST", + headers: Object.assign( + { "Content-Type": "application/json" }, + getAuthHeaderOptions(token) + ), + body: JSON.stringify(filter) + }).then(fetchRespHandler), + + updateFleetCANFilter: async (name, can_id, filter, token) => + fetch(`${API_ENDPOINT}/fleet/${name}/filter/${can_id}`, { + method: "PUT", + headers: Object.assign( + { "Content-Type": "application/json" }, + getAuthHeaderOptions(token) + ), + body: JSON.stringify(filter) + }).then(fetchRespHandler), + + deleteFleetCANFilter: async (name, can_id, token) => + fetch(`${API_ENDPOINT}/fleet/${name}/filter/${can_id}`, { + method: "DELETE", + headers: Object.assign( + { "Content-Type": "application/json" }, + getAuthHeaderOptions(token) + ) + }).then(fetchRespHandler), }; export default fleetsAPI;