Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: address field #7966

Open
wants to merge 54 commits into
base: develop
Choose a base branch
from
Open

feat: address field #7966

wants to merge 54 commits into from

Conversation

scottheng96
Copy link
Contributor

@scottheng96 scottheng96 commented Dec 2, 2024

Problem

Closes FRM-1931

There is no current good way to collect address information for forms. Current users are either (1) creating 1 text field to grab address information or (2) creating multiple text fields to grab separated address information (postal code, block number, street name etc.). These can lead to multiple problems:

  • address field has incomplete, incorrect information
  • no type-checking for fields, e.g.
    • unit/level/block number can have special characters/invalid formats
    • postal code is not 6 digits
  • difficult to parse address information downstream (in csv & CRM integrations)

Solution

Build a new basic field focused on collecting local address information. Explicily collect

  1. postal code
  2. block number
  3. street name
  4. building name
  5. level number
  6. unit number

Additionally:

  • provide auto-complete of address population using OneMap API to get address info (block number and street name) based on postal code for ease of input for users

  • allow editing after auto-complete for user input flexibility

  • enforce type-checking for particular address fields
    image

  • address output is handled 2 different ways depending on use case

    1. human-readable format 161, BUKIT BATOK STREET 11, #1-2, SINGAPORE 650161
    • Individual response on admin page
    • email response in storage mode email notification, PDF & email mode response
    1. machine-readable format
    • csv output (separated columns)
    image
    • email mode output (separated key-value pairs)
{"question":"Local address - streetName","answer":"BUKIT BATOK STREET 11"},{"question":"Local address - buildingName","answer":""},{"question":"Local address - levelNumber","answer":"1"},{"question":"Local address - unitNumber","answer":"2"},{"question":"Local address - postalCode","answer":"650161"}
  • address webhook integration is passed as 1 field and handled accordingly (Discussed with plumber team)
{
  "responses": [
    {
      "_id": "6796e79ebbdba20a6e37d6cb",
      "question": "Local address",
      "fieldType": "address",
      "answerArray": [
        "blockNumber_161",
        "streetName_BUKIT BATOK STREET 11",
        "buildingName_",
        "levelNumber_1",
        "unitNumber_1",
        "postalCode_650161"
      ]
    }
  ]
}

Breaking Changes

  • No - this PR is backwards compatible

Features:

  • New compound field Basic Address Field

Screenshots:
image
image

Tests

  1. Can create address field in storage mode
  • Create a new form
  • Create a new 'local address' field on form
  1. Can submitted address field in storage mode
  • Create new form
  • Create a new 'local address' field on form
  • Make form public
  • Attempt a submission
    • submission fails with empty required fields (postal code, block number, street name)
    • submission fails with block number having special characters
    • submission fails with level number having alphabets
    • submission fails with unit number having special characters
    • submission fails with empty level or unit number (populate 1 and keep the other empty)
    • submit form is successful
  • response is display as 1 string (in 1 row) in an individual response
  • download csv is successful; response is display as 6 separate columns with respective headers

(to check email notification display)

  • return to admin page and enable email notifications to be sent on submission
  • make another response
  • verify that email response display is 1 string (in 1 row)
  1. Can submit address field when optional
  • Repeat steps in Test 1 (or reuse existing form already created in step 1 & 2)
  • Make 'local address' field optional
  • Attempt a submission
    • submission fails with empty level or unit number (populate 1 and keep the other empty)
    • submission successful with empty inputs (for all fields)
  1. Can submit multiple address fields
  • Repeat steps in Test 1 (or reuse existing form already created in step 1 & 2)
  • Create additional 'local address' field
  • Attempt a submission
  • responses are displayed as 1 string (in 1 row) in an individual response for each address field (total 2 rows)
  • download csv is successful; response is display as 6 separate columns with respective headers (total 12 cols)
  1. Can submit address field in email mode
  • Create email mode form
  • Create a new 'local address' field on form
  • Attempt a submission, submission is succesful
  • response is display as 1 string (in 1 row) in an email display
  • response is displayed as 6 separate single responses in JSON data collation output (bottom of email)
  1. Can submit address field in MRF mode (1 step)
  • Create MRF mode form
  • Create a new 'short text' field on form
  • Create a new 'local address' field on form
  • Create workflow to assign 'local address' to step 1, 'short text' to step 2 (specify your email to receive step 2 in workflow)
  • Attempt a submission, for step 1
  • verify response display as 1 string (in 1 row) in an individual response
  • download csv is successful; response is display as 6 separate columns with respective headers
  • go to link in your email to complete step 2 (for short text field)
  • Attempted submission for step 2
  • verify response display as 1 string (in 1 row) in an individual response still
  • download csv is successful; response is display as 6 separate columns with respective headers still
  1. Can submit address field in MRF mode (2 step)
  • re-use form from Test 7
  • Update workflow to assign 'local address' to step 2, 'short text' to step 1 (specify your email to receive step 2 in workflow)
  • Attempt a submission, for step 1
  • go to link in your email to complete step 2 (for short text field)
  • Attempted submission for step 2
  • verify response display as 1 string (in 1 row) in an individual response
  • download csv is successful; response is display as 6 separate columns with respective headers
  1. Can edit and submit address field in MRF mode (2 step)
  • re-use form from Test 7
  • Update workflow to assign 'local address' and 'short text' to step 1, 'local address' again to step 2 (specify your email to receive step 2 in workflow)
  • Attempt a submission, for step 1
  • verify response display as 1 string (in 1 row) in an individual response
  • download csv is successful; response is display as 6 separate columns with respective headers
  • go to link in your email to complete step 2
  • Attempted submission for step 2
  • verify response display in individual response admin page is updated
  • download csv is successful; verify csv information in the 6 columns is updated

@datadog-opengovsg
Copy link

datadog-opengovsg bot commented Dec 3, 2024

Datadog Report

Branch report: feat/address-field-fe
Commit report: 2ffc587
Test service: formsg

✅ 0 Failed, 995 Passed, 1 Skipped, 2m 46.47s Total duration (4m 16.62s time saved)

@scottheng96 scottheng96 changed the title feat: address field front end work feat: address field Dec 9, 2024
@datadog-opengovsg
Copy link

datadog-opengovsg bot commented Jan 28, 2025

Datadog Report

Branch report: feat/address-field-fe
Commit report: cc72c86
Test service: formsg

✅ 0 Failed, 710 Passed, 0 Skipped, 1m 30.29s Total duration (5m 45.35s time saved)

Copy link

linear bot commented Jan 28, 2025

@scottheng96 scottheng96 marked this pull request as ready for review January 31, 2025 03:20
Copy link
Contributor

@KenLSM KenLSM left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM in general, have suggestions on import references and function flow.

_id: new ObjectId().toHexString(),
question: `Address question`,
answerArray: [
'blockNumber_161',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note to remove prefixes_

export const DecryptedRow = memo(
({ row, attachmentDecryptionKey }: DecryptedRowProps): JSX.Element => {
console.log(row)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A stray console.log here.

}

// handle postal code additions
if (arr && arr[arr.length - 1])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would recommend avoiding implicit references. i.e., the last value of the array to be postal code.

Perhaps array destructing would be terser here as we would be able to avoid referencing by arr[n]

const arr = [
  "blockNumber_161",
  "streetName_BUKIT BATOK STREET 11",
  "buildingName_",
  "levelNumber_",
  "unitNumber_",
  "postalCode_650161",
];

const [
  blockNumber,
  streetName,
  buildingName,
  levelNumber,
  unitNumber,
  postalCode,
] = arr;

/*
[LOG]: {
  "blockNumber": "blockNumber_161",
  "streetName": "streetName_BUKIT BATOK STREET 11",
  "buildingName": "buildingName_",
  "levelNumber": "levelNumber_",
  "unitNumber": "unitNumber_",
  "postalCode": "postalCode_650161"
} 
*/

// handle leve;/unit number additions
if (arr && arr[arr.length - 2] && arr[arr.length - 3]) {
const combinedUnit = '#' + arr[arr.length - 3] + '-' + arr[arr.length - 2]
arr.splice(arr.length - 3, 2, combinedUnit)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generally, we try to avoid in-place modification as it can easily lead to mistakes.

Comment on lines +91 to +92
* responses format:
* ["blockNumber_161","streetName_BUKIT BATOK STREET 11","buildingName_","levelNumber_","unitNumber_","postalCode_650161"]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can use jsdocs to annotate input format.

Suggested change
* responses format:
* ["blockNumber_161","streetName_BUKIT BATOK STREET 11","buildingName_","levelNumber_","unitNumber_","postalCode_650161"]
* @param responses ["blockNumber_161","streetName_BUKIT BATOK STREET 11","buildingName_","levelNumber_","unitNumber_","postalCode_650161"]

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can also annotate return values

@@ -49,6 +49,7 @@ export const CSP_CORE_DIRECTIVES = {
'https://*.google-analytics.com',
'https://*.analytics.google.com',
'https://*.googletagmanager.com',
'https://www.onemap.gov.sg/api/common/elastic/search?searchVal=*&returnGeom=Y&getAddrDetails=Y',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can whitelist a more general API.

The intention of having this is to protect our users from CORS via unsolicited requests to random sites. (i.e., our web app shouldn't need to call evil.com as part of the normal application usage).

Since we do intent to call onemap.gov.sg we can have it to be https://www.onemap.gov.sg/api/ instead.


/**
* @param responses answerArray from address field
* @returns human-readable format of address, similar to handleAddressResponseDisplay (mutations.ts)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the function's input and output are the same, could we move them to a shared utils for better reuse?

method: 'GET',
headers: {
'Content-Type': 'application/json',
Cookie: '_toffsuid=rB8uPWZEP/wp9bsoBHUZAg==', // check this
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can likely omit this cookie.

import FormLabel from '~components/FormControl/FormLabel'
import Input from '~components/Input'

import { verifyAddress } from '../../../../../src/app/services/address/address.service'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FE code should not reference BE code. If there's a need we'll have to move them to shared.

Specifically for this function, it should exist as a FE service. We can pair on creating this.

@@ -0,0 +1,44 @@
/**
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See comment on frontend/src/templates/Field/Address/AddressField.tsx to move this BE service to FE service

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants