Merge branch 'release/0.0.3'
This commit is contained in:
20
package-lock.json
generated
20
package-lock.json
generated
@@ -33,6 +33,7 @@
|
||||
"react-router-dom": "^5.3.0",
|
||||
"react-router-hash-link": "^2.4.3",
|
||||
"react-scripts": "5.0.0",
|
||||
"usehooks-ts": "^2.7.1",
|
||||
"web-vitals": "^2.1.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -16432,6 +16433,19 @@
|
||||
"punycode": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/usehooks-ts": {
|
||||
"version": "2.7.2",
|
||||
"resolved": "https://registry.npmjs.org/usehooks-ts/-/usehooks-ts-2.7.2.tgz",
|
||||
"integrity": "sha512-DeLqSnGg9VvpwPZA+6lKVURJKM9EBu7bbIXuYclQ9COO3w4lacnJa0uP0iJbC/lAmY7GlmPinjZfGNNmDTlUpg==",
|
||||
"engines": {
|
||||
"node": ">=16.15.0",
|
||||
"npm": ">=8"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0",
|
||||
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/util-deprecate": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
@@ -29270,6 +29284,12 @@
|
||||
"punycode": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"usehooks-ts": {
|
||||
"version": "2.7.2",
|
||||
"resolved": "https://registry.npmjs.org/usehooks-ts/-/usehooks-ts-2.7.2.tgz",
|
||||
"integrity": "sha512-DeLqSnGg9VvpwPZA+6lKVURJKM9EBu7bbIXuYclQ9COO3w4lacnJa0uP0iJbC/lAmY7GlmPinjZfGNNmDTlUpg==",
|
||||
"requires": {}
|
||||
},
|
||||
"util-deprecate": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
"react-router-dom": "^5.3.0",
|
||||
"react-router-hash-link": "^2.4.3",
|
||||
"react-scripts": "5.0.0",
|
||||
"usehooks-ts": "^2.7.1",
|
||||
"web-vitals": "^2.1.4"
|
||||
},
|
||||
"scripts": {
|
||||
@@ -67,9 +68,9 @@
|
||||
"coverageThreshold": {
|
||||
"global": {
|
||||
"branches": 39,
|
||||
"functions": 45.5,
|
||||
"lines": 50,
|
||||
"statements": 49
|
||||
"functions": 47,
|
||||
"lines": 55,
|
||||
"statements": 55
|
||||
}
|
||||
},
|
||||
"coverageReporters": [
|
||||
|
||||
@@ -3311,7 +3311,7 @@ exports[`App Route /packages authenticated 1`] = `
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
<div>
|
||||
<span>
|
||||
<a
|
||||
class=""
|
||||
href="/packages"
|
||||
@@ -3329,8 +3329,7 @@ exports[`App Route /packages authenticated 1`] = `
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
<div />
|
||||
</div>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
@@ -3460,6 +3459,7 @@ exports[`App Route /packages authenticated 1`] = `
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
<div />
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
@@ -6015,7 +6015,7 @@ exports[`App Route /vehicle-add authenticated 1`] = `
|
||||
class="PrivateSwitchBase-input-0"
|
||||
name="log-level-group"
|
||||
type="radio"
|
||||
value="warn"
|
||||
value="warning"
|
||||
/>
|
||||
<div
|
||||
class="PrivateRadioButtonIcon-root-0"
|
||||
@@ -6049,7 +6049,7 @@ exports[`App Route /vehicle-add authenticated 1`] = `
|
||||
<span
|
||||
class="MuiTypography-root MuiFormControlLabel-label MuiTypography-body1"
|
||||
>
|
||||
Warn
|
||||
Warning
|
||||
</span>
|
||||
</label>
|
||||
<label
|
||||
@@ -6918,6 +6918,24 @@ exports[`App Route /vehicle-status authenticated 1`] = `
|
||||
class="MuiTouchRipple-root"
|
||||
/>
|
||||
</button>
|
||||
<button
|
||||
aria-controls="tabpanel-6"
|
||||
aria-selected="false"
|
||||
class="MuiButtonBase-root MuiTab-root MuiTab-textColorInherit"
|
||||
id="tab-6"
|
||||
role="tab"
|
||||
tabindex="-1"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="MuiTab-wrapper"
|
||||
>
|
||||
Fleets
|
||||
</span>
|
||||
<span
|
||||
class="MuiTouchRipple-root"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
<span
|
||||
class="PrivateTabIndicator-root-0 PrivateTabIndicator-colorSecondary-0 MuiTabs-indicator"
|
||||
@@ -7089,6 +7107,13 @@ exports[`App Route /vehicle-status authenticated 1`] = `
|
||||
id="tabpanel-5"
|
||||
role="tabpanel"
|
||||
/>
|
||||
<div
|
||||
aria-labelledby="tab-6"
|
||||
class="makeStyles-fullWidth-0"
|
||||
hidden=""
|
||||
id="tabpanel-6"
|
||||
role="tabpanel"
|
||||
/>
|
||||
</div>
|
||||
</main>
|
||||
</main>
|
||||
|
||||
@@ -53,6 +53,7 @@ const PAGE_SIZE = "CAN_FILTER_TABLE_PAGE_SIZE";
|
||||
|
||||
const MainForm = ({ vin }) => {
|
||||
const classes = useStyles();
|
||||
const [search, onSearch] = useState("");
|
||||
const [pageSize, setPageSize] = useLocalStorage(PAGE_SIZE, 10);
|
||||
const [pageIndex, setPageIndex] = useState(0);
|
||||
const [orderBy, setOrderBy] = useState("id");
|
||||
@@ -68,6 +69,7 @@ const MainForm = ({ vin }) => {
|
||||
await getFilters(
|
||||
vin,
|
||||
{
|
||||
search,
|
||||
limit: pageSize,
|
||||
offset: pageSize * pageIndex,
|
||||
order: `${orderBy} ${order}`,
|
||||
@@ -80,7 +82,7 @@ const MainForm = ({ vin }) => {
|
||||
}
|
||||
})();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [vin, token, pageIndex, pageSize, orderBy, order]);
|
||||
}, [vin, token, pageIndex, pageSize, orderBy, order, search]);
|
||||
|
||||
const handleChangePageIndex = (event, newIndex) => {
|
||||
setPageIndex(newIndex);
|
||||
@@ -166,7 +168,7 @@ const MainForm = ({ vin }) => {
|
||||
</Link>
|
||||
</Grid>
|
||||
<Grid item md={8} className={classes.textCenterAlign}>
|
||||
<SearchField classes={classes} />
|
||||
<SearchField classes={classes} onSearch={onSearch}/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Table>
|
||||
|
||||
@@ -388,7 +388,7 @@ exports[`VehicleAddForm Render 1`] = `
|
||||
class="PrivateSwitchBase-input-0"
|
||||
name="log-level-group"
|
||||
type="radio"
|
||||
value="warn"
|
||||
value="warning"
|
||||
/>
|
||||
<div
|
||||
class="PrivateRadioButtonIcon-root-0"
|
||||
@@ -422,7 +422,7 @@ exports[`VehicleAddForm Render 1`] = `
|
||||
<span
|
||||
class="MuiTypography-root MuiFormControlLabel-label MuiTypography-body1"
|
||||
>
|
||||
Warn
|
||||
Warning
|
||||
</span>
|
||||
</label>
|
||||
<label
|
||||
|
||||
@@ -180,7 +180,7 @@ const MainForm = () => {
|
||||
<FormControlLabel value="trace" control={<Radio />} label="Trace" />
|
||||
<FormControlLabel value="debug" control={<Radio />} label="Debug" />
|
||||
<FormControlLabel value="info" control={<Radio />} label="Info" />
|
||||
<FormControlLabel value="warn" control={<Radio />} label="Warn" />
|
||||
<FormControlLabel value="warning" control={<Radio />} label="Warning" />
|
||||
<FormControlLabel value="error" control={<Radio />} label="Error" />
|
||||
<FormControlLabel value="critical" control={<Radio />} label="Critical" />
|
||||
</RadioGroup>
|
||||
|
||||
@@ -45,7 +45,7 @@ const Main = ({ vin }) => {
|
||||
};
|
||||
|
||||
const CANSignals = (props) => (
|
||||
<CANSignalProvider>
|
||||
<CANSignalProvider {...{token:props.token}}>
|
||||
<Main {...props} />
|
||||
</CANSignalProvider>
|
||||
);
|
||||
|
||||
39
src/components/Cars/Status/CANSignalsTab.test.jsx
Normal file
39
src/components/Cars/Status/CANSignalsTab.test.jsx
Normal file
@@ -0,0 +1,39 @@
|
||||
import addSnapshotSerializer from "../../../utils/snapshot";
|
||||
|
||||
jest.mock("../../Contexts/VehicleContext");
|
||||
jest.mock("../../Contexts/StatusContext");
|
||||
jest.mock("../../Contexts/UserContext");
|
||||
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 CANSignalsTab from "./CANSignalsTab";
|
||||
import {setToken} from "../../Contexts/UserContext";
|
||||
import {TEST_AUTH_OBJECT} from "../../../utils/testing";
|
||||
|
||||
const renderCANSignalsTab = async () => {
|
||||
const { container } = render(
|
||||
<BrowserRouter>
|
||||
<CANSignalsTab vin="TESTVIN1234567890" token="token"/>
|
||||
</BrowserRouter>
|
||||
);
|
||||
await waitFor(() => {
|
||||
/* render */
|
||||
});
|
||||
return container;
|
||||
};
|
||||
|
||||
describe("CANSignalsTab", () => {
|
||||
beforeAll(() => {
|
||||
addSnapshotSerializer(expect);
|
||||
});
|
||||
|
||||
it("Render", async () => {
|
||||
setToken(TEST_AUTH_OBJECT);
|
||||
const container = await renderCANSignalsTab();
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -48,6 +48,9 @@ const Main = (props) => {
|
||||
<div>
|
||||
<b>Connected</b>: {carState.online.toString()}
|
||||
</div>
|
||||
<div>
|
||||
<b>ICC Connected</b>: {carState?.online_hmi.toString()}
|
||||
</div>
|
||||
<DigitalTwin {...carState} />
|
||||
</>
|
||||
)}
|
||||
|
||||
140
src/components/Cars/Status/FleetsTab.jsx
Normal file
140
src/components/Cars/Status/FleetsTab.jsx
Normal file
@@ -0,0 +1,140 @@
|
||||
import useStyles from "../../useStyles";
|
||||
import clsx from "clsx";
|
||||
import {Grid, Table, TableBody, TableCell, TableFooter, TablePagination, TableRow, Typography} from "@material-ui/core";
|
||||
import React, {useEffect, useState} from "react";
|
||||
import {useVehicleContext, VehicleProvider} from "../../Contexts/VehicleContext";
|
||||
import SearchField from "../../Controls/SearchField";
|
||||
import {useLocalStorage} from "../../useLocalStorage";
|
||||
import {useStatusContext} from "../../Contexts/StatusContext";
|
||||
import {useUserContext} from "../../Contexts/UserContext";
|
||||
import {logger} from "../../../services/monitoring";
|
||||
import TableHeaderSortable from "../../Table/HeaderSortable";
|
||||
import {Link} from "react-router-dom";
|
||||
|
||||
const PAGE_SIZE = "VEHICLE_FLEETS_TABLE_PAGE_SIZE";
|
||||
|
||||
const tableColumns = [
|
||||
{
|
||||
id: "fleet",
|
||||
label: "CAN ID"
|
||||
},
|
||||
];
|
||||
|
||||
const MainForm = (props) => {
|
||||
const classes = useStyles();
|
||||
const {vin} = props;
|
||||
|
||||
const [search, onSearch] = useState("");
|
||||
const [pageSize, setPageSize] = useLocalStorage(PAGE_SIZE, 10);
|
||||
const [pageIndex, setPageIndex] = useState(0);
|
||||
const [orderBy, setOrderBy] = useState("id");
|
||||
const [order, setOrder] = useState("desc");
|
||||
const {setMessage} = useStatusContext();
|
||||
const {token: {idToken: {jwtToken: token}}} = useUserContext();
|
||||
const {getFleets, fleets, totalFleets} = useVehicleContext();
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
try {
|
||||
if (!vin || !token) return;
|
||||
await getFleets(
|
||||
vin,
|
||||
{
|
||||
search,
|
||||
limit: pageSize,
|
||||
offset: pageSize * pageIndex,
|
||||
order: `${orderBy} ${order}`,
|
||||
},
|
||||
token
|
||||
);
|
||||
} catch (e) {
|
||||
setMessage(e.message);
|
||||
logger.warn(e.stack);
|
||||
}
|
||||
})();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [vin, token, pageIndex, pageSize, orderBy, order, search]);
|
||||
|
||||
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)}>
|
||||
<Typography variant="h6">Fleets</Typography>
|
||||
<Grid container className={classes.textCenterAlign} spacing={2} justifyContent="center">
|
||||
<Grid item md={3} className={classes.textCenterAlign}>
|
||||
<SearchField classes={classes} onSearch={onSearch}/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Table style={{width:"auto"}}>
|
||||
<TableHeaderSortable
|
||||
classes={classes}
|
||||
orderBy={orderBy}
|
||||
order={order}
|
||||
columnData={tableColumns}
|
||||
onSortRequest={handleSort}
|
||||
/>
|
||||
<TableBody>
|
||||
{fleets.map((row) => (
|
||||
<TableRow key={row} width="200px">
|
||||
<TableCell align="center">
|
||||
<Link to={`/fleet/${row}`}>{row}</Link>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
<TableFooter>
|
||||
<TableRow>
|
||||
<TablePagination
|
||||
rowsPerPageOptions={[5, 10, 25, 100]}
|
||||
colSpan={8}
|
||||
count={totalFleets}
|
||||
rowsPerPage={pageSize}
|
||||
page={pageIndex}
|
||||
SelectProps={{
|
||||
inputProps: {"aria-label": "rows per page"},
|
||||
native: true,
|
||||
}}
|
||||
onPageChange={handleChangePageIndex}
|
||||
onRowsPerPageChange={handleChangePageSize}
|
||||
/>
|
||||
</TableRow>
|
||||
</TableFooter>
|
||||
</Table>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const FleetsTab = (props) => {
|
||||
return (
|
||||
<VehicleProvider>
|
||||
<MainForm {...props}/>
|
||||
</VehicleProvider>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
export default FleetsTab;
|
||||
47
src/components/Cars/Status/FleetsTab.test.jsx
Normal file
47
src/components/Cars/Status/FleetsTab.test.jsx
Normal file
@@ -0,0 +1,47 @@
|
||||
jest.mock("../../Contexts/VehicleContext");
|
||||
jest.mock("../../Contexts/StatusContext");
|
||||
jest.mock("../../Contexts/UserContext");
|
||||
jest.mock("@material-ui/core/utils/unstable_useId", () =>
|
||||
jest.fn().mockReturnValue("mui-test-id")
|
||||
);
|
||||
|
||||
import React from "react";
|
||||
import {render, waitFor} from "@testing-library/react";
|
||||
import { BrowserRouter } from "react-router-dom";
|
||||
|
||||
import {StatusProvider} from "../../Contexts/StatusContext";
|
||||
import {VehicleProvider} from "../../Contexts/VehicleContext";
|
||||
import {setToken, UserProvider} from "../../Contexts/UserContext";
|
||||
import {TEST_AUTH_OBJECT} from "../../../utils/testing";
|
||||
import addSnapshotSerializer from "../../../utils/snapshot";
|
||||
import FleetsTab from "./FleetsTab";
|
||||
|
||||
const renderFleetsTab = async () => {
|
||||
const {container} = render(
|
||||
<StatusProvider>
|
||||
<UserProvider>
|
||||
<VehicleProvider>
|
||||
<BrowserRouter>
|
||||
<FleetsTab vin="TESTVIN1234567890"/>
|
||||
</BrowserRouter>
|
||||
</VehicleProvider>
|
||||
</UserProvider>
|
||||
</StatusProvider>
|
||||
);
|
||||
await waitFor(() => {
|
||||
/* render */
|
||||
});
|
||||
return container;
|
||||
};
|
||||
|
||||
describe("FleetsTab", () => {
|
||||
beforeAll(() => {
|
||||
addSnapshotSerializer(expect);
|
||||
});
|
||||
|
||||
it("Render", async () => {
|
||||
setToken(TEST_AUTH_OBJECT);
|
||||
const container = await renderFleetsTab();
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,23 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`CANSignalsTab Render 1`] = `
|
||||
<div>
|
||||
<div
|
||||
data-testid="mocked-vehicleprovider"
|
||||
>
|
||||
<div
|
||||
class="makeStyles-paper-0 makeStyles-tableSize-0"
|
||||
>
|
||||
<h6
|
||||
class="MuiTypography-root MuiTypography-h6"
|
||||
style="padding-bottom: 10px;"
|
||||
>
|
||||
CAN Signals
|
||||
</h6>
|
||||
<h3>
|
||||
Loading...
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -27,6 +27,13 @@ exports[`DigitalTwinTab Render 1`] = `
|
||||
:
|
||||
false
|
||||
</div>
|
||||
<div>
|
||||
<b>
|
||||
ICC Connected
|
||||
</b>
|
||||
:
|
||||
true
|
||||
</div>
|
||||
<div>
|
||||
<p>
|
||||
<b>
|
||||
|
||||
280
src/components/Cars/Status/__snapshots__/FleetsTab.test.jsx.snap
Normal file
280
src/components/Cars/Status/__snapshots__/FleetsTab.test.jsx.snap
Normal file
@@ -0,0 +1,280 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`FleetsTab Render 1`] = `
|
||||
<div>
|
||||
<div
|
||||
data-testid="mocked-statusprovider"
|
||||
>
|
||||
<div
|
||||
data-testid="mocked-userprovider"
|
||||
>
|
||||
<div
|
||||
data-testid="mocked-vehicleprovider"
|
||||
>
|
||||
<div
|
||||
data-testid="mocked-vehicleprovider"
|
||||
>
|
||||
<div
|
||||
class="makeStyles-paper-0 makeStyles-tableSize-0"
|
||||
>
|
||||
<h6
|
||||
class="MuiTypography-root MuiTypography-h6"
|
||||
>
|
||||
Fleets
|
||||
</h6>
|
||||
<div
|
||||
class="MuiGrid-root makeStyles-textCenterAlign-0 MuiGrid-container MuiGrid-spacing-xs-2 MuiGrid-justify-content-xs-center"
|
||||
>
|
||||
<div
|
||||
class="MuiGrid-root makeStyles-textCenterAlign-0 MuiGrid-item MuiGrid-grid-md-3"
|
||||
>
|
||||
<div
|
||||
class="MuiFormControl-root makeStyles-margin-0 makeStyles-fullWidth-0"
|
||||
>
|
||||
<label
|
||||
class="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated"
|
||||
data-shrink="false"
|
||||
for="search"
|
||||
>
|
||||
Search
|
||||
</label>
|
||||
<div
|
||||
class="MuiInputBase-root MuiInput-root MuiInput-underline MuiInputBase-formControl MuiInput-formControl MuiInputBase-adornedEnd"
|
||||
>
|
||||
<input
|
||||
aria-invalid="false"
|
||||
class="MuiInputBase-input MuiInput-input MuiInputBase-inputAdornedEnd"
|
||||
id="search"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<div
|
||||
class="MuiInputAdornment-root MuiInputAdornment-positionEnd"
|
||||
>
|
||||
<button
|
||||
aria-label="search"
|
||||
class="MuiButtonBase-root MuiIconButton-root"
|
||||
tabindex="0"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="MuiIconButton-label"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="MuiSvgIcon-root"
|
||||
focusable="false"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
<span
|
||||
class="MuiTouchRipple-root"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<table
|
||||
class="MuiTable-root"
|
||||
style="width: auto;"
|
||||
>
|
||||
<thead
|
||||
class="MuiTableHead-root"
|
||||
>
|
||||
<tr
|
||||
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"
|
||||
>
|
||||
CAN ID
|
||||
<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"
|
||||
>
|
||||
<tr
|
||||
class="MuiTableRow-root"
|
||||
width="200px"
|
||||
>
|
||||
<td
|
||||
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
|
||||
>
|
||||
<a
|
||||
href="/fleet/fleet1"
|
||||
>
|
||||
fleet1
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr
|
||||
class="MuiTableRow-root"
|
||||
width="200px"
|
||||
>
|
||||
<td
|
||||
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
|
||||
>
|
||||
<a
|
||||
href="/fleet/fleet2"
|
||||
>
|
||||
fleet2
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tfoot
|
||||
class="MuiTableFooter-root"
|
||||
>
|
||||
<tr
|
||||
class="MuiTableRow-root MuiTableRow-footer"
|
||||
>
|
||||
<td
|
||||
class="MuiTableCell-root MuiTableCell-footer MuiTablePagination-root"
|
||||
colspan="8"
|
||||
>
|
||||
<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"
|
||||
>
|
||||
1-2 of 2
|
||||
</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>
|
||||
</div>
|
||||
`;
|
||||
@@ -137,6 +137,24 @@ exports[`CarStatus Render 1`] = `
|
||||
class="MuiTouchRipple-root"
|
||||
/>
|
||||
</button>
|
||||
<button
|
||||
aria-controls="tabpanel-6"
|
||||
aria-selected="false"
|
||||
class="MuiButtonBase-root MuiTab-root MuiTab-textColorInherit"
|
||||
id="tab-6"
|
||||
role="tab"
|
||||
tabindex="-1"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="MuiTab-wrapper"
|
||||
>
|
||||
Fleets
|
||||
</span>
|
||||
<span
|
||||
class="MuiTouchRipple-root"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
<span
|
||||
class="PrivateTabIndicator-root-0 PrivateTabIndicator-colorSecondary-0 MuiTabs-indicator"
|
||||
@@ -255,6 +273,13 @@ exports[`CarStatus Render 1`] = `
|
||||
id="tabpanel-5"
|
||||
role="tabpanel"
|
||||
/>
|
||||
<div
|
||||
aria-labelledby="tab-6"
|
||||
class="makeStyles-fullWidth-0"
|
||||
hidden=""
|
||||
id="tabpanel-6"
|
||||
role="tabpanel"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -13,6 +13,7 @@ import { useStatusContext } from "../../Contexts/StatusContext";
|
||||
import useStyles from "../../useStyles";
|
||||
import CANSignalsTab from "./CANSignalsTab";
|
||||
import RemoteCommandsTab from "./RemoteCommandsTab";
|
||||
import FleetsTab from "./FleetsTab";
|
||||
|
||||
const tabHashes = ["details", "updates", "filters"];
|
||||
|
||||
@@ -66,6 +67,7 @@ const CarStatus = () => {
|
||||
<Tab label="Digital Twin" {...tabProps(3)} />
|
||||
<Tab label="CAN Signals" {...tabProps(4)} />
|
||||
<Tab label="Remote Commands" {...tabProps(5)} />
|
||||
<Tab label="Fleets" {...tabProps(6)} />
|
||||
</Tabs>
|
||||
</Box>
|
||||
|
||||
@@ -92,6 +94,10 @@ const CarStatus = () => {
|
||||
<TabPanel value={tabIndex} index={5} className={classes.fullWidth}>
|
||||
<RemoteCommandsTab vin={vin} />
|
||||
</TabPanel>
|
||||
|
||||
<TabPanel value={tabIndex} index={6} className={classes.fullWidth}>
|
||||
<FleetsTab vin={vin} />
|
||||
</TabPanel>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -390,7 +390,7 @@ exports[`VehicleUpdate Render 1`] = `
|
||||
class="PrivateSwitchBase-input-0"
|
||||
name="log-level-group"
|
||||
type="radio"
|
||||
value="warn"
|
||||
value="warning"
|
||||
/>
|
||||
<div
|
||||
class="PrivateRadioButtonIcon-root-0"
|
||||
@@ -424,7 +424,7 @@ exports[`VehicleUpdate Render 1`] = `
|
||||
<span
|
||||
class="MuiTypography-root MuiFormControlLabel-label MuiTypography-body1"
|
||||
>
|
||||
Warn
|
||||
Warning
|
||||
</span>
|
||||
</label>
|
||||
<label
|
||||
|
||||
@@ -208,7 +208,7 @@ const MainForm = () => {
|
||||
<FormControlLabel value="trace" control={<Radio />} label="Trace" />
|
||||
<FormControlLabel value="debug" control={<Radio />} label="Debug" />
|
||||
<FormControlLabel value="info" control={<Radio />} label="Info" />
|
||||
<FormControlLabel value="warn" control={<Radio />} label="Warn" />
|
||||
<FormControlLabel value="warning" control={<Radio />} label="Warning" />
|
||||
<FormControlLabel value="error" control={<Radio />} label="Error" />
|
||||
<FormControlLabel value="critical" control={<Radio />} label="Critical" />
|
||||
</RadioGroup>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import React, { useContext, useState, useEffect } from "react";
|
||||
import React, {useContext, useState} from "react";
|
||||
|
||||
import api from "../../services/vehiclesAPI";
|
||||
import { useUserContext } from "./UserContext";
|
||||
import { LocalDateTimeString } from "../../utils/dates";
|
||||
import {useInterval} from "usehooks-ts";
|
||||
|
||||
const CANSignalContext = React.createContext();
|
||||
|
||||
@@ -15,41 +15,26 @@ const BlankSignal = (msg) => ({
|
||||
const transformSignals = (signals) =>
|
||||
signals
|
||||
.map((signal) => {
|
||||
const { Timestamp, ...Settings } = signal;
|
||||
const keys = Object.keys(Settings);
|
||||
const { timestamp, value, name } = signal;
|
||||
|
||||
return keys.map((key) => ({
|
||||
timestamp: LocalDateTimeString(Timestamp),
|
||||
signal: key,
|
||||
value: Settings[key],
|
||||
}));
|
||||
return {
|
||||
timestamp: LocalDateTimeString(timestamp),
|
||||
signal: name,
|
||||
value: value,
|
||||
};
|
||||
})
|
||||
.flat();
|
||||
|
||||
export const CANSignalProvider = ({ children }) => {
|
||||
const {
|
||||
token: {
|
||||
idToken: { jwtToken: token },
|
||||
},
|
||||
} = useUserContext();
|
||||
export const CANSignalProvider = ({ token, children }) => {
|
||||
const [vin, setVIN] = useState(null);
|
||||
const [signals, setSignals] = useState([]);
|
||||
let delay = 500;
|
||||
const [delay, setDelay] = useState(500);
|
||||
|
||||
useEffect(() => {
|
||||
getCANSignals();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [vin]);
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => {
|
||||
getCANSignals();
|
||||
}, delay);
|
||||
return () => {
|
||||
clearTimeout(timer);
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [signals]);
|
||||
useInterval(
|
||||
() => {
|
||||
getCANSignals()
|
||||
}, vin?delay:null
|
||||
)
|
||||
|
||||
const getCANSignals = async () => {
|
||||
try {
|
||||
@@ -62,10 +47,10 @@ export const CANSignalProvider = ({ children }) => {
|
||||
const items = transformSignals(result.data);
|
||||
|
||||
if (items.length > 0) {
|
||||
delay = 500;
|
||||
setDelay(500);
|
||||
setSignals(items);
|
||||
} else {
|
||||
delay = 1000;
|
||||
setDelay(1000);
|
||||
setSignals([BlankSignal("No signals")]);
|
||||
}
|
||||
} catch (e) {
|
||||
|
||||
75
src/components/Contexts/CANSignalsContext.test.jsx
Normal file
75
src/components/Contexts/CANSignalsContext.test.jsx
Normal file
@@ -0,0 +1,75 @@
|
||||
jest.mock("../../services/vehiclesAPI");
|
||||
|
||||
import {
|
||||
render,
|
||||
cleanup,
|
||||
screen,
|
||||
fireEvent,
|
||||
waitFor,
|
||||
act,
|
||||
} from "@testing-library/react";
|
||||
import {CANSignalProvider, useCANSignalContext} from "./CANSignalsContext";
|
||||
|
||||
const checkSignalsResults = (filters) => {
|
||||
expect(screen.getByTestId("signals").innerHTML).toEqual(filters);
|
||||
};
|
||||
|
||||
describe("CANSignalsContext", () => {
|
||||
describe("getSignals", () => {
|
||||
beforeEach(() => {
|
||||
jest.useFakeTimers("setInterval");
|
||||
const TestComp = () => {
|
||||
const { signals, setVIN } = useCANSignalContext();
|
||||
|
||||
return (
|
||||
<>
|
||||
<div data-testid="signals">{JSON.stringify(signals)}</div>
|
||||
<button
|
||||
data-testid="getSignals"
|
||||
onClick={()=>{setVIN("TESTVIN1234567890")}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
render(
|
||||
<CANSignalProvider>
|
||||
<TestComp />
|
||||
</CANSignalProvider>
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.useRealTimers();
|
||||
cleanup();
|
||||
});
|
||||
|
||||
it("initial state", () => {
|
||||
checkSignalsResults("[]");
|
||||
});
|
||||
|
||||
it("getSignals", async () => {
|
||||
// eslint-disable-next-line testing-library/no-unnecessary-act
|
||||
await act(async () => {
|
||||
fireEvent.click(screen.getByTestId("getSignals"));
|
||||
await waitFor(() =>
|
||||
expect(screen.getByTestId("signals").innerHTML).toBe("[]")
|
||||
);
|
||||
jest.advanceTimersByTime(501);
|
||||
})
|
||||
|
||||
await waitFor(() => {
|
||||
return expect(screen.getByTestId("signals").innerHTML).not.toBe("[]");
|
||||
} );
|
||||
|
||||
checkSignalsResults(JSON.stringify(expectedSignalsData));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const expectedSignalsData = [
|
||||
{
|
||||
timestamp: "7/14/2021 8:09:40 PM",
|
||||
signal: "signal",
|
||||
value: 123
|
||||
},
|
||||
];
|
||||
@@ -139,7 +139,7 @@ describe("FleetContext", () => {
|
||||
onClick={() =>
|
||||
add({
|
||||
name: "EU-WEST",
|
||||
log_level: "warn",
|
||||
log_level: "warning",
|
||||
canbus: { enabled: false },
|
||||
})
|
||||
}
|
||||
@@ -219,7 +219,7 @@ describe("FleetContext", () => {
|
||||
onClick={() =>
|
||||
update({
|
||||
name: "EU-WEST",
|
||||
log_level: "warn",
|
||||
log_level: "warning",
|
||||
canbus: { enabled: false },
|
||||
})
|
||||
}
|
||||
@@ -782,7 +782,7 @@ const expectedFleetsData = [
|
||||
},
|
||||
{
|
||||
name: "US-CENTRAL",
|
||||
log_level: "warn",
|
||||
log_level: "warning",
|
||||
canbus: {
|
||||
enabled: false,
|
||||
data_logger_enabled: false,
|
||||
|
||||
@@ -31,6 +31,8 @@ export const VehicleProvider = ({ children }) => {
|
||||
const [vehicle, setVehicle] = useState({});
|
||||
const [vehicles, setVehicles] = useState([]);
|
||||
const [totalVehicles, setTotalVehicles] = useState(0);
|
||||
const [fleets, setFleets] = useState([]);
|
||||
const [totalFleets, setTotalFleets] = useState(0);
|
||||
const [models, setModels] = useState([]);
|
||||
const [years, setYears] = useState([]);
|
||||
|
||||
@@ -45,6 +47,7 @@ export const VehicleProvider = ({ children }) => {
|
||||
}
|
||||
cars.forEach((car) => {
|
||||
car.connected = result[car.vin] || false;
|
||||
car.connectedHMI = result[`2:${car.vin}`] || false;
|
||||
});
|
||||
} catch (e) {
|
||||
logger.error(e.stack);
|
||||
@@ -219,6 +222,25 @@ export const VehicleProvider = ({ children }) => {
|
||||
}
|
||||
};
|
||||
|
||||
const getFleets = async (vin, search, token) => {
|
||||
try {
|
||||
setBusy(true);
|
||||
validateVIN(vin);
|
||||
|
||||
const result = await api.getFleets(vin, search, token);
|
||||
if (result.error) {
|
||||
setFleets([]);
|
||||
throw new Error(`Get Fleets of vehicle`)
|
||||
}
|
||||
setFleets(result.data ?? []);
|
||||
if (result.total) {
|
||||
setTotalFleets(result.total);
|
||||
}
|
||||
} finally {
|
||||
setBusy(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<VehicleContext.Provider
|
||||
value={{
|
||||
@@ -228,6 +250,8 @@ export const VehicleProvider = ({ children }) => {
|
||||
vehicle,
|
||||
vehicles,
|
||||
years,
|
||||
fleets,
|
||||
totalFleets,
|
||||
addVehicle,
|
||||
deleteVehicle,
|
||||
getConnections,
|
||||
@@ -241,6 +265,7 @@ export const VehicleProvider = ({ children }) => {
|
||||
getVehicles,
|
||||
sendCommand,
|
||||
updateVehicle,
|
||||
getFleets,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
||||
@@ -13,19 +13,64 @@ import { StatusProvider, useStatusContext } from "./StatusContext";
|
||||
const checkVehicleResult = (error, busy, vehicle) => {
|
||||
checkBaseResults(error, busy);
|
||||
expect(screen.getByTestId("vehicle").innerHTML).toEqual(vehicle);
|
||||
}
|
||||
};
|
||||
|
||||
const checkVehiclesResult = (error, busy, vehicles) => {
|
||||
checkBaseResults(error, busy);
|
||||
expect(screen.getByTestId("vehicles").innerHTML).toEqual(vehicles);
|
||||
};
|
||||
|
||||
const checkFleetsResult = (error, busy, fleets) => {
|
||||
checkBaseResults(error, busy);
|
||||
expect(screen.getByTestId("fleets").innerHTML).toEqual(fleets);
|
||||
};
|
||||
|
||||
const checkBaseResults = (error, busy) => {
|
||||
expect(screen.getByTestId("error").innerHTML).toEqual(error);
|
||||
expect(screen.getByTestId("busy").innerHTML).toEqual(busy);
|
||||
};
|
||||
|
||||
describe("VehicleContext", () => {
|
||||
describe("getFleets", () => {
|
||||
beforeEach(() => {
|
||||
const TestComp = () => {
|
||||
const { busy, error, fleets, getFleets } = useVehicleContext();
|
||||
|
||||
return (
|
||||
<>
|
||||
<div data-testid="error">{error}</div>
|
||||
<div data-testid="busy">{busy.toString()}</div>
|
||||
<div data-testid="fleets">{JSON.stringify(fleets)}</div>
|
||||
<button
|
||||
data-testid="getFleets"
|
||||
onClick={() => getFleets("3C4PDCBG0ET127145")}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
render(
|
||||
<VehicleProvider>
|
||||
<TestComp />
|
||||
</VehicleProvider>
|
||||
);
|
||||
});
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
});
|
||||
|
||||
it("Initial state", () => {
|
||||
checkFleetsResult("", "false", "[]");
|
||||
});
|
||||
|
||||
it("getFleets", async () => {
|
||||
fireEvent.click(screen.getByTestId("getFleets"));
|
||||
await waitFor(() =>
|
||||
expect(screen.getByTestId("fleets").innerHTML).not.toBe("[]")
|
||||
);
|
||||
checkFleetsResult("", "false", JSON.stringify(["fleet1", "fleet2"]));
|
||||
});
|
||||
});
|
||||
|
||||
describe("getVehicles", () => {
|
||||
beforeEach(() => {
|
||||
const TestComp = () => {
|
||||
@@ -195,12 +240,22 @@ describe("VehicleContext", () => {
|
||||
<>
|
||||
<div data-testid="error">{message}</div>
|
||||
<div data-testid="busy">{busy.toString()}</div>
|
||||
<button data-testid="updateVehicleNull" onClick={() => update(null)} />
|
||||
<button data-testid="updateVehicleNoVIN" onClick={() => update({})} />
|
||||
<button
|
||||
data-testid="updateVehicleNull"
|
||||
onClick={() => update(null)}
|
||||
/>
|
||||
<button
|
||||
data-testid="updateVehicleNoVIN"
|
||||
onClick={() => update({})}
|
||||
/>
|
||||
<button
|
||||
data-testid="updateVehicle"
|
||||
onClick={() =>
|
||||
update({ vin: "3C4PDCBG0ET127145", log_level: "warn", canbus: { enabled: false } })
|
||||
update({
|
||||
vin: "3C4PDCBG0ET127145",
|
||||
log_level: "warning",
|
||||
canbus: { enabled: false },
|
||||
})
|
||||
}
|
||||
/>
|
||||
</>
|
||||
@@ -265,13 +320,17 @@ describe("VehicleContext", () => {
|
||||
<>
|
||||
<div data-testid="error">{message}</div>
|
||||
<div data-testid="busy">{busy.toString()}</div>
|
||||
<button data-testid="deleteVehicleNull" onClick={() => deleteV(null)} />
|
||||
<button data-testid="deleteVehicleNonexistent" onClick={() => deleteV("11111111111111111")} />
|
||||
<button
|
||||
data-testid="deleteVehicleNull"
|
||||
onClick={() => deleteV(null)}
|
||||
/>
|
||||
<button
|
||||
data-testid="deleteVehicleNonexistent"
|
||||
onClick={() => deleteV("11111111111111111")}
|
||||
/>
|
||||
<button
|
||||
data-testid="deleteVehicle"
|
||||
onClick={() =>
|
||||
deleteV("3C4PDCBG0ET127145")
|
||||
}
|
||||
onClick={() => deleteV("3C4PDCBG0ET127145")}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
@@ -321,7 +380,7 @@ describe("VehicleContext", () => {
|
||||
describe("sendCommand", () => {
|
||||
beforeEach(async () => {
|
||||
const TestComp = () => {
|
||||
const {busy, sendCommand} = useVehicleContext();
|
||||
const { busy, sendCommand } = useVehicleContext();
|
||||
const { message, setMessage } = useStatusContext();
|
||||
|
||||
const sendC = async (vin, command) => {
|
||||
@@ -336,18 +395,31 @@ describe("VehicleContext", () => {
|
||||
<>
|
||||
<div data-testid="error">{message}</div>
|
||||
<div data-testid="busy">{busy.toString()}</div>
|
||||
<button data-testid="sendCommandNullVin" onClick={() => sendC(null, {command:"doors_lock"})} />
|
||||
<button data-testid="sendCommandNonexistentVin" onClick={() => sendC(["11111111111111111"], {command:"doors_lock"})} />
|
||||
<button
|
||||
data-testid="sendCommandNullVin"
|
||||
onClick={() => sendC(null, { command: "doors_lock" })}
|
||||
/>
|
||||
<button
|
||||
data-testid="sendCommandNonexistentVin"
|
||||
onClick={() =>
|
||||
sendC(["11111111111111111"], { command: "doors_lock" })
|
||||
}
|
||||
/>
|
||||
<button
|
||||
data-testid="sendCommandVin"
|
||||
onClick={() => sendC("3C4PDCBG0ET127145", {command:"doors_lock"})}
|
||||
onClick={() =>
|
||||
sendC("3C4PDCBG0ET127145", { command: "doors_lock" })
|
||||
}
|
||||
/>
|
||||
<button
|
||||
data-testid="sendCommandWrongCommand"
|
||||
onClick={() => sendC("3C4PDCBG0ET127145", {command:"noSuchCommand"})}
|
||||
onClick={() =>
|
||||
sendC("3C4PDCBG0ET127145", { command: "noSuchCommand" })
|
||||
}
|
||||
/>
|
||||
</>
|
||||
);}
|
||||
);
|
||||
};
|
||||
render(
|
||||
<StatusProvider>
|
||||
<VehicleProvider>
|
||||
@@ -364,24 +436,23 @@ describe("VehicleContext", () => {
|
||||
it("initial state", () => {
|
||||
checkBaseResults("", "false");
|
||||
});
|
||||
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
const expectedFilters = [
|
||||
{
|
||||
can_id: "123-456",
|
||||
interval: 789
|
||||
interval: 789,
|
||||
},
|
||||
{
|
||||
can_id: "1",
|
||||
interval: 1000
|
||||
interval: 1000,
|
||||
},
|
||||
{
|
||||
can_id: "1000",
|
||||
interval: 1
|
||||
}
|
||||
]
|
||||
interval: 1,
|
||||
},
|
||||
];
|
||||
|
||||
const expectedVehicleData = {
|
||||
vin: "3C4PDCBG0ET127145",
|
||||
@@ -390,9 +461,16 @@ const expectedVehicleData = {
|
||||
trim: "Basic",
|
||||
ecu_list: "ECUA 2.0.0, ECUB 2.1.1",
|
||||
log_level: "info",
|
||||
canbus: { enabled: true, data_logger_enabled: true, max_mem_buffer_size: 1, max_disk_buffer_size: 2, filters: expectedFilters },
|
||||
canbus: {
|
||||
enabled: true,
|
||||
data_logger_enabled: true,
|
||||
max_mem_buffer_size: 1,
|
||||
max_disk_buffer_size: 2,
|
||||
filters: expectedFilters,
|
||||
},
|
||||
connected: true,
|
||||
}
|
||||
connectedHMI: false,
|
||||
};
|
||||
|
||||
const expectedVehiclesData = [
|
||||
{
|
||||
@@ -402,19 +480,44 @@ const expectedVehiclesData = [
|
||||
trim: "Basic",
|
||||
ecu_list: "ECUA 2.0.0, ECUB 2.1.1",
|
||||
log_level: "info",
|
||||
canbus: { enabled: true, data_logger_enabled: true, max_mem_buffer_size: 1, max_disk_buffer_size: 2, filters: expectedFilters },
|
||||
connected: true,
|
||||
canbus: {
|
||||
enabled: true,
|
||||
data_logger_enabled: true,
|
||||
max_mem_buffer_size: 1,
|
||||
max_disk_buffer_size: 2,
|
||||
filters: expectedFilters,
|
||||
},
|
||||
connected: true,
|
||||
connectedHMI: false,
|
||||
},
|
||||
{ vin: "1G1FP87S3GN100062", connected: true, connectedHMI: false },
|
||||
{
|
||||
vin: "1HGCG325XYA062256",
|
||||
year: 2021,
|
||||
connected: true,
|
||||
connectedHMI: false,
|
||||
},
|
||||
{
|
||||
vin: "1J4GZ78YXWC160024",
|
||||
year: 2021,
|
||||
model: "Ocean",
|
||||
connected: true,
|
||||
connectedHMI: false,
|
||||
},
|
||||
{
|
||||
vin: "2C3CCAAG8CH222800",
|
||||
model: "Ocean",
|
||||
trim: "Basic",
|
||||
connected: true,
|
||||
connectedHMI: false,
|
||||
},
|
||||
{ vin: "1G1FP87S3GN100062", connected: true },
|
||||
{ vin: "1HGCG325XYA062256", year: 2021, connected: true },
|
||||
{ vin: "1J4GZ78YXWC160024", year: 2021, model: "Ocean", connected: true },
|
||||
{ vin: "2C3CCAAG8CH222800", model: "Ocean", trim: "Basic", connected: true },
|
||||
{
|
||||
vin: "KNADM4A39C6028108",
|
||||
year: 2021,
|
||||
model: "Ocean",
|
||||
trim: "Basic",
|
||||
connected: true,
|
||||
connectedHMI: false,
|
||||
},
|
||||
{
|
||||
vin: "1G11C5SL9FF153507",
|
||||
@@ -422,5 +525,6 @@ const expectedVehiclesData = [
|
||||
model: "Ocean",
|
||||
trim: "Basic",
|
||||
connected: true,
|
||||
connectedHMI: false,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -28,7 +28,7 @@ let fleets = [
|
||||
},
|
||||
{
|
||||
name: "US-CENTRAL",
|
||||
log_level: "warn",
|
||||
log_level: "warning",
|
||||
canbus: { enabled: false, data_logger_enabled: false, max_mem_buffer_size: 0, max_disk_buffer_size: 0 },
|
||||
vehicles: ["USCENTVIN12345678", "USCENTVIN12345679", "USCENTVIN12345670"]
|
||||
},
|
||||
|
||||
@@ -35,6 +35,7 @@ let vehicle = {
|
||||
let vehicleState = {
|
||||
data: {
|
||||
online: false,
|
||||
online_hmi: true,
|
||||
battery: {
|
||||
percent: 95,
|
||||
},
|
||||
@@ -77,6 +78,8 @@ let vehicles = [];
|
||||
let models = ["Ocean", "PEAR"];
|
||||
let years = [2023, 2024];
|
||||
let totalVehicles = 0;
|
||||
let fleets = ["fleet1", "fleet2"];
|
||||
let totalFleets = 2;
|
||||
let error = null;
|
||||
|
||||
export const VehicleProvider = ({ children }) => {
|
||||
@@ -86,6 +89,8 @@ export const VehicleProvider = ({ children }) => {
|
||||
export const useVehicleContext = () => ({
|
||||
busy,
|
||||
models,
|
||||
fleets,
|
||||
totalFleets,
|
||||
totalVehicles,
|
||||
vehicle,
|
||||
vehicles,
|
||||
@@ -141,6 +146,10 @@ export const useVehicleContext = () => ({
|
||||
command,
|
||||
parameters,
|
||||
})),
|
||||
getFleets: jest.fn((vin, search,_token) => {return {
|
||||
data: ["fleet1", "fleet2"],
|
||||
total: 2,
|
||||
}}),
|
||||
});
|
||||
|
||||
export const setBusy = (val) => {
|
||||
|
||||
@@ -19,7 +19,7 @@ import TableHeaderSortable from "../../Table/HeaderSortable";
|
||||
import { logger } from "../../../services/monitoring";
|
||||
import ConnectedIcon from "../../Controls/ConnectedIcon";
|
||||
import ECUList from "../../Controls/ECUList";
|
||||
import {useLocalStorage} from "../../useLocalStorage";
|
||||
import { useLocalStorage } from "../../useLocalStorage";
|
||||
|
||||
const tableColumns = [
|
||||
{
|
||||
@@ -163,7 +163,8 @@ const CarSelectionTable = (props) => {
|
||||
<TableCell align="center">
|
||||
<ConnectedIcon
|
||||
connected={row.connected}
|
||||
style={{ marginRight: 5 }}
|
||||
connectedHMI={row.connectedHMI}
|
||||
style={{ marginRight: 3 }}
|
||||
/>
|
||||
<Link to={`/vehicle-status/${row.vin}`}>{row.vin}</Link>
|
||||
{row.ecu_list && (
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import {
|
||||
LinearProgress,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
@@ -57,14 +58,21 @@ const MainForm = ({ vin, token }) => {
|
||||
const [pageIndex, setPageIndex] = useState(0);
|
||||
const [orderBy, setOrderBy] = useState("id");
|
||||
const [order, setOrder] = useState("desc");
|
||||
const { cancelUpdate, getCarUpdates, carUpdates, totalCarUpdates } =
|
||||
useCarUpdatesContext();
|
||||
const {
|
||||
cancelUpdate,
|
||||
getCarUpdates,
|
||||
carUpdates,
|
||||
totalCarUpdates,
|
||||
startMonitor,
|
||||
stopMonitor,
|
||||
} = useCarUpdatesContext();
|
||||
const { setMessage } = useStatusContext();
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
try {
|
||||
if (!vin || !token) return;
|
||||
stopMonitor();
|
||||
await getCarUpdates(
|
||||
{
|
||||
vin,
|
||||
@@ -82,6 +90,20 @@ const MainForm = ({ vin, token }) => {
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [vin, token, pageIndex, pageSize, orderBy, order]);
|
||||
|
||||
useEffect(() => {
|
||||
try {
|
||||
if (carUpdates.length === 0) return;
|
||||
startMonitor(token);
|
||||
} catch (e) {
|
||||
setMessage(e.message);
|
||||
logger.warn(e.stack);
|
||||
}
|
||||
return () => {
|
||||
stopMonitor();
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [carUpdates]);
|
||||
|
||||
const handleChangePageIndex = (event, newIndex) => {
|
||||
setPageIndex(newIndex);
|
||||
};
|
||||
@@ -141,7 +163,12 @@ const MainForm = ({ vin, token }) => {
|
||||
{updateName(row)}
|
||||
</Link>
|
||||
</TableCell>
|
||||
<TableCell align="center">{row.status}</TableCell>
|
||||
<TableCell align="center">
|
||||
{row.status}
|
||||
{row.progress > -1 && (
|
||||
<LinearProgress variant="determinate" value={row.progress} />
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell align="center">
|
||||
{LocalDateTimeString(row.created)}
|
||||
</TableCell>
|
||||
|
||||
@@ -1,12 +1,27 @@
|
||||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import CheckCircleIcon from "@material-ui/icons/CheckCircle";
|
||||
import CheckBoxIcon from "@material-ui/icons/CheckBox";
|
||||
import { Tooltip } from "@material-ui/core";
|
||||
|
||||
const ConnectedIcon = (props) => {
|
||||
if (props.connected) {
|
||||
if (props.connected || props.connectedHMI) {
|
||||
return (
|
||||
<span style={props.style}>
|
||||
<CheckCircleIcon style={{ color: "Green", fontSize: 12 }} />
|
||||
{props.connected && (
|
||||
<Tooltip title="TBOX">
|
||||
<CheckCircleIcon
|
||||
style={{ color: "Green", fontSize: 12, marginRight: 3 }}
|
||||
/>
|
||||
</Tooltip>
|
||||
)}
|
||||
{props.connectedHMI && (
|
||||
<Tooltip title="ICC">
|
||||
<CheckBoxIcon
|
||||
style={{ color: "Blue", fontSize: 12, marginRight: 3 }}
|
||||
/>
|
||||
</Tooltip>
|
||||
)}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,39 +1,9 @@
|
||||
import PropTypes from "prop-types";
|
||||
import {FormControl} from "@material-ui/core";
|
||||
import { FormControl } from "@material-ui/core";
|
||||
import useStyles from "../../useStyles";
|
||||
import {MuiPickersUtilsProvider} from "@material-ui/pickers";
|
||||
import DateFnsUtils from "@date-io/date-fns";
|
||||
|
||||
export const Dates = (
|
||||
{
|
||||
startDateFunc,
|
||||
endDateFunc,
|
||||
startDate,
|
||||
handleStartChange,
|
||||
endDate,
|
||||
handleEndChange,
|
||||
}) => {
|
||||
if (startDateFunc && endDateFunc) {
|
||||
return (<MuiPickersUtilsProvider utils={DateFnsUtils}>
|
||||
{startDateFunc(startDate, handleStartChange)}
|
||||
{endDateFunc(endDate, handleEndChange, startDate)}
|
||||
</MuiPickersUtilsProvider>);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
Dates.propTypes = {
|
||||
startDateFunc: PropTypes.func,
|
||||
endDateFunc: PropTypes.func,
|
||||
startDate: PropTypes.instanceOf(Date),
|
||||
handleStartChange: PropTypes.func,
|
||||
endDate: PropTypes.instanceOf(Date),
|
||||
handleEndChange: PropTypes.func
|
||||
}
|
||||
|
||||
export const Parameters = (props) => {
|
||||
const {params} = props;
|
||||
const { params } = props;
|
||||
|
||||
const classes = useStyles();
|
||||
|
||||
@@ -41,33 +11,19 @@ export const Parameters = (props) => {
|
||||
return null;
|
||||
}
|
||||
|
||||
const {data, handleDataChange} = props;
|
||||
const {startDate, handleStartChange} = props;
|
||||
const {endDate, handleEndChange} = props;
|
||||
const { data, handleDataChange } = props;
|
||||
|
||||
return (
|
||||
<FormControl size="small" className={classes.formControl}>
|
||||
<div style={{width: "300px", marginTop: "1em"}}>
|
||||
<div style={{ width: "300px", marginTop: "1em" }}>
|
||||
{params.dataFunc(data, handleDataChange)}
|
||||
</div>
|
||||
<Dates
|
||||
startDateFunc={params.startDateFunc}
|
||||
endDateFunc={params.endDateFunc}
|
||||
startDate={startDate}
|
||||
handleStartChange={handleStartChange}
|
||||
endDate={endDate}
|
||||
handleEndChange={handleEndChange}
|
||||
></Dates>
|
||||
</FormControl>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
Parameters.propTypes = {
|
||||
params: PropTypes.any,
|
||||
data: PropTypes.any,
|
||||
handleDataChange: PropTypes.func,
|
||||
startDate: PropTypes.instanceOf(Date),
|
||||
handleStartChange: PropTypes.func,
|
||||
endDate: PropTypes.instanceOf(Date),
|
||||
handleEndChange: PropTypes.func
|
||||
};
|
||||
@@ -1,140 +1,52 @@
|
||||
jest.mock("@material-ui/pickers/MuiPickersUtilsProvider")
|
||||
|
||||
|
||||
import {render, waitFor} from "@testing-library/react";
|
||||
import {Dates, Parameters} from "./Parameters";
|
||||
import { render, waitFor } from "@testing-library/react";
|
||||
import { Parameters } from "./Parameters";
|
||||
import addSnapshotSerializer from "../../../utils/snapshot";
|
||||
|
||||
|
||||
const date = Date.parse("2011-10-10T14:48:00")
|
||||
|
||||
const renderDates = async (empty) => {
|
||||
if (empty) {
|
||||
await waitFor(() => {
|
||||
/* render */
|
||||
});
|
||||
const { container } = render(<Dates startDateFunc={null} endDateFunc={null}/>)
|
||||
|
||||
return container;
|
||||
}
|
||||
|
||||
|
||||
const [start, setStart] = [date, (_) => {}];
|
||||
const [end, setEnd] = [date, (_) => {}];
|
||||
|
||||
const handleStartChange = (val) => {
|
||||
setStart(val)
|
||||
}
|
||||
|
||||
const handleEndChange = (val) => {
|
||||
setEnd(val)
|
||||
}
|
||||
|
||||
await waitFor(() => {
|
||||
/* render */
|
||||
});
|
||||
|
||||
const {container} = render(
|
||||
<div>
|
||||
<Dates
|
||||
startDateFunc={(val, handleValChange) => <div>{val.toString()}</div>}
|
||||
endDateFunc={(val, handleValChange, prevVal) => <div>{val.toString()}, {prevVal.toString()}</div>}
|
||||
startDate={start}
|
||||
endDate={end}
|
||||
handleStartChange={handleStartChange}
|
||||
handleEndChange={handleEndChange}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
|
||||
return container
|
||||
}
|
||||
|
||||
describe("Dates", () => {
|
||||
beforeAll(() => {
|
||||
addSnapshotSerializer(expect);
|
||||
})
|
||||
|
||||
it("Render empty", async () => {
|
||||
const container = await renderDates(true);
|
||||
expect(container).toMatchSnapshot();
|
||||
})
|
||||
|
||||
it("Render filled", async () => {
|
||||
const container = await renderDates(false);
|
||||
expect(container).toMatchSnapshot();
|
||||
})
|
||||
})
|
||||
|
||||
const renderState = {
|
||||
EMPTY: 0,
|
||||
JUST_DATA: 1,
|
||||
WITH_DATE: 2,
|
||||
}
|
||||
};
|
||||
|
||||
const renderParameters = async (rs) => {
|
||||
await waitFor(() => {
|
||||
/* render */
|
||||
});
|
||||
|
||||
if (rs===renderState.EMPTY) {
|
||||
const { container } = render(<Parameters/>)
|
||||
if (rs === renderState.EMPTY) {
|
||||
const { container } = render(<Parameters />);
|
||||
|
||||
return container;
|
||||
}
|
||||
const params = {dataFunc:(val, handleValChange) => <div>val.toString()</div>}
|
||||
const [data, handleDataChange] = [true, (_)=>{}];
|
||||
const params = {
|
||||
dataFunc: (val, handleValChange) => <div>val.toString()</div>,
|
||||
};
|
||||
const [data, handleDataChange] = [true, (_) => {}];
|
||||
|
||||
if (rs===renderState.JUST_DATA) {
|
||||
const { container } = render(<Parameters
|
||||
if (rs === renderState.JUST_DATA) {
|
||||
const { container } = render(
|
||||
<Parameters
|
||||
params={params}
|
||||
data={data}
|
||||
handleDataChange={handleDataChange}
|
||||
/>)
|
||||
|
||||
return container;
|
||||
}
|
||||
|
||||
params.startDateFunc = (val, handleValChange) => (<div>val.toString()</div>)
|
||||
params.endDateFunc = (val, handleValChange, prevVal) => (<div>val.toString(), prevVal.toString()</div>)
|
||||
|
||||
const [start, handleStartChange] = [date, (_)=>{}];
|
||||
const [end, handleEndChange] = [date, (_)=>{}];
|
||||
|
||||
if (rs===renderState.WITH_DATE) {
|
||||
const {container} = render(<Parameters
|
||||
params={params}
|
||||
data={data}
|
||||
handleDataChange={handleDataChange}
|
||||
start={start}
|
||||
handleStartChange={handleStartChange}
|
||||
end={end}
|
||||
handleEndChange={handleEndChange}
|
||||
/>
|
||||
)
|
||||
);
|
||||
|
||||
return container
|
||||
return container;
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
describe("Params", () => {
|
||||
beforeAll(() => {
|
||||
addSnapshotSerializer(expect);
|
||||
})
|
||||
});
|
||||
|
||||
it("Render empty", async () => {
|
||||
const container = await renderParameters(renderState.EMPTY);
|
||||
expect(container).toMatchSnapshot();
|
||||
})
|
||||
});
|
||||
|
||||
it("Render just data", async () => {
|
||||
const container = await renderDates(renderState.JUST_DATA);
|
||||
const container = await renderParameters(renderState.JUST_DATA);
|
||||
expect(container).toMatchSnapshot();
|
||||
})
|
||||
|
||||
it("Render with date", async () => {
|
||||
const container = await renderDates(renderState.WITH_DATE);
|
||||
expect(container).toMatchSnapshot();
|
||||
})
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,24 +1,19 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Dates Render empty 1`] = `<div />`;
|
||||
exports[`Params Render empty 1`] = `<div />`;
|
||||
|
||||
exports[`Dates Render filled 1`] = `
|
||||
exports[`Params Render just data 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="MuiFormControl-root makeStyles-formControl-0"
|
||||
>
|
||||
<div
|
||||
style="width: 300px; margin-top: 1em;"
|
||||
>
|
||||
<div>
|
||||
<div>
|
||||
1318258080000
|
||||
val.toString()
|
||||
</div>
|
||||
<div>
|
||||
1318258080000
|
||||
,
|
||||
1318258080000
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Params Render empty 1`] = `<div />`;
|
||||
|
||||
exports[`Params Render just data 1`] = `<div />`;
|
||||
|
||||
exports[`Params Render with date 1`] = `<div />`;
|
||||
|
||||
@@ -248,7 +248,7 @@ exports[`FleetAddForm Render 1`] = `
|
||||
class="PrivateSwitchBase-input-0"
|
||||
name="log-level-group"
|
||||
type="radio"
|
||||
value="warn"
|
||||
value="warning"
|
||||
/>
|
||||
<div
|
||||
class="PrivateRadioButtonIcon-root-0"
|
||||
@@ -282,7 +282,7 @@ exports[`FleetAddForm Render 1`] = `
|
||||
<span
|
||||
class="MuiTypography-root MuiFormControlLabel-label MuiTypography-body1"
|
||||
>
|
||||
Warn
|
||||
Warning
|
||||
</span>
|
||||
</label>
|
||||
<label
|
||||
|
||||
@@ -129,7 +129,7 @@ const MainForm = () => {
|
||||
<FormControlLabel value="trace" control={<Radio />} label="Trace" />
|
||||
<FormControlLabel value="debug" control={<Radio />} label="Debug" />
|
||||
<FormControlLabel value="info" control={<Radio />} label="Info" />
|
||||
<FormControlLabel value="warn" control={<Radio />} label="Warn" />
|
||||
<FormControlLabel value="warning" control={<Radio />} label="Warning" />
|
||||
<FormControlLabel value="error" control={<Radio />} label="Error" />
|
||||
<FormControlLabel value="critical" control={<Radio />} label="Critical" />
|
||||
</RadioGroup>
|
||||
|
||||
@@ -153,7 +153,7 @@ exports[`FleetVehiclesTable Render 1`] = `
|
||||
<td
|
||||
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
|
||||
>
|
||||
<div>
|
||||
<span>
|
||||
<a
|
||||
class=""
|
||||
href="/"
|
||||
@@ -172,7 +172,7 @@ exports[`FleetVehiclesTable Render 1`] = `
|
||||
</svg>
|
||||
</a>
|
||||
<div />
|
||||
</div>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr
|
||||
@@ -190,7 +190,7 @@ exports[`FleetVehiclesTable Render 1`] = `
|
||||
<td
|
||||
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
|
||||
>
|
||||
<div>
|
||||
<span>
|
||||
<a
|
||||
class=""
|
||||
href="/"
|
||||
@@ -209,7 +209,7 @@ exports[`FleetVehiclesTable Render 1`] = `
|
||||
</svg>
|
||||
</a>
|
||||
<div />
|
||||
</div>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr
|
||||
@@ -227,7 +227,7 @@ exports[`FleetVehiclesTable Render 1`] = `
|
||||
<td
|
||||
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
|
||||
>
|
||||
<div>
|
||||
<span>
|
||||
<a
|
||||
class=""
|
||||
href="/"
|
||||
@@ -246,7 +246,7 @@ exports[`FleetVehiclesTable Render 1`] = `
|
||||
</svg>
|
||||
</a>
|
||||
<div />
|
||||
</div>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
@@ -147,7 +147,7 @@ const MainForm = ({ name }) => {
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<div>
|
||||
<span key={`delete-${action.id}-of-div`}>
|
||||
<Tooltip key={`delete-${action.id}`} title={action.tip}>
|
||||
<Link to="#" onClick={() => onDelete(action.id)}>
|
||||
{action.icon}
|
||||
@@ -159,7 +159,7 @@ const MainForm = ({ name }) => {
|
||||
close={() => setShowDeleteModal(false)}
|
||||
deleteFunction={() => onDelete(action.id)}
|
||||
/>
|
||||
</div>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -152,7 +152,7 @@ exports[`VehiclesTab Render 1`] = `
|
||||
<td
|
||||
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
|
||||
>
|
||||
<div>
|
||||
<span>
|
||||
<a
|
||||
class=""
|
||||
href="/"
|
||||
@@ -171,7 +171,7 @@ exports[`VehiclesTab Render 1`] = `
|
||||
</svg>
|
||||
</a>
|
||||
<div />
|
||||
</div>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr
|
||||
@@ -189,7 +189,7 @@ exports[`VehiclesTab Render 1`] = `
|
||||
<td
|
||||
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
|
||||
>
|
||||
<div>
|
||||
<span>
|
||||
<a
|
||||
class=""
|
||||
href="/"
|
||||
@@ -208,7 +208,7 @@ exports[`VehiclesTab Render 1`] = `
|
||||
</svg>
|
||||
</a>
|
||||
<div />
|
||||
</div>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr
|
||||
@@ -226,7 +226,7 @@ exports[`VehiclesTab Render 1`] = `
|
||||
<td
|
||||
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
|
||||
>
|
||||
<div>
|
||||
<span>
|
||||
<a
|
||||
class=""
|
||||
href="/"
|
||||
@@ -245,7 +245,7 @@ exports[`VehiclesTab Render 1`] = `
|
||||
</svg>
|
||||
</a>
|
||||
<div />
|
||||
</div>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
@@ -304,7 +304,7 @@ exports[`FleetTable Render 1`] = `
|
||||
<td
|
||||
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
|
||||
>
|
||||
warn
|
||||
warning
|
||||
</td>
|
||||
<td
|
||||
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
|
||||
|
||||
@@ -248,7 +248,7 @@ exports[`FleetUpdate Render 1`] = `
|
||||
class="PrivateSwitchBase-input-0"
|
||||
name="log-level-group"
|
||||
type="radio"
|
||||
value="warn"
|
||||
value="warning"
|
||||
/>
|
||||
<div
|
||||
class="PrivateRadioButtonIcon-root-0"
|
||||
@@ -282,7 +282,7 @@ exports[`FleetUpdate Render 1`] = `
|
||||
<span
|
||||
class="MuiTypography-root MuiFormControlLabel-label MuiTypography-body1"
|
||||
>
|
||||
Warn
|
||||
Warning
|
||||
</span>
|
||||
</label>
|
||||
<label
|
||||
|
||||
@@ -9,30 +9,30 @@ import {
|
||||
FormLabel,
|
||||
Radio,
|
||||
RadioGroup,
|
||||
TextField
|
||||
TextField,
|
||||
} from "@material-ui/core";
|
||||
|
||||
import useStyles from "../../useStyles";
|
||||
import {
|
||||
useFleetContext,
|
||||
FleetProvider
|
||||
} from "../../Contexts/FleetContext";
|
||||
import { useFleetContext, FleetProvider } from "../../Contexts/FleetContext";
|
||||
import { useStatusContext } from "../../Contexts/StatusContext";
|
||||
import { useUserContext } from "../../Contexts/UserContext";
|
||||
import { logger } from "../../../services/monitoring";
|
||||
|
||||
|
||||
const MainForm = () => {
|
||||
const queries = new URLSearchParams(useLocation().search);
|
||||
|
||||
const { fleet, getFleet, updateFleet, busy } = useFleetContext();
|
||||
const { token: { idToken: { jwtToken: token } } } = useUserContext();
|
||||
const {
|
||||
token: {
|
||||
idToken: { jwtToken: token },
|
||||
},
|
||||
} = useUserContext();
|
||||
const { setMessage, setTitle, setSitePath } = useStatusContext();
|
||||
const [redirect, setRedirect] = useState(null);
|
||||
const classes = useStyles();
|
||||
|
||||
const [name, setName] = useState(queries.get("name") ?? "");
|
||||
const [oldName, ] = useState(name);
|
||||
const [oldName] = useState(name);
|
||||
const [selectedLogLevel, setSelectedLogLevel] = useState("info");
|
||||
const [canbusEnabled, setCANBusEnabled] = useState(true);
|
||||
const [dataLoggerEnabled, setDataLoggerEnabled] = useState(false);
|
||||
@@ -71,36 +71,40 @@ const MainForm = () => {
|
||||
|
||||
if (fleet.canbus) {
|
||||
setCANBusEnabled(fleet.canbus.enabled ?? canbusEnabled);
|
||||
setDataLoggerEnabled(fleet.canbus.data_logger_enabled ?? dataLoggerEnabled);
|
||||
setDataLoggerEnabled(
|
||||
fleet.canbus.data_logger_enabled ?? dataLoggerEnabled
|
||||
);
|
||||
setMaxMemBufferSize(fleet.canbus.max_mem_buffer_size ?? maxMemBufferSize);
|
||||
setMaxDiskBufferSize(fleet.canbus.max_disk_buffer_size ?? maxDiskBufferSize);
|
||||
setMaxDiskBufferSize(
|
||||
fleet.canbus.max_disk_buffer_size ?? maxDiskBufferSize
|
||||
);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [fleet]);
|
||||
|
||||
const onNameChange = (event) => {
|
||||
setName(event.target.value);
|
||||
}
|
||||
};
|
||||
|
||||
const onLogLevelChange = (event) => {
|
||||
setSelectedLogLevel(event.target.value);
|
||||
}
|
||||
};
|
||||
|
||||
const onCANBusChange = (event) => {
|
||||
setCANBusEnabled(event.target.checked);
|
||||
}
|
||||
};
|
||||
|
||||
const onDataLoggerChange = (event) => {
|
||||
setDataLoggerEnabled(event.target.checked);
|
||||
}
|
||||
};
|
||||
|
||||
const onMaxMemBufferSizeChange = (event) => {
|
||||
setMaxMemBufferSize(event.target.value);
|
||||
}
|
||||
};
|
||||
|
||||
const onMaxDiskBufferSizeChange = (event) => {
|
||||
setMaxDiskBufferSize(event.target.value);
|
||||
}
|
||||
};
|
||||
|
||||
const onSubmit = async (event) => {
|
||||
try {
|
||||
@@ -112,12 +116,13 @@ const MainForm = () => {
|
||||
enabled: canbusEnabled,
|
||||
data_logger_enabled: canbusEnabled ? dataLoggerEnabled : false,
|
||||
max_mem_buffer_size: canbusEnabled ? parseInt(maxMemBufferSize) : 0,
|
||||
max_disk_buffer_size: canbusEnabled && dataLoggerEnabled ? parseInt(maxDiskBufferSize) : 0
|
||||
}
|
||||
max_disk_buffer_size:
|
||||
canbusEnabled && dataLoggerEnabled
|
||||
? parseInt(maxDiskBufferSize)
|
||||
: 0,
|
||||
},
|
||||
};
|
||||
|
||||
console.log(oldName);
|
||||
|
||||
const result = await updateFleet(oldName, formData, token);
|
||||
if (!result || result.error) return;
|
||||
|
||||
@@ -162,22 +167,30 @@ const MainForm = () => {
|
||||
<FormControlLabel value="trace" control={<Radio />} label="Trace" />
|
||||
<FormControlLabel value="debug" control={<Radio />} label="Debug" />
|
||||
<FormControlLabel value="info" control={<Radio />} label="Info" />
|
||||
<FormControlLabel value="warn" control={<Radio />} label="Warn" />
|
||||
<FormControlLabel
|
||||
value="warning"
|
||||
control={<Radio />}
|
||||
label="Warning"
|
||||
/>
|
||||
<FormControlLabel value="error" control={<Radio />} label="Error" />
|
||||
<FormControlLabel value="critical" control={<Radio />} label="Critical" />
|
||||
<FormControlLabel
|
||||
value="critical"
|
||||
control={<Radio />}
|
||||
label="Critical"
|
||||
/>
|
||||
</RadioGroup>
|
||||
<FormLabel id="demo-row-radio-buttons-group-label">CAN Bus</FormLabel>
|
||||
<FormGroup>
|
||||
<FormControlLabel control={
|
||||
<Checkbox
|
||||
checked={canbusEnabled}
|
||||
onChange={onCANBusChange}
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox checked={canbusEnabled} onChange={onCANBusChange} />
|
||||
}
|
||||
label="CAN Bus Enabled"
|
||||
/>
|
||||
} label="CAN Bus Enabled" />
|
||||
<TextField
|
||||
id="max_mem_buffer_size"
|
||||
name="max_mem_buffer_size"
|
||||
label='Max Memory Buffer Size (0 uses default size)'
|
||||
label="Max Memory Buffer Size (0 uses default size)"
|
||||
value={maxMemBufferSize}
|
||||
onChange={onMaxMemBufferSizeChange}
|
||||
variant="outlined"
|
||||
@@ -190,18 +203,21 @@ const MainForm = () => {
|
||||
required
|
||||
fullWidth
|
||||
/>
|
||||
<FormControlLabel control={
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={dataLoggerEnabled}
|
||||
onChange={onDataLoggerChange}
|
||||
disabled={!canbusEnabled}
|
||||
/>
|
||||
} label="Data Logger Enabled" />
|
||||
}
|
||||
label="Data Logger Enabled"
|
||||
/>
|
||||
</FormGroup>
|
||||
<TextField
|
||||
id="max_disk_buffer_size"
|
||||
name="max_disk_buffer_size"
|
||||
label='Max Disk Buffer Size (0 uses default size)'
|
||||
label="Max Disk Buffer Size (0 uses default size)"
|
||||
value={maxDiskBufferSize}
|
||||
onChange={onMaxDiskBufferSizeChange}
|
||||
variant="outlined"
|
||||
|
||||
@@ -68,7 +68,11 @@ const MainForm = () => {
|
||||
const [orderBy, setOrderBy] = useState("id");
|
||||
const [order, setOrder] = useState("asc");
|
||||
const [search, setSearch] = useState("");
|
||||
|
||||
const [showDeleteModal, setShowDeleteModal] = useState(false);
|
||||
const [deleteId, setDeleteId] = useState("");
|
||||
const [deleteRowName, setDeleteRowName] = useState("");
|
||||
|
||||
const { getManifests, deleteManifest, manifests, totalManifests } =
|
||||
useManifestsContext();
|
||||
const { setMessage, setTitle, setSitePath } = useStatusContext();
|
||||
@@ -133,6 +137,12 @@ const MainForm = () => {
|
||||
setSearch(query);
|
||||
};
|
||||
|
||||
const setDeletePopup = (id, row) => {
|
||||
setDeleteId(id);
|
||||
setDeleteRowName(`${row.name} ${row.version}`);
|
||||
setShowDeleteModal(true);
|
||||
};
|
||||
|
||||
const onDelete = async (manifest_id) => {
|
||||
try {
|
||||
await deleteManifest(parseInt(manifest_id), token);
|
||||
@@ -180,19 +190,13 @@ const MainForm = () => {
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<div>
|
||||
<span key={`delete-${action.id}-of-key`}>
|
||||
<Tooltip key={`delete-${action.id}`} title={action.tip}>
|
||||
<Link to="#" onClick={() => onDelete(action.id)}>
|
||||
<Link to="#" onClick={() => setDeletePopup(action.id, row)}>
|
||||
{action.icon}
|
||||
</Link>
|
||||
</Tooltip>
|
||||
<DeleteConfirmation
|
||||
message={action.id}
|
||||
open={showDeleteModal}
|
||||
close={() => setShowDeleteModal(false)}
|
||||
deleteFunction={() => onDelete(action.id)}
|
||||
/>
|
||||
</div>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
});
|
||||
@@ -261,6 +265,12 @@ const MainForm = () => {
|
||||
</TableRow>
|
||||
</TableFooter>
|
||||
</Table>
|
||||
<DeleteConfirmation
|
||||
message={deleteRowName}
|
||||
open={showDeleteModal}
|
||||
close={() => setShowDeleteModal(false)}
|
||||
deleteFunction={() => onDelete(deleteId)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -74,12 +74,16 @@ const Component = () => {
|
||||
|
||||
useEffect(() => {
|
||||
if (!token) return;
|
||||
if (markers.length > 0) {
|
||||
const vins = markers.map((marker) => marker[2]);
|
||||
|
||||
const vins = markers
|
||||
.filter((marker) => marker[2].length > 0)
|
||||
.map((marker) => marker[2]);
|
||||
|
||||
if (vins.length === 0) return;
|
||||
|
||||
getConnections(vins, token).then((conns) => {
|
||||
setConnections(conns);
|
||||
});
|
||||
}
|
||||
// eslint-disable-next-line
|
||||
}, [markers, token]);
|
||||
|
||||
@@ -151,7 +155,6 @@ const Component = () => {
|
||||
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
|
||||
/>
|
||||
<CenterFocus center={center} zoom={zoom} />
|
||||
|
||||
{markers.map((marker) => (
|
||||
<Marker
|
||||
icon={getCarIcon(marker[2])}
|
||||
@@ -184,6 +187,7 @@ const Component = () => {
|
||||
key={carState.vin}
|
||||
vin={carState.vin}
|
||||
online={carState.online}
|
||||
onlineHMI={carState.online_hmi}
|
||||
battery={carState.battery}
|
||||
doors={carState.doors}
|
||||
location={carState.location}
|
||||
|
||||
@@ -13,7 +13,7 @@ import CANSignals from "../Cars/CANSignals";
|
||||
const VehiclePopUp = (props) => {
|
||||
const classes = useStyles();
|
||||
const [viewCAN, setViewCAN] = useState(false);
|
||||
const { vin, online, onClose } = props;
|
||||
const { vin, online, onClose, onlineHMI } = props;
|
||||
|
||||
const toggleView = (e) => {
|
||||
e.preventDefault();
|
||||
@@ -43,6 +43,9 @@ const VehiclePopUp = (props) => {
|
||||
<p>
|
||||
<b>Connected</b>: {online.toString()}
|
||||
</p>
|
||||
<p>
|
||||
<b>ICC Connected</b>: {onlineHMI?.toString()}
|
||||
</p>
|
||||
{viewCAN && <CANSignals vin={vin} />}
|
||||
{!viewCAN && <DigitalTwin {...props} />}
|
||||
</div>
|
||||
|
||||
@@ -22,7 +22,7 @@ const fleets = [
|
||||
},
|
||||
{
|
||||
name: "US-CENTRAL",
|
||||
log_level: "warn",
|
||||
log_level: "warning",
|
||||
canbus: { enabled: false, data_logger_enabled: false, max_mem_buffer_size: 0, max_disk_buffer_size: 0 },
|
||||
vehicles: ["USCENTVIN12345678", "USCENTVIN12345679", "USCENTVIN12345670"]
|
||||
},
|
||||
|
||||
@@ -50,6 +50,14 @@ const ecusData = [
|
||||
},
|
||||
];
|
||||
|
||||
const signals = {data:[
|
||||
{
|
||||
timestamp: "2021-07-14T20:09:40.98187Z",
|
||||
name: "signal",
|
||||
value: 123
|
||||
},
|
||||
]};
|
||||
|
||||
const vehiclesAPI = {
|
||||
addVehicle: async (vehicle) => {
|
||||
data.push(vehicle);
|
||||
@@ -89,6 +97,7 @@ const vehiclesAPI = {
|
||||
getVehicles: async () => {
|
||||
return { data };
|
||||
},
|
||||
getFleets: async (vin) => {return { data: ["fleet1", "fleet2"]}},
|
||||
getYears: async () => {
|
||||
return {
|
||||
data: [2021, 2022],
|
||||
@@ -104,6 +113,9 @@ const vehiclesAPI = {
|
||||
const index = data.findIndex(element => element.vin === vin);
|
||||
if (index >= 0) data[index] = vehicle;
|
||||
return vehicle;
|
||||
},
|
||||
getCANSignals: async (vin, vehicle) => {
|
||||
return signals;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -114,6 +114,19 @@ const vehiclesAPI = {
|
||||
.catch(errorHandler);
|
||||
},
|
||||
|
||||
getFleets: async (vin, search, token) => {
|
||||
const u = addQueryParams(`${API_ENDPOINT}/vehicle/${vin}/fleets`, search)
|
||||
return fetch(u, {
|
||||
method: "GET",
|
||||
headers: Object.assign(
|
||||
{ "Content-Type": "application/json" },
|
||||
getAuthHeaderOptions(token)
|
||||
),
|
||||
})
|
||||
.then(fetchRespHandler)
|
||||
.catch(errorHandler);
|
||||
},
|
||||
|
||||
getYears: async (token) =>
|
||||
fetch(`${API_ENDPOINT}/vehicleyears`, {
|
||||
method: "GET",
|
||||
|
||||
Reference in New Issue
Block a user