Unverified Commit d47f97d1 authored by Martin Lowe's avatar Martin Lowe 🇨🇦 Committed by GitHub
Browse files

Update address fields to generalize field names (#246)



* Update address fields to use generic fields

* Remove call that does not exist in production packages

* Update DDL for recreating DB, add JSONb property names in address fields

* Made address label dynamic based on country/region and solved conflicts

* Modified required logic for city field

* Fixed some small issues
Co-authored-by: Zhou Fang's avatarZhou Fang <zhou.fang@eclipse-foundation.org>
parent f8edb543
......@@ -601,15 +601,18 @@ components:
address:
type: object
properties:
street:
address_line_1:
type: string
description: Street address of the organization
city:
address_line_2:
type: string
description: The ciy in which the organization is based
province_state:
description: Additional information about the street address of the organization
locality:
type: string
description: The province/state in which the organization is based
description: The city/township in which the organization is based
administrative_area:
type: string
description: The province/state/area in which the organization is based
country:
type: string
description: The country in which the organization is based
......@@ -638,15 +641,18 @@ components:
address:
type: object
properties:
street:
address_line_1:
type: string
description: Street address of the organization
city:
address_line_2:
type: string
description: Additional information about the street address of the organization
locality:
type: string
description: The ciy in which the organization is based
province_state:
description: The city/township in which the organization is based
administrative_area:
type: string
description: The province/state in which the organization is based
description: The province/state/area in which the organization is based
country:
type: string
description: The country in which the organization is based
......
......@@ -114,11 +114,11 @@ public class DataLoader {
o.setEmployeeCount(RandomStringUtils.randomNumeric(5, 10));
o.setOrganizationType(OrganizationTypes.OTHER);
Address a = new Address();
a.setCity(RandomStringUtils.randomAlphabetic(4, 10));
a.setLocality(RandomStringUtils.randomAlphabetic(4, 10));
a.setCountry(RandomStringUtils.randomAlphabetic(4, 10));
a.setPostalCode(RandomStringUtils.randomAlphabetic(4, 10));
a.setProvinceState(RandomStringUtils.randomAlphabetic(2));
a.setStreet(RandomStringUtils.randomAlphabetic(4, 10));
a.setAdministrativeArea(RandomStringUtils.randomAlphabetic(2));
a.setAddressLine1(RandomStringUtils.randomAlphabetic(4, 10));
a.setOrganization(o);
o.setAddress(a);
organizations.add(o);
......
......@@ -16,6 +16,7 @@ import java.util.Objects;
import javax.inject.Inject;
import javax.inject.Singleton;
import javax.json.bind.annotation.JsonbProperty;
import javax.json.bind.annotation.JsonbTransient;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
......@@ -56,11 +57,14 @@ public class Address extends BareNode implements TargetedClone<Address> {
@JoinColumn(name = "organization_id", unique = true)
private FormOrganization organization;
@NotBlank(message = "Street cannot be blank")
private String street;
@NotBlank(message = "City cannot be blank")
private String city;
private String provinceState;
@NotBlank(message = "First address line cannot be blank")
@JsonbProperty("address_line_1")
private String addressLine1;
@JsonbProperty("address_line_2")
private String addressLine2;
@NotBlank(message = "Locality cannot be blank")
private String locality;
private String administrativeArea;
@NotBlank(message = "Country cannot be blank")
private String country;
private String postalCode;
......@@ -77,6 +81,7 @@ public class Address extends BareNode implements TargetedClone<Address> {
}
/** @return the organization */
@JsonbTransient
public FormOrganization getOrganization() {
return this.organization;
}
......@@ -86,83 +91,123 @@ public class Address extends BareNode implements TargetedClone<Address> {
this.organization = org;
}
/** @return the steet */
public String getStreet() {
return street;
/**
* @return the addressLine1
*/
public String getAddressLine1() {
return addressLine1;
}
/**
* @param addressLine1 the addressLine1 to set
*/
public void setAddressLine1(String addressLine1) {
this.addressLine1 = addressLine1;
}
/**
* @return the addressLine2
*/
public String getAddressLine2() {
return addressLine2;
}
/** @param street the street to set */
public void setStreet(String street) {
this.street = street;
/**
* @param addressLine2 the addressLine2 to set
*/
public void setAddressLine2(String addressLine2) {
this.addressLine2 = addressLine2;
}
/** @return the city */
public String getCity() {
return city;
/**
* @return the locality
*/
public String getLocality() {
return locality;
}
/** @param city the city to set */
public void setCity(String city) {
this.city = city;
/**
* @param locality the locality to set
*/
public void setLocality(String locality) {
this.locality = locality;
}
/** @return the provinceState */
public String getProvinceState() {
return provinceState;
/**
* @return the administrativeArea
*/
public String getAdministrativeArea() {
return administrativeArea;
}
/** @param provinceState the provinceState to set */
public void setProvinceState(String provinceState) {
this.provinceState = provinceState;
/**
* @param administrativeArea the administrativeArea to set
*/
public void setAdministrativeArea(String administrativeArea) {
this.administrativeArea = administrativeArea;
}
/** @return the country */
/**
* @return the country
*/
public String getCountry() {
return country;
}
/** @param country the country to set */
/**
* @param country the country to set
*/
public void setCountry(String country) {
this.country = country;
}
/** @return the postalCode */
/**
* @return the postalCode
*/
public String getPostalCode() {
return postalCode;
}
/** @param postalCode the postalCode to set */
/**
* @param postalCode the postalCode to set
*/
public void setPostalCode(String postalCode) {
this.postalCode = postalCode;
}
@Override
public Address cloneTo(Address target) {
target.setCity(getCity());
target.setCountry(getCountry());
target.setPostalCode(getPostalCode());
target.setProvinceState(getProvinceState());
target.setStreet(getStreet());
return target;
}
@Override
public int hashCode() {
return Objects.hash(city, country, id, postalCode, provinceState, street);
final int prime = 31;
int result = super.hashCode();
result = prime * result
+ Objects.hash(addressLine1, addressLine2, administrativeArea, country, id, locality, postalCode);
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
if (!super.equals(obj))
return false;
if (getClass() != obj.getClass())
return false;
Address other = (Address) obj;
return Objects.equals(city, other.city) && Objects.equals(country, other.country)
&& Objects.equals(id, other.id) && Objects.equals(postalCode, other.postalCode)
&& Objects.equals(provinceState, other.provinceState) && Objects.equals(street, other.street);
return Objects.equals(addressLine1, other.addressLine1) && Objects.equals(addressLine2, other.addressLine2)
&& Objects.equals(administrativeArea, other.administrativeArea)
&& Objects.equals(country, other.country) && Objects.equals(id, other.id)
&& Objects.equals(locality, other.locality) && Objects.equals(postalCode, other.postalCode);
}
@Override
public Address cloneTo(Address target) {
target.setAddressLine1(getAddressLine1());
target.setAddressLine2(getAddressLine2());
target.setLocality(getLocality());
target.setAdministrativeArea(getAdministrativeArea());
target.setCountry(getCountry());
target.setPostalCode(getPostalCode());
return target;
}
/**
......@@ -200,4 +245,5 @@ public class Address extends BareNode implements TargetedClone<Address> {
return Address.class;
}
}
}
......@@ -56,11 +56,12 @@ CREATE TABLE `FormWorkingGroup` (
CREATE TABLE `Address` (
`id` varchar(255) NOT NULL,
`city` varchar(255) DEFAULT NULL,
`locality` varchar(255) DEFAULT NULL,
`country` varchar(255) DEFAULT NULL,
`postalCode` varchar(255) DEFAULT NULL,
`provinceState` varchar(255) DEFAULT NULL,
`street` varchar(255) DEFAULT NULL,
`administrativeArea` varchar(255) DEFAULT NULL,
`addressLine1` varchar(255) DEFAULT NULL,
`addressLine2` varchar(255) DEFAULT NULL,
`organization_id` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `UK_i4vgutrsl3ve37hc8xx7vvslf` (`organization_id`),
......
Eclipse Foundation AISBL Membership Application date submitted: {now}
{#if includePreamble}
Dear {name},
Thank you for submitting the Membership Application Form on behalf of your organization. Our team will review your application and follow up within 2 business days.
......@@ -7,6 +8,8 @@ Meanwhile, if you have any questions, please reach out to us at membership.coord
Best regards,
Eclipse Foundation Membership Coordination
{/if}
New form submission by: {name} ({data.form.userID})
......@@ -22,8 +25,9 @@ Employee count: {data.org.employeeCount}
Organization Type: {data.org.organizationType}
Address
{data.org.address.street}
{data.org.address.city}, {org.address.provinceState}
{data.org.address.addressLine1}
{data.org.address.addressLine2}
{data.org.address.locality}, {data.org.address.administrativeArea}
{data.org.address.country}
{data.org.address.postalCode}
......
......@@ -24,7 +24,7 @@
<div>
<h3>Address</h3>
<p>
{data.org.address.street}<br /> {data.org.address.city}<br /> {data.org.address.provinceState}<br /> {data.org.address.country}<br /> {data.org.address.postalCode}
{data.org.address.addressLine1}<br />{data.org.address.addressLine2}<br />{data.org.address.locality}<br /> {data.org.address.administrativeArea}<br /> {data.org.address.country}<br /> {data.org.address.postalCode}
</p>
</div>
......
......@@ -19,6 +19,7 @@
"eclipsefdn-solstice-assets": "^0.0.162",
"formik": "^2.2.6",
"less-watch-compiler": "^1.15.1",
"postal-address-field-names": "^1.0.3",
"react": "^17.0.0",
"react-app-polyfill": "^2.0.0",
"react-dom": "^17.0.0",
......@@ -54,9 +55,9 @@
]
},
"devDependencies": {
"@types/react-router-dom": "^5.1.7",
"@openapi-contrib/openapi-schema-to-json-schema": "^3.1.1",
"@stoplight/json-ref-resolver": "^3.1.2",
"@types/react-router-dom": "^5.1.7",
"decamelize": "^5.0.0",
"js-yaml": "^4.1.0",
"react-datepicker": "^3.2.2",
......
......@@ -79,9 +79,10 @@ export function matchCompanyFields(existingOrganizationData) {
type: existingOrganizationData?.organization_type || '',
address: {
id: existingOrganizationData?.address?.id || '',
street: existingOrganizationData?.address?.street || '',
city: existingOrganizationData?.address?.city || '',
provinceOrState: existingOrganizationData?.address?.province_state || '',
street: existingOrganizationData?.address?.address_line_1 || '',
streetTwo: existingOrganizationData?.address?.address_line_2 || '',
city: existingOrganizationData?.address?.locality || '',
provinceOrState: existingOrganizationData?.address?.administrative_area || '',
country: existingOrganizationData?.address?.country || '',
'country-label': {
label: existingOrganizationData?.address?.country || '',
......@@ -233,11 +234,12 @@ export function matchWorkingGroupFields(
export function matchCompanyFieldsToBackend(organizationData, formId) {
var org = {
address: {
city: organizationData.address.city,
locality: organizationData.address.city,
country: organizationData.address.country,
postal_code: organizationData.address.postalCode || '',
province_state: organizationData.address.provinceOrState || '',
street: organizationData.address.street,
administrative_area: organizationData.address.provinceOrState || '',
address_line_1: organizationData.address.street,
address_line_2: organizationData.address.streetTwo,
},
form_id: formId,
id: organizationData.id,
......@@ -245,7 +247,7 @@ export function matchCompanyFieldsToBackend(organizationData, formId) {
twitter: organizationData.twitterHandle || '',
aggregate_revenue: organizationData.revenue,
employee_count: organizationData.employeeCount,
organization_type: organizationData.type
organization_type: organizationData.type,
};
if (organizationData.address.id) {
......
......@@ -9,6 +9,8 @@ import {
OPTIONS_FOR_EMPLOYEE_COUNT,
HELPERTEXT_FOR_REVENUE,
} from '../../../Constants/Constants';
import countryAddressDetails from 'postal-address-field-names';
import { useState } from 'react';
/**
* Render Oraganization selector (used React-Select)
......@@ -21,18 +23,39 @@ import {
const CompanyInformationCompany = ({ formik, useStyles }) => {
const classes = useStyles();
const { organizationName, organizationTwitter, organizationAddress, organizationRevenue, organizationType } =
formField;
const { organizationName, organizationTwitter, organizationAddress, organizationRevenue, organizationType } = formField;
const [orgAddressObj, setOrgAddressObj] = useState({
street: 'Address 1',
streetTwo: 'Address 2',
city: 'City',
provinceOrState: 'Province',
postalCode: 'Postal Code',
});
// get country list library and map as option pass to the React-Select
const countryList = require('country-list')
.getNames()
.map((item) => ({ label: item, value: item }));
const countryList = countryAddressDetails.map((item) => ({ label: item.name, value: item.name }));
const handleFieldChange = (value, fieldName) => {
formik.setFieldValue(fieldName, value);
};
const handleCountryOnChange = (ev, value) => {
// this is only for display
formik.setFieldValue(`${organizationAddress.country.name}-label`, value || null);
// this is the data will be actually used
formik.setFieldValue(organizationAddress.country.name, value?.value || null);
if (value) {
const currentAddressObj = countryAddressDetails.find((item) => item.name === value.value).fields;
console.log(currentAddressObj);
setOrgAddressObj({
street: currentAddressObj.addressLine1,
streetTwo: currentAddressObj.addressLine2,
city: currentAddressObj.locality,
provinceOrState: currentAddressObj.administrativeArea,
postalCode: currentAddressObj.postalCode,
});
}
};
return (
<>
<h2 className="fw-600 h4" id={organizationName.name}>
......@@ -121,10 +144,10 @@ const CompanyInformationCompany = ({ formik, useStyles }) => {
Address
</h2>
<div className="row">
<div className="col-md-16">
<div className="col-md-12">
<Input
name={organizationAddress.street.name}
labelName={organizationAddress.street.label}
labelName={orgAddressObj.street || 'Address 1'}
placeholder={organizationAddress.street.placeholder}
requiredMark={true}
value={formik.values.organization.address.street}
......@@ -134,17 +157,18 @@ const CompanyInformationCompany = ({ formik, useStyles }) => {
helperText={formik.errors.organization?.address?.street}
/>
</div>
<div className="col-md-8">
<div className="col-md-12">
<Input
name={organizationAddress.city.name}
labelName={organizationAddress.city.label}
placeholder={organizationAddress.city.placeholder}
requiredMark={true}
value={formik.values.organization.address.city}
name={organizationAddress.streetTwo.name}
labelName={orgAddressObj.streetTwo || 'Address 2'}
placeholder={organizationAddress.streetTwo.placeholder}
value={formik.values.organization.address.streetTwo}
onChange={formik.handleChange}
ariaLabel={`${organizationName.name}-address`}
error={formik.touched.organization?.address?.city && Boolean(formik.errors.organization?.address?.city)}
helperText={formik.errors.organization?.address?.city}
ariaLabel={`${organizationName.name}-address2`}
error={
formik.touched.organization?.address?.streetTwo && Boolean(formik.errors.organization?.address?.streetTwo)
}
helperText={formik.errors.organization?.address?.streetTwo}
/>
</div>
</div>
......@@ -159,13 +183,7 @@ const CompanyInformationCompany = ({ formik, useStyles }) => {
fullWidth={true}
freeSolo={true}
openOnFocus={true}
onChange={(ev, value) => {
// this is only for display
formik.setFieldValue(`${organizationAddress.country.name}-label`, value || null);
// this is the data will be actually used
formik.setFieldValue(organizationAddress.country.name, value?.value || null);
}}
onChange={(ev, value) => handleCountryOnChange(ev, value)}
value={formik.values.organization.address['country-label'] || null}
renderInput={(params) => {
params.inputProps = {
......@@ -192,11 +210,25 @@ const CompanyInformationCompany = ({ formik, useStyles }) => {
/>
</div>
<div className="col-md-4">
<Input
name={organizationAddress.city.name}
labelName={orgAddressObj.city || 'City'}
placeholder={orgAddressObj.city}
requiredMark={!!orgAddressObj.city}
value={formik.values.organization.address.city}
onChange={formik.handleChange}
ariaLabel={`${organizationName.name}-address`}
error={formik.touched.organization?.address?.city && Boolean(formik.errors.organization?.address?.city)}
helperText={formik.errors.organization?.address?.city}
/>
</div>
<div className="col-md-8">
<Input
name={organizationAddress.provinceOrState.name}
labelName={organizationAddress.provinceOrState.label}
placeholder={organizationAddress.provinceOrState.placeholder}
labelName={orgAddressObj.provinceOrState || 'Province'}
placeholder={orgAddressObj.provinceOrState}
requiredMark={false}
value={formik.values.organization.address.provinceOrState}
onChange={formik.handleChange}
......@@ -206,11 +238,11 @@ const CompanyInformationCompany = ({ formik, useStyles }) => {
/>
</div>
<div className="col-md-8">
<div className="col-md-4">
<Input
name={organizationAddress.postalCode.name}
labelName={organizationAddress.postalCode.label}
placeholder={organizationAddress.postalCode.placeholder}
labelName={orgAddressObj.postalCode || 'Postal Code'}
placeholder={orgAddressObj.postalCode}
requiredMark={false}
value={formik.values.organization.address.postalCode}
onChange={formik.handleChange}
......
import * as yup from 'yup';
import { MAX_LENGTH_HELPER_TEXT } from '../../../Constants/Constants';
import { requiredErrorMsg } from './formFieldModel';
import countryAddressDetails from 'postal-address-field-names';
/**
* Validation schema passed to Formik
......@@ -32,9 +33,7 @@ import { requiredErrorMsg } from './formFieldModel';
* please refer to: https://formik.org/docs/api/formik#errors--field-string-string-
*/
const countryList = require('country-list')
.getNames()
.map((item) => item);
const countryList = countryAddressDetails.map((item) => item.name);
const REQUIRED_MAX_YUP = yup.string().required(requiredErrorMsg).max(255, MAX_LENGTH_HELPER_TEXT);
const MAX_YUP = yup.string().max(255, MAX_LENGTH_HELPER_TEXT);
......
......@@ -5,7 +5,10 @@ const email = 'Email Address';
const orgName = 'Organization Name';
const twitterLabel = 'Twitter Handle';
const twitter = '@username';
const street = 'Street';
const street = 'Address 1';
const streetPlaceholder = 'Street address of the organization';
const streetTwo = 'Address 2';
const streetTwoPlaceholder = 'Additional information about the street address';
const city = 'City';
const provinceOrState = 'Province or State';
const postalCode = 'Postal Code';
......@@ -39,6 +42,7 @@ export const initialValues = {
address: {
id: '',
street: '',
streetTwo: '',
city: '',
provinceOrState: '',
country: '',
......@@ -165,7 +169,13 @@ export const formField = {
street: {
name: 'organization.address.street',
label: street,
placeholder: street,
placeholder: streetPlaceholder,
requiredErrorMsg: requiredErrorMsg,
},
streetTwo: {