From b80a2cb8bf56976f1c6d0fd7373a07f5ff27586f Mon Sep 17 00:00:00 2001 From: John Wu <76966357+jwu-fisker@users.noreply.github.com> Date: Tue, 10 Jan 2023 18:30:39 -0800 Subject: [PATCH] Release/0.0.3 to main (#254) * CEC-2628 - Display IP in digital twin in portal (#251) * CEC-3453 Update security dll instructions (#252) * CEC-2752-Add-Mobile-Issue-Tracker (#250) * first commit * removed comments * remove more comments * fix build issues * fix unused vars * update snapshot * fix test * Fix connect ECONNREFUSED 127.0.0.1:80 * Test Magna side menu * attempt to pass test * fix test * remove comments * fix some code smells * fix test * resolve comments * fix bug * resolved comments * resolve comments * resolve comments * update snapshot * resolved comments Co-authored-by: jwu-fisker * Cec 2752 small fix (#253) * first commit * removed comments * remove more comments * fix build issues * fix unused vars * update snapshot * fix test * Fix connect ECONNREFUSED 127.0.0.1:80 * Test Magna side menu * attempt to pass test * fix test * remove comments * fix some code smells * fix test * resolve comments * fix bug * resolved comments * resolve comments * resolve comments * update snapshot * resolved comments * small fix Co-authored-by: jwu-fisker Co-authored-by: Paul Adamsen <117673433+pauladamseniii@users.noreply.github.com> Co-authored-by: das31 <31259710+das31@users.noreply.github.com> --- src/components/App/App.test.js | 27 +- .../App/__snapshots__/App.test.js.snap | 2000 ++++++++++++++++- .../__snapshots__/CarUpdatesTab.test.jsx.snap | 118 +- .../DigitalTwinTab.test.jsx.snap | 11 + src/components/Contexts/IssueContext.jsx | 55 + src/components/Contexts/IssueContext.test.jsx | 102 + .../Contexts/__mocks__/IssueContext.jsx | 35 + .../Contexts/__mocks__/VehicleContext.jsx | 11 +- .../Controls/CarUpdatesTable/index.jsx | 5 +- .../Controls/IssueSelectionTable/index.jsx | 217 ++ src/components/Dashboard/index.jsx | 8 +- src/components/DigitalTwin/index.js | 7 +- src/components/Issues/Info/Details/index.jsx | 79 + src/components/Issues/Info/DetailsTab.jsx | 21 + src/components/Issues/Info/index.jsx | 104 + src/components/Issues/List/index.jsx | 51 + src/components/Layouts/SideMenu.jsx | 7 + src/components/Layouts/SideMenu.test.jsx | 11 +- .../__snapshots__/SideMenu.test.jsx.snap | 221 ++ .../__snapshots__/index.test.jsx.snap | 8 +- src/components/Magna/SecurityDLL/result.jsx | 3 +- src/components/Routes/SiteRoutes.jsx | 20 + .../SupersetDashboardList.jsx | 7 +- src/components/VehicleMap/index.jsx | 1 + src/services/__mocks__/issueAPI.js | 11 + src/services/__mocks__/superset.js | 6 +- src/services/__mocks__/suppliersAPI.js | 3 + src/services/issueAPI.js | 45 + src/services/superset.js | 2 +- 29 files changed, 3056 insertions(+), 140 deletions(-) create mode 100644 src/components/Contexts/IssueContext.jsx create mode 100644 src/components/Contexts/IssueContext.test.jsx create mode 100644 src/components/Contexts/__mocks__/IssueContext.jsx create mode 100644 src/components/Controls/IssueSelectionTable/index.jsx create mode 100644 src/components/Issues/Info/Details/index.jsx create mode 100644 src/components/Issues/Info/DetailsTab.jsx create mode 100644 src/components/Issues/Info/index.jsx create mode 100644 src/components/Issues/List/index.jsx create mode 100644 src/services/__mocks__/issueAPI.js create mode 100644 src/services/issueAPI.js diff --git a/src/components/App/App.test.js b/src/components/App/App.test.js index 0eab94a..b118fc8 100644 --- a/src/components/App/App.test.js +++ b/src/components/App/App.test.js @@ -3,9 +3,12 @@ jest.mock("../Contexts/FileUploadContext"); jest.mock("../Contexts/VehicleContext"); jest.mock("../Contexts/ManifestsContext"); jest.mock("../Contexts/UserContext"); +jest.mock("../Contexts/IssueContext"); jest.mock("../../services/monitoring"); jest.mock("../../services/vehiclesAPI"); -jest.mock("../../services/superset") +jest.mock("../../services/superset"); +jest.mock("../../services/suppliersAPI"); +jest.mock("../../services/issueAPI"); import { act, cleanup, render, @@ -39,13 +42,14 @@ const check = async (path, selector, compare) => { const sleepAndCheck = async (path, selector, compare) => { const container = await renderRoute(path); - await waitFor(() => {}); + await waitFor(() => { }); expect(container.querySelector(selector).innerHTML).toEqual(compare); expect(container).toMatchSnapshot(); }; describe("App", () => { beforeAll(() => { + global.URL.createObjectURL = jest.fn(); addSnapshotSerializer(expect); }, 60000); @@ -78,6 +82,14 @@ describe("App", () => { await check("/vehicle-add", "span.MuiButton-label", "Sign In"); }); + it("Route /issues unauthenticated", async () => { + await check("/issues", "span.MuiButton-label", "Sign In"); + }); + + it("Route /issue-info unauthenticated", async () => { + await check("/issue-info/FISKER123", "span.MuiButton-label", "Sign In"); + }); + it("Route /vehicles unauthenticated", async () => { await check("/vehicles", "span.MuiButton-label", "Sign In"); }); @@ -158,6 +170,17 @@ describe("App", () => { await check("/vehicles", "h6", "Vehicles"); }); + it("Route /issues authenticated", async () => { + setToken(TEST_AUTH_OBJECT_FISKER); + await check("/issues", "h6", "Issues"); + }); + + it("Route /issue-info authenticated", async () => { + setToken(TEST_AUTH_OBJECT_FISKER); + await check("/issue-info/FISKER123", "h6", "Issue FISKER123 Details"); + }); + + it("Route /vehicle-status authenticated", async () => { setToken(TEST_AUTH_OBJECT_FISKER); await check("/vehicle-status/FISKER123", "h6", "Vehicle FISKER123 Details"); diff --git a/src/components/App/__snapshots__/App.test.js.snap b/src/components/App/__snapshots__/App.test.js.snap index 6d15848..348551e 100644 --- a/src/components/App/__snapshots__/App.test.js.snap +++ b/src/components/App/__snapshots__/App.test.js.snap @@ -173,6 +173,42 @@ exports[`App Route / authenticated 1`] = ` /> +
  • + +
    + +
    +
    + + Issues + +
    + +
    +
  • +
  • + +
    + +
    +
    + + Issues + +
    + +
    +
  • `; +exports[`App Route /issue-info authenticated 1`] = ` +
    +
    +
    +
    + +
    + +
    +
    +
    +
    +
    +
    +
    +
    + +
    + +
    +
    +
    +
    +
    +
    +
    + Issue Details +
    +
    +
    +
    +
    +

    + + ID + + : + FISKER123 +

    +

    + + VIN + + : + 1GNGC26RXXJ407648 +

    +

    + + Title + + : + sometitle +

    +

    + + Description + + : + 2343242 +

    +

    + + timestamp + + : + 2022-12-09T23:16:38.074858Z +

    + Issue images +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +`; + +exports[`App Route /issue-info unauthenticated 1`] = ` +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + + Sign In + + + +

    + + Note: Your email address will be used as the user id + +

    +
    +
    +
    +
    +
    +
    +`; + +exports[`App Route /issues authenticated 1`] = ` +
    +
    +
    +
    +
    +
    +
    + Issues +
    +
    +
    + +
    +
    +
    + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Id + + + + + VIN + + + + + Title + + + + + Description + + + + + Driver ID + + + + + Created + + sorted ascending + + + + +
    + + 15 + + + 4Y1SL65848Z411439 + + sometitle + + 2343242 + + 12345 + + 12/9/2022 11:16:38 PM +
    + + 17 + + + 1GNGC26RXXJ407648 + + sometitle + + 2343242 + + valid-cognito-id-1 + + 12/9/2022 11:16:38 PM +
    +
    +
    +
    +
    +
    +
    +
    +
    +`; + +exports[`App Route /issues unauthenticated 1`] = ` +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + + Sign In + + + +

    + + Note: Your email address will be used as the user id + +

    +
    +
    +
    +
    +
    +
    +`; + exports[`App Route /package-deploy authenticated 1`] = `
  • +
  • + +
    + +
    +
    + + Issues + +
    + +
    +
  • +
  • + +
    + +
    +
    + + Issues + +
    + +
    +
  • +
  • + +
    + +
    +
    + + Issues + +
    + +
    +
  • +
  • + +
    + +
    +
    + + Issues + +
    + +
    +
  • +
  • + +
    + +
    +
    + + Issues + +
    + +
    +
  • -

    - Generating certificates... -

    +
    +

    + Please click the links below to download the certificate.pem, key.pem and the security.dll (either the 32-bit or 64-bit version to match your application). Install all files in the same folder, and then run your application of choice to connect to the Fisker cloud. +

    +

    + + Important Note: + + Certificates expire in one month from the time they are generated. They have to be re-downloaded again to connect to the Fisker cloud. +

    +
    +
    @@ -5798,6 +7648,42 @@ exports[`App Route /tools/sms/send authenticated 1`] = ` />
  • +
  • + +
    + +
    +
    + + Issues + +
    + +
    +
  • +
  • + +
    + +
    +
    + + Issues + +
    + +
    +
  • +
  • + +
    + +
    +
    + + Issues + +
    + +
    +
  • +
  • + +
    + +
    +
    + + Issues + +
    + +
    +
  • - -
    -
    -

    - Rows per page: -

    -
    - - -
    -

    - 0-0 of 0 -

    -
    - - -
    -
    - +

    + No Car Updates found +

    diff --git a/src/components/Cars/Status/__snapshots__/DigitalTwinTab.test.jsx.snap b/src/components/Cars/Status/__snapshots__/DigitalTwinTab.test.jsx.snap index 607b45f..7340395 100644 --- a/src/components/Cars/Status/__snapshots__/DigitalTwinTab.test.jsx.snap +++ b/src/components/Cars/Status/__snapshots__/DigitalTwinTab.test.jsx.snap @@ -130,6 +130,17 @@ exports[`DigitalTwinTab Render 1`] = ` 1000000

    +
    +

    + + Trex IP + + : + 172.20.0.17:49850 +

    +
    diff --git a/src/components/Contexts/IssueContext.jsx b/src/components/Contexts/IssueContext.jsx new file mode 100644 index 0000000..570929a --- /dev/null +++ b/src/components/Contexts/IssueContext.jsx @@ -0,0 +1,55 @@ +import React, { useContext, useState, useMemo, useCallback } from "react"; +import api from "../../services/issueAPI"; + +const IssueContext = React.createContext(); + +export const IssueProvider = ({ children }) => { + const [issue, setIssue] = useState({}); + const [issues, setIssues] = useState([]); + const [totalIssues, setTotalIssues] = useState(0); + + const getIssue = useCallback(async (id, token) => { + const result = await api.getIssue(id, token); + if (result.error) throw new Error(`Get issue error. ${result.message}`); + + setIssue(result.data ?? []); + return result; + }, []); + + const getIssues = useCallback(async (search,token) => { + const result = await api.getIssues(search,token); + if (result.error) { + setIssues([]); + throw new Error(`Get issues error. ${result.message}`); + } + setIssues(result.data ?? []); + if (result.total) { + setTotalIssues(result.total); + } + }, []); + + const deleteIssue = useCallback(async (id, token) => { + const result = await api.deleteIssue(id, token); + if (result.error) + throw new Error(`Delete issue error. ${result.message}`); + return result; + }, []); + + const value = useMemo(() => ({ + totalIssues, + issue, + issues, + + deleteIssue, + getIssue, + getIssues, + }), [totalIssues, issue, issues, deleteIssue, getIssue, getIssues]); + + return ( + + {children} + + ); +}; + +export const useIssueContext = () => useContext(IssueContext); diff --git a/src/components/Contexts/IssueContext.test.jsx b/src/components/Contexts/IssueContext.test.jsx new file mode 100644 index 0000000..b529158 --- /dev/null +++ b/src/components/Contexts/IssueContext.test.jsx @@ -0,0 +1,102 @@ +jest.mock("../../services/issueAPI"); + +import { + render, + cleanup, + screen, + fireEvent, + waitFor, +} from "@testing-library/react"; +import { IssueProvider, useIssueContext } from "./IssueContext"; + +const checkIssueResult = (issue) => { + expect(screen.getByTestId("issue").innerHTML).toEqual(issue); +}; + +const checkIssuesResult = (issues) => { + expect(screen.getByTestId("issues").innerHTML).toEqual(issues); +}; + +describe("IssueContext", () => { + describe("getIssues", () => { + beforeEach(() => { + const TestComp = () => { + const { issues, getIssues } = useIssueContext(); + + return ( + <> +
    {JSON.stringify(issues)}
    +
  • +
  • + +
    + +
    +
    + + Issues + +
    + +
    +
  • `; +exports[`SideMenu Magna Authenticated 1`] = ` +
    +
    + +
    +
    +`; + exports[`SideMenu Unauthenticated 1`] = `

    - Click to download your certificates and security.dll. -
    + Please click the links below to download the certificate.pem, key.pem and the security.dll (either the 32-bit or 64-bit version to match your application). Install all files in the same folder, and then run your application of choice to connect to the Fisker cloud. +

    +

    - DLL will not work unless certificate.pem and key.pem are placed next to the DLL + Important Note: + Certificates expire in one month from the time they are generated. They have to be re-downloaded again to connect to the Fisker cloud.

    • diff --git a/src/components/Magna/SecurityDLL/result.jsx b/src/components/Magna/SecurityDLL/result.jsx index c81b511..f26ba06 100644 --- a/src/components/Magna/SecurityDLL/result.jsx +++ b/src/components/Magna/SecurityDLL/result.jsx @@ -8,7 +8,8 @@ const CertMimeType = "application/x-pem-file"; const Result = ({ public_key, private_key }) => ( <> -

      Click to download your certificates and security.dll.
      DLL will not work unless certificate.pem and key.pem are placed next to the DLL

      +

      Please click the links below to download the certificate.pem, key.pem and the security.dll (either the 32-bit or 64-bit version to match your application). Install all files in the same folder, and then run your application of choice to connect to the Fisker cloud.

      +

      Important Note: Certificates expire in one month from the time they are generated. They have to be re-downloaded again to connect to the Fisker cloud.

      • import("../CANFilter/Add")); const CANFilterUpdate = React.lazy(() => import("../CANFilter/Update")); +const IssuesList = React.lazy(() => import("../Issues/List")) +const IssueInfo = React.lazy(() => import("../Issues/Info")) const CarsList = React.lazy(() => import("../Cars/List")); const CarStatus = React.lazy(() => import("../Cars/Status")); const CarUpdateStatus = React.lazy(() => import("../Cars/UpdateStatus")); @@ -175,6 +177,24 @@ const SiteRoutes = () => { 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.FiskerRead} + providers={providers} + /> } diff --git a/src/components/SupersetDashboardList/SupersetDashboardList.jsx b/src/components/SupersetDashboardList/SupersetDashboardList.jsx index cce2712..23714d6 100644 --- a/src/components/SupersetDashboardList/SupersetDashboardList.jsx +++ b/src/components/SupersetDashboardList/SupersetDashboardList.jsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from "react"; +import React, { useEffect, useState } from "react"; import { useUserContext } from "../Contexts/UserContext"; import supersetAPI from "../../services/superset"; @@ -29,10 +29,11 @@ const SupersetDashboardList = () => { internalEffect(token) } - + return () => { + setDashboardList([]); + } }, [groups, token]) - return (
          {dashboardList.map((subitem, index) => ( diff --git a/src/components/VehicleMap/index.jsx b/src/components/VehicleMap/index.jsx index c685dac..ff61861 100644 --- a/src/components/VehicleMap/index.jsx +++ b/src/components/VehicleMap/index.jsx @@ -193,6 +193,7 @@ const Component = () => { location={carState.location} windows={carState.windows} trex_version={carState.trex_version} + ip={carState.ip} updated={carState.updated} className={classes.popup} onClose={handleClose} diff --git a/src/services/__mocks__/issueAPI.js b/src/services/__mocks__/issueAPI.js new file mode 100644 index 0000000..2f7459d --- /dev/null +++ b/src/services/__mocks__/issueAPI.js @@ -0,0 +1,11 @@ +const issueAPI = { + getIssues: async (token) => { + return { "data": [{ "id": 18, "vin": "1GNGC26RXXJ407648", "title": "sometitle", "description": "2343242", "driver_id": "valid-cognito-id-1", "timestamp": "2022-12-09T23:16:38.074858Z" }, { "id": 19, "vin": "1GNGC26RXXJ407648", "title": "sometitle", "description": "2343242", "driver_id": "valid-cognito-id-1", "timestamp": "2022-12-09T23:16:38.074858Z" }, { "id": 20, "vin": "1GNGC26RXXJ407648", "title": "sometitle", "description": "2343242", "driver_id": "valid-cognito-id-1", "timestamp": "2022-12-09T23:16:38.074858Z" }, { "id": 21, "vin": "1GNGC26RXXJ407648", "title": "sometitle", "description": "2343242", "driver_id": "valid-cognito-id-1", "timestamp": "2022-12-09T23:16:38.074858Z" }, { "id": 22, "vin": "1GNGC26RXXJ407648", "title": "sometitle", "description": "2343242", "driver_id": "valid-cognito-id-1", "timestamp": "2022-12-09T23:16:38.074858Z" }, { "id": 25, "vin": "1GNGC26RXXJ407648", "title": "Example HMI Problem", "description": "HMI blue screen", "driver_id": "0b6b1930-b20a-4fce-967a-efac6a01fd10", "timestamp": "2022-12-19T22:25:03.848855Z" }, { "id": 26, "vin": "1GNGC26RXXJ407648", "title": "sometitle", "description": "2343242", "driver_id": "valid-cognito-id-1", "timestamp": "2022-12-09T23:16:38.074858Z" }, { "id": 27, "vin": "1GNGC26RXXJ407648", "title": "sometitle", "description": "2343242", "driver_id": "valid-cognito-id-1", "timestamp": "2022-12-09T23:16:38.074858Z" }, { "id": 28, "vin": "1GNGC26RXXJ407648", "title": "sometitle", "description": "2343242", "driver_id": "valid-cognito-id-1", "timestamp": "2022-12-09T23:16:38.074858Z" }], "total": 9 } + }, + getIssue: async (token) => { + return { "data": { "id": 18, "vin": "1GNGC26RXXJ407648", "title": "sometitle", "description": "2343242", "driver_id": "valid-cognito-id-1", "timestamp": "2022-12-09T23:16:38.074858Z", "images": [{ "id": 15, "image": "SGVsbG8x", "issue_id": 18 }] } } + } +} + + +export default issueAPI; \ No newline at end of file diff --git a/src/services/__mocks__/superset.js b/src/services/__mocks__/superset.js index cd2af34..9c02e96 100644 --- a/src/services/__mocks__/superset.js +++ b/src/services/__mocks__/superset.js @@ -1,4 +1,7 @@ const SupersetAPI = { + getGuestToken: async () => { + return "" + }, getEmbeddedDashboards: async () => { return [{ title: "test title", @@ -7,7 +10,8 @@ const SupersetAPI = { }, SupersetDashboardID: () => { return "11111100-0000-1111-1111-000000000000" - } + }, + SupersetDashboardURL: () => (null), } export default SupersetAPI \ No newline at end of file diff --git a/src/services/__mocks__/suppliersAPI.js b/src/services/__mocks__/suppliersAPI.js index 7c4f2e0..9b8e4ea 100644 --- a/src/services/__mocks__/suppliersAPI.js +++ b/src/services/__mocks__/suppliersAPI.js @@ -59,6 +59,9 @@ const suppliersAPI = { if (index >= 0) data[index] = supplier; return supplier; }, + getManufactureCert: async () => { + return {public_key:"-----BEGIN CERTIFICATE-----\nTEST\n-----END CERTIFICATE-----",private_key:"-----BEGIN RSA PRIVATE KEY-----\nTEST\n-----END RSA PRIVATE KEY-----","serial_number":"66:c8:45:20:bd:75:04:79:e8:5e:0e:46:5b:5c:1a:21:8b:ea:81:9f","type":"rsa"} + }, }; export default suppliersAPI; diff --git a/src/services/issueAPI.js b/src/services/issueAPI.js new file mode 100644 index 0000000..b329b8f --- /dev/null +++ b/src/services/issueAPI.js @@ -0,0 +1,45 @@ +import { + addQueryParams, errorHandler, fetchRespHandler, getAuthHeaderOptions +} from "../utils/http"; + +const API_ENDPOINT = process.env.REACT_APP_OTA_SERVICE_URL; + +const issuesAPI = { + deleteIssue: async (id, token) => + fetch(`${API_ENDPOINT}/issues/${id}`, { + method: "DELETE", + headers: Object.assign( + { "Content-Type": "application/json" }, + getAuthHeaderOptions(token) + ), + }) + .then(fetchRespHandler) + .catch(errorHandler), + + getIssue: async (id, token) => + fetch(`${API_ENDPOINT}/issues/${id}`, { + method: "GET", + headers: Object.assign( + { "Content-Type": "application/json" }, + getAuthHeaderOptions(token) + ), + }) + .then(fetchRespHandler) + .catch(errorHandler), + + getIssues: async (search, token) => { + const u = addQueryParams(`${API_ENDPOINT}/issues`, search); + return fetch(u, { + method: "GET", + headers: Object.assign( + { "Content-Type": "application/json" }, + getAuthHeaderOptions(token) + ), + }) + .then(fetchRespHandler) + .catch(errorHandler); + }, + +}; + +export default issuesAPI; diff --git a/src/services/superset.js b/src/services/superset.js index 7d827bc..54d6036 100644 --- a/src/services/superset.js +++ b/src/services/superset.js @@ -1,5 +1,5 @@ import { - addQueryParams, getAuthHeaderOptions + addQueryParams, getAuthHeaderOptions } from "../utils/http"; //Added the token we got from the first authorization and set it as the auth token, and that allowed us to hit the request