CEC-4882: add send sms bulk action (#416)
* CEC-4882: add send sms bulk action * npm audit fix * upgrade to version specified by react-scripts * override transitive package * hoist ejs override * add dep * force blackduck scan
This commit is contained in:
@@ -2,7 +2,7 @@ jest.mock("../../Contexts/UserContext");
|
||||
jest.mock("../../Contexts/StatusContext");
|
||||
jest.mock("../../../services/fleetsAPI");
|
||||
|
||||
import React, { useEffect, useState } from "react";
|
||||
import React, { useState } from "react";
|
||||
import {
|
||||
render,
|
||||
act,
|
||||
|
||||
77
src/components/BulkActions/actions/SendSMS.jsx
Normal file
77
src/components/BulkActions/actions/SendSMS.jsx
Normal file
@@ -0,0 +1,77 @@
|
||||
import { forwardRef, useImperativeHandle, useState } from "react";
|
||||
import {
|
||||
TextField,
|
||||
FormControlLabel,
|
||||
Checkbox,
|
||||
} from "@material-ui/core";
|
||||
import { useStatusContext } from "../../Contexts/StatusContext";
|
||||
import { useUserContext } from "../../Contexts/UserContext";
|
||||
import TaskRunner from "../../../utils/taskRunner";
|
||||
import vehiclesAPI from "../../../services/vehiclesAPI";
|
||||
import smsAPI from "../../../services/smsAPI";
|
||||
import { SMS } from "../../Contexts/SMSContext";
|
||||
|
||||
export default forwardRef(({
|
||||
vins,
|
||||
vinCSV,
|
||||
}, ref) => {
|
||||
const [shouldAwait, setShouldAwait] = useState(false);
|
||||
const [smsMessage, setSmsMessage] = useState("");
|
||||
const { setMessage } = useStatusContext();
|
||||
const { token: { idToken: { jwtToken: token } } } = useUserContext();
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
async submit() {
|
||||
return new Promise((resolve) => {
|
||||
const taskRunner = new TaskRunner(5, vins.length);
|
||||
|
||||
vins.forEach((vin) => {
|
||||
taskRunner.push(async () => {
|
||||
|
||||
const { iccid } = await vehiclesAPI.getVehicle(vin, token);
|
||||
if (!iccid) {
|
||||
throw new Error(`Missing ICC ID for ${vin}`);
|
||||
}
|
||||
|
||||
const sms = new SMS(smsMessage, iccid, shouldAwait);
|
||||
const result = await smsAPI.send(sms, token);
|
||||
if (result.error) {
|
||||
throw new Error(result.message);
|
||||
}
|
||||
|
||||
return vin
|
||||
})
|
||||
.then((vin) => setMessage(`Sent message to ${vin}`))
|
||||
.catch((error) => setMessage(`Failed to send SMS: ${error}`));
|
||||
});
|
||||
|
||||
taskRunner.onComplete()
|
||||
.then((responses) => {
|
||||
setMessage(`Sent ${vins.length} SMS messages`);
|
||||
resolve(responses);
|
||||
});
|
||||
});
|
||||
},
|
||||
}));
|
||||
|
||||
return (
|
||||
<div>
|
||||
<p>
|
||||
You are about to send SMS to the following vins: {vinCSV}.
|
||||
</p>
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Message"
|
||||
variant="filled"
|
||||
onChange={(event) => setSmsMessage(event.target.value)}
|
||||
/>
|
||||
<FormControlLabel
|
||||
label="Await"
|
||||
control={<Checkbox
|
||||
checked={shouldAwait}
|
||||
onChange={() => setShouldAwait(shouldAwait => !shouldAwait)}
|
||||
/>}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
67
src/components/BulkActions/actions/SendSMS.test.jsx
Normal file
67
src/components/BulkActions/actions/SendSMS.test.jsx
Normal file
@@ -0,0 +1,67 @@
|
||||
jest.mock("../../Contexts/UserContext");
|
||||
jest.mock("../../Contexts/StatusContext");
|
||||
jest.mock("../../../services/vehiclesAPI");
|
||||
jest.mock("../../../services/smsAPI");
|
||||
|
||||
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 SendSMS from "./SendSMS";
|
||||
import vehiclesAPI from "../../../services/vehiclesAPI";
|
||||
import smsAPI from "../../../services/smsAPI";
|
||||
|
||||
jest.mock('react', () => ({
|
||||
...jest.requireActual('react'),
|
||||
useState: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('@material-ui/core/FormControlLabel', () => {
|
||||
const React = require('react');
|
||||
return () => <div data-testid="mock-form-control-label" />;
|
||||
});
|
||||
|
||||
jest.mock('@material-ui/core/TextField', () => {
|
||||
const React = require('react');
|
||||
return () => <div data-testid="mock-text-field" />;
|
||||
});
|
||||
|
||||
jest.mock('@material-ui/core/Checkbox', () => {
|
||||
const React = require('react');
|
||||
return () => <div data-testid="mock-checkbox" />;
|
||||
});
|
||||
|
||||
describe("BulkActions/SendSMS", () => {
|
||||
beforeAll(() => {
|
||||
setToken(TEST_AUTH_OBJECT_FISKER);
|
||||
});
|
||||
|
||||
it("makes request to send multiple SMS messages", async () => {
|
||||
useState
|
||||
.mockReturnValueOnce([true, jest.fn()])
|
||||
.mockReturnValueOnce(["body", jest.fn()]);
|
||||
const vehiclesAPIMock = jest.spyOn(vehiclesAPI, "getVehicle");
|
||||
const smsAPIMock = jest.spyOn(smsAPI, "send");
|
||||
const ref = React.createRef();
|
||||
|
||||
render(
|
||||
<StatusProvider>
|
||||
<UserProvider>
|
||||
<SendSMS
|
||||
ref={ref}
|
||||
vins={["3C4PDCBG0ET127145"]}
|
||||
vinCSV=""
|
||||
/>
|
||||
</UserProvider>
|
||||
</StatusProvider>
|
||||
);
|
||||
|
||||
await act(async () => ref.current.submit());
|
||||
expect(vehiclesAPIMock).toHaveBeenCalledTimes(1);
|
||||
expect(smsAPIMock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
@@ -1,5 +1,7 @@
|
||||
import { useEffect, useState, useRef, Suspense, lazy } from "react";
|
||||
import { hasRole, Permissions } from "../../utils/roles";
|
||||
import DropDownButton from "../Controls/DropDownButton";
|
||||
import { useUserContext } from "../Contexts/UserContext";
|
||||
import { Modal } from "./Modal";
|
||||
|
||||
// Code-splitting individual actions
|
||||
@@ -8,6 +10,7 @@ const AddTags = lazy(() => import("./actions/AddTags"));
|
||||
const AddToFleet = lazy(() => import("./actions/AddToFleet"));
|
||||
const DeleteVehicles = lazy(() => import("./actions/DeleteVehicles"));
|
||||
const UpdateConfig = lazy(() => import("./actions/UpdateConfig"));
|
||||
const SendSMS = lazy(() => import("./actions/SendSMS"));
|
||||
|
||||
export default function BulkActions({
|
||||
vins = [],
|
||||
@@ -16,6 +19,7 @@ export default function BulkActions({
|
||||
const [title, setTitle] = useState("Action");
|
||||
const [active, setActive] = useState(null);
|
||||
const activeRef = useRef();
|
||||
const { groups, providers } = useUserContext();
|
||||
|
||||
const filteredActions = [
|
||||
{
|
||||
@@ -41,7 +45,13 @@ export default function BulkActions({
|
||||
name: "Update Config",
|
||||
disabled: false,
|
||||
trigger: () => setActive("updateConfig"),
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "sms",
|
||||
name: "Send SMS",
|
||||
disabled: !hasRole(groups, Permissions.FiskerCreate, providers),
|
||||
trigger: () => setActive("sms"),
|
||||
},
|
||||
].filter((action) => actions.includes(action.id));
|
||||
|
||||
const payload = {
|
||||
@@ -80,6 +90,7 @@ export default function BulkActions({
|
||||
{active === "addToFleet" && <AddToFleet {...payload} />}
|
||||
{active === "deleteVehicles" && <DeleteVehicles {...payload} />}
|
||||
{active === "updateConfig" && <UpdateConfig {...payload} />}
|
||||
{active === "sms" && <SendSMS {...payload} />}
|
||||
</section>
|
||||
</Suspense>
|
||||
</Modal>
|
||||
|
||||
@@ -468,6 +468,7 @@ const expectedVehicleData = {
|
||||
max_disk_buffer_size: 2,
|
||||
filters: expectedFilters,
|
||||
},
|
||||
iccid: "8988300000000000000",
|
||||
connected: true,
|
||||
connectedHMI: false,
|
||||
};
|
||||
@@ -487,6 +488,7 @@ const expectedVehiclesData = [
|
||||
max_disk_buffer_size: 2,
|
||||
filters: expectedFilters,
|
||||
},
|
||||
iccid: "8988300000000000000",
|
||||
connected: true,
|
||||
connectedHMI: false,
|
||||
},
|
||||
|
||||
@@ -96,7 +96,7 @@ const MainForm = ({ name }) => {
|
||||
</Tooltip>
|
||||
</Grid>
|
||||
<Grid item md={12} className={classes.textCenterAlign}>
|
||||
<BulkActions vins={fleet.vehicles} actions={["addTags", "updateConfig"]} />
|
||||
<BulkActions vins={fleet.vehicles} actions={["addTags", "updateConfig", "sms"]} />
|
||||
</Grid>
|
||||
</Grid>
|
||||
<DeleteConfirmation message={name} open={showDeleteModal} close={() => setShowDeleteModal(false)} deleteFunction={onDelete} />
|
||||
|
||||
Reference in New Issue
Block a user