diff --git a/src/components/App/__snapshots__/App.test.js.snap b/src/components/App/__snapshots__/App.test.js.snap
index cb1b82f..02bd570 100644
--- a/src/components/App/__snapshots__/App.test.js.snap
+++ b/src/components/App/__snapshots__/App.test.js.snap
@@ -4395,48 +4395,6 @@ exports[`App Route /package-deploy authenticated 1`] = `
-
|
+ >
+
+ 2023.03.00.00.E
+
+
diff --git a/src/components/Contexts/CarUpdatesContext.jsx b/src/components/Contexts/CarUpdatesContext.jsx
index 4021f76..746dfbe 100644
--- a/src/components/Contexts/CarUpdatesContext.jsx
+++ b/src/components/Contexts/CarUpdatesContext.jsx
@@ -56,6 +56,8 @@ export const CarUpdatesProvider = ({ children }) => {
const [busy, setBusy] = useState(false);
const [carUpdates, setCarUpdates] = useState([]);
const [versions, setVersions] = useState([SELECT_VERSION_OBJ]);
+ const [versionRxSwins, setVersionRxSwins] = useState([]);
+ const [totalVersionRxSwins, setTotalVersionRxSwins] = useState(0);
const [totalCarUpdates, setTotalCarUpdates] = useState(0);
const [delayCount, setDelayCount] = useState(0);
let progressTimer = 0;
@@ -307,12 +309,62 @@ export const CarUpdatesProvider = ({ children }) => {
return result;
};
+ const getSUMSVersionRxSwins = async (sums_version, options, token) => {
+ let result;
+
+ try {
+ setBusy(true);
+
+ result = await api.getSUMSVersionRxSwins(sums_version, options, token);
+ if (result.error)
+ throw new Error(`Get software version rxswins error. ${result.message}`);
+
+ setVersionRxSwins(result.data);
+ if (options && options.offset === 0 && result.total) {
+ setTotalVersionRxSwins(result.total);
+ }
+ } finally {
+ setBusy(false);
+ }
+
+ return result;
+ };
+
+ const deleteSUMSVersionRxSwins = async (sums_version, rxswin, token) => {
+ let result;
+
+ try {
+ setBusy(true);
+
+ result = await api.deleteSUMSVersionRxSwins(sums_version, rxswin, token);
+ if (result.error)
+ throw new Error(`Delete software version rxswin error. ${result.message}`);
+ } finally {
+ setBusy(false);
+ }
+
+ return result;
+ };
+
+ const addSUMSVersionRxSwins = async (sums_version, data, token) => {
+ try {
+ setBusy(true);
+ const result = await api.addSUMSVersionRxSwins(sums_version, data, token);
+ if (result.error)
+ throw new Error(`Add software version rxswin error. ${result.message}`);
+ return result;
+ } finally {
+ setBusy(false);
+ }
+ };
+
return (
{
startMonitor,
stopMonitor,
updateSUMSVersion,
+ getSUMSVersionRxSwins,
+ totalVersionRxSwins,
+ deleteSUMSVersionRxSwins,
+ addSUMSVersionRxSwins,
}}
>
{children}
diff --git a/src/components/Contexts/__mocks__/CarUpdatesContext.jsx b/src/components/Contexts/__mocks__/CarUpdatesContext.jsx
index 4ba1ce9..c99e25f 100644
--- a/src/components/Contexts/__mocks__/CarUpdatesContext.jsx
+++ b/src/components/Contexts/__mocks__/CarUpdatesContext.jsx
@@ -21,6 +21,7 @@ let carUpdates = [
ecu_list: "AGS 1.0.0,AMP 1.0.0",
created: "2021-08-20T18:37:41.960397Z",
updated: "2021-08-20T18:37:50.007853Z",
+ sums: "2023.03.00.00.E",
},
},
{
@@ -40,6 +41,7 @@ let carUpdates = [
ecu_list: "AGS 1.0.0,AMP 1.0.0",
created: "2021-08-20T18:37:41.960397Z",
updated: "2021-08-20T18:37:50.007853Z",
+ sums: "2023.03.00.00.E",
},
},
{
@@ -59,6 +61,7 @@ let carUpdates = [
ecu_list: "AGS 1.0.0,AMP 1.0.0",
created: "2021-08-20T18:37:41.960397Z",
updated: "2021-08-20T18:37:50.007853Z",
+ sums: "2023.03.00.00.E",
},
},
];
@@ -122,6 +125,19 @@ let sumsVersions = {
"data": ["2023.02.01.0.0.A", "2023.02.01.0.0.B"]
}
+let sumsVersionRxSwins = {
+ data: [
+ {
+ version: "2023.02.01.0.0.A",
+ rxswin: "testrxswin1",
+ },
+ {
+ version: "2023.02.01.0.0.A",
+ rxswin: "testrxswin2",
+ },
+ ]
+}
+
export const CarUpdatesProvider = ({ children }) => {
return {children} ;
};
@@ -140,5 +156,8 @@ export const useCarUpdatesContext = () => ({
stopMonitor: jest.fn(),
approveUpdate: jest.fn(),
getSUMSVersions: jest.fn(() => sumsVersions),
+ getSUMSVersionRxSwins: jest.fn(() => sumsVersionRxSwins),
+ deleteSUMSVersionRxSwins: jest.fn(),
+ addSUMSVersionRxSwins: jest.fn(),
updateSUMSVersion: jest.fn(),
});
\ No newline at end of file
diff --git a/src/components/Contexts/__mocks__/ManifestsContext.jsx b/src/components/Contexts/__mocks__/ManifestsContext.jsx
index a4e8b4e..5c02afc 100644
--- a/src/components/Contexts/__mocks__/ManifestsContext.jsx
+++ b/src/components/Contexts/__mocks__/ManifestsContext.jsx
@@ -53,6 +53,7 @@ let manifest = {
created: "2021-12-09T22:38:11.679943Z",
updated: "2022-01-14T00:47:08.996451Z",
active: true,
+ sums: "2023.03.00.00.E",
};
let manifests = [
{
@@ -62,6 +63,7 @@ let manifests = [
created: "2021-07-01T22:40:07.778509Z",
updated: "2021-07-12T18:22:13.736755Z",
active: true,
+ sums: "2023.03.00.00.E",
},
];
let totalManifests = 1;
diff --git a/src/components/Controls/CarUpdatesTable/index.jsx b/src/components/Controls/CarUpdatesTable/index.jsx
index 7da1829..d3cc482 100644
--- a/src/components/Controls/CarUpdatesTable/index.jsx
+++ b/src/components/Controls/CarUpdatesTable/index.jsx
@@ -177,7 +177,9 @@ const MainForm = ({ vin, token }) => {
- {row.updatemanifest?.sums}
+
+ {row.updatemanifest?.sums}
+
{row.username}
diff --git a/src/components/Manifest/List/index.jsx b/src/components/Manifest/List/index.jsx
index d1864d2..74d1871 100644
--- a/src/components/Manifest/List/index.jsx
+++ b/src/components/Manifest/List/index.jsx
@@ -447,7 +447,11 @@ const MainForm = () => {
{formatManifestType(row.manifest_type)}
- {row.sums}
+
+
+ {row.sums}
+
+
{formatType(row.type)}
diff --git a/src/components/Routes/SiteRoutes.jsx b/src/components/Routes/SiteRoutes.jsx
index 29052c2..ed31bb0 100644
--- a/src/components/Routes/SiteRoutes.jsx
+++ b/src/components/Routes/SiteRoutes.jsx
@@ -41,6 +41,8 @@ const SMSSend = React.lazy(() => import("../SMS/Send"));
const SuppliersList = React.lazy(() => import("../Suppliers/List"));
const SupplierDetails = React.lazy(() => import("../Suppliers/Details"));
const Datascope = React.lazy(() => import("../Dashboard"));
+const SumsRxSwin = React.lazy(() => import("../SUMS"));
+const SumsRxSwinAdd = React.lazy(() => import("../SUMS/Add"));
const DashboardCustom = React.lazy(() => import("../DashboardCustom"));
const SiteRoutes = () => {
@@ -305,6 +307,24 @@ const SiteRoutes = () => {
rolesPerGroup={Permissions.FiskerMagnaRead}
providers={providers}
/>
+ }
+ type={TYPES.PROTECTED}
+ token={token}
+ groups={groups}
+ rolesPerGroup={Permissions.FiskerMagnaCreate}
+ providers={providers}
+ />
+ }
+ type={TYPES.PROTECTED}
+ token={token}
+ groups={groups}
+ rolesPerGroup={Permissions.FiskerMagnaCreate}
+ providers={providers}
+ />
diff --git a/src/components/SUMS/Add/__snapshots__/index.test.jsx.snap b/src/components/SUMS/Add/__snapshots__/index.test.jsx.snap
new file mode 100644
index 0000000..1a38a9d
--- /dev/null
+++ b/src/components/SUMS/Add/__snapshots__/index.test.jsx.snap
@@ -0,0 +1,92 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`SUMSAdd Render 1`] = `
+
+`;
diff --git a/src/components/SUMS/Add/index.jsx b/src/components/SUMS/Add/index.jsx
new file mode 100644
index 0000000..4ef751b
--- /dev/null
+++ b/src/components/SUMS/Add/index.jsx
@@ -0,0 +1,116 @@
+import {
+ Button,
+ TextField
+} from "@material-ui/core";
+import { useParams } from "react-router";
+import React, { useEffect, useState } from "react";
+import { Redirect } from "react-router";
+import { logger } from "../../../services/monitoring";
+import { useStatusContext } from "../../Contexts/StatusContext";
+import {
+ CarUpdatesProvider,
+ useCarUpdatesContext
+} from "../../Contexts/CarUpdatesContext";
+import { useUserContext } from "../../Contexts/UserContext";
+import useStyles from "../../useStyles";
+
+const MainForm = () => {
+ const { addSUMSVersionRxSwins, busy } = useCarUpdatesContext();
+ const {
+ token: {
+ idToken: { jwtToken: token },
+ },
+ } = useUserContext();
+ const classes = useStyles();
+ const [redirect, setRedirect] = useState(null);
+ const { sums_version } = useParams();
+ const { setMessage, setTitle, setSitePath } = useStatusContext();
+ const [rxswin, setRxswin] = useState("");
+
+ useEffect(() => {
+ setTitle(`Add RXSWIN to SUMS Version ${sums_version}`);
+ setSitePath([
+ {
+ label: `SUMS Version ${sums_version}`,
+ link: `/sums/${sums_version}`,
+ },
+ {
+ label: `Add RXSWIN`,
+ },
+ ]);
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, []);
+
+ const onRxswinChange = (event) => {
+ setRxswin(event.target.value);
+ }
+
+ const onSubmit = async (event) => {
+ try {
+ event.preventDefault();
+
+ const data = {
+ "swversion_rxswins": [
+ {
+ "version": sums_version,
+ "rxswin": rxswin,
+ },
+ ],
+ };
+
+ const result = await addSUMSVersionRxSwins(sums_version, data, token);
+ if (!result || result.error) return;
+
+ setMessage(`Added ${rxswin}`);
+ setRedirect(`/sums/${sums_version}`);
+ } catch (e) {
+ setMessage(e.message);
+ logger.warn(e.stack);
+ }
+ };
+
+ if (redirect && redirect.length > 0) {
+ return ;
+ }
+
+ return (
+
+
+
+ );
+};
+
+const SumsRxSwinAdd = () => (
+
+
+
+);
+
+export default SumsRxSwinAdd;
\ No newline at end of file
diff --git a/src/components/SUMS/Add/index.test.jsx b/src/components/SUMS/Add/index.test.jsx
new file mode 100644
index 0000000..f44513f
--- /dev/null
+++ b/src/components/SUMS/Add/index.test.jsx
@@ -0,0 +1,46 @@
+jest.mock("../../Contexts/CarUpdatesContext");
+jest.mock("../../Contexts/StatusContext");
+jest.mock("../../Contexts/UserContext");
+jest.mock("@material-ui/core/utils/unstable_useId", () =>
+ jest.fn().mockReturnValue("mui-test-id")
+);
+import { render, waitFor } from "@testing-library/react";
+import { MemoryRouter, Route } from "react-router-dom";
+import { CarUpdatesProvider } from "../../Contexts/CarUpdatesContext";
+import { StatusProvider } from "../../Contexts/StatusContext";
+import { UserProvider, setToken } from "../../Contexts/UserContext";
+import { TEST_AUTH_OBJECT_FISKER } from "../../../utils/testing";
+import MainForm from "./index";
+import addSnapshotSerializer from "../../../utils/snapshot";
+
+const renderSUMSAdd = async () => {
+ const { container } = render(
+
+
+
+
+
+
+
+
+
+
+
+ );
+ await waitFor(() => {
+ /* render */
+ });
+ return container;
+};
+
+describe("SUMSAdd", () => {
+ beforeAll(() => {
+ addSnapshotSerializer(expect);
+ });
+
+ it("Render", async () => {
+ setToken(TEST_AUTH_OBJECT_FISKER);
+ const container = await renderSUMSAdd();
+ expect(container).toMatchSnapshot();
+ });
+});
diff --git a/src/components/SUMS/__snapshots__/index.test.jsx.snap b/src/components/SUMS/__snapshots__/index.test.jsx.snap
new file mode 100644
index 0000000..ee5e68c
--- /dev/null
+++ b/src/components/SUMS/__snapshots__/index.test.jsx.snap
@@ -0,0 +1,129 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`SUMS Render 1`] = `
+
+
+
+
+
+
+
+
+
+
+ |
+
+ RXSWIN (RX Software Identification Number)
+
+
+ |
+
+
+ Created
+
+ sorted descending
+
+
+
+ |
+
+
+
+
+
+
+
+
+
+
+
+
+`;
diff --git a/src/components/SUMS/index.jsx b/src/components/SUMS/index.jsx
new file mode 100644
index 0000000..82e64be
--- /dev/null
+++ b/src/components/SUMS/index.jsx
@@ -0,0 +1,233 @@
+import {
+ Grid,
+ IconButton,
+ Table,
+ TableBody,
+ TableCell,
+ TableFooter,
+ TablePagination,
+ TableRow,
+} from "@material-ui/core";
+import AddCircleIcon from "@material-ui/icons/AddCircle";
+import DeleteIcon from "@material-ui/icons/Delete";
+import clsx from "clsx";
+import { LocalDateTimeString } from "../../utils/dates";
+import { logger } from "../../services/monitoring";
+import React, { useEffect, useState } from "react";
+import { useParams } from "react-router";
+import {
+ CarUpdatesProvider,
+ useCarUpdatesContext
+} from "../Contexts/CarUpdatesContext";
+import { useStatusContext } from "../Contexts/StatusContext";
+import { useUserContext } from "../Contexts/UserContext";
+import { Link } from "react-router-dom";
+import TableHeaderSortable from "../Table/HeaderSortable";
+import { useLocalStorage } from "../useLocalStorage";
+import DeleteConfirmation from "../DeleteConfirmation";
+import useStyles from "../useStyles";
+import { Permissions } from "../../utils/roles";
+import { RoleWrap } from "../Controls/RoleWrap";
+
+const tableColumns = [
+ {
+ id: "rxswin",
+ label: "RXSWIN (RX Software Identification Number)",
+ },
+ {
+ id: "created_at",
+ label: "Created",
+ },
+];
+
+const PAGE_SIZE = "SUMS_RXSWIN_TABLE_PAGE_SIZE";
+
+const MainForm = () => {
+ const { sums_version } = useParams();
+ const classes = useStyles();
+ const [pageSize, setPageSize] = useLocalStorage(PAGE_SIZE, 10);
+ const [pageIndex, setPageIndex] = useState(0);
+ const [orderBy, setOrderBy] = useState("created_at");
+ const [order, setOrder] = useState("desc");
+ const [showDeleteModal, setShowDeleteModal] = useState(false);
+ const [rowToDelete, setRowToDelete] = useState({});
+ const {
+ deleteSUMSVersionRxSwins,
+ getSUMSVersionRxSwins,
+ versionRxSwins,
+ totalVersionRxSwins,
+ stopMonitor,
+ } = useCarUpdatesContext();
+ const {
+ token: {
+ idToken: { jwtToken: token },
+ },
+ groups,
+ providers,
+ } = useUserContext();
+ const { setMessage, setTitle, setSitePath } = useStatusContext();
+
+ useEffect(() => {
+ setTitle(`SUMS Version ${sums_version}`);
+ setSitePath([
+ {
+ label: `SUMS Version ${sums_version}`,
+ link: `/sums/${sums_version}`,
+ },
+ ]);
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, []);
+
+ useEffect(() => {
+ loadSUMSVersionRxSwins();
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [sums_version, token, pageIndex, pageSize, orderBy, order]);
+
+ const loadSUMSVersionRxSwins = async () => {
+ try {
+ if (!sums_version || !token) return;
+ stopMonitor();
+ await getSUMSVersionRxSwins(
+ sums_version,
+ {
+ limit: pageSize,
+ offset: pageSize * pageIndex,
+ order: `${orderBy} ${order}`,
+ },
+ token
+ );
+ } catch (e) {
+ setMessage(e.message);
+ logger.warn(e.stack);
+ }
+ };
+
+ const handleChangePageIndex = (event, newIndex) => {
+ setPageIndex(newIndex);
+ };
+
+ const handleChangePageSize = (event) => {
+ setPageSize(parseInt(event.target.value, 10));
+ setPageIndex(0);
+ };
+
+ const handleSort = (event, property) => {
+ try {
+ if (property === orderBy) {
+ if (order === "asc") {
+ setOrder("desc");
+ } else {
+ setOrder("asc");
+ }
+ } else {
+ setOrderBy(property);
+ setOrder("asc");
+ }
+ } catch (e) {
+ logger.warn(e.stack);
+ }
+ };
+
+ const onDeleteClick = (row) => {
+ setRowToDelete(row);
+ setShowDeleteModal(true);
+ }
+
+ const sendDelete = async () => {
+ if (rowToDelete) {
+ try {
+ const result = await deleteSUMSVersionRxSwins(rowToDelete.version, rowToDelete.rxswin, token);
+ if (!result || result.error) return;
+
+ setMessage(`Deleted ${rowToDelete.rxswin}`);
+ loadSUMSVersionRxSwins();
+ } catch (e) {
+ setMessage(e.message);
+ logger.warn(e.stack);
+ }
+ }
+ };
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ {versionRxSwins && versionRxSwins.map((row, index) => (
+
+
+ {row.rxswin}
+
+
+ {LocalDateTimeString(row.created)}
+
+
+
+ onDeleteClick(row)}
+ aria-label={`Send delete for ${row.rxswin}`}
+ size="small"
+ color="primary"
+ >
+
+
+
+
+
+ ))}
+
+
+
+ {!versionRxSwins || versionRxSwins.length === 0 ? (
+ No RX Software Identification Numbers
+ ) : (
+ )}
+
+
+
+ setShowDeleteModal(false)}
+ deleteFunction={sendDelete}
+ />
+
+ );
+};
+
+const SumsRxSwin = () => (
+
+
+
+);
+
+export default SumsRxSwin;
\ No newline at end of file
diff --git a/src/components/SUMS/index.test.jsx b/src/components/SUMS/index.test.jsx
new file mode 100644
index 0000000..cfd5390
--- /dev/null
+++ b/src/components/SUMS/index.test.jsx
@@ -0,0 +1,46 @@
+jest.mock("../Contexts/CarUpdatesContext");
+jest.mock("../Contexts/StatusContext");
+jest.mock("../Contexts/UserContext");
+jest.mock("@material-ui/core/utils/unstable_useId", () =>
+ jest.fn().mockReturnValue("mui-test-id")
+);
+import { render, waitFor } from "@testing-library/react";
+import { MemoryRouter, Route } from "react-router-dom";
+import { CarUpdatesProvider } from "../Contexts/CarUpdatesContext";
+import { StatusProvider } from "../Contexts/StatusContext";
+import { UserProvider, setToken } from "../Contexts/UserContext";
+import { TEST_AUTH_OBJECT_FISKER } from "../../utils/testing";
+import MainForm from "./index";
+import addSnapshotSerializer from "../../utils/snapshot";
+
+const renderSUMS = async () => {
+ const { container } = render(
+
+
+
+
+
+
+
+
+
+
+
+ );
+ await waitFor(() => {
+ /* render */
+ });
+ return container;
+};
+
+describe("SUMS", () => {
+ beforeAll(() => {
+ addSnapshotSerializer(expect);
+ });
+
+ it("Render", async () => {
+ setToken(TEST_AUTH_OBJECT_FISKER);
+ const container = await renderSUMS();
+ expect(container).toMatchSnapshot();
+ });
+});
diff --git a/src/services/updatesAPI.js b/src/services/updatesAPI.js
index 83f0736..adce177 100644
--- a/src/services/updatesAPI.js
+++ b/src/services/updatesAPI.js
@@ -122,7 +122,43 @@ const updatesAPI = {
})
.then(fetchRespHandler)
.catch(errorHandler);
- }
+ },
+
+ getSUMSVersionRxSwins: async (sums_version, options, token) => {
+ return fetch(addQueryParams(`${API_ENDPOINT}/manifest/sums/${sums_version}/rxswins`, options), {
+ method: "GET",
+ headers: Object.assign(
+ { "Content-Type": "application/json" },
+ getAuthHeaderOptions(token)
+ ),
+ })
+ .then(fetchRespHandler)
+ .catch(errorHandler);
+ },
+
+ deleteSUMSVersionRxSwins: async (sums_version, rxswin, token) => {
+ return fetch(`${API_ENDPOINT}/manifest/sums/${sums_version}/rxswins/${rxswin}`, {
+ method: "DELETE",
+ headers: Object.assign(
+ { "Content-Type": "application/json" },
+ getAuthHeaderOptions(token)
+ ),
+ })
+ .then(fetchRespHandler)
+ .catch(errorHandler);
+ },
+
+ addSUMSVersionRxSwins: async (sums_version, data, token) =>
+ fetch(`${API_ENDPOINT}/manifest/sums/${sums_version}/rxswins`, {
+ method: "POST",
+ headers: Object.assign(
+ { "Content-Type": "application/json" },
+ getAuthHeaderOptions(token)
+ ),
+ body: JSON.stringify(data),
+ })
+ .then(fetchRespHandler)
+ .catch(errorHandler),
};
export default updatesAPI;
\ No newline at end of file
diff --git a/src/utils/roles.js b/src/utils/roles.js
index fb8b43c..3a3ecbd 100644
--- a/src/utils/roles.js
+++ b/src/utils/roles.js
@@ -100,6 +100,11 @@ export const Permissions = {
[Providers.FISKER_QA]: [Roles.MANUFACTURE],
[Providers.MAGNA]: [Roles.MAGNAGROUP],
},
+ FiskerMagnaDelete: {
+ [Providers.FISKER]: [Roles.DELETE],
+ [Providers.FISKER_QA]: [Roles.MANUFACTURE],
+ [Providers.MAGNA]: [Roles.MAGNAGROUP],
+ },
ManifestMigration: {
[Providers.FISKER]: [Roles.MANIFEST_MIGRATION]
},
diff --git a/src/utils/roles.test.js b/src/utils/roles.test.js
index 9b40e97..0cc90fd 100644
--- a/src/utils/roles.test.js
+++ b/src/utils/roles.test.js
@@ -113,6 +113,18 @@ describe("Roles Helper", () => {
).toEqual(true);
});
+ it("Check FiskerMagnaDelete permission", () => {
+ expect(
+ hasRole([Roles.DELETE], Permissions.FiskerMagnaDelete, [Providers.FISKER])
+ ).toEqual(true);
+ expect(
+ hasRole([Roles.MAGNAGROUP], Permissions.FiskerMagnaDelete, [Providers.MAGNA])
+ ).toEqual(true);
+ expect(
+ hasRole([Roles.MANUFACTURE], Permissions.FiskerMagnaDelete, [Providers.FISKER_QA])
+ ).toEqual(true);
+ });
+
it("Get groups", () => {
expect(getGroups(TEST_TOKEN)).toEqual(TEST_EXPECTED_GROUPS);
});
|