Merge branch 'release/0.0.3'

This commit is contained in:
jwu-fisker
2022-10-05 14:18:38 -07:00
45 changed files with 1127 additions and 347 deletions

20
package-lock.json generated
View File

@@ -33,6 +33,7 @@
"react-router-dom": "^5.3.0", "react-router-dom": "^5.3.0",
"react-router-hash-link": "^2.4.3", "react-router-hash-link": "^2.4.3",
"react-scripts": "5.0.0", "react-scripts": "5.0.0",
"usehooks-ts": "^2.7.1",
"web-vitals": "^2.1.4" "web-vitals": "^2.1.4"
}, },
"devDependencies": { "devDependencies": {
@@ -16432,6 +16433,19 @@
"punycode": "^2.1.0" "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": { "node_modules/util-deprecate": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
@@ -29270,6 +29284,12 @@
"punycode": "^2.1.0" "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": { "util-deprecate": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",

View File

@@ -28,6 +28,7 @@
"react-router-dom": "^5.3.0", "react-router-dom": "^5.3.0",
"react-router-hash-link": "^2.4.3", "react-router-hash-link": "^2.4.3",
"react-scripts": "5.0.0", "react-scripts": "5.0.0",
"usehooks-ts": "^2.7.1",
"web-vitals": "^2.1.4" "web-vitals": "^2.1.4"
}, },
"scripts": { "scripts": {
@@ -67,9 +68,9 @@
"coverageThreshold": { "coverageThreshold": {
"global": { "global": {
"branches": 39, "branches": 39,
"functions": 45.5, "functions": 47,
"lines": 50, "lines": 55,
"statements": 49 "statements": 55
} }
}, },
"coverageReporters": [ "coverageReporters": [

View File

@@ -3311,7 +3311,7 @@ exports[`App Route /packages authenticated 1`] = `
/> />
</svg> </svg>
</a> </a>
<div> <span>
<a <a
class="" class=""
href="/packages" href="/packages"
@@ -3329,8 +3329,7 @@ exports[`App Route /packages authenticated 1`] = `
/> />
</svg> </svg>
</a> </a>
<div /> </span>
</div>
</td> </td>
</tr> </tr>
</tbody> </tbody>
@@ -3460,6 +3459,7 @@ exports[`App Route /packages authenticated 1`] = `
</tr> </tr>
</tfoot> </tfoot>
</table> </table>
<div />
</div> </div>
</div> </div>
</main> </main>
@@ -6015,7 +6015,7 @@ exports[`App Route /vehicle-add authenticated 1`] = `
class="PrivateSwitchBase-input-0" class="PrivateSwitchBase-input-0"
name="log-level-group" name="log-level-group"
type="radio" type="radio"
value="warn" value="warning"
/> />
<div <div
class="PrivateRadioButtonIcon-root-0" class="PrivateRadioButtonIcon-root-0"
@@ -6049,7 +6049,7 @@ exports[`App Route /vehicle-add authenticated 1`] = `
<span <span
class="MuiTypography-root MuiFormControlLabel-label MuiTypography-body1" class="MuiTypography-root MuiFormControlLabel-label MuiTypography-body1"
> >
Warn Warning
</span> </span>
</label> </label>
<label <label
@@ -6918,6 +6918,24 @@ exports[`App Route /vehicle-status authenticated 1`] = `
class="MuiTouchRipple-root" class="MuiTouchRipple-root"
/> />
</button> </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> </div>
<span <span
class="PrivateTabIndicator-root-0 PrivateTabIndicator-colorSecondary-0 MuiTabs-indicator" class="PrivateTabIndicator-root-0 PrivateTabIndicator-colorSecondary-0 MuiTabs-indicator"
@@ -7089,6 +7107,13 @@ exports[`App Route /vehicle-status authenticated 1`] = `
id="tabpanel-5" id="tabpanel-5"
role="tabpanel" role="tabpanel"
/> />
<div
aria-labelledby="tab-6"
class="makeStyles-fullWidth-0"
hidden=""
id="tabpanel-6"
role="tabpanel"
/>
</div> </div>
</main> </main>
</main> </main>

View File

@@ -53,6 +53,7 @@ const PAGE_SIZE = "CAN_FILTER_TABLE_PAGE_SIZE";
const MainForm = ({ vin }) => { const MainForm = ({ vin }) => {
const classes = useStyles(); const classes = useStyles();
const [search, onSearch] = useState("");
const [pageSize, setPageSize] = useLocalStorage(PAGE_SIZE, 10); const [pageSize, setPageSize] = useLocalStorage(PAGE_SIZE, 10);
const [pageIndex, setPageIndex] = useState(0); const [pageIndex, setPageIndex] = useState(0);
const [orderBy, setOrderBy] = useState("id"); const [orderBy, setOrderBy] = useState("id");
@@ -68,6 +69,7 @@ const MainForm = ({ vin }) => {
await getFilters( await getFilters(
vin, vin,
{ {
search,
limit: pageSize, limit: pageSize,
offset: pageSize * pageIndex, offset: pageSize * pageIndex,
order: `${orderBy} ${order}`, order: `${orderBy} ${order}`,
@@ -80,7 +82,7 @@ const MainForm = ({ vin }) => {
} }
})(); })();
// eslint-disable-next-line react-hooks/exhaustive-deps // 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) => { const handleChangePageIndex = (event, newIndex) => {
setPageIndex(newIndex); setPageIndex(newIndex);
@@ -166,7 +168,7 @@ const MainForm = ({ vin }) => {
</Link> </Link>
</Grid> </Grid>
<Grid item md={8} className={classes.textCenterAlign}> <Grid item md={8} className={classes.textCenterAlign}>
<SearchField classes={classes} /> <SearchField classes={classes} onSearch={onSearch}/>
</Grid> </Grid>
</Grid> </Grid>
<Table> <Table>

View File

@@ -388,7 +388,7 @@ exports[`VehicleAddForm Render 1`] = `
class="PrivateSwitchBase-input-0" class="PrivateSwitchBase-input-0"
name="log-level-group" name="log-level-group"
type="radio" type="radio"
value="warn" value="warning"
/> />
<div <div
class="PrivateRadioButtonIcon-root-0" class="PrivateRadioButtonIcon-root-0"
@@ -422,7 +422,7 @@ exports[`VehicleAddForm Render 1`] = `
<span <span
class="MuiTypography-root MuiFormControlLabel-label MuiTypography-body1" class="MuiTypography-root MuiFormControlLabel-label MuiTypography-body1"
> >
Warn Warning
</span> </span>
</label> </label>
<label <label

View File

@@ -180,7 +180,7 @@ const MainForm = () => {
<FormControlLabel value="trace" control={<Radio />} label="Trace" /> <FormControlLabel value="trace" control={<Radio />} label="Trace" />
<FormControlLabel value="debug" control={<Radio />} label="Debug" /> <FormControlLabel value="debug" control={<Radio />} label="Debug" />
<FormControlLabel value="info" control={<Radio />} label="Info" /> <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="error" control={<Radio />} label="Error" />
<FormControlLabel value="critical" control={<Radio />} label="Critical" /> <FormControlLabel value="critical" control={<Radio />} label="Critical" />
</RadioGroup> </RadioGroup>

View File

@@ -45,7 +45,7 @@ const Main = ({ vin }) => {
}; };
const CANSignals = (props) => ( const CANSignals = (props) => (
<CANSignalProvider> <CANSignalProvider {...{token:props.token}}>
<Main {...props} /> <Main {...props} />
</CANSignalProvider> </CANSignalProvider>
); );

View 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();
});
});

View File

@@ -48,6 +48,9 @@ const Main = (props) => {
<div> <div>
<b>Connected</b>: {carState.online.toString()} <b>Connected</b>: {carState.online.toString()}
</div> </div>
<div>
<b>ICC Connected</b>: {carState?.online_hmi.toString()}
</div>
<DigitalTwin {...carState} /> <DigitalTwin {...carState} />
</> </>
)} )}

View 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;

View 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();
});
});

View File

@@ -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>
`;

View File

@@ -27,6 +27,13 @@ exports[`DigitalTwinTab Render 1`] = `
: :
false false
</div> </div>
<div>
<b>
ICC Connected
</b>
:
true
</div>
<div> <div>
<p> <p>
<b> <b>

View 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>
`;

View File

@@ -137,6 +137,24 @@ exports[`CarStatus Render 1`] = `
class="MuiTouchRipple-root" class="MuiTouchRipple-root"
/> />
</button> </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> </div>
<span <span
class="PrivateTabIndicator-root-0 PrivateTabIndicator-colorSecondary-0 MuiTabs-indicator" class="PrivateTabIndicator-root-0 PrivateTabIndicator-colorSecondary-0 MuiTabs-indicator"
@@ -255,6 +273,13 @@ exports[`CarStatus Render 1`] = `
id="tabpanel-5" id="tabpanel-5"
role="tabpanel" role="tabpanel"
/> />
<div
aria-labelledby="tab-6"
class="makeStyles-fullWidth-0"
hidden=""
id="tabpanel-6"
role="tabpanel"
/>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -13,6 +13,7 @@ import { useStatusContext } from "../../Contexts/StatusContext";
import useStyles from "../../useStyles"; import useStyles from "../../useStyles";
import CANSignalsTab from "./CANSignalsTab"; import CANSignalsTab from "./CANSignalsTab";
import RemoteCommandsTab from "./RemoteCommandsTab"; import RemoteCommandsTab from "./RemoteCommandsTab";
import FleetsTab from "./FleetsTab";
const tabHashes = ["details", "updates", "filters"]; const tabHashes = ["details", "updates", "filters"];
@@ -66,6 +67,7 @@ const CarStatus = () => {
<Tab label="Digital Twin" {...tabProps(3)} /> <Tab label="Digital Twin" {...tabProps(3)} />
<Tab label="CAN Signals" {...tabProps(4)} /> <Tab label="CAN Signals" {...tabProps(4)} />
<Tab label="Remote Commands" {...tabProps(5)} /> <Tab label="Remote Commands" {...tabProps(5)} />
<Tab label="Fleets" {...tabProps(6)} />
</Tabs> </Tabs>
</Box> </Box>
@@ -92,6 +94,10 @@ const CarStatus = () => {
<TabPanel value={tabIndex} index={5} className={classes.fullWidth}> <TabPanel value={tabIndex} index={5} className={classes.fullWidth}>
<RemoteCommandsTab vin={vin} /> <RemoteCommandsTab vin={vin} />
</TabPanel> </TabPanel>
<TabPanel value={tabIndex} index={6} className={classes.fullWidth}>
<FleetsTab vin={vin} />
</TabPanel>
</div> </div>
); );
}; };

View File

@@ -390,7 +390,7 @@ exports[`VehicleUpdate Render 1`] = `
class="PrivateSwitchBase-input-0" class="PrivateSwitchBase-input-0"
name="log-level-group" name="log-level-group"
type="radio" type="radio"
value="warn" value="warning"
/> />
<div <div
class="PrivateRadioButtonIcon-root-0" class="PrivateRadioButtonIcon-root-0"
@@ -424,7 +424,7 @@ exports[`VehicleUpdate Render 1`] = `
<span <span
class="MuiTypography-root MuiFormControlLabel-label MuiTypography-body1" class="MuiTypography-root MuiFormControlLabel-label MuiTypography-body1"
> >
Warn Warning
</span> </span>
</label> </label>
<label <label

View File

@@ -208,7 +208,7 @@ const MainForm = () => {
<FormControlLabel value="trace" control={<Radio />} label="Trace" /> <FormControlLabel value="trace" control={<Radio />} label="Trace" />
<FormControlLabel value="debug" control={<Radio />} label="Debug" /> <FormControlLabel value="debug" control={<Radio />} label="Debug" />
<FormControlLabel value="info" control={<Radio />} label="Info" /> <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="error" control={<Radio />} label="Error" />
<FormControlLabel value="critical" control={<Radio />} label="Critical" /> <FormControlLabel value="critical" control={<Radio />} label="Critical" />
</RadioGroup> </RadioGroup>

View File

@@ -1,8 +1,8 @@
import React, { useContext, useState, useEffect } from "react"; import React, {useContext, useState} from "react";
import api from "../../services/vehiclesAPI"; import api from "../../services/vehiclesAPI";
import { useUserContext } from "./UserContext";
import { LocalDateTimeString } from "../../utils/dates"; import { LocalDateTimeString } from "../../utils/dates";
import {useInterval} from "usehooks-ts";
const CANSignalContext = React.createContext(); const CANSignalContext = React.createContext();
@@ -15,41 +15,26 @@ const BlankSignal = (msg) => ({
const transformSignals = (signals) => const transformSignals = (signals) =>
signals signals
.map((signal) => { .map((signal) => {
const { Timestamp, ...Settings } = signal; const { timestamp, value, name } = signal;
const keys = Object.keys(Settings);
return keys.map((key) => ({ return {
timestamp: LocalDateTimeString(Timestamp), timestamp: LocalDateTimeString(timestamp),
signal: key, signal: name,
value: Settings[key], value: value,
})); };
}) })
.flat(); .flat();
export const CANSignalProvider = ({ children }) => { export const CANSignalProvider = ({ token, children }) => {
const {
token: {
idToken: { jwtToken: token },
},
} = useUserContext();
const [vin, setVIN] = useState(null); const [vin, setVIN] = useState(null);
const [signals, setSignals] = useState([]); const [signals, setSignals] = useState([]);
let delay = 500; const [delay, setDelay] = useState(500);
useEffect(() => { useInterval(
getCANSignals(); () => {
// eslint-disable-next-line react-hooks/exhaustive-deps getCANSignals()
}, [vin]); }, vin?delay:null
)
useEffect(() => {
const timer = setTimeout(() => {
getCANSignals();
}, delay);
return () => {
clearTimeout(timer);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [signals]);
const getCANSignals = async () => { const getCANSignals = async () => {
try { try {
@@ -62,10 +47,10 @@ export const CANSignalProvider = ({ children }) => {
const items = transformSignals(result.data); const items = transformSignals(result.data);
if (items.length > 0) { if (items.length > 0) {
delay = 500; setDelay(500);
setSignals(items); setSignals(items);
} else { } else {
delay = 1000; setDelay(1000);
setSignals([BlankSignal("No signals")]); setSignals([BlankSignal("No signals")]);
} }
} catch (e) { } catch (e) {

View 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
},
];

View File

@@ -139,7 +139,7 @@ describe("FleetContext", () => {
onClick={() => onClick={() =>
add({ add({
name: "EU-WEST", name: "EU-WEST",
log_level: "warn", log_level: "warning",
canbus: { enabled: false }, canbus: { enabled: false },
}) })
} }
@@ -219,7 +219,7 @@ describe("FleetContext", () => {
onClick={() => onClick={() =>
update({ update({
name: "EU-WEST", name: "EU-WEST",
log_level: "warn", log_level: "warning",
canbus: { enabled: false }, canbus: { enabled: false },
}) })
} }
@@ -782,7 +782,7 @@ const expectedFleetsData = [
}, },
{ {
name: "US-CENTRAL", name: "US-CENTRAL",
log_level: "warn", log_level: "warning",
canbus: { canbus: {
enabled: false, enabled: false,
data_logger_enabled: false, data_logger_enabled: false,

View File

@@ -31,6 +31,8 @@ export const VehicleProvider = ({ children }) => {
const [vehicle, setVehicle] = useState({}); const [vehicle, setVehicle] = useState({});
const [vehicles, setVehicles] = useState([]); const [vehicles, setVehicles] = useState([]);
const [totalVehicles, setTotalVehicles] = useState(0); const [totalVehicles, setTotalVehicles] = useState(0);
const [fleets, setFleets] = useState([]);
const [totalFleets, setTotalFleets] = useState(0);
const [models, setModels] = useState([]); const [models, setModels] = useState([]);
const [years, setYears] = useState([]); const [years, setYears] = useState([]);
@@ -45,6 +47,7 @@ export const VehicleProvider = ({ children }) => {
} }
cars.forEach((car) => { cars.forEach((car) => {
car.connected = result[car.vin] || false; car.connected = result[car.vin] || false;
car.connectedHMI = result[`2:${car.vin}`] || false;
}); });
} catch (e) { } catch (e) {
logger.error(e.stack); 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 ( return (
<VehicleContext.Provider <VehicleContext.Provider
value={{ value={{
@@ -228,6 +250,8 @@ export const VehicleProvider = ({ children }) => {
vehicle, vehicle,
vehicles, vehicles,
years, years,
fleets,
totalFleets,
addVehicle, addVehicle,
deleteVehicle, deleteVehicle,
getConnections, getConnections,
@@ -241,6 +265,7 @@ export const VehicleProvider = ({ children }) => {
getVehicles, getVehicles,
sendCommand, sendCommand,
updateVehicle, updateVehicle,
getFleets,
}} }}
> >
{children} {children}

View File

@@ -13,19 +13,64 @@ import { StatusProvider, useStatusContext } from "./StatusContext";
const checkVehicleResult = (error, busy, vehicle) => { const checkVehicleResult = (error, busy, vehicle) => {
checkBaseResults(error, busy); checkBaseResults(error, busy);
expect(screen.getByTestId("vehicle").innerHTML).toEqual(vehicle); expect(screen.getByTestId("vehicle").innerHTML).toEqual(vehicle);
} };
const checkVehiclesResult = (error, busy, vehicles) => { const checkVehiclesResult = (error, busy, vehicles) => {
checkBaseResults(error, busy); checkBaseResults(error, busy);
expect(screen.getByTestId("vehicles").innerHTML).toEqual(vehicles); 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) => { const checkBaseResults = (error, busy) => {
expect(screen.getByTestId("error").innerHTML).toEqual(error); expect(screen.getByTestId("error").innerHTML).toEqual(error);
expect(screen.getByTestId("busy").innerHTML).toEqual(busy); expect(screen.getByTestId("busy").innerHTML).toEqual(busy);
}; };
describe("VehicleContext", () => { 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", () => { describe("getVehicles", () => {
beforeEach(() => { beforeEach(() => {
const TestComp = () => { const TestComp = () => {
@@ -195,12 +240,22 @@ describe("VehicleContext", () => {
<> <>
<div data-testid="error">{message}</div> <div data-testid="error">{message}</div>
<div data-testid="busy">{busy.toString()}</div> <div data-testid="busy">{busy.toString()}</div>
<button data-testid="updateVehicleNull" onClick={() => update(null)} /> <button
<button data-testid="updateVehicleNoVIN" onClick={() => update({})} /> data-testid="updateVehicleNull"
onClick={() => update(null)}
/>
<button
data-testid="updateVehicleNoVIN"
onClick={() => update({})}
/>
<button <button
data-testid="updateVehicle" data-testid="updateVehicle"
onClick={() => 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="error">{message}</div>
<div data-testid="busy">{busy.toString()}</div> <div data-testid="busy">{busy.toString()}</div>
<button data-testid="deleteVehicleNull" onClick={() => deleteV(null)} /> <button
<button data-testid="deleteVehicleNonexistent" onClick={() => deleteV("11111111111111111")} /> data-testid="deleteVehicleNull"
onClick={() => deleteV(null)}
/>
<button
data-testid="deleteVehicleNonexistent"
onClick={() => deleteV("11111111111111111")}
/>
<button <button
data-testid="deleteVehicle" data-testid="deleteVehicle"
onClick={() => onClick={() => deleteV("3C4PDCBG0ET127145")}
deleteV("3C4PDCBG0ET127145")
}
/> />
</> </>
); );
@@ -321,7 +380,7 @@ describe("VehicleContext", () => {
describe("sendCommand", () => { describe("sendCommand", () => {
beforeEach(async () => { beforeEach(async () => {
const TestComp = () => { const TestComp = () => {
const {busy, sendCommand} = useVehicleContext(); const { busy, sendCommand } = useVehicleContext();
const { message, setMessage } = useStatusContext(); const { message, setMessage } = useStatusContext();
const sendC = async (vin, command) => { const sendC = async (vin, command) => {
@@ -336,25 +395,38 @@ describe("VehicleContext", () => {
<> <>
<div data-testid="error">{message}</div> <div data-testid="error">{message}</div>
<div data-testid="busy">{busy.toString()}</div> <div data-testid="busy">{busy.toString()}</div>
<button data-testid="sendCommandNullVin" onClick={() => sendC(null, {command:"doors_lock"})} /> <button
<button data-testid="sendCommandNonexistentVin" onClick={() => sendC(["11111111111111111"], {command:"doors_lock"})} /> data-testid="sendCommandNullVin"
onClick={() => sendC(null, { command: "doors_lock" })}
/>
<button
data-testid="sendCommandNonexistentVin"
onClick={() =>
sendC(["11111111111111111"], { command: "doors_lock" })
}
/>
<button <button
data-testid="sendCommandVin" data-testid="sendCommandVin"
onClick={() => sendC("3C4PDCBG0ET127145", {command:"doors_lock"})} onClick={() =>
sendC("3C4PDCBG0ET127145", { command: "doors_lock" })
}
/> />
<button <button
data-testid="sendCommandWrongCommand" data-testid="sendCommandWrongCommand"
onClick={() => sendC("3C4PDCBG0ET127145", {command:"noSuchCommand"})} onClick={() =>
sendC("3C4PDCBG0ET127145", { command: "noSuchCommand" })
}
/> />
</> </>
);}
render(
<StatusProvider>
<VehicleProvider>
<TestComp />
</VehicleProvider>
</StatusProvider>
); );
};
render(
<StatusProvider>
<VehicleProvider>
<TestComp />
</VehicleProvider>
</StatusProvider>
);
}); });
afterEach(() => { afterEach(() => {
@@ -364,24 +436,23 @@ describe("VehicleContext", () => {
it("initial state", () => { it("initial state", () => {
checkBaseResults("", "false"); checkBaseResults("", "false");
}); });
});
})
}); });
const expectedFilters = [ const expectedFilters = [
{ {
can_id: "123-456", can_id: "123-456",
interval: 789 interval: 789,
}, },
{ {
can_id: "1", can_id: "1",
interval: 1000 interval: 1000,
}, },
{ {
can_id: "1000", can_id: "1000",
interval: 1 interval: 1,
} },
] ];
const expectedVehicleData = { const expectedVehicleData = {
vin: "3C4PDCBG0ET127145", vin: "3C4PDCBG0ET127145",
@@ -390,9 +461,16 @@ const expectedVehicleData = {
trim: "Basic", trim: "Basic",
ecu_list: "ECUA 2.0.0, ECUB 2.1.1", ecu_list: "ECUA 2.0.0, ECUB 2.1.1",
log_level: "info", 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, connected: true,
} connectedHMI: false,
};
const expectedVehiclesData = [ const expectedVehiclesData = [
{ {
@@ -402,19 +480,44 @@ const expectedVehiclesData = [
trim: "Basic", trim: "Basic",
ecu_list: "ECUA 2.0.0, ECUB 2.1.1", ecu_list: "ECUA 2.0.0, ECUB 2.1.1",
log_level: "info", 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, 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", vin: "KNADM4A39C6028108",
year: 2021, year: 2021,
model: "Ocean", model: "Ocean",
trim: "Basic", trim: "Basic",
connected: true, connected: true,
connectedHMI: false,
}, },
{ {
vin: "1G11C5SL9FF153507", vin: "1G11C5SL9FF153507",
@@ -422,5 +525,6 @@ const expectedVehiclesData = [
model: "Ocean", model: "Ocean",
trim: "Basic", trim: "Basic",
connected: true, connected: true,
connectedHMI: false,
}, },
]; ];

View File

@@ -28,7 +28,7 @@ let fleets = [
}, },
{ {
name: "US-CENTRAL", 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 }, canbus: { enabled: false, data_logger_enabled: false, max_mem_buffer_size: 0, max_disk_buffer_size: 0 },
vehicles: ["USCENTVIN12345678", "USCENTVIN12345679", "USCENTVIN12345670"] vehicles: ["USCENTVIN12345678", "USCENTVIN12345679", "USCENTVIN12345670"]
}, },

View File

@@ -35,6 +35,7 @@ let vehicle = {
let vehicleState = { let vehicleState = {
data: { data: {
online: false, online: false,
online_hmi: true,
battery: { battery: {
percent: 95, percent: 95,
}, },
@@ -77,6 +78,8 @@ let vehicles = [];
let models = ["Ocean", "PEAR"]; let models = ["Ocean", "PEAR"];
let years = [2023, 2024]; let years = [2023, 2024];
let totalVehicles = 0; let totalVehicles = 0;
let fleets = ["fleet1", "fleet2"];
let totalFleets = 2;
let error = null; let error = null;
export const VehicleProvider = ({ children }) => { export const VehicleProvider = ({ children }) => {
@@ -86,6 +89,8 @@ export const VehicleProvider = ({ children }) => {
export const useVehicleContext = () => ({ export const useVehicleContext = () => ({
busy, busy,
models, models,
fleets,
totalFleets,
totalVehicles, totalVehicles,
vehicle, vehicle,
vehicles, vehicles,
@@ -141,6 +146,10 @@ export const useVehicleContext = () => ({
command, command,
parameters, parameters,
})), })),
getFleets: jest.fn((vin, search,_token) => {return {
data: ["fleet1", "fleet2"],
total: 2,
}}),
}); });
export const setBusy = (val) => { export const setBusy = (val) => {

View File

@@ -19,7 +19,7 @@ import TableHeaderSortable from "../../Table/HeaderSortable";
import { logger } from "../../../services/monitoring"; import { logger } from "../../../services/monitoring";
import ConnectedIcon from "../../Controls/ConnectedIcon"; import ConnectedIcon from "../../Controls/ConnectedIcon";
import ECUList from "../../Controls/ECUList"; import ECUList from "../../Controls/ECUList";
import {useLocalStorage} from "../../useLocalStorage"; import { useLocalStorage } from "../../useLocalStorage";
const tableColumns = [ const tableColumns = [
{ {
@@ -163,7 +163,8 @@ const CarSelectionTable = (props) => {
<TableCell align="center"> <TableCell align="center">
<ConnectedIcon <ConnectedIcon
connected={row.connected} connected={row.connected}
style={{ marginRight: 5 }} connectedHMI={row.connectedHMI}
style={{ marginRight: 3 }}
/> />
<Link to={`/vehicle-status/${row.vin}`}>{row.vin}</Link> <Link to={`/vehicle-status/${row.vin}`}>{row.vin}</Link>
{row.ecu_list && ( {row.ecu_list && (

View File

@@ -1,5 +1,6 @@
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { import {
LinearProgress,
Table, Table,
TableBody, TableBody,
TableCell, TableCell,
@@ -57,14 +58,21 @@ const MainForm = ({ vin, token }) => {
const [pageIndex, setPageIndex] = useState(0); const [pageIndex, setPageIndex] = useState(0);
const [orderBy, setOrderBy] = useState("id"); const [orderBy, setOrderBy] = useState("id");
const [order, setOrder] = useState("desc"); const [order, setOrder] = useState("desc");
const { cancelUpdate, getCarUpdates, carUpdates, totalCarUpdates } = const {
useCarUpdatesContext(); cancelUpdate,
getCarUpdates,
carUpdates,
totalCarUpdates,
startMonitor,
stopMonitor,
} = useCarUpdatesContext();
const { setMessage } = useStatusContext(); const { setMessage } = useStatusContext();
useEffect(() => { useEffect(() => {
(async () => { (async () => {
try { try {
if (!vin || !token) return; if (!vin || !token) return;
stopMonitor();
await getCarUpdates( await getCarUpdates(
{ {
vin, vin,
@@ -82,6 +90,20 @@ const MainForm = ({ vin, token }) => {
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [vin, token, pageIndex, pageSize, orderBy, order]); }, [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) => { const handleChangePageIndex = (event, newIndex) => {
setPageIndex(newIndex); setPageIndex(newIndex);
}; };
@@ -141,7 +163,12 @@ const MainForm = ({ vin, token }) => {
{updateName(row)} {updateName(row)}
</Link> </Link>
</TableCell> </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"> <TableCell align="center">
{LocalDateTimeString(row.created)} {LocalDateTimeString(row.created)}
</TableCell> </TableCell>

View File

@@ -1,12 +1,27 @@
import React from "react"; import React from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import CheckCircleIcon from "@material-ui/icons/CheckCircle"; import CheckCircleIcon from "@material-ui/icons/CheckCircle";
import CheckBoxIcon from "@material-ui/icons/CheckBox";
import { Tooltip } from "@material-ui/core";
const ConnectedIcon = (props) => { const ConnectedIcon = (props) => {
if (props.connected) { if (props.connected || props.connectedHMI) {
return ( return (
<span style={props.style}> <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> </span>
); );
} }

View File

@@ -1,39 +1,9 @@
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import {FormControl} from "@material-ui/core"; import { FormControl } from "@material-ui/core";
import useStyles from "../../useStyles"; 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) => { export const Parameters = (props) => {
const {params} = props; const { params } = props;
const classes = useStyles(); const classes = useStyles();
@@ -41,33 +11,19 @@ export const Parameters = (props) => {
return null; return null;
} }
const {data, handleDataChange} = props; const { data, handleDataChange } = props;
const {startDate, handleStartChange} = props;
const {endDate, handleEndChange} = props;
return ( return (
<FormControl size="small" className={classes.formControl}> <FormControl size="small" className={classes.formControl}>
<div style={{width: "300px", marginTop: "1em"}}> <div style={{ width: "300px", marginTop: "1em" }}>
{params.dataFunc(data, handleDataChange)} {params.dataFunc(data, handleDataChange)}
</div> </div>
<Dates
startDateFunc={params.startDateFunc}
endDateFunc={params.endDateFunc}
startDate={startDate}
handleStartChange={handleStartChange}
endDate={endDate}
handleEndChange={handleEndChange}
></Dates>
</FormControl> </FormControl>
) );
} };
Parameters.propTypes = { Parameters.propTypes = {
params: PropTypes.any, params: PropTypes.any,
data: PropTypes.any, data: PropTypes.any,
handleDataChange: PropTypes.func, handleDataChange: PropTypes.func,
startDate: PropTypes.instanceOf(Date), };
handleStartChange: PropTypes.func,
endDate: PropTypes.instanceOf(Date),
handleEndChange: PropTypes.func
};

View File

@@ -1,140 +1,52 @@
jest.mock("@material-ui/pickers/MuiPickersUtilsProvider") import { render, waitFor } from "@testing-library/react";
import { Parameters } from "./Parameters";
import {render, waitFor} from "@testing-library/react";
import {Dates, Parameters} from "./Parameters";
import addSnapshotSerializer from "../../../utils/snapshot"; 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 = { const renderState = {
EMPTY: 0, EMPTY: 0,
JUST_DATA: 1, JUST_DATA: 1,
WITH_DATE: 2, };
}
const renderParameters = async (rs) => { const renderParameters = async (rs) => {
await waitFor(() => { await waitFor(() => {
/* render */ /* render */
}); });
if (rs===renderState.EMPTY) { if (rs === renderState.EMPTY) {
const { container } = render(<Parameters/>) const { container } = render(<Parameters />);
return container; return container;
} }
const params = {dataFunc:(val, handleValChange) => <div>val.toString()</div>} const params = {
const [data, handleDataChange] = [true, (_)=>{}]; dataFunc: (val, handleValChange) => <div>val.toString()</div>,
};
const [data, handleDataChange] = [true, (_) => {}];
if (rs===renderState.JUST_DATA) { if (rs === renderState.JUST_DATA) {
const { container } = render(<Parameters const { container } = render(
params={params} <Parameters
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} params={params}
data={data} data={data}
handleDataChange={handleDataChange} handleDataChange={handleDataChange}
start={start}
handleStartChange={handleStartChange}
end={end}
handleEndChange={handleEndChange}
/> />
) );
return container return container;
} }
} };
describe("Params", () => { describe("Params", () => {
beforeAll(() => { beforeAll(() => {
addSnapshotSerializer(expect); addSnapshotSerializer(expect);
}) });
it("Render empty", async () => { it("Render empty", async () => {
const container = await renderParameters(renderState.EMPTY); const container = await renderParameters(renderState.EMPTY);
expect(container).toMatchSnapshot(); expect(container).toMatchSnapshot();
}) });
it("Render just data", async () => { it("Render just data", async () => {
const container = await renderDates(renderState.JUST_DATA); const container = await renderParameters(renderState.JUST_DATA);
expect(container).toMatchSnapshot(); expect(container).toMatchSnapshot();
}) });
});
it("Render with date", async () => {
const container = await renderDates(renderState.WITH_DATE);
expect(container).toMatchSnapshot();
})
})

View File

@@ -1,24 +1,19 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // 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>
<div> <div
<div> class="MuiFormControl-root makeStyles-formControl-0"
1318258080000 >
</div> <div
<div> style="width: 300px; margin-top: 1em;"
1318258080000 >
, <div>
1318258080000 val.toString()
</div>
</div> </div>
</div> </div>
</div> </div>
`; `;
exports[`Params Render empty 1`] = `<div />`;
exports[`Params Render just data 1`] = `<div />`;
exports[`Params Render with date 1`] = `<div />`;

View File

@@ -248,7 +248,7 @@ exports[`FleetAddForm Render 1`] = `
class="PrivateSwitchBase-input-0" class="PrivateSwitchBase-input-0"
name="log-level-group" name="log-level-group"
type="radio" type="radio"
value="warn" value="warning"
/> />
<div <div
class="PrivateRadioButtonIcon-root-0" class="PrivateRadioButtonIcon-root-0"
@@ -282,7 +282,7 @@ exports[`FleetAddForm Render 1`] = `
<span <span
class="MuiTypography-root MuiFormControlLabel-label MuiTypography-body1" class="MuiTypography-root MuiFormControlLabel-label MuiTypography-body1"
> >
Warn Warning
</span> </span>
</label> </label>
<label <label

View File

@@ -129,7 +129,7 @@ const MainForm = () => {
<FormControlLabel value="trace" control={<Radio />} label="Trace" /> <FormControlLabel value="trace" control={<Radio />} label="Trace" />
<FormControlLabel value="debug" control={<Radio />} label="Debug" /> <FormControlLabel value="debug" control={<Radio />} label="Debug" />
<FormControlLabel value="info" control={<Radio />} label="Info" /> <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="error" control={<Radio />} label="Error" />
<FormControlLabel value="critical" control={<Radio />} label="Critical" /> <FormControlLabel value="critical" control={<Radio />} label="Critical" />
</RadioGroup> </RadioGroup>

View File

@@ -153,7 +153,7 @@ exports[`FleetVehiclesTable Render 1`] = `
<td <td
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter" class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
> >
<div> <span>
<a <a
class="" class=""
href="/" href="/"
@@ -172,7 +172,7 @@ exports[`FleetVehiclesTable Render 1`] = `
</svg> </svg>
</a> </a>
<div /> <div />
</div> </span>
</td> </td>
</tr> </tr>
<tr <tr
@@ -190,7 +190,7 @@ exports[`FleetVehiclesTable Render 1`] = `
<td <td
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter" class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
> >
<div> <span>
<a <a
class="" class=""
href="/" href="/"
@@ -209,7 +209,7 @@ exports[`FleetVehiclesTable Render 1`] = `
</svg> </svg>
</a> </a>
<div /> <div />
</div> </span>
</td> </td>
</tr> </tr>
<tr <tr
@@ -227,7 +227,7 @@ exports[`FleetVehiclesTable Render 1`] = `
<td <td
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter" class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
> >
<div> <span>
<a <a
class="" class=""
href="/" href="/"
@@ -246,7 +246,7 @@ exports[`FleetVehiclesTable Render 1`] = `
</svg> </svg>
</a> </a>
<div /> <div />
</div> </span>
</td> </td>
</tr> </tr>
</tbody> </tbody>

View File

@@ -147,7 +147,7 @@ const MainForm = ({ name }) => {
); );
} else { } else {
return ( return (
<div> <span key={`delete-${action.id}-of-div`}>
<Tooltip key={`delete-${action.id}`} title={action.tip}> <Tooltip key={`delete-${action.id}`} title={action.tip}>
<Link to="#" onClick={() => onDelete(action.id)}> <Link to="#" onClick={() => onDelete(action.id)}>
{action.icon} {action.icon}
@@ -159,7 +159,7 @@ const MainForm = ({ name }) => {
close={() => setShowDeleteModal(false)} close={() => setShowDeleteModal(false)}
deleteFunction={() => onDelete(action.id)} deleteFunction={() => onDelete(action.id)}
/> />
</div> </span>
); );
} }
}); });

View File

@@ -152,7 +152,7 @@ exports[`VehiclesTab Render 1`] = `
<td <td
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter" class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
> >
<div> <span>
<a <a
class="" class=""
href="/" href="/"
@@ -171,7 +171,7 @@ exports[`VehiclesTab Render 1`] = `
</svg> </svg>
</a> </a>
<div /> <div />
</div> </span>
</td> </td>
</tr> </tr>
<tr <tr
@@ -189,7 +189,7 @@ exports[`VehiclesTab Render 1`] = `
<td <td
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter" class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
> >
<div> <span>
<a <a
class="" class=""
href="/" href="/"
@@ -208,7 +208,7 @@ exports[`VehiclesTab Render 1`] = `
</svg> </svg>
</a> </a>
<div /> <div />
</div> </span>
</td> </td>
</tr> </tr>
<tr <tr
@@ -226,7 +226,7 @@ exports[`VehiclesTab Render 1`] = `
<td <td
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter" class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
> >
<div> <span>
<a <a
class="" class=""
href="/" href="/"
@@ -245,7 +245,7 @@ exports[`VehiclesTab Render 1`] = `
</svg> </svg>
</a> </a>
<div /> <div />
</div> </span>
</td> </td>
</tr> </tr>
</tbody> </tbody>

View File

@@ -304,7 +304,7 @@ exports[`FleetTable Render 1`] = `
<td <td
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter" class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
> >
warn warning
</td> </td>
<td <td
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter" class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"

View File

@@ -248,7 +248,7 @@ exports[`FleetUpdate Render 1`] = `
class="PrivateSwitchBase-input-0" class="PrivateSwitchBase-input-0"
name="log-level-group" name="log-level-group"
type="radio" type="radio"
value="warn" value="warning"
/> />
<div <div
class="PrivateRadioButtonIcon-root-0" class="PrivateRadioButtonIcon-root-0"
@@ -282,7 +282,7 @@ exports[`FleetUpdate Render 1`] = `
<span <span
class="MuiTypography-root MuiFormControlLabel-label MuiTypography-body1" class="MuiTypography-root MuiFormControlLabel-label MuiTypography-body1"
> >
Warn Warning
</span> </span>
</label> </label>
<label <label

View File

@@ -9,30 +9,30 @@ import {
FormLabel, FormLabel,
Radio, Radio,
RadioGroup, RadioGroup,
TextField TextField,
} from "@material-ui/core"; } from "@material-ui/core";
import useStyles from "../../useStyles"; import useStyles from "../../useStyles";
import { import { useFleetContext, FleetProvider } from "../../Contexts/FleetContext";
useFleetContext,
FleetProvider
} from "../../Contexts/FleetContext";
import { useStatusContext } from "../../Contexts/StatusContext"; import { useStatusContext } from "../../Contexts/StatusContext";
import { useUserContext } from "../../Contexts/UserContext"; import { useUserContext } from "../../Contexts/UserContext";
import { logger } from "../../../services/monitoring"; import { logger } from "../../../services/monitoring";
const MainForm = () => { const MainForm = () => {
const queries = new URLSearchParams(useLocation().search); const queries = new URLSearchParams(useLocation().search);
const { fleet, getFleet, updateFleet, busy } = useFleetContext(); const { fleet, getFleet, updateFleet, busy } = useFleetContext();
const { token: { idToken: { jwtToken: token } } } = useUserContext(); const {
token: {
idToken: { jwtToken: token },
},
} = useUserContext();
const { setMessage, setTitle, setSitePath } = useStatusContext(); const { setMessage, setTitle, setSitePath } = useStatusContext();
const [redirect, setRedirect] = useState(null); const [redirect, setRedirect] = useState(null);
const classes = useStyles(); const classes = useStyles();
const [name, setName] = useState(queries.get("name") ?? ""); const [name, setName] = useState(queries.get("name") ?? "");
const [oldName, ] = useState(name); const [oldName] = useState(name);
const [selectedLogLevel, setSelectedLogLevel] = useState("info"); const [selectedLogLevel, setSelectedLogLevel] = useState("info");
const [canbusEnabled, setCANBusEnabled] = useState(true); const [canbusEnabled, setCANBusEnabled] = useState(true);
const [dataLoggerEnabled, setDataLoggerEnabled] = useState(false); const [dataLoggerEnabled, setDataLoggerEnabled] = useState(false);
@@ -71,36 +71,40 @@ const MainForm = () => {
if (fleet.canbus) { if (fleet.canbus) {
setCANBusEnabled(fleet.canbus.enabled ?? canbusEnabled); 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); 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 // eslint-disable-next-line react-hooks/exhaustive-deps
}, [fleet]); }, [fleet]);
const onNameChange = (event) => { const onNameChange = (event) => {
setName(event.target.value); setName(event.target.value);
} };
const onLogLevelChange = (event) => { const onLogLevelChange = (event) => {
setSelectedLogLevel(event.target.value); setSelectedLogLevel(event.target.value);
} };
const onCANBusChange = (event) => { const onCANBusChange = (event) => {
setCANBusEnabled(event.target.checked); setCANBusEnabled(event.target.checked);
} };
const onDataLoggerChange = (event) => { const onDataLoggerChange = (event) => {
setDataLoggerEnabled(event.target.checked); setDataLoggerEnabled(event.target.checked);
} };
const onMaxMemBufferSizeChange = (event) => { const onMaxMemBufferSizeChange = (event) => {
setMaxMemBufferSize(event.target.value); setMaxMemBufferSize(event.target.value);
} };
const onMaxDiskBufferSizeChange = (event) => { const onMaxDiskBufferSizeChange = (event) => {
setMaxDiskBufferSize(event.target.value); setMaxDiskBufferSize(event.target.value);
} };
const onSubmit = async (event) => { const onSubmit = async (event) => {
try { try {
@@ -112,12 +116,13 @@ const MainForm = () => {
enabled: canbusEnabled, enabled: canbusEnabled,
data_logger_enabled: canbusEnabled ? dataLoggerEnabled : false, data_logger_enabled: canbusEnabled ? dataLoggerEnabled : false,
max_mem_buffer_size: canbusEnabled ? parseInt(maxMemBufferSize) : 0, 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); const result = await updateFleet(oldName, formData, token);
if (!result || result.error) return; if (!result || result.error) return;
@@ -162,22 +167,30 @@ const MainForm = () => {
<FormControlLabel value="trace" control={<Radio />} label="Trace" /> <FormControlLabel value="trace" control={<Radio />} label="Trace" />
<FormControlLabel value="debug" control={<Radio />} label="Debug" /> <FormControlLabel value="debug" control={<Radio />} label="Debug" />
<FormControlLabel value="info" control={<Radio />} label="Info" /> <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="error" control={<Radio />} label="Error" />
<FormControlLabel value="critical" control={<Radio />} label="Critical" /> <FormControlLabel
value="critical"
control={<Radio />}
label="Critical"
/>
</RadioGroup> </RadioGroup>
<FormLabel id="demo-row-radio-buttons-group-label">CAN Bus</FormLabel> <FormLabel id="demo-row-radio-buttons-group-label">CAN Bus</FormLabel>
<FormGroup> <FormGroup>
<FormControlLabel control={ <FormControlLabel
<Checkbox control={
checked={canbusEnabled} <Checkbox checked={canbusEnabled} onChange={onCANBusChange} />
onChange={onCANBusChange} }
/> label="CAN Bus Enabled"
} label="CAN Bus Enabled" /> />
<TextField <TextField
id="max_mem_buffer_size" id="max_mem_buffer_size"
name="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} value={maxMemBufferSize}
onChange={onMaxMemBufferSizeChange} onChange={onMaxMemBufferSizeChange}
variant="outlined" variant="outlined"
@@ -190,18 +203,21 @@ const MainForm = () => {
required required
fullWidth fullWidth
/> />
<FormControlLabel control={ <FormControlLabel
<Checkbox control={
checked={dataLoggerEnabled} <Checkbox
onChange={onDataLoggerChange} checked={dataLoggerEnabled}
disabled={!canbusEnabled} onChange={onDataLoggerChange}
/> disabled={!canbusEnabled}
} label="Data Logger Enabled" /> />
}
label="Data Logger Enabled"
/>
</FormGroup> </FormGroup>
<TextField <TextField
id="max_disk_buffer_size" id="max_disk_buffer_size"
name="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} value={maxDiskBufferSize}
onChange={onMaxDiskBufferSizeChange} onChange={onMaxDiskBufferSizeChange}
variant="outlined" variant="outlined"
@@ -236,4 +252,4 @@ const FleetUpdateForm = (props) => (
</FleetProvider> </FleetProvider>
); );
export default FleetUpdateForm; export default FleetUpdateForm;

View File

@@ -68,7 +68,11 @@ const MainForm = () => {
const [orderBy, setOrderBy] = useState("id"); const [orderBy, setOrderBy] = useState("id");
const [order, setOrder] = useState("asc"); const [order, setOrder] = useState("asc");
const [search, setSearch] = useState(""); const [search, setSearch] = useState("");
const [showDeleteModal, setShowDeleteModal] = useState(false); const [showDeleteModal, setShowDeleteModal] = useState(false);
const [deleteId, setDeleteId] = useState("");
const [deleteRowName, setDeleteRowName] = useState("");
const { getManifests, deleteManifest, manifests, totalManifests } = const { getManifests, deleteManifest, manifests, totalManifests } =
useManifestsContext(); useManifestsContext();
const { setMessage, setTitle, setSitePath } = useStatusContext(); const { setMessage, setTitle, setSitePath } = useStatusContext();
@@ -133,6 +137,12 @@ const MainForm = () => {
setSearch(query); setSearch(query);
}; };
const setDeletePopup = (id, row) => {
setDeleteId(id);
setDeleteRowName(`${row.name} ${row.version}`);
setShowDeleteModal(true);
};
const onDelete = async (manifest_id) => { const onDelete = async (manifest_id) => {
try { try {
await deleteManifest(parseInt(manifest_id), token); await deleteManifest(parseInt(manifest_id), token);
@@ -180,19 +190,13 @@ const MainForm = () => {
); );
} else { } else {
return ( return (
<div> <span key={`delete-${action.id}-of-key`}>
<Tooltip key={`delete-${action.id}`} title={action.tip}> <Tooltip key={`delete-${action.id}`} title={action.tip}>
<Link to="#" onClick={() => onDelete(action.id)}> <Link to="#" onClick={() => setDeletePopup(action.id, row)}>
{action.icon} {action.icon}
</Link> </Link>
</Tooltip> </Tooltip>
<DeleteConfirmation </span>
message={action.id}
open={showDeleteModal}
close={() => setShowDeleteModal(false)}
deleteFunction={() => onDelete(action.id)}
/>
</div>
); );
} }
}); });
@@ -261,6 +265,12 @@ const MainForm = () => {
</TableRow> </TableRow>
</TableFooter> </TableFooter>
</Table> </Table>
<DeleteConfirmation
message={deleteRowName}
open={showDeleteModal}
close={() => setShowDeleteModal(false)}
deleteFunction={() => onDelete(deleteId)}
/>
</div> </div>
); );
}; };

View File

@@ -74,12 +74,16 @@ const Component = () => {
useEffect(() => { useEffect(() => {
if (!token) return; if (!token) return;
if (markers.length > 0) {
const vins = markers.map((marker) => marker[2]); const vins = markers
getConnections(vins, token).then((conns) => { .filter((marker) => marker[2].length > 0)
setConnections(conns); .map((marker) => marker[2]);
});
} if (vins.length === 0) return;
getConnections(vins, token).then((conns) => {
setConnections(conns);
});
// eslint-disable-next-line // eslint-disable-next-line
}, [markers, token]); }, [markers, token]);
@@ -151,7 +155,6 @@ const Component = () => {
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/> />
<CenterFocus center={center} zoom={zoom} /> <CenterFocus center={center} zoom={zoom} />
{markers.map((marker) => ( {markers.map((marker) => (
<Marker <Marker
icon={getCarIcon(marker[2])} icon={getCarIcon(marker[2])}
@@ -184,6 +187,7 @@ const Component = () => {
key={carState.vin} key={carState.vin}
vin={carState.vin} vin={carState.vin}
online={carState.online} online={carState.online}
onlineHMI={carState.online_hmi}
battery={carState.battery} battery={carState.battery}
doors={carState.doors} doors={carState.doors}
location={carState.location} location={carState.location}

View File

@@ -13,7 +13,7 @@ import CANSignals from "../Cars/CANSignals";
const VehiclePopUp = (props) => { const VehiclePopUp = (props) => {
const classes = useStyles(); const classes = useStyles();
const [viewCAN, setViewCAN] = useState(false); const [viewCAN, setViewCAN] = useState(false);
const { vin, online, onClose } = props; const { vin, online, onClose, onlineHMI } = props;
const toggleView = (e) => { const toggleView = (e) => {
e.preventDefault(); e.preventDefault();
@@ -43,6 +43,9 @@ const VehiclePopUp = (props) => {
<p> <p>
<b>Connected</b>: {online.toString()} <b>Connected</b>: {online.toString()}
</p> </p>
<p>
<b>ICC Connected</b>: {onlineHMI?.toString()}
</p>
{viewCAN && <CANSignals vin={vin} />} {viewCAN && <CANSignals vin={vin} />}
{!viewCAN && <DigitalTwin {...props} />} {!viewCAN && <DigitalTwin {...props} />}
</div> </div>

View File

@@ -22,7 +22,7 @@ const fleets = [
}, },
{ {
name: "US-CENTRAL", 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 }, canbus: { enabled: false, data_logger_enabled: false, max_mem_buffer_size: 0, max_disk_buffer_size: 0 },
vehicles: ["USCENTVIN12345678", "USCENTVIN12345679", "USCENTVIN12345670"] vehicles: ["USCENTVIN12345678", "USCENTVIN12345679", "USCENTVIN12345670"]
}, },

View File

@@ -50,6 +50,14 @@ const ecusData = [
}, },
]; ];
const signals = {data:[
{
timestamp: "2021-07-14T20:09:40.98187Z",
name: "signal",
value: 123
},
]};
const vehiclesAPI = { const vehiclesAPI = {
addVehicle: async (vehicle) => { addVehicle: async (vehicle) => {
data.push(vehicle); data.push(vehicle);
@@ -89,6 +97,7 @@ const vehiclesAPI = {
getVehicles: async () => { getVehicles: async () => {
return { data }; return { data };
}, },
getFleets: async (vin) => {return { data: ["fleet1", "fleet2"]}},
getYears: async () => { getYears: async () => {
return { return {
data: [2021, 2022], data: [2021, 2022],
@@ -104,6 +113,9 @@ const vehiclesAPI = {
const index = data.findIndex(element => element.vin === vin); const index = data.findIndex(element => element.vin === vin);
if (index >= 0) data[index] = vehicle; if (index >= 0) data[index] = vehicle;
return vehicle; return vehicle;
},
getCANSignals: async (vin, vehicle) => {
return signals;
} }
}; };

View File

@@ -114,6 +114,19 @@ const vehiclesAPI = {
.catch(errorHandler); .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) => getYears: async (token) =>
fetch(`${API_ENDPOINT}/vehicleyears`, { fetch(`${API_ENDPOINT}/vehicleyears`, {
method: "GET", method: "GET",