diff --git a/src/components/Contexts/VehicleContext.jsx b/src/components/Contexts/VehicleContext.jsx index eb09f0e..ef6b781 100644 --- a/src/components/Contexts/VehicleContext.jsx +++ b/src/components/Contexts/VehicleContext.jsx @@ -354,6 +354,26 @@ export const VehicleProvider = ({ children }) => { } } + const addFlashpackVersionECUMapping = async (model, trim, year, flashpack, ecuName, ecuVersion, token) => { + try { + setBusy(true); + + const data = { + "car_ecu_name": ecuName, + "car_ecu_version": ecuVersion, + } + + const result = await api.addFlashpackVersionECUMapping(model, trim, year, flashpack, data, token) + if (result.error) { + throw new Error(`Add flashpack version ECU mapping error. ${result.message}`); + } + + return result; + } finally { + setBusy(false) + } + } + const deleteFlashpackVersion = async (model, trim, year, flashpack, token) => { try { setBusy(true); @@ -367,7 +387,22 @@ export const VehicleProvider = ({ children }) => { const result = await api.deleteFlashpackVersion(data, token); if (result.error) { - throw new Error(`Delete flashpack ecu mappings error. ${result.message}`); + throw new Error(`Delete flashpack version error. ${result.message}`); + } + + return result; + } finally { + setBusy(false); + } + }; + + const deleteFlashpackVersionECUMapping = async (model, trim, year, flashpack, ecuName, ecuVersion, token) => { + try { + setBusy(true); + + const result = await api.deleteFlashpackVersionECUMapping(model, trim, year, flashpack, ecuName, ecuVersion, token); + if (result.error) { + throw new Error(`Delete flashpack ecu mapping error. ${result.message}`); } return result; @@ -427,7 +462,9 @@ export const VehicleProvider = ({ children }) => { flashpackECUMappings, getFlashpackECUMappings, addFlashpackVersion, + addFlashpackVersionECUMapping, deleteFlashpackVersion, + deleteFlashpackVersionECUMapping, getCarFlashpackVersionInfo, }} > diff --git a/src/components/Flashpack/AddMapping/__snapshots__/index.test.jsx.snap b/src/components/Flashpack/AddMapping/__snapshots__/index.test.jsx.snap new file mode 100644 index 0000000..f85f07c --- /dev/null +++ b/src/components/Flashpack/AddMapping/__snapshots__/index.test.jsx.snap @@ -0,0 +1,612 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`FlashpackECUMappingAdd Render 1`] = ` +
+
+
+
+
+
+
+
+
+ +
+ + + +
+
+
+ +
+ + +
+
+
+ +
+
+
+
+
+
+
+`; diff --git a/src/components/Flashpack/AddMapping/index.jsx b/src/components/Flashpack/AddMapping/index.jsx new file mode 100644 index 0000000..2da1a17 --- /dev/null +++ b/src/components/Flashpack/AddMapping/index.jsx @@ -0,0 +1,134 @@ +import { + Button, + TextField +} from "@material-ui/core"; +import ECUDropDown from "../../Controls/ECUDropDown"; +import React, { useEffect, useState } from "react"; +import { useParams } from "react-router-dom"; +import { Redirect } from "react-router"; +import { logger } from "../../../services/monitoring"; +import { useStatusContext } from "../../Contexts/StatusContext"; +import { useVehicleContext, VehicleProvider } from "../../Contexts/VehicleContext"; +import { useUserContext } from "../../Contexts/UserContext"; +import useStyles from "../../useStyles"; + +const MainForm = () => { + const { model, trim, year, flashpack } = useParams(); + const { + token: { + idToken: { jwtToken: token }, + }, + } = useUserContext(); + const classes = useStyles(); + const [redirect, setRedirect] = useState(null); + const { setMessage, setTitle, setSitePath } = useStatusContext(); + const [ecuName, setECUName] = useState(""); + const [ecuVersion, setECUVersion] = useState(""); + const { + addFlashpackVersionECUMapping, + busy, + } = useVehicleContext(); + + useEffect(() => { + setTitle(`Add Flashpack ECU Version Mapping`); + setSitePath([ + { + label: "Tools", + link: "/tools/flashpacks", + }, + { + label: "Flashpack Versions", + link: "/tools/flashpacks", + }, + { + label: `Flashpack ${flashpack}`, + link: `/tools/flashpack/${model}/${trim}/${year}/${flashpack}` + }, + { + label: `Add Flashpack ECU Version Mapping`, + }, + ]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + const onCarECUNameChange = (event) => { + let newECU = event.target.value + + setECUName(newECU) + } + + const onCarECUVersionChange = (event) => { + let newECUVersion = event.target.value + + setECUVersion(newECUVersion) + } + + const onSubmit = async (event) => { + try { + event.preventDefault(); + + const result = await addFlashpackVersionECUMapping(model, trim, parseInt(year), flashpack, ecuName, ecuVersion, token); + if (!result || result.error) return; + + setMessage(`Added ${year} ${model} ${trim} ${flashpack} ${ecuName} ${ecuVersion}`); + setRedirect(`/tools/flashpack/${model}/${trim}/${year}/${flashpack}`); + } catch (e) { + setMessage(e.message); + logger.warn(e.stack); + } + }; + + if (redirect && redirect.length > 0) { + return ; + } + + return ( +
+
+
+ onCarECUNameChange(event)} + required + /> + onCarECUVersionChange(event)} + type="text" + /> +
+ +
+
+ ); +}; + +const FlashpackECUMappingAdd = () => ( + + + +); + +export default FlashpackECUMappingAdd; \ No newline at end of file diff --git a/src/components/Flashpack/AddMapping/index.test.jsx b/src/components/Flashpack/AddMapping/index.test.jsx new file mode 100644 index 0000000..3076847 --- /dev/null +++ b/src/components/Flashpack/AddMapping/index.test.jsx @@ -0,0 +1,46 @@ +jest.mock("../../Contexts/VehicleContext"); +jest.mock("../../Contexts/StatusContext"); +jest.mock("../../Contexts/UserContext"); +jest.mock("@material-ui/core/utils/unstable_useId", () => + jest.fn().mockReturnValue("mui-test-id") +); +import { render, waitFor } from "@testing-library/react"; +import { MemoryRouter, Route } from "react-router-dom"; +import { VehicleProvider } from "../../Contexts/VehicleContext"; +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 renderFlashpackECUMappingAdd = async () => { + const { container } = render( + + + + + + + + + + + + ); + await waitFor(() => { + /* render */ + }); + return container; +}; + +describe("FlashpackECUMappingAdd", () => { + beforeAll(() => { + addSnapshotSerializer(expect); + }); + + it("Render", async () => { + setToken(TEST_AUTH_OBJECT_FISKER); + const container = await renderFlashpackECUMappingAdd(); + expect(container).toMatchSnapshot(); + }); +}); diff --git a/src/components/Flashpack/Details/__snapshots__/index.test.jsx.snap b/src/components/Flashpack/Details/__snapshots__/index.test.jsx.snap index 303487f..f4e0c40 100644 --- a/src/components/Flashpack/Details/__snapshots__/index.test.jsx.snap +++ b/src/components/Flashpack/Details/__snapshots__/index.test.jsx.snap @@ -15,6 +15,29 @@ exports[`FlashpackDetails Render 1`] = ` data-testid="mocked-vehicleprovider" >
+
+ +
@@ -70,6 +93,29 @@ exports[`FlashpackDetails Render 1`] = ` +
+ + Delete + + +
+
diff --git a/src/components/Flashpack/Details/index.jsx b/src/components/Flashpack/Details/index.jsx index 9eec669..974e4f4 100644 --- a/src/components/Flashpack/Details/index.jsx +++ b/src/components/Flashpack/Details/index.jsx @@ -1,6 +1,8 @@ import React, { useEffect, useState } from "react"; import { useParams } from "react-router-dom"; import { + Grid, + IconButton, Table, TableBody, TableCell, @@ -8,12 +10,18 @@ import { TablePagination, TableRow, } from "@material-ui/core"; +import AddCircleIcon from "@material-ui/icons/AddCircle"; import { logger } from "../../../services/monitoring"; +import DeleteIcon from "@material-ui/icons/Delete"; +import clsx from "clsx"; +import { Link } from "react-router-dom"; import { useVehicleContext, VehicleProvider } from "../../Contexts/VehicleContext"; import { useStatusContext } from "../../Contexts/StatusContext"; import { useUserContext } from "../../Contexts/UserContext"; import TableHeaderSortable from "../../Table/HeaderSortable"; import { useLocalStorage } from "../../useLocalStorage"; +import DeleteConfirmation from "../../DeleteConfirmation"; +import { RoleWrap } from "../../Controls/RoleWrap"; import useStyles from "../../useStyles"; const tableColumns = [ @@ -25,6 +33,10 @@ const tableColumns = [ id: "car_ecu_version", label: "ECU Version", }, + { + id: "delete", + label: "Delete", + }, ]; const PAGE_SIZE = "FLASHPACK_MAPPINGS_TABLE_PAGE_SIZE"; @@ -37,15 +49,20 @@ const MainForm = () => { const [pageIndex, setPageIndex] = useState(0); const [orderBy, setOrderBy] = useState("flashpack"); const [order, setOrder] = useState("desc"); + const [showDeleteModal, setShowDeleteModal] = useState(false); + const [rowToDelete, setRowToDelete] = useState({}); const { getFlashpackECUMappings, flashpackECUMappings, totalFlashpackECUMappings, + deleteFlashpackVersionECUMapping, } = useVehicleContext(); const { token: { idToken: { jwtToken: token }, }, + groups, + providers, } = useUserContext(); useEffect(() => { @@ -118,8 +135,35 @@ const MainForm = () => { } }; + const onDeleteClick = (row) => { + setRowToDelete(row); + setShowDeleteModal(true); + } + + const sendDelete = async () => { + if (rowToDelete) { + try { + const result = await deleteFlashpackVersionECUMapping(rowToDelete.car_model, rowToDelete.car_trim, rowToDelete.car_year, rowToDelete.flashpack, rowToDelete.car_ecu_name, rowToDelete.car_ecu_version, token); + if (!result || result.error) return; + + setMessage(`Deleted ${rowToDelete.car_year} ${rowToDelete.car_model} ${rowToDelete.car_trim} ${rowToDelete.flashpack} ${rowToDelete.car_ecu_name} ${rowToDelete.car_ecu_version}`); + loadFlashpackECUMappings(); + } catch (e) { + setMessage(e.message); + logger.warn(e.stack); + } + } + }; + return (
+ + + + + + + { {row.car_ecu_version} + + + onDeleteClick(row)} + aria-label={`Send delete for ${row.car_year} ${row.car_model} ${row.car_trim} ${row.flashpack} ${row.car_ecu_name} ${row.car_ecu_version}`} + size="small" + color="primary" + > + + + + ))} @@ -147,7 +207,7 @@ const MainForm = () => { ) : ( {
+ setShowDeleteModal(false)} + deleteFunction={sendDelete} + />
); }; diff --git a/src/components/Flashpack/Details/index.test.jsx b/src/components/Flashpack/Details/index.test.jsx index b0f11fb..f366725 100644 --- a/src/components/Flashpack/Details/index.test.jsx +++ b/src/components/Flashpack/Details/index.test.jsx @@ -18,8 +18,8 @@ const renderFlashpackDetails = async () => { - - + + diff --git a/src/components/Routes/SiteRoutes.jsx b/src/components/Routes/SiteRoutes.jsx index ada8471..2c6cc5e 100644 --- a/src/components/Routes/SiteRoutes.jsx +++ b/src/components/Routes/SiteRoutes.jsx @@ -36,6 +36,7 @@ const SuppliersList = React.lazy(() => import("../Suppliers/List")); const SupplierDetails = React.lazy(() => import("../Suppliers/Details")); const Flashpacks = React.lazy(() => import("../Flashpack")); const FlashpackDetails = React.lazy(() => import("../Flashpack/Details")); +const FlashpackECUMappingAdd = React.lazy(() => import("../Flashpack/AddMapping")) const FlashpackAdd = React.lazy(() => import("../Flashpack/Add")) const Datascope = React.lazy(() => import("../Dashboard")); const Sums = React.lazy(() => import("../SUMS/List")) @@ -297,6 +298,15 @@ const SiteRoutes = () => { rolesPerGroup={Permissions.FiskerRead} providers={providers} /> + } + type={TYPES.PROTECTED} + token={token} + groups={groups} + rolesPerGroup={Permissions.FiskerCreate} + providers={providers} + /> } diff --git a/src/services/vehiclesAPI.js b/src/services/vehiclesAPI.js index 2f4ea17..c908f27 100644 --- a/src/services/vehiclesAPI.js +++ b/src/services/vehiclesAPI.js @@ -295,6 +295,18 @@ const vehiclesAPI = { .catch(errorHandler) }, + addFlashpackVersionECUMapping: async (model, trim, year, flashpack, data, token) => { + return fetch(`${API_ENDPOINT}/flashpack_version_ecu_mapping/${model}/${trim}/${year}/${flashpack}`, { + method: "POST", + headers: Object.assign( + { "Content-Type": "application/json" }, + getAuthHeaderOptions(token), + ), + body: JSON.stringify(data), + }).then(fetchRespHandler) + .catch(errorHandler) + }, + deleteFlashpackVersion: async (data, token) => { return fetch(`${API_ENDPOINT}/flashpack_version`, { method: "DELETE", @@ -307,6 +319,17 @@ const vehiclesAPI = { .catch(errorHandler) }, + deleteFlashpackVersionECUMapping: async (model, trim, year, flashpack, ecuName, ecuVersion, token) => { + return fetch(`${API_ENDPOINT}/flashpack_version_ecu_mapping/${model}/${trim}/${year}/${flashpack}/${ecuName}/${ecuVersion}`, { + method: "DELETE", + headers: Object.assign( + { "Content-Type": "application/json" }, + getAuthHeaderOptions(token) + ), + }).then(fetchRespHandler) + .catch(errorHandler) + }, + getCarFlashpackVersionInfo: async (vin, token) => { return fetch(`${API_ENDPOINT}/flashpack_version_info/${vin}`, { method: "GET",