CEC-5421: remove delete functionality (#482)
This commit is contained in:
@@ -11842,7 +11842,6 @@ exports[`App Route /vehicle-status authenticated 1`] = `
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div />
|
<div />
|
||||||
<div />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,55 +0,0 @@
|
|||||||
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(({
|
|
||||||
ids,
|
|
||||||
idCSV,
|
|
||||||
}, ref) => {
|
|
||||||
const { setMessage } = useStatusContext();
|
|
||||||
const { token: { idToken: { jwtToken: token } } } = useUserContext();
|
|
||||||
|
|
||||||
useImperativeHandle(ref, () => ({
|
|
||||||
async submit() {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const taskRunner = new TaskRunner(5, ids.length);
|
|
||||||
let errorCount = 0;
|
|
||||||
|
|
||||||
const task = (vin, index) => {
|
|
||||||
const progressMessage = `${index + 1}/${ids.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));
|
|
||||||
}
|
|
||||||
|
|
||||||
ids.forEach((vin, i) => {
|
|
||||||
taskRunner.push(task(vin, i));
|
|
||||||
});
|
|
||||||
|
|
||||||
taskRunner.onComplete().then((responses) => {
|
|
||||||
const completeMessage = `${ids.length - errorCount}/${ids.length}`;
|
|
||||||
setMessage(`Successfully deleted ${completeMessage} vehicles.`);
|
|
||||||
resolve(responses);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<p>
|
|
||||||
You are about to delete the following VINs: {idCSV}.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
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}
|
|
||||||
ids={["TESTVIN123456789a", "TESTVIN123456789b", "TESTVIN123456789c"]}
|
|
||||||
idCSV=""
|
|
||||||
/>
|
|
||||||
</UserProvider>
|
|
||||||
</StatusProvider>
|
|
||||||
);
|
|
||||||
|
|
||||||
await act(async () => ref.current.submit());
|
|
||||||
expect(api).toHaveBeenCalledTimes(3);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -9,7 +9,6 @@ import truncateCSV from "../../utils/truncateCSV";
|
|||||||
// https://react.dev/reference/react/lazy
|
// https://react.dev/reference/react/lazy
|
||||||
const AddTags = lazy(() => import("./actions/AddTags"));
|
const AddTags = lazy(() => import("./actions/AddTags"));
|
||||||
const AddToFleet = lazy(() => import("./actions/AddToFleet"));
|
const AddToFleet = lazy(() => import("./actions/AddToFleet"));
|
||||||
const DeleteVehicles = lazy(() => import("./actions/DeleteVehicles"));
|
|
||||||
const UpdateConfig = lazy(() => import("./actions/UpdateConfig"));
|
const UpdateConfig = lazy(() => import("./actions/UpdateConfig"));
|
||||||
const SendSMS = lazy(() => import("./actions/SendSMS"));
|
const SendSMS = lazy(() => import("./actions/SendSMS"));
|
||||||
const Cancel = lazy(() => import("./actions/Cancel"));
|
const Cancel = lazy(() => import("./actions/Cancel"));
|
||||||
@@ -41,12 +40,6 @@ export default function BulkActions({
|
|||||||
disabled: false,
|
disabled: false,
|
||||||
trigger: () => setActive("addToFleet"),
|
trigger: () => setActive("addToFleet"),
|
||||||
},
|
},
|
||||||
{
|
|
||||||
id: "deleteVehicles",
|
|
||||||
name: "Delete",
|
|
||||||
disabled: false,
|
|
||||||
trigger: () => setActive("deleteVehicles"),
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
id: "updateConfig",
|
id: "updateConfig",
|
||||||
name: "Update Config",
|
name: "Update Config",
|
||||||
@@ -125,7 +118,6 @@ export default function BulkActions({
|
|||||||
<section>
|
<section>
|
||||||
{active === "addTags" && <AddTags {...payload} />}
|
{active === "addTags" && <AddTags {...payload} />}
|
||||||
{active === "addToFleet" && <AddToFleet {...payload} />}
|
{active === "addToFleet" && <AddToFleet {...payload} />}
|
||||||
{active === "deleteVehicles" && <DeleteVehicles {...payload} />}
|
|
||||||
{active === "updateConfig" && <UpdateConfig {...payload} />}
|
{active === "updateConfig" && <UpdateConfig {...payload} />}
|
||||||
{active === "sms" && <SendSMS {...payload} />}
|
{active === "sms" && <SendSMS {...payload} />}
|
||||||
{active === "cancel" && <Cancel {...payload} />}
|
{active === "cancel" && <Cancel {...payload} />}
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ const MainForm = () => {
|
|||||||
<AddCircleIcon fontSize="large" />
|
<AddCircleIcon fontSize="large" />
|
||||||
</Link>
|
</Link>
|
||||||
</RoleWrap>
|
</RoleWrap>
|
||||||
<BulkActions ids={selectedVins} actions={["addTags", "addToFleet", "deleteVehicles", "updateConfig"]} />
|
<BulkActions ids={selectedVins} actions={["addTags", "addToFleet", "updateConfig"]} />
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item md={4} className={classes.textCenterAlign}>
|
<Grid item md={4} className={classes.textCenterAlign}>
|
||||||
<SearchField classes={classes} onSearch={handleSearch} savedSearchValue={query} />
|
<SearchField classes={classes} onSearch={handleSearch} savedSearchValue={query} />
|
||||||
|
|||||||
@@ -203,7 +203,6 @@ exports[`VehicleDetailsTab Render 1`] = `
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div />
|
<div />
|
||||||
<div />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
import { Box, Checkbox, FormControlLabel, Grid, Tooltip } from "@material-ui/core";
|
import { Box, Checkbox, FormControlLabel, Grid, Tooltip } from "@material-ui/core";
|
||||||
import DeleteIcon from "@material-ui/icons/Delete";
|
|
||||||
import EditIcon from "@material-ui/icons/Edit";
|
import EditIcon from "@material-ui/icons/Edit";
|
||||||
import UploadIcon from '@mui/icons-material/Upload';
|
import UploadIcon from '@mui/icons-material/Upload';
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { Redirect } from "react-router";
|
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
|
|
||||||
import { logger } from "../../../../services/monitoring";
|
import { logger } from "../../../../services/monitoring";
|
||||||
@@ -16,16 +14,13 @@ import {
|
|||||||
VehicleProvider
|
VehicleProvider
|
||||||
} from "../../../Contexts/VehicleContext";
|
} from "../../../Contexts/VehicleContext";
|
||||||
import { RoleWrap } from "../../../Controls/RoleWrap";
|
import { RoleWrap } from "../../../Controls/RoleWrap";
|
||||||
import DeleteConfirmation from "../../../DeleteConfirmation";
|
|
||||||
import GeneralConfirmation from "../../../GeneralConfirmation";
|
import GeneralConfirmation from "../../../GeneralConfirmation";
|
||||||
import useStyles from "../../../useStyles";
|
import useStyles from "../../../useStyles";
|
||||||
|
|
||||||
const MainForm = ({ vin }) => {
|
const MainForm = ({ vin }) => {
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
const { setMessage } = useStatusContext();
|
const { setMessage } = useStatusContext();
|
||||||
const { vehicle, getVehicle, deleteVehicle, uploadConfig } = useVehicleContext();
|
const { vehicle, getVehicle, uploadConfig } = useVehicleContext();
|
||||||
const [redirect, setRedirect] = useState(null);
|
|
||||||
const [showDeleteModal, setShowDeleteModal] = useState(false);
|
|
||||||
const [showUploadConfigModal, setShowUploadConfigModal] = useState(false);
|
const [showUploadConfigModal, setShowUploadConfigModal] = useState(false);
|
||||||
const [forced, setForced] = useState(false);
|
const [forced, setForced] = useState(false);
|
||||||
const {
|
const {
|
||||||
@@ -55,17 +50,6 @@ const MainForm = ({ vin }) => {
|
|||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [token]);
|
}, [token]);
|
||||||
|
|
||||||
const onDelete = async () => {
|
|
||||||
try {
|
|
||||||
await deleteVehicle(vin, token);
|
|
||||||
setMessage(`Deleted ${vin}`);
|
|
||||||
setRedirect(`/vehicles`);
|
|
||||||
} catch (e) {
|
|
||||||
setMessage(e.message);
|
|
||||||
logger.warn(e.stack);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const onUploadConfig = async () => {
|
const onUploadConfig = async () => {
|
||||||
try {
|
try {
|
||||||
await uploadConfig(vin, forced, token);
|
await uploadConfig(vin, forced, token);
|
||||||
@@ -99,10 +83,6 @@ const MainForm = ({ vin }) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (redirect && redirect.length > 0) {
|
|
||||||
return <Redirect to={redirect} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={clsx(classes.paper, classes.tableSize)}>
|
<div className={clsx(classes.paper, classes.tableSize)}>
|
||||||
<Grid container className={classes.root} spacing={2}>
|
<Grid container className={classes.root} spacing={2}>
|
||||||
@@ -232,25 +212,8 @@ const MainForm = ({ vin }) => {
|
|||||||
</Link>
|
</Link>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</RoleWrap>
|
</RoleWrap>
|
||||||
<RoleWrap
|
|
||||||
groups={groups}
|
|
||||||
providers={providers}
|
|
||||||
rolesPerProvider={Permissions.FiskerDelete}
|
|
||||||
>
|
|
||||||
<Tooltip key={`delete-${vin}`} title={`Delete "${vin}"`}>
|
|
||||||
<Link to="#" onClick={() => setShowDeleteModal(true)}>
|
|
||||||
<DeleteIcon aria-label={`Delete "${vin}"`} fontSize="large" />
|
|
||||||
</Link>
|
|
||||||
</Tooltip>
|
|
||||||
</RoleWrap>
|
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
<DeleteConfirmation
|
|
||||||
message={vin}
|
|
||||||
open={showDeleteModal}
|
|
||||||
close={() => setShowDeleteModal(false)}
|
|
||||||
deleteFunction={onDelete}
|
|
||||||
/>
|
|
||||||
<GeneralConfirmation
|
<GeneralConfirmation
|
||||||
message={"push config update to:" + vin}
|
message={"push config update to:" + vin}
|
||||||
open={showUploadConfigModal}
|
open={showUploadConfigModal}
|
||||||
|
|||||||
@@ -211,7 +211,6 @@ exports[`DetailsTab Render 1`] = `
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div />
|
<div />
|
||||||
<div />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -393,7 +393,6 @@ exports[`CarStatus Render 1`] = `
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div />
|
<div />
|
||||||
<div />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -243,20 +243,6 @@ export const VehicleProvider = ({ children }) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const deleteVehicle = async (vin, token) => {
|
|
||||||
try {
|
|
||||||
setBusy(true);
|
|
||||||
validateVIN(vin);
|
|
||||||
|
|
||||||
const result = await api.deleteVehicle(vin, token);
|
|
||||||
if (result.error)
|
|
||||||
throw new Error(`Delete vehicle error. ${result.message}`);
|
|
||||||
return result;
|
|
||||||
} finally {
|
|
||||||
setBusy(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const getCANSignals = async (vin, token) => {
|
const getCANSignals = async (vin, token) => {
|
||||||
try {
|
try {
|
||||||
setBusy(true);
|
setBusy(true);
|
||||||
@@ -313,7 +299,6 @@ export const VehicleProvider = ({ children }) => {
|
|||||||
fleets,
|
fleets,
|
||||||
totalFleets,
|
totalFleets,
|
||||||
addVehicle,
|
addVehicle,
|
||||||
deleteVehicle,
|
|
||||||
getConnections,
|
getConnections,
|
||||||
getCANSignals,
|
getCANSignals,
|
||||||
getECUs,
|
getECUs,
|
||||||
|
|||||||
@@ -303,80 +303,6 @@ describe("VehicleContext", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("deleteVehicle", () => {
|
|
||||||
beforeEach(async () => {
|
|
||||||
const TestComp = () => {
|
|
||||||
const { busy, deleteVehicle } = useVehicleContext();
|
|
||||||
const { message, setMessage } = useStatusContext();
|
|
||||||
const deleteV = async (name) => {
|
|
||||||
try {
|
|
||||||
await deleteVehicle(name);
|
|
||||||
} catch (e) {
|
|
||||||
setMessage(e.message);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div data-testid="error">{message}</div>
|
|
||||||
<div data-testid="busy">{busy.toString()}</div>
|
|
||||||
<button
|
|
||||||
data-testid="deleteVehicleNull"
|
|
||||||
onClick={() => deleteV(null)}
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
data-testid="deleteVehicleNonexistent"
|
|
||||||
onClick={() => deleteV("11111111111111111")}
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
data-testid="deleteVehicle"
|
|
||||||
onClick={() => deleteV("3C4PDCBG0ET127145")}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
render(
|
|
||||||
<StatusProvider>
|
|
||||||
<VehicleProvider>
|
|
||||||
<TestComp />
|
|
||||||
</VehicleProvider>
|
|
||||||
</StatusProvider>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
cleanup();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("initial state", () => {
|
|
||||||
checkBaseResults("", "false");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("deleteVehicleNull", async () => {
|
|
||||||
fireEvent.click(screen.getByTestId("deleteVehicleNull"));
|
|
||||||
await waitFor(() =>
|
|
||||||
expect(screen.getByTestId("busy").innerHTML).toEqual("false")
|
|
||||||
);
|
|
||||||
checkBaseResults("Invalid VIN", "false");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("deleteVehicleNonexistent", async () => {
|
|
||||||
fireEvent.click(screen.getByTestId("deleteVehicleNonexistent"));
|
|
||||||
await waitFor(() =>
|
|
||||||
expect(screen.getByTestId("busy").innerHTML).toEqual("false")
|
|
||||||
);
|
|
||||||
checkBaseResults("", "false");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("deleteVehicle", async () => {
|
|
||||||
fireEvent.click(screen.getByTestId("deleteVehicle"));
|
|
||||||
await waitFor(() =>
|
|
||||||
expect(screen.getByTestId("busy").innerHTML).toEqual("false")
|
|
||||||
);
|
|
||||||
checkBaseResults("", "false");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("sendCommand", () => {
|
describe("sendCommand", () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const TestComp = () => {
|
const TestComp = () => {
|
||||||
|
|||||||
@@ -179,7 +179,7 @@ const MainForm = ({ name }) => {
|
|||||||
</Link>
|
</Link>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item md={4}>
|
<Grid item md={4}>
|
||||||
<BulkActions ids={selected} actions={["addTags", "deleteVehicles", "sms", "updateConfig", "remoteCommand", "diagnostic"]} />
|
<BulkActions ids={selected} actions={["addTags", "sms", "updateConfig", "remoteCommand", "diagnostic"]} />
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item md={7} align="right" className={classes.textCenterAlign}>
|
<Grid item md={7} align="right" className={classes.textCenterAlign}>
|
||||||
<SearchField classes={classes} onSearch={handleSearch} />
|
<SearchField classes={classes} onSearch={handleSearch} />
|
||||||
|
|||||||
@@ -29,17 +29,6 @@ const vehiclesAPI = {
|
|||||||
.then(fetchRespHandler)
|
.then(fetchRespHandler)
|
||||||
.catch(errorHandler),
|
.catch(errorHandler),
|
||||||
|
|
||||||
deleteVehicle: async (vin, token) =>
|
|
||||||
fetch(`${API_ENDPOINT}/vehicle/${vin}`, {
|
|
||||||
method: "DELETE",
|
|
||||||
headers: Object.assign(
|
|
||||||
{ "Content-Type": "application/json" },
|
|
||||||
getAuthHeaderOptions(token)
|
|
||||||
),
|
|
||||||
})
|
|
||||||
.then(fetchRespHandler)
|
|
||||||
.catch(errorHandler),
|
|
||||||
|
|
||||||
getConnections: async (vins, token) => {
|
getConnections: async (vins, token) => {
|
||||||
const u = `${API_ENDPOINT}/carsconnected`;
|
const u = `${API_ENDPOINT}/carsconnected`;
|
||||||
return fetch(u, {
|
return fetch(u, {
|
||||||
|
|||||||
Reference in New Issue
Block a user