diff --git a/.github/workflows/blackduck.yml b/.github/workflows/blackduck.yml index f69b1a3..3fe8da2 100644 --- a/.github/workflows/blackduck.yml +++ b/.github/workflows/blackduck.yml @@ -1,40 +1,13 @@ name: Blackduck on: - schedule: - # run scans twice a month - - cron: "0 2 1,15 * *" + schedule: + # run scans twice a month + - cron: '0 2 1,15 * *' jobs: blackduck: - runs-on: ubuntu-latest - steps: - - name: Checkout Code - uses: actions/checkout@v2 - - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v2 - with: - node-version: "16" - cache: "npm" - - run: npm install - - run: npm run build - - # ota-admin-portal - - name: Run Synopsys Detect - ota-admin-portal - uses: synopsys-sig/detect-action@v0.3.2 - env: - DETECT_PROJECT_NAME: ota-admin-portal - DETECT_EXCLUDED_DIRECTORIES: node_modules - DETECT_PROJECT_VERSION_NAME: default - DETECT_NPM_INCLUDE_DEV_DEPENDENCIES: "FALSE" - # DETECT_DETECTOR_SEARCH_EXCLUSION_DEFAULTS: "true" - DETECT_DETECTOR_SEARCH_DEPTH: 0 - DETECT_DETECTOR_SEARCH_CONTINUE: "true" - - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - detect-version: 7.9.0 - blackduck-url: ${{ secrets.BLACKDUCK_URL }} - blackduck-api-token: ${{ secrets.BLACKDUCK_API_KEY }} - scan-mode: INTELLIGENT + name: Blackduck scan + uses: Fisker-Inc/github-actions/.github/workflows/blackduck.yml@main + with: + project: ota-admin-portal diff --git a/package-lock.json b/package-lock.json index 87ab3df..b14475f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,7 +17,7 @@ "@material-ui/icons": "^4.11.3", "@material-ui/pickers": "^3.3.10", "@mui/material": "^5.10.14", - "@superset-ui/embedded-sdk": "^0.1.0-alpha.7", + "@superset-ui/embedded-sdk": "^0.1.0-alpha.8", "@testing-library/jest-dom": "^5.16.4", "@testing-library/react": "^12.1.4", "@testing-library/user-event": "^13.5.0", @@ -4821,11 +4821,12 @@ } }, "node_modules/@superset-ui/embedded-sdk": { - "version": "0.1.0-alpha.7", - "resolved": "https://registry.npmjs.org/@superset-ui/embedded-sdk/-/embedded-sdk-0.1.0-alpha.7.tgz", - "integrity": "sha512-sBzfSnPvRw15D6A053t4gpKDydHaAjyn88By1Z3Vl3PWZScW6sTHh8n/5A69qMSJVorqFQ3g0IUquTF8sutGEQ==", + "version": "0.1.0-alpha.8", + "resolved": "https://registry.npmjs.org/@superset-ui/embedded-sdk/-/embedded-sdk-0.1.0-alpha.8.tgz", + "integrity": "sha512-X07s8uMbvQDEMe5GRyKXhVW5XzPm+yV4KQSo7WifWz7TdqLUT+rjNsL4jZAGAoO0/n0Fj7gFoDpT6bGlSULe5g==", "dependencies": { - "@superset-ui/switchboard": "^0.18.26-0" + "@superset-ui/switchboard": "^0.18.26-0", + "jwt-decode": "^3.1.2" } }, "node_modules/@superset-ui/switchboard": { @@ -21106,11 +21107,12 @@ } }, "@superset-ui/embedded-sdk": { - "version": "0.1.0-alpha.7", - "resolved": "https://registry.npmjs.org/@superset-ui/embedded-sdk/-/embedded-sdk-0.1.0-alpha.7.tgz", - "integrity": "sha512-sBzfSnPvRw15D6A053t4gpKDydHaAjyn88By1Z3Vl3PWZScW6sTHh8n/5A69qMSJVorqFQ3g0IUquTF8sutGEQ==", + "version": "0.1.0-alpha.8", + "resolved": "https://registry.npmjs.org/@superset-ui/embedded-sdk/-/embedded-sdk-0.1.0-alpha.8.tgz", + "integrity": "sha512-X07s8uMbvQDEMe5GRyKXhVW5XzPm+yV4KQSo7WifWz7TdqLUT+rjNsL4jZAGAoO0/n0Fj7gFoDpT6bGlSULe5g==", "requires": { - "@superset-ui/switchboard": "^0.18.26-0" + "@superset-ui/switchboard": "^0.18.26-0", + "jwt-decode": "^3.1.2" } }, "@superset-ui/switchboard": { diff --git a/package.json b/package.json index 0198a1f..f584165 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "@material-ui/icons": "^4.11.3", "@material-ui/pickers": "^3.3.10", "@mui/material": "^5.10.14", - "@superset-ui/embedded-sdk": "^0.1.0-alpha.7", + "@superset-ui/embedded-sdk": "^0.1.0-alpha.8", "@testing-library/jest-dom": "^5.16.4", "@testing-library/react": "^12.1.4", "@testing-library/user-event": "^13.5.0", @@ -87,4 +87,4 @@ "lcov" ] } -} +} \ No newline at end of file diff --git a/src/components/App/__snapshots__/App.test.js.snap b/src/components/App/__snapshots__/App.test.js.snap index 348551e..cadf0bb 100644 --- a/src/components/App/__snapshots__/App.test.js.snap +++ b/src/components/App/__snapshots__/App.test.js.snap @@ -549,13 +549,13 @@ exports[`App Route / authenticated 1`] = ` > Leaflet - + - © + © @@ -1185,13 +1185,13 @@ exports[`App Route /home authenticated 1`] = ` > Leaflet - + - © + © @@ -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 +

+

+ + Created + + : + 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 - @@ -2641,7 +2557,7 @@ exports[`App Route /issues authenticated 1`] = ` >
- 1-NaN of undefined + 0-0 of 0

@@ -3325,7 +3239,7 @@ exports[`App Route /package-deploy authenticated 1`] = `

- Created + Created 7/1/2021 10:40:07 PM .

@@ -9739,8 +9653,12 @@ exports[`App Route /vehicle-status authenticated 1`] = ` class="MuiTabs-root" >
+
- Remote Commands + ECUs + + Remote Commands + + + +
@@ -9991,35 +9927,35 @@ exports[`App Route /vehicle-status authenticated 1`] = ` Enabled - : + : true

Max Memory Buffer Size - : + : 1

Enabled - : + : true

Max Disk Buffer Size - : + : 2

Filters - : + : 3

@@ -10088,6 +10024,12 @@ exports[`App Route /vehicle-status authenticated 1`] = ` id="tabpanel-6" role="tabpanel" /> + diff --git a/src/components/Cars/Status/CarUpdatesTab.jsx b/src/components/Cars/Status/CarUpdatesTab.jsx index 43aa451..bd5dcda 100644 --- a/src/components/Cars/Status/CarUpdatesTab.jsx +++ b/src/components/Cars/Status/CarUpdatesTab.jsx @@ -1,12 +1,12 @@ +import { Typography } from "@material-ui/core"; +import clsx from "clsx"; import React from "react"; import { useParams } from "react-router"; -import clsx from "clsx"; -import { Typography } from "@material-ui/core"; -import CarECUsTable from "../../Controls/CarECUsTable"; -import CarUpdatesTable from "../../Controls/CarUpdatesTable"; -import { VehicleProvider } from "../../Contexts/VehicleContext"; import { useUserContext } from "../../Contexts/UserContext"; +import { VehicleProvider } from "../../Contexts/VehicleContext"; +import CarUpdatesTable from "../../Controls/CarUpdatesTable"; +import CarVersionLogTable from "../../Controls/CarVersionLogTable"; import useStyles from "../../useStyles"; const MainForm = () => { @@ -22,10 +22,8 @@ const MainForm = () => {
Car Updates - - Car ECUs - - + Version Log +
); }; diff --git a/src/components/Cars/Status/CarUpdatesTab.test.jsx b/src/components/Cars/Status/CarUpdatesTab.test.jsx index 1251721..8846413 100644 --- a/src/components/Cars/Status/CarUpdatesTab.test.jsx +++ b/src/components/Cars/Status/CarUpdatesTab.test.jsx @@ -1,6 +1,7 @@ jest.mock("../../Contexts/CANFiltersContext"); jest.mock("../../Contexts/StatusContext"); jest.mock("../../Contexts/UserContext"); +jest.mock("../../../services/vehiclesAPI"); jest.mock("@material-ui/core/utils/unstable_useId", () => jest.fn().mockReturnValue("mui-test-id") ); @@ -8,12 +9,12 @@ jest.mock("@material-ui/core/utils/unstable_useId", () => import { render, waitFor } from "@testing-library/react"; import { BrowserRouter } from "react-router-dom"; +import addSnapshotSerializer from "../../../utils/snapshot"; +import { TEST_AUTH_OBJECT_FISKER } from "../../../utils/testing"; import { CANFiltersProvider } from "../../Contexts/CANFiltersContext"; import { StatusProvider } from "../../Contexts/StatusContext"; -import { UserProvider, setToken } from "../../Contexts/UserContext"; -import { TEST_AUTH_OBJECT_FISKER } from "../../../utils/testing"; +import { setToken, UserProvider } from "../../Contexts/UserContext"; import MainForm from "./CarUpdatesTab"; -import addSnapshotSerializer from "../../../utils/snapshot"; const renderCarUpdatesTab = async () => { const { container } = render( diff --git a/src/components/Cars/Status/ECUsTab.jsx b/src/components/Cars/Status/ECUsTab.jsx new file mode 100644 index 0000000..efff526 --- /dev/null +++ b/src/components/Cars/Status/ECUsTab.jsx @@ -0,0 +1,36 @@ +import { Typography } from "@material-ui/core"; +import clsx from "clsx"; +import React from "react"; +import { useParams } from "react-router"; + +import { useUserContext } from "../../Contexts/UserContext"; +import { VehicleProvider } from "../../Contexts/VehicleContext"; +import CarECUsTable from "../../Controls/CarECUsTable"; +import useStyles from "../../useStyles"; + +const MainForm = () => { + const { vin } = useParams(); + const classes = useStyles(); + const { + token: { + idToken: { jwtToken: token }, + }, + } = useUserContext(); + + return ( +
+ + Car ECUs + + +
+ ); +}; + +const CarUpdatesTab = () => ( + + + +); + +export default CarUpdatesTab; diff --git a/src/components/Cars/Status/ECUsTab.test.jsx b/src/components/Cars/Status/ECUsTab.test.jsx new file mode 100644 index 0000000..edd5720 --- /dev/null +++ b/src/components/Cars/Status/ECUsTab.test.jsx @@ -0,0 +1,44 @@ +jest.mock("../../Contexts/CANFiltersContext"); +jest.mock("../../Contexts/StatusContext"); +jest.mock("../../Contexts/UserContext"); +jest.mock("../../../services/vehiclesAPI"); +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 addSnapshotSerializer from "../../../utils/snapshot"; +import { TEST_AUTH_OBJECT_FISKER } from "../../../utils/testing"; +import { StatusProvider } from "../../Contexts/StatusContext"; +import { setToken, UserProvider } from "../../Contexts/UserContext"; +import MainForm from "./ECUsTab"; + +const renderECUsTab = async () => { + const { container } = render( + + + + + + + + ); + await waitFor(() => { + /* render */ + }); + return container; +}; + +describe("ECUsTab", () => { + beforeAll(() => { + addSnapshotSerializer(expect); + }); + + it("Render", async () => { + setToken(TEST_AUTH_OBJECT_FISKER); + const container = await renderECUsTab(); + expect(container).toMatchSnapshot(); + }); +}); diff --git a/src/components/Cars/Status/__snapshots__/CarUpdatesTab.test.jsx.snap b/src/components/Cars/Status/__snapshots__/CarUpdatesTab.test.jsx.snap index 7468285..d3e7afe 100644 --- a/src/components/Cars/Status/__snapshots__/CarUpdatesTab.test.jsx.snap +++ b/src/components/Cars/Status/__snapshots__/CarUpdatesTab.test.jsx.snap @@ -164,16 +164,16 @@ exports[`CarUpdatesTab Render 1`] = ` -

+ No Car Updates found -

+
- Car ECUs + Version Log
+ + + Type + + + + + + Version + + + - ECU + Date @@ -216,121 +262,6 @@ exports[`CarUpdatesTab Render 1`] = ` - - - SW Version - - - - - - HW Version - - - - - - Config - - - - - - Created - - - - - - Updated - - -
+
+

+ Door Locks +

+

+ + driver + + : + Closed +

+

+ + all + + : + Locked +

+
+
+

+ Sunroof +

+

+ + sunroof + + : + closed +

+
@@ -152,6 +187,17 @@ exports[`DigitalTwinTab Render 1`] = ` 7/26/2022 12:26:38 AM

+
+

+ + DBC version + + : + d439abd3662dd20099f49dd8f43f7b145202e961caa2b5aba2c6154c8096348b +

+
diff --git a/src/components/Cars/Status/__snapshots__/ECUsTab.test.jsx.snap b/src/components/Cars/Status/__snapshots__/ECUsTab.test.jsx.snap new file mode 100644 index 0000000..9766841 --- /dev/null +++ b/src/components/Cars/Status/__snapshots__/ECUsTab.test.jsx.snap @@ -0,0 +1,309 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`ECUsTab Render 1`] = ` +
+
+
+
+
+ Car ECUs +
+
+ + + + + + + + + + + + + + + + + +
+ + ECU + + sorted descending + + + + + + SW Version + + + + + HW Version + + + + + Config + + + + + Created + + + + + Updated + + +
+
+
+
+
+
+`; diff --git a/src/components/Cars/Status/__snapshots__/index.test.jsx.snap b/src/components/Cars/Status/__snapshots__/index.test.jsx.snap index 23e1eba..e2731f0 100644 --- a/src/components/Cars/Status/__snapshots__/index.test.jsx.snap +++ b/src/components/Cars/Status/__snapshots__/index.test.jsx.snap @@ -21,8 +21,12 @@ exports[`CarStatus Render 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/Contexts/__mocks__/VehicleContext.jsx b/src/components/Contexts/__mocks__/VehicleContext.jsx index f31d80c..c545745 100644 --- a/src/components/Contexts/__mocks__/VehicleContext.jsx +++ b/src/components/Contexts/__mocks__/VehicleContext.jsx @@ -70,6 +70,7 @@ let vehicleState = { temperature: 26, }, trex_version: "1000000", + dbc_version: "d439abd3662dd20099f49dd8f43f7b145202e961caa2b5aba2c6154c8096348b", ip: "172.20.0.17:49850", updated: "2022-07-26T00:26:38.880381Z", }, diff --git a/src/components/Controls/CarUpdatesTable/index.jsx b/src/components/Controls/CarUpdatesTable/index.jsx index 19882c9..69fa586 100644 --- a/src/components/Controls/CarUpdatesTable/index.jsx +++ b/src/components/Controls/CarUpdatesTable/index.jsx @@ -6,7 +6,7 @@ import { TableFooter, TablePagination, TableRow, - Tooltip, + Tooltip } from "@material-ui/core"; import CancelIcon from "@material-ui/icons/Cancel"; import React, { useEffect, useState } from "react"; @@ -16,7 +16,7 @@ import { logger } from "../../../services/monitoring"; import { LocalDateTimeString } from "../../../utils/dates"; import { CarUpdatesProvider, - useCarUpdatesContext, + useCarUpdatesContext } from "../../Contexts/CarUpdatesContext"; import { useStatusContext } from "../../Contexts/StatusContext"; import { useUserContext } from "../../Contexts/UserContext"; @@ -198,7 +198,7 @@ const MainForm = ({ vin, token }) => { {totalCarUpdates === 0 ? ( -

No Car Updates found

+ No Car Updates found ) : ( { + 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 3dc8c5f..ad351e4 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 } = 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 ? ( -

No issues found

- ) : ( { onPageChange={handleChangePageIndex} onRowsPerPageChange={handleChangePageSize} /> - )}
@@ -214,4 +214,4 @@ IssueSelectionTable.propTypes = { onSelectAll: PropTypes.func, }; -export default IssueSelectionTable; +export default IssueSelectionTable; \ No newline at end of file diff --git a/src/components/DigitalTwin/index.js b/src/components/DigitalTwin/index.js index 8129ce9..a0c1141 100644 --- a/src/components/DigitalTwin/index.js +++ b/src/components/DigitalTwin/index.js @@ -1,7 +1,7 @@ import React from "react"; -import useStyles from "../useStyles"; import { LocalDateTimeString } from "../../utils/dates"; +import useStyles from "../useStyles"; const keyValueTemplate = (key, value) => (

@@ -14,7 +14,7 @@ const mapOpenCloseState = (value) => const DigitalTwin = (props) => { const classes = useStyles(); - const { battery, doors, location, trex_version, ip, updated, windows } = props; + const { battery, doors, location, trex_version, ip, updated, windows, misc_windows, sunroof, dbc_version, door_locks } = props; return (

@@ -28,10 +28,55 @@ const DigitalTwin = (props) => { {Object.entries(doors).map(mapOpenCloseState)}
)} + {door_locks != null && ( +
+

Door Locks

+ {Object.entries(door_locks).map((value) => { + if (value[0] === "driver") { + return keyValueTemplate(value[0], value[1] ? "Open" : "Closed"); + } else { + return keyValueTemplate(value[0], value[1] ? "Unlocked" : "Locked"); + } + })} +
+ )} {windows != null && (

Windows

- {Object.entries(windows).map(mapOpenCloseState)} + {Object.entries(windows).map((value) => { + if (value[1] === 0) { + return keyValueTemplate(value[0], "closed"); + } else { + const percentOpen = Math.min(value[1], 100); + return keyValueTemplate(value[0], `${percentOpen}% open`); + } + })} +
+ )} + {misc_windows != null && ( +
+

Misc Windows

+ {Object.entries(misc_windows).map((value) => { + if (value[1] === 0) { + return keyValueTemplate(value[0], "closed"); + } else { + const percentOpen = Math.min(value[1], 100); + return keyValueTemplate(value[0], `${percentOpen}% open`); + } + })} +
+ )} + {sunroof != null && ( +
+

Sunroof

+ {Object.entries(sunroof).map((value) => { + if (value[1] === 0) { + return keyValueTemplate(value[0], "closed"); + } else { + const percentOpen = Math.min(value[1], 100); + return keyValueTemplate(value[0], `${percentOpen}% open`); + } + })}
)} {location != null && ( @@ -61,6 +106,11 @@ const DigitalTwin = (props) => { {keyValueTemplate("Updated at", LocalDateTimeString(updated))}
)} + {dbc_version != null && ( +
+ {keyValueTemplate("DBC version", dbc_version)} +
+ )}
); }; diff --git a/src/components/Issues/Info/Details/index.jsx b/src/components/Issues/Info/Details/index.jsx index c3031aa..307eafc 100644 --- a/src/components/Issues/Info/Details/index.jsx +++ b/src/components/Issues/Info/Details/index.jsx @@ -3,15 +3,14 @@ import clsx from "clsx"; import React, { useEffect } from "react"; import { logger } from "../../../../services/monitoring"; +import { LocalDateTimeString } from "../../../../utils/dates"; +import { + IssueProvider, useIssueContext +} from "../../../Contexts/IssueContext"; import { useStatusContext } from "../../../Contexts/StatusContext"; import { useUserContext } from "../../../Contexts/UserContext"; -import { - useIssueContext, - IssueProvider -} from "../../../Contexts/IssueContext"; import useStyles from "../../../useStyles"; - const MainForm = ({ id }) => { const classes = useStyles(); const { setMessage } = useStatusContext(); @@ -55,7 +54,7 @@ const MainForm = ({ id }) => { Description: {issue.description}

- timestamp: {issue.timestamp} + Created: {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/components/VehicleMap/index.jsx b/src/components/VehicleMap/index.jsx index ff61861..58475ca 100644 --- a/src/components/VehicleMap/index.jsx +++ b/src/components/VehicleMap/index.jsx @@ -1,15 +1,15 @@ -import React, { useEffect, useState } from "react"; -import useStyles from "../useStyles"; -import L from "leaflet"; -import { MapContainer, TileLayer, Marker, Popup, useMap } from "react-leaflet"; import { Button } from "@material-ui/core"; +import L from "leaflet"; +import React, { useEffect, useState } from "react"; +import { MapContainer, Marker, Popup, TileLayer, useMap } from "react-leaflet"; +import useStyles from "../useStyles"; +import GrayMarkerIcon from "../../assets/gray-marker.png"; +import GreenMarkerIcon from "../../assets/green-marker.png"; +import { logger } from "../../services/monitoring"; import { useUserContext } from "../Contexts/UserContext"; import { useVehicleContext, VehicleProvider } from "../Contexts/VehicleContext"; import { VehiclePopUp } from "./popup"; -import GreenMarkerIcon from "../../assets/green-marker.png"; -import GrayMarkerIcon from "../../assets/gray-marker.png"; -import { logger } from "../../services/monitoring"; const Component = () => { const classes = useStyles(); @@ -185,16 +185,7 @@ const Component = () => { {carState ? ( 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;