+
+
+
+
+
+
+`;
+
+exports[`App Route /carupdate-deploy unauthenticated 1`] = `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Sign Out
+
+
+
+
+ + Fisker OTA Portal +
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
-
+
-
+
+ + + Home + ++ + +
+
-
+
+ + + View Updates + ++ + +
+
-
+
+ + + Create Updates + ++ + +
+
-
+
+ + + View Vehicles + ++ + +
+
-
+
+ + + Add Vehicles + ++ + +
+
+
+
+
+
+
+ + [1] +
+ +
+
+`;
+
+exports[`App Route /carupdate-status authenticated 1`] = `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + Fisker OTA Portal +
+
+
+`;
+
+exports[`App Route /carupdate-status unauthenticated 1`] = `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Sign Out
+
+
+
+
+ + Fisker OTA Portal +
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
-
+
-
+
+ + + Home + ++ + +
+
-
+
+ + + View Updates + ++ + +
+
-
+
+ + + Create Updates + ++ + +
+
-
+
+ + + View Vehicles + ++ + +
+
-
+
+ + + Add Vehicles + ++ + +
+
+
+
+
+
+
+
+
+
+
+
+ | + ID + | ++ Vehicle + | ++ Status + | ++ Created + | ++ Updated + | +
|---|---|---|---|---|
|
+
+
+
+ + Rows per page: + +
+
+
+
+ + 0-0 of 0 + +
+
+ |
+ ||||
+
+
+
+
+
+
+
+
+
+ + Fisker OTA Portal +
+
+
@@ -299,17 +1228,17 @@ exports[`App Route /home authenticated 1`] = `
data-testid="mocked-userprovider"
>
@@ -337,7 +1266,7 @@ exports[`App Route /home authenticated 1`] = `
Fisker OTA Portal
@@ -353,13 +1282,13 @@ exports[`App Route /home authenticated 1`] = `
@@ -592,17 +1521,17 @@ exports[`App Route /package-upload authenticated 1`] = `
data-testid="mocked-userprovider"
>
@@ -630,7 +1559,7 @@ exports[`App Route /package-upload authenticated 1`] = `
Fisker OTA Portal
@@ -646,13 +1575,13 @@ exports[`App Route /package-upload authenticated 1`] = `
@@ -1050,10 +1979,10 @@ exports[`App Route /package-upload unauthenticated 1`] = `
data-testid="mocked-userprovider"
>
+
+
+
+
+
+`;
+
+exports[`App Route /package-upload unauthenticated 2`] = `
+
+
+
+
+
+
+ Fisker OTA Portal
+
+
+
+
+
+
+
+
@@ -1107,17 +2093,17 @@ exports[`App Route /page-not-found authenticated 1`] = `
data-testid="mocked-userprovider"
>
@@ -1145,7 +2131,7 @@ exports[`App Route /page-not-found authenticated 1`] = `
Fisker OTA Portal
@@ -1161,13 +2147,13 @@ exports[`App Route /page-not-found authenticated 1`] = `
`;
-exports[`App Route /vehicle-add authenticated 1`] = `
+exports[`App Route /update authenticated 1`] = `
@@ -1427,7 +2413,7 @@ exports[`App Route /vehicle-add authenticated 1`] = `
Fisker OTA Portal
@@ -1443,13 +2429,13 @@ exports[`App Route /vehicle-add authenticated 1`] = `
+
+
+
+
+ Update Package
+ 1
+
+
+
+
+
+
+
+
+
+`;
+
+exports[`App Route /update unauthenticated 1`] = `
+
+
+
+
+
+
+ Fisker OTA Portal
+
+
+
+
+
+
+
+
+
+
+
+
+`;
+
+exports[`App Route /updates authenticated 1`] = `
+
+
+
+
+
+
+
+
+
+
+
+
+ Fisker OTA Portal
+
+
+
+ Sign Out
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ -
+
+
+
+ Home
+
+
+
+
+
+ -
+
+
+
+ View Updates
+
+
+
+
+
+ -
+
+
+
+ Create Updates
+
+
+
+
+
+ -
+
+
+
+ View Vehicles
+
+
+
+
+
+ -
+
+
+
+ Add Vehicles
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Updates
+
+
+
+
+
+
+ ID
+
+
+ Name
+
+
+ Version
+
+
+ Created
+
+
+ Actions
+
+
+
+
+
+
+
+
+
+
+ Rows per page:
+
+
+
+
+
+
+ 0-0 of 0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`;
+
+exports[`App Route /updates unauthenticated 1`] = `
+
+
+
+
+
+
+ Fisker OTA Portal
+
+
+
+
+
+
+
+
+
+
+
+
+`;
+
+exports[`App Route /vehicle-add authenticated 1`] = `
+
+
+
+
+
+
+
+
+
+
+
+
+ Fisker OTA Portal
+
+
+
+ Sign Out
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ -
+
+
+
+ Home
+
+
+
+
+
+ -
+
+
+
+ View Updates
+
+
+
+
+
+ -
+
+
+
+ Create Updates
+
+
+
+
+
+ -
+
+
+
+ View Vehicles
+
+
+
+
+
+ -
+
+
+
+ Add Vehicles
+
+
+
+
+
+
+
+
+
+
@@ -1788,10 +3793,10 @@ exports[`App Route /vehicle-add unauthenticated 1`] = `
data-testid="mocked-userprovider"
>
+
+
+
+
+
+`;
+
+exports[`App Route /vehicle-status authenticated 1`] = `
+
+
+
+
+
+
+
+
+
+
+
+
+ Fisker OTA Portal
+
+
+
+ Sign Out
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ -
+
+
+
+ Home
+
+
+
+
+
+ -
+
+
+
+ View Updates
+
+
+
+
+
+ -
+
+
+
+ Create Updates
+
+
+
+
+
+ -
+
+
+
+ View Vehicles
+
+
+
+
+
+ -
+
+
+
+ Add Vehicles
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ FISKER123
+ Updates
+
+
+
+
+
+
+ ID
+
+
+ Update
+
+
+ Status
+
+
+ Created
+
+
+ Updated
+
+
+
+
+
+
+
+
+
+
+ Rows per page:
+
+
+
+
+
+
+ 0-0 of 0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`;
+
+exports[`App Route /vehicle-status unauthenticated 1`] = `
+
+
+
+
+
+
+ Fisker OTA Portal
+
+
+
+
+
+
+
+
+
+
+
+
+`;
+
+exports[`App Route /vehicles authenticated 1`] = `
+
+
+
+
+
+
+
+
+
+
+
+
+ Fisker OTA Portal
+
+
+
+ Sign Out
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ -
+
+
+
+ Home
+
+
+
+
+
+ -
+
+
+
+ View Updates
+
+
+
+
+
+ -
+
+
+
+ Create Updates
+
+
+
+
+
+ -
+
+
+
+ View Vehicles
+
+
+
+
+
+ -
+
+
+
+ Add Vehicles
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Vehicles
+
+
+
+
+
+
+ VIN
+
+
+ Model
+
+
+ Year
+
+
+ Created
+
+
+ Updated
+
+
+
+
+
+
+
+
+
+
+ Rows per page:
+
+
+
+
+
+
+ 0-0 of 0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`;
+
+exports[`App Route /vehicles unauthenticated 1`] = `
+
+
+
+
+
+
+ Fisker OTA Portal
+
+
+
+
+
+
+
+
diff --git a/src/components/App/index.jsx b/src/components/App/index.jsx
index f7a4f12..9997de3 100644
--- a/src/components/App/index.jsx
+++ b/src/components/App/index.jsx
@@ -5,6 +5,7 @@ import { StatusProvider } from "../Contexts/StatusContext";
import { CssBaseline } from "@material-ui/core";
import MenuDrawer from "../Layouts/MenuDrawer";
import SiteRoutes from "../Routes/SiteRoutes";
+import {} from "../../services/monitoring";
function App() {
return (
diff --git a/src/components/CarUpdates/Deploy/index.jsx b/src/components/CarUpdates/Deploy/index.jsx
index c3c721b..85bacfc 100644
--- a/src/components/CarUpdates/Deploy/index.jsx
+++ b/src/components/CarUpdates/Deploy/index.jsx
@@ -49,18 +49,12 @@ const MainForm = () => {
const handleVehiclesChange = (event) => {
setSelectedVehicles(event.target.value);
};
- const getCarIDs = () => {
- if (!selectedVehicles) return [];
- return selectedVehicles.map((vin) => {
- return vehicles.find((vehicle) => vehicle.vin === vin).id;
- });
- };
const onSubmit = async (event) => {
try {
event.preventDefault();
const data = {
package_id: parseInt(packageid),
- car_ids: getCarIDs(),
+ vins: selectedVehicles,
};
await createCarUpdates(data, token);
setMessage(
diff --git a/src/components/CarUpdates/Status/index.jsx b/src/components/CarUpdates/Status/index.jsx
index e27d875..6df3514 100644
--- a/src/components/CarUpdates/Status/index.jsx
+++ b/src/components/CarUpdates/Status/index.jsx
@@ -20,12 +20,15 @@ import { useUserContext } from "../../Contexts/UserContext";
import { useStatusContext } from "../../Contexts/StatusContext";
import useStyles from "../../useStyles";
import { LocalDateTimeString } from "../../../utils/dates";
+import VehicleStatus from "../../Cars/StatusModal";
const MainForm = () => {
const { packageid } = useParams();
const classes = useStyles();
const [pageSize, setPageSize] = useState(10);
const [pageIndex, setPageIndex] = useState(0);
+ const [viewVIN, setViewVIN] = useState(null);
+
const {
getCarUpdates,
carUpdates,
@@ -74,6 +77,15 @@ const MainForm = () => {
setPageIndex(0);
};
+ const handleViewVIN = (event) => {
+ event.preventDefault();
+ setViewVIN(event.target.innerHTML);
+ };
+
+ const handleCloseViewVIN = (event) => {
+ setViewVIN(null);
+ };
+
return (
@@ -96,7 +108,11 @@ const MainForm = () => {
{carUpdates.map((row) => (
{row.id}
- {`${row.car.vin} ${row.car.model} ${row.car.year}`}
+
+
+ {row.vin}
+
+
{row.status}
{LocalDateTimeString(row.created)}
@@ -126,6 +142,7 @@ const MainForm = () => {
+
);
};
diff --git a/src/components/Cars/List/index.jsx b/src/components/Cars/List/index.jsx
index b3012b0..933e1da 100644
--- a/src/components/Cars/List/index.jsx
+++ b/src/components/Cars/List/index.jsx
@@ -1,4 +1,5 @@
import React, { useEffect, useState } from "react";
+import { Link } from "react-router-dom";
import {
Table,
TableBody,
@@ -65,7 +66,6 @@ const MainForm = () => {
- ID
VIN
Model
Year
@@ -75,9 +75,10 @@ const MainForm = () => {
{vehicles.map((row) => (
-
- {row.id}
- {row.vin}
+
+
+ {row.vin}
+
{row.model}
{row.year}
diff --git a/src/components/Cars/Status/index.jsx b/src/components/Cars/Status/index.jsx
new file mode 100644
index 0000000..37facd6
--- /dev/null
+++ b/src/components/Cars/Status/index.jsx
@@ -0,0 +1,122 @@
+import React, { useEffect, useState } from "react";
+import { useParams } from "react-router";
+import {
+ Table,
+ TableBody,
+ TableCell,
+ TableContainer,
+ TableFooter,
+ TableHead,
+ TablePagination,
+ TableRow,
+ Typography,
+} from "@material-ui/core";
+
+import {
+ UpdatesProvider,
+ useUpdatesContext,
+} from "../../Contexts/UpdatesContext";
+import { useUserContext } from "../../Contexts/UserContext";
+import { useStatusContext } from "../../Contexts/StatusContext";
+import useStyles from "../../useStyles";
+import { LocalDateTimeString } from "../../../utils/dates";
+
+const MainForm = () => {
+ const { vin } = useParams();
+ const classes = useStyles();
+ const [pageSize, setPageSize] = useState(10);
+ const [pageIndex, setPageIndex] = useState(0);
+ const { getCarUpdates, carUpdates, totalCarUpdates } = useUpdatesContext();
+ const { setMessage } = useStatusContext();
+ const {
+ token: {
+ idToken: { jwtToken: token },
+ },
+ } = useUserContext();
+
+ useEffect(() => {
+ try {
+ getCarUpdates(
+ {
+ vin,
+ limit: pageSize,
+ offset: pageSize * pageIndex,
+ },
+ token
+ );
+ } catch (e) {
+ setMessage(e.message);
+ }
+ // eslint-disable-next-line
+ }, [pageIndex, pageSize, token]);
+
+ const handleChangePageIndex = (event, newIndex) => {
+ setPageIndex(newIndex);
+ };
+
+ const handleChangePageSize = (event) => {
+ setPageSize(parseInt(event.target.value, 10));
+ setPageIndex(0);
+ };
+
+ return (
+
+
+ {vin} Updates
+
+
+
+
+
+ ID
+ Update
+ Status
+ Created
+ Updated
+
+
+
+ {carUpdates.map((row) => (
+
+ {row.id}
+ {`${row.updatepackage.package_name} ${row.updatepackage.version}`}
+ {row.status}
+
+ {LocalDateTimeString(row.created)}
+
+
+ {LocalDateTimeString(row.updated)}
+
+
+ ))}
+
+
+
+
+
+
+
+
+
+ );
+};
+
+const CarUpdates = () => (
+
+
+
+);
+
+export default CarUpdates;
diff --git a/src/components/Cars/StatusModal/index.jsx b/src/components/Cars/StatusModal/index.jsx
new file mode 100644
index 0000000..fc12d97
--- /dev/null
+++ b/src/components/Cars/StatusModal/index.jsx
@@ -0,0 +1,91 @@
+import React, { useEffect, useState } from "react";
+import {
+ Backdrop,
+ Modal,
+ Fade,
+ Table,
+ TableHead,
+ TableRow,
+ TableCell,
+ TableBody,
+} from "@material-ui/core";
+
+import useStyles from "../../useStyles";
+import { useUpdatesContext } from "../../Contexts/UpdatesContext";
+import { useUserContext } from "../../Contexts/UserContext";
+import { useStatusContext } from "../../Contexts/StatusContext";
+import { LocalDateTimeString } from "../../../utils/dates";
+
+export default function CarStatusModal(props) {
+ const classes = useStyles();
+ const [updates, setUpdates] = useState([]);
+ const { setMessage } = useStatusContext();
+ const { getVINUpdates } = useUpdatesContext();
+ const {
+ token: {
+ idToken: { jwtToken: token },
+ },
+ } = useUserContext();
+
+ useEffect(() => {
+ (async () => {
+ try {
+ if (!props.vin) return;
+ const result = await getVINUpdates(props.vin, token);
+ if (result.error) {
+ throw new Error(`Get VIN updates error. ${result.message}`);
+ } else {
+ setUpdates(result.data);
+ }
+ } catch (e) {
+ setMessage(e.message);
+ }
+ })();
+ // eslint-disable-next-line
+ }, [props.vin]);
+ return (
+
+
+
+
+ {props.vin} Updates
+
+
+
+ Date
+ Update
+ Status
+ Updated
+
+
+
+ {updates.map((update) => (
+
+
+ {LocalDateTimeString(update.created)}
+
+ {`${update.updatepackage.package_name} ${update.updatepackage.version}`}
+ {update.status}
+
+ {LocalDateTimeString(update.updated)}
+
+
+ ))}
+
+
+
+
+
+
+ );
+}
diff --git a/src/components/Contexts/UpdatesContext.jsx b/src/components/Contexts/UpdatesContext.jsx
index 97bf99c..5b89a44 100644
--- a/src/components/Contexts/UpdatesContext.jsx
+++ b/src/components/Contexts/UpdatesContext.jsx
@@ -81,6 +81,21 @@ export const UpdatesProvider = ({ children }) => {
return result;
};
+ const getVINUpdates = async (vin, token) => {
+ let result;
+
+ try {
+ setBusy(true);
+ result = await api.getVINUpdates(vin, token);
+ if (result.error)
+ throw new Error(`Get VIN updates error. ${result.message}`);
+ } finally {
+ setBusy(false);
+ }
+
+ return result;
+ };
+
return (
{
updatePackage,
createCarUpdates,
getCarUpdates,
+ getVINUpdates,
}}
>
{children}
@@ -125,7 +141,7 @@ const validateCreateCarUpdates = (data) => {
throw new Error("Package id required");
}
- if (!data.car_ids || data.car_ids.length === 0) {
+ if (!data.vins || data.vins.length === 0) {
throw new Error("Car ids required");
}
};
diff --git a/src/components/Contexts/UpdateContext.test.jsx b/src/components/Contexts/UpdatesContext.test.jsx
similarity index 96%
rename from src/components/Contexts/UpdateContext.test.jsx
rename to src/components/Contexts/UpdatesContext.test.jsx
index a3a3ed7..8baa89d 100644
--- a/src/components/Contexts/UpdateContext.test.jsx
+++ b/src/components/Contexts/UpdatesContext.test.jsx
@@ -7,8 +7,8 @@ import {
fireEvent,
waitFor,
} from "@testing-library/react";
-import { UpdatesProvider, useUpdatesContext } from "../Contexts/UpdatesContext";
-import { StatusProvider, useStatusContext } from "../Contexts/StatusContext";
+import { UpdatesProvider, useUpdatesContext } from "./UpdatesContext";
+import { StatusProvider, useStatusContext } from "./StatusContext";
import { TEST_AUTH_OBJECT } from "../../utils/testing";
describe("UpdatesContext", () => {
@@ -222,7 +222,7 @@ describe("UpdatesContext", () => {
data-testid="with-bad-data"
onClick={async () => {
result = await exec(
- { package_id: 1, car_ids: [] },
+ { package_id: 1, vins: [] },
TEST_AUTH_OBJECT
);
}}
@@ -233,7 +233,7 @@ describe("UpdatesContext", () => {
result = await exec(
{
package_id: 1,
- car_ids: [1, 2, 3],
+ vins: ["FISKER123", "FISKER124", "FISKER125"],
},
TEST_AUTH_OBJECT
);
@@ -284,7 +284,7 @@ describe("UpdatesContext", () => {
checkState("false", "", {
id: 1,
package_id: 1,
- car_ids: [1, 2, 3],
+ vins: ["FISKER123", "FISKER124", "FISKER125"],
});
});
});
diff --git a/src/components/Contexts/__mocks__/UpdatesContext.jsx b/src/components/Contexts/__mocks__/UpdatesContext.jsx
new file mode 100644
index 0000000..0521357
--- /dev/null
+++ b/src/components/Contexts/__mocks__/UpdatesContext.jsx
@@ -0,0 +1,26 @@
+import React from "react";
+
+const UpdatesContext = React.createContext();
+
+let busy = false;
+let packages = [];
+let totalPackages = 0;
+let carUpdates = [];
+let totalCarUpdates = 0;
+
+export const UpdatesProvider = ({ children }) => {
+ return {children};
+};
+
+export const useUpdatesContext = () => ({
+ busy,
+ packages,
+ totalPackages,
+ carUpdates,
+ totalCarUpdates,
+ getPackages: jest.fn(() => packages),
+ updatePackage: jest.fn((data) => data),
+ createCarUpdates: jest.fn((data) => data),
+ getCarUpdates: jest.fn(() => carUpdates),
+ getVINUpdates: jest.fn(() => carUpdates),
+});
diff --git a/src/components/Contexts/__mocks__/VehicleContext.jsx b/src/components/Contexts/__mocks__/VehicleContext.jsx
index 4813960..566826c 100644
--- a/src/components/Contexts/__mocks__/VehicleContext.jsx
+++ b/src/components/Contexts/__mocks__/VehicleContext.jsx
@@ -2,6 +2,7 @@ import React from "react";
let busy = false;
let vehicles = [];
+let totalVehicles = 0;
let error = null;
export const VehicleProvider = ({ children }) => {
@@ -11,6 +12,7 @@ export const VehicleProvider = ({ children }) => {
export const useVehicleContext = () => ({
busy,
vehicles,
+ totalVehicles,
getVehicles: jest.fn(() => vehicles),
addVehicle: jest.fn(),
});
diff --git a/src/components/ErrorBoundary.jsx b/src/components/ErrorBoundary.jsx
index c7931ae..ad4a168 100644
--- a/src/components/ErrorBoundary.jsx
+++ b/src/components/ErrorBoundary.jsx
@@ -2,25 +2,60 @@ import React, { Component } from "react";
import PropTypes from "prop-types";
import { Typography } from "@material-ui/core";
+const reload = () => {
+ window.location.reload();
+};
export default class ErrorBoundary extends Component {
state = {
error: "",
errorInfo: "",
hasError: false,
};
+
static getDerivedStateFromError(error) {
return { hasError: true, error };
}
+
componentDidCatch(error, errorInfo) {
this.setState({ errorInfo });
}
+
render() {
- if (this.state.hasError)
+ if (this.state.hasError) {
+ if (this.state.error && this.state.error.name === "ChunkLoadError") {
+ reload();
+ return;
+ }
+
return (
-
- Oops. An React JS Error Occured.
-
+
+
+ Sorry, an error has occured and been logged
+
+
+ Click to reload
+
+
);
+ }
return this.props.children;
}
}
diff --git a/src/components/Routes/SiteRoutes.jsx b/src/components/Routes/SiteRoutes.jsx
index 9a52f1e..602f52d 100644
--- a/src/components/Routes/SiteRoutes.jsx
+++ b/src/components/Routes/SiteRoutes.jsx
@@ -15,6 +15,7 @@ const UpdatePackagesForm = React.lazy(() => import("../UpdatePackages/List"));
const UpdatePackageEdit = React.lazy(() => import("../UpdatePackages/Edit"));
const CarUpdatesDeploy = React.lazy(() => import("../CarUpdates/Deploy"));
const CarUpdatesStatus = React.lazy(() => import("../CarUpdates/Status"));
+const CarUpdates = React.lazy(() => import("../Cars/Status"));
const VehiclesList = React.lazy(() => import("../Cars/List"));
const SiteRoutes = () => {
@@ -92,6 +93,14 @@ const SiteRoutes = () => {
groups={groups}
roles={[Roles.CREATE]}
/>
+ }
+ type={TYPES.PROTECTED}
+ token={token}
+ groups={groups}
+ roles={[Roles.READ, Roles.CREATE]}
+ />
diff --git a/src/components/SSOForm/__snapshots__/SSOForm.test.js.snap b/src/components/SSOForm/__snapshots__/SSOForm.test.js.snap
index d7bb611..0779f27 100644
--- a/src/components/SSOForm/__snapshots__/SSOForm.test.js.snap
+++ b/src/components/SSOForm/__snapshots__/SSOForm.test.js.snap
@@ -3,12 +3,12 @@
exports[`Sign In Form Should render 1`] = `
diff --git a/src/components/useStyles.jsx b/src/components/useStyles.jsx
index c000ab0..a1b6ee1 100644
--- a/src/components/useStyles.jsx
+++ b/src/components/useStyles.jsx
@@ -5,6 +5,17 @@ const MENUITEM_PADDING_TOP = 8;
const DRAWER_WIDTH = 240;
const useStyles = makeStyles((theme) => ({
+ modal: {
+ display: "flex",
+ alignItems: "center",
+ justifyContent: "center",
+ },
+ modaldialog: {
+ backgroundColor: theme.palette.background.paper,
+ border: "2px solid #000",
+ boxShadow: theme.shadows[5],
+ padding: theme.spacing(2, 4, 3),
+ },
paper: {
marginTop: theme.spacing(8),
display: "flex",
@@ -105,6 +116,13 @@ const useStyles = makeStyles((theme) => ({
marginLeft: "auto",
marginRight: -12,
},
+ link: {
+ cursor: "pointer",
+ textDecorationColor: "Blue",
+ textDecorationStyle: "solid",
+ textDecorationLine: "underline",
+ color: "Blue",
+ },
}));
export default useStyles;
diff --git a/src/services/__mocks__/monitoring.js b/src/services/__mocks__/monitoring.js
new file mode 100644
index 0000000..4e16c72
--- /dev/null
+++ b/src/services/__mocks__/monitoring.js
@@ -0,0 +1 @@
+// no actual monitoring with mock
\ No newline at end of file
diff --git a/src/services/__mocks__/updates.js b/src/services/__mocks__/updates.js
index 5c8c669..74704dd 100644
--- a/src/services/__mocks__/updates.js
+++ b/src/services/__mocks__/updates.js
@@ -41,7 +41,11 @@ const updatesAPI = {
},
getCarUpdates: async (filter, token) => {
- return [];
+ return { data:[] };
+ },
+
+ getVINUpdates: async (vin, token) => {
+ return { data:[] };
},
};
diff --git a/src/services/monitoring.js b/src/services/monitoring.js
new file mode 100644
index 0000000..ac2338b
--- /dev/null
+++ b/src/services/monitoring.js
@@ -0,0 +1,12 @@
+import { datadogRum } from '@datadog/browser-rum';
+
+datadogRum.init({
+ applicationId: '8ecd160c-ad5c-4e06-8d88-3a6b89833246',
+ clientToken: 'pubeb25449bb91773fc993855c7378e375a',
+ site: 'datadoghq.com',
+ service:'ota-portal',
+ // Specify a version number to identify the deployed version of your application in Datadog
+ // version: '1.0.0',
+ sampleRate: 100,
+ trackInteractions: true
+});
\ No newline at end of file
diff --git a/src/services/updates.js b/src/services/updates.js
index 1b7c328..c5788e2 100644
--- a/src/services/updates.js
+++ b/src/services/updates.js
@@ -37,6 +37,14 @@ const updatesAPI = {
.then(fetchRespHandler);
},
+ getVINUpdates: async (vin, token) => {
+ var u = addQueryParams(`${API_ENDPOINT}/carupdates`, { vin });
+ return fetch(u, {
+ method: "GET",
+ headers: Object.assign({ "Content-Type": "application/json" }, getAuthHeaderOptions(token)),
+ })
+ .then(fetchRespHandler);
+ },
};
export default updatesAPI;
@@ -592,17 +1521,17 @@ exports[`App Route /package-upload authenticated 1`] = `
data-testid="mocked-userprovider"
>
@@ -630,7 +1559,7 @@ exports[`App Route /package-upload authenticated 1`] = `
Fisker OTA Portal
@@ -646,13 +1575,13 @@ exports[`App Route /package-upload authenticated 1`] = `
@@ -1050,10 +1979,10 @@ exports[`App Route /package-upload unauthenticated 1`] = `
data-testid="mocked-userprovider"
>
+
+
+
+
+
+`;
+
+exports[`App Route /package-upload unauthenticated 2`] = `
+
+
+
+
+
+
+
+
+
+
+ + Fisker OTA Portal +
+
+
@@ -1107,17 +2093,17 @@ exports[`App Route /page-not-found authenticated 1`] = `
data-testid="mocked-userprovider"
>
@@ -1145,7 +2131,7 @@ exports[`App Route /page-not-found authenticated 1`] = `
Fisker OTA Portal
@@ -1161,13 +2147,13 @@ exports[`App Route /page-not-found authenticated 1`] = `
`;
-exports[`App Route /vehicle-add authenticated 1`] = `
+exports[`App Route /update authenticated 1`] = `
@@ -1427,7 +2413,7 @@ exports[`App Route /vehicle-add authenticated 1`] = `
Fisker OTA Portal
@@ -1443,13 +2429,13 @@ exports[`App Route /vehicle-add authenticated 1`] = `
+
+
+
+
+ Update Package
+ 1
+
+
+
+
+
+
+
+
+
+`;
+
+exports[`App Route /update unauthenticated 1`] = `
+
+
+
+
+
+
+ Fisker OTA Portal
+
+
+
+
+
+
+
+
+
+
+
+
+`;
+
+exports[`App Route /updates authenticated 1`] = `
+
+
+
+
+
+
+
+
+
+
+
+
+ Fisker OTA Portal
+
+
+
+ Sign Out
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ -
+
+
+
+ Home
+
+
+
+
+
+ -
+
+
+
+ View Updates
+
+
+
+
+
+ -
+
+
+
+ Create Updates
+
+
+
+
+
+ -
+
+
+
+ View Vehicles
+
+
+
+
+
+ -
+
+
+
+ Add Vehicles
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Updates
+
+
+
+
+
+
+ ID
+
+
+ Name
+
+
+ Version
+
+
+ Created
+
+
+ Actions
+
+
+
+
+
+
+
+
+
+
+ Rows per page:
+
+
+
+
+
+
+ 0-0 of 0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`;
+
+exports[`App Route /updates unauthenticated 1`] = `
+
+
+
+
+
+
+ Fisker OTA Portal
+
+
+
+
+
+
+
+
+
+
+
+
+`;
+
+exports[`App Route /vehicle-add authenticated 1`] = `
+
+
+
+
+
+
+
+
+
+
+
+
+ Fisker OTA Portal
+
+
+
+ Sign Out
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ -
+
+
+
+ Home
+
+
+
+
+
+ -
+
+
+
+ View Updates
+
+
+
+
+
+ -
+
+
+
+ Create Updates
+
+
+
+
+
+ -
+
+
+
+ View Vehicles
+
+
+
+
+
+ -
+
+
+
+ Add Vehicles
+
+
+
+
+
+
+
+
+
+
@@ -1788,10 +3793,10 @@ exports[`App Route /vehicle-add unauthenticated 1`] = `
data-testid="mocked-userprovider"
>
+
+
+
+
+
+`;
+
+exports[`App Route /vehicle-status authenticated 1`] = `
+
+
+
+
+
+
+
+
+
+
+
+
+ Fisker OTA Portal
+
+
+
+ Sign Out
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ -
+
+
+
+ Home
+
+
+
+
+
+ -
+
+
+
+ View Updates
+
+
+
+
+
+ -
+
+
+
+ Create Updates
+
+
+
+
+
+ -
+
+
+
+ View Vehicles
+
+
+
+
+
+ -
+
+
+
+ Add Vehicles
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ FISKER123
+ Updates
+
+
+
+
+
+
+ ID
+
+
+ Update
+
+
+ Status
+
+
+ Created
+
+
+ Updated
+
+
+
+
+
+
+
+
+
+
+ Rows per page:
+
+
+
+
+
+
+ 0-0 of 0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`;
+
+exports[`App Route /vehicle-status unauthenticated 1`] = `
+
+
+
+
+
+
+ Fisker OTA Portal
+
+
+
+
+
+
+
+
+
+
+
+
+`;
+
+exports[`App Route /vehicles authenticated 1`] = `
+
+
+
+
+
+
+
+
+
+
+
+
+ Fisker OTA Portal
+
+
+
+ Sign Out
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ -
+
+
+
+ Home
+
+
+
+
+
+ -
+
+
+
+ View Updates
+
+
+
+
+
+ -
+
+
+
+ Create Updates
+
+
+
+
+
+ -
+
+
+
+ View Vehicles
+
+
+
+
+
+ -
+
+
+
+ Add Vehicles
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Vehicles
+
+
+
+
+
+
+ VIN
+
+
+ Model
+
+
+ Year
+
+
+ Created
+
+
+ Updated
+
+
+
+
+
+
+
+
+
+
+ Rows per page:
+
+
+
+
+
+
+ 0-0 of 0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`;
+
+exports[`App Route /vehicles unauthenticated 1`] = `
+
+
+
+
+
+
+ Fisker OTA Portal
+
+
+
+
+
+
+
+
diff --git a/src/components/App/index.jsx b/src/components/App/index.jsx
index f7a4f12..9997de3 100644
--- a/src/components/App/index.jsx
+++ b/src/components/App/index.jsx
@@ -5,6 +5,7 @@ import { StatusProvider } from "../Contexts/StatusContext";
import { CssBaseline } from "@material-ui/core";
import MenuDrawer from "../Layouts/MenuDrawer";
import SiteRoutes from "../Routes/SiteRoutes";
+import {} from "../../services/monitoring";
function App() {
return (
diff --git a/src/components/CarUpdates/Deploy/index.jsx b/src/components/CarUpdates/Deploy/index.jsx
index c3c721b..85bacfc 100644
--- a/src/components/CarUpdates/Deploy/index.jsx
+++ b/src/components/CarUpdates/Deploy/index.jsx
@@ -49,18 +49,12 @@ const MainForm = () => {
const handleVehiclesChange = (event) => {
setSelectedVehicles(event.target.value);
};
- const getCarIDs = () => {
- if (!selectedVehicles) return [];
- return selectedVehicles.map((vin) => {
- return vehicles.find((vehicle) => vehicle.vin === vin).id;
- });
- };
const onSubmit = async (event) => {
try {
event.preventDefault();
const data = {
package_id: parseInt(packageid),
- car_ids: getCarIDs(),
+ vins: selectedVehicles,
};
await createCarUpdates(data, token);
setMessage(
diff --git a/src/components/CarUpdates/Status/index.jsx b/src/components/CarUpdates/Status/index.jsx
index e27d875..6df3514 100644
--- a/src/components/CarUpdates/Status/index.jsx
+++ b/src/components/CarUpdates/Status/index.jsx
@@ -20,12 +20,15 @@ import { useUserContext } from "../../Contexts/UserContext";
import { useStatusContext } from "../../Contexts/StatusContext";
import useStyles from "../../useStyles";
import { LocalDateTimeString } from "../../../utils/dates";
+import VehicleStatus from "../../Cars/StatusModal";
const MainForm = () => {
const { packageid } = useParams();
const classes = useStyles();
const [pageSize, setPageSize] = useState(10);
const [pageIndex, setPageIndex] = useState(0);
+ const [viewVIN, setViewVIN] = useState(null);
+
const {
getCarUpdates,
carUpdates,
@@ -74,6 +77,15 @@ const MainForm = () => {
setPageIndex(0);
};
+ const handleViewVIN = (event) => {
+ event.preventDefault();
+ setViewVIN(event.target.innerHTML);
+ };
+
+ const handleCloseViewVIN = (event) => {
+ setViewVIN(null);
+ };
+
return (
@@ -96,7 +108,11 @@ const MainForm = () => {
{carUpdates.map((row) => (
{row.id}
- {`${row.car.vin} ${row.car.model} ${row.car.year}`}
+
+
+ {row.vin}
+
+
{row.status}
{LocalDateTimeString(row.created)}
@@ -126,6 +142,7 @@ const MainForm = () => {
+
);
};
diff --git a/src/components/Cars/List/index.jsx b/src/components/Cars/List/index.jsx
index b3012b0..933e1da 100644
--- a/src/components/Cars/List/index.jsx
+++ b/src/components/Cars/List/index.jsx
@@ -1,4 +1,5 @@
import React, { useEffect, useState } from "react";
+import { Link } from "react-router-dom";
import {
Table,
TableBody,
@@ -65,7 +66,6 @@ const MainForm = () => {
- ID
VIN
Model
Year
@@ -75,9 +75,10 @@ const MainForm = () => {
{vehicles.map((row) => (
-
- {row.id}
- {row.vin}
+
+
+ {row.vin}
+
{row.model}
{row.year}
diff --git a/src/components/Cars/Status/index.jsx b/src/components/Cars/Status/index.jsx
new file mode 100644
index 0000000..37facd6
--- /dev/null
+++ b/src/components/Cars/Status/index.jsx
@@ -0,0 +1,122 @@
+import React, { useEffect, useState } from "react";
+import { useParams } from "react-router";
+import {
+ Table,
+ TableBody,
+ TableCell,
+ TableContainer,
+ TableFooter,
+ TableHead,
+ TablePagination,
+ TableRow,
+ Typography,
+} from "@material-ui/core";
+
+import {
+ UpdatesProvider,
+ useUpdatesContext,
+} from "../../Contexts/UpdatesContext";
+import { useUserContext } from "../../Contexts/UserContext";
+import { useStatusContext } from "../../Contexts/StatusContext";
+import useStyles from "../../useStyles";
+import { LocalDateTimeString } from "../../../utils/dates";
+
+const MainForm = () => {
+ const { vin } = useParams();
+ const classes = useStyles();
+ const [pageSize, setPageSize] = useState(10);
+ const [pageIndex, setPageIndex] = useState(0);
+ const { getCarUpdates, carUpdates, totalCarUpdates } = useUpdatesContext();
+ const { setMessage } = useStatusContext();
+ const {
+ token: {
+ idToken: { jwtToken: token },
+ },
+ } = useUserContext();
+
+ useEffect(() => {
+ try {
+ getCarUpdates(
+ {
+ vin,
+ limit: pageSize,
+ offset: pageSize * pageIndex,
+ },
+ token
+ );
+ } catch (e) {
+ setMessage(e.message);
+ }
+ // eslint-disable-next-line
+ }, [pageIndex, pageSize, token]);
+
+ const handleChangePageIndex = (event, newIndex) => {
+ setPageIndex(newIndex);
+ };
+
+ const handleChangePageSize = (event) => {
+ setPageSize(parseInt(event.target.value, 10));
+ setPageIndex(0);
+ };
+
+ return (
+
+
+ {vin} Updates
+
+
+
+
+
+ ID
+ Update
+ Status
+ Created
+ Updated
+
+
+
+ {carUpdates.map((row) => (
+
+ {row.id}
+ {`${row.updatepackage.package_name} ${row.updatepackage.version}`}
+ {row.status}
+
+ {LocalDateTimeString(row.created)}
+
+
+ {LocalDateTimeString(row.updated)}
+
+
+ ))}
+
+
+
+
+
+
+
+
+
+ );
+};
+
+const CarUpdates = () => (
+
+
+
+);
+
+export default CarUpdates;
diff --git a/src/components/Cars/StatusModal/index.jsx b/src/components/Cars/StatusModal/index.jsx
new file mode 100644
index 0000000..fc12d97
--- /dev/null
+++ b/src/components/Cars/StatusModal/index.jsx
@@ -0,0 +1,91 @@
+import React, { useEffect, useState } from "react";
+import {
+ Backdrop,
+ Modal,
+ Fade,
+ Table,
+ TableHead,
+ TableRow,
+ TableCell,
+ TableBody,
+} from "@material-ui/core";
+
+import useStyles from "../../useStyles";
+import { useUpdatesContext } from "../../Contexts/UpdatesContext";
+import { useUserContext } from "../../Contexts/UserContext";
+import { useStatusContext } from "../../Contexts/StatusContext";
+import { LocalDateTimeString } from "../../../utils/dates";
+
+export default function CarStatusModal(props) {
+ const classes = useStyles();
+ const [updates, setUpdates] = useState([]);
+ const { setMessage } = useStatusContext();
+ const { getVINUpdates } = useUpdatesContext();
+ const {
+ token: {
+ idToken: { jwtToken: token },
+ },
+ } = useUserContext();
+
+ useEffect(() => {
+ (async () => {
+ try {
+ if (!props.vin) return;
+ const result = await getVINUpdates(props.vin, token);
+ if (result.error) {
+ throw new Error(`Get VIN updates error. ${result.message}`);
+ } else {
+ setUpdates(result.data);
+ }
+ } catch (e) {
+ setMessage(e.message);
+ }
+ })();
+ // eslint-disable-next-line
+ }, [props.vin]);
+ return (
+
+
+
+
+ {props.vin} Updates
+
+
+
+ Date
+ Update
+ Status
+ Updated
+
+
+
+ {updates.map((update) => (
+
+
+ {LocalDateTimeString(update.created)}
+
+ {`${update.updatepackage.package_name} ${update.updatepackage.version}`}
+ {update.status}
+
+ {LocalDateTimeString(update.updated)}
+
+
+ ))}
+
+
+
+
+
+
+ );
+}
diff --git a/src/components/Contexts/UpdatesContext.jsx b/src/components/Contexts/UpdatesContext.jsx
index 97bf99c..5b89a44 100644
--- a/src/components/Contexts/UpdatesContext.jsx
+++ b/src/components/Contexts/UpdatesContext.jsx
@@ -81,6 +81,21 @@ export const UpdatesProvider = ({ children }) => {
return result;
};
+ const getVINUpdates = async (vin, token) => {
+ let result;
+
+ try {
+ setBusy(true);
+ result = await api.getVINUpdates(vin, token);
+ if (result.error)
+ throw new Error(`Get VIN updates error. ${result.message}`);
+ } finally {
+ setBusy(false);
+ }
+
+ return result;
+ };
+
return (
{
updatePackage,
createCarUpdates,
getCarUpdates,
+ getVINUpdates,
}}
>
{children}
@@ -125,7 +141,7 @@ const validateCreateCarUpdates = (data) => {
throw new Error("Package id required");
}
- if (!data.car_ids || data.car_ids.length === 0) {
+ if (!data.vins || data.vins.length === 0) {
throw new Error("Car ids required");
}
};
diff --git a/src/components/Contexts/UpdateContext.test.jsx b/src/components/Contexts/UpdatesContext.test.jsx
similarity index 96%
rename from src/components/Contexts/UpdateContext.test.jsx
rename to src/components/Contexts/UpdatesContext.test.jsx
index a3a3ed7..8baa89d 100644
--- a/src/components/Contexts/UpdateContext.test.jsx
+++ b/src/components/Contexts/UpdatesContext.test.jsx
@@ -7,8 +7,8 @@ import {
fireEvent,
waitFor,
} from "@testing-library/react";
-import { UpdatesProvider, useUpdatesContext } from "../Contexts/UpdatesContext";
-import { StatusProvider, useStatusContext } from "../Contexts/StatusContext";
+import { UpdatesProvider, useUpdatesContext } from "./UpdatesContext";
+import { StatusProvider, useStatusContext } from "./StatusContext";
import { TEST_AUTH_OBJECT } from "../../utils/testing";
describe("UpdatesContext", () => {
@@ -222,7 +222,7 @@ describe("UpdatesContext", () => {
data-testid="with-bad-data"
onClick={async () => {
result = await exec(
- { package_id: 1, car_ids: [] },
+ { package_id: 1, vins: [] },
TEST_AUTH_OBJECT
);
}}
@@ -233,7 +233,7 @@ describe("UpdatesContext", () => {
result = await exec(
{
package_id: 1,
- car_ids: [1, 2, 3],
+ vins: ["FISKER123", "FISKER124", "FISKER125"],
},
TEST_AUTH_OBJECT
);
@@ -284,7 +284,7 @@ describe("UpdatesContext", () => {
checkState("false", "", {
id: 1,
package_id: 1,
- car_ids: [1, 2, 3],
+ vins: ["FISKER123", "FISKER124", "FISKER125"],
});
});
});
diff --git a/src/components/Contexts/__mocks__/UpdatesContext.jsx b/src/components/Contexts/__mocks__/UpdatesContext.jsx
new file mode 100644
index 0000000..0521357
--- /dev/null
+++ b/src/components/Contexts/__mocks__/UpdatesContext.jsx
@@ -0,0 +1,26 @@
+import React from "react";
+
+const UpdatesContext = React.createContext();
+
+let busy = false;
+let packages = [];
+let totalPackages = 0;
+let carUpdates = [];
+let totalCarUpdates = 0;
+
+export const UpdatesProvider = ({ children }) => {
+ return {children};
+};
+
+export const useUpdatesContext = () => ({
+ busy,
+ packages,
+ totalPackages,
+ carUpdates,
+ totalCarUpdates,
+ getPackages: jest.fn(() => packages),
+ updatePackage: jest.fn((data) => data),
+ createCarUpdates: jest.fn((data) => data),
+ getCarUpdates: jest.fn(() => carUpdates),
+ getVINUpdates: jest.fn(() => carUpdates),
+});
diff --git a/src/components/Contexts/__mocks__/VehicleContext.jsx b/src/components/Contexts/__mocks__/VehicleContext.jsx
index 4813960..566826c 100644
--- a/src/components/Contexts/__mocks__/VehicleContext.jsx
+++ b/src/components/Contexts/__mocks__/VehicleContext.jsx
@@ -2,6 +2,7 @@ import React from "react";
let busy = false;
let vehicles = [];
+let totalVehicles = 0;
let error = null;
export const VehicleProvider = ({ children }) => {
@@ -11,6 +12,7 @@ export const VehicleProvider = ({ children }) => {
export const useVehicleContext = () => ({
busy,
vehicles,
+ totalVehicles,
getVehicles: jest.fn(() => vehicles),
addVehicle: jest.fn(),
});
diff --git a/src/components/ErrorBoundary.jsx b/src/components/ErrorBoundary.jsx
index c7931ae..ad4a168 100644
--- a/src/components/ErrorBoundary.jsx
+++ b/src/components/ErrorBoundary.jsx
@@ -2,25 +2,60 @@ import React, { Component } from "react";
import PropTypes from "prop-types";
import { Typography } from "@material-ui/core";
+const reload = () => {
+ window.location.reload();
+};
export default class ErrorBoundary extends Component {
state = {
error: "",
errorInfo: "",
hasError: false,
};
+
static getDerivedStateFromError(error) {
return { hasError: true, error };
}
+
componentDidCatch(error, errorInfo) {
this.setState({ errorInfo });
}
+
render() {
- if (this.state.hasError)
+ if (this.state.hasError) {
+ if (this.state.error && this.state.error.name === "ChunkLoadError") {
+ reload();
+ return;
+ }
+
return (
-
- Oops. An React JS Error Occured.
-
+
+
+ Sorry, an error has occured and been logged
+
+
+ Click to reload
+
+
);
+ }
return this.props.children;
}
}
diff --git a/src/components/Routes/SiteRoutes.jsx b/src/components/Routes/SiteRoutes.jsx
index 9a52f1e..602f52d 100644
--- a/src/components/Routes/SiteRoutes.jsx
+++ b/src/components/Routes/SiteRoutes.jsx
@@ -15,6 +15,7 @@ const UpdatePackagesForm = React.lazy(() => import("../UpdatePackages/List"));
const UpdatePackageEdit = React.lazy(() => import("../UpdatePackages/Edit"));
const CarUpdatesDeploy = React.lazy(() => import("../CarUpdates/Deploy"));
const CarUpdatesStatus = React.lazy(() => import("../CarUpdates/Status"));
+const CarUpdates = React.lazy(() => import("../Cars/Status"));
const VehiclesList = React.lazy(() => import("../Cars/List"));
const SiteRoutes = () => {
@@ -92,6 +93,14 @@ const SiteRoutes = () => {
groups={groups}
roles={[Roles.CREATE]}
/>
+ }
+ type={TYPES.PROTECTED}
+ token={token}
+ groups={groups}
+ roles={[Roles.READ, Roles.CREATE]}
+ />
diff --git a/src/components/SSOForm/__snapshots__/SSOForm.test.js.snap b/src/components/SSOForm/__snapshots__/SSOForm.test.js.snap
index d7bb611..0779f27 100644
--- a/src/components/SSOForm/__snapshots__/SSOForm.test.js.snap
+++ b/src/components/SSOForm/__snapshots__/SSOForm.test.js.snap
@@ -3,12 +3,12 @@
exports[`Sign In Form Should render 1`] = `
diff --git a/src/components/useStyles.jsx b/src/components/useStyles.jsx
index c000ab0..a1b6ee1 100644
--- a/src/components/useStyles.jsx
+++ b/src/components/useStyles.jsx
@@ -5,6 +5,17 @@ const MENUITEM_PADDING_TOP = 8;
const DRAWER_WIDTH = 240;
const useStyles = makeStyles((theme) => ({
+ modal: {
+ display: "flex",
+ alignItems: "center",
+ justifyContent: "center",
+ },
+ modaldialog: {
+ backgroundColor: theme.palette.background.paper,
+ border: "2px solid #000",
+ boxShadow: theme.shadows[5],
+ padding: theme.spacing(2, 4, 3),
+ },
paper: {
marginTop: theme.spacing(8),
display: "flex",
@@ -105,6 +116,13 @@ const useStyles = makeStyles((theme) => ({
marginLeft: "auto",
marginRight: -12,
},
+ link: {
+ cursor: "pointer",
+ textDecorationColor: "Blue",
+ textDecorationStyle: "solid",
+ textDecorationLine: "underline",
+ color: "Blue",
+ },
}));
export default useStyles;
diff --git a/src/services/__mocks__/monitoring.js b/src/services/__mocks__/monitoring.js
new file mode 100644
index 0000000..4e16c72
--- /dev/null
+++ b/src/services/__mocks__/monitoring.js
@@ -0,0 +1 @@
+// no actual monitoring with mock
\ No newline at end of file
diff --git a/src/services/__mocks__/updates.js b/src/services/__mocks__/updates.js
index 5c8c669..74704dd 100644
--- a/src/services/__mocks__/updates.js
+++ b/src/services/__mocks__/updates.js
@@ -41,7 +41,11 @@ const updatesAPI = {
},
getCarUpdates: async (filter, token) => {
- return [];
+ return { data:[] };
+ },
+
+ getVINUpdates: async (vin, token) => {
+ return { data:[] };
},
};
diff --git a/src/services/monitoring.js b/src/services/monitoring.js
new file mode 100644
index 0000000..ac2338b
--- /dev/null
+++ b/src/services/monitoring.js
@@ -0,0 +1,12 @@
+import { datadogRum } from '@datadog/browser-rum';
+
+datadogRum.init({
+ applicationId: '8ecd160c-ad5c-4e06-8d88-3a6b89833246',
+ clientToken: 'pubeb25449bb91773fc993855c7378e375a',
+ site: 'datadoghq.com',
+ service:'ota-portal',
+ // Specify a version number to identify the deployed version of your application in Datadog
+ // version: '1.0.0',
+ sampleRate: 100,
+ trackInteractions: true
+});
\ No newline at end of file
diff --git a/src/services/updates.js b/src/services/updates.js
index 1b7c328..c5788e2 100644
--- a/src/services/updates.js
+++ b/src/services/updates.js
@@ -37,6 +37,14 @@ const updatesAPI = {
.then(fetchRespHandler);
},
+ getVINUpdates: async (vin, token) => {
+ var u = addQueryParams(`${API_ENDPOINT}/carupdates`, { vin });
+ return fetch(u, {
+ method: "GET",
+ headers: Object.assign({ "Content-Type": "application/json" }, getAuthHeaderOptions(token)),
+ })
+ .then(fetchRespHandler);
+ },
};
export default updatesAPI;
`;
-exports[`App Route /vehicle-add authenticated 1`] = `
+exports[`App Route /update authenticated 1`] = `
@@ -1427,7 +2413,7 @@ exports[`App Route /vehicle-add authenticated 1`] = `
Fisker OTA Portal
@@ -1443,13 +2429,13 @@ exports[`App Route /vehicle-add authenticated 1`] = `
+
+
+
+
+ Update Package
+ 1
+
+
+
+
+
+
+
+
+
+`;
+
+exports[`App Route /update unauthenticated 1`] = `
+
+
+
+
+
+
+ Fisker OTA Portal
+
+
+
+
+
+
+
+
+
+
+
+
+`;
+
+exports[`App Route /updates authenticated 1`] = `
+
+
+
+
+
+
+
+
+
+
+
+
+ Fisker OTA Portal
+
+
+
+ Sign Out
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ -
+
+
+
+ Home
+
+
+
+
+
+ -
+
+
+
+ View Updates
+
+
+
+
+
+ -
+
+
+
+ Create Updates
+
+
+
+
+
+ -
+
+
+
+ View Vehicles
+
+
+
+
+
+ -
+
+
+
+ Add Vehicles
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Updates
+
+
+
+
+
+
+ ID
+
+
+ Name
+
+
+ Version
+
+
+ Created
+
+
+ Actions
+
+
+
+
+
+
+
+
+
+
+ Rows per page:
+
+
+
+
+
+
+ 0-0 of 0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`;
+
+exports[`App Route /updates unauthenticated 1`] = `
+
+
+
+
+
+
+ Fisker OTA Portal
+
+
+
+
+
+
+
+
+
+
+
+
+`;
+
+exports[`App Route /vehicle-add authenticated 1`] = `
+
+
+
+
+
+
+
+
+
+
+
+
+ Fisker OTA Portal
+
+
+
+ Sign Out
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ -
+
+
+
+ Home
+
+
+
+
+
+ -
+
+
+
+ View Updates
+
+
+
+
+
+ -
+
+
+
+ Create Updates
+
+
+
+
+
+ -
+
+
+
+ View Vehicles
+
+
+
+
+
+ -
+
+
+
+ Add Vehicles
+
+
+
+
+
+
+
+
+
+
@@ -1788,10 +3793,10 @@ exports[`App Route /vehicle-add unauthenticated 1`] = `
data-testid="mocked-userprovider"
>
+
+
+
+
+
+`;
+
+exports[`App Route /vehicle-status authenticated 1`] = `
+
+
+
+
+
+
+
+
+
+
+
+
+ Fisker OTA Portal
+
+
+
+ Sign Out
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ -
+
+
+
+ Home
+
+
+
+
+
+ -
+
+
+
+ View Updates
+
+
+
+
+
+ -
+
+
+
+ Create Updates
+
+
+
+
+
+ -
+
+
+
+ View Vehicles
+
+
+
+
+
+ -
+
+
+
+ Add Vehicles
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ FISKER123
+ Updates
+
+
+
+
+
+
+ ID
+
+
+ Update
+
+
+ Status
+
+
+ Created
+
+
+ Updated
+
+
+
+
+
+
+
+
+
+
+ Rows per page:
+
+
+
+
+
+
+ 0-0 of 0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`;
+
+exports[`App Route /vehicle-status unauthenticated 1`] = `
+
+
+
+
+
+
+ Fisker OTA Portal
+
+
+
+
+
+
+
+
+
+
+
+
+`;
+
+exports[`App Route /vehicles authenticated 1`] = `
+
+
+
+
+
+
+
+
+
+
+
+
+ Fisker OTA Portal
+
+
+
+ Sign Out
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ -
+
+
+
+ Home
+
+
+
+
+
+ -
+
+
+
+ View Updates
+
+
+
+
+
+ -
+
+
+
+ Create Updates
+
+
+
+
+
+ -
+
+
+
+ View Vehicles
+
+
+
+
+
+ -
+
+
+
+ Add Vehicles
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Vehicles
+
+
+
+
+
+
+ VIN
+
+
+ Model
+
+
+ Year
+
+
+ Created
+
+
+ Updated
+
+
+
+
+
+
+
+
+
+
+ Rows per page:
+
+
+
+
+
+
+ 0-0 of 0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`;
+
+exports[`App Route /vehicles unauthenticated 1`] = `
+
+
+
+
+
+
+ Fisker OTA Portal
+
+
+
+
+
+
+
+
diff --git a/src/components/App/index.jsx b/src/components/App/index.jsx
index f7a4f12..9997de3 100644
--- a/src/components/App/index.jsx
+++ b/src/components/App/index.jsx
@@ -5,6 +5,7 @@ import { StatusProvider } from "../Contexts/StatusContext";
import { CssBaseline } from "@material-ui/core";
import MenuDrawer from "../Layouts/MenuDrawer";
import SiteRoutes from "../Routes/SiteRoutes";
+import {} from "../../services/monitoring";
function App() {
return (
diff --git a/src/components/CarUpdates/Deploy/index.jsx b/src/components/CarUpdates/Deploy/index.jsx
index c3c721b..85bacfc 100644
--- a/src/components/CarUpdates/Deploy/index.jsx
+++ b/src/components/CarUpdates/Deploy/index.jsx
@@ -49,18 +49,12 @@ const MainForm = () => {
const handleVehiclesChange = (event) => {
setSelectedVehicles(event.target.value);
};
- const getCarIDs = () => {
- if (!selectedVehicles) return [];
- return selectedVehicles.map((vin) => {
- return vehicles.find((vehicle) => vehicle.vin === vin).id;
- });
- };
const onSubmit = async (event) => {
try {
event.preventDefault();
const data = {
package_id: parseInt(packageid),
- car_ids: getCarIDs(),
+ vins: selectedVehicles,
};
await createCarUpdates(data, token);
setMessage(
diff --git a/src/components/CarUpdates/Status/index.jsx b/src/components/CarUpdates/Status/index.jsx
index e27d875..6df3514 100644
--- a/src/components/CarUpdates/Status/index.jsx
+++ b/src/components/CarUpdates/Status/index.jsx
@@ -20,12 +20,15 @@ import { useUserContext } from "../../Contexts/UserContext";
import { useStatusContext } from "../../Contexts/StatusContext";
import useStyles from "../../useStyles";
import { LocalDateTimeString } from "../../../utils/dates";
+import VehicleStatus from "../../Cars/StatusModal";
const MainForm = () => {
const { packageid } = useParams();
const classes = useStyles();
const [pageSize, setPageSize] = useState(10);
const [pageIndex, setPageIndex] = useState(0);
+ const [viewVIN, setViewVIN] = useState(null);
+
const {
getCarUpdates,
carUpdates,
@@ -74,6 +77,15 @@ const MainForm = () => {
setPageIndex(0);
};
+ const handleViewVIN = (event) => {
+ event.preventDefault();
+ setViewVIN(event.target.innerHTML);
+ };
+
+ const handleCloseViewVIN = (event) => {
+ setViewVIN(null);
+ };
+
return (
@@ -96,7 +108,11 @@ const MainForm = () => {
{carUpdates.map((row) => (
{row.id}
- {`${row.car.vin} ${row.car.model} ${row.car.year}`}
+
+
+ {row.vin}
+
+
{row.status}
{LocalDateTimeString(row.created)}
@@ -126,6 +142,7 @@ const MainForm = () => {
+
);
};
diff --git a/src/components/Cars/List/index.jsx b/src/components/Cars/List/index.jsx
index b3012b0..933e1da 100644
--- a/src/components/Cars/List/index.jsx
+++ b/src/components/Cars/List/index.jsx
@@ -1,4 +1,5 @@
import React, { useEffect, useState } from "react";
+import { Link } from "react-router-dom";
import {
Table,
TableBody,
@@ -65,7 +66,6 @@ const MainForm = () => {
- ID
VIN
Model
Year
@@ -75,9 +75,10 @@ const MainForm = () => {
{vehicles.map((row) => (
-
- {row.id}
- {row.vin}
+
+
+ {row.vin}
+
{row.model}
{row.year}
diff --git a/src/components/Cars/Status/index.jsx b/src/components/Cars/Status/index.jsx
new file mode 100644
index 0000000..37facd6
--- /dev/null
+++ b/src/components/Cars/Status/index.jsx
@@ -0,0 +1,122 @@
+import React, { useEffect, useState } from "react";
+import { useParams } from "react-router";
+import {
+ Table,
+ TableBody,
+ TableCell,
+ TableContainer,
+ TableFooter,
+ TableHead,
+ TablePagination,
+ TableRow,
+ Typography,
+} from "@material-ui/core";
+
+import {
+ UpdatesProvider,
+ useUpdatesContext,
+} from "../../Contexts/UpdatesContext";
+import { useUserContext } from "../../Contexts/UserContext";
+import { useStatusContext } from "../../Contexts/StatusContext";
+import useStyles from "../../useStyles";
+import { LocalDateTimeString } from "../../../utils/dates";
+
+const MainForm = () => {
+ const { vin } = useParams();
+ const classes = useStyles();
+ const [pageSize, setPageSize] = useState(10);
+ const [pageIndex, setPageIndex] = useState(0);
+ const { getCarUpdates, carUpdates, totalCarUpdates } = useUpdatesContext();
+ const { setMessage } = useStatusContext();
+ const {
+ token: {
+ idToken: { jwtToken: token },
+ },
+ } = useUserContext();
+
+ useEffect(() => {
+ try {
+ getCarUpdates(
+ {
+ vin,
+ limit: pageSize,
+ offset: pageSize * pageIndex,
+ },
+ token
+ );
+ } catch (e) {
+ setMessage(e.message);
+ }
+ // eslint-disable-next-line
+ }, [pageIndex, pageSize, token]);
+
+ const handleChangePageIndex = (event, newIndex) => {
+ setPageIndex(newIndex);
+ };
+
+ const handleChangePageSize = (event) => {
+ setPageSize(parseInt(event.target.value, 10));
+ setPageIndex(0);
+ };
+
+ return (
+
+
+ {vin} Updates
+
+
+
+
+
+ ID
+ Update
+ Status
+ Created
+ Updated
+
+
+
+ {carUpdates.map((row) => (
+
+ {row.id}
+ {`${row.updatepackage.package_name} ${row.updatepackage.version}`}
+ {row.status}
+
+ {LocalDateTimeString(row.created)}
+
+
+ {LocalDateTimeString(row.updated)}
+
+
+ ))}
+
+
+
+
+
+
+
+
+
+ );
+};
+
+const CarUpdates = () => (
+
+
+
+);
+
+export default CarUpdates;
diff --git a/src/components/Cars/StatusModal/index.jsx b/src/components/Cars/StatusModal/index.jsx
new file mode 100644
index 0000000..fc12d97
--- /dev/null
+++ b/src/components/Cars/StatusModal/index.jsx
@@ -0,0 +1,91 @@
+import React, { useEffect, useState } from "react";
+import {
+ Backdrop,
+ Modal,
+ Fade,
+ Table,
+ TableHead,
+ TableRow,
+ TableCell,
+ TableBody,
+} from "@material-ui/core";
+
+import useStyles from "../../useStyles";
+import { useUpdatesContext } from "../../Contexts/UpdatesContext";
+import { useUserContext } from "../../Contexts/UserContext";
+import { useStatusContext } from "../../Contexts/StatusContext";
+import { LocalDateTimeString } from "../../../utils/dates";
+
+export default function CarStatusModal(props) {
+ const classes = useStyles();
+ const [updates, setUpdates] = useState([]);
+ const { setMessage } = useStatusContext();
+ const { getVINUpdates } = useUpdatesContext();
+ const {
+ token: {
+ idToken: { jwtToken: token },
+ },
+ } = useUserContext();
+
+ useEffect(() => {
+ (async () => {
+ try {
+ if (!props.vin) return;
+ const result = await getVINUpdates(props.vin, token);
+ if (result.error) {
+ throw new Error(`Get VIN updates error. ${result.message}`);
+ } else {
+ setUpdates(result.data);
+ }
+ } catch (e) {
+ setMessage(e.message);
+ }
+ })();
+ // eslint-disable-next-line
+ }, [props.vin]);
+ return (
+
+
+
+
+ {props.vin} Updates
+
+
+
+ Date
+ Update
+ Status
+ Updated
+
+
+
+ {updates.map((update) => (
+
+
+ {LocalDateTimeString(update.created)}
+
+ {`${update.updatepackage.package_name} ${update.updatepackage.version}`}
+ {update.status}
+
+ {LocalDateTimeString(update.updated)}
+
+
+ ))}
+
+
+
+
+
+
+ );
+}
diff --git a/src/components/Contexts/UpdatesContext.jsx b/src/components/Contexts/UpdatesContext.jsx
index 97bf99c..5b89a44 100644
--- a/src/components/Contexts/UpdatesContext.jsx
+++ b/src/components/Contexts/UpdatesContext.jsx
@@ -81,6 +81,21 @@ export const UpdatesProvider = ({ children }) => {
return result;
};
+ const getVINUpdates = async (vin, token) => {
+ let result;
+
+ try {
+ setBusy(true);
+ result = await api.getVINUpdates(vin, token);
+ if (result.error)
+ throw new Error(`Get VIN updates error. ${result.message}`);
+ } finally {
+ setBusy(false);
+ }
+
+ return result;
+ };
+
return (
{
updatePackage,
createCarUpdates,
getCarUpdates,
+ getVINUpdates,
}}
>
{children}
@@ -125,7 +141,7 @@ const validateCreateCarUpdates = (data) => {
throw new Error("Package id required");
}
- if (!data.car_ids || data.car_ids.length === 0) {
+ if (!data.vins || data.vins.length === 0) {
throw new Error("Car ids required");
}
};
diff --git a/src/components/Contexts/UpdateContext.test.jsx b/src/components/Contexts/UpdatesContext.test.jsx
similarity index 96%
rename from src/components/Contexts/UpdateContext.test.jsx
rename to src/components/Contexts/UpdatesContext.test.jsx
index a3a3ed7..8baa89d 100644
--- a/src/components/Contexts/UpdateContext.test.jsx
+++ b/src/components/Contexts/UpdatesContext.test.jsx
@@ -7,8 +7,8 @@ import {
fireEvent,
waitFor,
} from "@testing-library/react";
-import { UpdatesProvider, useUpdatesContext } from "../Contexts/UpdatesContext";
-import { StatusProvider, useStatusContext } from "../Contexts/StatusContext";
+import { UpdatesProvider, useUpdatesContext } from "./UpdatesContext";
+import { StatusProvider, useStatusContext } from "./StatusContext";
import { TEST_AUTH_OBJECT } from "../../utils/testing";
describe("UpdatesContext", () => {
@@ -222,7 +222,7 @@ describe("UpdatesContext", () => {
data-testid="with-bad-data"
onClick={async () => {
result = await exec(
- { package_id: 1, car_ids: [] },
+ { package_id: 1, vins: [] },
TEST_AUTH_OBJECT
);
}}
@@ -233,7 +233,7 @@ describe("UpdatesContext", () => {
result = await exec(
{
package_id: 1,
- car_ids: [1, 2, 3],
+ vins: ["FISKER123", "FISKER124", "FISKER125"],
},
TEST_AUTH_OBJECT
);
@@ -284,7 +284,7 @@ describe("UpdatesContext", () => {
checkState("false", "", {
id: 1,
package_id: 1,
- car_ids: [1, 2, 3],
+ vins: ["FISKER123", "FISKER124", "FISKER125"],
});
});
});
diff --git a/src/components/Contexts/__mocks__/UpdatesContext.jsx b/src/components/Contexts/__mocks__/UpdatesContext.jsx
new file mode 100644
index 0000000..0521357
--- /dev/null
+++ b/src/components/Contexts/__mocks__/UpdatesContext.jsx
@@ -0,0 +1,26 @@
+import React from "react";
+
+const UpdatesContext = React.createContext();
+
+let busy = false;
+let packages = [];
+let totalPackages = 0;
+let carUpdates = [];
+let totalCarUpdates = 0;
+
+export const UpdatesProvider = ({ children }) => {
+ return {children};
+};
+
+export const useUpdatesContext = () => ({
+ busy,
+ packages,
+ totalPackages,
+ carUpdates,
+ totalCarUpdates,
+ getPackages: jest.fn(() => packages),
+ updatePackage: jest.fn((data) => data),
+ createCarUpdates: jest.fn((data) => data),
+ getCarUpdates: jest.fn(() => carUpdates),
+ getVINUpdates: jest.fn(() => carUpdates),
+});
diff --git a/src/components/Contexts/__mocks__/VehicleContext.jsx b/src/components/Contexts/__mocks__/VehicleContext.jsx
index 4813960..566826c 100644
--- a/src/components/Contexts/__mocks__/VehicleContext.jsx
+++ b/src/components/Contexts/__mocks__/VehicleContext.jsx
@@ -2,6 +2,7 @@ import React from "react";
let busy = false;
let vehicles = [];
+let totalVehicles = 0;
let error = null;
export const VehicleProvider = ({ children }) => {
@@ -11,6 +12,7 @@ export const VehicleProvider = ({ children }) => {
export const useVehicleContext = () => ({
busy,
vehicles,
+ totalVehicles,
getVehicles: jest.fn(() => vehicles),
addVehicle: jest.fn(),
});
diff --git a/src/components/ErrorBoundary.jsx b/src/components/ErrorBoundary.jsx
index c7931ae..ad4a168 100644
--- a/src/components/ErrorBoundary.jsx
+++ b/src/components/ErrorBoundary.jsx
@@ -2,25 +2,60 @@ import React, { Component } from "react";
import PropTypes from "prop-types";
import { Typography } from "@material-ui/core";
+const reload = () => {
+ window.location.reload();
+};
export default class ErrorBoundary extends Component {
state = {
error: "",
errorInfo: "",
hasError: false,
};
+
static getDerivedStateFromError(error) {
return { hasError: true, error };
}
+
componentDidCatch(error, errorInfo) {
this.setState({ errorInfo });
}
+
render() {
- if (this.state.hasError)
+ if (this.state.hasError) {
+ if (this.state.error && this.state.error.name === "ChunkLoadError") {
+ reload();
+ return;
+ }
+
return (
-
- Oops. An React JS Error Occured.
-
+
+
+ Sorry, an error has occured and been logged
+
+
+ Click to reload
+
+
);
+ }
return this.props.children;
}
}
diff --git a/src/components/Routes/SiteRoutes.jsx b/src/components/Routes/SiteRoutes.jsx
index 9a52f1e..602f52d 100644
--- a/src/components/Routes/SiteRoutes.jsx
+++ b/src/components/Routes/SiteRoutes.jsx
@@ -15,6 +15,7 @@ const UpdatePackagesForm = React.lazy(() => import("../UpdatePackages/List"));
const UpdatePackageEdit = React.lazy(() => import("../UpdatePackages/Edit"));
const CarUpdatesDeploy = React.lazy(() => import("../CarUpdates/Deploy"));
const CarUpdatesStatus = React.lazy(() => import("../CarUpdates/Status"));
+const CarUpdates = React.lazy(() => import("../Cars/Status"));
const VehiclesList = React.lazy(() => import("../Cars/List"));
const SiteRoutes = () => {
@@ -92,6 +93,14 @@ const SiteRoutes = () => {
groups={groups}
roles={[Roles.CREATE]}
/>
+ }
+ type={TYPES.PROTECTED}
+ token={token}
+ groups={groups}
+ roles={[Roles.READ, Roles.CREATE]}
+ />
diff --git a/src/components/SSOForm/__snapshots__/SSOForm.test.js.snap b/src/components/SSOForm/__snapshots__/SSOForm.test.js.snap
index d7bb611..0779f27 100644
--- a/src/components/SSOForm/__snapshots__/SSOForm.test.js.snap
+++ b/src/components/SSOForm/__snapshots__/SSOForm.test.js.snap
@@ -3,12 +3,12 @@
exports[`Sign In Form Should render 1`] = `
diff --git a/src/components/useStyles.jsx b/src/components/useStyles.jsx
index c000ab0..a1b6ee1 100644
--- a/src/components/useStyles.jsx
+++ b/src/components/useStyles.jsx
@@ -5,6 +5,17 @@ const MENUITEM_PADDING_TOP = 8;
const DRAWER_WIDTH = 240;
const useStyles = makeStyles((theme) => ({
+ modal: {
+ display: "flex",
+ alignItems: "center",
+ justifyContent: "center",
+ },
+ modaldialog: {
+ backgroundColor: theme.palette.background.paper,
+ border: "2px solid #000",
+ boxShadow: theme.shadows[5],
+ padding: theme.spacing(2, 4, 3),
+ },
paper: {
marginTop: theme.spacing(8),
display: "flex",
@@ -105,6 +116,13 @@ const useStyles = makeStyles((theme) => ({
marginLeft: "auto",
marginRight: -12,
},
+ link: {
+ cursor: "pointer",
+ textDecorationColor: "Blue",
+ textDecorationStyle: "solid",
+ textDecorationLine: "underline",
+ color: "Blue",
+ },
}));
export default useStyles;
diff --git a/src/services/__mocks__/monitoring.js b/src/services/__mocks__/monitoring.js
new file mode 100644
index 0000000..4e16c72
--- /dev/null
+++ b/src/services/__mocks__/monitoring.js
@@ -0,0 +1 @@
+// no actual monitoring with mock
\ No newline at end of file
diff --git a/src/services/__mocks__/updates.js b/src/services/__mocks__/updates.js
index 5c8c669..74704dd 100644
--- a/src/services/__mocks__/updates.js
+++ b/src/services/__mocks__/updates.js
@@ -41,7 +41,11 @@ const updatesAPI = {
},
getCarUpdates: async (filter, token) => {
- return [];
+ return { data:[] };
+ },
+
+ getVINUpdates: async (vin, token) => {
+ return { data:[] };
},
};
diff --git a/src/services/monitoring.js b/src/services/monitoring.js
new file mode 100644
index 0000000..ac2338b
--- /dev/null
+++ b/src/services/monitoring.js
@@ -0,0 +1,12 @@
+import { datadogRum } from '@datadog/browser-rum';
+
+datadogRum.init({
+ applicationId: '8ecd160c-ad5c-4e06-8d88-3a6b89833246',
+ clientToken: 'pubeb25449bb91773fc993855c7378e375a',
+ site: 'datadoghq.com',
+ service:'ota-portal',
+ // Specify a version number to identify the deployed version of your application in Datadog
+ // version: '1.0.0',
+ sampleRate: 100,
+ trackInteractions: true
+});
\ No newline at end of file
diff --git a/src/services/updates.js b/src/services/updates.js
index 1b7c328..c5788e2 100644
--- a/src/services/updates.js
+++ b/src/services/updates.js
@@ -37,6 +37,14 @@ const updatesAPI = {
.then(fetchRespHandler);
},
+ getVINUpdates: async (vin, token) => {
+ var u = addQueryParams(`${API_ENDPOINT}/carupdates`, { vin });
+ return fetch(u, {
+ method: "GET",
+ headers: Object.assign({ "Content-Type": "application/json" }, getAuthHeaderOptions(token)),
+ })
+ .then(fetchRespHandler);
+ },
};
export default updatesAPI;
+
+
+
+ + Update Package + 1 +
+ +
+
+`;
+
+exports[`App Route /updates authenticated 1`] = `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + Fisker OTA Portal +
+
+
+`;
+
+exports[`App Route /updates unauthenticated 1`] = `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Sign Out
+
+
+
+
+ + Fisker OTA Portal +
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
-
+
-
+
+ + + Home + ++ + +
+
-
+
+ + + View Updates + ++ + +
+
-
+
+ + + Create Updates + ++ + +
+
-
+
+ + + View Vehicles + ++ + +
+
-
+
+ + + Add Vehicles + ++ + +
+
+
+
+
+ + Updates +
+
+
+
+
+
+ | + ID + | ++ Name + | ++ Version + | ++ Created + | ++ Actions + | +
|---|---|---|---|---|
|
+
+
+
+ + Rows per page: + +
+
+
+
+ + 0-0 of 0 + +
+
+ |
+ ||||
+
+`;
+
+exports[`App Route /vehicle-add authenticated 1`] = `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + Fisker OTA Portal +
+
+
+
@@ -1788,10 +3793,10 @@ exports[`App Route /vehicle-add unauthenticated 1`] = `
data-testid="mocked-userprovider"
>
+
+
+
+
+
+`;
+
+exports[`App Route /vehicle-status authenticated 1`] = `
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Sign Out
+
+
+
+
+ + Fisker OTA Portal +
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
-
+
-
+
+ + + Home + ++ + +
+
-
+
+ + + View Updates + ++ + +
+
-
+
+ + + Create Updates + ++ + +
+
-
+
+ + + View Vehicles + ++ + +
+
-
+
+ + + Add Vehicles + ++ + +
+
+
+`;
+
+exports[`App Route /vehicle-status unauthenticated 1`] = `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Sign Out
+
+
+
+
+ + Fisker OTA Portal +
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
-
+
-
+
+ + + Home + ++ + +
+
-
+
+ + + View Updates + ++ + +
+
-
+
+ + + Create Updates + ++ + +
+
-
+
+ + + View Vehicles + ++ + +
+
-
+
+ + + Add Vehicles + ++ + +
+
+
+
+
+ + FISKER123 + Updates +
+
+
+
+
+
+ | + ID + | ++ Update + | ++ Status + | ++ Created + | ++ Updated + | +
|---|---|---|---|---|
|
+
+
+
+ + Rows per page: + +
+
+
+
+ + 0-0 of 0 + +
+
+ |
+ ||||
+
+`;
+
+exports[`App Route /vehicles authenticated 1`] = `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + Fisker OTA Portal +
+
+
+`;
+
+exports[`App Route /vehicles unauthenticated 1`] = `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Sign Out
+
+
+
+
+ + Fisker OTA Portal +
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
-
+
-
+
+ + + Home + ++ + +
+
-
+
+ + + View Updates + ++ + +
+
-
+
+ + + Create Updates + ++ + +
+
-
+
+ + + View Vehicles + ++ + +
+
-
+
+ + + Add Vehicles + ++ + +
+
+
+
+
+ + Vehicles +
+
+
+
+
+
+ | + VIN + | ++ Model + | ++ Year + | ++ Created + | ++ Updated + | +|
|---|---|---|---|---|---|
|
+
+
+
+ + Rows per page: + +
+
+
+
+ + 0-0 of 0 + +
+
+ |
+ |||||
+
+
+
+
+
+
+
+
+
+ + Fisker OTA Portal +
+
+
diff --git a/src/components/App/index.jsx b/src/components/App/index.jsx
index f7a4f12..9997de3 100644
--- a/src/components/App/index.jsx
+++ b/src/components/App/index.jsx
@@ -5,6 +5,7 @@ import { StatusProvider } from "../Contexts/StatusContext";
import { CssBaseline } from "@material-ui/core";
import MenuDrawer from "../Layouts/MenuDrawer";
import SiteRoutes from "../Routes/SiteRoutes";
+import {} from "../../services/monitoring";
function App() {
return (
diff --git a/src/components/CarUpdates/Deploy/index.jsx b/src/components/CarUpdates/Deploy/index.jsx
index c3c721b..85bacfc 100644
--- a/src/components/CarUpdates/Deploy/index.jsx
+++ b/src/components/CarUpdates/Deploy/index.jsx
@@ -49,18 +49,12 @@ const MainForm = () => {
const handleVehiclesChange = (event) => {
setSelectedVehicles(event.target.value);
};
- const getCarIDs = () => {
- if (!selectedVehicles) return [];
- return selectedVehicles.map((vin) => {
- return vehicles.find((vehicle) => vehicle.vin === vin).id;
- });
- };
const onSubmit = async (event) => {
try {
event.preventDefault();
const data = {
package_id: parseInt(packageid),
- car_ids: getCarIDs(),
+ vins: selectedVehicles,
};
await createCarUpdates(data, token);
setMessage(
diff --git a/src/components/CarUpdates/Status/index.jsx b/src/components/CarUpdates/Status/index.jsx
index e27d875..6df3514 100644
--- a/src/components/CarUpdates/Status/index.jsx
+++ b/src/components/CarUpdates/Status/index.jsx
@@ -20,12 +20,15 @@ import { useUserContext } from "../../Contexts/UserContext";
import { useStatusContext } from "../../Contexts/StatusContext";
import useStyles from "../../useStyles";
import { LocalDateTimeString } from "../../../utils/dates";
+import VehicleStatus from "../../Cars/StatusModal";
const MainForm = () => {
const { packageid } = useParams();
const classes = useStyles();
const [pageSize, setPageSize] = useState(10);
const [pageIndex, setPageIndex] = useState(0);
+ const [viewVIN, setViewVIN] = useState(null);
+
const {
getCarUpdates,
carUpdates,
@@ -74,6 +77,15 @@ const MainForm = () => {
setPageIndex(0);
};
+ const handleViewVIN = (event) => {
+ event.preventDefault();
+ setViewVIN(event.target.innerHTML);
+ };
+
+ const handleCloseViewVIN = (event) => {
+ setViewVIN(null);
+ };
+
return (
@@ -96,7 +108,11 @@ const MainForm = () => {
{carUpdates.map((row) => (
{row.id}
- {`${row.car.vin} ${row.car.model} ${row.car.year}`}
+
+
+ {row.vin}
+
+
{row.status}
{LocalDateTimeString(row.created)}
@@ -126,6 +142,7 @@ const MainForm = () => {
+
);
};
diff --git a/src/components/Cars/List/index.jsx b/src/components/Cars/List/index.jsx
index b3012b0..933e1da 100644
--- a/src/components/Cars/List/index.jsx
+++ b/src/components/Cars/List/index.jsx
@@ -1,4 +1,5 @@
import React, { useEffect, useState } from "react";
+import { Link } from "react-router-dom";
import {
Table,
TableBody,
@@ -65,7 +66,6 @@ const MainForm = () => {
+
+
+
+
+
+ );
+}
diff --git a/src/components/Contexts/UpdatesContext.jsx b/src/components/Contexts/UpdatesContext.jsx
index 97bf99c..5b89a44 100644
--- a/src/components/Contexts/UpdatesContext.jsx
+++ b/src/components/Contexts/UpdatesContext.jsx
@@ -81,6 +81,21 @@ export const UpdatesProvider = ({ children }) => {
return result;
};
+ const getVINUpdates = async (vin, token) => {
+ let result;
+
+ try {
+ setBusy(true);
+ result = await api.getVINUpdates(vin, token);
+ if (result.error)
+ throw new Error(`Get VIN updates error. ${result.message}`);
+ } finally {
+ setBusy(false);
+ }
+
+ return result;
+ };
+
return (
+
+
+
+ {props.vin} Updates
+{children}
;
+};
+
+export const useUpdatesContext = () => ({
+ busy,
+ packages,
+ totalPackages,
+ carUpdates,
+ totalCarUpdates,
+ getPackages: jest.fn(() => packages),
+ updatePackage: jest.fn((data) => data),
+ createCarUpdates: jest.fn((data) => data),
+ getCarUpdates: jest.fn(() => carUpdates),
+ getVINUpdates: jest.fn(() => carUpdates),
+});
diff --git a/src/components/Contexts/__mocks__/VehicleContext.jsx b/src/components/Contexts/__mocks__/VehicleContext.jsx
index 4813960..566826c 100644
--- a/src/components/Contexts/__mocks__/VehicleContext.jsx
+++ b/src/components/Contexts/__mocks__/VehicleContext.jsx
@@ -2,6 +2,7 @@ import React from "react";
let busy = false;
let vehicles = [];
+let totalVehicles = 0;
let error = null;
export const VehicleProvider = ({ children }) => {
@@ -11,6 +12,7 @@ export const VehicleProvider = ({ children }) => {
export const useVehicleContext = () => ({
busy,
vehicles,
+ totalVehicles,
getVehicles: jest.fn(() => vehicles),
addVehicle: jest.fn(),
});
diff --git a/src/components/ErrorBoundary.jsx b/src/components/ErrorBoundary.jsx
index c7931ae..ad4a168 100644
--- a/src/components/ErrorBoundary.jsx
+++ b/src/components/ErrorBoundary.jsx
@@ -2,25 +2,60 @@ import React, { Component } from "react";
import PropTypes from "prop-types";
import { Typography } from "@material-ui/core";
+const reload = () => {
+ window.location.reload();
+};
export default class ErrorBoundary extends Component {
state = {
error: "",
errorInfo: "",
hasError: false,
};
+
static getDerivedStateFromError(error) {
return { hasError: true, error };
}
+
componentDidCatch(error, errorInfo) {
this.setState({ errorInfo });
}
+
render() {
- if (this.state.hasError)
+ if (this.state.hasError) {
+ if (this.state.error && this.state.error.name === "ChunkLoadError") {
+ reload();
+ return;
+ }
+
return (
-
+
+ Sorry, an error has occured and been logged
+
+
+ Click to reload
+
+
);
+ }
return this.props.children;
}
}
diff --git a/src/components/Routes/SiteRoutes.jsx b/src/components/Routes/SiteRoutes.jsx
index 9a52f1e..602f52d 100644
--- a/src/components/Routes/SiteRoutes.jsx
+++ b/src/components/Routes/SiteRoutes.jsx
@@ -15,6 +15,7 @@ const UpdatePackagesForm = React.lazy(() => import("../UpdatePackages/List"));
const UpdatePackageEdit = React.lazy(() => import("../UpdatePackages/Edit"));
const CarUpdatesDeploy = React.lazy(() => import("../CarUpdates/Deploy"));
const CarUpdatesStatus = React.lazy(() => import("../CarUpdates/Status"));
+const CarUpdates = React.lazy(() => import("../Cars/Status"));
const VehiclesList = React.lazy(() => import("../Cars/List"));
const SiteRoutes = () => {
@@ -92,6 +93,14 @@ const SiteRoutes = () => {
groups={groups}
roles={[Roles.CREATE]}
/>
+