From c5a5839d41d2cf9c55e01f46134e6349840505bd Mon Sep 17 00:00:00 2001 From: John Wu <76966357+jwu-fisker@users.noreply.github.com> Date: Fri, 13 Jan 2023 15:29:31 -0800 Subject: [PATCH] CEC-3519 Add car version history (#260) * CEC-3519 Add car version history CEC-3455 Delete button is icon and remove column CEC-3496 Fix Issue delete * smell * Remove tab from issues details page * Fix date format --- .../App/__snapshots__/App.test.js.snap | 330 ++++++++++-------- src/components/Cars/Status/CarUpdatesTab.jsx | 16 +- .../Cars/Status/CarUpdatesTab.test.jsx | 7 +- src/components/Cars/Status/ECUsTab.jsx | 36 ++ src/components/Cars/Status/ECUsTab.test.jsx | 44 +++ .../__snapshots__/CarUpdatesTab.test.jsx.snap | 167 +++------ .../__snapshots__/ECUsTab.test.jsx.snap | 309 ++++++++++++++++ .../Status/__snapshots__/index.test.jsx.snap | 34 +- src/components/Cars/Status/index.jsx | 6 + src/components/Contexts/VehicleContext.jsx | 12 + .../Controls/CarVersionLogTable/index.jsx | 136 ++++++++ .../Controls/IssueSelectionTable/index.jsx | 46 +-- src/components/Issues/Info/Details/index.jsx | 4 +- src/components/Issues/Info/index.jsx | 71 +--- src/services/__mocks__/vehiclesAPI.js | 21 +- src/services/vehiclesAPI.js | 15 +- 16 files changed, 889 insertions(+), 365 deletions(-) create mode 100644 src/components/Cars/Status/ECUsTab.jsx create mode 100644 src/components/Cars/Status/ECUsTab.test.jsx create mode 100644 src/components/Cars/Status/__snapshots__/ECUsTab.test.jsx.snap create mode 100644 src/components/Controls/CarVersionLogTable/index.jsx diff --git a/src/components/App/__snapshots__/App.test.js.snap b/src/components/App/__snapshots__/App.test.js.snap index 3ecf212..fe162bb 100644 --- a/src/components/App/__snapshots__/App.test.js.snap +++ b/src/components/App/__snapshots__/App.test.js.snap @@ -1752,115 +1752,64 @@ exports[`App Route /issue-info authenticated 1`] = ` class="makeStyles-paper-0 makeStyles-tableSize-0" >
-
-
-
- -
- -
-
-
-
+ Issue Details +
-
- Issue Details -
-
-
-

- - ID - - : - FISKER123 -

-

- - VIN - - : - 1GNGC26RXXJ407648 -

-

- - Title - - : - sometitle -

-

- - Description - - : - 2343242 -

-

- - timestamp - - : - 2022-12-09T23:16:38.074858Z -

- Issue images -
-
+

+ + ID + + : + FISKER123 +

+

+ + VIN + + : + 1GNGC26RXXJ407648 +

+

+ + Title + + : + sometitle +

+

+ + Description + + : + 2343242 +

+

+ + timestamp + + : + 12/9/2022 11:16:38 PM +

+ Issue images
@@ -2472,29 +2421,6 @@ exports[`App Route /issues authenticated 1`] = ` - - - Description - - - sometitle - - 2343242 - @@ -2616,11 +2537,6 @@ exports[`App Route /issues authenticated 1`] = ` > sometitle - - 2343242 - @@ -2639,8 +2555,122 @@ exports[`App Route /issues authenticated 1`] = ` - - No issues found + +
+
+

+ Rows per page: +

+
+ + +
+

+ 0-0 of 0 +

+
+ + +
+
@@ -9623,8 +9653,12 @@ exports[`App Route /vehicle-status authenticated 1`] = ` class="MuiTabs-root" >
+
- Remote Commands + ECUs + + Remote Commands + + + + +
diff --git a/src/components/Cars/Status/index.jsx b/src/components/Cars/Status/index.jsx index d2d8cec..1ea797f 100644 --- a/src/components/Cars/Status/index.jsx +++ b/src/components/Cars/Status/index.jsx @@ -14,6 +14,7 @@ import CANSignalsTab from "./CANSignalsTab"; import CarUpdatesTab from "./CarUpdatesTab"; import CarDetailsTab from "./DetailsTab"; import DigitalTwinTab from "./DigitalTwinTab"; +import ECUsTab from "./ECUsTab"; import FleetsTab from "./FleetsTab"; import RemoteCommandsTab from "./RemoteCommandsTab"; @@ -41,6 +42,10 @@ const TabViews = [ label: "CAN Signals", component: CANSignalsTab, }, + { + label: "ECUs", + component: ECUsTab, + }, { label: "Remote Commands", component: RemoteCommandsTab, @@ -110,6 +115,7 @@ const CarStatus = () => { value={tabIndex} onChange={handleTabChange} aria-label="car tabs" + variant="scrollable" indicatorColor="secondary"> {tabs.map((item, index) => )} diff --git a/src/components/Contexts/VehicleContext.jsx b/src/components/Contexts/VehicleContext.jsx index cf4062a..412c64e 100644 --- a/src/components/Contexts/VehicleContext.jsx +++ b/src/components/Contexts/VehicleContext.jsx @@ -233,6 +233,17 @@ export const VehicleProvider = ({ children }) => { } } + const getVersionLog = async (search, token) => { + try { + setBusy(true); + const result = await api.getVersionLog(search, token); + if (result.error) throw new Error(`Get version log error. ${result.message}`); + return result; + } finally { + setBusy(false); + } + }; + return ( { sendCommand, updateVehicle, getFleets, + getVersionLog, }} > {children} diff --git a/src/components/Controls/CarVersionLogTable/index.jsx b/src/components/Controls/CarVersionLogTable/index.jsx new file mode 100644 index 0000000..fc91563 --- /dev/null +++ b/src/components/Controls/CarVersionLogTable/index.jsx @@ -0,0 +1,136 @@ +import { + Table, + TableBody, + TableCell, + TableFooter, + TablePagination, + TableRow +} from "@material-ui/core"; +import clsx from "clsx"; +import React, { useEffect, useState } from "react"; + +import { logger } from "../../../services/monitoring"; +import { LocalDateTimeString } from "../../../utils/dates"; +import { useStatusContext } from "../../Contexts/StatusContext"; +import { useVehicleContext } from "../../Contexts/VehicleContext"; +import TableHeaderSortable from "../../Table/HeaderSortable"; +import { useLocalStorage } from "../../useLocalStorage"; + +const tableColumns = [ + { + id: "version_source", + label: "Type", + }, + { + id: "version", + label: "Version", + }, + { + id: "created_at", + label: "Date", + }, +]; + +const PAGE_SIZE = "CAR_VERSIONLOG_TABLE_PAGE_SIZE"; + +const CarVersionLogTable = ({ vin, token, classes }) => { + const [versions, setVersions] = useState([]); + const [total, setTotal] = useState(0); + const [pageSize, setPageSize] = useLocalStorage(PAGE_SIZE, 10); + const [pageIndex, setPageIndex] = useState(0); + const [orderBy, setOrderBy] = useState("created_at"); + const [order, setOrder] = useState("desc"); + const { getVersionLog } = useVehicleContext(); + const { setMessage } = useStatusContext(); + + useEffect(() => { + (async () => { + try { + if (!vin || !token) return; + const result = await getVersionLog( + { + vin, + limit: pageSize, + offset: pageSize * pageIndex, + order: `${orderBy} ${order}`, + }, + token + ); + setVersions(result.data); + if (result.total > -1) setTotal(result.total); + } catch (e) { + setMessage(e.message); + logger.warn(e.stack); + } + })(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [vin, 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); + } + }; + + return ( +
+ + + + {versions && versions.map((row, i) => ( + + {row.version_source} + {row.version} + {LocalDateTimeString(row.created_at)} + + ))} + + + + + + +
+
+ ); +}; + +export default CarVersionLogTable; diff --git a/src/components/Controls/IssueSelectionTable/index.jsx b/src/components/Controls/IssueSelectionTable/index.jsx index 7dc40cb..9ef35d6 100644 --- a/src/components/Controls/IssueSelectionTable/index.jsx +++ b/src/components/Controls/IssueSelectionTable/index.jsx @@ -1,26 +1,26 @@ -import React, { useEffect, useState } from "react"; -import { Link } from "react-router-dom"; -import PropTypes from "prop-types"; import { Table, TableBody, TableCell, TableFooter, TablePagination, - TableRow, - Button, + TableRow } from "@material-ui/core"; +import DeleteIcon from "@material-ui/icons/Delete"; import clsx from "clsx"; +import PropTypes from "prop-types"; +import React, { useEffect, useState } from "react"; +import { Link } from "react-router-dom"; +import { logger } from "../../../services/monitoring"; +import { LocalDateTimeString } from "../../../utils/dates"; +import { Permissions } from "../../../utils/roles"; import { useIssueContext } from "../../Contexts/IssueContext"; import { useStatusContext } from "../../Contexts/StatusContext"; -import { LocalDateTimeString } from "../../../utils/dates"; +import { useUserContext } from "../../Contexts/UserContext"; import TableHeaderSortable from "../../Table/HeaderSortable"; -import { logger } from "../../../services/monitoring"; import { useLocalStorage } from "../../useLocalStorage"; import { RoleWrap } from "../RoleWrap"; -import { useUserContext } from "../../Contexts/UserContext"; -import { Permissions } from "../../../utils/roles"; const tableColumns = [ { @@ -35,10 +35,6 @@ const tableColumns = [ id: "title", label: "Title", }, - { - id: "description", - label: "Description", - }, { id: "driver_id", label: "Driver ID", @@ -69,7 +65,7 @@ const IssueSelectionTable = (props) => { const [pageIndex, setPageIndex] = useState(0); const [orderBy, setOrderBy] = useState("created_at"); const [order, setOrder] = useState("asc"); - const { getIssues, issues, totalIssues = 0 } = useIssueContext(); + const { deleteIssue, getIssues, issues, totalIssues = 0 } = useIssueContext(); const { groups, providers } = useUserContext(); const { setMessage } = useStatusContext(); @@ -132,12 +128,19 @@ const IssueSelectionTable = (props) => { setPageIndex(0); }, [search]); - const { deleteIssue } = useIssueContext(); const handleDelete = (id) => { deleteIssue(id, token).then(() => { - getIssues(token) + getIssues( + { + limit: pageSize, + offset: pageSize * pageIndex, + order: `${orderBy} ${order}`, + }, + token + ); }); }; + return (
@@ -161,7 +164,6 @@ const IssueSelectionTable = (props) => { {row.vin}{row.title} - {row.description || ""}{row.driver_id} {LocalDateTimeString(row.timestamp)} @@ -172,7 +174,9 @@ const IssueSelectionTable = (props) => { rolesPerProvider={Permissions.FiskerDelete} > - + handleDelete(row.id)}> + + @@ -181,12 +185,9 @@ const IssueSelectionTable = (props) => { - {totalIssues === 0 ? ( - - ) : ( { onPageChange={handleChangePageIndex} onRowsPerPageChange={handleChangePageSize} /> - )}
No issues found
diff --git a/src/components/Issues/Info/Details/index.jsx b/src/components/Issues/Info/Details/index.jsx index c3031aa..f0dddf5 100644 --- a/src/components/Issues/Info/Details/index.jsx +++ b/src/components/Issues/Info/Details/index.jsx @@ -2,6 +2,7 @@ import { Grid } from "@material-ui/core"; import clsx from "clsx"; import React, { useEffect } from "react"; +import { LocalDateTimeString } from "../../../../utils/dates"; import { logger } from "../../../../services/monitoring"; import { useStatusContext } from "../../../Contexts/StatusContext"; import { useUserContext } from "../../../Contexts/UserContext"; @@ -11,7 +12,6 @@ import { } from "../../../Contexts/IssueContext"; import useStyles from "../../../useStyles"; - const MainForm = ({ id }) => { const classes = useStyles(); const { setMessage } = useStatusContext(); @@ -55,7 +55,7 @@ const MainForm = ({ id }) => { Description: {issue.description}

- timestamp: {issue.timestamp} + timestamp: {LocalDateTimeString(issue.timestamp)}

{issue.images && issue.images.map((image, index) => ( Issue images diff --git a/src/components/Issues/Info/index.jsx b/src/components/Issues/Info/index.jsx index bfa6deb..39942af 100644 --- a/src/components/Issues/Info/index.jsx +++ b/src/components/Issues/Info/index.jsx @@ -1,55 +1,15 @@ -import { Box, Tab, Tabs } from "@material-ui/core"; import clsx from "clsx"; -import React, { useEffect, useState } from "react"; +import React, { useEffect } from "react"; import { useParams } from "react-router"; -import { useLocation } from "react-router-dom"; -import { hasRole } from "../../../utils/roles"; import { useStatusContext } from "../../Contexts/StatusContext"; -import { useUserContext } from "../../Contexts/UserContext"; -import TabPanel from "../../Controls/TabPanel"; import useStyles from "../../useStyles"; import IssueDetailsTab from "./DetailsTab"; - -const tabHashes = ["details", "updates", "filters"]; - -const TabViews = [ - { - label: "Details", - component: IssueDetailsTab, - }, - -]; - -const filterTabs = (data, groups, providers) => { - return data.reduce((result, item) => { - if (hasRole(groups, item.rolesPerProvider, providers)) { - result.push(item); - } - return result; - }, []); -}; - const IssueInfo = () => { const { id } = useParams(); const classes = useStyles(); const { setTitle, setSitePath } = useStatusContext(); - const { hash } = useLocation(); - const [tabIndex, setTabIndex] = useState(0); - const [tabs, setTabs] = useState([]); - const { groups, providers } = useUserContext(); - - useEffect(() => { - const data = filterTabs(TabViews, groups, providers); - setTabs(data); - }, [groups, providers]); - - useEffect(() => { - const key = hash.replace("#", ""); - const index = tabHashes.findIndex((element) => element === key); - if (index >= 0) setTabIndex(index); - }, [hash]); useEffect(() => { const title = `Issue ${id} Details`; @@ -66,39 +26,12 @@ const IssueInfo = () => { // eslint-disable-next-line react-hooks/exhaustive-deps }, [id]); - const handleTabChange = (_event, newIndex) => { - setTabIndex(newIndex); - }; - return (
- - - {tabs.map((item, index) => )} - - - {tabs.map((item, index) => ( - - - - ))} +
); }; -function tabProps(index) { - return { - id: `tab-${index}`, - "aria-controls": `tabpanel-${index}`, - }; -} - export default IssueInfo; diff --git a/src/services/__mocks__/vehiclesAPI.js b/src/services/__mocks__/vehiclesAPI.js index ccd7537..d18a88b 100644 --- a/src/services/__mocks__/vehiclesAPI.js +++ b/src/services/__mocks__/vehiclesAPI.js @@ -116,7 +116,26 @@ const vehiclesAPI = { }, getCANSignals: async (vin, vehicle) => { return signals; - } + }, + getVersionLog: async (vin) => ({ + "data": [ + { + "id": 1, + "vin": "${vin}", + "version_source": "TREX", + "version": "0.9.56", + "created_at": "2023-01-13T02:11:33.327214Z" + }, + { + "id": 2, + "vin": "${vin}", + "version_source": "DBC", + "version": "386c18977a1be3cda60c953e5902c680dbe82b89523f2527e80cd9db863db991", + "created_at": "2023-01-13T02:11:33.330932Z" + } + ], + "total": 2 + }) }; export default vehiclesAPI; diff --git a/src/services/vehiclesAPI.js b/src/services/vehiclesAPI.js index 86a2bc8..9d8d0db 100644 --- a/src/services/vehiclesAPI.js +++ b/src/services/vehiclesAPI.js @@ -1,5 +1,5 @@ import { - addQueryParams, errorHandler, fetchRespHandler, getAuthHeaderOptions + addQueryParams, errorHandler, fetchRespHandler, getAuthHeaderOptions } from "../utils/http"; const API_ENDPOINT = process.env.REACT_APP_OTA_SERVICE_URL; @@ -172,6 +172,19 @@ const vehiclesAPI = { }) .then(fetchRespHandler) .catch(errorHandler), + + getVersionLog: async ({vin, ...search}, token) => { + const u = addQueryParams(`${API_ENDPOINT}/vehicle/${vin}/version/logs`, search); + return fetch(u, { + method: "GET", + headers: Object.assign( + { "Content-Type": "application/json" }, + getAuthHeaderOptions(token) + ), + }) + .then(fetchRespHandler) + .catch(errorHandler) + }, }; export default vehiclesAPI;