Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions doc/release/RELEASE-NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,21 @@ 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 1.32.0

## Introduction
* Product name: Open Supply Hub
* Release date: March 22, 2025

### 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:
* Ensure that the following commands are included in the `post_deployment` command:
* `migrate`
* `reindex_database`


## Release 1.31.0

## Introduction
Expand Down
114 changes: 100 additions & 14 deletions src/react/src/__tests__/components/ProductionLocationInfo.test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from "react";
import { fireEvent } from "@testing-library/react";
import { BrowserRouter as Router } from "react-router-dom";
import { MemoryRouter, Route, BrowserRouter as Router } from "react-router-dom";

import ProductionLocationInfo from "../../components/Contribute/ProductionLocationInfo";
import renderWithProviders from "../../util/testUtils/renderWithProviders";
Expand Down Expand Up @@ -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: {
Expand Down Expand Up @@ -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", () => {
Expand Down Expand Up @@ -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();
Expand All @@ -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();
Expand All @@ -177,28 +177,114 @@ describe("ProductionLocationInfo component", () => {

expect(submitButton).toBeEnabled();

const iconButton = getByTestId("toggle-additional-info");
fireEvent.click(iconButton);
const switchButton = getByTestId("switch-additional-info-fields");
expect(switchButton).not.toBeChecked();

const numberInput = getByPlaceholderText("Enter the number of workers as a number or range");
fireEvent.change(numberInput, { target: { value: "Test" } });
fireEvent.click(switchButton);
expect(switchButton).toBeChecked();

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();
});
});

describe("ProductionLocationInfo component, test invalid incoming data for UPDATE v1/production-locations", () => {
const osID = 'GR2019098DC1P4A';
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: osID,
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(
<MemoryRouter initialEntries={[`/contribute/single-location/${osID}/info/`]}>
<Route
path="/contribute/single-location/:osID/info/"
component={() => <ProductionLocationInfo {...defaultProps} {...props} />}
/>
</MemoryRouter>,
{ preloadedState: defaultState },
)

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();

const updateButton = getByRole("button", { name: /Update/i });
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();
});
});
103 changes: 59 additions & 44 deletions src/react/src/components/Contribute/ProductionLocationInfo.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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);
Expand All @@ -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]);
Expand Down Expand Up @@ -142,9 +172,6 @@ const ProductionLocationInfo = ({
setShowProductionLocationDialog,
] = useState(null);

const toggleExpand = () => {
setIsExpanded(!isExpanded);
};
const handleNameChange = event => {
setInputName(event.target.value);
};
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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]);

Expand Down Expand Up @@ -572,16 +586,17 @@ const ProductionLocationInfo = ({
>
Additional information
</Typography>
<IconButton
data-testid="toggle-additional-info"
onClick={toggleExpand}
>
{isExpanded ? (
<ArrowDropUpIcon />
) : (
<ArrowDropDownIcon />
)}
</IconButton>
<Switch
color="primary"
onChange={onSwitchChange}
checked={showAdditionalInfo}
style={{ zIndex: 1 }}
className={classes.switchButton}
inputProps={{
'data-testid':
'switch-additional-info-fields',
}}
/>
</div>
<Typography
component="h4"
Expand All @@ -591,7 +606,7 @@ const ProductionLocationInfo = ({
production location, including product types, number
of workers, parent company and more.
</Typography>
{isExpanded && (
{showAdditionalInfo && (
<>
<div
className={`${classes.inputSectionWrapStyles} ${classes.wrapStyles}`}
Expand Down
6 changes: 3 additions & 3 deletions src/react/src/util/styles.js
Original file line number Diff line number Diff line change
Expand Up @@ -1286,15 +1286,15 @@ export const productionLocationInfoStyles = theme =>
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',
}),
Expand Down