diff --git a/package-lock.json b/package-lock.json
index 176b24a..5dd6166 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -27,6 +27,8 @@
"date-fns": "^2.29.2",
"email-validator": "^2.0.4",
"env-cmd": "^10.1.0",
+ "file-saver": "^2.0.5",
+ "filesaver": "^0.0.13",
"jwt-decode": "^3.1.2",
"leaflet": "^1.8.0",
"material-ui-dropzone": "^3.5.0",
@@ -9424,6 +9426,11 @@
"webpack": "^4.0.0 || ^5.0.0"
}
},
+ "node_modules/file-saver": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.5.tgz",
+ "integrity": "sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA=="
+ },
"node_modules/file-selector": {
"version": "0.1.19",
"resolved": "https://registry.npmjs.org/file-selector/-/file-selector-0.1.19.tgz",
@@ -9443,6 +9450,15 @@
"minimatch": "^3.0.4"
}
},
+ "node_modules/filesaver": {
+ "version": "0.0.13",
+ "resolved": "https://registry.npmjs.org/filesaver/-/filesaver-0.0.13.tgz",
+ "integrity": "sha512-ay2iShYJKmzKRPk89cgb14foqtCXcJIe5i+qdlSPAouKfBv7F2VZ0lxk9GjpcODe9p2YrXfi3Q+4CRn7ZDmleQ==",
+ "dependencies": {
+ "mkdirp": "^0.5.0",
+ "safename": "0.0.4"
+ }
+ },
"node_modules/filesize": {
"version": "8.0.7",
"resolved": "https://registry.npmjs.org/filesize/-/filesize-8.0.7.tgz",
@@ -15612,6 +15628,11 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/safename": {
+ "version": "0.0.4",
+ "resolved": "https://registry.npmjs.org/safename/-/safename-0.0.4.tgz",
+ "integrity": "sha512-+n4TsvESZKTXbHxOTSyQ0Q1JCXRb6MohgrqC2fbdALzTNQP/IhPOnCNRA4JPtagQq+6DD5ZsQ3lKMy57BYvwJA=="
+ },
"node_modules/safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
@@ -24710,6 +24731,11 @@
"schema-utils": "^3.0.0"
}
},
+ "file-saver": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.5.tgz",
+ "integrity": "sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA=="
+ },
"file-selector": {
"version": "0.1.19",
"resolved": "https://registry.npmjs.org/file-selector/-/file-selector-0.1.19.tgz",
@@ -24726,6 +24752,15 @@
"minimatch": "^3.0.4"
}
},
+ "filesaver": {
+ "version": "0.0.13",
+ "resolved": "https://registry.npmjs.org/filesaver/-/filesaver-0.0.13.tgz",
+ "integrity": "sha512-ay2iShYJKmzKRPk89cgb14foqtCXcJIe5i+qdlSPAouKfBv7F2VZ0lxk9GjpcODe9p2YrXfi3Q+4CRn7ZDmleQ==",
+ "requires": {
+ "mkdirp": "^0.5.0",
+ "safename": "0.0.4"
+ }
+ },
"filesize": {
"version": "8.0.7",
"resolved": "https://registry.npmjs.org/filesize/-/filesize-8.0.7.tgz",
@@ -29080,6 +29115,11 @@
"is-regex": "^1.1.4"
}
},
+ "safename": {
+ "version": "0.0.4",
+ "resolved": "https://registry.npmjs.org/safename/-/safename-0.0.4.tgz",
+ "integrity": "sha512-+n4TsvESZKTXbHxOTSyQ0Q1JCXRb6MohgrqC2fbdALzTNQP/IhPOnCNRA4JPtagQq+6DD5ZsQ3lKMy57BYvwJA=="
+ },
"safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
diff --git a/package.json b/package.json
index 68a020d..f90fa4c 100644
--- a/package.json
+++ b/package.json
@@ -22,6 +22,8 @@
"date-fns": "^2.29.2",
"email-validator": "^2.0.4",
"env-cmd": "^10.1.0",
+ "file-saver": "^2.0.5",
+ "filesaver": "^0.0.13",
"jwt-decode": "^3.1.2",
"leaflet": "^1.8.0",
"material-ui-dropzone": "^3.5.0",
diff --git a/src/components/App/__snapshots__/App.test.js.snap b/src/components/App/__snapshots__/App.test.js.snap
index d1a399a..7ec5bb8 100644
--- a/src/components/App/__snapshots__/App.test.js.snap
+++ b/src/components/App/__snapshots__/App.test.js.snap
@@ -9911,6 +9911,24 @@ exports[`App Route /vehicle-status authenticated 1`] = `
class="MuiTouchRipple-root"
/>
+
+
diff --git a/src/components/CANSelfServe/SelfServe/__snapshots__/index.test.jsx.snap b/src/components/CANSelfServe/SelfServe/__snapshots__/index.test.jsx.snap
new file mode 100644
index 0000000..862780e
--- /dev/null
+++ b/src/components/CANSelfServe/SelfServe/__snapshots__/index.test.jsx.snap
@@ -0,0 +1,374 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Render Render 1`] = `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`;
diff --git a/src/components/CANSelfServe/SelfServe/index.jsx b/src/components/CANSelfServe/SelfServe/index.jsx
new file mode 100644
index 0000000..4dbf59d
--- /dev/null
+++ b/src/components/CANSelfServe/SelfServe/index.jsx
@@ -0,0 +1,197 @@
+import DateFnsUtils from '@date-io/date-fns';
+import { Button, Checkbox, Chip, CircularProgress, FormControl, Grid, InputLabel, ListItemText, MenuItem, Select } from "@material-ui/core";
+import { KeyboardDatePicker, KeyboardTimePicker, MuiPickersUtilsProvider } from '@material-ui/pickers';
+import React, { useEffect, useState } from "react";
+import { logger } from "../../../services/monitoring";
+import { CANSignalsExportProvider, useCANSignalsExportContext } from "../../Contexts/CANSignalsExportContext";
+import { useStatusContext } from "../../Contexts/StatusContext";
+import { useUserContext } from "../../Contexts/UserContext";
+import useStyles from "../../useStyles";
+
+const MainForm = ({ id }) => {
+ const classes = useStyles();
+ const { setMessage } = useStatusContext();
+ const { busy, canSignals, getCANSignalList, getDynamicColumnCANSignals } = useCANSignalsExportContext();
+
+ const [selectedStartDate, setSelectedStartDate] = useState(new Date(Date.now() - 24 * 60 * 60 * 1000));
+ const [selectedEndDate, setSelectedEndDate] = useState(new Date());
+ const [selectedCanSignals, setSelectedCanSignals] = useState([]);
+
+
+ const {
+ token: {
+ idToken: { jwtToken: token },
+ },
+ } = useUserContext();
+
+ const handleSubmit = async (event) => {
+ event.preventDefault();
+ let timestamp_start = Date.parse(selectedStartDate.toUTCString()) / 1000
+ let timestamp_end = Date.parse(selectedEndDate.toUTCString()) / 1000
+ try {
+ await getDynamicColumnCANSignals(id, timestamp_start, timestamp_end, selectedCanSignals, token)
+ } catch(e){
+ setMessage(e.message);
+ logger.error(e.stack)
+ }
+ };
+
+ const isSubmitDisabled = !selectedStartDate || !selectedEndDate || selectedCanSignals.length === 0;
+
+ useEffect(() => {
+ (async () => {
+ try {
+ if (!token) return;
+ await getCANSignalList(token);
+ } catch (e) {
+ setMessage(e.message);
+ logger.warn(e.stack);
+ }
+ })();
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [token]);
+
+ const handleDateChange = (value, dateType) => {
+ const newDate = new Date(value);
+ const oldDate = dateType === "start" ? selectedStartDate || new Date() : selectedEndDate || new Date();
+ newDate.setHours(oldDate.getHours());
+ newDate.setMinutes(oldDate.getMinutes());
+ newDate.setSeconds(oldDate.getSeconds());
+ if (dateType === "start") {
+ setSelectedStartDate(newDate);
+ } else {
+ setSelectedEndDate(newDate);
+ }
+ };
+
+ const handleTimeFromChange = (value) => {
+ setSelectedStartDate(value);
+ };
+ const handleTimeToChange = (value) => {
+ setSelectedEndDate(value);
+ };
+
+ const handleSelectedItemsChange = (event) => {
+ setSelectedCanSignals(event.target.value);
+ };
+
+ return (
+
+
+
+
+
+
+ handleDateChange(value, "start")}
+ KeyboardButtonProps={{
+ 'aria-label': 'change date',
+ }}
+ />
+
+
+
+
+
+ handleDateChange(value, "end")}
+ KeyboardButtonProps={{
+ 'aria-label': 'change date',
+ }}
+ />
+
+
+
+
+
+
+
+
+
+ Select CAN signals
+
+
+
+
+
+
+
+
+ );
+};
+
+const CANSignalExport = (props) => (
+
+
+
+);
+
+export default CANSignalExport;
diff --git a/src/components/CANSelfServe/SelfServe/index.test.jsx b/src/components/CANSelfServe/SelfServe/index.test.jsx
new file mode 100644
index 0000000..5106c38
--- /dev/null
+++ b/src/components/CANSelfServe/SelfServe/index.test.jsx
@@ -0,0 +1,42 @@
+jest.mock("../../Contexts/StatusContext");
+jest.mock("../../Contexts/UserContext");
+jest.mock("../../../services/CANSignalAPI");
+jest.useFakeTimers();
+jest.setSystemTime(new Date(2023, 3, 1, 6, 30, 45, 100));
+
+import { render, waitFor } from "@testing-library/react";
+import { BrowserRouter } from "react-router-dom";
+import addSnapshotSerializer from "../../../utils/snapshot";
+import { TEST_AUTH_OBJECT_FISKER } from "../../../utils/testing";
+
+import { StatusProvider } from "../../Contexts/StatusContext";
+import { setToken, UserProvider } from "../../Contexts/UserContext";
+import CANSignalExport from "./index";
+
+const renderCANSignalExport = async () => {
+ const { container } = render(
+
+
+
+
+
+
+
+ );
+ await waitFor(() => {
+ /* render */
+ });
+ return container;
+};
+
+describe("Render", () => {
+ beforeAll(() => {
+ addSnapshotSerializer(expect);
+ });
+
+ it("Render", async () => {
+ setToken(TEST_AUTH_OBJECT_FISKER);
+ const container = await renderCANSignalExport();
+ expect(container).toMatchSnapshot();
+ });
+});
diff --git a/src/components/CANSelfServe/SelfServeTab.jsx b/src/components/CANSelfServe/SelfServeTab.jsx
new file mode 100644
index 0000000..ce250d3
--- /dev/null
+++ b/src/components/CANSelfServe/SelfServeTab.jsx
@@ -0,0 +1,21 @@
+import { Typography } from "@material-ui/core";
+import clsx from "clsx";
+import React from "react";
+import { useParams } from "react-router";
+
+import useStyles from "../useStyles";
+import SelfServe from "./SelfServe";
+
+const SelfServeTab = () => {
+ const { vin } = useParams();
+ const classes = useStyles();
+
+ return (
+
+ Can Signals Self Serve
+
+
+ );
+};
+
+export default SelfServeTab;
diff --git a/src/components/Cars/Status/__snapshots__/index.test.jsx.snap b/src/components/Cars/Status/__snapshots__/index.test.jsx.snap
index b08cc58..2036b7d 100644
--- a/src/components/Cars/Status/__snapshots__/index.test.jsx.snap
+++ b/src/components/Cars/Status/__snapshots__/index.test.jsx.snap
@@ -195,6 +195,24 @@ exports[`CarStatus Render 1`] = `
class="MuiTouchRipple-root"
/>
+
+
diff --git a/src/components/Cars/Status/index.jsx b/src/components/Cars/Status/index.jsx
index 94a49c6..ffe3802 100644
--- a/src/components/Cars/Status/index.jsx
+++ b/src/components/Cars/Status/index.jsx
@@ -5,6 +5,7 @@ import { useParams } from "react-router";
import { useLocation } from "react-router-dom";
import { hasRole, Permissions } from "../../../utils/roles";
+import SelfServeTab from "../../CANSelfServe/SelfServeTab";
import { useStatusContext } from "../../Contexts/StatusContext";
import { useUserContext } from "../../Contexts/UserContext";
import TabPanel from "../../Controls/TabPanel";
@@ -17,7 +18,7 @@ import DigitalTwinTab from "./DigitalTwinTab";
import ECUsTab from "./ECUsTab";
import FleetsTab from "./FleetsTab";
import RemoteCommandsTab from "./RemoteCommandsTab";
-import TRexLogsTab from "./TRexLogsTab"
+import TRexLogsTab from "./TRexLogsTab";
const tabHashes = ["details", "updates", "filters"];
@@ -60,6 +61,10 @@ const TabViews = [
component: FleetsTab,
rolesPerProvider: Permissions.FiskerRead,
},
+ {
+ label: "CAN Signal Export",
+ component: SelfServeTab
+ }
];
const filterTabs = (data, groups, providers) => {
diff --git a/src/components/Cars/Status/index.test.jsx b/src/components/Cars/Status/index.test.jsx
index 21a32d6..739a284 100644
--- a/src/components/Cars/Status/index.test.jsx
+++ b/src/components/Cars/Status/index.test.jsx
@@ -6,15 +6,15 @@ jest.mock("@material-ui/core/utils/unstable_useId", () =>
);
import { render, waitFor } from "@testing-library/react";
-import { BrowserRouter } from "react-router-dom";
import routeData from "react-router";
+import { BrowserRouter } from "react-router-dom";
+import addSnapshotSerializer from "../../../utils/snapshot";
+import { TEST_AUTH_OBJECT_FISKER } from "../../../utils/testing";
import { CANFiltersProvider } from "../../Contexts/CANFiltersContext";
import { StatusProvider } from "../../Contexts/StatusContext";
-import { UserProvider, setToken } from "../../Contexts/UserContext";
-import { TEST_AUTH_OBJECT_FISKER } from "../../../utils/testing";
+import { setToken, UserProvider } from "../../Contexts/UserContext";
import CarStatus from "./index";
-import addSnapshotSerializer from "../../../utils/snapshot";
const renderCarStatus = async () => {
const { container } = render(
diff --git a/src/components/Contexts/CANFiltersContext.jsx b/src/components/Contexts/CANFiltersContext.jsx
index b751827..4b2870d 100644
--- a/src/components/Contexts/CANFiltersContext.jsx
+++ b/src/components/Contexts/CANFiltersContext.jsx
@@ -1,6 +1,6 @@
import React, { useContext, useState } from "react";
import api from "../../services/CANFiltersAPI";
-import { validateCANID, validateFilter } from "../../utils/validationSupplier";
+import { validateCANID, validateFilter, validateVIN } from "../../utils/validationSupplier";
const CANFiltersContext = React.createContext();
@@ -100,10 +100,4 @@ export const CANFiltersProvider = ({ children }) => {
);
};
-const validateVIN = (vin) => {
- if (vin == null || vin.length !== 17) {
- throw new Error("Invalid VIN");
- }
-};
-
export const useCANFiltersContext = () => useContext(CANFiltersContext);
diff --git a/src/components/Contexts/CANSignalsExportContext.jsx b/src/components/Contexts/CANSignalsExportContext.jsx
new file mode 100644
index 0000000..175c851
--- /dev/null
+++ b/src/components/Contexts/CANSignalsExportContext.jsx
@@ -0,0 +1,73 @@
+import React, { useContext, useState } from "react";
+import api from "../../services/CANSignalAPI";
+import { validateVIN } from "../../utils/validationSupplier";
+
+const CANSignalsExportContext = React.createContext();
+
+export const CANSignalsExportProvider = ({ children }) => {
+ const [busy, setBusy] = useState(false);
+
+ const [canSignals, setCanSignals] = useState([]);
+
+ const getCANSignalList = async (token) => {
+ try {
+ setBusy(true);
+ const result = await api.getCanSignalList(token);
+ if (result.error) {
+ throw new Error(`Get can signal list error. ${result.message}`);
+ }
+ setCanSignals(result.data ?? []);
+ return result;
+ } finally {
+ setBusy(false);
+ }
+ };
+
+ const getDynamicColumnCANSignals = async (vin, timestart, timeend, cansingals, token) => {
+ try {
+ setBusy(true)
+ if (!vin) return;
+
+ validateVIN(vin);
+ if (timestart > timeend) throw new Error("Start time cannot be after end time");
+ const result = await api.getCanSignalsVin(vin, timestart, timeend, cansingals, token);
+ if (result.error || !result.ok)
+ throw new Error(`Get CAN signals error. ${result.message}`);
+
+ const blob = await result.blob();
+ const reader = new FileReader();
+ reader.onload = () => {
+ const csvData = reader.result;
+ const blob = new Blob([csvData], { type: 'text/csv;charset=utf-8' });
+ const link = document.createElement('a');
+ link.href = window.URL.createObjectURL(blob);
+ link.download = 'CAN_signals.csv';
+ link.click();
+ };
+ reader.readAsText(blob);
+ } catch (e) {
+ throw new Error(e)
+ }
+ finally {
+ setBusy(false)
+ }
+ }
+
+ return (
+
+ {children}
+
+ );
+};
+
+export const useCANSignalsExportContext = () => useContext(CANSignalsExportContext);
+
+
+
diff --git a/src/components/Contexts/VehicleContext.jsx b/src/components/Contexts/VehicleContext.jsx
index 412c64e..31d0eb6 100644
--- a/src/components/Contexts/VehicleContext.jsx
+++ b/src/components/Contexts/VehicleContext.jsx
@@ -1,6 +1,8 @@
import React, { useContext, useState } from "react";
+
import { logger } from "../../services/monitoring";
import api from "../../services/vehiclesAPI";
+import { validateVIN } from "../../utils/validationSupplier";
const VehicleContext = React.createContext();
@@ -285,10 +287,4 @@ const validateVehicle = (v) => {
validateVIN(v.vin);
};
-const validateVIN = (vin) => {
- if (vin == null || vin.length !== 17) {
- throw new Error("Invalid VIN");
- }
-};
-
export const useVehicleContext = () => useContext(VehicleContext);
diff --git a/src/components/Contexts/__mocks__/CANSignalsExportContext.jsx b/src/components/Contexts/__mocks__/CANSignalsExportContext.jsx
new file mode 100644
index 0000000..a69a596
--- /dev/null
+++ b/src/components/Contexts/__mocks__/CANSignalsExportContext.jsx
@@ -0,0 +1,23 @@
+let busy = false;
+let canSignals = [
+ {
+ signal_name: "123",
+ },
+ {
+ signal_name: "456",
+ },
+ {
+ signal_name: "789",
+ },
+];
+
+export const CANSignalsExportProvider = ({ children }) => {
+ return {children}
;
+};
+
+export const useCANSignalsExportContext= () => ({
+ busy,
+ canSignals,
+ getCANSignalList: jest.fn(),
+ getDynamicColumnCANSignals: jest.fn(),
+});
diff --git a/src/components/Controls/TRexLogs/index.jsx b/src/components/Controls/TRexLogs/index.jsx
index 29e78d3..165c4ed 100644
--- a/src/components/Controls/TRexLogs/index.jsx
+++ b/src/components/Controls/TRexLogs/index.jsx
@@ -8,12 +8,12 @@ import {
TablePagination,
TableRow
} from "@material-ui/core";
-import LinearProgress from '@mui/material/LinearProgress';
import Checkbox from '@mui/material/Checkbox';
-import FormGroup from '@mui/material/FormGroup';
-import FormControlLabel from '@mui/material/FormControlLabel';
import FormControl from '@mui/material/FormControl';
+import FormControlLabel from '@mui/material/FormControlLabel';
+import FormGroup from '@mui/material/FormGroup';
import FormLabel from '@mui/material/FormLabel';
+import LinearProgress from '@mui/material/LinearProgress';
import {
KeyboardDatePicker, MuiPickersUtilsProvider
@@ -134,7 +134,6 @@ const TRexLogsTable = ({ vin, token, classes }) => {
let controller = new AbortController()
const readBlob = async (offset, count) => {
- console.log(`reading from offset: ${offset}`)
return await api.getTRexLogs(vin, fromatDateForRequest(selectedDate), offset, count, "UP", token, controller)
}
const getDesiredSize = () => {
@@ -156,10 +155,8 @@ const TRexLogsTable = ({ vin, token, classes }) => {
fetched.error = result.error
break
}
- console.log(`ret.RealOffset ${result.RealOffset}\nret.bytesRead ${result.bytesRead}\ndesired offset ${offset}\ndesired read size ${readSize}\nblobsize: ${result.blobSize}`)
setBlobSize(result.blobSize)
readSize *= 2
- console.log(`new read size: ${readSize}`)
offset = result.RealOffset + result.bytesRead
setCurrentOffset(offset)
fetched = transformLogs(result.data).concat(fetched)
@@ -179,7 +176,6 @@ const TRexLogsTable = ({ vin, token, classes }) => {
setTotal(0)
const msg = `Cannot fetch logs for ${fromatDateForRequest(selectedDate)}`
setMessage(msg)
- console.log(`${msg}, Cloud error:\n${fetched.error}`);
return
}
setCurrentOffset(offset)
@@ -191,9 +187,7 @@ const TRexLogsTable = ({ vin, token, classes }) => {
try {
if (!vin || !token) return;
const desiredSize = getDesiredSize()
- console.log(`desired size: ${desiredSize}, actual size: ${logs.length}`)
if (desiredSize < logs.length || allLogsFetched) {
- console.log(`enough logs in cache`)
setTotal(getFilteredLogs(logs).length)
return
}
@@ -228,7 +222,6 @@ const TRexLogsTable = ({ vin, token, classes }) => {
const handleNewFilter = (event) => {
setPageIndex(0)
- console.log(event)
setCurrentLogLevels({
...currectLogLevels,
[event.target.defaultValue]: event.target.checked,
diff --git a/src/components/SMS/Send/__snapshots__/index.test.jsx.snap b/src/components/SMS/Send/__snapshots__/index.test.jsx.snap
index d03c0ae..dbbf31c 100644
--- a/src/components/SMS/Send/__snapshots__/index.test.jsx.snap
+++ b/src/components/SMS/Send/__snapshots__/index.test.jsx.snap
@@ -1,3 +1,168 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`SMS Send Component Render 1`] = `undefined`;
+exports[`SMS Send Component Render 1`] = `
+
+`;
diff --git a/src/components/SMS/Send/index.jsx b/src/components/SMS/Send/index.jsx
index 0d4005b..0abe10a 100644
--- a/src/components/SMS/Send/index.jsx
+++ b/src/components/SMS/Send/index.jsx
@@ -1,9 +1,9 @@
import React, { useEffect, useState } from "react";
-import { useSMSContext, SMSProvider } from "../../Contexts/SMSContext";
+import { logger } from "../../../services/monitoring";
+import { SMSProvider, useSMSContext } from "../../Contexts/SMSContext";
import { useStatusContext } from "../../Contexts/StatusContext";
import { useUserContext } from "../../Contexts/UserContext";
-import { logger } from "../../../services/monitoring";
import SendForm from "./SendForm";
import ViewResult from "./ViewResult";
diff --git a/src/components/SMS/Send/index.test.jsx b/src/components/SMS/Send/index.test.jsx
index 29ce3dd..3e9b5b8 100644
--- a/src/components/SMS/Send/index.test.jsx
+++ b/src/components/SMS/Send/index.test.jsx
@@ -1,15 +1,15 @@
-import {TEST_AUTH_OBJECT_FISKER, TEST_TOKEN_FISKER} from "../../../utils/testing";
+import { TEST_AUTH_OBJECT_FISKER } from "../../../utils/testing";
jest.mock("../../Contexts/SMSContext");
jest.mock("../../Contexts/UserContext");
-import SMSSend from "./index";
-import {BrowserRouter} from "react-router-dom";
-import {UserProvider, setToken} from "../../Contexts/UserContext";
-import {StatusProvider} from "../../Contexts/StatusContext";
+import { render, waitFor } from "@testing-library/react";
+import { BrowserRouter } from "react-router-dom";
import addSnapshotSerializer from "../../../utils/snapshot";
-import {render, waitFor} from "@testing-library/react";
+import { StatusProvider } from "../../Contexts/StatusContext";
+import { setToken, UserProvider } from "../../Contexts/UserContext";
+import SMSSend from "./index";
const renderSendSMS = async () => {
@@ -38,7 +38,7 @@ describe("SMS Send Component", () => {
it("Render", async () => {
// setToken(TEST_AUTH_OBJECT_FISKER);
- const {container} = await renderSendSMS();
+ const container = await renderSendSMS();
expect(container).toMatchSnapshot();
});
});
\ No newline at end of file
diff --git a/src/services/CANSignalAPI.js b/src/services/CANSignalAPI.js
new file mode 100644
index 0000000..6035dab
--- /dev/null
+++ b/src/services/CANSignalAPI.js
@@ -0,0 +1,32 @@
+import {
+ addQueryParams, errorHandler, fetchRespHandler, getAuthHeaderOptions
+} from "../utils/http";
+
+const API_ENDPOINT = process.env.REACT_APP_OTA_SERVICE_URL;
+
+const canSignalAPI = {
+
+ getCanSignalsVin: async (vin, timestamp_start, timestamp_end, can_signals, token) =>
+ fetch(addQueryParams(`${API_ENDPOINT}/can_signals_export`, { vin, timestamp_start, timestamp_end, can_signals }), {
+ method: "GET",
+ headers: Object.assign(
+ { "Content-Type": "application/json" },
+ getAuthHeaderOptions(token)
+ ),
+ responseType: "blob"
+ })
+ .catch(errorHandler),
+
+
+ getCanSignalList: async (token) =>
+ fetch(addQueryParams(`${API_ENDPOINT}/can_signals_list`), {
+ method: "GET",
+ headers: Object.assign(
+ { "Content-Type": "application/json" },
+ getAuthHeaderOptions(token)
+ ),
+ })
+ .then(fetchRespHandler)
+ .catch(errorHandler),
+};
+export default canSignalAPI;
diff --git a/src/services/__mocks__/CANSignalAPI.js b/src/services/__mocks__/CANSignalAPI.js
new file mode 100644
index 0000000..e088c01
--- /dev/null
+++ b/src/services/__mocks__/CANSignalAPI.js
@@ -0,0 +1,16 @@
+const canSignalList = [
+ { signal_name: "123"},
+ { signal_name: "456"},
+ { signal_name: "789"}
+];
+
+const canSignalAPI = {
+ getCanSignalsVin: async (vin, timestamp_start, timestamp_end, can_signals, token) => {
+ return { data: "fake data" };
+ },
+ getCanSignalList: async (token) => {
+ return canSignalList;
+ }
+};
+
+export default canSignalAPI;
\ No newline at end of file
diff --git a/src/utils/http.js b/src/utils/http.js
index 2d8b69a..5709cb4 100644
--- a/src/utils/http.js
+++ b/src/utils/http.js
@@ -6,7 +6,6 @@ export const addQueryParams = (url, params) => {
const u = new URL(url);
Object.keys(params).forEach((key) => u.searchParams.append(key, params[key]));
-
return u.toString();
};