diff --git a/package-lock.json b/package-lock.json index df84ad42..480412e6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29,6 +29,7 @@ "hmpo-form-wizard": "^13.0.0", "hmpo-i18n": "^6.0.1", "js-yaml": "^4.1.0", + "json5": "^2.2.3", "lodash": "^4.17.21", "maplibre-gl": "^4.1.0", "multer": "^1.4.5-lts.1", @@ -8744,7 +8745,8 @@ }, "node_modules/json5": { "version": "2.2.3", - "license": "MIT", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "bin": { "json5": "lib/cli.js" }, diff --git a/package.json b/package.json index bb2d3adb..3db23dd3 100644 --- a/package.json +++ b/package.json @@ -76,6 +76,7 @@ "hmpo-form-wizard": "^13.0.0", "hmpo-i18n": "^6.0.1", "js-yaml": "^4.1.0", + "json5": "^2.2.3", "lodash": "^4.17.21", "maplibre-gl": "^4.1.0", "multer": "^1.4.5-lts.1", diff --git a/src/assets/scss/index.scss b/src/assets/scss/index.scss index 1a344e23..5203260e 100644 --- a/src/assets/scss/index.scss +++ b/src/assets/scss/index.scss @@ -108,4 +108,8 @@ $govuk-global-styles: true; code, code * { font-family: monospace; -} \ No newline at end of file +} + +.padding-top { + padding-top: 40px; +} diff --git a/src/middleware/common.middleware.js b/src/middleware/common.middleware.js index 94b49ac2..97e480ec 100644 --- a/src/middleware/common.middleware.js +++ b/src/middleware/common.middleware.js @@ -27,7 +27,7 @@ export const logPageError = (err, req, res, next) => { export const fetchDatasetInfo = fetchOne({ query: ({ params }) => { - return `SELECT name, dataset FROM dataset WHERE dataset = '${params.dataset}'` + return `SELECT name, dataset, collection FROM dataset WHERE dataset = '${params.dataset}'` }, result: 'dataset' }) @@ -64,7 +64,7 @@ export const fetchEntityCount = fetchOne({ export const fetchOrgInfo = fetchOne({ query: ({ params }) => { - return `SELECT name, organisation, statistical_geography FROM organisation WHERE organisation = '${params.lpa}'` + return `SELECT name, organisation, entity, statistical_geography FROM organisation WHERE organisation = '${params.lpa}'` }, result: 'orgInfo' }) diff --git a/src/middleware/datasetOverview.middleware.js b/src/middleware/datasetOverview.middleware.js index e9b50290..10e8fe81 100644 --- a/src/middleware/datasetOverview.middleware.js +++ b/src/middleware/datasetOverview.middleware.js @@ -1,20 +1,109 @@ import { fetchDatasetInfo, fetchLatestResource, fetchLpaDatasetIssues, fetchOrgInfo, isResourceAccessible, isResourceIdInParams, logPageError, takeResourceIdFromParams } from './common.middleware.js' -import { fetchIf, parallel, renderTemplate } from './middleware.builders.js' -import { getDatasetStats } from '../services/DatasetService.js' +import { fetchOne, fetchIf, fetchMany, parallel, renderTemplate, FetchOptions } from './middleware.builders.js' import { fetchResourceStatus } from './datasetTaskList.middleware.js' +import performanceDbApi from '../services/performanceDbApi.js' +import json5 from 'json5' -const fetchDatasetStats = async (req, res, next) => { - req.stats = await getDatasetStats(req.params.dataset, req.params.lpa) +const fetchColumnSummary = fetchMany({ + query: ({ params }) => `select * from column_field_summary + where resource != '' + and pipeline = '${params.dataset}' + AND organisation = '${params.lpa}' + limit 1000`, + result: 'columnSummary', + dataset: FetchOptions.performanceDb +}) + +const fetchSpecification = fetchOne({ + query: ({ req }) => `select * from specification WHERE specification = '${req.dataset.collection}'`, + result: 'specification' +}) + +export const pullOutDatasetSpecification = (req, res, next) => { + const { specification } = req + const collectionSpecifications = json5.parse(specification.json) + const datasetSpecification = collectionSpecifications.find((spec) => spec.dataset === req.dataset.dataset) + req.specification = datasetSpecification + next() +} + +const fetchSources = fetchMany({ + query: ({ params }) => ` + select rhe.endpoint, rhe.endpoint_url, rhe.status, rhe.exception, rhe.resource, rhe.latest_log_entry_date, rhe.endpoint_entry_date, rhe.endpoint_end_date, rhe.resource_start_date, rhe.resource_end_date, s.documentation_url + from reporting_historic_endpoints rhe + LEFT JOIN source s ON rhe.endpoint = s.endpoint + where REPLACE(rhe.organisation, '-eng', '') = '${params.lpa}' and rhe.pipeline = '${params.dataset}' + AND (rhe.resource_end_date >= current_timestamp OR rhe.resource_end_date is null) + `, + result: 'sources' +}) + +const fetchEntityCount = fetchOne({ + query: ({ req }) => performanceDbApi.entityCountQuery(req.orgInfo.entity), + result: 'entityCount', + dataset: FetchOptions.fromParams +}) + +export const prepareDatasetOverviewTemplateParams = (req, res, next) => { + const { orgInfo, specification, columnSummary, entityCount, sources, dataset } = req + + const matchingFields = columnSummary[0].matching_field?.split(',') ?? [] + const nonMatchingFields = columnSummary[0].non_matching_field?.split(',') ?? [] + const allFields = [...matchingFields, ...nonMatchingFields] + + const numberOfFieldsSupplied = specification.fields.map(field => field.field).reduce((acc, current) => { + return allFields.includes(current) ? acc + 1 : acc + }, 0) + + const numberOfFieldsMatched = specification.fields.map(field => field.field).reduce((acc, current) => { + return matchingFields.includes(current) ? acc + 1 : acc + }, 0) + + const numberOfExpectedFields = specification.fields.length + + // I'm pretty sure every endpoint has a separate documentation-url, but this isn't currently represented in the performance db. need to double check this and update if so + const endpoints = sources.sort((a, b) => { + if (a.status >= 200 && a.status < 300) return -1 + if (b.status >= 200 && b.status < 300) return 1 + return 0 + }).map((source, index) => { + let error + + if (parseInt(source.status) < 200 || parseInt(source.status) >= 300) { + error = { + code: parseInt(source.status), + exception: source.exception + } + } + + return { + name: `Data Url ${index}`, + endpoint: source.endpoint_url, + documentation_url: source.documentation_url, + lastAccessed: source.latest_log_entry_date, + lastUpdated: source.endpoint_entry_date, // not sure if this is the lastupdated + error + } + }) + + req.templateParams = { + organisation: orgInfo, + dataset, + stats: { + numberOfFieldsSupplied: numberOfFieldsSupplied ?? 0, + numberOfFieldsMatched: numberOfFieldsMatched ?? 0, + numberOfExpectedFields: numberOfExpectedFields ?? 0, + numberOfRecords: entityCount.entity_count, + endpoints + } + } next() } const getDatasetOverview = renderTemplate( { - templateParams (req) { - const { orgInfo: organisation, dataset, stats, issues } = req - return { organisation, dataset, stats, issueCount: issues.length } - }, + templateParams: (req) => req.templateParams, template: 'organisations/dataset-overview.html', handlerName: 'datasetOverview' } @@ -25,10 +114,17 @@ export default [ fetchOrgInfo, fetchDatasetInfo ]), - fetchResourceStatus, - fetchIf(isResourceIdInParams, fetchLatestResource, takeResourceIdFromParams), + parallel([ + fetchColumnSummary, + fetchResourceStatus, + fetchIf(isResourceIdInParams, fetchLatestResource, takeResourceIdFromParams) + ]), fetchIf(isResourceAccessible, fetchLpaDatasetIssues), - fetchDatasetStats, + fetchSpecification, + pullOutDatasetSpecification, + fetchSources, + fetchEntityCount, + prepareDatasetOverviewTemplateParams, getDatasetOverview, logPageError ] diff --git a/src/middleware/issueDetails.middleware.js b/src/middleware/issueDetails.middleware.js index 3fee650b..62693b87 100644 --- a/src/middleware/issueDetails.middleware.js +++ b/src/middleware/issueDetails.middleware.js @@ -86,8 +86,7 @@ async function fetchEntry (req, res, next) { // look at issue Entries and get the index of that entry - 1 - const entityNum = - Object.values(issuesByEntryNumber)[pageNum - 1][0].entry_number + const entityNum = Object.values(issuesByEntryNumber)[pageNum - 1][0].entry_number req.entryData = await performanceDbApi.getEntry( req.resource.resource, diff --git a/src/middleware/middleware.builders.js b/src/middleware/middleware.builders.js index 3340afd7..f641fc35 100644 --- a/src/middleware/middleware.builders.js +++ b/src/middleware/middleware.builders.js @@ -18,7 +18,8 @@ export const FetchOptions = { /** * Use 'dataset' from requets params. */ - fromParams: Symbol('from-params') + fromParams: Symbol('from-params'), + performanceDb: Symbol('performance-database') } const datasetOverride = (val, req) => { @@ -28,6 +29,8 @@ const datasetOverride = (val, req) => { if (val === FetchOptions.fromParams) { console.assert(req.params.dataset, 'no "dataset" in request params') return req.params.dataset + } else if (val === FetchOptions.performanceDb) { + return 'performance' } else { return val(req) } diff --git a/src/routes/schemas.js b/src/routes/schemas.js index feb1308f..a9484b0a 100644 --- a/src/routes/schemas.js +++ b/src/routes/schemas.js @@ -38,8 +38,8 @@ const datasetStatusEnum = { 'Not submitted': 'Not submitted' } -const OrgField = v.strictObject({ name: NonEmptyString, organisation: NonEmptyString, statistical_geography: v.optional(NonEmptyString) }) -const DatasetNameField = v.strictObject({ name: NonEmptyString, dataset: NonEmptyString }) +const OrgField = v.strictObject({ name: NonEmptyString, organisation: NonEmptyString, statistical_geography: v.optional(v.string()), entity: v.optional(v.integer()) }) +const DatasetNameField = v.strictObject({ name: NonEmptyString, dataset: NonEmptyString, collection: NonEmptyString }) export const OrgOverviewPage = v.strictObject({ organisation: OrgField, @@ -69,15 +69,24 @@ export const OrgGetStarted = v.strictObject({ export const OrgDatasetOverview = v.strictObject({ organisation: OrgField, - dataset: v.strictObject({ - name: NonEmptyString, - dataset: NonEmptyString - }), + dataset: DatasetNameField, stats: v.strictObject({ numberOfRecords: v.integer(), - numberOfFieldsSupplied: v.integer() - }), - issueCount: v.integer() + numberOfFieldsSupplied: v.integer(), + numberOfFieldsMatched: v.integer(), + numberOfExpectedFields: v.integer(), + endpoints: v.array(v.strictObject({ + name: v.string(), + documentation_url: v.optional(v.string()), + endpoint: v.string(), + lastAccessed: v.string(), + lastUpdated: v.string(), + error: v.optional(v.strictObject({ + code: v.integer(), + exception: v.string() + })) + })) + }) }) export const OrgDatasetTaskList = v.strictObject({ @@ -94,16 +103,14 @@ export const OrgDatasetTaskList = v.strictObject({ organisation: OrgField, dataset: v.strictObject({ dataset: v.optional(NonEmptyString), - name: NonEmptyString + name: NonEmptyString, + collection: NonEmptyString }) }) export const OrgEndpointError = v.strictObject({ organisation: OrgField, - dataset: v.object({ - name: NonEmptyString, - dataset: NonEmptyString - }), + dataset: DatasetNameField, errorData: v.strictObject({ endpoint_url: v.url(), http_status: v.integer(), @@ -179,10 +186,7 @@ export const ChooseDataset = v.strictObject({ export const DatasetDetails = v.strictObject({ organisation: OrgField, - dataset: v.strictObject({ - name: NonEmptyString, - dataset: NonEmptyString - }), + dataset: DatasetNameField, values: v.strictObject({ dataset: NonEmptyString }), diff --git a/src/services/DatasetService.js b/src/services/DatasetService.js deleted file mode 100644 index cf9b5067..00000000 --- a/src/services/DatasetService.js +++ /dev/null @@ -1,58 +0,0 @@ -import datasette from './datasette.js' -import logger from '../utils/logger.js' -import performanceDbApi from './performanceDbApi.js' - -async function getDatasetStatsForResourceId (dataset, resourceId) { - const sql = ` - SELECT 'numberOfRecords' AS metric, COUNT(*) AS value - FROM - ( - SELECT - * - FROM - fact_resource fr - WHERE - fr.resource = '${resourceId}' - GROUP BY - entry_number - ) - UNION ALL - SELECT 'numberOfFieldsSupplied' AS metric, COUNT(*) AS value - FROM - ( - SELECT - * - FROM - fact_resource fr - WHERE - fr.resource = '${resourceId}' - )` - - const { formattedData } = await datasette.runQuery(sql, dataset) - - return formattedData -} - -export async function getDatasetStats (dataset, lpa) { - try { - const stats = {} - const { resource: resourceId } = await performanceDbApi.getLatestResource(lpa, dataset) - const metrics = await getDatasetStatsForResourceId(dataset, resourceId) - - metrics.forEach(({ metric, value }) => { - stats[metric] = value - }) - - return stats - } catch (error) { - logger.warn( - `DatasetService.getDatasetStats(): Error getting dataset stats for ${lpa} in ${dataset}`, - { - errorMessage: error.message, - errorStack: error.stack - } - ) - - return {} - } -} diff --git a/src/services/performanceDbApi.js b/src/services/performanceDbApi.js index 602f182c..6b6de303 100644 --- a/src/services/performanceDbApi.js +++ b/src/services/performanceDbApi.js @@ -383,23 +383,23 @@ ORDER BY return result.formattedData }, - entityCountQuery (resource) { + entityCountQuery (orgEntity) { return /* sql */ ` - select dataset, entity_count, resource - from dataset_resource - WHERE resource = '${resource}' - ` + select count(entity) as entity_count + from entity + WHERE organisation_entity = '${orgEntity}' + ` }, /** - * Retrieves the entity count for a given resource and dataset. - * - * @param {string} resource - The resource to retrieve the entity count for. - * @param {string} dataset - The dataset to retrieve the entity count from. - * @returns {Promise} The entity count for the given resource and dataset. - */ - async getEntityCount (resource, dataset) { - const query = this.entityCountQuery(resource) + * Retrieves the entity count for a given organisation and dataset. + * + * @param {string} orgEntity - The organisation entity to retrieve the entity count for. + * @param {string} dataset - The dataset to retrieve the entity count from. + * @returns {number} The entity count for the given resource and dataset. + */ + async getEntityCount (orgEntity, dataset) { + const query = this.entityCountQuery(orgEntity) const result = await datasette.runQuery(query, dataset) return result.formattedData[0].entity_count } diff --git a/src/views/organisations/dataset-overview.html b/src/views/organisations/dataset-overview.html index b38f9cf2..6a462d78 100644 --- a/src/views/organisations/dataset-overview.html +++ b/src/views/organisations/dataset-overview.html @@ -17,6 +17,8 @@ {{organisation.name}} - {{dataset.name}} - Dataset overview {% endset %} +{% set urlStyle = 'text-overflow: ellipsis; overflow: hidden; white-space: nowrap; width: 400px; display: block;' %} + {% block beforeContent %} {{ super() }} @@ -43,6 +45,102 @@ {% endblock %} +{% set rows = [ + { + key: { + text: "Number of records" + }, + value: { + text: stats.numberOfRecords | default(0) + } + }, + { + key: { + text: "Number of fields supplied" + }, + value: { + text: stats.numberOfFieldsSupplied + '/' + stats.numberOfExpectedFields | default(0) + } + }, + { + key: { + text: "Number of fields matched" + }, + value: { + text: stats.numberOfFieldsMatched + '/' + stats.numberOfExpectedFields | default(0) + } + }, + { + key: { + text: "Licence" + }, + value: { + text: "Open Government Licence" + } + } +] %} + +{% for endpoint in stats.endpoints %} + {% set endpointRow = { + key: { + text: 'Data URL', + classes: 'padding-top' + }, + value: { + html: ''+endpoint.endpoint+'' + }, + classes: 'padding-top' + } %} + {{ rows.push(endpointRow) }} + + {% if endpoint.documentation_url %} + {% set documentationRow = { + key: { + text: 'Documentation URL' + }, + value: { + html: ''+endpoint.documentation_url+'' + } + } %}p + {{ rows.push(documentationRow) }} + {% endif %} + + {% if endpoint.error %} + {% set lastAccessedRow = { + key: { + text: 'Data URL last accessed', + classes: 'app-inset-text---error' + }, + value: { + html: (endpoint.lastAccessed | govukDateTime) + '

There was a '+endpoint.error.code+' error accessing the data URL

' + } + } %} + {% else %} + {% set lastAccessedRow = { + key: { + text: 'Data URL last accessed' + }, + value: { + text: endpoint.lastAccessed | govukDateTime + } + } %} + {% endif %} + + + {{ rows.push(lastAccessedRow) }} + + {% set lastUpdatedRow = { + key: { + text: 'Data URL last updated' + }, + value: { + text: endpoint.lastUpdated | govukDateTime + } + } %} + {{ rows.push(lastUpdatedRow) }} + +{% endfor %} + {% block content %}
@@ -73,34 +171,7 @@

Map of dataset

Dataset details

- {{ govukSummaryList({ - rows: [ - { - key: { - text: "Number of records" - }, - value: { - text: stats.numberOfRecords | default(0) - } - }, - { - key: { - text: "Number of fields supplied" - }, - value: { - text: stats.numberOfFieldsSupplied | default(0) - } - }, - { - key: { - text: "License" - }, - value: { - text: "Open Government Licence" - } - } - ] - }) }} + {{ govukSummaryList({ rows: rows }) }}
diff --git a/test/unit/dataset-details.test.js b/test/unit/dataset-details.test.js index add2d819..5d802230 100644 --- a/test/unit/dataset-details.test.js +++ b/test/unit/dataset-details.test.js @@ -46,7 +46,8 @@ describe('dataset details View', () => { }, dataset: { name: 'mock dataset', - dataset: 'mock-dataset' + dataset: 'mock-dataset', + collection: 'mock-collection' }, values: { dataset: 'mockDataset' diff --git a/test/unit/http-errorPage.test.js b/test/unit/http-errorPage.test.js index d908019c..19536ea9 100644 --- a/test/unit/http-errorPage.test.js +++ b/test/unit/http-errorPage.test.js @@ -13,7 +13,8 @@ describe('http-error.html', () => { }, dataset: { name: 'mock-dataset', - dataset: 'mock-dataset' + dataset: 'mock-dataset', + collection: 'mock-collection' }, errorData: { endpoint_url: 'https://example.com/data-url', diff --git a/test/unit/middleware/datasetOverview.middleware.test.js b/test/unit/middleware/datasetOverview.middleware.test.js new file mode 100644 index 00000000..4807205b --- /dev/null +++ b/test/unit/middleware/datasetOverview.middleware.test.js @@ -0,0 +1,63 @@ +import { describe, it, expect } from 'vitest' +import { prepareDatasetOverviewTemplateParams, pullOutDatasetSpecification } from '../../../src/middleware/datasetOverview.middleware.js' + +describe('Dataset Overview Middleware', () => { + const req = { + params: { + lpa: 'mock-lpa', + dataset: 'mock-dataset' + }, + dataset: { + name: 'mock dataset', + dataset: 'mock-dataset', + collection: 'mock-collection' + } + } + const res = {} + + describe('pullOutDatasetSpecification', () => { + it('', () => { + const reqWithSpecification = { + ...req, + specification: { + json: JSON.stringify([ + { dataset: 'mock-dataset', foo: 'bar' } + ]) + } + } + pullOutDatasetSpecification(reqWithSpecification, res, () => {}) + expect(reqWithSpecification.specification).toEqual({ dataset: 'mock-dataset', foo: 'bar' }) + }) + }) + + describe('prepareDatasetOverviewTemplateParams', () => { + it('should prepare template params for dataset overview', async () => { + const reqWithResults = { + ...req, + orgInfo: { name: 'mock-org' }, + specification: { fields: [{ field: 'field1' }, { field: 'field2' }] }, + columnSummary: [{ matching_field: 'field1', non_matching_field: 'field3' }], + entityCount: { entity_count: 10 }, + sources: [ + { endpoint_url: 'endpoint1', documentation_url: 'doc-url1', status: '200', endpoint_entry_date: 'LU1', latest_log_entry_date: 'LA1' }, + { endpoint_url: 'endpoint2', documentation_url: 'doc-url2', status: '404', exception: 'exception', endpoint_entry_date: 'LU2', latest_log_entry_date: 'LA2' } + ] + } + prepareDatasetOverviewTemplateParams(reqWithResults, res, () => {}) + expect(reqWithResults.templateParams).toEqual({ + organisation: { name: 'mock-org' }, + dataset: reqWithResults.dataset, + stats: { + numberOfFieldsSupplied: 1, + numberOfFieldsMatched: 1, + numberOfExpectedFields: 2, + numberOfRecords: 10, + endpoints: [ + { name: 'Data Url 0', endpoint: 'endpoint1', documentation_url: 'doc-url1', error: undefined, lastAccessed: 'LA1', lastUpdated: 'LU1' }, + { name: 'Data Url 1', endpoint: 'endpoint2', documentation_url: 'doc-url2', error: { code: 404, exception: 'exception' }, lastAccessed: 'LA2', lastUpdated: 'LU2' } + ] + } + }) + }) + }) +}) diff --git a/test/unit/middleware/getStarted.middleware.test.js b/test/unit/middleware/getStarted.middleware.test.js index 5a31ec96..9edb1eef 100644 --- a/test/unit/middleware/getStarted.middleware.test.js +++ b/test/unit/middleware/getStarted.middleware.test.js @@ -7,7 +7,7 @@ describe('get-started', () => { { name: 'Example LPA', organisation: 'LPA' } ] } - const exampleDataset = { name: 'Example Dataset', dataset: 'example-dataset' } + const exampleDataset = { name: 'Example Dataset', dataset: 'example-dataset', collection: 'example-collection' } it('should render the get-started template with the correct params', async () => { const req = { diff --git a/test/unit/middleware/issueDetails.middleware.test.js b/test/unit/middleware/issueDetails.middleware.test.js index 9e79e561..befa4b24 100644 --- a/test/unit/middleware/issueDetails.middleware.test.js +++ b/test/unit/middleware/issueDetails.middleware.test.js @@ -8,7 +8,7 @@ vi.mock('../../../src/services/performanceDbApi.js') describe('issueDetails.middleware.js', () => { const orgInfo = { name: 'mock lpa', organisation: 'ORG' } - const dataset = { name: 'mock dataset', dataset: 'mock-dataset' } + const dataset = { name: 'mock dataset', dataset: 'mock-dataset', collection: 'mock-collection' } const entryData = [ { field: 'start-date', @@ -76,7 +76,8 @@ describe('issueDetails.middleware.js', () => { }, dataset: { name: 'mock dataset', - dataset: 'mock-dataset' + dataset: 'mock-dataset', + collection: 'mock-collection' }, errorHeading: 'mockMessageFor: 0', issueItems: [ @@ -175,7 +176,8 @@ describe('issueDetails.middleware.js', () => { }, dataset: { name: 'mock dataset', - dataset: 'mock-dataset' + dataset: 'mock-dataset', + collection: 'mock-collection' }, errorHeading: 'mockMessageFor: 0', issueItems: [ @@ -231,7 +233,8 @@ describe('issueDetails.middleware.js', () => { }, dataset: { name: 'mock dataset', - dataset: 'mock-dataset' + dataset: 'mock-dataset', + collection: 'mock-collection' }, errorHeading: 'mockMessageFor: 0', issueItems: [ diff --git a/test/unit/services/DatasetService.test.js b/test/unit/services/DatasetService.test.js deleted file mode 100644 index 694b902f..00000000 --- a/test/unit/services/DatasetService.test.js +++ /dev/null @@ -1,30 +0,0 @@ -import { describe, it, expect, vi } from 'vitest' -import { getDatasetStats } from '../../../src/services/DatasetService.js' -import datasette from '../../../src/services/datasette' - -vi.mock('../../../src/services/datasette') - -describe('DatasetService', () => { - describe('getDatasetStats', () => { - it('should return dataset stats for a given LPA', async () => { - const mockStats = [ - { metric: 'numberOfRecords', value: 10 }, - { metric: 'numberOfFieldsSupplied', value: 5 } - ] - datasette.runQuery.mockResolvedValue({ formattedData: mockStats }) - - const result = await getDatasetStats('dataset1', 'lpa1') - expect(result).toEqual({ - numberOfRecords: 10, - numberOfFieldsSupplied: 5 - }) - }) - - it('should return an empty object if an error occurs', async () => { - datasette.runQuery.mockRejectedValue(new Error('Test error')) - - const result = await getDatasetStats('dataset1', 'lpa1') - expect(result).toEqual({}) - }) - }) -}) diff --git a/test/unit/views/organisations/dataset-overview.test.js b/test/unit/views/organisations/dataset-overview.test.js index 33594824..bbb5c1bb 100644 --- a/test/unit/views/organisations/dataset-overview.test.js +++ b/test/unit/views/organisations/dataset-overview.test.js @@ -4,6 +4,9 @@ import { runGenericPageTests } from '../../generic-page.js' import { stripWhitespace } from '../../../utils/stripWhiteSpace.js' import { setupNunjucks } from '../../../../src/serverSetup/nunjucks.js' +import xGovFilters from '@x-govuk/govuk-prototype-filters' +const { govukDateTime } = xGovFilters + describe('Dataset Overview Page', () => { const params = { organisation: { @@ -12,11 +15,33 @@ describe('Dataset Overview Page', () => { }, dataset: { dataset: 'world-heritage-site-buffer-zone', - name: 'World heritage site buffer zone' + name: 'World heritage site buffer zone', + collection: 'world-heritage-site' }, stats: { numberOfRecords: 10, - numberOfFieldsSupplied: 5 + numberOfFieldsSupplied: 5, + numberOfFieldsMatched: 6, + numberOfExpectedFields: 10, + endpoints: [ + { + name: 'endpoint 1', + endpoint: 'http://endpoint1.co.uk', + documentation_url: 'http://endpoint1-docs.co.uk', + lastAccessed: '2024-09-09', + lastUpdated: '2024-09-09' + }, + { + name: 'endpoint 2', + endpoint: 'http://endpoint2.co.uk', + lastAccessed: '2024-19-19', + lastUpdated: '2024-19-19', + error: { + code: 404, + exception: '' + } + } + ] } } @@ -47,9 +72,18 @@ describe('Dataset Overview Page', () => { it('Renders dataset details correctly', () => { expect(document.querySelector('h2.govuk-heading-m').textContent).toContain('Dataset details') const summaryListValues = document.querySelectorAll('dd.govuk-summary-list__value') - expect(summaryListValues[0].textContent.trim()).toEqual('10') - expect(summaryListValues[1].textContent.trim()).toEqual('5') - expect(summaryListValues[2].textContent.trim()).toEqual('Open Government Licence') + expect(summaryListValues[0].textContent.trim()).toEqual(params.stats.numberOfRecords.toString()) + expect(summaryListValues[1].textContent.trim()).toEqual(`${params.stats.numberOfFieldsSupplied}/${params.stats.numberOfExpectedFields}`) + expect(summaryListValues[2].textContent.trim()).toEqual(`${params.stats.numberOfFieldsMatched}/${params.stats.numberOfExpectedFields}`) + expect(summaryListValues[3].textContent.trim()).toEqual('Open Government Licence') + expect(summaryListValues[4].textContent).toContain(params.stats.endpoints[0].endpoint) + expect(summaryListValues[5].textContent).toContain(params.stats.endpoints[0].documentation_url) + expect(summaryListValues[6].textContent).toContain(govukDateTime(params.stats.endpoints[0].lastAccessed)) + expect(summaryListValues[7].textContent).toContain(govukDateTime(params.stats.endpoints[0].lastUpdated)) + expect(summaryListValues[8].textContent).toContain(params.stats.endpoints[1].endpoint) + expect(summaryListValues[9].textContent).toContain(govukDateTime(params.stats.endpoints[1].lastAccessed)) + expect(summaryListValues[9].textContent).toContain(params.stats.endpoints[1].error.code) + expect(summaryListValues[10].textContent).toContain(govukDateTime(params.stats.endpoints[1].lastUpdated)) }) it('Renders breadcrumbs correctly', () => { @@ -70,7 +104,8 @@ describe('Dataset Overview Page', () => { ...params, dataset: { dataset: 'article-4-direction-area', - name: 'Article 4 direction area' + name: 'Article 4 direction area', + collection: 'article-4-direction' } } const htmlWithGeometries = stripWhitespace(nunjucks.render('organisations/dataset-overview.html', paramsWithGeometries))