From 950d919445f44fcd857bea7e918e867b4a4bb287 Mon Sep 17 00:00:00 2001 From: VadimKovalenko Date: Tue, 4 Mar 2025 19:14:03 +0400 Subject: [PATCH 1/4] Add toogle switch for Additional data section --- doc/release/RELEASE-NOTES.md | 31 +++++ .../components/ProductionLocationInfo.test.js | 107 ++++++++++++++++-- .../Contribute/ProductionLocationInfo.jsx | 103 ++++++++++------- src/react/src/util/styles.js | 6 +- 4 files changed, 189 insertions(+), 58 deletions(-) diff --git a/doc/release/RELEASE-NOTES.md b/doc/release/RELEASE-NOTES.md index 69b8cb884..d6ba46a1e 100644 --- a/doc/release/RELEASE-NOTES.md +++ b/doc/release/RELEASE-NOTES.md @@ -3,6 +3,37 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). The format is based on the `RELEASE-NOTES-TEMPLATE.md` file. +## Release 2.0.0 + +## Introduction +* Product name: Open Supply Hub +* Release date: March 22, 2025 + +### Database changes +* *Describe high-level database changes.* + +#### Migrations: +* *Describe migrations here.* + +#### Schema changes +* *Describe schema changes here.* + +### Code/API changes +* *Describe code/API changes here.* + +### Architecture/Environment changes +* *Describe architecture/environment changes here.* + +### Bugfix +* *Describe bugfix here.* + +### What's new +* [OSDEV-1814](https://opensupplyhub.atlassian.net/browse/OSDEV-1814) - Added toggle switch button for production location info page to render additional data if necessary. If toggle switch button is inactive (default behavior), additional data won't be send to the server along with name, address and country. + +### Release instructions: +* *Provide release instructions here.* + + ## Release 1.31.0 ## Introduction diff --git a/src/react/src/__tests__/components/ProductionLocationInfo.test.js b/src/react/src/__tests__/components/ProductionLocationInfo.test.js index 83c3ec03b..833524c6d 100644 --- a/src/react/src/__tests__/components/ProductionLocationInfo.test.js +++ b/src/react/src/__tests__/components/ProductionLocationInfo.test.js @@ -1,5 +1,5 @@ import React from "react"; -import { fireEvent } from "@testing-library/react"; +import { fireEvent, waitFor } from "@testing-library/react"; import { BrowserRouter as Router } from "react-router-dom"; import ProductionLocationInfo from "../../components/Contribute/ProductionLocationInfo"; @@ -29,7 +29,7 @@ jest.mock("../../components/Filters/StyledSelect", () => (props) => { ); }); -describe("ProductionLocationInfo component", () => { +describe("ProductionLocationInfo component, test input fields for POST v1/production-locations", () => { const defaultState = { filterOptions: { countries: { @@ -60,7 +60,7 @@ describe("ProductionLocationInfo component", () => { , { preloadedState: defaultState }, ) - + /* test("renders the production location form", () => { const { getByText, getByPlaceholderText, getAllByText, getByTestId } = renderComponent(); @@ -89,8 +89,8 @@ describe("ProductionLocationInfo component", () => { expect(countrySelect).toBeInTheDocument(); expect(countrySelect).toHaveValue(""); - const iconButton = getByTestId("toggle-additional-info"); - expect(iconButton).toBeInTheDocument(); + const switchButton = getByTestId("switch-additional-info-fields"); + expect(switchButton).toBeInTheDocument(); }); test("displays error (and disables submit) when required fields are empty after blur", () => { @@ -135,8 +135,8 @@ describe("ProductionLocationInfo component", () => { test("displays additional information form when icon button is clicked", () => { const { getByTestId, getByText, queryByText } = renderComponent(); - const iconButton = getByTestId("toggle-additional-info"); - fireEvent.click(iconButton); + const switchButton = getByTestId("switch-additional-info-fields"); + fireEvent.click(switchButton); expect(getByText("Sector(s)")).toBeInTheDocument(); expect(getByText("Select the sector(s) that this location operates in. For example: Apparel, Electronics, Renewable Energy.")).toBeInTheDocument(); @@ -151,7 +151,7 @@ describe("ProductionLocationInfo component", () => { expect(getByText("Parent Company")).toBeInTheDocument(); expect(getByText("Enter the company that holds majority ownership for this production.")).toBeInTheDocument(); - fireEvent.click(iconButton); + fireEvent.click(switchButton); expect(queryByText("Sector(s)")).not.toBeInTheDocument(); expect(queryByText("Product Type(s)")).not.toBeInTheDocument(); @@ -160,10 +160,13 @@ describe("ProductionLocationInfo component", () => { expect(queryByText("Number of Workers")).not.toBeInTheDocument(); expect(queryByText("Parent Company")).not.toBeInTheDocument(); }); + */ - test("displays error when number of workers is not a valid number and disable submit button", () => { + test("displays error when number of workers is not a valid number and disable submit button", async () => { const { getByRole, getByPlaceholderText, getByTestId } = renderComponent(); + // await waitFor(() => console.log(document.body.innerHTML)); + const submitButton = getByRole("button", { name: /Submit/i }); expect(submitButton).toBeDisabled(); @@ -177,8 +180,13 @@ describe("ProductionLocationInfo component", () => { expect(submitButton).toBeEnabled(); - const iconButton = getByTestId("toggle-additional-info"); - fireEvent.click(iconButton); + // await waitFor(() => console.log(document.body.innerHTML)); + + const switchButton = getByTestId("switch-additional-info-fields"); + expect(switchButton).not.toBeChecked(); + + fireEvent.click(switchButton); + expect(switchButton).toBeChecked(); const numberInput = getByPlaceholderText("Enter the number of workers as a number or range"); fireEvent.change(numberInput, { target: { value: "Test" } }); @@ -202,3 +210,80 @@ describe("ProductionLocationInfo component", () => { expect(submitButton).toBeDisabled(); }); }); + +describe("ProductionLocationInfo component, test invalid incoming data for UPDATE v1/production-locations", () => { + const defaultState = { + auth: { + user: { user: { isAnon: false } }, + session: { fetching: false }, + }, + contributeProductionLocation: { + singleProductionLocation: { + data: { + processing_type: ['Apparel'], + name: 'Modelina', + coordinates: { + lat: 40.6875863, + lng: 22.9389083 + }, + os_id: 'GR2019098DC1P4A', + location_type: ['Apparel'], + country: { + name: 'Greece', + numeric: '300', + alpha_3: 'GRC', + alpha_2: 'GR' + }, + address: '1 Agiou Petrou Street, Oreokastrou, Thessaloniki, 56430', + claim_status: 'unclaimed', + sector: ['Apparel'], + number_of_workers: { + max: 150, + min: 0 + }, + product_type: ['Accessories'] + }, + fetching: false, + error: null + }, + productionLocations: { + data: [], + fetching: false, + error: null + }, + pendingModerationEvent: { + data: {}, + fetching: false, + error: null + } + }, + }; + + const defaultProps = { + submitMethod: "UPDATE", + }; + + const renderComponent = (props = {}) => + renderWithProviders( + + + , + { preloadedState: defaultState }, + ) + + test("submit button should be enabled when number of workers invalid but additional info is hidden", async () => { + const { getByRole, getByTestId } = renderComponent(); + + // await waitFor(() => console.log(document.body.innerHTML)); + + const updateButton = getByRole("button", { name: /Update/i }); + + await waitFor(() => console.log(document.body.innerHTML)); + expect(updateButton).toBeEnabled(); + + const switchButton = getByTestId("switch-additional-info-fields"); + fireEvent.click(switchButton); + + expect(updateButton).toBeDisabled(); + }); +}); diff --git a/src/react/src/components/Contribute/ProductionLocationInfo.jsx b/src/react/src/components/Contribute/ProductionLocationInfo.jsx index fdbe76ed1..49e4bcaf8 100644 --- a/src/react/src/components/Contribute/ProductionLocationInfo.jsx +++ b/src/react/src/components/Contribute/ProductionLocationInfo.jsx @@ -8,11 +8,9 @@ import { endsWith, isEmpty, toString } from 'lodash'; import Button from '@material-ui/core/Button'; import CircularProgress from '@material-ui/core/CircularProgress'; import Paper from '@material-ui/core/Paper'; +import Switch from '@material-ui/core/Switch'; import TextField from '@material-ui/core/TextField'; -import IconButton from '@material-ui/core/IconButton'; import Typography from '@material-ui/core/Typography'; -import ArrowDropDownIcon from '@material-ui/icons/ArrowDropDown'; -import ArrowDropUpIcon from '@material-ui/icons/ArrowDropUp'; import RequireAuthNotice from '../RequireAuthNotice'; import StyledSelect from '../Filters/StyledSelect'; import RequiredAsterisk from '../RequiredAsterisk'; @@ -92,7 +90,6 @@ const ProductionLocationInfo = ({ const nameInQuery = queryParams.get('name'); const addressInQuery = queryParams.get('address'); const countryInQuery = queryParams.get('country'); - const [isExpanded, setIsExpanded] = useState(false); const [inputName, setInputName] = useState(nameInQuery ?? ''); const [inputAddress, setInputAddress] = useState(addressInQuery ?? ''); const [inputCountry, setInputCountry] = useState(null); @@ -108,6 +105,39 @@ const ProductionLocationInfo = ({ const customSelectComponents = { DropdownIndicator: null }; const isCountryError = countryTouched && !inputCountry?.value; + const fillAdditionalDataFields = () => { + setNumberOfWorkers( + convertRangeField(singleProductionLocationData.number_of_workers) ?? + '', + ); + updateStateFromData(singleProductionLocationData, 'sector', setSector); + updateStateFromData( + singleProductionLocationData, + 'product_type', + setProductType, + ); + updateStateFromData( + singleProductionLocationData, + 'location_type', + setLocationType, + ); + updateStateFromData( + singleProductionLocationData, + 'processing_type', + setProcessingType, + ); + setParentCompany(singleProductionLocationData.parent_company ?? ''); + }; + + const resetAdditionalDataFields = () => { + setSector(''); + setProductType([]); + setLocationType(null); + setProcessingType(null); + setNumberOfWorkers(''); + setParentCompany(''); + }; + useEffect(() => { window.scrollTo(0, 0); }, [location]); @@ -142,9 +172,6 @@ const ProductionLocationInfo = ({ setShowProductionLocationDialog, ] = useState(null); - const toggleExpand = () => { - setIsExpanded(!isExpanded); - }; const handleNameChange = event => { setInputName(event.target.value); }; @@ -194,6 +221,19 @@ const ProductionLocationInfo = ({ : ''; const submitButtonText = submitMethod === 'POST' ? 'Submit' : 'Update'; + const [showAdditionalInfo, setShowAdditionalInfo] = useState(false); + const onSwitchChange = () => { + setShowAdditionalInfo(prevShowAdditionalInfo => { + const newShowAdditionalInfo = !prevShowAdditionalInfo; + if (newShowAdditionalInfo) { + fillAdditionalDataFields(); + } else { + resetAdditionalDataFields(); + } + return newShowAdditionalInfo; + }); + }; + useEffect(() => { if (submitMethod === 'PATCH' && osID) { fetchProductionLocation(osID); @@ -222,38 +262,12 @@ const ProductionLocationInfo = ({ if (singleProductionLocationData && osID) { setInputName(singleProductionLocationData.name ?? ''); setInputAddress(singleProductionLocationData.address ?? ''); - setNumberOfWorkers( - convertRangeField( - singleProductionLocationData.number_of_workers, - ) ?? '', - ); if (singleProductionLocationData.country) { setInputCountry({ value: singleProductionLocationData?.country.alpha_2, label: singleProductionLocationData?.country.name, }); } - updateStateFromData( - singleProductionLocationData, - 'sector', - setSector, - ); - updateStateFromData( - singleProductionLocationData, - 'product_type', - setProductType, - ); - updateStateFromData( - singleProductionLocationData, - 'location_type', - setLocationType, - ); - updateStateFromData( - singleProductionLocationData, - 'processing_type', - setProcessingType, - ); - setParentCompany(singleProductionLocationData.parent_company ?? ''); } }, [singleProductionLocationData, osID]); @@ -572,16 +586,17 @@ const ProductionLocationInfo = ({ > Additional information - - {isExpanded ? ( - - ) : ( - - )} - + - {isExpanded && ( + {showAdditionalInfo && ( <>
width: '100%', alignItems: 'center', }), - marginRight: Object.freeze({ - marginRight: '20px', - }), buttonsContainerStyles: Object.freeze({ display: 'flex', flexDirection: 'row', width: '100%', justifyContent: 'center', }), + switchButton: Object.freeze({ + marginTop: '10px', + }), selectStyles: Object.freeze({ maxWidth: '528px', }), From 2d20a33c44a6a83fb721cb2387d459256b942733 Mon Sep 17 00:00:00 2001 From: VadimKovalenko Date: Wed, 5 Mar 2025 10:17:40 +0400 Subject: [PATCH 2/4] Update unit tests --- doc/release/RELEASE-NOTES.md | 24 ++-------- .../components/ProductionLocationInfo.test.js | 47 ++++++++++--------- 2 files changed, 28 insertions(+), 43 deletions(-) diff --git a/doc/release/RELEASE-NOTES.md b/doc/release/RELEASE-NOTES.md index d6ba46a1e..adeccecaa 100644 --- a/doc/release/RELEASE-NOTES.md +++ b/doc/release/RELEASE-NOTES.md @@ -3,35 +3,19 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). The format is based on the `RELEASE-NOTES-TEMPLATE.md` file. -## Release 2.0.0 +## Release 1.32.0 ## Introduction * Product name: Open Supply Hub * Release date: March 22, 2025 -### Database changes -* *Describe high-level database changes.* - -#### Migrations: -* *Describe migrations here.* - -#### Schema changes -* *Describe schema changes here.* - -### Code/API changes -* *Describe code/API changes here.* - -### Architecture/Environment changes -* *Describe architecture/environment changes here.* - -### Bugfix -* *Describe bugfix here.* - ### What's new * [OSDEV-1814](https://opensupplyhub.atlassian.net/browse/OSDEV-1814) - Added toggle switch button for production location info page to render additional data if necessary. If toggle switch button is inactive (default behavior), additional data won't be send to the server along with name, address and country. ### Release instructions: -* *Provide release instructions here.* +* Ensure that the following commands are included in the `post_deployment` command: + * `migrate` + * `reindex_database` ## Release 1.31.0 diff --git a/src/react/src/__tests__/components/ProductionLocationInfo.test.js b/src/react/src/__tests__/components/ProductionLocationInfo.test.js index 833524c6d..9d4d65235 100644 --- a/src/react/src/__tests__/components/ProductionLocationInfo.test.js +++ b/src/react/src/__tests__/components/ProductionLocationInfo.test.js @@ -1,6 +1,6 @@ import React from "react"; -import { fireEvent, waitFor } from "@testing-library/react"; -import { BrowserRouter as Router } from "react-router-dom"; +import { fireEvent } from "@testing-library/react"; +import { MemoryRouter, Route, BrowserRouter as Router } from "react-router-dom"; import ProductionLocationInfo from "../../components/Contribute/ProductionLocationInfo"; import renderWithProviders from "../../util/testUtils/renderWithProviders"; @@ -60,7 +60,7 @@ describe("ProductionLocationInfo component, test input fields for POST v1/produc , { preloadedState: defaultState }, ) - /* + test("renders the production location form", () => { const { getByText, getByPlaceholderText, getAllByText, getByTestId } = renderComponent(); @@ -160,13 +160,10 @@ describe("ProductionLocationInfo component, test input fields for POST v1/produc expect(queryByText("Number of Workers")).not.toBeInTheDocument(); expect(queryByText("Parent Company")).not.toBeInTheDocument(); }); - */ - test("displays error when number of workers is not a valid number and disable submit button", async () => { + test("displays error when number of workers is not a valid number and disable submit button", () => { const { getByRole, getByPlaceholderText, getByTestId } = renderComponent(); - // await waitFor(() => console.log(document.body.innerHTML)); - const submitButton = getByRole("button", { name: /Submit/i }); expect(submitButton).toBeDisabled(); @@ -180,31 +177,29 @@ describe("ProductionLocationInfo component, test input fields for POST v1/produc expect(submitButton).toBeEnabled(); - // await waitFor(() => console.log(document.body.innerHTML)); - const switchButton = getByTestId("switch-additional-info-fields"); expect(switchButton).not.toBeChecked(); fireEvent.click(switchButton); expect(switchButton).toBeChecked(); - const numberInput = getByPlaceholderText("Enter the number of workers as a number or range"); - fireEvent.change(numberInput, { target: { value: "Test" } }); + const numberOfWorkersInput = getByPlaceholderText("Enter the number of workers as a number or range"); + fireEvent.change(numberOfWorkersInput, { target: { value: "Test" } }); expect(getByPlaceholderText("Enter the number of workers as a number or range")).toHaveAttribute("aria-invalid", "true"); expect(submitButton).toBeDisabled(); - fireEvent.change(numberInput, { target: { value: "100" } }); + fireEvent.change(numberOfWorkersInput, { target: { value: "100" } }); expect(getByPlaceholderText("Enter the number of workers as a number or range")).toHaveAttribute("aria-invalid", "false"); expect(submitButton).toBeEnabled(); - fireEvent.change(numberInput, { target: { value: "100-150" } }); + fireEvent.change(numberOfWorkersInput, { target: { value: "100-150" } }); expect(getByPlaceholderText("Enter the number of workers as a number or range")).toHaveAttribute("aria-invalid", "false"); expect(submitButton).toBeEnabled(); - fireEvent.change(numberInput, { target: { value: "200-100" } }); + fireEvent.change(numberOfWorkersInput, { target: { value: "200-100" } }); expect(getByPlaceholderText("Enter the number of workers as a number or range")).toHaveAttribute("aria-invalid", "true"); expect(submitButton).toBeDisabled(); @@ -212,6 +207,7 @@ describe("ProductionLocationInfo component, test input fields for POST v1/produc }); describe("ProductionLocationInfo component, test invalid incoming data for UPDATE v1/production-locations", () => { + const osID = 'GR2019098DC1P4A'; const defaultState = { auth: { user: { user: { isAnon: false } }, @@ -226,7 +222,7 @@ describe("ProductionLocationInfo component, test invalid incoming data for UPDAT lat: 40.6875863, lng: 22.9389083 }, - os_id: 'GR2019098DC1P4A', + os_id: osID, location_type: ['Apparel'], country: { name: 'Greece', @@ -265,25 +261,30 @@ describe("ProductionLocationInfo component, test invalid incoming data for UPDAT const renderComponent = (props = {}) => renderWithProviders( - - - , + + } + /> + , { preloadedState: defaultState }, ) - test("submit button should be enabled when number of workers invalid but additional info is hidden", async () => { - const { getByRole, getByTestId } = renderComponent(); + test("submit button should be enabled when number of workers invalid but additional info is hidden", () => { + const { getByRole, getByText, getByTestId, getByPlaceholderText, queryByText } = renderComponent(); - // await waitFor(() => console.log(document.body.innerHTML)); + expect(queryByText("Enter the number of workers as a number or range")).not.toBeInTheDocument(); const updateButton = getByRole("button", { name: /Update/i }); - - await waitFor(() => console.log(document.body.innerHTML)); expect(updateButton).toBeEnabled(); const switchButton = getByTestId("switch-additional-info-fields"); fireEvent.click(switchButton); + const numberOfWorkersInput = getByPlaceholderText("Enter the number of workers as a number or range"); + expect(numberOfWorkersInput).toHaveAttribute("aria-invalid", "true"); + expect(getByText("Enter the number of workers as a number or range")).toBeInTheDocument(); + expect(updateButton).toBeDisabled(); }); }); From f64bd10013acdb4a010b4d75fdf262032a80d4e0 Mon Sep 17 00:00:00 2001 From: VadimKovalenko Date: Wed, 5 Mar 2025 11:08:36 +0400 Subject: [PATCH 3/4] Minor naming fix --- .../src/__tests__/components/ProductionLocationInfo.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/react/src/__tests__/components/ProductionLocationInfo.test.js b/src/react/src/__tests__/components/ProductionLocationInfo.test.js index 9d4d65235..ed30fc0fe 100644 --- a/src/react/src/__tests__/components/ProductionLocationInfo.test.js +++ b/src/react/src/__tests__/components/ProductionLocationInfo.test.js @@ -270,7 +270,7 @@ describe("ProductionLocationInfo component, test invalid incoming data for UPDAT { preloadedState: defaultState }, ) - test("submit button should be enabled when number of workers invalid but additional info is hidden", () => { + test("update button should be enabled when number of workers invalid but additional info is hidden", () => { const { getByRole, getByText, getByTestId, getByPlaceholderText, queryByText } = renderComponent(); expect(queryByText("Enter the number of workers as a number or range")).not.toBeInTheDocument(); From aff075c83ab82ae3da5fc095b3e53bc549d2ff93 Mon Sep 17 00:00:00 2001 From: VadimKovalenko Date: Wed, 5 Mar 2025 11:51:29 +0400 Subject: [PATCH 4/4] Test that hiding the section re-enables the update button --- .../src/__tests__/components/ProductionLocationInfo.test.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/react/src/__tests__/components/ProductionLocationInfo.test.js b/src/react/src/__tests__/components/ProductionLocationInfo.test.js index ed30fc0fe..e9c1b2736 100644 --- a/src/react/src/__tests__/components/ProductionLocationInfo.test.js +++ b/src/react/src/__tests__/components/ProductionLocationInfo.test.js @@ -286,5 +286,9 @@ describe("ProductionLocationInfo component, test invalid incoming data for UPDAT expect(getByText("Enter the number of workers as a number or range")).toBeInTheDocument(); expect(updateButton).toBeDisabled(); + + fireEvent.click(switchButton); + expect(queryByText("Enter the number of workers as a number or range")).not.toBeInTheDocument(); + expect(updateButton).toBeEnabled(); }); });