import { MiddlewareError } from '../utils/errors.js'
import { issueErrorMessageHtml } from '../utils/utils.js'
import {
fetchDatasetInfo,
fetchOrgInfo,
logPageError,
validateQueryParams,
createPaginationTemplateParams,
show404IfPageNumberNotInRange,
fetchResources,
processRelevantIssuesMiddlewares,
processEntitiesMiddlewares,
processSpecificationMiddlewares,
getSetBaseSubPath,
getSetDataRange,
getErrorSummaryItems,
prepareIssueDetailsTemplateParams,
filterOutEntitiesWithoutIssues,
getIssueSpecification
} from './common.middleware.js'
import { renderTemplate } from './middleware.builders.js'
import * as v from 'valibot'
export const IssueDetailsQueryParams = v.object({
lpa: v.string(),
dataset: v.string(),
issue_type: v.string(),
issue_field: v.string(),
pageNumber: v.optional(v.pipe(v.string(), v.transform(s => parseInt(s, 10)), v.minValue(1)), '1'),
resourceId: v.optional(v.string())
})
const validateIssueDetailsQueryParams = validateQueryParams({
schema: IssueDetailsQueryParams
})
/**
* @typedef {Object} SummaryItem
* @property {Object} key
* @property {string} key.text
* @property {Object} value
* @property {string} value.html
* @property {string|undefined} value.originalValue
* @property {string} classes
*/
/**
* Returns a data in a format used by the `govukSummaryList` component.
*
* @param {*} text
* @param {*} html
* @param {*} classes
* @returns {SummaryItem}
*/
export const getIssueField = (text, html, classes) => {
classes = classes || ''
return {
key: {
text
},
value: {
html: html ? html.toString() : '',
originalValue: html // we don't want any markup here
},
classes
}
}
export const setRecordCount = (req, res, next) => {
req.recordCount = req?.issueEntities?.length || 0
next()
}
/**
* Updates `req` with `entry` object.
*
* - takes an issueEntity (based on current `pageNumber`)
* - selects `issues` for that entity
* - creates a map of field names to objects (in shape required by govuk component)
* - decorates (with some HTML markup) data in those objects if the field has issues
* - potentially extracts geometry from the entity
*
* @param {Object} req request object
* @param {Object} res response object
* @param {Function} next next function
*/
export function prepareEntity (req, res, next) {
const { issueEntities, issues, specification } = req
const { pageNumber, issue_type: issueType } = req.parsedParams
if (!issueEntities || issueEntities.length === 0) {
return next(new MiddlewareError('No issues for entity', 404))
}
const entityData = issueEntities[pageNumber - 1]
const entityIssues = issues.filter(issue => issue.entity === entityData.entity)
/** @type {Map<string, SummaryItem>} */
const specFields = new Map()
for (const { field, datasetField } of specification.fields) {
const value = entityData[field] || entityData[datasetField]
specFields.set(field, getIssueField(field, value))
}
entityIssues.forEach(issue => {
const field = specFields.get(issue.field)
if (field) {
const message = issue.message || issue.type
field.value.html = issueErrorMessageHtml(message, null) + field.value.html
field.classes += 'dl-summary-card-list__row--error govuk-form-group--error'
} else {
const errorMessage = issue.message || issueType
// TODO: pull the html out of here and into the template
const valueHtml = issueErrorMessageHtml(errorMessage, issue.value)
const classes = 'dl-summary-card-list__row--error govuk-form-group--error'
const newField = getIssueField(issue.field, valueHtml, classes)
newField.value.originalValue = issue.value
specFields.set(issue.field, newField)
}
})
const reference = specFields.get('reference')
const geometries = []
const pushGeometry = (key) => {
const val = specFields.get(key)
if (val) {
geometries.push({
type: key,
geo: val.value.originalValue,
reference: reference?.value?.html
})
}
}
pushGeometry('geometry')
pushGeometry('point')
req.entry = {
title: entityData.name || `entity: ${entityData.entity}`,
fields: [...specFields.values()],
geometries
}
next()
}
/**
*
* @param {Object} req request
* @param {number} req.recordCount
* @param {Object} res response
* @param {Function} next next function
* @returns {undefined}
*/
export const show404ifNoIssues = (req, res, next) => {
const { recordCount } = req
if (recordCount === 0) {
// possibly accessing an outdated URL
return next(new MiddlewareError('no issues found', 404))
}
next()
}
/**
* Middleware. Renders the issue details page with the list of issues, entry data,
* and organisation and dataset details.
*/
export const getIssueDetails = renderTemplate({
templateParams: (req) => req.templateParams,
template: 'organisations/issueDetails.html',
handlerName: 'getIssueDetails'
})
export default [
validateIssueDetailsQueryParams,
fetchOrgInfo,
fetchDatasetInfo,
fetchResources,
...processEntitiesMiddlewares,
...processRelevantIssuesMiddlewares,
...processSpecificationMiddlewares,
getIssueSpecification,
filterOutEntitiesWithoutIssues,
setRecordCount,
show404ifNoIssues,
getSetDataRange(1),
show404IfPageNumberNotInRange,
getSetBaseSubPath(['entity']),
createPaginationTemplateParams,
getErrorSummaryItems,
prepareEntity,
prepareIssueDetailsTemplateParams,
getIssueDetails,
logPageError
]