diff --git a/.env.dev b/.env.dev index ce1b79c..b51cbe0 100644 --- a/.env.dev +++ b/.env.dev @@ -2,4 +2,4 @@ REACT_APP_CERT_SERVICE_URL=https://dev-gw.cloud.fiskerinc.com/certificate REACT_APP_AUTH_SERVICE_URL=https://dev-gw.cloud.fiskerinc.com/compute_auth REACT_APP_UPLOAD_SERVICE_URL=https://dev-gw.cloud.fiskerinc.com/ota_update REACT_APP_AUTH_CALLBACK_URL=https://dev-ota-admin.cloud.fiskerinc.com -REACT_APP_SUPERSET_URL=http://superset-dev.fiskercloud.internal/superset/dashboard/8/?native_filters_key=KPnPthpLQ8rT--6PUdsPzQAcwnleRGHk_3dg0PVYfrXc3SE6zZ2x0p7JuerAZ0Pg +REACT_APP_SUPERSET_URL=https://superset-dev.cloud.fiskerinc.com/superset/dashboard/8/?native_filters_key=KPnPthpLQ8rT--6PUdsPzQAcwnleRGHk_3dg0PVYfrXc3SE6zZ2x0p7JuerAZ0Pg diff --git a/.env.local b/.env.local index 7a58b1b..5b55344 100644 --- a/.env.local +++ b/.env.local @@ -2,4 +2,4 @@ REACT_APP_AUTH_SERVICE_URL=http://localhost/compute_auth REACT_APP_CERT_SERVICE_URL=http://localhost/certificate REACT_APP_UPLOAD_SERVICE_URL=http://localhost/ota_update REACT_APP_AUTH_CALLBACK_URL=http://localhost:3000 -REACT_APP_SUPERSET_URL=http://superset-dev.fiskercloud.internal/superset/dashboard/8/?native_filters_key=KPnPthpLQ8rT--6PUdsPzQAcwnleRGHk_3dg0PVYfrXc3SE6zZ2x0p7JuerAZ0Pg +REACT_APP_SUPERSET_URL=https://superset-dev.cloud.fiskerinc.com/superset/dashboard/8/?native_filters_key=KPnPthpLQ8rT--6PUdsPzQAcwnleRGHk_3dg0PVYfrXc3SE6zZ2x0p7JuerAZ0Pg diff --git a/.env.prd b/.env.prd index d8ce7fe..b8da28c 100644 --- a/.env.prd +++ b/.env.prd @@ -2,4 +2,4 @@ REACT_APP_AUTH_SERVICE_URL=https://gw.cloud.fiskerinc.com/compute_auth REACT_APP_CERT_SERVICE_URL=https://gw.cloud.fiskerinc.com/certificate REACT_APP_UPLOAD_SERVICE_URL=https://gw.cloud.fiskerinc.com/ota_update REACT_APP_AUTH_CALLBACK_URL=https://ota-admin.cloud.fiskerinc.com -REACT_APP_SUPERSET_URL=https://superset.cloud.fiskerinc.com +REACT_APP_SUPERSET_URL=https://superset.cloud.fiskerinc.com/superset/dashboard/9/?native_filters_key=mfJ1VjGTcLUKz7gQs_DgClZhjcdNucYMrPruNibcyDnhkDwdHbAumBRVTpA5tFH_ diff --git a/.env.stg b/.env.stg index ae036f3..224b2c2 100644 --- a/.env.stg +++ b/.env.stg @@ -2,4 +2,4 @@ REACT_APP_AUTH_SERVICE_URL=https://stg-gw.cloud.fiskerinc.com/compute_auth REACT_APP_CERT_SERVICE_URL=https://stg-gw.cloud.fiskerinc.com/certificate REACT_APP_UPLOAD_SERVICE_URL=https://stg-gw.cloud.fiskerinc.com/ota_update REACT_APP_AUTH_CALLBACK_URL=https://stg-ota-admin.cloud.fiskerinc.com -REACT_APP_SUPERSET_URL=http://superset-dev.fiskercloud.internal/superset/dashboard/8/?native_filters_key=KPnPthpLQ8rT--6PUdsPzQAcwnleRGHk_3dg0PVYfrXc3SE6zZ2x0p7JuerAZ0Pg +REACT_APP_SUPERSET_URL=https://stg-superset.cloud.fiskerinc.com/superset/dashboard/6/?native_filters_key=XBwRgJIvmxhqBhqlz45kuTnXc1iUY_M_ovzXCzXy5_l-AOFAXEaGLWpYIsfrEHGR diff --git a/src/components/App/__snapshots__/App.test.js.snap b/src/components/App/__snapshots__/App.test.js.snap index 57bb2d5..e6a4999 100644 --- a/src/components/App/__snapshots__/App.test.js.snap +++ b/src/components/App/__snapshots__/App.test.js.snap @@ -8298,6 +8298,24 @@ exports[`App Route /vehicle-status authenticated 1`] = ` class="MuiTouchRipple-root" /> + + diff --git a/src/components/Cars/CANSignals/index.jsx b/src/components/Cars/CANSignals/index.jsx new file mode 100644 index 0000000..f7cc549 --- /dev/null +++ b/src/components/Cars/CANSignals/index.jsx @@ -0,0 +1,104 @@ +import { + Table, + TableBody, + TableCell, + TableHead, + TableRow, +} from "@material-ui/core"; +import React, { useEffect, useState } from "react"; +import { logger } from "../../../services/monitoring"; +import { LocalDateTimeString } from "../../../utils/dates"; + +import { useVehicleContext } from "../../Contexts/VehicleContext"; + +const BlankSignal = (msg) => ({ + timestamp: "", + signal: msg, + value: "", +}); + +const transformSignals = (signals) => + signals + .map((signal) => { + const { Timestamp, ...Settings } = signal; + const keys = Object.keys(Settings); + + return keys.map((key) => ({ + timestamp: LocalDateTimeString(Timestamp), + signal: key, + value: Settings[key], + })); + }) + .flat(); + +const CANSignals = (props) => { + const { vin, token } = props; + const { getCANSignals } = useVehicleContext(); + const [signals, setSignals] = useState([]); + const delay = 500; + let timer = 0; + + const stopTimer = async () => { + if (timer === 0) return; + clearTimeout(timer); + timer = 0; + }; + + const startTimer = () => { + stopTimer(); + timer = setTimeout(() => { + updateSignals(); + }, delay); + }; + + const updateSignals = async () => { + try { + const result = await getCANSignals(vin, token); + const items = transformSignals(result.data); + + if (items.length > 0) { + setSignals(items); + } else { + setSignals(BlankSignal("No signals")); + } + + if (delay > 0) startTimer(); + } catch (e) { + setSignals(BlankSignal(e.message)); + logger.warn(e.stack); + } + }; + + useEffect(() => { + startTimer(); + return () => { + stopTimer(); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + if (!signals || signals.length === 0) return

Loading...

; + + return ( + + + + Timestamp + Signal + Value + + + + {signals.map((signal, i) => ( + + {signal.timestamp} + {signal.signal} + {signal.value} + + ))} + +
+ ); +}; + +export default CANSignals; diff --git a/src/components/Cars/Status/CANSignalsTab.jsx b/src/components/Cars/Status/CANSignalsTab.jsx new file mode 100644 index 0000000..ad35df1 --- /dev/null +++ b/src/components/Cars/Status/CANSignalsTab.jsx @@ -0,0 +1,35 @@ +import React from "react"; +import clsx from "clsx"; +import { Typography } from "@material-ui/core"; + +import useStyles from "../../useStyles"; +import { useUserContext } from "../../Contexts/UserContext"; +import CANSignals from "../CANSignals"; +import { VehicleProvider } from "../../Contexts/VehicleContext"; + +const Main = (props) => { + const { + token: { + idToken: { jwtToken: token }, + }, + } = useUserContext(); + const classes = useStyles(); + const { vin } = props; + + return ( +
+ + CAN Signals + + +
+ ); +}; + +const CANSignalsTab = (props) => ( + +
+ +); + +export default CANSignalsTab; diff --git a/src/components/Cars/Status/__snapshots__/index.test.jsx.snap b/src/components/Cars/Status/__snapshots__/index.test.jsx.snap index 1b762cd..34e37ea 100644 --- a/src/components/Cars/Status/__snapshots__/index.test.jsx.snap +++ b/src/components/Cars/Status/__snapshots__/index.test.jsx.snap @@ -101,6 +101,24 @@ exports[`CarStatus Render 1`] = ` class="MuiTouchRipple-root" /> + + diff --git a/src/components/Cars/Status/index.jsx b/src/components/Cars/Status/index.jsx index 3086a59..97f6122 100644 --- a/src/components/Cars/Status/index.jsx +++ b/src/components/Cars/Status/index.jsx @@ -11,6 +11,7 @@ import DigitalTwinTab from "./DigitalTwinTab"; import TabPanel from "../../Controls/TabPanel"; import { useStatusContext } from "../../Contexts/StatusContext"; import useStyles from "../../useStyles"; +import CANSignalsTab from "./CANSignalsTab"; const tabHashes = ["details", "updates", "filters"]; @@ -62,6 +63,7 @@ const CarStatus = () => { + @@ -80,6 +82,10 @@ const CarStatus = () => { + + + + ); }; diff --git a/src/components/Contexts/VehicleContext.jsx b/src/components/Contexts/VehicleContext.jsx index 7d22072..d6aac72 100644 --- a/src/components/Contexts/VehicleContext.jsx +++ b/src/components/Contexts/VehicleContext.jsx @@ -205,6 +205,20 @@ export const VehicleProvider = ({ children }) => { } }; + const getCANSignals = async (vin, token) => { + try { + setBusy(true); + validateVIN(vin); + + const result = await api.getCANSignals(vin, token); + if (result.error) + throw new Error(`Get CAN signals error. ${result.message}`); + return result; + } finally { + setBusy(false); + } + }; + return ( { addVehicle, deleteVehicle, getConnections, + getCANSignals, getECUs, getLocations, getModels, diff --git a/src/services/vehiclesAPI.js b/src/services/vehiclesAPI.js index ea1d408..d57c1d0 100644 --- a/src/services/vehiclesAPI.js +++ b/src/services/vehiclesAPI.js @@ -152,6 +152,17 @@ const vehiclesAPI = { }) .then(fetchRespHandler) .catch(errorHandler), + + getCANSignals: async (vin, token) => + fetch(`${API_ENDPOINT}/cansignals/${vin}`, { + method: "GET", + headers: Object.assign( + { "Content-Type": "application/json" }, + getAuthHeaderOptions(token) + ), + }) + .then(fetchRespHandler) + .catch(errorHandler), }; export default vehiclesAPI;