Merge branch 'develop'

This commit is contained in:
jwu-fisker
2022-06-07 07:48:36 -07:00
26 changed files with 17792 additions and 425 deletions

View File

@@ -2,4 +2,4 @@ REACT_APP_CERT_SERVICE_URL=https://dev-gw.cloud.fiskerinc.com/certificate
REACT_APP_AUTH_SERVICE_URL=https://dev-gw.cloud.fiskerinc.com/compute_auth REACT_APP_AUTH_SERVICE_URL=https://dev-gw.cloud.fiskerinc.com/compute_auth
REACT_APP_UPLOAD_SERVICE_URL=https://dev-gw.cloud.fiskerinc.com/ota_update REACT_APP_UPLOAD_SERVICE_URL=https://dev-gw.cloud.fiskerinc.com/ota_update
REACT_APP_AUTH_CALLBACK_URL=https://dev-ota-admin.cloud.fiskerinc.com REACT_APP_AUTH_CALLBACK_URL=https://dev-ota-admin.cloud.fiskerinc.com
REACT_APP_SUPERSET_URL=http://superset-dev.fiskercloud.internal REACT_APP_SUPERSET_URL=https://dev-superset.cloud.fiskerinc.com

View File

@@ -2,4 +2,4 @@ REACT_APP_AUTH_SERVICE_URL=http://localhost/compute_auth
REACT_APP_CERT_SERVICE_URL=http://localhost/certificate REACT_APP_CERT_SERVICE_URL=http://localhost/certificate
REACT_APP_UPLOAD_SERVICE_URL=http://localhost/ota_update REACT_APP_UPLOAD_SERVICE_URL=http://localhost/ota_update
REACT_APP_AUTH_CALLBACK_URL=http://localhost:3000 REACT_APP_AUTH_CALLBACK_URL=http://localhost:3000
REACT_APP_SUPERSET_URL=http://superset-dev.fiskercloud.internal REACT_APP_SUPERSET_URL=https://dev-superset.cloud.fiskerinc.com

View File

@@ -2,4 +2,4 @@ REACT_APP_AUTH_SERVICE_URL=https://gw.cloud.fiskerinc.com/compute_auth
REACT_APP_CERT_SERVICE_URL=https://gw.cloud.fiskerinc.com/certificate REACT_APP_CERT_SERVICE_URL=https://gw.cloud.fiskerinc.com/certificate
REACT_APP_UPLOAD_SERVICE_URL=https://gw.cloud.fiskerinc.com/ota_update REACT_APP_UPLOAD_SERVICE_URL=https://gw.cloud.fiskerinc.com/ota_update
REACT_APP_AUTH_CALLBACK_URL=https://ota-admin.cloud.fiskerinc.com REACT_APP_AUTH_CALLBACK_URL=https://ota-admin.cloud.fiskerinc.com
REACT_APP_SUPERSET_URL=http://superset.fiskercloud.internal REACT_APP_SUPERSET_URL=https://superset.cloud.fiskerinc.com

View File

@@ -2,4 +2,4 @@ REACT_APP_AUTH_SERVICE_URL=https://stg-gw.cloud.fiskerinc.com/compute_auth
REACT_APP_CERT_SERVICE_URL=https://stg-gw.cloud.fiskerinc.com/certificate REACT_APP_CERT_SERVICE_URL=https://stg-gw.cloud.fiskerinc.com/certificate
REACT_APP_UPLOAD_SERVICE_URL=https://stg-gw.cloud.fiskerinc.com/ota_update REACT_APP_UPLOAD_SERVICE_URL=https://stg-gw.cloud.fiskerinc.com/ota_update
REACT_APP_AUTH_CALLBACK_URL=https://stg-ota-admin.cloud.fiskerinc.com REACT_APP_AUTH_CALLBACK_URL=https://stg-ota-admin.cloud.fiskerinc.com
REACT_APP_SUPERSET_URL=http://superset-stg.fiskercloud.internal REACT_APP_SUPERSET_URL=https://stg-superset.cloud.fiskerinc.com

View File

@@ -1,5 +1,5 @@
REACT_APP_AUTH_SERVICE_URL=http://localhost/compute_auth REACT_APP_AUTH_SERVICE_URL=http://localhost/compute_auth
REACT_APP_UPLOAD_SERVICE_URL=http://localhost/ota_update REACT_APP_UPLOAD_SERVICE_URL=http://localhost/ota_update
REACT_APP_AUTH_CALLBACK_URL=http://localhost:3000 REACT_APP_AUTH_CALLBACK_URL=http://localhost:3000
REACT_APP_SUPERSET_URL=http://superset-dev.fisker.internal REACT_APP_SUPERSET_URL=https://dev-superset.cloud.fiskerinc.com
REACT_APP_CERT_SERVICE_URL=http://localhost/certificate REACT_APP_CERT_SERVICE_URL=http://localhost/certificate

View File

@@ -1,6 +1,10 @@
name: Node.js CI name: Node.js CI
on: [pull_request] on:
push:
branches:
- develop
pull_request:
jobs: jobs:
build: build:
@@ -14,7 +18,7 @@ jobs:
- name: Use Node.js ${{ matrix.node-version }} - name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v2 uses: actions/setup-node@v2
with: with:
node-version: "14" node-version: "16"
cache: "npm" cache: "npm"
- run: npm install - run: npm install
- run: npm run build --if-present - run: npm run build --if-present

View File

@@ -6,7 +6,7 @@ Front-end web application for administrating services
Running locally Running locally
1. Install Node 14 1. Install Node 16
2. Run `npm install` 2. Run `npm install`
3. Copy .env.template to .env and edit the service urls for authentication and api services 3. Copy .env.template to .env and edit the service urls for authentication and api services
4. Run `./run.sh` from the terminal 4. Run `./run.sh` from the terminal

17453
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -213,7 +213,7 @@ exports[`App Route / authenticated 1`] = `
<a <a
aria-disabled="false" aria-disabled="false"
class="MuiTypography-root MuiLink-root MuiLink-underlineHover MuiButtonBase-root MuiListItem-root makeStyles-menuExternalLink-0000 MuiListItem-gutters MuiListItem-button MuiTypography-colorPrimary" class="MuiTypography-root MuiLink-root MuiLink-underlineHover MuiButtonBase-root MuiListItem-root makeStyles-menuExternalLink-0000 MuiListItem-gutters MuiListItem-button MuiTypography-colorPrimary"
href="http://superset-dev.fiskercloud.internal/r/3" href="https://dev-superset.cloud.fiskerinc.com/r/3"
rel="noopener" rel="noopener"
role="button" role="button"
tabindex="0" tabindex="0"
@@ -711,7 +711,7 @@ exports[`App Route /home authenticated 1`] = `
<a <a
aria-disabled="false" aria-disabled="false"
class="MuiTypography-root MuiLink-root MuiLink-underlineHover MuiButtonBase-root MuiListItem-root makeStyles-menuExternalLink-0000 MuiListItem-gutters MuiListItem-button MuiTypography-colorPrimary" class="MuiTypography-root MuiLink-root MuiLink-underlineHover MuiButtonBase-root MuiListItem-root makeStyles-menuExternalLink-0000 MuiListItem-gutters MuiListItem-button MuiTypography-colorPrimary"
href="http://superset-dev.fiskercloud.internal/r/3" href="https://dev-superset.cloud.fiskerinc.com/r/3"
rel="noopener" rel="noopener"
role="button" role="button"
tabindex="0" tabindex="0"
@@ -1245,7 +1245,7 @@ exports[`App Route /package-create authenticated 1`] = `
<a <a
aria-disabled="false" aria-disabled="false"
class="MuiTypography-root MuiLink-root MuiLink-underlineHover MuiButtonBase-root MuiListItem-root makeStyles-menuExternalLink-0000 MuiListItem-gutters MuiListItem-button MuiTypography-colorPrimary" class="MuiTypography-root MuiLink-root MuiLink-underlineHover MuiButtonBase-root MuiListItem-root makeStyles-menuExternalLink-0000 MuiListItem-gutters MuiListItem-button MuiTypography-colorPrimary"
href="http://superset-dev.fiskercloud.internal/r/3" href="https://dev-superset.cloud.fiskerinc.com/r/3"
rel="noopener" rel="noopener"
role="button" role="button"
tabindex="0" tabindex="0"
@@ -2742,7 +2742,7 @@ exports[`App Route /package-deploy authenticated 1`] = `
<a <a
aria-disabled="false" aria-disabled="false"
class="MuiTypography-root MuiLink-root MuiLink-underlineHover MuiButtonBase-root MuiListItem-root makeStyles-menuExternalLink-0000 MuiListItem-gutters MuiListItem-button MuiTypography-colorPrimary" class="MuiTypography-root MuiLink-root MuiLink-underlineHover MuiButtonBase-root MuiListItem-root makeStyles-menuExternalLink-0000 MuiListItem-gutters MuiListItem-button MuiTypography-colorPrimary"
href="http://superset-dev.fiskercloud.internal/r/3" href="https://dev-superset.cloud.fiskerinc.com/r/3"
rel="noopener" rel="noopener"
role="button" role="button"
tabindex="0" tabindex="0"
@@ -3614,7 +3614,7 @@ exports[`App Route /package-status authenticated 1`] = `
<a <a
aria-disabled="false" aria-disabled="false"
class="MuiTypography-root MuiLink-root MuiLink-underlineHover MuiButtonBase-root MuiListItem-root makeStyles-menuExternalLink-0000 MuiListItem-gutters MuiListItem-button MuiTypography-colorPrimary" class="MuiTypography-root MuiLink-root MuiLink-underlineHover MuiButtonBase-root MuiListItem-root makeStyles-menuExternalLink-0000 MuiListItem-gutters MuiListItem-button MuiTypography-colorPrimary"
href="http://superset-dev.fiskercloud.internal/r/3" href="https://dev-superset.cloud.fiskerinc.com/r/3"
rel="noopener" rel="noopener"
role="button" role="button"
tabindex="0" tabindex="0"
@@ -4222,7 +4222,7 @@ exports[`App Route /packages authenticated 1`] = `
<a <a
aria-disabled="false" aria-disabled="false"
class="MuiTypography-root MuiLink-root MuiLink-underlineHover MuiButtonBase-root MuiListItem-root makeStyles-menuExternalLink-0000 MuiListItem-gutters MuiListItem-button MuiTypography-colorPrimary" class="MuiTypography-root MuiLink-root MuiLink-underlineHover MuiButtonBase-root MuiListItem-root makeStyles-menuExternalLink-0000 MuiListItem-gutters MuiListItem-button MuiTypography-colorPrimary"
href="http://superset-dev.fiskercloud.internal/r/3" href="https://dev-superset.cloud.fiskerinc.com/r/3"
rel="noopener" rel="noopener"
role="button" role="button"
tabindex="0" tabindex="0"
@@ -5032,7 +5032,7 @@ exports[`App Route /page-not-found authenticated 1`] = `
<a <a
aria-disabled="false" aria-disabled="false"
class="MuiTypography-root MuiLink-root MuiLink-underlineHover MuiButtonBase-root MuiListItem-root makeStyles-menuExternalLink-0000 MuiListItem-gutters MuiListItem-button MuiTypography-colorPrimary" class="MuiTypography-root MuiLink-root MuiLink-underlineHover MuiButtonBase-root MuiListItem-root makeStyles-menuExternalLink-0000 MuiListItem-gutters MuiListItem-button MuiTypography-colorPrimary"
href="http://superset-dev.fiskercloud.internal/r/3" href="https://dev-superset.cloud.fiskerinc.com/r/3"
rel="noopener" rel="noopener"
role="button" role="button"
tabindex="0" tabindex="0"
@@ -5453,7 +5453,7 @@ exports[`App Route /tools/certificates/add authenticated 1`] = `
<a <a
aria-disabled="false" aria-disabled="false"
class="MuiTypography-root MuiLink-root MuiLink-underlineHover MuiButtonBase-root MuiListItem-root makeStyles-menuExternalLink-0000 MuiListItem-gutters MuiListItem-button MuiTypography-colorPrimary" class="MuiTypography-root MuiLink-root MuiLink-underlineHover MuiButtonBase-root MuiListItem-root makeStyles-menuExternalLink-0000 MuiListItem-gutters MuiListItem-button MuiTypography-colorPrimary"
href="http://superset-dev.fiskercloud.internal/r/3" href="https://dev-superset.cloud.fiskerinc.com/r/3"
rel="noopener" rel="noopener"
role="button" role="button"
tabindex="0" tabindex="0"
@@ -6112,7 +6112,7 @@ exports[`App Route /vehicle-add authenticated 1`] = `
<a <a
aria-disabled="false" aria-disabled="false"
class="MuiTypography-root MuiLink-root MuiLink-underlineHover MuiButtonBase-root MuiListItem-root makeStyles-menuExternalLink-0000 MuiListItem-gutters MuiListItem-button MuiTypography-colorPrimary" class="MuiTypography-root MuiLink-root MuiLink-underlineHover MuiButtonBase-root MuiListItem-root makeStyles-menuExternalLink-0000 MuiListItem-gutters MuiListItem-button MuiTypography-colorPrimary"
href="http://superset-dev.fiskercloud.internal/r/3" href="https://dev-superset.cloud.fiskerinc.com/r/3"
rel="noopener" rel="noopener"
role="button" role="button"
tabindex="0" tabindex="0"
@@ -7246,7 +7246,7 @@ exports[`App Route /vehicle-status authenticated 1`] = `
<a <a
aria-disabled="false" aria-disabled="false"
class="MuiTypography-root MuiLink-root MuiLink-underlineHover MuiButtonBase-root MuiListItem-root makeStyles-menuExternalLink-0000 MuiListItem-gutters MuiListItem-button MuiTypography-colorPrimary" class="MuiTypography-root MuiLink-root MuiLink-underlineHover MuiButtonBase-root MuiListItem-root makeStyles-menuExternalLink-0000 MuiListItem-gutters MuiListItem-button MuiTypography-colorPrimary"
href="http://superset-dev.fiskercloud.internal/r/3" href="https://dev-superset.cloud.fiskerinc.com/r/3"
rel="noopener" rel="noopener"
role="button" role="button"
tabindex="0" tabindex="0"
@@ -7911,7 +7911,7 @@ exports[`App Route /vehicles authenticated 1`] = `
<a <a
aria-disabled="false" aria-disabled="false"
class="MuiTypography-root MuiLink-root MuiLink-underlineHover MuiButtonBase-root MuiListItem-root makeStyles-menuExternalLink-0000 MuiListItem-gutters MuiListItem-button MuiTypography-colorPrimary" class="MuiTypography-root MuiLink-root MuiLink-underlineHover MuiButtonBase-root MuiListItem-root makeStyles-menuExternalLink-0000 MuiListItem-gutters MuiListItem-button MuiTypography-colorPrimary"
href="http://superset-dev.fiskercloud.internal/r/3" href="https://dev-superset.cloud.fiskerinc.com/r/3"
rel="noopener" rel="noopener"
role="button" role="button"
tabindex="0" tabindex="0"

View File

@@ -1,66 +1,32 @@
import React from "react"; import React from "react";
import { useParams } from "react-router"; import { useParams } from "react-router";
import clsx from "clsx"; import clsx from "clsx";
import { Button, Grid, Typography } from "@material-ui/core"; import { Typography } from "@material-ui/core";
import CarECUsTable from "../../Controls/CarECUsTable"; import CarECUsTable from "../../Controls/CarECUsTable";
import CarUpdatesTable from "../../Controls/CarUpdatesTable"; import CarUpdatesTable from "../../Controls/CarUpdatesTable";
import { logger } from "../../../services/monitoring"; import { VehicleProvider } from "../../Contexts/VehicleContext";
import {
VehicleProvider,
useVehicleContext,
} from "../../Contexts/VehicleContext";
import { useUserContext } from "../../Contexts/UserContext"; import { useUserContext } from "../../Contexts/UserContext";
import { useStatusContext } from "../../Contexts/StatusContext";
import useStyles from "../../useStyles"; import useStyles from "../../useStyles";
const MainForm = () => { const MainForm = () => {
const { vin } = useParams(); const { vin } = useParams();
const classes = useStyles(); const classes = useStyles();
const { setMessage } = useStatusContext();
const { busy, sendCommand } = useVehicleContext();
const { const {
token: { token: {
idToken: { jwtToken: token }, idToken: { jwtToken: token },
}, },
} = useUserContext(); } = useUserContext();
const updateHandler = async (e) => {
try {
await sendCommand([vin], "ecu", "", token);
setMessage(`Sent command to ${vin}`);
} catch (error) {
setMessage(error.message);
logger.error(error.stack);
}
};
return ( return (
<div className={clsx(classes.paper, classes.tableSize)}> <div className={clsx(classes.paper, classes.tableSize)}>
<Typography variant="h6">Car Updates</Typography> <Typography variant="h6">Car Updates</Typography>
<CarUpdatesTable vin={vin} token={token} classes={classes} /> <CarUpdatesTable vin={vin} token={token} classes={classes} />
<Grid container className={classes.root} spacing={2}> <Typography variant="h6" className={classes.labelInline}>
<Grid item md={4} className={classes.textJustifyAlign}></Grid> Car ECUs
<Grid item md={4} className={classes.textCenterAlign}> </Typography>
<Typography variant="h6" className={classes.labelInline}>
Car ECUs
</Typography>
</Grid>
<Grid item md={4} className={classes.textRightAlign}>
<Button
type="submit"
disabled={busy}
variant="contained"
color="primary"
className={clsx(classes.formControl, classes.textField)}
onClick={updateHandler}
>
{busy ? "Sending..." : "Refresh"}
</Button>
</Grid>
</Grid>
<CarECUsTable vin={vin} token={token} classes={classes} /> <CarECUsTable vin={vin} token={token} classes={classes} />
</div > </div>
); );
}; };

View File

@@ -278,40 +278,11 @@ exports[`CarUpdatesTab Render 1`] = `
</tr> </tr>
</tfoot> </tfoot>
</table> </table>
<div <h6
class="MuiGrid-root makeStyles-root-14 MuiGrid-container MuiGrid-spacing-xs-2" class="MuiTypography-root makeStyles-labelInline-9 MuiTypography-h6"
> >
<div Car ECUs
class="MuiGrid-root makeStyles-textJustifyAlign-47 MuiGrid-item MuiGrid-grid-md-4" </h6>
/>
<div
class="MuiGrid-root makeStyles-textCenterAlign-48 MuiGrid-item MuiGrid-grid-md-4"
>
<h6
class="MuiTypography-root makeStyles-labelInline-9 MuiTypography-h6"
>
Car ECUs
</h6>
</div>
<div
class="MuiGrid-root makeStyles-textRightAlign-49 MuiGrid-item MuiGrid-grid-md-4"
>
<button
class="MuiButtonBase-root MuiButton-root MuiButton-contained makeStyles-formControl-7 makeStyles-textField-29 MuiButton-containedPrimary"
tabindex="0"
type="submit"
>
<span
class="MuiButton-label"
>
Refresh
</span>
<span
class="MuiTouchRipple-root"
/>
</button>
</div>
</div>
<div <div
class="makeStyles-paper-3 makeStyles-tableSize-53" class="makeStyles-paper-3 makeStyles-tableSize-53"
> >

View File

@@ -1,4 +1,4 @@
jest.mock("../../services/CANFiltersAPI") jest.mock("../../services/CANFiltersAPI");
import { import {
render, render,
@@ -83,9 +83,7 @@ describe("CANFiltersContext", () => {
<button data-testid="addFilterNoCANID" onClick={() => add({})} /> <button data-testid="addFilterNoCANID" onClick={() => add({})} />
<button <button
data-testid="addFilter" data-testid="addFilter"
onClick={() => onClick={() => add({ can_id: "123", interval: 1000 })}
add({ can_id: "123", interval: 1000 })
}
/> />
</> </>
); );
@@ -149,13 +147,17 @@ describe("CANFiltersContext", () => {
<> <>
<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="updateFilterNull" onClick={() => update(null)} /> <button
<button data-testid="updateFilterNoCANID" onClick={() => update({})} /> data-testid="updateFilterNull"
onClick={() => update(null)}
/>
<button
data-testid="updateFilterNoCANID"
onClick={() => update({})}
/>
<button <button
data-testid="updateFilter" data-testid="updateFilter"
onClick={() => onClick={() => update({ can_id: "123", interval: 1000 })}
update({ can_id: "123", interval: 1000 })
}
/> />
</> </>
); );
@@ -182,7 +184,10 @@ describe("CANFiltersContext", () => {
await waitFor(() => await waitFor(() =>
expect(screen.getByTestId("busy").innerHTML).toEqual("false") expect(screen.getByTestId("busy").innerHTML).toEqual("false")
); );
checkBaseResults("Cannot read property 'can_id' of null", "false"); checkBaseResults(
"Cannot read properties of null (reading 'can_id')",
"false"
);
}); });
it("updateFilterNoCANID", async () => { it("updateFilterNoCANID", async () => {
@@ -219,14 +224,15 @@ describe("CANFiltersContext", () => {
<> <>
<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="deleteFilterNull" onClick={() => deleteF(null)} />
<button data-testid="deleteFilterNonexistent" onClick={() => deleteF(-1)} />
<button <button
data-testid="deleteFilter" data-testid="deleteFilterNull"
onClick={() => onClick={() => deleteF(null)}
deleteF(123)
}
/> />
<button
data-testid="deleteFilterNonexistent"
onClick={() => deleteF(-1)}
/>
<button data-testid="deleteFilter" onClick={() => deleteF(123)} />
</> </>
); );
}; };
@@ -276,14 +282,14 @@ describe("CANFiltersContext", () => {
const expectedFiltersData = [ const expectedFiltersData = [
{ {
can_id: "123", can_id: "123",
interval: 1000 interval: 1000,
}, },
{ {
can_id: "456", can_id: "456",
interval: 0 interval: 0,
}, },
{ {
can_id: "789-1000", can_id: "789-1000",
interval: 5 interval: 5,
}, },
]; ];

View File

@@ -1,4 +1,4 @@
jest.mock("../../services/fleetsAPI") jest.mock("../../services/fleetsAPI");
import { import {
render, render,
@@ -23,12 +23,12 @@ const checkFleetsResults = (error, busy, fleets) => {
const checkFleetVehicleResults = (error, busy, vehicles) => { const checkFleetVehicleResults = (error, busy, vehicles) => {
checkBaseResults(error, busy); checkBaseResults(error, busy);
expect(screen.getByTestId("fleet-vehicles").innerHTML).toEqual(vehicles); expect(screen.getByTestId("fleet-vehicles").innerHTML).toEqual(vehicles);
} };
const checkFleetCANFilterResults = (error, busy, filters) => { const checkFleetCANFilterResults = (error, busy, filters) => {
checkBaseResults(error, busy); checkBaseResults(error, busy);
expect(screen.getByTestId("fleet-filters").innerHTML).toEqual(filters); expect(screen.getByTestId("fleet-filters").innerHTML).toEqual(filters);
} };
const checkBaseResults = (error, busy) => { const checkBaseResults = (error, busy) => {
expect(screen.getByTestId("error").innerHTML).toEqual(error); expect(screen.getByTestId("error").innerHTML).toEqual(error);
@@ -46,10 +46,7 @@ describe("FleetContext", () => {
<div data-testid="error">{error}</div> <div data-testid="error">{error}</div>
<div data-testid="busy">{busy.toString()}</div> <div data-testid="busy">{busy.toString()}</div>
<div data-testid="fleets">{JSON.stringify(fleets)}</div> <div data-testid="fleets">{JSON.stringify(fleets)}</div>
<button <button data-testid="getFleets" onClick={() => getFleets()} />
data-testid="getFleets"
onClick={() => getFleets()}
/>
</> </>
); );
}; };
@@ -140,7 +137,11 @@ describe("FleetContext", () => {
<button <button
data-testid="addFleet" data-testid="addFleet"
onClick={() => onClick={() =>
add({ name: "EU-WEST", log_level: "warn", canbus: { enabled: false } }) add({
name: "EU-WEST",
log_level: "warn",
canbus: { enabled: false },
})
} }
/> />
</> </>
@@ -205,12 +206,22 @@ describe("FleetContext", () => {
<> <>
<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="updateFleetNull" onClick={() => update(null)} /> <button
<button data-testid="updateFleetNoName" onClick={() => update({})} /> data-testid="updateFleetNull"
onClick={() => update(null)}
/>
<button
data-testid="updateFleetNoName"
onClick={() => update({})}
/>
<button <button
data-testid="updateFleet" data-testid="updateFleet"
onClick={() => onClick={() =>
update({ name: "EU-WEST", log_level: "warn", canbus: { enabled: false } }) update({
name: "EU-WEST",
log_level: "warn",
canbus: { enabled: false },
})
} }
/> />
</> </>
@@ -275,13 +286,17 @@ describe("FleetContext", () => {
<> <>
<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="deleteFleetNull" onClick={() => deleteF(null)} /> <button
<button data-testid="deleteFleetNonexistent" onClick={() => deleteF("INVALID")} /> data-testid="deleteFleetNull"
onClick={() => deleteF(null)}
/>
<button
data-testid="deleteFleetNonexistent"
onClick={() => deleteF("INVALID")}
/>
<button <button
data-testid="deleteFleet" data-testid="deleteFleet"
onClick={() => onClick={() => deleteF("US-WEST")}
deleteF("US-WEST")
}
/> />
</> </>
); );
@@ -331,13 +346,16 @@ describe("FleetContext", () => {
describe("getFleetVehicles", () => { describe("getFleetVehicles", () => {
beforeEach(() => { beforeEach(() => {
const TestComp = () => { const TestComp = () => {
const { busy, error, fleetVehicles, getFleetVehicles } = useFleetContext(); const { busy, error, fleetVehicles, getFleetVehicles } =
useFleetContext();
return ( return (
<> <>
<div data-testid="error">{error}</div> <div data-testid="error">{error}</div>
<div data-testid="busy">{busy.toString()}</div> <div data-testid="busy">{busy.toString()}</div>
<div data-testid="fleet-vehicles">{JSON.stringify(fleetVehicles)}</div> <div data-testid="fleet-vehicles">
{JSON.stringify(fleetVehicles)}
</div>
<button <button
data-testid="getFleetVehicles" data-testid="getFleetVehicles"
onClick={() => getFleetVehicles("US-WEST")} onClick={() => getFleetVehicles("US-WEST")}
@@ -365,7 +383,11 @@ describe("FleetContext", () => {
await waitFor(() => await waitFor(() =>
expect(screen.getByTestId("fleet-vehicles").innerHTML).not.toBe("[]") expect(screen.getByTestId("fleet-vehicles").innerHTML).not.toBe("[]")
); );
checkFleetVehicleResults("", "false", JSON.stringify(expectedFleetVehiclesData)); checkFleetVehicleResults(
"",
"false",
JSON.stringify(expectedFleetVehiclesData)
);
}); });
}); });
@@ -386,13 +408,17 @@ describe("FleetContext", () => {
<> <>
<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="addFleetVehicleNull" onClick={() => add(null)} /> <button
<button data-testid="addFleetVehicleNoName" onClick={() => add({})} /> data-testid="addFleetVehicleNull"
onClick={() => add(null)}
/>
<button
data-testid="addFleetVehicleNoName"
onClick={() => add({})}
/>
<button <button
data-testid="addFleetVehicle" data-testid="addFleetVehicle"
onClick={() => onClick={() => add("US-TEST", { vin: "TESTVIN1234567890" })}
add("US-TEST", { vin: "TESTVIN1234567890" })
}
/> />
</> </>
); );
@@ -456,13 +482,17 @@ describe("FleetContext", () => {
<> <>
<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="deleteFleetVehicleNull" onClick={() => deleteFV("US-WEST", null)} /> <button
<button data-testid="deleteFleetVehicleInvalid" onClick={() => deleteFV("US-WEST", "INVALID")} /> data-testid="deleteFleetVehicleNull"
onClick={() => deleteFV("US-WEST", null)}
/>
<button
data-testid="deleteFleetVehicleInvalid"
onClick={() => deleteFV("US-WEST", "INVALID")}
/>
<button <button
data-testid="deleteFleetVehicle" data-testid="deleteFleetVehicle"
onClick={() => onClick={() => deleteFV("US-WEST", { vin: "USWESTVIN12345678" })}
deleteFV("US-WEST", { vin: "USWESTVIN12345678" })
}
/> />
</> </>
); );
@@ -489,7 +519,10 @@ describe("FleetContext", () => {
await waitFor(() => await waitFor(() =>
expect(screen.getByTestId("busy").innerHTML).toEqual("false") expect(screen.getByTestId("busy").innerHTML).toEqual("false")
); );
checkBaseResults("Cannot read property 'vin' of null", "false"); checkBaseResults(
"Cannot read properties of null (reading 'vin')",
"false"
);
}); });
it("deleteFleetVehicleNonexistent", async () => { it("deleteFleetVehicleNonexistent", async () => {
@@ -512,13 +545,16 @@ describe("FleetContext", () => {
describe("getFleetCANFilters", () => { describe("getFleetCANFilters", () => {
beforeEach(() => { beforeEach(() => {
const TestComp = () => { const TestComp = () => {
const { busy, error, fleetCANFilters, getFleetCANFilters } = useFleetContext(); const { busy, error, fleetCANFilters, getFleetCANFilters } =
useFleetContext();
return ( return (
<> <>
<div data-testid="error">{error}</div> <div data-testid="error">{error}</div>
<div data-testid="busy">{busy.toString()}</div> <div data-testid="busy">{busy.toString()}</div>
<div data-testid="fleet-filters">{JSON.stringify(fleetCANFilters)}</div> <div data-testid="fleet-filters">
{JSON.stringify(fleetCANFilters)}
</div>
<button <button
data-testid="getFleetCANFilters" data-testid="getFleetCANFilters"
onClick={() => getFleetCANFilters("US-TEST")} onClick={() => getFleetCANFilters("US-TEST")}
@@ -546,7 +582,11 @@ describe("FleetContext", () => {
await waitFor(() => await waitFor(() =>
expect(screen.getByTestId("fleet-filters").innerHTML).not.toBe("[]") expect(screen.getByTestId("fleet-filters").innerHTML).not.toBe("[]")
); );
checkFleetCANFilterResults("", "false", JSON.stringify(expectedFleetCANFiltersData)); checkFleetCANFilterResults(
"",
"false",
JSON.stringify(expectedFleetCANFiltersData)
);
}); });
}); });
@@ -567,13 +607,17 @@ describe("FleetContext", () => {
<> <>
<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="addFleetCANFilterNull" onClick={() => add(null)} /> <button
<button data-testid="addFleetCANFilterNoName" onClick={() => add({})} /> data-testid="addFleetCANFilterNull"
onClick={() => add(null)}
/>
<button
data-testid="addFleetCANFilterNoName"
onClick={() => add({})}
/>
<button <button
data-testid="addFleetCANFilter" data-testid="addFleetCANFilter"
onClick={() => onClick={() => add("US-TEST", { can_id: "111", interval: 222 })}
add("US-TEST", { can_id: "111", interval: 222 })
}
/> />
</> </>
); );
@@ -637,13 +681,17 @@ describe("FleetContext", () => {
<> <>
<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="deleteFleetCANFilterNull" onClick={() => deleteFF("US-WEST", null)} /> <button
<button data-testid="deleteFleetCANFilterInvalid" onClick={() => deleteFF("US-WEST", "INVALID")} /> data-testid="deleteFleetCANFilterNull"
onClick={() => deleteFF("US-WEST", null)}
/>
<button
data-testid="deleteFleetCANFilterInvalid"
onClick={() => deleteFF("US-WEST", "INVALID")}
/>
<button <button
data-testid="deleteFleetCANFilter" data-testid="deleteFleetCANFilter"
onClick={() => onClick={() => deleteFF("US-WEST", "123-456")}
deleteFF("US-WEST", "123-456")
}
/> />
</> </>
); );
@@ -694,59 +742,80 @@ describe("FleetContext", () => {
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 expectedFleetData = { const expectedFleetData = {
name: "US-WEST", name: "US-WEST",
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: {
vehicles: ["USWESTVIN12345678", "USWESTVIN12345679", "USWESTVIN12345670"] enabled: true,
} data_logger_enabled: true,
max_mem_buffer_size: 1,
max_disk_buffer_size: 2,
filters: expectedFilters,
},
vehicles: ["USWESTVIN12345678", "USWESTVIN12345679", "USWESTVIN12345670"],
};
const expectedFleetsData = [ const expectedFleetsData = [
{ {
name: "US-WEST", name: "US-WEST",
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: {
vehicles: ["USWESTVIN12345678", "USWESTVIN12345679", "USWESTVIN12345670"] enabled: true,
data_logger_enabled: true,
max_mem_buffer_size: 1,
max_disk_buffer_size: 2,
filters: expectedFilters,
},
vehicles: ["USWESTVIN12345678", "USWESTVIN12345679", "USWESTVIN12345670"],
}, },
{ {
name: "US-CENTRAL", name: "US-CENTRAL",
log_level: "warn", log_level: "warn",
canbus: { enabled: false, data_logger_enabled: false, max_mem_buffer_size: 0, max_disk_buffer_size: 0 }, canbus: {
vehicles: ["USCENTVIN12345678", "USCENTVIN12345679", "USCENTVIN12345670"] enabled: false,
data_logger_enabled: false,
max_mem_buffer_size: 0,
max_disk_buffer_size: 0,
},
vehicles: ["USCENTVIN12345678", "USCENTVIN12345679", "USCENTVIN12345670"],
}, },
{ {
name: "US-EAST", name: "US-EAST",
log_level: "error", log_level: "error",
canbus: { enabled: true }, canbus: { enabled: true },
vehicles: ["USEASTVIN12345678", "USEASTVIN12345679", "USEASTVIN12345670"] vehicles: ["USEASTVIN12345678", "USEASTVIN12345679", "USEASTVIN12345670"],
}, },
]; ];
const expectedFleetVehiclesData = ["USWESTVIN12345678", "USWESTVIN12345679", "USWESTVIN12345670"]; const expectedFleetVehiclesData = [
"USWESTVIN12345678",
"USWESTVIN12345679",
"USWESTVIN12345670",
];
const expectedFleetCANFiltersData = [ const expectedFleetCANFiltersData = [
{ {
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,
} },
]; ];

View File

@@ -127,12 +127,12 @@ export const VehicleProvider = ({ children }) => {
const result = await api.getVehicle(vin, token); const result = await api.getVehicle(vin, token);
if (result.error) throw new Error(`Get vehicle error. ${result.message}`); if (result.error) throw new Error(`Get vehicle error. ${result.message}`);
setVehicle(result); setVehicle(result ?? []);
return result; return result;
} finally { } finally {
setBusy(false); setBusy(false);
} }
} };
const getVehicles = async (search, token) => { const getVehicles = async (search, token) => {
try { try {
@@ -143,7 +143,7 @@ export const VehicleProvider = ({ children }) => {
throw new Error(`Get vehicles error. ${result.message}`); throw new Error(`Get vehicles error. ${result.message}`);
} }
await addConnections(result.data, token); await addConnections(result.data, token);
setVehicles(result.data); setVehicles(result.data ?? []);
if (result.total) { if (result.total) {
setTotalVehicles(result.total); setTotalVehicles(result.total);
} }
@@ -188,7 +188,7 @@ export const VehicleProvider = ({ children }) => {
} finally { } finally {
setBusy(false); setBusy(false);
} }
} };
const deleteVehicle = async (vin, token) => { const deleteVehicle = async (vin, token) => {
try { try {
@@ -202,7 +202,7 @@ export const VehicleProvider = ({ children }) => {
} finally { } finally {
setBusy(false); setBusy(false);
} }
} };
return ( return (
<VehicleContext.Provider <VehicleContext.Provider
@@ -224,7 +224,7 @@ export const VehicleProvider = ({ children }) => {
getVehicle, getVehicle,
getVehicles, getVehicles,
sendCommand, sendCommand,
updateVehicle updateVehicle,
}} }}
> >
{children} {children}
@@ -246,5 +246,4 @@ const validateVIN = (vin) => {
} }
}; };
export const useVehicleContext = () => useContext(VehicleContext); export const useVehicleContext = () => useContext(VehicleContext);

View File

@@ -48,7 +48,15 @@ const tableColumns = [
]; ];
const CarSelectionTable = (props) => { const CarSelectionTable = (props) => {
const { token, classes, search, multiSelect, selected, onSelect, onSelectAll } = props; const {
token,
classes,
search,
multiSelect,
selected,
onSelect,
onSelectAll,
} = props;
const [pageSize, setPageSize] = useState(10); const [pageSize, setPageSize] = useState(10);
const [pageIndex, setPageIndex] = useState(0); const [pageIndex, setPageIndex] = useState(0);
const [orderBy, setOrderBy] = useState("vin"); const [orderBy, setOrderBy] = useState("vin");
@@ -126,19 +134,23 @@ const CarSelectionTable = (props) => {
multiSelect={multiSelect} multiSelect={multiSelect}
onSelectAll={handleSelectAll} onSelectAll={handleSelectAll}
selectCount={selected ? selected.length : 0} selectCount={selected ? selected.length : 0}
rowCount={vehicles.length} rowCount={vehicles ? vehicles.length : 0}
/> />
<TableBody> <TableBody>
{vehicles.map((row) => { {vehicles.map((row) => {
const isSelected = selected ? selected.indexOf(row.vin) !== -1 : false; const isSelected = selected
? selected.indexOf(row.vin) !== -1
: false;
return ( return (
<TableRow key={row.vin}> <TableRow key={row.vin}>
{multiSelect && (<TableCell padding="checkbox"> {multiSelect && (
<Checkbox <TableCell padding="checkbox">
checked={isSelected} <Checkbox
onChange={(event) => handleSelect(event, row.vin)} checked={isSelected}
/> onChange={(event) => handleSelect(event, row.vin)}
</TableCell>)} />
</TableCell>
)}
<TableCell align="center"> <TableCell align="center">
<ConnectedIcon <ConnectedIcon
connected={row.connected} connected={row.connected}

View File

@@ -8,22 +8,26 @@ import s from "./Statuses";
const AwaitStatus = -1; const AwaitStatus = -1;
const ErrorStatus = -100; const ErrorStatus = -100;
const CompleteStatus = 100;
const PHASES = [ const PHASES = [
{ {
label: "Pending", label: "Pending",
events: [s.Pending], events: [s.Pending],
progress: () => 100, progress: () => CompleteStatus,
}, },
{ {
label: "Received", label: "Received",
events: [s.ManifestAccepted, s.ManifestReceived, s.ManifestRejected], events: [s.ManifestAccepted, s.ManifestReceived, s.ManifestRejected],
progress: () => 100, progress: (msg) => {
if (msg === s.ManifestRejected) return ErrorStatus;
return CompleteStatus;
},
}, },
{ {
label: "Precondition", label: "Precondition",
events: [s.PreconditionAwait, s.PreconditionSuceeded], events: [s.PreconditionAwait, s.PreconditionSuceeded],
progress: () => 100, progress: () => CompleteStatus,
}, },
{ {
label: "Download", label: "Download",
@@ -55,14 +59,14 @@ const PHASES = [
], ],
progress: (msg, progress) => { progress: (msg, progress) => {
if (msg === s.InstallFailed) return ErrorStatus; if (msg === s.InstallFailed) return ErrorStatus;
if (msg === s.PackageInstallCompleted) return 100; if (msg === s.PackageInstallCompleted) return CompleteStatus;
return progress; return progress;
}, },
}, },
{ {
label: "Updated", label: "Updated",
events: [s.ManifestSucceeded], events: [s.ManifestSucceeded],
progress: (_msg, _progress) => 100, progress: (_msg, _progress) => CompleteStatus,
}, },
]; ];
@@ -84,7 +88,7 @@ const Progress = ({ value, classes }) => {
const getProgress = (index, phase, progress) => { const getProgress = (index, phase, progress) => {
if (index === phase) return progress; if (index === phase) return progress;
if (index < phase) return 100; if (index < phase) return CompleteStatus;
return -1; return -1;
}; };

View File

@@ -156,7 +156,7 @@ exports[`SideMenu Authenticated 1`] = `
<a <a
aria-disabled="false" aria-disabled="false"
class="MuiTypography-root MuiLink-root MuiLink-underlineHover MuiButtonBase-root MuiListItem-root makeStyles-menuExternalLink-52 MuiListItem-gutters MuiListItem-button MuiTypography-colorPrimary" class="MuiTypography-root MuiLink-root MuiLink-underlineHover MuiButtonBase-root MuiListItem-root makeStyles-menuExternalLink-52 MuiListItem-gutters MuiListItem-button MuiTypography-colorPrimary"
href="http://superset-dev.fiskercloud.internal/r/3" href="https://dev-superset.cloud.fiskerinc.com/r/3"
rel="noopener" rel="noopener"
role="button" role="button"
tabindex="0" tabindex="0"

View File

@@ -246,16 +246,6 @@ const useStyles = makeStyles((theme) => ({
menuExternalLink: { menuExternalLink: {
textDecoration: "inherit", textDecoration: "inherit",
color: "inherit", color: "inherit",
"&:link": {
textDecoration: "inherit",
color: "inherit",
cursor: "auto",
},
"&:visited": {
textDecoration: "inherit",
color: "inherit",
cursor: "auto",
},
}, },
tableSize: { height: 700, width: "100%" }, tableSize: { height: 700, width: "100%" },
whiteBackground: { backgroundColor: "White" }, whiteBackground: { backgroundColor: "White" },

View File

@@ -1,49 +1,58 @@
import { import {
getAuthHeaderOptions, errorHandler,
fetchRespHandler, getAuthHeaderOptions,
addQueryParams, fetchRespHandler,
addQueryParams,
} from "../utils/http"; } from "../utils/http";
const API_ENDPOINT = process.env.REACT_APP_UPLOAD_SERVICE_URL; const API_ENDPOINT = process.env.REACT_APP_UPLOAD_SERVICE_URL;
const canFiltersAPI = { const canFiltersAPI = {
addFilter: async (vin, filter, token) => addFilter: async (vin, filter, token) =>
fetch(`${API_ENDPOINT}/vehicle/${vin}/filter`, { fetch(`${API_ENDPOINT}/vehicle/${vin}/filter`, {
method: "POST", method: "POST",
headers: Object.assign( headers: Object.assign(
{ "Content-Type": "application/json" }, { "Content-Type": "application/json" },
getAuthHeaderOptions(token) getAuthHeaderOptions(token)
), ),
body: JSON.stringify(filter) body: JSON.stringify(filter),
}).then(fetchRespHandler), })
.then(fetchRespHandler)
.catch(errorHandler),
getFilters: async (vin, search, token) => getFilters: async (vin, search, token) =>
fetch(addQueryParams(`${API_ENDPOINT}/vehicle/${vin}/filters`, search), { fetch(addQueryParams(`${API_ENDPOINT}/vehicle/${vin}/filters`, search), {
method: "GET", method: "GET",
headers: Object.assign( headers: Object.assign(
{ "Content-Type": "application/json" }, { "Content-Type": "application/json" },
getAuthHeaderOptions(token) getAuthHeaderOptions(token)
), ),
}).then(fetchRespHandler), })
.then(fetchRespHandler)
.catch(errorHandler),
updateFilter: async (vin, canID, filter, token) => updateFilter: async (vin, canID, filter, token) =>
fetch(`${API_ENDPOINT}/vehicle/${vin}/filter/${canID}`, { fetch(`${API_ENDPOINT}/vehicle/${vin}/filter/${canID}`, {
method: "PUT", method: "PUT",
headers: Object.assign( headers: Object.assign(
{ "Content-Type": "application/json" }, { "Content-Type": "application/json" },
getAuthHeaderOptions(token) getAuthHeaderOptions(token)
), ),
body: JSON.stringify(filter) body: JSON.stringify(filter),
}).then(fetchRespHandler), })
.then(fetchRespHandler)
.catch(errorHandler),
deleteFilter: async (vin, canID, token) => deleteFilter: async (vin, canID, token) =>
fetch(`${API_ENDPOINT}/vehicle/${vin}/filter/${canID}`, { fetch(`${API_ENDPOINT}/vehicle/${vin}/filter/${canID}`, {
method: "DELETE", method: "DELETE",
headers: Object.assign( headers: Object.assign(
{ "Content-Type": "application/json" }, { "Content-Type": "application/json" },
getAuthHeaderOptions(token) getAuthHeaderOptions(token)
) ),
}).then(fetchRespHandler), })
.then(fetchRespHandler)
.catch(errorHandler),
}; };
export default canFiltersAPI; export default canFiltersAPI;

View File

@@ -1,4 +1,4 @@
import { fetchRespHandler } from "../utils/http"; import { errorHandler, fetchRespHandler } from "../utils/http";
const AUTH_URL = process.env.REACT_APP_AUTH_SERVICE_URL; const AUTH_URL = process.env.REACT_APP_AUTH_SERVICE_URL;
const CALLBACK_URL = process.env.REACT_APP_AUTH_CALLBACK_URL; const CALLBACK_URL = process.env.REACT_APP_AUTH_CALLBACK_URL;
@@ -16,7 +16,9 @@ const auth = {
code, code,
redirect: CALLBACK_URL, redirect: CALLBACK_URL,
}), }),
}).then(fetchRespHandler), })
.then(fetchRespHandler)
.catch(errorHandler),
verify: (idToken) => verify: (idToken) =>
fetch(`${AUTH_URL}/verify`, { fetch(`${AUTH_URL}/verify`, {
@@ -25,7 +27,9 @@ const auth = {
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
body: JSON.stringify({ token: idToken }), body: JSON.stringify({ token: idToken }),
}).then(fetchRespHandler), })
.then(fetchRespHandler)
.catch(errorHandler),
refresh: (refreshToken) => refresh: (refreshToken) =>
fetch(`${AUTH_URL}/refresh`, { fetch(`${AUTH_URL}/refresh`, {
@@ -34,7 +38,9 @@ const auth = {
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
body: JSON.stringify({ refresh_token: refreshToken }), body: JSON.stringify({ refresh_token: refreshToken }),
}).then(fetchRespHandler), })
.then(fetchRespHandler)
.catch(errorHandler),
}; };
export default auth; export default auth;

View File

@@ -1,4 +1,8 @@
import { getAuthHeaderOptions, fetchRespHandler } from "../utils/http"; import {
errorHandler,
getAuthHeaderOptions,
fetchRespHandler,
} from "../utils/http";
const API_ENDPOINT = process.env.REACT_APP_CERT_SERVICE_URL; const API_ENDPOINT = process.env.REACT_APP_CERT_SERVICE_URL;
@@ -11,7 +15,9 @@ const certificatesAPI = {
getAuthHeaderOptions(token) getAuthHeaderOptions(token)
), ),
body: JSON.stringify(data), body: JSON.stringify(data),
}).then(fetchRespHandler), })
.then(fetchRespHandler)
.catch(errorHandler),
}; };
export default certificatesAPI; export default certificatesAPI;

View File

@@ -1,4 +1,5 @@
import { import {
errorHandler,
getAuthHeaderOptions, getAuthHeaderOptions,
fetchRespHandler, fetchRespHandler,
addQueryParams, addQueryParams,
@@ -14,8 +15,10 @@ const fleetsAPI = {
{ "Content-Type": "application/json" }, { "Content-Type": "application/json" },
getAuthHeaderOptions(token) getAuthHeaderOptions(token)
), ),
body: JSON.stringify(fleet) body: JSON.stringify(fleet),
}).then(fetchRespHandler), })
.then(fetchRespHandler)
.catch(errorHandler),
getFleet: async (name, token) => getFleet: async (name, token) =>
fetch(`${API_ENDPOINT}/fleet/${name}`, { fetch(`${API_ENDPOINT}/fleet/${name}`, {
@@ -23,8 +26,10 @@ const fleetsAPI = {
headers: Object.assign( headers: Object.assign(
{ "Content-Type": "application/json" }, { "Content-Type": "application/json" },
getAuthHeaderOptions(token) getAuthHeaderOptions(token)
) ),
}).then(fetchRespHandler), })
.then(fetchRespHandler)
.catch(errorHandler),
getFleets: async (search, token) => getFleets: async (search, token) =>
fetch(addQueryParams(`${API_ENDPOINT}/fleets`, search), { fetch(addQueryParams(`${API_ENDPOINT}/fleets`, search), {
@@ -33,7 +38,9 @@ const fleetsAPI = {
{ "Content-Type": "application/json" }, { "Content-Type": "application/json" },
getAuthHeaderOptions(token) getAuthHeaderOptions(token)
), ),
}).then(fetchRespHandler), })
.then(fetchRespHandler)
.catch(errorHandler),
updateFleet: async (name, fleet, token) => updateFleet: async (name, fleet, token) =>
fetch(`${API_ENDPOINT}/fleet/${name}`, { fetch(`${API_ENDPOINT}/fleet/${name}`, {
@@ -42,8 +49,10 @@ const fleetsAPI = {
{ "Content-Type": "application/json" }, { "Content-Type": "application/json" },
getAuthHeaderOptions(token) getAuthHeaderOptions(token)
), ),
body: JSON.stringify(fleet) body: JSON.stringify(fleet),
}).then(fetchRespHandler), })
.then(fetchRespHandler)
.catch(errorHandler),
deleteFleet: async (name, token) => deleteFleet: async (name, token) =>
fetch(`${API_ENDPOINT}/fleet/${name}`, { fetch(`${API_ENDPOINT}/fleet/${name}`, {
@@ -51,8 +60,10 @@ const fleetsAPI = {
headers: Object.assign( headers: Object.assign(
{ "Content-Type": "application/json" }, { "Content-Type": "application/json" },
getAuthHeaderOptions(token) getAuthHeaderOptions(token)
) ),
}).then(fetchRespHandler), })
.then(fetchRespHandler)
.catch(errorHandler),
getFleetVehicles: async (name, search, token) => getFleetVehicles: async (name, search, token) =>
fetch(addQueryParams(`${API_ENDPOINT}/fleet/${name}/vehicles`, search), { fetch(addQueryParams(`${API_ENDPOINT}/fleet/${name}/vehicles`, search), {
@@ -60,8 +71,10 @@ const fleetsAPI = {
headers: Object.assign( headers: Object.assign(
{ "Content-Type": "application/json" }, { "Content-Type": "application/json" },
getAuthHeaderOptions(token) getAuthHeaderOptions(token)
) ),
}).then(fetchRespHandler), })
.then(fetchRespHandler)
.catch(errorHandler),
addFleetVehicle: async (name, vehicle, token) => addFleetVehicle: async (name, vehicle, token) =>
fetch(`${API_ENDPOINT}/fleet/${name}/vehicle`, { fetch(`${API_ENDPOINT}/fleet/${name}/vehicle`, {
@@ -70,8 +83,10 @@ const fleetsAPI = {
{ "Content-Type": "application/json" }, { "Content-Type": "application/json" },
getAuthHeaderOptions(token) getAuthHeaderOptions(token)
), ),
body: JSON.stringify(vehicle) body: JSON.stringify(vehicle),
}).then(fetchRespHandler), })
.then(fetchRespHandler)
.catch(errorHandler),
deleteFleetVehicle: async (name, vehicle, token) => deleteFleetVehicle: async (name, vehicle, token) =>
fetch(`${API_ENDPOINT}/fleet/${name}/vehicle/${vehicle.vin}`, { fetch(`${API_ENDPOINT}/fleet/${name}/vehicle/${vehicle.vin}`, {
@@ -79,8 +94,10 @@ const fleetsAPI = {
headers: Object.assign( headers: Object.assign(
{ "Content-Type": "application/json" }, { "Content-Type": "application/json" },
getAuthHeaderOptions(token) getAuthHeaderOptions(token)
) ),
}).then(fetchRespHandler), })
.then(fetchRespHandler)
.catch(errorHandler),
getFleetCANFilters: async (name, search, token) => getFleetCANFilters: async (name, search, token) =>
fetch(addQueryParams(`${API_ENDPOINT}/fleet/${name}/filters`, search), { fetch(addQueryParams(`${API_ENDPOINT}/fleet/${name}/filters`, search), {
@@ -88,8 +105,10 @@ const fleetsAPI = {
headers: Object.assign( headers: Object.assign(
{ "Content-Type": "application/json" }, { "Content-Type": "application/json" },
getAuthHeaderOptions(token) getAuthHeaderOptions(token)
) ),
}).then(fetchRespHandler), })
.then(fetchRespHandler)
.catch(errorHandler),
addFleetCANFilter: async (name, filter, token) => addFleetCANFilter: async (name, filter, token) =>
fetch(`${API_ENDPOINT}/fleet/${name}/filter`, { fetch(`${API_ENDPOINT}/fleet/${name}/filter`, {
@@ -98,8 +117,10 @@ const fleetsAPI = {
{ "Content-Type": "application/json" }, { "Content-Type": "application/json" },
getAuthHeaderOptions(token) getAuthHeaderOptions(token)
), ),
body: JSON.stringify(filter) body: JSON.stringify(filter),
}).then(fetchRespHandler), })
.then(fetchRespHandler)
.catch(errorHandler),
updateFleetCANFilter: async (name, can_id, filter, token) => updateFleetCANFilter: async (name, can_id, filter, token) =>
fetch(`${API_ENDPOINT}/fleet/${name}/filter/${can_id}`, { fetch(`${API_ENDPOINT}/fleet/${name}/filter/${can_id}`, {
@@ -108,8 +129,10 @@ const fleetsAPI = {
{ "Content-Type": "application/json" }, { "Content-Type": "application/json" },
getAuthHeaderOptions(token) getAuthHeaderOptions(token)
), ),
body: JSON.stringify(filter) body: JSON.stringify(filter),
}).then(fetchRespHandler), })
.then(fetchRespHandler)
.catch(errorHandler),
deleteFleetCANFilter: async (name, can_id, token) => deleteFleetCANFilter: async (name, can_id, token) =>
fetch(`${API_ENDPOINT}/fleet/${name}/filter/${can_id}`, { fetch(`${API_ENDPOINT}/fleet/${name}/filter/${can_id}`, {
@@ -117,8 +140,10 @@ const fleetsAPI = {
headers: Object.assign( headers: Object.assign(
{ "Content-Type": "application/json" }, { "Content-Type": "application/json" },
getAuthHeaderOptions(token) getAuthHeaderOptions(token)
) ),
}).then(fetchRespHandler), })
.then(fetchRespHandler)
.catch(errorHandler),
}; };
export default fleetsAPI; export default fleetsAPI;

View File

@@ -1,4 +1,5 @@
import { import {
errorHandler,
getAuthHeaderOptions, getAuthHeaderOptions,
fetchRespHandler, fetchRespHandler,
addQueryParams, addQueryParams,
@@ -14,7 +15,22 @@ const manifestsAPI = {
{ "Content-Type": "application/json" }, { "Content-Type": "application/json" },
getAuthHeaderOptions(token) getAuthHeaderOptions(token)
), ),
}).then(fetchRespHandler), })
.then(fetchRespHandler)
.catch(errorHandler),
getManifest: async (id, token) => {
const u = addQueryParams(`${API_ENDPOINT}/manifest`, { id });
return fetch(u, {
method: "GET",
headers: Object.assign(
{ "Content-Type": "application/json" },
getAuthHeaderOptions(token)
),
})
.then(fetchRespHandler)
.catch(errorHandler);
},
getManifest: async (id, token) => { getManifest: async (id, token) => {
const u = addQueryParams(`${API_ENDPOINT}/manifest`, { id }); const u = addQueryParams(`${API_ENDPOINT}/manifest`, { id });
@@ -35,7 +51,9 @@ const manifestsAPI = {
{ "Content-Type": "application/json" }, { "Content-Type": "application/json" },
getAuthHeaderOptions(token) getAuthHeaderOptions(token)
), ),
}).then(fetchRespHandler); })
.then(fetchRespHandler)
.catch(errorHandler);
}, },
createManifest: async (data, token) => createManifest: async (data, token) =>
@@ -46,7 +64,9 @@ const manifestsAPI = {
getAuthHeaderOptions(token) getAuthHeaderOptions(token)
), ),
body: JSON.stringify(data), body: JSON.stringify(data),
}).then(fetchRespHandler), })
.then(fetchRespHandler)
.catch(errorHandler),
createManifestECU: async (data, token) => createManifestECU: async (data, token) =>
fetch(`${API_ENDPOINT}/manifestecu`, { fetch(`${API_ENDPOINT}/manifestecu`, {
@@ -56,7 +76,9 @@ const manifestsAPI = {
getAuthHeaderOptions(token) getAuthHeaderOptions(token)
), ),
body: JSON.stringify(data), body: JSON.stringify(data),
}).then(fetchRespHandler), })
.then(fetchRespHandler)
.catch(errorHandler),
}; };
export default manifestsAPI; export default manifestsAPI;

View File

@@ -1,4 +1,5 @@
import { import {
errorHandler,
getAuthHeaderOptions, getAuthHeaderOptions,
fetchRespHandler, fetchRespHandler,
addQueryParams, addQueryParams,
@@ -15,7 +16,9 @@ const updatesAPI = {
getAuthHeaderOptions(token) getAuthHeaderOptions(token)
), ),
body: JSON.stringify(data), body: JSON.stringify(data),
}).then(fetchRespHandler), })
.then(fetchRespHandler)
.catch(errorHandler),
getCarUpdateLog: async (query, token) => { getCarUpdateLog: async (query, token) => {
const u = addQueryParams(`${API_ENDPOINT}/carupdateslog`, query); const u = addQueryParams(`${API_ENDPOINT}/carupdateslog`, query);
@@ -25,7 +28,9 @@ const updatesAPI = {
{ "Content-Type": "application/json" }, { "Content-Type": "application/json" },
getAuthHeaderOptions(token) getAuthHeaderOptions(token)
), ),
}).then(fetchRespHandler); })
.then(fetchRespHandler)
.catch(errorHandler);
}, },
getCarUpdateProgress: async (carupdateids, token) => { getCarUpdateProgress: async (carupdateids, token) => {
@@ -36,7 +41,9 @@ const updatesAPI = {
{ "Content-Type": "application/json" }, { "Content-Type": "application/json" },
getAuthHeaderOptions(token) getAuthHeaderOptions(token)
), ),
}).then(fetchRespHandler); })
.then(fetchRespHandler)
.catch(errorHandler);
}, },
getCarUpdates: async (search, token) => { getCarUpdates: async (search, token) => {
@@ -47,7 +54,9 @@ const updatesAPI = {
{ "Content-Type": "application/json" }, { "Content-Type": "application/json" },
getAuthHeaderOptions(token) getAuthHeaderOptions(token)
), ),
}).then(fetchRespHandler); })
.then(fetchRespHandler)
.catch(errorHandler);
}, },
getVINUpdates: async (vin, token) => { getVINUpdates: async (vin, token) => {
@@ -58,7 +67,9 @@ const updatesAPI = {
{ "Content-Type": "application/json" }, { "Content-Type": "application/json" },
getAuthHeaderOptions(token) getAuthHeaderOptions(token)
), ),
}).then(fetchRespHandler); })
.then(fetchRespHandler)
.catch(errorHandler);
}, },
}; };

View File

@@ -1,4 +1,5 @@
import { import {
errorHandler,
getAuthHeaderOptions, getAuthHeaderOptions,
fetchRespHandler, fetchRespHandler,
addQueryParams, addQueryParams,
@@ -15,7 +16,20 @@ const vehiclesAPI = {
getAuthHeaderOptions(token) getAuthHeaderOptions(token)
), ),
body: JSON.stringify(vehicle), body: JSON.stringify(vehicle),
}).then(fetchRespHandler), })
.then(fetchRespHandler)
.catch(errorHandler),
deleteVehicle: async (vin, token) =>
fetch(`${API_ENDPOINT}/vehicle/${vin}`, {
method: "DELETE",
headers: Object.assign(
{ "Content-Type": "application/json" },
getAuthHeaderOptions(token)
),
})
.then(fetchRespHandler)
.catch(errorHandler),
deleteVehicle: async (vin, token) => deleteVehicle: async (vin, token) =>
fetch(`${API_ENDPOINT}/vehicle/${vin}`, { fetch(`${API_ENDPOINT}/vehicle/${vin}`, {
@@ -34,7 +48,9 @@ const vehiclesAPI = {
{ "Content-Type": "application/json" }, { "Content-Type": "application/json" },
getAuthHeaderOptions(token) getAuthHeaderOptions(token)
), ),
}).then(fetchRespHandler); })
.then(fetchRespHandler)
.catch(errorHandler);
}, },
getECUs: async (search, token) => { getECUs: async (search, token) => {
@@ -45,7 +61,9 @@ const vehiclesAPI = {
{ "Content-Type": "application/json" }, { "Content-Type": "application/json" },
getAuthHeaderOptions(token) getAuthHeaderOptions(token)
), ),
}).then(fetchRespHandler); })
.then(fetchRespHandler)
.catch(errorHandler);
}, },
getModels: async (token) => getModels: async (token) =>
@@ -55,7 +73,9 @@ const vehiclesAPI = {
{ "Content-Type": "application/json" }, { "Content-Type": "application/json" },
getAuthHeaderOptions(token) getAuthHeaderOptions(token)
), ),
}).then(fetchRespHandler), })
.then(fetchRespHandler)
.catch(errorHandler),
getLocations: async (token) => getLocations: async (token) =>
fetch(`${API_ENDPOINT}/carslocations`, { fetch(`${API_ENDPOINT}/carslocations`, {
@@ -64,7 +84,9 @@ const vehiclesAPI = {
{ "Content-Type": "application/json" }, { "Content-Type": "application/json" },
getAuthHeaderOptions(token) getAuthHeaderOptions(token)
), ),
}).then(fetchRespHandler), })
.then(fetchRespHandler)
.catch(errorHandler),
getState: async (token, vin) => getState: async (token, vin) =>
fetch(`${API_ENDPOINT}/carstate?vin=${vin}`, { fetch(`${API_ENDPOINT}/carstate?vin=${vin}`, {
@@ -73,7 +95,20 @@ const vehiclesAPI = {
{ "Content-Type": "application/json" }, { "Content-Type": "application/json" },
getAuthHeaderOptions(token) getAuthHeaderOptions(token)
), ),
}).then(fetchRespHandler), })
.then(fetchRespHandler)
.catch(errorHandler),
getVehicle: async (vin, token) =>
fetch(`${API_ENDPOINT}/vehicle/${vin}`, {
method: "GET",
headers: Object.assign(
{ "Content-Type": "application/json" },
getAuthHeaderOptions(token)
),
})
.then(fetchRespHandler)
.catch(errorHandler),
getVehicle: async (vin, token) => getVehicle: async (vin, token) =>
fetch(`${API_ENDPOINT}/vehicle/${vin}`, { fetch(`${API_ENDPOINT}/vehicle/${vin}`, {
@@ -92,7 +127,9 @@ const vehiclesAPI = {
{ "Content-Type": "application/json" }, { "Content-Type": "application/json" },
getAuthHeaderOptions(token) getAuthHeaderOptions(token)
), ),
}).then(fetchRespHandler); })
.then(fetchRespHandler)
.catch(errorHandler);
}, },
getYears: async (token) => getYears: async (token) =>
@@ -102,7 +139,9 @@ const vehiclesAPI = {
{ "Content-Type": "application/json" }, { "Content-Type": "application/json" },
getAuthHeaderOptions(token) getAuthHeaderOptions(token)
), ),
}).then(fetchRespHandler), })
.then(fetchRespHandler)
.catch(errorHandler),
sendCommand: async (vins, command, parameters, token) => sendCommand: async (vins, command, parameters, token) =>
fetch(`${API_ENDPOINT}/vehiclecommand`, { fetch(`${API_ENDPOINT}/vehiclecommand`, {
@@ -116,7 +155,9 @@ const vehiclesAPI = {
command, command,
parameters, parameters,
}), }),
}).then(fetchRespHandler), })
.then(fetchRespHandler)
.catch(errorHandler),
updateVehicle: async (vin, vehicle, token) => updateVehicle: async (vin, vehicle, token) =>
fetch(`${API_ENDPOINT}/vehicle/${vin}`, { fetch(`${API_ENDPOINT}/vehicle/${vin}`, {
@@ -125,8 +166,10 @@ const vehiclesAPI = {
{ "Content-Type": "application/json" }, { "Content-Type": "application/json" },
getAuthHeaderOptions(token) getAuthHeaderOptions(token)
), ),
body: JSON.stringify(vehicle) body: JSON.stringify(vehicle),
}).then(fetchRespHandler), })
.then(fetchRespHandler)
.catch(errorHandler),
}; };
export default vehiclesAPI; export default vehiclesAPI;

View File

@@ -1,13 +1,28 @@
import { logger } from "../services/monitoring"; import { logger } from "../services/monitoring";
export const getAuthHeaderOptions = (token) => ({ export const addQueryParams = (url, params) => {
"Authorization": `Bearer ${token}`, if (!params) return url;
});
const u = new URL(url);
Object.keys(params).forEach((key) => u.searchParams.append(key, params[key]));
return u.toString();
};
export const errorHandler = (e) => {
logger.error(e.stack);
return {
error: e.name,
message: e.message,
};
};
export const fetchRespHandler = (response) => { export const fetchRespHandler = (response) => {
if (response.ok) return response.json(); if (response.ok) return response.json();
return response.text() return response
.text()
.then((text) => { .then((text) => {
if (response.status >= 500) logger.error(text); if (response.status >= 500) logger.error(text);
return JSON.parse(text); return JSON.parse(text);
@@ -18,15 +33,9 @@ export const fetchRespHandler = (response) => {
error: response.statusText, error: response.statusText,
message: `${response.status} ${response.statusText}`, message: `${response.status} ${response.statusText}`,
}; };
}) });
} };
export const addQueryParams = (url, params) => { export const getAuthHeaderOptions = (token) => ({
if (!params) return url; Authorization: `Bearer ${token}`,
});
const u = new URL(url);
Object.keys(params).forEach(key => u.searchParams.append(key, params[key]))
return u.toString();
}