From 3c19a8d601d19c8bf39f6a86fc6a473697f40b7e Mon Sep 17 00:00:00 2001 From: Tristan Timblin Date: Fri, 1 Dec 2023 11:41:27 -0800 Subject: [PATCH 1/5] CEC-5436: add release_notes, update_duration, and max_attempts to manifest update form (#489) --- .../Controls/NumberField/NumberField.jsx | 44 +++++++++ .../Controls/NumberField/NumberField.test.jsx | 55 +++++++++++ src/components/Controls/NumberField/index.jsx | 1 + .../Update/__snapshots__/index.test.jsx.snap | 99 +++++++++++++++++++ src/components/Manifest/Update/index.jsx | 77 +++++++++++++-- 5 files changed, 266 insertions(+), 10 deletions(-) create mode 100644 src/components/Controls/NumberField/NumberField.jsx create mode 100644 src/components/Controls/NumberField/NumberField.test.jsx create mode 100644 src/components/Controls/NumberField/index.jsx diff --git a/src/components/Controls/NumberField/NumberField.jsx b/src/components/Controls/NumberField/NumberField.jsx new file mode 100644 index 0000000..d623365 --- /dev/null +++ b/src/components/Controls/NumberField/NumberField.jsx @@ -0,0 +1,44 @@ +import { + Box, + FormControl, + FormHelperText, + Input, + InputLabel, +} from "@material-ui/core"; + +const PREFIX = "number-field"; + +function kebab(str) { + return str.replaceAll(" ", "-").toLowerCase(); +} + +export function NumberField({ + name, + description, + value = 0, + setValue = () => { }, + ...rest +}) { + const inputId = `${PREFIX}-${kebab(name)}`; + const describeId = `${PREFIX}-${kebab(name)}-describe`; + + const handleChange = (event) => { + setValue(event.target.value); + } + return ( + + + {name} + + {description} + + + ); +} diff --git a/src/components/Controls/NumberField/NumberField.test.jsx b/src/components/Controls/NumberField/NumberField.test.jsx new file mode 100644 index 0000000..5c274d1 --- /dev/null +++ b/src/components/Controls/NumberField/NumberField.test.jsx @@ -0,0 +1,55 @@ +import React from "react"; +import { fireEvent, render } from "@testing-library/react"; + +import { NumberField } from "./index"; + +describe("NumberField", () => { + it("renders with form label and aria describe", () => { + const { getByText } = render( + + ); + + const labelEl = getByText("My First Field"); + const describeEl = getByText("Some helper text"); + + expect(labelEl.htmlFor).toEqual("number-field-my-first-field"); + expect(describeEl.id).toEqual("number-field-my-first-field-describe"); + }); + + it("input is of type number", () => { + const { getByLabelText } = render( + + ); + + const inputEl = getByLabelText("My First Field", { selector: "input" }); + + expect(inputEl.type).toEqual("number"); + }); + + it("updates parent state", () => { + let mockState = 0; + const mockSetState = jest.fn((value) => mockState = value); + + const { getByLabelText } = render( + + ); + + const inputEl = getByLabelText("My First Field", { selector: "input" }); + + fireEvent.change(inputEl, { target: { value: "1" } }); + + expect(mockState).toEqual("1"); + expect(mockSetState.mock.calls.length).toEqual(1); + }); +}) \ No newline at end of file diff --git a/src/components/Controls/NumberField/index.jsx b/src/components/Controls/NumberField/index.jsx new file mode 100644 index 0000000..a7245fa --- /dev/null +++ b/src/components/Controls/NumberField/index.jsx @@ -0,0 +1 @@ +export * from "./NumberField"; \ No newline at end of file diff --git a/src/components/Manifest/Update/__snapshots__/index.test.jsx.snap b/src/components/Manifest/Update/__snapshots__/index.test.jsx.snap index 9874fa0..8fce96e 100644 --- a/src/components/Manifest/Update/__snapshots__/index.test.jsx.snap +++ b/src/components/Manifest/Update/__snapshots__/index.test.jsx.snap @@ -112,6 +112,43 @@ exports[`Manifest Details Component Render 1`] = ` +
+ +
+ + +
+
@@ -334,6 +371,68 @@ exports[`Manifest Details Component Render 1`] = `
+
+
+ +
+ +
+

+

+
+
+
+ +
+ +
+

+

+
+ + + + ); +} + +function kebab(str) { + return str.replaceAll(" ", "-").toLowerCase(); +} + +function ConfigureInput({ + name, + description, + value = 0, + setValue = () => { }, + error, +}) { + const inputId = `deploy-configure-${kebab(name)}`; + const descriptionId = `deploy-configure-${kebab(name)}-explain`; + + const handleChange = (event) => { + setValue(event.target.value); + } + return ( + + + {name} + + {description} + + + ); +} diff --git a/src/components/Manifest/Deploy/index.jsx b/src/components/Manifest/Deploy/index.jsx index 7bb7abd..ded0190 100644 --- a/src/components/Manifest/Deploy/index.jsx +++ b/src/components/Manifest/Deploy/index.jsx @@ -1,13 +1,11 @@ -import { Button, Checkbox, FormControlLabel, Grid, MenuItem, Switch, Typography } from "@material-ui/core"; -import clsx from "clsx"; -import SendIcon from "@material-ui/icons/Send"; +import { Checkbox, FormControlLabel, Grid, MenuItem, Switch, Typography, Box } from "@material-ui/core"; import React, { useEffect, useState } from "react"; import { Redirect, useParams } from "react-router"; import { logger } from "../../../services/monitoring"; import { LocalDateTimeString } from "../../../utils/dates"; import { Permissions } from "../../../utils/roles"; -import { CarUpdatesProvider, SELECT_VERSION, useCarUpdatesContext } from "../../Contexts/CarUpdatesContext"; +import { CarUpdatesProvider, useCarUpdatesContext } from "../../Contexts/CarUpdatesContext"; import { FleetProvider } from "../../Contexts/FleetContext"; import { ManifestsProvider, useManifestsContext } from "../../Contexts/ManifestsContext"; import { useStatusContext } from "../../Contexts/StatusContext"; @@ -15,11 +13,11 @@ import { useUserContext } from "../../Contexts/UserContext"; import { VehicleProvider } from "../../Contexts/VehicleContext"; import CarSelectionTable from "../../Controls/CarSelectionTable"; import OptionsDropdown from "../../Controls/OptionsDropdown"; -import { DropDownList } from "../../Controls/DropDownList"; import FleetSelectionTable from "../../Controls/FleetSelectionTable"; import { RoleWrap } from "../../Controls/RoleWrap"; import SearchField from "../../Controls/SearchField"; import useStyles from "../../useStyles"; +import Configure from "./Configure"; const CAR_UPDATE = false; const FLEET_UPDATE = true; @@ -28,7 +26,7 @@ const MainForm = () => { const [updateType, setUpdateType] = useState(CAR_UPDATE); const { manifest_id } = useParams(); const { getManifests, manifests, busy } = useManifestsContext(); - const { deployCarUpdates, deployFleetUpdates, getSUMSVersions, versions, updateSUMSVersion } = useCarUpdatesContext(); + const { deployCarUpdates, deployFleetUpdates, getSUMSVersions, versions } = useCarUpdatesContext(); const { groups, providers, @@ -37,15 +35,11 @@ const MainForm = () => { }, } = useUserContext(); const { setMessage, setTitle, setSitePath } = useStatusContext(); - const [manifestName, setManifestName] = useState(""); - const [version, setVersion] = useState(""); - const [sumsVersion, setSUMSersion] = useState(""); - const [createDate, setCreateDate] = useState(""); + const [manifest, setManifest] = useState({}); const [selected, setSelected] = useState([]); const [search, setSearch] = useState(""); const [online, setOnline] = useState(false); const [onlineHMI, setOnlineHMI] = useState(false); - const [softwareVersion, setSoftwareVersion] = useState(SELECT_VERSION); const [redirect, setRedirect] = useState(""); const classes = useStyles(); @@ -86,15 +80,11 @@ const MainForm = () => { } }; - const onSubmit = async (event) => { + const onSubmit = async () => { try { - event.preventDefault(); const data = { manifest_id: parseInt(manifest_id), } - if (sumsVersion.length === 0) { - await updateSUMSVersion(manifest_id, softwareVersion, token); - } if (updateType === CAR_UPDATE) { data.vins = selected; @@ -104,7 +94,7 @@ const MainForm = () => { await deployFleetUpdates(data, token); } setMessage( - `Deployed ${manifestName} ${version} to ${selected.length} cars` + `Deployed ${manifest.name} ${manifest.version} to ${selected.length} cars` ); setRedirect(`/package-status/${manifest_id}`); } catch (e) { @@ -113,65 +103,77 @@ const MainForm = () => { } }; - const getData = async () => { - try { - await getManifests({ id: parseInt(manifest_id) }, token); - await getSUMSVersions(token); - } catch (e) { - setMessage(e.message); - logger.warn(e.stack); + useEffect(() => { + const control = new AbortController(); + + const fetchData = async () => { + try { + await getManifests({ + id: parseInt(manifest_id), + signal: control.signal, + }, token); + await getSUMSVersions(token); + } catch (e) { + setMessage(e.message); + logger.warn(e.stack); + } + } + fetchData(); + return () => { + control.abort(); } - }; - - const changeVersion = (e) => { - setSoftwareVersion(e.target.value); - } - - useEffect(() => { - getData(); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [token]); + }, [manifest_id, 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); - setSUMSersion(data.sums || ""); - setCreateDate(LocalDateTimeString(data.created)); + if (manifests && manifests.length !== 0) { + setManifest(manifests[0]); + } }, [manifests]); + useEffect(() => { + if (manifest) { + const title = `Deploy ${manifest.name} ${manifest.version}`; + setTitle(title); + setSitePath([ + { + label: "Deployments", + link: "/packages", + }, + { + label: title, + }, + ]); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [manifest]); + if (redirect.length > 0) { return ; } + if (!manifest && !manifest?.name) { + return ( +
Loading...
+ ); + } + return (
-
- Created {createDate}. - - + + Created {LocalDateTimeString(manifest.created)}. + +
{`${selected.length} Selected`}
- + + { />} label="Car(default) or Fleet" /> - - + + + + + + + } + label="Only online" + /> + + + + } + label="Only online HMI" + /> + + + - - - - } - label="Only online" - /> - - - - } - label="Only online HMI" - /> - - - - - {sumsVersion.length === 0 && - - } - + + + {updateType === CAR_UPDATE ? diff --git a/src/components/useStyles.jsx b/src/components/useStyles.jsx index 58d6c05..86b55c4 100644 --- a/src/components/useStyles.jsx +++ b/src/components/useStyles.jsx @@ -348,10 +348,18 @@ const useStyles = makeStyles((theme) => ({ formGridItem: { flexGrow: 1, }, + marginTop: { + marginTop: theme.spacing(2), + }, marginX: { marginTop: theme.spacing(2), marginBottom: theme.spacing(2), }, + closeModal: { + position: "absolute", + top: "5px", + right: "5px", + }, })); export default useStyles; From d58369def62a572e78177fcf16cd582344df1ae0 Mon Sep 17 00:00:00 2001 From: Tristan Timblin Date: Wed, 13 Dec 2023 10:26:33 -0800 Subject: [PATCH 4/5] CEC-5436: update default sorting of tables (#490) * CEC-5436: add release_notes, update_duration, and max_attempts to manifest update form * CEC-5436: reverse field order * CEC-5436: sort vehicles by last updated * CEC-5431: increase concurrency (#487) * increase concurrency * fix unmounted component leak --- .../App/__snapshots__/App.test.js.snap | 54 +++++++++---------- .../List/__snapshots__/index.test.jsx.snap | 18 +++---- .../Controls/CarSelectionTable/index.jsx | 4 +- .../Controls/IssueSelectionTable/index.jsx | 2 +- .../Add/__snapshots__/index.test.jsx.snap | 18 +++---- src/components/Manifest/List/index.jsx | 2 +- src/components/Manifest/Status/index.jsx | 2 +- 7 files changed, 50 insertions(+), 50 deletions(-) diff --git a/src/components/App/__snapshots__/App.test.js.snap b/src/components/App/__snapshots__/App.test.js.snap index 8012769..ad6d052 100644 --- a/src/components/App/__snapshots__/App.test.js.snap +++ b/src/components/App/__snapshots__/App.test.js.snap @@ -3427,7 +3427,7 @@ exports[`App Route /issues authenticated 1`] = ` @@ -3441,11 +3441,11 @@ exports[`App Route /issues authenticated 1`] = ` - sorted ascending + sorted descending VIN - - sorted ascending - Updated + + sorted descending + @@ -5420,11 +5420,11 @@ exports[`App Route /package-status authenticated 1`] = ` - sorted ascending + sorted descending @@ -6604,11 +6604,11 @@ exports[`App Route /packages authenticated 1`] = ` - sorted ascending + sorted descending VIN - - sorted ascending - Updated + + sorted descending + VIN - - sorted ascending - Updated + + sorted descending + VIN - - sorted ascending - Updated + + sorted descending +