diff --git a/src/components/App/__snapshots__/App.test.js.snap b/src/components/App/__snapshots__/App.test.js.snap index fb11ba2..a7883f8 100644 --- a/src/components/App/__snapshots__/App.test.js.snap +++ b/src/components/App/__snapshots__/App.test.js.snap @@ -456,6 +456,28 @@ exports[`App Route / authenticated 1`] = ` /> +
  • + +
    + + Flashpack + +
    + +
    +
  • @@ -1853,6 +1875,28 @@ exports[`App Route /home authenticated 1`] = ` /> +
  • + +
    + + Flashpack + +
    + +
    +
  • @@ -2627,6 +2671,28 @@ exports[`App Route /issue-info authenticated 1`] = ` /> +
  • + +
    + + Flashpack + +
    + +
    +
  • @@ -3239,6 +3305,28 @@ exports[`App Route /issues authenticated 1`] = ` /> +
  • + +
    + + Flashpack + +
    + +
    +
  • @@ -4220,6 +4308,28 @@ exports[`App Route /package-deploy authenticated 1`] = ` /> +
  • + +
    + + Flashpack + +
    + +
    +
  • @@ -5324,6 +5434,28 @@ exports[`App Route /package-status authenticated 1`] = ` /> +
  • + +
    + + Flashpack + +
    + +
    +
  • @@ -6383,6 +6515,28 @@ exports[`App Route /packages authenticated 1`] = ` /> +
  • + +
    + + Flashpack + +
    + +
    +
  • @@ -7561,6 +7715,28 @@ exports[`App Route /page-not-found authenticated 1`] = ` /> +
  • + +
    + + Flashpack + +
    + +
    +
  • @@ -8189,6 +8365,28 @@ exports[`App Route /tools/certificates/add authenticated 1`] = ` /> +
  • + +
    + + Flashpack + +
    + +
    +
  • @@ -9483,6 +9681,28 @@ exports[`App Route /tools/sms/send authenticated 1`] = ` /> +
  • + +
    + + Flashpack + +
    + +
    +
  • @@ -10210,6 +10430,28 @@ exports[`App Route /vehicle-add authenticated 1`] = ` /> +
  • + +
    + + Flashpack + +
    + +
    +
  • @@ -11392,6 +11634,28 @@ exports[`App Route /vehicle-status authenticated 1`] = ` /> +
  • + +
    + + Flashpack + +
    + +
    +
  • @@ -12503,6 +12767,28 @@ exports[`App Route /vehicles authenticated 1`] = ` /> +
  • + +
    + + Flashpack + +
    + +
    +
  • diff --git a/src/components/Contexts/VehicleContext.jsx b/src/components/Contexts/VehicleContext.jsx index c52d2fb..7ebd07f 100644 --- a/src/components/Contexts/VehicleContext.jsx +++ b/src/components/Contexts/VehicleContext.jsx @@ -30,6 +30,10 @@ export const VehicleProvider = ({ children }) => { const [totalFleets, setTotalFleets] = useState(0); const [models, setModels] = useState([]); const [years, setYears] = useState([]); + const [flashpacks, setFlashpacks] = useState([]); + const [totalFlashpacks, setTotalFlashpacks] = useState(0); + const [flashpackECUMappings, setFlashpackECUMappings] = useState([]) + const [totalFlashpackECUMappings, setTotalFlashpackECUMappings] = useState(0) const addConnections = async (cars, token) => { try { @@ -287,6 +291,90 @@ export const VehicleProvider = ({ children }) => { } }; + const getAllFlashpacks = async (options, token) => { + try { + setBusy(true); + + const result = await api.getAllFlashpacks(options, token); + if (result.error) { + throw new Error(`Get all flashpacks error. ${result.message}`); + } + + setFlashpacks(result.data); + if (options && options.offset === 0 && result.total) { + setTotalFlashpacks(result.total); + } + + return result; + } finally { + setBusy(false); + } + }; + + const getFlashpackECUMappings = async (model, year, flashpack, options, token) => { + try { + setBusy(true); + + const result = await api.getFlashpackECUMappings(model, year, flashpack, options, token); + if (result.error) { + throw new Error(`Get flashpack ecu mappings error. ${result.message}`); + } + + setFlashpackECUMappings(result.data); + if (options && options.offset === 0 && result.total) { + setTotalFlashpackECUMappings(result.total); + } + + return result; + } finally { + setBusy(false); + } + }; + + const addFlashpackVersion = async (model, year, flashpack, carFlashpackVersions, token) => { + try { + setBusy(true); + + const data = { + "car_model": model, + "car_year": year, + "flashpack": flashpack, + "car_flashpack_versions": carFlashpackVersions, + } + + const result = await api.addFlashpackVersion(data, token) + if (result.error) { + throw new Error(`Add flashpack version error. ${result.message}`); + } + + return result; + } finally { + setBusy(false) + } + } + + const deleteFlashpackVersion = async (model, year, flashpack, token) => { + try { + setBusy(true); + + const data = { + "car_model": model, + "car_year": year, + "flashpack": flashpack, + } + + const result = await api.deleteFlashpackVersion(data, token); + if (result.error) { + throw new Error(`Delete flashpack ecu mappings error. ${result.message}`); + } + + return result; + } finally { + setBusy(false); + } + + }; + return ( { getVersionLog, uploadConfig, addTags, + flashpacks, + totalFlashpacks, + getAllFlashpacks, + totalFlashpackECUMappings, + flashpackECUMappings, + getFlashpackECUMappings, + addFlashpackVersion, + deleteFlashpackVersion, }} > {children} diff --git a/src/components/Contexts/__mocks__/VehicleContext.jsx b/src/components/Contexts/__mocks__/VehicleContext.jsx index e280bc9..a996671 100644 --- a/src/components/Contexts/__mocks__/VehicleContext.jsx +++ b/src/components/Contexts/__mocks__/VehicleContext.jsx @@ -187,6 +187,44 @@ export const useVehicleContext = () => ({ total: 2, } }), + getAllFlashpacks: jest.fn((options, token) => { + return { + data: [ + { + flashpack: "44.4", + car_model: "Ocean", + car_year: "2023", + }, + { + flashpack: "38.4", + car_model: "Ocean", + car_year: "2023", + }, + ], + total: 2, + } + }), + getFlashpackECUMappings: jest.fn((model, year, flashpack, options, token) => { + return { + data: [ + { + flashpack: "44.4", + car_model: "Ocean", + car_year: "2023", + car_ecu_name: "ADAS", + car_ecu_version: "99", + }, + { + flashpack: "44.4", + car_model: "Ocean", + car_year: "2023", + car_ecu_name: "PDI", + car_ecu_version: "11", + }, + ], + total: 2, + } + }), }); export const setBusy = (val) => { diff --git a/src/components/Flashpack/Add/__snapshots__/index.test.jsx.snap b/src/components/Flashpack/Add/__snapshots__/index.test.jsx.snap new file mode 100644 index 0000000..bcb5f26 --- /dev/null +++ b/src/components/Flashpack/Add/__snapshots__/index.test.jsx.snap @@ -0,0 +1,293 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`FlashpackAdd Render 1`] = ` +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    + + +
    +
    +
    + +
    + + +
    +
    +
    + +
    + + +
    +
    +
    +
    +
    + +
    + + +
    +
    +
    + +
    + + +
    +
    + +
    +
    +
    + +
    +
    +
    +
    +
    +
    +
    +`; diff --git a/src/components/Flashpack/Add/index.jsx b/src/components/Flashpack/Add/index.jsx new file mode 100644 index 0000000..258483d --- /dev/null +++ b/src/components/Flashpack/Add/index.jsx @@ -0,0 +1,239 @@ +import { + Button, + IconButton, + TextField +} from "@material-ui/core"; +import React, { useEffect, useState } from "react"; +import AddCircleIcon from "@material-ui/icons/AddCircle"; +import DeleteIcon from "@material-ui/icons/Delete"; +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 { + token: { + idToken: { jwtToken: token }, + }, + } = useUserContext(); + const classes = useStyles(); + const [redirect, setRedirect] = useState(null); + const { setMessage, setTitle, setSitePath } = useStatusContext(); + const [carModel, setCarModel] = useState(""); + const [carYear, setCarYear] = useState(); + const [flashpack, setFlashpack] = useState(); + const [mappingInputs, setMappingInputs] = useState([{ ecuName: "", ecuVersion: "" }]); + const { + addFlashpackVersion, + busy, + } = useVehicleContext(); + + useEffect(() => { + setTitle(`Add Flashpack Version`); + setSitePath([ + { + label: "Tools", + link: "/tools/flashpacks", + }, + { + label: "Flashpack Versions", + link: "/tools/flashpacks", + }, + { + label: `Add Flashpack Version`, + }, + ]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + const onCarModelChange = (event) => { + setCarModel(event.target.value); + } + + const onCarYearChange = (event) => { + setCarYear(event.target.value); + } + + const onFlashpackChange = (event) => { + setFlashpack(event.target.value); + } + + const onSubmit = async (event) => { + try { + event.preventDefault(); + + const carFlashpackVersions = [] + for (let i = 0; i < mappingInputs.length; i++) { + mappingInputs[i] && mappingInputs[i].ecuName && mappingInputs[i].ecuVersion && + carFlashpackVersions.push({ + "car_model": carModel, + "car_year": parseInt(carYear), + "flashpack": flashpack, + "car_ecu_name": mappingInputs[i].ecuName, + "car_ecu_version": mappingInputs[i].ecuVersion, + }) + } + + const result = await addFlashpackVersion(carModel, parseInt(carYear), flashpack, carFlashpackVersions, token); + if (!result || result.error) return; + + setMessage(`Added ${carYear} ${carModel} ${flashpack}`); + setRedirect(`/tools/flashpack/${carModel}/${carYear}/${flashpack}`); + } catch (e) { + setMessage(e.message); + logger.warn(e.stack); + } + }; + + if (redirect && redirect.length > 0) { + return ; + } + + // code for handling the dynamic input fields adapted from + // https://blog.stackademic.com/how-to-dynamically-add-input-fields-on-button-click-in-reactjs-ddf8d8fe495b + + const handleAddMappingInput = () => { + setMappingInputs([...mappingInputs, { ecuName: "", ecuVersion: "", }]); + }; + + const handleChange = (event, index) => { + let { name, value } = event.target; + let onChangeValue = [...mappingInputs]; + onChangeValue[index][name] = value; + setMappingInputs(onChangeValue); + }; + + const handleDeleteMappingInput = (index) => { + const newArray = [...mappingInputs]; + newArray.splice(index, 1); + setMappingInputs(newArray); + }; + + return ( +
    +
    +
    + + + +
    + {mappingInputs.map((item, index) => ( +
    + handleChange(event, index)} + type="text" + /> + handleChange(event, index)} + type="text" + /> + {mappingInputs.length > 1 && ( + handleDeleteMappingInput(index)} + aria-label={`Delete`} + size="small" + color="primary" + > + + + )} + {index === mappingInputs.length - 1 && ( + handleAddMappingInput()} + aria-label={`Add`} + size="small" + color="primary" + > + + + )} +
    + ))} +
    +
    + +
    +
    + ); +}; + +const FlashpackAdd = () => ( + + + +); + +export default FlashpackAdd; \ No newline at end of file diff --git a/src/components/Flashpack/Add/index.test.jsx b/src/components/Flashpack/Add/index.test.jsx new file mode 100644 index 0000000..ba1235b --- /dev/null +++ b/src/components/Flashpack/Add/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 renderFlashpackAdd = async () => { + const { container } = render( + + + + + + + + + + + + ); + await waitFor(() => { + /* render */ + }); + return container; +}; + +describe("FlashpackAdd", () => { + beforeAll(() => { + addSnapshotSerializer(expect); + }); + + it("Render", async () => { + setToken(TEST_AUTH_OBJECT_FISKER); + const container = await renderFlashpackAdd(); + 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 new file mode 100644 index 0000000..303487f --- /dev/null +++ b/src/components/Flashpack/Details/__snapshots__/index.test.jsx.snap @@ -0,0 +1,99 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`FlashpackDetails Render 1`] = ` +
    +
    +
    +
    +
    +
    + + + + + + + + + + + + + +
    + + ECU Name + + + + + ECU Version + + +
    +
    +
    +
    +
    +
    +
    +`; diff --git a/src/components/Flashpack/Details/index.jsx b/src/components/Flashpack/Details/index.jsx new file mode 100644 index 0000000..2fc8bee --- /dev/null +++ b/src/components/Flashpack/Details/index.jsx @@ -0,0 +1,173 @@ +import React, { useEffect, useState } from "react"; +import { useParams } from "react-router-dom"; +import { + Table, + TableBody, + TableCell, + TableFooter, + TablePagination, + TableRow, +} from "@material-ui/core"; +import { logger } from "../../../services/monitoring"; +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 useStyles from "../../useStyles"; + +const tableColumns = [ + { + id: "car_ecu_name", + label: "ECU Name", + }, + { + id: "car_ecu_version", + label: "ECU Version", + }, +]; + +const PAGE_SIZE = "FLASHPACK_MAPPINGS_TABLE_PAGE_SIZE"; + +const MainForm = () => { + const { model, year, flashpack } = useParams(); + const classes = useStyles(); + const { setMessage, setTitle, setSitePath } = useStatusContext(); + const [pageSize, setPageSize] = useLocalStorage(PAGE_SIZE, 10); + const [pageIndex, setPageIndex] = useState(0); + const [orderBy, setOrderBy] = useState("flashpack"); + const [order, setOrder] = useState("desc"); + const { + getFlashpackECUMappings, + flashpackECUMappings, + totalFlashpackECUMappings, + } = useVehicleContext(); + const { + token: { + idToken: { jwtToken: token }, + }, + } = useUserContext(); + + useEffect(() => { + setTitle(`${year} ${model} Flashpack Version ${flashpack}`); + setSitePath([ + { + label: "Tools", + link: "/tools/flashpacks", + }, + { + label: "Flashpack Versions", + link: "/tools/flashpacks", + }, + { + label: `Flashpack ${flashpack}`, + }, + ]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + useEffect(() => { + loadFlashpackECUMappings(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [token, token, pageIndex, pageSize, orderBy, order]); + + const loadFlashpackECUMappings = async () => { + try { + if (!token) return; + await getFlashpackECUMappings( + model, + year, + flashpack, + { + 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); + } + }; + + return ( +
    + + + + {flashpackECUMappings && flashpackECUMappings.map((row, index) => ( + + + {row.car_ecu_name} + + + {row.car_ecu_version} + + + ))} + + + + {!flashpackECUMappings || flashpackECUMappings.length === 0 ? ( + No Flashpack ECU Mappings + ) : ( + )} + + +
    +
    + ); +}; + +const FlashpackDetails = () => ( + + + +); + +export default FlashpackDetails; \ No newline at end of file diff --git a/src/components/Flashpack/Details/index.test.jsx b/src/components/Flashpack/Details/index.test.jsx new file mode 100644 index 0000000..b0f11fb --- /dev/null +++ b/src/components/Flashpack/Details/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 renderFlashpackDetails = async () => { + const { container } = render( + + + + + + + + + + + + ); + await waitFor(() => { + /* render */ + }); + return container; +}; + +describe("FlashpackDetails", () => { + beforeAll(() => { + addSnapshotSerializer(expect); + }); + + it("Render", async () => { + setToken(TEST_AUTH_OBJECT_FISKER); + const container = await renderFlashpackDetails(); + expect(container).toMatchSnapshot(); + }); +}); diff --git a/src/components/Flashpack/__snapshots__/index.test.jsx.snap b/src/components/Flashpack/__snapshots__/index.test.jsx.snap new file mode 100644 index 0000000..02b2c42 --- /dev/null +++ b/src/components/Flashpack/__snapshots__/index.test.jsx.snap @@ -0,0 +1,175 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Flashpack Render 1`] = ` +
    +
    +
    +
    +
    +
    +
    + +
    + + + + + + + + + + + + + + + +
    + + Flashpack Number + + sorted descending + + + + + + Model + + + + + Year + + + + + Delete + + +
    +
    +
    +
    +
    +
    +
    +
    +`; diff --git a/src/components/Flashpack/index.jsx b/src/components/Flashpack/index.jsx new file mode 100644 index 0000000..a682f39 --- /dev/null +++ b/src/components/Flashpack/index.jsx @@ -0,0 +1,219 @@ +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 { Link } from "react-router-dom"; +import { logger } from "../../services/monitoring"; +import React, { useEffect, useState } from "react"; +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 = [ + { + id: "flashpack", + label: "Flashpack Number", + }, + { + id: "car_model", + label: "Model", + }, + { + id: "car_year", + label: "Year", + }, + { + id: "delete", + label: "Delete", + }, +]; + +const PAGE_SIZE = "FLASHPACKS_TABLE_PAGE_SIZE"; + +const MainForm = () => { + const classes = useStyles(); + const { setMessage, setTitle, setSitePath } = useStatusContext(); + const [pageSize, setPageSize] = useLocalStorage(PAGE_SIZE, 10); + const [pageIndex, setPageIndex] = useState(0); + const [showDeleteModal, setShowDeleteModal] = useState(false); + const [rowToDelete, setRowToDelete] = useState({}); + const { + getAllFlashpacks, + flashpacks, + totalFlashpacks, + deleteFlashpackVersion, + } = useVehicleContext(); + const { + token: { + idToken: { jwtToken: token }, + }, + groups, + providers, + } = useUserContext(); + + useEffect(() => { + setTitle("Flashpack Versions"); + setSitePath([ + { + label: "Tools", + link: "/tools/flashpacks", + }, + { + label: "Flashpack Versions", + }, + ]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + useEffect(() => { + loadFlashpacks(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [token, token, pageIndex, pageSize]); + + const loadFlashpacks = async () => { + try { + if (!token) return; + await getAllFlashpacks( + { + limit: pageSize, + offset: pageSize * pageIndex, + }, + 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 onDeleteClick = (row) => { + setRowToDelete(row); + setShowDeleteModal(true); + } + + const sendDelete = async () => { + if (rowToDelete) { + try { + const result = await deleteFlashpackVersion(rowToDelete.car_model, rowToDelete.car_year, rowToDelete.flashpack, token); + if (!result || result.error) return; + + setMessage(`Deleted ${rowToDelete.car_year} ${rowToDelete.car_model} ${rowToDelete.flashpack}`); + loadFlashpacks(); + } catch (e) { + setMessage(e.message); + logger.warn(e.stack); + } + } + }; + + return ( +
    + + + + + + + + + { return null }} + /> + + {flashpacks && flashpacks.map((row, index) => ( + + + + {row.flashpack} + + + + {row.car_model} + + + {row.car_year} + + + + onDeleteClick(row)} + aria-label={`Send delete for ${row.car_year} ${row.car_model} ${row.flashpack}`} + size="small" + color="primary" + > + + + + + + ))} + + + + {!flashpacks || flashpacks.length === 0 ? ( + No Flashpack Numbers + ) : ( + )} + + +
    + setShowDeleteModal(false)} + deleteFunction={sendDelete} + /> +
    + ); +}; + +const Flashpacks = () => ( + + + +); + +export default Flashpacks; \ No newline at end of file diff --git a/src/components/Flashpack/index.test.jsx b/src/components/Flashpack/index.test.jsx new file mode 100644 index 0000000..1d282cf --- /dev/null +++ b/src/components/Flashpack/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 renderFlashpack = async () => { + const { container } = render( + + + + + + + + + + + + ); + await waitFor(() => { + /* render */ + }); + return container; +}; + +describe("Flashpack", () => { + beforeAll(() => { + addSnapshotSerializer(expect); + }); + + it("Render", async () => { + setToken(TEST_AUTH_OBJECT_FISKER); + const container = await renderFlashpack(); + expect(container).toMatchSnapshot(); + }); +}); diff --git a/src/components/Layouts/SideMenu.jsx b/src/components/Layouts/SideMenu.jsx index 61139ae..97a89e8 100644 --- a/src/components/Layouts/SideMenu.jsx +++ b/src/components/Layouts/SideMenu.jsx @@ -79,6 +79,11 @@ const menuData = [ to: "/tools/sms/send", rolesPerProvider: Permissions.FiskerCreate, }, + { + label: "Flashpack", + to: "/tools/flashpacks", + rolesPerProvider: Permissions.FiskerRead, + }, ], }, ]; diff --git a/src/components/Layouts/__snapshots__/SideMenu.test.jsx.snap b/src/components/Layouts/__snapshots__/SideMenu.test.jsx.snap index 44be0aa..2e282bf 100644 --- a/src/components/Layouts/__snapshots__/SideMenu.test.jsx.snap +++ b/src/components/Layouts/__snapshots__/SideMenu.test.jsx.snap @@ -375,6 +375,28 @@ exports[`SideMenu Authenticated 1`] = ` /> +
  • + +
    + + Flashpack + +
    + +
    +
  • diff --git a/src/components/Routes/SiteRoutes.jsx b/src/components/Routes/SiteRoutes.jsx index ed31bb0..aac7613 100644 --- a/src/components/Routes/SiteRoutes.jsx +++ b/src/components/Routes/SiteRoutes.jsx @@ -40,6 +40,9 @@ const SecurityDLL = React.lazy(() => import("../Magna/SecurityDLL")); const SMSSend = React.lazy(() => import("../SMS/Send")); 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 FlashpackAdd = React.lazy(() => import("../Flashpack/Add")) const Datascope = React.lazy(() => import("../Dashboard")); const SumsRxSwin = React.lazy(() => import("../SUMS")); const SumsRxSwinAdd = React.lazy(() => import("../SUMS/Add")); @@ -280,6 +283,33 @@ const SiteRoutes = () => { rolesPerGroup={Permissions.FiskerCreate} providers={providers} /> + } + type={TYPES.PROTECTED} + token={token} + groups={groups} + rolesPerGroup={Permissions.FiskerRead} + providers={providers} + /> + } + type={TYPES.PROTECTED} + token={token} + groups={groups} + rolesPerGroup={Permissions.FiskerRead} + providers={providers} + /> + } + type={TYPES.PROTECTED} + token={token} + groups={groups} + rolesPerGroup={Permissions.FiskerCreate} + providers={providers} + /> } diff --git a/src/services/__mocks__/vehiclesAPI.js b/src/services/__mocks__/vehiclesAPI.js index 966dd3f..e4baec9 100644 --- a/src/services/__mocks__/vehiclesAPI.js +++ b/src/services/__mocks__/vehiclesAPI.js @@ -199,6 +199,36 @@ const vehiclesAPI = { sendDiagnosticCommand: async (search) => ({ Message: `remote diagnostic command sent to ${search.vins.length} vehicles` }), + getAllFlashpacks: async (token) => { + return { + data: ["41.14", "43.19"], + }; + }, + getFlashpackECUMappings: async (model, year, flashpack, token) => { + return { + "data": [ + { + "flashpack": "41.14", + "car_model": "Ocean", + "car_year": 2023, + "car_ecu_name": "ADAS", + "car_ecu_version": "ADASVersion" + }, + { + "flashpack": "41.14", + "car_model": "Ocean", + "car_year": 2023, "car_ecu_name": "ACUN", + "car_ecu_version": "ACUNVersion" + } + ], + }; + }, + addFlashpackECUMapping: async (data, token) => { + return { message: "Created" }; + }, + deleteFlashpackECUMapping: async (data, token) => { + return { message: "Deleted" }; + }, }; export default vehiclesAPI; diff --git a/src/services/vehiclesAPI.js b/src/services/vehiclesAPI.js index f40da4e..1ad68ec 100644 --- a/src/services/vehiclesAPI.js +++ b/src/services/vehiclesAPI.js @@ -260,6 +260,52 @@ const vehiclesAPI = { }).then(fetchRespHandler) .catch(errorHandler) }, + + getAllFlashpacks: async (options, token) => { + return fetch(addQueryParams(`${API_ENDPOINT}/flashpack_versions`, options), { + method: "GET", + headers: Object.assign( + { "Content-Type": "application/json" }, + getAuthHeaderOptions(token) + ), + }).then(fetchRespHandler) + .catch(errorHandler) + }, + + getFlashpackECUMappings: async (model, year, flashpack, options, token) => { + return fetch(addQueryParams(`${API_ENDPOINT}/flashpack_version_ecu_mappings/${model}/${year}/${flashpack}`, options), { + method: "GET", + headers: Object.assign( + { "Content-Type": "application/json" }, + getAuthHeaderOptions(token) + ), + }).then(fetchRespHandler) + .catch(errorHandler) + }, + + addFlashpackVersion: async (data, token) => { + return fetch(`${API_ENDPOINT}/flashpack_version`, { + 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", + headers: Object.assign( + { "Content-Type": "application/json" }, + getAuthHeaderOptions(token) + ), + body: JSON.stringify(data), + }).then(fetchRespHandler) + .catch(errorHandler) + }, }; export default vehiclesAPI;