CEC-4640: add bulk add to fleet (#384)
* refactor bulkactions component * refactor bulk actions * update dom tests * add addToFleet hook * make signal optional * implement code splitting * add deps * remove test label
This commit is contained in:
@@ -9,7 +9,6 @@ jest.mock("../../services/vehiclesAPI");
|
|||||||
jest.mock("../../services/superset");
|
jest.mock("../../services/superset");
|
||||||
jest.mock("../../services/suppliersAPI");
|
jest.mock("../../services/suppliersAPI");
|
||||||
jest.mock("../../services/issueAPI");
|
jest.mock("../../services/issueAPI");
|
||||||
jest.mock("../TransformModal");
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
act, cleanup, render,
|
act, cleanup, render,
|
||||||
|
|||||||
@@ -6314,6 +6314,7 @@ exports[`App Route /packages authenticated 1`] = `
|
|||||||
aria-haspopup="menu"
|
aria-haspopup="menu"
|
||||||
aria-label="select action"
|
aria-label="select action"
|
||||||
class="MuiButtonBase-root MuiButton-root MuiButton-contained MuiButton-containedPrimary MuiButton-sizeSmall MuiButton-containedSizeSmall MuiButtonGroup-grouped MuiButtonGroup-groupedHorizontal MuiButtonGroup-groupedContained MuiButtonGroup-groupedContainedHorizontal MuiButtonGroup-groupedContainedPrimary MuiButton-root MuiButton-contained MuiButton-containedPrimary MuiButton-sizeSmall MuiButton-containedSizeSmall MuiButtonGroup-grouped MuiButtonGroup-groupedHorizontal MuiButtonGroup-groupedContained MuiButtonGroup-groupedContainedHorizontal MuiButtonGroup-groupedContainedPrimary css-11qr2p8-MuiButtonBase-root-MuiButton-root"
|
class="MuiButtonBase-root MuiButton-root MuiButton-contained MuiButton-containedPrimary MuiButton-sizeSmall MuiButton-containedSizeSmall MuiButtonGroup-grouped MuiButtonGroup-groupedHorizontal MuiButtonGroup-groupedContained MuiButtonGroup-groupedContainedHorizontal MuiButtonGroup-groupedContainedPrimary MuiButton-root MuiButton-contained MuiButton-containedPrimary MuiButton-sizeSmall MuiButton-containedSizeSmall MuiButtonGroup-grouped MuiButtonGroup-groupedHorizontal MuiButtonGroup-groupedContained MuiButtonGroup-groupedContainedHorizontal MuiButtonGroup-groupedContainedPrimary css-11qr2p8-MuiButtonBase-root-MuiButton-root"
|
||||||
|
data-testid="dropdown-button-expand"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
@@ -12296,17 +12297,20 @@ exports[`App Route /vehicles authenticated 1`] = `
|
|||||||
role="group"
|
role="group"
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
class="MuiButtonBase-root MuiButton-root MuiButton-contained MuiButton-containedPrimary MuiButton-sizeMedium MuiButton-containedSizeMedium Mui-disabled MuiButtonGroup-grouped MuiButtonGroup-groupedHorizontal MuiButtonGroup-groupedContained MuiButtonGroup-groupedContainedHorizontal MuiButtonGroup-groupedContainedPrimary MuiButton-root MuiButton-contained MuiButton-containedPrimary MuiButton-sizeMedium MuiButton-containedSizeMedium MuiButtonGroup-grouped MuiButtonGroup-groupedHorizontal MuiButtonGroup-groupedContained MuiButtonGroup-groupedContainedHorizontal MuiButtonGroup-groupedContainedPrimary css-sghohy-MuiButtonBase-root-MuiButton-root"
|
class="MuiButtonBase-root MuiButton-root MuiButton-contained MuiButton-containedPrimary MuiButton-sizeMedium MuiButton-containedSizeMedium MuiButtonGroup-grouped MuiButtonGroup-groupedHorizontal MuiButtonGroup-groupedContained MuiButtonGroup-groupedContainedHorizontal MuiButtonGroup-groupedContainedPrimary MuiButton-root MuiButton-contained MuiButton-containedPrimary MuiButton-sizeMedium MuiButton-containedSizeMedium MuiButtonGroup-grouped MuiButtonGroup-groupedHorizontal MuiButtonGroup-groupedContained MuiButtonGroup-groupedContainedHorizontal MuiButtonGroup-groupedContainedPrimary css-sghohy-MuiButtonBase-root-MuiButton-root"
|
||||||
disabled=""
|
tabindex="0"
|
||||||
tabindex="-1"
|
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
Update Configs
|
Add Tags
|
||||||
|
<span
|
||||||
|
class="MuiTouchRipple-root css-8je8zh-MuiTouchRipple-root"
|
||||||
|
/>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
aria-haspopup="menu"
|
aria-haspopup="menu"
|
||||||
aria-label="select action"
|
aria-label="select action"
|
||||||
class="MuiButtonBase-root MuiButton-root MuiButton-contained MuiButton-containedPrimary MuiButton-sizeSmall MuiButton-containedSizeSmall MuiButtonGroup-grouped MuiButtonGroup-groupedHorizontal MuiButtonGroup-groupedContained MuiButtonGroup-groupedContainedHorizontal MuiButtonGroup-groupedContainedPrimary MuiButton-root MuiButton-contained MuiButton-containedPrimary MuiButton-sizeSmall MuiButton-containedSizeSmall MuiButtonGroup-grouped MuiButtonGroup-groupedHorizontal MuiButtonGroup-groupedContained MuiButtonGroup-groupedContainedHorizontal MuiButtonGroup-groupedContainedPrimary css-11qr2p8-MuiButtonBase-root-MuiButton-root"
|
class="MuiButtonBase-root MuiButton-root MuiButton-contained MuiButton-containedPrimary MuiButton-sizeSmall MuiButton-containedSizeSmall MuiButtonGroup-grouped MuiButtonGroup-groupedHorizontal MuiButtonGroup-groupedContained MuiButtonGroup-groupedContainedHorizontal MuiButtonGroup-groupedContainedPrimary MuiButton-root MuiButton-contained MuiButton-containedPrimary MuiButton-sizeSmall MuiButton-containedSizeSmall MuiButtonGroup-grouped MuiButtonGroup-groupedHorizontal MuiButtonGroup-groupedContained MuiButtonGroup-groupedContainedHorizontal MuiButtonGroup-groupedContainedPrimary css-11qr2p8-MuiButtonBase-root-MuiButton-root"
|
||||||
|
data-testid="dropdown-button-expand"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
@@ -12737,13 +12741,6 @@ exports[`App Route /vehicles authenticated 1`] = `
|
|||||||
</tfoot>
|
</tfoot>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<div
|
|
||||||
data-testid="transform-modal"
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
data-testid="transform-modal"
|
|
||||||
/>
|
|
||||||
<div />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|||||||
44
src/components/BulkActions/Modal.jsx
Normal file
44
src/components/BulkActions/Modal.jsx
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Dialog,
|
||||||
|
DialogTitle,
|
||||||
|
DialogActions,
|
||||||
|
DialogContent,
|
||||||
|
} from '@material-ui/core';
|
||||||
|
|
||||||
|
export const Modal = ({
|
||||||
|
open,
|
||||||
|
close,
|
||||||
|
submit,
|
||||||
|
title,
|
||||||
|
children,
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<Dialog
|
||||||
|
open={open}
|
||||||
|
onClose={close}
|
||||||
|
>
|
||||||
|
<DialogTitle id="alert-dialog-title">
|
||||||
|
{title}
|
||||||
|
</DialogTitle>
|
||||||
|
<DialogContent>
|
||||||
|
{children}
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button
|
||||||
|
onClick={close}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
color="secondary"
|
||||||
|
onClick={submit}
|
||||||
|
autoFocus
|
||||||
|
>
|
||||||
|
Submit
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
)
|
||||||
|
}
|
||||||
53
src/components/BulkActions/__snapshots__/index.test.jsx.snap
Normal file
53
src/components/BulkActions/__snapshots__/index.test.jsx.snap
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`BulkActions Render 1`] = `
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
data-testid="mocked-statusprovider"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
data-testid="mocked-userprovider"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
aria-label="choose action"
|
||||||
|
class="MuiButtonGroup-root MuiButtonGroup-contained css-zqcytf-MuiButtonGroup-root"
|
||||||
|
role="group"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="MuiButtonBase-root MuiButton-root MuiButton-contained MuiButton-containedPrimary MuiButton-sizeMedium MuiButton-containedSizeMedium MuiButtonGroup-grouped MuiButtonGroup-groupedHorizontal MuiButtonGroup-groupedContained MuiButtonGroup-groupedContainedHorizontal MuiButtonGroup-groupedContainedPrimary MuiButton-root MuiButton-contained MuiButton-containedPrimary MuiButton-sizeMedium MuiButton-containedSizeMedium MuiButtonGroup-grouped MuiButtonGroup-groupedHorizontal MuiButtonGroup-groupedContained MuiButtonGroup-groupedContainedHorizontal MuiButtonGroup-groupedContainedPrimary css-sghohy-MuiButtonBase-root-MuiButton-root"
|
||||||
|
tabindex="0"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
Add Tags
|
||||||
|
<span
|
||||||
|
class="MuiTouchRipple-root css-8je8zh-MuiTouchRipple-root"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
aria-haspopup="menu"
|
||||||
|
aria-label="select action"
|
||||||
|
class="MuiButtonBase-root MuiButton-root MuiButton-contained MuiButton-containedPrimary MuiButton-sizeSmall MuiButton-containedSizeSmall MuiButtonGroup-grouped MuiButtonGroup-groupedHorizontal MuiButtonGroup-groupedContained MuiButtonGroup-groupedContainedHorizontal MuiButtonGroup-groupedContainedPrimary MuiButton-root MuiButton-contained MuiButton-containedPrimary MuiButton-sizeSmall MuiButton-containedSizeSmall MuiButtonGroup-grouped MuiButtonGroup-groupedHorizontal MuiButtonGroup-groupedContained MuiButtonGroup-groupedContainedHorizontal MuiButtonGroup-groupedContainedPrimary css-11qr2p8-MuiButtonBase-root-MuiButton-root"
|
||||||
|
data-testid="dropdown-button-expand"
|
||||||
|
tabindex="0"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root MuiSvgIcon-fontSizeMedium css-i4bv87-MuiSvgIcon-root"
|
||||||
|
data-testid="ArrowDropDownIcon"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="m7 10 5 5 5-5z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<span
|
||||||
|
class="MuiTouchRipple-root css-8je8zh-MuiTouchRipple-root"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
43
src/components/BulkActions/actions/AddTags.jsx
Normal file
43
src/components/BulkActions/actions/AddTags.jsx
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
import { useState, forwardRef, useImperativeHandle } from "react";
|
||||||
|
import { useStatusContext } from "../../Contexts/StatusContext";
|
||||||
|
import { useUserContext } from "../../Contexts/UserContext";
|
||||||
|
import TextInputList from "../../Controls/TextInputList";
|
||||||
|
import vehiclesAPI from "../../../services/vehiclesAPI";
|
||||||
|
|
||||||
|
export default forwardRef(({
|
||||||
|
vins,
|
||||||
|
vinCSV,
|
||||||
|
}, ref) => {
|
||||||
|
const { setMessage } = useStatusContext();
|
||||||
|
const { token: { idToken: { jwtToken: token } } } = useUserContext();
|
||||||
|
|
||||||
|
const [tags, setTags] = useState([]);
|
||||||
|
|
||||||
|
useImperativeHandle(ref, () => ({
|
||||||
|
async submit() {
|
||||||
|
return vehiclesAPI
|
||||||
|
.addTags(vins, tags, token)
|
||||||
|
.then((data) => {
|
||||||
|
if (data.error) {
|
||||||
|
setMessage(`${data.error}: ${data.message}`);
|
||||||
|
} else if (data.tags && data.vins) {
|
||||||
|
setMessage(`Added ${data.tags.length} tags to ${data.vins.length} vehicles.`);
|
||||||
|
} else {
|
||||||
|
setMessage(JSON.stringify(data));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
You are adding tags to the following VINs: {vinCSV}.
|
||||||
|
</p>
|
||||||
|
<TextInputList
|
||||||
|
label="Tags"
|
||||||
|
onChange={setTags}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
51
src/components/BulkActions/actions/AddTags.test.jsx
Normal file
51
src/components/BulkActions/actions/AddTags.test.jsx
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
jest.mock("../../Contexts/UserContext");
|
||||||
|
jest.mock("../../Contexts/StatusContext");
|
||||||
|
jest.mock("../../../services/vehiclesAPI");
|
||||||
|
|
||||||
|
import React, { useState } from "react";
|
||||||
|
import {
|
||||||
|
render,
|
||||||
|
act,
|
||||||
|
} from "@testing-library/react";
|
||||||
|
import { UserProvider, setToken } from "../../Contexts/UserContext";
|
||||||
|
import { StatusProvider } from "../../Contexts/StatusContext";
|
||||||
|
import { TEST_AUTH_OBJECT_FISKER } from "../../../utils/testing";
|
||||||
|
import AddTags from "./AddTags";
|
||||||
|
import vehiclesAPI from "../../../services/vehiclesAPI";
|
||||||
|
|
||||||
|
jest.mock('react', () => ({
|
||||||
|
...jest.requireActual('react'),
|
||||||
|
useState: jest.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock('../../Controls/TextInputList', () => {
|
||||||
|
const React = require('react');
|
||||||
|
return () => <div data-testid="mock-text-input-list" />;
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("BulkActions/AddTags", () => {
|
||||||
|
beforeAll(() => {
|
||||||
|
setToken(TEST_AUTH_OBJECT_FISKER);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("makes request to update the config of multiple vehicles", async () => {
|
||||||
|
useState.mockReturnValue([["myTag"], jest.fn()]);
|
||||||
|
const api = jest.spyOn(vehiclesAPI, "addTags");
|
||||||
|
const ref = React.createRef();
|
||||||
|
|
||||||
|
render(
|
||||||
|
<StatusProvider>
|
||||||
|
<UserProvider>
|
||||||
|
<AddTags
|
||||||
|
ref={ref}
|
||||||
|
vins={["TESTVIN123456789a"]}
|
||||||
|
vinCSV=""
|
||||||
|
/>
|
||||||
|
</UserProvider>
|
||||||
|
</StatusProvider>
|
||||||
|
);
|
||||||
|
|
||||||
|
await act(async () => ref.current.submit());
|
||||||
|
expect(api).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
99
src/components/BulkActions/actions/AddToFleet.jsx
Normal file
99
src/components/BulkActions/actions/AddToFleet.jsx
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
import { useEffect, useState, forwardRef, useImperativeHandle } from "react";
|
||||||
|
import {
|
||||||
|
FormControl,
|
||||||
|
InputLabel,
|
||||||
|
MenuItem,
|
||||||
|
Select,
|
||||||
|
} from '@material-ui/core';
|
||||||
|
import { useStatusContext } from "../../Contexts/StatusContext";
|
||||||
|
import { useUserContext } from "../../Contexts/UserContext";
|
||||||
|
import fleetsAPI from "../../../services/fleetsAPI";
|
||||||
|
|
||||||
|
export default forwardRef(({
|
||||||
|
vins,
|
||||||
|
vinCSV,
|
||||||
|
}, ref) => {
|
||||||
|
const { setMessage } = useStatusContext();
|
||||||
|
const { token: { idToken: { jwtToken: token } } } = useUserContext();
|
||||||
|
|
||||||
|
const [fleet, setFleet] = useState("");
|
||||||
|
const [options, setOptions] = useState([]);
|
||||||
|
|
||||||
|
useImperativeHandle(ref, () => ({
|
||||||
|
async submit() {
|
||||||
|
if (!fleet) {
|
||||||
|
setMessage(`Select a valid fleet, "${fleet}" is invalid.`);
|
||||||
|
return Promise.reject("Invalid Fleet");
|
||||||
|
}
|
||||||
|
|
||||||
|
return fleetsAPI
|
||||||
|
.addFleetVehicles(fleet, { vins }, token)
|
||||||
|
.then((response) => {
|
||||||
|
if (response.error) {
|
||||||
|
setMessage(`${response.error}: ${response.message}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.vins) {
|
||||||
|
setMessage(`Added ${response.vins.length} vehicles to ${fleet}.`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setMessage(`Something unexpected happened while attempting to add vehicles to fleet.`);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
setMessage(JSON.stringify(error));
|
||||||
|
});
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const controller = new AbortController();
|
||||||
|
let isMounted = true;
|
||||||
|
|
||||||
|
fleetsAPI
|
||||||
|
.getFleets({
|
||||||
|
search: "",
|
||||||
|
limit: 10,
|
||||||
|
offset: 0,
|
||||||
|
order: `id desc`,
|
||||||
|
}, token, controller)
|
||||||
|
.then(({ data }) => {
|
||||||
|
if (isMounted) {
|
||||||
|
setOptions(data.map((fleet) => fleet.name));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return () => {
|
||||||
|
controller?.abort();
|
||||||
|
isMounted = false;
|
||||||
|
}
|
||||||
|
}, [token]);
|
||||||
|
|
||||||
|
const handleChange = (event) => {
|
||||||
|
setFleet(event.target.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
You are adding the following VINs to a fleet: {vinCSV}.
|
||||||
|
</p>
|
||||||
|
{options && (
|
||||||
|
<FormControl variant="filled" fullWidth={true}>
|
||||||
|
<InputLabel id="fleet-selection">
|
||||||
|
Fleet
|
||||||
|
</InputLabel>
|
||||||
|
<Select
|
||||||
|
labelId="fleet-selection"
|
||||||
|
value={fleet}
|
||||||
|
label="Fleet"
|
||||||
|
onChange={handleChange}
|
||||||
|
>
|
||||||
|
{options.map((option) => (
|
||||||
|
<MenuItem key={option} value={option}>{option}</MenuItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
53
src/components/BulkActions/actions/AddToFleet.test.jsx
Normal file
53
src/components/BulkActions/actions/AddToFleet.test.jsx
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
jest.mock("../../Contexts/UserContext");
|
||||||
|
jest.mock("../../Contexts/StatusContext");
|
||||||
|
jest.mock("../../../services/fleetsAPI");
|
||||||
|
|
||||||
|
import React, { useEffect, useState } from "react";
|
||||||
|
import {
|
||||||
|
render,
|
||||||
|
act,
|
||||||
|
} from "@testing-library/react";
|
||||||
|
import { UserProvider, setToken } from "../../Contexts/UserContext";
|
||||||
|
import { StatusProvider } from "../../Contexts/StatusContext";
|
||||||
|
import { TEST_AUTH_OBJECT_FISKER } from "../../../utils/testing";
|
||||||
|
import AddToFleet from "./AddToFleet";
|
||||||
|
import fleetsAPI from "../../../services/fleetsAPI";
|
||||||
|
|
||||||
|
jest.mock('react', () => ({
|
||||||
|
...jest.requireActual('react'),
|
||||||
|
useState: jest.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock('@material-ui/core/FormControl', () => {
|
||||||
|
const React = require('react');
|
||||||
|
return () => <div data-testid="mock-form-control" />;
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("BulkActions/AddToFleet", () => {
|
||||||
|
beforeAll(() => {
|
||||||
|
setToken(TEST_AUTH_OBJECT_FISKER);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("makes request to update the config of multiple vehicles", async () => {
|
||||||
|
useState
|
||||||
|
.mockReturnValueOnce(["Default-Test", jest.fn()])
|
||||||
|
.mockReturnValueOnce([["Default-Test"], jest.fn()]);
|
||||||
|
const api = jest.spyOn(fleetsAPI, "addFleetVehicles");
|
||||||
|
const ref = React.createRef();
|
||||||
|
|
||||||
|
render(
|
||||||
|
<StatusProvider>
|
||||||
|
<UserProvider>
|
||||||
|
<AddToFleet
|
||||||
|
ref={ref}
|
||||||
|
vins={["TESTVIN1234567890"]}
|
||||||
|
vinCSV=""
|
||||||
|
/>
|
||||||
|
</UserProvider>
|
||||||
|
</StatusProvider>
|
||||||
|
);
|
||||||
|
|
||||||
|
await act(async () => ref.current.submit());
|
||||||
|
expect(api).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
55
src/components/BulkActions/actions/DeleteVehicles.jsx
Normal file
55
src/components/BulkActions/actions/DeleteVehicles.jsx
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
import { forwardRef, useImperativeHandle } from "react";
|
||||||
|
import { useStatusContext } from "../../Contexts/StatusContext";
|
||||||
|
import { useUserContext } from "../../Contexts/UserContext";
|
||||||
|
import TaskRunner from "../../../utils/taskRunner";
|
||||||
|
import vehiclesAPI from "../../../services/vehiclesAPI";
|
||||||
|
|
||||||
|
export default forwardRef(({
|
||||||
|
vins,
|
||||||
|
vinCSV,
|
||||||
|
}, ref) => {
|
||||||
|
const { setMessage } = useStatusContext();
|
||||||
|
const { token: { idToken: { jwtToken: token } } } = useUserContext();
|
||||||
|
|
||||||
|
useImperativeHandle(ref, () => ({
|
||||||
|
async submit() {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const taskRunner = new TaskRunner(5, vins.length);
|
||||||
|
let errorCount = 0;
|
||||||
|
|
||||||
|
const task = (vin, index) => {
|
||||||
|
const progressMessage = `${index + 1}/${vins.length}`;
|
||||||
|
return async () => vehiclesAPI.deleteVehicle(vin, token)
|
||||||
|
.then((response) => {
|
||||||
|
if (response.error) {
|
||||||
|
errorCount += 1;
|
||||||
|
setMessage(`${progressMessage} ${response.error}: ${response.message}`);
|
||||||
|
} else {
|
||||||
|
setMessage(`${progressMessage} Deleted ${vin}`);
|
||||||
|
}
|
||||||
|
return response;
|
||||||
|
})
|
||||||
|
.catch((error) => reject(error));
|
||||||
|
}
|
||||||
|
|
||||||
|
vins.forEach((vin, i) => {
|
||||||
|
taskRunner.push(task(vin, i));
|
||||||
|
});
|
||||||
|
|
||||||
|
taskRunner.onComplete().then((responses) => {
|
||||||
|
const completeMessage = `${vins.length - errorCount}/${vins.length}`;
|
||||||
|
setMessage(`Successfully deleted ${completeMessage} vehicles.`);
|
||||||
|
resolve(responses);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
You are about to delete the following VINs: {vinCSV}.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
40
src/components/BulkActions/actions/DeleteVehicles.test.jsx
Normal file
40
src/components/BulkActions/actions/DeleteVehicles.test.jsx
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
jest.mock("../../Contexts/UserContext");
|
||||||
|
jest.mock("../../Contexts/StatusContext");
|
||||||
|
jest.mock("../../../services/vehiclesAPI");
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
import {
|
||||||
|
render,
|
||||||
|
act,
|
||||||
|
} from "@testing-library/react";
|
||||||
|
import { UserProvider, setToken } from "../../Contexts/UserContext";
|
||||||
|
import { StatusProvider } from "../../Contexts/StatusContext";
|
||||||
|
import { TEST_AUTH_OBJECT_FISKER } from "../../../utils/testing";
|
||||||
|
import DeleteVehicles from "./DeleteVehicles";
|
||||||
|
import vehiclesAPI from "../../../services/vehiclesAPI";
|
||||||
|
|
||||||
|
describe("BulkActions/DeleteVehicles", () => {
|
||||||
|
beforeAll(() => {
|
||||||
|
setToken(TEST_AUTH_OBJECT_FISKER);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("makes request to delete multiple vehicles", async () => {
|
||||||
|
const api = jest.spyOn(vehiclesAPI, "deleteVehicle");
|
||||||
|
const ref = React.createRef();
|
||||||
|
|
||||||
|
render(
|
||||||
|
<StatusProvider>
|
||||||
|
<UserProvider>
|
||||||
|
<DeleteVehicles
|
||||||
|
ref={ref}
|
||||||
|
vins={["TESTVIN123456789a", "TESTVIN123456789b", "TESTVIN123456789c"]}
|
||||||
|
vinCSV=""
|
||||||
|
/>
|
||||||
|
</UserProvider>
|
||||||
|
</StatusProvider>
|
||||||
|
);
|
||||||
|
|
||||||
|
await act(async () => ref.current.submit());
|
||||||
|
expect(api).toHaveBeenCalledTimes(3);
|
||||||
|
});
|
||||||
|
});
|
||||||
74
src/components/BulkActions/actions/UpdateConfig.jsx
Normal file
74
src/components/BulkActions/actions/UpdateConfig.jsx
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
import { useState, forwardRef, useImperativeHandle } from "react";
|
||||||
|
import {
|
||||||
|
Checkbox,
|
||||||
|
FormControlLabel,
|
||||||
|
} from '@material-ui/core';
|
||||||
|
import { useStatusContext } from "../../Contexts/StatusContext";
|
||||||
|
import { useUserContext } from "../../Contexts/UserContext";
|
||||||
|
import TaskRunner from "../../../utils/taskRunner";
|
||||||
|
import vehiclesAPI from "../../../services/vehiclesAPI";
|
||||||
|
|
||||||
|
export default forwardRef(({
|
||||||
|
vins,
|
||||||
|
vinCSV,
|
||||||
|
}, ref) => {
|
||||||
|
const { setMessage } = useStatusContext();
|
||||||
|
const { token: { idToken: { jwtToken: token } } } = useUserContext();
|
||||||
|
|
||||||
|
const [forcePush, setForcePush] = useState(false);
|
||||||
|
|
||||||
|
useImperativeHandle(ref, () => ({
|
||||||
|
async submit() {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const taskRunner = new TaskRunner(5, vins.length);
|
||||||
|
let errorCount = 0;
|
||||||
|
|
||||||
|
const task = (vin, index) => {
|
||||||
|
const progressMessage = `${index + 1}/${vins.length}`;
|
||||||
|
return async () => vehiclesAPI.updateConfig(vin, forcePush, token)
|
||||||
|
.then((response) => {
|
||||||
|
if (response.error) {
|
||||||
|
errorCount += 1;
|
||||||
|
setMessage(`${progressMessage} ${response.error}: ${response.message}`);
|
||||||
|
} else {
|
||||||
|
setMessage(`${progressMessage} Updated config for ${vin}`);
|
||||||
|
}
|
||||||
|
return response;
|
||||||
|
})
|
||||||
|
.catch((error) => reject(error));
|
||||||
|
}
|
||||||
|
|
||||||
|
vins.forEach((vin, i) => {
|
||||||
|
taskRunner.push(task(vin, i));
|
||||||
|
});
|
||||||
|
|
||||||
|
taskRunner.onComplete().then((responses) => {
|
||||||
|
const completeMessage = `${vins.length - errorCount}/${vins.length}`;
|
||||||
|
setMessage(`Successfully updated ${completeMessage} vehicles.`);
|
||||||
|
resolve(responses);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
const handleChange = () => {
|
||||||
|
setForcePush((forcePush) => !forcePush);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
You are updating the config for the following VINs: {vinCSV}.
|
||||||
|
</p>
|
||||||
|
<FormControlLabel
|
||||||
|
label="Force Push"
|
||||||
|
control={
|
||||||
|
<Checkbox
|
||||||
|
checked={forcePush}
|
||||||
|
onChange={() => handleChange()}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
40
src/components/BulkActions/actions/UpdateConfig.test.jsx
Normal file
40
src/components/BulkActions/actions/UpdateConfig.test.jsx
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
jest.mock("../../Contexts/UserContext");
|
||||||
|
jest.mock("../../Contexts/StatusContext");
|
||||||
|
jest.mock("../../../services/vehiclesAPI");
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
import {
|
||||||
|
render,
|
||||||
|
act,
|
||||||
|
} from "@testing-library/react";
|
||||||
|
import { UserProvider, setToken } from "../../Contexts/UserContext";
|
||||||
|
import { StatusProvider } from "../../Contexts/StatusContext";
|
||||||
|
import { TEST_AUTH_OBJECT_FISKER } from "../../../utils/testing";
|
||||||
|
import UpdateConfig from "./UpdateConfig";
|
||||||
|
import vehiclesAPI from "../../../services/vehiclesAPI";
|
||||||
|
|
||||||
|
describe("BulkActions/UpdateConfig", () => {
|
||||||
|
beforeAll(() => {
|
||||||
|
setToken(TEST_AUTH_OBJECT_FISKER);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("makes request to update the config of multiple vehicles", async () => {
|
||||||
|
const api = jest.spyOn(vehiclesAPI, "updateConfig");
|
||||||
|
const ref = React.createRef();
|
||||||
|
|
||||||
|
render(
|
||||||
|
<StatusProvider>
|
||||||
|
<UserProvider>
|
||||||
|
<UpdateConfig
|
||||||
|
ref={ref}
|
||||||
|
vins={["TESTVIN123456789a", "TESTVIN123456789b", "TESTVIN123456789c"]}
|
||||||
|
vinCSV=""
|
||||||
|
/>
|
||||||
|
</UserProvider>
|
||||||
|
</StatusProvider>
|
||||||
|
);
|
||||||
|
|
||||||
|
await act(async () => ref.current.submit());
|
||||||
|
expect(api).toHaveBeenCalledTimes(3);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,84 +1,86 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState, useRef, Suspense, lazy } from "react";
|
||||||
import TransformModal from "../TransformModal";
|
|
||||||
import DropDownButton from "../Controls/DropDownButton";
|
import DropDownButton from "../Controls/DropDownButton";
|
||||||
import { useUserContext } from "../Contexts/UserContext";
|
import { Modal } from "./Modal";
|
||||||
import { useStatusContext } from "../Contexts/StatusContext";
|
|
||||||
import useAddTags from "./useAddTags";
|
|
||||||
import useUpdateConfig from "./useUpdateConfig";
|
|
||||||
|
|
||||||
const transformArrayToCSV = (arr) => arr.join(", ");
|
// Code-splitting individual actions
|
||||||
|
// https://react.dev/reference/react/lazy
|
||||||
|
const AddTags = lazy(() => import("./actions/AddTags"));
|
||||||
|
const AddToFleet = lazy(() => import("./actions/AddToFleet"));
|
||||||
|
const DeleteVehicles = lazy(() => import("./actions/DeleteVehicles"));
|
||||||
|
const UpdateConfig = lazy(() => import("./actions/UpdateConfig"));
|
||||||
|
|
||||||
export default function BulkActions({
|
export default function BulkActions({
|
||||||
vins = [],
|
vins = [],
|
||||||
|
actions = [],
|
||||||
}) {
|
}) {
|
||||||
const [vinCSV, setVinCSV] = useState(transformArrayToCSV(vins));
|
const [title, setTitle] = useState("Action");
|
||||||
const [active, setActive] = useState(null);
|
const [active, setActive] = useState(null);
|
||||||
const actions = [
|
const activeRef = useRef();
|
||||||
{
|
|
||||||
name: "Update Configs",
|
const filteredActions = [
|
||||||
disabled: vins.length === 0,
|
|
||||||
trigger: () => setActive("updateConfig"),
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
|
id: "addTags",
|
||||||
name: "Add Tags",
|
name: "Add Tags",
|
||||||
disabled: vins.length === 0,
|
disabled: false,
|
||||||
trigger: () => setActive("addTags"),
|
trigger: () => setActive("addTags"),
|
||||||
},
|
},
|
||||||
];
|
{
|
||||||
|
id: "addToFleet",
|
||||||
const updateConfig = useUpdateConfig();
|
name: "Add To Fleet",
|
||||||
const addTags = useAddTags();
|
disabled: false,
|
||||||
|
trigger: () => setActive("addToFleet"),
|
||||||
const { setMessage } = useStatusContext();
|
|
||||||
const {
|
|
||||||
token: {
|
|
||||||
idToken: { jwtToken: token },
|
|
||||||
},
|
},
|
||||||
} = useUserContext();
|
{
|
||||||
|
id: "deleteVehicles",
|
||||||
|
name: "Delete",
|
||||||
|
disabled: false,
|
||||||
|
trigger: () => setActive("deleteVehicles"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "updateConfig",
|
||||||
|
name: "Update Config",
|
||||||
|
disabled: false,
|
||||||
|
trigger: () => setActive("updateConfig"),
|
||||||
|
}
|
||||||
|
].filter((action) => actions.includes(action.id));
|
||||||
|
|
||||||
const handleUpdateConfig = () => {
|
const payload = {
|
||||||
updateConfig.submit(vins, token)
|
vins,
|
||||||
.then(() => {
|
vinCSV: vins.join(", "),
|
||||||
setMessage(`${vins.length} vehicles updated.`);
|
ref: activeRef
|
||||||
})
|
};
|
||||||
.catch((error) => {
|
|
||||||
setMessage(error.message);
|
const handleClose = () => {
|
||||||
});
|
setActive(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleAddTags = () => {
|
const handleSubmit = () => {
|
||||||
addTags.submit(vins, token)
|
activeRef.current.submit();
|
||||||
.then(() => setMessage(`Added ${addTags.data.tags.value.length} tags to ${vins.length} vehicles.`))
|
handleClose();
|
||||||
.catch((error) => setMessage(error.message));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleClose = () => setActive(null);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setVinCSV(transformArrayToCSV(vins));
|
setTitle(filteredActions.find((action) => active === action.id)?.name || "Action");
|
||||||
}, [vins]);
|
}, [active, filteredActions]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<DropDownButton actions={actions} payload={[vins]} />
|
<DropDownButton actions={filteredActions} />
|
||||||
<TransformModal
|
<Modal
|
||||||
title="Update Config"
|
title={title}
|
||||||
body={`You are updating the config for the following VINs: ${vinCSV}.`}
|
open={!!active}
|
||||||
close={handleClose}
|
close={handleClose}
|
||||||
open={active === "updateConfig"}
|
submit={handleSubmit}
|
||||||
data={updateConfig.data}
|
>
|
||||||
setData={updateConfig.setData}
|
<Suspense fallback={<div>Loading...</div>}>
|
||||||
submit={handleUpdateConfig}
|
<section>
|
||||||
/>
|
{active === "addTags" && <AddTags {...payload} />}
|
||||||
<TransformModal
|
{active === "addToFleet" && <AddToFleet {...payload} />}
|
||||||
title="Add Tags"
|
{active === "deleteVehicles" && <DeleteVehicles {...payload} />}
|
||||||
body={`You are adding tags for the following VINs: ${vinCSV}.`}
|
{active === "updateConfig" && <UpdateConfig {...payload} />}
|
||||||
close={handleClose}
|
</section>
|
||||||
open={active === "addTags"}
|
</Suspense>
|
||||||
data={addTags.data}
|
</Modal>
|
||||||
setData={addTags.setData}
|
|
||||||
submit={handleAddTags}
|
|
||||||
/>
|
|
||||||
</>
|
</>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
77
src/components/BulkActions/index.test.jsx
Normal file
77
src/components/BulkActions/index.test.jsx
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
jest.mock("../Contexts/UserContext");
|
||||||
|
jest.mock("../Contexts/StatusContext");
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
import {
|
||||||
|
fireEvent,
|
||||||
|
render,
|
||||||
|
screen,
|
||||||
|
waitFor,
|
||||||
|
} from "@testing-library/react";
|
||||||
|
import { UserProvider, setToken } from "../Contexts/UserContext";
|
||||||
|
import { StatusProvider } from "../Contexts/StatusContext";
|
||||||
|
import { TEST_AUTH_OBJECT_FISKER } from "../../utils/testing";
|
||||||
|
import BulkActions from ".";
|
||||||
|
import addSnapshotSerializer from "../../utils/snapshot";
|
||||||
|
|
||||||
|
describe("BulkActions", () => {
|
||||||
|
beforeAll(() => {
|
||||||
|
setToken(TEST_AUTH_OBJECT_FISKER);
|
||||||
|
global.URL.createObjectURL = jest.fn();
|
||||||
|
global.URL.revokeObjectURL = jest.fn();
|
||||||
|
addSnapshotSerializer(expect);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Render", async () => {
|
||||||
|
const { container } = render(
|
||||||
|
<StatusProvider>
|
||||||
|
<UserProvider>
|
||||||
|
<BulkActions
|
||||||
|
actions={["addTags"]}
|
||||||
|
vins={["TESTVIN1234567890"]}
|
||||||
|
/>
|
||||||
|
</UserProvider>
|
||||||
|
</StatusProvider>
|
||||||
|
);
|
||||||
|
await waitFor(() => {
|
||||||
|
/* render */
|
||||||
|
});
|
||||||
|
expect(container).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("opens a modal", async () => {
|
||||||
|
render(
|
||||||
|
<StatusProvider>
|
||||||
|
<UserProvider>
|
||||||
|
<BulkActions
|
||||||
|
actions={["addTags"]}
|
||||||
|
vins={["TESTVIN1234567890"]}
|
||||||
|
/>
|
||||||
|
</UserProvider>
|
||||||
|
</StatusProvider>
|
||||||
|
);
|
||||||
|
|
||||||
|
const buttonEl = screen.getByText("Add Tags");
|
||||||
|
fireEvent.click(buttonEl);
|
||||||
|
const submitEl = screen.getByText("Submit");
|
||||||
|
expect(submitEl).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("filters valid actions", async () => {
|
||||||
|
render(
|
||||||
|
<StatusProvider>
|
||||||
|
<UserProvider>
|
||||||
|
<BulkActions
|
||||||
|
actions={["addTags", "someInvalidAction", "updateConfig"]}
|
||||||
|
vins={["TESTVIN1234567890"]}
|
||||||
|
/>
|
||||||
|
</UserProvider>
|
||||||
|
</StatusProvider>
|
||||||
|
);
|
||||||
|
|
||||||
|
const dropdownBtn = screen.getByTestId("dropdown-button-expand");
|
||||||
|
fireEvent.click(dropdownBtn);
|
||||||
|
const dropdownOptions = screen.getAllByRole("menuitem");
|
||||||
|
expect(dropdownOptions.length).toBe(2);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
import { useState } from "react";
|
|
||||||
import vehiclesAPI from "../../services/vehiclesAPI";
|
|
||||||
|
|
||||||
export default function useAddTags() {
|
|
||||||
const [tags, setTags] = useState({
|
|
||||||
tags: {
|
|
||||||
label: "Tags",
|
|
||||||
type: "list.string",
|
|
||||||
value: [],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const submit = async (vins, token) => {
|
|
||||||
return vehiclesAPI.addTags(vins, tags.tags.value, token);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
data: tags,
|
|
||||||
setData: setTags,
|
|
||||||
submit,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
import { useState } from "react";
|
|
||||||
import TaskRunner from "../../utils/taskRunner";
|
|
||||||
import vehiclesAPI from "../../services/vehiclesAPI";
|
|
||||||
|
|
||||||
export default function useUpdateConfig() {
|
|
||||||
const [config, setConfig] = useState({
|
|
||||||
force: {
|
|
||||||
label: "Force Push",
|
|
||||||
type: "boolean",
|
|
||||||
value: false,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const submit = async (vins, token) => {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const taskRunner = new TaskRunner(5);
|
|
||||||
|
|
||||||
const task = (vin, isLast) => {
|
|
||||||
return async () => vehiclesAPI.updateConfig(vin, config.force.value, token)
|
|
||||||
.then((response) => {
|
|
||||||
if (isLast) {
|
|
||||||
if (response.error) {
|
|
||||||
reject(response);
|
|
||||||
}
|
|
||||||
resolve(response)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch((error) => reject(error));
|
|
||||||
}
|
|
||||||
|
|
||||||
vins.forEach((vin, index) => taskRunner.push(task(vin, index === vins.length - 1)));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
data: config,
|
|
||||||
setData: setConfig,
|
|
||||||
submit,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -43,17 +43,20 @@ exports[`VehicleTable Render 1`] = `
|
|||||||
role="group"
|
role="group"
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
class="MuiButtonBase-root MuiButton-root MuiButton-contained MuiButton-containedPrimary MuiButton-sizeMedium MuiButton-containedSizeMedium Mui-disabled MuiButtonGroup-grouped MuiButtonGroup-groupedHorizontal MuiButtonGroup-groupedContained MuiButtonGroup-groupedContainedHorizontal MuiButtonGroup-groupedContainedPrimary MuiButton-root MuiButton-contained MuiButton-containedPrimary MuiButton-sizeMedium MuiButton-containedSizeMedium MuiButtonGroup-grouped MuiButtonGroup-groupedHorizontal MuiButtonGroup-groupedContained MuiButtonGroup-groupedContainedHorizontal MuiButtonGroup-groupedContainedPrimary css-sghohy-MuiButtonBase-root-MuiButton-root"
|
class="MuiButtonBase-root MuiButton-root MuiButton-contained MuiButton-containedPrimary MuiButton-sizeMedium MuiButton-containedSizeMedium MuiButtonGroup-grouped MuiButtonGroup-groupedHorizontal MuiButtonGroup-groupedContained MuiButtonGroup-groupedContainedHorizontal MuiButtonGroup-groupedContainedPrimary MuiButton-root MuiButton-contained MuiButton-containedPrimary MuiButton-sizeMedium MuiButton-containedSizeMedium MuiButtonGroup-grouped MuiButtonGroup-groupedHorizontal MuiButtonGroup-groupedContained MuiButtonGroup-groupedContainedHorizontal MuiButtonGroup-groupedContainedPrimary css-sghohy-MuiButtonBase-root-MuiButton-root"
|
||||||
disabled=""
|
tabindex="0"
|
||||||
tabindex="-1"
|
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
Update Configs
|
Add Tags
|
||||||
|
<span
|
||||||
|
class="MuiTouchRipple-root css-8je8zh-MuiTouchRipple-root"
|
||||||
|
/>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
aria-haspopup="menu"
|
aria-haspopup="menu"
|
||||||
aria-label="select action"
|
aria-label="select action"
|
||||||
class="MuiButtonBase-root MuiButton-root MuiButton-contained MuiButton-containedPrimary MuiButton-sizeSmall MuiButton-containedSizeSmall MuiButtonGroup-grouped MuiButtonGroup-groupedHorizontal MuiButtonGroup-groupedContained MuiButtonGroup-groupedContainedHorizontal MuiButtonGroup-groupedContainedPrimary MuiButton-root MuiButton-contained MuiButton-containedPrimary MuiButton-sizeSmall MuiButton-containedSizeSmall MuiButtonGroup-grouped MuiButtonGroup-groupedHorizontal MuiButtonGroup-groupedContained MuiButtonGroup-groupedContainedHorizontal MuiButtonGroup-groupedContainedPrimary css-11qr2p8-MuiButtonBase-root-MuiButton-root"
|
class="MuiButtonBase-root MuiButton-root MuiButton-contained MuiButton-containedPrimary MuiButton-sizeSmall MuiButton-containedSizeSmall MuiButtonGroup-grouped MuiButtonGroup-groupedHorizontal MuiButtonGroup-groupedContained MuiButtonGroup-groupedContainedHorizontal MuiButtonGroup-groupedContainedPrimary MuiButton-root MuiButton-contained MuiButton-containedPrimary MuiButton-sizeSmall MuiButton-containedSizeSmall MuiButtonGroup-grouped MuiButtonGroup-groupedHorizontal MuiButtonGroup-groupedContained MuiButtonGroup-groupedContainedHorizontal MuiButtonGroup-groupedContainedPrimary css-11qr2p8-MuiButtonBase-root-MuiButton-root"
|
||||||
|
data-testid="dropdown-button-expand"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
@@ -482,7 +485,6 @@ exports[`VehicleTable Render 1`] = `
|
|||||||
</tfoot>
|
</tfoot>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<div />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -7,17 +7,14 @@ import { Link } from "react-router-dom";
|
|||||||
import { Permissions } from "../../../utils/roles";
|
import { Permissions } from "../../../utils/roles";
|
||||||
import { useStatusContext } from "../../Contexts/StatusContext";
|
import { useStatusContext } from "../../Contexts/StatusContext";
|
||||||
import { useUserContext } from "../../Contexts/UserContext";
|
import { useUserContext } from "../../Contexts/UserContext";
|
||||||
import { VehicleProvider, VehicleConsumer } from "../../Contexts/VehicleContext";
|
import { VehicleProvider } from "../../Contexts/VehicleContext";
|
||||||
import CarSelectionTable from "../../Controls/CarSelectionTable";
|
import CarSelectionTable from "../../Controls/CarSelectionTable";
|
||||||
import OptionsDropdown from "../../Controls/OptionsDropdown";
|
import OptionsDropdown from "../../Controls/OptionsDropdown";
|
||||||
import { RoleWrap } from "../../Controls/RoleWrap";
|
import { RoleWrap } from "../../Controls/RoleWrap";
|
||||||
import SearchField from "../../Controls/SearchField";
|
import SearchField from "../../Controls/SearchField";
|
||||||
import DropDownButton from "../../Controls/DropDownButton";
|
import BulkActions from "../../BulkActions";
|
||||||
import TransformModal from "../../TransformModal";
|
|
||||||
import { useLocalStorage } from "../../useLocalStorage";
|
import { useLocalStorage } from "../../useLocalStorage";
|
||||||
import useStyles from "../../useStyles";
|
import useStyles from "../../useStyles";
|
||||||
import TaskRunner from "../../../utils/taskRunner";
|
|
||||||
import GeneralConfirmation from "../../GeneralConfirmation";
|
|
||||||
|
|
||||||
const MainForm = () => {
|
const MainForm = () => {
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
@@ -25,22 +22,7 @@ const MainForm = () => {
|
|||||||
const [online, setOnline] = useState(false);
|
const [online, setOnline] = useState(false);
|
||||||
const [onlineHMI, setOnlineHMI] = useState(false);
|
const [onlineHMI, setOnlineHMI] = useState(false);
|
||||||
const [selectedVins, setSelectedVins] = useState([]);
|
const [selectedVins, setSelectedVins] = useState([]);
|
||||||
const [config, setConfig] = useState({
|
const { setTitle, setSitePath } = useStatusContext();
|
||||||
force: {
|
|
||||||
label: "Force push",
|
|
||||||
type: "boolean",
|
|
||||||
value: false
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const [tagsToAdd, setTagsToAdd] = useState({
|
|
||||||
tags: {
|
|
||||||
label: "Tags",
|
|
||||||
type: "list.string",
|
|
||||||
value: [],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const [activeModal, setActiveModal] = useState(null);
|
|
||||||
const { setTitle, setSitePath, setMessage } = useStatusContext();
|
|
||||||
const {
|
const {
|
||||||
token: {
|
token: {
|
||||||
idToken: { jwtToken: token },
|
idToken: { jwtToken: token },
|
||||||
@@ -70,65 +52,6 @@ const MainForm = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleUploadConfig = (fn) => {
|
|
||||||
const taskRunner = new TaskRunner(5);
|
|
||||||
const request = (vin, i) => {
|
|
||||||
const messagePrefix = `${i + 1}/${selectedVins.length} "${vin}":`;
|
|
||||||
return async () => {
|
|
||||||
const result = await fn(vin, config.force.value, token)
|
|
||||||
.then(() => {
|
|
||||||
setMessage(`${messagePrefix} updated.`);
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
setMessage(`${messagePrefix} ${error.message}`);
|
|
||||||
});
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
selectedVins.forEach((vin, i) => taskRunner.push(request(vin, i)))
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleAddTags = async (fn) => {
|
|
||||||
await fn(selectedVins, tagsToAdd.tags.value, token)
|
|
||||||
.then(() => setMessage(`Added ${tagsToAdd.tags.value.length} tags to ${selectedVins.length} vehicles.`))
|
|
||||||
.catch((error) => setMessage(error.message));
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleDelete = async (fn) => {
|
|
||||||
const taskRunner = new TaskRunner(5);
|
|
||||||
const request = (vin) => {
|
|
||||||
return async () => {
|
|
||||||
return fn(vin, token)
|
|
||||||
.then(() => {
|
|
||||||
setMessage(`Deleted ${selectedVins.length} vehicles`);
|
|
||||||
setSelectedVins([]);
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
setMessage(error.message);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
selectedVins.forEach((vin) => taskRunner.push(request(vin)));
|
|
||||||
};
|
|
||||||
|
|
||||||
const actions = [
|
|
||||||
{
|
|
||||||
name: "Update Configs",
|
|
||||||
disabled: selectedVins.length === 0,
|
|
||||||
trigger: () => setActiveModal("updateConfig"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Add Tags",
|
|
||||||
disabled: selectedVins.length === 0,
|
|
||||||
trigger: () => setActiveModal("addTags"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Delete",
|
|
||||||
disabled: selectedVins.length === 0,
|
|
||||||
trigger: () => setActiveModal("delete"),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const handleOnlineHMI = (event) => {
|
const handleOnlineHMI = (event) => {
|
||||||
setOnlineHMI(event.target.checked);
|
setOnlineHMI(event.target.checked);
|
||||||
};
|
};
|
||||||
@@ -152,7 +75,7 @@ const MainForm = () => {
|
|||||||
<AddCircleIcon fontSize="large" />
|
<AddCircleIcon fontSize="large" />
|
||||||
</Link>
|
</Link>
|
||||||
</RoleWrap>
|
</RoleWrap>
|
||||||
<DropDownButton actions={actions} payload={[selectedVins]} />
|
<BulkActions vins={selectedVins} actions={["addTags", "addToFleet", "deleteVehicles", "updateConfig"]} />
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item md={4} className={classes.textCenterAlign}>
|
<Grid item md={4} className={classes.textCenterAlign}>
|
||||||
<SearchField classes={classes} onSearch={handleSearch} savedSearchValue={search} />
|
<SearchField classes={classes} onSearch={handleSearch} savedSearchValue={search} />
|
||||||
@@ -190,35 +113,6 @@ const MainForm = () => {
|
|||||||
onSelect={handleSelect}
|
onSelect={handleSelect}
|
||||||
onSelectAll={handleSelectAll}
|
onSelectAll={handleSelectAll}
|
||||||
/>
|
/>
|
||||||
<VehicleConsumer>
|
|
||||||
{(context) => (<>
|
|
||||||
<TransformModal
|
|
||||||
open={activeModal === "updateConfig"}
|
|
||||||
close={() => setActiveModal(null)}
|
|
||||||
title="Update Configs"
|
|
||||||
body={`You are updating the config for the following VINs: ${selectedVins.join(", ")}.`}
|
|
||||||
data={config}
|
|
||||||
setData={setConfig}
|
|
||||||
submit={() => handleUploadConfig(context.uploadConfig)}
|
|
||||||
/>
|
|
||||||
<TransformModal
|
|
||||||
open={activeModal === "addTags"}
|
|
||||||
close={() => setActiveModal(null)}
|
|
||||||
title="Add Tags"
|
|
||||||
body={`You are adding tags for the following VINs: ${selectedVins.join(", ")}.`}
|
|
||||||
data={tagsToAdd}
|
|
||||||
setData={setTagsToAdd}
|
|
||||||
submit={() => handleAddTags(context.addTags)}
|
|
||||||
/>
|
|
||||||
<GeneralConfirmation
|
|
||||||
open={activeModal === "delete"}
|
|
||||||
close={() => setActiveModal(null)}
|
|
||||||
title="Delete"
|
|
||||||
message={`You are about to delete the following VINs: ${selectedVins.join(", ")}`}
|
|
||||||
actionFunction={() => handleDelete(context.deleteVehicle)}
|
|
||||||
/>
|
|
||||||
</>)}
|
|
||||||
</VehicleConsumer>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ exports[`DropDownButton Render 1`] = `
|
|||||||
aria-haspopup="menu"
|
aria-haspopup="menu"
|
||||||
aria-label="select action"
|
aria-label="select action"
|
||||||
class="MuiButtonBase-root MuiButton-root MuiButton-contained MuiButton-containedPrimary MuiButton-sizeSmall MuiButton-containedSizeSmall MuiButtonGroup-grouped MuiButtonGroup-groupedHorizontal MuiButtonGroup-groupedContained MuiButtonGroup-groupedContainedHorizontal MuiButtonGroup-groupedContainedPrimary MuiButton-root MuiButton-contained MuiButton-containedPrimary MuiButton-sizeSmall MuiButton-containedSizeSmall MuiButtonGroup-grouped MuiButtonGroup-groupedHorizontal MuiButtonGroup-groupedContained MuiButtonGroup-groupedContainedHorizontal MuiButtonGroup-groupedContainedPrimary css-11qr2p8-MuiButtonBase-root-MuiButton-root"
|
class="MuiButtonBase-root MuiButton-root MuiButton-contained MuiButton-containedPrimary MuiButton-sizeSmall MuiButton-containedSizeSmall MuiButtonGroup-grouped MuiButtonGroup-groupedHorizontal MuiButtonGroup-groupedContained MuiButtonGroup-groupedContainedHorizontal MuiButtonGroup-groupedContainedPrimary MuiButton-root MuiButton-contained MuiButton-containedPrimary MuiButton-sizeSmall MuiButton-containedSizeSmall MuiButtonGroup-grouped MuiButtonGroup-groupedHorizontal MuiButtonGroup-groupedContained MuiButtonGroup-groupedContainedHorizontal MuiButtonGroup-groupedContainedPrimary css-11qr2p8-MuiButtonBase-root-MuiButton-root"
|
||||||
|
data-testid="dropdown-button-expand"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -65,6 +65,7 @@ const DropDownButton = ({ actions = [], payload = [] }) => {
|
|||||||
aria-label="select action"
|
aria-label="select action"
|
||||||
aria-haspopup="menu"
|
aria-haspopup="menu"
|
||||||
onClick={handleToggle}
|
onClick={handleToggle}
|
||||||
|
data-testid="dropdown-button-expand"
|
||||||
>
|
>
|
||||||
<ArrowDropDownIcon />
|
<ArrowDropDownIcon />
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -156,7 +156,7 @@ exports[`FleetDetailsTab Render 1`] = `
|
|||||||
tabindex="0"
|
tabindex="0"
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
Update Configs
|
Add Tags
|
||||||
<span
|
<span
|
||||||
class="MuiTouchRipple-root css-8je8zh-MuiTouchRipple-root"
|
class="MuiTouchRipple-root css-8je8zh-MuiTouchRipple-root"
|
||||||
/>
|
/>
|
||||||
@@ -165,6 +165,7 @@ exports[`FleetDetailsTab Render 1`] = `
|
|||||||
aria-haspopup="menu"
|
aria-haspopup="menu"
|
||||||
aria-label="select action"
|
aria-label="select action"
|
||||||
class="MuiButtonBase-root MuiButton-root MuiButton-contained MuiButton-containedPrimary MuiButton-sizeSmall MuiButton-containedSizeSmall MuiButtonGroup-grouped MuiButtonGroup-groupedHorizontal MuiButtonGroup-groupedContained MuiButtonGroup-groupedContainedHorizontal MuiButtonGroup-groupedContainedPrimary MuiButton-root MuiButton-contained MuiButton-containedPrimary MuiButton-sizeSmall MuiButton-containedSizeSmall MuiButtonGroup-grouped MuiButtonGroup-groupedHorizontal MuiButtonGroup-groupedContained MuiButtonGroup-groupedContainedHorizontal MuiButtonGroup-groupedContainedPrimary css-11qr2p8-MuiButtonBase-root-MuiButton-root"
|
class="MuiButtonBase-root MuiButton-root MuiButton-contained MuiButton-containedPrimary MuiButton-sizeSmall MuiButton-containedSizeSmall MuiButtonGroup-grouped MuiButtonGroup-groupedHorizontal MuiButtonGroup-groupedContained MuiButtonGroup-groupedContainedHorizontal MuiButtonGroup-groupedContainedPrimary MuiButton-root MuiButton-contained MuiButton-containedPrimary MuiButton-sizeSmall MuiButton-containedSizeSmall MuiButtonGroup-grouped MuiButtonGroup-groupedHorizontal MuiButtonGroup-groupedContained MuiButtonGroup-groupedContainedHorizontal MuiButtonGroup-groupedContainedPrimary css-11qr2p8-MuiButtonBase-root-MuiButton-root"
|
||||||
|
data-testid="dropdown-button-expand"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ const MainForm = ({ name }) => {
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item md={12} className={classes.textCenterAlign}>
|
<Grid item md={12} className={classes.textCenterAlign}>
|
||||||
<BulkActions vins={fleet.vehicles} />
|
<BulkActions vins={fleet.vehicles} actions={["addTags", "updateConfig"]} />
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
<DeleteConfirmation message={name} open={showDeleteModal} close={() => setShowDeleteModal(false)} deleteFunction={onDelete} />
|
<DeleteConfirmation message={name} open={showDeleteModal} close={() => setShowDeleteModal(false)} deleteFunction={onDelete} />
|
||||||
|
|||||||
@@ -164,7 +164,7 @@ exports[`DetailsTab Render 1`] = `
|
|||||||
tabindex="0"
|
tabindex="0"
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
Update Configs
|
Add Tags
|
||||||
<span
|
<span
|
||||||
class="MuiTouchRipple-root css-8je8zh-MuiTouchRipple-root"
|
class="MuiTouchRipple-root css-8je8zh-MuiTouchRipple-root"
|
||||||
/>
|
/>
|
||||||
@@ -173,6 +173,7 @@ exports[`DetailsTab Render 1`] = `
|
|||||||
aria-haspopup="menu"
|
aria-haspopup="menu"
|
||||||
aria-label="select action"
|
aria-label="select action"
|
||||||
class="MuiButtonBase-root MuiButton-root MuiButton-contained MuiButton-containedPrimary MuiButton-sizeSmall MuiButton-containedSizeSmall MuiButtonGroup-grouped MuiButtonGroup-groupedHorizontal MuiButtonGroup-groupedContained MuiButtonGroup-groupedContainedHorizontal MuiButtonGroup-groupedContainedPrimary MuiButton-root MuiButton-contained MuiButton-containedPrimary MuiButton-sizeSmall MuiButton-containedSizeSmall MuiButtonGroup-grouped MuiButtonGroup-groupedHorizontal MuiButtonGroup-groupedContained MuiButtonGroup-groupedContainedHorizontal MuiButtonGroup-groupedContainedPrimary css-11qr2p8-MuiButtonBase-root-MuiButton-root"
|
class="MuiButtonBase-root MuiButton-root MuiButton-contained MuiButton-containedPrimary MuiButton-sizeSmall MuiButton-containedSizeSmall MuiButtonGroup-grouped MuiButtonGroup-groupedHorizontal MuiButtonGroup-groupedContained MuiButtonGroup-groupedContainedHorizontal MuiButtonGroup-groupedContainedPrimary MuiButton-root MuiButton-contained MuiButton-containedPrimary MuiButton-sizeSmall MuiButton-containedSizeSmall MuiButtonGroup-grouped MuiButtonGroup-groupedHorizontal MuiButtonGroup-groupedContained MuiButtonGroup-groupedContainedHorizontal MuiButtonGroup-groupedContainedPrimary css-11qr2p8-MuiButtonBase-root-MuiButton-root"
|
||||||
|
data-testid="dropdown-button-expand"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -252,7 +252,7 @@ exports[`FleetStatus Render 1`] = `
|
|||||||
tabindex="0"
|
tabindex="0"
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
Update Configs
|
Add Tags
|
||||||
<span
|
<span
|
||||||
class="MuiTouchRipple-root css-8je8zh-MuiTouchRipple-root"
|
class="MuiTouchRipple-root css-8je8zh-MuiTouchRipple-root"
|
||||||
/>
|
/>
|
||||||
@@ -261,6 +261,7 @@ exports[`FleetStatus Render 1`] = `
|
|||||||
aria-haspopup="menu"
|
aria-haspopup="menu"
|
||||||
aria-label="select action"
|
aria-label="select action"
|
||||||
class="MuiButtonBase-root MuiButton-root MuiButton-contained MuiButton-containedPrimary MuiButton-sizeSmall MuiButton-containedSizeSmall MuiButtonGroup-grouped MuiButtonGroup-groupedHorizontal MuiButtonGroup-groupedContained MuiButtonGroup-groupedContainedHorizontal MuiButtonGroup-groupedContainedPrimary MuiButton-root MuiButton-contained MuiButton-containedPrimary MuiButton-sizeSmall MuiButton-containedSizeSmall MuiButtonGroup-grouped MuiButtonGroup-groupedHorizontal MuiButtonGroup-groupedContained MuiButtonGroup-groupedContainedHorizontal MuiButtonGroup-groupedContainedPrimary css-11qr2p8-MuiButtonBase-root-MuiButton-root"
|
class="MuiButtonBase-root MuiButton-root MuiButton-contained MuiButton-containedPrimary MuiButton-sizeSmall MuiButton-containedSizeSmall MuiButtonGroup-grouped MuiButtonGroup-groupedHorizontal MuiButtonGroup-groupedContained MuiButtonGroup-groupedContainedHorizontal MuiButtonGroup-groupedContainedPrimary MuiButton-root MuiButton-contained MuiButton-containedPrimary MuiButton-sizeSmall MuiButton-containedSizeSmall MuiButtonGroup-grouped MuiButtonGroup-groupedHorizontal MuiButtonGroup-groupedContained MuiButtonGroup-groupedContainedHorizontal MuiButtonGroup-groupedContainedPrimary css-11qr2p8-MuiButtonBase-root-MuiButton-root"
|
||||||
|
data-testid="dropdown-button-expand"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
const TransformModalMock = jest.fn().mockImplementation(() => {
|
|
||||||
return <div data-testid="transform-modal" />
|
|
||||||
});
|
|
||||||
|
|
||||||
export default TransformModalMock;
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
||||||
|
|
||||||
exports[`TransformModal Render 1`] = `
|
|
||||||
<div
|
|
||||||
aria-hidden="true"
|
|
||||||
/>
|
|
||||||
`;
|
|
||||||
@@ -1,110 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
import {
|
|
||||||
Button,
|
|
||||||
Checkbox,
|
|
||||||
Dialog,
|
|
||||||
DialogTitle,
|
|
||||||
DialogContentText,
|
|
||||||
DialogActions,
|
|
||||||
DialogContent,
|
|
||||||
FormGroup,
|
|
||||||
FormControlLabel,
|
|
||||||
} from '@material-ui/core';
|
|
||||||
import TextInputList from "../Controls/TextInputList";
|
|
||||||
|
|
||||||
const TransformModal = ({
|
|
||||||
open,
|
|
||||||
close,
|
|
||||||
title,
|
|
||||||
body,
|
|
||||||
data,
|
|
||||||
setData,
|
|
||||||
submit
|
|
||||||
}) => {
|
|
||||||
const handleClick = () => {
|
|
||||||
close();
|
|
||||||
submit();
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleChange = (key, value) => {
|
|
||||||
setData((data) => {
|
|
||||||
const { [key]: toChange, ...rest } = data;
|
|
||||||
switch (data[key].type) {
|
|
||||||
case "boolean":
|
|
||||||
toChange.value = !toChange.value;
|
|
||||||
break;
|
|
||||||
case "list.string":
|
|
||||||
toChange.value = value;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
[key]: toChange,
|
|
||||||
...rest
|
|
||||||
};
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Dialog
|
|
||||||
open={open}
|
|
||||||
onClose={close}
|
|
||||||
>
|
|
||||||
<DialogTitle id="alert-dialog-title">
|
|
||||||
{title}
|
|
||||||
</DialogTitle>
|
|
||||||
<DialogContent>
|
|
||||||
{body && <DialogContentText>
|
|
||||||
{body}
|
|
||||||
</DialogContentText>}
|
|
||||||
<FormGroup>
|
|
||||||
{Object.entries(data).map((([key, value]) => {
|
|
||||||
switch (value.type) {
|
|
||||||
case "boolean":
|
|
||||||
return (
|
|
||||||
<FormControlLabel
|
|
||||||
key={key}
|
|
||||||
label={value.label}
|
|
||||||
control={
|
|
||||||
<Checkbox
|
|
||||||
checked={data[key].value}
|
|
||||||
onChange={() => handleChange(key)}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
case "list.string":
|
|
||||||
return (
|
|
||||||
<TextInputList
|
|
||||||
key={key}
|
|
||||||
label={value.label}
|
|
||||||
onChange={(list) => handleChange(key, list)}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
default:
|
|
||||||
return <></>;
|
|
||||||
}
|
|
||||||
}))}
|
|
||||||
</FormGroup>
|
|
||||||
</DialogContent>
|
|
||||||
<DialogActions>
|
|
||||||
<Button
|
|
||||||
label="Test"
|
|
||||||
onClick={close}
|
|
||||||
>
|
|
||||||
Cancel
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
variant="contained"
|
|
||||||
color="secondary"
|
|
||||||
onClick={handleClick}
|
|
||||||
autoFocus
|
|
||||||
>
|
|
||||||
Submit
|
|
||||||
</Button>
|
|
||||||
</DialogActions>
|
|
||||||
</Dialog>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default TransformModal;
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
import { render, waitFor } from "@testing-library/react";
|
|
||||||
|
|
||||||
import TransformModal from ".";
|
|
||||||
import addSnapshotSerializer from "../../utils/snapshot";
|
|
||||||
|
|
||||||
const data = {
|
|
||||||
test: {
|
|
||||||
label: "Test field",
|
|
||||||
value: false,
|
|
||||||
type: "boolean",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
describe("TransformModal", () => {
|
|
||||||
beforeAll(() => {
|
|
||||||
addSnapshotSerializer(expect);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Render", async () => {
|
|
||||||
|
|
||||||
const { container } = render(
|
|
||||||
<TransformModal
|
|
||||||
open={true}
|
|
||||||
close={() => {}}
|
|
||||||
title="Title"
|
|
||||||
body="Body"
|
|
||||||
data={data}
|
|
||||||
setData={() => {}}
|
|
||||||
submit={() => {}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
await waitFor(() => {
|
|
||||||
/* render */
|
|
||||||
});
|
|
||||||
expect(container).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("properly renders a checkbox for a boolean", async () => {
|
|
||||||
const { getByText } = render(
|
|
||||||
<TransformModal
|
|
||||||
open={true}
|
|
||||||
close={() => {}}
|
|
||||||
title="Title"
|
|
||||||
body="Body"
|
|
||||||
data={data}
|
|
||||||
setData={() => {}}
|
|
||||||
submit={() => {}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
await waitFor(() => {
|
|
||||||
/* render */
|
|
||||||
});
|
|
||||||
expect(getByText("Test field")).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -62,9 +62,9 @@ const fleetsAPI = {
|
|||||||
getFleetVehicles: async () => {
|
getFleetVehicles: async () => {
|
||||||
return { data: vehicles };
|
return { data: vehicles };
|
||||||
},
|
},
|
||||||
addFleetVehicles: async (_name, vehicle) => {
|
addFleetVehicles: async (_name, payload) => {
|
||||||
vehicles.push(...vehicle.vins);
|
payload.vins && vehicles.push(...payload.vins);
|
||||||
return vehicle;
|
return payload;
|
||||||
},
|
},
|
||||||
deleteFleetVehicle: async (_name, vehicle) => {
|
deleteFleetVehicle: async (_name, vehicle) => {
|
||||||
const index = vehicles.findIndex(element => element === vehicle.vin);
|
const index = vehicles.findIndex(element => element === vehicle.vin);
|
||||||
|
|||||||
@@ -98,6 +98,12 @@ const trexLogs = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const vehiclesAPI = {
|
const vehiclesAPI = {
|
||||||
|
addTags: async (vins, tags) => {
|
||||||
|
return {
|
||||||
|
vins,
|
||||||
|
tags,
|
||||||
|
};
|
||||||
|
},
|
||||||
addVehicle: async (vehicle) => {
|
addVehicle: async (vehicle) => {
|
||||||
data.push(vehicle);
|
data.push(vehicle);
|
||||||
return vehicle;
|
return vehicle;
|
||||||
@@ -161,6 +167,9 @@ const vehiclesAPI = {
|
|||||||
if (index >= 0) data[index] = vehicle;
|
if (index >= 0) data[index] = vehicle;
|
||||||
return vehicle;
|
return vehicle;
|
||||||
},
|
},
|
||||||
|
updateConfig: async (vin, vehicle) => {
|
||||||
|
return { message: "Sent" };
|
||||||
|
},
|
||||||
getCANSignals: async (vin, vehicle) => {
|
getCANSignals: async (vin, vehicle) => {
|
||||||
return signals;
|
return signals;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
addQueryParams, errorHandler, fetchRespHandler, getAuthHeaderOptions
|
addQueryParams, errorHandler, fetchRespHandler, getAuthHeaderOptions
|
||||||
} from "../utils/http";
|
} from "../utils/http";
|
||||||
|
|
||||||
const API_ENDPOINT = process.env.REACT_APP_OTA_SERVICE_URL;
|
const API_ENDPOINT = process.env.REACT_APP_OTA_SERVICE_URL;
|
||||||
@@ -28,13 +28,14 @@ const fleetsAPI = {
|
|||||||
.then(fetchRespHandler)
|
.then(fetchRespHandler)
|
||||||
.catch(errorHandler),
|
.catch(errorHandler),
|
||||||
|
|
||||||
getFleets: async (search, token) =>
|
getFleets: async (search, token, controller) =>
|
||||||
fetch(addQueryParams(`${API_ENDPOINT}/fleets`, search), {
|
fetch(addQueryParams(`${API_ENDPOINT}/fleets`, search), {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: Object.assign(
|
headers: Object.assign(
|
||||||
{ "Content-Type": "application/json" },
|
{ "Content-Type": "application/json" },
|
||||||
getAuthHeaderOptions(token)
|
getAuthHeaderOptions(token)
|
||||||
),
|
),
|
||||||
|
signal: controller?.signal,
|
||||||
})
|
})
|
||||||
.then(fetchRespHandler)
|
.then(fetchRespHandler)
|
||||||
.catch(errorHandler),
|
.catch(errorHandler),
|
||||||
|
|||||||
@@ -3,3 +3,7 @@
|
|||||||
// expect(element).toHaveTextContent(/react/i)
|
// expect(element).toHaveTextContent(/react/i)
|
||||||
// learn more: https://github.com/testing-library/jest-dom
|
// learn more: https://github.com/testing-library/jest-dom
|
||||||
import "@testing-library/jest-dom";
|
import "@testing-library/jest-dom";
|
||||||
|
|
||||||
|
if (typeof window.URL.createObjectURL === 'undefined') {
|
||||||
|
window.URL.createObjectURL = jest.fn();
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user