diff --git a/src/controllers/publish.ts b/src/controllers/publish.ts index e5a63b9..f75d43d 100644 --- a/src/controllers/publish.ts +++ b/src/controllers/publish.ts @@ -1,7 +1,17 @@ import { Request, Response, NextFunction } from 'express'; +import { snakeCase } from 'lodash'; +import { FieldValidationError } from 'express-validator'; import { generateViewErrors } from '../utils/generate-view-errors'; -import { collectionValidator, descriptionValidator, hasError, titleValidator } from '../validators'; +import { + collectionValidator, + descriptionValidator, + hasError, + qualityValidator, + roundingAppliedValidator, + roundingDescriptionValidator, + titleValidator +} from '../validators'; import { ViewError } from '../dtos/view-error'; import { logger } from '../utils/logger'; import { ViewDTO, ViewErrDTO } from '../dtos/view-dto'; @@ -235,7 +245,7 @@ export const provideSummary = async (req: Request, res: Response, next: NextFunc res.redirect(req.buildUrl(`/publish/${dataset.id}/tasklist`, req.language)); return; } catch (err) { - const error: ViewError = { field: 'title', tag: { name: 'errors.description.missing' } }; + const error: ViewError = { field: 'description', tag: { name: 'errors.description.missing' } }; errors = generateViewErrors(undefined, 400, [error]); } } @@ -262,10 +272,49 @@ export const provideCollection = async (req: Request, res: Response, next: NextF res.redirect(req.buildUrl(`/publish/${dataset.id}/tasklist`, req.language)); return; } catch (err) { - const error: ViewError = { field: 'title', tag: { name: 'errors.collection.missing' } }; + const error: ViewError = { field: 'collection', tag: { name: 'errors.collection.missing' } }; errors = generateViewErrors(undefined, 400, [error]); } } res.render('publish/collection', { collection, errors }); }; + +export const provideQuality = async (req: Request, res: Response, next: NextFunction) => { + let errors: ViewErrDTO | undefined; + const dataset = singleLangDataset(res.locals.dataset, req.language); + let datasetInfo = dataset?.datasetInfo!; + + if (req.method === 'POST') { + try { + datasetInfo = { + quality: req.body.quality, + rounding_applied: req.body.rounding_applied ? req.body.rounding_applied === 'true' : undefined, + rounding_description: req.body.rounding_applied === 'true' ? req.body.rounding_description : '' + }; + + for (const validator of [qualityValidator(), roundingAppliedValidator(), roundingDescriptionValidator()]) { + const result = await validator.run(req); + if (!result.isEmpty()) { + res.status(400); + const error = result.array()[0] as FieldValidationError; + throw error; + } + } + + await req.swapi.updateDatasetInfo(dataset.id, { ...datasetInfo, language: req.language }); + res.redirect(req.buildUrl(`/publish/${dataset.id}/tasklist`, req.language)); + return; + } catch (err: any) { + if (err.path) { + const error: ViewError = { field: err.path, tag: { name: `errors.${snakeCase(err.path)}.missing` } }; + errors = generateViewErrors(dataset.id, 400, [error]); + } else { + next(new UnknownException()); + return; + } + } + } + + res.render('publish/quality', { ...datasetInfo, errors }); +}; diff --git a/src/dtos/dataset-info.ts b/src/dtos/dataset-info.ts index 2eb4e16..6fbc75b 100644 --- a/src/dtos/dataset-info.ts +++ b/src/dtos/dataset-info.ts @@ -4,4 +4,6 @@ export interface DatasetInfoDTO { description?: string; collection?: string; quality?: string; + rounding_applied?: boolean; + rounding_description?: string; } diff --git a/src/middleware/translations/en.json b/src/middleware/translations/en.json index d8e0050..2999259 100644 --- a/src/middleware/translations/en.json +++ b/src/middleware/translations/en.json @@ -89,6 +89,27 @@ "explain_3": "both of the above", "language": "This should be entered in the language in which you're viewing this service." }, + "quality": { + "heading": "What is the statistical quality of this dataset?", + "explain": "In short, simple sentences explain:", + "explain_1": "any issues or methodological changes related to the dataset including any:", + "explain_1a": "notes relevant to all data values in the dataset", + "explain_1b": "custom explanations needed to clarify any note codes used", + "explain_2": "a high-level summary of the statistical quality report, if available", + "language": "This should be entered in the language in which you're viewing this service.", + "rounding": { + "heading": "Has rounding been applied to the data values?", + "options": { + "yes": { + "label": "Yes", + "note": "In short, simple sentences explain what rounding has been applied." + }, + "no": { + "label": "No" + } + } + } + }, "upload": { "title": "Upload the data table", "note": "The file should be in a CSV format" @@ -254,6 +275,15 @@ "collection": { "missing": "Either a collection method was not entered or there is a problem." }, + "quality": { + "missing": "You need to provide the statistical quality of the dataset" + }, + "rounding_applied": { + "missing": "You need to provide whether rounding has been applied to the data values" + }, + "rounding_description": { + "missing": "You need to provide a description of the rounding applied" + }, "upload": { "no_csv": "No CSV data provided", "no_csv_data": "No CSV data available" diff --git a/src/routes/publish.ts b/src/routes/publish.ts index 9d13de6..44a2a13 100644 --- a/src/routes/publish.ts +++ b/src/routes/publish.ts @@ -12,7 +12,8 @@ import { changeData, redirectToTasklist, provideSummary, - provideCollection + provideCollection, + provideQuality } from '../controllers/publish'; export const publish = Router(); @@ -48,3 +49,6 @@ publish.post('/:datasetId/summary', fetchDataset, upload.none(), provideSummary) publish.get('/:datasetId/collection', fetchDataset, provideCollection); publish.post('/:datasetId/collection', fetchDataset, upload.none(), provideCollection); + +publish.get('/:datasetId/quality', fetchDataset, provideQuality); +publish.post('/:datasetId/quality', fetchDataset, upload.none(), provideQuality); diff --git a/src/validators/index.ts b/src/validators/index.ts index d212e8f..dca553c 100644 --- a/src/validators/index.ts +++ b/src/validators/index.ts @@ -1,5 +1,5 @@ import { Request } from 'express'; -import { body, param, query, ValidationChain } from 'express-validator'; +import { body, param, ValidationChain } from 'express-validator'; export const hasError = async (validator: ValidationChain, req: Request) => { return !(await validator.run(req)).isEmpty(); @@ -12,3 +12,8 @@ export const importIdValidator = () => param('importId').trim().notEmpty().isUUI export const titleValidator = () => body('title').trim().notEmpty(); export const descriptionValidator = () => body('description').trim().notEmpty(); export const collectionValidator = () => body('collection').trim().notEmpty(); + +export const qualityValidator = () => body('quality').trim().notEmpty(); +export const roundingAppliedValidator = () => body('rounding_applied').notEmpty().isBoolean(); +export const roundingDescriptionValidator = () => + body('rounding_description').if(body('rounding_applied').equals('true')).trim().notEmpty(); diff --git a/src/views/publish/quality.ejs b/src/views/publish/quality.ejs new file mode 100644 index 0000000..96a8761 --- /dev/null +++ b/src/views/publish/quality.ejs @@ -0,0 +1,78 @@ +<%- include("../partials/header"); %> +