From fb0ebfa6b2792ca989f6584348880d7275c8cc2d Mon Sep 17 00:00:00 2001 From: Roland Sadowski Date: Wed, 12 Mar 2025 13:47:59 +0000 Subject: [PATCH 1/2] issue details page for out of bounds expectations Also includes: - tweaks to logging - schema errors can easily produce megabytes of logs, so we limit that - refactoring of the pagination code - update issueDetails.html template to not display the back link to "Back to dataset table" - doesn't make sense --- config/default.yaml | 2 +- config/production.yaml | 2 +- config/util.js | 17 +- src/controllers/OrganisationsController.js | 4 +- src/middleware/common.middleware.js | 76 ++------- ...t-failed-expectation-details.middleware.js | 159 ++++++++++++++++++ src/middleware/datasetOverview.middleware.js | 2 +- src/middleware/datasetTaskList.middleware.js | 5 +- src/middleware/lpa-overview.middleware.js | 2 +- src/routes/organisations.js | 2 + src/routes/schemas.js | 34 ++-- src/serverSetup/errorHandlers.js | 2 +- src/utils/custom-renderer.js | 3 +- src/utils/pagination.js | 97 +++++++++++ src/views/organisations/issueDetails.html | 17 +- .../datasetTaskList.middleware.test.js | 2 +- 16 files changed, 332 insertions(+), 94 deletions(-) create mode 100644 src/middleware/dataset-failed-expectation-details.middleware.js diff --git a/config/default.yaml b/config/default.yaml index 50530ebf..3b967830 100644 --- a/config/default.yaml +++ b/config/default.yaml @@ -111,7 +111,7 @@ features: # enabling this feature will cause the results page to present # links to individual issue pages enabled: true - expectactionOutOfBoundsTask: + expectationOutOfBoundsTask: # this feature includes violated 'out of bounds' expectations # as 'tasks' on LPA overview pages etc. # https://github.com/digital-land/submit/issues/921 diff --git a/config/production.yaml b/config/production.yaml index 76618786..54973fea 100644 --- a/config/production.yaml +++ b/config/production.yaml @@ -28,5 +28,5 @@ features: # enabling this feature will cause the results page to present # links to individual issue pages enabled: false - expectactionOutOfBoundsTask: + expectationOutOfBoundsTask: enabled: false \ No newline at end of file diff --git a/config/util.js b/config/util.js index 151149c8..fee9243e 100644 --- a/config/util.js +++ b/config/util.js @@ -5,6 +5,18 @@ import * as v from 'valibot' const NonEmptyString = v.pipe(v.string(), v.nonEmpty()) +/** + * Informatino to allow us to display sanely worded messages about entities. + * + * - `base` is the part that doesn't change + * - `variable` should be specified in singular, and can be potentially turned into plural + * (by application code or template) when displayed. + */ +const EntityDisplay = v.object({ + base: v.optional(NonEmptyString), + variable: NonEmptyString +}) + export const ConfigSchema = v.object({ port: v.pipe(v.integer(), v.minValue(1)), asyncRequestApi: v.object({ @@ -64,7 +76,10 @@ export const ConfigSchema = v.object({ 'listed-building', 'listed-building-outline' ].reduce((acc, key) => { - acc[key] = v.object({ guidanceUrl: v.string() }) + acc[key] = v.object({ + guidanceUrl: v.string(), + entityDisplayName: EntityDisplay + }) return acc }, {}) ), diff --git a/src/controllers/OrganisationsController.js b/src/controllers/OrganisationsController.js index 347f24a2..4d7359b9 100644 --- a/src/controllers/OrganisationsController.js +++ b/src/controllers/OrganisationsController.js @@ -8,6 +8,7 @@ import getStartedMiddleware from '../middleware/getStarted.middleware.js' import overviewMiddleware from '../middleware/lpa-overview.middleware.js' import datasetDataviewMiddleware from '../middleware/dataview.middleware.js' import datasetEndpointIssueMiddleware from '../middleware/datasetEndpointIssue.middleware.js' +import datasetFailedExpectationIssueMiddleware from '../middleware/dataset-failed-expectation-issue.middleware.js' const organisationsController = { organisationsMiddleware, @@ -19,7 +20,8 @@ const organisationsController = { getStartedMiddleware, overviewMiddleware, datasetDataviewMiddleware, - datasetEndpointIssueMiddleware + datasetEndpointIssueMiddleware, + datasetFailedExpectationIssueMiddleware } export default organisationsController diff --git a/src/middleware/common.middleware.js b/src/middleware/common.middleware.js index 7f8e4261..db92e236 100644 --- a/src/middleware/common.middleware.js +++ b/src/middleware/common.middleware.js @@ -1,11 +1,15 @@ // @ts-check +/** + * @module middleware-common + * + */ import logger from '../utils/logger.js' import { types } from '../utils/logging.js' import { dataSubjects, entryIssueGroups } from '../utils/utils.js' import performanceDbApi from '../services/performanceDbApi.js' import { fetchMany, fetchOne, FetchOneFallbackPolicy, FetchOptions, renderTemplate } from './middleware.builders.js' import * as v from 'valibot' -import { pagination } from '../utils/pagination.js' +import { createPaginationTemplateParamsObject } from '../utils/pagination.js' import datasette from '../services/datasette.js' import { errorTemplateContext, MiddlewareError } from '../utils/errors.js' import { dataRangeParams } from '../routes/schemas.js' @@ -126,6 +130,14 @@ export const show404IfPageNumberNotInRange = (req, res, next) => { next() } +/** + * Potentially Updates `req` with `pagination` + * + * @param {Object} req request + * @param {Object} res response + * @param {Function} next next function + * @returns {undefined} + */ export const createPaginationTemplateParams = (req, res, next) => { const { pageNumber } = req.parsedParams const { baseSubpath, dataRange } = req @@ -139,62 +151,7 @@ export const createPaginationTemplateParams = (req, res, next) => { return next() } - /** - * @typedef {Object} PaginationItem - * @property {'ellipsis'|'number'} type - Type of pagination item - * @property {number} [number] - Page number (for number type) - * @property {string} href - Link URL - * @property {boolean} [ellipsis] - Whether this is an ellipsis item - * @property {boolean} [current] - Whether this is the current page - */ - - /** - * @typedef {Object} PaginationNav - * @property {Object} [previous] - Previous page link - * @property {string} previous.href - Previous page URL - * @property {Object} [next] - Next page link - * @property {string} next.href - Next page URL - * @property {PaginationItem[]} items - Pagination items - */ - - /** @type {PaginationNav} */ - const paginationObj = { - previous: undefined, - next: undefined, - items: [] - } - - if (pageNumber > 1) { - paginationObj.previous = { - href: `${baseSubpath}/${pageNumber - 1}` - } - } - if (pageNumber < dataRange.maxPageNumber) { - paginationObj.next = { - href: `${baseSubpath}/${pageNumber + 1}` - } - } - - for (const item of pagination(dataRange.maxPageNumber, Math.min(pageNumber, dataRange.maxPageNumber))) { - if (item === '...') { - paginationObj.items.push({ - type: 'ellipsis', - ellipsis: true, - href: '#' - }) - } else if (typeof item === 'number') { - paginationObj.items.push({ - type: 'number', - number: item, - href: `${baseSubpath}/${item}`, - current: pageNumber === item - }) - } else { - logger.warn('unexpected pagination item', { item, dataRange, types: types.App, endpoint: req.originalUrl }) - } - } - - req.pagination = paginationObj + req.pagination = createPaginationTemplateParamsObject({ pageNumber, dataRange, baseSubpath }) next() } @@ -852,7 +809,8 @@ export function noop (req, res, next) { const expectationsOutOfBoundsDetailsSelectClause = () => { return `CAST(JSON_EXTRACT(details, '$.actual') AS INTEGER) AS actual, - CAST(JSON_EXTRACT(details, '$.expected') AS INTEGER) AS expected` + CAST(JSON_EXTRACT(details, '$.expected') AS INTEGER) AS expected, + details as details` } const expectationsQuery = ({ lpa, dataset, expectation, includeDetails }) => { @@ -874,7 +832,7 @@ const expectationsQuery = ({ lpa, dataset, expectation, includeDetails }) => { * The `name` field is used in queries. */ export const expectations = { - entitiesOutOfBounds: { name: 'Check no entities are outside of the local planning authority boundary' } + entitiesOutOfBounds: { name: 'Check no entities are outside of the local planning authority boundary', slug: 'out-of-bounds' } } /** diff --git a/src/middleware/dataset-failed-expectation-details.middleware.js b/src/middleware/dataset-failed-expectation-details.middleware.js new file mode 100644 index 00000000..058060d8 --- /dev/null +++ b/src/middleware/dataset-failed-expectation-details.middleware.js @@ -0,0 +1,159 @@ +/** + * @module middleware-dataset-failed-expectation-details + * + * @description Responsible for displaying issue page for a failed expectation of a dataset. + * + * See https://datasette.planning.data.gov.uk/digital-land/expectation for data. + */ + +import * as v from 'valibot' +import { + validateOrgAndDatasetQueryParams, + expectationFetcher, + expectations, + validateQueryParams, + fetchDatasetInfo, + fetchOrgInfo +} from './common.middleware.js' +import { getIssueDetails } from './entityIssueDetails.middleware.js' +import { entityOutOfBoundsMessage } from './datasetTaskList.middleware.js' +import { MiddlewareError } from '../utils/errors.js' +import logger from '../utils/logger.js' +import { types } from '../utils/logging.js' +import { fetchOne, FetchOptions } from './middleware.builders.js' +import { createPaginationTemplateParamsObject } from '../utils/pagination.js' + +const ExpectationPathParams = v.union(Object.values(expectations).map(exp => v.literal(exp.slug))) + +const validateExpectationParams = validateQueryParams({ + schema: v.object({ + expectation: ExpectationPathParams, + pageNumber: v.optional(v.pipe(v.string(), v.transform(s => parseInt(s, 10)), v.minValue(1)), '1') + }) +}) + +const fetchOutOfBoundsExpectations = expectationFetcher({ + expectation: expectations.entitiesOutOfBounds, + includeDetails: true, + result: 'expectationOutOfBounds' +}) + +const fetchEntity = fetchOne({ + query: ({ req }) => /* sql */ `select * --entity, name, geometry, point, entry_date + from entity + where entity = ${req.entityIds[req.parsedParams.pageNumber - 1]}`, + dataset: FetchOptions.fromParams, + result: 'entity' +}) + +const validateExpectationsFailed = (req, res, next) => { + if (req.expectationOutOfBounds.length === 0) { + next(new MiddlewareError('expectation query for out of bounds entities returned no results)', 404)) + } else { + next() + } +} + +/** + * @param {String} s JSON string + * @returns {Object|undefined} + */ +const safeParse = (s) => { + try { + return JSON.parse(s) + } catch (error) { + logger.info('safeParse() failed to parse', { type: types.App, string: s }) + return undefined + } +} + +const deserialiseEntities = (req, res, next) => { + const { expectationOutOfBounds } = req + req.entityIds = safeParse(expectationOutOfBounds[0].details)?.entities + next() +} + +const preparePaginationInfo = (req, res, next) => { + const { orgInfo: organisation, dataset, entityIds = [] } = req + const { pageNumber } = req.parsedParams + + req.dataRange = { + minRow: 1, + maxRow: 1, + totalRows: 1, + maxPageNumber: entityIds.length, + pageLength: 1, + offset: 0 + } + + const baseSubpath = `/organisations/${organisation.organisation}/${dataset.dataset}/expectation/out-of-bounds/entity` + req.pagination = createPaginationTemplateParamsObject({ pageNumber, baseSubpath, dataRange: req.dataRange }) + + next() +} + +/** + * + * @param {Object} req The request object. It should contain the following properties: + * @param {Object} req.parsedParams An object containing the parameters of the request + * @param {Object} req.dataset dataset info + * @param {Object} req.orgInfo org info + * @param {Object[]} [req.expectationOutOfBounds] + * @param {string} req.expectationOutOfBounds[].dataset + * @param {boolean} req.expectationOutOfBounds[].passed did the exepectation pass + * @param {number} req.expectationOutOfBounds[].expected + * @param {number} req.expectationOutOfBounds[].actual + * @param {String} req.expectationOutOfBounds[].details JSON string + * @param {String[]} [req.entityIds] ids of entities out of bounds + * @param {Object} req.dataRange + * @param {Object} req.pagination pagination info + * @param {Object} [req.entity] + * @param {Object} req.templateParams OUT value + * @param {Object} res - The response object. + * @param {Function} next - The next middleware function. + * @returns {undefined} + */ +const prepareTemplateParams = (req, res, next) => { + const { orgInfo: organisation, dataset, expectationOutOfBounds, entity, dataRange, pagination } = req + + req.templateParams = { + organisation, + dataset, + errorSummary: { + items: [ + { html: entityOutOfBoundsMessage(dataset.dataset, expectationOutOfBounds[0].actual), href: '' } + ] + }, + // we're hijacking isssueType & issueField here + issueType: 'expectation', + issueField: expectations.entitiesOutOfBounds.slug, + entry: { + title: `Entity: ${entity.entity}`, + fields: Object.entries(entity).map(([k, v]) => { + return { + key: { text: k }, + value: { html: `${v}` }, + classes: '' + } + }) + }, + dataRange, + pagination + } + + next() +} + +export default [ + validateOrgAndDatasetQueryParams, + validateExpectationParams, + fetchOrgInfo, + fetchDatasetInfo, + fetchOutOfBoundsExpectations, + validateExpectationsFailed, + deserialiseEntities, + fetchEntity, + preparePaginationInfo, + prepareTemplateParams, + getIssueDetails +] diff --git a/src/middleware/datasetOverview.middleware.js b/src/middleware/datasetOverview.middleware.js index ebd00773..3e5811c6 100644 --- a/src/middleware/datasetOverview.middleware.js +++ b/src/middleware/datasetOverview.middleware.js @@ -266,7 +266,7 @@ export default [ fetchEntityIssueCounts, fetchEntryIssueCounts, fetchSpecification, - isFeatureEnabled('expectactionOutOfBoundsTask') ? fetchOutOfBoundsExpectations : noop, + isFeatureEnabled('expectationOutOfBoundsTask') ? fetchOutOfBoundsExpectations : noop, pullOutDatasetSpecification, // setNoticesFromSourceKey('resources'), // commented out as the logic is currently incorrect (https://github.com/digital-land/submit/issues/824) fetchEntityCount, diff --git a/src/middleware/datasetTaskList.middleware.js b/src/middleware/datasetTaskList.middleware.js index f9c67f4c..7e7756f3 100644 --- a/src/middleware/datasetTaskList.middleware.js +++ b/src/middleware/datasetTaskList.middleware.js @@ -75,7 +75,6 @@ const SPECIAL_ISSUE_TYPES = ['reference values are not unique'] */ export function entityOutOfBoundsMessage (dataset, count) { const displayNameConfig = config.datasetsConfig[dataset]?.entityDisplayName ?? { variable: 'entity', base: '' } - logger.info('about to pluralize: ', displayNameConfig) // if count is missing for some reason, we don't display it and default to plural form const displayName = `${displayNameConfig.base ?? ''} ${pluralize(displayNameConfig.variable, count ?? 2)}`.trim() return `You have ${count ?? ''} ${displayName} outside of your boundary`.replace(/ {2}/, ' ') @@ -170,7 +169,7 @@ export const prepareTasks = (req, res, next) => { title: { text: entityOutOfBoundsMessage(dataset, expectationOutOfBounds[0].actual) }, - href: '', // NOTE: design for 'expectation' task detail page not ready yet + href: `/organisations/${encodeURIComponent(lpa)}/${encodeURIComponent(dataset)}/expectation/${encodeURIComponent(expectations.entitiesOutOfBounds.slug)}`, status: getStatusTag('Needs fixing') }) } @@ -218,7 +217,7 @@ export default [ fetchSources, fetchDatasetInfo, fetchResources, - isFeatureEnabled('expectactionOutOfBoundsTask') ? fetchOutOfBoundsExpectations : noop, + isFeatureEnabled('expectationOutOfBoundsTask') ? fetchOutOfBoundsExpectations : noop, addEntityCountsToResources, ...processEntitiesMiddlewares, fetchEntityIssueCounts, diff --git a/src/middleware/lpa-overview.middleware.js b/src/middleware/lpa-overview.middleware.js index f7cb54cc..78c4cf66 100644 --- a/src/middleware/lpa-overview.middleware.js +++ b/src/middleware/lpa-overview.middleware.js @@ -364,7 +364,7 @@ export default [ fetchEntryIssueCounts, fetchEntityCounts, setAvailableDatasets, - isFeatureEnabled('expectactionOutOfBoundsTask') ? fetchOutOfBoundsExpectations : noop, + isFeatureEnabled('expectationOutOfBoundsTask') ? fetchOutOfBoundsExpectations : noop, groupResourcesByDataset, groupIssuesCountsByDataset, groupEndpointsByDataset, diff --git a/src/routes/organisations.js b/src/routes/organisations.js index 047f77fc..5ea31281 100644 --- a/src/routes/organisations.js +++ b/src/routes/organisations.js @@ -6,6 +6,8 @@ const router = express.Router() router.get('/:lpa/:dataset/get-started', OrganisationsController.getStartedMiddleware) router.get('/:lpa/:dataset/overview', OrganisationsController.datasetOverviewMiddleware) router.get('/:lpa/:dataset/endpoint-error/:endpoint', OrganisationsController.datasetEndpointIssueMiddleware) +router.get('/:lpa/:dataset/expectation/:expectation', OrganisationsController.datasetFailedExpectationIssueMiddleware) +router.get('/:lpa/:dataset/expectation/:expectation/entity/:pageNumber?', OrganisationsController.datasetFailedExpectationIssueMiddleware) router.get('/:lpa/:dataset/data/:pageNumber', OrganisationsController.datasetDataviewMiddleware) router.get('/:lpa/:dataset/data', OrganisationsController.datasetDataviewMiddleware) router.get('/:lpa/:dataset/:issue_type/:issue_field/entity/:pageNumber', OrganisationsController.entityIssueDetailsMiddleware) diff --git a/src/routes/schemas.js b/src/routes/schemas.js index c55968a7..8d6b9b23 100644 --- a/src/routes/schemas.js +++ b/src/routes/schemas.js @@ -28,6 +28,20 @@ export const StartPage = v.object({ ...Base.entries }) +export const PaginationItem = v.variant('type', [ + v.strictObject({ + type: v.literal('number'), + number: v.pipe(v.number(), v.integer()), + href: v.string(), + current: v.boolean() + }), + v.strictObject({ + type: v.literal('ellipsis'), + ellipsis: v.literal(true), + href: v.string() + }) +]) + export const PaginationParams = v.optional(v.strictObject({ previous: v.optional(v.strictObject({ href: v.string() @@ -35,19 +49,7 @@ export const PaginationParams = v.optional(v.strictObject({ next: v.optional(v.strictObject({ href: v.string() })), - items: v.array(v.variant('type', [ - v.strictObject({ - type: v.literal('number'), - number: v.integer(), - href: v.string(), - current: v.boolean() - }), - v.strictObject({ - type: v.literal('ellipsis'), - ellipsis: v.literal(true), - href: v.string() - }) - ])) + items: v.array(PaginationItem) })) export const dataRangeParams = v.object({ @@ -63,7 +65,7 @@ export const errorSummaryParams = v.strictObject({ heading: v.optional(v.string()), items: v.array(v.strictObject({ html: v.string(), - href: v.url() + href: v.string() })) }) @@ -76,7 +78,7 @@ export const tableParams = v.strictObject({ error: v.optional(v.object({ message: v.string() })), - value: v.optional(v.string()), + value: v.nullish(v.string()), html: v.optional(v.string()), classes: v.optional(v.string()) }) @@ -105,7 +107,7 @@ export const DeadlineNoticeField = v.strictObject({ deadline: v.string() }) -const OrgField = v.strictObject({ name: NonEmptyString, organisation: NonEmptyString, statistical_geography: v.optional(v.string()), entity: v.optional(v.integer()) }) +const OrgField = v.strictObject({ name: NonEmptyString, organisation: NonEmptyString, statistical_geography: v.optional(v.string()), entity: v.optional(v.number()) }) const DatasetNameField = v.strictObject({ name: NonEmptyString, dataset: NonEmptyString, collection: NonEmptyString }) const DatasetItem = v.strictObject({ endpointCount: v.optional(v.number()), diff --git a/src/serverSetup/errorHandlers.js b/src/serverSetup/errorHandlers.js index e9fc00a5..75ec2825 100644 --- a/src/serverSetup/errorHandlers.js +++ b/src/serverSetup/errorHandlers.js @@ -10,7 +10,7 @@ export function setupErrorHandlers (app) { method: req.method, endpoint: req.originalUrl, message: 'error occurred', - error: JSON.stringify(err), + error: JSON.stringify(err).substring(0, 1000), errorMessage: err.message, errorStack: err.stack }) diff --git a/src/utils/custom-renderer.js b/src/utils/custom-renderer.js index 4f00fe6f..78d7c359 100644 --- a/src/utils/custom-renderer.js +++ b/src/utils/custom-renderer.js @@ -47,11 +47,12 @@ export const render = (renderer, template, schema, params) => { if (error instanceof v.ValiError) { // the below will only show up in the terminal when testing // console.debug({ params, message: 'failed validation input' }) + const paths = invalidSchemaPaths(error) logger.warn( validationErrorMessage(error, template), { errorMessage: `${error.message}`, - pathsWithIssues: invalidSchemaPaths(error), + pathsWithIssues: paths.length < 10 ? paths : paths.slice(0, 9), type: types.App } ) diff --git a/src/utils/pagination.js b/src/utils/pagination.js index bd9f717a..4e6ed4a6 100644 --- a/src/utils/pagination.js +++ b/src/utils/pagination.js @@ -1,3 +1,10 @@ +/** + * @module utils-pagination + * + * @description Utilities for dealing with pagination. + */ + +import * as v from 'valibot' const { min, max } = Math const range = (lo, hi) => Array.from({ length: hi - lo }, (_, i) => i + lo) @@ -36,3 +43,93 @@ export const pagination = (count, current, ellipsis = '...') => { ] return result } + +/** + * + * Turn items in array returned by {@link pagination} into items + * conforming to the PaginationItem schema. + * + * @param {number|string} paginationItem + * @param {Object} options options + * @param {number} options.pageNumber + * @param {Function} options.href pagination link factory function paginationItem => string + * @returns {Object} + */ +export const makeItem = (paginationItem, options) => { + const { pageNumber, href } = v.parse(v.object({ pageNumber: v.pipe(v.number(), v.integer()), href: v.function() }), options) + + if (typeof paginationItem === 'number') { + return { + type: 'number', + number: paginationItem, + href: href(paginationItem), + current: paginationItem === pageNumber + } + } else { + return { + type: 'ellipsis', + ellipsis: true, + href: '#' + } + } +} + +/** + * + * @param {Object} options + * @param {number} options.pageNumber + * @param {string} options.baseSubpath + * @param {Object} options.dataRange + * @param {Object} options.dataRange.maxPageNumber + * @returns {Object} pagination object for use in templates + */ +export const createPaginationTemplateParamsObject = (options) => { + const { pageNumber, baseSubpath, dataRange } = v.parse(v.object({ + pageNumber: v.pipe(v.number(), v.integer()), + baseSubpath: v.string(), + dataRange: v.object({ maxPageNumber: v.pipe(v.number(), v.integer(), v.minValue(1)) }) + }), options) + + /** + * @typedef {Object} PaginationItem + * @property {'ellipsis'|'number'} type - Type of pagination item + * @property {number} [number] - Page number (for number type) + * @property {string} href - Link URL + * @property {boolean} [ellipsis] - Whether this is an ellipsis item + * @property {boolean} [current] - Whether this is the current page + */ + + /** + * @typedef {Object} PaginationNav + * @property {Object} [previous] - Previous page link + * @property {string} previous.href - Previous page URL + * @property {Object} [next] - Next page link + * @property {string} next.href - Next page URL + * @property {PaginationItem[]} items - Pagination items + */ + + /** @type {PaginationNav} */ + const paginationObj = { + previous: undefined, + next: undefined, + items: [] + } + + if (pageNumber > 1) { + paginationObj.previous = { + href: `${baseSubpath}/${pageNumber - 1}` + } + } + if (pageNumber < dataRange.maxPageNumber) { + paginationObj.next = { + href: `${baseSubpath}/${pageNumber + 1}` + } + } + + for (const item of pagination(dataRange.maxPageNumber, Math.min(pageNumber, dataRange.maxPageNumber))) { + const itemObj = makeItem(item, { pageNumber, href: (pitem) => `${baseSubpath}/${pitem}` }) + paginationObj.items.push(itemObj) + } + + return paginationObj +} diff --git a/src/views/organisations/issueDetails.html b/src/views/organisations/issueDetails.html index 428391bd..5631e091 100644 --- a/src/views/organisations/issueDetails.html +++ b/src/views/organisations/issueDetails.html @@ -3,6 +3,7 @@ {% from "govuk/components/pagination/macro.njk" import govukPagination %} {% from "govuk/components/breadcrumbs/macro.njk" import govukBreadcrumbs %} +{% set isExpectation = issueType === 'expectation' %} {% set navigationItems = currentPath | getNavigationLinks(['organisations', 'guidance']) %} {% set serviceType = 'Submit'%} {% if issueEntitiesCount > 1 %} @@ -50,14 +51,16 @@ {% block content %} -
-
- {{ govukBackLink({ - text: "Back to dataset table", - href: '/organisations/' + organisation.organisation + '/' + dataset.dataset + '/' + issueType + '/' + issueField | urlencode - }) }} +{%if not isExpectation %} +
+
+ {{ govukBackLink({ + text: "Back to dataset table", + href: '/organisations/' + organisation.organisation + '/' + dataset.dataset + '/' + issueType + '/' + issueField | urlencode + }) }} +
-
+{% endif %}
{% include "includes/_dataset-page-header.html" %} diff --git a/test/unit/middleware/datasetTaskList.middleware.test.js b/test/unit/middleware/datasetTaskList.middleware.test.js index 3c626065..7759467b 100644 --- a/test/unit/middleware/datasetTaskList.middleware.test.js +++ b/test/unit/middleware/datasetTaskList.middleware.test.js @@ -274,7 +274,7 @@ describe('datasetTaskList.middleware.js', () => { expect(req.taskList.length).toBe(1) const { href, status: { tag: { text } } } = req.taskList[0] - expect(href).toBe('') + expect(href).toMatch('/organisations/some-lpa/some-dataset/expectation/out-of-bounds') expect(text).toBe('Needs fixing') expect(next).toHaveBeenCalledTimes(1) From 3792075bb7faba9aa3d4ec0b0b3b0b23fe834e7d Mon Sep 17 00:00:00 2001 From: Roland Sadowski Date: Wed, 12 Mar 2025 14:25:10 +0000 Subject: [PATCH 2/2] module rename --- src/controllers/OrganisationsController.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/OrganisationsController.js b/src/controllers/OrganisationsController.js index 4d7359b9..a6da3fd5 100644 --- a/src/controllers/OrganisationsController.js +++ b/src/controllers/OrganisationsController.js @@ -8,7 +8,7 @@ import getStartedMiddleware from '../middleware/getStarted.middleware.js' import overviewMiddleware from '../middleware/lpa-overview.middleware.js' import datasetDataviewMiddleware from '../middleware/dataview.middleware.js' import datasetEndpointIssueMiddleware from '../middleware/datasetEndpointIssue.middleware.js' -import datasetFailedExpectationIssueMiddleware from '../middleware/dataset-failed-expectation-issue.middleware.js' +import datasetFailedExpectationIssueMiddleware from '../middleware/dataset-failed-expectation-details.middleware.js' const organisationsController = { organisationsMiddleware,