Release/0.0.3 to main (#254)
* CEC-2628 - Display IP in digital twin in portal (#251) * CEC-3453 Update security dll instructions (#252) * CEC-2752-Add-Mobile-Issue-Tracker (#250) * first commit * removed comments * remove more comments * fix build issues * fix unused vars * update snapshot * fix test * Fix connect ECONNREFUSED 127.0.0.1:80 * Test Magna side menu * attempt to pass test * fix test * remove comments * fix some code smells * fix test * resolve comments * fix bug * resolved comments * resolve comments * resolve comments * update snapshot * resolved comments Co-authored-by: jwu-fisker <jwu@fiskerinc.com> * Cec 2752 small fix (#253) * first commit * removed comments * remove more comments * fix build issues * fix unused vars * update snapshot * fix test * Fix connect ECONNREFUSED 127.0.0.1:80 * Test Magna side menu * attempt to pass test * fix test * remove comments * fix some code smells * fix test * resolve comments * fix bug * resolved comments * resolve comments * resolve comments * update snapshot * resolved comments * small fix Co-authored-by: jwu-fisker <jwu@fiskerinc.com> Co-authored-by: Paul Adamsen <117673433+pauladamseniii@users.noreply.github.com> Co-authored-by: das31 <31259710+das31@users.noreply.github.com>
This commit is contained in:
@@ -3,9 +3,12 @@ jest.mock("../Contexts/FileUploadContext");
|
|||||||
jest.mock("../Contexts/VehicleContext");
|
jest.mock("../Contexts/VehicleContext");
|
||||||
jest.mock("../Contexts/ManifestsContext");
|
jest.mock("../Contexts/ManifestsContext");
|
||||||
jest.mock("../Contexts/UserContext");
|
jest.mock("../Contexts/UserContext");
|
||||||
|
jest.mock("../Contexts/IssueContext");
|
||||||
jest.mock("../../services/monitoring");
|
jest.mock("../../services/monitoring");
|
||||||
jest.mock("../../services/vehiclesAPI");
|
jest.mock("../../services/vehiclesAPI");
|
||||||
jest.mock("../../services/superset")
|
jest.mock("../../services/superset");
|
||||||
|
jest.mock("../../services/suppliersAPI");
|
||||||
|
jest.mock("../../services/issueAPI");
|
||||||
|
|
||||||
import {
|
import {
|
||||||
act, cleanup, render,
|
act, cleanup, render,
|
||||||
@@ -39,13 +42,14 @@ const check = async (path, selector, compare) => {
|
|||||||
|
|
||||||
const sleepAndCheck = async (path, selector, compare) => {
|
const sleepAndCheck = async (path, selector, compare) => {
|
||||||
const container = await renderRoute(path);
|
const container = await renderRoute(path);
|
||||||
await waitFor(() => {});
|
await waitFor(() => { });
|
||||||
expect(container.querySelector(selector).innerHTML).toEqual(compare);
|
expect(container.querySelector(selector).innerHTML).toEqual(compare);
|
||||||
expect(container).toMatchSnapshot();
|
expect(container).toMatchSnapshot();
|
||||||
};
|
};
|
||||||
|
|
||||||
describe("App", () => {
|
describe("App", () => {
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
|
global.URL.createObjectURL = jest.fn();
|
||||||
addSnapshotSerializer(expect);
|
addSnapshotSerializer(expect);
|
||||||
}, 60000);
|
}, 60000);
|
||||||
|
|
||||||
@@ -78,6 +82,14 @@ describe("App", () => {
|
|||||||
await check("/vehicle-add", "span.MuiButton-label", "Sign In");
|
await check("/vehicle-add", "span.MuiButton-label", "Sign In");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("Route /issues unauthenticated", async () => {
|
||||||
|
await check("/issues", "span.MuiButton-label", "Sign In");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Route /issue-info unauthenticated", async () => {
|
||||||
|
await check("/issue-info/FISKER123", "span.MuiButton-label", "Sign In");
|
||||||
|
});
|
||||||
|
|
||||||
it("Route /vehicles unauthenticated", async () => {
|
it("Route /vehicles unauthenticated", async () => {
|
||||||
await check("/vehicles", "span.MuiButton-label", "Sign In");
|
await check("/vehicles", "span.MuiButton-label", "Sign In");
|
||||||
});
|
});
|
||||||
@@ -158,6 +170,17 @@ describe("App", () => {
|
|||||||
await check("/vehicles", "h6", "Vehicles");
|
await check("/vehicles", "h6", "Vehicles");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("Route /issues authenticated", async () => {
|
||||||
|
setToken(TEST_AUTH_OBJECT_FISKER);
|
||||||
|
await check("/issues", "h6", "Issues");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Route /issue-info authenticated", async () => {
|
||||||
|
setToken(TEST_AUTH_OBJECT_FISKER);
|
||||||
|
await check("/issue-info/FISKER123", "h6", "Issue FISKER123 Details");
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
it("Route /vehicle-status authenticated", async () => {
|
it("Route /vehicle-status authenticated", async () => {
|
||||||
setToken(TEST_AUTH_OBJECT_FISKER);
|
setToken(TEST_AUTH_OBJECT_FISKER);
|
||||||
await check("/vehicle-status/FISKER123", "h6", "Vehicle FISKER123 Details");
|
await check("/vehicle-status/FISKER123", "h6", "Vehicle FISKER123 Details");
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -164,121 +164,9 @@ exports[`CarUpdatesTab Render 1`] = `
|
|||||||
<tr
|
<tr
|
||||||
class="MuiTableRow-root MuiTableRow-footer"
|
class="MuiTableRow-root MuiTableRow-footer"
|
||||||
>
|
>
|
||||||
<td
|
<p>
|
||||||
class="MuiTableCell-root MuiTableCell-footer MuiTablePagination-root"
|
No Car Updates found
|
||||||
colspan="6"
|
</p>
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="MuiToolbar-root MuiToolbar-regular MuiTablePagination-toolbar MuiToolbar-gutters"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="MuiTablePagination-spacer"
|
|
||||||
/>
|
|
||||||
<p
|
|
||||||
class="MuiTypography-root MuiTablePagination-caption MuiTypography-body2 MuiTypography-colorInherit"
|
|
||||||
>
|
|
||||||
Rows per page:
|
|
||||||
</p>
|
|
||||||
<div
|
|
||||||
class="MuiInputBase-root MuiTablePagination-input MuiTablePagination-selectRoot"
|
|
||||||
>
|
|
||||||
<select
|
|
||||||
aria-label="rows per page"
|
|
||||||
class="MuiSelect-root MuiSelect-select MuiTablePagination-select MuiInputBase-input"
|
|
||||||
>
|
|
||||||
<option
|
|
||||||
class="MuiTablePagination-menuItem"
|
|
||||||
value="5"
|
|
||||||
>
|
|
||||||
5
|
|
||||||
</option>
|
|
||||||
<option
|
|
||||||
class="MuiTablePagination-menuItem"
|
|
||||||
value="10"
|
|
||||||
>
|
|
||||||
10
|
|
||||||
</option>
|
|
||||||
<option
|
|
||||||
class="MuiTablePagination-menuItem"
|
|
||||||
value="25"
|
|
||||||
>
|
|
||||||
25
|
|
||||||
</option>
|
|
||||||
<option
|
|
||||||
class="MuiTablePagination-menuItem"
|
|
||||||
value="100"
|
|
||||||
>
|
|
||||||
100
|
|
||||||
</option>
|
|
||||||
</select>
|
|
||||||
<svg
|
|
||||||
aria-hidden="true"
|
|
||||||
class="MuiSvgIcon-root MuiSelect-icon MuiTablePagination-selectIcon"
|
|
||||||
focusable="false"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M7 10l5 5 5-5z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<p
|
|
||||||
class="MuiTypography-root MuiTablePagination-caption MuiTypography-body2 MuiTypography-colorInherit"
|
|
||||||
>
|
|
||||||
0-0 of 0
|
|
||||||
</p>
|
|
||||||
<div
|
|
||||||
class="MuiTablePagination-actions"
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
aria-label="Previous page"
|
|
||||||
class="MuiButtonBase-root MuiIconButton-root MuiIconButton-colorInherit Mui-disabled Mui-disabled"
|
|
||||||
disabled=""
|
|
||||||
tabindex="-1"
|
|
||||||
title="Previous page"
|
|
||||||
type="button"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
class="MuiIconButton-label"
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
aria-hidden="true"
|
|
||||||
class="MuiSvgIcon-root"
|
|
||||||
focusable="false"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M15.41 16.09l-4.58-4.59 4.58-4.59L14 5.5l-6 6 6 6z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
aria-label="Next page"
|
|
||||||
class="MuiButtonBase-root MuiIconButton-root MuiIconButton-colorInherit Mui-disabled Mui-disabled"
|
|
||||||
disabled=""
|
|
||||||
tabindex="-1"
|
|
||||||
title="Next page"
|
|
||||||
type="button"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
class="MuiIconButton-label"
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
aria-hidden="true"
|
|
||||||
class="MuiSvgIcon-root"
|
|
||||||
focusable="false"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M8.59 16.34l4.58-4.59-4.58-4.59L10 5.75l6 6-6 6z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
</tfoot>
|
</tfoot>
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
@@ -130,6 +130,17 @@ exports[`DigitalTwinTab Render 1`] = `
|
|||||||
1000000
|
1000000
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
class="makeStyles-popupSection-0"
|
||||||
|
>
|
||||||
|
<p>
|
||||||
|
<b>
|
||||||
|
Trex IP
|
||||||
|
</b>
|
||||||
|
:
|
||||||
|
172.20.0.17:49850
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
class="makeStyles-popupSection-0"
|
class="makeStyles-popupSection-0"
|
||||||
>
|
>
|
||||||
|
|||||||
55
src/components/Contexts/IssueContext.jsx
Normal file
55
src/components/Contexts/IssueContext.jsx
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
import React, { useContext, useState, useMemo, useCallback } from "react";
|
||||||
|
import api from "../../services/issueAPI";
|
||||||
|
|
||||||
|
const IssueContext = React.createContext();
|
||||||
|
|
||||||
|
export const IssueProvider = ({ children }) => {
|
||||||
|
const [issue, setIssue] = useState({});
|
||||||
|
const [issues, setIssues] = useState([]);
|
||||||
|
const [totalIssues, setTotalIssues] = useState(0);
|
||||||
|
|
||||||
|
const getIssue = useCallback(async (id, token) => {
|
||||||
|
const result = await api.getIssue(id, token);
|
||||||
|
if (result.error) throw new Error(`Get issue error. ${result.message}`);
|
||||||
|
|
||||||
|
setIssue(result.data ?? []);
|
||||||
|
return result;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const getIssues = useCallback(async (search,token) => {
|
||||||
|
const result = await api.getIssues(search,token);
|
||||||
|
if (result.error) {
|
||||||
|
setIssues([]);
|
||||||
|
throw new Error(`Get issues error. ${result.message}`);
|
||||||
|
}
|
||||||
|
setIssues(result.data ?? []);
|
||||||
|
if (result.total) {
|
||||||
|
setTotalIssues(result.total);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const deleteIssue = useCallback(async (id, token) => {
|
||||||
|
const result = await api.deleteIssue(id, token);
|
||||||
|
if (result.error)
|
||||||
|
throw new Error(`Delete issue error. ${result.message}`);
|
||||||
|
return result;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const value = useMemo(() => ({
|
||||||
|
totalIssues,
|
||||||
|
issue,
|
||||||
|
issues,
|
||||||
|
|
||||||
|
deleteIssue,
|
||||||
|
getIssue,
|
||||||
|
getIssues,
|
||||||
|
}), [totalIssues, issue, issues, deleteIssue, getIssue, getIssues]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<IssueContext.Provider value={value}>
|
||||||
|
{children}
|
||||||
|
</IssueContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useIssueContext = () => useContext(IssueContext);
|
||||||
102
src/components/Contexts/IssueContext.test.jsx
Normal file
102
src/components/Contexts/IssueContext.test.jsx
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
jest.mock("../../services/issueAPI");
|
||||||
|
|
||||||
|
import {
|
||||||
|
render,
|
||||||
|
cleanup,
|
||||||
|
screen,
|
||||||
|
fireEvent,
|
||||||
|
waitFor,
|
||||||
|
} from "@testing-library/react";
|
||||||
|
import { IssueProvider, useIssueContext } from "./IssueContext";
|
||||||
|
|
||||||
|
const checkIssueResult = (issue) => {
|
||||||
|
expect(screen.getByTestId("issue").innerHTML).toEqual(issue);
|
||||||
|
};
|
||||||
|
|
||||||
|
const checkIssuesResult = (issues) => {
|
||||||
|
expect(screen.getByTestId("issues").innerHTML).toEqual(issues);
|
||||||
|
};
|
||||||
|
|
||||||
|
describe("IssueContext", () => {
|
||||||
|
describe("getIssues", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
const TestComp = () => {
|
||||||
|
const { issues, getIssues } = useIssueContext();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div data-testid="issues">{JSON.stringify(issues)}</div>
|
||||||
|
<button
|
||||||
|
data-testid="getIssues"
|
||||||
|
onClick={() => getIssues()}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
render(
|
||||||
|
<IssueProvider>
|
||||||
|
<TestComp />
|
||||||
|
</IssueProvider>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
cleanup();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Initial state", () => {
|
||||||
|
checkIssuesResult("[]");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("getIssues", async () => {
|
||||||
|
fireEvent.click(screen.getByTestId("getIssues"));
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(screen.getByTestId("issues").innerHTML).not.toBe([])
|
||||||
|
);
|
||||||
|
checkIssuesResult(JSON.stringify(expectedIssuesData));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("getIssue", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
const TestComp = () => {
|
||||||
|
const { issue, getIssue } = useIssueContext();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div data-testid="issue">{JSON.stringify(issue)}</div>
|
||||||
|
<button
|
||||||
|
data-testid="getIssue"
|
||||||
|
onClick={() => getIssue("1")}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
render(
|
||||||
|
<IssueProvider>
|
||||||
|
<TestComp />
|
||||||
|
</IssueProvider>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
cleanup();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Initial state", () => {
|
||||||
|
checkIssueResult("{}");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("getIssue", async () => {
|
||||||
|
fireEvent.click(screen.getByTestId("getIssue"));
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(screen.getByTestId("issue").innerHTML).not.toBe("{}")
|
||||||
|
);
|
||||||
|
checkIssueResult(JSON.stringify(expectedIssueData));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const expectedIssuesData = [{ "id": 18, "vin": "1GNGC26RXXJ407648", "title": "sometitle", "description": "2343242", "driver_id": "valid-cognito-id-1", "timestamp": "2022-12-09T23:16:38.074858Z" }, { "id": 19, "vin": "1GNGC26RXXJ407648", "title": "sometitle", "description": "2343242", "driver_id": "valid-cognito-id-1", "timestamp": "2022-12-09T23:16:38.074858Z" }, { "id": 20, "vin": "1GNGC26RXXJ407648", "title": "sometitle", "description": "2343242", "driver_id": "valid-cognito-id-1", "timestamp": "2022-12-09T23:16:38.074858Z" }, { "id": 21, "vin": "1GNGC26RXXJ407648", "title": "sometitle", "description": "2343242", "driver_id": "valid-cognito-id-1", "timestamp": "2022-12-09T23:16:38.074858Z" }, { "id": 22, "vin": "1GNGC26RXXJ407648", "title": "sometitle", "description": "2343242", "driver_id": "valid-cognito-id-1", "timestamp": "2022-12-09T23:16:38.074858Z" }, { "id": 25, "vin": "1GNGC26RXXJ407648", "title": "Example HMI Problem", "description": "HMI blue screen", "driver_id": "0b6b1930-b20a-4fce-967a-efac6a01fd10", "timestamp": "2022-12-19T22:25:03.848855Z" }, { "id": 26, "vin": "1GNGC26RXXJ407648", "title": "sometitle", "description": "2343242", "driver_id": "valid-cognito-id-1", "timestamp": "2022-12-09T23:16:38.074858Z" }, { "id": 27, "vin": "1GNGC26RXXJ407648", "title": "sometitle", "description": "2343242", "driver_id": "valid-cognito-id-1", "timestamp": "2022-12-09T23:16:38.074858Z" }, { "id": 28, "vin": "1GNGC26RXXJ407648", "title": "sometitle", "description": "2343242", "driver_id": "valid-cognito-id-1", "timestamp": "2022-12-09T23:16:38.074858Z" }]
|
||||||
|
|
||||||
|
const expectedIssueData = { "id": 18, "vin": "1GNGC26RXXJ407648", "title": "sometitle", "description": "2343242", "driver_id": "valid-cognito-id-1", "timestamp": "2022-12-09T23:16:38.074858Z", "images": [{ "id": 15, "image": "SGVsbG8x", "issue_id": 18 }] }
|
||||||
35
src/components/Contexts/__mocks__/IssueContext.jsx
Normal file
35
src/components/Contexts/__mocks__/IssueContext.jsx
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import React from "react";
|
||||||
|
|
||||||
|
|
||||||
|
let issue = {
|
||||||
|
"id": 1,
|
||||||
|
"vin": "1GNGC26RXXJ407648",
|
||||||
|
"title": "sometitle",
|
||||||
|
"description": "2343242",
|
||||||
|
"driver_id": "valid-cognito-id-1",
|
||||||
|
"timestamp": "2022-12-09T23:16:38.074858Z",
|
||||||
|
"images": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"image": "SGVsbG8x",
|
||||||
|
"issue_id": 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
let issues = [
|
||||||
|
{ "id": 15, "vin": "4Y1SL65848Z411439", "title": "sometitle", "description": "2343242", "driver_id": "12345", "timestamp": "2022-12-09T23:16:38.074858Z" },
|
||||||
|
{ "id": 17, "vin": "1GNGC26RXXJ407648", "title": "sometitle", "description": "2343242", "driver_id": "valid-cognito-id-1", "timestamp": "2022-12-09T23:16:38.074858Z" },
|
||||||
|
];
|
||||||
|
|
||||||
|
export const IssueProvider = ({ children }) => {
|
||||||
|
return <div data-testid="mocked-issueprovider">{children}</div>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useIssueContext = () => ({
|
||||||
|
issue,
|
||||||
|
issues,
|
||||||
|
deleteIssue: jest.fn(),
|
||||||
|
getIssues: jest.fn().mockReturnValue(issues),
|
||||||
|
getIssue: jest.fn(),
|
||||||
|
});
|
||||||
@@ -70,6 +70,7 @@ let vehicleState = {
|
|||||||
temperature: 26,
|
temperature: 26,
|
||||||
},
|
},
|
||||||
trex_version: "1000000",
|
trex_version: "1000000",
|
||||||
|
ip: "172.20.0.17:49850",
|
||||||
updated: "2022-07-26T00:26:38.880381Z",
|
updated: "2022-07-26T00:26:38.880381Z",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -146,10 +147,12 @@ export const useVehicleContext = () => ({
|
|||||||
command,
|
command,
|
||||||
parameters,
|
parameters,
|
||||||
})),
|
})),
|
||||||
getFleets: jest.fn((vin, search,_token) => {return {
|
getFleets: jest.fn((vin, search, _token) => {
|
||||||
data: ["fleet1", "fleet2"],
|
return {
|
||||||
total: 2,
|
data: ["fleet1", "fleet2"],
|
||||||
}}),
|
total: 2,
|
||||||
|
}
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const setBusy = (val) => {
|
export const setBusy = (val) => {
|
||||||
|
|||||||
@@ -197,6 +197,9 @@ const MainForm = ({ vin, token }) => {
|
|||||||
</TableBody>
|
</TableBody>
|
||||||
<TableFooter>
|
<TableFooter>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
|
{totalCarUpdates === 0 ? (
|
||||||
|
<p>No Car Updates found</p>
|
||||||
|
) : (
|
||||||
<TablePagination
|
<TablePagination
|
||||||
rowsPerPageOptions={[5, 10, 25, 100]}
|
rowsPerPageOptions={[5, 10, 25, 100]}
|
||||||
colSpan={6}
|
colSpan={6}
|
||||||
@@ -209,7 +212,7 @@ const MainForm = ({ vin, token }) => {
|
|||||||
}}
|
}}
|
||||||
onPageChange={handleChangePageIndex}
|
onPageChange={handleChangePageIndex}
|
||||||
onRowsPerPageChange={handleChangePageSize}
|
onRowsPerPageChange={handleChangePageSize}
|
||||||
/>
|
/>)}
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</TableFooter>
|
</TableFooter>
|
||||||
</Table>
|
</Table>
|
||||||
|
|||||||
217
src/components/Controls/IssueSelectionTable/index.jsx
Normal file
217
src/components/Controls/IssueSelectionTable/index.jsx
Normal file
@@ -0,0 +1,217 @@
|
|||||||
|
import React, { useEffect, useState } from "react";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
|
import PropTypes from "prop-types";
|
||||||
|
import {
|
||||||
|
Table,
|
||||||
|
TableBody,
|
||||||
|
TableCell,
|
||||||
|
TableFooter,
|
||||||
|
TablePagination,
|
||||||
|
TableRow,
|
||||||
|
Button,
|
||||||
|
} from "@material-ui/core";
|
||||||
|
import clsx from "clsx";
|
||||||
|
|
||||||
|
import { useIssueContext } from "../../Contexts/IssueContext";
|
||||||
|
import { useStatusContext } from "../../Contexts/StatusContext";
|
||||||
|
import { LocalDateTimeString } from "../../../utils/dates";
|
||||||
|
import TableHeaderSortable from "../../Table/HeaderSortable";
|
||||||
|
import { logger } from "../../../services/monitoring";
|
||||||
|
import { useLocalStorage } from "../../useLocalStorage";
|
||||||
|
import { RoleWrap } from "../RoleWrap";
|
||||||
|
import { useUserContext } from "../../Contexts/UserContext";
|
||||||
|
import { Permissions } from "../../../utils/roles";
|
||||||
|
|
||||||
|
const tableColumns = [
|
||||||
|
{
|
||||||
|
id: "id",
|
||||||
|
label: "Id",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "vin",
|
||||||
|
label: "VIN",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "title",
|
||||||
|
label: "Title",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "description",
|
||||||
|
label: "Description",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "driver_id",
|
||||||
|
label: "Driver ID",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "created_at",
|
||||||
|
label: "Created",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "",
|
||||||
|
label: "",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const PAGE_SIZE = "ISSUE_SELECTION_TABLE_PAGE_SIZE";
|
||||||
|
|
||||||
|
const IssueSelectionTable = (props) => {
|
||||||
|
const {
|
||||||
|
token,
|
||||||
|
classes,
|
||||||
|
search,
|
||||||
|
multiSelect,
|
||||||
|
selected,
|
||||||
|
onSelectAll,
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const [pageSize, setPageSize] = useLocalStorage(PAGE_SIZE, 10);
|
||||||
|
const [pageIndex, setPageIndex] = useState(0);
|
||||||
|
const [orderBy, setOrderBy] = useState("created_at");
|
||||||
|
const [order, setOrder] = useState("asc");
|
||||||
|
const { getIssues, issues, totalIssues } = useIssueContext();
|
||||||
|
const { groups, providers } = useUserContext();
|
||||||
|
const { setMessage } = useStatusContext();
|
||||||
|
|
||||||
|
const handleSort = (_event, property) => {
|
||||||
|
if (property === orderBy) {
|
||||||
|
if (order === "asc") {
|
||||||
|
setOrder("desc");
|
||||||
|
} else {
|
||||||
|
setOrder("asc");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setOrderBy(property);
|
||||||
|
setOrder("desc");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleChangePageIndex = (_event, newIndex) => {
|
||||||
|
setPageIndex(newIndex);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleChangePageSize = (event) => {
|
||||||
|
setPageSize(parseInt(event.target.value, 10));
|
||||||
|
setPageIndex(0);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSelectAll = (event) => {
|
||||||
|
if (!onSelectAll) return;
|
||||||
|
|
||||||
|
const newSelected = [];
|
||||||
|
if (event.target.checked) {
|
||||||
|
issues.forEach((car) => {
|
||||||
|
newSelected.push(car.vin);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onSelectAll(newSelected);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
(async () => {
|
||||||
|
try {
|
||||||
|
if (!token) return;
|
||||||
|
await getIssues(
|
||||||
|
{
|
||||||
|
limit: pageSize,
|
||||||
|
offset: pageSize * pageIndex,
|
||||||
|
order: `${orderBy} ${order}`,
|
||||||
|
},
|
||||||
|
token
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
setMessage(e.message);
|
||||||
|
logger.warn(e.stack);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [pageIndex, pageSize, orderBy, order, search, token]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setPageIndex(0);
|
||||||
|
}, [search]);
|
||||||
|
|
||||||
|
const { deleteIssue } = useIssueContext();
|
||||||
|
const handleDelete = (id) => {
|
||||||
|
deleteIssue(id, token).then(() => {
|
||||||
|
getIssues(token)
|
||||||
|
});
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<div className={clsx(classes.paper, classes.tableSize)}>
|
||||||
|
<Table>
|
||||||
|
<TableHeaderSortable
|
||||||
|
classes={classes}
|
||||||
|
orderBy={orderBy}
|
||||||
|
order={order}
|
||||||
|
columnData={tableColumns}
|
||||||
|
onSortRequest={handleSort}
|
||||||
|
multiSelect={multiSelect}
|
||||||
|
onSelectAll={handleSelectAll}
|
||||||
|
selectCount={selected ? selected.length : 0}
|
||||||
|
rowCount={issues ? issues.length : 0}
|
||||||
|
/>
|
||||||
|
<TableBody>
|
||||||
|
{issues.map((row) => {
|
||||||
|
return (
|
||||||
|
<TableRow key={row.id}>
|
||||||
|
<TableCell align="center">
|
||||||
|
<Link to={`/issue-info/${row.id}`}>{row.id}</Link>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell align="center">{row.vin}</TableCell>
|
||||||
|
<TableCell align="center">{row.title}</TableCell>
|
||||||
|
<TableCell align="center">{row.description || ""}</TableCell>
|
||||||
|
<TableCell align="center">{row.driver_id}</TableCell>
|
||||||
|
<TableCell align="center">
|
||||||
|
{LocalDateTimeString(row.timestamp)}
|
||||||
|
</TableCell>
|
||||||
|
<RoleWrap
|
||||||
|
groups={groups}
|
||||||
|
providers={providers}
|
||||||
|
rolesPerProvider={Permissions.FiskerDelete}
|
||||||
|
>
|
||||||
|
<TableCell>
|
||||||
|
<Button onClick={() => handleDelete(row.id)}>Delete</Button>
|
||||||
|
</TableCell>
|
||||||
|
</RoleWrap>
|
||||||
|
</TableRow>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</TableBody>
|
||||||
|
<TableFooter>
|
||||||
|
<TableRow>
|
||||||
|
{totalIssues === 0 ? (
|
||||||
|
<p>No issues found</p>
|
||||||
|
) : (
|
||||||
|
<TablePagination
|
||||||
|
rowsPerPageOptions={[5, 10, 25, 100]}
|
||||||
|
colSpan={7}
|
||||||
|
count={totalIssues}
|
||||||
|
rowsPerPage={pageSize}
|
||||||
|
page={pageIndex}
|
||||||
|
SelectProps={{
|
||||||
|
inputProps: { "aria-label": "rows per page" },
|
||||||
|
native: true,
|
||||||
|
}}
|
||||||
|
onPageChange={handleChangePageIndex}
|
||||||
|
onRowsPerPageChange={handleChangePageSize}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</TableRow>
|
||||||
|
</TableFooter>
|
||||||
|
</Table>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
IssueSelectionTable.propTypes = {
|
||||||
|
token: PropTypes.string.isRequired,
|
||||||
|
classes: PropTypes.object.isRequired,
|
||||||
|
multiSelect: PropTypes.bool,
|
||||||
|
selected: PropTypes.array,
|
||||||
|
onSelect: PropTypes.func,
|
||||||
|
onSelectAll: PropTypes.func,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default IssueSelectionTable;
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
|
import { embedDashboard } from "@superset-ui/embedded-sdk";
|
||||||
import React, { useEffect } from "react";
|
import React, { useEffect } from "react";
|
||||||
|
import { useHistory } from "react-router-dom";
|
||||||
import api from "../../services/superset";
|
import api from "../../services/superset";
|
||||||
import { useStatusContext } from "../Contexts/StatusContext";
|
import { useStatusContext } from "../Contexts/StatusContext";
|
||||||
import { useUserContext } from "../Contexts/UserContext";
|
import { useUserContext } from "../Contexts/UserContext";
|
||||||
import './index.css'
|
import './index.css';
|
||||||
import { embedDashboard } from "@superset-ui/embedded-sdk";
|
|
||||||
import { useHistory } from "react-router-dom";
|
|
||||||
|
|
||||||
|
|
||||||
const Dashboard = () => {
|
const Dashboard = () => {
|
||||||
@@ -29,7 +29,7 @@ const Dashboard = () => {
|
|||||||
fetchGuestToken: () => api.getGuestToken(token),
|
fetchGuestToken: () => api.getGuestToken(token),
|
||||||
dashboardUiConfig: { hideTab: true, hideTitle: true }, // dashboard UI config: hideTitle, hideTab, hideChartControls (optional)
|
dashboardUiConfig: { hideTab: true, hideTitle: true }, // dashboard UI config: hideTitle, hideTab, hideChartControls (optional)
|
||||||
});
|
});
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [history.location]);
|
}, [history.location]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ const mapOpenCloseState = (value) =>
|
|||||||
|
|
||||||
const DigitalTwin = (props) => {
|
const DigitalTwin = (props) => {
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
const { battery, doors, location, trex_version, updated, windows } = props;
|
const { battery, doors, location, trex_version, ip, updated, windows } = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
@@ -51,6 +51,11 @@ const DigitalTwin = (props) => {
|
|||||||
{keyValueTemplate("Trex Version", trex_version)}
|
{keyValueTemplate("Trex Version", trex_version)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{ip && (
|
||||||
|
<div className={classes.popupSection}>
|
||||||
|
{keyValueTemplate("Trex IP", ip)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
{updated != null && (
|
{updated != null && (
|
||||||
<div className={classes.popupSection}>
|
<div className={classes.popupSection}>
|
||||||
{keyValueTemplate("Updated at", LocalDateTimeString(updated))}
|
{keyValueTemplate("Updated at", LocalDateTimeString(updated))}
|
||||||
|
|||||||
79
src/components/Issues/Info/Details/index.jsx
Normal file
79
src/components/Issues/Info/Details/index.jsx
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
import { Grid } from "@material-ui/core";
|
||||||
|
import clsx from "clsx";
|
||||||
|
import React, { useEffect } from "react";
|
||||||
|
|
||||||
|
import { logger } from "../../../../services/monitoring";
|
||||||
|
import { useStatusContext } from "../../../Contexts/StatusContext";
|
||||||
|
import { useUserContext } from "../../../Contexts/UserContext";
|
||||||
|
import {
|
||||||
|
useIssueContext,
|
||||||
|
IssueProvider
|
||||||
|
} from "../../../Contexts/IssueContext";
|
||||||
|
import useStyles from "../../../useStyles";
|
||||||
|
|
||||||
|
|
||||||
|
const MainForm = ({ id }) => {
|
||||||
|
const classes = useStyles();
|
||||||
|
const { setMessage } = useStatusContext();
|
||||||
|
const { issue, getIssue } = useIssueContext();
|
||||||
|
|
||||||
|
const {
|
||||||
|
token: {
|
||||||
|
idToken: { jwtToken: token },
|
||||||
|
},
|
||||||
|
} = useUserContext();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
(async () => {
|
||||||
|
try {
|
||||||
|
if (!id || !token) return;
|
||||||
|
await getIssue(id, token);
|
||||||
|
} catch (e) {
|
||||||
|
setMessage(e.message);
|
||||||
|
logger.warn(e.stack);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [token]);
|
||||||
|
|
||||||
|
|
||||||
|
if (issue) {
|
||||||
|
return (
|
||||||
|
<div className={clsx(classes.paper, classes.tableSize)}>
|
||||||
|
<Grid container className={classes.root} spacing={2}>
|
||||||
|
<Grid item md={12} className={classes.textCenterAlign}>
|
||||||
|
<p>
|
||||||
|
<b>ID</b>: {id}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<b>VIN</b>: {issue.vin}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<b>Title</b>: {issue.title}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<b>Description</b>: {issue.description}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<b>timestamp</b>: {issue.timestamp}
|
||||||
|
</p>
|
||||||
|
{issue.images && issue.images.map((image, index) => (
|
||||||
|
<img key={image.id} src={`data:image/png;base64, ${image.image}`} alt="Issue images" />
|
||||||
|
))}
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return <p>Loading...</p>;
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
const IssueDetails = (props) => (
|
||||||
|
<IssueProvider>
|
||||||
|
<MainForm {...props} />
|
||||||
|
</IssueProvider>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default IssueDetails;
|
||||||
21
src/components/Issues/Info/DetailsTab.jsx
Normal file
21
src/components/Issues/Info/DetailsTab.jsx
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { useParams } from "react-router";
|
||||||
|
import clsx from "clsx";
|
||||||
|
import { Typography } from "@material-ui/core";
|
||||||
|
|
||||||
|
import IssueDetails from "./Details";
|
||||||
|
import useStyles from "../../useStyles";
|
||||||
|
|
||||||
|
const IssueDetailsTab = () => {
|
||||||
|
const { id } = useParams();
|
||||||
|
const classes = useStyles();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={clsx(classes.paper, classes.tableSize)}>
|
||||||
|
<Typography variant="h6">Issue Details</Typography>
|
||||||
|
<IssueDetails id={id} classes={classes} />
|
||||||
|
</div >
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default IssueDetailsTab;
|
||||||
104
src/components/Issues/Info/index.jsx
Normal file
104
src/components/Issues/Info/index.jsx
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
import { Box, Tab, Tabs } from "@material-ui/core";
|
||||||
|
import clsx from "clsx";
|
||||||
|
import React, { useEffect, useState } from "react";
|
||||||
|
import { useParams } from "react-router";
|
||||||
|
import { useLocation } from "react-router-dom";
|
||||||
|
|
||||||
|
import { hasRole } from "../../../utils/roles";
|
||||||
|
import { useStatusContext } from "../../Contexts/StatusContext";
|
||||||
|
import { useUserContext } from "../../Contexts/UserContext";
|
||||||
|
import TabPanel from "../../Controls/TabPanel";
|
||||||
|
import useStyles from "../../useStyles";
|
||||||
|
import IssueDetailsTab from "./DetailsTab";
|
||||||
|
|
||||||
|
|
||||||
|
const tabHashes = ["details", "updates", "filters"];
|
||||||
|
|
||||||
|
const TabViews = [
|
||||||
|
{
|
||||||
|
label: "Details",
|
||||||
|
component: IssueDetailsTab,
|
||||||
|
},
|
||||||
|
|
||||||
|
];
|
||||||
|
|
||||||
|
const filterTabs = (data, groups, providers) => {
|
||||||
|
return data.reduce((result, item) => {
|
||||||
|
if (hasRole(groups, item.rolesPerProvider, providers)) {
|
||||||
|
result.push(item);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}, []);
|
||||||
|
};
|
||||||
|
|
||||||
|
const IssueInfo = () => {
|
||||||
|
const { id } = useParams();
|
||||||
|
const classes = useStyles();
|
||||||
|
const { setTitle, setSitePath } = useStatusContext();
|
||||||
|
const { hash } = useLocation();
|
||||||
|
const [tabIndex, setTabIndex] = useState(0);
|
||||||
|
const [tabs, setTabs] = useState([]);
|
||||||
|
const { groups, providers } = useUserContext();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const data = filterTabs(TabViews, groups, providers);
|
||||||
|
setTabs(data);
|
||||||
|
}, [groups, providers]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const key = hash.replace("#", "");
|
||||||
|
const index = tabHashes.findIndex((element) => element === key);
|
||||||
|
if (index >= 0) setTabIndex(index);
|
||||||
|
}, [hash]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const title = `Issue ${id} Details`;
|
||||||
|
setTitle(title);
|
||||||
|
setSitePath([
|
||||||
|
{
|
||||||
|
label: "Issues",
|
||||||
|
link: "/issues",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: title,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [id]);
|
||||||
|
|
||||||
|
const handleTabChange = (_event, newIndex) => {
|
||||||
|
setTabIndex(newIndex);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={clsx(classes.paper, classes.tableSize)}>
|
||||||
|
<Box
|
||||||
|
className={classes.tableToolbar}
|
||||||
|
sx={{ borderBottom: 1, borderColor: "divider" }}
|
||||||
|
>
|
||||||
|
<Tabs
|
||||||
|
value={tabIndex}
|
||||||
|
onChange={handleTabChange}
|
||||||
|
aria-label="issue tabs"
|
||||||
|
indicatorColor="secondary">
|
||||||
|
{tabs.map((item, index) => <Tab key={index} label={item.label} {...tabProps(index)} />)}
|
||||||
|
</Tabs>
|
||||||
|
</Box>
|
||||||
|
{tabs.map((item, index) => (
|
||||||
|
<TabPanel key={index} value={tabIndex} index={index}>
|
||||||
|
<item.component id={id} />
|
||||||
|
</TabPanel>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
function tabProps(index) {
|
||||||
|
return {
|
||||||
|
id: `tab-${index}`,
|
||||||
|
"aria-controls": `tabpanel-${index}`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default IssueInfo;
|
||||||
51
src/components/Issues/List/index.jsx
Normal file
51
src/components/Issues/List/index.jsx
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
import { Grid } from "@material-ui/core";
|
||||||
|
import clsx from "clsx";
|
||||||
|
import React, { useEffect } from "react";
|
||||||
|
|
||||||
|
import { useStatusContext } from "../../Contexts/StatusContext";
|
||||||
|
import { useUserContext } from "../../Contexts/UserContext";
|
||||||
|
import { IssueProvider } from "../../Contexts/IssueContext";
|
||||||
|
import IssueSelectionTable from "../../Controls/IssueSelectionTable";
|
||||||
|
|
||||||
|
import useStyles from "../../useStyles";
|
||||||
|
|
||||||
|
const MainForm = () => {
|
||||||
|
const classes = useStyles();
|
||||||
|
const { setTitle, setSitePath } = useStatusContext();
|
||||||
|
const {
|
||||||
|
token: {
|
||||||
|
idToken: { jwtToken: token },
|
||||||
|
},
|
||||||
|
} = useUserContext();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setTitle("Issues");
|
||||||
|
setSitePath([]);
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={clsx(classes.paper, classes.tableSize)}>
|
||||||
|
<Grid container className={classes.root} spacing={2}>
|
||||||
|
<Grid item md={4} className={classes.textJustifyAlign}>
|
||||||
|
</Grid>
|
||||||
|
<Grid item md={2} className={classes.textRightAlign} />
|
||||||
|
</Grid>
|
||||||
|
<IssueSelectionTable
|
||||||
|
classes={classes}
|
||||||
|
token={token}
|
||||||
|
multiSelect={false}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const IssuesList = () => (
|
||||||
|
<IssueProvider>
|
||||||
|
<MainForm />
|
||||||
|
</IssueProvider>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default IssuesList;
|
||||||
@@ -4,6 +4,7 @@ import BuildIcon from "@material-ui/icons/Build";
|
|||||||
import CloudDownloadIcon from "@material-ui/icons/CloudDownload";
|
import CloudDownloadIcon from "@material-ui/icons/CloudDownload";
|
||||||
import CommuteIcon from "@material-ui/icons/Commute";
|
import CommuteIcon from "@material-ui/icons/Commute";
|
||||||
import DirectionsCarIcon from "@material-ui/icons/DirectionsCar";
|
import DirectionsCarIcon from "@material-ui/icons/DirectionsCar";
|
||||||
|
import BugReportIcon from "@material-ui/icons/BugReport";
|
||||||
import HomeIcon from "@material-ui/icons/Home";
|
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";
|
||||||
@@ -32,6 +33,12 @@ const menuData = [
|
|||||||
icon: <DirectionsCarIcon />,
|
icon: <DirectionsCarIcon />,
|
||||||
rolesPerProvider: Permissions.FiskerMagnaRead,
|
rolesPerProvider: Permissions.FiskerMagnaRead,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: "Issues",
|
||||||
|
to: "/issues",
|
||||||
|
icon: <BugReportIcon />,
|
||||||
|
rolesPerProvider: Permissions.FiskerRead,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: "Fleets",
|
label: "Fleets",
|
||||||
to: "/fleets",
|
to: "/fleets",
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ jest.mock("../../services/superset");
|
|||||||
import { render, screen, waitFor } from "@testing-library/react";
|
import { render, screen, waitFor } from "@testing-library/react";
|
||||||
import { BrowserRouter } from "react-router-dom";
|
import { BrowserRouter } from "react-router-dom";
|
||||||
import addSnapshotSerializer from "../../utils/snapshot";
|
import addSnapshotSerializer from "../../utils/snapshot";
|
||||||
import { TEST_AUTH_OBJECT_FISKER } from "../../utils/testing";
|
import { TEST_AUTH_OBJECT_FISKER, TEST_AUTH_OBJECT_MAGNA } from "../../utils/testing";
|
||||||
import { setToken, UserProvider } from "../Contexts/UserContext";
|
import { setToken, UserProvider } from "../Contexts/UserContext";
|
||||||
import SideMenu from "./SideMenu";
|
import SideMenu from "./SideMenu";
|
||||||
|
|
||||||
@@ -39,4 +39,13 @@ describe("SideMenu", () => {
|
|||||||
});
|
});
|
||||||
expect(container).toMatchSnapshot();
|
expect(container).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("Magna Authenticated", async () => {
|
||||||
|
setToken(TEST_AUTH_OBJECT_MAGNA);
|
||||||
|
const container = await renderMenu();
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByText("Tools")).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
expect(container).toMatchSnapshot();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -116,6 +116,42 @@ exports[`SideMenu Authenticated 1`] = `
|
|||||||
/>
|
/>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
aria-disabled="false"
|
||||||
|
class="MuiButtonBase-root MuiListItem-root MuiListItem-gutters MuiListItem-button"
|
||||||
|
href="/issues"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiListItemIcon-root"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M20 8h-2.81c-.45-.78-1.07-1.45-1.82-1.96L17 4.41 15.59 3l-2.17 2.17C12.96 5.06 12.49 5 12 5c-.49 0-.96.06-1.41.17L8.41 3 7 4.41l1.62 1.63C7.88 6.55 7.26 7.22 6.81 8H4v2h2.09c-.05.33-.09.66-.09 1v1H4v2h2v1c0 .34.04.67.09 1H4v2h2.81c1.04 1.79 2.97 3 5.19 3s4.15-1.21 5.19-3H20v-2h-2.09c.05-.33.09-.66.09-1v-1h2v-2h-2v-1c0-.34-.04-.67-.09-1H20V8zm-6 8h-4v-2h4v2zm0-4h-4v-2h4v2z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="MuiListItemText-root"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="MuiTypography-root MuiListItemText-primary MuiTypography-body1 MuiTypography-displayBlock"
|
||||||
|
>
|
||||||
|
Issues
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<span
|
||||||
|
class="MuiTouchRipple-root"
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
aria-disabled="false"
|
aria-disabled="false"
|
||||||
@@ -340,6 +376,191 @@ exports[`SideMenu Authenticated 1`] = `
|
|||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`SideMenu Magna Authenticated 1`] = `
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
data-testid="mocked-userprovider"
|
||||||
|
>
|
||||||
|
<ul
|
||||||
|
class="MuiList-root MuiList-padding"
|
||||||
|
>
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
aria-disabled="false"
|
||||||
|
class="MuiButtonBase-root MuiListItem-root MuiListItem-gutters MuiListItem-button"
|
||||||
|
href="/home"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiListItemIcon-root"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="MuiListItemText-root"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="MuiTypography-root MuiListItemText-primary MuiTypography-body1 MuiTypography-displayBlock"
|
||||||
|
>
|
||||||
|
Home
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<span
|
||||||
|
class="MuiTouchRipple-root"
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
aria-disabled="false"
|
||||||
|
class="MuiButtonBase-root MuiListItem-root MuiListItem-gutters MuiListItem-button"
|
||||||
|
href="/packages"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiListItemIcon-root"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M19.35 10.04C18.67 6.59 15.64 4 12 4 9.11 4 6.6 5.64 5.35 8.04 2.34 8.36 0 10.91 0 14c0 3.31 2.69 6 6 6h13c2.76 0 5-2.24 5-5 0-2.64-2.05-4.78-4.65-4.96zM17 13l-5 5-5-5h3V9h4v4h3z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="MuiListItemText-root"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="MuiTypography-root MuiListItemText-primary MuiTypography-body1 MuiTypography-displayBlock"
|
||||||
|
>
|
||||||
|
Deployments
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<span
|
||||||
|
class="MuiTouchRipple-root"
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
aria-disabled="false"
|
||||||
|
class="MuiButtonBase-root MuiListItem-root MuiListItem-gutters MuiListItem-button"
|
||||||
|
href="/vehicles"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiListItemIcon-root"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M18.92 6.01C18.72 5.42 18.16 5 17.5 5h-11c-.66 0-1.21.42-1.42 1.01L3 12v8c0 .55.45 1 1 1h1c.55 0 1-.45 1-1v-1h12v1c0 .55.45 1 1 1h1c.55 0 1-.45 1-1v-8l-2.08-5.99zM6.5 16c-.83 0-1.5-.67-1.5-1.5S5.67 13 6.5 13s1.5.67 1.5 1.5S7.33 16 6.5 16zm11 0c-.83 0-1.5-.67-1.5-1.5s.67-1.5 1.5-1.5 1.5.67 1.5 1.5-.67 1.5-1.5 1.5zM5 11l1.5-4.5h11L19 11H5z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="MuiListItemText-root"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="MuiTypography-root MuiListItemText-primary MuiTypography-body1 MuiTypography-displayBlock"
|
||||||
|
>
|
||||||
|
Vehicles
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<span
|
||||||
|
class="MuiTouchRipple-root"
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<span>
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
aria-disabled="false"
|
||||||
|
class="MuiButtonBase-root MuiListItem-root MuiListItem-gutters MuiListItem-button"
|
||||||
|
href="/tools/certificates/add"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiListItemIcon-root"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M22.7 19l-9.1-9.1c.9-2.3.4-5-1.5-6.9-2-2-5-2.4-7.4-1.3L9 6 6 9 1.6 4.7C.4 7.1.9 10.1 2.9 12.1c1.9 1.9 4.6 2.4 6.9 1.5l9.1 9.1c.4.4 1 .4 1.4 0l2.3-2.3c.5-.4.5-1.1.1-1.4z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="MuiListItemText-root"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="MuiTypography-root MuiListItemText-primary MuiTypography-body1 MuiTypography-displayBlock"
|
||||||
|
>
|
||||||
|
Tools
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<span
|
||||||
|
class="MuiTouchRipple-root"
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</span>
|
||||||
|
<ul
|
||||||
|
style="margin-left: 50px;"
|
||||||
|
>
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
aria-disabled="false"
|
||||||
|
class="MuiButtonBase-root MuiListItem-root MuiListItem-gutters MuiListItem-button"
|
||||||
|
href="/tools/certificates/add"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiListItemText-root"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="MuiTypography-root MuiListItemText-primary MuiTypography-body1 MuiTypography-displayBlock"
|
||||||
|
>
|
||||||
|
Certificate
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<span
|
||||||
|
class="MuiTouchRipple-root"
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`SideMenu Unauthenticated 1`] = `
|
exports[`SideMenu Unauthenticated 1`] = `
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -4,11 +4,13 @@ exports[`Magna Security DLL page Security DLL Result page 1`] = `
|
|||||||
<div>
|
<div>
|
||||||
<form>
|
<form>
|
||||||
<p>
|
<p>
|
||||||
Click to download your certificates and security.dll.
|
Please click the links below to download the certificate.pem, key.pem and the security.dll (either the 32-bit or 64-bit version to match your application). Install all files in the same folder, and then run your application of choice to connect to the Fisker cloud.
|
||||||
<br />
|
</p>
|
||||||
|
<p>
|
||||||
<strong>
|
<strong>
|
||||||
DLL will not work unless certificate.pem and key.pem are placed next to the DLL
|
Important Note:
|
||||||
</strong>
|
</strong>
|
||||||
|
Certificates expire in one month from the time they are generated. They have to be re-downloaded again to connect to the Fisker cloud.
|
||||||
</p>
|
</p>
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
|
|||||||
@@ -8,7 +8,8 @@ const CertMimeType = "application/x-pem-file";
|
|||||||
const Result = ({ public_key, private_key }) => (
|
const Result = ({ public_key, private_key }) => (
|
||||||
<>
|
<>
|
||||||
<form>
|
<form>
|
||||||
<p>Click to download your certificates and security.dll.<br /><strong>DLL will not work unless certificate.pem and key.pem are placed next to the DLL</strong></p>
|
<p>Please click the links below to download the certificate.pem, key.pem and the security.dll (either the 32-bit or 64-bit version to match your application). Install all files in the same folder, and then run your application of choice to connect to the Fisker cloud.</p>
|
||||||
|
<p><strong>Important Note:</strong> Certificates expire in one month from the time they are generated. They have to be re-downloaded again to connect to the Fisker cloud.</p>
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
<DownloadFileLink
|
<DownloadFileLink
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ import { AuthRoute, TYPES } from "../Routes/AuthRoute";
|
|||||||
|
|
||||||
const CANFilterCreate = React.lazy(() => import("../CANFilter/Add"));
|
const CANFilterCreate = React.lazy(() => import("../CANFilter/Add"));
|
||||||
const CANFilterUpdate = React.lazy(() => import("../CANFilter/Update"));
|
const CANFilterUpdate = React.lazy(() => import("../CANFilter/Update"));
|
||||||
|
const IssuesList = React.lazy(() => import("../Issues/List"))
|
||||||
|
const IssueInfo = React.lazy(() => import("../Issues/Info"))
|
||||||
const CarsList = React.lazy(() => import("../Cars/List"));
|
const CarsList = React.lazy(() => import("../Cars/List"));
|
||||||
const CarStatus = React.lazy(() => import("../Cars/Status"));
|
const CarStatus = React.lazy(() => import("../Cars/Status"));
|
||||||
const CarUpdateStatus = React.lazy(() => import("../Cars/UpdateStatus"));
|
const CarUpdateStatus = React.lazy(() => import("../Cars/UpdateStatus"));
|
||||||
@@ -175,6 +177,24 @@ const SiteRoutes = () => {
|
|||||||
rolesPerGroup={Permissions.FiskerRead}
|
rolesPerGroup={Permissions.FiskerRead}
|
||||||
providers={providers}
|
providers={providers}
|
||||||
/>
|
/>
|
||||||
|
<AuthRoute
|
||||||
|
path="/issues"
|
||||||
|
render={() => <IssuesList />}
|
||||||
|
type={TYPES.PROTECTED}
|
||||||
|
token={token}
|
||||||
|
groups={groups}
|
||||||
|
rolesPerGroup={Permissions.FiskerRead}
|
||||||
|
providers={providers}
|
||||||
|
/>
|
||||||
|
<AuthRoute
|
||||||
|
path="/issue-info/:id"
|
||||||
|
render={() => <IssueInfo />}
|
||||||
|
type={TYPES.PROTECTED}
|
||||||
|
token={token}
|
||||||
|
groups={groups}
|
||||||
|
rolesPerGroup={Permissions.FiskerRead}
|
||||||
|
providers={providers}
|
||||||
|
/>
|
||||||
<AuthRoute
|
<AuthRoute
|
||||||
path="/vehicles"
|
path="/vehicles"
|
||||||
render={() => <CarsList />}
|
render={() => <CarsList />}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useState, useEffect } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { useUserContext } from "../Contexts/UserContext";
|
import { useUserContext } from "../Contexts/UserContext";
|
||||||
|
|
||||||
import supersetAPI from "../../services/superset";
|
import supersetAPI from "../../services/superset";
|
||||||
@@ -29,10 +29,11 @@ const SupersetDashboardList = () => {
|
|||||||
|
|
||||||
internalEffect(token)
|
internalEffect(token)
|
||||||
}
|
}
|
||||||
|
return () => {
|
||||||
|
setDashboardList([]);
|
||||||
|
}
|
||||||
}, [groups, token])
|
}, [groups, token])
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ul style={{ marginLeft: 50 }}>
|
<ul style={{ marginLeft: 50 }}>
|
||||||
{dashboardList.map((subitem, index) => (
|
{dashboardList.map((subitem, index) => (
|
||||||
|
|||||||
@@ -193,6 +193,7 @@ const Component = () => {
|
|||||||
location={carState.location}
|
location={carState.location}
|
||||||
windows={carState.windows}
|
windows={carState.windows}
|
||||||
trex_version={carState.trex_version}
|
trex_version={carState.trex_version}
|
||||||
|
ip={carState.ip}
|
||||||
updated={carState.updated}
|
updated={carState.updated}
|
||||||
className={classes.popup}
|
className={classes.popup}
|
||||||
onClose={handleClose}
|
onClose={handleClose}
|
||||||
|
|||||||
11
src/services/__mocks__/issueAPI.js
Normal file
11
src/services/__mocks__/issueAPI.js
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
const issueAPI = {
|
||||||
|
getIssues: async (token) => {
|
||||||
|
return { "data": [{ "id": 18, "vin": "1GNGC26RXXJ407648", "title": "sometitle", "description": "2343242", "driver_id": "valid-cognito-id-1", "timestamp": "2022-12-09T23:16:38.074858Z" }, { "id": 19, "vin": "1GNGC26RXXJ407648", "title": "sometitle", "description": "2343242", "driver_id": "valid-cognito-id-1", "timestamp": "2022-12-09T23:16:38.074858Z" }, { "id": 20, "vin": "1GNGC26RXXJ407648", "title": "sometitle", "description": "2343242", "driver_id": "valid-cognito-id-1", "timestamp": "2022-12-09T23:16:38.074858Z" }, { "id": 21, "vin": "1GNGC26RXXJ407648", "title": "sometitle", "description": "2343242", "driver_id": "valid-cognito-id-1", "timestamp": "2022-12-09T23:16:38.074858Z" }, { "id": 22, "vin": "1GNGC26RXXJ407648", "title": "sometitle", "description": "2343242", "driver_id": "valid-cognito-id-1", "timestamp": "2022-12-09T23:16:38.074858Z" }, { "id": 25, "vin": "1GNGC26RXXJ407648", "title": "Example HMI Problem", "description": "HMI blue screen", "driver_id": "0b6b1930-b20a-4fce-967a-efac6a01fd10", "timestamp": "2022-12-19T22:25:03.848855Z" }, { "id": 26, "vin": "1GNGC26RXXJ407648", "title": "sometitle", "description": "2343242", "driver_id": "valid-cognito-id-1", "timestamp": "2022-12-09T23:16:38.074858Z" }, { "id": 27, "vin": "1GNGC26RXXJ407648", "title": "sometitle", "description": "2343242", "driver_id": "valid-cognito-id-1", "timestamp": "2022-12-09T23:16:38.074858Z" }, { "id": 28, "vin": "1GNGC26RXXJ407648", "title": "sometitle", "description": "2343242", "driver_id": "valid-cognito-id-1", "timestamp": "2022-12-09T23:16:38.074858Z" }], "total": 9 }
|
||||||
|
},
|
||||||
|
getIssue: async (token) => {
|
||||||
|
return { "data": { "id": 18, "vin": "1GNGC26RXXJ407648", "title": "sometitle", "description": "2343242", "driver_id": "valid-cognito-id-1", "timestamp": "2022-12-09T23:16:38.074858Z", "images": [{ "id": 15, "image": "SGVsbG8x", "issue_id": 18 }] } }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export default issueAPI;
|
||||||
@@ -1,4 +1,7 @@
|
|||||||
const SupersetAPI = {
|
const SupersetAPI = {
|
||||||
|
getGuestToken: async () => {
|
||||||
|
return ""
|
||||||
|
},
|
||||||
getEmbeddedDashboards: async () => {
|
getEmbeddedDashboards: async () => {
|
||||||
return [{
|
return [{
|
||||||
title: "test title",
|
title: "test title",
|
||||||
@@ -7,7 +10,8 @@ const SupersetAPI = {
|
|||||||
},
|
},
|
||||||
SupersetDashboardID: () => {
|
SupersetDashboardID: () => {
|
||||||
return "11111100-0000-1111-1111-000000000000"
|
return "11111100-0000-1111-1111-000000000000"
|
||||||
}
|
},
|
||||||
|
SupersetDashboardURL: () => (null),
|
||||||
}
|
}
|
||||||
|
|
||||||
export default SupersetAPI
|
export default SupersetAPI
|
||||||
@@ -59,6 +59,9 @@ const suppliersAPI = {
|
|||||||
if (index >= 0) data[index] = supplier;
|
if (index >= 0) data[index] = supplier;
|
||||||
return supplier;
|
return supplier;
|
||||||
},
|
},
|
||||||
|
getManufactureCert: async () => {
|
||||||
|
return {public_key:"-----BEGIN CERTIFICATE-----\nTEST\n-----END CERTIFICATE-----",private_key:"-----BEGIN RSA PRIVATE KEY-----\nTEST\n-----END RSA PRIVATE KEY-----","serial_number":"66:c8:45:20:bd:75:04:79:e8:5e:0e:46:5b:5c:1a:21:8b:ea:81:9f","type":"rsa"}
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default suppliersAPI;
|
export default suppliersAPI;
|
||||||
|
|||||||
45
src/services/issueAPI.js
Normal file
45
src/services/issueAPI.js
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
import {
|
||||||
|
addQueryParams, errorHandler, fetchRespHandler, getAuthHeaderOptions
|
||||||
|
} from "../utils/http";
|
||||||
|
|
||||||
|
const API_ENDPOINT = process.env.REACT_APP_OTA_SERVICE_URL;
|
||||||
|
|
||||||
|
const issuesAPI = {
|
||||||
|
deleteIssue: async (id, token) =>
|
||||||
|
fetch(`${API_ENDPOINT}/issues/${id}`, {
|
||||||
|
method: "DELETE",
|
||||||
|
headers: Object.assign(
|
||||||
|
{ "Content-Type": "application/json" },
|
||||||
|
getAuthHeaderOptions(token)
|
||||||
|
),
|
||||||
|
})
|
||||||
|
.then(fetchRespHandler)
|
||||||
|
.catch(errorHandler),
|
||||||
|
|
||||||
|
getIssue: async (id, token) =>
|
||||||
|
fetch(`${API_ENDPOINT}/issues/${id}`, {
|
||||||
|
method: "GET",
|
||||||
|
headers: Object.assign(
|
||||||
|
{ "Content-Type": "application/json" },
|
||||||
|
getAuthHeaderOptions(token)
|
||||||
|
),
|
||||||
|
})
|
||||||
|
.then(fetchRespHandler)
|
||||||
|
.catch(errorHandler),
|
||||||
|
|
||||||
|
getIssues: async (search, token) => {
|
||||||
|
const u = addQueryParams(`${API_ENDPOINT}/issues`, search);
|
||||||
|
return fetch(u, {
|
||||||
|
method: "GET",
|
||||||
|
headers: Object.assign(
|
||||||
|
{ "Content-Type": "application/json" },
|
||||||
|
getAuthHeaderOptions(token)
|
||||||
|
),
|
||||||
|
})
|
||||||
|
.then(fetchRespHandler)
|
||||||
|
.catch(errorHandler);
|
||||||
|
},
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
export default issuesAPI;
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
addQueryParams, getAuthHeaderOptions
|
addQueryParams, getAuthHeaderOptions
|
||||||
} from "../utils/http";
|
} from "../utils/http";
|
||||||
|
|
||||||
//Added the token we got from the first authorization and set it as the auth token, and that allowed us to hit the request
|
//Added the token we got from the first authorization and set it as the auth token, and that allowed us to hit the request
|
||||||
|
|||||||
Reference in New Issue
Block a user