From 64995ef7a6653ef1cbfd68c6bdc75df51f01646e Mon Sep 17 00:00:00 2001 From: John Wu <76966357+jwu-fisker@users.noreply.github.com> Date: Tue, 18 May 2021 12:51:24 -0700 Subject: [PATCH] CEC-222, CEC-214 Send car commands and log filtering (#41) * Send car commands * Log filter control * Fix message format * Move VehicleContext --- .../App/__snapshots__/App.test.js.snap | 675 +++++++++++++----- src/components/Cars/LogFilter/index.jsx | 123 ++++ src/components/Cars/SendCommand/index.jsx | 81 +++ src/components/Cars/Status/index.jsx | 21 +- src/components/Contexts/VehicleContext.jsx | 26 + .../Contexts/__mocks__/VehicleContext.jsx | 8 + src/services/__mocks__/vehicles.js | 8 + src/services/commands.js | 42 ++ src/services/vehicles.js | 17 + 9 files changed, 811 insertions(+), 190 deletions(-) create mode 100644 src/components/Cars/LogFilter/index.jsx create mode 100644 src/components/Cars/SendCommand/index.jsx create mode 100644 src/services/commands.js diff --git a/src/components/App/__snapshots__/App.test.js.snap b/src/components/App/__snapshots__/App.test.js.snap index 3d8e35f..c8bd935 100644 --- a/src/components/App/__snapshots__/App.test.js.snap +++ b/src/components/App/__snapshots__/App.test.js.snap @@ -299,17 +299,17 @@ exports[`App Route /carupdate-deploy authenticated 1`] = ` data-testid="mocked-userprovider" >

No Cars Selected

-

- FISKER123 - Updates -

- - +
+
- - - - - - - - - - - + ID + + + + + + + + + - + + +
- ID - - Update - - Status - - Created - - Updated -
+
+
+
+
+
+ +
+ + + +
+
+ + + + + + +
+
+
+
+
+ +
+
+
+ +
+ + +
+
+
+
+
+ +
+ +
+
+
+
+ + -
-

- 0-0 of 0 -

-
- - -
-
- - - - + + + +
+
+
+

diff --git a/src/components/Cars/LogFilter/index.jsx b/src/components/Cars/LogFilter/index.jsx new file mode 100644 index 0000000..2d2a205 --- /dev/null +++ b/src/components/Cars/LogFilter/index.jsx @@ -0,0 +1,123 @@ +import React, { useState } from "react"; +import PropTypes from "prop-types"; +import { + FormControlLabel, + Grid, + IconButton, + TextField, + Switch, +} from "@material-ui/core"; +import SendIcon from "@material-ui/icons/Send"; + +import { useVehicleContext } from "../../Contexts/VehicleContext"; +import { useUserContext } from "../../Contexts/UserContext"; +import { useStatusContext } from "../../Contexts/StatusContext"; + +const LogFilter = ({ vin }) => { + const { sendLogFilter, busy } = useVehicleContext(); + const { + token: { + idToken: { jwtToken: token }, + }, + } = useUserContext(); + const { setMessage } = useStatusContext(); + const [filter, setFilter] = useState(""); + const [enableLog, setEnableLog] = useState(true); + const [freq, setFreq] = useState(0); + + const changeFilterHandler = (e) => { + setFilter(e.target.value); + }; + + const changeStartHandler = (e) => { + setEnableLog(e.target.checked); + }; + + const changeFreqHandler = (e) => { + setFreq(e.target.value); + }; + + const clickHandler = async (e) => { + try { + const data = { + enable: enableLog, + frequency: freq, + filter, + }; + await sendLogFilter(vin, data, token); + setMessage(`Sent log command to ${vin}`); + } catch (e) { + setMessage(e.message); + } + }; + + return ( + + + + } + labelPlacement="top" + label="Logging" + /> + + + + + + + + + + + + + + ); +}; + +LogFilter.propTypes = { + vin: PropTypes.string.isRequired, +}; + +export default LogFilter; diff --git a/src/components/Cars/SendCommand/index.jsx b/src/components/Cars/SendCommand/index.jsx new file mode 100644 index 0000000..5a4add2 --- /dev/null +++ b/src/components/Cars/SendCommand/index.jsx @@ -0,0 +1,81 @@ +import React, { useEffect, useState } from "react"; +import PropTypes from "prop-types"; +import { FormControl, IconButton, InputLabel, Select } from "@material-ui/core"; +import SendIcon from "@material-ui/icons/Send"; + +import { useVehicleContext } from "../../Contexts/VehicleContext"; +import commands from "../../../services/commands"; +import useStyles from "../../useStyles"; +import { useUserContext } from "../../Contexts/UserContext"; +import { useStatusContext } from "../../Contexts/StatusContext"; + +const SendCommand = ({ vin }) => { + const classes = useStyles(); + const { sendCommand, busy } = useVehicleContext(); + const { + token: { + idToken: { jwtToken: token }, + }, + } = useUserContext(); + const { setMessage } = useStatusContext(); + const [command, setCommand] = useState(""); + + const changeHandler = (e) => { + setCommand(e.target.value); + }; + + const clickHandler = async (e) => { + try { + await sendCommand(vin, command, token); + setMessage(`Sent command to ${vin}`); + } catch (e) { + setMessage(e.message); + } + }; + + useEffect(() => { + if (!commands || commands.length === 0) return; + setCommand(commands[0].value); + }, []); + + return ( +
+ + + Command + + + + + + +
+ ); +}; + +SendCommand.propTypes = { + vin: PropTypes.string.isRequired, +}; + +export default SendCommand; diff --git a/src/components/Cars/Status/index.jsx b/src/components/Cars/Status/index.jsx index 37facd6..333bf62 100644 --- a/src/components/Cars/Status/index.jsx +++ b/src/components/Cars/Status/index.jsx @@ -1,6 +1,7 @@ import React, { useEffect, useState } from "react"; import { useParams } from "react-router"; import { + Grid, Table, TableBody, TableCell, @@ -16,10 +17,14 @@ import { UpdatesProvider, useUpdatesContext, } from "../../Contexts/UpdatesContext"; +import { VehicleProvider } from "../../Contexts/VehicleContext"; + import { useUserContext } from "../../Contexts/UserContext"; import { useStatusContext } from "../../Contexts/StatusContext"; import useStyles from "../../useStyles"; import { LocalDateTimeString } from "../../../utils/dates"; +import SendCommand from "../SendCommand"; +import LogFilter from "../LogFilter"; const MainForm = () => { const { vin } = useParams(); @@ -109,14 +114,24 @@ const MainForm = () => { + + + + + + + +

); }; const CarUpdates = () => ( - - - + + + + + ); export default CarUpdates; diff --git a/src/components/Contexts/VehicleContext.jsx b/src/components/Contexts/VehicleContext.jsx index 0ea3d84..698ca39 100644 --- a/src/components/Contexts/VehicleContext.jsx +++ b/src/components/Contexts/VehicleContext.jsx @@ -84,6 +84,30 @@ export const VehicleProvider = ({ children }) => { } }; + const sendCommand = async (vin, command, token) => { + try { + setBusy(true); + const result = await api.sendCommand(vin, command, token); + if (result.error) + throw new Error(`Send command error. ${result.message}`); + return result; + } finally { + setBusy(false); + } + }; + + const sendLogFilter = async (vin, filter, token) => { + try { + setBusy(true); + const result = await api.sendLog(vin, filter, token); + if (result.error) + throw new Error(`Send log filter error. ${result.message}`); + return result; + } finally { + setBusy(false); + } + }; + return ( { addVehicle, getModels, getYears, + sendCommand, + sendLogFilter, }} > {children} diff --git a/src/components/Contexts/__mocks__/VehicleContext.jsx b/src/components/Contexts/__mocks__/VehicleContext.jsx index 0a1b6f0..0a3e517 100644 --- a/src/components/Contexts/__mocks__/VehicleContext.jsx +++ b/src/components/Contexts/__mocks__/VehicleContext.jsx @@ -25,6 +25,14 @@ export const useVehicleContext = () => ({ getYears: jest.fn(() => { years = [2023, 2024]; }), + sendCommand: jest.fn((vin, command, token) => ({ + vin, + command, + })), + sendLogFilter: jest.fn((vin, filter, token) => ({ + vin, + filter, + })), }); export const setBusy = (val) => { diff --git a/src/services/__mocks__/vehicles.js b/src/services/__mocks__/vehicles.js index b405b0b..adaac89 100644 --- a/src/services/__mocks__/vehicles.js +++ b/src/services/__mocks__/vehicles.js @@ -25,6 +25,14 @@ const vehiclesAPI = { data: [2021, 2022], }; }, + sendCommand: async (vin, command, token) => { + return { + vin, command, + } + }, + sendLog: async (vin, filter, token) => { + return Object.assign(filter, {vin}); + }, }; export default vehiclesAPI; diff --git a/src/services/commands.js b/src/services/commands.js new file mode 100644 index 0000000..c1aa54c --- /dev/null +++ b/src/services/commands.js @@ -0,0 +1,42 @@ +const Commands = [{ + value: "LFRD", + label: "Lock front right door", + },{ + value: "UFRD", + label: "Unlock front right door", + },{ + value: "LFLD", + label: "Lock front left door", + },{ + value: "UFLD", + label: "Unlock front left door", + },{ + value: "LRRD", + label: "Lock rear right door", + },{ + value: "URRD", + label: "Unlock rear right door", + },{ + value: "LRLD", + label: "Lock rear left door", + },{ + value: "URLD", + label: "Unlock rear left door", + },{ + value: "LTRK", + label: "Lock trunk", + },{ + value: "UTRK", + label: "Unlock trunk", + },{ + value: "OWIN", + label: "Open Windows", + },{ + value: "CWIN", + label: "Close Windows", + },{ + value: "FLASH", + label: "Flash headlights", + }]; + +export default Commands; diff --git a/src/services/vehicles.js b/src/services/vehicles.js index 0ac6c19..11cf63a 100644 --- a/src/services/vehicles.js +++ b/src/services/vehicles.js @@ -30,6 +30,23 @@ const vehiclesAPI = { headers: Object.assign({ "Content-Type": "application/json" }, getAuthHeaderOptions(token)), }) .then(fetchRespHandler), + + sendCommand: async (vin, command, token) => fetch(`${API_ENDPOINT}/vehiclecommand`, { + method: "POST", + headers: Object.assign({ "Content-Type": "application/json" }, getAuthHeaderOptions(token)), + body: JSON.stringify({ + vin, command, + }), + }) + .then(fetchRespHandler), + + sendLog: async (vin, filter, token) => fetch(`${API_ENDPOINT}/vehiclelog`, { + method: "POST", + headers: Object.assign({ "Content-Type": "application/json" }, getAuthHeaderOptions(token)), + body: JSON.stringify(Object.assign(filter, {vin})), + }) + .then(fetchRespHandler), + }; export default vehiclesAPI;