Skip to content

Commit

Permalink
Edit form submission guidance
Browse files Browse the repository at this point in the history
  • Loading branch information
davidjamesstone committed Sep 11, 2024
1 parent f7761db commit bda9b4d
Show file tree
Hide file tree
Showing 9 changed files with 430 additions and 15 deletions.
36 changes: 36 additions & 0 deletions designer/server/src/models/forms/submission-guidance.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { buildErrorList } from '~/src/common/helpers/build-error-details.js'

/**
* @param {FormMetadata} metadata
* @param {ValidationFailure<FormMetadataInput>} [validation]
*/
export function submissionGuidanceViewModel(metadata, validation) {
const pageTitle = 'Tell users what happens after they submit their form'
const { formValues, formErrors } = validation ?? {}

return {
form: metadata,
backLink: {
text: 'Back',
href: `/library/${metadata.slug}`
},
pageTitle,
errorList: buildErrorList(formErrors),
formErrors: validation?.formErrors,
formValues: validation?.formValues,
field: {
id: 'submissionGuidance',
name: 'submissionGuidance',
label: {
text: 'What will happen after a user submits a form?'
},
value: formValues?.submissionGuidance ?? metadata.submissionGuidance
},
buttonText: 'Save and continue'
}
}

/**
* @import { FormMetadata, FormMetadataInput } from '@defra/forms-model'
* @import { ValidationFailure } from '~/src/common/helpers/types.js'
*/
48 changes: 48 additions & 0 deletions designer/server/src/models/forms/submission-guidance.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { submissionGuidanceViewModel } from '~/src/models/forms/submission-guidance.js'

describe('edit - model - submission - guidance', () => {
const now = new Date()
const authorId = 'f50ceeed-b7a4-47cf-a498-094efc99f8bc'
const authorDisplayName = 'Enrique Chase'

/**
* @satisfies {FormMetadataAuthor}
*/
const author = {
id: authorId,
displayName: authorDisplayName
}

/**
* @satisfies {FormMetadata}
*/
const formMetadata = {
id: '661e4ca5039739ef2902b214',
slug: 'my-form-slug',
title: 'Test form',
organisation: 'Defra',
teamName: 'Defra Forms',
teamEmail: '[email protected]',
submissionGuidance: 'We’ll send you an email to let you know the outcome.',
createdAt: now,
createdBy: author,
updatedAt: now,
updatedBy: author
}

it('should test submission guidance view model', () => {
const result = submissionGuidanceViewModel(formMetadata)
expect(result.pageTitle).toBe(
'Tell users what happens after they submit their form'
)
expect(result.field.id).toBe('submissionGuidance')
expect(result.field.name).toBe('submissionGuidance')
expect(result.field.value).toBe(
'We’ll send you an email to let you know the outcome.'
)
})
})

/**
* @import { FormMetadata, FormMetadataAuthor } from '@defra/forms-model'
*/
6 changes: 4 additions & 2 deletions designer/server/src/routes/forms/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,17 @@ import edit from '~/src/routes/forms/edit.js'
import formLifecycle from '~/src/routes/forms/form-lifecycle.js'
import library from '~/src/routes/forms/library.js'
import privacyNotice from '~/src/routes/forms/privacy-notice.js'
import submissionGuidance from '~/src/routes/forms/submission-guidance.js'

export default [
api,
create,
edit,
library,
formLifecycle,
privacyNotice,
contactPhone,
contactEmail,
contactOnline
contactOnline,
submissionGuidance,
privacyNotice
].flat()
116 changes: 116 additions & 0 deletions designer/server/src/routes/forms/submission-guidance.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import { submissionGuidanceSchema } from '@defra/forms-model'
import { StatusCodes } from 'http-status-codes'
import Joi from 'joi'

import * as scopes from '~/src/common/constants/scopes.js'
import { sessionNames } from '~/src/common/constants/session-names.js'
import { buildErrorDetails } from '~/src/common/helpers/build-error-details.js'
import * as forms from '~/src/lib/forms.js'
import { submissionGuidanceViewModel } from '~/src/models/forms/submission-guidance.js'

export const ROUTE_PATH_EDIT_SUBMISSION_GUIDANCE =
'/library/{slug}/edit/submission-guidance'

export const schema = Joi.object().keys({
submissionGuidance: submissionGuidanceSchema.required().messages({
'string.empty': 'Enter what will happen after a user submits a form'
})
})

export default [
/**
* @satisfies {ServerRoute<{ Params: { slug: string } }>}
*/
({
method: 'GET',
path: ROUTE_PATH_EDIT_SUBMISSION_GUIDANCE,
async handler(request, h) {
const { auth, params, yar } = request
const { slug } = params
const { token } = auth.credentials

const validation = yar.flash(sessionNames.validationFailure).at(0)

// Retrieve form by slug
const metadata = await forms.get(slug, token)

// Create the submission guidance view model
const model = submissionGuidanceViewModel(metadata, validation)

return h.view('forms/submission-guidance', model)
},
options: {
auth: {
mode: 'required',
access: {
entity: 'user',
scope: [`+${scopes.SCOPE_WRITE}`]
}
}
}
}),

/**
* @satisfies {ServerRoute<{ Params: { slug: string }, Payload: { submissionGuidance: string } }>}
*/
({
method: 'POST',
path: ROUTE_PATH_EDIT_SUBMISSION_GUIDANCE,
async handler(request, h) {
const { auth, params, payload, yar } = request
const { slug } = params
const { submissionGuidance } = payload
const { token } = auth.credentials

// Retrieve form by slug
const { id } = await forms.get(slug, token)

// Update the metadata with the submission guidance text
await forms.updateMetadata(id, { submissionGuidance }, token)

yar.flash(
sessionNames.successNotification,
'What happens after users submit their form has been updated'
)

return h.redirect(`/library/${slug}`).code(StatusCodes.SEE_OTHER)
},
options: {
validate: {
payload: schema,
/**
* @param {Request} request
* @param {ResponseToolkit} h
* @param {Error} [error]
*/
failAction: (request, h, error) => {
const { payload, yar, url } = request
const { pathname: redirectTo } = url

if (error && error instanceof Joi.ValidationError) {
const formErrors = buildErrorDetails(error)

yar.flash(sessionNames.validationFailure, {
formErrors,
formValues: payload
})
}

// Redirect POST to GET without resubmit on back button
return h.redirect(redirectTo).code(StatusCodes.SEE_OTHER).takeover()
}
},
auth: {
mode: 'required',
access: {
entity: 'user',
scope: [`+${scopes.SCOPE_WRITE}`]
}
}
}
})
]

/**
* @import { Request, ResponseToolkit, ServerRoute } from '@hapi/hapi'
*/
112 changes: 112 additions & 0 deletions designer/server/src/routes/forms/submission-guidance.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import { StatusCodes } from 'http-status-codes'

import { createServer } from '~/src/createServer.js'
import * as forms from '~/src/lib/forms.js'
import { auth } from '~/test/fixtures/auth.js'
import { renderResponse } from '~/test/helpers/component-helpers.js'

jest.mock('~/src/lib/forms.js')

describe('Forms submission guidance', () => {
/** @type {Server} */
let server

beforeAll(async () => {
server = await createServer()
await server.initialize()
})

const now = new Date()
const authorId = 'f50ceeed-b7a4-47cf-a498-094efc99f8bc'
const authorDisplayName = 'Enrique Chase'

/**
* @satisfies {FormMetadataAuthor}
*/
const author = {
id: authorId,
displayName: authorDisplayName
}

/**
* @satisfies {FormMetadata}
*/
const formMetadata = {
id: '661e4ca5039739ef2902b214',
slug: 'my-form-slug',
title: 'Test form',
organisation: 'Defra',
teamName: 'Defra Forms',
teamEmail: '[email protected]',
submissionGuidance: 'We’ll send you an email to let you know the outcome.',
createdAt: now,
createdBy: author,
updatedAt: now,
updatedBy: author
}

/**
* @satisfies {FormDefinition}
*/
const formDefinition = {
name: 'Test form',
pages: [],
conditions: [],
sections: [],
lists: []
}

test('GET - should check correct submission guidance is rendered in the view', async () => {
jest.mocked(forms.get).mockResolvedValueOnce(formMetadata)
jest
.mocked(forms.getDraftFormDefinition)
.mockResolvedValueOnce(formDefinition)

const options = {
method: 'get',
url: '/library/my-form-slug/edit/submission-guidance',
auth
}

const { document } = await renderResponse(server, options)

const submissionGuidance = /** @satisfies {HTMLInputElement | null} */ (
document.querySelector('#submissionGuidance')
)

expect(submissionGuidance?.value).toBe(
'We’ll send you an email to let you know the outcome.'
)
})

test('POST - should redirect to overviewpage after updating submission guidance', async () => {
jest.mocked(forms.get).mockResolvedValueOnce(formMetadata)
jest.mocked(forms.updateMetadata).mockResolvedValueOnce({
id: formMetadata.id,
slug: 'my-form-slug',
status: 'updated'
})

const options = {
method: 'post',
url: '/library/my-form-slug/edit/submission-guidance',
auth,
payload: {
submissionGuidance:
'We’ll send you an email to let you know the outcome1.'
}
}

const {
response: { headers, statusCode }
} = await renderResponse(server, options)

expect(statusCode).toBe(StatusCodes.SEE_OTHER)
expect(headers.location).toBe('/library/my-form-slug')
})
})

/**
* @import { FormDefinition, FormMetadata, FormMetadataAuthor } from '@defra/forms-model'
* @import { Server } from '@hapi/hapi'
*/
31 changes: 31 additions & 0 deletions designer/server/src/views/forms/overview.njk
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,37 @@
]
}) }}

{{ appHeading({
text: "What happens next",
size: "medium",
level: "2",
classes: "govuk-!-margin-top-8"
}) }}

{{ govukSummaryList({
classes: "govuk-!-margin-bottom-8",
rows: [
{
key: {
text: "Tell users what happens after they submit their form"
},
value: {
html: form.submissionGuidance | escape | nl2br | safe
if form.submissionGuidance else '<a class="govuk-link govuk-link--no-visited-state" href="/library/' + form.slug + '/edit/submission-guidance">Enter what happens next</a>'
},
actions: {
items: [
{
href: "/library/" + form.slug + "/edit/submission-guidance",
text: "Change",
visuallyHiddenText: "submission guidance"
}
]
} if form.submissionGuidance
}
]
}) }}

{{ appHeading({
text: "Data protection",
size: "medium",
Expand Down
Loading

0 comments on commit bda9b4d

Please sign in to comment.