diff --git a/.env.cec-euprd b/.env.cec-euprd index 81bef6f..0bcb8e9 100644 --- a/.env.cec-euprd +++ b/.env.cec-euprd @@ -14,4 +14,5 @@ REACT_APP_ROLE_DELETE=bfd1cccc-213a-4f31-b3d1-6e685976aec8 REACT_APP_ROLE_GENERATE_CERTIFICATE=9af2d8c0-c26d-4d6d-bbd1-ac53cbd37ebc REACT_APP_ROLE_MANUFACTURE=3412e11a-a2d1-4355-be3e-ef9aa5065b69 REACT_APP_ROLE_SUPPLIER_APPROVER=a6c9805e-80b2-42b2-bfbb-9df52e5504d8 +REACT_APP_ROLE_MANIFEST_MIGRATION=42798c8a-9fa7-4fb4-82c0-9582cabe364f REACT_APP_ECCKEY_ENV= diff --git a/.env.cec-prd b/.env.cec-prd index 2baee03..54ea7a8 100644 --- a/.env.cec-prd +++ b/.env.cec-prd @@ -14,4 +14,5 @@ REACT_APP_ROLE_DELETE=bfd1cccc-213a-4f31-b3d1-6e685976aec8 REACT_APP_ROLE_GENERATE_CERTIFICATE=9af2d8c0-c26d-4d6d-bbd1-ac53cbd37ebc REACT_APP_ROLE_MANUFACTURE=3412e11a-a2d1-4355-be3e-ef9aa5065b69 REACT_APP_ROLE_SUPPLIER_APPROVER=a6c9805e-80b2-42b2-bfbb-9df52e5504d8 +REACT_APP_ROLE_MANIFEST_MIGRATION=42798c8a-9fa7-4fb4-82c0-9582cabe364f REACT_APP_ECCKEY_ENV= diff --git a/.env.dev b/.env.dev index f984f2e..e08c37a 100644 --- a/.env.dev +++ b/.env.dev @@ -7,11 +7,12 @@ REACT_APP_MAGNA_GROUP_ID=68273225-9da4-4fa7-aea5-38e16ec471fe REACT_APP_OTA_SERVICE_URL=https://dev-gw.cloud.fiskerinc.com/ota_update REACT_APP_SECURITY_DLL_URL=https://assets.fiskerdps.com/cloud-supplier/fisker_security_32.dll REACT_APP_SECURITY_DLL_64_URL=https://assets.fiskerdps.com/cloud-supplier/fisker_security_64.dll -REACT_APP_SUPERSET_URL=https://dev-superset-new.cloud.fiskerinc.com +REACT_APP_SUPERSET_URL=https://dev-superset.cloud.fiskerinc.com REACT_APP_ROLE_CREATE=efcc3025-e2d8-4212-8227-805c7be39d2c REACT_APP_ROLE_READ_ONLY=a729bbd4-2038-4649-9127-16782bb1e701 REACT_APP_ROLE_DELETE=8f78dce7-f5f9-4033-a10c-c9c7408bfcfe REACT_APP_ROLE_GENERATE_CERTIFICATE=746f34b0-9ba0-4b5d-8d84-0256a9c8e390 REACT_APP_ROLE_MANUFACTURE=3412e11a-a2d1-4355-be3e-ef9aa5065b69 REACT_APP_ROLE_SUPPLIER_APPROVER=a6c9805e-80b2-42b2-bfbb-9df52e5504d8 +REACT_APP_ROLE_MANIFEST_MIGRATION=42798c8a-9fa7-4fb4-82c0-9582cabe364f REACT_APP_ECCKEY_ENV=stage,prod diff --git a/.env.local b/.env.local index 94c1a10..71f52ab 100644 --- a/.env.local +++ b/.env.local @@ -7,11 +7,12 @@ REACT_APP_MAGNA_GROUP_ID=68273225-9da4-4fa7-aea5-38e16ec471fe REACT_APP_OTA_SERVICE_URL=http://localhost/ota_update REACT_APP_SECURITY_DLL_URL=https://assets.fiskerdps.com/cloud-supplier/fisker_security_32.dll REACT_APP_SECURITY_DLL_64_URL=https://assets.fiskerdps.com/cloud-supplier/fisker_security_64.dll -REACT_APP_SUPERSET_URL=https://dev-superset-new.cloud.fiskerinc.com +REACT_APP_SUPERSET_URL=https://dev-superset.cloud.fiskerinc.com REACT_APP_ROLE_CREATE=efcc3025-e2d8-4212-8227-805c7be39d2c REACT_APP_ROLE_READ_ONLY=a729bbd4-2038-4649-9127-16782bb1e701 REACT_APP_ROLE_DELETE=8f78dce7-f5f9-4033-a10c-c9c7408bfcfe REACT_APP_ROLE_GENERATE_CERTIFICATE=746f34b0-9ba0-4b5d-8d84-0256a9c8e390 REACT_APP_ROLE_MANUFACTURE=3412e11a-a2d1-4355-be3e-ef9aa5065b69 REACT_APP_ROLE_SUPPLIER_APPROVER=a6c9805e-80b2-42b2-bfbb-9df52e5504d8 +REACT_APP_ROLE_MANIFEST_MIGRATION=42798c8a-9fa7-4fb4-82c0-9582cabe364f REACT_APP_ECCKEY_ENV=dev,stage,prod diff --git a/.env.prd b/.env.prd index 11f4e91..4174e96 100644 --- a/.env.prd +++ b/.env.prd @@ -14,4 +14,5 @@ REACT_APP_ROLE_DELETE=8f78dce7-f5f9-4033-a10c-c9c7408bfcfe REACT_APP_ROLE_GENERATE_CERTIFICATE=746f34b0-9ba0-4b5d-8d84-0256a9c8e390 REACT_APP_ROLE_MANUFACTURE=3412e11a-a2d1-4355-be3e-ef9aa5065b69 REACT_APP_ROLE_SUPPLIER_APPROVER=a6c9805e-80b2-42b2-bfbb-9df52e5504d8 +REACT_APP_ROLE_MANIFEST_MIGRATION=42798c8a-9fa7-4fb4-82c0-9582cabe364f REACT_APP_ECCKEY_ENV=stage diff --git a/.env.stg b/.env.stg index 41c4529..8bb96c4 100644 --- a/.env.stg +++ b/.env.stg @@ -14,4 +14,5 @@ REACT_APP_ROLE_DELETE=8f78dce7-f5f9-4033-a10c-c9c7408bfcfe REACT_APP_ROLE_GENERATE_CERTIFICATE=746f34b0-9ba0-4b5d-8d84-0256a9c8e390 REACT_APP_ROLE_MANUFACTURE=3412e11a-a2d1-4355-be3e-ef9aa5065b69 REACT_APP_ROLE_SUPPLIER_APPROVER=a6c9805e-80b2-42b2-bfbb-9df52e5504d8 +REACT_APP_ROLE_MANIFEST_MIGRATION=42798c8a-9fa7-4fb4-82c0-9582cabe364f REACT_APP_ECCKEY_ENV=prod diff --git a/.env.template b/.env.template index 3ddd694..1cdf893 100644 --- a/.env.template +++ b/.env.template @@ -14,4 +14,5 @@ REACT_APP_ROLE_DELETE=8f78dce7-f5f9-4033-a10c-c9c7408bfcfe REACT_APP_ROLE_GENERATE_CERTIFICATE=746f34b0-9ba0-4b5d-8d84-0256a9c8e390 REACT_APP_ROLE_MANUFACTURE=3412e11a-a2d1-4355-be3e-ef9aa5065b69 REACT_APP_ROLE_SUPPLIER_APPROVER=a6c9805e-80b2-42b2-bfbb-9df52e5504d8 +REACT_APP_ROLE_MANIFEST_MIGRATION=42798c8a-9fa7-4fb4-82c0-9582cabe364f REACT_APP_ECCKEY_ENV=dev,stage,prod diff --git a/src/components/App/App.test.js b/src/components/App/App.test.js index df56ca3..f665b6e 100644 --- a/src/components/App/App.test.js +++ b/src/components/App/App.test.js @@ -51,6 +51,7 @@ describe("App", () => { beforeAll(() => { global.URL.createObjectURL = jest.fn(); addSnapshotSerializer(expect); + jest.setTimeout(10000); }, 60000); afterEach(() => { diff --git a/src/components/App/__snapshots__/App.test.js.snap b/src/components/App/__snapshots__/App.test.js.snap index b3d9ca4..909fc68 100644 --- a/src/components/App/__snapshots__/App.test.js.snap +++ b/src/components/App/__snapshots__/App.test.js.snap @@ -271,11 +271,14 @@ exports[`App Route / authenticated 1`] = `
  • -
  • - Invalid Dashboard + Vehicle Map
  • @@ -956,11 +956,14 @@ exports[`App Route /dashboards/0 authenticated 1`] = `
  • -
  • - - Invalid Dashboard - +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    + @@ -1476,11 +1595,14 @@ exports[`App Route /home authenticated 1`] = `
  • -
  • -
    -
    -
    +
  • -
    -
    -
    +
  • -
    -
    -
    +
  • -
    -
    -
    +
  • -
    -
    -
    +
  • -
    -
    -
    +
  • -
    -
    -
    +
  • -
    -
    -
    +
  • -
  • -
    -
    -
    +
  • -
    -
    -
    +
  • -
    -
    -
    +
  • { useEffect(() => { setVIN(vin); + return () => { + setVIN(null) + } // eslint-disable-next-line react-hooks/exhaustive-deps }, [vin]); @@ -45,7 +48,7 @@ const Main = ({ vin }) => { }; const CANSignals = (props) => ( - +
    ); diff --git a/src/components/Cars/Status/DigitalTwinTab.jsx b/src/components/Cars/Status/DigitalTwinTab.jsx index 40673d3..b7254d6 100644 --- a/src/components/Cars/Status/DigitalTwinTab.jsx +++ b/src/components/Cars/Status/DigitalTwinTab.jsx @@ -10,6 +10,7 @@ import { VehicleProvider } from "../../Contexts/VehicleContext"; import DigitalTwin from "../../DigitalTwin"; +import VehiclePathsMap from "../../VehiclePathsMap"; import useStyles from "../../useStyles"; const REQUEST_INTERVAL = 10000; @@ -57,7 +58,10 @@ const Main = (props) => {
    ICC Connected: {carState?.online_hmi.toString()}
    - + +
    + +
    )} diff --git a/src/components/Cars/Status/__snapshots__/CarUpdatesTab.test.jsx.snap b/src/components/Cars/Status/__snapshots__/CarUpdatesTab.test.jsx.snap index 7dbc894..3b464e5 100644 --- a/src/components/Cars/Status/__snapshots__/CarUpdatesTab.test.jsx.snap +++ b/src/components/Cars/Status/__snapshots__/CarUpdatesTab.test.jsx.snap @@ -195,7 +195,7 @@ exports[`CarUpdatesTab Render 1`] = ` > No Car Updates diff --git a/src/components/Cars/Status/__snapshots__/DigitalTwinTab.test.jsx.snap b/src/components/Cars/Status/__snapshots__/DigitalTwinTab.test.jsx.snap index d487b3c..196e03f 100644 --- a/src/components/Cars/Status/__snapshots__/DigitalTwinTab.test.jsx.snap +++ b/src/components/Cars/Status/__snapshots__/DigitalTwinTab.test.jsx.snap @@ -220,6 +220,134 @@ exports[`DigitalTwinTab Render 1`] = `

    +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    diff --git a/src/components/Cars/Status/__snapshots__/ECUsTab.test.jsx.snap b/src/components/Cars/Status/__snapshots__/ECUsTab.test.jsx.snap index 89f219b..8ec2646 100644 --- a/src/components/Cars/Status/__snapshots__/ECUsTab.test.jsx.snap +++ b/src/components/Cars/Status/__snapshots__/ECUsTab.test.jsx.snap @@ -273,49 +273,94 @@ exports[`ECUsTab Render 1`] = ` class="MuiTableRow-root" > - ECUA + + ECUA + - SWVERSION + + SWVERSION + - HWVERSION + + HWVERSION + - VENDOR + + VENDOR + - SUPPLIER_SW_VERSION + + SUPPLIER_SW_VERSION + - SERIAL_NUMBER + + SERIAL_NUMBER + - BOOT_LOADER + + BOOT_LOADER + - CONFIG + + CONFIG + - FINGERPRINT + + FINGERPRINT + - ECUB + + ECUB + - SWVERSION + + SWVERSION + - HWVERSION + + HWVERSION + - - - - - CONFIG + + class="MuiTableCell-root MuiTableCell-body makeStyles-limitWidthTableCell-0 MuiTableCell-alignCenter" + > + + + + + + + + + + + CONFIG + + + + + diff --git a/src/components/Contexts/ManifestsContext.jsx b/src/components/Contexts/ManifestsContext.jsx index 1acc9b7..8695122 100644 --- a/src/components/Contexts/ManifestsContext.jsx +++ b/src/components/Contexts/ManifestsContext.jsx @@ -80,12 +80,28 @@ export const ManifestsProvider = ({ children }) => { return result; }; + const migrateManifest = async (package_id, token) => { + let result; + + try{ + setBusy(true) + result = await api.migrateManifest(package_id, token) + if(result.error) + throw new Error(`failed to migrate manifest. ${result.message}`); + } finally { + setBusy(false) + } + return result + + } + return ( { setBusy(true); const result = await api.getLocations(token); if (result.error) - throw new Error(`Get locations error. ${result.message}`); + throw new Error(`Get locations vehicle paths error. ${result.message}`); + return result; + } finally { + setBusy(false); + } + }; + + const getLocationsVehiclePaths = async (token, vinsParam) => { + try { + setBusy(true); + const result = await api.getLocationsVehiclePaths(token, vinsParam); + if (result.error) + throw new Error(`Get locations vehicle paths error. ${result.message}`); return result; } finally { setBusy(false); @@ -263,6 +275,7 @@ export const VehicleProvider = ({ children }) => { getCANSignals, getECUs, getLocations, + getLocationsVehiclePaths, getModels, getState, getYears, diff --git a/src/components/Contexts/__mocks__/VehicleContext.jsx b/src/components/Contexts/__mocks__/VehicleContext.jsx index c545745..3c76ba1 100644 --- a/src/components/Contexts/__mocks__/VehicleContext.jsx +++ b/src/components/Contexts/__mocks__/VehicleContext.jsx @@ -98,15 +98,14 @@ export const useVehicleContext = () => ({ vehicles, years, addVehicle: jest.fn(), - getConnections: jest.fn((vins, _token) => { - const result = {}; - - vins.forEach((vin) => { - result[vin] = true; - }); - - return result; - }), + getConnections: jest + .fn().mockImplementation((vins, _token) => { + const result = {}; + vins.forEach((vin) => { + result[vin] = true; + }); + return Promise.resolve(result); + }), getECUs: jest.fn(() => { return { data: [ @@ -134,6 +133,13 @@ export const useVehicleContext = () => ({ .mockResolvedValue([ { altitude: 5, longitude: 10, latitude: 15, vin: "TESTVIN123" }, ]), + getLocationsVehiclePaths: jest + .fn() + .mockResolvedValue({ + // tests only pass without mocking the data here + // '3FAFP13P31R199430': [[16.891136999999986, 26.832352999999955], [56.891136999999986, 66.832352999999955], [26.891136999999986, 36.832352999999955]], + // '3FAFP13P71R199060': [[36.891136999999986, 46.832352999999955], [76.891136999999986, 16.832352999999955]], + }), getModels: jest.fn(() => { models = ["Ocean", "PEAR"]; }), diff --git a/src/components/Controls/CarECUsTable/index.jsx b/src/components/Controls/CarECUsTable/index.jsx index 1292e2c..af32ffc 100644 --- a/src/components/Controls/CarECUsTable/index.jsx +++ b/src/components/Controls/CarECUsTable/index.jsx @@ -4,7 +4,8 @@ import { TableCell, TableFooter, TablePagination, - TableRow + TableRow, + Tooltip } from "@material-ui/core"; import clsx from "clsx"; import React, { useEffect, useState } from "react"; @@ -136,7 +137,13 @@ const CarECUsTable = ({ vin, token, classes }) => { {tableColumns.map((column, j) => { const key = `${row.ecu + i}${column.id}` if (column.id === "updated_at") return ({LocalDateTimeString(row.updated)}); - return ({row[column.id]}); + return ( + + + {row[column.id]} + + + ); })} ))} diff --git a/src/components/Controls/CarUpdateStatusProgress/Statuses.js b/src/components/Controls/CarUpdateStatusProgress/Statuses.js index b01c659..fc9be48 100644 --- a/src/components/Controls/CarUpdateStatusProgress/Statuses.js +++ b/src/components/Controls/CarUpdateStatusProgress/Statuses.js @@ -1,5 +1,6 @@ const Statuses = { Pending: "pending", + Sent: "sent", ManifestReceived: "manifest_received", ManifestAccepted: "manifest_accepted", ManifestRejected: "manifest_rejected", diff --git a/src/components/Controls/CarUpdateStatusProgress/index.jsx b/src/components/Controls/CarUpdateStatusProgress/index.jsx index 103eeaf..2131b30 100644 --- a/src/components/Controls/CarUpdateStatusProgress/index.jsx +++ b/src/components/Controls/CarUpdateStatusProgress/index.jsx @@ -1,7 +1,7 @@ -import React, { useState, useEffect } from "react"; -import { CheckCircle, RadioButtonUnchecked, Error } from "@material-ui/icons"; import Typography from "@material-ui/core/Typography"; +import { CheckCircle, Error, RadioButtonUnchecked } from "@material-ui/icons"; import clsx from "clsx"; +import React, { useEffect, useState } from "react"; import CircularProgress from "../CircularProgress"; import s from "./Statuses"; @@ -13,7 +13,7 @@ const CompleteStatus = 100; const PHASES = [ { label: "Pending", - events: [s.Pending], + events: [s.Pending, s.Sent], progress: () => CompleteStatus, }, { diff --git a/src/components/Controls/CarUpdatesTable/index.jsx b/src/components/Controls/CarUpdatesTable/index.jsx index c8eef07..d99fe94 100644 --- a/src/components/Controls/CarUpdatesTable/index.jsx +++ b/src/components/Controls/CarUpdatesTable/index.jsx @@ -213,11 +213,11 @@ const MainForm = ({ vin, token }) => { {totalCarUpdates === 0 ? ( - No Car Updates + No Car Updates ) : ( { id: "status_byte", label: "Status Code", }, + { + id: "ErrorText", + label: "Error Text", + no_sort : true, + }, { id: "epoch_usec", label: "Date", @@ -196,6 +201,7 @@ const MainForm = ({ vin }) => { {dtc.ecu_name} {dtc.trouble_code} {dtc.status_byte} + {dtc.trouble_code_information?.ErrorText?.Text} {formatDate(dtc.epoch_usec)} ))} diff --git a/src/components/Layouts/SideMenu.jsx b/src/components/Layouts/SideMenu.jsx index cf46991..f0d24cf 100644 --- a/src/components/Layouts/SideMenu.jsx +++ b/src/components/Layouts/SideMenu.jsx @@ -9,11 +9,11 @@ import HomeIcon from "@material-ui/icons/Home"; import SettingsInputCompositeIcon from "@material-ui/icons/SettingsInputComposite"; import { default as React, useEffect, useState } from "react"; -import { getCustomDashboardSubmenu } from "../../services/customDashboards"; import { hasRole, Permissions } from "../../utils/roles"; import { useUserContext } from "../Contexts/UserContext"; -import SupersetDashboardList from "../SupersetDashboardList/SupersetDashboardList"; import { ExpandableSideMenuItem, MenuItem } from "./MenuItem"; +import { getCustomDashboardSubmenu } from "../../services/customDashboards" + const menuData = [ { label: "Home", @@ -47,10 +47,9 @@ const menuData = [ }, { label: "Datascope", - to: null, + url: `${process.env.REACT_APP_SUPERSET_URL}/login`, icon: , rolesPerProvider: Permissions.FiskerMagnaRead, - component: SupersetDashboardList, submenus: getCustomDashboardSubmenu(Permissions.FiskerMagnaRead), }, { diff --git a/src/components/Layouts/__snapshots__/SideMenu.test.jsx.snap b/src/components/Layouts/__snapshots__/SideMenu.test.jsx.snap index f9e6128..44be0aa 100644 --- a/src/components/Layouts/__snapshots__/SideMenu.test.jsx.snap +++ b/src/components/Layouts/__snapshots__/SideMenu.test.jsx.snap @@ -190,11 +190,14 @@ exports[`SideMenu Authenticated 1`] = `
  • -
  • -
    -
    -
    +
  • { const [manifest, setManifest] = useState(null); const [redirect, setRedirect] = useState(null); - const { getManifest, busy, updateManifest } = useManifestsContext(); + const { getManifest, busy, migrateManifest, updateManifest } = useManifestsContext(); const { token: { idToken: { jwtToken: token }, }, + groups, + providers, } = useUserContext(); const { setMessage, setTitle, setSitePath } = useStatusContext(); @@ -86,6 +90,17 @@ const MainForm = () => { } } + const manifestMigrate = async (e) => { + e.preventDefault(); + try{ + const result = await migrateManifest(manifest_id, token) + if (!result || result.error) return; + setMessage(`Manifest Migrated ${manifest_id}`) + } catch(e) { + setMessage(`Failed to update manifest ${manifest_id}`) + } + } + useEffect(() => { (async () => { try { @@ -179,6 +194,24 @@ const MainForm = () => { > {busy ? "Updating..." : "Update"} + + + @@ -191,4 +224,4 @@ const ManifestUpdate = () => ( ); -export default ManifestUpdate; \ No newline at end of file +export default ManifestUpdate; diff --git a/src/components/Table/HeaderSortable/index.jsx b/src/components/Table/HeaderSortable/index.jsx index 2b297e8..c0b0b4a 100644 --- a/src/components/Table/HeaderSortable/index.jsx +++ b/src/components/Table/HeaderSortable/index.jsx @@ -35,6 +35,9 @@ const HeaderSortable = (props) => { value === "desc" ? "sorted descending" : "sorted ascending"; const ColumnLabel = (column) => { + if (column.no_sort) { + return column.label + } if (column.id) { return ( { {vin} {" "} - - + +
    diff --git a/src/components/VehiclePathsMap/index.jsx b/src/components/VehiclePathsMap/index.jsx new file mode 100644 index 0000000..97f7961 --- /dev/null +++ b/src/components/VehiclePathsMap/index.jsx @@ -0,0 +1,253 @@ +import { Button } from "@material-ui/core"; +import L from "leaflet"; +import React, { useEffect, useState } from "react"; +import { MapContainer, Marker, Polyline, Popup, TileLayer, useMap } from "react-leaflet"; +import useStyles from "../useStyles"; + +import GrayMarkerIcon from "../../assets/gray-marker.png"; +import GreenMarkerIcon from "../../assets/green-marker.png"; +import { logger } from "../../services/monitoring"; +import { ValidateLocationVehiclePathsData } from "../../utils/locations"; +import { useUserContext } from "../Contexts/UserContext"; +import { useVehicleContext, VehicleProvider } from "../Contexts/VehicleContext"; +import { VehiclePopUp } from "../VehicleMap/popup"; + +const ComponentVehiclePathsMap = (props) => { + const classes = useStyles(); + const { + token: { + idToken: { jwtToken: token }, + }, + } = useUserContext(); + const { getConnections, getLocationsVehiclePaths, getState } = useVehicleContext(); + + const REQUEST_INTERVAL = 10000; + + const [center, setCenter] = useState([0, 0]); + const [zoom, setZoom] = useState(2); + const [markers, setMarkers] = useState([]); + const [connections, setConnections] = useState({}); + + useEffect(() => { + if (!token) return; + retrieveAndStoreLocations(token).then((points) => { + centerAroundMarkers(points); + }); + const id = setInterval(function () { + retrieveAndStoreLocations(token); + }, REQUEST_INTERVAL); + return () => { + clearInterval(id); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [token]); + + const retrieveAndStoreLocations = (accessToken) => { + let vinsToShowOnMap = [...props.vinsToShowOnMapColors.keys()]; + let vinsParam = "" + for (let vinToShowOnMap of vinsToShowOnMap) { + vinsParam += "vins=" + vinsParam += vinToShowOnMap + vinsParam += "&" + } + vinsParam += "lookback_hours=" + vinsParam += props.lookbackHours + + return getLocationsVehiclePaths(accessToken, vinsParam) + .then((result) => { + let resultArray = Object.entries(result) + const points = [] + + // validate each location + for (let vinLocations of resultArray) { + let path = [] + path[0] = vinLocations[0] + path[1] = [] + for (let location of vinLocations[1]) { + if (ValidateLocationVehiclePathsData(location) !== false) { + path[1].push(location); + } + } + points.push(path) + } + + setMarkers(points); + return points; + }) + .catch((error) => logger.warn(error.stack)); + }; + + const centerAroundMarkers = (points) => { + // default center + let center = [37.0902, -95.7129] + + // center is the very first geographical point + if (points && points[0] && points[0][1] && points[0][1][0]) { + center = points[0][1][0] + } + + setCenter(center); + setZoom(4.5); + }; + + useEffect(() => { + if (!token) return; + + const vins = [] + + for (let vinLocations of markers) { + vins.push(vinLocations[0]) + } + + if (vins.length === 0) return; + + getConnections(vins, token).then((conns) => { + setConnections(conns); + }); + // eslint-disable-next-line + }, [markers, token]); + + const [selectedVIN, setSelectedVIN] = useState(null); + const [carState, setCarState] = useState(null); + + useEffect(() => { + if (selectedVIN != null) { + retrieveAndStoreCarState(selectedVIN); + const id = setInterval(function () { + retrieveAndStoreCarState(selectedVIN); + }, REQUEST_INTERVAL); + return () => { + clearInterval(id); + }; + } + // eslint-disable-next-line + }, [selectedVIN]); + + const selectCar = (e, vin) => { + e.preventDefault(); + setSelectedVIN(vin); + }; + + const retrieveAndStoreCarState = (vin) => { + getState(token, vin).then((results) => { + setCarState({ ...results.data, vin: vin }); + }); + }; + + const handleClose = () => { + setSelectedVIN(null); + setCarState(null); + }; + + const isOnline = (vin) => { + return connections[vin]; + }; + + const getZIndex = (vin) => { + if (isOnline(vin)) return 1000; + return 0; + }; + + function getCarIcon(vin) { + let icon = GrayMarkerIcon; + + if (isOnline(vin)) { + icon = GreenMarkerIcon; + } + + return new L.Icon({ + iconUrl: icon, + iconAnchor: [24, 42], + }); + } + + return ( + + + + {markers && markers.map((vinLocations) => ( +
    + + {vinLocations[1][0] && + { + setCenter(vinLocations[1][0]); + setZoom(16); + }, + }} + > + +
    +

    + {vinLocations[0]} +

    + +
    +
    +
    + } +
    + ))} + + {carState ? ( + + ) : null} +
    + ); +}; + +const CenterFocus = ({ center, zoom }) => { + const map = useMap(); + + useEffect(() => { + if (center[0] === 0 && center[1] === 0) { + map.flyTo([0, 0], 2, { duration: 1.5 }); + } else { + map.flyTo(center, zoom, { duration: 1.5 }); + } + }, [center, zoom, map]); + + return null; +}; + +const VehiclePathsMap = (props) => ( + + + +); + +export default VehiclePathsMap; diff --git a/src/components/useStyles.jsx b/src/components/useStyles.jsx index 1931093..edb09bc 100644 --- a/src/components/useStyles.jsx +++ b/src/components/useStyles.jsx @@ -288,7 +288,12 @@ const useStyles = makeStyles((theme) => ({ tableHeader: { textDecorationStyle: "solid", fontWeight:500, - } + }, + limitWidthTableCell: { + maxWidth: "200px", + whiteSpace: "normal", + wordWrap: "break-word", + }, })); export default useStyles; diff --git a/src/services/DTCTimelineAPI.js b/src/services/DTCTimelineAPI.js index c7c2a7e..3fce6c6 100644 --- a/src/services/DTCTimelineAPI.js +++ b/src/services/DTCTimelineAPI.js @@ -13,6 +13,7 @@ const DTCTimelineAPI = { ecu, start_time: startDate, end_time: endDate, + decode:true, ...search, }; const url = addQueryParams(`${API_ENDPOINT}/dtcs/${vin}`, queryParams); diff --git a/src/services/__mocks__/vehiclesAPI.js b/src/services/__mocks__/vehiclesAPI.js index 4b2844e..369bda6 100644 --- a/src/services/__mocks__/vehiclesAPI.js +++ b/src/services/__mocks__/vehiclesAPI.js @@ -55,13 +55,15 @@ const ecusData = [ }, ]; -const signals = {data:[ - { - timestamp: "2021-07-14T20:09:40.98187Z", - name: "signal", - value: 123 - }, -]}; +const signals = { + data: [ + { + timestamp: "2021-07-14T20:09:40.98187Z", + name: "signal", + value: 123 + }, + ], +}; const trexLogs = { RealOffset: 0, @@ -127,6 +129,12 @@ const vehiclesAPI = { .mockResolvedValue([ { altitude: 5, longitude: 10, latitude: 15, vin: "TESTVIN123" }, ]), + getLocationsVehiclePaths: async () => { + return { + '3FAFP13P31R199430': [[16.891136999999986, 26.832352999999955], [56.891136999999986, 66.832352999999955], [26.891136999999986, 36.832352999999955]], + '3FAFP13P71R199060': [[36.891136999999986, 46.832352999999955], [76.891136999999986, 16.832352999999955]], + }; + }, getVehicle: async (vin) => { const index = data.findIndex(element => element.vin === vin); return data[index]; @@ -134,7 +142,7 @@ const vehiclesAPI = { getVehicles: async () => { return { data }; }, - getFleets: async (vin) => {return { data: ["fleet1", "fleet2"]}}, + getFleets: async (vin) => { return { data: ["fleet1", "fleet2"] } }, getYears: async () => { return { data: [2021, 2022], @@ -152,29 +160,29 @@ const vehiclesAPI = { return vehicle; }, getCANSignals: async (vin, vehicle) => { - return signals; + return signals; }, getTRexLogs: async (vin, date, offset, count, direction, token) => { return trexLogs; }, getVersionLog: async (vin) => ({ - "data": [ - { - "id": 1, - "vin": "${vin}", - "version_source": "TREX", - "version": "0.9.56", - "created_at": "2023-01-13T02:11:33.327214Z" - }, - { - "id": 2, - "vin": "${vin}", - "version_source": "DBC", - "version": "386c18977a1be3cda60c953e5902c680dbe82b89523f2527e80cd9db863db991", - "created_at": "2023-01-13T02:11:33.330932Z" - } - ], - "total": 2 + "data": [ + { + "id": 1, + "vin": "${vin}", + "version_source": "TREX", + "version": "0.9.56", + "created_at": "2023-01-13T02:11:33.327214Z" + }, + { + "id": 2, + "vin": "${vin}", + "version_source": "DBC", + "version": "386c18977a1be3cda60c953e5902c680dbe82b89523f2527e80cd9db863db991", + "created_at": "2023-01-13T02:11:33.330932Z" + } + ], + "total": 2 }) }; diff --git a/src/services/customDashboards.js b/src/services/customDashboards.js index 8f9fe65..80dae84 100644 --- a/src/services/customDashboards.js +++ b/src/services/customDashboards.js @@ -1,8 +1,30 @@ +import VehiclePathsMap from "../components/VehiclePathsMap"; + const INVALID_DASHBOARD = { label: "Invalid Dashboard", error: "Invalid Dashboard" } +const vinsToShowOnMapColors = new Map([ + ['3FAFP13P71R199267', 'red'], + ['3FAFP13P71R199270', 'orange'], + ['3FAFP13P71R199222', 'blue'], + ['3FAFP13P61R199339', 'yellow'], + ['3FAFP13P71R199057', 'turquoise'], + ['3FAFP13P61R199387', 'lime'], + ['3FAFP13P71R199334', 'purple'], + ['3FAFP13P71R199284', 'green'], + ['3FAFP13P71R199303', 'sienna'], + ['3FAFP13P31R199430', 'navy'], + ['3FAFP13P81R199083', 'cadetblue'], + ['3FAFP13P71R199060', 'coral'], + ['3FAFP13P71R199317', 'darkkhaki'], + ['3FAFP13P71R199320', 'fuchsia'], + ['3FAFP13P61R199390', 'indigo'], + ['3FAFP13P61R199373', 'cyan'], +]) +const lookbackHours = 24 + export const CustomDashboardList = [ /* { @@ -14,6 +36,11 @@ export const CustomDashboardList = [ component: } */ + + { + label: "Vehicle Map", + component: + } ]; export const getCustomDashboard = (index) => { diff --git a/src/services/manifestsAPI.js b/src/services/manifestsAPI.js index 9091d97..8fbe7a8 100644 --- a/src/services/manifestsAPI.js +++ b/src/services/manifestsAPI.js @@ -78,6 +78,17 @@ const manifestsAPI = { }) .then(fetchRespHandler) .catch(errorHandler), + + migrateManifest: async (manifest_id, token) => + fetch(`${API_ENDPOINT}/manifestmigrate/${manifest_id}`,{ + method: "POST", + headers: Object.assign( + { "Content-Type": "application/json" }, + getAuthHeaderOptions(token) + ), + }) + .then(fetchRespHandler) + .catch(errorHandler), }; export default manifestsAPI; diff --git a/src/services/vehiclesAPI.js b/src/services/vehiclesAPI.js index 80345e4..8ca9e1f 100644 --- a/src/services/vehiclesAPI.js +++ b/src/services/vehiclesAPI.js @@ -76,6 +76,17 @@ const vehiclesAPI = { .then(fetchRespHandler) .catch(errorHandler), + getLocationsVehiclePaths: async (token, vinsParam) => + fetch(`${API_ENDPOINT}/vehicle_paths?${vinsParam}`, { + method: "GET", + headers: Object.assign( + { "Content-Type": "application/json" }, + getAuthHeaderOptions(token) + ), + }) + .then(fetchRespHandler) + .catch(errorHandler), + getState: async (token, vin) => fetch(`${API_ENDPOINT}/carstate?vin=${vin}`, { method: "GET", @@ -185,7 +196,7 @@ const vehiclesAPI = { .then(fetchRespHandler) .catch(errorHandler), - getVersionLog: async ({vin, ...search}, token) => { + getVersionLog: async ({ vin, ...search }, token) => { const u = addQueryParams(`${API_ENDPOINT}/vehicle/${vin}/version/logs`, search); return fetch(u, { method: "GET", diff --git a/src/utils/locations.js b/src/utils/locations.js index f4fdaba..542418c 100644 --- a/src/utils/locations.js +++ b/src/utils/locations.js @@ -10,6 +10,15 @@ export const ValidateLocationData = (location) => { return true; } +export const ValidateLocationVehiclePathsData = (location) => { + if (location.length < 2) { + return false; + } + + // location is an array of length 2 as { float64, float64} + return !(Math.abs(location[0]) > 90 || Math.abs(location[1]) > 180); +} + export const ValidateLocationByParam = (parameter, value) => { if (invalidLocation === value) return false; switch (parameter) { diff --git a/src/utils/roles.js b/src/utils/roles.js index 68a4783..4875da4 100644 --- a/src/utils/roles.js +++ b/src/utils/roles.js @@ -8,6 +8,7 @@ export const Roles = { APPROVESUPPLIERS: process.env.REACT_APP_ROLE_SUPPLIER_APPROVER, MANUFACTURE: process.env.REACT_APP_ROLE_MANUFACTURE, MAGNAGROUP: process.env.REACT_APP_MAGNA_GROUP_ID, + MANIFEST_MIGRATION: process.env.REACT_APP_ROLE_MANIFEST_MIGRATION }; export const Providers = { @@ -94,4 +95,7 @@ export const Permissions = { [Providers.FISKER_QA]: [Roles.MANUFACTURE], [Providers.MAGNA]: [Roles.MAGNAGROUP], }, + ManifestMigration: { + [Providers.FISKER]: [Roles.MANIFEST_MIGRATION] + } };