Skip to content
This repository has been archived by the owner on Mar 19, 2024. It is now read-only.

Commit

Permalink
Merge pull request #11 from nhsuk/feature/find-by-postcode
Browse files Browse the repository at this point in the history
Feature/find by postcode - Postcode.io integration and error messages as well as journey
  • Loading branch information
st3v3nhunt authored Mar 15, 2018
2 parents 21117db + 1d15cc0 commit f2931d4
Show file tree
Hide file tree
Showing 41 changed files with 1,569 additions and 157 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,7 @@ public/js
.DS_Store
# the header-items file is generated from an online resource when building the container
app/views/includes/header-items.nunjucks

# Editors of choice internal files (Vim, WebStorm)
*.swp
.idea/*
11 changes: 11 additions & 0 deletions .snyk
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,17 @@ ignore:
- brunch > since-app-start > debug > ms:
reason: no fix available
expires: '2018-03-24T16:54:35.090Z'
'npm:lodash:20180130':
- eslint-config-nhsuk > eslint-plugin-json > jshint > lodash:
reason: None given
expires: '2018-04-12T13:58:42.839Z'
elasticsearch > lodash:
reason: None given
expires: '2018-04-13T09:57:41.009Z'
'npm:shelljs:20140723':
- eslint-config-nhsuk > eslint-plugin-json > jshint > shelljs:
reason: None given
expires: '2018-04-12T13:58:42.839Z'
# patches apply the minimum changes required to fix a vulnerability
patch:
'npm:debug:20170905':
Expand Down
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@
- Symptoms page
- Age page
- Recommend page
- Location page
- PostcodeIO integration
- Form error handling
- Form query validation

0.2.0 / 2018-03-01
=======
Expand Down
21 changes: 21 additions & 0 deletions app/lib/constants.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,25 @@
/* eslint-disable sort-keys */
module.exports = {
ASSETS_URL: 'https://assets.nhs.uk',
SITE_ROOT: '/find-a-chlamydia-test',
SYMPTOMS: {
yes: 'yes',
no: 'no',
},
AGE: {
under16: '1',
'16to25': '2',
over25: '3',
},
SERVICE_CHOICES: {
symptoms: '0',
under16: '1',
'16to25': '2',
over25: '3',
},
SERVICE_TYPES: {
professional: 'professional',
kit: 'kit',
},
};
/* eslint-enable sort-keys */
25 changes: 25 additions & 0 deletions app/lib/messages.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,32 @@
function invalidUrlMessage() {
return 'This is not a valid url, please start again.';
}

function mandatorySelectionMessage() {
return 'You must choose one of the options.';
}

function emptyPostcodeMessage() {
return 'You must enter a postcode to find the service you are looking for.';
}

function invalidPostcodeMessage(location) {
return `We can't find the postcode '${location}'. Check the postcode is correct and try again.`;
}

function outsideOfEnglandPostcodeMessage() {
return 'This is an England only service. Please enter an English postcode.';
}

function technicalProblems() {
return 'Sorry, we are experiencing technical problems.';
}

module.exports = {
emptyPostcodeMessage,
invalidPostcodeMessage,
invalidUrlMessage,
mandatorySelectionMessage,
outsideOfEnglandPostcodeMessage,
technicalProblems,
};
2 changes: 1 addition & 1 deletion app/lib/promBundle.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
const expressPromBundle = require('express-prom-bundle');
const buckets = require('./constants').promHistogramBuckets;

const promBundle = expressPromBundle({ includePath: true, buckets });
const promBundle = expressPromBundle({ buckets, includePath: true });

module.exports = {
middleware: promBundle,
Expand Down
7 changes: 5 additions & 2 deletions app/lib/promCounters.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
const promClient = require('./promBundle').promClient;

module.exports = {
applicationStarts: new promClient.Counter({ name: 'app_starts', help: 'The number of times the application has been started' }),
errorPageViews: new promClient.Counter({ name: 'error_page_views', help: 'The number of error page views' }),
applicationStarts: new promClient.Counter({ help: 'The number of times the application has been started', name: 'app_starts' }),
emptySearchLocationErrors: new promClient.Counter({ help: 'The number of empty search location errors', name: 'empty_search_location_errors' }),
errorPageViews: new promClient.Counter({ help: 'The number of error page views', name: 'error_page_views' }),
outOfEnglandLocationWarnings: new promClient.Counter({ help: 'The number of out of England location warnings', name: 'out_of_england_location_warnings' }),
validationLocationErrors: new promClient.Counter({ help: 'The number of location validation errors', name: 'validation_location_errors' }),
};
51 changes: 51 additions & 0 deletions app/lib/utils/queryMapper.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
const constants = require('../constants');

function getLocationHeading(query) {
if (query.type) {
if ((query.type.localeCompare(constants.SERVICE_TYPES.professional, 'en', { sensitivity: 'base' }) === 0)
&& ((query.origin === constants.SERVICE_CHOICES.symptoms)
|| (query.origin === constants.SERVICE_CHOICES.under16)
|| (query.origin === constants.SERVICE_CHOICES['16to25'])
|| (query.origin === constants.SERVICE_CHOICES.over25))) {
return 'Where would you like to see a sexual health professional?';
}
if (query.type.localeCompare(constants.SERVICE_TYPES.kit, 'en', { sensitivity: 'base' }) === 0) {
if (query.origin === constants.SERVICE_CHOICES['16to25']) {
return 'Where would you like to collect your free test kit?';
} else if (query.origin === constants.SERVICE_CHOICES.over25) {
return 'Where would you like to collect your test kit?';
}
}
}
return undefined;
}

function mapServiceType(query) {
if (query.type) {
return query.type;
}
if (((query.age) && (query.age === constants.AGE.under16))
|| ((query.symptoms) && (query.symptoms === constants.SYMPTOMS.yes))) {
return constants.SERVICE_TYPES.professional;
}
return undefined;
}

function mapServiceChoice(query) {
if (query.origin) {
return query.origin;
}
if ((query.age) && (query.age === constants.AGE.under16)) {
return constants.SERVICE_CHOICES.under16;
}
if ((query.symptoms) && (query.symptoms === constants.SYMPTOMS.yes)) {
return constants.SERVICE_CHOICES.symptoms;
}
return undefined;
}

module.exports = {
getLocationHeading,
mapServiceChoice,
mapServiceType,
};
10 changes: 7 additions & 3 deletions app/middleware/locals.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
// eslint-disable-next-line no-unused-vars
const queryMapper = require('../lib/utils/queryMapper');

module.exports = config =>
(req, res, next) => {
res.locals.GOOGLE_ANALYTICS_TRACKING_ID = config.googleAnalyticsId;
res.locals.WEBTRENDS_ANALYTICS_TRACKING_ID = config.webtrendsId;
res.locals.HOTJAR_ANALYTICS_TRACKING_ID = config.hotjarId;
res.locals.SITE_ROOT = req.app.locals.SITE_ROOT;
res.locals.ASSETS_URL = req.app.locals.ASSETS_URL;

res.locals.symptoms = req.query.symptoms;
res.locals.age = req.query.age;

res.locals.type = queryMapper.mapServiceType(req.query);
res.locals.origin = queryMapper.mapServiceChoice(req.query);
res.locals.location = req.query.location;
res.locals.locationHeading = queryMapper.getLocationHeading(req.query);
res.locals.correctLocationParams = res.locals.locationHeading;
next();
};
13 changes: 13 additions & 0 deletions app/middleware/locationValidator.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
const emptySearchErrors = require('../lib/promCounters').emptySearchLocationErrors;
const renderer = require('../middleware/renderer');

function validateLocation(req, res, next) {
if (!(res.locals.location)) {
emptySearchErrors.inc(1);
renderer.emptyPostcode(req, res);
} else {
next();
}
}

module.exports = validateLocation;
19 changes: 19 additions & 0 deletions app/middleware/notInEnglandHandler.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
const renderer = require('../middleware/renderer');
const warningCounter = require('../lib/promCounters').outOfEnglandLocationWarnings;

function outsideEngland(countries) {
return !countries.includes('England');
}

function notInEnglandHandler(req, res, next) {
const location = res.locals.postcodeLocationDetails;

if (location && outsideEngland(location.countries)) {
warningCounter.inc(1);
renderer.outsideOfEngland(req, res);
} else {
next();
}
}

module.exports = notInEnglandHandler;
57 changes: 57 additions & 0 deletions app/middleware/postcodeLookup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
const PostcodesIOClient = require('postcodesio-client');
const errorCounter = require('../lib/promCounters').errorPageViews;
const log = require('../lib/logger');
const validationCounter = require('../lib/promCounters').validationLocationErrors;

// rewire (a framework for mocking) doesn't support const
// eslint-disable-next-line no-var
var PostcodesIO = new PostcodesIOClient();
// eslint-disable-next-line no-var
var renderer = require('./renderer');

function toArray(countries) {
return Array.isArray(countries) ? countries : [countries];
}

function isOutcode(postcodeDetails) {
return !postcodeDetails.incode;
}

function postcodeDetailsMapper(postcodeDetails) {
return {
countries: toArray(postcodeDetails.country),
isOutcode: isOutcode(postcodeDetails),
location: {
lat: postcodeDetails.latitude,
lon: postcodeDetails.longitude,
},
};
}

async function lookupPostcode(req, res, next) {
const location = res.locals.location;

log.debug({ location }, 'Postcode search text');
if (location) {
try {
const postcodeDetails = await PostcodesIO.lookup(location);
log.debug({ postcodeIOResponse: { postcodeDetails } }, 'PostcodeIO postcode response');
if (postcodeDetails) {
res.locals.postcodeLocationDetails = postcodeDetailsMapper(postcodeDetails);
next();
} else {
validationCounter.inc(1);
renderer.invalidPostcode(req, res, location);
}
} catch (error) {
log.debug({ location }, 'Error in postcode lookup');
errorCounter.inc(1);
next(error);
}
} else {
log.debug('No postcode');
next();
}
}

module.exports = lookupPostcode;
45 changes: 41 additions & 4 deletions app/middleware/renderer.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
const log = require('../lib/logger');
const messages = require('../lib/messages');

function startPage(req, res) {
res.render('start');
}
Expand All @@ -15,13 +18,47 @@ function age(req, res) {
}

function choose(req, res) {
res.render('choose');
return res.render('choose');
}

function location(req, res) {
if (!res.locals.correctLocationParams) {
res.locals.errorMessage = messages.invalidUrlMessage();
return res.render('start');
}
return res.render('location');
}

function results(req, res) {
res.render('results');
}

function emptyPostcode(req, res) {
res.locals.errorMessage = messages.emptyPostcodeMessage();
location(req, res);
}

function invalidPostcode(req, res, loc) {
log.debug({ location: loc }, 'Location failed validation');
res.locals.errorMessage = messages.invalidPostcodeMessage(loc);
location(req, res);
}

function outsideOfEngland(req, res) {
log.debug({ location: res.locals.location }, 'Outside of England');
res.locals.errorMessage = messages.outsideOfEnglandPostcodeMessage();
location(req, res);
}

module.exports = {
startPage,
symptoms,
recommend,
age,
choose,
emptyPostcode,
invalidPostcode,
location,
outsideOfEngland,
recommend,
results,
startPage,
symptoms,
};
2 changes: 1 addition & 1 deletion app/middleware/routeHelper.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ function renderSeeExpertUnder16Page(req, res) {
}

module.exports = {
renderSymptomsWithError,
renderAgePage,
renderAgePageWithError,
renderSeeExpertUnder16Page,
renderSymptomsWithError,
};
2 changes: 1 addition & 1 deletion app/views/error.nunjucks
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{% extends 'layout.nunjucks' %}

{% set message = 'Sorry, we are experiencing technical problems' %}
{% set message = 'Sorry, we are experiencing technical problems.' %}

{% block pageTitle %}{{ message }}{% endblock %}

Expand Down
Loading

0 comments on commit f2931d4

Please sign in to comment.