Merge branch 'release/0.9.0'

This commit is contained in:
jwu-fisker
2023-05-04 16:30:00 -07:00
36 changed files with 1378 additions and 613 deletions

View File

@@ -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_GENERATE_CERTIFICATE=9af2d8c0-c26d-4d6d-bbd1-ac53cbd37ebc
REACT_APP_ROLE_MANUFACTURE=3412e11a-a2d1-4355-be3e-ef9aa5065b69 REACT_APP_ROLE_MANUFACTURE=3412e11a-a2d1-4355-be3e-ef9aa5065b69
REACT_APP_ROLE_SUPPLIER_APPROVER=a6c9805e-80b2-42b2-bfbb-9df52e5504d8 REACT_APP_ROLE_SUPPLIER_APPROVER=a6c9805e-80b2-42b2-bfbb-9df52e5504d8
REACT_APP_ROLE_MANIFEST_MIGRATION=42798c8a-9fa7-4fb4-82c0-9582cabe364f
REACT_APP_ECCKEY_ENV= REACT_APP_ECCKEY_ENV=

View File

@@ -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_GENERATE_CERTIFICATE=9af2d8c0-c26d-4d6d-bbd1-ac53cbd37ebc
REACT_APP_ROLE_MANUFACTURE=3412e11a-a2d1-4355-be3e-ef9aa5065b69 REACT_APP_ROLE_MANUFACTURE=3412e11a-a2d1-4355-be3e-ef9aa5065b69
REACT_APP_ROLE_SUPPLIER_APPROVER=a6c9805e-80b2-42b2-bfbb-9df52e5504d8 REACT_APP_ROLE_SUPPLIER_APPROVER=a6c9805e-80b2-42b2-bfbb-9df52e5504d8
REACT_APP_ROLE_MANIFEST_MIGRATION=42798c8a-9fa7-4fb4-82c0-9582cabe364f
REACT_APP_ECCKEY_ENV= REACT_APP_ECCKEY_ENV=

View File

@@ -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_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_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_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_CREATE=efcc3025-e2d8-4212-8227-805c7be39d2c
REACT_APP_ROLE_READ_ONLY=a729bbd4-2038-4649-9127-16782bb1e701 REACT_APP_ROLE_READ_ONLY=a729bbd4-2038-4649-9127-16782bb1e701
REACT_APP_ROLE_DELETE=8f78dce7-f5f9-4033-a10c-c9c7408bfcfe REACT_APP_ROLE_DELETE=8f78dce7-f5f9-4033-a10c-c9c7408bfcfe
REACT_APP_ROLE_GENERATE_CERTIFICATE=746f34b0-9ba0-4b5d-8d84-0256a9c8e390 REACT_APP_ROLE_GENERATE_CERTIFICATE=746f34b0-9ba0-4b5d-8d84-0256a9c8e390
REACT_APP_ROLE_MANUFACTURE=3412e11a-a2d1-4355-be3e-ef9aa5065b69 REACT_APP_ROLE_MANUFACTURE=3412e11a-a2d1-4355-be3e-ef9aa5065b69
REACT_APP_ROLE_SUPPLIER_APPROVER=a6c9805e-80b2-42b2-bfbb-9df52e5504d8 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 REACT_APP_ECCKEY_ENV=stage,prod

View File

@@ -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_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_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_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_CREATE=efcc3025-e2d8-4212-8227-805c7be39d2c
REACT_APP_ROLE_READ_ONLY=a729bbd4-2038-4649-9127-16782bb1e701 REACT_APP_ROLE_READ_ONLY=a729bbd4-2038-4649-9127-16782bb1e701
REACT_APP_ROLE_DELETE=8f78dce7-f5f9-4033-a10c-c9c7408bfcfe REACT_APP_ROLE_DELETE=8f78dce7-f5f9-4033-a10c-c9c7408bfcfe
REACT_APP_ROLE_GENERATE_CERTIFICATE=746f34b0-9ba0-4b5d-8d84-0256a9c8e390 REACT_APP_ROLE_GENERATE_CERTIFICATE=746f34b0-9ba0-4b5d-8d84-0256a9c8e390
REACT_APP_ROLE_MANUFACTURE=3412e11a-a2d1-4355-be3e-ef9aa5065b69 REACT_APP_ROLE_MANUFACTURE=3412e11a-a2d1-4355-be3e-ef9aa5065b69
REACT_APP_ROLE_SUPPLIER_APPROVER=a6c9805e-80b2-42b2-bfbb-9df52e5504d8 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 REACT_APP_ECCKEY_ENV=dev,stage,prod

View File

@@ -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_GENERATE_CERTIFICATE=746f34b0-9ba0-4b5d-8d84-0256a9c8e390
REACT_APP_ROLE_MANUFACTURE=3412e11a-a2d1-4355-be3e-ef9aa5065b69 REACT_APP_ROLE_MANUFACTURE=3412e11a-a2d1-4355-be3e-ef9aa5065b69
REACT_APP_ROLE_SUPPLIER_APPROVER=a6c9805e-80b2-42b2-bfbb-9df52e5504d8 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 REACT_APP_ECCKEY_ENV=stage

View File

@@ -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_GENERATE_CERTIFICATE=746f34b0-9ba0-4b5d-8d84-0256a9c8e390
REACT_APP_ROLE_MANUFACTURE=3412e11a-a2d1-4355-be3e-ef9aa5065b69 REACT_APP_ROLE_MANUFACTURE=3412e11a-a2d1-4355-be3e-ef9aa5065b69
REACT_APP_ROLE_SUPPLIER_APPROVER=a6c9805e-80b2-42b2-bfbb-9df52e5504d8 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 REACT_APP_ECCKEY_ENV=prod

View File

@@ -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_GENERATE_CERTIFICATE=746f34b0-9ba0-4b5d-8d84-0256a9c8e390
REACT_APP_ROLE_MANUFACTURE=3412e11a-a2d1-4355-be3e-ef9aa5065b69 REACT_APP_ROLE_MANUFACTURE=3412e11a-a2d1-4355-be3e-ef9aa5065b69
REACT_APP_ROLE_SUPPLIER_APPROVER=a6c9805e-80b2-42b2-bfbb-9df52e5504d8 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 REACT_APP_ECCKEY_ENV=dev,stage,prod

View File

@@ -51,6 +51,7 @@ describe("App", () => {
beforeAll(() => { beforeAll(() => {
global.URL.createObjectURL = jest.fn(); global.URL.createObjectURL = jest.fn();
addSnapshotSerializer(expect); addSnapshotSerializer(expect);
jest.setTimeout(10000);
}, 60000); }, 60000);
afterEach(() => { afterEach(() => {

File diff suppressed because it is too large Load Diff

View File

@@ -17,6 +17,9 @@ const Main = ({ vin }) => {
useEffect(() => { useEffect(() => {
setVIN(vin); setVIN(vin);
return () => {
setVIN(null)
}
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [vin]); }, [vin]);
@@ -45,7 +48,7 @@ const Main = ({ vin }) => {
}; };
const CANSignals = (props) => ( const CANSignals = (props) => (
<CANSignalProvider {...{token:props.token}}> <CANSignalProvider {...{ token: props.token }}>
<Main {...props} /> <Main {...props} />
</CANSignalProvider> </CANSignalProvider>
); );

View File

@@ -10,6 +10,7 @@ import {
VehicleProvider VehicleProvider
} from "../../Contexts/VehicleContext"; } from "../../Contexts/VehicleContext";
import DigitalTwin from "../../DigitalTwin"; import DigitalTwin from "../../DigitalTwin";
import VehiclePathsMap from "../../VehiclePathsMap";
import useStyles from "../../useStyles"; import useStyles from "../../useStyles";
const REQUEST_INTERVAL = 10000; const REQUEST_INTERVAL = 10000;
@@ -57,7 +58,10 @@ const Main = (props) => {
<div> <div>
<b>ICC Connected</b>: {carState?.online_hmi.toString()} <b>ICC Connected</b>: {carState?.online_hmi.toString()}
</div> </div>
<DigitalTwin {...carState} /> <DigitalTwin {...carState} vin={vin} />
<div style={{ width: '100vh' }}>
<VehiclePathsMap vinsToShowOnMapColors={new Map([[vin, 'navy']])} lookbackHours={24} />
</div>
</> </>
)} )}
</div> </div>

View File

@@ -195,7 +195,7 @@ exports[`CarUpdatesTab Render 1`] = `
> >
<td <td
class="MuiTableCell-root MuiTableCell-footer MuiTableCell-alignCenter" class="MuiTableCell-root MuiTableCell-footer MuiTableCell-alignCenter"
colspan="7" colspan="8"
> >
No Car Updates No Car Updates
</td> </td>

View File

@@ -220,6 +220,134 @@ exports[`DigitalTwinTab Render 1`] = `
</p> </p>
</div> </div>
</div> </div>
<div
style="width: 100vh;"
>
<div
data-testid="mocked-vehicleprovider"
>
<div
class="leaflet-container leaflet-touch leaflet-grab leaflet-touch-drag leaflet-touch-zoom"
style="width: 100%; height: 900px; position: relative;"
tabindex="0"
>
<div
class="leaflet-pane leaflet-map-pane"
style="left: 0px; top: 0px;"
>
<div
class="leaflet-pane leaflet-tile-pane"
>
<div
class="leaflet-layer "
style="z-index: 1;"
>
<div
class="leaflet-tile-container leaflet-zoom-animated"
style="z-index: 18; left: 0px; top: 0px;"
>
<img
alt=""
class="leaflet-tile"
role="presentation"
src="https://b.tile.openstreetmap.org/5/7/12.png"
style="width: 256px; height: 256px; left: -126px; top: -114px;"
/>
</div>
</div>
</div>
<div
class="leaflet-pane leaflet-overlay-pane"
/>
<div
class="leaflet-pane leaflet-shadow-pane"
/>
<div
class="leaflet-pane leaflet-marker-pane"
/>
<div
class="leaflet-pane leaflet-tooltip-pane"
/>
<div
class="leaflet-pane leaflet-popup-pane"
/>
</div>
<div
class="leaflet-control-container"
>
<div
class="leaflet-top leaflet-left"
>
<div
class="leaflet-control-zoom leaflet-bar leaflet-control"
>
<a
aria-disabled="false"
aria-label="Zoom in"
class="leaflet-control-zoom-in"
href="#"
role="button"
title="Zoom in"
>
<span
aria-hidden="true"
>
+
</span>
</a>
<a
aria-disabled="false"
aria-label="Zoom out"
class="leaflet-control-zoom-out"
href="#"
role="button"
title="Zoom out"
>
<span
aria-hidden="true"
>
</span>
</a>
</div>
</div>
<div
class="leaflet-top leaflet-right"
/>
<div
class="leaflet-bottom leaflet-left"
/>
<div
class="leaflet-bottom leaflet-right"
>
<div
class="leaflet-control-attribution leaflet-control"
>
<a
href="https://leafletjs.com"
title="A JavaScript library for interactive maps"
>
Leaflet
</a>
<span
aria-hidden="true"
>
|
</span>
©
<a
href="http://osm.org/copyright"
>
OpenStreetMap
</a>
contributors
</div>
</div>
</div>
</div>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -273,49 +273,94 @@ exports[`ECUsTab Render 1`] = `
class="MuiTableRow-root" class="MuiTableRow-root"
> >
<td <td
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter" class="MuiTableCell-root MuiTableCell-body makeStyles-limitWidthTableCell-0 MuiTableCell-alignCenter"
> >
ECUA <span
class=""
title="ECUA"
>
ECUA
</span>
</td> </td>
<td <td
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter" class="MuiTableCell-root MuiTableCell-body makeStyles-limitWidthTableCell-0 MuiTableCell-alignCenter"
> >
SWVERSION <span
class=""
title="SWVERSION"
>
SWVERSION
</span>
</td> </td>
<td <td
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter" class="MuiTableCell-root MuiTableCell-body makeStyles-limitWidthTableCell-0 MuiTableCell-alignCenter"
> >
HWVERSION <span
class=""
title="HWVERSION"
>
HWVERSION
</span>
</td> </td>
<td <td
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter" class="MuiTableCell-root MuiTableCell-body makeStyles-limitWidthTableCell-0 MuiTableCell-alignCenter"
> >
VENDOR <span
class=""
title="VENDOR"
>
VENDOR
</span>
</td> </td>
<td <td
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter" class="MuiTableCell-root MuiTableCell-body makeStyles-limitWidthTableCell-0 MuiTableCell-alignCenter"
> >
SUPPLIER_SW_VERSION <span
class=""
title="SUPPLIER_SW_VERSION"
>
SUPPLIER_SW_VERSION
</span>
</td> </td>
<td <td
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter" class="MuiTableCell-root MuiTableCell-body makeStyles-limitWidthTableCell-0 MuiTableCell-alignCenter"
> >
SERIAL_NUMBER <span
class=""
title="SERIAL_NUMBER"
>
SERIAL_NUMBER
</span>
</td> </td>
<td <td
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter" class="MuiTableCell-root MuiTableCell-body makeStyles-limitWidthTableCell-0 MuiTableCell-alignCenter"
> >
BOOT_LOADER <span
class=""
title="BOOT_LOADER"
>
BOOT_LOADER
</span>
</td> </td>
<td <td
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter" class="MuiTableCell-root MuiTableCell-body makeStyles-limitWidthTableCell-0 MuiTableCell-alignCenter"
> >
CONFIG <span
class=""
title="CONFIG"
>
CONFIG
</span>
</td> </td>
<td <td
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter" class="MuiTableCell-root MuiTableCell-body makeStyles-limitWidthTableCell-0 MuiTableCell-alignCenter"
> >
FINGERPRINT <span
class=""
title="FINGERPRINT"
>
FINGERPRINT
</span>
</td> </td>
<td <td
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter" class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
@@ -327,40 +372,85 @@ exports[`ECUsTab Render 1`] = `
class="MuiTableRow-root" class="MuiTableRow-root"
> >
<td <td
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter" class="MuiTableCell-root MuiTableCell-body makeStyles-limitWidthTableCell-0 MuiTableCell-alignCenter"
> >
ECUB <span
class=""
title="ECUB"
>
ECUB
</span>
</td> </td>
<td <td
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter" class="MuiTableCell-root MuiTableCell-body makeStyles-limitWidthTableCell-0 MuiTableCell-alignCenter"
> >
SWVERSION <span
class=""
title="SWVERSION"
>
SWVERSION
</span>
</td> </td>
<td <td
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter" class="MuiTableCell-root MuiTableCell-body makeStyles-limitWidthTableCell-0 MuiTableCell-alignCenter"
> >
HWVERSION <span
class=""
title="HWVERSION"
>
HWVERSION
</span>
</td> </td>
<td <td
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter" class="MuiTableCell-root MuiTableCell-body makeStyles-limitWidthTableCell-0 MuiTableCell-alignCenter"
/>
<td
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
/>
<td
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
/>
<td
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
/>
<td
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
> >
CONFIG <span
class=""
title="none"
/>
</td> </td>
<td <td
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter" class="MuiTableCell-root MuiTableCell-body makeStyles-limitWidthTableCell-0 MuiTableCell-alignCenter"
/> >
<span
class=""
title="none"
/>
</td>
<td
class="MuiTableCell-root MuiTableCell-body makeStyles-limitWidthTableCell-0 MuiTableCell-alignCenter"
>
<span
class=""
title="none"
/>
</td>
<td
class="MuiTableCell-root MuiTableCell-body makeStyles-limitWidthTableCell-0 MuiTableCell-alignCenter"
>
<span
class=""
title="none"
/>
</td>
<td
class="MuiTableCell-root MuiTableCell-body makeStyles-limitWidthTableCell-0 MuiTableCell-alignCenter"
>
<span
class=""
title="CONFIG"
>
CONFIG
</span>
</td>
<td
class="MuiTableCell-root MuiTableCell-body makeStyles-limitWidthTableCell-0 MuiTableCell-alignCenter"
>
<span
class=""
title="none"
/>
</td>
<td <td
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter" class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
> >

View File

@@ -80,12 +80,28 @@ export const ManifestsProvider = ({ children }) => {
return result; 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 ( return (
<ManifestsContext.Provider <ManifestsContext.Provider
value={{ value={{
busy, busy,
manifests, manifests,
totalManifests, totalManifests,
migrateManifest,
updateManifest, updateManifest,
getManifest, getManifest,
getManifests, getManifests,

View File

@@ -88,7 +88,19 @@ export const VehicleProvider = ({ children }) => {
setBusy(true); setBusy(true);
const result = await api.getLocations(token); const result = await api.getLocations(token);
if (result.error) 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; return result;
} finally { } finally {
setBusy(false); setBusy(false);
@@ -263,6 +275,7 @@ export const VehicleProvider = ({ children }) => {
getCANSignals, getCANSignals,
getECUs, getECUs,
getLocations, getLocations,
getLocationsVehiclePaths,
getModels, getModels,
getState, getState,
getYears, getYears,

View File

@@ -98,15 +98,14 @@ export const useVehicleContext = () => ({
vehicles, vehicles,
years, years,
addVehicle: jest.fn(), addVehicle: jest.fn(),
getConnections: jest.fn((vins, _token) => { getConnections: jest
const result = {}; .fn().mockImplementation((vins, _token) => {
const result = {};
vins.forEach((vin) => { vins.forEach((vin) => {
result[vin] = true; result[vin] = true;
}); });
return Promise.resolve(result);
return result; }),
}),
getECUs: jest.fn(() => { getECUs: jest.fn(() => {
return { return {
data: [ data: [
@@ -134,6 +133,13 @@ export const useVehicleContext = () => ({
.mockResolvedValue([ .mockResolvedValue([
{ altitude: 5, longitude: 10, latitude: 15, vin: "TESTVIN123" }, { 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(() => { getModels: jest.fn(() => {
models = ["Ocean", "PEAR"]; models = ["Ocean", "PEAR"];
}), }),

View File

@@ -4,7 +4,8 @@ import {
TableCell, TableCell,
TableFooter, TableFooter,
TablePagination, TablePagination,
TableRow TableRow,
Tooltip
} from "@material-ui/core"; } from "@material-ui/core";
import clsx from "clsx"; import clsx from "clsx";
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
@@ -136,7 +137,13 @@ const CarECUsTable = ({ vin, token, classes }) => {
{tableColumns.map((column, j) => { {tableColumns.map((column, j) => {
const key = `${row.ecu + i}${column.id}` const key = `${row.ecu + i}${column.id}`
if (column.id === "updated_at") return (<TableCell key={key} align="center">{LocalDateTimeString(row.updated)}</TableCell>); if (column.id === "updated_at") return (<TableCell key={key} align="center">{LocalDateTimeString(row.updated)}</TableCell>);
return (<TableCell key={key} align="center">{row[column.id]}</TableCell>); return (
<TableCell key={key} align="center" className={classes.limitWidthTableCell}>
<Tooltip title={`${row[column.id] || 'none'}`}>
<span>{row[column.id]}</span>
</Tooltip>
</TableCell>
);
})} })}
</TableRow> </TableRow>
))} ))}

View File

@@ -1,5 +1,6 @@
const Statuses = { const Statuses = {
Pending: "pending", Pending: "pending",
Sent: "sent",
ManifestReceived: "manifest_received", ManifestReceived: "manifest_received",
ManifestAccepted: "manifest_accepted", ManifestAccepted: "manifest_accepted",
ManifestRejected: "manifest_rejected", ManifestRejected: "manifest_rejected",

View File

@@ -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 Typography from "@material-ui/core/Typography";
import { CheckCircle, Error, RadioButtonUnchecked } from "@material-ui/icons";
import clsx from "clsx"; import clsx from "clsx";
import React, { useEffect, useState } from "react";
import CircularProgress from "../CircularProgress"; import CircularProgress from "../CircularProgress";
import s from "./Statuses"; import s from "./Statuses";
@@ -13,7 +13,7 @@ const CompleteStatus = 100;
const PHASES = [ const PHASES = [
{ {
label: "Pending", label: "Pending",
events: [s.Pending], events: [s.Pending, s.Sent],
progress: () => CompleteStatus, progress: () => CompleteStatus,
}, },
{ {

View File

@@ -213,11 +213,11 @@ const MainForm = ({ vin, token }) => {
<TableFooter> <TableFooter>
<TableRow> <TableRow>
{totalCarUpdates === 0 ? ( {totalCarUpdates === 0 ? (
<TableCell colSpan={7} align="center">No Car Updates</TableCell> <TableCell colSpan={8} align="center">No Car Updates</TableCell>
) : ( ) : (
<TablePagination <TablePagination
rowsPerPageOptions={[5, 10, 25, 100]} rowsPerPageOptions={[5, 10, 25, 100]}
colSpan={7} colSpan={8}
count={totalCarUpdates} count={totalCarUpdates}
rowsPerPage={pageSize} rowsPerPage={pageSize}
page={pageIndex} page={pageIndex}

View File

@@ -48,6 +48,11 @@ const MainForm = ({ vin }) => {
id: "status_byte", id: "status_byte",
label: "Status Code", label: "Status Code",
}, },
{
id: "ErrorText",
label: "Error Text",
no_sort : true,
},
{ {
id: "epoch_usec", id: "epoch_usec",
label: "Date", label: "Date",
@@ -196,6 +201,7 @@ const MainForm = ({ vin }) => {
<TableCell>{dtc.ecu_name}</TableCell> <TableCell>{dtc.ecu_name}</TableCell>
<TableCell>{dtc.trouble_code}</TableCell> <TableCell>{dtc.trouble_code}</TableCell>
<TableCell>{dtc.status_byte}</TableCell> <TableCell>{dtc.status_byte}</TableCell>
<TableCell>{dtc.trouble_code_information?.ErrorText?.Text}</TableCell>
<TableCell>{formatDate(dtc.epoch_usec)}</TableCell> <TableCell>{formatDate(dtc.epoch_usec)}</TableCell>
</TableRow> </TableRow>
))} ))}

View File

@@ -9,11 +9,11 @@ import HomeIcon from "@material-ui/icons/Home";
import SettingsInputCompositeIcon from "@material-ui/icons/SettingsInputComposite"; import SettingsInputCompositeIcon from "@material-ui/icons/SettingsInputComposite";
import { default as React, useEffect, useState } from "react"; import { default as React, useEffect, useState } from "react";
import { getCustomDashboardSubmenu } from "../../services/customDashboards";
import { hasRole, Permissions } from "../../utils/roles"; import { hasRole, Permissions } from "../../utils/roles";
import { useUserContext } from "../Contexts/UserContext"; import { useUserContext } from "../Contexts/UserContext";
import SupersetDashboardList from "../SupersetDashboardList/SupersetDashboardList";
import { ExpandableSideMenuItem, MenuItem } from "./MenuItem"; import { ExpandableSideMenuItem, MenuItem } from "./MenuItem";
import { getCustomDashboardSubmenu } from "../../services/customDashboards"
const menuData = [ const menuData = [
{ {
label: "Home", label: "Home",
@@ -47,10 +47,9 @@ const menuData = [
}, },
{ {
label: "Datascope", label: "Datascope",
to: null, url: `${process.env.REACT_APP_SUPERSET_URL}/login`,
icon: <AssessmentIcon />, icon: <AssessmentIcon />,
rolesPerProvider: Permissions.FiskerMagnaRead, rolesPerProvider: Permissions.FiskerMagnaRead,
component: SupersetDashboardList,
submenus: getCustomDashboardSubmenu(Permissions.FiskerMagnaRead), submenus: getCustomDashboardSubmenu(Permissions.FiskerMagnaRead),
}, },
{ {

View File

@@ -190,11 +190,14 @@ exports[`SideMenu Authenticated 1`] = `
</li> </li>
<span> <span>
<li> <li>
<div <a
aria-disabled="false" aria-disabled="false"
class="MuiButtonBase-root MuiListItem-root MuiListItem-gutters MuiListItem-button" class="MuiTypography-root MuiLink-root MuiLink-underlineHover MuiButtonBase-root MuiListItem-root makeStyles-menuExternalLink-0 MuiListItem-gutters MuiListItem-button MuiTypography-colorPrimary"
href="https://dev-superset.cloud.fiskerinc.com/login"
rel="noopener"
role="button" role="button"
tabindex="0" tabindex="0"
target="_blank"
> >
<div <div
class="MuiListItemIcon-root" class="MuiListItemIcon-root"
@@ -222,38 +225,35 @@ exports[`SideMenu Authenticated 1`] = `
<span <span
class="MuiTouchRipple-root" class="MuiTouchRipple-root"
/> />
</div> </a>
<ul
style="margin-left: 50px;"
>
<li>
<a
aria-disabled="false"
class="MuiButtonBase-root MuiListItem-root MuiListItem-gutters MuiListItem-button"
href="/datascope/00000000-0000-0000-0000-000000000000"
role="button"
tabindex="0"
>
<div
class="MuiListItemText-root"
>
<span
class="MuiTypography-root MuiListItemText-primary MuiTypography-body1 MuiTypography-displayBlock"
>
test title
</span>
</div>
<span
class="MuiTouchRipple-root"
/>
</a>
</li>
</ul>
</li> </li>
</span> </span>
<ul <ul
style="margin-left: 50px;" style="margin-left: 50px;"
/> >
<li>
<a
aria-disabled="false"
class="MuiButtonBase-root MuiListItem-root MuiListItem-gutters MuiListItem-button"
href="/dashboards/0"
role="button"
tabindex="0"
>
<div
class="MuiListItemText-root"
>
<span
class="MuiTypography-root MuiListItemText-primary MuiTypography-body1 MuiTypography-displayBlock"
>
Vehicle Map
</span>
</div>
<span
class="MuiTouchRipple-root"
/>
</a>
</li>
</ul>
<li> <li>
<a <a
aria-disabled="false" aria-disabled="false"
@@ -499,11 +499,14 @@ exports[`SideMenu Magna Authenticated 1`] = `
</li> </li>
<span> <span>
<li> <li>
<div <a
aria-disabled="false" aria-disabled="false"
class="MuiButtonBase-root MuiListItem-root MuiListItem-gutters MuiListItem-button" class="MuiTypography-root MuiLink-root MuiLink-underlineHover MuiButtonBase-root MuiListItem-root makeStyles-menuExternalLink-0 MuiListItem-gutters MuiListItem-button MuiTypography-colorPrimary"
href="https://dev-superset.cloud.fiskerinc.com/login"
rel="noopener"
role="button" role="button"
tabindex="0" tabindex="0"
target="_blank"
> >
<div <div
class="MuiListItemIcon-root" class="MuiListItemIcon-root"
@@ -531,38 +534,35 @@ exports[`SideMenu Magna Authenticated 1`] = `
<span <span
class="MuiTouchRipple-root" class="MuiTouchRipple-root"
/> />
</div> </a>
<ul
style="margin-left: 50px;"
>
<li>
<a
aria-disabled="false"
class="MuiButtonBase-root MuiListItem-root MuiListItem-gutters MuiListItem-button"
href="/datascope/00000000-0000-0000-0000-000000000000"
role="button"
tabindex="0"
>
<div
class="MuiListItemText-root"
>
<span
class="MuiTypography-root MuiListItemText-primary MuiTypography-body1 MuiTypography-displayBlock"
>
test title
</span>
</div>
<span
class="MuiTouchRipple-root"
/>
</a>
</li>
</ul>
</li> </li>
</span> </span>
<ul <ul
style="margin-left: 50px;" style="margin-left: 50px;"
/> >
<li>
<a
aria-disabled="false"
class="MuiButtonBase-root MuiListItem-root MuiListItem-gutters MuiListItem-button"
href="/dashboards/0"
role="button"
tabindex="0"
>
<div
class="MuiListItemText-root"
>
<span
class="MuiTypography-root MuiListItemText-primary MuiTypography-body1 MuiTypography-displayBlock"
>
Vehicle Map
</span>
</div>
<span
class="MuiTouchRipple-root"
/>
</a>
</li>
</ul>
<span> <span>
<li> <li>
<a <a

View File

@@ -7,6 +7,8 @@ import { ManifestsProvider, useManifestsContext } from "../../Contexts/Manifests
import { useStatusContext } from "../../Contexts/StatusContext"; import { useStatusContext } from "../../Contexts/StatusContext";
import { useUserContext } from "../../Contexts/UserContext"; import { useUserContext } from "../../Contexts/UserContext";
import { DropDownList } from "../../Controls/DropDownList"; import { DropDownList } from "../../Controls/DropDownList";
import { RoleWrap } from "../../Controls/RoleWrap";
import { Permissions } from "../../../utils/roles";
import useStyles from "../../useStyles"; import useStyles from "../../useStyles";
const manifestTypes = [ const manifestTypes = [
@@ -37,11 +39,13 @@ const MainForm = () => {
const [manifest, setManifest] = useState(null); const [manifest, setManifest] = useState(null);
const [redirect, setRedirect] = useState(null); const [redirect, setRedirect] = useState(null);
const { getManifest, busy, updateManifest } = useManifestsContext(); const { getManifest, busy, migrateManifest, updateManifest } = useManifestsContext();
const { const {
token: { token: {
idToken: { jwtToken: token }, idToken: { jwtToken: token },
}, },
groups,
providers,
} = useUserContext(); } = useUserContext();
const { setMessage, setTitle, setSitePath } = useStatusContext(); 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(() => { useEffect(() => {
(async () => { (async () => {
try { try {
@@ -179,6 +194,24 @@ const MainForm = () => {
> >
{busy ? "Updating..." : "Update"} {busy ? "Updating..." : "Update"}
</Button> </Button>
<RoleWrap
groups={groups}
providers={providers}
rolesPerProvider={Permissions.ManifestMigration}
>
<Button
type="button"
aria-label="migrate manifest"
disabled={busy || manifest == null}
fullWidth
variant="contained"
color="secondary"
className={classes.submit}
onClick={manifestMigrate}
>
{busy ? "Migrating..." : "Migrate"}
</Button>
</RoleWrap>
</FormControl> </FormControl>
</div> </div>

View File

@@ -35,6 +35,9 @@ const HeaderSortable = (props) => {
value === "desc" ? "sorted descending" : "sorted ascending"; value === "desc" ? "sorted descending" : "sorted ascending";
const ColumnLabel = (column) => { const ColumnLabel = (column) => {
if (column.no_sort) {
return column.label
}
if (column.id) { if (column.id) {
return ( return (
<TableSortLabel <TableSortLabel

View File

@@ -41,8 +41,8 @@ const VehiclePopUp = (props) => {
<DialogTitle align="center" onClose={onClose}> <DialogTitle align="center" onClose={onClose}>
{vin} {vin}
{" "} {" "}
<IconButton> <IconButton onClick={toggleView}>
<VisibilityIcon fontSize="inherit" onClick={toggleView} /> <VisibilityIcon fontSize="inherit" />
</IconButton> </IconButton>
</DialogTitle> </DialogTitle>
<div align="center" className={classes.popUpContent}> <div align="center" className={classes.popUpContent}>

View File

@@ -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 (
<MapContainer
center={center}
zoom={zoom}
style={{
width: "100%",
height: "900px",
}}
>
<TileLayer
attribution='&copy <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
<CenterFocus center={center} zoom={zoom} />
{markers && markers.map((vinLocations) => (
<div key={vinLocations[0]}>
<Polyline
key={'line' + vinLocations[0]}
positions={vinLocations[1]}
pathOptions={{
color: props.vinsToShowOnMapColors && props.vinsToShowOnMapColors.get(vinLocations[0]),
}}
/>
{vinLocations[1][0] &&
<Marker
icon={getCarIcon(vinLocations[0])}
key={'marker' + vinLocations[0]}
position={vinLocations[1][0]}
title={vinLocations[0]}
opacity={0.9}
zIndexOffset={getZIndex(vinLocations[0])}
eventHandlers={{
click: () => {
setCenter(vinLocations[1][0]);
setZoom(16);
},
}}
>
<Popup>
<div align="center">
<p className={classes.markerTitle}>
<b>{vinLocations[0]}</b>
</p>
<Button
type="submit"
variant="contained"
color="primary"
onClick={(e) => selectCar(e, vinLocations[0])}
>
View Stats
</Button>
</div>
</Popup>
</Marker>
}
</div>
))}
{carState ? (
<VehiclePopUp
{...carState}
className={classes.popup}
onClose={handleClose}
/>
) : null}
</MapContainer>
);
};
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) => (
<VehicleProvider>
<ComponentVehiclePathsMap vinsToShowOnMapColors={props.vinsToShowOnMapColors} lookbackHours={props.lookbackHours} />
</VehicleProvider>
);
export default VehiclePathsMap;

View File

@@ -288,7 +288,12 @@ const useStyles = makeStyles((theme) => ({
tableHeader: { tableHeader: {
textDecorationStyle: "solid", textDecorationStyle: "solid",
fontWeight:500, fontWeight:500,
} },
limitWidthTableCell: {
maxWidth: "200px",
whiteSpace: "normal",
wordWrap: "break-word",
},
})); }));
export default useStyles; export default useStyles;

View File

@@ -13,6 +13,7 @@ const DTCTimelineAPI = {
ecu, ecu,
start_time: startDate, start_time: startDate,
end_time: endDate, end_time: endDate,
decode:true,
...search, ...search,
}; };
const url = addQueryParams(`${API_ENDPOINT}/dtcs/${vin}`, queryParams); const url = addQueryParams(`${API_ENDPOINT}/dtcs/${vin}`, queryParams);

View File

@@ -55,13 +55,15 @@ const ecusData = [
}, },
]; ];
const signals = {data:[ const signals = {
{ data: [
timestamp: "2021-07-14T20:09:40.98187Z", {
name: "signal", timestamp: "2021-07-14T20:09:40.98187Z",
value: 123 name: "signal",
}, value: 123
]}; },
],
};
const trexLogs = { const trexLogs = {
RealOffset: 0, RealOffset: 0,
@@ -127,6 +129,12 @@ const vehiclesAPI = {
.mockResolvedValue([ .mockResolvedValue([
{ altitude: 5, longitude: 10, latitude: 15, vin: "TESTVIN123" }, { 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) => { getVehicle: async (vin) => {
const index = data.findIndex(element => element.vin === vin); const index = data.findIndex(element => element.vin === vin);
return data[index]; return data[index];
@@ -134,7 +142,7 @@ const vehiclesAPI = {
getVehicles: async () => { getVehicles: async () => {
return { data }; return { data };
}, },
getFleets: async (vin) => {return { data: ["fleet1", "fleet2"]}}, getFleets: async (vin) => { return { data: ["fleet1", "fleet2"] } },
getYears: async () => { getYears: async () => {
return { return {
data: [2021, 2022], data: [2021, 2022],
@@ -152,29 +160,29 @@ const vehiclesAPI = {
return vehicle; return vehicle;
}, },
getCANSignals: async (vin, vehicle) => { getCANSignals: async (vin, vehicle) => {
return signals; return signals;
}, },
getTRexLogs: async (vin, date, offset, count, direction, token) => { getTRexLogs: async (vin, date, offset, count, direction, token) => {
return trexLogs; return trexLogs;
}, },
getVersionLog: async (vin) => ({ getVersionLog: async (vin) => ({
"data": [ "data": [
{ {
"id": 1, "id": 1,
"vin": "${vin}", "vin": "${vin}",
"version_source": "TREX", "version_source": "TREX",
"version": "0.9.56", "version": "0.9.56",
"created_at": "2023-01-13T02:11:33.327214Z" "created_at": "2023-01-13T02:11:33.327214Z"
}, },
{ {
"id": 2, "id": 2,
"vin": "${vin}", "vin": "${vin}",
"version_source": "DBC", "version_source": "DBC",
"version": "386c18977a1be3cda60c953e5902c680dbe82b89523f2527e80cd9db863db991", "version": "386c18977a1be3cda60c953e5902c680dbe82b89523f2527e80cd9db863db991",
"created_at": "2023-01-13T02:11:33.330932Z" "created_at": "2023-01-13T02:11:33.330932Z"
} }
], ],
"total": 2 "total": 2
}) })
}; };

View File

@@ -1,8 +1,30 @@
import VehiclePathsMap from "../components/VehiclePathsMap";
const INVALID_DASHBOARD = { const INVALID_DASHBOARD = {
label: "Invalid Dashboard", label: "Invalid Dashboard",
error: "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 = [ export const CustomDashboardList = [
/* /*
{ {
@@ -14,6 +36,11 @@ export const CustomDashboardList = [
component: <MyCustomComponent /> component: <MyCustomComponent />
} }
*/ */
{
label: "Vehicle Map",
component: <VehiclePathsMap vinsToShowOnMapColors={vinsToShowOnMapColors} lookbackHours={lookbackHours} />
}
]; ];
export const getCustomDashboard = (index) => { export const getCustomDashboard = (index) => {

View File

@@ -78,6 +78,17 @@ const manifestsAPI = {
}) })
.then(fetchRespHandler) .then(fetchRespHandler)
.catch(errorHandler), .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; export default manifestsAPI;

View File

@@ -76,6 +76,17 @@ const vehiclesAPI = {
.then(fetchRespHandler) .then(fetchRespHandler)
.catch(errorHandler), .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) => getState: async (token, vin) =>
fetch(`${API_ENDPOINT}/carstate?vin=${vin}`, { fetch(`${API_ENDPOINT}/carstate?vin=${vin}`, {
method: "GET", method: "GET",
@@ -185,7 +196,7 @@ const vehiclesAPI = {
.then(fetchRespHandler) .then(fetchRespHandler)
.catch(errorHandler), .catch(errorHandler),
getVersionLog: async ({vin, ...search}, token) => { getVersionLog: async ({ vin, ...search }, token) => {
const u = addQueryParams(`${API_ENDPOINT}/vehicle/${vin}/version/logs`, search); const u = addQueryParams(`${API_ENDPOINT}/vehicle/${vin}/version/logs`, search);
return fetch(u, { return fetch(u, {
method: "GET", method: "GET",

View File

@@ -10,6 +10,15 @@ export const ValidateLocationData = (location) => {
return true; return true;
} }
export const ValidateLocationVehiclePathsData = (location) => {
if (location.length < 2) {
return false;
}
// location is an array of length 2 as {<latitude> float64, <longitude> float64}
return !(Math.abs(location[0]) > 90 || Math.abs(location[1]) > 180);
}
export const ValidateLocationByParam = (parameter, value) => { export const ValidateLocationByParam = (parameter, value) => {
if (invalidLocation === value) return false; if (invalidLocation === value) return false;
switch (parameter) { switch (parameter) {

View File

@@ -8,6 +8,7 @@ export const Roles = {
APPROVESUPPLIERS: process.env.REACT_APP_ROLE_SUPPLIER_APPROVER, APPROVESUPPLIERS: process.env.REACT_APP_ROLE_SUPPLIER_APPROVER,
MANUFACTURE: process.env.REACT_APP_ROLE_MANUFACTURE, MANUFACTURE: process.env.REACT_APP_ROLE_MANUFACTURE,
MAGNAGROUP: process.env.REACT_APP_MAGNA_GROUP_ID, MAGNAGROUP: process.env.REACT_APP_MAGNA_GROUP_ID,
MANIFEST_MIGRATION: process.env.REACT_APP_ROLE_MANIFEST_MIGRATION
}; };
export const Providers = { export const Providers = {
@@ -94,4 +95,7 @@ export const Permissions = {
[Providers.FISKER_QA]: [Roles.MANUFACTURE], [Providers.FISKER_QA]: [Roles.MANUFACTURE],
[Providers.MAGNA]: [Roles.MAGNAGROUP], [Providers.MAGNA]: [Roles.MAGNAGROUP],
}, },
ManifestMigration: {
[Providers.FISKER]: [Roles.MANIFEST_MIGRATION]
}
}; };