Skip to content

Commit

Permalink
Merge branch 'main' into OSDEV-1117-contribution-record-integration
Browse files Browse the repository at this point in the history
  • Loading branch information
VadimKovalenkoSNF authored Jan 7, 2025
2 parents 6a23c67 + 82cad3d commit cab8a47
Show file tree
Hide file tree
Showing 19 changed files with 974 additions and 165 deletions.
14 changes: 12 additions & 2 deletions doc/release/RELEASE-NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ This project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html

## Introduction
* Product name: Open Supply Hub
* Release date: December 28, 2024
* Release date: January 11, 2025

### Database changes
#### Migrations:
Expand All @@ -26,12 +26,22 @@ This project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html
* [OSDEV-1493](https://opensupplyhub.atlassian.net/browse/OSDEV-1493) - Fixed an issue where the backend sorts countries not by `name` but by their `alpha-2 codes` in `GET /api/v1/moderation-events/` endpoint.
* [OSDEV-1532](https://opensupplyhub.atlassian.net/browse/OSDEV-1532) - Fixed the date range picker on the `Moderation Queue` page. A Data Moderator can change the Before date even if an Error message is displayed.
* [OSDEV-1533](https://opensupplyhub.atlassian.net/browse/OSDEV-1533) - The presentation of the `Moderation Decision Date` in the `Moderation Queue` table has been corrected. If the "status_change_date" is missing in the object, it now displays as "N/A".
* [OSDEV-1397](https://opensupplyhub.atlassian.net/browse/OSDEV-1397) - GET `/api/parent-companies/` request has been removed from the Open Supply Hub page and ClaimFacility component. Parent Company Select is a regular input field that allows the creation of multiple parent company names for filter on this page.
* [OSDEV-1556](https://opensupplyhub.atlassian.net/browse/OSDEV-1556) - Fixed validation of `os_id` for PATCH `/api/v1/moderation-events/{moderation_id}/production-locations/{os_id}/` endpoint.
* [OSDEV-1563](https://opensupplyhub.atlassian.net/browse/OSDEV-1563) - Fixed updating of the moderation decision date after moderation event approval.

### What's new
* [OSDEV-1376](https://opensupplyhub.atlassian.net/browse/OSDEV-1376) - Updated automated emails for closure reports (report_result) to remove the term "Rejected" for an improved user experience. Added link to Closure Policy and instructions for submitting a Reopening Report to make the process easier to understand for users.
* [OSDEV-1383](https://opensupplyhub.atlassian.net/browse/OSDEV-1383) - Edited text of the automated email that notifies a contributor when one of their facilities has been claimed. The new text provides more information to the contributor to understand the claim process and how they can encourage more of their facilities to claim their profile.
* [OSDEV-1474](https://opensupplyhub.atlassian.net/browse/OSDEV-1474) - Added contributor type value to response of `/api/contributors/` endpoint.
* [OSDEV-1117](https://opensupplyhub.atlassian.net/browse/OSDEV-1117) - Implemented integration of Contribution Record Page.
* [OSDEV-1117](https://opensupplyhub.atlassian.net/browse/OSDEV-1117) - Implemented integration of Contribution Record Page (`/dashboard/moderation-queue/contribution-record/{moderation_id}`).
* [OSDEV-1130](https://opensupplyhub.atlassian.net/browse/OSDEV-1130) A new page, `Production Location Information`, has been implemented. It includes the following inputs:
* Required and pre-fillable fields:
- Name
- Address
- Country
* Additional information section: Fields for optional contributions from the owner or manager of the production location, including sector(s), product type(s), location type(s), processing type(s), number of workers, and parent company.
The page also features `Go Back` and `Submit` buttons for navigation and form submission.

### Release instructions:
* Ensure that the following commands are included in the `post_deployment` command:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,7 @@ def __create_facility_match_record(
@staticmethod
def _update_event(event: ModerationEvent, item: FacilityListItem) -> None:
event.status = ModerationEvent.Status.APPROVED
event.status_change_date = timezone.now()
event.os_id = item.facility_id
event.save()

Expand Down
2 changes: 1 addition & 1 deletion src/django/api/os_id.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ def validate_os_id(raw_id, raise_on_invalid=True):
return True


os_id_regex = re.compile('[A-Z]{2}[0-9]{7}[A-Z0-9]{6}')
os_id_regex = re.compile('^[A-Z]{2}[0-9]{7}[A-Z0-9]{6}$')


def string_matches_os_id_format(string):
Expand Down
4 changes: 2 additions & 2 deletions src/django/api/services/moderation_events_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from api.exceptions import GoneException, InternalServerErrorException
from api.models.facility.facility import Facility
from api.models.moderation_event import ModerationEvent
from api.os_id import validate_os_id
from api.os_id import string_matches_os_id_format
from api.serializers.v1.opensearch_common_validators.moderation_id_validator \
import ModerationIdValidator
from api.views.v1.utils import create_error_detail
Expand Down Expand Up @@ -52,7 +52,7 @@ def validate_moderation_status(status):

@staticmethod
def validate_location_os_id(os_id):
if not validate_os_id(os_id, raise_on_invalid=False):
if not string_matches_os_id_format(os_id):
raise ParseError(
create_error_detail(
field="os_id",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ def assert_success_response(self, response, status_code):
uuid=self.moderation_event_id
)
self.assertEqual(moderation_event.status, 'APPROVED')
self.assertIsNotNone(moderation_event.status_change_date)
self.assertEqual(moderation_event.os_id, response.data["os_id"])

def assert_source_creation(self, source):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,21 +112,34 @@ def test_moderation_event_not_pending(self):

def test_invalid_os_id_format(self):
self.login_as_superuser()
response = self.client.patch(
self.get_url().replace(self.os_id, "invalid_os_id"),
data=json.dumps({}),
content_type="application/json",
)

self.assertEqual(400, response.status_code)
self.assertEqual(
"The request path parameter is invalid.", response.data["detail"]
)
self.assertEqual("os_id", response.data["errors"][0]["field"])
self.assertEqual(
APIV1CommonErrorMessages.LOCATION_ID_NOT_VALID,
response.data["errors"][0]["detail"],
)
invalid_ids = [
"A1234567ABCDEF", # Less than 15 characters
"ABC1234567ABCDEF", # More than 15 characters
"AB1234567abcdef", # Contains lowercase letters
"AB1234567AB!DEF", # Contains special character
"AB12345X7ABCDEF", # Letter in the digit section
"1234567ABABCDEF", # Starts with digits
"ABCD56789012345", # Too many letters at the start
"AB12345678ABCDEF" # Too many digits
]

for invalid_id in invalid_ids:
response = self.client.patch(
self.get_url().replace(self.os_id, invalid_id),
data=json.dumps({}),
content_type="application/json",
)

self.assertEqual(400, response.status_code)
self.assertEqual(
"The request path parameter is invalid.",
response.data["detail"]
)
self.assertEqual("os_id", response.data["errors"][0]["field"])
self.assertEqual(
APIV1CommonErrorMessages.LOCATION_ID_NOT_VALID,
response.data["errors"][0]["detail"],
)

def test_no_production_location_found_with_os_id(self):
self.login_as_superuser()
Expand Down
7 changes: 7 additions & 0 deletions src/react/src/Routes.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import ExternalRedirect from './components/ExternalRedirect';
import Facilities from './components/Facilities';
import ContributeProductionLocation from './components/Contribute/ContributeProductionLocation';
import SearchByOsIdResult from './components/Contribute/SearchByOsIdResult';
import ProductionLocationInfo from './components/Contribute/ProductionLocationInfo';

import { sessionLogin } from './actions/auth';
import { fetchFeatureFlags } from './actions/featureFlags';
Expand All @@ -57,6 +58,7 @@ import {
InfoPaths,
contributeProductionLocationRoute,
searchByOsIdResultRoute,
productionLocationInfoRoute,
} from './util/constants';

class Routes extends Component {
Expand Down Expand Up @@ -169,6 +171,11 @@ class Routes extends Component {
path={searchByOsIdResultRoute}
component={SearchByOsIdResult}
/>
<Route
exact
path={productionLocationInfoRoute}
component={ProductionLocationInfo}
/>
<Route exact path="/about/processing">
<ExternalRedirect
to={`${InfoLink}/${InfoPaths.dataQuality}`}
Expand Down
12 changes: 6 additions & 6 deletions src/react/src/actions/filterOptions.js
Original file line number Diff line number Diff line change
Expand Up @@ -287,9 +287,9 @@ export function fetchFacilityProcessingTypeOptions() {

return apiRequest
.get(makeGetFacilitiesTypeProcessingTypeURL())
.then(({ data }) => {
dispatch(completeFetchFacilityProcessingTypeOptions(data));
})
.then(({ data }) =>
dispatch(completeFetchFacilityProcessingTypeOptions(data)),
)
.catch(err =>
dispatch(
logErrorAndDispatchFailure(
Expand All @@ -309,9 +309,9 @@ export function fetchNumberOfWorkersOptions() {
return apiRequest
.get(makeGetNumberOfWorkersURL())
.then(({ data }) => mapDjangoChoiceTuplesValueToSelectOptions(data))
.then(data => {
dispatch(completeFetchNumberOfWorkersTypeOptions(data));
})
.then(data =>
dispatch(completeFetchNumberOfWorkersTypeOptions(data)),
)
.catch(err =>
dispatch(
logErrorAndDispatchFailure(
Expand Down
11 changes: 2 additions & 9 deletions src/react/src/components/ClaimFacility.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import {
fetchClaimFacilityData,
clearClaimFacilityDataAndForm,
} from '../actions/claimFacility';
import { fetchParentCompanyOptions } from '../actions/filterOptions';

import { facilityDetailsPropType } from '../util/propTypes';

Expand Down Expand Up @@ -119,16 +118,13 @@ const mapStateToProps = ({
claimFacility: {
facilityData: { data, fetching, error },
},
filterOptions: {
parentCompanies: { fetching: fetchingParentCompanyOptions },
},
auth: {
user: { user },
session: { fetching: sessionFetching },
},
}) => ({
data,
fetching: fetching || sessionFetching || fetchingParentCompanyOptions,
fetching: fetching || sessionFetching,
userHasSignedIn: !user.isAnon,
error,
});
Expand All @@ -141,10 +137,7 @@ const mapDispatchToProps = (
},
},
) => ({
getClaimData: () => {
dispatch(fetchParentCompanyOptions());
return dispatch(fetchClaimFacilityData(osID));
},
getClaimData: () => dispatch(fetchClaimFacilityData(osID)),
clearClaimData: () => dispatch(clearClaimFacilityDataAndForm()),
});

Expand Down
4 changes: 2 additions & 2 deletions src/react/src/components/ClaimFacilityAdditionalData.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import { fetchSectorOptions } from '../actions/filterOptions';

import { sectorOptionsPropType } from '../util/propTypes';

import { getValueFromEvent, validateNumberOfWorkers } from '../util/util';
import { getValueFromEvent, isValidNumberOfWorkers } from '../util/util';

import { claimAFacilitySupportDocsFormStyles } from '../util/styles';

Expand Down Expand Up @@ -284,7 +284,7 @@ function ClaimFacilityAdditionalData({
</Typography>
<TextField
id={numberOfWorkersForm.id}
error={validateNumberOfWorkers(numberOfWorkers)}
error={!isValidNumberOfWorkers(numberOfWorkers)}
variant="outlined"
style={claimAFacilitySupportDocsFormStyles.textFieldStyles}
value={numberOfWorkers}
Expand Down
6 changes: 3 additions & 3 deletions src/react/src/components/ClaimedFacilitiesDetails.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ import {
isValidFacilityURL,
makeClaimGeocoderURL,
logErrorToRollbar,
validateNumberOfWorkers,
isValidNumberOfWorkers,
} from '../util/util';

import {
Expand Down Expand Up @@ -529,7 +529,7 @@ function ClaimedFacilitiesDetails({
onChange={updateFacilityWorkersCount}
disabled={updating}
hasValidationErrorFn={() =>
validateNumberOfWorkers(data.facility_workers_count)
!isValidNumberOfWorkers(data.facility_workers_count)
}
/>
<InputSection
Expand Down Expand Up @@ -703,7 +703,7 @@ function ClaimedFacilitiesDetails({
!isEmail(data.point_of_contact_email)) ||
(!isEmpty(data.facility_website) &&
!isValidFacilityURL(data.facility_website)) ||
validateNumberOfWorkers(data.facility_workers_count)
!isValidNumberOfWorkers(data.facility_workers_count)
}
>
Save
Expand Down
25 changes: 25 additions & 0 deletions src/react/src/components/Contribute/InputErrorText.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import React from 'react';
import { withStyles } from '@material-ui/core/styles';
import { string, object } from 'prop-types';
import Typography from '@material-ui/core/Typography';
import ErrorOutlineIcon from '@material-ui/icons/ErrorOutline';
import { inputErrorText } from '../../util/styles';

const InputErrorText = ({ classes, text }) => (
<span className={classes.errorTextWrapStyles}>
<ErrorOutlineIcon className={classes.iconInfoStyles} />
<Typography component="span" className={classes.inputErrorTextStyles}>
{text}
</Typography>
</span>
);

InputErrorText.defaultProps = {
text: 'This field is required.',
};

InputErrorText.propTypes = {
text: string,
classes: object.isRequired,
};
export default withStyles(inputErrorText)(InputErrorText);
Loading

0 comments on commit cab8a47

Please sign in to comment.