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/lpa autoselect #112

Merged
merged 13 commits into from
Jul 10, 2024
19 changes: 19 additions & 0 deletions src/controllers/lpaDetailsController.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import PageController from './pageController.js'
import { fetchLocalAuthorities } from '../utils/fetchLocalAuthorities.js'

class LpaDetailsController extends PageController {
async locals (req, res, next) {
const localAuthoritiesNames = await fetchLocalAuthorities()

const listItems = localAuthoritiesNames.map(name => ({
text: name,
value: name
}))

req.form.options.localAuthorities = listItems

super.locals(req, res, next)
}
}

export default LpaDetailsController
3 changes: 2 additions & 1 deletion src/routes/form-wizard/endpoint-submission-form/steps.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// ToDo: Split this into two form wizards
import chooseDatasetController from '../../../controllers/chooseDatasetController.js'
import LpaDetailsController from '../../../controllers/lpaDetailsController.js'
import PageController from '../../../controllers/pageController.js'
import CheckAnswersController from '../../../controllers/CheckAnswersController.js'

Expand All @@ -20,6 +20,7 @@ export default {
...defaultParams,
fields: ['lpa', 'name', 'email'],
next: 'choose-dataset',
controller: LpaDetailsController,
backLink: '/start'
},
'/choose-dataset': {
Expand Down
1 change: 1 addition & 0 deletions src/serverSetup/middlewares.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export function setupMiddlewares (app) {
})

app.use('/assets', express.static('./node_modules/govuk-frontend/dist/govuk/assets'))
app.use('/assets', express.static('./node_modules/@x-govuk/govuk-prototype-components/x-govuk'))
app.use('/public', express.static('./public'))

app.use(cookieParser())
Expand Down
42 changes: 42 additions & 0 deletions src/utils/fetchLocalAuthorities.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import axios from 'axios'

/**
* Fetches a list of local authority names from a specified dataset.
*
* This function queries a dataset for local authorities, extracting a distinct list of names.
* It performs an HTTP GET request to retrieve the data, then processes the response to return
* only the names of the local authorities.
*
* @returns {Promise<string[]>} A promise that resolves to an array of local authority names.
* @throws {Error} Throws an error if the HTTP request fails or data processing encounters an issue.
*/
export const fetchLocalAuthorities = async () => {
const sql = `select
distinct provision.organisation,
organisation.name,
organisation.dataset
from
provision,
organisation
where
provision.organisation = organisation.organisation
order by
provision.organisation`

const url = `https://datasette.planning.data.gov.uk/digital-land.json?sql=${encodeURIComponent(sql)}`
try {
const response = await axios.get(url)
const names = response.data.rows.map(row => {
if (row[1] === null) {
console.log('Null value found in response:', row)
return null
} else {
return row[1]
}
}).filter(name => name !== null) // Filter out null values
return names // Return the fetched data
} catch (error) {
console.error('Error fetching local authorities data:', error)
throw error // Rethrow the error to be handled by the caller
}
}
7 changes: 7 additions & 0 deletions src/views/layouts/main.html
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,13 @@ <h2 class="govuk-heading-m">Get help</h2>
{% block bodyEnd %}
{{ super()}}
{%block scripts %}
<body>
...
<script type="module" src="/assets/all.js"></script>
<script type="module">
window.GOVUKPrototypeComponents.initAll()
</script>
</body>
{{ super() }}
<script src="/public/js/application.bundle.js"></script>
{% endblock %}
Expand Down
33 changes: 16 additions & 17 deletions src/views/submit/lpa-details.html
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{% extends "layouts/main.html" %}

{% from "govuk/components/input/macro.njk" import govukInput %}
{% from "govuk/components/button/macro.njk" import govukButton %}
{% from "x-govuk/components/autocomplete/macro.njk" import xGovukAutocomplete %}
{% from 'govuk/components/error-summary/macro.njk' import govukErrorSummary %}

{% set serviceType = 'Submit' %}
Expand Down Expand Up @@ -40,22 +40,21 @@ <h1 class="govuk-heading-l">

<form novalidate method="post">

{{
govukInput({
label: {
text: "Local planning authority",
classes: "govuk-label--m",
isPageHeading: false
},
id: "lpa",
name: "lpa",
classes: "govuk-!-width-three-quarters",
errorMessage: {
text: 'lpa' | validationMessageLookup(errors['lpa'].type)
} if 'lpa' in errors,
value: values.lpa
})
}}
{{ xGovukAutocomplete({
id: "lpa",
name: "lpa",
allowEmpty: false,
label: {
classes: "govuk-label--m",
isPageHeading: false,
text: "Choose your local planning authority"
},
items: options.localAuthorities,
errorMessage: {
text: 'lpa' | validationMessageLookup(errors['lpa'].type)
} if 'lpa' in errors,
value: values.lpa
}) }}

{{ govukInput({
label: {
Expand Down
41 changes: 41 additions & 0 deletions test/unit/fetchLocalAuthorities.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import axios from 'axios'
import { vi, it, describe, expect } from 'vitest'
import { fetchLocalAuthorities } from '../../src/utils/fetchLocalAuthorities'

// Mock axios.get to return a fake response
vi.mock('axios')
axios.get.mockResolvedValue({
data: {
rows: [
[1, 'Local Authority 1'],
[2, 'Local Authority 2'],
[3, 'Local Authority 3']
]
}
})

describe('fetchLocalAuthorities', () => {
it('should fetch local authority names', async () => {
const result = await fetchLocalAuthorities()
expect(result).toEqual(['Local Authority 1', 'Local Authority 2', 'Local Authority 3'])
})

it('should throw an error if the HTTP request fails', async () => {
axios.get.mockRejectedValue(new Error('Failed to fetch data'))
await expect(fetchLocalAuthorities()).rejects.toThrow('Failed to fetch data')
})

it('should throw an error if data processing encounters an issue', async () => {
axios.get.mockResolvedValue({
data: {
rows: [
[1, 'Local Authority 1'],
[2, null], // Simulate null value in the response
[3, 'Local Authority 3']
]
}
})
const result = await fetchLocalAuthorities()
expect(result).toEqual(['Local Authority 1', 'Local Authority 3'])
})
})
66 changes: 66 additions & 0 deletions test/unit/lpaDetailsController.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/* eslint-disable no-import-assign */
/* eslint-disable new-cap */

import PageController from '../../src/controllers/pageController.js'
import { vi, it, describe, expect, beforeEach, afterEach } from 'vitest'

vi.mock('../../src/utils/fetchLocalAuthorities.js')

describe('lpaDetailsController', async () => {
let fetchLocalAuthorities
let controller

beforeEach(async () => {
fetchLocalAuthorities = await import('../../src/utils/fetchLocalAuthorities')
const LpaDetailsController = await import('../../src/controllers/lpaDetailsController.js')
controller = new LpaDetailsController.default({
route: '/lpa-details'
})
})

afterEach(() => {
vi.restoreAllMocks()
})

describe('locals', () => {
it('should set localAuthorities options in the form', async () => {
const req = {
form: {
options: {}
}
}
const res = {}
const next = vi.fn()

const localAuthoritiesNames = ['Authority 1', 'Authority 2']

fetchLocalAuthorities.fetchLocalAuthorities = vi.fn().mockResolvedValue(localAuthoritiesNames)

await controller.locals(req, res, next)

expect(fetchLocalAuthorities.fetchLocalAuthorities).toHaveBeenCalled()
expect(req.form.options.localAuthorities).toEqual([
{ text: 'Authority 1', value: 'Authority 1' },
{ text: 'Authority 2', value: 'Authority 2' }
])
expect(next).toHaveBeenCalled()
})

it('should call super.locals', async () => {
const req = {
form: {
options: {}
}
}
const res = {}
const next = vi.fn()

fetchLocalAuthorities.fetchLocalAuthorities = vi.fn().mockResolvedValue([])
const superLocalsSpy = vi.spyOn(PageController.prototype, 'locals')

await controller.locals(req, res, next)

expect(superLocalsSpy).toHaveBeenCalledWith(req, res, next)
})
})
})