Merge branch 'develop' into release/0.0.3

This commit is contained in:
jwu-fisker
2023-01-13 17:30:54 -08:00
24 changed files with 905 additions and 435 deletions

View File

@@ -1,40 +1,13 @@
name: Blackduck name: Blackduck
on: on:
schedule: schedule:
# run scans twice a month # run scans twice a month
- cron: "0 2 1,15 * *" - cron: '0 2 1,15 * *'
jobs: jobs:
blackduck: blackduck:
runs-on: ubuntu-latest name: Blackduck scan
steps: uses: Fisker-Inc/github-actions/.github/workflows/blackduck.yml@main
- name: Checkout Code with:
uses: actions/checkout@v2 project: ota-admin-portal
- 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

20
package-lock.json generated
View File

@@ -17,7 +17,7 @@
"@material-ui/icons": "^4.11.3", "@material-ui/icons": "^4.11.3",
"@material-ui/pickers": "^3.3.10", "@material-ui/pickers": "^3.3.10",
"@mui/material": "^5.10.14", "@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/jest-dom": "^5.16.4",
"@testing-library/react": "^12.1.4", "@testing-library/react": "^12.1.4",
"@testing-library/user-event": "^13.5.0", "@testing-library/user-event": "^13.5.0",
@@ -4821,11 +4821,12 @@
} }
}, },
"node_modules/@superset-ui/embedded-sdk": { "node_modules/@superset-ui/embedded-sdk": {
"version": "0.1.0-alpha.7", "version": "0.1.0-alpha.8",
"resolved": "https://registry.npmjs.org/@superset-ui/embedded-sdk/-/embedded-sdk-0.1.0-alpha.7.tgz", "resolved": "https://registry.npmjs.org/@superset-ui/embedded-sdk/-/embedded-sdk-0.1.0-alpha.8.tgz",
"integrity": "sha512-sBzfSnPvRw15D6A053t4gpKDydHaAjyn88By1Z3Vl3PWZScW6sTHh8n/5A69qMSJVorqFQ3g0IUquTF8sutGEQ==", "integrity": "sha512-X07s8uMbvQDEMe5GRyKXhVW5XzPm+yV4KQSo7WifWz7TdqLUT+rjNsL4jZAGAoO0/n0Fj7gFoDpT6bGlSULe5g==",
"dependencies": { "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": { "node_modules/@superset-ui/switchboard": {
@@ -21106,11 +21107,12 @@
} }
}, },
"@superset-ui/embedded-sdk": { "@superset-ui/embedded-sdk": {
"version": "0.1.0-alpha.7", "version": "0.1.0-alpha.8",
"resolved": "https://registry.npmjs.org/@superset-ui/embedded-sdk/-/embedded-sdk-0.1.0-alpha.7.tgz", "resolved": "https://registry.npmjs.org/@superset-ui/embedded-sdk/-/embedded-sdk-0.1.0-alpha.8.tgz",
"integrity": "sha512-sBzfSnPvRw15D6A053t4gpKDydHaAjyn88By1Z3Vl3PWZScW6sTHh8n/5A69qMSJVorqFQ3g0IUquTF8sutGEQ==", "integrity": "sha512-X07s8uMbvQDEMe5GRyKXhVW5XzPm+yV4KQSo7WifWz7TdqLUT+rjNsL4jZAGAoO0/n0Fj7gFoDpT6bGlSULe5g==",
"requires": { "requires": {
"@superset-ui/switchboard": "^0.18.26-0" "@superset-ui/switchboard": "^0.18.26-0",
"jwt-decode": "^3.1.2"
} }
}, },
"@superset-ui/switchboard": { "@superset-ui/switchboard": {

View File

@@ -12,7 +12,7 @@
"@material-ui/icons": "^4.11.3", "@material-ui/icons": "^4.11.3",
"@material-ui/pickers": "^3.3.10", "@material-ui/pickers": "^3.3.10",
"@mui/material": "^5.10.14", "@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/jest-dom": "^5.16.4",
"@testing-library/react": "^12.1.4", "@testing-library/react": "^12.1.4",
"@testing-library/user-event": "^13.5.0", "@testing-library/user-event": "^13.5.0",
@@ -87,4 +87,4 @@
"lcov" "lcov"
] ]
} }
} }

View File

@@ -1752,115 +1752,64 @@ exports[`App Route /issue-info authenticated 1`] = `
class="makeStyles-paper-0 makeStyles-tableSize-0" class="makeStyles-paper-0 makeStyles-tableSize-0"
> >
<div <div
class="MuiBox-root MuiBox-root-0 makeStyles-tableToolbar-0" class="makeStyles-paper-0 makeStyles-tableSize-0"
> >
<div <h6
class="MuiTabs-root" class="MuiTypography-root MuiTypography-h6"
> >
<div Issue Details
class="MuiTabs-scroller MuiTabs-fixed" </h6>
style="overflow: hidden;"
>
<div
aria-label="issue tabs"
class="MuiTabs-flexContainer"
role="tablist"
>
<button
aria-controls="tabpanel-0"
aria-selected="true"
class="MuiButtonBase-root MuiTab-root MuiTab-textColorInherit Mui-selected"
id="tab-0"
role="tab"
tabindex="0"
type="button"
>
<span
class="MuiTab-wrapper"
>
Details
</span>
<span
class="MuiTouchRipple-root"
/>
</button>
</div>
<span
class="PrivateTabIndicator-root-0 PrivateTabIndicator-colorSecondary-0 MuiTabs-indicator"
style="left: 0px; width: 0px;"
/>
</div>
</div>
</div>
<div
aria-labelledby="tab-0"
id="tabpanel-0"
role="tabpanel"
>
<div <div
class="MuiBox-root MuiBox-root-0" data-testid="mocked-issueprovider"
> >
<div <div
class="makeStyles-paper-0 makeStyles-tableSize-0" class="makeStyles-paper-0 makeStyles-tableSize-0"
> >
<h6
class="MuiTypography-root MuiTypography-h6"
>
Issue Details
</h6>
<div <div
data-testid="mocked-issueprovider" class="MuiGrid-root makeStyles-root-0 MuiGrid-container MuiGrid-spacing-xs-2"
> >
<div <div
class="makeStyles-paper-0 makeStyles-tableSize-0" class="MuiGrid-root makeStyles-textCenterAlign-0 MuiGrid-item MuiGrid-grid-md-12"
> >
<div <p>
class="MuiGrid-root makeStyles-root-0 MuiGrid-container MuiGrid-spacing-xs-2" <b>
> ID
<div </b>
class="MuiGrid-root makeStyles-textCenterAlign-0 MuiGrid-item MuiGrid-grid-md-12" :
> FISKER123
<p> </p>
<b> <p>
ID <b>
</b> VIN
: </b>
FISKER123 :
</p> 1GNGC26RXXJ407648
<p> </p>
<b> <p>
VIN <b>
</b> Title
: </b>
1GNGC26RXXJ407648 :
</p> sometitle
<p> </p>
<b> <p>
Title <b>
</b> Description
: </b>
sometitle :
</p> 2343242
<p> </p>
<b> <p>
Description <b>
</b> Created
: </b>
2343242 :
</p> 12/9/2022 11:16:38 PM
<p> </p>
<b> <img
timestamp alt="Issue images"
</b> src="data:image/png;base64, SGVsbG8x"
: />
2022-12-09T23:16:38.074858Z
</p>
<img
alt="Issue images"
src="data:image/png;base64, SGVsbG8x"
/>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
@@ -2472,29 +2421,6 @@ exports[`App Route /issues authenticated 1`] = `
</svg> </svg>
</span> </span>
</th> </th>
<th
class="MuiTableCell-root MuiTableCell-head MuiTableCell-alignCenter"
scope="col"
>
<span
aria-disabled="false"
class="MuiButtonBase-root MuiTableSortLabel-root"
role="button"
tabindex="0"
>
Description
<svg
aria-hidden="true"
class="MuiSvgIcon-root MuiTableSortLabel-icon MuiTableSortLabel-iconDirectionAsc"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M20 12l-1.41-1.41L13 16.17V4h-2v12.17l-5.58-5.59L4 12l8 8 8-8z"
/>
</svg>
</span>
</th>
<th <th
class="MuiTableCell-root MuiTableCell-head MuiTableCell-alignCenter" class="MuiTableCell-root MuiTableCell-head MuiTableCell-alignCenter"
scope="col" scope="col"
@@ -2578,11 +2504,6 @@ exports[`App Route /issues authenticated 1`] = `
> >
sometitle sometitle
</td> </td>
<td
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
>
2343242
</td>
<td <td
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter" class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
> >
@@ -2616,11 +2537,6 @@ exports[`App Route /issues authenticated 1`] = `
> >
sometitle sometitle
</td> </td>
<td
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
>
2343242
</td>
<td <td
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter" class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
> >
@@ -2641,7 +2557,7 @@ exports[`App Route /issues authenticated 1`] = `
> >
<td <td
class="MuiTableCell-root MuiTableCell-footer MuiTablePagination-root" class="MuiTableCell-root MuiTableCell-footer MuiTablePagination-root"
colspan="7" colspan="6"
> >
<div <div
class="MuiToolbar-root MuiToolbar-regular MuiTablePagination-toolbar MuiToolbar-gutters" class="MuiToolbar-root MuiToolbar-regular MuiTablePagination-toolbar MuiToolbar-gutters"
@@ -2702,7 +2618,7 @@ exports[`App Route /issues authenticated 1`] = `
<p <p
class="MuiTypography-root MuiTablePagination-caption MuiTypography-body2 MuiTypography-colorInherit" class="MuiTypography-root MuiTablePagination-caption MuiTypography-body2 MuiTypography-colorInherit"
> >
1-NaN of undefined 0-0 of 0
</p> </p>
<div <div
class="MuiTablePagination-actions" class="MuiTablePagination-actions"
@@ -2732,8 +2648,9 @@ exports[`App Route /issues authenticated 1`] = `
</button> </button>
<button <button
aria-label="Next page" aria-label="Next page"
class="MuiButtonBase-root MuiIconButton-root MuiIconButton-colorInherit" class="MuiButtonBase-root MuiIconButton-root MuiIconButton-colorInherit Mui-disabled Mui-disabled"
tabindex="0" disabled=""
tabindex="-1"
title="Next page" title="Next page"
type="button" type="button"
> >
@@ -2751,9 +2668,6 @@ exports[`App Route /issues authenticated 1`] = `
/> />
</svg> </svg>
</span> </span>
<span
class="MuiTouchRipple-root"
/>
</button> </button>
</div> </div>
</div> </div>
@@ -9739,8 +9653,12 @@ exports[`App Route /vehicle-status authenticated 1`] = `
class="MuiTabs-root" class="MuiTabs-root"
> >
<div <div
class="MuiTabs-scroller MuiTabs-fixed" class="MuiTabs-scrollable"
style="overflow: hidden;" style="width: 99px; height: 99px; position: absolute; top: -9999px; overflow: scroll;"
/>
<div
class="MuiTabs-scroller MuiTabs-scrollable"
style="margin-bottom: 0px;"
> >
<div <div
aria-label="car tabs" aria-label="car tabs"
@@ -9849,7 +9767,7 @@ exports[`App Route /vehicle-status authenticated 1`] = `
<span <span
class="MuiTab-wrapper" class="MuiTab-wrapper"
> >
Remote Commands ECUs
</span> </span>
<span <span
class="MuiTouchRipple-root" class="MuiTouchRipple-root"
@@ -9863,6 +9781,24 @@ exports[`App Route /vehicle-status authenticated 1`] = `
role="tab" role="tab"
tabindex="-1" tabindex="-1"
type="button" type="button"
>
<span
class="MuiTab-wrapper"
>
Remote Commands
</span>
<span
class="MuiTouchRipple-root"
/>
</button>
<button
aria-controls="tabpanel-7"
aria-selected="false"
class="MuiButtonBase-root MuiTab-root MuiTab-textColorInherit"
id="tab-7"
role="tab"
tabindex="-1"
type="button"
> >
<span <span
class="MuiTab-wrapper" class="MuiTab-wrapper"
@@ -10088,6 +10024,12 @@ exports[`App Route /vehicle-status authenticated 1`] = `
id="tabpanel-6" id="tabpanel-6"
role="tabpanel" role="tabpanel"
/> />
<div
aria-labelledby="tab-7"
hidden=""
id="tabpanel-7"
role="tabpanel"
/>
</div> </div>
</main> </main>
</main> </main>

View File

@@ -1,12 +1,12 @@
import { Typography } from "@material-ui/core";
import clsx from "clsx";
import React from "react"; import React from "react";
import { useParams } from "react-router"; 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 { useUserContext } from "../../Contexts/UserContext";
import { VehicleProvider } from "../../Contexts/VehicleContext";
import CarUpdatesTable from "../../Controls/CarUpdatesTable";
import CarVersionLogTable from "../../Controls/CarVersionLogTable";
import useStyles from "../../useStyles"; import useStyles from "../../useStyles";
const MainForm = () => { const MainForm = () => {
@@ -22,10 +22,8 @@ const MainForm = () => {
<div className={clsx(classes.paper, classes.tableSize)}> <div className={clsx(classes.paper, classes.tableSize)}>
<Typography variant="h6">Car Updates</Typography> <Typography variant="h6">Car Updates</Typography>
<CarUpdatesTable vin={vin} token={token} classes={classes} /> <CarUpdatesTable vin={vin} token={token} classes={classes} />
<Typography variant="h6" className={classes.labelInline}> <Typography variant="h6" className={classes.labelInline}>Version Log</Typography>
Car ECUs <CarVersionLogTable vin={vin} token={token} classes={classes} />
</Typography>
<CarECUsTable vin={vin} token={token} classes={classes} />
</div> </div>
); );
}; };

View File

@@ -1,6 +1,7 @@
jest.mock("../../Contexts/CANFiltersContext"); jest.mock("../../Contexts/CANFiltersContext");
jest.mock("../../Contexts/StatusContext"); jest.mock("../../Contexts/StatusContext");
jest.mock("../../Contexts/UserContext"); jest.mock("../../Contexts/UserContext");
jest.mock("../../../services/vehiclesAPI");
jest.mock("@material-ui/core/utils/unstable_useId", () => jest.mock("@material-ui/core/utils/unstable_useId", () =>
jest.fn().mockReturnValue("mui-test-id") 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 { render, waitFor } from "@testing-library/react";
import { BrowserRouter } from "react-router-dom"; 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 { CANFiltersProvider } from "../../Contexts/CANFiltersContext";
import { StatusProvider } from "../../Contexts/StatusContext"; import { StatusProvider } from "../../Contexts/StatusContext";
import { UserProvider, setToken } from "../../Contexts/UserContext"; import { setToken, UserProvider } from "../../Contexts/UserContext";
import { TEST_AUTH_OBJECT_FISKER } from "../../../utils/testing";
import MainForm from "./CarUpdatesTab"; import MainForm from "./CarUpdatesTab";
import addSnapshotSerializer from "../../../utils/snapshot";
const renderCarUpdatesTab = async () => { const renderCarUpdatesTab = async () => {
const { container } = render( const { container } = render(

View File

@@ -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 (
<div className={clsx(classes.paper, classes.tableSize)}>
<Typography variant="h6" className={classes.labelInline}>
Car ECUs
</Typography>
<CarECUsTable vin={vin} token={token} classes={classes} />
</div>
);
};
const CarUpdatesTab = () => (
<VehicleProvider>
<MainForm />
</VehicleProvider>
);
export default CarUpdatesTab;

View File

@@ -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(
<StatusProvider>
<UserProvider>
<BrowserRouter>
<MainForm vin="TESTVIN1234567890" />
</BrowserRouter>
</UserProvider>
</StatusProvider>
);
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();
});
});

View File

@@ -164,16 +164,16 @@ exports[`CarUpdatesTab Render 1`] = `
<tr <tr
class="MuiTableRow-root MuiTableRow-footer" class="MuiTableRow-root MuiTableRow-footer"
> >
<p> <td>
No Car Updates found No Car Updates found
</p> </td>
</tr> </tr>
</tfoot> </tfoot>
</table> </table>
<h6 <h6
class="MuiTypography-root makeStyles-labelInline-0 MuiTypography-h6" class="MuiTypography-root makeStyles-labelInline-0 MuiTypography-h6"
> >
Car ECUs Version Log
</h6> </h6>
<div <div
class="makeStyles-paper-0 makeStyles-tableSize-0" class="makeStyles-paper-0 makeStyles-tableSize-0"
@@ -187,6 +187,52 @@ exports[`CarUpdatesTab Render 1`] = `
<tr <tr
class="MuiTableRow-root MuiTableRow-head" class="MuiTableRow-root MuiTableRow-head"
> >
<th
class="MuiTableCell-root MuiTableCell-head MuiTableCell-alignCenter"
scope="col"
>
<span
aria-disabled="false"
class="MuiButtonBase-root MuiTableSortLabel-root"
role="button"
tabindex="0"
>
Type
<svg
aria-hidden="true"
class="MuiSvgIcon-root MuiTableSortLabel-icon MuiTableSortLabel-iconDirectionAsc"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M20 12l-1.41-1.41L13 16.17V4h-2v12.17l-5.58-5.59L4 12l8 8 8-8z"
/>
</svg>
</span>
</th>
<th
class="MuiTableCell-root MuiTableCell-head MuiTableCell-alignCenter"
scope="col"
>
<span
aria-disabled="false"
class="MuiButtonBase-root MuiTableSortLabel-root"
role="button"
tabindex="0"
>
Version
<svg
aria-hidden="true"
class="MuiSvgIcon-root MuiTableSortLabel-icon MuiTableSortLabel-iconDirectionAsc"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M20 12l-1.41-1.41L13 16.17V4h-2v12.17l-5.58-5.59L4 12l8 8 8-8z"
/>
</svg>
</span>
</th>
<th <th
aria-sort="descending" aria-sort="descending"
class="MuiTableCell-root MuiTableCell-head MuiTableCell-alignCenter" class="MuiTableCell-root MuiTableCell-head MuiTableCell-alignCenter"
@@ -198,7 +244,7 @@ exports[`CarUpdatesTab Render 1`] = `
role="button" role="button"
tabindex="0" tabindex="0"
> >
ECU Date
<span <span
class="makeStyles-hiddenSortSpan-0" class="makeStyles-hiddenSortSpan-0"
> >
@@ -216,121 +262,6 @@ exports[`CarUpdatesTab Render 1`] = `
</svg> </svg>
</span> </span>
</th> </th>
<th
class="MuiTableCell-root MuiTableCell-head MuiTableCell-alignCenter"
scope="col"
>
<span
aria-disabled="false"
class="MuiButtonBase-root MuiTableSortLabel-root"
role="button"
tabindex="0"
>
SW Version
<svg
aria-hidden="true"
class="MuiSvgIcon-root MuiTableSortLabel-icon MuiTableSortLabel-iconDirectionAsc"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M20 12l-1.41-1.41L13 16.17V4h-2v12.17l-5.58-5.59L4 12l8 8 8-8z"
/>
</svg>
</span>
</th>
<th
class="MuiTableCell-root MuiTableCell-head MuiTableCell-alignCenter"
scope="col"
>
<span
aria-disabled="false"
class="MuiButtonBase-root MuiTableSortLabel-root"
role="button"
tabindex="0"
>
HW Version
<svg
aria-hidden="true"
class="MuiSvgIcon-root MuiTableSortLabel-icon MuiTableSortLabel-iconDirectionAsc"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M20 12l-1.41-1.41L13 16.17V4h-2v12.17l-5.58-5.59L4 12l8 8 8-8z"
/>
</svg>
</span>
</th>
<th
class="MuiTableCell-root MuiTableCell-head MuiTableCell-alignCenter"
scope="col"
>
<span
aria-disabled="false"
class="MuiButtonBase-root MuiTableSortLabel-root"
role="button"
tabindex="0"
>
Config
<svg
aria-hidden="true"
class="MuiSvgIcon-root MuiTableSortLabel-icon MuiTableSortLabel-iconDirectionAsc"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M20 12l-1.41-1.41L13 16.17V4h-2v12.17l-5.58-5.59L4 12l8 8 8-8z"
/>
</svg>
</span>
</th>
<th
class="MuiTableCell-root MuiTableCell-head MuiTableCell-alignCenter"
scope="col"
>
<span
aria-disabled="false"
class="MuiButtonBase-root MuiTableSortLabel-root"
role="button"
tabindex="0"
>
Created
<svg
aria-hidden="true"
class="MuiSvgIcon-root MuiTableSortLabel-icon MuiTableSortLabel-iconDirectionAsc"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M20 12l-1.41-1.41L13 16.17V4h-2v12.17l-5.58-5.59L4 12l8 8 8-8z"
/>
</svg>
</span>
</th>
<th
class="MuiTableCell-root MuiTableCell-head MuiTableCell-alignCenter"
scope="col"
>
<span
aria-disabled="false"
class="MuiButtonBase-root MuiTableSortLabel-root"
role="button"
tabindex="0"
>
Updated
<svg
aria-hidden="true"
class="MuiSvgIcon-root MuiTableSortLabel-icon MuiTableSortLabel-iconDirectionAsc"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M20 12l-1.41-1.41L13 16.17V4h-2v12.17l-5.58-5.59L4 12l8 8 8-8z"
/>
</svg>
</span>
</th>
</tr> </tr>
</thead> </thead>
<tbody <tbody
@@ -344,7 +275,7 @@ exports[`CarUpdatesTab Render 1`] = `
> >
<td <td
class="MuiTableCell-root MuiTableCell-footer MuiTablePagination-root" class="MuiTableCell-root MuiTableCell-footer MuiTablePagination-root"
colspan="10" colspan="3"
> >
<div <div
class="MuiToolbar-root MuiToolbar-regular MuiTablePagination-toolbar MuiToolbar-gutters" class="MuiToolbar-root MuiToolbar-regular MuiTablePagination-toolbar MuiToolbar-gutters"

View File

@@ -91,6 +91,41 @@ exports[`DigitalTwinTab Render 1`] = `
closed closed
</p> </p>
</div> </div>
<div
class="makeStyles-popupSection-0"
>
<h3>
Door Locks
</h3>
<p>
<b>
driver
</b>
:
Closed
</p>
<p>
<b>
all
</b>
:
Locked
</p>
</div>
<div
class="makeStyles-popupSection-0"
>
<h3>
Sunroof
</h3>
<p>
<b>
sunroof
</b>
:
closed
</p>
</div>
<div <div
class="makeStyles-popupSection-0" class="makeStyles-popupSection-0"
> >
@@ -152,6 +187,17 @@ exports[`DigitalTwinTab Render 1`] = `
7/26/2022 12:26:38 AM 7/26/2022 12:26:38 AM
</p> </p>
</div> </div>
<div
class="makeStyles-popupSection-0"
>
<p>
<b>
DBC version
</b>
:
d439abd3662dd20099f49dd8f43f7b145202e961caa2b5aba2c6154c8096348b
</p>
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -0,0 +1,309 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ECUsTab Render 1`] = `
<div>
<div
data-testid="mocked-statusprovider"
>
<div
data-testid="mocked-userprovider"
>
<div
class="makeStyles-paper-0 makeStyles-tableSize-0"
>
<h6
class="MuiTypography-root makeStyles-labelInline-0 MuiTypography-h6"
>
Car ECUs
</h6>
<div
class="makeStyles-paper-0 makeStyles-tableSize-0"
>
<table
class="MuiTable-root"
>
<thead
class="MuiTableHead-root"
>
<tr
class="MuiTableRow-root MuiTableRow-head"
>
<th
aria-sort="descending"
class="MuiTableCell-root MuiTableCell-head MuiTableCell-alignCenter"
scope="col"
>
<span
aria-disabled="false"
class="MuiButtonBase-root MuiTableSortLabel-root MuiTableSortLabel-active"
role="button"
tabindex="0"
>
ECU
<span
class="makeStyles-hiddenSortSpan-0"
>
sorted descending
</span>
<svg
aria-hidden="true"
class="MuiSvgIcon-root MuiTableSortLabel-icon MuiTableSortLabel-iconDirectionDesc"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M20 12l-1.41-1.41L13 16.17V4h-2v12.17l-5.58-5.59L4 12l8 8 8-8z"
/>
</svg>
</span>
</th>
<th
class="MuiTableCell-root MuiTableCell-head MuiTableCell-alignCenter"
scope="col"
>
<span
aria-disabled="false"
class="MuiButtonBase-root MuiTableSortLabel-root"
role="button"
tabindex="0"
>
SW Version
<svg
aria-hidden="true"
class="MuiSvgIcon-root MuiTableSortLabel-icon MuiTableSortLabel-iconDirectionAsc"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M20 12l-1.41-1.41L13 16.17V4h-2v12.17l-5.58-5.59L4 12l8 8 8-8z"
/>
</svg>
</span>
</th>
<th
class="MuiTableCell-root MuiTableCell-head MuiTableCell-alignCenter"
scope="col"
>
<span
aria-disabled="false"
class="MuiButtonBase-root MuiTableSortLabel-root"
role="button"
tabindex="0"
>
HW Version
<svg
aria-hidden="true"
class="MuiSvgIcon-root MuiTableSortLabel-icon MuiTableSortLabel-iconDirectionAsc"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M20 12l-1.41-1.41L13 16.17V4h-2v12.17l-5.58-5.59L4 12l8 8 8-8z"
/>
</svg>
</span>
</th>
<th
class="MuiTableCell-root MuiTableCell-head MuiTableCell-alignCenter"
scope="col"
>
<span
aria-disabled="false"
class="MuiButtonBase-root MuiTableSortLabel-root"
role="button"
tabindex="0"
>
Config
<svg
aria-hidden="true"
class="MuiSvgIcon-root MuiTableSortLabel-icon MuiTableSortLabel-iconDirectionAsc"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M20 12l-1.41-1.41L13 16.17V4h-2v12.17l-5.58-5.59L4 12l8 8 8-8z"
/>
</svg>
</span>
</th>
<th
class="MuiTableCell-root MuiTableCell-head MuiTableCell-alignCenter"
scope="col"
>
<span
aria-disabled="false"
class="MuiButtonBase-root MuiTableSortLabel-root"
role="button"
tabindex="0"
>
Created
<svg
aria-hidden="true"
class="MuiSvgIcon-root MuiTableSortLabel-icon MuiTableSortLabel-iconDirectionAsc"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M20 12l-1.41-1.41L13 16.17V4h-2v12.17l-5.58-5.59L4 12l8 8 8-8z"
/>
</svg>
</span>
</th>
<th
class="MuiTableCell-root MuiTableCell-head MuiTableCell-alignCenter"
scope="col"
>
<span
aria-disabled="false"
class="MuiButtonBase-root MuiTableSortLabel-root"
role="button"
tabindex="0"
>
Updated
<svg
aria-hidden="true"
class="MuiSvgIcon-root MuiTableSortLabel-icon MuiTableSortLabel-iconDirectionAsc"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M20 12l-1.41-1.41L13 16.17V4h-2v12.17l-5.58-5.59L4 12l8 8 8-8z"
/>
</svg>
</span>
</th>
</tr>
</thead>
<tbody
class="MuiTableBody-root"
/>
<tfoot
class="MuiTableFooter-root"
>
<tr
class="MuiTableRow-root MuiTableRow-footer"
>
<td
class="MuiTableCell-root MuiTableCell-footer MuiTablePagination-root"
colspan="10"
>
<div
class="MuiToolbar-root MuiToolbar-regular MuiTablePagination-toolbar MuiToolbar-gutters"
>
<div
class="MuiTablePagination-spacer"
/>
<p
class="MuiTypography-root MuiTablePagination-caption MuiTypography-body2 MuiTypography-colorInherit"
>
Rows per page:
</p>
<div
class="MuiInputBase-root MuiTablePagination-input MuiTablePagination-selectRoot"
>
<select
aria-label="rows per page"
class="MuiSelect-root MuiSelect-select MuiTablePagination-select MuiInputBase-input"
>
<option
class="MuiTablePagination-menuItem"
value="5"
>
5
</option>
<option
class="MuiTablePagination-menuItem"
value="10"
>
10
</option>
<option
class="MuiTablePagination-menuItem"
value="25"
>
25
</option>
<option
class="MuiTablePagination-menuItem"
value="100"
>
100
</option>
</select>
<svg
aria-hidden="true"
class="MuiSvgIcon-root MuiSelect-icon MuiTablePagination-selectIcon"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M7 10l5 5 5-5z"
/>
</svg>
</div>
<p
class="MuiTypography-root MuiTablePagination-caption MuiTypography-body2 MuiTypography-colorInherit"
>
0-0 of 0
</p>
<div
class="MuiTablePagination-actions"
>
<button
aria-label="Previous page"
class="MuiButtonBase-root MuiIconButton-root MuiIconButton-colorInherit Mui-disabled Mui-disabled"
disabled=""
tabindex="-1"
title="Previous page"
type="button"
>
<span
class="MuiIconButton-label"
>
<svg
aria-hidden="true"
class="MuiSvgIcon-root"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M15.41 16.09l-4.58-4.59 4.58-4.59L14 5.5l-6 6 6 6z"
/>
</svg>
</span>
</button>
<button
aria-label="Next page"
class="MuiButtonBase-root MuiIconButton-root MuiIconButton-colorInherit Mui-disabled Mui-disabled"
disabled=""
tabindex="-1"
title="Next page"
type="button"
>
<span
class="MuiIconButton-label"
>
<svg
aria-hidden="true"
class="MuiSvgIcon-root"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M8.59 16.34l4.58-4.59-4.58-4.59L10 5.75l6 6-6 6z"
/>
</svg>
</span>
</button>
</div>
</div>
</td>
</tr>
</tfoot>
</table>
</div>
</div>
</div>
</div>
</div>
`;

View File

@@ -21,8 +21,12 @@ exports[`CarStatus Render 1`] = `
class="MuiTabs-root" class="MuiTabs-root"
> >
<div <div
class="MuiTabs-scroller MuiTabs-fixed" class="MuiTabs-scrollable"
style="overflow: hidden;" style="width: 99px; height: 99px; position: absolute; top: -9999px; overflow: scroll;"
/>
<div
class="MuiTabs-scroller MuiTabs-scrollable"
style="margin-bottom: 0px;"
> >
<div <div
aria-label="car tabs" aria-label="car tabs"
@@ -131,7 +135,7 @@ exports[`CarStatus Render 1`] = `
<span <span
class="MuiTab-wrapper" class="MuiTab-wrapper"
> >
Remote Commands ECUs
</span> </span>
<span <span
class="MuiTouchRipple-root" class="MuiTouchRipple-root"
@@ -145,6 +149,24 @@ exports[`CarStatus Render 1`] = `
role="tab" role="tab"
tabindex="-1" tabindex="-1"
type="button" type="button"
>
<span
class="MuiTab-wrapper"
>
Remote Commands
</span>
<span
class="MuiTouchRipple-root"
/>
</button>
<button
aria-controls="tabpanel-7"
aria-selected="false"
class="MuiButtonBase-root MuiTab-root MuiTab-textColorInherit"
id="tab-7"
role="tab"
tabindex="-1"
type="button"
> >
<span <span
class="MuiTab-wrapper" class="MuiTab-wrapper"
@@ -314,6 +336,12 @@ exports[`CarStatus Render 1`] = `
id="tabpanel-6" id="tabpanel-6"
role="tabpanel" role="tabpanel"
/> />
<div
aria-labelledby="tab-7"
hidden=""
id="tabpanel-7"
role="tabpanel"
/>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -14,6 +14,7 @@ import CANSignalsTab from "./CANSignalsTab";
import CarUpdatesTab from "./CarUpdatesTab"; import CarUpdatesTab from "./CarUpdatesTab";
import CarDetailsTab from "./DetailsTab"; import CarDetailsTab from "./DetailsTab";
import DigitalTwinTab from "./DigitalTwinTab"; import DigitalTwinTab from "./DigitalTwinTab";
import ECUsTab from "./ECUsTab";
import FleetsTab from "./FleetsTab"; import FleetsTab from "./FleetsTab";
import RemoteCommandsTab from "./RemoteCommandsTab"; import RemoteCommandsTab from "./RemoteCommandsTab";
@@ -41,6 +42,10 @@ const TabViews = [
label: "CAN Signals", label: "CAN Signals",
component: CANSignalsTab, component: CANSignalsTab,
}, },
{
label: "ECUs",
component: ECUsTab,
},
{ {
label: "Remote Commands", label: "Remote Commands",
component: RemoteCommandsTab, component: RemoteCommandsTab,
@@ -110,6 +115,7 @@ const CarStatus = () => {
value={tabIndex} value={tabIndex}
onChange={handleTabChange} onChange={handleTabChange}
aria-label="car tabs" aria-label="car tabs"
variant="scrollable"
indicatorColor="secondary"> indicatorColor="secondary">
{tabs.map((item, index) => <Tab key={index} label={item.label} {...tabProps(index)} />)} {tabs.map((item, index) => <Tab key={index} label={item.label} {...tabProps(index)} />)}
</Tabs> </Tabs>

View File

@@ -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 ( return (
<VehicleContext.Provider <VehicleContext.Provider
value={{ value={{
@@ -258,6 +269,7 @@ export const VehicleProvider = ({ children }) => {
sendCommand, sendCommand,
updateVehicle, updateVehicle,
getFleets, getFleets,
getVersionLog,
}} }}
> >
{children} {children}

View File

@@ -70,6 +70,7 @@ let vehicleState = {
temperature: 26, temperature: 26,
}, },
trex_version: "1000000", trex_version: "1000000",
dbc_version: "d439abd3662dd20099f49dd8f43f7b145202e961caa2b5aba2c6154c8096348b",
ip: "172.20.0.17:49850", ip: "172.20.0.17:49850",
updated: "2022-07-26T00:26:38.880381Z", updated: "2022-07-26T00:26:38.880381Z",
}, },

View File

@@ -198,7 +198,7 @@ const MainForm = ({ vin, token }) => {
<TableFooter> <TableFooter>
<TableRow> <TableRow>
{totalCarUpdates === 0 ? ( {totalCarUpdates === 0 ? (
<p>No Car Updates found</p> <td>No Car Updates found</td>
) : ( ) : (
<TablePagination <TablePagination
rowsPerPageOptions={[5, 10, 25, 100]} rowsPerPageOptions={[5, 10, 25, 100]}

View File

@@ -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 (
<div className={clsx(classes.paper, classes.tableSize)}>
<Table>
<TableHeaderSortable
classes={classes}
orderBy={orderBy}
order={order}
columnData={tableColumns}
onSortRequest={handleSort}
/>
<TableBody>
{versions && versions.map((row, i) => (
<TableRow key={`row${i}`}>
<TableCell align="center">{row.version_source}</TableCell>
<TableCell align="center">{row.version}</TableCell>
<TableCell align="center">{LocalDateTimeString(row.created_at)}</TableCell>
</TableRow>
))}
</TableBody>
<TableFooter>
<TableRow>
<TablePagination
rowsPerPageOptions={[5, 10, 25, 100]}
colSpan={3}
count={total}
rowsPerPage={pageSize}
page={pageIndex}
SelectProps={{
inputProps: { "aria-label": "rows per page" },
native: true,
}}
onPageChange={handleChangePageIndex}
onRowsPerPageChange={handleChangePageSize}
/>
</TableRow>
</TableFooter>
</Table>
</div>
);
};
export default CarVersionLogTable;

View File

@@ -1,26 +1,26 @@
import React, { useEffect, useState } from "react";
import { Link } from "react-router-dom";
import PropTypes from "prop-types";
import { import {
Table, Table,
TableBody, TableBody,
TableCell, TableCell,
TableFooter, TableFooter,
TablePagination, TablePagination,
TableRow, TableRow
Button,
} from "@material-ui/core"; } from "@material-ui/core";
import DeleteIcon from "@material-ui/icons/Delete";
import clsx from "clsx"; 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 { useIssueContext } from "../../Contexts/IssueContext";
import { useStatusContext } from "../../Contexts/StatusContext"; import { useStatusContext } from "../../Contexts/StatusContext";
import { LocalDateTimeString } from "../../../utils/dates"; import { useUserContext } from "../../Contexts/UserContext";
import TableHeaderSortable from "../../Table/HeaderSortable"; import TableHeaderSortable from "../../Table/HeaderSortable";
import { logger } from "../../../services/monitoring";
import { useLocalStorage } from "../../useLocalStorage"; import { useLocalStorage } from "../../useLocalStorage";
import { RoleWrap } from "../RoleWrap"; import { RoleWrap } from "../RoleWrap";
import { useUserContext } from "../../Contexts/UserContext";
import { Permissions } from "../../../utils/roles";
const tableColumns = [ const tableColumns = [
{ {
@@ -35,10 +35,6 @@ const tableColumns = [
id: "title", id: "title",
label: "Title", label: "Title",
}, },
{
id: "description",
label: "Description",
},
{ {
id: "driver_id", id: "driver_id",
label: "Driver ID", label: "Driver ID",
@@ -69,7 +65,7 @@ const IssueSelectionTable = (props) => {
const [pageIndex, setPageIndex] = useState(0); const [pageIndex, setPageIndex] = useState(0);
const [orderBy, setOrderBy] = useState("created_at"); const [orderBy, setOrderBy] = useState("created_at");
const [order, setOrder] = useState("asc"); const [order, setOrder] = useState("asc");
const { getIssues, issues, totalIssues } = useIssueContext(); const { deleteIssue, getIssues, issues, totalIssues = 0 } = useIssueContext();
const { groups, providers } = useUserContext(); const { groups, providers } = useUserContext();
const { setMessage } = useStatusContext(); const { setMessage } = useStatusContext();
@@ -132,12 +128,19 @@ const IssueSelectionTable = (props) => {
setPageIndex(0); setPageIndex(0);
}, [search]); }, [search]);
const { deleteIssue } = useIssueContext();
const handleDelete = (id) => { const handleDelete = (id) => {
deleteIssue(id, token).then(() => { deleteIssue(id, token).then(() => {
getIssues(token) getIssues(
{
limit: pageSize,
offset: pageSize * pageIndex,
order: `${orderBy} ${order}`,
},
token
);
}); });
}; };
return ( return (
<div className={clsx(classes.paper, classes.tableSize)}> <div className={clsx(classes.paper, classes.tableSize)}>
<Table> <Table>
@@ -161,7 +164,6 @@ const IssueSelectionTable = (props) => {
</TableCell> </TableCell>
<TableCell align="center">{row.vin}</TableCell> <TableCell align="center">{row.vin}</TableCell>
<TableCell align="center">{row.title}</TableCell> <TableCell align="center">{row.title}</TableCell>
<TableCell align="center">{row.description || ""}</TableCell>
<TableCell align="center">{row.driver_id}</TableCell> <TableCell align="center">{row.driver_id}</TableCell>
<TableCell align="center"> <TableCell align="center">
{LocalDateTimeString(row.timestamp)} {LocalDateTimeString(row.timestamp)}
@@ -172,7 +174,9 @@ const IssueSelectionTable = (props) => {
rolesPerProvider={Permissions.FiskerDelete} rolesPerProvider={Permissions.FiskerDelete}
> >
<TableCell> <TableCell>
<Button onClick={() => handleDelete(row.id)}>Delete</Button> <Link to="#" onClick={() => handleDelete(row.id)}>
<DeleteIcon />
</Link>
</TableCell> </TableCell>
</RoleWrap> </RoleWrap>
</TableRow> </TableRow>
@@ -181,12 +185,9 @@ const IssueSelectionTable = (props) => {
</TableBody> </TableBody>
<TableFooter> <TableFooter>
<TableRow> <TableRow>
{totalIssues === 0 ? (
<p>No issues found</p>
) : (
<TablePagination <TablePagination
rowsPerPageOptions={[5, 10, 25, 100]} rowsPerPageOptions={[5, 10, 25, 100]}
colSpan={7} colSpan={6}
count={totalIssues} count={totalIssues}
rowsPerPage={pageSize} rowsPerPage={pageSize}
page={pageIndex} page={pageIndex}
@@ -197,7 +198,6 @@ const IssueSelectionTable = (props) => {
onPageChange={handleChangePageIndex} onPageChange={handleChangePageIndex}
onRowsPerPageChange={handleChangePageSize} onRowsPerPageChange={handleChangePageSize}
/> />
)}
</TableRow> </TableRow>
</TableFooter> </TableFooter>
</Table> </Table>

View File

@@ -1,7 +1,7 @@
import React from "react"; import React from "react";
import useStyles from "../useStyles";
import { LocalDateTimeString } from "../../utils/dates"; import { LocalDateTimeString } from "../../utils/dates";
import useStyles from "../useStyles";
const keyValueTemplate = (key, value) => ( const keyValueTemplate = (key, value) => (
<p key={key}> <p key={key}>
@@ -14,7 +14,7 @@ const mapOpenCloseState = (value) =>
const DigitalTwin = (props) => { const DigitalTwin = (props) => {
const classes = useStyles(); 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 ( return (
<div> <div>
@@ -28,10 +28,55 @@ const DigitalTwin = (props) => {
{Object.entries(doors).map(mapOpenCloseState)} {Object.entries(doors).map(mapOpenCloseState)}
</div> </div>
)} )}
{door_locks != null && (
<div className={classes.popupSection}>
<h3>Door Locks</h3>
{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");
}
})}
</div>
)}
{windows != null && ( {windows != null && (
<div className={classes.popupSection}> <div className={classes.popupSection}>
<h3>Windows</h3> <h3>Windows</h3>
{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`);
}
})}
</div>
)}
{misc_windows != null && (
<div className={classes.popupSection}>
<h3>Misc Windows</h3>
{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`);
}
})}
</div>
)}
{sunroof != null && (
<div className={classes.popupSection}>
<h3>Sunroof</h3>
{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`);
}
})}
</div> </div>
)} )}
{location != null && ( {location != null && (
@@ -61,6 +106,11 @@ const DigitalTwin = (props) => {
{keyValueTemplate("Updated at", LocalDateTimeString(updated))} {keyValueTemplate("Updated at", LocalDateTimeString(updated))}
</div> </div>
)} )}
{dbc_version != null && (
<div className={classes.popupSection}>
{keyValueTemplate("DBC version", dbc_version)}
</div>
)}
</div> </div>
); );
}; };

View File

@@ -3,15 +3,14 @@ import clsx from "clsx";
import React, { useEffect } from "react"; import React, { useEffect } from "react";
import { logger } from "../../../../services/monitoring"; import { logger } from "../../../../services/monitoring";
import { LocalDateTimeString } from "../../../../utils/dates";
import {
IssueProvider, useIssueContext
} from "../../../Contexts/IssueContext";
import { useStatusContext } from "../../../Contexts/StatusContext"; import { useStatusContext } from "../../../Contexts/StatusContext";
import { useUserContext } from "../../../Contexts/UserContext"; import { useUserContext } from "../../../Contexts/UserContext";
import {
useIssueContext,
IssueProvider
} from "../../../Contexts/IssueContext";
import useStyles from "../../../useStyles"; import useStyles from "../../../useStyles";
const MainForm = ({ id }) => { const MainForm = ({ id }) => {
const classes = useStyles(); const classes = useStyles();
const { setMessage } = useStatusContext(); const { setMessage } = useStatusContext();
@@ -55,7 +54,7 @@ const MainForm = ({ id }) => {
<b>Description</b>: {issue.description} <b>Description</b>: {issue.description}
</p> </p>
<p> <p>
<b>timestamp</b>: {issue.timestamp} <b>Created</b>: {LocalDateTimeString(issue.timestamp)}
</p> </p>
{issue.images && issue.images.map((image, index) => ( {issue.images && issue.images.map((image, index) => (
<img key={image.id} src={`data:image/png;base64, ${image.image}`} alt="Issue images" /> <img key={image.id} src={`data:image/png;base64, ${image.image}`} alt="Issue images" />

View File

@@ -1,55 +1,15 @@
import { Box, Tab, Tabs } from "@material-ui/core";
import clsx from "clsx"; import clsx from "clsx";
import React, { useEffect, useState } from "react"; import React, { useEffect } from "react";
import { useParams } from "react-router"; import { useParams } from "react-router";
import { useLocation } from "react-router-dom";
import { hasRole } from "../../../utils/roles";
import { useStatusContext } from "../../Contexts/StatusContext"; import { useStatusContext } from "../../Contexts/StatusContext";
import { useUserContext } from "../../Contexts/UserContext";
import TabPanel from "../../Controls/TabPanel";
import useStyles from "../../useStyles"; import useStyles from "../../useStyles";
import IssueDetailsTab from "./DetailsTab"; 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 IssueInfo = () => {
const { id } = useParams(); const { id } = useParams();
const classes = useStyles(); const classes = useStyles();
const { setTitle, setSitePath } = useStatusContext(); 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(() => { useEffect(() => {
const title = `Issue ${id} Details`; const title = `Issue ${id} Details`;
@@ -66,39 +26,12 @@ const IssueInfo = () => {
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [id]); }, [id]);
const handleTabChange = (_event, newIndex) => {
setTabIndex(newIndex);
};
return ( return (
<div className={clsx(classes.paper, classes.tableSize)}> <div className={clsx(classes.paper, classes.tableSize)}>
<Box <IssueDetailsTab id={id} />
className={classes.tableToolbar}
sx={{ borderBottom: 1, borderColor: "divider" }}
>
<Tabs
value={tabIndex}
onChange={handleTabChange}
aria-label="issue tabs"
indicatorColor="secondary">
{tabs.map((item, index) => <Tab key={index} label={item.label} {...tabProps(index)} />)}
</Tabs>
</Box>
{tabs.map((item, index) => (
<TabPanel key={index} value={tabIndex} index={index}>
<item.component id={id} />
</TabPanel>
))}
</div> </div>
); );
}; };
function tabProps(index) {
return {
id: `tab-${index}`,
"aria-controls": `tabpanel-${index}`,
};
}
export default IssueInfo; export default IssueInfo;

View File

@@ -185,16 +185,7 @@ const Component = () => {
{carState ? ( {carState ? (
<VehiclePopUp <VehiclePopUp
key={carState.vin} key={carState.vin}
vin={carState.vin} {...carState}
online={carState.online}
onlineHMI={carState.online_hmi}
battery={carState.battery}
doors={carState.doors}
location={carState.location}
windows={carState.windows}
trex_version={carState.trex_version}
ip={carState.ip}
updated={carState.updated}
className={classes.popup} className={classes.popup}
onClose={handleClose} onClose={handleClose}
/> />

View File

@@ -116,7 +116,26 @@ const vehiclesAPI = {
}, },
getCANSignals: async (vin, vehicle) => { getCANSignals: async (vin, vehicle) => {
return signals; 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; export default vehiclesAPI;

View File

@@ -1,5 +1,5 @@
import { import {
addQueryParams, errorHandler, fetchRespHandler, getAuthHeaderOptions addQueryParams, errorHandler, fetchRespHandler, getAuthHeaderOptions
} from "../utils/http"; } from "../utils/http";
const API_ENDPOINT = process.env.REACT_APP_OTA_SERVICE_URL; const API_ENDPOINT = process.env.REACT_APP_OTA_SERVICE_URL;
@@ -172,6 +172,19 @@ const vehiclesAPI = {
}) })
.then(fetchRespHandler) .then(fetchRespHandler)
.catch(errorHandler), .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; export default vehiclesAPI;