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

build: release v6.115.0 #7241

Merged
merged 11 commits into from
Apr 4, 2024
Merged
20 changes: 18 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,27 @@ All notable changes to this project will be documented in this file. Dates are d

Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).

#### [v6.115.0](https://github.com/opengovsg/FormSG/compare/v6.114.1...v6.115.0)

- fix: correct date validation for disabled fields [`#7240`](https://github.com/opengovsg/FormSG/pull/7240)
- build: merge release v6.114.1 back to develop [`#7228`](https://github.com/opengovsg/FormSG/pull/7228)
- chore(mrf): add announcement content [`#7229`](https://github.com/opengovsg/FormSG/pull/7229)
- chore(mrf): remove flags [`#7230`](https://github.com/opengovsg/FormSG/pull/7230)
- fix(mrf): workflow email UI cleanup [`#7215`](https://github.com/opengovsg/FormSG/pull/7215)
- fix: add secret key input mask [`#7227`](https://github.com/opengovsg/FormSG/pull/7227)
- fix: correcting submission button bug for MRF [`#7232`](https://github.com/opengovsg/FormSG/pull/7232)
- fix(mrf): handling for incorrect submission secret key in url query params [`#7219`](https://github.com/opengovsg/FormSG/pull/7219)
- chore(deps-dev): bump @types/json-stringify-safe from 5.0.0 to 5.0.3 [`#7235`](https://github.com/opengovsg/FormSG/pull/7235)
- fix(deps): bump nan from 2.17.0 to 2.19.0 [`#7212`](https://github.com/opengovsg/FormSG/pull/7212)
- chore(deps-dev): bump @types/express from 4.17.17 to 4.17.21 [`#7233`](https://github.com/opengovsg/FormSG/pull/7233)

#### [v6.114.1](https://github.com/opengovsg/FormSG/compare/v6.114.0...v6.114.1)

> 3 April 2024

- build: release v6.114.0 [`#7226`](https://github.com/opengovsg/FormSG/pull/7226)
- fix: correcting submission button bug for MRF [`7342138`](https://github.com/opengovsg/FormSG/commit/73421387daf8f3a33c0030a43c81bb11190ee198)
- ref: genericize EndPageBlock [`017cfb3`](https://github.com/opengovsg/FormSG/commit/017cfb36457cbe293360c963f3d66257e5172907)
- chore: bump version to 6.114.1 [`c1c7ac2`](https://github.com/opengovsg/FormSG/commit/c1c7ac29e6bd8e2c3ab80b2fde22f84b778f1b4f)
- fix: correcting submission button bug for MRF [`96628aa`](https://github.com/opengovsg/FormSG/commit/96628aa3b4dd5abae00b429486139431a8b3bf05)

#### [v6.114.0](https://github.com/opengovsg/FormSG/compare/v6.113.0...v6.114.0)

Expand Down
4 changes: 2 additions & 2 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "form-frontend",
"version": "6.114.1",
"version": "6.115.0",
"homepage": ".",
"private": true,
"dependencies": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,8 @@ export const SecretKeyVerificationInput = ({
<Stack direction="row" spacing="0.5rem">
<Skeleton isLoaded={!isLoading} w="100%">
<Input
data-testid="secretKey"
type="password"
isDisabled={isLoading}
{...register(SECRET_KEY_NAME, secretKeyValidationRules)}
/>
Expand Down
1 change: 1 addition & 0 deletions frontend/src/constants/links.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export const GUIDE_EMAIL_MODE = 'https://go.gov.sg/formsg-guide-email-mode'
export const GUIDE_STORAGE_MODE = 'https://go.gov.sg/formsg-guide-storage-mode'
export const GUIDE_FORM_LOGIC = 'https://go.gov.sg/formsg-guide-logic'
export const GUIDE_FORM_MRF = 'https://go.gov.sg/formsg-guide-mrf'
export const GUIDE_MRF_MODE = 'http://go.gov.sg/formsg-mrf'
export const GUIDE_SPCP_ESRVCID =
'https://go.gov.sg/formsg-guide-singpass-myinfo'
export const GUIDE_ENABLE_SPCP =
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/constants/localStorage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export const LOCAL_STORAGE_EVENT = 'local-storage'
* Key to store whether a user has seen the rollout announcements before.
*/
export const ROLLOUT_ANNOUNCEMENT_KEY_PREFIX =
'has-seen-rollout-announcement-20231121-'
'has-seen-rollout-announcement-20240404-'

/**
* Key to store whether the admin has seen the feature tour in localStorage.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,25 +135,14 @@ export const StorageFormUnlocked = Template.bind({})
StorageFormUnlocked.parameters = StorageForm.parameters
StorageFormUnlocked.play = async ({ canvasElement }) => {
const canvas = within(canvasElement)
const inputName =
/enter or upload secret key your secret key was downloaded when you created your form/i

await waitFor(
async () => {
expect(
canvas.getByRole('textbox', {
name: inputName,
}),
).not.toBeDisabled()
expect(canvas.getByTestId('secretKey')).not.toBeDisabled()
},
{ timeout: 5000 },
)
await userEvent.type(
canvas.getByRole('textbox', {
name: inputName,
}),
MOCK_KEYPAIR.secretKey,
)
await userEvent.type(canvas.getByTestId('secretKey'), MOCK_KEYPAIR.secretKey)

await userEvent.click(
canvas.getByRole('button', { name: /unlock responses/i }),
Expand Down
94 changes: 79 additions & 15 deletions frontend/src/features/public-form/PublicFormProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
} from 'react'
import { Helmet } from 'react-helmet-async'
import { SubmitHandler } from 'react-hook-form'
import { useNavigate } from 'react-router-dom'
import { useNavigate, useSearchParams } from 'react-router-dom'
import { useDisclosure } from '@chakra-ui/react'
import { datadogLogs } from '@datadog/browser-logs'
import { useGrowthBook } from '@growthbook/growthbook-react'
Expand Down Expand Up @@ -36,6 +36,7 @@ import { MONGODB_ID_REGEX } from '~constants/routes'
import { useBrowserStm } from '~hooks/payments'
import { useTimeout } from '~hooks/useTimeout'
import { useToast } from '~hooks/useToast'
import { isKeypairValid } from '~utils/secretKeyValidation'
import { HttpError } from '~services/ApiService'
import { FormFieldValues } from '~templates/Field'

Expand Down Expand Up @@ -138,6 +139,7 @@ export const PublicFormProvider = ({
// Stop querying once submissionData is present.
/* enabled= */ !submissionData,
)

const {
data: encryptedPreviousSubmission,
isLoading: isSubmissionLoading,
Expand All @@ -149,8 +151,45 @@ export const PublicFormProvider = ({
/* enabled= */ !submissionData,
)

const isLoading = isFormLoading || isSubmissionLoading
const error = publicFormError || encryptedSubmissionError

const [previousSubmission, setPreviousSubmission] =
useState<ReturnType<typeof decryptSubmission>>()
const [isSubmissionSecretKeyInvalid, setIsSubmissionSecretKeyInvalid] =
useState(false)

const [searchParams] = useSearchParams()

if (
previousSubmissionId &&
encryptedPreviousSubmission &&
!previousSubmission &&
!isSubmissionSecretKeyInvalid
) {
let submissionSecretKey = ''
try {
submissionSecretKey = decodeURIComponent(searchParams.get('key') ?? '')
} catch (e) {
console.log(e)
}

const isValid = isKeypairValid(
encryptedPreviousSubmission.submissionPublicKey,
submissionSecretKey,
)

if (isValid) {
setPreviousSubmission(
decryptSubmission({
submission: encryptedPreviousSubmission,
secretKey: submissionSecretKey,
}),
)
} else {
setIsSubmissionSecretKeyInvalid(true)
}
}

// Replace form fields, logic, and workflow with the previous version for MRF consistency.
if (data && encryptedPreviousSubmission) {
Expand All @@ -161,9 +200,6 @@ export const PublicFormProvider = ({
}
}

const isLoading = isFormLoading || isSubmissionLoading
const error = publicFormError || encryptedSubmissionError

const growthbook = useGrowthBook()

useEffect(() => {
Expand Down Expand Up @@ -265,15 +301,35 @@ export const PublicFormProvider = ({
if (data) trackVisitPublicForm(data.form)
}, [data])

const isFormNotFound = useMemo(() => {
return (
(error instanceof HttpError &&
(error.code === 404 || error.code === 410)) ||
(!!previousSubmissionId &&
!!data &&
data.form.responseMode !== FormResponseMode.Multirespondent)
)
}, [data, error, previousSubmissionId])
const formNotFoundMessage = useMemo(() => {
// Server response 404 or 410
const isFormNotFound =
error instanceof HttpError && (error.code === 404 || error.code === 410)

// Non MRFs should not use the :formId/edit/:submissionId path
const isNonMultirespondentFormWithPreviousSubmissionId =
!!data &&
data.form.responseMode !== FormResponseMode.Multirespondent &&
!!previousSubmissionId

if (isFormNotFound || isNonMultirespondentFormWithPreviousSubmissionId) {
return {
title: 'Form not found',
header: 'This form is not available.',
message: error?.message || 'Form not found',
}
}

// Decryption failed for previous submission
if (isSubmissionSecretKeyInvalid) {
return {
title: 'Invalid form link',
header: 'This form link is no longer valid.',
message:
'A submission may have already been made using this link. If you think this is a mistake, please contact the agency that gave you the form link.',
}
}
}, [error, data, previousSubmissionId, isSubmissionSecretKeyInvalid])

const generateVfnExpiryToast = useCallback(() => {
if (vfnToastIdRef.current) {
Expand Down Expand Up @@ -713,8 +769,16 @@ export const PublicFormProvider = ({
...rest,
}}
>
<Helmet title={isFormNotFound ? 'Form not found' : data?.form.title} />
{isFormNotFound ? <FormNotFound message={error?.message} /> : children}
<Helmet
title={
formNotFoundMessage ? formNotFoundMessage.title : data?.form.title
}
/>
{formNotFoundMessage ? (
<FormNotFound {...formNotFoundMessage} />
) : (
children
)}
</PublicFormContext.Provider>
)
}
Original file line number Diff line number Diff line change
@@ -1,37 +1,27 @@
import { useMemo } from 'react'
import { useSearchParams } from 'react-router-dom'
import { Box } from '@chakra-ui/react'

import { FormAuthType, FormResponseMode } from '~shared/types'

import { isKeypairValid } from '~utils/secretKeyValidation'

import { usePublicFormContext } from '~features/public-form/PublicFormContext'
import { decryptSubmission } from '~features/public-form/utils/decryptSubmission'

import { FormAuth } from '../FormAuth'

import { FormFields } from './FormFields'
import { FormFieldsSkeleton } from './FormFieldsSkeleton'
import { SecretKeyVerification } from './SecretKeyVerification'

export const FormFieldsContainer = (): JSX.Element | null => {
const {
form,
previousSubmissionId,
isAuthRequired,
isLoading,
handleSubmitForm,
submissionData,
encryptedPreviousSubmission,
previousSubmission,
setPreviousSubmission,
} = usePublicFormContext()

const { submissionPublicKey = null, workflowStep } =
encryptedPreviousSubmission ?? {}
const [searchParams] = useSearchParams()
const queryParams = Object.fromEntries([...searchParams])
const { workflowStep } = encryptedPreviousSubmission ?? {}

const renderFields = useMemo(() => {
// Render skeleton when no data
Expand All @@ -49,48 +39,6 @@ export const FormFieldsContainer = (): JSX.Element | null => {
return <FormAuth authType={form.authType} />
}

// MRF
if (previousSubmissionId && !previousSubmission) {
let submissionSecretKey = ''
try {
submissionSecretKey = queryParams.key
? decodeURIComponent(queryParams.key || '')
: ''
} catch (e) {
console.log(e)
}

const isValid = isKeypairValid(
submissionPublicKey || '',
submissionSecretKey,
)

if (isValid) {
setPreviousSubmission(
decryptSubmission({
submission: encryptedPreviousSubmission,
secretKey: submissionSecretKey,
}),
)
}

return (
<SecretKeyVerification
publicKey={submissionPublicKey}
setSecretKey={(secretKey) =>
setPreviousSubmission(
decryptSubmission({
submission: encryptedPreviousSubmission,
secretKey,
}),
)
}
isLoading={isLoading}
prefillSecretKey={submissionSecretKey}
/>
)
}

return (
<FormFields
previousResponses={previousSubmission?.responses}
Expand All @@ -113,14 +61,9 @@ export const FormFieldsContainer = (): JSX.Element | null => {
isLoading,
form,
isAuthRequired,
previousSubmissionId,
previousSubmission,
workflowStep,
handleSubmitForm,
submissionPublicKey,
queryParams.key,
setPreviousSubmission,
encryptedPreviousSubmission,
])

if (submissionData) return null
Expand Down

This file was deleted.

Loading
Loading