From 8d0dbf8030dd5745cd019363dc3ea9fcb8b39319 Mon Sep 17 00:00:00 2001 From: arpanetus Date: Tue, 30 Aug 2022 03:31:26 +0600 Subject: [PATCH] CEC-2144, CEC-2338 Add deploy by fleets and fix fleets table (#192) * Add fix for fleets search * Decompose fleets table * Add deploy by fleets * Add snapshots --- .../App/__snapshots__/App.test.js.snap | 214 +++-- src/components/Contexts/CarUpdatesContext.jsx | 34 +- .../Controls/CarSelectionTable/index.jsx | 6 +- .../Controls/FleetSelectionTable/index.jsx | 199 +++++ .../Table/__snapshots__/index.test.jsx.snap | 744 +++++++++--------- src/components/Fleets/Table/index.jsx | 170 +--- .../Manifest/Deploy/FleetDeployForm.jsx | 177 +++++ .../Manifest/Deploy/VehicleDeployForm.jsx | 175 ++++ src/components/Manifest/Deploy/index.jsx | 121 +-- src/services/__mocks__/updatesAPI.js | 6 + src/services/updatesAPI.js | 27 +- 11 files changed, 1210 insertions(+), 663 deletions(-) create mode 100644 src/components/Controls/FleetSelectionTable/index.jsx create mode 100644 src/components/Manifest/Deploy/FleetDeployForm.jsx create mode 100644 src/components/Manifest/Deploy/VehicleDeployForm.jsx diff --git a/src/components/App/__snapshots__/App.test.js.snap b/src/components/App/__snapshots__/App.test.js.snap index 582864f..507ed57 100644 --- a/src/components/App/__snapshots__/App.test.js.snap +++ b/src/components/App/__snapshots__/App.test.js.snap @@ -2965,112 +2965,154 @@ exports[`App Route /package-deploy authenticated 1`] = ` class="MuiContainer-root MuiContainer-maxWidthLg" >
-
-
+ Created + 7/1/2021 10:40:07 PM + . +

+
-

- Created - 7/1/2021 10:40:07 PM - . -

-
- 0 Selected -
+ 0 Selected
-
+
+
+
diff --git a/src/components/Contexts/CarUpdatesContext.jsx b/src/components/Contexts/CarUpdatesContext.jsx index 97c2802..7638004 100644 --- a/src/components/Contexts/CarUpdatesContext.jsx +++ b/src/components/Contexts/CarUpdatesContext.jsx @@ -6,7 +6,8 @@ import { validateStatusMessage } from "../../utils/statusMessage"; const FINAL_UPDATE_STATES = ["package_install_complete"]; const CarUpdatesContext = React.createContext(); -const validateDeployCarUpdates = (data) => { + +const validateDeployClosure = (data, propertyName, errPfx) => { if (data === null) { throw new Error("No car update data"); } @@ -15,11 +16,21 @@ const validateDeployCarUpdates = (data) => { throw new Error("Manifest id required"); } - if (!data.vins || data.vins.length === 0) { - throw new Error("Cars are required"); + const { [propertyName]: value } = data; + if (!value || value.length === 0) { + throw new Error(`${errPfx} are required`); } +} + +const validateDeployCarUpdates = (data) => { + return validateDeployClosure(data, 'vins', 'Cars') }; +const validateDeployFleetUpdates = (data) => { + return validateDeployClosure(data, 'fleet_names', 'Fleets') +}; + + export const CarUpdatesProvider = ({ children }) => { const [busy, setBusy] = useState(false); const [carUpdates, setCarUpdates] = useState([]); @@ -43,6 +54,22 @@ export const CarUpdatesProvider = ({ children }) => { return result; }; + const deployFleetUpdates = async (data, token) => { + let result; + + try { + setBusy(true); + validateDeployFleetUpdates(data); + result = await api.createFleetUpdates(data, token); + if (result.error) + throw new Error(`Deploy fleet updates error. ${result.message}`); + } finally { + setBusy(false); + } + + return result; + } + const getCarUpdates = async (search, token) => { let result; @@ -204,6 +231,7 @@ export const CarUpdatesProvider = ({ children }) => { carUpdates, totalCarUpdates, deployCarUpdates, + deployFleetUpdates, getCarUpdates, getLog, getVINUpdates, diff --git a/src/components/Controls/CarSelectionTable/index.jsx b/src/components/Controls/CarSelectionTable/index.jsx index 7a5d741..219e69b 100644 --- a/src/components/Controls/CarSelectionTable/index.jsx +++ b/src/components/Controls/CarSelectionTable/index.jsx @@ -60,6 +60,7 @@ const CarSelectionTable = (props) => { onSelect, onSelectAll, } = props; + const [pageSize, setPageSize] = useLocalStorage(PAGE_SIZE, 10); const [pageIndex, setPageIndex] = useState(0); const [orderBy, setOrderBy] = useState("vin"); @@ -67,7 +68,8 @@ const CarSelectionTable = (props) => { const { getVehicles, vehicles, totalVehicles } = useVehicleContext(); const { setMessage } = useStatusContext(); const { search: searchTerm } = search; - const sortHandler = (_event, property) => { + + const handleSort = (_event, property) => { if (property === orderBy) { if (order === "asc") { setOrder("desc"); @@ -137,7 +139,7 @@ const CarSelectionTable = (props) => { orderBy={orderBy} order={order} columnData={tableColumns} - onSortRequest={sortHandler} + onSortRequest={handleSort} multiSelect={multiSelect} onSelectAll={handleSelectAll} selectCount={selected ? selected.length : 0} diff --git a/src/components/Controls/FleetSelectionTable/index.jsx b/src/components/Controls/FleetSelectionTable/index.jsx new file mode 100644 index 0000000..e75df0e --- /dev/null +++ b/src/components/Controls/FleetSelectionTable/index.jsx @@ -0,0 +1,199 @@ +import React, {useEffect, useState} from "react"; +import {Link} from 'react-router-dom'; +import {Checkbox, Table, TableBody, TableCell, TableFooter, TablePagination, TableRow} from "@material-ui/core"; +import clsx from "clsx"; + +import TableHeaderSortable from "../../Table/HeaderSortable"; +import {useStatusContext} from "../../Contexts/StatusContext"; +import {useFleetContext} from "../../Contexts/FleetContext" +import {logger} from "../../../services/monitoring"; +import {useLocalStorage} from "../../useLocalStorage"; +import PropTypes from "prop-types"; + +const tableColumns = [ + { + id: "name", + label: "Name" + }, + { + id: "log_level", + label: "Log Level" + }, + { + id: "canbus_enabled", + label: "CAN Bus Enabled" + }, + { + id: "data_logger_enabled", + label: "Data Logger Enabled" + }, + { + id: "num_vehicles", + label: "Vehicles" + }, + { + id: "num_filters", + label: "Filters" + } +]; + +const PAGE_SIZE = "FLEET_SELECTION_TABLE_PAGE_SIZE"; + +const FleetSelectionTable = (props) => { + const { + token, + classes, + search, + multiSelect, + selected, + onSelect, + onSelectAll, + } = props; + + const [pageSize, setPageSize] = useLocalStorage(PAGE_SIZE, 10); + const [pageIndex, setPageIndex] = useState(0); + const [orderBy, setOrderBy] = useState("id"); + const [order, setOrder] = useState("desc"); + const { fleets, getFleets, totalFleets } = useFleetContext(); + const { setMessage } = useStatusContext(); + const { search: searchTerm } = search; + + const handleSort = (_event, property) => { + try { + if (property === orderBy) { + if (order === "asc") { + setOrder("desc"); + } else { + setOrder("asc"); + } + } else { + setOrderBy(property); + setOrder("asc"); + } + } catch (e) { + logger.warn(e.stack); + } + }; + + 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) { + fleets.forEach((car) => { + newSelected.push(car.name); + }); + } + + onSelectAll(newSelected); + }; + + const handleSelect = (event, key) => { + if (!onSelect) return; + + onSelect(event, key); + }; + + + useEffect(() => { + const options = { search: searchTerm, + limit: pageSize, + offset: pageSize * pageIndex, + order: `${orderBy} ${order}`, + }; + (async () => { + try { + if (!token) return; + await getFleets(options, token); + } catch (e) { + setMessage(e.message); + logger.warn(e.stack); + } + })(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [token, pageIndex, pageSize, orderBy, order, searchTerm]); + + return ( +
+ + + + {fleets.map((row) => { + const isSelected = selected + ? selected.indexOf(row.name) !== -1 + : false; + return ( + + {multiSelect && ( + + handleSelect(event, row.name)} + /> + + )} + + {row.name} + + {row.log_level} + {row.canbus.enabled ? "true" : "false"} + {row.canbus.data_logger_enabled ? "true" : "false"} + {!row.vehicles ? 0 : row.vehicles.length} + {!row.canbus.filters ? 0 : row.canbus.filters.length} + ) + } + )} + + + + + + +
+
+ ); +}; + +FleetSelectionTable.propTypes = { + token: PropTypes.string.isRequired, + classes: PropTypes.object.isRequired, + search: PropTypes.object.isRequired, + multiSelect: PropTypes.bool.isRequired, + selected: PropTypes.array, + onSelect: PropTypes.func, + onSelectAll: PropTypes.func, + connectionStatus: PropTypes.bool, +} + +export default FleetSelectionTable; diff --git a/src/components/Fleets/Table/__snapshots__/index.test.jsx.snap b/src/components/Fleets/Table/__snapshots__/index.test.jsx.snap index c8e656b..5f9f641 100644 --- a/src/components/Fleets/Table/__snapshots__/index.test.jsx.snap +++ b/src/components/Fleets/Table/__snapshots__/index.test.jsx.snap @@ -24,7 +24,6 @@ exports[`FleetTable Render 1`] = ` class="MuiGrid-root makeStyles-textJustifyAlign-0 MuiGrid-item MuiGrid-grid-md-4" >
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - -
- - Name - - - - - Log Level - - - - - CAN Bus Enabled - - - - - Data Logger Enabled - - - - - Vehicles - - - - - Filters - - -
- - US-WEST - - - info - - true - - true - - 3 - - 3 -
- - US-CENTRAL - - - warn - - false - - false - - 3 - - 0 -
- - US-EAST - - - error - - true - - false - - 3 - - 0 -
+ + US-WEST + + + info + + true + + true + + 3 + + 3 +
+ + US-CENTRAL + + + warn + + false + + false + + 3 + + 0 +
+ + US-EAST + + + error + + true + + false + + 3 + + 0 +
+ + + + +
diff --git a/src/components/Fleets/Table/index.jsx b/src/components/Fleets/Table/index.jsx index 2ffccf9..2a34897 100644 --- a/src/components/Fleets/Table/index.jsx +++ b/src/components/Fleets/Table/index.jsx @@ -1,66 +1,26 @@ -import React, { useEffect, useState } from "react"; -import { Link } from 'react-router-dom'; -import { - Grid, - Table, - TableBody, - TableCell, - TableFooter, - TablePagination, - TableRow -} from "@material-ui/core"; +import React, {useEffect, useState} from "react"; +import {Link} from 'react-router-dom'; +import {Grid,} from "@material-ui/core"; import AddCircleIcon from "@material-ui/icons/AddCircle"; import clsx from "clsx"; -import TableHeaderSortable from "../../Table/HeaderSortable"; -import { - useUserContext -} from "../../Contexts/UserContext" -import { useStatusContext } from "../../Contexts/StatusContext"; -import { FleetProvider, useFleetContext } from "../../Contexts/FleetContext" +import {useUserContext} from "../../Contexts/UserContext" +import {useStatusContext} from "../../Contexts/StatusContext"; +import {FleetProvider} from "../../Contexts/FleetContext" import useStyles from "../../useStyles"; import SearchField from "../../Controls/SearchField"; -import { logger } from "../../../services/monitoring"; -import {useLocalStorage} from "../../useLocalStorage"; +import FleetSelectionTable from "../../Controls/FleetSelectionTable"; -const tableColumns = [ - { - id: "name", - label: "Name" - }, - { - id: "log_level", - label: "Log Level" - }, - { - id: "canbus_enabled", - label: "CAN Bus Enabled" - }, - { - id: "data_logger_enabled", - label: "Data Logger Enabled" - }, - { - id: "num_vehicles", - label: "Vehicles" - }, - { - id: "num_filters", - label: "Filters" - } -]; - -const PAGE_SIZE = "FLEET_TABLE_PAGE_SIZE"; const MainForm = () => { - const [pageSize, setPageSize] = useLocalStorage(PAGE_SIZE, 10); - const [pageIndex, setPageIndex] = useState(0); - const [orderBy, setOrderBy] = useState("id"); - const [order, setOrder] = useState("desc"); const classes = useStyles(); - const { setMessage, setSitePath, setTitle } = useStatusContext(); - const { fleets, totalFleets, getFleets } = useFleetContext(); - const { token: { idToken: { jwtToken: token } } } = useUserContext(); + const [search, setSearch] = useState(""); + const {setSitePath, setTitle} = useStatusContext(); + const {token: {idToken: {jwtToken: token}}} = useUserContext(); + + const handleSearch = (query) => { + setSearch(query); + }; useEffect(() => { setTitle("Fleets"); @@ -68,106 +28,26 @@ const MainForm = () => { // eslint-disable-next-line react-hooks/exhaustive-deps }, []); - useEffect(() => { - (async () => { - try { - if (!token) return; - await getFleets( - { - 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 - }, [token, pageIndex, pageSize, orderBy, order]); - - const handleChangePageIndex = (_event, newIndex) => { - setPageIndex(newIndex); - }; - - const handleChangePageSize = (event) => { - setPageSize(parseInt(event.target.value, 10)); - setPageIndex(0); - }; - - const handleSort = (_event, property) => { - try { - if (property === orderBy) { - if (order === "asc") { - setOrder("desc"); - } else { - setOrder("asc"); - } - } else { - setOrderBy(property); - setOrder("asc"); - } - } catch (e) { - logger.warn(e.stack); - } - }; - return (
- - + + - - + + - - - - {fleets.map((row) => ( - - - {row.name} - - {row.log_level} - {row.canbus.enabled ? "true" : "false"} - {row.canbus.data_logger_enabled ? "true" : "false"} - {!row.vehicles ? 0 : row.vehicles.length} - {!row.canbus.filters ? 0 : row.canbus.filters.length} - - ))} - - - - - - -
-
+ + ); }; diff --git a/src/components/Manifest/Deploy/FleetDeployForm.jsx b/src/components/Manifest/Deploy/FleetDeployForm.jsx new file mode 100644 index 0000000..59890d4 --- /dev/null +++ b/src/components/Manifest/Deploy/FleetDeployForm.jsx @@ -0,0 +1,177 @@ +import React, { useEffect, useState } from "react"; +import { useParams, Redirect } from "react-router"; +import { Button, Grid, Typography } from "@material-ui/core"; +import clsx from "clsx"; + +import { + ManifestsProvider, + useManifestsContext, +} from "../../Contexts/ManifestsContext"; +import { + CarUpdatesProvider, + useCarUpdatesContext, +} from "../../Contexts/CarUpdatesContext"; +import { VehicleProvider } from "../../Contexts/VehicleContext"; +import { useUserContext } from "../../Contexts/UserContext"; +import { useStatusContext } from "../../Contexts/StatusContext"; +import useStyles from "../../useStyles"; +import SearchField from "../../Controls/SearchField"; +import + CarSelectionTable from "../../Controls/CarSelectionTable"; +import { logger } from "../../../services/monitoring"; +import { LocalDateTimeString } from "../../../utils/dates"; +import {FleetProvider} from "../../Contexts/FleetContext"; + +const MainForm = () => { + const { manifest_id } = useParams(); + const { getManifests, manifests, busy } = useManifestsContext(); + const { deployCarUpdates } = useCarUpdatesContext(); + const { + token: { + idToken: { jwtToken: token }, + }, + } = useUserContext(); + const { setMessage, setTitle, setSitePath } = useStatusContext(); + const [manifestName, setManifestName] = useState(""); + const [version, setVersion] = useState(""); + const [createDate, setCreateDate] = useState(""); + const [selected, setSelected] = useState([]); + const [search, setSearch] = useState(""); + const [redirect, setRedirect] = useState(""); + const classes = useStyles(); + + const handleSearch = (query) => { + setSelected([]); + setSearch(query); + }; + + const handleSelectAll = (cars) => { + setSelected(cars); + }; + + const handleSelect = (event, key) => { + try { + let newSelected; + if (event.target.checked) { + newSelected = [...selected]; + newSelected.push(key); + } else { + newSelected = selected.filter((vin) => vin !== key); + } + setSelected(newSelected); + } catch (e) { + logger.warn(e.stack); + } + }; + + const onSubmit = async (event) => { + try { + event.preventDefault(); + const data = { + manifest_id: parseInt(manifest_id), + vins: selected, + }; + await deployCarUpdates(data, token); + setMessage( + `Deployed ${manifestName} ${version} to ${selected.length} cars` + ); + setRedirect(`/package-status/${manifest_id}`); + } catch (e) { + setMessage(e.message); + logger.warn(e.stack); + } + }; + + const getData = async () => { + try { + getManifests({ id: parseInt(manifest_id) }, token); + } catch (e) { + setMessage(e.message); + logger.warn(e.stack); + } + }; + + useEffect(() => { + getData(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [token]); + + useEffect(() => { + const title = `Deploy ${manifestName} ${version}`; + setTitle(title); + setSitePath([ + { + label: "Deployments", + link: "/packages", + }, + { + label: title, + }, + ]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [manifestName, version]); + + useEffect(() => { + if (!manifests || manifests.length === 0) return; + const data = manifests[0]; + + setManifestName(data.name); + setVersion(data.version); + setCreateDate(LocalDateTimeString(data.created)); + }, [manifests]); + + if (redirect.length > 0) { + return ; + } + + return ( +
+
+ Created {createDate}. + + +
{`${selected.length} Selected`}
+
+ + + + + + +
+ + +
+ ); +}; + +const FleetDeployForm = () => ( + + + + + + + +); + +export default FleetDeployForm; diff --git a/src/components/Manifest/Deploy/VehicleDeployForm.jsx b/src/components/Manifest/Deploy/VehicleDeployForm.jsx new file mode 100644 index 0000000..e26f8c2 --- /dev/null +++ b/src/components/Manifest/Deploy/VehicleDeployForm.jsx @@ -0,0 +1,175 @@ +import React, { useEffect, useState } from "react"; +import { useParams, Redirect } from "react-router"; +import { Button, Grid, Typography } from "@material-ui/core"; +import clsx from "clsx"; + +import { + ManifestsProvider, + useManifestsContext, +} from "../../Contexts/ManifestsContext"; +import { + CarUpdatesProvider, + useCarUpdatesContext, +} from "../../Contexts/CarUpdatesContext"; +import { VehicleProvider } from "../../Contexts/VehicleContext"; +import { useUserContext } from "../../Contexts/UserContext"; +import { useStatusContext } from "../../Contexts/StatusContext"; +import useStyles from "../../useStyles"; +import SearchField from "../../Controls/SearchField"; +import CarSelectionTable from "../../Controls/CarSelectionTable"; +import { logger } from "../../../services/monitoring"; +import { LocalDateTimeString } from "../../../utils/dates"; + +const MainForm = () => { + const { manifest_id } = useParams(); + const { getManifests, manifests, busy } = useManifestsContext(); + const { deployCarUpdates } = useCarUpdatesContext(); + const { + token: { + idToken: { jwtToken: token }, + }, + } = useUserContext(); + const { setMessage, setTitle, setSitePath } = useStatusContext(); + const [manifestName, setManifestName] = useState(""); + const [version, setVersion] = useState(""); + const [createDate, setCreateDate] = useState(""); + const [selected, setSelected] = useState([]); + const [search, setSearch] = useState(""); + const [redirect, setRedirect] = useState(""); + const classes = useStyles(); + + const handleSearch = (query) => { + setSelected([]); + setSearch(query); + }; + + const handleSelectAll = (cars) => { + setSelected(cars); + }; + + const handleSelect = (event, key) => { + try { + let newSelected; + if (event.target.checked) { + newSelected = [...selected]; + newSelected.push(key); + } else { + newSelected = selected.filter((vin) => vin !== key); + } + setSelected(newSelected); + } catch (e) { + logger.warn(e.stack); + } + }; + + const onSubmit = async (event) => { + try { + event.preventDefault(); + const data = { + manifest_id: parseInt(manifest_id), + vins: selected, + }; + await deployCarUpdates(data, token); + setMessage( + `Deployed ${manifestName} ${version} to ${selected.length} cars` + ); + setRedirect(`/package-status/${manifest_id}`); + } catch (e) { + setMessage(e.message); + logger.warn(e.stack); + } + }; + + const getData = async () => { + try { + getManifests({ id: parseInt(manifest_id) }, token); + } catch (e) { + setMessage(e.message); + logger.warn(e.stack); + } + }; + + useEffect(() => { + getData(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [token]); + + useEffect(() => { + const title = `Deploy ${manifestName} ${version}`; + setTitle(title); + setSitePath([ + { + label: "Deployments", + link: "/packages", + }, + { + label: title, + }, + ]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [manifestName, version]); + + useEffect(() => { + if (!manifests || manifests.length === 0) return; + const data = manifests[0]; + + setManifestName(data.name); + setVersion(data.version); + setCreateDate(LocalDateTimeString(data.created)); + }, [manifests]); + + if (redirect.length > 0) { + return ; + } + + return ( +
+
+ Created {createDate}. + + +
{`${selected.length} Selected`}
+
+ + + + + + +
+ + +
+ ); +}; + +const VehicleDeployForm = () => ( + + + + + + + +); + +export default VehicleDeployForm; diff --git a/src/components/Manifest/Deploy/index.jsx b/src/components/Manifest/Deploy/index.jsx index 23318c8..eeb825b 100644 --- a/src/components/Manifest/Deploy/index.jsx +++ b/src/components/Manifest/Deploy/index.jsx @@ -1,35 +1,37 @@ -import React, { useEffect, useState } from "react"; -import { useParams, Redirect } from "react-router"; -import { Button, Grid, Typography } from "@material-ui/core"; +import React, {useEffect, useState} from "react"; +import {Redirect, useParams} from "react-router"; +import {Button, FormControlLabel, Grid, Switch, Typography} from "@material-ui/core"; import clsx from "clsx"; -import { - ManifestsProvider, - useManifestsContext, -} from "../../Contexts/ManifestsContext"; -import { - CarUpdatesProvider, - useCarUpdatesContext, -} from "../../Contexts/CarUpdatesContext"; -import { VehicleProvider } from "../../Contexts/VehicleContext"; -import { useUserContext } from "../../Contexts/UserContext"; -import { useStatusContext } from "../../Contexts/StatusContext"; +import {ManifestsProvider, useManifestsContext,} from "../../Contexts/ManifestsContext"; +import {CarUpdatesProvider, useCarUpdatesContext,} from "../../Contexts/CarUpdatesContext"; +import {VehicleProvider} from "../../Contexts/VehicleContext"; +import {useUserContext} from "../../Contexts/UserContext"; +import {useStatusContext} from "../../Contexts/StatusContext"; import useStyles from "../../useStyles"; import SearchField from "../../Controls/SearchField"; import CarSelectionTable from "../../Controls/CarSelectionTable"; -import { logger } from "../../../services/monitoring"; -import { LocalDateTimeString } from "../../../utils/dates"; +import {logger} from "../../../services/monitoring"; +import {LocalDateTimeString} from "../../../utils/dates"; +import FleetSelectionTable from "../../Controls/FleetSelectionTable"; +import {FleetProvider} from "../../Contexts/FleetContext"; + + +const CAR_UPDATE = false; +const FLEET_UPDATE = true; const MainForm = () => { - const { manifest_id } = useParams(); - const { getManifests, manifests, busy } = useManifestsContext(); - const { deployCarUpdates } = useCarUpdatesContext(); + const [updateType, setUpdateType] = useState(CAR_UPDATE); + + const {manifest_id} = useParams(); + const {getManifests, manifests, busy} = useManifestsContext(); + const {deployCarUpdates, deployFleetUpdates} = useCarUpdatesContext(); const { token: { - idToken: { jwtToken: token }, + idToken: {jwtToken: token}, }, } = useUserContext(); - const { setMessage, setTitle, setSitePath } = useStatusContext(); + const {setMessage, setTitle, setSitePath} = useStatusContext(); const [manifestName, setManifestName] = useState(""); const [version, setVersion] = useState(""); const [createDate, setCreateDate] = useState(""); @@ -38,6 +40,11 @@ const MainForm = () => { const [redirect, setRedirect] = useState(""); const classes = useStyles(); + const handleChange = (_) => { + setSelected([]); + setUpdateType(updateType === CAR_UPDATE ? FLEET_UPDATE : CAR_UPDATE); + } + const handleSearch = (query) => { setSelected([]); setSearch(query); @@ -54,7 +61,7 @@ const MainForm = () => { newSelected = [...selected]; newSelected.push(key); } else { - newSelected = selected.filter((vin) => vin !== key); + newSelected = selected.filter((vinOrName) => vinOrName !== key); } setSelected(newSelected); } catch (e) { @@ -67,9 +74,14 @@ const MainForm = () => { event.preventDefault(); const data = { manifest_id: parseInt(manifest_id), - vins: selected, - }; - await deployCarUpdates(data, token); + } + if (updateType === CAR_UPDATE) { + data.vins = selected; + await deployCarUpdates(data, token); + } else { + data.fleet_names = selected; + await deployFleetUpdates(data, token); + } setMessage( `Deployed ${manifestName} ${version} to ${selected.length} cars` ); @@ -82,7 +94,7 @@ const MainForm = () => { const getData = async () => { try { - getManifests({ id: parseInt(manifest_id) }, token); + getManifests({id: parseInt(manifest_id)}, token); } catch (e) { setMessage(e.message); logger.warn(e.stack); @@ -119,7 +131,7 @@ const MainForm = () => { }, [manifests]); if (redirect.length > 0) { - return ; + return ; } return ( @@ -127,13 +139,21 @@ const MainForm = () => {
Created {createDate}. - +
{`${selected.length} Selected`}
+ + } label="Car(default) or Fleet"/> + + - +
- + {updateType === CAR_UPDATE ? + + : + + + + } ); }; const ManifestDeployForm = () => ( - - - - - - - + + + + + ); export default ManifestDeployForm; diff --git a/src/services/__mocks__/updatesAPI.js b/src/services/__mocks__/updatesAPI.js index 58c3ca6..e0efe59 100644 --- a/src/services/__mocks__/updatesAPI.js +++ b/src/services/__mocks__/updatesAPI.js @@ -5,6 +5,12 @@ const updatesAPI = { return data; }, + createFleetUpdates: async (data, token) => { + if (!data.id) data.id = 0; + data.id++; + return data; + }, + getCarUpdates: async (filter, token) => { return { data: [] }; }, diff --git a/src/services/updatesAPI.js b/src/services/updatesAPI.js index e5c56f7..42731a1 100644 --- a/src/services/updatesAPI.js +++ b/src/services/updatesAPI.js @@ -7,18 +7,23 @@ import { const API_ENDPOINT = process.env.REACT_APP_UPLOAD_SERVICE_URL; +const createDeployUpdatesClosure = (suffix) => { + return async (data, token) => fetch(`${API_ENDPOINT}/${suffix}`, { + method: "POST", + headers: Object.assign( + { "Content-Type": "application/json" }, + getAuthHeaderOptions(token) + ), + body: JSON.stringify(data), + }) + .then(fetchRespHandler) + .catch(errorHandler) +} + const updatesAPI = { - createCarUpdates: async (data, token) => - fetch(`${API_ENDPOINT}/carupdate`, { - method: "POST", - headers: Object.assign( - { "Content-Type": "application/json" }, - getAuthHeaderOptions(token) - ), - body: JSON.stringify(data), - }) - .then(fetchRespHandler) - .catch(errorHandler), + createFleetUpdates: createDeployUpdatesClosure("fleetupdate"), + + createCarUpdates: createDeployUpdatesClosure("carupdate"), getCarUpdateLog: async (query, token) => { const u = addQueryParams(`${API_ENDPOINT}/carupdateslog`, query);